agent-messenger 2.20.5 → 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/dist/package.json +9 -1
- 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 +9 -1
- 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,197 @@
|
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
|
|
3
|
+
import { cliOutput } from '@/shared/utils/cli-output'
|
|
4
|
+
|
|
5
|
+
import type { WebexMessage } from '../../webex/types'
|
|
6
|
+
import type { BotOption } from './shared'
|
|
7
|
+
import { getClient } from './shared'
|
|
8
|
+
|
|
9
|
+
interface MessageResult {
|
|
10
|
+
id?: string
|
|
11
|
+
roomId?: string
|
|
12
|
+
text?: string
|
|
13
|
+
markdown?: string
|
|
14
|
+
html?: string
|
|
15
|
+
personEmail?: string
|
|
16
|
+
created?: string
|
|
17
|
+
messages?: Array<{
|
|
18
|
+
id: string
|
|
19
|
+
roomId: string
|
|
20
|
+
text?: string
|
|
21
|
+
personEmail: string
|
|
22
|
+
created: string
|
|
23
|
+
}>
|
|
24
|
+
deleted?: string
|
|
25
|
+
error?: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function formatMessage(message: WebexMessage): MessageResult {
|
|
29
|
+
return {
|
|
30
|
+
id: message.id,
|
|
31
|
+
roomId: message.roomId,
|
|
32
|
+
text: message.text,
|
|
33
|
+
markdown: message.markdown,
|
|
34
|
+
html: message.html,
|
|
35
|
+
personEmail: message.personEmail,
|
|
36
|
+
created: message.created,
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function sendAction(
|
|
41
|
+
space: string,
|
|
42
|
+
text: string,
|
|
43
|
+
options: BotOption & { markdown?: boolean },
|
|
44
|
+
): Promise<MessageResult> {
|
|
45
|
+
try {
|
|
46
|
+
const client = await getClient(options)
|
|
47
|
+
const message = await client.sendMessage(space, text, { markdown: options.markdown })
|
|
48
|
+
|
|
49
|
+
return formatMessage(message)
|
|
50
|
+
} catch (error) {
|
|
51
|
+
return { error: (error as Error).message }
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function dmAction(
|
|
56
|
+
email: string,
|
|
57
|
+
text: string,
|
|
58
|
+
options: BotOption & { markdown?: boolean },
|
|
59
|
+
): Promise<MessageResult> {
|
|
60
|
+
try {
|
|
61
|
+
const client = await getClient(options)
|
|
62
|
+
const message = await client.sendDirectMessage(email, text, { markdown: options.markdown })
|
|
63
|
+
|
|
64
|
+
return formatMessage(message)
|
|
65
|
+
} catch (error) {
|
|
66
|
+
return { error: (error as Error).message }
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function listAction(space: string, options: BotOption & { max?: string }): Promise<MessageResult> {
|
|
71
|
+
try {
|
|
72
|
+
const client = await getClient(options)
|
|
73
|
+
const max = options.max ? parseInt(options.max, 10) : 50
|
|
74
|
+
const messages = await client.listMessages(space, { max })
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
messages: messages.map((msg) => ({
|
|
78
|
+
id: msg.id,
|
|
79
|
+
roomId: msg.roomId,
|
|
80
|
+
text: msg.text,
|
|
81
|
+
personEmail: msg.personEmail,
|
|
82
|
+
created: msg.created,
|
|
83
|
+
})),
|
|
84
|
+
}
|
|
85
|
+
} catch (error) {
|
|
86
|
+
return { error: (error as Error).message }
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function getAction(messageId: string, options: BotOption): Promise<MessageResult> {
|
|
91
|
+
try {
|
|
92
|
+
const client = await getClient(options)
|
|
93
|
+
const message = await client.getMessage(messageId)
|
|
94
|
+
|
|
95
|
+
return formatMessage(message)
|
|
96
|
+
} catch (error) {
|
|
97
|
+
return { error: (error as Error).message }
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export async function deleteAction(messageId: string, options: BotOption): Promise<MessageResult> {
|
|
102
|
+
try {
|
|
103
|
+
const client = await getClient(options)
|
|
104
|
+
await client.deleteMessage(messageId)
|
|
105
|
+
|
|
106
|
+
return { deleted: messageId }
|
|
107
|
+
} catch (error) {
|
|
108
|
+
return { error: (error as Error).message }
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export async function editAction(
|
|
113
|
+
messageId: string,
|
|
114
|
+
space: string,
|
|
115
|
+
text: string,
|
|
116
|
+
options: BotOption & { markdown?: boolean },
|
|
117
|
+
): Promise<MessageResult> {
|
|
118
|
+
try {
|
|
119
|
+
const client = await getClient(options)
|
|
120
|
+
const message = await client.editMessage(messageId, space, text, { markdown: options.markdown })
|
|
121
|
+
|
|
122
|
+
return formatMessage(message)
|
|
123
|
+
} catch (error) {
|
|
124
|
+
return { error: (error as Error).message }
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export const messageCommand = new Command('message')
|
|
129
|
+
.description('Message commands')
|
|
130
|
+
.addCommand(
|
|
131
|
+
new Command('send')
|
|
132
|
+
.description('Send a message to a space')
|
|
133
|
+
.argument('<space>', 'Space/Room ID')
|
|
134
|
+
.argument('<text>', 'Message text')
|
|
135
|
+
.option('--markdown', 'Send as markdown')
|
|
136
|
+
.option('--bot <id>', 'Use specific bot')
|
|
137
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
138
|
+
.action(async (space: string, text: string, opts: BotOption & { markdown?: boolean }) => {
|
|
139
|
+
cliOutput(await sendAction(space, text, opts), opts.pretty)
|
|
140
|
+
}),
|
|
141
|
+
)
|
|
142
|
+
.addCommand(
|
|
143
|
+
new Command('dm')
|
|
144
|
+
.description('Send a direct message by recipient email')
|
|
145
|
+
.argument('<email>', 'Recipient email address')
|
|
146
|
+
.argument('<text>', 'Message text')
|
|
147
|
+
.option('--markdown', 'Send as markdown')
|
|
148
|
+
.option('--bot <id>', 'Use specific bot')
|
|
149
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
150
|
+
.action(async (email: string, text: string, opts: BotOption & { markdown?: boolean }) => {
|
|
151
|
+
cliOutput(await dmAction(email, text, opts), opts.pretty)
|
|
152
|
+
}),
|
|
153
|
+
)
|
|
154
|
+
.addCommand(
|
|
155
|
+
new Command('list')
|
|
156
|
+
.description('List messages in a space')
|
|
157
|
+
.argument('<space>', 'Space/Room ID')
|
|
158
|
+
.option('--max <n>', 'Number of messages to fetch', '50')
|
|
159
|
+
.option('--bot <id>', 'Use specific bot')
|
|
160
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
161
|
+
.action(async (space: string, opts: BotOption & { max?: string }) => {
|
|
162
|
+
cliOutput(await listAction(space, opts), opts.pretty)
|
|
163
|
+
}),
|
|
164
|
+
)
|
|
165
|
+
.addCommand(
|
|
166
|
+
new Command('get')
|
|
167
|
+
.description('Get a single message')
|
|
168
|
+
.argument('<id>', 'Message ID')
|
|
169
|
+
.option('--bot <id>', 'Use specific bot')
|
|
170
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
171
|
+
.action(async (messageId: string, opts: BotOption) => {
|
|
172
|
+
cliOutput(await getAction(messageId, opts), opts.pretty)
|
|
173
|
+
}),
|
|
174
|
+
)
|
|
175
|
+
.addCommand(
|
|
176
|
+
new Command('delete')
|
|
177
|
+
.description('Delete a message')
|
|
178
|
+
.argument('<id>', 'Message ID')
|
|
179
|
+
.option('--bot <id>', 'Use specific bot')
|
|
180
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
181
|
+
.action(async (messageId: string, opts: BotOption) => {
|
|
182
|
+
cliOutput(await deleteAction(messageId, opts), opts.pretty)
|
|
183
|
+
}),
|
|
184
|
+
)
|
|
185
|
+
.addCommand(
|
|
186
|
+
new Command('edit')
|
|
187
|
+
.description('Edit a message')
|
|
188
|
+
.argument('<id>', 'Message ID')
|
|
189
|
+
.argument('<space>', 'Space/Room ID')
|
|
190
|
+
.argument('<text>', 'New message text')
|
|
191
|
+
.option('--markdown', 'Send as markdown')
|
|
192
|
+
.option('--bot <id>', 'Use specific bot')
|
|
193
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
194
|
+
.action(async (messageId: string, space: string, text: string, opts: BotOption & { markdown?: boolean }) => {
|
|
195
|
+
cliOutput(await editAction(messageId, space, text, opts), opts.pretty)
|
|
196
|
+
}),
|
|
197
|
+
)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { formatOutput } from '@/shared/utils/output'
|
|
2
|
+
|
|
3
|
+
import { WebexBotClient } from '../client'
|
|
4
|
+
import { WebexBotCredentialManager } from '../credential-manager'
|
|
5
|
+
|
|
6
|
+
export interface BotOption {
|
|
7
|
+
bot?: string
|
|
8
|
+
pretty?: boolean
|
|
9
|
+
_credManager?: WebexBotCredentialManager
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function getClient(options: BotOption): Promise<WebexBotClient> {
|
|
13
|
+
const credManager = options._credManager ?? new WebexBotCredentialManager()
|
|
14
|
+
const creds = await credManager.getCredentials(options.bot)
|
|
15
|
+
|
|
16
|
+
if (!creds) {
|
|
17
|
+
console.log(formatOutput({ error: 'No credentials. Run "auth set <token>" first.' }, options.pretty))
|
|
18
|
+
process.exit(1)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return new WebexBotClient().login({ token: creds.token })
|
|
22
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
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 SpaceResult {
|
|
9
|
+
id?: string
|
|
10
|
+
title?: string
|
|
11
|
+
type?: 'group' | 'direct'
|
|
12
|
+
isLocked?: boolean
|
|
13
|
+
teamId?: string | null
|
|
14
|
+
lastActivity?: string
|
|
15
|
+
created?: string
|
|
16
|
+
creatorId?: string
|
|
17
|
+
spaces?: Array<{
|
|
18
|
+
id: string
|
|
19
|
+
title: string
|
|
20
|
+
type: 'group' | 'direct'
|
|
21
|
+
lastActivity: string
|
|
22
|
+
created: string
|
|
23
|
+
}>
|
|
24
|
+
error?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function listAction(options: BotOption & { max?: string; type?: string }): Promise<SpaceResult> {
|
|
28
|
+
try {
|
|
29
|
+
const client = await getClient(options)
|
|
30
|
+
const max = options.max ? parseInt(options.max, 10) : 50
|
|
31
|
+
const spaces = await client.listSpaces({ type: options.type, max })
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
spaces: spaces.map((s) => ({
|
|
35
|
+
id: s.id,
|
|
36
|
+
title: s.title,
|
|
37
|
+
type: s.type,
|
|
38
|
+
lastActivity: s.lastActivity,
|
|
39
|
+
created: s.created,
|
|
40
|
+
})),
|
|
41
|
+
}
|
|
42
|
+
} catch (error) {
|
|
43
|
+
return { error: (error as Error).message }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function infoAction(spaceId: string, options: BotOption): Promise<SpaceResult> {
|
|
48
|
+
try {
|
|
49
|
+
const client = await getClient(options)
|
|
50
|
+
const space = await client.getSpace(spaceId)
|
|
51
|
+
return {
|
|
52
|
+
id: space.id,
|
|
53
|
+
title: space.title,
|
|
54
|
+
type: space.type,
|
|
55
|
+
isLocked: space.isLocked,
|
|
56
|
+
teamId: space.teamId || null,
|
|
57
|
+
lastActivity: space.lastActivity,
|
|
58
|
+
created: space.created,
|
|
59
|
+
creatorId: space.creatorId,
|
|
60
|
+
}
|
|
61
|
+
} catch (error) {
|
|
62
|
+
return { error: (error as Error).message }
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const spaceCommand = new Command('space')
|
|
67
|
+
.description('Space commands')
|
|
68
|
+
.addCommand(
|
|
69
|
+
new Command('list')
|
|
70
|
+
.description('List spaces')
|
|
71
|
+
.option('--max <n>', 'Number of spaces to retrieve', '50')
|
|
72
|
+
.option('--type <type>', 'Filter by type (group or direct)')
|
|
73
|
+
.option('--bot <id>', 'Use specific bot')
|
|
74
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
75
|
+
.action(async (opts: BotOption & { max?: string; type?: string }) => {
|
|
76
|
+
cliOutput(await listAction(opts), opts.pretty)
|
|
77
|
+
}),
|
|
78
|
+
)
|
|
79
|
+
.addCommand(
|
|
80
|
+
new Command('info')
|
|
81
|
+
.description('Get space details')
|
|
82
|
+
.argument('<id>', 'Space ID')
|
|
83
|
+
.option('--bot <id>', 'Use specific bot')
|
|
84
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
85
|
+
.action(async (spaceId: string, opts: BotOption) => {
|
|
86
|
+
cliOutput(await infoAction(spaceId, opts), opts.pretty)
|
|
87
|
+
}),
|
|
88
|
+
)
|
|
@@ -0,0 +1,43 @@
|
|
|
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 WhoamiResult {
|
|
9
|
+
id?: string
|
|
10
|
+
emails?: string[]
|
|
11
|
+
displayName?: string
|
|
12
|
+
avatar?: string
|
|
13
|
+
orgId?: string
|
|
14
|
+
type?: 'person' | 'bot'
|
|
15
|
+
created?: string
|
|
16
|
+
error?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function whoamiAction(options: BotOption): Promise<WhoamiResult> {
|
|
20
|
+
try {
|
|
21
|
+
const client = await getClient(options)
|
|
22
|
+
const info = await client.testAuth()
|
|
23
|
+
return {
|
|
24
|
+
id: info.id,
|
|
25
|
+
emails: info.emails,
|
|
26
|
+
displayName: info.displayName,
|
|
27
|
+
avatar: info.avatar,
|
|
28
|
+
orgId: info.orgId,
|
|
29
|
+
type: info.type,
|
|
30
|
+
created: info.created,
|
|
31
|
+
}
|
|
32
|
+
} catch (error) {
|
|
33
|
+
return { error: (error as Error).message }
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const whoamiCommand = new Command('whoami')
|
|
38
|
+
.description('Show current authenticated bot')
|
|
39
|
+
.option('--bot <id>', 'Bot ID to use')
|
|
40
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
41
|
+
.action(async (opts: BotOption) => {
|
|
42
|
+
cliOutput(await whoamiAction(opts), opts.pretty)
|
|
43
|
+
})
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } 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
|
+
|
|
7
|
+
import { WebexBotCredentialManager } from './credential-manager'
|
|
8
|
+
|
|
9
|
+
const CREDS_A = {
|
|
10
|
+
token: 'bot-token-a',
|
|
11
|
+
bot_id: 'bot-123',
|
|
12
|
+
bot_name: 'Bot A',
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const CREDS_B = {
|
|
16
|
+
token: 'bot-token-b',
|
|
17
|
+
bot_id: 'bot-456',
|
|
18
|
+
bot_name: 'Bot B',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('WebexBotCredentialManager', () => {
|
|
22
|
+
let tempDir: string
|
|
23
|
+
let manager: WebexBotCredentialManager
|
|
24
|
+
|
|
25
|
+
beforeEach(async () => {
|
|
26
|
+
tempDir = join(tmpdir(), `webexbot-cred-test-${Date.now()}`)
|
|
27
|
+
await mkdir(tempDir, { recursive: true })
|
|
28
|
+
manager = new WebexBotCredentialManager(tempDir)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
if (existsSync(tempDir)) {
|
|
33
|
+
rmSync(tempDir, { recursive: true })
|
|
34
|
+
}
|
|
35
|
+
delete process.env.E2E_WEBEXBOT_TOKEN
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('returns empty config when no file exists', async () => {
|
|
39
|
+
const config = await manager.load()
|
|
40
|
+
|
|
41
|
+
expect(config.current).toBeNull()
|
|
42
|
+
expect(config.bots).toEqual({})
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('persists config to file', async () => {
|
|
46
|
+
const config = {
|
|
47
|
+
current: { bot_id: 'bot-123' },
|
|
48
|
+
bots: {
|
|
49
|
+
'bot-123': { bot_id: 'bot-123', bot_name: 'Test Bot', token: 'test-token' },
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
await manager.save(config)
|
|
54
|
+
const loaded = await manager.load()
|
|
55
|
+
|
|
56
|
+
expect(loaded).toEqual(config)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('returns null when no credentials exist', async () => {
|
|
60
|
+
expect(await manager.getCredentials()).toBeNull()
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('returns current bot credentials', async () => {
|
|
64
|
+
await manager.setCredentials(CREDS_A)
|
|
65
|
+
|
|
66
|
+
const creds = await manager.getCredentials()
|
|
67
|
+
|
|
68
|
+
expect(creds).toEqual(CREDS_A)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('returns specific bot by id', async () => {
|
|
72
|
+
await manager.setCredentials(CREDS_A)
|
|
73
|
+
await manager.setCredentials(CREDS_B)
|
|
74
|
+
|
|
75
|
+
const creds = await manager.getCredentials('bot-123')
|
|
76
|
+
|
|
77
|
+
expect(creds).toEqual(CREDS_A)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('returns null for non-existent bot id', async () => {
|
|
81
|
+
await manager.setCredentials(CREDS_A)
|
|
82
|
+
|
|
83
|
+
const creds = await manager.getCredentials('nonexistent')
|
|
84
|
+
|
|
85
|
+
expect(creds).toBeNull()
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('env var takes precedence when no botId specified', async () => {
|
|
89
|
+
await manager.setCredentials(CREDS_A)
|
|
90
|
+
|
|
91
|
+
process.env.E2E_WEBEXBOT_TOKEN = 'env-token'
|
|
92
|
+
|
|
93
|
+
const creds = await manager.getCredentials()
|
|
94
|
+
|
|
95
|
+
expect(creds).toEqual({ token: 'env-token', bot_id: 'env', bot_name: 'env' })
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('env var is ignored when botId explicitly provided', async () => {
|
|
99
|
+
await manager.setCredentials(CREDS_A)
|
|
100
|
+
|
|
101
|
+
process.env.E2E_WEBEXBOT_TOKEN = 'env-token'
|
|
102
|
+
|
|
103
|
+
const creds = await manager.getCredentials('bot-123')
|
|
104
|
+
|
|
105
|
+
expect(creds).toEqual(CREDS_A)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('stores multiple bots and sets latest as current', async () => {
|
|
109
|
+
await manager.setCredentials(CREDS_A)
|
|
110
|
+
await manager.setCredentials(CREDS_B)
|
|
111
|
+
|
|
112
|
+
const config = await manager.load()
|
|
113
|
+
expect(Object.keys(config.bots)).toEqual(['bot-123', 'bot-456'])
|
|
114
|
+
expect(config.current).toEqual({ bot_id: 'bot-456' })
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('returns all bots with current flag', async () => {
|
|
118
|
+
await manager.setCredentials(CREDS_A)
|
|
119
|
+
await manager.setCredentials(CREDS_B)
|
|
120
|
+
|
|
121
|
+
const all = await manager.listAll()
|
|
122
|
+
|
|
123
|
+
expect(all).toHaveLength(2)
|
|
124
|
+
expect(all.find((b) => b.bot_id === 'bot-123')?.is_current).toBe(false)
|
|
125
|
+
expect(all.find((b) => b.bot_id === 'bot-456')?.is_current).toBe(true)
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('switches current bot', async () => {
|
|
129
|
+
await manager.setCredentials(CREDS_A)
|
|
130
|
+
await manager.setCredentials(CREDS_B)
|
|
131
|
+
|
|
132
|
+
const switched = await manager.setCurrent('bot-123')
|
|
133
|
+
|
|
134
|
+
expect(switched).toBe(true)
|
|
135
|
+
const creds = await manager.getCredentials()
|
|
136
|
+
expect(creds?.bot_id).toBe('bot-123')
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it('returns false when switching to unknown bot', async () => {
|
|
140
|
+
expect(await manager.setCurrent('nonexistent')).toBe(false)
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('removes a bot by id', async () => {
|
|
144
|
+
await manager.setCredentials(CREDS_A)
|
|
145
|
+
await manager.setCredentials(CREDS_B)
|
|
146
|
+
|
|
147
|
+
const removed = await manager.removeBot('bot-123')
|
|
148
|
+
|
|
149
|
+
expect(removed).toBe(true)
|
|
150
|
+
const config = await manager.load()
|
|
151
|
+
expect(Object.keys(config.bots)).toEqual(['bot-456'])
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('clears current when current bot is removed', async () => {
|
|
155
|
+
await manager.setCredentials(CREDS_A)
|
|
156
|
+
|
|
157
|
+
await manager.removeBot('bot-123')
|
|
158
|
+
|
|
159
|
+
const config = await manager.load()
|
|
160
|
+
expect(config.current).toBeNull()
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('clears all credentials', async () => {
|
|
164
|
+
await manager.setCredentials(CREDS_A)
|
|
165
|
+
await manager.setCredentials(CREDS_B)
|
|
166
|
+
|
|
167
|
+
await manager.clearCredentials()
|
|
168
|
+
|
|
169
|
+
const config = await manager.load()
|
|
170
|
+
expect(config.current).toBeNull()
|
|
171
|
+
expect(config.bots).toEqual({})
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
it('saves file with secure permissions', async () => {
|
|
175
|
+
await manager.setCredentials(CREDS_A)
|
|
176
|
+
|
|
177
|
+
const credPath = join(tempDir, 'webexbot-credentials.json')
|
|
178
|
+
const stats = await stat(credPath)
|
|
179
|
+
|
|
180
|
+
expect(stats.mode & 0o777).toBe(0o600)
|
|
181
|
+
})
|
|
182
|
+
})
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
|
|
5
|
+
import { getConfigDir } from '../../shared/utils/config-dir'
|
|
6
|
+
import type { WebexBotConfig, WebexBotCredentials } from './types'
|
|
7
|
+
import { WebexBotConfigSchema } from './types'
|
|
8
|
+
|
|
9
|
+
export class WebexBotCredentialManager {
|
|
10
|
+
private configDir: string
|
|
11
|
+
private credentialsPath: string
|
|
12
|
+
|
|
13
|
+
constructor(configDir?: string) {
|
|
14
|
+
this.configDir = configDir ?? getConfigDir()
|
|
15
|
+
this.credentialsPath = join(this.configDir, 'webexbot-credentials.json')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async load(): Promise<WebexBotConfig> {
|
|
19
|
+
if (!existsSync(this.credentialsPath)) {
|
|
20
|
+
return { current: null, bots: {} }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const content = await readFile(this.credentialsPath, 'utf-8')
|
|
24
|
+
|
|
25
|
+
let json: unknown
|
|
26
|
+
try {
|
|
27
|
+
json = JSON.parse(content)
|
|
28
|
+
} catch {
|
|
29
|
+
return { current: null, bots: {} }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const parsed = WebexBotConfigSchema.safeParse(json)
|
|
33
|
+
if (!parsed.success) {
|
|
34
|
+
return { current: null, bots: {} }
|
|
35
|
+
}
|
|
36
|
+
return parsed.data
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async save(config: WebexBotConfig): Promise<void> {
|
|
40
|
+
await mkdir(this.configDir, { recursive: true })
|
|
41
|
+
await writeFile(this.credentialsPath, JSON.stringify(config, null, 2), { mode: 0o600 })
|
|
42
|
+
await chmod(this.credentialsPath, 0o600)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async getCredentials(botId?: string): Promise<WebexBotCredentials | null> {
|
|
46
|
+
const envToken = process.env.E2E_WEBEXBOT_TOKEN
|
|
47
|
+
|
|
48
|
+
if (envToken && !botId) {
|
|
49
|
+
return {
|
|
50
|
+
token: envToken,
|
|
51
|
+
bot_id: 'env',
|
|
52
|
+
bot_name: 'env',
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const config = await this.load()
|
|
57
|
+
|
|
58
|
+
if (botId) {
|
|
59
|
+
const bot = config.bots[botId]
|
|
60
|
+
if (!bot) return null
|
|
61
|
+
return {
|
|
62
|
+
token: bot.token,
|
|
63
|
+
bot_id: bot.bot_id,
|
|
64
|
+
bot_name: bot.bot_name,
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!config.current) {
|
|
69
|
+
return null
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const bot = config.bots[config.current.bot_id]
|
|
73
|
+
if (!bot) return null
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
token: bot.token,
|
|
77
|
+
bot_id: bot.bot_id,
|
|
78
|
+
bot_name: bot.bot_name,
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async setCredentials(creds: WebexBotCredentials): Promise<void> {
|
|
83
|
+
const config = await this.load()
|
|
84
|
+
|
|
85
|
+
config.bots[creds.bot_id] = {
|
|
86
|
+
bot_id: creds.bot_id,
|
|
87
|
+
bot_name: creds.bot_name,
|
|
88
|
+
token: creds.token,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
config.current = {
|
|
92
|
+
bot_id: creds.bot_id,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
await this.save(config)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async removeBot(botId: string): Promise<boolean> {
|
|
99
|
+
const config = await this.load()
|
|
100
|
+
|
|
101
|
+
if (!config.bots[botId]) {
|
|
102
|
+
return false
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
delete config.bots[botId]
|
|
106
|
+
|
|
107
|
+
if (config.current?.bot_id === botId) {
|
|
108
|
+
config.current = null
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
await this.save(config)
|
|
112
|
+
return true
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async setCurrent(botId: string): Promise<boolean> {
|
|
116
|
+
const config = await this.load()
|
|
117
|
+
|
|
118
|
+
if (!config.bots[botId]) {
|
|
119
|
+
return false
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
config.current = {
|
|
123
|
+
bot_id: botId,
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
await this.save(config)
|
|
127
|
+
return true
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async listAll(): Promise<Array<WebexBotCredentials & { is_current: boolean }>> {
|
|
131
|
+
const config = await this.load()
|
|
132
|
+
const results: Array<WebexBotCredentials & { is_current: boolean }> = []
|
|
133
|
+
|
|
134
|
+
for (const bot of Object.values(config.bots)) {
|
|
135
|
+
results.push({
|
|
136
|
+
token: bot.token,
|
|
137
|
+
bot_id: bot.bot_id,
|
|
138
|
+
bot_name: bot.bot_name,
|
|
139
|
+
is_current: config.current?.bot_id === bot.bot_id,
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return results
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async clearCredentials(): Promise<void> {
|
|
147
|
+
await this.save({ current: null, bots: {} })
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { WebexBotClient } from './client'
|
|
2
|
+
export { WebexBotCredentialManager } from './credential-manager'
|
|
3
|
+
export { WebexBotListener } from './listener'
|
|
4
|
+
export type { WebexBotListenerOptions } from './listener'
|
|
5
|
+
export type { WebexBotConfig, WebexBotCredentials, WebexBotEntry, WebexBotListenerEventMap } from './types'
|
|
6
|
+
export { WebexBotConfigSchema, WebexBotCredentialsSchema, WebexBotEntrySchema, WebexBotError } from './types'
|
|
7
|
+
export type { WebexMembership, WebexMessage, WebexPerson, WebexSpace } from '../webex/types'
|
|
8
|
+
export { WebexMembershipSchema, WebexMessageSchema, WebexPersonSchema, WebexSpaceSchema } from '../webex/types'
|