@jackwener/opencli 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (514) hide show
  1. package/.github/actions/setup-chrome/action.yml +5 -4
  2. package/.github/workflows/build-extension.yml +2 -6
  3. package/.github/workflows/ci.yml +37 -3
  4. package/.github/workflows/e2e-headed.yml +16 -3
  5. package/CHANGELOG.md +23 -0
  6. package/PRIVACY.md +57 -0
  7. package/README.md +36 -7
  8. package/README.zh-CN.md +13 -6
  9. package/SKILL.md +103 -2
  10. package/dist/browser/cdp.d.ts +2 -1
  11. package/dist/browser/discover.d.ts +4 -1
  12. package/dist/browser/discover.js +6 -2
  13. package/dist/browser/errors.d.ts +2 -2
  14. package/dist/browser/errors.js +4 -12
  15. package/dist/browser/mcp.d.ts +2 -1
  16. package/dist/build-manifest.d.ts +2 -0
  17. package/dist/build-manifest.js +39 -14
  18. package/dist/build-manifest.test.js +21 -0
  19. package/dist/capabilityRouting.d.ts +2 -0
  20. package/dist/capabilityRouting.js +2 -1
  21. package/dist/cli-manifest.json +1838 -151
  22. package/dist/cli.js +34 -3
  23. package/dist/clis/36kr/article.d.ts +1 -0
  24. package/dist/clis/36kr/article.js +62 -0
  25. package/dist/clis/36kr/hot.d.ts +3 -0
  26. package/dist/clis/36kr/hot.js +80 -0
  27. package/dist/clis/36kr/hot.test.d.ts +1 -0
  28. package/dist/clis/36kr/hot.test.js +15 -0
  29. package/dist/clis/36kr/news.d.ts +1 -0
  30. package/dist/clis/36kr/news.js +51 -0
  31. package/dist/clis/36kr/news.test.d.ts +1 -0
  32. package/dist/clis/36kr/news.test.js +85 -0
  33. package/dist/clis/36kr/search.d.ts +1 -0
  34. package/dist/clis/36kr/search.js +72 -0
  35. package/dist/clis/apple-podcasts/search.js +2 -1
  36. package/dist/clis/arxiv/search.js +2 -2
  37. package/dist/clis/bbc/news.js +0 -1
  38. package/dist/clis/bilibili/comments.d.ts +5 -0
  39. package/dist/clis/bilibili/comments.js +40 -0
  40. package/dist/clis/bilibili/comments.test.d.ts +1 -0
  41. package/dist/clis/bilibili/comments.test.js +82 -0
  42. package/dist/clis/chatgpt/ask.js +29 -14
  43. package/dist/clis/chatgpt/ax.d.ts +6 -0
  44. package/dist/clis/chatgpt/ax.js +172 -1
  45. package/dist/clis/chatgpt/model.d.ts +1 -0
  46. package/dist/clis/chatgpt/model.js +24 -0
  47. package/dist/clis/chatgpt/send.js +12 -3
  48. package/dist/clis/ctrip/search.js +0 -1
  49. package/dist/clis/douban/download.d.ts +1 -0
  50. package/dist/clis/douban/download.js +67 -0
  51. package/dist/clis/douban/download.test.d.ts +1 -0
  52. package/dist/clis/douban/download.test.js +170 -0
  53. package/dist/clis/douban/photos.d.ts +1 -0
  54. package/dist/clis/douban/photos.js +34 -0
  55. package/dist/clis/douban/utils.d.ts +25 -0
  56. package/dist/clis/douban/utils.js +190 -1
  57. package/dist/clis/douban/utils.test.d.ts +1 -0
  58. package/dist/clis/douban/utils.test.js +64 -0
  59. package/dist/clis/douyin/_shared/browser-fetch.d.ts +10 -0
  60. package/dist/clis/douyin/_shared/browser-fetch.js +30 -0
  61. package/dist/clis/douyin/_shared/browser-fetch.test.d.ts +1 -0
  62. package/dist/clis/douyin/_shared/browser-fetch.test.js +31 -0
  63. package/dist/clis/douyin/_shared/creation-id.d.ts +1 -0
  64. package/dist/clis/douyin/_shared/creation-id.js +5 -0
  65. package/dist/clis/douyin/_shared/creation-id.test.d.ts +1 -0
  66. package/dist/clis/douyin/_shared/creation-id.test.js +22 -0
  67. package/dist/clis/douyin/_shared/imagex-upload.d.ts +20 -0
  68. package/dist/clis/douyin/_shared/imagex-upload.js +53 -0
  69. package/dist/clis/douyin/_shared/imagex-upload.test.d.ts +1 -0
  70. package/dist/clis/douyin/_shared/imagex-upload.test.js +87 -0
  71. package/dist/clis/douyin/_shared/sts2.d.ts +8 -0
  72. package/dist/clis/douyin/_shared/sts2.js +15 -0
  73. package/dist/clis/douyin/_shared/text-extra.d.ts +18 -0
  74. package/dist/clis/douyin/_shared/text-extra.js +15 -0
  75. package/dist/clis/douyin/_shared/text-extra.test.d.ts +1 -0
  76. package/dist/clis/douyin/_shared/text-extra.test.js +37 -0
  77. package/dist/clis/douyin/_shared/timing.d.ts +2 -0
  78. package/dist/clis/douyin/_shared/timing.js +22 -0
  79. package/dist/clis/douyin/_shared/timing.test.d.ts +1 -0
  80. package/dist/clis/douyin/_shared/timing.test.js +28 -0
  81. package/dist/clis/douyin/_shared/tos-upload-short-read.test.d.ts +11 -0
  82. package/dist/clis/douyin/_shared/tos-upload-short-read.test.js +83 -0
  83. package/dist/clis/douyin/_shared/tos-upload.d.ts +53 -0
  84. package/dist/clis/douyin/_shared/tos-upload.js +295 -0
  85. package/dist/clis/douyin/_shared/tos-upload.test.d.ts +1 -0
  86. package/dist/clis/douyin/_shared/tos-upload.test.js +229 -0
  87. package/dist/clis/douyin/_shared/transcode.d.ts +27 -0
  88. package/dist/clis/douyin/_shared/transcode.js +45 -0
  89. package/dist/clis/douyin/_shared/transcode.test.d.ts +1 -0
  90. package/dist/clis/douyin/_shared/transcode.test.js +93 -0
  91. package/dist/clis/douyin/_shared/types.d.ts +26 -0
  92. package/dist/clis/douyin/_shared/types.js +1 -0
  93. package/dist/clis/douyin/activities.d.ts +1 -0
  94. package/dist/clis/douyin/activities.js +20 -0
  95. package/dist/clis/douyin/activities.test.d.ts +1 -0
  96. package/dist/clis/douyin/activities.test.js +22 -0
  97. package/dist/clis/douyin/collections.d.ts +1 -0
  98. package/dist/clis/douyin/collections.js +22 -0
  99. package/dist/clis/douyin/collections.test.d.ts +1 -0
  100. package/dist/clis/douyin/collections.test.js +23 -0
  101. package/dist/clis/douyin/delete.d.ts +1 -0
  102. package/dist/clis/douyin/delete.js +18 -0
  103. package/dist/clis/douyin/delete.test.d.ts +1 -0
  104. package/dist/clis/douyin/delete.test.js +11 -0
  105. package/dist/clis/douyin/draft.d.ts +14 -0
  106. package/dist/clis/douyin/draft.js +237 -0
  107. package/dist/clis/douyin/draft.test.d.ts +1 -0
  108. package/dist/clis/douyin/draft.test.js +11 -0
  109. package/dist/clis/douyin/drafts.d.ts +1 -0
  110. package/dist/clis/douyin/drafts.js +23 -0
  111. package/dist/clis/douyin/drafts.test.d.ts +1 -0
  112. package/dist/clis/douyin/drafts.test.js +11 -0
  113. package/dist/clis/douyin/hashtag.d.ts +1 -0
  114. package/dist/clis/douyin/hashtag.js +45 -0
  115. package/dist/clis/douyin/hashtag.test.d.ts +1 -0
  116. package/dist/clis/douyin/hashtag.test.js +25 -0
  117. package/dist/clis/douyin/location.d.ts +1 -0
  118. package/dist/clis/douyin/location.js +24 -0
  119. package/dist/clis/douyin/location.test.d.ts +1 -0
  120. package/dist/clis/douyin/location.test.js +23 -0
  121. package/dist/clis/douyin/profile.d.ts +1 -0
  122. package/dist/clis/douyin/profile.js +28 -0
  123. package/dist/clis/douyin/profile.test.d.ts +1 -0
  124. package/dist/clis/douyin/profile.test.js +11 -0
  125. package/dist/clis/douyin/publish.d.ts +14 -0
  126. package/dist/clis/douyin/publish.js +288 -0
  127. package/dist/clis/douyin/publish.test.d.ts +1 -0
  128. package/dist/clis/douyin/publish.test.js +38 -0
  129. package/dist/clis/douyin/stats.d.ts +1 -0
  130. package/dist/clis/douyin/stats.js +27 -0
  131. package/dist/clis/douyin/stats.test.d.ts +1 -0
  132. package/dist/clis/douyin/stats.test.js +22 -0
  133. package/dist/clis/douyin/update.d.ts +1 -0
  134. package/dist/clis/douyin/update.js +31 -0
  135. package/dist/clis/douyin/update.test.d.ts +1 -0
  136. package/dist/clis/douyin/update.test.js +11 -0
  137. package/dist/clis/douyin/videos.d.ts +1 -0
  138. package/dist/clis/douyin/videos.js +34 -0
  139. package/dist/clis/douyin/videos.test.d.ts +1 -0
  140. package/dist/clis/douyin/videos.test.js +11 -0
  141. package/dist/clis/hackernews/search.yaml +1 -1
  142. package/dist/clis/imdb/person.d.ts +1 -0
  143. package/dist/clis/imdb/person.js +203 -0
  144. package/dist/clis/imdb/reviews.d.ts +1 -0
  145. package/dist/clis/imdb/reviews.js +88 -0
  146. package/dist/clis/imdb/search.d.ts +1 -0
  147. package/dist/clis/imdb/search.js +161 -0
  148. package/dist/clis/imdb/title.d.ts +1 -0
  149. package/dist/clis/imdb/title.js +93 -0
  150. package/dist/clis/imdb/top.d.ts +1 -0
  151. package/dist/clis/imdb/top.js +53 -0
  152. package/dist/clis/imdb/trending.d.ts +1 -0
  153. package/dist/clis/imdb/trending.js +52 -0
  154. package/dist/clis/imdb/utils.d.ts +46 -0
  155. package/dist/clis/imdb/utils.js +285 -0
  156. package/dist/clis/imdb/utils.test.d.ts +1 -0
  157. package/dist/clis/imdb/utils.test.js +88 -0
  158. package/dist/clis/instagram/search.yaml +2 -1
  159. package/dist/clis/jd/item.d.ts +4 -0
  160. package/dist/clis/jd/item.js +16 -15
  161. package/dist/clis/jd/item.test.js +16 -1
  162. package/dist/clis/linux-do/categories.yaml +38 -9
  163. package/dist/clis/linux-do/category.d.ts +1 -0
  164. package/dist/clis/linux-do/category.js +36 -0
  165. package/dist/clis/linux-do/feed.d.ts +45 -0
  166. package/dist/clis/linux-do/feed.js +397 -0
  167. package/dist/clis/linux-do/feed.test.d.ts +1 -0
  168. package/dist/clis/linux-do/feed.test.js +118 -0
  169. package/dist/clis/linux-do/hot.d.ts +1 -0
  170. package/dist/clis/linux-do/hot.js +25 -0
  171. package/dist/clis/linux-do/latest.d.ts +1 -0
  172. package/dist/clis/linux-do/latest.js +18 -0
  173. package/dist/clis/linux-do/search.yaml +3 -1
  174. package/dist/clis/linux-do/tags.yaml +41 -0
  175. package/dist/clis/linux-do/topic.yaml +41 -3
  176. package/dist/clis/linux-do/user-posts.yaml +67 -0
  177. package/dist/clis/linux-do/user-topics.yaml +54 -0
  178. package/dist/clis/medium/search.js +1 -1
  179. package/dist/clis/paperreview/commands.test.d.ts +3 -0
  180. package/dist/clis/paperreview/commands.test.js +243 -0
  181. package/dist/clis/paperreview/feedback.d.ts +1 -0
  182. package/dist/clis/paperreview/feedback.js +52 -0
  183. package/dist/clis/paperreview/review.d.ts +1 -0
  184. package/dist/clis/paperreview/review.js +37 -0
  185. package/dist/clis/paperreview/submit.d.ts +1 -0
  186. package/dist/clis/paperreview/submit.js +85 -0
  187. package/dist/clis/paperreview/utils.d.ts +46 -0
  188. package/dist/clis/paperreview/utils.js +197 -0
  189. package/dist/clis/paperreview/utils.test.d.ts +1 -0
  190. package/dist/clis/paperreview/utils.test.js +49 -0
  191. package/dist/clis/producthunt/browse.d.ts +1 -0
  192. package/dist/clis/producthunt/browse.js +99 -0
  193. package/dist/clis/producthunt/hot.d.ts +1 -0
  194. package/dist/clis/producthunt/hot.js +110 -0
  195. package/dist/clis/producthunt/posts.d.ts +1 -0
  196. package/dist/clis/producthunt/posts.js +28 -0
  197. package/dist/clis/producthunt/today.d.ts +1 -0
  198. package/dist/clis/producthunt/today.js +35 -0
  199. package/dist/clis/producthunt/utils.d.ts +29 -0
  200. package/dist/clis/producthunt/utils.js +99 -0
  201. package/dist/clis/producthunt/utils.test.d.ts +1 -0
  202. package/dist/clis/producthunt/utils.test.js +64 -0
  203. package/dist/clis/reuters/search.js +0 -1
  204. package/dist/clis/twitter/article.js +4 -28
  205. package/dist/clis/twitter/likes.d.ts +24 -0
  206. package/dist/clis/twitter/likes.js +217 -0
  207. package/dist/clis/twitter/likes.test.d.ts +1 -0
  208. package/dist/clis/twitter/likes.test.js +85 -0
  209. package/dist/clis/twitter/profile.js +4 -28
  210. package/dist/clis/twitter/search.js +7 -4
  211. package/dist/clis/twitter/search.test.js +56 -2
  212. package/dist/clis/twitter/shared.d.ts +6 -0
  213. package/dist/clis/twitter/shared.js +35 -0
  214. package/dist/clis/twitter/timeline.js +2 -13
  215. package/dist/clis/weibo/comments.d.ts +1 -0
  216. package/dist/clis/weibo/comments.js +53 -0
  217. package/dist/clis/weibo/feed.d.ts +1 -0
  218. package/dist/clis/weibo/feed.js +56 -0
  219. package/dist/clis/weibo/hot.js +0 -1
  220. package/dist/clis/weibo/me.d.ts +1 -0
  221. package/dist/clis/weibo/me.js +76 -0
  222. package/dist/clis/weibo/post.d.ts +1 -0
  223. package/dist/clis/weibo/post.js +75 -0
  224. package/dist/clis/weibo/user.d.ts +1 -0
  225. package/dist/clis/weibo/user.js +63 -0
  226. package/dist/clis/weibo/utils.d.ts +6 -0
  227. package/dist/clis/weibo/utils.js +30 -0
  228. package/dist/clis/weixin/download.d.ts +17 -0
  229. package/dist/clis/weixin/download.js +88 -20
  230. package/dist/clis/weread/book.js +2 -2
  231. package/dist/clis/weread/commands.test.d.ts +3 -0
  232. package/dist/clis/weread/commands.test.js +43 -0
  233. package/dist/clis/weread/highlights.js +2 -2
  234. package/dist/clis/weread/notebooks.js +2 -2
  235. package/dist/clis/weread/notes.js +3 -3
  236. package/dist/clis/weread/search.js +3 -2
  237. package/dist/clis/weread/shelf.js +2 -2
  238. package/dist/clis/weread/utils.d.ts +4 -4
  239. package/dist/clis/weread/utils.js +32 -14
  240. package/dist/clis/weread/utils.test.js +1 -28
  241. package/dist/clis/xiaohongshu/comments.d.ts +5 -0
  242. package/dist/clis/xiaohongshu/comments.js +74 -0
  243. package/dist/clis/xiaohongshu/comments.test.d.ts +1 -0
  244. package/dist/clis/xiaohongshu/comments.test.js +79 -0
  245. package/dist/clis/xiaohongshu/publish.js +114 -18
  246. package/dist/clis/xiaohongshu/publish.test.d.ts +1 -0
  247. package/dist/clis/xiaohongshu/publish.test.js +119 -0
  248. package/dist/clis/xueqiu/search.yaml +2 -1
  249. package/dist/clis/yahoo-finance/quote.js +0 -1
  250. package/dist/clis/youtube/channel.d.ts +1 -0
  251. package/dist/clis/youtube/channel.js +150 -0
  252. package/dist/clis/youtube/comments.d.ts +1 -0
  253. package/dist/clis/youtube/comments.js +95 -0
  254. package/dist/clis/youtube/search.js +0 -1
  255. package/dist/clis/zhihu/search.yaml +2 -1
  256. package/dist/commanderAdapter.d.ts +1 -0
  257. package/dist/commanderAdapter.js +176 -29
  258. package/dist/commanderAdapter.test.d.ts +1 -0
  259. package/dist/commanderAdapter.test.js +62 -0
  260. package/dist/daemon.js +17 -1
  261. package/dist/discovery.js +8 -14
  262. package/dist/doctor.d.ts +1 -0
  263. package/dist/doctor.js +9 -2
  264. package/dist/download/index.js +63 -51
  265. package/dist/download/index.test.js +17 -4
  266. package/dist/errors.d.ts +3 -1
  267. package/dist/errors.js +15 -32
  268. package/dist/execution.d.ts +1 -3
  269. package/dist/execution.js +21 -1
  270. package/dist/external-clis.yaml +0 -17
  271. package/dist/hooks.js +2 -0
  272. package/dist/main.js +5 -0
  273. package/dist/output.js +5 -1
  274. package/dist/pipeline/executor.js +3 -4
  275. package/dist/plugin-manifest.d.ts +70 -0
  276. package/dist/plugin-manifest.js +160 -0
  277. package/dist/plugin-manifest.test.d.ts +4 -0
  278. package/dist/plugin-manifest.test.js +179 -0
  279. package/dist/plugin.d.ts +38 -5
  280. package/dist/plugin.js +267 -33
  281. package/dist/plugin.test.js +220 -3
  282. package/dist/registry.d.ts +4 -0
  283. package/dist/registry.js +2 -0
  284. package/dist/runtime-detect.d.ts +21 -0
  285. package/dist/runtime-detect.js +32 -0
  286. package/dist/runtime-detect.test.d.ts +1 -0
  287. package/dist/runtime-detect.test.js +27 -0
  288. package/dist/runtime.js +1 -1
  289. package/dist/serialization.d.ts +2 -0
  290. package/dist/serialization.js +6 -0
  291. package/dist/types.d.ts +1 -0
  292. package/dist/update-check.d.ts +22 -0
  293. package/dist/update-check.js +112 -0
  294. package/dist/weixin-download.test.d.ts +1 -0
  295. package/dist/weixin-download.test.js +30 -0
  296. package/dist/weread-private-api-regression.test.d.ts +1 -0
  297. package/dist/weread-private-api-regression.test.js +122 -0
  298. package/dist/weread-search-regression.test.d.ts +1 -0
  299. package/dist/weread-search-regression.test.js +39 -0
  300. package/dist/yaml-schema.d.ts +3 -0
  301. package/dist/yaml-schema.js +18 -1
  302. package/docs/.vitepress/config.mts +17 -0
  303. package/docs/adapters/browser/36kr.md +47 -0
  304. package/docs/adapters/browser/douban.md +14 -0
  305. package/docs/adapters/browser/douyin.md +75 -0
  306. package/docs/adapters/browser/imdb.md +47 -0
  307. package/docs/adapters/browser/jd.md +2 -2
  308. package/docs/adapters/browser/linux-do.md +181 -20
  309. package/docs/adapters/browser/paperreview.md +43 -0
  310. package/docs/adapters/browser/producthunt.md +49 -0
  311. package/docs/adapters/browser/twitter.md +6 -0
  312. package/docs/adapters/desktop/chatgpt.md +5 -0
  313. package/docs/adapters/index.md +12 -3
  314. package/docs/advanced/download.md +4 -0
  315. package/docs/advanced/rate-limiter-plugin.md +99 -0
  316. package/docs/guide/electron-app-cli.md +200 -0
  317. package/docs/guide/getting-started.md +1 -0
  318. package/docs/guide/plugins.md +87 -0
  319. package/docs/zh/guide/electron-app-cli.md +188 -0
  320. package/docs/zh/guide/getting-started.md +1 -0
  321. package/docs/zh/guide/plugins.md +65 -0
  322. package/extension/dist/background.js +508 -518
  323. package/extension/manifest.json +6 -2
  324. package/extension/package.json +2 -1
  325. package/extension/popup.html +84 -0
  326. package/extension/popup.js +25 -0
  327. package/extension/scripts/package-release.mjs +179 -0
  328. package/extension/src/background.ts +22 -1
  329. package/package.json +4 -1
  330. package/scripts/postinstall.js +10 -0
  331. package/src/browser/cdp.ts +2 -1
  332. package/src/browser/discover.ts +8 -3
  333. package/src/browser/errors.ts +13 -14
  334. package/src/browser/mcp.ts +2 -1
  335. package/src/build-manifest.test.ts +23 -0
  336. package/src/build-manifest.ts +40 -15
  337. package/src/capabilityRouting.ts +2 -1
  338. package/src/cli.ts +35 -3
  339. package/src/clis/36kr/article.ts +69 -0
  340. package/src/clis/36kr/hot.test.ts +19 -0
  341. package/src/clis/36kr/hot.ts +100 -0
  342. package/src/clis/36kr/news.test.ts +90 -0
  343. package/src/clis/36kr/news.ts +54 -0
  344. package/src/clis/36kr/search.ts +78 -0
  345. package/src/clis/apple-podcasts/search.ts +2 -1
  346. package/src/clis/arxiv/search.ts +2 -2
  347. package/src/clis/bbc/news.ts +0 -1
  348. package/src/clis/bilibili/comments.test.ts +102 -0
  349. package/src/clis/bilibili/comments.ts +44 -0
  350. package/src/clis/chatgpt/ask.ts +28 -14
  351. package/src/clis/chatgpt/ax.ts +180 -1
  352. package/src/clis/chatgpt/model.ts +27 -0
  353. package/src/clis/chatgpt/send.ts +16 -6
  354. package/src/clis/ctrip/search.ts +0 -1
  355. package/src/clis/douban/download.test.ts +196 -0
  356. package/src/clis/douban/download.ts +78 -0
  357. package/src/clis/douban/photos.ts +36 -0
  358. package/src/clis/douban/utils.test.ts +97 -0
  359. package/src/clis/douban/utils.ts +232 -1
  360. package/src/clis/douyin/_shared/browser-fetch.test.ts +38 -0
  361. package/src/clis/douyin/_shared/browser-fetch.ts +45 -0
  362. package/src/clis/douyin/_shared/creation-id.test.ts +26 -0
  363. package/src/clis/douyin/_shared/creation-id.ts +8 -0
  364. package/src/clis/douyin/_shared/imagex-upload.test.ts +113 -0
  365. package/src/clis/douyin/_shared/imagex-upload.ts +76 -0
  366. package/src/clis/douyin/_shared/sts2.ts +20 -0
  367. package/src/clis/douyin/_shared/text-extra.test.ts +42 -0
  368. package/src/clis/douyin/_shared/text-extra.ts +33 -0
  369. package/src/clis/douyin/_shared/timing.test.ts +38 -0
  370. package/src/clis/douyin/_shared/timing.ts +22 -0
  371. package/src/clis/douyin/_shared/tos-upload-short-read.test.ts +102 -0
  372. package/src/clis/douyin/_shared/tos-upload.test.ts +281 -0
  373. package/src/clis/douyin/_shared/tos-upload.ts +444 -0
  374. package/src/clis/douyin/_shared/transcode.test.ts +117 -0
  375. package/src/clis/douyin/_shared/transcode.ts +78 -0
  376. package/src/clis/douyin/_shared/types.ts +29 -0
  377. package/src/clis/douyin/activities.test.ts +25 -0
  378. package/src/clis/douyin/activities.ts +23 -0
  379. package/src/clis/douyin/collections.test.ts +26 -0
  380. package/src/clis/douyin/collections.ts +25 -0
  381. package/src/clis/douyin/delete.test.ts +12 -0
  382. package/src/clis/douyin/delete.ts +20 -0
  383. package/src/clis/douyin/draft.test.ts +12 -0
  384. package/src/clis/douyin/draft.ts +282 -0
  385. package/src/clis/douyin/drafts.test.ts +12 -0
  386. package/src/clis/douyin/drafts.ts +27 -0
  387. package/src/clis/douyin/hashtag.test.ts +28 -0
  388. package/src/clis/douyin/hashtag.ts +56 -0
  389. package/src/clis/douyin/location.test.ts +26 -0
  390. package/src/clis/douyin/location.ts +27 -0
  391. package/src/clis/douyin/profile.test.ts +12 -0
  392. package/src/clis/douyin/profile.ts +37 -0
  393. package/src/clis/douyin/publish.test.ts +45 -0
  394. package/src/clis/douyin/publish.ts +340 -0
  395. package/src/clis/douyin/stats.test.ts +25 -0
  396. package/src/clis/douyin/stats.ts +30 -0
  397. package/src/clis/douyin/update.test.ts +12 -0
  398. package/src/clis/douyin/update.ts +43 -0
  399. package/src/clis/douyin/videos.test.ts +12 -0
  400. package/src/clis/douyin/videos.ts +49 -0
  401. package/src/clis/hackernews/search.yaml +1 -1
  402. package/src/clis/imdb/person.ts +232 -0
  403. package/src/clis/imdb/reviews.ts +111 -0
  404. package/src/clis/imdb/search.ts +179 -0
  405. package/src/clis/imdb/title.ts +121 -0
  406. package/src/clis/imdb/top.ts +67 -0
  407. package/src/clis/imdb/trending.ts +66 -0
  408. package/src/clis/imdb/utils.test.ts +117 -0
  409. package/src/clis/imdb/utils.ts +305 -0
  410. package/src/clis/instagram/search.yaml +2 -1
  411. package/src/clis/jd/item.test.ts +18 -1
  412. package/src/clis/jd/item.ts +18 -15
  413. package/src/clis/linux-do/categories.yaml +38 -9
  414. package/src/clis/linux-do/category.ts +37 -0
  415. package/src/clis/linux-do/feed.test.ts +132 -0
  416. package/src/clis/linux-do/feed.ts +501 -0
  417. package/src/clis/linux-do/hot.ts +26 -0
  418. package/src/clis/linux-do/latest.ts +19 -0
  419. package/src/clis/linux-do/search.yaml +3 -1
  420. package/src/clis/linux-do/tags.yaml +41 -0
  421. package/src/clis/linux-do/topic.yaml +41 -3
  422. package/src/clis/linux-do/user-posts.yaml +67 -0
  423. package/src/clis/linux-do/user-topics.yaml +54 -0
  424. package/src/clis/medium/search.ts +1 -1
  425. package/src/clis/paperreview/commands.test.ts +283 -0
  426. package/src/clis/paperreview/feedback.ts +64 -0
  427. package/src/clis/paperreview/review.ts +47 -0
  428. package/src/clis/paperreview/submit.ts +119 -0
  429. package/src/clis/paperreview/utils.test.ts +68 -0
  430. package/src/clis/paperreview/utils.ts +276 -0
  431. package/src/clis/producthunt/browse.ts +109 -0
  432. package/src/clis/producthunt/hot.ts +127 -0
  433. package/src/clis/producthunt/posts.ts +29 -0
  434. package/src/clis/producthunt/today.ts +37 -0
  435. package/src/clis/producthunt/utils.test.ts +72 -0
  436. package/src/clis/producthunt/utils.ts +122 -0
  437. package/src/clis/reuters/search.ts +0 -1
  438. package/src/clis/twitter/article.ts +5 -28
  439. package/src/clis/twitter/likes.test.ts +91 -0
  440. package/src/clis/twitter/likes.ts +256 -0
  441. package/src/clis/twitter/profile.ts +5 -28
  442. package/src/clis/twitter/search.test.ts +71 -2
  443. package/src/clis/twitter/search.ts +8 -4
  444. package/src/clis/twitter/shared.ts +45 -0
  445. package/src/clis/twitter/timeline.ts +2 -13
  446. package/src/clis/weibo/comments.ts +54 -0
  447. package/src/clis/weibo/feed.ts +57 -0
  448. package/src/clis/weibo/hot.ts +0 -1
  449. package/src/clis/weibo/me.ts +77 -0
  450. package/src/clis/weibo/post.ts +77 -0
  451. package/src/clis/weibo/user.ts +64 -0
  452. package/src/clis/weibo/utils.ts +32 -0
  453. package/src/clis/weixin/download.ts +114 -20
  454. package/src/clis/weread/book.ts +2 -2
  455. package/src/clis/weread/commands.test.ts +57 -0
  456. package/src/clis/weread/highlights.ts +2 -2
  457. package/src/clis/weread/notebooks.ts +2 -2
  458. package/src/clis/weread/notes.ts +3 -3
  459. package/src/clis/weread/search.ts +3 -2
  460. package/src/clis/weread/shelf.ts +2 -2
  461. package/src/clis/weread/utils.test.ts +1 -32
  462. package/src/clis/weread/utils.ts +41 -16
  463. package/src/clis/xiaohongshu/comments.test.ts +96 -0
  464. package/src/clis/xiaohongshu/comments.ts +81 -0
  465. package/src/clis/xiaohongshu/publish.test.ts +137 -0
  466. package/src/clis/xiaohongshu/publish.ts +129 -18
  467. package/src/clis/xueqiu/search.yaml +2 -1
  468. package/src/clis/yahoo-finance/quote.ts +0 -1
  469. package/src/clis/youtube/channel.ts +155 -0
  470. package/src/clis/youtube/comments.ts +97 -0
  471. package/src/clis/youtube/search.ts +0 -1
  472. package/src/clis/zhihu/search.yaml +2 -1
  473. package/src/commanderAdapter.test.ts +78 -0
  474. package/src/commanderAdapter.ts +188 -24
  475. package/src/daemon.ts +19 -1
  476. package/src/discovery.ts +8 -15
  477. package/src/doctor.ts +13 -2
  478. package/src/download/index.test.ts +14 -4
  479. package/src/download/index.ts +67 -55
  480. package/src/errors.ts +25 -66
  481. package/src/execution.ts +28 -3
  482. package/src/external-clis.yaml +0 -17
  483. package/src/hooks.ts +1 -0
  484. package/src/main.ts +6 -0
  485. package/src/output.ts +3 -1
  486. package/src/pipeline/executor.ts +4 -6
  487. package/src/plugin-manifest.test.ts +223 -0
  488. package/src/plugin-manifest.ts +206 -0
  489. package/src/plugin.test.ts +246 -2
  490. package/src/plugin.ts +338 -36
  491. package/src/registry.ts +6 -1
  492. package/src/runtime-detect.test.ts +30 -0
  493. package/src/runtime-detect.ts +36 -0
  494. package/src/runtime.ts +1 -1
  495. package/src/serialization.ts +4 -0
  496. package/src/types.ts +1 -0
  497. package/src/update-check.ts +114 -0
  498. package/src/weixin-download.test.ts +64 -0
  499. package/src/weread-private-api-regression.test.ts +150 -0
  500. package/src/weread-search-regression.test.ts +44 -0
  501. package/src/yaml-schema.ts +20 -0
  502. package/tests/e2e/browser-auth.test.ts +13 -9
  503. package/tests/e2e/browser-public-extended.test.ts +162 -0
  504. package/tests/e2e/browser-public.test.ts +55 -136
  505. package/tests/e2e/helpers.ts +2 -1
  506. package/tests/e2e/public-commands.test.ts +37 -3
  507. package/tests/smoke/api-health.test.ts +1 -1
  508. package/vitest.config.ts +34 -17
  509. package/dist/clis/linux-do/category.yaml +0 -51
  510. package/dist/clis/linux-do/hot.yaml +0 -50
  511. package/dist/clis/linux-do/latest.yaml +0 -40
  512. package/src/clis/linux-do/category.yaml +0 -51
  513. package/src/clis/linux-do/hot.yaml +0 -50
  514. package/src/clis/linux-do/latest.yaml +0 -40
