freddie 0.0.47 → 0.0.49

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 (232) hide show
  1. package/AGENTS.md +12 -0
  2. package/CHANGELOG.md +26 -2
  3. package/package.json +3 -2
  4. package/plugins/ansi_strip/handler.js +7 -0
  5. package/plugins/ansi_strip/plugin.js +2 -0
  6. package/plugins/approval/handler.js +13 -0
  7. package/plugins/approval/plugin.js +2 -0
  8. package/plugins/bash/handler.js +33 -0
  9. package/plugins/bash/plugin.js +2 -0
  10. package/plugins/binary_extensions/handler.js +20 -0
  11. package/plugins/binary_extensions/plugin.js +2 -0
  12. package/plugins/browser/handler.js +46 -0
  13. package/plugins/browser/plugin.js +2 -0
  14. package/plugins/budget_config/handler.js +12 -0
  15. package/plugins/budget_config/plugin.js +2 -0
  16. package/plugins/checkpoint/handler.js +27 -0
  17. package/plugins/checkpoint/plugin.js +2 -0
  18. package/plugins/clarify/handler.js +13 -0
  19. package/plugins/clarify/plugin.js +2 -0
  20. package/plugins/code_execution/handler.js +25 -0
  21. package/plugins/code_execution/plugin.js +2 -0
  22. package/plugins/core-agent-machine/plugin.js +8 -0
  23. package/plugins/core-cli/plugin.js +83 -0
  24. package/plugins/core-commands/plugin.js +7 -0
  25. package/plugins/core-compressor/plugin.js +15 -0
  26. package/plugins/core-context-engine/plugin.js +7 -0
  27. package/plugins/core-cron/plugin.js +7 -0
  28. package/plugins/core-skills/plugin.js +7 -0
  29. package/plugins/credential_files/handler.js +14 -0
  30. package/plugins/credential_files/plugin.js +2 -0
  31. package/plugins/cronjob/handler.js +14 -0
  32. package/plugins/cronjob/plugin.js +2 -0
  33. package/plugins/debug_helpers/handler.js +8 -0
  34. package/plugins/debug_helpers/plugin.js +2 -0
  35. package/plugins/delegate/handler.js +27 -0
  36. package/plugins/delegate/plugin.js +2 -0
  37. package/plugins/discord_tool/handler.js +12 -0
  38. package/plugins/discord_tool/plugin.js +2 -0
  39. package/plugins/edit/handler.js +29 -0
  40. package/plugins/edit/plugin.js +2 -0
  41. package/plugins/env_passthrough/handler.js +14 -0
  42. package/plugins/env_passthrough/plugin.js +2 -0
  43. package/plugins/feishu_doc/handler.js +14 -0
  44. package/plugins/feishu_doc/plugin.js +2 -0
  45. package/plugins/feishu_drive/handler.js +13 -0
  46. package/plugins/feishu_drive/plugin.js +2 -0
  47. package/plugins/file_operations/handler.js +15 -0
  48. package/plugins/file_operations/plugin.js +2 -0
  49. package/plugins/file_state/handler.js +14 -0
  50. package/plugins/file_state/plugin.js +2 -0
  51. package/plugins/file_tools/handler.js +21 -0
  52. package/plugins/file_tools/plugin.js +2 -0
  53. package/plugins/fuzzy_match/handler.js +7 -0
  54. package/plugins/fuzzy_match/plugin.js +2 -0
  55. package/plugins/gm-cc/plugin.js +28 -0
  56. package/plugins/grep/handler.js +49 -0
  57. package/plugins/grep/plugin.js +2 -0
  58. package/plugins/gui-agents/plugin.js +26 -0
  59. package/plugins/gui-batch/plugin.js +11 -0
  60. package/plugins/gui-chat/plugin.js +21 -0
  61. package/plugins/gui-config/plugin.js +12 -0
  62. package/plugins/gui-cron/plugin.js +13 -0
  63. package/plugins/gui-debug/plugin.js +24 -0
  64. package/plugins/gui-env/plugin.js +7 -0
  65. package/plugins/gui-gateway/plugin.js +9 -0
  66. package/plugins/gui-profiles-commands-health/plugin.js +11 -0
  67. package/plugins/gui-sessions/plugin.js +9 -0
  68. package/plugins/gui-skills/plugin.js +8 -0
  69. package/plugins/gui-tools/plugin.js +7 -0
  70. package/plugins/homeassistant_tool/handler.js +14 -0
  71. package/plugins/homeassistant_tool/plugin.js +2 -0
  72. package/plugins/image_gen/handler.js +31 -0
  73. package/plugins/image_gen/plugin.js +2 -0
  74. package/plugins/interrupt/handler.js +16 -0
  75. package/plugins/interrupt/plugin.js +2 -0
  76. package/plugins/managed_tool_gateway/handler.js +9 -0
  77. package/plugins/managed_tool_gateway/plugin.js +2 -0
  78. package/plugins/mcp_oauth/handler.js +20 -0
  79. package/plugins/mcp_oauth/plugin.js +2 -0
  80. package/plugins/mcp_oauth_manager/handler.js +18 -0
  81. package/plugins/mcp_oauth_manager/plugin.js +2 -0
  82. package/plugins/mcp_tool/handler.js +34 -0
  83. package/plugins/mcp_tool/plugin.js +2 -0
  84. package/plugins/memory/handler.js +66 -0
  85. package/plugins/memory/plugin.js +2 -0
  86. package/plugins/memory-byterover/handler.js +25 -0
  87. package/plugins/memory-byterover/plugin.js +2 -0
  88. package/plugins/memory-hindsight/handler.js +25 -0
  89. package/plugins/memory-hindsight/plugin.js +2 -0
  90. package/plugins/memory-holographic/handler.js +31 -0
  91. package/plugins/memory-holographic/plugin.js +2 -0
  92. package/plugins/memory-honcho/handler.js +25 -0
  93. package/plugins/memory-honcho/plugin.js +2 -0
  94. package/plugins/memory-mem0/handler.js +25 -0
  95. package/plugins/memory-mem0/plugin.js +2 -0
  96. package/plugins/memory-openviking/handler.js +25 -0
  97. package/plugins/memory-openviking/plugin.js +2 -0
  98. package/plugins/memory-retaindb/handler.js +25 -0
  99. package/plugins/memory-retaindb/plugin.js +2 -0
  100. package/plugins/memory-supermemory/handler.js +25 -0
  101. package/plugins/memory-supermemory/plugin.js +2 -0
  102. package/plugins/mixture_of_agents/handler.js +13 -0
  103. package/plugins/mixture_of_agents/plugin.js +2 -0
  104. package/plugins/neutts_synth/handler.js +12 -0
  105. package/plugins/neutts_synth/plugin.js +2 -0
  106. package/plugins/openrouter_client/handler.js +12 -0
  107. package/plugins/openrouter_client/plugin.js +2 -0
  108. package/plugins/osv_check/handler.js +10 -0
  109. package/plugins/osv_check/plugin.js +2 -0
  110. package/plugins/patch_parser/handler.js +40 -0
  111. package/plugins/patch_parser/plugin.js +2 -0
  112. package/plugins/path_security/handler.js +14 -0
  113. package/plugins/path_security/plugin.js +2 -0
  114. package/plugins/platform-api_server/handler.js +21 -0
  115. package/plugins/platform-api_server/plugin.js +2 -0
  116. package/plugins/platform-bluebubbles/handler.js +32 -0
  117. package/plugins/platform-bluebubbles/plugin.js +2 -0
  118. package/plugins/platform-dingtalk/handler.js +32 -0
  119. package/plugins/platform-dingtalk/plugin.js +2 -0
  120. package/plugins/platform-discord/handler.js +24 -0
  121. package/plugins/platform-discord/plugin.js +2 -0
  122. package/plugins/platform-email/handler.js +51 -0
  123. package/plugins/platform-email/plugin.js +2 -0
  124. package/plugins/platform-feishu/handler.js +32 -0
  125. package/plugins/platform-feishu/plugin.js +2 -0
  126. package/plugins/platform-feishu_comment/handler.js +12 -0
  127. package/plugins/platform-feishu_comment/plugin.js +2 -0
  128. package/plugins/platform-feishu_comment_rules/handler.js +11 -0
  129. package/plugins/platform-feishu_comment_rules/plugin.js +2 -0
  130. package/plugins/platform-homeassistant/handler.js +32 -0
  131. package/plugins/platform-homeassistant/plugin.js +2 -0
  132. package/plugins/platform-matrix/handler.js +40 -0
  133. package/plugins/platform-matrix/plugin.js +2 -0
  134. package/plugins/platform-mattermost/handler.js +29 -0
  135. package/plugins/platform-mattermost/plugin.js +2 -0
  136. package/plugins/platform-qqbot/handler.js +32 -0
  137. package/plugins/platform-qqbot/plugin.js +2 -0
  138. package/plugins/platform-signal/handler.js +33 -0
  139. package/plugins/platform-signal/plugin.js +2 -0
  140. package/plugins/platform-slack/handler.js +34 -0
  141. package/plugins/platform-slack/plugin.js +2 -0
  142. package/plugins/platform-sms/handler.js +34 -0
  143. package/plugins/platform-sms/plugin.js +2 -0
  144. package/plugins/platform-telegram/handler.js +38 -0
  145. package/plugins/platform-telegram/plugin.js +2 -0
  146. package/plugins/platform-telegram_network/handler.js +17 -0
  147. package/plugins/platform-telegram_network/plugin.js +2 -0
  148. package/plugins/platform-webhook/handler.js +19 -0
  149. package/plugins/platform-webhook/plugin.js +2 -0
  150. package/plugins/platform-wecom/handler.js +32 -0
  151. package/plugins/platform-wecom/plugin.js +2 -0
  152. package/plugins/platform-wecom_callback/handler.js +15 -0
  153. package/plugins/platform-wecom_callback/plugin.js +2 -0
  154. package/plugins/platform-wecom_crypto/handler.js +16 -0
  155. package/plugins/platform-wecom_crypto/plugin.js +2 -0
  156. package/plugins/platform-weixin/handler.js +32 -0
  157. package/plugins/platform-weixin/plugin.js +2 -0
  158. package/plugins/platform-whatsapp/handler.js +40 -0
  159. package/plugins/platform-whatsapp/plugin.js +2 -0
  160. package/plugins/platform-yuanbao/handler.js +9 -0
  161. package/plugins/platform-yuanbao/plugin.js +2 -0
  162. package/plugins/platform-yuanbao_media/handler.js +5 -0
  163. package/plugins/platform-yuanbao_media/plugin.js +2 -0
  164. package/plugins/platform-yuanbao_proto/handler.js +9 -0
  165. package/plugins/platform-yuanbao_proto/plugin.js +2 -0
  166. package/plugins/platform-yuanbao_sticker/handler.js +6 -0
  167. package/plugins/platform-yuanbao_sticker/plugin.js +2 -0
  168. package/plugins/process_registry/handler.js +15 -0
  169. package/plugins/process_registry/plugin.js +2 -0
  170. package/plugins/read/handler.js +24 -0
  171. package/plugins/read/plugin.js +2 -0
  172. package/plugins/rl_training/handler.js +12 -0
  173. package/plugins/rl_training/plugin.js +2 -0
  174. package/plugins/schema_sanitizer/handler.js +17 -0
  175. package/plugins/schema_sanitizer/plugin.js +2 -0
  176. package/plugins/send_message/handler.js +30 -0
  177. package/plugins/send_message/plugin.js +2 -0
  178. package/plugins/session_search/handler.js +21 -0
  179. package/plugins/session_search/plugin.js +2 -0
  180. package/plugins/skill_manager/handler.js +16 -0
  181. package/plugins/skill_manager/plugin.js +2 -0
  182. package/plugins/skill_usage/handler.js +18 -0
  183. package/plugins/skill_usage/plugin.js +2 -0
  184. package/plugins/skills_guard/handler.js +16 -0
  185. package/plugins/skills_guard/plugin.js +2 -0
  186. package/plugins/skills_hub/handler.js +29 -0
  187. package/plugins/skills_hub/plugin.js +2 -0
  188. package/plugins/skills_index/handler.js +12 -0
  189. package/plugins/skills_index/plugin.js +2 -0
  190. package/plugins/skills_sync/handler.js +17 -0
  191. package/plugins/skills_sync/plugin.js +2 -0
  192. package/plugins/skills_tool/handler.js +9 -0
  193. package/plugins/skills_tool/plugin.js +2 -0
  194. package/plugins/slash_confirm/handler.js +14 -0
  195. package/plugins/slash_confirm/plugin.js +2 -0
  196. package/plugins/terminal/handler.js +27 -0
  197. package/plugins/terminal/plugin.js +2 -0
  198. package/plugins/tirith_security/handler.js +23 -0
  199. package/plugins/tirith_security/plugin.js +2 -0
  200. package/plugins/todo/handler.js +52 -0
  201. package/plugins/todo/plugin.js +2 -0
  202. package/plugins/tool_backend_helpers/handler.js +24 -0
  203. package/plugins/tool_backend_helpers/plugin.js +2 -0
  204. package/plugins/tool_output_limits/handler.js +14 -0
  205. package/plugins/tool_output_limits/plugin.js +2 -0
  206. package/plugins/tool_result_storage/handler.js +18 -0
  207. package/plugins/tool_result_storage/plugin.js +2 -0
  208. package/plugins/transcription/handler.js +18 -0
  209. package/plugins/transcription/plugin.js +2 -0
  210. package/plugins/tts/handler.js +18 -0
  211. package/plugins/tts/plugin.js +2 -0
  212. package/plugins/url_safety/handler.js +14 -0
  213. package/plugins/url_safety/plugin.js +2 -0
  214. package/plugins/vision/handler.js +17 -0
  215. package/plugins/vision/plugin.js +2 -0
  216. package/plugins/voice_mode/handler.js +9 -0
  217. package/plugins/voice_mode/plugin.js +2 -0
  218. package/plugins/web_search/handler.js +35 -0
  219. package/plugins/web_search/plugin.js +2 -0
  220. package/plugins/web_tools/handler.js +17 -0
  221. package/plugins/web_tools/plugin.js +2 -0
  222. package/plugins/website_policy/handler.js +13 -0
  223. package/plugins/website_policy/plugin.js +2 -0
  224. package/plugins/write/handler.js +23 -0
  225. package/plugins/write/plugin.js +2 -0
  226. package/plugins/xai_http/handler.js +12 -0
  227. package/plugins/xai_http/plugin.js +2 -0
  228. package/plugins/yuanbao_tools/handler.js +12 -0
  229. package/plugins/yuanbao_tools/plugin.js +2 -0
  230. package/src/sessions.js +2 -1
  231. package/src/web/app.js +17 -0
  232. package/src/web/index.html +7 -3
