agent-messenger 2.2.0 → 2.4.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 (229) hide show
  1. package/.claude-plugin/README.md +16 -16
  2. package/.claude-plugin/marketplace.json +29 -29
  3. package/.claude-plugin/plugin.json +5 -5
  4. package/CONTRIBUTING.md +1 -1
  5. package/README.md +9 -6
  6. package/bun.lock +89 -105
  7. package/bunfig.toml +3 -0
  8. package/dist/package.json +13 -3
  9. package/dist/src/platforms/discordbot/client.js +2 -2
  10. package/dist/src/platforms/discordbot/client.js.map +1 -1
  11. package/dist/src/platforms/kakaotalk/cli.d.ts.map +1 -1
  12. package/dist/src/platforms/kakaotalk/cli.js +2 -1
  13. package/dist/src/platforms/kakaotalk/cli.js.map +1 -1
  14. package/dist/src/platforms/kakaotalk/client.d.ts +2 -1
  15. package/dist/src/platforms/kakaotalk/client.d.ts.map +1 -1
  16. package/dist/src/platforms/kakaotalk/client.js +52 -2
  17. package/dist/src/platforms/kakaotalk/client.js.map +1 -1
  18. package/dist/src/platforms/kakaotalk/commands/index.d.ts +1 -0
  19. package/dist/src/platforms/kakaotalk/commands/index.d.ts.map +1 -1
  20. package/dist/src/platforms/kakaotalk/commands/index.js +1 -0
  21. package/dist/src/platforms/kakaotalk/commands/index.js.map +1 -1
  22. package/dist/src/platforms/kakaotalk/commands/profile.d.ts +3 -0
  23. package/dist/src/platforms/kakaotalk/commands/profile.d.ts.map +1 -0
  24. package/dist/src/platforms/kakaotalk/commands/profile.js +19 -0
  25. package/dist/src/platforms/kakaotalk/commands/profile.js.map +1 -0
  26. package/dist/src/platforms/kakaotalk/index.d.ts +2 -2
  27. package/dist/src/platforms/kakaotalk/index.d.ts.map +1 -1
  28. package/dist/src/platforms/kakaotalk/index.js +1 -1
  29. package/dist/src/platforms/kakaotalk/index.js.map +1 -1
  30. package/dist/src/platforms/kakaotalk/protocol/session.d.ts.map +1 -1
  31. package/dist/src/platforms/kakaotalk/protocol/session.js +2 -1
  32. package/dist/src/platforms/kakaotalk/protocol/session.js.map +1 -1
  33. package/dist/src/platforms/kakaotalk/types.d.ts +16 -0
  34. package/dist/src/platforms/kakaotalk/types.d.ts.map +1 -1
  35. package/dist/src/platforms/kakaotalk/types.js +8 -0
  36. package/dist/src/platforms/kakaotalk/types.js.map +1 -1
  37. package/dist/src/platforms/line/commands/auth.d.ts.map +1 -1
  38. package/dist/src/platforms/line/commands/auth.js +32 -20
  39. package/dist/src/platforms/line/commands/auth.js.map +1 -1
  40. package/dist/src/platforms/teams/commands/reaction.d.ts.map +1 -1
  41. package/dist/src/platforms/teams/commands/reaction.js +2 -0
  42. package/dist/src/platforms/teams/commands/reaction.js.map +1 -1
  43. package/dist/src/platforms/webex/client.d.ts +2 -0
  44. package/dist/src/platforms/webex/client.d.ts.map +1 -1
  45. package/dist/src/platforms/webex/client.js +66 -23
  46. package/dist/src/platforms/webex/client.js.map +1 -1
  47. package/dist/src/platforms/webex/commands/auth.d.ts.map +1 -1
  48. package/dist/src/platforms/webex/commands/auth.js +4 -0
  49. package/dist/src/platforms/webex/commands/auth.js.map +1 -1
  50. package/dist/src/platforms/webex/encryption.d.ts +10 -0
  51. package/dist/src/platforms/webex/encryption.d.ts.map +1 -0
  52. package/dist/src/platforms/webex/encryption.js +49 -0
  53. package/dist/src/platforms/webex/encryption.js.map +1 -0
  54. package/dist/src/platforms/webex/ensure-auth.d.ts.map +1 -1
  55. package/dist/src/platforms/webex/ensure-auth.js +4 -0
  56. package/dist/src/platforms/webex/ensure-auth.js.map +1 -1
  57. package/dist/src/platforms/webex/token-extractor.d.ts +6 -5
  58. package/dist/src/platforms/webex/token-extractor.d.ts.map +1 -1
  59. package/dist/src/platforms/webex/token-extractor.js +92 -43
  60. package/dist/src/platforms/webex/token-extractor.js.map +1 -1
  61. package/dist/src/platforms/webex/types.d.ts +4 -0
  62. package/dist/src/platforms/webex/types.d.ts.map +1 -1
  63. package/dist/src/platforms/webex/types.js +2 -0
  64. package/dist/src/platforms/webex/types.js.map +1 -1
  65. package/dist/src/platforms/wechatbot/cli.d.ts +5 -0
  66. package/dist/src/platforms/wechatbot/cli.d.ts.map +1 -0
  67. package/dist/src/platforms/wechatbot/cli.js +18 -0
  68. package/dist/src/platforms/wechatbot/cli.js.map +1 -0
  69. package/dist/src/platforms/wechatbot/client.d.ts +36 -0
  70. package/dist/src/platforms/wechatbot/client.d.ts.map +1 -0
  71. package/dist/src/platforms/wechatbot/client.js +208 -0
  72. package/dist/src/platforms/wechatbot/client.js.map +1 -0
  73. package/dist/src/platforms/wechatbot/commands/auth.d.ts +28 -0
  74. package/dist/src/platforms/wechatbot/commands/auth.d.ts.map +1 -0
  75. package/dist/src/platforms/wechatbot/commands/auth.js +164 -0
  76. package/dist/src/platforms/wechatbot/commands/auth.js.map +1 -0
  77. package/dist/src/platforms/wechatbot/commands/index.d.ts +5 -0
  78. package/dist/src/platforms/wechatbot/commands/index.d.ts.map +1 -0
  79. package/dist/src/platforms/wechatbot/commands/index.js +5 -0
  80. package/dist/src/platforms/wechatbot/commands/index.js.map +1 -0
  81. package/dist/src/platforms/wechatbot/commands/message.d.ts +18 -0
  82. package/dist/src/platforms/wechatbot/commands/message.d.ts.map +1 -0
  83. package/dist/src/platforms/wechatbot/commands/message.js +80 -0
  84. package/dist/src/platforms/wechatbot/commands/message.js.map +1 -0
  85. package/dist/src/platforms/wechatbot/commands/shared.d.ts +9 -0
  86. package/dist/src/platforms/wechatbot/commands/shared.d.ts.map +1 -0
  87. package/dist/src/platforms/wechatbot/commands/shared.js +13 -0
  88. package/dist/src/platforms/wechatbot/commands/shared.js.map +1 -0
  89. package/dist/src/platforms/wechatbot/commands/template.d.ts +19 -0
  90. package/dist/src/platforms/wechatbot/commands/template.d.ts.map +1 -0
  91. package/dist/src/platforms/wechatbot/commands/template.js +76 -0
  92. package/dist/src/platforms/wechatbot/commands/template.js.map +1 -0
  93. package/dist/src/platforms/wechatbot/commands/user.d.ts +20 -0
  94. package/dist/src/platforms/wechatbot/commands/user.d.ts.map +1 -0
  95. package/dist/src/platforms/wechatbot/commands/user.js +53 -0
  96. package/dist/src/platforms/wechatbot/commands/user.js.map +1 -0
  97. package/dist/src/platforms/wechatbot/credential-manager.d.ts +17 -0
  98. package/dist/src/platforms/wechatbot/credential-manager.d.ts.map +1 -0
  99. package/dist/src/platforms/wechatbot/credential-manager.js +121 -0
  100. package/dist/src/platforms/wechatbot/credential-manager.js.map +1 -0
  101. package/dist/src/platforms/wechatbot/index.d.ts +5 -0
  102. package/dist/src/platforms/wechatbot/index.d.ts.map +1 -0
  103. package/dist/src/platforms/wechatbot/index.js +4 -0
  104. package/dist/src/platforms/wechatbot/index.js.map +1 -0
  105. package/dist/src/platforms/wechatbot/types.d.ts +94 -0
  106. package/dist/src/platforms/wechatbot/types.d.ts.map +1 -0
  107. package/dist/src/platforms/wechatbot/types.js +54 -0
  108. package/dist/src/platforms/wechatbot/types.js.map +1 -0
  109. package/dist/src/platforms/whatsapp/client.d.ts +1 -0
  110. package/dist/src/platforms/whatsapp/client.d.ts.map +1 -1
  111. package/dist/src/platforms/whatsapp/client.js +27 -13
  112. package/dist/src/platforms/whatsapp/client.js.map +1 -1
  113. package/dist/src/platforms/whatsapp/commands/auth.d.ts.map +1 -1
  114. package/dist/src/platforms/whatsapp/commands/auth.js +21 -18
  115. package/dist/src/platforms/whatsapp/commands/auth.js.map +1 -1
  116. package/dist/src/platforms/whatsapp/credential-manager.d.ts.map +1 -1
  117. package/dist/src/platforms/whatsapp/credential-manager.js +14 -8
  118. package/dist/src/platforms/whatsapp/credential-manager.js.map +1 -1
  119. package/docs/content/docs/agent-skills.mdx +4 -4
  120. package/docs/content/docs/cli/channeltalk.mdx +1 -1
  121. package/docs/content/docs/cli/channeltalkbot.mdx +1 -1
  122. package/docs/content/docs/cli/discord.mdx +1 -1
  123. package/docs/content/docs/cli/discordbot.mdx +1 -1
  124. package/docs/content/docs/cli/instagram.mdx +1 -1
  125. package/docs/content/docs/cli/kakaotalk.mdx +1 -1
  126. package/docs/content/docs/cli/line.mdx +1 -1
  127. package/docs/content/docs/cli/meta.json +1 -0
  128. package/docs/content/docs/cli/slack.mdx +1 -1
  129. package/docs/content/docs/cli/slackbot.mdx +1 -1
  130. package/docs/content/docs/cli/teams.mdx +1 -1
  131. package/docs/content/docs/cli/webex.mdx +5 -3
  132. package/docs/content/docs/cli/wechatbot.mdx +179 -0
  133. package/docs/content/docs/cli/whatsapp.mdx +1 -1
  134. package/docs/content/docs/cli/whatsappbot.mdx +1 -1
  135. package/docs/content/docs/sdk/meta.json +1 -1
  136. package/docs/content/docs/sdk/wechatbot.mdx +282 -0
  137. package/docs/content/docs/tui.mdx +1 -1
  138. package/docs/src/app/page.tsx +5 -5
  139. package/package.json +13 -3
  140. package/skills/agent-channeltalk/SKILL.md +1 -1
  141. package/skills/agent-channeltalkbot/SKILL.md +1 -1
  142. package/skills/agent-discord/SKILL.md +1 -1
  143. package/skills/agent-discordbot/SKILL.md +1 -1
  144. package/skills/agent-instagram/SKILL.md +1 -1
  145. package/skills/agent-kakaotalk/SKILL.md +24 -1
  146. package/skills/agent-line/SKILL.md +7 -11
  147. package/skills/agent-line/references/authentication.md +13 -4
  148. package/skills/agent-slack/SKILL.md +1 -1
  149. package/skills/agent-slackbot/SKILL.md +1 -1
  150. package/skills/agent-teams/SKILL.md +1 -1
  151. package/skills/agent-telegram/SKILL.md +1 -1
  152. package/skills/agent-webex/SKILL.md +1 -1
  153. package/skills/agent-webex/references/authentication.md +4 -3
  154. package/skills/agent-webex/references/common-patterns.md +1 -1
  155. package/skills/agent-wechatbot/SKILL.md +385 -0
  156. package/skills/agent-whatsapp/SKILL.md +12 -1
  157. package/skills/agent-whatsappbot/SKILL.md +1 -1
  158. package/src/platforms/discord/credential-manager.test.ts +18 -1
  159. package/src/platforms/discordbot/client.ts +2 -2
  160. package/src/platforms/instagram/commands/auth.test.ts +216 -0
  161. package/src/platforms/instagram/commands/chat.test.ts +127 -0
  162. package/src/platforms/instagram/commands/message.test.ts +178 -0
  163. package/src/platforms/kakaotalk/cli.ts +2 -1
  164. package/src/platforms/kakaotalk/client.test.ts +157 -0
  165. package/src/platforms/kakaotalk/client.ts +57 -3
  166. package/src/platforms/kakaotalk/commands/auth.test.ts +299 -0
  167. package/src/platforms/kakaotalk/commands/chat.test.ts +97 -0
  168. package/src/platforms/kakaotalk/commands/index.ts +1 -0
  169. package/src/platforms/kakaotalk/commands/message.test.ts +113 -0
  170. package/src/platforms/kakaotalk/commands/profile.test.ts +84 -0
  171. package/src/platforms/kakaotalk/commands/profile.ts +21 -0
  172. package/src/platforms/kakaotalk/index.test.ts +5 -0
  173. package/src/platforms/kakaotalk/index.ts +2 -0
  174. package/src/platforms/kakaotalk/protocol/session.ts +2 -0
  175. package/src/platforms/kakaotalk/types.ts +18 -0
  176. package/src/platforms/line/commands/auth.test.ts +141 -0
  177. package/src/platforms/line/commands/auth.ts +28 -19
  178. package/src/platforms/line/commands/chat.test.ts +110 -0
  179. package/src/platforms/line/commands/friend.test.ts +98 -0
  180. package/src/platforms/line/commands/message.test.ts +119 -0
  181. package/src/platforms/line/commands/profile.test.ts +85 -0
  182. package/src/platforms/slackbot/commands/channel.test.ts +139 -0
  183. package/src/platforms/slackbot/commands/message.test.ts +226 -0
  184. package/src/platforms/slackbot/commands/reaction.test.ts +90 -0
  185. package/src/platforms/slackbot/commands/user.test.ts +143 -0
  186. package/src/platforms/teams/commands/reaction.test.ts +45 -61
  187. package/src/platforms/teams/commands/reaction.ts +2 -0
  188. package/src/platforms/telegram/commands/chat.test.ts +125 -0
  189. package/src/platforms/telegram/commands/message.test.ts +92 -0
  190. package/src/platforms/webex/client.ts +98 -26
  191. package/src/platforms/webex/commands/auth.ts +4 -0
  192. package/src/platforms/webex/commands/member.test.ts +65 -58
  193. package/src/platforms/webex/commands/message.test.ts +78 -121
  194. package/src/platforms/webex/commands/snapshot.test.ts +59 -46
  195. package/src/platforms/webex/commands/space.test.ts +49 -48
  196. package/src/platforms/webex/encryption.ts +53 -0
  197. package/src/platforms/webex/ensure-auth.ts +4 -0
  198. package/src/platforms/webex/token-extractor.ts +107 -40
  199. package/src/platforms/webex/types.ts +4 -0
  200. package/src/platforms/webex/typings/node-jose.d.ts +27 -0
  201. package/src/platforms/wechatbot/cli.ts +24 -0
  202. package/src/platforms/wechatbot/client.test.ts +497 -0
  203. package/src/platforms/wechatbot/client.ts +268 -0
  204. package/src/platforms/wechatbot/commands/auth.test.ts +211 -0
  205. package/src/platforms/wechatbot/commands/auth.ts +203 -0
  206. package/src/platforms/wechatbot/commands/index.ts +4 -0
  207. package/src/platforms/wechatbot/commands/message.test.ts +155 -0
  208. package/src/platforms/wechatbot/commands/message.ts +104 -0
  209. package/src/platforms/wechatbot/commands/shared.ts +22 -0
  210. package/src/platforms/wechatbot/commands/template.test.ts +199 -0
  211. package/src/platforms/wechatbot/commands/template.ts +102 -0
  212. package/src/platforms/wechatbot/commands/user.test.ts +165 -0
  213. package/src/platforms/wechatbot/commands/user.ts +75 -0
  214. package/src/platforms/wechatbot/credential-manager.test.ts +255 -0
  215. package/src/platforms/wechatbot/credential-manager.ts +148 -0
  216. package/src/platforms/wechatbot/index.test.ts +49 -0
  217. package/src/platforms/wechatbot/index.ts +19 -0
  218. package/src/platforms/wechatbot/types.test.ts +223 -0
  219. package/src/platforms/wechatbot/types.ts +107 -0
  220. package/src/platforms/whatsapp/client.ts +24 -13
  221. package/src/platforms/whatsapp/commands/auth.test.ts +311 -0
  222. package/src/platforms/whatsapp/commands/auth.ts +21 -17
  223. package/src/platforms/whatsapp/commands/chat.test.ts +198 -0
  224. package/src/platforms/whatsapp/commands/message.test.ts +231 -0
  225. package/src/platforms/whatsapp/credential-manager.test.ts +20 -0
  226. package/src/platforms/whatsapp/credential-manager.ts +17 -8
  227. package/src/platforms/whatsappbot/commands/auth.test.ts +217 -0
  228. package/src/platforms/whatsappbot/commands/message.test.ts +198 -0
  229. package/src/platforms/whatsappbot/commands/template.test.ts +112 -0