package/src/errors.ts CHANGED
@@ -14,109 +14,61 @@ export class CliError extends Error {
14
14
 
15
15
  constructor(code: string, message: string, hint?: string) {
16
16
  super(message);
17
- this.name = 'CliError';
17
+ this.name = new.target.name;
18
18
  this.code = code;
19
19
  this.hint = hint;
20
20
  }
21
21
  }
22
22
 
23
- // ── Browser / Connection ────────────────────────────────────────────────────
23
+ export type BrowserConnectKind = 'daemon-not-running' | 'extension-not-connected' | 'command-failed' | 'unknown';
24
24
 
25
25
  export class BrowserConnectError extends CliError {
26
- constructor(message: string, hint?: string) {
26
+ readonly kind: BrowserConnectKind;
27
+ constructor(message: string, hint?: string, kind: BrowserConnectKind = 'unknown') {
27
28
  super('BROWSER_CONNECT', message, hint);
28
- this.name = 'BrowserConnectError';
29
+ this.kind = kind;
29
30
  }
30
31
  }
31
32
 
32
- // ── Adapter loading ─────────────────────────────────────────────────────────
33
-
34
33
  export class AdapterLoadError extends CliError {
35
- constructor(message: string, hint?: string) {
36
- super('ADAPTER_LOAD', message, hint);
37
- this.name = 'AdapterLoadError';
38
- }
34
+ constructor(message: string, hint?: string) { super('ADAPTER_LOAD', message, hint); }
39
35
  }
40
36
 
41
- // ── Command execution ───────────────────────────────────────────────────────
42
-
43
37
  export class CommandExecutionError extends CliError {
44
- constructor(message: string, hint?: string) {
45
- super('COMMAND_EXEC', message, hint);
46
- this.name = 'CommandExecutionError';
47
- }
38
+ constructor(message: string, hint?: string) { super('COMMAND_EXEC', message, hint); }
48
39
  }
49
40
 
50
- // ── Configuration ───────────────────────────────────────────────────────────
51
-
52
41
  export class ConfigError extends CliError {
53
- constructor(message: string, hint?: string) {
54
- super('CONFIG', message, hint);
55
- this.name = 'ConfigError';
56
- }
42
+ constructor(message: string, hint?: string) { super('CONFIG', message, hint); }
57
43
  }
58
44
 
59
- // ── Authentication / Login ──────────────────────────────────────────────────
60
-
61
45
  export class AuthRequiredError extends CliError {
62
46
  readonly domain: string;
63
-
64
47
  constructor(domain: string, message?: string) {
65
- super(
66
- 'AUTH_REQUIRED',
67
- message ?? `Not logged in to ${domain}`,
68
- `Please open Chrome and log in to https://${domain}`,
69
- );
70
- this.name = 'AuthRequiredError';
48
+ super('AUTH_REQUIRED', message ?? `Not logged in to ${domain}`, `Please open Chrome and log in to https://${domain}`);
71
49
  this.domain = domain;
72
50
  }
73
51
  }
74
52
 
75
- // ── Timeout ─────────────────────────────────────────────────────────────────
76
-
77
53
  export class TimeoutError extends CliError {
78
54
  constructor(label: string, seconds: number) {
79
- super(
80
- 'TIMEOUT',
81
- `${label} timed out after ${seconds}s`,
82
- 'Try again, or increase timeout with OPENCLI_BROWSER_COMMAND_TIMEOUT env var',
83
- );
84
- this.name = 'TimeoutError';
55
+ super('TIMEOUT', `${label} timed out after ${seconds}s`, 'Try again, or increase timeout with OPENCLI_BROWSER_COMMAND_TIMEOUT env var');
85
56
  }
86
57
  }
87
58
 
88
- // ── Argument validation ─────────────────────────────────────────────────────
89
-
90
59
  export class ArgumentError extends CliError {
91
- constructor(message: string, hint?: string) {
92
- super('ARGUMENT', message, hint);
93
- this.name = 'ArgumentError';
94
- }
60
+ constructor(message: string, hint?: string) { super('ARGUMENT', message, hint); }
95
61
  }
96
62
 
97
- // ── Empty result ────────────────────────────────────────────────────────────
98
-
99
63
  export class EmptyResultError extends CliError {
100
64
  constructor(command: string, hint?: string) {
101
- super(
102
- 'EMPTY_RESULT',
103
- `${command} returned no data`,
104
- hint ?? 'The page structure may have changed, or you may need to log in',
105
- );
106
- this.name = 'EmptyResultError';
65
+ super('EMPTY_RESULT', `${command} returned no data`, hint ?? 'The page structure may have changed, or you may need to log in');
107
66
  }
108
67
  }
109
68
 
110
- // ── Selector / DOM ──────────────────────────────────────────────────────────
111
-
112
69
  export class SelectorError extends CliError {
113
70
  constructor(selector: string, hint?: string) {
114
- super(
115
- 'SELECTOR',
116
- `Could not find element: ${selector}`,
117
- hint ?? 'The page UI may have changed. Please report this issue.',
118
- );
119
- this.name = 'SelectorError';
71
+ super('SELECTOR', `Could not find element: ${selector}`, hint ?? 'The page UI may have changed. Please report this issue.');
120
72
  }
121
73
  }
122
74
 
@@ -129,10 +81,17 @@ export function getErrorMessage(error: unknown): string {
129
81
 
130
82
  /** Error code → emoji mapping for CLI output rendering. */
131
83
  export const ERROR_ICONS: Record<string, string> = {
132
- AUTH_REQUIRED: '🔒',
84
+ AUTH_REQUIRED: '🔒',
133
85
  BROWSER_CONNECT: '🔌',
134
- TIMEOUT: '⏱ ',
135
- ARGUMENT: '❌',
136
- EMPTY_RESULT: '📭',
137
- SELECTOR: '🔍',
86
+ TIMEOUT: '⏱ ',
87
+ ARGUMENT: '❌',
88
+ EMPTY_RESULT: '📭',
89
+ SELECTOR: '🔍',
90
+ COMMAND_EXEC: '💥',
91
+ ADAPTER_LOAD: '📦',
92
+ NETWORK: '🌐',
93
+ API_ERROR: '🚫',
94
+ RATE_LIMITED: '⏳',
95
+ PAGE_CHANGED: '🔄',
96
+ CONFIG: '⚙️ ',
138
97
  };
package/src/execution.ts CHANGED
@@ -10,17 +10,19 @@
10
10
  * 6. Lifecycle hooks (onBeforeExecute / onAfterExecute)
11
11
  */
12
12
 
13
- import { type CliCommand, type InternalCliCommand, type Arg, Strategy, getRegistry, fullName } from './registry.js';
13
+ import { type CliCommand, type InternalCliCommand, type Arg, type CommandArgs, Strategy, getRegistry, fullName } from './registry.js';
14
14
  import type { IPage } from './types.js';
15
15
  import { pathToFileURL } from 'node:url';
16
16
  import { executePipeline } from './pipeline/index.js';
17
- import { AdapterLoadError, ArgumentError, CommandExecutionError, getErrorMessage } from './errors.js';
17
+ import { AdapterLoadError, ArgumentError, BrowserConnectError, CommandExecutionError, getErrorMessage } from './errors.js';
18
18
  import { shouldUseBrowserSession } from './capabilityRouting.js';
19
19
  import { getBrowserFactory, browserSession, runWithTimeout, DEFAULT_BROWSER_COMMAND_TIMEOUT } from './runtime.js';
20
20
  import { emitHook, type HookContext } from './hooks.js';
21
+ import { checkDaemonStatus } from './browser/discover.js';
22
+ import { PKG_VERSION } from './version.js';
23
+ import chalk from 'chalk';
21
24
 
22
25
  const _loadedModules = new Set<string>();
23
- type CommandArgs = Record<string, unknown>;
24
26
 
25
27
  export function coerceAndValidateArgs(cmdArgs: Arg[], kwargs: CommandArgs): CommandArgs {
26
28
  const result: CommandArgs = { ...kwargs };
@@ -151,6 +153,29 @@ export async function executeCommand(
151
153
  let result: unknown;
152
154
  try {
153
155
  if (shouldUseBrowserSession(cmd)) {
156
+ // ── Fail-fast: only when daemon is UP but extension is not connected ──
157
+ // If daemon is not running, let browserSession() handle auto-start as usual.
158
+ // We only short-circuit when the daemon confirms the extension is missing —
159
+ // that's a clear setup gap, not a transient startup state.
160
+ // Use a short timeout: localhost responds in <50ms when running.
161
+ // 300ms avoids a full 2s wait on cold-start (daemon not yet running).
162
+ const status = await checkDaemonStatus({ timeout: 300 });
163
+ if (status.running && !status.extensionConnected) {
164
+ throw new BrowserConnectError(
165
+ 'Browser Bridge extension not connected',
166
+ 'Install the Browser Bridge:\n' +
167
+ ' 1. Download: https://github.com/jackwener/opencli/releases\n' +
168
+ ' 2. chrome://extensions → Developer Mode → Load unpacked\n' +
169
+ ' Then run: opencli doctor',
170
+ );
171
+ }
172
+ // ── Version mismatch: warn but don't block ──
173
+ if (status.extensionVersion && status.extensionVersion !== PKG_VERSION) {
174
+ process.stderr.write(
175
+ chalk.yellow(`⚠ Extension v${status.extensionVersion} ≠ CLI v${PKG_VERSION} — consider updating the extension.\n`)
176
+ );
177
+ }
178
+
154
179
  ensureRequiredEnv(cmd);
155
180
  const BrowserFactory = getBrowserFactory();
156
181
  result = await browserSession(BrowserFactory, async (page) => {
@@ -14,14 +14,6 @@
14
14
  install:
15
15
  mac: "brew install --cask obsidian"
16
16
 
17
- - name: readwise
18
- binary: readwise
19
- description: "Readwise & Reader CLI — highlights, annotations, reading list"
20
- homepage: "https://github.com/readwiseio/readwise-cli"
21
- tags: [reading, highlights]
22
- install:
23
- default: "npm install -g @readwiseio/readwise-cli"
24
-
25
17
  - name: docker
26
18
  binary: docker
27
19
  description: "Docker command-line interface"
@@ -29,12 +21,3 @@
29
21
  tags: [docker, containers, devops]
30
22
  install:
31
23
  mac: "brew install --cask docker"
32
-
33
- - name: gws
34
- binary: gws
35
- description: "Google Workspace CLI — Docs, Sheets, Drive, Gmail, Calendar"
36
- homepage: "https://github.com/nicholasgasior/gws"
37
- tags: [google, docs, sheets, drive, workspace]
38
- install:
39
- mac: "brew install gws"
40
- default: "npm install -g @nicholasgasior/gws"
package/src/hooks.ts CHANGED
@@ -44,6 +44,7 @@ const _hooks: Map<HookName, HookFn[]> =
44
44
 
45
45
  function addHook(name: HookName, fn: HookFn): void {
46
46
  const list = _hooks.get(name) ?? [];
47
+ if (list.includes(fn)) return;
47
48
  list.push(fn);
48
49
  _hooks.set(name, list);
49
50
  }
package/src/main.ts CHANGED
@@ -20,6 +20,7 @@ import { discoverClis, discoverPlugins } from './discovery.js';
20
20
  import { getCompletions } from './completion.js';
21
21
  import { runCli } from './cli.js';
22
22
  import { emitHook } from './hooks.js';
23
+ import { registerUpdateNoticeOnExit, checkForUpdateBackground } from './update-check.js';
23
24
 
24
25
  const __filename = fileURLToPath(import.meta.url);
25
26
  const __dirname = path.dirname(__filename);
@@ -29,6 +30,11 @@ const USER_CLIS = path.join(os.homedir(), '.opencli', 'clis');
29
30
  await discoverClis(BUILTIN_CLIS, USER_CLIS);
30
31
  await discoverPlugins();
31
32
 
33
+ // Register exit hook: notice appears after command output (same as npm/gh/yarn)
34
+ registerUpdateNoticeOnExit();
35
+ // Kick off background fetch for next run (non-blocking)
36
+ checkForUpdateBackground();
37
+
32
38
  // ── Fast-path: handle --get-completions before commander parses ─────────
33
39
  // Usage: opencli --get-completions --cursor <N> [word1 word2 ...]
34
40
  const getCompIdx = process.argv.indexOf('--get-completions');
package/src/output.ts CHANGED
@@ -16,7 +16,9 @@ export interface RenderOptions {
16
16
  }
17
17
 
18
18
  function normalizeRows(data: unknown): Record<string, unknown>[] {
19
- return Array.isArray(data) ? data : [data as Record<string, unknown>];
19
+ if (Array.isArray(data)) return data;
20
+ if (data && typeof data === 'object') return [data as Record<string, unknown>];
21
+ return [{ value: data }];
20
22
  }
21
23
 
22
24
  function resolveColumns(rows: Record<string, unknown>[], opts: RenderOptions): string[] {
@@ -7,6 +7,7 @@ import type { IPage } from '../types.js';
7
7
  import { getStep, type StepHandler } from './registry.js';
8
8
  import { log } from '../logger.js';
9
9
  import { ConfigError } from '../errors.js';
10
+ import { BROWSER_ONLY_STEPS } from '../capabilityRouting.js';
10
11
 
11
12
  export interface PipelineContext {
12
13
  args?: Record<string, unknown>;
@@ -15,9 +16,6 @@ export interface PipelineContext {
15
16
  stepRetries?: number;
16
17
  }
17
18
 
18
- /** Steps that interact with the browser and may fail transiently */
19
- const BROWSER_STEPS = new Set(['navigate', 'evaluate', 'click', 'type', 'press', 'wait', 'snapshot']);
20
-
21
19
  export async function executePipeline(
22
20
  page: IPage | null,
23
21
  pipeline: unknown[],
@@ -50,8 +48,8 @@ export async function executePipeline(
50
48
  }
51
49
  } catch (err) {
52
50
  // Attempt cleanup: close automation window on pipeline failure
53
- if (page && typeof (page as unknown as Record<string, unknown>).closeWindow === 'function') {
54
- try { await (page as unknown as { closeWindow: () => Promise<void> }).closeWindow(); } catch { /* ignore */ }
51
+ if (page?.closeWindow) {
52
+ try { await page.closeWindow(); } catch { /* ignore */ }
55
53
  }
56
54
  throw err;
57
55
  }
@@ -67,7 +65,7 @@ async function executeStepWithRetry(
67
65
  op: string,
68
66
  configRetries?: number,
69
67
  ): Promise<unknown> {
70
- const maxRetries = configRetries ?? (BROWSER_STEPS.has(op) ? 2 : 0);
68
+ const maxRetries = configRetries ?? (BROWSER_ONLY_STEPS.has(op) ? 2 : 0);
71
69
 
72
70
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
73
71
  try {
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Tests for plugin manifest: reading, validating, and compatibility checks.
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
6
+ import * as fs from 'node:fs';
7
+ import * as path from 'node:path';
8
+ import * as os from 'node:os';
9
+ import {
10
+ _readPluginManifest as readPluginManifest,
11
+ _isMonorepo as isMonorepo,
12
+ _getEnabledPlugins as getEnabledPlugins,
13
+ _parseVersion as parseVersion,
14
+ _satisfiesRange as satisfiesRange,
15
+ MANIFEST_FILENAME,
16
+ type PluginManifest,
17
+ } from './plugin-manifest.js';
18
+
19
+ // ── readPluginManifest ──────────────────────────────────────────────────────
20
+
21
+ describe('readPluginManifest', () => {
22
+ let tmpDir: string;
23
+
24
+ beforeEach(() => {
25
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'opencli-manifest-test-'));
26
+ });
27
+
28
+ afterEach(() => {
29
+ fs.rmSync(tmpDir, { recursive: true, force: true });
30
+ });
31
+
32
+ it('returns null when no manifest file exists', () => {
33
+ expect(readPluginManifest(tmpDir)).toBeNull();
34
+ });
35
+
36
+ it('returns null for malformed JSON', () => {
37
+ fs.writeFileSync(path.join(tmpDir, MANIFEST_FILENAME), 'not json {{{');
38
+ expect(readPluginManifest(tmpDir)).toBeNull();
39
+ });
40
+
41
+ it('returns null for non-object JSON (array)', () => {
42
+ fs.writeFileSync(path.join(tmpDir, MANIFEST_FILENAME), '["a","b"]');
43
+ expect(readPluginManifest(tmpDir)).toBeNull();
44
+ });
45
+
46
+ it('returns null for non-object JSON (string)', () => {
47
+ fs.writeFileSync(path.join(tmpDir, MANIFEST_FILENAME), '"hello"');
48
+ expect(readPluginManifest(tmpDir)).toBeNull();
49
+ });
50
+
51
+ it('reads a single-plugin manifest', () => {
52
+ const manifest: PluginManifest = {
53
+ name: 'polymarket',
54
+ version: '1.2.0',
55
+ opencli: '>=1.0.0',
56
+ description: 'Prediction market analysis',
57
+ };
58
+ fs.writeFileSync(path.join(tmpDir, MANIFEST_FILENAME), JSON.stringify(manifest));
59
+ const result = readPluginManifest(tmpDir);
60
+ expect(result).toEqual(manifest);
61
+ });
62
+
63
+ it('reads a monorepo manifest', () => {
64
+ const manifest: PluginManifest = {
65
+ version: '1.0.0',
66
+ opencli: '>=0.9.0',
67
+ description: 'My plugin collection',
68
+ plugins: {
69
+ polymarket: {
70
+ path: 'packages/polymarket',
71
+ description: 'Prediction market',
72
+ version: '1.2.0',
73
+ },
74
+ defi: {
75
+ path: 'packages/defi',
76
+ description: 'DeFi data',
77
+ version: '0.8.0',
78
+ disabled: true,
79
+ },
80
+ },
81
+ };
82
+ fs.writeFileSync(path.join(tmpDir, MANIFEST_FILENAME), JSON.stringify(manifest));
83
+ const result = readPluginManifest(tmpDir);
84
+ expect(result).toEqual(manifest);
85
+ expect(result!.plugins!.polymarket.path).toBe('packages/polymarket');
86
+ expect(result!.plugins!.defi.disabled).toBe(true);
87
+ });
88
+ });
89
+
90
+ // ── isMonorepo ──────────────────────────────────────────────────────────────
91
+
92
+ describe('isMonorepo', () => {
93
+ it('returns false for single-plugin manifest', () => {
94
+ expect(isMonorepo({ name: 'test', version: '1.0.0' })).toBe(false);
95
+ });
96
+
97
+ it('returns false for empty plugins object', () => {
98
+ expect(isMonorepo({ plugins: {} })).toBe(false);
99
+ });
100
+
101
+ it('returns true for manifest with plugins', () => {
102
+ expect(
103
+ isMonorepo({
104
+ plugins: {
105
+ foo: { path: 'packages/foo' },
106
+ },
107
+ }),
108
+ ).toBe(true);
109
+ });
110
+ });
111
+
112
+ // ── getEnabledPlugins ───────────────────────────────────────────────────────
113
+
114
+ describe('getEnabledPlugins', () => {
115
+ it('returns empty array for no plugins', () => {
116
+ expect(getEnabledPlugins({ name: 'test' })).toEqual([]);
117
+ });
118
+
119
+ it('filters out disabled plugins', () => {
120
+ const manifest: PluginManifest = {
121
+ plugins: {
122
+ foo: { path: 'packages/foo' },
123
+ bar: { path: 'packages/bar', disabled: true },
124
+ baz: { path: 'packages/baz' },
125
+ },
126
+ };
127
+ const result = getEnabledPlugins(manifest);
128
+ expect(result).toHaveLength(2);
129
+ expect(result.map((r) => r.name)).toEqual(['baz', 'foo']); // sorted
130
+ });
131
+
132
+ it('returns all when none disabled', () => {
133
+ const manifest: PluginManifest = {
134
+ plugins: {
135
+ charlie: { path: 'packages/charlie' },
136
+ alpha: { path: 'packages/alpha' },
137
+ },
138
+ };
139
+ const result = getEnabledPlugins(manifest);
140
+ expect(result).toHaveLength(2);
141
+ expect(result[0].name).toBe('alpha');
142
+ expect(result[1].name).toBe('charlie');
143
+ });
144
+ });
145
+
146
+ // ── parseVersion ────────────────────────────────────────────────────────────
147
+
148
+ describe('parseVersion', () => {
149
+ it('parses standard versions', () => {
150
+ expect(parseVersion('1.2.3')).toEqual([1, 2, 3]);
151
+ expect(parseVersion('0.0.0')).toEqual([0, 0, 0]);
152
+ expect(parseVersion('10.20.30')).toEqual([10, 20, 30]);
153
+ });
154
+
155
+ it('parses versions with prerelease suffix', () => {
156
+ expect(parseVersion('1.2.3-beta.1')).toEqual([1, 2, 3]);
157
+ });
158
+
159
+ it('returns null for invalid versions', () => {
160
+ expect(parseVersion('abc')).toBeNull();
161
+ expect(parseVersion('')).toBeNull();
162
+ expect(parseVersion('1.2')).toBeNull();
163
+ });
164
+ });
165
+
166
+ // ── satisfiesRange ──────────────────────────────────────────────────────────
167
+
168
+ describe('satisfiesRange', () => {
169
+ it('handles >= constraint', () => {
170
+ expect(satisfiesRange('1.4.1', '>=1.0.0')).toBe(true);
171
+ expect(satisfiesRange('1.0.0', '>=1.0.0')).toBe(true);
172
+ expect(satisfiesRange('0.9.9', '>=1.0.0')).toBe(false);
173
+ });
174
+
175
+ it('handles <= constraint', () => {
176
+ expect(satisfiesRange('1.0.0', '<=1.0.0')).toBe(true);
177
+ expect(satisfiesRange('0.9.0', '<=1.0.0')).toBe(true);
178
+ expect(satisfiesRange('1.0.1', '<=1.0.0')).toBe(false);
179
+ });
180
+
181
+ it('handles > constraint', () => {
182
+ expect(satisfiesRange('1.0.1', '>1.0.0')).toBe(true);
183
+ expect(satisfiesRange('1.0.0', '>1.0.0')).toBe(false);
184
+ });
185
+
186
+ it('handles < constraint', () => {
187
+ expect(satisfiesRange('0.9.9', '<1.0.0')).toBe(true);
188
+ expect(satisfiesRange('1.0.0', '<1.0.0')).toBe(false);
189
+ });
190
+
191
+ it('handles ^ (caret) constraint', () => {
192
+ expect(satisfiesRange('1.2.0', '^1.2.0')).toBe(true);
193
+ expect(satisfiesRange('1.9.9', '^1.2.0')).toBe(true);
194
+ expect(satisfiesRange('2.0.0', '^1.2.0')).toBe(false);
195
+ expect(satisfiesRange('1.1.0', '^1.2.0')).toBe(false);
196
+ });
197
+
198
+ it('handles ~ (tilde) constraint', () => {
199
+ expect(satisfiesRange('1.2.0', '~1.2.0')).toBe(true);
200
+ expect(satisfiesRange('1.2.9', '~1.2.0')).toBe(true);
201
+ expect(satisfiesRange('1.3.0', '~1.2.0')).toBe(false);
202
+ });
203
+
204
+ it('handles exact match', () => {
205
+ expect(satisfiesRange('1.2.3', '1.2.3')).toBe(true);
206
+ expect(satisfiesRange('1.2.4', '1.2.3')).toBe(false);
207
+ });
208
+
209
+ it('handles compound range (AND)', () => {
210
+ expect(satisfiesRange('1.5.0', '>=1.0.0 <2.0.0')).toBe(true);
211
+ expect(satisfiesRange('2.0.0', '>=1.0.0 <2.0.0')).toBe(false);
212
+ expect(satisfiesRange('0.9.0', '>=1.0.0 <2.0.0')).toBe(false);
213
+ });
214
+
215
+ it('returns true for empty range', () => {
216
+ expect(satisfiesRange('1.0.0', '')).toBe(true);
217
+ expect(satisfiesRange('1.0.0', ' ')).toBe(true);
218
+ });
219
+
220
+ it('returns true for unparseable version', () => {
221
+ expect(satisfiesRange('dev', '>=1.0.0')).toBe(true);
222
+ });
223
+ });