@botcord/botcord 0.1.2 → 0.1.3

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/api.ts ADDED
@@ -0,0 +1,3 @@
1
+ // api.ts — setup-only public surface (no runtime deps)
2
+ export { botCordPlugin } from "./src/channel.js";
3
+ export type { BotCordChannelConfig, BotCordAccountConfig } from "./src/types.js";
package/index.ts CHANGED
@@ -1,14 +1,6 @@
1
1
  /**
2
2
  * @botcord/botcord — OpenClaw plugin for BotCord A2A messaging protocol.
3
- *
4
- * Registers:
5
- * - Channel plugin (botcord) with WebSocket + polling gateway
6
- * - Agent tools: botcord_send, botcord_upload, botcord_rooms, botcord_topics, botcord_contacts, botcord_account, botcord_directory, botcord_payment, botcord_subscription, botcord_bind
7
- * - Commands: /botcord_healthcheck, /botcord_token, /botcord_bind
8
- * - CLI: openclaw botcord-register, openclaw botcord-import, openclaw botcord-export
9
3
  */
10
- import type { ChannelPlugin, OpenClawPluginApi } from "openclaw/plugin-sdk";
11
- import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
12
4
  import { botCordPlugin } from "./src/channel.js";
13
5
  import { setBotCordRuntime, setConfigGetter } from "./src/runtime.js";
14
6
  import { createMessagingTool, createUploadTool } from "./src/tools/messaging.js";
@@ -33,34 +25,36 @@ import {
33
25
  shouldRunBotCordLoopRiskCheck,
34
26
  } from "./src/loop-risk.js";
35
27
 
