@botcord/openclaw-plugin 0.0.2 → 0.0.4

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,176 @@
1
+ /**
2
+ * botcord_subscription — Create and manage coin-priced subscription products.
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
+ function formatProduct(product: any): string {
13
+ return [
14
+ `Product: ${product.product_id}`,
15
+ `Owner: ${product.owner_agent_id}`,
16
+ `Name: ${product.name}`,
17
+ `Amount: ${product.amount_minor} minor units`,
18
+ `Interval: ${product.billing_interval}`,
19
+ `Status: ${product.status}`,
20
+ ].join("\n");
21
+ }
22
+
23
+ function formatSubscription(subscription: any): string {
24
+ return [
25
+ `Subscription: ${subscription.subscription_id}`,
26
+ `Product: ${subscription.product_id}`,
27
+ `Subscriber: ${subscription.subscriber_agent_id}`,
28
+ `Provider: ${subscription.provider_agent_id}`,
29
+ `Amount: ${subscription.amount_minor} minor units`,
30
+ `Interval: ${subscription.billing_interval}`,
31
+ `Status: ${subscription.status}`,
32
+ `Next charge: ${subscription.next_charge_at}`,
33
+ ].join("\n");
34
+ }
35
+
36
+ function formatProductList(products: any[]): string {
37
+ if (products.length === 0) return "No subscription products found.";
38
+ return products.map((product) => formatProduct(product)).join("\n\n");
39
+ }
40
+
41
+ function formatSubscriptionList(subscriptions: any[]): string {
42
+ if (subscriptions.length === 0) return "No subscriptions found.";
43
+ return subscriptions.map((subscription) => formatSubscription(subscription)).join("\n\n");
44
+ }
45
+
46
+ export function createSubscriptionTool() {
47
+ return {
48
+ name: "botcord_subscription",
49
+ description:
50
+ "Create subscription products priced in BotCord coin, subscribe to products, list active subscriptions, and manage cancellation or product archiving.",
51
+ parameters: {
52
+ type: "object" as const,
53
+ properties: {
54
+ action: {
55
+ type: "string" as const,
56
+ enum: [
57
+ "create_product",
58
+ "list_my_products",
59
+ "list_products",
60
+ "archive_product",
61
+ "subscribe",
62
+ "list_my_subscriptions",
63
+ "list_subscribers",
64
+ "cancel",
65
+ ],
66
+ description: "Subscription action to perform",
67
+ },
68
+ product_id: {
69
+ type: "string" as const,
70
+ description: "Product ID — for archive_product, subscribe, list_subscribers",
71
+ },
72
+ subscription_id: {
73
+ type: "string" as const,
74
+ description: "Subscription ID — for cancel",
75
+ },
76
+ name: {
77
+ type: "string" as const,
78
+ description: "Product name — for create_product",
79
+ },
80
+ description: {
81
+ type: "string" as const,
82
+ description: "Product description — for create_product",
83
+ },
84
+ amount_minor: {
85
+ type: "string" as const,
86
+ description: "Price in minor coin units — for create_product",
87
+ },
88
+ billing_interval: {
89
+ type: "string" as const,
90
+ enum: ["week", "month"],
91
+ description: "Billing interval — for create_product",
92
+ },
93
+ asset_code: {
94
+ type: "string" as const,
95
+ description: "Asset code — for create_product",
96
+ },
97
+ },
98
+ required: ["action"],
99
+ },
100
+ execute: async (toolCallId: any, args: any, signal?: any, onUpdate?: any) => {
101
+ const cfg = getAppConfig();
102
+ if (!cfg) return { error: "No configuration available" };
103
+ const singleAccountError = getSingleAccountModeError(cfg);
104
+ if (singleAccountError) return { error: singleAccountError };
105
+
106
+ const acct = resolveAccountConfig(cfg);
107
+ if (!isAccountConfigured(acct)) {
108
+ return { error: "BotCord is not configured." };
109
+ }
110
+
111
+ const client = new BotCordClient(acct);
112
+
113
+ try {
114
+ switch (args.action) {
115
+ case "create_product": {
116
+ if (!args.name) return { error: "name is required" };
117
+ if (!args.amount_minor) return { error: "amount_minor is required" };
118
+ if (!args.billing_interval) return { error: "billing_interval is required" };
119
+ const product = await client.createSubscriptionProduct({
120
+ name: args.name,
121
+ description: args.description,
122
+ amount_minor: args.amount_minor,
123
+ billing_interval: args.billing_interval,
124
+ asset_code: args.asset_code,
125
+ });
126
+ return { result: formatProduct(product), data: product };
127
+ }
128
+
129
+ case "list_my_products": {
130
+ const products = await client.listMySubscriptionProducts();
131
+ return { result: formatProductList(products), data: products };
132
+ }
133
+
134
+ case "list_products": {
135
+ const products = await client.listSubscriptionProducts();
136
+ return { result: formatProductList(products), data: products };
137
+ }
138
+
139
+ case "archive_product": {
140
+ if (!args.product_id) return { error: "product_id is required" };
141
+ const product = await client.archiveSubscriptionProduct(args.product_id);
142
+ return { result: formatProduct(product), data: product };
143
+ }
144
+
145
+ case "subscribe": {
146
+ if (!args.product_id) return { error: "product_id is required" };
147
+ const subscription = await client.subscribeToProduct(args.product_id);
148
+ return { result: formatSubscription(subscription), data: subscription };
149
+ }
150
+
151
+ case "list_my_subscriptions": {
152
+ const subscriptions = await client.listMySubscriptions();
153
+ return { result: formatSubscriptionList(subscriptions), data: subscriptions };
154
+ }
155
+
156
+ case "list_subscribers": {
157
+ if (!args.product_id) return { error: "product_id is required" };
158
+ const subscriptions = await client.listProductSubscribers(args.product_id);
159
+ return { result: formatSubscriptionList(subscriptions), data: subscriptions };
160
+ }
161
+
162
+ case "cancel": {
163
+ if (!args.subscription_id) return { error: "subscription_id is required" };
164
+ const subscription = await client.cancelSubscription(args.subscription_id);
165
+ return { result: formatSubscription(subscription), data: subscription };
166
+ }
167
+
168
+ default:
169
+ return { error: `Unknown action: ${args.action}` };
170
+ }
171
+ } catch (err: any) {
172
+ return { error: `Subscription action failed: ${err.message}` };
173
+ }
174
+ },
175
+ };
176
+ }
package/src/types.ts CHANGED
@@ -58,6 +58,7 @@ export type InboxMessage = {
58
58
  text?: string;
59
59
  room_id?: string;
60
60
  room_name?: string;
61
+ room_rule?: string | null;
61
62
  room_member_count?: number;
62
63
  room_member_names?: string[];
63
64
  my_role?: string;
@@ -86,6 +87,7 @@ export type RoomInfo = {
86
87
  room_id: string;
87
88
  name: string;
88
89
  description?: string;
90
+ rule?: string | null;
89
91
  visibility: "private" | "public";
90
92
  join_policy: "invite_only" | "open";
91
93
  default_send: boolean;
@@ -153,8 +155,12 @@ export type WalletTransaction = {
153
155
  fee_minor: string;
154
156
  from_agent_id: string | null;
155
157
  to_agent_id: string | null;
158
+ reference_type: string | null;
159
+ reference_id: string | null;
160
+ idempotency_key: string | null;
156
161
  metadata_json: string | null;
157
162
  created_at: string;
163
+ updated_at: string;
158
164
  completed_at: string | null;
159
165
  };
160
166
 
@@ -201,3 +207,58 @@ export type WithdrawalResponse = {
201
207
  reviewed_at: string | null;
202
208
  completed_at: string | null;
203
209
  };
210
+
211
+ export type BillingInterval = "week" | "month";
212
+
213
+ export type SubscriptionProductStatus = "active" | "archived";
214
+
215
+ export type SubscriptionStatus = "active" | "past_due" | "cancelled";
216
+
217
+ export type SubscriptionChargeAttemptStatus = "pending" | "succeeded" | "failed";
218
+
219
+ export type SubscriptionProduct = {
220
+ product_id: string;
221
+ owner_agent_id: string;
222
+ name: string;
223
+ description: string;
224
+ asset_code: string;
225
+ amount_minor: string;
226
+ billing_interval: BillingInterval;
227
+ status: SubscriptionProductStatus;
228
+ created_at: string;
229
+ updated_at: string;
230
+ archived_at: string | null;
231
+ };
232
+
233
+ export type Subscription = {
234
+ subscription_id: string;
235
+ product_id: string;
236
+ subscriber_agent_id: string;
237
+ provider_agent_id: string;
238
+ asset_code: string;
239
+ amount_minor: string;
240
+ billing_interval: BillingInterval;
241
+ status: SubscriptionStatus;
242
+ current_period_start: string;
243
+ current_period_end: string;
244
+ next_charge_at: string;
245
+ cancel_at_period_end: boolean;
246
+ cancelled_at: string | null;
247
+ last_charged_at: string | null;
248
+ last_charge_tx_id: string | null;
249
+ consecutive_failed_attempts: number;
250
+ created_at: string;
251
+ updated_at: string;
252
+ };
253
+
254
+ export type SubscriptionChargeAttempt = {
255
+ attempt_id: string;
256
+ subscription_id: string;
257
+ billing_cycle_key: string;
258
+ status: SubscriptionChargeAttemptStatus;
259
+ scheduled_at: string;
260
+ attempted_at: string | null;
261
+ tx_id: string | null;
262
+ failure_reason: string | null;
263
+ created_at: string;
264
+ };
package/src/ws-client.ts CHANGED
@@ -13,6 +13,7 @@ import WebSocket from "ws";
13
13
  import { BotCordClient } from "./client.js";
14
14
  import { handleInboxMessage } from "./inbound.js";
15
15
  import { displayPrefix } from "./config.js";
16
+ import { buildHubWebSocketUrl } from "./hub-url.js";
16
17
 
17
18
  interface WsClientOptions {
18
19
  client: BotCordClient;
@@ -73,7 +74,7 @@ export function startWsClient(opts: WsClientOptions): { stop: () => void } {
73
74
  // Get a fresh JWT token
74
75
  const token = await client.ensureToken();
75
76
  const hubUrl = client.getHubUrl();
76
- const wsUrl = hubUrl.replace(/^http/, "ws") + "/hub/ws";
77
+ const wsUrl = buildHubWebSocketUrl(hubUrl);
77
78
 
78
79
  log?.info(`[${dp}] WebSocket connecting to ${wsUrl}`);
79
80
  ws = new WebSocket(wsUrl);
@@ -184,4 +185,3 @@ export function stopWsClient(accountId: string): void {
184
185
  const entry = activeWsClients.get(accountId);
185
186
  if (entry) entry.stop();
186
187
  }
187
-