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
@@ -16,6 +16,140 @@ const mockPaths: TelegramAccountPaths = {
16
16
  files_dir: '/tmp/test-files',
17
17
  }
18
18
 
19
+ function createMockClient(sendHandler: (request: any, events: any[]) => void) {
20
+ const events: any[] = []
21
+ const createClientId = mock(() => 1)
22
+ const send = mock((_clientId: number, request: any) => sendHandler(request, events))
23
+ const receive = mock(() => events.shift() ?? null)
24
+
25
+ const client = new (TelegramTdlibClient as unknown as new (
26
+ account: TelegramAccount,
27
+ paths: TelegramAccountPaths,
28
+ tdjson: any,
29
+ ) => TelegramTdlibClient)(mockAccount, mockPaths, {
30
+ createClientId,
31
+ send,
32
+ receive,
33
+ libraryPath: '/mock/lib',
34
+ })
35
+
36
+ return { client, events, send }
37
+ }
38
+
39
+ function pushAuthReady(events: any[], extra: string) {
40
+ events.push({
41
+ '@type': 'updateAuthorizationState',
42
+ authorization_state: { '@type': 'authorizationStateReady' },
43
+ '@extra': extra,
44
+ })
45
+ }
46
+
47
+ describe('listChats', () => {
48
+ test('loads chats across multiple loadChats calls until 404', async () => {
49
+ let loadChatsCallCount = 0
50
+ const allChatIds = [1, 2, 3, 4, 5]
51
+
52
+ const { client } = createMockClient((request, events) => {
53
+ if (request['@type'] === 'getAuthorizationState') {
54
+ pushAuthReady(events, request['@extra'])
55
+ return
56
+ }
57
+
58
+ if (request['@type'] === 'loadChats') {
59
+ loadChatsCallCount += 1
60
+ if (loadChatsCallCount <= 2) {
61
+ // given — first two calls succeed (simulate partial loading)
62
+ events.push({ '@type': 'ok', '@extra': request['@extra'] })
63
+ } else {
64
+ // given — third call returns 404 (all chats loaded)
65
+ events.push({ '@type': 'error', code: 404, message: 'Chat list has been loaded completely', '@extra': request['@extra'] })
66
+ }
67
+ return
68
+ }
69
+
70
+ if (request['@type'] === 'getChats') {
71
+ // when — after first two loadChats calls, return partial; after 404, return all
72
+ const returnCount = loadChatsCallCount >= 3 ? allChatIds.length : Math.min(loadChatsCallCount * 2, allChatIds.length)
73
+ events.push({
74
+ '@type': 'chats',
75
+ total_count: returnCount,
76
+ chat_ids: allChatIds.slice(0, returnCount),
77
+ '@extra': request['@extra'],
78
+ })
79
+ return
80
+ }
81
+
82
+ if (request['@type'] === 'getChat') {
83
+ const chatId = request.chat_id
84
+ const typeNames = ['chatTypePrivate', 'chatTypePrivate', 'chatTypeBasicGroup', 'chatTypeSupergroup', 'chatTypeSupergroup']
85
+ const idx = allChatIds.indexOf(chatId)
86
+ events.push({
87
+ '@type': 'chat',
88
+ id: chatId,
89
+ title: `Chat ${chatId}`,
90
+ type: { '@type': typeNames[idx] ?? 'chatTypePrivate' },
91
+ unread_count: 0,
92
+ '@extra': request['@extra'],
93
+ })
94
+ return
95
+ }
96
+ })
97
+
98
+ const chats = await client.listChats(5)
99
+
100
+ // then — all 5 chats returned including groups
101
+ expect(chats).toHaveLength(5)
102
+ expect(chats.map((c) => c.type)).toEqual(['private', 'private', 'basicgroup', 'supergroup', 'supergroup'])
103
+ expect(loadChatsCallCount).toBe(3)
104
+ })
105
+
106
+ test('stops loading when enough chats are cached before 404', async () => {
107
+ let loadChatsCallCount = 0
108
+
109
+ const { client } = createMockClient((request, events) => {
110
+ if (request['@type'] === 'getAuthorizationState') {
111
+ pushAuthReady(events, request['@extra'])
112
+ return
113
+ }
114
+
115
+ if (request['@type'] === 'loadChats') {
116
+ loadChatsCallCount += 1
117
+ events.push({ '@type': 'ok', '@extra': request['@extra'] })
118
+ return
119
+ }
120
+
121
+ if (request['@type'] === 'getChats') {
122
+ // given — always return 3 chats (enough for limit=3)
123
+ events.push({
124
+ '@type': 'chats',
125
+ total_count: 3,
126
+ chat_ids: [10, 20, 30],
127
+ '@extra': request['@extra'],
128
+ })
129
+ return
130
+ }
131
+
132
+ if (request['@type'] === 'getChat') {
133
+ events.push({
134
+ '@type': 'chat',
135
+ id: request.chat_id,
136
+ title: `Chat ${request.chat_id}`,
137
+ type: { '@type': 'chatTypeSupergroup' },
138
+ unread_count: 0,
139
+ '@extra': request['@extra'],
140
+ })
141
+ return
142
+ }
143
+ })
144
+
145
+ const chats = await client.listChats(3)
146
+
147
+ // then — stops after first loop iteration since we have enough
148
+ expect(chats).toHaveLength(3)
149
+ expect(loadChatsCallCount).toBe(1)
150
+ })
151
+ })
152
+
19
153
  describe('sendMessage confirmation', () => {
20
154
  test('returns confirmed message id when updateMessageSendSucceeded arrives', async () => {
21
155
  const tempId = 100
@@ -162,16 +162,37 @@ export class TelegramTdlibClient {
162
162
  async listChats(limit: number = 20): Promise<TelegramChatSummary[]> {
163
163
  await this.ensureReady()
164
164
 
165
- try {
166
- await this.call({
167
- '@type': 'loadChats',
165
+ // loadChats may load fewer chats than requested per call. Loop until TDLib
166
+ // signals 404 ("chat list fully loaded") or we have enough cached entries.
167
+ for (;;) {
168
+ try {
169
+ await this.call({
170
+ '@type': 'loadChats',
171
+ chat_list: {
172
+ '@type': 'chatListMain',
173
+ },
174
+ limit,
175
+ })
176
+ } catch (error) {
177
+ // TDLib signals 404 when the entire chat list has been loaded.
178
+ if (error instanceof TelegramError && error.code === 404) {
179
+ break
180
+ }
181
+
182
+ break
183
+ }
184
+
185
+ const partial = (await this.call({
186
+ '@type': 'getChats',
168
187
  chat_list: {
169
188
  '@type': 'chatListMain',
170
189
  },
171
190
  limit,
172
- })
173
- } catch {
174
- // Best-effort cache warmup only.
191
+ })) as TdChats
192
+
193
+ if ((partial.chat_ids ?? []).length >= limit) {
194
+ break
195
+ }
175
196
  }
176
197
 
177
198
  const response = (await this.call({
@@ -4,6 +4,7 @@ import { Command } from 'commander'
4
4
 
5
5
  import { handleError } from '../../../shared/utils/error-handler'
6
6
  import { formatOutput } from '../../../shared/utils/output'
7
+ import { info, error as stderrError } from '@/shared/utils/stderr'
7
8
  import { getTelegramAppCredentials } from '../app-config'
8
9
  import { TelegramTdlibClient } from '../client'
9
10
  import { TelegramCredentialManager } from '../credential-manager'
@@ -126,8 +127,8 @@ async function fillMissingBootstrappingInputs(
126
127
  if (!resolved.apiId && !existing?.api_id) {
127
128
  if (shouldUseInteractivePrompts()) {
128
129
  try {
129
- console.error('No API credentials found. Provisioning via my.telegram.org...')
130
- console.error('A verification code will be sent to your Telegram account.\n')
130
+ info('No API credentials found. Provisioning via my.telegram.org...')
131
+ info('A verification code will be sent to your Telegram account.\n')
131
132
 
132
133
  const phone = resolved.phone || (await promptText('Phone number (e.g. +14155551234)'))
133
134
  if (!phone) {
@@ -148,10 +149,10 @@ async function fillMissingBootstrappingInputs(
148
149
 
149
150
  resolved.apiId = String(app.api_id)
150
151
  resolved.apiHash = app.api_hash
151
- console.error(`\n✓ API credentials obtained (api_id: ${app.api_id})`)
152
+ info(`\n✓ API credentials obtained (api_id: ${app.api_id})`)
152
153
  } catch (error) {
153
- console.error(`\nAuto-provisioning failed: ${error instanceof Error ? error.message : error}`)
154
- console.error('Enter your API credentials manually (from https://my.telegram.org/apps):\n')
154
+ stderrError(`\nAuto-provisioning failed: ${error instanceof Error ? error.message : error}`)
155
+ info('Enter your API credentials manually (from https://my.telegram.org/apps):\n')
155
156
  resolved.apiId = await promptText('Telegram API ID')
156
157
  resolved.apiHash = await promptHidden('Telegram API hash')
157
158
  }
@@ -0,0 +1,98 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
2
+ import { getWebexAppCredentials } from './app-config'
3
+
4
+ const ENV_KEYS = [
5
+ 'AGENT_WEBEX_CLIENT_ID',
6
+ 'AGENT_WEBEX_CLIENT_SECRET',
7
+ 'AGENT_MESSENGER_WEBEX_CLIENT_ID',
8
+ 'AGENT_MESSENGER_WEBEX_CLIENT_SECRET',
9
+ ] as const
10
+
11
+ describe('webex app config', () => {
12
+ let savedEnv: Record<string, string | undefined> = {}
13
+
14
+ beforeEach(() => {
15
+ savedEnv = {}
16
+ for (const key of ENV_KEYS) {
17
+ savedEnv[key] = process.env[key]
18
+ delete process.env[key]
19
+ }
20
+ })
21
+
22
+ afterEach(() => {
23
+ for (const key of ENV_KEYS) {
24
+ if (savedEnv[key] === undefined) {
25
+ delete process.env[key]
26
+ } else {
27
+ process.env[key] = savedEnv[key]
28
+ }
29
+ }
30
+ })
31
+
32
+ test('returns env credentials when primary env vars are set', () => {
33
+ process.env.AGENT_WEBEX_CLIENT_ID = 'my-client-id'
34
+ process.env.AGENT_WEBEX_CLIENT_SECRET = 'my-client-secret'
35
+
36
+ expect(getWebexAppCredentials()).toEqual({
37
+ clientId: 'my-client-id',
38
+ clientSecret: 'my-client-secret',
39
+ source: 'env',
40
+ })
41
+ })
42
+
43
+ test('returns env credentials when legacy env vars are set', () => {
44
+ process.env.AGENT_MESSENGER_WEBEX_CLIENT_ID = 'legacy-client-id'
45
+ process.env.AGENT_MESSENGER_WEBEX_CLIENT_SECRET = 'legacy-client-secret'
46
+
47
+ expect(getWebexAppCredentials()).toEqual({
48
+ clientId: 'legacy-client-id',
49
+ clientSecret: 'legacy-client-secret',
50
+ source: 'env',
51
+ })
52
+ })
53
+
54
+ test('returns builtin credentials when nothing is configured', () => {
55
+ const result = getWebexAppCredentials()
56
+ expect(result.source).toBe('builtin')
57
+ expect(result.clientId).toBeTruthy()
58
+ expect(result.clientSecret).toBeTruthy()
59
+ })
60
+
61
+ test('returns builtin when only clientId is set', () => {
62
+ process.env.AGENT_WEBEX_CLIENT_ID = 'my-client-id'
63
+
64
+ const result = getWebexAppCredentials()
65
+ expect(result.source).toBe('builtin')
66
+ })
67
+
68
+ test('returns builtin when only clientSecret is set', () => {
69
+ process.env.AGENT_WEBEX_CLIENT_SECRET = 'my-client-secret'
70
+
71
+ const result = getWebexAppCredentials()
72
+ expect(result.source).toBe('builtin')
73
+ })
74
+
75
+ test('trims whitespace from clientId and clientSecret', () => {
76
+ process.env.AGENT_WEBEX_CLIENT_ID = ' my-client-id '
77
+ process.env.AGENT_WEBEX_CLIENT_SECRET = ' my-client-secret '
78
+
79
+ expect(getWebexAppCredentials()).toEqual({
80
+ clientId: 'my-client-id',
81
+ clientSecret: 'my-client-secret',
82
+ source: 'env',
83
+ })
84
+ })
85
+
86
+ test('primary env takes precedence over legacy env', () => {
87
+ process.env.AGENT_WEBEX_CLIENT_ID = 'primary-id'
88
+ process.env.AGENT_WEBEX_CLIENT_SECRET = 'primary-secret'
89
+ process.env.AGENT_MESSENGER_WEBEX_CLIENT_ID = 'legacy-id'
90
+ process.env.AGENT_MESSENGER_WEBEX_CLIENT_SECRET = 'legacy-secret'
91
+
92
+ expect(getWebexAppCredentials()).toEqual({
93
+ clientId: 'primary-id',
94
+ clientSecret: 'primary-secret',
95
+ source: 'env',
96
+ })
97
+ })
98
+ })
@@ -0,0 +1,31 @@
1
+ const BUILTIN_CLIENT_ID = 'C720341c19d8dfb4cab8a5db78be4bc6d5c3983fbe84df94be34c8aa69a695583'
2
+ const BUILTIN_CLIENT_SECRET = 'e90806657443a7f16093c0846690aeeea96cd2b3ed9b79cf544297c526b4f9af'
3
+
4
+ export interface WebexAppCredentials {
5
+ clientId: string
6
+ clientSecret: string
7
+ source: 'env' | 'builtin'
8
+ }
9
+
10
+ function parseTrimmed(value: string | undefined): string | undefined {
11
+ const normalized = value?.trim()
12
+ return normalized ? normalized : undefined
13
+ }
14
+
15
+ export function getWebexAppCredentials(): WebexAppCredentials {
16
+ const envClientId = parseTrimmed(process.env.AGENT_WEBEX_CLIENT_ID)
17
+ const envClientSecret = parseTrimmed(process.env.AGENT_WEBEX_CLIENT_SECRET)
18
+
19
+ if (envClientId && envClientSecret) {
20
+ return { clientId: envClientId, clientSecret: envClientSecret, source: 'env' }
21
+ }
22
+
23
+ const legacyClientId = parseTrimmed(process.env.AGENT_MESSENGER_WEBEX_CLIENT_ID)
24
+ const legacyClientSecret = parseTrimmed(process.env.AGENT_MESSENGER_WEBEX_CLIENT_SECRET)
25
+
26
+ if (legacyClientId && legacyClientSecret) {
27
+ return { clientId: legacyClientId, clientSecret: legacyClientSecret, source: 'env' }
28
+ }
29
+
30
+ return { clientId: BUILTIN_CLIENT_ID, clientSecret: BUILTIN_CLIENT_SECRET, source: 'builtin' }
31
+ }
@@ -0,0 +1,58 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+
3
+ import { spawn } from 'bun'
4
+
5
+ import pkg from '../../../package.json' with { type: 'json' }
6
+
7
+ describe('Webex CLI program structure', () => {
8
+ test('--help shows all commands', async () => {
9
+ const proc = spawn(['bun', 'run', './src/platforms/webex/cli.ts', '--help'], {
10
+ cwd: process.cwd(),
11
+ stdio: ['pipe', 'pipe', 'pipe'],
12
+ })
13
+
14
+ const output = await new Response(proc.stdout).text()
15
+
16
+ expect(output).toContain('auth')
17
+ expect(output).toContain('member')
18
+ expect(output).toContain('message')
19
+ expect(output).toContain('snapshot')
20
+ expect(output).toContain('space')
21
+ })
22
+
23
+ test('--version shows package version', async () => {
24
+ const proc = spawn(['bun', 'run', './src/platforms/webex/cli.ts', '--version'], {
25
+ cwd: process.cwd(),
26
+ stdio: ['pipe', 'pipe', 'pipe'],
27
+ })
28
+
29
+ const output = await new Response(proc.stdout).text()
30
+ expect(output.trim()).toBe(pkg.version)
31
+ })
32
+
33
+ test('auth login --help shows Device Grant options', async () => {
34
+ const proc = spawn(['bun', 'run', './src/platforms/webex/cli.ts', 'auth', 'login', '--help'], {
35
+ cwd: process.cwd(),
36
+ stdio: ['pipe', 'pipe', 'pipe'],
37
+ })
38
+
39
+ const output = await new Response(proc.stdout).text()
40
+
41
+ expect(output).toContain('--token')
42
+ expect(output).toContain('--client-id')
43
+ expect(output).toContain('--client-secret')
44
+ expect(output).toContain('--pretty')
45
+ })
46
+
47
+ test('message dm --help shows email argument', async () => {
48
+ const proc = spawn(['bun', 'run', './src/platforms/webex/cli.ts', 'message', 'dm', '--help'], {
49
+ cwd: process.cwd(),
50
+ stdio: ['pipe', 'pipe', 'pipe'],
51
+ })
52
+
53
+ const output = await new Response(proc.stdout).text()
54
+
55
+ expect(output).toContain('email')
56
+ expect(output).toContain('--markdown')
57
+ })
58
+ })
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import type { Command as CommandType } from 'commander'
4
+ import { Command } from 'commander'
5
+
6
+ import pkg from '../../../package.json' with { type: 'json' }
7
+ import { authCommand, memberCommand, messageCommand, snapshotCommand, spaceCommand } from './commands'
8
+ import { ensureWebexAuth } from './ensure-auth'
9
+
10
+ function isAuthCommand(command: CommandType): boolean {
11
+ let cmd: CommandType | null = command
12
+ while (cmd) {
13
+ if (cmd.name() === 'auth') return true
14
+ cmd = cmd.parent
15
+ }
16
+ return false
17
+ }
18
+
19
+ const program = new Command()
20
+
21
+ program
22
+ .name('agent-webex')
23
+ .description('CLI tool for Cisco Webex communication')
24
+ .version(pkg.version)
25
+
26
+ program.hook('preAction', async (_thisCommand, actionCommand) => {
27
+ if (isAuthCommand(actionCommand)) return
28
+ await ensureWebexAuth()
29
+ })
30
+
31
+ program.addCommand(authCommand)
32
+ program.addCommand(memberCommand)
33
+ program.addCommand(messageCommand)
34
+ program.addCommand(snapshotCommand)
35
+ program.addCommand(spaceCommand)
36
+
37
+ program.parse(process.argv)
38
+
39
+ export default program