agent-messenger 1.0.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 (394) hide show
  1. package/.claude/commands/release.md +92 -0
  2. package/.claude-plugin/README.md +144 -0
  3. package/.claude-plugin/marketplace.json +37 -0
  4. package/.claude-plugin/plugin.json +17 -0
  5. package/.github/workflows/ci.yml +30 -0
  6. package/CLAUDE.md +106 -0
  7. package/CONTRIBUTING.md +131 -0
  8. package/README.md +140 -0
  9. package/biome.json +34 -0
  10. package/bun.lock +252 -0
  11. package/dist/cli.d.ts +5 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +21 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/commands/auth.d.ts +3 -0
  16. package/dist/commands/auth.d.ts.map +1 -0
  17. package/dist/commands/auth.js +140 -0
  18. package/dist/commands/auth.js.map +1 -0
  19. package/dist/commands/channel.d.ts +3 -0
  20. package/dist/commands/channel.d.ts.map +1 -0
  21. package/dist/commands/channel.js +118 -0
  22. package/dist/commands/channel.js.map +1 -0
  23. package/dist/commands/file.d.ts +3 -0
  24. package/dist/commands/file.d.ts.map +1 -0
  25. package/dist/commands/file.js +113 -0
  26. package/dist/commands/file.js.map +1 -0
  27. package/dist/commands/index.d.ts +9 -0
  28. package/dist/commands/index.d.ts.map +1 -0
  29. package/dist/commands/index.js +9 -0
  30. package/dist/commands/index.js.map +1 -0
  31. package/dist/commands/message.d.ts +3 -0
  32. package/dist/commands/message.d.ts.map +1 -0
  33. package/dist/commands/message.js +214 -0
  34. package/dist/commands/message.js.map +1 -0
  35. package/dist/commands/reaction.d.ts +3 -0
  36. package/dist/commands/reaction.d.ts.map +1 -0
  37. package/dist/commands/reaction.js +100 -0
  38. package/dist/commands/reaction.js.map +1 -0
  39. package/dist/commands/snapshot.d.ts +3 -0
  40. package/dist/commands/snapshot.d.ts.map +1 -0
  41. package/dist/commands/snapshot.js +88 -0
  42. package/dist/commands/snapshot.js.map +1 -0
  43. package/dist/commands/user.d.ts +3 -0
  44. package/dist/commands/user.d.ts.map +1 -0
  45. package/dist/commands/user.js +96 -0
  46. package/dist/commands/user.js.map +1 -0
  47. package/dist/commands/workspace.d.ts +3 -0
  48. package/dist/commands/workspace.d.ts.map +1 -0
  49. package/dist/commands/workspace.js +89 -0
  50. package/dist/commands/workspace.js.map +1 -0
  51. package/dist/lib/credential-manager.d.ts +13 -0
  52. package/dist/lib/credential-manager.d.ts.map +1 -0
  53. package/dist/lib/credential-manager.js +58 -0
  54. package/dist/lib/credential-manager.js.map +1 -0
  55. package/dist/lib/index.d.ts +3 -0
  56. package/dist/lib/index.d.ts.map +1 -0
  57. package/dist/lib/index.js +3 -0
  58. package/dist/lib/index.js.map +1 -0
  59. package/dist/lib/ref-manager.d.ts +26 -0
  60. package/dist/lib/ref-manager.d.ts.map +1 -0
  61. package/dist/lib/ref-manager.js +92 -0
  62. package/dist/lib/ref-manager.js.map +1 -0
  63. package/dist/lib/slack-client.d.ts +37 -0
  64. package/dist/lib/slack-client.d.ts.map +1 -0
  65. package/dist/lib/slack-client.js +379 -0
  66. package/dist/lib/slack-client.js.map +1 -0
  67. package/dist/lib/token-extractor.d.ts +28 -0
  68. package/dist/lib/token-extractor.d.ts.map +1 -0
  69. package/dist/lib/token-extractor.js +401 -0
  70. package/dist/lib/token-extractor.js.map +1 -0
  71. package/dist/package.json +37 -0
  72. package/dist/src/cli.d.ts +5 -0
  73. package/dist/src/cli.d.ts.map +1 -0
  74. package/dist/src/cli.js +22 -0
  75. package/dist/src/cli.js.map +1 -0
  76. package/dist/src/platforms/discord/cli.d.ts +5 -0
  77. package/dist/src/platforms/discord/cli.d.ts.map +1 -0
  78. package/dist/src/platforms/discord/cli.js +22 -0
  79. package/dist/src/platforms/discord/cli.js.map +1 -0
  80. package/dist/src/platforms/discord/client.d.ts +34 -0
  81. package/dist/src/platforms/discord/client.d.ts.map +1 -0
  82. package/dist/src/platforms/discord/client.js +187 -0
  83. package/dist/src/platforms/discord/client.js.map +1 -0
  84. package/dist/src/platforms/discord/client.test.d.ts +2 -0
  85. package/dist/src/platforms/discord/client.test.d.ts.map +1 -0
  86. package/dist/src/platforms/discord/client.test.js +367 -0
  87. package/dist/src/platforms/discord/client.test.js.map +1 -0
  88. package/dist/src/platforms/discord/commands/auth.d.ts +13 -0
  89. package/dist/src/platforms/discord/commands/auth.d.ts.map +1 -0
  90. package/dist/src/platforms/discord/commands/auth.js +155 -0
  91. package/dist/src/platforms/discord/commands/auth.js.map +1 -0
  92. package/dist/src/platforms/discord/commands/auth.test.d.ts +2 -0
  93. package/dist/src/platforms/discord/commands/auth.test.d.ts.map +1 -0
  94. package/dist/src/platforms/discord/commands/auth.test.js +65 -0
  95. package/dist/src/platforms/discord/commands/auth.test.js.map +1 -0
  96. package/dist/src/platforms/discord/commands/channel.d.ts +13 -0
  97. package/dist/src/platforms/discord/commands/channel.d.ts.map +1 -0
  98. package/dist/src/platforms/discord/commands/channel.js +99 -0
  99. package/dist/src/platforms/discord/commands/channel.js.map +1 -0
  100. package/dist/src/platforms/discord/commands/channel.test.d.ts +2 -0
  101. package/dist/src/platforms/discord/commands/channel.test.d.ts.map +1 -0
  102. package/dist/src/platforms/discord/commands/channel.test.js +136 -0
  103. package/dist/src/platforms/discord/commands/channel.test.js.map +1 -0
  104. package/dist/src/platforms/discord/commands/file.d.ts +13 -0
  105. package/dist/src/platforms/discord/commands/file.d.ts.map +1 -0
  106. package/dist/src/platforms/discord/commands/file.js +99 -0
  107. package/dist/src/platforms/discord/commands/file.js.map +1 -0
  108. package/dist/src/platforms/discord/commands/file.test.d.ts +2 -0
  109. package/dist/src/platforms/discord/commands/file.test.d.ts.map +1 -0
  110. package/dist/src/platforms/discord/commands/file.test.js +83 -0
  111. package/dist/src/platforms/discord/commands/file.test.js.map +1 -0
  112. package/dist/src/platforms/discord/commands/guild.d.ts +15 -0
  113. package/dist/src/platforms/discord/commands/guild.d.ts.map +1 -0
  114. package/dist/src/platforms/discord/commands/guild.js +102 -0
  115. package/dist/src/platforms/discord/commands/guild.js.map +1 -0
  116. package/dist/src/platforms/discord/commands/guild.test.d.ts +2 -0
  117. package/dist/src/platforms/discord/commands/guild.test.d.ts.map +1 -0
  118. package/dist/src/platforms/discord/commands/guild.test.js +100 -0
  119. package/dist/src/platforms/discord/commands/guild.test.js.map +1 -0
  120. package/dist/src/platforms/discord/commands/index.d.ts +9 -0
  121. package/dist/src/platforms/discord/commands/index.d.ts.map +1 -0
  122. package/dist/src/platforms/discord/commands/index.js +9 -0
  123. package/dist/src/platforms/discord/commands/index.js.map +1 -0
  124. package/dist/src/platforms/discord/commands/message.d.ts +17 -0
  125. package/dist/src/platforms/discord/commands/message.d.ts.map +1 -0
  126. package/dist/src/platforms/discord/commands/message.js +131 -0
  127. package/dist/src/platforms/discord/commands/message.js.map +1 -0
  128. package/dist/src/platforms/discord/commands/message.test.d.ts +2 -0
  129. package/dist/src/platforms/discord/commands/message.test.d.ts.map +1 -0
  130. package/dist/src/platforms/discord/commands/message.test.js +91 -0
  131. package/dist/src/platforms/discord/commands/message.test.js.map +1 -0
  132. package/dist/src/platforms/discord/commands/reaction.d.ts +12 -0
  133. package/dist/src/platforms/discord/commands/reaction.d.ts.map +1 -0
  134. package/dist/src/platforms/discord/commands/reaction.js +99 -0
  135. package/dist/src/platforms/discord/commands/reaction.js.map +1 -0
  136. package/dist/src/platforms/discord/commands/reaction.test.d.ts +2 -0
  137. package/dist/src/platforms/discord/commands/reaction.test.d.ts.map +1 -0
  138. package/dist/src/platforms/discord/commands/reaction.test.js +115 -0
  139. package/dist/src/platforms/discord/commands/reaction.test.js.map +1 -0
  140. package/dist/src/platforms/discord/commands/snapshot.d.ts +9 -0
  141. package/dist/src/platforms/discord/commands/snapshot.d.ts.map +1 -0
  142. package/dist/src/platforms/discord/commands/snapshot.js +80 -0
  143. package/dist/src/platforms/discord/commands/snapshot.js.map +1 -0
  144. package/dist/src/platforms/discord/commands/snapshot.test.d.ts +2 -0
  145. package/dist/src/platforms/discord/commands/snapshot.test.d.ts.map +1 -0
  146. package/dist/src/platforms/discord/commands/snapshot.test.js +25 -0
  147. package/dist/src/platforms/discord/commands/snapshot.test.js.map +1 -0
  148. package/dist/src/platforms/discord/commands/user.d.ts +3 -0
  149. package/dist/src/platforms/discord/commands/user.d.ts.map +1 -0
  150. package/dist/src/platforms/discord/commands/user.js +94 -0
  151. package/dist/src/platforms/discord/commands/user.js.map +1 -0
  152. package/dist/src/platforms/discord/commands/user.test.d.ts +2 -0
  153. package/dist/src/platforms/discord/commands/user.test.d.ts.map +1 -0
  154. package/dist/src/platforms/discord/commands/user.test.js +103 -0
  155. package/dist/src/platforms/discord/commands/user.test.js.map +1 -0
  156. package/dist/src/platforms/discord/credential-manager.d.ts +33 -0
  157. package/dist/src/platforms/discord/credential-manager.d.ts.map +1 -0
  158. package/dist/src/platforms/discord/credential-manager.js +73 -0
  159. package/dist/src/platforms/discord/credential-manager.js.map +1 -0
  160. package/dist/src/platforms/discord/credential-manager.test.d.ts +2 -0
  161. package/dist/src/platforms/discord/credential-manager.test.d.ts.map +1 -0
  162. package/dist/src/platforms/discord/credential-manager.test.js +136 -0
  163. package/dist/src/platforms/discord/credential-manager.test.js.map +1 -0
  164. package/dist/src/platforms/discord/token-extractor.d.ts +55 -0
  165. package/dist/src/platforms/discord/token-extractor.d.ts.map +1 -0
  166. package/dist/src/platforms/discord/token-extractor.js +462 -0
  167. package/dist/src/platforms/discord/token-extractor.js.map +1 -0
  168. package/dist/src/platforms/discord/token-extractor.test.d.ts +2 -0
  169. package/dist/src/platforms/discord/token-extractor.test.d.ts.map +1 -0
  170. package/dist/src/platforms/discord/token-extractor.test.js +789 -0
  171. package/dist/src/platforms/discord/token-extractor.test.js.map +1 -0
  172. package/dist/src/platforms/discord/types.d.ts +251 -0
  173. package/dist/src/platforms/discord/types.d.ts.map +1 -0
  174. package/dist/src/platforms/discord/types.js +74 -0
  175. package/dist/src/platforms/discord/types.js.map +1 -0
  176. package/dist/src/platforms/discord/types.test.d.ts +2 -0
  177. package/dist/src/platforms/discord/types.test.d.ts.map +1 -0
  178. package/dist/src/platforms/discord/types.test.js +211 -0
  179. package/dist/src/platforms/discord/types.test.js.map +1 -0
  180. package/dist/src/platforms/slack/cli.d.ts +5 -0
  181. package/dist/src/platforms/slack/cli.d.ts.map +1 -0
  182. package/dist/src/platforms/slack/cli.js +22 -0
  183. package/dist/src/platforms/slack/cli.js.map +1 -0
  184. package/dist/src/platforms/slack/client.d.ts +47 -0
  185. package/dist/src/platforms/slack/client.d.ts.map +1 -0
  186. package/dist/src/platforms/slack/client.js +412 -0
  187. package/dist/src/platforms/slack/client.js.map +1 -0
  188. package/dist/src/platforms/slack/commands/auth.d.ts +3 -0
  189. package/dist/src/platforms/slack/commands/auth.d.ts.map +1 -0
  190. package/dist/src/platforms/slack/commands/auth.js +156 -0
  191. package/dist/src/platforms/slack/commands/auth.js.map +1 -0
  192. package/dist/src/platforms/slack/commands/channel.d.ts +3 -0
  193. package/dist/src/platforms/slack/commands/channel.d.ts.map +1 -0
  194. package/dist/src/platforms/slack/commands/channel.js +118 -0
  195. package/dist/src/platforms/slack/commands/channel.js.map +1 -0
  196. package/dist/src/platforms/slack/commands/file.d.ts +3 -0
  197. package/dist/src/platforms/slack/commands/file.d.ts.map +1 -0
  198. package/dist/src/platforms/slack/commands/file.js +113 -0
  199. package/dist/src/platforms/slack/commands/file.js.map +1 -0
  200. package/dist/src/platforms/slack/commands/index.d.ts +9 -0
  201. package/dist/src/platforms/slack/commands/index.d.ts.map +1 -0
  202. package/dist/src/platforms/slack/commands/index.js +9 -0
  203. package/dist/src/platforms/slack/commands/index.js.map +1 -0
  204. package/dist/src/platforms/slack/commands/message.d.ts +3 -0
  205. package/dist/src/platforms/slack/commands/message.d.ts.map +1 -0
  206. package/dist/src/platforms/slack/commands/message.js +263 -0
  207. package/dist/src/platforms/slack/commands/message.js.map +1 -0
  208. package/dist/src/platforms/slack/commands/reaction.d.ts +3 -0
  209. package/dist/src/platforms/slack/commands/reaction.d.ts.map +1 -0
  210. package/dist/src/platforms/slack/commands/reaction.js +100 -0
  211. package/dist/src/platforms/slack/commands/reaction.js.map +1 -0
  212. package/dist/src/platforms/slack/commands/snapshot.d.ts +3 -0
  213. package/dist/src/platforms/slack/commands/snapshot.d.ts.map +1 -0
  214. package/dist/src/platforms/slack/commands/snapshot.js +87 -0
  215. package/dist/src/platforms/slack/commands/snapshot.js.map +1 -0
  216. package/dist/src/platforms/slack/commands/user.d.ts +3 -0
  217. package/dist/src/platforms/slack/commands/user.d.ts.map +1 -0
  218. package/dist/src/platforms/slack/commands/user.js +96 -0
  219. package/dist/src/platforms/slack/commands/user.js.map +1 -0
  220. package/dist/src/platforms/slack/commands/workspace.d.ts +3 -0
  221. package/dist/src/platforms/slack/commands/workspace.d.ts.map +1 -0
  222. package/dist/src/platforms/slack/commands/workspace.js +89 -0
  223. package/dist/src/platforms/slack/commands/workspace.js.map +1 -0
  224. package/dist/src/platforms/slack/credential-manager.d.ts +13 -0
  225. package/dist/src/platforms/slack/credential-manager.d.ts.map +1 -0
  226. package/dist/src/platforms/slack/credential-manager.js +58 -0
  227. package/dist/src/platforms/slack/credential-manager.js.map +1 -0
  228. package/dist/src/platforms/slack/index.d.ts +3 -0
  229. package/dist/src/platforms/slack/index.d.ts.map +1 -0
  230. package/dist/src/platforms/slack/index.js +3 -0
  231. package/dist/src/platforms/slack/index.js.map +1 -0
  232. package/dist/src/platforms/slack/token-extractor.d.ts +28 -0
  233. package/dist/src/platforms/slack/token-extractor.d.ts.map +1 -0
  234. package/dist/src/platforms/slack/token-extractor.js +401 -0
  235. package/dist/src/platforms/slack/token-extractor.js.map +1 -0
  236. package/dist/src/platforms/slack/types.d.ts +369 -0
  237. package/dist/src/platforms/slack/types.d.ts.map +1 -0
  238. package/dist/src/platforms/slack/types.js +92 -0
  239. package/dist/src/platforms/slack/types.js.map +1 -0
  240. package/dist/src/shared/utils/concurrency.d.ts +2 -0
  241. package/dist/src/shared/utils/concurrency.d.ts.map +1 -0
  242. package/dist/src/shared/utils/concurrency.js +14 -0
  243. package/dist/src/shared/utils/concurrency.js.map +1 -0
  244. package/dist/src/shared/utils/concurrency.test.d.ts +2 -0
  245. package/dist/src/shared/utils/concurrency.test.d.ts.map +1 -0
  246. package/dist/src/shared/utils/concurrency.test.js +39 -0
  247. package/dist/src/shared/utils/concurrency.test.js.map +1 -0
  248. package/dist/src/shared/utils/error-handler.d.ts +2 -0
  249. package/dist/src/shared/utils/error-handler.d.ts.map +1 -0
  250. package/dist/src/shared/utils/error-handler.js +5 -0
  251. package/dist/src/shared/utils/error-handler.js.map +1 -0
  252. package/dist/src/shared/utils/output.d.ts +2 -0
  253. package/dist/src/shared/utils/output.d.ts.map +1 -0
  254. package/dist/src/shared/utils/output.js +4 -0
  255. package/dist/src/shared/utils/output.js.map +1 -0
  256. package/dist/tests/cli.test.d.ts +2 -0
  257. package/dist/tests/cli.test.d.ts.map +1 -0
  258. package/dist/tests/cli.test.js +83 -0
  259. package/dist/tests/cli.test.js.map +1 -0
  260. package/dist/tests/commands/.test-slack-data/Local Storage/leveldb/CURRENT +1 -0
  261. package/dist/tests/commands/.test-slack-data/Local Storage/leveldb/LOCK +0 -0
  262. package/dist/tests/commands/.test-slack-data/Local Storage/leveldb/LOG +3 -0
  263. package/dist/tests/commands/.test-slack-data/Local Storage/leveldb/LOG.old +1 -0
  264. package/dist/tests/commands/.test-slack-data/Local Storage/leveldb/MANIFEST-000004 +0 -0
  265. package/dist/tests/commands/auth.test.d.ts +2 -0
  266. package/dist/tests/commands/auth.test.d.ts.map +1 -0
  267. package/dist/tests/commands/auth.test.js +304 -0
  268. package/dist/tests/commands/auth.test.js.map +1 -0
  269. package/dist/tests/commands/channel.test.d.ts +2 -0
  270. package/dist/tests/commands/channel.test.d.ts.map +1 -0
  271. package/dist/tests/commands/channel.test.js +166 -0
  272. package/dist/tests/commands/channel.test.js.map +1 -0
  273. package/dist/tests/commands/file.test.d.ts +2 -0
  274. package/dist/tests/commands/file.test.d.ts.map +1 -0
  275. package/dist/tests/commands/file.test.js +175 -0
  276. package/dist/tests/commands/file.test.js.map +1 -0
  277. package/dist/tests/commands/message.test.d.ts +2 -0
  278. package/dist/tests/commands/message.test.d.ts.map +1 -0
  279. package/dist/tests/commands/message.test.js +293 -0
  280. package/dist/tests/commands/message.test.js.map +1 -0
  281. package/dist/tests/commands/reaction.test.d.ts +2 -0
  282. package/dist/tests/commands/reaction.test.d.ts.map +1 -0
  283. package/dist/tests/commands/reaction.test.js +84 -0
  284. package/dist/tests/commands/reaction.test.js.map +1 -0
  285. package/dist/tests/commands/snapshot.test.d.ts +2 -0
  286. package/dist/tests/commands/snapshot.test.d.ts.map +1 -0
  287. package/dist/tests/commands/snapshot.test.js +280 -0
  288. package/dist/tests/commands/snapshot.test.js.map +1 -0
  289. package/dist/tests/commands/user.test.d.ts +2 -0
  290. package/dist/tests/commands/user.test.d.ts.map +1 -0
  291. package/dist/tests/commands/user.test.js +117 -0
  292. package/dist/tests/commands/user.test.js.map +1 -0
  293. package/dist/tests/commands/workspace.test.d.ts +2 -0
  294. package/dist/tests/commands/workspace.test.d.ts.map +1 -0
  295. package/dist/tests/commands/workspace.test.js +453 -0
  296. package/dist/tests/commands/workspace.test.js.map +1 -0
  297. package/dist/tests/credential-manager.test.d.ts +2 -0
  298. package/dist/tests/credential-manager.test.d.ts.map +1 -0
  299. package/dist/tests/credential-manager.test.js +199 -0
  300. package/dist/tests/credential-manager.test.js.map +1 -0
  301. package/dist/tests/slack-client.test.d.ts +2 -0
  302. package/dist/tests/slack-client.test.d.ts.map +1 -0
  303. package/dist/tests/slack-client.test.js +741 -0
  304. package/dist/tests/slack-client.test.js.map +1 -0
  305. package/dist/tests/types.test.d.ts +2 -0
  306. package/dist/tests/types.test.d.ts.map +1 -0
  307. package/dist/tests/types.test.js +215 -0
  308. package/dist/tests/types.test.js.map +1 -0
  309. package/dist/types/index.d.ts +369 -0
  310. package/dist/types/index.d.ts.map +1 -0
  311. package/dist/types/index.js +92 -0
  312. package/dist/types/index.js.map +1 -0
  313. package/dist/utils/error-handler.d.ts +2 -0
  314. package/dist/utils/error-handler.d.ts.map +1 -0
  315. package/dist/utils/error-handler.js +5 -0
  316. package/dist/utils/error-handler.js.map +1 -0
  317. package/dist/utils/output.d.ts +2 -0
  318. package/dist/utils/output.d.ts.map +1 -0
  319. package/dist/utils/output.js +4 -0
  320. package/dist/utils/output.js.map +1 -0
  321. package/docs/discord.md +182 -0
  322. package/docs/slack.md +160 -0
  323. package/package.json +37 -0
  324. package/skills/agent-discord/SKILL.md +273 -0
  325. package/skills/agent-discord/references/authentication.md +294 -0
  326. package/skills/agent-discord/references/common-patterns.md +455 -0
  327. package/skills/agent-discord/templates/guild-summary.sh +167 -0
  328. package/skills/agent-discord/templates/monitor-channel.sh +180 -0
  329. package/skills/agent-discord/templates/post-message.sh +173 -0
  330. package/skills/agent-slack/SKILL.md +268 -0
  331. package/skills/agent-slack/references/authentication.md +332 -0
  332. package/skills/agent-slack/references/common-patterns.md +527 -0
  333. package/skills/agent-slack/templates/monitor-channel.sh +186 -0
  334. package/skills/agent-slack/templates/post-message.sh +130 -0
  335. package/skills/agent-slack/templates/workspace-summary.sh +149 -0
  336. package/src/cli.ts +29 -0
  337. package/src/platforms/discord/cli.ts +36 -0
  338. package/src/platforms/discord/client.test.ts +456 -0
  339. package/src/platforms/discord/client.ts +281 -0
  340. package/src/platforms/discord/commands/auth.test.ts +72 -0
  341. package/src/platforms/discord/commands/auth.ts +206 -0
  342. package/src/platforms/discord/commands/channel.test.ts +153 -0
  343. package/src/platforms/discord/commands/channel.ts +127 -0
  344. package/src/platforms/discord/commands/file.test.ts +98 -0
  345. package/src/platforms/discord/commands/file.ts +134 -0
  346. package/src/platforms/discord/commands/guild.test.ts +117 -0
  347. package/src/platforms/discord/commands/guild.ts +129 -0
  348. package/src/platforms/discord/commands/index.ts +8 -0
  349. package/src/platforms/discord/commands/message.test.ts +107 -0
  350. package/src/platforms/discord/commands/message.ts +182 -0
  351. package/src/platforms/discord/commands/reaction.test.ts +123 -0
  352. package/src/platforms/discord/commands/reaction.ts +156 -0
  353. package/src/platforms/discord/commands/snapshot.test.ts +29 -0
  354. package/src/platforms/discord/commands/snapshot.ts +104 -0
  355. package/src/platforms/discord/commands/user.test.ts +115 -0
  356. package/src/platforms/discord/commands/user.ts +124 -0
  357. package/src/platforms/discord/credential-manager.test.ts +173 -0
  358. package/src/platforms/discord/credential-manager.ts +95 -0
  359. package/src/platforms/discord/token-extractor.test.ts +918 -0
  360. package/src/platforms/discord/token-extractor.ts +549 -0
  361. package/src/platforms/discord/types.test.ts +245 -0
  362. package/src/platforms/discord/types.ts +158 -0
  363. package/src/platforms/slack/cli.ts +36 -0
  364. package/src/platforms/slack/client.ts +466 -0
  365. package/src/platforms/slack/commands/auth.ts +211 -0
  366. package/src/platforms/slack/commands/channel.ts +158 -0
  367. package/src/platforms/slack/commands/file.ts +153 -0
  368. package/src/platforms/slack/commands/index.ts +8 -0
  369. package/src/platforms/slack/commands/message.ts +369 -0
  370. package/src/platforms/slack/commands/reaction.ts +166 -0
  371. package/src/platforms/slack/commands/snapshot.ts +114 -0
  372. package/src/platforms/slack/commands/user.ts +110 -0
  373. package/src/platforms/slack/commands/workspace.ts +111 -0
  374. package/src/platforms/slack/credential-manager.ts +74 -0
  375. package/src/platforms/slack/index.ts +2 -0
  376. package/src/platforms/slack/token-extractor.ts +496 -0
  377. package/src/platforms/slack/types.ts +193 -0
  378. package/src/shared/utils/concurrency.test.ts +53 -0
  379. package/src/shared/utils/concurrency.ts +20 -0
  380. package/src/shared/utils/error-handler.ts +4 -0
  381. package/src/shared/utils/output.ts +3 -0
  382. package/tests/cli.test.ts +94 -0
  383. package/tests/commands/auth.test.ts +383 -0
  384. package/tests/commands/channel.test.ts +185 -0
  385. package/tests/commands/file.test.ts +204 -0
  386. package/tests/commands/message.test.ts +344 -0
  387. package/tests/commands/reaction.test.ts +101 -0
  388. package/tests/commands/snapshot.test.ts +308 -0
  389. package/tests/commands/user.test.ts +138 -0
  390. package/tests/commands/workspace.test.ts +528 -0
  391. package/tests/credential-manager.test.ts +241 -0
  392. package/tests/slack-client.test.ts +916 -0
  393. package/tests/types.test.ts +241 -0
  394. package/tsconfig.json +36 -0
