@jackwener/opencli 1.4.1 → 1.5.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 (369) hide show
  1. package/.github/workflows/build-extension.yml +2 -6
  2. package/.github/workflows/ci.yml +21 -1
  3. package/README.md +35 -6
  4. package/README.zh-CN.md +12 -5
  5. package/SKILL.md +2 -0
  6. package/dist/browser/cdp.d.ts +2 -1
  7. package/dist/browser/cdp.js +5 -0
  8. package/dist/browser/discover.d.ts +4 -1
  9. package/dist/browser/discover.js +6 -2
  10. package/dist/browser/errors.d.ts +2 -2
  11. package/dist/browser/errors.js +4 -12
  12. package/dist/browser/mcp.d.ts +2 -1
  13. package/dist/browser/page.d.ts +3 -0
  14. package/dist/browser/page.js +24 -1
  15. package/dist/build-manifest.d.ts +2 -0
  16. package/dist/build-manifest.js +39 -14
  17. package/dist/build-manifest.test.js +21 -0
  18. package/dist/capabilityRouting.d.ts +2 -0
  19. package/dist/capabilityRouting.js +2 -1
  20. package/dist/cli-manifest.json +1567 -108
  21. package/dist/cli.js +68 -6
  22. package/dist/clis/36kr/article.d.ts +1 -0
  23. package/dist/clis/36kr/article.js +62 -0
  24. package/dist/clis/36kr/hot.d.ts +3 -0
  25. package/dist/clis/36kr/hot.js +80 -0
  26. package/dist/clis/36kr/hot.test.d.ts +1 -0
  27. package/dist/clis/36kr/hot.test.js +15 -0
  28. package/dist/clis/36kr/news.d.ts +1 -0
  29. package/dist/clis/36kr/news.js +51 -0
  30. package/dist/clis/36kr/news.test.d.ts +1 -0
  31. package/dist/clis/36kr/news.test.js +85 -0
  32. package/dist/clis/36kr/search.d.ts +1 -0
  33. package/dist/clis/36kr/search.js +72 -0
  34. package/dist/clis/bilibili/comments.d.ts +5 -0
  35. package/dist/clis/bilibili/comments.js +40 -0
  36. package/dist/clis/bilibili/comments.test.d.ts +1 -0
  37. package/dist/clis/bilibili/comments.test.js +82 -0
  38. package/dist/clis/bluesky/feeds.yaml +29 -0
  39. package/dist/clis/bluesky/followers.yaml +33 -0
  40. package/dist/clis/bluesky/following.yaml +33 -0
  41. package/dist/clis/bluesky/profile.yaml +27 -0
  42. package/dist/clis/bluesky/search.yaml +34 -0
  43. package/dist/clis/bluesky/starter-packs.yaml +34 -0
  44. package/dist/clis/bluesky/thread.yaml +32 -0
  45. package/dist/clis/bluesky/trending.yaml +27 -0
  46. package/dist/clis/bluesky/user.yaml +34 -0
  47. package/dist/clis/chatgpt/ask.js +29 -14
  48. package/dist/clis/chatgpt/ax.d.ts +6 -0
  49. package/dist/clis/chatgpt/ax.js +172 -1
  50. package/dist/clis/chatgpt/model.d.ts +1 -0
  51. package/dist/clis/chatgpt/model.js +24 -0
  52. package/dist/clis/chatgpt/send.js +12 -3
  53. package/dist/clis/douban/download.d.ts +1 -0
  54. package/dist/clis/douban/download.js +67 -0
  55. package/dist/clis/douban/download.test.d.ts +1 -0
  56. package/dist/clis/douban/download.test.js +170 -0
  57. package/dist/clis/douban/photos.d.ts +1 -0
  58. package/dist/clis/douban/photos.js +34 -0
  59. package/dist/clis/douban/utils.d.ts +25 -0
  60. package/dist/clis/douban/utils.js +190 -1
  61. package/dist/clis/douban/utils.test.d.ts +1 -0
  62. package/dist/clis/douban/utils.test.js +64 -0
  63. package/dist/clis/imdb/person.d.ts +1 -0
  64. package/dist/clis/imdb/person.js +203 -0
  65. package/dist/clis/imdb/reviews.d.ts +1 -0
  66. package/dist/clis/imdb/reviews.js +88 -0
  67. package/dist/clis/imdb/search.d.ts +1 -0
  68. package/dist/clis/imdb/search.js +161 -0
  69. package/dist/clis/imdb/title.d.ts +1 -0
  70. package/dist/clis/imdb/title.js +93 -0
  71. package/dist/clis/imdb/top.d.ts +1 -0
  72. package/dist/clis/imdb/top.js +53 -0
  73. package/dist/clis/imdb/trending.d.ts +1 -0
  74. package/dist/clis/imdb/trending.js +52 -0
  75. package/dist/clis/imdb/utils.d.ts +46 -0
  76. package/dist/clis/imdb/utils.js +285 -0
  77. package/dist/clis/imdb/utils.test.d.ts +1 -0
  78. package/dist/clis/imdb/utils.test.js +88 -0
  79. package/dist/clis/jd/item.d.ts +4 -0
  80. package/dist/clis/jd/item.js +16 -15
  81. package/dist/clis/jd/item.test.js +16 -1
  82. package/dist/clis/linux-do/categories.yaml +38 -9
  83. package/dist/clis/linux-do/category.d.ts +1 -0
  84. package/dist/clis/linux-do/category.js +36 -0
  85. package/dist/clis/linux-do/feed.d.ts +45 -0
  86. package/dist/clis/linux-do/feed.js +397 -0
  87. package/dist/clis/linux-do/feed.test.d.ts +1 -0
  88. package/dist/clis/linux-do/feed.test.js +118 -0
  89. package/dist/clis/linux-do/hot.d.ts +1 -0
  90. package/dist/clis/linux-do/hot.js +25 -0
  91. package/dist/clis/linux-do/latest.d.ts +1 -0
  92. package/dist/clis/linux-do/latest.js +18 -0
  93. package/dist/clis/linux-do/tags.yaml +41 -0
  94. package/dist/clis/linux-do/topic.yaml +41 -3
  95. package/dist/clis/linux-do/user-posts.yaml +67 -0
  96. package/dist/clis/linux-do/user-topics.yaml +54 -0
  97. package/dist/clis/paperreview/commands.test.d.ts +3 -0
  98. package/dist/clis/paperreview/commands.test.js +243 -0
  99. package/dist/clis/paperreview/feedback.d.ts +1 -0
  100. package/dist/clis/paperreview/feedback.js +52 -0
  101. package/dist/clis/paperreview/review.d.ts +1 -0
  102. package/dist/clis/paperreview/review.js +37 -0
  103. package/dist/clis/paperreview/submit.d.ts +1 -0
  104. package/dist/clis/paperreview/submit.js +85 -0
  105. package/dist/clis/paperreview/utils.d.ts +46 -0
  106. package/dist/clis/paperreview/utils.js +197 -0
  107. package/dist/clis/paperreview/utils.test.d.ts +1 -0
  108. package/dist/clis/paperreview/utils.test.js +49 -0
  109. package/dist/clis/producthunt/browse.d.ts +1 -0
  110. package/dist/clis/producthunt/browse.js +99 -0
  111. package/dist/clis/producthunt/hot.d.ts +1 -0
  112. package/dist/clis/producthunt/hot.js +110 -0
  113. package/dist/clis/producthunt/posts.d.ts +1 -0
  114. package/dist/clis/producthunt/posts.js +28 -0
  115. package/dist/clis/producthunt/today.d.ts +1 -0
  116. package/dist/clis/producthunt/today.js +35 -0
  117. package/dist/clis/producthunt/utils.d.ts +29 -0
  118. package/dist/clis/producthunt/utils.js +99 -0
  119. package/dist/clis/producthunt/utils.test.d.ts +1 -0
  120. package/dist/clis/producthunt/utils.test.js +64 -0
  121. package/dist/clis/twitter/article.js +4 -28
  122. package/dist/clis/twitter/likes.d.ts +24 -0
  123. package/dist/clis/twitter/likes.js +217 -0
  124. package/dist/clis/twitter/likes.test.d.ts +1 -0
  125. package/dist/clis/twitter/likes.test.js +85 -0
  126. package/dist/clis/twitter/profile.js +4 -28
  127. package/dist/clis/twitter/search.js +2 -1
  128. package/dist/clis/twitter/search.test.js +2 -0
  129. package/dist/clis/twitter/shared.d.ts +6 -0
  130. package/dist/clis/twitter/shared.js +35 -0
  131. package/dist/clis/twitter/timeline.js +2 -13
  132. package/dist/clis/twitter/trending.js +29 -61
  133. package/dist/clis/v2ex/hot.yaml +17 -3
  134. package/dist/clis/weixin/download.d.ts +17 -0
  135. package/dist/clis/weixin/download.js +88 -20
  136. package/dist/clis/weread/book.js +2 -2
  137. package/dist/clis/weread/commands.test.d.ts +3 -0
  138. package/dist/clis/weread/commands.test.js +43 -0
  139. package/dist/clis/weread/highlights.js +2 -2
  140. package/dist/clis/weread/notebooks.js +2 -2
  141. package/dist/clis/weread/notes.js +3 -3
  142. package/dist/clis/weread/shelf.js +2 -2
  143. package/dist/clis/weread/utils.d.ts +4 -4
  144. package/dist/clis/weread/utils.js +32 -14
  145. package/dist/clis/weread/utils.test.js +1 -28
  146. package/dist/clis/xiaohongshu/comments.d.ts +5 -0
  147. package/dist/clis/xiaohongshu/comments.js +74 -0
  148. package/dist/clis/xiaohongshu/comments.test.d.ts +1 -0
  149. package/dist/clis/xiaohongshu/comments.test.js +79 -0
  150. package/dist/clis/xiaohongshu/publish.js +179 -47
  151. package/dist/clis/xiaohongshu/publish.test.d.ts +1 -0
  152. package/dist/clis/xiaohongshu/publish.test.js +131 -0
  153. package/dist/clis/xiaohongshu/search.d.ts +8 -1
  154. package/dist/clis/xiaohongshu/search.js +20 -1
  155. package/dist/clis/xiaohongshu/search.test.d.ts +1 -1
  156. package/dist/clis/xiaohongshu/search.test.js +32 -1
  157. package/dist/commanderAdapter.d.ts +1 -0
  158. package/dist/commanderAdapter.js +176 -29
  159. package/dist/commanderAdapter.test.d.ts +1 -0
  160. package/dist/commanderAdapter.test.js +62 -0
  161. package/dist/daemon.js +17 -1
  162. package/dist/discovery.js +48 -42
  163. package/dist/doctor.d.ts +2 -2
  164. package/dist/doctor.js +11 -4
  165. package/dist/download/index.js +63 -51
  166. package/dist/download/index.test.js +17 -4
  167. package/dist/engine.test.js +42 -0
  168. package/dist/errors.d.ts +4 -2
  169. package/dist/errors.js +17 -34
  170. package/dist/execution.d.ts +1 -3
  171. package/dist/execution.js +66 -8
  172. package/dist/execution.test.d.ts +1 -0
  173. package/dist/execution.test.js +40 -0
  174. package/dist/external.js +6 -1
  175. package/dist/hooks.js +2 -0
  176. package/dist/main.js +6 -0
  177. package/dist/output.js +5 -1
  178. package/dist/pipeline/executor.js +3 -4
  179. package/dist/plugin-manifest.d.ts +70 -0
  180. package/dist/plugin-manifest.js +160 -0
  181. package/dist/plugin-manifest.test.d.ts +4 -0
  182. package/dist/plugin-manifest.test.js +179 -0
  183. package/dist/plugin-scaffold.d.ts +28 -0
  184. package/dist/plugin-scaffold.js +142 -0
  185. package/dist/plugin-scaffold.test.d.ts +4 -0
  186. package/dist/plugin-scaffold.test.js +83 -0
  187. package/dist/plugin.d.ts +82 -11
  188. package/dist/plugin.js +870 -84
  189. package/dist/plugin.test.js +1032 -17
  190. package/dist/registry.d.ts +4 -0
  191. package/dist/registry.js +2 -0
  192. package/dist/runtime-detect.d.ts +21 -0
  193. package/dist/runtime-detect.js +32 -0
  194. package/dist/runtime-detect.test.d.ts +1 -0
  195. package/dist/runtime-detect.test.js +27 -0
  196. package/dist/runtime.d.ts +1 -0
  197. package/dist/runtime.js +2 -2
  198. package/dist/serialization.d.ts +2 -0
  199. package/dist/serialization.js +6 -0
  200. package/dist/types.d.ts +3 -0
  201. package/dist/update-check.d.ts +22 -0
  202. package/dist/update-check.js +112 -0
  203. package/dist/weixin-download.test.d.ts +1 -0
  204. package/dist/weixin-download.test.js +30 -0
  205. package/dist/weread-private-api-regression.test.d.ts +1 -0
  206. package/dist/weread-private-api-regression.test.js +122 -0
  207. package/dist/yaml-schema.d.ts +3 -0
  208. package/dist/yaml-schema.js +18 -1
  209. package/docs/.vitepress/config.mts +4 -0
  210. package/docs/adapters/browser/36kr.md +47 -0
  211. package/docs/adapters/browser/bluesky.md +53 -0
  212. package/docs/adapters/browser/douban.md +14 -0
  213. package/docs/adapters/browser/imdb.md +47 -0
  214. package/docs/adapters/browser/jd.md +2 -2
  215. package/docs/adapters/browser/linux-do.md +181 -20
  216. package/docs/adapters/browser/paperreview.md +43 -0
  217. package/docs/adapters/browser/producthunt.md +49 -0
  218. package/docs/adapters/desktop/chatgpt.md +5 -0
  219. package/docs/adapters/index.md +6 -2
  220. package/docs/advanced/download.md +4 -0
  221. package/docs/advanced/rate-limiter-plugin.md +99 -0
  222. package/docs/guide/electron-app-cli.md +200 -0
  223. package/docs/guide/getting-started.md +1 -0
  224. package/docs/guide/plugins.md +97 -0
  225. package/docs/zh/guide/electron-app-cli.md +188 -0
  226. package/docs/zh/guide/getting-started.md +1 -0
  227. package/docs/zh/guide/plugins.md +65 -0
  228. package/extension/package.json +1 -0
  229. package/extension/scripts/package-release.mjs +179 -0
  230. package/extension/src/background.ts +2 -0
  231. package/package.json +4 -1
  232. package/scripts/postinstall.js +10 -0
  233. package/src/browser/cdp.ts +8 -1
  234. package/src/browser/discover.ts +8 -3
  235. package/src/browser/errors.ts +13 -14
  236. package/src/browser/mcp.ts +2 -1
  237. package/src/browser/page.ts +24 -1
  238. package/src/build-manifest.test.ts +23 -0
  239. package/src/build-manifest.ts +40 -15
  240. package/src/capabilityRouting.ts +2 -1
  241. package/src/cli.ts +69 -6
  242. package/src/clis/36kr/article.ts +69 -0
  243. package/src/clis/36kr/hot.test.ts +19 -0
  244. package/src/clis/36kr/hot.ts +100 -0
  245. package/src/clis/36kr/news.test.ts +90 -0
  246. package/src/clis/36kr/news.ts +54 -0
  247. package/src/clis/36kr/search.ts +78 -0
  248. package/src/clis/bilibili/comments.test.ts +102 -0
  249. package/src/clis/bilibili/comments.ts +44 -0
  250. package/src/clis/bluesky/feeds.yaml +29 -0
  251. package/src/clis/bluesky/followers.yaml +33 -0
  252. package/src/clis/bluesky/following.yaml +33 -0
  253. package/src/clis/bluesky/profile.yaml +27 -0
  254. package/src/clis/bluesky/search.yaml +34 -0
  255. package/src/clis/bluesky/starter-packs.yaml +34 -0
  256. package/src/clis/bluesky/thread.yaml +32 -0
  257. package/src/clis/bluesky/trending.yaml +27 -0
  258. package/src/clis/bluesky/user.yaml +34 -0
  259. package/src/clis/chatgpt/ask.ts +28 -14
  260. package/src/clis/chatgpt/ax.ts +180 -1
  261. package/src/clis/chatgpt/model.ts +27 -0
  262. package/src/clis/chatgpt/send.ts +16 -6
  263. package/src/clis/douban/download.test.ts +196 -0
  264. package/src/clis/douban/download.ts +78 -0
  265. package/src/clis/douban/photos.ts +36 -0
  266. package/src/clis/douban/utils.test.ts +97 -0
  267. package/src/clis/douban/utils.ts +232 -1
  268. package/src/clis/imdb/person.ts +232 -0
  269. package/src/clis/imdb/reviews.ts +111 -0
  270. package/src/clis/imdb/search.ts +179 -0
  271. package/src/clis/imdb/title.ts +121 -0
  272. package/src/clis/imdb/top.ts +67 -0
  273. package/src/clis/imdb/trending.ts +66 -0
  274. package/src/clis/imdb/utils.test.ts +117 -0
  275. package/src/clis/imdb/utils.ts +305 -0
  276. package/src/clis/jd/item.test.ts +18 -1
  277. package/src/clis/jd/item.ts +18 -15
  278. package/src/clis/linux-do/categories.yaml +38 -9
  279. package/src/clis/linux-do/category.ts +37 -0
  280. package/src/clis/linux-do/feed.test.ts +132 -0
  281. package/src/clis/linux-do/feed.ts +501 -0
  282. package/src/clis/linux-do/hot.ts +26 -0
  283. package/src/clis/linux-do/latest.ts +19 -0
  284. package/src/clis/linux-do/tags.yaml +41 -0
  285. package/src/clis/linux-do/topic.yaml +41 -3
  286. package/src/clis/linux-do/user-posts.yaml +67 -0
  287. package/src/clis/linux-do/user-topics.yaml +54 -0
  288. package/src/clis/paperreview/commands.test.ts +283 -0
  289. package/src/clis/paperreview/feedback.ts +64 -0
  290. package/src/clis/paperreview/review.ts +47 -0
  291. package/src/clis/paperreview/submit.ts +119 -0
  292. package/src/clis/paperreview/utils.test.ts +68 -0
  293. package/src/clis/paperreview/utils.ts +276 -0
  294. package/src/clis/producthunt/browse.ts +109 -0
  295. package/src/clis/producthunt/hot.ts +127 -0
  296. package/src/clis/producthunt/posts.ts +29 -0
  297. package/src/clis/producthunt/today.ts +37 -0
  298. package/src/clis/producthunt/utils.test.ts +72 -0
  299. package/src/clis/producthunt/utils.ts +122 -0
  300. package/src/clis/twitter/article.ts +5 -28
  301. package/src/clis/twitter/likes.test.ts +91 -0
  302. package/src/clis/twitter/likes.ts +256 -0
  303. package/src/clis/twitter/profile.ts +5 -28
  304. package/src/clis/twitter/search.test.ts +2 -0
  305. package/src/clis/twitter/search.ts +3 -1
  306. package/src/clis/twitter/shared.ts +45 -0
  307. package/src/clis/twitter/timeline.ts +2 -13
  308. package/src/clis/twitter/trending.ts +29 -77
  309. package/src/clis/v2ex/hot.yaml +17 -3
  310. package/src/clis/weixin/download.ts +114 -20
  311. package/src/clis/weread/book.ts +2 -2
  312. package/src/clis/weread/commands.test.ts +57 -0
  313. package/src/clis/weread/highlights.ts +2 -2
  314. package/src/clis/weread/notebooks.ts +2 -2
  315. package/src/clis/weread/notes.ts +3 -3
  316. package/src/clis/weread/shelf.ts +2 -2
  317. package/src/clis/weread/utils.test.ts +1 -32
  318. package/src/clis/weread/utils.ts +41 -16
  319. package/src/clis/xiaohongshu/comments.test.ts +96 -0
  320. package/src/clis/xiaohongshu/comments.ts +81 -0
  321. package/src/clis/xiaohongshu/publish.test.ts +151 -0
  322. package/src/clis/xiaohongshu/publish.ts +206 -54
  323. package/src/clis/xiaohongshu/search.test.ts +39 -1
  324. package/src/clis/xiaohongshu/search.ts +19 -1
  325. package/src/commanderAdapter.test.ts +78 -0
  326. package/src/commanderAdapter.ts +188 -24
  327. package/src/daemon.ts +19 -1
  328. package/src/discovery.ts +49 -48
  329. package/src/doctor.ts +15 -5
  330. package/src/download/index.test.ts +14 -4
  331. package/src/download/index.ts +67 -55
  332. package/src/engine.test.ts +38 -0
  333. package/src/errors.ts +26 -63
  334. package/src/execution.test.ts +47 -0
  335. package/src/execution.ts +67 -9
  336. package/src/external.ts +6 -1
  337. package/src/hooks.ts +1 -0
  338. package/src/main.ts +7 -0
  339. package/src/output.ts +3 -1
  340. package/src/pipeline/executor.ts +4 -6
  341. package/src/plugin-manifest.test.ts +223 -0
  342. package/src/plugin-manifest.ts +206 -0
  343. package/src/plugin-scaffold.test.ts +98 -0
  344. package/src/plugin-scaffold.ts +170 -0
  345. package/src/plugin.test.ts +1104 -17
  346. package/src/plugin.ts +1101 -86
  347. package/src/registry.ts +6 -1
  348. package/src/runtime-detect.test.ts +30 -0
  349. package/src/runtime-detect.ts +36 -0
  350. package/src/runtime.ts +3 -3
  351. package/src/serialization.ts +4 -0
  352. package/src/types.ts +3 -0
  353. package/src/update-check.ts +114 -0
  354. package/src/weixin-download.test.ts +64 -0
  355. package/src/weread-private-api-regression.test.ts +150 -0
  356. package/src/yaml-schema.ts +20 -0
  357. package/tests/e2e/browser-auth.test.ts +13 -9
  358. package/tests/e2e/browser-public-extended.test.ts +1 -1
  359. package/tests/e2e/browser-public.test.ts +62 -4
  360. package/tests/e2e/helpers.ts +2 -1
  361. package/tests/e2e/public-commands.test.ts +37 -3
  362. package/tests/smoke/api-health.test.ts +1 -1
  363. package/vitest.config.ts +10 -0
  364. package/dist/clis/linux-do/category.yaml +0 -51
  365. package/dist/clis/linux-do/hot.yaml +0 -50
  366. package/dist/clis/linux-do/latest.yaml +0 -40
  367. package/src/clis/linux-do/category.yaml +0 -51
  368. package/src/clis/linux-do/hot.yaml +0 -50
  369. package/src/clis/linux-do/latest.yaml +0 -40
