@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.
@@ -1,14 +1,8 @@
1
1
  /**
2
2
  * botcord_payment — Unified payment and transaction tool for BotCord coin flows.
3
3
  */
4
- import {
5
- getSingleAccountModeError,
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
- const cfg = getAppConfig();
296
- if (!cfg) return { error: "No configuration available" };
297
- const singleAccountError = getSingleAccountModeError(cfg);
298
- if (singleAccountError) return { error: singleAccountError };
299
-
300
- const acct = resolveAccountConfig(cfg);
301
- if (!isAccountConfigured(acct)) {
302
- return { error: "BotCord is not configured." };
303
- }
304
-
305
- const client = new BotCordClient(acct);
306
- attachTokenPersistence(client, acct);
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 { error: "agent_id is required" };
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 { error: "to_agent_id is required" };
332
- if (!args.amount_minor) return { error: "amount_minor is required" };
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 { error: "amount_minor is required" };
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 { error: "amount_minor is required" };
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 { error: "withdrawal_id is required" };
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 { error: "tx_id is required" };
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 { error: `Unknown action: ${args.action}` };
398
+ return validationError(`Unknown action: ${args.action}`);
393
399
  }
394
- } catch (err: any) {
395
- return { error: `Payment action failed: ${err.message}` };
396
- }
400
+ });
397
401
  },
398
402
  };
399
403
  }
@@ -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 { error: "name is required" };
39
+ return validationError("name is required");
39
40
  }
40
41
 
41
42
  const cfg = getAppConfig();
42
- if (!cfg) return { error: "No configuration available" };
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: any) {
63
- return { error: `Registration failed: ${err.message}` };
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 { error: "No configuration available" };
34
- if (!args.agent_id) return { error: "agent_id is required" };
35
- if (!args.reset_code) return { error: "reset_code is required" };
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: any) {
54
- return { error: err.message };
54
+ } catch (err: unknown) {
55
+ return classifyError(err);
55
56
  }
56
57
  },
57
58
  };
@@ -1,14 +1,8 @@
1
1
  /**
2
2
  * botcord_rooms — Room lifecycle and membership management.
3
3
  */
4
- import {
5
- getSingleAccountModeError,
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
- const cfg = getAppConfig();
110
- if (!cfg) return { error: "No configuration available" };
111
- const singleAccountError = getSingleAccountModeError(cfg);
112
- if (singleAccountError) return { error: singleAccountError };
113
-
114
- const acct = resolveAccountConfig(cfg);
115
- if (!isAccountConfigured(acct)) {
116
- return { error: "BotCord is not configured." };
117
- }
118
-
119
- const client = new BotCordClient(acct);
120
- attachTokenPersistence(client, acct);
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 { error: "name is required" };
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 { error: "room_id is required" };
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 { error: "room_id is required" };
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 { error: "room_id is required" };
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 { error: "room_id is required" };
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 { error: "room_id is required" };
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 { error: "room_id is required" };
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 { error: "room_id and agent_id are required" };
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 { error: "room_id and agent_id are required" };
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 { error: "room_id and agent_id are required" };
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 { error: "room_id and agent_id are required" };
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 { error: "room_id and agent_id are required" };
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 { error: "room_id is required" };
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 { error: `Unknown action: ${args.action}` };
251
+ return validationError(`Unknown action: ${args.action}`);
225
252
  }
226
- } catch (err: any) {
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
- getSingleAccountModeError,
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
- const cfg = getAppConfig();
135
- if (!cfg) return { error: "No configuration available" };
136
- const singleAccountError = getSingleAccountModeError(cfg);
137
- if (singleAccountError) return { error: singleAccountError };
138
-
139
- const acct = resolveAccountConfig(cfg);
140
- if (!isAccountConfigured(acct)) {
141
- return { error: "BotCord is not configured." };
142
- }
143
-
144
- const client = new BotCordClient(acct);
145
- attachTokenPersistence(client, acct);
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 { error: "name is required" };
151
- if (!args.amount_minor) return { error: "amount_minor is required" };
152
- if (!args.billing_interval) return { error: "billing_interval is required" };
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 { error: "product_id is required" };
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 { error: "product_id is required" };
181
- if (!args.name) return { error: "name is required" };
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 { error: "room_id is required" };
202
- if (!args.product_id) return { error: "product_id is required" };
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 { error: "product_id is required" };
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 { error: "product_id is required" };
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 { error: "subscription_id is required" };
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 { error: `Unknown action: ${args.action}` };
260
+ return validationError(`Unknown action: ${args.action}`);
246
261
  }
247
- } catch (err: any) {
248
- return { error: `Subscription action failed: ${err.message}` };
249
- }
262
+ });
250
263
  },
251
264
  };
252
265
  }