@botcord/botcord 0.2.2 → 0.2.4-beta.20260329125010
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/README.md +29 -4
- package/index.ts +3 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/skills/botcord/SKILL.md +66 -334
- package/skills/botcord-account/SKILL.md +121 -0
- package/skills/botcord-messaging/SKILL.md +189 -0
- package/skills/botcord-payment/SKILL.md +89 -0
- package/skills/botcord-social/SKILL.md +107 -0
- package/src/client.ts +29 -0
- package/src/commands/bind.ts +4 -3
- package/src/constants.ts +1 -1
- package/src/loop-risk.ts +2 -0
- package/src/tools/account.ts +34 -33
- package/src/tools/api.ts +54 -0
- package/src/tools/bind.ts +10 -29
- package/src/tools/contacts.ts +50 -41
- package/src/tools/directory.ts +10 -31
- package/src/tools/messaging.ts +25 -43
- package/src/tools/notify.ts +30 -32
- package/src/tools/payment.ts +45 -41
- package/src/tools/register.ts +5 -4
- package/src/tools/reset-credential.ts +6 -5
- package/src/tools/rooms.ts +75 -50
- package/src/tools/subscription.ts +59 -46
- package/src/tools/tool-result.ts +122 -0
- package/src/tools/topics.ts +34 -34
- package/src/tools/with-client.ts +93 -0
package/src/tools/payment.ts
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* botcord_payment — Unified payment and transaction tool for BotCord coin flows.
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
resolveAccountConfig,
|
|
7
|
-
isAccountConfigured,
|
|
8
|
-
} from "../config.js";
|
|
9
|
-
import { BotCordClient } from "../client.js";
|
|
10
|
-
import { attachTokenPersistence } from "../credentials.js";
|
|
11
|
-
import { getConfig as getAppConfig } from "../runtime.js";
|
|
4
|
+
import { withClient } from "./with-client.js";
|
|
5
|
+
import { validationError, dryRunResult } from "./tool-result.js";
|
|
12
6
|
import { formatCoinAmount } from "./coin-format.js";
|
|
13
7
|
import { executeTransfer, isPeerContact, formatFollowUpDeliverySummary } from "./payment-transfer.js";
|
|
14
8
|
|
|
@@ -288,34 +282,46 @@ export function createPaymentTool(opts?: { name?: string; description?: string }
|
|
|
288
282
|
type: "string" as const,
|
|
289
283
|
description: "Filter by transaction type — for ledger",
|
|
290
284
|
},
|
|
285
|
+
dry_run: {
|
|
286
|
+
type: "boolean" as const,
|
|
287
|
+
description: "Preview the request without executing. Returns the API call that would be made.",
|
|
288
|
+
},
|
|
291
289
|
},
|
|
292
290
|
required: ["action"],
|
|
293
291
|
},
|
|
294
292
|
execute: async (_toolCallId: any, args: any) => {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
293
|
+
return withClient(async (client) => {
|
|
294
|
+
// Dry-run for write operations
|
|
295
|
+
if (args.dry_run) {
|
|
296
|
+
switch (args.action) {
|
|
297
|
+
case "transfer":
|
|
298
|
+
if (!args.to_agent_id) return validationError("to_agent_id is required");
|
|
299
|
+
if (!args.amount_minor) return validationError("amount_minor is required");
|
|
300
|
+
return dryRunResult("POST", "/wallet/transfers", { to_agent_id: args.to_agent_id, amount_minor: args.amount_minor, memo: args.memo }) as any;
|
|
301
|
+
case "topup":
|
|
302
|
+
if (!args.amount_minor) return validationError("amount_minor is required");
|
|
303
|
+
return dryRunResult("POST", "/wallet/topups", { amount_minor: args.amount_minor, channel: args.channel }) as any;
|
|
304
|
+
case "withdraw":
|
|
305
|
+
if (!args.amount_minor) return validationError("amount_minor is required");
|
|
306
|
+
return dryRunResult("POST", "/wallet/withdrawals", { amount_minor: args.amount_minor, destination_type: args.destination_type }) as any;
|
|
307
|
+
case "cancel_withdrawal":
|
|
308
|
+
if (!args.withdrawal_id) return validationError("withdrawal_id is required");
|
|
309
|
+
return dryRunResult("POST", `/wallet/withdrawals/${args.withdrawal_id}/cancel`) as any;
|
|
310
|
+
default:
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
307
314
|
|
|
308
|
-
try {
|
|
309
315
|
switch (args.action) {
|
|
310
316
|
case "recipient_verify": {
|
|
311
|
-
if (!args.agent_id) return
|
|
317
|
+
if (!args.agent_id) return validationError("agent_id is required");
|
|
312
318
|
const agent = await client.resolve(args.agent_id);
|
|
313
|
-
return { result: formatRecipient(agent), data: agent };
|
|
319
|
+
return { result: formatRecipient(agent), data: agent } as any;
|
|
314
320
|
}
|
|
315
321
|
|
|
316
322
|
case "balance": {
|
|
317
323
|
const summary = await client.getWallet();
|
|
318
|
-
return { result: formatBalance(summary), data: sanitizeBalance(summary) };
|
|
324
|
+
return { result: formatBalance(summary), data: sanitizeBalance(summary) } as any;
|
|
319
325
|
}
|
|
320
326
|
|
|
321
327
|
case "ledger": {
|
|
@@ -324,18 +330,18 @@ export function createPaymentTool(opts?: { name?: string; description?: string }
|
|
|
324
330
|
if (args.limit) opts.limit = args.limit;
|
|
325
331
|
if (args.type) opts.type = args.type;
|
|
326
332
|
const ledger = await client.getWalletLedger(opts);
|
|
327
|
-
return { result: formatLedger(ledger), data: sanitizeLedger(ledger) };
|
|
333
|
+
return { result: formatLedger(ledger), data: sanitizeLedger(ledger) } as any;
|
|
328
334
|
}
|
|
329
335
|
|
|
330
336
|
case "transfer": {
|
|
331
|
-
if (!args.to_agent_id) return
|
|
332
|
-
if (!args.amount_minor) return
|
|
337
|
+
if (!args.to_agent_id) return validationError("to_agent_id is required");
|
|
338
|
+
if (!args.amount_minor) return validationError("amount_minor is required");
|
|
333
339
|
|
|
334
340
|
const isContact = await isPeerContact(client, args.to_agent_id);
|
|
335
341
|
if (!isContact && args.confirmed !== true) {
|
|
336
342
|
return {
|
|
337
343
|
result: `\u26a0\ufe0f ${args.to_agent_id} is not in your contacts. This is a stranger transfer of ${formatCoinAmount(args.amount_minor)}. To proceed, call this tool again with confirmed: true. The transfer will create a chat room between you and the recipient.`,
|
|
338
|
-
};
|
|
344
|
+
} as any;
|
|
339
345
|
}
|
|
340
346
|
|
|
341
347
|
const transfer = await executeTransfer(client, {
|
|
@@ -350,22 +356,22 @@ export function createPaymentTool(opts?: { name?: string; description?: string }
|
|
|
350
356
|
return {
|
|
351
357
|
result: `${formatTransaction(transfer.tx)}\n${formatFollowUpDeliverySummary(transfer)}`,
|
|
352
358
|
data: sanitizeTransferResult(transfer),
|
|
353
|
-
};
|
|
359
|
+
} as any;
|
|
354
360
|
}
|
|
355
361
|
|
|
356
362
|
case "topup": {
|
|
357
|
-
if (!args.amount_minor) return
|
|
363
|
+
if (!args.amount_minor) return validationError("amount_minor is required");
|
|
358
364
|
const topup = await client.createTopup({
|
|
359
365
|
amount_minor: args.amount_minor,
|
|
360
366
|
channel: args.channel,
|
|
361
367
|
metadata: args.metadata,
|
|
362
368
|
idempotency_key: args.idempotency_key,
|
|
363
369
|
});
|
|
364
|
-
return { result: formatTopup(topup), data: sanitizeTopup(topup) };
|
|
370
|
+
return { result: formatTopup(topup), data: sanitizeTopup(topup) } as any;
|
|
365
371
|
}
|
|
366
372
|
|
|
367
373
|
case "withdraw": {
|
|
368
|
-
if (!args.amount_minor) return
|
|
374
|
+
if (!args.amount_minor) return validationError("amount_minor is required");
|
|
369
375
|
const withdrawal = await client.createWithdrawal({
|
|
370
376
|
amount_minor: args.amount_minor,
|
|
371
377
|
fee_minor: args.fee_minor,
|
|
@@ -373,27 +379,25 @@ export function createPaymentTool(opts?: { name?: string; description?: string }
|
|
|
373
379
|
destination: args.destination,
|
|
374
380
|
idempotency_key: args.idempotency_key,
|
|
375
381
|
});
|
|
376
|
-
return { result: formatWithdrawal(withdrawal), data: sanitizeWithdrawal(withdrawal) };
|
|
382
|
+
return { result: formatWithdrawal(withdrawal), data: sanitizeWithdrawal(withdrawal) } as any;
|
|
377
383
|
}
|
|
378
384
|
|
|
379
385
|
case "cancel_withdrawal": {
|
|
380
|
-
if (!args.withdrawal_id) return
|
|
386
|
+
if (!args.withdrawal_id) return validationError("withdrawal_id is required");
|
|
381
387
|
const withdrawal = await client.cancelWithdrawal(args.withdrawal_id);
|
|
382
|
-
return { result: formatWithdrawal(withdrawal), data: sanitizeWithdrawal(withdrawal) };
|
|
388
|
+
return { result: formatWithdrawal(withdrawal), data: sanitizeWithdrawal(withdrawal) } as any;
|
|
383
389
|
}
|
|
384
390
|
|
|
385
391
|
case "tx_status": {
|
|
386
|
-
if (!args.tx_id) return
|
|
392
|
+
if (!args.tx_id) return validationError("tx_id is required");
|
|
387
393
|
const tx = await client.getWalletTransaction(args.tx_id);
|
|
388
|
-
return { result: formatTransaction(tx), data: sanitizeTransaction(tx) };
|
|
394
|
+
return { result: formatTransaction(tx), data: sanitizeTransaction(tx) } as any;
|
|
389
395
|
}
|
|
390
396
|
|
|
391
397
|
default:
|
|
392
|
-
return
|
|
398
|
+
return validationError(`Unknown action: ${args.action}`);
|
|
393
399
|
}
|
|
394
|
-
}
|
|
395
|
-
return { error: `Payment action failed: ${err.message}` };
|
|
396
|
-
}
|
|
400
|
+
});
|
|
397
401
|
},
|
|
398
402
|
};
|
|
399
403
|
}
|
package/src/tools/register.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { registerAgent } from "../commands/register.js";
|
|
5
5
|
import { getConfig as getAppConfig } from "../runtime.js";
|
|
6
6
|
import { DEFAULT_HUB } from "../constants.js";
|
|
7
|
+
import { validationError, configError, classifyError } from "./tool-result.js";
|
|
7
8
|
|
|
8
9
|
export function createRegisterTool() {
|
|
9
10
|
return {
|
|
@@ -35,11 +36,11 @@ export function createRegisterTool() {
|
|
|
35
36
|
},
|
|
36
37
|
execute: async (toolCallId: any, args: any, signal?: any, onUpdate?: any) => {
|
|
37
38
|
if (!args.name) {
|
|
38
|
-
return
|
|
39
|
+
return validationError("name is required");
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
const cfg = getAppConfig();
|
|
42
|
-
if (!cfg) return
|
|
43
|
+
if (!cfg) return configError("No configuration available");
|
|
43
44
|
|
|
44
45
|
try {
|
|
45
46
|
const result = await registerAgent({
|
|
@@ -59,8 +60,8 @@ export function createRegisterTool() {
|
|
|
59
60
|
claim_url: result.claimUrl,
|
|
60
61
|
note: "Restart OpenClaw to activate: openclaw gateway restart",
|
|
61
62
|
};
|
|
62
|
-
} catch (err:
|
|
63
|
-
return
|
|
63
|
+
} catch (err: unknown) {
|
|
64
|
+
return classifyError(err);
|
|
64
65
|
}
|
|
65
66
|
},
|
|
66
67
|
};
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { getConfig as getAppConfig } from "../runtime.js";
|
|
5
5
|
import { resetCredential } from "../reset-credential.js";
|
|
6
|
+
import { validationError, configError, classifyError } from "./tool-result.js";
|
|
6
7
|
|
|
7
8
|
export function createResetCredentialTool() {
|
|
8
9
|
return {
|
|
@@ -30,9 +31,9 @@ export function createResetCredentialTool() {
|
|
|
30
31
|
},
|
|
31
32
|
execute: async (_toolCallId: any, args: any) => {
|
|
32
33
|
const cfg = getAppConfig();
|
|
33
|
-
if (!cfg) return
|
|
34
|
-
if (!args.agent_id) return
|
|
35
|
-
if (!args.reset_code) return
|
|
34
|
+
if (!cfg) return configError("No configuration available");
|
|
35
|
+
if (!args.agent_id) return validationError("agent_id is required");
|
|
36
|
+
if (!args.reset_code) return validationError("reset_code is required");
|
|
36
37
|
|
|
37
38
|
try {
|
|
38
39
|
const result = await resetCredential({
|
|
@@ -50,8 +51,8 @@ export function createResetCredentialTool() {
|
|
|
50
51
|
credentials_file: result.credentialsFile,
|
|
51
52
|
note: "Restart OpenClaw to activate: openclaw gateway restart",
|
|
52
53
|
};
|
|
53
|
-
} catch (err:
|
|
54
|
-
return
|
|
54
|
+
} catch (err: unknown) {
|
|
55
|
+
return classifyError(err);
|
|
55
56
|
}
|
|
56
57
|
},
|
|
57
58
|
};
|
package/src/tools/rooms.ts
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* botcord_rooms — Room lifecycle and membership management.
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
resolveAccountConfig,
|
|
7
|
-
isAccountConfigured,
|
|
8
|
-
} from "../config.js";
|
|
9
|
-
import { BotCordClient } from "../client.js";
|
|
10
|
-
import { attachTokenPersistence } from "../credentials.js";
|
|
11
|
-
import { getConfig as getAppConfig } from "../runtime.js";
|
|
4
|
+
import { withClient } from "./with-client.js";
|
|
5
|
+
import { validationError, dryRunResult } from "./tool-result.js";
|
|
12
6
|
|
|
13
7
|
export function createRoomsTool() {
|
|
14
8
|
return {
|
|
@@ -102,27 +96,60 @@ export function createRoomsTool() {
|
|
|
102
96
|
type: "boolean" as const,
|
|
103
97
|
description: "Mute or unmute the current member in a room — for mute",
|
|
104
98
|
},
|
|
99
|
+
dry_run: {
|
|
100
|
+
type: "boolean" as const,
|
|
101
|
+
description: "Preview the request without executing. Returns the API call that would be made.",
|
|
102
|
+
},
|
|
105
103
|
},
|
|
106
104
|
required: ["action"],
|
|
107
105
|
},
|
|
108
106
|
execute: async (toolCallId: any, args: any, signal?: any, onUpdate?: any) => {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
107
|
+
return withClient(async (client) => {
|
|
108
|
+
// Dry-run for write operations
|
|
109
|
+
if (args.dry_run) {
|
|
110
|
+
switch (args.action) {
|
|
111
|
+
case "create":
|
|
112
|
+
if (!args.name) return validationError("name is required");
|
|
113
|
+
return dryRunResult("POST", "/hub/rooms", { name: args.name, visibility: args.visibility || "private", join_policy: args.join_policy, member_ids: args.member_ids }) as any;
|
|
114
|
+
case "update":
|
|
115
|
+
if (!args.room_id) return validationError("room_id is required");
|
|
116
|
+
return dryRunResult("PATCH", `/hub/rooms/${args.room_id}`, { name: args.name, description: args.description, visibility: args.visibility }) as any;
|
|
117
|
+
case "dissolve":
|
|
118
|
+
if (!args.room_id) return validationError("room_id is required");
|
|
119
|
+
return dryRunResult("DELETE", `/hub/rooms/${args.room_id}`) as any;
|
|
120
|
+
case "join":
|
|
121
|
+
if (!args.room_id) return validationError("room_id is required");
|
|
122
|
+
return dryRunResult("POST", `/hub/rooms/${args.room_id}/members`, { agent_id: "{self}" }) as any;
|
|
123
|
+
case "invite":
|
|
124
|
+
if (!args.room_id || !args.agent_id) return validationError("room_id and agent_id are required");
|
|
125
|
+
return dryRunResult("POST", `/hub/rooms/${args.room_id}/members`, { agent_id: args.agent_id, can_send: args.can_send, can_invite: args.can_invite }) as any;
|
|
126
|
+
case "remove_member":
|
|
127
|
+
if (!args.room_id || !args.agent_id) return validationError("room_id and agent_id are required");
|
|
128
|
+
return dryRunResult("DELETE", `/hub/rooms/${args.room_id}/members/${args.agent_id}`) as any;
|
|
129
|
+
case "promote":
|
|
130
|
+
if (!args.room_id || !args.agent_id) return validationError("room_id and agent_id are required");
|
|
131
|
+
return dryRunResult("POST", `/hub/rooms/${args.room_id}/promote`, { agent_id: args.agent_id, role: args.role || "admin" }) as any;
|
|
132
|
+
case "transfer":
|
|
133
|
+
if (!args.room_id || !args.agent_id) return validationError("room_id and agent_id are required");
|
|
134
|
+
return dryRunResult("POST", `/hub/rooms/${args.room_id}/transfer`, { new_owner_id: args.agent_id }) as any;
|
|
135
|
+
case "permissions":
|
|
136
|
+
if (!args.room_id || !args.agent_id) return validationError("room_id and agent_id are required");
|
|
137
|
+
return dryRunResult("POST", `/hub/rooms/${args.room_id}/permissions`, { agent_id: args.agent_id, can_send: args.can_send, can_invite: args.can_invite }) as any;
|
|
138
|
+
case "leave":
|
|
139
|
+
if (!args.room_id) return validationError("room_id is required");
|
|
140
|
+
return dryRunResult("POST", `/hub/rooms/${args.room_id}/leave`) as any;
|
|
141
|
+
case "mute":
|
|
142
|
+
if (!args.room_id) return validationError("room_id is required");
|
|
143
|
+
return dryRunResult("POST", `/hub/rooms/${args.room_id}/mute`, { muted: args.muted ?? true }) as any;
|
|
144
|
+
default:
|
|
145
|
+
// Read actions don't support dry-run, fall through to normal execution
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
121
149
|
|
|
122
|
-
try {
|
|
123
150
|
switch (args.action) {
|
|
124
151
|
case "create":
|
|
125
|
-
if (!args.name) return
|
|
152
|
+
if (!args.name) return validationError("name is required");
|
|
126
153
|
return await client.createRoom({
|
|
127
154
|
name: args.name,
|
|
128
155
|
description: args.description,
|
|
@@ -138,14 +165,14 @@ export function createRoomsTool() {
|
|
|
138
165
|
});
|
|
139
166
|
|
|
140
167
|
case "list":
|
|
141
|
-
return await client.listMyRooms();
|
|
168
|
+
return { rooms: await client.listMyRooms() } as any;
|
|
142
169
|
|
|
143
170
|
case "info":
|
|
144
|
-
if (!args.room_id) return
|
|
171
|
+
if (!args.room_id) return validationError("room_id is required");
|
|
145
172
|
return await client.getRoomInfo(args.room_id);
|
|
146
173
|
|
|
147
174
|
case "update":
|
|
148
|
-
if (!args.room_id) return
|
|
175
|
+
if (!args.room_id) return validationError("room_id is required");
|
|
149
176
|
return await client.updateRoom(args.room_id, {
|
|
150
177
|
name: args.name,
|
|
151
178
|
description: args.description,
|
|
@@ -160,72 +187,70 @@ export function createRoomsTool() {
|
|
|
160
187
|
});
|
|
161
188
|
|
|
162
189
|
case "discover":
|
|
163
|
-
return await client.discoverRooms(args.name);
|
|
190
|
+
return { rooms: await client.discoverRooms(args.name) } as any;
|
|
164
191
|
|
|
165
192
|
case "join":
|
|
166
|
-
if (!args.room_id) return
|
|
193
|
+
if (!args.room_id) return validationError("room_id is required");
|
|
167
194
|
await client.joinRoom(args.room_id, {
|
|
168
195
|
can_send: args.can_send,
|
|
169
196
|
can_invite: args.can_invite,
|
|
170
197
|
});
|
|
171
|
-
return { ok: true, joined: args.room_id };
|
|
198
|
+
return { ok: true, joined: args.room_id } as any;
|
|
172
199
|
|
|
173
200
|
case "leave":
|
|
174
|
-
if (!args.room_id) return
|
|
201
|
+
if (!args.room_id) return validationError("room_id is required");
|
|
175
202
|
await client.leaveRoom(args.room_id);
|
|
176
|
-
return { ok: true, left: args.room_id };
|
|
203
|
+
return { ok: true, left: args.room_id } as any;
|
|
177
204
|
|
|
178
205
|
case "dissolve":
|
|
179
|
-
if (!args.room_id) return
|
|
206
|
+
if (!args.room_id) return validationError("room_id is required");
|
|
180
207
|
await client.dissolveRoom(args.room_id);
|
|
181
|
-
return { ok: true, dissolved: args.room_id };
|
|
208
|
+
return { ok: true, dissolved: args.room_id } as any;
|
|
182
209
|
|
|
183
210
|
case "members":
|
|
184
|
-
if (!args.room_id) return
|
|
185
|
-
return await client.getRoomMembers(args.room_id);
|
|
211
|
+
if (!args.room_id) return validationError("room_id is required");
|
|
212
|
+
return { members: await client.getRoomMembers(args.room_id) } as any;
|
|
186
213
|
|
|
187
214
|
case "invite":
|
|
188
|
-
if (!args.room_id || !args.agent_id) return
|
|
215
|
+
if (!args.room_id || !args.agent_id) return validationError("room_id and agent_id are required");
|
|
189
216
|
await client.inviteToRoom(args.room_id, args.agent_id, {
|
|
190
217
|
can_send: args.can_send,
|
|
191
218
|
can_invite: args.can_invite,
|
|
192
219
|
});
|
|
193
|
-
return { ok: true, invited: args.agent_id, room: args.room_id };
|
|
220
|
+
return { ok: true, invited: args.agent_id, room: args.room_id } as any;
|
|
194
221
|
|
|
195
222
|
case "remove_member":
|
|
196
|
-
if (!args.room_id || !args.agent_id) return
|
|
223
|
+
if (!args.room_id || !args.agent_id) return validationError("room_id and agent_id are required");
|
|
197
224
|
await client.removeMember(args.room_id, args.agent_id);
|
|
198
|
-
return { ok: true, removed: args.agent_id, room: args.room_id };
|
|
225
|
+
return { ok: true, removed: args.agent_id, room: args.room_id } as any;
|
|
199
226
|
|
|
200
227
|
case "promote":
|
|
201
|
-
if (!args.room_id || !args.agent_id) return
|
|
228
|
+
if (!args.room_id || !args.agent_id) return validationError("room_id and agent_id are required");
|
|
202
229
|
await client.promoteMember(args.room_id, args.agent_id, args.role || "admin");
|
|
203
|
-
return { ok: true, promoted: args.agent_id, role: args.role || "admin", room: args.room_id };
|
|
230
|
+
return { ok: true, promoted: args.agent_id, role: args.role || "admin", room: args.room_id } as any;
|
|
204
231
|
|
|
205
232
|
case "transfer":
|
|
206
|
-
if (!args.room_id || !args.agent_id) return
|
|
233
|
+
if (!args.room_id || !args.agent_id) return validationError("room_id and agent_id are required");
|
|
207
234
|
await client.transferOwnership(args.room_id, args.agent_id);
|
|
208
|
-
return { ok: true, new_owner: args.agent_id, room: args.room_id };
|
|
235
|
+
return { ok: true, new_owner: args.agent_id, room: args.room_id } as any;
|
|
209
236
|
|
|
210
237
|
case "permissions":
|
|
211
|
-
if (!args.room_id || !args.agent_id) return
|
|
238
|
+
if (!args.room_id || !args.agent_id) return validationError("room_id and agent_id are required");
|
|
212
239
|
await client.setMemberPermissions(args.room_id, args.agent_id, {
|
|
213
240
|
can_send: args.can_send,
|
|
214
241
|
can_invite: args.can_invite,
|
|
215
242
|
});
|
|
216
|
-
return { ok: true, agent: args.agent_id, room: args.room_id };
|
|
243
|
+
return { ok: true, agent: args.agent_id, room: args.room_id } as any;
|
|
217
244
|
|
|
218
245
|
case "mute":
|
|
219
|
-
if (!args.room_id) return
|
|
246
|
+
if (!args.room_id) return validationError("room_id is required");
|
|
220
247
|
await client.muteRoom(args.room_id, args.muted ?? true);
|
|
221
|
-
return { ok: true, room: args.room_id, muted: args.muted ?? true };
|
|
248
|
+
return { ok: true, room: args.room_id, muted: args.muted ?? true } as any;
|
|
222
249
|
|
|
223
250
|
default:
|
|
224
|
-
return
|
|
251
|
+
return validationError(`Unknown action: ${args.action}`);
|
|
225
252
|
}
|
|
226
|
-
}
|
|
227
|
-
return { error: `Room action failed: ${err.message}` };
|
|
228
|
-
}
|
|
253
|
+
});
|
|
229
254
|
},
|
|
230
255
|
};
|
|
231
256
|
}
|
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* botcord_subscription — Create and manage coin-priced subscription products.
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
resolveAccountConfig,
|
|
7
|
-
isAccountConfigured,
|
|
8
|
-
} from "../config.js";
|
|
9
|
-
import { BotCordClient } from "../client.js";
|
|
10
|
-
import { attachTokenPersistence } from "../credentials.js";
|
|
11
|
-
import { getConfig as getAppConfig } from "../runtime.js";
|
|
4
|
+
import { withClient } from "./with-client.js";
|
|
5
|
+
import { validationError, dryRunResult } from "./tool-result.js";
|
|
12
6
|
import { formatCoinAmount } from "./coin-format.js";
|
|
13
7
|
|
|
14
8
|
function formatProduct(product: any): string {
|
|
@@ -127,29 +121,50 @@ export function createSubscriptionTool() {
|
|
|
127
121
|
type: "number" as const,
|
|
128
122
|
description: "Slow mode interval in seconds — for create_subscription_room or bind_room_to_product",
|
|
129
123
|
},
|
|
124
|
+
dry_run: {
|
|
125
|
+
type: "boolean" as const,
|
|
126
|
+
description: "Preview the request without executing. Returns the API call that would be made.",
|
|
127
|
+
},
|
|
130
128
|
},
|
|
131
129
|
required: ["action"],
|
|
132
130
|
},
|
|
133
131
|
execute: async (toolCallId: any, args: any, signal?: any, onUpdate?: any) => {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
132
|
+
return withClient(async (client) => {
|
|
133
|
+
// Dry-run for write operations
|
|
134
|
+
if (args.dry_run) {
|
|
135
|
+
switch (args.action) {
|
|
136
|
+
case "create_product":
|
|
137
|
+
if (!args.name) return validationError("name is required");
|
|
138
|
+
if (!args.amount_minor) return validationError("amount_minor is required");
|
|
139
|
+
if (!args.billing_interval) return validationError("billing_interval is required");
|
|
140
|
+
return dryRunResult("POST", "/subscriptions/products", { name: args.name, amount_minor: args.amount_minor, billing_interval: args.billing_interval }) as any;
|
|
141
|
+
case "subscribe":
|
|
142
|
+
if (!args.product_id) return validationError("product_id is required");
|
|
143
|
+
return dryRunResult("POST", `/subscriptions/products/${args.product_id}/subscribe`) as any;
|
|
144
|
+
case "archive_product":
|
|
145
|
+
if (!args.product_id) return validationError("product_id is required");
|
|
146
|
+
return dryRunResult("POST", `/subscriptions/products/${args.product_id}/archive`) as any;
|
|
147
|
+
case "cancel":
|
|
148
|
+
if (!args.subscription_id) return validationError("subscription_id is required");
|
|
149
|
+
return dryRunResult("POST", `/subscriptions/${args.subscription_id}/cancel`) as any;
|
|
150
|
+
case "create_subscription_room":
|
|
151
|
+
if (!args.product_id) return validationError("product_id is required");
|
|
152
|
+
if (!args.name) return validationError("name is required");
|
|
153
|
+
return dryRunResult("POST", "/hub/rooms", { name: args.name, description: args.description, visibility: "public", join_policy: "open", required_subscription_product_id: args.product_id }) as any;
|
|
154
|
+
case "bind_room_to_product":
|
|
155
|
+
if (!args.room_id) return validationError("room_id is required");
|
|
156
|
+
if (!args.product_id) return validationError("product_id is required");
|
|
157
|
+
return dryRunResult("PATCH", `/hub/rooms/${args.room_id}`, { visibility: "public", join_policy: "open", required_subscription_product_id: args.product_id }) as any;
|
|
158
|
+
default:
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
146
162
|
|
|
147
|
-
try {
|
|
148
163
|
switch (args.action) {
|
|
149
164
|
case "create_product": {
|
|
150
|
-
if (!args.name) return
|
|
151
|
-
if (!args.amount_minor) return
|
|
152
|
-
if (!args.billing_interval) return
|
|
165
|
+
if (!args.name) return validationError("name is required");
|
|
166
|
+
if (!args.amount_minor) return validationError("amount_minor is required");
|
|
167
|
+
if (!args.billing_interval) return validationError("billing_interval is required");
|
|
153
168
|
const product = await client.createSubscriptionProduct({
|
|
154
169
|
name: args.name,
|
|
155
170
|
description: args.description,
|
|
@@ -157,28 +172,28 @@ export function createSubscriptionTool() {
|
|
|
157
172
|
billing_interval: args.billing_interval,
|
|
158
173
|
asset_code: args.asset_code,
|
|
159
174
|
});
|
|
160
|
-
return { result: formatProduct(product), data: product };
|
|
175
|
+
return { result: formatProduct(product), data: product } as any;
|
|
161
176
|
}
|
|
162
177
|
|
|
163
178
|
case "list_my_products": {
|
|
164
179
|
const products = await client.listMySubscriptionProducts();
|
|
165
|
-
return { result: formatProductList(products), data: products };
|
|
180
|
+
return { result: formatProductList(products), data: products } as any;
|
|
166
181
|
}
|
|
167
182
|
|
|
168
183
|
case "list_products": {
|
|
169
184
|
const products = await client.listSubscriptionProducts();
|
|
170
|
-
return { result: formatProductList(products), data: products };
|
|
185
|
+
return { result: formatProductList(products), data: products } as any;
|
|
171
186
|
}
|
|
172
187
|
|
|
173
188
|
case "archive_product": {
|
|
174
|
-
if (!args.product_id) return
|
|
189
|
+
if (!args.product_id) return validationError("product_id is required");
|
|
175
190
|
const product = await client.archiveSubscriptionProduct(args.product_id);
|
|
176
|
-
return { result: formatProduct(product), data: product };
|
|
191
|
+
return { result: formatProduct(product), data: product } as any;
|
|
177
192
|
}
|
|
178
193
|
|
|
179
194
|
case "create_subscription_room": {
|
|
180
|
-
if (!args.product_id) return
|
|
181
|
-
if (!args.name) return
|
|
195
|
+
if (!args.product_id) return validationError("product_id is required");
|
|
196
|
+
if (!args.name) return validationError("name is required");
|
|
182
197
|
const room = await client.createRoom({
|
|
183
198
|
name: args.name,
|
|
184
199
|
description: args.description,
|
|
@@ -194,12 +209,12 @@ export function createSubscriptionTool() {
|
|
|
194
209
|
return {
|
|
195
210
|
result: `Subscription room created: ${room.room_id} bound to ${args.product_id}`,
|
|
196
211
|
data: room,
|
|
197
|
-
};
|
|
212
|
+
} as any;
|
|
198
213
|
}
|
|
199
214
|
|
|
200
215
|
case "bind_room_to_product": {
|
|
201
|
-
if (!args.room_id) return
|
|
202
|
-
if (!args.product_id) return
|
|
216
|
+
if (!args.room_id) return validationError("room_id is required");
|
|
217
|
+
if (!args.product_id) return validationError("product_id is required");
|
|
203
218
|
const room = await client.updateRoom(args.room_id, {
|
|
204
219
|
name: args.name,
|
|
205
220
|
description: args.description,
|
|
@@ -215,38 +230,36 @@ export function createSubscriptionTool() {
|
|
|
215
230
|
return {
|
|
216
231
|
result: `Room ${room.room_id} bound to subscription product ${args.product_id}`,
|
|
217
232
|
data: room,
|
|
218
|
-
};
|
|
233
|
+
} as any;
|
|
219
234
|
}
|
|
220
235
|
|
|
221
236
|
case "subscribe": {
|
|
222
|
-
if (!args.product_id) return
|
|
237
|
+
if (!args.product_id) return validationError("product_id is required");
|
|
223
238
|
const subscription = await client.subscribeToProduct(args.product_id, args.idempotency_key);
|
|
224
|
-
return { result: formatSubscription(subscription), data: subscription };
|
|
239
|
+
return { result: formatSubscription(subscription), data: subscription } as any;
|
|
225
240
|
}
|
|
226
241
|
|
|
227
242
|
case "list_my_subscriptions": {
|
|
228
243
|
const subscriptions = await client.listMySubscriptions();
|
|
229
|
-
return { result: formatSubscriptionList(subscriptions), data: subscriptions };
|
|
244
|
+
return { result: formatSubscriptionList(subscriptions), data: subscriptions } as any;
|
|
230
245
|
}
|
|
231
246
|
|
|
232
247
|
case "list_subscribers": {
|
|
233
|
-
if (!args.product_id) return
|
|
248
|
+
if (!args.product_id) return validationError("product_id is required");
|
|
234
249
|
const subscriptions = await client.listProductSubscribers(args.product_id);
|
|
235
|
-
return { result: formatSubscriptionList(subscriptions), data: subscriptions };
|
|
250
|
+
return { result: formatSubscriptionList(subscriptions), data: subscriptions } as any;
|
|
236
251
|
}
|
|
237
252
|
|
|
238
253
|
case "cancel": {
|
|
239
|
-
if (!args.subscription_id) return
|
|
254
|
+
if (!args.subscription_id) return validationError("subscription_id is required");
|
|
240
255
|
const subscription = await client.cancelSubscription(args.subscription_id);
|
|
241
|
-
return { result: formatSubscription(subscription), data: subscription };
|
|
256
|
+
return { result: formatSubscription(subscription), data: subscription } as any;
|
|
242
257
|
}
|
|
243
258
|
|
|
244
259
|
default:
|
|
245
|
-
return
|
|
260
|
+
return validationError(`Unknown action: ${args.action}`);
|
|
246
261
|
}
|
|
247
|
-
}
|
|
248
|
-
return { error: `Subscription action failed: ${err.message}` };
|
|
249
|
-
}
|
|
262
|
+
});
|
|
250
263
|
},
|
|
251
264
|
};
|
|
252
265
|
}
|