@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
package/src/plugin.ts CHANGED
@@ -6,16 +6,120 @@
6
6
  */
7
7
 
8
8
  import * as fs from 'node:fs';
9
+ import * as os from 'node:os';
9
10
  import * as path from 'node:path';
10
11
  import { execSync, execFileSync } from 'node:child_process';
12
+ import { fileURLToPath } from 'node:url';
11
13
  import { PLUGINS_DIR } from './discovery.js';
14
+ import { getErrorMessage } from './errors.js';
12
15
  import { log } from './logger.js';
13
16
 
17
+ const isWindows = process.platform === 'win32';
18
+
19
+ /** Get home directory, respecting HOME environment variable for test isolation. */
20
+ function getHomeDir(): string {
21
+ return process.env.HOME || process.env.USERPROFILE || os.homedir();
22
+ }
23
+
24
+ /** Path to the lock file that tracks installed plugin versions. */
25
+ export function getLockFilePath(): string {
26
+ return path.join(getHomeDir(), '.opencli', 'plugins.lock.json');
27
+ }
28
+
29
+ // Legacy const for backward compatibility (computed at load time)
30
+ export const LOCK_FILE = path.join(os.homedir(), '.opencli', 'plugins.lock.json');
31
+
32
+ export interface LockEntry {
33
+ source: string;
34
+ commitHash: string;
35
+ installedAt: string;
36
+ updatedAt?: string;
37
+ }
38
+
14
39
  export interface PluginInfo {
15
40
  name: string;
16
41
  path: string;
17
42
  commands: string[];
18
43
  source?: string;
44
+ version?: string;
45
+ installedAt?: string;
46
+ }
47
+
48
+ // ── Validation helpers ──────────────────────────────────────────────────────
49
+
50
+ export interface ValidationResult {
51
+ valid: boolean;
52
+ errors: string[];
53
+ }
54
+
55
+ // ── Lock file helpers ───────────────────────────────────────────────────────
56
+
57
+ export function readLockFile(): Record<string, LockEntry> {
58
+ try {
59
+ const raw = fs.readFileSync(getLockFilePath(), 'utf-8');
60
+ return JSON.parse(raw) as Record<string, LockEntry>;
61
+ } catch {
62
+ return {};
63
+ }
64
+ }
65
+
66
+ export function writeLockFile(lock: Record<string, LockEntry>): void {
67
+ const lockPath = getLockFilePath();
68
+ fs.mkdirSync(path.dirname(lockPath), { recursive: true });
69
+ fs.writeFileSync(lockPath, JSON.stringify(lock, null, 2) + '\n');
70
+ }
71
+
72
+ /** Get the HEAD commit hash of a git repo directory. */
73
+ export function getCommitHash(dir: string): string | undefined {
74
+ try {
75
+ return execFileSync('git', ['rev-parse', 'HEAD'], {
76
+ cwd: dir,
77
+ encoding: 'utf-8',
78
+ stdio: ['pipe', 'pipe', 'pipe'],
79
+ }).trim();
80
+ } catch {
81
+ return undefined;
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Validate that a downloaded plugin directory is a structurally valid plugin.
87
+ * Checks for at least one command file (.yaml, .yml, .ts, .js) and a valid
88
+ * package.json if it contains .ts files.
89
+ */
90
+ export function validatePluginStructure(pluginDir: string): ValidationResult {
91
+ const errors: string[] = [];
92
+
93
+ if (!fs.existsSync(pluginDir)) {
94
+ return { valid: false, errors: ['Plugin directory does not exist'] };
95
+ }
96
+
97
+ const files = fs.readdirSync(pluginDir);
98
+ const hasYaml = files.some(f => f.endsWith('.yaml') || f.endsWith('.yml'));
99
+ const hasTs = files.some(f => f.endsWith('.ts') && !f.endsWith('.d.ts') && !f.endsWith('.test.ts'));
100
+ const hasJs = files.some(f => f.endsWith('.js') && !f.endsWith('.d.js'));
101
+
102
+ if (!hasYaml && !hasTs && !hasJs) {
103
+ errors.push(`No command files found in plugin directory. A plugin must contain at least one .yaml, .ts, or .js command file.`);
104
+ }
105
+
106
+ if (hasTs) {
107
+ const pkgJsonPath = path.join(pluginDir, 'package.json');
108
+ if (!fs.existsSync(pkgJsonPath)) {
109
+ errors.push(`Plugin contains .ts files but no package.json. A package.json with "type": "module" and "@jackwener/opencli" peer dependency is required for TS plugins.`);
110
+ } else {
111
+ try {
112
+ const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
113
+ if (pkg.type !== 'module') {
114
+ errors.push(`Plugin package.json must have "type": "module" for TypeScript plugins.`);
115
+ }
116
+ } catch {
117
+ errors.push(`Plugin package.json is malformed or invalid JSON.`);
118
+ }
119
+ }
120
+ }
121
+
122
+ return { valid: errors.length === 0, errors };
19
123
  }
20
124
 
21
125
  /**
@@ -31,9 +135,10 @@ function postInstallLifecycle(pluginDir: string): void {
31
135
  cwd: pluginDir,
32
136
  encoding: 'utf-8',
33
137
  stdio: ['pipe', 'pipe', 'pipe'],
138
+ ...(isWindows && { shell: true }),
34
139
  });
35
- } catch {
36
- // Non-fatal: npm install may fail if no real deps
140
+ } catch (err) {
141
+ console.error(`[plugin] npm install failed in ${pluginDir}: ${err instanceof Error ? err.message : err}`);
37
142
  }
38
143
 
39
144
  // Symlink host opencli so TS plugins resolve '@jackwener/opencli/registry'
@@ -74,11 +179,30 @@ export function installPlugin(source: string): string {
74
179
  encoding: 'utf-8',
75
180
  stdio: ['pipe', 'pipe', 'pipe'],
76
181
  });
77
- } catch (err: any) {
78
- throw new Error(`Failed to clone plugin: ${err.message}`);
182
+ } catch (err) {
183
+ throw new Error(`Failed to clone plugin: ${getErrorMessage(err)}`);
184
+ }
185
+
186
+ const validation = validatePluginStructure(targetDir);
187
+ if (!validation.valid) {
188
+ // If validation fails, clean up the cloned directory and abort
189
+ fs.rmSync(targetDir, { recursive: true, force: true });
190
+ throw new Error(`Invalid plugin structure:\n- ${validation.errors.join('\n- ')}`);
79
191
  }
80
192
 
81
193
  postInstallLifecycle(targetDir);
194
+
195
+ const commitHash = getCommitHash(targetDir);
196
+ if (commitHash) {
197
+ const lock = readLockFile();
198
+ lock[name] = {
199
+ source: cloneUrl,
200
+ commitHash,
201
+ installedAt: new Date().toISOString(),
202
+ };
203
+ writeLockFile(lock);
204
+ }
205
+
82
206
  return name;
83
207
  }
84
208
 
@@ -91,6 +215,12 @@ export function uninstallPlugin(name: string): void {
91
215
  throw new Error(`Plugin "${name}" is not installed.`);
92
216
  }
93
217
  fs.rmSync(targetDir, { recursive: true, force: true });
218
+
219
+ const lock = readLockFile();
220
+ if (lock[name]) {
221
+ delete lock[name];
222
+ writeLockFile(lock);
223
+ }
94
224
  }
95
225
 
96
226
  /**
@@ -108,11 +238,54 @@ export function updatePlugin(name: string): void {
108
238
  encoding: 'utf-8',
109
239
  stdio: ['pipe', 'pipe', 'pipe'],
110
240
  });
111
- } catch (err: any) {
112
- throw new Error(`Failed to update plugin: ${err.message}`);
241
+ } catch (err) {
242
+ throw new Error(`Failed to update plugin: ${getErrorMessage(err)}`);
243
+ }
244
+
245
+ const validation = validatePluginStructure(targetDir);
246
+ if (!validation.valid) {
247
+ log.warn(`Plugin "${name}" updated, but structure is now invalid:\n- ${validation.errors.join('\n- ')}`);
113
248
  }
114
249
 
115
250
  postInstallLifecycle(targetDir);
251
+
252
+ const commitHash = getCommitHash(targetDir);
253
+ if (commitHash) {
254
+ const lock = readLockFile();
255
+ const existing = lock[name];
256
+ lock[name] = {
257
+ source: existing?.source ?? getPluginSource(targetDir) ?? '',
258
+ commitHash,
259
+ installedAt: existing?.installedAt ?? new Date().toISOString(),
260
+ updatedAt: new Date().toISOString(),
261
+ };
262
+ writeLockFile(lock);
263
+ }
264
+ }
265
+
266
+ export interface UpdateResult {
267
+ name: string;
268
+ success: boolean;
269
+ error?: string;
270
+ }
271
+
272
+ /**
273
+ * Update all installed plugins.
274
+ * Continues even if individual plugin updates fail.
275
+ */
276
+ export function updateAllPlugins(): UpdateResult[] {
277
+ return listPlugins().map((plugin): UpdateResult => {
278
+ try {
279
+ updatePlugin(plugin.name);
280
+ return { name: plugin.name, success: true };
281
+ } catch (err) {
282
+ return {
283
+ name: plugin.name,
284
+ success: false,
285
+ error: getErrorMessage(err),
286
+ };
287
+ }
288
+ });
116
289
  }
117
290
 
118
291
  /**
@@ -122,6 +295,7 @@ export function listPlugins(): PluginInfo[] {
122
295
  if (!fs.existsSync(PLUGINS_DIR)) return [];
123
296
 
124
297
  const entries = fs.readdirSync(PLUGINS_DIR, { withFileTypes: true });
298
+ const lock = readLockFile();
125
299
  const plugins: PluginInfo[] = [];
126
300
 
127
301
  for (const entry of entries) {
@@ -129,12 +303,15 @@ export function listPlugins(): PluginInfo[] {
129
303
  const pluginDir = path.join(PLUGINS_DIR, entry.name);
130
304
  const commands = scanPluginCommands(pluginDir);
131
305
  const source = getPluginSource(pluginDir);
306
+ const lockEntry = lock[entry.name];
132
307
 
133
308
  plugins.push({
134
309
  name: entry.name,
135
310
  path: pluginDir,
136
311
  commands,
137
312
  source,
313
+ version: lockEntry?.commitHash?.slice(0, 7),
314
+ installedAt: lockEntry?.installedAt,
138
315
  });
139
316
  }
140
317
 
@@ -163,7 +340,7 @@ function scanPluginCommands(dir: string): string[] {
163
340
  /** Get git remote origin URL */
164
341
  function getPluginSource(dir: string): string | undefined {
165
342
  try {
166
- return execSync('git config --get remote.origin.url', {
343
+ return execFileSync('git', ['config', '--get', 'remote.origin.url'], {
167
344
  cwd: dir,
168
345
  encoding: 'utf-8',
169
346
  stdio: ['pipe', 'pipe', 'pipe'],
@@ -210,7 +387,7 @@ function linkHostOpencli(pluginDir: string): void {
210
387
  // Determine the host opencli package root from this module's location.
211
388
  // Both dev (tsx src/plugin.ts) and prod (node dist/plugin.js) are one level
212
389
  // deep, so path.dirname + '..' always gives us the package root.
213
- const thisFile = new URL(import.meta.url).pathname;
390
+ const thisFile = fileURLToPath(import.meta.url);
214
391
  const hostRoot = path.resolve(path.dirname(thisFile), '..');
215
392
 
216
393
  const targetLink = path.join(pluginDir, 'node_modules', '@jackwener', 'opencli');
@@ -223,27 +400,85 @@ function linkHostOpencli(pluginDir: string): void {
223
400
  // Ensure parent directory exists
224
401
  fs.mkdirSync(path.dirname(targetLink), { recursive: true });
225
402
 
226
- // Create symlink
227
- fs.symlinkSync(hostRoot, targetLink, 'dir');
403
+ // Use 'junction' on Windows (doesn't require admin privileges),
404
+ // 'dir' symlink on other platforms.
405
+ const linkType = isWindows ? 'junction' : 'dir';
406
+ fs.symlinkSync(hostRoot, targetLink, linkType);
228
407
  log.debug(`Linked host opencli into plugin: ${targetLink} → ${hostRoot}`);
229
- } catch (err: any) {
230
- log.warn(`Failed to link host opencli into plugin: ${err.message}`);
408
+ } catch (err) {
409
+ log.warn(`Failed to link host opencli into plugin: ${getErrorMessage(err)}`);
231
410
  }
232
411
  }
233
412
 
413
+ /**
414
+ * Resolve the path to the esbuild CLI executable with fallback strategies.
415
+ */
416
+ export function resolveEsbuildBin(): string | null {
417
+ const thisFile = fileURLToPath(import.meta.url);
418
+ const hostRoot = path.resolve(path.dirname(thisFile), '..');
419
+
420
+ // Strategy 1 (Windows): prefer the .cmd wrapper which is executable via shell
421
+ if (isWindows) {
422
+ const cmdPath = path.join(hostRoot, 'node_modules', '.bin', 'esbuild.cmd');
423
+ if (fs.existsSync(cmdPath)) {
424
+ return cmdPath;
425
+ }
426
+ }
427
+
428
+ // Strategy 2: resolve esbuild binary via import.meta.resolve
429
+ // (On Unix, shebang scripts are directly executable; on Windows they are not,
430
+ // so this strategy is skipped on Windows in favour of the .cmd wrapper above.)
431
+ if (!isWindows) {
432
+ try {
433
+ const pkgUrl = import.meta.resolve('esbuild/package.json');
434
+ if (pkgUrl.startsWith('file://')) {
435
+ const pkgPath = fileURLToPath(pkgUrl);
436
+ const pkgRaw = fs.readFileSync(pkgPath, 'utf8');
437
+ const pkg = JSON.parse(pkgRaw);
438
+ if (pkg.bin && typeof pkg.bin === 'object' && pkg.bin.esbuild) {
439
+ const binPath = path.resolve(path.dirname(pkgPath), pkg.bin.esbuild);
440
+ if (fs.existsSync(binPath)) return binPath;
441
+ } else if (typeof pkg.bin === 'string') {
442
+ const binPath = path.resolve(path.dirname(pkgPath), pkg.bin);
443
+ if (fs.existsSync(binPath)) return binPath;
444
+ }
445
+ }
446
+ } catch {
447
+ // ignore package resolution failures
448
+ }
449
+ }
450
+
451
+ // Strategy 3: fallback to node_modules/.bin/esbuild (Unix)
452
+ const binFallback = path.join(hostRoot, 'node_modules', '.bin', 'esbuild');
453
+ if (fs.existsSync(binFallback)) {
454
+ return binFallback;
455
+ }
456
+
457
+ // Strategy 4: global esbuild in PATH
458
+ try {
459
+ const lookupCmd = isWindows ? 'where esbuild' : 'which esbuild';
460
+ // `where` on Windows may return multiple lines; take only the first match.
461
+ const globalBin = execSync(lookupCmd, { encoding: 'utf-8', stdio: 'pipe' }).trim().split('\n')[0].trim();
462
+ if (globalBin && fs.existsSync(globalBin)) {
463
+ return globalBin;
464
+ }
465
+ } catch {
466
+ // ignore PATH lookup failures
467
+ }
468
+
469
+ return null;
470
+ }
471
+
234
472
  /**
235
473
  * Transpile TS plugin files to JS so they work in production mode.
236
474
  * Uses esbuild from the host opencli's node_modules for fast single-file transpilation.
237
475
  */
238
476
  function transpilePluginTs(pluginDir: string): void {
239
477
  try {
240
- // Resolve esbuild binary from the host opencli's node_modules
241
- const thisFile = new URL(import.meta.url).pathname;
242
- const hostRoot = path.resolve(path.dirname(thisFile), '..');
243
- const esbuildBin = path.join(hostRoot, 'node_modules', '.bin', 'esbuild');
478
+ const esbuildBin = resolveEsbuildBin();
244
479
 
245
- if (!fs.existsSync(esbuildBin)) {
246
- log.debug('esbuild not found in host node_modules, skipping TS transpilation');
480
+ if (!esbuildBin) {
481
+ log.debug('esbuild not found in host node_modules, via resolve, or in PATH, skipping TS transpilation');
247
482
  return;
248
483
  }
249
484
 
@@ -264,10 +499,11 @@ function transpilePluginTs(pluginDir: string): void {
264
499
  cwd: pluginDir,
265
500
  encoding: 'utf-8',
266
501
  stdio: ['pipe', 'pipe', 'pipe'],
502
+ ...(isWindows && { shell: true }),
267
503
  });
268
504
  log.debug(`Transpiled plugin file: ${tsFile} → ${jsFile}`);
269
- } catch (err: any) {
270
- log.warn(`Failed to transpile ${tsFile}: ${err.message}`);
505
+ } catch (err) {
506
+ log.warn(`Failed to transpile ${tsFile}: ${getErrorMessage(err)}`);
271
507
  }
272
508
  }
273
509
  } catch {
@@ -275,4 +511,12 @@ function transpilePluginTs(pluginDir: string): void {
275
511
  }
276
512
  }
277
513
 
278
- export { parseSource as _parseSource };
514
+ export {
515
+ resolveEsbuildBin as _resolveEsbuildBin,
516
+ getCommitHash as _getCommitHash,
517
+ parseSource as _parseSource,
518
+ readLockFile as _readLockFile,
519
+ updateAllPlugins as _updateAllPlugins,
520
+ validatePluginStructure as _validatePluginStructure,
521
+ writeLockFile as _writeLockFile,
522
+ };
package/src/record.ts CHANGED
@@ -19,12 +19,15 @@ import chalk from 'chalk';
19
19
  import yaml from 'js-yaml';
20
20
  import { sendCommand } from './browser/daemon-client.js';
21
21
  import type { IPage } from './types.js';
22
+ import { SEARCH_PARAMS, PAGINATION_PARAMS, FIELD_ROLES } from './constants.js';
22
23
  import {
23
- VOLATILE_PARAMS,
24
- SEARCH_PARAMS,
25
- PAGINATION_PARAMS,
26
- FIELD_ROLES,
27
- } from './constants.js';
24
+ urlToPattern,
25
+ findArrayPath,
26
+ inferCapabilityName,
27
+ inferStrategy,
28
+ detectAuthFromContent,
29
+ classifyQueryParams,
30
+ } from './analysis.js';
28
31
 
29
32
  // ── Types ──────────────────────────────────────────────────────────────────
30
33
 
@@ -142,78 +145,6 @@ function generateReadRecordedJs(): string {
142
145
 
143
146
  // ── Analysis helpers ───────────────────────────────────────────────────────
144
147
 
145
- function urlToPattern(url: string): string {
146
- try {
147
- const p = new URL(url);
148
- const pathNorm = p.pathname
149
- .replace(/\/\d+/g, '/{id}')
150
- .replace(/\/[0-9a-fA-F]{8,}/g, '/{hex}')
151
- .replace(/\/BV[a-zA-Z0-9]{10}/g, '/{bvid}');
152
- const params: string[] = [];
153
- p.searchParams.forEach((_v, k) => { if (!VOLATILE_PARAMS.has(k)) params.push(k); });
154
- return `${p.host}${pathNorm}${params.length ? '?' + params.sort().map(k => `${k}={}`).join('&') : ''}`;
155
- } catch { return url; }
156
- }
157
-
158
- function detectAuthIndicators(url: string, body: unknown): string[] {
159
- const indicators: string[] = [];
160
- // Heuristic: if body contains sign/w_rid fields, it's likely signed
161
- if (body && typeof body === 'object') {
162
- const keys = Object.keys(body as object).map(k => k.toLowerCase());
163
- if (keys.some(k => k.includes('sign') || k === 'w_rid' || k.includes('token'))) {
164
- indicators.push('signature');
165
- }
166
- }
167
- // Check URL for common auth patterns
168
- if (url.includes('/wbi/') || url.includes('w_rid=')) indicators.push('signature');
169
- if (url.includes('bearer') || url.includes('access_token')) indicators.push('bearer');
170
- return indicators;
171
- }
172
-
173
- function findArrayPath(obj: unknown, depth = 0): { path: string; items: unknown[] } | null {
174
- if (depth > 5 || !obj || typeof obj !== 'object') return null;
175
- if (Array.isArray(obj)) {
176
- if (obj.length >= 2 && obj.some(i => i && typeof i === 'object' && !Array.isArray(i))) {
177
- return { path: '', items: obj };
178
- }
179
- return null;
180
- }
181
- let best: { path: string; items: unknown[] } | null = null;
182
- for (const [key, val] of Object.entries(obj as Record<string, unknown>)) {
183
- const found = findArrayPath(val, depth + 1);
184
- if (found) {
185
- const fullPath = found.path ? `${key}.${found.path}` : key;
186
- const candidate = { path: fullPath, items: found.items };
187
- if (!best || candidate.items.length > best.items.length) best = candidate;
188
- }
189
- }
190
- return best;
191
- }
192
-
193
- function inferCapabilityName(url: string): string {
194
- const u = url.toLowerCase();
195
- if (u.includes('hot') || u.includes('popular') || u.includes('ranking') || u.includes('trending')) return 'hot';
196
- if (u.includes('search')) return 'search';
197
- if (u.includes('feed') || u.includes('timeline') || u.includes('dynamic')) return 'feed';
198
- if (u.includes('comment') || u.includes('reply')) return 'comments';
199
- if (u.includes('history')) return 'history';
200
- if (u.includes('profile') || u.includes('me')) return 'me';
201
- if (u.includes('favorite') || u.includes('collect') || u.includes('bookmark')) return 'favorite';
202
- try {
203
- const segs = new URL(url).pathname
204
- .split('/')
205
- .filter(s => s && !s.match(/^\d+$/) && !s.match(/^[0-9a-f]{8,}$/i) && !s.match(/^v\d+$/));
206
- if (segs.length) return segs[segs.length - 1].replace(/[^a-z0-9]/gi, '_').toLowerCase();
207
- } catch {}
208
- return 'data';
209
- }
210
-
211
- function inferStrategy(authIndicators: string[]): string {
212
- if (authIndicators.includes('signature')) return 'intercept';
213
- if (authIndicators.includes('bearer') || authIndicators.includes('csrf')) return 'header';
214
- return 'cookie';
215
- }
216
-
217
148
  function scoreRequest(req: RecordedRequest, arrayResult: ReturnType<typeof findArrayPath> | null): number {
218
149
  let s = 0;
219
150
  if (arrayResult) {
@@ -267,10 +198,7 @@ function buildRecordedYaml(
267
198
  : itemPath.split('.').map(p => `?.${p}`).join('');
268
199
 
269
200
  // Detect search/limit/page params (must be before fetch URL building to use hasSearch/hasPage)
270
- const qp: string[] = [];
271
- try { new URL(req.url).searchParams.forEach((_v, k) => { if (!VOLATILE_PARAMS.has(k)) qp.push(k); }); } catch {}
272
- const hasSearch = qp.some(p => SEARCH_PARAMS.has(p));
273
- const hasPage = qp.some(p => PAGINATION_PARAMS.has(p));
201
+ const { hasSearch, hasPagination: hasPage } = classifyQueryParams(req.url);
274
202
 
275
203
  // Build evaluate script
276
204
  const mapLines = Object.entries(detectedFields)
@@ -397,10 +325,9 @@ export async function recordSession(opts: RecordOptions): Promise<RecordResult>
397
325
  const stop = () => { stopped = true; };
398
326
 
399
327
  const { promise: enterPromise, cleanup: cleanupEnter } = waitForEnter();
400
- const enterRace = enterPromise.then(stop);
328
+ enterPromise.then(stop);
401
329
  const timeoutPromise = new Promise<void>(r => setTimeout(() => {
402
330
  stop();
403
- cleanupEnter(); // close readline to prevent process from hanging
404
331
  r();
405
332
  }, timeoutMs));
406
333
 
@@ -428,6 +355,7 @@ export async function recordSession(opts: RecordOptions): Promise<RecordResult>
428
355
  }, pollMs);
429
356
 
430
357
  await Promise.race([enterPromise, timeoutPromise]);
358
+ cleanupEnter(); // Always clean up readline to prevent process from hanging
431
359
  clearInterval(pollInterval);
432
360
 
433
361
  // Final drain from all known tabs
@@ -532,7 +460,7 @@ function analyzeAndWrite(
532
460
  const scored: ScoredEntry[] = [];
533
461
  for (const [pattern, req] of seen) {
534
462
  const arrayResult = findArrayPath(req.body);
535
- const authIndicators = detectAuthIndicators(req.url, req.body);
463
+ const authIndicators = detectAuthFromContent(req.url, req.body);
536
464
  const score = scoreRequest(req, arrayResult);
537
465
  if (score > 0) {
538
466
  scored.push({ req, pattern, arrayResult, authIndicators, score });
@@ -10,3 +10,5 @@
10
10
  export { cli, Strategy, getRegistry, fullName, registerCommand } from './registry.js';
11
11
  export type { CliCommand, Arg, CliOptions } from './registry.js';
12
12
  export type { IPage } from './types.js';
13
+ export { onStartup, onBeforeExecute, onAfterExecute } from './hooks.js';
14
+ export type { HookFn, HookContext, HookName } from './hooks.js';
package/src/registry.ts CHANGED
@@ -22,6 +22,11 @@ export interface Arg {
22
22
  choices?: string[];
23
23
  }
24
24
 
25
+ export interface RequiredEnv {
26
+ name: string;
27
+ help?: string;
28
+ }
29
+
25
30
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- kwargs from CLI parsing are inherently untyped
26
31
  export type CommandArgs = Record<string, any>;
27
32
 
@@ -40,6 +45,7 @@ export interface CliCommand {
40
45
  /** Origin of this command: 'yaml', 'ts', or plugin name. */
41
46
  source?: string;
42
47
  footerExtra?: (kwargs: CommandArgs) => string | undefined;
48
+ requiredEnv?: RequiredEnv[];
43
49
  /**
44
50
  * Control pre-navigation for cookie/header context before command execution.
45
51
  *
@@ -88,6 +94,7 @@ export function cli(opts: CliOptions): CliCommand {
88
94
  pipeline: opts.pipeline,
89
95
  timeoutSeconds: opts.timeoutSeconds,
90
96
  footerExtra: opts.footerExtra,
97
+ requiredEnv: opts.requiredEnv,
91
98
  navigateBefore: opts.navigateBefore,
92
99
  };
93
100
 
@@ -112,7 +119,3 @@ export function registerCommand(cmd: CliCommand): void {
112
119
  _registry.set(fullName(cmd), cmd);
113
120
  }
114
121
 
115
- // Re-export serialization helpers from their dedicated module
116
- export { serializeArg, serializeCommand, formatArgSummary, formatRegistryHelpText } from './serialization.js';
117
- export type { SerializedArg } from './serialization.js';
118
-
package/src/runtime.ts CHANGED
@@ -10,10 +10,20 @@ export function getBrowserFactory(): new () => IBrowserFactory {
10
10
  return (process.env.OPENCLI_CDP_ENDPOINT ? CDPBridge : BrowserBridge) as unknown as new () => IBrowserFactory;
11
11
  }
12
12
 
13
- export const DEFAULT_BROWSER_CONNECT_TIMEOUT = parseInt(process.env.OPENCLI_BROWSER_CONNECT_TIMEOUT ?? '30', 10);
14
- export const DEFAULT_BROWSER_COMMAND_TIMEOUT = parseInt(process.env.OPENCLI_BROWSER_COMMAND_TIMEOUT ?? '60', 10);
15
- export const DEFAULT_BROWSER_EXPLORE_TIMEOUT = parseInt(process.env.OPENCLI_BROWSER_EXPLORE_TIMEOUT ?? '120', 10);
16
- export const DEFAULT_BROWSER_SMOKE_TIMEOUT = parseInt(process.env.OPENCLI_BROWSER_SMOKE_TIMEOUT ?? '60', 10);
13
+ function parseEnvTimeout(envVar: string, fallback: number): number {
14
+ const raw = process.env[envVar];
15
+ if (raw === undefined) return fallback;
16
+ const parsed = parseInt(raw, 10);
17
+ if (Number.isNaN(parsed) || parsed <= 0) {
18
+ console.error(`[runtime] Invalid ${envVar}="${raw}", using default ${fallback}s`);
19
+ return fallback;
20
+ }
21
+ return parsed;
22
+ }
23
+
24
+ export const DEFAULT_BROWSER_CONNECT_TIMEOUT = parseEnvTimeout('OPENCLI_BROWSER_CONNECT_TIMEOUT', 30);
25
+ export const DEFAULT_BROWSER_COMMAND_TIMEOUT = parseEnvTimeout('OPENCLI_BROWSER_COMMAND_TIMEOUT', 60);
26
+ export const DEFAULT_BROWSER_EXPLORE_TIMEOUT = parseEnvTimeout('OPENCLI_BROWSER_EXPLORE_TIMEOUT', 120);
17
27
 
18
28
  /**
19
29
  * Timeout with seconds unit. Used for high-level command timeouts.