agent-messenger 2.4.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (359) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.github/workflows/ci.yml +35 -0
  3. package/.github/workflows/release.yml +0 -12
  4. package/README.md +3 -3
  5. package/bun.lock +10 -2
  6. package/dist/package.json +3 -1
  7. package/dist/src/platforms/channeltalk/cli.d.ts.map +1 -1
  8. package/dist/src/platforms/channeltalk/cli.js +2 -1
  9. package/dist/src/platforms/channeltalk/cli.js.map +1 -1
  10. package/dist/src/platforms/channeltalk/commands/index.d.ts +1 -0
  11. package/dist/src/platforms/channeltalk/commands/index.d.ts.map +1 -1
  12. package/dist/src/platforms/channeltalk/commands/index.js +1 -0
  13. package/dist/src/platforms/channeltalk/commands/index.js.map +1 -1
  14. package/dist/src/platforms/channeltalk/commands/whoami.d.ts +22 -0
  15. package/dist/src/platforms/channeltalk/commands/whoami.d.ts.map +1 -0
  16. package/dist/src/platforms/channeltalk/commands/whoami.js +40 -0
  17. package/dist/src/platforms/channeltalk/commands/whoami.js.map +1 -0
  18. package/dist/src/platforms/channeltalkbot/cli.d.ts.map +1 -1
  19. package/dist/src/platforms/channeltalkbot/cli.js +2 -1
  20. package/dist/src/platforms/channeltalkbot/cli.js.map +1 -1
  21. package/dist/src/platforms/channeltalkbot/commands/index.d.ts +1 -0
  22. package/dist/src/platforms/channeltalkbot/commands/index.d.ts.map +1 -1
  23. package/dist/src/platforms/channeltalkbot/commands/index.js +1 -0
  24. package/dist/src/platforms/channeltalkbot/commands/index.js.map +1 -1
  25. package/dist/src/platforms/channeltalkbot/commands/whoami.d.ts +13 -0
  26. package/dist/src/platforms/channeltalkbot/commands/whoami.d.ts.map +1 -0
  27. package/dist/src/platforms/channeltalkbot/commands/whoami.js +31 -0
  28. package/dist/src/platforms/channeltalkbot/commands/whoami.js.map +1 -0
  29. package/dist/src/platforms/discord/cli.d.ts.map +1 -1
  30. package/dist/src/platforms/discord/cli.js +2 -1
  31. package/dist/src/platforms/discord/cli.js.map +1 -1
  32. package/dist/src/platforms/discord/commands/index.d.ts +1 -0
  33. package/dist/src/platforms/discord/commands/index.d.ts.map +1 -1
  34. package/dist/src/platforms/discord/commands/index.js +1 -0
  35. package/dist/src/platforms/discord/commands/index.js.map +1 -1
  36. package/dist/src/platforms/discord/commands/whoami.d.ts +6 -0
  37. package/dist/src/platforms/discord/commands/whoami.d.ts.map +1 -0
  38. package/dist/src/platforms/discord/commands/whoami.js +33 -0
  39. package/dist/src/platforms/discord/commands/whoami.js.map +1 -0
  40. package/dist/src/platforms/discordbot/cli.d.ts.map +1 -1
  41. package/dist/src/platforms/discordbot/cli.js +2 -1
  42. package/dist/src/platforms/discordbot/cli.js.map +1 -1
  43. package/dist/src/platforms/discordbot/commands/index.d.ts +1 -0
  44. package/dist/src/platforms/discordbot/commands/index.d.ts.map +1 -1
  45. package/dist/src/platforms/discordbot/commands/index.js +1 -0
  46. package/dist/src/platforms/discordbot/commands/index.js.map +1 -1
  47. package/dist/src/platforms/discordbot/commands/whoami.d.ts +14 -0
  48. package/dist/src/platforms/discordbot/commands/whoami.d.ts.map +1 -0
  49. package/dist/src/platforms/discordbot/commands/whoami.js +32 -0
  50. package/dist/src/platforms/discordbot/commands/whoami.js.map +1 -0
  51. package/dist/src/platforms/instagram/cli.d.ts.map +1 -1
  52. package/dist/src/platforms/instagram/cli.js +2 -1
  53. package/dist/src/platforms/instagram/cli.js.map +1 -1
  54. package/dist/src/platforms/instagram/client.d.ts +6 -0
  55. package/dist/src/platforms/instagram/client.d.ts.map +1 -1
  56. package/dist/src/platforms/instagram/client.js +12 -0
  57. package/dist/src/platforms/instagram/client.js.map +1 -1
  58. package/dist/src/platforms/instagram/commands/index.d.ts +1 -0
  59. package/dist/src/platforms/instagram/commands/index.d.ts.map +1 -1
  60. package/dist/src/platforms/instagram/commands/index.js +1 -0
  61. package/dist/src/platforms/instagram/commands/index.js.map +1 -1
  62. package/dist/src/platforms/instagram/commands/whoami.d.ts +7 -0
  63. package/dist/src/platforms/instagram/commands/whoami.d.ts.map +1 -0
  64. package/dist/src/platforms/instagram/commands/whoami.js +19 -0
  65. package/dist/src/platforms/instagram/commands/whoami.js.map +1 -0
  66. package/dist/src/platforms/kakaotalk/cli.js +2 -2
  67. package/dist/src/platforms/kakaotalk/cli.js.map +1 -1
  68. package/dist/src/platforms/kakaotalk/client.d.ts +4 -1
  69. package/dist/src/platforms/kakaotalk/client.d.ts.map +1 -1
  70. package/dist/src/platforms/kakaotalk/client.js +193 -27
  71. package/dist/src/platforms/kakaotalk/client.js.map +1 -1
  72. package/dist/src/platforms/kakaotalk/commands/auth.d.ts.map +1 -1
  73. package/dist/src/platforms/kakaotalk/commands/auth.js +24 -55
  74. package/dist/src/platforms/kakaotalk/commands/auth.js.map +1 -1
  75. package/dist/src/platforms/kakaotalk/commands/index.d.ts +1 -1
  76. package/dist/src/platforms/kakaotalk/commands/index.d.ts.map +1 -1
  77. package/dist/src/platforms/kakaotalk/commands/index.js +1 -1
  78. package/dist/src/platforms/kakaotalk/commands/index.js.map +1 -1
  79. package/dist/src/platforms/kakaotalk/commands/whoami.d.ts +3 -0
  80. package/dist/src/platforms/kakaotalk/commands/whoami.d.ts.map +1 -0
  81. package/dist/src/platforms/kakaotalk/commands/{profile.js → whoami.js} +5 -5
  82. package/dist/src/platforms/kakaotalk/commands/whoami.js.map +1 -0
  83. package/dist/src/platforms/kakaotalk/credential-manager.d.ts.map +1 -1
  84. package/dist/src/platforms/kakaotalk/credential-manager.js +1 -0
  85. package/dist/src/platforms/kakaotalk/credential-manager.js.map +1 -1
  86. package/dist/src/platforms/kakaotalk/index.d.ts +1 -1
  87. package/dist/src/platforms/kakaotalk/index.d.ts.map +1 -1
  88. package/dist/src/platforms/kakaotalk/index.js.map +1 -1
  89. package/dist/src/platforms/kakaotalk/listener.js +2 -2
  90. package/dist/src/platforms/kakaotalk/listener.js.map +1 -1
  91. package/dist/src/platforms/kakaotalk/protocol/config.d.ts +8 -2
  92. package/dist/src/platforms/kakaotalk/protocol/config.d.ts.map +1 -1
  93. package/dist/src/platforms/kakaotalk/protocol/config.js +15 -2
  94. package/dist/src/platforms/kakaotalk/protocol/config.js.map +1 -1
  95. package/dist/src/platforms/kakaotalk/protocol/session.d.ts +6 -2
  96. package/dist/src/platforms/kakaotalk/protocol/session.d.ts.map +1 -1
  97. package/dist/src/platforms/kakaotalk/protocol/session.js +37 -15
  98. package/dist/src/platforms/kakaotalk/protocol/session.js.map +1 -1
  99. package/dist/src/platforms/kakaotalk/protocol/types.d.ts +17 -0
  100. package/dist/src/platforms/kakaotalk/protocol/types.d.ts.map +1 -1
  101. package/dist/src/platforms/kakaotalk/protocol/types.js.map +1 -1
  102. package/dist/src/platforms/kakaotalk/types.d.ts +22 -0
  103. package/dist/src/platforms/kakaotalk/types.d.ts.map +1 -1
  104. package/dist/src/platforms/kakaotalk/types.js +7 -0
  105. package/dist/src/platforms/kakaotalk/types.js.map +1 -1
  106. package/dist/src/platforms/line/cli.js +2 -2
  107. package/dist/src/platforms/line/cli.js.map +1 -1
  108. package/dist/src/platforms/line/commands/auth.d.ts.map +1 -1
  109. package/dist/src/platforms/line/commands/auth.js +9 -59
  110. package/dist/src/platforms/line/commands/auth.js.map +1 -1
  111. package/dist/src/platforms/line/commands/index.d.ts +1 -1
  112. package/dist/src/platforms/line/commands/index.d.ts.map +1 -1
  113. package/dist/src/platforms/line/commands/index.js +1 -1
  114. package/dist/src/platforms/line/commands/index.js.map +1 -1
  115. package/dist/src/platforms/line/commands/whoami.d.ts +3 -0
  116. package/dist/src/platforms/line/commands/whoami.d.ts.map +1 -0
  117. package/dist/src/platforms/line/commands/{profile.js → whoami.js} +5 -5
  118. package/dist/src/platforms/line/commands/whoami.js.map +1 -0
  119. package/dist/src/platforms/slack/cli.d.ts.map +1 -1
  120. package/dist/src/platforms/slack/cli.js +2 -1
  121. package/dist/src/platforms/slack/cli.js.map +1 -1
  122. package/dist/src/platforms/slack/commands/index.d.ts +1 -0
  123. package/dist/src/platforms/slack/commands/index.d.ts.map +1 -1
  124. package/dist/src/platforms/slack/commands/index.js +1 -0
  125. package/dist/src/platforms/slack/commands/index.js.map +1 -1
  126. package/dist/src/platforms/slack/commands/whoami.d.ts +6 -0
  127. package/dist/src/platforms/slack/commands/whoami.d.ts.map +1 -0
  128. package/dist/src/platforms/slack/commands/whoami.js +39 -0
  129. package/dist/src/platforms/slack/commands/whoami.js.map +1 -0
  130. package/dist/src/platforms/slackbot/cli.d.ts.map +1 -1
  131. package/dist/src/platforms/slackbot/cli.js +2 -1
  132. package/dist/src/platforms/slackbot/cli.js.map +1 -1
  133. package/dist/src/platforms/slackbot/commands/index.d.ts +1 -0
  134. package/dist/src/platforms/slackbot/commands/index.d.ts.map +1 -1
  135. package/dist/src/platforms/slackbot/commands/index.js +1 -0
  136. package/dist/src/platforms/slackbot/commands/index.js.map +1 -1
  137. package/dist/src/platforms/slackbot/commands/whoami.d.ts +14 -0
  138. package/dist/src/platforms/slackbot/commands/whoami.d.ts.map +1 -0
  139. package/dist/src/platforms/slackbot/commands/whoami.js +32 -0
  140. package/dist/src/platforms/slackbot/commands/whoami.js.map +1 -0
  141. package/dist/src/platforms/teams/cli.d.ts.map +1 -1
  142. package/dist/src/platforms/teams/cli.js +2 -1
  143. package/dist/src/platforms/teams/cli.js.map +1 -1
  144. package/dist/src/platforms/teams/commands/index.d.ts +1 -0
  145. package/dist/src/platforms/teams/commands/index.d.ts.map +1 -1
  146. package/dist/src/platforms/teams/commands/index.js +1 -0
  147. package/dist/src/platforms/teams/commands/index.js.map +1 -1
  148. package/dist/src/platforms/teams/commands/whoami.d.ts +6 -0
  149. package/dist/src/platforms/teams/commands/whoami.d.ts.map +1 -0
  150. package/dist/src/platforms/teams/commands/whoami.js +30 -0
  151. package/dist/src/platforms/teams/commands/whoami.js.map +1 -0
  152. package/dist/src/platforms/telegram/cli.d.ts.map +1 -1
  153. package/dist/src/platforms/telegram/cli.js +2 -1
  154. package/dist/src/platforms/telegram/cli.js.map +1 -1
  155. package/dist/src/platforms/telegram/commands/index.d.ts +1 -0
  156. package/dist/src/platforms/telegram/commands/index.d.ts.map +1 -1
  157. package/dist/src/platforms/telegram/commands/index.js +1 -0
  158. package/dist/src/platforms/telegram/commands/index.js.map +1 -1
  159. package/dist/src/platforms/telegram/commands/whoami.d.ts +7 -0
  160. package/dist/src/platforms/telegram/commands/whoami.d.ts.map +1 -0
  161. package/dist/src/platforms/telegram/commands/whoami.js +27 -0
  162. package/dist/src/platforms/telegram/commands/whoami.js.map +1 -0
  163. package/dist/src/platforms/webex/cli.d.ts.map +1 -1
  164. package/dist/src/platforms/webex/cli.js +2 -1
  165. package/dist/src/platforms/webex/cli.js.map +1 -1
  166. package/dist/src/platforms/webex/commands/index.d.ts +1 -0
  167. package/dist/src/platforms/webex/commands/index.d.ts.map +1 -1
  168. package/dist/src/platforms/webex/commands/index.js +1 -0
  169. package/dist/src/platforms/webex/commands/index.js.map +1 -1
  170. package/dist/src/platforms/webex/commands/message.js +1 -1
  171. package/dist/src/platforms/webex/commands/message.js.map +1 -1
  172. package/dist/src/platforms/webex/commands/whoami.d.ts +6 -0
  173. package/dist/src/platforms/webex/commands/whoami.d.ts.map +1 -0
  174. package/dist/src/platforms/webex/commands/whoami.js +30 -0
  175. package/dist/src/platforms/webex/commands/whoami.js.map +1 -0
  176. package/dist/src/platforms/wechatbot/cli.d.ts.map +1 -1
  177. package/dist/src/platforms/wechatbot/cli.js +2 -1
  178. package/dist/src/platforms/wechatbot/cli.js.map +1 -1
  179. package/dist/src/platforms/wechatbot/commands/index.d.ts +1 -0
  180. package/dist/src/platforms/wechatbot/commands/index.d.ts.map +1 -1
  181. package/dist/src/platforms/wechatbot/commands/index.js +1 -0
  182. package/dist/src/platforms/wechatbot/commands/index.js.map +1 -1
  183. package/dist/src/platforms/wechatbot/commands/whoami.d.ts +12 -0
  184. package/dist/src/platforms/wechatbot/commands/whoami.d.ts.map +1 -0
  185. package/dist/src/platforms/wechatbot/commands/whoami.js +33 -0
  186. package/dist/src/platforms/wechatbot/commands/whoami.js.map +1 -0
  187. package/dist/src/platforms/whatsapp/cli.d.ts.map +1 -1
  188. package/dist/src/platforms/whatsapp/cli.js +2 -1
  189. package/dist/src/platforms/whatsapp/cli.js.map +1 -1
  190. package/dist/src/platforms/whatsapp/client.d.ts +8 -0
  191. package/dist/src/platforms/whatsapp/client.d.ts.map +1 -1
  192. package/dist/src/platforms/whatsapp/client.js +116 -8
  193. package/dist/src/platforms/whatsapp/client.js.map +1 -1
  194. package/dist/src/platforms/whatsapp/commands/auth.d.ts.map +1 -1
  195. package/dist/src/platforms/whatsapp/commands/auth.js +115 -45
  196. package/dist/src/platforms/whatsapp/commands/auth.js.map +1 -1
  197. package/dist/src/platforms/whatsapp/commands/index.d.ts +1 -0
  198. package/dist/src/platforms/whatsapp/commands/index.d.ts.map +1 -1
  199. package/dist/src/platforms/whatsapp/commands/index.js +1 -0
  200. package/dist/src/platforms/whatsapp/commands/index.js.map +1 -1
  201. package/dist/src/platforms/whatsapp/commands/shared.js +2 -2
  202. package/dist/src/platforms/whatsapp/commands/shared.js.map +1 -1
  203. package/dist/src/platforms/whatsapp/commands/whoami.d.ts +7 -0
  204. package/dist/src/platforms/whatsapp/commands/whoami.d.ts.map +1 -0
  205. package/dist/src/platforms/whatsapp/commands/whoami.js +19 -0
  206. package/dist/src/platforms/whatsapp/commands/whoami.js.map +1 -0
  207. package/dist/src/platforms/whatsapp/ensure-auth.js +2 -2
  208. package/dist/src/platforms/whatsapp/ensure-auth.js.map +1 -1
  209. package/dist/src/platforms/whatsappbot/cli.d.ts.map +1 -1
  210. package/dist/src/platforms/whatsappbot/cli.js +2 -1
  211. package/dist/src/platforms/whatsappbot/cli.js.map +1 -1
  212. package/dist/src/platforms/whatsappbot/commands/index.d.ts +1 -0
  213. package/dist/src/platforms/whatsappbot/commands/index.d.ts.map +1 -1
  214. package/dist/src/platforms/whatsappbot/commands/index.js +1 -0
  215. package/dist/src/platforms/whatsappbot/commands/index.js.map +1 -1
  216. package/dist/src/platforms/whatsappbot/commands/whoami.d.ts +17 -0
  217. package/dist/src/platforms/whatsappbot/commands/whoami.d.ts.map +1 -0
  218. package/dist/src/platforms/whatsappbot/commands/whoami.js +39 -0
  219. package/dist/src/platforms/whatsappbot/commands/whoami.js.map +1 -0
  220. package/dist/src/shared/utils/qr.d.ts +15 -0
  221. package/dist/src/shared/utils/qr.d.ts.map +1 -0
  222. package/dist/src/shared/utils/qr.js +74 -0
  223. package/dist/src/shared/utils/qr.js.map +1 -0
  224. package/dist/src/tui/adapters/kakaotalk-adapter.d.ts.map +1 -1
  225. package/dist/src/tui/adapters/kakaotalk-adapter.js +5 -2
  226. package/dist/src/tui/adapters/kakaotalk-adapter.js.map +1 -1
  227. package/dist/src/tui/adapters/whatsapp-adapter.d.ts.map +1 -1
  228. package/dist/src/tui/adapters/whatsapp-adapter.js +20 -15
  229. package/dist/src/tui/adapters/whatsapp-adapter.js.map +1 -1
  230. package/docs/content/docs/cli/channeltalk.mdx +11 -0
  231. package/docs/content/docs/cli/channeltalkbot.mdx +9 -0
  232. package/docs/content/docs/cli/discord.mdx +10 -0
  233. package/docs/content/docs/cli/discordbot.mdx +9 -0
  234. package/docs/content/docs/cli/instagram.mdx +11 -0
  235. package/docs/content/docs/cli/kakaotalk.mdx +27 -39
  236. package/docs/content/docs/cli/line.mdx +4 -4
  237. package/docs/content/docs/cli/slack.mdx +10 -0
  238. package/docs/content/docs/cli/slackbot.mdx +9 -0
  239. package/docs/content/docs/cli/teams.mdx +10 -0
  240. package/docs/content/docs/cli/telegram.mdx +11 -0
  241. package/docs/content/docs/cli/webex.mdx +10 -0
  242. package/docs/content/docs/cli/wechatbot.mdx +9 -0
  243. package/docs/content/docs/cli/whatsapp.mdx +36 -7
  244. package/docs/content/docs/cli/whatsappbot.mdx +9 -0
  245. package/e2e/config.ts +1 -1
  246. package/package.json +3 -1
  247. package/skills/agent-channeltalk/SKILL.md +12 -1
  248. package/skills/agent-channeltalkbot/SKILL.md +10 -1
  249. package/skills/agent-discord/SKILL.md +11 -1
  250. package/skills/agent-discordbot/SKILL.md +10 -1
  251. package/skills/agent-instagram/SKILL.md +12 -1
  252. package/skills/agent-kakaotalk/SKILL.md +24 -70
  253. package/skills/agent-kakaotalk/references/authentication.md +4 -69
  254. package/skills/agent-kakaotalk/references/common-patterns.md +14 -1
  255. package/skills/agent-line/SKILL.md +5 -5
  256. package/skills/agent-slack/SKILL.md +11 -1
  257. package/skills/agent-slackbot/SKILL.md +10 -1
  258. package/skills/agent-teams/SKILL.md +11 -1
  259. package/skills/agent-telegram/SKILL.md +6 -1
  260. package/skills/agent-webex/SKILL.md +11 -1
  261. package/skills/agent-wechatbot/SKILL.md +10 -1
  262. package/skills/agent-whatsapp/SKILL.md +52 -15
  263. package/skills/agent-whatsapp/references/authentication.md +36 -6
  264. package/skills/agent-whatsappbot/SKILL.md +10 -1
  265. package/src/platforms/channeltalk/cli.ts +2 -0
  266. package/src/platforms/channeltalk/commands/index.ts +1 -0
  267. package/src/platforms/channeltalk/commands/whoami.test.ts +64 -0
  268. package/src/platforms/channeltalk/commands/whoami.ts +62 -0
  269. package/src/platforms/channeltalkbot/cli.ts +2 -0
  270. package/src/platforms/channeltalkbot/commands/index.ts +1 -0
  271. package/src/platforms/channeltalkbot/commands/whoami.test.ts +104 -0
  272. package/src/platforms/channeltalkbot/commands/whoami.ts +42 -0
  273. package/src/platforms/discord/cli.ts +2 -0
  274. package/src/platforms/discord/commands/index.ts +1 -0
  275. package/src/platforms/discord/commands/whoami.test.ts +91 -0
  276. package/src/platforms/discord/commands/whoami.ts +36 -0
  277. package/src/platforms/discordbot/cli.ts +2 -0
  278. package/src/platforms/discordbot/commands/index.ts +1 -0
  279. package/src/platforms/discordbot/commands/whoami.test.ts +96 -0
  280. package/src/platforms/discordbot/commands/whoami.ts +44 -0
  281. package/src/platforms/instagram/cli.ts +2 -1
  282. package/src/platforms/instagram/client.ts +13 -0
  283. package/src/platforms/instagram/commands/chat.test.ts +1 -5
  284. package/src/platforms/instagram/commands/index.ts +1 -0
  285. package/src/platforms/instagram/commands/message.test.ts +1 -5
  286. package/src/platforms/instagram/commands/whoami.test.ts +60 -0
  287. package/src/platforms/instagram/commands/whoami.ts +21 -0
  288. package/src/platforms/kakaotalk/cli.ts +2 -2
  289. package/src/platforms/kakaotalk/client.test.ts +25 -14
  290. package/src/platforms/kakaotalk/client.ts +228 -33
  291. package/src/platforms/kakaotalk/commands/auth.ts +22 -73
  292. package/src/platforms/kakaotalk/commands/index.ts +1 -1
  293. package/src/platforms/kakaotalk/commands/{profile.test.ts → whoami.test.ts} +37 -5
  294. package/src/platforms/kakaotalk/commands/{profile.ts → whoami.ts} +4 -4
  295. package/src/platforms/kakaotalk/credential-manager.ts +1 -0
  296. package/src/platforms/kakaotalk/index.ts +1 -0
  297. package/src/platforms/kakaotalk/listener.test.ts +2 -2
  298. package/src/platforms/kakaotalk/listener.ts +2 -2
  299. package/src/platforms/kakaotalk/protocol/config.ts +26 -2
  300. package/src/platforms/kakaotalk/protocol/session.ts +42 -16
  301. package/src/platforms/kakaotalk/protocol/types.ts +9 -0
  302. package/src/platforms/kakaotalk/types.ts +16 -0
  303. package/src/platforms/line/cli.ts +2 -2
  304. package/src/platforms/line/commands/auth.ts +37 -70
  305. package/src/platforms/line/commands/index.ts +1 -1
  306. package/src/platforms/line/commands/{profile.test.ts → whoami.test.ts} +11 -11
  307. package/src/platforms/line/commands/{profile.ts → whoami.ts} +4 -4
  308. package/src/platforms/slack/cli.ts +2 -0
  309. package/src/platforms/slack/commands/index.ts +1 -0
  310. package/src/platforms/slack/commands/whoami.test.ts +126 -0
  311. package/src/platforms/slack/commands/whoami.ts +40 -0
  312. package/src/platforms/slackbot/cli.ts +2 -1
  313. package/src/platforms/slackbot/commands/index.ts +1 -0
  314. package/src/platforms/slackbot/commands/whoami.test.ts +102 -0
  315. package/src/platforms/slackbot/commands/whoami.ts +44 -0
  316. package/src/platforms/teams/cli.ts +2 -0
  317. package/src/platforms/teams/commands/index.ts +1 -0
  318. package/src/platforms/teams/commands/whoami.test.ts +83 -0
  319. package/src/platforms/teams/commands/whoami.ts +33 -0
  320. package/src/platforms/telegram/cli.ts +2 -1
  321. package/src/platforms/telegram/commands/index.ts +1 -0
  322. package/src/platforms/telegram/commands/whoami.test.ts +75 -0
  323. package/src/platforms/telegram/commands/whoami.ts +29 -0
  324. package/src/platforms/webex/cli.ts +2 -1
  325. package/src/platforms/webex/commands/auth.test.ts +58 -46
  326. package/src/platforms/webex/commands/index.ts +1 -0
  327. package/src/platforms/webex/commands/member.test.ts +1 -5
  328. package/src/platforms/webex/commands/message.test.ts +1 -5
  329. package/src/platforms/webex/commands/message.ts +1 -1
  330. package/src/platforms/webex/commands/snapshot.test.ts +1 -5
  331. package/src/platforms/webex/commands/space.test.ts +1 -5
  332. package/src/platforms/webex/commands/whoami.test.ts +113 -0
  333. package/src/platforms/webex/commands/whoami.ts +31 -0
  334. package/src/platforms/webex/credential-manager.test.ts +0 -1
  335. package/src/platforms/wechatbot/cli.ts +2 -1
  336. package/src/platforms/wechatbot/commands/index.ts +1 -0
  337. package/src/platforms/wechatbot/commands/whoami.test.ts +109 -0
  338. package/src/platforms/wechatbot/commands/whoami.ts +43 -0
  339. package/src/platforms/whatsapp/cli.ts +2 -1
  340. package/src/platforms/whatsapp/client.ts +156 -24
  341. package/src/platforms/whatsapp/commands/auth.ts +176 -70
  342. package/src/platforms/whatsapp/commands/index.ts +1 -0
  343. package/src/platforms/whatsapp/commands/shared.ts +2 -2
  344. package/src/platforms/whatsapp/commands/whoami.test.ts +59 -0
  345. package/src/platforms/whatsapp/commands/whoami.ts +21 -0
  346. package/src/platforms/whatsapp/ensure-auth.ts +2 -2
  347. package/src/platforms/whatsappbot/cli.ts +2 -1
  348. package/src/platforms/whatsappbot/commands/index.ts +1 -0
  349. package/src/platforms/whatsappbot/commands/whoami.test.ts +100 -0
  350. package/src/platforms/whatsappbot/commands/whoami.ts +57 -0
  351. package/src/shared/utils/qr.ts +92 -0
  352. package/src/tui/adapters/kakaotalk-adapter.ts +5 -2
  353. package/src/tui/adapters/whatsapp-adapter.ts +19 -16
  354. package/dist/src/platforms/kakaotalk/commands/profile.d.ts +0 -3
  355. package/dist/src/platforms/kakaotalk/commands/profile.d.ts.map +0 -1
  356. package/dist/src/platforms/kakaotalk/commands/profile.js.map +0 -1
  357. package/dist/src/platforms/line/commands/profile.d.ts +0 -3
  358. package/dist/src/platforms/line/commands/profile.d.ts.map +0 -1
  359. package/dist/src/platforms/line/commands/profile.js.map +0 -1
