@jackwener/opencli 1.3.3 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (680) hide show
  1. package/.github/actions/setup-chrome/action.yml +5 -4
  2. package/.github/pull_request_template.md +3 -1
  3. package/.github/workflows/build-extension.yml +7 -1
  4. package/.github/workflows/ci.yml +46 -6
  5. package/.github/workflows/docs.yml +1 -1
  6. package/.github/workflows/e2e-headed.yml +36 -3
  7. package/.github/workflows/release.yml +1 -1
  8. package/.github/workflows/security.yml +0 -3
  9. package/CHANGELOG.md +78 -0
  10. package/CONTRIBUTING.md +6 -3
  11. package/PRIVACY.md +57 -0
  12. package/README.md +31 -4
  13. package/README.zh-CN.md +31 -4
  14. package/SKILL.md +107 -2
  15. package/TESTING.md +1 -0
  16. package/chatwise-opencli.ps1 +82 -0
  17. package/dist/analysis.d.ts +38 -0
  18. package/dist/analysis.js +166 -0
  19. package/dist/browser/cdp.d.ts +0 -4
  20. package/dist/browser/cdp.js +53 -41
  21. package/dist/browser/cdp.test.d.ts +1 -0
  22. package/dist/browser/cdp.test.js +52 -0
  23. package/dist/browser/dom-snapshot.d.ts +2 -2
  24. package/dist/browser/dom-snapshot.js +54 -1
  25. package/dist/browser/dom-snapshot.test.js +36 -0
  26. package/dist/browser/index.d.ts +2 -2
  27. package/dist/browser/index.js +1 -1
  28. package/dist/browser/mcp.d.ts +0 -2
  29. package/dist/browser/mcp.js +2 -3
  30. package/dist/browser/page.d.ts +4 -3
  31. package/dist/browser/page.js +34 -37
  32. package/dist/browser/stealth.d.ts +0 -2
  33. package/dist/browser/stealth.js +24 -9
  34. package/dist/browser.test.js +2 -2
  35. package/dist/build-manifest.js +15 -9
  36. package/dist/build-manifest.test.js +12 -0
  37. package/dist/cascade.js +4 -2
  38. package/dist/cli-manifest.json +1325 -256
  39. package/dist/cli.js +57 -29
  40. package/dist/clis/_shared/desktop-commands.d.ts +22 -0
  41. package/dist/clis/_shared/desktop-commands.js +108 -0
  42. package/dist/clis/antigravity/serve.js +5 -2
  43. package/dist/clis/apple-podcasts/search.js +2 -1
  44. package/dist/clis/arxiv/search.js +3 -3
  45. package/dist/clis/bbc/news.js +0 -1
  46. package/dist/clis/bilibili/dynamic.test.d.ts +1 -0
  47. package/dist/clis/bilibili/dynamic.test.js +68 -0
  48. package/dist/clis/bilibili/favorite.js +4 -2
  49. package/dist/clis/bilibili/following.js +3 -2
  50. package/dist/clis/bilibili/subtitle.js +8 -7
  51. package/dist/clis/bilibili/utils.js +2 -2
  52. package/dist/clis/boss/batchgreet.js +1 -1
  53. package/dist/clis/boss/chatlist.js +1 -1
  54. package/dist/clis/boss/chatmsg.js +1 -1
  55. package/dist/clis/boss/detail.js +1 -1
  56. package/dist/clis/boss/exchange.js +1 -1
  57. package/dist/clis/boss/greet.js +1 -1
  58. package/dist/clis/boss/invite.js +1 -1
  59. package/dist/clis/boss/joblist.js +1 -1
  60. package/dist/clis/boss/mark.js +4 -3
  61. package/dist/clis/boss/recommend.js +1 -1
  62. package/dist/clis/boss/resume.js +1 -1
  63. package/dist/clis/boss/search.js +1 -1
  64. package/dist/clis/boss/send.js +5 -4
  65. package/dist/clis/boss/stats.js +1 -1
  66. package/dist/clis/chatgpt/ask.js +4 -0
  67. package/dist/clis/chatgpt/new.js +5 -1
  68. package/dist/clis/chatgpt/read.js +5 -1
  69. package/dist/clis/chatgpt/send.js +2 -1
  70. package/dist/clis/chatgpt/status.js +5 -1
  71. package/dist/clis/chatwise/ask.js +8 -2
  72. package/dist/clis/chatwise/export.js +2 -0
  73. package/dist/clis/chatwise/history.js +2 -0
  74. package/dist/clis/chatwise/model.js +8 -3
  75. package/dist/clis/chatwise/new.js +3 -18
  76. package/dist/clis/chatwise/read.js +2 -0
  77. package/dist/clis/chatwise/screenshot.js +3 -27
  78. package/dist/clis/chatwise/send.js +8 -2
  79. package/dist/clis/chatwise/shared.d.ts +2 -0
  80. package/dist/clis/chatwise/shared.js +6 -0
  81. package/dist/clis/chatwise/status.js +3 -22
  82. package/dist/clis/codex/ask.js +6 -2
  83. package/dist/clis/codex/dump.js +2 -25
  84. package/dist/clis/codex/new.js +2 -25
  85. package/dist/clis/codex/screenshot.js +2 -27
  86. package/dist/clis/codex/send.js +6 -4
  87. package/dist/clis/codex/status.js +2 -22
  88. package/dist/clis/ctrip/search.js +0 -1
  89. package/dist/clis/cursor/ask.js +2 -1
  90. package/dist/clis/cursor/composer.js +2 -1
  91. package/dist/clis/cursor/dump.js +2 -25
  92. package/dist/clis/cursor/new.js +2 -18
  93. package/dist/clis/cursor/read.js +2 -1
  94. package/dist/clis/cursor/screenshot.js +1 -30
  95. package/dist/clis/cursor/send.js +2 -1
  96. package/dist/clis/cursor/status.js +2 -21
  97. package/dist/clis/dictionary/examples.yaml +25 -0
  98. package/dist/clis/dictionary/search.yaml +27 -0
  99. package/dist/clis/dictionary/synonyms.yaml +25 -0
  100. package/dist/clis/douban/book-hot.js +1 -1
  101. package/dist/clis/douban/movie-hot.js +1 -1
  102. package/dist/clis/douban/search.js +1 -1
  103. package/dist/clis/douban/utils.d.ts +4 -1
  104. package/dist/clis/douban/utils.js +156 -1
  105. package/dist/clis/doubao/ask.js +1 -1
  106. package/dist/clis/doubao/new.js +1 -1
  107. package/dist/clis/doubao/read.js +1 -1
  108. package/dist/clis/doubao/send.js +1 -1
  109. package/dist/clis/doubao/status.js +1 -1
  110. package/dist/clis/doubao-app/ask.js +1 -1
  111. package/dist/clis/doubao-app/new.js +1 -1
  112. package/dist/clis/doubao-app/read.js +1 -1
  113. package/dist/clis/doubao-app/send.js +1 -1
  114. package/dist/clis/douyin/_shared/browser-fetch.d.ts +10 -0
  115. package/dist/clis/douyin/_shared/browser-fetch.js +30 -0
  116. package/dist/clis/douyin/_shared/browser-fetch.test.d.ts +1 -0
  117. package/dist/clis/douyin/_shared/browser-fetch.test.js +31 -0
  118. package/dist/clis/douyin/_shared/creation-id.d.ts +1 -0
  119. package/dist/clis/douyin/_shared/creation-id.js +5 -0
  120. package/dist/clis/douyin/_shared/creation-id.test.d.ts +1 -0
  121. package/dist/clis/douyin/_shared/creation-id.test.js +22 -0
  122. package/dist/clis/douyin/_shared/imagex-upload.d.ts +20 -0
  123. package/dist/clis/douyin/_shared/imagex-upload.js +53 -0
  124. package/dist/clis/douyin/_shared/imagex-upload.test.d.ts +1 -0
  125. package/dist/clis/douyin/_shared/imagex-upload.test.js +87 -0
  126. package/dist/clis/douyin/_shared/sts2.d.ts +8 -0
  127. package/dist/clis/douyin/_shared/sts2.js +15 -0
  128. package/dist/clis/douyin/_shared/text-extra.d.ts +18 -0
  129. package/dist/clis/douyin/_shared/text-extra.js +15 -0
  130. package/dist/clis/douyin/_shared/text-extra.test.d.ts +1 -0
  131. package/dist/clis/douyin/_shared/text-extra.test.js +37 -0
  132. package/dist/clis/douyin/_shared/timing.d.ts +2 -0
  133. package/dist/clis/douyin/_shared/timing.js +22 -0
  134. package/dist/clis/douyin/_shared/timing.test.d.ts +1 -0
  135. package/dist/clis/douyin/_shared/timing.test.js +28 -0
  136. package/dist/clis/douyin/_shared/tos-upload-short-read.test.d.ts +11 -0
  137. package/dist/clis/douyin/_shared/tos-upload-short-read.test.js +83 -0
  138. package/dist/clis/douyin/_shared/tos-upload.d.ts +53 -0
  139. package/dist/clis/douyin/_shared/tos-upload.js +295 -0
  140. package/dist/clis/douyin/_shared/tos-upload.test.d.ts +1 -0
  141. package/dist/clis/douyin/_shared/tos-upload.test.js +229 -0
  142. package/dist/clis/douyin/_shared/transcode.d.ts +27 -0
  143. package/dist/clis/douyin/_shared/transcode.js +45 -0
  144. package/dist/clis/douyin/_shared/transcode.test.d.ts +1 -0
  145. package/dist/clis/douyin/_shared/transcode.test.js +93 -0
  146. package/dist/clis/douyin/_shared/types.d.ts +26 -0
  147. package/dist/clis/douyin/_shared/types.js +1 -0
  148. package/dist/clis/douyin/activities.d.ts +1 -0
  149. package/dist/clis/douyin/activities.js +20 -0
  150. package/dist/clis/douyin/activities.test.d.ts +1 -0
  151. package/dist/clis/douyin/activities.test.js +22 -0
  152. package/dist/clis/douyin/collections.d.ts +1 -0
  153. package/dist/clis/douyin/collections.js +22 -0
  154. package/dist/clis/douyin/collections.test.d.ts +1 -0
  155. package/dist/clis/douyin/collections.test.js +23 -0
  156. package/dist/clis/douyin/delete.d.ts +1 -0
  157. package/dist/clis/douyin/delete.js +18 -0
  158. package/dist/clis/douyin/delete.test.d.ts +1 -0
  159. package/dist/clis/douyin/delete.test.js +11 -0
  160. package/dist/clis/douyin/draft.d.ts +14 -0
  161. package/dist/clis/douyin/draft.js +237 -0
  162. package/dist/clis/douyin/draft.test.d.ts +1 -0
  163. package/dist/clis/douyin/draft.test.js +11 -0
  164. package/dist/clis/douyin/drafts.d.ts +1 -0
  165. package/dist/clis/douyin/drafts.js +23 -0
  166. package/dist/clis/douyin/drafts.test.d.ts +1 -0
  167. package/dist/clis/douyin/drafts.test.js +11 -0
  168. package/dist/clis/douyin/hashtag.d.ts +1 -0
  169. package/dist/clis/douyin/hashtag.js +45 -0
  170. package/dist/clis/douyin/hashtag.test.d.ts +1 -0
  171. package/dist/clis/douyin/hashtag.test.js +25 -0
  172. package/dist/clis/douyin/location.d.ts +1 -0
  173. package/dist/clis/douyin/location.js +24 -0
  174. package/dist/clis/douyin/location.test.d.ts +1 -0
  175. package/dist/clis/douyin/location.test.js +23 -0
  176. package/dist/clis/douyin/profile.d.ts +1 -0
  177. package/dist/clis/douyin/profile.js +28 -0
  178. package/dist/clis/douyin/profile.test.d.ts +1 -0
  179. package/dist/clis/douyin/profile.test.js +11 -0
  180. package/dist/clis/douyin/publish.d.ts +14 -0
  181. package/dist/clis/douyin/publish.js +288 -0
  182. package/dist/clis/douyin/publish.test.d.ts +1 -0
  183. package/dist/clis/douyin/publish.test.js +38 -0
  184. package/dist/clis/douyin/stats.d.ts +1 -0
  185. package/dist/clis/douyin/stats.js +27 -0
  186. package/dist/clis/douyin/stats.test.d.ts +1 -0
  187. package/dist/clis/douyin/stats.test.js +22 -0
  188. package/dist/clis/douyin/update.d.ts +1 -0
  189. package/dist/clis/douyin/update.js +31 -0
  190. package/dist/clis/douyin/update.test.d.ts +1 -0
  191. package/dist/clis/douyin/update.test.js +11 -0
  192. package/dist/clis/douyin/videos.d.ts +1 -0
  193. package/dist/clis/douyin/videos.js +34 -0
  194. package/dist/clis/douyin/videos.test.d.ts +1 -0
  195. package/dist/clis/douyin/videos.test.js +11 -0
  196. package/dist/clis/grok/ask.d.ts +4 -0
  197. package/dist/clis/grok/ask.js +28 -10
  198. package/dist/clis/grok/ask.test.js +18 -0
  199. package/dist/clis/hackernews/search.yaml +1 -1
  200. package/dist/clis/instagram/search.yaml +2 -1
  201. package/dist/clis/jd/item.d.ts +1 -0
  202. package/dist/clis/jd/item.js +96 -0
  203. package/dist/clis/jd/item.test.d.ts +1 -0
  204. package/dist/clis/jd/item.test.js +28 -0
  205. package/dist/clis/jike/feed.js +1 -1
  206. package/dist/clis/jike/search.js +1 -1
  207. package/dist/clis/linkedin/search.js +5 -4
  208. package/dist/clis/linkedin/timeline.d.ts +21 -0
  209. package/dist/clis/linkedin/timeline.js +503 -0
  210. package/dist/clis/linkedin/timeline.test.d.ts +1 -0
  211. package/dist/clis/linkedin/timeline.test.js +81 -0
  212. package/dist/clis/linux-do/search.yaml +3 -1
  213. package/dist/clis/medium/feed.js +1 -1
  214. package/dist/clis/medium/search.js +2 -2
  215. package/dist/clis/medium/user.js +1 -1
  216. package/dist/clis/medium/{shared.js → utils.js} +2 -1
  217. package/dist/clis/pixiv/detail.yaml +49 -0
  218. package/dist/clis/pixiv/download.d.ts +7 -0
  219. package/dist/clis/pixiv/download.js +78 -0
  220. package/dist/clis/pixiv/download.test.d.ts +1 -0
  221. package/dist/clis/pixiv/download.test.js +87 -0
  222. package/dist/clis/pixiv/illusts.d.ts +8 -0
  223. package/dist/clis/pixiv/illusts.js +65 -0
  224. package/dist/clis/pixiv/illusts.test.d.ts +1 -0
  225. package/dist/clis/pixiv/illusts.test.js +99 -0
  226. package/dist/clis/pixiv/ranking.yaml +53 -0
  227. package/dist/clis/pixiv/search.d.ts +6 -0
  228. package/dist/clis/pixiv/search.js +43 -0
  229. package/dist/clis/pixiv/search.test.d.ts +1 -0
  230. package/dist/clis/pixiv/search.test.js +83 -0
  231. package/dist/clis/pixiv/test-utils.d.ts +12 -0
  232. package/dist/clis/pixiv/test-utils.js +23 -0
  233. package/dist/clis/pixiv/user.yaml +46 -0
  234. package/dist/clis/pixiv/utils.d.ts +27 -0
  235. package/dist/clis/pixiv/utils.js +49 -0
  236. package/dist/clis/reddit/comment.js +2 -1
  237. package/dist/clis/reddit/read.js +4 -3
  238. package/dist/clis/reddit/read.test.d.ts +1 -0
  239. package/dist/clis/reddit/read.test.js +28 -0
  240. package/dist/clis/reddit/save.js +2 -1
  241. package/dist/clis/reddit/saved.js +7 -3
  242. package/dist/clis/reddit/subscribe.js +2 -1
  243. package/dist/clis/reddit/upvote.js +2 -1
  244. package/dist/clis/reddit/upvoted.js +7 -3
  245. package/dist/clis/reuters/search.js +0 -1
  246. package/dist/clis/sinablog/article.js +1 -1
  247. package/dist/clis/sinablog/hot.js +1 -1
  248. package/dist/clis/sinablog/user.js +1 -1
  249. package/dist/clis/substack/feed.js +1 -1
  250. package/dist/clis/substack/publication.js +1 -1
  251. package/dist/clis/substack/search.js +3 -2
  252. package/dist/clis/substack/{shared.js → utils.js} +3 -2
  253. package/dist/clis/tiktok/search.yaml +2 -1
  254. package/dist/clis/twitter/accept.js +2 -1
  255. package/dist/clis/twitter/article.js +4 -1
  256. package/dist/clis/twitter/block.js +2 -1
  257. package/dist/clis/twitter/bookmark.js +2 -1
  258. package/dist/clis/twitter/bookmarks.js +3 -2
  259. package/dist/clis/twitter/delete.js +2 -1
  260. package/dist/clis/twitter/follow.js +2 -1
  261. package/dist/clis/twitter/followers.js +3 -2
  262. package/dist/clis/twitter/following.js +3 -2
  263. package/dist/clis/twitter/hide-reply.js +2 -1
  264. package/dist/clis/twitter/like.js +2 -1
  265. package/dist/clis/twitter/notifications.js +2 -1
  266. package/dist/clis/twitter/post.js +2 -1
  267. package/dist/clis/twitter/profile.js +5 -2
  268. package/dist/clis/twitter/reply-dm.js +2 -1
  269. package/dist/clis/twitter/reply.js +2 -1
  270. package/dist/clis/twitter/search.js +32 -13
  271. package/dist/clis/twitter/search.test.d.ts +1 -0
  272. package/dist/clis/twitter/search.test.js +156 -0
  273. package/dist/clis/twitter/thread.js +2 -2
  274. package/dist/clis/twitter/timeline.js +3 -2
  275. package/dist/clis/twitter/trending.js +3 -2
  276. package/dist/clis/twitter/unblock.js +2 -1
  277. package/dist/clis/twitter/unbookmark.js +2 -1
  278. package/dist/clis/twitter/unfollow.js +2 -1
  279. package/dist/clis/v2ex/daily.js +3 -2
  280. package/dist/clis/v2ex/me.js +3 -2
  281. package/dist/clis/v2ex/notifications.js +4 -4
  282. package/dist/clis/web/read.d.ts +16 -0
  283. package/dist/clis/web/read.js +202 -0
  284. package/dist/clis/weibo/comments.d.ts +1 -0
  285. package/dist/clis/weibo/comments.js +53 -0
  286. package/dist/clis/weibo/feed.d.ts +1 -0
  287. package/dist/clis/weibo/feed.js +56 -0
  288. package/dist/clis/weibo/hot.js +0 -1
  289. package/dist/clis/weibo/me.d.ts +1 -0
  290. package/dist/clis/weibo/me.js +76 -0
  291. package/dist/clis/weibo/post.d.ts +1 -0
  292. package/dist/clis/weibo/post.js +75 -0
  293. package/dist/clis/weibo/user.d.ts +1 -0
  294. package/dist/clis/weibo/user.js +63 -0
  295. package/dist/clis/weibo/utils.d.ts +6 -0
  296. package/dist/clis/weibo/utils.js +30 -0
  297. package/dist/clis/weread/search.js +3 -2
  298. package/dist/clis/xueqiu/danjuan-utils.d.ts +55 -0
  299. package/dist/clis/xueqiu/danjuan-utils.js +126 -0
  300. package/dist/clis/xueqiu/danjuan-utils.test.d.ts +1 -0
  301. package/dist/clis/xueqiu/danjuan-utils.test.js +41 -0
  302. package/dist/clis/xueqiu/fund-holdings.d.ts +1 -0
  303. package/dist/clis/xueqiu/fund-holdings.js +28 -0
  304. package/dist/clis/xueqiu/fund-snapshot.d.ts +1 -0
  305. package/dist/clis/xueqiu/fund-snapshot.js +25 -0
  306. package/dist/clis/xueqiu/search.yaml +2 -1
  307. package/dist/clis/yahoo-finance/quote.js +0 -1
  308. package/dist/clis/youtube/channel.d.ts +1 -0
  309. package/dist/clis/youtube/channel.js +150 -0
  310. package/dist/clis/youtube/comments.d.ts +1 -0
  311. package/dist/clis/youtube/comments.js +95 -0
  312. package/dist/clis/youtube/search.js +0 -1
  313. package/dist/clis/youtube/transcript.js +5 -4
  314. package/dist/clis/youtube/video.js +3 -2
  315. package/dist/clis/zhihu/search.yaml +2 -1
  316. package/dist/daemon.js +7 -3
  317. package/dist/discovery.js +11 -10
  318. package/dist/doctor.js +2 -1
  319. package/dist/download/index.d.ts +4 -12
  320. package/dist/download/index.js +33 -12
  321. package/dist/download/index.test.js +79 -2
  322. package/dist/download/media-download.js +4 -2
  323. package/dist/engine.test.js +76 -4
  324. package/dist/execution.d.ts +1 -9
  325. package/dist/execution.js +56 -46
  326. package/dist/explore.js +12 -111
  327. package/dist/external-clis.yaml +0 -25
  328. package/dist/external.js +7 -5
  329. package/dist/external.test.js +4 -0
  330. package/dist/generate.d.ts +0 -9
  331. package/dist/generate.js +4 -20
  332. package/dist/hooks.d.ts +46 -0
  333. package/dist/hooks.js +56 -0
  334. package/dist/hooks.test.d.ts +4 -0
  335. package/dist/hooks.test.js +92 -0
  336. package/dist/interceptor.js +70 -23
  337. package/dist/main.js +2 -0
  338. package/dist/output.js +12 -6
  339. package/dist/pipeline/executor.js +1 -1
  340. package/dist/pipeline/steps/browser.js +1 -3
  341. package/dist/pipeline/steps/download.js +42 -26
  342. package/dist/pipeline/steps/download.test.d.ts +1 -0
  343. package/dist/pipeline/steps/download.test.js +101 -0
  344. package/dist/pipeline/steps/fetch.js +40 -22
  345. package/dist/pipeline/steps/fetch.test.d.ts +1 -0
  346. package/dist/pipeline/steps/fetch.test.js +123 -0
  347. package/dist/pipeline/steps/transform.js +2 -6
  348. package/dist/pipeline/template.js +66 -52
  349. package/dist/pipeline/template.test.js +28 -0
  350. package/dist/pipeline/transform.test.js +18 -0
  351. package/dist/plugin.d.ts +40 -1
  352. package/dist/plugin.js +214 -17
  353. package/dist/plugin.test.d.ts +1 -1
  354. package/dist/plugin.test.js +219 -3
  355. package/dist/record.js +6 -98
  356. package/dist/registry-api.d.ts +2 -0
  357. package/dist/registry-api.js +1 -0
  358. package/dist/registry.d.ts +5 -2
  359. package/dist/registry.js +1 -2
  360. package/dist/runtime.d.ts +0 -1
  361. package/dist/runtime.js +14 -4
  362. package/dist/snapshotFormatter.d.ts +7 -14
  363. package/dist/snapshotFormatter.js +38 -78
  364. package/dist/utils.d.ts +9 -0
  365. package/dist/utils.js +29 -0
  366. package/dist/validate.js +3 -5
  367. package/dist/weread-search-regression.test.d.ts +1 -0
  368. package/dist/weread-search-regression.test.js +39 -0
  369. package/dist/yaml-schema.d.ts +26 -0
  370. package/dist/yaml-schema.js +5 -0
  371. package/docs/.vitepress/config.mts +16 -0
  372. package/docs/adapters/browser/dictionary.md +27 -0
  373. package/docs/adapters/browser/douyin.md +75 -0
  374. package/docs/adapters/browser/jd.md +27 -0
  375. package/docs/adapters/browser/linkedin.md +6 -0
  376. package/docs/adapters/browser/pixiv.md +92 -0
  377. package/docs/adapters/browser/twitter.md +6 -0
  378. package/docs/adapters/browser/web.md +30 -0
  379. package/docs/adapters/browser/xueqiu.md +27 -9
  380. package/docs/adapters/index.md +9 -2
  381. package/docs/comparison.md +125 -0
  382. package/docs/developer/contributing.md +21 -2
  383. package/docs/developer/testing.md +14 -8
  384. package/docs/developer/ts-adapter.md +18 -0
  385. package/docs/developer/yaml-adapter.md +16 -0
  386. package/docs/guide/plugins.md +10 -0
  387. package/docs/zh/guide/plugins.md +10 -0
  388. package/extension/dist/background.js +100 -35
  389. package/extension/manifest.json +6 -2
  390. package/extension/package.json +1 -1
  391. package/extension/popup.html +84 -0
  392. package/extension/popup.js +25 -0
  393. package/extension/src/background.test.ts +46 -1
  394. package/extension/src/background.ts +128 -34
  395. package/extension/src/cdp.ts +9 -9
  396. package/package.json +3 -2
  397. package/scripts/check-doc-coverage.sh +2 -0
  398. package/src/analysis.ts +170 -0
  399. package/src/browser/cdp.test.ts +66 -0
  400. package/src/browser/cdp.ts +59 -44
  401. package/src/browser/dom-snapshot.test.ts +42 -0
  402. package/src/browser/dom-snapshot.ts +56 -3
  403. package/src/browser/index.ts +2 -2
  404. package/src/browser/mcp.ts +2 -4
  405. package/src/browser/page.ts +34 -37
  406. package/src/browser/stealth.ts +24 -10
  407. package/src/browser.test.ts +2 -2
  408. package/src/build-manifest.test.ts +14 -0
  409. package/src/build-manifest.ts +13 -31
  410. package/src/cascade.ts +5 -3
  411. package/src/cli.ts +66 -34
  412. package/src/clis/_shared/desktop-commands.ts +121 -0
  413. package/src/clis/antigravity/serve.ts +6 -3
  414. package/src/clis/apple-podcasts/search.ts +2 -1
  415. package/src/clis/arxiv/search.ts +3 -3
  416. package/src/clis/bbc/news.ts +0 -1
  417. package/src/clis/bilibili/dynamic.test.ts +79 -0
  418. package/src/clis/bilibili/favorite.ts +5 -2
  419. package/src/clis/bilibili/following.ts +3 -2
  420. package/src/clis/bilibili/subtitle.ts +8 -7
  421. package/src/clis/bilibili/utils.ts +2 -2
  422. package/src/clis/boss/batchgreet.ts +1 -1
  423. package/src/clis/boss/chatlist.ts +1 -1
  424. package/src/clis/boss/chatmsg.ts +1 -1
  425. package/src/clis/boss/detail.ts +1 -1
  426. package/src/clis/boss/exchange.ts +1 -1
  427. package/src/clis/boss/greet.ts +1 -1
  428. package/src/clis/boss/invite.ts +1 -1
  429. package/src/clis/boss/joblist.ts +1 -1
  430. package/src/clis/boss/mark.ts +4 -3
  431. package/src/clis/boss/recommend.ts +1 -1
  432. package/src/clis/boss/resume.ts +1 -1
  433. package/src/clis/boss/search.ts +1 -1
  434. package/src/clis/boss/send.ts +5 -4
  435. package/src/clis/boss/stats.ts +1 -1
  436. package/src/clis/chatgpt/ask.ts +5 -0
  437. package/src/clis/chatgpt/new.ts +7 -2
  438. package/src/clis/chatgpt/read.ts +7 -2
  439. package/src/clis/chatgpt/send.ts +3 -2
  440. package/src/clis/chatgpt/status.ts +6 -1
  441. package/src/clis/chatwise/ask.ts +7 -2
  442. package/src/clis/chatwise/export.ts +2 -0
  443. package/src/clis/chatwise/history.ts +2 -0
  444. package/src/clis/chatwise/model.ts +7 -3
  445. package/src/clis/chatwise/new.ts +3 -20
  446. package/src/clis/chatwise/read.ts +2 -0
  447. package/src/clis/chatwise/screenshot.ts +3 -32
  448. package/src/clis/chatwise/send.ts +7 -2
  449. package/src/clis/chatwise/shared.ts +8 -0
  450. package/src/clis/chatwise/status.ts +3 -24
  451. package/src/clis/codex/ask.ts +5 -2
  452. package/src/clis/codex/dump.ts +2 -27
  453. package/src/clis/codex/new.ts +2 -28
  454. package/src/clis/codex/screenshot.ts +2 -32
  455. package/src/clis/codex/send.ts +5 -4
  456. package/src/clis/codex/status.ts +2 -24
  457. package/src/clis/ctrip/search.ts +0 -1
  458. package/src/clis/cursor/ask.ts +2 -1
  459. package/src/clis/cursor/composer.ts +2 -1
  460. package/src/clis/cursor/dump.ts +2 -27
  461. package/src/clis/cursor/new.ts +2 -20
  462. package/src/clis/cursor/read.ts +2 -1
  463. package/src/clis/cursor/screenshot.ts +1 -36
  464. package/src/clis/cursor/send.ts +2 -1
  465. package/src/clis/cursor/status.ts +2 -22
  466. package/src/clis/dictionary/examples.yaml +25 -0
  467. package/src/clis/dictionary/search.yaml +27 -0
  468. package/src/clis/dictionary/synonyms.yaml +25 -0
  469. package/src/clis/douban/book-hot.ts +1 -1
  470. package/src/clis/douban/movie-hot.ts +1 -1
  471. package/src/clis/douban/search.ts +1 -1
  472. package/src/clis/douban/utils.ts +165 -1
  473. package/src/clis/doubao/ask.ts +1 -1
  474. package/src/clis/doubao/new.ts +1 -1
  475. package/src/clis/doubao/read.ts +1 -1
  476. package/src/clis/doubao/send.ts +1 -1
  477. package/src/clis/doubao/status.ts +1 -1
  478. package/src/clis/doubao-app/ask.ts +1 -1
  479. package/src/clis/doubao-app/new.ts +1 -1
  480. package/src/clis/doubao-app/read.ts +1 -1
  481. package/src/clis/doubao-app/send.ts +1 -1
  482. package/src/clis/douyin/_shared/browser-fetch.test.ts +38 -0
  483. package/src/clis/douyin/_shared/browser-fetch.ts +45 -0
  484. package/src/clis/douyin/_shared/creation-id.test.ts +26 -0
  485. package/src/clis/douyin/_shared/creation-id.ts +8 -0
  486. package/src/clis/douyin/_shared/imagex-upload.test.ts +113 -0
  487. package/src/clis/douyin/_shared/imagex-upload.ts +76 -0
  488. package/src/clis/douyin/_shared/sts2.ts +20 -0
  489. package/src/clis/douyin/_shared/text-extra.test.ts +42 -0
  490. package/src/clis/douyin/_shared/text-extra.ts +33 -0
  491. package/src/clis/douyin/_shared/timing.test.ts +38 -0
  492. package/src/clis/douyin/_shared/timing.ts +22 -0
  493. package/src/clis/douyin/_shared/tos-upload-short-read.test.ts +102 -0
  494. package/src/clis/douyin/_shared/tos-upload.test.ts +281 -0
  495. package/src/clis/douyin/_shared/tos-upload.ts +444 -0
  496. package/src/clis/douyin/_shared/transcode.test.ts +117 -0
  497. package/src/clis/douyin/_shared/transcode.ts +78 -0
  498. package/src/clis/douyin/_shared/types.ts +29 -0
  499. package/src/clis/douyin/activities.test.ts +25 -0
  500. package/src/clis/douyin/activities.ts +23 -0
  501. package/src/clis/douyin/collections.test.ts +26 -0
  502. package/src/clis/douyin/collections.ts +25 -0
  503. package/src/clis/douyin/delete.test.ts +12 -0
  504. package/src/clis/douyin/delete.ts +20 -0
  505. package/src/clis/douyin/draft.test.ts +12 -0
  506. package/src/clis/douyin/draft.ts +282 -0
  507. package/src/clis/douyin/drafts.test.ts +12 -0
  508. package/src/clis/douyin/drafts.ts +27 -0
  509. package/src/clis/douyin/hashtag.test.ts +28 -0
  510. package/src/clis/douyin/hashtag.ts +56 -0
  511. package/src/clis/douyin/location.test.ts +26 -0
  512. package/src/clis/douyin/location.ts +27 -0
  513. package/src/clis/douyin/profile.test.ts +12 -0
  514. package/src/clis/douyin/profile.ts +37 -0
  515. package/src/clis/douyin/publish.test.ts +45 -0
  516. package/src/clis/douyin/publish.ts +340 -0
  517. package/src/clis/douyin/stats.test.ts +25 -0
  518. package/src/clis/douyin/stats.ts +30 -0
  519. package/src/clis/douyin/update.test.ts +12 -0
  520. package/src/clis/douyin/update.ts +43 -0
  521. package/src/clis/douyin/videos.test.ts +12 -0
  522. package/src/clis/douyin/videos.ts +49 -0
  523. package/src/clis/grok/ask.test.ts +25 -0
  524. package/src/clis/grok/ask.ts +25 -12
  525. package/src/clis/hackernews/search.yaml +1 -1
  526. package/src/clis/instagram/search.yaml +2 -1
  527. package/src/clis/jd/item.test.ts +35 -0
  528. package/src/clis/jd/item.ts +101 -0
  529. package/src/clis/jike/feed.ts +1 -1
  530. package/src/clis/jike/search.ts +1 -1
  531. package/src/clis/linkedin/search.ts +5 -4
  532. package/src/clis/linkedin/timeline.test.ts +99 -0
  533. package/src/clis/linkedin/timeline.ts +532 -0
  534. package/src/clis/linux-do/search.yaml +3 -1
  535. package/src/clis/medium/feed.ts +1 -1
  536. package/src/clis/medium/search.ts +2 -2
  537. package/src/clis/medium/user.ts +1 -1
  538. package/src/clis/medium/{shared.ts → utils.ts} +2 -1
  539. package/src/clis/pixiv/detail.yaml +49 -0
  540. package/src/clis/pixiv/download.test.ts +114 -0
  541. package/src/clis/pixiv/download.ts +91 -0
  542. package/src/clis/pixiv/illusts.test.ts +115 -0
  543. package/src/clis/pixiv/illusts.ts +78 -0
  544. package/src/clis/pixiv/ranking.yaml +53 -0
  545. package/src/clis/pixiv/search.test.ts +97 -0
  546. package/src/clis/pixiv/search.ts +53 -0
  547. package/src/clis/pixiv/test-utils.ts +29 -0
  548. package/src/clis/pixiv/user.yaml +46 -0
  549. package/src/clis/pixiv/utils.ts +62 -0
  550. package/src/clis/reddit/comment.ts +2 -1
  551. package/src/clis/reddit/read.test.ts +34 -0
  552. package/src/clis/reddit/read.ts +4 -3
  553. package/src/clis/reddit/save.ts +2 -1
  554. package/src/clis/reddit/saved.ts +6 -2
  555. package/src/clis/reddit/subscribe.ts +2 -1
  556. package/src/clis/reddit/upvote.ts +2 -1
  557. package/src/clis/reddit/upvoted.ts +6 -2
  558. package/src/clis/reuters/search.ts +0 -1
  559. package/src/clis/sinablog/article.ts +1 -1
  560. package/src/clis/sinablog/hot.ts +1 -1
  561. package/src/clis/sinablog/user.ts +1 -1
  562. package/src/clis/substack/feed.ts +1 -1
  563. package/src/clis/substack/publication.ts +1 -1
  564. package/src/clis/substack/search.ts +3 -2
  565. package/src/clis/substack/{shared.ts → utils.ts} +3 -2
  566. package/src/clis/tiktok/search.yaml +2 -1
  567. package/src/clis/twitter/accept.ts +2 -1
  568. package/src/clis/twitter/article.ts +3 -1
  569. package/src/clis/twitter/block.ts +2 -1
  570. package/src/clis/twitter/bookmark.ts +2 -1
  571. package/src/clis/twitter/bookmarks.ts +3 -2
  572. package/src/clis/twitter/delete.ts +2 -1
  573. package/src/clis/twitter/follow.ts +2 -1
  574. package/src/clis/twitter/followers.ts +3 -2
  575. package/src/clis/twitter/following.ts +3 -2
  576. package/src/clis/twitter/hide-reply.ts +2 -1
  577. package/src/clis/twitter/like.ts +2 -1
  578. package/src/clis/twitter/notifications.ts +2 -1
  579. package/src/clis/twitter/post.ts +2 -1
  580. package/src/clis/twitter/profile.ts +4 -2
  581. package/src/clis/twitter/reply-dm.ts +2 -1
  582. package/src/clis/twitter/reply.ts +2 -1
  583. package/src/clis/twitter/search.test.ts +180 -0
  584. package/src/clis/twitter/search.ts +40 -14
  585. package/src/clis/twitter/thread.ts +2 -2
  586. package/src/clis/twitter/timeline.ts +3 -2
  587. package/src/clis/twitter/trending.ts +3 -2
  588. package/src/clis/twitter/unblock.ts +2 -1
  589. package/src/clis/twitter/unbookmark.ts +2 -1
  590. package/src/clis/twitter/unfollow.ts +2 -1
  591. package/src/clis/v2ex/daily.ts +3 -2
  592. package/src/clis/v2ex/me.ts +3 -2
  593. package/src/clis/v2ex/notifications.ts +3 -4
  594. package/src/clis/web/read.ts +210 -0
  595. package/src/clis/weibo/comments.ts +54 -0
  596. package/src/clis/weibo/feed.ts +57 -0
  597. package/src/clis/weibo/hot.ts +0 -1
  598. package/src/clis/weibo/me.ts +77 -0
  599. package/src/clis/weibo/post.ts +77 -0
  600. package/src/clis/weibo/user.ts +64 -0
  601. package/src/clis/weibo/utils.ts +32 -0
  602. package/src/clis/weread/search.ts +3 -2
  603. package/src/clis/xueqiu/danjuan-utils.test.ts +49 -0
  604. package/src/clis/xueqiu/danjuan-utils.ts +176 -0
  605. package/src/clis/xueqiu/fund-holdings.ts +32 -0
  606. package/src/clis/xueqiu/fund-snapshot.ts +27 -0
  607. package/src/clis/xueqiu/search.yaml +2 -1
  608. package/src/clis/yahoo-finance/quote.ts +0 -1
  609. package/src/clis/youtube/channel.ts +155 -0
  610. package/src/clis/youtube/comments.ts +97 -0
  611. package/src/clis/youtube/search.ts +0 -1
  612. package/src/clis/youtube/transcript.ts +5 -4
  613. package/src/clis/youtube/video.ts +3 -2
  614. package/src/clis/zhihu/search.yaml +2 -1
  615. package/src/daemon.ts +5 -4
  616. package/src/discovery.ts +12 -34
  617. package/src/doctor.ts +3 -2
  618. package/src/download/index.test.ts +93 -2
  619. package/src/download/index.ts +44 -23
  620. package/src/download/media-download.ts +5 -3
  621. package/src/engine.test.ts +84 -3
  622. package/src/execution.ts +62 -46
  623. package/src/explore.ts +21 -90
  624. package/src/external-clis.yaml +0 -25
  625. package/src/external.test.ts +9 -0
  626. package/src/external.ts +12 -10
  627. package/src/generate.ts +4 -41
  628. package/src/hooks.test.ts +126 -0
  629. package/src/hooks.ts +90 -0
  630. package/src/interceptor.ts +73 -23
  631. package/src/main.ts +2 -0
  632. package/src/output.ts +14 -6
  633. package/src/pipeline/executor.ts +1 -1
  634. package/src/pipeline/steps/browser.ts +1 -3
  635. package/src/pipeline/steps/download.test.ts +136 -0
  636. package/src/pipeline/steps/download.ts +47 -34
  637. package/src/pipeline/steps/fetch.test.ts +179 -0
  638. package/src/pipeline/steps/fetch.ts +39 -23
  639. package/src/pipeline/steps/transform.ts +2 -6
  640. package/src/pipeline/template.test.ts +28 -0
  641. package/src/pipeline/template.ts +67 -79
  642. package/src/pipeline/transform.test.ts +20 -0
  643. package/src/plugin.test.ts +251 -3
  644. package/src/plugin.ts +265 -21
  645. package/src/record.ts +12 -84
  646. package/src/registry-api.ts +2 -0
  647. package/src/registry.ts +7 -4
  648. package/src/runtime.ts +14 -4
  649. package/src/snapshotFormatter.ts +43 -121
  650. package/src/utils.ts +39 -0
  651. package/src/validate.ts +3 -5
  652. package/src/weread-search-regression.test.ts +44 -0
  653. package/src/yaml-schema.ts +28 -0
  654. package/tests/e2e/browser-auth.test.ts +25 -0
  655. package/tests/e2e/browser-public-extended.test.ts +162 -0
  656. package/tests/e2e/browser-public.test.ts +7 -146
  657. package/tests/e2e/plugin-management.test.ts +137 -0
  658. package/tests/e2e/public-commands.test.ts +34 -1
  659. package/vitest.config.ts +33 -8
  660. package/.github/workflows/pkg-pr-new.yml +0 -30
  661. package/dist/clis/douban/shared.d.ts +0 -4
  662. package/dist/clis/douban/shared.js +0 -155
  663. package/src/clis/douban/shared.ts +0 -165
  664. /package/dist/clis/boss/{common.d.ts → utils.d.ts} +0 -0
  665. /package/dist/clis/boss/{common.js → utils.js} +0 -0
  666. /package/dist/clis/doubao/{common.d.ts → utils.d.ts} +0 -0
  667. /package/dist/clis/doubao/{common.js → utils.js} +0 -0
  668. /package/dist/clis/doubao-app/{common.d.ts → utils.d.ts} +0 -0
  669. /package/dist/clis/doubao-app/{common.js → utils.js} +0 -0
  670. /package/dist/clis/jike/{shared.d.ts → utils.d.ts} +0 -0
  671. /package/dist/clis/jike/{shared.js → utils.js} +0 -0
  672. /package/dist/clis/medium/{shared.d.ts → utils.d.ts} +0 -0
  673. /package/dist/clis/sinablog/{shared.d.ts → utils.d.ts} +0 -0
  674. /package/dist/clis/sinablog/{shared.js → utils.js} +0 -0
  675. /package/dist/clis/substack/{shared.d.ts → utils.d.ts} +0 -0
  676. /package/src/clis/boss/{common.ts → utils.ts} +0 -0
  677. /package/src/clis/doubao/{common.ts → utils.ts} +0 -0
  678. /package/src/clis/doubao-app/{common.ts → utils.ts} +0 -0
  679. /package/src/clis/jike/{shared.ts → utils.ts} +0 -0
  680. /package/src/clis/sinablog/{shared.ts → utils.ts} +0 -0
