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,918 @@
1
+ import { beforeEach, describe, expect, mock, test } from 'bun:test'
2
+ import { execSync, spawn } from 'node:child_process'
3
+ import { existsSync, readdirSync, readFileSync } from 'node:fs'
4
+ import { homedir } from 'node:os'
5
+ import { join } from 'node:path'
6
+
7
+ import { CDP_PORT, DiscordTokenExtractor, TOKEN_EXTRACTION_JS } from './token-extractor'
8
+
9
+ // Mock modules
10
+ mock.module('node:fs', () => ({
11
+ existsSync: mock(() => false),
12
+ readdirSync: mock(() => []),
13
+ readFileSync: mock(() => Buffer.from('')),
14
+ }))
15
+
16
+ const mockSpawn = mock(() => ({
17
+ unref: mock(() => {}),
18
+ }))
19
+
20
+ mock.module('node:child_process', () => ({
21
+ execSync: mock(() => ''),
22
+ spawn: mockSpawn,
23
+ }))
24
+
25
+ describe('DiscordTokenExtractor', () => {
26
+ let extractor: DiscordTokenExtractor
27
+
28
+ beforeEach(() => {
29
+ extractor = new DiscordTokenExtractor()
30
+ })
31
+
32
+ describe('getDiscordDirs', () => {
33
+ test('returns darwin paths on macOS', () => {
34
+ const darwinExtractor = new DiscordTokenExtractor('darwin')
35
+ const dirs = darwinExtractor.getDiscordDirs()
36
+
37
+ expect(dirs).toContain(join(homedir(), 'Library', 'Application Support', 'Discord'))
38
+ expect(dirs).toContain(join(homedir(), 'Library', 'Application Support', 'discordcanary'))
39
+ expect(dirs).toContain(join(homedir(), 'Library', 'Application Support', 'discordptb'))
40
+ })
41
+
42
+ test('returns linux paths on Linux', () => {
43
+ const linuxExtractor = new DiscordTokenExtractor('linux')
44
+ const dirs = linuxExtractor.getDiscordDirs()
45
+
46
+ expect(dirs).toContain(join(homedir(), '.config', 'discord'))
47
+ expect(dirs).toContain(join(homedir(), '.config', 'discordcanary'))
48
+ expect(dirs).toContain(join(homedir(), '.config', 'discordptb'))
49
+ })
50
+
51
+ test('returns win32 paths on Windows', () => {
52
+ const winExtractor = new DiscordTokenExtractor('win32')
53
+ const dirs = winExtractor.getDiscordDirs()
54
+
55
+ const appdata = process.env.APPDATA || join(homedir(), 'AppData', 'Roaming')
56
+ expect(dirs).toContain(join(appdata, 'Discord'))
57
+ expect(dirs).toContain(join(appdata, 'discordcanary'))
58
+ expect(dirs).toContain(join(appdata, 'discordptb'))
59
+ })
60
+
61
+ test('returns multiple paths for all 3 variants', () => {
62
+ const dirs = extractor.getDiscordDirs()
63
+ expect(dirs.length).toBe(3)
64
+ })
65
+ })
66
+
67
+ describe('token patterns', () => {
68
+ test('validates standard token format (base64.base64.base64)', () => {
69
+ // Token: base64(user_id).base64(timestamp).base64(hmac)
70
+ const validToken = 'MTIzNDU2Nzg5MDEyMzQ1Njc4.GrE5dA.abcdefghijklmnopqrstuvwxyz1234567890'
71
+ expect(extractor.isValidToken(validToken)).toBe(true)
72
+ })
73
+
74
+ test('validates MFA token format', () => {
75
+ const mfaToken = `mfa.${'a'.repeat(84)}`
76
+ expect(extractor.isValidToken(mfaToken)).toBe(true)
77
+ })
78
+
79
+ test('rejects invalid tokens', () => {
80
+ expect(extractor.isValidToken('')).toBe(false)
81
+ expect(extractor.isValidToken('invalid')).toBe(false)
82
+ expect(extractor.isValidToken('xoxc-123')).toBe(false)
83
+ })
84
+
85
+ test('detects encrypted tokens by prefix', () => {
86
+ const encryptedToken = 'dQw4w9WgXcQ:' + 'encrypted_data'
87
+ expect(extractor.isEncryptedToken(encryptedToken)).toBe(true)
88
+ expect(extractor.isEncryptedToken('MTIzNDU2.xxx.yyy')).toBe(false)
89
+ })
90
+ })
91
+
92
+ describe('extract', () => {
93
+ test('returns null when no Discord directories exist on linux', async () => {
94
+ const mockExistsSync = existsSync as unknown as ReturnType<typeof mock>
95
+ mockExistsSync.mockImplementation(() => false)
96
+
97
+ const linuxExtractor = new DiscordTokenExtractor('linux')
98
+ const result = await linuxExtractor.extract()
99
+ expect(result).toBeNull()
100
+ })
101
+
102
+ test('extracts token from LevelDB files on linux', async () => {
103
+ const mockToken = 'MTIzNDU2Nzg5MDEyMzQ1Njc4.GrE5dA.abcdefghijklmnopqrstuvwxyz1234567890'
104
+ const ldbContent = Buffer.from(`some_data"${mockToken}"more_data`)
105
+
106
+ const mockExistsSync = existsSync as unknown as ReturnType<typeof mock>
107
+ mockExistsSync.mockImplementation((path: string) => {
108
+ if (path.includes('discord') || path.includes('leveldb')) return true
109
+ if (path.includes('Local Storage')) return true
110
+ return false
111
+ })
112
+
113
+ const mockReaddirSync = readdirSync as unknown as ReturnType<typeof mock>
114
+ mockReaddirSync.mockImplementation((path: string) => {
115
+ if (path.includes('leveldb')) return ['000001.ldb']
116
+ if (path.includes('Local Storage')) return ['leveldb']
117
+ return []
118
+ })
119
+
120
+ const mockReadFileSync = readFileSync as unknown as ReturnType<typeof mock>
121
+ mockReadFileSync.mockImplementation(() => ldbContent)
122
+
123
+ const linuxExtractor = new DiscordTokenExtractor('linux')
124
+ const result = await linuxExtractor.extract()
125
+
126
+ expect(result).not.toBeNull()
127
+ expect(result?.token).toBe(mockToken)
128
+ })
129
+
130
+ test('tries LevelDB first on macOS, CDP as fallback', async () => {
131
+ const levelDbToken = 'MTIzNDU2Nzg5MDEyMzQ1Njc4.GrE5dA.leveldb_token_123456789012345'
132
+ const ldbContent = Buffer.from(`some_data"${levelDbToken}"more_data`)
133
+
134
+ const mockExistsSync = existsSync as unknown as ReturnType<typeof mock>
135
+ mockExistsSync.mockImplementation((path: string) => {
136
+ if (path.includes('Discord') || path.includes('leveldb')) return true
137
+ if (path.includes('Local Storage')) return true
138
+ return false
139
+ })
140
+
141
+ const mockReaddirSync = readdirSync as unknown as ReturnType<typeof mock>
142
+ mockReaddirSync.mockImplementation((path: string) => {
143
+ if (path.includes('leveldb')) return ['000001.ldb']
144
+ if (path.includes('Local Storage')) return ['leveldb']
145
+ return []
146
+ })
147
+
148
+ const mockReadFileSync = readFileSync as unknown as ReturnType<typeof mock>
149
+ mockReadFileSync.mockImplementation(() => ldbContent)
150
+
151
+ const darwinExtractor = new DiscordTokenExtractor('darwin', 0)
152
+ const result = await darwinExtractor.extract()
153
+
154
+ expect(result).not.toBeNull()
155
+ expect(result?.token).toBe(levelDbToken)
156
+ })
157
+
158
+ test('falls back to leveldb on macOS when CDP fails', async () => {
159
+ const mockToken = 'MTIzNDU2Nzg5MDEyMzQ1Njc4.GrE5dA.leveldb_fallback_token_12345'
160
+ const ldbContent = Buffer.from(`some_data"${mockToken}"more_data`)
161
+
162
+ const originalFetch = globalThis.fetch
163
+ globalThis.fetch = mock(async () => ({
164
+ ok: true,
165
+ json: async () => [],
166
+ })) as unknown as typeof fetch
167
+
168
+ const mockExistsSync = existsSync as unknown as ReturnType<typeof mock>
169
+ mockExistsSync.mockImplementation((path: string) => {
170
+ if (path.includes('/Applications/')) return false
171
+ if (path.includes('Discord') || path.includes('leveldb')) return true
172
+ if (path.includes('Local Storage')) return true
173
+ return false
174
+ })
175
+
176
+ const mockReaddirSync = readdirSync as unknown as ReturnType<typeof mock>
177
+ mockReaddirSync.mockImplementation((path: string) => {
178
+ if (path.includes('leveldb')) return ['000001.ldb']
179
+ if (path.includes('Local Storage')) return ['leveldb']
180
+ return []
181
+ })
182
+
183
+ const mockReadFileSync = readFileSync as unknown as ReturnType<typeof mock>
184
+ mockReadFileSync.mockImplementation(() => ldbContent)
185
+
186
+ try {
187
+ const darwinExtractor = new DiscordTokenExtractor('darwin', 0)
188
+ const result = await darwinExtractor.extract()
189
+
190
+ expect(result).not.toBeNull()
191
+ expect(result?.token).toBe(mockToken)
192
+ } finally {
193
+ globalThis.fetch = originalFetch
194
+ }
195
+ })
196
+
197
+ test('returns first valid token found across variants on linux', async () => {
198
+ const mockToken = 'MTIzNDU2Nzg5MDEyMzQ1Njc4.GrE5dA.first_token_found_1234567890'
199
+ const ldbContent = Buffer.from(`"${mockToken}"`)
200
+
201
+ const mockExistsSync = existsSync as unknown as ReturnType<typeof mock>
202
+ mockExistsSync.mockImplementation(() => true)
203
+
204
+ const mockReaddirSync = readdirSync as unknown as ReturnType<typeof mock>
205
+ mockReaddirSync.mockImplementation((path: string) => {
206
+ if (path.includes('leveldb')) return ['test.ldb']
207
+ if (path.includes('Local Storage')) return ['leveldb']
208
+ return []
209
+ })
210
+
211
+ const mockReadFileSync = readFileSync as unknown as ReturnType<typeof mock>
212
+ mockReadFileSync.mockImplementation(() => ldbContent)
213
+
214
+ const linuxExtractor = new DiscordTokenExtractor('linux')
215
+ const result = await linuxExtractor.extract()
216
+
217
+ expect(result).not.toBeNull()
218
+ expect(typeof result?.token).toBe('string')
219
+ })
220
+ })
221
+
222
+ describe('encrypted token handling', () => {
223
+ test('decrypts Windows DPAPI encrypted token', async () => {
224
+ const mockExecSync = execSync as unknown as ReturnType<typeof mock>
225
+ const decryptedKey = Buffer.from('0'.repeat(32), 'hex').toString('base64')
226
+
227
+ mockExecSync.mockImplementation((cmd: string) => {
228
+ if (cmd.includes('powershell') && cmd.includes('ProtectedData')) {
229
+ return `${decryptedKey}\n`
230
+ }
231
+ return ''
232
+ })
233
+
234
+ const winExtractor = new DiscordTokenExtractor('win32')
235
+
236
+ // Mock Local State file reading
237
+ const mockReadFileSync = readFileSync as unknown as ReturnType<typeof mock>
238
+ mockReadFileSync.mockImplementation((path: string) => {
239
+ if (path.includes('Local State')) {
240
+ return JSON.stringify({
241
+ os_crypt: {
242
+ encrypted_key: Buffer.from(`DPAPI${'x'.repeat(32)}`).toString('base64'),
243
+ },
244
+ })
245
+ }
246
+ return Buffer.from('')
247
+ })
248
+
249
+ // Test that DPAPI decryption is called
250
+ const encryptedToken = `dQw4w9WgXcQ:${Buffer.from('test').toString('base64')}`
251
+ expect(winExtractor.isEncryptedToken(encryptedToken)).toBe(true)
252
+ })
253
+
254
+ test('decrypts macOS Keychain encrypted token', async () => {
255
+ const mockExecSync = execSync as unknown as ReturnType<typeof mock>
256
+ const keychainPassword = 'test_password'
257
+
258
+ mockExecSync.mockImplementation((cmd: string) => {
259
+ if (cmd.includes('security find-generic-password')) {
260
+ if (cmd.includes('Discord Safe Storage')) {
261
+ return keychainPassword
262
+ }
263
+ }
264
+ throw new Error('Not found')
265
+ })
266
+
267
+ const macExtractor = new DiscordTokenExtractor('darwin')
268
+
269
+ // Verify keychain command patterns
270
+ expect(macExtractor.getKeychainVariants()).toEqual([
271
+ { service: 'discord Safe Storage', account: 'discord Key' },
272
+ { service: 'discordcanary Safe Storage', account: 'discordcanary Key' },
273
+ { service: 'discordptb Safe Storage', account: 'discordptb Key' },
274
+ { service: 'Discord Safe Storage', account: 'Discord' },
275
+ { service: 'Discord Canary Safe Storage', account: 'Discord Canary' },
276
+ { service: 'Discord PTB Safe Storage', account: 'Discord PTB' },
277
+ ])
278
+ })
279
+ })
280
+
281
+ describe('variant detection', () => {
282
+ test('identifies Discord Stable', () => {
283
+ expect(extractor.getVariantFromPath('/path/to/Discord')).toBe('stable')
284
+ expect(extractor.getVariantFromPath('/path/to/discord')).toBe('stable')
285
+ })
286
+
287
+ test('identifies Discord Canary', () => {
288
+ expect(extractor.getVariantFromPath('/path/to/discordcanary')).toBe('canary')
289
+ expect(extractor.getVariantFromPath('/path/to/Discord Canary')).toBe('canary')
290
+ })
291
+
292
+ test('identifies Discord PTB', () => {
293
+ expect(extractor.getVariantFromPath('/path/to/discordptb')).toBe('ptb')
294
+ expect(extractor.getVariantFromPath('/path/to/Discord PTB')).toBe('ptb')
295
+ })
296
+ })
297
+
298
+ describe('process management', () => {
299
+ describe('isDiscordRunning', () => {
300
+ test('returns true when Discord process is found on macOS', async () => {
301
+ const mockExecSync = execSync as unknown as ReturnType<typeof mock>
302
+ mockExecSync.mockImplementation((cmd: string) => {
303
+ if (cmd.includes('pgrep') && cmd.includes('Discord')) {
304
+ return '12345\n'
305
+ }
306
+ return ''
307
+ })
308
+
309
+ const darwinExtractor = new DiscordTokenExtractor('darwin', 0, 0)
310
+ const result = await darwinExtractor.isDiscordRunning('stable')
311
+ expect(result).toBe(true)
312
+ })
313
+
314
+ test('returns false when no Discord process is found', async () => {
315
+ const mockExecSync = execSync as unknown as ReturnType<typeof mock>
316
+ mockExecSync.mockImplementation(() => '')
317
+
318
+ const darwinExtractor = new DiscordTokenExtractor('darwin', 0, 0)
319
+ const result = await darwinExtractor.isDiscordRunning('stable')
320
+ expect(result).toBe(false)
321
+ })
322
+
323
+ test('checks all variants when no specific variant provided', async () => {
324
+ const mockExecSync = execSync as unknown as ReturnType<typeof mock>
325
+ const checkedProcesses: string[] = []
326
+
327
+ mockExecSync.mockImplementation((cmd: string) => {
328
+ if (cmd.includes('pgrep')) {
329
+ const match = cmd.match(/pgrep -f "([^"]+)"/)
330
+ if (match) checkedProcesses.push(match[1])
331
+ }
332
+ return ''
333
+ })
334
+
335
+ const darwinExtractor = new DiscordTokenExtractor('darwin', 0, 0)
336
+ await darwinExtractor.isDiscordRunning()
337
+
338
+ expect(checkedProcesses).toContain('Discord')
339
+ expect(checkedProcesses).toContain('Discord Canary')
340
+ expect(checkedProcesses).toContain('Discord PTB')
341
+ })
342
+
343
+ test('returns true when Windows process is found', async () => {
344
+ const mockExecSync = execSync as unknown as ReturnType<typeof mock>
345
+ mockExecSync.mockImplementation((cmd: string) => {
346
+ if (cmd.includes('tasklist') && cmd.includes('Discord.exe')) {
347
+ return 'Discord.exe 12345 Console 1 123,456 K\n'
348
+ }
349
+ return ''
350
+ })
351
+
352
+ const winExtractor = new DiscordTokenExtractor('win32', 0, 0)
353
+ const result = await winExtractor.isDiscordRunning('stable')
354
+ expect(result).toBe(true)
355
+ })
356
+ })
357
+
358
+ describe('killDiscord', () => {
359
+ test('kills Discord process on macOS using pkill', async () => {
360
+ const mockExecSync = execSync as unknown as ReturnType<typeof mock>
361
+ const killedProcesses: string[] = []
362
+
363
+ mockExecSync.mockImplementation((cmd: string) => {
364
+ if (cmd.includes('pkill')) {
365
+ const match = cmd.match(/pkill -f "([^"]+)"/)
366
+ if (match) killedProcesses.push(match[1])
367
+ }
368
+ return ''
369
+ })
370
+
371
+ const darwinExtractor = new DiscordTokenExtractor('darwin', 0, 0)
372
+ await darwinExtractor.killDiscord('stable')
373
+
374
+ expect(killedProcesses).toContain('Discord')
375
+ })
376
+
377
+ test('kills Discord process on Windows using taskkill', async () => {
378
+ const mockExecSync = execSync as unknown as ReturnType<typeof mock>
379
+ const killedProcesses: string[] = []
380
+
381
+ mockExecSync.mockImplementation((cmd: string) => {
382
+ if (cmd.includes('taskkill')) {
383
+ const match = cmd.match(/taskkill \/F \/IM "([^"]+)"/)
384
+ if (match) killedProcesses.push(match[1])
385
+ }
386
+ return ''
387
+ })
388
+
389
+ const winExtractor = new DiscordTokenExtractor('win32', 0, 0)
390
+ await winExtractor.killDiscord('stable')
391
+
392
+ expect(killedProcesses).toContain('Discord.exe')
393
+ })
394
+
395
+ test('kills all variants when no specific variant provided', async () => {
396
+ const mockExecSync = execSync as unknown as ReturnType<typeof mock>
397
+ const killedProcesses: string[] = []
398
+
399
+ mockExecSync.mockImplementation((cmd: string) => {
400
+ if (cmd.includes('pkill')) {
401
+ const match = cmd.match(/pkill -f "([^"]+)"/)
402
+ if (match) killedProcesses.push(match[1])
403
+ }
404
+ return ''
405
+ })
406
+
407
+ const darwinExtractor = new DiscordTokenExtractor('darwin', 0, 0)
408
+ await darwinExtractor.killDiscord()
409
+
410
+ expect(killedProcesses).toContain('Discord')
411
+ expect(killedProcesses).toContain('Discord Canary')
412
+ expect(killedProcesses).toContain('Discord PTB')
413
+ })
414
+ })
415
+
416
+ describe('launchDiscordWithDebug', () => {
417
+ test('throws error when Discord app not found', async () => {
418
+ const mockExistsSync = existsSync as unknown as ReturnType<typeof mock>
419
+ mockExistsSync.mockImplementation(() => false)
420
+
421
+ const darwinExtractor = new DiscordTokenExtractor('darwin', 0, 0)
422
+
423
+ await expect(darwinExtractor.launchDiscordWithDebug('stable')).rejects.toThrow(
424
+ 'Discord stable not found'
425
+ )
426
+ })
427
+
428
+ test('launches Discord with remote debugging port on macOS', async () => {
429
+ const mockExistsSync = existsSync as unknown as ReturnType<typeof mock>
430
+ mockExistsSync.mockImplementation((path: string) => {
431
+ return path.includes('/Applications/Discord.app')
432
+ })
433
+
434
+ const mockExecSync = execSync as unknown as ReturnType<typeof mock>
435
+ mockExecSync.mockImplementation(() => '')
436
+
437
+ let spawnedPath = ''
438
+ let spawnedArgs: string[] = []
439
+ const mockSpawnFn = spawn as unknown as ReturnType<typeof mock>
440
+ mockSpawnFn.mockImplementation((path: string, args: string[]) => {
441
+ spawnedPath = path
442
+ spawnedArgs = args
443
+ return { unref: () => {} }
444
+ })
445
+
446
+ const darwinExtractor = new DiscordTokenExtractor('darwin', 0, 0)
447
+ await darwinExtractor.launchDiscordWithDebug('stable', 9222)
448
+
449
+ expect(spawnedPath).toBe('/Applications/Discord.app/Contents/MacOS/Discord')
450
+ expect(spawnedArgs).toContain('--remote-debugging-port=9222')
451
+ })
452
+
453
+ test('uses default CDP port when not specified', async () => {
454
+ const mockExistsSync = existsSync as unknown as ReturnType<typeof mock>
455
+ mockExistsSync.mockImplementation(() => true)
456
+
457
+ const mockExecSync = execSync as unknown as ReturnType<typeof mock>
458
+ mockExecSync.mockImplementation(() => '')
459
+
460
+ let spawnedArgs: string[] = []
461
+ const mockSpawnFn = spawn as unknown as ReturnType<typeof mock>
462
+ mockSpawnFn.mockImplementation((_path: string, args: string[]) => {
463
+ spawnedArgs = args
464
+ return { unref: () => {} }
465
+ })
466
+
467
+ const darwinExtractor = new DiscordTokenExtractor('darwin', 0, 0)
468
+ await darwinExtractor.launchDiscordWithDebug('stable')
469
+
470
+ expect(spawnedArgs).toContain(`--remote-debugging-port=${CDP_PORT}`)
471
+ })
472
+ })
473
+ })
474
+
475
+ describe('CDP client methods', () => {
476
+ describe('discoverCDPTargets', () => {
477
+ test('returns empty array when CDP endpoint is not reachable', async () => {
478
+ const originalFetch = globalThis.fetch
479
+ globalThis.fetch = mock(async () => {
480
+ throw new Error('Connection refused')
481
+ }) as unknown as typeof fetch
482
+
483
+ try {
484
+ const extractor = new DiscordTokenExtractor('darwin')
485
+ const targets = await extractor.discoverCDPTargets(19999)
486
+ expect(targets).toEqual([])
487
+ } finally {
488
+ globalThis.fetch = originalFetch
489
+ }
490
+ })
491
+
492
+ test('returns targets from CDP endpoint', async () => {
493
+ const mockTargets = [
494
+ {
495
+ id: '1',
496
+ type: 'page',
497
+ title: 'Discord',
498
+ url: 'https://discord.com/app',
499
+ webSocketDebuggerUrl: 'ws://localhost:9222/devtools/page/1',
500
+ },
501
+ ]
502
+
503
+ const originalFetch = globalThis.fetch
504
+ globalThis.fetch = mock(async () => ({
505
+ ok: true,
506
+ json: async () => mockTargets,
507
+ })) as unknown as typeof fetch
508
+
509
+ try {
510
+ const extractor = new DiscordTokenExtractor('darwin')
511
+ const targets = await extractor.discoverCDPTargets(9222)
512
+ expect(targets).toEqual(mockTargets)
513
+ } finally {
514
+ globalThis.fetch = originalFetch
515
+ }
516
+ })
517
+
518
+ test('returns empty array on HTTP error', async () => {
519
+ const originalFetch = globalThis.fetch
520
+ globalThis.fetch = mock(async () => ({
521
+ ok: false,
522
+ status: 500,
523
+ })) as unknown as typeof fetch
524
+
525
+ try {
526
+ const extractor = new DiscordTokenExtractor('darwin')
527
+ const targets = await extractor.discoverCDPTargets(9222)
528
+ expect(targets).toEqual([])
529
+ } finally {
530
+ globalThis.fetch = originalFetch
531
+ }
532
+ })
533
+ })
534
+
535
+ describe('findDiscordPageTarget', () => {
536
+ test('finds target by discord.com URL', () => {
537
+ const targets = [
538
+ {
539
+ id: '1',
540
+ type: 'page',
541
+ title: 'Discord',
542
+ url: 'https://discord.com/app',
543
+ webSocketDebuggerUrl: 'ws://localhost:9222/devtools/page/1',
544
+ },
545
+ {
546
+ id: '2',
547
+ type: 'background_page',
548
+ title: 'background',
549
+ url: 'about:blank',
550
+ webSocketDebuggerUrl: 'ws://localhost:9222/devtools/page/2',
551
+ },
552
+ ]
553
+
554
+ const extractor = new DiscordTokenExtractor('darwin')
555
+ const target = extractor.findDiscordPageTarget(targets)
556
+
557
+ expect(target).not.toBeNull()
558
+ expect(target?.id).toBe('1')
559
+ })
560
+
561
+ test('finds target by Discord title', () => {
562
+ const targets = [
563
+ {
564
+ id: '1',
565
+ type: 'page',
566
+ title: 'Discord - Chat',
567
+ url: 'https://app.discord.com/channels',
568
+ webSocketDebuggerUrl: 'ws://localhost:9222/devtools/page/1',
569
+ },
570
+ ]
571
+
572
+ const extractor = new DiscordTokenExtractor('darwin')
573
+ const target = extractor.findDiscordPageTarget(targets)
574
+
575
+ expect(target).not.toBeNull()
576
+ expect(target?.id).toBe('1')
577
+ })
578
+
579
+ test('returns null when no Discord page found', () => {
580
+ const targets = [
581
+ {
582
+ id: '1',
583
+ type: 'background_page',
584
+ title: 'background',
585
+ url: 'about:blank',
586
+ webSocketDebuggerUrl: 'ws://localhost:9222/devtools/page/1',
587
+ },
588
+ ]
589
+
590
+ const extractor = new DiscordTokenExtractor('darwin')
591
+ const target = extractor.findDiscordPageTarget(targets)
592
+
593
+ expect(target).toBeNull()
594
+ })
595
+
596
+ test('returns null for empty targets', () => {
597
+ const extractor = new DiscordTokenExtractor('darwin')
598
+ const target = extractor.findDiscordPageTarget([])
599
+ expect(target).toBeNull()
600
+ })
601
+ })
602
+
603
+ describe('executeJSViaCDP', () => {
604
+ test('executes JavaScript and returns result', async () => {
605
+ const mockToken = 'test_token_12345'
606
+
607
+ const mockWebSocket = class {
608
+ onopen: (() => void) | null = null
609
+ onmessage: ((event: { data: string }) => void) | null = null
610
+ onerror: ((error: unknown) => void) | null = null
611
+
612
+ constructor() {
613
+ setTimeout(() => {
614
+ this.onopen?.()
615
+ }, 10)
616
+ }
617
+
618
+ send(data: string) {
619
+ const message = JSON.parse(data)
620
+ setTimeout(() => {
621
+ this.onmessage?.({
622
+ data: JSON.stringify({
623
+ id: message.id,
624
+ result: { result: { value: mockToken } },
625
+ }),
626
+ })
627
+ }, 10)
628
+ }
629
+
630
+ close() {}
631
+ }
632
+
633
+ const originalWebSocket = globalThis.WebSocket
634
+ globalThis.WebSocket = mockWebSocket as unknown as typeof WebSocket
635
+
636
+ try {
637
+ const extractor = new DiscordTokenExtractor('darwin')
638
+ const result = await extractor.executeJSViaCDP(
639
+ 'ws://localhost:9222/devtools/page/1',
640
+ TOKEN_EXTRACTION_JS
641
+ )
642
+ expect(result).toBe(mockToken)
643
+ } finally {
644
+ globalThis.WebSocket = originalWebSocket
645
+ }
646
+ })
647
+
648
+ test('rejects on CDP error response', async () => {
649
+ const mockWebSocket = class {
650
+ onopen: (() => void) | null = null
651
+ onmessage: ((event: { data: string }) => void) | null = null
652
+ onerror: ((error: unknown) => void) | null = null
653
+
654
+ constructor() {
655
+ setTimeout(() => {
656
+ this.onopen?.()
657
+ }, 10)
658
+ }
659
+
660
+ send(data: string) {
661
+ const message = JSON.parse(data)
662
+ setTimeout(() => {
663
+ this.onmessage?.({
664
+ data: JSON.stringify({
665
+ id: message.id,
666
+ error: { code: -32000, message: 'Evaluation failed' },
667
+ }),
668
+ })
669
+ }, 10)
670
+ }
671
+
672
+ close() {}
673
+ }
674
+
675
+ const originalWebSocket = globalThis.WebSocket
676
+ globalThis.WebSocket = mockWebSocket as unknown as typeof WebSocket
677
+
678
+ try {
679
+ const extractor = new DiscordTokenExtractor('darwin')
680
+ await expect(
681
+ extractor.executeJSViaCDP('ws://localhost:9222/devtools/page/1', TOKEN_EXTRACTION_JS)
682
+ ).rejects.toThrow('Evaluation failed')
683
+ } finally {
684
+ globalThis.WebSocket = originalWebSocket
685
+ }
686
+ })
687
+
688
+ test('rejects on WebSocket error', async () => {
689
+ const mockWebSocket = class {
690
+ onopen: (() => void) | null = null
691
+ onmessage: ((event: { data: string }) => void) | null = null
692
+ onerror: ((error: unknown) => void) | null = null
693
+
694
+ constructor() {
695
+ setTimeout(() => {
696
+ this.onerror?.(new Error('Connection failed'))
697
+ }, 10)
698
+ }
699
+
700
+ send() {}
701
+ close() {}
702
+ }
703
+
704
+ const originalWebSocket = globalThis.WebSocket
705
+ globalThis.WebSocket = mockWebSocket as unknown as typeof WebSocket
706
+
707
+ try {
708
+ const extractor = new DiscordTokenExtractor('darwin')
709
+ await expect(
710
+ extractor.executeJSViaCDP('ws://localhost:9222/devtools/page/1', TOKEN_EXTRACTION_JS)
711
+ ).rejects.toThrow()
712
+ } finally {
713
+ globalThis.WebSocket = originalWebSocket
714
+ }
715
+ })
716
+ })
717
+
718
+ describe('extractViaCDP', () => {
719
+ test('returns null when no CDP targets available', async () => {
720
+ const originalFetch = globalThis.fetch
721
+ globalThis.fetch = mock(async () => ({
722
+ ok: true,
723
+ json: async () => [],
724
+ })) as unknown as typeof fetch
725
+
726
+ try {
727
+ const extractor = new DiscordTokenExtractor('darwin')
728
+ const result = await extractor.extractViaCDP(9222)
729
+ expect(result).toBeNull()
730
+ } finally {
731
+ globalThis.fetch = originalFetch
732
+ }
733
+ })
734
+
735
+ test('returns null when no Discord page target found', async () => {
736
+ const originalFetch = globalThis.fetch
737
+ globalThis.fetch = mock(async () => ({
738
+ ok: true,
739
+ json: async () => [
740
+ {
741
+ id: '1',
742
+ type: 'background_page',
743
+ title: 'background',
744
+ url: 'about:blank',
745
+ webSocketDebuggerUrl: 'ws://localhost:9222/devtools/page/1',
746
+ },
747
+ ],
748
+ })) as unknown as typeof fetch
749
+
750
+ try {
751
+ const extractor = new DiscordTokenExtractor('darwin')
752
+ const result = await extractor.extractViaCDP(9222)
753
+ expect(result).toBeNull()
754
+ } finally {
755
+ globalThis.fetch = originalFetch
756
+ }
757
+ })
758
+
759
+ test('extracts token via CDP when Discord is running with debug port', async () => {
760
+ const mockToken = 'MTIzNDU2Nzg5MDEyMzQ1Njc4.GrE5dA.abcdefghijklmnopqrstuvwxyz1234567890'
761
+
762
+ const originalFetch = globalThis.fetch
763
+ globalThis.fetch = mock(async () => ({
764
+ ok: true,
765
+ json: async () => [
766
+ {
767
+ id: '1',
768
+ type: 'page',
769
+ title: 'Discord',
770
+ url: 'https://discord.com/app',
771
+ webSocketDebuggerUrl: 'ws://localhost:9222/devtools/page/1',
772
+ },
773
+ ],
774
+ })) as unknown as typeof fetch
775
+
776
+ const mockWebSocket = class {
777
+ onopen: (() => void) | null = null
778
+ onmessage: ((event: { data: string }) => void) | null = null
779
+ onerror: ((error: unknown) => void) | null = null
780
+
781
+ constructor() {
782
+ setTimeout(() => this.onopen?.(), 10)
783
+ }
784
+
785
+ send(data: string) {
786
+ const message = JSON.parse(data)
787
+ setTimeout(() => {
788
+ this.onmessage?.({
789
+ data: JSON.stringify({
790
+ id: message.id,
791
+ result: { result: { value: mockToken } },
792
+ }),
793
+ })
794
+ }, 10)
795
+ }
796
+
797
+ close() {}
798
+ }
799
+
800
+ const originalWebSocket = globalThis.WebSocket
801
+ globalThis.WebSocket = mockWebSocket as unknown as typeof WebSocket
802
+
803
+ try {
804
+ const extractor = new DiscordTokenExtractor('darwin')
805
+ const result = await extractor.extractViaCDP(9222)
806
+ expect(result).toBe(mockToken)
807
+ } finally {
808
+ globalThis.fetch = originalFetch
809
+ globalThis.WebSocket = originalWebSocket
810
+ }
811
+ })
812
+
813
+ test('returns null when token extraction JS fails', async () => {
814
+ const originalFetch = globalThis.fetch
815
+ globalThis.fetch = mock(async () => ({
816
+ ok: true,
817
+ json: async () => [
818
+ {
819
+ id: '1',
820
+ type: 'page',
821
+ title: 'Discord',
822
+ url: 'https://discord.com/app',
823
+ webSocketDebuggerUrl: 'ws://localhost:9222/devtools/page/1',
824
+ },
825
+ ],
826
+ })) as unknown as typeof fetch
827
+
828
+ const mockWebSocket = class {
829
+ onopen: (() => void) | null = null
830
+ onmessage: ((event: { data: string }) => void) | null = null
831
+ onerror: ((error: unknown) => void) | null = null
832
+
833
+ constructor() {
834
+ setTimeout(() => this.onopen?.(), 10)
835
+ }
836
+
837
+ send(data: string) {
838
+ const message = JSON.parse(data)
839
+ setTimeout(() => {
840
+ this.onmessage?.({
841
+ data: JSON.stringify({
842
+ id: message.id,
843
+ error: { code: -32000, message: 'Cannot find module' },
844
+ }),
845
+ })
846
+ }, 10)
847
+ }
848
+
849
+ close() {}
850
+ }
851
+
852
+ const originalWebSocket = globalThis.WebSocket
853
+ globalThis.WebSocket = mockWebSocket as unknown as typeof WebSocket
854
+
855
+ try {
856
+ const extractor = new DiscordTokenExtractor('darwin')
857
+ const result = await extractor.extractViaCDP(9222)
858
+ expect(result).toBeNull()
859
+ } finally {
860
+ globalThis.fetch = originalFetch
861
+ globalThis.WebSocket = originalWebSocket
862
+ }
863
+ })
864
+
865
+ test('returns null when returned value is not a valid token', async () => {
866
+ const originalFetch = globalThis.fetch
867
+ globalThis.fetch = mock(async () => ({
868
+ ok: true,
869
+ json: async () => [
870
+ {
871
+ id: '1',
872
+ type: 'page',
873
+ title: 'Discord',
874
+ url: 'https://discord.com/app',
875
+ webSocketDebuggerUrl: 'ws://localhost:9222/devtools/page/1',
876
+ },
877
+ ],
878
+ })) as unknown as typeof fetch
879
+
880
+ const mockWebSocket = class {
881
+ onopen: (() => void) | null = null
882
+ onmessage: ((event: { data: string }) => void) | null = null
883
+ onerror: ((error: unknown) => void) | null = null
884
+
885
+ constructor() {
886
+ setTimeout(() => this.onopen?.(), 10)
887
+ }
888
+
889
+ send(data: string) {
890
+ const message = JSON.parse(data)
891
+ setTimeout(() => {
892
+ this.onmessage?.({
893
+ data: JSON.stringify({
894
+ id: message.id,
895
+ result: { result: { value: 'not_a_valid_token' } },
896
+ }),
897
+ })
898
+ }, 10)
899
+ }
900
+
901
+ close() {}
902
+ }
903
+
904
+ const originalWebSocket = globalThis.WebSocket
905
+ globalThis.WebSocket = mockWebSocket as unknown as typeof WebSocket
906
+
907
+ try {
908
+ const extractor = new DiscordTokenExtractor('darwin')
909
+ const result = await extractor.extractViaCDP(9222)
910
+ expect(result).toBeNull()
911
+ } finally {
912
+ globalThis.fetch = originalFetch
913
+ globalThis.WebSocket = originalWebSocket
914
+ }
915
+ })
916
+ })
917
+ })
918
+ })