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,281 @@
1
+ import type {
2
+ DiscordChannel,
3
+ DiscordFile,
4
+ DiscordGuild,
5
+ DiscordMessage,
6
+ DiscordUser,
7
+ } from './types'
8
+
9
+ export class DiscordError extends Error {
10
+ code: string
11
+
12
+ constructor(message: string, code: string) {
13
+ super(message)
14
+ this.name = 'DiscordError'
15
+ this.code = code
16
+ }
17
+ }
18
+
19
+ interface RateLimitBucket {
20
+ remaining: number
21
+ resetAt: number
22
+ bucketHash: string
23
+ }
24
+
25
+ const BASE_URL = 'https://discord.com/api/v10'
26
+ const MAX_RETRIES = 3
27
+ const BASE_BACKOFF_MS = 100
28
+
29
+ export class DiscordClient {
30
+ private token: string
31
+ private buckets: Map<string, RateLimitBucket> = new Map()
32
+ private globalRateLimitUntil: number = 0
33
+
34
+ constructor(token: string) {
35
+ if (!token) {
36
+ throw new DiscordError('Token is required', 'missing_token')
37
+ }
38
+ this.token = token
39
+ }
40
+
41
+ private getBucketKey(method: string, path: string): string {
42
+ const normalized = path
43
+ .replace(/\/channels\/\d+/, '/channels/{channel_id}')
44
+ .replace(/\/guilds\/\d+/, '/guilds/{guild_id}')
45
+ .replace(/\/users\/\d+/, '/users/{user_id}')
46
+ .replace(/\/messages\/\d+/, '/messages/{message_id}')
47
+ return `${method}:${normalized}`
48
+ }
49
+
50
+ private async waitForRateLimit(bucketKey: string): Promise<void> {
51
+ const now = Date.now()
52
+
53
+ if (this.globalRateLimitUntil > now) {
54
+ await this.sleep(this.globalRateLimitUntil - now)
55
+ }
56
+
57
+ const bucket = this.buckets.get(bucketKey)
58
+ if (bucket && bucket.remaining === 0 && bucket.resetAt * 1000 > now) {
59
+ await this.sleep(bucket.resetAt * 1000 - now)
60
+ }
61
+ }
62
+
63
+ private updateBucket(bucketKey: string, response: Response): void {
64
+ const remaining = response.headers.get('X-RateLimit-Remaining')
65
+ const reset = response.headers.get('X-RateLimit-Reset')
66
+ const bucketHash = response.headers.get('X-RateLimit-Bucket')
67
+
68
+ if (remaining !== null && reset !== null && bucketHash !== null) {
69
+ this.buckets.set(bucketKey, {
70
+ remaining: parseInt(remaining, 10),
71
+ resetAt: parseFloat(reset),
72
+ bucketHash,
73
+ })
74
+ }
75
+ }
76
+
77
+ private async handleRateLimitResponse(response: Response): Promise<number> {
78
+ const retryAfter = response.headers.get('Retry-After')
79
+ const isGlobal = response.headers.get('X-RateLimit-Global') === 'true'
80
+ const waitMs = parseFloat(retryAfter || '1') * 1000
81
+
82
+ if (isGlobal) {
83
+ this.globalRateLimitUntil = Date.now() + waitMs
84
+ }
85
+
86
+ await this.sleep(waitMs)
87
+ return waitMs
88
+ }
89
+
90
+ private sleep(ms: number): Promise<void> {
91
+ return new Promise((resolve) => setTimeout(resolve, ms))
92
+ }
93
+
94
+ private async request<T>(method: string, path: string, body?: unknown): Promise<T> {
95
+ const url = `${BASE_URL}${path}`
96
+ const bucketKey = this.getBucketKey(method, path)
97
+ let lastError: Error | undefined
98
+
99
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
100
+ await this.waitForRateLimit(bucketKey)
101
+
102
+ const headers: Record<string, string> = {
103
+ Authorization: this.token,
104
+ 'Content-Type': 'application/json',
105
+ }
106
+
107
+ const options: RequestInit = {
108
+ method,
109
+ headers,
110
+ }
111
+
112
+ if (body !== undefined) {
113
+ options.body = JSON.stringify(body)
114
+ }
115
+
116
+ const response = await fetch(url, options)
117
+ this.updateBucket(bucketKey, response)
118
+
119
+ if (response.status === 429) {
120
+ if (attempt < MAX_RETRIES) {
121
+ await this.handleRateLimitResponse(response)
122
+ continue
123
+ }
124
+ const errorBody = await response.json().catch(() => ({}))
125
+ throw new DiscordError((errorBody as any).message || 'Rate limited', 'rate_limited')
126
+ }
127
+
128
+ if (response.status >= 500 && attempt < MAX_RETRIES) {
129
+ await this.sleep(BASE_BACKOFF_MS * 2 ** attempt)
130
+ continue
131
+ }
132
+
133
+ if (!response.ok) {
134
+ const errorBody = await response.json().catch(() => ({}))
135
+ throw new DiscordError(
136
+ (errorBody as any).message || `HTTP ${response.status}`,
137
+ (errorBody as any).code?.toString() || `http_${response.status}`
138
+ )
139
+ }
140
+
141
+ if (response.status === 204) {
142
+ return undefined as T
143
+ }
144
+
145
+ return response.json() as Promise<T>
146
+ }
147
+
148
+ throw lastError || new DiscordError('Request failed after retries', 'max_retries')
149
+ }
150
+
151
+ private async requestFormData<T>(path: string, formData: FormData): Promise<T> {
152
+ const url = `${BASE_URL}${path}`
153
+ const bucketKey = this.getBucketKey('POST', path)
154
+
155
+ await this.waitForRateLimit(bucketKey)
156
+
157
+ const response = await fetch(url, {
158
+ method: 'POST',
159
+ headers: {
160
+ Authorization: this.token,
161
+ },
162
+ body: formData,
163
+ })
164
+
165
+ this.updateBucket(bucketKey, response)
166
+
167
+ if (!response.ok) {
168
+ const errorBody = await response.json().catch(() => ({}))
169
+ throw new DiscordError(
170
+ (errorBody as any).message || `HTTP ${response.status}`,
171
+ (errorBody as any).code?.toString() || `http_${response.status}`
172
+ )
173
+ }
174
+
175
+ return response.json() as Promise<T>
176
+ }
177
+
178
+ async testAuth(): Promise<DiscordUser> {
179
+ return this.request<DiscordUser>('GET', '/users/@me')
180
+ }
181
+
182
+ async listGuilds(): Promise<DiscordGuild[]> {
183
+ return this.request<DiscordGuild[]>('GET', '/users/@me/guilds')
184
+ }
185
+
186
+ async getGuild(guildId: string): Promise<DiscordGuild> {
187
+ return this.request<DiscordGuild>('GET', `/guilds/${guildId}`)
188
+ }
189
+
190
+ async listChannels(guildId: string): Promise<DiscordChannel[]> {
191
+ return this.request<DiscordChannel[]>('GET', `/guilds/${guildId}/channels`)
192
+ }
193
+
194
+ async getChannel(channelId: string): Promise<DiscordChannel> {
195
+ return this.request<DiscordChannel>('GET', `/channels/${channelId}`)
196
+ }
197
+
198
+ async sendMessage(channelId: string, content: string): Promise<DiscordMessage> {
199
+ return this.request<DiscordMessage>('POST', `/channels/${channelId}/messages`, { content })
200
+ }
201
+
202
+ async getMessages(channelId: string, limit: number = 50): Promise<DiscordMessage[]> {
203
+ return this.request<DiscordMessage[]>('GET', `/channels/${channelId}/messages?limit=${limit}`)
204
+ }
205
+
206
+ async getMessage(channelId: string, messageId: string): Promise<DiscordMessage> {
207
+ return this.request<DiscordMessage>('GET', `/channels/${channelId}/messages/${messageId}`)
208
+ }
209
+
210
+ async deleteMessage(channelId: string, messageId: string): Promise<void> {
211
+ return this.request<void>('DELETE', `/channels/${channelId}/messages/${messageId}`)
212
+ }
213
+
214
+ async addReaction(channelId: string, messageId: string, emoji: string): Promise<void> {
215
+ const encodedEmoji = encodeURIComponent(emoji)
216
+ return this.request<void>(
217
+ 'PUT',
218
+ `/channels/${channelId}/messages/${messageId}/reactions/${encodedEmoji}/@me`
219
+ )
220
+ }
221
+
222
+ async removeReaction(channelId: string, messageId: string, emoji: string): Promise<void> {
223
+ const encodedEmoji = encodeURIComponent(emoji)
224
+ return this.request<void>(
225
+ 'DELETE',
226
+ `/channels/${channelId}/messages/${messageId}/reactions/${encodedEmoji}/@me`
227
+ )
228
+ }
229
+
230
+ async listUsers(guildId: string): Promise<DiscordUser[]> {
231
+ interface GuildMember {
232
+ user: DiscordUser
233
+ }
234
+ const members = await this.request<GuildMember[]>(
235
+ 'GET',
236
+ `/guilds/${guildId}/members?limit=1000`
237
+ )
238
+ return members.map((m) => m.user)
239
+ }
240
+
241
+ async getUser(userId: string): Promise<DiscordUser> {
242
+ return this.request<DiscordUser>('GET', `/users/${userId}`)
243
+ }
244
+
245
+ async uploadFile(channelId: string, filePath: string): Promise<DiscordFile> {
246
+ const file = Bun.file(filePath)
247
+ const filename = filePath.split('/').pop() || 'file'
248
+ const blob = await file.arrayBuffer()
249
+
250
+ const formData = new FormData()
251
+ formData.append('files[0]', new Blob([blob]), filename)
252
+
253
+ interface MessageWithAttachments extends DiscordMessage {
254
+ attachments: DiscordFile[]
255
+ }
256
+ const message = await this.requestFormData<MessageWithAttachments>(
257
+ `/channels/${channelId}/messages`,
258
+ formData
259
+ )
260
+
261
+ return message.attachments[0]
262
+ }
263
+
264
+ async listFiles(channelId: string): Promise<DiscordFile[]> {
265
+ interface MessageWithAttachments extends DiscordMessage {
266
+ attachments: DiscordFile[]
267
+ }
268
+ const messages = await this.request<MessageWithAttachments[]>(
269
+ 'GET',
270
+ `/channels/${channelId}/messages?limit=100`
271
+ )
272
+
273
+ const files: DiscordFile[] = []
274
+ for (const msg of messages) {
275
+ if (msg.attachments && msg.attachments.length > 0) {
276
+ files.push(...msg.attachments)
277
+ }
278
+ }
279
+ return files
280
+ }
281
+ }
@@ -0,0 +1,72 @@
1
+ import { expect, mock, test } from 'bun:test'
2
+ import { DiscordClient } from '../client'
3
+ import { DiscordCredentialManager } from '../credential-manager'
4
+ import { DiscordTokenExtractor } from '../token-extractor'
5
+
6
+ // Mock modules
7
+ mock.module('../token-extractor', () => ({
8
+ DiscordTokenExtractor: mock(() => ({
9
+ extract: mock(async () => ({
10
+ token: 'test-token-123',
11
+ })),
12
+ })),
13
+ }))
14
+
15
+ mock.module('../client', () => ({
16
+ DiscordClient: mock((_token: string) => ({
17
+ testAuth: mock(async () => ({
18
+ id: 'user-123',
19
+ username: 'testuser',
20
+ })),
21
+ listGuilds: mock(async () => [
22
+ { id: 'guild-1', name: 'Guild One' },
23
+ { id: 'guild-2', name: 'Guild Two' },
24
+ ]),
25
+ })),
26
+ }))
27
+
28
+ mock.module('../credential-manager', () => ({
29
+ DiscordCredentialManager: mock(() => ({
30
+ load: mock(async () => ({
31
+ token: null,
32
+ current_guild: null,
33
+ guilds: {},
34
+ })),
35
+ save: mock(async () => {}),
36
+ clearToken: mock(async () => {}),
37
+ })),
38
+ }))
39
+
40
+ test('extract: calls DiscordTokenExtractor', async () => {
41
+ const extractor = new DiscordTokenExtractor()
42
+ const result = await extractor.extract()
43
+ expect(result).toBeDefined()
44
+ expect(result?.token).toBe('test-token-123')
45
+ })
46
+
47
+ test('extract: validates token with DiscordClient', async () => {
48
+ const client = new DiscordClient('test-token-123')
49
+ const authInfo = await client.testAuth()
50
+ expect(authInfo).toBeDefined()
51
+ expect(authInfo.id).toBe('user-123')
52
+ })
53
+
54
+ test('extract: discovers guilds', async () => {
55
+ const client = new DiscordClient('test-token-123')
56
+ const guilds = await client.listGuilds()
57
+ expect(guilds).toHaveLength(2)
58
+ expect(guilds[0].id).toBe('guild-1')
59
+ })
60
+
61
+ test('logout: clears credentials', async () => {
62
+ const credManager = new DiscordCredentialManager()
63
+ await credManager.clearToken()
64
+ expect(credManager.clearToken).toHaveBeenCalled()
65
+ })
66
+
67
+ test('status: returns auth state', async () => {
68
+ const credManager = new DiscordCredentialManager()
69
+ const config = await credManager.load()
70
+ expect(config.token).toBeNull()
71
+ expect(config.current_guild).toBeNull()
72
+ })
@@ -0,0 +1,206 @@
1
+ import { Command } from 'commander'
2
+ import { handleError } from '../../../shared/utils/error-handler'
3
+ import { formatOutput } from '../../../shared/utils/output'
4
+ import { DiscordClient } from '../client'
5
+ import { DiscordCredentialManager } from '../credential-manager'
6
+ import { DiscordTokenExtractor } from '../token-extractor'
7
+
8
+ export async function extractAction(options: { pretty?: boolean; debug?: boolean }): Promise<void> {
9
+ try {
10
+ const extractor = new DiscordTokenExtractor()
11
+
12
+ if (process.platform === 'darwin') {
13
+ console.log('')
14
+ console.log(' Extracting your Discord credentials...')
15
+ console.log('')
16
+ console.log(' Your Mac may ask for your password to access Keychain.')
17
+ console.log(' This is required because Discord encrypts your login token')
18
+ console.log(' using macOS Keychain for security.')
19
+ console.log('')
20
+ console.log(' What happens:')
21
+ console.log(" 1. We read the encrypted token from Discord's local storage")
22
+ console.log(' 2. macOS Keychain decrypts it (requires your password)')
23
+ console.log(' 3. The token is stored locally in ~/.config/agent-messenger/')
24
+ console.log('')
25
+ console.log(' Your password is never stored or transmitted anywhere.')
26
+ console.log('')
27
+ }
28
+
29
+ if (options.debug) {
30
+ console.error(`[debug] Extracting Discord token...`)
31
+ }
32
+
33
+ const extracted = await extractor.extract()
34
+
35
+ if (!extracted) {
36
+ console.log(
37
+ formatOutput(
38
+ {
39
+ error:
40
+ 'No Discord token found. Make sure Discord desktop app is installed and logged in.',
41
+ hint: options.debug ? undefined : 'Run with --debug for more info.',
42
+ },
43
+ options.pretty
44
+ )
45
+ )
46
+ process.exit(1)
47
+ }
48
+
49
+ if (options.debug) {
50
+ console.error(`[debug] Token extracted: ${extracted.token.substring(0, 20)}...`)
51
+ }
52
+
53
+ try {
54
+ const client = new DiscordClient(extracted.token)
55
+
56
+ if (options.debug) {
57
+ console.error(`[debug] Testing token validity...`)
58
+ }
59
+
60
+ const authInfo = await client.testAuth()
61
+
62
+ if (options.debug) {
63
+ console.error(`[debug] ✓ Token valid for user: ${authInfo.username}`)
64
+ console.error(`[debug] Discovering guilds...`)
65
+ }
66
+
67
+ const guilds = await client.listGuilds()
68
+
69
+ if (options.debug) {
70
+ console.error(`[debug] ✓ Found ${guilds.length} guild(s)`)
71
+ }
72
+
73
+ if (guilds.length === 0) {
74
+ console.log(
75
+ formatOutput(
76
+ {
77
+ error: 'No guilds found. Make sure you are a member of at least one Discord server.',
78
+ },
79
+ options.pretty
80
+ )
81
+ )
82
+ process.exit(1)
83
+ }
84
+
85
+ const credManager = new DiscordCredentialManager()
86
+ const guildMap: Record<string, { guild_id: string; guild_name: string }> = {}
87
+
88
+ for (const guild of guilds) {
89
+ guildMap[guild.id] = {
90
+ guild_id: guild.id,
91
+ guild_name: guild.name,
92
+ }
93
+ }
94
+
95
+ const config = {
96
+ token: extracted.token,
97
+ current_guild: guilds[0].id,
98
+ guilds: guildMap,
99
+ }
100
+
101
+ await credManager.save(config)
102
+
103
+ if (options.debug) {
104
+ console.error(`[debug] ✓ Credentials saved`)
105
+ }
106
+
107
+ const output = {
108
+ guilds: guilds.map((g) => `${g.id}/${g.name}`),
109
+ current: guilds[0].id,
110
+ }
111
+
112
+ console.log(formatOutput(output, options.pretty))
113
+ } catch (error) {
114
+ console.log(
115
+ formatOutput(
116
+ {
117
+ error: `Token validation failed: ${(error as Error).message}`,
118
+ hint: 'Make sure your Discord token is valid and has not expired.',
119
+ },
120
+ options.pretty
121
+ )
122
+ )
123
+ process.exit(1)
124
+ }
125
+ } catch (error) {
126
+ handleError(error as Error)
127
+ }
128
+ }
129
+
130
+ export async function logoutAction(options: { pretty?: boolean }): Promise<void> {
131
+ try {
132
+ const credManager = new DiscordCredentialManager()
133
+ const config = await credManager.load()
134
+
135
+ if (!config.token) {
136
+ console.log(
137
+ formatOutput({ error: 'Not authenticated. Run "auth extract" first.' }, options.pretty)
138
+ )
139
+ process.exit(1)
140
+ }
141
+
142
+ await credManager.clearToken()
143
+
144
+ console.log(formatOutput({ removed: 'discord', success: true }, options.pretty))
145
+ } catch (error) {
146
+ handleError(error as Error)
147
+ }
148
+ }
149
+
150
+ export async function statusAction(options: { pretty?: boolean }): Promise<void> {
151
+ try {
152
+ const credManager = new DiscordCredentialManager()
153
+ const config = await credManager.load()
154
+
155
+ if (!config.token) {
156
+ console.log(
157
+ formatOutput({ error: 'Not authenticated. Run "auth extract" first.' }, options.pretty)
158
+ )
159
+ process.exit(1)
160
+ }
161
+
162
+ let authInfo: { id: string; username: string } | null = null
163
+ let valid = false
164
+
165
+ try {
166
+ const client = new DiscordClient(config.token)
167
+ authInfo = await client.testAuth()
168
+ valid = true
169
+ } catch {
170
+ valid = false
171
+ }
172
+
173
+ const output = {
174
+ authenticated: valid,
175
+ user: authInfo?.username,
176
+ current_guild: config.current_guild,
177
+ guilds_count: Object.keys(config.guilds).length,
178
+ }
179
+
180
+ console.log(formatOutput(output, options.pretty))
181
+ } catch (error) {
182
+ handleError(error as Error)
183
+ }
184
+ }
185
+
186
+ export const authCommand = new Command('auth')
187
+ .description('Authentication commands')
188
+ .addCommand(
189
+ new Command('extract')
190
+ .description('Extract token from Discord desktop app')
191
+ .option('--pretty', 'Pretty print JSON output')
192
+ .option('--debug', 'Show debug output for troubleshooting')
193
+ .action(extractAction)
194
+ )
195
+ .addCommand(
196
+ new Command('logout')
197
+ .description('Logout from Discord')
198
+ .option('--pretty', 'Pretty print JSON output')
199
+ .action(logoutAction)
200
+ )
201
+ .addCommand(
202
+ new Command('status')
203
+ .description('Show authentication status')
204
+ .option('--pretty', 'Pretty print JSON output')
205
+ .action(statusAction)
206
+ )
@@ -0,0 +1,153 @@
1
+ import { expect, mock, test } from 'bun:test'
2
+ import { DiscordClient } from '../client'
3
+
4
+ // Mock modules
5
+ mock.module('../client', () => ({
6
+ DiscordClient: mock((_token: string) => ({
7
+ listChannels: mock(async (guildId: string) => [
8
+ { id: 'ch-1', guild_id: guildId, name: 'general', type: 0, topic: 'General discussion' },
9
+ { id: 'ch-2', guild_id: guildId, name: 'announcements', type: 0, topic: 'Announcements' },
10
+ { id: 'ch-3', guild_id: guildId, name: 'voice-channel', type: 2, topic: null },
11
+ ]),
12
+ getChannel: mock(async (channelId: string) => {
13
+ if (channelId === 'ch-1') {
14
+ return {
15
+ id: 'ch-1',
16
+ guild_id: 'guild-1',
17
+ name: 'general',
18
+ type: 0,
19
+ topic: 'General discussion',
20
+ }
21
+ }
22
+ if (channelId === 'ch-2') {
23
+ return {
24
+ id: 'ch-2',
25
+ guild_id: 'guild-1',
26
+ name: 'announcements',
27
+ type: 0,
28
+ topic: 'Announcements',
29
+ }
30
+ }
31
+ throw new Error('Channel not found')
32
+ }),
33
+ getMessages: mock(async (channelId: string, _limit: number) => [
34
+ {
35
+ id: 'msg-1',
36
+ channel_id: channelId,
37
+ author: { id: 'user-1', username: 'alice' },
38
+ content: 'Hello world',
39
+ timestamp: '2024-01-29T10:00:00Z',
40
+ },
41
+ {
42
+ id: 'msg-2',
43
+ channel_id: channelId,
44
+ author: { id: 'user-2', username: 'bob' },
45
+ content: 'Hi there',
46
+ timestamp: '2024-01-29T09:00:00Z',
47
+ },
48
+ ]),
49
+ })),
50
+ }))
51
+
52
+ mock.module('../credential-manager', () => ({
53
+ DiscordCredentialManager: mock(() => ({
54
+ load: mock(async () => ({
55
+ token: 'test-token',
56
+ current_guild: 'guild-1',
57
+ guilds: {
58
+ 'guild-1': { guild_id: 'guild-1', guild_name: 'Guild One' },
59
+ },
60
+ })),
61
+ })),
62
+ }))
63
+
64
+ test('list: returns text channels (type=0) from guild', async () => {
65
+ // given: discord client with channels
66
+ const client = new DiscordClient('test-token')
67
+ const channels = await client.listChannels('guild-1')
68
+
69
+ // when: filtering text channels
70
+ const textChannels = channels.filter((ch) => ch.type === 0)
71
+
72
+ // then: only text channels are returned
73
+ expect(textChannels).toHaveLength(2)
74
+ expect(textChannels[0].name).toBe('general')
75
+ expect(textChannels[1].name).toBe('announcements')
76
+ })
77
+
78
+ test('list: includes channel metadata', async () => {
79
+ // given: discord client with channels
80
+ const client = new DiscordClient('test-token')
81
+ const channels = await client.listChannels('guild-1')
82
+ const textChannels = channels.filter((ch) => ch.type === 0)
83
+
84
+ // when: checking channel properties
85
+ const channel = textChannels[0]
86
+
87
+ // then: channel has id, name, type, topic
88
+ expect(channel.id).toBeDefined()
89
+ expect(channel.name).toBeDefined()
90
+ expect(channel.type).toBe(0)
91
+ expect(channel.topic).toBeDefined()
92
+ })
93
+
94
+ test('info: returns channel details', async () => {
95
+ // given: discord client with channel data
96
+ const client = new DiscordClient('test-token')
97
+ const channel = await client.getChannel('ch-1')
98
+
99
+ // when: getting channel info
100
+ expect(channel).toBeDefined()
101
+
102
+ // then: channel details are returned
103
+ expect(channel.id).toBe('ch-1')
104
+ expect(channel.name).toBe('general')
105
+ expect(channel.type).toBe(0)
106
+ expect(channel.topic).toBe('General discussion')
107
+ })
108
+
109
+ test('info: throws error for non-existent channel', async () => {
110
+ // given: discord client
111
+ const client = new DiscordClient('test-token')
112
+
113
+ // when: getting non-existent channel
114
+ // then: error is thrown
115
+ try {
116
+ await client.getChannel('non-existent')
117
+ expect(true).toBe(false) // should not reach here
118
+ } catch (error) {
119
+ expect((error as Error).message).toContain('Channel not found')
120
+ }
121
+ })
122
+
123
+ test('history: returns messages in reverse chronological order', async () => {
124
+ // given: discord client with messages
125
+ const client = new DiscordClient('test-token')
126
+ const messages = await client.getMessages('ch-1', 50)
127
+
128
+ // when: getting message history
129
+ expect(messages).toBeDefined()
130
+
131
+ // then: messages are returned (Discord API returns newest first)
132
+ expect(messages).toHaveLength(2)
133
+ expect(messages[0].id).toBe('msg-1')
134
+ expect(messages[0].author.username).toBe('alice')
135
+ expect(messages[1].id).toBe('msg-2')
136
+ expect(messages[1].author.username).toBe('bob')
137
+ })
138
+
139
+ test('history: includes message metadata', async () => {
140
+ // given: discord client with messages
141
+ const client = new DiscordClient('test-token')
142
+ const messages = await client.getMessages('ch-1', 50)
143
+
144
+ // when: checking message properties
145
+ const message = messages[0]
146
+
147
+ // then: message has id, content, author, timestamp
148
+ expect(message.id).toBeDefined()
149
+ expect(message.content).toBeDefined()
150
+ expect(message.author).toBeDefined()
151
+ expect(message.author.username).toBeDefined()
152
+ expect(message.timestamp).toBeDefined()
153
+ })