agent-messenger 2.10.2 → 2.11.1

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 (330) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.env.template +4 -1
  3. package/README.md +77 -27
  4. package/bun.lock +26 -0
  5. package/dist/package.json +21 -1
  6. package/dist/src/platforms/channeltalk/commands/auth.d.ts +2 -1
  7. package/dist/src/platforms/channeltalk/commands/auth.d.ts.map +1 -1
  8. package/dist/src/platforms/channeltalk/commands/auth.js +5 -3
  9. package/dist/src/platforms/channeltalk/commands/auth.js.map +1 -1
  10. package/dist/src/platforms/channeltalk/token-extractor.d.ts +2 -1
  11. package/dist/src/platforms/channeltalk/token-extractor.d.ts.map +1 -1
  12. package/dist/src/platforms/channeltalk/token-extractor.js +22 -6
  13. package/dist/src/platforms/channeltalk/token-extractor.js.map +1 -1
  14. package/dist/src/platforms/channeltalkbot/cli.d.ts.map +1 -1
  15. package/dist/src/platforms/channeltalkbot/cli.js +11 -1
  16. package/dist/src/platforms/channeltalkbot/cli.js.map +1 -1
  17. package/dist/src/platforms/channeltalkbot/commands/auth.d.ts.map +1 -1
  18. package/dist/src/platforms/channeltalkbot/commands/auth.js +1 -5
  19. package/dist/src/platforms/channeltalkbot/commands/auth.js.map +1 -1
  20. package/dist/src/platforms/channeltalkbot/commands/bot.d.ts.map +1 -1
  21. package/dist/src/platforms/channeltalkbot/commands/bot.js +1 -6
  22. package/dist/src/platforms/channeltalkbot/commands/bot.js.map +1 -1
  23. package/dist/src/platforms/channeltalkbot/commands/chat.d.ts.map +1 -1
  24. package/dist/src/platforms/channeltalkbot/commands/chat.js +1 -6
  25. package/dist/src/platforms/channeltalkbot/commands/chat.js.map +1 -1
  26. package/dist/src/platforms/channeltalkbot/commands/group.d.ts.map +1 -1
  27. package/dist/src/platforms/channeltalkbot/commands/group.js +1 -6
  28. package/dist/src/platforms/channeltalkbot/commands/group.js.map +1 -1
  29. package/dist/src/platforms/channeltalkbot/commands/manager.d.ts.map +1 -1
  30. package/dist/src/platforms/channeltalkbot/commands/manager.js +1 -6
  31. package/dist/src/platforms/channeltalkbot/commands/manager.js.map +1 -1
  32. package/dist/src/platforms/channeltalkbot/commands/message.d.ts.map +1 -1
  33. package/dist/src/platforms/channeltalkbot/commands/message.js +1 -6
  34. package/dist/src/platforms/channeltalkbot/commands/message.js.map +1 -1
  35. package/dist/src/platforms/channeltalkbot/commands/whoami.d.ts.map +1 -1
  36. package/dist/src/platforms/channeltalkbot/commands/whoami.js +1 -6
  37. package/dist/src/platforms/channeltalkbot/commands/whoami.js.map +1 -1
  38. package/dist/src/platforms/channeltalkbot/credential-manager.d.ts +5 -0
  39. package/dist/src/platforms/channeltalkbot/credential-manager.d.ts.map +1 -1
  40. package/dist/src/platforms/channeltalkbot/credential-manager.js +34 -4
  41. package/dist/src/platforms/channeltalkbot/credential-manager.js.map +1 -1
  42. package/dist/src/platforms/discord/commands/auth.d.ts +1 -0
  43. package/dist/src/platforms/discord/commands/auth.d.ts.map +1 -1
  44. package/dist/src/platforms/discord/commands/auth.js +3 -1
  45. package/dist/src/platforms/discord/commands/auth.js.map +1 -1
  46. package/dist/src/platforms/discord/listener.d.ts +2 -0
  47. package/dist/src/platforms/discord/listener.d.ts.map +1 -1
  48. package/dist/src/platforms/discord/listener.js +51 -21
  49. package/dist/src/platforms/discord/listener.js.map +1 -1
  50. package/dist/src/platforms/discord/token-extractor.d.ts +2 -1
  51. package/dist/src/platforms/discord/token-extractor.d.ts.map +1 -1
  52. package/dist/src/platforms/discord/token-extractor.js +21 -6
  53. package/dist/src/platforms/discord/token-extractor.js.map +1 -1
  54. package/dist/src/platforms/discordbot/cli.d.ts.map +1 -1
  55. package/dist/src/platforms/discordbot/cli.js +12 -1
  56. package/dist/src/platforms/discordbot/cli.js.map +1 -1
  57. package/dist/src/platforms/discordbot/client.d.ts +3 -0
  58. package/dist/src/platforms/discordbot/client.d.ts.map +1 -1
  59. package/dist/src/platforms/discordbot/client.js +3 -0
  60. package/dist/src/platforms/discordbot/client.js.map +1 -1
  61. package/dist/src/platforms/discordbot/commands/auth.d.ts.map +1 -1
  62. package/dist/src/platforms/discordbot/commands/auth.js +1 -5
  63. package/dist/src/platforms/discordbot/commands/auth.js.map +1 -1
  64. package/dist/src/platforms/discordbot/commands/message.d.ts.map +1 -1
  65. package/dist/src/platforms/discordbot/commands/message.js +1 -6
  66. package/dist/src/platforms/discordbot/commands/message.js.map +1 -1
  67. package/dist/src/platforms/discordbot/commands/server.d.ts.map +1 -1
  68. package/dist/src/platforms/discordbot/commands/server.js +1 -4
  69. package/dist/src/platforms/discordbot/commands/server.js.map +1 -1
  70. package/dist/src/platforms/discordbot/commands/whoami.d.ts.map +1 -1
  71. package/dist/src/platforms/discordbot/commands/whoami.js +1 -6
  72. package/dist/src/platforms/discordbot/commands/whoami.js.map +1 -1
  73. package/dist/src/platforms/discordbot/index.d.ts +3 -1
  74. package/dist/src/platforms/discordbot/index.d.ts.map +1 -1
  75. package/dist/src/platforms/discordbot/index.js +2 -1
  76. package/dist/src/platforms/discordbot/index.js.map +1 -1
  77. package/dist/src/platforms/discordbot/listener.d.ts +43 -0
  78. package/dist/src/platforms/discordbot/listener.d.ts.map +1 -0
  79. package/dist/src/platforms/discordbot/listener.js +292 -0
  80. package/dist/src/platforms/discordbot/listener.js.map +1 -0
  81. package/dist/src/platforms/discordbot/types.d.ts +161 -0
  82. package/dist/src/platforms/discordbot/types.d.ts.map +1 -1
  83. package/dist/src/platforms/discordbot/types.js +34 -0
  84. package/dist/src/platforms/discordbot/types.js.map +1 -1
  85. package/dist/src/platforms/instagram/commands/auth.d.ts.map +1 -1
  86. package/dist/src/platforms/instagram/commands/auth.js +3 -1
  87. package/dist/src/platforms/instagram/commands/auth.js.map +1 -1
  88. package/dist/src/platforms/instagram/token-extractor.d.ts +2 -1
  89. package/dist/src/platforms/instagram/token-extractor.d.ts.map +1 -1
  90. package/dist/src/platforms/instagram/token-extractor.js +11 -2
  91. package/dist/src/platforms/instagram/token-extractor.js.map +1 -1
  92. package/dist/src/platforms/slack/commands/auth.d.ts.map +1 -1
  93. package/dist/src/platforms/slack/commands/auth.js +4 -2
  94. package/dist/src/platforms/slack/commands/auth.js.map +1 -1
  95. package/dist/src/platforms/slack/token-extractor.d.ts +4 -1
  96. package/dist/src/platforms/slack/token-extractor.d.ts.map +1 -1
  97. package/dist/src/platforms/slack/token-extractor.js +64 -15
  98. package/dist/src/platforms/slack/token-extractor.js.map +1 -1
  99. package/dist/src/platforms/slackbot/cli.d.ts.map +1 -1
  100. package/dist/src/platforms/slackbot/cli.js +15 -3
  101. package/dist/src/platforms/slackbot/cli.js.map +1 -1
  102. package/dist/src/platforms/slackbot/client.d.ts +22 -1
  103. package/dist/src/platforms/slackbot/client.d.ts.map +1 -1
  104. package/dist/src/platforms/slackbot/client.js +104 -1
  105. package/dist/src/platforms/slackbot/client.js.map +1 -1
  106. package/dist/src/platforms/slackbot/commands/auth.d.ts.map +1 -1
  107. package/dist/src/platforms/slackbot/commands/auth.js +1 -5
  108. package/dist/src/platforms/slackbot/commands/auth.js.map +1 -1
  109. package/dist/src/platforms/slackbot/commands/file.d.ts +3 -0
  110. package/dist/src/platforms/slackbot/commands/file.d.ts.map +1 -0
  111. package/dist/src/platforms/slackbot/commands/file.js +164 -0
  112. package/dist/src/platforms/slackbot/commands/file.js.map +1 -0
  113. package/dist/src/platforms/slackbot/commands/index.d.ts +1 -0
  114. package/dist/src/platforms/slackbot/commands/index.d.ts.map +1 -1
  115. package/dist/src/platforms/slackbot/commands/index.js +1 -0
  116. package/dist/src/platforms/slackbot/commands/index.js.map +1 -1
  117. package/dist/src/platforms/slackbot/commands/message.d.ts.map +1 -1
  118. package/dist/src/platforms/slackbot/commands/message.js +19 -0
  119. package/dist/src/platforms/slackbot/commands/message.js.map +1 -1
  120. package/dist/src/platforms/slackbot/commands/whoami.d.ts.map +1 -1
  121. package/dist/src/platforms/slackbot/commands/whoami.js +1 -6
  122. package/dist/src/platforms/slackbot/commands/whoami.js.map +1 -1
  123. package/dist/src/platforms/slackbot/credential-manager.d.ts +1 -0
  124. package/dist/src/platforms/slackbot/credential-manager.d.ts.map +1 -1
  125. package/dist/src/platforms/slackbot/credential-manager.js +30 -2
  126. package/dist/src/platforms/slackbot/credential-manager.js.map +1 -1
  127. package/dist/src/platforms/slackbot/index.d.ts +4 -1
  128. package/dist/src/platforms/slackbot/index.d.ts.map +1 -1
  129. package/dist/src/platforms/slackbot/index.js +1 -0
  130. package/dist/src/platforms/slackbot/index.js.map +1 -1
  131. package/dist/src/platforms/slackbot/listener.d.ts +44 -0
  132. package/dist/src/platforms/slackbot/listener.d.ts.map +1 -0
  133. package/dist/src/platforms/slackbot/listener.js +313 -0
  134. package/dist/src/platforms/slackbot/listener.js.map +1 -0
  135. package/dist/src/platforms/slackbot/types.d.ts +196 -1
  136. package/dist/src/platforms/slackbot/types.d.ts.map +1 -1
  137. package/dist/src/platforms/slackbot/types.js +4 -1
  138. package/dist/src/platforms/slackbot/types.js.map +1 -1
  139. package/dist/src/platforms/teams/commands/auth.d.ts +1 -0
  140. package/dist/src/platforms/teams/commands/auth.d.ts.map +1 -1
  141. package/dist/src/platforms/teams/commands/auth.js +37 -6
  142. package/dist/src/platforms/teams/commands/auth.js.map +1 -1
  143. package/dist/src/platforms/teams/ensure-auth.js +31 -9
  144. package/dist/src/platforms/teams/ensure-auth.js.map +1 -1
  145. package/dist/src/platforms/teams/token-extractor.d.ts +4 -1
  146. package/dist/src/platforms/teams/token-extractor.d.ts.map +1 -1
  147. package/dist/src/platforms/teams/token-extractor.js +71 -29
  148. package/dist/src/platforms/teams/token-extractor.js.map +1 -1
  149. package/dist/src/platforms/webex/commands/auth.d.ts +1 -0
  150. package/dist/src/platforms/webex/commands/auth.d.ts.map +1 -1
  151. package/dist/src/platforms/webex/commands/auth.js +3 -1
  152. package/dist/src/platforms/webex/commands/auth.js.map +1 -1
  153. package/dist/src/platforms/webex/token-extractor.d.ts +3 -1
  154. package/dist/src/platforms/webex/token-extractor.d.ts.map +1 -1
  155. package/dist/src/platforms/webex/token-extractor.js +16 -2
  156. package/dist/src/platforms/webex/token-extractor.js.map +1 -1
  157. package/dist/src/platforms/wechatbot/cli.d.ts.map +1 -1
  158. package/dist/src/platforms/wechatbot/cli.js +11 -1
  159. package/dist/src/platforms/wechatbot/cli.js.map +1 -1
  160. package/dist/src/platforms/wechatbot/commands/auth.d.ts.map +1 -1
  161. package/dist/src/platforms/wechatbot/commands/auth.js +1 -5
  162. package/dist/src/platforms/wechatbot/commands/auth.js.map +1 -1
  163. package/dist/src/platforms/wechatbot/commands/message.d.ts.map +1 -1
  164. package/dist/src/platforms/wechatbot/commands/message.js +1 -6
  165. package/dist/src/platforms/wechatbot/commands/message.js.map +1 -1
  166. package/dist/src/platforms/wechatbot/commands/template.d.ts.map +1 -1
  167. package/dist/src/platforms/wechatbot/commands/template.js +1 -6
  168. package/dist/src/platforms/wechatbot/commands/template.js.map +1 -1
  169. package/dist/src/platforms/wechatbot/commands/user.d.ts.map +1 -1
  170. package/dist/src/platforms/wechatbot/commands/user.js +1 -6
  171. package/dist/src/platforms/wechatbot/commands/user.js.map +1 -1
  172. package/dist/src/platforms/wechatbot/commands/whoami.d.ts.map +1 -1
  173. package/dist/src/platforms/wechatbot/commands/whoami.js +1 -6
  174. package/dist/src/platforms/wechatbot/commands/whoami.js.map +1 -1
  175. package/dist/src/platforms/whatsappbot/cli.d.ts.map +1 -1
  176. package/dist/src/platforms/whatsappbot/cli.js +11 -1
  177. package/dist/src/platforms/whatsappbot/cli.js.map +1 -1
  178. package/dist/src/platforms/whatsappbot/commands/auth.d.ts.map +1 -1
  179. package/dist/src/platforms/whatsappbot/commands/auth.js +1 -5
  180. package/dist/src/platforms/whatsappbot/commands/auth.js.map +1 -1
  181. package/dist/src/platforms/whatsappbot/commands/message.d.ts.map +1 -1
  182. package/dist/src/platforms/whatsappbot/commands/message.js +1 -6
  183. package/dist/src/platforms/whatsappbot/commands/message.js.map +1 -1
  184. package/dist/src/platforms/whatsappbot/commands/template.d.ts.map +1 -1
  185. package/dist/src/platforms/whatsappbot/commands/template.js +1 -6
  186. package/dist/src/platforms/whatsappbot/commands/template.js.map +1 -1
  187. package/dist/src/platforms/whatsappbot/commands/whoami.d.ts.map +1 -1
  188. package/dist/src/platforms/whatsappbot/commands/whoami.js +1 -6
  189. package/dist/src/platforms/whatsappbot/commands/whoami.js.map +1 -1
  190. package/dist/src/shared/chromium/browsers.d.ts +8 -0
  191. package/dist/src/shared/chromium/browsers.d.ts.map +1 -1
  192. package/dist/src/shared/chromium/browsers.js +58 -3
  193. package/dist/src/shared/chromium/browsers.js.map +1 -1
  194. package/dist/src/shared/chromium/cli-options.d.ts +5 -0
  195. package/dist/src/shared/chromium/cli-options.d.ts.map +1 -0
  196. package/dist/src/shared/chromium/cli-options.js +8 -0
  197. package/dist/src/shared/chromium/cli-options.js.map +1 -0
  198. package/dist/src/shared/chromium/index.d.ts +3 -1
  199. package/dist/src/shared/chromium/index.d.ts.map +1 -1
  200. package/dist/src/shared/chromium/index.js +2 -1
  201. package/dist/src/shared/chromium/index.js.map +1 -1
  202. package/dist/src/shared/utils/cli-output.d.ts +7 -0
  203. package/dist/src/shared/utils/cli-output.d.ts.map +1 -0
  204. package/dist/src/shared/utils/cli-output.js +7 -0
  205. package/dist/src/shared/utils/cli-output.js.map +1 -0
  206. package/dist/src/tui/app.d.ts.map +1 -1
  207. package/dist/src/tui/app.js +73 -20
  208. package/dist/src/tui/app.js.map +1 -1
  209. package/docs/content/docs/cli/channeltalk.mdx +4 -0
  210. package/docs/content/docs/cli/discord.mdx +5 -0
  211. package/docs/content/docs/cli/instagram.mdx +3 -0
  212. package/docs/content/docs/cli/slack.mdx +5 -0
  213. package/docs/content/docs/cli/slackbot.mdx +60 -22
  214. package/docs/content/docs/cli/teams.mdx +5 -0
  215. package/docs/content/docs/cli/webex.mdx +3 -0
  216. package/docs/content/docs/sdk/channeltalkbot.mdx +38 -1
  217. package/docs/content/docs/sdk/discordbot.mdx +501 -0
  218. package/docs/content/docs/sdk/meta.json +2 -0
  219. package/docs/content/docs/sdk/slackbot.mdx +576 -0
  220. package/e2e/README.md +1 -1
  221. package/e2e/config.ts +9 -4
  222. package/examples/discordbot-listen.ts +65 -0
  223. package/examples/slackbot-listen.ts +65 -0
  224. package/package.json +21 -1
  225. package/skills/agent-channeltalk/SKILL.md +5 -1
  226. package/skills/agent-channeltalk/references/authentication.md +5 -1
  227. package/skills/agent-channeltalkbot/SKILL.md +17 -3
  228. package/skills/agent-channeltalkbot/references/authentication.md +7 -5
  229. package/skills/agent-discord/SKILL.md +5 -1
  230. package/skills/agent-discord/references/authentication.md +7 -1
  231. package/skills/agent-discordbot/SKILL.md +13 -2
  232. package/skills/agent-discordbot/references/common-patterns.md +1 -1
  233. package/skills/agent-instagram/SKILL.md +7 -1
  234. package/skills/agent-instagram/references/authentication.md +6 -0
  235. package/skills/agent-kakaotalk/SKILL.md +1 -1
  236. package/skills/agent-line/SKILL.md +1 -1
  237. package/skills/agent-slack/SKILL.md +5 -1
  238. package/skills/agent-slack/references/authentication.md +7 -1
  239. package/skills/agent-slackbot/SKILL.md +56 -4
  240. package/skills/agent-slackbot/references/authentication.md +4 -0
  241. package/skills/agent-teams/SKILL.md +5 -1
  242. package/skills/agent-teams/references/authentication.md +7 -1
  243. package/skills/agent-telegram/SKILL.md +1 -1
  244. package/skills/agent-webex/SKILL.md +7 -1
  245. package/skills/agent-webex/references/authentication.md +6 -0
  246. package/skills/agent-wechatbot/SKILL.md +16 -1
  247. package/skills/agent-wechatbot/references/authentication.md +219 -0
  248. package/skills/agent-wechatbot/references/common-patterns.md +358 -0
  249. package/skills/agent-wechatbot/templates/account-summary.sh +122 -0
  250. package/skills/agent-wechatbot/templates/post-message.sh +122 -0
  251. package/skills/agent-wechatbot/templates/send-template.sh +152 -0
  252. package/skills/agent-whatsapp/SKILL.md +1 -1
  253. package/skills/agent-whatsappbot/SKILL.md +30 -1
  254. package/src/platforms/channeltalk/commands/auth.test.ts +15 -3
  255. package/src/platforms/channeltalk/commands/auth.ts +15 -5
  256. package/src/platforms/channeltalk/token-extractor.ts +24 -5
  257. package/src/platforms/channeltalkbot/cli.ts +9 -0
  258. package/src/platforms/channeltalkbot/commands/auth.ts +1 -5
  259. package/src/platforms/channeltalkbot/commands/bot.ts +1 -6
  260. package/src/platforms/channeltalkbot/commands/chat.ts +1 -6
  261. package/src/platforms/channeltalkbot/commands/group.ts +1 -6
  262. package/src/platforms/channeltalkbot/commands/manager.ts +1 -6
  263. package/src/platforms/channeltalkbot/commands/message.ts +1 -6
  264. package/src/platforms/channeltalkbot/commands/whoami.test.ts +2 -0
  265. package/src/platforms/channeltalkbot/commands/whoami.ts +1 -6
  266. package/src/platforms/channeltalkbot/credential-manager.test.ts +96 -2
  267. package/src/platforms/channeltalkbot/credential-manager.ts +37 -4
  268. package/src/platforms/discord/commands/auth.ts +13 -2
  269. package/src/platforms/discord/listener.test.ts +59 -1
  270. package/src/platforms/discord/listener.ts +43 -19
  271. package/src/platforms/discord/token-extractor.ts +30 -6
  272. package/src/platforms/discordbot/cli.ts +10 -0
  273. package/src/platforms/discordbot/client.ts +4 -0
  274. package/src/platforms/discordbot/commands/auth.ts +1 -5
  275. package/src/platforms/discordbot/commands/message.ts +1 -6
  276. package/src/platforms/discordbot/commands/server.ts +1 -5
  277. package/src/platforms/discordbot/commands/whoami.ts +1 -6
  278. package/src/platforms/discordbot/index.test.ts +82 -0
  279. package/src/platforms/discordbot/index.ts +27 -9
  280. package/src/platforms/discordbot/listener.test.ts +1002 -0
  281. package/src/platforms/discordbot/listener.ts +321 -0
  282. package/src/platforms/discordbot/types.ts +163 -0
  283. package/src/platforms/instagram/commands/auth.ts +9 -1
  284. package/src/platforms/instagram/token-extractor.ts +13 -1
  285. package/src/platforms/slack/commands/auth.ts +11 -2
  286. package/src/platforms/slack/token-extractor.test.ts +96 -0
  287. package/src/platforms/slack/token-extractor.ts +76 -13
  288. package/src/platforms/slackbot/cli.ts +13 -1
  289. package/src/platforms/slackbot/client.test.ts +274 -0
  290. package/src/platforms/slackbot/client.ts +130 -2
  291. package/src/platforms/slackbot/commands/auth.ts +1 -5
  292. package/src/platforms/slackbot/commands/file.test.ts +201 -0
  293. package/src/platforms/slackbot/commands/file.ts +212 -0
  294. package/src/platforms/slackbot/commands/index.ts +1 -0
  295. package/src/platforms/slackbot/commands/message.ts +22 -0
  296. package/src/platforms/slackbot/commands/whoami.ts +1 -6
  297. package/src/platforms/slackbot/credential-manager.test.ts +62 -2
  298. package/src/platforms/slackbot/credential-manager.ts +32 -2
  299. package/src/platforms/slackbot/index.test.ts +59 -0
  300. package/src/platforms/slackbot/index.ts +31 -7
  301. package/src/platforms/slackbot/listener.test.ts +1012 -0
  302. package/src/platforms/slackbot/listener.ts +362 -0
  303. package/src/platforms/slackbot/types.ts +224 -1
  304. package/src/platforms/teams/commands/auth.test.ts +1 -1
  305. package/src/platforms/teams/commands/auth.ts +66 -7
  306. package/src/platforms/teams/ensure-auth.test.ts +56 -5
  307. package/src/platforms/teams/ensure-auth.ts +39 -11
  308. package/src/platforms/teams/token-extractor.test.ts +146 -24
  309. package/src/platforms/teams/token-extractor.ts +87 -29
  310. package/src/platforms/webex/commands/auth.ts +13 -2
  311. package/src/platforms/webex/token-extractor.ts +25 -3
  312. package/src/platforms/wechatbot/cli.ts +9 -0
  313. package/src/platforms/wechatbot/commands/auth.ts +1 -5
  314. package/src/platforms/wechatbot/commands/message.ts +1 -6
  315. package/src/platforms/wechatbot/commands/template.ts +1 -6
  316. package/src/platforms/wechatbot/commands/user.ts +1 -6
  317. package/src/platforms/wechatbot/commands/whoami.ts +1 -6
  318. package/src/platforms/whatsappbot/cli.ts +9 -0
  319. package/src/platforms/whatsappbot/commands/auth.ts +1 -5
  320. package/src/platforms/whatsappbot/commands/message.ts +1 -6
  321. package/src/platforms/whatsappbot/commands/template.ts +1 -6
  322. package/src/platforms/whatsappbot/commands/whoami.ts +1 -6
  323. package/src/shared/chromium/browsers.test.ts +80 -0
  324. package/src/shared/chromium/browsers.ts +72 -3
  325. package/src/shared/chromium/cli-options.test.ts +22 -0
  326. package/src/shared/chromium/cli-options.ts +12 -0
  327. package/src/shared/chromium/index.ts +3 -0
  328. package/src/shared/utils/cli-output.test.ts +57 -0
  329. package/src/shared/utils/cli-output.ts +8 -0
  330. package/src/tui/app.ts +129 -20
