agent-messenger 1.3.4 → 1.3.6

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 (101) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.env.template +17 -1
  3. package/bunfig.e2e.toml +3 -0
  4. package/bunfig.toml +2 -0
  5. package/dist/package.json +3 -3
  6. package/dist/src/cli.d.ts.map +1 -1
  7. package/dist/src/cli.js +4 -3
  8. package/dist/src/cli.js.map +1 -1
  9. package/dist/src/platforms/discord/cli.js +1 -1
  10. package/dist/src/platforms/discord/client.d.ts +1 -1
  11. package/dist/src/platforms/discord/client.js +1 -1
  12. package/dist/src/platforms/discord/commands/auth.js +5 -5
  13. package/dist/src/platforms/discord/commands/channel.js +4 -4
  14. package/dist/src/platforms/discord/commands/dm.js +4 -4
  15. package/dist/src/platforms/discord/commands/file.js +4 -4
  16. package/dist/src/platforms/discord/commands/friend.js +2 -2
  17. package/dist/src/platforms/discord/commands/index.d.ts +8 -8
  18. package/dist/src/platforms/discord/commands/index.js +8 -8
  19. package/dist/src/platforms/discord/commands/member.js +4 -4
  20. package/dist/src/platforms/discord/commands/mention.js +4 -4
  21. package/dist/src/platforms/discord/commands/message.js +4 -4
  22. package/dist/src/platforms/discord/commands/note.js +4 -4
  23. package/dist/src/platforms/discord/commands/profile.js +4 -4
  24. package/dist/src/platforms/discord/commands/reaction.js +4 -4
  25. package/dist/src/platforms/discord/commands/server.js +4 -4
  26. package/dist/src/platforms/discord/commands/snapshot.js +5 -5
  27. package/dist/src/platforms/discord/commands/thread.js +4 -4
  28. package/dist/src/platforms/discord/commands/user.js +4 -4
  29. package/dist/src/platforms/discord/token-extractor.d.ts +1 -1
  30. package/dist/src/platforms/discord/token-extractor.js +1 -1
  31. package/dist/src/platforms/slack/cli.js +1 -1
  32. package/dist/src/platforms/slack/client.d.ts +1 -1
  33. package/dist/src/platforms/slack/client.js +3 -3
  34. package/dist/src/platforms/slack/client.js.map +1 -1
  35. package/dist/src/platforms/slack/commands/activity.js +4 -4
  36. package/dist/src/platforms/slack/commands/auth.js +5 -5
  37. package/dist/src/platforms/slack/commands/channel.js +4 -4
  38. package/dist/src/platforms/slack/commands/drafts.js +4 -4
  39. package/dist/src/platforms/slack/commands/file.js +4 -4
  40. package/dist/src/platforms/slack/commands/index.d.ts +13 -13
  41. package/dist/src/platforms/slack/commands/index.js +13 -13
  42. package/dist/src/platforms/slack/commands/message.js +4 -4
  43. package/dist/src/platforms/slack/commands/reaction.js +4 -4
  44. package/dist/src/platforms/slack/commands/saved.js +4 -4
  45. package/dist/src/platforms/slack/commands/sections.js +4 -4
  46. package/dist/src/platforms/slack/commands/snapshot.js +5 -5
  47. package/dist/src/platforms/slack/commands/unread.js +4 -4
  48. package/dist/src/platforms/slack/commands/user.js +4 -4
  49. package/dist/src/platforms/slack/commands/workspace.js +3 -3
  50. package/dist/src/platforms/slack/credential-manager.d.ts +1 -1
  51. package/dist/src/platforms/slack/index.d.ts +2 -2
  52. package/dist/src/platforms/slack/index.js +2 -2
  53. package/dist/src/platforms/slack/token-extractor.d.ts +1 -1
  54. package/dist/src/platforms/slack/token-extractor.js +1 -1
  55. package/dist/src/platforms/slackbot/cli.js +2 -2
  56. package/dist/src/platforms/slackbot/client.d.ts +1 -1
  57. package/dist/src/platforms/slackbot/client.js +1 -1
  58. package/dist/src/platforms/slackbot/commands/auth.d.ts +1 -1
  59. package/dist/src/platforms/slackbot/commands/auth.js +3 -3
  60. package/dist/src/platforms/slackbot/commands/channel.js +3 -3
  61. package/dist/src/platforms/slackbot/commands/index.d.ts +5 -5
  62. package/dist/src/platforms/slackbot/commands/index.js +5 -5
  63. package/dist/src/platforms/slackbot/commands/message.js +3 -3
  64. package/dist/src/platforms/slackbot/commands/reaction.js +3 -3
  65. package/dist/src/platforms/slackbot/commands/shared.d.ts +2 -2
  66. package/dist/src/platforms/slackbot/commands/shared.js +3 -3
  67. package/dist/src/platforms/slackbot/commands/user.js +3 -3
  68. package/dist/src/platforms/slackbot/credential-manager.d.ts +1 -1
  69. package/dist/src/platforms/slackbot/index.d.ts +3 -3
  70. package/dist/src/platforms/slackbot/index.js +3 -3
  71. package/dist/src/platforms/teams/cli.js +2 -2
  72. package/dist/src/platforms/teams/client.d.ts +1 -1
  73. package/dist/src/platforms/teams/client.js +1 -1
  74. package/dist/src/platforms/teams/commands/auth.js +5 -5
  75. package/dist/src/platforms/teams/commands/channel.js +4 -4
  76. package/dist/src/platforms/teams/commands/file.js +4 -4
  77. package/dist/src/platforms/teams/commands/index.d.ts +8 -8
  78. package/dist/src/platforms/teams/commands/index.js +8 -8
  79. package/dist/src/platforms/teams/commands/message.js +4 -4
  80. package/dist/src/platforms/teams/commands/reaction.js +4 -4
  81. package/dist/src/platforms/teams/commands/snapshot.js +5 -5
  82. package/dist/src/platforms/teams/commands/team.js +4 -4
  83. package/dist/src/platforms/teams/commands/user.js +4 -4
  84. package/dist/src/platforms/teams/credential-manager.d.ts +1 -1
  85. package/dist/src/platforms/teams/token-extractor.d.ts +1 -1
  86. package/dist/src/platforms/teams/token-extractor.js +1 -1
  87. package/e2e/README.md +62 -12
  88. package/e2e/config.ts +38 -0
  89. package/e2e/discord.e2e.test.ts +49 -2
  90. package/e2e/helpers.ts +1 -0
  91. package/e2e/setup.ts +10 -0
  92. package/e2e/slack.e2e.test.ts +77 -0
  93. package/e2e/teams.e2e.test.ts +287 -0
  94. package/package.json +3 -3
  95. package/scripts/postbuild.ts +2 -0
  96. package/skills/agent-discord/SKILL.md +11 -5
  97. package/skills/agent-slack/SKILL.md +11 -5
  98. package/skills/agent-slackbot/SKILL.md +11 -5
  99. package/skills/agent-teams/SKILL.md +11 -5
  100. package/src/cli.ts +4 -3
  101. package/src/platforms/slack/client.ts +4 -4
