@dennisdamenace/clawtell 0.2.3 → 0.2.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,697 @@
1
+ #!/usr/bin/env node
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
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
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
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);
41
+ var fs = __toESM(require("fs"));
42
+ var path = __toESM(require("path"));
43
+ var os = __toESM(require("os"));
44
+ var CLAWDBOT_DIR = path.join(os.homedir(), ".clawdbot");
45
+ var EXTENSIONS_DIR = path.join(CLAWDBOT_DIR, "extensions");
46
+ var PLUGIN_DIR = path.join(EXTENSIONS_DIR, "clawtell");
47
+ var PLUGIN_JSON = {
48
+ id: "clawtell",
49
+ name: "ClawTell",
50
+ version: "2026.2.7",
51
+ channels: ["clawtell"],
52
+ configSchema: {
53
+ type: "object",
54
+ additionalProperties: false,
55
+ properties: {
56
+ name: {
57
+ type: "string",
58
+ description: "Your registered ClawTell name (without tell/ prefix)"
59
+ },
60
+ apiKey: {
61
+ type: "string",
62
+ description: "Your ClawTell API key"
63
+ },
64
+ pollIntervalMs: {
65
+ type: "number",
66
+ default: 3e4,
67
+ description: "How often to poll inbox for new messages (ms)"
68
+ },
69
+ webhookPath: {
70
+ type: "string",
71
+ default: "/webhook/clawtell",
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)"
81
+ }
82
+ }
83
+ }
84
+ };
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";
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
+ }
389
+
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
+ };
555
+
556
+ // Plugin Export
557
+ const plugin = {
558
+ id: "clawtell",
559
+ name: "ClawTell",
560
+ description: "ClawTell channel plugin - agent-to-agent messaging",
561
+ configSchema: emptyPluginConfigSchema(),
562
+ register(api: ClawdbotPluginApi) {
563
+ state.runtime = api.runtime;
564
+
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");
574
+ },
575
+ };
576
+
577
+ export default plugin;
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
+ `;
649
+ function installPlugin() {
650
+ if (!fs.existsSync(CLAWDBOT_DIR)) {
651
+ console.log("\u2139\uFE0F Clawdbot not detected - skipping plugin install");
652
+ console.log(" To use with Clawdbot later, run: npx clawtell setup-clawdbot");
653
+ return;
654
+ }
655
+ console.log("\u{1F43E} Clawdbot detected! Installing ClawTell channel plugin...");
656
+ try {
657
+ if (!fs.existsSync(EXTENSIONS_DIR)) {
658
+ fs.mkdirSync(EXTENSIONS_DIR, { recursive: true });
659
+ }
660
+ if (!fs.existsSync(PLUGIN_DIR)) {
661
+ fs.mkdirSync(PLUGIN_DIR, { recursive: true });
662
+ }
663
+ fs.writeFileSync(
664
+ path.join(PLUGIN_DIR, "clawdbot.plugin.json"),
665
+ JSON.stringify(PLUGIN_JSON, null, 2)
666
+ );
667
+ fs.writeFileSync(
668
+ path.join(PLUGIN_DIR, "index.ts"),
669
+ INDEX_TS
670
+ );
671
+ console.log("\u2705 ClawTell plugin installed to ~/.clawdbot/extensions/clawtell/");
672
+ console.log("");
673
+ console.log("\u{1F4DD} Add this to your Clawdbot config:");
674
+ console.log("");
675
+ console.log(" channels:");
676
+ console.log(" clawtell:");
677
+ console.log(" enabled: true");
678
+ console.log(' name: "YOUR_NAME"');
679
+ console.log(' apiKey: "claw_xxx_yyy"');
680
+ console.log("");
681
+ console.log(" Then restart: clawdbot gateway restart");
682
+ console.log("");
683
+ } catch (error) {
684
+ console.error("\u26A0\uFE0F Failed to install Clawdbot plugin:", error.message);
685
+ console.log(" You can manually install later with: npx clawtell setup-clawdbot");
686
+ }
687
+ }
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
+ });