@@ -6,11 +6,20 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
6
6
  import { tmpdir } from 'node:os'
7
7
  import { join } from 'node:path'
8
8
 
9
+ import { ChromiumCookieDecryptor } from '@/shared/chromium'
10
+
9
11
  import { ExtractedWorkspace, TokenExtractor } from './token-extractor'
10
12
 
11
13
  const tempDirs: string[] = []
14
+ const originalAgentBrowserProfile = process.env.AGENT_BROWSER_PROFILE
12
15
 
13
16
  afterEach(() => {
17
+ if (originalAgentBrowserProfile) {
18
+ process.env.AGENT_BROWSER_PROFILE = originalAgentBrowserProfile
19
+ } else {
20
+ delete process.env.AGENT_BROWSER_PROFILE
21
+ }
22
+
14
23
  for (const dir of tempDirs) {
15
24
  rmSync(dir, { recursive: true, force: true })
16
25
  }
@@ -802,6 +811,52 @@ describe('TokenExtractor browser fallback', () => {
802
811
  expect(result).toEqual([])
803
812
  })
804
813
 
814
+ it('resolves Local State from agent-browser profile root for encrypted cookies', async () => {
815
+ // given
816
+ const agentBrowserProfile = mkdtempSync(join(tmpdir(), 'agent-browser-slack-profile-'))
817
+ tempDirs.push(agentBrowserProfile)
818
+ process.env.AGENT_BROWSER_PROFILE = agentBrowserProfile
819
+
820
+ const hex64 = 'c'.repeat(64)
821
+ const token = `xoxc-1111111111-2222222222-3333333333-${hex64}`
822
+ const profileDir = join(agentBrowserProfile, 'Default')
823
+ const leveldbDir = join(profileDir, 'Local Storage', 'leveldb')
824
+ const networkDir = join(profileDir, 'Network')
825
+ mkdirSync(leveldbDir, { recursive: true })
826
+ mkdirSync(networkDir, { recursive: true })
827
+ writeFileSync(join(leveldbDir, '000001.log'), `"${token}"T12345678"name":"agent-browser-workspace"`)
828
+ writeFileSync(join(agentBrowserProfile, 'Local State'), '{}')
829
+ createCookiesDb(join(networkDir, 'Cookies'), [
830
+ {
831
+ name: 'd',
832
+ value: '',
833
+ encrypted_value: new Uint8Array([1, 2, 3]),
834
+ host_key: '.slack.com',
835
+ last_access_utc: 1,
836
+ },
837
+ ])
838
+ const decryptSpy = spyOn(ChromiumCookieDecryptor.prototype, 'decryptCookie').mockReturnValue('xoxd-AgentBrowser')
839
+
840
+ try {
841
+ // when
842
+ const extractor = new TokenExtractor('darwin', join(agentBrowserProfile, 'missing-desktop'))
843
+ const result = await extractor.extractFromBrowsers()
844
+
845
+ // then
846
+ expect(result).toEqual([
847
+ {
848
+ workspace_id: 'T12345678',
849
+ workspace_name: 'agent-browser-workspace',
850
+ token,
851
+ cookie: 'xoxd-AgentBrowser',
852
+ },
853
+ ])
854
+ expect(decryptSpy).toHaveBeenCalledWith(Buffer.from([1, 2, 3]), join(agentBrowserProfile, 'Local State'))
855
+ } finally {
856
+ decryptSpy.mockRestore()
857
+ }
858
+ })
859
+
805
860
  it('extract tries desktop before browser profiles', async () => {
806
861
  // given — slackDir with LevelDB token data
807
862
  const slackDir = mkdtempSync(join(tmpdir(), 'slack-fallback-desktop-'))
@@ -826,6 +881,47 @@ describe('TokenExtractor browser fallback', () => {
826
881
  extractFromBrowsersSpy.mockRestore()
827
882
  })
828
883
 
884
+ it('custom browser profile can upgrade desktop token with a cookie', async () => {
885
+ // given
886
+ const slackDir = mkdtempSync(join(tmpdir(), 'slack-custom-cookie-upgrade-'))
887
+ tempDirs.push(slackDir)
888
+
889
+ const hex64 = 'd'.repeat(64)
890
+ const token = `xoxc-1111111111-2222222222-3333333333-${hex64}`
891
+ const leveldbDir = join(slackDir, 'Local Storage', 'leveldb')
892
+ mkdirSync(leveldbDir, { recursive: true })
893
+ writeFileSync(join(leveldbDir, '000001.log'), `"${token}"T12345678"name":"desktop-workspace"`)
894
+
895
+ const browserWorkspace: ExtractedWorkspace = {
896
+ workspace_id: 'T12345678',
897
+ workspace_name: 'browser-workspace',
898
+ token,
899
+ cookie: 'xoxd-browser-cookie',
900
+ }
901
+ const extractFromBrowsersSpy = spyOn(TokenExtractor.prototype as any, 'extractFromBrowsers').mockResolvedValue([
902
+ browserWorkspace,
903
+ ])
904
+
905
+ try {
906
+ // when
907
+ const extractor = new TokenExtractor('darwin', slackDir, undefined, undefined, ['/tmp/custom-profile'])
908
+ const result = await extractor.extract()
909
+
910
+ // then
911
+ expect(extractFromBrowsersSpy).toHaveBeenCalled()
912
+ expect(result).toEqual([
913
+ {
914
+ workspace_id: 'T12345678',
915
+ workspace_name: 'desktop-workspace',
916
+ token,
917
+ cookie: 'xoxd-browser-cookie',
918
+ },
919
+ ])
920
+ } finally {
921
+ extractFromBrowsersSpy.mockRestore()
922
+ }
923
+ })
924
+
829
925
  it('extract falls back to browser profiles when desktop has no tokens', async () => {
830
926
  // given — empty slackDir (no tokens)
831
927
  const slackDir = mkdtempSync(join(tmpdir(), 'slack-fallback-browser-'))
@@ -12,7 +12,9 @@ import {
12
12
  ChromiumCookieDecryptor,
13
13
  ChromiumCookieReader,
14
14
  discoverBrowserProfileDirs,
15
+ findLocalStatePath,
15
16
  getBrowserBasePath,
17
+ getAgentBrowserProfileDirs,
16
18
  } from '@/shared/chromium'
17
19
  import { DerivedKeyCache } from '@/shared/utils/derived-key-cache'
18
20
  import { lookupLinuxKeyringPassword } from '@/shared/utils/linux-keyring'
@@ -63,12 +65,14 @@ export class TokenExtractor {
63
65
  private debugLog: ((message: string) => void) | null
64
66
  private browserDecryptor: ChromiumCookieDecryptor
65
67
  private browserCookieReader: ChromiumCookieReader
68
+ private customBrowserProfileDirs: string[]
66
69
 
67
70
  constructor(
68
71
  platform?: NodeJS.Platform,
69
72
  slackDir?: string,
70
73
  keyCache?: DerivedKeyCache,
71
74
  debugLog?: (message: string) => void,
75
+ customBrowserProfileDirs?: string[],
72
76
  ) {
73
77
  this.platform = platform ?? process.platform
74
78
 
@@ -79,6 +83,7 @@ export class TokenExtractor {
79
83
  this.slackDir = slackDir ?? this.getSlackDir()
80
84
  this.keyCache = keyCache ?? new DerivedKeyCache()
81
85
  this.debugLog = debugLog ?? null
86
+ this.customBrowserProfileDirs = customBrowserProfileDirs ?? []
82
87
  this.browserDecryptor = new ChromiumCookieDecryptor({ platform: this.platform })
83
88
  this.browserCookieReader = new ChromiumCookieReader()
84
89
  }
@@ -188,6 +193,9 @@ export class TokenExtractor {
188
193
  }
189
194
 
190
195
  async extract(): Promise<ExtractedWorkspace[]> {
196
+ const results: ExtractedWorkspace[] = []
197
+ const seenTokens = new Set<string>()
198
+
191
199
  if (existsSync(this.slackDir)) {
192
200
  await this.getDerivedKeyAsync()
193
201
 
@@ -201,27 +209,55 @@ export class TokenExtractor {
201
209
  if (this.cachedKey) {
202
210
  await this.keyCache.set('slack', this.cachedKey)
203
211
  const retryCookie = await this.extractCookieFromSQLite()
204
- return tokens.map((t) => ({
205
- workspace_id: t.teamId,
206
- workspace_name: t.teamName,
207
- token: t.token,
208
- cookie: retryCookie,
209
- }))
212
+ this.addExtractedWorkspaces(results, seenTokens, tokens, retryCookie)
213
+ return this.customBrowserProfileDirs.length > 0 ? this.mergeBrowserResults(results, seenTokens) : results
210
214
  }
211
215
  }
212
216
 
213
- return tokens.map((t) => ({
214
- workspace_id: t.teamId,
215
- workspace_name: t.teamName,
216
- token: t.token,
217
- cookie: cookie,
218
- }))
217
+ this.addExtractedWorkspaces(results, seenTokens, tokens, cookie)
218
+ return this.customBrowserProfileDirs.length > 0 ? this.mergeBrowserResults(results, seenTokens) : results
219
219
  }
220
220
  }
221
221
 
222
222
  return this.extractFromBrowsers()
223
223
  }
224
224
 
225
+ private async mergeBrowserResults(
226
+ results: ExtractedWorkspace[],
227
+ seenTokens: Set<string>,
228
+ ): Promise<ExtractedWorkspace[]> {
229
+ for (const workspace of await this.extractFromBrowsers()) {
230
+ const existing = results.find((item) => item.token === workspace.token)
231
+ if (existing) {
232
+ if (!existing.cookie && workspace.cookie) {
233
+ existing.cookie = workspace.cookie
234
+ }
235
+ continue
236
+ }
237
+ seenTokens.add(workspace.token)
238
+ results.push(workspace)
239
+ }
240
+ return results
241
+ }
242
+
243
+ private addExtractedWorkspaces(
244
+ results: ExtractedWorkspace[],
245
+ seenTokens: Set<string>,
246
+ tokens: TokenInfo[],
247
+ cookie: string,
248
+ ): void {
249
+ for (const token of tokens) {
250
+ if (seenTokens.has(token.token)) continue
251
+ seenTokens.add(token.token)
252
+ results.push({
253
+ workspace_id: token.teamId,
254
+ workspace_name: token.teamName,
255
+ token: token.token,
256
+ cookie,
257
+ })
258
+ }
259
+ }
260
+
225
261
  async extractFromBrowsers(): Promise<ExtractedWorkspace[]> {
226
262
  const results: ExtractedWorkspace[] = []
227
263
  const seenTokens = new Set<string>()
@@ -259,6 +295,33 @@ export class TokenExtractor {
259
295
  }
260
296
  }
261
297
 
298
+ for (const profileDir of getAgentBrowserProfileDirs({ customProfileDirs: this.customBrowserProfileDirs })) {
299
+ const leveldbDir = join(profileDir, 'Local Storage', 'leveldb')
300
+ if (!existsSync(leveldbDir)) continue
301
+
302
+ let tokenInfos: TokenInfo[]
303
+ try {
304
+ tokenInfos = await this.extractFromLevelDB(leveldbDir, 'local-storage')
305
+ } catch {
306
+ continue
307
+ }
308
+
309
+ if (tokenInfos.length === 0) continue
310
+
311
+ const cookie = await this.extractCookieFromBrowserProfile(profileDir, profileDir)
312
+
313
+ for (const t of tokenInfos) {
314
+ if (seenTokens.has(t.token)) continue
315
+ seenTokens.add(t.token)
316
+ results.push({
317
+ workspace_id: t.teamId,
318
+ workspace_name: t.teamName,
319
+ token: t.token,
320
+ cookie,
321
+ })
322
+ }
323
+ }
324
+
262
325
  return results
263
326
  }
264
327
 
@@ -293,7 +356,7 @@ export class TokenExtractor {
293
356
  if (row.value?.startsWith('xoxd-')) return row.value
294
357
 
295
358
  if (row.encrypted_value && row.encrypted_value.length > 0) {
296
- const localStatePath = join(browserBase, 'Local State')
359
+ const localStatePath = findLocalStatePath(cookiesPath) ?? join(browserBase, 'Local State')
297
360
  const decrypted = this.browserDecryptor.decryptCookie(
298
361
  Buffer.from(row.encrypted_value),
299
362
  existsSync(localStatePath) ? localStatePath : undefined,
@@ -6,6 +6,7 @@ import pkg from '../../../package.json' with { type: 'json' }
6
6
  import {
7
7
  authCommand,
8
8
  channelCommand,
9
+ fileCommand,
9
10
  messageCommand,
10
11
  reactionCommand,
11
12
  userCommand,
@@ -18,7 +19,17 @@ program
18
19
  .name('agent-slackbot')
19
20
  .description('CLI tool for Slack bot integration using bot tokens (xoxb-)')
20
21
  .version(pkg.version)
22
+ .option('--pretty', 'Pretty-print JSON output')
21
23
  .option('--bot <id>', 'Use specific bot (default: current)')
24
+ .hook('preAction', (thisCmd, actionCmd) => {
25
+ for (const [key, value] of Object.entries(thisCmd.opts())) {
26
+ if (value === undefined) continue
27
+ const source = actionCmd.getOptionValueSource(key)
28
+ if (source === undefined || source === 'default') {
29
+ actionCmd.setOptionValue(key, value)
30
+ }
31
+ }
32
+ })
22
33
 
23
34
  program.addCommand(authCommand)
24
35
  program.addCommand(whoamiCommand)
@@ -26,7 +37,8 @@ program.addCommand(messageCommand)
26
37
  program.addCommand(channelCommand)
27
38
  program.addCommand(userCommand)
28
39
  program.addCommand(reactionCommand)
40
+ program.addCommand(fileCommand)
29
41
 
30
- program.parse(process.argv)
42
+ program.parseAsync(process.argv)
31
43
 
32
44
  export default program
@@ -65,6 +65,70 @@ const mockReactions = {
65
65
  add: mock(() => Promise.resolve({ ok: true })),
66
66
  remove: mock(() => Promise.resolve({ ok: true })),
67
67
  }
68
+ const mockAssistant = {
69
+ threads: {
70
+ setStatus: mock(() => Promise.resolve({ ok: true })),
71
+ },
72
+ }
73
+ const mockFiles = {
74
+ uploadV2: mock(() =>
75
+ Promise.resolve({
76
+ ok: true,
77
+ files: [
78
+ {
79
+ ok: true,
80
+ files: [
81
+ {
82
+ id: 'F123',
83
+ name: 'test.txt',
84
+ title: 'test.txt',
85
+ mimetype: 'text/plain',
86
+ size: 12,
87
+ url_private: 'https://files.slack.com/files-pri/T123-F123/test.txt',
88
+ created: 1234567890,
89
+ user: 'U123',
90
+ channels: ['C123'],
91
+ },
92
+ ],
93
+ },
94
+ ],
95
+ }),
96
+ ),
97
+ list: mock(() =>
98
+ Promise.resolve({
99
+ ok: true,
100
+ files: [
101
+ {
102
+ id: 'F123',
103
+ name: 'test.txt',
104
+ title: 'test.txt',
105
+ mimetype: 'text/plain',
106
+ size: 1024,
107
+ url_private: 'https://files.slack.com/files-pri/T123-F123/test.txt',
108
+ created: 1234567890,
109
+ user: 'U123',
110
+ },
111
+ ],
112
+ }),
113
+ ),
114
+ info: mock(() =>
115
+ Promise.resolve({
116
+ ok: true,
117
+ file: {
118
+ id: 'F123',
119
+ name: 'test.txt',
120
+ title: 'test.txt',
121
+ mimetype: 'text/plain',
122
+ size: 1024,
123
+ url_private: 'https://files.slack.com/files-pri/T123-F123/test.txt',
124
+ created: 1234567890,
125
+ user: 'U123',
126
+ channels: ['C123'],
127
+ },
128
+ }),
129
+ ),
130
+ delete: mock(() => Promise.resolve({ ok: true })),
131
+ }
68
132
  const mockUsers = {
69
133
  list: mock(() =>
70
134
  Promise.resolve({
@@ -105,6 +169,8 @@ mock.module('@slack/web-api', () => ({
105
169
  chat = mockChat
106
170
  reactions = mockReactions
107
171
  users = mockUsers
172
+ files = mockFiles
173
+ assistant = mockAssistant
108
174
  },
109
175
  }))
110
176
 
@@ -120,6 +186,11 @@ describe('SlackBotClient', () => {
120
186
  mockReactions.remove.mockClear()
121
187
  mockUsers.list.mockClear()
122
188
  mockUsers.info.mockClear()
189
+ mockFiles.uploadV2.mockClear()
190
+ mockFiles.list.mockClear()
191
+ mockFiles.info.mockClear()
192
+ mockFiles.delete.mockClear()
193
+ mockAssistant.threads.setStatus.mockClear()
123
194
  })
124
195
 
125
196
  describe('login', () => {
@@ -224,6 +295,38 @@ describe('SlackBotClient', () => {
224
295
  })
225
296
  })
226
297
 
298
+ describe('setAssistantStatus', () => {
299
+ it('sets assistant typing status with channel_id and thread_ts', async () => {
300
+ // given
301
+ const client = await new SlackBotClient().login({ token: 'xoxb-test-token' })
302
+
303
+ // when
304
+ await client.setAssistantStatus('C123', '1234567890.123456', 'is typing...')
305
+
306
+ // then
307
+ expect(mockAssistant.threads.setStatus).toHaveBeenCalledWith({
308
+ channel_id: 'C123',
309
+ thread_ts: '1234567890.123456',
310
+ status: 'is typing...',
311
+ })
312
+ })
313
+
314
+ it('clears status when given empty string', async () => {
315
+ // given
316
+ const client = await new SlackBotClient().login({ token: 'xoxb-test-token' })
317
+
318
+ // when
319
+ await client.setAssistantStatus('C123', '1234567890.123456', '')
320
+
321
+ // then
322
+ expect(mockAssistant.threads.setStatus).toHaveBeenCalledWith({
323
+ channel_id: 'C123',
324
+ thread_ts: '1234567890.123456',
325
+ status: '',
326
+ })
327
+ })
328
+ })
329
+
227
330
  describe('listChannels', () => {
228
331
  it('returns list of channels', async () => {
229
332
  // given
@@ -367,4 +470,175 @@ describe('SlackBotClient', () => {
367
470
  expect(user.name).toBe('testuser')
368
471
  })
369
472
  })
473
+
474
+ describe('uploadFile', () => {
475
+ it('uploads file via files.uploadV2 and returns mapped file', async () => {
476
+ // given
477
+ const client = await new SlackBotClient().login({ token: 'xoxb-test-token' })
478
+
479
+ // when
480
+ const file = await client.uploadFile('C123', Buffer.from('test content'), 'test.txt')
481
+
482
+ // then
483
+ expect(file.id).toBe('F123')
484
+ expect(file.name).toBe('test.txt')
485
+ expect(file.size).toBe(12)
486
+ expect(mockFiles.uploadV2).toHaveBeenCalledWith(
487
+ expect.objectContaining({ channel_id: 'C123', filename: 'test.txt' }),
488
+ )
489
+ })
490
+
491
+ it('forwards thread_ts, title, initial_comment to the SDK', async () => {
492
+ // given
493
+ const client = await new SlackBotClient().login({ token: 'xoxb-test-token' })
494
+
495
+ // when
496
+ await client.uploadFile('C123', Buffer.from('x'), 'test.txt', {
497
+ thread_ts: '1234567890.123456',
498
+ title: 'My Title',
499
+ initial_comment: 'Here you go',
500
+ })
501
+
502
+ // then
503
+ expect(mockFiles.uploadV2).toHaveBeenCalledWith(
504
+ expect.objectContaining({
505
+ channel_id: 'C123',
506
+ filename: 'test.txt',
507
+ thread_ts: '1234567890.123456',
508
+ title: 'My Title',
509
+ initial_comment: 'Here you go',
510
+ }),
511
+ )
512
+ })
513
+
514
+ it('throws SlackBotError when API responds not ok', async () => {
515
+ // given
516
+ mockFiles.uploadV2.mockResolvedValueOnce({ ok: false, error: 'file_upload_failed' } as any)
517
+ const client = await new SlackBotClient().login({ token: 'xoxb-test-token' })
518
+
519
+ // when/then
520
+ await expect(client.uploadFile('C123', Buffer.from('x'), 'test.txt')).rejects.toThrow(SlackBotError)
521
+ })
522
+
523
+ it('throws SlackBotError when completion has no inner files', async () => {
524
+ // given
525
+ mockFiles.uploadV2.mockResolvedValueOnce({ ok: true, files: [{ ok: true, files: [] }] } as any)
526
+ const client = await new SlackBotClient().login({ token: 'xoxb-test-token' })
527
+
528
+ // when/then
529
+ await expect(client.uploadFile('C123', Buffer.from('x'), 'test.txt')).rejects.toThrow(SlackBotError)
530
+ })
531
+ })
532
+
533
+ describe('listFiles', () => {
534
+ it('returns mapped files', async () => {
535
+ // given
536
+ const client = await new SlackBotClient().login({ token: 'xoxb-test-token' })
537
+
538
+ // when
539
+ const files = await client.listFiles()
540
+
541
+ // then
542
+ expect(files).toHaveLength(1)
543
+ expect(files[0].id).toBe('F123')
544
+ expect(files[0].mimetype).toBe('text/plain')
545
+ })
546
+
547
+ it('forwards channel/user/limit to the SDK', async () => {
548
+ // given
549
+ const client = await new SlackBotClient().login({ token: 'xoxb-test-token' })
550
+
551
+ // when
552
+ await client.listFiles({ channel: 'C123', user: 'U123', limit: 50 })
553
+
554
+ // then
555
+ expect(mockFiles.list).toHaveBeenCalledWith({ channel: 'C123', user: 'U123', count: 50 })
556
+ })
557
+ })
558
+
559
+ describe('getFileInfo', () => {
560
+ it('returns file metadata', async () => {
561
+ // given
562
+ const client = await new SlackBotClient().login({ token: 'xoxb-test-token' })
563
+
564
+ // when
565
+ const file = await client.getFileInfo('F123')
566
+
567
+ // then
568
+ expect(file.id).toBe('F123')
569
+ expect(file.url_private).toBe('https://files.slack.com/files-pri/T123-F123/test.txt')
570
+ })
571
+
572
+ it('throws on API failure', async () => {
573
+ // given
574
+ mockFiles.info.mockResolvedValueOnce({ ok: false, error: 'file_not_found' } as any)
575
+ const client = await new SlackBotClient().login({ token: 'xoxb-test-token' })
576
+
577
+ // when/then
578
+ await expect(client.getFileInfo('F999')).rejects.toThrow(SlackBotError)
579
+ })
580
+ })
581
+
582
+ describe('downloadFile', () => {
583
+ it('downloads file content using the bot token', async () => {
584
+ // given
585
+ const originalFetch = globalThis.fetch
586
+ let capturedAuthHeader: string | null = null
587
+ globalThis.fetch = async (_url: any, init: any = {}) => {
588
+ capturedAuthHeader = init?.headers?.Authorization ?? null
589
+ return new Response(Buffer.from('downloaded content'), { status: 200 })
590
+ }
591
+
592
+ try {
593
+ const client = await new SlackBotClient().login({ token: 'xoxb-test-token' })
594
+
595
+ // when
596
+ const result = await client.downloadFile('F123')
597
+
598
+ // then
599
+ expect(capturedAuthHeader).toBe('Bearer xoxb-test-token')
600
+ expect(result.file.id).toBe('F123')
601
+ expect(result.buffer.toString()).toBe('downloaded content')
602
+ } finally {
603
+ globalThis.fetch = originalFetch
604
+ }
605
+ })
606
+
607
+ it('throws SlackBotError when download HTTP response is not ok', async () => {
608
+ // given
609
+ const originalFetch = globalThis.fetch
610
+ globalThis.fetch = async () => new Response('forbidden', { status: 403, statusText: 'Forbidden' })
611
+
612
+ try {
613
+ const client = await new SlackBotClient().login({ token: 'xoxb-test-token' })
614
+
615
+ // when/then
616
+ await expect(client.downloadFile('F123')).rejects.toThrow(SlackBotError)
617
+ } finally {
618
+ globalThis.fetch = originalFetch
619
+ }
620
+ })
621
+ })
622
+
623
+ describe('deleteFile', () => {
624
+ it('calls files.delete with the given file ID', async () => {
625
+ // given
626
+ const client = await new SlackBotClient().login({ token: 'xoxb-test-token' })
627
+
628
+ // when
629
+ await client.deleteFile('F123')
630
+
631
+ // then
632
+ expect(mockFiles.delete).toHaveBeenCalledWith({ file: 'F123' })
633
+ })
634
+
635
+ it('throws on API failure', async () => {
636
+ // given
637
+ mockFiles.delete.mockResolvedValueOnce({ ok: false, error: 'cant_delete_file' } as any)
638
+ const client = await new SlackBotClient().login({ token: 'xoxb-test-token' })
639
+
640
+ // when/then
641
+ await expect(client.deleteFile('F123')).rejects.toThrow(SlackBotError)
642
+ })
643
+ })
370
644
  })