agent-messenger 1.0.0 → 1.1.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 (330) hide show
  1. package/.claude/commands/release.md +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.github/workflows/ci.yml +1 -1
  4. package/.github/workflows/e2e.yml.disabled +69 -0
  5. package/README.md +16 -14
  6. package/biome.json +33 -1
  7. package/bun.lock +63 -0
  8. package/dist/package.json +8 -4
  9. package/dist/src/cli.d.ts.map +1 -1
  10. package/dist/src/cli.js +4 -1
  11. package/dist/src/cli.js.map +1 -1
  12. package/dist/src/platforms/discord/cli.js +1 -1
  13. package/dist/src/platforms/discord/client.d.ts.map +1 -1
  14. package/dist/src/platforms/discord/client.js +3 -3
  15. package/dist/src/platforms/discord/client.js.map +1 -1
  16. package/dist/src/platforms/discord/commands/user.d.ts.map +1 -1
  17. package/dist/src/platforms/discord/commands/user.js +10 -1
  18. package/dist/src/platforms/discord/commands/user.js.map +1 -1
  19. package/dist/src/platforms/discord/credential-manager.d.ts.map +1 -1
  20. package/dist/src/platforms/discord/credential-manager.js +18 -12
  21. package/dist/src/platforms/discord/credential-manager.js.map +1 -1
  22. package/dist/src/platforms/slack/cli.js +1 -1
  23. package/dist/src/platforms/slack/credential-manager.d.ts.map +1 -1
  24. package/dist/src/platforms/slack/credential-manager.js +20 -6
  25. package/dist/src/platforms/slack/credential-manager.js.map +1 -1
  26. package/dist/src/platforms/slack/token-extractor.d.ts.map +1 -1
  27. package/dist/src/platforms/slack/token-extractor.js +34 -9
  28. package/dist/src/platforms/slack/token-extractor.js.map +1 -1
  29. package/dist/src/platforms/teams/cli.d.ts.map +1 -0
  30. package/dist/{cli.js → src/platforms/teams/cli.js} +11 -10
  31. package/dist/src/platforms/teams/cli.js.map +1 -0
  32. package/dist/src/platforms/teams/client.d.ts +32 -0
  33. package/dist/src/platforms/teams/client.d.ts.map +1 -0
  34. package/dist/src/platforms/teams/client.js +202 -0
  35. package/dist/src/platforms/teams/client.js.map +1 -0
  36. package/dist/src/platforms/teams/commands/auth.d.ts +14 -0
  37. package/dist/src/platforms/teams/commands/auth.d.ts.map +1 -0
  38. package/dist/src/platforms/teams/commands/auth.js +176 -0
  39. package/dist/src/platforms/teams/commands/auth.js.map +1 -0
  40. package/dist/src/platforms/teams/commands/channel.d.ts +13 -0
  41. package/dist/src/platforms/teams/commands/channel.d.ts.map +1 -0
  42. package/dist/src/platforms/teams/commands/channel.js +97 -0
  43. package/dist/src/platforms/teams/commands/channel.js.map +1 -0
  44. package/dist/src/platforms/teams/commands/file.d.ts +12 -0
  45. package/dist/src/platforms/teams/commands/file.d.ts.map +1 -0
  46. package/dist/src/platforms/teams/commands/file.js +104 -0
  47. package/dist/src/platforms/teams/commands/file.js.map +1 -0
  48. package/dist/{commands → src/platforms/teams/commands}/index.d.ts +5 -2
  49. package/dist/src/platforms/teams/commands/index.d.ts.map +1 -0
  50. package/dist/{commands → src/platforms/teams/commands}/index.js +5 -2
  51. package/dist/src/platforms/teams/commands/index.js.map +1 -0
  52. package/dist/src/platforms/teams/commands/message.d.ts +17 -0
  53. package/dist/src/platforms/teams/commands/message.d.ts.map +1 -0
  54. package/dist/src/platforms/teams/commands/message.js +133 -0
  55. package/dist/src/platforms/teams/commands/message.js.map +1 -0
  56. package/dist/src/platforms/teams/commands/reaction.d.ts +9 -0
  57. package/dist/src/platforms/teams/commands/reaction.d.ts.map +1 -0
  58. package/dist/src/platforms/teams/commands/reaction.js +68 -0
  59. package/dist/src/platforms/teams/commands/reaction.js.map +1 -0
  60. package/dist/src/platforms/teams/commands/snapshot.d.ts +10 -0
  61. package/dist/src/platforms/teams/commands/snapshot.d.ts.map +1 -0
  62. package/dist/src/platforms/teams/commands/snapshot.js +85 -0
  63. package/dist/src/platforms/teams/commands/snapshot.js.map +1 -0
  64. package/dist/src/platforms/teams/commands/team.d.ts +18 -0
  65. package/dist/src/platforms/teams/commands/team.d.ts.map +1 -0
  66. package/dist/src/platforms/teams/commands/team.js +130 -0
  67. package/dist/src/platforms/teams/commands/team.js.map +1 -0
  68. package/dist/src/platforms/teams/commands/user.d.ts.map +1 -0
  69. package/dist/src/platforms/teams/commands/user.js +88 -0
  70. package/dist/src/platforms/teams/commands/user.js.map +1 -0
  71. package/dist/src/platforms/teams/credential-manager.d.ts +18 -0
  72. package/dist/src/platforms/teams/credential-manager.d.ts.map +1 -0
  73. package/dist/src/platforms/teams/credential-manager.js +81 -0
  74. package/dist/src/platforms/teams/credential-manager.js.map +1 -0
  75. package/dist/src/platforms/teams/index.d.ts +4 -0
  76. package/dist/src/platforms/teams/index.d.ts.map +1 -0
  77. package/dist/src/platforms/teams/index.js +6 -0
  78. package/dist/src/platforms/teams/index.js.map +1 -0
  79. package/dist/src/platforms/teams/token-extractor.d.ts +36 -0
  80. package/dist/src/platforms/teams/token-extractor.d.ts.map +1 -0
  81. package/dist/src/platforms/teams/token-extractor.js +335 -0
  82. package/dist/src/platforms/teams/token-extractor.js.map +1 -0
  83. package/dist/src/platforms/teams/types.d.ts +209 -0
  84. package/dist/src/platforms/teams/types.d.ts.map +1 -0
  85. package/dist/src/platforms/teams/types.js +65 -0
  86. package/dist/src/platforms/teams/types.js.map +1 -0
  87. package/docs/teams.md +321 -0
  88. package/e2e/README.md +256 -0
  89. package/e2e/config.ts +45 -0
  90. package/e2e/discord.e2e.test.ts +252 -0
  91. package/e2e/helpers.ts +107 -0
  92. package/e2e/slack.e2e.test.ts +309 -0
  93. package/package.json +8 -4
  94. package/scripts/postbuild.ts +15 -0
  95. package/skills/agent-teams/SKILL.md +292 -0
  96. package/skills/agent-teams/references/authentication.md +375 -0
  97. package/skills/agent-teams/references/common-patterns.md +596 -0
  98. package/skills/agent-teams/templates/monitor-channel.sh +239 -0
  99. package/skills/agent-teams/templates/post-message.sh +224 -0
  100. package/skills/agent-teams/templates/team-summary.sh +210 -0
  101. package/src/cli.ts +4 -0
  102. package/src/platforms/discord/client.ts +3 -3
  103. package/src/platforms/discord/commands/auth.test.ts +48 -32
  104. package/src/platforms/discord/commands/channel.test.ts +54 -42
  105. package/src/platforms/discord/commands/file.test.ts +40 -53
  106. package/src/platforms/discord/commands/guild.test.ts +47 -27
  107. package/src/platforms/discord/commands/message.test.ts +54 -51
  108. package/src/platforms/discord/commands/reaction.test.ts +54 -42
  109. package/src/platforms/discord/commands/user.ts +12 -1
  110. package/src/platforms/discord/credential-manager.test.ts +137 -136
  111. package/src/platforms/discord/credential-manager.ts +20 -13
  112. package/src/platforms/discord/token-extractor.test.ts +133 -383
  113. package/{tests → src/platforms/slack}/cli.test.ts +3 -3
  114. package/{tests/slack-client.test.ts → src/platforms/slack/client.test.ts} +1 -1
  115. package/{tests → src/platforms/slack}/commands/auth.test.ts +25 -13
  116. package/{tests → src/platforms/slack}/commands/channel.test.ts +2 -2
  117. package/{tests → src/platforms/slack}/commands/file.test.ts +2 -2
  118. package/{tests → src/platforms/slack}/commands/message.test.ts +2 -2
  119. package/{tests → src/platforms/slack}/commands/reaction.test.ts +1 -1
  120. package/{tests → src/platforms/slack}/commands/snapshot.test.ts +117 -105
  121. package/{tests → src/platforms/slack}/commands/user.test.ts +3 -3
  122. package/{tests → src/platforms/slack}/commands/workspace.test.ts +44 -95
  123. package/{tests → src/platforms/slack}/credential-manager.test.ts +2 -2
  124. package/src/platforms/slack/credential-manager.ts +22 -7
  125. package/src/platforms/slack/token-extractor-node-test.ts +40 -0
  126. package/src/platforms/slack/token-extractor-node.test.ts +10 -0
  127. package/src/platforms/slack/token-extractor.ts +36 -10
  128. package/{tests → src/platforms/slack}/types.test.ts +1 -1
  129. package/src/platforms/teams/cli.ts +36 -0
  130. package/src/platforms/teams/client.test.ts +500 -0
  131. package/src/platforms/teams/client.ts +365 -0
  132. package/src/platforms/teams/commands/auth.test.ts +99 -0
  133. package/src/platforms/teams/commands/auth.ts +232 -0
  134. package/src/platforms/teams/commands/channel.test.ts +147 -0
  135. package/src/platforms/teams/commands/channel.ts +129 -0
  136. package/src/platforms/teams/commands/file.test.ts +88 -0
  137. package/src/platforms/teams/commands/file.ts +144 -0
  138. package/src/platforms/teams/commands/index.ts +12 -0
  139. package/src/platforms/teams/commands/message.test.ts +110 -0
  140. package/src/platforms/teams/commands/message.ts +188 -0
  141. package/src/platforms/teams/commands/reaction.test.ts +87 -0
  142. package/src/platforms/teams/commands/reaction.ts +104 -0
  143. package/src/platforms/teams/commands/snapshot.test.ts +35 -0
  144. package/src/platforms/teams/commands/snapshot.ts +115 -0
  145. package/src/platforms/teams/commands/team.test.ts +157 -0
  146. package/src/platforms/teams/commands/team.ts +164 -0
  147. package/src/platforms/teams/commands/user.test.ts +83 -0
  148. package/src/platforms/teams/commands/user.ts +112 -0
  149. package/src/platforms/teams/credential-manager.test.ts +178 -0
  150. package/src/platforms/teams/credential-manager.ts +92 -0
  151. package/src/platforms/teams/index.ts +5 -0
  152. package/src/platforms/teams/token-extractor.test.ts +429 -0
  153. package/src/platforms/teams/token-extractor.ts +462 -0
  154. package/src/platforms/teams/types.test.ts +226 -0
  155. package/src/platforms/teams/types.ts +140 -0
  156. package/tsconfig.json +1 -1
  157. package/dist/cli.d.ts.map +0 -1
  158. package/dist/cli.js.map +0 -1
  159. package/dist/commands/auth.d.ts +0 -3
  160. package/dist/commands/auth.d.ts.map +0 -1
  161. package/dist/commands/auth.js +0 -140
  162. package/dist/commands/auth.js.map +0 -1
  163. package/dist/commands/channel.d.ts +0 -3
  164. package/dist/commands/channel.d.ts.map +0 -1
  165. package/dist/commands/channel.js +0 -118
  166. package/dist/commands/channel.js.map +0 -1
  167. package/dist/commands/file.d.ts +0 -3
  168. package/dist/commands/file.d.ts.map +0 -1
  169. package/dist/commands/file.js +0 -113
  170. package/dist/commands/file.js.map +0 -1
  171. package/dist/commands/index.d.ts.map +0 -1
  172. package/dist/commands/index.js.map +0 -1
  173. package/dist/commands/message.d.ts +0 -3
  174. package/dist/commands/message.d.ts.map +0 -1
  175. package/dist/commands/message.js +0 -214
  176. package/dist/commands/message.js.map +0 -1
  177. package/dist/commands/reaction.d.ts +0 -3
  178. package/dist/commands/reaction.d.ts.map +0 -1
  179. package/dist/commands/reaction.js +0 -100
  180. package/dist/commands/reaction.js.map +0 -1
  181. package/dist/commands/snapshot.d.ts +0 -3
  182. package/dist/commands/snapshot.d.ts.map +0 -1
  183. package/dist/commands/snapshot.js +0 -88
  184. package/dist/commands/snapshot.js.map +0 -1
  185. package/dist/commands/user.d.ts.map +0 -1
  186. package/dist/commands/user.js +0 -96
  187. package/dist/commands/user.js.map +0 -1
  188. package/dist/commands/workspace.d.ts +0 -3
  189. package/dist/commands/workspace.d.ts.map +0 -1
  190. package/dist/commands/workspace.js +0 -89
  191. package/dist/commands/workspace.js.map +0 -1
  192. package/dist/lib/credential-manager.d.ts +0 -13
  193. package/dist/lib/credential-manager.d.ts.map +0 -1
  194. package/dist/lib/credential-manager.js +0 -58
  195. package/dist/lib/credential-manager.js.map +0 -1
  196. package/dist/lib/index.d.ts +0 -3
  197. package/dist/lib/index.d.ts.map +0 -1
  198. package/dist/lib/index.js +0 -3
  199. package/dist/lib/index.js.map +0 -1
  200. package/dist/lib/ref-manager.d.ts +0 -26
  201. package/dist/lib/ref-manager.d.ts.map +0 -1
  202. package/dist/lib/ref-manager.js +0 -92
  203. package/dist/lib/ref-manager.js.map +0 -1
  204. package/dist/lib/slack-client.d.ts +0 -37
  205. package/dist/lib/slack-client.d.ts.map +0 -1
  206. package/dist/lib/slack-client.js +0 -379
  207. package/dist/lib/slack-client.js.map +0 -1
  208. package/dist/lib/token-extractor.d.ts +0 -28
  209. package/dist/lib/token-extractor.d.ts.map +0 -1
  210. package/dist/lib/token-extractor.js +0 -401
  211. package/dist/lib/token-extractor.js.map +0 -1
  212. package/dist/src/platforms/discord/client.test.d.ts +0 -2
  213. package/dist/src/platforms/discord/client.test.d.ts.map +0 -1
  214. package/dist/src/platforms/discord/client.test.js +0 -367
  215. package/dist/src/platforms/discord/client.test.js.map +0 -1
  216. package/dist/src/platforms/discord/commands/auth.test.d.ts +0 -2
  217. package/dist/src/platforms/discord/commands/auth.test.d.ts.map +0 -1
  218. package/dist/src/platforms/discord/commands/auth.test.js +0 -65
  219. package/dist/src/platforms/discord/commands/auth.test.js.map +0 -1
  220. package/dist/src/platforms/discord/commands/channel.test.d.ts +0 -2
  221. package/dist/src/platforms/discord/commands/channel.test.d.ts.map +0 -1
  222. package/dist/src/platforms/discord/commands/channel.test.js +0 -136
  223. package/dist/src/platforms/discord/commands/channel.test.js.map +0 -1
  224. package/dist/src/platforms/discord/commands/file.test.d.ts +0 -2
  225. package/dist/src/platforms/discord/commands/file.test.d.ts.map +0 -1
  226. package/dist/src/platforms/discord/commands/file.test.js +0 -83
  227. package/dist/src/platforms/discord/commands/file.test.js.map +0 -1
  228. package/dist/src/platforms/discord/commands/guild.test.d.ts +0 -2
  229. package/dist/src/platforms/discord/commands/guild.test.d.ts.map +0 -1
  230. package/dist/src/platforms/discord/commands/guild.test.js +0 -100
  231. package/dist/src/platforms/discord/commands/guild.test.js.map +0 -1
  232. package/dist/src/platforms/discord/commands/message.test.d.ts +0 -2
  233. package/dist/src/platforms/discord/commands/message.test.d.ts.map +0 -1
  234. package/dist/src/platforms/discord/commands/message.test.js +0 -91
  235. package/dist/src/platforms/discord/commands/message.test.js.map +0 -1
  236. package/dist/src/platforms/discord/commands/reaction.test.d.ts +0 -2
  237. package/dist/src/platforms/discord/commands/reaction.test.d.ts.map +0 -1
  238. package/dist/src/platforms/discord/commands/reaction.test.js +0 -115
  239. package/dist/src/platforms/discord/commands/reaction.test.js.map +0 -1
  240. package/dist/src/platforms/discord/commands/snapshot.test.d.ts +0 -2
  241. package/dist/src/platforms/discord/commands/snapshot.test.d.ts.map +0 -1
  242. package/dist/src/platforms/discord/commands/snapshot.test.js +0 -25
  243. package/dist/src/platforms/discord/commands/snapshot.test.js.map +0 -1
  244. package/dist/src/platforms/discord/commands/user.test.d.ts +0 -2
  245. package/dist/src/platforms/discord/commands/user.test.d.ts.map +0 -1
  246. package/dist/src/platforms/discord/commands/user.test.js +0 -103
  247. package/dist/src/platforms/discord/commands/user.test.js.map +0 -1
  248. package/dist/src/platforms/discord/credential-manager.test.d.ts +0 -2
  249. package/dist/src/platforms/discord/credential-manager.test.d.ts.map +0 -1
  250. package/dist/src/platforms/discord/credential-manager.test.js +0 -136
  251. package/dist/src/platforms/discord/credential-manager.test.js.map +0 -1
  252. package/dist/src/platforms/discord/token-extractor.test.d.ts +0 -2
  253. package/dist/src/platforms/discord/token-extractor.test.d.ts.map +0 -1
  254. package/dist/src/platforms/discord/token-extractor.test.js +0 -789
  255. package/dist/src/platforms/discord/token-extractor.test.js.map +0 -1
  256. package/dist/src/platforms/discord/types.test.d.ts +0 -2
  257. package/dist/src/platforms/discord/types.test.d.ts.map +0 -1
  258. package/dist/src/platforms/discord/types.test.js +0 -211
  259. package/dist/src/platforms/discord/types.test.js.map +0 -1
  260. package/dist/src/shared/utils/concurrency.test.d.ts +0 -2
  261. package/dist/src/shared/utils/concurrency.test.d.ts.map +0 -1
  262. package/dist/src/shared/utils/concurrency.test.js +0 -39
  263. package/dist/src/shared/utils/concurrency.test.js.map +0 -1
  264. package/dist/tests/cli.test.d.ts +0 -2
  265. package/dist/tests/cli.test.d.ts.map +0 -1
  266. package/dist/tests/cli.test.js +0 -83
  267. package/dist/tests/cli.test.js.map +0 -1
  268. package/dist/tests/commands/.test-slack-data/Local Storage/leveldb/CURRENT +0 -1
  269. package/dist/tests/commands/.test-slack-data/Local Storage/leveldb/LOCK +0 -0
  270. package/dist/tests/commands/.test-slack-data/Local Storage/leveldb/LOG +0 -3
  271. package/dist/tests/commands/.test-slack-data/Local Storage/leveldb/LOG.old +0 -1
  272. package/dist/tests/commands/.test-slack-data/Local Storage/leveldb/MANIFEST-000004 +0 -0
  273. package/dist/tests/commands/auth.test.d.ts +0 -2
  274. package/dist/tests/commands/auth.test.d.ts.map +0 -1
  275. package/dist/tests/commands/auth.test.js +0 -304
  276. package/dist/tests/commands/auth.test.js.map +0 -1
  277. package/dist/tests/commands/channel.test.d.ts +0 -2
  278. package/dist/tests/commands/channel.test.d.ts.map +0 -1
  279. package/dist/tests/commands/channel.test.js +0 -166
  280. package/dist/tests/commands/channel.test.js.map +0 -1
  281. package/dist/tests/commands/file.test.d.ts +0 -2
  282. package/dist/tests/commands/file.test.d.ts.map +0 -1
  283. package/dist/tests/commands/file.test.js +0 -175
  284. package/dist/tests/commands/file.test.js.map +0 -1
  285. package/dist/tests/commands/message.test.d.ts +0 -2
  286. package/dist/tests/commands/message.test.d.ts.map +0 -1
  287. package/dist/tests/commands/message.test.js +0 -293
  288. package/dist/tests/commands/message.test.js.map +0 -1
  289. package/dist/tests/commands/reaction.test.d.ts +0 -2
  290. package/dist/tests/commands/reaction.test.d.ts.map +0 -1
  291. package/dist/tests/commands/reaction.test.js +0 -84
  292. package/dist/tests/commands/reaction.test.js.map +0 -1
  293. package/dist/tests/commands/snapshot.test.d.ts +0 -2
  294. package/dist/tests/commands/snapshot.test.d.ts.map +0 -1
  295. package/dist/tests/commands/snapshot.test.js +0 -280
  296. package/dist/tests/commands/snapshot.test.js.map +0 -1
  297. package/dist/tests/commands/user.test.d.ts +0 -2
  298. package/dist/tests/commands/user.test.d.ts.map +0 -1
  299. package/dist/tests/commands/user.test.js +0 -117
  300. package/dist/tests/commands/user.test.js.map +0 -1
  301. package/dist/tests/commands/workspace.test.d.ts +0 -2
  302. package/dist/tests/commands/workspace.test.d.ts.map +0 -1
  303. package/dist/tests/commands/workspace.test.js +0 -453
  304. package/dist/tests/commands/workspace.test.js.map +0 -1
  305. package/dist/tests/credential-manager.test.d.ts +0 -2
  306. package/dist/tests/credential-manager.test.d.ts.map +0 -1
  307. package/dist/tests/credential-manager.test.js +0 -199
  308. package/dist/tests/credential-manager.test.js.map +0 -1
  309. package/dist/tests/slack-client.test.d.ts +0 -2
  310. package/dist/tests/slack-client.test.d.ts.map +0 -1
  311. package/dist/tests/slack-client.test.js +0 -741
  312. package/dist/tests/slack-client.test.js.map +0 -1
  313. package/dist/tests/types.test.d.ts +0 -2
  314. package/dist/tests/types.test.d.ts.map +0 -1
  315. package/dist/tests/types.test.js +0 -215
  316. package/dist/tests/types.test.js.map +0 -1
  317. package/dist/types/index.d.ts +0 -369
  318. package/dist/types/index.d.ts.map +0 -1
  319. package/dist/types/index.js +0 -92
  320. package/dist/types/index.js.map +0 -1
  321. package/dist/utils/error-handler.d.ts +0 -2
  322. package/dist/utils/error-handler.d.ts.map +0 -1
  323. package/dist/utils/error-handler.js +0 -5
  324. package/dist/utils/error-handler.js.map +0 -1
  325. package/dist/utils/output.d.ts +0 -2
  326. package/dist/utils/output.d.ts.map +0 -1
  327. package/dist/utils/output.js +0 -4
  328. package/dist/utils/output.js.map +0 -1
  329. /package/dist/{cli.d.ts → src/platforms/teams/cli.d.ts} +0 -0
  330. /package/dist/{commands → src/platforms/teams/commands}/user.d.ts +0 -0
