agent-messenger 1.3.6 → 1.5.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 (127) 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/cli.d.ts.map +1 -1
  8. package/dist/src/cli.js +3 -0
  9. package/dist/src/cli.js.map +1 -1
  10. package/dist/src/platforms/discord/cli.d.ts +2 -2
  11. package/dist/src/platforms/discord/cli.d.ts.map +1 -1
  12. package/dist/src/platforms/discord/cli.js +23 -1
  13. package/dist/src/platforms/discord/cli.js.map +1 -1
  14. package/dist/src/platforms/discord/commands/file.d.ts.map +1 -1
  15. package/dist/src/platforms/discord/commands/file.js +13 -7
  16. package/dist/src/platforms/discord/commands/file.js.map +1 -1
  17. package/dist/src/platforms/discord/commands/friend.d.ts.map +1 -1
  18. package/dist/src/platforms/discord/commands/friend.js +30 -30
  19. package/dist/src/platforms/discord/commands/friend.js.map +1 -1
  20. package/dist/src/platforms/discord/commands/index.d.ts +7 -0
  21. package/dist/src/platforms/discord/commands/index.d.ts.map +1 -1
  22. package/dist/src/platforms/discord/commands/index.js +7 -0
  23. package/dist/src/platforms/discord/commands/index.js.map +1 -1
  24. package/dist/src/platforms/discord/commands/snapshot.d.ts.map +1 -1
  25. package/dist/src/platforms/discord/commands/snapshot.js +1 -2
  26. package/dist/src/platforms/discord/commands/snapshot.js.map +1 -1
  27. package/dist/src/platforms/discord/ensure-auth.d.ts +2 -0
  28. package/dist/src/platforms/discord/ensure-auth.d.ts.map +1 -0
  29. package/dist/src/platforms/discord/ensure-auth.js +31 -0
  30. package/dist/src/platforms/discord/ensure-auth.js.map +1 -0
  31. package/dist/src/platforms/slack/cli.d.ts +2 -2
  32. package/dist/src/platforms/slack/cli.d.ts.map +1 -1
  33. package/dist/src/platforms/slack/cli.js +15 -0
  34. package/dist/src/platforms/slack/cli.js.map +1 -1
  35. package/dist/src/platforms/slack/client.d.ts +1 -0
  36. package/dist/src/platforms/slack/client.d.ts.map +1 -1
  37. package/dist/src/platforms/slack/client.js +13 -0
  38. package/dist/src/platforms/slack/client.js.map +1 -1
  39. package/dist/src/platforms/slack/commands/channel.d.ts.map +1 -1
  40. package/dist/src/platforms/slack/commands/channel.js +2 -0
  41. package/dist/src/platforms/slack/commands/channel.js.map +1 -1
  42. package/dist/src/platforms/slack/commands/file.d.ts.map +1 -1
  43. package/dist/src/platforms/slack/commands/file.js +13 -5
  44. package/dist/src/platforms/slack/commands/file.js.map +1 -1
  45. package/dist/src/platforms/slack/commands/message.d.ts.map +1 -1
  46. package/dist/src/platforms/slack/commands/message.js +12 -6
  47. package/dist/src/platforms/slack/commands/message.js.map +1 -1
  48. package/dist/src/platforms/slack/commands/reaction.d.ts.map +1 -1
  49. package/dist/src/platforms/slack/commands/reaction.js +3 -0
  50. package/dist/src/platforms/slack/commands/reaction.js.map +1 -1
  51. package/dist/src/platforms/slack/commands/sections.d.ts.map +1 -1
  52. package/dist/src/platforms/slack/commands/sections.js +5 -6
  53. package/dist/src/platforms/slack/commands/sections.js.map +1 -1
  54. package/dist/src/platforms/slack/commands/snapshot.d.ts.map +1 -1
  55. package/dist/src/platforms/slack/commands/snapshot.js +1 -2
  56. package/dist/src/platforms/slack/commands/snapshot.js.map +1 -1
  57. package/dist/src/platforms/slack/commands/unread.d.ts.map +1 -1
  58. package/dist/src/platforms/slack/commands/unread.js +2 -0
  59. package/dist/src/platforms/slack/commands/unread.js.map +1 -1
  60. package/dist/src/platforms/slack/commands/user.js +8 -8
  61. package/dist/src/platforms/slack/ensure-auth.d.ts +2 -0
  62. package/dist/src/platforms/slack/ensure-auth.d.ts.map +1 -0
  63. package/dist/src/platforms/slack/ensure-auth.js +30 -0
  64. package/dist/src/platforms/slack/ensure-auth.js.map +1 -0
  65. package/dist/src/platforms/slackbot/client.d.ts +1 -0
  66. package/dist/src/platforms/slackbot/client.d.ts.map +1 -1
  67. package/dist/src/platforms/slackbot/client.js +13 -0
  68. package/dist/src/platforms/slackbot/client.js.map +1 -1
  69. package/dist/src/platforms/slackbot/commands/channel.d.ts.map +1 -1
  70. package/dist/src/platforms/slackbot/commands/channel.js +3 -2
  71. package/dist/src/platforms/slackbot/commands/channel.js.map +1 -1
  72. package/dist/src/platforms/slackbot/commands/message.d.ts.map +1 -1
  73. package/dist/src/platforms/slackbot/commands/message.js +18 -12
  74. package/dist/src/platforms/slackbot/commands/message.js.map +1 -1
  75. package/dist/src/platforms/slackbot/commands/reaction.d.ts.map +1 -1
  76. package/dist/src/platforms/slackbot/commands/reaction.js +8 -6
  77. package/dist/src/platforms/slackbot/commands/reaction.js.map +1 -1
  78. package/dist/src/platforms/teams/cli.d.ts +2 -2
  79. package/dist/src/platforms/teams/cli.d.ts.map +1 -1
  80. package/dist/src/platforms/teams/cli.js +15 -0
  81. package/dist/src/platforms/teams/cli.js.map +1 -1
  82. package/dist/src/platforms/teams/commands/file.js +12 -12
  83. package/dist/src/platforms/teams/commands/file.js.map +1 -1
  84. package/dist/src/platforms/teams/commands/snapshot.d.ts.map +1 -1
  85. package/dist/src/platforms/teams/commands/snapshot.js +1 -2
  86. package/dist/src/platforms/teams/commands/snapshot.js.map +1 -1
  87. package/dist/src/platforms/teams/ensure-auth.d.ts +2 -0
  88. package/dist/src/platforms/teams/ensure-auth.d.ts.map +1 -0
  89. package/dist/src/platforms/teams/ensure-auth.js +32 -0
  90. package/dist/src/platforms/teams/ensure-auth.js.map +1 -0
  91. package/e2e/README.md +1 -1
  92. package/package.json +1 -1
  93. package/skills/agent-discord/SKILL.md +22 -22
  94. package/skills/agent-slack/SKILL.md +28 -40
  95. package/skills/agent-teams/SKILL.md +41 -65
  96. package/skills/agent-teams/references/common-patterns.md +63 -49
  97. package/src/cli.ts +4 -0
  98. package/src/platforms/discord/cli.ts +30 -0
  99. package/src/platforms/discord/commands/file.ts +13 -7
  100. package/src/platforms/discord/commands/friend.ts +34 -34
  101. package/src/platforms/discord/commands/index.ts +7 -0
  102. package/src/platforms/discord/commands/snapshot.ts +1 -2
  103. package/src/platforms/discord/ensure-auth.test.ts +123 -0
  104. package/src/platforms/discord/ensure-auth.ts +31 -0
  105. package/src/platforms/slack/cli.ts +16 -0
  106. package/src/platforms/slack/client.test.ts +101 -0
  107. package/src/platforms/slack/client.ts +22 -0
  108. package/src/platforms/slack/commands/channel.ts +2 -0
  109. package/src/platforms/slack/commands/file.ts +15 -5
  110. package/src/platforms/slack/commands/message.ts +17 -6
  111. package/src/platforms/slack/commands/reaction.ts +3 -0
  112. package/src/platforms/slack/commands/sections.ts +8 -9
  113. package/src/platforms/slack/commands/snapshot.ts +1 -2
  114. package/src/platforms/slack/commands/unread.ts +2 -0
  115. package/src/platforms/slack/commands/user.ts +8 -8
  116. package/src/platforms/slack/ensure-auth.test.ts +186 -0
  117. package/src/platforms/slack/ensure-auth.ts +30 -0
  118. package/src/platforms/slackbot/client.test.ts +87 -0
  119. package/src/platforms/slackbot/client.ts +21 -0
  120. package/src/platforms/slackbot/commands/channel.ts +3 -2
  121. package/src/platforms/slackbot/commands/message.ts +18 -12
  122. package/src/platforms/slackbot/commands/reaction.ts +8 -6
  123. package/src/platforms/teams/cli.ts +16 -0
  124. package/src/platforms/teams/commands/file.ts +12 -12
  125. package/src/platforms/teams/commands/snapshot.ts +1 -2
  126. package/src/platforms/teams/ensure-auth.test.ts +167 -0
  127. package/src/platforms/teams/ensure-auth.ts +34 -0
