@jackwener/opencli 1.3.3 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (496) hide show
  1. package/.github/pull_request_template.md +3 -1
  2. package/.github/workflows/build-extension.yml +7 -1
  3. package/.github/workflows/ci.yml +29 -3
  4. package/.github/workflows/docs.yml +1 -1
  5. package/.github/workflows/e2e-headed.yml +20 -0
  6. package/.github/workflows/release.yml +1 -1
  7. package/.github/workflows/security.yml +0 -3
  8. package/CHANGELOG.md +55 -0
  9. package/CONTRIBUTING.md +6 -3
  10. package/README.md +30 -3
  11. package/README.zh-CN.md +30 -3
  12. package/SKILL.md +7 -1
  13. package/TESTING.md +1 -0
  14. package/chatwise-opencli.ps1 +82 -0
  15. package/dist/analysis.d.ts +38 -0
  16. package/dist/analysis.js +166 -0
  17. package/dist/browser/cdp.d.ts +0 -4
  18. package/dist/browser/cdp.js +53 -41
  19. package/dist/browser/cdp.test.d.ts +1 -0
  20. package/dist/browser/cdp.test.js +52 -0
  21. package/dist/browser/dom-snapshot.d.ts +2 -2
  22. package/dist/browser/dom-snapshot.js +54 -1
  23. package/dist/browser/dom-snapshot.test.js +36 -0
  24. package/dist/browser/index.d.ts +2 -2
  25. package/dist/browser/index.js +1 -1
  26. package/dist/browser/mcp.d.ts +0 -2
  27. package/dist/browser/mcp.js +2 -3
  28. package/dist/browser/page.d.ts +4 -3
  29. package/dist/browser/page.js +34 -37
  30. package/dist/browser/stealth.d.ts +0 -2
  31. package/dist/browser/stealth.js +24 -9
  32. package/dist/browser.test.js +2 -2
  33. package/dist/build-manifest.js +15 -9
  34. package/dist/build-manifest.test.js +12 -0
  35. package/dist/cascade.js +4 -2
  36. package/dist/cli-manifest.json +639 -258
  37. package/dist/cli.js +57 -29
  38. package/dist/clis/_shared/desktop-commands.d.ts +22 -0
  39. package/dist/clis/_shared/desktop-commands.js +108 -0
  40. package/dist/clis/antigravity/serve.js +5 -2
  41. package/dist/clis/arxiv/search.js +1 -1
  42. package/dist/clis/bilibili/dynamic.test.d.ts +1 -0
  43. package/dist/clis/bilibili/dynamic.test.js +68 -0
  44. package/dist/clis/bilibili/favorite.js +4 -2
  45. package/dist/clis/bilibili/following.js +3 -2
  46. package/dist/clis/bilibili/subtitle.js +8 -7
  47. package/dist/clis/bilibili/utils.js +2 -2
  48. package/dist/clis/boss/batchgreet.js +1 -1
  49. package/dist/clis/boss/chatlist.js +1 -1
  50. package/dist/clis/boss/chatmsg.js +1 -1
  51. package/dist/clis/boss/detail.js +1 -1
  52. package/dist/clis/boss/exchange.js +1 -1
  53. package/dist/clis/boss/greet.js +1 -1
  54. package/dist/clis/boss/invite.js +1 -1
  55. package/dist/clis/boss/joblist.js +1 -1
  56. package/dist/clis/boss/mark.js +4 -3
  57. package/dist/clis/boss/recommend.js +1 -1
  58. package/dist/clis/boss/resume.js +1 -1
  59. package/dist/clis/boss/search.js +1 -1
  60. package/dist/clis/boss/send.js +5 -4
  61. package/dist/clis/boss/stats.js +1 -1
  62. package/dist/clis/chatgpt/ask.js +4 -0
  63. package/dist/clis/chatgpt/new.js +5 -1
  64. package/dist/clis/chatgpt/read.js +5 -1
  65. package/dist/clis/chatgpt/send.js +2 -1
  66. package/dist/clis/chatgpt/status.js +5 -1
  67. package/dist/clis/chatwise/ask.js +8 -2
  68. package/dist/clis/chatwise/export.js +2 -0
  69. package/dist/clis/chatwise/history.js +2 -0
  70. package/dist/clis/chatwise/model.js +8 -3
  71. package/dist/clis/chatwise/new.js +3 -18
  72. package/dist/clis/chatwise/read.js +2 -0
  73. package/dist/clis/chatwise/screenshot.js +3 -27
  74. package/dist/clis/chatwise/send.js +8 -2
  75. package/dist/clis/chatwise/shared.d.ts +2 -0
  76. package/dist/clis/chatwise/shared.js +6 -0
  77. package/dist/clis/chatwise/status.js +3 -22
  78. package/dist/clis/codex/ask.js +6 -2
  79. package/dist/clis/codex/dump.js +2 -25
  80. package/dist/clis/codex/new.js +2 -25
  81. package/dist/clis/codex/screenshot.js +2 -27
  82. package/dist/clis/codex/send.js +6 -4
  83. package/dist/clis/codex/status.js +2 -22
  84. package/dist/clis/cursor/ask.js +2 -1
  85. package/dist/clis/cursor/composer.js +2 -1
  86. package/dist/clis/cursor/dump.js +2 -25
  87. package/dist/clis/cursor/new.js +2 -18
  88. package/dist/clis/cursor/read.js +2 -1
  89. package/dist/clis/cursor/screenshot.js +1 -30
  90. package/dist/clis/cursor/send.js +2 -1
  91. package/dist/clis/cursor/status.js +2 -21
  92. package/dist/clis/dictionary/examples.yaml +25 -0
  93. package/dist/clis/dictionary/search.yaml +27 -0
  94. package/dist/clis/dictionary/synonyms.yaml +25 -0
  95. package/dist/clis/douban/book-hot.js +1 -1
  96. package/dist/clis/douban/movie-hot.js +1 -1
  97. package/dist/clis/douban/search.js +1 -1
  98. package/dist/clis/douban/utils.d.ts +4 -1
  99. package/dist/clis/douban/utils.js +156 -1
  100. package/dist/clis/doubao/ask.js +1 -1
  101. package/dist/clis/doubao/new.js +1 -1
  102. package/dist/clis/doubao/read.js +1 -1
  103. package/dist/clis/doubao/send.js +1 -1
  104. package/dist/clis/doubao/status.js +1 -1
  105. package/dist/clis/doubao-app/ask.js +1 -1
  106. package/dist/clis/doubao-app/new.js +1 -1
  107. package/dist/clis/doubao-app/read.js +1 -1
  108. package/dist/clis/doubao-app/send.js +1 -1
  109. package/dist/clis/grok/ask.d.ts +4 -0
  110. package/dist/clis/grok/ask.js +28 -10
  111. package/dist/clis/grok/ask.test.js +18 -0
  112. package/dist/clis/jd/item.d.ts +1 -0
  113. package/dist/clis/jd/item.js +96 -0
  114. package/dist/clis/jd/item.test.d.ts +1 -0
  115. package/dist/clis/jd/item.test.js +28 -0
  116. package/dist/clis/jike/feed.js +1 -1
  117. package/dist/clis/jike/search.js +1 -1
  118. package/dist/clis/linkedin/search.js +5 -4
  119. package/dist/clis/linkedin/timeline.d.ts +21 -0
  120. package/dist/clis/linkedin/timeline.js +503 -0
  121. package/dist/clis/linkedin/timeline.test.d.ts +1 -0
  122. package/dist/clis/linkedin/timeline.test.js +81 -0
  123. package/dist/clis/medium/feed.js +1 -1
  124. package/dist/clis/medium/search.js +1 -1
  125. package/dist/clis/medium/user.js +1 -1
  126. package/dist/clis/medium/{shared.js → utils.js} +2 -1
  127. package/dist/clis/pixiv/detail.yaml +49 -0
  128. package/dist/clis/pixiv/download.d.ts +7 -0
  129. package/dist/clis/pixiv/download.js +78 -0
  130. package/dist/clis/pixiv/download.test.d.ts +1 -0
  131. package/dist/clis/pixiv/download.test.js +87 -0
  132. package/dist/clis/pixiv/illusts.d.ts +8 -0
  133. package/dist/clis/pixiv/illusts.js +65 -0
  134. package/dist/clis/pixiv/illusts.test.d.ts +1 -0
  135. package/dist/clis/pixiv/illusts.test.js +99 -0
  136. package/dist/clis/pixiv/ranking.yaml +53 -0
  137. package/dist/clis/pixiv/search.d.ts +6 -0
  138. package/dist/clis/pixiv/search.js +43 -0
  139. package/dist/clis/pixiv/search.test.d.ts +1 -0
  140. package/dist/clis/pixiv/search.test.js +83 -0
  141. package/dist/clis/pixiv/test-utils.d.ts +12 -0
  142. package/dist/clis/pixiv/test-utils.js +23 -0
  143. package/dist/clis/pixiv/user.yaml +46 -0
  144. package/dist/clis/pixiv/utils.d.ts +27 -0
  145. package/dist/clis/pixiv/utils.js +49 -0
  146. package/dist/clis/reddit/comment.js +2 -1
  147. package/dist/clis/reddit/read.js +4 -3
  148. package/dist/clis/reddit/read.test.d.ts +1 -0
  149. package/dist/clis/reddit/read.test.js +28 -0
  150. package/dist/clis/reddit/save.js +2 -1
  151. package/dist/clis/reddit/saved.js +7 -3
  152. package/dist/clis/reddit/subscribe.js +2 -1
  153. package/dist/clis/reddit/upvote.js +2 -1
  154. package/dist/clis/reddit/upvoted.js +7 -3
  155. package/dist/clis/sinablog/article.js +1 -1
  156. package/dist/clis/sinablog/hot.js +1 -1
  157. package/dist/clis/sinablog/user.js +1 -1
  158. package/dist/clis/substack/feed.js +1 -1
  159. package/dist/clis/substack/publication.js +1 -1
  160. package/dist/clis/substack/search.js +3 -2
  161. package/dist/clis/substack/{shared.js → utils.js} +3 -2
  162. package/dist/clis/tiktok/search.yaml +2 -1
  163. package/dist/clis/twitter/accept.js +2 -1
  164. package/dist/clis/twitter/article.js +4 -1
  165. package/dist/clis/twitter/block.js +2 -1
  166. package/dist/clis/twitter/bookmark.js +2 -1
  167. package/dist/clis/twitter/bookmarks.js +3 -2
  168. package/dist/clis/twitter/delete.js +2 -1
  169. package/dist/clis/twitter/follow.js +2 -1
  170. package/dist/clis/twitter/followers.js +3 -2
  171. package/dist/clis/twitter/following.js +3 -2
  172. package/dist/clis/twitter/hide-reply.js +2 -1
  173. package/dist/clis/twitter/like.js +2 -1
  174. package/dist/clis/twitter/notifications.js +2 -1
  175. package/dist/clis/twitter/post.js +2 -1
  176. package/dist/clis/twitter/profile.js +5 -2
  177. package/dist/clis/twitter/reply-dm.js +2 -1
  178. package/dist/clis/twitter/reply.js +2 -1
  179. package/dist/clis/twitter/search.js +30 -13
  180. package/dist/clis/twitter/search.test.d.ts +1 -0
  181. package/dist/clis/twitter/search.test.js +104 -0
  182. package/dist/clis/twitter/thread.js +2 -2
  183. package/dist/clis/twitter/timeline.js +3 -2
  184. package/dist/clis/twitter/trending.js +3 -2
  185. package/dist/clis/twitter/unblock.js +2 -1
  186. package/dist/clis/twitter/unbookmark.js +2 -1
  187. package/dist/clis/twitter/unfollow.js +2 -1
  188. package/dist/clis/v2ex/daily.js +3 -2
  189. package/dist/clis/v2ex/me.js +3 -2
  190. package/dist/clis/v2ex/notifications.js +4 -4
  191. package/dist/clis/web/read.d.ts +16 -0
  192. package/dist/clis/web/read.js +202 -0
  193. package/dist/clis/xueqiu/danjuan-utils.d.ts +55 -0
  194. package/dist/clis/xueqiu/danjuan-utils.js +126 -0
  195. package/dist/clis/xueqiu/danjuan-utils.test.d.ts +1 -0
  196. package/dist/clis/xueqiu/danjuan-utils.test.js +41 -0
  197. package/dist/clis/xueqiu/fund-holdings.d.ts +1 -0
  198. package/dist/clis/xueqiu/fund-holdings.js +28 -0
  199. package/dist/clis/xueqiu/fund-snapshot.d.ts +1 -0
  200. package/dist/clis/xueqiu/fund-snapshot.js +25 -0
  201. package/dist/clis/youtube/transcript.js +5 -4
  202. package/dist/clis/youtube/video.js +3 -2
  203. package/dist/daemon.js +7 -3
  204. package/dist/discovery.js +11 -10
  205. package/dist/doctor.js +2 -1
  206. package/dist/download/index.d.ts +4 -12
  207. package/dist/download/index.js +33 -12
  208. package/dist/download/index.test.js +79 -2
  209. package/dist/download/media-download.js +4 -2
  210. package/dist/engine.test.js +76 -4
  211. package/dist/execution.d.ts +1 -9
  212. package/dist/execution.js +56 -46
  213. package/dist/explore.js +12 -111
  214. package/dist/external-clis.yaml +0 -8
  215. package/dist/external.js +7 -5
  216. package/dist/external.test.js +4 -0
  217. package/dist/generate.d.ts +0 -9
  218. package/dist/generate.js +4 -20
  219. package/dist/hooks.d.ts +46 -0
  220. package/dist/hooks.js +56 -0
  221. package/dist/hooks.test.d.ts +4 -0
  222. package/dist/hooks.test.js +92 -0
  223. package/dist/interceptor.js +70 -23
  224. package/dist/main.js +2 -0
  225. package/dist/output.js +12 -6
  226. package/dist/pipeline/executor.js +1 -1
  227. package/dist/pipeline/steps/browser.js +1 -3
  228. package/dist/pipeline/steps/download.js +42 -26
  229. package/dist/pipeline/steps/download.test.d.ts +1 -0
  230. package/dist/pipeline/steps/download.test.js +101 -0
  231. package/dist/pipeline/steps/fetch.js +40 -22
  232. package/dist/pipeline/steps/fetch.test.d.ts +1 -0
  233. package/dist/pipeline/steps/fetch.test.js +123 -0
  234. package/dist/pipeline/steps/transform.js +2 -6
  235. package/dist/pipeline/template.js +66 -52
  236. package/dist/pipeline/template.test.js +28 -0
  237. package/dist/pipeline/transform.test.js +18 -0
  238. package/dist/plugin.d.ts +40 -1
  239. package/dist/plugin.js +214 -17
  240. package/dist/plugin.test.d.ts +1 -1
  241. package/dist/plugin.test.js +219 -3
  242. package/dist/record.js +6 -98
  243. package/dist/registry-api.d.ts +2 -0
  244. package/dist/registry-api.js +1 -0
  245. package/dist/registry.d.ts +5 -2
  246. package/dist/registry.js +1 -2
  247. package/dist/runtime.d.ts +0 -1
  248. package/dist/runtime.js +14 -4
  249. package/dist/snapshotFormatter.d.ts +7 -14
  250. package/dist/snapshotFormatter.js +38 -78
  251. package/dist/utils.d.ts +9 -0
  252. package/dist/utils.js +29 -0
  253. package/dist/validate.js +3 -5
  254. package/dist/yaml-schema.d.ts +26 -0
  255. package/dist/yaml-schema.js +5 -0
  256. package/docs/.vitepress/config.mts +3 -0
  257. package/docs/adapters/browser/dictionary.md +27 -0
  258. package/docs/adapters/browser/jd.md +27 -0
  259. package/docs/adapters/browser/linkedin.md +6 -0
  260. package/docs/adapters/browser/pixiv.md +92 -0
  261. package/docs/adapters/browser/web.md +30 -0
  262. package/docs/adapters/browser/xueqiu.md +27 -9
  263. package/docs/adapters/index.md +3 -1
  264. package/docs/comparison.md +125 -0
  265. package/docs/developer/contributing.md +21 -2
  266. package/docs/developer/testing.md +14 -8
  267. package/docs/developer/ts-adapter.md +18 -0
  268. package/docs/developer/yaml-adapter.md +16 -0
  269. package/docs/guide/plugins.md +10 -0
  270. package/docs/zh/guide/plugins.md +10 -0
  271. package/extension/dist/background.js +519 -444
  272. package/extension/manifest.json +1 -1
  273. package/extension/package.json +1 -1
  274. package/extension/src/background.test.ts +46 -1
  275. package/extension/src/background.ts +108 -33
  276. package/extension/src/cdp.ts +9 -9
  277. package/package.json +3 -2
  278. package/scripts/check-doc-coverage.sh +2 -0
  279. package/src/analysis.ts +170 -0
  280. package/src/browser/cdp.test.ts +66 -0
  281. package/src/browser/cdp.ts +59 -44
  282. package/src/browser/dom-snapshot.test.ts +42 -0
  283. package/src/browser/dom-snapshot.ts +56 -3
  284. package/src/browser/index.ts +2 -2
  285. package/src/browser/mcp.ts +2 -4
  286. package/src/browser/page.ts +34 -37
  287. package/src/browser/stealth.ts +24 -10
  288. package/src/browser.test.ts +2 -2
  289. package/src/build-manifest.test.ts +14 -0
  290. package/src/build-manifest.ts +13 -31
  291. package/src/cascade.ts +5 -3
  292. package/src/cli.ts +66 -34
  293. package/src/clis/_shared/desktop-commands.ts +121 -0
  294. package/src/clis/antigravity/serve.ts +6 -3
  295. package/src/clis/arxiv/search.ts +1 -1
  296. package/src/clis/bilibili/dynamic.test.ts +79 -0
  297. package/src/clis/bilibili/favorite.ts +5 -2
  298. package/src/clis/bilibili/following.ts +3 -2
  299. package/src/clis/bilibili/subtitle.ts +8 -7
  300. package/src/clis/bilibili/utils.ts +2 -2
  301. package/src/clis/boss/batchgreet.ts +1 -1
  302. package/src/clis/boss/chatlist.ts +1 -1
  303. package/src/clis/boss/chatmsg.ts +1 -1
  304. package/src/clis/boss/detail.ts +1 -1
  305. package/src/clis/boss/exchange.ts +1 -1
  306. package/src/clis/boss/greet.ts +1 -1
  307. package/src/clis/boss/invite.ts +1 -1
  308. package/src/clis/boss/joblist.ts +1 -1
  309. package/src/clis/boss/mark.ts +4 -3
  310. package/src/clis/boss/recommend.ts +1 -1
  311. package/src/clis/boss/resume.ts +1 -1
  312. package/src/clis/boss/search.ts +1 -1
  313. package/src/clis/boss/send.ts +5 -4
  314. package/src/clis/boss/stats.ts +1 -1
  315. package/src/clis/chatgpt/ask.ts +5 -0
  316. package/src/clis/chatgpt/new.ts +7 -2
  317. package/src/clis/chatgpt/read.ts +7 -2
  318. package/src/clis/chatgpt/send.ts +3 -2
  319. package/src/clis/chatgpt/status.ts +6 -1
  320. package/src/clis/chatwise/ask.ts +7 -2
  321. package/src/clis/chatwise/export.ts +2 -0
  322. package/src/clis/chatwise/history.ts +2 -0
  323. package/src/clis/chatwise/model.ts +7 -3
  324. package/src/clis/chatwise/new.ts +3 -20
  325. package/src/clis/chatwise/read.ts +2 -0
  326. package/src/clis/chatwise/screenshot.ts +3 -32
  327. package/src/clis/chatwise/send.ts +7 -2
  328. package/src/clis/chatwise/shared.ts +8 -0
  329. package/src/clis/chatwise/status.ts +3 -24
  330. package/src/clis/codex/ask.ts +5 -2
  331. package/src/clis/codex/dump.ts +2 -27
  332. package/src/clis/codex/new.ts +2 -28
  333. package/src/clis/codex/screenshot.ts +2 -32
  334. package/src/clis/codex/send.ts +5 -4
  335. package/src/clis/codex/status.ts +2 -24
  336. package/src/clis/cursor/ask.ts +2 -1
  337. package/src/clis/cursor/composer.ts +2 -1
  338. package/src/clis/cursor/dump.ts +2 -27
  339. package/src/clis/cursor/new.ts +2 -20
  340. package/src/clis/cursor/read.ts +2 -1
  341. package/src/clis/cursor/screenshot.ts +1 -36
  342. package/src/clis/cursor/send.ts +2 -1
  343. package/src/clis/cursor/status.ts +2 -22
  344. package/src/clis/dictionary/examples.yaml +25 -0
  345. package/src/clis/dictionary/search.yaml +27 -0
  346. package/src/clis/dictionary/synonyms.yaml +25 -0
  347. package/src/clis/douban/book-hot.ts +1 -1
  348. package/src/clis/douban/movie-hot.ts +1 -1
  349. package/src/clis/douban/search.ts +1 -1
  350. package/src/clis/douban/utils.ts +165 -1
  351. package/src/clis/doubao/ask.ts +1 -1
  352. package/src/clis/doubao/new.ts +1 -1
  353. package/src/clis/doubao/read.ts +1 -1
  354. package/src/clis/doubao/send.ts +1 -1
  355. package/src/clis/doubao/status.ts +1 -1
  356. package/src/clis/doubao-app/ask.ts +1 -1
  357. package/src/clis/doubao-app/new.ts +1 -1
  358. package/src/clis/doubao-app/read.ts +1 -1
  359. package/src/clis/doubao-app/send.ts +1 -1
  360. package/src/clis/grok/ask.test.ts +25 -0
  361. package/src/clis/grok/ask.ts +25 -12
  362. package/src/clis/jd/item.test.ts +35 -0
  363. package/src/clis/jd/item.ts +101 -0
  364. package/src/clis/jike/feed.ts +1 -1
  365. package/src/clis/jike/search.ts +1 -1
  366. package/src/clis/linkedin/search.ts +5 -4
  367. package/src/clis/linkedin/timeline.test.ts +99 -0
  368. package/src/clis/linkedin/timeline.ts +532 -0
  369. package/src/clis/medium/feed.ts +1 -1
  370. package/src/clis/medium/search.ts +1 -1
  371. package/src/clis/medium/user.ts +1 -1
  372. package/src/clis/medium/{shared.ts → utils.ts} +2 -1
  373. package/src/clis/pixiv/detail.yaml +49 -0
  374. package/src/clis/pixiv/download.test.ts +114 -0
  375. package/src/clis/pixiv/download.ts +91 -0
  376. package/src/clis/pixiv/illusts.test.ts +115 -0
  377. package/src/clis/pixiv/illusts.ts +78 -0
  378. package/src/clis/pixiv/ranking.yaml +53 -0
  379. package/src/clis/pixiv/search.test.ts +97 -0
  380. package/src/clis/pixiv/search.ts +53 -0
  381. package/src/clis/pixiv/test-utils.ts +29 -0
  382. package/src/clis/pixiv/user.yaml +46 -0
  383. package/src/clis/pixiv/utils.ts +62 -0
  384. package/src/clis/reddit/comment.ts +2 -1
  385. package/src/clis/reddit/read.test.ts +34 -0
  386. package/src/clis/reddit/read.ts +4 -3
  387. package/src/clis/reddit/save.ts +2 -1
  388. package/src/clis/reddit/saved.ts +6 -2
  389. package/src/clis/reddit/subscribe.ts +2 -1
  390. package/src/clis/reddit/upvote.ts +2 -1
  391. package/src/clis/reddit/upvoted.ts +6 -2
  392. package/src/clis/sinablog/article.ts +1 -1
  393. package/src/clis/sinablog/hot.ts +1 -1
  394. package/src/clis/sinablog/user.ts +1 -1
  395. package/src/clis/substack/feed.ts +1 -1
  396. package/src/clis/substack/publication.ts +1 -1
  397. package/src/clis/substack/search.ts +3 -2
  398. package/src/clis/substack/{shared.ts → utils.ts} +3 -2
  399. package/src/clis/tiktok/search.yaml +2 -1
  400. package/src/clis/twitter/accept.ts +2 -1
  401. package/src/clis/twitter/article.ts +3 -1
  402. package/src/clis/twitter/block.ts +2 -1
  403. package/src/clis/twitter/bookmark.ts +2 -1
  404. package/src/clis/twitter/bookmarks.ts +3 -2
  405. package/src/clis/twitter/delete.ts +2 -1
  406. package/src/clis/twitter/follow.ts +2 -1
  407. package/src/clis/twitter/followers.ts +3 -2
  408. package/src/clis/twitter/following.ts +3 -2
  409. package/src/clis/twitter/hide-reply.ts +2 -1
  410. package/src/clis/twitter/like.ts +2 -1
  411. package/src/clis/twitter/notifications.ts +2 -1
  412. package/src/clis/twitter/post.ts +2 -1
  413. package/src/clis/twitter/profile.ts +4 -2
  414. package/src/clis/twitter/reply-dm.ts +2 -1
  415. package/src/clis/twitter/reply.ts +2 -1
  416. package/src/clis/twitter/search.test.ts +113 -0
  417. package/src/clis/twitter/search.ts +38 -14
  418. package/src/clis/twitter/thread.ts +2 -2
  419. package/src/clis/twitter/timeline.ts +3 -2
  420. package/src/clis/twitter/trending.ts +3 -2
  421. package/src/clis/twitter/unblock.ts +2 -1
  422. package/src/clis/twitter/unbookmark.ts +2 -1
  423. package/src/clis/twitter/unfollow.ts +2 -1
  424. package/src/clis/v2ex/daily.ts +3 -2
  425. package/src/clis/v2ex/me.ts +3 -2
  426. package/src/clis/v2ex/notifications.ts +3 -4
  427. package/src/clis/web/read.ts +210 -0
  428. package/src/clis/xueqiu/danjuan-utils.test.ts +49 -0
  429. package/src/clis/xueqiu/danjuan-utils.ts +176 -0
  430. package/src/clis/xueqiu/fund-holdings.ts +32 -0
  431. package/src/clis/xueqiu/fund-snapshot.ts +27 -0
  432. package/src/clis/youtube/transcript.ts +5 -4
  433. package/src/clis/youtube/video.ts +3 -2
  434. package/src/daemon.ts +5 -4
  435. package/src/discovery.ts +12 -34
  436. package/src/doctor.ts +3 -2
  437. package/src/download/index.test.ts +93 -2
  438. package/src/download/index.ts +44 -23
  439. package/src/download/media-download.ts +5 -3
  440. package/src/engine.test.ts +84 -3
  441. package/src/execution.ts +62 -46
  442. package/src/explore.ts +21 -90
  443. package/src/external-clis.yaml +0 -8
  444. package/src/external.test.ts +9 -0
  445. package/src/external.ts +12 -10
  446. package/src/generate.ts +4 -41
  447. package/src/hooks.test.ts +126 -0
  448. package/src/hooks.ts +90 -0
  449. package/src/interceptor.ts +73 -23
  450. package/src/main.ts +2 -0
  451. package/src/output.ts +14 -6
  452. package/src/pipeline/executor.ts +1 -1
  453. package/src/pipeline/steps/browser.ts +1 -3
  454. package/src/pipeline/steps/download.test.ts +136 -0
  455. package/src/pipeline/steps/download.ts +47 -34
  456. package/src/pipeline/steps/fetch.test.ts +179 -0
  457. package/src/pipeline/steps/fetch.ts +39 -23
  458. package/src/pipeline/steps/transform.ts +2 -6
  459. package/src/pipeline/template.test.ts +28 -0
  460. package/src/pipeline/template.ts +67 -79
  461. package/src/pipeline/transform.test.ts +20 -0
  462. package/src/plugin.test.ts +251 -3
  463. package/src/plugin.ts +265 -21
  464. package/src/record.ts +12 -84
  465. package/src/registry-api.ts +2 -0
  466. package/src/registry.ts +7 -4
  467. package/src/runtime.ts +14 -4
  468. package/src/snapshotFormatter.ts +43 -121
  469. package/src/utils.ts +39 -0
  470. package/src/validate.ts +3 -5
  471. package/src/yaml-schema.ts +28 -0
  472. package/tests/e2e/browser-auth.test.ts +25 -0
  473. package/tests/e2e/plugin-management.test.ts +137 -0
  474. package/tests/e2e/public-commands.test.ts +34 -1
  475. package/vitest.config.ts +19 -1
  476. package/.github/workflows/pkg-pr-new.yml +0 -30
  477. package/dist/clis/douban/shared.d.ts +0 -4
  478. package/dist/clis/douban/shared.js +0 -155
  479. package/src/clis/douban/shared.ts +0 -165
  480. /package/dist/clis/boss/{common.d.ts → utils.d.ts} +0 -0
  481. /package/dist/clis/boss/{common.js → utils.js} +0 -0
  482. /package/dist/clis/doubao/{common.d.ts → utils.d.ts} +0 -0
  483. /package/dist/clis/doubao/{common.js → utils.js} +0 -0
  484. /package/dist/clis/doubao-app/{common.d.ts → utils.d.ts} +0 -0
  485. /package/dist/clis/doubao-app/{common.js → utils.js} +0 -0
  486. /package/dist/clis/jike/{shared.d.ts → utils.d.ts} +0 -0
  487. /package/dist/clis/jike/{shared.js → utils.js} +0 -0
  488. /package/dist/clis/medium/{shared.d.ts → utils.d.ts} +0 -0
  489. /package/dist/clis/sinablog/{shared.d.ts → utils.d.ts} +0 -0
  490. /package/dist/clis/sinablog/{shared.js → utils.js} +0 -0
  491. /package/dist/clis/substack/{shared.d.ts → utils.d.ts} +0 -0
  492. /package/src/clis/boss/{common.ts → utils.ts} +0 -0
  493. /package/src/clis/doubao/{common.ts → utils.ts} +0 -0
  494. /package/src/clis/doubao-app/{common.ts → utils.ts} +0 -0
  495. /package/src/clis/jike/{shared.ts → utils.ts} +0 -0
  496. /package/src/clis/sinablog/{shared.ts → utils.ts} +0 -0
