agent-messenger 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +1 -1
  3. package/bun.lock +25 -1
  4. package/dist/package.json +4 -2
  5. package/dist/src/platforms/line/client.d.ts.map +1 -1
  6. package/dist/src/platforms/line/client.js +36 -9
  7. package/dist/src/platforms/line/client.js.map +1 -1
  8. package/dist/src/platforms/webex/client.d.ts +2 -0
  9. package/dist/src/platforms/webex/client.d.ts.map +1 -1
  10. package/dist/src/platforms/webex/client.js +66 -23
  11. package/dist/src/platforms/webex/client.js.map +1 -1
  12. package/dist/src/platforms/webex/commands/auth.d.ts.map +1 -1
  13. package/dist/src/platforms/webex/commands/auth.js +4 -0
  14. package/dist/src/platforms/webex/commands/auth.js.map +1 -1
  15. package/dist/src/platforms/webex/encryption.d.ts +10 -0
  16. package/dist/src/platforms/webex/encryption.d.ts.map +1 -0
  17. package/dist/src/platforms/webex/encryption.js +49 -0
  18. package/dist/src/platforms/webex/encryption.js.map +1 -0
  19. package/dist/src/platforms/webex/ensure-auth.d.ts.map +1 -1
  20. package/dist/src/platforms/webex/ensure-auth.js +4 -0
  21. package/dist/src/platforms/webex/ensure-auth.js.map +1 -1
  22. package/dist/src/platforms/webex/token-extractor.d.ts +6 -5
  23. package/dist/src/platforms/webex/token-extractor.d.ts.map +1 -1
  24. package/dist/src/platforms/webex/token-extractor.js +92 -43
  25. package/dist/src/platforms/webex/token-extractor.js.map +1 -1
  26. package/dist/src/platforms/webex/types.d.ts +4 -0
  27. package/dist/src/platforms/webex/types.d.ts.map +1 -1
  28. package/dist/src/platforms/webex/types.js +2 -0
  29. package/dist/src/platforms/webex/types.js.map +1 -1
  30. package/docs/content/docs/cli/webex.mdx +4 -2
  31. package/package.json +4 -2
  32. package/skills/agent-channeltalk/SKILL.md +1 -1
  33. package/skills/agent-channeltalkbot/SKILL.md +1 -1
  34. package/skills/agent-discord/SKILL.md +1 -1
  35. package/skills/agent-discordbot/SKILL.md +1 -1
  36. package/skills/agent-instagram/SKILL.md +1 -1
  37. package/skills/agent-kakaotalk/SKILL.md +1 -1
  38. package/skills/agent-line/SKILL.md +1 -1
  39. package/skills/agent-slack/SKILL.md +1 -1
  40. package/skills/agent-slackbot/SKILL.md +1 -1
  41. package/skills/agent-teams/SKILL.md +1 -1
  42. package/skills/agent-telegram/SKILL.md +1 -1
  43. package/skills/agent-webex/SKILL.md +1 -1
  44. package/skills/agent-webex/references/authentication.md +4 -3
  45. package/skills/agent-webex/references/common-patterns.md +1 -1
  46. package/skills/agent-whatsapp/SKILL.md +1 -1
  47. package/skills/agent-whatsappbot/SKILL.md +1 -1
  48. package/src/platforms/line/client.ts +39 -14
  49. package/src/platforms/webex/client.ts +98 -26
  50. package/src/platforms/webex/commands/auth.ts +4 -0
  51. package/src/platforms/webex/encryption.ts +53 -0
  52. package/src/platforms/webex/ensure-auth.ts +4 -0
  53. package/src/platforms/webex/token-extractor.ts +107 -40
  54. package/src/platforms/webex/types.ts +4 -0
  55. package/src/platforms/webex/typings/node-jose.d.ts +27 -0
@@ -17,6 +17,8 @@ export interface ExtractedWebexToken {
17
17
  refreshToken?: string
18
18
  expiresAt?: number
19
19
  deviceUrl?: string
20
+ userId?: string
21
+ encryptionKeys?: Map<string, string>
20
22
  }
