@sherwoodagent/cli 0.3.6 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chat-KTV3JZRT.js → chat-YHUZPD54.js} +8 -14
- package/dist/chat-YHUZPD54.js.map +1 -0
- package/dist/index.js +2 -2
- package/dist/{xmtp-UZVGWSFM.js → xmtp-BQAJYBMY.js} +38 -6
- package/dist/xmtp-BQAJYBMY.js.map +1 -0
- package/package.json +1 -1
- package/dist/chat-KTV3JZRT.js.map +0 -1
- package/dist/xmtp-UZVGWSFM.js.map +0 -1
|
@@ -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-
|
|
14
|
+
return import("./xmtp-BQAJYBMY.js");
|
|
15
15
|
}
|
|
16
16
|
function formatTimestamp(date) {
|
|
17
17
|
return `${date.getHours().toString().padStart(2, "0")}:${date.getMinutes().toString().padStart(2, "0")}`;
|
|
@@ -100,8 +100,7 @@ async function handleSend(name, message, markdown) {
|
|
|
100
100
|
const spinner = ora("Sending...").start();
|
|
101
101
|
try {
|
|
102
102
|
const xmtp = await loadXmtp();
|
|
103
|
-
const
|
|
104
|
-
const group = await xmtp.getGroup(client, name);
|
|
103
|
+
const group = await xmtp.getGroup("", name);
|
|
105
104
|
if (markdown) {
|
|
106
105
|
await xmtp.sendMarkdown(group, message);
|
|
107
106
|
} else {
|
|
@@ -124,8 +123,7 @@ async function handleReact(name, messageId, emoji) {
|
|
|
124
123
|
const spinner = ora("Reacting...").start();
|
|
125
124
|
try {
|
|
126
125
|
const xmtp = await loadXmtp();
|
|
127
|
-
const
|
|
128
|
-
const group = await xmtp.getGroup(client, name);
|
|
126
|
+
const group = await xmtp.getGroup("", name);
|
|
129
127
|
await xmtp.sendReaction(group, messageId, emoji);
|
|
130
128
|
spinner.succeed(`Reacted ${emoji}`);
|
|
131
129
|
} catch (err) {
|
|
@@ -138,8 +136,7 @@ async function handleLog(name, limit) {
|
|
|
138
136
|
const spinner = ora("Loading messages...").start();
|
|
139
137
|
try {
|
|
140
138
|
const xmtp = await loadXmtp();
|
|
141
|
-
const
|
|
142
|
-
const group = await xmtp.getGroup(client, name);
|
|
139
|
+
const group = await xmtp.getGroup("", name);
|
|
143
140
|
const messages = await xmtp.getRecentMessages(group, limit);
|
|
144
141
|
spinner.stop();
|
|
145
142
|
console.log();
|
|
@@ -163,8 +160,7 @@ async function handleMembers(name) {
|
|
|
163
160
|
const spinner = ora("Loading members...").start();
|
|
164
161
|
try {
|
|
165
162
|
const xmtp = await loadXmtp();
|
|
166
|
-
const
|
|
167
|
-
const group = await xmtp.getGroup(client, name);
|
|
163
|
+
const group = await xmtp.getGroup("", name);
|
|
168
164
|
const members = await xmtp.getMembers(group);
|
|
169
165
|
spinner.stop();
|
|
170
166
|
console.log();
|
|
@@ -187,8 +183,7 @@ async function handleAdd(name, address) {
|
|
|
187
183
|
const spinner = ora("Adding member...").start();
|
|
188
184
|
try {
|
|
189
185
|
const xmtp = await loadXmtp();
|
|
190
|
-
const
|
|
191
|
-
const group = await xmtp.getGroup(client, name);
|
|
186
|
+
const group = await xmtp.getGroup("", name);
|
|
192
187
|
await xmtp.addMember(group, address);
|
|
193
188
|
await xmtp.sendEnvelope(group, {
|
|
194
189
|
type: "MEMBER_JOIN",
|
|
@@ -207,8 +202,7 @@ async function handleRemove(name, address) {
|
|
|
207
202
|
const spinner = ora("Removing member...").start();
|
|
208
203
|
try {
|
|
209
204
|
const xmtp = await loadXmtp();
|
|
210
|
-
const
|
|
211
|
-
const group = await xmtp.getGroup(client, name);
|
|
205
|
+
const group = await xmtp.getGroup("", name);
|
|
212
206
|
await xmtp.removeMember(group, address);
|
|
213
207
|
spinner.succeed(`Member removed: ${address}`);
|
|
214
208
|
} catch (err) {
|
|
@@ -313,4 +307,4 @@ function registerChatCommands(program) {
|
|
|
313
307
|
export {
|
|
314
308
|
registerChatCommands
|
|
315
309
|
};
|
|
316
|
-
//# sourceMappingURL=chat-
|
|
310
|
+
//# sourceMappingURL=chat-YHUZPD54.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/chat.ts"],"sourcesContent":["/**\n * Chat commands — sherwood chat <syndicate-name> [action] [args...]\n *\n * Uses XMTP for encrypted group messaging tied to syndicates.\n * Shells out to the @xmtp/cli binary for all XMTP operations.\n *\n * Commander can't dispatch subcommands when the parent has a positional <name> arg\n * (it always runs the parent action). So we use manual dispatch: a single .action()\n * that routes based on the [action] argument.\n *\n * Usage:\n * sherwood chat <name> — stream messages (default)\n * sherwood chat <name> send \"hello\" — send a text message\n * sherwood chat <name> send \"hello\" --markdown — send formatted markdown\n * sherwood chat <name> react <id> <emoji> — react to a message\n * sherwood chat <name> log [--limit 50] — show recent messages\n * sherwood chat <name> members — list group members\n * sherwood chat <name> add <address> — add member (creator only)\n * sherwood chat <name> init [--force] — create XMTP group + write ENS record\n */\n\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport { getAccount } from \"../lib/client.js\";\nimport { resolveSyndicate, setTextRecord, getTextRecord } from \"../lib/ens.js\";\nimport { cacheGroupId, getCachedGroupId } from \"../lib/config.js\";\nimport type { ChatEnvelope, MessageType } from \"../lib/types.js\";\nimport type { XmtpMessage } from \"../lib/xmtp.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// ── Formatting ──\n\nfunction formatTimestamp(date: Date): string {\n return `${date.getHours().toString().padStart(2, \"0\")}:${date.getMinutes().toString().padStart(2, \"0\")}`;\n}\n\nfunction truncateAddress(addr: string): string {\n return `${addr.slice(0, 6)}...${addr.slice(-4)}`;\n}\n\nfunction colorByType(type: MessageType): (text: string) => string {\n switch (type) {\n case \"TRADE_EXECUTED\":\n return chalk.green;\n case \"RISK_ALERT\":\n return chalk.red;\n case \"TRADE_SIGNAL\":\n return chalk.yellow;\n case \"POSITION_UPDATE\":\n return chalk.cyan;\n case \"LP_REPORT\":\n return chalk.magenta;\n case \"AGENT_REGISTERED\":\n case \"MEMBER_JOIN\":\n return chalk.blue;\n case \"RAGEQUIT_NOTICE\":\n return chalk.red;\n default:\n return chalk.white;\n }\n}\n\nfunction formatMessage(msg: XmtpMessage): string {\n const time = chalk.dim(`[${formatTimestamp(msg.sentAt)}]`);\n const sender = chalk.dim(truncateAddress(msg.senderInboxId));\n\n const text = msg.content;\n try {\n const envelope: ChatEnvelope = JSON.parse(text);\n const color = colorByType(envelope.type);\n const from = envelope.from ? truncateAddress(envelope.from) : sender;\n\n if (envelope.type === \"REACTION\") {\n const data = envelope.data as { reference?: string; emoji?: string } | undefined;\n return `${time} ${sender} reacted ${data?.emoji || \"?\"} to ${truncateAddress(data?.reference || \"?\")}`;\n }\n\n if (envelope.type === \"MESSAGE\") {\n if ((envelope.data as Record<string, unknown>)?.format === \"markdown\") {\n return `${time} ${chalk.dim(from)}\\n${envelope.text || \"\"}`;\n }\n return `${time} ${chalk.dim(from)}: ${envelope.text || \"\"}`;\n }\n\n if (envelope.type === \"AGENT_REGISTERED\") {\n return `${time} ${color(`[${envelope.type}]`)} Agent ${truncateAddress(envelope.agent?.address || \"?\")} registered`;\n }\n\n if (envelope.type === \"MEMBER_JOIN\") {\n return `${time} ${color(`[${envelope.type}]`)} ${truncateAddress(envelope.from || \"?\")} joined`;\n }\n\n const summary = envelope.text || envelope.type;\n return `${time} ${color(`[${envelope.type}]`)} ${chalk.dim(from)}: ${summary}`;\n } catch {\n return `${time} ${sender}: ${text}`;\n }\n}\n\n// ── Action handlers ──\n\nasync function handleStream(name: string): Promise<void> {\n const spinner = ora(\"Connecting to chat...\").start();\n try {\n await resolveSyndicate(name);\n const xmtp = await loadXmtp();\n const client = await xmtp.getXmtpClient();\n const group = await xmtp.getGroup(client, name);\n spinner.succeed(`Connected to ${name}.sherwoodagent.eth`);\n console.log(chalk.dim(\"Streaming messages... (Ctrl+C to exit)\\n\"));\n\n const cleanup = await xmtp.streamMessages(group, (msg) => {\n console.log(formatMessage(msg));\n });\n\n process.on(\"SIGINT\", async () => {\n console.log(chalk.dim(\"\\nDisconnecting...\"));\n cleanup();\n process.exit(0);\n });\n\n await new Promise(() => {});\n } catch (err) {\n spinner.fail(\"Failed to connect to chat\");\n console.error(chalk.red(err instanceof Error ? err.message : String(err)));\n process.exit(1);\n }\n}\n\nasync function handleSend(name: string, message: string, markdown: boolean): Promise<void> {\n const spinner = ora(\"Sending...\").start();\n try {\n const xmtp = await loadXmtp();\n const group = await xmtp.getGroup(\"\", name);\n\n if (markdown) {\n await xmtp.sendMarkdown(group, message);\n } else {\n const envelope: ChatEnvelope = {\n type: \"MESSAGE\",\n from: getAccount().address,\n text: message,\n timestamp: Math.floor(Date.now() / 1000),\n };\n await xmtp.sendEnvelope(group, envelope);\n }\n\n spinner.succeed(\"Message sent\");\n } catch (err) {\n spinner.fail(\"Failed to send message\");\n console.error(chalk.red(err instanceof Error ? err.message : String(err)));\n process.exit(1);\n }\n}\n\nasync function handleReact(name: string, messageId: string, emoji: string): Promise<void> {\n const spinner = ora(\"Reacting...\").start();\n try {\n const xmtp = await loadXmtp();\n const group = await xmtp.getGroup(\"\", name);\n await xmtp.sendReaction(group, messageId, emoji);\n spinner.succeed(`Reacted ${emoji}`);\n } catch (err) {\n spinner.fail(\"Failed to send reaction\");\n console.error(chalk.red(err instanceof Error ? err.message : String(err)));\n process.exit(1);\n }\n}\n\nasync function handleLog(name: string, limit: number): Promise<void> {\n const spinner = ora(\"Loading messages...\").start();\n try {\n const xmtp = await loadXmtp();\n const group = await xmtp.getGroup(\"\", name);\n const messages = await xmtp.getRecentMessages(group, limit);\n\n spinner.stop();\n console.log();\n console.log(chalk.bold(`Chat log: ${name}.sherwoodagent.eth`));\n console.log(chalk.dim(\"─\".repeat(50)));\n\n if (messages.length === 0) {\n console.log(chalk.dim(\" No messages yet\"));\n } else {\n for (const msg of messages.reverse()) {\n console.log(formatMessage(msg));\n }\n }\n console.log();\n } catch (err) {\n spinner.fail(\"Failed to load messages\");\n console.error(chalk.red(err instanceof Error ? err.message : String(err)));\n process.exit(1);\n }\n}\n\nasync function handleMembers(name: string): Promise<void> {\n const spinner = ora(\"Loading members...\").start();\n try {\n const xmtp = await loadXmtp();\n const group = await xmtp.getGroup(\"\", name);\n const members = await xmtp.getMembers(group);\n\n spinner.stop();\n console.log();\n console.log(chalk.bold(`Members: ${name}.sherwoodagent.eth`));\n console.log(chalk.dim(\"─\".repeat(50)));\n\n for (const member of members) {\n const role = member.permissionLevel === \"super_admin\"\n ? chalk.yellow(\" (super admin)\")\n : member.permissionLevel === \"admin\"\n ? chalk.blue(\" (admin)\")\n : \"\";\n console.log(` ${member.inboxId}${role}`);\n }\n\n console.log(chalk.dim(`\\n Total: ${members.length} members`));\n console.log();\n } catch (err) {\n spinner.fail(\"Failed to load members\");\n console.error(chalk.red(err instanceof Error ? err.message : String(err)));\n process.exit(1);\n }\n}\n\nasync function handleAdd(name: string, address: string): Promise<void> {\n const spinner = ora(\"Adding member...\").start();\n try {\n const xmtp = await loadXmtp();\n const group = await xmtp.getGroup(\"\", name);\n await xmtp.addMember(group, address);\n\n await xmtp.sendEnvelope(group, {\n type: \"MEMBER_JOIN\",\n from: address,\n syndicate: name,\n timestamp: Math.floor(Date.now() / 1000),\n });\n\n spinner.succeed(`Member added: ${address}`);\n } catch (err) {\n spinner.fail(\"Failed to add member\");\n console.error(chalk.red(err instanceof Error ? err.message : String(err)));\n process.exit(1);\n }\n}\n\nasync function handleRemove(name: string, address: string): Promise<void> {\n const spinner = ora(\"Removing member...\").start();\n try {\n const xmtp = await loadXmtp();\n const group = await xmtp.getGroup(\"\", name);\n await xmtp.removeMember(group, address);\n spinner.succeed(`Member removed: ${address}`);\n } catch (err) {\n spinner.fail(\"Failed to remove member\");\n console.error(chalk.red(err instanceof Error ? err.message : String(err)));\n process.exit(1);\n }\n}\n\nasync function handleInit(name: string, force: boolean, publicChat: boolean): Promise<void> {\n const spinner = ora(\"Initializing chat group...\").start();\n try {\n const syndicate = await resolveSyndicate(name);\n const callerAddress = getAccount().address.toLowerCase();\n if (syndicate.creator.toLowerCase() !== callerAddress) {\n spinner.fail(\"Only the syndicate creator can initialize the chat group\");\n process.exit(1);\n }\n\n // Idempotency check\n if (!force) {\n const existingId = getCachedGroupId(name) || await getTextRecord(name, \"xmtpGroupId\");\n if (existingId) {\n spinner.succeed(\"XMTP group already exists for this syndicate\");\n console.log(chalk.dim(` Group ID: ${existingId}`));\n return;\n }\n }\n\n // Create the group\n spinner.text = \"Creating XMTP group...\";\n const xmtp = await loadXmtp();\n const client = await xmtp.getXmtpClient();\n const groupId = await xmtp.createSyndicateGroup(client, name, publicChat);\n\n cacheGroupId(name, groupId);\n\n try {\n spinner.text = \"Writing group ID to ENS...\";\n await setTextRecord(name, \"xmtpGroupId\", groupId, syndicate.vault);\n } catch (ensErr) {\n console.warn(chalk.yellow(\"\\n \\u26a0 Could not write ENS text record (cached locally only)\"));\n console.warn(chalk.dim(` ${ensErr instanceof Error ? ensErr.message : String(ensErr)}`));\n }\n\n spinner.succeed(`Chat group created for ${name}.sherwoodagent.eth`);\n console.log(chalk.dim(` Group ID: ${groupId}`));\n console.log(chalk.dim(` Stream: sherwood chat ${name}`));\n } catch (err) {\n spinner.fail(\"Failed to initialize chat group\");\n console.error(chalk.red(err instanceof Error ? err.message : String(err)));\n process.exit(1);\n }\n}\n\n// ── Command Registration ──\n\nexport function registerChatCommands(program: Command): void {\n program\n .command(\"chat <name> [action] [actionArgs...]\")\n .description(\"Syndicate chat — stream, send, log, members, add, remove, init\")\n .option(\"--markdown\", \"Send as rich markdown (for send)\", false)\n .option(\"--limit <n>\", \"Number of messages to show (for log)\", \"20\")\n .option(\"--force\", \"Recreate group even if one exists (for init)\", false)\n .option(\"--public-chat\", \"Enable dashboard spectator mode (for init)\", false)\n .action(async (name: string, action: string | undefined, actionArgs: string[], opts: { markdown: boolean; limit: string; force: boolean; publicChat: boolean }) => {\n switch (action) {\n case \"send\": {\n const message = actionArgs[0];\n if (!message) {\n console.error(chalk.red(\"Usage: sherwood chat <name> send <message> [--markdown]\"));\n process.exit(1);\n }\n await handleSend(name, message, opts.markdown);\n break;\n }\n\n case \"react\": {\n const [messageId, emoji] = actionArgs;\n if (!messageId || !emoji) {\n console.error(chalk.red(\"Usage: sherwood chat <name> react <messageId> <emoji>\"));\n process.exit(1);\n }\n await handleReact(name, messageId, emoji);\n break;\n }\n\n case \"log\":\n await handleLog(name, parseInt(opts.limit, 10));\n break;\n\n case \"members\":\n await handleMembers(name);\n break;\n\n case \"add\": {\n const address = actionArgs[0];\n if (!address) {\n console.error(chalk.red(\"Usage: sherwood chat <name> add <address>\"));\n process.exit(1);\n }\n await handleAdd(name, address);\n break;\n }\n\n case \"remove\": {\n const removeAddr = actionArgs[0];\n if (!removeAddr) {\n console.error(chalk.red(\"Usage: sherwood chat <name> remove <address>\"));\n process.exit(1);\n }\n await handleRemove(name, removeAddr);\n break;\n }\n\n case \"init\":\n await handleInit(name, opts.force, opts.publicChat);\n break;\n\n case undefined:\n default:\n await handleStream(name);\n break;\n }\n });\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -1869,7 +1869,7 @@ try {
|
|
|
1869
1869
|
} catch {
|
|
1870
1870
|
}
|
|
1871
1871
|
async function loadXmtp() {
|
|
1872
|
-
return import("./xmtp-
|
|
1872
|
+
return import("./xmtp-BQAJYBMY.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-
|
|
2709
|
+
const { registerChatCommands } = await import("./chat-YHUZPD54.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(() => {
|
|
@@ -178,11 +178,46 @@ async function createSyndicateGroup(_client, subdomain, publicChat = false) {
|
|
|
178
178
|
cacheGroupId(subdomain, groupId);
|
|
179
179
|
return groupId;
|
|
180
180
|
}
|
|
181
|
+
function conversationExists(groupId) {
|
|
182
|
+
try {
|
|
183
|
+
execXmtp(["conversations", "get", groupId]);
|
|
184
|
+
return true;
|
|
185
|
+
} catch {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function findGroupByName(subdomain) {
|
|
190
|
+
try {
|
|
191
|
+
const conversations = execXmtpJson(
|
|
192
|
+
["conversations", "list", "--type", "group"]
|
|
193
|
+
);
|
|
194
|
+
if (!Array.isArray(conversations)) return null;
|
|
195
|
+
const match = conversations.find(
|
|
196
|
+
(c) => c.name === subdomain || c.description?.includes(`${subdomain}.sherwoodagent.eth`)
|
|
197
|
+
);
|
|
198
|
+
return match?.id || null;
|
|
199
|
+
} catch {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
181
203
|
async function getGroup(_client, subdomain) {
|
|
204
|
+
syncConversations();
|
|
182
205
|
let groupId = getCachedGroupId(subdomain);
|
|
206
|
+
if (groupId && !conversationExists(groupId)) {
|
|
207
|
+
cacheGroupId(subdomain, "");
|
|
208
|
+
groupId = void 0;
|
|
209
|
+
}
|
|
183
210
|
if (!groupId) {
|
|
184
|
-
|
|
185
|
-
if (
|
|
211
|
+
const ensId = await getTextRecord(subdomain, "xmtpGroupId");
|
|
212
|
+
if (ensId && conversationExists(ensId)) {
|
|
213
|
+
groupId = ensId;
|
|
214
|
+
cacheGroupId(subdomain, groupId);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (!groupId) {
|
|
218
|
+
const found = findGroupByName(subdomain);
|
|
219
|
+
if (found) {
|
|
220
|
+
groupId = found;
|
|
186
221
|
cacheGroupId(subdomain, groupId);
|
|
187
222
|
}
|
|
188
223
|
}
|
|
@@ -214,7 +249,6 @@ async function removeMember(groupId, address) {
|
|
|
214
249
|
execXmtp(["conversation", "remove-members", groupId, address]);
|
|
215
250
|
}
|
|
216
251
|
async function sendEnvelope(groupId, envelope) {
|
|
217
|
-
syncConversations();
|
|
218
252
|
const text = JSON.stringify(envelope);
|
|
219
253
|
execXmtp(["conversation", "send-text", groupId, text]);
|
|
220
254
|
}
|
|
@@ -280,7 +314,6 @@ async function streamMessages(groupId, onMessage) {
|
|
|
280
314
|
};
|
|
281
315
|
}
|
|
282
316
|
async function getRecentMessages(groupId, limit = 20) {
|
|
283
|
-
syncConversations();
|
|
284
317
|
const raw = execXmtpJson([
|
|
285
318
|
"conversation",
|
|
286
319
|
"messages",
|
|
@@ -299,7 +332,6 @@ async function getRecentMessages(groupId, limit = 20) {
|
|
|
299
332
|
return messages.slice(-limit);
|
|
300
333
|
}
|
|
301
334
|
async function getMembers(groupId) {
|
|
302
|
-
syncConversations();
|
|
303
335
|
const raw = execXmtpJson([
|
|
304
336
|
"conversation",
|
|
305
337
|
"members",
|
|
@@ -324,4 +356,4 @@ export {
|
|
|
324
356
|
sendReaction,
|
|
325
357
|
streamMessages
|
|
326
358
|
};
|
|
327
|
-
//# sourceMappingURL=xmtp-
|
|
359
|
+
//# sourceMappingURL=xmtp-BQAJYBMY.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 *\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 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\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 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/chat.ts"],"sourcesContent":["/**\n * Chat commands — sherwood chat <syndicate-name> [action] [args...]\n *\n * Uses XMTP for encrypted group messaging tied to syndicates.\n * Shells out to the @xmtp/cli binary for all XMTP operations.\n *\n * Commander can't dispatch subcommands when the parent has a positional <name> arg\n * (it always runs the parent action). So we use manual dispatch: a single .action()\n * that routes based on the [action] argument.\n *\n * Usage:\n * sherwood chat <name> — stream messages (default)\n * sherwood chat <name> send \"hello\" — send a text message\n * sherwood chat <name> send \"hello\" --markdown — send formatted markdown\n * sherwood chat <name> react <id> <emoji> — react to a message\n * sherwood chat <name> log [--limit 50] — show recent messages\n * sherwood chat <name> members — list group members\n * sherwood chat <name> add <address> — add member (creator only)\n * sherwood chat <name> init [--force] — create XMTP group + write ENS record\n */\n\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport { getAccount } from \"../lib/client.js\";\nimport { resolveSyndicate, setTextRecord, getTextRecord } from \"../lib/ens.js\";\nimport { cacheGroupId, getCachedGroupId } from \"../lib/config.js\";\nimport type { ChatEnvelope, MessageType } from \"../lib/types.js\";\nimport type { XmtpMessage } from \"../lib/xmtp.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// ── Formatting ──\n\nfunction formatTimestamp(date: Date): string {\n return `${date.getHours().toString().padStart(2, \"0\")}:${date.getMinutes().toString().padStart(2, \"0\")}`;\n}\n\nfunction truncateAddress(addr: string): string {\n return `${addr.slice(0, 6)}...${addr.slice(-4)}`;\n}\n\nfunction colorByType(type: MessageType): (text: string) => string {\n switch (type) {\n case \"TRADE_EXECUTED\":\n return chalk.green;\n case \"RISK_ALERT\":\n return chalk.red;\n case \"TRADE_SIGNAL\":\n return chalk.yellow;\n case \"POSITION_UPDATE\":\n return chalk.cyan;\n case \"LP_REPORT\":\n return chalk.magenta;\n case \"AGENT_REGISTERED\":\n case \"MEMBER_JOIN\":\n return chalk.blue;\n case \"RAGEQUIT_NOTICE\":\n return chalk.red;\n default:\n return chalk.white;\n }\n}\n\nfunction formatMessage(msg: XmtpMessage): string {\n const time = chalk.dim(`[${formatTimestamp(msg.sentAt)}]`);\n const sender = chalk.dim(truncateAddress(msg.senderInboxId));\n\n const text = msg.content;\n try {\n const envelope: ChatEnvelope = JSON.parse(text);\n const color = colorByType(envelope.type);\n const from = envelope.from ? truncateAddress(envelope.from) : sender;\n\n if (envelope.type === \"REACTION\") {\n const data = envelope.data as { reference?: string; emoji?: string } | undefined;\n return `${time} ${sender} reacted ${data?.emoji || \"?\"} to ${truncateAddress(data?.reference || \"?\")}`;\n }\n\n if (envelope.type === \"MESSAGE\") {\n if ((envelope.data as Record<string, unknown>)?.format === \"markdown\") {\n return `${time} ${chalk.dim(from)}\\n${envelope.text || \"\"}`;\n }\n return `${time} ${chalk.dim(from)}: ${envelope.text || \"\"}`;\n }\n\n if (envelope.type === \"AGENT_REGISTERED\") {\n return `${time} ${color(`[${envelope.type}]`)} Agent ${truncateAddress(envelope.agent?.address || \"?\")} registered`;\n }\n\n if (envelope.type === \"MEMBER_JOIN\") {\n return `${time} ${color(`[${envelope.type}]`)} ${truncateAddress(envelope.from || \"?\")} joined`;\n }\n\n const summary = envelope.text || envelope.type;\n return `${time} ${color(`[${envelope.type}]`)} ${chalk.dim(from)}: ${summary}`;\n } catch {\n return `${time} ${sender}: ${text}`;\n }\n}\n\n// ── Action handlers ──\n\nasync function handleStream(name: string): Promise<void> {\n const spinner = ora(\"Connecting to chat...\").start();\n try {\n await resolveSyndicate(name);\n const xmtp = await loadXmtp();\n const client = await xmtp.getXmtpClient();\n const group = await xmtp.getGroup(client, name);\n spinner.succeed(`Connected to ${name}.sherwoodagent.eth`);\n console.log(chalk.dim(\"Streaming messages... (Ctrl+C to exit)\\n\"));\n\n const cleanup = await xmtp.streamMessages(group, (msg) => {\n console.log(formatMessage(msg));\n });\n\n process.on(\"SIGINT\", async () => {\n console.log(chalk.dim(\"\\nDisconnecting...\"));\n cleanup();\n process.exit(0);\n });\n\n await new Promise(() => {});\n } catch (err) {\n spinner.fail(\"Failed to connect to chat\");\n console.error(chalk.red(err instanceof Error ? err.message : String(err)));\n process.exit(1);\n }\n}\n\nasync function handleSend(name: string, message: string, markdown: boolean): Promise<void> {\n const spinner = ora(\"Sending...\").start();\n try {\n const xmtp = await loadXmtp();\n const client = await xmtp.getXmtpClient();\n const group = await xmtp.getGroup(client, name);\n\n if (markdown) {\n await xmtp.sendMarkdown(group, message);\n } else {\n const envelope: ChatEnvelope = {\n type: \"MESSAGE\",\n from: getAccount().address,\n text: message,\n timestamp: Math.floor(Date.now() / 1000),\n };\n await xmtp.sendEnvelope(group, envelope);\n }\n\n spinner.succeed(\"Message sent\");\n } catch (err) {\n spinner.fail(\"Failed to send message\");\n console.error(chalk.red(err instanceof Error ? err.message : String(err)));\n process.exit(1);\n }\n}\n\nasync function handleReact(name: string, messageId: string, emoji: string): Promise<void> {\n const spinner = ora(\"Reacting...\").start();\n try {\n const xmtp = await loadXmtp();\n const client = await xmtp.getXmtpClient();\n const group = await xmtp.getGroup(client, name);\n await xmtp.sendReaction(group, messageId, emoji);\n spinner.succeed(`Reacted ${emoji}`);\n } catch (err) {\n spinner.fail(\"Failed to send reaction\");\n console.error(chalk.red(err instanceof Error ? err.message : String(err)));\n process.exit(1);\n }\n}\n\nasync function handleLog(name: string, limit: number): Promise<void> {\n const spinner = ora(\"Loading messages...\").start();\n try {\n const xmtp = await loadXmtp();\n const client = await xmtp.getXmtpClient();\n const group = await xmtp.getGroup(client, name);\n const messages = await xmtp.getRecentMessages(group, limit);\n\n spinner.stop();\n console.log();\n console.log(chalk.bold(`Chat log: ${name}.sherwoodagent.eth`));\n console.log(chalk.dim(\"─\".repeat(50)));\n\n if (messages.length === 0) {\n console.log(chalk.dim(\" No messages yet\"));\n } else {\n for (const msg of messages.reverse()) {\n console.log(formatMessage(msg));\n }\n }\n console.log();\n } catch (err) {\n spinner.fail(\"Failed to load messages\");\n console.error(chalk.red(err instanceof Error ? err.message : String(err)));\n process.exit(1);\n }\n}\n\nasync function handleMembers(name: string): Promise<void> {\n const spinner = ora(\"Loading members...\").start();\n try {\n const xmtp = await loadXmtp();\n const client = await xmtp.getXmtpClient();\n const group = await xmtp.getGroup(client, name);\n const members = await xmtp.getMembers(group);\n\n spinner.stop();\n console.log();\n console.log(chalk.bold(`Members: ${name}.sherwoodagent.eth`));\n console.log(chalk.dim(\"─\".repeat(50)));\n\n for (const member of members) {\n const role = member.permissionLevel === \"super_admin\"\n ? chalk.yellow(\" (super admin)\")\n : member.permissionLevel === \"admin\"\n ? chalk.blue(\" (admin)\")\n : \"\";\n console.log(` ${member.inboxId}${role}`);\n }\n\n console.log(chalk.dim(`\\n Total: ${members.length} members`));\n console.log();\n } catch (err) {\n spinner.fail(\"Failed to load members\");\n console.error(chalk.red(err instanceof Error ? err.message : String(err)));\n process.exit(1);\n }\n}\n\nasync function handleAdd(name: string, address: string): Promise<void> {\n const spinner = ora(\"Adding member...\").start();\n try {\n const xmtp = await loadXmtp();\n const client = await xmtp.getXmtpClient();\n const group = await xmtp.getGroup(client, name);\n await xmtp.addMember(group, address);\n\n await xmtp.sendEnvelope(group, {\n type: \"MEMBER_JOIN\",\n from: address,\n syndicate: name,\n timestamp: Math.floor(Date.now() / 1000),\n });\n\n spinner.succeed(`Member added: ${address}`);\n } catch (err) {\n spinner.fail(\"Failed to add member\");\n console.error(chalk.red(err instanceof Error ? err.message : String(err)));\n process.exit(1);\n }\n}\n\nasync function handleRemove(name: string, address: string): Promise<void> {\n const spinner = ora(\"Removing member...\").start();\n try {\n const xmtp = await loadXmtp();\n const client = await xmtp.getXmtpClient();\n const group = await xmtp.getGroup(client, name);\n await xmtp.removeMember(group, address);\n spinner.succeed(`Member removed: ${address}`);\n } catch (err) {\n spinner.fail(\"Failed to remove member\");\n console.error(chalk.red(err instanceof Error ? err.message : String(err)));\n process.exit(1);\n }\n}\n\nasync function handleInit(name: string, force: boolean, publicChat: boolean): Promise<void> {\n const spinner = ora(\"Initializing chat group...\").start();\n try {\n const syndicate = await resolveSyndicate(name);\n const callerAddress = getAccount().address.toLowerCase();\n if (syndicate.creator.toLowerCase() !== callerAddress) {\n spinner.fail(\"Only the syndicate creator can initialize the chat group\");\n process.exit(1);\n }\n\n // Idempotency check\n if (!force) {\n const existingId = getCachedGroupId(name) || await getTextRecord(name, \"xmtpGroupId\");\n if (existingId) {\n spinner.succeed(\"XMTP group already exists for this syndicate\");\n console.log(chalk.dim(` Group ID: ${existingId}`));\n return;\n }\n }\n\n // Create the group\n spinner.text = \"Creating XMTP group...\";\n const xmtp = await loadXmtp();\n const client = await xmtp.getXmtpClient();\n const groupId = await xmtp.createSyndicateGroup(client, name, publicChat);\n\n cacheGroupId(name, groupId);\n\n try {\n spinner.text = \"Writing group ID to ENS...\";\n await setTextRecord(name, \"xmtpGroupId\", groupId, syndicate.vault);\n } catch (ensErr) {\n console.warn(chalk.yellow(\"\\n \\u26a0 Could not write ENS text record (cached locally only)\"));\n console.warn(chalk.dim(` ${ensErr instanceof Error ? ensErr.message : String(ensErr)}`));\n }\n\n spinner.succeed(`Chat group created for ${name}.sherwoodagent.eth`);\n console.log(chalk.dim(` Group ID: ${groupId}`));\n console.log(chalk.dim(` Stream: sherwood chat ${name}`));\n } catch (err) {\n spinner.fail(\"Failed to initialize chat group\");\n console.error(chalk.red(err instanceof Error ? err.message : String(err)));\n process.exit(1);\n }\n}\n\n// ── Command Registration ──\n\nexport function registerChatCommands(program: Command): void {\n program\n .command(\"chat <name> [action] [actionArgs...]\")\n .description(\"Syndicate chat — stream, send, log, members, add, remove, init\")\n .option(\"--markdown\", \"Send as rich markdown (for send)\", false)\n .option(\"--limit <n>\", \"Number of messages to show (for log)\", \"20\")\n .option(\"--force\", \"Recreate group even if one exists (for init)\", false)\n .option(\"--public-chat\", \"Enable dashboard spectator mode (for init)\", false)\n .action(async (name: string, action: string | undefined, actionArgs: string[], opts: { markdown: boolean; limit: string; force: boolean; publicChat: boolean }) => {\n switch (action) {\n case \"send\": {\n const message = actionArgs[0];\n if (!message) {\n console.error(chalk.red(\"Usage: sherwood chat <name> send <message> [--markdown]\"));\n process.exit(1);\n }\n await handleSend(name, message, opts.markdown);\n break;\n }\n\n case \"react\": {\n const [messageId, emoji] = actionArgs;\n if (!messageId || !emoji) {\n console.error(chalk.red(\"Usage: sherwood chat <name> react <messageId> <emoji>\"));\n process.exit(1);\n }\n await handleReact(name, messageId, emoji);\n break;\n }\n\n case \"log\":\n await handleLog(name, parseInt(opts.limit, 10));\n break;\n\n case \"members\":\n await handleMembers(name);\n break;\n\n case \"add\": {\n const address = actionArgs[0];\n if (!address) {\n console.error(chalk.red(\"Usage: sherwood chat <name> add <address>\"));\n process.exit(1);\n }\n await handleAdd(name, address);\n break;\n }\n\n case \"remove\": {\n const removeAddr = actionArgs[0];\n if (!removeAddr) {\n console.error(chalk.red(\"Usage: sherwood chat <name> remove <address>\"));\n process.exit(1);\n }\n await handleRemove(name, removeAddr);\n break;\n }\n\n case \"init\":\n await handleInit(name, opts.force, opts.publicChat);\n break;\n\n case undefined:\n default:\n await handleStream(name);\n break;\n }\n });\n}\n"],"mappings":";;;;;;;;;;AAsBA,OAAO,WAAW;AAClB,OAAO,SAAS;AAQhB,eAAe,WAAW;AACxB,SAAO,OAAO,oBAAgB;AAChC;AAIA,SAAS,gBAAgB,MAAoB;AAC3C,SAAO,GAAG,KAAK,SAAS,EAAE,SAAS,EAAE,SAAS,GAAG,GAAG,CAAC,IAAI,KAAK,WAAW,EAAE,SAAS,EAAE,SAAS,GAAG,GAAG,CAAC;AACxG;AAEA,SAAS,gBAAgB,MAAsB;AAC7C,SAAO,GAAG,KAAK,MAAM,GAAG,CAAC,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;AAChD;AAEA,SAAS,YAAY,MAA6C;AAChE,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,aAAO,MAAM;AAAA,IACf,KAAK;AACH,aAAO,MAAM;AAAA,IACf,KAAK;AACH,aAAO,MAAM;AAAA,IACf,KAAK;AACH,aAAO,MAAM;AAAA,IACf,KAAK;AACH,aAAO,MAAM;AAAA,IACf,KAAK;AAAA,IACL,KAAK;AACH,aAAO,MAAM;AAAA,IACf,KAAK;AACH,aAAO,MAAM;AAAA,IACf;AACE,aAAO,MAAM;AAAA,EACjB;AACF;AAEA,SAAS,cAAc,KAA0B;AAC/C,QAAM,OAAO,MAAM,IAAI,IAAI,gBAAgB,IAAI,MAAM,CAAC,GAAG;AACzD,QAAM,SAAS,MAAM,IAAI,gBAAgB,IAAI,aAAa,CAAC;AAE3D,QAAM,OAAO,IAAI;AACjB,MAAI;AACF,UAAM,WAAyB,KAAK,MAAM,IAAI;AAC9C,UAAM,QAAQ,YAAY,SAAS,IAAI;AACvC,UAAM,OAAO,SAAS,OAAO,gBAAgB,SAAS,IAAI,IAAI;AAE9D,QAAI,SAAS,SAAS,YAAY;AAChC,YAAM,OAAO,SAAS;AACtB,aAAO,GAAG,IAAI,IAAI,MAAM,YAAY,MAAM,SAAS,GAAG,OAAO,gBAAgB,MAAM,aAAa,GAAG,CAAC;AAAA,IACtG;AAEA,QAAI,SAAS,SAAS,WAAW;AAC/B,UAAK,SAAS,MAAkC,WAAW,YAAY;AACrE,eAAO,GAAG,IAAI,IAAI,MAAM,IAAI,IAAI,CAAC;AAAA,EAAK,SAAS,QAAQ,EAAE;AAAA,MAC3D;AACA,aAAO,GAAG,IAAI,IAAI,MAAM,IAAI,IAAI,CAAC,KAAK,SAAS,QAAQ,EAAE;AAAA,IAC3D;AAEA,QAAI,SAAS,SAAS,oBAAoB;AACxC,aAAO,GAAG,IAAI,IAAI,MAAM,IAAI,SAAS,IAAI,GAAG,CAAC,UAAU,gBAAgB,SAAS,OAAO,WAAW,GAAG,CAAC;AAAA,IACxG;AAEA,QAAI,SAAS,SAAS,eAAe;AACnC,aAAO,GAAG,IAAI,IAAI,MAAM,IAAI,SAAS,IAAI,GAAG,CAAC,IAAI,gBAAgB,SAAS,QAAQ,GAAG,CAAC;AAAA,IACxF;AAEA,UAAM,UAAU,SAAS,QAAQ,SAAS;AAC1C,WAAO,GAAG,IAAI,IAAI,MAAM,IAAI,SAAS,IAAI,GAAG,CAAC,IAAI,MAAM,IAAI,IAAI,CAAC,KAAK,OAAO;AAAA,EAC9E,QAAQ;AACN,WAAO,GAAG,IAAI,IAAI,MAAM,KAAK,IAAI;AAAA,EACnC;AACF;AAIA,eAAe,aAAa,MAA6B;AACvD,QAAM,UAAU,IAAI,uBAAuB,EAAE,MAAM;AACnD,MAAI;AACF,UAAM,iBAAiB,IAAI;AAC3B,UAAM,OAAO,MAAM,SAAS;AAC5B,UAAM,SAAS,MAAM,KAAK,cAAc;AACxC,UAAM,QAAQ,MAAM,KAAK,SAAS,QAAQ,IAAI;AAC9C,YAAQ,QAAQ,gBAAgB,IAAI,oBAAoB;AACxD,YAAQ,IAAI,MAAM,IAAI,0CAA0C,CAAC;AAEjE,UAAM,UAAU,MAAM,KAAK,eAAe,OAAO,CAAC,QAAQ;AACxD,cAAQ,IAAI,cAAc,GAAG,CAAC;AAAA,IAChC,CAAC;AAED,YAAQ,GAAG,UAAU,YAAY;AAC/B,cAAQ,IAAI,MAAM,IAAI,oBAAoB,CAAC;AAC3C,cAAQ;AACR,cAAQ,KAAK,CAAC;AAAA,IAChB,CAAC;AAED,UAAM,IAAI,QAAQ,MAAM;AAAA,IAAC,CAAC;AAAA,EAC5B,SAAS,KAAK;AACZ,YAAQ,KAAK,2BAA2B;AACxC,YAAQ,MAAM,MAAM,IAAI,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,CAAC;AACzE,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAEA,eAAe,WAAW,MAAc,SAAiB,UAAkC;AACzF,QAAM,UAAU,IAAI,YAAY,EAAE,MAAM;AACxC,MAAI;AACF,UAAM,OAAO,MAAM,SAAS;AAC5B,UAAM,SAAS,MAAM,KAAK,cAAc;AACxC,UAAM,QAAQ,MAAM,KAAK,SAAS,QAAQ,IAAI;AAE9C,QAAI,UAAU;AACZ,YAAM,KAAK,aAAa,OAAO,OAAO;AAAA,IACxC,OAAO;AACL,YAAM,WAAyB;AAAA,QAC7B,MAAM;AAAA,QACN,MAAM,WAAW,EAAE;AAAA,QACnB,MAAM;AAAA,QACN,WAAW,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAAA,MACzC;AACA,YAAM,KAAK,aAAa,OAAO,QAAQ;AAAA,IACzC;AAEA,YAAQ,QAAQ,cAAc;AAAA,EAChC,SAAS,KAAK;AACZ,YAAQ,KAAK,wBAAwB;AACrC,YAAQ,MAAM,MAAM,IAAI,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,CAAC;AACzE,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAEA,eAAe,YAAY,MAAc,WAAmB,OAA8B;AACxF,QAAM,UAAU,IAAI,aAAa,EAAE,MAAM;AACzC,MAAI;AACF,UAAM,OAAO,MAAM,SAAS;AAC5B,UAAM,SAAS,MAAM,KAAK,cAAc;AACxC,UAAM,QAAQ,MAAM,KAAK,SAAS,QAAQ,IAAI;AAC9C,UAAM,KAAK,aAAa,OAAO,WAAW,KAAK;AAC/C,YAAQ,QAAQ,WAAW,KAAK,EAAE;AAAA,EACpC,SAAS,KAAK;AACZ,YAAQ,KAAK,yBAAyB;AACtC,YAAQ,MAAM,MAAM,IAAI,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,CAAC;AACzE,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAEA,eAAe,UAAU,MAAc,OAA8B;AACnE,QAAM,UAAU,IAAI,qBAAqB,EAAE,MAAM;AACjD,MAAI;AACF,UAAM,OAAO,MAAM,SAAS;AAC5B,UAAM,SAAS,MAAM,KAAK,cAAc;AACxC,UAAM,QAAQ,MAAM,KAAK,SAAS,QAAQ,IAAI;AAC9C,UAAM,WAAW,MAAM,KAAK,kBAAkB,OAAO,KAAK;AAE1D,YAAQ,KAAK;AACb,YAAQ,IAAI;AACZ,YAAQ,IAAI,MAAM,KAAK,aAAa,IAAI,oBAAoB,CAAC;AAC7D,YAAQ,IAAI,MAAM,IAAI,SAAI,OAAO,EAAE,CAAC,CAAC;AAErC,QAAI,SAAS,WAAW,GAAG;AACzB,cAAQ,IAAI,MAAM,IAAI,mBAAmB,CAAC;AAAA,IAC5C,OAAO;AACL,iBAAW,OAAO,SAAS,QAAQ,GAAG;AACpC,gBAAQ,IAAI,cAAc,GAAG,CAAC;AAAA,MAChC;AAAA,IACF;AACA,YAAQ,IAAI;AAAA,EACd,SAAS,KAAK;AACZ,YAAQ,KAAK,yBAAyB;AACtC,YAAQ,MAAM,MAAM,IAAI,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,CAAC;AACzE,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAEA,eAAe,cAAc,MAA6B;AACxD,QAAM,UAAU,IAAI,oBAAoB,EAAE,MAAM;AAChD,MAAI;AACF,UAAM,OAAO,MAAM,SAAS;AAC5B,UAAM,SAAS,MAAM,KAAK,cAAc;AACxC,UAAM,QAAQ,MAAM,KAAK,SAAS,QAAQ,IAAI;AAC9C,UAAM,UAAU,MAAM,KAAK,WAAW,KAAK;AAE3C,YAAQ,KAAK;AACb,YAAQ,IAAI;AACZ,YAAQ,IAAI,MAAM,KAAK,YAAY,IAAI,oBAAoB,CAAC;AAC5D,YAAQ,IAAI,MAAM,IAAI,SAAI,OAAO,EAAE,CAAC,CAAC;AAErC,eAAW,UAAU,SAAS;AAC5B,YAAM,OAAO,OAAO,oBAAoB,gBACpC,MAAM,OAAO,gBAAgB,IAC7B,OAAO,oBAAoB,UACzB,MAAM,KAAK,UAAU,IACrB;AACN,cAAQ,IAAI,KAAK,OAAO,OAAO,GAAG,IAAI,EAAE;AAAA,IAC1C;AAEA,YAAQ,IAAI,MAAM,IAAI;AAAA,WAAc,QAAQ,MAAM,UAAU,CAAC;AAC7D,YAAQ,IAAI;AAAA,EACd,SAAS,KAAK;AACZ,YAAQ,KAAK,wBAAwB;AACrC,YAAQ,MAAM,MAAM,IAAI,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,CAAC;AACzE,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAEA,eAAe,UAAU,MAAc,SAAgC;AACrE,QAAM,UAAU,IAAI,kBAAkB,EAAE,MAAM;AAC9C,MAAI;AACF,UAAM,OAAO,MAAM,SAAS;AAC5B,UAAM,SAAS,MAAM,KAAK,cAAc;AACxC,UAAM,QAAQ,MAAM,KAAK,SAAS,QAAQ,IAAI;AAC9C,UAAM,KAAK,UAAU,OAAO,OAAO;AAEnC,UAAM,KAAK,aAAa,OAAO;AAAA,MAC7B,MAAM;AAAA,MACN,MAAM;AAAA,MACN,WAAW;AAAA,MACX,WAAW,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAAA,IACzC,CAAC;AAED,YAAQ,QAAQ,iBAAiB,OAAO,EAAE;AAAA,EAC5C,SAAS,KAAK;AACZ,YAAQ,KAAK,sBAAsB;AACnC,YAAQ,MAAM,MAAM,IAAI,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,CAAC;AACzE,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAEA,eAAe,aAAa,MAAc,SAAgC;AACxE,QAAM,UAAU,IAAI,oBAAoB,EAAE,MAAM;AAChD,MAAI;AACF,UAAM,OAAO,MAAM,SAAS;AAC5B,UAAM,SAAS,MAAM,KAAK,cAAc;AACxC,UAAM,QAAQ,MAAM,KAAK,SAAS,QAAQ,IAAI;AAC9C,UAAM,KAAK,aAAa,OAAO,OAAO;AACtC,YAAQ,QAAQ,mBAAmB,OAAO,EAAE;AAAA,EAC9C,SAAS,KAAK;AACZ,YAAQ,KAAK,yBAAyB;AACtC,YAAQ,MAAM,MAAM,IAAI,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,CAAC;AACzE,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAEA,eAAe,WAAW,MAAc,OAAgB,YAAoC;AAC1F,QAAM,UAAU,IAAI,4BAA4B,EAAE,MAAM;AACxD,MAAI;AACF,UAAM,YAAY,MAAM,iBAAiB,IAAI;AAC7C,UAAM,gBAAgB,WAAW,EAAE,QAAQ,YAAY;AACvD,QAAI,UAAU,QAAQ,YAAY,MAAM,eAAe;AACrD,cAAQ,KAAK,0DAA0D;AACvE,cAAQ,KAAK,CAAC;AAAA,IAChB;AAGA,QAAI,CAAC,OAAO;AACV,YAAM,aAAa,iBAAiB,IAAI,KAAK,MAAM,cAAc,MAAM,aAAa;AACpF,UAAI,YAAY;AACd,gBAAQ,QAAQ,8CAA8C;AAC9D,gBAAQ,IAAI,MAAM,IAAI,eAAe,UAAU,EAAE,CAAC;AAClD;AAAA,MACF;AAAA,IACF;AAGA,YAAQ,OAAO;AACf,UAAM,OAAO,MAAM,SAAS;AAC5B,UAAM,SAAS,MAAM,KAAK,cAAc;AACxC,UAAM,UAAU,MAAM,KAAK,qBAAqB,QAAQ,MAAM,UAAU;AAExE,iBAAa,MAAM,OAAO;AAE1B,QAAI;AACF,cAAQ,OAAO;AACf,YAAM,cAAc,MAAM,eAAe,SAAS,UAAU,KAAK;AAAA,IACnE,SAAS,QAAQ;AACf,cAAQ,KAAK,MAAM,OAAO,kEAAkE,CAAC;AAC7F,cAAQ,KAAK,MAAM,IAAI,OAAO,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM,CAAC,EAAE,CAAC;AAAA,IAC5F;AAEA,YAAQ,QAAQ,0BAA0B,IAAI,oBAAoB;AAClE,YAAQ,IAAI,MAAM,IAAI,eAAe,OAAO,EAAE,CAAC;AAC/C,YAAQ,IAAI,MAAM,IAAI,6BAA6B,IAAI,EAAE,CAAC;AAAA,EAC5D,SAAS,KAAK;AACZ,YAAQ,KAAK,iCAAiC;AAC9C,YAAQ,MAAM,MAAM,IAAI,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,CAAC;AACzE,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAIO,SAAS,qBAAqB,SAAwB;AAC3D,UACG,QAAQ,sCAAsC,EAC9C,YAAY,qEAAgE,EAC5E,OAAO,cAAc,oCAAoC,KAAK,EAC9D,OAAO,eAAe,wCAAwC,IAAI,EAClE,OAAO,WAAW,gDAAgD,KAAK,EACvE,OAAO,iBAAiB,8CAA8C,KAAK,EAC3E,OAAO,OAAO,MAAc,QAA4B,YAAsB,SAAoF;AACjK,YAAQ,QAAQ;AAAA,MACd,KAAK,QAAQ;AACX,cAAM,UAAU,WAAW,CAAC;AAC5B,YAAI,CAAC,SAAS;AACZ,kBAAQ,MAAM,MAAM,IAAI,yDAAyD,CAAC;AAClF,kBAAQ,KAAK,CAAC;AAAA,QAChB;AACA,cAAM,WAAW,MAAM,SAAS,KAAK,QAAQ;AAC7C;AAAA,MACF;AAAA,MAEA,KAAK,SAAS;AACZ,cAAM,CAAC,WAAW,KAAK,IAAI;AAC3B,YAAI,CAAC,aAAa,CAAC,OAAO;AACxB,kBAAQ,MAAM,MAAM,IAAI,uDAAuD,CAAC;AAChF,kBAAQ,KAAK,CAAC;AAAA,QAChB;AACA,cAAM,YAAY,MAAM,WAAW,KAAK;AACxC;AAAA,MACF;AAAA,MAEA,KAAK;AACH,cAAM,UAAU,MAAM,SAAS,KAAK,OAAO,EAAE,CAAC;AAC9C;AAAA,MAEF,KAAK;AACH,cAAM,cAAc,IAAI;AACxB;AAAA,MAEF,KAAK,OAAO;AACV,cAAM,UAAU,WAAW,CAAC;AAC5B,YAAI,CAAC,SAAS;AACZ,kBAAQ,MAAM,MAAM,IAAI,2CAA2C,CAAC;AACpE,kBAAQ,KAAK,CAAC;AAAA,QAChB;AACA,cAAM,UAAU,MAAM,OAAO;AAC7B;AAAA,MACF;AAAA,MAEA,KAAK,UAAU;AACb,cAAM,aAAa,WAAW,CAAC;AAC/B,YAAI,CAAC,YAAY;AACf,kBAAQ,MAAM,MAAM,IAAI,8CAA8C,CAAC;AACvE,kBAAQ,KAAK,CAAC;AAAA,QAChB;AACA,cAAM,aAAa,MAAM,UAAU;AACnC;AAAA,MACF;AAAA,MAEA,KAAK;AACH,cAAM,WAAW,MAAM,KAAK,OAAO,KAAK,UAAU;AAClD;AAAA,MAEF,KAAK;AAAA,MACL;AACE,cAAM,aAAa,IAAI;AACvB;AAAA,IACJ;AAAA,EACF,CAAC;AACL;","names":[]}
|
|
@@ -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 * 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 *\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 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 // 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 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":[]}
|