@alfe.ai/openclaw-whatsapp 0.0.6 → 0.0.8

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/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ const require_plugin = require("./plugin.cjs");
2
+ module.exports = require_plugin;
@@ -0,0 +1,2 @@
1
+ import plugin from "./plugin.cjs";
2
+ export { plugin as default };
@@ -0,0 +1,152 @@
1
+ let _sinclair_typebox = require("@sinclair/typebox");
2
+ let _alfe_ai_config = require("@alfe.ai/config");
3
+ //#region src/plugin.ts
4
+ /**
5
+ * @alfe/openclaw-whatsapp — OpenClaw native plugin
6
+ *
7
+ * Registers WhatsApp tools with OpenClaw. Tools call the Twilio service
8
+ * API using the agent's API key for authentication.
9
+ *
10
+ * This plugin provides:
11
+ * - whatsapp_send_message — send a free-form WhatsApp message (requires active session)
12
+ * - whatsapp_send_template — send a template message to initiate conversation
13
+ * - whatsapp_list_templates — list available approved templates
14
+ */
15
+ function ok(data) {
16
+ return {
17
+ content: [{
18
+ type: "text",
19
+ text: JSON.stringify(data)
20
+ }],
21
+ details: data
22
+ };
23
+ }
24
+ function errResult(message) {
25
+ return {
26
+ content: [{
27
+ type: "text",
28
+ text: JSON.stringify({ error: message })
29
+ }],
30
+ details: { error: message }
31
+ };
32
+ }
33
+ function defineTool(def) {
34
+ return {
35
+ name: def.name,
36
+ description: def.description,
37
+ label: def.name,
38
+ parameters: def.parameters,
39
+ execute: async (_toolCallId, params) => {
40
+ try {
41
+ return ok(await def.handler(params));
42
+ } catch (e) {
43
+ return errResult(e.message);
44
+ }
45
+ }
46
+ };
47
+ }
48
+ let apiUrl = "";
49
+ let apiKey = "";
50
+ async function whatsappApi(method, path, body) {
51
+ const url = `${apiUrl}${path}`;
52
+ const headers = {
53
+ "Content-Type": "application/json",
54
+ Authorization: `Bearer ${apiKey}`
55
+ };
56
+ const res = await fetch(url, {
57
+ method,
58
+ headers,
59
+ body: body ? JSON.stringify(body) : void 0
60
+ });
61
+ const json = await res.json();
62
+ if (!res.ok) {
63
+ const errorMsg = typeof json.error === "string" ? json.error : typeof json.message === "string" ? json.message : `WhatsApp service returned ${String(res.status)}`;
64
+ throw new Error(errorMsg);
65
+ }
66
+ if (json.data && typeof json.data === "object") return json.data;
67
+ return json;
68
+ }
69
+ const whatsappTools = [
70
+ defineTool({
71
+ name: "whatsapp_send_message",
72
+ description: "Send a free-form WhatsApp message from your phone number. This only works if the recipient has messaged you within the last 24 hours (active session). If there is no active session, you must use whatsapp_send_template to initiate the conversation first.",
73
+ parameters: _sinclair_typebox.Type.Object({
74
+ to: _sinclair_typebox.Type.String({ description: "Recipient phone number in E.164 format (e.g., +12025551234)" }),
75
+ body: _sinclair_typebox.Type.String({ description: "The message content to send" })
76
+ }),
77
+ handler: async (params) => {
78
+ const { to, body } = params;
79
+ try {
80
+ if (!(await whatsappApi("GET", `/mobile/whatsapp/session?to=${encodeURIComponent(to)}`)).active) throw new Error("No active WhatsApp session with this user. Use whatsapp_send_template to initiate the conversation first, then the user can reply and you can send free-form messages.");
81
+ } catch (e) {
82
+ const msg = e.message;
83
+ if (msg.includes("No active WhatsApp session") || msg.includes("whatsapp_send_template")) throw e;
84
+ }
85
+ return whatsappApi("POST", "/mobile/whatsapp/send", {
86
+ to,
87
+ body
88
+ });
89
+ }
90
+ }),
91
+ defineTool({
92
+ name: "whatsapp_send_template",
93
+ description: "Send a pre-approved WhatsApp template message to initiate a conversation. Use this when you need to message a user who hasn't contacted you recently (no active 24-hour session). First call whatsapp_list_templates to see available templates and their variables, then call this tool with the chosen template. After the user replies to your template, you can use whatsapp_send_message for free-form messages.",
94
+ parameters: _sinclair_typebox.Type.Object({
95
+ to: _sinclair_typebox.Type.String({ description: "Recipient phone number in E.164 format (e.g., +12025551234)" }),
96
+ contentSid: _sinclair_typebox.Type.String({ description: "The template content SID (from whatsapp_list_templates)" }),
97
+ contentVariables: _sinclair_typebox.Type.Object({}, {
98
+ description: "Variables to fill in the template, e.g. {\"1\": \"Kevin\", \"2\": \"Agent Name\"}",
99
+ additionalProperties: _sinclair_typebox.Type.String()
100
+ }),
101
+ bodyPreview: _sinclair_typebox.Type.Optional(_sinclair_typebox.Type.String({ description: "The filled template text for conversation context" }))
102
+ }),
103
+ handler: async (params) => {
104
+ const { to, contentSid, contentVariables, bodyPreview } = params;
105
+ return whatsappApi("POST", "/mobile/whatsapp/send-template", {
106
+ to,
107
+ contentSid,
108
+ contentVariables,
109
+ bodyPreview
110
+ });
111
+ }
112
+ }),
113
+ defineTool({
114
+ name: "whatsapp_list_templates",
115
+ description: "List all approved WhatsApp message templates available for outreach. Each template has a contentSid, name, body with {{1}}, {{2}} placeholders, and variables showing what each placeholder represents. Use this to find the right template before calling whatsapp_send_template.",
116
+ parameters: _sinclair_typebox.Type.Object({}),
117
+ handler: async () => {
118
+ return whatsappApi("GET", "/mobile/whatsapp/templates");
119
+ }
120
+ })
121
+ ];
122
+ const plugin = {
123
+ id: "@alfe.ai/openclaw-whatsapp",
124
+ name: "Alfe WhatsApp Plugin",
125
+ description: "WhatsApp integration — messaging and templates via Twilio",
126
+ version: "0.0.1",
127
+ activate(api) {
128
+ const log = api.logger;
129
+ for (const tool of whatsappTools) api.registerTool(tool);
130
+ log.info(`Registered ${String(whatsappTools.length)} WhatsApp tools: ${whatsappTools.map((t) => t.name).join(", ")}`);
131
+ if (!globalThis.__whatsappPluginActivated) {
132
+ globalThis.__whatsappPluginActivated = true;
133
+ log.info("Alfe WhatsApp plugin activating...");
134
+ try {
135
+ const config = (0, _alfe_ai_config.resolveConfig)();
136
+ apiUrl = config.apiUrl;
137
+ apiKey = config.apiKey;
138
+ } catch (err) {
139
+ log.error(`Failed to resolve config: ${err instanceof Error ? err.message : String(err)}`);
140
+ log.warn("WhatsApp tools will fail — no API config available");
141
+ }
142
+ log.info(`WhatsApp API: ${apiUrl}`);
143
+ }
144
+ log.info("Alfe WhatsApp plugin activated");
145
+ },
146
+ deactivate(api) {
147
+ globalThis.__whatsappPluginActivated = false;
148
+ api.logger.info("Alfe WhatsApp plugin deactivated");
149
+ }
150
+ };
151
+ //#endregion
152
+ module.exports = plugin;
@@ -0,0 +1,42 @@
1
+ import { TSchema } from "@sinclair/typebox";
2
+
3
+ //#region src/plugin.d.ts
4
+
5
+ interface Logger {
6
+ info(msg: string, ...args: unknown[]): void;
7
+ warn(msg: string, ...args: unknown[]): void;
8
+ error(msg: string, ...args: unknown[]): void;
9
+ debug(msg: string, ...args: unknown[]): void;
10
+ }
11
+ interface OpenClawPluginApi {
12
+ logger: Logger;
13
+ registerTool(tool: ToolDef): void;
14
+ registerGatewayMethod(name: string, handler: (...args: unknown[]) => Promise<unknown>): void;
15
+ on(event: string, handler: (...args: unknown[]) => void | Promise<void>, options?: {
16
+ priority?: number;
17
+ }): void;
18
+ }
19
+ interface ToolDef {
20
+ name: string;
21
+ description: string;
22
+ label: string;
23
+ parameters: TSchema;
24
+ execute: (toolCallId: string, params: Record<string, unknown>) => Promise<{
25
+ content: {
26
+ type: "text";
27
+ text: string;
28
+ }[];
29
+ details: unknown;
30
+ }>;
31
+ }
32
+ declare const plugin: {
33
+ id: string;
34
+ name: string;
35
+ description: string;
36
+ version: string;
37
+ activate(api: OpenClawPluginApi): void;
38
+ deactivate(api: OpenClawPluginApi): void;
39
+ };
40
+ //#endregion
41
+ export { plugin as default };
42
+ //# sourceMappingURL=plugin.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.cts","names":[],"sources":["../src/plugin.ts"],"mappings":";;;;UAeU,MAAA,CAgBO;MAIH,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;MAC0B,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;OAA4B,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;EAAO,KAAA,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;AAAA;UAdjE,iBAAA,CAkNT;QAjCe,EAhLN,MAgLM;cA6BE,CAAA,IAAA,EA5MG,OA4MH,CAAA,EAAA,IAAA;EAAiB,qBAAA,CAAA,IAAA,EAAA,MAAA,EAAA,OAAA,EAAA,CAAA,GAAA,IAAA,EAAA,OAAA,EAAA,EAAA,GA3MoC,OA2MpC,CAAA,OAAA,CAAA,CAAA,EAAA,IAAA;4DA1MyB;;;;UAKlD,OAAA;;;;cAII;wCAC0B,4BAA4B;;;;;;;;cA6J9D;;;;;gBAMU;kBA6BE"}
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","names":[],"sources":["../src/plugin.ts"],"mappings":";;;;UAaU,MAAA,CAWyD;EAKzD,IAAA,CAAA,GAAA,EAAO,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;EAAA,IAAA,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;OAIH,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;OAC0B,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;;UAd9B,iBAAA,CAciE;EAkGrE,MAAA,EA/GI,MAsJT;EAAA,YAAA,CAAA,IAAA,EArJoB,OAqJpB,CAAA,EAAA,IAAA;uBAjCe,CAAA,IAAA,EAAA,MAAA,EAAA,OAAA,EAAA,CAAA,GAAA,IAAA,EAAA,OAAA,EAAA,EAAA,GAnHuD,OAmHvD,CAAA,OAAA,CAAA,CAAA,EAAA,IAAA;KA6BE,KAAA,EAAA,MAAA,EAAA,OAAA,EAAA,CAAA,GAAA,IAAA,EAAA,OAAA,EAAA,EAAA,GAAA,IAAA,GA/I0C,OA+I1C,CAAA,IAAA,CAAA,EAAA,QAAA,EAAA;IAAiB,QAAA,CAAA,EAAA,MAAA;;;UA1IzB,OAAA;;;;cAII;wCAC0B,4BAA4B;;;;;;;;cAkG9D;;;;;gBAMU;kBA6BE"}
1
+ {"version":3,"file":"plugin.d.ts","names":[],"sources":["../src/plugin.ts"],"mappings":";;;;UAeU,MAAA,CAgBO;MAIH,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;MAC0B,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;OAA4B,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;EAAO,KAAA,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;AAAA;UAdjE,iBAAA,CAkNT;QAjCe,EAhLN,MAgLM;cA6BE,CAAA,IAAA,EA5MG,OA4MH,CAAA,EAAA,IAAA;EAAiB,qBAAA,CAAA,IAAA,EAAA,MAAA,EAAA,OAAA,EAAA,CAAA,GAAA,IAAA,EAAA,OAAA,EAAA,EAAA,GA3MoC,OA2MpC,CAAA,OAAA,CAAA,CAAA,EAAA,IAAA;4DA1MyB;;;;UAKlD,OAAA;;;;cAII;wCAC0B,4BAA4B;;;;;;;;cA6J9D;;;;;gBAMU;kBA6BE"}
package/dist/plugin.js CHANGED
@@ -8,7 +8,9 @@ import { resolveConfig } from "@alfe.ai/config";
8
8
  * API using the agent's API key for authentication.
