agent-messenger 2.3.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (438) hide show
  1. package/.claude-plugin/README.md +16 -16
  2. package/.claude-plugin/marketplace.json +29 -29
  3. package/.claude-plugin/plugin.json +5 -5
  4. package/.github/workflows/release.yml +0 -12
  5. package/CONTRIBUTING.md +1 -1
  6. package/README.md +11 -8
  7. package/bun.lock +70 -110
  8. package/bunfig.toml +3 -0
  9. package/dist/package.json +11 -3
  10. package/dist/src/platforms/channeltalk/cli.d.ts.map +1 -1
  11. package/dist/src/platforms/channeltalk/cli.js +2 -1
  12. package/dist/src/platforms/channeltalk/cli.js.map +1 -1
  13. package/dist/src/platforms/channeltalk/commands/index.d.ts +1 -0
  14. package/dist/src/platforms/channeltalk/commands/index.d.ts.map +1 -1
  15. package/dist/src/platforms/channeltalk/commands/index.js +1 -0
  16. package/dist/src/platforms/channeltalk/commands/index.js.map +1 -1
  17. package/dist/src/platforms/channeltalk/commands/whoami.d.ts +22 -0
  18. package/dist/src/platforms/channeltalk/commands/whoami.d.ts.map +1 -0
  19. package/dist/src/platforms/channeltalk/commands/whoami.js +40 -0
  20. package/dist/src/platforms/channeltalk/commands/whoami.js.map +1 -0
  21. package/dist/src/platforms/channeltalkbot/cli.d.ts.map +1 -1
  22. package/dist/src/platforms/channeltalkbot/cli.js +2 -1
  23. package/dist/src/platforms/channeltalkbot/cli.js.map +1 -1
  24. package/dist/src/platforms/channeltalkbot/commands/index.d.ts +1 -0
  25. package/dist/src/platforms/channeltalkbot/commands/index.d.ts.map +1 -1
  26. package/dist/src/platforms/channeltalkbot/commands/index.js +1 -0
  27. package/dist/src/platforms/channeltalkbot/commands/index.js.map +1 -1
  28. package/dist/src/platforms/channeltalkbot/commands/whoami.d.ts +13 -0
  29. package/dist/src/platforms/channeltalkbot/commands/whoami.d.ts.map +1 -0
  30. package/dist/src/platforms/channeltalkbot/commands/whoami.js +31 -0
  31. package/dist/src/platforms/channeltalkbot/commands/whoami.js.map +1 -0
  32. package/dist/src/platforms/discord/cli.d.ts.map +1 -1
  33. package/dist/src/platforms/discord/cli.js +2 -1
  34. package/dist/src/platforms/discord/cli.js.map +1 -1
  35. package/dist/src/platforms/discord/commands/index.d.ts +1 -0
  36. package/dist/src/platforms/discord/commands/index.d.ts.map +1 -1
  37. package/dist/src/platforms/discord/commands/index.js +1 -0
  38. package/dist/src/platforms/discord/commands/index.js.map +1 -1
  39. package/dist/src/platforms/discord/commands/whoami.d.ts +6 -0
  40. package/dist/src/platforms/discord/commands/whoami.d.ts.map +1 -0
  41. package/dist/src/platforms/discord/commands/whoami.js +33 -0
  42. package/dist/src/platforms/discord/commands/whoami.js.map +1 -0
  43. package/dist/src/platforms/discordbot/cli.d.ts.map +1 -1
  44. package/dist/src/platforms/discordbot/cli.js +2 -1
  45. package/dist/src/platforms/discordbot/cli.js.map +1 -1
  46. package/dist/src/platforms/discordbot/client.js +2 -2
  47. package/dist/src/platforms/discordbot/client.js.map +1 -1
  48. package/dist/src/platforms/discordbot/commands/index.d.ts +1 -0
  49. package/dist/src/platforms/discordbot/commands/index.d.ts.map +1 -1
  50. package/dist/src/platforms/discordbot/commands/index.js +1 -0
  51. package/dist/src/platforms/discordbot/commands/index.js.map +1 -1
  52. package/dist/src/platforms/discordbot/commands/whoami.d.ts +14 -0
  53. package/dist/src/platforms/discordbot/commands/whoami.d.ts.map +1 -0
  54. package/dist/src/platforms/discordbot/commands/whoami.js +32 -0
  55. package/dist/src/platforms/discordbot/commands/whoami.js.map +1 -0
  56. package/dist/src/platforms/instagram/cli.d.ts.map +1 -1
  57. package/dist/src/platforms/instagram/cli.js +2 -1
  58. package/dist/src/platforms/instagram/cli.js.map +1 -1
  59. package/dist/src/platforms/instagram/client.d.ts +6 -0
  60. package/dist/src/platforms/instagram/client.d.ts.map +1 -1
  61. package/dist/src/platforms/instagram/client.js +12 -0
  62. package/dist/src/platforms/instagram/client.js.map +1 -1
  63. package/dist/src/platforms/instagram/commands/index.d.ts +1 -0
  64. package/dist/src/platforms/instagram/commands/index.d.ts.map +1 -1
  65. package/dist/src/platforms/instagram/commands/index.js +1 -0
  66. package/dist/src/platforms/instagram/commands/index.js.map +1 -1
  67. package/dist/src/platforms/instagram/commands/whoami.d.ts +7 -0
  68. package/dist/src/platforms/instagram/commands/whoami.d.ts.map +1 -0
  69. package/dist/src/platforms/instagram/commands/whoami.js +19 -0
  70. package/dist/src/platforms/instagram/commands/whoami.js.map +1 -0
  71. package/dist/src/platforms/kakaotalk/cli.d.ts.map +1 -1
  72. package/dist/src/platforms/kakaotalk/cli.js +2 -1
  73. package/dist/src/platforms/kakaotalk/cli.js.map +1 -1
  74. package/dist/src/platforms/kakaotalk/client.d.ts +2 -1
  75. package/dist/src/platforms/kakaotalk/client.d.ts.map +1 -1
  76. package/dist/src/platforms/kakaotalk/client.js +225 -23
  77. package/dist/src/platforms/kakaotalk/client.js.map +1 -1
  78. package/dist/src/platforms/kakaotalk/commands/index.d.ts +1 -0
  79. package/dist/src/platforms/kakaotalk/commands/index.d.ts.map +1 -1
  80. package/dist/src/platforms/kakaotalk/commands/index.js +1 -0
  81. package/dist/src/platforms/kakaotalk/commands/index.js.map +1 -1
  82. package/dist/src/platforms/kakaotalk/commands/whoami.d.ts +3 -0
  83. package/dist/src/platforms/kakaotalk/commands/whoami.d.ts.map +1 -0
  84. package/dist/src/platforms/kakaotalk/commands/whoami.js +19 -0
  85. package/dist/src/platforms/kakaotalk/commands/whoami.js.map +1 -0
  86. package/dist/src/platforms/kakaotalk/index.d.ts +2 -2
  87. package/dist/src/platforms/kakaotalk/index.d.ts.map +1 -1
  88. package/dist/src/platforms/kakaotalk/index.js +1 -1
  89. package/dist/src/platforms/kakaotalk/index.js.map +1 -1
  90. package/dist/src/platforms/kakaotalk/protocol/session.d.ts +4 -2
  91. package/dist/src/platforms/kakaotalk/protocol/session.d.ts.map +1 -1
  92. package/dist/src/platforms/kakaotalk/protocol/session.js +27 -7
  93. package/dist/src/platforms/kakaotalk/protocol/session.js.map +1 -1
  94. package/dist/src/platforms/kakaotalk/protocol/types.d.ts +17 -0
  95. package/dist/src/platforms/kakaotalk/protocol/types.d.ts.map +1 -1
  96. package/dist/src/platforms/kakaotalk/protocol/types.js.map +1 -1
  97. package/dist/src/platforms/kakaotalk/types.d.ts +28 -0
  98. package/dist/src/platforms/kakaotalk/types.d.ts.map +1 -1
  99. package/dist/src/platforms/kakaotalk/types.js +14 -0
  100. package/dist/src/platforms/kakaotalk/types.js.map +1 -1
  101. package/dist/src/platforms/line/cli.js +2 -2
  102. package/dist/src/platforms/line/cli.js.map +1 -1
  103. package/dist/src/platforms/line/client.d.ts.map +1 -1
  104. package/dist/src/platforms/line/client.js +9 -36
  105. package/dist/src/platforms/line/client.js.map +1 -1
  106. package/dist/src/platforms/line/commands/auth.d.ts.map +1 -1
  107. package/dist/src/platforms/line/commands/auth.js +9 -47
  108. package/dist/src/platforms/line/commands/auth.js.map +1 -1
  109. package/dist/src/platforms/line/commands/index.d.ts +1 -1
  110. package/dist/src/platforms/line/commands/index.d.ts.map +1 -1
  111. package/dist/src/platforms/line/commands/index.js +1 -1
  112. package/dist/src/platforms/line/commands/index.js.map +1 -1
  113. package/dist/src/platforms/line/commands/whoami.d.ts +3 -0
  114. package/dist/src/platforms/line/commands/whoami.d.ts.map +1 -0
  115. package/dist/src/platforms/line/commands/{profile.js → whoami.js} +5 -5
  116. package/dist/src/platforms/line/commands/whoami.js.map +1 -0
  117. package/dist/src/platforms/slack/cli.d.ts.map +1 -1
  118. package/dist/src/platforms/slack/cli.js +2 -1
  119. package/dist/src/platforms/slack/cli.js.map +1 -1
  120. package/dist/src/platforms/slack/commands/index.d.ts +1 -0
  121. package/dist/src/platforms/slack/commands/index.d.ts.map +1 -1
  122. package/dist/src/platforms/slack/commands/index.js +1 -0
  123. package/dist/src/platforms/slack/commands/index.js.map +1 -1
  124. package/dist/src/platforms/slack/commands/whoami.d.ts +6 -0
  125. package/dist/src/platforms/slack/commands/whoami.d.ts.map +1 -0
  126. package/dist/src/platforms/slack/commands/whoami.js +39 -0
  127. package/dist/src/platforms/slack/commands/whoami.js.map +1 -0
  128. package/dist/src/platforms/slackbot/cli.d.ts.map +1 -1
  129. package/dist/src/platforms/slackbot/cli.js +2 -1
  130. package/dist/src/platforms/slackbot/cli.js.map +1 -1
  131. package/dist/src/platforms/slackbot/commands/index.d.ts +1 -0
  132. package/dist/src/platforms/slackbot/commands/index.d.ts.map +1 -1
  133. package/dist/src/platforms/slackbot/commands/index.js +1 -0
  134. package/dist/src/platforms/slackbot/commands/index.js.map +1 -1
  135. package/dist/src/platforms/slackbot/commands/whoami.d.ts +14 -0
  136. package/dist/src/platforms/slackbot/commands/whoami.d.ts.map +1 -0
  137. package/dist/src/platforms/slackbot/commands/whoami.js +32 -0
  138. package/dist/src/platforms/slackbot/commands/whoami.js.map +1 -0
  139. package/dist/src/platforms/teams/cli.d.ts.map +1 -1
  140. package/dist/src/platforms/teams/cli.js +2 -1
  141. package/dist/src/platforms/teams/cli.js.map +1 -1
  142. package/dist/src/platforms/teams/commands/index.d.ts +1 -0
  143. package/dist/src/platforms/teams/commands/index.d.ts.map +1 -1
  144. package/dist/src/platforms/teams/commands/index.js +1 -0
  145. package/dist/src/platforms/teams/commands/index.js.map +1 -1
  146. package/dist/src/platforms/teams/commands/reaction.d.ts.map +1 -1
  147. package/dist/src/platforms/teams/commands/reaction.js +2 -0
  148. package/dist/src/platforms/teams/commands/reaction.js.map +1 -1
  149. package/dist/src/platforms/teams/commands/whoami.d.ts +6 -0
  150. package/dist/src/platforms/teams/commands/whoami.d.ts.map +1 -0
  151. package/dist/src/platforms/teams/commands/whoami.js +30 -0
  152. package/dist/src/platforms/teams/commands/whoami.js.map +1 -0
  153. package/dist/src/platforms/telegram/cli.d.ts.map +1 -1
  154. package/dist/src/platforms/telegram/cli.js +2 -1
  155. package/dist/src/platforms/telegram/cli.js.map +1 -1
  156. package/dist/src/platforms/telegram/commands/index.d.ts +1 -0
  157. package/dist/src/platforms/telegram/commands/index.d.ts.map +1 -1
  158. package/dist/src/platforms/telegram/commands/index.js +1 -0
  159. package/dist/src/platforms/telegram/commands/index.js.map +1 -1
  160. package/dist/src/platforms/telegram/commands/whoami.d.ts +7 -0
  161. package/dist/src/platforms/telegram/commands/whoami.d.ts.map +1 -0
  162. package/dist/src/platforms/telegram/commands/whoami.js +27 -0
  163. package/dist/src/platforms/telegram/commands/whoami.js.map +1 -0
  164. package/dist/src/platforms/webex/cli.d.ts.map +1 -1
  165. package/dist/src/platforms/webex/cli.js +2 -1
  166. package/dist/src/platforms/webex/cli.js.map +1 -1
  167. package/dist/src/platforms/webex/commands/index.d.ts +1 -0
  168. package/dist/src/platforms/webex/commands/index.d.ts.map +1 -1
  169. package/dist/src/platforms/webex/commands/index.js +1 -0
  170. package/dist/src/platforms/webex/commands/index.js.map +1 -1
  171. package/dist/src/platforms/webex/commands/message.js +1 -1
  172. package/dist/src/platforms/webex/commands/message.js.map +1 -1
  173. package/dist/src/platforms/webex/commands/whoami.d.ts +6 -0
  174. package/dist/src/platforms/webex/commands/whoami.d.ts.map +1 -0
  175. package/dist/src/platforms/webex/commands/whoami.js +30 -0
  176. package/dist/src/platforms/webex/commands/whoami.js.map +1 -0
  177. package/dist/src/platforms/wechatbot/cli.d.ts +5 -0
  178. package/dist/src/platforms/wechatbot/cli.d.ts.map +1 -0
  179. package/dist/src/platforms/wechatbot/cli.js +19 -0
  180. package/dist/src/platforms/wechatbot/cli.js.map +1 -0
  181. package/dist/src/platforms/wechatbot/client.d.ts +36 -0
  182. package/dist/src/platforms/wechatbot/client.d.ts.map +1 -0
  183. package/dist/src/platforms/wechatbot/client.js +208 -0
  184. package/dist/src/platforms/wechatbot/client.js.map +1 -0
  185. package/dist/src/platforms/wechatbot/commands/auth.d.ts +28 -0
  186. package/dist/src/platforms/wechatbot/commands/auth.d.ts.map +1 -0
  187. package/dist/src/platforms/wechatbot/commands/auth.js +164 -0
  188. package/dist/src/platforms/wechatbot/commands/auth.js.map +1 -0
  189. package/dist/src/platforms/wechatbot/commands/index.d.ts +6 -0
  190. package/dist/src/platforms/wechatbot/commands/index.d.ts.map +1 -0
  191. package/dist/src/platforms/wechatbot/commands/index.js +6 -0
  192. package/dist/src/platforms/wechatbot/commands/index.js.map +1 -0
  193. package/dist/src/platforms/wechatbot/commands/message.d.ts +18 -0
  194. package/dist/src/platforms/wechatbot/commands/message.d.ts.map +1 -0
  195. package/dist/src/platforms/wechatbot/commands/message.js +80 -0
  196. package/dist/src/platforms/wechatbot/commands/message.js.map +1 -0
  197. package/dist/src/platforms/wechatbot/commands/shared.d.ts +9 -0
  198. package/dist/src/platforms/wechatbot/commands/shared.d.ts.map +1 -0
  199. package/dist/src/platforms/wechatbot/commands/shared.js +13 -0
  200. package/dist/src/platforms/wechatbot/commands/shared.js.map +1 -0
  201. package/dist/src/platforms/wechatbot/commands/template.d.ts +19 -0
  202. package/dist/src/platforms/wechatbot/commands/template.d.ts.map +1 -0
  203. package/dist/src/platforms/wechatbot/commands/template.js +76 -0
  204. package/dist/src/platforms/wechatbot/commands/template.js.map +1 -0
  205. package/dist/src/platforms/wechatbot/commands/user.d.ts +20 -0
  206. package/dist/src/platforms/wechatbot/commands/user.d.ts.map +1 -0
  207. package/dist/src/platforms/wechatbot/commands/user.js +53 -0
  208. package/dist/src/platforms/wechatbot/commands/user.js.map +1 -0
  209. package/dist/src/platforms/wechatbot/commands/whoami.d.ts +12 -0
  210. package/dist/src/platforms/wechatbot/commands/whoami.d.ts.map +1 -0
  211. package/dist/src/platforms/wechatbot/commands/whoami.js +33 -0
  212. package/dist/src/platforms/wechatbot/commands/whoami.js.map +1 -0
  213. package/dist/src/platforms/wechatbot/credential-manager.d.ts +17 -0
  214. package/dist/src/platforms/wechatbot/credential-manager.d.ts.map +1 -0
  215. package/dist/src/platforms/wechatbot/credential-manager.js +121 -0
  216. package/dist/src/platforms/wechatbot/credential-manager.js.map +1 -0
  217. package/dist/src/platforms/wechatbot/index.d.ts +5 -0
  218. package/dist/src/platforms/wechatbot/index.d.ts.map +1 -0
  219. package/dist/src/platforms/wechatbot/index.js +4 -0
  220. package/dist/src/platforms/wechatbot/index.js.map +1 -0
  221. package/dist/src/platforms/wechatbot/types.d.ts +94 -0
  222. package/dist/src/platforms/wechatbot/types.d.ts.map +1 -0
  223. package/dist/src/platforms/wechatbot/types.js +54 -0
  224. package/dist/src/platforms/wechatbot/types.js.map +1 -0
  225. package/dist/src/platforms/whatsapp/cli.d.ts.map +1 -1
  226. package/dist/src/platforms/whatsapp/cli.js +2 -1
  227. package/dist/src/platforms/whatsapp/cli.js.map +1 -1
  228. package/dist/src/platforms/whatsapp/client.d.ts +9 -0
  229. package/dist/src/platforms/whatsapp/client.d.ts.map +1 -1
  230. package/dist/src/platforms/whatsapp/client.js +143 -21
  231. package/dist/src/platforms/whatsapp/client.js.map +1 -1
  232. package/dist/src/platforms/whatsapp/commands/auth.d.ts.map +1 -1
  233. package/dist/src/platforms/whatsapp/commands/auth.js +133 -60
  234. package/dist/src/platforms/whatsapp/commands/auth.js.map +1 -1
  235. package/dist/src/platforms/whatsapp/commands/index.d.ts +1 -0
  236. package/dist/src/platforms/whatsapp/commands/index.d.ts.map +1 -1
  237. package/dist/src/platforms/whatsapp/commands/index.js +1 -0
  238. package/dist/src/platforms/whatsapp/commands/index.js.map +1 -1
  239. package/dist/src/platforms/whatsapp/commands/shared.js +2 -2
  240. package/dist/src/platforms/whatsapp/commands/shared.js.map +1 -1
  241. package/dist/src/platforms/whatsapp/commands/whoami.d.ts +7 -0
  242. package/dist/src/platforms/whatsapp/commands/whoami.d.ts.map +1 -0
  243. package/dist/src/platforms/whatsapp/commands/whoami.js +19 -0
  244. package/dist/src/platforms/whatsapp/commands/whoami.js.map +1 -0
  245. package/dist/src/platforms/whatsapp/credential-manager.d.ts.map +1 -1
  246. package/dist/src/platforms/whatsapp/credential-manager.js +14 -8
  247. package/dist/src/platforms/whatsapp/credential-manager.js.map +1 -1
  248. package/dist/src/platforms/whatsapp/ensure-auth.js +2 -2
  249. package/dist/src/platforms/whatsapp/ensure-auth.js.map +1 -1
  250. package/dist/src/platforms/whatsappbot/cli.d.ts.map +1 -1
  251. package/dist/src/platforms/whatsappbot/cli.js +2 -1
  252. package/dist/src/platforms/whatsappbot/cli.js.map +1 -1
  253. package/dist/src/platforms/whatsappbot/commands/index.d.ts +1 -0
  254. package/dist/src/platforms/whatsappbot/commands/index.d.ts.map +1 -1
  255. package/dist/src/platforms/whatsappbot/commands/index.js +1 -0
  256. package/dist/src/platforms/whatsappbot/commands/index.js.map +1 -1
  257. package/dist/src/platforms/whatsappbot/commands/whoami.d.ts +17 -0
  258. package/dist/src/platforms/whatsappbot/commands/whoami.d.ts.map +1 -0
  259. package/dist/src/platforms/whatsappbot/commands/whoami.js +39 -0
  260. package/dist/src/platforms/whatsappbot/commands/whoami.js.map +1 -0
  261. package/dist/src/shared/utils/qr.d.ts +15 -0
  262. package/dist/src/shared/utils/qr.d.ts.map +1 -0
  263. package/dist/src/shared/utils/qr.js +74 -0
  264. package/dist/src/shared/utils/qr.js.map +1 -0
  265. package/dist/src/tui/adapters/whatsapp-adapter.d.ts.map +1 -1
  266. package/dist/src/tui/adapters/whatsapp-adapter.js +20 -15
  267. package/dist/src/tui/adapters/whatsapp-adapter.js.map +1 -1
  268. package/docs/content/docs/agent-skills.mdx +4 -4
  269. package/docs/content/docs/cli/channeltalk.mdx +12 -1
  270. package/docs/content/docs/cli/channeltalkbot.mdx +10 -1
  271. package/docs/content/docs/cli/discord.mdx +11 -1
  272. package/docs/content/docs/cli/discordbot.mdx +10 -1
  273. package/docs/content/docs/cli/instagram.mdx +12 -1
  274. package/docs/content/docs/cli/kakaotalk.mdx +25 -1
  275. package/docs/content/docs/cli/line.mdx +5 -5
  276. package/docs/content/docs/cli/meta.json +1 -0
  277. package/docs/content/docs/cli/slack.mdx +11 -1
  278. package/docs/content/docs/cli/slackbot.mdx +10 -1
  279. package/docs/content/docs/cli/teams.mdx +11 -1
  280. package/docs/content/docs/cli/telegram.mdx +11 -0
  281. package/docs/content/docs/cli/webex.mdx +11 -1
  282. package/docs/content/docs/cli/wechatbot.mdx +188 -0
  283. package/docs/content/docs/cli/whatsapp.mdx +37 -8
  284. package/docs/content/docs/cli/whatsappbot.mdx +10 -1
  285. package/docs/content/docs/sdk/meta.json +1 -1
  286. package/docs/content/docs/sdk/wechatbot.mdx +282 -0
  287. package/docs/content/docs/tui.mdx +1 -1
  288. package/docs/src/app/page.tsx +5 -5
  289. package/package.json +11 -3
  290. package/skills/agent-channeltalk/SKILL.md +12 -1
  291. package/skills/agent-channeltalkbot/SKILL.md +10 -1
  292. package/skills/agent-discord/SKILL.md +11 -1
  293. package/skills/agent-discordbot/SKILL.md +10 -1
  294. package/skills/agent-instagram/SKILL.md +12 -1
  295. package/skills/agent-kakaotalk/SKILL.md +30 -1
  296. package/skills/agent-kakaotalk/references/common-patterns.md +1 -1
  297. package/skills/agent-line/SKILL.md +11 -15
  298. package/skills/agent-line/references/authentication.md +13 -4
  299. package/skills/agent-slack/SKILL.md +11 -1
  300. package/skills/agent-slackbot/SKILL.md +10 -1
  301. package/skills/agent-teams/SKILL.md +11 -1
  302. package/skills/agent-telegram/SKILL.md +6 -1
  303. package/skills/agent-webex/SKILL.md +11 -1
  304. package/skills/agent-wechatbot/SKILL.md +394 -0
  305. package/skills/agent-whatsapp/SKILL.md +63 -15
  306. package/skills/agent-whatsapp/references/authentication.md +36 -6
  307. package/skills/agent-whatsappbot/SKILL.md +10 -1
  308. package/src/platforms/channeltalk/cli.ts +2 -0
  309. package/src/platforms/channeltalk/commands/index.ts +1 -0
  310. package/src/platforms/channeltalk/commands/whoami.test.ts +64 -0
  311. package/src/platforms/channeltalk/commands/whoami.ts +62 -0
  312. package/src/platforms/channeltalkbot/cli.ts +2 -0
  313. package/src/platforms/channeltalkbot/commands/index.ts +1 -0
  314. package/src/platforms/channeltalkbot/commands/whoami.test.ts +104 -0
  315. package/src/platforms/channeltalkbot/commands/whoami.ts +42 -0
  316. package/src/platforms/discord/cli.ts +2 -0
  317. package/src/platforms/discord/commands/index.ts +1 -0
  318. package/src/platforms/discord/commands/whoami.test.ts +91 -0
  319. package/src/platforms/discord/commands/whoami.ts +36 -0
  320. package/src/platforms/discord/credential-manager.test.ts +18 -1
  321. package/src/platforms/discordbot/cli.ts +2 -0
  322. package/src/platforms/discordbot/client.ts +2 -2
  323. package/src/platforms/discordbot/commands/index.ts +1 -0
  324. package/src/platforms/discordbot/commands/whoami.test.ts +96 -0
  325. package/src/platforms/discordbot/commands/whoami.ts +44 -0
  326. package/src/platforms/instagram/cli.ts +2 -1
  327. package/src/platforms/instagram/client.ts +13 -0
  328. package/src/platforms/instagram/commands/auth.test.ts +216 -0
  329. package/src/platforms/instagram/commands/chat.test.ts +123 -0
  330. package/src/platforms/instagram/commands/index.ts +1 -0
  331. package/src/platforms/instagram/commands/message.test.ts +174 -0
  332. package/src/platforms/instagram/commands/whoami.test.ts +60 -0
  333. package/src/platforms/instagram/commands/whoami.ts +21 -0
  334. package/src/platforms/kakaotalk/cli.ts +2 -1
  335. package/src/platforms/kakaotalk/client.test.ts +182 -14
  336. package/src/platforms/kakaotalk/client.ts +261 -27
  337. package/src/platforms/kakaotalk/commands/auth.test.ts +299 -0
  338. package/src/platforms/kakaotalk/commands/chat.test.ts +97 -0
  339. package/src/platforms/kakaotalk/commands/index.ts +1 -0
  340. package/src/platforms/kakaotalk/commands/message.test.ts +113 -0
  341. package/src/platforms/kakaotalk/commands/whoami.test.ts +116 -0
  342. package/src/platforms/kakaotalk/commands/whoami.ts +21 -0
  343. package/src/platforms/kakaotalk/index.test.ts +5 -0
  344. package/src/platforms/kakaotalk/index.ts +2 -0
  345. package/src/platforms/kakaotalk/protocol/session.ts +29 -7
  346. package/src/platforms/kakaotalk/protocol/types.ts +9 -0
  347. package/src/platforms/kakaotalk/types.ts +30 -0
  348. package/src/platforms/line/cli.ts +2 -2
  349. package/src/platforms/line/client.ts +14 -39
  350. package/src/platforms/line/commands/auth.test.ts +141 -0
  351. package/src/platforms/line/commands/auth.ts +37 -61
  352. package/src/platforms/line/commands/chat.test.ts +110 -0
  353. package/src/platforms/line/commands/friend.test.ts +98 -0
  354. package/src/platforms/line/commands/index.ts +1 -1
  355. package/src/platforms/line/commands/message.test.ts +119 -0
  356. package/src/platforms/line/commands/whoami.test.ts +85 -0
  357. package/src/platforms/line/commands/{profile.ts → whoami.ts} +4 -4
  358. package/src/platforms/slack/cli.ts +2 -0
  359. package/src/platforms/slack/commands/index.ts +1 -0
  360. package/src/platforms/slack/commands/whoami.test.ts +126 -0
  361. package/src/platforms/slack/commands/whoami.ts +40 -0
  362. package/src/platforms/slackbot/cli.ts +2 -1
  363. package/src/platforms/slackbot/commands/channel.test.ts +139 -0
  364. package/src/platforms/slackbot/commands/index.ts +1 -0
  365. package/src/platforms/slackbot/commands/message.test.ts +226 -0
  366. package/src/platforms/slackbot/commands/reaction.test.ts +90 -0
  367. package/src/platforms/slackbot/commands/user.test.ts +143 -0
  368. package/src/platforms/slackbot/commands/whoami.test.ts +102 -0
  369. package/src/platforms/slackbot/commands/whoami.ts +44 -0
  370. package/src/platforms/teams/cli.ts +2 -0
  371. package/src/platforms/teams/commands/index.ts +1 -0
  372. package/src/platforms/teams/commands/reaction.test.ts +45 -61
  373. package/src/platforms/teams/commands/reaction.ts +2 -0
  374. package/src/platforms/teams/commands/whoami.test.ts +83 -0
  375. package/src/platforms/teams/commands/whoami.ts +33 -0
  376. package/src/platforms/telegram/cli.ts +2 -1
  377. package/src/platforms/telegram/commands/chat.test.ts +125 -0
  378. package/src/platforms/telegram/commands/index.ts +1 -0
  379. package/src/platforms/telegram/commands/message.test.ts +92 -0
  380. package/src/platforms/telegram/commands/whoami.test.ts +75 -0
  381. package/src/platforms/telegram/commands/whoami.ts +29 -0
  382. package/src/platforms/webex/cli.ts +2 -1
  383. package/src/platforms/webex/commands/auth.test.ts +58 -46
  384. package/src/platforms/webex/commands/index.ts +1 -0
  385. package/src/platforms/webex/commands/member.test.ts +60 -57
  386. package/src/platforms/webex/commands/message.test.ts +74 -121
  387. package/src/platforms/webex/commands/message.ts +1 -1
  388. package/src/platforms/webex/commands/snapshot.test.ts +54 -45
  389. package/src/platforms/webex/commands/space.test.ts +46 -49
  390. package/src/platforms/webex/commands/whoami.test.ts +113 -0
  391. package/src/platforms/webex/commands/whoami.ts +31 -0
  392. package/src/platforms/webex/credential-manager.test.ts +0 -1
  393. package/src/platforms/wechatbot/cli.ts +25 -0
  394. package/src/platforms/wechatbot/client.test.ts +497 -0
  395. package/src/platforms/wechatbot/client.ts +268 -0
  396. package/src/platforms/wechatbot/commands/auth.test.ts +211 -0
  397. package/src/platforms/wechatbot/commands/auth.ts +203 -0
  398. package/src/platforms/wechatbot/commands/index.ts +5 -0
  399. package/src/platforms/wechatbot/commands/message.test.ts +155 -0
  400. package/src/platforms/wechatbot/commands/message.ts +104 -0
  401. package/src/platforms/wechatbot/commands/shared.ts +22 -0
  402. package/src/platforms/wechatbot/commands/template.test.ts +199 -0
  403. package/src/platforms/wechatbot/commands/template.ts +102 -0
  404. package/src/platforms/wechatbot/commands/user.test.ts +165 -0
  405. package/src/platforms/wechatbot/commands/user.ts +75 -0
  406. package/src/platforms/wechatbot/commands/whoami.test.ts +109 -0
  407. package/src/platforms/wechatbot/commands/whoami.ts +43 -0
  408. package/src/platforms/wechatbot/credential-manager.test.ts +255 -0
  409. package/src/platforms/wechatbot/credential-manager.ts +148 -0
  410. package/src/platforms/wechatbot/index.test.ts +49 -0
  411. package/src/platforms/wechatbot/index.ts +19 -0
  412. package/src/platforms/wechatbot/types.test.ts +223 -0
  413. package/src/platforms/wechatbot/types.ts +107 -0
  414. package/src/platforms/whatsapp/cli.ts +2 -1
  415. package/src/platforms/whatsapp/client.ts +180 -37
  416. package/src/platforms/whatsapp/commands/auth.test.ts +311 -0
  417. package/src/platforms/whatsapp/commands/auth.ts +194 -84
  418. package/src/platforms/whatsapp/commands/chat.test.ts +198 -0
  419. package/src/platforms/whatsapp/commands/index.ts +1 -0
  420. package/src/platforms/whatsapp/commands/message.test.ts +231 -0
  421. package/src/platforms/whatsapp/commands/shared.ts +2 -2
  422. package/src/platforms/whatsapp/commands/whoami.test.ts +59 -0
  423. package/src/platforms/whatsapp/commands/whoami.ts +21 -0
  424. package/src/platforms/whatsapp/credential-manager.test.ts +20 -0
  425. package/src/platforms/whatsapp/credential-manager.ts +17 -8
  426. package/src/platforms/whatsapp/ensure-auth.ts +2 -2
  427. package/src/platforms/whatsappbot/cli.ts +2 -1
  428. package/src/platforms/whatsappbot/commands/auth.test.ts +217 -0
  429. package/src/platforms/whatsappbot/commands/index.ts +1 -0
  430. package/src/platforms/whatsappbot/commands/message.test.ts +198 -0
  431. package/src/platforms/whatsappbot/commands/template.test.ts +112 -0
  432. package/src/platforms/whatsappbot/commands/whoami.test.ts +100 -0
  433. package/src/platforms/whatsappbot/commands/whoami.ts +57 -0
  434. package/src/shared/utils/qr.ts +92 -0
  435. package/src/tui/adapters/whatsapp-adapter.ts +19 -16
  436. package/dist/src/platforms/line/commands/profile.d.ts +0 -3
  437. package/dist/src/platforms/line/commands/profile.d.ts.map +0 -1
  438. package/dist/src/platforms/line/commands/profile.js.map +0 -1
