agent-messenger 1.2.0 → 1.3.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 (80) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +1 -1
  3. package/dist/package.json +3 -2
  4. package/dist/src/platforms/slackbot/cli.d.ts +5 -0
  5. package/dist/src/platforms/slackbot/cli.d.ts.map +1 -0
  6. package/dist/src/platforms/slackbot/cli.js +19 -0
  7. package/dist/src/platforms/slackbot/cli.js.map +1 -0
  8. package/dist/src/platforms/slackbot/client.d.ts +43 -0
  9. package/dist/src/platforms/slackbot/client.d.ts.map +1 -0
  10. package/dist/src/platforms/slackbot/client.js +347 -0
  11. package/dist/src/platforms/slackbot/client.js.map +1 -0
  12. package/dist/src/platforms/slackbot/commands/auth.d.ts +35 -0
  13. package/dist/src/platforms/slackbot/commands/auth.d.ts.map +1 -0
  14. package/dist/src/platforms/slackbot/commands/auth.js +185 -0
  15. package/dist/src/platforms/slackbot/commands/auth.js.map +1 -0
  16. package/dist/src/platforms/slackbot/commands/channel.d.ts +3 -0
  17. package/dist/src/platforms/slackbot/commands/channel.d.ts.map +1 -0
  18. package/dist/src/platforms/slackbot/commands/channel.js +40 -0
  19. package/dist/src/platforms/slackbot/commands/channel.js.map +1 -0
  20. package/dist/src/platforms/slackbot/commands/index.d.ts +6 -0
  21. package/dist/src/platforms/slackbot/commands/index.d.ts.map +1 -0
  22. package/dist/src/platforms/slackbot/commands/index.js +6 -0
  23. package/dist/src/platforms/slackbot/commands/index.js.map +1 -0
  24. package/dist/src/platforms/slackbot/commands/message.d.ts +3 -0
  25. package/dist/src/platforms/slackbot/commands/message.d.ts.map +1 -0
  26. package/dist/src/platforms/slackbot/commands/message.js +135 -0
  27. package/dist/src/platforms/slackbot/commands/message.js.map +1 -0
  28. package/dist/src/platforms/slackbot/commands/reaction.d.ts +3 -0
  29. package/dist/src/platforms/slackbot/commands/reaction.d.ts.map +1 -0
  30. package/dist/src/platforms/slackbot/commands/reaction.js +43 -0
  31. package/dist/src/platforms/slackbot/commands/reaction.js.map +1 -0
  32. package/dist/src/platforms/slackbot/commands/shared.d.ts +9 -0
  33. package/dist/src/platforms/slackbot/commands/shared.d.ts.map +1 -0
  34. package/dist/src/platforms/slackbot/commands/shared.js +13 -0
  35. package/dist/src/platforms/slackbot/commands/shared.js.map +1 -0
  36. package/dist/src/platforms/slackbot/commands/user.d.ts +3 -0
  37. package/dist/src/platforms/slackbot/commands/user.d.ts.map +1 -0
  38. package/dist/src/platforms/slackbot/commands/user.js +40 -0
  39. package/dist/src/platforms/slackbot/commands/user.js.map +1 -0
  40. package/dist/src/platforms/slackbot/credential-manager.d.ts +18 -0
  41. package/dist/src/platforms/slackbot/credential-manager.d.ts.map +1 -0
  42. package/dist/src/platforms/slackbot/credential-manager.js +187 -0
  43. package/dist/src/platforms/slackbot/credential-manager.js.map +1 -0
  44. package/dist/src/platforms/slackbot/index.d.ts +4 -0
  45. package/dist/src/platforms/slackbot/index.d.ts.map +1 -0
  46. package/dist/src/platforms/slackbot/index.js +4 -0
  47. package/dist/src/platforms/slackbot/index.js.map +1 -0
  48. package/dist/src/platforms/slackbot/types.d.ts +460 -0
  49. package/dist/src/platforms/slackbot/types.d.ts.map +1 -0
  50. package/dist/src/platforms/slackbot/types.js +114 -0
  51. package/dist/src/platforms/slackbot/types.js.map +1 -0
  52. package/docs/content/docs/integrations/meta.json +1 -1
  53. package/docs/content/docs/integrations/slackbot.mdx +204 -0
  54. package/docs/src/app/page.tsx +18 -1
  55. package/e2e/config.ts +26 -0
  56. package/e2e/helpers.ts +6 -1
  57. package/e2e/slackbot.e2e.test.ts +306 -0
  58. package/package.json +3 -2
  59. package/skills/agent-slackbot/SKILL.md +285 -0
  60. package/skills/agent-slackbot/references/authentication.md +253 -0
  61. package/skills/agent-slackbot/references/common-patterns.md +218 -0
  62. package/skills/agent-slackbot/templates/monitor-channel.sh +98 -0
  63. package/skills/agent-slackbot/templates/post-message.sh +107 -0
  64. package/skills/agent-slackbot/templates/workspace-summary.sh +113 -0
  65. package/src/platforms/slackbot/cli.ts +30 -0
  66. package/src/platforms/slackbot/client.test.ts +282 -0
  67. package/src/platforms/slackbot/client.ts +401 -0
  68. package/src/platforms/slackbot/commands/auth.test.ts +245 -0
  69. package/src/platforms/slackbot/commands/auth.ts +240 -0
  70. package/src/platforms/slackbot/commands/channel.ts +46 -0
  71. package/src/platforms/slackbot/commands/index.ts +5 -0
  72. package/src/platforms/slackbot/commands/message.ts +182 -0
  73. package/src/platforms/slackbot/commands/reaction.ts +59 -0
  74. package/src/platforms/slackbot/commands/shared.ts +23 -0
  75. package/src/platforms/slackbot/commands/user.ts +46 -0
  76. package/src/platforms/slackbot/credential-manager.test.ts +264 -0
  77. package/src/platforms/slackbot/credential-manager.ts +218 -0
  78. package/src/platforms/slackbot/index.ts +19 -0
  79. package/src/platforms/slackbot/types.test.ts +90 -0
  80. package/src/platforms/slackbot/types.ts +222 -0
