agent-messenger 2.8.0 → 2.10.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 (169) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +0 -11
  3. package/dist/package.json +1 -1
  4. package/dist/src/platforms/channeltalk/commands/snapshot.d.ts +4 -2
  5. package/dist/src/platforms/channeltalk/commands/snapshot.d.ts.map +1 -1
  6. package/dist/src/platforms/channeltalk/commands/snapshot.js +86 -31
  7. package/dist/src/platforms/channeltalk/commands/snapshot.js.map +1 -1
  8. package/dist/src/platforms/channeltalkbot/commands/snapshot.d.ts +3 -1
  9. package/dist/src/platforms/channeltalkbot/commands/snapshot.d.ts.map +1 -1
  10. package/dist/src/platforms/channeltalkbot/commands/snapshot.js +110 -60
  11. package/dist/src/platforms/channeltalkbot/commands/snapshot.js.map +1 -1
  12. package/dist/src/platforms/discord/commands/snapshot.d.ts +1 -0
  13. package/dist/src/platforms/discord/commands/snapshot.d.ts.map +1 -1
  14. package/dist/src/platforms/discord/commands/snapshot.js +48 -34
  15. package/dist/src/platforms/discord/commands/snapshot.js.map +1 -1
  16. package/dist/src/platforms/discordbot/commands/snapshot.d.ts +2 -0
  17. package/dist/src/platforms/discordbot/commands/snapshot.d.ts.map +1 -1
  18. package/dist/src/platforms/discordbot/commands/snapshot.js +46 -34
  19. package/dist/src/platforms/discordbot/commands/snapshot.js.map +1 -1
  20. package/dist/src/platforms/slack/commands/snapshot.d.ts.map +1 -1
  21. package/dist/src/platforms/slack/commands/snapshot.js +75 -55
  22. package/dist/src/platforms/slack/commands/snapshot.js.map +1 -1
  23. package/dist/src/platforms/teams/client.d.ts +9 -1
  24. package/dist/src/platforms/teams/client.d.ts.map +1 -1
  25. package/dist/src/platforms/teams/client.js +69 -18
  26. package/dist/src/platforms/teams/client.js.map +1 -1
  27. package/dist/src/platforms/teams/commands/auth.d.ts.map +1 -1
  28. package/dist/src/platforms/teams/commands/auth.js +7 -2
  29. package/dist/src/platforms/teams/commands/auth.js.map +1 -1
  30. package/dist/src/platforms/teams/commands/channel.d.ts.map +1 -1
  31. package/dist/src/platforms/teams/commands/channel.js +18 -3
  32. package/dist/src/platforms/teams/commands/channel.js.map +1 -1
  33. package/dist/src/platforms/teams/commands/file.d.ts.map +1 -1
  34. package/dist/src/platforms/teams/commands/file.js +18 -3
  35. package/dist/src/platforms/teams/commands/file.js.map +1 -1
  36. package/dist/src/platforms/teams/commands/message.d.ts.map +1 -1
  37. package/dist/src/platforms/teams/commands/message.js +24 -4
  38. package/dist/src/platforms/teams/commands/message.js.map +1 -1
  39. package/dist/src/platforms/teams/commands/reaction.d.ts.map +1 -1
  40. package/dist/src/platforms/teams/commands/reaction.js +12 -2
  41. package/dist/src/platforms/teams/commands/reaction.js.map +1 -1
  42. package/dist/src/platforms/teams/commands/snapshot.d.ts +1 -0
  43. package/dist/src/platforms/teams/commands/snapshot.d.ts.map +1 -1
  44. package/dist/src/platforms/teams/commands/snapshot.js +50 -32
  45. package/dist/src/platforms/teams/commands/snapshot.js.map +1 -1
  46. package/dist/src/platforms/teams/commands/team.d.ts.map +1 -1
  47. package/dist/src/platforms/teams/commands/team.js +6 -1
  48. package/dist/src/platforms/teams/commands/team.js.map +1 -1
  49. package/dist/src/platforms/teams/commands/user.d.ts.map +1 -1
  50. package/dist/src/platforms/teams/commands/user.js +18 -3
  51. package/dist/src/platforms/teams/commands/user.js.map +1 -1
  52. package/dist/src/platforms/teams/commands/whoami.d.ts.map +1 -1
  53. package/dist/src/platforms/teams/commands/whoami.js +6 -1
  54. package/dist/src/platforms/teams/commands/whoami.js.map +1 -1
  55. package/dist/src/platforms/teams/credential-manager.d.ts +3 -1
  56. package/dist/src/platforms/teams/credential-manager.d.ts.map +1 -1
  57. package/dist/src/platforms/teams/credential-manager.js +6 -1
  58. package/dist/src/platforms/teams/credential-manager.js.map +1 -1
  59. package/dist/src/platforms/teams/ensure-auth.d.ts.map +1 -1
  60. package/dist/src/platforms/teams/ensure-auth.js +7 -2
  61. package/dist/src/platforms/teams/ensure-auth.js.map +1 -1
  62. package/dist/src/platforms/teams/token-extractor.d.ts +3 -1
  63. package/dist/src/platforms/teams/token-extractor.d.ts.map +1 -1
  64. package/dist/src/platforms/teams/token-extractor.js +67 -10
  65. package/dist/src/platforms/teams/token-extractor.js.map +1 -1
  66. package/dist/src/platforms/teams/types.d.ts +17 -0
  67. package/dist/src/platforms/teams/types.d.ts.map +1 -1
  68. package/dist/src/platforms/teams/types.js +2 -0
  69. package/dist/src/platforms/teams/types.js.map +1 -1
  70. package/dist/src/platforms/webex/client.d.ts +3 -0
  71. package/dist/src/platforms/webex/client.d.ts.map +1 -1
  72. package/dist/src/platforms/webex/client.js +58 -13
  73. package/dist/src/platforms/webex/client.js.map +1 -1
  74. package/dist/src/platforms/webex/commands/auth.d.ts.map +1 -1
  75. package/dist/src/platforms/webex/commands/auth.js +61 -10
  76. package/dist/src/platforms/webex/commands/auth.js.map +1 -1
  77. package/dist/src/platforms/webex/commands/snapshot.d.ts +1 -0
  78. package/dist/src/platforms/webex/commands/snapshot.d.ts.map +1 -1
  79. package/dist/src/platforms/webex/commands/snapshot.js +14 -7
  80. package/dist/src/platforms/webex/commands/snapshot.js.map +1 -1
  81. package/dist/src/platforms/webex/credential-manager.d.ts.map +1 -1
  82. package/dist/src/platforms/webex/credential-manager.js +18 -6
  83. package/dist/src/platforms/webex/credential-manager.js.map +1 -1
  84. package/dist/src/platforms/webex/encryption.d.ts.map +1 -1
  85. package/dist/src/platforms/webex/encryption.js +3 -1
  86. package/dist/src/platforms/webex/encryption.js.map +1 -1
  87. package/dist/src/platforms/webex/ensure-auth.d.ts.map +1 -1
  88. package/dist/src/platforms/webex/ensure-auth.js +10 -2
  89. package/dist/src/platforms/webex/ensure-auth.js.map +1 -1
  90. package/dist/src/platforms/webex/token-extractor.d.ts +1 -0
  91. package/dist/src/platforms/webex/token-extractor.d.ts.map +1 -1
  92. package/dist/src/platforms/webex/token-extractor.js +21 -4
  93. package/dist/src/platforms/webex/token-extractor.js.map +1 -1
  94. package/docs/content/docs/agent-skills.mdx +0 -10
  95. package/docs/content/docs/cli/channeltalk.mdx +18 -8
  96. package/docs/content/docs/cli/channeltalkbot.mdx +16 -6
  97. package/docs/content/docs/cli/discord.mdx +23 -7
  98. package/docs/content/docs/cli/discordbot.mdx +23 -7
  99. package/docs/content/docs/cli/slack.mdx +24 -7
  100. package/docs/content/docs/cli/teams.mdx +24 -8
  101. package/docs/content/docs/cli/webex.mdx +15 -2
  102. package/e2e/webex.e2e.test.ts +57 -0
  103. package/package.json +1 -1
  104. package/skills/agent-channeltalk/SKILL.md +19 -9
  105. package/skills/agent-channeltalk/references/common-patterns.md +10 -9
  106. package/skills/agent-channeltalkbot/SKILL.md +19 -9
  107. package/skills/agent-channeltalkbot/references/common-patterns.md +10 -9
  108. package/skills/agent-discord/SKILL.md +18 -9
  109. package/skills/agent-discord/references/common-patterns.md +8 -7
  110. package/skills/agent-discordbot/SKILL.md +18 -9
  111. package/skills/agent-instagram/SKILL.md +1 -1
  112. package/skills/agent-kakaotalk/SKILL.md +1 -1
  113. package/skills/agent-line/SKILL.md +1 -1
  114. package/skills/agent-slack/SKILL.md +19 -10
  115. package/skills/agent-slack/references/common-patterns.md +4 -7
  116. package/skills/agent-slackbot/SKILL.md +1 -1
  117. package/skills/agent-teams/SKILL.md +18 -9
  118. package/skills/agent-teams/references/common-patterns.md +9 -7
  119. package/skills/agent-telegram/SKILL.md +1 -1
  120. package/skills/agent-webex/SKILL.md +13 -4
  121. package/skills/agent-webex/references/common-patterns.md +8 -2
  122. package/skills/agent-wechatbot/SKILL.md +1 -1
  123. package/skills/agent-whatsapp/SKILL.md +1 -1
  124. package/skills/agent-whatsappbot/SKILL.md +1 -1
  125. package/src/platforms/channeltalk/commands/snapshot.test.ts +58 -26
  126. package/src/platforms/channeltalk/commands/snapshot.ts +107 -33
  127. package/src/platforms/channeltalkbot/commands/snapshot.test.ts +26 -8
  128. package/src/platforms/channeltalkbot/commands/snapshot.ts +131 -64
  129. package/src/platforms/discord/commands/snapshot.test.ts +1 -1
  130. package/src/platforms/discord/commands/snapshot.ts +58 -42
  131. package/src/platforms/discordbot/commands/snapshot.test.ts +40 -18
  132. package/src/platforms/discordbot/commands/snapshot.ts +54 -37
  133. package/src/platforms/slack/commands/snapshot.test.ts +63 -8
  134. package/src/platforms/slack/commands/snapshot.ts +98 -66
  135. package/src/platforms/teams/client.test.ts +34 -30
  136. package/src/platforms/teams/client.ts +92 -20
  137. package/src/platforms/teams/commands/auth.test.ts +6 -2
  138. package/src/platforms/teams/commands/auth.ts +7 -2
  139. package/src/platforms/teams/commands/channel.test.ts +6 -6
  140. package/src/platforms/teams/commands/channel.ts +18 -3
  141. package/src/platforms/teams/commands/file.ts +18 -3
  142. package/src/platforms/teams/commands/message.ts +24 -4
  143. package/src/platforms/teams/commands/reaction.ts +12 -2
  144. package/src/platforms/teams/commands/snapshot.test.ts +1 -1
  145. package/src/platforms/teams/commands/snapshot.ts +59 -39
  146. package/src/platforms/teams/commands/team.test.ts +2 -2
  147. package/src/platforms/teams/commands/team.ts +6 -1
  148. package/src/platforms/teams/commands/user.ts +18 -3
  149. package/src/platforms/teams/commands/whoami.ts +6 -1
  150. package/src/platforms/teams/credential-manager.test.ts +25 -0
  151. package/src/platforms/teams/credential-manager.ts +13 -3
  152. package/src/platforms/teams/ensure-auth.test.ts +6 -1
  153. package/src/platforms/teams/ensure-auth.ts +7 -2
  154. package/src/platforms/teams/token-extractor.ts +77 -12
  155. package/src/platforms/teams/types.test.ts +17 -0
  156. package/src/platforms/teams/types.ts +6 -0
  157. package/src/platforms/webex/client.test.ts +157 -13
  158. package/src/platforms/webex/client.ts +64 -15
  159. package/src/platforms/webex/commands/auth.test.ts +122 -1
  160. package/src/platforms/webex/commands/auth.ts +72 -17
  161. package/src/platforms/webex/commands/snapshot.test.ts +14 -1
  162. package/src/platforms/webex/commands/snapshot.ts +17 -9
  163. package/src/platforms/webex/credential-manager.test.ts +63 -0
  164. package/src/platforms/webex/credential-manager.ts +22 -8
  165. package/src/platforms/webex/encryption.test.ts +54 -0
  166. package/src/platforms/webex/encryption.ts +3 -1
  167. package/src/platforms/webex/ensure-auth.ts +10 -2
  168. package/src/platforms/webex/token-extractor.test.ts +32 -3
  169. package/src/platforms/webex/token-extractor.ts +26 -5
