agent-messenger 2.3.0 → 2.5.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 (438) 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/.github/workflows/release.yml +0 -12
  5. package/CONTRIBUTING.md +1 -1
  6. package/README.md +11 -8
  7. package/bun.lock +70 -110
  8. package/bunfig.toml +3 -0
  9. package/dist/package.json +11 -3
  10. package/dist/src/platforms/channeltalk/cli.d.ts.map +1 -1
  11. package/dist/src/platforms/channeltalk/cli.js +2 -1
  12. package/dist/src/platforms/channeltalk/cli.js.map +1 -1
  13. package/dist/src/platforms/channeltalk/commands/index.d.ts +1 -0
  14. package/dist/src/platforms/channeltalk/commands/index.d.ts.map +1 -1
  15. package/dist/src/platforms/channeltalk/commands/index.js +1 -0
  16. package/dist/src/platforms/channeltalk/commands/index.js.map +1 -1
  17. package/dist/src/platforms/channeltalk/commands/whoami.d.ts +22 -0
  18. package/dist/src/platforms/channeltalk/commands/whoami.d.ts.map +1 -0
  19. package/dist/src/platforms/channeltalk/commands/whoami.js +40 -0
  20. package/dist/src/platforms/channeltalk/commands/whoami.js.map +1 -0
  21. package/dist/src/platforms/channeltalkbot/cli.d.ts.map +1 -1
  22. package/dist/src/platforms/channeltalkbot/cli.js +2 -1
  23. package/dist/src/platforms/channeltalkbot/cli.js.map +1 -1
  24. package/dist/src/platforms/channeltalkbot/commands/index.d.ts +1 -0
  25. package/dist/src/platforms/channeltalkbot/commands/index.d.ts.map +1 -1
  26. package/dist/src/platforms/channeltalkbot/commands/index.js +1 -0
  27. package/dist/src/platforms/channeltalkbot/commands/index.js.map +1 -1
  28. package/dist/src/platforms/channeltalkbot/commands/whoami.d.ts +13 -0
  29. package/dist/src/platforms/channeltalkbot/commands/whoami.d.ts.map +1 -0
  30. package/dist/src/platforms/channeltalkbot/commands/whoami.js +31 -0
  31. package/dist/src/platforms/channeltalkbot/commands/whoami.js.map +1 -0
  32. package/dist/src/platforms/discord/cli.d.ts.map +1 -1
  33. package/dist/src/platforms/discord/cli.js +2 -1
  34. package/dist/src/platforms/discord/cli.js.map +1 -1
  35. package/dist/src/platforms/discord/commands/index.d.ts +1 -0
  36. package/dist/src/platforms/discord/commands/index.d.ts.map +1 -1
  37. package/dist/src/platforms/discord/commands/index.js +1 -0
  38. package/dist/src/platforms/discord/commands/index.js.map +1 -1
  39. package/dist/src/platforms/discord/commands/whoami.d.ts +6 -0
  40. package/dist/src/platforms/discord/commands/whoami.d.ts.map +1 -0
  41. package/dist/src/platforms/discord/commands/whoami.js +33 -0
  42. package/dist/src/platforms/discord/commands/whoami.js.map +1 -0
  43. package/dist/src/platforms/discordbot/cli.d.ts.map +1 -1
  44. package/dist/src/platforms/discordbot/cli.js +2 -1
  45. package/dist/src/platforms/discordbot/cli.js.map +1 -1
  46. package/dist/src/platforms/discordbot/client.js +2 -2
  47. package/dist/src/platforms/discordbot/client.js.map +1 -1
  48. package/dist/src/platforms/discordbot/commands/index.d.ts +1 -0
  49. package/dist/src/platforms/discordbot/commands/index.d.ts.map +1 -1
  50. package/dist/src/platforms/discordbot/commands/index.js +1 -0
  51. package/dist/src/platforms/discordbot/commands/index.js.map +1 -1
  52. package/dist/src/platforms/discordbot/commands/whoami.d.ts +14 -0
  53. package/dist/src/platforms/discordbot/commands/whoami.d.ts.map +1 -0
  54. package/dist/src/platforms/discordbot/commands/whoami.js +32 -0
  55. package/dist/src/platforms/discordbot/commands/whoami.js.map +1 -0
  56. package/dist/src/platforms/instagram/cli.d.ts.map +1 -1
  57. package/dist/src/platforms/instagram/cli.js +2 -1
  58. package/dist/src/platforms/instagram/cli.js.map +1 -1
  59. package/dist/src/platforms/instagram/client.d.ts +6 -0
  60. package/dist/src/platforms/instagram/client.d.ts.map +1 -1
  61. package/dist/src/platforms/instagram/client.js +12 -0
  62. package/dist/src/platforms/instagram/client.js.map +1 -1
  63. package/dist/src/platforms/instagram/commands/index.d.ts +1 -0
  64. package/dist/src/platforms/instagram/commands/index.d.ts.map +1 -1
  65. package/dist/src/platforms/instagram/commands/index.js +1 -0
  66. package/dist/src/platforms/instagram/commands/index.js.map +1 -1
  67. package/dist/src/platforms/instagram/commands/whoami.d.ts +7 -0
  68. package/dist/src/platforms/instagram/commands/whoami.d.ts.map +1 -0
  69. package/dist/src/platforms/instagram/commands/whoami.js +19 -0
  70. package/dist/src/platforms/instagram/commands/whoami.js.map +1 -0
  71. package/dist/src/platforms/kakaotalk/cli.d.ts.map +1 -1
  72. package/dist/src/platforms/kakaotalk/cli.js +2 -1
  73. package/dist/src/platforms/kakaotalk/cli.js.map +1 -1
  74. package/dist/src/platforms/kakaotalk/client.d.ts +2 -1
  75. package/dist/src/platforms/kakaotalk/client.d.ts.map +1 -1
  76. package/dist/src/platforms/kakaotalk/client.js +225 -23
  77. package/dist/src/platforms/kakaotalk/client.js.map +1 -1
  78. package/dist/src/platforms/kakaotalk/commands/index.d.ts +1 -0
  79. package/dist/src/platforms/kakaotalk/commands/index.d.ts.map +1 -1
  80. package/dist/src/platforms/kakaotalk/commands/index.js +1 -0
  81. package/dist/src/platforms/kakaotalk/commands/index.js.map +1 -1
  82. package/dist/src/platforms/kakaotalk/commands/whoami.d.ts +3 -0
  83. package/dist/src/platforms/kakaotalk/commands/whoami.d.ts.map +1 -0
  84. package/dist/src/platforms/kakaotalk/commands/whoami.js +19 -0
  85. package/dist/src/platforms/kakaotalk/commands/whoami.js.map +1 -0
  86. package/dist/src/platforms/kakaotalk/index.d.ts +2 -2
  87. package/dist/src/platforms/kakaotalk/index.d.ts.map +1 -1
  88. package/dist/src/platforms/kakaotalk/index.js +1 -1
  89. package/dist/src/platforms/kakaotalk/index.js.map +1 -1
  90. package/dist/src/platforms/kakaotalk/protocol/session.d.ts +4 -2
  91. package/dist/src/platforms/kakaotalk/protocol/session.d.ts.map +1 -1
  92. package/dist/src/platforms/kakaotalk/protocol/session.js +27 -7
  93. package/dist/src/platforms/kakaotalk/protocol/session.js.map +1 -1
  94. package/dist/src/platforms/kakaotalk/protocol/types.d.ts +17 -0
  95. package/dist/src/platforms/kakaotalk/protocol/types.d.ts.map +1 -1
  96. package/dist/src/platforms/kakaotalk/protocol/types.js.map +1 -1
  97. package/dist/src/platforms/kakaotalk/types.d.ts +28 -0
  98. package/dist/src/platforms/kakaotalk/types.d.ts.map +1 -1
  99. package/dist/src/platforms/kakaotalk/types.js +14 -0
  100. package/dist/src/platforms/kakaotalk/types.js.map +1 -1
  101. package/dist/src/platforms/line/cli.js +2 -2
  102. package/dist/src/platforms/line/cli.js.map +1 -1
  103. package/dist/src/platforms/line/client.d.ts.map +1 -1
  104. package/dist/src/platforms/line/client.js +9 -36
  105. package/dist/src/platforms/line/client.js.map +1 -1
  106. package/dist/src/platforms/line/commands/auth.d.ts.map +1 -1
  107. package/dist/src/platforms/line/commands/auth.js +9 -47
  108. package/dist/src/platforms/line/commands/auth.js.map +1 -1
  109. package/dist/src/platforms/line/commands/index.d.ts +1 -1
  110. package/dist/src/platforms/line/commands/index.d.ts.map +1 -1
  111. package/dist/src/platforms/line/commands/index.js +1 -1
  112. package/dist/src/platforms/line/commands/index.js.map +1 -1
  113. package/dist/src/platforms/line/commands/whoami.d.ts +3 -0
  114. package/dist/src/platforms/line/commands/whoami.d.ts.map +1 -0
  115. package/dist/src/platforms/line/commands/{profile.js → whoami.js} +5 -5
  116. package/dist/src/platforms/line/commands/whoami.js.map +1 -0
  117. package/dist/src/platforms/slack/cli.d.ts.map +1 -1
  118. package/dist/src/platforms/slack/cli.js +2 -1
  119. package/dist/src/platforms/slack/cli.js.map +1 -1
  120. package/dist/src/platforms/slack/commands/index.d.ts +1 -0
  121. package/dist/src/platforms/slack/commands/index.d.ts.map +1 -1
  122. package/dist/src/platforms/slack/commands/index.js +1 -0
  123. package/dist/src/platforms/slack/commands/index.js.map +1 -1
  124. package/dist/src/platforms/slack/commands/whoami.d.ts +6 -0
  125. package/dist/src/platforms/slack/commands/whoami.d.ts.map +1 -0
  126. package/dist/src/platforms/slack/commands/whoami.js +39 -0
  127. package/dist/src/platforms/slack/commands/whoami.js.map +1 -0
  128. package/dist/src/platforms/slackbot/cli.d.ts.map +1 -1
  129. package/dist/src/platforms/slackbot/cli.js +2 -1
  130. package/dist/src/platforms/slackbot/cli.js.map +1 -1
  131. package/dist/src/platforms/slackbot/commands/index.d.ts +1 -0
  132. package/dist/src/platforms/slackbot/commands/index.d.ts.map +1 -1
  133. package/dist/src/platforms/slackbot/commands/index.js +1 -0
  134. package/dist/src/platforms/slackbot/commands/index.js.map +1 -1
  135. package/dist/src/platforms/slackbot/commands/whoami.d.ts +14 -0
  136. package/dist/src/platforms/slackbot/commands/whoami.d.ts.map +1 -0
  137. package/dist/src/platforms/slackbot/commands/whoami.js +32 -0
  138. package/dist/src/platforms/slackbot/commands/whoami.js.map +1 -0
  139. package/dist/src/platforms/teams/cli.d.ts.map +1 -1
  140. package/dist/src/platforms/teams/cli.js +2 -1
  141. package/dist/src/platforms/teams/cli.js.map +1 -1
  142. package/dist/src/platforms/teams/commands/index.d.ts +1 -0
  143. package/dist/src/platforms/teams/commands/index.d.ts.map +1 -1
  144. package/dist/src/platforms/teams/commands/index.js +1 -0
  145. package/dist/src/platforms/teams/commands/index.js.map +1 -1
  146. package/dist/src/platforms/teams/commands/reaction.d.ts.map +1 -1
  147. package/dist/src/platforms/teams/commands/reaction.js +2 -0
  148. package/dist/src/platforms/teams/commands/reaction.js.map +1 -1
  149. package/dist/src/platforms/teams/commands/whoami.d.ts +6 -0
  150. package/dist/src/platforms/teams/commands/whoami.d.ts.map +1 -0
  151. package/dist/src/platforms/teams/commands/whoami.js +30 -0
  152. package/dist/src/platforms/teams/commands/whoami.js.map +1 -0
  153. package/dist/src/platforms/telegram/cli.d.ts.map +1 -1
  154. package/dist/src/platforms/telegram/cli.js +2 -1
  155. package/dist/src/platforms/telegram/cli.js.map +1 -1
  156. package/dist/src/platforms/telegram/commands/index.d.ts +1 -0
  157. package/dist/src/platforms/telegram/commands/index.d.ts.map +1 -1
  158. package/dist/src/platforms/telegram/commands/index.js +1 -0
  159. package/dist/src/platforms/telegram/commands/index.js.map +1 -1
  160. package/dist/src/platforms/telegram/commands/whoami.d.ts +7 -0
  161. package/dist/src/platforms/telegram/commands/whoami.d.ts.map +1 -0
  162. package/dist/src/platforms/telegram/commands/whoami.js +27 -0
  163. package/dist/src/platforms/telegram/commands/whoami.js.map +1 -0
  164. package/dist/src/platforms/webex/cli.d.ts.map +1 -1
  165. package/dist/src/platforms/webex/cli.js +2 -1
  166. package/dist/src/platforms/webex/cli.js.map +1 -1
  167. package/dist/src/platforms/webex/commands/index.d.ts +1 -0
  168. package/dist/src/platforms/webex/commands/index.d.ts.map +1 -1
  169. package/dist/src/platforms/webex/commands/index.js +1 -0
  170. package/dist/src/platforms/webex/commands/index.js.map +1 -1
  171. package/dist/src/platforms/webex/commands/message.js +1 -1
  172. package/dist/src/platforms/webex/commands/message.js.map +1 -1
  173. package/dist/src/platforms/webex/commands/whoami.d.ts +6 -0
  174. package/dist/src/platforms/webex/commands/whoami.d.ts.map +1 -0
  175. package/dist/src/platforms/webex/commands/whoami.js +30 -0
  176. package/dist/src/platforms/webex/commands/whoami.js.map +1 -0
  177. package/dist/src/platforms/wechatbot/cli.d.ts +5 -0
  178. package/dist/src/platforms/wechatbot/cli.d.ts.map +1 -0
  179. package/dist/src/platforms/wechatbot/cli.js +19 -0
  180. package/dist/src/platforms/wechatbot/cli.js.map +1 -0
  181. package/dist/src/platforms/wechatbot/client.d.ts +36 -0
  182. package/dist/src/platforms/wechatbot/client.d.ts.map +1 -0
  183. package/dist/src/platforms/wechatbot/client.js +208 -0
  184. package/dist/src/platforms/wechatbot/client.js.map +1 -0
  185. package/dist/src/platforms/wechatbot/commands/auth.d.ts +28 -0
  186. package/dist/src/platforms/wechatbot/commands/auth.d.ts.map +1 -0
  187. package/dist/src/platforms/wechatbot/commands/auth.js +164 -0
  188. package/dist/src/platforms/wechatbot/commands/auth.js.map +1 -0
  189. package/dist/src/platforms/wechatbot/commands/index.d.ts +6 -0
  190. package/dist/src/platforms/wechatbot/commands/index.d.ts.map +1 -0
  191. package/dist/src/platforms/wechatbot/commands/index.js +6 -0
  192. package/dist/src/platforms/wechatbot/commands/index.js.map +1 -0
  193. package/dist/src/platforms/wechatbot/commands/message.d.ts +18 -0
  194. package/dist/src/platforms/wechatbot/commands/message.d.ts.map +1 -0
  195. package/dist/src/platforms/wechatbot/commands/message.js +80 -0
  196. package/dist/src/platforms/wechatbot/commands/message.js.map +1 -0
  197. package/dist/src/platforms/wechatbot/commands/shared.d.ts +9 -0
  198. package/dist/src/platforms/wechatbot/commands/shared.d.ts.map +1 -0
  199. package/dist/src/platforms/wechatbot/commands/shared.js +13 -0
  200. package/dist/src/platforms/wechatbot/commands/shared.js.map +1 -0
  201. package/dist/src/platforms/wechatbot/commands/template.d.ts +19 -0
  202. package/dist/src/platforms/wechatbot/commands/template.d.ts.map +1 -0
  203. package/dist/src/platforms/wechatbot/commands/template.js +76 -0
  204. package/dist/src/platforms/wechatbot/commands/template.js.map +1 -0
  205. package/dist/src/platforms/wechatbot/commands/user.d.ts +20 -0
  206. package/dist/src/platforms/wechatbot/commands/user.d.ts.map +1 -0
  207. package/dist/src/platforms/wechatbot/commands/user.js +53 -0
  208. package/dist/src/platforms/wechatbot/commands/user.js.map +1 -0
  209. package/dist/src/platforms/wechatbot/commands/whoami.d.ts +12 -0
  210. package/dist/src/platforms/wechatbot/commands/whoami.d.ts.map +1 -0
  211. package/dist/src/platforms/wechatbot/commands/whoami.js +33 -0
  212. package/dist/src/platforms/wechatbot/commands/whoami.js.map +1 -0
  213. package/dist/src/platforms/wechatbot/credential-manager.d.ts +17 -0
  214. package/dist/src/platforms/wechatbot/credential-manager.d.ts.map +1 -0
  215. package/dist/src/platforms/wechatbot/credential-manager.js +121 -0
  216. package/dist/src/platforms/wechatbot/credential-manager.js.map +1 -0
  217. package/dist/src/platforms/wechatbot/index.d.ts +5 -0
  218. package/dist/src/platforms/wechatbot/index.d.ts.map +1 -0
  219. package/dist/src/platforms/wechatbot/index.js +4 -0
  220. package/dist/src/platforms/wechatbot/index.js.map +1 -0
  221. package/dist/src/platforms/wechatbot/types.d.ts +94 -0
  222. package/dist/src/platforms/wechatbot/types.d.ts.map +1 -0
  223. package/dist/src/platforms/wechatbot/types.js +54 -0
  224. package/dist/src/platforms/wechatbot/types.js.map +1 -0
  225. package/dist/src/platforms/whatsapp/cli.d.ts.map +1 -1
  226. package/dist/src/platforms/whatsapp/cli.js +2 -1
  227. package/dist/src/platforms/whatsapp/cli.js.map +1 -1
  228. package/dist/src/platforms/whatsapp/client.d.ts +9 -0
  229. package/dist/src/platforms/whatsapp/client.d.ts.map +1 -1
  230. package/dist/src/platforms/whatsapp/client.js +143 -21
  231. package/dist/src/platforms/whatsapp/client.js.map +1 -1
  232. package/dist/src/platforms/whatsapp/commands/auth.d.ts.map +1 -1
  233. package/dist/src/platforms/whatsapp/commands/auth.js +133 -60
  234. package/dist/src/platforms/whatsapp/commands/auth.js.map +1 -1
  235. package/dist/src/platforms/whatsapp/commands/index.d.ts +1 -0
  236. package/dist/src/platforms/whatsapp/commands/index.d.ts.map +1 -1
  237. package/dist/src/platforms/whatsapp/commands/index.js +1 -0
  238. package/dist/src/platforms/whatsapp/commands/index.js.map +1 -1
  239. package/dist/src/platforms/whatsapp/commands/shared.js +2 -2
  240. package/dist/src/platforms/whatsapp/commands/shared.js.map +1 -1
  241. package/dist/src/platforms/whatsapp/commands/whoami.d.ts +7 -0
  242. package/dist/src/platforms/whatsapp/commands/whoami.d.ts.map +1 -0
  243. package/dist/src/platforms/whatsapp/commands/whoami.js +19 -0
  244. package/dist/src/platforms/whatsapp/commands/whoami.js.map +1 -0
  245. package/dist/src/platforms/whatsapp/credential-manager.d.ts.map +1 -1
  246. package/dist/src/platforms/whatsapp/credential-manager.js +14 -8
  247. package/dist/src/platforms/whatsapp/credential-manager.js.map +1 -1
  248. package/dist/src/platforms/whatsapp/ensure-auth.js +2 -2
  249. package/dist/src/platforms/whatsapp/ensure-auth.js.map +1 -1
  250. package/dist/src/platforms/whatsappbot/cli.d.ts.map +1 -1
  251. package/dist/src/platforms/whatsappbot/cli.js +2 -1
  252. package/dist/src/platforms/whatsappbot/cli.js.map +1 -1
  253. package/dist/src/platforms/whatsappbot/commands/index.d.ts +1 -0
  254. package/dist/src/platforms/whatsappbot/commands/index.d.ts.map +1 -1
  255. package/dist/src/platforms/whatsappbot/commands/index.js +1 -0
  256. package/dist/src/platforms/whatsappbot/commands/index.js.map +1 -1
  257. package/dist/src/platforms/whatsappbot/commands/whoami.d.ts +17 -0
  258. package/dist/src/platforms/whatsappbot/commands/whoami.d.ts.map +1 -0
  259. package/dist/src/platforms/whatsappbot/commands/whoami.js +39 -0
  260. package/dist/src/platforms/whatsappbot/commands/whoami.js.map +1 -0
  261. package/dist/src/shared/utils/qr.d.ts +15 -0
  262. package/dist/src/shared/utils/qr.d.ts.map +1 -0
  263. package/dist/src/shared/utils/qr.js +74 -0
  264. package/dist/src/shared/utils/qr.js.map +1 -0
  265. package/dist/src/tui/adapters/whatsapp-adapter.d.ts.map +1 -1
  266. package/dist/src/tui/adapters/whatsapp-adapter.js +20 -15
  267. package/dist/src/tui/adapters/whatsapp-adapter.js.map +1 -1
  268. package/docs/content/docs/agent-skills.mdx +4 -4
  269. package/docs/content/docs/cli/channeltalk.mdx +12 -1
  270. package/docs/content/docs/cli/channeltalkbot.mdx +10 -1
  271. package/docs/content/docs/cli/discord.mdx +11 -1
  272. package/docs/content/docs/cli/discordbot.mdx +10 -1
  273. package/docs/content/docs/cli/instagram.mdx +12 -1
  274. package/docs/content/docs/cli/kakaotalk.mdx +25 -1
  275. package/docs/content/docs/cli/line.mdx +5 -5
  276. package/docs/content/docs/cli/meta.json +1 -0
  277. package/docs/content/docs/cli/slack.mdx +11 -1
  278. package/docs/content/docs/cli/slackbot.mdx +10 -1
  279. package/docs/content/docs/cli/teams.mdx +11 -1
  280. package/docs/content/docs/cli/telegram.mdx +11 -0
  281. package/docs/content/docs/cli/webex.mdx +11 -1
  282. package/docs/content/docs/cli/wechatbot.mdx +188 -0
  283. package/docs/content/docs/cli/whatsapp.mdx +37 -8
  284. package/docs/content/docs/cli/whatsappbot.mdx +10 -1
  285. package/docs/content/docs/sdk/meta.json +1 -1
  286. package/docs/content/docs/sdk/wechatbot.mdx +282 -0
  287. package/docs/content/docs/tui.mdx +1 -1
  288. package/docs/src/app/page.tsx +5 -5
  289. package/package.json +11 -3
  290. package/skills/agent-channeltalk/SKILL.md +12 -1
  291. package/skills/agent-channeltalkbot/SKILL.md +10 -1
  292. package/skills/agent-discord/SKILL.md +11 -1
  293. package/skills/agent-discordbot/SKILL.md +10 -1
  294. package/skills/agent-instagram/SKILL.md +12 -1
  295. package/skills/agent-kakaotalk/SKILL.md +30 -1
  296. package/skills/agent-kakaotalk/references/common-patterns.md +1 -1
  297. package/skills/agent-line/SKILL.md +11 -15
  298. package/skills/agent-line/references/authentication.md +13 -4
  299. package/skills/agent-slack/SKILL.md +11 -1
  300. package/skills/agent-slackbot/SKILL.md +10 -1
  301. package/skills/agent-teams/SKILL.md +11 -1
  302. package/skills/agent-telegram/SKILL.md +6 -1
  303. package/skills/agent-webex/SKILL.md +11 -1
  304. package/skills/agent-wechatbot/SKILL.md +394 -0
  305. package/skills/agent-whatsapp/SKILL.md +63 -15
  306. package/skills/agent-whatsapp/references/authentication.md +36 -6
  307. package/skills/agent-whatsappbot/SKILL.md +10 -1
  308. package/src/platforms/channeltalk/cli.ts +2 -0
  309. package/src/platforms/channeltalk/commands/index.ts +1 -0
  310. package/src/platforms/channeltalk/commands/whoami.test.ts +64 -0
  311. package/src/platforms/channeltalk/commands/whoami.ts +62 -0
  312. package/src/platforms/channeltalkbot/cli.ts +2 -0
  313. package/src/platforms/channeltalkbot/commands/index.ts +1 -0
  314. package/src/platforms/channeltalkbot/commands/whoami.test.ts +104 -0
  315. package/src/platforms/channeltalkbot/commands/whoami.ts +42 -0
  316. package/src/platforms/discord/cli.ts +2 -0
  317. package/src/platforms/discord/commands/index.ts +1 -0
  318. package/src/platforms/discord/commands/whoami.test.ts +91 -0
  319. package/src/platforms/discord/commands/whoami.ts +36 -0
  320. package/src/platforms/discord/credential-manager.test.ts +18 -1
  321. package/src/platforms/discordbot/cli.ts +2 -0
  322. package/src/platforms/discordbot/client.ts +2 -2
  323. package/src/platforms/discordbot/commands/index.ts +1 -0
  324. package/src/platforms/discordbot/commands/whoami.test.ts +96 -0
  325. package/src/platforms/discordbot/commands/whoami.ts +44 -0
  326. package/src/platforms/instagram/cli.ts +2 -1
  327. package/src/platforms/instagram/client.ts +13 -0
  328. package/src/platforms/instagram/commands/auth.test.ts +216 -0
  329. package/src/platforms/instagram/commands/chat.test.ts +123 -0
  330. package/src/platforms/instagram/commands/index.ts +1 -0
  331. package/src/platforms/instagram/commands/message.test.ts +174 -0
  332. package/src/platforms/instagram/commands/whoami.test.ts +60 -0
  333. package/src/platforms/instagram/commands/whoami.ts +21 -0
  334. package/src/platforms/kakaotalk/cli.ts +2 -1
  335. package/src/platforms/kakaotalk/client.test.ts +182 -14
  336. package/src/platforms/kakaotalk/client.ts +261 -27
  337. package/src/platforms/kakaotalk/commands/auth.test.ts +299 -0
  338. package/src/platforms/kakaotalk/commands/chat.test.ts +97 -0
  339. package/src/platforms/kakaotalk/commands/index.ts +1 -0
  340. package/src/platforms/kakaotalk/commands/message.test.ts +113 -0
  341. package/src/platforms/kakaotalk/commands/whoami.test.ts +116 -0
  342. package/src/platforms/kakaotalk/commands/whoami.ts +21 -0
  343. package/src/platforms/kakaotalk/index.test.ts +5 -0
  344. package/src/platforms/kakaotalk/index.ts +2 -0
  345. package/src/platforms/kakaotalk/protocol/session.ts +29 -7
  346. package/src/platforms/kakaotalk/protocol/types.ts +9 -0
  347. package/src/platforms/kakaotalk/types.ts +30 -0
  348. package/src/platforms/line/cli.ts +2 -2
  349. package/src/platforms/line/client.ts +14 -39
  350. package/src/platforms/line/commands/auth.test.ts +141 -0
  351. package/src/platforms/line/commands/auth.ts +37 -61
  352. package/src/platforms/line/commands/chat.test.ts +110 -0
  353. package/src/platforms/line/commands/friend.test.ts +98 -0
  354. package/src/platforms/line/commands/index.ts +1 -1
  355. package/src/platforms/line/commands/message.test.ts +119 -0
  356. package/src/platforms/line/commands/whoami.test.ts +85 -0
  357. package/src/platforms/line/commands/{profile.ts → whoami.ts} +4 -4
  358. package/src/platforms/slack/cli.ts +2 -0
  359. package/src/platforms/slack/commands/index.ts +1 -0
  360. package/src/platforms/slack/commands/whoami.test.ts +126 -0
  361. package/src/platforms/slack/commands/whoami.ts +40 -0
  362. package/src/platforms/slackbot/cli.ts +2 -1
  363. package/src/platforms/slackbot/commands/channel.test.ts +139 -0
  364. package/src/platforms/slackbot/commands/index.ts +1 -0
  365. package/src/platforms/slackbot/commands/message.test.ts +226 -0
  366. package/src/platforms/slackbot/commands/reaction.test.ts +90 -0
  367. package/src/platforms/slackbot/commands/user.test.ts +143 -0
  368. package/src/platforms/slackbot/commands/whoami.test.ts +102 -0
  369. package/src/platforms/slackbot/commands/whoami.ts +44 -0
  370. package/src/platforms/teams/cli.ts +2 -0
  371. package/src/platforms/teams/commands/index.ts +1 -0
  372. package/src/platforms/teams/commands/reaction.test.ts +45 -61
  373. package/src/platforms/teams/commands/reaction.ts +2 -0
  374. package/src/platforms/teams/commands/whoami.test.ts +83 -0
  375. package/src/platforms/teams/commands/whoami.ts +33 -0
  376. package/src/platforms/telegram/cli.ts +2 -1
  377. package/src/platforms/telegram/commands/chat.test.ts +125 -0
  378. package/src/platforms/telegram/commands/index.ts +1 -0
  379. package/src/platforms/telegram/commands/message.test.ts +92 -0
  380. package/src/platforms/telegram/commands/whoami.test.ts +75 -0
  381. package/src/platforms/telegram/commands/whoami.ts +29 -0
  382. package/src/platforms/webex/cli.ts +2 -1
  383. package/src/platforms/webex/commands/auth.test.ts +58 -46
  384. package/src/platforms/webex/commands/index.ts +1 -0
  385. package/src/platforms/webex/commands/member.test.ts +60 -57
  386. package/src/platforms/webex/commands/message.test.ts +74 -121
  387. package/src/platforms/webex/commands/message.ts +1 -1
  388. package/src/platforms/webex/commands/snapshot.test.ts +54 -45
  389. package/src/platforms/webex/commands/space.test.ts +46 -49
  390. package/src/platforms/webex/commands/whoami.test.ts +113 -0
  391. package/src/platforms/webex/commands/whoami.ts +31 -0
  392. package/src/platforms/webex/credential-manager.test.ts +0 -1
  393. package/src/platforms/wechatbot/cli.ts +25 -0
  394. package/src/platforms/wechatbot/client.test.ts +497 -0
  395. package/src/platforms/wechatbot/client.ts +268 -0
  396. package/src/platforms/wechatbot/commands/auth.test.ts +211 -0
  397. package/src/platforms/wechatbot/commands/auth.ts +203 -0
  398. package/src/platforms/wechatbot/commands/index.ts +5 -0
  399. package/src/platforms/wechatbot/commands/message.test.ts +155 -0
  400. package/src/platforms/wechatbot/commands/message.ts +104 -0
  401. package/src/platforms/wechatbot/commands/shared.ts +22 -0
  402. package/src/platforms/wechatbot/commands/template.test.ts +199 -0
  403. package/src/platforms/wechatbot/commands/template.ts +102 -0
  404. package/src/platforms/wechatbot/commands/user.test.ts +165 -0
  405. package/src/platforms/wechatbot/commands/user.ts +75 -0
  406. package/src/platforms/wechatbot/commands/whoami.test.ts +109 -0
  407. package/src/platforms/wechatbot/commands/whoami.ts +43 -0
  408. package/src/platforms/wechatbot/credential-manager.test.ts +255 -0
  409. package/src/platforms/wechatbot/credential-manager.ts +148 -0
  410. package/src/platforms/wechatbot/index.test.ts +49 -0
  411. package/src/platforms/wechatbot/index.ts +19 -0
  412. package/src/platforms/wechatbot/types.test.ts +223 -0
  413. package/src/platforms/wechatbot/types.ts +107 -0
  414. package/src/platforms/whatsapp/cli.ts +2 -1
  415. package/src/platforms/whatsapp/client.ts +180 -37
  416. package/src/platforms/whatsapp/commands/auth.test.ts +311 -0
  417. package/src/platforms/whatsapp/commands/auth.ts +194 -84
  418. package/src/platforms/whatsapp/commands/chat.test.ts +198 -0
  419. package/src/platforms/whatsapp/commands/index.ts +1 -0
  420. package/src/platforms/whatsapp/commands/message.test.ts +231 -0
  421. package/src/platforms/whatsapp/commands/shared.ts +2 -2
  422. package/src/platforms/whatsapp/commands/whoami.test.ts +59 -0
  423. package/src/platforms/whatsapp/commands/whoami.ts +21 -0
  424. package/src/platforms/whatsapp/credential-manager.test.ts +20 -0
  425. package/src/platforms/whatsapp/credential-manager.ts +17 -8
  426. package/src/platforms/whatsapp/ensure-auth.ts +2 -2
  427. package/src/platforms/whatsappbot/cli.ts +2 -1
  428. package/src/platforms/whatsappbot/commands/auth.test.ts +217 -0
  429. package/src/platforms/whatsappbot/commands/index.ts +1 -0
  430. package/src/platforms/whatsappbot/commands/message.test.ts +198 -0
  431. package/src/platforms/whatsappbot/commands/template.test.ts +112 -0
  432. package/src/platforms/whatsappbot/commands/whoami.test.ts +100 -0
  433. package/src/platforms/whatsappbot/commands/whoami.ts +57 -0
  434. package/src/shared/utils/qr.ts +92 -0
  435. package/src/tui/adapters/whatsapp-adapter.ts +19 -16
  436. package/dist/src/platforms/line/commands/profile.d.ts +0 -3
  437. package/dist/src/platforms/line/commands/profile.d.ts.map +0 -1
  438. package/dist/src/platforms/line/commands/profile.js.map +0 -1
