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,36 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { Command } from 'commander'
4
+ import pkg from '../../../package.json'
5
+ import {
6
+ authCommand,
7
+ channelCommand,
8
+ fileCommand,
9
+ messageCommand,
10
+ reactionCommand,
11
+ snapshotCommand,
12
+ teamCommand,
13
+ userCommand,
14
+ } from './commands'
15
+
16
+ const program = new Command()
17
+
18
+ program
19
+ .name('agent-teams')
20
+ .description('CLI tool for Microsoft Teams communication')
21
+ .version(pkg.version)
22
+ .option('--pretty', 'Pretty-print JSON output')
23
+ .option('--team <id>', 'Use specific team')
24
+
25
+ program.addCommand(authCommand)
26
+ program.addCommand(teamCommand)
27
+ program.addCommand(channelCommand)
28
+ program.addCommand(fileCommand)
29
+ program.addCommand(messageCommand)
30
+ program.addCommand(reactionCommand)
31
+ program.addCommand(snapshotCommand)
32
+ program.addCommand(userCommand)
33
+
34
+ program.parse(process.argv)
35
+
36
+ export default program
@@ -0,0 +1,500 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
2
+ import { unlinkSync } from 'node:fs'
3
+ import { TeamsClient } from './client'
4
+ import { TeamsError } from './types'
5
+
6
+ const TEMP_FILES_TO_CLEANUP = ['/tmp/test-teams-upload.txt']
7
+
8
+ describe('TeamsClient', () => {
9
+ const originalFetch = globalThis.fetch
10
+ let fetchCalls: Array<{ url: string; options?: RequestInit }> = []
11
+ let fetchResponses: Response[] = []
12
+ let fetchIndex = 0
13
+
14
+ beforeEach(() => {
15
+ fetchCalls = []
16
+ fetchResponses = []
17
+ fetchIndex = 0
18
+ ;(globalThis as any).fetch = async (
19
+ url: string | URL | Request,
20
+ options?: RequestInit
21
+ ): Promise<Response> => {
22
+ fetchCalls.push({ url: url.toString(), options })
23
+ const response = fetchResponses[fetchIndex]
24
+ fetchIndex++
25
+ if (!response) {
26
+ throw new Error('No mock response configured')
27
+ }
28
+ return response
29
+ }
30
+ })
31
+
32
+ afterEach(() => {
33
+ globalThis.fetch = originalFetch
34
+ for (const file of TEMP_FILES_TO_CLEANUP) {
35
+ try {
36
+ unlinkSync(file)
37
+ } catch {
38
+ // File may not exist, ignore
39
+ }
40
+ }
41
+ })
42
+
43
+ const mockResponse = (body: unknown, status = 200, headers: Record<string, string> = {}) => {
44
+ const defaultHeaders: Record<string, string> = {
45
+ 'Content-Type': 'application/json',
46
+ 'X-RateLimit-Remaining': '10',
47
+ 'X-RateLimit-Reset': String(Date.now() / 1000 + 60),
48
+ ...headers,
49
+ }
50
+ fetchResponses.push(
51
+ new Response(body === null ? null : JSON.stringify(body), {
52
+ status,
53
+ headers: defaultHeaders,
54
+ })
55
+ )
56
+ }
57
+
58
+ describe('constructor', () => {
59
+ test('requires token', () => {
60
+ expect(() => new TeamsClient('')).toThrow(TeamsError)
61
+ expect(() => new TeamsClient('')).toThrow('Token is required')
62
+ })
63
+
64
+ test('accepts valid token', () => {
65
+ const client = new TeamsClient('test-token')
66
+ expect(client).toBeInstanceOf(TeamsClient)
67
+ })
68
+
69
+ test('accepts token with expiry time', () => {
70
+ const expiresAt = new Date(Date.now() + 3600000).toISOString()
71
+ const client = new TeamsClient('test-token', expiresAt)
72
+ expect(client).toBeInstanceOf(TeamsClient)
73
+ })
74
+ })
75
+
76
+ describe('token expiry', () => {
77
+ test('throws when token is expired', async () => {
78
+ const expiredAt = new Date(Date.now() - 1000).toISOString()
79
+ const client = new TeamsClient('expired-token', expiredAt)
80
+
81
+ await expect(client.testAuth()).rejects.toThrow(TeamsError)
82
+ await expect(client.testAuth()).rejects.toThrow('Token has expired')
83
+ })
84
+
85
+ test('works when token is not expired', async () => {
86
+ const expiresAt = new Date(Date.now() + 3600000).toISOString()
87
+ mockResponse({
88
+ userDetails: JSON.stringify({ name: 'Test User' }),
89
+ locale: 'en-us',
90
+ })
91
+
92
+ const client = new TeamsClient('valid-token', expiresAt)
93
+ const user = await client.testAuth()
94
+
95
+ expect(user.id).toBe('ME')
96
+ expect(user.displayName).toBe('Test User')
97
+ })
98
+ })
99
+
100
+ describe('testAuth', () => {
101
+ test('returns current user info', async () => {
102
+ mockResponse({
103
+ userDetails: JSON.stringify({ name: 'Test User' }),
104
+ locale: 'en-us',
105
+ })
106
+
107
+ const client = new TeamsClient('test-token')
108
+ const user = await client.testAuth()
109
+
110
+ expect(user.id).toBe('ME')
111
+ expect(user.displayName).toBe('Test User')
112
+ expect(fetchCalls.length).toBe(1)
113
+ expect(fetchCalls[0].url).toBe(
114
+ 'https://emea.ng.msg.teams.microsoft.com/v1/users/ME/properties'
115
+ )
116
+ expect(fetchCalls[0].options?.headers).toMatchObject({
117
+ 'X-Skypetoken': 'test-token',
118
+ })
119
+ })
120
+
121
+ test('throws TeamsError on API error', async () => {
122
+ mockResponse({ message: 'Unauthorized', code: 'unauthorized' }, 401)
123
+
124
+ const client = new TeamsClient('bad-token')
125
+ await expect(client.testAuth()).rejects.toThrow(TeamsError)
126
+ })
127
+ })
128
+
129
+ describe('listTeams', () => {
130
+ test('returns list of teams from conversations', async () => {
131
+ mockResponse({
132
+ conversations: [
133
+ {
134
+ id: '19:abc@thread.tacv2',
135
+ threadProperties: {
136
+ groupId: '111',
137
+ spaceThreadTopic: 'Team One',
138
+ productThreadType: 'TeamsChannel',
139
+ threadType: 'space',
140
+ },
141
+ },
142
+ {
143
+ id: '19:def@thread.tacv2',
144
+ threadProperties: {
145
+ groupId: '222',
146
+ spaceThreadTopic: 'Team Two',
147
+ productThreadType: 'TeamsPrivateChannel',
148
+ threadType: 'space',
149
+ },
150
+ },
151
+ {
152
+ id: '19:chat@thread.v2',
153
+ threadProperties: {
154
+ threadType: 'chat',
155
+ },
156
+ },
157
+ ],
158
+ })
159
+
160
+ const client = new TeamsClient('test-token')
161
+ const teams = await client.listTeams()
162
+
163
+ expect(teams).toHaveLength(2)
164
+ expect(teams[0].id).toBe('111')
165
+ expect(teams[0].name).toBe('Team One')
166
+ expect(teams[1].id).toBe('222')
167
+ expect(teams[1].name).toBe('Team Two')
168
+ expect(fetchCalls[0].url).toBe(
169
+ 'https://emea.ng.msg.teams.microsoft.com/v1/users/ME/conversations'
170
+ )
171
+ })
172
+ })
173
+
174
+ describe('getTeam', () => {
175
+ test('returns team info', async () => {
176
+ mockResponse({ id: '111', name: 'Test Team', description: 'A test team' })
177
+
178
+ const client = new TeamsClient('test-token')
179
+ const team = await client.getTeam('111')
180
+
181
+ expect(team.id).toBe('111')
182
+ expect(team.name).toBe('Test Team')
183
+ expect(fetchCalls[0].url).toBe('https://teams.microsoft.com/api/csa/api/v1/teams/111')
184
+ })
185
+ })
186
+
187
+ describe('listChannels', () => {
188
+ test('returns list of channels for team', async () => {
189
+ mockResponse([
190
+ { id: 'ch1', team_id: '111', name: 'General', type: 'standard' },
191
+ { id: 'ch2', team_id: '111', name: 'Random', type: 'standard' },
192
+ ])
193
+
194
+ const client = new TeamsClient('test-token')
195
+ const channels = await client.listChannels('111')
196
+
197
+ expect(channels).toHaveLength(2)
198
+ expect(channels[0].name).toBe('General')
199
+ expect(fetchCalls[0].url).toBe(
200
+ 'https://teams.microsoft.com/api/csa/api/v1/teams/111/channels'
201
+ )
202
+ })
203
+ })
204
+
205
+ describe('getChannel', () => {
206
+ test('returns channel info', async () => {
207
+ mockResponse({ id: 'ch1', team_id: '111', name: 'General', type: 'standard' })
208
+
209
+ const client = new TeamsClient('test-token')
210
+ const channel = await client.getChannel('111', 'ch1')
211
+
212
+ expect(channel.id).toBe('ch1')
213
+ expect(channel.name).toBe('General')
214
+ expect(fetchCalls[0].url).toBe(
215
+ 'https://teams.microsoft.com/api/csa/api/v1/teams/111/channels/ch1'
216
+ )
217
+ })
218
+ })
219
+
220
+ describe('sendMessage', () => {
221
+ test('sends message to channel', async () => {
222
+ mockResponse({
223
+ id: 'msg1',
224
+ channel_id: 'ch1',
225
+ author: { id: '123', displayName: 'Test User' },
226
+ content: 'Hello world',
227
+ timestamp: '2024-01-01T00:00:00.000Z',
228
+ })
229
+
230
+ const client = new TeamsClient('test-token')
231
+ const message = await client.sendMessage('111', 'ch1', 'Hello world')
232
+
233
+ expect(message.content).toBe('Hello world')
234
+ expect(fetchCalls[0].url).toBe(
235
+ 'https://teams.microsoft.com/api/csa/emea/api/v2/teams/111/channels/ch1/messages'
236
+ )
237
+ expect(fetchCalls[0].options?.method).toBe('POST')
238
+ expect(fetchCalls[0].options?.body).toBe(JSON.stringify({ content: 'Hello world' }))
239
+ })
240
+ })
241
+
242
+ describe('getMessages', () => {
243
+ test('returns messages from channel', async () => {
244
+ mockResponse([
245
+ {
246
+ id: 'msg1',
247
+ channel_id: 'ch1',
248
+ author: { id: '123', displayName: 'User 1' },
249
+ content: 'Message 1',
250
+ timestamp: '2024-01-01T00:00:00.000Z',
251
+ },
252
+ ])
253
+
254
+ const client = new TeamsClient('test-token')
255
+ const messages = await client.getMessages('111', 'ch1', 50)
256
+
257
+ expect(messages).toHaveLength(1)
258
+ expect(messages[0].content).toBe('Message 1')
259
+ expect(fetchCalls[0].url).toBe(
260
+ 'https://teams.microsoft.com/api/csa/emea/api/v2/teams/111/channels/ch1/messages?limit=50'
261
+ )
262
+ })
263
+
264
+ test('uses default limit of 50', async () => {
265
+ mockResponse([])
266
+
267
+ const client = new TeamsClient('test-token')
268
+ await client.getMessages('111', 'ch1')
269
+
270
+ expect(fetchCalls[0].url).toBe(
271
+ 'https://teams.microsoft.com/api/csa/emea/api/v2/teams/111/channels/ch1/messages?limit=50'
272
+ )
273
+ })
274
+ })
275
+
276
+ describe('getMessage', () => {
277
+ test('returns single message', async () => {
278
+ mockResponse({
279
+ id: 'msg1',
280
+ channel_id: 'ch1',
281
+ author: { id: '123', displayName: 'User 1' },
282
+ content: 'Message 1',
283
+ timestamp: '2024-01-01T00:00:00.000Z',
284
+ })
285
+
286
+ const client = new TeamsClient('test-token')
287
+ const message = await client.getMessage('111', 'ch1', 'msg1')
288
+
289
+ expect(message.id).toBe('msg1')
290
+ expect(fetchCalls[0].url).toBe(
291
+ 'https://teams.microsoft.com/api/csa/emea/api/v2/teams/111/channels/ch1/messages/msg1'
292
+ )
293
+ })
294
+ })
295
+
296
+ describe('deleteMessage', () => {
297
+ test('deletes message', async () => {
298
+ mockResponse(null, 204)
299
+
300
+ const client = new TeamsClient('test-token')
301
+ await client.deleteMessage('111', 'ch1', 'msg1')
302
+
303
+ expect(fetchCalls[0].url).toBe(
304
+ 'https://teams.microsoft.com/api/csa/emea/api/v2/teams/111/channels/ch1/messages/msg1'
305
+ )
306
+ expect(fetchCalls[0].options?.method).toBe('DELETE')
307
+ })
308
+ })
309
+
310
+ describe('addReaction', () => {
311
+ test('adds reaction to message', async () => {
312
+ mockResponse(null, 204)
313
+
314
+ const client = new TeamsClient('test-token')
315
+ await client.addReaction('111', 'ch1', 'msg1', 'like')
316
+
317
+ expect(fetchCalls[0].url).toBe(
318
+ 'https://teams.microsoft.com/api/csa/emea/api/v2/teams/111/channels/ch1/messages/msg1/reactions'
319
+ )
320
+ expect(fetchCalls[0].options?.method).toBe('POST')
321
+ expect(fetchCalls[0].options?.body).toBe(JSON.stringify({ emoji: 'like' }))
322
+ })
323
+ })
324
+
325
+ describe('removeReaction', () => {
326
+ test('removes reaction from message', async () => {
327
+ mockResponse(null, 204)
328
+
329
+ const client = new TeamsClient('test-token')
330
+ await client.removeReaction('111', 'ch1', 'msg1', 'like')
331
+
332
+ expect(fetchCalls[0].url).toBe(
333
+ 'https://teams.microsoft.com/api/csa/emea/api/v2/teams/111/channels/ch1/messages/msg1/reactions/like'
334
+ )
335
+ expect(fetchCalls[0].options?.method).toBe('DELETE')
336
+ })
337
+ })
338
+
339
+ describe('listUsers', () => {
340
+ test('returns list of team members', async () => {
341
+ mockResponse([
342
+ { id: 'u1', displayName: 'User 1', email: 'user1@example.com' },
343
+ { id: 'u2', displayName: 'User 2', email: 'user2@example.com' },
344
+ ])
345
+
346
+ const client = new TeamsClient('test-token')
347
+ const users = await client.listUsers('111')
348
+
349
+ expect(users).toHaveLength(2)
350
+ expect(users[0].displayName).toBe('User 1')
351
+ expect(fetchCalls[0].url).toBe('https://teams.microsoft.com/api/csa/api/v1/teams/111/members')
352
+ })
353
+ })
354
+
355
+ describe('getUser', () => {
356
+ test('returns user info', async () => {
357
+ mockResponse({ id: 'u1', displayName: 'Test User', email: 'test@example.com' })
358
+
359
+ const client = new TeamsClient('test-token')
360
+ const user = await client.getUser('u1')
361
+
362
+ expect(user.id).toBe('u1')
363
+ expect(user.displayName).toBe('Test User')
364
+ expect(fetchCalls[0].url).toBe('https://teams.microsoft.com/api/csa/api/v1/users/u1')
365
+ })
366
+ })
367
+
368
+ describe('uploadFile', () => {
369
+ test('uploads file to channel', async () => {
370
+ const tempFile = '/tmp/test-teams-upload.txt'
371
+ await Bun.write(tempFile, 'test content')
372
+
373
+ mockResponse({
374
+ id: 'file1',
375
+ name: 'test-teams-upload.txt',
376
+ size: 12,
377
+ url: 'https://teams.microsoft.com/files/file1',
378
+ })
379
+
380
+ const client = new TeamsClient('test-token')
381
+ const file = await client.uploadFile('111', 'ch1', tempFile)
382
+
383
+ expect(file.name).toBe('test-teams-upload.txt')
384
+ expect(fetchCalls[0].url).toBe(
385
+ 'https://teams.microsoft.com/api/csa/emea/api/v2/teams/111/channels/ch1/files'
386
+ )
387
+ expect(fetchCalls[0].options?.method).toBe('POST')
388
+ })
389
+ })
390
+
391
+ describe('listFiles', () => {
392
+ test('returns files from channel', async () => {
393
+ mockResponse([
394
+ { id: 'file1', name: 'doc.pdf', size: 1024, url: 'https://example.com/doc.pdf' },
395
+ { id: 'file2', name: 'image.png', size: 2048, url: 'https://example.com/image.png' },
396
+ ])
397
+
398
+ const client = new TeamsClient('test-token')
399
+ const files = await client.listFiles('111', 'ch1')
400
+
401
+ expect(files).toHaveLength(2)
402
+ expect(files[0].name).toBe('doc.pdf')
403
+ expect(fetchCalls[0].url).toBe(
404
+ 'https://teams.microsoft.com/api/csa/emea/api/v2/teams/111/channels/ch1/files'
405
+ )
406
+ })
407
+ })
408
+
409
+ describe('rate limiting', () => {
410
+ test('waits when bucket is exhausted before making request', async () => {
411
+ mockResponse({ userDetails: JSON.stringify({ name: 'User 1' }), locale: 'en-us' }, 200, {
412
+ 'X-RateLimit-Remaining': '0',
413
+ 'X-RateLimit-Reset': String(Date.now() / 1000 + 0.1),
414
+ })
415
+ mockResponse({ userDetails: JSON.stringify({ name: 'User 2' }), locale: 'en-us' }, 200, {
416
+ 'X-RateLimit-Remaining': '10',
417
+ 'X-RateLimit-Reset': String(Date.now() / 1000 + 60),
418
+ })
419
+
420
+ const client = new TeamsClient('test-token')
421
+ await client.testAuth()
422
+
423
+ const startTime = Date.now()
424
+ await client.testAuth()
425
+ const elapsed = Date.now() - startTime
426
+
427
+ expect(elapsed).toBeGreaterThanOrEqual(50)
428
+ expect(fetchCalls.length).toBe(2)
429
+ })
430
+
431
+ test('retries on 429 with Retry-After header', async () => {
432
+ mockResponse({ message: 'Rate limited' }, 429, { 'Retry-After': '0.1' })
433
+ mockResponse({ userDetails: JSON.stringify({ name: 'User' }), locale: 'en-us' })
434
+
435
+ const client = new TeamsClient('test-token')
436
+ const user = await client.testAuth()
437
+
438
+ expect(user.id).toBe('ME')
439
+ expect(fetchCalls.length).toBe(2)
440
+ })
441
+
442
+ test('throws after max retries exceeded', async () => {
443
+ for (let i = 0; i <= 3; i++) {
444
+ mockResponse({ message: 'Rate limited' }, 429, { 'Retry-After': '0.01' })
445
+ }
446
+
447
+ const client = new TeamsClient('test-token')
448
+ await expect(client.testAuth()).rejects.toThrow(TeamsError)
449
+ expect(fetchCalls.length).toBeLessThanOrEqual(4)
450
+ })
451
+ })
452
+
453
+ describe('retry logic', () => {
454
+ test('retries on 500 server error', async () => {
455
+ mockResponse({ message: 'Internal Server Error' }, 500)
456
+ mockResponse({ userDetails: JSON.stringify({ name: 'User' }), locale: 'en-us' })
457
+
458
+ const client = new TeamsClient('test-token')
459
+ const user = await client.testAuth()
460
+
461
+ expect(user.id).toBe('ME')
462
+ expect(fetchCalls.length).toBe(2)
463
+ })
464
+
465
+ test('does not retry on 4xx client errors (except 429)', async () => {
466
+ mockResponse({ message: 'Not Found' }, 404)
467
+
468
+ const client = new TeamsClient('test-token')
469
+ await expect(client.testAuth()).rejects.toThrow(TeamsError)
470
+ expect(fetchCalls.length).toBe(1)
471
+ })
472
+
473
+ test('exponential backoff increases delay', async () => {
474
+ mockResponse({ message: 'Error' }, 500)
475
+ mockResponse({ message: 'Error' }, 500)
476
+ mockResponse({ userDetails: JSON.stringify({ name: 'User' }), locale: 'en-us' })
477
+
478
+ const client = new TeamsClient('test-token')
479
+ const startTime = Date.now()
480
+ await client.testAuth()
481
+ const elapsed = Date.now() - startTime
482
+
483
+ expect(elapsed).toBeGreaterThanOrEqual(150)
484
+ expect(fetchCalls.length).toBe(3)
485
+ })
486
+ })
487
+
488
+ describe('bucket key normalization', () => {
489
+ test('normalizes team and channel IDs in routes', async () => {
490
+ mockResponse([])
491
+ mockResponse([])
492
+
493
+ const client = new TeamsClient('test-token')
494
+ await client.getMessages('team1', 'ch1')
495
+ await client.getMessages('team2', 'ch2')
496
+
497
+ expect(fetchCalls.length).toBe(2)
498
+ })
499
+ })
500
+ })