@jackwener/opencli 1.6.0 → 1.6.2

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 (390) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/CONTRIBUTING.md +1 -1
  3. package/README.md +27 -45
  4. package/README.zh-CN.md +32 -34
  5. package/autoresearch/browse-tasks.json +18 -20
  6. package/autoresearch/commands/debug.ts +163 -0
  7. package/autoresearch/commands/fix.ts +145 -0
  8. package/autoresearch/commands/plan.ts +88 -0
  9. package/autoresearch/commands/run.ts +138 -0
  10. package/autoresearch/config.ts +82 -0
  11. package/autoresearch/engine.ts +359 -0
  12. package/autoresearch/eval-all.ts +127 -0
  13. package/autoresearch/eval-browse.ts +1 -1
  14. package/autoresearch/eval-publish.ts +238 -0
  15. package/autoresearch/eval-save.ts +249 -0
  16. package/autoresearch/eval-skill.ts +14 -8
  17. package/autoresearch/eval-v2ex.ts +220 -0
  18. package/autoresearch/eval-zhihu.ts +230 -0
  19. package/autoresearch/logger.ts +69 -0
  20. package/autoresearch/presets/combined-reliability.ts +27 -0
  21. package/autoresearch/presets/index.ts +23 -0
  22. package/autoresearch/presets/operate-reliability.ts +24 -0
  23. package/autoresearch/presets/save-reliability.ts +26 -0
  24. package/autoresearch/presets/skill-quality.ts +20 -0
  25. package/autoresearch/presets/v2ex-reliability.ts +24 -0
  26. package/autoresearch/presets/zhihu-reliability.ts +25 -0
  27. package/autoresearch/publish-tasks.json +345 -0
  28. package/autoresearch/run-save.sh +11 -0
  29. package/autoresearch/save-adapters/xhs-explore-deep.ts +64 -0
  30. package/autoresearch/save-adapters/xhs-note-comments.ts +61 -0
  31. package/autoresearch/save-adapters/xhs-search-full.ts +62 -0
  32. package/autoresearch/save-adapters/zhihu-hot-detail.ts +52 -0
  33. package/autoresearch/save-adapters/zhihu-question-full.ts +57 -0
  34. package/autoresearch/save-adapters/zhihu-search-detail.ts +53 -0
  35. package/autoresearch/save-tasks.json +281 -0
  36. package/autoresearch/v2ex-tasks.json +899 -0
  37. package/autoresearch/zhihu-tasks.json +848 -0
  38. package/bun.lock +615 -0
  39. package/dist/browser/base-page.d.ts +4 -2
  40. package/dist/browser/base-page.js +37 -4
  41. package/dist/browser/bridge.js +10 -8
  42. package/dist/browser/cdp.js +2 -6
  43. package/dist/browser/daemon-client.d.ts +11 -1
  44. package/dist/browser/daemon-client.js +3 -0
  45. package/dist/browser/dom-helpers.d.ts +4 -2
  46. package/dist/browser/dom-helpers.js +42 -31
  47. package/dist/browser/dom-snapshot.js +23 -1
  48. package/dist/browser/page.d.ts +7 -2
  49. package/dist/browser/page.js +112 -30
  50. package/dist/browser.test.js +1 -1
  51. package/dist/build-manifest.d.ts +1 -0
  52. package/dist/build-manifest.js +1 -0
  53. package/dist/cli-manifest.json +1133 -182
  54. package/dist/cli.d.ts +2 -0
  55. package/dist/cli.js +48 -7
  56. package/dist/cli.test.d.ts +1 -0
  57. package/dist/cli.test.js +88 -0
  58. package/dist/clis/1688/item.d.ts +70 -0
  59. package/dist/clis/1688/item.js +187 -0
  60. package/dist/clis/1688/item.test.d.ts +1 -0
  61. package/dist/clis/1688/item.test.js +67 -0
  62. package/dist/clis/1688/search.d.ts +56 -0
  63. package/dist/clis/1688/search.js +309 -0
  64. package/dist/clis/1688/search.test.d.ts +1 -0
  65. package/dist/clis/1688/search.test.js +75 -0
  66. package/dist/clis/1688/shared.d.ts +112 -0
  67. package/dist/clis/1688/shared.js +514 -0
  68. package/dist/clis/1688/shared.test.d.ts +1 -0
  69. package/dist/clis/1688/shared.test.js +57 -0
  70. package/dist/clis/1688/store.d.ts +45 -0
  71. package/dist/clis/1688/store.js +226 -0
  72. package/dist/clis/1688/store.test.d.ts +1 -0
  73. package/dist/clis/1688/store.test.js +62 -0
  74. package/dist/clis/amazon/bestsellers.d.ts +0 -20
  75. package/dist/clis/amazon/bestsellers.js +6 -129
  76. package/dist/clis/amazon/bestsellers.test.js +12 -3
  77. package/dist/clis/amazon/movers-shakers.d.ts +1 -0
  78. package/dist/clis/amazon/movers-shakers.js +7 -0
  79. package/dist/clis/amazon/new-releases.d.ts +1 -0
  80. package/dist/clis/amazon/new-releases.js +7 -0
  81. package/dist/clis/amazon/rankings.d.ts +59 -0
  82. package/dist/clis/amazon/rankings.js +226 -0
  83. package/dist/clis/amazon/rankings.test.d.ts +1 -0
  84. package/dist/clis/amazon/rankings.test.js +41 -0
  85. package/dist/clis/amazon/shared.d.ts +11 -0
  86. package/dist/clis/amazon/shared.js +121 -11
  87. package/dist/clis/amazon/shared.test.js +11 -0
  88. package/dist/clis/bilibili/comments.js +2 -2
  89. package/dist/clis/bilibili/comments.test.js +3 -2
  90. package/dist/clis/bilibili/download.js +2 -1
  91. package/dist/clis/bilibili/subtitle.js +4 -3
  92. package/dist/clis/bilibili/subtitle.test.js +2 -1
  93. package/dist/clis/bilibili/utils.d.ts +5 -0
  94. package/dist/clis/bilibili/utils.js +30 -0
  95. package/dist/clis/bilibili/utils.test.d.ts +1 -0
  96. package/dist/clis/bilibili/utils.test.js +17 -0
  97. package/dist/clis/douban/marks.js +1 -1
  98. package/dist/clis/douban/subject.yaml +50 -19
  99. package/dist/clis/doubao/utils.js +32 -12
  100. package/dist/clis/douyin/_shared/browser-fetch.test.js +0 -1
  101. package/dist/clis/douyin/_shared/transcode.test.js +0 -2
  102. package/dist/clis/douyin/draft.test.js +0 -2
  103. package/dist/clis/facebook/search.test.js +0 -2
  104. package/dist/clis/gemini/ask.js +9 -3
  105. package/dist/clis/gemini/ask.test.d.ts +1 -0
  106. package/dist/clis/gemini/ask.test.js +100 -0
  107. package/dist/clis/gemini/reply-state.test.d.ts +1 -0
  108. package/dist/clis/gemini/reply-state.test.js +641 -0
  109. package/dist/clis/gemini/utils.d.ts +44 -1
  110. package/dist/clis/gemini/utils.js +528 -61
  111. package/dist/clis/gemini/utils.test.js +149 -2
  112. package/dist/clis/hupu/detail.d.ts +1 -0
  113. package/dist/clis/hupu/detail.js +72 -0
  114. package/dist/clis/hupu/hot.yaml +43 -0
  115. package/dist/clis/hupu/like.d.ts +1 -0
  116. package/dist/clis/hupu/like.js +75 -0
  117. package/dist/clis/hupu/reply.d.ts +1 -0
  118. package/dist/clis/hupu/reply.js +71 -0
  119. package/dist/clis/hupu/search.d.ts +1 -0
  120. package/dist/clis/hupu/search.js +59 -0
  121. package/dist/clis/hupu/unlike.d.ts +1 -0
  122. package/dist/clis/hupu/unlike.js +75 -0
  123. package/dist/clis/hupu/utils.d.ts +20 -0
  124. package/dist/clis/hupu/utils.js +319 -0
  125. package/dist/clis/instagram/_shared/private-publish.d.ts +138 -0
  126. package/dist/clis/instagram/_shared/private-publish.js +1030 -0
  127. package/dist/clis/instagram/_shared/private-publish.test.d.ts +1 -0
  128. package/dist/clis/instagram/_shared/private-publish.test.js +705 -0
  129. package/dist/clis/instagram/_shared/protocol-capture.d.ts +26 -0
  130. package/dist/clis/instagram/_shared/protocol-capture.js +282 -0
  131. package/dist/clis/instagram/_shared/protocol-capture.test.d.ts +1 -0
  132. package/dist/clis/instagram/_shared/protocol-capture.test.js +114 -0
  133. package/dist/clis/instagram/_shared/runtime-info.d.ts +9 -0
  134. package/dist/clis/instagram/_shared/runtime-info.js +81 -0
  135. package/dist/clis/instagram/note.d.ts +1 -0
  136. package/dist/clis/instagram/note.js +222 -0
  137. package/dist/clis/instagram/note.test.d.ts +1 -0
  138. package/dist/clis/instagram/note.test.js +81 -0
  139. package/dist/clis/instagram/post.d.ts +4 -0
  140. package/dist/clis/instagram/post.js +1496 -0
  141. package/dist/clis/instagram/post.test.d.ts +1 -0
  142. package/dist/clis/instagram/post.test.js +1647 -0
  143. package/dist/clis/instagram/reel.d.ts +1 -0
  144. package/dist/clis/instagram/reel.js +826 -0
  145. package/dist/clis/instagram/reel.test.d.ts +1 -0
  146. package/dist/clis/instagram/reel.test.js +167 -0
  147. package/dist/clis/instagram/story.d.ts +1 -0
  148. package/dist/clis/instagram/story.js +115 -0
  149. package/dist/clis/instagram/story.test.d.ts +1 -0
  150. package/dist/clis/instagram/story.test.js +167 -0
  151. package/dist/clis/sinafinance/stock-rank.d.ts +4 -0
  152. package/dist/clis/sinafinance/stock-rank.js +65 -0
  153. package/dist/clis/substack/utils.test.js +0 -2
  154. package/dist/clis/twitter/post.js +72 -45
  155. package/dist/clis/twitter/post.test.d.ts +1 -0
  156. package/dist/clis/twitter/post.test.js +116 -0
  157. package/dist/clis/twitter/reply.d.ts +12 -0
  158. package/dist/clis/twitter/reply.js +257 -35
  159. package/dist/clis/twitter/reply.test.d.ts +1 -0
  160. package/dist/clis/twitter/reply.test.js +151 -0
  161. package/dist/clis/twitter/search.js +67 -5
  162. package/dist/clis/twitter/search.test.js +83 -5
  163. package/dist/clis/xianyu/chat.d.ts +7 -0
  164. package/dist/clis/xianyu/chat.js +146 -0
  165. package/dist/clis/xianyu/chat.test.d.ts +1 -0
  166. package/dist/clis/xianyu/chat.test.js +15 -0
  167. package/dist/clis/xianyu/item.d.ts +7 -0
  168. package/dist/clis/xianyu/item.js +152 -0
  169. package/dist/clis/xianyu/item.test.d.ts +1 -0
  170. package/dist/clis/xianyu/item.test.js +56 -0
  171. package/dist/clis/xianyu/search.d.ts +10 -0
  172. package/dist/clis/xianyu/search.js +134 -0
  173. package/dist/clis/xianyu/search.test.d.ts +1 -0
  174. package/dist/clis/xianyu/search.test.js +17 -0
  175. package/dist/clis/xianyu/utils.d.ts +1 -0
  176. package/dist/clis/xianyu/utils.js +8 -0
  177. package/dist/clis/xiaoe/catalog.yaml +129 -0
  178. package/dist/clis/xiaoe/content.yaml +43 -0
  179. package/dist/clis/xiaoe/courses.yaml +73 -0
  180. package/dist/clis/xiaoe/detail.yaml +39 -0
  181. package/dist/clis/xiaoe/play-url.yaml +124 -0
  182. package/dist/clis/xiaohongshu/comments.test.js +0 -2
  183. package/dist/clis/xiaohongshu/creator-note-detail.test.js +0 -2
  184. package/dist/clis/xiaohongshu/creator-notes.test.js +0 -2
  185. package/dist/clis/xiaohongshu/download.test.js +0 -2
  186. package/dist/clis/xiaohongshu/note.test.js +0 -2
  187. package/dist/clis/xiaohongshu/publish.test.js +0 -2
  188. package/dist/clis/xiaohongshu/search.js +29 -20
  189. package/dist/clis/xiaohongshu/search.test.js +56 -48
  190. package/dist/clis/yuanbao/ask.d.ts +21 -0
  191. package/dist/clis/yuanbao/ask.js +427 -0
  192. package/dist/clis/yuanbao/ask.test.d.ts +1 -0
  193. package/dist/clis/yuanbao/ask.test.js +124 -0
  194. package/dist/clis/yuanbao/new.d.ts +1 -0
  195. package/dist/clis/yuanbao/new.js +70 -0
  196. package/dist/clis/yuanbao/new.test.d.ts +1 -0
  197. package/dist/clis/yuanbao/new.test.js +30 -0
  198. package/dist/clis/yuanbao/shared.d.ts +13 -0
  199. package/dist/clis/yuanbao/shared.js +49 -0
  200. package/dist/clis/zhihu/question.js +30 -19
  201. package/dist/clis/zhihu/question.test.js +34 -16
  202. package/dist/commanderAdapter.js +8 -4
  203. package/dist/commanderAdapter.test.js +42 -0
  204. package/dist/completion.js +3 -1
  205. package/dist/completion.test.d.ts +1 -0
  206. package/dist/completion.test.js +23 -0
  207. package/dist/doctor.js +1 -1
  208. package/dist/electron-apps.d.ts +2 -0
  209. package/dist/electron-apps.js +7 -1
  210. package/dist/errors.js +1 -1
  211. package/dist/execution.js +25 -35
  212. package/dist/explore.js +1 -1
  213. package/dist/launcher.d.ts +4 -0
  214. package/dist/launcher.js +64 -8
  215. package/dist/launcher.test.js +88 -7
  216. package/dist/output.d.ts +2 -0
  217. package/dist/output.js +10 -1
  218. package/dist/output.test.d.ts +0 -3
  219. package/dist/output.test.js +59 -92
  220. package/dist/pipeline/executor.test.js +0 -2
  221. package/dist/pipeline/steps/download.test.js +0 -2
  222. package/dist/registry.d.ts +2 -0
  223. package/dist/serialization.d.ts +1 -0
  224. package/dist/serialization.js +1 -0
  225. package/dist/types.d.ts +9 -2
  226. package/docs/.vitepress/config.mts +4 -0
  227. package/docs/adapters/browser/1688.md +52 -0
  228. package/docs/adapters/browser/36kr.md +2 -1
  229. package/docs/adapters/browser/doubao.md +5 -1
  230. package/docs/adapters/browser/hupu.md +53 -0
  231. package/docs/adapters/browser/sinafinance.md +32 -2
  232. package/docs/adapters/browser/weibo.md +6 -1
  233. package/docs/adapters/browser/wikipedia.md +2 -0
  234. package/docs/adapters/browser/xianyu.md +42 -0
  235. package/docs/adapters/browser/xiaoe.md +44 -0
  236. package/docs/adapters/browser/yuanbao.md +64 -0
  237. package/docs/adapters/index.md +14 -5
  238. package/docs/comparison.md +1 -1
  239. package/docs/developer/ai-workflow.md +2 -2
  240. package/docs/developer/contributing.md +1 -1
  241. package/docs/developer/testing.md +2 -0
  242. package/docs/guide/plugins.md +1 -0
  243. package/docs/guide/troubleshooting.md +11 -0
  244. package/docs/superpowers/specs/2026-04-03-v2ex-autoresearch-design.md +41 -0
  245. package/docs/zh/guide/plugins.md +1 -0
  246. package/extension/dist/background.js +1127 -0
  247. package/extension/src/background.test.ts +39 -0
  248. package/extension/src/background.ts +223 -34
  249. package/extension/src/cdp.ts +194 -4
  250. package/extension/src/protocol.ts +22 -1
  251. package/package.json +3 -2
  252. package/scripts/postinstall.js +1 -1
  253. package/skills/opencli-explorer/SKILL.md +1 -1
  254. package/skills/opencli-oneshot/SKILL.md +2 -2
  255. package/skills/opencli-operate/SKILL.md +120 -27
  256. package/skills/opencli-usage/SKILL.md +31 -20
  257. package/skills/opencli-usage/browser.md +114 -16
  258. package/skills/opencli-usage/public-api.md +32 -3
  259. package/skills/smart-search/SKILL.md +156 -0
  260. package/skills/smart-search/references/sources-ai.md +74 -0
  261. package/skills/smart-search/references/sources-info.md +43 -0
  262. package/skills/smart-search/references/sources-media.md +50 -0
  263. package/skills/smart-search/references/sources-other.md +42 -0
  264. package/skills/smart-search/references/sources-shopping.md +31 -0
  265. package/skills/smart-search/references/sources-social.md +51 -0
  266. package/skills/smart-search/references/sources-tech.md +42 -0
  267. package/skills/smart-search/references/sources-travel.md +20 -0
  268. package/src/browser/base-page.ts +41 -6
  269. package/src/browser/bridge.ts +11 -8
  270. package/src/browser/cdp.ts +1 -8
  271. package/src/browser/daemon-client.ts +11 -1
  272. package/src/browser/dom-helpers.ts +43 -31
  273. package/src/browser/dom-snapshot.ts +23 -1
  274. package/src/browser/page.ts +115 -31
  275. package/src/browser.test.ts +1 -1
  276. package/src/build-manifest.ts +2 -0
  277. package/src/cli.test.ts +133 -0
  278. package/src/cli.ts +73 -11
  279. package/src/clis/1688/item.test.ts +69 -0
  280. package/src/clis/1688/item.ts +282 -0
  281. package/src/clis/1688/search.test.ts +81 -0
  282. package/src/clis/1688/search.ts +402 -0
  283. package/src/clis/1688/shared.test.ts +75 -0
  284. package/src/clis/1688/shared.ts +623 -0
  285. package/src/clis/1688/store.test.ts +69 -0
  286. package/src/clis/1688/store.ts +300 -0
  287. package/src/clis/amazon/bestsellers.test.ts +12 -3
  288. package/src/clis/amazon/bestsellers.ts +6 -178
  289. package/src/clis/amazon/movers-shakers.ts +8 -0
  290. package/src/clis/amazon/new-releases.ts +8 -0
  291. package/src/clis/amazon/rankings.test.ts +47 -0
  292. package/src/clis/amazon/rankings.ts +312 -0
  293. package/src/clis/amazon/shared.test.ts +16 -0
  294. package/src/clis/amazon/shared.ts +134 -12
  295. package/src/clis/bilibili/comments.test.ts +4 -3
  296. package/src/clis/bilibili/comments.ts +2 -2
  297. package/src/clis/bilibili/download.ts +2 -1
  298. package/src/clis/bilibili/subtitle.test.ts +2 -1
  299. package/src/clis/bilibili/subtitle.ts +4 -3
  300. package/src/clis/bilibili/utils.test.ts +21 -0
  301. package/src/clis/bilibili/utils.ts +27 -0
  302. package/src/clis/douban/marks.ts +1 -1
  303. package/src/clis/douban/subject.yaml +50 -19
  304. package/src/clis/doubao/utils.ts +32 -12
  305. package/src/clis/douyin/_shared/browser-fetch.test.ts +0 -1
  306. package/src/clis/douyin/_shared/transcode.test.ts +0 -2
  307. package/src/clis/douyin/draft.test.ts +0 -2
  308. package/src/clis/facebook/search.test.ts +0 -2
  309. package/src/clis/gemini/ask.test.ts +116 -0
  310. package/src/clis/gemini/ask.ts +10 -3
  311. package/src/clis/gemini/reply-state.test.ts +708 -0
  312. package/src/clis/gemini/utils.test.ts +184 -2
  313. package/src/clis/gemini/utils.ts +588 -60
  314. package/src/clis/hupu/detail.ts +126 -0
  315. package/src/clis/hupu/hot.yaml +43 -0
  316. package/src/clis/hupu/like.ts +76 -0
  317. package/src/clis/hupu/reply.ts +76 -0
  318. package/src/clis/hupu/search.ts +95 -0
  319. package/src/clis/hupu/unlike.ts +76 -0
  320. package/src/clis/hupu/utils.ts +381 -0
  321. package/src/clis/instagram/_shared/private-publish.test.ts +827 -0
  322. package/src/clis/instagram/_shared/private-publish.ts +1303 -0
  323. package/src/clis/instagram/_shared/protocol-capture.test.ts +148 -0
  324. package/src/clis/instagram/_shared/protocol-capture.ts +321 -0
  325. package/src/clis/instagram/_shared/runtime-info.ts +91 -0
  326. package/src/clis/instagram/note.test.ts +96 -0
  327. package/src/clis/instagram/note.ts +254 -0
  328. package/src/clis/instagram/post.test.ts +1716 -0
  329. package/src/clis/instagram/post.ts +1620 -0
  330. package/src/clis/instagram/reel.test.ts +191 -0
  331. package/src/clis/instagram/reel.ts +886 -0
  332. package/src/clis/instagram/story.test.ts +191 -0
  333. package/src/clis/instagram/story.ts +151 -0
  334. package/src/clis/sinafinance/stock-rank.ts +68 -0
  335. package/src/clis/substack/utils.test.ts +0 -2
  336. package/src/clis/twitter/post.test.ts +157 -0
  337. package/src/clis/twitter/post.ts +82 -48
  338. package/src/clis/twitter/reply.test.ts +177 -0
  339. package/src/clis/twitter/reply.ts +285 -39
  340. package/src/clis/twitter/search.test.ts +88 -5
  341. package/src/clis/twitter/search.ts +68 -5
  342. package/src/clis/xianyu/chat.test.ts +20 -0
  343. package/src/clis/xianyu/chat.ts +175 -0
  344. package/src/clis/xianyu/item.test.ts +67 -0
  345. package/src/clis/xianyu/item.ts +172 -0
  346. package/src/clis/xianyu/search.test.ts +22 -0
  347. package/src/clis/xianyu/search.ts +151 -0
  348. package/src/clis/xianyu/utils.ts +9 -0
  349. package/src/clis/xiaoe/catalog.yaml +129 -0
  350. package/src/clis/xiaoe/content.yaml +43 -0
  351. package/src/clis/xiaoe/courses.yaml +73 -0
  352. package/src/clis/xiaoe/detail.yaml +39 -0
  353. package/src/clis/xiaoe/play-url.yaml +124 -0
  354. package/src/clis/xiaohongshu/comments.test.ts +0 -2
  355. package/src/clis/xiaohongshu/creator-note-detail.test.ts +0 -2
  356. package/src/clis/xiaohongshu/creator-notes.test.ts +0 -2
  357. package/src/clis/xiaohongshu/download.test.ts +0 -2
  358. package/src/clis/xiaohongshu/note.test.ts +0 -2
  359. package/src/clis/xiaohongshu/publish.test.ts +0 -2
  360. package/src/clis/xiaohongshu/search.test.ts +59 -48
  361. package/src/clis/xiaohongshu/search.ts +31 -21
  362. package/src/clis/yuanbao/ask.test.ts +156 -0
  363. package/src/clis/yuanbao/ask.ts +522 -0
  364. package/src/clis/yuanbao/new.test.ts +36 -0
  365. package/src/clis/yuanbao/new.ts +81 -0
  366. package/src/clis/yuanbao/shared.ts +57 -0
  367. package/src/clis/zhihu/question.test.ts +42 -17
  368. package/src/clis/zhihu/question.ts +31 -26
  369. package/src/commanderAdapter.test.ts +51 -0
  370. package/src/commanderAdapter.ts +8 -4
  371. package/src/completion.test.ts +30 -0
  372. package/src/completion.ts +3 -1
  373. package/src/doctor.ts +1 -1
  374. package/src/electron-apps.ts +9 -1
  375. package/src/errors.ts +1 -1
  376. package/src/execution.ts +26 -30
  377. package/src/explore.ts +1 -1
  378. package/src/launcher.test.ts +121 -7
  379. package/src/launcher.ts +87 -9
  380. package/src/output.test.ts +50 -90
  381. package/src/output.ts +10 -1
  382. package/src/pipeline/executor.test.ts +0 -2
  383. package/src/pipeline/steps/download.test.ts +0 -2
  384. package/src/registry.ts +2 -0
  385. package/src/serialization.ts +2 -0
  386. package/src/types.ts +9 -2
  387. package/tests/e2e/browser-auth.test.ts +9 -0
  388. package/CLI-EXPLORER.md +0 -724
  389. package/CLI-ONESHOT.md +0 -216
  390. package/SKILL.md +0 -59
