agent-messenger 2.20.4 → 2.21.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/plugin.json +1 -1
- package/README.md +8 -5
- package/bun.lock +2 -2
- 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/webexbot/cli.d.ts +5 -0
- package/dist/src/platforms/webexbot/cli.d.ts.map +1 -0
- package/dist/src/platforms/webexbot/cli.js +30 -0
- package/dist/src/platforms/webexbot/cli.js.map +1 -0
- package/dist/src/platforms/webexbot/client.d.ts +41 -0
- package/dist/src/platforms/webexbot/client.d.ts.map +1 -0
- package/dist/src/platforms/webexbot/client.js +66 -0
- package/dist/src/platforms/webexbot/client.js.map +1 -0
- package/dist/src/platforms/webexbot/commands/auth.d.ts +28 -0
- package/dist/src/platforms/webexbot/commands/auth.d.ts.map +1 -0
- package/dist/src/platforms/webexbot/commands/auth.js +166 -0
- package/dist/src/platforms/webexbot/commands/auth.js.map +1 -0
- package/dist/src/platforms/webexbot/commands/index.d.ts +7 -0
- package/dist/src/platforms/webexbot/commands/index.d.ts.map +1 -0
- package/dist/src/platforms/webexbot/commands/index.js +7 -0
- package/dist/src/platforms/webexbot/commands/index.js.map +1 -0
- package/dist/src/platforms/webexbot/commands/listen.d.ts +12 -0
- package/dist/src/platforms/webexbot/commands/listen.d.ts.map +1 -0
- package/dist/src/platforms/webexbot/commands/listen.js +85 -0
- package/dist/src/platforms/webexbot/commands/listen.js.map +1 -0
- package/dist/src/platforms/webexbot/commands/member.d.ts +19 -0
- package/dist/src/platforms/webexbot/commands/member.d.ts.map +1 -0
- package/dist/src/platforms/webexbot/commands/member.js +33 -0
- package/dist/src/platforms/webexbot/commands/member.js.map +1 -0
- package/dist/src/platforms/webexbot/commands/message.d.ts +37 -0
- package/dist/src/platforms/webexbot/commands/message.d.ts.map +1 -0
- package/dist/src/platforms/webexbot/commands/message.js +142 -0
- package/dist/src/platforms/webexbot/commands/message.js.map +1 -0
- package/dist/src/platforms/webexbot/commands/shared.d.ts +9 -0
- package/dist/src/platforms/webexbot/commands/shared.d.ts.map +1 -0
- package/dist/src/platforms/webexbot/commands/shared.js +13 -0
- package/dist/src/platforms/webexbot/commands/shared.js.map +1 -0
- package/dist/src/platforms/webexbot/commands/space.d.ts +28 -0
- package/dist/src/platforms/webexbot/commands/space.d.ts.map +1 -0
- package/dist/src/platforms/webexbot/commands/space.js +61 -0
- package/dist/src/platforms/webexbot/commands/space.js.map +1 -0
- package/dist/src/platforms/webexbot/commands/whoami.d.ts +16 -0
- package/dist/src/platforms/webexbot/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/webexbot/commands/whoami.js +29 -0
- package/dist/src/platforms/webexbot/commands/whoami.js.map +1 -0
- package/dist/src/platforms/webexbot/credential-manager.d.ts +17 -0
- package/dist/src/platforms/webexbot/credential-manager.d.ts.map +1 -0
- package/dist/src/platforms/webexbot/credential-manager.js +120 -0
- package/dist/src/platforms/webexbot/credential-manager.js.map +1 -0
- package/dist/src/platforms/webexbot/index.d.ts +9 -0
- package/dist/src/platforms/webexbot/index.d.ts.map +1 -0
- package/dist/src/platforms/webexbot/index.js +6 -0
- package/dist/src/platforms/webexbot/index.js.map +1 -0
- package/dist/src/platforms/webexbot/listener.d.ts +44 -0
- package/dist/src/platforms/webexbot/listener.d.ts.map +1 -0
- package/dist/src/platforms/webexbot/listener.js +214 -0
- package/dist/src/platforms/webexbot/listener.js.map +1 -0
- package/dist/src/platforms/webexbot/types.d.ts +60 -0
- package/dist/src/platforms/webexbot/types.d.ts.map +1 -0
- package/dist/src/platforms/webexbot/types.js +28 -0
- package/dist/src/platforms/webexbot/types.js.map +1 -0
- package/dist/src/platforms/webexbot/wdm-discovery.d.ts +4 -0
- package/dist/src/platforms/webexbot/wdm-discovery.d.ts.map +1 -0
- package/dist/src/platforms/webexbot/wdm-discovery.js +36 -0
- package/dist/src/platforms/webexbot/wdm-discovery.js.map +1 -0
- package/docs/content/docs/cli/meta.json +1 -0
- package/docs/content/docs/cli/webexbot.mdx +290 -0
- package/docs/content/docs/sdk/meta.json +1 -0
- package/docs/content/docs/sdk/webexbot.mdx +340 -0
- package/docs/src/app/page.tsx +115 -19
- 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-telegrambot/SKILL.md +1 -1
- package/skills/agent-webex/SKILL.md +1 -1
- package/skills/agent-webexbot/SKILL.md +361 -0
- package/skills/agent-webexbot/references/authentication.md +225 -0
- package/skills/agent-webexbot/references/common-patterns.md +590 -0
- package/skills/agent-wechatbot/SKILL.md +1 -1
- 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/typings/webex-message-handler.d.ts +360 -29
- package/src/platforms/webexbot/cli.ts +42 -0
- package/src/platforms/webexbot/client.ts +87 -0
- package/src/platforms/webexbot/commands/auth.test.ts +185 -0
- package/src/platforms/webexbot/commands/auth.ts +210 -0
- package/src/platforms/webexbot/commands/index.ts +6 -0
- package/src/platforms/webexbot/commands/listen.test.ts +20 -0
- package/src/platforms/webexbot/commands/listen.ts +104 -0
- package/src/platforms/webexbot/commands/member.ts +51 -0
- package/src/platforms/webexbot/commands/message.ts +197 -0
- package/src/platforms/webexbot/commands/shared.ts +22 -0
- package/src/platforms/webexbot/commands/space.ts +88 -0
- package/src/platforms/webexbot/commands/whoami.ts +43 -0
- package/src/platforms/webexbot/credential-manager.test.ts +182 -0
- package/src/platforms/webexbot/credential-manager.ts +149 -0
- package/src/platforms/webexbot/index.ts +8 -0
- package/src/platforms/webexbot/listener.test.ts +234 -0
- package/src/platforms/webexbot/listener.ts +255 -0
- package/src/platforms/webexbot/types.test.ts +87 -0
- package/src/platforms/webexbot/types.ts +72 -0
- package/src/platforms/webexbot/wdm-discovery.test.ts +97 -0
- package/src/platforms/webexbot/wdm-discovery.ts +43 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, mock } from 'bun:test'
|
|
2
|
+
import { existsSync, rmSync } from 'node:fs'
|
|
3
|
+
import { mkdir } from 'node:fs/promises'
|
|
4
|
+
import { tmpdir } from 'node:os'
|
|
5
|
+
import { join } from 'node:path'
|
|
6
|
+
|
|
7
|
+
import type { WebexPerson } from '../../webex/types'
|
|
8
|
+
|
|
9
|
+
const mockTestAuth = mock(() =>
|
|
10
|
+
Promise.resolve({
|
|
11
|
+
id: 'bot123',
|
|
12
|
+
emails: ['bot@example.com'],
|
|
13
|
+
displayName: 'Test Bot',
|
|
14
|
+
orgId: 'org123',
|
|
15
|
+
type: 'bot' as const,
|
|
16
|
+
created: '2024-01-01T00:00:00Z',
|
|
17
|
+
}),
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
mock.module('../client', () => ({
|
|
21
|
+
WebexBotClient: class MockWebexBotClient {
|
|
22
|
+
async login(_credentials?: { token: string }): Promise<this> {
|
|
23
|
+
return this
|
|
24
|
+
}
|
|
25
|
+
testAuth = mockTestAuth
|
|
26
|
+
},
|
|
27
|
+
}))
|
|
28
|
+
|
|
29
|
+
import { WebexBotCredentialManager } from '../credential-manager'
|
|
30
|
+
import { clearAction, listAction, removeAction, setAction, statusAction, useAction } from './auth'
|
|
31
|
+
|
|
32
|
+
describe('webexbot auth commands', () => {
|
|
33
|
+
let tempDir: string
|
|
34
|
+
let originalEnv: NodeJS.ProcessEnv
|
|
35
|
+
|
|
36
|
+
beforeEach(async () => {
|
|
37
|
+
tempDir = join(tmpdir(), `webexbot-auth-test-${Date.now()}`)
|
|
38
|
+
await mkdir(tempDir, { recursive: true })
|
|
39
|
+
originalEnv = { ...process.env }
|
|
40
|
+
delete process.env.E2E_WEBEXBOT_TOKEN
|
|
41
|
+
mockTestAuth.mockClear()
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
if (existsSync(tempDir)) {
|
|
46
|
+
rmSync(tempDir, { recursive: true })
|
|
47
|
+
}
|
|
48
|
+
process.env = originalEnv
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('validates and stores bot token with default bot_id from auth', async () => {
|
|
52
|
+
const manager = new WebexBotCredentialManager(tempDir)
|
|
53
|
+
|
|
54
|
+
const result = await setAction('token123', { _credManager: manager })
|
|
55
|
+
|
|
56
|
+
expect(result.success).toBe(true)
|
|
57
|
+
expect(result.bot_id).toBe('bot123')
|
|
58
|
+
expect(result.bot_name).toBe('Test Bot')
|
|
59
|
+
|
|
60
|
+
const creds = await manager.getCredentials()
|
|
61
|
+
expect(creds?.token).toBe('token123')
|
|
62
|
+
expect(creds?.bot_id).toBe('bot123')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('uses --bot flag as bot_id', async () => {
|
|
66
|
+
const manager = new WebexBotCredentialManager(tempDir)
|
|
67
|
+
|
|
68
|
+
const result = await setAction('token123', { bot: 'mybot', _credManager: manager })
|
|
69
|
+
|
|
70
|
+
expect(result.bot_id).toBe('mybot')
|
|
71
|
+
const creds = await manager.getCredentials('mybot')
|
|
72
|
+
expect(creds?.token).toBe('token123')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('rejects user tokens', async () => {
|
|
76
|
+
mockTestAuth.mockImplementationOnce(() =>
|
|
77
|
+
Promise.resolve({
|
|
78
|
+
id: 'user123',
|
|
79
|
+
emails: ['user@example.com'],
|
|
80
|
+
displayName: 'Test User',
|
|
81
|
+
orgId: 'org123',
|
|
82
|
+
type: 'person',
|
|
83
|
+
created: '2024-01-01T00:00:00Z',
|
|
84
|
+
} satisfies WebexPerson),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
const manager = new WebexBotCredentialManager(tempDir)
|
|
88
|
+
|
|
89
|
+
const result = await setAction('token123', { _credManager: manager })
|
|
90
|
+
|
|
91
|
+
expect(result.error).toBeDefined()
|
|
92
|
+
expect(result.error).toContain('not a bot token')
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('handles client errors', async () => {
|
|
96
|
+
mockTestAuth.mockImplementationOnce(() => Promise.reject(new Error('Invalid token')))
|
|
97
|
+
|
|
98
|
+
const manager = new WebexBotCredentialManager(tempDir)
|
|
99
|
+
|
|
100
|
+
const result = await setAction('invalid', { _credManager: manager })
|
|
101
|
+
|
|
102
|
+
expect(result.error).toBeDefined()
|
|
103
|
+
expect(result.error).toContain('Invalid token')
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('clearAction removes all stored credentials', async () => {
|
|
107
|
+
const manager = new WebexBotCredentialManager(tempDir)
|
|
108
|
+
await manager.setCredentials({ token: 'token123', bot_id: 'mybot', bot_name: 'My Bot' })
|
|
109
|
+
|
|
110
|
+
const result = await clearAction({ _credManager: manager })
|
|
111
|
+
|
|
112
|
+
expect(result.success).toBe(true)
|
|
113
|
+
expect(await manager.getCredentials()).toBeNull()
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('statusAction returns no credentials when none set', async () => {
|
|
117
|
+
const manager = new WebexBotCredentialManager(tempDir)
|
|
118
|
+
|
|
119
|
+
const result = await statusAction({ _credManager: manager })
|
|
120
|
+
|
|
121
|
+
expect(result.valid).toBe(false)
|
|
122
|
+
expect(result.error).toBeDefined()
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('statusAction returns valid status for current bot', async () => {
|
|
126
|
+
const manager = new WebexBotCredentialManager(tempDir)
|
|
127
|
+
await manager.setCredentials({ token: 'token123', bot_id: 'mybot', bot_name: 'My Bot' })
|
|
128
|
+
|
|
129
|
+
const result = await statusAction({ _credManager: manager })
|
|
130
|
+
|
|
131
|
+
expect(result.valid).toBe(true)
|
|
132
|
+
expect(result.bot_id).toBe('bot123')
|
|
133
|
+
expect(result.bot_name).toBe('Test Bot')
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it('statusAction returns invalid when token test fails', async () => {
|
|
137
|
+
mockTestAuth.mockImplementationOnce(() => Promise.reject(new Error('Unauthorized')))
|
|
138
|
+
|
|
139
|
+
const manager = new WebexBotCredentialManager(tempDir)
|
|
140
|
+
await manager.setCredentials({ token: 'invalid-token', bot_id: 'mybot', bot_name: 'My Bot' })
|
|
141
|
+
|
|
142
|
+
const result = await statusAction({ _credManager: manager })
|
|
143
|
+
|
|
144
|
+
expect(result.valid).toBe(false)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('listAction returns all stored bots', async () => {
|
|
148
|
+
const manager = new WebexBotCredentialManager(tempDir)
|
|
149
|
+
await manager.setCredentials({ token: 'token1', bot_id: 'bot1', bot_name: 'Bot 1' })
|
|
150
|
+
await manager.setCredentials({ token: 'token2', bot_id: 'bot2', bot_name: 'Bot 2' })
|
|
151
|
+
|
|
152
|
+
const result = await listAction({ _credManager: manager })
|
|
153
|
+
|
|
154
|
+
expect(result.bots).toHaveLength(2)
|
|
155
|
+
expect(result.bots?.find((b) => b.bot_id === 'bot2')?.is_current).toBe(true)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it('useAction switches current bot', async () => {
|
|
159
|
+
const manager = new WebexBotCredentialManager(tempDir)
|
|
160
|
+
await manager.setCredentials({ token: 'token1', bot_id: 'bot1', bot_name: 'Bot 1' })
|
|
161
|
+
await manager.setCredentials({ token: 'token2', bot_id: 'bot2', bot_name: 'Bot 2' })
|
|
162
|
+
|
|
163
|
+
const result = await useAction('bot1', { _credManager: manager })
|
|
164
|
+
|
|
165
|
+
expect(result.success).toBe(true)
|
|
166
|
+
expect(result.bot_id).toBe('bot1')
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('useAction returns error for unknown bot', async () => {
|
|
170
|
+
const manager = new WebexBotCredentialManager(tempDir)
|
|
171
|
+
|
|
172
|
+
const result = await useAction('nonexistent', { _credManager: manager })
|
|
173
|
+
expect(result.error).toBeDefined()
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
it('removeAction removes a stored bot', async () => {
|
|
177
|
+
const manager = new WebexBotCredentialManager(tempDir)
|
|
178
|
+
await manager.setCredentials({ token: 'token1', bot_id: 'bot1', bot_name: 'Bot 1' })
|
|
179
|
+
|
|
180
|
+
const result = await removeAction('bot1', { _credManager: manager })
|
|
181
|
+
|
|
182
|
+
expect(result.success).toBe(true)
|
|
183
|
+
expect(await manager.getCredentials('bot1')).toBeNull()
|
|
184
|
+
})
|
|
185
|
+
})
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
|
|
3
|
+
import { cliOutput } from '@/shared/utils/cli-output'
|
|
4
|
+
import { formatOutput } from '@/shared/utils/output'
|
|
5
|
+
|
|
6
|
+
import { WebexBotClient } from '../client'
|
|
7
|
+
import { WebexBotCredentialManager } from '../credential-manager'
|
|
8
|
+
|
|
9
|
+
interface ActionOptions {
|
|
10
|
+
pretty?: boolean
|
|
11
|
+
bot?: string
|
|
12
|
+
_credManager?: WebexBotCredentialManager
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface ActionResult {
|
|
16
|
+
success?: boolean
|
|
17
|
+
error?: string
|
|
18
|
+
bot_id?: string
|
|
19
|
+
bot_name?: string
|
|
20
|
+
valid?: boolean
|
|
21
|
+
bots?: Array<{
|
|
22
|
+
bot_id: string
|
|
23
|
+
bot_name: string
|
|
24
|
+
is_current: boolean
|
|
25
|
+
}>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function setAction(token: string, options: ActionOptions): Promise<ActionResult> {
|
|
29
|
+
try {
|
|
30
|
+
const client = await new WebexBotClient().login({ token })
|
|
31
|
+
const authInfo = await client.testAuth()
|
|
32
|
+
|
|
33
|
+
if (authInfo.type !== 'bot') {
|
|
34
|
+
return { error: 'Token is not a bot token. Use agent-webex for user tokens.' }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const botId = options.bot || authInfo.id || 'default'
|
|
38
|
+
const botName = authInfo.displayName || botId
|
|
39
|
+
|
|
40
|
+
const credManager = options._credManager ?? new WebexBotCredentialManager()
|
|
41
|
+
await credManager.setCredentials({
|
|
42
|
+
token,
|
|
43
|
+
bot_id: botId,
|
|
44
|
+
bot_name: botName,
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
success: true,
|
|
49
|
+
bot_id: botId,
|
|
50
|
+
bot_name: botName,
|
|
51
|
+
}
|
|
52
|
+
} catch (error) {
|
|
53
|
+
return { error: (error as Error).message }
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function clearAction(options: ActionOptions): Promise<ActionResult> {
|
|
58
|
+
try {
|
|
59
|
+
const credManager = options._credManager ?? new WebexBotCredentialManager()
|
|
60
|
+
await credManager.clearCredentials()
|
|
61
|
+
return { success: true }
|
|
62
|
+
} catch (error) {
|
|
63
|
+
return { error: (error as Error).message }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function statusAction(options: ActionOptions): Promise<ActionResult> {
|
|
68
|
+
try {
|
|
69
|
+
const credManager = options._credManager ?? new WebexBotCredentialManager()
|
|
70
|
+
const creds = await credManager.getCredentials(options.bot)
|
|
71
|
+
|
|
72
|
+
if (!creds) {
|
|
73
|
+
return {
|
|
74
|
+
valid: false,
|
|
75
|
+
error: options.bot
|
|
76
|
+
? `Bot "${options.bot}" not found. Run "auth list" to see available bots.`
|
|
77
|
+
: 'No credentials configured. Run "auth set <token>" first.',
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let valid = false
|
|
82
|
+
let authInfo: { id: string; displayName: string; type: 'person' | 'bot' } | null = null
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const client = await new WebexBotClient().login({ token: creds.token })
|
|
86
|
+
authInfo = await client.testAuth()
|
|
87
|
+
valid = authInfo.type === 'bot'
|
|
88
|
+
} catch {
|
|
89
|
+
valid = false
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
valid,
|
|
94
|
+
bot_id: authInfo?.id ?? creds.bot_id,
|
|
95
|
+
bot_name: authInfo?.displayName ?? creds.bot_name,
|
|
96
|
+
}
|
|
97
|
+
} catch (error) {
|
|
98
|
+
return { error: (error as Error).message }
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export async function listAction(options: ActionOptions): Promise<ActionResult> {
|
|
103
|
+
try {
|
|
104
|
+
const credManager = options._credManager ?? new WebexBotCredentialManager()
|
|
105
|
+
const all = await credManager.listAll()
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
bots: all.map((b) => ({
|
|
109
|
+
bot_id: b.bot_id,
|
|
110
|
+
bot_name: b.bot_name,
|
|
111
|
+
is_current: b.is_current,
|
|
112
|
+
})),
|
|
113
|
+
}
|
|
114
|
+
} catch (error) {
|
|
115
|
+
return { error: (error as Error).message }
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export async function useAction(botId: string, options: ActionOptions): Promise<ActionResult> {
|
|
120
|
+
try {
|
|
121
|
+
const credManager = options._credManager ?? new WebexBotCredentialManager()
|
|
122
|
+
const found = await credManager.setCurrent(botId)
|
|
123
|
+
|
|
124
|
+
if (!found) {
|
|
125
|
+
return { error: `Bot "${botId}" not found. Run "auth list" to see available bots.` }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const creds = await credManager.getCredentials()
|
|
129
|
+
return {
|
|
130
|
+
success: true,
|
|
131
|
+
bot_id: creds?.bot_id,
|
|
132
|
+
bot_name: creds?.bot_name,
|
|
133
|
+
}
|
|
134
|
+
} catch (error) {
|
|
135
|
+
return { error: (error as Error).message }
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export async function removeAction(botId: string, options: ActionOptions): Promise<ActionResult> {
|
|
140
|
+
try {
|
|
141
|
+
const credManager = options._credManager ?? new WebexBotCredentialManager()
|
|
142
|
+
const removed = await credManager.removeBot(botId)
|
|
143
|
+
|
|
144
|
+
if (!removed) {
|
|
145
|
+
return { error: `Bot "${botId}" not found. Run "auth list" to see available bots.` }
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return { success: true }
|
|
149
|
+
} catch (error) {
|
|
150
|
+
return { error: (error as Error).message }
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export const authCommand = new Command('auth')
|
|
155
|
+
.description('Bot authentication commands')
|
|
156
|
+
.addCommand(
|
|
157
|
+
new Command('set')
|
|
158
|
+
.description('Set bot token')
|
|
159
|
+
.argument('<token>', 'Bot token')
|
|
160
|
+
.option('--bot <id>', 'Bot identifier for switching later')
|
|
161
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
162
|
+
.action(async (token: string, opts: { bot?: string; pretty?: boolean }) => {
|
|
163
|
+
cliOutput(await setAction(token, opts), opts.pretty)
|
|
164
|
+
}),
|
|
165
|
+
)
|
|
166
|
+
.addCommand(
|
|
167
|
+
new Command('clear')
|
|
168
|
+
.description('Clear all stored credentials')
|
|
169
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
170
|
+
.action(async (opts: { pretty?: boolean }) => {
|
|
171
|
+
cliOutput(await clearAction(opts), opts.pretty)
|
|
172
|
+
}),
|
|
173
|
+
)
|
|
174
|
+
.addCommand(
|
|
175
|
+
new Command('status')
|
|
176
|
+
.description('Show authentication status')
|
|
177
|
+
.option('--bot <id>', 'Check specific bot (default: current)')
|
|
178
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
179
|
+
.action(async (opts: { bot?: string; pretty?: boolean }) => {
|
|
180
|
+
const result = await statusAction(opts)
|
|
181
|
+
console.log(formatOutput(result, opts.pretty))
|
|
182
|
+
if (!result.valid) process.exit(1)
|
|
183
|
+
}),
|
|
184
|
+
)
|
|
185
|
+
.addCommand(
|
|
186
|
+
new Command('list')
|
|
187
|
+
.description('List all stored bots')
|
|
188
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
189
|
+
.action(async (opts: { pretty?: boolean }) => {
|
|
190
|
+
cliOutput(await listAction(opts), opts.pretty)
|
|
191
|
+
}),
|
|
192
|
+
)
|
|
193
|
+
.addCommand(
|
|
194
|
+
new Command('use')
|
|
195
|
+
.description('Switch active bot')
|
|
196
|
+
.argument('<bot>', 'Bot ID')
|
|
197
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
198
|
+
.action(async (botId: string, opts: { pretty?: boolean }) => {
|
|
199
|
+
cliOutput(await useAction(botId, opts), opts.pretty)
|
|
200
|
+
}),
|
|
201
|
+
)
|
|
202
|
+
.addCommand(
|
|
203
|
+
new Command('remove')
|
|
204
|
+
.description('Remove a stored bot')
|
|
205
|
+
.argument('<bot>', 'Bot ID')
|
|
206
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
207
|
+
.action(async (botId: string, opts: { pretty?: boolean }) => {
|
|
208
|
+
cliOutput(await removeAction(botId, opts), opts.pretty)
|
|
209
|
+
}),
|
|
210
|
+
)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import { parseEvents } from './listen'
|
|
4
|
+
|
|
5
|
+
describe('webexbot listen parseEvents', () => {
|
|
6
|
+
it('returns defaults when no filter is given', () => {
|
|
7
|
+
const events = parseEvents(undefined)
|
|
8
|
+
expect(events).toContain('message_created')
|
|
9
|
+
expect(events).toContain('error')
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('accepts known event names', () => {
|
|
13
|
+
expect(parseEvents('message_created,disconnected')).toEqual(['message_created', 'disconnected'])
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('throws on unknown event names and lists supported events', () => {
|
|
17
|
+
expect(() => parseEvents('message_created,mesage_deleted')).toThrow(/Unknown event\(s\): mesage_deleted/)
|
|
18
|
+
expect(() => parseEvents('bogus')).toThrow(/Supported events:/)
|
|
19
|
+
})
|
|
20
|
+
})
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
|
|
3
|
+
import { cliOutput } from '@/shared/utils/cli-output'
|
|
4
|
+
|
|
5
|
+
import { WebexBotListener } from '../listener'
|
|
6
|
+
import type { WebexBotListenerEventMap } from '../types'
|
|
7
|
+
import type { BotOption } from './shared'
|
|
8
|
+
import { getClient } from './shared'
|
|
9
|
+
|
|
10
|
+
type ListenEvent = keyof WebexBotListenerEventMap
|
|
11
|
+
|
|
12
|
+
const SUPPORTED_EVENTS = new Set<ListenEvent>([
|
|
13
|
+
'message_created',
|
|
14
|
+
'message_updated',
|
|
15
|
+
'message_deleted',
|
|
16
|
+
'membership_created',
|
|
17
|
+
'attachment_action',
|
|
18
|
+
'room_created',
|
|
19
|
+
'room_updated',
|
|
20
|
+
'webex_event',
|
|
21
|
+
'connected',
|
|
22
|
+
'reconnecting',
|
|
23
|
+
'disconnected',
|
|
24
|
+
'error',
|
|
25
|
+
])
|
|
26
|
+
|
|
27
|
+
const DEFAULT_EVENTS: ListenEvent[] = [
|
|
28
|
+
'message_created',
|
|
29
|
+
'message_updated',
|
|
30
|
+
'message_deleted',
|
|
31
|
+
'membership_created',
|
|
32
|
+
'attachment_action',
|
|
33
|
+
'connected',
|
|
34
|
+
'reconnecting',
|
|
35
|
+
'disconnected',
|
|
36
|
+
'error',
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
interface ListenOptions extends BotOption {
|
|
40
|
+
events?: string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function printEvent(type: string, payload: unknown, pretty?: boolean): void {
|
|
44
|
+
const line = payload === undefined ? { type } : { type, payload }
|
|
45
|
+
console.log(JSON.stringify(line, null, pretty ? 2 : undefined))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function parseEvents(events?: string): ListenEvent[] {
|
|
49
|
+
if (!events) return DEFAULT_EVENTS
|
|
50
|
+
|
|
51
|
+
const tokens = events
|
|
52
|
+
.split(',')
|
|
53
|
+
.map((event) => event.trim())
|
|
54
|
+
.filter((event) => event.length > 0)
|
|
55
|
+
|
|
56
|
+
const unknown = tokens.filter((event) => !SUPPORTED_EVENTS.has(event as ListenEvent))
|
|
57
|
+
if (unknown.length > 0) {
|
|
58
|
+
const supported = [...SUPPORTED_EVENTS].join(', ')
|
|
59
|
+
throw new Error(`Unknown event(s): ${unknown.join(', ')}. Supported events: ${supported}`)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return tokens as ListenEvent[]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function listenAction(options: ListenOptions): Promise<void> {
|
|
66
|
+
let events: ListenEvent[]
|
|
67
|
+
try {
|
|
68
|
+
events = parseEvents(options.events)
|
|
69
|
+
} catch (error) {
|
|
70
|
+
cliOutput({ error: (error as Error).message }, options.pretty, true)
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const client = await getClient(options)
|
|
75
|
+
const listener = new WebexBotListener(client)
|
|
76
|
+
|
|
77
|
+
for (const event of events) {
|
|
78
|
+
if (event === 'error') {
|
|
79
|
+
listener.on(event, (error) => printEvent(event, { message: error.message, name: error.name }, options.pretty))
|
|
80
|
+
continue
|
|
81
|
+
}
|
|
82
|
+
listener.on(event, (payload) => printEvent(event, payload, options.pretty))
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
process.once('SIGINT', () => {
|
|
86
|
+
void listener.stop().finally(() => process.exit(0))
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
await listener.start()
|
|
91
|
+
} catch (error) {
|
|
92
|
+
printEvent('error', { message: (error as Error).message, name: (error as Error).name }, options.pretty)
|
|
93
|
+
process.exit(1)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export const listenCommand = new Command('listen')
|
|
98
|
+
.description('Listen for real-time Webex bot events')
|
|
99
|
+
.option('--events <list>', 'Comma-separated event filter')
|
|
100
|
+
.option('--bot <id>', 'Use specific bot')
|
|
101
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
102
|
+
.action(async (opts: ListenOptions) => {
|
|
103
|
+
await listenAction(opts)
|
|
104
|
+
})
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
|
|
3
|
+
import { cliOutput } from '@/shared/utils/cli-output'
|
|
4
|
+
|
|
5
|
+
import type { BotOption } from './shared'
|
|
6
|
+
import { getClient } from './shared'
|
|
7
|
+
|
|
8
|
+
interface MemberResult {
|
|
9
|
+
members?: Array<{
|
|
10
|
+
id: string
|
|
11
|
+
personId: string
|
|
12
|
+
personEmail: string
|
|
13
|
+
personDisplayName: string
|
|
14
|
+
isModerator: boolean
|
|
15
|
+
created: string
|
|
16
|
+
}>
|
|
17
|
+
error?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function listAction(space: string, options: BotOption & { max?: string }): Promise<MemberResult> {
|
|
21
|
+
try {
|
|
22
|
+
const client = await getClient(options)
|
|
23
|
+
const max = options.max ? parseInt(options.max, 10) : 100
|
|
24
|
+
const members = await client.listMemberships(space, { max })
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
members: members.map((m) => ({
|
|
28
|
+
id: m.id,
|
|
29
|
+
personId: m.personId,
|
|
30
|
+
personEmail: m.personEmail,
|
|
31
|
+
personDisplayName: m.personDisplayName,
|
|
32
|
+
isModerator: m.isModerator,
|
|
33
|
+
created: m.created,
|
|
34
|
+
})),
|
|
35
|
+
}
|
|
36
|
+
} catch (error) {
|
|
37
|
+
return { error: (error as Error).message }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const memberCommand = new Command('member').description('Member commands').addCommand(
|
|
42
|
+
new Command('list')
|
|
43
|
+
.description('List members of a space')
|
|
44
|
+
.argument('<space>', 'Space ID')
|
|
45
|
+
.option('--max <n>', 'Number of members to retrieve', '100')
|
|
46
|
+
.option('--bot <id>', 'Use specific bot')
|
|
47
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
48
|
+
.action(async (space: string, opts: BotOption & { max?: string }) => {
|
|
49
|
+
cliOutput(await listAction(space, opts), opts.pretty)
|
|
50
|
+
}),
|
|
51
|
+
)
|