9
9
  *
10
10
  * This plugin provides:
11
- * - whatsapp_send_message — send a WhatsApp message from the agent's phone number
11
+ * - whatsapp_send_message — send a free-form WhatsApp message (requires active session)
12
+ * - whatsapp_send_template — send a template message to initiate conversation
13
+ * - whatsapp_list_templates — list available approved templates
12
14
  */
13
15
  function ok(data) {
14
16
  return {
@@ -64,25 +66,63 @@ async function whatsappApi(method, path, body) {
64
66
  if (json.data && typeof json.data === "object") return json.data;
65
67
  return json;
66
68
  }
67
- const whatsappTools = [defineTool({
68
- name: "whatsapp_send_message",
69
- description: "Send a WhatsApp message from your phone number to the specified number. The recipient number must be in E.164 format (e.g., +12025551234). Note: the recipient must have messaged you within the last 24 hours, or the message will fail (WhatsApp session window policy).",
70
- parameters: Type.Object({
71
- to: Type.String({ description: "Recipient phone number in E.164 format (e.g., +12025551234)" }),
72
- body: Type.String({ description: "The message content to send" })
69
+ const whatsappTools = [
70
+ defineTool({
71
+ name: "whatsapp_send_message",
72
+ description: "Send a free-form WhatsApp message from your phone number. This only works if the recipient has messaged you within the last 24 hours (active session). If there is no active session, you must use whatsapp_send_template to initiate the conversation first.",
73
+ parameters: Type.Object({
74
+ to: Type.String({ description: "Recipient phone number in E.164 format (e.g., +12025551234)" }),
75
+ body: Type.String({ description: "The message content to send" })
76
+ }),
77
+ handler: async (params) => {
78
+ const { to, body } = params;
79
+ try {
80
+ if (!(await whatsappApi("GET", `/mobile/whatsapp/session?to=${encodeURIComponent(to)}`)).active) throw new Error("No active WhatsApp session with this user. Use whatsapp_send_template to initiate the conversation first, then the user can reply and you can send free-form messages.");
81
+ } catch (e) {
82
+ const msg = e.message;
83
+ if (msg.includes("No active WhatsApp session") || msg.includes("whatsapp_send_template")) throw e;
84
+ }
85
+ return whatsappApi("POST", "/mobile/whatsapp/send", {
86
+ to,
87
+ body
88
+ });
89
+ }
73
90
  }),
74
- handler: async (params) => {
75
- const { to, body } = params;
76
- return whatsappApi("POST", "/mobile/whatsapp/send", {
77
- to,
78
- body
79
- });
80
- }
81
- })];
91
+ defineTool({
92
+ name: "whatsapp_send_template",
93
+ description: "Send a pre-approved WhatsApp template message to initiate a conversation. Use this when you need to message a user who hasn't contacted you recently (no active 24-hour session). First call whatsapp_list_templates to see available templates and their variables, then call this tool with the chosen template. After the user replies to your template, you can use whatsapp_send_message for free-form messages.",
94
+ parameters: Type.Object({
95
+ to: Type.String({ description: "Recipient phone number in E.164 format (e.g., +12025551234)" }),
96
+ contentSid: Type.String({ description: "The template content SID (from whatsapp_list_templates)" }),
97
+ contentVariables: Type.Object({}, {
98
+ description: "Variables to fill in the template, e.g. {\"1\": \"Kevin\", \"2\": \"Agent Name\"}",
99
+ additionalProperties: Type.String()
100
+ }),
101
+ bodyPreview: Type.Optional(Type.String({ description: "The filled template text for conversation context" }))
102
+ }),
103
+ handler: async (params) => {
104
+ const { to, contentSid, contentVariables, bodyPreview } = params;
105
+ return whatsappApi("POST", "/mobile/whatsapp/send-template", {
106
+ to,
107
+ contentSid,
108
+ contentVariables,
109
+ bodyPreview
110
+ });
111
+ }
112
+ }),
113
+ defineTool({
114
+ name: "whatsapp_list_templates",
115
+ description: "List all approved WhatsApp message templates available for outreach. Each template has a contentSid, name, body with {{1}}, {{2}} placeholders, and variables showing what each placeholder represents. Use this to find the right template before calling whatsapp_send_template.",
116
+ parameters: Type.Object({}),
117
+ handler: async () => {
118
+ return whatsappApi("GET", "/mobile/whatsapp/templates");
119
+ }
120
+ })
121
+ ];
82
122
  const plugin = {
83
123
  id: "@alfe.ai/openclaw-whatsapp",
84
124
  name: "Alfe WhatsApp Plugin",
85
- description: "WhatsApp integration — outbound messaging via Twilio",
125
+ description: "WhatsApp integration — messaging and templates via Twilio",
86
126
  version: "0.0.1",
87
127
  activate(api) {
88
128
  const log = api.logger;
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","names":[],"sources":["../src/plugin.ts"],"sourcesContent":["/**\n * @alfe/openclaw-whatsapp — OpenClaw native plugin\n *\n * Registers WhatsApp tools with OpenClaw. Tools call the Twilio service\n * API using the agent's API key for authentication.\n *\n * This plugin provides:\n * - whatsapp_send_message — send a WhatsApp message from the agent's phone number\n */\n\nimport { Type, type TSchema } from \"@sinclair/typebox\";\nimport { resolveConfig } from \"@alfe.ai/config\";\n\ninterface Logger {\n info(msg: string, ...args: unknown[]): void;\n warn(msg: string, ...args: unknown[]): void;\n error(msg: string, ...args: unknown[]): void;\n debug(msg: string, ...args: unknown[]): void;\n}\n\ninterface OpenClawPluginApi {\n logger: Logger;\n registerTool(tool: ToolDef): void;\n registerGatewayMethod(name: string, handler: (...args: unknown[]) => Promise<unknown>): void;\n on(event: string, handler: (...args: unknown[]) => void | Promise<void>, options?: { priority?: number }): void;\n}\n\n// ── Tool types ───────────────────────────────────────────────\n\ninterface ToolDef {\n name: string;\n description: string;\n label: string;\n parameters: TSchema;\n execute: (toolCallId: string, params: Record<string, unknown>) => Promise<{\n content: { type: \"text\"; text: string }[];\n details: unknown;\n }>;\n}\n\nfunction ok(data: unknown) {\n return { content: [{ type: \"text\" as const, text: JSON.stringify(data) }], details: data };\n}\n\nfunction errResult(message: string) {\n return { content: [{ type: \"text\" as const, text: JSON.stringify({ error: message }) }], details: { error: message } };\n}\n\nfunction defineTool(def: {\n name: string;\n description: string;\n parameters: TSchema;\n handler: (params: Record<string, unknown>) => Promise<unknown>;\n}): ToolDef {\n return {\n name: def.name,\n description: def.description,\n label: def.name,\n parameters: def.parameters,\n execute: async (_toolCallId: string, params: Record<string, unknown>) => {\n try {\n const result = await def.handler(params);\n return ok(result);\n } catch (e) {\n return errResult((e as Error).message);\n }\n },\n };\n}\n\n// ── API client ───────────────────────────────────────────────\n\nlet apiUrl = \"\";\nlet apiKey = \"\";\n\nasync function whatsappApi(\n method: string,\n path: string,\n body?: Record<string, unknown>,\n): Promise<Record<string, unknown>> {\n const url = `${apiUrl}${path}`;\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n };\n\n const res = await fetch(url, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n });\n\n const json = (await res.json()) as Record<string, unknown>;\n if (!res.ok) {\n const errorMsg = typeof json.error === \"string\"\n ? json.error\n : typeof json.message === \"string\"\n ? json.message\n : `WhatsApp service returned ${String(res.status)}`;\n throw new Error(errorMsg);\n }\n\n // unwrap { data: ... } envelope if present\n if (json.data && typeof json.data === \"object\") {\n return json.data as Record<string, unknown>;\n }\n return json;\n}\n\n// ── Tool definitions ─────────────────────────────────────────\n\nconst whatsappTools: ToolDef[] = [\n defineTool({\n name: \"whatsapp_send_message\",\n description:\n \"Send a WhatsApp message from your phone number to the specified number. \" +\n \"The recipient number must be in E.164 format (e.g., +12025551234). \" +\n \"Note: the recipient must have messaged you within the last 24 hours, \" +\n \"or the message will fail (WhatsApp session window policy).\",\n parameters: Type.Object({\n to: Type.String({ description: \"Recipient phone number in E.164 format (e.g., +12025551234)\" }),\n body: Type.String({ description: \"The message content to send\" }),\n }),\n handler: async (params) => {\n const { to, body } = params as { to: string; body: string };\n return whatsappApi(\"POST\", \"/mobile/whatsapp/send\", { to, body });\n },\n }),\n];\n\n// ── Plugin definition ────────────────────────────────────────\n\nconst plugin = {\n id: \"@alfe.ai/openclaw-whatsapp\",\n name: \"Alfe WhatsApp Plugin\",\n description: \"WhatsApp integration — outbound messaging via Twilio\",\n version: \"0.0.1\",\n\n activate(api: OpenClawPluginApi) {\n const log = api.logger;\n\n // Always register tools — OpenClaw may reload the plugin registry per-session\n for (const tool of whatsappTools) {\n api.registerTool(tool);\n }\n log.info(`Registered ${String(whatsappTools.length)} WhatsApp tools: ${whatsappTools.map((t) => t.name).join(\", \")}`);\n\n // Only initialize config once (side effects)\n if (!(globalThis as Record<string, unknown>).__whatsappPluginActivated) {\n (globalThis as Record<string, unknown>).__whatsappPluginActivated = true;\n log.info(\"Alfe WhatsApp plugin activating...\");\n\n try {\n const config = resolveConfig();\n apiUrl = config.apiUrl;\n apiKey = config.apiKey;\n } catch (err) {\n log.error(`Failed to resolve config: ${err instanceof Error ? err.message : String(err)}`);\n log.warn(\"WhatsApp tools will fail — no API config available\");\n }\n\n log.info(`WhatsApp API: ${apiUrl}`);\n }\n\n log.info(\"Alfe WhatsApp plugin activated\");\n },\n\n deactivate(api: OpenClawPluginApi) {\n (globalThis as Record<string, unknown>).__whatsappPluginActivated = false;\n api.logger.info(\"Alfe WhatsApp plugin deactivated\");\n },\n};\n\nexport default plugin;\n"],"mappings":";;;;;;;;;;;;AAwCA,SAAS,GAAG,MAAe;AACzB,QAAO;EAAE,SAAS,CAAC;GAAE,MAAM;GAAiB,MAAM,KAAK,UAAU,KAAK;GAAE,CAAC;EAAE,SAAS;EAAM;;AAG5F,SAAS,UAAU,SAAiB;AAClC,QAAO;EAAE,SAAS,CAAC;GAAE,MAAM;GAAiB,MAAM,KAAK,UAAU,EAAE,OAAO,SAAS,CAAC;GAAE,CAAC;EAAE,SAAS,EAAE,OAAO,SAAS;EAAE;;AAGxH,SAAS,WAAW,KAKR;AACV,QAAO;EACL,MAAM,IAAI;EACV,aAAa,IAAI;EACjB,OAAO,IAAI;EACX,YAAY,IAAI;EAChB,SAAS,OAAO,aAAqB,WAAoC;AACvE,OAAI;AAEF,WAAO,GADQ,MAAM,IAAI,QAAQ,OAAO,CACvB;YACV,GAAG;AACV,WAAO,UAAW,EAAY,QAAQ;;;EAG3C;;AAKH,IAAI,SAAS;AACb,IAAI,SAAS;AAEb,eAAe,YACb,QACA,MACA,MACkC;CAClC,MAAM,MAAM,GAAG,SAAS;CACxB,MAAM,UAAkC;EACtC,gBAAgB;EAChB,eAAe,UAAU;EAC1B;CAED,MAAM,MAAM,MAAM,MAAM,KAAK;EAC3B;EACA;EACA,MAAM,OAAO,KAAK,UAAU,KAAK,GAAG,KAAA;EACrC,CAAC;CAEF,MAAM,OAAQ,MAAM,IAAI,MAAM;AAC9B,KAAI,CAAC,IAAI,IAAI;EACX,MAAM,WAAW,OAAO,KAAK,UAAU,WACnC,KAAK,QACL,OAAO,KAAK,YAAY,WACtB,KAAK,UACL,6BAA6B,OAAO,IAAI,OAAO;AACrD,QAAM,IAAI,MAAM,SAAS;;AAI3B,KAAI,KAAK,QAAQ,OAAO,KAAK,SAAS,SACpC,QAAO,KAAK;AAEd,QAAO;;AAKT,MAAM,gBAA2B,CAC/B,WAAW;CACT,MAAM;CACN,aACE;CAIF,YAAY,KAAK,OAAO;EACtB,IAAI,KAAK,OAAO,EAAE,aAAa,+DAA+D,CAAC;EAC/F,MAAM,KAAK,OAAO,EAAE,aAAa,+BAA+B,CAAC;EAClE,CAAC;CACF,SAAS,OAAO,WAAW;EACzB,MAAM,EAAE,IAAI,SAAS;AACrB,SAAO,YAAY,QAAQ,yBAAyB;GAAE;GAAI;GAAM,CAAC;;CAEpE,CAAC,CACH;AAID,MAAM,SAAS;CACb,IAAI;CACJ,MAAM;CACN,aAAa;CACb,SAAS;CAET,SAAS,KAAwB;EAC/B,MAAM,MAAM,IAAI;AAGhB,OAAK,MAAM,QAAQ,cACjB,KAAI,aAAa,KAAK;AAExB,MAAI,KAAK,cAAc,OAAO,cAAc,OAAO,CAAC,mBAAmB,cAAc,KAAK,MAAM,EAAE,KAAK,CAAC,KAAK,KAAK,GAAG;AAGrH,MAAI,CAAE,WAAuC,2BAA2B;AACrE,cAAuC,4BAA4B;AACpE,OAAI,KAAK,qCAAqC;AAE9C,OAAI;IACF,MAAM,SAAS,eAAe;AAC9B,aAAS,OAAO;AAChB,aAAS,OAAO;YACT,KAAK;AACZ,QAAI,MAAM,6BAA6B,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;AAC1F,QAAI,KAAK,qDAAqD;;AAGhE,OAAI,KAAK,iBAAiB,SAAS;;AAGrC,MAAI,KAAK,iCAAiC;;CAG5C,WAAW,KAAwB;AAChC,aAAuC,4BAA4B;AACpE,MAAI,OAAO,KAAK,mCAAmC;;CAEtD"}
1
+ {"version":3,"file":"plugin.js","names":[],"sources":["../src/plugin.ts"],"sourcesContent":["/**\n * @alfe/openclaw-whatsapp — OpenClaw native plugin\n *\n * Registers WhatsApp tools with OpenClaw. Tools call the Twilio service\n * API using the agent's API key for authentication.\n *\n * This plugin provides:\n * - whatsapp_send_message — send a free-form WhatsApp message (requires active session)\n * - whatsapp_send_template — send a template message to initiate conversation\n * - whatsapp_list_templates — list available approved templates\n */\n\nimport { Type, type TSchema } from \"@sinclair/typebox\";\nimport { resolveConfig } from \"@alfe.ai/config\";\n\ninterface Logger {\n info(msg: string, ...args: unknown[]): void;\n warn(msg: string, ...args: unknown[]): void;\n error(msg: string, ...args: unknown[]): void;\n debug(msg: string, ...args: unknown[]): void;\n}\n\ninterface OpenClawPluginApi {\n logger: Logger;\n registerTool(tool: ToolDef): void;\n registerGatewayMethod(name: string, handler: (...args: unknown[]) => Promise<unknown>): void;\n on(event: string, handler: (...args: unknown[]) => void | Promise<void>, options?: { priority?: number }): void;\n}\n\n// ── Tool types ───────────────────────────────────────────────\n\ninterface ToolDef {\n name: string;\n description: string;\n label: string;\n parameters: TSchema;\n execute: (toolCallId: string, params: Record<string, unknown>) => Promise<{\n content: { type: \"text\"; text: string }[];\n details: unknown;\n }>;\n}\n\nfunction ok(data: unknown) {\n return { content: [{ type: \"text\" as const, text: JSON.stringify(data) }], details: data };\n}\n\nfunction errResult(message: string) {\n return { content: [{ type: \"text\" as const, text: JSON.stringify({ error: message }) }], details: { error: message } };\n}\n\nfunction defineTool(def: {\n name: string;\n description: string;\n parameters: TSchema;\n handler: (params: Record<string, unknown>) => Promise<unknown>;\n}): ToolDef {\n return {\n name: def.name,\n description: def.description,\n label: def.name,\n parameters: def.parameters,\n execute: async (_toolCallId: string, params: Record<string, unknown>) => {\n try {\n const result = await def.handler(params);\n return ok(result);\n } catch (e) {\n return errResult((e as Error).message);\n }\n },\n };\n}\n\n// ── API client ───────────────────────────────────────────────\n\nlet apiUrl = \"\";\nlet apiKey = \"\";\n\nasync function whatsappApi(\n method: string,\n path: string,\n body?: Record<string, unknown>,\n): Promise<Record<string, unknown>> {\n const url = `${apiUrl}${path}`;\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n };\n\n const res = await fetch(url, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n });\n\n const json = (await res.json()) as Record<string, unknown>;\n if (!res.ok) {\n const errorMsg = typeof json.error === \"string\"\n ? json.error\n : typeof json.message === \"string\"\n ? json.message\n : `WhatsApp service returned ${String(res.status)}`;\n throw new Error(errorMsg);\n }\n\n // unwrap { data: ... } envelope if present\n if (json.data && typeof json.data === \"object\") {\n return json.data as Record<string, unknown>;\n }\n return json;\n}\n\n// ── Tool definitions ─────────────────────────────────────────\n\nconst whatsappTools: ToolDef[] = [\n defineTool({\n name: \"whatsapp_send_message\",\n description:\n \"Send a free-form WhatsApp message from your phone number. \" +\n \"This only works if the recipient has messaged you within the last 24 hours (active session). \" +\n \"If there is no active session, you must use whatsapp_send_template to initiate the conversation first.\",\n parameters: Type.Object({\n to: Type.String({ description: \"Recipient phone number in E.164 format (e.g., +12025551234)\" }),\n body: Type.String({ description: \"The message content to send\" }),\n }),\n handler: async (params) => {\n const { to, body } = params as { to: string; body: string };\n\n // Pre-check session to give a clear error before hitting the API\n try {\n const session = await whatsappApi(\"GET\", `/mobile/whatsapp/session?to=${encodeURIComponent(to)}`);\n if (!session.active) {\n throw new Error(\n \"No active WhatsApp session with this user. Use whatsapp_send_template to initiate the conversation first, then the user can reply and you can send free-form messages.\",\n );\n }\n } catch (e) {\n const msg = (e as Error).message;\n if (msg.includes(\"No active WhatsApp session\") || msg.includes(\"whatsapp_send_template\")) {\n throw e;\n }\n // If session check fails for other reasons, try sending anyway — the API will enforce\n }\n\n return whatsappApi(\"POST\", \"/mobile/whatsapp/send\", { to, body });\n },\n }),\n\n defineTool({\n name: \"whatsapp_send_template\",\n description:\n \"Send a pre-approved WhatsApp template message to initiate a conversation. \" +\n \"Use this when you need to message a user who hasn't contacted you recently (no active 24-hour session). \" +\n \"First call whatsapp_list_templates to see available templates and their variables, \" +\n \"then call this tool with the chosen template. \" +\n \"After the user replies to your template, you can use whatsapp_send_message for free-form messages.\",\n parameters: Type.Object({\n to: Type.String({ description: \"Recipient phone number in E.164 format (e.g., +12025551234)\" }),\n contentSid: Type.String({ description: \"The template content SID (from whatsapp_list_templates)\" }),\n contentVariables: Type.Object({}, { description: \"Variables to fill in the template, e.g. {\\\"1\\\": \\\"Kevin\\\", \\\"2\\\": \\\"Agent Name\\\"}\", additionalProperties: Type.String() }),\n bodyPreview: Type.Optional(Type.String({ description: \"The filled template text for conversation context\" })),\n }),\n handler: async (params) => {\n const { to, contentSid, contentVariables, bodyPreview } = params as {\n to: string;\n contentSid: string;\n contentVariables: Record<string, string>;\n bodyPreview?: string;\n };\n return whatsappApi(\"POST\", \"/mobile/whatsapp/send-template\", {\n to,\n contentSid,\n contentVariables,\n bodyPreview,\n });\n },\n }),\n\n defineTool({\n name: \"whatsapp_list_templates\",\n description:\n \"List all approved WhatsApp message templates available for outreach. \" +\n \"Each template has a contentSid, name, body with {{1}}, {{2}} placeholders, \" +\n \"and variables showing what each placeholder represents. \" +\n \"Use this to find the right template before calling whatsapp_send_template.\",\n parameters: Type.Object({}),\n handler: async () => {\n return whatsappApi(\"GET\", \"/mobile/whatsapp/templates\");\n },\n }),\n];\n\n// ── Plugin definition ────────────────────────────────────────\n\nconst plugin = {\n id: \"@alfe.ai/openclaw-whatsapp\",\n name: \"Alfe WhatsApp Plugin\",\n description: \"WhatsApp integration — messaging and templates via Twilio\",\n version: \"0.0.1\",\n\n activate(api: OpenClawPluginApi) {\n const log = api.logger;\n\n // Always register tools — OpenClaw may reload the plugin registry per-session\n for (const tool of whatsappTools) {\n api.registerTool(tool);\n }\n log.info(`Registered ${String(whatsappTools.length)} WhatsApp tools: ${whatsappTools.map((t) => t.name).join(\", \")}`);\n\n // Only initialize config once (side effects)\n if (!(globalThis as Record<string, unknown>).__whatsappPluginActivated) {\n (globalThis as Record<string, unknown>).__whatsappPluginActivated = true;\n log.info(\"Alfe WhatsApp plugin activating...\");\n\n try {\n const config = resolveConfig();\n apiUrl = config.apiUrl;\n apiKey = config.apiKey;\n } catch (err) {\n log.error(`Failed to resolve config: ${err instanceof Error ? err.message : String(err)}`);\n log.warn(\"WhatsApp tools will fail — no API config available\");\n }\n\n log.info(`WhatsApp API: ${apiUrl}`);\n }\n\n log.info(\"Alfe WhatsApp plugin activated\");\n },\n\n deactivate(api: OpenClawPluginApi) {\n (globalThis as Record<string, unknown>).__whatsappPluginActivated = false;\n api.logger.info(\"Alfe WhatsApp plugin deactivated\");\n },\n};\n\nexport default plugin;\n"],"mappings":";;;;;;;;;;;;;;AA0CA,SAAS,GAAG,MAAe;AACzB,QAAO;EAAE,SAAS,CAAC;GAAE,MAAM;GAAiB,MAAM,KAAK,UAAU,KAAK;GAAE,CAAC;EAAE,SAAS;EAAM;;AAG5F,SAAS,UAAU,SAAiB;AAClC,QAAO;EAAE,SAAS,CAAC;GAAE,MAAM;GAAiB,MAAM,KAAK,UAAU,EAAE,OAAO,SAAS,CAAC;GAAE,CAAC;EAAE,SAAS,EAAE,OAAO,SAAS;EAAE;;AAGxH,SAAS,WAAW,KAKR;AACV,QAAO;EACL,MAAM,IAAI;EACV,aAAa,IAAI;EACjB,OAAO,IAAI;EACX,YAAY,IAAI;EAChB,SAAS,OAAO,aAAqB,WAAoC;AACvE,OAAI;AAEF,WAAO,GADQ,MAAM,IAAI,QAAQ,OAAO,CACvB;YACV,GAAG;AACV,WAAO,UAAW,EAAY,QAAQ;;;EAG3C;;AAKH,IAAI,SAAS;AACb,IAAI,SAAS;AAEb,eAAe,YACb,QACA,MACA,MACkC;CAClC,MAAM,MAAM,GAAG,SAAS;CACxB,MAAM,UAAkC;EACtC,gBAAgB;EAChB,eAAe,UAAU;EAC1B;CAED,MAAM,MAAM,MAAM,MAAM,KAAK;EAC3B;EACA;EACA,MAAM,OAAO,KAAK,UAAU,KAAK,GAAG,KAAA;EACrC,CAAC;CAEF,MAAM,OAAQ,MAAM,IAAI,MAAM;AAC9B,KAAI,CAAC,IAAI,IAAI;EACX,MAAM,WAAW,OAAO,KAAK,UAAU,WACnC,KAAK,QACL,OAAO,KAAK,YAAY,WACtB,KAAK,UACL,6BAA6B,OAAO,IAAI,OAAO;AACrD,QAAM,IAAI,MAAM,SAAS;;AAI3B,KAAI,KAAK,QAAQ,OAAO,KAAK,SAAS,SACpC,QAAO,KAAK;AAEd,QAAO;;AAKT,MAAM,gBAA2B;CAC/B,WAAW;EACT,MAAM;EACN,aACE;EAGF,YAAY,KAAK,OAAO;GACtB,IAAI,KAAK,OAAO,EAAE,aAAa,+DAA+D,CAAC;GAC/F,MAAM,KAAK,OAAO,EAAE,aAAa,+BAA+B,CAAC;GAClE,CAAC;EACF,SAAS,OAAO,WAAW;GACzB,MAAM,EAAE,IAAI,SAAS;AAGrB,OAAI;AAEF,QAAI,EADY,MAAM,YAAY,OAAO,+BAA+B,mBAAmB,GAAG,GAAG,EACpF,OACX,OAAM,IAAI,MACR,yKACD;YAEI,GAAG;IACV,MAAM,MAAO,EAAY;AACzB,QAAI,IAAI,SAAS,6BAA6B,IAAI,IAAI,SAAS,yBAAyB,CACtF,OAAM;;AAKV,UAAO,YAAY,QAAQ,yBAAyB;IAAE;IAAI;IAAM,CAAC;;EAEpE,CAAC;CAEF,WAAW;EACT,MAAM;EACN,aACE;EAKF,YAAY,KAAK,OAAO;GACtB,IAAI,KAAK,OAAO,EAAE,aAAa,+DAA+D,CAAC;GAC/F,YAAY,KAAK,OAAO,EAAE,aAAa,2DAA2D,CAAC;GACnG,kBAAkB,KAAK,OAAO,EAAE,EAAE;IAAE,aAAa;IAAqF,sBAAsB,KAAK,QAAQ;IAAE,CAAC;GAC5K,aAAa,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,qDAAqD,CAAC,CAAC;GAC9G,CAAC;EACF,SAAS,OAAO,WAAW;GACzB,MAAM,EAAE,IAAI,YAAY,kBAAkB,gBAAgB;AAM1D,UAAO,YAAY,QAAQ,kCAAkC;IAC3D;IACA;IACA;IACA;IACD,CAAC;;EAEL,CAAC;CAEF,WAAW;EACT,MAAM;EACN,aACE;EAIF,YAAY,KAAK,OAAO,EAAE,CAAC;EAC3B,SAAS,YAAY;AACnB,UAAO,YAAY,OAAO,6BAA6B;;EAE1D,CAAC;CACH;AAID,MAAM,SAAS;CACb,IAAI;CACJ,MAAM;CACN,aAAa;CACb,SAAS;CAET,SAAS,KAAwB;EAC/B,MAAM,MAAM,IAAI;AAGhB,OAAK,MAAM,QAAQ,cACjB,KAAI,aAAa,KAAK;AAExB,MAAI,KAAK,cAAc,OAAO,cAAc,OAAO,CAAC,mBAAmB,cAAc,KAAK,MAAM,EAAE,KAAK,CAAC,KAAK,KAAK,GAAG;AAGrH,MAAI,CAAE,WAAuC,2BAA2B;AACrE,cAAuC,4BAA4B;AACpE,OAAI,KAAK,qCAAqC;AAE9C,OAAI;IACF,MAAM,SAAS,eAAe;AAC9B,aAAS,OAAO;AAChB,aAAS,OAAO;YACT,KAAK;AACZ,QAAI,MAAM,6BAA6B,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;AAC1F,QAAI,KAAK,qDAAqD;;AAGhE,OAAI,KAAK,iBAAiB,SAAS;;AAGrC,MAAI,KAAK,iCAAiC;;CAG5C,WAAW,KAAwB;AAChC,aAAuC,4BAA4B;AACpE,MAAI,OAAO,KAAK,mCAAmC;;CAEtD"}
package/package.json CHANGED
@@ -1,18 +1,20 @@
1
1
  {
2
2
  "name": "@alfe.ai/openclaw-whatsapp",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "description": "OpenClaw WhatsApp plugin for Alfe — outbound WhatsApp messaging via Twilio",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
8
8
  "exports": {
9
9
  ".": {
10
- "import": "./dist/index.js",
11
- "types": "./dist/index.d.ts"
10
+ "types": "./dist/index.d.ts",
11
+ "require": "./dist/index.cjs",
12
+ "import": "./dist/index.js"
12
13
  },
13
14
  "./plugin": {
14
- "import": "./dist/plugin.js",
15
- "types": "./dist/plugin.d.ts"
15
+ "types": "./dist/plugin.d.ts",
16
+ "require": "./dist/plugin.cjs",
17
+ "import": "./dist/plugin.js"
16
18
  }
17
19
  },
18
20
  "openclaw": {
@@ -26,7 +28,7 @@
26
28
  ],
27
29
  "dependencies": {
28
30
  "@sinclair/typebox": "^0.34.48",
29
- "@alfe.ai/config": "0.0.6"
31
+ "@alfe.ai/config": "0.0.7"
30
32
  },
31
33
  "scripts": {
32
34
  "build": "tsdown",