package/src/launcher.ts CHANGED
@@ -11,6 +11,7 @@
11
11
 
12
12
  import { execFileSync, spawn } from 'node:child_process';
13
13
  import { request as httpRequest } from 'node:http';
14
+ import * as path from 'node:path';
14
15
  import type { ElectronAppEntry } from './electron-apps.js';
15
16
  import { getElectronApp } from './electron-apps.js';
16
17
  import { confirmPrompt } from './tui.js';
@@ -46,6 +47,7 @@ export function probeCDP(port: number, timeoutMs: number = PROBE_TIMEOUT_MS): Pr
46
47
  * Uses pgrep on macOS/Linux.
47
48
  */
48
49
  export function detectProcess(processName: string): boolean {
50
+ if (process.platform === 'win32') return false; // pgrep not available on Windows
49
51
  try {
50
52
  execFileSync('pgrep', ['-x', processName], { encoding: 'utf-8', stdio: 'pipe' });
51
53
  return true;
@@ -58,6 +60,7 @@ export function detectProcess(processName: string): boolean {
58
60
  * Kill a process by name. Sends SIGTERM first, then SIGKILL after grace period.
59
61
  */
60
62
  export function killProcess(processName: string): void {
63
+ if (process.platform === 'win32') return; // pkill not available on Windows
61
64
  try {
62
65
  execFileSync('pkill', ['-x', processName], { stdio: 'pipe' });
63
66
  } catch {
@@ -101,6 +104,78 @@ function resolveExecutable(appPath: string, processName: string): string {
101
104
  return `${appPath}/Contents/MacOS/${processName}`;
102
105
  }
103
106
 
107
+ function isMissingExecutableError(err: unknown, label: string): boolean {
108
+ return err instanceof CommandExecutionError
109
+ && err.message.startsWith(`Could not launch ${label}: executable not found at `);
110
+ }
111
+
112
+ export function resolveExecutableCandidates(appPath: string, app: ElectronAppEntry): string[] {
113
+ const executableNames = app.executableNames?.length ? app.executableNames : [app.processName];
114
+ return [...new Set(executableNames)].map((name) => resolveExecutable(appPath, name));
115
+ }
116
+
117
+ export async function launchDetachedApp(executable: string, args: string[], label: string): Promise<void> {
118
+ await new Promise<void>((resolve, reject) => {
119
+ const child = spawn(executable, args, {
120
+ detached: true,
121
+ stdio: 'ignore',
122
+ });
123
+
124
+ const onError = (err: NodeJS.ErrnoException): void => {
125
+ if (err.code === 'ENOENT') {
126
+ reject(new CommandExecutionError(
127
+ `Could not launch ${label}: executable not found at ${executable}`,
128
+ `Install ${label}, reinstall it, or register a custom app path in ~/.opencli/apps.yaml`,
129
+ ));
130
+ return;
131
+ }
132
+
133
+ reject(new CommandExecutionError(
134
+ `Failed to launch ${label}`,
135
+ err.message,
136
+ ));
137
+ };
138
+
139
+ child.once('error', onError);
140
+ child.once('spawn', () => {
141
+ child.off('error', onError);
142
+ child.unref();
143
+ resolve();
144
+ });
145
+ });
146
+ }
147
+
148
+ export async function launchElectronApp(appPath: string, app: ElectronAppEntry, args: string[], label: string): Promise<void> {
149
+ const executables = resolveExecutableCandidates(appPath, app);
150
+ let lastMissingExecutableError: CommandExecutionError | undefined;
151
+
152
+ for (const executable of executables) {
153
+ log.debug(`[launcher] Launching: ${executable} ${args.join(' ')}`);
154
+ try {
155
+ await launchDetachedApp(executable, args, label);
156
+ return;
157
+ } catch (err) {
158
+ if (isMissingExecutableError(err, label)) {
159
+ lastMissingExecutableError = err as CommandExecutionError;
160
+ continue;
161
+ }
162
+ throw err;
163
+ }
164
+ }
165
+
166
+ if (executables.length > 1) {
167
+ throw new CommandExecutionError(
168
+ `Could not launch ${label}: no compatible executable found in ${path.join(appPath, 'Contents', 'MacOS')}`,
169
+ `Tried: ${executables.map((executable) => path.basename(executable)).join(', ')}. Install ${label}, reinstall it, or register a custom app path in ~/.opencli/apps.yaml`,
170
+ );
171
+ }
172
+
173
+ throw lastMissingExecutableError ?? new CommandExecutionError(
174
+ `Could not launch ${label}`,
175
+ `Install ${label}, reinstall it, or register a custom app path in ~/.opencli/apps.yaml`,
176
+ );
177
+ }
178
+
104
179
  async function pollForReady(port: number): Promise<void> {
105
180
  const deadline = Date.now() + POLL_TIMEOUT_MS;
106
181
  while (Date.now() < deadline) {
@@ -138,7 +213,17 @@ export async function resolveElectronEndpoint(site: string): Promise<string> {
138
213
  return endpoint;
139
214
  }
140
215
 
141
- // Step 2: Running without CDP?
216
+ // Step 2: Running without CDP? (process detection requires Unix tools)
217
+ if (process.platform !== 'darwin' && process.platform !== 'linux') {
218
+ throw new CommandExecutionError(
219
+ `${label} is not reachable on CDP port ${port}.`,
220
+ `Auto-launch is not yet supported on ${process.platform}.\n` +
221
+ `Start ${label} manually with --remote-debugging-port=${port}, then either:\n` +
222
+ ` • Set OPENCLI_CDP_ENDPOINT=http://127.0.0.1:${port}\n` +
223
+ ` • Or just re-run the command once ${label} is listening on port ${port}.`,
224
+ );
225
+ }
226
+
142
227
  const isRunning = detectProcess(processName);
143
228
  if (isRunning) {
144
229
  log.debug(`[launcher] ${label} is running but CDP not available`);
@@ -166,15 +251,8 @@ export async function resolveElectronEndpoint(site: string): Promise<string> {
166
251
  }
167
252
 
168
253
  // Step 4: Launch
169
- const executable = resolveExecutable(appPath, processName);
170
254
  const args = [`--remote-debugging-port=${port}`, ...(app.extraArgs ?? [])];
171
- log.debug(`[launcher] Launching: ${executable} ${args.join(' ')}`);
172
-
173
- const child = spawn(executable, args, {
174
- detached: true,
175
- stdio: 'ignore',
176
- });
177
- child.unref();
255
+ await launchElectronApp(appPath, app, args, label);
178
256
 
179
257
  // Step 5: Poll for readiness
180
258
  process.stderr.write(` Waiting for ${label} on port ${port}...\n`);
@@ -1,109 +1,69 @@
1
- /**
2
- * Tests for output.ts: render function format coverage.
3
- */
4
-
5
- import { describe, it, expect, vi, afterEach } from 'vitest';
1
+ import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
6
2
  import { render } from './output.js';
7
3
 
8
- afterEach(() => {
9
- vi.restoreAllMocks();
10
- });
11
-
12
- describe('render', () => {
13
- it('renders JSON output', () => {
14
- const log = vi.spyOn(console, 'log').mockImplementation(() => {});
15
- render([{ title: 'Hello', rank: 1 }], { fmt: 'json' });
16
- expect(log).toHaveBeenCalledOnce();
17
- const output = log.mock.calls[0]?.[0];
18
- const parsed = JSON.parse(output);
19
- expect(parsed).toEqual([{ title: 'Hello', rank: 1 }]);
20
- });
21
-
22
- it('renders Markdown table output', () => {
23
- const log = vi.spyOn(console, 'log').mockImplementation(() => {});
24
- render([{ name: 'Alice', score: 100 }], { fmt: 'md', columns: ['name', 'score'] });
25
- const calls = log.mock.calls.map(c => c[0]);
26
- expect(calls[0]).toContain('| name | score |');
27
- expect(calls[1]).toContain('| --- | --- |');
28
- expect(calls[2]).toContain('| Alice | 100 |');
29
- });
30
-
31
- it('renders CSV output with proper quoting', () => {
32
- const log = vi.spyOn(console, 'log').mockImplementation(() => {});
33
- render([{ name: 'Alice, Bob', value: 'say "hi"' }], { fmt: 'csv' });
34
- const calls = log.mock.calls.map(c => c[0]);
35
- // Header
36
- expect(calls[0]).toBe('name,value');
37
- // Values with commas/quotes are quoted
38
- expect(calls[1]).toContain('"Alice, Bob"');
39
- expect(calls[1]).toContain('"say ""hi"""');
40
- });
41
-
42
- it('handles null and undefined data', () => {
43
- const log = vi.spyOn(console, 'log').mockImplementation(() => {});
44
- render(null, { fmt: 'json' });
45
- expect(log).toHaveBeenCalledWith(null);
46
- });
4
+ describe('output TTY detection', () => {
5
+ const originalIsTTY = process.stdout.isTTY;
6
+ const originalEnv = process.env.OUTPUT;
7
+ let logSpy: ReturnType<typeof vi.spyOn>;
47
8
 
48
- it('renders single object as single-row table', () => {
49
- const log = vi.spyOn(console, 'log').mockImplementation(() => {});
50
- render({ title: 'Test' }, { fmt: 'json' });
51
- const output = log.mock.calls[0]?.[0];
52
- const parsed = JSON.parse(output);
53
- expect(parsed).toEqual({ title: 'Test' });
9
+ beforeEach(() => {
10
+ logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
54
11
  });
55
12
 
56
- it('handles empty array gracefully', () => {
57
- const log = vi.spyOn(console, 'log').mockImplementation(() => {});
58
- render([], { fmt: 'table' });
59
- // Should show "(no data)" for empty arrays
60
- expect(log).toHaveBeenCalled();
13
+ afterEach(() => {
14
+ Object.defineProperty(process.stdout, 'isTTY', { value: originalIsTTY, writable: true });
15
+ if (originalEnv === undefined) delete process.env.OUTPUT;
16
+ else process.env.OUTPUT = originalEnv;
17
+ logSpy.mockRestore();
61
18
  });
62
19
 
63
- it('uses custom columns for CSV', () => {
64
- const log = vi.spyOn(console, 'log').mockImplementation(() => {});
65
- render([{ a: 1, b: 2, c: 3 }], { fmt: 'csv', columns: ['a', 'c'] });
66
- const calls = log.mock.calls.map(c => c[0]);
67
- expect(calls[0]).toBe('a,c');
68
- expect(calls[1]).toBe('1,3');
20
+ it('outputs YAML in non-TTY when format is default table', () => {
21
+ Object.defineProperty(process.stdout, 'isTTY', { value: false, writable: true });
22
+ // commanderAdapter always passes fmt:'table' as default this must still trigger downgrade
23
+ render([{ name: 'alice', score: 10 }], { fmt: 'table', columns: ['name', 'score'] });
24
+ const out = logSpy.mock.calls.map((c: any[]) => c[0]).join('\n');
25
+ expect(out).toContain('name: alice');
26
+ expect(out).toContain('score: 10');
69
27
  });
70
28
 
71
- it('renders YAML output', () => {
72
- const log = vi.spyOn(console, 'log').mockImplementation(() => {});
73
- render([{ title: 'Hello', rank: 1 }], { fmt: 'yaml' });
74
- expect(log).toHaveBeenCalledOnce();
75
- expect(log.mock.calls[0]?.[0]).toContain('- title: Hello');
76
- expect(log.mock.calls[0]?.[0]).toContain('rank: 1');
29
+ it('outputs table in TTY when format is default table', () => {
30
+ Object.defineProperty(process.stdout, 'isTTY', { value: true, writable: true });
31
+ render([{ name: 'alice', score: 10 }], { fmt: 'table', columns: ['name', 'score'] });
32
+ const out = logSpy.mock.calls.map((c: any[]) => c[0]).join('\n');
33
+ expect(out).toContain('alice');
77
34
  });
78
35
 
79
- it('renders yml alias as YAML output', () => {
80
- const log = vi.spyOn(console, 'log').mockImplementation(() => {});
81
- render({ title: 'Hello' }, { fmt: 'yml' });
82
- expect(log).toHaveBeenCalledOnce();
83
- expect(log.mock.calls[0]?.[0]).toContain('title: Hello');
36
+ it('respects explicit -f json even in non-TTY', () => {
37
+ Object.defineProperty(process.stdout, 'isTTY', { value: false, writable: true });
38
+ render([{ name: 'alice' }], { fmt: 'json' });
39
+ const out = logSpy.mock.calls.map((c: any[]) => c[0]).join('\n');
40
+ expect(JSON.parse(out)).toEqual([{ name: 'alice' }]);
84
41
  });
85
42
 
86
- it('handles null values in CSV cells', () => {
87
- const log = vi.spyOn(console, 'log').mockImplementation(() => {});
88
- render([{ name: 'test', value: null }], { fmt: 'csv' });
89
- const calls = log.mock.calls.map(c => c[0]);
90
- expect(calls[1]).toBe('test,');
43
+ it('OUTPUT env var overrides default table in non-TTY', () => {
44
+ Object.defineProperty(process.stdout, 'isTTY', { value: false, writable: true });
45
+ process.env.OUTPUT = 'json';
46
+ render([{ name: 'alice' }], { fmt: 'table' });
47
+ const out = logSpy.mock.calls.map((c: any[]) => c[0]).join('\n');
48
+ expect(JSON.parse(out)).toEqual([{ name: 'alice' }]);
91
49
  });
92
50
 
93
- it('renders single-field rows in plain mode as the bare value', () => {
94
- const log = vi.spyOn(console, 'log').mockImplementation(() => {});
95
- render([{ response: 'Gemini says hi' }], { fmt: 'plain' });
96
- expect(log).toHaveBeenCalledWith('Gemini says hi');
51
+ it('explicit -f flag takes precedence over OUTPUT env var', () => {
52
+ Object.defineProperty(process.stdout, 'isTTY', { value: false, writable: true });
53
+ process.env.OUTPUT = 'json';
54
+ render([{ name: 'alice' }], { fmt: 'csv', fmtExplicit: true });
55
+ const out = logSpy.mock.calls.map((c: any[]) => c[0]).join('\n');
56
+ expect(out).toContain('name');
57
+ expect(out).toContain('alice');
58
+ expect(out).not.toContain('"name"'); // not JSON
97
59
  });
98
60
 
99
- it('renders multi-field rows in plain mode as key-value lines', () => {
100
- const log = vi.spyOn(console, 'log').mockImplementation(() => {});
101
- render([{ status: 'ok', file: '~/tmp/a.png', link: 'https://example.com' }], { fmt: 'plain' });
102
- const calls = log.mock.calls.map(c => c[0]);
103
- expect(calls).toEqual([
104
- 'status: ok',
105
- 'file: ~/tmp/a.png',
106
- 'link: https://example.com',
107
- ]);
61
+ it('explicit -f table overrides non-TTY auto-downgrade', () => {
62
+ Object.defineProperty(process.stdout, 'isTTY', { value: false, writable: true });
63
+ render([{ name: 'alice' }], { fmt: 'table', fmtExplicit: true, columns: ['name'] });
64
+ const out = logSpy.mock.calls.map((c: any[]) => c[0]).join('\n');
65
+ // Should be table output, not YAML
66
+ expect(out).not.toContain('name: alice');
67
+ expect(out).toContain('alice');
108
68
  });
109
69
  });
package/src/output.ts CHANGED
@@ -8,6 +8,8 @@ import yaml from 'js-yaml';
8
8
 
9
9
  export interface RenderOptions {
10
10
  fmt?: string;
11
+ /** True when the user explicitly passed -f on the command line */
12
+ fmtExplicit?: boolean;
11
13
  columns?: string[];
12
14
  title?: string;
13
15
  elapsed?: number;
@@ -26,7 +28,14 @@ function resolveColumns(rows: Record<string, unknown>[], opts: RenderOptions): s
26
28
  }
27
29
 
28
30
  export function render(data: unknown, opts: RenderOptions = {}): void {
29
- const fmt = opts.fmt ?? 'table';
31
+ let fmt = opts.fmt ?? 'table';
32
+ // Non-TTY auto-downgrade only when format was NOT explicitly passed by user.
33
+ // Priority: explicit -f (any value) > OUTPUT env var > TTY auto-detect > table
34
+ if (!opts.fmtExplicit) {
35
+ const envFmt = process.env.OUTPUT?.trim().toLowerCase();
36
+ if (envFmt) fmt = envFmt;
37
+ else if (fmt === 'table' && !process.stdout.isTTY) fmt = 'yaml';
38
+ }
30
39
  if (data === null || data === undefined) {
31
40
  console.log(data);
32
41
  return;
@@ -20,8 +20,6 @@ function createMockPage(overrides: Partial<IPage> = {}): IPage {
20
20
  getFormState: vi.fn().mockResolvedValue({}),
21
21
  wait: vi.fn(),
22
22
  tabs: vi.fn().mockResolvedValue([]),
23
- closeTab: vi.fn(),
24
- newTab: vi.fn(),
25
23
  selectTab: vi.fn(),
26
24
  networkRequests: vi.fn().mockResolvedValue([]),
27
25
  consoleMessages: vi.fn().mockResolvedValue(''),
@@ -34,8 +34,6 @@ function createMockPage(getCookies: IPage['getCookies']): IPage {
34
34
  getFormState: vi.fn().mockResolvedValue({}),
35
35
  wait: vi.fn(),
36
36
  tabs: vi.fn().mockResolvedValue([]),
37
- closeTab: vi.fn(),
38
- newTab: vi.fn(),
39
37
  selectTab: vi.fn(),
40
38
  networkRequests: vi.fn().mockResolvedValue([]),
41
39
  consoleMessages: vi.fn().mockResolvedValue([]),
package/src/registry.ts CHANGED
@@ -17,6 +17,7 @@ export interface Arg {
17
17
  type?: string;
18
18
  default?: unknown;
19
19
  required?: boolean;
20
+ valueRequired?: boolean;
20
21
  positional?: boolean;
21
22
  help?: string;
22
23
  choices?: string[];
@@ -47,6 +48,7 @@ export interface CliCommand {
47
48
  source?: string;
48
49
  footerExtra?: (kwargs: CommandArgs) => string | undefined;
49
50
  requiredEnv?: RequiredEnv[];
51
+ validateArgs?: (kwargs: CommandArgs) => void;
50
52
  /** Deprecation note shown in help / execution warnings. */
51
53
  deprecated?: boolean | string;
52
54
  /** Preferred replacement command, if any. */
@@ -14,6 +14,7 @@ export type SerializedArg = {
14
14
  name: string;
15
15
  type: string;
16
16
  required: boolean;
17
+ valueRequired: boolean;
17
18
  positional: boolean;
18
19
  choices: string[];
19
20
  default: unknown;
@@ -26,6 +27,7 @@ export function serializeArg(a: Arg): SerializedArg {
26
27
  name: a.name,
27
28
  type: a.type ?? 'string',
28
29
  required: !!a.required,
30
+ valueRequired: !!a.valueRequired,
29
31
  positional: !!a.positional,
30
32
  choices: a.choices ?? [],
31
33
  default: a.default ?? null,
package/src/types.ts CHANGED
@@ -56,8 +56,8 @@ export interface IPage {
56
56
  getFormState(): Promise<any>;
57
57
  wait(options: number | WaitOptions): Promise<void>;
58
58
  tabs(): Promise<any>;
59
- closeTab(index?: number): Promise<void>;
60
- newTab(): Promise<void>;
59
+ closeTab?(index?: number): Promise<void>;
60
+ newTab?(): Promise<void>;
61
61
  selectTab(index: number): Promise<void>;
62
62
  networkRequests(includeStatic?: boolean): Promise<any>;
63
63
  consoleMessages(level?: string): Promise<any>;
@@ -67,11 +67,18 @@ export interface IPage {
67
67
  getInterceptedRequests(): Promise<any[]>;
68
68
  waitForCapture(timeout?: number): Promise<void>;
69
69
  screenshot(options?: ScreenshotOptions): Promise<string>;
70
+ startNetworkCapture?(pattern?: string): Promise<void>;
71
+ readNetworkCapture?(): Promise<unknown[]>;
70
72
  /**
71
73
  * Set local file paths on a file input element via CDP DOM.setFileInputFiles.
72
74
  * Chrome reads the files directly — no base64 encoding or payload size limits.
73
75
  */
74
76
  setFileInput?(files: string[], selector?: string): Promise<void>;
77
+ /**
78
+ * Insert text via native CDP Input.insertText into the currently focused element.
79
+ * Useful for rich editors that ignore synthetic DOM value/text mutations.
80
+ */
81
+ insertText?(text: string): Promise<void>;
75
82
  closeWindow?(): Promise<void>;
76
83
  /** Returns the current page URL, or null if unavailable. */
77
84
  getCurrentUrl?(): Promise<string | null>;
@@ -105,6 +105,15 @@ describe('login-required commands — graceful failure', () => {
105
105
  await expectGracefulAuthFailure(['xiaohongshu', 'notifications', '--limit', '3', '-f', 'json']);
106
106
  }, 60_000);
107
107
 
108
+ // ── yuanbao (requires login) ──
109
+ it('yuanbao new fails gracefully without login', async () => {
110
+ await expectGracefulAuthFailure(['yuanbao', 'new', '-f', 'json']);
111
+ }, 60_000);
112
+
113
+ it('yuanbao ask fails gracefully without login', async () => {
114
+ await expectGracefulAuthFailure(['yuanbao', 'ask', '你好', '-f', 'json']);
115
+ }, 60_000);
116
+
108
117
  // ── pixiv (requires login) ──
109
118
  it('pixiv ranking fails gracefully without login', async () => {
110
119
  await expectGracefulAuthFailure(['pixiv', 'ranking', '--limit', '3', '-f', 'json']);