package/e2e/README.md CHANGED
@@ -24,8 +24,10 @@ Before running E2E tests, you need:
24
24
  |------|-------------|
25
25
  | `config.ts` | Hardcoded test workspace/server IDs and validation |
26
26
  | `helpers.ts` | CLI runner, JSON parser, message cleanup utilities |
27
- | `slack.e2e.test.ts` | Slack command tests (23 tests) |
28
- | `discord.e2e.test.ts` | Discord command tests (20 tests) |
27
+ | `slack.e2e.test.ts` | Slack command tests |
28
+ | `slackbot.e2e.test.ts` | SlackBot command tests |
29
+ | `discord.e2e.test.ts` | Discord command tests |
30
+ | `teams.e2e.test.ts` | Teams command tests |
29
31
 
30
32
  ## Running E2E Tests Locally
31
33
 
@@ -39,6 +41,9 @@ agent-slack auth extract
39
41
 
40
42
  # Extract Discord credentials
41
43
  agent-discord auth extract
44
+
45
+ # Extract Teams credentials
46
+ agent-teams auth extract
42
47
  ```
43
48
 
44
49
  ### Step 2: Switch to Test Workspace
@@ -71,9 +76,15 @@ bun test e2e/
71
76
  # Run only Slack tests
72
77
  bun test e2e/slack.e2e.test.ts
73
78
 
79
+ # Run only SlackBot tests
80
+ bun test e2e/slackbot.e2e.test.ts
81
+
74
82
  # Run only Discord tests
75
83
  bun test e2e/discord.e2e.test.ts
76
84
 
85
+ # Run only Teams tests
86
+ E2E_TEAMS_TEAM_ID=<id> E2E_TEAMS_CHANNEL_ID=<id> bun test e2e/teams.e2e.test.ts
87
+
77
88
  # Run specific test by name
78
89
  bun test e2e/slack.e2e.test.ts --test-name-pattern "message send"
79
90
  ```
@@ -100,6 +111,15 @@ Set these secrets in your GitHub repository settings:
100
111
  | `E2E_DISCORD_TOKEN` | Discord auth token | `agent-discord auth extract` → check output |
101
112
  | `E2E_DISCORD_SERVER_ID` | Test server ID | `agent-discord server current` |
102
113
 
114
+ #### Teams Secrets
115
+
116
+ | Secret | Description | How to Get |
117
+ |--------|-------------|------------|
118
+ | `E2E_TEAMS_TOKEN` | Teams auth token (skypetoken_asm) | `agent-teams auth extract` → check output |
119
+ | `E2E_TEAMS_TEAM_ID` | Test team ID | `agent-teams team list` |
120
+ | `E2E_TEAMS_CHANNEL_ID` | Test channel ID | `agent-teams channel list` |
121
+ | `E2E_TEAMS_TEAM_NAME` | Test team name (optional) | `agent-teams team current` |
122
+
103
123
  ### Getting Credentials for CI
