agent-messenger 2.9.0 → 2.10.1

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 (118) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/dist/package.json +1 -1
  3. package/dist/src/platforms/teams/client.d.ts +9 -1
  4. package/dist/src/platforms/teams/client.d.ts.map +1 -1
  5. package/dist/src/platforms/teams/client.js +69 -18
  6. package/dist/src/platforms/teams/client.js.map +1 -1
  7. package/dist/src/platforms/teams/commands/auth.d.ts.map +1 -1
  8. package/dist/src/platforms/teams/commands/auth.js +7 -2
  9. package/dist/src/platforms/teams/commands/auth.js.map +1 -1
  10. package/dist/src/platforms/teams/commands/channel.d.ts.map +1 -1
  11. package/dist/src/platforms/teams/commands/channel.js +18 -3
  12. package/dist/src/platforms/teams/commands/channel.js.map +1 -1
  13. package/dist/src/platforms/teams/commands/file.d.ts.map +1 -1
  14. package/dist/src/platforms/teams/commands/file.js +18 -3
  15. package/dist/src/platforms/teams/commands/file.js.map +1 -1
  16. package/dist/src/platforms/teams/commands/message.d.ts.map +1 -1
  17. package/dist/src/platforms/teams/commands/message.js +24 -4
  18. package/dist/src/platforms/teams/commands/message.js.map +1 -1
  19. package/dist/src/platforms/teams/commands/reaction.d.ts.map +1 -1
  20. package/dist/src/platforms/teams/commands/reaction.js +12 -2
  21. package/dist/src/platforms/teams/commands/reaction.js.map +1 -1
  22. package/dist/src/platforms/teams/commands/snapshot.d.ts.map +1 -1
  23. package/dist/src/platforms/teams/commands/snapshot.js +6 -1
  24. package/dist/src/platforms/teams/commands/snapshot.js.map +1 -1
  25. package/dist/src/platforms/teams/commands/team.d.ts.map +1 -1
  26. package/dist/src/platforms/teams/commands/team.js +6 -1
  27. package/dist/src/platforms/teams/commands/team.js.map +1 -1
  28. package/dist/src/platforms/teams/commands/user.d.ts.map +1 -1
  29. package/dist/src/platforms/teams/commands/user.js +18 -3
  30. package/dist/src/platforms/teams/commands/user.js.map +1 -1
  31. package/dist/src/platforms/teams/commands/whoami.d.ts.map +1 -1
  32. package/dist/src/platforms/teams/commands/whoami.js +6 -1
  33. package/dist/src/platforms/teams/commands/whoami.js.map +1 -1
  34. package/dist/src/platforms/teams/credential-manager.d.ts +3 -1
  35. package/dist/src/platforms/teams/credential-manager.d.ts.map +1 -1
  36. package/dist/src/platforms/teams/credential-manager.js +6 -1
  37. package/dist/src/platforms/teams/credential-manager.js.map +1 -1
  38. package/dist/src/platforms/teams/ensure-auth.d.ts.map +1 -1
  39. package/dist/src/platforms/teams/ensure-auth.js +7 -2
  40. package/dist/src/platforms/teams/ensure-auth.js.map +1 -1
  41. package/dist/src/platforms/teams/token-extractor.d.ts +3 -1
  42. package/dist/src/platforms/teams/token-extractor.d.ts.map +1 -1
  43. package/dist/src/platforms/teams/token-extractor.js +73 -10
  44. package/dist/src/platforms/teams/token-extractor.js.map +1 -1
  45. package/dist/src/platforms/teams/types.d.ts +17 -0
  46. package/dist/src/platforms/teams/types.d.ts.map +1 -1
  47. package/dist/src/platforms/teams/types.js +2 -0
  48. package/dist/src/platforms/teams/types.js.map +1 -1
  49. package/dist/src/platforms/webex/client.d.ts +3 -0
  50. package/dist/src/platforms/webex/client.d.ts.map +1 -1
  51. package/dist/src/platforms/webex/client.js +58 -13
  52. package/dist/src/platforms/webex/client.js.map +1 -1
  53. package/dist/src/platforms/webex/commands/auth.d.ts.map +1 -1
  54. package/dist/src/platforms/webex/commands/auth.js +61 -10
  55. package/dist/src/platforms/webex/commands/auth.js.map +1 -1
  56. package/dist/src/platforms/webex/credential-manager.d.ts.map +1 -1
  57. package/dist/src/platforms/webex/credential-manager.js +18 -6
  58. package/dist/src/platforms/webex/credential-manager.js.map +1 -1
  59. package/dist/src/platforms/webex/encryption.d.ts.map +1 -1
  60. package/dist/src/platforms/webex/encryption.js +3 -1
  61. package/dist/src/platforms/webex/encryption.js.map +1 -1
  62. package/dist/src/platforms/webex/ensure-auth.d.ts.map +1 -1
  63. package/dist/src/platforms/webex/ensure-auth.js +10 -2
  64. package/dist/src/platforms/webex/ensure-auth.js.map +1 -1
  65. package/dist/src/platforms/webex/token-extractor.d.ts +1 -0
  66. package/dist/src/platforms/webex/token-extractor.d.ts.map +1 -1
  67. package/dist/src/platforms/webex/token-extractor.js +21 -4
  68. package/dist/src/platforms/webex/token-extractor.js.map +1 -1
  69. package/e2e/webex.e2e.test.ts +57 -0
  70. package/package.json +1 -1
  71. package/skills/agent-channeltalk/SKILL.md +1 -1
  72. package/skills/agent-channeltalkbot/SKILL.md +1 -1
  73. package/skills/agent-discord/SKILL.md +1 -1
  74. package/skills/agent-discordbot/SKILL.md +1 -1
  75. package/skills/agent-instagram/SKILL.md +1 -1
  76. package/skills/agent-kakaotalk/SKILL.md +1 -1
  77. package/skills/agent-line/SKILL.md +1 -1
  78. package/skills/agent-slack/SKILL.md +1 -1
  79. package/skills/agent-slackbot/SKILL.md +1 -1
  80. package/skills/agent-teams/SKILL.md +1 -1
  81. package/skills/agent-telegram/SKILL.md +1 -1
  82. package/skills/agent-webex/SKILL.md +1 -1
  83. package/skills/agent-wechatbot/SKILL.md +1 -1
  84. package/skills/agent-whatsapp/SKILL.md +1 -1
  85. package/skills/agent-whatsappbot/SKILL.md +1 -1
  86. package/src/platforms/teams/client.test.ts +34 -30
  87. package/src/platforms/teams/client.ts +92 -20
  88. package/src/platforms/teams/commands/auth.test.ts +6 -2
  89. package/src/platforms/teams/commands/auth.ts +7 -2
  90. package/src/platforms/teams/commands/channel.test.ts +6 -6
  91. package/src/platforms/teams/commands/channel.ts +18 -3
  92. package/src/platforms/teams/commands/file.ts +18 -3
  93. package/src/platforms/teams/commands/message.ts +24 -4
  94. package/src/platforms/teams/commands/reaction.ts +12 -2
  95. package/src/platforms/teams/commands/snapshot.ts +6 -1
  96. package/src/platforms/teams/commands/team.test.ts +2 -2
  97. package/src/platforms/teams/commands/team.ts +6 -1
  98. package/src/platforms/teams/commands/user.ts +18 -3
  99. package/src/platforms/teams/commands/whoami.ts +6 -1
  100. package/src/platforms/teams/credential-manager.test.ts +25 -0
  101. package/src/platforms/teams/credential-manager.ts +13 -3
  102. package/src/platforms/teams/ensure-auth.test.ts +6 -1
  103. package/src/platforms/teams/ensure-auth.ts +7 -2
  104. package/src/platforms/teams/token-extractor.test.ts +112 -98
  105. package/src/platforms/teams/token-extractor.ts +83 -12
  106. package/src/platforms/teams/types.test.ts +17 -0
  107. package/src/platforms/teams/types.ts +6 -0
  108. package/src/platforms/webex/client.test.ts +157 -13
  109. package/src/platforms/webex/client.ts +64 -15
  110. package/src/platforms/webex/commands/auth.test.ts +122 -1
  111. package/src/platforms/webex/commands/auth.ts +72 -17
  112. package/src/platforms/webex/credential-manager.test.ts +63 -0
  113. package/src/platforms/webex/credential-manager.ts +22 -8
  114. package/src/platforms/webex/encryption.test.ts +54 -0
  115. package/src/platforms/webex/encryption.ts +3 -1
  116. package/src/platforms/webex/ensure-auth.ts +10 -2
  117. package/src/platforms/webex/token-extractor.test.ts +32 -3
  118. package/src/platforms/webex/token-extractor.ts +26 -5