@@ -0,0 +1,179 @@
1
+ import * as fs from 'node:fs/promises';
2
+ import * as path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+ const extensionDir = path.resolve(__dirname, '..');
7
+ const repoRoot = path.resolve(extensionDir, '..');
8
+
9
+ function parseArgs(argv) {
10
+ const args = { outDir: path.join(repoRoot, 'extension-package') };
11
+ for (let i = 0; i < argv.length; i++) {
12
+ const arg = argv[i];
13
+ if (arg === '--out' && argv[i + 1]) {
14
+ const outDir = argv[++i];
15
+ args.outDir = path.isAbsolute(outDir)
16
+ ? outDir
17
+ : path.resolve(process.cwd(), outDir);
18
+ }
19
+ }
20
+ return args;
21
+ }
22
+
23
+ async function exists(targetPath) {
24
+ try {
25
+ await fs.access(targetPath);
26
+ return true;
27
+ } catch {
28
+ return false;
29
+ }
30
+ }
31
+
32
+ function isLocalAsset(ref) {
33
+ return typeof ref === 'string'
34
+ && ref.length > 0
35
+ && !ref.startsWith('http://')
36
+ && !ref.startsWith('https://')
37
+ && !ref.startsWith('//')
38
+ && !ref.startsWith('chrome://')
39
+ && !ref.startsWith('chrome-extension://')
40
+ && !ref.startsWith('data:')
41
+ && !ref.startsWith('#');
42
+ }
43
+
44
+ function addLocalAsset(files, ref) {
45
+ if (isLocalAsset(ref)) files.add(ref);
46
+ }
47
+
48
+ function collectManifestEntrypoints(manifest) {
49
+ const files = new Set(['manifest.json']);
50
+
51
+ addLocalAsset(files, manifest.background?.service_worker);
52
+ addLocalAsset(files, manifest.action?.default_popup);
53
+ addLocalAsset(files, manifest.options_page);
54
+ addLocalAsset(files, manifest.devtools_page);
55
+ addLocalAsset(files, manifest.side_panel?.default_path);
56
+
57
+ for (const ref of Object.values(manifest.icons ?? {})) addLocalAsset(files, ref);
58
+ for (const ref of Object.values(manifest.action?.default_icon ?? {})) addLocalAsset(files, ref);
59
+ for (const contentScript of manifest.content_scripts ?? []) {
60
+ for (const jsFile of contentScript.js ?? []) addLocalAsset(files, jsFile);
61
+ for (const cssFile of contentScript.css ?? []) addLocalAsset(files, cssFile);
62
+ }
63
+ for (const page of manifest.sandbox?.pages ?? []) addLocalAsset(files, page);
64
+ for (const overridePage of Object.values(manifest.chrome_url_overrides ?? {})) addLocalAsset(files, overridePage);
65
+ for (const entry of manifest.web_accessible_resources ?? []) {
66
+ for (const resource of entry.resources ?? []) addLocalAsset(files, resource);
67
+ }
68
+ if (manifest.default_locale) files.add('_locales');
69
+
70
+ return [...files];
71
+ }
72
+
73
+ async function collectHtmlDependencies(relativeHtmlPath, files, visited) {
74
+ if (visited.has(relativeHtmlPath)) return;
75
+ visited.add(relativeHtmlPath);
76
+
77
+ const htmlPath = path.join(extensionDir, relativeHtmlPath);
78
+ const html = await fs.readFile(htmlPath, 'utf8');
79
+ const attrRe = /\b(?:src|href)=["']([^"'#?]+(?:\?[^"']*)?)["']/gi;
80
+
81
+ for (const match of html.matchAll(attrRe)) {
82
+ const rawRef = match[1];
83
+ const cleanRef = rawRef.split('?')[0];
84
+ if (!isLocalAsset(cleanRef)) continue;
85
+
86
+ const resolvedRelativePath = cleanRef.startsWith('/')
87
+ ? cleanRef.slice(1)
88
+ : path.posix.normalize(path.posix.join(path.posix.dirname(relativeHtmlPath), cleanRef));
89
+
90
+ addLocalAsset(files, resolvedRelativePath);
91
+ if (resolvedRelativePath.endsWith('.html')) {
92
+ await collectHtmlDependencies(resolvedRelativePath, files, visited);
93
+ }
94
+ }
95
+ }
96
+
97
+ async function collectManifestAssets(manifest) {
98
+ const files = new Set(collectManifestEntrypoints(manifest));
99
+ const htmlPages = [];
100
+
101
+ if (manifest.action?.default_popup) {
102
+ htmlPages.push(manifest.action.default_popup);
103
+ }
104
+ if (manifest.options_page) htmlPages.push(manifest.options_page);
105
+ if (manifest.devtools_page) htmlPages.push(manifest.devtools_page);
106
+ if (manifest.side_panel?.default_path) htmlPages.push(manifest.side_panel.default_path);
107
+ for (const page of manifest.sandbox?.pages ?? []) htmlPages.push(page);
108
+ for (const overridePage of Object.values(manifest.chrome_url_overrides ?? {})) htmlPages.push(overridePage);
109
+
110
+ const visited = new Set();
111
+ for (const htmlPage of htmlPages) {
112
+ if (isLocalAsset(htmlPage)) {
113
+ await collectHtmlDependencies(htmlPage, files, visited);
114
+ }
115
+ }
116
+
117
+ return [...files];
118
+ }
119
+
120
+ async function copyEntry(relativePath, outDir) {
121
+ const fromPath = path.join(extensionDir, relativePath);
122
+ const toPath = path.join(outDir, relativePath);
123
+ const stats = await fs.stat(fromPath);
124
+
125
+ if (stats.isDirectory()) {
126
+ await fs.cp(fromPath, toPath, { recursive: true });
127
+ return;
128
+ }
129
+
130
+ await fs.mkdir(path.dirname(toPath), { recursive: true });
131
+ await fs.copyFile(fromPath, toPath);
132
+ }
133
+
134
+ async function findMissingEntries(baseDir, entries) {
135
+ const missingEntries = [];
136
+ for (const relativePath of entries) {
137
+ const absolutePath = path.join(baseDir, relativePath);
138
+ if (!(await exists(absolutePath))) {
139
+ missingEntries.push(relativePath);
140
+ }
141
+ }
142
+ return missingEntries;
143
+ }
144
+
145
+ async function main() {
146
+ const { outDir } = parseArgs(process.argv.slice(2));
147
+ const manifestPath = path.join(extensionDir, 'manifest.json');
148
+ const manifest = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
149
+
150
+ const requiredEntries = await collectManifestAssets(manifest);
151
+ const missingEntries = await findMissingEntries(extensionDir, requiredEntries);
152
+
153
+ if (missingEntries.length > 0) {
154
+ console.error('Missing files referenced by the extension package:');
155
+ for (const missingEntry of missingEntries) console.error(`- ${missingEntry}`);
156
+ process.exit(1);
157
+ }
158
+
159
+ await fs.rm(outDir, { recursive: true, force: true });
160
+ await fs.mkdir(outDir, { recursive: true });
161
+
162
+ for (const relativePath of requiredEntries) {
163
+ await copyEntry(relativePath, outDir);
164
+ }
165
+
166
+ // Guard against regressions where manifest entry files (e.g. action.default_popup)
167
+ // are accidentally omitted from the packaged directory.
168
+ const packagedEntrypoints = collectManifestEntrypoints(manifest);
169
+ const missingPackagedEntrypoints = await findMissingEntries(outDir, packagedEntrypoints);
170
+ if (missingPackagedEntrypoints.length > 0) {
171
+ console.error('Packaged extension is missing files referenced by manifest.json:');
172
+ for (const missingEntry of missingPackagedEntrypoints) console.error(`- ${missingEntry}`);
173
+ process.exit(1);
174
+ }
175
+
176
+ console.log(`Extension package prepared at ${path.relative(repoRoot, outDir) || outDir}`);
177
+ }
178
+
179
+ await main();
@@ -51,6 +51,8 @@ function connect(): void {
51
51
  clearTimeout(reconnectTimer);
52
52
  reconnectTimer = null;
53
53
  }