@@ -0,0 +1,123 @@
1
+ import { afterEach, beforeEach, describe, expect, spyOn, test } from 'bun:test'
2
+ import { DiscordClient } from './client'
3
+ import { DiscordCredentialManager } from './credential-manager'
4
+ import { ensureDiscordAuth } from './ensure-auth'
5
+ import { DiscordTokenExtractor } from './token-extractor'
6
+
7
+ let getTokenSpy: ReturnType<typeof spyOn>
8
+ let extractSpy: ReturnType<typeof spyOn>
9
+ let testAuthSpy: ReturnType<typeof spyOn>
10
+ let listServersSpy: ReturnType<typeof spyOn>
11
+ let saveSpy: ReturnType<typeof spyOn>
12
+
13
+ beforeEach(() => {
14
+ getTokenSpy = spyOn(DiscordCredentialManager.prototype, 'getToken').mockResolvedValue(null)
15
+
16
+ extractSpy = spyOn(DiscordTokenExtractor.prototype, 'extract').mockResolvedValue({
17
+ token: 'test-token-123',
18
+ })
19
+
20
+ testAuthSpy = spyOn(DiscordClient.prototype, 'testAuth').mockResolvedValue({
21
+ id: 'user-123',
22
+ username: 'testuser',
23
+ })
24
+
25
+ listServersSpy = spyOn(DiscordClient.prototype, 'listServers').mockResolvedValue([
26
+ { id: 'server-1', name: 'Server One' },
27
+ { id: 'server-2', name: 'Server Two' },
28
+ ])
29
+
30
+ saveSpy = spyOn(DiscordCredentialManager.prototype, 'save').mockResolvedValue(undefined)
31
+ })
32
+
33
+ afterEach(() => {
34
+ getTokenSpy?.mockRestore()
35
+ extractSpy?.mockRestore()
36
+ testAuthSpy?.mockRestore()
37
+ listServersSpy?.mockRestore()
38
+ saveSpy?.mockRestore()
39
+ })
40
+
41
+ describe('ensureDiscordAuth', () => {
42
+ test('skips extraction when token already exists', async () => {
43
+ // given
44
+ getTokenSpy.mockResolvedValue('existing-token')
45
+
46
+ // when
47
+ await ensureDiscordAuth()
48
+
49
+ // then
50
+ expect(extractSpy).not.toHaveBeenCalled()
51
+ })
52
+
53
+ test('extracts and saves credentials when no token', async () => {
54
+ // when
55
+ await ensureDiscordAuth()
56
+
57
+ // then
58
+ expect(extractSpy).toHaveBeenCalled()
59
+ expect(testAuthSpy).toHaveBeenCalled()
60
+ expect(listServersSpy).toHaveBeenCalled()
61
+ expect(saveSpy).toHaveBeenCalledWith({
62
+ token: 'test-token-123',
63
+ current_server: 'server-1',
64
+ servers: {
65
+ 'server-1': { server_id: 'server-1', server_name: 'Server One' },
66
+ 'server-2': { server_id: 'server-2', server_name: 'Server Two' },
67
+ },
68
+ })
69
+ })
70
+
71
+ test('sets first server as current', async () => {
72
+ // when
73
+ await ensureDiscordAuth()
74
+
75
+ // then
76
+ expect(saveSpy).toHaveBeenCalledWith(expect.objectContaining({ current_server: 'server-1' }))
77
+ })
78
+
79
+ test('handles no servers with null current_server', async () => {
80
+ // given
81
+ listServersSpy.mockResolvedValue([])
82
+
83
+ // when
84
+ await ensureDiscordAuth()
85
+
86
+ // then
87
+ expect(saveSpy).toHaveBeenCalledWith(expect.objectContaining({ current_server: null, servers: {} }))
88
+ })
89
+
90
+ test('does not save when extraction returns null', async () => {
91
+ // given
92
+ extractSpy.mockResolvedValue(null)
93
+
94
+ // when
95
+ await ensureDiscordAuth()
96
+
97
+ // then
98
+ expect(testAuthSpy).not.toHaveBeenCalled()
99
+ expect(saveSpy).not.toHaveBeenCalled()
100
+ })
101
+
102
+ test('silently handles extraction failure', async () => {
103
+ // given
104
+ extractSpy.mockRejectedValue(new Error('Discord not found'))
105
+
106
+ // when
107
+ await ensureDiscordAuth()
108
+
109
+ // then
110
+ expect(saveSpy).not.toHaveBeenCalled()
111
+ })
112
+
113
+ test('silently handles auth validation failure', async () => {
114
+ // given
115
+ testAuthSpy.mockRejectedValue(new Error('401 Unauthorized'))
116
+
117
+ // when
118
+ await ensureDiscordAuth()
119
+
120
+ // then
121
+ expect(saveSpy).not.toHaveBeenCalled()
122
+ })
123
+ })
@@ -0,0 +1,31 @@
1
+ import { DiscordClient } from './client'
2
+ import { DiscordCredentialManager } from './credential-manager'
3
+ import { DiscordTokenExtractor } from './token-extractor'
4
+
5
+ export async function ensureDiscordAuth(): Promise<void> {
6
+ try {
7
+ const credManager = new DiscordCredentialManager()
8
+ const token = await credManager.getToken()
9
+ if (token) return
10
+
11
+ const extractor = new DiscordTokenExtractor()
12
+ const extracted = await extractor.extract()
13
+ if (!extracted) return
14
+
15
+ const client = new DiscordClient(extracted.token)
16
+ const authInfo = await client.testAuth()
17
+ if (!authInfo) return
18
+
19
+ const servers = await client.listServers()
20
+ const serverMap: Record<string, { server_id: string; server_name: string }> = {}
21
+ for (const server of servers) {
22
+ serverMap[server.id] = { server_id: server.id, server_name: server.name }
23
+ }
24
+
25
+ await credManager.save({
26
+ token: extracted.token,
27
+ current_server: servers[0]?.id ?? null,
28
+ servers: serverMap,
29
+ })
30
+ } catch {}
31
+ }
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
+ import type { Command as CommandType } from 'commander'
3
4
  import { Command } from 'commander'
