@jackwener/opencli 1.5.5 → 1.5.7

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 (540) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +31 -4
  3. package/README.zh-CN.md +40 -5
  4. package/SKILL.md +1 -1
  5. package/dist/browser/cdp.d.ts +1 -0
  6. package/dist/browser/cdp.js +30 -27
  7. package/dist/browser/daemon-client.d.ts +11 -1
  8. package/dist/browser/daemon-client.js +3 -0
  9. package/dist/browser/dom-helpers.js +1 -0
  10. package/dist/browser/dom-helpers.test.js +14 -1
  11. package/dist/browser/mcp.js +18 -13
  12. package/dist/browser/page.d.ts +6 -0
  13. package/dist/browser/page.js +37 -2
  14. package/dist/browser/page.test.d.ts +1 -0
  15. package/dist/browser/page.test.js +44 -0
  16. package/dist/browser/stealth.js +198 -0
  17. package/dist/browser/stealth.test.d.ts +1 -0
  18. package/dist/browser/stealth.test.js +134 -0
  19. package/dist/browser.test.js +1 -1
  20. package/dist/build-manifest.d.ts +1 -0
  21. package/dist/build-manifest.js +5 -1
  22. package/dist/build-manifest.test.js +2 -0
  23. package/dist/cli-manifest.json +1821 -252
  24. package/dist/cli.js +20 -3
  25. package/dist/clis/antigravity/serve.d.ts +1 -1
  26. package/dist/clis/antigravity/serve.js +5 -8
  27. package/dist/clis/band/bands.d.ts +1 -0
  28. package/dist/clis/band/bands.js +72 -0
  29. package/dist/clis/band/mentions.d.ts +1 -0
  30. package/dist/clis/band/mentions.js +127 -0
  31. package/dist/clis/band/post.d.ts +1 -0
  32. package/dist/clis/band/post.js +175 -0
  33. package/dist/clis/band/posts.d.ts +1 -0
  34. package/dist/clis/band/posts.js +94 -0
  35. package/dist/clis/bilibili/subtitle.js +4 -0
  36. package/dist/clis/bilibili/subtitle.test.d.ts +1 -0
  37. package/dist/clis/bilibili/subtitle.test.js +48 -0
  38. package/dist/clis/chatwise/ask.js +0 -2
  39. package/dist/clis/chatwise/export.js +0 -2
  40. package/dist/clis/chatwise/history.js +0 -2
  41. package/dist/clis/chatwise/model.js +0 -2
  42. package/dist/clis/chatwise/new.js +1 -2
  43. package/dist/clis/chatwise/read.js +0 -2
  44. package/dist/clis/chatwise/screenshot.js +1 -2
  45. package/dist/clis/chatwise/send.js +0 -2
  46. package/dist/clis/chatwise/status.js +1 -2
  47. package/dist/clis/ctrip/search.d.ts +13 -0
  48. package/dist/clis/ctrip/search.js +73 -48
  49. package/dist/clis/ctrip/search.test.d.ts +1 -0
  50. package/dist/clis/ctrip/search.test.js +64 -0
  51. package/dist/clis/doubao/detail.d.ts +1 -0
  52. package/dist/clis/doubao/detail.js +33 -0
  53. package/dist/clis/doubao/detail.test.d.ts +1 -0
  54. package/dist/clis/doubao/detail.test.js +42 -0
  55. package/dist/clis/doubao/history.d.ts +1 -0
  56. package/dist/clis/doubao/history.js +28 -0
  57. package/dist/clis/doubao/history.test.d.ts +1 -0
  58. package/dist/clis/doubao/history.test.js +37 -0
  59. package/dist/clis/doubao/meeting-summary.d.ts +1 -0
  60. package/dist/clis/doubao/meeting-summary.js +39 -0
  61. package/dist/clis/doubao/meeting-transcript.d.ts +1 -0
  62. package/dist/clis/doubao/meeting-transcript.js +36 -0
  63. package/dist/clis/doubao/utils.d.ts +27 -0
  64. package/dist/clis/doubao/utils.js +317 -0
  65. package/dist/clis/doubao/utils.test.d.ts +1 -0
  66. package/dist/clis/doubao/utils.test.js +24 -0
  67. package/dist/clis/douyin/_shared/public-api.d.ts +33 -0
  68. package/dist/clis/douyin/_shared/public-api.js +29 -0
  69. package/dist/clis/douyin/_shared/sts2.js +8 -2
  70. package/dist/clis/douyin/_shared/sts2.test.d.ts +1 -0
  71. package/dist/clis/douyin/_shared/sts2.test.js +27 -0
  72. package/dist/clis/douyin/activities.js +4 -2
  73. package/dist/clis/douyin/activities.test.js +34 -1
  74. package/dist/clis/douyin/collections.js +1 -1
  75. package/dist/clis/douyin/collections.test.js +24 -2
  76. package/dist/clis/douyin/draft.d.ts +8 -11
  77. package/dist/clis/douyin/draft.js +302 -185
  78. package/dist/clis/douyin/draft.test.d.ts +1 -1
  79. package/dist/clis/douyin/draft.test.js +357 -2
  80. package/dist/clis/douyin/hashtag.js +9 -2
  81. package/dist/clis/douyin/hashtag.test.js +35 -2
  82. package/dist/clis/douyin/profile.js +1 -1
  83. package/dist/clis/douyin/profile.test.js +36 -1
  84. package/dist/clis/douyin/user-videos.d.ts +5 -0
  85. package/dist/clis/douyin/user-videos.js +74 -0
  86. package/dist/clis/douyin/user-videos.test.d.ts +1 -0
  87. package/dist/clis/douyin/user-videos.test.js +108 -0
  88. package/dist/clis/douyin/videos.js +22 -5
  89. package/dist/clis/douyin/videos.test.js +45 -2
  90. package/dist/clis/facebook/search.test.d.ts +5 -0
  91. package/dist/clis/facebook/search.test.js +60 -0
  92. package/dist/clis/facebook/search.yaml +4 -3
  93. package/dist/clis/instagram/download.d.ts +16 -0
  94. package/dist/clis/instagram/download.js +225 -0
  95. package/dist/clis/instagram/download.test.d.ts +1 -0
  96. package/dist/clis/instagram/download.test.js +118 -0
  97. package/dist/clis/notebooklm/bind-current.d.ts +1 -0
  98. package/dist/clis/notebooklm/bind-current.js +29 -0
  99. package/dist/clis/notebooklm/bind-current.test.d.ts +1 -0
  100. package/dist/clis/notebooklm/bind-current.test.js +35 -0
  101. package/dist/clis/notebooklm/binding.test.d.ts +1 -0
  102. package/dist/clis/notebooklm/binding.test.js +44 -0
  103. package/dist/clis/notebooklm/compat.test.d.ts +3 -0
  104. package/dist/clis/notebooklm/compat.test.js +16 -0
  105. package/dist/clis/notebooklm/current.d.ts +1 -0
  106. package/dist/clis/notebooklm/current.js +28 -0
  107. package/dist/clis/notebooklm/get.d.ts +1 -0
  108. package/dist/clis/notebooklm/get.js +37 -0
  109. package/dist/clis/notebooklm/history.d.ts +1 -0
  110. package/dist/clis/notebooklm/history.js +25 -0
  111. package/dist/clis/notebooklm/history.test.d.ts +1 -0
  112. package/dist/clis/notebooklm/history.test.js +58 -0
  113. package/dist/clis/notebooklm/list.d.ts +1 -0
  114. package/dist/clis/notebooklm/list.js +35 -0
  115. package/dist/clis/notebooklm/note-list.d.ts +1 -0
  116. package/dist/clis/notebooklm/note-list.js +28 -0
  117. package/dist/clis/notebooklm/note-list.test.d.ts +1 -0
  118. package/dist/clis/notebooklm/note-list.test.js +56 -0
  119. package/dist/clis/notebooklm/notes-get.d.ts +1 -0
  120. package/dist/clis/notebooklm/notes-get.js +47 -0
  121. package/dist/clis/notebooklm/notes-get.test.d.ts +1 -0
  122. package/dist/clis/notebooklm/notes-get.test.js +72 -0
  123. package/dist/clis/notebooklm/rpc.d.ts +36 -0
  124. package/dist/clis/notebooklm/rpc.js +189 -0
  125. package/dist/clis/notebooklm/rpc.test.d.ts +1 -0
  126. package/dist/clis/notebooklm/rpc.test.js +105 -0
  127. package/dist/clis/notebooklm/shared.d.ts +87 -0
  128. package/dist/clis/notebooklm/shared.js +3 -0
  129. package/dist/clis/notebooklm/source-fulltext.d.ts +1 -0
  130. package/dist/clis/notebooklm/source-fulltext.js +44 -0
  131. package/dist/clis/notebooklm/source-fulltext.test.d.ts +1 -0
  132. package/dist/clis/notebooklm/source-fulltext.test.js +106 -0
  133. package/dist/clis/notebooklm/source-get.d.ts +1 -0
  134. package/dist/clis/notebooklm/source-get.js +40 -0
  135. package/dist/clis/notebooklm/source-get.test.d.ts +1 -0
  136. package/dist/clis/notebooklm/source-get.test.js +84 -0
  137. package/dist/clis/notebooklm/source-guide.d.ts +1 -0
  138. package/dist/clis/notebooklm/source-guide.js +44 -0
  139. package/dist/clis/notebooklm/source-guide.test.d.ts +1 -0
  140. package/dist/clis/notebooklm/source-guide.test.js +104 -0
  141. package/dist/clis/notebooklm/source-list.d.ts +1 -0
  142. package/dist/clis/notebooklm/source-list.js +30 -0
  143. package/dist/clis/notebooklm/status.d.ts +1 -0
  144. package/dist/clis/notebooklm/status.js +31 -0
  145. package/dist/clis/notebooklm/summary.d.ts +1 -0
  146. package/dist/clis/notebooklm/summary.js +30 -0
  147. package/dist/clis/notebooklm/summary.test.d.ts +1 -0
  148. package/dist/clis/notebooklm/summary.test.js +78 -0
  149. package/dist/clis/notebooklm/utils.d.ts +37 -0
  150. package/dist/clis/notebooklm/utils.js +739 -0
  151. package/dist/clis/notebooklm/utils.test.d.ts +1 -0
  152. package/dist/clis/notebooklm/utils.test.js +390 -0
  153. package/dist/clis/ones/common.d.ts +32 -0
  154. package/dist/clis/ones/common.js +144 -0
  155. package/dist/clis/ones/enrich-tasks.d.ts +5 -0
  156. package/dist/clis/ones/enrich-tasks.js +37 -0
  157. package/dist/clis/ones/login.d.ts +1 -0
  158. package/dist/clis/ones/login.js +80 -0
  159. package/dist/clis/ones/logout.d.ts +1 -0
  160. package/dist/clis/ones/logout.js +17 -0
  161. package/dist/clis/ones/me.d.ts +1 -0
  162. package/dist/clis/ones/me.js +30 -0
  163. package/dist/clis/ones/my-tasks.d.ts +1 -0
  164. package/dist/clis/ones/my-tasks.js +120 -0
  165. package/dist/clis/ones/resolve-labels.d.ts +10 -0
  166. package/dist/clis/ones/resolve-labels.js +64 -0
  167. package/dist/clis/ones/task-helpers.d.ts +29 -0
  168. package/dist/clis/ones/task-helpers.js +212 -0
  169. package/dist/clis/ones/task-helpers.test.d.ts +1 -0
  170. package/dist/clis/ones/task-helpers.test.js +12 -0
  171. package/dist/clis/ones/task.d.ts +1 -0
  172. package/dist/clis/ones/task.js +66 -0
  173. package/dist/clis/ones/tasks.d.ts +1 -0
  174. package/dist/clis/ones/tasks.js +79 -0
  175. package/dist/clis/ones/token-info.d.ts +1 -0
  176. package/dist/clis/ones/token-info.js +42 -0
  177. package/dist/clis/ones/worklog.d.ts +11 -0
  178. package/dist/clis/ones/worklog.js +267 -0
  179. package/dist/clis/ones/worklog.test.d.ts +1 -0
  180. package/dist/clis/ones/worklog.test.js +20 -0
  181. package/dist/clis/spotify/spotify.d.ts +1 -0
  182. package/dist/clis/spotify/spotify.js +316 -0
  183. package/dist/clis/spotify/utils.d.ts +21 -0
  184. package/dist/clis/spotify/utils.js +66 -0
  185. package/dist/clis/spotify/utils.test.d.ts +1 -0
  186. package/dist/clis/spotify/utils.test.js +67 -0
  187. package/dist/clis/substack/utils.d.ts +4 -0
  188. package/dist/clis/substack/utils.js +8 -2
  189. package/dist/clis/substack/utils.test.d.ts +1 -0
  190. package/dist/clis/substack/utils.test.js +46 -0
  191. package/dist/clis/tieba/commands.test.d.ts +4 -0
  192. package/dist/clis/tieba/commands.test.js +79 -0
  193. package/dist/clis/tieba/hot.d.ts +1 -0
  194. package/dist/clis/tieba/hot.js +48 -0
  195. package/dist/clis/tieba/posts.d.ts +1 -0
  196. package/dist/clis/tieba/posts.js +85 -0
  197. package/dist/clis/tieba/read.d.ts +1 -0
  198. package/dist/clis/tieba/read.js +140 -0
  199. package/dist/clis/tieba/search.d.ts +1 -0
  200. package/dist/clis/tieba/search.js +108 -0
  201. package/dist/clis/tieba/utils.d.ts +101 -0
  202. package/dist/clis/tieba/utils.js +240 -0
  203. package/dist/clis/tieba/utils.test.d.ts +1 -0
  204. package/dist/clis/tieba/utils.test.js +290 -0
  205. package/dist/clis/v2ex/hot.yaml +4 -1
  206. package/dist/clis/v2ex/latest.yaml +4 -1
  207. package/dist/clis/v2ex/topic.yaml +6 -1
  208. package/dist/clis/weixin/download.d.ts +9 -0
  209. package/dist/clis/weixin/download.js +76 -6
  210. package/dist/clis/weread/book.js +206 -13
  211. package/dist/clis/weread/commands.test.js +331 -0
  212. package/dist/clis/weread/private-api-regression.test.d.ts +1 -0
  213. package/dist/{weread-private-api-regression.test.js → clis/weread/private-api-regression.test.js} +92 -30
  214. package/dist/clis/weread/search-regression.test.d.ts +1 -0
  215. package/dist/clis/weread/search-regression.test.js +407 -0
  216. package/dist/clis/weread/search.js +143 -7
  217. package/dist/clis/weread/shelf.js +13 -95
  218. package/dist/clis/weread/utils.d.ts +56 -0
  219. package/dist/clis/weread/utils.js +234 -7
  220. package/dist/clis/weread/utils.test.js +71 -1
  221. package/dist/clis/xiaohongshu/comments.d.ts +3 -0
  222. package/dist/clis/xiaohongshu/comments.js +76 -17
  223. package/dist/clis/xiaohongshu/comments.test.js +70 -9
  224. package/dist/clis/xiaohongshu/download.d.ts +4 -1
  225. package/dist/clis/xiaohongshu/download.js +83 -22
  226. package/dist/clis/xiaohongshu/download.test.d.ts +1 -0
  227. package/dist/clis/xiaohongshu/download.test.js +75 -0
  228. package/dist/clis/xiaohongshu/note-helpers.d.ts +12 -0
  229. package/dist/clis/xiaohongshu/note-helpers.js +23 -0
  230. package/dist/clis/xiaohongshu/note.d.ts +7 -0
  231. package/dist/clis/xiaohongshu/note.js +76 -0
  232. package/dist/clis/xiaohongshu/note.test.d.ts +1 -0
  233. package/dist/clis/xiaohongshu/note.test.js +136 -0
  234. package/dist/clis/xiaohongshu/publish.d.ts +1 -1
  235. package/dist/clis/xiaohongshu/publish.js +78 -31
  236. package/dist/clis/xiaohongshu/publish.test.js +66 -1
  237. package/dist/clis/xiaohongshu/search.js +9 -0
  238. package/dist/clis/xiaohongshu/search.test.js +10 -4
  239. package/dist/clis/xiaohongshu/user-helpers.d.ts +1 -0
  240. package/dist/clis/xiaohongshu/user-helpers.js +2 -0
  241. package/dist/clis/xiaohongshu/user-helpers.test.js +18 -0
  242. package/dist/clis/xueqiu/comments.d.ts +118 -0
  243. package/dist/clis/xueqiu/comments.js +354 -0
  244. package/dist/clis/xueqiu/comments.test.d.ts +1 -0
  245. package/dist/clis/xueqiu/comments.test.js +696 -0
  246. package/dist/clis/youtube/search.js +57 -17
  247. package/dist/clis/youtube/transcript.js +2 -4
  248. package/dist/clis/youtube/utils.d.ts +9 -0
  249. package/dist/clis/youtube/utils.js +67 -3
  250. package/dist/clis/youtube/utils.test.d.ts +1 -0
  251. package/dist/clis/youtube/utils.test.js +37 -0
  252. package/dist/clis/youtube/video.js +16 -15
  253. package/dist/clis/zhihu/question.js +19 -17
  254. package/dist/clis/zhihu/question.test.d.ts +1 -0
  255. package/dist/clis/zhihu/question.test.js +54 -0
  256. package/dist/clis/zsxq/dynamics.d.ts +1 -0
  257. package/dist/clis/zsxq/dynamics.js +47 -0
  258. package/dist/clis/zsxq/groups.d.ts +1 -0
  259. package/dist/clis/zsxq/groups.js +32 -0
  260. package/dist/clis/zsxq/search.d.ts +1 -0
  261. package/dist/clis/zsxq/search.js +43 -0
  262. package/dist/clis/zsxq/search.test.d.ts +1 -0
  263. package/dist/clis/zsxq/search.test.js +24 -0
  264. package/dist/clis/zsxq/topic.d.ts +1 -0
  265. package/dist/clis/zsxq/topic.js +47 -0
  266. package/dist/clis/zsxq/topic.test.d.ts +1 -0
  267. package/dist/clis/zsxq/topic.test.js +29 -0
  268. package/dist/clis/zsxq/topics.d.ts +1 -0
  269. package/dist/clis/zsxq/topics.js +25 -0
  270. package/dist/clis/zsxq/topics.test.d.ts +1 -0
  271. package/dist/clis/zsxq/topics.test.js +24 -0
  272. package/dist/clis/zsxq/utils.d.ts +97 -0
  273. package/dist/clis/zsxq/utils.js +230 -0
  274. package/dist/commanderAdapter.js +10 -1
  275. package/dist/commanderAdapter.test.js +64 -0
  276. package/dist/commands/daemon.d.ts +9 -0
  277. package/dist/commands/daemon.js +124 -0
  278. package/dist/commands/daemon.test.d.ts +1 -0
  279. package/dist/commands/daemon.test.js +185 -0
  280. package/dist/completion.js +3 -1
  281. package/dist/constants.d.ts +2 -0
  282. package/dist/constants.js +2 -0
  283. package/dist/daemon.d.ts +1 -1
  284. package/dist/daemon.js +25 -14
  285. package/dist/daemon.test.d.ts +1 -0
  286. package/dist/daemon.test.js +65 -0
  287. package/dist/discovery.d.ts +9 -0
  288. package/dist/discovery.js +47 -2
  289. package/dist/electron-apps.d.ts +29 -0
  290. package/dist/electron-apps.js +65 -0
  291. package/dist/electron-apps.test.d.ts +1 -0
  292. package/dist/electron-apps.test.js +43 -0
  293. package/dist/engine.test.js +41 -9
  294. package/dist/execution.js +20 -16
  295. package/dist/external-clis.yaml +17 -0
  296. package/dist/idle-manager.d.ts +19 -0
  297. package/dist/idle-manager.js +54 -0
  298. package/dist/launcher.d.ts +36 -0
  299. package/dist/launcher.js +152 -0
  300. package/dist/launcher.test.d.ts +1 -0
  301. package/dist/launcher.test.js +57 -0
  302. package/dist/main.js +3 -3
  303. package/dist/registry.d.ts +1 -0
  304. package/dist/registry.js +31 -3
  305. package/dist/registry.test.js +13 -0
  306. package/dist/runtime.d.ts +5 -3
  307. package/dist/runtime.js +12 -5
  308. package/dist/serialization.d.ts +1 -0
  309. package/dist/serialization.js +3 -0
  310. package/dist/serialization.test.js +17 -1
  311. package/dist/tui.d.ts +7 -0
  312. package/dist/tui.js +52 -0
  313. package/dist/tui.test.d.ts +1 -0
  314. package/dist/tui.test.js +19 -0
  315. package/dist/types.d.ts +5 -0
  316. package/dist/weixin-download.test.js +14 -0
  317. package/docs/.vitepress/config.mts +4 -0
  318. package/docs/adapters/browser/band.md +63 -0
  319. package/docs/adapters/browser/notebooklm.md +69 -0
  320. package/docs/adapters/browser/ones.md +59 -0
  321. package/docs/adapters/browser/spotify.md +62 -0
  322. package/docs/adapters/browser/tieba.md +45 -0
  323. package/docs/adapters/browser/xiaohongshu.md +19 -10
  324. package/docs/adapters/browser/xueqiu.md +5 -0
  325. package/docs/adapters/browser/zsxq.md +49 -0
  326. package/docs/adapters/index.md +67 -63
  327. package/docs/adapters-doc/ones.md +32 -0
  328. package/docs/guide/browser-bridge.md +12 -0
  329. package/docs/guide/troubleshooting.md +9 -4
  330. package/docs/superpowers/plans/2026-03-31-daemon-lifecycle-redesign.md +857 -0
  331. package/docs/superpowers/specs/2026-03-31-daemon-lifecycle-redesign.md +208 -0
  332. package/docs/zh/guide/browser-bridge.md +12 -0
  333. package/extension/dist/background.js +794 -513
  334. package/extension/src/background.test.ts +202 -2
  335. package/extension/src/background.ts +189 -10
  336. package/extension/src/cdp.ts +54 -0
  337. package/extension/src/protocol.ts +11 -5
  338. package/package.json +1 -1
  339. package/scripts/postinstall.js +16 -0
  340. package/src/browser/cdp.ts +24 -17
  341. package/src/browser/daemon-client.ts +11 -1
  342. package/src/browser/dom-helpers.test.ts +15 -1
  343. package/src/browser/dom-helpers.ts +1 -0
  344. package/src/browser/mcp.ts +18 -13
  345. package/src/browser/page.test.ts +58 -0
  346. package/src/browser/page.ts +34 -2
  347. package/src/browser/stealth.test.ts +153 -0
  348. package/src/browser/stealth.ts +198 -0
  349. package/src/browser.test.ts +1 -1
  350. package/src/build-manifest.test.ts +2 -0
  351. package/src/build-manifest.ts +6 -1
  352. package/src/cli.ts +21 -3
  353. package/src/clis/antigravity/SKILL.md +3 -12
  354. package/src/clis/antigravity/serve.ts +5 -10
  355. package/src/clis/band/bands.ts +76 -0
  356. package/src/clis/band/mentions.ts +134 -0
  357. package/src/clis/band/post.ts +187 -0
  358. package/src/clis/band/posts.ts +106 -0
  359. package/src/clis/bilibili/subtitle.test.ts +60 -0
  360. package/src/clis/bilibili/subtitle.ts +4 -0
  361. package/src/clis/chatwise/ask.ts +0 -2
  362. package/src/clis/chatwise/export.ts +0 -2
  363. package/src/clis/chatwise/history.ts +0 -2
  364. package/src/clis/chatwise/model.ts +0 -2
  365. package/src/clis/chatwise/new.ts +1 -2
  366. package/src/clis/chatwise/read.ts +0 -2
  367. package/src/clis/chatwise/screenshot.ts +1 -2
  368. package/src/clis/chatwise/send.ts +0 -2
  369. package/src/clis/chatwise/status.ts +1 -2
  370. package/src/clis/ctrip/search.test.ts +73 -0
  371. package/src/clis/ctrip/search.ts +97 -47
  372. package/src/clis/doubao/detail.test.ts +53 -0
  373. package/src/clis/doubao/detail.ts +41 -0
  374. package/src/clis/doubao/history.test.ts +45 -0
  375. package/src/clis/doubao/history.ts +32 -0
  376. package/src/clis/doubao/meeting-summary.ts +53 -0
  377. package/src/clis/doubao/meeting-transcript.ts +48 -0
  378. package/src/clis/doubao/utils.test.ts +45 -0
  379. package/src/clis/doubao/utils.ts +371 -0
  380. package/src/clis/douyin/_shared/public-api.ts +84 -0
  381. package/src/clis/douyin/_shared/sts2.test.ts +31 -0
  382. package/src/clis/douyin/_shared/sts2.ts +11 -3
  383. package/src/clis/douyin/activities.test.ts +41 -1
  384. package/src/clis/douyin/activities.ts +12 -3
  385. package/src/clis/douyin/collections.test.ts +35 -2
  386. package/src/clis/douyin/collections.ts +1 -1
  387. package/src/clis/douyin/draft.test.ts +444 -2
  388. package/src/clis/douyin/draft.ts +382 -218
  389. package/src/clis/douyin/hashtag.test.ts +42 -2
  390. package/src/clis/douyin/hashtag.ts +11 -3
  391. package/src/clis/douyin/profile.test.ts +43 -1
  392. package/src/clis/douyin/profile.ts +9 -2
  393. package/src/clis/douyin/user-videos.test.ts +122 -0
  394. package/src/clis/douyin/user-videos.ts +101 -0
  395. package/src/clis/douyin/videos.test.ts +52 -2
  396. package/src/clis/douyin/videos.ts +49 -15
  397. package/src/clis/facebook/search.test.ts +70 -0
  398. package/src/clis/facebook/search.yaml +4 -3
  399. package/src/clis/instagram/download.test.ts +159 -0
  400. package/src/clis/instagram/download.ts +286 -0
  401. package/src/clis/notebooklm/bind-current.test.ts +43 -0
  402. package/src/clis/notebooklm/bind-current.ts +36 -0
  403. package/src/clis/notebooklm/binding.test.ts +53 -0
  404. package/src/clis/notebooklm/compat.test.ts +19 -0
  405. package/src/clis/notebooklm/current.ts +38 -0
  406. package/src/clis/notebooklm/get.ts +53 -0
  407. package/src/clis/notebooklm/history.test.ts +70 -0
  408. package/src/clis/notebooklm/history.ts +36 -0
  409. package/src/clis/notebooklm/list.ts +40 -0
  410. package/src/clis/notebooklm/note-list.test.ts +64 -0
  411. package/src/clis/notebooklm/note-list.ts +42 -0
  412. package/src/clis/notebooklm/notes-get.test.ts +88 -0
  413. package/src/clis/notebooklm/notes-get.ts +67 -0
  414. package/src/clis/notebooklm/rpc.test.ts +126 -0
  415. package/src/clis/notebooklm/rpc.ts +286 -0
  416. package/src/clis/notebooklm/shared.ts +98 -0
  417. package/src/clis/notebooklm/source-fulltext.test.ts +123 -0
  418. package/src/clis/notebooklm/source-fulltext.ts +69 -0
  419. package/src/clis/notebooklm/source-get.test.ts +100 -0
  420. package/src/clis/notebooklm/source-get.ts +60 -0
  421. package/src/clis/notebooklm/source-guide.test.ts +121 -0
  422. package/src/clis/notebooklm/source-guide.ts +69 -0
  423. package/src/clis/notebooklm/source-list.ts +45 -0
  424. package/src/clis/notebooklm/status.ts +34 -0
  425. package/src/clis/notebooklm/summary.test.ts +94 -0
  426. package/src/clis/notebooklm/summary.ts +45 -0
  427. package/src/clis/notebooklm/utils.test.ts +446 -0
  428. package/src/clis/notebooklm/utils.ts +893 -0
  429. package/src/clis/ones/common.ts +187 -0
  430. package/src/clis/ones/enrich-tasks.ts +47 -0
  431. package/src/clis/ones/login.ts +103 -0
  432. package/src/clis/ones/logout.ts +19 -0
  433. package/src/clis/ones/me.ts +34 -0
  434. package/src/clis/ones/my-tasks.ts +148 -0
  435. package/src/clis/ones/resolve-labels.ts +80 -0
  436. package/src/clis/ones/task-helpers.test.ts +14 -0
  437. package/src/clis/ones/task-helpers.ts +214 -0
  438. package/src/clis/ones/task.ts +79 -0
  439. package/src/clis/ones/tasks.ts +92 -0
  440. package/src/clis/ones/token-info.ts +46 -0
  441. package/src/clis/ones/worklog.test.ts +24 -0
  442. package/src/clis/ones/worklog.ts +306 -0
  443. package/src/clis/spotify/spotify.ts +328 -0
  444. package/src/clis/spotify/utils.test.ts +87 -0
  445. package/src/clis/spotify/utils.ts +92 -0
  446. package/src/clis/substack/utils.test.ts +54 -0
  447. package/src/clis/substack/utils.ts +10 -2
  448. package/src/clis/tieba/commands.test.ts +86 -0
  449. package/src/clis/tieba/hot.ts +52 -0
  450. package/src/clis/tieba/posts.ts +108 -0
  451. package/src/clis/tieba/read.ts +158 -0
  452. package/src/clis/tieba/search.ts +119 -0
  453. package/src/clis/tieba/utils.test.ts +322 -0
  454. package/src/clis/tieba/utils.ts +348 -0
  455. package/src/clis/v2ex/hot.yaml +4 -1
  456. package/src/clis/v2ex/latest.yaml +4 -1
  457. package/src/clis/v2ex/topic.yaml +6 -1
  458. package/src/clis/weixin/download.ts +95 -6
  459. package/src/clis/weread/book.ts +256 -13
  460. package/src/clis/weread/commands.test.ts +409 -0
  461. package/src/{weread-private-api-regression.test.ts → clis/weread/private-api-regression.test.ts} +108 -30
  462. package/src/clis/weread/search-regression.test.ts +440 -0
  463. package/src/clis/weread/search.ts +189 -9
  464. package/src/clis/weread/shelf.ts +20 -122
  465. package/src/clis/weread/utils.test.ts +81 -1
  466. package/src/clis/weread/utils.ts +293 -7
  467. package/src/clis/xiaohongshu/comments.test.ts +85 -9
  468. package/src/clis/xiaohongshu/comments.ts +76 -17
  469. package/src/clis/xiaohongshu/download.test.ts +96 -0
  470. package/src/clis/xiaohongshu/download.ts +83 -22
  471. package/src/clis/xiaohongshu/note-helpers.ts +25 -0
  472. package/src/clis/xiaohongshu/note.test.ts +164 -0
  473. package/src/clis/xiaohongshu/note.ts +86 -0
  474. package/src/clis/xiaohongshu/publish.test.ts +79 -1
  475. package/src/clis/xiaohongshu/publish.ts +84 -30
  476. package/src/clis/xiaohongshu/search.test.ts +11 -4
  477. package/src/clis/xiaohongshu/search.ts +13 -0
  478. package/src/clis/xiaohongshu/user-helpers.test.ts +23 -0
  479. package/src/clis/xiaohongshu/user-helpers.ts +4 -0
  480. package/src/clis/xueqiu/comments.test.ts +823 -0
  481. package/src/clis/xueqiu/comments.ts +461 -0
  482. package/src/clis/youtube/search.ts +57 -17
  483. package/src/clis/youtube/transcript.ts +2 -4
  484. package/src/clis/youtube/utils.test.ts +43 -0
  485. package/src/clis/youtube/utils.ts +69 -0
  486. package/src/clis/youtube/video.ts +16 -15
  487. package/src/clis/zhihu/question.test.ts +71 -0
  488. package/src/clis/zhihu/question.ts +27 -15
  489. package/src/clis/zsxq/dynamics.ts +60 -0
  490. package/src/clis/zsxq/groups.ts +41 -0
  491. package/src/clis/zsxq/search.test.ts +29 -0
  492. package/src/clis/zsxq/search.ts +54 -0
  493. package/src/clis/zsxq/topic.test.ts +34 -0
  494. package/src/clis/zsxq/topic.ts +68 -0
  495. package/src/clis/zsxq/topics.test.ts +29 -0
  496. package/src/clis/zsxq/topics.ts +36 -0
  497. package/src/clis/zsxq/utils.ts +351 -0
  498. package/src/commanderAdapter.test.ts +77 -0
  499. package/src/commanderAdapter.ts +8 -1
  500. package/src/commands/daemon.test.ts +238 -0
  501. package/src/commands/daemon.ts +135 -0
  502. package/src/completion.ts +2 -1
  503. package/src/constants.ts +3 -0
  504. package/src/daemon.test.ts +88 -0
  505. package/src/daemon.ts +26 -14
  506. package/src/discovery.ts +52 -2
  507. package/src/electron-apps.test.ts +50 -0
  508. package/src/electron-apps.ts +89 -0
  509. package/src/engine.test.ts +45 -9
  510. package/src/execution.ts +24 -19
  511. package/src/external-clis.yaml +17 -0
  512. package/src/idle-manager.ts +60 -0
  513. package/src/launcher.test.ts +67 -0
  514. package/src/launcher.ts +185 -0
  515. package/src/main.ts +3 -2
  516. package/src/registry.test.ts +15 -0
  517. package/src/registry.ts +32 -3
  518. package/src/runtime.ts +13 -7
  519. package/src/serialization.test.ts +19 -1
  520. package/src/serialization.ts +2 -0
  521. package/src/tui.test.ts +23 -0
  522. package/src/tui.ts +65 -0
  523. package/src/types.ts +5 -0
  524. package/src/weixin-download.test.ts +27 -0
  525. package/tests/e2e/band-auth.test.ts +20 -0
  526. package/tests/e2e/browser-auth-helpers.ts +18 -0
  527. package/tests/e2e/browser-auth.test.ts +35 -47
  528. package/tests/e2e/browser-public-extended.test.ts +6 -2
  529. package/tests/e2e/browser-public.test.ts +288 -0
  530. package/tests/e2e/management.test.ts +1 -1
  531. package/tests/e2e/plugin-management.test.ts +1 -1
  532. package/vitest.config.ts +1 -0
  533. package/chatwise-opencli.ps1 +0 -82
  534. package/dist/clis/chatwise/shared.d.ts +0 -2
  535. package/dist/clis/chatwise/shared.js +0 -6
  536. package/dist/weread-private-api-regression.test.d.ts +0 -1
  537. package/dist/weread-search-regression.test.d.ts +0 -1
  538. package/dist/weread-search-regression.test.js +0 -39
  539. package/src/clis/chatwise/shared.ts +0 -8
  540. package/src/weread-search-regression.test.ts +0 -44
