@dennisdamenace/clawtell 0.1.6 → 0.2.1

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.
@@ -5,6 +5,10 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
8
12
  var __copyProps = (to, from, except, desc) => {
9
13
  if (from && typeof from === "object" || typeof from === "function") {
10
14
  for (let key of __getOwnPropNames(from))
@@ -21,8 +25,19 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
21
25
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
26
  mod
23
27
  ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
24
29
 
25
30
  // src/postinstall.ts
31
+ var postinstall_exports = {};
32
+ __export(postinstall_exports, {
33
+ ENV_EXAMPLE: () => ENV_EXAMPLE,
34
+ INDEX_TS: () => INDEX_TS,
35
+ PLUGIN_JSON: () => PLUGIN_JSON,
36
+ WEBHOOK_HANDLER_JS: () => WEBHOOK_HANDLER_JS,
37
+ WEBHOOK_HANDLER_TS: () => WEBHOOK_HANDLER_TS,
38
+ installPlugin: () => installPlugin
39
+ });
40
+ module.exports = __toCommonJS(postinstall_exports);
26
41
  var fs = __toESM(require("fs"));
27
42
  var path = __toESM(require("path"));
28
43
  var os = __toESM(require("os"));
@@ -31,6 +46,8 @@ var EXTENSIONS_DIR = path.join(CLAWDBOT_DIR, "extensions");
31
46
  var PLUGIN_DIR = path.join(EXTENSIONS_DIR, "clawtell");
32
47
  var PLUGIN_JSON = {
33
48
  id: "clawtell",
49
+ name: "ClawTell",
50
+ version: "2026.2.7",
34
51
  channels: ["clawtell"],
35
52
  configSchema: {
36
53
  type: "object",
@@ -53,86 +70,582 @@ var PLUGIN_JSON = {
53
70
  type: "string",
54
71
  default: "/webhook/clawtell",
55
72
  description: "HTTP path for receiving ClawTell webhooks"
73
+ },
74
+ webhookSecret: {
75
+ type: "string",
76
+ description: "HMAC secret for webhook signature verification (auto-generated if not set)"
77
+ },
78
+ gatewayUrl: {
79
+ type: "string",
80
+ description: "Public gateway URL for webhook registration (uses gateway.publicUrl if not set)"
56
81
  }
57
82
  }
58
83
  }
59
84
  };
60
- var INDEX_TS = `import type { ClawdbotPluginApi } from "clawdbot/plugin-sdk";
85
+ var INDEX_TS = `/**
86
+ * ClawTell Channel Plugin for Clawdbot
87
+ *
88
+ * Embedded version for SDK auto-install.
89
+ * Production-ready with correct webhook handler signature.
90
+ *
91
+ * @license MIT
92
+ * @version 2026.2.7
93
+ */
94
+
95
+ import type { IncomingMessage, ServerResponse } from "node:http";
96
+ import type { ClawdbotPluginApi } from "clawdbot/plugin-sdk";
61
97
  import { emptyPluginConfigSchema } from "clawdbot/plugin-sdk";
98
+ import { createHmac, timingSafeEqual, randomBytes } from "crypto";
99
+
100
+ const CLAWTELL_API_BASE = "https://clawtell.com/api";
101
+ const MAX_RETRIES = 3;
102
+ const INITIAL_RETRY_DELAY_MS = 1000;
103
+
104
+ // Runtime state (module-level for webhook handler access)
105
+ interface ClawTellState {
106
+ runtime: any;
107
+ config: {
108
+ name?: string;
109
+ apiKey?: string;
110
+ webhookSecret?: string;
111
+ webhookPath?: string;
112
+ pollIntervalMs?: number;
113
+ gatewayUrl?: string;
114
+ } | null;
115
+ generatedSecrets: Map<string, string>;
116
+ }
117
+
118
+ const state: ClawTellState = {
119
+ runtime: null,
120
+ config: null,
121
+ generatedSecrets: new Map(),
122
+ };
123
+
124
+ // Helpers
125
+ function sleep(ms: number): Promise<void> {
126
+ return new Promise(resolve => setTimeout(resolve, ms));
127
+ }
128
+
129
+ function getRetryDelay(attempt: number): number {
130
+ const baseDelay = INITIAL_RETRY_DELAY_MS * Math.pow(2, attempt);
131
+ const jitter = Math.random() * 0.3 * baseDelay;
132
+ return Math.min(baseDelay + jitter, 10000);
133
+ }
134
+
135
+ function isRetryableError(status: number): boolean {
136
+ return status >= 500 || status === 429 || status === 408;
137
+ }
138
+
139
+ // API Functions
140
+ async function sendMessage(opts: {
141
+ apiKey: string;
142
+ to: string;
143
+ body: string;
144
+ subject?: string;
145
+ replyToId?: string;
146
+ }): Promise<{ ok: boolean; messageId?: string; error?: Error }> {
147
+ const { apiKey, to, body, subject, replyToId } = opts;
148
+
149
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
150
+ try {
151
+ const response = await fetch(\`\${CLAWTELL_API_BASE}/messages/send\`, {
152
+ method: "POST",
153
+ headers: {
154
+ "Content-Type": "application/json",
155
+ "Authorization": \`Bearer \${apiKey}\`,
156
+ },
157
+ body: JSON.stringify({
158
+ to,
159
+ body,
160
+ subject: subject ?? "Message",
161
+ replyTo: replyToId,
162
+ }),
163
+ signal: AbortSignal.timeout(30000),
164
+ });
165
+
166
+ if (!response.ok) {
167
+ const errorData = await response.json().catch(() => ({}));
168
+ if (attempt < MAX_RETRIES && isRetryableError(response.status)) {
169
+ await sleep(getRetryDelay(attempt));
170
+ continue;
171
+ }
172
+ return { ok: false, error: new Error(errorData.error || \`HTTP \${response.status}\`) };
173
+ }
174
+
175
+ const data = await response.json();
176
+ return { ok: true, messageId: data.messageId };
177
+ } catch (error) {
178
+ if (attempt < MAX_RETRIES) {
179
+ await sleep(getRetryDelay(attempt));
180
+ continue;
181
+ }
182
+ return { ok: false, error: error instanceof Error ? error : new Error(String(error)) };
183
+ }
184
+ }
185
+ return { ok: false, error: new Error("Max retries exceeded") };
186
+ }
187
+
188
+ async function probeApi(apiKey: string): Promise<{ ok: boolean; name?: string; error?: string }> {
189
+ try {
190
+ const response = await fetch(\`\${CLAWTELL_API_BASE}/me\`, {
191
+ headers: { "Authorization": \`Bearer \${apiKey}\` },
192
+ signal: AbortSignal.timeout(10000),
193
+ });
194
+ if (!response.ok) {
195
+ const data = await response.json().catch(() => ({}));
196
+ return { ok: false, error: data.error || \`HTTP \${response.status}\` };
197
+ }
198
+ const data = await response.json();
199
+ return { ok: true, name: data.name };
200
+ } catch (e: any) {
201
+ return { ok: false, error: e.message };
202
+ }
203
+ }
204
+
205
+ async function fetchInbox(apiKey: string): Promise<any[]> {
206
+ const response = await fetch(\`\${CLAWTELL_API_BASE}/messages/inbox?unread=true&limit=50\`, {
207
+ headers: { "Authorization": \`Bearer \${apiKey}\` },
208
+ signal: AbortSignal.timeout(30000),
209
+ });
210
+ if (!response.ok) throw new Error(\`HTTP \${response.status}\`);
211
+ const data = await response.json();
212
+ return data.messages ?? [];
213
+ }
214
+
215
+ async function markAsRead(apiKey: string, messageId: string): Promise<void> {
216
+ await fetch(\`\${CLAWTELL_API_BASE}/messages/\${messageId}/read\`, {
217
+ method: "POST",
218
+ headers: { "Authorization": \`Bearer \${apiKey}\` },
219
+ signal: AbortSignal.timeout(10000),
220
+ }).catch(() => {});
221
+ }
222
+
223
+ async function registerGateway(opts: {
224
+ apiKey: string;
225
+ tellName: string;
226
+ webhookUrl: string;
227
+ webhookSecret: string;
228
+ }): Promise<{ ok: boolean; error?: string }> {
229
+ try {
230
+ const response = await fetch(\`\${CLAWTELL_API_BASE}/names/\${encodeURIComponent(opts.tellName)}\`, {
231
+ method: "PATCH",
232
+ headers: {
233
+ "Content-Type": "application/json",
234
+ "Authorization": \`Bearer \${opts.apiKey}\`,
235
+ },
236
+ body: JSON.stringify({
237
+ gateway_url: opts.webhookUrl,
238
+ webhook_secret: opts.webhookSecret,
239
+ }),
240
+ signal: AbortSignal.timeout(15000),
241
+ });
242
+ if (!response.ok) {
243
+ const data = await response.json().catch(() => ({}));
244
+ return { ok: false, error: data.error || \`HTTP \${response.status}\` };
245
+ }
246
+ return { ok: true };
247
+ } catch (e: any) {
248
+ return { ok: false, error: e.message };
249
+ }
250
+ }
251
+
252
+ // Webhook Handler - CORRECT SIGNATURE: (req, res) => Promise<boolean>
253
+ const rateLimitMap = new Map<string, { count: number; resetAt: number }>();
254
+
255
+ function checkRateLimit(clientId: string): boolean {
256
+ const now = Date.now();
257
+ const entry = rateLimitMap.get(clientId);
258
+ if (!entry || now > entry.resetAt) {
259
+ rateLimitMap.set(clientId, { count: 1, resetAt: now + 60000 });
260
+ return true;
261
+ }
262
+ if (entry.count >= 100) return false;
263
+ entry.count++;
264
+ return true;
265
+ }
266
+
267
+ setInterval(() => {
268
+ const now = Date.now();
269
+ for (const [key, entry] of rateLimitMap) {
270
+ if (now > entry.resetAt) rateLimitMap.delete(key);
271
+ }
272
+ }, 60000);
273
+
274
+ function verifySignature(signature: string | null, body: string, secret: string): boolean {
275
+ if (!signature || !secret) return false;
276
+ const parts = signature.split("=");
277
+ if (parts.length !== 2 || parts[0] !== "sha256") return false;
278
+ try {
279
+ const expected = createHmac("sha256", secret).update(body, "utf8").digest("hex");
280
+ const providedBuf = Buffer.from(parts[1], "hex");
281
+ const expectedBuf = Buffer.from(expected, "hex");
282
+ if (providedBuf.length !== expectedBuf.length) return false;
283
+ return timingSafeEqual(providedBuf, expectedBuf);
284
+ } catch {
285
+ return false;
286
+ }
287
+ }
288
+
289
+ async function readBody(req: IncomingMessage): Promise<string | null> {
290
+ return new Promise((resolve) => {
291
+ const chunks: Buffer[] = [];
292
+ let total = 0;
293
+ req.on("data", (chunk: Buffer) => {
294
+ total += chunk.length;
295
+ if (total > 1024 * 1024) { req.destroy(); resolve(null); return; }
296
+ chunks.push(chunk);
297
+ });
298
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
299
+ req.on("error", () => resolve(null));
300
+ });
301
+ }
302
+
303
+ function sendJson(res: ServerResponse, status: number, data: unknown): void {
304
+ res.statusCode = status;
305
+ res.setHeader("Content-Type", "application/json");
306
+ res.end(JSON.stringify(data));
307
+ }
308
+
309
+ async function handleWebhook(req: IncomingMessage, res: ServerResponse): Promise<boolean> {
310
+ const webhookPath = state.config?.webhookPath ?? "/webhook/clawtell";
311
+ const url = new URL(req.url ?? "/", "http://localhost");
312
+
313
+ if (url.pathname !== webhookPath) return false;
314
+
315
+ if (req.method !== "POST") {
316
+ res.statusCode = 405;
317
+ res.setHeader("Allow", "POST");
318
+ res.end("Method Not Allowed");
319
+ return true;
320
+ }
321
+
322
+ const clientIp = (req.headers["x-forwarded-for"] as string)?.split(",")[0]?.trim()
323
+ || (req.headers["x-real-ip"] as string)
324
+ || req.socket?.remoteAddress || "unknown";
325
+ if (!checkRateLimit(clientIp)) {
326
+ sendJson(res, 429, { error: "Rate limit exceeded" });
327
+ return true;
328
+ }
329
+
330
+ const rawBody = await readBody(req);
331
+ if (!rawBody) {
332
+ sendJson(res, 400, { error: "Failed to read body" });
333
+ return true;
334
+ }
335
+
336
+ const secret = state.config?.webhookSecret || state.generatedSecrets.get("default");
337
+ if (secret) {
338
+ const sig = req.headers["x-clawtell-signature"] as string | undefined;
339
+ if (!verifySignature(sig ?? null, rawBody, secret)) {
340
+ sendJson(res, 401, { error: "Invalid signature" });
341
+ return true;
342
+ }
343
+ }
344
+
345
+ let payload: any;
346
+ try {
347
+ payload = JSON.parse(rawBody);
348
+ } catch {
349
+ sendJson(res, 400, { error: "Invalid JSON" });
350
+ return true;
351
+ }
352
+
353
+ if (!payload.messageId || !payload.from || !payload.body) {
354
+ sendJson(res, 400, { error: "Missing required fields" });
355
+ return true;
356
+ }
357
+
358
+ const senderName = payload.from.replace(/^tell\\//, "");
359
+ const messageContent = payload.subject ? \`**\${payload.subject}**\\n\\n\${payload.body}\` : payload.body;
360
+
361
+ try {
362
+ await state.runtime.routeInboundMessage({
363
+ channel: "clawtell",
364
+ accountId: state.config?.name ?? "default",
365
+ senderId: \`tell/\${senderName}\`,
366
+ senderDisplay: senderName,
367
+ chatId: payload.threadId ?? \`dm:\${senderName}\`,
368
+ chatType: payload.threadId ? "thread" : "direct",
369
+ messageId: payload.messageId,
370
+ text: messageContent,
371
+ timestamp: new Date(payload.timestamp || Date.now()),
372
+ replyToId: payload.replyToMessageId,
373
+ metadata: {
374
+ clawtell: {
375
+ autoReplyEligible: payload.autoReplyEligible,
376
+ subject: payload.subject,
377
+ threadId: payload.threadId,
378
+ },
379
+ },
380
+ });
381
+ sendJson(res, 200, { received: true, messageId: payload.messageId });
382
+ } catch (error) {
383
+ console.error(\`[clawtell] Failed to route message:\`, error);
384
+ sendJson(res, 500, { error: "Failed to process message" });
385
+ }
386
+
387
+ return true;
388
+ }
62
389
 
63
- // Minimal ClawTell channel plugin for Clawdbot
64
- // Full implementation at: https://github.com/Dennis-Da-Menace/clawtell-channel
390
+ // Channel Plugin
391
+ const clawtellChannel = {
392
+ id: "clawtell",
393
+ meta: {
394
+ id: "clawtell",
395
+ label: "ClawTell",
396
+ selectionLabel: "ClawTell (Agent-to-Agent)",
397
+ blurb: "Agent-to-agent messaging via ClawTell network.",
398
+ aliases: ["ct", "tell"],
399
+ order: 80,
400
+ },
401
+ capabilities: {
402
+ chatTypes: ["direct"],
403
+ media: true,
404
+ reactions: false,
405
+ edit: false,
406
+ unsend: false,
407
+ reply: true,
408
+ },
409
+ config: {
410
+ listAccountIds: (cfg: any) => {
411
+ const cc = cfg.channels?.clawtell;
412
+ if (!cc) return [];
413
+ const ids: string[] = [];
414
+ if (cc.name && cc.apiKey) ids.push("default");
415
+ if (cc.accounts) ids.push(...Object.keys(cc.accounts));
416
+ return ids;
417
+ },
418
+ resolveAccount: (cfg: any, accountId?: string) => {
419
+ const cc = cfg.channels?.clawtell ?? {};
420
+ const isDefault = !accountId || accountId === "default";
421
+ const acc = isDefault ? cc : cc.accounts?.[accountId];
422
+ return {
423
+ accountId: accountId ?? "default",
424
+ name: acc?.name ?? accountId ?? "default",
425
+ enabled: acc?.enabled ?? (isDefault && cc.enabled) ?? false,
426
+ configured: Boolean(acc?.name && acc?.apiKey),
427
+ apiKey: acc?.apiKey ?? null,
428
+ tellName: acc?.name ?? null,
429
+ pollIntervalMs: acc?.pollIntervalMs ?? 30000,
430
+ webhookPath: acc?.webhookPath ?? "/webhook/clawtell",
431
+ webhookSecret: acc?.webhookSecret ?? null,
432
+ gatewayUrl: acc?.gatewayUrl ?? null,
433
+ config: acc ?? {},
434
+ };
435
+ },
436
+ defaultAccountId: () => "default",
437
+ isConfigured: (account: any) => account.configured,
438
+ describeAccount: (account: any) => ({
439
+ accountId: account.accountId,
440
+ name: account.name,
441
+ enabled: account.enabled,
442
+ configured: account.configured,
443
+ }),
444
+ },
445
+ messaging: {
446
+ normalizeTarget: (target: string) => target?.trim().toLowerCase().replace(/^tell\\//, "") || null,
447
+ formatTargetDisplay: ({ target }: any) => \`tell/\${target?.replace(/^tell\\//, "") ?? ""}\`,
448
+ },
449
+ outbound: {
450
+ deliveryMode: "direct",
451
+ textChunkLimit: 50000,
452
+ resolveTarget: ({ to }: any) => {
453
+ if (!to?.trim()) return { ok: false, error: new Error("Missing --to") };
454
+ return { ok: true, to: to.trim().toLowerCase().replace(/^tell\\//, "") };
455
+ },
456
+ sendText: async ({ cfg, to, text, accountId, replyToId }: any) => {
457
+ const account = clawtellChannel.config.resolveAccount(cfg, accountId);
458
+ if (!account.apiKey) return { ok: false, error: new Error("No API key") };
459
+ const result = await sendMessage({ apiKey: account.apiKey, to, body: text, replyToId });
460
+ return { channel: "clawtell", ...result };
461
+ },
462
+ sendMedia: async ({ cfg, to, caption, mediaUrl, accountId, replyToId }: any) => {
463
+ const account = clawtellChannel.config.resolveAccount(cfg, accountId);
464
+ if (!account.apiKey) return { ok: false, error: new Error("No API key") };
465
+ const body = mediaUrl ? \`\${caption ?? "Attachment"}\\n\\n\u{1F4CE} \${mediaUrl}\` : caption ?? "";
466
+ const result = await sendMessage({ apiKey: account.apiKey, to, body, replyToId });
467
+ return { channel: "clawtell", ...result };
468
+ },
469
+ },
470
+ status: {
471
+ probeAccount: async ({ account }: any) => {
472
+ if (!account.apiKey) return { ok: false, error: "No API key" };
473
+ return probeApi(account.apiKey);
474
+ },
475
+ },
476
+ gateway: {
477
+ startAccount: async (ctx: any) => {
478
+ const { account, cfg, abortSignal, setStatus, log } = ctx;
479
+
480
+ setStatus({ accountId: account.accountId, running: true, lastStartAt: new Date().toISOString() });
481
+ log?.info(\`[clawtell] Starting (name=\${account.tellName})\`);
482
+
483
+ const gatewayUrl = account.gatewayUrl || cfg.gateway?.publicUrl || cfg.gateway?.url;
484
+ if (gatewayUrl && account.apiKey && account.tellName) {
485
+ let secret = account.webhookSecret;
486
+ if (!secret) {
487
+ secret = randomBytes(32).toString("hex");
488
+ state.generatedSecrets.set(account.accountId, secret);
489
+ log?.info(\`[clawtell] Generated webhook secret\`);
490
+ }
491
+ const webhookUrl = gatewayUrl.replace(/\\/$/, "") + account.webhookPath;
492
+ const reg = await registerGateway({
493
+ apiKey: account.apiKey,
494
+ tellName: account.tellName,
495
+ webhookUrl,
496
+ webhookSecret: secret,
497
+ });
498
+ if (reg.ok) {
499
+ log?.info(\`[clawtell] Registered gateway: \${webhookUrl}\`);
500
+ } else {
501
+ log?.warn(\`[clawtell] Gateway registration failed: \${reg.error}\`);
502
+ }
503
+ }
504
+
505
+ const processedIds = new Set<string>();
506
+ const pollIntervalMs = account.pollIntervalMs;
507
+
508
+ while (!abortSignal.aborted) {
509
+ try {
510
+ const messages = await fetchInbox(account.apiKey);
511
+ for (const msg of messages) {
512
+ if (processedIds.has(msg.id)) continue;
513
+ processedIds.add(msg.id);
514
+
515
+ if (processedIds.size > 1000) {
516
+ const arr = Array.from(processedIds);
517
+ processedIds.clear();
518
+ arr.slice(-500).forEach(id => processedIds.add(id));
519
+ }
520
+
521
+ const senderName = msg.from.replace(/^tell\\//, "");
522
+ const content = msg.subject ? \`**\${msg.subject}**\\n\\n\${msg.body}\` : msg.body;
523
+
524
+ await state.runtime.routeInboundMessage({
525
+ channel: "clawtell",
526
+ accountId: account.accountId,
527
+ senderId: \`tell/\${senderName}\`,
528
+ senderDisplay: senderName,
529
+ chatId: msg.thread_id ?? \`dm:\${senderName}\`,
530
+ chatType: msg.thread_id ? "thread" : "direct",
531
+ messageId: msg.id,
532
+ text: content,
533
+ timestamp: new Date(msg.created_at),
534
+ replyToId: msg.reply_to_id,
535
+ metadata: { clawtell: { autoReplyEligible: msg.auto_reply_eligible } },
536
+ });
537
+
538
+ await markAsRead(account.apiKey, msg.id);
539
+ setStatus({ lastInboundAt: new Date().toISOString() });
540
+ }
541
+ } catch (e: any) {
542
+ setStatus({ lastError: e.message });
543
+ }
544
+
545
+ await new Promise<void>(r => {
546
+ const t = setTimeout(r, pollIntervalMs);
547
+ abortSignal.addEventListener("abort", () => { clearTimeout(t); r(); }, { once: true });
548
+ });
549
+ }
550
+
551
+ setStatus({ running: false, lastStopAt: new Date().toISOString() });
552
+ },
553
+ },
554
+ };
65
555
 
556
+ // Plugin Export
66
557
  const plugin = {
67
558
  id: "clawtell",
68
559
  name: "ClawTell",
69
560
  description: "ClawTell channel plugin - agent-to-agent messaging",
70
561
  configSchema: emptyPluginConfigSchema(),
71
562
  register(api: ClawdbotPluginApi) {
72
- console.log("\u{1F43E} ClawTell plugin loaded");
563
+ state.runtime = api.runtime;
73
564
 
74
- // Register the channel
75
- api.registerChannel({
76
- plugin: {
77
- id: "clawtell",
78
- name: "ClawTell",
79
-
80
- // Channel config
81
- async probe(config: any) {
82
- if (!config.apiKey) {
83
- return { ok: false, error: "Missing apiKey" };
84
- }
85
- try {
86
- const res = await fetch("https://www.clawtell.com/api/me", {
87
- headers: { "Authorization": \`Bearer \${config.apiKey}\` }
88
- });
89
- if (!res.ok) return { ok: false, error: "Invalid API key" };
90
- const data = await res.json();
91
- return { ok: true, detail: \`Connected as tell/\${data.name}\` };
92
- } catch (e: any) {
93
- return { ok: false, error: e.message };
94
- }
95
- },
96
-
97
- // Send message
98
- async send(config: any, message: any) {
99
- const res = await fetch("https://www.clawtell.com/api/messages/send", {
100
- method: "POST",
101
- headers: {
102
- "Authorization": \`Bearer \${config.apiKey}\`,
103
- "Content-Type": "application/json"
104
- },
105
- body: JSON.stringify({
106
- to: message.to || config.name,
107
- body: message.text || message.body,
108
- subject: message.subject
109
- })
110
- });
111
- if (!res.ok) throw new Error(\`Failed to send: \${res.status}\`);
112
- return { ok: true };
113
- },
114
-
115
- // Poll for messages (if no webhook)
116
- async poll(config: any, since?: Date) {
117
- const res = await fetch(\`https://www.clawtell.com/api/messages/inbox?unread=true\`, {
118
- headers: { "Authorization": \`Bearer \${config.apiKey}\` }
119
- });
120
- if (!res.ok) return [];
121
- const data = await res.json();
122
- return (data.messages || []).map((m: any) => ({
123
- id: m.id,
124
- from: m.from_name,
125
- text: m.body,
126
- timestamp: new Date(m.sent_at)
127
- }));
128
- }
129
- }
130
- });
565
+ const cfg = api.runtime.getConfig?.();
566
+ if (cfg?.channels?.clawtell) {
567
+ state.config = cfg.channels.clawtell as any;
568
+ }
569
+
570
+ api.registerChannel({ plugin: clawtellChannel as any });
571
+ api.registerHttpHandler(handleWebhook);
572
+
573
+ console.log("\u{1F43E} ClawTell plugin loaded");
131
574
  },
132
575
  };
133
576
 
134
577
  export default plugin;
135
578
  `;
579
+ var WEBHOOK_HANDLER_TS = `import express from 'express';
580
+ import { ClawTell } from '@dennisdamenace/clawtell';
581
+
582
+ const app = express();
583
+ app.use(express.json());
584
+
585
+ const client = new ClawTell(process.env.CLAWTELL_API_KEY!);
586
+
587
+ // Webhook endpoint to receive messages from other agents
588
+ app.post('/webhook', async (req, res) => {
589
+ const { from, body, subject, metadata } = req.body;
590
+
591
+ console.log(\`\u{1F4E8} Message from \${from}: \${body}\`);
592
+
593
+ // TODO: Process the incoming message
594
+ // Example: Echo back
595
+ // await client.send(from, \`Echo: \${body}\`);
596
+
597
+ res.json({ ok: true });
598
+ });
599
+
600
+ // Health check
601
+ app.get('/health', (req, res) => {
602
+ res.json({ status: 'ok', agent: 'my-agent' });
603
+ });
604
+
605
+ const PORT = process.env.PORT || 3000;
606
+ app.listen(PORT, () => {
607
+ console.log(\`\u{1F43E} ClawTell agent listening on port \${PORT}\`);
608
+ console.log(\` Webhook URL: http://localhost:\${PORT}/webhook\`);
609
+ });
610
+ `;
611
+ var WEBHOOK_HANDLER_JS = `const express = require('express');
612
+ const { ClawTell } = require('@dennisdamenace/clawtell');
613
+
614
+ const app = express();
615
+ app.use(express.json());
616
+
617
+ const client = new ClawTell(process.env.CLAWTELL_API_KEY);
618
+
619
+ // Webhook endpoint to receive messages from other agents
620
+ app.post('/webhook', async (req, res) => {
621
+ const { from, body, subject, metadata } = req.body;
622
+
623
+ console.log(\`\u{1F4E8} Message from \${from}: \${body}\`);
624
+
625
+ // TODO: Process the incoming message
626
+ // Example: Echo back
627
+ // await client.send(from, \`Echo: \${body}\`);
628
+
629
+ res.json({ ok: true });
630
+ });
631
+
632
+ // Health check
633
+ app.get('/health', (req, res) => {
634
+ res.json({ status: 'ok', agent: 'my-agent' });
635
+ });
636
+
637
+ const PORT = process.env.PORT || 3000;
638
+ app.listen(PORT, () => {
639
+ console.log(\`\u{1F43E} ClawTell agent listening on port \${PORT}\`);
640
+ console.log(\` Webhook URL: http://localhost:\${PORT}/webhook\`);
641
+ });
642
+ `;
643
+ var ENV_EXAMPLE = `# ClawTell Configuration
644
+ CLAWTELL_API_KEY=claw_xxx_yyy
645
+
646
+ # Server
647
+ PORT=3000
648
+ `;
136
649
  function installPlugin() {
137
650
  if (!fs.existsSync(CLAWDBOT_DIR)) {
138
651
  console.log("\u2139\uFE0F Clawdbot not detected - skipping plugin install");
@@ -165,9 +678,20 @@ function installPlugin() {
165
678
  console.log(' name: "YOUR_NAME"');
166
679
  console.log(' apiKey: "claw_xxx_yyy"');
167
680
  console.log("");
681
+ console.log(" Then restart: clawdbot gateway restart");
682
+ console.log("");
168
683
  } catch (error) {
169
684
  console.error("\u26A0\uFE0F Failed to install Clawdbot plugin:", error.message);
170
685
  console.log(" You can manually install later with: npx clawtell setup-clawdbot");
171
686
  }
172
687
  }
173
688
  installPlugin();
689
+ // Annotate the CommonJS export names for ESM import in node:
690
+ 0 && (module.exports = {
691
+ ENV_EXAMPLE,
692
+ INDEX_TS,
693
+ PLUGIN_JSON,
694
+ WEBHOOK_HANDLER_JS,
695
+ WEBHOOK_HANDLER_TS,
696
+ installPlugin
697
+ });