4
5
  import pkg from '../../../package.json' with { type: 'json' }
5
6
  import {
@@ -17,6 +18,16 @@ import {
17
18
  userCommand,
18
19
  workspaceCommand,
19
20
  } from './commands/index'
21
+ import { ensureSlackAuth } from './ensure-auth'
22
+
23
+ function isAuthCommand(command: CommandType): boolean {
24
+ let cmd: CommandType | null = command
25
+ while (cmd) {
26
+ if (cmd.name() === 'auth') return true
27
+ cmd = cmd.parent
28
+ }
29
+ return false
30
+ }
20
31
 
21
32
  const program = new Command()
22
33
 
@@ -27,6 +38,11 @@ program
27
38
  .option('--pretty', 'Pretty-print JSON output')
28
39
  .option('--workspace <id>', 'Use specific workspace')
29
40
 
41
+ program.hook('preAction', async (_thisCommand, actionCommand) => {
42
+ if (isAuthCommand(actionCommand)) return
43
+ await ensureSlackAuth()
44
+ })
45
+
30
46
  program.addCommand(authCommand)
31
47
  program.addCommand(workspaceCommand)
32
48
  program.addCommand(messageCommand)
@@ -226,6 +226,107 @@ describe('SlackClient', () => {
226
226
  })
227
227
  })