104
124
 
105
125
  Run locally to extract credentials, then copy them to GitHub Secrets:
@@ -133,34 +153,64 @@ When triggering manually, you can select which platform to test:
133
153
 
134
154
  ## Test Coverage
135
155
 
136
- ### Slack Tests (23 tests)
156
+ ### Slack Tests
137
157
 
138
158
  | Command Group | Tests |
139
159
  |---------------|-------|
140
160
  | `auth` | status |
141
161
  | `workspace` | list, current |
142
162
  | `message` | send, list, get, update, delete, thread reply, replies, search |
143
- | `channel` | list, list --type, info |
163
+ | `channel` | list, list --type, info, history |
144
164
  | `user` | list, me, info |
145
165
  | `reaction` | add, list, remove |
146
- | `file` | upload, list, list --channel, info |
147
- | `snapshot` | default, --channels-only, --users-only, --limit |
166
+ | `file` | upload, list |
167
+ | `unread` | counts, mark |
168
+ | `activity` | list |
169
+ | `saved` | list |
170
+ | `drafts` | list |
171
+ | `sections` | list |
172
+ | `snapshot` | default, --channels-only, --users-only |
173
+
174
+ ### SlackBot Tests
175
+
176
+ | Command Group | Tests |
177
+ |---------------|-------|
178
+ | `auth` | status |
179
+ | `message` | send, list, get, update, delete, thread reply, replies |
180
+ | `channel` | list, info |
181
+ | `user` | list, info |
182
+ | `reaction` | add, remove |
148
183
 
149
- ### Discord Tests (20 tests)
184
+ ### Discord Tests
150
185
 
151
186
  | Command Group | Tests |
152
187
  |---------------|-------|
153
188
  | `auth` | status |
154
189
  | `server` | list, current, info |
155
- | `message` | send, list, get*, delete |
190
+ | `message` | send, list, get\*, delete, ack, search |
156
191
  | `channel` | list, info, history |
157
- | `user` | list, me, info* |
158
- | `reaction` | add*, list*, remove* |
159
- | `file` | upload, list |
160
- | `snapshot` | default, --channels-only, --users-only |
192
+ | `user` | list\*, me, info |
193
+ | `reaction` | add\*, list\*, remove\* |
194
+ | `file` | upload, list, info |
195
+ | `snapshot` | default\*, --channels-only, --users-only\* |
161
196
 
162
197
  \* Some Discord tests are skipped because they require Bot Token permissions not available to user tokens.
163
198
 
199
+ ### Teams Tests
200
+
201
+ | Command Group | Tests |
202
+ |---------------|-------|
203
+ | `auth` | status |
204
+ | `team` | list, current, info |
205
+ | `message` | send, list, get, delete |
206
+ | `channel` | list, info, history |
207
+ | `user` | list, me, info |
208
+ | `reaction` | add, list, remove |
209
+ | `file` | upload, list, info |
210
+ | `snapshot` | default, --channels-only, --users-only |
211
+
212
+ > ⚠️ Teams tests require `E2E_TEAMS_TEAM_ID` and `E2E_TEAMS_CHANNEL_ID` environment variables. Teams tokens expire in 60-90 minutes.
213
+
164
214
  ## Troubleshooting
165
215
 
166
216
  ### "Wrong workspace" / "Wrong server" Error
package/e2e/config.ts CHANGED
@@ -75,3 +75,41 @@ export async function validateDiscordEnvironment() {
75
75
  }
76
76
  }
77
77
  }