21
23
 
22
24
  interface BrowserConfig {
@@ -73,6 +75,11 @@ const BROWSERS: BrowserConfig[] = [
73
75
 
74
76
  const WEBEX_STORAGE_KEY = '_https://web.webex.com\x00\x01webex-web-client-bounded'
75
77
 
78
+ interface ScanResult {
79
+ token: ExtractedWebexToken | null
80
+ encryptionKeys: Map<string, string>
81
+ }
82
+
76
83
  export class WebexTokenExtractor {
77
84
  private platform: NodeJS.Platform
78
85
  private baseDir: string | null
@@ -169,11 +176,17 @@ export class WebexTokenExtractor {
169
176
  for (const leveldbDir of profileDirs) {
170
177
  this.debug(`Scanning: ${leveldbDir}`)
171
178
 
172
- const token = await this.extractViaClassicLevelCopy(leveldbDir)
173
- ?? this.extractFromRawFiles(leveldbDir)
179
+ const result = await this.scanViaClassicLevelCopy(leveldbDir)
180
+ ?? this.scanRawFiles(leveldbDir)
174
181
 
175
- if (token) {
182
+ if (result?.token) {
176
183
  this.debug(`Found token in: ${leveldbDir}`)
184
+
185
+ const token = result.token
186
+ if (result.encryptionKeys.size > 0) {
187
+ token.encryptionKeys = result.encryptionKeys
188
+ }
189
+
177
190
  return token
178
191
  }
179
192
  }
@@ -182,7 +195,7 @@ export class WebexTokenExtractor {
182
195
  return null
183
196
  }
184
197
 
185
- private async extractViaClassicLevelCopy(dbPath: string): Promise<ExtractedWebexToken | null> {
198
+ private async scanViaClassicLevelCopy(dbPath: string): Promise<ScanResult | null> {
186
199
  const tempDir = join(tmpdir(), `webex-leveldb-${Date.now()}-${Math.random().toString(36).slice(2)}`)
187
200
 
188
201
  try {
@@ -199,7 +212,7 @@ export class WebexTokenExtractor {
199
212
  } catch {}
200
213
  }
201
214
 
202
- return await this.extractViaClassicLevel(tempDir)
215
+ return await this.copyAndScanLevelDB(tempDir)
203
216
  } catch {
204
217
  return null
205
218
  } finally {
@@ -209,8 +222,11 @@ export class WebexTokenExtractor {
209
222
  }
210
223
  }
211
224
 
212
- private async extractViaClassicLevel(dbPath: string): Promise<ExtractedWebexToken | null> {
225
+ private async copyAndScanLevelDB(dbPath: string): Promise<ScanResult | null> {
213
226
  let db: ClassicLevel<string, Buffer> | null = null
227
+ let token: ExtractedWebexToken | null = null
228
+ const encryptionKeys = new Map<string, string>()
229
+
214
230
  try {
215
231
  db = new ClassicLevel(dbPath, { keyEncoding: 'utf8', valueEncoding: 'buffer' })
216
232
 
@@ -218,13 +234,21 @@ export class WebexTokenExtractor {
218
234
  if (!key.includes('web.webex.com')) continue
219
235
 
220
236
  const decoded = this.decodeLevelDBValue(value)
221
- if (!decoded.includes('"supertoken"') && !decoded.includes('"Credentials"')) continue
222
237
 
223
- const token = this.extractTokenFromString(decoded)
224
- if (token) return token
238
+ if (!token && (decoded.includes('"supertoken"') || decoded.includes('"Credentials"'))) {
239
+ token = this.extractTokenFromString(decoded)
240
+ }
241
+
242
+ if (decoded.includes('"Encryption"') && decoded.includes('kms://')) {
243
+ const found = this.extractEncryptionKeysFromString(decoded)
244
+ for (const [uri, keyStr] of found) {
245
+ encryptionKeys.set(uri, keyStr)
246
+ }
247
+ }
225
248
  }
226
249
  } catch (e) {
227
250
  this.debug(`ClassicLevel failed: ${e instanceof Error ? e.message : String(e)}`)
251
+ return null
228
252
  } finally {
229
253
  if (db) {
230
254
  try {
@@ -232,22 +256,15 @@ export class WebexTokenExtractor {
232
256
  } catch {}
233
257
  }
234
258
  }
235
- return null
236
- }
237
259
 
238
- private decodeLevelDBValue(buf: Buffer): string {
239
- if (buf.length < 2) return buf.toString('utf8')
240
- // Chromium localStorage: 0x00 prefix = UTF-16LE, 0x01 prefix = Latin1/UTF-8
241
- if (buf[0] === 0x00 && (buf.length - 1) % 2 === 0) {
242
- return buf.subarray(1).toString('utf16le')
243
- }
244
- if (buf[0] === 0x01) {
245
- return buf.subarray(1).toString('utf8')
246
- }
247
- return buf.toString('utf8')
260
+ if (!token) return null
261
+ return { token, encryptionKeys }
248
262
  }
249
263
 
250
- private extractFromRawFiles(leveldbDir: string): ExtractedWebexToken | null {
264
+ private scanRawFiles(leveldbDir: string): ScanResult | null {
265
+ let token: ExtractedWebexToken | null = null
266
+ const encryptionKeys = new Map<string, string>()
267
+
251
268
  try {
252
269
  const files = readdirSync(leveldbDir)
253
270
 
@@ -265,32 +282,48 @@ export class WebexTokenExtractor {
265
282
  })
266
283
 
267
284
  for (const file of sorted) {
268
- const token = this.extractFromFile(join(leveldbDir, file))
269
- if (token) return token
285
+ const filePath = join(leveldbDir, file)
286
+ try {
287
+ const stat = statSync(filePath)
288
+ if (stat.size > 50 * 1024 * 1024) continue
289
+
290
+ const buffer = readFileSync(filePath)
291
+ const candidates = [buffer.toString('utf8'), this.stripNullBytes(buffer)]
292
+
293
+ for (const content of candidates) {
294
+ if (!token && (content.includes('"supertoken"') || content.includes('"Credentials"'))) {
295
+ token = this.extractTokenFromString(content)
296
+ }
297
+
298
+ if (content.includes('"Encryption"') && content.includes('kms://')) {
299
+ const found = this.extractEncryptionKeysFromString(content)
300
+ for (const [uri, keyStr] of found) {
301
+ encryptionKeys.set(uri, keyStr)
302
+ }
303
+ }
304
+ }
305
+ } catch {
306
+ // Skip unreadable files
307
+ }
270
308
  }
271
309
  } catch {
272
310
  this.debug(`Failed to read directory: ${leveldbDir}`)
273
311
  }
274
312
 
275
- return null
313
+ if (!token) return null
314
+ return { token, encryptionKeys }
276
315
  }
277
316
 
278
- private extractFromFile(filePath: string): ExtractedWebexToken | null {
279
- try {
280
- const stat = statSync(filePath)
281
- if (stat.size > 50 * 1024 * 1024) return null
282
-
283
- const buffer = readFileSync(filePath)
284
- return this.extractTokenFromBuffer(buffer)
285
- } catch {
286
- return null
317
+ private decodeLevelDBValue(buf: Buffer): string {
318
+ if (buf.length < 2) return buf.toString('utf8')
319
+ // Chromium localStorage: 0x00 prefix = UTF-16LE, 0x01 prefix = Latin1/UTF-8
320
+ if (buf[0] === 0x00 && (buf.length - 1) % 2 === 0) {
321
+ return buf.subarray(1).toString('utf16le')
287
322
  }
288
- }
289
-
290
- private extractTokenFromBuffer(buffer: Buffer): ExtractedWebexToken | null {
291
- // Try UTF-8 first, then strip null bytes for Chromium's UTF-16LE localStorage encoding
292
- return this.extractTokenFromString(buffer.toString('utf8'))
293
- ?? this.extractTokenFromString(this.stripNullBytes(buffer))
323
+ if (buf[0] === 0x01) {
324
+ return buf.subarray(1).toString('utf8')
325
+ }
326
+ return buf.toString('utf8')
294
327
  }
295
328
 
296
329
  private stripNullBytes(buffer: Buffer): string {
@@ -385,9 +418,43 @@ export class WebexTokenExtractor {
385
418
  result.deviceUrl = deviceUrl
386
419
  }
387
420
 
421
+ const userId = data?.Device?.['@']?.userId ?? null
422
+ if (userId && typeof userId === 'string') {
423
+ result.userId = userId
424
+ }
425
+
388
426
  return result
389
427
  } catch {
390
428
  return null
391
429
  }
392
430
  }
431
+
432
+ private extractEncryptionKeysFromString(content: string): Map<string, string> {
433
+ const keys = new Map<string, string>()
434
+
435
+ if (!content.includes('"Encryption"') || !content.includes('kms://')) {
436
+ return keys
437
+ }
438
+
439
+ // Values in the Encryption map are double-encoded: a kms:// URI key maps to a
440
+ // JSON string whose content is the serialized key object {"uri":..., "jwk":{...}}.
441
+ // Pattern: "kms://..." : "{\"uri\":...,\"jwk\":{...}}"
442
+ const kmsPattern = /"(kms:\/\/[^"]+)"\s*:\s*("(?:[^"\\]|\\.)*")/g
443
+ let match: RegExpExecArray | null
444
+
445
+ while ((match = kmsPattern.exec(content)) !== null) {
446
+ const uri = match[1]!
447
+ const rawValue = match[2]!
448
+ try {
449
+ const innerStr = JSON.parse(rawValue) as unknown
450
+ if (typeof innerStr !== 'string') continue
451
+ const keyObj = JSON.parse(innerStr) as Record<string, unknown>
452
+ if (keyObj?.jwk) {
453
+ keys.set(uri, innerStr)
454
+ }
455
+ } catch {}
456
+ }
457
+
458
+ return keys
459
+ }
393
460
  }
@@ -57,6 +57,8 @@ export interface WebexConfig {
57
57
  clientSecret?: string
58
58
  tokenType?: 'oauth' | 'manual' | 'extracted'
59
59
  deviceUrl?: string
60
+ userId?: string
61
+ encryptionKeys?: Record<string, string>
60
62
  }
61
63
 
62
64
  export class WebexError extends Error {
@@ -126,4 +128,6 @@ export const WebexConfigSchema = z.object({
126
128
  clientSecret: z.string().optional(),
127
129
  tokenType: z.enum(['oauth', 'manual', 'extracted']).optional(),
128
130
  deviceUrl: z.string().optional(),
131
+ userId: z.string().optional(),
132
+ encryptionKeys: z.record(z.string(), z.string()).optional(),
129
133
  })
@@ -0,0 +1,27 @@
1
+ export {}
2
+
3
+ declare module 'node-jose' {
4
+ namespace JWE {
5
+ interface JWERecipient {
6
+ key: JWK.Key
7
+ header?: Record<string, string | null>
8
+ reference?: string | null | boolean
9
+ }
10
+
11
+ function createEncrypt(options: EncryptOptions, recipient: JWERecipient): Encryptor
12
+
13
+ interface Encryptor {
14
+ final(data: string | Buffer, encoding?: BufferEncoding): Promise<string>
15
+ }
16
+
17
+ function createDecrypt(key: JWK.Key): Decryptor
18
+
19
+ interface Decryptor {
20
+ decrypt(input: string | Buffer): Promise<DecryptResult>
21
+ }
22
+
23
+ interface DecryptResult {
24
+ plaintext: Buffer
25
+ }
26
+ }
27
+ }