agent-messenger 2.4.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 (329) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.github/workflows/release.yml +0 -12
  3. package/README.md +3 -3
  4. package/dist/package.json +1 -1
  5. package/dist/src/platforms/channeltalk/cli.d.ts.map +1 -1
  6. package/dist/src/platforms/channeltalk/cli.js +2 -1
  7. package/dist/src/platforms/channeltalk/cli.js.map +1 -1
  8. package/dist/src/platforms/channeltalk/commands/index.d.ts +1 -0
  9. package/dist/src/platforms/channeltalk/commands/index.d.ts.map +1 -1
  10. package/dist/src/platforms/channeltalk/commands/index.js +1 -0
  11. package/dist/src/platforms/channeltalk/commands/index.js.map +1 -1
  12. package/dist/src/platforms/channeltalk/commands/whoami.d.ts +22 -0
  13. package/dist/src/platforms/channeltalk/commands/whoami.d.ts.map +1 -0
  14. package/dist/src/platforms/channeltalk/commands/whoami.js +40 -0
  15. package/dist/src/platforms/channeltalk/commands/whoami.js.map +1 -0
  16. package/dist/src/platforms/channeltalkbot/cli.d.ts.map +1 -1
  17. package/dist/src/platforms/channeltalkbot/cli.js +2 -1
  18. package/dist/src/platforms/channeltalkbot/cli.js.map +1 -1
  19. package/dist/src/platforms/channeltalkbot/commands/index.d.ts +1 -0
  20. package/dist/src/platforms/channeltalkbot/commands/index.d.ts.map +1 -1
  21. package/dist/src/platforms/channeltalkbot/commands/index.js +1 -0
  22. package/dist/src/platforms/channeltalkbot/commands/index.js.map +1 -1
  23. package/dist/src/platforms/channeltalkbot/commands/whoami.d.ts +13 -0
  24. package/dist/src/platforms/channeltalkbot/commands/whoami.d.ts.map +1 -0
  25. package/dist/src/platforms/channeltalkbot/commands/whoami.js +31 -0
  26. package/dist/src/platforms/channeltalkbot/commands/whoami.js.map +1 -0
  27. package/dist/src/platforms/discord/cli.d.ts.map +1 -1
  28. package/dist/src/platforms/discord/cli.js +2 -1
  29. package/dist/src/platforms/discord/cli.js.map +1 -1
  30. package/dist/src/platforms/discord/commands/index.d.ts +1 -0
  31. package/dist/src/platforms/discord/commands/index.d.ts.map +1 -1
  32. package/dist/src/platforms/discord/commands/index.js +1 -0
  33. package/dist/src/platforms/discord/commands/index.js.map +1 -1
  34. package/dist/src/platforms/discord/commands/whoami.d.ts +6 -0
  35. package/dist/src/platforms/discord/commands/whoami.d.ts.map +1 -0
  36. package/dist/src/platforms/discord/commands/whoami.js +33 -0
  37. package/dist/src/platforms/discord/commands/whoami.js.map +1 -0
  38. package/dist/src/platforms/discordbot/cli.d.ts.map +1 -1
  39. package/dist/src/platforms/discordbot/cli.js +2 -1
  40. package/dist/src/platforms/discordbot/cli.js.map +1 -1
  41. package/dist/src/platforms/discordbot/commands/index.d.ts +1 -0
  42. package/dist/src/platforms/discordbot/commands/index.d.ts.map +1 -1
  43. package/dist/src/platforms/discordbot/commands/index.js +1 -0
  44. package/dist/src/platforms/discordbot/commands/index.js.map +1 -1
  45. package/dist/src/platforms/discordbot/commands/whoami.d.ts +14 -0
  46. package/dist/src/platforms/discordbot/commands/whoami.d.ts.map +1 -0
  47. package/dist/src/platforms/discordbot/commands/whoami.js +32 -0
  48. package/dist/src/platforms/discordbot/commands/whoami.js.map +1 -0
  49. package/dist/src/platforms/instagram/cli.d.ts.map +1 -1
  50. package/dist/src/platforms/instagram/cli.js +2 -1
  51. package/dist/src/platforms/instagram/cli.js.map +1 -1
  52. package/dist/src/platforms/instagram/client.d.ts +6 -0
  53. package/dist/src/platforms/instagram/client.d.ts.map +1 -1
  54. package/dist/src/platforms/instagram/client.js +12 -0
  55. package/dist/src/platforms/instagram/client.js.map +1 -1
  56. package/dist/src/platforms/instagram/commands/index.d.ts +1 -0
  57. package/dist/src/platforms/instagram/commands/index.d.ts.map +1 -1
  58. package/dist/src/platforms/instagram/commands/index.js +1 -0
  59. package/dist/src/platforms/instagram/commands/index.js.map +1 -1
  60. package/dist/src/platforms/instagram/commands/whoami.d.ts +7 -0
  61. package/dist/src/platforms/instagram/commands/whoami.d.ts.map +1 -0
  62. package/dist/src/platforms/instagram/commands/whoami.js +19 -0
  63. package/dist/src/platforms/instagram/commands/whoami.js.map +1 -0
  64. package/dist/src/platforms/kakaotalk/cli.js +2 -2
  65. package/dist/src/platforms/kakaotalk/cli.js.map +1 -1
  66. package/dist/src/platforms/kakaotalk/client.d.ts.map +1 -1
  67. package/dist/src/platforms/kakaotalk/client.js +173 -21
  68. package/dist/src/platforms/kakaotalk/client.js.map +1 -1
  69. package/dist/src/platforms/kakaotalk/commands/index.d.ts +1 -1
  70. package/dist/src/platforms/kakaotalk/commands/index.d.ts.map +1 -1
  71. package/dist/src/platforms/kakaotalk/commands/index.js +1 -1
  72. package/dist/src/platforms/kakaotalk/commands/index.js.map +1 -1
  73. package/dist/src/platforms/kakaotalk/commands/whoami.d.ts +3 -0
  74. package/dist/src/platforms/kakaotalk/commands/whoami.d.ts.map +1 -0
  75. package/dist/src/platforms/kakaotalk/commands/{profile.js → whoami.js} +5 -5
  76. package/dist/src/platforms/kakaotalk/commands/whoami.js.map +1 -0
  77. package/dist/src/platforms/kakaotalk/protocol/session.d.ts +4 -2
  78. package/dist/src/platforms/kakaotalk/protocol/session.d.ts.map +1 -1
  79. package/dist/src/platforms/kakaotalk/protocol/session.js +25 -6
  80. package/dist/src/platforms/kakaotalk/protocol/session.js.map +1 -1
  81. package/dist/src/platforms/kakaotalk/protocol/types.d.ts +17 -0
  82. package/dist/src/platforms/kakaotalk/protocol/types.d.ts.map +1 -1
  83. package/dist/src/platforms/kakaotalk/protocol/types.js.map +1 -1
  84. package/dist/src/platforms/kakaotalk/types.d.ts +12 -0
  85. package/dist/src/platforms/kakaotalk/types.d.ts.map +1 -1
  86. package/dist/src/platforms/kakaotalk/types.js +6 -0
  87. package/dist/src/platforms/kakaotalk/types.js.map +1 -1
  88. package/dist/src/platforms/line/cli.js +2 -2
  89. package/dist/src/platforms/line/cli.js.map +1 -1
  90. package/dist/src/platforms/line/commands/auth.d.ts.map +1 -1
  91. package/dist/src/platforms/line/commands/auth.js +9 -59
  92. package/dist/src/platforms/line/commands/auth.js.map +1 -1
  93. package/dist/src/platforms/line/commands/index.d.ts +1 -1
  94. package/dist/src/platforms/line/commands/index.d.ts.map +1 -1
  95. package/dist/src/platforms/line/commands/index.js +1 -1
  96. package/dist/src/platforms/line/commands/index.js.map +1 -1
  97. package/dist/src/platforms/line/commands/whoami.d.ts +3 -0
  98. package/dist/src/platforms/line/commands/whoami.d.ts.map +1 -0
  99. package/dist/src/platforms/line/commands/{profile.js → whoami.js} +5 -5
  100. package/dist/src/platforms/line/commands/whoami.js.map +1 -0
  101. package/dist/src/platforms/slack/cli.d.ts.map +1 -1
  102. package/dist/src/platforms/slack/cli.js +2 -1
  103. package/dist/src/platforms/slack/cli.js.map +1 -1
  104. package/dist/src/platforms/slack/commands/index.d.ts +1 -0
  105. package/dist/src/platforms/slack/commands/index.d.ts.map +1 -1
  106. package/dist/src/platforms/slack/commands/index.js +1 -0
  107. package/dist/src/platforms/slack/commands/index.js.map +1 -1
  108. package/dist/src/platforms/slack/commands/whoami.d.ts +6 -0
  109. package/dist/src/platforms/slack/commands/whoami.d.ts.map +1 -0
  110. package/dist/src/platforms/slack/commands/whoami.js +39 -0
  111. package/dist/src/platforms/slack/commands/whoami.js.map +1 -0
  112. package/dist/src/platforms/slackbot/cli.d.ts.map +1 -1
  113. package/dist/src/platforms/slackbot/cli.js +2 -1
  114. package/dist/src/platforms/slackbot/cli.js.map +1 -1
  115. package/dist/src/platforms/slackbot/commands/index.d.ts +1 -0
  116. package/dist/src/platforms/slackbot/commands/index.d.ts.map +1 -1
  117. package/dist/src/platforms/slackbot/commands/index.js +1 -0
  118. package/dist/src/platforms/slackbot/commands/index.js.map +1 -1
  119. package/dist/src/platforms/slackbot/commands/whoami.d.ts +14 -0
  120. package/dist/src/platforms/slackbot/commands/whoami.d.ts.map +1 -0
  121. package/dist/src/platforms/slackbot/commands/whoami.js +32 -0
  122. package/dist/src/platforms/slackbot/commands/whoami.js.map +1 -0
  123. package/dist/src/platforms/teams/cli.d.ts.map +1 -1
  124. package/dist/src/platforms/teams/cli.js +2 -1
  125. package/dist/src/platforms/teams/cli.js.map +1 -1
  126. package/dist/src/platforms/teams/commands/index.d.ts +1 -0
  127. package/dist/src/platforms/teams/commands/index.d.ts.map +1 -1
  128. package/dist/src/platforms/teams/commands/index.js +1 -0
  129. package/dist/src/platforms/teams/commands/index.js.map +1 -1
  130. package/dist/src/platforms/teams/commands/whoami.d.ts +6 -0
  131. package/dist/src/platforms/teams/commands/whoami.d.ts.map +1 -0
  132. package/dist/src/platforms/teams/commands/whoami.js +30 -0
  133. package/dist/src/platforms/teams/commands/whoami.js.map +1 -0
  134. package/dist/src/platforms/telegram/cli.d.ts.map +1 -1
  135. package/dist/src/platforms/telegram/cli.js +2 -1
  136. package/dist/src/platforms/telegram/cli.js.map +1 -1
  137. package/dist/src/platforms/telegram/commands/index.d.ts +1 -0
  138. package/dist/src/platforms/telegram/commands/index.d.ts.map +1 -1
  139. package/dist/src/platforms/telegram/commands/index.js +1 -0
  140. package/dist/src/platforms/telegram/commands/index.js.map +1 -1
  141. package/dist/src/platforms/telegram/commands/whoami.d.ts +7 -0
  142. package/dist/src/platforms/telegram/commands/whoami.d.ts.map +1 -0
  143. package/dist/src/platforms/telegram/commands/whoami.js +27 -0
  144. package/dist/src/platforms/telegram/commands/whoami.js.map +1 -0
  145. package/dist/src/platforms/webex/cli.d.ts.map +1 -1
  146. package/dist/src/platforms/webex/cli.js +2 -1
  147. package/dist/src/platforms/webex/cli.js.map +1 -1
  148. package/dist/src/platforms/webex/commands/index.d.ts +1 -0
  149. package/dist/src/platforms/webex/commands/index.d.ts.map +1 -1
  150. package/dist/src/platforms/webex/commands/index.js +1 -0
  151. package/dist/src/platforms/webex/commands/index.js.map +1 -1
  152. package/dist/src/platforms/webex/commands/message.js +1 -1
  153. package/dist/src/platforms/webex/commands/message.js.map +1 -1
  154. package/dist/src/platforms/webex/commands/whoami.d.ts +6 -0
  155. package/dist/src/platforms/webex/commands/whoami.d.ts.map +1 -0
  156. package/dist/src/platforms/webex/commands/whoami.js +30 -0
  157. package/dist/src/platforms/webex/commands/whoami.js.map +1 -0
  158. package/dist/src/platforms/wechatbot/cli.d.ts.map +1 -1
  159. package/dist/src/platforms/wechatbot/cli.js +2 -1
  160. package/dist/src/platforms/wechatbot/cli.js.map +1 -1
  161. package/dist/src/platforms/wechatbot/commands/index.d.ts +1 -0
  162. package/dist/src/platforms/wechatbot/commands/index.d.ts.map +1 -1
  163. package/dist/src/platforms/wechatbot/commands/index.js +1 -0
  164. package/dist/src/platforms/wechatbot/commands/index.js.map +1 -1
  165. package/dist/src/platforms/wechatbot/commands/whoami.d.ts +12 -0
  166. package/dist/src/platforms/wechatbot/commands/whoami.d.ts.map +1 -0
  167. package/dist/src/platforms/wechatbot/commands/whoami.js +33 -0
  168. package/dist/src/platforms/wechatbot/commands/whoami.js.map +1 -0
  169. package/dist/src/platforms/whatsapp/cli.d.ts.map +1 -1
  170. package/dist/src/platforms/whatsapp/cli.js +2 -1
  171. package/dist/src/platforms/whatsapp/cli.js.map +1 -1
  172. package/dist/src/platforms/whatsapp/client.d.ts +8 -0
  173. package/dist/src/platforms/whatsapp/client.d.ts.map +1 -1
  174. package/dist/src/platforms/whatsapp/client.js +116 -8
  175. package/dist/src/platforms/whatsapp/client.js.map +1 -1
  176. package/dist/src/platforms/whatsapp/commands/auth.d.ts.map +1 -1
  177. package/dist/src/platforms/whatsapp/commands/auth.js +115 -45
  178. package/dist/src/platforms/whatsapp/commands/auth.js.map +1 -1
  179. package/dist/src/platforms/whatsapp/commands/index.d.ts +1 -0
  180. package/dist/src/platforms/whatsapp/commands/index.d.ts.map +1 -1
  181. package/dist/src/platforms/whatsapp/commands/index.js +1 -0
  182. package/dist/src/platforms/whatsapp/commands/index.js.map +1 -1
  183. package/dist/src/platforms/whatsapp/commands/shared.js +2 -2
  184. package/dist/src/platforms/whatsapp/commands/shared.js.map +1 -1
  185. package/dist/src/platforms/whatsapp/commands/whoami.d.ts +7 -0
  186. package/dist/src/platforms/whatsapp/commands/whoami.d.ts.map +1 -0
  187. package/dist/src/platforms/whatsapp/commands/whoami.js +19 -0
  188. package/dist/src/platforms/whatsapp/commands/whoami.js.map +1 -0
  189. package/dist/src/platforms/whatsapp/ensure-auth.js +2 -2
  190. package/dist/src/platforms/whatsapp/ensure-auth.js.map +1 -1
  191. package/dist/src/platforms/whatsappbot/cli.d.ts.map +1 -1
  192. package/dist/src/platforms/whatsappbot/cli.js +2 -1
  193. package/dist/src/platforms/whatsappbot/cli.js.map +1 -1
  194. package/dist/src/platforms/whatsappbot/commands/index.d.ts +1 -0
  195. package/dist/src/platforms/whatsappbot/commands/index.d.ts.map +1 -1
  196. package/dist/src/platforms/whatsappbot/commands/index.js +1 -0
  197. package/dist/src/platforms/whatsappbot/commands/index.js.map +1 -1
  198. package/dist/src/platforms/whatsappbot/commands/whoami.d.ts +17 -0
  199. package/dist/src/platforms/whatsappbot/commands/whoami.d.ts.map +1 -0
  200. package/dist/src/platforms/whatsappbot/commands/whoami.js +39 -0
  201. package/dist/src/platforms/whatsappbot/commands/whoami.js.map +1 -0
  202. package/dist/src/shared/utils/qr.d.ts +15 -0
  203. package/dist/src/shared/utils/qr.d.ts.map +1 -0
  204. package/dist/src/shared/utils/qr.js +74 -0
  205. package/dist/src/shared/utils/qr.js.map +1 -0
  206. package/dist/src/tui/adapters/whatsapp-adapter.d.ts.map +1 -1
  207. package/dist/src/tui/adapters/whatsapp-adapter.js +20 -15
  208. package/dist/src/tui/adapters/whatsapp-adapter.js.map +1 -1
  209. package/docs/content/docs/cli/channeltalk.mdx +11 -0
  210. package/docs/content/docs/cli/channeltalkbot.mdx +9 -0
  211. package/docs/content/docs/cli/discord.mdx +10 -0
  212. package/docs/content/docs/cli/discordbot.mdx +9 -0
  213. package/docs/content/docs/cli/instagram.mdx +11 -0
  214. package/docs/content/docs/cli/kakaotalk.mdx +24 -0
  215. package/docs/content/docs/cli/line.mdx +4 -4
  216. package/docs/content/docs/cli/slack.mdx +10 -0
  217. package/docs/content/docs/cli/slackbot.mdx +9 -0
  218. package/docs/content/docs/cli/teams.mdx +10 -0
  219. package/docs/content/docs/cli/telegram.mdx +11 -0
  220. package/docs/content/docs/cli/webex.mdx +10 -0
  221. package/docs/content/docs/cli/wechatbot.mdx +9 -0
  222. package/docs/content/docs/cli/whatsapp.mdx +36 -7
  223. package/docs/content/docs/cli/whatsappbot.mdx +9 -0
  224. package/package.json +1 -1
  225. package/skills/agent-channeltalk/SKILL.md +12 -1
  226. package/skills/agent-channeltalkbot/SKILL.md +10 -1
  227. package/skills/agent-discord/SKILL.md +11 -1
  228. package/skills/agent-discordbot/SKILL.md +10 -1
  229. package/skills/agent-instagram/SKILL.md +12 -1
  230. package/skills/agent-kakaotalk/SKILL.md +14 -8
  231. package/skills/agent-kakaotalk/references/common-patterns.md +1 -1
  232. package/skills/agent-line/SKILL.md +5 -5
  233. package/skills/agent-slack/SKILL.md +11 -1
  234. package/skills/agent-slackbot/SKILL.md +10 -1
  235. package/skills/agent-teams/SKILL.md +11 -1
  236. package/skills/agent-telegram/SKILL.md +6 -1
  237. package/skills/agent-webex/SKILL.md +11 -1
  238. package/skills/agent-wechatbot/SKILL.md +10 -1
  239. package/skills/agent-whatsapp/SKILL.md +52 -15
  240. package/skills/agent-whatsapp/references/authentication.md +36 -6
  241. package/skills/agent-whatsappbot/SKILL.md +10 -1
  242. package/src/platforms/channeltalk/cli.ts +2 -0
  243. package/src/platforms/channeltalk/commands/index.ts +1 -0
  244. package/src/platforms/channeltalk/commands/whoami.test.ts +64 -0
  245. package/src/platforms/channeltalk/commands/whoami.ts +62 -0
  246. package/src/platforms/channeltalkbot/cli.ts +2 -0
  247. package/src/platforms/channeltalkbot/commands/index.ts +1 -0
  248. package/src/platforms/channeltalkbot/commands/whoami.test.ts +104 -0
  249. package/src/platforms/channeltalkbot/commands/whoami.ts +42 -0
  250. package/src/platforms/discord/cli.ts +2 -0
  251. package/src/platforms/discord/commands/index.ts +1 -0
  252. package/src/platforms/discord/commands/whoami.test.ts +91 -0
  253. package/src/platforms/discord/commands/whoami.ts +36 -0
  254. package/src/platforms/discordbot/cli.ts +2 -0
  255. package/src/platforms/discordbot/commands/index.ts +1 -0
  256. package/src/platforms/discordbot/commands/whoami.test.ts +96 -0
  257. package/src/platforms/discordbot/commands/whoami.ts +44 -0
  258. package/src/platforms/instagram/cli.ts +2 -1
  259. package/src/platforms/instagram/client.ts +13 -0
  260. package/src/platforms/instagram/commands/chat.test.ts +1 -5
  261. package/src/platforms/instagram/commands/index.ts +1 -0
  262. package/src/platforms/instagram/commands/message.test.ts +1 -5
  263. package/src/platforms/instagram/commands/whoami.test.ts +60 -0
  264. package/src/platforms/instagram/commands/whoami.ts +21 -0
  265. package/src/platforms/kakaotalk/cli.ts +2 -2
  266. package/src/platforms/kakaotalk/client.test.ts +25 -14
  267. package/src/platforms/kakaotalk/client.ts +204 -24
  268. package/src/platforms/kakaotalk/commands/index.ts +1 -1
  269. package/src/platforms/kakaotalk/commands/{profile.test.ts → whoami.test.ts} +37 -5
  270. package/src/platforms/kakaotalk/commands/{profile.ts → whoami.ts} +4 -4
  271. package/src/platforms/kakaotalk/protocol/session.ts +27 -7
  272. package/src/platforms/kakaotalk/protocol/types.ts +9 -0
  273. package/src/platforms/kakaotalk/types.ts +12 -0
  274. package/src/platforms/line/cli.ts +2 -2
  275. package/src/platforms/line/commands/auth.ts +37 -70
  276. package/src/platforms/line/commands/index.ts +1 -1
  277. package/src/platforms/line/commands/{profile.test.ts → whoami.test.ts} +11 -11
  278. package/src/platforms/line/commands/{profile.ts → whoami.ts} +4 -4
  279. package/src/platforms/slack/cli.ts +2 -0
  280. package/src/platforms/slack/commands/index.ts +1 -0
  281. package/src/platforms/slack/commands/whoami.test.ts +126 -0
  282. package/src/platforms/slack/commands/whoami.ts +40 -0
  283. package/src/platforms/slackbot/cli.ts +2 -1
  284. package/src/platforms/slackbot/commands/index.ts +1 -0
  285. package/src/platforms/slackbot/commands/whoami.test.ts +102 -0
  286. package/src/platforms/slackbot/commands/whoami.ts +44 -0
  287. package/src/platforms/teams/cli.ts +2 -0
  288. package/src/platforms/teams/commands/index.ts +1 -0
  289. package/src/platforms/teams/commands/whoami.test.ts +83 -0
  290. package/src/platforms/teams/commands/whoami.ts +33 -0
  291. package/src/platforms/telegram/cli.ts +2 -1
  292. package/src/platforms/telegram/commands/index.ts +1 -0
  293. package/src/platforms/telegram/commands/whoami.test.ts +75 -0
  294. package/src/platforms/telegram/commands/whoami.ts +29 -0
  295. package/src/platforms/webex/cli.ts +2 -1
  296. package/src/platforms/webex/commands/auth.test.ts +58 -46
  297. package/src/platforms/webex/commands/index.ts +1 -0
  298. package/src/platforms/webex/commands/member.test.ts +1 -5
  299. package/src/platforms/webex/commands/message.test.ts +1 -5
  300. package/src/platforms/webex/commands/message.ts +1 -1
  301. package/src/platforms/webex/commands/snapshot.test.ts +1 -5
  302. package/src/platforms/webex/commands/space.test.ts +1 -5
  303. package/src/platforms/webex/commands/whoami.test.ts +113 -0
  304. package/src/platforms/webex/commands/whoami.ts +31 -0
  305. package/src/platforms/webex/credential-manager.test.ts +0 -1
  306. package/src/platforms/wechatbot/cli.ts +2 -1
  307. package/src/platforms/wechatbot/commands/index.ts +1 -0
  308. package/src/platforms/wechatbot/commands/whoami.test.ts +109 -0
  309. package/src/platforms/wechatbot/commands/whoami.ts +43 -0
  310. package/src/platforms/whatsapp/cli.ts +2 -1
  311. package/src/platforms/whatsapp/client.ts +156 -24
  312. package/src/platforms/whatsapp/commands/auth.ts +176 -70
  313. package/src/platforms/whatsapp/commands/index.ts +1 -0
  314. package/src/platforms/whatsapp/commands/shared.ts +2 -2
  315. package/src/platforms/whatsapp/commands/whoami.test.ts +59 -0
  316. package/src/platforms/whatsapp/commands/whoami.ts +21 -0
  317. package/src/platforms/whatsapp/ensure-auth.ts +2 -2
  318. package/src/platforms/whatsappbot/cli.ts +2 -1
  319. package/src/platforms/whatsappbot/commands/index.ts +1 -0
  320. package/src/platforms/whatsappbot/commands/whoami.test.ts +100 -0
  321. package/src/platforms/whatsappbot/commands/whoami.ts +57 -0
  322. package/src/shared/utils/qr.ts +92 -0
  323. package/src/tui/adapters/whatsapp-adapter.ts +19 -16
  324. package/dist/src/platforms/kakaotalk/commands/profile.d.ts +0 -3
  325. package/dist/src/platforms/kakaotalk/commands/profile.d.ts.map +0 -1
  326. package/dist/src/platforms/kakaotalk/commands/profile.js.map +0 -1
  327. package/dist/src/platforms/line/commands/profile.d.ts +0 -3
  328. package/dist/src/platforms/line/commands/profile.d.ts.map +0 -1
  329. package/dist/src/platforms/line/commands/profile.js.map +0 -1