@@ -0,0 +1,123 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+ import { CliError } from '../../errors.js';
3
+ import { stepFetch } from './fetch.js';
4
+ afterEach(() => {
5
+ vi.restoreAllMocks();
6
+ vi.unstubAllGlobals();
7
+ });
8
+ describe('stepFetch', () => {
9
+ // W1 + W4: non-browser single fetch throws CliError with FETCH_ERROR code and full message
10
+ it('throws CliError with FETCH_ERROR code on non-ok responses without a browser session', async () => {
11
+ const jsonMock = vi.fn().mockResolvedValue({ error: 'rate limited' });
12
+ const fetchMock = vi.fn().mockResolvedValue({
13
+ ok: false,
14
+ status: 429,
15
+ statusText: 'Too Many Requests',
16
+ json: jsonMock,
17
+ });
18
+ vi.stubGlobal('fetch', fetchMock);
19
+ const err = await stepFetch(null, { url: 'https://api.example.com/items' }, null, {}).catch((e) => e);
20
+ expect(err).toBeInstanceOf(CliError);
21
+ expect(err.code).toBe('FETCH_ERROR');
22
+ expect(err.message).toBe('HTTP 429 Too Many Requests from https://api.example.com/items');
23
+ expect(jsonMock).not.toHaveBeenCalled();
24
+ });
25
+ // W1 + W3: browser single fetch returns error status from evaluate, outer code throws CliError
26
+ it('throws CliError with FETCH_ERROR code on non-ok responses inside the browser session', async () => {
27
+ const jsonMock = vi.fn().mockResolvedValue({ error: 'auth required' });
28
+ const fetchMock = vi.fn().mockResolvedValue({
29
+ ok: false,
30
+ status: 401,
31
+ statusText: 'Unauthorized',
32
+ json: jsonMock,
33
+ });
34
+ vi.stubGlobal('fetch', fetchMock);
35
+ // Simulate real CDP behavior: evaluate returns a value, errors are thrown outside
36
+ const page = {
37
+ evaluate: vi.fn(async (js) => Function(`return (${js})`)()()),
38
+ };
39
+ const err = await stepFetch(page, { url: 'https://api.example.com/items' }, null, {}).catch((e) => e);
40
+ expect(err).toBeInstanceOf(CliError);
41
+ expect(err.code).toBe('FETCH_ERROR');
42
+ expect(err.message).toBe('HTTP 401 Unauthorized from https://api.example.com/items');
43
+ expect(jsonMock).not.toHaveBeenCalled();
44
+ });
45
+ it('returns per-item HTTP errors for batch fetches without a browser session', async () => {
46
+ const jsonMock = vi.fn().mockResolvedValue({ error: 'upstream unavailable' });
47
+ const fetchMock = vi.fn().mockResolvedValue({
48
+ ok: false,
49
+ status: 503,
50
+ statusText: 'Service Unavailable',
51
+ json: jsonMock,
52
+ });
53
+ vi.stubGlobal('fetch', fetchMock);
54
+ await expect(stepFetch(null, { url: 'https://api.example.com/items/${{ item.id }}' }, [{ id: 1 }], {})).resolves.toEqual([
55
+ { error: 'HTTP 503 Service Unavailable from https://api.example.com/items/1' },
56
+ ]);
57
+ expect(jsonMock).not.toHaveBeenCalled();
58
+ });
59
+ it('returns per-item HTTP errors for batch browser fetches', async () => {
60
+ const jsonMock = vi.fn().mockResolvedValue({ error: 'upstream unavailable' });
61
+ const fetchMock = vi.fn().mockResolvedValue({
62
+ ok: false,
63
+ status: 503,
64
+ statusText: 'Service Unavailable',
65
+ json: jsonMock,
66
+ });
67
+ vi.stubGlobal('fetch', fetchMock);
68
+ const page = {
69
+ evaluate: vi.fn(async (js) => Function(`return (${js})`)()()),
70
+ };
71
+ await expect(stepFetch(page, { url: 'https://api.example.com/items/${{ item.id }}' }, [{ id: 1 }], {})).resolves.toEqual([
72
+ { error: 'HTTP 503 Service Unavailable from https://api.example.com/items/1' },
73
+ ]);
74
+ expect(jsonMock).not.toHaveBeenCalled();
75
+ });
76
+ it('stringifies non-Error batch browser failures consistently', async () => {
77
+ vi.stubGlobal('fetch', vi.fn().mockRejectedValue('socket hang up'));
78
+ const page = {
79
+ evaluate: vi.fn(async (js) => Function(`return (${js})`)()()),
80
+ };
81
+ await expect(stepFetch(page, { url: 'https://api.example.com/items/${{ item.id }}' }, [{ id: 1 }], {})).resolves.toEqual([
82
+ { error: 'socket hang up' },
83
+ ]);
84
+ });
85
+ it('stringifies non-Error batch non-browser failures consistently', async () => {
86
+ vi.stubGlobal('fetch', vi.fn().mockRejectedValue('socket hang up'));
87
+ await expect(stepFetch(null, { url: 'https://api.example.com/items/${{ item.id }}' }, [{ id: 1 }], {})).resolves.toEqual([
88
+ { error: 'socket hang up' },
89
+ ]);
90
+ });
91
+ // W2: batch item failures emit a warning log
92
+ it('logs a warning for each failed batch item in non-browser mode', async () => {
93
+ const { log } = await import('../../logger.js');
94
+ const warnSpy = vi.spyOn(log, 'warn');
95
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
96
+ ok: false,
97
+ status: 503,
98
+ statusText: 'Service Unavailable',
99
+ json: vi.fn(),
100
+ }));
101
+ await stepFetch(null, { url: 'https://api.example.com/items/${{ item.id }}' }, [{ id: 1 }, { id: 2 }], {});
102
+ expect(warnSpy).toHaveBeenCalledTimes(2);
103
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('https://api.example.com/items/1'));
104
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('https://api.example.com/items/2'));
105
+ });
106
+ it('logs a warning for each failed batch item in browser mode', async () => {
107
+ const { log } = await import('../../logger.js');
108
+ const warnSpy = vi.spyOn(log, 'warn');
109
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
110
+ ok: false,
111
+ status: 502,
112
+ statusText: 'Bad Gateway',
113
+ json: vi.fn(),
114
+ }));
115
+ const page = {
116
+ evaluate: vi.fn(async (js) => Function(`return (${js})`)()()),
117
+ };
118
+ await stepFetch(page, { url: 'https://api.example.com/items/${{ item.id }}' }, [{ id: 1 }, { id: 2 }], {});
119
+ expect(warnSpy).toHaveBeenCalledTimes(2);
120
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('https://api.example.com/items/1'));
121
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('https://api.example.com/items/2'));
122
+ });
123
+ });
@@ -2,9 +2,7 @@
2
2
  * Pipeline steps: data transforms — select, map, filter, sort, limit.
