agent-messenger 2.23.1 → 2.23.2
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/dist/package.json +1 -1
- package/dist/src/platforms/webex/client.d.ts +18 -0
- package/dist/src/platforms/webex/client.d.ts.map +1 -1
- package/dist/src/platforms/webex/client.js +178 -37
- package/dist/src/platforms/webex/client.js.map +1 -1
- package/dist/src/platforms/webex/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/webex/commands/auth.js +10 -6
- package/dist/src/platforms/webex/commands/auth.js.map +1 -1
- package/dist/src/platforms/webex/commands/member.d.ts.map +1 -1
- package/dist/src/platforms/webex/commands/member.js +3 -0
- package/dist/src/platforms/webex/commands/member.js.map +1 -1
- package/dist/src/platforms/webex/commands/message.d.ts.map +1 -1
- package/dist/src/platforms/webex/commands/message.js +3 -0
- package/dist/src/platforms/webex/commands/message.js.map +1 -1
- package/dist/src/platforms/webex/commands/snapshot.d.ts.map +1 -1
- package/dist/src/platforms/webex/commands/snapshot.js +3 -1
- package/dist/src/platforms/webex/commands/snapshot.js.map +1 -1
- package/dist/src/platforms/webex/commands/space.d.ts.map +1 -1
- package/dist/src/platforms/webex/commands/space.js +5 -0
- package/dist/src/platforms/webex/commands/space.js.map +1 -1
- package/dist/src/platforms/webex/commands/whoami.d.ts.map +1 -1
- package/dist/src/platforms/webex/commands/whoami.js +3 -0
- package/dist/src/platforms/webex/commands/whoami.js.map +1 -1
- package/dist/src/platforms/webex/id-normalizer.d.ts +7 -0
- package/dist/src/platforms/webex/id-normalizer.d.ts.map +1 -1
- package/dist/src/platforms/webex/id-normalizer.js +16 -0
- package/dist/src/platforms/webex/id-normalizer.js.map +1 -1
- package/dist/src/platforms/webex/index.d.ts +2 -2
- package/dist/src/platforms/webex/index.d.ts.map +1 -1
- package/dist/src/platforms/webex/index.js +1 -1
- package/dist/src/platforms/webex/index.js.map +1 -1
- package/dist/src/platforms/webexbot/client.d.ts +0 -4
- package/dist/src/platforms/webexbot/client.d.ts.map +1 -1
- package/dist/src/platforms/webexbot/client.js +8 -65
- package/dist/src/platforms/webexbot/client.js.map +1 -1
- package/dist/src/platforms/webexbot/commands/file.d.ts +2 -0
- package/dist/src/platforms/webexbot/commands/file.d.ts.map +1 -1
- package/dist/src/platforms/webexbot/commands/file.js +3 -0
- package/dist/src/platforms/webexbot/commands/file.js.map +1 -1
- package/dist/src/platforms/webexbot/commands/member.d.ts +2 -0
- package/dist/src/platforms/webexbot/commands/member.d.ts.map +1 -1
- package/dist/src/platforms/webexbot/commands/member.js +3 -0
- package/dist/src/platforms/webexbot/commands/member.js.map +1 -1
- package/dist/src/platforms/webexbot/commands/message.d.ts +4 -0
- package/dist/src/platforms/webexbot/commands/message.d.ts.map +1 -1
- package/dist/src/platforms/webexbot/commands/message.js +7 -0
- package/dist/src/platforms/webexbot/commands/message.js.map +1 -1
- package/dist/src/platforms/webexbot/commands/snapshot.d.ts +2 -0
- package/dist/src/platforms/webexbot/commands/snapshot.d.ts.map +1 -1
- package/dist/src/platforms/webexbot/commands/snapshot.js +10 -2
- package/dist/src/platforms/webexbot/commands/snapshot.js.map +1 -1
- package/dist/src/platforms/webexbot/commands/space.d.ts +4 -0
- package/dist/src/platforms/webexbot/commands/space.d.ts.map +1 -1
- package/dist/src/platforms/webexbot/commands/space.js +5 -0
- package/dist/src/platforms/webexbot/commands/space.js.map +1 -1
- package/dist/src/platforms/webexbot/commands/user.d.ts +3 -0
- package/dist/src/platforms/webexbot/commands/user.d.ts.map +1 -1
- package/dist/src/platforms/webexbot/commands/user.js +4 -0
- package/dist/src/platforms/webexbot/commands/user.js.map +1 -1
- package/dist/src/platforms/webexbot/commands/whoami.d.ts +2 -0
- package/dist/src/platforms/webexbot/commands/whoami.d.ts.map +1 -1
- package/dist/src/platforms/webexbot/commands/whoami.js +3 -0
- package/dist/src/platforms/webexbot/commands/whoami.js.map +1 -1
- package/dist/src/platforms/webexbot/index.d.ts +2 -2
- package/dist/src/platforms/webexbot/index.d.ts.map +1 -1
- package/dist/src/platforms/webexbot/index.js +1 -1
- package/dist/src/platforms/webexbot/index.js.map +1 -1
- package/dist/src/tui/adapters/types.d.ts +3 -0
- package/dist/src/tui/adapters/types.d.ts.map +1 -1
- package/dist/src/tui/adapters/webex-adapter.d.ts.map +1 -1
- package/dist/src/tui/adapters/webex-adapter.js +4 -0
- package/dist/src/tui/adapters/webex-adapter.js.map +1 -1
- package/docs/content/docs/cli/webex.mdx +2 -2
- package/package.json +1 -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 +3 -3
- package/skills/agent-webexbot/SKILL.md +2 -2
- package/skills/agent-webexbot/references/common-patterns.md +1 -1
- 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/platforms/webex/client.test.ts +94 -6
- package/src/platforms/webex/client.ts +194 -32
- package/src/platforms/webex/commands/auth.test.ts +3 -1
- package/src/platforms/webex/commands/auth.ts +12 -7
- package/src/platforms/webex/commands/member.test.ts +18 -8
- package/src/platforms/webex/commands/member.ts +3 -0
- package/src/platforms/webex/commands/message.test.ts +31 -23
- package/src/platforms/webex/commands/message.ts +3 -0
- package/src/platforms/webex/commands/snapshot.test.ts +18 -10
- package/src/platforms/webex/commands/snapshot.ts +3 -1
- package/src/platforms/webex/commands/space.test.ts +36 -17
- package/src/platforms/webex/commands/space.ts +5 -0
- package/src/platforms/webex/commands/whoami.test.ts +14 -6
- package/src/platforms/webex/commands/whoami.ts +3 -0
- package/src/platforms/webex/id-normalizer.test.ts +37 -0
- package/src/platforms/webex/id-normalizer.ts +21 -0
- package/src/platforms/webex/index.ts +2 -2
- package/src/platforms/webexbot/client.ts +8 -74
- package/src/platforms/webexbot/commands/file.ts +5 -0
- package/src/platforms/webexbot/commands/member.ts +5 -0
- package/src/platforms/webexbot/commands/message.ts +11 -0
- package/src/platforms/webexbot/commands/snapshot.ts +12 -2
- package/src/platforms/webexbot/commands/space.ts +9 -0
- package/src/platforms/webexbot/commands/user.test.ts +11 -5
- package/src/platforms/webexbot/commands/user.ts +7 -0
- package/src/platforms/webexbot/commands/whoami.ts +5 -0
- package/src/platforms/webexbot/index.ts +2 -2
- package/src/tui/adapters/types.ts +3 -0
- package/src/tui/adapters/webex-adapter.ts +4 -0
|
@@ -4,12 +4,15 @@ import { handleError } from '@/shared/utils/error-handler'
|
|
|
4
4
|
import { formatOutput } from '@/shared/utils/output'
|
|
5
5
|
|
|
6
6
|
import { WebexClient } from '../client'
|
|
7
|
+
import { toRef } from '../id-normalizer'
|
|
7
8
|
import type { WebexMessage } from '../types'
|
|
8
9
|
|
|
9
10
|
function formatMessageOutput(message: WebexMessage) {
|
|
10
11
|
return {
|
|
11
12
|
id: message.id,
|
|
13
|
+
ref: toRef(message.id),
|
|
12
14
|
roomId: message.roomId,
|
|
15
|
+
roomRef: toRef(message.roomId),
|
|
13
16
|
text: message.text,
|
|
14
17
|
html: message.html,
|
|
15
18
|
personEmail: message.personEmail,
|
|
@@ -3,32 +3,38 @@ import { afterEach, beforeEach, describe, expect, spyOn, it } from 'bun:test'
|
|
|
3
3
|
import { WebexClient } from '../client'
|
|
4
4
|
import { WebexError } from '../types'
|
|
5
5
|
|
|
6
|
+
const restId = (type: string, ref: string) => Buffer.from(`ciscospark://us/${type}/${ref}`).toString('base64url')
|
|
7
|
+
const space1Id = restId('ROOM', 'space-1')
|
|
8
|
+
const space2Id = restId('ROOM', 'space-2')
|
|
9
|
+
const member1Id = restId('MEMBERSHIP', 'person-1:space-1')
|
|
10
|
+
const person1Id = restId('PEOPLE', 'person-1')
|
|
11
|
+
|
|
6
12
|
const mockSpaces = [
|
|
7
13
|
{
|
|
8
|
-
id:
|
|
14
|
+
id: space1Id,
|
|
9
15
|
title: 'General',
|
|
10
16
|
type: 'group',
|
|
11
17
|
isLocked: false,
|
|
12
18
|
lastActivity: '2024-01-15T00:00:00.000Z',
|
|
13
19
|
created: '2024-01-01T00:00:00.000Z',
|
|
14
|
-
creatorId:
|
|
20
|
+
creatorId: person1Id,
|
|
15
21
|
},
|
|
16
22
|
{
|
|
17
|
-
id:
|
|
23
|
+
id: space2Id,
|
|
18
24
|
title: 'Random',
|
|
19
25
|
type: 'group',
|
|
20
26
|
isLocked: false,
|
|
21
27
|
lastActivity: '2024-01-14T00:00:00.000Z',
|
|
22
28
|
created: '2024-01-01T00:00:00.000Z',
|
|
23
|
-
creatorId:
|
|
29
|
+
creatorId: person1Id,
|
|
24
30
|
},
|
|
25
31
|
]
|
|
26
32
|
|
|
27
33
|
const mockMyMemberships = [
|
|
28
34
|
{
|
|
29
|
-
id:
|
|
30
|
-
roomId:
|
|
31
|
-
personId:
|
|
35
|
+
id: member1Id,
|
|
36
|
+
roomId: space1Id,
|
|
37
|
+
personId: person1Id,
|
|
32
38
|
personEmail: 'alice@example.com',
|
|
33
39
|
personDisplayName: 'Alice',
|
|
34
40
|
isModerator: true,
|
|
@@ -77,7 +83,8 @@ describe('snapshot command', () => {
|
|
|
77
83
|
expect(consoleSpy).toHaveBeenCalled()
|
|
78
84
|
const output = JSON.parse(consoleSpy.mock.calls[consoleSpy.mock.calls.length - 1][0])
|
|
79
85
|
expect(output.spaces).toHaveLength(1)
|
|
80
|
-
expect(output.spaces[0].id).toBe(
|
|
86
|
+
expect(output.spaces[0].id).toBe(space1Id)
|
|
87
|
+
expect(output.spaces[0].ref).toBe('space-1')
|
|
81
88
|
expect(output.spaces[0].title).toBe('General')
|
|
82
89
|
expect(output.spaces[0].type).toBeUndefined()
|
|
83
90
|
expect(output.hint).toBeDefined()
|
|
@@ -89,7 +96,8 @@ describe('snapshot command', () => {
|
|
|
89
96
|
expect(consoleSpy).toHaveBeenCalled()
|
|
90
97
|
const output = JSON.parse(consoleSpy.mock.calls[consoleSpy.mock.calls.length - 1][0])
|
|
91
98
|
expect(output.spaces).toHaveLength(1)
|
|
92
|
-
expect(output.spaces[0].id).toBe(
|
|
99
|
+
expect(output.spaces[0].id).toBe(space1Id)
|
|
100
|
+
expect(output.spaces[0].ref).toBe('space-1')
|
|
93
101
|
expect(output.spaces[0].title).toBe('General')
|
|
94
102
|
expect(output.spaces[0].type).toBe('group')
|
|
95
103
|
expect(output.spaces[0].lastActivity).toBe('2024-01-15T00:00:00.000Z')
|
|
@@ -101,7 +109,7 @@ describe('snapshot command', () => {
|
|
|
101
109
|
|
|
102
110
|
const output = JSON.parse(consoleSpy.mock.calls[consoleSpy.mock.calls.length - 1][0])
|
|
103
111
|
expect(output.spaces).toHaveLength(1)
|
|
104
|
-
expect(output.spaces[0].id).toBe(
|
|
112
|
+
expect(output.spaces[0].id).toBe(space1Id)
|
|
105
113
|
})
|
|
106
114
|
|
|
107
115
|
it('exits with code 1 when not authenticated', async () => {
|
|
@@ -4,6 +4,7 @@ import { handleError } from '@/shared/utils/error-handler'
|
|
|
4
4
|
import { formatOutput } from '@/shared/utils/output'
|
|
5
5
|
|
|
6
6
|
import { WebexClient } from '../client'
|
|
7
|
+
import { toRef } from '../id-normalizer'
|
|
7
8
|
|
|
8
9
|
export async function snapshotAction(options: { full?: boolean; pretty?: boolean }): Promise<void> {
|
|
9
10
|
try {
|
|
@@ -19,11 +20,12 @@ export async function snapshotAction(options: { full?: boolean; pretty?: boolean
|
|
|
19
20
|
spaces: options.full
|
|
20
21
|
? spaces.map((s) => ({
|
|
21
22
|
id: s.id,
|
|
23
|
+
ref: toRef(s.id),
|
|
22
24
|
title: s.title,
|
|
23
25
|
type: s.type,
|
|
24
26
|
lastActivity: s.lastActivity,
|
|
25
27
|
}))
|
|
26
|
-
: spaces.map((s) => ({ id: s.id, title: s.title })),
|
|
28
|
+
: spaces.map((s) => ({ id: s.id, ref: toRef(s.id), title: s.title })),
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
if (!options.full) {
|
|
@@ -3,36 +3,43 @@ import { afterEach, beforeEach, describe, expect, spyOn, it } from 'bun:test'
|
|
|
3
3
|
import { WebexClient } from '../client'
|
|
4
4
|
import { WebexError } from '../types'
|
|
5
5
|
|
|
6
|
+
const restId = (type: string, ref: string) => Buffer.from(`ciscospark://us/${type}/${ref}`).toString('base64url')
|
|
7
|
+
const space1Id = restId('ROOM', 'space-1')
|
|
8
|
+
const space2Id = restId('ROOM', 'space-2')
|
|
9
|
+
const person1Id = restId('PEOPLE', 'person-1')
|
|
10
|
+
const person2Id = restId('PEOPLE', 'person-2')
|
|
11
|
+
const teamId = restId('TEAM', 'team-abc')
|
|
12
|
+
|
|
6
13
|
const mockSpaces = [
|
|
7
14
|
{
|
|
8
|
-
id:
|
|
15
|
+
id: space1Id,
|
|
9
16
|
title: 'General',
|
|
10
17
|
type: 'group' as const,
|
|
11
18
|
isLocked: false,
|
|
12
19
|
lastActivity: '2024-01-02T00:00:00.000Z',
|
|
13
20
|
created: '2024-01-01T00:00:00.000Z',
|
|
14
|
-
creatorId:
|
|
21
|
+
creatorId: person1Id,
|
|
15
22
|
},
|
|
16
23
|
{
|
|
17
|
-
id:
|
|
24
|
+
id: space2Id,
|
|
18
25
|
title: 'Direct with Alice',
|
|
19
26
|
type: 'direct' as const,
|
|
20
27
|
isLocked: false,
|
|
21
28
|
lastActivity: '2024-01-03T00:00:00.000Z',
|
|
22
29
|
created: '2024-01-01T00:00:00.000Z',
|
|
23
|
-
creatorId:
|
|
30
|
+
creatorId: person2Id,
|
|
24
31
|
},
|
|
25
32
|
]
|
|
26
33
|
|
|
27
34
|
const mockSpace = {
|
|
28
|
-
id:
|
|
35
|
+
id: space1Id,
|
|
29
36
|
title: 'General',
|
|
30
37
|
type: 'group' as const,
|
|
31
38
|
isLocked: false,
|
|
32
|
-
teamId
|
|
39
|
+
teamId,
|
|
33
40
|
lastActivity: '2024-01-02T00:00:00.000Z',
|
|
34
41
|
created: '2024-01-01T00:00:00.000Z',
|
|
35
|
-
creatorId:
|
|
42
|
+
creatorId: person1Id,
|
|
36
43
|
}
|
|
37
44
|
|
|
38
45
|
import { infoAction, listAction } from './space'
|
|
@@ -79,14 +86,16 @@ describe('listAction', () => {
|
|
|
79
86
|
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
80
87
|
JSON.stringify([
|
|
81
88
|
{
|
|
82
|
-
id:
|
|
89
|
+
id: space1Id,
|
|
90
|
+
ref: 'space-1',
|
|
83
91
|
title: 'General',
|
|
84
92
|
type: 'group',
|
|
85
93
|
lastActivity: '2024-01-02T00:00:00.000Z',
|
|
86
94
|
created: '2024-01-01T00:00:00.000Z',
|
|
87
95
|
},
|
|
88
96
|
{
|
|
89
|
-
id:
|
|
97
|
+
id: space2Id,
|
|
98
|
+
ref: 'space-2',
|
|
90
99
|
title: 'Direct with Alice',
|
|
91
100
|
type: 'direct',
|
|
92
101
|
lastActivity: '2024-01-03T00:00:00.000Z',
|
|
@@ -115,14 +124,16 @@ describe('listAction', () => {
|
|
|
115
124
|
JSON.stringify(
|
|
116
125
|
[
|
|
117
126
|
{
|
|
118
|
-
id:
|
|
127
|
+
id: space1Id,
|
|
128
|
+
ref: 'space-1',
|
|
119
129
|
title: 'General',
|
|
120
130
|
type: 'group',
|
|
121
131
|
lastActivity: '2024-01-02T00:00:00.000Z',
|
|
122
132
|
created: '2024-01-01T00:00:00.000Z',
|
|
123
133
|
},
|
|
124
134
|
{
|
|
125
|
-
id:
|
|
135
|
+
id: space2Id,
|
|
136
|
+
ref: 'space-2',
|
|
126
137
|
title: 'Direct with Alice',
|
|
127
138
|
type: 'direct',
|
|
128
139
|
lastActivity: '2024-01-03T00:00:00.000Z',
|
|
@@ -152,14 +163,17 @@ describe('infoAction', () => {
|
|
|
152
163
|
expect(mockGetSpace).toHaveBeenCalledWith('space-1')
|
|
153
164
|
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
154
165
|
JSON.stringify({
|
|
155
|
-
id:
|
|
166
|
+
id: space1Id,
|
|
167
|
+
ref: 'space-1',
|
|
156
168
|
title: 'General',
|
|
157
169
|
type: 'group',
|
|
158
170
|
isLocked: false,
|
|
159
|
-
teamId
|
|
171
|
+
teamId,
|
|
172
|
+
teamRef: 'team-abc',
|
|
160
173
|
lastActivity: '2024-01-02T00:00:00.000Z',
|
|
161
174
|
created: '2024-01-01T00:00:00.000Z',
|
|
162
|
-
creatorId:
|
|
175
|
+
creatorId: person1Id,
|
|
176
|
+
creatorRef: 'person-1',
|
|
163
177
|
}),
|
|
164
178
|
)
|
|
165
179
|
})
|
|
@@ -171,8 +185,10 @@ describe('infoAction', () => {
|
|
|
171
185
|
|
|
172
186
|
const output = JSON.parse(consoleLogSpy.mock.calls[0][0] as string) as {
|
|
173
187
|
teamId: null
|
|
188
|
+
teamRef: null
|
|
174
189
|
}
|
|
175
190
|
expect(output.teamId).toBeNull()
|
|
191
|
+
expect(output.teamRef).toBeNull()
|
|
176
192
|
})
|
|
177
193
|
|
|
178
194
|
it('outputs pretty-printed JSON when pretty is true', async () => {
|
|
@@ -181,14 +197,17 @@ describe('infoAction', () => {
|
|
|
181
197
|
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
182
198
|
JSON.stringify(
|
|
183
199
|
{
|
|
184
|
-
id:
|
|
200
|
+
id: space1Id,
|
|
201
|
+
ref: 'space-1',
|
|
185
202
|
title: 'General',
|
|
186
203
|
type: 'group',
|
|
187
204
|
isLocked: false,
|
|
188
|
-
teamId
|
|
205
|
+
teamId,
|
|
206
|
+
teamRef: 'team-abc',
|
|
189
207
|
lastActivity: '2024-01-02T00:00:00.000Z',
|
|
190
208
|
created: '2024-01-01T00:00:00.000Z',
|
|
191
|
-
creatorId:
|
|
209
|
+
creatorId: person1Id,
|
|
210
|
+
creatorRef: 'person-1',
|
|
192
211
|
},
|
|
193
212
|
null,
|
|
194
213
|
2,
|
|
@@ -4,6 +4,7 @@ import { handleError } from '@/shared/utils/error-handler'
|
|
|
4
4
|
import { formatOutput } from '@/shared/utils/output'
|
|
5
5
|
|
|
6
6
|
import { WebexClient } from '../client'
|
|
7
|
+
import { toRef } from '../id-normalizer'
|
|
7
8
|
|
|
8
9
|
export async function listAction(options: { type?: string; limit?: number; pretty?: boolean }): Promise<void> {
|
|
9
10
|
try {
|
|
@@ -11,6 +12,7 @@ export async function listAction(options: { type?: string; limit?: number; prett
|
|
|
11
12
|
const spaces = await client.listSpaces({ type: options.type, max: options.limit })
|
|
12
13
|
const output = spaces.map((s) => ({
|
|
13
14
|
id: s.id,
|
|
15
|
+
ref: toRef(s.id),
|
|
14
16
|
title: s.title,
|
|
15
17
|
type: s.type,
|
|
16
18
|
lastActivity: s.lastActivity,
|
|
@@ -28,13 +30,16 @@ export async function infoAction(spaceId: string, options: { pretty?: boolean })
|
|
|
28
30
|
const space = await client.getSpace(spaceId)
|
|
29
31
|
const output = {
|
|
30
32
|
id: space.id,
|
|
33
|
+
ref: toRef(space.id),
|
|
31
34
|
title: space.title,
|
|
32
35
|
type: space.type,
|
|
33
36
|
isLocked: space.isLocked,
|
|
34
37
|
teamId: space.teamId || null,
|
|
38
|
+
teamRef: space.teamId ? toRef(space.teamId) : null,
|
|
35
39
|
lastActivity: space.lastActivity,
|
|
36
40
|
created: space.created,
|
|
37
41
|
creatorId: space.creatorId,
|
|
42
|
+
creatorRef: toRef(space.creatorId),
|
|
38
43
|
}
|
|
39
44
|
console.log(formatOutput(output, options.pretty))
|
|
40
45
|
} catch (error) {
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
import { afterEach, beforeEach, expect, spyOn, it } from 'bun:test'
|
|
2
2
|
|
|
3
3
|
import { WebexClient } from '../client'
|
|
4
|
+
import { toRestId } from '../id-normalizer'
|
|
4
5
|
import { WebexError } from '../types'
|
|
5
6
|
import { whoamiCommand } from './whoami'
|
|
6
7
|
|
|
8
|
+
const orgId = Buffer.from('ciscospark://us/ORGANIZATION/org-123').toString('base64url')
|
|
9
|
+
const personId = toRestId('person-123', 'PEOPLE')
|
|
10
|
+
|
|
7
11
|
const mockUser = {
|
|
8
|
-
id:
|
|
12
|
+
id: personId,
|
|
9
13
|
emails: ['test@example.com'],
|
|
10
14
|
displayName: 'Test User',
|
|
11
15
|
nickName: 'Testy',
|
|
12
16
|
firstName: 'Test',
|
|
13
17
|
lastName: 'User',
|
|
14
18
|
avatar: 'https://example.com/avatar.jpg',
|
|
15
|
-
orgId
|
|
19
|
+
orgId,
|
|
16
20
|
type: 'person' as const,
|
|
17
21
|
created: '2024-01-01T00:00:00.000Z',
|
|
18
22
|
}
|
|
@@ -58,14 +62,16 @@ it('whoami calls testAuth and outputs user fields', async () => {
|
|
|
58
62
|
// then: outputs all expected fields
|
|
59
63
|
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
60
64
|
JSON.stringify({
|
|
61
|
-
id:
|
|
65
|
+
id: personId,
|
|
66
|
+
ref: 'person-123',
|
|
62
67
|
emails: ['test@example.com'],
|
|
63
68
|
displayName: 'Test User',
|
|
64
69
|
nickName: 'Testy',
|
|
65
70
|
firstName: 'Test',
|
|
66
71
|
lastName: 'User',
|
|
67
72
|
avatar: 'https://example.com/avatar.jpg',
|
|
68
|
-
orgId
|
|
73
|
+
orgId,
|
|
74
|
+
orgRef: 'org-123',
|
|
69
75
|
type: 'person',
|
|
70
76
|
}),
|
|
71
77
|
)
|
|
@@ -80,14 +86,16 @@ it('whoami outputs pretty-printed JSON when --pretty flag is passed', async () =
|
|
|
80
86
|
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
81
87
|
JSON.stringify(
|
|
82
88
|
{
|
|
83
|
-
id:
|
|
89
|
+
id: personId,
|
|
90
|
+
ref: 'person-123',
|
|
84
91
|
emails: ['test@example.com'],
|
|
85
92
|
displayName: 'Test User',
|
|
86
93
|
nickName: 'Testy',
|
|
87
94
|
firstName: 'Test',
|
|
88
95
|
lastName: 'User',
|
|
89
96
|
avatar: 'https://example.com/avatar.jpg',
|
|
90
|
-
orgId
|
|
97
|
+
orgId,
|
|
98
|
+
orgRef: 'org-123',
|
|
91
99
|
type: 'person',
|
|
92
100
|
},
|
|
93
101
|
null,
|
|
@@ -4,6 +4,7 @@ import { handleError } from '@/shared/utils/error-handler'
|
|
|
4
4
|
import { formatOutput } from '@/shared/utils/output'
|
|
5
5
|
|
|
6
6
|
import { WebexClient } from '../client'
|
|
7
|
+
import { toRef } from '../id-normalizer'
|
|
7
8
|
|
|
8
9
|
export async function whoamiAction(options: { pretty?: boolean }): Promise<void> {
|
|
9
10
|
try {
|
|
@@ -12,6 +13,7 @@ export async function whoamiAction(options: { pretty?: boolean }): Promise<void>
|
|
|
12
13
|
|
|
13
14
|
const output = {
|
|
14
15
|
id: user.id,
|
|
16
|
+
ref: toRef(user.id),
|
|
15
17
|
emails: user.emails,
|
|
16
18
|
displayName: user.displayName,
|
|
17
19
|
nickName: user.nickName,
|
|
@@ -19,6 +21,7 @@ export async function whoamiAction(options: { pretty?: boolean }): Promise<void>
|
|
|
19
21
|
lastName: user.lastName,
|
|
20
22
|
avatar: user.avatar,
|
|
21
23
|
orgId: user.orgId,
|
|
24
|
+
orgRef: toRef(user.orgId),
|
|
22
25
|
type: user.type,
|
|
23
26
|
}
|
|
24
27
|
console.log(formatOutput(output, options.pretty))
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
normalizeMessage,
|
|
18
18
|
normalizeRoomActivity,
|
|
19
19
|
toRestId,
|
|
20
|
+
toRef,
|
|
20
21
|
} from './id-normalizer'
|
|
21
22
|
|
|
22
23
|
const RAW: MercuryActivity = {
|
|
@@ -28,6 +29,8 @@ const RAW: MercuryActivity = {
|
|
|
28
29
|
published: '2024-01-01T00:00:00Z',
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
const restId = (type: string, ref: string) => Buffer.from(`ciscospark://us/${type}/${ref}`).toString('base64url')
|
|
33
|
+
|
|
31
34
|
describe('toRestId / fromRestId', () => {
|
|
32
35
|
it('encodes a uuid into a ciscospark REST id round-trippable to the uuid', () => {
|
|
33
36
|
const restId = toRestId('abc-123', 'PEOPLE')
|
|
@@ -58,6 +61,40 @@ describe('toRestId / fromRestId', () => {
|
|
|
58
61
|
})
|
|
59
62
|
})
|
|
60
63
|
|
|
64
|
+
describe('toRef', () => {
|
|
65
|
+
it('returns a room uuid ref', () => {
|
|
66
|
+
const uuid = '12345678-1234-1234-1234-1234567890ab'
|
|
67
|
+
|
|
68
|
+
expect(toRef(toRestId(uuid, 'ROOM'))).toBe(uuid)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('returns a person uuid ref', () => {
|
|
72
|
+
const uuid = '22222222-2222-2222-2222-222222222222'
|
|
73
|
+
|
|
74
|
+
expect(toRef(toRestId(uuid, 'PEOPLE'))).toBe(uuid)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('returns a legacy person email ref', () => {
|
|
78
|
+
expect(toRef(toRestId('legacy@example.com', 'PEOPLE'))).toBe('legacy@example.com')
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('returns an organization uuid ref', () => {
|
|
82
|
+
const uuid = '33333333-3333-3333-3333-333333333333'
|
|
83
|
+
|
|
84
|
+
expect(toRef(restId('ORGANIZATION', uuid))).toBe(uuid)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('returns a membership person-room pair ref', () => {
|
|
88
|
+
const membershipRef = '44444444-4444-4444-4444-444444444444:55555555-5555-5555-5555-555555555555'
|
|
89
|
+
|
|
90
|
+
expect(toRef(restId('MEMBERSHIP', membershipRef))).toBe(membershipRef)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('returns empty input unchanged', () => {
|
|
94
|
+
expect(toRef('')).toBe('')
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
|
|
61
98
|
describe('normalizeMessage', () => {
|
|
62
99
|
const message: DecryptedMessage = {
|
|
63
100
|
id: 'msg-uuid',
|
|
@@ -13,6 +13,27 @@ export { fromRestId }
|
|
|
13
13
|
// (the resource type behind GET /v1/attachment/actions).
|
|
14
14
|
export type WebexRestIdType = 'MESSAGE' | 'PEOPLE' | 'ROOM' | 'ATTACHMENT_ACTION'
|
|
15
15
|
|
|
16
|
+
export interface DecodedWebexId {
|
|
17
|
+
cluster: string
|
|
18
|
+
type: string
|
|
19
|
+
uuid: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function toRef(id: string): string {
|
|
23
|
+
if (!id) return id
|
|
24
|
+
return fromRestId(id)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Webex REST ids are base64(url) of `ciscospark://<cluster>/<TYPE>/<uuid>`; room
|
|
28
|
+
// cluster correction needs all three parts, not just the trailing ref value.
|
|
29
|
+
export function decodeWebexId(restId: string): DecodedWebexId | null {
|
|
30
|
+
if (!restId) return null
|
|
31
|
+
const decoded = Buffer.from(restId, 'base64').toString('utf-8')
|
|
32
|
+
const match = decoded.match(/^ciscospark:\/\/([^/]+)\/([^/]+)\/(.+)$/)
|
|
33
|
+
if (!match) return null
|
|
34
|
+
return { cluster: match[1], type: match[2], uuid: match[3] }
|
|
35
|
+
}
|
|
36
|
+
|
|
16
37
|
/**
|
|
17
38
|
* Encode a raw Mercury UUID as a Webex REST ID. Empty input is returned unchanged
|
|
18
39
|
* so an absent ID never becomes a bogus `ciscospark://us/{TYPE}/` value.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { WebexClient } from './client'
|
|
2
2
|
export { WebexCredentialManager } from './credential-manager'
|
|
3
|
-
export { fromRestId, toRestId } from './id-normalizer'
|
|
4
|
-
export type { WebexRestIdType } from './id-normalizer'
|
|
3
|
+
export { decodeWebexId, fromRestId, toRef, toRestId } from './id-normalizer'
|
|
4
|
+
export type { DecodedWebexId, WebexRestIdType } from './id-normalizer'
|
|
5
5
|
export { WebexListener } from './listener'
|
|
6
6
|
export type { WebexListenerClient, WebexListenerEventMap, WebexListenerOptions } from './listener'
|
|
7
7
|
export { loginWithPassword } from './password-login'
|
|
@@ -2,33 +2,10 @@ import { WebexClient } from '../webex/client'
|
|
|
2
2
|
import type { WebexMembership, WebexMessage, WebexPerson, WebexSpace } from '../webex/types'
|
|
3
3
|
import { WebexBotError } from './types'
|
|
4
4
|
|
|
5
|
-
interface DecodedWebexId {
|
|
6
|
-
cluster: string
|
|
7
|
-
type: string
|
|
8
|
-
uuid: string
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// Webex REST ids are base64(url) of `ciscospark://<cluster>/<TYPE>/<uuid>`; the
|
|
12
|
-
// cluster correction needs all three parts, not just the <uuid> `fromRestId` returns.
|
|
13
|
-
function decodeWebexId(restId: string): DecodedWebexId | null {
|
|
14
|
-
if (!restId) return null
|
|
15
|
-
const decoded = Buffer.from(restId, 'base64').toString('utf-8')
|
|
16
|
-
const match = decoded.match(/^ciscospark:\/\/([^/]+)\/([^/]+)\/(.+)$/)
|
|
17
|
-
if (!match) return null
|
|
18
|
-
return { cluster: match[1], type: match[2], uuid: match[3] }
|
|
19
|
-
}
|
|
20
|
-
|
|
21
5
|
export class WebexBotClient {
|
|
22
|
-
private client = new WebexClient()
|
|
6
|
+
private client = new WebexClient({ roomResolutionWarningPrefix: '[webexbot]' })
|
|
23
7
|
private token: string | null = null
|
|
24
8
|
|
|
25
|
-
// The listener flattens room ids to `ciscospark://us/ROOM/<uuid>`, but team/group
|
|
26
|
-
// rooms live on `ciscospark://urn:TEAM:<cluster>/ROOM/<uuid>` — a cluster the bare
|
|
27
|
-
// uuid cannot recover. Cache the real clustered id per uuid and dedupe concurrent
|
|
28
|
-
// lookups so a burst of calls triggers a single `listSpaces`.
|
|
29
|
-
private clusteredRoomIds = new Map<string, string>()
|
|
30
|
-
private roomIdLookups = new Map<string, Promise<string>>()
|
|
31
|
-
|
|
32
9
|
async login(credentials?: { token: string }): Promise<this> {
|
|
33
10
|
if (credentials) {
|
|
34
11
|
if (!credentials.token) {
|
|
@@ -64,7 +41,7 @@ export class WebexBotClient {
|
|
|
64
41
|
}
|
|
65
42
|
|
|
66
43
|
async getSpace(spaceId: string): Promise<WebexSpace> {
|
|
67
|
-
return this.client.getSpace(
|
|
44
|
+
return this.client.getSpace(spaceId)
|
|
68
45
|
}
|
|
69
46
|
|
|
70
47
|
async sendMessage(
|
|
@@ -72,7 +49,7 @@ export class WebexBotClient {
|
|
|
72
49
|
text: string,
|
|
73
50
|
options?: { markdown?: boolean; parentId?: string; files?: string[] },
|
|
74
51
|
): Promise<WebexMessage> {
|
|
75
|
-
return this.client.sendMessage(
|
|
52
|
+
return this.client.sendMessage(roomId, text, options)
|
|
76
53
|
}
|
|
77
54
|
|
|
78
55
|
async sendDirectMessage(personEmail: string, text: string, options?: { markdown?: boolean }): Promise<WebexMessage> {
|
|
@@ -80,14 +57,14 @@ export class WebexBotClient {
|
|
|
80
57
|
}
|
|
81
58
|
|
|
82
59
|
async listMessages(roomId: string, options?: { max?: number; parentId?: string }): Promise<WebexMessage[]> {
|
|
83
|
-
const resolvedRoomId = await this.resolveRoomId(roomId)
|
|
60
|
+
const resolvedRoomId = await this.client.resolveRoomId(roomId)
|
|
84
61
|
const space = await this.client.getSpace(resolvedRoomId)
|
|
85
62
|
const messageOptions = space.type === 'group' ? { ...options, mentionedPeople: 'me' } : options
|
|
86
63
|
return this.client.listMessages(resolvedRoomId, messageOptions)
|
|
87
64
|
}
|
|
88
65
|
|
|
89
66
|
async listReplies(roomId: string, parentId: string, options?: { max?: number }): Promise<WebexMessage[]> {
|
|
90
|
-
return this.client.listMessages(
|
|
67
|
+
return this.client.listMessages(roomId, { ...options, parentId })
|
|
91
68
|
}
|
|
92
69
|
|
|
93
70
|
async getMessage(messageId: string): Promise<WebexMessage> {
|
|
@@ -108,7 +85,7 @@ export class WebexBotClient {
|
|
|
108
85
|
text: string,
|
|
109
86
|
options?: { markdown?: boolean },
|
|
110
87
|
): Promise<WebexMessage> {
|
|
111
|
-
return this.client.editMessage(messageId,
|
|
88
|
+
return this.client.editMessage(messageId, roomId, text, options)
|
|
112
89
|
}
|
|
113
90
|
|
|
114
91
|
async listPeople(options?: { email?: string; displayName?: string; max?: number }): Promise<WebexPerson[]> {
|
|
@@ -124,7 +101,7 @@ export class WebexBotClient {
|
|
|
124
101
|
}
|
|
125
102
|
|
|
126
103
|
async listMemberships(roomId: string, options?: { max?: number }): Promise<WebexMembership[]> {
|
|
127
|
-
return this.client.listMemberships(
|
|
104
|
+
return this.client.listMemberships(roomId, options)
|
|
128
105
|
}
|
|
129
106
|
|
|
130
107
|
async uploadFile(
|
|
@@ -132,53 +109,10 @@ export class WebexBotClient {
|
|
|
132
109
|
file: { content: Blob; filename: string },
|
|
133
110
|
options?: { text?: string; markdown?: boolean; parentId?: string },
|
|
134
111
|
): Promise<WebexMessage> {
|
|
135
|
-
return this.client.uploadFile(
|
|
112
|
+
return this.client.uploadFile(roomId, file, options)
|
|
136
113
|
}
|
|
137
114
|
|
|
138
115
|
async downloadContent(contentRef: string): Promise<{ data: ArrayBuffer; filename: string; contentType: string }> {
|
|
139
116
|
return this.client.downloadContent(contentRef)
|
|
140
117
|
}
|
|
141
|
-
|
|
142
|
-
private async resolveRoomId(roomId: string): Promise<string> {
|
|
143
|
-
const decoded = decodeWebexId(roomId)
|
|
144
|
-
// Already cluster-qualified or undecodable: nothing to correct.
|
|
145
|
-
if (!decoded || decoded.cluster.startsWith('urn:')) return roomId
|
|
146
|
-
|
|
147
|
-
const { uuid } = decoded
|
|
148
|
-
const cached = this.clusteredRoomIds.get(uuid)
|
|
149
|
-
if (cached) return cached
|
|
150
|
-
|
|
151
|
-
const inFlight = this.roomIdLookups.get(uuid)
|
|
152
|
-
if (inFlight) return inFlight
|
|
153
|
-
|
|
154
|
-
const lookup = this.lookupRoomId(uuid, roomId)
|
|
155
|
-
this.roomIdLookups.set(uuid, lookup)
|
|
156
|
-
try {
|
|
157
|
-
return await lookup
|
|
158
|
-
} finally {
|
|
159
|
-
this.roomIdLookups.delete(uuid)
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
private async lookupRoomId(uuid: string, fallback: string): Promise<string> {
|
|
164
|
-
try {
|
|
165
|
-
// Page through every room the bot belongs to (largest page size, following
|
|
166
|
-
// `Link` pages), stopping as soon as the trailing UUID matches.
|
|
167
|
-
for await (const room of this.client.iterateSpaces({ max: 1000 })) {
|
|
168
|
-
if (decodeWebexId(room.id)?.uuid === uuid) {
|
|
169
|
-
this.clusteredRoomIds.set(uuid, room.id)
|
|
170
|
-
return room.id
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
} catch {
|
|
174
|
-
// Network/auth failure: fail open to the un-corrected id rather than block the call.
|
|
175
|
-
return fallback
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
console.warn(
|
|
179
|
-
`[webexbot] Could not resolve clustered room id for ${uuid}; falling back to the un-clustered id. ` +
|
|
180
|
-
'Room-scoped calls may fail if this room lives on a non-default Webex cluster.',
|
|
181
|
-
)
|
|
182
|
-
return fallback
|
|
183
|
-
}
|
|
184
118
|
}
|
|
@@ -5,12 +5,15 @@ import { Command } from 'commander'
|
|
|
5
5
|
|
|
6
6
|
import { cliOutput } from '@/shared/utils/cli-output'
|
|
7
7
|
|
|
8
|
+
import { toRef } from '../../webex/id-normalizer'
|
|
8
9
|
import type { BotOption } from './shared'
|
|
9
10
|
import { getClient } from './shared'
|
|
10
11
|
|
|
11
12
|
interface FileResult {
|
|
12
13
|
id?: string
|
|
14
|
+
ref?: string
|
|
13
15
|
roomId?: string
|
|
16
|
+
roomRef?: string
|
|
14
17
|
files?: string[]
|
|
15
18
|
created?: string
|
|
16
19
|
downloaded?: string
|
|
@@ -37,7 +40,9 @@ export async function uploadAction(
|
|
|
37
40
|
|
|
38
41
|
return {
|
|
39
42
|
id: message.id,
|
|
43
|
+
ref: toRef(message.id),
|
|
40
44
|
roomId: message.roomId,
|
|
45
|
+
roomRef: toRef(message.roomId),
|
|
41
46
|
files: message.files,
|
|
42
47
|
created: message.created,
|
|
43
48
|
}
|
|
@@ -2,13 +2,16 @@ import { Command } from 'commander'
|
|
|
2
2
|
|
|
3
3
|
import { cliOutput } from '@/shared/utils/cli-output'
|
|
4
4
|
|
|
5
|
+
import { toRef } from '../../webex/id-normalizer'
|
|
5
6
|
import type { BotOption } from './shared'
|
|
6
7
|
import { getClient } from './shared'
|
|
7
8
|
|
|
8
9
|
interface MemberResult {
|
|
9
10
|
members?: Array<{
|
|
10
11
|
id: string
|
|
12
|
+
ref: string
|
|
11
13
|
personId: string
|
|
14
|
+
personRef: string
|
|
12
15
|
personEmail: string
|
|
13
16
|
personDisplayName: string
|
|
14
17
|
isModerator: boolean
|
|
@@ -26,7 +29,9 @@ export async function listAction(space: string, options: BotOption & { max?: str
|
|
|
26
29
|
return {
|
|
27
30
|
members: members.map((m) => ({
|
|
28
31
|
id: m.id,
|
|
32
|
+
ref: toRef(m.id),
|
|
29
33
|
personId: m.personId,
|
|
34
|
+
personRef: toRef(m.personId),
|
|
30
35
|
personEmail: m.personEmail,
|
|
31
36
|
personDisplayName: m.personDisplayName,
|
|
32
37
|
isModerator: m.isModerator,
|