@@ -0,0 +1,113 @@
1
+ import { afterEach, beforeEach, expect, spyOn, test } from 'bun:test'
2
+
3
+ import * as clientModule from '../client'
4
+ import { WebexError } from '../types'
5
+ import { whoamiCommand } from './whoami'
6
+
7
+ const mockUser = {
8
+ id: 'person-123',
9
+ emails: ['test@example.com'],
10
+ displayName: 'Test User',
11
+ nickName: 'Testy',
12
+ firstName: 'Test',
13
+ lastName: 'User',
14
+ avatar: 'https://example.com/avatar.jpg',
15
+ orgId: 'org-123',
16
+ type: 'person' as const,
17
+ created: '2024-01-01T00:00:00.000Z',
18
+ }
19
+
20
+ const makeFakeClient = () => ({
21
+ login: async function (this: unknown) { return this },
22
+ testAuth: async () => mockUser,
23
+ })
24
+
25
+ let webexClientSpy: ReturnType<typeof spyOn>
26
+ let consoleLogSpy: ReturnType<typeof spyOn>
27
+ let processExitSpy: ReturnType<typeof spyOn>
28
+
29
+ beforeEach(() => {
30
+ webexClientSpy = spyOn(clientModule, 'WebexClient').mockImplementation(
31
+ makeFakeClient as unknown as typeof clientModule.WebexClient,
32
+ )
33
+ consoleLogSpy = spyOn(console, 'log').mockImplementation(() => {})
34
+ processExitSpy = spyOn(process, 'exit').mockImplementation((_code?: number) => undefined as never)
35
+ })
36
+
37
+ afterEach(() => {
38
+ webexClientSpy?.mockRestore()
39
+ consoleLogSpy?.mockRestore()
40
+ processExitSpy?.mockRestore()
41
+ })
42
+
43
+ test('whoami command is defined with correct name and description', () => {
44
+ expect(whoamiCommand).toBeDefined()
45
+ expect(whoamiCommand.name()).toBe('whoami')
46
+ expect(whoamiCommand.description()).toBe('Show current authenticated user')
47
+ })
48
+
49
+ test('whoami command has --pretty option', () => {
50
+ const options = whoamiCommand.options
51
+ const hasPretty = options.some((opt: { long?: string }) => opt.long === '--pretty')
52
+ expect(hasPretty).toBe(true)
53
+ })
54
+
55
+ test('whoami calls testAuth and outputs user fields', async () => {
56
+ // given: authenticated webex user
57
+ // when: running whoami
58
+ await whoamiCommand.parseAsync([], { from: 'user' })
59
+
60
+ // then: outputs all expected fields
61
+ expect(consoleLogSpy).toHaveBeenCalledWith(
62
+ JSON.stringify({
63
+ id: 'person-123',
64
+ emails: ['test@example.com'],
65
+ displayName: 'Test User',
66
+ nickName: 'Testy',
67
+ firstName: 'Test',
68
+ lastName: 'User',
69
+ avatar: 'https://example.com/avatar.jpg',
70
+ orgId: 'org-123',
71
+ type: 'person',
72
+ }),
73
+ )
74
+ })
75
+
76
+ test('whoami outputs pretty-printed JSON when --pretty flag is passed', async () => {
77
+ // given: authenticated webex user
78
+ // when: running whoami with --pretty
79
+ await whoamiCommand.parseAsync(['--pretty'], { from: 'user' })
80
+
81
+ // then: output is pretty-printed
82
+ expect(consoleLogSpy).toHaveBeenCalledWith(
83
+ JSON.stringify(
84
+ {
85
+ id: 'person-123',
86
+ emails: ['test@example.com'],
87
+ displayName: 'Test User',
88
+ nickName: 'Testy',
89
+ firstName: 'Test',
90
+ lastName: 'User',
91
+ avatar: 'https://example.com/avatar.jpg',
92
+ orgId: 'org-123',
93
+ type: 'person',
94
+ },
95
+ null,
96
+ 2,
97
+ ),
98
+ )
99
+ })
100
+
101
+ test('whoami exits with code 1 when not authenticated', async () => {
102
+ // given: no credentials
103
+ webexClientSpy.mockImplementation(() => ({
104
+ login: async () => { throw new WebexError('No Webex credentials found.', 'no_credentials') },
105
+ testAuth: async () => mockUser,
106
+ }) as unknown as clientModule.WebexClient)
107
+
108
+ // when: running whoami
109
+ await whoamiCommand.parseAsync([], { from: 'user' })
110
+
111
+ // then: process exits with code 1
112
+ expect(processExitSpy).toHaveBeenCalledWith(1)
113
+ })
@@ -0,0 +1,31 @@
1
+ import { Command } from 'commander'
2
+ import { handleError } from '@/shared/utils/error-handler'
3
+ import { formatOutput } from '@/shared/utils/output'
4
+ import { WebexClient } from '../client'
5
+
6
+ export async function whoamiAction(options: { pretty?: boolean }): Promise<void> {
7
+ try {
8
+ const client = await new WebexClient().login()
9
+ const user = await client.testAuth()
10
+
11
+ const output = {
12
+ id: user.id,
13
+ emails: user.emails,
14
+ displayName: user.displayName,
15
+ nickName: user.nickName,
16
+ firstName: user.firstName,
17
+ lastName: user.lastName,
18
+ avatar: user.avatar,
19
+ orgId: user.orgId,
20
+ type: user.type,
21
+ }
22
+ console.log(formatOutput(output, options.pretty))
23
+ } catch (error) {
24
+ handleError(error as Error)
25
+ }
26
+ }
27
+
28
+ export const whoamiCommand = new Command('whoami')
29
+ .description('Show current authenticated user')
30
+ .option('--pretty', 'Pretty print JSON output')
31
+ .action(whoamiAction)
@@ -16,7 +16,6 @@ describe('WebexCredentialManager', () => {
16
16
 
17
17
  afterEach(async () => {
18
18
  await rm(tempDir, { recursive: true, force: true })
19
- mock.restore()
20
19
  })
21
20
 
22
21
  test('loadConfig returns null when no file exists', async () => {
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { Command } from 'commander'
4
+
5
+ import pkg from '../../../package.json' with { type: 'json' }
6
+ import { authCommand, messageCommand, templateCommand, userCommand, whoamiCommand } from './commands/index'
7
+
8
+ const program = new Command()
9
+
10
+ program
11
+ .name('agent-wechatbot')
12
+ .description('CLI tool for WeChat Official Account bot integration')
13
+ .version(pkg.version)
14
+ .option('--pretty', 'Pretty-print JSON output')
15
+ .option('--account <id>', 'Account ID to use')
16
+
17
+ program.addCommand(authCommand)
18
+ program.addCommand(whoamiCommand)
19
+ program.addCommand(messageCommand)
20
+ program.addCommand(templateCommand)
21
+ program.addCommand(userCommand)
22
+
23
+ program.parseAsync(process.argv)
24
+
25
+ export default program
@@ -0,0 +1,497 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
2
+
3
+ import { WeChatBotClient } from '@/platforms/wechatbot/client'
4
+ import { WeChatBotError } from '@/platforms/wechatbot/types'
5
+
6
+ describe('WeChatBotClient', () => {
7
+ const originalFetch = globalThis.fetch
8
+ let fetchCalls: Array<{ url: string; options?: RequestInit }> = []
9
+ let fetchResponses: Response[] = []
10
+ let fetchIndex = 0
11
+
12
+ beforeEach(() => {
13
+ fetchCalls = []
14
+ fetchResponses = []
15
+ fetchIndex = 0
16
+ Object.defineProperty(globalThis, 'fetch', {
17
+ value: async (url: string | URL | Request, options?: RequestInit): Promise<Response> => {
18
+ fetchCalls.push({ url: url.toString(), options })
19
+ const response = fetchResponses[fetchIndex]
20
+ fetchIndex++
21
+ if (!response) {
22
+ throw new Error('No mock response configured')
23
+ }
24
+ return response
25
+ },
26
+ writable: true,
27
+ configurable: true,
28
+ })
29
+ })
30
+
31
+ afterEach(() => {
32
+ Object.defineProperty(globalThis, 'fetch', {
33
+ value: originalFetch,
34
+ writable: true,
35
+ configurable: true,
36
+ })
37
+ })
38
+
39
+ const mockResponse = (body: unknown, status = 200) => {
40
+ fetchResponses.push(
41
+ new Response(JSON.stringify(body), {
42
+ status,
43
+ headers: { 'Content-Type': 'application/json' },
44
+ }),
45
+ )
46
+ }
47
+
48
+ const tokenResponse = () => mockResponse({ access_token: 'test-token', expires_in: 7200 })
49
+
50
+ describe('login', () => {
51
+ test('throws on empty appId', async () => {
52
+ await expect(new WeChatBotClient().login({ appId: '', appSecret: 'secret' })).rejects.toThrow(WeChatBotError)
53
+ await expect(new WeChatBotClient().login({ appId: '', appSecret: 'secret' })).rejects.toThrow('App ID is required')
54
+ })
55
+
56
+ test('throws on empty appSecret', async () => {
57
+ await expect(new WeChatBotClient().login({ appId: 'wx123', appSecret: '' })).rejects.toThrow(WeChatBotError)
58
+ await expect(new WeChatBotClient().login({ appId: 'wx123', appSecret: '' })).rejects.toThrow('App Secret is required')
59
+ })
60
+
61
+ test('accepts valid credentials and returns client', async () => {
62
+ const client = await new WeChatBotClient().login({ appId: 'wx123', appSecret: 'secret123' })
63
+ expect(client).toBeInstanceOf(WeChatBotClient)
64
+ })
65
+ })
66
+
67
+ describe('verifyCredentials', () => {
68
+ test('calls token endpoint and returns true on success', async () => {
69
+ tokenResponse()
70
+
71
+ const client = await new WeChatBotClient().login({ appId: 'wx123', appSecret: 'secret123' })
72
+ const result = await client.verifyCredentials()
73
+
74
+ expect(result).toBe(true)
75
+ expect(fetchCalls.length).toBe(1)
76
+ expect(fetchCalls[0].url).toContain('/cgi-bin/token')
77
+ expect(fetchCalls[0].url).toContain('appid=wx123')
78
+ expect(fetchCalls[0].url).toContain('secret=secret123')
79
+ })
80
+
81
+ test('returns false on token error', async () => {
82
+ mockResponse({ errcode: 40125, errmsg: 'invalid appsecret' })
83
+
84
+ const client = await new WeChatBotClient().login({ appId: 'wx123', appSecret: 'bad-secret' })
85
+ const result = await client.verifyCredentials()
86
+
87
+ expect(result).toBe(false)
88
+ })
89
+
90
+ test('returns false on network error', async () => {
91
+ fetchResponses = []
92
+ Object.defineProperty(globalThis, 'fetch', {
93
+ value: async (): Promise<Response> => {
94
+ throw new Error('network error')
95
+ },
96
+ writable: true,
97
+ configurable: true,
98
+ })
99
+
100
+ const client = await new WeChatBotClient().login({ appId: 'wx123', appSecret: 'secret123' })
101
+ const result = await client.verifyCredentials()
102
+
103
+ expect(result).toBe(false)
104
+ })
105
+ })
106
+
107
+ describe('sendTextMessage', () => {
108
+ test('sends POST to /cgi-bin/message/custom/send with token in query', async () => {
109
+ tokenResponse()
110
+ mockResponse({ errcode: 0, errmsg: 'ok' })
111
+
112
+ const client = await new WeChatBotClient().login({ appId: 'wx123', appSecret: 'secret123' })
113
+ await client.sendTextMessage('openid-123', 'Hello world')
114
+
115
+ expect(fetchCalls.length).toBe(2)
116
+ const call = fetchCalls[1]
117
+ expect(call.url).toContain('/cgi-bin/message/custom/send')
118
+ expect(call.url).toContain('access_token=test-token')
119
+ expect(call.options?.method).toBe('POST')
120
+ })
121
+
122
+ test('sends correct body shape', async () => {
123
+ tokenResponse()
124
+ mockResponse({ errcode: 0, errmsg: 'ok' })
125
+
126
+ const client = await new WeChatBotClient().login({ appId: 'wx123', appSecret: 'secret123' })
127
+ await client.sendTextMessage('openid-123', 'Hello world')
128
+
129
+ const body = JSON.parse(fetchCalls[1].options?.body as string)
130
+ expect(body).toMatchObject({
131
+ touser: 'openid-123',
132
+ msgtype: 'text',
133
+ text: { content: 'Hello world' },
134
+ })
135
+ })
136
+ })
137
+
138
+ describe('sendImageMessage', () => {
139
+ test('sends POST with image payload', async () => {
140
+ tokenResponse()
141
+ mockResponse({ errcode: 0, errmsg: 'ok' })
142
+
143
+ const client = await new WeChatBotClient().login({ appId: 'wx123', appSecret: 'secret123' })
144
+ await client.sendImageMessage('openid-123', 'media-id-456')
145
+
146
+ const body = JSON.parse(fetchCalls[1].options?.body as string)
147
+ expect(body).toMatchObject({
148
+ touser: 'openid-123',
149
+ msgtype: 'image',
150
+ image: { media_id: 'media-id-456' },
151
+ })
152
+ })
153
+ })
154
+
155
+ describe('sendNewsMessage', () => {
156
+ test('sends POST with news/articles payload', async () => {
157
+ tokenResponse()
158
+ mockResponse({ errcode: 0, errmsg: 'ok' })
159
+
160
+ const client = await new WeChatBotClient().login({ appId: 'wx123', appSecret: 'secret123' })
161
+ const articles = [
162
+ {
163
+ title: 'Test Article',
164
+ description: 'Test desc',
165
+ url: 'https://example.com',
166
+ picurl: 'https://example.com/pic.jpg',
167
+ },
168
+ ]
169
+ await client.sendNewsMessage('openid-123', articles)
170
+
171
+ const body = JSON.parse(fetchCalls[1].options?.body as string)
172
+ expect(body).toMatchObject({
173
+ touser: 'openid-123',
174
+ msgtype: 'news',
175
+ news: { articles },
176
+ })
177
+ })
178
+ })
179
+
180
+ describe('sendTemplateMessage', () => {
181
+ test('sends POST to /cgi-bin/message/template/send and returns msgid', async () => {
182
+ tokenResponse()
183
+ mockResponse({ errcode: 0, errmsg: 'ok', msgid: 12345 })
184
+
185
+ const client = await new WeChatBotClient().login({ appId: 'wx123', appSecret: 'secret123' })
186
+ const result = await client.sendTemplateMessage(
187
+ 'openid-123',
188
+ 'template-id-abc',
189
+ { first: { value: 'Hello' } },
190
+ 'https://example.com',
191
+ )
192
+
193
+ expect(result.msgid).toBe(12345)
194
+ expect(fetchCalls[1].url).toContain('/cgi-bin/message/template/send')
195
+
196
+ const body = JSON.parse(fetchCalls[1].options?.body as string)
197
+ expect(body).toMatchObject({
198
+ touser: 'openid-123',
199
+ template_id: 'template-id-abc',
200
+ url: 'https://example.com',
201
+ data: { first: { value: 'Hello' } },
202
+ })
203
+ })
204
+ })
205
+
206
+ describe('listTemplates', () => {
207
+ test('sends GET and unwraps template_list from response', async () => {
208
+ tokenResponse()
209
+ mockResponse({
210
+ errcode: 0,
211
+ errmsg: 'ok',
212
+ template_list: [
213
+ {
214
+ template_id: 'tmpl-001',
215
+ title: 'Order Notification',
216
+ primary_industry: 'IT科技',
217
+ deputy_industry: '互联网|电子商务',
218
+ content: 'ORDER_STATUS {{status.DATA}}',
219
+ example: 'Order shipped',
220
+ },
221
+ ],
222
+ })
223
+
224
+ const client = await new WeChatBotClient().login({ appId: 'wx123', appSecret: 'secret123' })
225
+ const templates = await client.listTemplates()
226
+
227
+ expect(templates).toHaveLength(1)
228
+ expect(templates[0].template_id).toBe('tmpl-001')
229
+ expect(templates[0].title).toBe('Order Notification')
230
+ expect(fetchCalls[1].url).toContain('/cgi-bin/template/get_all_private_template')
231
+ })
232
+ })
233
+
234
+ describe('deleteTemplate', () => {
235
+ test('sends POST with template_id body', async () => {
236
+ tokenResponse()
237
+ mockResponse({ errcode: 0, errmsg: 'ok' })
238
+
239
+ const client = await new WeChatBotClient().login({ appId: 'wx123', appSecret: 'secret123' })
240
+ await client.deleteTemplate('tmpl-to-delete')
241
+
242
+ expect(fetchCalls[1].url).toContain('/cgi-bin/template/del_private_template')
243
+ const body = JSON.parse(fetchCalls[1].options?.body as string)
244
+ expect(body).toEqual({ template_id: 'tmpl-to-delete' })
245
+ })
246
+ })
247
+
248
+ describe('getFollowers', () => {
249
+ test('sends GET to /cgi-bin/user/get and returns openids array', async () => {
250
+ tokenResponse()
251
+ mockResponse({
252
+ errcode: 0,
253
+ total: 3,
254
+ count: 2,
255
+ data: { openid: ['openid-1', 'openid-2'] },
256
+ next_openid: 'openid-2',
257
+ })
258
+
259
+ const client = await new WeChatBotClient().login({ appId: 'wx123', appSecret: 'secret123' })
260
+ const result = await client.getFollowers()
261
+
262
+ expect(result.total).toBe(3)
263
+ expect(result.count).toBe(2)
264
+ expect(result.openids).toEqual(['openid-1', 'openid-2'])
265
+ expect(result.next_openid).toBe('openid-2')
266
+ expect(fetchCalls[1].url).toContain('/cgi-bin/user/get')
267
+ })
268
+
269
+ test('passes next_openid parameter when provided', async () => {
270
+ tokenResponse()
271
+ mockResponse({
272
+ total: 1,
273
+ count: 1,
274
+ data: { openid: ['openid-3'] },
275
+ next_openid: '',
276
+ })
277
+
278
+ const client = await new WeChatBotClient().login({ appId: 'wx123', appSecret: 'secret123' })
279
+ await client.getFollowers('openid-2')
280
+
281
+ expect(fetchCalls[1].url).toContain('next_openid=openid-2')
282
+ })
283
+
284
+ test('returns empty openids when data is missing', async () => {
285
+ tokenResponse()
286
+ mockResponse({
287
+ total: 0,
288
+ count: 0,
289
+ next_openid: '',
290
+ })
291
+
292
+ const client = await new WeChatBotClient().login({ appId: 'wx123', appSecret: 'secret123' })
293
+ const result = await client.getFollowers()
294
+
295
+ expect(result.openids).toEqual([])
296
+ })
297
+ })
298
+
299
+ describe('getUserInfo', () => {
300
+ test('sends GET with openid and lang params', async () => {
301
+ tokenResponse()
302
+ mockResponse({
303
+ subscribe: 1,
304
+ openid: 'openid-123',
305
+ language: 'zh_CN',
306
+ subscribe_time: 1609459200,
307
+ remark: '',
308
+ tagid_list: [],
309
+ subscribe_scene: 'ADD_SCENE_QR_CODE',
310
+ qr_scene: 0,
311
+ qr_scene_str: '',
312
+ })
313
+
314
+ const client = await new WeChatBotClient().login({ appId: 'wx123', appSecret: 'secret123' })
315
+ const user = await client.getUserInfo('openid-123', 'zh_CN')
316
+
317
+ expect(user.openid).toBe('openid-123')
318
+ expect(user.subscribe).toBe(1)
319
+ expect(fetchCalls[1].url).toContain('/cgi-bin/user/info')
320
+ expect(fetchCalls[1].url).toContain('openid=openid-123')
321
+ expect(fetchCalls[1].url).toContain('lang=zh_CN')
322
+ })
323
+
324
+ test('defaults to zh_CN lang', async () => {
325
+ tokenResponse()
326
+ mockResponse({
327
+ subscribe: 1,
328
+ openid: 'openid-456',
329
+ language: 'zh_CN',
330
+ subscribe_time: 1609459200,
331
+ remark: '',
332
+ tagid_list: [],
333
+ subscribe_scene: 'ADD_SCENE_QR_CODE',
334
+ qr_scene: 0,
335
+ qr_scene_str: '',
336
+ })
337
+
338
+ const client = await new WeChatBotClient().login({ appId: 'wx123', appSecret: 'secret123' })
339
+ await client.getUserInfo('openid-456')
340
+
341
+ expect(fetchCalls[1].url).toContain('lang=zh_CN')
342
+ })
343
+ })
344
+
345
+ describe('token caching', () => {
346
+ test('second call does not re-fetch token if not expired', async () => {
347
+ tokenResponse()
348
+ mockResponse({ errcode: 0, errmsg: 'ok' })
349
+ mockResponse({ errcode: 0, errmsg: 'ok' })
350
+
351
+ const client = await new WeChatBotClient().login({ appId: 'wx123', appSecret: 'secret123' })
352
+ await client.sendTextMessage('openid-1', 'msg1')
353
+ await client.sendTextMessage('openid-2', 'msg2')
354
+
355
+ expect(fetchCalls.length).toBe(3)
356
+ expect(fetchCalls[0].url).toContain('/cgi-bin/token')
357
+ expect(fetchCalls[1].url).toContain('/cgi-bin/message/custom/send')
358
+ expect(fetchCalls[2].url).toContain('/cgi-bin/message/custom/send')
359
+ })
360
+ })
361
+
362
+ describe('token auto-refresh on 40001', () => {
363
+ test('fetches new token and retries on errcode 40001', async () => {
364
+ tokenResponse()
365
+ mockResponse({ errcode: 40001, errmsg: 'invalid credential' })
366
+ mockResponse({ access_token: 'new-token', expires_in: 7200 })
367
+ mockResponse({ errcode: 0, errmsg: 'ok' })
368
+
369
+ const client = await new WeChatBotClient().login({ appId: 'wx123', appSecret: 'secret123' })
370
+ await client.sendTextMessage('openid-123', 'Hello')
371
+
372
+ expect(fetchCalls.length).toBe(4)
373
+ expect(fetchCalls[2].url).toContain('/cgi-bin/token')
374
+ expect(fetchCalls[3].url).toContain('/cgi-bin/message/custom/send')
375
+ expect(fetchCalls[3].url).toContain('access_token=new-token')
376
+ })
377
+ })
378
+
379
+ describe('retry on system busy (errcode -1)', () => {
380
+ test('retries with backoff on errcode -1', async () => {
381
+ tokenResponse()
382
+ mockResponse({ errcode: -1, errmsg: 'system busy' })
383
+ mockResponse({ errcode: -1, errmsg: 'system busy' })
384
+ mockResponse({ errcode: -1, errmsg: 'system busy' })
385
+ mockResponse({ errcode: -1, errmsg: 'system busy' })
386
+
387
+ const client = await new WeChatBotClient().login({ appId: 'wx123', appSecret: 'secret123' })
388
+
389
+ try {
390
+ await client.sendTextMessage('openid-123', 'Hello')
391
+ expect.unreachable('should have thrown')
392
+ } catch (err) {
393
+ expect(err).toBeInstanceOf(WeChatBotError)
394
+ expect((err as WeChatBotError).message).toBe('WeChat system busy')
395
+ }
396
+ })
397
+ })
398
+
399
+ describe('network error retry', () => {
400
+ test('GET retries on fetch throw', async () => {
401
+ let callCount = 0
402
+ Object.defineProperty(globalThis, 'fetch', {
403
+ value: async (url: string | URL | Request, options?: RequestInit): Promise<Response> => {
404
+ fetchCalls.push({ url: url.toString(), options })
405
+ callCount++
406
+ if (callCount === 1) {
407
+ return new Response(JSON.stringify({ access_token: 'test-token', expires_in: 7200 }), { status: 200 })
408
+ }
409
+ if (callCount <= 3) {
410
+ throw new Error('network error')
411
+ }
412
+ return new Response(
413
+ JSON.stringify({
414
+ total: 0,
415
+ count: 0,
416
+ data: { openid: [] },
417
+ next_openid: '',
418
+ }),
419
+ { status: 200 },
420
+ )
421
+ },
422
+ writable: true,
423
+ configurable: true,
424
+ })
425
+
426
+ const client = await new WeChatBotClient().login({ appId: 'wx123', appSecret: 'secret123' })
427
+ const result = await client.getFollowers()
428
+
429
+ expect(result.openids).toEqual([])
430
+ expect(callCount).toBe(4)
431
+ })
432
+
433
+ test('POST does not retry on fetch throw', async () => {
434
+ let callCount = 0
435
+ Object.defineProperty(globalThis, 'fetch', {
436
+ value: async (url: string | URL | Request, options?: RequestInit): Promise<Response> => {
437
+ fetchCalls.push({ url: url.toString(), options })
438
+ callCount++
439
+ if (callCount === 1) {
440
+ return new Response(JSON.stringify({ access_token: 'test-token', expires_in: 7200 }), { status: 200 })
441
+ }
442
+ throw new Error('network error')
443
+ },
444
+ writable: true,
445
+ configurable: true,
446
+ })
447
+
448
+ const client = await new WeChatBotClient().login({ appId: 'wx123', appSecret: 'secret123' })
449
+ await expect(client.sendTextMessage('openid-123', 'Hello')).rejects.toThrow(WeChatBotError)
450
+ expect(callCount).toBe(2)
451
+ })
452
+ })
453
+
454
+ describe('errcode handling', () => {
455
+ test('non-zero errcode throws WeChatBotError with correct code', async () => {
456
+ tokenResponse()
457
+ mockResponse({ errcode: 48001, errmsg: 'api unauthorized' })
458
+
459
+ const client = await new WeChatBotClient().login({ appId: 'wx123', appSecret: 'secret123' })
460
+
461
+ try {
462
+ await client.sendTextMessage('openid-123', 'Hello')
463
+ expect.unreachable('should have thrown')
464
+ } catch (err) {
465
+ expect(err).toBeInstanceOf(WeChatBotError)
466
+ expect((err as WeChatBotError).code).toBe('48001')
467
+ expect((err as WeChatBotError).message).toBe('api unauthorized')
468
+ }
469
+ })
470
+
471
+ test('errcode 0 does not throw', async () => {
472
+ tokenResponse()
473
+ mockResponse({ errcode: 0, errmsg: 'ok' })
474
+
475
+ const client = await new WeChatBotClient().login({ appId: 'wx123', appSecret: 'secret123' })
476
+ await expect(client.sendTextMessage('openid-123', 'Hello')).resolves.toBeUndefined()
477
+ })
478
+ })
479
+
480
+ describe('rate limit (errcode 45009)', () => {
481
+ test('throws WeChatBotError with code 45009 and appropriate message', async () => {
482
+ tokenResponse()
483
+ mockResponse({ errcode: 45009, errmsg: 'reach max api daily quota limit' })
484
+
485
+ const client = await new WeChatBotClient().login({ appId: 'wx123', appSecret: 'secret123' })
486
+
487
+ try {
488
+ await client.sendTextMessage('openid-123', 'Hello')
489
+ expect.unreachable('should have thrown')
490
+ } catch (err) {
491
+ expect(err).toBeInstanceOf(WeChatBotError)
492
+ expect((err as WeChatBotError).code).toBe('45009')
493
+ expect((err as WeChatBotError).message).toContain('frequency limit')
494
+ }
495
+ })
496
+ })
497
+ })