3
3
  */
4
4
  import { render, evalExpr } from '../template.js';
5
- function isRecord(value) {
6
- return typeof value === 'object' && value !== null && !Array.isArray(value);
7
- }
5
+ import { isRecord } from '../../utils.js';
8
6
  export async function stepSelect(_page, params, data, args) {
9
7
  const pathStr = String(render(params, { args, data }));
10
8
  if (data && typeof data === 'object') {
@@ -61,9 +59,7 @@ export async function stepSort(_page, params, data, _args) {
61
59
  return [...data].sort((a, b) => {
62
60
  const left = isRecord(a) ? a[key] : undefined;
63
61
  const right = isRecord(b) ? b[key] : undefined;
64
- const va = left ?? '';
65
- const vb = right ?? '';
66
- const cmp = va < vb ? -1 : va > vb ? 1 : 0;
62
+ const cmp = String(left ?? '').localeCompare(String(right ?? ''), undefined, { numeric: true });
67
63
  return reverse ? -cmp : cmp;
68
64
  });
69
65
  }
@@ -1,9 +1,8 @@
1
1
  /**
2
2
  * Pipeline template engine: ${{ ... }} expression rendering.
3
3
  */
4
- function isRecord(value) {
5
- return typeof value === 'object' && value !== null && !Array.isArray(value);
6
- }
4
+ import vm from 'node:vm';
5
+ import { isRecord } from '../utils.js';
7
6
  export function render(template, ctx) {
8
7
  if (typeof template !== 'string')
9
8
  return template;
@@ -29,46 +28,28 @@ export function evalExpr(expr, ctx) {
29
28
  const data = ctx.data;
30
29
  const index = ctx.index ?? 0;
31
30
  // ── Pipe filters: expr | filter1(arg) | filter2 ──
32
- // Supports: default(val), join(sep), upper, lower, truncate(n), trim, replace(old,new)
33
- if (expr.includes('|') && !expr.includes('||')) {
34
- const segments = expr.split('|').map(s => s.trim());
35
- const mainExpr = segments[0];
36
- let result = resolvePath(mainExpr, { args, item, data, index });
37
- for (let i = 1; i < segments.length; i++) {
38
- result = applyFilter(segments[i], result);
31
+ // Split on single | (not ||) so "item.a || item.b | upper" works correctly.
32
+ const pipeSegments = expr.split(/(?<!\|)\|(?!\|)/).map(s => s.trim());
33
+ if (pipeSegments.length > 1) {
34
+ let result = evalExpr(pipeSegments[0], ctx);
35
+ for (let i = 1; i < pipeSegments.length; i++) {
36
+ result = applyFilter(pipeSegments[i], result);
39
37
  }
40
38
  return result;
41
39
  }
42
- // Arithmetic: index + 1
43
- const arithMatch = expr.match(/^([\w][\w.]*)\s*([+\-*/])\s*(\d+)$/);
44
- if (arithMatch) {
45
- const [, varName, op, numStr] = arithMatch;
46
- const val = resolvePath(varName, { args, item, data, index });
47
- if (val !== null && val !== undefined) {
48
- const numVal = Number(val);
49
- const num = Number(numStr);
50
- if (!isNaN(numVal)) {
51
- switch (op) {
52
- case '+': return numVal + num;
53
- case '-': return numVal - num;
54
- case '*': return numVal * num;
55
- case '/': return num !== 0 ? numVal / num : 0;
56
- }
57
- }
58
- }
59
- }
60
- // JS-like fallback expression: item.tweetCount || 'N/A'
61
- const orMatch = expr.match(/^(.+?)\s*\|\|\s*(.+)$/);
62
- if (orMatch) {
63
- const left = evalExpr(orMatch[1].trim(), ctx);
64
- if (left)
65
- return left;
66
- const right = orMatch[2].trim();
67
- return right.replace(/^['"]|['"]$/g, '');
68
- }
40
+ // Fast path: quoted string literal — skip VM overhead
41
+ const strLit = expr.match(/^(['"])(.*)\1$/);
42
+ if (strLit)
43
+ return strLit[2];
44
+ // Fast path: numeric literal
45
+ if (/^\d+(\.\d+)?$/.test(expr))
46
+ return Number(expr);
47
+ // Try resolving as a simple dotted path (item.foo.bar, args.limit, index)
69
48
  const resolved = resolvePath(expr, { args, item, data, index });
70
49
  if (resolved !== null && resolved !== undefined)
71
50
  return resolved;
51
+ // Fallback: evaluate as JS in a sandboxed VM.
52
+ // Handles ||, ??, arithmetic, ternary, method calls, etc. natively.
72
53
  return evalJsExpr(expr, { args, item, data, index });
73
54
  }
74
55
  /**
@@ -87,7 +68,7 @@ function applyFilter(filterExpr, value) {
87
68
  case 'default': {
88
69
  if (value === null || value === undefined || value === '') {
89
70
  const intVal = parseInt(filterArg, 10);
90
- if (!isNaN(intVal) && String(intVal) === filterArg.trim())
71
+ if (!Number.isNaN(intVal) && String(intVal) === filterArg.trim())
91
72
  return intVal;
92
73
  return filterArg;
93
74
  }
@@ -103,7 +84,7 @@ function applyFilter(filterExpr, value) {
103
84
  return typeof value === 'string' ? value.trim() : value;
104
85
  case 'truncate': {
105
86
  const n = parseInt(filterArg, 10) || 50;
106
- return typeof value === 'string' && value.length > n ? value.slice(0, n) + '...' : value;
87
+ return typeof value === 'string' && value.length > n ? `${value.slice(0, n)}...` : value;
107
88
  }
108
89
  case 'replace': {
109
90
  if (typeof value !== 'string')
@@ -132,6 +113,7 @@ function applyFilter(filterExpr, value) {
132
113
  case 'sanitize':
133
114
  // Remove invalid filename characters
134
115
  return typeof value === 'string'
116
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: intentional - strips C0 control chars from filenames
135
117
  ? value.replace(/[<>:"/\\|?*\x00-\x1f]/g, '_')
136
118
  : value;
137
119
  case 'ext': {
@@ -196,26 +178,58 @@ export function resolvePath(pathStr, ctx) {
196
178
  }
197
179
  /**
198
180
  * Evaluate arbitrary JS expressions as a last-resort fallback.
199
- *
200
- * ⚠️ SECURITY NOTE: Uses `new Function()` to execute the expression.
201
- * This is acceptable here because:
202
- * 1. YAML adapters are authored by trusted repo contributors only.
203
- * 2. The expression runs in the same Node.js process (no sandbox).
204
- * 3. Only a curated set of globals is exposed (no require/import/process/fs).
205
- * If opencli ever loads untrusted third-party adapters, this MUST be replaced
206
- * with a proper sandboxed evaluator.
181
+ * Runs inside a `node:vm` sandbox with dynamic code generation disabled.
182
+ */
183
+ const FORBIDDEN_EXPR_PATTERNS = /\b(constructor|__proto__|prototype|globalThis|process|require|import|eval)\b/;
184
+ /**
185
+ * Deep-copy plain data to sever prototype chains, preventing sandbox escape
186
+ * via `args.constructor.constructor('return process')()` etc.
207
187
  */
188
+ function sanitizeContext(obj) {
189
+ if (obj === null || obj === undefined)
190
+ return obj;
191
+ if (typeof obj !== 'object' && typeof obj !== 'function')
192
+ return obj;
193
+ try {
194
+ return JSON.parse(JSON.stringify(obj));
195
+ }
196
+ catch {
197
+ return {};
198
+ }
199
+ }
208
200
  function evalJsExpr(expr, ctx) {
209
201
  // Guard against absurdly long expressions that could indicate injection.
210
202
  if (expr.length > 2000)
211
203
  return undefined;
212
- const args = ctx.args ?? {};
213
- const item = ctx.item ?? {};
214
- const data = ctx.data;
204
+ // Block obvious sandbox escape attempts.
205
+ if (FORBIDDEN_EXPR_PATTERNS.test(expr))
206
+ return undefined;
207
+ const args = sanitizeContext(ctx.args ?? {});
208
+ const item = sanitizeContext(ctx.item ?? {});
209
+ const data = sanitizeContext(ctx.data);
215
210
  const index = ctx.index ?? 0;
216
211
  try {
217
- const fn = new Function('args', 'item', 'data', 'index', 'encodeURIComponent', 'decodeURIComponent', 'JSON', 'Math', 'Number', 'String', 'Boolean', 'Array', 'Object', 'Date', `"use strict"; return (${expr});`);
218
- return fn(args, item, data, index, encodeURIComponent, decodeURIComponent, JSON, Math, Number, String, Boolean, Array, Object, Date);
212
+ return vm.runInNewContext(`(${expr})`, {
213
+ args,
214
+ item,
215
+ data,
216
+ index,
217
+ encodeURIComponent,
218
+ decodeURIComponent,
219
+ JSON,
220
+ Math,
221
+ Number,
222
+ String,
223
+ Boolean,
224
+ Array,
225
+ Date,
226
+ }, {
227
+ timeout: 50,
228
+ contextCodeGeneration: {
229
+ strings: false,
230
+ wasm: false,
231
+ },
232
+ });
219
233
  }
220
234
  catch {
221
235
  return undefined;
@@ -51,6 +51,31 @@ describe('evalExpr', () => {
51
51
  it('evaluates || with truthy left', () => {
52
52
  expect(evalExpr("item.name || 'N/A'", { item: { name: 'Alice' } })).toBe('Alice');
53
53
  });
54
+ it('evaluates chained || fallback (issue #303)', () => {
55
+ // When first two are falsy, should evaluate through to the string literal
56
+ expect(evalExpr("item.a || item.b || 'default'", { item: {} })).toBe('default');
57
+ });
58
+ it('evaluates chained || with middle value truthy', () => {
59
+ expect(evalExpr("item.a || item.b || 'default'", { item: { b: 'middle' } })).toBe('middle');
60
+ });
61
+ it('evaluates chained || with first value truthy', () => {
62
+ expect(evalExpr("item.a || item.b || 'default'", { item: { a: 'first', b: 'middle' } })).toBe('first');
63
+ });
64
+ it('evaluates || with 0 as falsy left (JS semantics)', () => {
65
+ expect(evalExpr("item.count || 'N/A'", { item: { count: 0 } })).toBe('N/A');
66
+ });
67
+ it('evaluates || with empty string as falsy left', () => {
68
+ expect(evalExpr("item.name || 'unknown'", { item: { name: '' } })).toBe('unknown');
69
+ });
70
+ it('evaluates || with numeric fallback returning number type', () => {
71
+ expect(evalExpr('item.a || 42', { item: {} })).toBe(42);
72
+ });
73
+ it('evaluates 4-way chained ||', () => {
74
+ expect(evalExpr("item.a || item.b || item.c || 'last'", { item: { c: 'third' } })).toBe('third');
75
+ });
76
+ it('handles || combined with pipe filter', () => {
77
+ expect(evalExpr("item.a || item.b | upper", { item: { b: 'hello' } })).toBe('HELLO');
78
+ });
54
79
  it('resolves simple path', () => {
55
80
  expect(evalExpr('item.title', { item: { title: 'Test' } })).toBe('Test');
56
81
  });
@@ -63,6 +88,9 @@ describe('evalExpr', () => {
63
88
  it('evaluates method calls on values', () => {
64
89
  expect(evalExpr("args.username.startsWith('@') ? args.username : '@' + args.username", { args: { username: 'alice' } })).toBe('@alice');
65
90
  });
91
+ it('rejects constructor-based sandbox escapes', () => {
92
+ expect(evalExpr("args['cons' + 'tructor']['constructor']('return process')()", { args: {} })).toBeUndefined();
93
+ });
66
94
  it('applies join filter', () => {
67
95
  expect(evalExpr('item.tags | join(,)', { item: { tags: ['a', 'b', 'c'] } })).toBe('a,b,c');
68
96
  });
@@ -85,6 +85,24 @@ describe('stepSort', () => {
85
85
  await stepSort(null, 'score', SAMPLE_DATA, {});
86
86
  expect(SAMPLE_DATA).toEqual(original);
87
87
  });
88
+ it('sorts string-encoded numbers naturally by default', async () => {
89
+ const data = [
90
+ { name: 'A', volume: '99' },
91
+ { name: 'B', volume: '1000' },
92
+ { name: 'C', volume: '250' },
93
+ ];
94
+ const result = await stepSort(null, { by: 'volume', order: 'desc' }, data, {});
95
+ expect(result.map((r) => r.name)).toEqual(['B', 'C', 'A']);
96
+ });
97
+ it('handles missing fields gracefully', async () => {
98
+ const data = [
99
+ { name: 'A', value: '10' },
100
+ { name: 'B' },
101
+ { name: 'C', value: '5' },
102
+ ];
103
+ const result = await stepSort(null, { by: 'value', order: 'asc' }, data, {});
104
+ expect(result.map((r) => r.name)).toEqual(['B', 'C', 'A']);
105
+ });
88
106
  });
89
107
  describe('stepLimit', () => {
90
108
  it('limits array to N items', async () => {
package/dist/plugin.d.ts CHANGED
@@ -4,12 +4,37 @@
4
4
  * Plugins live in ~/.opencli/plugins/<name>/.
5
5
  * Install source format: "github:user/repo"
6
6
  */
7
+ /** Path to the lock file that tracks installed plugin versions. */
8
+ export declare function getLockFilePath(): string;
9
+ export declare const LOCK_FILE: string;
10
+ export interface LockEntry {
11
+ source: string;
12
+ commitHash: string;
13
+ installedAt: string;
14
+ updatedAt?: string;
15
+ }
7
16
  export interface PluginInfo {
8
17
  name: string;
9
18
  path: string;
10
19
  commands: string[];
11
20
  source?: string;
21
+ version?: string;
22
+ installedAt?: string;
23
+ }
24
+ export interface ValidationResult {
25
+ valid: boolean;
26
+ errors: string[];
12
27
  }
28
+ export declare function readLockFile(): Record<string, LockEntry>;
29
+ export declare function writeLockFile(lock: Record<string, LockEntry>): void;
30
+ /** Get the HEAD commit hash of a git repo directory. */
31
+ export declare function getCommitHash(dir: string): string | undefined;
32
+ /**
33
+ * Validate that a downloaded plugin directory is a structurally valid plugin.
34
+ * Checks for at least one command file (.yaml, .yml, .ts, .js) and a valid
35
+ * package.json if it contains .ts files.
36
+ */
37
+ export declare function validatePluginStructure(pluginDir: string): ValidationResult;
13
38
  /**
14
39
  * Install a plugin from a source.
15
40
  * Currently supports "github:user/repo" format (git clone wrapper).
@@ -23,6 +48,16 @@ export declare function uninstallPlugin(name: string): void;
23
48
  * Update a plugin by name (git pull + re-install lifecycle).
24
49
  */
25
50
  export declare function updatePlugin(name: string): void;
51
+ export interface UpdateResult {
52
+ name: string;
53
+ success: boolean;
54
+ error?: string;
55
+ }
56
+ /**
57
+ * Update all installed plugins.
58
+ * Continues even if individual plugin updates fail.
59
+ */
60
+ export declare function updateAllPlugins(): UpdateResult[];
26
61
  /**
27
62
  * List all installed plugins.
28
63
  */
@@ -32,4 +67,8 @@ declare function parseSource(source: string): {
32
67
  cloneUrl: string;
33
68
  name: string;
34
69
  } | null;
35
- export { parseSource as _parseSource };
70
+ /**
71
+ * Resolve the path to the esbuild CLI executable with fallback strategies.
72
+ */
73
+ export declare function resolveEsbuildBin(): string | null;
74
+ export { resolveEsbuildBin as _resolveEsbuildBin, getCommitHash as _getCommitHash, parseSource as _parseSource, readLockFile as _readLockFile, updateAllPlugins as _updateAllPlugins, validatePluginStructure as _validatePluginStructure, writeLockFile as _writeLockFile, };