agent-messenger 2.10.2 → 2.11.1

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 (330) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.env.template +4 -1
  3. package/README.md +77 -27
  4. package/bun.lock +26 -0
  5. package/dist/package.json +21 -1
  6. package/dist/src/platforms/channeltalk/commands/auth.d.ts +2 -1
  7. package/dist/src/platforms/channeltalk/commands/auth.d.ts.map +1 -1
  8. package/dist/src/platforms/channeltalk/commands/auth.js +5 -3
  9. package/dist/src/platforms/channeltalk/commands/auth.js.map +1 -1
  10. package/dist/src/platforms/channeltalk/token-extractor.d.ts +2 -1
  11. package/dist/src/platforms/channeltalk/token-extractor.d.ts.map +1 -1
  12. package/dist/src/platforms/channeltalk/token-extractor.js +22 -6
  13. package/dist/src/platforms/channeltalk/token-extractor.js.map +1 -1
  14. package/dist/src/platforms/channeltalkbot/cli.d.ts.map +1 -1
  15. package/dist/src/platforms/channeltalkbot/cli.js +11 -1
  16. package/dist/src/platforms/channeltalkbot/cli.js.map +1 -1
  17. package/dist/src/platforms/channeltalkbot/commands/auth.d.ts.map +1 -1
  18. package/dist/src/platforms/channeltalkbot/commands/auth.js +1 -5
  19. package/dist/src/platforms/channeltalkbot/commands/auth.js.map +1 -1
  20. package/dist/src/platforms/channeltalkbot/commands/bot.d.ts.map +1 -1
  21. package/dist/src/platforms/channeltalkbot/commands/bot.js +1 -6
  22. package/dist/src/platforms/channeltalkbot/commands/bot.js.map +1 -1
  23. package/dist/src/platforms/channeltalkbot/commands/chat.d.ts.map +1 -1
  24. package/dist/src/platforms/channeltalkbot/commands/chat.js +1 -6
  25. package/dist/src/platforms/channeltalkbot/commands/chat.js.map +1 -1
  26. package/dist/src/platforms/channeltalkbot/commands/group.d.ts.map +1 -1
  27. package/dist/src/platforms/channeltalkbot/commands/group.js +1 -6
  28. package/dist/src/platforms/channeltalkbot/commands/group.js.map +1 -1
  29. package/dist/src/platforms/channeltalkbot/commands/manager.d.ts.map +1 -1
  30. package/dist/src/platforms/channeltalkbot/commands/manager.js +1 -6
  31. package/dist/src/platforms/channeltalkbot/commands/manager.js.map +1 -1
  32. package/dist/src/platforms/channeltalkbot/commands/message.d.ts.map +1 -1
  33. package/dist/src/platforms/channeltalkbot/commands/message.js +1 -6
  34. package/dist/src/platforms/channeltalkbot/commands/message.js.map +1 -1
  35. package/dist/src/platforms/channeltalkbot/commands/whoami.d.ts.map +1 -1
  36. package/dist/src/platforms/channeltalkbot/commands/whoami.js +1 -6
  37. package/dist/src/platforms/channeltalkbot/commands/whoami.js.map +1 -1
  38. package/dist/src/platforms/channeltalkbot/credential-manager.d.ts +5 -0
  39. package/dist/src/platforms/channeltalkbot/credential-manager.d.ts.map +1 -1
  40. package/dist/src/platforms/channeltalkbot/credential-manager.js +34 -4
  41. package/dist/src/platforms/channeltalkbot/credential-manager.js.map +1 -1
  42. package/dist/src/platforms/discord/commands/auth.d.ts +1 -0
  43. package/dist/src/platforms/discord/commands/auth.d.ts.map +1 -1
  44. package/dist/src/platforms/discord/commands/auth.js +3 -1
  45. package/dist/src/platforms/discord/commands/auth.js.map +1 -1
  46. package/dist/src/platforms/discord/listener.d.ts +2 -0
  47. package/dist/src/platforms/discord/listener.d.ts.map +1 -1
  48. package/dist/src/platforms/discord/listener.js +51 -21
  49. package/dist/src/platforms/discord/listener.js.map +1 -1
  50. package/dist/src/platforms/discord/token-extractor.d.ts +2 -1
  51. package/dist/src/platforms/discord/token-extractor.d.ts.map +1 -1
  52. package/dist/src/platforms/discord/token-extractor.js +21 -6
  53. package/dist/src/platforms/discord/token-extractor.js.map +1 -1
  54. package/dist/src/platforms/discordbot/cli.d.ts.map +1 -1
  55. package/dist/src/platforms/discordbot/cli.js +12 -1
  56. package/dist/src/platforms/discordbot/cli.js.map +1 -1
  57. package/dist/src/platforms/discordbot/client.d.ts +3 -0
  58. package/dist/src/platforms/discordbot/client.d.ts.map +1 -1
  59. package/dist/src/platforms/discordbot/client.js +3 -0
  60. package/dist/src/platforms/discordbot/client.js.map +1 -1
  61. package/dist/src/platforms/discordbot/commands/auth.d.ts.map +1 -1
  62. package/dist/src/platforms/discordbot/commands/auth.js +1 -5
  63. package/dist/src/platforms/discordbot/commands/auth.js.map +1 -1
  64. package/dist/src/platforms/discordbot/commands/message.d.ts.map +1 -1
  65. package/dist/src/platforms/discordbot/commands/message.js +1 -6
  66. package/dist/src/platforms/discordbot/commands/message.js.map +1 -1
  67. package/dist/src/platforms/discordbot/commands/server.d.ts.map +1 -1
  68. package/dist/src/platforms/discordbot/commands/server.js +1 -4
  69. package/dist/src/platforms/discordbot/commands/server.js.map +1 -1
  70. package/dist/src/platforms/discordbot/commands/whoami.d.ts.map +1 -1
  71. package/dist/src/platforms/discordbot/commands/whoami.js +1 -6
  72. package/dist/src/platforms/discordbot/commands/whoami.js.map +1 -1
  73. package/dist/src/platforms/discordbot/index.d.ts +3 -1
  74. package/dist/src/platforms/discordbot/index.d.ts.map +1 -1
  75. package/dist/src/platforms/discordbot/index.js +2 -1
  76. package/dist/src/platforms/discordbot/index.js.map +1 -1
  77. package/dist/src/platforms/discordbot/listener.d.ts +43 -0
  78. package/dist/src/platforms/discordbot/listener.d.ts.map +1 -0
  79. package/dist/src/platforms/discordbot/listener.js +292 -0
  80. package/dist/src/platforms/discordbot/listener.js.map +1 -0
  81. package/dist/src/platforms/discordbot/types.d.ts +161 -0
  82. package/dist/src/platforms/discordbot/types.d.ts.map +1 -1
  83. package/dist/src/platforms/discordbot/types.js +34 -0
  84. package/dist/src/platforms/discordbot/types.js.map +1 -1
  85. package/dist/src/platforms/instagram/commands/auth.d.ts.map +1 -1
  86. package/dist/src/platforms/instagram/commands/auth.js +3 -1
  87. package/dist/src/platforms/instagram/commands/auth.js.map +1 -1
  88. package/dist/src/platforms/instagram/token-extractor.d.ts +2 -1
  89. package/dist/src/platforms/instagram/token-extractor.d.ts.map +1 -1
  90. package/dist/src/platforms/instagram/token-extractor.js +11 -2
  91. package/dist/src/platforms/instagram/token-extractor.js.map +1 -1
  92. package/dist/src/platforms/slack/commands/auth.d.ts.map +1 -1
  93. package/dist/src/platforms/slack/commands/auth.js +4 -2
  94. package/dist/src/platforms/slack/commands/auth.js.map +1 -1
  95. package/dist/src/platforms/slack/token-extractor.d.ts +4 -1
  96. package/dist/src/platforms/slack/token-extractor.d.ts.map +1 -1
  97. package/dist/src/platforms/slack/token-extractor.js +64 -15
  98. package/dist/src/platforms/slack/token-extractor.js.map +1 -1
  99. package/dist/src/platforms/slackbot/cli.d.ts.map +1 -1
  100. package/dist/src/platforms/slackbot/cli.js +15 -3
  101. package/dist/src/platforms/slackbot/cli.js.map +1 -1
  102. package/dist/src/platforms/slackbot/client.d.ts +22 -1
  103. package/dist/src/platforms/slackbot/client.d.ts.map +1 -1
  104. package/dist/src/platforms/slackbot/client.js +104 -1
  105. package/dist/src/platforms/slackbot/client.js.map +1 -1
  106. package/dist/src/platforms/slackbot/commands/auth.d.ts.map +1 -1
  107. package/dist/src/platforms/slackbot/commands/auth.js +1 -5
  108. package/dist/src/platforms/slackbot/commands/auth.js.map +1 -1
  109. package/dist/src/platforms/slackbot/commands/file.d.ts +3 -0
  110. package/dist/src/platforms/slackbot/commands/file.d.ts.map +1 -0
  111. package/dist/src/platforms/slackbot/commands/file.js +164 -0
  112. package/dist/src/platforms/slackbot/commands/file.js.map +1 -0
  113. package/dist/src/platforms/slackbot/commands/index.d.ts +1 -0
  114. package/dist/src/platforms/slackbot/commands/index.d.ts.map +1 -1
  115. package/dist/src/platforms/slackbot/commands/index.js +1 -0
  116. package/dist/src/platforms/slackbot/commands/index.js.map +1 -1
  117. package/dist/src/platforms/slackbot/commands/message.d.ts.map +1 -1
  118. package/dist/src/platforms/slackbot/commands/message.js +19 -0
  119. package/dist/src/platforms/slackbot/commands/message.js.map +1 -1
  120. package/dist/src/platforms/slackbot/commands/whoami.d.ts.map +1 -1
  121. package/dist/src/platforms/slackbot/commands/whoami.js +1 -6
  122. package/dist/src/platforms/slackbot/commands/whoami.js.map +1 -1
  123. package/dist/src/platforms/slackbot/credential-manager.d.ts +1 -0
  124. package/dist/src/platforms/slackbot/credential-manager.d.ts.map +1 -1
  125. package/dist/src/platforms/slackbot/credential-manager.js +30 -2
  126. package/dist/src/platforms/slackbot/credential-manager.js.map +1 -1
  127. package/dist/src/platforms/slackbot/index.d.ts +4 -1
  128. package/dist/src/platforms/slackbot/index.d.ts.map +1 -1
  129. package/dist/src/platforms/slackbot/index.js +1 -0
  130. package/dist/src/platforms/slackbot/index.js.map +1 -1
  131. package/dist/src/platforms/slackbot/listener.d.ts +44 -0
  132. package/dist/src/platforms/slackbot/listener.d.ts.map +1 -0
  133. package/dist/src/platforms/slackbot/listener.js +313 -0
  134. package/dist/src/platforms/slackbot/listener.js.map +1 -0
  135. package/dist/src/platforms/slackbot/types.d.ts +196 -1
  136. package/dist/src/platforms/slackbot/types.d.ts.map +1 -1
  137. package/dist/src/platforms/slackbot/types.js +4 -1
  138. package/dist/src/platforms/slackbot/types.js.map +1 -1
  139. package/dist/src/platforms/teams/commands/auth.d.ts +1 -0
  140. package/dist/src/platforms/teams/commands/auth.d.ts.map +1 -1
  141. package/dist/src/platforms/teams/commands/auth.js +37 -6
  142. package/dist/src/platforms/teams/commands/auth.js.map +1 -1
  143. package/dist/src/platforms/teams/ensure-auth.js +31 -9
  144. package/dist/src/platforms/teams/ensure-auth.js.map +1 -1
  145. package/dist/src/platforms/teams/token-extractor.d.ts +4 -1
  146. package/dist/src/platforms/teams/token-extractor.d.ts.map +1 -1
  147. package/dist/src/platforms/teams/token-extractor.js +71 -29
  148. package/dist/src/platforms/teams/token-extractor.js.map +1 -1
  149. package/dist/src/platforms/webex/commands/auth.d.ts +1 -0
  150. package/dist/src/platforms/webex/commands/auth.d.ts.map +1 -1
  151. package/dist/src/platforms/webex/commands/auth.js +3 -1
  152. package/dist/src/platforms/webex/commands/auth.js.map +1 -1
  153. package/dist/src/platforms/webex/token-extractor.d.ts +3 -1
  154. package/dist/src/platforms/webex/token-extractor.d.ts.map +1 -1
  155. package/dist/src/platforms/webex/token-extractor.js +16 -2
  156. package/dist/src/platforms/webex/token-extractor.js.map +1 -1
  157. package/dist/src/platforms/wechatbot/cli.d.ts.map +1 -1
  158. package/dist/src/platforms/wechatbot/cli.js +11 -1
  159. package/dist/src/platforms/wechatbot/cli.js.map +1 -1
  160. package/dist/src/platforms/wechatbot/commands/auth.d.ts.map +1 -1
  161. package/dist/src/platforms/wechatbot/commands/auth.js +1 -5
  162. package/dist/src/platforms/wechatbot/commands/auth.js.map +1 -1
  163. package/dist/src/platforms/wechatbot/commands/message.d.ts.map +1 -1
  164. package/dist/src/platforms/wechatbot/commands/message.js +1 -6
  165. package/dist/src/platforms/wechatbot/commands/message.js.map +1 -1
  166. package/dist/src/platforms/wechatbot/commands/template.d.ts.map +1 -1
  167. package/dist/src/platforms/wechatbot/commands/template.js +1 -6
  168. package/dist/src/platforms/wechatbot/commands/template.js.map +1 -1
  169. package/dist/src/platforms/wechatbot/commands/user.d.ts.map +1 -1
  170. package/dist/src/platforms/wechatbot/commands/user.js +1 -6
  171. package/dist/src/platforms/wechatbot/commands/user.js.map +1 -1
  172. package/dist/src/platforms/wechatbot/commands/whoami.d.ts.map +1 -1
  173. package/dist/src/platforms/wechatbot/commands/whoami.js +1 -6
  174. package/dist/src/platforms/wechatbot/commands/whoami.js.map +1 -1
  175. package/dist/src/platforms/whatsappbot/cli.d.ts.map +1 -1
  176. package/dist/src/platforms/whatsappbot/cli.js +11 -1
  177. package/dist/src/platforms/whatsappbot/cli.js.map +1 -1
  178. package/dist/src/platforms/whatsappbot/commands/auth.d.ts.map +1 -1
  179. package/dist/src/platforms/whatsappbot/commands/auth.js +1 -5
  180. package/dist/src/platforms/whatsappbot/commands/auth.js.map +1 -1
  181. package/dist/src/platforms/whatsappbot/commands/message.d.ts.map +1 -1
  182. package/dist/src/platforms/whatsappbot/commands/message.js +1 -6
  183. package/dist/src/platforms/whatsappbot/commands/message.js.map +1 -1
  184. package/dist/src/platforms/whatsappbot/commands/template.d.ts.map +1 -1
  185. package/dist/src/platforms/whatsappbot/commands/template.js +1 -6
  186. package/dist/src/platforms/whatsappbot/commands/template.js.map +1 -1
  187. package/dist/src/platforms/whatsappbot/commands/whoami.d.ts.map +1 -1
  188. package/dist/src/platforms/whatsappbot/commands/whoami.js +1 -6
  189. package/dist/src/platforms/whatsappbot/commands/whoami.js.map +1 -1
  190. package/dist/src/shared/chromium/browsers.d.ts +8 -0
  191. package/dist/src/shared/chromium/browsers.d.ts.map +1 -1
  192. package/dist/src/shared/chromium/browsers.js +58 -3
  193. package/dist/src/shared/chromium/browsers.js.map +1 -1
  194. package/dist/src/shared/chromium/cli-options.d.ts +5 -0
  195. package/dist/src/shared/chromium/cli-options.d.ts.map +1 -0
  196. package/dist/src/shared/chromium/cli-options.js +8 -0
  197. package/dist/src/shared/chromium/cli-options.js.map +1 -0
  198. package/dist/src/shared/chromium/index.d.ts +3 -1
  199. package/dist/src/shared/chromium/index.d.ts.map +1 -1
  200. package/dist/src/shared/chromium/index.js +2 -1
  201. package/dist/src/shared/chromium/index.js.map +1 -1
  202. package/dist/src/shared/utils/cli-output.d.ts +7 -0
  203. package/dist/src/shared/utils/cli-output.d.ts.map +1 -0
  204. package/dist/src/shared/utils/cli-output.js +7 -0
  205. package/dist/src/shared/utils/cli-output.js.map +1 -0
  206. package/dist/src/tui/app.d.ts.map +1 -1
  207. package/dist/src/tui/app.js +73 -20
  208. package/dist/src/tui/app.js.map +1 -1
  209. package/docs/content/docs/cli/channeltalk.mdx +4 -0
  210. package/docs/content/docs/cli/discord.mdx +5 -0
  211. package/docs/content/docs/cli/instagram.mdx +3 -0
  212. package/docs/content/docs/cli/slack.mdx +5 -0
  213. package/docs/content/docs/cli/slackbot.mdx +60 -22
  214. package/docs/content/docs/cli/teams.mdx +5 -0
  215. package/docs/content/docs/cli/webex.mdx +3 -0
  216. package/docs/content/docs/sdk/channeltalkbot.mdx +38 -1
  217. package/docs/content/docs/sdk/discordbot.mdx +501 -0
  218. package/docs/content/docs/sdk/meta.json +2 -0
  219. package/docs/content/docs/sdk/slackbot.mdx +576 -0
  220. package/e2e/README.md +1 -1
  221. package/e2e/config.ts +9 -4
  222. package/examples/discordbot-listen.ts +65 -0
  223. package/examples/slackbot-listen.ts +65 -0
  224. package/package.json +21 -1
  225. package/skills/agent-channeltalk/SKILL.md +5 -1
  226. package/skills/agent-channeltalk/references/authentication.md +5 -1
  227. package/skills/agent-channeltalkbot/SKILL.md +17 -3
  228. package/skills/agent-channeltalkbot/references/authentication.md +7 -5
  229. package/skills/agent-discord/SKILL.md +5 -1
  230. package/skills/agent-discord/references/authentication.md +7 -1
  231. package/skills/agent-discordbot/SKILL.md +13 -2
  232. package/skills/agent-discordbot/references/common-patterns.md +1 -1
  233. package/skills/agent-instagram/SKILL.md +7 -1
  234. package/skills/agent-instagram/references/authentication.md +6 -0
  235. package/skills/agent-kakaotalk/SKILL.md +1 -1
  236. package/skills/agent-line/SKILL.md +1 -1
  237. package/skills/agent-slack/SKILL.md +5 -1
  238. package/skills/agent-slack/references/authentication.md +7 -1
  239. package/skills/agent-slackbot/SKILL.md +56 -4
  240. package/skills/agent-slackbot/references/authentication.md +4 -0
  241. package/skills/agent-teams/SKILL.md +5 -1
  242. package/skills/agent-teams/references/authentication.md +7 -1
  243. package/skills/agent-telegram/SKILL.md +1 -1
  244. package/skills/agent-webex/SKILL.md +7 -1
  245. package/skills/agent-webex/references/authentication.md +6 -0
  246. package/skills/agent-wechatbot/SKILL.md +16 -1
  247. package/skills/agent-wechatbot/references/authentication.md +219 -0
  248. package/skills/agent-wechatbot/references/common-patterns.md +358 -0
  249. package/skills/agent-wechatbot/templates/account-summary.sh +122 -0
  250. package/skills/agent-wechatbot/templates/post-message.sh +122 -0
  251. package/skills/agent-wechatbot/templates/send-template.sh +152 -0
  252. package/skills/agent-whatsapp/SKILL.md +1 -1
  253. package/skills/agent-whatsappbot/SKILL.md +30 -1
  254. package/src/platforms/channeltalk/commands/auth.test.ts +15 -3
  255. package/src/platforms/channeltalk/commands/auth.ts +15 -5
  256. package/src/platforms/channeltalk/token-extractor.ts +24 -5
  257. package/src/platforms/channeltalkbot/cli.ts +9 -0
  258. package/src/platforms/channeltalkbot/commands/auth.ts +1 -5
  259. package/src/platforms/channeltalkbot/commands/bot.ts +1 -6
  260. package/src/platforms/channeltalkbot/commands/chat.ts +1 -6
  261. package/src/platforms/channeltalkbot/commands/group.ts +1 -6
  262. package/src/platforms/channeltalkbot/commands/manager.ts +1 -6
  263. package/src/platforms/channeltalkbot/commands/message.ts +1 -6
  264. package/src/platforms/channeltalkbot/commands/whoami.test.ts +2 -0
  265. package/src/platforms/channeltalkbot/commands/whoami.ts +1 -6
  266. package/src/platforms/channeltalkbot/credential-manager.test.ts +96 -2
  267. package/src/platforms/channeltalkbot/credential-manager.ts +37 -4
  268. package/src/platforms/discord/commands/auth.ts +13 -2
  269. package/src/platforms/discord/listener.test.ts +59 -1
  270. package/src/platforms/discord/listener.ts +43 -19
  271. package/src/platforms/discord/token-extractor.ts +30 -6
  272. package/src/platforms/discordbot/cli.ts +10 -0
  273. package/src/platforms/discordbot/client.ts +4 -0
  274. package/src/platforms/discordbot/commands/auth.ts +1 -5
  275. package/src/platforms/discordbot/commands/message.ts +1 -6
  276. package/src/platforms/discordbot/commands/server.ts +1 -5
  277. package/src/platforms/discordbot/commands/whoami.ts +1 -6
  278. package/src/platforms/discordbot/index.test.ts +82 -0
  279. package/src/platforms/discordbot/index.ts +27 -9
  280. package/src/platforms/discordbot/listener.test.ts +1002 -0
  281. package/src/platforms/discordbot/listener.ts +321 -0
  282. package/src/platforms/discordbot/types.ts +163 -0
  283. package/src/platforms/instagram/commands/auth.ts +9 -1
  284. package/src/platforms/instagram/token-extractor.ts +13 -1
  285. package/src/platforms/slack/commands/auth.ts +11 -2
  286. package/src/platforms/slack/token-extractor.test.ts +96 -0
  287. package/src/platforms/slack/token-extractor.ts +76 -13
  288. package/src/platforms/slackbot/cli.ts +13 -1
  289. package/src/platforms/slackbot/client.test.ts +274 -0
  290. package/src/platforms/slackbot/client.ts +130 -2
  291. package/src/platforms/slackbot/commands/auth.ts +1 -5
  292. package/src/platforms/slackbot/commands/file.test.ts +201 -0
  293. package/src/platforms/slackbot/commands/file.ts +212 -0
  294. package/src/platforms/slackbot/commands/index.ts +1 -0
  295. package/src/platforms/slackbot/commands/message.ts +22 -0
  296. package/src/platforms/slackbot/commands/whoami.ts +1 -6
  297. package/src/platforms/slackbot/credential-manager.test.ts +62 -2
  298. package/src/platforms/slackbot/credential-manager.ts +32 -2
  299. package/src/platforms/slackbot/index.test.ts +59 -0
  300. package/src/platforms/slackbot/index.ts +31 -7
  301. package/src/platforms/slackbot/listener.test.ts +1012 -0
  302. package/src/platforms/slackbot/listener.ts +362 -0
  303. package/src/platforms/slackbot/types.ts +224 -1
  304. package/src/platforms/teams/commands/auth.test.ts +1 -1
  305. package/src/platforms/teams/commands/auth.ts +66 -7
  306. package/src/platforms/teams/ensure-auth.test.ts +56 -5
  307. package/src/platforms/teams/ensure-auth.ts +39 -11
  308. package/src/platforms/teams/token-extractor.test.ts +146 -24
  309. package/src/platforms/teams/token-extractor.ts +87 -29
  310. package/src/platforms/webex/commands/auth.ts +13 -2
  311. package/src/platforms/webex/token-extractor.ts +25 -3
  312. package/src/platforms/wechatbot/cli.ts +9 -0
  313. package/src/platforms/wechatbot/commands/auth.ts +1 -5
  314. package/src/platforms/wechatbot/commands/message.ts +1 -6
  315. package/src/platforms/wechatbot/commands/template.ts +1 -6
  316. package/src/platforms/wechatbot/commands/user.ts +1 -6
  317. package/src/platforms/wechatbot/commands/whoami.ts +1 -6
  318. package/src/platforms/whatsappbot/cli.ts +9 -0
  319. package/src/platforms/whatsappbot/commands/auth.ts +1 -5
  320. package/src/platforms/whatsappbot/commands/message.ts +1 -6
  321. package/src/platforms/whatsappbot/commands/template.ts +1 -6
  322. package/src/platforms/whatsappbot/commands/whoami.ts +1 -6
  323. package/src/shared/chromium/browsers.test.ts +80 -0
  324. package/src/shared/chromium/browsers.ts +72 -3
  325. package/src/shared/chromium/cli-options.test.ts +22 -0
  326. package/src/shared/chromium/cli-options.ts +12 -0
  327. package/src/shared/chromium/index.ts +3 -0
  328. package/src/shared/utils/cli-output.test.ts +57 -0
  329. package/src/shared/utils/cli-output.ts +8 -0
  330. package/src/tui/app.ts +129 -20
