@sherwoodagent/cli 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{commands/chat.js → chat-X5PDOVZ6.js} +32 -49
- package/dist/chat-X5PDOVZ6.js.map +1 -0
- package/dist/index.js +6 -7
- package/dist/index.js.map +1 -1
- package/dist/xmtp-4G7BTQZJ.js +262 -0
- package/dist/xmtp-4G7BTQZJ.js.map +1 -0
- package/package.json +2 -2
- package/dist/commands/chat.js.map +0 -1
- package/dist/xmtp-2UPH45XE.js +0 -142
- package/dist/xmtp-2UPH45XE.js.map +0 -1
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cacheGroupId,
|
|
3
|
+
getAccount,
|
|
4
|
+
getCachedGroupId,
|
|
5
|
+
getNetwork,
|
|
6
|
+
getTextRecord,
|
|
7
|
+
loadConfig,
|
|
8
|
+
saveConfig
|
|
9
|
+
} from "./chunk-CR3ZNOB7.js";
|
|
10
|
+
|
|
11
|
+
// src/lib/xmtp.ts
|
|
12
|
+
import { execFileSync, spawn, execSync } from "child_process";
|
|
13
|
+
import fs from "fs";
|
|
14
|
+
import path from "path";
|
|
15
|
+
import { homedir } from "os";
|
|
16
|
+
var _binaryPath = null;
|
|
17
|
+
function getXmtpBinaryPath() {
|
|
18
|
+
if (_binaryPath) return _binaryPath;
|
|
19
|
+
const searchPaths = [
|
|
20
|
+
// From dist/ after build
|
|
21
|
+
path.resolve(import.meta.dirname, "..", "node_modules", "@xmtp", "cli", "bin", "run.js"),
|
|
22
|
+
// From src/ during dev
|
|
23
|
+
path.resolve(import.meta.dirname, "..", "..", "node_modules", "@xmtp", "cli", "bin", "run.js"),
|
|
24
|
+
// From cwd
|
|
25
|
+
path.resolve(process.cwd(), "node_modules", "@xmtp", "cli", "bin", "run.js")
|
|
26
|
+
];
|
|
27
|
+
for (const p of searchPaths) {
|
|
28
|
+
if (fs.existsSync(p)) {
|
|
29
|
+
_binaryPath = p;
|
|
30
|
+
return _binaryPath;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const which = execSync("which xmtp", { encoding: "utf8" }).trim();
|
|
35
|
+
if (which) {
|
|
36
|
+
_binaryPath = which;
|
|
37
|
+
return _binaryPath;
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
}
|
|
41
|
+
throw new Error(
|
|
42
|
+
"XMTP CLI not found. Install with: npm install -g @xmtp/cli"
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
var _synced = false;
|
|
46
|
+
function syncXmtpEnv() {
|
|
47
|
+
if (_synced) return;
|
|
48
|
+
const config = loadConfig();
|
|
49
|
+
if (!config.privateKey) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
'No private key configured. Run "sherwood config set --private-key 0x..."'
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
const xmtpDir = path.join(homedir(), ".xmtp");
|
|
55
|
+
const envFile = path.join(xmtpDir, ".env");
|
|
56
|
+
const walletKey = config.privateKey.replace(/^0x/, "");
|
|
57
|
+
if (fs.existsSync(envFile)) {
|
|
58
|
+
const existing = fs.readFileSync(envFile, "utf8");
|
|
59
|
+
if (existing.includes(`XMTP_WALLET_KEY=${walletKey}`)) {
|
|
60
|
+
_synced = true;
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
fs.mkdirSync(xmtpDir, { recursive: true });
|
|
65
|
+
const content = [
|
|
66
|
+
`XMTP_WALLET_KEY=${walletKey}`,
|
|
67
|
+
`XMTP_DB_ENCRYPTION_KEY=${config.dbEncryptionKey}`,
|
|
68
|
+
`XMTP_ENV=${getXmtpEnv()}`
|
|
69
|
+
].join("\n");
|
|
70
|
+
fs.writeFileSync(envFile, content, { mode: 384 });
|
|
71
|
+
_synced = true;
|
|
72
|
+
}
|
|
73
|
+
function getXmtpEnv() {
|
|
74
|
+
return getNetwork() === "base" ? "production" : "dev";
|
|
75
|
+
}
|
|
76
|
+
function getXmtpEnvFile() {
|
|
77
|
+
return path.join(homedir(), ".xmtp", ".env");
|
|
78
|
+
}
|
|
79
|
+
function execXmtp(args) {
|
|
80
|
+
syncXmtpEnv();
|
|
81
|
+
const bin = getXmtpBinaryPath();
|
|
82
|
+
const fullArgs = [...args, "--env", getXmtpEnv(), "--env-file", getXmtpEnvFile()];
|
|
83
|
+
if (bin.endsWith(".js")) {
|
|
84
|
+
return execFileSync("node", [bin, ...fullArgs], {
|
|
85
|
+
encoding: "utf8",
|
|
86
|
+
timeout: 3e4
|
|
87
|
+
}).trim();
|
|
88
|
+
}
|
|
89
|
+
return execFileSync(bin, fullArgs, {
|
|
90
|
+
encoding: "utf8",
|
|
91
|
+
timeout: 3e4
|
|
92
|
+
}).trim();
|
|
93
|
+
}
|
|
94
|
+
function execXmtpJson(args) {
|
|
95
|
+
const stdout = execXmtp([...args, "--json", "--log-level", "off"]);
|
|
96
|
+
return JSON.parse(stdout);
|
|
97
|
+
}
|
|
98
|
+
async function getXmtpClient() {
|
|
99
|
+
syncXmtpEnv();
|
|
100
|
+
const result = execXmtpJson(["client", "info"]);
|
|
101
|
+
const config = loadConfig();
|
|
102
|
+
if (!config.xmtpInboxId && result.inboxId) {
|
|
103
|
+
config.xmtpInboxId = result.inboxId;
|
|
104
|
+
saveConfig(config);
|
|
105
|
+
}
|
|
106
|
+
return result.inboxId;
|
|
107
|
+
}
|
|
108
|
+
async function createSyndicateGroup(_client, subdomain, publicChat = false) {
|
|
109
|
+
const creatorAddress = getAccount().address;
|
|
110
|
+
const result = execXmtpJson(
|
|
111
|
+
[
|
|
112
|
+
"conversations",
|
|
113
|
+
"create-group",
|
|
114
|
+
creatorAddress,
|
|
115
|
+
"--name",
|
|
116
|
+
subdomain,
|
|
117
|
+
"--description",
|
|
118
|
+
`Sherwood syndicate: ${subdomain}.sherwoodagent.eth`,
|
|
119
|
+
"--permissions",
|
|
120
|
+
"admin-only"
|
|
121
|
+
]
|
|
122
|
+
);
|
|
123
|
+
const groupId = result.id || result.conversationId || result.groupId;
|
|
124
|
+
if (!groupId) {
|
|
125
|
+
throw new Error("Failed to parse group ID from xmtp CLI output");
|
|
126
|
+
}
|
|
127
|
+
if (publicChat && process.env.DASHBOARD_SPECTATOR_ADDRESS) {
|
|
128
|
+
await addMember(groupId, process.env.DASHBOARD_SPECTATOR_ADDRESS);
|
|
129
|
+
}
|
|
130
|
+
cacheGroupId(subdomain, groupId);
|
|
131
|
+
return groupId;
|
|
132
|
+
}
|
|
133
|
+
async function getGroup(_client, subdomain) {
|
|
134
|
+
let groupId = getCachedGroupId(subdomain);
|
|
135
|
+
if (!groupId) {
|
|
136
|
+
groupId = await getTextRecord(subdomain, "xmtpGroupId");
|
|
137
|
+
if (groupId) {
|
|
138
|
+
cacheGroupId(subdomain, groupId);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (!groupId) {
|
|
142
|
+
throw new Error(
|
|
143
|
+
`No XMTP group found for syndicate "${subdomain}". Run "sherwood chat ${subdomain} init" to create one.`
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
return groupId;
|
|
147
|
+
}
|
|
148
|
+
async function addMember(groupId, address) {
|
|
149
|
+
execXmtp(["conversation", "add-members", groupId, address]);
|
|
150
|
+
}
|
|
151
|
+
async function removeMember(groupId, address) {
|
|
152
|
+
execXmtp(["conversation", "remove-members", groupId, address]);
|
|
153
|
+
}
|
|
154
|
+
async function sendEnvelope(groupId, envelope) {
|
|
155
|
+
const text = JSON.stringify(envelope);
|
|
156
|
+
execXmtp(["conversation", "send-text", groupId, text]);
|
|
157
|
+
}
|
|
158
|
+
async function sendMarkdown(groupId, markdown) {
|
|
159
|
+
const envelope = {
|
|
160
|
+
type: "MESSAGE",
|
|
161
|
+
from: getAccount().address,
|
|
162
|
+
text: markdown,
|
|
163
|
+
data: { format: "markdown" },
|
|
164
|
+
timestamp: Math.floor(Date.now() / 1e3)
|
|
165
|
+
};
|
|
166
|
+
await sendEnvelope(groupId, envelope);
|
|
167
|
+
}
|
|
168
|
+
async function sendReaction(groupId, messageId, emoji) {
|
|
169
|
+
const envelope = {
|
|
170
|
+
type: "REACTION",
|
|
171
|
+
from: getAccount().address,
|
|
172
|
+
data: { reference: messageId, emoji },
|
|
173
|
+
timestamp: Math.floor(Date.now() / 1e3)
|
|
174
|
+
};
|
|
175
|
+
await sendEnvelope(groupId, envelope);
|
|
176
|
+
}
|
|
177
|
+
async function streamMessages(groupId, onMessage) {
|
|
178
|
+
syncXmtpEnv();
|
|
179
|
+
const bin = getXmtpBinaryPath();
|
|
180
|
+
const args = [
|
|
181
|
+
"conversations",
|
|
182
|
+
"stream-all-messages",
|
|
183
|
+
"--json",
|
|
184
|
+
"--log-level",
|
|
185
|
+
"off",
|
|
186
|
+
"--env",
|
|
187
|
+
getXmtpEnv(),
|
|
188
|
+
"--env-file",
|
|
189
|
+
getXmtpEnvFile()
|
|
190
|
+
];
|
|
191
|
+
const proc = bin.endsWith(".js") ? spawn("node", [bin, ...args]) : spawn(bin, args);
|
|
192
|
+
let buffer = "";
|
|
193
|
+
proc.stdout.on("data", (chunk) => {
|
|
194
|
+
buffer += chunk.toString();
|
|
195
|
+
const lines = buffer.split("\n");
|
|
196
|
+
buffer = lines.pop() || "";
|
|
197
|
+
for (const line of lines) {
|
|
198
|
+
if (!line.trim()) continue;
|
|
199
|
+
try {
|
|
200
|
+
const msg = JSON.parse(line);
|
|
201
|
+
if (msg.conversationId === groupId) {
|
|
202
|
+
onMessage({
|
|
203
|
+
id: msg.id || "",
|
|
204
|
+
conversationId: msg.conversationId || "",
|
|
205
|
+
senderInboxId: msg.senderInboxId || "",
|
|
206
|
+
contentType: msg.contentType?.typeId || "text",
|
|
207
|
+
content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content),
|
|
208
|
+
sentAt: new Date(msg.sentAt || Date.now())
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
} catch {
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
return () => {
|
|
216
|
+
proc.kill("SIGTERM");
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
async function getRecentMessages(groupId, limit = 20) {
|
|
220
|
+
const raw = execXmtpJson([
|
|
221
|
+
"conversation",
|
|
222
|
+
"messages",
|
|
223
|
+
groupId
|
|
224
|
+
]);
|
|
225
|
+
const messages = (Array.isArray(raw) ? raw : []).map((m) => ({
|
|
226
|
+
id: String(m.id || ""),
|
|
227
|
+
conversationId: String(m.conversationId || ""),
|
|
228
|
+
senderInboxId: String(m.senderInboxId || ""),
|
|
229
|
+
contentType: String(
|
|
230
|
+
m.contentType?.typeId || "text"
|
|
231
|
+
),
|
|
232
|
+
content: typeof m.content === "string" ? m.content : JSON.stringify(m.content),
|
|
233
|
+
sentAt: new Date(m.sentAt || Date.now())
|
|
234
|
+
}));
|
|
235
|
+
return messages.slice(-limit);
|
|
236
|
+
}
|
|
237
|
+
async function getMembers(groupId) {
|
|
238
|
+
const raw = execXmtpJson([
|
|
239
|
+
"conversation",
|
|
240
|
+
"members",
|
|
241
|
+
groupId
|
|
242
|
+
]);
|
|
243
|
+
const levelMap = { 0: "member", 1: "admin", 2: "super_admin" };
|
|
244
|
+
return (Array.isArray(raw) ? raw : []).map((m) => ({
|
|
245
|
+
inboxId: String(m.inboxId || ""),
|
|
246
|
+
permissionLevel: levelMap[Number(m.permissionLevel)] || "member"
|
|
247
|
+
}));
|
|
248
|
+
}
|
|
249
|
+
export {
|
|
250
|
+
addMember,
|
|
251
|
+
createSyndicateGroup,
|
|
252
|
+
getGroup,
|
|
253
|
+
getMembers,
|
|
254
|
+
getRecentMessages,
|
|
255
|
+
getXmtpClient,
|
|
256
|
+
removeMember,
|
|
257
|
+
sendEnvelope,
|
|
258
|
+
sendMarkdown,
|
|
259
|
+
sendReaction,
|
|
260
|
+
streamMessages
|
|
261
|
+
};
|
|
262
|
+
//# sourceMappingURL=xmtp-4G7BTQZJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/xmtp.ts"],"sourcesContent":["/**\n * XMTP client and group operations for syndicate chat.\n *\n * Shells out to the @xmtp/cli binary instead of using @xmtp/node-sdk directly.\n * This avoids native binding (GLIBC) issues on Linux.\n */\n\nimport { execFileSync, spawn, execSync } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { homedir } from \"node:os\";\nimport {\n loadConfig,\n saveConfig,\n cacheGroupId,\n getCachedGroupId,\n} from \"./config.js\";\nimport { getTextRecord } from \"./ens.js\";\nimport { getNetwork } from \"./network.js\";\nimport type { ChatEnvelope } from \"./types.js\";\nimport { getAccount } from \"./client.js\";\n\n// ── Types ──\n\nexport interface XmtpMessage {\n id: string;\n conversationId: string;\n senderInboxId: string;\n contentType: string;\n content: string;\n sentAt: Date;\n}\n\nexport interface XmtpMember {\n inboxId: string;\n permissionLevel: string;\n}\n\n// ── Binary resolution ──\n\nlet _binaryPath: string | null = null;\n\nfunction getXmtpBinaryPath(): string {\n if (_binaryPath) return _binaryPath;\n\n // Try local node_modules/@xmtp/cli/bin/run.js relative to this file\n const searchPaths = [\n // From dist/ after build\n path.resolve(import.meta.dirname, \"..\", \"node_modules\", \"@xmtp\", \"cli\", \"bin\", \"run.js\"),\n // From src/ during dev\n path.resolve(import.meta.dirname, \"..\", \"..\", \"node_modules\", \"@xmtp\", \"cli\", \"bin\", \"run.js\"),\n // From cwd\n path.resolve(process.cwd(), \"node_modules\", \"@xmtp\", \"cli\", \"bin\", \"run.js\"),\n ];\n\n for (const p of searchPaths) {\n if (fs.existsSync(p)) {\n _binaryPath = p;\n return _binaryPath;\n }\n }\n\n // Fall back to system PATH\n try {\n const which = execSync(\"which xmtp\", { encoding: \"utf8\" }).trim();\n if (which) {\n _binaryPath = which;\n return _binaryPath;\n }\n } catch {\n // Not on PATH\n }\n\n throw new Error(\n \"XMTP CLI not found. Install with: npm install -g @xmtp/cli\",\n );\n}\n\n// ── Environment sync ──\n\nlet _synced = false;\n\nfunction syncXmtpEnv(): void {\n if (_synced) return;\n\n const config = loadConfig();\n if (!config.privateKey) {\n throw new Error(\n 'No private key configured. Run \"sherwood config set --private-key 0x...\"',\n );\n }\n\n const xmtpDir = path.join(homedir(), \".xmtp\");\n const envFile = path.join(xmtpDir, \".env\");\n const walletKey = config.privateKey.replace(/^0x/, \"\");\n\n // Check if already synced\n if (fs.existsSync(envFile)) {\n const existing = fs.readFileSync(envFile, \"utf8\");\n if (existing.includes(`XMTP_WALLET_KEY=${walletKey}`)) {\n _synced = true;\n return;\n }\n }\n\n // Write env file\n fs.mkdirSync(xmtpDir, { recursive: true });\n const content = [\n `XMTP_WALLET_KEY=${walletKey}`,\n `XMTP_DB_ENCRYPTION_KEY=${config.dbEncryptionKey}`,\n `XMTP_ENV=${getXmtpEnv()}`,\n ].join(\"\\n\");\n fs.writeFileSync(envFile, content, { mode: 0o600 });\n\n _synced = true;\n}\n\nfunction getXmtpEnv(): string {\n return getNetwork() === \"base\" ? \"production\" : \"dev\";\n}\n\n// ── Subprocess runners ──\n\nfunction getXmtpEnvFile(): string {\n return path.join(homedir(), \".xmtp\", \".env\");\n}\n\nfunction execXmtp(args: string[]): string {\n syncXmtpEnv();\n const bin = getXmtpBinaryPath();\n const fullArgs = [...args, \"--env\", getXmtpEnv(), \"--env-file\", getXmtpEnvFile()];\n\n // Use node to run the bin/run.js if it's a .js file\n if (bin.endsWith(\".js\")) {\n return execFileSync(\"node\", [bin, ...fullArgs], {\n encoding: \"utf8\",\n timeout: 30_000,\n }).trim();\n }\n\n return execFileSync(bin, fullArgs, {\n encoding: \"utf8\",\n timeout: 30_000,\n }).trim();\n}\n\nfunction execXmtpJson<T>(args: string[]): T {\n const stdout = execXmtp([...args, \"--json\", \"--log-level\", \"off\"]);\n return JSON.parse(stdout) as T;\n}\n\n// ── Client ──\n\nexport async function getXmtpClient(): Promise<string> {\n syncXmtpEnv();\n\n const result = execXmtpJson<{ inboxId: string }>([\"client\", \"info\"]);\n\n // Cache inbox ID\n const config = loadConfig();\n if (!config.xmtpInboxId && result.inboxId) {\n config.xmtpInboxId = result.inboxId;\n saveConfig(config);\n }\n\n return result.inboxId;\n}\n\n// ── Group Creation ──\n\nexport async function createSyndicateGroup(\n _client: string,\n subdomain: string,\n publicChat: boolean = false,\n): Promise<string> {\n // CLI requires at least one member address; use creator's own address\n // (creator is auto-added as super admin regardless)\n const creatorAddress = getAccount().address;\n const result = execXmtpJson<{ id?: string; conversationId?: string; groupId?: string }>(\n [\n \"conversations\",\n \"create-group\",\n creatorAddress,\n \"--name\",\n subdomain,\n \"--description\",\n `Sherwood syndicate: ${subdomain}.sherwoodagent.eth`,\n \"--permissions\",\n \"admin-only\",\n ],\n );\n\n const groupId = result.id || result.conversationId || result.groupId;\n if (!groupId) {\n throw new Error(\"Failed to parse group ID from xmtp CLI output\");\n }\n\n // Add spectator if requested\n if (publicChat && process.env.DASHBOARD_SPECTATOR_ADDRESS) {\n await addMember(groupId, process.env.DASHBOARD_SPECTATOR_ADDRESS);\n }\n\n // Cache locally\n cacheGroupId(subdomain, groupId);\n\n return groupId;\n}\n\n// ── Group Lookup ──\n\nexport async function getGroup(\n _client: string,\n subdomain: string,\n): Promise<string> {\n // Try local cache first\n let groupId = getCachedGroupId(subdomain);\n\n // Fall back to on-chain ENS text record\n if (!groupId) {\n groupId = await getTextRecord(subdomain, \"xmtpGroupId\");\n if (groupId) {\n cacheGroupId(subdomain, groupId);\n }\n }\n\n if (!groupId) {\n throw new Error(\n `No XMTP group found for syndicate \"${subdomain}\". Run \"sherwood chat ${subdomain} init\" to create one.`,\n );\n }\n\n return groupId;\n}\n\n// ── Member Management ──\n\nexport async function addMember(\n groupId: string,\n address: string,\n): Promise<void> {\n execXmtp([\"conversation\", \"add-members\", groupId, address]);\n}\n\nexport async function removeMember(\n groupId: string,\n address: string,\n): Promise<void> {\n execXmtp([\"conversation\", \"remove-members\", groupId, address]);\n}\n\n// ── Messaging ──\n\nexport async function sendEnvelope(\n groupId: string,\n envelope: ChatEnvelope,\n): Promise<void> {\n const text = JSON.stringify(envelope);\n execXmtp([\"conversation\", \"send-text\", groupId, text]);\n}\n\nexport async function sendMarkdown(\n groupId: string,\n markdown: string,\n): Promise<void> {\n const envelope: ChatEnvelope = {\n type: \"MESSAGE\",\n from: getAccount().address,\n text: markdown,\n data: { format: \"markdown\" },\n timestamp: Math.floor(Date.now() / 1000),\n };\n await sendEnvelope(groupId, envelope);\n}\n\nexport async function sendReaction(\n groupId: string,\n messageId: string,\n emoji: string,\n): Promise<void> {\n const envelope: ChatEnvelope = {\n type: \"REACTION\",\n from: getAccount().address,\n data: { reference: messageId, emoji },\n timestamp: Math.floor(Date.now() / 1000),\n };\n await sendEnvelope(groupId, envelope);\n}\n\n// ── Streaming ──\n\nexport async function streamMessages(\n groupId: string,\n onMessage: (msg: XmtpMessage) => void,\n): Promise<() => void> {\n syncXmtpEnv();\n const bin = getXmtpBinaryPath();\n\n const args = [\n \"conversations\",\n \"stream-all-messages\",\n \"--json\",\n \"--log-level\",\n \"off\",\n \"--env\",\n getXmtpEnv(),\n \"--env-file\",\n getXmtpEnvFile(),\n ];\n\n const proc = bin.endsWith(\".js\")\n ? spawn(\"node\", [bin, ...args])\n : spawn(bin, args);\n\n let buffer = \"\";\n proc.stdout.on(\"data\", (chunk: Buffer) => {\n buffer += chunk.toString();\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() || \"\";\n for (const line of lines) {\n if (!line.trim()) continue;\n try {\n const msg = JSON.parse(line);\n // Filter to our group\n if (msg.conversationId === groupId) {\n onMessage({\n id: msg.id || \"\",\n conversationId: msg.conversationId || \"\",\n senderInboxId: msg.senderInboxId || \"\",\n contentType: msg.contentType?.typeId || \"text\",\n content: typeof msg.content === \"string\" ? msg.content : JSON.stringify(msg.content),\n sentAt: new Date(msg.sentAt || Date.now()),\n });\n }\n } catch {\n // Skip unparseable lines\n }\n }\n });\n\n // Return cleanup function\n return () => {\n proc.kill(\"SIGTERM\");\n };\n}\n\n// ── Message History ──\n\nexport async function getRecentMessages(\n groupId: string,\n limit: number = 20,\n): Promise<XmtpMessage[]> {\n const raw = execXmtpJson<Array<Record<string, unknown>>>([\n \"conversation\",\n \"messages\",\n groupId,\n ]);\n\n const messages: XmtpMessage[] = (Array.isArray(raw) ? raw : []).map((m) => ({\n id: String(m.id || \"\"),\n conversationId: String(m.conversationId || \"\"),\n senderInboxId: String(m.senderInboxId || \"\"),\n contentType: String(\n (m.contentType as Record<string, unknown>)?.typeId || \"text\",\n ),\n content:\n typeof m.content === \"string\" ? m.content : JSON.stringify(m.content),\n sentAt: new Date((m.sentAt as string) || Date.now()),\n }));\n\n return messages.slice(-limit);\n}\n\n// ── Members ──\n\nexport async function getMembers(\n groupId: string,\n): Promise<XmtpMember[]> {\n const raw = execXmtpJson<Array<Record<string, unknown>>>([\n \"conversation\",\n \"members\",\n groupId,\n ]);\n\n // permissionLevel from CLI: 0 = member, 1 = admin, 2 = super_admin\n const levelMap: Record<number, string> = { 0: \"member\", 1: \"admin\", 2: \"super_admin\" };\n return (Array.isArray(raw) ? raw : []).map((m) => ({\n inboxId: String(m.inboxId || \"\"),\n permissionLevel: levelMap[Number(m.permissionLevel)] || \"member\",\n }));\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sherwoodagent/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "CLI for agent-managed investment syndicates — onchain DeFi syndicates with XMTP chat",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@inquirer/prompts": "^8.3.2",
|
|
32
|
-
"@xmtp/
|
|
32
|
+
"@xmtp/cli": "0.2.0",
|
|
33
33
|
"agent0-sdk": "^1.7.0",
|
|
34
34
|
"chalk": "^5.3.0",
|
|
35
35
|
"commander": "^12.1.0",
|
|
@@ -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 *\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 { isText, isMarkdown, isReaction } from \"@xmtp/node-sdk\";\nimport type { DecodedMessage, Reaction } from \"@xmtp/node-sdk\";\nimport { PermissionLevel } from \"@xmtp/node-bindings\";\n\n// Lazy-load XMTP to avoid breaking non-chat commands when native bindings are 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: DecodedMessage): string {\n const time = chalk.dim(`[${formatTimestamp(msg.sentAt)}]`);\n const sender = chalk.dim(truncateAddress(msg.senderInboxId));\n\n if (isReaction(msg)) {\n const reaction = msg.content as Reaction;\n return `${time} ${sender} reacted ${reaction.content} to ${truncateAddress(reaction.reference)}`;\n }\n\n if (isMarkdown(msg)) {\n return `${time} ${sender}\\n${msg.content as string}`;\n }\n\n if (isText(msg)) {\n const text = msg.content as string;\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 === \"MESSAGE\") {\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 return `${time} ${sender}: ${msg.fallback || \"[unsupported content]\"}`;\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 await 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 group.members();\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 === PermissionLevel.SuperAdmin\n ? chalk.yellow(\" (super admin)\")\n : member.permissionLevel === 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 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 try {\n const xmtp = await loadXmtp();\n const client = await xmtp.getXmtpClient();\n await client.conversations.sync();\n const existing = await client.conversations.getConversationById(existingId);\n if (existing) {\n spinner.succeed(\"XMTP group already exists for this syndicate\");\n console.log(chalk.dim(` Group ID: ${existingId}`));\n return;\n }\n } catch {\n // Can't verify — fall through to suggest --force\n }\n spinner.warn(\"Found stale group ID — group is not accessible\");\n console.log(chalk.dim(\" Use --force to create a new group\"));\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 ⚠ 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, 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 \"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":["xmtp","client"]}
|
package/dist/xmtp-2UPH45XE.js
DELETED
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
cacheGroupId,
|
|
3
|
-
getAccount,
|
|
4
|
-
getCachedGroupId,
|
|
5
|
-
getTextRecord,
|
|
6
|
-
loadConfig,
|
|
7
|
-
saveConfig
|
|
8
|
-
} from "./chunk-CR3ZNOB7.js";
|
|
9
|
-
|
|
10
|
-
// src/lib/xmtp.ts
|
|
11
|
-
import {
|
|
12
|
-
Client,
|
|
13
|
-
IdentifierKind
|
|
14
|
-
} from "@xmtp/node-sdk";
|
|
15
|
-
import { ReactionAction, ReactionSchema } from "@xmtp/node-bindings";
|
|
16
|
-
function createSigner() {
|
|
17
|
-
return {
|
|
18
|
-
type: "EOA",
|
|
19
|
-
getIdentifier: () => ({
|
|
20
|
-
identifier: getAccount().address,
|
|
21
|
-
identifierKind: IdentifierKind.Ethereum
|
|
22
|
-
}),
|
|
23
|
-
signMessage: async (message) => {
|
|
24
|
-
const account = getAccount();
|
|
25
|
-
const sig = await account.signMessage({ message });
|
|
26
|
-
return Buffer.from(sig.slice(2), "hex");
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
var _client = null;
|
|
31
|
-
async function getXmtpClient() {
|
|
32
|
-
if (_client) return _client;
|
|
33
|
-
const config = loadConfig();
|
|
34
|
-
const signer = createSigner();
|
|
35
|
-
const keyBytes = new Uint8Array(
|
|
36
|
-
Buffer.from(config.dbEncryptionKey.replace(/^0x/, ""), "hex")
|
|
37
|
-
);
|
|
38
|
-
_client = await Client.create(signer, {
|
|
39
|
-
dbEncryptionKey: keyBytes
|
|
40
|
-
});
|
|
41
|
-
if (!config.xmtpInboxId) {
|
|
42
|
-
config.xmtpInboxId = _client.inboxId;
|
|
43
|
-
saveConfig(config);
|
|
44
|
-
}
|
|
45
|
-
return _client;
|
|
46
|
-
}
|
|
47
|
-
async function createSyndicateGroup(client, subdomain, publicChat = false) {
|
|
48
|
-
const group = await client.conversations.createGroupWithIdentifiers(
|
|
49
|
-
[],
|
|
50
|
-
// no other members yet; creator is auto-added as super admin
|
|
51
|
-
{
|
|
52
|
-
groupName: subdomain,
|
|
53
|
-
groupDescription: `Sherwood syndicate: ${subdomain}.sherwoodagent.eth`
|
|
54
|
-
}
|
|
55
|
-
);
|
|
56
|
-
if (publicChat && process.env.DASHBOARD_SPECTATOR_ADDRESS) {
|
|
57
|
-
const spectatorIdentifier = {
|
|
58
|
-
identifier: process.env.DASHBOARD_SPECTATOR_ADDRESS,
|
|
59
|
-
identifierKind: IdentifierKind.Ethereum
|
|
60
|
-
};
|
|
61
|
-
await group.addMembersByIdentifiers([spectatorIdentifier]);
|
|
62
|
-
}
|
|
63
|
-
cacheGroupId(subdomain, group.id);
|
|
64
|
-
return group.id;
|
|
65
|
-
}
|
|
66
|
-
async function getGroup(client, subdomain) {
|
|
67
|
-
let groupId = getCachedGroupId(subdomain);
|
|
68
|
-
if (!groupId) {
|
|
69
|
-
groupId = await getTextRecord(subdomain, "xmtpGroupId");
|
|
70
|
-
if (groupId) {
|
|
71
|
-
cacheGroupId(subdomain, groupId);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
if (!groupId) {
|
|
75
|
-
throw new Error(
|
|
76
|
-
`No XMTP group found for syndicate "${subdomain}". Run "sherwood chat ${subdomain} init" to create one.`
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
await client.conversations.sync();
|
|
80
|
-
const conversation = await client.conversations.getConversationById(groupId);
|
|
81
|
-
if (!conversation) {
|
|
82
|
-
throw new Error(
|
|
83
|
-
`XMTP group "${groupId}" not found. You may not be a member of this group.`
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
return conversation;
|
|
87
|
-
}
|
|
88
|
-
async function addMember(group, address) {
|
|
89
|
-
const identifier = {
|
|
90
|
-
identifier: address,
|
|
91
|
-
identifierKind: IdentifierKind.Ethereum
|
|
92
|
-
};
|
|
93
|
-
await group.addMembersByIdentifiers([identifier]);
|
|
94
|
-
}
|
|
95
|
-
async function removeMember(group, address) {
|
|
96
|
-
const identifier = {
|
|
97
|
-
identifier: address,
|
|
98
|
-
identifierKind: IdentifierKind.Ethereum
|
|
99
|
-
};
|
|
100
|
-
await group.removeMembersByIdentifiers([identifier]);
|
|
101
|
-
}
|
|
102
|
-
async function sendEnvelope(group, envelope) {
|
|
103
|
-
await group.sendText(JSON.stringify(envelope));
|
|
104
|
-
}
|
|
105
|
-
async function sendMarkdown(group, markdown) {
|
|
106
|
-
await group.sendMarkdown(markdown);
|
|
107
|
-
}
|
|
108
|
-
async function sendReaction(group, messageId, emoji) {
|
|
109
|
-
await group.sendReaction({
|
|
110
|
-
reference: messageId,
|
|
111
|
-
referenceInboxId: "",
|
|
112
|
-
action: ReactionAction.Added,
|
|
113
|
-
schema: ReactionSchema.Unicode,
|
|
114
|
-
content: emoji
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
async function streamMessages(group, onMessage) {
|
|
118
|
-
const stream = await group.stream({
|
|
119
|
-
onValue: onMessage
|
|
120
|
-
});
|
|
121
|
-
return async () => {
|
|
122
|
-
await stream.return();
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
async function getRecentMessages(group, limit = 20) {
|
|
126
|
-
await group.sync();
|
|
127
|
-
const messages = await group.messages({ limit });
|
|
128
|
-
return messages;
|
|
129
|
-
}
|
|
130
|
-
export {
|
|
131
|
-
addMember,
|
|
132
|
-
createSyndicateGroup,
|
|
133
|
-
getGroup,
|
|
134
|
-
getRecentMessages,
|
|
135
|
-
getXmtpClient,
|
|
136
|
-
removeMember,
|
|
137
|
-
sendEnvelope,
|
|
138
|
-
sendMarkdown,
|
|
139
|
-
sendReaction,
|
|
140
|
-
streamMessages
|
|
141
|
-
};
|
|
142
|
-
//# sourceMappingURL=xmtp-2UPH45XE.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/xmtp.ts"],"sourcesContent":["/**\n * XMTP client and group operations for syndicate chat.\n *\n * Uses the wallet's PRIVATE_KEY as the XMTP identity signer.\n * Groups are creator-managed (super admin only) with E2E encryption via MLS.\n */\n\nimport {\n Client,\n type Signer,\n type Identifier,\n IdentifierKind,\n type Group,\n type DecodedMessage,\n} from \"@xmtp/node-sdk\";\nimport { ReactionAction, ReactionSchema } from \"@xmtp/node-bindings\";\nimport { getAccount } from \"./client.js\";\nimport {\n loadConfig,\n saveConfig,\n cacheGroupId,\n getCachedGroupId,\n} from \"./config.js\";\nimport { getTextRecord } from \"./ens.js\";\nimport type { ChatEnvelope } from \"./types.js\";\n\n// ── Signer ──\n\nfunction createSigner(): Signer {\n return {\n type: \"EOA\" as const,\n getIdentifier: () => ({\n identifier: getAccount().address,\n identifierKind: IdentifierKind.Ethereum,\n }),\n signMessage: async (message: string) => {\n const account = getAccount();\n const sig = await account.signMessage({ message });\n return Buffer.from(sig.slice(2), \"hex\");\n },\n };\n}\n\n// ── Client ──\n\nlet _client: Client | null = null;\n\nexport async function getXmtpClient(): Promise<Client> {\n if (_client) return _client;\n\n const config = loadConfig();\n const signer = createSigner();\n\n const keyBytes = new Uint8Array(\n Buffer.from(config.dbEncryptionKey.replace(/^0x/, \"\"), \"hex\"),\n );\n\n _client = await Client.create(signer, {\n dbEncryptionKey: keyBytes,\n });\n\n // Cache inbox ID\n if (!config.xmtpInboxId) {\n config.xmtpInboxId = _client.inboxId;\n saveConfig(config);\n }\n\n return _client;\n}\n\n// ── Group Creation ──\n\nexport async function createSyndicateGroup(\n client: Client,\n subdomain: string,\n publicChat: boolean = false,\n): Promise<string> {\n // Create group with creator-only permissions\n const group = await client.conversations.createGroupWithIdentifiers(\n [], // no other members yet; creator is auto-added as super admin\n {\n groupName: subdomain,\n groupDescription: `Sherwood syndicate: ${subdomain}.sherwoodagent.eth`,\n },\n );\n\n // If public chat, add spectator bot for dashboard integration\n if (publicChat && process.env.DASHBOARD_SPECTATOR_ADDRESS) {\n const spectatorIdentifier: Identifier = {\n identifier: process.env.DASHBOARD_SPECTATOR_ADDRESS,\n identifierKind: IdentifierKind.Ethereum,\n };\n await (group as Group).addMembersByIdentifiers([spectatorIdentifier]);\n }\n\n // Cache locally\n cacheGroupId(subdomain, group.id);\n\n return group.id;\n}\n\n// ── Group Lookup ──\n\nexport async function getGroup(\n client: Client,\n subdomain: string,\n): Promise<Group> {\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 // Sync conversations to make sure we have latest\n await client.conversations.sync();\n\n const conversation = await client.conversations.getConversationById(groupId);\n if (!conversation) {\n throw new Error(\n `XMTP group \"${groupId}\" not found. You may not be a member of this group.`,\n );\n }\n\n return conversation as Group;\n}\n\n// ── Member Management ──\n\nexport async function addMember(\n group: Group,\n address: string,\n): Promise<void> {\n const identifier: Identifier = {\n identifier: address,\n identifierKind: IdentifierKind.Ethereum,\n };\n await group.addMembersByIdentifiers([identifier]);\n}\n\nexport async function removeMember(\n group: Group,\n address: string,\n): Promise<void> {\n const identifier: Identifier = {\n identifier: address,\n identifierKind: IdentifierKind.Ethereum,\n };\n await group.removeMembersByIdentifiers([identifier]);\n}\n\n// ── Messaging ──\n\nexport async function sendEnvelope(\n group: Group,\n envelope: ChatEnvelope,\n): Promise<void> {\n await group.sendText(JSON.stringify(envelope));\n}\n\nexport async function sendMarkdown(\n group: Group,\n markdown: string,\n): Promise<void> {\n await group.sendMarkdown(markdown);\n}\n\nexport async function sendReaction(\n group: Group,\n messageId: string,\n emoji: string,\n): Promise<void> {\n await group.sendReaction({\n reference: messageId,\n referenceInboxId: \"\",\n action: ReactionAction.Added,\n schema: ReactionSchema.Unicode,\n content: emoji,\n });\n}\n\n// ── Streaming ──\n\nexport async function streamMessages(\n group: Group,\n onMessage: (msg: DecodedMessage) => void,\n): Promise<() => void> {\n const stream = await group.stream({\n onValue: onMessage,\n });\n\n // Return cleanup function\n return async () => {\n await stream.return();\n };\n}\n\n// ── Message History ──\n\nexport async function getRecentMessages(\n group: Group,\n limit: number = 20,\n): Promise<DecodedMessage[]> {\n await group.sync();\n const messages = await group.messages({ limit });\n return messages;\n}\n"],"mappings":"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","names":[]}
|