54
+ // Send version so the daemon can report mismatches to the CLI
55
+ ws?.send(JSON.stringify({ type: 'hello', version: chrome.runtime.getManifest().version }));
54
56
  };
55
57
 
56
58
  ws.onmessage = async (event) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jackwener/opencli",
3
- "version": "1.4.1",
3
+ "version": "1.5.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -19,17 +19,20 @@
19
19
  },
20
20
  "scripts": {
21
21
  "dev": "tsx src/main.ts",
22
+ "dev:bun": "bun src/main.ts",
22
23
  "build": "npm run clean-dist && tsc && npm run clean-yaml && npm run copy-yaml && npm run build-manifest",
23
24
  "build-manifest": "node dist/build-manifest.js",
24
25
  "clean-dist": "node scripts/clean-dist.cjs",
25
26
  "clean-yaml": "node scripts/clean-yaml.cjs",
26
27
  "copy-yaml": "node scripts/copy-yaml.cjs",
27
28
  "start": "node dist/main.js",
29
+ "start:bun": "bun dist/main.js",
28
30
  "postinstall": "node scripts/postinstall.js || true",
29
31
  "typecheck": "tsc --noEmit",
30
32
  "lint": "tsc --noEmit",
31
33
  "prepublishOnly": "npm run build",
32
34
  "test": "vitest run --project unit",
35
+ "test:bun": "bun vitest run --project unit",
33
36
  "test:adapter": "vitest run --project adapter",
34
37
  "test:all": "vitest run",
35
38
  "test:e2e": "vitest run --project e2e",
@@ -195,6 +195,16 @@ function main() {
195
195
  console.error(`Warning: Could not install shell completion: ${err.message}`);
196
196
  }
197
197
  }
