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
@@ -11,6 +11,7 @@ import type { TeamsChannel } from '../types'
11
11
  export async function snapshotAction(options: {
12
12
  channelsOnly?: boolean
13
13
  usersOnly?: boolean
14
+ full?: boolean
14
15
  limit?: number
15
16
  teamId?: string
16
17
  pretty?: boolean
@@ -33,8 +34,12 @@ export async function snapshotAction(options: {
33
34
  process.exit(1)
34
35
  }
35
36
 
36
- const client = await new TeamsClient().login({ token: cred.token, tokenExpiresAt: cred.tokenExpiresAt })
37
- const messageLimit = options.limit || 20
37
+ const client = await new TeamsClient().login({
38
+ token: cred.token,
39
+ tokenExpiresAt: cred.tokenExpiresAt,
40
+ accountType: cred.accountType,
41
+ region: cred.region,
42
+ })
38
43
 
39
44
  const snapshot: Record<string, unknown> = {}
40
45
 
@@ -45,47 +50,60 @@ export async function snapshotAction(options: {
45
50
  description: team.description,
46
51
  }
47
52
 
48
- if (!options.usersOnly) {
49
- const channels = await client.listChannels(teamId)
53
+ const isFull = options.full || options.channelsOnly || options.usersOnly
54
+ if (isFull) {
55
+ const messageLimit = options.limit || 20
50
56
 
51
- snapshot.channels = channels.map((ch) => ({
52
- id: ch.id,
53
- name: ch.name,
54
- type: ch.type,
55
- }))
57
+ if (!options.usersOnly) {
58
+ const channels = await client.listChannels(teamId)
56
59
 
57
- if (!options.channelsOnly) {
58
- const channelMessages = await parallelMap(
59
- channels,
60
- async (channel: TeamsChannel) => {
61
- const messages = await client.getMessages(teamId, channel.id, messageLimit)
62
- return messages.map((msg) => ({
63
- ...msg,
64
- channel_name: channel.name,
65
- }))
66
- },
67
- 5,
68
- )
69
-
70
- snapshot.recent_messages = channelMessages.flat().map((msg) => ({
71
- channel_id: msg.channel_id,
72
- channel_name: msg.channel_name,
73
- id: msg.id,
74
- author: msg.author.displayName,
75
- content: msg.content,
76
- timestamp: msg.timestamp,
60
+ snapshot.channels = channels.map((ch) => ({
61
+ id: ch.id,
62
+ name: ch.name,
63
+ type: ch.type,
77
64
  }))
65
+
66
+ if (!options.channelsOnly) {
67
+ const channelMessages = await parallelMap(
68
+ channels,
69
+ async (channel: TeamsChannel) => {
70
+ const messages = await client.getMessages(teamId, channel.id, messageLimit)
71
+ return messages.map((msg) => ({
72
+ ...msg,
73
+ channel_name: channel.name,
74
+ }))
75
+ },
76
+ 5,
77
+ )
78
+
79
+ snapshot.recent_messages = channelMessages.flat().map((msg) => ({
80
+ channel_id: msg.channel_id,
81
+ channel_name: msg.channel_name,
82
+ id: msg.id,
83
+ author: msg.author.displayName,
84
+ content: msg.content,
85
+ timestamp: msg.timestamp,
86
+ }))
87
+ }
78
88
  }
79
- }
80
89
 
81
- if (!options.channelsOnly) {
82
- const users = await client.listUsers(teamId)
90
+ if (!options.channelsOnly) {
91
+ const users = await client.listUsers(teamId)
92
+
93
+ snapshot.members = users.map((u) => ({
94
+ id: u.id,
95
+ displayName: u.displayName,
96
+ email: u.email || null,
97
+ }))
98
+ }
99
+ } else {
100
+ if (!options.usersOnly) {
101
+ const channels = await client.listChannels(teamId)
102
+ snapshot.channels = channels.map((ch) => ({ id: ch.id, name: ch.name }))
103
+ }
83
104
 
84
- snapshot.members = users.map((u) => ({
85
- id: u.id,
86
- displayName: u.displayName,
87
- email: u.email || null,
88
- }))
105
+ snapshot.hint =
106
+ "Use 'message list <channel>' for messages, 'channel info <channel>' for channel details, 'user list' for members."
89
107
  }
90
108
 
91
109
  console.log(formatOutput(snapshot, options.pretty))
@@ -95,16 +113,18 @@ export async function snapshotAction(options: {
95
113
  }
96
114
 
97
115
  export const snapshotCommand = new Command('snapshot')
98
- .description('Get comprehensive team state for AI agents')
116
+ .description('Get team overview for AI agents (brief by default, use --full for comprehensive data)')
117
+ .option('--full', 'Include messages and members (verbose)')
99
118
  .option('--channels-only', 'Include only channels (exclude messages and members)')
100
119
  .option('--users-only', 'Include only members (exclude channels and messages)')
101
- .option('--limit <n>', 'Number of recent messages per channel (default: 20)', '20')
120
+ .option('--limit <n>', 'Number of recent messages per channel with --full (default: 20)', '20')
102
121
  .option('--team-id <id>', 'Team ID (defaults to current team)')
103
122
  .option('--pretty', 'Pretty print JSON output')
104
123
  .action(async (options) => {
105
124
  await snapshotAction({
106
125
  channelsOnly: options.channelsOnly,
107
126
  usersOnly: options.usersOnly,
127
+ full: options.full,
108
128
  limit: parseInt(options.limit, 10),
109
129
  teamId: options.teamId,
110
130
  pretty: options.pretty,
@@ -90,7 +90,7 @@ test('list: marks current team', async () => {
90
90
 
91
91
  test('info: returns team details', async () => {
92
92
  // given: teams client with team data
93
- const client = await new TeamsClient().login({ token: 'test-token' })
93
+ const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
94
94
  const team = await client.getTeam('team-1')
95
95
 
96
96
  // when: getting team info
@@ -104,7 +104,7 @@ test('info: returns team details', async () => {
104
104
 
105
105
  test('info: throws error for non-existent team', async () => {
106
106
  // given: teams client
107
- const client = await new TeamsClient().login({ token: 'test-token' })
107
+ const client = await new TeamsClient().login({ token: 'test-token', region: 'emea' })
108
108
 
109
109
  // when: getting non-existent team
110
110
  // then: error is thrown
@@ -34,7 +34,12 @@ export async function infoAction(teamId: string, options: { pretty?: boolean }):
34
34
  process.exit(1)
35
35
  }
36
36
 
37
- const client = await new TeamsClient().login({ token: cred.token, tokenExpiresAt: cred.tokenExpiresAt })
37
+ const client = await new TeamsClient().login({
38
+ token: cred.token,
39
+ tokenExpiresAt: cred.tokenExpiresAt,
40
+ accountType: cred.accountType,
41
+ region: cred.region,
42
+ })
38
43
  const team = await client.getTeam(teamId)
39
44
 
40
45
  const output = {
@@ -16,7 +16,12 @@ async function listAction(teamId: string, options: { pretty?: boolean }): Promis
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 users = await client.listUsers(teamId)
21
26
 
22
27
  const output = users.map((user) => ({
@@ -42,7 +47,12 @@ async function infoAction(userId: string, options: { pretty?: boolean }): Promis
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 user = await client.getUser(userId)
47
57
 
48
58
  const output = {
@@ -68,7 +78,12 @@ async function meAction(options: { pretty?: boolean }): Promise<void> {
68
78
  process.exit(1)
69
79
  }
70
80
 
71
- const client = await new TeamsClient().login({ token: cred.token, tokenExpiresAt: cred.tokenExpiresAt })
81
+ const client = await new TeamsClient().login({
82
+ token: cred.token,
83
+ tokenExpiresAt: cred.tokenExpiresAt,
84
+ accountType: cred.accountType,
85
+ region: cred.region,
86
+ })
72
87
  const user = await client.testAuth()
73
88
 
74
89
  const output = {
@@ -16,7 +16,12 @@ export async function whoamiAction(options: { pretty?: boolean }): Promise<void>
16
16
  return 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 user = await client.testAuth()
21
26
 
22
27
  const output = {
@@ -88,6 +88,31 @@ describe('TeamsCredentialManager', () => {
88
88
  expect(team).toBeNull()
89
89
  })
90
90
 
91
+ test('getTokenWithExpiry includes region', async () => {
92
+ const manager = setup()
93
+ await manager.saveConfig({
94
+ current_account: 'work',
95
+ accounts: {
96
+ work: {
97
+ token: 'test-token',
98
+ token_expires_at: '2025-12-31T23:59:59Z',
99
+ region: 'emea',
100
+ account_type: 'work',
101
+ current_team: null,
102
+ teams: {},
103
+ },
104
+ },
105
+ })
106
+
107
+ const token = await manager.getTokenWithExpiry()
108
+ expect(token).toEqual({
109
+ token: 'test-token',
110
+ tokenExpiresAt: '2025-12-31T23:59:59Z',
111
+ accountType: 'work',
112
+ region: 'emea',
113
+ })
114
+ })
115
+
91
116
  test('getCurrentTeam returns null when current_team is set but team not in teams record', async () => {
92
117
  const manager = setup()
93
118
  await manager.setToken('test-token', 'work')
@@ -3,7 +3,7 @@ import { mkdir, readFile, rm, writeFile } from 'node:fs/promises'
3
3
  import { homedir } from 'node:os'
4
4
  import { join } from 'node:path'
5
5
 
6
- import type { TeamsAccount, TeamsAccountType, TeamsConfig, TeamsConfigLegacy } from './types'
6
+ import type { TeamsAccount, TeamsAccountType, TeamsConfig, TeamsConfigLegacy, TeamsRegion } from './types'
7
7
 
8
8
  export class TeamsCredentialManager {
9
9
  static accountOverride?: TeamsAccountType
@@ -78,12 +78,22 @@ export class TeamsCredentialManager {
78
78
  return this.resolveCurrentAccount(config)?.token ?? null
79
79
  }
80
80
 
81
- async getTokenWithExpiry(): Promise<{ token: string; tokenExpiresAt?: string } | null> {
81
+ async getTokenWithExpiry(): Promise<{
82
+ token: string
83
+ tokenExpiresAt?: string
84
+ accountType?: TeamsAccountType
85
+ region?: TeamsRegion
86
+ } | null> {
82
87
  const config = await this.loadConfig()
83
88
  if (!config) return null
84
89
  const account = this.resolveCurrentAccount(config)
85
90
  if (!account?.token) return null
86
- return { token: account.token, tokenExpiresAt: account.token_expires_at }
91
+ return {
92
+ token: account.token,
93
+ tokenExpiresAt: account.token_expires_at,
94
+ accountType: account.account_type,
95
+ region: account.region,
96
+ }
87
97
  }
88
98
 
89
99
  async setToken(token: string, accountType: TeamsAccountType, expiresAt?: string): Promise<void> {
@@ -10,6 +10,7 @@ let extractSpy: ReturnType<typeof spyOn>
10
10
  let testAuthSpy: ReturnType<typeof spyOn>
11
11
  let listTeamsSpy: ReturnType<typeof spyOn>
12
12
  let saveConfigSpy: ReturnType<typeof spyOn>
13
+ let getRegionSpy: ReturnType<typeof spyOn>
13
14
 
14
15
  beforeEach(() => {
15
16
  loadConfigSpy = spyOn(TeamsCredentialManager.prototype, 'loadConfig').mockResolvedValue(null)
@@ -28,6 +29,8 @@ beforeEach(() => {
28
29
  { id: 'team-2', name: 'Team Two' },
29
30
  ])
30
31
 
32
+ getRegionSpy = spyOn(TeamsClient.prototype, 'getRegion').mockReturnValue('emea')
33
+
31
34
  saveConfigSpy = spyOn(TeamsCredentialManager.prototype, 'saveConfig').mockResolvedValue(undefined)
32
35
  })
33
36
 
@@ -37,6 +40,7 @@ afterEach(() => {
37
40
  testAuthSpy?.mockRestore()
38
41
  listTeamsSpy?.mockRestore()
39
42
  saveConfigSpy?.mockRestore()
43
+ getRegionSpy?.mockRestore()
40
44
  })
41
45
 
42
46
  describe('ensureTeamsAuth', () => {
@@ -78,6 +82,7 @@ describe('ensureTeamsAuth', () => {
78
82
  accounts: expect.objectContaining({
79
83
  work: expect.objectContaining({
80
84
  token: 'test-teams-token',
85
+ region: 'emea',
81
86
  current_team: 'team-1',
82
87
  teams: {
83
88
  'team-1': { team_id: 'team-1', team_name: 'Team One' },
@@ -215,7 +220,7 @@ describe('ensureTeamsAuth', () => {
215
220
  current_account: 'work',
216
221
  accounts: expect.objectContaining({
217
222
  work: expect.objectContaining({ token: 'work-token', current_team: 'team-w' }),
218
- personal: expect.objectContaining({ token: 'personal-token', current_team: 'team-p' }),
223
+ personal: expect.objectContaining({ token: 'personal-token', region: 'emea', current_team: 'team-p' }),
219
224
  }),
220
225
  }),
221
226
  )
@@ -23,11 +23,15 @@ export async function ensureTeamsAuth(): Promise<void> {
23
23
 
24
24
  for (const { token, accountType } of extracted) {
25
25
  try {
26
- const client = await new TeamsClient().login({ token })
26
+ const client = await new TeamsClient().login({
27
+ token,
28
+ accountType,
29
+ region: config?.accounts[accountType]?.region,
30
+ })
27
31
  await client.testAuth()
28
32
 
29
33
  const teams = await client.listTeams()
30
- if (teams.length === 0) continue
34
+ if (accountType !== 'personal' && teams.length === 0) continue
31
35
 
32
36
  const teamMap: Record<string, { team_id: string; team_name: string }> = {}
33
37
  for (const team of teams) {
@@ -38,6 +42,7 @@ export async function ensureTeamsAuth(): Promise<void> {
38
42
  const account: TeamsAccount = {
39
43
  token,
40
44
  token_expires_at: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
45
+ region: client.getRegion(),
41
46
  account_type: accountType,
42
47
  user_name: existing?.user_name,
43
48
  current_team: existing?.current_team ?? teams[0].id,
@@ -55,9 +55,11 @@ export class TeamsTokenExtractor {
55
55
  private platform: NodeJS.Platform
56
56
  private decryptor: ChromiumCookieDecryptor
57
57
  private cookieReader: ChromiumCookieReader
58
+ private debugLog: ((message: string) => void) | null
58
59
 
59
- constructor(platform?: NodeJS.Platform, keyCache?: DerivedKeyCache) {
60
+ constructor(platform?: NodeJS.Platform, keyCache?: DerivedKeyCache, debugLog?: (message: string) => void) {
60
61
  this.platform = platform ?? process.platform
62
+ this.debugLog = debugLog ?? null
61
63
 
62
64
  const resolvedKeyCache = keyCache ?? new DerivedKeyCache()
63
65
  this.decryptor = new ChromiumCookieDecryptor({
@@ -69,6 +71,10 @@ export class TeamsTokenExtractor {
69
71
  this.cookieReader = new ChromiumCookieReader()
70
72
  }
71
73
 
74
+ private debug(message: string): void {
75
+ this.debugLog?.(message)
76
+ }
77
+
72
78
  getDesktopCookiesPaths(): TeamsCookiePath[] {
73
79
  switch (this.platform) {
74
80
  case 'darwin': {
@@ -197,17 +203,38 @@ export class TeamsTokenExtractor {
197
203
  private async extractFromCookiesDB(): Promise<ExtractedTeamsToken[]> {
198
204
  const results: ExtractedTeamsToken[] = []
199
205
  const seenAccountTypes = new Set<TeamsAccountType>()
206
+ const allPaths = this.getTeamsCookiesPaths()
207
+
208
+ this.debug(`Scanning ${allPaths.length} candidate cookie path(s)`)
209
+
210
+ for (const { path: dbPath, accountType } of allPaths) {
211
+ if (!dbPath) continue
212
+
213
+ if (!existsSync(dbPath)) {
214
+ this.debug(` [skip] ${dbPath} (not found)`)
215
+ continue
216
+ }
217
+
218
+ if (seenAccountTypes.has(accountType)) {
219
+ this.debug(` [skip] ${dbPath} (already have ${accountType} account)`)
220
+ continue
221
+ }
200
222
 
201
- for (const { path: dbPath, accountType } of this.getTeamsCookiesPaths()) {
202
- if (!dbPath || !existsSync(dbPath) || seenAccountTypes.has(accountType)) continue
223
+ this.debug(` [try] ${dbPath} (${accountType})`)
203
224
 
204
225
  const token = await this.copyAndExtract(dbPath)
205
226
  if (token && this.isValidSkypeToken(token)) {
227
+ this.debug(` [ok] Extracted valid token (${token.length} chars)`)
206
228
  results.push({ token, accountType })
207
229
  seenAccountTypes.add(accountType)
230
+ } else if (token) {
231
+ this.debug(` [fail] Token too short (${token.length} chars, need >=50)`)
232
+ } else {
233
+ this.debug(` [fail] No token extracted`)
208
234
  }
209
235
  }
210
236
 
237
+ this.debug(`Extraction complete: ${results.length} token(s) found`)
211
238
  return results
212
239
  }
213
240
 
@@ -216,11 +243,26 @@ export class TeamsTokenExtractor {
216
243
 
217
244
  try {
218
245
  tempPath = this.copyDatabaseToTemp(dbPath, dbPath)
219
- const localStatePath =
220
- this.platform === 'win32' ? (findLocalStatePath(dbPath) ?? this.getLocalStatePath()) : undefined
246
+
247
+ let localStatePath: string | undefined
248
+ if (this.platform === 'win32') {
249
+ localStatePath = findLocalStatePath(dbPath) ?? undefined
250
+ if (localStatePath) {
251
+ this.debug(` Local State (from cookie path): ${localStatePath}`)
252
+ } else {
253
+ localStatePath = this.getLocalStatePath()
254
+ if (existsSync(localStatePath)) {
255
+ this.debug(` Local State (fallback): ${localStatePath}`)
256
+ } else {
257
+ this.debug(` Local State not found (tried fallback: ${localStatePath})`)
258
+ localStatePath = undefined
259
+ }
260
+ }
261
+ }
221
262
 
222
263
  return await this.extractFromSQLite(tempPath, localStatePath)
223
- } catch {
264
+ } catch (error) {
265
+ this.debug(` Copy/extract error: ${(error as Error).message}`)
224
266
  return null
225
267
  } finally {
226
268
  this.cleanupTempFile(tempPath)
@@ -231,27 +273,50 @@ export class TeamsTokenExtractor {
231
273
  try {
232
274
  for (const hostPattern of TEAMS_HOST_PATTERNS) {
233
275
  const sql = `
234
- SELECT encrypted_value
276
+ SELECT value, encrypted_value
235
277
  FROM cookies
236
278
  WHERE name = '${SKYPETOKEN_COOKIE_NAME}'
237
279
  AND host_key LIKE '%${hostPattern}%'
238
280
  LIMIT 1
239
281
  `
240
282
 
241
- type CookieRow = { encrypted_value?: Uint8Array | Buffer } | null
283
+ type CookieRow = { value?: string; encrypted_value?: Uint8Array | Buffer } | null
242
284
 
243
285
  const row = await this.cookieReader.queryFirst<CookieRow>(dbPath, sql)
244
- if (!row?.encrypted_value) continue
286
+ if (!row) continue
287
+
288
+ if (row.value && row.value.length >= 50) {
289
+ this.debug(` Found plaintext cookie for ${hostPattern} (${row.value.length} chars)`)
290
+ return row.value
291
+ }
292
+
293
+ if (!row.encrypted_value || row.encrypted_value.length === 0) {
294
+ this.debug(` No cookie data for ${hostPattern}`)
295
+ continue
296
+ }
297
+
298
+ const encBuf = Buffer.from(row.encrypted_value)
299
+ const isEncrypted = this.isEncryptedValue(encBuf)
300
+ this.debug(
301
+ ` Found cookie for ${hostPattern}: ${encBuf.length} bytes, encrypted=${isEncrypted}, prefix=${encBuf.subarray(0, 3).toString('utf8')}`,
302
+ )
245
303
 
246
- const decryptedBuf = this.decryptor.decryptCookieRaw(Buffer.from(row.encrypted_value), localStatePath)
247
- if (!decryptedBuf) continue
304
+ const decryptedBuf = this.decryptor.decryptCookieRaw(encBuf, localStatePath)
305
+ if (!decryptedBuf) {
306
+ this.debug(` Decryption failed`)
307
+ continue
308
+ }
248
309
 
310
+ this.debug(` Decrypted: ${decryptedBuf.length} bytes`)
249
311
  const token = this.postProcessDecrypted(decryptedBuf)
250
312
  if (this.isValidSkypeToken(token)) return token
313
+
314
+ this.debug(` Post-process result not a valid token (${token.length} chars)`)
251
315
  }
252
316
 
253
317
  return null
254
- } catch {
318
+ } catch (error) {
319
+ this.debug(` SQLite query error: ${(error as Error).message}`)
255
320
  return null
256
321
  }
257
322
  }
@@ -203,6 +203,7 @@ test('TeamsConfigSchema validates config with multiple accounts', () => {
203
203
  work: {
204
204
  token: 'work_token',
205
205
  token_expires_at: '2024-01-01T00:00:00.000Z',
206
+ region: 'emea',
206
207
  account_type: 'work',
207
208
  user_name: 'Work User',
208
209
  current_team: '19:abc123@thread.tacv2',
@@ -224,6 +225,22 @@ test('TeamsConfigSchema validates config with multiple accounts', () => {
224
225
  expect(result.success).toBe(true)
225
226
  })
226
227
 
228
+ test('TeamsConfigSchema rejects invalid region', () => {
229
+ const result = TeamsConfigSchema.safeParse({
230
+ current_account: 'work',
231
+ accounts: {
232
+ work: {
233
+ token: 'token_value',
234
+ region: 'invalid',
235
+ account_type: 'work',
236
+ current_team: null,
237
+ teams: {},
238
+ },
239
+ },
240
+ })
241
+ expect(result.success).toBe(false)
242
+ })
243
+
227
244
  test('TeamsConfigSchema rejects missing required fields', () => {
228
245
  const result = TeamsConfigSchema.safeParse({
229
246
  current_account: null,
@@ -55,9 +55,12 @@ export interface TeamsCredentials {
55
55
 
56
56
  export type TeamsAccountType = 'work' | 'personal'
57
57
 
58
+ export type TeamsRegion = 'amer' | 'emea' | 'apac'
59
+
58
60
  export interface TeamsAccount {
59
61
  token: string
60
62
  token_expires_at?: string
63
+ region?: TeamsRegion
61
64
  account_type: TeamsAccountType
62
65
  user_name?: string
63
66
  current_team: string | null
@@ -141,9 +144,12 @@ export const TeamsCredentialsSchema = z.object({
141
144
 
142
145
  export const TeamsAccountTypeSchema = z.enum(['work', 'personal'])
143
146
 
147
+ export const TeamsRegionSchema = z.enum(['amer', 'emea', 'apac'])
148
+
144
149
  export const TeamsAccountSchema = z.object({
145
150
  token: z.string(),
146
151
  token_expires_at: z.string().optional(),
152
+ region: TeamsRegionSchema.optional(),
147
153
  account_type: TeamsAccountTypeSchema,
148
154
  user_name: z.string().optional(),
149
155
  current_team: z.string().nullable(),