78
+
79
+ // Teams Test Environment
80
+ export const TEAMS_TEST_TEAM_ID = process.env.E2E_TEAMS_TEAM_ID || ''
81
+ export const TEAMS_TEST_TEAM_NAME = process.env.E2E_TEAMS_TEAM_NAME || 'Agent Messenger'
82
+ export const TEAMS_TEST_CHANNEL_ID = process.env.E2E_TEAMS_CHANNEL_ID || ''
83
+ export const TEAMS_TEST_CHANNEL = 'e2e-test'
84
+
85
+ export async function validateTeamsEnvironment() {
86
+ const { runCLI, parseJSON } = await import('./helpers')
87
+
88
+ if (!TEAMS_TEST_TEAM_ID || !TEAMS_TEST_CHANNEL_ID) {
89
+ throw new Error(
90
+ 'Teams E2E environment not configured. Set E2E_TEAMS_TEAM_ID and E2E_TEAMS_CHANNEL_ID environment variables.',
91
+ )
92
+ }
93
+
94
+ const result = await runCLI('teams', ['auth', 'status'])
95
+ if (result.exitCode !== 0) {
96
+ throw new Error('Teams authentication failed. Please run: agent-teams auth extract')
97
+ }
98
+
99
+ const data = parseJSON<{ authenticated: boolean; expired?: boolean }>(result.stdout)
100
+ if (data?.expired) {
101
+ throw new Error('Teams token expired. Please run: agent-teams auth extract')
102
+ }
103
+
104
+ const currentResult = await runCLI('teams', ['team', 'current'])
105
+ const team = parseJSON<{ team_id: string }>(currentResult.stdout)
106
+ if (team?.team_id !== TEAMS_TEST_TEAM_ID) {
107
+ const switchResult = await runCLI('teams', ['team', 'switch', TEAMS_TEST_TEAM_ID])
108
+ if (switchResult.exitCode !== 0) {
109
+ throw new Error(
110
+ `Failed to switch to test team. Expected: ${TEAMS_TEST_TEAM_NAME} (${TEAMS_TEST_TEAM_ID}). ` +
111
+ `Make sure you have access to the test team.`,
112
+ )
113
+ }
114
+ }
115
+ }
@@ -104,6 +104,29 @@ describe('Discord E2E Tests', () => {
104
104
  const result = await runCLI('discord', ['message', 'delete', DISCORD_TEST_CHANNEL_ID, id, '--force'])
105
105
  expect(result.exitCode).toBe(0)
106
106
  })
107
+
108
+ test('message ack acknowledges message', async () => {
109
+ const testId = generateTestId()
110
+ const { id } = await createTestMessage('discord', DISCORD_TEST_CHANNEL_ID, `Ack test ${testId}`)
111
+ testMessages.push(id)
112
+
113
+ await waitForRateLimit()
114
+
115
+ const result = await runCLI('discord', ['message', 'ack', DISCORD_TEST_CHANNEL_ID, id])
116
+ expect(result.exitCode).toBe(0)
117
+
118
+ const data = parseJSON<{ acknowledged: string }>(result.stdout)
119
+ expect(data?.acknowledged).toBe(id)
120
+ })
121
+
122
+ test('message search finds messages', async () => {
123
+ const result = await runCLI('discord', ['message', 'search', 'test', '--limit', '5'])
124
+ expect(result.exitCode).toBe(0)
125
+
126
+ const data = parseJSON<{ results: unknown[]; total_results: number }>(result.stdout)
127
+ expect(data?.results).toBeDefined()
128
+ expect(typeof data?.total_results).toBe('number')
129
+ })
107
130
  })
108
131
 
