@gamalan/pi-gateway 1.0.0

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.
Files changed (75) hide show
  1. package/README.md +366 -0
  2. package/config/config.default.json +51 -0
  3. package/dist/adapters/base.d.ts +85 -0
  4. package/dist/adapters/base.js +34 -0
  5. package/dist/adapters/base.js.map +1 -0
  6. package/dist/adapters/discord.d.ts +53 -0
  7. package/dist/adapters/discord.js +224 -0
  8. package/dist/adapters/discord.js.map +1 -0
  9. package/dist/adapters/index.d.ts +13 -0
  10. package/dist/adapters/index.js +8 -0
  11. package/dist/adapters/index.js.map +1 -0
  12. package/dist/adapters/slack.d.ts +49 -0
  13. package/dist/adapters/slack.js +231 -0
  14. package/dist/adapters/slack.js.map +1 -0
  15. package/dist/adapters/telegram.d.ts +64 -0
  16. package/dist/adapters/telegram.js +274 -0
  17. package/dist/adapters/telegram.js.map +1 -0
  18. package/dist/adapters/twitch.d.ts +75 -0
  19. package/dist/adapters/twitch.js +222 -0
  20. package/dist/adapters/twitch.js.map +1 -0
  21. package/dist/adapters/websocket.d.ts +30 -0
  22. package/dist/adapters/websocket.js +132 -0
  23. package/dist/adapters/websocket.js.map +1 -0
  24. package/dist/adapters/whatsapp.d.ts +49 -0
  25. package/dist/adapters/whatsapp.js +251 -0
  26. package/dist/adapters/whatsapp.js.map +1 -0
  27. package/dist/background/index.d.ts +1 -0
  28. package/dist/background/index.js +2 -0
  29. package/dist/background/index.js.map +1 -0
  30. package/dist/background/manager.d.ts +70 -0
  31. package/dist/background/manager.js +291 -0
  32. package/dist/background/manager.js.map +1 -0
  33. package/dist/index.d.ts +18 -0
  34. package/dist/index.js +1275 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/logger.d.ts +12 -0
  37. package/dist/logger.js +39 -0
  38. package/dist/logger.js.map +1 -0
  39. package/dist/paths.d.ts +14 -0
  40. package/dist/paths.js +32 -0
  41. package/dist/paths.js.map +1 -0
  42. package/dist/security/auth.d.ts +91 -0
  43. package/dist/security/auth.js +316 -0
  44. package/dist/security/auth.js.map +1 -0
  45. package/dist/security/index.d.ts +1 -0
  46. package/dist/security/index.js +2 -0
  47. package/dist/security/index.js.map +1 -0
  48. package/dist/security/tool-policy.d.ts +66 -0
  49. package/dist/security/tool-policy.js +467 -0
  50. package/dist/security/tool-policy.js.map +1 -0
  51. package/dist/sessions/index.d.ts +1 -0
  52. package/dist/sessions/index.js +2 -0
  53. package/dist/sessions/index.js.map +1 -0
  54. package/dist/sessions/store.d.ts +64 -0
  55. package/dist/sessions/store.js +247 -0
  56. package/dist/sessions/store.js.map +1 -0
  57. package/package.json +57 -0
  58. package/src/adapters/base.ts +124 -0
  59. package/src/adapters/discord.ts +270 -0
  60. package/src/adapters/index.ts +13 -0
  61. package/src/adapters/slack.ts +313 -0
  62. package/src/adapters/telegram.ts +394 -0
  63. package/src/adapters/twitch.ts +316 -0
  64. package/src/adapters/websocket.ts +158 -0
  65. package/src/adapters/whatsapp.ts +296 -0
  66. package/src/background/index.ts +1 -0
  67. package/src/background/manager.ts +395 -0
  68. package/src/index.ts +1665 -0
  69. package/src/logger.ts +43 -0
  70. package/src/paths.ts +40 -0
  71. package/src/security/auth.ts +458 -0
  72. package/src/security/index.ts +1 -0
  73. package/src/security/tool-policy.ts +568 -0
  74. package/src/sessions/index.ts +1 -0
  75. package/src/sessions/store.ts +360 -0
