agent-messenger 2.0.0 → 2.1.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.
- package/.claude-plugin/marketplace.json +14 -1
- package/.claude-plugin/plugin.json +4 -2
- package/README.md +33 -29
- package/dist/package.json +10 -2
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +3 -0
- package/dist/src/cli.js.map +1 -1
- package/dist/src/platforms/webex/app-config.d.ts +7 -0
- package/dist/src/platforms/webex/app-config.d.ts.map +1 -0
- package/dist/src/platforms/webex/app-config.js +20 -0
- package/dist/src/platforms/webex/app-config.js.map +1 -0
- package/dist/src/platforms/webex/cli.d.ts +5 -0
- package/dist/src/platforms/webex/cli.d.ts.map +1 -0
- package/dist/src/platforms/webex/cli.js +32 -0
- package/dist/src/platforms/webex/cli.js.map +1 -0
- package/dist/src/platforms/webex/client.d.ts +45 -0
- package/dist/src/platforms/webex/client.d.ts.map +1 -0
- package/dist/src/platforms/webex/client.js +175 -0
- package/dist/src/platforms/webex/client.js.map +1 -0
- package/dist/src/platforms/webex/commands/auth.d.ts +15 -0
- package/dist/src/platforms/webex/commands/auth.d.ts.map +1 -0
- package/dist/src/platforms/webex/commands/auth.js +124 -0
- package/dist/src/platforms/webex/commands/auth.js.map +1 -0
- package/dist/src/platforms/webex/commands/index.d.ts +6 -0
- package/dist/src/platforms/webex/commands/index.d.ts.map +1 -0
- package/dist/src/platforms/webex/commands/index.js +6 -0
- package/dist/src/platforms/webex/commands/index.js.map +1 -0
- package/dist/src/platforms/webex/commands/member.d.ts +7 -0
- package/dist/src/platforms/webex/commands/member.d.ts.map +1 -0
- package/dist/src/platforms/webex/commands/member.js +34 -0
- package/dist/src/platforms/webex/commands/member.js.map +1 -0
- package/dist/src/platforms/webex/commands/message.d.ts +26 -0
- package/dist/src/platforms/webex/commands/message.d.ts.map +1 -0
- package/dist/src/platforms/webex/commands/message.js +153 -0
- package/dist/src/platforms/webex/commands/message.js.map +1 -0
- package/dist/src/platforms/webex/commands/snapshot.d.ts +9 -0
- package/dist/src/platforms/webex/commands/snapshot.d.ts.map +1 -0
- package/dist/src/platforms/webex/commands/snapshot.js +72 -0
- package/dist/src/platforms/webex/commands/snapshot.js.map +1 -0
- package/dist/src/platforms/webex/commands/space.d.ts +11 -0
- package/dist/src/platforms/webex/commands/space.d.ts.map +1 -0
- package/dist/src/platforms/webex/commands/space.js +59 -0
- package/dist/src/platforms/webex/commands/space.js.map +1 -0
- package/dist/src/platforms/webex/credential-manager.d.ts +23 -0
- package/dist/src/platforms/webex/credential-manager.d.ts.map +1 -0
- package/dist/src/platforms/webex/credential-manager.js +148 -0
- package/dist/src/platforms/webex/credential-manager.js.map +1 -0
- package/dist/src/platforms/webex/ensure-auth.d.ts +2 -0
- package/dist/src/platforms/webex/ensure-auth.d.ts.map +1 -0
- package/dist/src/platforms/webex/ensure-auth.js +20 -0
- package/dist/src/platforms/webex/ensure-auth.js.map +1 -0
- package/dist/src/platforms/webex/index.d.ts +6 -0
- package/dist/src/platforms/webex/index.d.ts.map +1 -0
- package/dist/src/platforms/webex/index.js +5 -0
- package/dist/src/platforms/webex/index.js.map +1 -0
- package/dist/src/platforms/webex/types.d.ts +124 -0
- package/dist/src/platforms/webex/types.d.ts.map +1 -0
- package/dist/src/platforms/webex/types.js +63 -0
- package/dist/src/platforms/webex/types.js.map +1 -0
- package/dist/src/tui/adapters/webex-adapter.d.ts +14 -0
- package/dist/src/tui/adapters/webex-adapter.d.ts.map +1 -0
- package/dist/src/tui/adapters/webex-adapter.js +79 -0
- package/dist/src/tui/adapters/webex-adapter.js.map +1 -0
- package/dist/src/tui/app.d.ts.map +1 -1
- package/dist/src/tui/app.js +2 -0
- package/dist/src/tui/app.js.map +1 -1
- package/docs/content/docs/cli/meta.json +1 -0
- package/docs/content/docs/cli/webex.mdx +291 -0
- package/docs/content/docs/sdk/meta.json +1 -1
- package/docs/content/docs/sdk/webex.mdx +260 -0
- package/docs/content/docs/tui.mdx +4 -3
- package/docs/src/app/page.tsx +2 -2
- package/package.json +10 -2
- package/skills/agent-channeltalk/SKILL.md +1 -1
- package/skills/agent-channeltalkbot/SKILL.md +1 -1
- package/skills/agent-discord/SKILL.md +1 -1
- package/skills/agent-discordbot/SKILL.md +1 -1
- package/skills/agent-instagram/SKILL.md +1 -1
- package/skills/agent-kakaotalk/SKILL.md +1 -1
- package/skills/agent-line/SKILL.md +1 -1
- package/skills/agent-slack/SKILL.md +1 -1
- package/skills/agent-slackbot/SKILL.md +1 -1
- package/skills/agent-teams/SKILL.md +1 -1
- package/skills/agent-telegram/SKILL.md +1 -1
- package/skills/agent-webex/SKILL.md +386 -0
- package/skills/agent-webex/references/authentication.md +318 -0
- package/skills/agent-webex/references/common-patterns.md +723 -0
- package/skills/agent-webex/templates/monitor-space.sh +165 -0
- package/skills/agent-webex/templates/post-message.sh +170 -0
- package/skills/agent-whatsapp/SKILL.md +1 -1
- package/skills/agent-whatsappbot/SKILL.md +1 -1
- package/src/cli.ts +4 -0
- package/src/platforms/webex/app-config.test.ts +98 -0
- package/src/platforms/webex/app-config.ts +31 -0
- package/src/platforms/webex/cli.test.ts +58 -0
- package/src/platforms/webex/cli.ts +39 -0
- package/src/platforms/webex/client.test.ts +429 -0
- package/src/platforms/webex/client.ts +247 -0
- package/src/platforms/webex/commands/auth.test.ts +222 -0
- package/src/platforms/webex/commands/auth.ts +180 -0
- package/src/platforms/webex/commands/index.ts +5 -0
- package/src/platforms/webex/commands/member.test.ts +103 -0
- package/src/platforms/webex/commands/member.ts +45 -0
- package/src/platforms/webex/commands/message.test.ts +231 -0
- package/src/platforms/webex/commands/message.ts +204 -0
- package/src/platforms/webex/commands/snapshot.test.ts +96 -0
- package/src/platforms/webex/commands/snapshot.ts +91 -0
- package/src/platforms/webex/commands/space.test.ts +206 -0
- package/src/platforms/webex/commands/space.ts +74 -0
- package/src/platforms/webex/credential-manager.test.ts +314 -0
- package/src/platforms/webex/credential-manager.ts +197 -0
- package/src/platforms/webex/ensure-auth.test.ts +85 -0
- package/src/platforms/webex/ensure-auth.ts +19 -0
- package/src/platforms/webex/index.test.ts +25 -0
- package/src/platforms/webex/index.ts +17 -0
- package/src/platforms/webex/types.test.ts +307 -0
- package/src/platforms/webex/types.ts +127 -0
- package/src/tui/adapters/webex-adapter.ts +103 -0
- package/src/tui/app.ts +2 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { afterEach, beforeEach, expect, mock, spyOn, test } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import { WebexClient } from '../client'
|
|
4
|
+
import { WebexError } from '../types'
|
|
5
|
+
import { deleteAction, dmAction, editAction, getAction, listAction, sendAction } from './message'
|
|
6
|
+
|
|
7
|
+
let clientSendMessageSpy: ReturnType<typeof spyOn>
|
|
8
|
+
let clientSendDirectMessageSpy: ReturnType<typeof spyOn>
|
|
9
|
+
let clientListMessagesSpy: ReturnType<typeof spyOn>
|
|
10
|
+
let clientGetMessageSpy: ReturnType<typeof spyOn>
|
|
11
|
+
let clientDeleteMessageSpy: ReturnType<typeof spyOn>
|
|
12
|
+
let clientEditMessageSpy: ReturnType<typeof spyOn>
|
|
13
|
+
let clientLoginSpy: ReturnType<typeof spyOn>
|
|
14
|
+
const originalConsoleLog = console.log
|
|
15
|
+
const originalConsoleError = console.error
|
|
16
|
+
|
|
17
|
+
const mockMessage = {
|
|
18
|
+
id: 'msg_123',
|
|
19
|
+
roomId: 'space_456',
|
|
20
|
+
roomType: 'group' as const,
|
|
21
|
+
text: 'Hello world',
|
|
22
|
+
personId: 'person_789',
|
|
23
|
+
personEmail: 'user@example.com',
|
|
24
|
+
created: '2025-01-29T10:00:00.000Z',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const mockMessage2 = {
|
|
28
|
+
id: 'msg_124',
|
|
29
|
+
roomId: 'space_456',
|
|
30
|
+
roomType: 'group' as const,
|
|
31
|
+
text: 'Second message',
|
|
32
|
+
personId: 'person_789',
|
|
33
|
+
personEmail: 'user@example.com',
|
|
34
|
+
created: '2025-01-29T10:01:00.000Z',
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
clientLoginSpy = spyOn(WebexClient.prototype, 'login').mockResolvedValue(new WebexClient() as any)
|
|
39
|
+
|
|
40
|
+
clientSendMessageSpy = spyOn(WebexClient.prototype, 'sendMessage').mockResolvedValue(mockMessage)
|
|
41
|
+
|
|
42
|
+
clientSendDirectMessageSpy = spyOn(WebexClient.prototype, 'sendDirectMessage').mockResolvedValue(mockMessage)
|
|
43
|
+
|
|
44
|
+
clientListMessagesSpy = spyOn(WebexClient.prototype, 'listMessages').mockResolvedValue([
|
|
45
|
+
mockMessage,
|
|
46
|
+
mockMessage2,
|
|
47
|
+
])
|
|
48
|
+
|
|
49
|
+
clientGetMessageSpy = spyOn(WebexClient.prototype, 'getMessage').mockResolvedValue(mockMessage)
|
|
50
|
+
|
|
51
|
+
clientDeleteMessageSpy = spyOn(WebexClient.prototype, 'deleteMessage').mockResolvedValue(
|
|
52
|
+
undefined,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
clientEditMessageSpy = spyOn(WebexClient.prototype, 'editMessage').mockResolvedValue({
|
|
56
|
+
...mockMessage,
|
|
57
|
+
text: 'Updated message',
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
afterEach(() => {
|
|
62
|
+
clientLoginSpy?.mockRestore()
|
|
63
|
+
clientSendMessageSpy?.mockRestore()
|
|
64
|
+
clientSendDirectMessageSpy?.mockRestore()
|
|
65
|
+
clientListMessagesSpy?.mockRestore()
|
|
66
|
+
clientGetMessageSpy?.mockRestore()
|
|
67
|
+
clientDeleteMessageSpy?.mockRestore()
|
|
68
|
+
clientEditMessageSpy?.mockRestore()
|
|
69
|
+
console.log = originalConsoleLog
|
|
70
|
+
console.error = originalConsoleError
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test('send: calls sendMessage with correct args and outputs result', async () => {
|
|
74
|
+
const consoleSpy = mock((_msg: string) => {})
|
|
75
|
+
console.log = consoleSpy
|
|
76
|
+
|
|
77
|
+
await sendAction('space_456', 'Hello world', { pretty: false })
|
|
78
|
+
|
|
79
|
+
expect(clientSendMessageSpy).toHaveBeenCalledWith('space_456', 'Hello world', {
|
|
80
|
+
markdown: undefined,
|
|
81
|
+
})
|
|
82
|
+
expect(consoleSpy).toHaveBeenCalled()
|
|
83
|
+
const output = consoleSpy.mock.calls[0][0]
|
|
84
|
+
expect(output).toContain('msg_123')
|
|
85
|
+
expect(output).toContain('space_456')
|
|
86
|
+
expect(output).toContain('user@example.com')
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test('send: with --markdown passes markdown option', async () => {
|
|
90
|
+
const consoleSpy = mock((_msg: string) => {})
|
|
91
|
+
console.log = consoleSpy
|
|
92
|
+
|
|
93
|
+
await sendAction('space_456', '**bold**', { markdown: true, pretty: false })
|
|
94
|
+
|
|
95
|
+
expect(clientSendMessageSpy).toHaveBeenCalledWith('space_456', '**bold**', { markdown: true })
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test('send: not authenticated shows error', async () => {
|
|
99
|
+
clientLoginSpy.mockRejectedValue(new WebexError('No Webex credentials found.', 'no_credentials'))
|
|
100
|
+
const errorSpy = mock((_msg: string) => {})
|
|
101
|
+
console.error = errorSpy
|
|
102
|
+
|
|
103
|
+
const originalExit = process.exit
|
|
104
|
+
process.exit = mock((_code?: number) => {
|
|
105
|
+
throw new Error('process.exit called')
|
|
106
|
+
}) as never
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
await sendAction('space_456', 'Hello', { pretty: false })
|
|
110
|
+
} catch {
|
|
111
|
+
} finally {
|
|
112
|
+
process.exit = originalExit
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
expect(errorSpy).toHaveBeenCalled()
|
|
116
|
+
const output = errorSpy.mock.calls[0][0]
|
|
117
|
+
expect(output).toContain('No Webex credentials found')
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
test('dm: calls sendDirectMessage with email and text', async () => {
|
|
121
|
+
const consoleSpy = mock((_msg: string) => {})
|
|
122
|
+
console.log = consoleSpy
|
|
123
|
+
|
|
124
|
+
await dmAction('alice@example.com', 'Hello!', { pretty: false })
|
|
125
|
+
|
|
126
|
+
expect(clientSendDirectMessageSpy).toHaveBeenCalledWith('alice@example.com', 'Hello!', {
|
|
127
|
+
markdown: undefined,
|
|
128
|
+
})
|
|
129
|
+
expect(consoleSpy).toHaveBeenCalled()
|
|
130
|
+
const output = consoleSpy.mock.calls[0][0]
|
|
131
|
+
expect(output).toContain('msg_123')
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
test('dm: with --markdown passes markdown option', async () => {
|
|
135
|
+
const consoleSpy = mock((_msg: string) => {})
|
|
136
|
+
console.log = consoleSpy
|
|
137
|
+
|
|
138
|
+
await dmAction('alice@example.com', '**bold**', { markdown: true, pretty: false })
|
|
139
|
+
|
|
140
|
+
expect(clientSendDirectMessageSpy).toHaveBeenCalledWith('alice@example.com', '**bold**', {
|
|
141
|
+
markdown: true,
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
test('list: calls listMessages with limit and outputs array', async () => {
|
|
146
|
+
const consoleSpy = mock((_msg: string) => {})
|
|
147
|
+
console.log = consoleSpy
|
|
148
|
+
|
|
149
|
+
await listAction('space_456', { limit: 50, pretty: false })
|
|
150
|
+
|
|
151
|
+
expect(clientListMessagesSpy).toHaveBeenCalledWith('space_456', { max: 50 })
|
|
152
|
+
expect(consoleSpy).toHaveBeenCalled()
|
|
153
|
+
const output = consoleSpy.mock.calls[0][0]
|
|
154
|
+
expect(output).toContain('msg_123')
|
|
155
|
+
expect(output).toContain('msg_124')
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
test('get: calls getMessage with correct id and outputs result', async () => {
|
|
159
|
+
const consoleSpy = mock((_msg: string) => {})
|
|
160
|
+
console.log = consoleSpy
|
|
161
|
+
|
|
162
|
+
await getAction('msg_123', { pretty: false })
|
|
163
|
+
|
|
164
|
+
expect(clientGetMessageSpy).toHaveBeenCalledWith('msg_123')
|
|
165
|
+
expect(consoleSpy).toHaveBeenCalled()
|
|
166
|
+
const output = consoleSpy.mock.calls[0][0]
|
|
167
|
+
expect(output).toContain('msg_123')
|
|
168
|
+
expect(output).toContain('user@example.com')
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
test('delete: with --force calls deleteMessage and outputs deleted id', async () => {
|
|
172
|
+
const consoleSpy = mock((_msg: string) => {})
|
|
173
|
+
console.log = consoleSpy
|
|
174
|
+
|
|
175
|
+
await deleteAction('msg_123', { force: true, pretty: false })
|
|
176
|
+
|
|
177
|
+
expect(clientDeleteMessageSpy).toHaveBeenCalledWith('msg_123')
|
|
178
|
+
expect(consoleSpy).toHaveBeenCalled()
|
|
179
|
+
const output = consoleSpy.mock.calls[0][0]
|
|
180
|
+
expect(output).toContain('deleted')
|
|
181
|
+
expect(output).toContain('msg_123')
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
test('delete: without --force shows warning and does not delete', async () => {
|
|
185
|
+
const consoleSpy = mock((_msg: string) => {})
|
|
186
|
+
console.log = consoleSpy
|
|
187
|
+
|
|
188
|
+
const originalExit = process.exit
|
|
189
|
+
process.exit = mock((_code?: number) => {
|
|
190
|
+
throw new Error('process.exit called')
|
|
191
|
+
}) as never
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
await deleteAction('msg_123', { force: false, pretty: false })
|
|
195
|
+
} catch {
|
|
196
|
+
} finally {
|
|
197
|
+
process.exit = originalExit
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
expect(clientDeleteMessageSpy).not.toHaveBeenCalled()
|
|
201
|
+
expect(consoleSpy).toHaveBeenCalled()
|
|
202
|
+
const output = consoleSpy.mock.calls[0][0]
|
|
203
|
+
expect(output).toContain('warning')
|
|
204
|
+
expect(output).toContain('--force')
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
test('edit: calls editMessage with roomId in args and outputs result', async () => {
|
|
208
|
+
const consoleSpy = mock((_msg: string) => {})
|
|
209
|
+
console.log = consoleSpy
|
|
210
|
+
|
|
211
|
+
await editAction('msg_123', 'space_456', 'Updated message', { pretty: false })
|
|
212
|
+
|
|
213
|
+
expect(clientEditMessageSpy).toHaveBeenCalledWith('msg_123', 'space_456', 'Updated message', {
|
|
214
|
+
markdown: undefined,
|
|
215
|
+
})
|
|
216
|
+
expect(consoleSpy).toHaveBeenCalled()
|
|
217
|
+
const output = consoleSpy.mock.calls[0][0]
|
|
218
|
+
expect(output).toContain('msg_123')
|
|
219
|
+
expect(output).toContain('Updated message')
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
test('edit: with --markdown passes markdown option', async () => {
|
|
223
|
+
const consoleSpy = mock((_msg: string) => {})
|
|
224
|
+
console.log = consoleSpy
|
|
225
|
+
|
|
226
|
+
await editAction('msg_123', 'space_456', '**updated**', { markdown: true, pretty: false })
|
|
227
|
+
|
|
228
|
+
expect(clientEditMessageSpy).toHaveBeenCalledWith('msg_123', 'space_456', '**updated**', {
|
|
229
|
+
markdown: true,
|
|
230
|
+
})
|
|
231
|
+
})
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
|
|
3
|
+
import { handleError } from '@/shared/utils/error-handler'
|
|
4
|
+
import { formatOutput } from '@/shared/utils/output'
|
|
5
|
+
|
|
6
|
+
import { WebexClient } from '../client'
|
|
7
|
+
import type { WebexMessage } from '../types'
|
|
8
|
+
|
|
9
|
+
export async function sendAction(
|
|
10
|
+
spaceId: string,
|
|
11
|
+
text: string,
|
|
12
|
+
options: { markdown?: boolean; pretty?: boolean },
|
|
13
|
+
): Promise<void> {
|
|
14
|
+
try {
|
|
15
|
+
const client = await new WebexClient().login()
|
|
16
|
+
const message = await client.sendMessage(spaceId, text, { markdown: options.markdown })
|
|
17
|
+
|
|
18
|
+
const output = {
|
|
19
|
+
id: message.id,
|
|
20
|
+
roomId: message.roomId,
|
|
21
|
+
text: message.text,
|
|
22
|
+
personEmail: message.personEmail,
|
|
23
|
+
created: message.created,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
console.log(formatOutput(output, options.pretty))
|
|
27
|
+
} catch (error) {
|
|
28
|
+
handleError(error as Error)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function listAction(
|
|
33
|
+
spaceId: string,
|
|
34
|
+
options: { limit?: number; pretty?: boolean },
|
|
35
|
+
): Promise<void> {
|
|
36
|
+
try {
|
|
37
|
+
const client = await new WebexClient().login()
|
|
38
|
+
const limit = options.limit ?? 50
|
|
39
|
+
const messages = await client.listMessages(spaceId, { max: limit })
|
|
40
|
+
|
|
41
|
+
const output = messages.map((msg: WebexMessage) => ({
|
|
42
|
+
id: msg.id,
|
|
43
|
+
roomId: msg.roomId,
|
|
44
|
+
text: msg.text,
|
|
45
|
+
personEmail: msg.personEmail,
|
|
46
|
+
created: msg.created,
|
|
47
|
+
}))
|
|
48
|
+
|
|
49
|
+
console.log(formatOutput(output, options.pretty))
|
|
50
|
+
} catch (error) {
|
|
51
|
+
handleError(error as Error)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function getAction(
|
|
56
|
+
messageId: string,
|
|
57
|
+
options: { pretty?: boolean },
|
|
58
|
+
): Promise<void> {
|
|
59
|
+
try {
|
|
60
|
+
const client = await new WebexClient().login()
|
|
61
|
+
const message = await client.getMessage(messageId)
|
|
62
|
+
|
|
63
|
+
const output = {
|
|
64
|
+
id: message.id,
|
|
65
|
+
roomId: message.roomId,
|
|
66
|
+
text: message.text,
|
|
67
|
+
personEmail: message.personEmail,
|
|
68
|
+
created: message.created,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
console.log(formatOutput(output, options.pretty))
|
|
72
|
+
} catch (error) {
|
|
73
|
+
handleError(error as Error)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function deleteAction(
|
|
78
|
+
messageId: string,
|
|
79
|
+
options: { force?: boolean; pretty?: boolean },
|
|
80
|
+
): Promise<void> {
|
|
81
|
+
try {
|
|
82
|
+
if (!options.force) {
|
|
83
|
+
console.log(
|
|
84
|
+
formatOutput({ warning: 'Use --force to confirm deletion', messageId }, options.pretty),
|
|
85
|
+
)
|
|
86
|
+
process.exit(0)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const client = await new WebexClient().login()
|
|
90
|
+
await client.deleteMessage(messageId)
|
|
91
|
+
|
|
92
|
+
console.log(formatOutput({ deleted: messageId }, options.pretty))
|
|
93
|
+
} catch (error) {
|
|
94
|
+
handleError(error as Error)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function editAction(
|
|
99
|
+
messageId: string,
|
|
100
|
+
spaceId: string,
|
|
101
|
+
text: string,
|
|
102
|
+
options: { markdown?: boolean; pretty?: boolean },
|
|
103
|
+
): Promise<void> {
|
|
104
|
+
try {
|
|
105
|
+
const client = await new WebexClient().login()
|
|
106
|
+
const message = await client.editMessage(messageId, spaceId, text, {
|
|
107
|
+
markdown: options.markdown,
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
const output = {
|
|
111
|
+
id: message.id,
|
|
112
|
+
roomId: message.roomId,
|
|
113
|
+
text: message.text,
|
|
114
|
+
personEmail: message.personEmail,
|
|
115
|
+
created: message.created,
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
console.log(formatOutput(output, options.pretty))
|
|
119
|
+
} catch (error) {
|
|
120
|
+
handleError(error as Error)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export async function dmAction(
|
|
125
|
+
email: string,
|
|
126
|
+
text: string,
|
|
127
|
+
options: { markdown?: boolean; pretty?: boolean },
|
|
128
|
+
): Promise<void> {
|
|
129
|
+
try {
|
|
130
|
+
const client = await new WebexClient().login()
|
|
131
|
+
const message = await client.sendDirectMessage(email, text, { markdown: options.markdown })
|
|
132
|
+
|
|
133
|
+
const output = {
|
|
134
|
+
id: message.id,
|
|
135
|
+
roomId: message.roomId,
|
|
136
|
+
text: message.text,
|
|
137
|
+
personEmail: message.personEmail,
|
|
138
|
+
created: message.created,
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
console.log(formatOutput(output, options.pretty))
|
|
142
|
+
} catch (error) {
|
|
143
|
+
handleError(error as Error)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export const messageCommand = new Command('message')
|
|
148
|
+
.description('Message commands')
|
|
149
|
+
.addCommand(
|
|
150
|
+
new Command('send')
|
|
151
|
+
.description('Send message to a space')
|
|
152
|
+
.argument('<space-id>', 'Space/Room ID')
|
|
153
|
+
.argument('<text>', 'Message text')
|
|
154
|
+
.option('--markdown', 'Send as markdown')
|
|
155
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
156
|
+
.action(sendAction),
|
|
157
|
+
)
|
|
158
|
+
.addCommand(
|
|
159
|
+
new Command('dm')
|
|
160
|
+
.description('Send a direct message by email')
|
|
161
|
+
.argument('<email>', 'Recipient email address')
|
|
162
|
+
.argument('<text>', 'Message text')
|
|
163
|
+
.option('--markdown', 'Send as markdown')
|
|
164
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
165
|
+
.action(dmAction),
|
|
166
|
+
)
|
|
167
|
+
.addCommand(
|
|
168
|
+
new Command('list')
|
|
169
|
+
.description('List messages from a space')
|
|
170
|
+
.argument('<space-id>', 'Space/Room ID')
|
|
171
|
+
.option('--limit <n>', 'Number of messages to retrieve', '50')
|
|
172
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
173
|
+
.action((spaceId: string, options: { limit: string; pretty?: boolean }) => {
|
|
174
|
+
return listAction(spaceId, {
|
|
175
|
+
limit: parseInt(options.limit, 10),
|
|
176
|
+
pretty: options.pretty,
|
|
177
|
+
})
|
|
178
|
+
}),
|
|
179
|
+
)
|
|
180
|
+
.addCommand(
|
|
181
|
+
new Command('get')
|
|
182
|
+
.description('Get a single message by ID')
|
|
183
|
+
.argument('<message-id>', 'Message ID')
|
|
184
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
185
|
+
.action(getAction),
|
|
186
|
+
)
|
|
187
|
+
.addCommand(
|
|
188
|
+
new Command('delete')
|
|
189
|
+
.description('Delete a message')
|
|
190
|
+
.argument('<message-id>', 'Message ID')
|
|
191
|
+
.option('--force', 'Skip confirmation')
|
|
192
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
193
|
+
.action(deleteAction),
|
|
194
|
+
)
|
|
195
|
+
.addCommand(
|
|
196
|
+
new Command('edit')
|
|
197
|
+
.description('Edit a message')
|
|
198
|
+
.argument('<message-id>', 'Message ID')
|
|
199
|
+
.argument('<space-id>', 'Space/Room ID (required by Webex API)')
|
|
200
|
+
.argument('<text>', 'New message text')
|
|
201
|
+
.option('--markdown', 'Send as markdown')
|
|
202
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
203
|
+
.action(editAction),
|
|
204
|
+
)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from 'bun:test'
|
|
2
|
+
import { WebexClient } from '../client'
|
|
3
|
+
import { WebexError } from '../types'
|
|
4
|
+
import { snapshotAction } from './snapshot'
|
|
5
|
+
|
|
6
|
+
describe('snapshot command', () => {
|
|
7
|
+
let consoleSpy: ReturnType<typeof spyOn>
|
|
8
|
+
let consoleErrorSpy: ReturnType<typeof spyOn>
|
|
9
|
+
|
|
10
|
+
const mockSpaces = [
|
|
11
|
+
{ id: 'space-1', title: 'General', type: 'group', isLocked: false, lastActivity: '2024-01-15T00:00:00.000Z', created: '2024-01-01T00:00:00.000Z', creatorId: 'person-1' },
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
const mockMessages = [
|
|
15
|
+
{ id: 'msg-1', roomId: 'space-1', roomType: 'group', text: 'Hello', personId: 'person-1', personEmail: 'alice@example.com', created: '2024-01-15T00:00:00.000Z' },
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
const mockMembers = [
|
|
19
|
+
{ id: 'mem-1', roomId: 'space-1', personId: 'person-1', personEmail: 'alice@example.com', personDisplayName: 'Alice', isModerator: true, created: '2024-01-01T00:00:00.000Z' },
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
consoleSpy = spyOn(console, 'log').mockImplementation(() => {})
|
|
24
|
+
consoleErrorSpy = spyOn(console, 'error').mockImplementation(() => {})
|
|
25
|
+
spyOn(WebexClient.prototype, 'login').mockResolvedValue(new WebexClient() as any)
|
|
26
|
+
spyOn(WebexClient.prototype, 'listSpaces').mockResolvedValue(mockSpaces as any)
|
|
27
|
+
spyOn(WebexClient.prototype, 'listMessages').mockResolvedValue(mockMessages as any)
|
|
28
|
+
spyOn(WebexClient.prototype, 'listMemberships').mockResolvedValue(mockMembers as any)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
afterEach(() => { mock.restore() })
|
|
32
|
+
|
|
33
|
+
test('full snapshot includes spaces, recent_messages, members', async () => {
|
|
34
|
+
await snapshotAction({})
|
|
35
|
+
|
|
36
|
+
expect(consoleSpy).toHaveBeenCalled()
|
|
37
|
+
const output = JSON.parse(consoleSpy.mock.calls[consoleSpy.mock.calls.length - 1][0])
|
|
38
|
+
expect(output.spaces).toBeDefined()
|
|
39
|
+
expect(output.spaces[0].id).toBe('space-1')
|
|
40
|
+
expect(output.spaces[0].title).toBe('General')
|
|
41
|
+
expect(output.recent_messages).toBeDefined()
|
|
42
|
+
expect(output.recent_messages[0].id).toBe('msg-1')
|
|
43
|
+
expect(output.recent_messages[0].author).toBe('alice@example.com')
|
|
44
|
+
expect(output.members).toBeDefined()
|
|
45
|
+
expect(output.members[0].personEmail).toBe('alice@example.com')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('--spaces-only includes only spaces (no messages, no members)', async () => {
|
|
49
|
+
await snapshotAction({ spacesOnly: true })
|
|
50
|
+
|
|
51
|
+
expect(consoleSpy).toHaveBeenCalled()
|
|
52
|
+
const output = JSON.parse(consoleSpy.mock.calls[consoleSpy.mock.calls.length - 1][0])
|
|
53
|
+
expect(output.spaces).toBeDefined()
|
|
54
|
+
expect(output.recent_messages).toBeUndefined()
|
|
55
|
+
expect(output.members).toBeUndefined()
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test('--members-only includes only members (no spaces, no messages)', async () => {
|
|
59
|
+
await snapshotAction({ membersOnly: true })
|
|
60
|
+
|
|
61
|
+
expect(consoleSpy).toHaveBeenCalled()
|
|
62
|
+
const output = JSON.parse(consoleSpy.mock.calls[consoleSpy.mock.calls.length - 1][0])
|
|
63
|
+
expect(output.spaces).toBeUndefined()
|
|
64
|
+
expect(output.recent_messages).toBeUndefined()
|
|
65
|
+
expect(output.members).toBeDefined()
|
|
66
|
+
expect(output.members[0].personEmail).toBe('alice@example.com')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('not authenticated outputs error', async () => {
|
|
70
|
+
spyOn(WebexClient.prototype, 'login').mockRejectedValue(
|
|
71
|
+
new WebexError('No Webex credentials found.', 'no_credentials'),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
const originalExit = process.exit
|
|
75
|
+
process.exit = mock((_code?: number) => { throw new Error('process.exit called') }) as never
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
await snapshotAction({})
|
|
79
|
+
} catch {
|
|
80
|
+
} finally {
|
|
81
|
+
process.exit = originalExit
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
expect(consoleErrorSpy).toHaveBeenCalled()
|
|
85
|
+
const output = consoleErrorSpy.mock.calls[0][0]
|
|
86
|
+
expect(output).toContain('No Webex credentials found')
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test('passes limit option to listMessages', async () => {
|
|
90
|
+
const listMessagesSpy = spyOn(WebexClient.prototype, 'listMessages').mockResolvedValue(mockMessages as any)
|
|
91
|
+
|
|
92
|
+
await snapshotAction({ limit: 5 })
|
|
93
|
+
|
|
94
|
+
expect(listMessagesSpy).toHaveBeenCalledWith('space-1', { max: 5 })
|
|
95
|
+
})
|
|
96
|
+
})
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
import { parallelMap } from '@/shared/utils/concurrency'
|
|
3
|
+
import { handleError } from '@/shared/utils/error-handler'
|
|
4
|
+
import { formatOutput } from '@/shared/utils/output'
|
|
5
|
+
import { WebexClient } from '../client'
|
|
6
|
+
import type { WebexSpace } from '../types'
|
|
7
|
+
|
|
8
|
+
export async function snapshotAction(options: {
|
|
9
|
+
spacesOnly?: boolean
|
|
10
|
+
membersOnly?: boolean
|
|
11
|
+
limit?: number
|
|
12
|
+
pretty?: boolean
|
|
13
|
+
}): Promise<void> {
|
|
14
|
+
try {
|
|
15
|
+
const client = await new WebexClient().login()
|
|
16
|
+
const messageLimit = options.limit || 20
|
|
17
|
+
const snapshot: Record<string, unknown> = {}
|
|
18
|
+
|
|
19
|
+
if (!options.membersOnly) {
|
|
20
|
+
const spaces = await client.listSpaces({ max: 50 })
|
|
21
|
+
snapshot.spaces = spaces.map((s) => ({
|
|
22
|
+
id: s.id,
|
|
23
|
+
title: s.title,
|
|
24
|
+
type: s.type,
|
|
25
|
+
lastActivity: s.lastActivity,
|
|
26
|
+
}))
|
|
27
|
+
|
|
28
|
+
if (!options.spacesOnly) {
|
|
29
|
+
const spaceMessages = await parallelMap(
|
|
30
|
+
spaces,
|
|
31
|
+
async (space: WebexSpace) => {
|
|
32
|
+
const messages = await client.listMessages(space.id, { max: messageLimit })
|
|
33
|
+
return messages.map((msg) => ({
|
|
34
|
+
...msg,
|
|
35
|
+
space_title: space.title,
|
|
36
|
+
}))
|
|
37
|
+
},
|
|
38
|
+
5,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
snapshot.recent_messages = spaceMessages.flat().map((msg) => ({
|
|
42
|
+
space_id: msg.roomId,
|
|
43
|
+
space_title: msg.space_title,
|
|
44
|
+
id: msg.id,
|
|
45
|
+
author: msg.personEmail,
|
|
46
|
+
text: msg.text || msg.markdown || '',
|
|
47
|
+
created: msg.created,
|
|
48
|
+
}))
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!options.spacesOnly) {
|
|
53
|
+
// Get members for the first few spaces (avoid massive API calls)
|
|
54
|
+
const spaces = await client.listSpaces({ max: 10 })
|
|
55
|
+
const spaceMembers = await parallelMap(
|
|
56
|
+
spaces,
|
|
57
|
+
async (space: WebexSpace) => {
|
|
58
|
+
const members = await client.listMemberships(space.id, { max: 100 })
|
|
59
|
+
return members.map((m) => ({
|
|
60
|
+
space_id: space.id,
|
|
61
|
+
space_title: space.title,
|
|
62
|
+
personEmail: m.personEmail,
|
|
63
|
+
personDisplayName: m.personDisplayName,
|
|
64
|
+
isModerator: m.isModerator,
|
|
65
|
+
}))
|
|
66
|
+
},
|
|
67
|
+
5,
|
|
68
|
+
)
|
|
69
|
+
snapshot.members = spaceMembers.flat()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.log(formatOutput(snapshot, options.pretty))
|
|
73
|
+
} catch (error) {
|
|
74
|
+
handleError(error as Error)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const snapshotCommand = new Command('snapshot')
|
|
79
|
+
.description('Get comprehensive workspace state for AI agents')
|
|
80
|
+
.option('--spaces-only', 'Include only spaces (exclude messages and members)')
|
|
81
|
+
.option('--members-only', 'Include only members (exclude spaces and messages)')
|
|
82
|
+
.option('--limit <n>', 'Number of recent messages per space (default: 20)', '20')
|
|
83
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
84
|
+
.action(async (options) => {
|
|
85
|
+
await snapshotAction({
|
|
86
|
+
spacesOnly: options.spacesOnly,
|
|
87
|
+
membersOnly: options.membersOnly,
|
|
88
|
+
limit: parseInt(options.limit, 10),
|
|
89
|
+
pretty: options.pretty,
|
|
90
|
+
})
|
|
91
|
+
})
|