agent-messenger 2.0.0 → 2.2.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 (272) hide show
  1. package/.claude-plugin/marketplace.json +14 -1
  2. package/.claude-plugin/plugin.json +4 -2
  3. package/.env.template +35 -17
  4. package/README.md +37 -33
  5. package/bun.lock +6 -6
  6. package/dist/package.json +11 -3
  7. package/dist/src/cli.d.ts.map +1 -1
  8. package/dist/src/cli.js +3 -0
  9. package/dist/src/cli.js.map +1 -1
  10. package/dist/src/platforms/channeltalk/commands/auth.d.ts.map +1 -1
  11. package/dist/src/platforms/channeltalk/commands/auth.js +35 -28
  12. package/dist/src/platforms/channeltalk/commands/auth.js.map +1 -1
  13. package/dist/src/platforms/channeltalk/ensure-auth.js +6 -6
  14. package/dist/src/platforms/channeltalk/ensure-auth.js.map +1 -1
  15. package/dist/src/platforms/channeltalk/token-extractor.d.ts +23 -1
  16. package/dist/src/platforms/channeltalk/token-extractor.d.ts.map +1 -1
  17. package/dist/src/platforms/channeltalk/token-extractor.js +299 -29
  18. package/dist/src/platforms/channeltalk/token-extractor.js.map +1 -1
  19. package/dist/src/platforms/discord/commands/auth.d.ts.map +1 -1
  20. package/dist/src/platforms/discord/commands/auth.js +57 -49
  21. package/dist/src/platforms/discord/commands/auth.js.map +1 -1
  22. package/dist/src/platforms/discord/ensure-auth.js +3 -3
  23. package/dist/src/platforms/discord/ensure-auth.js.map +1 -1
  24. package/dist/src/platforms/discord/token-extractor.d.ts +6 -1
  25. package/dist/src/platforms/discord/token-extractor.d.ts.map +1 -1
  26. package/dist/src/platforms/discord/token-extractor.js +167 -14
  27. package/dist/src/platforms/discord/token-extractor.js.map +1 -1
  28. package/dist/src/platforms/instagram/client.d.ts +2 -0
  29. package/dist/src/platforms/instagram/client.d.ts.map +1 -1
  30. package/dist/src/platforms/instagram/client.js +2 -2
  31. package/dist/src/platforms/instagram/client.js.map +1 -1
  32. package/dist/src/platforms/instagram/commands/auth.d.ts.map +1 -1
  33. package/dist/src/platforms/instagram/commands/auth.js +107 -14
  34. package/dist/src/platforms/instagram/commands/auth.js.map +1 -1
  35. package/dist/src/platforms/instagram/ensure-auth.d.ts.map +1 -1
  36. package/dist/src/platforms/instagram/ensure-auth.js +57 -11
  37. package/dist/src/platforms/instagram/ensure-auth.js.map +1 -1
  38. package/dist/src/platforms/instagram/index.d.ts +1 -0
  39. package/dist/src/platforms/instagram/index.d.ts.map +1 -1
  40. package/dist/src/platforms/instagram/index.js +1 -0
  41. package/dist/src/platforms/instagram/index.js.map +1 -1
  42. package/dist/src/platforms/instagram/token-extractor.d.ts +44 -0
  43. package/dist/src/platforms/instagram/token-extractor.d.ts.map +1 -0
  44. package/dist/src/platforms/instagram/token-extractor.js +407 -0
  45. package/dist/src/platforms/instagram/token-extractor.js.map +1 -0
  46. package/dist/src/platforms/kakaotalk/client.d.ts.map +1 -1
  47. package/dist/src/platforms/kakaotalk/client.js +2 -1
  48. package/dist/src/platforms/kakaotalk/client.js.map +1 -1
  49. package/dist/src/platforms/kakaotalk/commands/auth.d.ts.map +1 -1
  50. package/dist/src/platforms/kakaotalk/commands/auth.js +14 -13
  51. package/dist/src/platforms/kakaotalk/commands/auth.js.map +1 -1
  52. package/dist/src/platforms/kakaotalk/protocol/connection.d.ts.map +1 -1
  53. package/dist/src/platforms/kakaotalk/protocol/connection.js +2 -1
  54. package/dist/src/platforms/kakaotalk/protocol/connection.js.map +1 -1
  55. package/dist/src/platforms/line/commands/auth.d.ts.map +1 -1
  56. package/dist/src/platforms/line/commands/auth.js +6 -5
  57. package/dist/src/platforms/line/commands/auth.js.map +1 -1
  58. package/dist/src/platforms/slack/commands/auth.d.ts.map +1 -1
  59. package/dist/src/platforms/slack/commands/auth.js +11 -10
  60. package/dist/src/platforms/slack/commands/auth.js.map +1 -1
  61. package/dist/src/platforms/slack/token-extractor.d.ts +9 -0
  62. package/dist/src/platforms/slack/token-extractor.d.ts.map +1 -1
  63. package/dist/src/platforms/slack/token-extractor.js +300 -23
  64. package/dist/src/platforms/slack/token-extractor.js.map +1 -1
  65. package/dist/src/platforms/teams/commands/auth.d.ts.map +1 -1
  66. package/dist/src/platforms/teams/commands/auth.js +9 -8
  67. package/dist/src/platforms/teams/commands/auth.js.map +1 -1
  68. package/dist/src/platforms/teams/ensure-auth.d.ts.map +1 -1
  69. package/dist/src/platforms/teams/ensure-auth.js +2 -1
  70. package/dist/src/platforms/teams/ensure-auth.js.map +1 -1
  71. package/dist/src/platforms/teams/token-extractor.d.ts +5 -0
  72. package/dist/src/platforms/teams/token-extractor.d.ts.map +1 -1
  73. package/dist/src/platforms/teams/token-extractor.js +161 -29
  74. package/dist/src/platforms/teams/token-extractor.js.map +1 -1
  75. package/dist/src/platforms/telegram/client.d.ts.map +1 -1
  76. package/dist/src/platforms/telegram/client.js +25 -7
  77. package/dist/src/platforms/telegram/client.js.map +1 -1
  78. package/dist/src/platforms/telegram/commands/auth.d.ts.map +1 -1
  79. package/dist/src/platforms/telegram/commands/auth.js +6 -5
  80. package/dist/src/platforms/telegram/commands/auth.js.map +1 -1
  81. package/dist/src/platforms/webex/app-config.d.ts +7 -0
  82. package/dist/src/platforms/webex/app-config.d.ts.map +1 -0
  83. package/dist/src/platforms/webex/app-config.js +20 -0
  84. package/dist/src/platforms/webex/app-config.js.map +1 -0
  85. package/dist/src/platforms/webex/cli.d.ts +5 -0
  86. package/dist/src/platforms/webex/cli.d.ts.map +1 -0
  87. package/dist/src/platforms/webex/cli.js +32 -0
  88. package/dist/src/platforms/webex/cli.js.map +1 -0
  89. package/dist/src/platforms/webex/client.d.ts +55 -0
  90. package/dist/src/platforms/webex/client.d.ts.map +1 -0
  91. package/dist/src/platforms/webex/client.js +299 -0
  92. package/dist/src/platforms/webex/client.js.map +1 -0
  93. package/dist/src/platforms/webex/commands/auth.d.ts +19 -0
  94. package/dist/src/platforms/webex/commands/auth.d.ts.map +1 -0
  95. package/dist/src/platforms/webex/commands/auth.js +166 -0
  96. package/dist/src/platforms/webex/commands/auth.js.map +1 -0
  97. package/dist/src/platforms/webex/commands/index.d.ts +6 -0
  98. package/dist/src/platforms/webex/commands/index.d.ts.map +1 -0
  99. package/dist/src/platforms/webex/commands/index.js +6 -0
  100. package/dist/src/platforms/webex/commands/index.js.map +1 -0
  101. package/dist/src/platforms/webex/commands/member.d.ts +7 -0
  102. package/dist/src/platforms/webex/commands/member.d.ts.map +1 -0
  103. package/dist/src/platforms/webex/commands/member.js +34 -0
  104. package/dist/src/platforms/webex/commands/member.js.map +1 -0
  105. package/dist/src/platforms/webex/commands/message.d.ts +26 -0
  106. package/dist/src/platforms/webex/commands/message.d.ts.map +1 -0
  107. package/dist/src/platforms/webex/commands/message.js +153 -0
  108. package/dist/src/platforms/webex/commands/message.js.map +1 -0
  109. package/dist/src/platforms/webex/commands/snapshot.d.ts +9 -0
  110. package/dist/src/platforms/webex/commands/snapshot.d.ts.map +1 -0
  111. package/dist/src/platforms/webex/commands/snapshot.js +72 -0
  112. package/dist/src/platforms/webex/commands/snapshot.js.map +1 -0
  113. package/dist/src/platforms/webex/commands/space.d.ts +11 -0
  114. package/dist/src/platforms/webex/commands/space.d.ts.map +1 -0
  115. package/dist/src/platforms/webex/commands/space.js +59 -0
  116. package/dist/src/platforms/webex/commands/space.js.map +1 -0
  117. package/dist/src/platforms/webex/credential-manager.d.ts +23 -0
  118. package/dist/src/platforms/webex/credential-manager.d.ts.map +1 -0
  119. package/dist/src/platforms/webex/credential-manager.js +148 -0
  120. package/dist/src/platforms/webex/credential-manager.js.map +1 -0
  121. package/dist/src/platforms/webex/ensure-auth.d.ts +2 -0
  122. package/dist/src/platforms/webex/ensure-auth.d.ts.map +1 -0
  123. package/dist/src/platforms/webex/ensure-auth.js +36 -0
  124. package/dist/src/platforms/webex/ensure-auth.js.map +1 -0
  125. package/dist/src/platforms/webex/index.d.ts +8 -0
  126. package/dist/src/platforms/webex/index.d.ts.map +1 -0
  127. package/dist/src/platforms/webex/index.js +6 -0
  128. package/dist/src/platforms/webex/index.js.map +1 -0
  129. package/dist/src/platforms/webex/token-extractor.d.ts +28 -0
  130. package/dist/src/platforms/webex/token-extractor.d.ts.map +1 -0
  131. package/dist/src/platforms/webex/token-extractor.js +344 -0
  132. package/dist/src/platforms/webex/token-extractor.js.map +1 -0
  133. package/dist/src/platforms/webex/types.d.ts +127 -0
  134. package/dist/src/platforms/webex/types.d.ts.map +1 -0
  135. package/dist/src/platforms/webex/types.js +64 -0
  136. package/dist/src/platforms/webex/types.js.map +1 -0
  137. package/dist/src/platforms/whatsapp/client.d.ts.map +1 -1
  138. package/dist/src/platforms/whatsapp/client.js +6 -2
  139. package/dist/src/platforms/whatsapp/client.js.map +1 -1
  140. package/dist/src/shared/utils/derived-key-cache.d.ts +1 -1
  141. package/dist/src/shared/utils/derived-key-cache.d.ts.map +1 -1
  142. package/dist/src/shared/utils/error-handler.d.ts +1 -1
  143. package/dist/src/shared/utils/error-handler.d.ts.map +1 -1
  144. package/dist/src/shared/utils/error-handler.js +3 -2
  145. package/dist/src/shared/utils/error-handler.js.map +1 -1
  146. package/dist/src/shared/utils/stderr.d.ts +5 -0
  147. package/dist/src/shared/utils/stderr.d.ts.map +1 -0
  148. package/dist/src/shared/utils/stderr.js +18 -0
  149. package/dist/src/shared/utils/stderr.js.map +1 -0
  150. package/dist/src/tui/adapters/webex-adapter.d.ts +14 -0
  151. package/dist/src/tui/adapters/webex-adapter.d.ts.map +1 -0
  152. package/dist/src/tui/adapters/webex-adapter.js +79 -0
  153. package/dist/src/tui/adapters/webex-adapter.js.map +1 -0
  154. package/dist/src/tui/app.d.ts.map +1 -1
  155. package/dist/src/tui/app.js +2 -0
  156. package/dist/src/tui/app.js.map +1 -1
  157. package/docs/content/docs/cli/channeltalk.mdx +7 -7
  158. package/docs/content/docs/cli/discord.mdx +3 -3
  159. package/docs/content/docs/cli/instagram.mdx +28 -6
  160. package/docs/content/docs/cli/meta.json +1 -0
  161. package/docs/content/docs/cli/slack.mdx +2 -2
  162. package/docs/content/docs/cli/teams.mdx +6 -4
  163. package/docs/content/docs/cli/webex.mdx +310 -0
  164. package/docs/content/docs/sdk/meta.json +1 -1
  165. package/docs/content/docs/sdk/webex.mdx +260 -0
  166. package/docs/content/docs/tui.mdx +4 -3
  167. package/docs/src/app/page.tsx +2 -2
  168. package/e2e/README.md +132 -8
  169. package/e2e/channeltalk.e2e.test.ts +2 -7
  170. package/e2e/channeltalkbot.e2e.test.ts +2 -6
  171. package/e2e/config.ts +172 -10
  172. package/e2e/helpers.ts +7 -0
  173. package/e2e/instagram.e2e.test.ts +97 -0
  174. package/e2e/kakaotalk.e2e.test.ts +74 -0
  175. package/e2e/line.e2e.test.ts +92 -0
  176. package/e2e/teams.e2e.test.ts +46 -1
  177. package/e2e/telegram.e2e.test.ts +84 -0
  178. package/e2e/webex.e2e.test.ts +190 -0
  179. package/e2e/whatsapp.e2e.test.ts +90 -0
  180. package/e2e/whatsappbot.e2e.test.ts +78 -0
  181. package/package.json +11 -3
  182. package/skills/agent-channeltalk/SKILL.md +9 -9
  183. package/skills/agent-channeltalk/references/authentication.md +21 -18
  184. package/skills/agent-channeltalkbot/SKILL.md +1 -1
  185. package/skills/agent-discord/SKILL.md +5 -5
  186. package/skills/agent-discord/references/authentication.md +8 -8
  187. package/skills/agent-discordbot/SKILL.md +1 -1
  188. package/skills/agent-instagram/SKILL.md +51 -9
  189. package/skills/agent-instagram/references/authentication.md +35 -3
  190. package/skills/agent-kakaotalk/SKILL.md +1 -1
  191. package/skills/agent-line/SKILL.md +1 -1
  192. package/skills/agent-slack/SKILL.md +5 -5
  193. package/skills/agent-slack/references/authentication.md +8 -8
  194. package/skills/agent-slackbot/SKILL.md +1 -1
  195. package/skills/agent-teams/SKILL.md +6 -6
  196. package/skills/agent-teams/references/authentication.md +8 -8
  197. package/skills/agent-telegram/SKILL.md +1 -1
  198. package/skills/agent-webex/SKILL.md +406 -0
  199. package/skills/agent-webex/references/authentication.md +371 -0
  200. package/skills/agent-webex/references/common-patterns.md +726 -0
  201. package/skills/agent-webex/templates/monitor-space.sh +165 -0
  202. package/skills/agent-webex/templates/post-message.sh +170 -0
  203. package/skills/agent-whatsapp/SKILL.md +1 -1
  204. package/skills/agent-whatsappbot/SKILL.md +1 -1
  205. package/src/cli.ts +4 -0
  206. package/src/platforms/channeltalk/commands/auth.test.ts +5 -5
  207. package/src/platforms/channeltalk/commands/auth.ts +38 -32
  208. package/src/platforms/channeltalk/ensure-auth.test.ts +6 -6
  209. package/src/platforms/channeltalk/ensure-auth.ts +6 -6
  210. package/src/platforms/channeltalk/token-extractor.test.ts +182 -15
  211. package/src/platforms/channeltalk/token-extractor.ts +344 -30
  212. package/src/platforms/discord/commands/auth.test.ts +3 -3
  213. package/src/platforms/discord/commands/auth.ts +58 -54
  214. package/src/platforms/discord/ensure-auth.test.ts +3 -3
  215. package/src/platforms/discord/ensure-auth.ts +3 -3
  216. package/src/platforms/discord/token-extractor.test.ts +199 -27
  217. package/src/platforms/discord/token-extractor.ts +190 -17
  218. package/src/platforms/instagram/client.ts +2 -2
  219. package/src/platforms/instagram/commands/auth.ts +133 -14
  220. package/src/platforms/instagram/ensure-auth.ts +63 -12
  221. package/src/platforms/instagram/index.ts +1 -0
  222. package/src/platforms/instagram/token-extractor.test.ts +424 -0
  223. package/src/platforms/instagram/token-extractor.ts +478 -0
  224. package/src/platforms/kakaotalk/client.ts +3 -1
  225. package/src/platforms/kakaotalk/commands/auth.ts +14 -13
  226. package/src/platforms/kakaotalk/protocol/connection.ts +3 -1
  227. package/src/platforms/line/commands/auth.ts +7 -6
  228. package/src/platforms/slack/cli.test.ts +6 -5
  229. package/src/platforms/slack/commands/auth.test.ts +11 -7
  230. package/src/platforms/slack/commands/auth.ts +11 -10
  231. package/src/platforms/slack/token-extractor.test.ts +98 -1
  232. package/src/platforms/slack/token-extractor.ts +338 -26
  233. package/src/platforms/teams/commands/auth.ts +9 -8
  234. package/src/platforms/teams/ensure-auth.ts +3 -1
  235. package/src/platforms/teams/token-extractor.test.ts +136 -17
  236. package/src/platforms/teams/token-extractor.ts +182 -31
  237. package/src/platforms/telegram/client.test.ts +134 -0
  238. package/src/platforms/telegram/client.ts +27 -6
  239. package/src/platforms/telegram/commands/auth.ts +6 -5
  240. package/src/platforms/webex/app-config.test.ts +98 -0
  241. package/src/platforms/webex/app-config.ts +31 -0
  242. package/src/platforms/webex/cli.test.ts +58 -0
  243. package/src/platforms/webex/cli.ts +39 -0
  244. package/src/platforms/webex/client.test.ts +743 -0
  245. package/src/platforms/webex/client.ts +405 -0
  246. package/src/platforms/webex/commands/auth.test.ts +222 -0
  247. package/src/platforms/webex/commands/auth.ts +243 -0
  248. package/src/platforms/webex/commands/index.ts +5 -0
  249. package/src/platforms/webex/commands/member.test.ts +112 -0
  250. package/src/platforms/webex/commands/member.ts +45 -0
  251. package/src/platforms/webex/commands/message.test.ts +235 -0
  252. package/src/platforms/webex/commands/message.ts +204 -0
  253. package/src/platforms/webex/commands/snapshot.test.ts +105 -0
  254. package/src/platforms/webex/commands/snapshot.ts +91 -0
  255. package/src/platforms/webex/commands/space.test.ts +216 -0
  256. package/src/platforms/webex/commands/space.ts +74 -0
  257. package/src/platforms/webex/credential-manager.test.ts +314 -0
  258. package/src/platforms/webex/credential-manager.ts +197 -0
  259. package/src/platforms/webex/ensure-auth.test.ts +89 -0
  260. package/src/platforms/webex/ensure-auth.ts +38 -0
  261. package/src/platforms/webex/index.test.ts +25 -0
  262. package/src/platforms/webex/index.ts +19 -0
  263. package/src/platforms/webex/token-extractor.test.ts +327 -0
  264. package/src/platforms/webex/token-extractor.ts +393 -0
  265. package/src/platforms/webex/types.test.ts +307 -0
  266. package/src/platforms/webex/types.ts +129 -0
  267. package/src/platforms/whatsapp/client.ts +11 -7
  268. package/src/shared/utils/derived-key-cache.ts +1 -1
  269. package/src/shared/utils/error-handler.ts +4 -2
  270. package/src/shared/utils/stderr.ts +22 -0
  271. package/src/tui/adapters/webex-adapter.ts +103 -0
  272. package/src/tui/app.ts +2 -0