198
+
199
+ // ── Browser Bridge setup hint ───────────────────────────────────────
200
+ console.log('');
201
+ console.log(' \x1b[1mNext step — Browser Bridge setup\x1b[0m');
202
+ console.log(' Browser commands (bilibili, zhihu, twitter...) require the extension:');
203
+ console.log(' 1. Download: https://github.com/jackwener/opencli/releases');
204
+ console.log(' 2. Open chrome://extensions → enable Developer Mode → Load unpacked');
205
+ console.log('');
206
+ console.log(' Then run \x1b[36mopencli doctor\x1b[0m to verify.');
207
+ console.log('');
198
208
  }
199
209
 
200
210
  main();
@@ -12,6 +12,7 @@ import { WebSocket, type RawData } from 'ws';
12
12
  import { request as httpRequest } from 'node:http';
13
13
  import { request as httpsRequest } from 'node:https';
14
14
  import type { BrowserCookie, IPage, ScreenshotOptions, SnapshotOptions, WaitOptions } from '../types.js';
15
+ import type { IBrowserFactory } from '../runtime.js';
15
16
  import { wrapForEval } from './utils.js';
16
17
  import { generateSnapshotJs, scrollToRefJs, getFormStateJs } from './dom-snapshot.js';
17
18
  import { generateStealthJs } from './stealth.js';
