agent-messenger 1.4.0 → 1.6.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 (111) hide show
  1. package/.claude-plugin/README.md +38 -14
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.github/workflows/ci.yml +3 -0
  4. package/CONTRIBUTING.md +24 -1
  5. package/README.md +12 -8
  6. package/dist/package.json +1 -1
  7. package/dist/src/platforms/discord/cli.d.ts.map +1 -1
  8. package/dist/src/platforms/discord/cli.js +8 -1
  9. package/dist/src/platforms/discord/cli.js.map +1 -1
  10. package/dist/src/platforms/discord/commands/file.d.ts.map +1 -1
  11. package/dist/src/platforms/discord/commands/file.js +13 -7
  12. package/dist/src/platforms/discord/commands/file.js.map +1 -1
  13. package/dist/src/platforms/discord/commands/friend.d.ts.map +1 -1
  14. package/dist/src/platforms/discord/commands/friend.js +30 -30
  15. package/dist/src/platforms/discord/commands/friend.js.map +1 -1
  16. package/dist/src/platforms/discord/commands/index.d.ts +7 -0
  17. package/dist/src/platforms/discord/commands/index.d.ts.map +1 -1
  18. package/dist/src/platforms/discord/commands/index.js +7 -0
  19. package/dist/src/platforms/discord/commands/index.js.map +1 -1
  20. package/dist/src/platforms/discord/commands/snapshot.d.ts.map +1 -1
  21. package/dist/src/platforms/discord/commands/snapshot.js +1 -2
  22. package/dist/src/platforms/discord/commands/snapshot.js.map +1 -1
  23. package/dist/src/platforms/slack/commands/file.d.ts.map +1 -1
  24. package/dist/src/platforms/slack/commands/file.js +10 -4
  25. package/dist/src/platforms/slack/commands/file.js.map +1 -1
  26. package/dist/src/platforms/slack/commands/sections.d.ts.map +1 -1
  27. package/dist/src/platforms/slack/commands/sections.js +5 -6
  28. package/dist/src/platforms/slack/commands/sections.js.map +1 -1
  29. package/dist/src/platforms/slack/commands/snapshot.d.ts.map +1 -1
  30. package/dist/src/platforms/slack/commands/snapshot.js +1 -2
  31. package/dist/src/platforms/slack/commands/snapshot.js.map +1 -1
  32. package/dist/src/platforms/slack/commands/user.js +8 -8
  33. package/dist/src/platforms/slackbot/commands/reaction.js +2 -2
  34. package/dist/src/platforms/slackbot/commands/reaction.js.map +1 -1
  35. package/dist/src/platforms/teams/cli.d.ts.map +1 -1
  36. package/dist/src/platforms/teams/cli.js +7 -1
  37. package/dist/src/platforms/teams/cli.js.map +1 -1
  38. package/dist/src/platforms/teams/commands/auth.d.ts +3 -0
  39. package/dist/src/platforms/teams/commands/auth.d.ts.map +1 -1
  40. package/dist/src/platforms/teams/commands/auth.js +208 -107
  41. package/dist/src/platforms/teams/commands/auth.js.map +1 -1
  42. package/dist/src/platforms/teams/commands/channel.js +9 -9
  43. package/dist/src/platforms/teams/commands/channel.js.map +1 -1
  44. package/dist/src/platforms/teams/commands/file.js +21 -21
  45. package/dist/src/platforms/teams/commands/file.js.map +1 -1
  46. package/dist/src/platforms/teams/commands/message.js +12 -12
  47. package/dist/src/platforms/teams/commands/message.js.map +1 -1
  48. package/dist/src/platforms/teams/commands/reaction.js +6 -6
  49. package/dist/src/platforms/teams/commands/reaction.js.map +1 -1
  50. package/dist/src/platforms/teams/commands/snapshot.d.ts.map +1 -1
  51. package/dist/src/platforms/teams/commands/snapshot.js +6 -6
  52. package/dist/src/platforms/teams/commands/snapshot.js.map +1 -1
  53. package/dist/src/platforms/teams/commands/team.d.ts.map +1 -1
  54. package/dist/src/platforms/teams/commands/team.js +21 -25
  55. package/dist/src/platforms/teams/commands/team.js.map +1 -1
  56. package/dist/src/platforms/teams/commands/user.js +9 -9
  57. package/dist/src/platforms/teams/commands/user.js.map +1 -1
  58. package/dist/src/platforms/teams/credential-manager.d.ts +15 -2
  59. package/dist/src/platforms/teams/credential-manager.d.ts.map +1 -1
  60. package/dist/src/platforms/teams/credential-manager.js +110 -28
  61. package/dist/src/platforms/teams/credential-manager.js.map +1 -1
  62. package/dist/src/platforms/teams/ensure-auth.d.ts.map +1 -1
  63. package/dist/src/platforms/teams/ensure-auth.js +46 -16
  64. package/dist/src/platforms/teams/ensure-auth.js.map +1 -1
  65. package/dist/src/platforms/teams/token-extractor.d.ts +8 -2
  66. package/dist/src/platforms/teams/token-extractor.d.ts.map +1 -1
  67. package/dist/src/platforms/teams/token-extractor.js +36 -24
  68. package/dist/src/platforms/teams/token-extractor.js.map +1 -1
  69. package/dist/src/platforms/teams/types.d.ts +121 -0
  70. package/dist/src/platforms/teams/types.d.ts.map +1 -1
  71. package/dist/src/platforms/teams/types.js +16 -0
  72. package/dist/src/platforms/teams/types.js.map +1 -1
  73. package/e2e/README.md +1 -1
  74. package/package.json +1 -1
  75. package/skills/agent-discord/SKILL.md +5 -0
  76. package/skills/agent-slack/SKILL.md +11 -17
  77. package/skills/agent-teams/SKILL.md +21 -24
  78. package/skills/agent-teams/references/common-patterns.md +63 -49
  79. package/src/platforms/discord/cli.ts +14 -0
  80. package/src/platforms/discord/commands/file.ts +13 -7
  81. package/src/platforms/discord/commands/friend.ts +34 -34
  82. package/src/platforms/discord/commands/index.ts +7 -0
  83. package/src/platforms/discord/commands/snapshot.ts +1 -2
  84. package/src/platforms/slack/commands/file.ts +12 -4
  85. package/src/platforms/slack/commands/sections.ts +8 -9
  86. package/src/platforms/slack/commands/snapshot.ts +1 -2
  87. package/src/platforms/slack/commands/user.ts +8 -8
  88. package/src/platforms/slackbot/commands/reaction.ts +2 -2
  89. package/src/platforms/teams/cli.ts +7 -0
  90. package/src/platforms/teams/commands/auth.test.ts +6 -5
  91. package/src/platforms/teams/commands/auth.ts +283 -120
  92. package/src/platforms/teams/commands/channel.test.ts +10 -4
  93. package/src/platforms/teams/commands/channel.ts +9 -9
  94. package/src/platforms/teams/commands/file.test.ts +9 -3
  95. package/src/platforms/teams/commands/file.ts +21 -21
  96. package/src/platforms/teams/commands/message.test.ts +9 -3
  97. package/src/platforms/teams/commands/message.ts +12 -12
  98. package/src/platforms/teams/commands/reaction.test.ts +9 -3
  99. package/src/platforms/teams/commands/reaction.ts +6 -6
  100. package/src/platforms/teams/commands/snapshot.ts +6 -6
  101. package/src/platforms/teams/commands/team.test.ts +19 -12
  102. package/src/platforms/teams/commands/team.ts +22 -28
  103. package/src/platforms/teams/commands/user.ts +9 -9
  104. package/src/platforms/teams/credential-manager.test.ts +30 -24
  105. package/src/platforms/teams/credential-manager.ts +106 -31
  106. package/src/platforms/teams/ensure-auth.test.ts +125 -26
  107. package/src/platforms/teams/ensure-auth.ts +47 -15
  108. package/src/platforms/teams/token-extractor.test.ts +116 -85
  109. package/src/platforms/teams/token-extractor.ts +65 -97
  110. package/src/platforms/teams/types.test.ts +31 -13
  111. package/src/platforms/teams/types.ts +45 -0