@@ -0,0 +1,51 @@
1
+ import { EventEmitter } from 'node:events'
2
+ import net from 'node:net'
3
+
4
+ export class EmailAdapter extends EventEmitter {
5
+ constructor(opts = {}) {
6
+ super()
7
+ this.platform = 'email'
8
+ this.smtpHost = opts.smtpHost || process.env.SMTP_HOST
9
+ this.smtpPort = opts.smtpPort || Number(process.env.SMTP_PORT || 587)
10
+ this.smtpUser = opts.smtpUser || process.env.SMTP_USER
11
+ this.smtpPass = opts.smtpPass || process.env.SMTP_PASS
12
+ this.imapHost = opts.imapHost || process.env.IMAP_HOST
13
+ this._running = false
14
+ }
15
+ getRequiredEnv() { return ['SMTP_HOST', 'SMTP_USER', 'SMTP_PASS'] }
16
+ async start() {
17
+ if (!this.smtpHost || !this.smtpUser || !this.smtpPass) throw new Error('EmailAdapter: SMTP_HOST/USER/PASS required')
18
+ this._running = true
19
+ }
20
+ async stop() { this._running = false }
21
+ async send(reply) {
22
+ return new Promise((resolve, reject) => {
23
+ const sock = net.createConnection(this.smtpPort, this.smtpHost)
24
+ const lines = []
25
+ const send = s => sock.write(s + '\r\n')
26
+ sock.on('data', d => {
27
+ const text = d.toString()
28
+ lines.push(text)
29
+ const code = parseInt(text.slice(0, 3), 10)
30
+ if (code >= 400) { sock.end(); return reject(new Error('SMTP error: ' + text)) }
31
+ })
32
+ sock.on('error', reject)
33
+ sock.on('connect', () => {
34
+ send('EHLO freddie')
35
+ send('AUTH LOGIN')
36
+ send(Buffer.from(this.smtpUser).toString('base64'))
37
+ send(Buffer.from(this.smtpPass).toString('base64'))
38
+ send('MAIL FROM:<' + this.smtpUser + '>')
39
+ send('RCPT TO:<' + reply.to + '>')
40
+ send('DATA')
41
+ send('Subject: ' + (reply.subject || 'freddie'))
42
+ send('To: ' + reply.to)
43
+ send('')
44
+ send(reply.text)
45
+ send('.')
46
+ send('QUIT')
47
+ setTimeout(() => { sock.end(); resolve({ ok: true, log: lines.join('') }) }, 500)
48
+ })
49
+ })
50
+ }
51
+ }
@@ -0,0 +1,2 @@
1
+ import * as adapter from './handler.js'
2
+ export default { name: 'platform-email', surfaces: 'pi', register({ pi }) { pi.platforms.register({ name: 'email', module: adapter }) } }
@@ -0,0 +1,32 @@
1
+ import express from 'express'
2
+ import { EventEmitter } from 'node:events'
3
+
4
+ export class FeishuAdapter extends EventEmitter {
5
+ constructor(opts = {}) {
6
+ super()
7
+ this.platform = 'feishu'
8
+ this.token = opts.token || process.env.FEISHU_APP_TOKEN
9
+ this.port = opts.port || 0
10
+ this.api = opts.api || "https://open.feishu.cn/open-apis/im/v1/messages"
11
+ this._server = null
12
+ }
13
+ getRequiredEnv() { return ["FEISHU_APP_TOKEN"] }
14
+ async start() {
15
+ if (!this.token) throw new Error('FeishuAdapter: ' + this.getRequiredEnv().join(', ') + ' required')
16
+ const app = express()
17
+ app.use(express.json())
18
+ app.post('/webhook', (req, res) => {
19
+ const text = req.body?.text || req.body?.message?.text || req.body?.content || ''
20
+ const from = req.body?.from || req.body?.user_id || req.body?.sender_id || ''
21
+ this.emit('message', { from: String(from), text, raw: req.body })
22
+ res.json({ ok: true })
23
+ })
24
+ await new Promise(r => { this._server = app.listen(this.port, () => r()) })
25
+ this.port = this._server.address().port
26
+ }
27
+ async stop() { if (this._server) await new Promise(r => this._server.close(() => r())) }
28
+ async send(reply) {
29
+ if (!this.token) throw new Error('FeishuAdapter: token required')
30
+ return fetch(this.api, { method: 'POST', headers: { authorization: `Bearer ${this.token}`, 'content-type': 'application/json' }, body: JSON.stringify({ to: reply.to, text: reply.text }) }).then(r => r.json())
31
+ }
32
+ }
@@ -0,0 +1,2 @@
1
+ import * as adapter from './handler.js'
2
+ export default { name: 'platform-feishu', surfaces: 'pi', register({ pi }) { pi.platforms.register({ name: 'feishu', module: adapter }) } }
@@ -0,0 +1,12 @@
1
+ export async function listComments({ token, docToken }) {
2
+ const r = await fetch('https://open.feishu.cn/open-apis/comments/v1/files/' + docToken + '/comments', { headers: { authorization: 'Bearer ' + token } })
3
+ return await r.json()
4
+ }
5
+ export async function postComment({ token, docToken, content }) {
6
+ const r = await fetch('https://open.feishu.cn/open-apis/comments/v1/files/' + docToken + '/comments', { method: 'POST', headers: { authorization: 'Bearer ' + token, 'content-type': 'application/json' }, body: JSON.stringify({ comment: { content } }) })
7
+ return await r.json()
8
+ }
9
+ export async function resolveComment({ token, docToken, commentId }) {
10
+ const r = await fetch('https://open.feishu.cn/open-apis/comments/v1/files/' + docToken + '/comments/' + commentId + '/patch', { method: 'PATCH', headers: { authorization: 'Bearer ' + token, 'content-type': 'application/json' }, body: JSON.stringify({ is_solved: true }) })
11
+ return await r.json()
12
+ }
@@ -0,0 +1,2 @@
1
+ import * as adapter from './handler.js'
2
+ export default { name: 'platform-feishu_comment', surfaces: 'pi', register({ pi }) { pi.platforms.register({ name: 'feishu_comment', module: adapter }) } }
@@ -0,0 +1,11 @@
1
+ export const COMMENT_RULES = {
2
+ auto_reply_keywords: ['question', 'pls', 'help'],
3
+ skip_authors: [],
4
+ max_comment_age_hours: 168,
5
+ }
6
+ export function shouldAutoReply(comment, rules = COMMENT_RULES) {
7
+ const text = String(comment?.content || '').toLowerCase()
8
+ if (rules.skip_authors.includes(comment.author)) return false
9
+ if (rules.max_comment_age_hours && comment.created && (Date.now() - comment.created) > rules.max_comment_age_hours * 3600_000) return false
10
+ return rules.auto_reply_keywords.some(k => text.includes(k.toLowerCase()))
11
+ }
@@ -0,0 +1,2 @@
1
+ import * as adapter from './handler.js'
2
+ export default { name: 'platform-feishu_comment_rules', surfaces: 'pi', register({ pi }) { pi.platforms.register({ name: 'feishu_comment_rules', module: adapter }) } }
@@ -0,0 +1,32 @@
1
+ import express from 'express'
2
+ import { EventEmitter } from 'node:events'
3
+
4
+ export class HomeassistantAdapter extends EventEmitter {
5
+ constructor(opts = {}) {
6
+ super()
7
+ this.platform = 'homeassistant'
8
+ this.token = opts.token || process.env.HASS_TOKEN
9
+ this.port = opts.port || 0
10
+ this.api = opts.api || "http://homeassistant.local:8123/api/services/notify/notify"
11
+ this._server = null
12
+ }
13
+ getRequiredEnv() { return ["HASS_TOKEN"] }
14
+ async start() {
15
+ if (!this.token) throw new Error('HomeassistantAdapter: ' + this.getRequiredEnv().join(', ') + ' required')
16
+ const app = express()
17
+ app.use(express.json())
18
+ app.post('/webhook', (req, res) => {
19
+ const text = req.body?.text || req.body?.message?.text || req.body?.content || ''
20
+ const from = req.body?.from || req.body?.user_id || req.body?.sender_id || ''
21
+ this.emit('message', { from: String(from), text, raw: req.body })
22
+ res.json({ ok: true })
23
+ })
24
+ await new Promise(r => { this._server = app.listen(this.port, () => r()) })
25
+ this.port = this._server.address().port
26
+ }
27
+ async stop() { if (this._server) await new Promise(r => this._server.close(() => r())) }
28
+ async send(reply) {
29
+ if (!this.token) throw new Error('HomeassistantAdapter: token required')
30
+ return fetch(this.api, { method: 'POST', headers: { authorization: `Bearer ${this.token}`, 'content-type': 'application/json' }, body: JSON.stringify({ to: reply.to, text: reply.text }) }).then(r => r.json())
31
+ }
32
+ }
@@ -0,0 +1,2 @@
1
+ import * as adapter from './handler.js'
2
+ export default { name: 'platform-homeassistant', surfaces: 'pi', register({ pi }) { pi.platforms.register({ name: 'homeassistant', module: adapter }) } }
@@ -0,0 +1,40 @@
1
+ import { EventEmitter } from 'node:events'
2
+
3
+ export class MatrixAdapter extends EventEmitter {
4
+ constructor(opts = {}) {
5
+ super()
6
+ this.platform = 'matrix'
7
+ this.homeserver = opts.homeserver || process.env.MATRIX_HOMESERVER
8
+ this.token = opts.token || process.env.MATRIX_ACCESS_TOKEN
9
+ this.since = null
10
+ this._running = false
11
+ }
12
+ getRequiredEnv() { return ['MATRIX_HOMESERVER', 'MATRIX_ACCESS_TOKEN'] }
13
+ async start() {
14
+ if (!this.homeserver || !this.token) throw new Error('MatrixAdapter: MATRIX_HOMESERVER + MATRIX_ACCESS_TOKEN required')
15
+ this._running = true
16
+ this._loop()
17
+ }
18
+ async stop() { this._running = false }
19
+ async _loop() {
20
+ while (this._running) {
21
+ try {
22
+ const url = `${this.homeserver}/_matrix/client/v3/sync?timeout=25000${this.since ? '&since=' + this.since : ''}`
23
+ const res = await fetch(url, { headers: { authorization: `Bearer ${this.token}` } })
24
+ const data = await res.json()
25
+ this.since = data.next_batch
26
+ const rooms = data.rooms?.join || {}
27
+ for (const [roomId, room] of Object.entries(rooms)) {
28
+ for (const ev of (room.timeline?.events || [])) {
29
+ if (ev.type === 'm.room.message') this.emit('message', { from: roomId, text: ev.content?.body || '', user: ev.sender, raw: ev })
30
+ }
31
+ }
32
+ } catch (e) { await new Promise(r => setTimeout(r, 2000)) }
33
+ }
34
+ }
35
+ async send(reply) {
36
+ const txnId = Date.now() + '-' + Math.random().toString(36).slice(2, 8)
37
+ const url = `${this.homeserver}/_matrix/client/v3/rooms/${encodeURIComponent(reply.to)}/send/m.room.message/${txnId}`
38
+ return fetch(url, { method: 'PUT', headers: { authorization: `Bearer ${this.token}`, 'content-type': 'application/json' }, body: JSON.stringify({ msgtype: 'm.text', body: reply.text }) }).then(r => r.json())
39
+ }
40
+ }
@@ -0,0 +1,2 @@
1
+ import * as adapter from './handler.js'
2
+ export default { name: 'platform-matrix', surfaces: 'pi', register({ pi }) { pi.platforms.register({ name: 'matrix', module: adapter }) } }
@@ -0,0 +1,29 @@
1
+ import express from 'express'
2
+ import { EventEmitter } from 'node:events'
3
+
4
+ export class MattermostAdapter extends EventEmitter {
5
+ constructor(opts = {}) {
6
+ super()
7
+ this.platform = 'mattermost'
8
+ this.url = opts.url || process.env.MATTERMOST_URL
9
+ this.token = opts.token || process.env.MATTERMOST_TOKEN
10
+ this.port = opts.port || 0
11
+ this._server = null
12
+ }
13
+ getRequiredEnv() { return ['MATTERMOST_URL', 'MATTERMOST_TOKEN'] }
14
+ async start() {
15
+ if (!this.url || !this.token) throw new Error('MattermostAdapter: MATTERMOST_URL + MATTERMOST_TOKEN required')
16
+ const app = express()
17
+ app.use(express.urlencoded({ extended: true }))
18
+ app.post('/hook', (req, res) => {
19
+ this.emit('message', { from: req.body.channel_id, text: req.body.text || '', user: req.body.user_id, raw: req.body })
20
+ res.json({})
21
+ })
22
+ await new Promise(r => { this._server = app.listen(this.port, () => r()) })
23
+ this.port = this._server.address().port
24
+ }
25
+ async stop() { if (this._server) await new Promise(r => this._server.close(() => r())) }
26
+ async send(reply) {
27
+ return fetch(`${this.url}/api/v4/posts`, { method: 'POST', headers: { authorization: `Bearer ${this.token}`, 'content-type': 'application/json' }, body: JSON.stringify({ channel_id: reply.to, message: reply.text }) }).then(r => r.json())
28
+ }
29
+ }
@@ -0,0 +1,2 @@
1
+ import * as adapter from './handler.js'
2
+ export default { name: 'platform-mattermost', surfaces: 'pi', register({ pi }) { pi.platforms.register({ name: 'mattermost', module: adapter }) } }
@@ -0,0 +1,32 @@
1
+ import express from 'express'
2
+ import { EventEmitter } from 'node:events'
3
+
4
+ export class QqbotAdapter extends EventEmitter {
5
+ constructor(opts = {}) {
6
+ super()
7
+ this.platform = 'qqbot'
8
+ this.token = opts.token || process.env.QQBOT_TOKEN
9
+ this.port = opts.port || 0
10
+ this.api = opts.api || "https://api.sgroup.qq.com/channels/messages"
11
+ this._server = null
12
+ }
13
+ getRequiredEnv() { return ["QQBOT_TOKEN"] }
14
+ async start() {
15
+ if (!this.token) throw new Error('QqbotAdapter: ' + this.getRequiredEnv().join(', ') + ' required')
16
+ const app = express()
17
+ app.use(express.json())
18
+ app.post('/webhook', (req, res) => {
19
+ const text = req.body?.text || req.body?.message?.text || req.body?.content || ''
20
+ const from = req.body?.from || req.body?.user_id || req.body?.sender_id || ''
21
+ this.emit('message', { from: String(from), text, raw: req.body })
22
+ res.json({ ok: true })
23
+ })
24
+ await new Promise(r => { this._server = app.listen(this.port, () => r()) })
25
+ this.port = this._server.address().port
26
+ }
27
+ async stop() { if (this._server) await new Promise(r => this._server.close(() => r())) }
28
+ async send(reply) {
29
+ if (!this.token) throw new Error('QqbotAdapter: token required')
30
+ return fetch(this.api, { method: 'POST', headers: { authorization: `Bearer ${this.token}`, 'content-type': 'application/json' }, body: JSON.stringify({ to: reply.to, text: reply.text }) }).then(r => r.json())
31
+ }
32
+ }
@@ -0,0 +1,2 @@
1
+ import * as adapter from './handler.js'
2
+ export default { name: 'platform-qqbot', surfaces: 'pi', register({ pi }) { pi.platforms.register({ name: 'qqbot', module: adapter }) } }
@@ -0,0 +1,33 @@
1
+ import { EventEmitter } from 'node:events'
2
+
3
+ export class SignalAdapter extends EventEmitter {
4
+ constructor(opts = {}) {
5
+ super()
6
+ this.platform = 'signal'
7
+ this.api = opts.api || process.env.SIGNAL_CLI_URL || 'http://127.0.0.1:8080'
8
+ this.number = opts.number || process.env.SIGNAL_NUMBER
9
+ this._running = false
10
+ }
11
+ getRequiredEnv() { return ['SIGNAL_CLI_URL', 'SIGNAL_NUMBER'] }
12
+ async start() {
13
+ if (!this.number) throw new Error('SignalAdapter: SIGNAL_NUMBER required')
14
+ this._running = true
15
+ this._loop()
16
+ }
17
+ async stop() { this._running = false }
18
+ async _loop() {
19
+ while (this._running) {
20
+ try {
21
+ const res = await fetch(`${this.api}/v1/receive/${this.number}`)
22
+ const items = await res.json()
23
+ for (const it of items) {
24
+ const msg = it?.envelope?.dataMessage
25
+ if (msg) this.emit('message', { from: it.envelope.source, text: msg.message || '', raw: it })
26
+ }
27
+ } catch (e) { await new Promise(r => setTimeout(r, 2000)) }
28
+ }
29
+ }
30
+ async send(reply) {
31
+ return fetch(`${this.api}/v2/send`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ number: this.number, recipients: [reply.to], message: reply.text }) }).then(r => r.json())
32
+ }
33
+ }
@@ -0,0 +1,2 @@
1
+ import * as adapter from './handler.js'
2
+ export default { name: 'platform-signal', surfaces: 'pi', register({ pi }) { pi.platforms.register({ name: 'signal', module: adapter }) } }
@@ -0,0 +1,34 @@
1
+ import express from 'express'
2
+ import { EventEmitter } from 'node:events'
3
+
4
+ export class SlackAdapter extends EventEmitter {
5
+ constructor(opts = {}) {
6
+ super()
7
+ this.platform = 'slack'
8
+ this.token = opts.token || process.env.SLACK_BOT_TOKEN
9
+ this.signingSecret = opts.signingSecret || process.env.SLACK_SIGNING_SECRET
10
+ this.port = opts.port || 0
11
+ this.path = opts.path || '/slack/events'
12
+ this.api = opts.api || 'https://slack.com/api'
13
+ this._server = null
14
+ }
15
+ getRequiredEnv() { return ['SLACK_BOT_TOKEN', 'SLACK_SIGNING_SECRET'] }
16
+ async start() {
17
+ if (!this.token) throw new Error('SlackAdapter: SLACK_BOT_TOKEN required')
18
+ const app = express()
19
+ app.use(express.json())
20
+ app.post(this.path, (req, res) => {
21
+ if (req.body?.type === 'url_verification') return res.json({ challenge: req.body.challenge })
22
+ const ev = req.body?.event
23
+ if (ev?.type === 'message' && !ev.bot_id) this.emit('message', { from: ev.channel, text: ev.text || '', user: ev.user, raw: req.body })
24
+ res.json({ ok: true })
25
+ })
26
+ await new Promise(r => { this._server = app.listen(this.port, () => r()) })
27
+ this.port = this._server.address().port
28
+ }
29
+ async stop() { if (this._server) await new Promise(r => this._server.close(() => r())) }
30
+ async send(reply) {
31
+ if (!this.token) throw new Error('SlackAdapter: token required')
32
+ return fetch(`${this.api}/chat.postMessage`, { method: 'POST', headers: { authorization: `Bearer ${this.token}`, 'content-type': 'application/json' }, body: JSON.stringify({ channel: reply.to, text: reply.text }) }).then(r => r.json())
33
+ }
34
+ }
@@ -0,0 +1,2 @@
1
+ import * as adapter from './handler.js'
2
+ export default { name: 'platform-slack', surfaces: 'pi', register({ pi }) { pi.platforms.register({ name: 'slack', module: adapter }) } }
@@ -0,0 +1,34 @@
1
+ import express from 'express'
2
+ import { EventEmitter } from 'node:events'
3
+
4
+ export class SmsAdapter extends EventEmitter {
5
+ constructor(opts = {}) {
6
+ super()
7
+ this.platform = 'sms'
8
+ this.sid = opts.sid || process.env.TWILIO_SID
9
+ this.token = opts.token || process.env.TWILIO_TOKEN
10
+ this.from = opts.from || process.env.TWILIO_FROM
11
+ this.port = opts.port || 0
12
+ this._server = null
13
+ }
14
+ getRequiredEnv() { return ['TWILIO_SID', 'TWILIO_TOKEN', 'TWILIO_FROM'] }
15
+ async start() {
16
+ if (!this.sid || !this.token || !this.from) throw new Error('SmsAdapter: TWILIO_SID/TOKEN/FROM required')
17
+ const app = express()
18
+ app.use(express.urlencoded({ extended: true }))
19
+ app.post('/sms', (req, res) => {
20
+ this.emit('message', { from: req.body.From, text: req.body.Body || '', raw: req.body })
21
+ res.set('content-type', 'text/xml')
22
+ res.send('<Response/>')
23
+ })
24
+ await new Promise(r => { this._server = app.listen(this.port, () => r()) })
25
+ this.port = this._server.address().port
26
+ }
27
+ async stop() { if (this._server) await new Promise(r => this._server.close(() => r())) }
28
+ async send(reply) {
29
+ const url = `https://api.twilio.com/2010-04-01/Accounts/${this.sid}/Messages.json`
30
+ const body = new URLSearchParams({ To: reply.to, From: this.from, Body: reply.text }).toString()
31
+ const auth = 'Basic ' + Buffer.from(`${this.sid}:${this.token}`).toString('base64')
32
+ return fetch(url, { method: 'POST', headers: { authorization: auth, 'content-type': 'application/x-www-form-urlencoded' }, body }).then(r => r.json())
33
+ }
34
+ }
@@ -0,0 +1,2 @@
1
+ import * as adapter from './handler.js'
2
+ export default { name: 'platform-sms', surfaces: 'pi', register({ pi }) { pi.platforms.register({ name: 'sms', module: adapter }) } }
@@ -0,0 +1,38 @@
1
+ import { EventEmitter } from 'node:events'
2
+
3
+ export class TelegramAdapter extends EventEmitter {
4
+ constructor(opts = {}) {
5
+ super()
6
+ this.platform = 'telegram'
7
+ this.token = opts.token || process.env.TELEGRAM_BOT_TOKEN
8
+ this.api = opts.api || 'https://api.telegram.org'
9
+ this.offset = 0
10
+ this._running = false
11
+ this._poll = null
12
+ }
13
+ getRequiredEnv() { return ['TELEGRAM_BOT_TOKEN'] }
14
+ async start() {
15
+ if (!this.token) throw new Error('TelegramAdapter: TELEGRAM_BOT_TOKEN required')
16
+ this._running = true
17
+ this._loop()
18
+ }
19
+ async stop() { this._running = false; if (this._poll) clearTimeout(this._poll) }
20
+ async _loop() {
21
+ while (this._running) {
22
+ try {
23
+ const url = `${this.api}/bot${this.token}/getUpdates?timeout=25&offset=${this.offset + 1}`
24
+ const res = await fetch(url)
25
+ const data = await res.json()
26
+ if (data.ok) for (const u of data.result) {
27
+ this.offset = Math.max(this.offset, u.update_id)
28
+ if (u.message) this.emit('message', { from: String(u.message.from?.id || ''), text: u.message.text || '', raw: u })
29
+ }
30
+ } catch (e) { await new Promise(r => setTimeout(r, 5000)) }
31
+ }
32
+ }
33
+ async send(reply) {
34
+ if (!this.token) throw new Error('TelegramAdapter: token required')
35
+ const url = `${this.api}/bot${this.token}/sendMessage`
36
+ return fetch(url, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ chat_id: reply.to, text: reply.text }) }).then(r => r.json())
37
+ }
38
+ }
@@ -0,0 +1,2 @@
1
+ import * as adapter from './handler.js'
2
+ export default { name: 'platform-telegram', surfaces: 'pi', register({ pi }) { pi.platforms.register({ name: 'telegram', module: adapter }) } }
@@ -0,0 +1,17 @@
1
+ export function tgFloodWait(error) {
2
+ const m = String(error?.message || '').match(/FLOOD_WAIT_(\d+)/)
3
+ return m ? Number(m[1]) * 1000 : null
4
+ }
5
+ export async function tgWithRetry(fn, { attempts = 3 } = {}) {
6
+ let last
7
+ for (let i = 0; i < attempts; i++) {
8
+ try { return await fn() } catch (e) {
9
+ last = e
10
+ const wait = tgFloodWait(e)
11
+ if (wait != null) await new Promise(r => setTimeout(r, wait + 100))
12
+ else if (i < attempts - 1) await new Promise(r => setTimeout(r, 500 * (i + 1)))
13
+ }
14
+ }
15
+ throw last
16
+ }
17
+ export function withProxy(fetchOpts, proxy) { return proxy ? { ...fetchOpts, proxy } : fetchOpts }
@@ -0,0 +1,2 @@
1
+ import * as adapter from './handler.js'
2
+ export default { name: 'platform-telegram_network', surfaces: 'pi', register({ pi }) { pi.platforms.register({ name: 'telegram_network', module: adapter }) } }
@@ -0,0 +1,19 @@
1
+ import express from 'express'
2
+ import { EventEmitter } from 'node:events'
3
+
4
+ export class WebhookAdapter extends EventEmitter {
5
+ constructor({ port = 0, path = '/webhook' } = {}) { super(); this.port = port; this.path = path; this._server = null; this.outbox = [] }
6
+ async start() {
7
+ const app = express()
8
+ app.use(express.json())
9
+ app.post(this.path, (req, res) => {
10
+ const m = { from: req.body?.from || 'webhook', text: req.body?.text || '', raw: req.body }
11
+ this.emit('message', m)
12
+ res.json({ ok: true })
13
+ })
14
+ await new Promise(resolve => { this._server = app.listen(this.port, () => resolve()) })
15
+ this.port = this._server.address().port
16
+ }
17
+ async stop() { if (this._server) await new Promise(r => this._server.close(() => r())) }
18
+ async send(reply) { this.outbox.push(reply) }
19
+ }
@@ -0,0 +1,2 @@
1
+ import * as adapter from './handler.js'
2
+ export default { name: 'platform-webhook', surfaces: 'pi', register({ pi }) { pi.platforms.register({ name: 'webhook', module: adapter }) } }
@@ -0,0 +1,32 @@
1
+ import express from 'express'
2
+ import { EventEmitter } from 'node:events'
3
+
4
+ export class WecomAdapter extends EventEmitter {
5
+ constructor(opts = {}) {
6
+ super()
7
+ this.platform = 'wecom'
8
+ this.token = opts.token || process.env.WECOM_WEBHOOK_KEY
9
+ this.port = opts.port || 0
10
+ this.api = opts.api || "https://qyapi.weixin.qq.com/cgi-bin/webhook/send"
11
+ this._server = null
12
+ }
13
+ getRequiredEnv() { return ["WECOM_WEBHOOK_KEY"] }
14
+ async start() {
15
+ if (!this.token) throw new Error('WecomAdapter: ' + this.getRequiredEnv().join(', ') + ' required')
16
+ const app = express()
17
+ app.use(express.json())
18
+ app.post('/webhook', (req, res) => {
19
+ const text = req.body?.text || req.body?.message?.text || req.body?.content || ''
20
+ const from = req.body?.from || req.body?.user_id || req.body?.sender_id || ''
21
+ this.emit('message', { from: String(from), text, raw: req.body })
22
+ res.json({ ok: true })
23
+ })
24
+ await new Promise(r => { this._server = app.listen(this.port, () => r()) })
25
+ this.port = this._server.address().port
26
+ }
27
+ async stop() { if (this._server) await new Promise(r => this._server.close(() => r())) }
28
+ async send(reply) {
29
+ if (!this.token) throw new Error('WecomAdapter: token required')
30
+ return fetch(this.api, { method: 'POST', headers: { authorization: `Bearer ${this.token}`, 'content-type': 'application/json' }, body: JSON.stringify({ to: reply.to, text: reply.text }) }).then(r => r.json())
31
+ }
32
+ }
@@ -0,0 +1,2 @@
1
+ import * as adapter from './handler.js'
2
+ export default { name: 'platform-wecom', surfaces: 'pi', register({ pi }) { pi.platforms.register({ name: 'wecom', module: adapter }) } }
@@ -0,0 +1,15 @@
1
+ import express from 'express'
2
+ import { EventEmitter } from 'node:events'
3
+ export class WecomCallbackAdapter extends EventEmitter {
4
+ constructor(opts = {}) { super(); this.platform = 'wecom_callback'; this.token = opts.token || process.env.WECOM_CALLBACK_TOKEN; this.aesKey = opts.aesKey || process.env.WECOM_ENCODING_AES_KEY; this.port = opts.port || 0 }
5
+ getRequiredEnv() { return ['WECOM_CALLBACK_TOKEN'] }
6
+ async start() {
7
+ if (!this.token) throw new Error('WECOM_CALLBACK_TOKEN required')
8
+ const app = express(); app.use(express.text({ type: '*/*' }))
9
+ app.post('/wecom/callback', (req, res) => { this.emit('message', { from: 'wecom', text: req.body || '', raw: req.body }); res.send('') })
10
+ this._server = await new Promise(r => { const s = app.listen(this.port, () => r(s)) })
11
+ this.port = this._server.address().port
12
+ }
13
+ async stop() { if (this._server) await new Promise(r => this._server.close(() => r())) }
14
+ async send() { return { error: 'wecom_callback is receive-only; use wecom adapter for outbound' } }
15
+ }
@@ -0,0 +1,2 @@
1
+ import * as adapter from './handler.js'
2
+ export default { name: 'platform-wecom_callback', surfaces: 'pi', register({ pi }) { pi.platforms.register({ name: 'wecom_callback', module: adapter }) } }
@@ -0,0 +1,16 @@
1
+ import crypto from 'node:crypto'
2
+ export function sha1(...parts) { return crypto.createHash('sha1').update(parts.sort().join('')).digest('hex') }
3
+ export function verifyMsgSignature({ token, timestamp, nonce, encrypt, signature }) {
4
+ return sha1(token, timestamp, nonce, encrypt) === signature
5
+ }
6
+ export function decryptMsg(encryptedB64, encodingAesKey) {
7
+ const aesKey = Buffer.from(encodingAesKey + '=', 'base64')
8
+ const iv = aesKey.slice(0, 16)
9
+ const decipher = crypto.createDecipheriv('aes-256-cbc', aesKey, iv)
10
+ decipher.setAutoPadding(false)
11
+ const decrypted = Buffer.concat([decipher.update(Buffer.from(encryptedB64, 'base64')), decipher.final()])
12
+ const padLen = decrypted[decrypted.length - 1]
13
+ const trimmed = decrypted.slice(16, decrypted.length - padLen)
14
+ const xmlLen = trimmed.readUInt32BE(0)
15
+ return { xml: trimmed.slice(4, 4 + xmlLen).toString('utf8'), corpId: trimmed.slice(4 + xmlLen).toString('utf8') }
16
+ }
@@ -0,0 +1,2 @@
1
+ import * as adapter from './handler.js'
2
+ export default { name: 'platform-wecom_crypto', surfaces: 'pi', register({ pi }) { pi.platforms.register({ name: 'wecom_crypto', module: adapter }) } }
@@ -0,0 +1,32 @@
1
+ import express from 'express'
2
+ import { EventEmitter } from 'node:events'
3
+
4
+ export class WeixinAdapter extends EventEmitter {
5
+ constructor(opts = {}) {
6
+ super()
7
+ this.platform = 'weixin'
8
+ this.token = opts.token || process.env.WEIXIN_TOKEN
9
+ this.port = opts.port || 0
10
+ this.api = opts.api || "https://api.weixin.qq.com/cgi-bin/message/send"
11
+ this._server = null
12
+ }
13
+ getRequiredEnv() { return ["WEIXIN_TOKEN"] }
14
+ async start() {
15
+ if (!this.token) throw new Error('WeixinAdapter: ' + this.getRequiredEnv().join(', ') + ' required')
16
+ const app = express()
17
+ app.use(express.json())
18
+ app.post('/webhook', (req, res) => {
19
+ const text = req.body?.text || req.body?.message?.text || req.body?.content || ''
20
+ const from = req.body?.from || req.body?.user_id || req.body?.sender_id || ''
21
+ this.emit('message', { from: String(from), text, raw: req.body })
22
+ res.json({ ok: true })
23
+ })
24
+ await new Promise(r => { this._server = app.listen(this.port, () => r()) })
25
+ this.port = this._server.address().port
26
+ }
27
+ async stop() { if (this._server) await new Promise(r => this._server.close(() => r())) }
28
+ async send(reply) {
29
+ if (!this.token) throw new Error('WeixinAdapter: token required')
30
+ return fetch(this.api, { method: 'POST', headers: { authorization: `Bearer ${this.token}`, 'content-type': 'application/json' }, body: JSON.stringify({ to: reply.to, text: reply.text }) }).then(r => r.json())
31
+ }
32
+ }
@@ -0,0 +1,2 @@
1
+ import * as adapter from './handler.js'
2
+ export default { name: 'platform-weixin', surfaces: 'pi', register({ pi }) { pi.platforms.register({ name: 'weixin', module: adapter }) } }