@@ -29,6 +29,7 @@ import {
29
29
  waitForSelectorJs,
30
30
  } from './dom-helpers.js';
31
31
  import { isRecord, saveBase64ToFile } from '../utils.js';
32
+ import { getAllElectronApps } from '../electron-apps.js';
32
33
 
33
34
  export interface CDPTarget {
34
35
  type?: string;
@@ -56,11 +57,11 @@ export class CDPBridge implements IBrowserFactory {
56
57
  private _pending = new Map<number, { resolve: (val: unknown) => void; reject: (err: Error) => void; timer: ReturnType<typeof setTimeout> }>();
57
58
  private _eventListeners = new Map<string, Set<(params: unknown) => void>>();
58
59
 
59
- async connect(opts?: { timeout?: number; workspace?: string }): Promise<IPage> {
60
+ async connect(opts?: { timeout?: number; workspace?: string; cdpEndpoint?: string }): Promise<IPage> {
60
61
  if (this._ws) throw new Error('CDPBridge is already connected. Call close() before reconnecting.');
61
62
 
62
- const endpoint = process.env.OPENCLI_CDP_ENDPOINT;
63
- if (!endpoint) throw new Error('OPENCLI_CDP_ENDPOINT is not set');
63
+ const endpoint = opts?.cdpEndpoint ?? process.env.OPENCLI_CDP_ENDPOINT;
64
+ if (!endpoint) throw new Error('CDP endpoint not provided (pass cdpEndpoint or set OPENCLI_CDP_ENDPOINT)');
64
65
 
65
66
  let wsUrl = endpoint;
66
67
  if (endpoint.startsWith('http')) {
@@ -326,7 +327,17 @@ class CDPPage implements IPage {
326
327
  }
327
328
 
328
329
  async getCurrentUrl(): Promise<string | null> {
329
- return this._lastUrl;
330
+ if (this._lastUrl) return this._lastUrl;
331
+ try {
332
+ const current = await this.evaluate('window.location.href');
333
+ if (typeof current === 'string' && current) {
334
+ this._lastUrl = current;
335
+ return current;
336
+ }
337
+ } catch {
338
+ // Best-effort: direct CDP sessions may not have a ready page yet.
339
+ }
340
+ return null;
330
341
  }
331
342
 
332
343
  async installInterceptor(pattern: string): Promise<void> {
@@ -404,19 +415,15 @@ function scoreCDPTarget(target: CDPTarget, preferredPattern?: RegExp): number {
404
415
  if (url === '' || url === 'about:blank') score -= 40;
405
416
 
406
417
  if (title && title !== 'devtools') score += 25;
407
- if (title.includes('antigravity')) score += 120;
408
- if (title.includes('codex')) score += 120;
409
- if (title.includes('cursor')) score += 120;
410
- if (title.includes('chatwise')) score += 120;
411
- if (title.includes('notion')) score += 120;
412
- if (title.includes('discord')) score += 120;
413
-
414
- if (url.includes('antigravity')) score += 100;
415
- if (url.includes('codex')) score += 100;
416
- if (url.includes('cursor')) score += 100;
417
- if (url.includes('chatwise')) score += 100;
418
- if (url.includes('notion')) score += 100;
419
- if (url.includes('discord')) score += 100;
418
+
419
+ // Boost score for known Electron app names from the registry (builtin + user-defined)
420
+ const appNames = Object.values(getAllElectronApps()).map(a => (a.displayName ?? a.processName).toLowerCase());
421
+ for (const name of appNames) {
422
+ if (title.includes(name)) { score += 120; break; }
423
+ }
424
+ for (const name of appNames) {
425
+ if (url.includes(name)) { score += 100; break; }
426
+ }
420
427
 
421
428
  return score;
422
429
  }
@@ -19,7 +19,7 @@ function generateId(): string {
19
19
 
20
20
  export interface DaemonCommand {
21
21
  id: string;
22
- action: 'exec' | 'navigate' | 'tabs' | 'cookies' | 'screenshot' | 'close-window' | 'sessions';
22
+ action: 'exec' | 'navigate' | 'tabs' | 'cookies' | 'screenshot' | 'close-window' | 'sessions' | 'set-file-input' | 'bind-current';
23
23
  tabId?: number;
24
24
  code?: string;
25
25
  workspace?: string;
@@ -27,9 +27,15 @@ export interface DaemonCommand {
27
27
  op?: string;
28
28
  index?: number;
29
29
  domain?: string;
30
+ matchDomain?: string;
31
+ matchPathPrefix?: string;
30
32
  format?: 'png' | 'jpeg';
31
33
  quality?: number;
32
34
  fullPage?: boolean;
35
+ /** Local file paths for set-file-input action */
36
+ files?: string[];
37
+ /** CSS selector for file input element (set-file-input action) */
38
+ selector?: string;
33
39
  }
34
40
 
35
41
  export interface DaemonResult {
@@ -141,3 +147,7 @@ export async function listSessions(): Promise<BrowserSessionInfo[]> {
141
147
  return Array.isArray(result) ? result : [];
142
148
  }
143
149
 
150
+ export async function bindCurrentTab(workspace: string, opts: { matchDomain?: string; matchPathPrefix?: string } = {}): Promise<unknown> {
151
+ return sendCommand('bind-current', { workspace, ...opts });
152
+ }
153
+
@@ -1,5 +1,19 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { waitForCaptureJs, waitForSelectorJs } from './dom-helpers.js';
2
+ import { autoScrollJs, waitForCaptureJs, waitForSelectorJs } from './dom-helpers.js';
3
+
4
+ describe('autoScrollJs', () => {
5
+ it('returns early without error when document.body is null', async () => {
6
+ const g = globalThis as any;
7
+ const origDoc = g.document;
8
+ g.document = { body: null, documentElement: {} };
9
+ g.window = g;
10
+ const code = autoScrollJs(3, 500);
11
+ // Should resolve without throwing
12
+ await expect(eval(code)).resolves.not.toThrow();
13
+ g.document = origDoc;
14
+ delete g.window;
15
+ });
16
+ });
3
17
 
4
18
  describe('waitForCaptureJs', () => {
5
19
  it('returns a non-empty string', () => {
@@ -109,6 +109,7 @@ export function scrollJs(direction: string, amount: number): string {
109
109
  export function autoScrollJs(times: number, delayMs: number): string {
110
110
  return `
111
111
  (async () => {
112
+ if (!document.body) return;
112
113
  for (let i = 0; i < ${times}; i++) {
113
114
  const lastHeight = document.body.scrollHeight;
114
115
  window.scrollTo(0, lastHeight);
@@ -57,21 +57,30 @@ export class BrowserBridge implements IBrowserFactory {
57
57
  }
58
58
 
59
59
  private async _ensureDaemon(timeoutSeconds?: number): Promise<void> {
60
- // Use default if not provided, zero, or negative
61
60
  const effectiveSeconds = (timeoutSeconds && timeoutSeconds > 0) ? timeoutSeconds : Math.ceil(DAEMON_SPAWN_TIMEOUT / 1000);
62
61
  const timeoutMs = effectiveSeconds * 1000;
63
62
 
63
+ // Fast path: extension already connected
64
64
  if (await isExtensionConnected()) return;
65
+
66
+ // Daemon running but no extension — wait for extension with progress
65
67
  if (await isDaemonRunning()) {
68
+ if (process.env.OPENCLI_VERBOSE || process.stderr.isTTY) {
69
+ process.stderr.write('⏳ Waiting for Chrome extension to connect...\n');
70
+ process.stderr.write(' Make sure Chrome is open and the OpenCLI extension is enabled.\n');
71
+ }
72
+ const deadline = Date.now() + timeoutMs;
73
+ while (Date.now() < deadline) {
74
+ await new Promise(resolve => setTimeout(resolve, 200));
75
+ if (await isExtensionConnected()) return;
76
+ }
66
77
  throw new Error(
67
78
  'Daemon is running but the Browser Extension is not connected.\n' +
68
79
  'Please install and enable the opencli Browser Bridge extension in Chrome.',
69
80
  );
70
81
  }
71
82
 
72
- // Find daemon relative to this file works for both:
73
- // npx tsx src/main.ts → src/browser/mcp.ts → src/daemon.ts
74
- // node dist/main.js → dist/browser/mcp.js → dist/daemon.js
83
+ // No daemon — spawn one
75
84
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
76
85
  const parentDir = path.resolve(__dirname, '..');
77
86
  const daemonTs = path.join(parentDir, 'daemon.ts');
@@ -79,12 +88,10 @@ export class BrowserBridge implements IBrowserFactory {
79
88
  const isTs = fs.existsSync(daemonTs);
80
89
  const daemonPath = isTs ? daemonTs : daemonJs;
81
90
 
82
- if (process.env.OPENCLI_VERBOSE) {
83
- console.error(`[opencli] Starting daemon (${isTs ? 'ts' : 'js'})...`);
91
+ if (process.env.OPENCLI_VERBOSE || process.stderr.isTTY) {
92
+ process.stderr.write('⏳ Starting daemon...\n');
84
93
  }
85
94
 
86
- // For compiled .js, use the current node binary directly (fast).
87
- // For .ts dev mode, node can't run .ts files — use tsx via --import.
88
95
  const spawnArgs = isTs
89
96
  ? [process.execPath, '--import', 'tsx/esm', daemonPath]
90
97
  : [process.execPath, daemonPath];
@@ -96,15 +103,13 @@ export class BrowserBridge implements IBrowserFactory {
96
103
  });
97
104
  this._daemonProc.unref();
98
105
 
99
- // Wait for daemon to be ready AND extension to connect (exponential backoff)
100
- const backoffs = [50, 100, 200, 400, 800, 1500, 3000];
106
+ // Wait for daemon + extension with faster polling
101
107
  const deadline = Date.now() + timeoutMs;
102
- for (let i = 0; Date.now() < deadline; i++) {
103
- await new Promise(resolve => setTimeout(resolve, backoffs[Math.min(i, backoffs.length - 1)]));
108
+ while (Date.now() < deadline) {
109
+ await new Promise(resolve => setTimeout(resolve, 200));
104
110
  if (await isExtensionConnected()) return;
105
111
  }
106
112
 
107
- // Daemon might be up but extension not connected — give a useful error
108
113
  if (await isDaemonRunning()) {
109
114
  throw new Error(
110
115
  'Daemon is running but the Browser Extension is not connected.\n' +
@@ -0,0 +1,58 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ const { sendCommandMock } = vi.hoisted(() => ({
4
+ sendCommandMock: vi.fn(),
5
+ }));
6
+
7
+ vi.mock('./daemon-client.js', () => ({
8
+ sendCommand: sendCommandMock,
9
+ }));
10
+
11
+ import { Page } from './page.js';
12
+
13
+ describe('Page.getCurrentUrl', () => {
14
+ beforeEach(() => {
15
+ sendCommandMock.mockReset();
16
+ });
17
+
18
+ it('reads the real browser URL when no local navigation cache exists', async () => {
19
+ sendCommandMock.mockResolvedValueOnce('https://notebooklm.google.com/notebook/nb-live');
20
+
21
+ const page = new Page('site:notebooklm');
22
+ const url = await page.getCurrentUrl();
23
+
24
+ expect(url).toBe('https://notebooklm.google.com/notebook/nb-live');
25
+ expect(sendCommandMock).toHaveBeenCalledTimes(1);
26
+ expect(sendCommandMock).toHaveBeenCalledWith('exec', expect.objectContaining({
27
+ workspace: 'site:notebooklm',
28
+ }));
29
+ });
30
+
31
+ it('caches the discovered browser URL for later reads', async () => {
32
+ sendCommandMock.mockResolvedValueOnce('https://notebooklm.google.com/notebook/nb-live');
33
+
34
+ const page = new Page('site:notebooklm');
35
+ expect(await page.getCurrentUrl()).toBe('https://notebooklm.google.com/notebook/nb-live');
36
+ expect(await page.getCurrentUrl()).toBe('https://notebooklm.google.com/notebook/nb-live');
37
+
38
+ expect(sendCommandMock).toHaveBeenCalledTimes(1);
39
+ });
40
+ });
41
+
42
+ describe('Page.evaluate', () => {
43
+ beforeEach(() => {
44
+ sendCommandMock.mockReset();
45
+ });
46
+
47
+ it('retries once when the inspected target navigated during exec', async () => {
48
+ sendCommandMock
49
+ .mockRejectedValueOnce(new Error('{"code":-32000,"message":"Inspected target navigated or closed"}'))
50
+ .mockResolvedValueOnce(42);
51
+
52
+ const page = new Page('site:notebooklm');
53
+ const value = await page.evaluate('21 + 21');
54
+
55
+ expect(value).toBe(42);
56
+ expect(sendCommandMock).toHaveBeenCalledTimes(2);
57
+ });
58
+ });
@@ -108,7 +108,17 @@ export class Page implements IPage {
108
108
  }
109
109
 
110
110
  async getCurrentUrl(): Promise<string | null> {
111
- return this._lastUrl;
111
+ if (this._lastUrl) return this._lastUrl;
112
+ try {
113
+ const current = await this.evaluate('window.location.href');
114
+ if (typeof current === 'string' && current) {
115
+ this._lastUrl = current;
116
+ return current;
117
+ }
118
+ } catch {
119
+ // Best-effort: some commands may run before a debuggable tab is ready.
120
+ }
121
+ return null;
112
122
  }
113
123
 
114
124
  /** Close the automation window in the extension */
@@ -122,7 +132,13 @@ export class Page implements IPage {
122
132
 
123
133
  async evaluate(js: string): Promise<unknown> {
124
134
  const code = wrapForEval(js);
125
- return sendCommand('exec', { code, ...this._cmdOpts() });
135
+ try {
136
+ return await sendCommand('exec', { code, ...this._cmdOpts() });
137
+ } catch (err) {
138
+ if (!isRetryableSettleError(err)) throw err;
139
+ await new Promise((resolve) => setTimeout(resolve, 200));
140
+ return sendCommand('exec', { code, ...this._cmdOpts() });
141
+ }
126
142
  }
127
143
 
128
144
  async getCookies(opts: { domain?: string; url?: string } = {}): Promise<BrowserCookie[]> {
@@ -339,6 +355,22 @@ export class Page implements IPage {
339
355
  return Array.isArray(result) ? result : [];
340
356
  }
341
357
 
358
+ /**
359
+ * Set local file paths on a file input element via CDP DOM.setFileInputFiles.
360
+ * Chrome reads the files directly from the local filesystem, avoiding the
361
+ * payload size limits of base64-in-evaluate.
362
+ */
363
+ async setFileInput(files: string[], selector?: string): Promise<void> {
364
+ const result = await sendCommand('set-file-input', {
365
+ files,
366
+ selector,
367
+ ...this._cmdOpts(),
368
+ }) as { count?: number };
369
+ if (!result?.count) {
370
+ throw new Error('setFileInput returned no count — command may not be supported by the extension');
371
+ }
372
+ }
373
+
342
374
  async waitForCapture(timeout: number = 10): Promise<void> {
343
375
  const maxMs = timeout * 1000;
344
376
  await sendCommand('exec', {
@@ -0,0 +1,153 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { generateStealthJs } from './stealth.js';
3
+
4
+ /**
5
+ * Tests for the stealth anti-detection module.
6
+ *
7
+ * We test the generated JS string for expected content and structure.
8
+ * Evaluating in Node is fragile because stealth patches target browser
9
+ * globals (navigator, Performance, HTMLIFrameElement) that don't exist
10
+ * or behave differently in Node. Instead we verify the code string
11
+ * contains the right patches and is syntactically valid.
12
+ */
13
+
14
+ describe('generateStealthJs', () => {
15
+ it('returns a non-empty string', () => {
16
+ const code = generateStealthJs();
17
+ expect(typeof code).toBe('string');
18
+ expect(code.length).toBeGreaterThan(0);
19
+ });
20
+
21
+ it('is a valid self-contained IIFE', () => {
22
+ const code = generateStealthJs();
23
+ // Should start/end as an IIFE
24
+ expect(code.trim()).toMatch(/^\(\(\) => \{/);
25
+ expect(code.trim()).toMatch(/\}\)\(\)$/);
26
+ });
27
+
28
+ it('patches navigator.webdriver', () => {
29
+ const code = generateStealthJs();
30
+ expect(code).toContain("navigator, 'webdriver'");
31
+ expect(code).toContain('() => false');
32
+ });
33
+
34
+ it('stubs window.chrome', () => {
35
+ const code = generateStealthJs();
36
+ expect(code).toContain('window.chrome');
37
+ expect(code).toContain('runtime');
38
+ expect(code).toContain('loadTimes');
39
+ expect(code).toContain('csi');
40
+ });
41
+
42
+ it('fakes navigator.plugins if empty', () => {
43
+ const code = generateStealthJs();
44
+ expect(code).toContain('navigator.plugins');
45
+ expect(code).toContain('PDF Viewer');
46
+ expect(code).toContain('Chrome PDF Viewer');
47
+ });
48
+
49
+ it('ensures navigator.languages is non-empty', () => {
50
+ const code = generateStealthJs();
51
+ expect(code).toContain('navigator.languages');
52
+ expect(code).toContain("'en-US'");
53
+ });
54
+
55
+ it('normalizes Permissions.query for notifications', () => {
56
+ const code = generateStealthJs();
57
+ expect(code).toContain('Permissions');
58
+ expect(code).toContain('notifications');
59
+ });
60
+
61
+ it('cleans automation artifacts', () => {
62
+ const code = generateStealthJs();
63
+ expect(code).toContain('__playwright');
64
+ expect(code).toContain('__puppeteer');
65
+ expect(code).toContain("'cdc_'");
66
+ expect(code).toContain("'__cdc_'");
67
+ });
68
+
69
+ it('filters CDP patterns from Error.stack', () => {
70
+ const code = generateStealthJs();
71
+ expect(code).toContain('puppeteer_evaluation_script');
72
+ expect(code).toContain("'pptr:'");
73
+ expect(code).toContain("'debugger://'");
74
+ });
75
+
76
+ it('neutralizes debugger statement traps', () => {
77
+ const code = generateStealthJs();
78
+ // Should patch Function constructor with new.target / Reflect.construct
79
+ expect(code).toContain('_OrigFunction');
80
+ expect(code).toContain('_PatchedFunction');
81
+ expect(code).toContain('new.target');
82
+ expect(code).toContain('Reflect.construct');
83
+ // Should patch eval
84
+ expect(code).toContain('_origEval');
85
+ expect(code).toContain('_patchedEval');
86
+ // Regex to strip debugger (lookbehind for statement boundaries)
87
+ expect(code).toContain('_debuggerRe');
88
+ });
89
+
90
+ it('uses shared toString disguise via WeakMap', () => {
91
+ const code = generateStealthJs();
92
+ // Shared infrastructure at the top of the IIFE
93
+ expect(code).toContain('_origToString');
94
+ expect(code).toContain('WeakMap');
95
+ expect(code).toContain('_disguised');
96
+ expect(code).toContain('_disguise');
97
+ // Should NOT have per-instance toString overrides on Function/eval
98
+ // (they go through _disguise instead)
99
+ });
100
+
101
+ it('defends console method fingerprinting', () => {
102
+ const code = generateStealthJs();
103
+ expect(code).toContain('_consoleMethods');
104
+ expect(code).toContain("'log'");
105
+ expect(code).toContain("'warn'");
106
+ expect(code).toContain("'error'");
107
+ expect(code).toContain('[native code]');
108
+ // Uses saved _origToString reference
109
+ expect(code).toContain('_origToString.call');
110
+ });
111
+
112
+ it('defends window dimension detection', () => {
113
+ const code = generateStealthJs();
114
+ expect(code).toContain('outerWidth');
115
+ expect(code).toContain('outerHeight');
116
+ expect(code).toContain('innerWidth');
117
+ expect(code).toContain('innerHeight');
118
+ });
119
+
120
+ it('filters Performance API entries', () => {
121
+ const code = generateStealthJs();
122
+ expect(code).toContain('getEntries');
123
+ expect(code).toContain('getEntriesByType');
124
+ expect(code).toContain('getEntriesByName');
125
+ expect(code).toContain('_suspiciousPatterns');
126
+ });
127
+
128
+ it('cleans document $cdc_ properties', () => {
129
+ const code = generateStealthJs();
130
+ expect(code).toContain("'$cdc_'");
131
+ expect(code).toContain("'$chrome_'");
132
+ });
133
+
134
+ it('patches iframe contentWindow.chrome consistency', () => {
135
+ const code = generateStealthJs();
136
+ expect(code).toContain('contentWindow');
137
+ expect(code).toContain('HTMLIFrameElement');
138
+ });
139
+
140
+ it('uses non-enumerable guard flag on EventTarget.prototype', () => {
141
+ const code = generateStealthJs();
142
+ expect(code).toContain('EventTarget.prototype');
143
+ expect(code).toContain("'__lsn'");
144
+ expect(code).toContain('enumerable: false');
145
+ });
146
+
147
+ it('generates syntactically valid JavaScript', () => {
148
+ const code = generateStealthJs();
149
+ // new Function() parses the code without executing it in a real
150
+ // browser context, catching syntax errors from template literal issues.
151
+ expect(() => new Function(code)).not.toThrow();
152
+ });
153
+ });
@@ -150,6 +150,204 @@ export function generateStealthJs(): string {
150
150
  }
151
151
  } catch {}
152
152
 
153
+ // ── Shared toString disguise infrastructure ──
154
+ // Save the pristine Function.prototype.toString BEFORE any patches,
155
+ // so all subsequent disguises use the real native reference.
156
+ // Anti-bot scripts detect per-instance toString overrides via:
157
+ // Function.hasOwnProperty('toString') → true if patched
158
+ // Function.prototype.toString.call(fn) !== fn.toString()
159
+ // Instead we patch Function.prototype.toString once with a WeakMap
160
+ // lookup, making disguised functions indistinguishable from native.
161
+ const _origToString = Function.prototype.toString;
162
+ const _disguised = new WeakMap();
163
+ try {
164
+ Object.defineProperty(Function.prototype, 'toString', {
165
+ value: function() {
166
+ const override = _disguised.get(this);
167
+ return override !== undefined ? override : _origToString.call(this);
168
+ },
169
+ writable: true, configurable: true,
170
+ });
171
+ } catch {}
172
+ const _disguise = (fn, name) => {
173
+ _disguised.set(fn, 'function ' + name + '() { [native code] }');
174
+ try { Object.defineProperty(fn, 'name', { value: name, configurable: true }); } catch {}
175
+ return fn;
176
+ };
177
+
178
+ // 8. Anti-debugger statement trap
179
+ // Sites inject debugger statements to detect DevTools/CDP.
180
+ // When a CDP debugger is attached, the statement pauses execution
181
+ // and the site measures the time gap to confirm automation.
182
+ // We neutralize this by overriding the Function constructor and
183
+ // eval to strip debugger statements from dynamically created code.
184
+ // Note: this does NOT affect static debugger statements in parsed
185
+ // scripts — those require CDP Debugger.setBreakpointsActive(false)
186
+ // which we handle at the extension level.
187
+ // Caveat: the regex targets standalone debugger statements (preceded
188
+ // by a statement boundary) to minimise false positives inside string
189
+ // literals, but cannot perfectly distinguish all cases without a
190
+ // full parser. This is an acceptable trade-off for stealth code.
191
+ try {
192
+ const _OrigFunction = Function;
193
+ // Match standalone debugger statements preceded by a statement
194
+ // boundary (start of string, semicolon, brace, or newline).
195
+ // This avoids most false positives inside string literals like
196
+ // "use debugger mode" while still catching the anti-bot patterns.
197
+ const _debuggerRe = /(?:^|(?<=[;{}\\n\\r]))\\s*debugger\\s*;?/g;
198
+ const _cleanDebugger = (src) => typeof src === 'string' ? src.replace(_debuggerRe, '') : src;
199
+ // Patch Function constructor to strip debugger from dynamic code.
200
+ // Support both Function('code') and new Function('code') via
201
+ // new.target / Reflect.construct.
202
+ const _PatchedFunction = function(...args) {
203
+ if (args.length > 0) {
204
+ args[args.length - 1] = _cleanDebugger(args[args.length - 1]);
205
+ }
206
+ if (new.target) {
207
+ return Reflect.construct(_OrigFunction, args, new.target);
208
+ }
209
+ return _OrigFunction.apply(this, args);
210
+ };
211
+ _PatchedFunction.prototype = _OrigFunction.prototype;
212
+ Object.setPrototypeOf(_PatchedFunction, _OrigFunction);
213
+ _disguise(_PatchedFunction, 'Function');
214
+ try { window.Function = _PatchedFunction; } catch {}
215
+
216
+ // Patch eval to strip debugger
217
+ const _origEval = window.eval;
218
+ const _patchedEval = function(code) {
219
+ return _origEval.call(this, _cleanDebugger(code));
220
+ };
221
+ _disguise(_patchedEval, 'eval');
222
+ try { window.eval = _patchedEval; } catch {}
223
+ } catch {}
224
+
225
+ // 9. Console method fingerprinting defense
226
+ // When CDP Runtime.enable is called, Chrome replaces console.log etc.
227
+ // with CDP-bound versions. These bound functions have a different
228
+ // toString() output: "function log() { [native code] }" becomes
229
+ // something like "function () { [native code] }" (no name) or the
230
+ // bound function signature leaks. Anti-bot scripts check:
231
+ // console.log.toString().includes('[native code]')
232
+ // console.log.name === 'log'
233
+ // We re-wrap console methods and register them via the shared
234
+ // _disguise infrastructure so Function.prototype.toString.call()
235
+ // also returns the correct native string.
236
+ try {
237
+ const _consoleMethods = ['log', 'warn', 'error', 'info', 'debug', 'table', 'trace', 'dir', 'group', 'groupEnd', 'groupCollapsed', 'clear', 'count', 'assert', 'profile', 'profileEnd', 'time', 'timeEnd', 'timeStamp'];
238
+ for (const _m of _consoleMethods) {
239
+ if (typeof console[_m] !== 'function') continue;
240
+ const _origMethod = console[_m];
241
+ const _nativeStr = 'function ' + _m + '() { [native code] }';
242
+ // Only patch if toString is wrong (i.e. CDP has replaced it)
243
+ try {
244
+ const _currentStr = _origToString.call(_origMethod);
245
+ if (_currentStr === _nativeStr) continue; // already looks native
246
+ } catch {}
247
+ const _wrapper = function() { return _origMethod.apply(console, arguments); };
248
+ Object.defineProperty(_wrapper, 'length', { value: _origMethod.length || 0, configurable: true });
249
+ _disguise(_wrapper, _m);
250
+ try { console[_m] = _wrapper; } catch {}
251
+ }
252
+ } catch {}
253
+
254
+ // 10. window.outerWidth/outerHeight defense
255
+ // When DevTools or CDP debugger is attached, Chrome may alter the
256
+ // window dimensions. Anti-bot scripts compare outerWidth/innerWidth
257
+ // and outerHeight/innerHeight — a significant difference indicates
258
+ // DevTools is open. We freeze the relationship so the delta stays
259
+ // consistent with a normal browser window.
260
+ // Thresholds: width delta > 100px or height delta > 200px indicates
261
+ // a docked DevTools panel. When triggered, we report outerWidth
262
+ // equal to innerWidth (normal for maximised windows) and
263
+ // outerHeight as innerHeight + the captured "normal" delta (capped
264
+ // to a reasonable range), so the result is plausible across OSes.
265
+ try {
266
+ const _normalWidthDelta = window.outerWidth - window.innerWidth;
267
+ const _normalHeightDelta = window.outerHeight - window.innerHeight;
268
+ // Only patch if the delta looks suspicious (e.g. DevTools docked)
269
+ if (_normalWidthDelta > 100 || _normalHeightDelta > 200) {
270
+ Object.defineProperty(window, 'outerWidth', {
271
+ get: () => window.innerWidth,
272
+ configurable: true,
273
+ });
274
+ // Use a clamped height offset (40-120px covers macOS ~78px,
275
+ // Windows ~40px, and Linux ~37-50px title bar heights).
276
+ const _heightOffset = Math.max(40, Math.min(120, _normalHeightDelta));
277
+ Object.defineProperty(window, 'outerHeight', {
278
+ get: () => window.innerHeight + _heightOffset,
279
+ configurable: true,
280
+ });
281
+ }
282
+ } catch {}
283
+
284
+ // 11. Performance API cleanup
285
+ // CDP injects internal resources and timing entries that don't exist
286
+ // in normal browsing. Filter entries with debugger/devtools URLs.
287
+ try {
288
+ const _origGetEntries = Performance.prototype.getEntries;
289
+ const _origGetByType = Performance.prototype.getEntriesByType;
290
+ const _origGetByName = Performance.prototype.getEntriesByName;
291
+ const _suspiciousPatterns = ['debugger', 'devtools', '__puppeteer', '__playwright', 'pptr:'];
292
+ const _filterEntries = (entries) => {
293
+ if (!Array.isArray(entries)) return entries;
294
+ return entries.filter(e => {
295
+ const name = e.name || '';
296
+ return !_suspiciousPatterns.some(p => name.includes(p));
297
+ });
298
+ };
299
+ Performance.prototype.getEntries = function() {
300
+ return _filterEntries(_origGetEntries.call(this));
301
+ };
302
+ Performance.prototype.getEntriesByType = function(type) {
303
+ return _filterEntries(_origGetByType.call(this, type));
304
+ };
305
+ Performance.prototype.getEntriesByName = function(name, type) {
306
+ return _filterEntries(_origGetByName.call(this, name, type));
307
+ };
308
+ } catch {}
309
+
310
+ // 12. WebDriver-related property defense
311
+ // Some anti-bot systems check additional navigator properties
312
+ // and document properties that may indicate automation.
313
+ try {
314
+ // document.$cdc_ properties (ChromeDriver specific, backup for #6)
315
+ for (const _prop of Object.getOwnPropertyNames(document)) {
316
+ if (_prop.startsWith('$cdc_') || _prop.startsWith('$chrome_')) {
317
+ try { delete document[_prop]; } catch {}
318
+ }
319
+ }
320
+ } catch {}
321
+
322
+ // 13. Iframe contentWindow.chrome consistency
323
+ // Anti-bot scripts create iframes and check if
324
+ // iframe.contentWindow.chrome exists and matches the parent.
325
+ // CDP-controlled pages may have inconsistent iframe contexts.
326
+ try {
327
+ const _origHTMLIFrame = HTMLIFrameElement.prototype;
328
+ const _origContentWindow = Object.getOwnPropertyDescriptor(_origHTMLIFrame, 'contentWindow');
329
+ if (_origContentWindow && _origContentWindow.get) {
330
+ Object.defineProperty(_origHTMLIFrame, 'contentWindow', {
331
+ get: function() {
332
+ const _w = _origContentWindow.get.call(this);
333
+ if (_w) {
334
+ try {
335
+ if (!_w.chrome) {
336
+ Object.defineProperty(_w, 'chrome', {
337
+ value: window.chrome,
338
+ writable: true,
339
+ configurable: true,
340
+ });
341
+ }
342
+ } catch {}
343
+ }
344
+ return _w;
345
+ },
346
+ configurable: true,
347
+ });
348
+ }
349
+ } catch {}
350
+
153
351
  return 'applied';
154
352
  })()
155
353
  `;