109
132
  describe('channel', () => {
@@ -203,7 +226,7 @@ describe('Discord E2E Tests', () => {
203
226
  expect(Array.isArray(data)).toBe(true)
204
227
  })
205
228
 
206
- test('file upload uploads file', async () => {
229
+ test('file upload uploads file and file info retrieves it', async () => {
207
230
  const testId = generateTestId()
208
231
  const testFilePath = `/tmp/discord-e2e-${testId}.txt`
209
232
  await Bun.write(testFilePath, `Discord E2E test file ${testId}`)
@@ -212,15 +235,39 @@ describe('Discord E2E Tests', () => {
212
235
  const result = await runCLI('discord', ['file', 'upload', DISCORD_TEST_CHANNEL_ID, testFilePath])
213
236
  expect(result.exitCode).toBe(0)
214
237
 
215
- const data = parseJSON<{ id: string }>(result.stdout)
238
+ const data = parseJSON<{ id: string; attachments?: Array<{ id: string }> }>(result.stdout)
216
239
  expect(data?.id).toBeTruthy()
217
240
 
218
241
  // Track message for cleanup (file uploads create messages)
219
242
  if (data?.id) testMessages.push(data.id)
243
+
244
+ // Test file info using the attachment from the uploaded message
245
+ if (data?.attachments?.[0]?.id) {
246
+ await waitForRateLimit()
247
+ const infoResult = await runCLI('discord', ['file', 'info', DISCORD_TEST_CHANNEL_ID, data.attachments[0].id])
248
+ expect(infoResult.exitCode).toBe(0)
249
+ }
220
250
  } finally {
221
251
  await Bun.$`rm -f ${testFilePath}`.quiet()
222
252
  }
223
253
  })
254
+
255
+ test('file info retrieves file details', async () => {
256
+ // List files first to get a file ID
257
+ const listResult = await runCLI('discord', ['file', 'list', DISCORD_TEST_CHANNEL_ID])
258
+ expect(listResult.exitCode).toBe(0)
259
+
260
+ const files = parseJSON<Array<{ id: string }>>(listResult.stdout)
261
+ if (files && files.length > 0) {
262
+ await waitForRateLimit()
263
+
264
+ const result = await runCLI('discord', ['file', 'info', DISCORD_TEST_CHANNEL_ID, files[0].id])
265
+ expect(result.exitCode).toBe(0)
266
+
267
+ const data = parseJSON<{ id: string }>(result.stdout)
268
+ expect(data?.id).toBeTruthy()
269
+ }
270
+ })
224
271
  })
225
272
 
226
273
  describe('snapshot', () => {
package/e2e/helpers.ts CHANGED
@@ -11,6 +11,7 @@ export async function runCLI(platform: string, args: string[]): Promise<CLIResul
11
11
  slack: 'agent-slack',
12
12
  discord: 'agent-discord',
13
13
  slackbot: 'agent-slackbot',
14
+ teams: 'agent-teams',
14
15
  }
15
16
  const command = commandMap[platform] || platform
16
17
 
package/e2e/setup.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { beforeAll, afterAll } from 'bun:test'
2
+ import { $ } from 'bun'
3
+
4
+ beforeAll(async () => {
5
+ await $`bun link`.quiet()
6
+ })
7
+
8
+ afterAll(async () => {
9
+ await $`bun unlink`.quiet()
10
+ })
@@ -280,6 +280,83 @@ describe('Slack E2E Tests', () => {
280
280
  })
281
281
  })
282
282
 
283
+ describe('channel history', () => {
284
+ test('channel history returns messages', async () => {
285
+ const result = await runCLI('slack', ['channel', 'history', SLACK_TEST_CHANNEL_ID, '--limit', '5'])
286
+ expect(result.exitCode).toBe(0)
287
+
288
+ const data = parseJSON<Array<{ ts: string }>>(result.stdout)
289
+ expect(Array.isArray(data)).toBe(true)
290
+ })
291
+ })
292
+
293
+ describe('unread', () => {
294
+ test('unread counts returns unread summary', async () => {
295
+ const result = await runCLI('slack', ['unread', 'counts'])
296
+ expect(result.exitCode).toBe(0)
297
+
298
+ const data = parseJSON<{ total_unread: number; channels: unknown[] }>(result.stdout)
299
+ expect(data?.total_unread).toBeDefined()
300
+ expect(Array.isArray(data?.channels)).toBe(true)
301
+ })
302
+
303
+ test('unread mark marks channel as read', async () => {
304
+ const testId = generateTestId()
305
+ const { id: ts } = await createTestMessage('slack', SLACK_TEST_CHANNEL_ID, `Unread mark test ${testId}`)
306
+ testMessages.push(ts)
307
+
308
+ await waitForRateLimit()
309
+
310
+ const result = await runCLI('slack', ['unread', 'mark', SLACK_TEST_CHANNEL_ID, ts])
311
+ expect(result.exitCode).toBe(0)
312
+
313
+ const data = parseJSON<{ marked_read: boolean }>(result.stdout)
314
+ expect(data?.marked_read).toBe(true)
315
+ })
316
+ })
317
+
318
+ describe('activity', () => {
319
+ test('activity list returns activity items', async () => {
320
+ const result = await runCLI('slack', ['activity', 'list', '--limit', '5'])
321
+ expect(result.exitCode).toBe(0)
322
+
323
+ const data = parseJSON<{ items: unknown[]; count: number }>(result.stdout)
324
+ expect(data?.items).toBeDefined()
325
+ expect(typeof data?.count).toBe('number')
326
+ })
327
+ })
328
+
329
+ describe('saved', () => {
330
+ test('saved list returns saved items', async () => {
331
+ const result = await runCLI('slack', ['saved', 'list'])
332
+ expect(result.exitCode).toBe(0)
333
+
334
+ const data = parseJSON<{ items: unknown[] }>(result.stdout)
335
+ expect(data?.items).toBeDefined()
336
+ expect(Array.isArray(data?.items)).toBe(true)
337
+ })
338
+ })
339
+
340
+ describe('drafts', () => {
341
+ test('drafts list returns drafts', async () => {
342
+ const result = await runCLI('slack', ['drafts', 'list'])
343
+ expect(result.exitCode).toBe(0)
344
+
345
+ const data = parseJSON<unknown[]>(result.stdout)
346
+ expect(Array.isArray(data)).toBe(true)
347
+ })
348
+ })
349
+
350
+ describe('sections', () => {
351
+ test('sections list returns channel sections', async () => {
352
+ const result = await runCLI('slack', ['sections', 'list'])
353
+ expect(result.exitCode).toBe(0)
354
+
355
+ const data = parseJSON<unknown[]>(result.stdout)
356
+ expect(Array.isArray(data)).toBe(true)
357
+ })
358
+ })
359
+
283
360
  describe('snapshot', () => {
284
361
  test('snapshot returns full workspace data', async () => {
285
362
  const result = await runCLI('slack', ['snapshot', '--limit', '2'])
@@ -0,0 +1,287 @@
1
+ import { describe, test, expect, beforeAll, afterEach } from 'bun:test'
2
+ import { runCLI, parseJSON, generateTestId, waitForRateLimit } from './helpers'
3
+ import {
4
+ TEAMS_TEST_CHANNEL_ID,
5
+ TEAMS_TEST_TEAM_ID,
6
+ validateTeamsEnvironment,
7
+ } from './config'
8
+
9
+ let testMessages: string[] = []
10
+
11
+ async function createTeamsMessage(text: string): Promise<string> {
12
+ const result = await runCLI('teams', ['message', 'send', TEAMS_TEST_TEAM_ID, TEAMS_TEST_CHANNEL_ID, text])
13
+ if (result.exitCode !== 0) {
14
+ throw new Error(`Failed to create Teams test message: ${result.stderr}`)
15
+ }
16
+ const data = parseJSON<{ id: string }>(result.stdout)
17
+ if (!data?.id) throw new Error('No message ID returned')
18
+ return data.id
19
+ }
20
+
21
+ async function cleanupTeamsMessages(ids: string[]) {
22
+ for (const id of ids) {
23
+ try {
24
+ await runCLI('teams', ['message', 'delete', TEAMS_TEST_TEAM_ID, TEAMS_TEST_CHANNEL_ID, id, '--force'])
25
+ await waitForRateLimit(500)
26
+ } catch {
27
+ // best-effort cleanup
28
+ }
29
+ }
30
+ }
31
+
32
+ describe('Teams E2E Tests', () => {
33
+ beforeAll(async () => {
34
+ await validateTeamsEnvironment()
35
+ })
36
+
37
+ afterEach(async () => {
38
+ if (testMessages.length > 0) {
39
+ await cleanupTeamsMessages(testMessages)
40
+ testMessages = []
41
+ }
42
+ await waitForRateLimit()
43
+ })
44
+
45
+ describe('auth', () => {
46
+ test('auth status returns authenticated team info', async () => {
47
+ const result = await runCLI('teams', ['auth', 'status'])
48
+ expect(result.exitCode).toBe(0)
49
+
50
+ const data = parseJSON<{ authenticated: boolean; current_team: string; token_expired: boolean }>(result.stdout)
51
+ expect(data).not.toBeNull()
52
+ expect(data?.authenticated).toBe(true)
53
+ expect(data?.current_team).toBeTruthy()
54
+ expect(typeof data?.token_expired).toBe('boolean')
55
+ })
56
+ })
57
+
58
+ describe('team', () => {
59
+ test('team list returns array', async () => {
60
+ const result = await runCLI('teams', ['team', 'list'])
61
+ expect(result.exitCode).toBe(0)
62
+
63
+ const data = parseJSON<Array<{ id: string; name: string }>>(result.stdout)
64
+ expect(Array.isArray(data)).toBe(true)
65
+ })
66
+
67
+ test('team current returns current team', async () => {
68
+ const result = await runCLI('teams', ['team', 'current'])
69
+ expect(result.exitCode).toBe(0)
70
+
71
+ const data = parseJSON<{ team_id: string }>(result.stdout)
72
+ expect(data?.team_id).toBe(TEAMS_TEST_TEAM_ID)
73
+ })
74
+
75
+ test('team info returns team details', async () => {
76
+ const result = await runCLI('teams', ['team', 'info', TEAMS_TEST_TEAM_ID])
77
+ expect(result.exitCode).toBe(0)
78
+
79
+ const data = parseJSON<{ id: string; name: string }>(result.stdout)
80
+ expect(data?.id).toBe(TEAMS_TEST_TEAM_ID)
81
+ expect(data?.name).toBeTruthy()
82
+ })
83
+ })
84
+
85
+ describe('message', () => {
86
+ test('message send creates message', async () => {
87
+ const testId = generateTestId()
88
+ const result = await runCLI('teams', ['message', 'send', TEAMS_TEST_TEAM_ID, TEAMS_TEST_CHANNEL_ID, `Test message ${testId}`])
89
+ expect(result.exitCode).toBe(0)
90
+
91
+ const data = parseJSON<{ id: string }>(result.stdout)
92
+ expect(data?.id).toBeTruthy()
93
+
94
+ if (data?.id) testMessages.push(data.id)
95
+ })
96
+
97
+ test('message list returns messages array', async () => {
98
+ const result = await runCLI('teams', ['message', 'list', TEAMS_TEST_TEAM_ID, TEAMS_TEST_CHANNEL_ID, '--limit', '5'])
99
+ expect(result.exitCode).toBe(0)
100
+
101
+ const data = parseJSON<Array<{ id: string }>>(result.stdout)
102
+ expect(Array.isArray(data)).toBe(true)
103
+ })
104
+
105
+ test('message get retrieves specific message', async () => {
106
+ const testId = generateTestId()
107
+ const id = await createTeamsMessage(`Get test ${testId}`)
108
+ testMessages.push(id)
109
+
110
+ await waitForRateLimit()
111
+
112
+ const result = await runCLI('teams', ['message', 'get', TEAMS_TEST_TEAM_ID, TEAMS_TEST_CHANNEL_ID, id])
113
+ expect(result.exitCode).toBe(0)
114
+
115
+ const data = parseJSON<{ content: string }>(result.stdout)
116
+ expect(data?.content).toContain(testId)
117
+ })
118
+
119
+ test('message delete removes message', async () => {
120
+ const testId = generateTestId()
121
+ const id = await createTeamsMessage(`Delete me ${testId}`)
122
+
123
+ await waitForRateLimit()
124
+
125
+ const result = await runCLI('teams', ['message', 'delete', TEAMS_TEST_TEAM_ID, TEAMS_TEST_CHANNEL_ID, id, '--force'])
126
+ expect(result.exitCode).toBe(0)
127
+ })
128
+
129
+ test('message command does not register update subcommand', async () => {
130
+ const result = await runCLI('teams', ['message', '--help'])
131
+ expect(result.exitCode).toBe(0)
132
+ expect(result.stdout).not.toContain('update')
133
+ })
134
+ })
135
+
136
+ describe('channel', () => {
137
+ test('channel list returns channels array', async () => {
138
+ const result = await runCLI('teams', ['channel', 'list', TEAMS_TEST_TEAM_ID])
139
+ expect(result.exitCode).toBe(0)
140
+
141
+ const data = parseJSON<Array<{ id: string; name: string }>>(result.stdout)
142
+ expect(Array.isArray(data)).toBe(true)
143
+ })
144
+
145
+ test('channel info returns channel details', async () => {
146
+ const result = await runCLI('teams', ['channel', 'info', TEAMS_TEST_TEAM_ID, TEAMS_TEST_CHANNEL_ID])
147
+ expect(result.exitCode).toBe(0)
148
+
149
+ const data = parseJSON<{ id: string }>(result.stdout)
150
+ expect(data?.id).toBe(TEAMS_TEST_CHANNEL_ID)
151
+ })
152
+
153
+ test('channel history returns messages', async () => {
154
+ const result = await runCLI('teams', ['channel', 'history', TEAMS_TEST_TEAM_ID, TEAMS_TEST_CHANNEL_ID, '--limit', '5'])
155
+ expect(result.exitCode).toBe(0)
156
+
157
+ const data = parseJSON<Array<{ id: string }>>(result.stdout)
158
+ expect(Array.isArray(data)).toBe(true)
159
+ })
160
+ })
161
+
162
+ describe('user', () => {
163
+ test('user list returns users array', async () => {
164
+ const result = await runCLI('teams', ['user', 'list', TEAMS_TEST_TEAM_ID])
165
+ expect(result.exitCode).toBe(0)
166
+
167
+ const data = parseJSON<Array<{ id: string }>>(result.stdout)
168
+ expect(Array.isArray(data)).toBe(true)
169
+ })
170
+
171
+ test('user me returns current user', async () => {
172
+ const result = await runCLI('teams', ['user', 'me'])
173
+ expect(result.exitCode).toBe(0)
174
+
175
+ const data = parseJSON<{ id: string; displayName: string }>(result.stdout)
176
+ expect(data?.id).toBeTruthy()
177
+ expect(data?.displayName).toBeTruthy()
178
+ })
179
+
180
+ test('user info returns user details', async () => {
181
+ const meResult = await runCLI('teams', ['user', 'me'])
182
+ expect(meResult.exitCode).toBe(0)
183
+
184
+ const me = parseJSON<{ id: string }>(meResult.stdout)
185
+ expect(me?.id).toBeTruthy()
186
+
187
+ await waitForRateLimit()
188
+
189
+ const result = await runCLI('teams', ['user', 'info', me!.id])
190
+ expect(result.exitCode).toBe(0)
191
+
192
+ const data = parseJSON<{ id: string }>(result.stdout)
193
+ expect(data?.id).toBe(me?.id)
194
+ })
195
+ })
196
+
197
+ describe('reaction', () => {
198
+ test('reaction add/remove lifecycle', async () => {
199
+ const testId = generateTestId()
200
+ const id = await createTeamsMessage(`Reaction test ${testId}`)
201
+ testMessages.push(id)
202
+
203
+ await waitForRateLimit(2000)
204
+
205
+ const addResult = await runCLI('teams', ['reaction', 'add', TEAMS_TEST_TEAM_ID, TEAMS_TEST_CHANNEL_ID, id, 'like'])
206
+ expect(addResult.exitCode).toBe(0)
207
+
208
+ const addData = parseJSON<{ success: boolean }>(addResult.stdout)
209
+ expect(addData?.success).toBe(true)
210
+
211
+ await waitForRateLimit(2000)
212
+
213
+ const removeResult = await runCLI('teams', ['reaction', 'remove', TEAMS_TEST_TEAM_ID, TEAMS_TEST_CHANNEL_ID, id, 'like'])
214
+ expect(removeResult.exitCode).toBe(0)
215
+
216
+ const removeData = parseJSON<{ success: boolean }>(removeResult.stdout)
217
+ expect(removeData?.success).toBe(true)
218
+ }, 15000)
219
+
220
+ test('reaction command does not register list subcommand', async () => {
221
+ const result = await runCLI('teams', ['reaction', '--help'])
222
+ expect(result.exitCode).toBe(0)
223
+ expect(result.stdout).not.toContain('list')
224
+ })
225
+ })
226
+
227
+ describe('file', () => {
228
+ test('file list returns files array', async () => {
229
+ const result = await runCLI('teams', ['file', 'list', TEAMS_TEST_TEAM_ID, TEAMS_TEST_CHANNEL_ID])
230
+ expect(result.exitCode).toBe(0)
231
+
232
+ const data = parseJSON<Array<{ id: string }>>(result.stdout)
233
+ expect(Array.isArray(data)).toBe(true)
234
+ })
235
+
236
+ test('file upload uploads file and file info returns details', async () => {
237
+ const testId = generateTestId()
238
+ const testFilePath = `/tmp/teams-e2e-${testId}.txt`
239
+ await Bun.write(testFilePath, `Teams E2E test file ${testId}`)
240
+
241
+ try {
242
+ const uploadResult = await runCLI('teams', ['file', 'upload', TEAMS_TEST_TEAM_ID, TEAMS_TEST_CHANNEL_ID, testFilePath])
243
+ expect(uploadResult.exitCode).toBe(0)
244
+
245
+ const uploadData = parseJSON<{ id: string }>(uploadResult.stdout)
246
+ expect(uploadData?.id).toBeTruthy()
247
+
248
+ await waitForRateLimit(2000)
249
+
250
+ const infoResult = await runCLI('teams', ['file', 'info', TEAMS_TEST_TEAM_ID, TEAMS_TEST_CHANNEL_ID, uploadData!.id])
251
+ expect(infoResult.exitCode).toBe(0)
252
+
253
+ const infoData = parseJSON<{ id: string }>(infoResult.stdout)
254
+ expect(infoData?.id).toBe(uploadData?.id)
255
+ } finally {
256
+ await Bun.$`rm -f ${testFilePath}`.quiet()
257
+ }
258
+ })
259
+ })
260
+
261
+ describe('snapshot', () => {
262
+ test('snapshot returns full team data', async () => {
263
+ const result = await runCLI('teams', ['snapshot', '--team-id', TEAMS_TEST_TEAM_ID, '--limit', '2'])
264
+ expect(result.exitCode).toBe(0)
265
+
266
+ const data = parseJSON<{ channels: unknown[]; members: unknown[] }>(result.stdout)
267
+ expect(data?.channels).toBeDefined()
268
+ expect(data?.members).toBeDefined()
269
+ })
270
+
271
+ test('snapshot --channels-only returns only channels', async () => {
272
+ const result = await runCLI('teams', ['snapshot', '--team-id', TEAMS_TEST_TEAM_ID, '--channels-only'])
273
+ expect(result.exitCode).toBe(0)
274
+
275
+ const data = parseJSON<{ channels: unknown[] }>(result.stdout)
276
+ expect(data?.channels).toBeDefined()
277
+ })
278
+
279
+ test('snapshot --users-only returns only users', async () => {
280
+ const result = await runCLI('teams', ['snapshot', '--team-id', TEAMS_TEST_TEAM_ID, '--users-only'])
281
+ expect(result.exitCode).toBe(0)
282
+
283
+ const data = parseJSON<{ members: unknown[] }>(result.stdout)
284
+ expect(data?.members).toBeDefined()
285
+ })
286
+ })
287
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-messenger",
3
- "version": "1.3.4",
3
+ "version": "1.3.6",
4
4
  "description": "Multi-platform messaging CLI for AI agents (Slack, Discord, Teams)",
5
5
  "repository": {
6
6
  "type": "git",
@@ -18,10 +18,10 @@
18
18
  "agent-slackbot": "dist/src/platforms/slackbot/cli.js"
19
19
  },
20
20
  "scripts": {
21
- "build": "tsc && tsc-alias",
21
+ "build": "tsc && tsc-alias --resolve-full-paths",
22
22
  "postbuild": "bun scripts/postbuild.ts",
23
23
  "test": "bun test src/",
24
- "test:e2e": "bun test e2e/",
24
+ "test:e2e": "bun --config=bunfig.e2e.toml test",
25
25
  "typecheck": "tsc --noEmit",
26
26
  "lint": "biome check .",
27
27
  "lint:fix": "biome check --write --unsafe .",
@@ -4,6 +4,8 @@ const cliFiles = [
4
4
  'dist/src/cli.js',
5
5
  'dist/src/platforms/slack/cli.js',
6
6
  'dist/src/platforms/discord/cli.js',
7
+ 'dist/src/platforms/teams/cli.js',
8
+ 'dist/src/platforms/slackbot/cli.js',
7
9
  ]
8
10
 
9
11
  for (const file of cliFiles) {