@@ -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(
@@ -34,7 +34,12 @@ export async function snapshotAction(options: {
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
 
39
44
  const snapshot: Record<string, unknown> = {}
40
45
 
@@ -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,
@@ -1,5 +1,6 @@
1
1
  import { beforeEach, describe, expect, spyOn, test } from 'bun:test'
2
- import { homedir } from 'node:os'
2
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
3
+ import { homedir, tmpdir } from 'node:os'
3
4
  import { join } from 'node:path'
4
5
 
5
6
  import { TeamsTokenExtractor } from './token-extractor'
@@ -16,58 +17,25 @@ describe('TeamsTokenExtractor', () => {
16
17
  const darwinExtractor = new TeamsTokenExtractor('darwin')
17
18
  const paths = darwinExtractor.getDesktopCookiesPaths()
18
19
 
20
+ const darwinEbWebView = join(
21
+ homedir(),
22
+ 'Library',
23
+ 'Containers',
24
+ 'com.microsoft.teams2',
25
+ 'Data',
26
+ 'Library',
27
+ 'Application Support',
28
+ 'Microsoft',
29
+ 'MSTeams',
30
+ 'EBWebView',
31
+ )
19
32
  expect(paths).toEqual([
20
- {
21
- path: join(
22
- homedir(),
23
- 'Library',
24
- 'Containers',
25
- 'com.microsoft.teams2',
26
- 'Data',
27
- 'Library',
28
- 'Application Support',
29
- 'Microsoft',
30
- 'MSTeams',
31
- 'EBWebView',
32
- 'WV2Profile_tfw',
33
- 'Cookies',
34
- ),
35
- accountType: 'work',
36
- },
37
- {
38
- path: join(
39
- homedir(),
40
- 'Library',
41
- 'Containers',
42
- 'com.microsoft.teams2',
43
- 'Data',
44
- 'Library',
45
- 'Application Support',
46
- 'Microsoft',
47
- 'MSTeams',
48
- 'EBWebView',
49
- 'WV2Profile_tfl',
50
- 'Cookies',
51
- ),
52
- accountType: 'personal',
53
- },
54
- {
55
- path: join(
56
- homedir(),
57
- 'Library',
58
- 'Containers',
59
- 'com.microsoft.teams2',
60
- 'Data',
61
- 'Library',
62
- 'Application Support',
63
- 'Microsoft',
64
- 'MSTeams',
65
- 'EBWebView',
66
- 'Default',
67
- 'Cookies',
68
- ),
69
- accountType: 'work',
70
- },
33
+ { path: join(darwinEbWebView, 'WV2Profile_tfw', 'Cookies'), accountType: 'work' },
34
+ { path: join(darwinEbWebView, 'WV2Profile_tfw', 'Network', 'Cookies'), accountType: 'work' },
35
+ { path: join(darwinEbWebView, 'WV2Profile_tfl', 'Cookies'), accountType: 'personal' },
36
+ { path: join(darwinEbWebView, 'WV2Profile_tfl', 'Network', 'Cookies'), accountType: 'personal' },
37
+ { path: join(darwinEbWebView, 'Default', 'Cookies'), accountType: 'work' },
38
+ { path: join(darwinEbWebView, 'Default', 'Network', 'Cookies'), accountType: 'work' },
71
39
  {
72
40
  path: join(homedir(), 'Library', 'Application Support', 'Microsoft', 'Teams', 'Cookies'),
73
41
  accountType: 'work',
@@ -93,53 +61,23 @@ describe('TeamsTokenExtractor', () => {
93
61
 
94
62
  const localAppData = process.env.LOCALAPPDATA || join(homedir(), 'AppData', 'Local')
95
63
  const appdata = process.env.APPDATA || join(homedir(), 'AppData', 'Roaming')
64
+ const winEbWebView = join(
65
+ localAppData,
66
+ 'Packages',
67
+ 'MSTeams_8wekyb3d8bbwe',
68
+ 'LocalCache',
69
+ 'Microsoft',
70
+ 'MSTeams',
71
+ 'EBWebView',
72
+ )
96
73
  expect(paths).toEqual([
97
- {
98
- path: join(
99
- localAppData,
100
- 'Packages',
101
- 'MSTeams_8wekyb3d8bbwe',
102
- 'LocalCache',
103
- 'Microsoft',
104
- 'MSTeams',
105
- 'EBWebView',
106
- 'WV2Profile_tfw',
107
- 'Cookies',
108
- ),
109
- accountType: 'work',
110
- },
111
- {
112
- path: join(
113
- localAppData,
114
- 'Packages',
115
- 'MSTeams_8wekyb3d8bbwe',
116
- 'LocalCache',
117
- 'Microsoft',
118
- 'MSTeams',
119
- 'EBWebView',
120
- 'WV2Profile_tfl',
121
- 'Cookies',
122
- ),
123
- accountType: 'personal',
124
- },
125
- {
126
- path: join(
127
- localAppData,
128
- 'Packages',
129
- 'MSTeams_8wekyb3d8bbwe',
130
- 'LocalCache',
131
- 'Microsoft',
132
- 'MSTeams',
133
- 'EBWebView',
134
- 'Default',
135
- 'Cookies',
136
- ),
137
- accountType: 'work',
138
- },
139
- {
140
- path: join(appdata, 'Microsoft', 'Teams', 'Cookies'),
141
- accountType: 'work',
142
- },
74
+ { path: join(winEbWebView, 'WV2Profile_tfw', 'Cookies'), accountType: 'work' },
75
+ { path: join(winEbWebView, 'WV2Profile_tfw', 'Network', 'Cookies'), accountType: 'work' },
76
+ { path: join(winEbWebView, 'WV2Profile_tfl', 'Cookies'), accountType: 'personal' },
77
+ { path: join(winEbWebView, 'WV2Profile_tfl', 'Network', 'Cookies'), accountType: 'personal' },
78
+ { path: join(winEbWebView, 'Default', 'Cookies'), accountType: 'work' },
79
+ { path: join(winEbWebView, 'Default', 'Network', 'Cookies'), accountType: 'work' },
80
+ { path: join(appdata, 'Microsoft', 'Teams', 'Cookies'), accountType: 'work' },
143
81
  ])
144
82
  })
145
83
 
@@ -400,6 +338,82 @@ describe('TeamsTokenExtractor', () => {
400
338
  })
401
339
  })
402
340
 
341
+ describe('extractFromCookiesDB (Network/Cookies fallback)', () => {
342
+ const mockToken = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.signature_here'
343
+ let workDir: string
344
+
345
+ beforeEach(() => {
346
+ workDir = mkdtempSync(join(tmpdir(), 'teams-extractor-test-'))
347
+ })
348
+
349
+ const cleanup = () => rmSync(workDir, { recursive: true, force: true })
350
+
351
+ // Regression for #156: if only Network/Cookies exists, missing sibling must not poison accountType.
352
+ test('falls through to Network/Cookies when Cookies is missing', async () => {
353
+ // given: only Network/Cookies exists on disk for WV2Profile_tfl
354
+ const profileDir = join(workDir, 'WV2Profile_tfl')
355
+ const networkDir = join(profileDir, 'Network')
356
+ mkdirSync(networkDir, { recursive: true })
357
+ const cookiesPath = join(profileDir, 'Cookies')
358
+ const networkCookiesPath = join(networkDir, 'Cookies')
359
+ writeFileSync(networkCookiesPath, '')
360
+
361
+ const winExtractor = new TeamsTokenExtractor('win32')
362
+ const getPathsSpy = spyOn(winExtractor, 'getTeamsCookiesPaths').mockReturnValue([
363
+ { path: cookiesPath, accountType: 'personal' },
364
+ { path: networkCookiesPath, accountType: 'personal' },
365
+ ])
366
+ const tried: string[] = []
367
+ const copyAndExtractSpy = spyOn(winExtractor as any, 'copyAndExtract').mockImplementation(async (...args) => {
368
+ const path = args[0] as string
369
+ tried.push(path)
370
+ return mockToken
371
+ })
372
+
373
+ // when
374
+ const results = await (winExtractor as any).extractFromCookiesDB()
375
+
376
+ // then: the Cookies path was skipped (never passed to copyAndExtract),
377
+ // the Network/Cookies sibling was tried, and the token was returned.
378
+ expect(tried).toEqual([networkCookiesPath])
379
+ expect(results).toEqual([{ token: mockToken, accountType: 'personal' }])
380
+
381
+ getPathsSpy.mockRestore()
382
+ copyAndExtractSpy.mockRestore()
383
+ cleanup()
384
+ })
385
+
386
+ test('a missing path does not mark the account type as seen', async () => {
387
+ // given: work account has Cookies missing but Network/Cookies present
388
+ const workProfile = join(workDir, 'WV2Profile_tfw')
389
+ const workNetworkDir = join(workProfile, 'Network')
390
+ mkdirSync(workNetworkDir, { recursive: true })
391
+ const workCookies = join(workProfile, 'Cookies')
392
+ const workNetworkCookies = join(workNetworkDir, 'Cookies')
393
+ writeFileSync(workNetworkCookies, '')
394
+
395
+ const winExtractor = new TeamsTokenExtractor('win32')
396
+ const getPathsSpy = spyOn(winExtractor, 'getTeamsCookiesPaths').mockReturnValue([
397
+ { path: workCookies, accountType: 'work' },
398
+ { path: workNetworkCookies, accountType: 'work' },
399
+ ])
400
+ const copyAndExtractSpy = spyOn(winExtractor as any, 'copyAndExtract').mockResolvedValue(mockToken)
401
+
402
+ // when
403
+ const results = await (winExtractor as any).extractFromCookiesDB()
404
+
405
+ // then: missing first path did not block the sibling; work token extracted
406
+ expect(results).toHaveLength(1)
407
+ expect(results[0].accountType).toBe('work')
408
+ expect(copyAndExtractSpy).toHaveBeenCalledTimes(1)
409
+ expect(copyAndExtractSpy).toHaveBeenCalledWith(workNetworkCookies)
410
+
411
+ getPathsSpy.mockRestore()
412
+ copyAndExtractSpy.mockRestore()
413
+ cleanup()
414
+ })
415
+ })
416
+
403
417
  describe('copyAndExtract', () => {
404
418
  test('attempts to copy database to temp location', async () => {
405
419
  const darwinExtractor = new TeamsTokenExtractor('darwin')