@@ -47,7 +48,7 @@ interface RuntimeEvaluateResult {
47
48
 
48
49
  const CDP_SEND_TIMEOUT = 30_000;
49
50
 
50
- export class CDPBridge {
51
+ export class CDPBridge implements IBrowserFactory {
51
52
  private _ws: WebSocket | null = null;
52
53
  private _idCounter = 0;
53
54
  private _pending = new Map<number, { resolve: (val: unknown) => void; reject: (err: Error) => void; timer: ReturnType<typeof setTimeout> }>();
@@ -172,6 +173,7 @@ export class CDPBridge {
172
173
 
173
174
  class CDPPage implements IPage {
174
175
  private _pageEnabled = false;
176
+ private _lastUrl: string | null = null;
175
177
  constructor(private bridge: CDPBridge) {}
176
178
 
177
179
  async goto(url: string, options?: { waitUntil?: 'load' | 'none'; settleMs?: number }): Promise<void> {
@@ -182,6 +184,7 @@ class CDPPage implements IPage {
182
184
  const loadPromise = this.bridge.waitForEvent('Page.loadEventFired', 30_000).catch(() => {});
183
185
  await this.bridge.send('Page.navigate', { url });
184
186
  await loadPromise;
187
+ this._lastUrl = url;
185
188
  if (options?.waitUntil !== 'none') {
186
189
  const maxMs = options?.settleMs ?? 1000;
187
190
  await this.evaluate(waitForDomStableJs(maxMs, Math.min(500, maxMs)));
@@ -306,6 +309,10 @@ class CDPPage implements IPage {
306
309
  return [];
307
310
  }
308
311
 
312
+ async getCurrentUrl(): Promise<string | null> {
313
+ return this._lastUrl;
314
+ }
315
+
309
316
  async installInterceptor(pattern: string): Promise<void> {
310
317
  const { generateInterceptorJs } = await import('../interceptor.js');
311
318
  await this.evaluate(generateInterceptorJs(JSON.stringify(pattern), {
@@ -13,17 +13,22 @@ export { isDaemonRunning };
13
13
  /**
14
14
  * Check daemon status and return connection info.
15
15
  */
16
- export async function checkDaemonStatus(): Promise<{
16
+ export async function checkDaemonStatus(opts?: { timeout?: number }): Promise<{
17
17
  running: boolean;
18
18
  extensionConnected: boolean;
19
+ extensionVersion?: string;
19
20
  }> {
20
21
  try {
21
22
  const port = parseInt(process.env.OPENCLI_DAEMON_PORT ?? String(DEFAULT_DAEMON_PORT), 10);
23
+ const controller = new AbortController();
24
+ const timer = setTimeout(() => controller.abort(), opts?.timeout ?? 2000);
22
25
  const res = await fetch(`http://127.0.0.1:${port}/status`, {
23
26
  headers: { 'X-OpenCLI': '1' },
27
+ signal: controller.signal,
24
28
  });
25
- const data = await res.json() as { ok: boolean; extensionConnected: boolean };
26
- return { running: true, extensionConnected: data.extensionConnected };
29
+ const data = await res.json() as { ok: boolean; extensionConnected: boolean; extensionVersion?: string };
30
+ clearTimeout(timer);
31
+ return { running: true, extensionConnected: data.extensionConnected, extensionVersion: data.extensionVersion };
27
32
  } catch {
28
33
  return { running: false, extensionConnected: false };
29
34
  }
@@ -5,38 +5,37 @@
5
5
  * The daemon architecture has a single failure mode: daemon not reachable or extension not connected.
6
6
  */
7
7
 
8
- import { BrowserConnectError } from '../errors.js';
8
+ import { BrowserConnectError, type BrowserConnectKind } from '../errors.js';
9
9
  import { DEFAULT_DAEMON_PORT } from '../constants.js';
10
10
 
11
- export type ConnectFailureKind = 'daemon-not-running' | 'extension-not-connected' | 'command-failed' | 'unknown';
11
+ // Re-export so callers don't need to import from two places
12
+ export type ConnectFailureKind = BrowserConnectKind;
12
13
 
13
14
  export function formatBrowserConnectError(kind: ConnectFailureKind, detail?: string): BrowserConnectError {
14
15
  switch (kind) {
15
16
  case 'daemon-not-running':
16
17
  return new BrowserConnectError(
17
- 'Cannot connect to opencli daemon.' +
18
- (detail ? `\n\n${detail}` : ''),
19
- 'The daemon should start automatically. If it doesn\'t, try:\n' +
20
- ' node dist/daemon.js\n' +
21
- `Make sure port ${DEFAULT_DAEMON_PORT} is available.`,
18
+ 'Cannot connect to opencli daemon.' + (detail ? `\n\n${detail}` : ''),
19
+ `The daemon should auto-start. If it keeps failing, make sure port ${DEFAULT_DAEMON_PORT} is available.`,
20
+ kind,
22
21
  );
23
22
  case 'extension-not-connected':
24
23
  return new BrowserConnectError(
25
- 'opencli Browser Bridge extension is not connected.' +
26
- (detail ? `\n\n${detail}` : ''),
27
- 'Please install the extension:\n' +
28
- ' 1. Download from GitHub Releases\n' +
29
- ' 2. Open chrome://extensions/ → Enable Developer Mode\n' +
30
- ' 3. Click "Load unpacked" → select the extension folder\n' +
31
- ' 4. Make sure Chrome is running',
24
+ 'Browser Bridge extension is not connected.' + (detail ? `\n\n${detail}` : ''),
25
+ 'Install the extension from GitHub Releases, then reload.',
26
+ kind,
32
27
  );
33
28
  case 'command-failed':
34
29
  return new BrowserConnectError(
35
30
  `Browser command failed: ${detail ?? 'unknown error'}`,
31
+ undefined,
32
+ kind,
36
33
  );
37
34
  default:
38
35
  return new BrowserConnectError(
39
36
  detail ?? 'Failed to connect to browser',
37
+ undefined,
38
+ kind,
40
39
  );
41
40
  }
42
41
  }
@@ -7,6 +7,7 @@ import { fileURLToPath } from 'node:url';
7
7
  import * as path from 'node:path';
8
8
  import * as fs from 'node:fs';
9
9
  import type { IPage } from '../types.js';
10
+ import type { IBrowserFactory } from '../runtime.js';
10
11
  import { Page } from './page.js';
11
12
  import { isDaemonRunning, isExtensionConnected } from './daemon-client.js';
12
13
  import { DEFAULT_DAEMON_PORT } from '../constants.js';
@@ -18,7 +19,7 @@ export type BrowserBridgeState = 'idle' | 'connecting' | 'connected' | 'closing'
18
19
  /**
19
20
  * Browser factory: manages daemon lifecycle and provides IPage instances.
20
21
  */
21
- export class BrowserBridge {
22
+ export class BrowserBridge implements IBrowserFactory {
22
23
  private _state: BrowserBridgeState = 'idle';
23
24
  private _page: Page | null = null;
24
25
  private _daemonProc: ChildProcess | null = null;
@@ -36,6 +36,8 @@ export class Page implements IPage {
36
36
 
37
37
  /** Active tab ID, set after navigate and used in all subsequent commands */
38
38
  private _tabId: number | undefined;
39
+ /** Last navigated URL, tracked in-memory to avoid extra round-trips */
40
+ private _lastUrl: string | null = null;
39
41
 
40
42
  /** Helper: spread workspace into command params */
41
43
  private _wsOpt(): { workspace: string } {
@@ -55,10 +57,11 @@ export class Page implements IPage {
55
57
  url,
56
58
  ...this._cmdOpts(),
57
59
  }) as { tabId?: number };
58
- // Remember the tabId for subsequent exec calls
60
+ // Remember the tabId and URL for subsequent calls
59
61
  if (result?.tabId) {
60
62
  this._tabId = result.tabId;
61
63
  }
64
+ this._lastUrl = url;
62
65
  // Inject stealth anti-detection patches (guard flag prevents double-injection).
63
66
  try {
64
67
  await sendCommand('exec', {
@@ -79,6 +82,10 @@ export class Page implements IPage {
79
82
  }
80
83
  }
81
84
 
85
+ async getCurrentUrl(): Promise<string | null> {
86
+ return this._lastUrl;
87
+ }
88
+
82
89
  /** Close the automation window in the extension */
83
90
  async closeWindow(): Promise<void> {
84
91
  try {
@@ -183,6 +190,22 @@ export class Page implements IPage {
183
190
 
184
191
  async wait(options: number | WaitOptions): Promise<void> {
185
192
  if (typeof options === 'number') {
193
+ if (options >= 1) {
194
+ // For waits >= 1s, use DOM-stable check: return early when the page
195
+ // stops mutating, with the original wait time as the hard cap.
196
+ // This turns e.g. `page.wait(5)` from a fixed 5s sleep into
197
+ // "wait until DOM is stable, max 5s" — often completing in <1s.
198
+ try {
199
+ const maxMs = options * 1000;
200
+ await sendCommand('exec', {
201
+ code: waitForDomStableJs(maxMs, Math.min(500, maxMs)),
202
+ ...this._cmdOpts(),
203
+ });
204
+ return;
205
+ } catch {
206
+ // Fallback: fixed sleep (e.g. if page has no DOM yet)
207
+ }
208
+ }
186
209
  await new Promise(resolve => setTimeout(resolve, options * 1000));
187
210
  return;
188
211
  }
@@ -143,4 +143,27 @@ describe('manifest helper rules', () => {
143
143
  modulePath: 'xueqiu/fund-holdings.js',
144
144
  });
145
145
  });
146
+
147
+ it('captures deprecated metadata for TS adapters', () => {
148
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'opencli-manifest-'));
149
+ tempDirs.push(dir);
150
+ const file = path.join(dir, 'legacy.ts');
151
+ fs.writeFileSync(file, `
152
+ import { cli } from '../../registry.js';
153
+ cli({
154
+ site: 'demo',
155
+ name: 'legacy',
156
+ description: 'legacy command',
157
+ deprecated: 'legacy is deprecated',
158
+ replacedBy: 'opencli demo new',
159
+ });
160
+ `);
161
+
162
+ expect(scanTs(file, 'demo')).toMatchObject({
163
+ site: 'demo',
164
+ name: 'legacy',
165
+ deprecated: 'legacy is deprecated',
166
+ replacedBy: 'opencli demo new',
167
+ });
168
+ });
146
169
  });
@@ -38,6 +38,8 @@ export interface ManifestEntry {
38
38
  columns?: string[];
39
39
  pipeline?: Record<string, unknown>[];
40
40
  timeout?: number;
41
+ deprecated?: boolean | string;
42
+ replacedBy?: string;
41
43
  /** 'yaml' or 'ts' — determines how executeCommand loads the handler */
42
44
  type: 'yaml' | 'ts';
43
45
  /** Relative path from clis/ dir, e.g. 'bilibili/hot.yaml' or 'bilibili/search.js' */
@@ -46,7 +48,7 @@ export interface ManifestEntry {
46
48
  navigateBefore?: boolean | string;
47
49
  }
48
50
 
49
- import type { YamlCliDefinition } from './yaml-schema.js';
51
+ import { type YamlCliDefinition, parseYamlArgs } from './yaml-schema.js';
50
52
 
51
53
  import { isRecord } from './utils.js';
52
54
 
@@ -173,20 +175,7 @@ function scanYaml(filePath: string, site: string): ManifestEntry | null {
173
175
  const strategy = strategyStr.toUpperCase();
174
176
  const browser = cliDef.browser ?? (strategy !== 'PUBLIC');
175
177
 
176
- const args: ManifestEntry['args'] = [];
177
- if (cliDef.args && typeof cliDef.args === 'object') {
178
- for (const [argName, argDef] of Object.entries(cliDef.args)) {
179
- args.push({
180
- name: argName,
181
- type: argDef?.type ?? 'str',
182
- default: argDef?.default,
183
- required: argDef?.required ?? false,
184
- positional: argDef?.positional === true || undefined,
185
- help: argDef?.description ?? argDef?.help ?? '',
186
- choices: argDef?.choices,
187
- });
188
- }
189
- }
178
+ const args = parseYamlArgs(cliDef.args);
190
179
 
191
180
  return {
192
181
  site: cliDef.site ?? site,
@@ -199,6 +188,8 @@ function scanYaml(filePath: string, site: string): ManifestEntry | null {
199
188
  columns: cliDef.columns,
200
189
  pipeline: cliDef.pipeline,
201
190
  timeout: cliDef.timeout,
191
+ deprecated: (cliDef as Record<string, unknown>).deprecated as boolean | string | undefined,
192
+ replacedBy: (cliDef as Record<string, unknown>).replacedBy as string | undefined,
202
193
  type: 'yaml',
203
194
  navigateBefore: cliDef.navigateBefore,
204
195
  };
@@ -269,6 +260,17 @@ export function scanTs(filePath: string, site: string): ManifestEntry | null {
269
260
  if (navStringMatch) entry.navigateBefore = navStringMatch[1];
270
261
  }
271
262
 
263
+ const deprecatedBoolMatch = src.match(/deprecated\s*:\s*(true|false)/);
264
+ if (deprecatedBoolMatch) {
265
+ entry.deprecated = deprecatedBoolMatch[1] === 'true';
266
+ } else {
267
+ const deprecatedStringMatch = src.match(/deprecated\s*:\s*['"`]([^'"`]+)['"`]/);
268
+ if (deprecatedStringMatch) entry.deprecated = deprecatedStringMatch[1];
269
+ }
270
+
271
+ const replacedByMatch = src.match(/replacedBy\s*:\s*['"`]([^'"`]+)['"`]/);
272
+ if (replacedByMatch) entry.replacedBy = replacedByMatch[1];
273
+
272
274
  return entry;
273
275
  } catch (err) {
274
276
  // If parsing fails, log a warning (matching scanYaml behaviour) and skip the entry.
@@ -338,6 +340,29 @@ function main(): void {
338
340
  const yamlCount = manifest.filter(e => e.type === 'yaml').length;
339
341
  const tsCount = manifest.filter(e => e.type === 'ts').length;
340
342
  console.log(`✅ Manifest compiled: ${manifest.length} entries (${yamlCount} YAML, ${tsCount} TS) → ${OUTPUT}`);
343
+
344
+ // Restore executable permissions on bin entries.
345
+ // tsc does not preserve the +x bit, so after a clean rebuild the CLI
346
+ // entry-point loses its executable permission, causing "Permission denied".
347
+ // See: https://github.com/jackwener/opencli/issues/446
348
+ if (process.platform !== 'win32') {
349
+ const pkgPath = path.resolve(__dirname, '..', 'package.json');
350
+ try {
351
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
352
+ const bins: Record<string, string> = typeof pkg.bin === 'string'
353
+ ? { [pkg.name ?? 'cli']: pkg.bin }
354
+ : pkg.bin ?? {};
355
+ for (const binPath of Object.values(bins)) {
356
+ const abs = path.resolve(__dirname, '..', binPath);
357
+ if (fs.existsSync(abs)) {
358
+ fs.chmodSync(abs, 0o755);
359
+ console.log(`✅ Restored executable permission: ${binPath}`);
360
+ }
361
+ }
362
+ } catch {
363
+ // Best-effort; never break the build for a permission fix.
364
+ }
365
+ }
341
366
  }
342
367
 
343
368
  const entrypoint = process.argv[1] ? pathToFileURL(path.resolve(process.argv[1])).href : null;
@@ -1,6 +1,7 @@
1
1
  import { Strategy, type CliCommand } from './registry.js';
2
2
 
3
- const BROWSER_ONLY_STEPS = new Set([
3
+ /** Pipeline steps that require a live browser session. */
4
+ export const BROWSER_ONLY_STEPS = new Set([
4
5
  'navigate',
5
6
  'click',
6
7
  'type',