@@ -0,0 +1,252 @@
1
+ import { describe, test, expect, beforeAll, afterEach } from 'bun:test'
2
+ import {
3
+ runCLI, parseJSON, generateTestId, createTestMessage,
4
+ deleteTestMessage, waitForRateLimit, cleanupMessages
5
+ } from './helpers'
6
+ import {
7
+ DISCORD_TEST_CHANNEL_ID,
8
+ DISCORD_TEST_GUILD_ID,
9
+ validateDiscordEnvironment
10
+ } from './config'
11
+
12
+ // Track messages created during tests for cleanup
13
+ let testMessages: string[] = []
14
+
15
+ describe('Discord E2E Tests', () => {
16
+ beforeAll(async () => {
17
+ await validateDiscordEnvironment()
18
+ })
19
+
20
+ afterEach(async () => {
21
+ if (testMessages.length > 0) {
22
+ await cleanupMessages('discord', DISCORD_TEST_CHANNEL_ID, testMessages)
23
+ testMessages = []
24
+ }
25
+ await waitForRateLimit()
26
+ })
27
+
28
+ describe('auth', () => {
29
+ test('auth status returns authenticated status', async () => {
30
+ const result = await runCLI('discord', ['auth', 'status'])
31
+ expect(result.exitCode).toBe(0)
32
+
33
+ const data = parseJSON<{ authenticated: boolean }>(result.stdout)
34
+ expect(data?.authenticated).toBe(true)
35
+ })
36
+ })
37
+
38
+ describe('guild', () => {
39
+ test('guild list returns array', async () => {
40
+ const result = await runCLI('discord', ['guild', 'list'])
41
+ expect(result.exitCode).toBe(0)
42
+
43
+ const data = parseJSON<Array<{ guild_id: string }>>(result.stdout)
44
+ expect(Array.isArray(data)).toBe(true)
45
+ })
46
+
47
+ test('guild current returns current guild', async () => {
48
+ const result = await runCLI('discord', ['guild', 'current'])
49
+ expect(result.exitCode).toBe(0)
50
+
51
+ const data = parseJSON<{ guild_id: string }>(result.stdout)
52
+ expect(data?.guild_id).toBe(DISCORD_TEST_GUILD_ID)
53
+ })
54
+
55
+ test('guild info returns guild details', async () => {
56
+ const result = await runCLI('discord', ['guild', 'info', DISCORD_TEST_GUILD_ID])
57
+ expect(result.exitCode).toBe(0)
58
+
59
+ const data = parseJSON<{ id: string }>(result.stdout)
60
+ expect(data?.id).toBeTruthy()
61
+ })
62
+ })
63
+
64
+ describe('message', () => {
65
+ test('message send creates message', async () => {
66
+ const testId = generateTestId()
67
+ const result = await runCLI('discord', ['message', 'send', DISCORD_TEST_CHANNEL_ID, `Test message ${testId}`])
68
+ expect(result.exitCode).toBe(0)
69
+
70
+ const data = parseJSON<{ id: string }>(result.stdout)
71
+ expect(data?.id).toBeTruthy()
72
+
73
+ if (data?.id) testMessages.push(data.id)
74
+ })
75
+
76
+ test('message list returns messages array', async () => {
77
+ const result = await runCLI('discord', ['message', 'list', DISCORD_TEST_CHANNEL_ID, '--limit', '5'])
78
+ expect(result.exitCode).toBe(0)
79
+
80
+ const data = parseJSON<Array<{ id: string }>>(result.stdout)
81
+ expect(Array.isArray(data)).toBe(true)
82
+ })
83
+
84
+ test.skip('message get retrieves specific message (requires bot token)', async () => {
85
+ const testId = generateTestId()
86
+ const { id } = await createTestMessage('discord', DISCORD_TEST_CHANNEL_ID, `Get test ${testId}`)
87
+ testMessages.push(id)
88
+
89
+ await waitForRateLimit()
90
+
91
+ const result = await runCLI('discord', ['message', 'get', DISCORD_TEST_CHANNEL_ID, id])
92
+ expect(result.exitCode).toBe(0)
93
+
94
+ const data = parseJSON<{ content: string }>(result.stdout)
95
+ expect(data?.content).toContain(testId)
96
+ })
97
+
98
+ test('message delete removes message', async () => {
99
+ const testId = generateTestId()
100
+ const { id } = await createTestMessage('discord', DISCORD_TEST_CHANNEL_ID, `Delete me ${testId}`)
101
+
102
+ await waitForRateLimit()
103
+
104
+ const result = await runCLI('discord', ['message', 'delete', DISCORD_TEST_CHANNEL_ID, id, '--force'])
105
+ expect(result.exitCode).toBe(0)
106
+ })
107
+ })
108
+
109
+ describe('channel', () => {
110
+ test('channel list returns channels array', async () => {
111
+ const result = await runCLI('discord', ['channel', 'list'])
112
+ expect(result.exitCode).toBe(0)
113
+
114
+ const data = parseJSON<Array<{ id: string }>>(result.stdout)
115
+ expect(Array.isArray(data)).toBe(true)
116
+ })
117
+
118
+ test('channel info returns channel details', async () => {
119
+ const result = await runCLI('discord', ['channel', 'info', DISCORD_TEST_CHANNEL_ID])
120
+ expect(result.exitCode).toBe(0)
121
+
122
+ const data = parseJSON<{ id: string }>(result.stdout)
123
+ expect(data?.id).toBe(DISCORD_TEST_CHANNEL_ID)
124
+ })
125
+
126
+ test('channel history returns messages', async () => {
127
+ const result = await runCLI('discord', ['channel', 'history', DISCORD_TEST_CHANNEL_ID, '--limit', '5'])
128
+ expect(result.exitCode).toBe(0)
129
+
130
+ const data = parseJSON<Array<{ id: string }>>(result.stdout)
131
+ expect(Array.isArray(data)).toBe(true)
132
+ })
133
+ })
134
+
135
+ describe('user', () => {
136
+ test.skip('user list returns users array (requires bot token)', async () => {
137
+ const result = await runCLI('discord', ['user', 'list'])
138
+ expect(result.exitCode).toBe(0)
139
+
140
+ const data = parseJSON<Array<{ id: string }>>(result.stdout)
141
+ expect(Array.isArray(data)).toBe(true)
142
+ })
143
+
144
+ test('user me returns current user', async () => {
145
+ const result = await runCLI('discord', ['user', 'me'])
146
+ expect(result.exitCode).toBe(0)
147
+
148
+ const data = parseJSON<{ id: string; username: string }>(result.stdout)
149
+ expect(data?.id).toBeTruthy()
150
+ expect(data?.username).toBeTruthy()
151
+ })
152
+
153
+ test('user info returns user details', async () => {
154
+ // First get current user ID
155
+ const meResult = await runCLI('discord', ['user', 'me'])
156
+ expect(meResult.exitCode).toBe(0)
157
+
158
+ const me = parseJSON<{ id: string }>(meResult.stdout)
159
+ expect(me?.id).toBeTruthy()
160
+
161
+ await waitForRateLimit()
162
+
163
+ const result = await runCLI('discord', ['user', 'info', me!.id])
164
+ expect(result.exitCode).toBe(0)
165
+
166
+ const data = parseJSON<{ id: string }>(result.stdout)
167
+ expect(data?.id).toBe(me?.id)
168
+ })
169
+ })
170
+
171
+ describe('reaction', () => {
172
+ test.skip('reaction add/list/remove lifecycle (requires bot token)', async () => {
173
+ const testId = generateTestId()
174
+ const { id } = await createTestMessage('discord', DISCORD_TEST_CHANNEL_ID, `Reaction test ${testId}`)
175
+ testMessages.push(id)
176
+
177
+ await waitForRateLimit()
178
+
179
+ // Add reaction (using emoji name without colons)
180
+ const addResult = await runCLI('discord', ['reaction', 'add', DISCORD_TEST_CHANNEL_ID, id, '👍'])
181
+ expect(addResult.exitCode).toBe(0)
182
+
183
+ await waitForRateLimit()
184
+
185
+ // List reactions
186
+ const listResult = await runCLI('discord', ['reaction', 'list', DISCORD_TEST_CHANNEL_ID, id])
187
+ expect(listResult.exitCode).toBe(0)
188
+
189
+ await waitForRateLimit()
190
+
191
+ // Remove reaction
192
+ const removeResult = await runCLI('discord', ['reaction', 'remove', DISCORD_TEST_CHANNEL_ID, id, '👍'])
193
+ expect(removeResult.exitCode).toBe(0)
194
+ }, 15000) // Longer timeout for multiple operations
195
+ })
196
+
197
+ describe('file', () => {
198
+ test('file list returns files array', async () => {
199
+ const result = await runCLI('discord', ['file', 'list', DISCORD_TEST_CHANNEL_ID])
200
+ expect(result.exitCode).toBe(0)
201
+
202
+ const data = parseJSON<Array<{ id: string }>>(result.stdout)
203
+ expect(Array.isArray(data)).toBe(true)
204
+ })
205
+
206
+ test('file upload uploads file', async () => {
207
+ const testId = generateTestId()
208
+ const testFilePath = `/tmp/discord-e2e-${testId}.txt`
209
+ await Bun.write(testFilePath, `Discord E2E test file ${testId}`)
210
+
211
+ try {
212
+ const result = await runCLI('discord', ['file', 'upload', DISCORD_TEST_CHANNEL_ID, testFilePath])
213
+ expect(result.exitCode).toBe(0)
214
+
215
+ const data = parseJSON<{ id: string }>(result.stdout)
216
+ expect(data?.id).toBeTruthy()
217
+
218
+ // Track message for cleanup (file uploads create messages)
219
+ if (data?.id) testMessages.push(data.id)
220
+ } finally {
221
+ await Bun.$`rm -f ${testFilePath}`.quiet()
222
+ }
223
+ })
224
+ })
225
+
226
+ describe('snapshot', () => {
227
+ test.skip('snapshot returns full guild data (requires bot token)', async () => {
228
+ const result = await runCLI('discord', ['snapshot', '--limit', '2'])
229
+ expect(result.exitCode).toBe(0)
230
+
231
+ const data = parseJSON<{ channels: unknown[]; users: unknown[] }>(result.stdout)
232
+ expect(data?.channels).toBeDefined()
233
+ expect(data?.users).toBeDefined()
234
+ })
235
+
236
+ test('snapshot --channels-only returns only channels', async () => {
237
+ const result = await runCLI('discord', ['snapshot', '--channels-only'])
238
+ expect(result.exitCode).toBe(0)
239
+
240
+ const data = parseJSON<{ channels: unknown[] }>(result.stdout)
241
+ expect(data?.channels).toBeDefined()
242
+ })
243
+
244
+ test.skip('snapshot --users-only returns only users (requires bot token)', async () => {
245
+ const result = await runCLI('discord', ['snapshot', '--users-only'])
246
+ expect(result.exitCode).toBe(0)
247
+
248
+ const data = parseJSON<{ users: unknown[] }>(result.stdout)
249
+ expect(data?.users).toBeDefined()
250
+ })
251
+ })
252
+ })
package/e2e/helpers.ts ADDED
@@ -0,0 +1,107 @@
1
+ import { $ } from 'bun'
2
+
3
+ export interface CLIResult {
4
+ exitCode: number
5
+ stdout: string
6
+ stderr: string
7
+ }
8
+
9
+ export async function runCLI(platform: string, args: string[]): Promise<CLIResult> {
10
+ const command = platform === 'slack' ? 'agent-slack' : 'agent-discord'
11
+
12
+ try {
13
+ const result = await $`${command} ${args}`.quiet()
14
+ return {
15
+ exitCode: result.exitCode,
16
+ stdout: result.stdout.toString(),
17
+ stderr: result.stderr.toString(),
18
+ }
19
+ } catch (error: any) {
20
+ return {
21
+ exitCode: error.exitCode || 1,
22
+ stdout: error.stdout?.toString() || '',
23
+ stderr: error.stderr?.toString() || '',
24
+ }
25
+ }
26
+ }
27
+
28
+ export function parseJSON<T>(output: string): T | null {
29
+ try {
30
+ return JSON.parse(output) as T
31
+ } catch {
32
+ return null
33
+ }
34
+ }
35
+
36
+ export function generateTestId(): string {
37
+ return `e2e-${Date.now()}-${Math.random().toString(36).substring(7)}`
38
+ }
39
+
40
+ export async function createTestMessage(
41
+ platform: string,
42
+ channel: string,
43
+ text: string
44
+ ): Promise<{ id: string }> {
45
+ const result = await runCLI(platform, ['message', 'send', channel, text])
46
+ if (result.exitCode !== 0) {
47
+ throw new Error(`Failed to create test message: ${result.stderr}`)
48
+ }
49
+
50
+ const data = parseJSON<{ ts?: string; id?: string }>(result.stdout)
51
+ const messageId = data?.ts || data?.id
52
+ if (!messageId) {
53
+ throw new Error('No message ID returned')
54
+ }
55
+
56
+ return { id: messageId }
57
+ }
58
+
59
+ export async function deleteTestMessage(
60
+ platform: string,
61
+ channel: string,
62
+ messageId: string
63
+ ): Promise<void> {
64
+ // For Slack, check for thread replies and delete them first
65
+ if (platform === 'slack') {
66
+ try {
67
+ const repliesResult = await runCLI(platform, ['message', 'replies', channel, messageId])
68
+ if (repliesResult.exitCode === 0) {
69
+ const replies = parseJSON<Array<{ ts?: string; id?: string }>>(repliesResult.stdout)
70
+ if (replies && replies.length > 0) {
71
+ // Delete replies in reverse order (newest first), skip parent
72
+ const threadReplies = replies.filter(r => (r.ts || r.id) !== messageId)
73
+ for (const reply of threadReplies.reverse()) {
74
+ const replyId = reply.ts || reply.id
75
+ if (replyId) {
76
+ await runCLI(platform, ['message', 'delete', channel, replyId, '--force'])
77
+ }
78
+ }
79
+ }
80
+ }
81
+ } catch {
82
+ // Continue with parent deletion even if replies fail
83
+ }
84
+ }
85
+
86
+ // Delete the parent message
87
+ await runCLI(platform, ['message', 'delete', channel, messageId, '--force'])
88
+ }
89
+
90
+ export async function waitForRateLimit(ms: number = 1000): Promise<void> {
91
+ await new Promise(resolve => setTimeout(resolve, ms))
92
+ }
93
+
94
+ export async function cleanupMessages(
95
+ platform: string,
96
+ channel: string,
97
+ messageIds: string[]
98
+ ): Promise<void> {
99
+ for (const id of messageIds) {
100
+ try {
101
+ await deleteTestMessage(platform, channel, id)
102
+ await waitForRateLimit(500)
103
+ } catch (error) {
104
+ console.warn(`Failed to cleanup message ${id}:`, error)
105
+ }
106
+ }
107
+ }
@@ -0,0 +1,309 @@
1
+ import { describe, test, expect, beforeAll, afterEach } from 'bun:test'
2
+ import {
3
+ runCLI, parseJSON, generateTestId, createTestMessage,
4
+ deleteTestMessage, waitForRateLimit, cleanupMessages
5
+ } from './helpers'
6
+ import {
7
+ SLACK_TEST_CHANNEL, SLACK_TEST_CHANNEL_ID,
8
+ validateSlackEnvironment
9
+ } from './config'
10
+
11
+ // Track messages created during tests for cleanup
12
+ let testMessages: string[] = []
13
+
14
+ describe('Slack E2E Tests', () => {
15
+ beforeAll(async () => {
16
+ // Validate we're in the correct workspace
17
+ await validateSlackEnvironment()
18
+ })
19
+
20
+ afterEach(async () => {
21
+ if (testMessages.length > 0) {
22
+ await cleanupMessages('slack', SLACK_TEST_CHANNEL_ID, testMessages)
23
+ testMessages = []
24
+ }
25
+ await waitForRateLimit()
26
+ })
27
+
28
+ describe('auth', () => {
29
+ test('auth status returns authenticated workspace info', async () => {
30
+ const result = await runCLI('slack', ['auth', 'status'])
31
+ expect(result.exitCode).toBe(0)
32
+
33
+ const data = parseJSON<{ workspace_id: string; workspace_name: string }>(result.stdout)
34
+ expect(data).not.toBeNull()
35
+ expect(data?.workspace_id).toBeTruthy()
36
+ expect(data?.workspace_name).toBeTruthy()
37
+ })
38
+ })
39
+
40
+ describe('workspace', () => {
41
+ test('workspace list returns array', async () => {
42
+ const result = await runCLI('slack', ['workspace', 'list'])
43
+ expect(result.exitCode).toBe(0)
44
+
45
+ const data = parseJSON<Array<{ id: string; name: string }>>(result.stdout)
46
+ expect(Array.isArray(data)).toBe(true)
47
+ })
48
+
49
+ test('workspace current returns current workspace', async () => {
50
+ const result = await runCLI('slack', ['workspace', 'current'])
51
+ expect(result.exitCode).toBe(0)
52
+
53
+ const data = parseJSON<{ workspace_id: string }>(result.stdout)
54
+ expect(data?.workspace_id).toBeTruthy()
55
+ })
56
+ })
57
+
58
+ describe('message', () => {
59
+ test('message send creates message and returns ts', async () => {
60
+ const testId = generateTestId()
61
+ const result = await runCLI('slack', ['message', 'send', SLACK_TEST_CHANNEL_ID, `Test message ${testId}`])
62
+ expect(result.exitCode).toBe(0)
63
+
64
+ const data = parseJSON<{ ts: string }>(result.stdout)
65
+ expect(data?.ts).toBeTruthy()
66
+
67
+ if (data?.ts) testMessages.push(data.ts)
68
+ })
69
+
70
+ test('message list returns messages array', async () => {
71
+ const result = await runCLI('slack', ['message', 'list', SLACK_TEST_CHANNEL_ID, '--limit', '5'])
72
+ expect(result.exitCode).toBe(0)
73
+
74
+ const data = parseJSON<Array<{ ts: string }>>(result.stdout)
75
+ expect(Array.isArray(data)).toBe(true)
76
+ })
77
+
78
+ test('message get retrieves specific message', async () => {
79
+ const testId = generateTestId()
80
+ const { id: ts } = await createTestMessage('slack', SLACK_TEST_CHANNEL_ID, `Get test ${testId}`)
81
+ testMessages.push(ts)
82
+
83
+ await waitForRateLimit()
84
+
85
+ const result = await runCLI('slack', ['message', 'get', SLACK_TEST_CHANNEL_ID, ts])
86
+ expect(result.exitCode).toBe(0)
87
+
88
+ const data = parseJSON<{ text: string }>(result.stdout)
89
+ expect(data?.text).toContain(testId)
90
+ })
91
+
92
+ test('message update modifies message', async () => {
93
+ const testId = generateTestId()
94
+ const { id: ts } = await createTestMessage('slack', SLACK_TEST_CHANNEL_ID, `Original ${testId}`)
95
+ testMessages.push(ts)
96
+
97
+ await waitForRateLimit()
98
+
99
+ const result = await runCLI('slack', ['message', 'update', SLACK_TEST_CHANNEL_ID, ts, `Updated ${testId}`])
100
+ expect(result.exitCode).toBe(0)
101
+
102
+ await waitForRateLimit()
103
+ const getResult = await runCLI('slack', ['message', 'get', SLACK_TEST_CHANNEL_ID, ts])
104
+ const data = parseJSON<{ text: string }>(getResult.stdout)
105
+ expect(data?.text).toContain('Updated')
106
+ })
107
+
108
+ test('message search finds messages', async () => {
109
+ const result = await runCLI('slack', ['message', 'search', 'test', '--limit', '5'])
110
+ expect(result.exitCode).toBe(0)
111
+
112
+ const data = parseJSON<Array<{ ts: string }>>(result.stdout)
113
+ expect(Array.isArray(data)).toBe(true)
114
+ })
115
+
116
+ test('message send with thread creates reply', async () => {
117
+ const testId = generateTestId()
118
+ const { id: parentTs } = await createTestMessage('slack', SLACK_TEST_CHANNEL_ID, `Parent ${testId}`)
119
+ testMessages.push(parentTs)
120
+
121
+ await waitForRateLimit()
122
+
123
+ const result = await runCLI('slack', ['message', 'send', SLACK_TEST_CHANNEL_ID, `Reply ${testId}`, '--thread', parentTs])
124
+ expect(result.exitCode).toBe(0)
125
+
126
+ const data = parseJSON<{ ts: string; thread_ts: string }>(result.stdout)
127
+ expect(data?.thread_ts).toBe(parentTs)
128
+
129
+ if (data?.ts) testMessages.push(data.ts)
130
+ }, 30000)
131
+
132
+ test('message replies gets thread replies', async () => {
133
+ const testId = generateTestId()
134
+ const { id: parentTs } = await createTestMessage('slack', SLACK_TEST_CHANNEL_ID, `Thread parent ${testId}`)
135
+ testMessages.push(parentTs)
136
+
137
+ await waitForRateLimit()
138
+
139
+ await runCLI('slack', ['message', 'send', SLACK_TEST_CHANNEL_ID, `Thread reply ${testId}`, '--thread', parentTs])
140
+
141
+ await waitForRateLimit()
142
+
143
+ const result = await runCLI('slack', ['message', 'replies', SLACK_TEST_CHANNEL_ID, parentTs])
144
+ expect(result.exitCode).toBe(0)
145
+
146
+ const data = parseJSON<Array<{ ts: string }>>(result.stdout)
147
+ expect(Array.isArray(data)).toBe(true)
148
+ expect(data?.length).toBeGreaterThanOrEqual(1)
149
+ })
150
+
151
+ test('message delete removes message', async () => {
152
+ const testId = generateTestId()
153
+ const { id: ts } = await createTestMessage('slack', SLACK_TEST_CHANNEL_ID, `Delete me ${testId}`)
154
+
155
+ await waitForRateLimit()
156
+
157
+ const result = await runCLI('slack', ['message', 'delete', SLACK_TEST_CHANNEL_ID, ts, '--force'])
158
+ expect(result.exitCode).toBe(0)
159
+ })
160
+ })
161
+
162
+ describe('channel', () => {
163
+ test('channel list returns channels array', async () => {
164
+ const result = await runCLI('slack', ['channel', 'list'])
165
+ expect(result.exitCode).toBe(0)
166
+
167
+ const data = parseJSON<Array<{ id: string; name: string }>>(result.stdout)
168
+ expect(Array.isArray(data)).toBe(true)
169
+ expect(data?.length).toBeGreaterThan(0)
170
+ })
171
+
172
+ test('channel list --type public filters channels', async () => {
173
+ const result = await runCLI('slack', ['channel', 'list', '--type', 'public'])
174
+ expect(result.exitCode).toBe(0)
175
+
176
+ const data = parseJSON<Array<{ is_private: boolean }>>(result.stdout)
177
+ expect(Array.isArray(data)).toBe(true)
178
+ })
179
+
180
+ test('channel info returns channel details', async () => {
181
+ const result = await runCLI('slack', ['channel', 'info', SLACK_TEST_CHANNEL_ID])
182
+ expect(result.exitCode).toBe(0)
183
+
184
+ const data = parseJSON<{ id: string; name: string }>(result.stdout)
185
+ expect(data?.id).toBeTruthy()
186
+ expect(data?.name).toBe('e2e-test')
187
+ })
188
+ })
189
+
190
+ describe('user', () => {
191
+ test('user list returns users array', async () => {
192
+ const result = await runCLI('slack', ['user', 'list'])
193
+ expect(result.exitCode).toBe(0)
194
+
195
+ const data = parseJSON<Array<{ id: string }>>(result.stdout)
196
+ expect(Array.isArray(data)).toBe(true)
197
+ })
198
+
199
+ test('user me returns current user', async () => {
200
+ const result = await runCLI('slack', ['user', 'me'])
201
+ expect(result.exitCode).toBe(0)
202
+
203
+ const data = parseJSON<{ id: string; name: string }>(result.stdout)
204
+ expect(data?.id).toBeTruthy()
205
+ expect(data?.name).toBeTruthy()
206
+ })
207
+
208
+ test('user info returns user details', async () => {
209
+ // First get current user ID
210
+ const meResult = await runCLI('slack', ['user', 'me'])
211
+ const me = parseJSON<{ id: string }>(meResult.stdout)
212
+ expect(me?.id).toBeTruthy()
213
+
214
+ await waitForRateLimit()
215
+
216
+ // Get user info
217
+ const result = await runCLI('slack', ['user', 'info', me!.id])
218
+ expect(result.exitCode).toBe(0)
219
+
220
+ const data = parseJSON<{ id: string }>(result.stdout)
221
+ expect(data?.id).toBe(me?.id)
222
+ })
223
+ })
224
+
225
+ describe('reaction', () => {
226
+ test('reaction add/list/remove lifecycle', async () => {
227
+ const testId = generateTestId()
228
+ const { id: ts } = await createTestMessage('slack', SLACK_TEST_CHANNEL_ID, `Reaction test ${testId}`)
229
+ testMessages.push(ts)
230
+
231
+ await waitForRateLimit(2000)
232
+
233
+ const addResult = await runCLI('slack', ['reaction', 'add', SLACK_TEST_CHANNEL_ID, ts, 'thumbsup'])
234
+ expect(addResult.exitCode).toBe(0)
235
+
236
+ await waitForRateLimit(2000)
237
+
238
+ const listResult = await runCLI('slack', ['reaction', 'list', SLACK_TEST_CHANNEL_ID, ts])
239
+ expect(listResult.exitCode).toBe(0)
240
+
241
+ const data = parseJSON<{ reactions: Array<{ name: string }> }>(listResult.stdout)
242
+ expect(Array.isArray(data?.reactions)).toBe(true)
243
+
244
+ await waitForRateLimit(2000)
245
+
246
+ const removeResult = await runCLI('slack', ['reaction', 'remove', SLACK_TEST_CHANNEL_ID, ts, 'thumbsup'])
247
+ expect(removeResult.exitCode).toBe(0)
248
+ }, 15000)
249
+ })
250
+
251
+ describe('file', () => {
252
+ test('file list returns files array', async () => {
253
+ const result = await runCLI('slack', ['file', 'list'])
254
+ expect(result.exitCode).toBe(0)
255
+
256
+ const data = parseJSON<Array<{ id: string }>>(result.stdout)
257
+ expect(Array.isArray(data)).toBe(true)
258
+ })
259
+
260
+ test.skip('file upload uploads file', async () => {
261
+ const testId = generateTestId()
262
+ const testFilePath = `/tmp/slack-e2e-${testId}.txt`
263
+ await Bun.write(testFilePath, `E2E test file content ${testId}`)
264
+
265
+ try {
266
+ const result = await runCLI('slack', ['file', 'upload', SLACK_TEST_CHANNEL_ID, testFilePath])
267
+ expect(result.exitCode).toBe(0)
268
+
269
+ const data = parseJSON<{ id: string }>(result.stdout)
270
+ expect(data?.id).toBeTruthy()
271
+
272
+ if (data?.id) {
273
+ await waitForRateLimit()
274
+ const infoResult = await runCLI('slack', ['file', 'info', data.id])
275
+ expect(infoResult.exitCode).toBe(0)
276
+ }
277
+ } finally {
278
+ await Bun.$`rm -f ${testFilePath}`.quiet()
279
+ }
280
+ })
281
+ })
282
+
283
+ describe('snapshot', () => {
284
+ test('snapshot returns full workspace data', async () => {
285
+ const result = await runCLI('slack', ['snapshot', '--limit', '2'])
286
+ expect(result.exitCode).toBe(0)
287
+
288
+ const data = parseJSON<{ channels: unknown[]; users: unknown[] }>(result.stdout)
289
+ expect(data?.channels).toBeDefined()
290
+ expect(data?.users).toBeDefined()
291
+ })
292
+
293
+ test('snapshot --channels-only returns only channels', async () => {
294
+ const result = await runCLI('slack', ['snapshot', '--channels-only'])
295
+ expect(result.exitCode).toBe(0)
296
+
297
+ const data = parseJSON<{ channels: unknown[] }>(result.stdout)
298
+ expect(data?.channels).toBeDefined()
299
+ })
300
+
301
+ test('snapshot --users-only returns only users', async () => {
302
+ const result = await runCLI('slack', ['snapshot', '--users-only'])
303
+ expect(result.exitCode).toBe(0)
304
+
305
+ const data = parseJSON<{ users: unknown[] }>(result.stdout)
306
+ expect(data?.users).toBeDefined()
307
+ })
308
+ })
309
+ })