228
228
 
229
+ describe('resolveChannel', () => {
230
+ beforeEach(() => resetMocks())
231
+
232
+ test('returns channel ID unchanged when input starts with C', async () => {
233
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
234
+ const channel = await client.resolveChannel('C123ABC')
235
+ expect(channel).toBe('C123ABC')
236
+ })
237
+
238
+ test('returns channel ID unchanged when input starts with D', async () => {
239
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
240
+ const channel = await client.resolveChannel('D123ABC')
241
+ expect(channel).toBe('D123ABC')
242
+ })
243
+
244
+ test('returns channel ID unchanged when input starts with G', async () => {
245
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
246
+ const channel = await client.resolveChannel('G123ABC')
247
+ expect(channel).toBe('G123ABC')
248
+ })
249
+
250
+ test('resolves channel name to ID by calling listChannels', async () => {
251
+ mockWebClient.conversations.list.mockResolvedValue({
252
+ ok: true,
253
+ channels: [
254
+ {
255
+ id: 'C123',
256
+ name: 'general',
257
+ is_private: false,
258
+ is_archived: false,
259
+ created: 1234567890,
260
+ creator: 'U123',
261
+ },
262
+ ],
263
+ })
264
+
265
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
266
+ // @ts-expect-error - accessing private property for testing
267
+ client.client = mockWebClient as unknown as WebClient
268
+
269
+ const channel = await client.resolveChannel('general')
270
+ expect(channel).toBe('C123')
271
+ expect(mockWebClient.conversations.list).toHaveBeenCalledTimes(1)
272
+ })
273
+
274
+ test('strips leading # from channel name', async () => {
275
+ mockWebClient.conversations.list.mockResolvedValue({
276
+ ok: true,
277
+ channels: [
278
+ {
279
+ id: 'C123',
280
+ name: 'general',
281
+ is_private: false,
282
+ is_archived: false,
283
+ created: 1234567890,
284
+ creator: 'U123',
285
+ },
286
+ ],
287
+ })
288
+
289
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
290
+ // @ts-expect-error - accessing private property for testing
291
+ client.client = mockWebClient as unknown as WebClient
292
+
293
+ const channel = await client.resolveChannel('#general')
294
+ expect(channel).toBe('C123')
295
+ expect(mockWebClient.conversations.list).toHaveBeenCalledTimes(1)
296
+ })
297
+
298
+ test('returns channel ID unchanged when input is #C prefixed ID', async () => {
299
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
300
+ const channel = await client.resolveChannel('#C123ABC')
301
+ expect(channel).toBe('C123ABC')
302
+ })
303
+
304
+ test("throws SlackError with code 'channel_not_found' when name is not found", async () => {
305
+ mockWebClient.conversations.list.mockResolvedValue({
306
+ ok: true,
307
+ channels: [
308
+ {
309
+ id: 'C123',
310
+ name: 'general',
311
+ is_private: false,
312
+ is_archived: false,
313
+ created: 1234567890,
314
+ creator: 'U123',
315
+ },
316
+ ],
317
+ })
318
+
319
+ const client = new SlackClient('xoxc-token', 'xoxd-cookie')
320
+ // @ts-expect-error - accessing private property for testing
321
+ client.client = mockWebClient as unknown as WebClient
322
+
323
+ await expect(client.resolveChannel('missing-channel')).rejects.toMatchObject({
324
+ name: 'SlackError',
325
+ code: 'channel_not_found',
326
+ })
327
+ })
328
+ })
329
+
229
330
  describe('getMessages', () => {
230
331
  beforeEach(() => resetMocks())
231
332
 
@@ -161,6 +161,28 @@ export class SlackClient {
161
161
  })
162
162
  }