36
- const plugin = {
28
+ // Inline replacement for defineChannelPluginEntry from openclaw/plugin-sdk/core.
29
+ // Avoids missing dist artifacts in npm-installed openclaw (see openclaw#53685).
30
+ export default {
37
31
  id: "botcord",
38
32
  name: "BotCord",
39
33
  description: "BotCord A2A messaging protocol — secure agent-to-agent communication with Ed25519 signing",
40
- configSchema: emptyPluginConfigSchema(),
41
-
42
- register(api: OpenClawPluginApi) {
43
- // Store runtime reference and config getter
34
+ register(api: any) {
44
35
  setBotCordRuntime(api.runtime);
45
- setConfigGetter(() => api.config);
36
+ api.registerChannel({ plugin: botCordPlugin });
46
37
 
47
- // Register channel plugin
48
- api.registerChannel({ plugin: botCordPlugin as ChannelPlugin });
38
+ if (api.registrationMode !== "full") return;
39
+
40
+ setConfigGetter(() => api.config);
49
41
 
50
- // Register agent tools
42
+ // Agent tools — `as any` needed until tool execute() return types are
43
+ // migrated to the AgentToolResult<T> shape (P2 task).
51
44
  api.registerTool(createMessagingTool() as any);
45
+ api.registerTool(createUploadTool() as any);
52
46
  api.registerTool(createRoomsTool() as any);
53
47
  api.registerTool(createTopicsTool() as any);
54
48
  api.registerTool(createContactsTool() as any);
55
49
  api.registerTool(createAccountTool() as any);
56
50
  api.registerTool(createDirectoryTool() as any);
57
- api.registerTool(createUploadTool() as any);
58
51
  api.registerTool(createPaymentTool() as any);
59
52
  api.registerTool(createSubscriptionTool() as any);
60
53
  api.registerTool(createNotifyTool() as any);
61
54
  api.registerTool(createBindTool() as any);
62
55
 
63
- api.on("after_tool_call", async (event, ctx) => {
56
+ // Hooks
57
+ api.on("after_tool_call", async (event: any, ctx: any) => {
64
58
  if (ctx.toolName !== "botcord_send") return;
65
59
  if (!didBotCordSendSucceed(event.result, event.error)) return;
66
60
  recordBotCordOutboundText({
@@ -69,7 +63,7 @@ const plugin = {
69
63
  });
70
64
  });
71
65
 
72
- api.on("before_prompt_build", async (event, ctx) => {
66
+ api.on("before_prompt_build", async (event: any, ctx: any) => {
73
67
  if (!shouldRunBotCordLoopRiskCheck({
74
68
  channelId: ctx.channelId,
75
69
  prompt: event.prompt,
@@ -88,16 +82,16 @@ const plugin = {
88
82
  return { prependContext };
89
83
  }, { priority: 10 });
90
84
 
91
- api.on("session_end", async (_event, ctx) => {
85
+ api.on("session_end", async (_event: any, ctx: any) => {
92
86
  clearBotCordLoopRiskSession(ctx.sessionKey);
93
87
  });
94
88
 
95
- // Register commands
96
- api.registerCommand(createHealthcheckCommand() as any);
97
- api.registerCommand(createTokenCommand() as any);
98
- api.registerCommand(createBindCommand() as any);
89
+ // Commands
90
+ api.registerCommand(createHealthcheckCommand());
91
+ api.registerCommand(createTokenCommand());
92
+ api.registerCommand(createBindCommand());
99
93
 
100
- // Register CLI command
94
+ // CLI
101
95
  const registerCli = createRegisterCli();
102
96
  api.registerCli(registerCli.setup, { commands: registerCli.commands });
103
97
  },
@@ -105,5 +99,3 @@ const plugin = {
105
99
 
106
100
  export { TopicTracker } from "./src/topic-tracker.js";
107
101
  export type { TopicState, TopicInfo } from "./src/topic-tracker.js";
108
-
109
- export default plugin;
@@ -2,7 +2,7 @@
2
2
  "id": "botcord",
3
3
  "name": "BotCord",
4
4
  "description": "Secure agent-to-agent messaging via the BotCord A2A protocol (Ed25519 signed envelopes)",
5
- "version": "0.1.2",
5
+ "version": "0.1.3",
6
6
  "channels": [
7
7
  "botcord"
8
8
  ],
package/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "@botcord/botcord",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "OpenClaw channel plugin for BotCord A2A messaging protocol (Ed25519 signed envelopes)",
5
5
  "type": "module",
6
6
  "main": "./index.ts",
7
7
  "license": "MIT",
8
8
  "files": [
9
9
  "index.ts",
10
+ "setup-entry.ts",
11
+ "api.ts",
12
+ "runtime-api.ts",
10
13
  "src/",
11
14
  "!src/__tests__/",
12
15
  "skills/",
@@ -35,11 +38,11 @@
35
38
  "ws": "^8.18.0"
36
39
  },
37
40
  "peerDependencies": {
38
- "openclaw": ">=2026.1.0"
41
+ "openclaw": ">=2026.3.22"
39
42
  },
40
43
  "devDependencies": {
41
44
  "@types/ws": "^8.5.0",
42
- "openclaw": ">=2026.1.0",
45
+ "openclaw": "^2026.3.23",
43
46
  "typescript": "^5.9.3",
44
47
  "vitest": "^4.0.18"
45
48
  },
@@ -47,6 +50,12 @@
47
50
  "extensions": [
48
51
  "./index.ts"
49
52
  ],
53
+ "setupEntry": "./setup-entry.ts",
54
+ "install": {
55
+ "npmSpec": "@botcord/botcord",
56
+ "defaultChoice": "npm",
57
+ "minHostVersion": ">=2026.3.22"
58
+ },
50
59
  "channel": {
51
60
  "id": "botcord",
52
61
  "label": "BotCord",
package/runtime-api.ts ADDED
@@ -0,0 +1,7 @@
1
+ // runtime-api.ts — full runtime surface
2
+ export { botCordPlugin } from "./src/channel.js";
3
+ export { BotCordClient } from "./src/client.js";
4
+ export { getBotCordRuntime } from "./src/runtime.js";
5
+ export { TopicTracker } from "./src/topic-tracker.js";
6
+ export type { TopicState, TopicInfo } from "./src/topic-tracker.js";
7
+ export type { BotCordChannelConfig, BotCordAccountConfig, MessageAttachment } from "./src/types.js";
package/setup-entry.ts ADDED
@@ -0,0 +1,5 @@
1
+ // setup-entry.ts — lightweight entry for onboarding/config (no heavy deps like ws)
2
+ import { botCordPlugin } from "./src/channel.js";
3
+
4
+ // Inline replacement for defineSetupPluginEntry (just returns { plugin }).
5
+ export default { plugin: botCordPlugin };
package/src/channel.ts CHANGED
@@ -3,28 +3,56 @@
3
3
  * outbound (send via signed envelopes), gateway (start websocket/polling),
4
4
  * security, messaging, and status adapters.
5
5
  */
6
- import type { ChannelPlugin, ClawdbotConfig } from "openclaw/plugin-sdk";
7
- import {
8
- buildBaseChannelStatusSummary,
9
- createDefaultChannelRuntimeState,
10
- DEFAULT_ACCOUNT_ID,
11
- } from "openclaw/plugin-sdk";
6
+ import type { ChannelPlugin, OpenClawConfig as ClawdbotConfig } from "openclaw/plugin-sdk/core";
7
+ // Inlined from openclaw/plugin-sdk/status-helpers and account-id to avoid
8
+ // missing dist artifacts in npm-installed openclaw (see openclaw#53685).
9
+ const DEFAULT_ACCOUNT_ID = "default";
10
+
11
+ function createDefaultChannelRuntimeState(accountId: string) {
12
+ return {
13
+ accountId,
14
+ running: false as const,
15
+ lastStartAt: null,
16
+ lastStopAt: null,
17
+ lastError: null,
18
+ };
19
+ }
20
+
21
+ function buildBaseChannelStatusSummary(snapshot: {
22
+ configured?: boolean | null;
23
+ running?: boolean | null;
24
+ lastStartAt?: number | null;
25
+ lastStopAt?: number | null;
26
+ lastError?: string | null;
27
+ }) {
28
+ return {
29
+ configured: snapshot.configured ?? false,
30
+ running: snapshot.running ?? false,
31
+ lastStartAt: snapshot.lastStartAt ?? null,
32
+ lastStopAt: snapshot.lastStopAt ?? null,
33
+ lastError: snapshot.lastError ?? null,
34
+ };
35
+ }
12
36
  import {
13
37
  resolveChannelConfig,
14
38
  resolveAccounts,
15
39
  isAccountConfigured,
16
40
  displayPrefix,
17
41
  } from "./config.js";
18
- import { BotCordClient } from "./client.js";
19
- import { getBotCordRuntime } from "./runtime.js";
20
- import { startPoller, stopPoller } from "./poller.js";
21
- import { startWsClient, stopWsClient } from "./ws-client.js";
22
42
  import type {
23
43
  BotCordAccountConfig,
24
44
  BotCordChannelConfig,
25
45
  MessageAttachment,
26
46
  } from "./types.js";
27
47
 
48
+ // runtime.js is lightweight (no ws/network deps) — safe for static import.
49
+ // Heavy deps (client, poller, ws-client) are lazy-loaded so that setup-entry.ts
50
+ // can import botCordPlugin without pulling in ws at module level.
51
+ import { getBotCordRuntime } from "./runtime.js";
52
+ const lazyClient = () => import("./client.js").then((m) => m.BotCordClient);
53
+ const lazyPoller = () => import("./poller.js");
54
+ const lazyWsClient = () => import("./ws-client.js");
55
+
28
56
  // ── Types ────────────────────────────────────────────────────────
29
57
 
30
58
  export interface ResolvedBotCordAccount {
@@ -291,7 +319,8 @@ export const botCordPlugin: ChannelPlugin<ResolvedBotCordAccount> = {
291
319
  const account = resolveBotCordAccount({ cfg: cfg as CoreConfig, accountId });
292
320
  if (!account.configured || !account.agentId) return null;
293
321
  try {
294
- const client = new BotCordClient(account.config);
322
+ const Client = await lazyClient();
323
+ const client = new Client(account.config);
295
324
  const info = await client.resolve(account.agentId);
296
325
  return { kind: "user", id: info.agent_id, name: info.display_name || info.agent_id };
297
326
  } catch {
@@ -302,7 +331,8 @@ export const botCordPlugin: ChannelPlugin<ResolvedBotCordAccount> = {
302
331
  const account = resolveBotCordAccount({ cfg: cfg as CoreConfig, accountId });
303
332
  if (!account.configured) return [];
304
333
  try {
305
- const client = new BotCordClient(account.config);
334
+ const Client = await lazyClient();
335
+ const client = new Client(account.config);
306
336
  const contacts = await client.listContacts();
307
337
  const q = query?.trim().toLowerCase() ?? "";
308
338
  return contacts
@@ -326,7 +356,8 @@ export const botCordPlugin: ChannelPlugin<ResolvedBotCordAccount> = {
326
356
  const account = resolveBotCordAccount({ cfg: cfg as CoreConfig, accountId });
327
357
  if (!account.configured) return [];
328
358
  try {
329
- const client = new BotCordClient(account.config);
359
+ const Client = await lazyClient();
360
+ const client = new Client(account.config);
330
361
  const rooms = await client.listMyRooms();
331
362
  const q = query?.trim().toLowerCase() ?? "";
332
363
  return rooms
@@ -345,7 +376,8 @@ export const botCordPlugin: ChannelPlugin<ResolvedBotCordAccount> = {
345
376
  textChunkLimit: 4000,
346
377
  sendText: async ({ cfg, to, text, accountId }) => {
347
378
  const account = resolveBotCordAccount({ cfg: cfg as CoreConfig, accountId: accountId ?? undefined });
348
- const client = new BotCordClient(account.config);
379
+ const Client = await lazyClient();
380
+ const client = new Client(account.config);
349
381
  const result = await client.sendMessage(to, text);
350
382
  return {
351
383
  channel: "botcord",
@@ -355,7 +387,8 @@ export const botCordPlugin: ChannelPlugin<ResolvedBotCordAccount> = {
355
387
  },
356
388
  sendMedia: async ({ cfg, to, text, mediaUrl, accountId }) => {
357
389
  const account = resolveBotCordAccount({ cfg: cfg as CoreConfig, accountId: accountId ?? undefined });
358
- const client = new BotCordClient(account.config);
390
+ const Client = await lazyClient();
391
+ const client = new Client(account.config);
359
392
  const attachments: MessageAttachment[] = [];
360
393
  if (mediaUrl) {
361
394
  const filename = mediaUrl.split("/").pop() || "attachment";
@@ -402,11 +435,13 @@ export const botCordPlugin: ChannelPlugin<ResolvedBotCordAccount> = {
402
435
  const dp = displayPrefix(account.accountId, ctx.cfg);
403
436
  ctx.log?.info(`[${dp}] starting BotCord gateway (${account.deliveryMode} mode)`);
404
437
 
405
- const client = new BotCordClient(account.config);
438
+ const Client = await lazyClient();
439
+ const client = new Client(account.config);
406
440
  const mode = account.deliveryMode || "websocket";
407
441
 
408
442
  if (mode === "websocket") {
409
443
  ctx.log?.info(`[${dp}] starting WebSocket connection to Hub`);
444
+ const { startWsClient } = await lazyWsClient();
410
445
  startWsClient({
411
446
  client,
412
447
  accountId: account.accountId,
@@ -415,6 +450,7 @@ export const botCordPlugin: ChannelPlugin<ResolvedBotCordAccount> = {
415
450
  log: ctx.log,
416
451
  });
417
452
  } else {
453
+ const { startPoller } = await lazyPoller();
418
454
  startPoller({
419
455
  client,
420
456
  accountId: account.accountId,
@@ -438,6 +474,8 @@ export const botCordPlugin: ChannelPlugin<ResolvedBotCordAccount> = {
438
474
  ctx.abortSignal?.addEventListener("abort", () => resolve(), { once: true });
439
475
  });
440
476
 
477
+ const { stopWsClient } = await lazyWsClient();
478
+ const { stopPoller } = await lazyPoller();
441
479
  stopWsClient(account.accountId);
442
480
  stopPoller(account.accountId);
443
481
  ctx.setStatus({ accountId: ctx.accountId, running: false, lastStopAt: Date.now() });
@@ -10,8 +10,8 @@ export function createBindCommand() {
10
10
  "Bind this agent to a BotCord web dashboard account using a bind ticket.",
11
11
  acceptsArgs: true,
12
12
  requireAuth: true,
13
- handler: async (args?: string) => {
14
- const bindTicket = (args || "").trim();
13
+ handler: async (ctx: any) => {
14
+ const bindTicket = (ctx.args || "").trim();
15
15
  if (!bindTicket) {
16
16
  return { text: "[FAIL] Usage: /botcord_bind <bind_ticket>" };
17
17
  }
@@ -18,7 +18,7 @@ export function createHealthcheckCommand() {
18
18
  description: "Check BotCord integration health: config, Hub connectivity, token, delivery mode.",
19
19
  acceptsArgs: false,
20
20
  requireAuth: true,
21
- handler: async () => {
21
+ handler: async (_ctx: any) => {
22
22
  const lines: string[] = [];
23
23
  let pass = 0;
24
24
  let warn = 0;
@@ -25,8 +25,7 @@ import {
25
25
  } from "../config.js";
26
26
  import { normalizeAndValidateHubUrl } from "../hub-url.js";
27
27
  import { getBotCordRuntime } from "../runtime.js";
28
-
29
- const DEFAULT_HUB = "https://api.botcord.chat";
28
+ import { DEFAULT_HUB } from "../constants.js";
30
29
 
31
30
  interface RegisterResult {
32
31
  agentId: string;
@@ -15,7 +15,7 @@ export function createTokenCommand() {
15
15
  description: "Fetch and display the current BotCord JWT token.",
16
16
  acceptsArgs: false,
17
17
  requireAuth: true,
18
- handler: async () => {
18
+ handler: async (_ctx: any) => {
19
19
  const cfg = getAppConfig();
20
20
  if (!cfg) {
21
21
  return { text: "[FAIL] No OpenClaw configuration available" };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Release channel and environment-dependent defaults.
3
+ *
4
+ * In the stable (production) build this file ships as-is.
5
+ * The CI pipeline replaces the RELEASE_CHANNEL value with "beta"
6
+ * before publishing to the `beta` npm dist-tag, so that beta
7
+ * installations automatically point at the test hub.
8
+ */
9
+
10
+ export type ReleaseChannel = "stable" | "beta";
11
+
12
+ export const RELEASE_CHANNEL: ReleaseChannel = "stable";
13
+
14
+ const HUB_URLS: Record<ReleaseChannel, string> = {
15
+ stable: "https://api.botcord.chat",
16
+ beta: "https://test.botcord.chat",
17
+ };
18
+
19
+ export const DEFAULT_HUB = HUB_URLS[RELEASE_CHANNEL];
package/src/inbound.ts CHANGED
@@ -5,7 +5,20 @@
5
5
  import { getBotCordRuntime } from "./runtime.js";
6
6
  import { resolveAccountConfig } from "./config.js";
7
7
  import { buildSessionKey } from "./session-key.js";
8
- import { loadSessionStore } from "openclaw/plugin-sdk/mattermost";
8
+ import { readFileSync } from "node:fs";
9
+
10
+ // Simplified inline replacement for loadSessionStore from openclaw/plugin-sdk/mattermost.
11
+ // Avoids missing dist artifacts in npm-installed openclaw (see openclaw#53685).
12
+ function loadSessionStore(storePath: string): Record<string, any> {
13
+ try {
14
+ const raw = readFileSync(storePath, "utf-8");
15
+ if (!raw) return {};
16
+ const parsed = JSON.parse(raw);
17
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
18
+ } catch {
19
+ return {};
20
+ }
21
+ }
9
22
  import { sanitizeUntrustedContent, sanitizeSenderName } from "./sanitize.js";
10
23
  import { BotCordClient } from "./client.js";
11
24
  import { createBotCordReplyDispatcher } from "./reply-dispatcher.js";
@@ -227,7 +240,7 @@ async function handleA2AMessage(
227
240
  // Prompt the agent to notify its owner when receiving contact requests
228
241
  const notifyOwnerHint =
229
242
  envelope.type === "contact_request"
230
- ? `\n\n[You received a contact request from ${senderId}. Use the botcord_notify tool to inform your owner about this request so they can decide whether to accept or reject it. Include the sender's agent ID and any message they attached.]`
243
+ ? `\n\n[You received a contact request from ${sanitizedSender}. Use the botcord_notify tool to inform your owner about this request so they can decide whether to accept or reject it. Include the sender's agent ID and any message they attached.]`
231
244
  : "";
232
245
 
233
246
  const sanitizedContent = sanitizeUntrustedContent(rawContent);
package/src/runtime.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * Plugin runtime store — holds a reference to OpenClaw's PluginRuntime
3
3
  * and a config getter for tools/hooks that need the full app config.
4
4
  */
5
- import type { PluginRuntime } from "openclaw/plugin-sdk";
5
+ import type { PluginRuntime } from "openclaw/plugin-sdk/core";
6
6
 
7
7
  let runtime: PluginRuntime | null = null;
8
8
  let configGetter: (() => any) | null = null;
package/src/sanitize.ts CHANGED
@@ -33,11 +33,14 @@ export function sanitizeUntrustedContent(text: string): string {
33
33
  }
34
34
 
35
35
  /**
36
- * Sanitize sender name — must not contain newlines or structural markers.
36
+ * Sanitize sender name — must not contain newlines, structural markers,
37
+ * or characters that could break XML attribute boundaries.
37
38
  */
38
39
  export function sanitizeSenderName(name: string): string {
39
40
  return name
40
41
  .replace(/[\n\r]/g, " ")
41
42
  .replace(/\[/g, "⟦").replace(/\]/g, "⟧")
43
+ .replace(/"/g, "'")
44
+ .replace(/</g, "<").replace(/>/g, ">")
42
45
  .slice(0, 100);
43
46
  }
@@ -12,6 +12,7 @@ import { getConfig as getAppConfig } from "../runtime.js";
12
12
  export function createAccountTool() {
13
13
  return {
14
14
  name: "botcord_account",
15
+ label: "Manage Account",
15
16
  description:
16
17
  "Manage your own BotCord agent: view identity, update profile, get/set message policy, check message delivery status.",
17
18
  parameters: {
package/src/tools/bind.ts CHANGED
@@ -70,6 +70,7 @@ export async function executeBind(
70
70
  export function createBindTool() {
71
71
  return {
72
72
  name: "botcord_bind",
73
+ label: "Bind Dashboard",
73
74
  description:
74
75
  "Bind this BotCord agent to a user's web dashboard account using a bind ticket.",
75
76
  parameters: {
@@ -12,6 +12,7 @@ import { getConfig as getAppConfig } from "../runtime.js";
12
12
  export function createContactsTool() {
13
13
  return {
14
14
  name: "botcord_contacts",
15
+ label: "Manage Contacts",
15
16
  description: "Manage BotCord contacts: list/remove contacts, send/accept/reject requests, block/unblock agents.",
16
17
  parameters: {
17
18
  type: "object" as const,
@@ -12,6 +12,7 @@ import { getConfig as getAppConfig } from "../runtime.js";
12
12
  export function createDirectoryTool() {
13
13
  return {
14
14
  name: "botcord_directory",
15
+ label: "Search Directory",
15
16
  description: "Look up agents, discover public rooms, and query message history on BotCord.",
16
17
  parameters: {
17
18
  type: "object" as const,
@@ -77,6 +77,7 @@ async function uploadLocalFiles(
77
77
  export function createMessagingTool() {
78
78
  return {
79
79
  name: "botcord_send",
80
+ label: "Send Message",
80
81
  description:
81
82
  "Send a message to another agent or room via BotCord. " +
82
83
  "Use ag_* for direct messages, rm_* for rooms. " +
@@ -192,6 +193,7 @@ export function createMessagingTool() {
192
193
  export function createUploadTool() {
193
194
  return {
194
195
  name: "botcord_upload",
196
+ label: "Upload Files",
195
197
  description:
196
198
  "Upload one or more local files to BotCord Hub. " +
197
199
  "Returns file URLs that can be used later in botcord_send's file_urls parameter. " +
@@ -11,6 +11,7 @@ import { deliverNotification } from "../inbound.js";
11
11
  export function createNotifyTool() {
12
12
  return {
13
13
  name: "botcord_notify",
14
+ label: "Send Notification",
14
15
  description:
15
16
  "Send a notification to the owner's configured channel (e.g. Telegram, Discord). " +
16
17
  "Use this when you receive an important BotCord message that the owner should know about — " +
@@ -194,6 +194,7 @@ function sanitizeLedger(data: any): any {
194
194
  export function createPaymentTool(opts?: { name?: string; description?: string }) {
195
195
  return {
196
196
  name: opts?.name || "botcord_payment",
197
+ label: "Manage Payments",
197
198
  description:
198
199
  opts?.description ||
199
200
  "Manage BotCord coin payments and transactions: verify recipients, check balance, view ledger, transfer coins, create topups and withdrawals, cancel withdrawals, and query transaction status.",
@@ -12,6 +12,7 @@ import { getConfig as getAppConfig } from "../runtime.js";
12
12
  export function createRoomsTool() {
13
13
  return {
14
14
  name: "botcord_rooms",
15
+ label: "Manage Rooms",
15
16
  description:
16
17
  "Manage BotCord rooms: create, list, join, leave, update, invite/remove members, " +
17
18
  "set permissions, promote/transfer/dissolve, discover public rooms.",
@@ -47,6 +47,7 @@ function formatSubscriptionList(subscriptions: any[]): string {
47
47
  export function createSubscriptionTool() {
48
48
  return {
49
49
  name: "botcord_subscription",
50
+ label: "Manage Subscriptions",
50
51
  description:
51
52
  "Create subscription products priced in BotCord coin, subscribe to products, list active subscriptions, and manage cancellation or product archiving.",
52
53
  parameters: {
@@ -12,6 +12,7 @@ import { getConfig as getAppConfig } from "../runtime.js";
12
12
  export function createTopicsTool() {
13
13
  return {
14
14
  name: "botcord_topics",
15
+ label: "Manage Topics",
15
16
  description:
16
17
  "Manage topics within BotCord rooms. Topics are goal-driven conversation units " +
17
18
  "with lifecycle states: open → completed/failed/expired.",