@@ -1,507 +1,582 @@
1
- const DAEMON_PORT = 19825;
2
- const DAEMON_HOST = "localhost";
3
- const DAEMON_WS_URL = `ws://${DAEMON_HOST}:${DAEMON_PORT}/ext`;
4
- const WS_RECONNECT_BASE_DELAY = 2e3;
5
- const WS_RECONNECT_MAX_DELAY = 6e4;
6
-
7
- const attached = /* @__PURE__ */ new Set();
1
+ //#region src/protocol.ts
2
+ /** Default daemon port */
3
+ var DAEMON_PORT = 19825;
4
+ var DAEMON_HOST = "localhost";
5
+ var DAEMON_WS_URL = `ws://${DAEMON_HOST}:${DAEMON_PORT}/ext`;
6
+ `${DAEMON_HOST}${DAEMON_PORT}`;
7
+ /** Base reconnect delay for extension WebSocket (ms) */
8
+ var WS_RECONNECT_BASE_DELAY = 2e3;
9
+ /** Max reconnect delay (ms) */
10
+ var WS_RECONNECT_MAX_DELAY = 6e4;
11
+ //#endregion
12
+ //#region src/cdp.ts
13
+ /**
14
+ * CDP execution via chrome.debugger API.
15
+ *
16
+ * chrome.debugger only needs the "debugger" permission — no host_permissions.
17
+ * It can attach to any http/https tab. Avoid chrome:// and chrome-extension://
18
+ * tabs (resolveTabId in background.ts filters them).
19
+ */
20
+ var attached = /* @__PURE__ */ new Set();
21
+ /** Check if a URL can be attached via CDP */
8
22
  function isDebuggableUrl$1(url) {
9
- if (!url) return true;
10
- return !url.startsWith("chrome://") && !url.startsWith("chrome-extension://");
23
+ if (!url) return true;
24
+ return !url.startsWith("chrome://") && !url.startsWith("chrome-extension://");
11
25
  }