@@ -0,0 +1,243 @@
1
+ import { Command } from 'commander'
2
+
3
+ import { handleError } from '@/shared/utils/error-handler'
4
+ import { formatOutput } from '@/shared/utils/output'
5
+ import { info, debug } from '@/shared/utils/stderr'
6
+
7
+ import { getWebexAppCredentials } from '../app-config'
8
+ import { WebexClient } from '../client'
9
+ import { WebexCredentialManager } from '../credential-manager'
10
+ import { WebexTokenExtractor } from '../token-extractor'
11
+
12
+ interface ResolvedCredentials {
13
+ clientId: string
14
+ clientSecret: string
15
+ }
16
+
17
+ async function openBrowser(url: string): Promise<void> {
18
+ const { exec } = await import('node:child_process')
19
+ const command =
20
+ process.platform === 'darwin'
21
+ ? `open "${url}"`
22
+ : process.platform === 'win32'
23
+ ? `start "" "${url}"`
24
+ : `xdg-open "${url}"`
25
+ exec(command)
26
+ }
27
+
28
+ async function resolveClientCredentials(options: {
29
+ clientId?: string
30
+ clientSecret?: string
31
+ }): Promise<ResolvedCredentials> {
32
+ // 1. CLI flags
33
+ if (options.clientId || options.clientSecret) {
34
+ if (!options.clientId || !options.clientSecret) {
35
+ throw new Error('Both --client-id and --client-secret must be provided together.')
36
+ }
37
+ return { clientId: options.clientId, clientSecret: options.clientSecret }
38
+ }
39
+
40
+ // 2. Env vars → 3. Built-in defaults (always resolves)
41
+ return getWebexAppCredentials()
42
+ }
43
+
44
+ export async function loginAction(options: { token?: string; clientId?: string; clientSecret?: string; pretty?: boolean }): Promise<void> {
45
+ try {
46
+ const credManager = new WebexCredentialManager()
47
+
48
+ if (options.token) {
49
+ const client = await new WebexClient().login({ token: options.token })
50
+ const person = await client.testAuth()
51
+ await credManager.saveConfig({
52
+ accessToken: options.token,
53
+ refreshToken: '',
54
+ expiresAt: 0,
55
+ tokenType: 'manual',
56
+ })
57
+ console.log(
58
+ formatOutput(
59
+ {
60
+ user: { id: person.id, displayName: person.displayName, emails: person.emails },
61
+ authenticated: true,
62
+ },
63
+ options.pretty,
64
+ ),
65
+ )
66
+ return
67
+ }
68
+
69
+ const { clientId, clientSecret } = await resolveClientCredentials(options)
70
+
71
+ const device = await credManager.requestDeviceCode(clientId)
72
+
73
+ info(`Open this URL and enter the code: ${device.verificationUri}`)
74
+ info(`Code: ${device.userCode}`)
75
+ info('')
76
+ await openBrowser(device.verificationUriComplete)
77
+ info('Waiting for authorization...')
78
+
79
+ const config = await credManager.pollDeviceToken(
80
+ device.deviceCode,
81
+ device.interval,
82
+ device.expiresIn,
83
+ clientId,
84
+ clientSecret,
85
+ )
86
+
87
+ await credManager.saveConfig({ ...config, clientId, clientSecret, tokenType: 'oauth' })
88
+
89
+ const client = await new WebexClient().login({ token: config.accessToken })
90
+ const person = await client.testAuth()
91
+
92
+ console.log(
93
+ formatOutput(
94
+ {
95
+ user: { id: person.id, displayName: person.displayName, emails: person.emails },
96
+ authenticated: true,
97
+ },
98
+ options.pretty,
99
+ ),
100
+ )
101
+ } catch (error) {
102
+ handleError(error as Error)
103
+ }
104
+ }
105
+
106
+ export async function statusAction(options: { pretty?: boolean }): Promise<void> {
107
+ try {
108
+ const credManager = new WebexCredentialManager()
109
+ const config = await credManager.loadConfig()
110
+ const token = await credManager.getToken(config?.clientId, config?.clientSecret)
111
+
112
+ if (!token) {
113
+ console.log(
114
+ formatOutput({ error: 'Not authenticated. Run "auth login" first.' }, options.pretty),
115
+ )
116
+ process.exit(1)
117
+ return
118
+ }
119
+
120
+ try {
121
+ const client = await new WebexClient().login({ token })
122
+ const person = await client.testAuth()
123
+ console.log(
124
+ formatOutput(
125
+ {
126
+ authenticated: true,
127
+ user: { id: person.id, displayName: person.displayName, emails: person.emails },
128
+ },
129
+ options.pretty,
130
+ ),
131
+ )
132
+ } catch {
133
+ console.log(formatOutput({ authenticated: false, user: null }, options.pretty))
134
+ }
135
+ } catch (error) {
136
+ handleError(error as Error)
137
+ }
138
+ }
139
+
140
+ export async function extractAction(options: { pretty?: boolean; debug?: boolean }): Promise<void> {
141
+ try {
142
+ const extractor = new WebexTokenExtractor(
143
+ undefined,
144
+ options.debug ? (msg) => debug(`[debug] ${msg}`) : undefined,
145
+ )
146
+
147
+ if (options.debug) {
148
+ debug('[debug] Searching browser profiles for Webex tokens...')
149
+ }
150
+
151
+ const extracted = await extractor.extract()
152
+
153
+ if (!extracted) {
154
+ console.log(
155
+ formatOutput(
156
+ {
157
+ error: 'No Webex token found in any browser. Make sure you are logged in to web.webex.com in Chrome, Edge, Arc, or Brave.',
158
+ hint: 'Run "auth login" for OAuth Device Grant flow, or --debug for more info.',
159
+ },
160
+ options.pretty,
161
+ ),
162
+ )
163
+ process.exit(1)
164
+ return
165
+ }
166
+
167
+ const client = await new WebexClient().login({ token: extracted.accessToken })
168
+ const person = await client.testAuth()
169
+
170
+ const credManager = new WebexCredentialManager()
171
+ await credManager.saveConfig({
172
+ accessToken: extracted.accessToken,
173
+ refreshToken: extracted.refreshToken ?? '',
174
+ expiresAt: extracted.expiresAt ?? 0,
175
+ tokenType: 'extracted',
176
+ deviceUrl: extracted.deviceUrl,
177
+ })
178
+
179
+ console.log(
180
+ formatOutput(
181
+ {
182
+ user: { id: person.id, displayName: person.displayName, emails: person.emails },
183
+ authenticated: true,
184
+ tokenType: 'extracted',
185
+ },
186
+ options.pretty,
187
+ ),
188
+ )
189
+ } catch (error) {
190
+ handleError(error as Error)
191
+ }
192
+ }
193
+
194
+ export async function logoutAction(options: { pretty?: boolean }): Promise<void> {
195
+ try {
196
+ const credManager = new WebexCredentialManager()
197
+ const config = await credManager.loadConfig()
198
+
199
+ if (!config) {
200
+ console.log(
201
+ formatOutput({ error: 'Not authenticated. Run "auth login" first.' }, options.pretty),
202
+ )
203
+ process.exit(1)
204
+ return
205
+ }
206
+
207
+ await credManager.clearCredentials()
208
+ console.log(formatOutput({ removed: 'webex', success: true }, options.pretty))
209
+ } catch (error) {
210
+ handleError(error as Error)
211
+ }
212
+ }
213
+
214
+ export const authCommand = new Command('auth')
215
+ .description('Authentication commands')
216
+ .addCommand(
217
+ new Command('login')
218
+ .description('Login to Webex')
219
+ .option('--token <token>', 'Use a bot token or personal access token directly')
220
+ .option('--client-id <id>', 'Webex Integration client ID')
221
+ .option('--client-secret <secret>', 'Webex Integration client secret')
222
+ .option('--pretty', 'Pretty print JSON output')
223
+ .action(loginAction),
224
+ )
225
+ .addCommand(
226
+ new Command('extract')
227
+ .description('Extract Webex token from browser (Chrome, Edge, Arc, Brave)')
228
+ .option('--pretty', 'Pretty print JSON output')
229
+ .option('--debug', 'Show debug output')
230
+ .action(extractAction),
231
+ )
232
+ .addCommand(
233
+ new Command('status')
234
+ .description('Show authentication status')
235
+ .option('--pretty', 'Pretty print JSON output')
236
+ .action(statusAction),
237
+ )
238
+ .addCommand(
239
+ new Command('logout')
240
+ .description('Logout from Webex')
241
+ .option('--pretty', 'Pretty print JSON output')
242
+ .action(logoutAction),
243
+ )
@@ -0,0 +1,5 @@
1
+ export { authCommand } from './auth'
2
+ export { memberCommand } from './member'
3
+ export { messageCommand } from './message'
4
+ export { snapshotAction, snapshotCommand } from './snapshot'
5
+ export { spaceCommand } from './space'
@@ -0,0 +1,112 @@
1
+ import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from 'bun:test'
2
+
3
+ import { WebexClient } from '../client'
4
+ import { WebexError } from '../types'
5
+ import { listAction } from './member'
6
+
7
+ describe('member commands', () => {
8
+ let consoleSpy: ReturnType<typeof spyOn>
9
+ let consoleErrorSpy: ReturnType<typeof spyOn>
10
+ let processExitSpy: ReturnType<typeof spyOn>
11
+ let stderrOutput: string
12
+ let origStderrWrite: typeof process.stderr.write
13
+ const mockMembers = [
14
+ {
15
+ id: 'mem-1',
16
+ roomId: 'room-1',
17
+ personId: 'person-1',
18
+ personEmail: 'alice@example.com',
19
+ personDisplayName: 'Alice',
20
+ isModerator: true,
21
+ created: '2024-01-01T00:00:00.000Z',
22
+ },
23
+ {
24
+ id: 'mem-2',
25
+ roomId: 'room-1',
26
+ personId: 'person-2',
27
+ personEmail: 'bob@example.com',
28
+ personDisplayName: 'Bob',
29
+ isModerator: false,
30
+ created: '2024-01-02T00:00:00.000Z',
31
+ },
32
+ ]
33
+
34
+ beforeEach(() => {
35
+ consoleSpy = spyOn(console, 'log').mockImplementation(() => {})
36
+ consoleErrorSpy = spyOn(console, 'error').mockImplementation(() => {})
37
+ processExitSpy = spyOn(process, 'exit').mockImplementation((_code?: number) => {
38
+ throw new Error(`process.exit(${_code})`)
39
+ })
40
+ stderrOutput = ''
41
+ origStderrWrite = process.stderr.write
42
+ process.stderr.write = ((chunk: string | Uint8Array) => {
43
+ stderrOutput += typeof chunk === 'string' ? chunk : new TextDecoder().decode(chunk)
44
+ return true
45
+ }) as typeof process.stderr.write
46
+ spyOn(WebexClient.prototype, 'login').mockResolvedValue(new WebexClient() as any)
47
+ spyOn(WebexClient.prototype, 'listMemberships').mockResolvedValue(mockMembers)
48
+ })
49
+
50
+ afterEach(() => {
51
+ process.stderr.write = origStderrWrite
52
+ mock.restore()
53
+ })
54
+
55
+ test('listAction calls listMemberships with spaceId and outputs mapped members', async () => {
56
+ const listMembershipsSpy = spyOn(WebexClient.prototype, 'listMemberships').mockResolvedValue(
57
+ mockMembers,
58
+ )
59
+
60
+ await listAction('room-1', {})
61
+
62
+ expect(listMembershipsSpy).toHaveBeenCalledWith('room-1', { max: undefined })
63
+ expect(consoleSpy).toHaveBeenCalledWith(
64
+ JSON.stringify([
65
+ {
66
+ id: 'mem-1',
67
+ personId: 'person-1',
68
+ personEmail: 'alice@example.com',
69
+ personDisplayName: 'Alice',
70
+ isModerator: true,
71
+ created: '2024-01-01T00:00:00.000Z',
72
+ },
73
+ {
74
+ id: 'mem-2',
75
+ personId: 'person-2',
76
+ personEmail: 'bob@example.com',
77
+ personDisplayName: 'Bob',
78
+ isModerator: false,
79
+ created: '2024-01-02T00:00:00.000Z',
80
+ },
81
+ ]),
82
+ )
83
+ })
84
+
85
+ test('listAction passes limit option to listMemberships', async () => {
86
+ const listMembershipsSpy = spyOn(WebexClient.prototype, 'listMemberships').mockResolvedValue(
87
+ mockMembers,
88
+ )
89
+
90
+ await listAction('room-1', { limit: 25 })
91
+
92
+ expect(listMembershipsSpy).toHaveBeenCalledWith('room-1', { max: 25 })
93
+ })
94
+
95
+ test('listAction handles not-authenticated case', async () => {
96
+ spyOn(WebexClient.prototype, 'login').mockRejectedValue(
97
+ new WebexError('No Webex credentials found.', 'no_credentials'),
98
+ )
99
+
100
+ await expect(listAction('room-1', {})).rejects.toThrow('process.exit(1)')
101
+
102
+ expect(stderrOutput).toContain('No Webex credentials found')
103
+ })
104
+
105
+ test('listAction handles API error', async () => {
106
+ spyOn(WebexClient.prototype, 'listMemberships').mockRejectedValue(new Error('API failure'))
107
+
108
+ await expect(listAction('room-1', {})).rejects.toThrow('process.exit(1)')
109
+
110
+ expect(processExitSpy).toHaveBeenCalledWith(1)
111
+ })
112
+ })
@@ -0,0 +1,45 @@
1
+ import { Command } from 'commander'
2
+
3
+ import { handleError } from '@/shared/utils/error-handler'
4
+ import { formatOutput } from '@/shared/utils/output'
5
+
6
+ import { WebexClient } from '../client'
7
+
8
+ export async function listAction(
9
+ spaceId: string,
10
+ options: { limit?: number; pretty?: boolean },
11
+ ): Promise<void> {
12
+ try {
13
+ const client = await new WebexClient().login()
14
+ const members = await client.listMemberships(spaceId, { max: options.limit })
15
+
16
+ const output = members.map((m) => ({
17
+ id: m.id,
18
+ personId: m.personId,
19
+ personEmail: m.personEmail,
20
+ personDisplayName: m.personDisplayName,
21
+ isModerator: m.isModerator,
22
+ created: m.created,
23
+ }))
24
+
25
+ console.log(formatOutput(output, options.pretty))
26
+ } catch (error) {
27
+ handleError(error as Error)
28
+ }
29
+ }
30
+
31
+ export const memberCommand = new Command('member')
32
+ .description('Member commands')
33
+ .addCommand(
34
+ new Command('list')
35
+ .description('List members of a space')
36
+ .argument('<space-id>', 'Space ID')
37
+ .option('--limit <n>', 'Number of members to retrieve', '100')
38
+ .option('--pretty', 'Pretty print JSON output')
39
+ .action((spaceId, options) =>
40
+ listAction(spaceId, {
41
+ limit: parseInt(options.limit, 10),
42
+ pretty: options.pretty,
43
+ }),
44
+ ),
45
+ )
@@ -0,0 +1,235 @@
1
+ import { afterEach, beforeEach, expect, mock, spyOn, test } from 'bun:test'
2
+
3
+ import { WebexClient } from '../client'
4
+ import { WebexError } from '../types'
5
+ import { deleteAction, dmAction, editAction, getAction, listAction, sendAction } from './message'
6
+
7
+ let clientSendMessageSpy: ReturnType<typeof spyOn>
8
+ let clientSendDirectMessageSpy: ReturnType<typeof spyOn>
9
+ let clientListMessagesSpy: ReturnType<typeof spyOn>
10
+ let clientGetMessageSpy: ReturnType<typeof spyOn>
11
+ let clientDeleteMessageSpy: ReturnType<typeof spyOn>
12
+ let clientEditMessageSpy: ReturnType<typeof spyOn>
13
+ let clientLoginSpy: ReturnType<typeof spyOn>
14
+ const originalConsoleLog = console.log
15
+ const originalConsoleError = console.error
16
+
17
+ const mockMessage = {
18
+ id: 'msg_123',
19
+ roomId: 'space_456',
20
+ roomType: 'group' as const,
21
+ text: 'Hello world',
22
+ personId: 'person_789',
23
+ personEmail: 'user@example.com',
24
+ created: '2025-01-29T10:00:00.000Z',
25
+ }
26
+
27
+ const mockMessage2 = {
28
+ id: 'msg_124',
29
+ roomId: 'space_456',
30
+ roomType: 'group' as const,
31
+ text: 'Second message',
32
+ personId: 'person_789',
33
+ personEmail: 'user@example.com',
34
+ created: '2025-01-29T10:01:00.000Z',
35
+ }
36
+
37
+ beforeEach(() => {
38
+ clientLoginSpy = spyOn(WebexClient.prototype, 'login').mockResolvedValue(new WebexClient() as any)
39
+
40
+ clientSendMessageSpy = spyOn(WebexClient.prototype, 'sendMessage').mockResolvedValue(mockMessage)
41
+
42
+ clientSendDirectMessageSpy = spyOn(WebexClient.prototype, 'sendDirectMessage').mockResolvedValue(mockMessage)
43
+
44
+ clientListMessagesSpy = spyOn(WebexClient.prototype, 'listMessages').mockResolvedValue([
45
+ mockMessage,
46
+ mockMessage2,
47
+ ])
48
+
49
+ clientGetMessageSpy = spyOn(WebexClient.prototype, 'getMessage').mockResolvedValue(mockMessage)
50
+
51
+ clientDeleteMessageSpy = spyOn(WebexClient.prototype, 'deleteMessage').mockResolvedValue(
52
+ undefined,
53
+ )
54
+
55
+ clientEditMessageSpy = spyOn(WebexClient.prototype, 'editMessage').mockResolvedValue({
56
+ ...mockMessage,
57
+ text: 'Updated message',
58
+ })
59
+ })
60
+
61
+ afterEach(() => {
62
+ clientLoginSpy?.mockRestore()
63
+ clientSendMessageSpy?.mockRestore()
64
+ clientSendDirectMessageSpy?.mockRestore()
65
+ clientListMessagesSpy?.mockRestore()
66
+ clientGetMessageSpy?.mockRestore()
67
+ clientDeleteMessageSpy?.mockRestore()
68
+ clientEditMessageSpy?.mockRestore()
69
+ console.log = originalConsoleLog
70
+ console.error = originalConsoleError
71
+ })
72
+
73
+ test('send: calls sendMessage with correct args and outputs result', async () => {
74
+ const consoleSpy = mock((_msg: string) => {})
75
+ console.log = consoleSpy
76
+
77
+ await sendAction('space_456', 'Hello world', { pretty: false })
78
+
79
+ expect(clientSendMessageSpy).toHaveBeenCalledWith('space_456', 'Hello world', {
80
+ markdown: undefined,
81
+ })
82
+ expect(consoleSpy).toHaveBeenCalled()
83
+ const output = consoleSpy.mock.calls[0][0]
84
+ expect(output).toContain('msg_123')
85
+ expect(output).toContain('space_456')
86
+ expect(output).toContain('user@example.com')
87
+ })
88
+
89
+ test('send: with --markdown passes markdown option', async () => {
90
+ const consoleSpy = mock((_msg: string) => {})
91
+ console.log = consoleSpy
92
+
93
+ await sendAction('space_456', '**bold**', { markdown: true, pretty: false })
94
+
95
+ expect(clientSendMessageSpy).toHaveBeenCalledWith('space_456', '**bold**', { markdown: true })
96
+ })
97
+
98
+ test('send: not authenticated shows error', async () => {
99
+ clientLoginSpy.mockRejectedValue(new WebexError('No Webex credentials found.', 'no_credentials'))
100
+
101
+ let stderrOutput = ''
102
+ const origWrite = process.stderr.write
103
+ process.stderr.write = ((chunk: string | Uint8Array) => {
104
+ stderrOutput += typeof chunk === 'string' ? chunk : new TextDecoder().decode(chunk)
105
+ return true
106
+ }) as typeof process.stderr.write
107
+
108
+ const originalExit = process.exit
109
+ process.exit = mock((_code?: number) => {
110
+ throw new Error('process.exit called')
111
+ }) as never
112
+
113
+ try {
114
+ await sendAction('space_456', 'Hello', { pretty: false })
115
+ } catch {
116
+ } finally {
117
+ process.exit = originalExit
118
+ process.stderr.write = origWrite
119
+ }
120
+
121
+ expect(stderrOutput).toContain('No Webex credentials found')
122
+ })
123
+
124
+ test('dm: calls sendDirectMessage with email and text', async () => {
125
+ const consoleSpy = mock((_msg: string) => {})
126
+ console.log = consoleSpy
127
+
128
+ await dmAction('alice@example.com', 'Hello!', { pretty: false })
129
+
130
+ expect(clientSendDirectMessageSpy).toHaveBeenCalledWith('alice@example.com', 'Hello!', {
131
+ markdown: undefined,
132
+ })
133
+ expect(consoleSpy).toHaveBeenCalled()
134
+ const output = consoleSpy.mock.calls[0][0]
135
+ expect(output).toContain('msg_123')
136
+ })
137
+
138
+ test('dm: with --markdown passes markdown option', async () => {
139
+ const consoleSpy = mock((_msg: string) => {})
140
+ console.log = consoleSpy
141
+
142
+ await dmAction('alice@example.com', '**bold**', { markdown: true, pretty: false })
143
+
144
+ expect(clientSendDirectMessageSpy).toHaveBeenCalledWith('alice@example.com', '**bold**', {
145
+ markdown: true,
146
+ })
147
+ })
148
+
149
+ test('list: calls listMessages with limit and outputs array', async () => {
150
+ const consoleSpy = mock((_msg: string) => {})
151
+ console.log = consoleSpy
152
+
153
+ await listAction('space_456', { limit: 50, pretty: false })
154
+
155
+ expect(clientListMessagesSpy).toHaveBeenCalledWith('space_456', { max: 50 })
156
+ expect(consoleSpy).toHaveBeenCalled()
157
+ const output = consoleSpy.mock.calls[0][0]
158
+ expect(output).toContain('msg_123')
159
+ expect(output).toContain('msg_124')
160
+ })
161
+
162
+ test('get: calls getMessage with correct id and outputs result', async () => {
163
+ const consoleSpy = mock((_msg: string) => {})
164
+ console.log = consoleSpy
165
+
166
+ await getAction('msg_123', { pretty: false })
167
+
168
+ expect(clientGetMessageSpy).toHaveBeenCalledWith('msg_123')
169
+ expect(consoleSpy).toHaveBeenCalled()
170
+ const output = consoleSpy.mock.calls[0][0]
171
+ expect(output).toContain('msg_123')
172
+ expect(output).toContain('user@example.com')
173
+ })
174
+
175
+ test('delete: with --force calls deleteMessage and outputs deleted id', async () => {
176
+ const consoleSpy = mock((_msg: string) => {})
177
+ console.log = consoleSpy
178
+
179
+ await deleteAction('msg_123', { force: true, pretty: false })
180
+
181
+ expect(clientDeleteMessageSpy).toHaveBeenCalledWith('msg_123')
182
+ expect(consoleSpy).toHaveBeenCalled()
183
+ const output = consoleSpy.mock.calls[0][0]
184
+ expect(output).toContain('deleted')
185
+ expect(output).toContain('msg_123')
186
+ })
187
+
188
+ test('delete: without --force shows warning and does not delete', async () => {
189
+ const consoleSpy = mock((_msg: string) => {})
190
+ console.log = consoleSpy
191
+
192
+ const originalExit = process.exit
193
+ process.exit = mock((_code?: number) => {
194
+ throw new Error('process.exit called')
195
+ }) as never
196
+
197
+ try {
198
+ await deleteAction('msg_123', { force: false, pretty: false })
199
+ } catch {
200
+ } finally {
201
+ process.exit = originalExit
202
+ }
203
+
204
+ expect(clientDeleteMessageSpy).not.toHaveBeenCalled()
205
+ expect(consoleSpy).toHaveBeenCalled()
206
+ const output = consoleSpy.mock.calls[0][0]
207
+ expect(output).toContain('warning')
208
+ expect(output).toContain('--force')
209
+ })
210
+
211
+ test('edit: calls editMessage with roomId in args and outputs result', async () => {
212
+ const consoleSpy = mock((_msg: string) => {})
213
+ console.log = consoleSpy
214
+
215
+ await editAction('msg_123', 'space_456', 'Updated message', { pretty: false })
216
+
217
+ expect(clientEditMessageSpy).toHaveBeenCalledWith('msg_123', 'space_456', 'Updated message', {
218
+ markdown: undefined,
219
+ })
220
+ expect(consoleSpy).toHaveBeenCalled()
221
+ const output = consoleSpy.mock.calls[0][0]
222
+ expect(output).toContain('msg_123')
223
+ expect(output).toContain('Updated message')
224
+ })
225
+
226
+ test('edit: with --markdown passes markdown option', async () => {
227
+ const consoleSpy = mock((_msg: string) => {})
228
+ console.log = consoleSpy
229
+
230
+ await editAction('msg_123', 'space_456', '**updated**', { markdown: true, pretty: false })
231
+
232
+ expect(clientEditMessageSpy).toHaveBeenCalledWith('msg_123', 'space_456', '**updated**', {
233
+ markdown: true,
234
+ })
235
+ })