@@ -1,11 +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
 
5
- import { APP_VERSION, LANG, OS } from './protocol/config'
9
+ import { LANG, PC_OS_NAME, getLocoDeviceConfig } from './protocol/config'
6
10
  import { LocoSession } from './protocol/session'
7
- import type { ChatListResponse, LoginListResponse } from './protocol/types'
8
- import type { KakaoChat, KakaoMessage, KakaoProfile, KakaoSendResult } from './types'
11
+ import type { ChatListResponse, LoginListResponse, SyncState } from './protocol/types'
12
+ import type { KakaoChat, KakaoDeviceType, KakaoMessage, KakaoProfile, KakaoSendResult } from './types'
9
13
 
10
14
  export class KakaoTalkError extends Error {
11
15
  code: string
@@ -74,6 +78,14 @@ function matchesSearch(chat: ChatData, term: string): boolean {
74
78
  return names.some((n) => n.toLowerCase().includes(lower))
75
79
  }
76
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
+
77
89
  function collectChats(chatDatas: ChatData[], into: ChatData[], seen: Set<string>): void {
78
90
  for (const chat of chatDatas) {
79
91
  const id = String(chat.c)
@@ -92,16 +104,140 @@ function wrapError(error: unknown, code: string): KakaoTalkError {
92
104
 
93
105
  const MAX_PAGES = 50
94
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
+
95
230
  export class KakaoTalkClient {
96
231
  private oauthToken: string | null = null
97
232
  private userId: string | null = null
98
233
  private deviceUuid: string | null = null
234
+ private deviceType: KakaoDeviceType = 'tablet'
99
235
  private state: SessionState | null = null
100
236
  private initPromise: Promise<SessionState> | null = null
101
237
  private closed = false
102
238
 
103
239
  async login(
104
- credentials?: { oauthToken: string; userId: string; deviceUuid?: string },
240
+ credentials?: { oauthToken: string; userId: string; deviceUuid?: string; deviceType?: KakaoDeviceType },
105
241
  accountId?: string,
106
242
  ): Promise<this> {
107
243
  if (credentials) {
@@ -110,19 +246,26 @@ export class KakaoTalkClient {
110
246
  this.oauthToken = credentials.oauthToken
111
247
  this.userId = credentials.userId
112
248
  this.deviceUuid = credentials.deviceUuid ?? `agent-messenger-${credentials.userId}`
249
+ this.deviceType = credentials.deviceType ?? 'tablet'
113
250
  return this
114
251
  }
115
252
  const { ensureKakaoAuth } = await import('./ensure-auth')
116
253
  const account = await ensureKakaoAuth(accountId)
117
- return this.login({ oauthToken: account.oauth_token, userId: account.user_id, deviceUuid: account.device_uuid })
254
+ return this.login({
255
+ oauthToken: account.oauth_token,
256
+ userId: account.user_id,
257
+ deviceUuid: account.device_uuid,
258
+ deviceType: account.device_type,
259
+ })
118
260
  }
119
261
 
120
- getCredentials(): { oauthToken: string; userId: string; deviceUuid: string } {
262
+ getCredentials(): { oauthToken: string; userId: string; deviceUuid: string; deviceType: KakaoDeviceType } {
121
263
  this.ensureAuth()
122
264
  return {
123
265
  oauthToken: this.oauthToken!,
124
266
  userId: this.userId!,
125
267
  deviceUuid: this.deviceUuid!,
268
+ deviceType: this.deviceType,
126
269
  }
127
270
  }
128
271
 
@@ -179,7 +322,11 @@ export class KakaoTalkClient {
179
322
  private async connect(): Promise<SessionState> {
180
323
  const session = new LocoSession()
181
324
  try {
182
- const loginResult = await session.login(this.oauthToken!, this.userId!, this.deviceUuid!)
325
+ const syncState = await loadSyncState(this.deviceUuid!)
326
+ const loginResult = await session.login(this.oauthToken!, this.userId!, this.deviceUuid!, syncState, this.deviceType)
327
+
328
+ const newSyncState = mergeSyncState(syncState, loginResult)
329
+ await saveSyncState(this.deviceUuid!, newSyncState)
183
330
 
184
331
  session.onClose(() => {
185
332
  if (this.state?.session === session) {
@@ -253,21 +400,62 @@ export class KakaoTalkClient {
253
400
  }
254
401
 
255
402
  async getMessages(chatId: string, options?: { count?: number; from?: string }): Promise<KakaoMessage[]> {
256
- return this.executeWithReconnect(async ({ session, loginResult }) => {
403
+ return this.executeWithReconnect(async ({ session }) => {
257
404
  try {
258
- const rawChats = (loginResult.chatDatas ?? []) as ChatData[]
259
- const chat = rawChats.find((c) => String(c.c) === chatId)
260
- const lastLogId = chat?.ll as { high: number; low: number } | undefined
261
- const maxLogId = lastLogId ? new Long(lastLogId.low, lastLogId.high) : undefined
262
-
263
405
  const count = options?.count ?? 20
264
406
  const cursor = options?.from ? parseLong(options.from) : undefined
265
407
 
266
408
  const cid = parseLong(chatId)
267
- const startCursor = cursor ?? Long.fromNumber(0)
409
+
268
410
  const allMessages: Array<Record<string, unknown>> = []
269
411
  const seenLogIds = new Set<string>()
270
- let cur = startCursor
412
+ let cur = cursor ?? Long.fromNumber(0)
413
+
414
+ try {
415
+ for (let page = 0; page < MAX_PAGES; page++) {
416
+ const response = await session.getChatLogs([cid], [cur])
417
+ const responseStatus = response.body.status
418
+ if (typeof responseStatus === 'number' && responseStatus !== 0) {
419
+ throw new Error(`MCHATLOGS failed: ${responseStatus}`)
420
+ }
421
+
422
+ const batch = ((response.body.chatLogs ?? []) as Array<Record<string, unknown>>).filter(
423
+ (log) => longToString(log.chatId) === chatId,
424
+ )
425
+ if (batch.length === 0) {
426
+ return formatMessages(allMessages, count)
427
+ }
428
+
429
+ for (const log of batch) {
430
+ const lid = longToString(log.logId)
431
+ if (!seenLogIds.has(lid)) {
432
+ seenLogIds.add(lid)
433
+ allMessages.push(log)
434
+ }
435
+ }
436
+
437
+ const maxLog = findMaxLogId(batch, 'logId')
438
+ if (!maxLog || maxLog.equals(cur) || response.body.eof) {
439
+ return formatMessages(allMessages, count)
440
+ }
441
+
442
+ cur = maxLog
443
+ }
444
+ } catch {
445
+ allMessages.length = 0
446
+ seenLogIds.clear()
447
+ cur = cursor ?? Long.fromNumber(0)
448
+ }
449
+
450
+ if (allMessages.length > 0) {
451
+ warn(`[agent-kakaotalk] Warning: message fetch capped at ${MAX_PAGES} pages. Results may be incomplete.`)
452
+ return formatMessages(allMessages, count)
453
+ }
454
+
455
+ // Fetch fresh lastLogId via CHATONROOM (not the stale login-time snapshot)
456
+ const chatInfo = await session.getChatInfo(cid)
457
+ const chatBody = chatInfo.body as Record<string, unknown>
458
+ const maxLogId = bsonToLong(chatBody.l)
271
459
 
272
460
  let reachedEnd = false
273
461
  for (let page = 0; page < MAX_PAGES; page++) {
@@ -283,11 +471,7 @@ export class KakaoTalkClient {
283
471
  }
284
472
  }
285
473
 
286
- const maxLog = batch.reduce<Long | null>((max, l) => {
287
- const lid = l.logId as { high: number; low: number }
288
- const long = new Long(lid.low, lid.high)
289
- return !max || long.greaterThan(max) ? long : max
290
- }, null)
474
+ const maxLog = findMaxLogId(batch, 'logId')
291
475
 
292
476
  if (!maxLog || maxLog.equals(cur) || response.body.isOK) { reachedEnd = true; break }
293
477
  cur = maxLog
@@ -296,15 +480,7 @@ export class KakaoTalkClient {
296
480
  warn(`[agent-kakaotalk] Warning: message fetch capped at ${MAX_PAGES} pages. Results may be incomplete.`)
297
481
  }
298
482
 
299
- allMessages.sort((a, b) => (a.sendAt as number) - (b.sendAt as number))
300
-
301
- return allMessages.slice(-count).map((log) => ({
302
- log_id: longToString(log.logId),
303
- type: log.type as number,
304
- author_id: log.authorId as number,
305
- message: log.message as string,
306
- sent_at: log.sendAt as number,
307
- }))
483
+ return formatMessages(allMessages, count)
308
484
  } catch (error) {
309
485
  throw wrapError(error, 'get_messages_failed')
310
486
  }
@@ -332,17 +508,24 @@ export class KakaoTalkClient {
332
508
  async getProfile(): Promise<KakaoProfile> {
333
509
  this.ensureAuth()
334
510
  try {
511
+ const deviceConfig = getLocoDeviceConfig(this.deviceType)
512
+ const isPC = deviceConfig.os !== 'android'
513
+ const apiPrefix = isPC ? 'mac' : 'android'
514
+ const userAgent = isPC
515
+ ? `KT/${deviceConfig.appVersion} Md/${PC_OS_NAME} ${LANG}`
516
+ : `KT/${deviceConfig.appVersion} An/13 ${LANG}`
517
+
335
518
  const headers = {
336
519
  Authorization: `${this.oauthToken}-${this.deviceUuid}`,
337
- A: `${OS}/${APP_VERSION}/${LANG}`,
338
- 'User-Agent': `KT/${APP_VERSION} Md/macOS ${LANG}`,
520
+ A: `${deviceConfig.os}/${deviceConfig.appVersion}/${LANG}`,
521
+ 'User-Agent': userAgent,
339
522
  Accept: '*/*',
340
523
  'Accept-Language': LANG,
341
524
  }
342
525
 
343
526
  const [profileRes, settingsRes] = await Promise.all([
344
- fetch('https://katalk.kakao.com/mac/profile3/me.json', { headers }),
345
- fetch('https://katalk.kakao.com/mac/account/more_settings.json?since=0&lang=ko', { headers }),
527
+ fetch(`https://katalk.kakao.com/${apiPrefix}/profile3/me.json`, { headers }),
528
+ fetch(`https://katalk.kakao.com/${apiPrefix}/account/more_settings.json?since=0&lang=ko`, { headers }),
346
529
  ])
347
530
 
348
531
  if (!profileRes.ok) {
@@ -353,9 +536,15 @@ export class KakaoTalkClient {
353
536
  const profile = profileData.profile as Record<string, unknown> | undefined
354
537
 
355
538
  let accountDisplayId: string | null = null
539
+ let accountEmail: string | null = null
540
+ let pstnNumber: string | null = null
541
+ let emailVerified: boolean | null = null
356
542
  if (settingsRes.ok) {
357
543
  const settingsData = await settingsRes.json() as Record<string, unknown>
358
544
  accountDisplayId = (settingsData.accountDisplayId as string) || null
545
+ accountEmail = (settingsData.accountEmail as string) || null
546
+ pstnNumber = (settingsData.pstnNumber as string) || null
547
+ emailVerified = typeof settingsData.emailVerified === 'boolean' ? settingsData.emailVerified : null
359
548
  }
360
549
 
361
550
  return {
@@ -363,8 +552,14 @@ export class KakaoTalkClient {
363
552
  nickname: (profile?.nickName as string) || '',
364
553
  profile_image_url: (profile?.profileImageUrl as string) || null,
365
554
  original_profile_image_url: (profile?.originalProfileImageUrl as string) || null,
555
+ background_image_url: (profile?.backgroundImageUrl as string) || null,
556
+ original_background_image_url: (profile?.originalBackgroundImageUrl as string) || null,
557
+ fullname: (profile?.fullname as string) || null,
366
558
  status_message: (profile?.statusMessage as string) || null,
367
559
  account_display_id: accountDisplayId,
560
+ account_email: accountEmail,
561
+ pstn_number: pstnNumber,
562
+ email_verified: emailVerified,
368
563
  }
369
564
  } catch (error) {
370
565
  throw wrapError(error, 'get_profile_failed')
@@ -6,7 +6,7 @@ import { handleError } from '@/shared/utils/error-handler'
6
6
  import { formatOutput } from '@/shared/utils/output'
7
7
  import { info, error, debug } from '@/shared/utils/stderr'
8
8
 
9
- import { generateDeviceUuid, loginFlow } from '../auth/kakao-login'
9
+ import { loginFlow } from '../auth/kakao-login'
10
10
  import { CredentialManager } from '../credential-manager'
11
11
  import { KakaoTokenExtractor } from '../token-extractor'
12
12
  import {
@@ -23,7 +23,9 @@ function isInteractiveSession(): boolean {
23
23
  function hasTTY(): boolean {
24
24
  try {
25
25
  const { openSync, closeSync } = require('node:fs') as typeof import('node:fs')
26
- const fd = openSync('/dev/tty', 'r')
26
+ // CONIN$ is the Windows console input device; /dev/tty is the Unix equivalent
27
+ const ttyDevice = process.platform === 'win32' ? 'CONIN$' : '/dev/tty'
28
+ const fd = openSync(ttyDevice, 'r')
27
29
  closeSync(fd)
28
30
  return true
29
31
  } catch { return false }
@@ -178,6 +180,20 @@ async function promptHidden(message: string): Promise<string | undefined> {
178
180
  }
179
181
 
180
182
  async function promptHiddenTTY(message: string): Promise<string | undefined> {
183
+ if (process.platform === 'win32') {
184
+ const { execSync } = require('node:child_process') as typeof import('node:child_process')
185
+ try {
186
+ const escapedMessage = message.replace(/'/g, "''")
187
+ const ps = `$p = Read-Host '${escapedMessage}' -AsSecureString; [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($p))`
188
+ const result = execSync(`powershell -NoProfile -Command "${ps}"`, {
189
+ encoding: 'utf-8',
190
+ timeout: 120_000,
191
+ stdio: ['inherit', 'pipe', 'inherit'],
192
+ })
193
+ return result.trim() || undefined
194
+ } catch { return undefined }
195
+ }
196
+
181
197
  const { createReadStream } = await import('node:fs')
182
198
  const { createInterface } = await import('node:readline/promises')
183
199
  const ttyIn = createReadStream('/dev/tty')
@@ -277,7 +293,8 @@ async function loginAction(options: KakaoAuthOptions): Promise<void> {
277
293
 
278
294
  const existing = await credManager.getAccount()
279
295
  const pendingState = await credManager.loadPendingLogin()
280
- const savedDeviceUuid = pendingState?.device_uuid ?? existing?.device_uuid
296
+ const existingUuid = existing?.auth_method === 'login' ? existing?.device_uuid : undefined
297
+ const savedDeviceUuid = pendingState?.device_uuid ?? existingUuid
281
298
 
282
299
  const onPasscodeDisplay = (code: string) => {
283
300
  if (interactive) {
@@ -355,6 +372,7 @@ async function handleLoginResult(
355
372
  refresh_token: result.credentials.refresh_token,
356
373
  device_uuid: result.credentials.device_uuid,
357
374
  device_type: result.credentials.device_type,
375
+ auth_method: 'login',
358
376
  created_at: now,
359
377
  updated_at: now,
360
378
  })
@@ -372,67 +390,6 @@ async function handleLoginResult(
372
390
  }
373
391
  }
374
392
 
375
- async function extractAction(options: {
376
- pretty?: boolean
377
- debug?: boolean
378
- unsafelyShowSecrets?: boolean
379
- }): Promise<void> {
380
- try {
381
- if (options.unsafelyShowSecrets) {
382
- options.debug = true
383
- }
384
- const debugLog = options.debug ? (msg: string) => debug(`[debug] ${msg}`) : undefined
385
- const extractor = new KakaoTokenExtractor(undefined, debugLog)
386
-
387
- const token = await extractor.extract()
388
-
389
- if (!token) {
390
- console.log(
391
- formatOutput(
392
- {
393
- error: 'No credentials found. Make sure KakaoTalk desktop app is installed and logged in.',
394
- hint: options.debug ? undefined : 'Run with --debug for more info.',
395
- },
396
- options.pretty,
397
- ),
398
- )
399
- process.exit(1)
400
- }
401
-
402
- if (options.debug) {
403
- const display = options.unsafelyShowSecrets
404
- ? token.oauth_token
405
- : `${token.oauth_token.substring(0, 12)}...`
406
- debug(`[debug] oauth_token: ${display}`)
407
- debug(`[debug] user_id: ${token.user_id}`)
408
- }
409
-
410
- const credManager = new CredentialManager()
411
- const accountId = token.user_id || 'default'
412
- const now = new Date().toISOString()
413
-
414
- await credManager.setAccount({
415
- account_id: accountId,
416
- oauth_token: token.oauth_token,
417
- user_id: token.user_id,
418
- refresh_token: token.refresh_token,
419
- device_uuid: token.device_uuid ?? generateDeviceUuid(),
420
- device_type: 'tablet',
421
- created_at: now,
422
- updated_at: now,
423
- })
424
-
425
- const config = await credManager.load()
426
- if (!config.current_account) {
427
- await credManager.setCurrentAccount(accountId)
428
- }
429
-
430
- console.log(formatOutput({ account_id: accountId, user_id: token.user_id, extracted: true }, options.pretty))
431
- } catch (error) {
432
- handleError(error as Error)
433
- }
434
- }
435
-
436
393
  async function listAction(options: { pretty?: boolean }): Promise<void> {
437
394
  try {
438
395
  const credManager = new CredentialManager()
@@ -483,7 +440,7 @@ async function statusAction(options: { account?: string; pretty?: boolean }): Pr
483
440
  const account = await credManager.getAccount(options.account)
484
441
 
485
442
  if (!account) {
486
- console.log(formatOutput({ error: 'No account configured. Run "auth login" or "auth extract" first.' }, options.pretty))
443
+ console.log(formatOutput({ error: 'No account configured. Run "auth login" first.' }, options.pretty))
487
444
  process.exit(1)
488
445
  }
489
446
 
@@ -533,14 +490,6 @@ export const authCommand = new Command('auth')
533
490
  .option('--debug', 'Show debug output')
534
491
  .action(loginAction),
535
492
  )
536
- .addCommand(
537
- new Command('extract')
538
- .description('Extract credentials from KakaoTalk desktop app (kicks desktop session)')
539
- .option('--pretty', 'Pretty print JSON output')
540
- .option('--debug', 'Show debug output for troubleshooting')
541
- .option('--unsafely-show-secrets', 'Show full token values in debug output')
542
- .action(extractAction),
543
- )
544
493
  .addCommand(
545
494
  new Command('list')
546
495
  .description('List all authenticated KakaoTalk accounts')
@@ -1,4 +1,4 @@
1
1
  export { authCommand } from './auth'
2
2
  export { chatCommand } from './chat'
3
3
  export { messageCommand } from './message'
4
- export { profileCommand } from './profile'
4
+ export { whoamiCommand } from './whoami'
@@ -13,6 +13,15 @@ const mockGetProfile = mock(() =>
13
13
  user_id: 'user-1',
14
14
  nickname: 'Test User',
15
15
  profile_image_url: 'https://example.com/avatar.jpg',
16
+ original_profile_image_url: 'https://example.com/avatar_orig.jpg',
17
+ background_image_url: 'https://example.com/bg.jpg',
18
+ original_background_image_url: 'https://example.com/bg_orig.jpg',
19
+ fullname: 'Real Name',
20
+ status_message: 'Hello!',
21
+ account_display_id: 'testuser',
22
+ account_email: 'test@example.com',
23
+ pstn_number: '+821012345678',
24
+ email_verified: true,
16
25
  }),
17
26
  )
18
27
 
@@ -24,9 +33,9 @@ mock.module('./shared', () => ({
24
33
  withKakaoClient: mockWithKakaoClient,
25
34
  }))
26
35
 
27
- import { profileCommand } from './profile'
36
+ import { whoamiCommand } from './whoami'
28
37
 
29
- describe('profile command', () => {
38
+ describe('whoami command', () => {
30
39
  let consoleLogSpy: ReturnType<typeof mock>
31
40
 
32
41
  beforeEach(() => {
@@ -43,6 +52,15 @@ describe('profile command', () => {
43
52
  user_id: 'user-1',
44
53
  nickname: 'Test User',
45
54
  profile_image_url: 'https://example.com/avatar.jpg',
55
+ original_profile_image_url: 'https://example.com/avatar_orig.jpg',
56
+ background_image_url: 'https://example.com/bg.jpg',
57
+ original_background_image_url: 'https://example.com/bg_orig.jpg',
58
+ fullname: 'Real Name',
59
+ status_message: 'Hello!',
60
+ account_display_id: 'testuser',
61
+ account_email: 'test@example.com',
62
+ pstn_number: '+821012345678',
63
+ email_verified: true,
46
64
  }),
47
65
  )
48
66
 
@@ -55,7 +73,7 @@ describe('profile command', () => {
55
73
  })
56
74
 
57
75
  test('outputs profile information', async () => {
58
- await profileCommand.parseAsync([], { from: 'user' })
76
+ await whoamiCommand.parseAsync([], { from: 'user' })
59
77
 
60
78
  expect(mockGetProfile).toHaveBeenCalled()
61
79
  const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
@@ -64,8 +82,22 @@ describe('profile command', () => {
64
82
  expect(output.profile_image_url).toBe('https://example.com/avatar.jpg')
65
83
  })
66
84
 
85
+ test('outputs enriched profile fields', async () => {
86
+ await whoamiCommand.parseAsync([], { from: 'user' })
87
+
88
+ const output = JSON.parse(consoleLogSpy.mock.calls[0][0])
89
+ expect(output.background_image_url).toBe('https://example.com/bg.jpg')
90
+ expect(output.original_background_image_url).toBe('https://example.com/bg_orig.jpg')
91
+ expect(output.fullname).toBe('Real Name')
92
+ expect(output.status_message).toBe('Hello!')
93
+ expect(output.account_display_id).toBe('testuser')
94
+ expect(output.account_email).toBe('test@example.com')
95
+ expect(output.pstn_number).toBe('+821012345678')
96
+ expect(output.email_verified).toBe(true)
97
+ })
98
+
67
99
  test('passes account option to withKakaoClient', async () => {
68
- await profileCommand.parseAsync(['--account', 'my-account'], { from: 'user' })
100
+ await whoamiCommand.parseAsync(['--account', 'my-account'], { from: 'user' })
69
101
 
70
102
  expect(mockWithKakaoClient).toHaveBeenCalledWith(
71
103
  expect.objectContaining({ account: 'my-account' }),
@@ -74,7 +106,7 @@ describe('profile command', () => {
74
106
  })
75
107
 
76
108
  test('outputs profile with pretty flag', async () => {
77
- await profileCommand.parseAsync(['--pretty'], { from: 'user' })
109
+ await whoamiCommand.parseAsync(['--pretty'], { from: 'user' })
78
110
 
79
111
  expect(mockGetProfile).toHaveBeenCalled()
80
112
  const rawOutput = consoleLogSpy.mock.calls[0][0]
@@ -5,7 +5,7 @@ import { formatOutput } from '@/shared/utils/output'
5
5
 
6
6
  import { withKakaoClient } from './shared'
7
7
 
8
- async function profileAction(options: { account?: string; pretty?: boolean }): Promise<void> {
8
+ async function whoamiAction(options: { account?: string; pretty?: boolean }): Promise<void> {
9
9
  try {
10
10
  const profile = await withKakaoClient(options, (client) => client.getProfile())
11
11
  console.log(formatOutput(profile, options.pretty))
@@ -14,8 +14,8 @@ async function profileAction(options: { account?: string; pretty?: boolean }): P
14
14
  }
15
15
  }
16
16
 
17
- export const profileCommand = new Command('profile')
18
- .description('Show your KakaoTalk profile')
17
+ export const whoamiCommand = new Command('whoami')
18
+ .description('Show current authenticated user')
19
19
  .option('--account <id>', 'Use a specific KakaoTalk account')
20
20
  .option('--pretty', 'Pretty print JSON output')
21
- .action(profileAction)
21
+ .action(whoamiAction)
@@ -34,6 +34,7 @@ export class KakaoCredentialManager {
34
34
 
35
35
  async save(config: KakaoConfig): Promise<void> {
36
36
  await mkdir(this.configDir, { recursive: true })
37
+ // TODO: Windows does not honor mode 0o600 — consider platform-specific credential storage
37
38
  await writeFile(this.credentialsPath, JSON.stringify(config, null, 2))
38
39
  await chmod(this.credentialsPath, 0o600)
39
40
  }
@@ -4,6 +4,7 @@ export { KakaoTalkListener } from './listener'
4
4
  export type { PendingLoginState } from './credential-manager'
5
5
  export type {
6
6
  KakaoAccountCredentials,
7
+ KakaoAuthMethod,
7
8
  KakaoChat,
8
9
  KakaoConfig,
9
10
  KakaoDeviceType,
@@ -45,7 +45,7 @@ mock.module('./protocol/session', () => ({ LocoSession: MockLocoSession }))
45
45
 
46
46
  function createMockClient(overrides: Record<string, unknown> = {}) {
47
47
  return {
48
- getCredentials: mock(() => ({ oauthToken: 'token', userId: 'user1', deviceUuid: 'device1' })),
48
+ getCredentials: mock(() => ({ oauthToken: 'token', userId: 'user1', deviceUuid: 'device1', deviceType: 'tablet' as const })),
49
49
  ...overrides,
50
50
  } as any
51
51
  }
@@ -68,7 +68,7 @@ describe('KakaoTalkListener', () => {
68
68
  await listener.start()
69
69
 
70
70
  expect(mockLogin).toHaveBeenCalledTimes(1)
71
- expect(mockLogin).toHaveBeenCalledWith('token', 'user1', 'device1')
71
+ expect(mockLogin).toHaveBeenCalledWith('token', 'user1', 'device1', undefined, 'tablet')
72
72
  })
73
73
 
74
74
  test('is idempotent', async () => {