12
26
  async function ensureAttached(tabId) {
13
- try {
14
- const tab = await chrome.tabs.get(tabId);
15
- if (!isDebuggableUrl$1(tab.url)) {
16
- attached.delete(tabId);
17
- throw new Error(`Cannot debug tab ${tabId}: URL is ${tab.url ?? "unknown"}`);
18
- }
19
- } catch (e) {
20
- if (e instanceof Error && e.message.startsWith("Cannot debug tab")) throw e;
21
- attached.delete(tabId);
22
- throw new Error(`Tab ${tabId} no longer exists`);
23
- }
24
- if (attached.has(tabId)) {
25
- try {
26
- await chrome.debugger.sendCommand({ tabId }, "Runtime.evaluate", {
27
- expression: "1",
28
- returnByValue: true
29
- });
30
- return;
31
- } catch {
32
- attached.delete(tabId);
33
- }
34
- }
35
- try {
36
- await chrome.debugger.attach({ tabId }, "1.3");
37
- } catch (e) {
38
- const msg = e instanceof Error ? e.message : String(e);
39
- const hint = msg.includes("chrome-extension://") ? ". Tip: another Chrome extension may be interfering — try disabling other extensions" : "";
40
- if (msg.includes("Another debugger is already attached")) {
41
- try {
42
- await chrome.debugger.detach({ tabId });
43
- } catch {
44
- }
45
- try {
46
- await chrome.debugger.attach({ tabId }, "1.3");
47
- } catch {
48
- throw new Error(`attach failed: ${msg}${hint}`);
49
- }
50
- } else {
51
- throw new Error(`attach failed: ${msg}${hint}`);
52
- }
53
- }
54
- attached.add(tabId);
55
- try {
56
- await chrome.debugger.sendCommand({ tabId }, "Runtime.enable");
57
- } catch {
58
- }
27
+ try {
28
+ const tab = await chrome.tabs.get(tabId);
29
+ if (!isDebuggableUrl$1(tab.url)) {
30
+ attached.delete(tabId);
31
+ throw new Error(`Cannot debug tab ${tabId}: URL is ${tab.url ?? "unknown"}`);
32
+ }
33
+ } catch (e) {
34
+ if (e instanceof Error && e.message.startsWith("Cannot debug tab")) throw e;
35
+ attached.delete(tabId);
36
+ throw new Error(`Tab ${tabId} no longer exists`);
37
+ }
38
+ if (attached.has(tabId)) try {
39
+ await chrome.debugger.sendCommand({ tabId }, "Runtime.evaluate", {
40
+ expression: "1",
41
+ returnByValue: true
42
+ });
43
+ return;
44
+ } catch {
45
+ attached.delete(tabId);
46
+ }
47
+ try {
48
+ await chrome.debugger.attach({ tabId }, "1.3");
49
+ } catch (e) {
50
+ const msg = e instanceof Error ? e.message : String(e);
51
+ const hint = msg.includes("chrome-extension://") ? ". Tip: another Chrome extension may be interfering — try disabling other extensions" : "";
52
+ if (msg.includes("Another debugger is already attached")) {
53
+ try {
54
+ await chrome.debugger.detach({ tabId });
55
+ } catch {}
56
+ try {
57
+ await chrome.debugger.attach({ tabId }, "1.3");
58
+ } catch {
59
+ throw new Error(`attach failed: ${msg}${hint}`);
60
+ }
61
+ } else throw new Error(`attach failed: ${msg}${hint}`);
62
+ }
63
+ attached.add(tabId);
64
+ try {
65
+ await chrome.debugger.sendCommand({ tabId }, "Runtime.enable");
66
+ } catch {}
59
67
  }
