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
@@ -5,6 +5,8 @@ import { KakaoTalkClient, KakaoTalkError } from './client'
5
5
  // Mock LocoSession at module level
6
6
  const mockLogin = mock(() => Promise.resolve({}))
7
7
  const mockGetChatList = mock(() => Promise.resolve({}))
8
+ const mockGetChatLogs = mock(() => Promise.resolve({}))
9
+ const mockGetChatInfo = mock(() => Promise.resolve({}))
8
10
  const mockSyncMessages = mock(() => Promise.resolve({}))
9
11
  const mockSendMessage = mock(() => Promise.resolve({}))
10
12
  const mockClose = mock(() => {})
@@ -14,6 +16,8 @@ mock.module('./protocol/session', () => ({
14
16
  LocoSession: class MockLocoSession {
15
17
  login = mockLogin
16
18
  getChatList = mockGetChatList
19
+ getChatLogs = mockGetChatLogs
20
+ getChatInfo = mockGetChatInfo
17
21
  syncMessages = mockSyncMessages
18
22
  sendMessage = mockSendMessage
19
23
  close = mockClose
@@ -28,6 +32,8 @@ function makeLong(n: number): { low: number; high: number } {
28
32
  function resetAllMocks() {
29
33
  mockLogin.mockReset()
30
34
  mockGetChatList.mockReset()
35
+ mockGetChatLogs.mockReset()
36
+ mockGetChatInfo.mockReset()
31
37
  mockSyncMessages.mockReset()
32
38
  mockSendMessage.mockReset()
33
39
  mockClose.mockReset()
@@ -67,6 +73,8 @@ describe('KakaoTalkClient', () => {
67
73
  beforeEach(() => {
68
74
  resetAllMocks()
69
75
  mockLogin.mockResolvedValue(DEFAULT_LOGIN_RESULT)
76
+ mockGetChatLogs.mockResolvedValue({ body: { status: 0, chatLogs: [], eof: true } })
77
+ mockGetChatInfo.mockResolvedValue({ body: { l: makeLong(99999) } })
70
78
  })
71
79
 
72
80
  afterEach(() => {
@@ -146,6 +154,68 @@ describe('KakaoTalkClient', () => {
146
154
  client.close()
147
155
  })
148
156
 
157
+ test('falls back to LCHATLIST when login snapshot is empty (new device)', async () => {
158
+ // given — LOGINLIST returns empty chatDatas with eof:true (new device scenario)
159
+ const emptyLoginResult = {
160
+ chatDatas: [],
161
+ lastTokenId: makeLong(0),
162
+ lastChatId: makeLong(0),
163
+ eof: true,
164
+ }
165
+ mockLogin.mockResolvedValue(emptyLoginResult)
166
+
167
+ mockGetChatList.mockResolvedValueOnce({
168
+ body: {
169
+ chatDatas: [
170
+ {
171
+ c: 100,
172
+ t: 1,
173
+ k: ['Alice', 'Bob'],
174
+ a: 2,
175
+ n: 3,
176
+ o: 1700000000,
177
+ l: { authorId: 1, message: 'hi', sendAt: 1700000000 },
178
+ ll: makeLong(999),
179
+ },
180
+ {
181
+ c: 200,
182
+ t: 2,
183
+ k: ['Charlie'],
184
+ a: 1,
185
+ n: 0,
186
+ o: 1699999000,
187
+ l: null,
188
+ ll: makeLong(500),
189
+ },
190
+ ],
191
+ lastTokenId: makeLong(1),
192
+ lastChatId: makeLong(200),
193
+ eof: true,
194
+ },
195
+ })
196
+
197
+ // when — default chat list (no --all flag)
198
+ const client = await new KakaoTalkClient().login({ oauthToken: 'token', userId: 'user1', deviceUuid: 'device1' })
199
+ const chats = await client.getChats()
200
+
201
+ // then — fetched via LCHATLIST despite eof:true and no --all
202
+ expect(chats).toHaveLength(2)
203
+ expect(mockGetChatList).toHaveBeenCalledTimes(1)
204
+ expect(chats[0].display_name).toBe('Alice, Bob')
205
+ expect(chats[1].display_name).toBe('Charlie')
206
+
207
+ client.close()
208
+ })
209
+
210
+ test('does not call LCHATLIST when login snapshot has chats', async () => {
211
+ const client = await new KakaoTalkClient().login({ oauthToken: 'token', userId: 'user1', deviceUuid: 'device1' })
212
+ await client.getChats()
213
+
214
+ expect(mockGetChatList).not.toHaveBeenCalled()
215
+
216
+ client.close()
217
+ })
218
+
149
219
  test('paginates when all=true and not eof', async () => {
150
220
  const loginResult = {
151
221
  ...DEFAULT_LOGIN_RESULT,
@@ -254,13 +324,14 @@ describe('KakaoTalkClient', () => {
254
324
 
255
325
  describe('getMessages', () => {
256
326
  test('returns formatted messages', async () => {
257
- mockSyncMessages.mockResolvedValueOnce({
327
+ mockGetChatLogs.mockResolvedValueOnce({
258
328
  body: {
329
+ status: 0,
259
330
  chatLogs: [
260
- { logId: makeLong(10), type: 1, authorId: 42, message: 'hello', sendAt: 1700000001 },
261
- { logId: makeLong(11), type: 1, authorId: 43, message: 'world', sendAt: 1700000002 },
331
+ { logId: makeLong(10), chatId: 100, type: 1, authorId: 42, message: 'hello', sendAt: 1700000001 },
332
+ { logId: makeLong(11), chatId: 100, type: 1, authorId: 43, message: 'world', sendAt: 1700000002 },
262
333
  ],
263
- isOK: true,
334
+ eof: true,
264
335
  },
265
336
  })
266
337
 
@@ -289,14 +360,15 @@ describe('KakaoTalkClient', () => {
289
360
  test('respects count option', async () => {
290
361
  const logs = Array.from({ length: 50 }, (_, i) => ({
291
362
  logId: makeLong(i + 1),
363
+ chatId: 100,
292
364
  type: 1,
293
365
  authorId: 1,
294
366
  message: `msg-${i}`,
295
367
  sendAt: 1700000000 + i,
296
368
  }))
297
369
 
298
- mockSyncMessages.mockResolvedValueOnce({
299
- body: { chatLogs: logs, isOK: true },
370
+ mockGetChatLogs.mockResolvedValueOnce({
371
+ body: { status: 0, chatLogs: logs, eof: true },
300
372
  })
301
373
 
302
374
  const client = await new KakaoTalkClient().login({ oauthToken: 'token', userId: 'user1', deviceUuid: 'device1' })
@@ -311,13 +383,14 @@ describe('KakaoTalkClient', () => {
311
383
  })
312
384
 
313
385
  test('sorts messages by sent_at ascending', async () => {
314
- mockSyncMessages.mockResolvedValueOnce({
386
+ mockGetChatLogs.mockResolvedValueOnce({
315
387
  body: {
388
+ status: 0,
316
389
  chatLogs: [
317
- { logId: makeLong(2), type: 1, authorId: 1, message: 'second', sendAt: 200 },
318
- { logId: makeLong(1), type: 1, authorId: 1, message: 'first', sendAt: 100 },
390
+ { logId: makeLong(2), chatId: 100, type: 1, authorId: 1, message: 'second', sendAt: 200 },
391
+ { logId: makeLong(1), chatId: 100, type: 1, authorId: 1, message: 'first', sendAt: 100 },
319
392
  ],
320
- isOK: true,
393
+ eof: true,
321
394
  },
322
395
  })
323
396
 
@@ -384,6 +457,101 @@ describe('KakaoTalkClient', () => {
384
457
  })
385
458
  })
386
459
 
460
+ describe('getProfile', () => {
461
+ const mockFetch = mock(() => Promise.resolve(new Response()))
462
+
463
+ beforeEach(() => {
464
+ mockFetch.mockReset()
465
+ globalThis.fetch = mockFetch as unknown as typeof fetch
466
+ })
467
+
468
+ afterEach(() => {
469
+ mockFetch.mockReset()
470
+ })
471
+
472
+ function makeJsonResponse(data: unknown, status = 200): Response {
473
+ return new Response(JSON.stringify(data), {
474
+ status,
475
+ headers: { 'Content-Type': 'application/json' },
476
+ })
477
+ }
478
+
479
+ test('returns profile data on success', async () => {
480
+ mockFetch
481
+ .mockResolvedValueOnce(makeJsonResponse({
482
+ profile: {
483
+ nickName: 'Test User',
484
+ profileImageUrl: 'https://example.com/profile.jpg',
485
+ originalProfileImageUrl: 'https://example.com/original.jpg',
486
+ statusMessage: 'Hello world',
487
+ },
488
+ }))
489
+ .mockResolvedValueOnce(makeJsonResponse({ accountDisplayId: 'testuser123' }))
490
+
491
+ const client = await new KakaoTalkClient().login({ oauthToken: 'mytoken', userId: 'user42', deviceUuid: 'device1' })
492
+ const profile = await client.getProfile()
493
+
494
+ expect(profile.user_id).toBe('user42')
495
+ expect(profile.nickname).toBe('Test User')
496
+ expect(profile.profile_image_url).toBe('https://example.com/profile.jpg')
497
+ expect(profile.original_profile_image_url).toBe('https://example.com/original.jpg')
498
+ expect(profile.status_message).toBe('Hello world')
499
+ expect(profile.account_display_id).toBe('testuser123')
500
+
501
+ client.close()
502
+ })
503
+
504
+ test('throws not_authenticated when not logged in', async () => {
505
+ const client = new KakaoTalkClient()
506
+ try {
507
+ await client.getProfile()
508
+ expect.unreachable('should have thrown')
509
+ } catch (e) {
510
+ expect(e).toBeInstanceOf(KakaoTalkError)
511
+ expect((e as KakaoTalkError).code).toBe('not_authenticated')
512
+ }
513
+ })
514
+
515
+ test('throws profile_request_failed when profile HTTP request fails', async () => {
516
+ mockFetch
517
+ .mockResolvedValueOnce(makeJsonResponse({}, 401))
518
+ .mockResolvedValueOnce(makeJsonResponse({ accountDisplayId: null }))
519
+
520
+ const client = await new KakaoTalkClient().login({ oauthToken: 'mytoken', userId: 'user42', deviceUuid: 'device1' })
521
+ try {
522
+ await client.getProfile()
523
+ expect.unreachable('should have thrown')
524
+ } catch (e) {
525
+ expect(e).toBeInstanceOf(KakaoTalkError)
526
+ expect((e as KakaoTalkError).code).toBe('profile_request_failed')
527
+ }
528
+
529
+ client.close()
530
+ })
531
+
532
+ test('returns null account_display_id when more_settings request fails', async () => {
533
+ mockFetch
534
+ .mockResolvedValueOnce(makeJsonResponse({
535
+ profile: {
536
+ nickName: 'Test User',
537
+ profileImageUrl: null,
538
+ originalProfileImageUrl: null,
539
+ statusMessage: null,
540
+ },
541
+ }))
542
+ .mockResolvedValueOnce(makeJsonResponse({}, 500))
543
+
544
+ const client = await new KakaoTalkClient().login({ oauthToken: 'mytoken', userId: 'user42', deviceUuid: 'device1' })
545
+ const profile = await client.getProfile()
546
+
547
+ expect(profile.user_id).toBe('user42')
548
+ expect(profile.nickname).toBe('Test User')
549
+ expect(profile.account_display_id).toBeNull()
550
+
551
+ client.close()
552
+ })
553
+ })
554
+
387
555
  describe('session lifecycle', () => {
388
556
  test('lazy init: does not call login until first method call', async () => {
389
557
  const client = await new KakaoTalkClient().login({ oauthToken: 'token', userId: 'user1', deviceUuid: 'device1' })
@@ -399,8 +567,8 @@ describe('KakaoTalkClient', () => {
399
567
  })
400
568
 
401
569
  test('reuses session across multiple calls', async () => {
402
- mockSyncMessages.mockResolvedValue({
403
- body: { chatLogs: [], isOK: true },
570
+ mockGetChatLogs.mockResolvedValue({
571
+ body: { status: 0, chatLogs: [], eof: true },
404
572
  })
405
573
 
406
574
  const client = await new KakaoTalkClient().login({ oauthToken: 'token', userId: 'user1', deviceUuid: 'device1' })
@@ -417,8 +585,8 @@ describe('KakaoTalkClient', () => {
417
585
  mockLogin.mockImplementation(
418
586
  () => new Promise((resolve) => setTimeout(() => resolve(DEFAULT_LOGIN_RESULT), 50)),
419
587
  )
420
- mockSyncMessages.mockResolvedValue({
421
- body: { chatLogs: [], isOK: true },
588
+ mockGetChatLogs.mockResolvedValue({
589
+ body: { status: 0, chatLogs: [], eof: true },
422
590
  })
423
591
 
424
592
  const client = await new KakaoTalkClient().login({ oauthToken: 'token', userId: 'user1', deviceUuid: 'device1' })
@@ -1,10 +1,15 @@
1
1
  import { Long } from 'bson'
2
+ import { existsSync } from 'node:fs'
3
+ import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises'
4
+ import { homedir } from 'node:os'
5
+ import { join } from 'node:path'
2
6
 
3
7
  import { warn } from '@/shared/utils/stderr'
4
8
 
9
+ import { APP_VERSION, LANG, OS } from './protocol/config'
5
10
  import { LocoSession } from './protocol/session'
6
- import type { ChatListResponse, LoginListResponse } from './protocol/types'
7
- import type { KakaoChat, KakaoMessage, KakaoSendResult } from './types'
11
+ import type { ChatListResponse, LoginListResponse, SyncState } from './protocol/types'
12
+ import type { KakaoChat, KakaoMessage, KakaoProfile, KakaoSendResult } from './types'
8
13
 
9
14
  export class KakaoTalkError extends Error {
10
15
  code: string
@@ -73,6 +78,14 @@ function matchesSearch(chat: ChatData, term: string): boolean {
73
78
  return names.some((n) => n.toLowerCase().includes(lower))
74
79
  }
75
80
 
81
+ function findMaxLogId(logs: Array<Record<string, unknown>>, field: string): Long | null {
82
+ return logs.reduce<Long | null>((max, log) => {
83
+ const current = bsonToLong(log[field])
84
+ if (!current) return max
85
+ return !max || current.greaterThan(max) ? current : max
86
+ }, null)
87
+ }
88
+
76
89
  function collectChats(chatDatas: ChatData[], into: ChatData[], seen: Set<string>): void {
77
90
  for (const chat of chatDatas) {
78
91
  const id = String(chat.c)
@@ -91,6 +104,129 @@ function wrapError(error: unknown, code: string): KakaoTalkError {
91
104
 
92
105
  const MAX_PAGES = 50
93
106
 
107
+ const CONFIG_DIR = join(homedir(), '.config', 'agent-messenger')
108
+
109
+ function syncStatePath(deviceUuid: string): string {
110
+ return join(CONFIG_DIR, `kakaotalk-sync-state-${deviceUuid}.json`)
111
+ }
112
+
113
+ async function loadSyncState(deviceUuid: string): Promise<SyncState | undefined> {
114
+ const path = syncStatePath(deviceUuid)
115
+ if (!existsSync(path)) return undefined
116
+ const content = await readFile(path, 'utf-8')
117
+ const parsed = JSON.parse(content) as Partial<SyncState>
118
+
119
+ if (
120
+ parsed.version !== 2 ||
121
+ typeof parsed.revision !== 'number' ||
122
+ !Array.isArray(parsed.chatIds) ||
123
+ !Array.isArray(parsed.maxIds) ||
124
+ parsed.chatIds.length !== parsed.maxIds.length ||
125
+ !parsed.lastTokenId ||
126
+ typeof parsed.lbk !== 'number'
127
+ ) {
128
+ return undefined
129
+ }
130
+
131
+ return parsed as SyncState
132
+ }
133
+
134
+ async function saveSyncState(deviceUuid: string, state: SyncState): Promise<void> {
135
+ await mkdir(CONFIG_DIR, { recursive: true })
136
+ const path = syncStatePath(deviceUuid)
137
+ await writeFile(path, JSON.stringify(state, null, 2))
138
+ await chmod(path, 0o600)
139
+ }
140
+
141
+ function toLongLike(v: unknown): { low: number; high: number } {
142
+ if (v && typeof v === 'object' && 'low' in v && 'high' in v) {
143
+ const { low, high } = v as { low: number; high: number }
144
+ return { low, high }
145
+ }
146
+ if (typeof v === 'number') {
147
+ const big = BigInt(v)
148
+ return { low: Number(big & 0xffffffffn), high: Number((big >> 32n) & 0xffffffffn) }
149
+ }
150
+ return { low: 0, high: 0 }
151
+ }
152
+
153
+ function buildSyncState(loginResult: LoginListResponse, previousRevision: number): SyncState {
154
+ const chatDatas = (loginResult.chatDatas ?? []) as Array<Record<string, unknown>>
155
+ return {
156
+ version: 2,
157
+ revision: typeof loginResult.revision === 'number' ? loginResult.revision : previousRevision,
158
+ chatIds: chatDatas.map((chat) => toLongLike(chat.c)),
159
+ maxIds: chatDatas.map((chat) => toLongLike(chat.ll)),
160
+ lastTokenId: toLongLike(loginResult.lastTokenId),
161
+ lbk: typeof loginResult.lbk === 'number' ? loginResult.lbk : 0,
162
+ }
163
+ }
164
+
165
+ function deleteFromSyncState(state: SyncState, chatId: string): void {
166
+ const index = state.chatIds.findIndex((entry) => longToString(entry) === chatId)
167
+ if (index === -1) return
168
+
169
+ state.chatIds.splice(index, 1)
170
+ state.maxIds.splice(index, 1)
171
+ }
172
+
173
+ function upsertSyncState(state: SyncState, chatId: unknown, maxId: unknown): void {
174
+ const chatIdString = longToString(chatId)
175
+ const nextChatId = toLongLike(chatId)
176
+ const nextMaxId = toLongLike(maxId)
177
+ const index = state.chatIds.findIndex((entry) => longToString(entry) === chatIdString)
178
+
179
+ if (index === -1) {
180
+ state.chatIds.push(nextChatId)
181
+ state.maxIds.push(nextMaxId)
182
+ return
183
+ }
184
+
185
+ state.chatIds[index] = nextChatId
186
+ state.maxIds[index] = nextMaxId
187
+ }
188
+
189
+ function mergeSyncState(previous: SyncState | undefined, loginResult: LoginListResponse): SyncState {
190
+ const next = previous
191
+ ? {
192
+ version: 2 as const,
193
+ revision: previous.revision,
194
+ chatIds: [...previous.chatIds],
195
+ maxIds: [...previous.maxIds],
196
+ lastTokenId: previous.lastTokenId,
197
+ lbk: previous.lbk,
198
+ }
199
+ : buildSyncState(loginResult, 0)
200
+
201
+ next.revision = typeof loginResult.revision === 'number' ? loginResult.revision : next.revision
202
+ next.lastTokenId = toLongLike(loginResult.lastTokenId)
203
+ next.lbk = typeof loginResult.lbk === 'number' ? loginResult.lbk : next.lbk
204
+
205
+ const delChatIds = Array.isArray(loginResult.delChatIds) ? loginResult.delChatIds : []
206
+ for (const chatId of delChatIds) {
207
+ deleteFromSyncState(next, longToString(chatId))
208
+ }
209
+
210
+ const chatDatas = Array.isArray(loginResult.chatDatas) ? loginResult.chatDatas : []
211
+ for (const chat of chatDatas) {
212
+ upsertSyncState(next, chat.c, chat.ll)
213
+ }
214
+
215
+ return next
216
+ }
217
+
218
+ function formatMessages(logs: Array<Record<string, unknown>>, count: number): KakaoMessage[] {
219
+ logs.sort((a, b) => (a.sendAt as number) - (b.sendAt as number))
220
+
221
+ return logs.slice(-count).map((log) => ({
222
+ log_id: longToString(log.logId),
223
+ type: log.type as number,
224
+ author_id: log.authorId as number,
225
+ message: log.message as string,
226
+ sent_at: log.sendAt as number,
227
+ }))
228
+ }
229
+
94
230
  export class KakaoTalkClient {
95
231
  private oauthToken: string | null = null
96
232
  private userId: string | null = null
@@ -178,7 +314,11 @@ export class KakaoTalkClient {
178
314
  private async connect(): Promise<SessionState> {
179
315
  const session = new LocoSession()
180
316
  try {
181
- const loginResult = await session.login(this.oauthToken!, this.userId!, this.deviceUuid!)
317
+ const syncState = await loadSyncState(this.deviceUuid!)
318
+ const loginResult = await session.login(this.oauthToken!, this.userId!, this.deviceUuid!, syncState)
319
+
320
+ const newSyncState = mergeSyncState(syncState, loginResult)
321
+ await saveSyncState(this.deviceUuid!, newSyncState)
182
322
 
183
323
  session.onClose(() => {
184
324
  if (this.state?.session === session) {
@@ -206,11 +346,22 @@ export class KakaoTalkClient {
206
346
 
207
347
  collectChats((loginResult.chatDatas ?? []) as ChatData[], allChats, seenChatIds)
208
348
 
209
- if (options?.all || options?.search) {
349
+ // Paginate via LCHATLIST when explicitly requested (--all / --search) OR when
350
+ // the login snapshot is empty. New device registrations often return an empty
351
+ // chatDatas with eof=true because the server has no prior sync state for the
352
+ // device — LCHATLIST fetches the canonical chat list regardless of device history.
353
+ const snapshotEmpty = allChats.length === 0
354
+ if (options?.all || options?.search || snapshotEmpty) {
210
355
  let cursor: ChatListResponse = loginResult
211
356
  let pages = 0
212
357
 
213
- while (!cursor.eof && pages < MAX_PAGES) {
358
+ while (pages < MAX_PAGES) {
359
+ // Trust eof only when the snapshot had data. When the snapshot was empty
360
+ // (new device), ignore eof for the first iteration so we always attempt
361
+ // at least one LCHATLIST call.
362
+ if (cursor.eof && !snapshotEmpty) break
363
+ if (cursor.eof && snapshotEmpty && pages > 0) break
364
+
214
365
  const lastTokenId = bsonToLong(cursor.lastTokenId)
215
366
  const lastChatId = bsonToLong(cursor.lastChatId)
216
367
 
@@ -241,21 +392,62 @@ export class KakaoTalkClient {
241
392
  }
242
393
 
243
394
  async getMessages(chatId: string, options?: { count?: number; from?: string }): Promise<KakaoMessage[]> {
244
- return this.executeWithReconnect(async ({ session, loginResult }) => {
395
+ return this.executeWithReconnect(async ({ session }) => {
245
396
  try {
246
- const rawChats = (loginResult.chatDatas ?? []) as ChatData[]
247
- const chat = rawChats.find((c) => String(c.c) === chatId)
248
- const lastLogId = chat?.ll as { high: number; low: number } | undefined
249
- const maxLogId = lastLogId ? new Long(lastLogId.low, lastLogId.high) : undefined
250
-
251
397
  const count = options?.count ?? 20
252
398
  const cursor = options?.from ? parseLong(options.from) : undefined
253
399
 
254
400
  const cid = parseLong(chatId)
255
- const startCursor = cursor ?? Long.fromNumber(0)
401
+
256
402
  const allMessages: Array<Record<string, unknown>> = []
257
403
  const seenLogIds = new Set<string>()
258
- let cur = startCursor
404
+ let cur = cursor ?? Long.fromNumber(0)
405
+
406
+ try {
407
+ for (let page = 0; page < MAX_PAGES; page++) {
408
+ const response = await session.getChatLogs([cid], [cur])
409
+ const responseStatus = response.body.status
410
+ if (typeof responseStatus === 'number' && responseStatus !== 0) {
411
+ throw new Error(`MCHATLOGS failed: ${responseStatus}`)
412
+ }
413
+
414
+ const batch = ((response.body.chatLogs ?? []) as Array<Record<string, unknown>>).filter(
415
+ (log) => longToString(log.chatId) === chatId,
416
+ )
417
+ if (batch.length === 0) {
418
+ return formatMessages(allMessages, count)
419
+ }
420
+
421
+ for (const log of batch) {
422
+ const lid = longToString(log.logId)
423
+ if (!seenLogIds.has(lid)) {
424
+ seenLogIds.add(lid)
425
+ allMessages.push(log)
426
+ }
427
+ }
428
+
429
+ const maxLog = findMaxLogId(batch, 'logId')
430
+ if (!maxLog || maxLog.equals(cur) || response.body.eof) {
431
+ return formatMessages(allMessages, count)
432
+ }
433
+
434
+ cur = maxLog
435
+ }
436
+ } catch {
437
+ allMessages.length = 0
438
+ seenLogIds.clear()
439
+ cur = cursor ?? Long.fromNumber(0)
440
+ }
441
+
442
+ if (allMessages.length > 0) {
443
+ warn(`[agent-kakaotalk] Warning: message fetch capped at ${MAX_PAGES} pages. Results may be incomplete.`)
444
+ return formatMessages(allMessages, count)
445
+ }
446
+
447
+ // Fetch fresh lastLogId via CHATONROOM (not the stale login-time snapshot)
448
+ const chatInfo = await session.getChatInfo(cid)
449
+ const chatBody = chatInfo.body as Record<string, unknown>
450
+ const maxLogId = bsonToLong(chatBody.l)
259
451
 
260
452
  let reachedEnd = false
261
453
  for (let page = 0; page < MAX_PAGES; page++) {
@@ -271,11 +463,7 @@ export class KakaoTalkClient {
271
463
  }
272
464
  }
273
465
 
274
- const maxLog = batch.reduce<Long | null>((max, l) => {
275
- const lid = l.logId as { high: number; low: number }
276
- const long = new Long(lid.low, lid.high)
277
- return !max || long.greaterThan(max) ? long : max
278
- }, null)
466
+ const maxLog = findMaxLogId(batch, 'logId')
279
467
 
280
468
  if (!maxLog || maxLog.equals(cur) || response.body.isOK) { reachedEnd = true; break }
281
469
  cur = maxLog
@@ -284,15 +472,7 @@ export class KakaoTalkClient {
284
472
  warn(`[agent-kakaotalk] Warning: message fetch capped at ${MAX_PAGES} pages. Results may be incomplete.`)
285
473
  }
286
474
 
287
- allMessages.sort((a, b) => (a.sendAt as number) - (b.sendAt as number))
288
-
289
- return allMessages.slice(-count).map((log) => ({
290
- log_id: longToString(log.logId),
291
- type: log.type as number,
292
- author_id: log.authorId as number,
293
- message: log.message as string,
294
- sent_at: log.sendAt as number,
295
- }))
475
+ return formatMessages(allMessages, count)
296
476
  } catch (error) {
297
477
  throw wrapError(error, 'get_messages_failed')
298
478
  }
@@ -317,6 +497,60 @@ export class KakaoTalkClient {
317
497
  })
318
498
  }
319
499
 
500
+ async getProfile(): Promise<KakaoProfile> {
501
+ this.ensureAuth()
502
+ try {
503
+ const headers = {
504
+ Authorization: `${this.oauthToken}-${this.deviceUuid}`,
505
+ A: `${OS}/${APP_VERSION}/${LANG}`,
506
+ 'User-Agent': `KT/${APP_VERSION} Md/macOS ${LANG}`,
507
+ Accept: '*/*',
508
+ 'Accept-Language': LANG,
509
+ }
510
+
511
+ const [profileRes, settingsRes] = await Promise.all([
512
+ fetch('https://katalk.kakao.com/mac/profile3/me.json', { headers }),
513
+ fetch('https://katalk.kakao.com/mac/account/more_settings.json?since=0&lang=ko', { headers }),
514
+ ])
515
+
516
+ if (!profileRes.ok) {
517
+ throw new KakaoTalkError(`Profile request failed: ${profileRes.status}`, 'profile_request_failed')
518
+ }
519
+
520
+ const profileData = await profileRes.json() as Record<string, unknown>
521
+ const profile = profileData.profile as Record<string, unknown> | undefined
522
+
523
+ let accountDisplayId: string | null = null
524
+ let accountEmail: string | null = null
525
+ let pstnNumber: string | null = null
526
+ let emailVerified: boolean | null = null
527
+ if (settingsRes.ok) {
528
+ const settingsData = await settingsRes.json() as Record<string, unknown>
529
+ accountDisplayId = (settingsData.accountDisplayId as string) || null
530
+ accountEmail = (settingsData.accountEmail as string) || null
531
+ pstnNumber = (settingsData.pstnNumber as string) || null
532
+ emailVerified = typeof settingsData.emailVerified === 'boolean' ? settingsData.emailVerified : null
533
+ }
534
+
535
+ return {
536
+ user_id: this.userId!,
537
+ nickname: (profile?.nickName as string) || '',
538
+ profile_image_url: (profile?.profileImageUrl as string) || null,
539
+ original_profile_image_url: (profile?.originalProfileImageUrl as string) || null,
540
+ background_image_url: (profile?.backgroundImageUrl as string) || null,
541
+ original_background_image_url: (profile?.originalBackgroundImageUrl as string) || null,
542
+ fullname: (profile?.fullname as string) || null,
543
+ status_message: (profile?.statusMessage as string) || null,
544
+ account_display_id: accountDisplayId,
545
+ account_email: accountEmail,
546
+ pstn_number: pstnNumber,
547
+ email_verified: emailVerified,
548
+ }
549
+ } catch (error) {
550
+ throw wrapError(error, 'get_profile_failed')
551
+ }
552
+ }
553
+
320
554
  close(): void {
321
555
  this.closed = true
322
556
  if (this.state) {