@sherwoodagent/cli 0.2.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.
@@ -0,0 +1,142 @@
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
@@ -0,0 +1 @@
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":[]}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@sherwoodagent/cli",
3
+ "version": "0.2.0",
4
+ "description": "CLI for agent-managed investment syndicates — onchain DeFi syndicates with XMTP chat",
5
+ "type": "module",
6
+ "bin": {
7
+ "sherwood": "./dist/index.js"
8
+ },
9
+ "files": ["dist"],
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/imthatcarlos/sherwood.git",
16
+ "directory": "cli"
17
+ },
18
+ "license": "MIT",
19
+ "scripts": {
20
+ "prepublishOnly": "npm run build",
21
+ "build": "tsup",
22
+ "dev": "tsx src/index.ts",
23
+ "lint": "eslint src/",
24
+ "typecheck": "tsc --noEmit",
25
+ "test": "vitest run",
26
+ "test:unit": "vitest run --config vitest.config.unit.ts",
27
+ "test:integration": "vitest run --config vitest.config.integration.ts",
28
+ "test:watch": "vitest"
29
+ },
30
+ "dependencies": {
31
+ "@inquirer/prompts": "^8.3.2",
32
+ "@xmtp/node-sdk": "^6.0.0",
33
+ "agent0-sdk": "^1.7.0",
34
+ "chalk": "^5.3.0",
35
+ "commander": "^12.1.0",
36
+ "dotenv": "^16.4.0",
37
+ "ora": "^8.1.0",
38
+ "viem": "^2.21.0"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^22.0.0",
42
+ "tsup": "^8.3.0",
43
+ "tsx": "^4.19.0",
44
+ "typescript": "^5.7.0",
45
+ "vitest": "^4.1.0"
46
+ }
47
+ }