@@ -13,6 +13,15 @@ const mockGetProfile = mock(() =>
13
13
  user_id: 'user-1',
14
14
  nickname: 'Test User',
15
15
  profile_image_url: 'https://example.com/avatar.jpg',
16
+ original_profile_image_url: 'https://example.com/avatar_orig.jpg',
17
+ background_image_url: 'https://example.com/bg.jpg',
18
+ original_background_image_url: 'https://example.com/bg_orig.jpg',
19
+ fullname: 'Real Name',
20
+ status_message: 'Hello!',
21
+ account_display_id: 'testuser',
22
+ account_email: 'test@example.com',
23
+ pstn_number: '+821012345678',
24
+ email_verified: true,
16
25
  }),
17
26
  )
18
27
 
@@ -24,9 +33,9 @@ mock.module('./shared', () => ({
24
33
  withKakaoClient: mockWithKakaoClient,
25
34
  }))
26
35
 
27
- import { profileCommand } from './profile'
36
+ import { whoamiCommand } from './whoami'
28
37
 
29
- describe('profile command', () => {
38
+ describe('whoami command', () => {
30
39
  let consoleLogSpy: ReturnType<typeof mock>
31
40
 
32
41
  beforeEach(() => {
@@ -43,6 +52,15 @@ describe('profile command', () => {
43
52
  user_id: 'user-1',
44
53
  nickname: 'Test User',
45
54
  profile_image_url: 'https://example.com/avatar.jpg',
55
+ original_profile_image_url: 'https://example.com/avatar_orig.jpg',
56
+ background_image_url: 'https://example.com/bg.jpg',
57
+ original_background_image_url: 'https://example.com/bg_orig.jpg',
58
+ fullname: 'Real Name',
59
+ status_message: 'Hello!',
60
+ account_display_id: 'testuser',
61
+ account_email: 'test@example.com',
62
+ pstn_number: '+821012345678',
63
+ email_verified: true,
46
64
  }),
47
65
  )
48
66
 
@@ -55,7 +73,7 @@ describe('profile command', () => {
55
73
  })
56
74
 
57
75
  test('outputs profile information', async () => {
58
- await profileCommand.parseAsync([], { from: 'user' })
76
+ await whoamiCommand.parseAsync([], { from: 'user' })
59
77
 
60
78
  expect(mockGetProfile).toHaveBeenCalled()
61
79
  const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
@@ -64,8 +82,22 @@ describe('profile command', () => {
64
82
  expect(output.profile_image_url).toBe('https://example.com/avatar.jpg')
65
83
  })
66
84
 
85
+ test('outputs enriched profile fields', async () => {
86
+ await whoamiCommand.parseAsync([], { from: 'user' })
87
+
88
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
89
+ expect(output.background_image_url).toBe('https://example.com/bg.jpg')
90
+ expect(output.original_background_image_url).toBe('https://example.com/bg_orig.jpg')
91
+ expect(output.fullname).toBe('Real Name')
92
+ expect(output.status_message).toBe('Hello!')
93
+ expect(output.account_display_id).toBe('testuser')
94
+ expect(output.account_email).toBe('test@example.com')
95
+ expect(output.pstn_number).toBe('+821012345678')
96
+ expect(output.email_verified).toBe(true)
97
+ })
98
+
67
99
  test('passes account option to withKakaoClient', async () => {
68
- await profileCommand.parseAsync(['--account', 'my-account'], { from: 'user' })
100
+ await whoamiCommand.parseAsync(['--account', 'my-account'], { from: 'user' })
69
101
 
70
102
  expect(mockWithKakaoClient).toHaveBeenCalledWith(
71
103
  expect.objectContaining({ account: 'my-account' }),
@@ -74,7 +106,7 @@ describe('profile command', () => {
74
106
  })
75
107
 
76
108
  test('outputs profile with pretty flag', async () => {
77
- await profileCommand.parseAsync(['--pretty'], { from: 'user' })
109
+ await whoamiCommand.parseAsync(['--pretty'], { from: 'user' })
78
110
 
79
111
  expect(mockGetProfile).toHaveBeenCalled()
80
112
  const rawOutput = consoleLogSpy.mock.calls[0][0]
@@ -5,7 +5,7 @@ import { formatOutput } from '@/shared/utils/output'
5
5
 
6
6
  import { withKakaoClient } from './shared'
7
7
 
8
- async function profileAction(options: { account?: string; pretty?: boolean }): Promise<void> {
8
+ async function whoamiAction(options: { account?: string; pretty?: boolean }): Promise<void> {
9
9
  try {
10
10
  const profile = await withKakaoClient(options, (client) => client.getProfile())
11
11
  console.log(formatOutput(profile, options.pretty))
@@ -14,8 +14,8 @@ async function profileAction(options: { account?: string; pretty?: boolean }): P
14
14
  }
15
15
  }
16
16
 
17
- export const profileCommand = new Command('profile')
18
- .description('Show your KakaoTalk profile')
17
+ export const whoamiCommand = new Command('whoami')
18
+ .description('Show current authenticated user')
19
19
  .option('--account <id>', 'Use a specific KakaoTalk account')
20
20
  .option('--pretty', 'Pretty print JSON output')
21
- .action(profileAction)
21
+ .action(whoamiAction)
@@ -14,7 +14,7 @@ import {
14
14
  PROTOCOL_VERSION,
15
15
  } from './config'
16
16
  import { LocoConnection } from './connection'
17
- import type { BookingResponse, CheckinResponse, LoginListResponse, LocoPacket } from './types'
17
+ import type { BookingResponse, CheckinResponse, LoginListResponse, LocoPacket, SyncState } from './types'
18
18
 
19
19
  export class LocoSession {
20
20
  private connection: LocoConnection | null = null
@@ -22,7 +22,7 @@ export class LocoSession {
22
22
  private pushHandler: ((packet: LocoPacket) => void) | null = null
23
23
  private closeHandler: (() => void) | null = null
24
24
 
25
- async login(oauthToken: string, userId: string, deviceUuid: string): Promise<LoginListResponse> {
25
+ async login(oauthToken: string, userId: string, deviceUuid: string, syncState?: SyncState): Promise<LoginListResponse> {
26
26
  const { host, port } = await this.bookAndCheckin(userId)
27
27
 
28
28
  this.connection = new LocoConnection()
@@ -35,6 +35,13 @@ export class LocoSession {
35
35
  this.connection.onClose(this.closeHandler)
36
36
  }
37
37
 
38
+ const chatIds = syncState?.chatIds.map((id) => new Long(id.low, id.high)) ?? []
39
+ const maxIds = syncState?.maxIds.map((id) => new Long(id.low, id.high)) ?? []
40
+ const lastTokenId = syncState
41
+ ? new Long(syncState.lastTokenId.low, syncState.lastTokenId.high)
42
+ : Long.fromNumber(0)
43
+ const lbk = syncState?.lbk ?? 0
44
+
38
45
  const response = await this.connection.sendPacket('LOGINLIST', {
39
46
  appVer: APP_VERSION,
40
47
  prtVer: PROTOCOL_VERSION,
@@ -45,11 +52,11 @@ export class LocoSession {
45
52
  oauthToken,
46
53
  ntype: 0,
47
54
  MCCMNC: MCCMNC,
48
- revision: 0,
49
- chatIds: [],
50
- maxIds: [],
51
- lastTokenId: Long.fromNumber(0),
52
- lbk: Long.fromNumber(0),
55
+ revision: syncState?.revision ?? 0,
56
+ chatIds,
57
+ maxIds,
58
+ lastTokenId,
59
+ lbk,
53
60
  rp: new Binary(Buffer.from([0x00, 0x00, 0xff, 0xff, 0x00, 0x00])),
54
61
  bg: false,
55
62
  })
@@ -117,6 +124,19 @@ export class LocoSession {
117
124
  })
118
125
  }
119
126
 
127
+ async getChatLogs(chatIds: Long[], sinces: Long[]): Promise<LocoPacket> {
128
+ if (!this.connection) throw new Error('Not connected')
129
+ return this.connection.sendPacket('MCHATLOGS', {
130
+ chatIds,
131
+ sinces,
132
+ })
133
+ }
134
+
135
+ async getChatInfo(chatId: Long): Promise<LocoPacket> {
136
+ if (!this.connection) throw new Error('Not connected')
137
+ return this.connection.sendPacket('CHATONROOM', { chatId })
138
+ }
139
+
120
140
  async getChatList(lastTokenId?: Long, lastChatId?: Long): Promise<LocoPacket> {
121
141
  if (!this.connection) throw new Error('Not connected')
122
142
  return this.connection.sendPacket('LCHATLIST', {
@@ -25,6 +25,15 @@ export interface ChatListResponse {
25
25
  [key: string]: unknown
26
26
  }
27
27
 
28
+ export interface SyncState {
29
+ version: 2
30
+ revision: number
31
+ chatIds: Array<{ low: number; high: number }>
32
+ maxIds: Array<{ low: number; high: number }>
33
+ lastTokenId: { low: number; high: number }
34
+ lbk: number
35
+ }
36
+
28
37
  // LOGINLIST uses short BSON field names: c=chatId, t=type, a=activeMembers, etc.
29
38
  export interface LoginListResponse extends ChatListResponse {
30
39
  userId: number
@@ -130,8 +130,14 @@ export interface KakaoProfile {
130
130
  nickname: string
131
131
  profile_image_url: string | null
132
132
  original_profile_image_url: string | null
133
+ background_image_url?: string | null
134
+ original_background_image_url?: string | null
135
+ fullname?: string | null
133
136
  status_message: string | null
134
137
  account_display_id: string | null
138
+ account_email?: string | null
139
+ pstn_number?: string | null
140
+ email_verified?: boolean | null
135
141
  }
136
142
 
137
143
  export const KakaoProfileSchema = z.object({
@@ -139,8 +145,14 @@ export const KakaoProfileSchema = z.object({
139
145
  nickname: z.string(),
140
146
  profile_image_url: z.string().nullable(),
141
147
  original_profile_image_url: z.string().nullable(),
148
+ background_image_url: z.string().nullable().optional(),
149
+ original_background_image_url: z.string().nullable().optional(),
150
+ fullname: z.string().nullable().optional(),
142
151
  status_message: z.string().nullable(),
143
152
  account_display_id: z.string().nullable(),
153
+ account_email: z.string().nullable().optional(),
154
+ pstn_number: z.string().nullable().optional(),
155
+ email_verified: z.boolean().nullable().optional(),
144
156
  })
145
157
 
146
158
  export const KakaoAccountCredentialsSchema = z.object({
@@ -4,7 +4,7 @@ import type { Command as CommandType } from 'commander'
4
4
  import { Command } from 'commander'
5
5
 
6
6
  import pkg from '../../../package.json' with { type: 'json' }
7
- import { authCommand, chatCommand, friendCommand, messageCommand, profileCommand } from './commands/index'
7
+ import { authCommand, chatCommand, friendCommand, messageCommand, whoamiCommand } from './commands/index'
8
8
  import { ensureLineAuth } from './ensure-auth'
9
9
 
10
10
  function isAuthCommand(command: CommandType): boolean {
@@ -32,7 +32,7 @@ program.addCommand(authCommand)
32
32
  program.addCommand(chatCommand)
33
33
  program.addCommand(friendCommand)
34
34
  program.addCommand(messageCommand)
35
- program.addCommand(profileCommand)
35
+ program.addCommand(whoamiCommand)
36
36
 
37
37
  program.parse(process.argv)
38
38
 
@@ -1,13 +1,8 @@
1
- import { execSync } from 'node:child_process'
2
- import { writeFileSync, unlinkSync } from 'node:fs'
3
- import { tmpdir } from 'node:os'
4
- import { join } from 'node:path'
5
-
6
1
  import { Command } from 'commander'
7
- import QRCode from 'qrcode'
8
2
 
9
3
  import { handleError } from '@/shared/utils/error-handler'
10
4
  import { formatOutput } from '@/shared/utils/output'
5
+ import { displayQR } from '@/shared/utils/qr'
11
6
  import { info } from '@/shared/utils/stderr'
12
7
 
13
8
  import { LineClient } from '../client'
@@ -22,34 +17,6 @@ function getDefaultDevice(): LineDevice {
22
17
  return 'ANDROIDSECONDARY'
23
18
  }
24
19
 
25
- async function createQRHtmlFile(url: string): Promise<string> {
26
- const svgString = await QRCode.toString(url, { type: 'svg', margin: 2 })
27
- const html = `<!DOCTYPE html>
28
- <html><head><meta charset="utf-8"><title>LINE QR Login</title>
29
- <style>body{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;margin:0;font-family:-apple-system,system-ui,sans-serif;background:#06C755}
30
- .card{background:#fff;border-radius:16px;padding:40px;text-align:center;box-shadow:0 4px 24px rgba(0,0,0,.15)}
31
- h1{margin:0 0 8px;font-size:22px;color:#111}p{margin:0 0 24px;color:#666;font-size:14px}
32
- svg{width:280px;height:280px}</style></head>
33
- <body><div class="card"><h1>LINE Login</h1><p>Scan with the LINE mobile app</p>${svgString}</div></body></html>`
34
-
35
- const htmlPath = join(tmpdir(), `line-qr-${Date.now()}.html`)
36
- writeFileSync(htmlPath, html)
37
- setTimeout(() => { try { unlinkSync(htmlPath) } catch {} }, 300_000).unref()
38
- return htmlPath
39
- }
40
-
41
- function openInBrowser(filePath: string): void {
42
- try {
43
- if (process.platform === 'darwin') {
44
- execSync(`open "${filePath}"`, { stdio: 'ignore' })
45
- } else if (process.platform === 'win32') {
46
- execSync(`start "" "${filePath}"`, { stdio: 'ignore' })
47
- } else {
48
- execSync(`xdg-open "${filePath}"`, { stdio: 'ignore' })
49
- }
50
- } catch {}
51
- }
52
-
53
20
  async function loginAction(options: {
54
21
  email?: string
55
22
  password?: string
@@ -76,18 +43,23 @@ async function loginAction(options: {
76
43
  const profile = await client.getProfile()
77
44
  const credentials = { ...tempCredentials, account_id: profile.mid, display_name: profile.display_name }
78
45
  await credManager.setAccount(credentials)
79
- console.log(formatOutput({
80
- authenticated: true,
81
- account_id: profile.mid,
82
- display_name: profile.display_name,
83
- device,
84
- }, options.pretty))
46
+ console.log(
47
+ formatOutput(
48
+ {
49
+ authenticated: true,
50
+ account_id: profile.mid,
51
+ display_name: profile.display_name,
52
+ device,
53
+ },
54
+ options.pretty,
55
+ ),
56
+ )
85
57
  } else if (options.email && options.password) {
86
58
  const result = await client.loginWithEmail({
87
59
  email: options.email,
88
60
  password: options.password,
89
61
  device,
90
- onPincode: (pin) => {
62
+ onPincode: (pin) => {
91
63
  if (interactive) {
92
64
  info(`\nEnter this PIN in the LINE mobile app: ${pin}\n`)
93
65
  }
@@ -98,27 +70,14 @@ async function loginAction(options: {
98
70
  const result = await client.loginWithQR({
99
71
  device,
100
72
  onQRUrl: async (url) => {
101
- const htmlPath = await createQRHtmlFile(url).catch(() => null)
102
- if (htmlPath) openInBrowser(htmlPath)
103
-
104
- if (interactive) {
105
- try {
106
- const qrAscii = await QRCode.toString(url, { type: 'terminal', small: true })
107
- info('\nScan this QR code with the LINE mobile app:\n')
108
- info(qrAscii)
109
- } catch {
110
- info(`\nOpen the QR code in the browser window, or scan this URL:\n${url}\n`)
111
- }
112
- } else {
113
- console.log(formatOutput({
114
- next_action: 'scan_qr',
115
- qr_url: url,
116
- qr_html_path: htmlPath,
117
- message: htmlPath
118
- ? 'QR code opened in browser. Scan with LINE mobile app to complete login.'
119
- : 'QR code generated. Open qr_url to scan with LINE mobile app.',
120
- }, options.pretty))
121
- }
73
+ await displayQR(url, {
74
+ platform: 'LINE',
75
+ brandColor: '#06C755',
76
+ scanInstruction: 'Scan with the LINE mobile app',
77
+ interactive,
78
+ formatOutput,
79
+ pretty: options.pretty,
80
+ })
122
81
  },
123
82
  onPincode: (pin) => {
124
83
  info(`\nEnter this PIN in the LINE mobile app: ${pin}\n`)
@@ -141,13 +100,18 @@ async function statusAction(options: { pretty?: boolean; account?: string }): Pr
141
100
  return
142
101
  }
143
102
 
144
- console.log(formatOutput({
145
- account_id: account.account_id,
146
- device: account.device,
147
- display_name: account.display_name,
148
- created_at: account.created_at,
149
- updated_at: account.updated_at,
150
- }, options.pretty))
103
+ console.log(
104
+ formatOutput(
105
+ {
106
+ account_id: account.account_id,
107
+ device: account.device,
108
+ display_name: account.display_name,
109
+ created_at: account.created_at,
110
+ updated_at: account.updated_at,
111
+ },
112
+ options.pretty,
113
+ ),
114
+ )
151
115
  } catch (error) {
152
116
  handleError(error as Error)
153
117
  }
@@ -196,7 +160,10 @@ export const authCommand = new Command('auth')
196
160
  .option('--email <email>', 'Email address for email/password login')
197
161
  .option('--password <password>', 'Password for email login')
198
162
  .option('--token <token>', 'Login with existing auth token directly')
199
- .option('--device <type>', 'Device type (default: ANDROIDSECONDARY). Secondary device that coexists with LINE desktop. Use DESKTOPMAC/DESKTOPWIN to replace desktop session.')
163
+ .option(
164
+ '--device <type>',
165
+ 'Device type (default: ANDROIDSECONDARY). Secondary device that coexists with LINE desktop. Use DESKTOPMAC/DESKTOPWIN to replace desktop session.',
166
+ )
200
167
  .option('--pretty', 'Pretty print JSON output')
201
168
  .action(loginAction),
202
169
  )
@@ -2,4 +2,4 @@ export { authCommand } from './auth'
2
2
  export { chatCommand } from './chat'
3
3
  export { friendCommand } from './friend'
4
4
  export { messageCommand } from './message'
5
- export { profileCommand } from './profile'
5
+ export { whoamiCommand } from './whoami'
@@ -3,7 +3,7 @@ import { afterEach, beforeEach, expect, mock, spyOn, test } from 'bun:test'
3
3
  const originalConsoleLog = console.log
4
4
 
5
5
  import { LineClient } from '../client'
6
- import { profileCommand } from './profile'
6
+ import { whoamiCommand } from './whoami'
7
7
 
8
8
  let loginSpy: ReturnType<typeof spyOn>
9
9
  let getProfileSpy: ReturnType<typeof spyOn>
@@ -33,9 +33,9 @@ afterEach(() => {
33
33
  console.log = originalConsoleLog
34
34
  })
35
35
 
36
- test('profile: fetches and outputs profile', async () => {
36
+ test('whoami: fetches and outputs profile', async () => {
37
37
  // when
38
- await profileCommand.parseAsync(['node', 'profile'])
38
+ await whoamiCommand.parseAsync(['node', 'whoami'])
39
39
 
40
40
  // then
41
41
  expect(loginSpy).toHaveBeenCalledTimes(1)
@@ -46,9 +46,9 @@ test('profile: fetches and outputs profile', async () => {
46
46
  expect(output.display_name).toBe('Test User')
47
47
  })
48
48
 
49
- test('profile: outputs profile with all fields', async () => {
49
+ test('whoami: outputs profile with all fields', async () => {
50
50
  // when
51
- await profileCommand.parseAsync(['node', 'profile'])
51
+ await whoamiCommand.parseAsync(['node', 'whoami'])
52
52
 
53
53
  // then
54
54
  const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
@@ -58,26 +58,26 @@ test('profile: outputs profile with all fields', async () => {
58
58
  expect(output.status_message).toBeDefined()
59
59
  })
60
60
 
61
- test('profile: closes client after fetching profile', async () => {
61
+ test('whoami: closes client after fetching profile', async () => {
62
62
  // when
63
- await profileCommand.parseAsync(['node', 'profile'])
63
+ await whoamiCommand.parseAsync(['node', 'whoami'])
64
64
 
65
65
  // then
66
66
  expect(closeSpy).toHaveBeenCalledTimes(1)
67
67
  })
68
68
 
69
- test('profile: outputs profile with picture_url', async () => {
69
+ test('whoami: outputs profile with picture_url', async () => {
70
70
  // when
71
- await profileCommand.parseAsync(['node', 'profile'])
71
+ await whoamiCommand.parseAsync(['node', 'whoami'])
72
72
 
73
73
  // then
74
74
  const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
75
75
  expect(output.picture_url).toBe('https://example.com/pic.jpg')
76
76
  })
77
77
 
78
- test('profile: outputs profile with status_message', async () => {
78
+ test('whoami: outputs profile with status_message', async () => {
79
79
  // when
80
- await profileCommand.parseAsync(['node', 'profile'])
80
+ await whoamiCommand.parseAsync(['node', 'whoami'])
81
81
 
82
82
  // then
83
83
  const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
@@ -5,7 +5,7 @@ import { formatOutput } from '@/shared/utils/output'
5
5
 
6
6
  import { LineClient } from '../client'
7
7
 
8
- async function profileAction(options: { pretty?: boolean }): Promise<void> {
8
+ async function whoamiAction(options: { pretty?: boolean }): Promise<void> {
9
9
  let client: LineClient | undefined
10
10
  try {
11
11
  client = await new LineClient().login()
@@ -18,7 +18,7 @@ async function profileAction(options: { pretty?: boolean }): Promise<void> {
18
18
  }
19
19
  }
20
20
 
21
- export const profileCommand = new Command('profile')
22
- .description('Show your LINE profile')
21
+ export const whoamiCommand = new Command('whoami')
22
+ .description('Show current authenticated user')
23
23
  .option('--pretty', 'Pretty print JSON output')
24
- .action(profileAction)
24
+ .action(whoamiAction)
@@ -22,6 +22,7 @@ import {
22
22
  unreadCommand,
23
23
  userCommand,
24
24
  usergroupCommand,
25
+ whoamiCommand,
25
26
  workspaceCommand,
26
27
  } from './commands/index'
27
28
  import { ensureSlackAuth } from './ensure-auth'
@@ -66,6 +67,7 @@ program.addCommand(bookmarkCommand)
66
67
  program.addCommand(reminderCommand)
67
68
  program.addCommand(emojiCommand)
68
69
  program.addCommand(usergroupCommand)
70
+ program.addCommand(whoamiCommand)
69
71
 
70
72
  program.parse(process.argv)
71
73
 
@@ -15,4 +15,5 @@ export { snapshotCommand } from './snapshot'
15
15
  export { unreadCommand } from './unread'
16
16
  export { userCommand } from './user'
17
17
  export { usergroupCommand } from './usergroup'
18
+ export { whoamiCommand } from './whoami'
18
19
  export { workspaceCommand } from './workspace'
@@ -0,0 +1,126 @@
1
+ import { afterEach, beforeEach, expect, spyOn, test } from 'bun:test'
2
+
3
+ import { SlackClient } from '@/platforms/slack/client'
4
+ import { CredentialManager } from '@/platforms/slack/credential-manager'
5
+ import { whoamiAction, whoamiCommand } from '@/platforms/slack/commands/whoami'
6
+
7
+ let credManagerSpy: ReturnType<typeof spyOn>
8
+ let clientTestAuthSpy: ReturnType<typeof spyOn>
9
+ let clientGetUserSpy: ReturnType<typeof spyOn>
10
+ let consoleLogSpy: ReturnType<typeof spyOn>
11
+ let processExitSpy: ReturnType<typeof spyOn>
12
+
13
+ const mockWorkspace = {
14
+ workspace_id: 'T123',
15
+ workspace_name: 'Test Workspace',
16
+ token: 'xoxc-test',
17
+ cookie: 'test-cookie',
18
+ }
19
+
20
+ const mockAuthInfo = {
21
+ user_id: 'U123',
22
+ team_id: 'T123',
23
+ user: 'alice',
24
+ team: 'Test Workspace',
25
+ }
26
+
27
+ const mockUser = {
28
+ id: 'U123',
29
+ name: 'alice',
30
+ real_name: 'Alice Smith',
31
+ is_admin: true,
32
+ is_owner: false,
33
+ is_bot: false,
34
+ is_app_user: false,
35
+ profile: {
36
+ email: 'alice@example.com',
37
+ title: 'Engineer',
38
+ },
39
+ }
40
+
41
+ beforeEach(() => {
42
+ credManagerSpy = spyOn(CredentialManager.prototype, 'getWorkspace').mockResolvedValue(mockWorkspace)
43
+ clientTestAuthSpy = spyOn(SlackClient.prototype, 'testAuth').mockResolvedValue(mockAuthInfo)
44
+ clientGetUserSpy = spyOn(SlackClient.prototype, 'getUser').mockResolvedValue(mockUser)
45
+ consoleLogSpy = spyOn(console, 'log').mockImplementation(() => {})
46
+ processExitSpy = spyOn(process, 'exit').mockImplementation((_code?: number) => undefined as never)
47
+ })
48
+
49
+ afterEach(() => {
50
+ credManagerSpy?.mockRestore()
51
+ clientTestAuthSpy?.mockRestore()
52
+ clientGetUserSpy?.mockRestore()
53
+ consoleLogSpy?.mockRestore()
54
+ processExitSpy?.mockRestore()
55
+ })
56
+
57
+ test('whoami command is defined with correct name and description', () => {
58
+ expect(whoamiCommand).toBeDefined()
59
+ expect(whoamiCommand.name()).toBe('whoami')
60
+ expect(whoamiCommand.description()).toBe('Show current authenticated user')
61
+ })
62
+
63
+ test('whoami command has --pretty option', () => {
64
+ const options = whoamiCommand.options
65
+ const hasPretty = options.some((opt: { long?: string }) => opt.long === '--pretty')
66
+ expect(hasPretty).toBe(true)
67
+ })
68
+
69
+ test('whoami outputs expected fields', async () => {
70
+ await whoamiAction({})
71
+
72
+ expect(consoleLogSpy).toHaveBeenCalledWith(
73
+ JSON.stringify({
74
+ id: 'U123',
75
+ name: 'alice',
76
+ real_name: 'Alice Smith',
77
+ is_admin: true,
78
+ is_owner: false,
79
+ is_bot: false,
80
+ is_app_user: false,
81
+ team_id: 'T123',
82
+ team: 'Test Workspace',
83
+ profile: {
84
+ email: 'alice@example.com',
85
+ title: 'Engineer',
86
+ },
87
+ }),
88
+ )
89
+ })
90
+
91
+ test('whoami outputs pretty-printed JSON when pretty is true', async () => {
92
+ await whoamiAction({ pretty: true })
93
+
94
+ expect(consoleLogSpy).toHaveBeenCalledWith(
95
+ JSON.stringify(
96
+ {
97
+ id: 'U123',
98
+ name: 'alice',
99
+ real_name: 'Alice Smith',
100
+ is_admin: true,
101
+ is_owner: false,
102
+ is_bot: false,
103
+ is_app_user: false,
104
+ team_id: 'T123',
105
+ team: 'Test Workspace',
106
+ profile: {
107
+ email: 'alice@example.com',
108
+ title: 'Engineer',
109
+ },
110
+ },
111
+ null,
112
+ 2,
113
+ ),
114
+ )
115
+ })
116
+
117
+ test('whoami exits with error when no workspace is set', async () => {
118
+ credManagerSpy.mockResolvedValue(null)
119
+
120
+ await whoamiAction({})
121
+
122
+ expect(consoleLogSpy).toHaveBeenCalledWith(
123
+ JSON.stringify({ error: 'No current workspace set. Run "auth extract" first.' }),
124
+ )
125
+ expect(processExitSpy).toHaveBeenCalledWith(1)
126
+ })