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