@jackwener/opencli 1.3.3 → 1.4.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 (680) hide show
  1. package/.github/actions/setup-chrome/action.yml +5 -4
  2. package/.github/pull_request_template.md +3 -1
  3. package/.github/workflows/build-extension.yml +7 -1
  4. package/.github/workflows/ci.yml +46 -6
  5. package/.github/workflows/docs.yml +1 -1
  6. package/.github/workflows/e2e-headed.yml +36 -3
  7. package/.github/workflows/release.yml +1 -1
  8. package/.github/workflows/security.yml +0 -3
  9. package/CHANGELOG.md +78 -0
  10. package/CONTRIBUTING.md +6 -3
  11. package/PRIVACY.md +57 -0
  12. package/README.md +31 -4
  13. package/README.zh-CN.md +31 -4
  14. package/SKILL.md +107 -2
  15. package/TESTING.md +1 -0
  16. package/chatwise-opencli.ps1 +82 -0
  17. package/dist/analysis.d.ts +38 -0
  18. package/dist/analysis.js +166 -0
  19. package/dist/browser/cdp.d.ts +0 -4
  20. package/dist/browser/cdp.js +53 -41
  21. package/dist/browser/cdp.test.d.ts +1 -0
  22. package/dist/browser/cdp.test.js +52 -0
  23. package/dist/browser/dom-snapshot.d.ts +2 -2
  24. package/dist/browser/dom-snapshot.js +54 -1
  25. package/dist/browser/dom-snapshot.test.js +36 -0
  26. package/dist/browser/index.d.ts +2 -2
  27. package/dist/browser/index.js +1 -1
  28. package/dist/browser/mcp.d.ts +0 -2
  29. package/dist/browser/mcp.js +2 -3
  30. package/dist/browser/page.d.ts +4 -3
  31. package/dist/browser/page.js +34 -37
  32. package/dist/browser/stealth.d.ts +0 -2
  33. package/dist/browser/stealth.js +24 -9
  34. package/dist/browser.test.js +2 -2
  35. package/dist/build-manifest.js +15 -9
  36. package/dist/build-manifest.test.js +12 -0
  37. package/dist/cascade.js +4 -2
  38. package/dist/cli-manifest.json +1325 -256
  39. package/dist/cli.js +57 -29
  40. package/dist/clis/_shared/desktop-commands.d.ts +22 -0
  41. package/dist/clis/_shared/desktop-commands.js +108 -0
  42. package/dist/clis/antigravity/serve.js +5 -2
  43. package/dist/clis/apple-podcasts/search.js +2 -1
  44. package/dist/clis/arxiv/search.js +3 -3
  45. package/dist/clis/bbc/news.js +0 -1
  46. package/dist/clis/bilibili/dynamic.test.d.ts +1 -0
  47. package/dist/clis/bilibili/dynamic.test.js +68 -0
  48. package/dist/clis/bilibili/favorite.js +4 -2
  49. package/dist/clis/bilibili/following.js +3 -2
  50. package/dist/clis/bilibili/subtitle.js +8 -7
  51. package/dist/clis/bilibili/utils.js +2 -2
  52. package/dist/clis/boss/batchgreet.js +1 -1
  53. package/dist/clis/boss/chatlist.js +1 -1
  54. package/dist/clis/boss/chatmsg.js +1 -1
  55. package/dist/clis/boss/detail.js +1 -1
  56. package/dist/clis/boss/exchange.js +1 -1
  57. package/dist/clis/boss/greet.js +1 -1
  58. package/dist/clis/boss/invite.js +1 -1
  59. package/dist/clis/boss/joblist.js +1 -1
  60. package/dist/clis/boss/mark.js +4 -3
  61. package/dist/clis/boss/recommend.js +1 -1
  62. package/dist/clis/boss/resume.js +1 -1
  63. package/dist/clis/boss/search.js +1 -1
  64. package/dist/clis/boss/send.js +5 -4
  65. package/dist/clis/boss/stats.js +1 -1
  66. package/dist/clis/chatgpt/ask.js +4 -0
  67. package/dist/clis/chatgpt/new.js +5 -1
  68. package/dist/clis/chatgpt/read.js +5 -1
  69. package/dist/clis/chatgpt/send.js +2 -1
  70. package/dist/clis/chatgpt/status.js +5 -1
  71. package/dist/clis/chatwise/ask.js +8 -2
  72. package/dist/clis/chatwise/export.js +2 -0
  73. package/dist/clis/chatwise/history.js +2 -0
  74. package/dist/clis/chatwise/model.js +8 -3
  75. package/dist/clis/chatwise/new.js +3 -18
  76. package/dist/clis/chatwise/read.js +2 -0
  77. package/dist/clis/chatwise/screenshot.js +3 -27
  78. package/dist/clis/chatwise/send.js +8 -2
  79. package/dist/clis/chatwise/shared.d.ts +2 -0
  80. package/dist/clis/chatwise/shared.js +6 -0
  81. package/dist/clis/chatwise/status.js +3 -22
  82. package/dist/clis/codex/ask.js +6 -2
  83. package/dist/clis/codex/dump.js +2 -25
  84. package/dist/clis/codex/new.js +2 -25
  85. package/dist/clis/codex/screenshot.js +2 -27
  86. package/dist/clis/codex/send.js +6 -4
  87. package/dist/clis/codex/status.js +2 -22
  88. package/dist/clis/ctrip/search.js +0 -1
  89. package/dist/clis/cursor/ask.js +2 -1
  90. package/dist/clis/cursor/composer.js +2 -1
  91. package/dist/clis/cursor/dump.js +2 -25
  92. package/dist/clis/cursor/new.js +2 -18
  93. package/dist/clis/cursor/read.js +2 -1
  94. package/dist/clis/cursor/screenshot.js +1 -30
  95. package/dist/clis/cursor/send.js +2 -1
  96. package/dist/clis/cursor/status.js +2 -21
  97. package/dist/clis/dictionary/examples.yaml +25 -0
  98. package/dist/clis/dictionary/search.yaml +27 -0
  99. package/dist/clis/dictionary/synonyms.yaml +25 -0
  100. package/dist/clis/douban/book-hot.js +1 -1
  101. package/dist/clis/douban/movie-hot.js +1 -1
  102. package/dist/clis/douban/search.js +1 -1
  103. package/dist/clis/douban/utils.d.ts +4 -1
  104. package/dist/clis/douban/utils.js +156 -1
  105. package/dist/clis/doubao/ask.js +1 -1
  106. package/dist/clis/doubao/new.js +1 -1
  107. package/dist/clis/doubao/read.js +1 -1
  108. package/dist/clis/doubao/send.js +1 -1
  109. package/dist/clis/doubao/status.js +1 -1
  110. package/dist/clis/doubao-app/ask.js +1 -1
  111. package/dist/clis/doubao-app/new.js +1 -1
  112. package/dist/clis/doubao-app/read.js +1 -1
  113. package/dist/clis/doubao-app/send.js +1 -1
  114. package/dist/clis/douyin/_shared/browser-fetch.d.ts +10 -0
  115. package/dist/clis/douyin/_shared/browser-fetch.js +30 -0
  116. package/dist/clis/douyin/_shared/browser-fetch.test.d.ts +1 -0
  117. package/dist/clis/douyin/_shared/browser-fetch.test.js +31 -0
  118. package/dist/clis/douyin/_shared/creation-id.d.ts +1 -0
  119. package/dist/clis/douyin/_shared/creation-id.js +5 -0
  120. package/dist/clis/douyin/_shared/creation-id.test.d.ts +1 -0
  121. package/dist/clis/douyin/_shared/creation-id.test.js +22 -0
  122. package/dist/clis/douyin/_shared/imagex-upload.d.ts +20 -0
  123. package/dist/clis/douyin/_shared/imagex-upload.js +53 -0
  124. package/dist/clis/douyin/_shared/imagex-upload.test.d.ts +1 -0
  125. package/dist/clis/douyin/_shared/imagex-upload.test.js +87 -0
  126. package/dist/clis/douyin/_shared/sts2.d.ts +8 -0
  127. package/dist/clis/douyin/_shared/sts2.js +15 -0
  128. package/dist/clis/douyin/_shared/text-extra.d.ts +18 -0
  129. package/dist/clis/douyin/_shared/text-extra.js +15 -0
  130. package/dist/clis/douyin/_shared/text-extra.test.d.ts +1 -0
  131. package/dist/clis/douyin/_shared/text-extra.test.js +37 -0
  132. package/dist/clis/douyin/_shared/timing.d.ts +2 -0
  133. package/dist/clis/douyin/_shared/timing.js +22 -0
  134. package/dist/clis/douyin/_shared/timing.test.d.ts +1 -0
  135. package/dist/clis/douyin/_shared/timing.test.js +28 -0
  136. package/dist/clis/douyin/_shared/tos-upload-short-read.test.d.ts +11 -0
  137. package/dist/clis/douyin/_shared/tos-upload-short-read.test.js +83 -0
  138. package/dist/clis/douyin/_shared/tos-upload.d.ts +53 -0
  139. package/dist/clis/douyin/_shared/tos-upload.js +295 -0
  140. package/dist/clis/douyin/_shared/tos-upload.test.d.ts +1 -0
  141. package/dist/clis/douyin/_shared/tos-upload.test.js +229 -0
  142. package/dist/clis/douyin/_shared/transcode.d.ts +27 -0
  143. package/dist/clis/douyin/_shared/transcode.js +45 -0
  144. package/dist/clis/douyin/_shared/transcode.test.d.ts +1 -0
  145. package/dist/clis/douyin/_shared/transcode.test.js +93 -0
  146. package/dist/clis/douyin/_shared/types.d.ts +26 -0
  147. package/dist/clis/douyin/_shared/types.js +1 -0
  148. package/dist/clis/douyin/activities.d.ts +1 -0
  149. package/dist/clis/douyin/activities.js +20 -0
  150. package/dist/clis/douyin/activities.test.d.ts +1 -0
  151. package/dist/clis/douyin/activities.test.js +22 -0
  152. package/dist/clis/douyin/collections.d.ts +1 -0
  153. package/dist/clis/douyin/collections.js +22 -0
  154. package/dist/clis/douyin/collections.test.d.ts +1 -0
  155. package/dist/clis/douyin/collections.test.js +23 -0
  156. package/dist/clis/douyin/delete.d.ts +1 -0
  157. package/dist/clis/douyin/delete.js +18 -0
  158. package/dist/clis/douyin/delete.test.d.ts +1 -0
  159. package/dist/clis/douyin/delete.test.js +11 -0
  160. package/dist/clis/douyin/draft.d.ts +14 -0
  161. package/dist/clis/douyin/draft.js +237 -0
  162. package/dist/clis/douyin/draft.test.d.ts +1 -0
  163. package/dist/clis/douyin/draft.test.js +11 -0
  164. package/dist/clis/douyin/drafts.d.ts +1 -0
  165. package/dist/clis/douyin/drafts.js +23 -0
  166. package/dist/clis/douyin/drafts.test.d.ts +1 -0
  167. package/dist/clis/douyin/drafts.test.js +11 -0
  168. package/dist/clis/douyin/hashtag.d.ts +1 -0
  169. package/dist/clis/douyin/hashtag.js +45 -0
  170. package/dist/clis/douyin/hashtag.test.d.ts +1 -0
  171. package/dist/clis/douyin/hashtag.test.js +25 -0
  172. package/dist/clis/douyin/location.d.ts +1 -0
  173. package/dist/clis/douyin/location.js +24 -0
  174. package/dist/clis/douyin/location.test.d.ts +1 -0
  175. package/dist/clis/douyin/location.test.js +23 -0
  176. package/dist/clis/douyin/profile.d.ts +1 -0
  177. package/dist/clis/douyin/profile.js +28 -0
  178. package/dist/clis/douyin/profile.test.d.ts +1 -0
  179. package/dist/clis/douyin/profile.test.js +11 -0
  180. package/dist/clis/douyin/publish.d.ts +14 -0
  181. package/dist/clis/douyin/publish.js +288 -0
  182. package/dist/clis/douyin/publish.test.d.ts +1 -0
  183. package/dist/clis/douyin/publish.test.js +38 -0
  184. package/dist/clis/douyin/stats.d.ts +1 -0
  185. package/dist/clis/douyin/stats.js +27 -0
  186. package/dist/clis/douyin/stats.test.d.ts +1 -0
  187. package/dist/clis/douyin/stats.test.js +22 -0
  188. package/dist/clis/douyin/update.d.ts +1 -0
  189. package/dist/clis/douyin/update.js +31 -0
  190. package/dist/clis/douyin/update.test.d.ts +1 -0
  191. package/dist/clis/douyin/update.test.js +11 -0
  192. package/dist/clis/douyin/videos.d.ts +1 -0
  193. package/dist/clis/douyin/videos.js +34 -0
  194. package/dist/clis/douyin/videos.test.d.ts +1 -0
  195. package/dist/clis/douyin/videos.test.js +11 -0
  196. package/dist/clis/grok/ask.d.ts +4 -0
  197. package/dist/clis/grok/ask.js +28 -10
  198. package/dist/clis/grok/ask.test.js +18 -0
  199. package/dist/clis/hackernews/search.yaml +1 -1
  200. package/dist/clis/instagram/search.yaml +2 -1
  201. package/dist/clis/jd/item.d.ts +1 -0
  202. package/dist/clis/jd/item.js +96 -0
  203. package/dist/clis/jd/item.test.d.ts +1 -0
  204. package/dist/clis/jd/item.test.js +28 -0
  205. package/dist/clis/jike/feed.js +1 -1
  206. package/dist/clis/jike/search.js +1 -1
  207. package/dist/clis/linkedin/search.js +5 -4
  208. package/dist/clis/linkedin/timeline.d.ts +21 -0
  209. package/dist/clis/linkedin/timeline.js +503 -0
  210. package/dist/clis/linkedin/timeline.test.d.ts +1 -0
  211. package/dist/clis/linkedin/timeline.test.js +81 -0
  212. package/dist/clis/linux-do/search.yaml +3 -1
  213. package/dist/clis/medium/feed.js +1 -1
  214. package/dist/clis/medium/search.js +2 -2
  215. package/dist/clis/medium/user.js +1 -1
  216. package/dist/clis/medium/{shared.js → utils.js} +2 -1
  217. package/dist/clis/pixiv/detail.yaml +49 -0
  218. package/dist/clis/pixiv/download.d.ts +7 -0
  219. package/dist/clis/pixiv/download.js +78 -0
  220. package/dist/clis/pixiv/download.test.d.ts +1 -0
  221. package/dist/clis/pixiv/download.test.js +87 -0
  222. package/dist/clis/pixiv/illusts.d.ts +8 -0
  223. package/dist/clis/pixiv/illusts.js +65 -0
  224. package/dist/clis/pixiv/illusts.test.d.ts +1 -0
  225. package/dist/clis/pixiv/illusts.test.js +99 -0
  226. package/dist/clis/pixiv/ranking.yaml +53 -0
  227. package/dist/clis/pixiv/search.d.ts +6 -0
  228. package/dist/clis/pixiv/search.js +43 -0
  229. package/dist/clis/pixiv/search.test.d.ts +1 -0
  230. package/dist/clis/pixiv/search.test.js +83 -0
  231. package/dist/clis/pixiv/test-utils.d.ts +12 -0
  232. package/dist/clis/pixiv/test-utils.js +23 -0
  233. package/dist/clis/pixiv/user.yaml +46 -0
  234. package/dist/clis/pixiv/utils.d.ts +27 -0
  235. package/dist/clis/pixiv/utils.js +49 -0
  236. package/dist/clis/reddit/comment.js +2 -1
  237. package/dist/clis/reddit/read.js +4 -3
  238. package/dist/clis/reddit/read.test.d.ts +1 -0
  239. package/dist/clis/reddit/read.test.js +28 -0
  240. package/dist/clis/reddit/save.js +2 -1
  241. package/dist/clis/reddit/saved.js +7 -3
  242. package/dist/clis/reddit/subscribe.js +2 -1
  243. package/dist/clis/reddit/upvote.js +2 -1
  244. package/dist/clis/reddit/upvoted.js +7 -3
  245. package/dist/clis/reuters/search.js +0 -1
  246. package/dist/clis/sinablog/article.js +1 -1
  247. package/dist/clis/sinablog/hot.js +1 -1
  248. package/dist/clis/sinablog/user.js +1 -1
  249. package/dist/clis/substack/feed.js +1 -1
  250. package/dist/clis/substack/publication.js +1 -1
  251. package/dist/clis/substack/search.js +3 -2
  252. package/dist/clis/substack/{shared.js → utils.js} +3 -2
  253. package/dist/clis/tiktok/search.yaml +2 -1
  254. package/dist/clis/twitter/accept.js +2 -1
  255. package/dist/clis/twitter/article.js +4 -1
  256. package/dist/clis/twitter/block.js +2 -1
  257. package/dist/clis/twitter/bookmark.js +2 -1
  258. package/dist/clis/twitter/bookmarks.js +3 -2
  259. package/dist/clis/twitter/delete.js +2 -1
  260. package/dist/clis/twitter/follow.js +2 -1
  261. package/dist/clis/twitter/followers.js +3 -2
  262. package/dist/clis/twitter/following.js +3 -2
  263. package/dist/clis/twitter/hide-reply.js +2 -1
  264. package/dist/clis/twitter/like.js +2 -1
  265. package/dist/clis/twitter/notifications.js +2 -1
  266. package/dist/clis/twitter/post.js +2 -1
  267. package/dist/clis/twitter/profile.js +5 -2
  268. package/dist/clis/twitter/reply-dm.js +2 -1
  269. package/dist/clis/twitter/reply.js +2 -1
  270. package/dist/clis/twitter/search.js +32 -13
  271. package/dist/clis/twitter/search.test.d.ts +1 -0
  272. package/dist/clis/twitter/search.test.js +156 -0
  273. package/dist/clis/twitter/thread.js +2 -2
  274. package/dist/clis/twitter/timeline.js +3 -2
  275. package/dist/clis/twitter/trending.js +3 -2
  276. package/dist/clis/twitter/unblock.js +2 -1
  277. package/dist/clis/twitter/unbookmark.js +2 -1
  278. package/dist/clis/twitter/unfollow.js +2 -1
  279. package/dist/clis/v2ex/daily.js +3 -2
  280. package/dist/clis/v2ex/me.js +3 -2
  281. package/dist/clis/v2ex/notifications.js +4 -4
  282. package/dist/clis/web/read.d.ts +16 -0
  283. package/dist/clis/web/read.js +202 -0
  284. package/dist/clis/weibo/comments.d.ts +1 -0
  285. package/dist/clis/weibo/comments.js +53 -0
  286. package/dist/clis/weibo/feed.d.ts +1 -0
  287. package/dist/clis/weibo/feed.js +56 -0
  288. package/dist/clis/weibo/hot.js +0 -1
  289. package/dist/clis/weibo/me.d.ts +1 -0
  290. package/dist/clis/weibo/me.js +76 -0
  291. package/dist/clis/weibo/post.d.ts +1 -0
  292. package/dist/clis/weibo/post.js +75 -0
  293. package/dist/clis/weibo/user.d.ts +1 -0
  294. package/dist/clis/weibo/user.js +63 -0
  295. package/dist/clis/weibo/utils.d.ts +6 -0
  296. package/dist/clis/weibo/utils.js +30 -0
  297. package/dist/clis/weread/search.js +3 -2
  298. package/dist/clis/xueqiu/danjuan-utils.d.ts +55 -0
  299. package/dist/clis/xueqiu/danjuan-utils.js +126 -0
  300. package/dist/clis/xueqiu/danjuan-utils.test.d.ts +1 -0
  301. package/dist/clis/xueqiu/danjuan-utils.test.js +41 -0
  302. package/dist/clis/xueqiu/fund-holdings.d.ts +1 -0
  303. package/dist/clis/xueqiu/fund-holdings.js +28 -0
  304. package/dist/clis/xueqiu/fund-snapshot.d.ts +1 -0
  305. package/dist/clis/xueqiu/fund-snapshot.js +25 -0
  306. package/dist/clis/xueqiu/search.yaml +2 -1
  307. package/dist/clis/yahoo-finance/quote.js +0 -1
  308. package/dist/clis/youtube/channel.d.ts +1 -0
  309. package/dist/clis/youtube/channel.js +150 -0
  310. package/dist/clis/youtube/comments.d.ts +1 -0
  311. package/dist/clis/youtube/comments.js +95 -0
  312. package/dist/clis/youtube/search.js +0 -1
  313. package/dist/clis/youtube/transcript.js +5 -4
  314. package/dist/clis/youtube/video.js +3 -2
  315. package/dist/clis/zhihu/search.yaml +2 -1
  316. package/dist/daemon.js +7 -3
  317. package/dist/discovery.js +11 -10
  318. package/dist/doctor.js +2 -1
  319. package/dist/download/index.d.ts +4 -12
  320. package/dist/download/index.js +33 -12
  321. package/dist/download/index.test.js +79 -2
  322. package/dist/download/media-download.js +4 -2
  323. package/dist/engine.test.js +76 -4
  324. package/dist/execution.d.ts +1 -9
  325. package/dist/execution.js +56 -46
  326. package/dist/explore.js +12 -111
  327. package/dist/external-clis.yaml +0 -25
  328. package/dist/external.js +7 -5
  329. package/dist/external.test.js +4 -0
  330. package/dist/generate.d.ts +0 -9
  331. package/dist/generate.js +4 -20
  332. package/dist/hooks.d.ts +46 -0
  333. package/dist/hooks.js +56 -0
  334. package/dist/hooks.test.d.ts +4 -0
  335. package/dist/hooks.test.js +92 -0
  336. package/dist/interceptor.js +70 -23
  337. package/dist/main.js +2 -0
  338. package/dist/output.js +12 -6
  339. package/dist/pipeline/executor.js +1 -1
  340. package/dist/pipeline/steps/browser.js +1 -3
  341. package/dist/pipeline/steps/download.js +42 -26
  342. package/dist/pipeline/steps/download.test.d.ts +1 -0
  343. package/dist/pipeline/steps/download.test.js +101 -0
  344. package/dist/pipeline/steps/fetch.js +40 -22
  345. package/dist/pipeline/steps/fetch.test.d.ts +1 -0
  346. package/dist/pipeline/steps/fetch.test.js +123 -0
  347. package/dist/pipeline/steps/transform.js +2 -6
  348. package/dist/pipeline/template.js +66 -52
  349. package/dist/pipeline/template.test.js +28 -0
  350. package/dist/pipeline/transform.test.js +18 -0
  351. package/dist/plugin.d.ts +40 -1
  352. package/dist/plugin.js +214 -17
  353. package/dist/plugin.test.d.ts +1 -1
  354. package/dist/plugin.test.js +219 -3
  355. package/dist/record.js +6 -98
  356. package/dist/registry-api.d.ts +2 -0
  357. package/dist/registry-api.js +1 -0
  358. package/dist/registry.d.ts +5 -2
  359. package/dist/registry.js +1 -2
  360. package/dist/runtime.d.ts +0 -1
  361. package/dist/runtime.js +14 -4
  362. package/dist/snapshotFormatter.d.ts +7 -14
  363. package/dist/snapshotFormatter.js +38 -78
  364. package/dist/utils.d.ts +9 -0
  365. package/dist/utils.js +29 -0
  366. package/dist/validate.js +3 -5
  367. package/dist/weread-search-regression.test.d.ts +1 -0
  368. package/dist/weread-search-regression.test.js +39 -0
  369. package/dist/yaml-schema.d.ts +26 -0
  370. package/dist/yaml-schema.js +5 -0
  371. package/docs/.vitepress/config.mts +16 -0
  372. package/docs/adapters/browser/dictionary.md +27 -0
  373. package/docs/adapters/browser/douyin.md +75 -0
  374. package/docs/adapters/browser/jd.md +27 -0
  375. package/docs/adapters/browser/linkedin.md +6 -0
  376. package/docs/adapters/browser/pixiv.md +92 -0
  377. package/docs/adapters/browser/twitter.md +6 -0
  378. package/docs/adapters/browser/web.md +30 -0
  379. package/docs/adapters/browser/xueqiu.md +27 -9
  380. package/docs/adapters/index.md +9 -2
  381. package/docs/comparison.md +125 -0
  382. package/docs/developer/contributing.md +21 -2
  383. package/docs/developer/testing.md +14 -8
  384. package/docs/developer/ts-adapter.md +18 -0
  385. package/docs/developer/yaml-adapter.md +16 -0
  386. package/docs/guide/plugins.md +10 -0
  387. package/docs/zh/guide/plugins.md +10 -0
  388. package/extension/dist/background.js +100 -35
  389. package/extension/manifest.json +6 -2
  390. package/extension/package.json +1 -1
  391. package/extension/popup.html +84 -0
  392. package/extension/popup.js +25 -0
  393. package/extension/src/background.test.ts +46 -1
  394. package/extension/src/background.ts +128 -34
  395. package/extension/src/cdp.ts +9 -9
  396. package/package.json +3 -2
  397. package/scripts/check-doc-coverage.sh +2 -0
  398. package/src/analysis.ts +170 -0
  399. package/src/browser/cdp.test.ts +66 -0
  400. package/src/browser/cdp.ts +59 -44
  401. package/src/browser/dom-snapshot.test.ts +42 -0
  402. package/src/browser/dom-snapshot.ts +56 -3
  403. package/src/browser/index.ts +2 -2
  404. package/src/browser/mcp.ts +2 -4
  405. package/src/browser/page.ts +34 -37
  406. package/src/browser/stealth.ts +24 -10
  407. package/src/browser.test.ts +2 -2
  408. package/src/build-manifest.test.ts +14 -0
  409. package/src/build-manifest.ts +13 -31
  410. package/src/cascade.ts +5 -3
  411. package/src/cli.ts +66 -34
  412. package/src/clis/_shared/desktop-commands.ts +121 -0
  413. package/src/clis/antigravity/serve.ts +6 -3
  414. package/src/clis/apple-podcasts/search.ts +2 -1
  415. package/src/clis/arxiv/search.ts +3 -3
  416. package/src/clis/bbc/news.ts +0 -1
  417. package/src/clis/bilibili/dynamic.test.ts +79 -0
  418. package/src/clis/bilibili/favorite.ts +5 -2
  419. package/src/clis/bilibili/following.ts +3 -2
  420. package/src/clis/bilibili/subtitle.ts +8 -7
  421. package/src/clis/bilibili/utils.ts +2 -2
  422. package/src/clis/boss/batchgreet.ts +1 -1
  423. package/src/clis/boss/chatlist.ts +1 -1
  424. package/src/clis/boss/chatmsg.ts +1 -1
  425. package/src/clis/boss/detail.ts +1 -1
  426. package/src/clis/boss/exchange.ts +1 -1
  427. package/src/clis/boss/greet.ts +1 -1
  428. package/src/clis/boss/invite.ts +1 -1
  429. package/src/clis/boss/joblist.ts +1 -1
  430. package/src/clis/boss/mark.ts +4 -3
  431. package/src/clis/boss/recommend.ts +1 -1
  432. package/src/clis/boss/resume.ts +1 -1
  433. package/src/clis/boss/search.ts +1 -1
  434. package/src/clis/boss/send.ts +5 -4
  435. package/src/clis/boss/stats.ts +1 -1
  436. package/src/clis/chatgpt/ask.ts +5 -0
  437. package/src/clis/chatgpt/new.ts +7 -2
  438. package/src/clis/chatgpt/read.ts +7 -2
  439. package/src/clis/chatgpt/send.ts +3 -2
  440. package/src/clis/chatgpt/status.ts +6 -1
  441. package/src/clis/chatwise/ask.ts +7 -2
  442. package/src/clis/chatwise/export.ts +2 -0
  443. package/src/clis/chatwise/history.ts +2 -0
  444. package/src/clis/chatwise/model.ts +7 -3
  445. package/src/clis/chatwise/new.ts +3 -20
  446. package/src/clis/chatwise/read.ts +2 -0
  447. package/src/clis/chatwise/screenshot.ts +3 -32
  448. package/src/clis/chatwise/send.ts +7 -2
  449. package/src/clis/chatwise/shared.ts +8 -0
  450. package/src/clis/chatwise/status.ts +3 -24
  451. package/src/clis/codex/ask.ts +5 -2
  452. package/src/clis/codex/dump.ts +2 -27
  453. package/src/clis/codex/new.ts +2 -28
  454. package/src/clis/codex/screenshot.ts +2 -32
  455. package/src/clis/codex/send.ts +5 -4
  456. package/src/clis/codex/status.ts +2 -24
  457. package/src/clis/ctrip/search.ts +0 -1
  458. package/src/clis/cursor/ask.ts +2 -1
  459. package/src/clis/cursor/composer.ts +2 -1
  460. package/src/clis/cursor/dump.ts +2 -27
  461. package/src/clis/cursor/new.ts +2 -20
  462. package/src/clis/cursor/read.ts +2 -1
  463. package/src/clis/cursor/screenshot.ts +1 -36
  464. package/src/clis/cursor/send.ts +2 -1
  465. package/src/clis/cursor/status.ts +2 -22
  466. package/src/clis/dictionary/examples.yaml +25 -0
  467. package/src/clis/dictionary/search.yaml +27 -0
  468. package/src/clis/dictionary/synonyms.yaml +25 -0
  469. package/src/clis/douban/book-hot.ts +1 -1
  470. package/src/clis/douban/movie-hot.ts +1 -1
  471. package/src/clis/douban/search.ts +1 -1
  472. package/src/clis/douban/utils.ts +165 -1
  473. package/src/clis/doubao/ask.ts +1 -1
  474. package/src/clis/doubao/new.ts +1 -1
  475. package/src/clis/doubao/read.ts +1 -1
  476. package/src/clis/doubao/send.ts +1 -1
  477. package/src/clis/doubao/status.ts +1 -1
  478. package/src/clis/doubao-app/ask.ts +1 -1
  479. package/src/clis/doubao-app/new.ts +1 -1
  480. package/src/clis/doubao-app/read.ts +1 -1
  481. package/src/clis/doubao-app/send.ts +1 -1
  482. package/src/clis/douyin/_shared/browser-fetch.test.ts +38 -0
  483. package/src/clis/douyin/_shared/browser-fetch.ts +45 -0
  484. package/src/clis/douyin/_shared/creation-id.test.ts +26 -0
  485. package/src/clis/douyin/_shared/creation-id.ts +8 -0
  486. package/src/clis/douyin/_shared/imagex-upload.test.ts +113 -0
  487. package/src/clis/douyin/_shared/imagex-upload.ts +76 -0
  488. package/src/clis/douyin/_shared/sts2.ts +20 -0
  489. package/src/clis/douyin/_shared/text-extra.test.ts +42 -0
  490. package/src/clis/douyin/_shared/text-extra.ts +33 -0
  491. package/src/clis/douyin/_shared/timing.test.ts +38 -0
  492. package/src/clis/douyin/_shared/timing.ts +22 -0
  493. package/src/clis/douyin/_shared/tos-upload-short-read.test.ts +102 -0
  494. package/src/clis/douyin/_shared/tos-upload.test.ts +281 -0
  495. package/src/clis/douyin/_shared/tos-upload.ts +444 -0
  496. package/src/clis/douyin/_shared/transcode.test.ts +117 -0
  497. package/src/clis/douyin/_shared/transcode.ts +78 -0
  498. package/src/clis/douyin/_shared/types.ts +29 -0
  499. package/src/clis/douyin/activities.test.ts +25 -0
  500. package/src/clis/douyin/activities.ts +23 -0
  501. package/src/clis/douyin/collections.test.ts +26 -0
  502. package/src/clis/douyin/collections.ts +25 -0
  503. package/src/clis/douyin/delete.test.ts +12 -0
  504. package/src/clis/douyin/delete.ts +20 -0
  505. package/src/clis/douyin/draft.test.ts +12 -0
  506. package/src/clis/douyin/draft.ts +282 -0
  507. package/src/clis/douyin/drafts.test.ts +12 -0
  508. package/src/clis/douyin/drafts.ts +27 -0
  509. package/src/clis/douyin/hashtag.test.ts +28 -0
  510. package/src/clis/douyin/hashtag.ts +56 -0
  511. package/src/clis/douyin/location.test.ts +26 -0
  512. package/src/clis/douyin/location.ts +27 -0
  513. package/src/clis/douyin/profile.test.ts +12 -0
  514. package/src/clis/douyin/profile.ts +37 -0
  515. package/src/clis/douyin/publish.test.ts +45 -0
  516. package/src/clis/douyin/publish.ts +340 -0
  517. package/src/clis/douyin/stats.test.ts +25 -0
  518. package/src/clis/douyin/stats.ts +30 -0
  519. package/src/clis/douyin/update.test.ts +12 -0
  520. package/src/clis/douyin/update.ts +43 -0
  521. package/src/clis/douyin/videos.test.ts +12 -0
  522. package/src/clis/douyin/videos.ts +49 -0
  523. package/src/clis/grok/ask.test.ts +25 -0
  524. package/src/clis/grok/ask.ts +25 -12
  525. package/src/clis/hackernews/search.yaml +1 -1
  526. package/src/clis/instagram/search.yaml +2 -1
  527. package/src/clis/jd/item.test.ts +35 -0
  528. package/src/clis/jd/item.ts +101 -0
  529. package/src/clis/jike/feed.ts +1 -1
  530. package/src/clis/jike/search.ts +1 -1
  531. package/src/clis/linkedin/search.ts +5 -4
  532. package/src/clis/linkedin/timeline.test.ts +99 -0
  533. package/src/clis/linkedin/timeline.ts +532 -0
  534. package/src/clis/linux-do/search.yaml +3 -1
  535. package/src/clis/medium/feed.ts +1 -1
  536. package/src/clis/medium/search.ts +2 -2
  537. package/src/clis/medium/user.ts +1 -1
  538. package/src/clis/medium/{shared.ts → utils.ts} +2 -1
  539. package/src/clis/pixiv/detail.yaml +49 -0
  540. package/src/clis/pixiv/download.test.ts +114 -0
  541. package/src/clis/pixiv/download.ts +91 -0
  542. package/src/clis/pixiv/illusts.test.ts +115 -0
  543. package/src/clis/pixiv/illusts.ts +78 -0
  544. package/src/clis/pixiv/ranking.yaml +53 -0
  545. package/src/clis/pixiv/search.test.ts +97 -0
  546. package/src/clis/pixiv/search.ts +53 -0
  547. package/src/clis/pixiv/test-utils.ts +29 -0
  548. package/src/clis/pixiv/user.yaml +46 -0
  549. package/src/clis/pixiv/utils.ts +62 -0
  550. package/src/clis/reddit/comment.ts +2 -1
  551. package/src/clis/reddit/read.test.ts +34 -0
  552. package/src/clis/reddit/read.ts +4 -3
  553. package/src/clis/reddit/save.ts +2 -1
  554. package/src/clis/reddit/saved.ts +6 -2
  555. package/src/clis/reddit/subscribe.ts +2 -1
  556. package/src/clis/reddit/upvote.ts +2 -1
  557. package/src/clis/reddit/upvoted.ts +6 -2
  558. package/src/clis/reuters/search.ts +0 -1
  559. package/src/clis/sinablog/article.ts +1 -1
  560. package/src/clis/sinablog/hot.ts +1 -1
  561. package/src/clis/sinablog/user.ts +1 -1
  562. package/src/clis/substack/feed.ts +1 -1
  563. package/src/clis/substack/publication.ts +1 -1
  564. package/src/clis/substack/search.ts +3 -2
  565. package/src/clis/substack/{shared.ts → utils.ts} +3 -2
  566. package/src/clis/tiktok/search.yaml +2 -1
  567. package/src/clis/twitter/accept.ts +2 -1
  568. package/src/clis/twitter/article.ts +3 -1
  569. package/src/clis/twitter/block.ts +2 -1
  570. package/src/clis/twitter/bookmark.ts +2 -1
  571. package/src/clis/twitter/bookmarks.ts +3 -2
  572. package/src/clis/twitter/delete.ts +2 -1
  573. package/src/clis/twitter/follow.ts +2 -1
  574. package/src/clis/twitter/followers.ts +3 -2
  575. package/src/clis/twitter/following.ts +3 -2
  576. package/src/clis/twitter/hide-reply.ts +2 -1
  577. package/src/clis/twitter/like.ts +2 -1
  578. package/src/clis/twitter/notifications.ts +2 -1
  579. package/src/clis/twitter/post.ts +2 -1
  580. package/src/clis/twitter/profile.ts +4 -2
  581. package/src/clis/twitter/reply-dm.ts +2 -1
  582. package/src/clis/twitter/reply.ts +2 -1
  583. package/src/clis/twitter/search.test.ts +180 -0
  584. package/src/clis/twitter/search.ts +40 -14
  585. package/src/clis/twitter/thread.ts +2 -2
  586. package/src/clis/twitter/timeline.ts +3 -2
  587. package/src/clis/twitter/trending.ts +3 -2
  588. package/src/clis/twitter/unblock.ts +2 -1
  589. package/src/clis/twitter/unbookmark.ts +2 -1
  590. package/src/clis/twitter/unfollow.ts +2 -1
  591. package/src/clis/v2ex/daily.ts +3 -2
  592. package/src/clis/v2ex/me.ts +3 -2
  593. package/src/clis/v2ex/notifications.ts +3 -4
  594. package/src/clis/web/read.ts +210 -0
  595. package/src/clis/weibo/comments.ts +54 -0
  596. package/src/clis/weibo/feed.ts +57 -0
  597. package/src/clis/weibo/hot.ts +0 -1
  598. package/src/clis/weibo/me.ts +77 -0
  599. package/src/clis/weibo/post.ts +77 -0
  600. package/src/clis/weibo/user.ts +64 -0
  601. package/src/clis/weibo/utils.ts +32 -0
  602. package/src/clis/weread/search.ts +3 -2
  603. package/src/clis/xueqiu/danjuan-utils.test.ts +49 -0
  604. package/src/clis/xueqiu/danjuan-utils.ts +176 -0
  605. package/src/clis/xueqiu/fund-holdings.ts +32 -0
  606. package/src/clis/xueqiu/fund-snapshot.ts +27 -0
  607. package/src/clis/xueqiu/search.yaml +2 -1
  608. package/src/clis/yahoo-finance/quote.ts +0 -1
  609. package/src/clis/youtube/channel.ts +155 -0
  610. package/src/clis/youtube/comments.ts +97 -0
  611. package/src/clis/youtube/search.ts +0 -1
  612. package/src/clis/youtube/transcript.ts +5 -4
  613. package/src/clis/youtube/video.ts +3 -2
  614. package/src/clis/zhihu/search.yaml +2 -1
  615. package/src/daemon.ts +5 -4
  616. package/src/discovery.ts +12 -34
  617. package/src/doctor.ts +3 -2
  618. package/src/download/index.test.ts +93 -2
  619. package/src/download/index.ts +44 -23
  620. package/src/download/media-download.ts +5 -3
  621. package/src/engine.test.ts +84 -3
  622. package/src/execution.ts +62 -46
  623. package/src/explore.ts +21 -90
  624. package/src/external-clis.yaml +0 -25
  625. package/src/external.test.ts +9 -0
  626. package/src/external.ts +12 -10
  627. package/src/generate.ts +4 -41
  628. package/src/hooks.test.ts +126 -0
  629. package/src/hooks.ts +90 -0
  630. package/src/interceptor.ts +73 -23
  631. package/src/main.ts +2 -0
  632. package/src/output.ts +14 -6
  633. package/src/pipeline/executor.ts +1 -1
  634. package/src/pipeline/steps/browser.ts +1 -3
  635. package/src/pipeline/steps/download.test.ts +136 -0
  636. package/src/pipeline/steps/download.ts +47 -34
  637. package/src/pipeline/steps/fetch.test.ts +179 -0
  638. package/src/pipeline/steps/fetch.ts +39 -23
  639. package/src/pipeline/steps/transform.ts +2 -6
  640. package/src/pipeline/template.test.ts +28 -0
  641. package/src/pipeline/template.ts +67 -79
  642. package/src/pipeline/transform.test.ts +20 -0
  643. package/src/plugin.test.ts +251 -3
  644. package/src/plugin.ts +265 -21
  645. package/src/record.ts +12 -84
  646. package/src/registry-api.ts +2 -0
  647. package/src/registry.ts +7 -4
  648. package/src/runtime.ts +14 -4
  649. package/src/snapshotFormatter.ts +43 -121
  650. package/src/utils.ts +39 -0
  651. package/src/validate.ts +3 -5
  652. package/src/weread-search-regression.test.ts +44 -0
  653. package/src/yaml-schema.ts +28 -0
  654. package/tests/e2e/browser-auth.test.ts +25 -0
  655. package/tests/e2e/browser-public-extended.test.ts +162 -0
  656. package/tests/e2e/browser-public.test.ts +7 -146
  657. package/tests/e2e/plugin-management.test.ts +137 -0
  658. package/tests/e2e/public-commands.test.ts +34 -1
  659. package/vitest.config.ts +33 -8
  660. package/.github/workflows/pkg-pr-new.yml +0 -30
  661. package/dist/clis/douban/shared.d.ts +0 -4
  662. package/dist/clis/douban/shared.js +0 -155
  663. package/src/clis/douban/shared.ts +0 -165
  664. /package/dist/clis/boss/{common.d.ts → utils.d.ts} +0 -0
  665. /package/dist/clis/boss/{common.js → utils.js} +0 -0
  666. /package/dist/clis/doubao/{common.d.ts → utils.d.ts} +0 -0
  667. /package/dist/clis/doubao/{common.js → utils.js} +0 -0
  668. /package/dist/clis/doubao-app/{common.d.ts → utils.d.ts} +0 -0
  669. /package/dist/clis/doubao-app/{common.js → utils.js} +0 -0
  670. /package/dist/clis/jike/{shared.d.ts → utils.d.ts} +0 -0
  671. /package/dist/clis/jike/{shared.js → utils.js} +0 -0
  672. /package/dist/clis/medium/{shared.d.ts → utils.d.ts} +0 -0
  673. /package/dist/clis/sinablog/{shared.d.ts → utils.d.ts} +0 -0
  674. /package/dist/clis/sinablog/{shared.js → utils.js} +0 -0
  675. /package/dist/clis/substack/{shared.d.ts → utils.d.ts} +0 -0
  676. /package/src/clis/boss/{common.ts → utils.ts} +0 -0
  677. /package/src/clis/doubao/{common.ts → utils.ts} +0 -0
  678. /package/src/clis/doubao-app/{common.ts → utils.ts} +0 -0
  679. /package/src/clis/jike/{shared.ts → utils.ts} +0 -0
  680. /package/src/clis/sinablog/{shared.ts → utils.ts} +0 -0