@@ -0,0 +1,158 @@
1
+ /**
2
+ * WebSocket Adapter - For web clients and other WebSocket-based platforms
3
+ */
4
+
5
+ import { BaseAdapter, type PlatformMessage, type PlatformConfig } from "./base.js";
6
+ import { logger } from "../logger.js";
7
+
8
+ export interface WebSocketConfig extends PlatformConfig {
9
+ platform: "websocket";
10
+ clientId: string;
11
+ }
12
+
13
+ export class WebSocketAdapter extends BaseAdapter {
14
+ readonly platform = "websocket" as const;
15
+ config: WebSocketConfig;
16
+ private client: WebSocket | null = null;
17
+ private reconnectAttempts = 0;
18
+ private maxReconnectAttempts = 5;
19
+ private reconnectDelay = 1000;
20
+
21
+ constructor(config: WebSocketConfig) {
22
+ super();
23
+ this.config = config;
24
+ }
25
+
26
+ async initialize(): Promise<void> {
27
+ logger.info(`[WebSocket] Adapter initialized for client ${this.config.clientId}`);
28
+ }
29
+
30
+ async start(callbacks): Promise<void> {
31
+ await super.start(callbacks);
32
+ // For server-side: this would be handled by the main gateway
33
+ // This adapter is more for client-side connections
34
+ }
35
+
36
+ async connect(url: string, token?: string): Promise<void> {
37
+ return new Promise((resolve, reject) => {
38
+ const headers: Record<string, string> = {};
39
+ if (token) {
40
+ headers["Authorization"] = `Bearer ${token}`;
41
+ }
42
+
43
+ this.client = new WebSocket(url, { headers });
44
+
45
+ this.client.onopen = () => {
46
+ logger.info(`[WebSocket] Connected to ${url}`);
47
+ this.reconnectAttempts = 0;
48
+ resolve();
49
+ };
50
+
51
+ this.client.onmessage = (event) => {
52
+ try {
53
+ const data = JSON.parse(event.data);
54
+
55
+ if (data.type === "message") {
56
+ const message: PlatformMessage = {
57
+ id: this.generateMessageId(),
58
+ platform: this.platform,
59
+ channelId: this.config.clientId,
60
+ userId: this.config.clientId,
61
+ content: data.content,
62
+ timestamp: Date.now(),
63
+ metadata: data.metadata,
64
+ };
65
+ this.emitMessage(message);
66
+ }
67
+ } catch (err) {
68
+ logger.error("[WebSocket] Failed to parse message:", err);
69
+ }
70
+ };
71
+
72
+ this.client.onclose = () => {
73
+ logger.info("[WebSocket] Connection closed");
74
+ this.callbacks?.onDisconnect?.();
75
+ this.attemptReconnect(url, token);
76
+ };
77
+
78
+ this.client.onerror = (err) => {
79
+ logger.error("[WebSocket] Error:", err);
80
+ reject(err);
81
+ };
82
+ });
83
+ }
84
+
85
+ private attemptReconnect(url: string, token?: string): void {
86
+ if (this.reconnectAttempts < this.maxReconnectAttempts) {
87
+ this.reconnectAttempts++;
88
+ const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
89
+ logger.info(`[WebSocket] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
90
+ setTimeout(() => this.connect(url, token), delay);
91
+ }
92
+ }
93
+
94
+ async sendMessage(channelId: string, content: string): Promise<string> {
95
+ if (!this.client || this.client.readyState !== WebSocket.OPEN) {
96
+ throw new Error("WebSocket not connected");
97
+ }
98
+
99
+ const id = this.generateMessageId();
100
+ this.client.send(JSON.stringify({
101
+ type: "message",
102
+ id,
103
+ content,
104
+ channelId,
105
+ }));
106
+
107
+ return id;
108
+ }
109
+
110
+ async editMessage(channelId: string, messageId: string, content: string): Promise<void> {
111
+ if (!this.client || this.client.readyState !== WebSocket.OPEN) {
112
+ throw new Error("WebSocket not connected");
113
+ }
114
+
115
+ this.client.send(JSON.stringify({
116
+ type: "edit",
117
+ id: messageId,
118
+ content,
119
+ channelId,
120
+ }));
121
+ }
122
+
123
+ async deleteMessage(channelId: string, messageId: string): Promise<void> {
124
+ if (!this.client || this.client.readyState !== WebSocket.OPEN) {
125
+ throw new Error("WebSocket not connected");
126
+ }
127
+
128
+ this.client.send(JSON.stringify({
129
+ type: "delete",
130
+ id: messageId,
131
+ channelId,
132
+ }));
133
+ }
134
+
135
+ async setTyping(channelId: string, isTyping: boolean): Promise<void> {
136
+ if (!this.client || this.client.readyState !== WebSocket.OPEN) return;
137
+
138
+ this.client.send(JSON.stringify({
139
+ type: "typing",
140
+ channelId,
141
+ isTyping,
142
+ }));
143
+ }
144
+
145
+ async getStatus(): Promise<{ connected: boolean; latency?: number }> {
146
+ return {
147
+ connected: this.client?.readyState === WebSocket.OPEN,
148
+ };
149
+ }
150
+
151
+ async stop(): Promise<void> {
152
+ if (this.client) {
153
+ this.client.close();
154
+ this.client = null;
155
+ }
156
+ await super.stop();
157
+ }
158
+ }
@@ -0,0 +1,296 @@
1
+ /**
2
+ * WhatsApp Adapter - Hermes-style WhatsApp platform adapter
3
+ *
4
+ * Features:
5
+ * - WhatsApp Web protocol via Baileys
6
+ * - QR code authentication
7
+ * - Contact and group management
8
+ * - Media messaging
9
+ */
10
+
11
+ import { BaseAdapter, type PlatformMessage, type PlatformConfig } from "./base.js";
12
+ import { logger } from "../logger.js";
13
+
14
+ export interface WhatsAppConfig extends PlatformConfig {
15
+ platform: "whatsapp";
16
+ sessionPath?: string;
17
+ printQr?: boolean; // Print QR to console
18
+ maxMessageLength?: number;
19
+ }
20
+
21
+ interface WhatsAppContact {
22
+ id: string;
23
+ name?: string;
24
+ isGroup: boolean;
25
+ }
26
+
27
+ export class WhatsAppAdapter extends BaseAdapter {
28
+ readonly platform = "whatsapp" as const;
29
+ config: WhatsAppConfig;
30
+
31
+ private sock: any = null;
32
+ private connected = false;
33
+ private qrCode: string | null = null;
34
+
35
+ constructor(config: WhatsAppConfig) {
36
+ super();
37
+ this.config = {
38
+ enabled: true,
39
+ platform: "whatsapp",
40
+ sessionPath: "./whatsapp-session",
41
+ printQr: true,
42
+ maxMessageLength: 4096,
43
+ ...config,
44
+ };
45
+ }
46
+
47
+ async initialize(): Promise<void> {
48
+ try {
49
+ const baileys = await import("@whiskeysockets/baileys");
50
+
51
+ const { state, saveCreds } = await baileys.useMultiFileAuthState(this.config.sessionPath || "./whatsapp-session");
52
+
53
+ this.sock = baileys.makeWASocket({
54
+ auth: state,
55
+ printQRInTerminal: this.config.printQr,
56
+ defaultQueryTimeoutMs: 60 * 1000,
57
+ });
58
+
59
+ // Handle QR code
60
+ this.sock.ev.on("qr", (qr: string) => {
61
+ this.qrCode = qr;
62
+ logger.info("[WhatsApp] QR Code received - scan with WhatsApp app");
63
+ logger.info(qr);
64
+ });
65
+
66
+ // Handle connection update
67
+ this.sock.ev.on("connection.update", ({ qr, connection }: any) => {
68
+ if (qr) {
69
+ this.qrCode = qr;
70
+ }
71
+ if (connection === "open") {
72
+ this.connected = true;
73
+ this.qrCode = null;
74
+ logger.info("[WhatsApp] Connected!");
75
+ }
76
+ if (connection === "close") {
77
+ this.connected = false;
78
+ logger.info("[WhatsApp] Disconnected");
79
+ }
80
+ });
81
+
82
+ // Handle credentials update
83
+ this.sock.ev.on("creds.update", saveCreds);
84
+
85
+ // Handle messages
86
+ this.sock.ev.on("messages.upsert", ({ messages }: any) => {
87
+ this.handleMessages(messages);
88
+ });
89
+
90
+ logger.info("[WhatsApp] Initializing...");
91
+ } catch (err) {
92
+ logger.error("[WhatsApp] Failed to initialize:", err);
93
+ throw err;
94
+ }
95
+ }
96
+
97
+ private handleMessages(messages: any[]): void {
98
+ for (const msg of messages) {
99
+ // Skip messages sent by us
100
+ if (msg.key.fromMe) continue;
101
+
102
+ const jid = msg.key.remoteJid;
103
+ const isGroup = jid?.endsWith("@g.us");
104
+
105
+ // Get message content
106
+ const content =
107
+ msg.message?.conversation ||
108
+ msg.message?.extendedTextMessage?.text ||
109
+ msg.message?.imageMessage?.caption ||
110
+ "";
111
+
112
+ if (!content) continue;
113
+
114
+ const message: PlatformMessage = {
115
+ id: msg.key.id || this.generateMessageId(),
116
+ platform: "whatsapp",
117
+ channelId: jid,
118
+ userId: msg.key.participant || jid,
119
+ content,
120
+ timestamp: msg.messageTimestamp ? msg.messageTimestamp * 1000 : Date.now(),
121
+ metadata: {
122
+ isGroup,
123
+ messageType: msg.message ? Object.keys(msg.message)[0] : "unknown",
124
+ pushName: msg.pushName,
125
+ },
126
+ };
127
+
128
+ this.emitMessage(message);
129
+ }
130
+ }
131
+
132
+ async start(callbacks): Promise<void> {
133
+ await super.start(callbacks);
134
+
135
+ // Wait for connection
136
+ let attempts = 0;
137
+ while (!this.connected && attempts < 30) {
138
+ await this.sleep(1000);
139
+ attempts++;
140
+ }
141
+
142
+ if (!this.connected) {
143
+ logger.warn("[WhatsApp] Not yet connected - waiting for QR scan");
144
+ }
145
+ }
146
+
147
+ private sleep(ms: number): Promise<void> {
148
+ return new Promise(resolve => setTimeout(resolve, ms));
149
+ }
150
+
151
+ async stop(): Promise<void> {
152
+ if (this.sock) {
153
+ await this.sock.logout();
154
+ this.sock = null;
155
+ }
156
+ this.connected = false;
157
+ await super.stop();
158
+ }
159
+
160
+ async sendMessage(channelId: string, content: string): Promise<string> {
161
+ if (!this.sock || !this.connected) {
162
+ throw new Error("WhatsApp not connected");
163
+ }
164
+
165
+ // Truncate if too long
166
+ const text = content.length > (this.config.maxMessageLength || 4096)
167
+ ? content.slice(0, this.config.maxMessageLength - 3) + "..."
168
+ : content;
169
+
170
+ try {
171
+ const result = await this.sock.sendMessage(channelId, { text });
172
+ return result?.key?.id || this.generateMessageId();
173
+ } catch (err) {
174
+ logger.error("[WhatsApp] Send error:", err);
175
+ throw err;
176
+ }
177
+ }
178
+
179
+ async sendImage(channelId: string, imageUrl: string, caption?: string): Promise<string> {
180
+ if (!this.sock || !this.connected) {
181
+ throw new Error("WhatsApp not connected");
182
+ }
183
+
184
+ try {
185
+ const result = await this.sock.sendMessage(channelId, {
186
+ image: { url: imageUrl },
187
+ caption,
188
+ });
189
+ return result?.key?.id || this.generateMessageId();
190
+ } catch (err) {
191
+ logger.error("[WhatsApp] Send image error:", err);
192
+ throw err;
193
+ }
194
+ }
195
+
196
+ async sendReaction(channelId: string, messageId: string, emoji: string): Promise<void> {
197
+ if (!this.sock || !this.connected) {
198
+ throw new Error("WhatsApp not connected");
199
+ }
200
+
201
+ try {
202
+ await this.sock.sendMessage(channelId, {
203
+ react: { text: emoji, key: { remoteJid: channelId, id: messageId } },
204
+ });
205
+ } catch (err) {
206
+ logger.error("[WhatsApp] Reaction error:", err);
207
+ }
208
+ }
209
+
210
+ async reply(channelId: string, content: string, messageId: string): Promise<string> {
211
+ if (!this.sock || !this.connected) {
212
+ throw new Error("WhatsApp not connected");
213
+ }
214
+
215
+ try {
216
+ const result = await this.sock.sendMessage(channelId, {
217
+ text: content,
218
+ contextInfo: {
219
+ stanzaId: messageId,
220
+ remoteJid: channelId,
221
+ },
222
+ });
223
+ return result?.key?.id || this.generateMessageId();
224
+ } catch (err) {
225
+ logger.error("[WhatsApp] Reply error:", err);
226
+ throw err;
227
+ }
228
+ }
229
+
230
+ async editMessage(channelId: string, messageId: string, content: string): Promise<void> {
231
+ if (!this.sock || !this.connected) {
232
+ throw new Error("WhatsApp not connected");
233
+ }
234
+
235
+ try {
236
+ await this.sock.relayMessage(channelId, {
237
+ protocolMessage: {
238
+ type: 6, // MESSAGE_EDIT
239
+ key: { remoteJid: channelId, id: messageId },
240
+ editedMessage: { conversation: [{ text: content }] },
241
+ },
242
+ }, {});
243
+ } catch (err) {
244
+ logger.error("[WhatsApp] Edit error:", err);
245
+ }
246
+ }
247
+
248
+ async deleteMessage(channelId: string, messageId: string): Promise<void> {
249
+ if (!this.sock || !this.connected) {
250
+ throw new Error("WhatsApp not connected");
251
+ }
252
+
253
+ try {
254
+ await this.sock.sendMessage(channelId, {
255
+ delete: { remoteJid: channelId, id: messageId },
256
+ });
257
+ } catch (err) {
258
+ logger.error("[WhatsApp] Delete error:", err);
259
+ }
260
+ }
261
+
262
+ async setTyping(channelId: string, isTyping: boolean): Promise<void> {
263
+ if (!this.sock || !this.connected) return;
264
+
265
+ try {
266
+ await this.sock.sendPresenceUpdate(isTyping ? "composing" : "available", channelId);
267
+ } catch (err) {
268
+ // Ignore presence errors
269
+ }
270
+ }
271
+
272
+ async getStatus(): Promise<{ connected: boolean; latency?: number }> {
273
+ return { connected: this.connected };
274
+ }
275
+
276
+ async getContacts(): Promise<WhatsAppContact[]> {
277
+ if (!this.sock?.store?.contacts) {
278
+ return [];
279
+ }
280
+
281
+ return Object.entries(this.sock.store.contacts).map(([id, contact]: [string, any]) => ({
282
+ id,
283
+ name: contact?.name || contact?.notify || id.split("@")[0],
284
+ isGroup: id.endsWith("@g.us"),
285
+ }));
286
+ }
287
+
288
+ async getContact(jid: string): Promise<WhatsAppContact | null> {
289
+ const contacts = await this.getContacts();
290
+ return contacts.find(c => c.id === jid) || null;
291
+ }
292
+
293
+ getQrCode(): string | null {
294
+ return this.qrCode;
295
+ }
296
+ }
@@ -0,0 +1 @@
1
+ export { initBackgroundTasks, startBackgroundTask, getPendingResultsForSession, markTaskDelivered, listTasks, cancelTask, type BackgroundTask, type BackgroundStatus } from "./manager.js";