pinto-app-openclaw 1.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,636 @@
1
+ # pinto-app-openclaw
2
+
3
+ [ภาษาไทย](#ภาษาไทย) | [English](#english)
4
+
5
+ OpenClaw channel plugin for Pinto Chat. It receives webhook events from Pinto, forwards them to an OpenClaw agent, and sends the final reply back to Pinto.
6
+
7
+ ## ภาษาไทย
8
+
9
+ ### ภาพรวม
10
+
11
+ `pinto-app-openclaw` คือ OpenClaw channel plugin สำหรับเชื่อมต่อ Pinto Chat กับ OpenClaw agent
12
+
13
+ flow การทำงาน:
14
+
15
+ 1. ผู้ใช้ส่งข้อความใน Pinto
16
+ 2. Pinto API เรียก webhook ของ OpenClaw ที่ `/plugins/pinto/webhook`
17
+ 3. ปลั๊กอินส่งข้อความเข้า agent ใน OpenClaw
18
+ 4. เมื่อ agent ตอบกลับ ปลั๊กอินจะส่งข้อความกลับไปที่ Pinto ผ่าน `POST /v1/bots/webhook/receive`
19
+
20
+ ความสามารถหลัก:
21
+
22
+ - รองรับ direct chat
23
+ - รองรับข้อความตอบกลับพร้อม `media_url`
24
+ - รองรับ `Webhook Secret` ผ่าน header `X-Pinto-Secret`
25
+ - ตั้งค่าได้ผ่านหน้า `Channels > Pinto Chat` ใน OpenClaw
26
+
27
+ ### สิ่งที่ต้องมี
28
+
29
+ - OpenClaw ที่รันได้แล้ว
30
+ - Node.js 20+ และ npm
31
+ - Pinto bot ที่สร้างไว้แล้ว
32
+ - Bot UUID จริงของ Pinto
33
+ - Pinto API base URL ที่ถูกต้อง เช่น `https://api-dev.pinto-app.com`
34
+ - URL ที่ Pinto เข้าถึง OpenClaw ได้จริง เช่น domain, reverse proxy, Tailscale, tunnel
35
+
36
+ ### การติดตั้ง
37
+
38
+ #### ติดตั้งผ่าน OpenClaw package name
39
+
40
+ วิธีที่แนะนำ:
41
+
42
+ ```bash
43
+ openclaw plugins install pinto-app-openclaw
44
+ ```
45
+
46
+ #### ติดตั้งจาก source ในเครื่อง
47
+
48
+ ```bash
49
+ git clone https://github.com/fakduai-logistics-and-digital-platform/pinto-openclaw-gateway.git
50
+ cd pinto-openclaw-gateway
51
+ npm install
52
+ npm run build
53
+ openclaw plugins install .
54
+ ```
55
+
56
+ #### ติดตั้งแบบคัดลอกไฟล์เอง
57
+
58
+ ถ้าคุณ deploy แบบ manual ให้คัดลอกไฟล์เหล่านี้ไปยัง OpenClaw extensions directory:
59
+
60
+ - `dist/`
61
+ - `openclaw.plugin.json`
62
+ - `package.json`
63
+ - `README.md`
64
+
65
+ ตัวอย่างปลายทาง:
66
+
67
+ ```bash
68
+ ~/.openclaw/extensions/pinto-app-openclaw
69
+ ```
70
+
71
+ ### การตั้งค่าใน OpenClaw
72
+
73
+ สามารถตั้งค่าได้สองแบบ:
74
+
75
+ - ผ่าน OpenClaw UI ที่ `Channels > Pinto Chat`
76
+ - ผ่าน config file ของ OpenClaw
77
+
78
+ ค่าที่ต้องกรอก:
79
+
80
+ - `Api Url`
81
+ - Pinto API base URL
82
+ - ใส่ได้ทั้งแบบมี `/` ท้ายหรือไม่มี `/` ท้าย
83
+ - ตัวอย่าง `https://api-dev.pinto-app.com`
84
+ - `Bot Id`
85
+ - ต้องเป็น Bot UUID จริงของ Pinto
86
+ - ไม่ใช่ `bot_id` แบบ slug
87
+ - `Enabled`
88
+ - เปิดหรือปิด channel
89
+ - `Webhook Secret`
90
+ - secret ที่ใช้ร่วมกับ header `X-Pinto-Secret`
91
+ - ถ้าไม่ได้ตั้งค่าไว้ ระบบจะไม่บังคับตรวจ secret ขาเข้า
92
+
93
+ ตัวอย่าง config:
94
+
95
+ ```json
96
+ {
97
+ "channels": {
98
+ "pinto": {
99
+ "enabled": true,
100
+ "apiUrl": "https://api-dev.pinto-app.com",
101
+ "botId": "20387880-7934-40c3-b7d4-9fa6557697cf",
102
+ "webhookSecret": "pinto-oc-9f3a1b7c5d2e8k4m"
103
+ }
104
+ }
105
+ }
106
+ ```
107
+
108
+ หมายเหตุ:
109
+
110
+ - channel id ของปลั๊กอินคือ `pinto`
111
+ - package name คือ `pinto-app-openclaw`
112
+
113
+ ### การตั้งค่าฝั่ง Pinto
114
+
115
+ Pinto bot ต้องมีข้อมูลต่อไปนี้:
116
+
117
+ - `webhook_url`
118
+ - URL ที่ Pinto จะยิงเข้ามา
119
+ - ตัวอย่าง:
120
+ - `https://your-host.example.com/plugins/pinto/webhook`
121
+ - Bot UUID
122
+ - ใช้ค่า `_id` ของ bot เป็นค่า `Bot Id` ใน OpenClaw
123
+ - ถ้ามีการเปิดใช้ secret
124
+ - Pinto ต้องส่ง header `X-Pinto-Secret` เข้ามา
125
+ - ค่า secret ต้องตรงกับ `Webhook Secret` ใน OpenClaw
126
+
127
+ ### Webhook Secret
128
+
129
+ ปลั๊กอินรองรับ `Webhook Secret` ทั้ง inbound และ outbound
130
+
131
+ #### Inbound: Pinto -> OpenClaw
132
+
133
+ ถ้ามีการตั้ง `Webhook Secret` ใน OpenClaw:
134
+
135
+ - Pinto ต้องส่ง header:
136
+
137
+ ```http
138
+ X-Pinto-Secret: <your-secret>
139
+ ```
140
+
141
+ - ถ้าค่าไม่ตรง ปลั๊กอินจะตอบ `401 Unauthorized`
142
+
143
+ ถ้าไม่ได้ตั้ง `Webhook Secret`:
144
+
145
+ - ปลั๊กอินจะไม่บังคับตรวจ secret ขาเข้า
146
+
147
+ #### Outbound: OpenClaw -> Pinto
148
+
149
+ เมื่อปลั๊กอินส่งข้อความกลับไป Pinto ที่:
150
+
151
+ ```http
152
+ POST <apiUrl>/v1/bots/webhook/receive
153
+ ```
154
+
155
+ ปลั๊กอินจะส่ง header นี้ให้อัตโนมัติ ถ้ามีการตั้ง `Webhook Secret`:
156
+
157
+ ```http
158
+ X-Pinto-Secret: <your-secret>
159
+ ```
160
+
161
+ ### Endpoint ที่เกี่ยวข้อง
162
+
163
+ #### Inbound webhook
164
+
165
+ Pinto จะเรียก:
166
+
167
+ ```http
168
+ POST /plugins/pinto/webhook
169
+ ```
170
+
171
+ ตัวอย่าง request body:
172
+
173
+ ```json
174
+ {
175
+ "bot_id": "20387880-7934-40c3-b7d4-9fa6557697cf",
176
+ "chat_id": "5f315d4e-cf22-4054-bbb0-2fe074bd3892",
177
+ "message": "hello",
178
+ "user_id": "a7c3fe36-cf41-42b5-a290-ca98e6129fac",
179
+ "username": "demo-user"
180
+ }
181
+ ```
182
+
183
+ #### Outbound reply
184
+
185
+ ปลั๊กอินจะส่ง:
186
+
187
+ ```http
188
+ POST <apiUrl>/v1/bots/webhook/receive
189
+ ```
190
+
191
+ ตัวอย่าง body:
192
+
193
+ ```json
194
+ {
195
+ "bot_id": "20387880-7934-40c3-b7d4-9fa6557697cf",
196
+ "chat_id": "5f315d4e-cf22-4054-bbb0-2fe074bd3892",
197
+ "reply_message": "สวัสดีจาก OpenClaw"
198
+ }
199
+ ```
200
+
201
+ ตัวอย่างเมื่อมี media:
202
+
203
+ ```json
204
+ {
205
+ "bot_id": "20387880-7934-40c3-b7d4-9fa6557697cf",
206
+ "chat_id": "5f315d4e-cf22-4054-bbb0-2fe074bd3892",
207
+ "reply_message": "ดูรูปนี้",
208
+ "media_url": "https://example.com/image.png"
209
+ }
210
+ ```
211
+
212
+ ### วิธีเชื่อมต่อแบบแนะนำ
213
+
214
+ 1. ติดตั้งปลั๊กอินด้วย:
215
+
216
+ ```bash
217
+ openclaw plugins install pinto-app-openclaw
218
+ ```
219
+
220
+ 2. รีสตาร์ต OpenClaw หรือ reload plugins
221
+ 3. เปิดหน้า `Channels > Pinto Chat`
222
+ 4. กรอก `Api Url`, `Bot Id`, `Webhook Secret`
223
+ 5. กด `Save`
224
+ 6. ตั้งค่า `webhook_url` ของ Pinto bot ให้ชี้มาที่ `/plugins/pinto/webhook`
225
+ 7. ทดสอบ webhook ก่อนด้วย `curl`
226
+ 8. ส่งข้อความจริงจาก Pinto
227
+ 9. ตรวจว่า OpenClaw ตอบกลับเข้า Pinto ได้
228
+
229
+ ### การทดสอบ
230
+
231
+ #### ทดสอบ inbound แบบ local
232
+
233
+ ```bash
234
+ curl -i -X POST http://127.0.0.1:18789/plugins/pinto/webhook \
235
+ -H 'Content-Type: application/json' \
236
+ -H 'X-Pinto-Secret: pinto-oc-9f3a1b7c5d2e8k4m' \
237
+ -d '{
238
+ "bot_id":"20387880-7934-40c3-b7d4-9fa6557697cf",
239
+ "chat_id":"5f315d4e-cf22-4054-bbb0-2fe074bd3892",
240
+ "message":"hello",
241
+ "user_id":"a7c3fe36-cf41-42b5-a290-ca98e6129fac"
242
+ }'
243
+ ```
244
+
245
+ response ที่คาดหวัง:
246
+
247
+ ```json
248
+ {"message":"Message forwarded to agent"}
249
+ ```
250
+
251
+ #### ทดสอบ inbound แบบ public URL
252
+
253
+ ```bash
254
+ curl -i -X POST https://your-host.example.com/plugins/pinto/webhook \
255
+ -H 'Content-Type: application/json' \
256
+ -H 'X-Pinto-Secret: pinto-oc-9f3a1b7c5d2e8k4m' \
257
+ -d '{
258
+ "bot_id":"20387880-7934-40c3-b7d4-9fa6557697cf",
259
+ "chat_id":"5f315d4e-cf22-4054-bbb0-2fe074bd3892",
260
+ "message":"hello",
261
+ "user_id":"a7c3fe36-cf41-42b5-a290-ca98e6129fac"
262
+ }'
263
+ ```
264
+
265
+ #### ทดสอบ outbound ตรงไปที่ Pinto API
266
+
267
+ ```bash
268
+ curl -i -X POST https://api-dev.pinto-app.com/v1/bots/webhook/receive \
269
+ -H 'Content-Type: application/json' \
270
+ -H 'X-Pinto-Secret: pinto-oc-9f3a1b7c5d2e8k4m' \
271
+ -d '{
272
+ "bot_id":"20387880-7934-40c3-b7d4-9fa6557697cf",
273
+ "chat_id":"5f315d4e-cf22-4054-bbb0-2fe074bd3892",
274
+ "reply_message":"OpenClaw outbound test"
275
+ }'
276
+ ```
277
+
278
+ ### Troubleshooting
279
+
280
+ #### 404 Not Found
281
+
282
+ สาเหตุที่พบบ่อย:
283
+
284
+ - เปิด URL ผ่าน browser ด้วย `GET` แทน `POST`
285
+ - reverse proxy หรือ tunnel ไม่ได้ forward มาที่ OpenClaw gateway
286
+ - path ไม่ถูก ต้องใช้ `/plugins/pinto/webhook`
287
+
288
+ #### 401 Unauthorized
289
+
290
+ สาเหตุที่พบบ่อย:
291
+
292
+ - ค่า `X-Pinto-Secret` ไม่ตรงกัน
293
+ - Pinto ส่ง secret มาไม่ตรงกับ `Webhook Secret` ใน OpenClaw
294
+ - OpenClaw ส่ง secret กลับไป Pinto ไม่ตรงกับค่าที่ Pinto คาดไว้
295
+
296
+ #### 400 Invalid request body
297
+
298
+ สาเหตุที่พบบ่อย:
299
+
300
+ - `Bot Id` ไม่ใช่ UUID จริง
301
+ - `chat_id` อยู่คนละ environment
302
+ - payload ที่ส่งไป `POST /v1/bots/webhook/receive` ไม่ตรงกับ backend Pinto
303
+
304
+ #### ไม่เห็นข้อความตอบกลับใน Pinto
305
+
306
+ ตรวจตามนี้:
307
+
308
+ - `Api Url` ถูก environment หรือไม่
309
+ - `Bot Id` ถูกต้องหรือไม่
310
+ - `chat_id` เป็นห้องจริงหรือไม่
311
+ - Pinto API รับ `POST /v1/bots/webhook/receive` สำเร็จหรือไม่
312
+ - `X-Pinto-Secret` ตรงกันทั้งสองฝั่งหรือไม่
313
+
314
+ ### การพัฒนา
315
+
316
+ ```bash
317
+ npm install
318
+ npm run build
319
+ npm test
320
+ ```
321
+
322
+ ## English
323
+
324
+ ### Overview
325
+
326
+ `pinto-app-openclaw` is an OpenClaw channel plugin for Pinto Chat.
327
+
328
+ Flow:
329
+
330
+ 1. A user sends a message in Pinto
331
+ 2. Pinto calls the OpenClaw webhook at `/plugins/pinto/webhook`
332
+ 3. The plugin forwards the message to an OpenClaw agent
333
+ 4. The plugin sends the final reply back to Pinto through `POST /v1/bots/webhook/receive`
334
+
335
+ Main features:
336
+
337
+ - Direct chat support
338
+ - Optional `media_url` in replies
339
+ - `Webhook Secret` support via `X-Pinto-Secret`
340
+ - OpenClaw UI configuration in `Channels > Pinto Chat`
341
+
342
+ ### Requirements
343
+
344
+ - A working OpenClaw instance
345
+ - Node.js 20+ and npm
346
+ - An existing Pinto bot
347
+ - The real Pinto bot UUID
348
+ - A valid Pinto API base URL such as `https://api-dev.pinto-app.com`
349
+ - A public or reachable URL that Pinto can call
350
+
351
+ ### Installation
352
+
353
+ #### Install by package name
354
+
355
+ Recommended:
356
+
357
+ ```bash
358
+ openclaw plugins install pinto-app-openclaw
359
+ ```
360
+
361
+ #### Install from local source
362
+
363
+ ```bash
364
+ git clone https://github.com/fakduai-logistics-and-digital-platform/pinto-openclaw-gateway.git
365
+ cd pinto-openclaw-gateway
366
+ npm install
367
+ npm run build
368
+ openclaw plugins install .
369
+ ```
370
+
371
+ #### Manual deployment
372
+
373
+ If you deploy by copying files manually, copy these files into the OpenClaw extensions directory:
374
+
375
+ - `dist/`
376
+ - `openclaw.plugin.json`
377
+ - `package.json`
378
+ - `README.md`
379
+
380
+ Example destination:
381
+
382
+ ```bash
383
+ ~/.openclaw/extensions/pinto-app-openclaw
384
+ ```
385
+
386
+ ### OpenClaw Configuration
387
+
388
+ You can configure the plugin either:
389
+
390
+ - In the OpenClaw UI at `Channels > Pinto Chat`
391
+ - In the OpenClaw config file
392
+
393
+ Fields:
394
+
395
+ - `Api Url`
396
+ - Pinto API base URL
397
+ - With or without a trailing slash is supported
398
+ - `Bot Id`
399
+ - Must be the real Pinto bot UUID
400
+ - Do not use the human-readable bot slug
401
+ - `Enabled`
402
+ - Enables or disables the channel
403
+ - `Webhook Secret`
404
+ - Shared secret used with `X-Pinto-Secret`
405
+ - If empty, inbound secret validation is not enforced
406
+
407
+ Example config:
408
+
409
+ ```json
410
+ {
411
+ "channels": {
412
+ "pinto": {
413
+ "enabled": true,
414
+ "apiUrl": "https://api-dev.pinto-app.com",
415
+ "botId": "20387880-7934-40c3-b7d4-9fa6557697cf",
416
+ "webhookSecret": "pinto-oc-9f3a1b7c5d2e8k4m"
417
+ }
418
+ }
419
+ }
420
+ ```
421
+
422
+ Notes:
423
+
424
+ - The channel id is `pinto`
425
+ - The package name is `pinto-app-openclaw`
426
+
427
+ ### Pinto Configuration
428
+
429
+ Your Pinto bot must have:
430
+
431
+ - `webhook_url`
432
+ - The URL Pinto calls
433
+ - Example:
434
+ - `https://your-host.example.com/plugins/pinto/webhook`
435
+ - Bot UUID
436
+ - Use the bot `_id` as `Bot Id` in OpenClaw
437
+ - If webhook security is enabled
438
+ - Pinto must send `X-Pinto-Secret`
439
+ - The value must match the OpenClaw `Webhook Secret`
440
+
441
+ ### Webhook Secret
442
+
443
+ The plugin supports `Webhook Secret` for both inbound and outbound requests.
444
+
445
+ #### Inbound: Pinto -> OpenClaw
446
+
447
+ If `Webhook Secret` is configured in OpenClaw:
448
+
449
+ - Pinto must send:
450
+
451
+ ```http
452
+ X-Pinto-Secret: <your-secret>
453
+ ```
454
+
455
+ - If the value does not match, the plugin returns `401 Unauthorized`
456
+
457
+ If no `Webhook Secret` is configured:
458
+
459
+ - The plugin does not enforce inbound secret validation
460
+
461
+ #### Outbound: OpenClaw -> Pinto
462
+
463
+ When the plugin sends replies back to Pinto through:
464
+
465
+ ```http
466
+ POST <apiUrl>/v1/bots/webhook/receive
467
+ ```
468
+
469
+ it automatically includes:
470
+
471
+ ```http
472
+ X-Pinto-Secret: <your-secret>
473
+ ```
474
+
475
+ when `Webhook Secret` is configured.
476
+
477
+ ### Relevant Endpoints
478
+
479
+ #### Inbound webhook
480
+
481
+ Pinto calls:
482
+
483
+ ```http
484
+ POST /plugins/pinto/webhook
485
+ ```
486
+
487
+ Example request body:
488
+
489
+ ```json
490
+ {
491
+ "bot_id": "20387880-7934-40c3-b7d4-9fa6557697cf",
492
+ "chat_id": "5f315d4e-cf22-4054-bbb0-2fe074bd3892",
493
+ "message": "hello",
494
+ "user_id": "a7c3fe36-cf41-42b5-a290-ca98e6129fac",
495
+ "username": "demo-user"
496
+ }
497
+ ```
498
+
499
+ #### Outbound reply
500
+
501
+ The plugin sends:
502
+
503
+ ```http
504
+ POST <apiUrl>/v1/bots/webhook/receive
505
+ ```
506
+
507
+ Example body:
508
+
509
+ ```json
510
+ {
511
+ "bot_id": "20387880-7934-40c3-b7d4-9fa6557697cf",
512
+ "chat_id": "5f315d4e-cf22-4054-bbb0-2fe074bd3892",
513
+ "reply_message": "Hello from OpenClaw"
514
+ }
515
+ ```
516
+
517
+ Media example:
518
+
519
+ ```json
520
+ {
521
+ "bot_id": "20387880-7934-40c3-b7d4-9fa6557697cf",
522
+ "chat_id": "5f315d4e-cf22-4054-bbb0-2fe074bd3892",
523
+ "reply_message": "See this image",
524
+ "media_url": "https://example.com/image.png"
525
+ }
526
+ ```
527
+
528
+ ### Recommended Setup Flow
529
+
530
+ 1. Install the plugin with:
531
+
532
+ ```bash
533
+ openclaw plugins install pinto-app-openclaw
534
+ ```
535
+
536
+ 2. Restart OpenClaw or reload plugins
537
+ 3. Open `Channels > Pinto Chat`
538
+ 4. Fill in `Api Url`, `Bot Id`, and `Webhook Secret`
539
+ 5. Save the channel config
540
+ 6. Set the Pinto bot `webhook_url` to `/plugins/pinto/webhook`
541
+ 7. Test the webhook with `curl`
542
+ 8. Send a real message from Pinto
543
+ 9. Confirm that OpenClaw replies back into Pinto
544
+
545
+ ### Testing
546
+
547
+ #### Local inbound test
548
+
549
+ ```bash
550
+ curl -i -X POST http://127.0.0.1:18789/plugins/pinto/webhook \
551
+ -H 'Content-Type: application/json' \
552
+ -H 'X-Pinto-Secret: pinto-oc-9f3a1b7c5d2e8k4m' \
553
+ -d '{
554
+ "bot_id":"20387880-7934-40c3-b7d4-9fa6557697cf",
555
+ "chat_id":"5f315d4e-cf22-4054-bbb0-2fe074bd3892",
556
+ "message":"hello",
557
+ "user_id":"a7c3fe36-cf41-42b5-a290-ca98e6129fac"
558
+ }'
559
+ ```
560
+
561
+ Expected response:
562
+
563
+ ```json
564
+ {"message":"Message forwarded to agent"}
565
+ ```
566
+
567
+ #### Public inbound test
568
+
569
+ ```bash
570
+ curl -i -X POST https://your-host.example.com/plugins/pinto/webhook \
571
+ -H 'Content-Type: application/json' \
572
+ -H 'X-Pinto-Secret: pinto-oc-9f3a1b7c5d2e8k4m' \
573
+ -d '{
574
+ "bot_id":"20387880-7934-40c3-b7d4-9fa6557697cf",
575
+ "chat_id":"5f315d4e-cf22-4054-bbb0-2fe074bd3892",
576
+ "message":"hello",
577
+ "user_id":"a7c3fe36-cf41-42b5-a290-ca98e6129fac"
578
+ }'
579
+ ```
580
+
581
+ #### Direct outbound test to Pinto API
582
+
583
+ ```bash
584
+ curl -i -X POST https://api-dev.pinto-app.com/v1/bots/webhook/receive \
585
+ -H 'Content-Type: application/json' \
586
+ -H 'X-Pinto-Secret: pinto-oc-9f3a1b7c5d2e8k4m' \
587
+ -d '{
588
+ "bot_id":"20387880-7934-40c3-b7d4-9fa6557697cf",
589
+ "chat_id":"5f315d4e-cf22-4054-bbb0-2fe074bd3892",
590
+ "reply_message":"OpenClaw outbound test"
591
+ }'
592
+ ```
593
+
594
+ ### Troubleshooting
595
+
596
+ #### 404 Not Found
597
+
598
+ Common causes:
599
+
600
+ - Opening the webhook URL in a browser with `GET` instead of `POST`
601
+ - Your reverse proxy or tunnel is not forwarding to OpenClaw
602
+ - The path is wrong. Use `/plugins/pinto/webhook`
603
+
604
+ #### 401 Unauthorized
605
+
606
+ Common causes:
607
+
608
+ - `X-Pinto-Secret` does not match
609
+ - Pinto sends the wrong secret to OpenClaw
610
+ - OpenClaw sends the wrong secret back to Pinto
611
+
612
+ #### 400 Invalid request body
613
+
614
+ Common causes:
615
+
616
+ - `Bot Id` is not a real UUID
617
+ - `chat_id` belongs to a different environment
618
+ - The payload sent to `POST /v1/bots/webhook/receive` does not match Pinto backend expectations
619
+
620
+ #### No reply appears in Pinto
621
+
622
+ Check:
623
+
624
+ - `Api Url` points to the correct environment
625
+ - `Bot Id` is correct
626
+ - `chat_id` is a real chat
627
+ - Pinto API accepts `POST /v1/bots/webhook/receive`
628
+ - `X-Pinto-Secret` matches on both sides
629
+
630
+ ### Development
631
+
632
+ ```bash
633
+ npm install
634
+ npm run build
635
+ npm test
636
+ ```
@@ -0,0 +1,6 @@
1
+ import type { ChannelPlugin, RuntimeEnv } from "openclaw/plugin-sdk";
2
+ export declare const setPintoRuntime: (r: RuntimeEnv) => void;
3
+ export declare const pintoPlugin: ChannelPlugin<any, any> & {
4
+ configSchema?: any;
5
+ };
6
+ //# sourceMappingURL=channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAarE,eAAO,MAAM,eAAe,GAAI,GAAG,UAAU,SAE5C,CAAC;AA6GF,eAAO,MAAM,WAAW,EAAE,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG;IAAE,YAAY,CAAC,EAAE,GAAG,CAAA;CAgNvE,CAAC"}
@@ -0,0 +1,264 @@
1
+ import { buildChannelConfigSchema, registerPluginHttpRoute, } from "openclaw/plugin-sdk";
2
+ import { z } from "zod";
3
+ const stripTrailingSlash = (url) => url.replace(/\/+$/, "");
4
+ const PINTO_SECRET_HEADER = "x-pinto-secret";
5
+ let runtime;
6
+ export const setPintoRuntime = (r) => {
7
+ runtime = r;
8
+ };
9
+ const PintoChannelConfigSchema = z
10
+ .object({
11
+ enabled: z.boolean().default(true),
12
+ apiUrl: z.string().trim().min(1).default("https://api.pinto-app.com/"),
13
+ botId: z.string().trim().min(1).optional(),
14
+ webhookSecret: z.string().trim().optional(),
15
+ })
16
+ .strict();
17
+ const getPintoChannelConfig = (cfg, accountId) => {
18
+ const resolvedAccountId = accountId ?? "default";
19
+ const channelConfig = cfg?.channels?.pinto ?? {};
20
+ const accountConfig = channelConfig.accounts?.[resolvedAccountId];
21
+ return {
22
+ enabled: true,
23
+ ...(accountConfig ?? channelConfig),
24
+ };
25
+ };
26
+ const buildPintoHeaders = (webhookSecret) => {
27
+ const headers = {
28
+ "Content-Type": "application/json",
29
+ };
30
+ const secret = webhookSecret?.trim();
31
+ if (secret) {
32
+ headers["X-Pinto-Secret"] = secret;
33
+ }
34
+ return headers;
35
+ };
36
+ const getRequestHeader = (req, headerName) => {
37
+ const value = req.headers[headerName.toLowerCase()];
38
+ if (Array.isArray(value)) {
39
+ return value[0];
40
+ }
41
+ return value ?? undefined;
42
+ };
43
+ async function sendPintoText(params) {
44
+ const account = getPintoChannelConfig(params.cfg, params.accountId);
45
+ const apiUrl = stripTrailingSlash(account?.apiUrl ?? "https://api-dev.pinto-app.com");
46
+ const botId = account?.botId?.trim();
47
+ const webhookSecret = account?.webhookSecret?.trim();
48
+ if (!botId) {
49
+ throw new Error("Pinto botId is not configured");
50
+ }
51
+ const payload = {
52
+ bot_id: botId,
53
+ chat_id: params.to,
54
+ reply_message: params.text,
55
+ };
56
+ const res = await fetch(`${apiUrl}/v1/bots/webhook/receive`, {
57
+ method: "POST",
58
+ headers: buildPintoHeaders(webhookSecret),
59
+ body: JSON.stringify(payload),
60
+ });
61
+ if (!res.ok) {
62
+ throw new Error(`Pinto API error: ${res.status} ${res.statusText}`);
63
+ }
64
+ return { channel: "pinto", messageId: Date.now().toString() };
65
+ }
66
+ const waitUntilAbort = (signal, onAbort) => new Promise((resolve) => {
67
+ const complete = () => {
68
+ onAbort?.();
69
+ resolve();
70
+ };
71
+ if (!signal)
72
+ return;
73
+ if (signal.aborted) {
74
+ complete();
75
+ return;
76
+ }
77
+ signal.addEventListener("abort", complete, { once: true });
78
+ });
79
+ async function readJsonBody(req) {
80
+ const raw = await new Promise((resolve, reject) => {
81
+ const chunks = [];
82
+ req.on("data", (chunk) => {
83
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
84
+ });
85
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
86
+ req.on("error", reject);
87
+ });
88
+ return JSON.parse(raw || "{}");
89
+ }
90
+ export const pintoPlugin = {
91
+ id: "pinto",
92
+ meta: {
93
+ id: "pinto",
94
+ name: "Pinto",
95
+ label: "Pinto Chat",
96
+ selectionLabel: "Pinto (Chat Bot)",
97
+ blurb: "Pinto App Thailand",
98
+ aliases: ["pinto"],
99
+ detailLabel: "Pinto Chat via API",
100
+ description: "Adapter for Pinto Chat platform",
101
+ },
102
+ configSchema: buildChannelConfigSchema(PintoChannelConfigSchema),
103
+ capabilities: {
104
+ chatTypes: ["direct"],
105
+ media: true,
106
+ nativeCommands: false,
107
+ reactions: false,
108
+ threads: false,
109
+ },
110
+ config: {
111
+ listAccountIds: (cfg) => {
112
+ return ["default"];
113
+ },
114
+ resolveAccount: (cfg, accountId) => {
115
+ const bot = getPintoChannelConfig(cfg, accountId);
116
+ return {
117
+ id: accountId || "default",
118
+ config: bot,
119
+ enabled: bot?.enabled ?? true,
120
+ };
121
+ },
122
+ inspectAccount: (cfg, accountId) => {
123
+ const bot = getPintoChannelConfig(cfg, accountId);
124
+ if (!bot || !bot.apiUrl || !bot.botId) {
125
+ return { configured_unavailable: true };
126
+ }
127
+ return {
128
+ tokenSource: "config",
129
+ tokenStatus: "available",
130
+ };
131
+ },
132
+ isConfigured: (account) => {
133
+ return Boolean(account.config?.apiUrl?.trim() && account.config?.botId?.trim());
134
+ },
135
+ describeAccount: (account) => ({
136
+ accountId: account.id,
137
+ name: "Pinto Default Bot",
138
+ enabled: account.enabled,
139
+ configured: Boolean(account.config?.apiUrl?.trim() && account.config?.botId?.trim()),
140
+ }),
141
+ },
142
+ outbound: {
143
+ deliveryMode: "direct",
144
+ sendText: async ({ to, text, accountId, cfg }) => sendPintoText({ cfg, accountId, to, text }),
145
+ sendMedia: async ({ to, text, mediaUrl, accountId, cfg }) => {
146
+ const account = getPintoChannelConfig(cfg, accountId);
147
+ const apiUrl = stripTrailingSlash(account?.apiUrl ?? "https://api-dev.pinto-app.com");
148
+ const botId = account?.botId?.trim();
149
+ const webhookSecret = account?.webhookSecret?.trim();
150
+ if (!botId) {
151
+ throw new Error("Pinto botId is not configured");
152
+ }
153
+ const payload = {
154
+ bot_id: botId,
155
+ chat_id: to,
156
+ reply_message: text,
157
+ media_url: mediaUrl,
158
+ };
159
+ const res = await fetch(`${apiUrl}/v1/bots/webhook/receive`, {
160
+ method: "POST",
161
+ headers: buildPintoHeaders(webhookSecret),
162
+ body: JSON.stringify(payload),
163
+ });
164
+ if (!res.ok) {
165
+ throw new Error(`Pinto API error: ${res.status} ${res.statusText}`);
166
+ }
167
+ return { channel: "pinto", messageId: Date.now().toString() };
168
+ },
169
+ },
170
+ gateway: {
171
+ startAccount: async (ctx) => {
172
+ const account = getPintoChannelConfig(ctx.cfg, ctx.accountId);
173
+ if (account?.enabled === false ||
174
+ !account?.apiUrl?.trim() ||
175
+ !account?.botId?.trim()) {
176
+ return waitUntilAbort(ctx.abortSignal);
177
+ }
178
+ if (!ctx.channelRuntime) {
179
+ ctx.log?.warn?.("Pinto channelRuntime unavailable; webhook route not started");
180
+ return waitUntilAbort(ctx.abortSignal);
181
+ }
182
+ const unregister = registerPluginHttpRoute({
183
+ path: "/plugins/pinto/webhook",
184
+ auth: "plugin",
185
+ replaceExisting: true,
186
+ pluginId: "pinto",
187
+ accountId: ctx.accountId,
188
+ handler: async (req, res) => {
189
+ try {
190
+ const configuredSecret = account?.webhookSecret?.trim();
191
+ const inboundSecret = getRequestHeader(req, PINTO_SECRET_HEADER);
192
+ if (configuredSecret && inboundSecret !== configuredSecret) {
193
+ res.statusCode = 401;
194
+ res.end(JSON.stringify({ error: "Invalid webhook secret" }));
195
+ return true;
196
+ }
197
+ const payload = await readJsonBody(req);
198
+ if (!payload.bot_id || !payload.chat_id) {
199
+ res.statusCode = 400;
200
+ res.end(JSON.stringify({ error: "Missing required fields" }));
201
+ return true;
202
+ }
203
+ const route = ctx.channelRuntime.routing.resolveAgentRoute({
204
+ cfg: ctx.cfg,
205
+ channel: "pinto",
206
+ accountId: ctx.accountId,
207
+ peer: { kind: "direct", id: payload.chat_id },
208
+ });
209
+ const msgCtx = ctx.channelRuntime.reply.finalizeInboundContext({
210
+ Body: payload.message ?? "",
211
+ RawBody: payload.message ?? "",
212
+ CommandBody: payload.message ?? "",
213
+ From: `pinto:${payload.user_id ?? payload.chat_id}`,
214
+ To: `pinto:${payload.chat_id}`,
215
+ SessionKey: route.sessionKey,
216
+ AccountId: route.accountId,
217
+ OriginatingChannel: "pinto",
218
+ OriginatingTo: `pinto:${payload.chat_id}`,
219
+ ChatType: "direct",
220
+ SenderName: payload.username ?? payload.user_id ?? payload.chat_id,
221
+ SenderId: payload.user_id ?? payload.chat_id,
222
+ Provider: "pinto",
223
+ Surface: "pinto",
224
+ ConversationLabel: `Pinto: ${payload.chat_id}`,
225
+ Timestamp: Date.now(),
226
+ CommandAuthorized: true,
227
+ });
228
+ await ctx.channelRuntime.reply.dispatchReplyWithBufferedBlockDispatcher({
229
+ ctx: msgCtx,
230
+ cfg: ctx.cfg,
231
+ dispatcherOptions: {
232
+ deliver: async (replyPayload) => {
233
+ const text = replyPayload?.text ?? replyPayload?.body;
234
+ if (!text)
235
+ return;
236
+ await sendPintoText({
237
+ cfg: ctx.cfg,
238
+ accountId: ctx.accountId,
239
+ to: payload.chat_id,
240
+ text,
241
+ });
242
+ },
243
+ },
244
+ });
245
+ res.statusCode = 200;
246
+ res.end(JSON.stringify({ message: "Message forwarded to agent" }));
247
+ return true;
248
+ }
249
+ catch (error) {
250
+ ctx.log?.error?.(`[PintoPlugin] Webhook error: ${error?.message ?? String(error)}`);
251
+ res.statusCode = 500;
252
+ res.end(JSON.stringify({
253
+ error: "Internal Server Error",
254
+ detail: error?.message ?? String(error),
255
+ }));
256
+ return true;
257
+ }
258
+ },
259
+ });
260
+ return waitUntilAbort(ctx.abortSignal, () => unregister());
261
+ },
262
+ },
263
+ };
264
+ //# sourceMappingURL=channel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.js","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
+ declare const plugin: {
3
+ id: string;
4
+ name: string;
5
+ description: string;
6
+ register(api: OpenClawPluginApi): void;
7
+ };
8
+ export default plugin;
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAG7D,QAAA,MAAM,MAAM;;;;kBAKI,iBAAiB;CAWhC,CAAC;AAEF,eAAe,MAAM,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ import { pintoPlugin, setPintoRuntime } from "./channel.js";
2
+ const plugin = {
3
+ id: "pinto-app-openclaw",
4
+ name: "Pinto Chat",
5
+ description: "Plugin to connect Pinto Chat with OpenClaw AI Agents",
6
+ register(api) {
7
+ const logger = api.runtime?.logger;
8
+ setPintoRuntime(api.runtime);
9
+ api.registerChannel({
10
+ plugin: pintoPlugin,
11
+ });
12
+ logger?.info("Pinto Chat Plugin Registered successfully");
13
+ },
14
+ };
15
+ export default plugin;
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE5D,MAAM,MAAM,GAAG;IACb,EAAE,EAAE,oBAAoB;IACxB,IAAI,EAAE,YAAY;IAClB,WAAW,EAAE,sDAAsD;IAEnE,QAAQ,CAAC,GAAsB;QAC7B,MAAM,MAAM,GAAI,GAAG,CAAC,OAAe,EAAE,MAAM,CAAC;QAE5C,eAAe,CAAC,GAAG,CAAC,OAAc,CAAC,CAAC;QAEpC,GAAG,CAAC,eAAe,CAAC;YAClB,MAAM,EAAE,WAAW;SACpB,CAAC,CAAC;QAEH,MAAM,EAAE,IAAI,CAAC,2CAA2C,CAAC,CAAC;IAC5D,CAAC;CACF,CAAC;AAEF,eAAe,MAAM,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Pinto Request Payload (Inbound to OpenClaw)
3
+ */
4
+ export interface PintoWebhookPayload {
5
+ user_id: string;
6
+ username?: string;
7
+ message: string;
8
+ image_url?: string;
9
+ chat_id: string;
10
+ bot_id: string;
11
+ }
12
+ /**
13
+ * Pinto Response Payload (Outbound to Pinto)
14
+ */
15
+ export interface PintoWebhookReceiveRequest {
16
+ bot_id: string;
17
+ chat_id: string;
18
+ reply_message: string;
19
+ media_url?: string;
20
+ webhook_secret?: string;
21
+ }
22
+ /**
23
+ * Plugin Configuration
24
+ */
25
+ export interface PintoPluginConfig {
26
+ pintoApiUrl: string;
27
+ pintoWebhookSecret?: string;
28
+ }
29
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,14 @@
1
+ {
2
+ "id": "pinto-app-openclaw",
3
+ "name": "Pinto Chat",
4
+ "description": "Integration with Pinto Chat API",
5
+ "version": "1.3.0",
6
+ "channels": [
7
+ "pinto"
8
+ ],
9
+ "configSchema": {
10
+ "type": "object",
11
+ "additionalProperties": false,
12
+ "properties": {}
13
+ }
14
+ }
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "pinto-app-openclaw",
3
+ "version": "1.3.4",
4
+ "description": "OpenClaw Channel Plugin for Pinto Chat",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "author": "Fakduai Logistics and Digital Platform",
9
+ "license": "MIT",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/fakduai-logistics-and-digital-platform/pinto-openclaw-gateway.git"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/fakduai-logistics-and-digital-platform/pinto-openclaw-gateway/issues"
16
+ },
17
+ "homepage": "https://github.com/fakduai-logistics-and-digital-platform/pinto-openclaw-gateway#readme",
18
+ "keywords": [
19
+ "openclaw",
20
+ "pinto",
21
+ "chat",
22
+ "plugin",
23
+ "gateway",
24
+ "channel"
25
+ ],
26
+ "channel": {
27
+ "id": "pinto",
28
+ "label": "Pinto",
29
+ "selectionLabel": "Pinto (Chat Bot)",
30
+ "blurb": "Pinto App Thailand"
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "openclaw.plugin.json",
35
+ "README.md"
36
+ ],
37
+ "openclaw": {
38
+ "extensions": [
39
+ "./dist/index.js"
40
+ ]
41
+ },
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "scripts": {
46
+ "build": "tsc",
47
+ "dev": "tsx watch src/index.ts",
48
+ "start": "node dist/index.js",
49
+ "test": "vitest run",
50
+ "test:watch": "vitest",
51
+ "prepare": "npm run build"
52
+ },
53
+ "dependencies": {
54
+ "zod": "^4.3.6"
55
+ },
56
+ "devDependencies": {
57
+ "@types/node": "^20.11.0",
58
+ "openclaw": "^2026.3.8",
59
+ "tsx": "^4.7.0",
60
+ "typescript": "^5.4.0",
61
+ "vitest": "^1.6.0"
62
+ },
63
+ "peerDependencies": {
64
+ "openclaw": ">=2026.0.0"
65
+ }
66
+ }