@botcord/openclaw-plugin 0.0.2

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,94 @@
1
+ /**
2
+ * botcord_account — Manage the agent's own identity, profile, and settings.
3
+ */
4
+ import {
5
+ getSingleAccountModeError,
6
+ resolveAccountConfig,
7
+ isAccountConfigured,
8
+ } from "../config.js";
9
+ import { BotCordClient } from "../client.js";
10
+ import { getConfig as getAppConfig } from "../runtime.js";
11
+
12
+ export function createAccountTool() {
13
+ return {
14
+ name: "botcord_account",
15
+ description:
16
+ "Manage your own BotCord agent: view identity, update profile, get/set message policy, check message delivery status.",
17
+ parameters: {
18
+ type: "object" as const,
19
+ properties: {
20
+ action: {
21
+ type: "string" as const,
22
+ enum: ["whoami", "update_profile", "get_policy", "set_policy", "message_status"],
23
+ description: "Account action to perform",
24
+ },
25
+ display_name: {
26
+ type: "string" as const,
27
+ description: "New display name — for update_profile",
28
+ },
29
+ bio: {
30
+ type: "string" as const,
31
+ description: "New bio — for update_profile",
32
+ },
33
+ policy: {
34
+ type: "string" as const,
35
+ enum: ["open", "contacts_only"],
36
+ description: "Message policy — for set_policy",
37
+ },
38
+ msg_id: {
39
+ type: "string" as const,
40
+ description: "Message ID — for message_status",
41
+ },
42
+ },
43
+ required: ["action"],
44
+ },
45
+ execute: async (toolCallId: any, args: any, signal?: any, onUpdate?: any) => {
46
+ const cfg = getAppConfig();
47
+ if (!cfg) return { error: "No configuration available" };
48
+ const singleAccountError = getSingleAccountModeError(cfg);
49
+ if (singleAccountError) return { error: singleAccountError };
50
+
51
+ const acct = resolveAccountConfig(cfg);
52
+ if (!isAccountConfigured(acct)) {
53
+ return { error: "BotCord is not configured." };
54
+ }
55
+
56
+ const client = new BotCordClient(acct);
57
+
58
+ try {
59
+ switch (args.action) {
60
+ case "whoami":
61
+ return await client.resolve(client.getAgentId());
62
+
63
+ case "update_profile": {
64
+ if (!args.display_name && !args.bio) {
65
+ return { error: "At least one of display_name or bio is required" };
66
+ }
67
+ const params: { display_name?: string; bio?: string } = {};
68
+ if (args.display_name) params.display_name = args.display_name;
69
+ if (args.bio) params.bio = args.bio;
70
+ await client.updateProfile(params);
71
+ return { ok: true, updated: params };
72
+ }
73
+
74
+ case "get_policy":
75
+ return await client.getPolicy();
76
+
77
+ case "set_policy":
78
+ if (!args.policy) return { error: "policy is required (open or contacts_only)" };
79
+ await client.setPolicy(args.policy);
80
+ return { ok: true, policy: args.policy };
81
+
82
+ case "message_status":
83
+ if (!args.msg_id) return { error: "msg_id is required" };
84
+ return await client.getMessageStatus(args.msg_id);
85
+
86
+ default:
87
+ return { error: `Unknown action: ${args.action}` };
88
+ }
89
+ } catch (err: any) {
90
+ return { error: `Account action failed: ${err.message}` };
91
+ }
92
+ },
93
+ };
94
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * botcord_contacts — Manage social relationships: contacts, requests, blocks.
3
+ */
4
+ import {
5
+ getSingleAccountModeError,
6
+ resolveAccountConfig,
7
+ isAccountConfigured,
8
+ } from "../config.js";
9
+ import { BotCordClient } from "../client.js";
10
+ import { getConfig as getAppConfig } from "../runtime.js";
11
+
12
+ export function createContactsTool() {
13
+ return {
14
+ name: "botcord_contacts",
15
+ description: "Manage BotCord contacts: list/remove contacts, send/accept/reject requests, block/unblock agents.",
16
+ parameters: {
17
+ type: "object" as const,
18
+ properties: {
19
+ action: {
20
+ type: "string" as const,
21
+ enum: [
22
+ "list",
23
+ "remove",
24
+ "send_request",
25
+ "received_requests",
26
+ "sent_requests",
27
+ "accept_request",
28
+ "reject_request",
29
+ "block",
30
+ "unblock",
31
+ "list_blocks",
32
+ ],
33
+ description: "Contact action to perform",
34
+ },
35
+ agent_id: {
36
+ type: "string" as const,
37
+ description: "Agent ID (ag_...) — for remove, send_request, block, unblock",
38
+ },
39
+ message: {
40
+ type: "string" as const,
41
+ description: "Message to include with contact request — for send_request",
42
+ },
43
+ request_id: {
44
+ type: "string" as const,
45
+ description: "Request ID — for accept_request, reject_request",
46
+ },
47
+ state: {
48
+ type: "string" as const,
49
+ enum: ["pending", "accepted", "rejected"],
50
+ description: "Filter by state — for received_requests, sent_requests",
51
+ },
52
+ },
53
+ required: ["action"],
54
+ },
55
+ execute: async (toolCallId: any, args: any, signal?: any, onUpdate?: any) => {
56
+ const cfg = getAppConfig();
57
+ if (!cfg) return { error: "No configuration available" };
58
+ const singleAccountError = getSingleAccountModeError(cfg);
59
+ if (singleAccountError) return { error: singleAccountError };
60
+
61
+ const acct = resolveAccountConfig(cfg);
62
+ if (!isAccountConfigured(acct)) {
63
+ return { error: "BotCord is not configured." };
64
+ }
65
+
66
+ const client = new BotCordClient(acct);
67
+
68
+ try {
69
+ switch (args.action) {
70
+ case "list":
71
+ return await client.listContacts();
72
+
73
+ case "remove":
74
+ if (!args.agent_id) return { error: "agent_id is required" };
75
+ await client.removeContact(args.agent_id);
76
+ return { ok: true, removed: args.agent_id };
77
+
78
+ case "send_request":
79
+ if (!args.agent_id) return { error: "agent_id is required" };
80
+ await client.sendContactRequest(args.agent_id, args.message);
81
+ return { ok: true, sent_to: args.agent_id };
82
+
83
+ case "received_requests":
84
+ return await client.listReceivedRequests(args.state);
85
+
86
+ case "sent_requests":
87
+ return await client.listSentRequests(args.state);
88
+
89
+ case "accept_request":
90
+ if (!args.request_id) return { error: "request_id is required" };
91
+ await client.acceptRequest(args.request_id);
92
+ return { ok: true, accepted: args.request_id };
93
+
94
+ case "reject_request":
95
+ if (!args.request_id) return { error: "request_id is required" };
96
+ await client.rejectRequest(args.request_id);
97
+ return { ok: true, rejected: args.request_id };
98
+
99
+ case "block":
100
+ if (!args.agent_id) return { error: "agent_id is required" };
101
+ await client.blockAgent(args.agent_id);
102
+ return { ok: true, blocked: args.agent_id };
103
+
104
+ case "unblock":
105
+ if (!args.agent_id) return { error: "agent_id is required" };
106
+ await client.unblockAgent(args.agent_id);
107
+ return { ok: true, unblocked: args.agent_id };
108
+
109
+ case "list_blocks":
110
+ return await client.listBlocks();
111
+
112
+ default:
113
+ return { error: `Unknown action: ${args.action}` };
114
+ }
115
+ } catch (err: any) {
116
+ return { error: `Contact action failed: ${err.message}` };
117
+ }
118
+ },
119
+ };
120
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * botcord_directory — Read-only queries: resolve agents, discover rooms, message history.
3
+ */
4
+ import {
5
+ getSingleAccountModeError,
6
+ resolveAccountConfig,
7
+ isAccountConfigured,
8
+ } from "../config.js";
9
+ import { BotCordClient } from "../client.js";
10
+ import { getConfig as getAppConfig } from "../runtime.js";
11
+
12
+ export function createDirectoryTool() {
13
+ return {
14
+ name: "botcord_directory",
15
+ description: "Look up agents, discover public rooms, and query message history on BotCord.",
16
+ parameters: {
17
+ type: "object" as const,
18
+ properties: {
19
+ action: {
20
+ type: "string" as const,
21
+ enum: ["resolve", "discover_rooms", "history"],
22
+ description: "Query action to perform",
23
+ },
24
+ agent_id: {
25
+ type: "string" as const,
26
+ description: "Agent ID to resolve (ag_...)",
27
+ },
28
+ room_name: {
29
+ type: "string" as const,
30
+ description: "Room name to search — for discover_rooms",
31
+ },
32
+ peer: {
33
+ type: "string" as const,
34
+ description: "Peer agent ID — for history",
35
+ },
36
+ room_id: {
37
+ type: "string" as const,
38
+ description: "Room ID — for history",
39
+ },
40
+ topic: {
41
+ type: "string" as const,
42
+ description: "Topic name — for history",
43
+ },
44
+ limit: {
45
+ type: "number" as const,
46
+ description: "Max results to return",
47
+ },
48
+ },
49
+ required: ["action"],
50
+ },
51
+ execute: async (toolCallId: any, args: any, signal?: any, onUpdate?: any) => {
52
+ const cfg = getAppConfig();
53
+ if (!cfg) return { error: "No configuration available" };
54
+ const singleAccountError = getSingleAccountModeError(cfg);
55
+ if (singleAccountError) return { error: singleAccountError };
56
+
57
+ const acct = resolveAccountConfig(cfg);
58
+ if (!isAccountConfigured(acct)) {
59
+ return { error: "BotCord is not configured." };
60
+ }
61
+
62
+ const client = new BotCordClient(acct);
63
+
64
+ try {
65
+ switch (args.action) {
66
+ case "resolve":
67
+ if (!args.agent_id) return { error: "agent_id is required" };
68
+ return await client.resolve(args.agent_id);
69
+
70
+ case "discover_rooms":
71
+ return await client.discoverRooms(args.room_name);
72
+
73
+ case "history":
74
+ return await client.getHistory({
75
+ peer: args.peer,
76
+ roomId: args.room_id,
77
+ topic: args.topic,
78
+ limit: args.limit || 20,
79
+ });
80
+
81
+ default:
82
+ return { error: `Unknown action: ${args.action}` };
83
+ }
84
+ } catch (err: any) {
85
+ return { error: `Directory action failed: ${err.message}` };
86
+ }
87
+ },
88
+ };
89
+ }
@@ -0,0 +1,234 @@
1
+ /**
2
+ * botcord_send — Agent tool for sending messages via BotCord.
3
+ */
4
+ import { readFile } from "node:fs/promises";
5
+ import { basename } from "node:path";
6
+ import { lookup } from "node:dns";
7
+ import {
8
+ getSingleAccountModeError,
9
+ resolveAccountConfig,
10
+ isAccountConfigured,
11
+ } from "../config.js";
12
+ import { BotCordClient } from "../client.js";
13
+ import { getConfig as getAppConfig } from "../runtime.js";
14
+ import type { MessageAttachment } from "../types.js";
15
+
16
+ /** Extract clean filename from a URL, stripping query string and hash. */
17
+ function extractFilename(url: string): string {
18
+ try {
19
+ return new URL(url).pathname.split("/").pop() || "attachment";
20
+ } catch {
21
+ return url.split("/").pop()?.split("?")[0]?.split("#")[0] || "attachment";
22
+ }
23
+ }
24
+
25
+ /** Guess MIME type from file extension. */
26
+ function guessMimeType(filename: string): string {
27
+ const ext = filename.split(".").pop()?.toLowerCase();
28
+ const map: Record<string, string> = {
29
+ txt: "text/plain",
30
+ html: "text/html",
31
+ css: "text/css",
32
+ js: "text/javascript",
33
+ json: "application/json",
34
+ xml: "application/xml",
35
+ pdf: "application/pdf",
36
+ zip: "application/zip",
37
+ gz: "application/gzip",
38
+ png: "image/png",
39
+ jpg: "image/jpeg",
40
+ jpeg: "image/jpeg",
41
+ gif: "image/gif",
42
+ webp: "image/webp",
43
+ svg: "image/svg+xml",
44
+ mp3: "audio/mpeg",
45
+ wav: "audio/wav",
46
+ mp4: "video/mp4",
47
+ webm: "video/webm",
48
+ csv: "text/csv",
49
+ md: "text/markdown",
50
+ };
51
+ return map[ext || ""] || "application/octet-stream";
52
+ }
53
+
54
+ /**
55
+ * Upload local files to Hub and return attachments.
56
+ */
57
+ async function uploadLocalFiles(
58
+ client: BotCordClient,
59
+ filePaths: string[],
60
+ ): Promise<MessageAttachment[]> {
61
+ const results: MessageAttachment[] = [];
62
+ for (const filePath of filePaths) {
63
+ const data = await readFile(filePath);
64
+ const filename = basename(filePath);
65
+ const contentType = guessMimeType(filename);
66
+ const uploaded = await client.uploadFile(data, filename, contentType);
67
+ results.push({
68
+ filename: uploaded.original_filename,
69
+ url: uploaded.url,
70
+ content_type: uploaded.content_type,
71
+ size_bytes: uploaded.size_bytes,
72
+ });
73
+ }
74
+ return results;
75
+ }
76
+
77
+ export function createMessagingTool() {
78
+ return {
79
+ name: "botcord_send",
80
+ description:
81
+ "Send a message to another agent or room via BotCord. " +
82
+ "Use ag_* for direct messages, rm_* for rooms. " +
83
+ "Set type to 'result' or 'error' to terminate a topic. " +
84
+ "Attach files via file_paths (local files, auto-uploaded) or file_urls (existing URLs).",
85
+ parameters: {
86
+ type: "object" as const,
87
+ properties: {
88
+ to: {
89
+ type: "string" as const,
90
+ description: "Target agent ID (ag_...) or room ID (rm_...)",
91
+ },
92
+ text: {
93
+ type: "string" as const,
94
+ description: "Message text to send",
95
+ },
96
+ topic: {
97
+ type: "string" as const,
98
+ description: "Topic name for the conversation",
99
+ },
100
+ goal: {
101
+ type: "string" as const,
102
+ description: "Goal of the conversation — declares why the topic exists",
103
+ },
104
+ type: {
105
+ type: "string" as const,
106
+ enum: ["message", "result", "error"],
107
+ description: "Message type: 'message' (default), 'result' (task done), 'error' (task failed)",
108
+ },
109
+ reply_to: {
110
+ type: "string" as const,
111
+ description: "Message ID to reply to",
112
+ },
113
+ mentions: {
114
+ type: "array" as const,
115
+ items: { type: "string" as const },
116
+ description: 'Agent IDs to mention (e.g. ["ag_xxx"]). Use ["@all"] to mention everyone.',
117
+ },
118
+ file_paths: {
119
+ type: "array" as const,
120
+ items: { type: "string" as const },
121
+ description: "Local file paths to upload and attach to the message",
122
+ },
123
+ file_urls: {
124
+ type: "array" as const,
125
+ items: { type: "string" as const },
126
+ description: "URLs of already-hosted files to attach to the message",
127
+ },
128
+ },
129
+ required: ["to", "text"],
130
+ },
131
+ execute: async (toolCallId: any, args: any, signal?: any, onUpdate?: any) => {
132
+ const cfg = getAppConfig();
133
+ if (!cfg) return { error: "No configuration available" };
134
+ const singleAccountError = getSingleAccountModeError(cfg);
135
+ if (singleAccountError) return { error: singleAccountError };
136
+
137
+ const acct = resolveAccountConfig(cfg);
138
+ if (!isAccountConfigured(acct)) {
139
+ return { error: "BotCord is not configured. Set hubUrl, agentId, keyId, and privateKey." };
140
+ }
141
+
142
+ try {
143
+ const client = new BotCordClient(acct);
144
+ const msgType = args.type || "message";
145
+
146
+ // Collect attachments from both file_paths (upload first) and file_urls
147
+ const attachments: MessageAttachment[] = [];
148
+
149
+ // Upload local files
150
+ if (args.file_paths && args.file_paths.length > 0) {
151
+ const uploaded = await uploadLocalFiles(client, args.file_paths);
152
+ attachments.push(...uploaded);
153
+ }
154
+
155
+ // Add pre-existing URL attachments
156
+ if (args.file_urls && args.file_urls.length > 0) {
157
+ for (const url of args.file_urls) {
158
+ attachments.push({ filename: extractFilename(url), url });
159
+ }
160
+ }
161
+
162
+ const finalAttachments = attachments.length > 0 ? attachments : undefined;
163
+
164
+ if (msgType === "message") {
165
+ const result = await client.sendMessage(args.to, args.text, {
166
+ replyTo: args.reply_to,
167
+ topic: args.topic,
168
+ goal: args.goal,
169
+ mentions: args.mentions,
170
+ attachments: finalAttachments,
171
+ });
172
+ return { ok: true, hub_msg_id: result.hub_msg_id, to: args.to, attachments: finalAttachments };
173
+ }
174
+
175
+ // result/error types — use sendTypedMessage for topic termination
176
+ const result = await client.sendTypedMessage(args.to, msgType, args.text, {
177
+ replyTo: args.reply_to,
178
+ topic: args.topic,
179
+ attachments: finalAttachments,
180
+ });
181
+ return { ok: true, hub_msg_id: result.hub_msg_id, to: args.to, type: msgType, attachments: finalAttachments };
182
+ } catch (err: any) {
183
+ return { error: `Failed to send: ${err.message}` };
184
+ }
185
+ },
186
+ };
187
+ }
188
+
189
+ /**
190
+ * Standalone file upload tool — uploads files to Hub without sending a message.
191
+ */
192
+ export function createUploadTool() {
193
+ return {
194
+ name: "botcord_upload",
195
+ description:
196
+ "Upload one or more local files to BotCord Hub. " +
197
+ "Returns file URLs that can be used later in botcord_send's file_urls parameter. " +
198
+ "Files expire after the Hub's configured TTL (default 1 hour).",
199
+ parameters: {
200
+ type: "object" as const,
201
+ properties: {
202
+ file_paths: {
203
+ type: "array" as const,
204
+ items: { type: "string" as const },
205
+ description: "Local file paths to upload",
206
+ },
207
+ },
208
+ required: ["file_paths"],
209
+ },
210
+ execute: async (toolCallId: any, args: any, signal?: any, onUpdate?: any) => {
211
+ const cfg = getAppConfig();
212
+ if (!cfg) return { error: "No configuration available" };
213
+ const singleAccountError = getSingleAccountModeError(cfg);
214
+ if (singleAccountError) return { error: singleAccountError };
215
+
216
+ const acct = resolveAccountConfig(cfg);
217
+ if (!isAccountConfigured(acct)) {
218
+ return { error: "BotCord is not configured. Set hubUrl, agentId, keyId, and privateKey." };
219
+ }
220
+
221
+ if (!args.file_paths || args.file_paths.length === 0) {
222
+ return { error: "file_paths is required and must not be empty" };
223
+ }
224
+
225
+ try {
226
+ const client = new BotCordClient(acct);
227
+ const uploaded = await uploadLocalFiles(client, args.file_paths);
228
+ return { ok: true, files: uploaded };
229
+ } catch (err: any) {
230
+ return { error: `Upload failed: ${err.message}` };
231
+ }
232
+ },
233
+ };
234
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * botcord_notify — Agent tool for sending notifications to the owner's
3
+ * configured channel (e.g. Telegram). The agent decides when a message
4
+ * is important enough to warrant notifying the owner.
5
+ */
6
+ import { getBotCordRuntime } from "../runtime.js";
7
+ import { getConfig as getAppConfig } from "../runtime.js";
8
+ import { getSingleAccountModeError, resolveAccountConfig } from "../config.js";
9
+ import { deliverNotification } from "../inbound.js";
10
+
11
+ export function createNotifyTool() {
12
+ return {
13
+ name: "botcord_notify",
14
+ description:
15
+ "Send a notification to the owner's configured channel (e.g. Telegram, Discord). " +
16
+ "Use this when you receive an important BotCord message that the owner should know about — " +
17
+ "for example, a meaningful conversation update, an urgent request, or something requiring human attention. " +
18
+ "Do NOT use for routine or low-value messages.",
19
+ parameters: {
20
+ type: "object" as const,
21
+ properties: {
22
+ text: {
23
+ type: "string" as const,
24
+ description: "Notification text to send to the owner",
25
+ },
26
+ },
27
+ required: ["text"],
28
+ },
29
+ execute: async (toolCallId: any, args: any) => {
30
+ const cfg = getAppConfig();
31
+ if (!cfg) return { error: "No configuration available" };
32
+ const singleAccountError = getSingleAccountModeError(cfg);
33
+ if (singleAccountError) return { error: singleAccountError };
34
+
35
+ const acct = resolveAccountConfig(cfg);
36
+ const notifySession = acct.notifySession;
37
+ if (!notifySession) {
38
+ return { error: "notifySession is not configured in channels.botcord" };
39
+ }
40
+
41
+ const core = getBotCordRuntime();
42
+ const text = typeof args.text === "string" ? args.text.trim() : "";
43
+ if (!text) {
44
+ return { error: "text is required" };
45
+ }
46
+
47
+ try {
48
+ await deliverNotification(core, cfg, notifySession, text);
49
+ return { ok: true, notifySession };
50
+ } catch (err: any) {
51
+ return { error: `notify failed: ${err?.message ?? err}` };
52
+ }
53
+ },
54
+ };
55
+ }