@@ -0,0 +1,264 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
2
+ import { existsSync, rmSync } from 'node:fs'
3
+ import { mkdir, stat } from 'node:fs/promises'
4
+ import { tmpdir } from 'node:os'
5
+ import { join } from 'node:path'
6
+ import { SlackBotCredentialManager } from './credential-manager'
7
+
8
+ const CREDS_A = {
9
+ token: 'xoxb-token-a',
10
+ workspace_id: 'T123',
11
+ workspace_name: 'Workspace A',
12
+ bot_id: 'deploy',
13
+ bot_name: 'Deploy Bot',
14
+ }
15
+
16
+ const CREDS_B = {
17
+ token: 'xoxb-token-b',
18
+ workspace_id: 'T123',
19
+ workspace_name: 'Workspace A',
20
+ bot_id: 'alert',
21
+ bot_name: 'Alert Bot',
22
+ }
23
+
24
+ const CREDS_C = {
25
+ token: 'xoxb-token-c',
26
+ workspace_id: 'T999',
27
+ workspace_name: 'Workspace B',
28
+ bot_id: 'deploy',
29
+ bot_name: 'Deploy Bot (B)',
30
+ }
31
+
32
+ describe('SlackBotCredentialManager', () => {
33
+ let tempDir: string
34
+ let manager: SlackBotCredentialManager
35
+
36
+ beforeEach(async () => {
37
+ tempDir = join(tmpdir(), `slackbot-cred-test-${Date.now()}`)
38
+ await mkdir(tempDir, { recursive: true })
39
+ manager = new SlackBotCredentialManager(tempDir)
40
+ })
41
+
42
+ afterEach(() => {
43
+ if (existsSync(tempDir)) {
44
+ rmSync(tempDir, { recursive: true })
45
+ }
46
+ delete process.env.E2E_SLACKBOT_TOKEN
47
+ delete process.env.E2E_SLACKBOT_WORKSPACE_ID
48
+ delete process.env.E2E_SLACKBOT_WORKSPACE_NAME
49
+ })
50
+
51
+ describe('load', () => {
52
+ test('returns empty config when no file exists', async () => {
53
+ const config = await manager.load()
54
+
55
+ expect(config.current).toBeNull()
56
+ expect(config.workspaces).toEqual({})
57
+ })
58
+ })
59
+
60
+ describe('save and load', () => {
61
+ test('persists config to file', async () => {
62
+ const config = {
63
+ current: { workspace_id: 'T123', bot_id: 'deploy' },
64
+ workspaces: {
65
+ T123: {
66
+ workspace_id: 'T123',
67
+ workspace_name: 'Test Workspace',
68
+ bots: {
69
+ deploy: { bot_id: 'deploy', bot_name: 'Deploy Bot', token: 'xoxb-test-token' },
70
+ },
71
+ },
72
+ },
73
+ }
74
+
75
+ await manager.save(config)
76
+ const loaded = await manager.load()
77
+
78
+ expect(loaded).toEqual(config)
79
+ })
80
+ })
81
+
82
+ describe('getCredentials', () => {
83
+ test('returns null when no credentials exist', async () => {
84
+ expect(await manager.getCredentials()).toBeNull()
85
+ })
86
+
87
+ test('returns current bot credentials', async () => {
88
+ await manager.setCredentials(CREDS_A)
89
+
90
+ const creds = await manager.getCredentials()
91
+
92
+ expect(creds).toEqual(CREDS_A)
93
+ })
94
+
95
+ test('returns specific bot by id', async () => {
96
+ await manager.setCredentials(CREDS_A)
97
+ await manager.setCredentials(CREDS_B)
98
+
99
+ const creds = await manager.getCredentials('deploy')
100
+
101
+ expect(creds).toEqual(CREDS_A)
102
+ })
103
+
104
+ test('returns specific bot by workspace_id/bot_id', async () => {
105
+ await manager.setCredentials(CREDS_A)
106
+ await manager.setCredentials(CREDS_C)
107
+
108
+ const creds = await manager.getCredentials('T123/deploy')
109
+
110
+ expect(creds).toEqual(CREDS_A)
111
+ })
112
+
113
+ test('returns null for ambiguous bot_id across workspaces', async () => {
114
+ await manager.setCredentials(CREDS_A)
115
+ await manager.setCredentials(CREDS_C)
116
+
117
+ const creds = await manager.getCredentials('deploy')
118
+
119
+ expect(creds).toBeNull()
120
+ })
121
+
122
+ test('env vars take precedence over file', async () => {
123
+ await manager.setCredentials(CREDS_A)
124
+
125
+ process.env.E2E_SLACKBOT_TOKEN = 'xoxb-env-token'
126
+ process.env.E2E_SLACKBOT_WORKSPACE_ID = 'T789'
127
+ process.env.E2E_SLACKBOT_WORKSPACE_NAME = 'Env Workspace'
128
+
129
+ const creds = await manager.getCredentials()
130
+
131
+ expect(creds?.token).toBe('xoxb-env-token')
132
+ expect(creds?.workspace_id).toBe('T789')
133
+ expect(creds?.bot_id).toBe('env')
134
+ })
135
+ })
136
+
137
+ describe('setCredentials', () => {
138
+ test('stores bot and sets as current', async () => {
139
+ await manager.setCredentials(CREDS_A)
140
+
141
+ const config = await manager.load()
142
+ expect(config.current).toEqual({ workspace_id: 'T123', bot_id: 'deploy' })
143
+ expect(config.workspaces.T123.bots.deploy.token).toBe('xoxb-token-a')
144
+ })
145
+
146
+ test('stores multiple bots in same workspace', async () => {
147
+ await manager.setCredentials(CREDS_A)
148
+ await manager.setCredentials(CREDS_B)
149
+
150
+ const config = await manager.load()
151
+ expect(Object.keys(config.workspaces.T123.bots)).toEqual(['deploy', 'alert'])
152
+ expect(config.current).toEqual({ workspace_id: 'T123', bot_id: 'alert' })
153
+ })
154
+
155
+ test('stores bots across workspaces', async () => {
156
+ await manager.setCredentials(CREDS_A)
157
+ await manager.setCredentials(CREDS_C)
158
+
159
+ const config = await manager.load()
160
+ expect(Object.keys(config.workspaces)).toEqual(['T123', 'T999'])
161
+ })
162
+ })
163
+
164
+ describe('listAll', () => {
165
+ test('returns all bots with current flag', async () => {
166
+ await manager.setCredentials(CREDS_A)
167
+ await manager.setCredentials(CREDS_B)
168
+
169
+ const all = await manager.listAll()
170
+
171
+ expect(all).toHaveLength(2)
172
+ expect(all.find((b) => b.bot_id === 'deploy')?.is_current).toBe(false)
173
+ expect(all.find((b) => b.bot_id === 'alert')?.is_current).toBe(true)
174
+ })
175
+ })
176
+
177
+ describe('setCurrent', () => {
178
+ test('switches current bot', async () => {
179
+ await manager.setCredentials(CREDS_A)
180
+ await manager.setCredentials(CREDS_B)
181
+
182
+ const switched = await manager.setCurrent('deploy')
183
+
184
+ expect(switched).toBe(true)
185
+ const creds = await manager.getCredentials()
186
+ expect(creds?.bot_id).toBe('deploy')
187
+ })
188
+
189
+ test('returns false for unknown bot', async () => {
190
+ expect(await manager.setCurrent('nonexistent')).toBe(false)
191
+ })
192
+ })
193
+
194
+ describe('removeBot', () => {
195
+ test('removes a bot by id', async () => {
196
+ await manager.setCredentials(CREDS_A)
197
+ await manager.setCredentials(CREDS_B)
198
+
199
+ const removed = await manager.removeBot('deploy')
200
+
201
+ expect(removed).toBe(true)
202
+ const config = await manager.load()
203
+ expect(Object.keys(config.workspaces.T123.bots)).toEqual(['alert'])
204
+ })
205
+
206
+ test('removes workspace when last bot removed', async () => {
207
+ await manager.setCredentials(CREDS_A)
208
+
209
+ await manager.removeBot('deploy')
210
+
211
+ const config = await manager.load()
212
+ expect(config.workspaces.T123).toBeUndefined()
213
+ })
214
+
215
+ test('clears current when current bot removed', async () => {
216
+ await manager.setCredentials(CREDS_A)
217
+
218
+ await manager.removeBot('deploy')
219
+
220
+ const config = await manager.load()
221
+ expect(config.current).toBeNull()
222
+ })
223
+
224
+ test('returns false for unknown bot', async () => {
225
+ expect(await manager.removeBot('nonexistent')).toBe(false)
226
+ })
227
+
228
+ test('returns false for ambiguous bot_id across workspaces', async () => {
229
+ await manager.setCredentials(CREDS_A)
230
+ await manager.setCredentials(CREDS_C)
231
+
232
+ const removed = await manager.removeBot('deploy')
233
+
234
+ expect(removed).toBe(false)
235
+ const config = await manager.load()
236
+ expect(config.workspaces.T123.bots.deploy).toBeDefined()
237
+ expect(config.workspaces.T999.bots.deploy).toBeDefined()
238
+ })
239
+ })
240
+
241
+ describe('clearCredentials', () => {
242
+ test('removes all credentials', async () => {
243
+ await manager.setCredentials(CREDS_A)
244
+ await manager.setCredentials(CREDS_B)
245
+
246
+ await manager.clearCredentials()
247
+
248
+ const config = await manager.load()
249
+ expect(config.current).toBeNull()
250
+ expect(config.workspaces).toEqual({})
251
+ })
252
+ })
253
+
254
+ describe('file permissions', () => {
255
+ test('saves file with secure permissions (600)', async () => {
256
+ await manager.setCredentials(CREDS_A)
257
+
258
+ const credPath = join(tempDir, 'slackbot-credentials.json')
259
+ const stats = await stat(credPath)
260
+
261
+ expect(stats.mode & 0o777).toBe(0o600)
262
+ })
263
+ })
264
+ })
@@ -0,0 +1,218 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises'
3
+ import { homedir } from 'node:os'
4
+ import { join } from 'node:path'
5
+ import type { SlackBotConfig, SlackBotCredentials, SlackBotWorkspace } from './types'
6
+
7
+ export class SlackBotCredentialManager {
8
+ private configDir: string
9
+ private credentialsPath: string
10
+
11
+ constructor(configDir?: string) {
12
+ this.configDir = configDir ?? join(homedir(), '.config', 'agent-messenger')
13
+ this.credentialsPath = join(this.configDir, 'slackbot-credentials.json')
14
+ }
15
+
16
+ async load(): Promise<SlackBotConfig> {
17
+ if (!existsSync(this.credentialsPath)) {
18
+ return { current: null, workspaces: {} }
19
+ }
20
+
21
+ const content = await readFile(this.credentialsPath, 'utf-8')
22
+ return JSON.parse(content) as SlackBotConfig
23
+ }
24
+
25
+ async save(config: SlackBotConfig): Promise<void> {
26
+ await mkdir(this.configDir, { recursive: true })
27
+ await writeFile(this.credentialsPath, JSON.stringify(config, null, 2))
28
+ await chmod(this.credentialsPath, 0o600)
29
+ }
30
+
31
+ async getCredentials(botId?: string): Promise<SlackBotCredentials | null> {
32
+ const config = await this.load()
33
+
34
+ if (botId) {
35
+ return this.findBot(config, botId)
36
+ }
37
+
38
+ const envToken = process.env.E2E_SLACKBOT_TOKEN
39
+ const envWorkspaceId = process.env.E2E_SLACKBOT_WORKSPACE_ID
40
+ const envWorkspaceName = process.env.E2E_SLACKBOT_WORKSPACE_NAME
41
+
42
+ if (envToken && envWorkspaceId && envWorkspaceName) {
43
+ return {
44
+ token: envToken,
45
+ workspace_id: envWorkspaceId,
46
+ workspace_name: envWorkspaceName,
47
+ bot_id: 'env',
48
+ bot_name: 'env',
49
+ }
50
+ }
51
+
52
+ if (!config.current) {
53
+ return null
54
+ }
55
+
56
+ const workspace = config.workspaces[config.current.workspace_id]
57
+ if (!workspace) return null
58
+
59
+ const bot = workspace.bots[config.current.bot_id]
60
+ if (!bot) return null
61
+
62
+ return {
63
+ token: bot.token,
64
+ workspace_id: workspace.workspace_id,
65
+ workspace_name: workspace.workspace_name,
66
+ bot_id: bot.bot_id,
67
+ bot_name: bot.bot_name,
68
+ }
69
+ }
70
+
71
+ private findBot(config: SlackBotConfig, botId: string): SlackBotCredentials | null {
72
+ // Try "workspace_id/bot_id" format first
73
+ if (botId.includes('/')) {
74
+ const [workspaceId, id] = botId.split('/')
75
+ const workspace = config.workspaces[workspaceId]
76
+ if (!workspace) return null
77
+ const bot = workspace.bots[id]
78
+ if (!bot) return null
79
+ return {
80
+ token: bot.token,
81
+ workspace_id: workspace.workspace_id,
82
+ workspace_name: workspace.workspace_name,
83
+ bot_id: bot.bot_id,
84
+ bot_name: bot.bot_name,
85
+ }
86
+ }
87
+
88
+ // Search by bot_id across all workspaces — must be unique
89
+ const matches: SlackBotCredentials[] = []
90
+ for (const workspace of Object.values(config.workspaces)) {
91
+ const bot = workspace.bots[botId]
92
+ if (bot) {
93
+ matches.push({
94
+ token: bot.token,
95
+ workspace_id: workspace.workspace_id,
96
+ workspace_name: workspace.workspace_name,
97
+ bot_id: bot.bot_id,
98
+ bot_name: bot.bot_name,
99
+ })
100
+ }
101
+ }
102
+
103
+ if (matches.length === 1) return matches[0]
104
+ return null
105
+ }
106
+
107
+ async setCredentials(creds: SlackBotCredentials): Promise<void> {
108
+ const config = await this.load()
109
+
110
+ if (!config.workspaces[creds.workspace_id]) {
111
+ config.workspaces[creds.workspace_id] = {
112
+ workspace_id: creds.workspace_id,
113
+ workspace_name: creds.workspace_name,
114
+ bots: {},
115
+ }
116
+ }
117
+
118
+ const workspace = config.workspaces[creds.workspace_id]
119
+ workspace.workspace_name = creds.workspace_name
120
+ workspace.bots[creds.bot_id] = {
121
+ bot_id: creds.bot_id,
122
+ bot_name: creds.bot_name,
123
+ token: creds.token,
124
+ }
125
+
126
+ config.current = {
127
+ workspace_id: creds.workspace_id,
128
+ bot_id: creds.bot_id,
129
+ }
130
+
131
+ await this.save(config)
132
+ }
133
+
134
+ async removeBot(botId: string): Promise<boolean> {
135
+ const config = await this.load()
136
+
137
+ if (botId.includes('/')) {
138
+ const [workspaceId, id] = botId.split('/')
139
+ const workspace = config.workspaces[workspaceId]
140
+ if (!workspace || !workspace.bots[id]) return false
141
+
142
+ delete workspace.bots[id]
143
+ if (Object.keys(workspace.bots).length === 0) {
144
+ delete config.workspaces[workspaceId]
145
+ }
146
+
147
+ if (config.current?.workspace_id === workspaceId && config.current?.bot_id === id) {
148
+ config.current = null
149
+ }
150
+
151
+ await this.save(config)
152
+ return true
153
+ }
154
+
155
+ const matches: { workspace: SlackBotWorkspace }[] = []
156
+ for (const workspace of Object.values(config.workspaces)) {
157
+ if (workspace.bots[botId]) {
158
+ matches.push({ workspace })
159
+ }
160
+ }
161
+
162
+ if (matches.length !== 1) return false
163
+
164
+ const { workspace } = matches[0]
165
+ delete workspace.bots[botId]
166
+ if (Object.keys(workspace.bots).length === 0) {
167
+ delete config.workspaces[workspace.workspace_id]
168
+ }
169
+ if (
170
+ config.current?.workspace_id === workspace.workspace_id &&
171
+ config.current?.bot_id === botId
172
+ ) {
173
+ config.current = null
174
+ }
175
+ await this.save(config)
176
+ return true
177
+ }
178
+
179
+ async setCurrent(botId: string): Promise<boolean> {
180
+ const config = await this.load()
181
+ const creds = this.findBot(config, botId)
182
+ if (!creds) return false
183
+
184
+ config.current = {
185
+ workspace_id: creds.workspace_id,
186
+ bot_id: creds.bot_id,
187
+ }
188
+
189
+ await this.save(config)
190
+ return true
191
+ }
192
+
193
+ async listAll(): Promise<Array<SlackBotCredentials & { is_current: boolean }>> {
194
+ const config = await this.load()
195
+ const results: Array<SlackBotCredentials & { is_current: boolean }> = []
196
+
197
+ for (const workspace of Object.values(config.workspaces)) {
198
+ for (const bot of Object.values(workspace.bots)) {
199
+ results.push({
200
+ token: bot.token,
201
+ workspace_id: workspace.workspace_id,
202
+ workspace_name: workspace.workspace_name,
203
+ bot_id: bot.bot_id,
204
+ bot_name: bot.bot_name,
205
+ is_current:
206
+ config.current?.workspace_id === workspace.workspace_id &&
207
+ config.current?.bot_id === bot.bot_id,
208
+ })
209
+ }
210
+ }
211
+
212
+ return results
213
+ }
214
+
215
+ async clearCredentials(): Promise<void> {
216
+ await this.save({ current: null, workspaces: {} })
217
+ }
218
+ }
@@ -0,0 +1,19 @@
1
+ export { SlackBotClient } from './client'
2
+ export { SlackBotCredentialManager } from './credential-manager'
3
+ export {
4
+ SlackBotConfig,
5
+ SlackBotConfigSchema,
6
+ SlackBotCredentials,
7
+ SlackBotCredentialsSchema,
8
+ SlackBotError,
9
+ SlackChannel,
10
+ SlackChannelSchema,
11
+ SlackFile,
12
+ SlackFileSchema,
13
+ SlackMessage,
14
+ SlackMessageSchema,
15
+ SlackReaction,
16
+ SlackReactionSchema,
17
+ SlackUser,
18
+ SlackUserSchema,
19
+ } from './types'
@@ -0,0 +1,90 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import {
3
+ type SlackBotConfig,
4
+ SlackBotConfigSchema,
5
+ type SlackBotCredentials,
6
+ SlackBotCredentialsSchema,
7
+ SlackBotError,
8
+ } from './types'
9
+
10
+ describe('SlackBotError', () => {
11
+ test('creates error with message and code', () => {
12
+ const error = new SlackBotError('Token is invalid', 'invalid_auth')
13
+
14
+ expect(error.message).toBe('Token is invalid')
15
+ expect(error.code).toBe('invalid_auth')
16
+ expect(error.name).toBe('SlackBotError')
17
+ expect(error instanceof Error).toBe(true)
18
+ })
19
+ })
20
+
21
+ describe('SlackBotCredentialsSchema', () => {
22
+ test('validates correct bot token credentials', () => {
23
+ const creds: SlackBotCredentials = {
24
+ token: 'xoxb-123456789-abcdef',
25
+ workspace_id: 'T12345678',
26
+ workspace_name: 'test-workspace',
27
+ bot_id: 'deploy',
28
+ bot_name: 'Deploy Bot',
29
+ }
30
+
31
+ const result = SlackBotCredentialsSchema.safeParse(creds)
32
+ expect(result.success).toBe(true)
33
+ })
34
+
35
+ test('rejects user tokens (xoxp-)', () => {
36
+ const creds = {
37
+ token: 'xoxp-123456789-abcdef',
38
+ workspace_id: 'T12345678',
39
+ workspace_name: 'test-workspace',
40
+ bot_id: 'deploy',
41
+ bot_name: 'Deploy Bot',
42
+ }
43
+
44
+ const result = SlackBotCredentialsSchema.safeParse(creds)
45
+ expect(result.success).toBe(false)
46
+ })
47
+
48
+ test('rejects missing fields', () => {
49
+ const creds = {
50
+ token: 'xoxb-123456789-abcdef',
51
+ }
52
+
53
+ const result = SlackBotCredentialsSchema.safeParse(creds)
54
+ expect(result.success).toBe(false)
55
+ })
56
+ })
57
+
58
+ describe('SlackBotConfigSchema', () => {
59
+ test('validates correct config', () => {
60
+ const config: SlackBotConfig = {
61
+ current: { workspace_id: 'T12345678', bot_id: 'deploy' },
62
+ workspaces: {
63
+ T12345678: {
64
+ workspace_id: 'T12345678',
65
+ workspace_name: 'test-workspace',
66
+ bots: {
67
+ deploy: {
68
+ bot_id: 'deploy',
69
+ bot_name: 'Deploy Bot',
70
+ token: 'xoxb-123456789-abcdef',
71
+ },
72
+ },
73
+ },
74
+ },
75
+ }
76
+
77
+ const result = SlackBotConfigSchema.safeParse(config)
78
+ expect(result.success).toBe(true)
79
+ })
80
+
81
+ test('validates config with null current', () => {
82
+ const config: SlackBotConfig = {
83
+ current: null,
84
+ workspaces: {},
85
+ }
86
+
87
+ const result = SlackBotConfigSchema.safeParse(config)
88
+ expect(result.success).toBe(true)
89
+ })
90
+ })