@@ -7,13 +7,14 @@ import {
7
7
  CHECKIN_HOST,
8
8
  CHECKIN_PORT,
9
9
  COUNTRY_ISO,
10
+ DTYPE,
10
11
  LANG,
11
12
  MCCMNC,
12
13
  PING_INTERVAL_MS,
13
14
  PROTOCOL_VERSION,
14
15
  } from './config'
15
16
  import { LocoConnection } from './connection'
16
- import type { BookingResponse, CheckinResponse, LoginListResponse, LocoPacket } from './types'
17
+ import type { BookingResponse, CheckinResponse, LoginListResponse, LocoPacket, SyncState } from './types'
17
18
 
18
19
  export class LocoSession {
19
20
  private connection: LocoConnection | null = null
@@ -21,7 +22,7 @@ export class LocoSession {
21
22
  private pushHandler: ((packet: LocoPacket) => void) | null = null
22
23
  private closeHandler: (() => void) | null = null
23
24
 
24
- async login(oauthToken: string, userId: string, deviceUuid: string): Promise<LoginListResponse> {
25
+ async login(oauthToken: string, userId: string, deviceUuid: string, syncState?: SyncState): Promise<LoginListResponse> {
25
26
  const { host, port } = await this.bookAndCheckin(userId)
26
27
 
27
28
  this.connection = new LocoConnection()
@@ -34,20 +35,28 @@ export class LocoSession {
34
35
  this.connection.onClose(this.closeHandler)
35
36
  }
36
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
+
37
45
  const response = await this.connection.sendPacket('LOGINLIST', {
38
46
  appVer: APP_VERSION,
39
47
  prtVer: PROTOCOL_VERSION,
40
48
  os: 'mac',
41
49
  lang: LANG,
50
+ dtype: DTYPE,
42
51
  duuid: deviceUuid,
43
52
  oauthToken,
44
53
  ntype: 0,
45
54
  MCCMNC: MCCMNC,
46
- revision: 0,
47
- chatIds: [],
48
- maxIds: [],
49
- lastTokenId: Long.fromNumber(0),
50
- lbk: Long.fromNumber(0),
55
+ revision: syncState?.revision ?? 0,
56
+ chatIds,
57
+ maxIds,
58
+ lastTokenId,
59
+ lbk,
51
60
  rp: new Binary(Buffer.from([0x00, 0x00, 0xff, 0xff, 0x00, 0x00])),
52
61
  bg: false,
53
62
  })
@@ -115,6 +124,19 @@ export class LocoSession {
115
124
  })
116
125
  }
117
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
+
118
140
  async getChatList(lastTokenId?: Long, lastChatId?: Long): Promise<LocoPacket> {
119
141
  if (!this.connection) throw new Error('Not connected')
120
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
@@ -125,6 +125,36 @@ export const KakaoSendResultSchema = z.object({
125
125
  sent_at: z.number(),
126
126
  })
127
127
 
128
+ export interface KakaoProfile {
129
+ user_id: string
130
+ nickname: string
131
+ profile_image_url: string | null
132
+ original_profile_image_url: string | null
133
+ background_image_url?: string | null
134
+ original_background_image_url?: string | null
135
+ fullname?: string | null
136
+ status_message: string | null
137
+ account_display_id: string | null
138
+ account_email?: string | null
139
+ pstn_number?: string | null
140
+ email_verified?: boolean | null
141
+ }
142
+
143
+ export const KakaoProfileSchema = z.object({
144
+ user_id: z.string(),
145
+ nickname: z.string(),
146
+ profile_image_url: z.string().nullable(),
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(),
151
+ status_message: z.string().nullable(),
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(),
156
+ })
157
+
128
158
  export const KakaoAccountCredentialsSchema = z.object({
129
159
  account_id: z.string(),
130
160
  oauth_token: z.string(),
@@ -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
 
@@ -2,34 +2,13 @@ import { mkdirSync } from 'node:fs'
2
2
  import { homedir } from 'node:os'
3
3
  import { join } from 'node:path'
4
4
 
5
- type LinejsModule = typeof import('@evex/linejs')
6
- type LinejsStorageModule = typeof import('@evex/linejs/storage')
7
- type Client = Awaited<ReturnType<LinejsModule['loginWithQR']>>
8
-
9
- let cachedLinejs: LinejsModule | undefined
10
- let cachedLinejsStorage: LinejsStorageModule | undefined
11
-
12
- const INSTALL_HINT = '@evex/linejs is required for LINE support. Install it with: bun add @evex/linejs@npm:@jsr/evex__linejs'
13
-
14
- async function getLinejs(): Promise<LinejsModule> {
15
- if (cachedLinejs) return cachedLinejs
16
- try {
17
- cachedLinejs = await import('@evex/linejs')
18
- return cachedLinejs
19
- } catch {
20
- throw new Error(INSTALL_HINT)
21
- }
22
- }
23
-
24
- async function getLinejsStorage(): Promise<LinejsStorageModule> {
25
- if (cachedLinejsStorage) return cachedLinejsStorage
26
- try {
27
- cachedLinejsStorage = await import('@evex/linejs/storage')
28
- return cachedLinejsStorage
29
- } catch {
30
- throw new Error(INSTALL_HINT)
31
- }
32
- }
5
+ import {
6
+ loginWithQR as linejsLoginWithQR,
7
+ loginWithPassword as linejsLoginWithPassword,
8
+ loginWithAuthToken as linejsLoginWithAuthToken,
9
+ type Client,
10
+ } from '@evex/linejs'
11
+ import { FileStorage } from '@evex/linejs/storage'
33
12
 
34
13
  import { LineCredentialManager } from './credential-manager'
35
14
  import type {
@@ -61,8 +40,7 @@ function getDefaultDevice(): LineDevice {
61
40
  return 'ANDROIDSECONDARY'
62
41
  }
63
42
 
64
- async function createStorage(accountId?: string) {
65
- const { FileStorage } = await getLinejsStorage()
43
+ function createStorage(accountId?: string): FileStorage {
66
44
  const dir = join(homedir(), '.config', 'agent-messenger', 'line-storage')
67
45
  mkdirSync(dir, { recursive: true })
68
46
  return new FileStorage(join(dir, `${accountId ?? 'default'}.json`))
@@ -82,11 +60,10 @@ export class LineClient {
82
60
  onPincode: (pin: string) => void
83
61
  }): Promise<LineLoginResult> {
84
62
  try {
85
- const linejs = await getLinejs()
86
63
  const device: LineDevice = options.device ?? getDefaultDevice()
87
- const storage = await createStorage()
64
+ const storage = createStorage()
88
65
 
89
- const client = await linejs.loginWithQR(
66
+ const client = await linejsLoginWithQR(
90
67
  {
91
68
  onReceiveQRUrl: (url) => options.onQRUrl(url),
92
69
  onPincodeRequest: (pin) => options.onPincode(pin),
@@ -126,11 +103,10 @@ export class LineClient {
126
103
  onPincode: (pin: string) => void
127
104
  }): Promise<LineLoginResult> {
128
105
  try {
129
- const linejs = await getLinejs()
130
106
  const device: LineDevice = options.device ?? getDefaultDevice()
131
- const storage = await createStorage()
107
+ const storage = createStorage()
132
108
 
133
- const client = await linejs.loginWithPassword(
109
+ const client = await linejsLoginWithPassword(
134
110
  {
135
111
  email: options.email,
136
112
  password: options.password,
@@ -178,11 +154,10 @@ export class LineClient {
178
154
  creds = account
179
155
  }
180
156
 
181
- const linejs = await getLinejs()
182
157
  const device: LineDevice = creds.device ?? getDefaultDevice()
183
- const storage = await createStorage()
158
+ const storage = createStorage()
184
159
 
185
- this.client = await linejs.loginWithAuthToken(creds.auth_token, { device, storage })
160
+ this.client = await linejsLoginWithAuthToken(creds.auth_token, { device, storage })
186
161
  return this
187
162
  } catch (error) {
188
163
  throw wrapError(error, 'login_failed')
@@ -0,0 +1,141 @@
1
+ import { afterEach, beforeEach, expect, mock, spyOn, test } from 'bun:test'
2
+
3
+ const originalConsoleLog = console.log
4
+
5
+ import { LineCredentialManager } from '../credential-manager'
6
+ import { authCommand } from './auth'
7
+
8
+ let getAccountSpy: ReturnType<typeof spyOn>
9
+ let listAccountsSpy: ReturnType<typeof spyOn>
10
+ let setCurrentAccountSpy: ReturnType<typeof spyOn>
11
+ let removeAccountSpy: ReturnType<typeof spyOn>
12
+ let clearAllSpy: ReturnType<typeof spyOn>
13
+ let consoleLogSpy: ReturnType<typeof mock>
14
+
15
+ beforeEach(() => {
16
+ getAccountSpy = spyOn(LineCredentialManager.prototype, 'getAccount').mockResolvedValue({
17
+ account_id: 'u123',
18
+ auth_token: 'token-abc',
19
+ device: 'ANDROIDSECONDARY',
20
+ display_name: 'Test User',
21
+ created_at: '2024-01-01T00:00:00Z',
22
+ updated_at: '2024-01-01T00:00:00Z',
23
+ })
24
+
25
+ listAccountsSpy = spyOn(LineCredentialManager.prototype, 'listAccounts').mockResolvedValue([
26
+ {
27
+ account_id: 'u123',
28
+ display_name: 'Test User',
29
+ device: 'ANDROIDSECONDARY',
30
+ is_current: true,
31
+ created_at: '2024-01-01T00:00:00Z',
32
+ },
33
+ {
34
+ account_id: 'u456',
35
+ display_name: 'Other User',
36
+ device: 'DESKTOPMAC',
37
+ is_current: false,
38
+ created_at: '2024-01-02T00:00:00Z',
39
+ },
40
+ ])
41
+
42
+ setCurrentAccountSpy = spyOn(LineCredentialManager.prototype, 'setCurrentAccount').mockResolvedValue(undefined)
43
+ removeAccountSpy = spyOn(LineCredentialManager.prototype, 'removeAccount').mockResolvedValue(undefined)
44
+ clearAllSpy = spyOn(LineCredentialManager.prototype, 'clearAll').mockResolvedValue(undefined)
45
+ consoleLogSpy = mock((..._args: unknown[]) => {}); console.log = consoleLogSpy
46
+ })
47
+
48
+ afterEach(() => {
49
+ getAccountSpy?.mockRestore()
50
+ listAccountsSpy?.mockRestore()
51
+ setCurrentAccountSpy?.mockRestore()
52
+ removeAccountSpy?.mockRestore()
53
+ clearAllSpy?.mockRestore()
54
+ console.log = originalConsoleLog
55
+ })
56
+
57
+ test('status: outputs account info when account exists', async () => {
58
+ // when
59
+ await authCommand.parseAsync(['node', 'auth', 'status'])
60
+
61
+ // then
62
+ expect(getAccountSpy).toHaveBeenCalledTimes(1)
63
+ expect(consoleLogSpy).toHaveBeenCalledTimes(1)
64
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
65
+ expect(output.account_id).toBe('u123')
66
+ expect(output.display_name).toBe('Test User')
67
+ expect(output.device).toBe('ANDROIDSECONDARY')
68
+ })
69
+
70
+ test('status: outputs error when no account configured', async () => {
71
+ // given
72
+ getAccountSpy.mockResolvedValue(null)
73
+
74
+ // when
75
+ await authCommand.parseAsync(['node', 'auth', 'status'])
76
+
77
+ // then
78
+ expect(consoleLogSpy).toHaveBeenCalledTimes(1)
79
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
80
+ expect(output.error).toBe('No LINE account configured')
81
+ })
82
+
83
+ test('list: outputs all accounts', async () => {
84
+ // when
85
+ await authCommand.parseAsync(['node', 'auth', 'list'])
86
+
87
+ // then
88
+ expect(listAccountsSpy).toHaveBeenCalledTimes(1)
89
+ expect(consoleLogSpy).toHaveBeenCalledTimes(1)
90
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
91
+ expect(output).toHaveLength(2)
92
+ expect(output[0].account_id).toBe('u123')
93
+ expect(output[1].account_id).toBe('u456')
94
+ })
95
+
96
+ test('list: outputs empty array when no accounts', async () => {
97
+ // given
98
+ listAccountsSpy.mockResolvedValue([])
99
+
100
+ // when
101
+ await authCommand.parseAsync(['node', 'auth', 'list'])
102
+
103
+ // then
104
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
105
+ expect(output).toHaveLength(0)
106
+ })
107
+
108
+ test('use: sets current account and outputs result', async () => {
109
+ // when
110
+ await authCommand.parseAsync(['node', 'auth', 'use', 'u456'])
111
+
112
+ // then
113
+ expect(setCurrentAccountSpy).toHaveBeenCalledWith('u456')
114
+ expect(consoleLogSpy).toHaveBeenCalledTimes(1)
115
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
116
+ expect(output.current_account).toBe('u456')
117
+ })
118
+
119
+ test('logout: removes specific account when account-id provided', async () => {
120
+ // when
121
+ await authCommand.parseAsync(['node', 'auth', 'logout', 'u123'])
122
+
123
+ // then
124
+ expect(removeAccountSpy).toHaveBeenCalledWith('u123')
125
+ expect(consoleLogSpy).toHaveBeenCalledTimes(1)
126
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
127
+ expect(output.success).toBe(true)
128
+ expect(output.message).toContain('u123')
129
+ })
130
+
131
+ test('logout: clears all accounts when no account-id provided', async () => {
132
+ // when
133
+ await authCommand.parseAsync(['node', 'auth', 'logout'])
134
+
135
+ // then
136
+ expect(clearAllSpy).toHaveBeenCalledTimes(1)
137
+ expect(consoleLogSpy).toHaveBeenCalledTimes(1)
138
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
139
+ expect(output.success).toBe(true)
140
+ expect(output.message).toBe('Logged out')
141
+ })
@@ -1,19 +1,13 @@
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'
14
9
  import { LineCredentialManager } from '../credential-manager'
15
10
  import type { LineDevice } from '../types'
16
- import { LINE_NEXT_ACTIONS } from '../types'
17
11
 
18
12
  function isInteractiveSession(): boolean {
19
13
  return Boolean(process.stdin.isTTY && process.stdout.isTTY)
@@ -23,32 +17,6 @@ function getDefaultDevice(): LineDevice {
23
17
  return 'ANDROIDSECONDARY'
24
18
  }
25
19
 
26
- async function openQRInBrowser(url: string): Promise<void> {
27
- const svgString = await QRCode.toString(url, { type: 'svg', margin: 2 })
28
- const html = `<!DOCTYPE html>
29
- <html><head><meta charset="utf-8"><title>LINE QR Login</title>
30
- <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}
31
- .card{background:#fff;border-radius:16px;padding:40px;text-align:center;box-shadow:0 4px 24px rgba(0,0,0,.15)}
32
- h1{margin:0 0 8px;font-size:22px;color:#111}p{margin:0 0 24px;color:#666;font-size:14px}
33
- svg{width:280px;height:280px}</style></head>
34
- <body><div class="card"><h1>LINE Login</h1><p>Scan with the LINE mobile app</p>${svgString}</div></body></html>`
35
-
36
- const htmlPath = join(tmpdir(), `line-qr-${Date.now()}.html`)
37
- writeFileSync(htmlPath, html)
38
-
39
- try {
40
- if (process.platform === 'darwin') {
41
- execSync(`open "${htmlPath}"`, { stdio: 'ignore' })
42
- } else if (process.platform === 'win32') {
43
- execSync(`start "" "${htmlPath}"`, { stdio: 'ignore' })
44
- } else {
45
- execSync(`xdg-open "${htmlPath}"`, { stdio: 'ignore' })
46
- }
47
- } catch {}
48
-
49
- setTimeout(() => { try { unlinkSync(htmlPath) } catch {} }, 30_000)
50
- }
51
-
52
20
  async function loginAction(options: {
53
21
  email?: string
54
22
  password?: string
@@ -75,18 +43,23 @@ async function loginAction(options: {
75
43
  const profile = await client.getProfile()
76
44
  const credentials = { ...tempCredentials, account_id: profile.mid, display_name: profile.display_name }
77
45
  await credManager.setAccount(credentials)
78
- console.log(formatOutput({
79
- authenticated: true,
80
- account_id: profile.mid,
81
- display_name: profile.display_name,
82
- device,
83
- }, 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
+ )
84
57
  } else if (options.email && options.password) {
85
58
  const result = await client.loginWithEmail({
86
59
  email: options.email,
87
60
  password: options.password,
88
61
  device,
89
- onPincode: (pin) => {
62
+ onPincode: (pin) => {
90
63
  if (interactive) {
91
64
  info(`\nEnter this PIN in the LINE mobile app: ${pin}\n`)
92
65
  }
@@ -94,22 +67,17 @@ async function loginAction(options: {
94
67
  })
95
68
  console.log(formatOutput(result, options.pretty))
96
69
  } else {
97
- if (!interactive) {
98
- console.log(formatOutput(LINE_NEXT_ACTIONS.run_interactive, options.pretty))
99
- return
100
- }
101
-
102
70
  const result = await client.loginWithQR({
103
71
  device,
104
72
  onQRUrl: async (url) => {
105
- await openQRInBrowser(url).catch(() => {})
106
- try {
107
- const qrAscii = await QRCode.toString(url, { type: 'terminal', small: true })
108
- info('\nScan this QR code with the LINE mobile app:\n')
109
- info(qrAscii)
110
- } catch {
111
- info(`\nOpen the QR code in the browser window, or scan this URL:\n${url}\n`)
112
- }
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
+ })
113
81
  },
114
82
  onPincode: (pin) => {
115
83
  info(`\nEnter this PIN in the LINE mobile app: ${pin}\n`)
@@ -132,13 +100,18 @@ async function statusAction(options: { pretty?: boolean; account?: string }): Pr
132
100
  return
133
101
  }
134
102
 
135
- console.log(formatOutput({
136
- account_id: account.account_id,
137
- device: account.device,
138
- display_name: account.display_name,
139
- created_at: account.created_at,
140
- updated_at: account.updated_at,
141
- }, 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
+ )
142
115
  } catch (error) {
143
116
  handleError(error as Error)
144
117
  }
@@ -187,7 +160,10 @@ export const authCommand = new Command('auth')
187
160
  .option('--email <email>', 'Email address for email/password login')
188
161
  .option('--password <password>', 'Password for email login')
189
162
  .option('--token <token>', 'Login with existing auth token directly')
190
- .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
+ )
191
167
  .option('--pretty', 'Pretty print JSON output')
192
168
  .action(loginAction),
193
169
  )
@@ -0,0 +1,110 @@
1
+ import { afterEach, beforeEach, expect, mock, spyOn, test } from 'bun:test'
2
+
3
+ const originalConsoleLog = console.log
4
+
5
+ import { LineClient } from '../client'
6
+ import { chatCommand } from './chat'
7
+
8
+ let loginSpy: ReturnType<typeof spyOn>
9
+ let getChatsSpy: ReturnType<typeof spyOn>
10
+ let closeSpy: ReturnType<typeof spyOn>
11
+ let consoleLogSpy: ReturnType<typeof mock>
12
+
13
+ beforeEach(() => {
14
+ loginSpy = spyOn(LineClient.prototype, 'login').mockImplementation(async function (this: LineClient) {
15
+ return this
16
+ })
17
+
18
+ getChatsSpy = spyOn(LineClient.prototype, 'getChats').mockResolvedValue([
19
+ {
20
+ chat_id: 'c111',
21
+ type: 'user',
22
+ display_name: 'Alice',
23
+ member_count: 2,
24
+ },
25
+ {
26
+ chat_id: 'c222',
27
+ type: 'group',
28
+ display_name: 'Team Chat',
29
+ member_count: 10,
30
+ },
31
+ {
32
+ chat_id: 'c333',
33
+ type: 'room',
34
+ display_name: 'Project Room',
35
+ member_count: 5,
36
+ },
37
+ ])
38
+
39
+ closeSpy = spyOn(LineClient.prototype, 'close').mockImplementation(() => {})
40
+ consoleLogSpy = mock((..._args: unknown[]) => {}); console.log = consoleLogSpy
41
+ })
42
+
43
+ afterEach(() => {
44
+ loginSpy?.mockRestore()
45
+ getChatsSpy?.mockRestore()
46
+ closeSpy?.mockRestore()
47
+ console.log = originalConsoleLog
48
+ })
49
+
50
+ test('list: fetches and outputs chats', async () => {
51
+ // when
52
+ await chatCommand.parseAsync(['node', 'chat', 'list'])
53
+
54
+ // then
55
+ expect(loginSpy).toHaveBeenCalledTimes(1)
56
+ expect(getChatsSpy).toHaveBeenCalledWith({ limit: 50 })
57
+ expect(consoleLogSpy).toHaveBeenCalledTimes(1)
58
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
59
+ expect(output).toHaveLength(3)
60
+ expect(output[0].chat_id).toBe('c111')
61
+ expect(output[1].chat_id).toBe('c222')
62
+ })
63
+
64
+ test('list: uses default limit of 50 when no limit provided', async () => {
65
+ // when
66
+ await chatCommand.parseAsync(['node', 'chat', 'list'])
67
+
68
+ // then
69
+ expect(getChatsSpy).toHaveBeenCalledWith({ limit: 50 })
70
+ })
71
+
72
+ test('list: uses custom limit when --limit option provided', async () => {
73
+ // when
74
+ await chatCommand.parseAsync(['node', 'chat', 'list', '--limit', '10'])
75
+
76
+ // then
77
+ expect(getChatsSpy).toHaveBeenCalledWith({ limit: 10 })
78
+ })
79
+
80
+ test('list: closes client after fetching chats', async () => {
81
+ // when
82
+ await chatCommand.parseAsync(['node', 'chat', 'list'])
83
+
84
+ // then
85
+ expect(closeSpy).toHaveBeenCalledTimes(1)
86
+ })
87
+
88
+ test('list: outputs chat metadata', async () => {
89
+ // when
90
+ await chatCommand.parseAsync(['node', 'chat', 'list'])
91
+
92
+ // then
93
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
94
+ const chat = output[0]
95
+ expect(chat.chat_id).toBeDefined()
96
+ expect(chat.type).toBeDefined()
97
+ expect(chat.display_name).toBeDefined()
98
+ })
99
+
100
+ test('list: includes different chat types', async () => {
101
+ // when
102
+ await chatCommand.parseAsync(['node', 'chat', 'list'])
103
+
104
+ // then
105
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
106
+ const types = output.map((c: { type: string }) => c.type)
107
+ expect(types).toContain('user')
108
+ expect(types).toContain('group')
109
+ expect(types).toContain('room')
110
+ })