@@ -74,10 +74,17 @@ function connect(): void {
74
74
  };
75
75
  }
76
76
 
77
+ /**
78
+ * After MAX_EAGER_ATTEMPTS (reaching 60s backoff), stop scheduling reconnects.
79
+ * The keepalive alarm (~24s) will still call connect() periodically, but at a
80
+ * much lower frequency — reducing console noise when the daemon is not running.
81
+ */
82
+ const MAX_EAGER_ATTEMPTS = 6; // 2s, 4s, 8s, 16s, 32s, 60s — then stop
83
+
77
84
  function scheduleReconnect(): void {
78
85
  if (reconnectTimer) return;
79
86
  reconnectAttempts++;
80
- // Exponential backoff: 2s, 4s, 8s, 16s, ..., capped at 60s
87
+ if (reconnectAttempts > MAX_EAGER_ATTEMPTS) return; // let keepalive alarm handle it
81
88
  const delay = Math.min(WS_RECONNECT_BASE_DELAY * Math.pow(2, reconnectAttempts - 1), WS_RECONNECT_MAX_DELAY);
82
89
  reconnectTimer = setTimeout(() => {
83
90
  reconnectTimer = null;
@@ -138,7 +145,7 @@ async function getAutomationWindow(workspace: string): Promise<number> {
138
145
  // Create a new window with a data: URI that New Tab Override extensions cannot intercept.
139
146
  // Using about:blank would be hijacked by extensions like "New Tab Override".
140
147
  const win = await chrome.windows.create({
141
- url: 'data:text/html,<html></html>',
148
+ url: BLANK_PAGE,
142
149
  focused: false,
143
150
  width: 1280,
144
151
  height: 900,
@@ -193,6 +200,18 @@ chrome.alarms.onAlarm.addListener((alarm) => {
193
200
  if (alarm.name === 'keepalive') connect();
194
201
  });
195
202
 
203
+ // ─── Popup status API ───────────────────────────────────────────────
204
+
205
+ chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
206
+ if (msg?.type === 'getStatus') {
207
+ sendResponse({
208
+ connected: ws?.readyState === WebSocket.OPEN,
209
+ reconnecting: reconnectTimer !== null,
210
+ });
211
+ }
212
+ return false;
213
+ });
214
+
196
215
  // ─── Command dispatcher ─────────────────────────────────────────────
197
216
 
198
217
  async function handleCommand(cmd: Command): Promise<Result> {
@@ -229,10 +248,37 @@ async function handleCommand(cmd: Command): Promise<Result> {
229
248
 
230
249
  // ─── Action handlers ─────────────────────────────────────────────────
231
250
 
232
- /** Check if a URL can be attached via CDP (not chrome:// or chrome-extension://) */
251
+ /** Internal blank page used when no user URL is provided. */
252
+ const BLANK_PAGE = 'data:text/html,<html></html>';
253
+
254
+ /** Check if a URL can be attached via CDP — only allow http(s) and our internal blank page. */
233
255
  function isDebuggableUrl(url?: string): boolean {
234
256
  if (!url) return true; // empty/undefined = tab still loading, allow it
235
- return !url.startsWith('chrome://') && !url.startsWith('chrome-extension://');
257
+ return url.startsWith('http://') || url.startsWith('https://') || url === BLANK_PAGE;
258
+ }
259
+
260
+ /** Check if a URL is safe for user-facing navigation (http/https only). */
261
+ function isSafeNavigationUrl(url: string): boolean {
262
+ return url.startsWith('http://') || url.startsWith('https://');
263
+ }
264
+
265
+ /** Minimal URL normalization for same-page comparison: root slash + default port only. */
266
+ function normalizeUrlForComparison(url?: string): string {
267
+ if (!url) return '';
268
+ try {
269
+ const parsed = new URL(url);
270
+ if ((parsed.protocol === 'https:' && parsed.port === '443') || (parsed.protocol === 'http:' && parsed.port === '80')) {
271
+ parsed.port = '';
272
+ }
273
+ const pathname = parsed.pathname === '/' ? '' : parsed.pathname;
274
+ return `${parsed.protocol}//${parsed.host}${pathname}${parsed.search}${parsed.hash}`;
275
+ } catch {
276
+ return url;
277
+ }
278
+ }
279
+
280
+ function isTargetUrl(currentUrl: string | undefined, targetUrl: string): boolean {
281
+ return normalizeUrlForComparison(currentUrl) === normalizeUrlForComparison(targetUrl);
236
282
  }
237
283
 
238
284
  /**
@@ -247,9 +293,14 @@ async function resolveTabId(tabId: number | undefined, workspace: string): Promi
247
293
  if (tabId !== undefined) {
248
294
  try {
249
295
  const tab = await chrome.tabs.get(tabId);
250
- if (isDebuggableUrl(tab.url)) return tabId;
251
- // Tab exists but URL is not debuggable fall through to auto-resolve
252
- console.warn(`[opencli] Tab ${tabId} URL is not debuggable (${tab.url}), re-resolving`);
296
+ const session = automationSessions.get(workspace);
297
+ if (isDebuggableUrl(tab.url) && session && tab.windowId === session.windowId) return tabId;
298
+ if (session && tab.windowId !== session.windowId) {
299
+ console.warn(`[opencli] Tab ${tabId} belongs to window ${tab.windowId}, not automation window ${session.windowId}, re-resolving`);
300
+ } else if (!isDebuggableUrl(tab.url)) {
301
+ // Tab exists but URL is not debuggable — fall through to auto-resolve
302
+ console.warn(`[opencli] Tab ${tabId} URL is not debuggable (${tab.url}), re-resolving`);
303
+ }
253
304
  } catch {
254
305
  // Tab was closed — fall through to auto-resolve
255
306
  console.warn(`[opencli] Tab ${tabId} no longer exists, re-resolving`);
@@ -268,7 +319,7 @@ async function resolveTabId(tabId: number | undefined, workspace: string): Promi
268
319
  // Try to reuse by navigating to a data: URI (not interceptable by New Tab Override).
269
320
  const reuseTab = tabs.find(t => t.id);
270
321
  if (reuseTab?.id) {
271
- await chrome.tabs.update(reuseTab.id, { url: 'data:text/html,<html></html>' });
322
+ await chrome.tabs.update(reuseTab.id, { url: BLANK_PAGE });
272
323
  await new Promise(resolve => setTimeout(resolve, 300));
273
324
  try {
274
325
  const updated = await chrome.tabs.get(reuseTab.id);
@@ -280,7 +331,7 @@ async function resolveTabId(tabId: number | undefined, workspace: string): Promi
280
331
  }
281
332
 
282
333
  // Fallback: create a new tab
283
- const newTab = await chrome.tabs.create({ windowId, url: 'data:text/html,<html></html>', active: true });
334
+ const newTab = await chrome.tabs.create({ windowId, url: BLANK_PAGE, active: true });
284
335
  if (!newTab.id) throw new Error('Failed to create tab in automation window');
285
336
  return newTab.id;
286
337
  }
@@ -314,54 +365,79 @@ async function handleExec(cmd: Command, workspace: string): Promise<Result> {
314
365
 
315
366
  async function handleNavigate(cmd: Command, workspace: string): Promise<Result> {
316
367
  if (!cmd.url) return { id: cmd.id, ok: false, error: 'Missing url' };
368
+ if (!isSafeNavigationUrl(cmd.url)) {
369
+ return { id: cmd.id, ok: false, error: 'Blocked URL scheme -- only http:// and https:// are allowed' };
370
+ }
317
371
  const tabId = await resolveTabId(cmd.tabId, workspace);
318
372
 
319
- // Capture the current URL before navigation to detect actual URL change
320
373
  const beforeTab = await chrome.tabs.get(tabId);
321
- const beforeUrl = beforeTab.url ?? '';
374
+ const beforeNormalized = normalizeUrlForComparison(beforeTab.url);
322
375
  const targetUrl = cmd.url;
323
376
 
377
+ // Fast-path: tab is already at the target URL and fully loaded.
378
+ if (beforeTab.status === 'complete' && isTargetUrl(beforeTab.url, targetUrl)) {
379
+ return {
380
+ id: cmd.id,
381
+ ok: true,
382
+ data: { title: beforeTab.title, url: beforeTab.url, tabId, timedOut: false },
383
+ };
384
+ }
385
+
386
+ // Detach any existing debugger before top-level navigation.
387
+ // Some sites (observed on creator.xiaohongshu.com flows) can invalidate the
388
+ // current inspected target during navigation, which leaves a stale CDP attach
389
+ // state and causes the next Runtime.evaluate to fail with
390
+ // "Inspected target navigated or closed". Resetting here forces a clean
391
+ // re-attach after navigation.
392
+ await executor.detach(tabId);
393
+
324
394
  await chrome.tabs.update(tabId, { url: targetUrl });
325
395
 
326
- // Wait for: 1) URL to change from the old URL, 2) tab.status === 'complete'
327
- // This avoids the race where 'complete' fires for the OLD URL (e.g. about:blank)
396
+ // Wait until navigation completes. Resolve when status is 'complete' AND either:
397
+ // - the URL matches the target (handles same-URL / canonicalized navigations), OR
398
+ // - the URL differs from the pre-navigation URL (handles redirects).
328
399
  let timedOut = false;
329
400
  await new Promise<void>((resolve) => {
330
- let urlChanged = false;
401
+ let settled = false;
402
+ let checkTimer: ReturnType<typeof setTimeout> | null = null;
403
+ let timeoutTimer: ReturnType<typeof setTimeout> | null = null;
331
404
 
332
- const listener = (id: number, info: chrome.tabs.TabChangeInfo, tab: chrome.tabs.Tab) => {
333
- if (id !== tabId) return;
405
+ const finish = () => {
406
+ if (settled) return;
407
+ settled = true;
408
+ chrome.tabs.onUpdated.removeListener(listener);
409
+ if (checkTimer) clearTimeout(checkTimer);
410
+ if (timeoutTimer) clearTimeout(timeoutTimer);
411
+ resolve();
412
+ };
334
413
 
335
- // Track URL change (new URL differs from the one before navigation)
336
- if (info.url && info.url !== beforeUrl) {
337
- urlChanged = true;
338
- }
414
+ const isNavigationDone = (url: string | undefined): boolean => {
415
+ return isTargetUrl(url, targetUrl) || normalizeUrlForComparison(url) !== beforeNormalized;
416
+ };
339
417
 
340
- // Only resolve when both URL has changed AND status is complete
341
- if (urlChanged && info.status === 'complete') {
342
- chrome.tabs.onUpdated.removeListener(listener);
343
- resolve();
418
+ const listener = (id: number, info: chrome.tabs.TabChangeInfo, tab: chrome.tabs.Tab) => {
419
+ if (id !== tabId) return;
420
+ if (info.status === 'complete' && isNavigationDone(tab.url ?? info.url)) {
421
+ finish();
344
422
  }
345
423
  };
346
424
  chrome.tabs.onUpdated.addListener(listener);
347
425
 
348
426
  // Also check if the tab already navigated (e.g. instant cache hit)
349
- setTimeout(async () => {
427
+ checkTimer = setTimeout(async () => {
350
428
  try {
351
429
  const currentTab = await chrome.tabs.get(tabId);
352
- if (currentTab.url !== beforeUrl && currentTab.status === 'complete') {
353
- chrome.tabs.onUpdated.removeListener(listener);
354
- resolve();
430
+ if (currentTab.status === 'complete' && isNavigationDone(currentTab.url)) {
431
+ finish();
355
432
  }
356
433
  } catch { /* tab gone */ }
357
434
  }, 100);
358
435
 
359
436
  // Timeout fallback with warning
360
- setTimeout(() => {
361
- chrome.tabs.onUpdated.removeListener(listener);
437
+ timeoutTimer = setTimeout(() => {
362
438
  timedOut = true;
363
439
  console.warn(`[opencli] Navigate to ${targetUrl} timed out after 15s`);
364
- resolve();
440
+ finish();
365
441
  }, 15000);
366
442
  });
367
443
 
@@ -388,8 +464,11 @@ async function handleTabs(cmd: Command, workspace: string): Promise<Result> {
388
464
  return { id: cmd.id, ok: true, data };
389
465
  }
390
466
  case 'new': {
467
+ if (cmd.url && !isSafeNavigationUrl(cmd.url)) {
468
+ return { id: cmd.id, ok: false, error: 'Blocked URL scheme -- only http:// and https:// are allowed' };
469
+ }
391
470
  const windowId = await getAutomationWindow(workspace);
392
- const tab = await chrome.tabs.create({ windowId, url: cmd.url ?? 'data:text/html,<html></html>', active: true });
471
+ const tab = await chrome.tabs.create({ windowId, url: cmd.url ?? BLANK_PAGE, active: true });
393
472
  return { id: cmd.id, ok: true, data: { tabId: tab.id, url: tab.url } };
394
473
  }
395
474
  case 'close': {
@@ -398,18 +477,28 @@ async function handleTabs(cmd: Command, workspace: string): Promise<Result> {
398
477
  const target = tabs[cmd.index];
399
478
  if (!target?.id) return { id: cmd.id, ok: false, error: `Tab index ${cmd.index} not found` };
400
479
  await chrome.tabs.remove(target.id);
401
- executor.detach(target.id);
480
+ await executor.detach(target.id);
402
481
  return { id: cmd.id, ok: true, data: { closed: target.id } };
403
482
  }
404
483
  const tabId = await resolveTabId(cmd.tabId, workspace);
405
484
  await chrome.tabs.remove(tabId);
406
- executor.detach(tabId);
485
+ await executor.detach(tabId);
407
486
  return { id: cmd.id, ok: true, data: { closed: tabId } };
408
487
  }
409
488
  case 'select': {
410
489
  if (cmd.index === undefined && cmd.tabId === undefined)
411
490
  return { id: cmd.id, ok: false, error: 'Missing index or tabId' };
412
491
  if (cmd.tabId !== undefined) {
492
+ const session = automationSessions.get(workspace);
493
+ let tab: chrome.tabs.Tab;
494
+ try {
495
+ tab = await chrome.tabs.get(cmd.tabId);
496
+ } catch {
497
+ return { id: cmd.id, ok: false, error: `Tab ${cmd.tabId} no longer exists` };
498
+ }
499
+ if (!session || tab.windowId !== session.windowId) {
500
+ return { id: cmd.id, ok: false, error: `Tab ${cmd.tabId} is not in the automation window` };
501
+ }
413
502
  await chrome.tabs.update(cmd.tabId, { active: true });
414
503
  return { id: cmd.id, ok: true, data: { selected: cmd.tabId } };
415
504
  }
@@ -425,6 +514,9 @@ async function handleTabs(cmd: Command, workspace: string): Promise<Result> {
425
514
  }
426
515
 
427
516
  async function handleCookies(cmd: Command): Promise<Result> {
517
+ if (!cmd.domain && !cmd.url) {
518
+ return { id: cmd.id, ok: false, error: 'Cookie scope required: provide domain or url to avoid dumping all cookies' };
519
+ }
428
520
  const details: chrome.cookies.GetAllDetails = {};
429
521
  if (cmd.domain) details.domain = cmd.domain;
430
522
  if (cmd.url) details.url = cmd.url;
@@ -481,6 +573,8 @@ async function handleSessions(cmd: Command): Promise<Result> {
481
573
  }
482
574
 
483
575
  export const __test__ = {
576
+ handleNavigate,
577
+ isTargetUrl,
484
578
  handleTabs,
485
579
  handleSessions,
486
580
  getAutomationWindowId: (workspace: string = 'default') => automationSessions.get(workspace)?.windowId ?? null,
@@ -8,10 +8,13 @@
8
8
 
9
9
  const attached = new Set<number>();
10
10
 
11
- /** Check if a URL can be attached via CDP */
11
+ /** Internal blank page used when no user URL is provided. */
12
+ const BLANK_PAGE = 'data:text/html,<html></html>';
13
+
14
+ /** Check if a URL can be attached via CDP — only allow http(s) and our internal blank page. */
12
15
  function isDebuggableUrl(url?: string): boolean {
13
16
  if (!url) return true; // empty/undefined = tab still loading, allow it
14
- return !url.startsWith('chrome://') && !url.startsWith('chrome-extension://');
17
+ return url.startsWith('http://') || url.startsWith('https://') || url === BLANK_PAGE;
15
18
  }
16
19
 
17
20
  async function ensureAttached(tabId: number): Promise<void> {
@@ -144,10 +147,10 @@ export async function screenshot(
144
147
  }
145
148
  }
146
149
 
147
- export function detach(tabId: number): void {
150
+ export async function detach(tabId: number): Promise<void> {
148
151
  if (!attached.has(tabId)) return;
149
152
  attached.delete(tabId);
150
- try { chrome.debugger.detach({ tabId }); } catch { /* ignore */ }
153
+ try { await chrome.debugger.detach({ tabId }); } catch { /* ignore */ }
151
154
  }
152
155
 
153
156
  export function registerListeners(): void {
@@ -158,12 +161,9 @@ export function registerListeners(): void {
158
161
  if (source.tabId) attached.delete(source.tabId);
159
162
  });
160
163
  // Invalidate attached cache when tab URL changes to non-debuggable
161
- chrome.tabs.onUpdated.addListener((tabId, info) => {
164
+ chrome.tabs.onUpdated.addListener(async (tabId, info) => {
162
165
  if (info.url && !isDebuggableUrl(info.url)) {
163
- if (attached.has(tabId)) {
164
- attached.delete(tabId);
165
- try { chrome.debugger.detach({ tabId }); } catch { /* ignore */ }
166
- }
166
+ await detach(tabId);
167
167
  }
168
168
  });
169
169
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jackwener/opencli",
3
- "version": "1.3.3",
3
+ "version": "1.4.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -30,6 +30,7 @@
30
30
  "lint": "tsc --noEmit",
31
31
  "prepublishOnly": "npm run build",
32
32
  "test": "vitest run --project unit",
33
+ "test:adapter": "vitest run --project adapter",
33
34
  "test:all": "vitest run",
34
35
  "test:e2e": "vitest run --project e2e",
35
36
  "docs:dev": "vitepress dev docs",
@@ -62,7 +63,7 @@
62
63
  "@types/turndown": "^5.0.6",
63
64
  "@types/ws": "^8.5.13",
64
65
  "tsx": "^4.19.3",
65
- "typescript": "^5.8.2",
66
+ "typescript": "^6.0.2",
66
67
  "vitepress": "^1.6.4",
67
68
  "vitest": "^4.1.0"
68
69
  }
@@ -28,6 +28,8 @@ total=0
28
28
 
29
29
  for adapter_dir in "$SRC_DIR"/*/; do
30
30
  adapter_name="$(basename "$adapter_dir")"
31
+ # Skip internal directories (e.g., _shared)
32
+ [[ "$adapter_name" == _* ]] && continue
31
33
  total=$((total + 1))
32
34
 
33
35
  # Check if doc exists in browser/ or desktop/ subdirectories
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Shared API analysis helpers used by both explore.ts and record.ts.
3
+ *
4
+ * Extracts common logic for:
5
+ * - URL pattern normalization
6
+ * - Array path discovery in JSON responses
7
+ * - Field role detection
8
+ * - Auth indicator inference
9
+ * - Capability name inference
10
+ * - Strategy inference
11
+ */
12
+
13
+ import {
14
+ VOLATILE_PARAMS,
15
+ SEARCH_PARAMS,
16
+ PAGINATION_PARAMS,
17
+ LIMIT_PARAMS,
18
+ FIELD_ROLES,
19
+ } from './constants.js';
20
+
21
+ // ── URL pattern normalization ───────────────────────────────────────────────
22
+
23
+ /** Normalize a full URL into a pattern (replace IDs, strip volatile params). */
24
+ export function urlToPattern(url: string): string {
25
+ try {
26
+ const p = new URL(url);
27
+ const pathNorm = p.pathname
28
+ .replace(/\/\d+/g, '/{id}')
29
+ .replace(/\/[0-9a-fA-F]{8,}/g, '/{hex}')
30
+ .replace(/\/BV[a-zA-Z0-9]{10}/g, '/{bvid}');
31
+ const params: string[] = [];
32
+ p.searchParams.forEach((_v, k) => { if (!VOLATILE_PARAMS.has(k)) params.push(k); });
33
+ return `${p.host}${pathNorm}${params.length ? '?' + params.sort().map(k => `${k}={}`).join('&') : ''}`;
34
+ } catch { return url; }
35
+ }
36
+
37
+ // ── Array discovery in JSON responses ───────────────────────────────────────
38
+
39
+ export interface ArrayDiscovery {
40
+ path: string;
41
+ items: unknown[];
42
+ }
43
+
44
+ /** Find the best (largest) array of objects in a JSON response body. */
45
+ export function findArrayPath(obj: unknown, depth = 0): ArrayDiscovery | null {
46
+ if (depth > 5 || !obj || typeof obj !== 'object') return null;
47
+ if (Array.isArray(obj)) {
48
+ if (obj.length >= 2 && obj.some(i => i && typeof i === 'object' && !Array.isArray(i))) {
49
+ return { path: '', items: obj };
50
+ }
51
+ return null;
52
+ }
53
+ let best: ArrayDiscovery | null = null;
54
+ for (const [key, val] of Object.entries(obj as Record<string, unknown>)) {
55
+ const found = findArrayPath(val, depth + 1);
56
+ if (found) {
57
+ const fullPath = found.path ? `${key}.${found.path}` : key;
58
+ const candidate = { path: fullPath, items: found.items };
59
+ if (!best || candidate.items.length > best.items.length) best = candidate;
60
+ }
61
+ }
62
+ return best;
63
+ }
64
+
65
+ // ── Field flattening & role detection ───────────────────────────────────────
66
+
67
+ /** Flatten nested object keys up to maxDepth. */
68
+ export function flattenFields(obj: unknown, prefix: string, maxDepth: number): string[] {
69
+ if (maxDepth <= 0 || !obj || typeof obj !== 'object') return [];
70
+ const names: string[] = [];
71
+ const record = obj as Record<string, unknown>;
72
+ for (const key of Object.keys(record)) {
73
+ const full = prefix ? `${prefix}.${key}` : key;
74
+ names.push(full);
75
+ const val = record[key];
76
+ if (val && typeof val === 'object' && !Array.isArray(val)) names.push(...flattenFields(val, full, maxDepth - 1));
77
+ }
78
+ return names;
79
+ }
80
+
81
+ /** Detect semantic field roles (title, url, author, etc.) from sample fields. */
82
+ export function detectFieldRoles(sampleFields: string[]): Record<string, string> {
83
+ const detectedFields: Record<string, string> = {};
84
+ for (const [role, aliases] of Object.entries(FIELD_ROLES)) {
85
+ for (const f of sampleFields) {
86
+ if (aliases.includes(f.split('.').pop()?.toLowerCase() ?? '')) {
87
+ detectedFields[role] = f;
88
+ break;
89
+ }
90
+ }
91
+ }
92
+ return detectedFields;
93
+ }
94
+
95
+ // ── Capability name inference ───────────────────────────────────────────────
96
+
97
+ /** Infer a CLI capability name from a URL. */
98
+ export function inferCapabilityName(url: string, goal?: string): string {
99
+ if (goal) return goal;
100
+ const u = url.toLowerCase();
101
+ if (u.includes('hot') || u.includes('popular') || u.includes('ranking') || u.includes('trending')) return 'hot';
102
+ if (u.includes('search')) return 'search';
103
+ if (u.includes('feed') || u.includes('timeline') || u.includes('dynamic')) return 'feed';
104
+ if (u.includes('comment') || u.includes('reply')) return 'comments';
105
+ if (u.includes('history')) return 'history';
106
+ if (u.includes('profile') || u.includes('userinfo') || u.includes('/me')) return 'me';
107
+ if (u.includes('favorite') || u.includes('collect') || u.includes('bookmark')) return 'favorite';
108
+ try {
109
+ const segs = new URL(url).pathname
110
+ .split('/')
111
+ .filter(s => s && !s.match(/^\d+$/) && !s.match(/^[0-9a-f]{8,}$/i) && !s.match(/^v\d+$/));
112
+ if (segs.length) return segs[segs.length - 1].replace(/[^a-z0-9]/gi, '_').toLowerCase();
113
+ } catch {}
114
+ return 'data';
115
+ }
116
+
117
+ // ── Strategy inference ──────────────────────────────────────────────────────
118
+
119
+ /** Infer auth strategy from detected indicators. */
120
+ export function inferStrategy(authIndicators: string[]): string {
121
+ if (authIndicators.includes('signature')) return 'intercept';
122
+ if (authIndicators.includes('bearer') || authIndicators.includes('csrf')) return 'header';
123
+ return 'cookie';
124
+ }
125
+
126
+ // ── Auth indicator detection ────────────────────────────────────────────────
127
+
128
+ /** Detect auth indicators from HTTP headers. */
129
+ export function detectAuthFromHeaders(headers?: Record<string, string>): string[] {
130
+ if (!headers) return [];
131
+ const indicators: string[] = [];
132
+ const keys = Object.keys(headers).map(k => k.toLowerCase());
133
+ if (keys.some(k => k === 'authorization')) indicators.push('bearer');
134
+ if (keys.some(k => k.startsWith('x-csrf') || k.startsWith('x-xsrf'))) indicators.push('csrf');
135
+ if (keys.some(k => k.startsWith('x-s') || k === 'x-t' || k === 'x-s-common')) indicators.push('signature');
136
+ return indicators;
137
+ }
138
+
139
+ /** Detect auth indicators from URL and response body (heuristic). */
140
+ export function detectAuthFromContent(url: string, body: unknown): string[] {
141
+ const indicators: string[] = [];
142
+ if (body && typeof body === 'object') {
143
+ const keys = Object.keys(body as object).map(k => k.toLowerCase());
144
+ if (keys.some(k => k.includes('sign') || k === 'w_rid' || k.includes('token'))) {
145
+ indicators.push('signature');
146
+ }
147
+ }
148
+ if (url.includes('/wbi/') || url.includes('w_rid=')) indicators.push('signature');
149
+ if (url.includes('bearer') || url.includes('access_token')) indicators.push('bearer');
150
+ return indicators;
151
+ }
152
+
153
+ // ── Query param classification ──────────────────────────────────────────────
154
+
155
+ /** Extract non-volatile query params and classify them. */
156
+ export function classifyQueryParams(url: string): {
157
+ params: string[];
158
+ hasSearch: boolean;
159
+ hasPagination: boolean;
160
+ hasLimit: boolean;
161
+ } {
162
+ const params: string[] = [];
163
+ try { new URL(url).searchParams.forEach((_v, k) => { if (!VOLATILE_PARAMS.has(k)) params.push(k); }); } catch {}
164
+ return {
165
+ params,
166
+ hasSearch: params.some(p => SEARCH_PARAMS.has(p)),
167
+ hasPagination: params.some(p => PAGINATION_PARAMS.has(p)),
168
+ hasLimit: params.some(p => LIMIT_PARAMS.has(p)),
169
+ };
170
+ }
@@ -0,0 +1,66 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ const { MockWebSocket } = vi.hoisted(() => {
4
+ class MockWebSocket {
5
+ static OPEN = 1;
6
+ readyState = 1;
7
+ private handlers = new Map<string, Array<(...args: any[]) => void>>();
8
+
9
+ constructor(_url: string) {
10
+ queueMicrotask(() => this.emit('open'));
11
+ }
12
+
13
+ on(event: string, handler: (...args: any[]) => void): void {
14
+ const handlers = this.handlers.get(event) ?? [];
15
+ handlers.push(handler);
16
+ this.handlers.set(event, handlers);
17
+ }
18
+
19
+ send(_message: string): void {}
20
+
21
+ close(): void {
22
+ this.readyState = 3;
23
+ }
24
+
25
+ private emit(event: string, ...args: any[]): void {
26
+ for (const handler of this.handlers.get(event) ?? []) {
27
+ handler(...args);
28
+ }
29
+ }
30
+ }
31
+
32
+ return { MockWebSocket };
33
+ });
34
+
35
+ vi.mock('ws', () => ({
36
+ WebSocket: MockWebSocket,
37
+ }));
38
+
39
+ import { CDPBridge } from './cdp.js';
40
+
41
+ describe('CDPBridge cookies', () => {
42
+ beforeEach(() => {
43
+ vi.unstubAllEnvs();
44
+ });
45
+
46
+ it('filters cookies by actual domain match instead of substring match', async () => {
47
+ vi.stubEnv('OPENCLI_CDP_ENDPOINT', 'ws://127.0.0.1:9222/devtools/page/1');
48
+
49
+ const bridge = new CDPBridge();
50
+ vi.spyOn(bridge, 'send').mockResolvedValue({
51
+ cookies: [
52
+ { name: 'good', value: '1', domain: '.example.com' },
53
+ { name: 'exact', value: '2', domain: 'example.com' },
54
+ { name: 'bad', value: '3', domain: 'notexample.com' },
55
+ ],
56
+ });
57
+
58
+ const page = await bridge.connect();
59
+ const cookies = await page.getCookies({ domain: 'example.com' });
60
+
61
+ expect(cookies).toEqual([
62
+ { name: 'good', value: '1', domain: '.example.com' },
63
+ { name: 'exact', value: '2', domain: 'example.com' },
64
+ ]);
65
+ });
66
+ });