@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
@@ -5,7 +5,8 @@
5
5
  * rather than making direct API calls.
6
6
  */
7
7
  import { cli, Strategy } from '../../registry.js';
8
- import { requirePage, navigateToChat, findFriendByUid, clickCandidateInList, typeAndSendMessage, } from './common.js';
8
+ import { requirePage, navigateToChat, findFriendByUid, clickCandidateInList, typeAndSendMessage, } from './utils.js';
9
+ import { EmptyResultError, SelectorError } from '../../errors.js';
9
10
  cli({
10
11
  site: 'boss',
11
12
  name: 'send',
@@ -24,17 +25,17 @@ cli({
24
25
  await navigateToChat(page, 3);
25
26
  const friend = await findFriendByUid(page, kwargs.uid, { maxPages: 5 });
26
27
  if (!friend)
27
- throw new Error('未找到该候选人,请确认 uid 是否正确');
28
+ throw new EmptyResultError('boss candidate search', '请确认 uid 是否正确');
28
29
  const numericUid = friend.uid;
29
30
  const friendName = friend.name || '候选人';
30
31
  const clicked = await clickCandidateInList(page, numericUid);
31
32
  if (!clicked) {
32
- throw new Error('无法在聊天列表中找到该用户,请确认聊天列表中有此人');
33
+ throw new SelectorError('聊天列表中的用户', '请确认聊天列表中有此人');
33
34
  }
34
35
  await page.wait({ time: 2 });
35
36
  const sent = await typeAndSendMessage(page, kwargs.text);
36
37
  if (!sent) {
37
- throw new Error('找不到消息输入框');
38
+ throw new SelectorError('消息输入框', '聊天页面 UI 可能已改变');
38
39
  }
39
40
  await page.wait({ time: 1 });
40
41
  return [{ status: '✅ 发送成功', detail: `已向 ${friendName} 发送: ${kwargs.text}` }];
@@ -2,7 +2,7 @@
2
2
  * BOSS直聘 stats — job statistics overview.
3
3
  */
4
4
  import { cli, Strategy } from '../../registry.js';
5
- import { requirePage, navigateToChat, bossFetch, fetchFriendList, verbose } from './common.js';
5
+ import { requirePage, navigateToChat, bossFetch, fetchFriendList, verbose } from './utils.js';
6
6
  cli({
7
7
  site: 'boss',
8
8
  name: 'stats',
@@ -1,5 +1,6 @@
1
1
  import { execSync, spawnSync } from 'node:child_process';
2
2
  import { cli, Strategy } from '../../registry.js';
3
+ import { ConfigError } from '../../errors.js';
3
4
  import { getVisibleChatMessages } from './ax.js';
4
5
  export const askCommand = cli({
5
6
  site: 'chatgpt',
@@ -14,6 +15,9 @@ export const askCommand = cli({
14
15
  ],
15
16
  columns: ['Role', 'Text'],
16
17
  func: async (page, kwargs) => {
18
+ if (process.platform !== 'darwin') {
19
+ throw new ConfigError('ChatGPT Desktop integration requires macOS (osascript is not available on this platform)');
20
+ }
17
21
  const text = kwargs.text;
18
22
  const timeout = parseInt(kwargs.timeout, 10) || 30;
19
23
  // Backup clipboard
@@ -1,5 +1,6 @@
1
1
  import { execSync } from 'node:child_process';
2
2
  import { cli, Strategy } from '../../registry.js';
3
+ import { ConfigError, getErrorMessage } from '../../errors.js';
3
4
  export const newCommand = cli({
4
5
  site: 'chatgpt',
5
6
  name: 'new',
@@ -10,6 +11,9 @@ export const newCommand = cli({
10
11
  args: [],
11
12
  columns: ['Status'],
12
13
  func: async (page) => {
14
+ if (process.platform !== 'darwin') {
15
+ throw new ConfigError('ChatGPT Desktop integration requires macOS (osascript is not available on this platform)');
16
+ }
13
17
  try {
14
18
  execSync("osascript -e 'tell application \"ChatGPT\" to activate'");
15
19
  execSync("osascript -e 'delay 0.5'");
@@ -17,7 +21,7 @@ export const newCommand = cli({
17
21
  return [{ Status: 'Success' }];
18
22
  }
19
23
  catch (err) {
20
- return [{ Status: "Error: " + err.message }];
24
+ return [{ Status: "Error: " + getErrorMessage(err) }];
21
25
  }
22
26
  },
23
27
  });
@@ -1,5 +1,6 @@
1
1
  import { execSync } from 'node:child_process';
2
2
  import { cli, Strategy } from '../../registry.js';
3
+ import { CommandExecutionError, ConfigError, getErrorMessage } from '../../errors.js';
3
4
  import { getVisibleChatMessages } from './ax.js';
4
5
  export const readCommand = cli({
5
6
  site: 'chatgpt',
@@ -11,6 +12,9 @@ export const readCommand = cli({
11
12
  args: [],
12
13
  columns: ['Role', 'Text'],
13
14
  func: async (page) => {
15
+ if (process.platform !== 'darwin') {
16
+ throw new ConfigError('ChatGPT Desktop integration requires macOS (osascript is not available on this platform)');
17
+ }
14
18
  try {
15
19
  execSync("osascript -e 'tell application \"ChatGPT\" to activate'");
16
20
  execSync("osascript -e 'delay 0.3'");
@@ -21,7 +25,7 @@ export const readCommand = cli({
21
25
  return [{ Role: 'Assistant', Text: messages[messages.length - 1] }];
22
26
  }
23
27
  catch (err) {
24
- throw new Error("Failed to read from ChatGPT: " + err.message);
28
+ throw new CommandExecutionError("Failed to read from ChatGPT: " + getErrorMessage(err));
25
29
  }
26
30
  },
27
31
  });
@@ -1,5 +1,6 @@
1
1
  import { execSync, spawnSync } from 'node:child_process';
2
2
  import { cli, Strategy } from '../../registry.js';
3
+ import { getErrorMessage } from '../../errors.js';
3
4
  export const sendCommand = cli({
4
5
  site: 'chatgpt',
5
6
  name: 'send',
@@ -36,7 +37,7 @@ export const sendCommand = cli({
36
37
  return [{ Status: 'Success' }];
37
38
  }
38
39
  catch (err) {
39
- return [{ Status: "Error: " + err.message }];
40
+ return [{ Status: "Error: " + getErrorMessage(err) }];
40
41
  }
41
42
  },
42
43
  });
@@ -1,5 +1,6 @@
1
1
  import { execSync } from 'node:child_process';
2
2
  import { cli, Strategy } from '../../registry.js';
3
+ import { CommandExecutionError, ConfigError } from '../../errors.js';
3
4
  export const statusCommand = cli({
4
5
  site: 'chatgpt',
5
6
  name: 'status',
@@ -10,12 +11,15 @@ export const statusCommand = cli({
10
11
  args: [],
11
12
  columns: ['Status'],
12
13
  func: async (page) => {
14
+ if (process.platform !== 'darwin') {
15
+ throw new ConfigError('ChatGPT Desktop integration requires macOS (osascript is not available on this platform)');
16
+ }
13
17
  try {
14
18
  const output = execSync("osascript -e 'application \"ChatGPT\" is running'", { encoding: 'utf-8' }).trim();
15
19
  return [{ Status: output === 'true' ? 'Running' : 'Stopped' }];
16
20
  }
17
21
  catch {
18
- return [{ Status: 'Error querying application state' }];
22
+ throw new CommandExecutionError('Error querying ChatGPT application state');
19
23
  }
20
24
  },
21
25
  });
@@ -1,4 +1,6 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
+ import { SelectorError } from '../../errors.js';
3
+ import { chatwiseRequiredEnv } from './shared.js';
2
4
  export const askCommand = cli({
3
5
  site: 'chatwise',
4
6
  name: 'ask',
@@ -6,6 +8,7 @@ export const askCommand = cli({
6
8
  domain: 'localhost',
7
9
  strategy: Strategy.UI,
8
10
  browser: true,
11
+ requiredEnv: chatwiseRequiredEnv,
9
12
  args: [
10
13
  { name: 'text', required: true, positional: true, help: 'Prompt to send' },
11
14
  { name: 'timeout', required: false, help: 'Max seconds to wait (default: 30)', default: '30' },
@@ -22,14 +25,14 @@ export const askCommand = cli({
22
25
  })()
23
26
  `);
24
27
  // Send message
25
- await page.evaluate(`
28
+ const injected = await page.evaluate(`
26
29
  (function(text) {
27
30
  let composer = document.querySelector('textarea');
28
31
  if (!composer) {
29
32
  const editables = Array.from(document.querySelectorAll('[contenteditable="true"]'));
30
33
  composer = editables.length > 0 ? editables[editables.length - 1] : null;
31
34
  }
32
- if (!composer) throw new Error('Could not find input');
35
+ if (!composer) return false;
33
36
  composer.focus();
34
37
  if (composer.tagName === 'TEXTAREA') {
35
38
  const setter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set;
@@ -38,8 +41,11 @@ export const askCommand = cli({
38
41
  } else {
39
42
  document.execCommand('insertText', false, text);
40
43
  }
44
+ return true;
41
45
  })(${JSON.stringify(text)})
42
46
  `);
47
+ if (!injected)
48
+ throw new SelectorError('ChatWise input element');
43
49
  await page.wait(0.5);
44
50
  await page.pressKey('Enter');
45
51
  // Poll for response
@@ -1,5 +1,6 @@
1
1
  import * as fs from 'node:fs';
2
2
  import { cli, Strategy } from '../../registry.js';
3
+ import { chatwiseRequiredEnv } from './shared.js';
3
4
  export const exportCommand = cli({
4
5
  site: 'chatwise',
5
6
  name: 'export',
@@ -7,6 +8,7 @@ export const exportCommand = cli({
7
8
  domain: 'localhost',
8
9
  strategy: Strategy.UI,
9
10
  browser: true,
11
+ requiredEnv: chatwiseRequiredEnv,
10
12
  args: [
11
13
  { name: 'output', required: false, help: 'Output file (default: /tmp/chatwise-export.md)' },
12
14
  ],
@@ -1,4 +1,5 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
+ import { chatwiseRequiredEnv } from './shared.js';
2
3
  export const historyCommand = cli({
3
4
  site: 'chatwise',
4
5
  name: 'history',
@@ -6,6 +7,7 @@ export const historyCommand = cli({
6
7
  domain: 'localhost',
7
8
  strategy: Strategy.UI,
8
9
  browser: true,
10
+ requiredEnv: chatwiseRequiredEnv,
9
11
  args: [],
10
12
  columns: ['Index', 'Title'],
11
13
  func: async (page) => {
@@ -1,4 +1,6 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
+ import { SelectorError } from '../../errors.js';
3
+ import { chatwiseRequiredEnv } from './shared.js';
2
4
  export const modelCommand = cli({
3
5
  site: 'chatwise',
4
6
  name: 'model',
@@ -6,6 +8,7 @@ export const modelCommand = cli({
6
8
  domain: 'localhost',
7
9
  strategy: Strategy.UI,
8
10
  browser: true,
11
+ requiredEnv: chatwiseRequiredEnv,
9
12
  args: [
10
13
  { name: 'model-name', required: false, positional: true, help: 'Model to switch to (e.g. gpt-4, claude-3)' },
11
14
  ],
@@ -41,7 +44,7 @@ export const modelCommand = cli({
41
44
  }
42
45
  else {
43
46
  // Try to switch model
44
- await page.evaluate(`
47
+ const opened = await page.evaluate(`
45
48
  (function(target) {
46
49
  const selectors = [
47
50
  '[class*="model"]',
@@ -51,11 +54,13 @@ export const modelCommand = cli({
51
54
 
52
55
  for (const sel of selectors) {
53
56
  const el = document.querySelector(sel);
54
- if (el) { el.click(); return; }
57
+ if (el) { el.click(); return true; }
55
58
  }
56
- throw new Error('Could not find model selector');
59
+ return false;
57
60
  })(${JSON.stringify(desiredModel)})
58
61
  `);
62
+ if (!opened)
63
+ throw new SelectorError('ChatWise model selector');
59
64
  await page.wait(0.5);
60
65
  // Find and click the target model in the dropdown
61
66
  const found = await page.evaluate(`
@@ -1,18 +1,3 @@
1
- import { cli, Strategy } from '../../registry.js';
2
- export const newCommand = cli({
3
- site: 'chatwise',
4
- name: 'new',
5
- description: 'Start a new conversation in ChatWise',
6
- domain: 'localhost',
7
- strategy: Strategy.UI,
8
- browser: true,
9
- args: [],
10
- columns: ['Status'],
11
- func: async (page) => {
12
- // ChatWise uses standard Electron shortcuts
13
- const isMac = process.platform === 'darwin';
14
- await page.pressKey(isMac ? 'Meta+N' : 'Control+N');
15
- await page.wait(1);
16
- return [{ Status: 'Success' }];
17
- },
18
- });
1
+ import { makeNewCommand } from '../_shared/desktop-commands.js';
2
+ import { chatwiseRequiredEnv } from './shared.js';
3
+ export const newCommand = makeNewCommand('chatwise', 'ChatWise conversation', { requiredEnv: chatwiseRequiredEnv });
@@ -1,4 +1,5 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
+ import { chatwiseRequiredEnv } from './shared.js';
2
3
  export const readCommand = cli({
3
4
  site: 'chatwise',
4
5
  name: 'read',
@@ -6,6 +7,7 @@ export const readCommand = cli({
6
7
  domain: 'localhost',
7
8
  strategy: Strategy.UI,
8
9
  browser: true,
10
+ requiredEnv: chatwiseRequiredEnv,
9
11
  args: [],
10
12
  columns: ['Content'],
11
13
  func: async (page) => {
@@ -1,27 +1,3 @@
1
- import * as fs from 'node:fs';
2
- import { cli, Strategy } from '../../registry.js';
3
- export const screenshotCommand = cli({
4
- site: 'chatwise',
5
- name: 'screenshot',
6
- description: 'Capture a snapshot of the current ChatWise window (DOM + Accessibility tree)',
7
- domain: 'localhost',
8
- strategy: Strategy.UI,
9
- browser: true,
10
- args: [
11
- { name: 'output', required: false, help: 'Output file path (default: /tmp/chatwise-snapshot)' },
12
- ],
13
- columns: ['Status', 'File'],
14
- func: async (page, kwargs) => {
15
- const basePath = kwargs.output || '/tmp/chatwise-snapshot';
16
- const snap = await page.snapshot({ compact: true });
17
- const html = await page.evaluate('document.documentElement.outerHTML');
18
- const htmlPath = basePath + '-dom.html';
19
- const snapPath = basePath + '-a11y.txt';
20
- fs.writeFileSync(htmlPath, html);
21
- fs.writeFileSync(snapPath, typeof snap === 'string' ? snap : JSON.stringify(snap, null, 2));
22
- return [
23
- { Status: 'Success', File: htmlPath },
24
- { Status: 'Success', File: snapPath },
25
- ];
26
- },
27
- });
1
+ import { makeScreenshotCommand } from '../_shared/desktop-commands.js';
2
+ import { chatwiseRequiredEnv } from './shared.js';
3
+ export const screenshotCommand = makeScreenshotCommand('chatwise', 'ChatWise', { requiredEnv: chatwiseRequiredEnv });
@@ -1,4 +1,6 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
+ import { SelectorError } from '../../errors.js';
3
+ import { chatwiseRequiredEnv } from './shared.js';
2
4
  export const sendCommand = cli({
3
5
  site: 'chatwise',
4
6
  name: 'send',
@@ -6,11 +8,12 @@ export const sendCommand = cli({
6
8
  domain: 'localhost',
7
9
  strategy: Strategy.UI,
8
10
  browser: true,
11
+ requiredEnv: chatwiseRequiredEnv,
9
12
  args: [{ name: 'text', required: true, positional: true, help: 'Message to send' }],
10
13
  columns: ['Status', 'InjectedText'],
11
14
  func: async (page, kwargs) => {
12
15
  const text = kwargs.text;
13
- await page.evaluate(`
16
+ const injected = await page.evaluate(`
14
17
  (function(text) {
15
18
  // ChatWise input can be textarea or contenteditable
16
19
  let composer = document.querySelector('textarea');
@@ -19,7 +22,7 @@ export const sendCommand = cli({
19
22
  composer = editables.length > 0 ? editables[editables.length - 1] : null;
20
23
  }
21
24
 
22
- if (!composer) throw new Error('Could not find ChatWise input element');
25
+ if (!composer) return false;
23
26
 
24
27
  composer.focus();
25
28
 
@@ -31,8 +34,11 @@ export const sendCommand = cli({
31
34
  } else {
32
35
  document.execCommand('insertText', false, text);
33
36
  }
37
+ return true;
34
38
  })(${JSON.stringify(text)})
35
39
  `);
40
+ if (!injected)
41
+ throw new SelectorError('ChatWise input element');
36
42
  await page.wait(0.5);
37
43
  await page.pressKey('Enter');
38
44
  return [
@@ -0,0 +1,2 @@
1
+ import type { RequiredEnv } from '../../registry.js';
2
+ export declare const chatwiseRequiredEnv: RequiredEnv[];
@@ -0,0 +1,6 @@
1
+ export const chatwiseRequiredEnv = [
2
+ {
3
+ name: 'OPENCLI_CDP_ENDPOINT',
4
+ help: 'Launch ChatWise with --remote-debugging-port=9228, then run OPENCLI_CDP_ENDPOINT=http://127.0.0.1:9228 opencli chatwise status. If you use a local proxy, also set NO_PROXY=127.0.0.1,localhost.',
5
+ },
6
+ ];
@@ -1,22 +1,3 @@
1
- import { cli, Strategy } from '../../registry.js';
2
- export const statusCommand = cli({
3
- site: 'chatwise',
4
- name: 'status',
5
- description: 'Check active CDP connection to ChatWise Desktop',
6
- domain: 'localhost',
7
- strategy: Strategy.UI,
8
- browser: true,
9
- args: [],
10
- columns: ['Status', 'Url', 'Title'],
11
- func: async (page) => {
12
- const url = await page.evaluate('window.location.href');
13
- const title = await page.evaluate('document.title');
14
- return [
15
- {
16
- Status: 'Connected',
17
- Url: url,
18
- Title: title,
19
- },
20
- ];
21
- },
22
- });
1
+ import { makeStatusCommand } from '../_shared/desktop-commands.js';
2
+ import { chatwiseRequiredEnv } from './shared.js';
3
+ export const statusCommand = makeStatusCommand('chatwise', 'ChatWise Desktop', { requiredEnv: chatwiseRequiredEnv });
@@ -1,4 +1,5 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
+ import { SelectorError } from '../../errors.js';
2
3
  export const askCommand = cli({
3
4
  site: 'codex',
4
5
  name: 'ask',
@@ -22,15 +23,18 @@ export const askCommand = cli({
22
23
  })()
23
24
  `);
24
25
  // Inject and send
25
- await page.evaluate(`
26
+ const injected = await page.evaluate(`
26
27
  (function(text) {
27
28
  const editables = Array.from(document.querySelectorAll('[contenteditable="true"]'));
28
29
  const composer = editables.length > 0 ? editables[editables.length - 1] : document.querySelector('textarea');
29
- if (!composer) throw new Error('Could not find Codex input');
30
+ if (!composer) return false;
30
31
  composer.focus();
31
32
  document.execCommand('insertText', false, text);
33
+ return true;
32
34
  })(${JSON.stringify(text)})
33
35
  `);
36
+ if (!injected)
37
+ throw new SelectorError('Codex input element');
34
38
  await page.wait(0.5);
35
39
  await page.pressKey('Enter');
36
40
  // Poll for new content
@@ -1,25 +1,2 @@
1
- import { cli, Strategy } from '../../registry.js';
2
- import * as fs from 'fs';
3
- export const dumpCommand = cli({
4
- site: 'codex',
5
- name: 'dump',
6
- description: 'Dump the DOM and Accessibility tree of Codex for reverse-engineering',
7
- domain: 'localhost',
8
- strategy: Strategy.UI,
9
- browser: true,
10
- columns: ['action', 'files'],
11
- func: async (page) => {
12
- // Extract full HTML
13
- const dom = await page.evaluate('document.body.innerHTML');
14
- fs.writeFileSync('/tmp/codex-dom.html', dom);
15
- // Get accessibility snapshot
16
- const snap = await page.snapshot({ interactive: false });
17
- fs.writeFileSync('/tmp/codex-snapshot.json', JSON.stringify(snap, null, 2));
18
- return [
19
- {
20
- action: 'Dom extraction finished',
21
- files: '/tmp/codex-dom.html, /tmp/codex-snapshot.json',
22
- },
23
- ];
24
- },
25
- });
1
+ import { makeDumpCommand } from '../_shared/desktop-commands.js';
2
+ export const dumpCommand = makeDumpCommand('codex');
@@ -1,25 +1,2 @@
1
- import { cli, Strategy } from '../../registry.js';
2
- export const newCommand = cli({
3
- site: 'codex',
4
- name: 'new',
5
- description: 'Start a new Codex conversation thread / isolated workspace',
6
- domain: 'localhost',
7
- strategy: Strategy.UI,
8
- browser: true,
9
- columns: ['Status', 'Action'],
10
- func: async (page) => {
11
- // According to research, Cmd+N / Ctrl+N spins up a new thread
12
- const isMac = process.platform === 'darwin';
13
- const newThreadKey = isMac ? 'Meta+N' : 'Control+N';
14
- // Simulate keyboard shortcut
15
- await page.pressKey(newThreadKey);
16
- // Wait a brief moment for UI animation
17
- await page.wait(1);
18
- return [
19
- {
20
- Status: 'Success',
21
- Action: `Pressed ${newThreadKey} to trigger New Thread`,
22
- },
23
- ];
24
- },
25
- });
1
+ import { makeNewCommand } from '../_shared/desktop-commands.js';
2
+ export const newCommand = makeNewCommand('codex', 'Codex conversation');
@@ -1,27 +1,2 @@
1
- import * as fs from 'node:fs';
2
- import { cli, Strategy } from '../../registry.js';
3
- export const screenshotCommand = cli({
4
- site: 'codex',
5
- name: 'screenshot',
6
- description: 'Capture a snapshot of the current Codex window (DOM + Accessibility tree)',
7
- domain: 'localhost',
8
- strategy: Strategy.UI,
9
- browser: true,
10
- args: [
11
- { name: 'output', required: false, help: 'Output file path (default: /tmp/codex-snapshot.txt)' },
12
- ],
13
- columns: ['Status', 'File'],
14
- func: async (page, kwargs) => {
15
- const outputPath = kwargs.output || '/tmp/codex-snapshot.txt';
16
- const snap = await page.snapshot({ compact: true });
17
- const html = await page.evaluate('document.documentElement.outerHTML');
18
- const htmlPath = outputPath.replace(/\.\w+$/, '') + '-dom.html';
19
- const snapPath = outputPath.replace(/\.\w+$/, '') + '-a11y.txt';
20
- fs.writeFileSync(htmlPath, html);
21
- fs.writeFileSync(snapPath, typeof snap === 'string' ? snap : JSON.stringify(snap, null, 2));
22
- return [
23
- { Status: 'Success', File: htmlPath },
24
- { Status: 'Success', File: snapPath },
25
- ];
26
- },
27
- });
1
+ import { makeScreenshotCommand } from '../_shared/desktop-commands.js';
2
+ export const screenshotCommand = makeScreenshotCommand('codex', 'Codex');
@@ -1,4 +1,5 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
+ import { SelectorError } from '../../errors.js';
2
3
  export const sendCommand = cli({
3
4
  site: 'codex',
4
5
  name: 'send',
@@ -10,7 +11,7 @@ export const sendCommand = cli({
10
11
  columns: ['Status', 'InjectedText'],
11
12
  func: async (page, kwargs) => {
12
13
  const textToInsert = kwargs.text;
13
- await page.evaluate(`
14
+ const injected = await page.evaluate(`
14
15
  (function(text) {
15
16
  let composer = document.querySelector('textarea, [contenteditable="true"]');
16
17
 
@@ -19,14 +20,15 @@ export const sendCommand = cli({
19
20
  composer = editables[editables.length - 1];
20
21
  }
21
22
 
22
- if (!composer) {
23
- throw new Error('Could not find Composer input element in Codex UI');
24
- }
23
+ if (!composer) return false;
25
24
 
26
25
  composer.focus();
27
26
  document.execCommand('insertText', false, text);
27
+ return true;
28
28
  })(${JSON.stringify(textToInsert)})
29
29
  `);
30
+ if (!injected)
31
+ throw new SelectorError('Codex Composer input element');
30
32
  // Wait for the UI to register the input
31
33
  await page.wait(0.5);
32
34
  // Simulate Enter key to submit
@@ -1,22 +1,2 @@
1
- import { cli, Strategy } from '../../registry.js';
2
- export const statusCommand = cli({
3
- site: 'codex',
4
- name: 'status',
5
- description: 'Check active CDP connection to OpenAI Codex App',
6
- domain: 'localhost',
7
- strategy: Strategy.UI,
8
- browser: true,
9
- args: [],
10
- columns: ['Status', 'Url', 'Title'],
11
- func: async (page) => {
12
- const url = await page.evaluate('window.location.href');
13
- const title = await page.evaluate('document.title');
14
- return [
15
- {
16
- Status: 'Connected',
17
- Url: url,
18
- Title: title,
19
- },
20
- ];
21
- },
22
- });
1
+ import { makeStatusCommand } from '../_shared/desktop-commands.js';
2
+ export const statusCommand = makeStatusCommand('codex', 'OpenAI Codex App');
@@ -1,4 +1,5 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
+ import { SelectorError } from '../../errors.js';
2
3
  export const askCommand = cli({
3
4
  site: 'cursor',
4
5
  name: 'ask',
@@ -27,7 +28,7 @@ export const askCommand = cli({
27
28
  return true;
28
29
  })(${JSON.stringify(text)})`);
29
30
  if (!injected)
30
- throw new Error('Could not find input element.');
31
+ throw new SelectorError('Cursor input element');
31
32
  await page.wait(0.5);
32
33
  await page.pressKey('Enter');
33
34
  // Poll until a new assistant message appears or timeout
@@ -1,4 +1,5 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
+ import { SelectorError } from '../../errors.js';
2
3
  export const composerCommand = cli({
3
4
  site: 'cursor',
4
5
  name: 'composer',
@@ -26,7 +27,7 @@ export const composerCommand = cli({
26
27
  return true;
27
28
  })(${JSON.stringify(textToInsert)})`);
28
29
  if (!typed) {
29
- throw new Error('Could not find Cursor Composer input element after pressing Cmd+I.');
30
+ throw new SelectorError('Cursor Composer input element', 'Could not find Cursor Composer input element after pressing Cmd+I.');
30
31
  }
31
32
  await page.wait(0.5);
32
33
  await page.pressKey('Enter');
@@ -1,25 +1,2 @@
1
- import { cli, Strategy } from '../../registry.js';
2
- import * as fs from 'fs';
3
- export const dumpCommand = cli({
4
- site: 'cursor',
5
- name: 'dump',
6
- description: 'Dump the DOM and Accessibility tree of Cursor for reverse-engineering',
7
- domain: 'localhost',
8
- strategy: Strategy.UI,
9
- browser: true,
10
- columns: ['action', 'files'],
11
- func: async (page) => {
12
- // Extract full HTML
13
- const dom = await page.evaluate('document.body.innerHTML');
14
- fs.writeFileSync('/tmp/cursor-dom.html', dom);
15
- // Get accessibility snapshot
16
- const snap = await page.snapshot({ interactive: false });
17
- fs.writeFileSync('/tmp/cursor-snapshot.json', JSON.stringify(snap, null, 2));
18
- return [
19
- {
20
- action: 'Dom extraction finished',
21
- files: '/tmp/cursor-dom.html, /tmp/cursor-snapshot.json',
22
- },
23
- ];
24
- },
25
- });
1
+ import { makeDumpCommand } from '../_shared/desktop-commands.js';
2
+ export const dumpCommand = makeDumpCommand('cursor');