60
68
  async function evaluate(tabId, expression) {
61
- await ensureAttached(tabId);
62
- const result = await chrome.debugger.sendCommand({ tabId }, "Runtime.evaluate", {
63
- expression,
64
- returnByValue: true,
65
- awaitPromise: true
66
- });
67
- if (result.exceptionDetails) {
68
- const errMsg = result.exceptionDetails.exception?.description || result.exceptionDetails.text || "Eval error";
69
- throw new Error(errMsg);
70
- }
71
- return result.result?.value;
69
+ await ensureAttached(tabId);
70
+ const result = await chrome.debugger.sendCommand({ tabId }, "Runtime.evaluate", {
71
+ expression,
72
+ returnByValue: true,
73
+ awaitPromise: true
74
+ });
75
+ if (result.exceptionDetails) {
76
+ const errMsg = result.exceptionDetails.exception?.description || result.exceptionDetails.text || "Eval error";
77
+ throw new Error(errMsg);
78
+ }
79
+ return result.result?.value;
72
80
  }
73
- const evaluateAsync = evaluate;
81
+ var evaluateAsync = evaluate;
82
+ /**
83
+ * Capture a screenshot via CDP Page.captureScreenshot.
84
+ * Returns base64-encoded image data.
85
+ */
74
86
  async function screenshot(tabId, options = {}) {
75
- await ensureAttached(tabId);
76
- const format = options.format ?? "png";
77
- if (options.fullPage) {
78
- const metrics = await chrome.debugger.sendCommand({ tabId }, "Page.getLayoutMetrics");
79
- const size = metrics.cssContentSize || metrics.contentSize;
80
- if (size) {
81
- await chrome.debugger.sendCommand({ tabId }, "Emulation.setDeviceMetricsOverride", {
82
- mobile: false,
83
- width: Math.ceil(size.width),
84
- height: Math.ceil(size.height),
85
- deviceScaleFactor: 1
86
- });
87
- }
88
- }
89
- try {
90
- const params = { format };
91
- if (format === "jpeg" && options.quality !== void 0) {
92
- params.quality = Math.max(0, Math.min(100, options.quality));
93
- }
94
- const result = await chrome.debugger.sendCommand({ tabId }, "Page.captureScreenshot", params);
95
- return result.data;
96
- } finally {
97
- if (options.fullPage) {
98
- await chrome.debugger.sendCommand({ tabId }, "Emulation.clearDeviceMetricsOverride").catch(() => {
99
- });
100
- }
101
- }
87
+ await ensureAttached(tabId);
88
+ const format = options.format ?? "png";
89
+ if (options.fullPage) {
90
+ const metrics = await chrome.debugger.sendCommand({ tabId }, "Page.getLayoutMetrics");
91
+ const size = metrics.cssContentSize || metrics.contentSize;
92
+ if (size) await chrome.debugger.sendCommand({ tabId }, "Emulation.setDeviceMetricsOverride", {
93
+ mobile: false,
94
+ width: Math.ceil(size.width),
95
+ height: Math.ceil(size.height),
96
+ deviceScaleFactor: 1
97
+ });
98
+ }
99
+ try {
100
+ const params = { format };
101
+ if (format === "jpeg" && options.quality !== void 0) params.quality = Math.max(0, Math.min(100, options.quality));
102
+ return (await chrome.debugger.sendCommand({ tabId }, "Page.captureScreenshot", params)).data;
103
+ } finally {
104
+ if (options.fullPage) await chrome.debugger.sendCommand({ tabId }, "Emulation.clearDeviceMetricsOverride").catch(() => {});
105
+ }
102
106
  }