@@ -4,140 +4,235 @@ import { formatOutput } from '@/shared/utils/output'
4
4
  import { TeamsClient } from '../client'
5
5
  import { TeamsCredentialManager } from '../credential-manager'
6
6
  import { TeamsTokenExtractor } from '../token-extractor'
7
+ import type { TeamsAccount, TeamsAccountType, TeamsConfig } from '../types'
7
8
 
8
9
  export async function extractAction(options: { pretty?: boolean; debug?: boolean; token?: string }): Promise<void> {
9
10
  try {
10
- let token: string
11
-
12
11
  if (options.token) {
13
- token = options.token
14
- if (options.debug) {
15
- console.error(`[debug] Using provided token: ${token.substring(0, 20)}...`)
16
- }
17
- } else {
18
- const extractor = new TeamsTokenExtractor()
19
-
20
- if (process.platform === 'darwin') {
21
- console.log('')
22
- console.log(' Extracting your Microsoft Teams credentials...')
23
- console.log('')
24
- console.log(' Your Mac may ask for your password to access Keychain.')
25
- console.log(' This is required because Teams encrypts your login token')
26
- console.log(' using macOS Keychain for security.')
27
- console.log('')
28
- console.log(' What happens:')
29
- console.log(" 1. We read the encrypted token from Teams' cookies")
30
- console.log(' 2. macOS Keychain decrypts it (requires your password)')
31
- console.log(' 3. The token is stored locally in ~/.config/agent-messenger/')
32
- console.log('')
33
- console.log(' Your password is never stored or transmitted anywhere.')
34
- console.log('')
35
- }
36
-
37
- if (options.debug) {
38
- console.error(`[debug] Extracting Teams token...`)
39
- }
40
-
41
- const extracted = await extractor.extract()
12
+ await extractManualToken(options.token, options)
13
+ return
14
+ }
42
15
 
43
- if (!extracted) {
44
- console.log(
45
- formatOutput(
46
- {
47
- error: 'No Teams token found. Make sure Microsoft Teams desktop app is installed and logged in.',
48
- hint: 'Run with --token <token> to manually provide a token, or --debug for more info.',
49
- },
50
- options.pretty,
51
- ),
52
- )
53
- process.exit(1)
54
- }
55
- token = extracted.token
16
+ const extractor = new TeamsTokenExtractor()
17
+
18
+ if (process.platform === 'darwin') {
19
+ console.log('')
20
+ console.log(' Extracting your Microsoft Teams credentials...')
21
+ console.log('')
22
+ console.log(' Your Mac may ask for your password to access Keychain.')
23
+ console.log(' This is required because Teams encrypts your login token')
24
+ console.log(' using macOS Keychain for security.')
25
+ console.log('')
26
+ console.log(' What happens:')
27
+ console.log(" 1. We read the encrypted token from Teams' cookies")
28
+ console.log(' 2. macOS Keychain decrypts it (requires your password)')
29
+ console.log(' 3. The token is stored locally in ~/.config/agent-messenger/')
30
+ console.log('')
31
+ console.log(' Your password is never stored or transmitted anywhere.')
32
+ console.log('')
56
33
  }
57
34
 
58
35
  if (options.debug) {
59
- console.error(`[debug] Token extracted: ${token.substring(0, 20)}...`)
36
+ console.error('[debug] Extracting Teams tokens from all accounts...')
60
37
  }
61
38
 
62
- try {
63
- const client = new TeamsClient(token)
39
+ const extracted = await extractor.extract()
64
40
 
65
- if (options.debug) {
66
- console.error(`[debug] Testing token validity...`)
67
- }
41
+ if (extracted.length === 0) {
42
+ console.log(
43
+ formatOutput(
44
+ {
45
+ error: 'No Teams token found. Make sure Microsoft Teams desktop app is installed and logged in.',
46
+ hint: 'Run with --token <token> to manually provide a token, or --debug for more info.',
47
+ },
48
+ options.pretty,
49
+ ),
50
+ )
51
+ process.exit(1)
52
+ }
68
53
 
69
- const authInfo = await client.testAuth()
54
+ if (options.debug) {
55
+ console.error(`[debug] Found ${extracted.length} account(s)`)
56
+ }
70
57
 
58
+ const credManager = new TeamsCredentialManager()
59
+ const config: TeamsConfig = { current_account: null, accounts: {} }
60
+ const outputAccounts: Array<{
61
+ account_type: TeamsAccountType
62
+ user: string
63
+ teams: string[]
64
+ }> = []
65
+
66
+ for (const { token, accountType } of extracted) {
71
67
  if (options.debug) {
72
- console.error(`[debug] Token valid for user: ${authInfo.displayName}`)
73
- console.error(`[debug] Discovering teams...`)
68
+ console.error(`[debug] Validating ${accountType} account token...`)
74
69
  }
75
70
 
76
- const teams = await client.listTeams()
71
+ try {
72
+ const client = new TeamsClient(token)
73
+ const authInfo = await client.testAuth()
74
+ const teams = await client.listTeams()
77
75
 
78
- if (options.debug) {
79
- console.error(`[debug] ✓ Found ${teams.length} team(s)`)
80
- }
76
+ if (options.debug) {
77
+ console.error(`[debug] ✓ ${accountType}: ${authInfo.displayName} (${teams.length} team(s))`)
78
+ }
81
79
 
82
- if (teams.length === 0) {
83
- console.log(
84
- formatOutput(
85
- {
86
- error: 'No teams found. Make sure you are a member of at least one Microsoft Teams team.',
87
- },
88
- options.pretty,
89
- ),
90
- )
91
- process.exit(1)
92
- }
80
+ const teamMap: Record<string, { team_id: string; team_name: string }> = {}
81
+ for (const team of teams) {
82
+ teamMap[team.id] = { team_id: team.id, team_name: team.name }
83
+ }
93
84
 
94
- const credManager = new TeamsCredentialManager()
95
- const teamMap: Record<string, { team_id: string; team_name: string }> = {}
85
+ const account: TeamsAccount = {
86
+ token,
87
+ token_expires_at: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
88
+ account_type: accountType,
89
+ user_name: authInfo.displayName,
90
+ current_team: teams[0]?.id ?? null,
91
+ teams: teamMap,
92
+ }
96
93
 
97
- for (const team of teams) {
98
- teamMap[team.id] = {
99
- team_id: team.id,
100
- team_name: team.name,
94
+ config.accounts[accountType] = account
95
+ if (!config.current_account) {
96
+ config.current_account = accountType
101
97
  }
102
- }
103
98
 
104
- const config = {
105
- token: token,
106
- current_team: teams[0].id,
107
- teams: teamMap,
108
- token_expires_at: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
99
+ outputAccounts.push({
100
+ account_type: accountType,
101
+ user: authInfo.displayName,
102
+ teams: teams.map((t) => `${t.id}/${t.name}`),
103
+ })
104
+ } catch (error) {
105
+ const errorMessage = (error as Error).message
106
+ const is401 = errorMessage.includes('401') || errorMessage.includes('Unauthorized')
107
+ if (options.debug) {
108
+ console.error(`[debug] ✗ ${accountType}: ${errorMessage}`)
109
+ }
110
+ if (extracted.length === 1) {
111
+ console.log(
112
+ formatOutput(
113
+ {
114
+ error: `Token validation failed: ${errorMessage}`,
115
+ hint: is401
116
+ ? 'Token expired. Open Microsoft Teams, send a message to refresh your session, then run "auth extract" again.'
117
+ : 'Make sure Microsoft Teams desktop app is running and you are logged in.',
118
+ },
119
+ options.pretty,
120
+ ),
121
+ )
122
+ process.exit(1)
123
+ }
109
124
  }
125
+ }
110
126
 
111
- await credManager.saveConfig(config)
127
+ if (Object.keys(config.accounts).length === 0) {
128
+ console.log(
129
+ formatOutput(
130
+ { error: 'All extracted tokens failed validation. Make sure Microsoft Teams is running and logged in.' },
131
+ options.pretty,
132
+ ),
133
+ )
134
+ process.exit(1)
135
+ }
112
136
 
113
- if (options.debug) {
114
- console.error(`[debug] ✓ Credentials saved`)
115
- }
137
+ await credManager.saveConfig(config)
116
138
 
117
- const output = {
118
- teams: teams.map((t) => `${t.id}/${t.name}`),
119
- current: teams[0].id,
120
- }
139
+ if (options.debug) {
140
+ console.error('[debug] Credentials saved')
141
+ }
121
142
 
122
- console.log(formatOutput(output, options.pretty))
123
- } catch (error) {
124
- const errorMessage = (error as Error).message
125
- const is401 = errorMessage.includes('401') || errorMessage.includes('Unauthorized')
143
+ console.log(
144
+ formatOutput(
145
+ {
146
+ accounts: outputAccounts,
147
+ current_account: config.current_account,
148
+ },
149
+ options.pretty,
150
+ ),
151
+ )
152
+ } catch (error) {
153
+ handleError(error as Error)
154
+ }
155
+ }
156
+
157
+ async function extractManualToken(token: string, options: { pretty?: boolean; debug?: boolean }): Promise<void> {
158
+ if (options.debug) {
159
+ console.error(`[debug] Using provided token: ${token.substring(0, 20)}...`)
160
+ }
161
+
162
+ try {
163
+ const client = new TeamsClient(token)
164
+ const authInfo = await client.testAuth()
165
+ const teams = await client.listTeams()
166
+
167
+ if (teams.length === 0) {
126
168
  console.log(
127
169
  formatOutput(
128
- {
129
- error: `Token validation failed: ${errorMessage}`,
130
- hint: is401
131
- ? 'Token expired. Open Microsoft Teams, send a message to refresh your session, then run "auth extract" again.'
132
- : 'Make sure Microsoft Teams desktop app is running and you are logged in.',
133
- },
170
+ { error: 'No teams found. Make sure you are a member of at least one Microsoft Teams team.' },
134
171
  options.pretty,
135
172
  ),
136
173
  )
137
174
  process.exit(1)
138
175
  }
176
+
177
+ const credManager = new TeamsCredentialManager()
178
+ const accountType: TeamsAccountType = 'work'
179
+
180
+ const teamMap: Record<string, { team_id: string; team_name: string }> = {}
181
+ for (const team of teams) {
182
+ teamMap[team.id] = { team_id: team.id, team_name: team.name }
183
+ }
184
+
185
+ const account: TeamsAccount = {
186
+ token,
187
+ token_expires_at: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
188
+ account_type: accountType,
189
+ user_name: authInfo.displayName,
190
+ current_team: teams[0].id,
191
+ teams: teamMap,
192
+ }
193
+
194
+ const existingConfig = await credManager.loadConfig()
195
+ const config: TeamsConfig = existingConfig ?? { current_account: accountType, accounts: {} }
196
+ config.accounts[accountType] = account
197
+ if (!config.current_account) {
198
+ config.current_account = accountType
199
+ }
200
+ await credManager.saveConfig(config)
201
+
202
+ if (options.debug) {
203
+ console.error('[debug] ✓ Credentials saved')
204
+ }
205
+
206
+ console.log(
207
+ formatOutput(
208
+ {
209
+ accounts: [
210
+ {
211
+ account_type: accountType,
212
+ user: authInfo.displayName,
213
+ teams: teams.map((t) => `${t.id}/${t.name}`),
214
+ },
215
+ ],
216
+ current_account: accountType,
217
+ },
218
+ options.pretty,
219
+ ),
220
+ )
139
221
  } catch (error) {
140
- handleError(error as Error)
222
+ const errorMessage = (error as Error).message
223
+ const is401 = errorMessage.includes('401') || errorMessage.includes('Unauthorized')
224
+ console.log(
225
+ formatOutput(
226
+ {
227
+ error: `Token validation failed: ${errorMessage}`,
228
+ hint: is401
229
+ ? 'Token expired. Open Microsoft Teams, send a message to refresh your session, then run "auth extract" again.'
230
+ : 'Make sure Microsoft Teams desktop app is running and you are logged in.',
231
+ },
232
+ options.pretty,
233
+ ),
234
+ )
235
+ process.exit(1)
141
236
  }
142
237
  }
143
238
 
@@ -146,7 +241,7 @@ export async function logoutAction(options: { pretty?: boolean }): Promise<void>
146
241
  const credManager = new TeamsCredentialManager()
147
242
  const config = await credManager.loadConfig()
148
243
 
149
- if (!config?.token) {
244
+ if (!config || Object.keys(config.accounts).length === 0) {
150
245
  console.log(formatOutput({ error: 'Not authenticated. Run "auth extract" first.' }, options.pretty))
151
246
  process.exit(1)
152
247
  }
@@ -164,35 +259,96 @@ export async function statusAction(options: { pretty?: boolean }): Promise<void>
164
259
  const credManager = new TeamsCredentialManager()
165
260
  const config = await credManager.loadConfig()
166
261
 
167
- if (!config?.token) {
262
+ if (!config || Object.keys(config.accounts).length === 0) {
168
263
  console.log(formatOutput({ error: 'Not authenticated. Run "auth extract" first.' }, options.pretty))
169
264
  process.exit(1)
170
265
  }
171
266
 
172
- let authInfo: { id: string; displayName: string } | null = null
173
- let valid = false
174
- const isExpired = await credManager.isTokenExpired()
175
-
176
- if (!isExpired) {
177
- try {
178
- const client = new TeamsClient(config.token, config.token_expires_at)
179
- authInfo = await client.testAuth()
180
- valid = true
181
- } catch {
182
- valid = false
267
+ const accountStatuses: Array<{
268
+ account_type: string
269
+ authenticated: boolean
270
+ user: string | null
271
+ teams_count: number
272
+ token_expires_at: string | null
273
+ token_expired: boolean
274
+ current: boolean
275
+ }> = []
276
+
277
+ for (const [key, account] of Object.entries(config.accounts)) {
278
+ const isExpired = account.token_expires_at ? new Date(account.token_expires_at).getTime() <= Date.now() : true
279
+
280
+ let displayName: string | null = account.user_name ?? null
281
+ let valid = false
282
+
283
+ if (!isExpired) {
284
+ try {
285
+ const client = new TeamsClient(account.token, account.token_expires_at)
286
+ const authInfo = await client.testAuth()
287
+ displayName = authInfo.displayName
288
+ valid = true
289
+ } catch {
290
+ valid = false
291
+ }
183
292
  }
293
+
294
+ accountStatuses.push({
295
+ account_type: key,
296
+ authenticated: valid,
297
+ user: displayName,
298
+ teams_count: Object.keys(account.teams).length,
299
+ token_expires_at: account.token_expires_at ?? null,
300
+ token_expired: isExpired,
301
+ current: key === config.current_account,
302
+ })
184
303
  }
185
304
 
186
- const output = {
187
- authenticated: valid,
188
- user: authInfo?.displayName,
189
- current_team: config.current_team,
190
- teams_count: Object.keys(config.teams).length,
191
- token_expires_at: config.token_expires_at ?? null,
192
- token_expired: isExpired,
305
+ console.log(
306
+ formatOutput(
307
+ {
308
+ current_account: config.current_account,
309
+ accounts: accountStatuses,
310
+ },
311
+ options.pretty,
312
+ ),
313
+ )
314
+ } catch (error) {
315
+ handleError(error as Error)
316
+ }
317
+ }
318
+
319
+ export async function switchAccountAction(accountType: string, options: { pretty?: boolean }): Promise<void> {
320
+ try {
321
+ const credManager = new TeamsCredentialManager()
322
+ const config = await credManager.loadConfig()
323
+
324
+ if (!config || !config.accounts[accountType]) {
325
+ const available = config ? Object.keys(config.accounts).join(', ') : 'none'
326
+ console.log(
327
+ formatOutput(
328
+ {
329
+ error: `Account not found: ${accountType}`,
330
+ available,
331
+ hint: 'Run "auth extract" to discover all accounts.',
332
+ },
333
+ options.pretty,
334
+ ),
335
+ )
336
+ process.exit(1)
193
337
  }
194
338
 
195
- console.log(formatOutput(output, options.pretty))
339
+ await credManager.setCurrentAccount(accountType as TeamsAccountType)
340
+ const account = config.accounts[accountType]
341
+
342
+ console.log(
343
+ formatOutput(
344
+ {
345
+ current_account: accountType,
346
+ user: account.user_name,
347
+ teams_count: Object.keys(account.teams).length,
348
+ },
349
+ options.pretty,
350
+ ),
351
+ )
196
352
  } catch (error) {
197
353
  handleError(error as Error)
198
354
  }
@@ -220,3 +376,10 @@ export const authCommand = new Command('auth')
220
376
  .option('--pretty', 'Pretty print JSON output')
221
377
  .action(statusAction),
222
378
  )
379
+ .addCommand(
380
+ new Command('switch-account')
381
+ .description('Switch active account (work or personal)')
382
+ .argument('<account-type>', 'Account type: work or personal')
383
+ .option('--pretty', 'Pretty print JSON output')
384
+ .action(switchAccountAction),
385
+ )
@@ -43,10 +43,16 @@ beforeEach(() => {
43
43
  ])
44
44
 
45
45
  credManagerLoadConfigSpy = spyOn(TeamsCredentialManager.prototype, 'loadConfig').mockResolvedValue({
46
- token: 'test-token',
47
- current_team: 'team-1',
48
- teams: {
49
- 'team-1': { team_id: 'team-1', team_name: 'Team One' },
46
+ current_account: 'work',
47
+ accounts: {
48
+ work: {
49
+ token: 'test-token',
50
+ account_type: 'work' as const,
51
+ current_team: 'team-1',
52
+ teams: {
53
+ 'team-1': { team_id: 'team-1', team_name: 'Team One' },
54
+ },
55
+ },
50
56
  },
51
57
  })
52
58
  })
@@ -7,14 +7,14 @@ import { TeamsCredentialManager } from '../credential-manager'
7
7
  export async function listAction(teamId: string, options: { pretty?: boolean }): Promise<void> {
8
8
  try {
9
9
  const credManager = new TeamsCredentialManager()
10
- const config = await credManager.loadConfig()
10
+ const cred = await credManager.getTokenWithExpiry()
11
11
 
12
- if (!config?.token) {
12
+ if (!cred) {
13
13
  console.log(formatOutput({ error: 'Not authenticated. Run "auth extract" first.' }, options.pretty))
14
14
  process.exit(1)
15
15
  }
16
16
 
17
- const client = new TeamsClient(config.token, config.token_expires_at)
17
+ const client = new TeamsClient(cred.token, cred.tokenExpiresAt)
18
18
  const channels = await client.listChannels(teamId)
19
19
 
20
20
  const output = channels.map((ch) => ({
@@ -33,14 +33,14 @@ export async function listAction(teamId: string, options: { pretty?: boolean }):
33
33
  export async function infoAction(teamId: string, channelId: string, options: { pretty?: boolean }): Promise<void> {
34
34
  try {
35
35
  const credManager = new TeamsCredentialManager()
36
- const config = await credManager.loadConfig()
36
+ const cred = await credManager.getTokenWithExpiry()
37
37
 
38
- if (!config?.token) {
38
+ if (!cred) {
39
39
  console.log(formatOutput({ error: 'Not authenticated. Run "auth extract" first.' }, options.pretty))
40
40
  process.exit(1)
41
41
  }
42
42
 
43
- const client = new TeamsClient(config.token, config.token_expires_at)
43
+ const client = new TeamsClient(cred.token, cred.tokenExpiresAt)
44
44
  const channel = await client.getChannel(teamId, channelId)
45
45
 
46
46
  const output = {
@@ -63,14 +63,14 @@ export async function historyAction(
63
63
  ): Promise<void> {
64
64
  try {
65
65
  const credManager = new TeamsCredentialManager()
66
- const config = await credManager.loadConfig()
66
+ const cred = await credManager.getTokenWithExpiry()
67
67
 
68
- if (!config?.token) {
68
+ if (!cred) {
69
69
  console.log(formatOutput({ error: 'Not authenticated. Run "auth extract" first.' }, options.pretty))
70
70
  process.exit(1)
71
71
  }
72
72
 
73
- const client = new TeamsClient(config.token, config.token_expires_at)
73
+ const client = new TeamsClient(cred.token, cred.tokenExpiresAt)
74
74
  const messages = await client.getMessages(teamId, channelId, options.limit || 50)
75
75
 
76
76
  const output = messages.map((msg) => ({
@@ -35,9 +35,15 @@ beforeEach(() => {
35
35
  ])
36
36
 
37
37
  credManagerLoadConfigSpy = spyOn(TeamsCredentialManager.prototype, 'loadConfig').mockResolvedValue({
38
- token: 'test_token',
39
- current_team: 'team_123',
40
- teams: { team_123: { team_id: 'team_123', team_name: 'Test Team' } },
38
+ current_account: 'work',
39
+ accounts: {
40
+ work: {
41
+ token: 'test_token',
42
+ account_type: 'work' as const,
43
+ current_team: 'team_123',
44
+ teams: { team_123: { team_id: 'team_123', team_name: 'Test Team' } },
45
+ },
46
+ },
41
47
  })
42
48
  })
43
49
 
@@ -14,14 +14,14 @@ export async function uploadAction(
14
14
  ): Promise<void> {
15
15
  try {
16
16
  const credManager = new TeamsCredentialManager()
17
- const config = await credManager.loadConfig()
17
+ const cred = await credManager.getTokenWithExpiry()
18
18
 
19
- if (!config?.token) {
19
+ if (!cred) {
20
20
  console.log(formatOutput({ error: 'Not authenticated. Run "auth extract" first.' }, options.pretty))
21
21
  process.exit(1)
22
22
  }
23
23
 
24
- const client = new TeamsClient(config.token, config.token_expires_at)
24
+ const client = new TeamsClient(cred.token, cred.tokenExpiresAt)
25
25
  const filePath = resolve(path)
26
26
  const file = await client.uploadFile(teamId, channelId, filePath)
27
27
 
@@ -42,14 +42,14 @@ export async function uploadAction(
42
42
  export async function listAction(teamId: string, channelId: string, options: { pretty?: boolean }): Promise<void> {
43
43
  try {
44
44
  const credManager = new TeamsCredentialManager()
45
- const config = await credManager.loadConfig()
45
+ const cred = await credManager.getTokenWithExpiry()
46
46
 
47
- if (!config?.token) {
47
+ if (!cred) {
48
48
  console.log(formatOutput({ error: 'Not authenticated. Run "auth extract" first.' }, options.pretty))
49
49
  process.exit(1)
50
50
  }
51
51
 
52
- const client = new TeamsClient(config.token, config.token_expires_at)
52
+ const client = new TeamsClient(cred.token, cred.tokenExpiresAt)
53
53
  const files = await client.listFiles(teamId, channelId)
54
54
 
55
55
  const output = files.map((file: TeamsFile) => ({
@@ -74,14 +74,14 @@ export async function infoAction(
74
74
  ): Promise<void> {
75
75
  try {
76
76
  const credManager = new TeamsCredentialManager()
77
- const config = await credManager.loadConfig()
77
+ const cred = await credManager.getTokenWithExpiry()
78
78
 
79
- if (!config?.token) {
79
+ if (!cred) {
80
80
  console.log(formatOutput({ error: 'Not authenticated. Run "auth extract" first.' }, options.pretty))
81
81
  process.exit(1)
82
82
  }
83
83
 
84
- const client = new TeamsClient(config.token, config.token_expires_at)
84
+ const client = new TeamsClient(cred.token, cred.tokenExpiresAt)
85
85
  const files = await client.listFiles(teamId, channelId)
86
86
  const fileData = files.find((f) => f.id === fileId)
87
87
 
@@ -105,30 +105,30 @@ export async function infoAction(
105
105
  }
106
106
 
107
107
  export const fileCommand = new Command('file')
108
- .description('file commands')
108
+ .description('File commands')
109
109
  .addCommand(
110
110
  new Command('upload')
111
- .description('upload file to channel')
112
- .argument('<team>', 'team ID')
113
- .argument('<channel>', 'channel ID')
114
- .argument('<path>', 'file path')
111
+ .description('Upload file to channel')
112
+ .argument('<team-id>', 'Team ID')
113
+ .argument('<channel-id>', 'Channel ID')
114
+ .argument('<path>', 'File path')
115
115
  .option('--pretty', 'Pretty print JSON output')
116
116
  .action(uploadAction),
117
117
  )
118
118
  .addCommand(
119
119
  new Command('list')
120
- .description('list files in channel')
121
- .argument('<team>', 'team ID')
122
- .argument('<channel>', 'channel ID')
120
+ .description('List files in channel')
121
+ .argument('<team-id>', 'Team ID')
122
+ .argument('<channel-id>', 'Channel ID')
123
123
  .option('--pretty', 'Pretty print JSON output')
124
124
  .action(listAction),
125
125
  )
126
126
  .addCommand(
127
127
  new Command('info')
128
- .description('show file details')
129
- .argument('<team>', 'team ID')
130
- .argument('<channel>', 'channel ID')
131
- .argument('<file>', 'file ID')
128
+ .description('Show file details')
129
+ .argument('<team-id>', 'Team ID')
130
+ .argument('<channel-id>', 'Channel ID')
131
+ .argument('<file-id>', 'File ID')
132
132
  .option('--pretty', 'Pretty print JSON output')
133
133
  .action(infoAction),
134
134
  )