@@ -2,7 +2,15 @@ import { readFile } from 'node:fs/promises'
2
2
  import { basename } from 'node:path'
3
3
 
4
4
  import { TeamsCredentialManager } from './credential-manager'
5
- import type { TeamsChannel, TeamsFile, TeamsMessage, TeamsTeam, TeamsUser } from './types'
5
+ import type {
6
+ TeamsAccountType,
7
+ TeamsChannel,
8
+ TeamsFile,
9
+ TeamsMessage,
10
+ TeamsRegion,
11
+ TeamsTeam,
12
+ TeamsUser,
13
+ } from './types'
6
14
  import { TeamsError } from './types'
7
15
 
8
16
  interface RateLimitBucket {
@@ -10,18 +18,28 @@ interface RateLimitBucket {
10
18
  resetAt: number
11
19
  }
12
20
 
13
- const MSG_API_BASE = 'https://emea.ng.msg.teams.microsoft.com/v1'
21
+ const PERSONAL_MSG_API_BASE = 'https://msgapi.teams.live.com/v1'
14
22
  const CSA_API_BASE = 'https://teams.microsoft.com/api'
15
23
  const MAX_RETRIES = 3
16
24
  const BASE_BACKOFF_MS = 100
25
+ const DEFAULT_REGION: TeamsRegion = 'amer'
26
+ const REGIONS: TeamsRegion[] = ['amer', 'emea', 'apac']
17
27
 
18
28
  export class TeamsClient {
19
29
  private token: string | null = null
20
30
  private tokenExpiresAt?: Date
31
+ private isPersonalAccount: boolean = false
32
+ private region: TeamsRegion = DEFAULT_REGION
33
+ private regionDiscovered: boolean = false
21
34
  private buckets: Map<string, RateLimitBucket> = new Map()
22
35
  private globalRateLimitUntil: number = 0
23
36
 
24
- async login(credentials?: { token: string; tokenExpiresAt?: string }): Promise<this> {
37
+ async login(credentials?: {
38
+ token: string
39
+ tokenExpiresAt?: string
40
+ accountType?: TeamsAccountType
41
+ region?: TeamsRegion
42
+ }): Promise<this> {
25
43
  if (credentials) {
26
44
  if (!credentials.token) {
27
45
  throw new TeamsError('Token is required', 'missing_token')
@@ -30,6 +48,11 @@ export class TeamsClient {
30
48
  if (credentials.tokenExpiresAt) {
31
49
  this.tokenExpiresAt = new Date(credentials.tokenExpiresAt)
32
50
  }
51
+ this.isPersonalAccount = credentials.accountType === 'personal'
52
+ if (credentials.region) {
53
+ this.region = credentials.region
54
+ this.regionDiscovered = true
55
+ }
33
56
  return this
34
57
  }
35
58
 
@@ -43,7 +66,16 @@ export class TeamsClient {
43
66
  'no_credentials',
44
67
  )
45
68
  }
46
- return this.login({ token: creds.token, tokenExpiresAt: creds.tokenExpiresAt })
69
+ return this.login({
70
+ token: creds.token,
71
+ tokenExpiresAt: creds.tokenExpiresAt,
72
+ accountType: creds.accountType,
73
+ region: creds.region,
74
+ })
75
+ }
76
+
77
+ getRegion(): TeamsRegion {
78
+ return this.region
47
79
  }
48
80
 
49
81
  private ensureAuth(): string {
@@ -108,12 +140,47 @@ export class TeamsClient {
108
140
  return new Promise((resolve) => setTimeout(resolve, ms))
109
141
  }
110
142
 
111
- private async request<T>(method: string, path: string, body?: unknown, baseUrl: string = MSG_API_BASE): Promise<T> {
143
+ private getMsgApiBase(): string {
144
+ if (this.isPersonalAccount) return PERSONAL_MSG_API_BASE
145
+ return `https://${this.region}.ng.msg.teams.microsoft.com/v1`
146
+ }
147
+
148
+ private async discoverRegion(): Promise<void> {
149
+ if (this.isPersonalAccount) {
150
+ this.regionDiscovered = true
151
+ return
152
+ }
153
+
154
+ const token = this.ensureAuth()
155
+
156
+ for (const region of REGIONS) {
157
+ try {
158
+ const response = await fetch(`https://${region}.ng.msg.teams.microsoft.com/v1/users/ME/properties`, {
159
+ headers: {
160
+ 'X-Skypetoken': token,
161
+ },
162
+ })
163
+
164
+ if (response.ok || response.status !== 403) {
165
+ this.region = region
166
+ break
167
+ }
168
+ } catch {}
169
+ }
170
+
171
+ this.regionDiscovered = true
172
+ }
173
+
174
+ private async request<T>(method: string, path: string, body?: unknown, baseUrl?: string): Promise<T> {
112
175
  if (this.isTokenExpired()) {
113
176
  throw new TeamsError('Token has expired. Run "auth extract" to refresh.', 'token_expired')
114
177
  }
115
178
 
116
- const url = `${baseUrl}${path}`
179
+ if (baseUrl === undefined && !this.regionDiscovered) {
180
+ await this.discoverRegion()
181
+ }
182
+
183
+ const url = `${baseUrl ?? this.getMsgApiBase()}${path}`
117
184
  const bucketKey = this.getBucketKey(method, path)
118
185
 
119
186
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
@@ -144,7 +211,7 @@ export class TeamsClient {
144
211
  const errorBody = (await response.json().catch(() => null)) as {
145
212
  message?: string
146
213
  } | null
147
- throw new TeamsError(errorBody?.message ?? 'Rate limited', 'rate_limited')
214
+ throw new TeamsError(errorBody?.message || 'Rate limited', 'rate_limited')
148
215
  }
149
216
 
150
217
  if (response.status >= 500 && attempt < MAX_RETRIES) {
@@ -158,7 +225,7 @@ export class TeamsClient {
158
225
  code?: string | number
159
226
  } | null
160
227
  throw new TeamsError(
161
- errorBody?.message ?? `HTTP ${response.status}`,
228
+ errorBody?.message || `HTTP ${response.status}`,
162
229
  errorBody?.code?.toString() ?? `http_${response.status}`,
163
230
  )
164
231
  }
@@ -173,12 +240,16 @@ export class TeamsClient {
173
240
  throw new TeamsError('Request failed after retries', 'max_retries')
174
241
  }
175
242
 
176
- private async requestFormData<T>(path: string, formData: FormData, baseUrl: string = MSG_API_BASE): Promise<T> {
243
+ private async requestFormData<T>(path: string, formData: FormData, baseUrl?: string): Promise<T> {
177
244
  if (this.isTokenExpired()) {
178
245
  throw new TeamsError('Token has expired. Run "auth extract" to refresh.', 'token_expired')
179
246
  }
180
247
 
181
- const url = `${baseUrl}${path}`
248
+ if (baseUrl === undefined && !this.regionDiscovered) {
249
+ await this.discoverRegion()
250
+ }
251
+
252
+ const url = `${baseUrl ?? this.getMsgApiBase()}${path}`
182
253
  const bucketKey = this.getBucketKey('POST', path)
183
254
 
184
255
  await this.waitForRateLimit(bucketKey)
@@ -199,7 +270,7 @@ export class TeamsClient {
199
270
  code?: string | number
200
271
  } | null
201
272
  throw new TeamsError(
202
- errorBody?.message ?? `HTTP ${response.status}`,
273
+ errorBody?.message || `HTTP ${response.status}`,
203
274
  errorBody?.code?.toString() ?? `http_${response.status}`,
204
275
  )
205
276
  }
@@ -210,13 +281,14 @@ export class TeamsClient {
210
281
  async testAuth(): Promise<TeamsUser> {
211
282
  interface UserProperties {
212
283
  userDetails?: string
284
+ primaryMemberName?: string
213
285
  locale?: string
214
286
  }
215
287
  const props = await this.request<UserProperties>('GET', '/users/ME/properties')
216
288
  const userDetails = props.userDetails ? JSON.parse(props.userDetails) : {}
217
289
  return {
218
290
  id: 'ME',
219
- displayName: userDetails.name || 'Teams User',
291
+ displayName: userDetails.name || props.primaryMemberName || 'Teams User',
220
292
  }
221
293
  }
222
294
 
@@ -272,7 +344,7 @@ export class TeamsClient {
272
344
  async sendMessage(teamId: string, channelId: string, content: string): Promise<TeamsMessage> {
273
345
  return this.request<TeamsMessage>(
274
346
  'POST',
275
- `/csa/emea/api/v2/teams/${teamId}/channels/${channelId}/messages`,
347
+ `/csa/${this.region}/api/v2/teams/${teamId}/channels/${channelId}/messages`,
276
348
  { content },
277
349
  CSA_API_BASE,
278
350
  )
@@ -281,7 +353,7 @@ export class TeamsClient {
281
353
  async getMessages(teamId: string, channelId: string, limit: number = 50): Promise<TeamsMessage[]> {
282
354
  return this.request<TeamsMessage[]>(
283
355
  'GET',
284
- `/csa/emea/api/v2/teams/${teamId}/channels/${channelId}/messages?limit=${limit}`,
356
+ `/csa/${this.region}/api/v2/teams/${teamId}/channels/${channelId}/messages?limit=${limit}`,
285
357
  undefined,
286
358
  CSA_API_BASE,
287
359
  )
@@ -290,7 +362,7 @@ export class TeamsClient {
290
362
  async getMessage(teamId: string, channelId: string, messageId: string): Promise<TeamsMessage> {
291
363
  return this.request<TeamsMessage>(
292
364
  'GET',
293
- `/csa/emea/api/v2/teams/${teamId}/channels/${channelId}/messages/${messageId}`,
365
+ `/csa/${this.region}/api/v2/teams/${teamId}/channels/${channelId}/messages/${messageId}`,
294
366
  undefined,
295
367
  CSA_API_BASE,
296
368
  )
@@ -299,7 +371,7 @@ export class TeamsClient {
299
371
  async deleteMessage(teamId: string, channelId: string, messageId: string): Promise<void> {
300
372
  return this.request<void>(
301
373
  'DELETE',
302
- `/csa/emea/api/v2/teams/${teamId}/channels/${channelId}/messages/${messageId}`,
374
+ `/csa/${this.region}/api/v2/teams/${teamId}/channels/${channelId}/messages/${messageId}`,
303
375
  undefined,
304
376
  CSA_API_BASE,
305
377
  )
@@ -308,7 +380,7 @@ export class TeamsClient {
308
380
  async addReaction(teamId: string, channelId: string, messageId: string, emoji: string): Promise<void> {
309
381
  return this.request<void>(
310
382
  'POST',
311
- `/csa/emea/api/v2/teams/${teamId}/channels/${channelId}/messages/${messageId}/reactions`,
383
+ `/csa/${this.region}/api/v2/teams/${teamId}/channels/${channelId}/messages/${messageId}/reactions`,
312
384
  { emoji },
313
385
  CSA_API_BASE,
314
386
  )
@@ -317,7 +389,7 @@ export class TeamsClient {
317
389
  async removeReaction(teamId: string, channelId: string, messageId: string, emoji: string): Promise<void> {
318
390
  return this.request<void>(
319
391
  'DELETE',
320
- `/csa/emea/api/v2/teams/${teamId}/channels/${channelId}/messages/${messageId}/reactions/${emoji}`,
392
+ `/csa/${this.region}/api/v2/teams/${teamId}/channels/${channelId}/messages/${messageId}/reactions/${emoji}`,
321
393
  undefined,
322
394
  CSA_API_BASE,
323
395
  )
@@ -339,7 +411,7 @@ export class TeamsClient {
339
411
  formData.append('file', new Blob([fileBuffer]), filename)
340
412
 
341
413
  return this.requestFormData<TeamsFile>(
342
- `/csa/emea/api/v2/teams/${teamId}/channels/${channelId}/files`,
414
+ `/csa/${this.region}/api/v2/teams/${teamId}/channels/${channelId}/files`,
343
415
  formData,
344
416
  CSA_API_BASE,
345
417
  )
@@ -348,7 +420,7 @@ export class TeamsClient {
348
420
  async listFiles(teamId: string, channelId: string): Promise<TeamsFile[]> {
349
421
  return this.request<TeamsFile[]>(
350
422
  'GET',
351
- `/csa/emea/api/v2/teams/${teamId}/channels/${channelId}/files`,
423
+ `/csa/${this.region}/api/v2/teams/${teamId}/channels/${channelId}/files`,
352
424
  undefined,
353
425
  CSA_API_BASE,
354
426
  )
@@ -12,6 +12,7 @@ let credManagerLoadConfigSpy: ReturnType<typeof spyOn>
12
12
  let credManagerSaveConfigSpy: ReturnType<typeof spyOn>
13
13
  let credManagerClearCredentialsSpy: ReturnType<typeof spyOn>
14
14
  let credManagerIsTokenExpiredSpy: ReturnType<typeof spyOn>
15
+ let clientGetRegionSpy: ReturnType<typeof spyOn>
15
16
 
16
17
  beforeEach(() => {
17
18
  extractorExtractSpy = spyOn(TeamsTokenExtractor.prototype, 'extract').mockResolvedValue([
@@ -29,6 +30,8 @@ beforeEach(() => {
29
30
  { id: 'team-2', name: 'Team Two' },
30
31
  ])
31
32
 
33
+ clientGetRegionSpy = spyOn(TeamsClient.prototype, 'getRegion').mockReturnValue('emea')
34
+
32
35
  credManagerLoadConfigSpy = spyOn(TeamsCredentialManager.prototype, 'loadConfig').mockResolvedValue(null)
33
36
 
34
37
  credManagerSaveConfigSpy = spyOn(TeamsCredentialManager.prototype, 'saveConfig').mockResolvedValue(undefined)
@@ -48,6 +51,7 @@ afterEach(() => {
48
51
  credManagerSaveConfigSpy?.mockRestore()
49
52
  credManagerClearCredentialsSpy?.mockRestore()
50
53
  credManagerIsTokenExpiredSpy?.mockRestore()
54
+ clientGetRegionSpy?.mockRestore()
51
55
  })
52
56
 
53
57
  test('extract: calls TeamsTokenExtractor', async () => {
@@ -59,7 +63,7 @@ test('extract: calls TeamsTokenExtractor', async () => {
59
63
  })
60
64
 
61
65
  test('extract: validates token with TeamsClient', async () => {
62
- const client = await new TeamsClient().login({ token: 'test-skype-token-123' })
66
+ const client = await new TeamsClient().login({ token: 'test-skype-token-123', region: 'emea' })
63
67
  const authInfo = await client.testAuth()
64
68
  expect(authInfo).toBeDefined()
65
69
  expect(authInfo.id).toBe('user-123')
@@ -67,7 +71,7 @@ test('extract: validates token with TeamsClient', async () => {
67
71
  })
68
72
 
69
73
  test('extract: discovers teams', async () => {
70
- const client = await new TeamsClient().login({ token: 'test-skype-token-123' })
74
+ const client = await new TeamsClient().login({ token: 'test-skype-token-123', region: 'emea' })
71
75
  const teams = await client.listTeams()
72
76
  expect(teams).toHaveLength(2)
73
77
  expect(teams[0].id).toBe('team-1')
@@ -16,7 +16,8 @@ export async function extractAction(options: { pretty?: boolean; debug?: boolean
16
16
  return
17
17
  }
18
18
 
19
- const extractor = new TeamsTokenExtractor()
19
+ const debugLog = options.debug ? (msg: string) => debug(`[debug] ${msg}`) : undefined
20
+ const extractor = new TeamsTokenExtractor(undefined, undefined, debugLog)
20
21
 
21
22
  if (process.platform === 'darwin') {
22
23
  console.log('')
@@ -72,7 +73,7 @@ export async function extractAction(options: { pretty?: boolean; debug?: boolean
72
73
  }
73
74
 
74
75
  try {
75
- const client = await new TeamsClient().login({ token })
76
+ const client = await new TeamsClient().login({ token, accountType })
76
77
  const authInfo = await client.testAuth()
77
78
  const teams = await client.listTeams()
78
79
 
@@ -88,6 +89,7 @@ export async function extractAction(options: { pretty?: boolean; debug?: boolean
88
89
  const account: TeamsAccount = {
89
90
  token,
90
91
  token_expires_at: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
92
+ region: client.getRegion(),
91
93
  account_type: accountType,
92
94
  user_name: authInfo.displayName,
93
95
  current_team: teams[0]?.id ?? null,
@@ -191,6 +193,7 @@ async function extractManualToken(token: string, options: { pretty?: boolean; de
191
193
  const account: TeamsAccount = {
192
194
  token,
193
195
  token_expires_at: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
196
+ region: client.getRegion(),
194
197
  account_type: accountType,
195
198
  user_name: authInfo.displayName,
196
199
  current_team: teams[0].id,
@@ -295,6 +298,8 @@ export async function statusAction(options: { pretty?: boolean }): Promise<void>
295
298
  const client = await new TeamsClient().login({
296
299
  token: account.token,
297
300
  tokenExpiresAt: account.token_expires_at ?? undefined,
301
+ accountType: account.account_type,
302
+ region: account.region,
298
303
  })
299
304
  const authInfo = await client.testAuth()
300
305
  displayName = authInfo.displayName
@@ -67,7 +67,7 @@ afterEach(() => {
67
67
 
68
68
  test('list: returns channels from team', async () => {
69
69
  // given
70
- const client = await new TeamsClient().login({ token: 'test-token' })
70
+ const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
71
71
 
72
72
  // when
73
73
  const channels = await client.listChannels('team-1')
@@ -80,7 +80,7 @@ test('list: returns channels from team', async () => {
80
80
 
81
81
  test('list: includes channel metadata', async () => {
82
82
  // given
83
- const client = await new TeamsClient().login({ token: 'test-token' })
83
+ const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
84
84
  const channels = await client.listChannels('team-1')
85
85
 
86
86
  // when
@@ -95,7 +95,7 @@ test('list: includes channel metadata', async () => {
95
95
 
96
96
  test('info: returns channel details', async () => {
97
97
  // given
98
- const client = await new TeamsClient().login({ token: 'test-token' })
98
+ const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
99
99
 
100
100
  // when
101
101
  const channel = await client.getChannel('team-1', 'ch-1')
@@ -108,7 +108,7 @@ test('info: returns channel details', async () => {
108
108
 
109
109
  test('info: throws error for non-existent channel', async () => {
110
110
  // given
111
- const client = await new TeamsClient().login({ token: 'test-token' })
111
+ const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
112
112
 
113
113
  // when/then
114
114
  try {
@@ -121,7 +121,7 @@ test('info: throws error for non-existent channel', async () => {
121
121
 
122
122
  test('history: returns messages', async () => {
123
123
  // given
124
- const client = await new TeamsClient().login({ token: 'test-token' })
124
+ const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
125
125
 
126
126
  // when
127
127
  const messages = await client.getMessages('team-1', 'ch-1', 50)
@@ -136,7 +136,7 @@ test('history: returns messages', async () => {
136
136
 
137
137
  test('history: includes message metadata', async () => {
138
138
  // given
139
- const client = await new TeamsClient().login({ token: 'test-token' })
139
+ const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
140
140
  const messages = await client.getMessages('team-1', 'ch-1', 50)
141
141
 
142
142
  // when
@@ -16,7 +16,12 @@ export async function listAction(teamId: string, options: { pretty?: boolean }):
16
16
  process.exit(1)
17
17
  }
18
18
 
19
- const client = await new TeamsClient().login({ token: cred.token, tokenExpiresAt: cred.tokenExpiresAt })
19
+ const client = await new TeamsClient().login({
20
+ token: cred.token,
21
+ tokenExpiresAt: cred.tokenExpiresAt,
22
+ accountType: cred.accountType,
23
+ region: cred.region,
24
+ })
20
25
  const channels = await client.listChannels(teamId)
21
26
 
22
27
  const output = channels.map((ch) => ({
@@ -42,7 +47,12 @@ export async function infoAction(teamId: string, channelId: string, options: { p
42
47
  process.exit(1)
43
48
  }
44
49
 
45
- const client = await new TeamsClient().login({ token: cred.token, tokenExpiresAt: cred.tokenExpiresAt })
50
+ const client = await new TeamsClient().login({
51
+ token: cred.token,
52
+ tokenExpiresAt: cred.tokenExpiresAt,
53
+ accountType: cred.accountType,
54
+ region: cred.region,
55
+ })
46
56
  const channel = await client.getChannel(teamId, channelId)
47
57
 
48
58
  const output = {
@@ -72,7 +82,12 @@ export async function historyAction(
72
82
  process.exit(1)
73
83
  }
74
84
 
75
- const client = await new TeamsClient().login({ token: cred.token, tokenExpiresAt: cred.tokenExpiresAt })
85
+ const client = await new TeamsClient().login({
86
+ token: cred.token,
87
+ tokenExpiresAt: cred.tokenExpiresAt,
88
+ accountType: cred.accountType,
89
+ region: cred.region,
90
+ })
76
91
  const messages = await client.getMessages(teamId, channelId, options.limit || 50)
77
92
 
78
93
  const output = messages.map((msg) => ({
@@ -24,7 +24,12 @@ export async function uploadAction(
24
24
  process.exit(1)
25
25
  }
26
26
 
27
- const client = await new TeamsClient().login({ token: cred.token, tokenExpiresAt: cred.tokenExpiresAt })
27
+ const client = await new TeamsClient().login({
28
+ token: cred.token,
29
+ tokenExpiresAt: cred.tokenExpiresAt,
30
+ accountType: cred.accountType,
31
+ region: cred.region,
32
+ })
28
33
  const filePath = resolve(path)
29
34
  const file = await client.uploadFile(teamId, channelId, filePath)
30
35
 
@@ -52,7 +57,12 @@ export async function listAction(teamId: string, channelId: string, options: { p
52
57
  process.exit(1)
53
58
  }
54
59
 
55
- const client = await new TeamsClient().login({ token: cred.token, tokenExpiresAt: cred.tokenExpiresAt })
60
+ const client = await new TeamsClient().login({
61
+ token: cred.token,
62
+ tokenExpiresAt: cred.tokenExpiresAt,
63
+ accountType: cred.accountType,
64
+ region: cred.region,
65
+ })
56
66
  const files = await client.listFiles(teamId, channelId)
57
67
 
58
68
  const output = files.map((file: TeamsFile) => ({
@@ -84,7 +94,12 @@ export async function infoAction(
84
94
  process.exit(1)
85
95
  }
86
96
 
87
- const client = await new TeamsClient().login({ token: cred.token, tokenExpiresAt: cred.tokenExpiresAt })
97
+ const client = await new TeamsClient().login({
98
+ token: cred.token,
99
+ tokenExpiresAt: cred.tokenExpiresAt,
100
+ accountType: cred.accountType,
101
+ region: cred.region,
102
+ })
88
103
  const files = await client.listFiles(teamId, channelId)
89
104
  const fileData = files.find((f) => f.id === fileId)
90
105
 
@@ -22,7 +22,12 @@ export async function sendAction(
22
22
  process.exit(1)
23
23
  }
24
24
 
25
- const client = await new TeamsClient().login({ token: cred.token, tokenExpiresAt: cred.tokenExpiresAt })
25
+ const client = await new TeamsClient().login({
26
+ token: cred.token,
27
+ tokenExpiresAt: cred.tokenExpiresAt,
28
+ accountType: cred.accountType,
29
+ region: cred.region,
30
+ })
26
31
  const message = await client.sendMessage(teamId, channelId, content)
27
32
 
28
33
  const output = {
@@ -52,7 +57,12 @@ export async function listAction(
52
57
  process.exit(1)
53
58
  }
54
59
 
55
- const client = await new TeamsClient().login({ token: cred.token, tokenExpiresAt: cred.tokenExpiresAt })
60
+ const client = await new TeamsClient().login({
61
+ token: cred.token,
62
+ tokenExpiresAt: cred.tokenExpiresAt,
63
+ accountType: cred.accountType,
64
+ region: cred.region,
65
+ })
56
66
  const limit = options.limit || 50
57
67
  const messages = await client.getMessages(teamId, channelId, limit)
58
68
 
@@ -84,7 +94,12 @@ export async function getAction(
84
94
  process.exit(1)
85
95
  }
86
96
 
87
- const client = await new TeamsClient().login({ token: cred.token, tokenExpiresAt: cred.tokenExpiresAt })
97
+ const client = await new TeamsClient().login({
98
+ token: cred.token,
99
+ tokenExpiresAt: cred.tokenExpiresAt,
100
+ accountType: cred.accountType,
101
+ region: cred.region,
102
+ })
88
103
  const message = await client.getMessage(teamId, channelId, messageId)
89
104
 
90
105
  if (!message) {
@@ -125,7 +140,12 @@ export async function deleteAction(
125
140
  process.exit(0)
126
141
  }
127
142
 
128
- const client = await new TeamsClient().login({ token: cred.token, tokenExpiresAt: cred.tokenExpiresAt })
143
+ const client = await new TeamsClient().login({
144
+ token: cred.token,
145
+ tokenExpiresAt: cred.tokenExpiresAt,
146
+ accountType: cred.accountType,
147
+ region: cred.region,
148
+ })
129
149
  await client.deleteMessage(teamId, channelId, messageId)
130
150
 
131
151
  console.log(formatOutput({ deleted: messageId }, options.pretty))
@@ -23,7 +23,12 @@ export async function addAction(
23
23
  return
24
24
  }
25
25
 
26
- const client = await new TeamsClient().login({ token: cred.token, tokenExpiresAt: cred.tokenExpiresAt })
26
+ const client = await new TeamsClient().login({
27
+ token: cred.token,
28
+ tokenExpiresAt: cred.tokenExpiresAt,
29
+ accountType: cred.accountType,
30
+ region: cred.region,
31
+ })
27
32
  await client.addReaction(teamId, channelId, messageId, emoji)
28
33
 
29
34
  console.log(
@@ -60,7 +65,12 @@ export async function removeAction(
60
65
  return
61
66
  }
62
67
 
63
- const client = await new TeamsClient().login({ token: cred.token, tokenExpiresAt: cred.tokenExpiresAt })
68
+ const client = await new TeamsClient().login({
69
+ token: cred.token,
70
+ tokenExpiresAt: cred.tokenExpiresAt,
71
+ accountType: cred.accountType,
72
+ region: cred.region,
73
+ })
64
74
  await client.removeReaction(teamId, channelId, messageId, emoji)
65
75
 
66
76
  console.log(
@@ -8,7 +8,7 @@ test('snapshot: command is defined', () => {
8
8
  })
9
9
 
10
10
  test('snapshot: command has correct description', () => {
11
- expect(snapshotCommand.description()).toContain('team state')
11
+ expect(snapshotCommand.description()).toContain('team overview')
12
12
  })
13
13
 
14
14
  test('snapshot: command has --channels-only option', () => {