@@ -0,0 +1,385 @@
1
+ ---
2
+ name: agent-wechatbot
3
+ description: Interact with WeChat Official Account using API credentials - send messages, manage templates, list followers
4
+ version: 2.4.0
5
+ allowed-tools: Bash(agent-wechatbot:*)
6
+ metadata:
7
+ openclaw:
8
+ requires:
9
+ bins:
10
+ - agent-wechatbot
11
+ install:
12
+ - kind: node
13
+ package: agent-messenger
14
+ bins: [agent-wechatbot]
15
+ ---
16
+
17
+ # Agent WeChatBot
18
+
19
+ A TypeScript CLI tool that enables AI agents and humans to send messages through WeChat Official Account API. Designed for customer engagement, template notifications, and CI/CD integrations using App ID + App Secret authentication.
20
+
21
+ ## Key Concepts
22
+
23
+ Before diving in, a few things about WeChat Official Account API:
24
+
25
+ - **Send-only** — The Official Account API delivers inbound messages via webhooks only. This CLI cannot list or read received messages.
26
+ - **Customer service messages** — You can send free-form text, image, and news (article) messages to users who have interacted with your account within the last 48 hours.
27
+ - **Template messages** — Pre-approved message templates can be sent at any time. Templates must be created and approved in the WeChat Official Account admin panel.
28
+ - **App ID** — Your Official Account's unique application identifier. Found in the WeChat Official Account admin panel under Development > Basic Configuration.
29
+ - **App Secret** — Your application's secret key, paired with the App ID. Found in the same location.
30
+ - **OpenID** — Each follower has a unique OpenID scoped to your Official Account. Use `user list` to retrieve follower OpenIDs.
31
+ - **IP Whitelist** — Your server IP must be added to the Official Account's IP whitelist, or API calls will fail with error `40164`.
32
+ - **Rate limits** — WeChat enforces API call frequency limits. Customer service messages are limited per account per day.
33
+
34
+ ## Quick Start
35
+
36
+ ```bash
37
+ # Set your API credentials
38
+ agent-wechatbot auth set your-app-id your-app-secret
39
+
40
+ # Verify authentication
41
+ agent-wechatbot auth status
42
+
43
+ # Send a text message (recipient must have interacted within 48h)
44
+ agent-wechatbot message send oXXXXXXXXXXXXXXX "Hello from the CLI!"
45
+
46
+ # List available templates
47
+ agent-wechatbot template list --pretty
48
+
49
+ # List followers
50
+ agent-wechatbot user list --pretty
51
+ ```
52
+
53
+ ## Authentication
54
+
55
+ ### API Credential Setup
56
+
57
+ agent-wechatbot uses App ID + App Secret pairs from the WeChat Official Account admin panel:
58
+
59
+ ```bash
60
+ # Set credentials (validates against WeChat API before saving)
61
+ agent-wechatbot auth set your-app-id your-app-secret
62
+
63
+ # Check auth status
64
+ agent-wechatbot auth status
65
+
66
+ # Clear stored credentials
67
+ agent-wechatbot auth clear
68
+ ```
69
+
70
+ ### Multi-Account Management
71
+
72
+ ```bash
73
+ # List stored accounts
74
+ agent-wechatbot auth list
75
+
76
+ # Switch active account
77
+ agent-wechatbot auth use <account-id>
78
+
79
+ # Remove a stored account
80
+ agent-wechatbot auth remove <account-id>
81
+ ```
82
+
83
+ ## Memory
84
+
85
+ The agent maintains a `~/.config/agent-messenger/MEMORY.md` file as persistent memory across sessions. This is agent-managed, the CLI does not read or write this file. Use the `Read` and `Write` tools to manage your memory file.
86
+
87
+ ### Reading Memory
88
+
89
+ At the **start of every task**, read `~/.config/agent-messenger/MEMORY.md` using the `Read` tool to load any previously discovered account IDs, template names, follower OpenIDs, and preferences.
90
+
91
+ - If the file doesn't exist yet, that's fine. Proceed without it and create it when you first have useful information to store.
92
+ - If the file can't be read (permissions, missing directory), proceed without memory. Don't error out.
93
+
94
+ ### Writing Memory
95
+
96
+ After discovering useful information, update `~/.config/agent-messenger/MEMORY.md` using the `Write` tool. Write triggers include:
97
+
98
+ - After discovering account IDs and App IDs (from `auth list`, `auth status`, etc.)
99
+ - After discovering template names and their parameters (from `template list`, etc.)
100
+ - After discovering follower OpenIDs (from `user list`, `user get`, etc.)
101
+ - After the user gives you an alias or preference ("call this the notifications account", "my main template is X")
102
+
103
+ When writing, include the **complete file content**. The `Write` tool overwrites the entire file.
104
+
105
+ ### What to Store
106
+
107
+ - Account IDs (App IDs) with names
108
+ - Template IDs with their required parameters
109
+ - Frequently used follower OpenIDs with context
110
+ - User-given aliases ("notifications account", "marketing account")
111
+ - Any user preference expressed during interaction
112
+
113
+ ### What NOT to Store
114
+
115
+ Never store App Secrets or any credentials. Never store full message content (just context). Never store personal user data.
116
+
117
+ ### Handling Stale Data
118
+
119
+ If a memorized template returns an error (template not found, account invalid), remove it from `MEMORY.md`. Don't blindly trust memorized data. Verify when something seems off. Prefer re-listing over using a memorized value that might be stale.
120
+
121
+ ### Format / Example
122
+
123
+ ```markdown
124
+ # Agent Messenger Memory
125
+
126
+ ## WeChat Accounts
127
+
128
+ - `wx1234567890` - Acme Notifications
129
+
130
+ ## Templates (Acme Notifications)
131
+
132
+ - `TM00001` - Order confirmation, params: [order_id, customer_name]
133
+ - `TM00002` - Shipping update, params: [tracking_number]
134
+
135
+ ## Frequent Recipients
136
+
137
+ - `oABCD1234` - Test user (internal QA)
138
+ - `oEFGH5678` - VIP customer
139
+
140
+ ## Aliases
141
+
142
+ - "notifications" -> `wx1234567890` (Acme Notifications)
143
+
144
+ ## Notes
145
+
146
+ - IP whitelist configured for 203.0.113.10
147
+ - Customer service messages limited to 48h interaction window
148
+ ```
149
+
150
+ > Memory lets you skip repeated `template list` calls. When you already know a template ID from a previous session, use it directly.
151
+
152
+ ## Commands
153
+
154
+ ### Auth Commands
155
+
156
+ ```bash
157
+ # Set account credentials (validates against API)
158
+ agent-wechatbot auth set <app-id> <app-secret>
159
+
160
+ # Check auth status
161
+ agent-wechatbot auth status
162
+ agent-wechatbot auth status --account <account-id>
163
+
164
+ # List stored accounts
165
+ agent-wechatbot auth list
166
+
167
+ # Switch active account
168
+ agent-wechatbot auth use <account-id>
169
+
170
+ # Remove a stored account
171
+ agent-wechatbot auth remove <account-id>
172
+
173
+ # Clear all credentials
174
+ agent-wechatbot auth clear
175
+ ```
176
+
177
+ ### Message Commands
178
+
179
+ ```bash
180
+ # Send a text message (customer service, within 48h window)
181
+ agent-wechatbot message send <open-id> <text>
182
+ agent-wechatbot message send oABCD1234 "Your order has shipped!"
183
+
184
+ # Send an image message (customer service, within 48h window)
185
+ agent-wechatbot message send-image <open-id> <media-id>
186
+ agent-wechatbot message send-image oABCD1234 MEDIA_ID_HERE
187
+
188
+ # Send a news/article message (customer service, within 48h window)
189
+ agent-wechatbot message send-news <open-id> --title "Title" --description "Desc" --url "https://..." --picurl "https://..."
190
+ ```
191
+
192
+ ### Template Commands
193
+
194
+ ```bash
195
+ # List message templates
196
+ agent-wechatbot template list
197
+
198
+ # Send a template message
199
+ agent-wechatbot template send <open-id> <template-id>
200
+ agent-wechatbot template send oABCD1234 TM00001 --data '{"order_id":{"value":"ORD-9876"},"customer_name":{"value":"Alice"}}' --url "https://example.com/order/9876"
201
+
202
+ # Delete a template
203
+ agent-wechatbot template delete <template-id>
204
+ ```
205
+
206
+ ### User Commands
207
+
208
+ ```bash
209
+ # List followers (paginated)
210
+ agent-wechatbot user list
211
+ agent-wechatbot user list --next-openid oLAST_OPENID
212
+
213
+ # Get user info by OpenID
214
+ agent-wechatbot user get <open-id>
215
+ agent-wechatbot user get oABCD1234 --lang en
216
+ ```
217
+
218
+ ## Output Format
219
+
220
+ ### JSON (Default)
221
+
222
+ All commands output JSON by default for AI consumption:
223
+
224
+ ```json
225
+ {
226
+ "success": true,
227
+ "app_id": "wx1234567890",
228
+ "account_name": "wx1234567890"
229
+ }
230
+ ```
231
+
232
+ ### Pretty (Human-Readable)
233
+
234
+ Use `--pretty` flag for formatted output:
235
+
236
+ ```bash
237
+ agent-wechatbot template list --pretty
238
+ ```
239
+
240
+ ## Global Options
241
+
242
+ | Option | Description |
243
+ | ---------------- | -------------------------------------- |
244
+ | `--pretty` | Human-readable output instead of JSON |
245
+ | `--account <id>` | Use a specific account for this command |
246
+
247
+ ## Common Patterns
248
+
249
+ ### Send a customer service message within 48h window
250
+
251
+ Customer service messages can be sent to users who have interacted with your account within the last 48 hours:
252
+
253
+ ```bash
254
+ # Send a text reply
255
+ agent-wechatbot message send oABCD1234 "Thanks for reaching out! We'll look into this right away."
256
+
257
+ # Send a news article
258
+ agent-wechatbot message send-news oABCD1234 \
259
+ --title "Your Order Update" \
260
+ --description "Your order #12345 has been shipped" \
261
+ --url "https://example.com/orders/12345" \
262
+ --picurl "https://example.com/images/shipping.jpg"
263
+ ```
264
+
265
+ ### Send a template notification (anytime)
266
+
267
+ Template messages can be sent at any time, regardless of the 48h window:
268
+
269
+ ```bash
270
+ # List templates to find the right one
271
+ agent-wechatbot template list --pretty
272
+
273
+ # Send a template message with data
274
+ agent-wechatbot template send oABCD1234 TM00001 \
275
+ --data '{"order_id":{"value":"ORD-9876"},"status":{"value":"Shipped"}}' \
276
+ --url "https://example.com/orders/9876"
277
+ ```
278
+
279
+ ### List and inspect followers
280
+
281
+ ```bash
282
+ # Get first page of followers
283
+ agent-wechatbot user list --pretty
284
+
285
+ # Get next page
286
+ agent-wechatbot user list --next-openid oLAST_OPENID --pretty
287
+
288
+ # Get details for a specific follower
289
+ agent-wechatbot user get oABCD1234 --pretty
290
+ ```
291
+
292
+ ### CI/CD deployment notification
293
+
294
+ ```bash
295
+ agent-wechatbot template send oABCD1234 deployment_alert \
296
+ --data '{"version":{"value":"v2.1.0"},"environment":{"value":"production"},"status":{"value":"success"}}'
297
+ ```
298
+
299
+ ## Error Handling
300
+
301
+ All commands return consistent error format:
302
+
303
+ ```json
304
+ {
305
+ "error": "No credentials. Run \"auth set <app-id> <app-secret>\" first."
306
+ }
307
+ ```
308
+
309
+ Common errors: `No credentials`, `Account not found`, `Invalid credentials`, `WeChat API error (errcode: 40001)`, `IP not in whitelist (errcode: 40164)`, `Rate limit exceeded (errcode: 45009)`.
310
+
311
+ ## Configuration
312
+
313
+ Credentials stored in `~/.config/agent-messenger/wechatbot-credentials.json` (0600 permissions).
314
+
315
+ Config format:
316
+
317
+ ```json
318
+ {
319
+ "current": { "account_id": "wx1234567890" },
320
+ "accounts": {
321
+ "wx1234567890": {
322
+ "app_id": "wx1234567890",
323
+ "app_secret": "...",
324
+ "account_name": "wx1234567890"
325
+ }
326
+ }
327
+ }
328
+ ```
329
+
330
+ ## Limitations
331
+
332
+ - **Cannot list or read received messages** — WeChat Official Account API delivers inbound messages via webhooks only. This CLI is send-only.
333
+ - **Customer service messages require 48h window** — Free-form text, image, and news messages only work within 48 hours of the user's last interaction.
334
+ - **Template messages require pre-approval** — Templates must be created and approved in the WeChat admin panel before use.
335
+ - **IP whitelist required** — Your server's IP must be added to the Official Account's whitelist, or you'll get error `40164`.
336
+ - **Media IDs required for images** — Images must be uploaded to WeChat's media platform first. The CLI accepts media IDs, not URLs.
337
+ - **No group chat support** — Official Account API communicates with individual followers only.
338
+ - **No real-time events / WebSocket connection** — Inbound messages require a separate webhook server.
339
+ - **No message editing or deletion**
340
+ - **No voice or video calls**
341
+ - **Access tokens expire** — Tokens are valid for 7200 seconds. The CLI handles automatic refresh.
342
+
343
+ ## Troubleshooting
344
+
345
+ ### `agent-wechatbot: command not found`
346
+
347
+ **`agent-wechatbot` is NOT the npm package name.** The npm package is `agent-messenger`.
348
+
349
+ If the package is installed globally, use `agent-wechatbot` directly:
350
+
351
+ ```bash
352
+ agent-wechatbot message send oABCD1234 "Hello"
353
+ ```
354
+
355
+ If the package is NOT installed, run it directly with `npx -y`:
356
+
357
+ ```bash
358
+ npx -y agent-messenger wechatbot message send oABCD1234 "Hello"
359
+ ```
360
+
361
+ > **Note**: If the user prefers a different package runner (e.g., `bunx`, `pnpx`, `pnpm dlx`), use that instead.
362
+
363
+ **NEVER run `npx agent-wechatbot`, `bunx agent-wechatbot`, or `pnpm dlx agent-wechatbot`**. It will fail or install a wrong package since `agent-wechatbot` is not the npm package name.
364
+
365
+ ### How to get API credentials
366
+
367
+ 1. Log in to the [WeChat Official Account admin panel](https://mp.weixin.qq.com/)
368
+ 2. Navigate to **Development > Basic Configuration**
369
+ 3. Copy your **App ID** and **App Secret** (you may need to reset the secret if you don't have it saved)
370
+ 4. Add your server's IP to the **IP Whitelist**
371
+ 5. Run `agent-wechatbot auth set <app-id> <app-secret>`
372
+
373
+ ### IP whitelist errors (40164)
374
+
375
+ If you get error `40164`, your server's IP is not in the Official Account's whitelist. Add it in the admin panel under **Development > Basic Configuration > IP Whitelist**.
376
+
377
+ ### Token errors (40001, 42001)
378
+
379
+ These indicate an expired or invalid access token. The CLI handles automatic token refresh, but if you see persistent errors:
380
+ - Verify your App Secret hasn't been reset in the admin panel
381
+ - Re-run `agent-wechatbot auth set <app-id> <app-secret>` with the current credentials
382
+
383
+ ### Rate limiting (45009)
384
+
385
+ WeChat enforces API call frequency limits. If you hit error `45009`, wait before retrying. For bulk operations, add delays between requests.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-whatsapp
3
3
  description: Interact with WhatsApp - send messages, read chats, manage conversations
4
- version: 2.2.0
4
+ version: 2.4.0
5
5
  allowed-tools: Bash(agent-whatsapp:*)
6
6
  metadata:
7
7
  openclaw:
@@ -369,6 +369,17 @@ pnpm dlx --package agent-messenger agent-whatsapp chat list --pretty
369
369
  4. Enter the pairing code shown in the terminal
370
370
  5. If the code expires, run `auth login --phone <number>` again for a fresh code
371
371
 
372
+ ### Empty chat list after linking a new device
373
+
374
+ WhatsApp companion devices receive their chat list from the primary phone — there's no server API to fetch it directly. On a freshly linked device, `chat list` may return only group chats (or nothing at all) for individual/DM conversations.
375
+
376
+ - **Keep your phone online** with WhatsApp open during and after linking. The phone pushes history to the new device; if it's off or WhatsApp is killed, nothing arrives.
377
+ - **Wait a few minutes** and run `chat list` again. History trickles in over time and is saved locally, so subsequent runs will show more chats.
378
+ - **Groups always work** immediately — they're fetched from the server, not from history sync.
379
+ - **Sending a message** to a known phone number will make that chat appear even if it wasn't in the initial sync.
380
+
381
+ This is a WhatsApp protocol limitation, not a bug.
382
+
372
383
  ### Connection drops
373
384
 
374
385
  WhatsApp may disconnect linked devices that are inactive for extended periods. If commands start failing:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agent-whatsappbot
3
3
  description: Interact with WhatsApp using Cloud API credentials - send messages, manage templates
4
- version: 2.2.0
4
+ version: 2.4.0
5
5
  allowed-tools: Bash(agent-whatsappbot:*)
6
6
  metadata:
7
7
  openclaw:
@@ -1,10 +1,11 @@
1
- import { afterAll, describe, expect, test } from 'bun:test'
1
+ import { afterAll, afterEach, beforeEach, describe, expect, test } from 'bun:test'
2
2
  import { existsSync, rmSync } from 'node:fs'
3
3
  import { join } from 'node:path'
4
4
 
5
5
  import { DiscordCredentialManager } from './credential-manager'
6
6
 
7
7
  const testDirs: string[] = []
8
+ let savedEnv: { token?: string; serverId?: string }
8
9
 
9
10
  function setup(): DiscordCredentialManager {
10
11
  const testConfigDir = join(
@@ -15,6 +16,22 @@ function setup(): DiscordCredentialManager {
15
16
  return new DiscordCredentialManager(testConfigDir)
16
17
  }
17
18
 
19
+ beforeEach(() => {
20
+ savedEnv = {
21
+ token: process.env.E2E_DISCORD_TOKEN,
22
+ serverId: process.env.E2E_DISCORD_SERVER_ID,
23
+ }
24
+ delete process.env.E2E_DISCORD_TOKEN
25
+ delete process.env.E2E_DISCORD_SERVER_ID
26
+ })
27
+
28
+ afterEach(() => {
29
+ if (savedEnv.token !== undefined) process.env.E2E_DISCORD_TOKEN = savedEnv.token
30
+ else delete process.env.E2E_DISCORD_TOKEN
31
+ if (savedEnv.serverId !== undefined) process.env.E2E_DISCORD_SERVER_ID = savedEnv.serverId
32
+ else delete process.env.E2E_DISCORD_SERVER_ID
33
+ })
34
+
18
35
  afterAll(() => {
19
36
  for (const dir of testDirs) {
20
37
  rmSync(dir, { recursive: true, force: true })
@@ -49,7 +49,7 @@ export class DiscordBotClient {
49
49
  private getHeaders(): Record<string, string> {
50
50
  return {
51
51
  Authorization: `Bot ${this.ensureAuth()}`,
52
- 'User-Agent': 'DiscordBot (https://github.com/devxoul/agent-messenger, 1.0)',
52
+ 'User-Agent': 'DiscordBot (https://github.com/agent-messenger/agent-messenger, 1.0)',
53
53
  'Content-Type': 'application/json',
54
54
  }
55
55
  }
@@ -186,7 +186,7 @@ export class DiscordBotClient {
186
186
 
187
187
  const headers: Record<string, string> = {
188
188
  Authorization: `Bot ${this.ensureAuth()}`,
189
- 'User-Agent': 'DiscordBot (https://github.com/devxoul/agent-messenger, 1.0)',
189
+ 'User-Agent': 'DiscordBot (https://github.com/agent-messenger/agent-messenger, 1.0)',
190
190
  }
191
191
 
192
192
  let response: Response
@@ -0,0 +1,216 @@
1
+ import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from 'bun:test'
2
+
3
+ const originalConsoleLog = console.log
4
+ import type { Command } from 'commander'
5
+
6
+ const mockGetAccount = mock(() => Promise.resolve(null))
7
+ const mockListAccounts = mock(() => Promise.resolve([]))
8
+ const mockSetCurrent = mock(() => Promise.resolve(true))
9
+ const mockRemoveAccount = mock(() => Promise.resolve(true))
10
+
11
+ mock.module('../credential-manager', () => ({
12
+ InstagramCredentialManager: class {
13
+ getAccount = mockGetAccount
14
+ listAccounts = mockListAccounts
15
+ setCurrent = mockSetCurrent
16
+ removeAccount = mockRemoveAccount
17
+ },
18
+ }))
19
+
20
+ import { authCommand } from './auth'
21
+
22
+ function resetCommandState(cmd: Command): void {
23
+ for (const sub of cmd.commands) {
24
+ (sub as unknown as { _optionValues: Record<string, unknown>; _optionValueSources: Record<string, unknown> })._optionValues = {}
25
+ ;(sub as unknown as { _optionValues: Record<string, unknown>; _optionValueSources: Record<string, unknown> })._optionValueSources = {}
26
+ }
27
+ }
28
+
29
+ describe('auth commands', () => {
30
+ let consoleLogSpy: ReturnType<typeof mock>
31
+ let processExitSpy: ReturnType<typeof spyOn>
32
+
33
+ beforeEach(() => {
34
+ resetCommandState(authCommand)
35
+
36
+ mockGetAccount.mockReset()
37
+ mockListAccounts.mockReset()
38
+ mockSetCurrent.mockReset()
39
+ mockRemoveAccount.mockReset()
40
+
41
+ consoleLogSpy = mock((..._args: unknown[]) => {}); console.log = consoleLogSpy
42
+ processExitSpy = spyOn(process, 'exit').mockImplementation(() => {
43
+ throw new Error('process.exit called')
44
+ })
45
+ })
46
+
47
+ afterEach(() => {
48
+ console.log = originalConsoleLog
49
+ processExitSpy.mockRestore()
50
+ })
51
+
52
+ describe('status', () => {
53
+ test('outputs error and exits when no account found', async () => {
54
+ mockGetAccount.mockImplementation(() => Promise.resolve(null))
55
+
56
+ await expect(
57
+ authCommand.parseAsync(['status'], { from: 'user' }),
58
+ ).rejects.toThrow('process.exit called')
59
+
60
+ expect(processExitSpy).toHaveBeenCalledWith(1)
61
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
62
+ expect(output.error).toContain('No Instagram account configured')
63
+ })
64
+
65
+ test('outputs account info when account exists', async () => {
66
+ mockGetAccount.mockImplementation(() =>
67
+ Promise.resolve({
68
+ account_id: 'user_testuser',
69
+ username: 'testuser',
70
+ pk: '12345',
71
+ created_at: '2024-01-01T00:00:00.000Z',
72
+ updated_at: '2024-01-01T00:00:00.000Z',
73
+ }),
74
+ )
75
+
76
+ await authCommand.parseAsync(['status'], { from: 'user' })
77
+
78
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
79
+ expect(output.account_id).toBe('user_testuser')
80
+ expect(output.username).toBe('testuser')
81
+ expect(output.pk).toBe('12345')
82
+ })
83
+
84
+ test('outputs error for specific account not found', async () => {
85
+ mockGetAccount.mockImplementation(() => Promise.resolve(null))
86
+
87
+ await expect(
88
+ authCommand.parseAsync(['status', '--account', 'missing_account'], { from: 'user' }),
89
+ ).rejects.toThrow('process.exit called')
90
+
91
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
92
+ expect(output.error).toContain('missing_account')
93
+ })
94
+ })
95
+
96
+ describe('list', () => {
97
+ test('outputs empty array when no accounts', async () => {
98
+ mockListAccounts.mockImplementation(() => Promise.resolve([]))
99
+
100
+ await authCommand.parseAsync(['list'], { from: 'user' })
101
+
102
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
103
+ expect(output).toEqual([])
104
+ })
105
+
106
+ test('outputs accounts list', async () => {
107
+ mockListAccounts.mockImplementation(() =>
108
+ Promise.resolve([
109
+ {
110
+ account_id: 'user_alice',
111
+ username: 'alice',
112
+ pk: '111',
113
+ created_at: '2024-01-01T00:00:00.000Z',
114
+ updated_at: '2024-01-01T00:00:00.000Z',
115
+ is_current: true,
116
+ },
117
+ {
118
+ account_id: 'user_bob',
119
+ username: 'bob',
120
+ pk: '222',
121
+ created_at: '2024-01-02T00:00:00.000Z',
122
+ updated_at: '2024-01-02T00:00:00.000Z',
123
+ is_current: false,
124
+ },
125
+ ]),
126
+ )
127
+
128
+ await authCommand.parseAsync(['list'], { from: 'user' })
129
+
130
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
131
+ expect(output).toHaveLength(2)
132
+ expect(output[0].account_id).toBe('user_alice')
133
+ expect(output[0].is_current).toBe(true)
134
+ expect(output[1].account_id).toBe('user_bob')
135
+ expect(output[1].is_current).toBe(false)
136
+ })
137
+ })
138
+
139
+ describe('use', () => {
140
+ test('switches to specified account', async () => {
141
+ mockSetCurrent.mockImplementation(() => Promise.resolve(true))
142
+ mockGetAccount.mockImplementation(() =>
143
+ Promise.resolve({
144
+ account_id: 'user_alice',
145
+ username: 'alice',
146
+ pk: '111',
147
+ created_at: '2024-01-01T00:00:00.000Z',
148
+ updated_at: '2024-01-01T00:00:00.000Z',
149
+ }),
150
+ )
151
+
152
+ await authCommand.parseAsync(['use', 'user_alice'], { from: 'user' })
153
+
154
+ expect(mockSetCurrent).toHaveBeenCalledWith('user_alice')
155
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
156
+ expect(output.success).toBe(true)
157
+ expect(output.account_id).toBe('user_alice')
158
+ })
159
+
160
+ test('outputs error when account not found', async () => {
161
+ mockSetCurrent.mockImplementation(() => Promise.resolve(false))
162
+
163
+ await expect(
164
+ authCommand.parseAsync(['use', 'missing_account'], { from: 'user' }),
165
+ ).rejects.toThrow('process.exit called')
166
+
167
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
168
+ expect(output.error).toContain('missing_account')
169
+ })
170
+ })
171
+
172
+ describe('logout', () => {
173
+ test('removes account and outputs success', async () => {
174
+ mockGetAccount.mockImplementation(() =>
175
+ Promise.resolve({
176
+ account_id: 'user_alice',
177
+ username: 'alice',
178
+ pk: '111',
179
+ created_at: '2024-01-01T00:00:00.000Z',
180
+ updated_at: '2024-01-01T00:00:00.000Z',
181
+ }),
182
+ )
183
+ mockRemoveAccount.mockImplementation(() => Promise.resolve(true))
184
+
185
+ await authCommand.parseAsync(['logout'], { from: 'user' })
186
+
187
+ expect(mockRemoveAccount).toHaveBeenCalledWith('user_alice')
188
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
189
+ expect(output.success).toBe(true)
190
+ expect(output.logged_out).toBe(true)
191
+ expect(output.account_id).toBe('user_alice')
192
+ })
193
+
194
+ test('outputs error when no account configured', async () => {
195
+ mockGetAccount.mockImplementation(() => Promise.resolve(null))
196
+
197
+ await expect(
198
+ authCommand.parseAsync(['logout'], { from: 'user' }),
199
+ ).rejects.toThrow('process.exit called')
200
+
201
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
202
+ expect(output.error).toContain('No Instagram account configured')
203
+ })
204
+
205
+ test('outputs error for specific account not found', async () => {
206
+ mockGetAccount.mockImplementation(() => Promise.resolve(null))
207
+
208
+ await expect(
209
+ authCommand.parseAsync(['logout', '--account', 'missing_account'], { from: 'user' }),
210
+ ).rejects.toThrow('process.exit called')
211
+
212
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
213
+ expect(output.error).toContain('missing_account')
214
+ })
215
+ })
216
+ })