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,462 @@
1
+ import { execSync } from 'node:child_process'
2
+ import { createDecipheriv, pbkdf2Sync } from 'node:crypto'
3
+ import { copyFileSync, existsSync, readFileSync, unlinkSync } from 'node:fs'
4
+ import { homedir, tmpdir } from 'node:os'
5
+ import { join } from 'node:path'
6
+
7
+ export interface ExtractedTeamsToken {
8
+ token: string
9
+ }
10
+
11
+ interface KeychainVariant {
12
+ service: string
13
+ account: string
14
+ }
15
+
16
+ const TEAMS_PROCESS_NAMES: Record<string, string> = {
17
+ darwin: 'Microsoft Teams',
18
+ win32: 'Teams.exe',
19
+ linux: 'teams',
20
+ }
21
+
22
+ const SKYPETOKEN_COOKIE_NAME = 'skypetoken_asm'
23
+ const TEAMS_HOST_PATTERNS = [
24
+ '.asyncgw.teams.microsoft.com',
25
+ '.asm.skype.com',
26
+ 'teams.microsoft.com',
27
+ 'teams.live.com',
28
+ '.microsoft.com',
29
+ ]
30
+
31
+ export class TeamsTokenExtractor {
32
+ private platform: NodeJS.Platform
33
+
34
+ constructor(platform?: NodeJS.Platform) {
35
+ this.platform = platform ?? process.platform
36
+ }
37
+
38
+ getTeamsCookiesPaths(): string[] {
39
+ switch (this.platform) {
40
+ case 'darwin':
41
+ return [
42
+ join(
43
+ homedir(),
44
+ 'Library',
45
+ 'Containers',
46
+ 'com.microsoft.teams2',
47
+ 'Data',
48
+ 'Library',
49
+ 'Application Support',
50
+ 'Microsoft',
51
+ 'MSTeams',
52
+ 'EBWebView',
53
+ 'WV2Profile_tfw',
54
+ 'Cookies'
55
+ ),
56
+ join(
57
+ homedir(),
58
+ 'Library',
59
+ 'Containers',
60
+ 'com.microsoft.teams2',
61
+ 'Data',
62
+ 'Library',
63
+ 'Application Support',
64
+ 'Microsoft',
65
+ 'MSTeams',
66
+ 'EBWebView',
67
+ 'WV2Profile_tfl',
68
+ 'Cookies'
69
+ ),
70
+ join(
71
+ homedir(),
72
+ 'Library',
73
+ 'Containers',
74
+ 'com.microsoft.teams2',
75
+ 'Data',
76
+ 'Library',
77
+ 'Application Support',
78
+ 'Microsoft',
79
+ 'MSTeams',
80
+ 'EBWebView',
81
+ 'Default',
82
+ 'Cookies'
83
+ ),
84
+ join(homedir(), 'Library', 'Application Support', 'Microsoft', 'Teams', 'Cookies'),
85
+ ]
86
+ case 'linux':
87
+ return [join(homedir(), '.config', 'Microsoft', 'Microsoft Teams', 'Cookies')]
88
+ case 'win32': {
89
+ const localAppData = process.env.LOCALAPPDATA || join(homedir(), 'AppData', 'Local')
90
+ const appdata = process.env.APPDATA || join(homedir(), 'AppData', 'Roaming')
91
+ return [
92
+ // New Teams (MSIX/Store) - WebView2 profile paths
93
+ join(
94
+ localAppData,
95
+ 'Packages',
96
+ 'MSTeams_8wekyb3d8bbwe',
97
+ 'LocalCache',
98
+ 'Microsoft',
99
+ 'MSTeams',
100
+ 'EBWebView',
101
+ 'WV2Profile_tfw',
102
+ 'Cookies'
103
+ ),
104
+ join(
105
+ localAppData,
106
+ 'Packages',
107
+ 'MSTeams_8wekyb3d8bbwe',
108
+ 'LocalCache',
109
+ 'Microsoft',
110
+ 'MSTeams',
111
+ 'EBWebView',
112
+ 'WV2Profile_tfl',
113
+ 'Cookies'
114
+ ),
115
+ join(
116
+ localAppData,
117
+ 'Packages',
118
+ 'MSTeams_8wekyb3d8bbwe',
119
+ 'LocalCache',
120
+ 'Microsoft',
121
+ 'MSTeams',
122
+ 'EBWebView',
123
+ 'Default',
124
+ 'Cookies'
125
+ ),
126
+ // Classic Teams fallback
127
+ join(appdata, 'Microsoft', 'Teams', 'Cookies'),
128
+ ]
129
+ }
130
+ default:
131
+ return []
132
+ }
133
+ }
134
+
135
+ getLocalStatePath(): string {
136
+ switch (this.platform) {
137
+ case 'darwin':
138
+ return join(
139
+ homedir(),
140
+ 'Library',
141
+ 'Application Support',
142
+ 'Microsoft',
143
+ 'Teams',
144
+ 'Local State'
145
+ )
146
+ case 'linux':
147
+ return join(homedir(), '.config', 'Microsoft', 'Microsoft Teams', 'Local State')
148
+ case 'win32': {
149
+ const localAppData = process.env.LOCALAPPDATA || join(homedir(), 'AppData', 'Local')
150
+ const appdata = process.env.APPDATA || join(homedir(), 'AppData', 'Roaming')
151
+ const newTeamsPath = join(
152
+ localAppData,
153
+ 'Packages',
154
+ 'MSTeams_8wekyb3d8bbwe',
155
+ 'LocalCache',
156
+ 'Microsoft',
157
+ 'MSTeams',
158
+ 'EBWebView',
159
+ 'Local State'
160
+ )
161
+ if (existsSync(newTeamsPath)) return newTeamsPath
162
+ return join(appdata, 'Microsoft', 'Teams', 'Local State')
163
+ }
164
+ default:
165
+ return ''
166
+ }
167
+ }
168
+
169
+ getKeychainVariants(): KeychainVariant[] {
170
+ return [
171
+ // New Teams (com.microsoft.teams2) keychain entry - try first
172
+ { service: 'Microsoft Teams Safe Storage', account: 'Microsoft Teams' },
173
+ // Work/school variant
174
+ {
175
+ service: 'Microsoft Teams (work or school) Safe Storage',
176
+ account: 'Microsoft Teams (work or school)',
177
+ },
178
+ // Edge WebView2 fallback
179
+ { service: 'Microsoft Edge Safe Storage', account: 'Microsoft Edge' },
180
+ // Classic Teams fallback
181
+ { service: 'Teams Safe Storage', account: 'Teams' },
182
+ ]
183
+ }
184
+
185
+ isValidSkypeToken(token: string): boolean {
186
+ if (!token || token.length === 0) return false
187
+ // Skype tokens are typically JWT format or long base64 strings (50+ chars)
188
+ return token.length >= 50
189
+ }
190
+
191
+ isEncryptedValue(value: Buffer): boolean {
192
+ if (!value || value.length < 4) return false
193
+ const prefix = value.subarray(0, 3).toString('utf8')
194
+ return prefix === 'v10' || prefix === 'v11'
195
+ }
196
+
197
+ async extract(): Promise<ExtractedTeamsToken | null> {
198
+ // Extract from Cookies database (works for both New Teams and Classic Teams)
199
+ const cookieToken = await this.extractFromCookiesDB()
200
+ if (cookieToken && this.isValidSkypeToken(cookieToken)) {
201
+ return { token: cookieToken }
202
+ }
203
+
204
+ return null
205
+ }
206
+
207
+ private async extractFromCookiesDB(): Promise<string | null> {
208
+ const dbPaths = this.getTeamsCookiesPaths()
209
+
210
+ for (const dbPath of dbPaths) {
211
+ if (!dbPath || !existsSync(dbPath)) continue
212
+
213
+ // Try copy-first strategy to handle file locking
214
+ const token = await this.copyAndExtract(dbPath)
215
+ if (token) return token
216
+ }
217
+
218
+ return null
219
+ }
220
+
221
+ private async copyAndExtract(dbPath: string): Promise<string | null> {
222
+ const tempPath = join(tmpdir(), `teams-cookies-${Date.now()}`)
223
+
224
+ try {
225
+ this.copyDatabaseToTemp(dbPath, tempPath)
226
+ const token = await this.extractFromSQLite(tempPath)
227
+ this.cleanupTempFile(tempPath)
228
+ return token
229
+ } catch {
230
+ // File locked or copy failed
231
+ this.cleanupTempFile(tempPath)
232
+ return null
233
+ }
234
+ }
235
+
236
+ private copyDatabaseToTemp(sourcePath: string, destPath: string): string {
237
+ copyFileSync(sourcePath, destPath)
238
+ return destPath
239
+ }
240
+
241
+ private cleanupTempFile(tempPath: string): void {
242
+ try {
243
+ if (existsSync(tempPath)) {
244
+ unlinkSync(tempPath)
245
+ }
246
+ } catch {
247
+ // Ignore cleanup errors
248
+ }
249
+ }
250
+
251
+ private async extractFromSQLite(dbPath: string): Promise<string | null> {
252
+ try {
253
+ for (const hostPattern of TEAMS_HOST_PATTERNS) {
254
+ const sql = `
255
+ SELECT encrypted_value
256
+ FROM cookies
257
+ WHERE name = '${SKYPETOKEN_COOKIE_NAME}'
258
+ AND host_key LIKE '%${hostPattern}%'
259
+ LIMIT 1
260
+ `
261
+
262
+ type CookieRow = { encrypted_value?: Uint8Array | Buffer } | null
263
+
264
+ let row: CookieRow
265
+ if (typeof globalThis.Bun !== 'undefined') {
266
+ const { Database } = require('bun:sqlite')
267
+ const db = new Database(dbPath, { readonly: true })
268
+ row = db.query(sql).get() as CookieRow
269
+ db.close()
270
+ } else {
271
+ const Database = require('better-sqlite3')
272
+ const db = new Database(dbPath, { readonly: true })
273
+ row = db.prepare(sql).get() as CookieRow
274
+ db.close()
275
+ }
276
+
277
+ if (row?.encrypted_value) {
278
+ const decrypted = this.decryptCookie(Buffer.from(row.encrypted_value))
279
+ if (decrypted && this.isValidSkypeToken(decrypted)) {
280
+ return decrypted
281
+ }
282
+ }
283
+ }
284
+
285
+ return null
286
+ } catch {
287
+ return null
288
+ }
289
+ }
290
+
291
+ private decryptCookie(encryptedValue: Buffer): string | null {
292
+ if (!this.isEncryptedValue(encryptedValue)) {
293
+ // Not encrypted, return as-is
294
+ return encryptedValue.toString('utf8')
295
+ }
296
+
297
+ if (this.platform === 'win32') {
298
+ return this.decryptWindowsCookie(encryptedValue)
299
+ } else if (this.platform === 'darwin') {
300
+ return this.decryptMacCookie(encryptedValue)
301
+ } else if (this.platform === 'linux') {
302
+ return this.decryptLinuxCookie(encryptedValue)
303
+ }
304
+
305
+ return null
306
+ }
307
+
308
+ private decryptWindowsCookie(encryptedData: Buffer): string | null {
309
+ try {
310
+ const localStatePath = this.getLocalStatePath()
311
+ if (!existsSync(localStatePath)) return null
312
+
313
+ const localState = JSON.parse(readFileSync(localStatePath, 'utf8'))
314
+ const encryptedKey = Buffer.from(localState.os_crypt.encrypted_key, 'base64')
315
+
316
+ // Remove DPAPI prefix (5 bytes)
317
+ const dpapiBlobKey = encryptedKey.subarray(5)
318
+ const masterKey = this.decryptDPAPI(dpapiBlobKey)
319
+ if (!masterKey) return null
320
+
321
+ return this.decryptAESGCM(encryptedData, masterKey)
322
+ } catch {
323
+ return null
324
+ }
325
+ }
326
+
327
+ private decryptDPAPI(encryptedBlob: Buffer): Buffer | null {
328
+ try {
329
+ const b64 = encryptedBlob.toString('base64')
330
+ const psScript = `
331
+ Add-Type -AssemblyName System.Security
332
+ $bytes = [Convert]::FromBase64String('${b64}')
333
+ $decrypted = [Security.Cryptography.ProtectedData]::Unprotect($bytes, $null, 'CurrentUser')
334
+ [Convert]::ToBase64String($decrypted)
335
+ `.replace(/\n/g, ' ')
336
+
337
+ const result = execSync(`powershell -Command "${psScript}"`, { encoding: 'utf8' })
338
+ return Buffer.from(result.trim(), 'base64')
339
+ } catch {
340
+ return null
341
+ }
342
+ }
343
+
344
+ private decryptMacCookie(encryptedData: Buffer): string | null {
345
+ const password = this.getKeychainPassword()
346
+ if (!password) return null
347
+
348
+ // Derive key using PBKDF2 (Chromium uses 1003 iterations, 16 byte key for AES-128-CBC)
349
+ const key = pbkdf2Sync(password, 'saltysalt', 1003, 16, 'sha1')
350
+ return this.decryptAESCBC(encryptedData, key)
351
+ }
352
+
353
+ private decryptLinuxCookie(encryptedData: Buffer): string | null {
354
+ // Linux uses a hardcoded password 'peanuts' for Chromium-based apps
355
+ const key = pbkdf2Sync('peanuts', 'saltysalt', 1, 16, 'sha1')
356
+ return this.decryptAESCBC(encryptedData, key)
357
+ }
358
+
359
+ private getKeychainPassword(): string | null {
360
+ const variants = this.getKeychainVariants()
361
+
362
+ for (const variant of variants) {
363
+ const password = this.execSecurityCommand(variant.service, variant.account)
364
+ if (password) return password
365
+ }
366
+
367
+ return null
368
+ }
369
+
370
+ private execSecurityCommand(service: string, account: string): string | null {
371
+ try {
372
+ // Escape double quotes in service/account to prevent command injection
373
+ const safeService = service.replace(/"/g, '\\"')
374
+ const safeAccount = account.replace(/"/g, '\\"')
375
+ const result = execSync(
376
+ `security find-generic-password -s "${safeService}" -a "${safeAccount}" -w 2>/dev/null`,
377
+ { encoding: 'utf8' }
378
+ )
379
+ return result.trim()
380
+ } catch {
381
+ return null
382
+ }
383
+ }
384
+
385
+ private decryptAESCBC(encryptedData: Buffer, key: Buffer): string | null {
386
+ try {
387
+ const ciphertext = encryptedData.subarray(3)
388
+ const iv = Buffer.alloc(16, 0x20)
389
+
390
+ const decipher = createDecipheriv('aes-128-cbc', key, iv)
391
+ decipher.setAutoPadding(true)
392
+
393
+ const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()])
394
+ const decryptedStr = decrypted.toString('utf8')
395
+
396
+ // Chromium v24+ prepends a 32-byte integrity hash before the actual value
397
+ // Look for JWT token start (eyJ) or other token patterns
398
+ const jwtStart = decryptedStr.indexOf('eyJ')
399
+ if (jwtStart > 0 && jwtStart <= 32) {
400
+ return decryptedStr.substring(jwtStart)
401
+ }
402
+
403
+ // If no JWT prefix found but decryption succeeded, check if first 32 bytes are garbage
404
+ if (decrypted.length > 32) {
405
+ const possibleToken = decryptedStr.substring(32)
406
+ if (possibleToken.length > 50 && /^[A-Za-z0-9._-]+$/.test(possibleToken.substring(0, 50))) {
407
+ return possibleToken
408
+ }
409
+ }
410
+
411
+ return decryptedStr
412
+ } catch {
413
+ return null
414
+ }
415
+ }
416
+
417
+ private decryptAESGCM(encryptedData: Buffer, key: Buffer): string | null {
418
+ try {
419
+ // Format: v10 (3 bytes) + IV (12 bytes) + ciphertext + auth tag (16 bytes)
420
+ if (encryptedData.length < 3 + 12 + 16) return null
421
+
422
+ const iv = encryptedData.subarray(3, 15)
423
+ const authTag = encryptedData.subarray(-16)
424
+ const ciphertext = encryptedData.subarray(15, -16)
425
+
426
+ const decipher = createDecipheriv('aes-256-gcm', key, iv)
427
+ decipher.setAuthTag(authTag)
428
+
429
+ const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()])
430
+ return decrypted.toString('utf8')
431
+ } catch {
432
+ return null
433
+ }
434
+ }
435
+
436
+ async isTeamsRunning(): Promise<boolean> {
437
+ const processName = this.getProcessName()
438
+ return this.checkProcessRunning(processName)
439
+ }
440
+
441
+ private getProcessName(): string {
442
+ return TEAMS_PROCESS_NAMES[this.platform] || TEAMS_PROCESS_NAMES.linux
443
+ }
444
+
445
+ private checkProcessRunning(processName: string): boolean {
446
+ try {
447
+ if (this.platform === 'win32') {
448
+ const result = execSync(`tasklist /FI "IMAGENAME eq ${processName}" 2>nul`, {
449
+ encoding: 'utf8',
450
+ })
451
+ return result.toLowerCase().includes(processName.toLowerCase())
452
+ } else {
453
+ const result = execSync(`pgrep -f "${processName}" 2>/dev/null || true`, {
454
+ encoding: 'utf8',
455
+ })
456
+ return result.trim().length > 0
457
+ }
458
+ } catch {
459
+ return false
460
+ }
461
+ }
462
+ }
@@ -0,0 +1,226 @@
1
+ import { expect, test } from 'bun:test'
2
+ import {
3
+ TeamsChannelSchema,
4
+ TeamsConfigSchema,
5
+ TeamsCredentialsSchema,
6
+ TeamsError,
7
+ TeamsFileSchema,
8
+ TeamsMessageSchema,
9
+ TeamsReactionSchema,
10
+ TeamsTeamSchema,
11
+ TeamsUserSchema,
12
+ } from './types'
13
+
14
+ // TeamsTeamSchema tests
15
+ test('TeamsTeamSchema validates correct team', () => {
16
+ const result = TeamsTeamSchema.safeParse({
17
+ id: '19:abc123@thread.tacv2',
18
+ name: 'Test Team',
19
+ })
20
+ expect(result.success).toBe(true)
21
+ })
22
+
23
+ test('TeamsTeamSchema validates team with optional description', () => {
24
+ const result = TeamsTeamSchema.safeParse({
25
+ id: '19:abc123@thread.tacv2',
26
+ name: 'Test Team',
27
+ description: 'A test team',
28
+ })
29
+ expect(result.success).toBe(true)
30
+ })
31
+
32
+ test('TeamsTeamSchema rejects missing id', () => {
33
+ const result = TeamsTeamSchema.safeParse({
34
+ name: 'Test Team',
35
+ })
36
+ expect(result.success).toBe(false)
37
+ })
38
+
39
+ test('TeamsTeamSchema rejects missing name', () => {
40
+ const result = TeamsTeamSchema.safeParse({
41
+ id: '19:abc123@thread.tacv2',
42
+ })
43
+ expect(result.success).toBe(false)
44
+ })
45
+
46
+ // TeamsChannelSchema tests
47
+ test('TeamsChannelSchema validates correct channel', () => {
48
+ const result = TeamsChannelSchema.safeParse({
49
+ id: '19:channel123@thread.tacv2',
50
+ team_id: '19:abc123@thread.tacv2',
51
+ name: 'General',
52
+ type: 'standard',
53
+ })
54
+ expect(result.success).toBe(true)
55
+ })
56
+
57
+ test('TeamsChannelSchema rejects missing required fields', () => {
58
+ const result = TeamsChannelSchema.safeParse({
59
+ id: '19:channel123@thread.tacv2',
60
+ name: 'General',
61
+ })
62
+ expect(result.success).toBe(false)
63
+ })
64
+
65
+ // TeamsMessageSchema tests
66
+ test('TeamsMessageSchema validates correct message', () => {
67
+ const result = TeamsMessageSchema.safeParse({
68
+ id: '1234567890123',
69
+ channel_id: '19:channel123@thread.tacv2',
70
+ author: {
71
+ id: 'user123',
72
+ displayName: 'Test User',
73
+ },
74
+ content: 'Hello world',
75
+ timestamp: '2024-01-01T00:00:00.000Z',
76
+ })
77
+ expect(result.success).toBe(true)
78
+ })
79
+
80
+ test('TeamsMessageSchema rejects missing required fields', () => {
81
+ const result = TeamsMessageSchema.safeParse({
82
+ id: '1234567890123',
83
+ channel_id: '19:channel123@thread.tacv2',
84
+ content: 'Hello world',
85
+ })
86
+ expect(result.success).toBe(false)
87
+ })
88
+
89
+ // TeamsUserSchema tests
90
+ test('TeamsUserSchema validates correct user', () => {
91
+ const result = TeamsUserSchema.safeParse({
92
+ id: 'user123',
93
+ displayName: 'Test User',
94
+ })
95
+ expect(result.success).toBe(true)
96
+ })
97
+
98
+ test('TeamsUserSchema validates user with optional fields', () => {
99
+ const result = TeamsUserSchema.safeParse({
100
+ id: 'user123',
101
+ displayName: 'Test User',
102
+ email: 'test@example.com',
103
+ userPrincipalName: 'test@example.onmicrosoft.com',
104
+ })
105
+ expect(result.success).toBe(true)
106
+ })
107
+
108
+ test('TeamsUserSchema rejects missing required fields', () => {
109
+ const result = TeamsUserSchema.safeParse({
110
+ id: 'user123',
111
+ })
112
+ expect(result.success).toBe(false)
113
+ })
114
+
115
+ // TeamsReactionSchema tests
116
+ test('TeamsReactionSchema validates correct reaction', () => {
117
+ const result = TeamsReactionSchema.safeParse({
118
+ emoji: 'like',
119
+ count: 5,
120
+ })
121
+ expect(result.success).toBe(true)
122
+ })
123
+
124
+ test('TeamsReactionSchema rejects missing required fields', () => {
125
+ const result = TeamsReactionSchema.safeParse({
126
+ emoji: 'like',
127
+ })
128
+ expect(result.success).toBe(false)
129
+ })
130
+
131
+ // TeamsFileSchema tests
132
+ test('TeamsFileSchema validates correct file', () => {
133
+ const result = TeamsFileSchema.safeParse({
134
+ id: 'file123',
135
+ name: 'document.pdf',
136
+ size: 1024,
137
+ url: 'https://teams.microsoft.com/files/...',
138
+ })
139
+ expect(result.success).toBe(true)
140
+ })
141
+
142
+ test('TeamsFileSchema validates file with optional contentType', () => {
143
+ const result = TeamsFileSchema.safeParse({
144
+ id: 'file123',
145
+ name: 'document.pdf',
146
+ size: 1024,
147
+ url: 'https://teams.microsoft.com/files/...',
148
+ contentType: 'application/pdf',
149
+ })
150
+ expect(result.success).toBe(true)
151
+ })
152
+
153
+ test('TeamsFileSchema rejects missing required fields', () => {
154
+ const result = TeamsFileSchema.safeParse({
155
+ id: 'file123',
156
+ name: 'document.pdf',
157
+ })
158
+ expect(result.success).toBe(false)
159
+ })
160
+
161
+ // TeamsCredentialsSchema tests
162
+ test('TeamsCredentialsSchema validates correct credentials', () => {
163
+ const result = TeamsCredentialsSchema.safeParse({
164
+ token: 'skypetoken_value',
165
+ })
166
+ expect(result.success).toBe(true)
167
+ })
168
+
169
+ test('TeamsCredentialsSchema validates credentials with cookie', () => {
170
+ const result = TeamsCredentialsSchema.safeParse({
171
+ token: 'skypetoken_value',
172
+ cookie: 'skypetoken_asm_value',
173
+ })
174
+ expect(result.success).toBe(true)
175
+ })
176
+
177
+ test('TeamsCredentialsSchema rejects missing token', () => {
178
+ const result = TeamsCredentialsSchema.safeParse({})
179
+ expect(result.success).toBe(false)
180
+ })
181
+
182
+ // TeamsConfigSchema tests
183
+ test('TeamsConfigSchema validates correct config', () => {
184
+ const result = TeamsConfigSchema.safeParse({
185
+ current_team: null,
186
+ token: 'token_value',
187
+ teams: {},
188
+ })
189
+ expect(result.success).toBe(true)
190
+ })
191
+
192
+ test('TeamsConfigSchema validates config with token_expires_at', () => {
193
+ const result = TeamsConfigSchema.safeParse({
194
+ current_team: '19:abc123@thread.tacv2',
195
+ token: 'token_value',
196
+ token_expires_at: '2024-01-01T00:00:00.000Z',
197
+ teams: {
198
+ '19:abc123@thread.tacv2': {
199
+ team_id: '19:abc123@thread.tacv2',
200
+ team_name: 'Test Team',
201
+ },
202
+ },
203
+ })
204
+ expect(result.success).toBe(true)
205
+ })
206
+
207
+ test('TeamsConfigSchema rejects missing required fields', () => {
208
+ const result = TeamsConfigSchema.safeParse({
209
+ current_team: null,
210
+ token: 'token_value',
211
+ })
212
+ expect(result.success).toBe(false)
213
+ })
214
+
215
+ // TeamsError tests
216
+ test('TeamsError has correct name and code', () => {
217
+ const error = new TeamsError('Test error', 'TEST_CODE')
218
+ expect(error.name).toBe('TeamsError')
219
+ expect(error.message).toBe('Test error')
220
+ expect(error.code).toBe('TEST_CODE')
221
+ })
222
+
223
+ test('TeamsError is instance of Error', () => {
224
+ const error = new TeamsError('Test error', 'TEST_CODE')
225
+ expect(error instanceof Error).toBe(true)
226
+ })