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
@@ -72,7 +72,7 @@ export class KakaoTalkListener {
72
72
  if (!this.running) return
73
73
 
74
74
  try {
75
- const { oauthToken, userId, deviceUuid } = this.client.getCredentials()
75
+ const { oauthToken, userId, deviceUuid, deviceType } = this.client.getCredentials()
76
76
  if (!this.running) return
77
77
 
78
78
  this.userId = userId
@@ -88,7 +88,7 @@ export class KakaoTalkListener {
88
88
  }
89
89
  })
90
90
 
91
- await session.login(oauthToken, userId, deviceUuid)
91
+ await session.login(oauthToken, userId, deviceUuid, undefined, deviceType)
92
92
 
93
93
  if (!this.running) {
94
94
  session.close()
@@ -1,6 +1,8 @@
1
1
  // Protocol constants for KakaoTalk LOCO.
2
2
  // See protocol/NOTICE.md for attribution of protocol knowledge.
3
3
 
4
+ import type { KakaoDeviceType } from '../types'
5
+
4
6
  // LOCO RSA public key (PKCS#1 DER, base64). RSA-2048, e=3.
5
7
  // Source: openkakao (MIT) — extracted from KakaoTalk macOS binary.
6
8
  export const LOCO_RSA_PUBLIC_KEY_DER_B64 =
@@ -17,8 +19,17 @@ export const BOOKING_PORT = 443
17
19
  export const CHECKIN_HOST = 'ticket-loco.kakao.com'
18
20
  export const CHECKIN_PORT = 995
19
21
 
20
- export const APP_VERSION = '26.2.0'
21
- export const OS = 'mac'
22
+ // PC slot identity — platform-aware: 'win' on Windows, 'mac' on macOS
23
+ const PC_APP_VERSION = '26.2.0'
24
+ const PC_OS: string = process.platform === 'win32' ? 'win' : 'mac'
25
+ export const PC_OS_NAME: string = process.platform === 'win32' ? 'Windows' : 'macOS'
26
+
27
+ // Android (tablet slot) identity — must match the Android sub-device agent
28
+ // used in auth/kakao-login.ts so the server sees a consistent tablet session.
29
+ const ANDROID_APP_VERSION = '25.9.2'
30
+ const ANDROID_OS = 'android'
31
+
32
+ // dtype: 2 = sub-device, 1 = main device (ref: node-kakao config.ts)
22
33
  export const DTYPE = 2
23
34
  export const MCCMNC = '99999'
24
35
  export const LANG = 'ko'
@@ -26,3 +37,16 @@ export const COUNTRY_ISO = 'KR'
26
37
  export const PROTOCOL_VERSION = '1'
27
38
 
28
39
  export const PING_INTERVAL_MS = 300_000
40
+
41
+ export interface LocoDeviceConfig {
42
+ os: string
43
+ appVersion: string
44
+ useSub: boolean
45
+ }
46
+
47
+ export function getLocoDeviceConfig(deviceType: KakaoDeviceType): LocoDeviceConfig {
48
+ if (deviceType === 'tablet') {
49
+ return { os: ANDROID_OS, appVersion: ANDROID_APP_VERSION, useSub: true }
50
+ }
51
+ return { os: PC_OS, appVersion: PC_APP_VERSION, useSub: false }
52
+ }
@@ -1,7 +1,8 @@
1
1
  import { Binary, Long } from 'bson'
2
2
 
3
+ import type { KakaoDeviceType } from '../types'
4
+
3
5
  import {
4
- APP_VERSION,
5
6
  BOOKING_HOST,
6
7
  BOOKING_PORT,
7
8
  CHECKIN_HOST,
@@ -12,18 +13,23 @@ import {
12
13
  MCCMNC,
13
14
  PING_INTERVAL_MS,
14
15
  PROTOCOL_VERSION,
16
+ getLocoDeviceConfig,
15
17
  } from './config'
16
18
  import { LocoConnection } from './connection'
17
- import type { BookingResponse, CheckinResponse, LoginListResponse, LocoPacket } from './types'
19
+ import type { BookingResponse, CheckinResponse, LoginListResponse, LocoPacket, SyncState } from './types'
18
20
 
19
21
  export class LocoSession {
20
22
  private connection: LocoConnection | null = null
21
23
  private pingTimer: ReturnType<typeof setInterval> | null = null
22
24
  private pushHandler: ((packet: LocoPacket) => void) | null = null
23
25
  private closeHandler: (() => void) | null = null
26
+ private deviceType: KakaoDeviceType = 'tablet'
27
+
28
+ async login(oauthToken: string, userId: string, deviceUuid: string, syncState?: SyncState, deviceType?: KakaoDeviceType): Promise<LoginListResponse> {
29
+ this.deviceType = deviceType ?? 'tablet'
30
+ const deviceConfig = getLocoDeviceConfig(this.deviceType)
24
31
 
25
- async login(oauthToken: string, userId: string, deviceUuid: string): Promise<LoginListResponse> {
26
- const { host, port } = await this.bookAndCheckin(userId)
32
+ const { host, port } = await this.bookAndCheckin(userId, deviceConfig)
27
33
 
28
34
  this.connection = new LocoConnection()
29
35
  await this.connection.connectSecure(host, port)
@@ -35,21 +41,28 @@ export class LocoSession {
35
41
  this.connection.onClose(this.closeHandler)
36
42
  }
37
43
 
44
+ const chatIds = syncState?.chatIds.map((id) => new Long(id.low, id.high)) ?? []
45
+ const maxIds = syncState?.maxIds.map((id) => new Long(id.low, id.high)) ?? []
46
+ const lastTokenId = syncState
47
+ ? new Long(syncState.lastTokenId.low, syncState.lastTokenId.high)
48
+ : Long.fromNumber(0)
49
+ const lbk = syncState?.lbk ?? 0
50
+
38
51
  const response = await this.connection.sendPacket('LOGINLIST', {
39
- appVer: APP_VERSION,
52
+ appVer: deviceConfig.appVersion,
40
53
  prtVer: PROTOCOL_VERSION,
41
- os: 'mac',
54
+ os: deviceConfig.os,
42
55
  lang: LANG,
43
56
  dtype: DTYPE,
44
57
  duuid: deviceUuid,
45
58
  oauthToken,
46
59
  ntype: 0,
47
60
  MCCMNC: MCCMNC,
48
- revision: 0,
49
- chatIds: [],
50
- maxIds: [],
51
- lastTokenId: Long.fromNumber(0),
52
- lbk: Long.fromNumber(0),
61
+ revision: syncState?.revision ?? 0,
62
+ chatIds,
63
+ maxIds,
64
+ lastTokenId,
65
+ lbk,
53
66
  rp: new Binary(Buffer.from([0x00, 0x00, 0xff, 0xff, 0x00, 0x00])),
54
67
  bg: false,
55
68
  })
@@ -58,12 +71,12 @@ export class LocoSession {
58
71
  return response.body as unknown as LoginListResponse
59
72
  }
60
73
 
61
- private async bookAndCheckin(userId: string): Promise<{ host: string; port: number }> {
74
+ private async bookAndCheckin(userId: string, deviceConfig: { os: string; appVersion: string; useSub: boolean }): Promise<{ host: string; port: number }> {
62
75
  const bookingConn = new LocoConnection()
63
76
  await bookingConn.connectTls(BOOKING_HOST, BOOKING_PORT)
64
77
 
65
78
  const bookingResponse = await bookingConn.sendPacket('GETCONF', {
66
- os: 'mac',
79
+ os: deviceConfig.os,
67
80
  model: '',
68
81
  })
69
82
  bookingConn.close()
@@ -79,13 +92,13 @@ export class LocoSession {
79
92
 
80
93
  const checkinResponse = await checkinConn.sendPacket('CHECKIN', {
81
94
  userId: Number(userId),
82
- os: 'mac',
95
+ os: deviceConfig.os,
83
96
  ntype: 0,
84
- appVer: APP_VERSION,
97
+ appVer: deviceConfig.appVersion,
85
98
  MCCMNC: MCCMNC,
86
99
  lang: LANG,
87
100
  countryISO: COUNTRY_ISO,
88
- useSub: true,
101
+ useSub: deviceConfig.useSub,
89
102
  })
90
103
  checkinConn.close()
91
104
 
@@ -117,6 +130,19 @@ export class LocoSession {
117
130
  })
118
131
  }
119
132
 
133
+ async getChatLogs(chatIds: Long[], sinces: Long[]): Promise<LocoPacket> {
134
+ if (!this.connection) throw new Error('Not connected')
135
+ return this.connection.sendPacket('MCHATLOGS', {
136
+ chatIds,
137
+ sinces,
138
+ })
139
+ }
140
+
141
+ async getChatInfo(chatId: Long): Promise<LocoPacket> {
142
+ if (!this.connection) throw new Error('Not connected')
143
+ return this.connection.sendPacket('CHATONROOM', { chatId })
144
+ }
145
+
120
146
  async getChatList(lastTokenId?: Long, lastChatId?: Long): Promise<LocoPacket> {
121
147
  if (!this.connection) throw new Error('Not connected')
122
148
  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
@@ -11,6 +11,8 @@ export interface ExtractedKakaoToken {
11
11
  login_form_body?: string
12
12
  }
13
13
 
14
+ export type KakaoAuthMethod = 'login' | 'extract'
15
+
14
16
  export interface KakaoAccountCredentials {
15
17
  account_id: string
16
18
  oauth_token: string
@@ -18,6 +20,7 @@ export interface KakaoAccountCredentials {
18
20
  refresh_token?: string
19
21
  device_uuid: string
20
22
  device_type: KakaoDeviceType
23
+ auth_method?: KakaoAuthMethod
21
24
  created_at: string
22
25
  updated_at: string
23
26
  }
@@ -130,8 +133,14 @@ export interface KakaoProfile {
130
133
  nickname: string
131
134
  profile_image_url: string | null
132
135
  original_profile_image_url: string | null
136
+ background_image_url?: string | null
137
+ original_background_image_url?: string | null
138
+ fullname?: string | null
133
139
  status_message: string | null
134
140
  account_display_id: string | null
141
+ account_email?: string | null
142
+ pstn_number?: string | null
143
+ email_verified?: boolean | null
135
144
  }
136
145
 
137
146
  export const KakaoProfileSchema = z.object({
@@ -139,8 +148,14 @@ export const KakaoProfileSchema = z.object({
139
148
  nickname: z.string(),
140
149
  profile_image_url: z.string().nullable(),
141
150
  original_profile_image_url: z.string().nullable(),
151
+ background_image_url: z.string().nullable().optional(),
152
+ original_background_image_url: z.string().nullable().optional(),
153
+ fullname: z.string().nullable().optional(),
142
154
  status_message: z.string().nullable(),
143
155
  account_display_id: z.string().nullable(),
156
+ account_email: z.string().nullable().optional(),
157
+ pstn_number: z.string().nullable().optional(),
158
+ email_verified: z.boolean().nullable().optional(),
144
159
  })
145
160
 
146
161
  export const KakaoAccountCredentialsSchema = z.object({
@@ -150,6 +165,7 @@ export const KakaoAccountCredentialsSchema = z.object({
150
165
  refresh_token: z.string().optional(),
151
166
  device_uuid: z.string(),
152
167
  device_type: z.enum(['pc', 'tablet']),
168
+ auth_method: z.enum(['login', 'extract']).optional(),
153
169
  created_at: z.string(),
154
170
  updated_at: z.string(),
155
171
  })
@@ -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
+ })