@@ -0,0 +1,916 @@
1
+ import { beforeEach, describe, expect, mock, test } from 'bun:test'
2
+ import type { WebClient } from '@slack/web-api'
3
+ import { SlackClient, SlackError } from '../src/platforms/slack/client'
4
+
5
+ const mockWebClient: any = {
6
+ conversations: {
7
+ list: mock((): Promise<any> => Promise.resolve({ ok: true, channels: [] })),
8
+ info: mock((): Promise<any> => Promise.resolve({ ok: true, channel: {} })),
9
+ history: mock((): Promise<any> => Promise.resolve({ ok: true, messages: [] })),
10
+ replies: mock((): Promise<any> => Promise.resolve({ ok: true, messages: [], has_more: false })),
11
+ },
12
+ chat: {
13
+ postMessage: mock(
14
+ (): Promise<any> => Promise.resolve({ ok: true, ts: '123.456', message: {} })
15
+ ),
16
+ update: mock((): Promise<any> => Promise.resolve({ ok: true, ts: '123.456', message: {} })),
17
+ delete: mock((): Promise<any> => Promise.resolve({ ok: true })),
18
+ },
19
+ reactions: {
20
+ add: mock((): Promise<any> => Promise.resolve({ ok: true })),
21
+ remove: mock((): Promise<any> => Promise.resolve({ ok: true })),
22
+ },
23
+ users: {
24
+ list: mock((): Promise<any> => Promise.resolve({ ok: true, members: [] })),
25
+ info: mock((): Promise<any> => Promise.resolve({ ok: true, user: {} })),
26
+ },
27
+ files: {
28
+ uploadV2: mock((): Promise<any> => Promise.resolve({ ok: true, file: {} })),
29
+ list: mock((): Promise<any> => Promise.resolve({ ok: true, files: [] })),
30
+ },
31
+ auth: {
32
+ test: mock((): Promise<any> => Promise.resolve({ ok: true, user_id: 'U123', team_id: 'T123' })),
33
+ },
34
+ }
35
+
36
+ function resetMocks() {
37
+ for (const group of Object.values(mockWebClient) as any[]) {
38
+ for (const fn of Object.values(group) as any[]) {
39
+ if (typeof fn.mockReset === 'function') {
40
+ fn.mockReset()
41
+ }
42
+ }
43
+ }
44
+ }
45
+
46
+ describe('SlackClient', () => {
47
+ describe('constructor', () => {
48
+ test('throws SlackError when token is empty', () => {
49
+ expect(() => new SlackClient('', 'xoxd-cookie')).toThrow(SlackError)
50
+ expect(() => new SlackClient('', 'xoxd-cookie')).toThrow('Token is required')
51
+ })
52
+
53
+ test('throws SlackError when cookie is empty', () => {
54
+ expect(() => new SlackClient('xoxc-token', '')).toThrow(SlackError)
55
+ expect(() => new SlackClient('xoxc-token', '')).toThrow('Cookie is required')
56
+ })
57
+
58
+ test('throws SlackError when both token and cookie are empty', () => {
59
+ expect(() => new SlackClient('', '')).toThrow(SlackError)
60
+ })
61
+
62
+ test('creates client successfully with valid token and cookie', () => {
63
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
64
+ expect(client).toBeInstanceOf(SlackClient)
65
+ })
66
+ })
67
+
68
+ describe('testAuth', () => {
69
+ beforeEach(() => resetMocks())
70
+
71
+ test('returns auth info on success', async () => {
72
+ mockWebClient.auth.test.mockResolvedValue({
73
+ ok: true,
74
+ user_id: 'U123',
75
+ team_id: 'T123',
76
+ user: 'testuser',
77
+ team: 'Test Team',
78
+ })
79
+
80
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
81
+ // @ts-expect-error - accessing private property for testing
82
+ client.client = mockWebClient as unknown as WebClient
83
+
84
+ const result = await client.testAuth()
85
+ expect(result.user_id).toBe('U123')
86
+ expect(result.team_id).toBe('T123')
87
+ })
88
+
89
+ test('throws SlackError on API failure', async () => {
90
+ mockWebClient.auth.test.mockResolvedValue({
91
+ ok: false,
92
+ error: 'invalid_auth',
93
+ })
94
+
95
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
96
+ // @ts-expect-error - accessing private property for testing
97
+ client.client = mockWebClient as unknown as WebClient
98
+
99
+ await expect(client.testAuth()).rejects.toThrow(SlackError)
100
+ })
101
+ })
102
+
103
+ describe('listChannels', () => {
104
+ beforeEach(() => resetMocks())
105
+
106
+ test('returns list of channels', async () => {
107
+ mockWebClient.conversations.list.mockResolvedValue({
108
+ ok: true,
109
+ channels: [
110
+ {
111
+ id: 'C123',
112
+ name: 'general',
113
+ is_private: false,
114
+ is_archived: false,
115
+ created: 1234567890,
116
+ creator: 'U123',
117
+ },
118
+ {
119
+ id: 'C456',
120
+ name: 'random',
121
+ is_private: false,
122
+ is_archived: false,
123
+ created: 1234567891,
124
+ creator: 'U123',
125
+ },
126
+ ],
127
+ })
128
+
129
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
130
+ // @ts-expect-error - accessing private property for testing
131
+ client.client = mockWebClient as unknown as WebClient
132
+
133
+ const channels = await client.listChannels()
134
+ expect(channels).toHaveLength(2)
135
+ expect(channels[0].id).toBe('C123')
136
+ expect(channels[1].name).toBe('random')
137
+ })
138
+
139
+ test('handles pagination automatically', async () => {
140
+ mockWebClient.conversations.list
141
+ .mockResolvedValueOnce({
142
+ ok: true,
143
+ channels: [
144
+ {
145
+ id: 'C123',
146
+ name: 'general',
147
+ is_private: false,
148
+ is_archived: false,
149
+ created: 1234567890,
150
+ creator: 'U123',
151
+ },
152
+ ],
153
+ response_metadata: { next_cursor: 'cursor123' },
154
+ })
155
+ .mockResolvedValueOnce({
156
+ ok: true,
157
+ channels: [
158
+ {
159
+ id: 'C456',
160
+ name: 'random',
161
+ is_private: false,
162
+ is_archived: false,
163
+ created: 1234567891,
164
+ creator: 'U123',
165
+ },
166
+ ],
167
+ })
168
+
169
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
170
+ // @ts-expect-error - accessing private property for testing
171
+ client.client = mockWebClient as unknown as WebClient
172
+
173
+ const channels = await client.listChannels()
174
+ expect(channels).toHaveLength(2)
175
+ expect(mockWebClient.conversations.list).toHaveBeenCalledTimes(2)
176
+ })
177
+
178
+ test('throws SlackError on API failure', async () => {
179
+ mockWebClient.conversations.list.mockResolvedValue({
180
+ ok: false,
181
+ error: 'channel_not_found',
182
+ })
183
+
184
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
185
+ // @ts-expect-error - accessing private property for testing
186
+ client.client = mockWebClient as unknown as WebClient
187
+
188
+ await expect(client.listChannels()).rejects.toThrow(SlackError)
189
+ })
190
+ })
191
+
192
+ describe('getChannel', () => {
193
+ beforeEach(() => resetMocks())
194
+
195
+ test('returns channel info', async () => {
196
+ mockWebClient.conversations.info.mockResolvedValue({
197
+ ok: true,
198
+ channel: {
199
+ id: 'C123',
200
+ name: 'general',
201
+ is_private: false,
202
+ is_archived: false,
203
+ created: 1234567890,
204
+ creator: 'U123',
205
+ },
206
+ })
207
+
208
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
209
+ // @ts-expect-error - accessing private property for testing
210
+ client.client = mockWebClient as unknown as WebClient
211
+
212
+ const channel = await client.getChannel('C123')
213
+ expect(channel.id).toBe('C123')
214
+ expect(channel.name).toBe('general')
215
+ })
216
+
217
+ test('throws SlackError when channel not found', async () => {
218
+ mockWebClient.conversations.info.mockResolvedValue({
219
+ ok: false,
220
+ error: 'channel_not_found',
221
+ })
222
+
223
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
224
+ // @ts-expect-error - accessing private property for testing
225
+ client.client = mockWebClient as unknown as WebClient
226
+
227
+ await expect(client.getChannel('C999')).rejects.toThrow(SlackError)
228
+ })
229
+ })
230
+
231
+ describe('getMessages', () => {
232
+ beforeEach(() => resetMocks())
233
+
234
+ test('returns messages with default limit of 20', async () => {
235
+ const messages = Array.from({ length: 20 }, (_, i) => ({
236
+ ts: `123.${i}`,
237
+ text: `Message ${i}`,
238
+ type: 'message',
239
+ }))
240
+ mockWebClient.conversations.history.mockResolvedValue({
241
+ ok: true,
242
+ messages,
243
+ })
244
+
245
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
246
+ // @ts-expect-error - accessing private property for testing
247
+ client.client = mockWebClient as unknown as WebClient
248
+
249
+ const result = await client.getMessages('C123')
250
+ expect(result).toHaveLength(20)
251
+ expect(mockWebClient.conversations.history).toHaveBeenCalledWith(
252
+ expect.objectContaining({ channel: 'C123', limit: 20 })
253
+ )
254
+ })
255
+
256
+ test('respects custom limit', async () => {
257
+ mockWebClient.conversations.history.mockResolvedValue({
258
+ ok: true,
259
+ messages: [{ ts: '123.456', text: 'Hello', type: 'message' }],
260
+ })
261
+
262
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
263
+ // @ts-expect-error - accessing private property for testing
264
+ client.client = mockWebClient as unknown as WebClient
265
+
266
+ await client.getMessages('C123', 50)
267
+ expect(mockWebClient.conversations.history).toHaveBeenCalledWith(
268
+ expect.objectContaining({ channel: 'C123', limit: 50 })
269
+ )
270
+ })
271
+
272
+ test('throws SlackError on API failure', async () => {
273
+ mockWebClient.conversations.history.mockResolvedValue({
274
+ ok: false,
275
+ error: 'channel_not_found',
276
+ })
277
+
278
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
279
+ // @ts-expect-error - accessing private property for testing
280
+ client.client = mockWebClient as unknown as WebClient
281
+
282
+ await expect(client.getMessages('C999')).rejects.toThrow(SlackError)
283
+ })
284
+ })
285
+
286
+ describe('sendMessage', () => {
287
+ beforeEach(() => resetMocks())
288
+
289
+ test('sends message to channel', async () => {
290
+ mockWebClient.chat.postMessage.mockResolvedValue({
291
+ ok: true,
292
+ ts: '123.456',
293
+ message: { ts: '123.456', text: 'Hello', type: 'message' },
294
+ })
295
+
296
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
297
+ // @ts-expect-error - accessing private property for testing
298
+ client.client = mockWebClient as unknown as WebClient
299
+
300
+ const message = await client.sendMessage('C123', 'Hello')
301
+ expect(message.ts).toBe('123.456')
302
+ expect(mockWebClient.chat.postMessage).toHaveBeenCalledWith(
303
+ expect.objectContaining({ channel: 'C123', text: 'Hello' })
304
+ )
305
+ })
306
+
307
+ test('sends message to thread', async () => {
308
+ mockWebClient.chat.postMessage.mockResolvedValue({
309
+ ok: true,
310
+ ts: '123.789',
311
+ message: { ts: '123.789', text: 'Reply', type: 'message', thread_ts: '123.456' },
312
+ })
313
+
314
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
315
+ // @ts-expect-error - accessing private property for testing
316
+ client.client = mockWebClient as unknown as WebClient
317
+
318
+ const message = await client.sendMessage('C123', 'Reply', '123.456')
319
+ expect(message.thread_ts).toBe('123.456')
320
+ expect(mockWebClient.chat.postMessage).toHaveBeenCalledWith(
321
+ expect.objectContaining({ channel: 'C123', text: 'Reply', thread_ts: '123.456' })
322
+ )
323
+ })
324
+
325
+ test('throws SlackError on API failure', async () => {
326
+ mockWebClient.chat.postMessage.mockResolvedValue({
327
+ ok: false,
328
+ error: 'channel_not_found',
329
+ })
330
+
331
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
332
+ // @ts-expect-error - accessing private property for testing
333
+ client.client = mockWebClient as unknown as WebClient
334
+
335
+ await expect(client.sendMessage('C999', 'Hello')).rejects.toThrow(SlackError)
336
+ })
337
+ })
338
+
339
+ describe('updateMessage', () => {
340
+ beforeEach(() => resetMocks())
341
+
342
+ test('updates message text', async () => {
343
+ mockWebClient.chat.update.mockResolvedValue({
344
+ ok: true,
345
+ ts: '123.456',
346
+ message: { ts: '123.456', text: 'Updated', type: 'message' },
347
+ })
348
+
349
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
350
+ // @ts-expect-error - accessing private property for testing
351
+ client.client = mockWebClient as unknown as WebClient
352
+
353
+ const message = await client.updateMessage('C123', '123.456', 'Updated')
354
+ expect(message.text).toBe('Updated')
355
+ expect(mockWebClient.chat.update).toHaveBeenCalledWith(
356
+ expect.objectContaining({ channel: 'C123', ts: '123.456', text: 'Updated' })
357
+ )
358
+ })
359
+
360
+ test('throws SlackError on API failure', async () => {
361
+ mockWebClient.chat.update.mockResolvedValue({
362
+ ok: false,
363
+ error: 'message_not_found',
364
+ })
365
+
366
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
367
+ // @ts-expect-error - accessing private property for testing
368
+ client.client = mockWebClient as unknown as WebClient
369
+
370
+ await expect(client.updateMessage('C123', '999.999', 'Updated')).rejects.toThrow(SlackError)
371
+ })
372
+ })
373
+
374
+ describe('deleteMessage', () => {
375
+ beforeEach(() => resetMocks())
376
+
377
+ test('deletes message', async () => {
378
+ mockWebClient.chat.delete.mockResolvedValue({ ok: true })
379
+
380
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
381
+ // @ts-expect-error - accessing private property for testing
382
+ client.client = mockWebClient as unknown as WebClient
383
+
384
+ await expect(client.deleteMessage('C123', '123.456')).resolves.toBeUndefined()
385
+ expect(mockWebClient.chat.delete).toHaveBeenCalledWith(
386
+ expect.objectContaining({ channel: 'C123', ts: '123.456' })
387
+ )
388
+ })
389
+
390
+ test('throws SlackError on API failure', async () => {
391
+ mockWebClient.chat.delete.mockResolvedValue({
392
+ ok: false,
393
+ error: 'message_not_found',
394
+ })
395
+
396
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
397
+ // @ts-expect-error - accessing private property for testing
398
+ client.client = mockWebClient as unknown as WebClient
399
+
400
+ await expect(client.deleteMessage('C123', '999.999')).rejects.toThrow(SlackError)
401
+ })
402
+ })
403
+
404
+ describe('addReaction', () => {
405
+ beforeEach(() => resetMocks())
406
+
407
+ test('adds reaction to message', async () => {
408
+ mockWebClient.reactions.add.mockResolvedValue({ ok: true })
409
+
410
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
411
+ // @ts-expect-error - accessing private property for testing
412
+ client.client = mockWebClient as unknown as WebClient
413
+
414
+ await expect(client.addReaction('C123', '123.456', 'thumbsup')).resolves.toBeUndefined()
415
+ expect(mockWebClient.reactions.add).toHaveBeenCalledWith(
416
+ expect.objectContaining({ channel: 'C123', timestamp: '123.456', name: 'thumbsup' })
417
+ )
418
+ })
419
+
420
+ test('throws SlackError on API failure', async () => {
421
+ mockWebClient.reactions.add.mockResolvedValue({
422
+ ok: false,
423
+ error: 'already_reacted',
424
+ })
425
+
426
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
427
+ // @ts-expect-error - accessing private property for testing
428
+ client.client = mockWebClient as unknown as WebClient
429
+
430
+ await expect(client.addReaction('C123', '123.456', 'thumbsup')).rejects.toThrow(SlackError)
431
+ })
432
+ })
433
+
434
+ describe('removeReaction', () => {
435
+ beforeEach(() => resetMocks())
436
+
437
+ test('removes reaction from message', async () => {
438
+ mockWebClient.reactions.remove.mockResolvedValue({ ok: true })
439
+
440
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
441
+ // @ts-expect-error - accessing private property for testing
442
+ client.client = mockWebClient as unknown as WebClient
443
+
444
+ await expect(client.removeReaction('C123', '123.456', 'thumbsup')).resolves.toBeUndefined()
445
+ expect(mockWebClient.reactions.remove).toHaveBeenCalledWith(
446
+ expect.objectContaining({ channel: 'C123', timestamp: '123.456', name: 'thumbsup' })
447
+ )
448
+ })
449
+
450
+ test('throws SlackError on API failure', async () => {
451
+ mockWebClient.reactions.remove.mockResolvedValue({
452
+ ok: false,
453
+ error: 'no_reaction',
454
+ })
455
+
456
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
457
+ // @ts-expect-error - accessing private property for testing
458
+ client.client = mockWebClient as unknown as WebClient
459
+
460
+ await expect(client.removeReaction('C123', '123.456', 'thumbsup')).rejects.toThrow(SlackError)
461
+ })
462
+ })
463
+
464
+ describe('listUsers', () => {
465
+ beforeEach(() => resetMocks())
466
+
467
+ test('returns list of users', async () => {
468
+ mockWebClient.users.list.mockResolvedValue({
469
+ ok: true,
470
+ members: [
471
+ {
472
+ id: 'U123',
473
+ name: 'alice',
474
+ real_name: 'Alice',
475
+ is_admin: false,
476
+ is_owner: false,
477
+ is_bot: false,
478
+ is_app_user: false,
479
+ },
480
+ {
481
+ id: 'U456',
482
+ name: 'bob',
483
+ real_name: 'Bob',
484
+ is_admin: true,
485
+ is_owner: false,
486
+ is_bot: false,
487
+ is_app_user: false,
488
+ },
489
+ ],
490
+ })
491
+
492
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
493
+ // @ts-expect-error - accessing private property for testing
494
+ client.client = mockWebClient as unknown as WebClient
495
+
496
+ const users = await client.listUsers()
497
+ expect(users).toHaveLength(2)
498
+ expect(users[0].name).toBe('alice')
499
+ expect(users[1].is_admin).toBe(true)
500
+ })
501
+
502
+ test('handles pagination automatically', async () => {
503
+ mockWebClient.users.list
504
+ .mockResolvedValueOnce({
505
+ ok: true,
506
+ members: [
507
+ {
508
+ id: 'U123',
509
+ name: 'alice',
510
+ real_name: 'Alice',
511
+ is_admin: false,
512
+ is_owner: false,
513
+ is_bot: false,
514
+ is_app_user: false,
515
+ },
516
+ ],
517
+ response_metadata: { next_cursor: 'cursor123' },
518
+ })
519
+ .mockResolvedValueOnce({
520
+ ok: true,
521
+ members: [
522
+ {
523
+ id: 'U456',
524
+ name: 'bob',
525
+ real_name: 'Bob',
526
+ is_admin: false,
527
+ is_owner: false,
528
+ is_bot: false,
529
+ is_app_user: false,
530
+ },
531
+ ],
532
+ })
533
+
534
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
535
+ // @ts-expect-error - accessing private property for testing
536
+ client.client = mockWebClient as unknown as WebClient
537
+
538
+ const users = await client.listUsers()
539
+ expect(users).toHaveLength(2)
540
+ expect(mockWebClient.users.list).toHaveBeenCalledTimes(2)
541
+ })
542
+
543
+ test('throws SlackError on API failure', async () => {
544
+ mockWebClient.users.list.mockResolvedValue({
545
+ ok: false,
546
+ error: 'invalid_auth',
547
+ })
548
+
549
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
550
+ // @ts-expect-error - accessing private property for testing
551
+ client.client = mockWebClient as unknown as WebClient
552
+
553
+ await expect(client.listUsers()).rejects.toThrow(SlackError)
554
+ })
555
+ })
556
+
557
+ describe('getUser', () => {
558
+ beforeEach(() => resetMocks())
559
+
560
+ test('returns user info', async () => {
561
+ mockWebClient.users.info.mockResolvedValue({
562
+ ok: true,
563
+ user: {
564
+ id: 'U123',
565
+ name: 'alice',
566
+ real_name: 'Alice',
567
+ is_admin: false,
568
+ is_owner: false,
569
+ is_bot: false,
570
+ is_app_user: false,
571
+ },
572
+ })
573
+
574
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
575
+ // @ts-expect-error - accessing private property for testing
576
+ client.client = mockWebClient as unknown as WebClient
577
+
578
+ const user = await client.getUser('U123')
579
+ expect(user.id).toBe('U123')
580
+ expect(user.name).toBe('alice')
581
+ })
582
+
583
+ test('throws SlackError when user not found', async () => {
584
+ mockWebClient.users.info.mockResolvedValue({
585
+ ok: false,
586
+ error: 'user_not_found',
587
+ })
588
+
589
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
590
+ // @ts-expect-error - accessing private property for testing
591
+ client.client = mockWebClient as unknown as WebClient
592
+
593
+ await expect(client.getUser('U999')).rejects.toThrow(SlackError)
594
+ })
595
+ })
596
+
597
+ describe('uploadFile', () => {
598
+ beforeEach(() => resetMocks())
599
+
600
+ test('uploads file to channels', async () => {
601
+ mockWebClient.files.uploadV2.mockResolvedValue({
602
+ ok: true,
603
+ file: {
604
+ id: 'F123',
605
+ name: 'test.txt',
606
+ title: 'test.txt',
607
+ mimetype: 'text/plain',
608
+ size: 100,
609
+ url_private: 'https://...',
610
+ created: 1234567890,
611
+ user: 'U123',
612
+ },
613
+ })
614
+
615
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
616
+ // @ts-expect-error - accessing private property for testing
617
+ client.client = mockWebClient as unknown as WebClient
618
+
619
+ const file = await client.uploadFile(['C123'], Buffer.from('test'), 'test.txt')
620
+ expect(file.id).toBe('F123')
621
+ expect(mockWebClient.files.uploadV2).toHaveBeenCalledWith(
622
+ expect.objectContaining({ channel_id: 'C123', filename: 'test.txt' })
623
+ )
624
+ })
625
+
626
+ test('throws SlackError on API failure', async () => {
627
+ mockWebClient.files.uploadV2.mockResolvedValue({
628
+ ok: false,
629
+ error: 'file_upload_failed',
630
+ })
631
+
632
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
633
+ // @ts-expect-error - accessing private property for testing
634
+ client.client = mockWebClient as unknown as WebClient
635
+
636
+ await expect(client.uploadFile(['C123'], Buffer.from('test'), 'test.txt')).rejects.toThrow(
637
+ SlackError
638
+ )
639
+ })
640
+ })
641
+
642
+ describe('listFiles', () => {
643
+ beforeEach(() => resetMocks())
644
+
645
+ test('returns list of files', async () => {
646
+ mockWebClient.files.list.mockResolvedValue({
647
+ ok: true,
648
+ files: [
649
+ {
650
+ id: 'F123',
651
+ name: 'test.txt',
652
+ title: 'test.txt',
653
+ mimetype: 'text/plain',
654
+ size: 100,
655
+ url_private: 'https://...',
656
+ created: 1234567890,
657
+ user: 'U123',
658
+ },
659
+ ],
660
+ })
661
+
662
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
663
+ // @ts-expect-error - accessing private property for testing
664
+ client.client = mockWebClient as unknown as WebClient
665
+
666
+ const files = await client.listFiles()
667
+ expect(files).toHaveLength(1)
668
+ expect(files[0].name).toBe('test.txt')
669
+ })
670
+
671
+ test('filters by channel when provided', async () => {
672
+ mockWebClient.files.list.mockResolvedValue({
673
+ ok: true,
674
+ files: [],
675
+ })
676
+
677
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
678
+ // @ts-expect-error - accessing private property for testing
679
+ client.client = mockWebClient as unknown as WebClient
680
+
681
+ await client.listFiles('C123')
682
+ expect(mockWebClient.files.list).toHaveBeenCalledWith(
683
+ expect.objectContaining({ channel: 'C123' })
684
+ )
685
+ })
686
+
687
+ test('throws SlackError on API failure', async () => {
688
+ mockWebClient.files.list.mockResolvedValue({
689
+ ok: false,
690
+ error: 'invalid_auth',
691
+ })
692
+
693
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
694
+ // @ts-expect-error - accessing private property for testing
695
+ client.client = mockWebClient as unknown as WebClient
696
+
697
+ await expect(client.listFiles()).rejects.toThrow(SlackError)
698
+ })
699
+ })
700
+
701
+ describe('getThreadReplies', () => {
702
+ beforeEach(() => resetMocks())
703
+
704
+ test('returns thread replies including parent message', async () => {
705
+ mockWebClient.conversations.replies.mockResolvedValue({
706
+ ok: true,
707
+ messages: [
708
+ {
709
+ ts: '123.456',
710
+ text: 'Parent message',
711
+ type: 'message',
712
+ user: 'U123',
713
+ thread_ts: '123.456',
714
+ reply_count: 2,
715
+ },
716
+ {
717
+ ts: '123.457',
718
+ text: 'First reply',
719
+ type: 'message',
720
+ user: 'U456',
721
+ thread_ts: '123.456',
722
+ },
723
+ {
724
+ ts: '123.458',
725
+ text: 'Second reply',
726
+ type: 'message',
727
+ user: 'U789',
728
+ thread_ts: '123.456',
729
+ },
730
+ ],
731
+ has_more: false,
732
+ })
733
+
734
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
735
+ // @ts-expect-error - accessing private property for testing
736
+ client.client = mockWebClient as unknown as WebClient
737
+
738
+ const result = await client.getThreadReplies('C123', '123.456')
739
+ expect(result.messages).toHaveLength(3)
740
+ expect(result.messages[0].text).toBe('Parent message')
741
+ expect(result.messages[0].reply_count).toBe(2)
742
+ expect(result.messages[1].text).toBe('First reply')
743
+ expect(result.messages[2].text).toBe('Second reply')
744
+ expect(result.has_more).toBe(false)
745
+ })
746
+
747
+ test('respects limit parameter', async () => {
748
+ mockWebClient.conversations.replies.mockResolvedValue({
749
+ ok: true,
750
+ messages: [{ ts: '123.456', text: 'Hello', type: 'message' }],
751
+ has_more: false,
752
+ })
753
+
754
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
755
+ // @ts-expect-error - accessing private property for testing
756
+ client.client = mockWebClient as unknown as WebClient
757
+
758
+ await client.getThreadReplies('C123', '123.456', { limit: 50 })
759
+ expect(mockWebClient.conversations.replies).toHaveBeenCalledWith(
760
+ expect.objectContaining({ channel: 'C123', ts: '123.456', limit: 50 })
761
+ )
762
+ })
763
+
764
+ test('passes optional oldest and latest parameters', async () => {
765
+ mockWebClient.conversations.replies.mockResolvedValue({
766
+ ok: true,
767
+ messages: [],
768
+ has_more: false,
769
+ })
770
+
771
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
772
+ // @ts-expect-error - accessing private property for testing
773
+ client.client = mockWebClient as unknown as WebClient
774
+
775
+ await client.getThreadReplies('C123', '123.456', {
776
+ oldest: '123.400',
777
+ latest: '123.500',
778
+ })
779
+ expect(mockWebClient.conversations.replies).toHaveBeenCalledWith(
780
+ expect.objectContaining({
781
+ channel: 'C123',
782
+ ts: '123.456',
783
+ oldest: '123.400',
784
+ latest: '123.500',
785
+ })
786
+ )
787
+ })
788
+
789
+ test('returns pagination info when has_more is true', async () => {
790
+ mockWebClient.conversations.replies.mockResolvedValue({
791
+ ok: true,
792
+ messages: [{ ts: '123.456', text: 'Hello', type: 'message' }],
793
+ has_more: true,
794
+ response_metadata: { next_cursor: 'cursor123' },
795
+ })
796
+
797
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
798
+ // @ts-expect-error - accessing private property for testing
799
+ client.client = mockWebClient as unknown as WebClient
800
+
801
+ const result = await client.getThreadReplies('C123', '123.456')
802
+ expect(result.has_more).toBe(true)
803
+ expect(result.next_cursor).toBe('cursor123')
804
+ })
805
+
806
+ test('throws SlackError when thread not found', async () => {
807
+ mockWebClient.conversations.replies.mockResolvedValue({
808
+ ok: false,
809
+ error: 'thread_not_found',
810
+ })
811
+
812
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
813
+ // @ts-expect-error - accessing private property for testing
814
+ client.client = mockWebClient as unknown as WebClient
815
+
816
+ await expect(client.getThreadReplies('C123', '999.999')).rejects.toThrow(SlackError)
817
+ })
818
+
819
+ test('throws SlackError when channel not found', async () => {
820
+ mockWebClient.conversations.replies.mockResolvedValue({
821
+ ok: false,
822
+ error: 'channel_not_found',
823
+ })
824
+
825
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
826
+ // @ts-expect-error - accessing private property for testing
827
+ client.client = mockWebClient as unknown as WebClient
828
+
829
+ await expect(client.getThreadReplies('C999', '123.456')).rejects.toThrow(SlackError)
830
+ })
831
+ })
832
+
833
+ describe('rate limiting', () => {
834
+ beforeEach(() => resetMocks())
835
+
836
+ test('retries on rate limit error with exponential backoff', async () => {
837
+ const rateLimitError = new Error('Rate limited')
838
+ ;(rateLimitError as any).code = 'slack_webapi_rate_limited_error'
839
+ ;(rateLimitError as any).retryAfter = 0.001
840
+
841
+ mockWebClient.conversations.list
842
+ .mockRejectedValueOnce(rateLimitError)
843
+ .mockRejectedValueOnce(rateLimitError)
844
+ .mockResolvedValueOnce({
845
+ ok: true,
846
+ channels: [
847
+ {
848
+ id: 'C123',
849
+ name: 'general',
850
+ is_private: false,
851
+ is_archived: false,
852
+ created: 1234567890,
853
+ creator: 'U123',
854
+ },
855
+ ],
856
+ })
857
+
858
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
859
+ // @ts-expect-error - accessing private property for testing
860
+ client.client = mockWebClient as unknown as WebClient
861
+
862
+ const channels = await client.listChannels()
863
+ expect(channels).toHaveLength(1)
864
+ expect(mockWebClient.conversations.list).toHaveBeenCalledTimes(3)
865
+ })
866
+
867
+ test('throws SlackError after max retries (3)', async () => {
868
+ const rateLimitError = new Error('Rate limited')
869
+ ;(rateLimitError as any).code = 'slack_webapi_rate_limited_error'
870
+ ;(rateLimitError as any).retryAfter = 0.001
871
+
872
+ mockWebClient.conversations.list.mockRejectedValue(rateLimitError)
873
+
874
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
875
+ // @ts-expect-error - accessing private property for testing
876
+ client.client = mockWebClient as unknown as WebClient
877
+
878
+ await expect(client.listChannels()).rejects.toThrow(SlackError)
879
+ // Initial call + 3 retries = 4 total calls
880
+ expect(mockWebClient.conversations.list).toHaveBeenCalledTimes(4)
881
+ })
882
+
883
+ test('does not retry on non-rate-limit errors', async () => {
884
+ const otherError = new Error('Some other error')
885
+ ;(otherError as any).code = 'some_other_error'
886
+
887
+ mockWebClient.conversations.list.mockRejectedValue(otherError)
888
+
889
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
890
+ // @ts-expect-error - accessing private property for testing
891
+ client.client = mockWebClient as unknown as WebClient
892
+
893
+ await expect(client.listChannels()).rejects.toThrow(SlackError)
894
+ expect(mockWebClient.conversations.list).toHaveBeenCalledTimes(1)
895
+ })
896
+ })
897
+
898
+ describe('SlackError', () => {
899
+ test('is an instance of Error', () => {
900
+ const error = new SlackError('test error', 'test_code')
901
+ expect(error).toBeInstanceOf(Error)
902
+ expect(error).toBeInstanceOf(SlackError)
903
+ })
904
+
905
+ test('has message and code properties', () => {
906
+ const error = new SlackError('test error', 'test_code')
907
+ expect(error.message).toBe('test error')
908
+ expect(error.code).toBe('test_code')
909
+ })
910
+
911
+ test('has name property set to SlackError', () => {
912
+ const error = new SlackError('test error', 'test_code')
913
+ expect(error.name).toBe('SlackError')
914
+ })
915
+ })
916
+ })