163
163
 
164
+ async resolveChannel(channel: string): Promise<string> {
165
+ const normalized = channel.replace(/^#/, '')
166
+
167
+ if (/^[CDG][A-Z0-9]+$/.test(normalized)) {
168
+ return normalized
169
+ }
170
+
171
+ const name = normalized
172
+
173
+ const channels = await this.listChannels()
174
+ const found = channels.find((ch) => ch.name === name)
175
+
176
+ if (!found) {
177
+ throw new SlackError(
178
+ `Channel not found: "${channel}". Use channel ID or exact channel name.`,
179
+ 'channel_not_found',
180
+ )
181
+ }
182
+
183
+ return found.id
184
+ }
185
+
164
186
  async sendMessage(channel: string, text: string, threadTs?: string): Promise<SlackMessage> {
165
187
  return this.withRetry(async () => {
166
188
  const response = await this.client.chat.postMessage({
@@ -56,6 +56,7 @@ async function infoAction(channel: string, options: { pretty?: boolean }): Promi
56
56
  }
57
57
 
58
58
  const client = new SlackClient(workspace.token, workspace.cookie)
59
+ channel = await client.resolveChannel(channel)
59
60
  const ch = await client.getChannel(channel)
60
61
 
61
62
  const output = {
@@ -86,6 +87,7 @@ async function historyAction(channel: string, options: { limit?: number; pretty?
86
87
  }
87
88
 
88
89
  const client = new SlackClient(workspace.token, workspace.cookie)
90
+ channel = await client.resolveChannel(channel)
89
91
  const messages = await client.getMessages(channel, options.limit || 20)
90
92
 
91
93
  const output = messages.map((msg) => ({
@@ -21,6 +21,7 @@ async function uploadAction(
21
21
  }
22
22
 
23
23
  const client = new SlackClient(workspace.token, workspace.cookie)
24
+ channel = await client.resolveChannel(channel)
24
25
 
25
26
  const filePath = resolve(path)
26
27
  const fileBuffer = readFileSync(filePath)
@@ -57,7 +58,8 @@ async function listAction(options: { channel?: string; pretty?: boolean }): Prom
57
58
  }
58
59
 
59
60
  const client = new SlackClient(workspace.token, workspace.cookie)
60
- const files = await client.listFiles(options.channel)
61
+ const channel = options.channel ? await client.resolveChannel(options.channel) : undefined
62
+ const files = await client.listFiles(channel)
61
63
 
62
64
  const output = files.map((file) => ({
63
65
  id: file.id,
@@ -115,19 +117,27 @@ async function infoAction(fileId: string, options: { pretty?: boolean }): Promis
115
117
  }
116
118
 
117
119
  export const fileCommand = new Command('file')
118
- .description('file commands')
120
+ .description('File commands')
119
121
  .addCommand(
120
122
  new Command('upload')
121
- .description('upload file to channel')
123
+ .description('Upload file to channel')
122
124
  .argument('<channel>', 'channel ID or name')
123
125
  .argument('<path>', 'file path')
124
126
  .option('--filename <name>', 'override filename')
127
+ .option('--pretty', 'Pretty print JSON output')
125
128
  .action(uploadAction),
126
129
  )
127
130
  .addCommand(
128
131
  new Command('list')
129
- .description('list files in workspace')
132
+ .description('List files in workspace')
130
133
  .option('--channel <id>', 'filter by channel')
134
+ .option('--pretty', 'Pretty print JSON output')
131
135
  .action(listAction),
132
136
  )
133
- .addCommand(new Command('info').description('show file details').argument('<file>', 'file ID').action(infoAction))
137
+ .addCommand(
138
+ new Command('info')
139
+ .description('Show file details')
140
+ .argument('<file>', 'file ID')
141
+ .option('--pretty', 'Pretty print JSON output')
142
+ .action(infoAction),
143
+ )
@@ -6,7 +6,7 @@ import { CredentialManager } from '../credential-manager'
6
6
  import type { SlackMessage } from '../types'
7
7
 
8
8
  async function sendAction(
9
- channel: string,
9
+ channelInput: string,
10
10
  text: string,
11
11
  options: { thread?: string; pretty?: boolean },
12
12
  ): Promise<void> {
@@ -20,6 +20,7 @@ async function sendAction(
20
20
  }
21
21
 
22
22
  const client = new SlackClient(workspace.token, workspace.cookie)
23
+ const channel = await client.resolveChannel(channelInput)
23
24
  const message = await client.sendMessage(channel, text, options.thread)
24
25
 
25
26
  const output = {
@@ -37,7 +38,7 @@ async function sendAction(
37
38
  }
38
39
 
39
40
  async function listAction(
40
- channel: string,
41
+ channelInput: string,
41
42
  options: { limit?: number; thread?: string; pretty?: boolean },
42
43
  ): Promise<void> {
43
44
  try {
@@ -50,6 +51,7 @@ async function listAction(
50
51
  }
51
52
 
52
53
  const client = new SlackClient(workspace.token, workspace.cookie)
54
+ const channel = await client.resolveChannel(channelInput)
53
55
  const limit = options.limit || 20
54
56
  const messages = await client.getMessages(channel, limit)
55
57
 
@@ -70,7 +72,7 @@ async function listAction(
70
72
  }
71
73
  }
72
74
 
73
- async function getAction(channel: string, ts: string, options: { pretty?: boolean }): Promise<void> {
75
+ async function getAction(channelInput: string, ts: string, options: { pretty?: boolean }): Promise<void> {
74
76
  try {
75
77
  const credManager = new CredentialManager()
76
78
  const workspace = await credManager.getWorkspace()
@@ -81,6 +83,7 @@ async function getAction(channel: string, ts: string, options: { pretty?: boolea
81
83
  }
82
84
 
83
85
  const client = new SlackClient(workspace.token, workspace.cookie)
86
+ const channel = await client.resolveChannel(channelInput)
84
87
  const message = await client.getMessage(channel, ts)
85
88
 
86
89
  if (!message) {
@@ -105,7 +108,12 @@ async function getAction(channel: string, ts: string, options: { pretty?: boolea
105
108
  }
106
109
  }
107
110
 
108
- async function updateAction(channel: string, ts: string, text: string, options: { pretty?: boolean }): Promise<void> {
111
+ async function updateAction(
112
+ channelInput: string,
113
+ ts: string,
114
+ text: string,
115
+ options: { pretty?: boolean },
116
+ ): Promise<void> {
109
117
  try {
110
118
  const credManager = new CredentialManager()
111
119
  const workspace = await credManager.getWorkspace()
@@ -116,6 +124,7 @@ async function updateAction(channel: string, ts: string, text: string, options:
116
124
  }
117
125
 
118
126
  const client = new SlackClient(workspace.token, workspace.cookie)
127
+ const channel = await client.resolveChannel(channelInput)
119
128
  const message = await client.updateMessage(channel, ts, text)
120
129
 
121
130
  const output = {
@@ -132,7 +141,7 @@ async function updateAction(channel: string, ts: string, text: string, options:
132
141
  }
133
142
 
134
143
  async function deleteAction(
135
- channel: string,
144
+ channelInput: string,
136
145
  ts: string,
137
146
  options: { force?: boolean; pretty?: boolean },
138
147
  ): Promise<void> {
@@ -151,6 +160,7 @@ async function deleteAction(
151
160
  }
152
161
 
153
162
  const client = new SlackClient(workspace.token, workspace.cookie)
163
+ const channel = await client.resolveChannel(channelInput)
154
164
  await client.deleteMessage(channel, ts)
155
165
 
156
166
  console.log(formatOutput({ deleted: ts }, options.pretty))
@@ -196,7 +206,7 @@ async function searchAction(
196
206
  }
197
207
 
198
208
  async function repliesAction(
199
- channel: string,
209
+ channelInput: string,
200
210
  threadTs: string,
201
211
  options: { limit?: number; oldest?: string; latest?: string; cursor?: string; pretty?: boolean },
202
212
  ): Promise<void> {
@@ -210,6 +220,7 @@ async function repliesAction(
210
220
  }
211
221
 
212
222
  const client = new SlackClient(workspace.token, workspace.cookie)
223
+ const channel = await client.resolveChannel(channelInput)
213
224
  const result = await client.getThreadReplies(channel, threadTs, {
214
225
  limit: options.limit,
215
226
  oldest: options.oldest,
@@ -15,6 +15,7 @@ async function addAction(channel: string, ts: string, emoji: string, options: {
15
15
  }
16
16
 
17
17
  const client = new SlackClient(ws.token, ws.cookie)
18
+ channel = await client.resolveChannel(channel)
18
19
  await client.addReaction(channel, ts, emoji)
19
20
 
20
21
  console.log(
@@ -44,6 +45,7 @@ async function removeAction(channel: string, ts: string, emoji: string, options:
44
45
  }
45
46
 
46
47
  const client = new SlackClient(ws.token, ws.cookie)
48
+ channel = await client.resolveChannel(channel)
47
49
  await client.removeReaction(channel, ts, emoji)
48
50
 
49
51
  console.log(
@@ -73,6 +75,7 @@ async function listAction(channel: string, ts: string, options: { pretty?: boole
73
75
  }
74
76
 
75
77
  const client = new SlackClient(ws.token, ws.cookie)
78
+ channel = await client.resolveChannel(channel)
76
79
  const message = await client.getMessage(channel, ts)
77
80
 
78
81
  if (!message) {
@@ -32,12 +32,11 @@ async function listAction(options: { pretty?: boolean }): Promise<void> {
32
32
  }
33
33
  }
34
34
 
35
- const sections = new Command('sections').description('Manage Slack channel sections (sidebar folders)')
36
-
37
- sections
38
- .command('list')
39
- .description('List all channel sections')
40
- .option('--pretty', 'Pretty print output')
41
- .action(listAction)
42
-
43
- export const sectionsCommand = sections
35
+ export const sectionsCommand = new Command('sections')
36
+ .description('Sidebar section commands')
37
+ .addCommand(
38
+ new Command('list')
39
+ .description('List all channel sections')
40
+ .option('--pretty', 'Pretty print JSON output')
41
+ .action(listAction),
42
+ )
@@ -93,8 +93,7 @@ async function snapshotAction(options: {
93
93
  }
94
94
  }
95
95
 
96
- export const snapshotCommand = new Command()
97
- .name('snapshot')
96
+ export const snapshotCommand = new Command('snapshot')
98
97
  .description('Get comprehensive workspace state for AI agents')
99
98
  .option('--channels-only', 'Include only channels (exclude messages and users)')
100
99
  .option('--users-only', 'Include only users (exclude channels and messages)')
@@ -45,6 +45,7 @@ export async function threadsAction(channel: string, threadTs: string, options:
45
45
  }
46
46
 
47
47
  const client = new SlackClient(workspace.token, workspace.cookie)
48
+ channel = await client.resolveChannel(channel)
48
49
  const threadView = await client.getThreadView(channel, threadTs)
49
50
 
50
51
  const output = {
@@ -72,6 +73,7 @@ export async function markAction(channel: string, ts: string, options: { pretty?
72
73
  }
73
74
 
74
75
  const client = new SlackClient(workspace.token, workspace.cookie)
76
+ channel = await client.resolveChannel(channel)
75
77
  await client.markRead(channel, ts)
76
78
 
77
79
  console.log(formatOutput({ marked_read: true, channel, ts }, options.pretty))
@@ -17,12 +17,12 @@ async function getClient(pretty?: boolean): Promise<SlackClient | null> {
17
17
  }
18
18
 
19
19
  export const userCommand = new Command('user')
20
- .description('user commands')
20
+ .description('User commands')
21
21
  .addCommand(
22
22
  new Command('list')
23
- .description('list workspace users')
24
- .option('--include-bots', 'include bot users')
25
- .option('--pretty', 'pretty-print JSON output')
23
+ .description('List workspace users')
24
+ .option('--include-bots', 'Include bot users')
25
+ .option('--pretty', 'Pretty print JSON output')
26
26
  .action(async (options) => {
27
27
  try {
28
28
  const client = await getClient(options.pretty)
@@ -51,9 +51,9 @@ export const userCommand = new Command('user')
51
51
  )
52
52
  .addCommand(
53
53
  new Command('info')
54
- .description('show user details')
54
+ .description('Show user details')
55
55
  .argument('<user>', 'user ID or username')
56
- .option('--pretty', 'pretty-print JSON output')
56
+ .option('--pretty', 'Pretty print JSON output')
57
57
  .action(async (userArg, options) => {
58
58
  try {
59
59
  const client = await getClient(options.pretty)
@@ -80,8 +80,8 @@ export const userCommand = new Command('user')
80
80
  )
81
81
  .addCommand(
82
82
  new Command('me')
83
- .description('show current authenticated user')
84
- .option('--pretty', 'pretty-print JSON output')
83
+ .description('Show current authenticated user')
84
+ .option('--pretty', 'Pretty print JSON output')
85
85
  .action(async (options) => {
86
86
  try {
87
87
  const client = await getClient(options.pretty)