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
@@ -1,6 +1,8 @@
1
1
  import { existsSync } from 'node:fs'
2
2
  import { readFile, writeFile } from 'node:fs/promises'
3
3
  import { join } from 'node:path'
4
+
5
+ import { Boom } from '@hapi/boom'
4
6
  import makeWASocket, {
5
7
  DisconnectReason,
6
8
  fetchLatestBaileysVersion,
@@ -12,8 +14,8 @@ import makeWASocket, {
12
14
  type WAMessage,
13
15
  type WASocket,
14
16
  } from '@whiskeysockets/baileys'
15
- import { Boom } from '@hapi/boom'
16
17
  import pino from 'pino'
18
+
17
19
  import {
18
20
  extractMessageText,
19
21
  getMessageType,
@@ -28,7 +30,12 @@ const MAX_MESSAGES_PER_CHAT = 500
28
30
  function toTimestampMs(ts: unknown): number {
29
31
  if (ts == null) return 0
30
32
  if (typeof ts === 'number') return ts * 1000
31
- if (typeof ts === 'object' && ts !== null && 'toNumber' in ts && typeof (ts as Record<string, unknown>).toNumber === 'function') {
33
+ if (
34
+ typeof ts === 'object' &&
35
+ ts !== null &&
36
+ 'toNumber' in ts &&
37
+ typeof (ts as Record<string, unknown>).toNumber === 'function'
38
+ ) {
32
39
  return (ts as { toNumber(): number }).toNumber() * 1000
33
40
  }
34
41
  const n = Number(ts)
@@ -44,11 +51,7 @@ function resolveJid(input: string): string {
44
51
  function summarizeMessage(msg: WAMessage): WhatsAppMessageSummary {
45
52
  const jid = msg.key.remoteJid ?? ''
46
53
  const isGroup = jid.endsWith('@g.us')
47
- const from = msg.key.fromMe
48
- ? ''
49
- : isGroup
50
- ? (msg.key.participant ?? msg.participant ?? jid)
51
- : jid
54
+ const from = msg.key.fromMe ? '' : isGroup ? (msg.key.participant ?? msg.participant ?? jid) : jid
52
55
 
53
56
  return {
54
57
  id: msg.key.id ?? '',
@@ -62,10 +65,7 @@ function summarizeMessage(msg: WAMessage): WhatsAppMessageSummary {
62
65
  }
63
66
  }
64
67
 
65
- function summarizeChat(
66
- chat: Chat,
67
- lastMessage?: WhatsAppMessageSummary,
68
- ): WhatsAppChatSummary {
68
+ function summarizeChat(chat: Chat, lastMessage?: WhatsAppMessageSummary): WhatsAppChatSummary {
69
69
  const id = chat.id ?? ''
70
70
  return {
71
71
  id,
@@ -86,6 +86,7 @@ export class WhatsAppClient {
86
86
  private pendingResolve: (() => void) | null = null
87
87
  private pendingPromise: Promise<void> | null = null
88
88
  private authCompleteResolve: (() => void) | null = null
89
+ private authCompleteReject: ((err: Error) => void) | null = null
89
90
 
90
91
  private storePath: string | null = null
91
92
 
@@ -105,7 +106,7 @@ export class WhatsAppClient {
105
106
  const account = await manager.getAccount()
106
107
  if (!account) {
107
108
  throw new WhatsAppError(
108
- 'No WhatsApp credentials found. Run "agent-whatsapp auth login --phone <phone-number>" first.',
109
+ 'No WhatsApp credentials found. Run "agent-whatsapp auth login --qr" or "agent-whatsapp auth login --phone <phone-number>" first.',
109
110
  'no_credentials',
110
111
  )
111
112
  }
@@ -225,10 +226,12 @@ export class WhatsAppClient {
225
226
  retries++
226
227
  if (retries > MAX_RETRIES) {
227
228
  clearTimeout(timeout)
228
- outerReject(new WhatsAppError(
229
- `Connection failed after ${MAX_RETRIES} retries: ${lastDisconnect?.error?.message ?? 'unknown'}`,
230
- 'max_retries',
231
- ))
229
+ outerReject(
230
+ new WhatsAppError(
231
+ `Connection failed after ${MAX_RETRIES} retries: ${lastDisconnect?.error?.message ?? 'unknown'}`,
232
+ 'max_retries',
233
+ ),
234
+ )
232
235
  return
233
236
  }
234
237
 
@@ -248,8 +251,9 @@ export class WhatsAppClient {
248
251
  this.pendingResolve = resolve
249
252
  })
250
253
 
251
- const authCompletePromise = new Promise<void>((resolve) => {
254
+ const authCompletePromise = new Promise<void>((resolve, reject) => {
252
255
  this.authCompleteResolve = resolve
256
+ this.authCompleteReject = reject
253
257
  })
254
258
 
255
259
  const cleaned = phoneNumber.replace(/[^0-9]/g, '')
@@ -258,16 +262,24 @@ export class WhatsAppClient {
258
262
 
259
263
  return new Promise((outerResolve, outerReject) => {
260
264
  let codeReturned = false
261
- let postPairingRetries = 0
262
- const MAX_POST_PAIRING_RETRIES = 5
263
265
 
264
266
  const overallTimeout = setTimeout(() => {
265
- outerReject(new WhatsAppError('Pairing timed out', 'pairing_timeout'))
267
+ const err = new WhatsAppError('Pairing timed out', 'pairing_timeout')
268
+ if (codeReturned) {
269
+ this.authCompleteReject?.(err)
270
+ } else {
271
+ outerReject(err)
272
+ }
266
273
  }, 120_000)
267
274
 
268
275
  const onError = (err: unknown): void => {
269
276
  clearTimeout(overallTimeout)
270
- outerReject(err instanceof Error ? err : new WhatsAppError(String(err), 'pairing_error'))
277
+ const error = err instanceof Error ? err : new WhatsAppError(String(err), 'pairing_error')
278
+ if (codeReturned) {
279
+ this.authCompleteReject?.(error)
280
+ } else {
281
+ outerReject(error)
282
+ }
271
283
  }
272
284
 
273
285
  const attempt = async (): Promise<void> => {
@@ -294,20 +306,21 @@ export class WhatsAppClient {
294
306
 
295
307
  const statusCode = (lastDisconnect?.error as Boom)?.output?.statusCode
296
308
 
297
- // 401 is normal for fresh credentials — always reconnect during pairing
298
309
  if (statusCode === DisconnectReason.forbidden) {
299
310
  clearTimeout(overallTimeout)
300
- outerReject(new WhatsAppError('Account banned or restricted.', 'forbidden'))
311
+ const err = new WhatsAppError('Account banned or restricted.', 'forbidden')
312
+ if (codeReturned) {
313
+ this.authCompleteReject?.(err)
314
+ } else {
315
+ outerReject(err)
316
+ }
301
317
  return
302
318
  }
303
319
 
304
320
  if (codeReturned) {
305
- postPairingRetries++
306
- if (postPairingRetries > MAX_POST_PAIRING_RETRIES) {
307
- clearTimeout(overallTimeout)
308
- outerReject(new WhatsAppError('Post-pairing reconnection failed.', 'post_pairing_failed'))
309
- return
310
- }
321
+ // Post-pairing: rely on overallTimeout (120s) instead of retry cap.
322
+ // Baileys cycles the connection during pairing — keep reconnecting
323
+ // until the user enters the code and the connection opens.
311
324
  setTimeout(() => attempt().catch(onError), 2000)
312
325
  return
313
326
  }
@@ -315,10 +328,12 @@ export class WhatsAppClient {
315
328
  retries++
316
329
  if (retries > MAX_RETRIES) {
317
330
  clearTimeout(overallTimeout)
318
- outerReject(new WhatsAppError(
319
- `Connection failed after ${MAX_RETRIES} retries: ${lastDisconnect?.error?.message ?? 'unknown'}`,
320
- 'max_retries',
321
- ))
331
+ outerReject(
332
+ new WhatsAppError(
333
+ `Connection failed after ${MAX_RETRIES} retries: ${lastDisconnect?.error?.message ?? 'unknown'}`,
334
+ 'max_retries',
335
+ ),
336
+ )
322
337
  return
323
338
  }
324
339
 
@@ -351,8 +366,120 @@ export class WhatsAppClient {
351
366
  })
352
367
  }
353
368
 
369
+ async connectForQR(onQR: (qr: string) => void | Promise<void>): Promise<{ waitForAuth: () => Promise<void> }> {
370
+ this.ensureAuth()
371
+ this.pendingPromise = new Promise<void>((resolve) => {
372
+ this.pendingResolve = resolve
373
+ })
374
+
375
+ const authCompletePromise = new Promise<void>((resolve, reject) => {
376
+ this.authCompleteResolve = resolve
377
+ this.authCompleteReject = reject
378
+ })
379
+
380
+ const MAX_RETRIES = 5
381
+ let retries = 0
382
+
383
+ return new Promise((outerResolve, outerReject) => {
384
+ let qrEmitted = false
385
+
386
+ const overallTimeout = setTimeout(() => {
387
+ const err = new WhatsAppError('QR auth timed out', 'qr_timeout')
388
+ if (qrEmitted) {
389
+ this.authCompleteReject?.(err)
390
+ } else {
391
+ outerReject(err)
392
+ }
393
+ }, 120_000)
394
+
395
+ const onError = (err: unknown): void => {
396
+ clearTimeout(overallTimeout)
397
+ const error = err instanceof Error ? err : new WhatsAppError(String(err), 'qr_error')
398
+ if (qrEmitted) {
399
+ this.authCompleteReject?.(error)
400
+ } else {
401
+ outerReject(error)
402
+ }
403
+ }
404
+
405
+ const attempt = async (): Promise<void> => {
406
+ const { sock, saveCreds } = await this.createSocket()
407
+ this.setupBufferListeners(sock, saveCreds)
408
+
409
+ sock.ev.on('connection.update', async (update: Partial<ConnectionState>) => {
410
+ const { connection, lastDisconnect, qr } = update
411
+
412
+ if (qr) {
413
+ try {
414
+ await onQR(qr)
415
+ } catch (err) {
416
+ onError(err)
417
+ return
418
+ }
419
+ if (!qrEmitted) {
420
+ qrEmitted = true
421
+ outerResolve({ waitForAuth: () => authCompletePromise })
422
+ }
423
+ }
424
+
425
+ if (connection === 'open') {
426
+ clearTimeout(overallTimeout)
427
+ this.authCompleteResolve?.()
428
+ if (!qrEmitted) {
429
+ qrEmitted = true
430
+ outerResolve({ waitForAuth: async () => {} })
431
+ }
432
+ }
433
+
434
+ if (connection === 'close') {
435
+ this.cleanupSocket(sock)
436
+
437
+ const statusCode = (lastDisconnect?.error as Boom)?.output?.statusCode
438
+
439
+ if (statusCode === DisconnectReason.forbidden) {
440
+ clearTimeout(overallTimeout)
441
+ const err = new WhatsAppError('Account banned or restricted.', 'forbidden')
442
+ if (qrEmitted) {
443
+ this.authCompleteReject?.(err)
444
+ } else {
445
+ outerReject(err)
446
+ }
447
+ return
448
+ }
449
+
450
+ if (qrEmitted) {
451
+ // Post-QR scan: keep reconnecting until auth completes
452
+ setTimeout(() => attempt().catch(onError), 2000)
453
+ return
454
+ }
455
+
456
+ retries++
457
+ if (retries > MAX_RETRIES) {
458
+ clearTimeout(overallTimeout)
459
+ outerReject(
460
+ new WhatsAppError(
461
+ `Connection failed after ${MAX_RETRIES} retries: ${lastDisconnect?.error?.message ?? 'unknown'}`,
462
+ 'max_retries',
463
+ ),
464
+ )
465
+ return
466
+ }
467
+
468
+ setTimeout(() => attempt().catch(onError), 1000)
469
+ }
470
+ })
471
+ }
472
+
473
+ attempt().catch(onError)
474
+ })
475
+ }
476
+
354
477
  private cleanupSocket(sock: WASocket): void {
355
- try { sock.end(undefined) } catch { /* already closed */ }
478
+ try {
479
+ sock.end(undefined)
480
+ } catch {
481
+ /* already closed */
482
+ }
356
483
  }
357
484
 
358
485
  private setupBufferListeners(sock: WASocket, saveCreds: () => Promise<void>): void {
@@ -464,9 +591,7 @@ export class WhatsAppClient {
464
591
  async searchChats(query: string, limit?: number): Promise<WhatsAppChatSummary[]> {
465
592
  const allChats = await this.listChats()
466
593
  const lower = query.toLowerCase()
467
- const filtered = allChats.filter((c) =>
468
- c.name.toLowerCase().includes(lower) || c.id.toLowerCase().includes(lower),
469
- )
594
+ const filtered = allChats.filter((c) => c.name.toLowerCase().includes(lower) || c.id.toLowerCase().includes(lower))
470
595
  return limit ? filtered.slice(0, limit) : filtered
471
596
  }
472
597
 
@@ -504,6 +629,24 @@ export class WhatsAppClient {
504
629
  return summarizeMessage(result)
505
630
  }
506
631
 
632
+ async getProfile(): Promise<{ id: string; name: string | null; phone_number: string | null }> {
633
+ this.ensureAuth()
634
+ if (!this.sock) {
635
+ throw new WhatsAppError('Not connected. Call connect() first.', 'not_connected')
636
+ }
637
+ const user = this.sock.user
638
+ if (!user) {
639
+ throw new WhatsAppError('Not connected. Call connect() first.', 'not_connected')
640
+ }
641
+ const jid = user.id ?? ''
642
+ const phone = jid.includes(':') ? jid.split(':')[0] : jid.split('@')[0]
643
+ return {
644
+ id: jid,
645
+ name: user.name ?? null,
646
+ phone_number: phone || null,
647
+ }
648
+ }
649
+
507
650
  getSocket(): WASocket | null {
508
651
  return this.sock
509
652
  }
@@ -0,0 +1,311 @@
1
+ import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from 'bun:test'
2
+
3
+ const originalConsoleLog = console.log
4
+
5
+ mock.module('@/shared/utils/error-handler', () => ({
6
+ handleError: (err: Error) => { throw err },
7
+ }))
8
+
9
+ const mockGetAccount = mock(() => Promise.resolve(null))
10
+ const mockListAccounts = mock(() => Promise.resolve([]))
11
+ const mockSetCurrent = mock(() => Promise.resolve(false))
12
+ const mockRemoveAccount = mock(() => Promise.resolve(false))
13
+ const mockGetAccountPaths = mock(() => ({ account_dir: '/tmp/test', auth_dir: '/tmp/test/auth' }))
14
+
15
+ mock.module('../credential-manager', () => ({
16
+ WhatsAppCredentialManager: class {
17
+ getAccount = mockGetAccount
18
+ listAccounts = mockListAccounts
19
+ setCurrent = mockSetCurrent
20
+ removeAccount = mockRemoveAccount
21
+ getAccountPaths = mockGetAccountPaths
22
+ ensureAccountPaths = mock(() => Promise.resolve({ account_dir: '/tmp/test', auth_dir: '/tmp/test/auth' }))
23
+ setAccount = mock(() => Promise.resolve())
24
+ },
25
+ }))
26
+
27
+ const mockConnect = mock(() => Promise.resolve())
28
+ const mockClose = mock(() => Promise.resolve())
29
+ const mockGetSocket = mock(() => null)
30
+ const mockLogin = mock(function (this: unknown) { return Promise.resolve(this) })
31
+
32
+ mock.module('../client', () => ({
33
+ WhatsAppClient: class {
34
+ login = mockLogin
35
+ connect = mockConnect
36
+ close = mockClose
37
+ getSocket = mockGetSocket
38
+ },
39
+ }))
40
+
41
+ import { authCommand } from './auth'
42
+
43
+ describe('auth commands', () => {
44
+ let consoleLogSpy: ReturnType<typeof mock>
45
+ let processExitSpy: ReturnType<typeof spyOn>
46
+
47
+ beforeEach(() => {
48
+ mockGetAccount.mockReset()
49
+ mockListAccounts.mockReset()
50
+ mockSetCurrent.mockReset()
51
+ mockRemoveAccount.mockReset()
52
+ mockGetAccountPaths.mockReset()
53
+ mockConnect.mockReset()
54
+ mockClose.mockReset()
55
+ mockGetSocket.mockReset()
56
+ mockLogin.mockReset()
57
+
58
+ mockGetAccount.mockImplementation(() => Promise.resolve(null))
59
+ mockListAccounts.mockImplementation(() => Promise.resolve([]))
60
+ mockSetCurrent.mockImplementation(() => Promise.resolve(false))
61
+ mockRemoveAccount.mockImplementation(() => Promise.resolve(false))
62
+ mockGetAccountPaths.mockImplementation(() => ({ account_dir: '/tmp/test', auth_dir: '/tmp/test/auth' }))
63
+ mockConnect.mockImplementation(() => Promise.resolve())
64
+ mockClose.mockImplementation(() => Promise.resolve())
65
+ mockGetSocket.mockImplementation(() => null)
66
+ mockLogin.mockImplementation(function (this: unknown) { return Promise.resolve(this) })
67
+
68
+ consoleLogSpy = mock((..._args: unknown[]) => {}); console.log = consoleLogSpy
69
+ processExitSpy = spyOn(process, 'exit').mockImplementation((_code?: number) => {
70
+ throw new Error(`process.exit(${_code})`)
71
+ })
72
+ processExitSpy.mockClear()
73
+ })
74
+
75
+ afterEach(() => {
76
+ console.log = originalConsoleLog
77
+ processExitSpy.mockRestore()
78
+ })
79
+
80
+ // list has no throwing tests — run first to avoid Commander state corruption
81
+ describe('list', () => {
82
+ test('outputs empty array when no accounts', async () => {
83
+ mockListAccounts.mockImplementation(() => Promise.resolve([]))
84
+
85
+ await authCommand.parseAsync(['list'], { from: 'user' })
86
+
87
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
88
+ expect(output).toEqual([])
89
+ })
90
+
91
+ test('outputs accounts list with is_current flag', async () => {
92
+ mockListAccounts.mockImplementation(() =>
93
+ Promise.resolve([
94
+ {
95
+ account_id: 'plus-12025551234',
96
+ phone_number: '+12025551234',
97
+ name: 'Alice',
98
+ created_at: '2024-01-01T00:00:00.000Z',
99
+ updated_at: '2024-01-01T00:00:00.000Z',
100
+ is_current: true,
101
+ },
102
+ {
103
+ account_id: 'plus-19995551234',
104
+ phone_number: '+19995551234',
105
+ name: null,
106
+ created_at: '2024-01-02T00:00:00.000Z',
107
+ updated_at: '2024-01-02T00:00:00.000Z',
108
+ is_current: false,
109
+ },
110
+ ]),
111
+ )
112
+
113
+ await authCommand.parseAsync(['list'], { from: 'user' })
114
+
115
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
116
+ expect(output).toHaveLength(2)
117
+ expect(output[0].account_id).toBe('plus-12025551234')
118
+ expect(output[0].is_current).toBe(true)
119
+ expect(output[1].account_id).toBe('plus-19995551234')
120
+ expect(output[1].is_current).toBe(false)
121
+ })
122
+ })
123
+
124
+ // use: success test first, then throwing test last
125
+ describe('use', () => {
126
+ test('switches to specified account and outputs success', async () => {
127
+ mockSetCurrent.mockImplementation(() => Promise.resolve(true))
128
+ mockGetAccount.mockImplementation(() =>
129
+ Promise.resolve({
130
+ account_id: 'plus-12025551234',
131
+ phone_number: '+12025551234',
132
+ name: 'Alice',
133
+ created_at: '2024-01-01T00:00:00.000Z',
134
+ updated_at: '2024-01-01T00:00:00.000Z',
135
+ }),
136
+ )
137
+
138
+ await authCommand.parseAsync(['use', 'plus-12025551234'], { from: 'user' })
139
+
140
+ expect(mockSetCurrent).toHaveBeenCalledWith('plus-12025551234')
141
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
142
+ expect(output.success).toBe(true)
143
+ expect(output.account_id).toBe('plus-12025551234')
144
+ })
145
+
146
+ test('outputs error and exits when account not found', async () => {
147
+ mockSetCurrent.mockImplementation(() => Promise.resolve(false))
148
+
149
+ await expect(
150
+ authCommand.parseAsync(['use', 'nonexistent'], { from: 'user' }),
151
+ ).rejects.toThrow('process.exit(1)')
152
+
153
+ expect(processExitSpy).toHaveBeenCalledWith(1)
154
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
155
+ expect(output.error).toContain('nonexistent')
156
+ })
157
+ })
158
+
159
+ // status: no-account tests first, --account tests last (avoids Commander option caching)
160
+ describe('status', () => {
161
+ test('outputs account info when account exists', async () => {
162
+ mockGetAccount.mockImplementation(() =>
163
+ Promise.resolve({
164
+ account_id: 'plus-12025551234',
165
+ phone_number: '+12025551234',
166
+ name: 'Test User',
167
+ created_at: '2024-01-01T00:00:00.000Z',
168
+ updated_at: '2024-01-01T00:00:00.000Z',
169
+ }),
170
+ )
171
+
172
+ await authCommand.parseAsync(['status'], { from: 'user' })
173
+
174
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
175
+ expect(output.account_id).toBe('plus-12025551234')
176
+ expect(output.phone_number).toBe('+12025551234')
177
+ expect(output.name).toBe('Test User')
178
+ })
179
+
180
+ test('outputs error and exits when no account configured', async () => {
181
+ mockGetAccount.mockImplementation(() => Promise.resolve(null))
182
+
183
+ await expect(
184
+ authCommand.parseAsync(['status'], { from: 'user' }),
185
+ ).rejects.toThrow('process.exit(1)')
186
+
187
+ expect(processExitSpy).toHaveBeenCalledWith(1)
188
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
189
+ expect(output.error).toBeDefined()
190
+ expect(output.error).toContain('No WhatsApp account configured')
191
+ })
192
+
193
+ test('passes --account option to getAccount', async () => {
194
+ mockGetAccount.mockImplementation((id?: string) => {
195
+ if (id === 'plus-19995551234') {
196
+ return Promise.resolve({
197
+ account_id: 'plus-19995551234',
198
+ phone_number: '+19995551234',
199
+ name: null,
200
+ created_at: '2024-01-01T00:00:00.000Z',
201
+ updated_at: '2024-01-01T00:00:00.000Z',
202
+ })
203
+ }
204
+ return Promise.resolve(null)
205
+ })
206
+
207
+ await authCommand.parseAsync(['status', '--account', 'plus-19995551234'], { from: 'user' })
208
+
209
+ expect(mockGetAccount).toHaveBeenCalledWith('plus-19995551234')
210
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
211
+ expect(output.account_id).toBe('plus-19995551234')
212
+ })
213
+
214
+ test('outputs error for specific missing account', async () => {
215
+ mockGetAccount.mockImplementation(() => Promise.resolve(null))
216
+
217
+ await expect(
218
+ authCommand.parseAsync(['status', '--account', 'missing-id'], { from: 'user' }),
219
+ ).rejects.toThrow('process.exit(1)')
220
+
221
+ expect(processExitSpy).toHaveBeenCalledWith(1)
222
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
223
+ expect(output.error).toContain('missing-id')
224
+ })
225
+ })
226
+
227
+ // logout: no-account tests first, --account test last (avoids Commander option caching)
228
+ describe('logout', () => {
229
+ test('removes current account and outputs success', async () => {
230
+ mockGetAccount.mockImplementation(() =>
231
+ Promise.resolve({
232
+ account_id: 'plus-12025551234',
233
+ phone_number: '+12025551234',
234
+ name: 'Alice',
235
+ created_at: '2024-01-01T00:00:00.000Z',
236
+ updated_at: '2024-01-01T00:00:00.000Z',
237
+ }),
238
+ )
239
+ mockRemoveAccount.mockImplementation(() => Promise.resolve(true))
240
+
241
+ await expect(
242
+ authCommand.parseAsync(['logout'], { from: 'user' }),
243
+ ).rejects.toThrow('process.exit(0)')
244
+
245
+ expect(mockRemoveAccount).toHaveBeenCalledWith('plus-12025551234')
246
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
247
+ expect(output.success).toBe(true)
248
+ expect(output.account_id).toBe('plus-12025551234')
249
+ expect(output.logged_out).toBe(true)
250
+ })
251
+
252
+ test('outputs error and exits when no account configured', async () => {
253
+ mockGetAccount.mockImplementation(() => Promise.resolve(null))
254
+
255
+ await expect(
256
+ authCommand.parseAsync(['logout'], { from: 'user' }),
257
+ ).rejects.toThrow('process.exit(1)')
258
+
259
+ expect(processExitSpy).toHaveBeenCalledWith(1)
260
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
261
+ expect(output.error).toBeDefined()
262
+ })
263
+
264
+ test('proceeds with local cleanup even when client connection fails', async () => {
265
+ mockGetAccount.mockImplementation(() =>
266
+ Promise.resolve({
267
+ account_id: 'plus-12025551234',
268
+ phone_number: '+12025551234',
269
+ name: 'Alice',
270
+ created_at: '2024-01-01T00:00:00.000Z',
271
+ updated_at: '2024-01-01T00:00:00.000Z',
272
+ }),
273
+ )
274
+ mockConnect.mockImplementation(() => Promise.reject(new Error('Connection failed')))
275
+ mockRemoveAccount.mockImplementation(() => Promise.resolve(true))
276
+
277
+ await expect(
278
+ authCommand.parseAsync(['logout'], { from: 'user' }),
279
+ ).rejects.toThrow('process.exit(0)')
280
+
281
+ expect(mockRemoveAccount).toHaveBeenCalledWith('plus-12025551234')
282
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
283
+ expect(output.success).toBe(true)
284
+ })
285
+
286
+ test('removes specific account with --account flag', async () => {
287
+ mockGetAccount.mockImplementation((id?: string) => {
288
+ if (id === 'plus-19995551234') {
289
+ return Promise.resolve({
290
+ account_id: 'plus-19995551234',
291
+ phone_number: '+19995551234',
292
+ name: null,
293
+ created_at: '2024-01-01T00:00:00.000Z',
294
+ updated_at: '2024-01-01T00:00:00.000Z',
295
+ })
296
+ }
297
+ return Promise.resolve(null)
298
+ })
299
+ mockRemoveAccount.mockImplementation(() => Promise.resolve(true))
300
+
301
+ await expect(
302
+ authCommand.parseAsync(['logout', '--account', 'plus-19995551234'], { from: 'user' }),
303
+ ).rejects.toThrow('process.exit(0)')
304
+
305
+ expect(mockRemoveAccount).toHaveBeenCalledWith('plus-19995551234')
306
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
307
+ expect(output.success).toBe(true)
308
+ expect(output.account_id).toBe('plus-19995551234')
309
+ })
310
+ })
311
+ })