@@ -11,6 +11,7 @@ import {
11
11
  discoverBrowserProfileDirs,
12
12
  findLocalStatePath,
13
13
  getBrowserBasePath,
14
+ getAgentBrowserProfileDirs,
14
15
  } from '@/shared/chromium'
15
16
  import type { KeychainVariant } from '@/shared/chromium'
16
17
  import { DerivedKeyCache } from '@/shared/utils/derived-key-cache'
@@ -20,11 +21,17 @@ import type { TeamsAccountType } from './types'
20
21
  export interface ExtractedTeamsToken {
21
22
  token: string
22
23
  accountType: TeamsAccountType
24
+ // False when the account type was guessed from the cookie path and needs to be
25
+ // confirmed against the Teams API (e.g. browser profiles, which don't encode
26
+ // work vs personal in the path). True for desktop paths that reliably encode
27
+ // the account type (WV2Profile_tfw vs WV2Profile_tfl).
28
+ accountTypeKnown: boolean
23
29
  }
24
30
 
25
31
  interface TeamsCookiePath {
26
32
  path: string
27
33
  accountType: TeamsAccountType
34
+ accountTypeKnown: boolean
28
35
  }
29
36
 
30
37
  const TEAMS_PROCESS_NAMES: Record<string, string> = {
@@ -56,10 +63,17 @@ export class TeamsTokenExtractor {
56
63
  private decryptor: ChromiumCookieDecryptor
57
64
  private cookieReader: ChromiumCookieReader
58
65
  private debugLog: ((message: string) => void) | null
59
-
60
- constructor(platform?: NodeJS.Platform, keyCache?: DerivedKeyCache, debugLog?: (message: string) => void) {
66
+ private customBrowserProfileDirs: string[]
67
+
68
+ constructor(
69
+ platform?: NodeJS.Platform,
70
+ keyCache?: DerivedKeyCache,
71
+ debugLog?: (message: string) => void,
72
+ customBrowserProfileDirs?: string[],
73
+ ) {
61
74
  this.platform = platform ?? process.platform
62
75
  this.debugLog = debugLog ?? null
76
+ this.customBrowserProfileDirs = customBrowserProfileDirs ?? []
63
77
 
64
78
  const resolvedKeyCache = keyCache ?? new DerivedKeyCache()
65
79
  this.decryptor = new ChromiumCookieDecryptor({
@@ -91,15 +105,28 @@ export class TeamsTokenExtractor {
91
105
  'EBWebView',
92
106
  )
93
107
  return [
94
- { path: join(ebWebViewBase, 'WV2Profile_tfw', 'Cookies'), accountType: 'work' },
95
- { path: join(ebWebViewBase, 'WV2Profile_tfw', 'Network', 'Cookies'), accountType: 'work' },
96
- { path: join(ebWebViewBase, 'WV2Profile_tfl', 'Cookies'), accountType: 'personal' },
97
- { path: join(ebWebViewBase, 'WV2Profile_tfl', 'Network', 'Cookies'), accountType: 'personal' },
98
- { path: join(ebWebViewBase, 'Default', 'Cookies'), accountType: 'work' },
99
- { path: join(ebWebViewBase, 'Default', 'Network', 'Cookies'), accountType: 'work' },
108
+ { path: join(ebWebViewBase, 'WV2Profile_tfw', 'Cookies'), accountType: 'work', accountTypeKnown: true },
109
+ {
110
+ path: join(ebWebViewBase, 'WV2Profile_tfw', 'Network', 'Cookies'),
111
+ accountType: 'work',
112
+ accountTypeKnown: true,
113
+ },
114
+ {
115
+ path: join(ebWebViewBase, 'WV2Profile_tfl', 'Cookies'),
116
+ accountType: 'personal',
117
+ accountTypeKnown: true,
118
+ },
119
+ {
120
+ path: join(ebWebViewBase, 'WV2Profile_tfl', 'Network', 'Cookies'),
121
+ accountType: 'personal',
122
+ accountTypeKnown: true,
123
+ },
124
+ { path: join(ebWebViewBase, 'Default', 'Cookies'), accountType: 'work', accountTypeKnown: false },
125
+ { path: join(ebWebViewBase, 'Default', 'Network', 'Cookies'), accountType: 'work', accountTypeKnown: false },
100
126
  {
101
127
  path: join(homedir(), 'Library', 'Application Support', 'Microsoft', 'Teams', 'Cookies'),
102
128
  accountType: 'work',
129
+ accountTypeKnown: false,
103
130
  },
104
131
  ]
105
132
  }
@@ -108,6 +135,7 @@ export class TeamsTokenExtractor {
108
135
  {
109
136
  path: join(homedir(), '.config', 'Microsoft', 'Microsoft Teams', 'Cookies'),
110
137
  accountType: 'work',
138
+ accountTypeKnown: false,
111
139
  },
112
140
  ]
113
141
  case 'win32': {
@@ -123,13 +151,25 @@ export class TeamsTokenExtractor {
123
151
  'EBWebView',
124
152
  )
125
153
  return [
126
- { path: join(ebWebViewBase, 'WV2Profile_tfw', 'Cookies'), accountType: 'work' },
127
- { path: join(ebWebViewBase, 'WV2Profile_tfw', 'Network', 'Cookies'), accountType: 'work' },
128
- { path: join(ebWebViewBase, 'WV2Profile_tfl', 'Cookies'), accountType: 'personal' },
129
- { path: join(ebWebViewBase, 'WV2Profile_tfl', 'Network', 'Cookies'), accountType: 'personal' },
130
- { path: join(ebWebViewBase, 'Default', 'Cookies'), accountType: 'work' },
131
- { path: join(ebWebViewBase, 'Default', 'Network', 'Cookies'), accountType: 'work' },
132
- { path: join(appdata, 'Microsoft', 'Teams', 'Cookies'), accountType: 'work' },
154
+ { path: join(ebWebViewBase, 'WV2Profile_tfw', 'Cookies'), accountType: 'work', accountTypeKnown: true },
155
+ {
156
+ path: join(ebWebViewBase, 'WV2Profile_tfw', 'Network', 'Cookies'),
157
+ accountType: 'work',
158
+ accountTypeKnown: true,
159
+ },
160
+ {
161
+ path: join(ebWebViewBase, 'WV2Profile_tfl', 'Cookies'),
162
+ accountType: 'personal',
163
+ accountTypeKnown: true,
164
+ },
165
+ {
166
+ path: join(ebWebViewBase, 'WV2Profile_tfl', 'Network', 'Cookies'),
167
+ accountType: 'personal',
168
+ accountTypeKnown: true,
169
+ },
170
+ { path: join(ebWebViewBase, 'Default', 'Cookies'), accountType: 'work', accountTypeKnown: false },
171
+ { path: join(ebWebViewBase, 'Default', 'Network', 'Cookies'), accountType: 'work', accountTypeKnown: false },
172
+ { path: join(appdata, 'Microsoft', 'Teams', 'Cookies'), accountType: 'work', accountTypeKnown: false },
133
173
  ]
134
174
  }
135
175
  default:
@@ -145,11 +185,16 @@ export class TeamsTokenExtractor {
145
185
  if (!browserBase) continue
146
186
 
147
187
  for (const profileDir of discoverBrowserProfileDirs(browserBase)) {
148
- paths.push({ path: join(profileDir, 'Cookies'), accountType: 'work' })
149
- paths.push({ path: join(profileDir, 'Network', 'Cookies'), accountType: 'work' })
188
+ paths.push({ path: join(profileDir, 'Cookies'), accountType: 'work', accountTypeKnown: false })
189
+ paths.push({ path: join(profileDir, 'Network', 'Cookies'), accountType: 'work', accountTypeKnown: false })
150
190
  }
151
191
  }
152
192
 
193
+ for (const profileDir of getAgentBrowserProfileDirs({ customProfileDirs: this.customBrowserProfileDirs })) {
194
+ paths.push({ path: join(profileDir, 'Cookies'), accountType: 'work', accountTypeKnown: false })
195
+ paths.push({ path: join(profileDir, 'Network', 'Cookies'), accountType: 'work', accountTypeKnown: false })
196
+ }
197
+
153
198
  return paths
154
199
  }
155
200
 
@@ -213,12 +258,13 @@ export class TeamsTokenExtractor {
213
258
 
214
259
  private async extractFromCookiesDB(): Promise<ExtractedTeamsToken[]> {
215
260
  const results: ExtractedTeamsToken[] = []
216
- const seenAccountTypes = new Set<TeamsAccountType>()
261
+ const seenKnownAccountTypes = new Set<TeamsAccountType>()
262
+ const seenTokens = new Set<string>()
217
263
  const allPaths = this.getTeamsCookiesPaths()
218
264
 
219
265
  this.debug(`Scanning ${allPaths.length} candidate cookie path(s)`)
220
266
 
221
- for (const { path: dbPath, accountType } of allPaths) {
267
+ for (const { path: dbPath, accountType, accountTypeKnown } of allPaths) {
222
268
  if (!dbPath) continue
223
269
 
224
270
  if (!existsSync(dbPath)) {
@@ -226,22 +272,34 @@ export class TeamsTokenExtractor {
226
272
  continue
227
273
  }
228
274
 
229
- if (seenAccountTypes.has(accountType)) {
275
+ if (accountTypeKnown && seenKnownAccountTypes.has(accountType)) {
230
276
  this.debug(` [skip] ${dbPath} (already have ${accountType} account)`)
231
277
  continue
232
278
  }
233
279
 
234
- this.debug(` [try] ${dbPath} (${accountType})`)
280
+ const typeLabel = accountTypeKnown ? accountType : `${accountType}?`
281
+ this.debug(` [try] ${dbPath} (${typeLabel})`)
235
282
 
236
283
  const token = await this.copyAndExtract(dbPath)
237
- if (token && this.isValidSkypeToken(token)) {
238
- this.debug(` [ok] Extracted valid token (${token.length} chars)`)
239
- results.push({ token, accountType })
240
- seenAccountTypes.add(accountType)
241
- } else if (token) {
242
- this.debug(` [fail] Token too short (${token.length} chars, need >=50)`)
243
- } else {
244
- this.debug(` [fail] No token extracted`)
284
+ if (!token || !this.isValidSkypeToken(token)) {
285
+ if (token) {
286
+ this.debug(` [fail] Token too short (${token.length} chars, need >=50)`)
287
+ } else {
288
+ this.debug(` [fail] No token extracted`)
289
+ }
290
+ continue
291
+ }
292
+
293
+ if (seenTokens.has(token)) {
294
+ this.debug(` [skip] Duplicate token (already extracted from another path)`)
295
+ continue
296
+ }
297
+
298
+ this.debug(` [ok] Extracted valid token (${token.length} chars)`)
299
+ results.push({ token, accountType, accountTypeKnown })
300
+ seenTokens.add(token)
301
+ if (accountTypeKnown) {
302
+ seenKnownAccountTypes.add(accountType)
245
303
  }
246
304
  }
247
305
 
@@ -1,5 +1,6 @@
1
1
  import { Command } from 'commander'
2
2
 
3
+ import { collectBrowserProfileOption } from '@/shared/chromium'
3
4
  import { handleError } from '@/shared/utils/error-handler'
4
5
  import { formatOutput } from '@/shared/utils/output'
5
6
  import { info, debug } from '@/shared/utils/stderr'
@@ -141,10 +142,14 @@ export async function statusAction(options: { pretty?: boolean }): Promise<void>
141
142
  }
142
143
  }
143
144
 
144
- export async function extractAction(options: { pretty?: boolean; debug?: boolean }): Promise<void> {
145
+ export async function extractAction(options: {
146
+ pretty?: boolean
147
+ debug?: boolean
148
+ browserProfile?: string[]
149
+ }): Promise<void> {
145
150
  try {
146
151
  const debugLog = options.debug ? (msg: string) => debug(`[debug] ${msg}`) : undefined
147
- const extractor = new WebexTokenExtractor(undefined, debugLog)
152
+ const extractor = new WebexTokenExtractor(undefined, debugLog, undefined, options.browserProfile)
148
153
 
149
154
  if (options.debug) {
150
155
  debug('[debug] Searching browser profiles for Webex tokens...')
@@ -283,6 +288,12 @@ export const authCommand = new Command('auth')
283
288
  .description('Extract Webex token from browser (Chrome, Edge, Arc, Brave)')
284
289
  .option('--pretty', 'Pretty print JSON output')
285
290
  .option('--debug', 'Show debug output')
291
+ .option(
292
+ '--browser-profile <path>',
293
+ 'Additional Chromium profile/user-data directory to scan (repeatable, comma-separated supported)',
294
+ collectBrowserProfileOption,
295
+ [],
296
+ )
286
297
  .action(extractAction),
287
298
  )
288
299
  .addCommand(
@@ -4,6 +4,8 @@ import { join } from 'node:path'
4
4
 
5
5
  import { ClassicLevel } from 'classic-level'
6
6
 
7
+ import { getAgentBrowserProfileDirs } from '@/shared/chromium'
8
+
7
9
  export interface ExtractedWebexToken {
8
10
  accessToken: string
9
11
  refreshToken?: string
@@ -74,11 +76,18 @@ export class WebexTokenExtractor {
74
76
  private platform: NodeJS.Platform
75
77
  private baseDir: string | null
76
78
  private debugLog: ((message: string) => void) | null
77
-
78
- constructor(platform?: NodeJS.Platform, debugLog?: (message: string) => void, baseDir?: string) {
79
+ private customBrowserProfileDirs: string[]
80
+
81
+ constructor(
82
+ platform?: NodeJS.Platform,
83
+ debugLog?: (message: string) => void,
84
+ baseDir?: string,
85
+ customBrowserProfileDirs?: string[],
86
+ ) {
79
87
  this.platform = platform ?? process.platform
80
88
  this.debugLog = debugLog ?? null
81
89
  this.baseDir = baseDir ?? null
90
+ this.customBrowserProfileDirs = customBrowserProfileDirs ?? []
82
91
  }
83
92
 
84
93
  private debug(message: string): void {
@@ -87,7 +96,7 @@ export class WebexTokenExtractor {
87
96
 
88
97
  getBrowserProfileDirs(): string[] {
89
98
  if (this.baseDir) {
90
- return this.discoverProfileDirs(this.baseDir)
99
+ return [...this.discoverProfileDirs(this.baseDir), ...this.getCustomBrowserProfileLevelDBDirs()]
91
100
  }
92
101
 
93
102
  const dirs: string[] = []
@@ -100,6 +109,19 @@ export class WebexTokenExtractor {
100
109
  dirs.push(...profileDirs)
101
110
  }
102
111
 
112
+ dirs.push(...this.getCustomBrowserProfileLevelDBDirs())
113
+
114
+ return dirs
115
+ }
116
+
117
+ private getCustomBrowserProfileLevelDBDirs(): string[] {
118
+ const dirs: string[] = []
119
+ for (const profileDir of getAgentBrowserProfileDirs({ customProfileDirs: this.customBrowserProfileDirs })) {
120
+ const leveldb = join(profileDir, 'Local Storage', 'leveldb')
121
+ if (existsSync(leveldb)) {
122
+ dirs.push(leveldb)
123
+ }
124
+ }
103
125
  return dirs
104
126
  }
105
127
 
@@ -13,6 +13,15 @@ program
13
13
  .version(pkg.version)
14
14
  .option('--pretty', 'Pretty-print JSON output')
15
15
  .option('--account <id>', 'Account ID to use')
16
+ .hook('preAction', (thisCmd, actionCmd) => {
17
+ for (const [key, value] of Object.entries(thisCmd.opts())) {
18
+ if (value === undefined) continue
19
+ const source = actionCmd.getOptionValueSource(key)
20
+ if (source === undefined || source === 'default') {
21
+ actionCmd.setOptionValue(key, value)
22
+ }
23
+ }
24
+ })
16
25
 
17
26
  program.addCommand(authCommand)
18
27
  program.addCommand(whoamiCommand)
@@ -1,5 +1,6 @@
1
1
  import { Command } from 'commander'
2
2
 
3
+ import { cliOutput } from '@/shared/utils/cli-output'
3
4
  import { formatOutput } from '@/shared/utils/output'
4
5
 
5
6
  import { WeChatBotClient } from '../client'
@@ -139,11 +140,6 @@ export async function removeAction(accountId: string, options: ActionOptions): P
139
140
  }
140
141
  }
141
142
 
142
- function cliOutput(result: ActionResult, pretty?: boolean, exitOnError = true): void {
143
- console.log(formatOutput(result, pretty))
144
- if (result.error && exitOnError) process.exit(1)
145
- }
146
-
147
143
  export const authCommand = new Command('auth')
148
144
  .description('Authentication commands')
149
145
  .addCommand(
@@ -1,6 +1,6 @@
1
1
  import { Command } from 'commander'
2
2
 
3
- import { formatOutput } from '@/shared/utils/output'
3
+ import { cliOutput } from '@/shared/utils/cli-output'
4
4
 
5
5
  import type { WeChatBotNewsArticle } from '../types'
6
6
  import type { AccountOption } from './shared'
@@ -63,11 +63,6 @@ export async function sendNewsAction(openId: string, options: MessageOptions): P
63
63
  }
64
64
  }
65
65
 
66
- function cliOutput(result: MessageResult, pretty?: boolean): void {
67
- console.log(formatOutput(result, pretty))
68
- if (result.error) process.exit(1)
69
- }
70
-
71
66
  export const messageCommand = new Command('message')
72
67
  .description('Message commands')
73
68
  .addCommand(
@@ -1,6 +1,6 @@
1
1
  import { Command } from 'commander'
2
2
 
3
- import { formatOutput } from '@/shared/utils/output'
3
+ import { cliOutput } from '@/shared/utils/cli-output'
4
4
 
5
5
  import type { WeChatBotTemplate } from '../types'
6
6
  import type { AccountOption } from './shared'
@@ -61,11 +61,6 @@ export async function deleteAction(templateId: string, options: TemplateOptions)
61
61
  }
62
62
  }
63
63
 
64
- function cliOutput(result: TemplateResult, pretty?: boolean): void {
65
- console.log(formatOutput(result, pretty))
66
- if (result.error) process.exit(1)
67
- }
68
-
69
64
  export const templateCommand = new Command('template')
70
65
  .description('Template message commands')
71
66
  .addCommand(
@@ -1,6 +1,6 @@
1
1
  import { Command } from 'commander'
2
2
 
3
- import { formatOutput } from '@/shared/utils/output'
3
+ import { cliOutput } from '@/shared/utils/cli-output'
4
4
 
5
5
  import type { WeChatBotUserInfo } from '../types'
6
6
  import type { AccountOption } from './shared'
@@ -45,11 +45,6 @@ export async function getAction(openId: string, options: UserOptions): Promise<U
45
45
  }
46
46
  }
47
47
 
48
- function cliOutput(result: UserResult, pretty?: boolean): void {
49
- console.log(formatOutput(result, pretty))
50
- if (result.error) process.exit(1)
51
- }
52
-
53
48
  export const userCommand = new Command('user')
54
49
  .description('User management commands')
55
50
  .addCommand(
@@ -1,6 +1,6 @@
1
1
  import { Command } from 'commander'
2
2
 
3
- import { formatOutput } from '@/shared/utils/output'
3
+ import { cliOutput } from '@/shared/utils/cli-output'
4
4
 
5
5
  import { WeChatBotCredentialManager } from '../credential-manager'
6
6
  import type { AccountOption } from './shared'
@@ -29,11 +29,6 @@ export async function whoamiAction(options: AccountOption): Promise<WhoamiResult
29
29
  }
30
30
  }
31
31
 
32
- function cliOutput(result: WhoamiResult, pretty?: boolean): void {
33
- console.log(formatOutput(result, pretty))
34
- if (result.error) process.exit(1)
35
- }
36
-
37
32
  export const whoamiCommand = new Command('whoami')
38
33
  .description('Show current authenticated bot')
39
34
  .option('--account <id>', 'Account ID to use')
@@ -13,6 +13,15 @@ program
13
13
  .version(pkg.version)
14
14
  .option('--pretty', 'Pretty-print JSON output')
15
15
  .option('--account <id>', 'Account ID to use')
16
+ .hook('preAction', (thisCmd, actionCmd) => {
17
+ for (const [key, value] of Object.entries(thisCmd.opts())) {
18
+ if (value === undefined) continue
19
+ const source = actionCmd.getOptionValueSource(key)
20
+ if (source === undefined || source === 'default') {
21
+ actionCmd.setOptionValue(key, value)
22
+ }
23
+ }
24
+ })
16
25
 
17
26
  program.addCommand(authCommand)
18
27
  program.addCommand(whoamiCommand)
@@ -1,5 +1,6 @@
1
1
  import { Command } from 'commander'
2
2
 
3
+ import { cliOutput } from '@/shared/utils/cli-output'
3
4
  import { formatOutput } from '@/shared/utils/output'
4
5
 
5
6
  import { WhatsAppBotClient } from '../client'
@@ -145,11 +146,6 @@ export async function removeAction(accountId: string, options: ActionOptions): P
145
146
  }
146
147
  }
147
148
 
148
- function cliOutput(result: ActionResult, pretty?: boolean, exitOnError = true): void {
149
- console.log(formatOutput(result, pretty))
150
- if (result.error && exitOnError) process.exit(1)
151
- }
152
-
153
149
  export const authCommand = new Command('auth')
154
150
  .description('Authentication commands')
155
151
  .addCommand(
@@ -1,6 +1,6 @@
1
1
  import { Command } from 'commander'
2
2
 
3
- import { formatOutput } from '@/shared/utils/output'
3
+ import { cliOutput } from '@/shared/utils/cli-output'
4
4
 
5
5
  import type { AccountOption } from './shared'
6
6
  import { getClient } from './shared'
@@ -91,11 +91,6 @@ export async function sendDocumentAction(
91
91
  }
92
92
  }
93
93
 
94
- function cliOutput(result: MessageResult, pretty?: boolean): void {
95
- console.log(formatOutput(result, pretty))
96
- if (result.error) process.exit(1)
97
- }
98
-
99
94
  export const messageCommand = new Command('message')
100
95
  .description('Message commands')
101
96
  .addCommand(
@@ -1,6 +1,6 @@
1
1
  import { Command } from 'commander'
2
2
 
3
- import { formatOutput } from '@/shared/utils/output'
3
+ import { cliOutput } from '@/shared/utils/cli-output'
4
4
 
5
5
  import type { WhatsAppBotTemplate } from '../types'
6
6
  import type { AccountOption } from './shared'
@@ -40,11 +40,6 @@ export async function getAction(templateName: string, options: TemplateOptions):
40
40
  }
41
41
  }
42
42
 
43
- function cliOutput(result: TemplateResult, pretty?: boolean): void {
44
- console.log(formatOutput(result, pretty))
45
- if (result.error) process.exit(1)
46
- }
47
-
48
43
  export const templateCommand = new Command('template')
49
44
  .description('Template commands')
50
45
  .addCommand(
@@ -1,6 +1,6 @@
1
1
  import { Command } from 'commander'
2
2
 
3
- import { formatOutput } from '@/shared/utils/output'
3
+ import { cliOutput } from '@/shared/utils/cli-output'
4
4
 
5
5
  import { WhatsAppBotClient } from '../client'
6
6
  import { WhatsAppBotCredentialManager } from '../credential-manager'
@@ -43,11 +43,6 @@ export async function whoamiAction(options: WhoamiOptions): Promise<WhoamiResult
43
43
  }
44
44
  }
45
45
 
46
- function cliOutput(result: WhoamiResult, pretty?: boolean): void {
47
- console.log(formatOutput(result, pretty))
48
- if (result.error) process.exit(1)
49
- }
50
-
51
46
  export const whoamiCommand = new Command('whoami')
52
47
  .description('Show current authenticated bot')
53
48
  .option('--account <id>', 'Account ID to use')
@@ -8,6 +8,7 @@ import {
8
8
  CHROMIUM_BROWSERS,
9
9
  discoverBrowserProfileDirs,
10
10
  findLocalStatePath,
11
+ getAgentBrowserProfileDirs,
11
12
  getBrowserBasePath,
12
13
  } from './browsers'
13
14
 
@@ -195,6 +196,85 @@ describe('browsers', () => {
195
196
  })
196
197
  })
197
198
 
199
+ describe('getAgentBrowserProfileDirs', () => {
200
+ it('includes profile dirs from env and config files', () => {
201
+ // given
202
+ const homeDir = mkdtempSync(join(tmpdir(), 'agent-browser-home-'))
203
+ const projectDir = mkdtempSync(join(tmpdir(), 'agent-browser-project-'))
204
+ const customConfigDir = mkdtempSync(join(tmpdir(), 'agent-browser-custom-'))
205
+ tempDirs.push(homeDir, projectDir, customConfigDir)
206
+
207
+ mkdirSync(join(homeDir, '.agent-browser'))
208
+ writeFileSync(join(homeDir, '.agent-browser', 'config.json'), JSON.stringify({ profile: './global-profile' }))
209
+ writeFileSync(join(projectDir, 'agent-browser.json'), JSON.stringify({ profile: './project-profile' }))
210
+ const customConfigPath = join(customConfigDir, 'custom-agent-browser.json')
211
+ writeFileSync(customConfigPath, JSON.stringify({ profile: './custom-profile' }))
212
+
213
+ // when
214
+ const dirs = getAgentBrowserProfileDirs({
215
+ cwd: projectDir,
216
+ env: {
217
+ AGENT_BROWSER_CONFIG: customConfigPath,
218
+ AGENT_BROWSER_PROFILE: join(projectDir, 'env-profile'),
219
+ },
220
+ homeDir,
221
+ customProfileDirs: ['./cli-profile', join(projectDir, 'second-cli-profile')],
222
+ })
223
+
224
+ // then
225
+ expect(dirs).toEqual([
226
+ join(projectDir, 'cli-profile'),
227
+ join(projectDir, 'cli-profile', 'Default'),
228
+ join(projectDir, 'second-cli-profile'),
229
+ join(projectDir, 'second-cli-profile', 'Default'),
230
+ join(projectDir, 'env-profile'),
231
+ join(projectDir, 'env-profile', 'Default'),
232
+ join(homeDir, '.agent-browser', 'global-profile'),
233
+ join(homeDir, '.agent-browser', 'global-profile', 'Default'),
234
+ join(projectDir, 'project-profile'),
235
+ join(projectDir, 'project-profile', 'Default'),
236
+ join(customConfigDir, 'custom-profile'),
237
+ join(customConfigDir, 'custom-profile', 'Default'),
238
+ ])
239
+ })
240
+
241
+ it('expands existing agent-browser profile bases with numbered Chromium profiles', () => {
242
+ // given
243
+ const homeDir = mkdtempSync(join(tmpdir(), 'agent-browser-expand-home-'))
244
+ const projectDir = mkdtempSync(join(tmpdir(), 'agent-browser-expand-project-'))
245
+ const profileBase = join(projectDir, 'browser-data')
246
+ tempDirs.push(homeDir, projectDir)
247
+ mkdirSync(join(profileBase, 'Profile 1'), { recursive: true })
248
+ mkdirSync(join(profileBase, 'Profile 2'))
249
+ writeFileSync(join(projectDir, 'agent-browser.json'), JSON.stringify({ profile: './browser-data' }))
250
+
251
+ // when
252
+ const dirs = getAgentBrowserProfileDirs({ cwd: projectDir, env: {}, homeDir })
253
+
254
+ // then
255
+ expect(dirs).toEqual([
256
+ profileBase,
257
+ join(profileBase, 'Default'),
258
+ join(profileBase, 'Profile 1'),
259
+ join(profileBase, 'Profile 2'),
260
+ ])
261
+ })
262
+
263
+ it('ignores malformed agent-browser configs', () => {
264
+ // given
265
+ const homeDir = mkdtempSync(join(tmpdir(), 'agent-browser-malformed-home-'))
266
+ const projectDir = mkdtempSync(join(tmpdir(), 'agent-browser-malformed-project-'))
267
+ tempDirs.push(homeDir, projectDir)
268
+ writeFileSync(join(projectDir, 'agent-browser.json'), '{')
269
+
270
+ // when
271
+ const dirs = getAgentBrowserProfileDirs({ cwd: projectDir, env: {}, homeDir })
272
+
273
+ // then
274
+ expect(dirs).toEqual([])
275
+ })
276
+ })
277
+
198
278
  describe('findLocalStatePath', () => {
199
279
  it('returns null when no Local State exists at any level', () => {
200
280
  // given