agent-messenger 2.4.0 → 2.6.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 (359) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.github/workflows/ci.yml +35 -0
  3. package/.github/workflows/release.yml +0 -12
  4. package/README.md +3 -3
  5. package/bun.lock +10 -2
  6. package/dist/package.json +3 -1
  7. package/dist/src/platforms/channeltalk/cli.d.ts.map +1 -1
  8. package/dist/src/platforms/channeltalk/cli.js +2 -1
  9. package/dist/src/platforms/channeltalk/cli.js.map +1 -1
  10. package/dist/src/platforms/channeltalk/commands/index.d.ts +1 -0
  11. package/dist/src/platforms/channeltalk/commands/index.d.ts.map +1 -1
  12. package/dist/src/platforms/channeltalk/commands/index.js +1 -0
  13. package/dist/src/platforms/channeltalk/commands/index.js.map +1 -1
  14. package/dist/src/platforms/channeltalk/commands/whoami.d.ts +22 -0
  15. package/dist/src/platforms/channeltalk/commands/whoami.d.ts.map +1 -0
  16. package/dist/src/platforms/channeltalk/commands/whoami.js +40 -0
  17. package/dist/src/platforms/channeltalk/commands/whoami.js.map +1 -0
  18. package/dist/src/platforms/channeltalkbot/cli.d.ts.map +1 -1
  19. package/dist/src/platforms/channeltalkbot/cli.js +2 -1
  20. package/dist/src/platforms/channeltalkbot/cli.js.map +1 -1
  21. package/dist/src/platforms/channeltalkbot/commands/index.d.ts +1 -0
  22. package/dist/src/platforms/channeltalkbot/commands/index.d.ts.map +1 -1
  23. package/dist/src/platforms/channeltalkbot/commands/index.js +1 -0
  24. package/dist/src/platforms/channeltalkbot/commands/index.js.map +1 -1
  25. package/dist/src/platforms/channeltalkbot/commands/whoami.d.ts +13 -0
  26. package/dist/src/platforms/channeltalkbot/commands/whoami.d.ts.map +1 -0
  27. package/dist/src/platforms/channeltalkbot/commands/whoami.js +31 -0
  28. package/dist/src/platforms/channeltalkbot/commands/whoami.js.map +1 -0
  29. package/dist/src/platforms/discord/cli.d.ts.map +1 -1
  30. package/dist/src/platforms/discord/cli.js +2 -1
  31. package/dist/src/platforms/discord/cli.js.map +1 -1
  32. package/dist/src/platforms/discord/commands/index.d.ts +1 -0
  33. package/dist/src/platforms/discord/commands/index.d.ts.map +1 -1
  34. package/dist/src/platforms/discord/commands/index.js +1 -0
  35. package/dist/src/platforms/discord/commands/index.js.map +1 -1
  36. package/dist/src/platforms/discord/commands/whoami.d.ts +6 -0
  37. package/dist/src/platforms/discord/commands/whoami.d.ts.map +1 -0
  38. package/dist/src/platforms/discord/commands/whoami.js +33 -0
  39. package/dist/src/platforms/discord/commands/whoami.js.map +1 -0
  40. package/dist/src/platforms/discordbot/cli.d.ts.map +1 -1
  41. package/dist/src/platforms/discordbot/cli.js +2 -1
  42. package/dist/src/platforms/discordbot/cli.js.map +1 -1
  43. package/dist/src/platforms/discordbot/commands/index.d.ts +1 -0
  44. package/dist/src/platforms/discordbot/commands/index.d.ts.map +1 -1
  45. package/dist/src/platforms/discordbot/commands/index.js +1 -0
  46. package/dist/src/platforms/discordbot/commands/index.js.map +1 -1
  47. package/dist/src/platforms/discordbot/commands/whoami.d.ts +14 -0
  48. package/dist/src/platforms/discordbot/commands/whoami.d.ts.map +1 -0
  49. package/dist/src/platforms/discordbot/commands/whoami.js +32 -0
  50. package/dist/src/platforms/discordbot/commands/whoami.js.map +1 -0
  51. package/dist/src/platforms/instagram/cli.d.ts.map +1 -1
  52. package/dist/src/platforms/instagram/cli.js +2 -1
  53. package/dist/src/platforms/instagram/cli.js.map +1 -1
  54. package/dist/src/platforms/instagram/client.d.ts +6 -0
  55. package/dist/src/platforms/instagram/client.d.ts.map +1 -1
  56. package/dist/src/platforms/instagram/client.js +12 -0
  57. package/dist/src/platforms/instagram/client.js.map +1 -1
  58. package/dist/src/platforms/instagram/commands/index.d.ts +1 -0
  59. package/dist/src/platforms/instagram/commands/index.d.ts.map +1 -1
  60. package/dist/src/platforms/instagram/commands/index.js +1 -0
  61. package/dist/src/platforms/instagram/commands/index.js.map +1 -1
  62. package/dist/src/platforms/instagram/commands/whoami.d.ts +7 -0
  63. package/dist/src/platforms/instagram/commands/whoami.d.ts.map +1 -0
  64. package/dist/src/platforms/instagram/commands/whoami.js +19 -0
  65. package/dist/src/platforms/instagram/commands/whoami.js.map +1 -0
  66. package/dist/src/platforms/kakaotalk/cli.js +2 -2
  67. package/dist/src/platforms/kakaotalk/cli.js.map +1 -1
  68. package/dist/src/platforms/kakaotalk/client.d.ts +4 -1
  69. package/dist/src/platforms/kakaotalk/client.d.ts.map +1 -1
  70. package/dist/src/platforms/kakaotalk/client.js +193 -27
  71. package/dist/src/platforms/kakaotalk/client.js.map +1 -1
  72. package/dist/src/platforms/kakaotalk/commands/auth.d.ts.map +1 -1
  73. package/dist/src/platforms/kakaotalk/commands/auth.js +24 -55
  74. package/dist/src/platforms/kakaotalk/commands/auth.js.map +1 -1
  75. package/dist/src/platforms/kakaotalk/commands/index.d.ts +1 -1
  76. package/dist/src/platforms/kakaotalk/commands/index.d.ts.map +1 -1
  77. package/dist/src/platforms/kakaotalk/commands/index.js +1 -1
  78. package/dist/src/platforms/kakaotalk/commands/index.js.map +1 -1
  79. package/dist/src/platforms/kakaotalk/commands/whoami.d.ts +3 -0
  80. package/dist/src/platforms/kakaotalk/commands/whoami.d.ts.map +1 -0
  81. package/dist/src/platforms/kakaotalk/commands/{profile.js → whoami.js} +5 -5
  82. package/dist/src/platforms/kakaotalk/commands/whoami.js.map +1 -0
  83. package/dist/src/platforms/kakaotalk/credential-manager.d.ts.map +1 -1
  84. package/dist/src/platforms/kakaotalk/credential-manager.js +1 -0
  85. package/dist/src/platforms/kakaotalk/credential-manager.js.map +1 -1
  86. package/dist/src/platforms/kakaotalk/index.d.ts +1 -1
  87. package/dist/src/platforms/kakaotalk/index.d.ts.map +1 -1
  88. package/dist/src/platforms/kakaotalk/index.js.map +1 -1
  89. package/dist/src/platforms/kakaotalk/listener.js +2 -2
  90. package/dist/src/platforms/kakaotalk/listener.js.map +1 -1
  91. package/dist/src/platforms/kakaotalk/protocol/config.d.ts +8 -2
  92. package/dist/src/platforms/kakaotalk/protocol/config.d.ts.map +1 -1
  93. package/dist/src/platforms/kakaotalk/protocol/config.js +15 -2
  94. package/dist/src/platforms/kakaotalk/protocol/config.js.map +1 -1
  95. package/dist/src/platforms/kakaotalk/protocol/session.d.ts +6 -2
  96. package/dist/src/platforms/kakaotalk/protocol/session.d.ts.map +1 -1
  97. package/dist/src/platforms/kakaotalk/protocol/session.js +37 -15
  98. package/dist/src/platforms/kakaotalk/protocol/session.js.map +1 -1
  99. package/dist/src/platforms/kakaotalk/protocol/types.d.ts +17 -0
  100. package/dist/src/platforms/kakaotalk/protocol/types.d.ts.map +1 -1
  101. package/dist/src/platforms/kakaotalk/protocol/types.js.map +1 -1
  102. package/dist/src/platforms/kakaotalk/types.d.ts +22 -0
  103. package/dist/src/platforms/kakaotalk/types.d.ts.map +1 -1
  104. package/dist/src/platforms/kakaotalk/types.js +7 -0
  105. package/dist/src/platforms/kakaotalk/types.js.map +1 -1
  106. package/dist/src/platforms/line/cli.js +2 -2
  107. package/dist/src/platforms/line/cli.js.map +1 -1
  108. package/dist/src/platforms/line/commands/auth.d.ts.map +1 -1
  109. package/dist/src/platforms/line/commands/auth.js +9 -59
  110. package/dist/src/platforms/line/commands/auth.js.map +1 -1
  111. package/dist/src/platforms/line/commands/index.d.ts +1 -1
  112. package/dist/src/platforms/line/commands/index.d.ts.map +1 -1
  113. package/dist/src/platforms/line/commands/index.js +1 -1
  114. package/dist/src/platforms/line/commands/index.js.map +1 -1
  115. package/dist/src/platforms/line/commands/whoami.d.ts +3 -0
  116. package/dist/src/platforms/line/commands/whoami.d.ts.map +1 -0
  117. package/dist/src/platforms/line/commands/{profile.js → whoami.js} +5 -5
  118. package/dist/src/platforms/line/commands/whoami.js.map +1 -0
  119. package/dist/src/platforms/slack/cli.d.ts.map +1 -1
  120. package/dist/src/platforms/slack/cli.js +2 -1
  121. package/dist/src/platforms/slack/cli.js.map +1 -1
  122. package/dist/src/platforms/slack/commands/index.d.ts +1 -0
  123. package/dist/src/platforms/slack/commands/index.d.ts.map +1 -1
  124. package/dist/src/platforms/slack/commands/index.js +1 -0
  125. package/dist/src/platforms/slack/commands/index.js.map +1 -1
  126. package/dist/src/platforms/slack/commands/whoami.d.ts +6 -0
  127. package/dist/src/platforms/slack/commands/whoami.d.ts.map +1 -0
  128. package/dist/src/platforms/slack/commands/whoami.js +39 -0
  129. package/dist/src/platforms/slack/commands/whoami.js.map +1 -0
  130. package/dist/src/platforms/slackbot/cli.d.ts.map +1 -1
  131. package/dist/src/platforms/slackbot/cli.js +2 -1
  132. package/dist/src/platforms/slackbot/cli.js.map +1 -1
  133. package/dist/src/platforms/slackbot/commands/index.d.ts +1 -0
  134. package/dist/src/platforms/slackbot/commands/index.d.ts.map +1 -1
  135. package/dist/src/platforms/slackbot/commands/index.js +1 -0
  136. package/dist/src/platforms/slackbot/commands/index.js.map +1 -1
  137. package/dist/src/platforms/slackbot/commands/whoami.d.ts +14 -0
  138. package/dist/src/platforms/slackbot/commands/whoami.d.ts.map +1 -0
  139. package/dist/src/platforms/slackbot/commands/whoami.js +32 -0
  140. package/dist/src/platforms/slackbot/commands/whoami.js.map +1 -0
  141. package/dist/src/platforms/teams/cli.d.ts.map +1 -1
  142. package/dist/src/platforms/teams/cli.js +2 -1
  143. package/dist/src/platforms/teams/cli.js.map +1 -1
  144. package/dist/src/platforms/teams/commands/index.d.ts +1 -0
  145. package/dist/src/platforms/teams/commands/index.d.ts.map +1 -1
  146. package/dist/src/platforms/teams/commands/index.js +1 -0
  147. package/dist/src/platforms/teams/commands/index.js.map +1 -1
  148. package/dist/src/platforms/teams/commands/whoami.d.ts +6 -0
  149. package/dist/src/platforms/teams/commands/whoami.d.ts.map +1 -0
  150. package/dist/src/platforms/teams/commands/whoami.js +30 -0
  151. package/dist/src/platforms/teams/commands/whoami.js.map +1 -0
  152. package/dist/src/platforms/telegram/cli.d.ts.map +1 -1
  153. package/dist/src/platforms/telegram/cli.js +2 -1
  154. package/dist/src/platforms/telegram/cli.js.map +1 -1
  155. package/dist/src/platforms/telegram/commands/index.d.ts +1 -0
  156. package/dist/src/platforms/telegram/commands/index.d.ts.map +1 -1
  157. package/dist/src/platforms/telegram/commands/index.js +1 -0
  158. package/dist/src/platforms/telegram/commands/index.js.map +1 -1
  159. package/dist/src/platforms/telegram/commands/whoami.d.ts +7 -0
  160. package/dist/src/platforms/telegram/commands/whoami.d.ts.map +1 -0
  161. package/dist/src/platforms/telegram/commands/whoami.js +27 -0
  162. package/dist/src/platforms/telegram/commands/whoami.js.map +1 -0
  163. package/dist/src/platforms/webex/cli.d.ts.map +1 -1
  164. package/dist/src/platforms/webex/cli.js +2 -1
  165. package/dist/src/platforms/webex/cli.js.map +1 -1
  166. package/dist/src/platforms/webex/commands/index.d.ts +1 -0
  167. package/dist/src/platforms/webex/commands/index.d.ts.map +1 -1
  168. package/dist/src/platforms/webex/commands/index.js +1 -0
  169. package/dist/src/platforms/webex/commands/index.js.map +1 -1
  170. package/dist/src/platforms/webex/commands/message.js +1 -1
  171. package/dist/src/platforms/webex/commands/message.js.map +1 -1
  172. package/dist/src/platforms/webex/commands/whoami.d.ts +6 -0
  173. package/dist/src/platforms/webex/commands/whoami.d.ts.map +1 -0
  174. package/dist/src/platforms/webex/commands/whoami.js +30 -0
  175. package/dist/src/platforms/webex/commands/whoami.js.map +1 -0
  176. package/dist/src/platforms/wechatbot/cli.d.ts.map +1 -1
  177. package/dist/src/platforms/wechatbot/cli.js +2 -1
  178. package/dist/src/platforms/wechatbot/cli.js.map +1 -1
  179. package/dist/src/platforms/wechatbot/commands/index.d.ts +1 -0
  180. package/dist/src/platforms/wechatbot/commands/index.d.ts.map +1 -1
  181. package/dist/src/platforms/wechatbot/commands/index.js +1 -0
  182. package/dist/src/platforms/wechatbot/commands/index.js.map +1 -1
  183. package/dist/src/platforms/wechatbot/commands/whoami.d.ts +12 -0
  184. package/dist/src/platforms/wechatbot/commands/whoami.d.ts.map +1 -0
  185. package/dist/src/platforms/wechatbot/commands/whoami.js +33 -0
  186. package/dist/src/platforms/wechatbot/commands/whoami.js.map +1 -0
  187. package/dist/src/platforms/whatsapp/cli.d.ts.map +1 -1
  188. package/dist/src/platforms/whatsapp/cli.js +2 -1
  189. package/dist/src/platforms/whatsapp/cli.js.map +1 -1
  190. package/dist/src/platforms/whatsapp/client.d.ts +8 -0
  191. package/dist/src/platforms/whatsapp/client.d.ts.map +1 -1
  192. package/dist/src/platforms/whatsapp/client.js +116 -8
  193. package/dist/src/platforms/whatsapp/client.js.map +1 -1
  194. package/dist/src/platforms/whatsapp/commands/auth.d.ts.map +1 -1
  195. package/dist/src/platforms/whatsapp/commands/auth.js +115 -45
  196. package/dist/src/platforms/whatsapp/commands/auth.js.map +1 -1
  197. package/dist/src/platforms/whatsapp/commands/index.d.ts +1 -0
  198. package/dist/src/platforms/whatsapp/commands/index.d.ts.map +1 -1
  199. package/dist/src/platforms/whatsapp/commands/index.js +1 -0
  200. package/dist/src/platforms/whatsapp/commands/index.js.map +1 -1
  201. package/dist/src/platforms/whatsapp/commands/shared.js +2 -2
  202. package/dist/src/platforms/whatsapp/commands/shared.js.map +1 -1
  203. package/dist/src/platforms/whatsapp/commands/whoami.d.ts +7 -0
  204. package/dist/src/platforms/whatsapp/commands/whoami.d.ts.map +1 -0
  205. package/dist/src/platforms/whatsapp/commands/whoami.js +19 -0
  206. package/dist/src/platforms/whatsapp/commands/whoami.js.map +1 -0
  207. package/dist/src/platforms/whatsapp/ensure-auth.js +2 -2
  208. package/dist/src/platforms/whatsapp/ensure-auth.js.map +1 -1
  209. package/dist/src/platforms/whatsappbot/cli.d.ts.map +1 -1
  210. package/dist/src/platforms/whatsappbot/cli.js +2 -1
  211. package/dist/src/platforms/whatsappbot/cli.js.map +1 -1
  212. package/dist/src/platforms/whatsappbot/commands/index.d.ts +1 -0
  213. package/dist/src/platforms/whatsappbot/commands/index.d.ts.map +1 -1
  214. package/dist/src/platforms/whatsappbot/commands/index.js +1 -0
  215. package/dist/src/platforms/whatsappbot/commands/index.js.map +1 -1
  216. package/dist/src/platforms/whatsappbot/commands/whoami.d.ts +17 -0
  217. package/dist/src/platforms/whatsappbot/commands/whoami.d.ts.map +1 -0
  218. package/dist/src/platforms/whatsappbot/commands/whoami.js +39 -0
  219. package/dist/src/platforms/whatsappbot/commands/whoami.js.map +1 -0
  220. package/dist/src/shared/utils/qr.d.ts +15 -0
  221. package/dist/src/shared/utils/qr.d.ts.map +1 -0
  222. package/dist/src/shared/utils/qr.js +74 -0
  223. package/dist/src/shared/utils/qr.js.map +1 -0
  224. package/dist/src/tui/adapters/kakaotalk-adapter.d.ts.map +1 -1
  225. package/dist/src/tui/adapters/kakaotalk-adapter.js +5 -2
  226. package/dist/src/tui/adapters/kakaotalk-adapter.js.map +1 -1
  227. package/dist/src/tui/adapters/whatsapp-adapter.d.ts.map +1 -1
  228. package/dist/src/tui/adapters/whatsapp-adapter.js +20 -15
  229. package/dist/src/tui/adapters/whatsapp-adapter.js.map +1 -1
  230. package/docs/content/docs/cli/channeltalk.mdx +11 -0
  231. package/docs/content/docs/cli/channeltalkbot.mdx +9 -0
  232. package/docs/content/docs/cli/discord.mdx +10 -0
  233. package/docs/content/docs/cli/discordbot.mdx +9 -0
  234. package/docs/content/docs/cli/instagram.mdx +11 -0
  235. package/docs/content/docs/cli/kakaotalk.mdx +27 -39
  236. package/docs/content/docs/cli/line.mdx +4 -4
  237. package/docs/content/docs/cli/slack.mdx +10 -0
  238. package/docs/content/docs/cli/slackbot.mdx +9 -0
  239. package/docs/content/docs/cli/teams.mdx +10 -0
  240. package/docs/content/docs/cli/telegram.mdx +11 -0
  241. package/docs/content/docs/cli/webex.mdx +10 -0
  242. package/docs/content/docs/cli/wechatbot.mdx +9 -0
  243. package/docs/content/docs/cli/whatsapp.mdx +36 -7
  244. package/docs/content/docs/cli/whatsappbot.mdx +9 -0
  245. package/e2e/config.ts +1 -1
  246. package/package.json +3 -1
  247. package/skills/agent-channeltalk/SKILL.md +12 -1
  248. package/skills/agent-channeltalkbot/SKILL.md +10 -1
  249. package/skills/agent-discord/SKILL.md +11 -1
  250. package/skills/agent-discordbot/SKILL.md +10 -1
  251. package/skills/agent-instagram/SKILL.md +12 -1
  252. package/skills/agent-kakaotalk/SKILL.md +24 -70
  253. package/skills/agent-kakaotalk/references/authentication.md +4 -69
  254. package/skills/agent-kakaotalk/references/common-patterns.md +14 -1
  255. package/skills/agent-line/SKILL.md +5 -5
  256. package/skills/agent-slack/SKILL.md +11 -1
  257. package/skills/agent-slackbot/SKILL.md +10 -1
  258. package/skills/agent-teams/SKILL.md +11 -1
  259. package/skills/agent-telegram/SKILL.md +6 -1
  260. package/skills/agent-webex/SKILL.md +11 -1
  261. package/skills/agent-wechatbot/SKILL.md +10 -1
  262. package/skills/agent-whatsapp/SKILL.md +52 -15
  263. package/skills/agent-whatsapp/references/authentication.md +36 -6
  264. package/skills/agent-whatsappbot/SKILL.md +10 -1
  265. package/src/platforms/channeltalk/cli.ts +2 -0
  266. package/src/platforms/channeltalk/commands/index.ts +1 -0
  267. package/src/platforms/channeltalk/commands/whoami.test.ts +64 -0
  268. package/src/platforms/channeltalk/commands/whoami.ts +62 -0
  269. package/src/platforms/channeltalkbot/cli.ts +2 -0
  270. package/src/platforms/channeltalkbot/commands/index.ts +1 -0
  271. package/src/platforms/channeltalkbot/commands/whoami.test.ts +104 -0
  272. package/src/platforms/channeltalkbot/commands/whoami.ts +42 -0
  273. package/src/platforms/discord/cli.ts +2 -0
  274. package/src/platforms/discord/commands/index.ts +1 -0
  275. package/src/platforms/discord/commands/whoami.test.ts +91 -0
  276. package/src/platforms/discord/commands/whoami.ts +36 -0
  277. package/src/platforms/discordbot/cli.ts +2 -0
  278. package/src/platforms/discordbot/commands/index.ts +1 -0
  279. package/src/platforms/discordbot/commands/whoami.test.ts +96 -0
  280. package/src/platforms/discordbot/commands/whoami.ts +44 -0
  281. package/src/platforms/instagram/cli.ts +2 -1
  282. package/src/platforms/instagram/client.ts +13 -0
  283. package/src/platforms/instagram/commands/chat.test.ts +1 -5
  284. package/src/platforms/instagram/commands/index.ts +1 -0
  285. package/src/platforms/instagram/commands/message.test.ts +1 -5
  286. package/src/platforms/instagram/commands/whoami.test.ts +60 -0
  287. package/src/platforms/instagram/commands/whoami.ts +21 -0
  288. package/src/platforms/kakaotalk/cli.ts +2 -2
  289. package/src/platforms/kakaotalk/client.test.ts +25 -14
  290. package/src/platforms/kakaotalk/client.ts +228 -33
  291. package/src/platforms/kakaotalk/commands/auth.ts +22 -73
  292. package/src/platforms/kakaotalk/commands/index.ts +1 -1
  293. package/src/platforms/kakaotalk/commands/{profile.test.ts → whoami.test.ts} +37 -5
  294. package/src/platforms/kakaotalk/commands/{profile.ts → whoami.ts} +4 -4
  295. package/src/platforms/kakaotalk/credential-manager.ts +1 -0
  296. package/src/platforms/kakaotalk/index.ts +1 -0
  297. package/src/platforms/kakaotalk/listener.test.ts +2 -2
  298. package/src/platforms/kakaotalk/listener.ts +2 -2
  299. package/src/platforms/kakaotalk/protocol/config.ts +26 -2
  300. package/src/platforms/kakaotalk/protocol/session.ts +42 -16
  301. package/src/platforms/kakaotalk/protocol/types.ts +9 -0
  302. package/src/platforms/kakaotalk/types.ts +16 -0
  303. package/src/platforms/line/cli.ts +2 -2
  304. package/src/platforms/line/commands/auth.ts +37 -70
  305. package/src/platforms/line/commands/index.ts +1 -1
  306. package/src/platforms/line/commands/{profile.test.ts → whoami.test.ts} +11 -11
  307. package/src/platforms/line/commands/{profile.ts → whoami.ts} +4 -4
  308. package/src/platforms/slack/cli.ts +2 -0
  309. package/src/platforms/slack/commands/index.ts +1 -0
  310. package/src/platforms/slack/commands/whoami.test.ts +126 -0
  311. package/src/platforms/slack/commands/whoami.ts +40 -0
  312. package/src/platforms/slackbot/cli.ts +2 -1
  313. package/src/platforms/slackbot/commands/index.ts +1 -0
  314. package/src/platforms/slackbot/commands/whoami.test.ts +102 -0
  315. package/src/platforms/slackbot/commands/whoami.ts +44 -0
  316. package/src/platforms/teams/cli.ts +2 -0
  317. package/src/platforms/teams/commands/index.ts +1 -0
  318. package/src/platforms/teams/commands/whoami.test.ts +83 -0
  319. package/src/platforms/teams/commands/whoami.ts +33 -0
  320. package/src/platforms/telegram/cli.ts +2 -1
  321. package/src/platforms/telegram/commands/index.ts +1 -0
  322. package/src/platforms/telegram/commands/whoami.test.ts +75 -0
  323. package/src/platforms/telegram/commands/whoami.ts +29 -0
  324. package/src/platforms/webex/cli.ts +2 -1
  325. package/src/platforms/webex/commands/auth.test.ts +58 -46
  326. package/src/platforms/webex/commands/index.ts +1 -0
  327. package/src/platforms/webex/commands/member.test.ts +1 -5
  328. package/src/platforms/webex/commands/message.test.ts +1 -5
  329. package/src/platforms/webex/commands/message.ts +1 -1
  330. package/src/platforms/webex/commands/snapshot.test.ts +1 -5
  331. package/src/platforms/webex/commands/space.test.ts +1 -5
  332. package/src/platforms/webex/commands/whoami.test.ts +113 -0
  333. package/src/platforms/webex/commands/whoami.ts +31 -0
  334. package/src/platforms/webex/credential-manager.test.ts +0 -1
  335. package/src/platforms/wechatbot/cli.ts +2 -1
  336. package/src/platforms/wechatbot/commands/index.ts +1 -0
  337. package/src/platforms/wechatbot/commands/whoami.test.ts +109 -0
  338. package/src/platforms/wechatbot/commands/whoami.ts +43 -0
  339. package/src/platforms/whatsapp/cli.ts +2 -1
  340. package/src/platforms/whatsapp/client.ts +156 -24
  341. package/src/platforms/whatsapp/commands/auth.ts +176 -70
  342. package/src/platforms/whatsapp/commands/index.ts +1 -0
  343. package/src/platforms/whatsapp/commands/shared.ts +2 -2
  344. package/src/platforms/whatsapp/commands/whoami.test.ts +59 -0
  345. package/src/platforms/whatsapp/commands/whoami.ts +21 -0
  346. package/src/platforms/whatsapp/ensure-auth.ts +2 -2
  347. package/src/platforms/whatsappbot/cli.ts +2 -1
  348. package/src/platforms/whatsappbot/commands/index.ts +1 -0
  349. package/src/platforms/whatsappbot/commands/whoami.test.ts +100 -0
  350. package/src/platforms/whatsappbot/commands/whoami.ts +57 -0
  351. package/src/shared/utils/qr.ts +92 -0
  352. package/src/tui/adapters/kakaotalk-adapter.ts +5 -2
  353. package/src/tui/adapters/whatsapp-adapter.ts +19 -16
  354. package/dist/src/platforms/kakaotalk/commands/profile.d.ts +0 -3
  355. package/dist/src/platforms/kakaotalk/commands/profile.d.ts.map +0 -1
  356. package/dist/src/platforms/kakaotalk/commands/profile.js.map +0 -1
  357. package/dist/src/platforms/line/commands/profile.d.ts +0 -3
  358. package/dist/src/platforms/line/commands/profile.d.ts.map +0 -1
  359. package/dist/src/platforms/line/commands/profile.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from 'bun:test'
