@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.
- package/README.md +125 -184
- package/dist/chunk-Y6FXYEAI.mjs +10 -0
- package/dist/cli.js +107 -0
- package/dist/cli.mjs +110 -0
- package/dist/index.d.mts +142 -22
- package/dist/index.d.ts +155 -22
- package/dist/index.js +143 -22
- package/dist/index.mjs +115 -22
- package/dist/postinstall.d.mts +52 -0
- package/dist/postinstall.d.ts +52 -0
- package/dist/postinstall.js +697 -0
- package/dist/postinstall.mjs +660 -0
- package/package.json +13 -33
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import "./chunk-Y6FXYEAI.mjs";
|
|
2
|
+
|
|
1
3
|
// src/index.ts
|
|
2
4
|
var ClawTellError = class extends Error {
|
|
3
5
|
constructor(message, statusCode) {
|
|
@@ -102,7 +104,7 @@ var ClawTell = class {
|
|
|
102
104
|
throw lastError || new ClawTellError("Request failed after retries");
|
|
103
105
|
}
|
|
104
106
|
cleanName(name) {
|
|
105
|
-
return name.toLowerCase().replace(/^tell\//, "")
|
|
107
|
+
return name.toLowerCase().replace(/^tell\//, "");
|
|
106
108
|
}
|
|
107
109
|
// ─────────────────────────────────────────────────────────────
|
|
108
110
|
// Messages
|
|
@@ -135,6 +137,45 @@ var ClawTell = class {
|
|
|
135
137
|
async markRead(messageId) {
|
|
136
138
|
return this.request("POST", `/messages/${messageId}/read`);
|
|
137
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Long poll for new messages (RECOMMENDED for receiving messages).
|
|
142
|
+
*
|
|
143
|
+
* This is the primary way agents receive messages. The request will:
|
|
144
|
+
* - Return immediately if messages are waiting
|
|
145
|
+
* - Hold connection open until a message arrives OR timeout
|
|
146
|
+
* - Use minimal server resources while waiting
|
|
147
|
+
*
|
|
148
|
+
* @param options.timeout - Max seconds to wait (1-30, default 30)
|
|
149
|
+
* @param options.limit - Max messages to return (1-100, default 50)
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```typescript
|
|
153
|
+
* // Efficient message loop
|
|
154
|
+
* while (true) {
|
|
155
|
+
* const result = await client.poll({ timeout: 30 });
|
|
156
|
+
* for (const msg of result.messages) {
|
|
157
|
+
* console.log(`From: ${msg.from_name}: ${msg.body}`);
|
|
158
|
+
* await client.markRead(msg.id);
|
|
159
|
+
* }
|
|
160
|
+
* // Loop continues - no sleep needed!
|
|
161
|
+
* }
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
async poll(options = {}) {
|
|
165
|
+
const timeout = Math.min(Math.max(options.timeout || 30, 1), 30);
|
|
166
|
+
const limit = Math.min(Math.max(options.limit || 50, 1), 100);
|
|
167
|
+
const params = {
|
|
168
|
+
timeout: String(timeout),
|
|
169
|
+
limit: String(limit)
|
|
170
|
+
};
|
|
171
|
+
const originalTimeout = this.timeout;
|
|
172
|
+
this.timeout = (timeout + 5) * 1e3;
|
|
173
|
+
try {
|
|
174
|
+
return await this.request("GET", "/messages/poll", { params });
|
|
175
|
+
} finally {
|
|
176
|
+
this.timeout = originalTimeout;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
138
179
|
// ─────────────────────────────────────────────────────────────
|
|
139
180
|
// Profile
|
|
140
181
|
// ─────────────────────────────────────────────────────────────
|
|
@@ -304,39 +345,91 @@ var ClawTell = class {
|
|
|
304
345
|
});
|
|
305
346
|
}
|
|
306
347
|
// ─────────────────────────────────────────────────────────────
|
|
307
|
-
//
|
|
348
|
+
// Delivery Channels
|
|
308
349
|
// ─────────────────────────────────────────────────────────────
|
|
309
350
|
/**
|
|
310
|
-
*
|
|
351
|
+
* List your configured delivery channels.
|
|
352
|
+
*
|
|
353
|
+
* @example
|
|
354
|
+
* ```typescript
|
|
355
|
+
* const { channels } = await client.deliveryChannels();
|
|
356
|
+
* for (const ch of channels) {
|
|
357
|
+
* console.log(`${ch.platform}: ${ch.enabled ? 'enabled' : 'disabled'}`);
|
|
358
|
+
* }
|
|
359
|
+
* ```
|
|
360
|
+
*/
|
|
361
|
+
async deliveryChannels() {
|
|
362
|
+
return this.request("GET", "/delivery-channels");
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Add a delivery channel for offline message delivery.
|
|
366
|
+
*
|
|
367
|
+
* @param platform - "telegram", "discord", or "slack"
|
|
368
|
+
* @param credentials - Platform-specific credentials
|
|
369
|
+
* @param sendTestMessage - Whether to send a test message to verify
|
|
370
|
+
*
|
|
371
|
+
* @example
|
|
372
|
+
* ```typescript
|
|
373
|
+
* // Add Telegram
|
|
374
|
+
* await client.addDeliveryChannel('telegram', {
|
|
375
|
+
* botToken: '123456:ABC...',
|
|
376
|
+
* chatId: '987654321'
|
|
377
|
+
* });
|
|
311
378
|
*
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
379
|
+
* // Add Discord
|
|
380
|
+
* await client.addDeliveryChannel('discord', {
|
|
381
|
+
* webhookUrl: 'https://discord.com/api/webhooks/...'
|
|
382
|
+
* });
|
|
315
383
|
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
384
|
+
* // Add Slack
|
|
385
|
+
* await client.addDeliveryChannel('slack', {
|
|
386
|
+
* webhookUrl: 'https://hooks.slack.com/services/...'
|
|
387
|
+
* });
|
|
388
|
+
* ```
|
|
318
389
|
*/
|
|
319
|
-
async
|
|
320
|
-
return this.request("
|
|
390
|
+
async addDeliveryChannel(platform, credentials, sendTestMessage = true) {
|
|
391
|
+
return this.request("POST", "/delivery-channels", {
|
|
392
|
+
body: {
|
|
393
|
+
platform,
|
|
394
|
+
credentials,
|
|
395
|
+
sendTestMessage
|
|
396
|
+
}
|
|
397
|
+
});
|
|
321
398
|
}
|
|
322
399
|
/**
|
|
323
|
-
*
|
|
400
|
+
* Remove a delivery channel.
|
|
324
401
|
*
|
|
325
|
-
*
|
|
402
|
+
* @param platform - "telegram", "discord", or "slack"
|
|
403
|
+
*/
|
|
404
|
+
async removeDeliveryChannel(platform) {
|
|
405
|
+
return this.request("DELETE", `/delivery-channels?platform=${platform}`);
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Discover available Telegram chats for a bot.
|
|
409
|
+
* Use this to find your chat ID when setting up Telegram delivery.
|
|
410
|
+
* You must send a message to your bot first.
|
|
411
|
+
*
|
|
412
|
+
* @param botToken - Your Telegram bot token from @BotFather
|
|
326
413
|
*
|
|
327
|
-
* @
|
|
328
|
-
*
|
|
414
|
+
* @example
|
|
415
|
+
* ```typescript
|
|
416
|
+
* const result = await client.discoverTelegramChats('123456:ABC...');
|
|
417
|
+
* console.log(`Bot: @${result.botInfo.username}`);
|
|
418
|
+
* for (const chat of result.chats) {
|
|
419
|
+
* console.log(` Chat ID: ${chat.id} (${chat.type})`);
|
|
420
|
+
* }
|
|
421
|
+
* ```
|
|
329
422
|
*/
|
|
330
|
-
async
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
423
|
+
async discoverTelegramChats(botToken) {
|
|
424
|
+
return this.request("POST", "/delivery-channels/discover", {
|
|
425
|
+
body: {
|
|
426
|
+
platform: "telegram",
|
|
427
|
+
botToken
|
|
428
|
+
}
|
|
429
|
+
});
|
|
337
430
|
}
|
|
338
431
|
};
|
|
339
|
-
var SDK_VERSION = "0.2.
|
|
432
|
+
var SDK_VERSION = "0.2.2";
|
|
340
433
|
var index_default = ClawTell;
|
|
341
434
|
export {
|
|
342
435
|
AuthenticationError,
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ClawTell SDK Postinstall Script
|
|
4
|
+
* Automatically installs the Clawdbot channel plugin if Clawdbot is detected
|
|
5
|
+
*
|
|
6
|
+
* @version 2026.2.7
|
|
7
|
+
*/
|
|
8
|
+
declare const PLUGIN_JSON: {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
version: string;
|
|
12
|
+
channels: string[];
|
|
13
|
+
configSchema: {
|
|
14
|
+
type: string;
|
|
15
|
+
additionalProperties: boolean;
|
|
16
|
+
properties: {
|
|
17
|
+
name: {
|
|
18
|
+
type: string;
|
|
19
|
+
description: string;
|
|
20
|
+
};
|
|
21
|
+
apiKey: {
|
|
22
|
+
type: string;
|
|
23
|
+
description: string;
|
|
24
|
+
};
|
|
25
|
+
pollIntervalMs: {
|
|
26
|
+
type: string;
|
|
27
|
+
default: number;
|
|
28
|
+
description: string;
|
|
29
|
+
};
|
|
30
|
+
webhookPath: {
|
|
31
|
+
type: string;
|
|
32
|
+
default: string;
|
|
33
|
+
description: string;
|
|
34
|
+
};
|
|
35
|
+
webhookSecret: {
|
|
36
|
+
type: string;
|
|
37
|
+
description: string;
|
|
38
|
+
};
|
|
39
|
+
gatewayUrl: {
|
|
40
|
+
type: string;
|
|
41
|
+
description: string;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
declare const INDEX_TS = "/**\n * ClawTell Channel Plugin for Clawdbot\n * \n * Embedded version for SDK auto-install.\n * Production-ready with correct webhook handler signature.\n * \n * @license MIT\n * @version 2026.2.7\n */\n\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport type { ClawdbotPluginApi } from \"clawdbot/plugin-sdk\";\nimport { emptyPluginConfigSchema } from \"clawdbot/plugin-sdk\";\nimport { createHmac, timingSafeEqual, randomBytes } from \"crypto\";\n\nconst CLAWTELL_API_BASE = \"https://clawtell.com/api\";\nconst MAX_RETRIES = 3;\nconst INITIAL_RETRY_DELAY_MS = 1000;\n\n// Runtime state (module-level for webhook handler access)\ninterface ClawTellState {\n runtime: any;\n config: {\n name?: string;\n apiKey?: string;\n webhookSecret?: string;\n webhookPath?: string;\n pollIntervalMs?: number;\n gatewayUrl?: string;\n } | null;\n generatedSecrets: Map<string, string>;\n}\n\nconst state: ClawTellState = {\n runtime: null,\n config: null,\n generatedSecrets: new Map(),\n};\n\n// Helpers\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\nfunction getRetryDelay(attempt: number): number {\n const baseDelay = INITIAL_RETRY_DELAY_MS * Math.pow(2, attempt);\n const jitter = Math.random() * 0.3 * baseDelay;\n return Math.min(baseDelay + jitter, 10000);\n}\n\nfunction isRetryableError(status: number): boolean {\n return status >= 500 || status === 429 || status === 408;\n}\n\n// API Functions\nasync function sendMessage(opts: {\n apiKey: string;\n to: string;\n body: string;\n subject?: string;\n replyToId?: string;\n}): Promise<{ ok: boolean; messageId?: string; error?: Error }> {\n const { apiKey, to, body, subject, replyToId } = opts;\n \n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n try {\n const response = await fetch(`${CLAWTELL_API_BASE}/messages/send`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${apiKey}`,\n },\n body: JSON.stringify({\n to,\n body,\n subject: subject ?? \"Message\",\n replyTo: replyToId,\n }),\n signal: AbortSignal.timeout(30000),\n });\n \n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n if (attempt < MAX_RETRIES && isRetryableError(response.status)) {\n await sleep(getRetryDelay(attempt));\n continue;\n }\n return { ok: false, error: new Error(errorData.error || `HTTP ${response.status}`) };\n }\n \n const data = await response.json();\n return { ok: true, messageId: data.messageId };\n } catch (error) {\n if (attempt < MAX_RETRIES) {\n await sleep(getRetryDelay(attempt));\n continue;\n }\n return { ok: false, error: error instanceof Error ? error : new Error(String(error)) };\n }\n }\n return { ok: false, error: new Error(\"Max retries exceeded\") };\n}\n\nasync function probeApi(apiKey: string): Promise<{ ok: boolean; name?: string; error?: string }> {\n try {\n const response = await fetch(`${CLAWTELL_API_BASE}/me`, {\n headers: { \"Authorization\": `Bearer ${apiKey}` },\n signal: AbortSignal.timeout(10000),\n });\n if (!response.ok) {\n const data = await response.json().catch(() => ({}));\n return { ok: false, error: data.error || `HTTP ${response.status}` };\n }\n const data = await response.json();\n return { ok: true, name: data.name };\n } catch (e: any) {\n return { ok: false, error: e.message };\n }\n}\n\nasync function fetchInbox(apiKey: string): Promise<any[]> {\n const response = await fetch(`${CLAWTELL_API_BASE}/messages/inbox?unread=true&limit=50`, {\n headers: { \"Authorization\": `Bearer ${apiKey}` },\n signal: AbortSignal.timeout(30000),\n });\n if (!response.ok) throw new Error(`HTTP ${response.status}`);\n const data = await response.json();\n return data.messages ?? [];\n}\n\nasync function markAsRead(apiKey: string, messageId: string): Promise<void> {\n await fetch(`${CLAWTELL_API_BASE}/messages/${messageId}/read`, {\n method: \"POST\",\n headers: { \"Authorization\": `Bearer ${apiKey}` },\n signal: AbortSignal.timeout(10000),\n }).catch(() => {});\n}\n\nasync function registerGateway(opts: {\n apiKey: string;\n tellName: string;\n webhookUrl: string;\n webhookSecret: string;\n}): Promise<{ ok: boolean; error?: string }> {\n try {\n const response = await fetch(`${CLAWTELL_API_BASE}/names/${encodeURIComponent(opts.tellName)}`, {\n method: \"PATCH\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${opts.apiKey}`,\n },\n body: JSON.stringify({\n gateway_url: opts.webhookUrl,\n webhook_secret: opts.webhookSecret,\n }),\n signal: AbortSignal.timeout(15000),\n });\n if (!response.ok) {\n const data = await response.json().catch(() => ({}));\n return { ok: false, error: data.error || `HTTP ${response.status}` };\n }\n return { ok: true };\n } catch (e: any) {\n return { ok: false, error: e.message };\n }\n}\n\n// Webhook Handler - CORRECT SIGNATURE: (req, res) => Promise<boolean>\nconst rateLimitMap = new Map<string, { count: number; resetAt: number }>();\n\nfunction checkRateLimit(clientId: string): boolean {\n const now = Date.now();\n const entry = rateLimitMap.get(clientId);\n if (!entry || now > entry.resetAt) {\n rateLimitMap.set(clientId, { count: 1, resetAt: now + 60000 });\n return true;\n }\n if (entry.count >= 100) return false;\n entry.count++;\n return true;\n}\n\nsetInterval(() => {\n const now = Date.now();\n for (const [key, entry] of rateLimitMap) {\n if (now > entry.resetAt) rateLimitMap.delete(key);\n }\n}, 60000);\n\nfunction verifySignature(signature: string | null, body: string, secret: string): boolean {\n if (!signature || !secret) return false;\n const parts = signature.split(\"=\");\n if (parts.length !== 2 || parts[0] !== \"sha256\") return false;\n try {\n const expected = createHmac(\"sha256\", secret).update(body, \"utf8\").digest(\"hex\");\n const providedBuf = Buffer.from(parts[1], \"hex\");\n const expectedBuf = Buffer.from(expected, \"hex\");\n if (providedBuf.length !== expectedBuf.length) return false;\n return timingSafeEqual(providedBuf, expectedBuf);\n } catch {\n return false;\n }\n}\n\nasync function readBody(req: IncomingMessage): Promise<string | null> {\n return new Promise((resolve) => {\n const chunks: Buffer[] = [];\n let total = 0;\n req.on(\"data\", (chunk: Buffer) => {\n total += chunk.length;\n if (total > 1024 * 1024) { req.destroy(); resolve(null); return; }\n chunks.push(chunk);\n });\n req.on(\"end\", () => resolve(Buffer.concat(chunks).toString(\"utf8\")));\n req.on(\"error\", () => resolve(null));\n });\n}\n\nfunction sendJson(res: ServerResponse, status: number, data: unknown): void {\n res.statusCode = status;\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(JSON.stringify(data));\n}\n\nasync function handleWebhook(req: IncomingMessage, res: ServerResponse): Promise<boolean> {\n const webhookPath = state.config?.webhookPath ?? \"/webhook/clawtell\";\n const url = new URL(req.url ?? \"/\", \"http://localhost\");\n \n if (url.pathname !== webhookPath) return false;\n \n if (req.method !== \"POST\") {\n res.statusCode = 405;\n res.setHeader(\"Allow\", \"POST\");\n res.end(\"Method Not Allowed\");\n return true;\n }\n \n const clientIp = (req.headers[\"x-forwarded-for\"] as string)?.split(\",\")[0]?.trim()\n || (req.headers[\"x-real-ip\"] as string)\n || req.socket?.remoteAddress || \"unknown\";\n if (!checkRateLimit(clientIp)) {\n sendJson(res, 429, { error: \"Rate limit exceeded\" });\n return true;\n }\n \n const rawBody = await readBody(req);\n if (!rawBody) {\n sendJson(res, 400, { error: \"Failed to read body\" });\n return true;\n }\n \n const secret = state.config?.webhookSecret || state.generatedSecrets.get(\"default\");\n if (secret) {\n const sig = req.headers[\"x-clawtell-signature\"] as string | undefined;\n if (!verifySignature(sig ?? null, rawBody, secret)) {\n sendJson(res, 401, { error: \"Invalid signature\" });\n return true;\n }\n }\n \n let payload: any;\n try {\n payload = JSON.parse(rawBody);\n } catch {\n sendJson(res, 400, { error: \"Invalid JSON\" });\n return true;\n }\n \n if (!payload.messageId || !payload.from || !payload.body) {\n sendJson(res, 400, { error: \"Missing required fields\" });\n return true;\n }\n \n const senderName = payload.from.replace(/^tell\\//, \"\");\n const messageContent = payload.subject ? `**${payload.subject}**\\n\\n${payload.body}` : payload.body;\n \n try {\n await state.runtime.routeInboundMessage({\n channel: \"clawtell\",\n accountId: state.config?.name ?? \"default\",\n senderId: `tell/${senderName}`,\n senderDisplay: senderName,\n chatId: payload.threadId ?? `dm:${senderName}`,\n chatType: payload.threadId ? \"thread\" : \"direct\",\n messageId: payload.messageId,\n text: messageContent,\n timestamp: new Date(payload.timestamp || Date.now()),\n replyToId: payload.replyToMessageId,\n metadata: {\n clawtell: {\n autoReplyEligible: payload.autoReplyEligible,\n subject: payload.subject,\n threadId: payload.threadId,\n },\n },\n });\n sendJson(res, 200, { received: true, messageId: payload.messageId });\n } catch (error) {\n console.error(`[clawtell] Failed to route message:`, error);\n sendJson(res, 500, { error: \"Failed to process message\" });\n }\n \n return true;\n}\n\n// Channel Plugin\nconst clawtellChannel = {\n id: \"clawtell\",\n meta: {\n id: \"clawtell\",\n label: \"ClawTell\",\n selectionLabel: \"ClawTell (Agent-to-Agent)\",\n blurb: \"Agent-to-agent messaging via ClawTell network.\",\n aliases: [\"ct\", \"tell\"],\n order: 80,\n },\n capabilities: {\n chatTypes: [\"direct\"],\n media: true,\n reactions: false,\n edit: false,\n unsend: false,\n reply: true,\n },\n config: {\n listAccountIds: (cfg: any) => {\n const cc = cfg.channels?.clawtell;\n if (!cc) return [];\n const ids: string[] = [];\n if (cc.name && cc.apiKey) ids.push(\"default\");\n if (cc.accounts) ids.push(...Object.keys(cc.accounts));\n return ids;\n },\n resolveAccount: (cfg: any, accountId?: string) => {\n const cc = cfg.channels?.clawtell ?? {};\n const isDefault = !accountId || accountId === \"default\";\n const acc = isDefault ? cc : cc.accounts?.[accountId];\n return {\n accountId: accountId ?? \"default\",\n name: acc?.name ?? accountId ?? \"default\",\n enabled: acc?.enabled ?? (isDefault && cc.enabled) ?? false,\n configured: Boolean(acc?.name && acc?.apiKey),\n apiKey: acc?.apiKey ?? null,\n tellName: acc?.name ?? null,\n pollIntervalMs: acc?.pollIntervalMs ?? 30000,\n webhookPath: acc?.webhookPath ?? \"/webhook/clawtell\",\n webhookSecret: acc?.webhookSecret ?? null,\n gatewayUrl: acc?.gatewayUrl ?? null,\n config: acc ?? {},\n };\n },\n defaultAccountId: () => \"default\",\n isConfigured: (account: any) => account.configured,\n describeAccount: (account: any) => ({\n accountId: account.accountId,\n name: account.name,\n enabled: account.enabled,\n configured: account.configured,\n }),\n },\n messaging: {\n normalizeTarget: (target: string) => target?.trim().toLowerCase().replace(/^tell\\//, \"\") || null,\n formatTargetDisplay: ({ target }: any) => `tell/${target?.replace(/^tell\\//, \"\") ?? \"\"}`,\n },\n outbound: {\n deliveryMode: \"direct\",\n textChunkLimit: 50000,\n resolveTarget: ({ to }: any) => {\n if (!to?.trim()) return { ok: false, error: new Error(\"Missing --to\") };\n return { ok: true, to: to.trim().toLowerCase().replace(/^tell\\//, \"\") };\n },\n sendText: async ({ cfg, to, text, accountId, replyToId }: any) => {\n const account = clawtellChannel.config.resolveAccount(cfg, accountId);\n if (!account.apiKey) return { ok: false, error: new Error(\"No API key\") };\n const result = await sendMessage({ apiKey: account.apiKey, to, body: text, replyToId });\n return { channel: \"clawtell\", ...result };\n },\n sendMedia: async ({ cfg, to, caption, mediaUrl, accountId, replyToId }: any) => {\n const account = clawtellChannel.config.resolveAccount(cfg, accountId);\n if (!account.apiKey) return { ok: false, error: new Error(\"No API key\") };\n const body = mediaUrl ? `${caption ?? \"Attachment\"}\\n\\n\uD83D\uDCCE ${mediaUrl}` : caption ?? \"\";\n const result = await sendMessage({ apiKey: account.apiKey, to, body, replyToId });\n return { channel: \"clawtell\", ...result };\n },\n },\n status: {\n probeAccount: async ({ account }: any) => {\n if (!account.apiKey) return { ok: false, error: \"No API key\" };\n return probeApi(account.apiKey);\n },\n },\n gateway: {\n startAccount: async (ctx: any) => {\n const { account, cfg, abortSignal, setStatus, log } = ctx;\n \n setStatus({ accountId: account.accountId, running: true, lastStartAt: new Date().toISOString() });\n log?.info(`[clawtell] Starting (name=${account.tellName})`);\n \n const gatewayUrl = account.gatewayUrl || cfg.gateway?.publicUrl || cfg.gateway?.url;\n if (gatewayUrl && account.apiKey && account.tellName) {\n let secret = account.webhookSecret;\n if (!secret) {\n secret = randomBytes(32).toString(\"hex\");\n state.generatedSecrets.set(account.accountId, secret);\n log?.info(`[clawtell] Generated webhook secret`);\n }\n const webhookUrl = gatewayUrl.replace(/\\/$/, \"\") + account.webhookPath;\n const reg = await registerGateway({\n apiKey: account.apiKey,\n tellName: account.tellName,\n webhookUrl,\n webhookSecret: secret,\n });\n if (reg.ok) {\n log?.info(`[clawtell] Registered gateway: ${webhookUrl}`);\n } else {\n log?.warn(`[clawtell] Gateway registration failed: ${reg.error}`);\n }\n }\n \n const processedIds = new Set<string>();\n const pollIntervalMs = account.pollIntervalMs;\n \n while (!abortSignal.aborted) {\n try {\n const messages = await fetchInbox(account.apiKey);\n for (const msg of messages) {\n if (processedIds.has(msg.id)) continue;\n processedIds.add(msg.id);\n \n if (processedIds.size > 1000) {\n const arr = Array.from(processedIds);\n processedIds.clear();\n arr.slice(-500).forEach(id => processedIds.add(id));\n }\n \n const senderName = msg.from.replace(/^tell\\//, \"\");\n const content = msg.subject ? `**${msg.subject}**\\n\\n${msg.body}` : msg.body;\n \n await state.runtime.routeInboundMessage({\n channel: \"clawtell\",\n accountId: account.accountId,\n senderId: `tell/${senderName}`,\n senderDisplay: senderName,\n chatId: msg.thread_id ?? `dm:${senderName}`,\n chatType: msg.thread_id ? \"thread\" : \"direct\",\n messageId: msg.id,\n text: content,\n timestamp: new Date(msg.created_at),\n replyToId: msg.reply_to_id,\n metadata: { clawtell: { autoReplyEligible: msg.auto_reply_eligible } },\n });\n \n await markAsRead(account.apiKey, msg.id);\n setStatus({ lastInboundAt: new Date().toISOString() });\n }\n } catch (e: any) {\n setStatus({ lastError: e.message });\n }\n \n await new Promise<void>(r => {\n const t = setTimeout(r, pollIntervalMs);\n abortSignal.addEventListener(\"abort\", () => { clearTimeout(t); r(); }, { once: true });\n });\n }\n \n setStatus({ running: false, lastStopAt: new Date().toISOString() });\n },\n },\n};\n\n// Plugin Export\nconst plugin = {\n id: \"clawtell\",\n name: \"ClawTell\",\n description: \"ClawTell channel plugin - agent-to-agent messaging\",\n configSchema: emptyPluginConfigSchema(),\n register(api: ClawdbotPluginApi) {\n state.runtime = api.runtime;\n \n const cfg = api.runtime.getConfig?.();\n if (cfg?.channels?.clawtell) {\n state.config = cfg.channels.clawtell as any;\n }\n \n api.registerChannel({ plugin: clawtellChannel as any });\n api.registerHttpHandler(handleWebhook);\n \n console.log(\"\uD83D\uDC3E ClawTell plugin loaded\");\n },\n};\n\nexport default plugin;\n";
|
|
47
|
+
declare const WEBHOOK_HANDLER_TS = "import express from 'express';\nimport { ClawTell } from '@dennisdamenace/clawtell';\n\nconst app = express();\napp.use(express.json());\n\nconst client = new ClawTell(process.env.CLAWTELL_API_KEY!);\n\n// Webhook endpoint to receive messages from other agents\napp.post('/webhook', async (req, res) => {\n const { from, body, subject, metadata } = req.body;\n \n console.log(`\uD83D\uDCE8 Message from ${from}: ${body}`);\n \n // TODO: Process the incoming message\n // Example: Echo back\n // await client.send(from, `Echo: ${body}`);\n \n res.json({ ok: true });\n});\n\n// Health check\napp.get('/health', (req, res) => {\n res.json({ status: 'ok', agent: 'my-agent' });\n});\n\nconst PORT = process.env.PORT || 3000;\napp.listen(PORT, () => {\n console.log(`\uD83D\uDC3E ClawTell agent listening on port ${PORT}`);\n console.log(` Webhook URL: http://localhost:${PORT}/webhook`);\n});\n";
|
|
48
|
+
declare const WEBHOOK_HANDLER_JS = "const express = require('express');\nconst { ClawTell } = require('@dennisdamenace/clawtell');\n\nconst app = express();\napp.use(express.json());\n\nconst client = new ClawTell(process.env.CLAWTELL_API_KEY);\n\n// Webhook endpoint to receive messages from other agents\napp.post('/webhook', async (req, res) => {\n const { from, body, subject, metadata } = req.body;\n \n console.log(`\uD83D\uDCE8 Message from ${from}: ${body}`);\n \n // TODO: Process the incoming message\n // Example: Echo back\n // await client.send(from, `Echo: ${body}`);\n \n res.json({ ok: true });\n});\n\n// Health check\napp.get('/health', (req, res) => {\n res.json({ status: 'ok', agent: 'my-agent' });\n});\n\nconst PORT = process.env.PORT || 3000;\napp.listen(PORT, () => {\n console.log(`\uD83D\uDC3E ClawTell agent listening on port ${PORT}`);\n console.log(` Webhook URL: http://localhost:${PORT}/webhook`);\n});\n";
|
|
49
|
+
declare const ENV_EXAMPLE = "# ClawTell Configuration\nCLAWTELL_API_KEY=claw_xxx_yyy\n\n# Server\nPORT=3000\n";
|
|
50
|
+
declare function installPlugin(): void;
|
|
51
|
+
|
|
52
|
+
export { ENV_EXAMPLE, INDEX_TS, PLUGIN_JSON, WEBHOOK_HANDLER_JS, WEBHOOK_HANDLER_TS, installPlugin };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ClawTell SDK Postinstall Script
|
|
4
|
+
* Automatically installs the Clawdbot channel plugin if Clawdbot is detected
|
|
5
|
+
*
|
|
6
|
+
* @version 2026.2.7
|
|
7
|
+
*/
|
|
8
|
+
declare const PLUGIN_JSON: {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
version: string;
|
|
12
|
+
channels: string[];
|
|
13
|
+
configSchema: {
|
|
14
|
+
type: string;
|
|
15
|
+
additionalProperties: boolean;
|
|
16
|
+
properties: {
|
|
17
|
+
name: {
|
|
18
|
+
type: string;
|
|
19
|
+
description: string;
|
|
20
|
+
};
|
|
21
|
+
apiKey: {
|
|
22
|
+
type: string;
|
|
23
|
+
description: string;
|
|
24
|
+
};
|
|
25
|
+
pollIntervalMs: {
|
|
26
|
+
type: string;
|
|
27
|
+
default: number;
|
|
28
|
+
description: string;
|
|
29
|
+
};
|
|
30
|
+
webhookPath: {
|
|
31
|
+
type: string;
|
|
32
|
+
default: string;
|
|
33
|
+
description: string;
|
|
34
|
+
};
|
|
35
|
+
webhookSecret: {
|
|
36
|
+
type: string;
|
|
37
|
+
description: string;
|
|
38
|
+
};
|
|
39
|
+
gatewayUrl: {
|
|
40
|
+
type: string;
|
|
41
|
+
description: string;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
declare const INDEX_TS = "/**\n * ClawTell Channel Plugin for Clawdbot\n * \n * Embedded version for SDK auto-install.\n * Production-ready with correct webhook handler signature.\n * \n * @license MIT\n * @version 2026.2.7\n */\n\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport type { ClawdbotPluginApi } from \"clawdbot/plugin-sdk\";\nimport { emptyPluginConfigSchema } from \"clawdbot/plugin-sdk\";\nimport { createHmac, timingSafeEqual, randomBytes } from \"crypto\";\n\nconst CLAWTELL_API_BASE = \"https://clawtell.com/api\";\nconst MAX_RETRIES = 3;\nconst INITIAL_RETRY_DELAY_MS = 1000;\n\n// Runtime state (module-level for webhook handler access)\ninterface ClawTellState {\n runtime: any;\n config: {\n name?: string;\n apiKey?: string;\n webhookSecret?: string;\n webhookPath?: string;\n pollIntervalMs?: number;\n gatewayUrl?: string;\n } | null;\n generatedSecrets: Map<string, string>;\n}\n\nconst state: ClawTellState = {\n runtime: null,\n config: null,\n generatedSecrets: new Map(),\n};\n\n// Helpers\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\nfunction getRetryDelay(attempt: number): number {\n const baseDelay = INITIAL_RETRY_DELAY_MS * Math.pow(2, attempt);\n const jitter = Math.random() * 0.3 * baseDelay;\n return Math.min(baseDelay + jitter, 10000);\n}\n\nfunction isRetryableError(status: number): boolean {\n return status >= 500 || status === 429 || status === 408;\n}\n\n// API Functions\nasync function sendMessage(opts: {\n apiKey: string;\n to: string;\n body: string;\n subject?: string;\n replyToId?: string;\n}): Promise<{ ok: boolean; messageId?: string; error?: Error }> {\n const { apiKey, to, body, subject, replyToId } = opts;\n \n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n try {\n const response = await fetch(`${CLAWTELL_API_BASE}/messages/send`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${apiKey}`,\n },\n body: JSON.stringify({\n to,\n body,\n subject: subject ?? \"Message\",\n replyTo: replyToId,\n }),\n signal: AbortSignal.timeout(30000),\n });\n \n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n if (attempt < MAX_RETRIES && isRetryableError(response.status)) {\n await sleep(getRetryDelay(attempt));\n continue;\n }\n return { ok: false, error: new Error(errorData.error || `HTTP ${response.status}`) };\n }\n \n const data = await response.json();\n return { ok: true, messageId: data.messageId };\n } catch (error) {\n if (attempt < MAX_RETRIES) {\n await sleep(getRetryDelay(attempt));\n continue;\n }\n return { ok: false, error: error instanceof Error ? error : new Error(String(error)) };\n }\n }\n return { ok: false, error: new Error(\"Max retries exceeded\") };\n}\n\nasync function probeApi(apiKey: string): Promise<{ ok: boolean; name?: string; error?: string }> {\n try {\n const response = await fetch(`${CLAWTELL_API_BASE}/me`, {\n headers: { \"Authorization\": `Bearer ${apiKey}` },\n signal: AbortSignal.timeout(10000),\n });\n if (!response.ok) {\n const data = await response.json().catch(() => ({}));\n return { ok: false, error: data.error || `HTTP ${response.status}` };\n }\n const data = await response.json();\n return { ok: true, name: data.name };\n } catch (e: any) {\n return { ok: false, error: e.message };\n }\n}\n\nasync function fetchInbox(apiKey: string): Promise<any[]> {\n const response = await fetch(`${CLAWTELL_API_BASE}/messages/inbox?unread=true&limit=50`, {\n headers: { \"Authorization\": `Bearer ${apiKey}` },\n signal: AbortSignal.timeout(30000),\n });\n if (!response.ok) throw new Error(`HTTP ${response.status}`);\n const data = await response.json();\n return data.messages ?? [];\n}\n\nasync function markAsRead(apiKey: string, messageId: string): Promise<void> {\n await fetch(`${CLAWTELL_API_BASE}/messages/${messageId}/read`, {\n method: \"POST\",\n headers: { \"Authorization\": `Bearer ${apiKey}` },\n signal: AbortSignal.timeout(10000),\n }).catch(() => {});\n}\n\nasync function registerGateway(opts: {\n apiKey: string;\n tellName: string;\n webhookUrl: string;\n webhookSecret: string;\n}): Promise<{ ok: boolean; error?: string }> {\n try {\n const response = await fetch(`${CLAWTELL_API_BASE}/names/${encodeURIComponent(opts.tellName)}`, {\n method: \"PATCH\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${opts.apiKey}`,\n },\n body: JSON.stringify({\n gateway_url: opts.webhookUrl,\n webhook_secret: opts.webhookSecret,\n }),\n signal: AbortSignal.timeout(15000),\n });\n if (!response.ok) {\n const data = await response.json().catch(() => ({}));\n return { ok: false, error: data.error || `HTTP ${response.status}` };\n }\n return { ok: true };\n } catch (e: any) {\n return { ok: false, error: e.message };\n }\n}\n\n// Webhook Handler - CORRECT SIGNATURE: (req, res) => Promise<boolean>\nconst rateLimitMap = new Map<string, { count: number; resetAt: number }>();\n\nfunction checkRateLimit(clientId: string): boolean {\n const now = Date.now();\n const entry = rateLimitMap.get(clientId);\n if (!entry || now > entry.resetAt) {\n rateLimitMap.set(clientId, { count: 1, resetAt: now + 60000 });\n return true;\n }\n if (entry.count >= 100) return false;\n entry.count++;\n return true;\n}\n\nsetInterval(() => {\n const now = Date.now();\n for (const [key, entry] of rateLimitMap) {\n if (now > entry.resetAt) rateLimitMap.delete(key);\n }\n}, 60000);\n\nfunction verifySignature(signature: string | null, body: string, secret: string): boolean {\n if (!signature || !secret) return false;\n const parts = signature.split(\"=\");\n if (parts.length !== 2 || parts[0] !== \"sha256\") return false;\n try {\n const expected = createHmac(\"sha256\", secret).update(body, \"utf8\").digest(\"hex\");\n const providedBuf = Buffer.from(parts[1], \"hex\");\n const expectedBuf = Buffer.from(expected, \"hex\");\n if (providedBuf.length !== expectedBuf.length) return false;\n return timingSafeEqual(providedBuf, expectedBuf);\n } catch {\n return false;\n }\n}\n\nasync function readBody(req: IncomingMessage): Promise<string | null> {\n return new Promise((resolve) => {\n const chunks: Buffer[] = [];\n let total = 0;\n req.on(\"data\", (chunk: Buffer) => {\n total += chunk.length;\n if (total > 1024 * 1024) { req.destroy(); resolve(null); return; }\n chunks.push(chunk);\n });\n req.on(\"end\", () => resolve(Buffer.concat(chunks).toString(\"utf8\")));\n req.on(\"error\", () => resolve(null));\n });\n}\n\nfunction sendJson(res: ServerResponse, status: number, data: unknown): void {\n res.statusCode = status;\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(JSON.stringify(data));\n}\n\nasync function handleWebhook(req: IncomingMessage, res: ServerResponse): Promise<boolean> {\n const webhookPath = state.config?.webhookPath ?? \"/webhook/clawtell\";\n const url = new URL(req.url ?? \"/\", \"http://localhost\");\n \n if (url.pathname !== webhookPath) return false;\n \n if (req.method !== \"POST\") {\n res.statusCode = 405;\n res.setHeader(\"Allow\", \"POST\");\n res.end(\"Method Not Allowed\");\n return true;\n }\n \n const clientIp = (req.headers[\"x-forwarded-for\"] as string)?.split(\",\")[0]?.trim()\n || (req.headers[\"x-real-ip\"] as string)\n || req.socket?.remoteAddress || \"unknown\";\n if (!checkRateLimit(clientIp)) {\n sendJson(res, 429, { error: \"Rate limit exceeded\" });\n return true;\n }\n \n const rawBody = await readBody(req);\n if (!rawBody) {\n sendJson(res, 400, { error: \"Failed to read body\" });\n return true;\n }\n \n const secret = state.config?.webhookSecret || state.generatedSecrets.get(\"default\");\n if (secret) {\n const sig = req.headers[\"x-clawtell-signature\"] as string | undefined;\n if (!verifySignature(sig ?? null, rawBody, secret)) {\n sendJson(res, 401, { error: \"Invalid signature\" });\n return true;\n }\n }\n \n let payload: any;\n try {\n payload = JSON.parse(rawBody);\n } catch {\n sendJson(res, 400, { error: \"Invalid JSON\" });\n return true;\n }\n \n if (!payload.messageId || !payload.from || !payload.body) {\n sendJson(res, 400, { error: \"Missing required fields\" });\n return true;\n }\n \n const senderName = payload.from.replace(/^tell\\//, \"\");\n const messageContent = payload.subject ? `**${payload.subject}**\\n\\n${payload.body}` : payload.body;\n \n try {\n await state.runtime.routeInboundMessage({\n channel: \"clawtell\",\n accountId: state.config?.name ?? \"default\",\n senderId: `tell/${senderName}`,\n senderDisplay: senderName,\n chatId: payload.threadId ?? `dm:${senderName}`,\n chatType: payload.threadId ? \"thread\" : \"direct\",\n messageId: payload.messageId,\n text: messageContent,\n timestamp: new Date(payload.timestamp || Date.now()),\n replyToId: payload.replyToMessageId,\n metadata: {\n clawtell: {\n autoReplyEligible: payload.autoReplyEligible,\n subject: payload.subject,\n threadId: payload.threadId,\n },\n },\n });\n sendJson(res, 200, { received: true, messageId: payload.messageId });\n } catch (error) {\n console.error(`[clawtell] Failed to route message:`, error);\n sendJson(res, 500, { error: \"Failed to process message\" });\n }\n \n return true;\n}\n\n// Channel Plugin\nconst clawtellChannel = {\n id: \"clawtell\",\n meta: {\n id: \"clawtell\",\n label: \"ClawTell\",\n selectionLabel: \"ClawTell (Agent-to-Agent)\",\n blurb: \"Agent-to-agent messaging via ClawTell network.\",\n aliases: [\"ct\", \"tell\"],\n order: 80,\n },\n capabilities: {\n chatTypes: [\"direct\"],\n media: true,\n reactions: false,\n edit: false,\n unsend: false,\n reply: true,\n },\n config: {\n listAccountIds: (cfg: any) => {\n const cc = cfg.channels?.clawtell;\n if (!cc) return [];\n const ids: string[] = [];\n if (cc.name && cc.apiKey) ids.push(\"default\");\n if (cc.accounts) ids.push(...Object.keys(cc.accounts));\n return ids;\n },\n resolveAccount: (cfg: any, accountId?: string) => {\n const cc = cfg.channels?.clawtell ?? {};\n const isDefault = !accountId || accountId === \"default\";\n const acc = isDefault ? cc : cc.accounts?.[accountId];\n return {\n accountId: accountId ?? \"default\",\n name: acc?.name ?? accountId ?? \"default\",\n enabled: acc?.enabled ?? (isDefault && cc.enabled) ?? false,\n configured: Boolean(acc?.name && acc?.apiKey),\n apiKey: acc?.apiKey ?? null,\n tellName: acc?.name ?? null,\n pollIntervalMs: acc?.pollIntervalMs ?? 30000,\n webhookPath: acc?.webhookPath ?? \"/webhook/clawtell\",\n webhookSecret: acc?.webhookSecret ?? null,\n gatewayUrl: acc?.gatewayUrl ?? null,\n config: acc ?? {},\n };\n },\n defaultAccountId: () => \"default\",\n isConfigured: (account: any) => account.configured,\n describeAccount: (account: any) => ({\n accountId: account.accountId,\n name: account.name,\n enabled: account.enabled,\n configured: account.configured,\n }),\n },\n messaging: {\n normalizeTarget: (target: string) => target?.trim().toLowerCase().replace(/^tell\\//, \"\") || null,\n formatTargetDisplay: ({ target }: any) => `tell/${target?.replace(/^tell\\//, \"\") ?? \"\"}`,\n },\n outbound: {\n deliveryMode: \"direct\",\n textChunkLimit: 50000,\n resolveTarget: ({ to }: any) => {\n if (!to?.trim()) return { ok: false, error: new Error(\"Missing --to\") };\n return { ok: true, to: to.trim().toLowerCase().replace(/^tell\\//, \"\") };\n },\n sendText: async ({ cfg, to, text, accountId, replyToId }: any) => {\n const account = clawtellChannel.config.resolveAccount(cfg, accountId);\n if (!account.apiKey) return { ok: false, error: new Error(\"No API key\") };\n const result = await sendMessage({ apiKey: account.apiKey, to, body: text, replyToId });\n return { channel: \"clawtell\", ...result };\n },\n sendMedia: async ({ cfg, to, caption, mediaUrl, accountId, replyToId }: any) => {\n const account = clawtellChannel.config.resolveAccount(cfg, accountId);\n if (!account.apiKey) return { ok: false, error: new Error(\"No API key\") };\n const body = mediaUrl ? `${caption ?? \"Attachment\"}\\n\\n\uD83D\uDCCE ${mediaUrl}` : caption ?? \"\";\n const result = await sendMessage({ apiKey: account.apiKey, to, body, replyToId });\n return { channel: \"clawtell\", ...result };\n },\n },\n status: {\n probeAccount: async ({ account }: any) => {\n if (!account.apiKey) return { ok: false, error: \"No API key\" };\n return probeApi(account.apiKey);\n },\n },\n gateway: {\n startAccount: async (ctx: any) => {\n const { account, cfg, abortSignal, setStatus, log } = ctx;\n \n setStatus({ accountId: account.accountId, running: true, lastStartAt: new Date().toISOString() });\n log?.info(`[clawtell] Starting (name=${account.tellName})`);\n \n const gatewayUrl = account.gatewayUrl || cfg.gateway?.publicUrl || cfg.gateway?.url;\n if (gatewayUrl && account.apiKey && account.tellName) {\n let secret = account.webhookSecret;\n if (!secret) {\n secret = randomBytes(32).toString(\"hex\");\n state.generatedSecrets.set(account.accountId, secret);\n log?.info(`[clawtell] Generated webhook secret`);\n }\n const webhookUrl = gatewayUrl.replace(/\\/$/, \"\") + account.webhookPath;\n const reg = await registerGateway({\n apiKey: account.apiKey,\n tellName: account.tellName,\n webhookUrl,\n webhookSecret: secret,\n });\n if (reg.ok) {\n log?.info(`[clawtell] Registered gateway: ${webhookUrl}`);\n } else {\n log?.warn(`[clawtell] Gateway registration failed: ${reg.error}`);\n }\n }\n \n const processedIds = new Set<string>();\n const pollIntervalMs = account.pollIntervalMs;\n \n while (!abortSignal.aborted) {\n try {\n const messages = await fetchInbox(account.apiKey);\n for (const msg of messages) {\n if (processedIds.has(msg.id)) continue;\n processedIds.add(msg.id);\n \n if (processedIds.size > 1000) {\n const arr = Array.from(processedIds);\n processedIds.clear();\n arr.slice(-500).forEach(id => processedIds.add(id));\n }\n \n const senderName = msg.from.replace(/^tell\\//, \"\");\n const content = msg.subject ? `**${msg.subject}**\\n\\n${msg.body}` : msg.body;\n \n await state.runtime.routeInboundMessage({\n channel: \"clawtell\",\n accountId: account.accountId,\n senderId: `tell/${senderName}`,\n senderDisplay: senderName,\n chatId: msg.thread_id ?? `dm:${senderName}`,\n chatType: msg.thread_id ? \"thread\" : \"direct\",\n messageId: msg.id,\n text: content,\n timestamp: new Date(msg.created_at),\n replyToId: msg.reply_to_id,\n metadata: { clawtell: { autoReplyEligible: msg.auto_reply_eligible } },\n });\n \n await markAsRead(account.apiKey, msg.id);\n setStatus({ lastInboundAt: new Date().toISOString() });\n }\n } catch (e: any) {\n setStatus({ lastError: e.message });\n }\n \n await new Promise<void>(r => {\n const t = setTimeout(r, pollIntervalMs);\n abortSignal.addEventListener(\"abort\", () => { clearTimeout(t); r(); }, { once: true });\n });\n }\n \n setStatus({ running: false, lastStopAt: new Date().toISOString() });\n },\n },\n};\n\n// Plugin Export\nconst plugin = {\n id: \"clawtell\",\n name: \"ClawTell\",\n description: \"ClawTell channel plugin - agent-to-agent messaging\",\n configSchema: emptyPluginConfigSchema(),\n register(api: ClawdbotPluginApi) {\n state.runtime = api.runtime;\n \n const cfg = api.runtime.getConfig?.();\n if (cfg?.channels?.clawtell) {\n state.config = cfg.channels.clawtell as any;\n }\n \n api.registerChannel({ plugin: clawtellChannel as any });\n api.registerHttpHandler(handleWebhook);\n \n console.log(\"\uD83D\uDC3E ClawTell plugin loaded\");\n },\n};\n\nexport default plugin;\n";
|
|
47
|
+
declare const WEBHOOK_HANDLER_TS = "import express from 'express';\nimport { ClawTell } from '@dennisdamenace/clawtell';\n\nconst app = express();\napp.use(express.json());\n\nconst client = new ClawTell(process.env.CLAWTELL_API_KEY!);\n\n// Webhook endpoint to receive messages from other agents\napp.post('/webhook', async (req, res) => {\n const { from, body, subject, metadata } = req.body;\n \n console.log(`\uD83D\uDCE8 Message from ${from}: ${body}`);\n \n // TODO: Process the incoming message\n // Example: Echo back\n // await client.send(from, `Echo: ${body}`);\n \n res.json({ ok: true });\n});\n\n// Health check\napp.get('/health', (req, res) => {\n res.json({ status: 'ok', agent: 'my-agent' });\n});\n\nconst PORT = process.env.PORT || 3000;\napp.listen(PORT, () => {\n console.log(`\uD83D\uDC3E ClawTell agent listening on port ${PORT}`);\n console.log(` Webhook URL: http://localhost:${PORT}/webhook`);\n});\n";
|
|
48
|
+
declare const WEBHOOK_HANDLER_JS = "const express = require('express');\nconst { ClawTell } = require('@dennisdamenace/clawtell');\n\nconst app = express();\napp.use(express.json());\n\nconst client = new ClawTell(process.env.CLAWTELL_API_KEY);\n\n// Webhook endpoint to receive messages from other agents\napp.post('/webhook', async (req, res) => {\n const { from, body, subject, metadata } = req.body;\n \n console.log(`\uD83D\uDCE8 Message from ${from}: ${body}`);\n \n // TODO: Process the incoming message\n // Example: Echo back\n // await client.send(from, `Echo: ${body}`);\n \n res.json({ ok: true });\n});\n\n// Health check\napp.get('/health', (req, res) => {\n res.json({ status: 'ok', agent: 'my-agent' });\n});\n\nconst PORT = process.env.PORT || 3000;\napp.listen(PORT, () => {\n console.log(`\uD83D\uDC3E ClawTell agent listening on port ${PORT}`);\n console.log(` Webhook URL: http://localhost:${PORT}/webhook`);\n});\n";
|
|
49
|
+
declare const ENV_EXAMPLE = "# ClawTell Configuration\nCLAWTELL_API_KEY=claw_xxx_yyy\n\n# Server\nPORT=3000\n";
|
|
50
|
+
declare function installPlugin(): void;
|
|
51
|
+
|
|
52
|
+
export { ENV_EXAMPLE, INDEX_TS, PLUGIN_JSON, WEBHOOK_HANDLER_JS, WEBHOOK_HANDLER_TS, installPlugin };
|