@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.
- package/README.md +50 -15
- package/dist/index.d.mts +149 -6
- package/dist/index.d.ts +149 -6
- package/dist/index.js +125 -2
- package/dist/index.mjs +125 -2
- package/dist/postinstall.d.mts +51 -0
- package/dist/postinstall.d.ts +51 -0
- package/dist/postinstall.js +585 -61
- package/dist/postinstall.mjs +570 -61
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -104,7 +104,7 @@ var ClawTell = class {
|
|
|
104
104
|
throw lastError || new ClawTellError("Request failed after retries");
|
|
105
105
|
}
|
|
106
106
|
cleanName(name) {
|
|
107
|
-
return name.toLowerCase().replace(/^tell\//, "")
|
|
107
|
+
return name.toLowerCase().replace(/^tell\//, "");
|
|
108
108
|
}
|
|
109
109
|
// ─────────────────────────────────────────────────────────────
|
|
110
110
|
// Messages
|
|
@@ -137,6 +137,45 @@ var ClawTell = class {
|
|
|
137
137
|
async markRead(messageId) {
|
|
138
138
|
return this.request("POST", `/messages/${messageId}/read`);
|
|
139
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
|
+
}
|
|
140
179
|
// ─────────────────────────────────────────────────────────────
|
|
141
180
|
// Profile
|
|
142
181
|
// ─────────────────────────────────────────────────────────────
|
|
@@ -305,8 +344,92 @@ var ClawTell = class {
|
|
|
305
344
|
}
|
|
306
345
|
});
|
|
307
346
|
}
|
|
347
|
+
// ─────────────────────────────────────────────────────────────
|
|
348
|
+
// Delivery Channels
|
|
349
|
+
// ─────────────────────────────────────────────────────────────
|
|
350
|
+
/**
|
|
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
|
+
* });
|
|
378
|
+
*
|
|
379
|
+
* // Add Discord
|
|
380
|
+
* await client.addDeliveryChannel('discord', {
|
|
381
|
+
* webhookUrl: 'https://discord.com/api/webhooks/...'
|
|
382
|
+
* });
|
|
383
|
+
*
|
|
384
|
+
* // Add Slack
|
|
385
|
+
* await client.addDeliveryChannel('slack', {
|
|
386
|
+
* webhookUrl: 'https://hooks.slack.com/services/...'
|
|
387
|
+
* });
|
|
388
|
+
* ```
|
|
389
|
+
*/
|
|
390
|
+
async addDeliveryChannel(platform, credentials, sendTestMessage = true) {
|
|
391
|
+
return this.request("POST", "/delivery-channels", {
|
|
392
|
+
body: {
|
|
393
|
+
platform,
|
|
394
|
+
credentials,
|
|
395
|
+
sendTestMessage
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Remove a delivery channel.
|
|
401
|
+
*
|
|
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
|
|
413
|
+
*
|
|
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
|
+
* ```
|
|
422
|
+
*/
|
|
423
|
+
async discoverTelegramChats(botToken) {
|
|
424
|
+
return this.request("POST", "/delivery-channels/discover", {
|
|
425
|
+
body: {
|
|
426
|
+
platform: "telegram",
|
|
427
|
+
botToken
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
}
|
|
308
431
|
};
|
|
309
|
-
var SDK_VERSION = "0.1
|
|
432
|
+
var SDK_VERSION = "0.2.1";
|
|
310
433
|
var index_default = ClawTell;
|
|
311
434
|
export {
|
|
312
435
|
AuthenticationError,
|
package/dist/postinstall.d.mts
CHANGED
|
@@ -1 +1,52 @@
|
|
|
1
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 };
|
package/dist/postinstall.d.ts
CHANGED
|
@@ -1 +1,52 @@
|
|
|
1
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 };
|