1
+ import { afterEach, beforeEach, describe, expect, spyOn, test } from 'bun:test'
2
2
  import * as childProcess from 'node:child_process'
3
3
 
4
4
  import { WebexClient } from '../client'
@@ -7,7 +7,9 @@ import { loginAction, logoutAction, statusAction } from './auth'
7
7
 
8
8
  describe('auth commands', () => {
9
9
  let consoleSpy: ReturnType<typeof spyOn>
10
- let _consoleErrorSpy: ReturnType<typeof spyOn>
10
+ let consoleErrorSpy: ReturnType<typeof spyOn>
11
+ let execSpy: ReturnType<typeof spyOn>
12
+ const protoSpies: ReturnType<typeof spyOn>[] = []
11
13
  const mockPerson = {
12
14
  id: 'person-1',
13
15
  displayName: 'Test User',
@@ -17,21 +19,31 @@ describe('auth commands', () => {
17
19
  created: '2024-01-01T00:00:00.000Z',
18
20
  }
19
21
 
22
+ function protoSpy<T extends object>(target: T, method: keyof T) {
23
+ const s = spyOn(target, method as any)
24
+ protoSpies.push(s)
25
+ return s
26
+ }
27
+
20
28
  beforeEach(() => {
21
29
  consoleSpy = spyOn(console, 'log').mockImplementation(() => {})
22
- _consoleErrorSpy = spyOn(console, 'error').mockImplementation(() => {})
23
- spyOn(childProcess, 'exec').mockImplementation((() => {}) as any)
30
+ consoleErrorSpy = spyOn(console, 'error').mockImplementation(() => {})
31
+ execSpy = spyOn(childProcess, 'exec').mockImplementation((() => {}) as any)
24
32
  })
25
33
 
26
34
  afterEach(() => {
27
- mock.restore()
35
+ consoleSpy.mockRestore()
36
+ consoleErrorSpy.mockRestore()
37
+ execSpy.mockRestore()
38
+ for (const s of protoSpies) s.mockRestore()
39
+ protoSpies.length = 0
28
40
  })
29
41
 
30
42
  describe('loginAction with --token', () => {
31
43
  test('authenticates with provided token (bot token flow)', async () => {
32
- spyOn(WebexClient.prototype, 'login').mockResolvedValue(new WebexClient())
33
- spyOn(WebexClient.prototype, 'testAuth').mockResolvedValue(mockPerson)
34
- spyOn(WebexCredentialManager.prototype, 'saveConfig').mockResolvedValue(undefined)
44
+ protoSpy(WebexClient.prototype, 'login').mockResolvedValue(new WebexClient())
45
+ protoSpy(WebexClient.prototype, 'testAuth').mockResolvedValue(mockPerson)
46
+ protoSpy(WebexCredentialManager.prototype, 'saveConfig').mockResolvedValue(undefined)
35
47
 
36
48
  await loginAction({ token: 'bot-token-123', pretty: false })
37
49
 
@@ -43,9 +55,9 @@ describe('auth commands', () => {
43
55
  })
44
56
 
45
57
  test('saves tokenType as manual with expiresAt 0', async () => {
46
- spyOn(WebexClient.prototype, 'login').mockResolvedValue(new WebexClient())
47
- spyOn(WebexClient.prototype, 'testAuth').mockResolvedValue(mockPerson)
48
- const saveSpy = spyOn(WebexCredentialManager.prototype, 'saveConfig').mockResolvedValue(undefined)
58
+ protoSpy(WebexClient.prototype, 'login').mockResolvedValue(new WebexClient())
59
+ protoSpy(WebexClient.prototype, 'testAuth').mockResolvedValue(mockPerson)
60
+ const saveSpy = protoSpy(WebexCredentialManager.prototype, 'saveConfig').mockResolvedValue(undefined)
49
61
 
50
62
  await loginAction({ token: 'bot-token-123', pretty: false })
51
63
 
@@ -58,7 +70,7 @@ describe('auth commands', () => {
58
70
 
59
71
  describe('loginAction with --client-id and --client-secret', () => {
60
72
  test('uses provided credentials for Device Grant flow', async () => {
61
- spyOn(WebexCredentialManager.prototype, 'requestDeviceCode').mockResolvedValue({
73
+ protoSpy(WebexCredentialManager.prototype, 'requestDeviceCode').mockResolvedValue({
62
74
  deviceCode: 'd',
63
75
  userCode: 'u',
64
76
  verificationUri: 'https://v',
@@ -66,14 +78,14 @@ describe('auth commands', () => {
66
78
  expiresIn: 300,
67
79
  interval: 0.01,
68
80
  })
69
- spyOn(WebexCredentialManager.prototype, 'pollDeviceToken').mockResolvedValue({
81
+ protoSpy(WebexCredentialManager.prototype, 'pollDeviceToken').mockResolvedValue({
70
82
  accessToken: 'at',
71
83
  refreshToken: 'rt',
72
84
  expiresAt: Date.now() + 3600000,
73
85
  })
74
- spyOn(WebexCredentialManager.prototype, 'saveConfig').mockResolvedValue(undefined)
75
- spyOn(WebexClient.prototype, 'login').mockResolvedValue(new WebexClient())
76
- spyOn(WebexClient.prototype, 'testAuth').mockResolvedValue(mockPerson)
86
+ protoSpy(WebexCredentialManager.prototype, 'saveConfig').mockResolvedValue(undefined)
87
+ protoSpy(WebexClient.prototype, 'login').mockResolvedValue(new WebexClient())
88
+ protoSpy(WebexClient.prototype, 'testAuth').mockResolvedValue(mockPerson)
77
89
 
78
90
  await loginAction({ clientId: 'my-id', clientSecret: 'my-secret', pretty: false })
79
91
 
@@ -82,7 +94,7 @@ describe('auth commands', () => {
82
94
  })
83
95
 
84
96
  test('saves tokenType as oauth in config', async () => {
85
- spyOn(WebexCredentialManager.prototype, 'requestDeviceCode').mockResolvedValue({
97
+ protoSpy(WebexCredentialManager.prototype, 'requestDeviceCode').mockResolvedValue({
86
98
  deviceCode: 'd',
87
99
  userCode: 'u',
88
100
  verificationUri: 'https://v',
@@ -90,14 +102,14 @@ describe('auth commands', () => {
90
102
  expiresIn: 300,
91
103
  interval: 0.01,
92
104
  })
93
- spyOn(WebexCredentialManager.prototype, 'pollDeviceToken').mockResolvedValue({
105
+ protoSpy(WebexCredentialManager.prototype, 'pollDeviceToken').mockResolvedValue({
94
106
  accessToken: 'at',
95
107
  refreshToken: 'rt',
96
108
  expiresAt: Date.now() + 3600000,
97
109
  })
98
- const saveSpy = spyOn(WebexCredentialManager.prototype, 'saveConfig').mockResolvedValue(undefined)
99
- spyOn(WebexClient.prototype, 'login').mockResolvedValue(new WebexClient())
100
- spyOn(WebexClient.prototype, 'testAuth').mockResolvedValue(mockPerson)
110
+ const saveSpy = protoSpy(WebexCredentialManager.prototype, 'saveConfig').mockResolvedValue(undefined)
111
+ protoSpy(WebexClient.prototype, 'login').mockResolvedValue(new WebexClient())
112
+ protoSpy(WebexClient.prototype, 'testAuth').mockResolvedValue(mockPerson)
101
113
 
102
114
  await loginAction({ clientId: 'my-id', clientSecret: 'my-secret', pretty: false })
103
115
 
@@ -106,7 +118,7 @@ describe('auth commands', () => {
106
118
  })
107
119
 
108
120
  test('saves clientId and clientSecret in config', async () => {
109
- spyOn(WebexCredentialManager.prototype, 'requestDeviceCode').mockResolvedValue({
121
+ protoSpy(WebexCredentialManager.prototype, 'requestDeviceCode').mockResolvedValue({
110
122
  deviceCode: 'd',
111
123
  userCode: 'u',
112
124
  verificationUri: 'https://v',
@@ -114,18 +126,18 @@ describe('auth commands', () => {
114
126
  expiresIn: 300,
115
127
  interval: 0.01,
116
128
  })
117
- spyOn(WebexCredentialManager.prototype, 'pollDeviceToken').mockResolvedValue({
129
+ protoSpy(WebexCredentialManager.prototype, 'pollDeviceToken').mockResolvedValue({
118
130
  accessToken: 'at',
119
131
  refreshToken: 'rt',
120
132
  expiresAt: Date.now() + 3600000,
121
133
  })
122
- spyOn(WebexCredentialManager.prototype, 'saveConfig').mockResolvedValue(undefined)
123
- spyOn(WebexClient.prototype, 'login').mockResolvedValue(new WebexClient())
124
- spyOn(WebexClient.prototype, 'testAuth').mockResolvedValue(mockPerson)
134
+ const saveSpy = protoSpy(WebexCredentialManager.prototype, 'saveConfig').mockResolvedValue(undefined)
135
+ protoSpy(WebexClient.prototype, 'login').mockResolvedValue(new WebexClient())
136
+ protoSpy(WebexClient.prototype, 'testAuth').mockResolvedValue(mockPerson)
125
137
 
126
138
  await loginAction({ clientId: 'my-id', clientSecret: 'my-secret', pretty: false })
127
139
 
128
- const savedConfig = (WebexCredentialManager.prototype.saveConfig as ReturnType<typeof spyOn>).mock.calls[0][0] as { clientId: string; clientSecret: string }
140
+ const savedConfig = saveSpy.mock.calls[0][0] as { clientId: string; clientSecret: string }
129
141
  expect(savedConfig.clientId).toBe('my-id')
130
142
  expect(savedConfig.clientSecret).toBe('my-secret')
131
143
  })
@@ -133,10 +145,10 @@ describe('auth commands', () => {
133
145
 
134
146
  describe('statusAction', () => {
135
147
  test('shows authenticated status when token is valid', async () => {
136
- spyOn(WebexCredentialManager.prototype, 'loadConfig').mockResolvedValue(null)
137
- spyOn(WebexCredentialManager.prototype, 'getToken').mockResolvedValue('valid-token')
138
- spyOn(WebexClient.prototype, 'login').mockResolvedValue(new WebexClient())
139
- spyOn(WebexClient.prototype, 'testAuth').mockResolvedValue(mockPerson)
148
+ protoSpy(WebexCredentialManager.prototype, 'loadConfig').mockResolvedValue(null)
149
+ protoSpy(WebexCredentialManager.prototype, 'getToken').mockResolvedValue('valid-token')
150
+ protoSpy(WebexClient.prototype, 'login').mockResolvedValue(new WebexClient())
151
+ protoSpy(WebexClient.prototype, 'testAuth').mockResolvedValue(mockPerson)
140
152
 
141
153
  await statusAction({ pretty: false })
142
154
 
@@ -147,9 +159,9 @@ describe('auth commands', () => {
147
159
  })
148
160
 
149
161
  test('shows not authenticated when no token', async () => {
150
- spyOn(WebexCredentialManager.prototype, 'loadConfig').mockResolvedValue(null)
151
- spyOn(WebexCredentialManager.prototype, 'getToken').mockResolvedValue(null)
152
- const exitSpy = spyOn(process, 'exit').mockImplementation(() => undefined as never)
162
+ protoSpy(WebexCredentialManager.prototype, 'loadConfig').mockResolvedValue(null)
163
+ protoSpy(WebexCredentialManager.prototype, 'getToken').mockResolvedValue(null)
164
+ const exitSpy = protoSpy(process, 'exit').mockImplementation(() => undefined as never)
153
165
 
154
166
  await statusAction({ pretty: false })
155
167
 
@@ -160,10 +172,10 @@ describe('auth commands', () => {
160
172
  })
161
173
 
162
174
  test('shows not authenticated when token validation fails', async () => {
163
- spyOn(WebexCredentialManager.prototype, 'loadConfig').mockResolvedValue(null)
164
- spyOn(WebexCredentialManager.prototype, 'getToken').mockResolvedValue('invalid-token')
165
- spyOn(WebexClient.prototype, 'login').mockResolvedValue(new WebexClient())
166
- spyOn(WebexClient.prototype, 'testAuth').mockRejectedValue(new Error('401 Unauthorized'))
175
+ protoSpy(WebexCredentialManager.prototype, 'loadConfig').mockResolvedValue(null)
176
+ protoSpy(WebexCredentialManager.prototype, 'getToken').mockResolvedValue('invalid-token')
177
+ protoSpy(WebexClient.prototype, 'login').mockResolvedValue(new WebexClient())
178
+ protoSpy(WebexClient.prototype, 'testAuth').mockRejectedValue(new Error('401 Unauthorized'))
167
179
 
168
180
  await statusAction({ pretty: false })
169
181
 
@@ -173,16 +185,16 @@ describe('auth commands', () => {
173
185
  })
174
186
 
175
187
  test('loads config for stored client credentials', async () => {
176
- spyOn(WebexCredentialManager.prototype, 'loadConfig').mockResolvedValue({
188
+ protoSpy(WebexCredentialManager.prototype, 'loadConfig').mockResolvedValue({
177
189
  accessToken: 'at',
178
190
  refreshToken: 'rt',
179
191
  expiresAt: Date.now() + 3600000,
180
192
  clientId: 'stored-id',
181
193
  clientSecret: 'stored-secret',
182
194
  })
183
- spyOn(WebexCredentialManager.prototype, 'getToken').mockResolvedValue('valid-token')
184
- spyOn(WebexClient.prototype, 'login').mockResolvedValue(new WebexClient())
185
- spyOn(WebexClient.prototype, 'testAuth').mockResolvedValue(mockPerson)
195
+ protoSpy(WebexCredentialManager.prototype, 'getToken').mockResolvedValue('valid-token')
196
+ protoSpy(WebexClient.prototype, 'login').mockResolvedValue(new WebexClient())
197
+ protoSpy(WebexClient.prototype, 'testAuth').mockResolvedValue(mockPerson)
186
198
 
187
199
  await statusAction({ pretty: false })
188
200
 
@@ -192,12 +204,12 @@ describe('auth commands', () => {
192
204
 
193
205
  describe('logoutAction', () => {
194
206
  test('clears credentials when authenticated', async () => {
195
- spyOn(WebexCredentialManager.prototype, 'loadConfig').mockResolvedValue({
207
+ protoSpy(WebexCredentialManager.prototype, 'loadConfig').mockResolvedValue({
196
208
  accessToken: 'token',
197
209
  refreshToken: 'refresh',
198
210
  expiresAt: Date.now() + 3600000,
199
211
  })
200
- const clearSpy = spyOn(WebexCredentialManager.prototype, 'clearCredentials').mockResolvedValue(undefined)
212
+ const clearSpy = protoSpy(WebexCredentialManager.prototype, 'clearCredentials').mockResolvedValue(undefined)
201
213
 
202
214
  await logoutAction({ pretty: false })
203
215
 
@@ -208,8 +220,8 @@ describe('auth commands', () => {
208
220
  })
209
221
 
210
222
  test('shows error when not authenticated', async () => {
211
- spyOn(WebexCredentialManager.prototype, 'loadConfig').mockResolvedValue(null)
212
- const exitSpy = spyOn(process, 'exit').mockImplementation(() => undefined as never)
223
+ protoSpy(WebexCredentialManager.prototype, 'loadConfig').mockResolvedValue(null)
224
+ const exitSpy = protoSpy(process, 'exit').mockImplementation(() => undefined as never)
213
225
 
214
226
  await logoutAction({ pretty: false })
215
227
 
@@ -3,3 +3,4 @@ export { memberCommand } from './member'
3
3
  export { messageCommand } from './message'
4
4
  export { snapshotAction, snapshotCommand } from './snapshot'
5
5
  export { spaceCommand } from './space'
6
+ export { whoamiCommand } from './whoami'
@@ -1,4 +1,4 @@
1
- import { afterAll, afterEach, beforeEach, describe, expect, mock, spyOn, test } from 'bun:test'
1
+ import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from 'bun:test'
2
2
 
3
3
  import { WebexError } from '../types'
4
4
 
@@ -42,10 +42,6 @@ mock.module('../client', () => ({
42
42
 
43
43
  import { listAction } from './member'
44
44
 
45
- afterAll(() => {
46
- mock.restore()
47
- })
48
-
49
45
  describe('member commands', () => {
50
46
  let consoleSpy: ReturnType<typeof spyOn>
51
47
 
@@ -1,4 +1,4 @@
1
- import { afterAll, afterEach, beforeEach, expect, mock, spyOn, test } from 'bun:test'
1
+ import { afterEach, beforeEach, expect, mock, spyOn, test } from 'bun:test'
2
2
 
3
3
  import { WebexError } from '../types'
4
4
 
@@ -56,10 +56,6 @@ mock.module('../client', () => ({
56
56
 
57
57
  import { deleteAction, dmAction, editAction, getAction, listAction, sendAction } from './message'
58
58
 
59
- afterAll(() => {
60
- mock.restore()
61
- })
62
-
63
59
  let consoleLogSpy: ReturnType<typeof spyOn>
64
60
 
65
61
  beforeEach(() => {
@@ -83,7 +83,7 @@ export async function deleteAction(
83
83
  console.log(
84
84
  formatOutput({ warning: 'Use --force to confirm deletion', messageId }, options.pretty),
85
85
  )
86
- process.exit(0)
86
+ return process.exit(0)
87
87
  }
88
88
 
89
89
  const client = await new WebexClient().login()
@@ -1,4 +1,4 @@
1
- import { afterAll, afterEach, beforeEach, describe, expect, mock, spyOn, test } from 'bun:test'
1
+ import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from 'bun:test'
2
2
  import { WebexError } from '../types'
3
3
 
4
4
  const mockHandleError = mock((err: Error) => {
@@ -41,10 +41,6 @@ mock.module('../client', () => ({
41
41
 
42
42
  import { snapshotAction } from './snapshot'
43
43
 
44
- afterAll(() => {
45
- mock.restore()
46
- })
47
-
48
44
  describe('snapshot command', () => {
49
45
  let consoleSpy: ReturnType<typeof spyOn>
50
46
 
@@ -1,4 +1,4 @@
1
- import { afterAll, afterEach, beforeEach, describe, expect, mock, spyOn, test } from 'bun:test'
1
+ import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from 'bun:test'
2
2
 
3
3
  import { WebexError } from '../types'
4
4
 
@@ -54,10 +54,6 @@ mock.module('../client', () => ({
54
54
 
55
55
  import { infoAction, listAction } from './space'
56
56
 
57
- afterAll(() => {
58
- mock.restore()
59
- })
60
-
61
57
  let consoleLogSpy: ReturnType<typeof spyOn>
62
58
 
63
59
  beforeEach(() => {
@@ -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 () => {
@@ -3,7 +3,7 @@
3
3
  import { Command } from 'commander'
4
4
 
5
5
  import pkg from '../../../package.json' with { type: 'json' }
6
- import { authCommand, messageCommand, templateCommand, userCommand } from './commands/index'
6
+ import { authCommand, messageCommand, templateCommand, userCommand, whoamiCommand } from './commands/index'
7
7
 
8
8
  const program = new Command()
9
9
 
@@ -15,6 +15,7 @@ program
15
15
  .option('--account <id>', 'Account ID to use')
16
16
 
17
17
  program.addCommand(authCommand)
18
+ program.addCommand(whoamiCommand)
18
19
  program.addCommand(messageCommand)
19
20
  program.addCommand(templateCommand)
20
21
  program.addCommand(userCommand)
@@ -2,3 +2,4 @@ export { authCommand } from './auth'
2
2
  export { messageCommand } from './message'
3
3
  export { templateCommand } from './template'
4
4
  export { userCommand } from './user'
5
+ export { whoamiCommand } from './whoami'
@@ -0,0 +1,109 @@
1
+ import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test'
2
+ import { existsSync, rmSync } from 'node:fs'
3
+ import { mkdir } from 'node:fs/promises'
4
+ import { tmpdir } from 'node:os'
5
+ import { join } from 'node:path'
6
+
7
+ const mockVerifyCredentials = mock(() => Promise.resolve(true))
8
+
9
+ mock.module('../client', () => ({
10
+ WeChatBotClient: class MockWeChatBotClient {
11
+ async login(_credentials?: { appId: string; appSecret: string }) {
12
+ return this
13
+ }
14
+ verifyCredentials = mockVerifyCredentials
15
+ },
16
+ }))
17
+
18
+ import { WeChatBotCredentialManager } from '../credential-manager'
19
+ import { whoamiAction } from './whoami'
20
+
21
+ describe('whoami command', () => {
22
+ let tempDir: string
23
+ let originalEnv: NodeJS.ProcessEnv
24
+
25
+ beforeEach(async () => {
26
+ tempDir = join(tmpdir(), `wechatbot-whoami-test-${Date.now()}`)
27
+ await mkdir(tempDir, { recursive: true })
28
+ originalEnv = { ...process.env }
29
+ delete process.env.E2E_WECHATBOT_APP_ID
30
+ delete process.env.E2E_WECHATBOT_APP_SECRET
31
+ mockVerifyCredentials.mockClear()
32
+ })
33
+
34
+ afterEach(() => {
35
+ if (existsSync(tempDir)) {
36
+ rmSync(tempDir, { recursive: true })
37
+ }
38
+ process.env = originalEnv
39
+ })
40
+
41
+ test('returns app_id, account_name, and verified status', async () => {
42
+ const manager = new WeChatBotCredentialManager(tempDir)
43
+ await manager.setCredentials({
44
+ app_id: 'wx1234567890',
45
+ app_secret: 'secret123',
46
+ account_name: 'Test Official Account',
47
+ })
48
+
49
+ const result = await whoamiAction({ _credManager: manager })
50
+
51
+ expect(result.app_id).toBe('wx1234567890')
52
+ expect(result.account_name).toBe('Test Official Account')
53
+ expect(result.verified).toBe(true)
54
+ expect(result.error).toBeUndefined()
55
+ })
56
+
57
+ test('returns info for specific --account', async () => {
58
+ const manager = new WeChatBotCredentialManager(tempDir)
59
+ await manager.setCredentials({
60
+ app_id: 'wxAAA',
61
+ app_secret: 'secretA',
62
+ account_name: 'Account A',
63
+ })
64
+ await manager.setCredentials({
65
+ app_id: 'wxBBB',
66
+ app_secret: 'secretB',
67
+ account_name: 'Account B',
68
+ })
69
+
70
+ const result = await whoamiAction({ account: 'wxAAA', _credManager: manager })
71
+
72
+ expect(result.app_id).toBe('wxAAA')
73
+ expect(result.account_name).toBe('Account A')
74
+ expect(result.verified).toBe(true)
75
+ })
76
+
77
+ test('returns verified false when credentials are invalid', async () => {
78
+ mockVerifyCredentials.mockImplementationOnce(() => Promise.resolve(false))
79
+
80
+ const manager = new WeChatBotCredentialManager(tempDir)
81
+ await manager.setCredentials({
82
+ app_id: 'wx1234567890',
83
+ app_secret: 'bad-secret',
84
+ account_name: 'Test Account',
85
+ })
86
+
87
+ const result = await whoamiAction({ _credManager: manager })
88
+
89
+ expect(result.app_id).toBe('wx1234567890')
90
+ expect(result.verified).toBe(false)
91
+ expect(result.error).toBeUndefined()
92
+ })
93
+
94
+ test('returns error when client throws', async () => {
95
+ mockVerifyCredentials.mockImplementationOnce(() => Promise.reject(new Error('Network error')))
96
+
97
+ const manager = new WeChatBotCredentialManager(tempDir)
98
+ await manager.setCredentials({
99
+ app_id: 'wx1234567890',
100
+ app_secret: 'secret123',
101
+ account_name: 'Test Account',
102
+ })
103
+
104
+ const result = await whoamiAction({ _credManager: manager })
105
+
106
+ expect(result.error).toBeDefined()
107
+ expect(result.error).toContain('Network error')
108
+ })
109
+ })
@@ -0,0 +1,43 @@
1
+ import { Command } from 'commander'
2
+
3
+ import { formatOutput } from '@/shared/utils/output'
4
+
5
+ import { WeChatBotCredentialManager } from '../credential-manager'
6
+ import type { AccountOption } from './shared'
7
+ import { getClient } from './shared'
8
+
9
+ interface WhoamiResult {
10
+ app_id?: string | null
11
+ account_name?: string | null
12
+ verified?: boolean
13
+ error?: string
14
+ }
15
+
16
+ export async function whoamiAction(options: AccountOption): Promise<WhoamiResult> {
17
+ try {
18
+ const client = await getClient(options)
19
+ const verified = await client.verifyCredentials()
20
+ const credManager = options._credManager ?? new WeChatBotCredentialManager()
21
+ const creds = await credManager.getCredentials(options.account)
22
+ return {
23
+ app_id: creds?.app_id ?? null,
24
+ account_name: creds?.account_name ?? null,
25
+ verified,
26
+ }
27
+ } catch (error) {
28
+ return { error: (error as Error).message }
29
+ }
30
+ }
31
+
32
+ function cliOutput(result: WhoamiResult, pretty?: boolean): void {
33
+ console.log(formatOutput(result, pretty))
34
+ if (result.error) process.exit(1)
35
+ }
36
+
37
+ export const whoamiCommand = new Command('whoami')
38
+ .description('Show current authenticated bot')
39
+ .option('--account <id>', 'Account ID to use')
40
+ .option('--pretty', 'Pretty print JSON output')
41
+ .action(async (opts: AccountOption) => {
42
+ cliOutput(await whoamiAction(opts), opts.pretty)
43
+ })