103
- function detach(tabId) {
104
- if (!attached.has(tabId)) return;
105
- attached.delete(tabId);
106
- try {
107
- chrome.debugger.detach({ tabId });
108
- } catch {
109
- }
107
+ async function detach(tabId) {
108
+ if (!attached.has(tabId)) return;
109
+ attached.delete(tabId);
110
+ try {
111
+ await chrome.debugger.detach({ tabId });
112
+ } catch {}
110
113
  }
111
114
  function registerListeners() {
112
- chrome.tabs.onRemoved.addListener((tabId) => {
113
- attached.delete(tabId);
114
- });
115
- chrome.debugger.onDetach.addListener((source) => {
116
- if (source.tabId) attached.delete(source.tabId);
117
- });
118
- chrome.tabs.onUpdated.addListener((tabId, info) => {
119
- if (info.url && !isDebuggableUrl$1(info.url)) {
120
- if (attached.has(tabId)) {
121
- attached.delete(tabId);
122
- try {
123
- chrome.debugger.detach({ tabId });
124
- } catch {
125
- }
126
- }
127
- }
128
- });
115
+ chrome.tabs.onRemoved.addListener((tabId) => {
116
+ attached.delete(tabId);
117
+ });
118
+ chrome.debugger.onDetach.addListener((source) => {
119
+ if (source.tabId) attached.delete(source.tabId);
120
+ });
121
+ chrome.tabs.onUpdated.addListener(async (tabId, info) => {
122
+ if (info.url && !isDebuggableUrl$1(info.url)) await detach(tabId);
123
+ });
129
124
  }
130
-
131
- let ws = null;
132
- let reconnectTimer = null;
133
- let reconnectAttempts = 0;
134
- const _origLog = console.log.bind(console);
135
- const _origWarn = console.warn.bind(console);
136
- const _origError = console.error.bind(console);
125
+ //#endregion
126
+ //#region src/background.ts
127
+ var ws = null;
128
+ var reconnectTimer = null;
129
+ var reconnectAttempts = 0;
130
+ var _origLog = console.log.bind(console);
131
+ var _origWarn = console.warn.bind(console);
132
+ var _origError = console.error.bind(console);
137
133
  function forwardLog(level, args) {
138
- if (!ws || ws.readyState !== WebSocket.OPEN) return;
139
- try {
140
- const msg = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
141
- ws.send(JSON.stringify({ type: "log", level, msg, ts: Date.now() }));
142
- } catch {
143
- }
134
+ if (!ws || ws.readyState !== WebSocket.OPEN) return;
135
+ try {
136
+ const msg = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
137
+ ws.send(JSON.stringify({
138
+ type: "log",
139
+ level,
140
+ msg,
141
+ ts: Date.now()
142
+ }));
143
+ } catch {}
144
144
  }
145
145
  console.log = (...args) => {
146
- _origLog(...args);
147
- forwardLog("info", args);
146
+ _origLog(...args);
147
+ forwardLog("info", args);
148
148
  };
149
149
  console.warn = (...args) => {
150
- _origWarn(...args);
151
- forwardLog("warn", args);
150
+ _origWarn(...args);
151
+ forwardLog("warn", args);
152
152
  };
153
153
  console.error = (...args) => {
154
- _origError(...args);
155
- forwardLog("error", args);
154
+ _origError(...args);
155
+ forwardLog("error", args);
156
156
  };
157
157
  function connect() {
158
- if (ws?.readyState === WebSocket.OPEN || ws?.readyState === WebSocket.CONNECTING) return;
159
- try {
160
- ws = new WebSocket(DAEMON_WS_URL);
161
- } catch {
162
- scheduleReconnect();
163
- return;
164
- }
165
- ws.onopen = () => {
166
- console.log("[opencli] Connected to daemon");
167
- reconnectAttempts = 0;
168
- if (reconnectTimer) {
169
- clearTimeout(reconnectTimer);
170
- reconnectTimer = null;
171
- }
172
- };
173
- ws.onmessage = async (event) => {
174
- try {
175
- const command = JSON.parse(event.data);
176
- const result = await handleCommand(command);
177
- ws?.send(JSON.stringify(result));
178
- } catch (err) {
179
- console.error("[opencli] Message handling error:", err);
180
- }
181
- };
182
- ws.onclose = () => {
183
- console.log("[opencli] Disconnected from daemon");
184
- ws = null;
185
- scheduleReconnect();
186
- };
187
- ws.onerror = () => {
188
- ws?.close();
189
- };
158
+ if (ws?.readyState === WebSocket.OPEN || ws?.readyState === WebSocket.CONNECTING) return;
159
+ try {
160
+ ws = new WebSocket(DAEMON_WS_URL);
161
+ } catch {
162
+ scheduleReconnect();
163
+ return;
164
+ }
165
+ ws.onopen = () => {
166
+ console.log("[opencli] Connected to daemon");
167
+ reconnectAttempts = 0;
168
+ if (reconnectTimer) {
169
+ clearTimeout(reconnectTimer);
170
+ reconnectTimer = null;
171
+ }
172
+ };
173
+ ws.onmessage = async (event) => {
174
+ try {
175
+ const result = await handleCommand(JSON.parse(event.data));
176
+ ws?.send(JSON.stringify(result));
177
+ } catch (err) {
178
+ console.error("[opencli] Message handling error:", err);
179
+ }
180
+ };
181
+ ws.onclose = () => {
182
+ console.log("[opencli] Disconnected from daemon");
183
+ ws = null;
184
+ scheduleReconnect();
185
+ };
186
+ ws.onerror = () => {
187
+ ws?.close();
188
+ };
190
189
  }
191
190
  function scheduleReconnect() {
192
- if (reconnectTimer) return;
193
- reconnectAttempts++;
194
- const delay = Math.min(WS_RECONNECT_BASE_DELAY * Math.pow(2, reconnectAttempts - 1), WS_RECONNECT_MAX_DELAY);
195
- reconnectTimer = setTimeout(() => {
196
- reconnectTimer = null;
197
- connect();
198
- }, delay);
191
+ if (reconnectTimer) return;
192
+ reconnectAttempts++;
193
+ const delay = Math.min(WS_RECONNECT_BASE_DELAY * Math.pow(2, reconnectAttempts - 1), WS_RECONNECT_MAX_DELAY);
194
+ reconnectTimer = setTimeout(() => {
195
+ reconnectTimer = null;
196
+ connect();
197
+ }, delay);
199
198
  }
200
- const automationSessions = /* @__PURE__ */ new Map();
201
- const WINDOW_IDLE_TIMEOUT = 12e4;
199
+ var automationSessions = /* @__PURE__ */ new Map();
200
+ var WINDOW_IDLE_TIMEOUT = 12e4;
202
201
  function getWorkspaceKey(workspace) {
203
- return workspace?.trim() || "default";
202
+ return workspace?.trim() || "default";
204
203
  }
205
204
  function resetWindowIdleTimer(workspace) {
206
- const session = automationSessions.get(workspace);
207
- if (!session) return;
208
- if (session.idleTimer) clearTimeout(session.idleTimer);
209
- session.idleDeadlineAt = Date.now() + WINDOW_IDLE_TIMEOUT;
210
- session.idleTimer = setTimeout(async () => {
211
- const current = automationSessions.get(workspace);
212
- if (!current) return;
213
- try {
214
- await chrome.windows.remove(current.windowId);
215
- console.log(`[opencli] Automation window ${current.windowId} (${workspace}) closed (idle timeout)`);
216
- } catch {
217
- }
218
- automationSessions.delete(workspace);
219
- }, WINDOW_IDLE_TIMEOUT);
205
+ const session = automationSessions.get(workspace);
206
+ if (!session) return;
207
+ if (session.idleTimer) clearTimeout(session.idleTimer);
208
+ session.idleDeadlineAt = Date.now() + WINDOW_IDLE_TIMEOUT;
209
+ session.idleTimer = setTimeout(async () => {
210
+ const current = automationSessions.get(workspace);
211
+ if (!current) return;
212
+ try {
213
+ await chrome.windows.remove(current.windowId);
214
+ console.log(`[opencli] Automation window ${current.windowId} (${workspace}) closed (idle timeout)`);
215
+ } catch {}
216
+ automationSessions.delete(workspace);
217
+ }, WINDOW_IDLE_TIMEOUT);
220
218
  }
219
+ /** Get or create the dedicated automation window. */
221
220
  async function getAutomationWindow(workspace) {
222
- const existing = automationSessions.get(workspace);
223
- if (existing) {
224
- try {
225
- await chrome.windows.get(existing.windowId);
226
- return existing.windowId;
227
- } catch {
228
- automationSessions.delete(workspace);
229
- }
230
- }
231
- const win = await chrome.windows.create({
232
- url: "data:text/html,<html></html>",
233
- focused: false,
234
- width: 1280,
235
- height: 900,
236
- type: "normal"
237
- });
238
- const session = {
239
- windowId: win.id,
240
- idleTimer: null,
241
- idleDeadlineAt: Date.now() + WINDOW_IDLE_TIMEOUT
242
- };
243
- automationSessions.set(workspace, session);
244
- console.log(`[opencli] Created automation window ${session.windowId} (${workspace})`);
245
- resetWindowIdleTimer(workspace);
246
- await new Promise((resolve) => setTimeout(resolve, 200));
247
- return session.windowId;
221
+ const existing = automationSessions.get(workspace);
222
+ if (existing) try {
223
+ await chrome.windows.get(existing.windowId);
224
+ return existing.windowId;
225
+ } catch {
226
+ automationSessions.delete(workspace);
227
+ }
228
+ const session = {
229
+ windowId: (await chrome.windows.create({
230
+ url: "data:text/html,<html></html>",
231
+ focused: false,
232
+ width: 1280,
233
+ height: 900,
234
+ type: "normal"
235
+ })).id,
236
+ idleTimer: null,
237
+ idleDeadlineAt: Date.now() + WINDOW_IDLE_TIMEOUT
238
+ };
239
+ automationSessions.set(workspace, session);
240
+ console.log(`[opencli] Created automation window ${session.windowId} (${workspace})`);
241
+ resetWindowIdleTimer(workspace);
242
+ await new Promise((resolve) => setTimeout(resolve, 200));
243
+ return session.windowId;
248
244
  }
249
245
  chrome.windows.onRemoved.addListener((windowId) => {
250
- for (const [workspace, session] of automationSessions.entries()) {
251
- if (session.windowId === windowId) {
252
- console.log(`[opencli] Automation window closed (${workspace})`);
253
- if (session.idleTimer) clearTimeout(session.idleTimer);
254
- automationSessions.delete(workspace);
255
- }
256
- }
246
+ for (const [workspace, session] of automationSessions.entries()) if (session.windowId === windowId) {
247
+ console.log(`[opencli] Automation window closed (${workspace})`);
248
+ if (session.idleTimer) clearTimeout(session.idleTimer);
249
+ automationSessions.delete(workspace);
250
+ }
257
251
  });
258
- let initialized = false;
252
+ var initialized = false;
259
253
  function initialize() {
260
- if (initialized) return;
261
- initialized = true;
262
- chrome.alarms.create("keepalive", { periodInMinutes: 0.4 });
263
- registerListeners();
264
- connect();
265
- console.log("[opencli] OpenCLI extension initialized");
254
+ if (initialized) return;
255
+ initialized = true;
256
+ chrome.alarms.create("keepalive", { periodInMinutes: .4 });
257
+ registerListeners();
258
+ connect();
259
+ console.log("[opencli] OpenCLI extension initialized");
266
260
  }
267
261
  chrome.runtime.onInstalled.addListener(() => {
268
- initialize();
262
+ initialize();
269
263
  });
270
264
  chrome.runtime.onStartup.addListener(() => {
271
- initialize();
265
+ initialize();
272
266
  });
273
267
  chrome.alarms.onAlarm.addListener((alarm) => {
274
- if (alarm.name === "keepalive") connect();
268
+ if (alarm.name === "keepalive") connect();
275
269
  });
276
270
  async function handleCommand(cmd) {
277
- const workspace = getWorkspaceKey(cmd.workspace);
278
- resetWindowIdleTimer(workspace);
279
- try {
280
- switch (cmd.action) {
281
- case "exec":
282
- return await handleExec(cmd, workspace);
283
- case "navigate":
284
- return await handleNavigate(cmd, workspace);
285
- case "tabs":
286
- return await handleTabs(cmd, workspace);
287
- case "cookies":
288
- return await handleCookies(cmd);
289
- case "screenshot":
290
- return await handleScreenshot(cmd, workspace);
291
- case "close-window":
292
- return await handleCloseWindow(cmd, workspace);
293
- case "sessions":
294
- return await handleSessions(cmd);
295
- default:
296
- return { id: cmd.id, ok: false, error: `Unknown action: ${cmd.action}` };
297
- }
298
- } catch (err) {
299
- return {
300
- id: cmd.id,
301
- ok: false,
302
- error: err instanceof Error ? err.message : String(err)
303
- };
304
- }
271
+ const workspace = getWorkspaceKey(cmd.workspace);
272
+ resetWindowIdleTimer(workspace);
273
+ try {
274
+ switch (cmd.action) {
275
+ case "exec": return await handleExec(cmd, workspace);
276
+ case "navigate": return await handleNavigate(cmd, workspace);
277
+ case "tabs": return await handleTabs(cmd, workspace);
278
+ case "cookies": return await handleCookies(cmd);
279
+ case "screenshot": return await handleScreenshot(cmd, workspace);
280
+ case "close-window": return await handleCloseWindow(cmd, workspace);
281
+ case "sessions": return await handleSessions(cmd);
282
+ default: return {
283
+ id: cmd.id,
284
+ ok: false,
285
+ error: `Unknown action: ${cmd.action}`
286
+ };
287
+ }
288
+ } catch (err) {
289
+ return {
290
+ id: cmd.id,
291
+ ok: false,
292
+ error: err instanceof Error ? err.message : String(err)
293
+ };
294
+ }
305
295
  }
296
+ /** Check if a URL can be attached via CDP (not chrome:// or chrome-extension://) */
306
297
  function isDebuggableUrl(url) {
307
- if (!url) return true;
308
- return !url.startsWith("chrome://") && !url.startsWith("chrome-extension://");
298
+ if (!url) return true;
299
+ return !url.startsWith("chrome://") && !url.startsWith("chrome-extension://");
309
300
  }
301
+ /**
302
+ * Resolve target tab in the automation window.
303
+ * If explicit tabId is given, use that directly.
304
+ * Otherwise, find or create a tab in the dedicated automation window.
305
+ */
310
306
  async function resolveTabId(tabId, workspace) {
311
- if (tabId !== void 0) {
312
- try {
313
- const tab = await chrome.tabs.get(tabId);
314
- if (isDebuggableUrl(tab.url)) return tabId;
315
- console.warn(`[opencli] Tab ${tabId} URL is not debuggable (${tab.url}), re-resolving`);
316
- } catch {
317
- console.warn(`[opencli] Tab ${tabId} no longer exists, re-resolving`);
318
- }
319
- }
320
- const windowId = await getAutomationWindow(workspace);
321
- const tabs = await chrome.tabs.query({ windowId });
322
- const debuggableTab = tabs.find((t) => t.id && isDebuggableUrl(t.url));
323
- if (debuggableTab?.id) return debuggableTab.id;
324
- const reuseTab = tabs.find((t) => t.id);
325
- if (reuseTab?.id) {
326
- await chrome.tabs.update(reuseTab.id, { url: "data:text/html,<html></html>" });
327
- await new Promise((resolve) => setTimeout(resolve, 300));
328
- try {
329
- const updated = await chrome.tabs.get(reuseTab.id);
330
- if (isDebuggableUrl(updated.url)) return reuseTab.id;
331
- console.warn(`[opencli] data: URI was intercepted (${updated.url}), creating fresh tab`);
332
- } catch {
333
- }
334
- }
335
- const newTab = await chrome.tabs.create({ windowId, url: "data:text/html,<html></html>", active: true });
336
- if (!newTab.id) throw new Error("Failed to create tab in automation window");
337
- return newTab.id;
307
+ if (tabId !== void 0) try {
308
+ const tab = await chrome.tabs.get(tabId);
309
+ if (isDebuggableUrl(tab.url)) return tabId;
310
+ console.warn(`[opencli] Tab ${tabId} URL is not debuggable (${tab.url}), re-resolving`);
311
+ } catch {
312
+ console.warn(`[opencli] Tab ${tabId} no longer exists, re-resolving`);
313
+ }
314
+ const windowId = await getAutomationWindow(workspace);
315
+ const tabs = await chrome.tabs.query({ windowId });
316
+ const debuggableTab = tabs.find((t) => t.id && isDebuggableUrl(t.url));
317
+ if (debuggableTab?.id) return debuggableTab.id;
318
+ const reuseTab = tabs.find((t) => t.id);
319
+ if (reuseTab?.id) {
320
+ await chrome.tabs.update(reuseTab.id, { url: "data:text/html,<html></html>" });
321
+ await new Promise((resolve) => setTimeout(resolve, 300));
322
+ try {
323
+ const updated = await chrome.tabs.get(reuseTab.id);
324
+ if (isDebuggableUrl(updated.url)) return reuseTab.id;
325
+ console.warn(`[opencli] data: URI was intercepted (${updated.url}), creating fresh tab`);
326
+ } catch {}
327
+ }
328
+ const newTab = await chrome.tabs.create({
329
+ windowId,
330
+ url: "data:text/html,<html></html>",
331
+ active: true
332
+ });
333
+ if (!newTab.id) throw new Error("Failed to create tab in automation window");
334
+ return newTab.id;
338
335
  }
339
336
  async function listAutomationTabs(workspace) {
340
- const session = automationSessions.get(workspace);
341
- if (!session) return [];
342
- try {
343
- return await chrome.tabs.query({ windowId: session.windowId });
344
- } catch {
345
- automationSessions.delete(workspace);
346
- return [];
347
- }
337
+ const session = automationSessions.get(workspace);
338
+ if (!session) return [];
339
+ try {
340
+ return await chrome.tabs.query({ windowId: session.windowId });
341
+ } catch {
342
+ automationSessions.delete(workspace);
343
+ return [];
344
+ }
348
345
  }
349
346
  async function listAutomationWebTabs(workspace) {
350
- const tabs = await listAutomationTabs(workspace);
351
- return tabs.filter((tab) => isDebuggableUrl(tab.url));
347
+ return (await listAutomationTabs(workspace)).filter((tab) => isDebuggableUrl(tab.url));
352
348
  }
353
349
  async function handleExec(cmd, workspace) {
354
- if (!cmd.code) return { id: cmd.id, ok: false, error: "Missing code" };
355
- const tabId = await resolveTabId(cmd.tabId, workspace);
356
- try {
357
- const data = await evaluateAsync(tabId, cmd.code);
358
- return { id: cmd.id, ok: true, data };
359
- } catch (err) {
360
- return { id: cmd.id, ok: false, error: err instanceof Error ? err.message : String(err) };
361
- }
350
+ if (!cmd.code) return {
351
+ id: cmd.id,
352
+ ok: false,
353
+ error: "Missing code"
354
+ };
355
+ const tabId = await resolveTabId(cmd.tabId, workspace);
356
+ try {
357
+ const data = await evaluateAsync(tabId, cmd.code);
358
+ return {
359
+ id: cmd.id,
360
+ ok: true,
361
+ data
362
+ };
363
+ } catch (err) {
364
+ return {
365
+ id: cmd.id,
366
+ ok: false,
367
+ error: err instanceof Error ? err.message : String(err)
368
+ };
369
+ }
362
370
  }
363
371
  async function handleNavigate(cmd, workspace) {
364
- if (!cmd.url) return { id: cmd.id, ok: false, error: "Missing url" };
365
- const tabId = await resolveTabId(cmd.tabId, workspace);
366
- const beforeTab = await chrome.tabs.get(tabId);
367
- const beforeUrl = beforeTab.url ?? "";
368
- const targetUrl = cmd.url;
369
- await chrome.tabs.update(tabId, { url: targetUrl });
370
- let timedOut = false;
371
- await new Promise((resolve) => {
372
- let urlChanged = false;
373
- const listener = (id, info, tab2) => {
374
- if (id !== tabId) return;
375
- if (info.url && info.url !== beforeUrl) {
376
- urlChanged = true;
377
- }
378
- if (urlChanged && info.status === "complete") {
379
- chrome.tabs.onUpdated.removeListener(listener);
380
- resolve();
381
- }
382
- };
383
- chrome.tabs.onUpdated.addListener(listener);
384
- setTimeout(async () => {
385
- try {
386
- const currentTab = await chrome.tabs.get(tabId);
387
- if (currentTab.url !== beforeUrl && currentTab.status === "complete") {
388
- chrome.tabs.onUpdated.removeListener(listener);
389
- resolve();
390
- }
391
- } catch {
392
- }
393
- }, 100);
394
- setTimeout(() => {
395
- chrome.tabs.onUpdated.removeListener(listener);
396
- timedOut = true;
397
- console.warn(`[opencli] Navigate to ${targetUrl} timed out after 15s`);
398
- resolve();
399
- }, 15e3);
400
- });
401
- const tab = await chrome.tabs.get(tabId);
402
- return {
403
- id: cmd.id,
404
- ok: true,
405
- data: { title: tab.title, url: tab.url, tabId, timedOut }
406
- };
372
+ if (!cmd.url) return {
373
+ id: cmd.id,
374
+ ok: false,
375
+ error: "Missing url"
376
+ };
377
+ const tabId = await resolveTabId(cmd.tabId, workspace);
378
+ const beforeUrl = (await chrome.tabs.get(tabId)).url ?? "";
379
+ const targetUrl = cmd.url;
380
+ await detach(tabId);
381
+ await chrome.tabs.update(tabId, { url: targetUrl });
382
+ let timedOut = false;
383
+ await new Promise((resolve) => {
384
+ let urlChanged = false;
385
+ const listener = (id, info, tab) => {
386
+ if (id !== tabId) return;
387
+ if (info.url && info.url !== beforeUrl) urlChanged = true;
388
+ if (urlChanged && info.status === "complete") {
389
+ chrome.tabs.onUpdated.removeListener(listener);
390
+ resolve();
391
+ }
392
+ };
393
+ chrome.tabs.onUpdated.addListener(listener);
394
+ setTimeout(async () => {
395
+ try {
396
+ const currentTab = await chrome.tabs.get(tabId);
397
+ if (currentTab.url !== beforeUrl && currentTab.status === "complete") {
398
+ chrome.tabs.onUpdated.removeListener(listener);
399
+ resolve();
400
+ }
401
+ } catch {}
402
+ }, 100);
403
+ setTimeout(() => {
404
+ chrome.tabs.onUpdated.removeListener(listener);
405
+ timedOut = true;
406
+ console.warn(`[opencli] Navigate to ${targetUrl} timed out after 15s`);
407
+ resolve();
408
+ }, 15e3);
409
+ });
410
+ const tab = await chrome.tabs.get(tabId);
411
+ return {
412
+ id: cmd.id,
413
+ ok: true,
414
+ data: {
415
+ title: tab.title,
416
+ url: tab.url,
417
+ tabId,
418
+ timedOut
419
+ }
420
+ };
407
421
  }
408
422
  async function handleTabs(cmd, workspace) {
409
- switch (cmd.op) {
410
- case "list": {
411
- const tabs = await listAutomationWebTabs(workspace);
412
- const data = tabs.map((t, i) => ({
413
- index: i,
414
- tabId: t.id,
415
- url: t.url,
416
- title: t.title,
417
- active: t.active
418
- }));
419
- return { id: cmd.id, ok: true, data };
420
- }
421
- case "new": {
422
- const windowId = await getAutomationWindow(workspace);
423
- const tab = await chrome.tabs.create({ windowId, url: cmd.url ?? "data:text/html,<html></html>", active: true });
424
- return { id: cmd.id, ok: true, data: { tabId: tab.id, url: tab.url } };
425
- }
426
- case "close": {
427
- if (cmd.index !== void 0) {
428
- const tabs = await listAutomationWebTabs(workspace);
429
- const target = tabs[cmd.index];
430
- if (!target?.id) return { id: cmd.id, ok: false, error: `Tab index ${cmd.index} not found` };
431
- await chrome.tabs.remove(target.id);
432
- detach(target.id);
433
- return { id: cmd.id, ok: true, data: { closed: target.id } };
434
- }
435
- const tabId = await resolveTabId(cmd.tabId, workspace);
436
- await chrome.tabs.remove(tabId);
437
- detach(tabId);
438
- return { id: cmd.id, ok: true, data: { closed: tabId } };
439
- }
440
- case "select": {
441
- if (cmd.index === void 0 && cmd.tabId === void 0)
442
- return { id: cmd.id, ok: false, error: "Missing index or tabId" };
443
- if (cmd.tabId !== void 0) {
444
- await chrome.tabs.update(cmd.tabId, { active: true });
445
- return { id: cmd.id, ok: true, data: { selected: cmd.tabId } };
446
- }
447
- const tabs = await listAutomationWebTabs(workspace);
448
- const target = tabs[cmd.index];
449
- if (!target?.id) return { id: cmd.id, ok: false, error: `Tab index ${cmd.index} not found` };
450
- await chrome.tabs.update(target.id, { active: true });
451
- return { id: cmd.id, ok: true, data: { selected: target.id } };
452
- }
453
- default:
454
- return { id: cmd.id, ok: false, error: `Unknown tabs op: ${cmd.op}` };
455
- }
423
+ switch (cmd.op) {
424
+ case "list": {
425
+ const data = (await listAutomationWebTabs(workspace)).map((t, i) => ({
426
+ index: i,
427
+ tabId: t.id,
428
+ url: t.url,
429
+ title: t.title,
430
+ active: t.active
431
+ }));
432
+ return {
433
+ id: cmd.id,
434
+ ok: true,
435
+ data
436
+ };
437
+ }
438
+ case "new": {
439
+ const windowId = await getAutomationWindow(workspace);
440
+ const tab = await chrome.tabs.create({
441
+ windowId,
442
+ url: cmd.url ?? "data:text/html,<html></html>",
443
+ active: true
444
+ });
445
+ return {
446
+ id: cmd.id,
447
+ ok: true,
448
+ data: {
449
+ tabId: tab.id,
450
+ url: tab.url
451
+ }
452
+ };
453
+ }
454
+ case "close": {
455
+ if (cmd.index !== void 0) {
456
+ const target = (await listAutomationWebTabs(workspace))[cmd.index];
457
+ if (!target?.id) return {
458
+ id: cmd.id,
459
+ ok: false,
460
+ error: `Tab index ${cmd.index} not found`
461
+ };
462
+ await chrome.tabs.remove(target.id);
463
+ await detach(target.id);
464
+ return {
465
+ id: cmd.id,
466
+ ok: true,
467
+ data: { closed: target.id }
468
+ };
469
+ }
470
+ const tabId = await resolveTabId(cmd.tabId, workspace);
471
+ await chrome.tabs.remove(tabId);
472
+ await detach(tabId);
473
+ return {
474
+ id: cmd.id,
475
+ ok: true,
476
+ data: { closed: tabId }
477
+ };
478
+ }
479
+ case "select": {
480
+ if (cmd.index === void 0 && cmd.tabId === void 0) return {
481
+ id: cmd.id,
482
+ ok: false,
483
+ error: "Missing index or tabId"
484
+ };
485
+ if (cmd.tabId !== void 0) {
486
+ await chrome.tabs.update(cmd.tabId, { active: true });
487
+ return {
488
+ id: cmd.id,
489
+ ok: true,
490
+ data: { selected: cmd.tabId }
491
+ };
492
+ }
493
+ const target = (await listAutomationWebTabs(workspace))[cmd.index];
494
+ if (!target?.id) return {
495
+ id: cmd.id,
496
+ ok: false,
497
+ error: `Tab index ${cmd.index} not found`
498
+ };
499
+ await chrome.tabs.update(target.id, { active: true });
500
+ return {
501
+ id: cmd.id,
502
+ ok: true,
503
+ data: { selected: target.id }
504
+ };
505
+ }
506
+ default: return {
507
+ id: cmd.id,
508
+ ok: false,
509
+ error: `Unknown tabs op: ${cmd.op}`
510
+ };
511
+ }
456
512
  }
457
513
  async function handleCookies(cmd) {
458
- const details = {};
459
- if (cmd.domain) details.domain = cmd.domain;
460
- if (cmd.url) details.url = cmd.url;
461
- const cookies = await chrome.cookies.getAll(details);
462
- const data = cookies.map((c) => ({
463
- name: c.name,
464
- value: c.value,
465
- domain: c.domain,
466
- path: c.path,
467
- secure: c.secure,
468
- httpOnly: c.httpOnly,
469
- expirationDate: c.expirationDate
470
- }));
471
- return { id: cmd.id, ok: true, data };
514
+ const details = {};
515
+ if (cmd.domain) details.domain = cmd.domain;
516
+ if (cmd.url) details.url = cmd.url;
517
+ const data = (await chrome.cookies.getAll(details)).map((c) => ({
518
+ name: c.name,
519
+ value: c.value,
520
+ domain: c.domain,
521
+ path: c.path,
522
+ secure: c.secure,
523
+ httpOnly: c.httpOnly,
524
+ expirationDate: c.expirationDate
525
+ }));
526
+ return {
527
+ id: cmd.id,
528
+ ok: true,
529
+ data
530
+ };
472
531
  }
473
532
  async function handleScreenshot(cmd, workspace) {
474
- const tabId = await resolveTabId(cmd.tabId, workspace);
475
- try {
476
- const data = await screenshot(tabId, {
477
- format: cmd.format,
478
- quality: cmd.quality,
479
- fullPage: cmd.fullPage
480
- });
481
- return { id: cmd.id, ok: true, data };
482
- } catch (err) {
483
- return { id: cmd.id, ok: false, error: err instanceof Error ? err.message : String(err) };
484
- }
533
+ const tabId = await resolveTabId(cmd.tabId, workspace);
534
+ try {
535
+ const data = await screenshot(tabId, {
536
+ format: cmd.format,
537
+ quality: cmd.quality,
538
+ fullPage: cmd.fullPage
539
+ });
540
+ return {
541
+ id: cmd.id,
542
+ ok: true,
543
+ data
544
+ };
545
+ } catch (err) {
546
+ return {
547
+ id: cmd.id,
548
+ ok: false,
549
+ error: err instanceof Error ? err.message : String(err)
550
+ };
551
+ }
485
552
  }
486
553
  async function handleCloseWindow(cmd, workspace) {
487
- const session = automationSessions.get(workspace);
488
- if (session) {
489
- try {
490
- await chrome.windows.remove(session.windowId);
491
- } catch {
492
- }
493
- if (session.idleTimer) clearTimeout(session.idleTimer);
494
- automationSessions.delete(workspace);
495
- }
496
- return { id: cmd.id, ok: true, data: { closed: true } };
554
+ const session = automationSessions.get(workspace);
555
+ if (session) {
556
+ try {
557
+ await chrome.windows.remove(session.windowId);
558
+ } catch {}
559
+ if (session.idleTimer) clearTimeout(session.idleTimer);
560
+ automationSessions.delete(workspace);
561
+ }
562
+ return {
563
+ id: cmd.id,
564
+ ok: true,
565
+ data: { closed: true }
566
+ };
497
567
  }
498
568
  async function handleSessions(cmd) {
499
- const now = Date.now();
500
- const data = await Promise.all([...automationSessions.entries()].map(async ([workspace, session]) => ({
501
- workspace,
502
- windowId: session.windowId,
503
- tabCount: (await chrome.tabs.query({ windowId: session.windowId })).filter((tab) => isDebuggableUrl(tab.url)).length,
504
- idleMsRemaining: Math.max(0, session.idleDeadlineAt - now)
505
- })));
506
- return { id: cmd.id, ok: true, data };
569
+ const now = Date.now();
570
+ const data = await Promise.all([...automationSessions.entries()].map(async ([workspace, session]) => ({
571
+ workspace,
572
+ windowId: session.windowId,
573
+ tabCount: (await chrome.tabs.query({ windowId: session.windowId })).filter((tab) => isDebuggableUrl(tab.url)).length,
574
+ idleMsRemaining: Math.max(0, session.idleDeadlineAt - now)
575
+ })));
576
+ return {
577
+ id: cmd.id,
578
+ ok: true,
579
+ data
580
+ };
507
581
  }
582
+ //#endregion