@jackwener/opencli 1.1.0 → 1.1.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 (354) hide show
  1. package/.agents/skills/cross-project-adapter-migration/SKILL.md +2 -2
  2. package/.github/pull_request_template.md +7 -0
  3. package/.github/workflows/doc-check.yml +36 -0
  4. package/.github/workflows/docs.yml +7 -42
  5. package/CHANGELOG.md +23 -0
  6. package/CLI-EXPLORER.md +9 -8
  7. package/README.md +25 -10
  8. package/README.zh-CN.md +26 -11
  9. package/SKILL.md +95 -31
  10. package/dist/browser/cdp.js +6 -1
  11. package/dist/browser/page.d.ts +4 -1
  12. package/dist/browser/page.js +7 -1
  13. package/dist/build-manifest.js +23 -16
  14. package/dist/cli-manifest.json +431 -276
  15. package/dist/cli.d.ts +6 -0
  16. package/dist/cli.js +189 -162
  17. package/dist/clis/apple-podcasts/commands.test.d.ts +2 -0
  18. package/dist/clis/apple-podcasts/commands.test.js +76 -0
  19. package/dist/clis/apple-podcasts/search.js +2 -2
  20. package/dist/clis/apple-podcasts/top.js +9 -2
  21. package/dist/clis/arxiv/search.js +1 -1
  22. package/dist/clis/bilibili/dynamic.js +1 -1
  23. package/dist/clis/bilibili/favorite.js +1 -1
  24. package/dist/clis/bilibili/feed.js +1 -1
  25. package/dist/clis/bilibili/following.js +1 -1
  26. package/dist/clis/bilibili/history.js +1 -1
  27. package/dist/clis/bilibili/me.js +1 -1
  28. package/dist/clis/bilibili/ranking.js +1 -1
  29. package/dist/clis/bilibili/search.js +3 -3
  30. package/dist/clis/bilibili/subtitle.js +1 -1
  31. package/dist/clis/bilibili/user-videos.js +1 -1
  32. package/dist/{bilibili.d.ts → clis/bilibili/utils.d.ts} +1 -1
  33. package/dist/clis/bloomberg/businessweek.js +17 -0
  34. package/dist/clis/bloomberg/economics.js +17 -0
  35. package/dist/clis/bloomberg/feeds.d.ts +1 -0
  36. package/dist/clis/bloomberg/feeds.js +15 -0
  37. package/dist/clis/bloomberg/industries.d.ts +1 -0
  38. package/dist/clis/bloomberg/industries.js +17 -0
  39. package/dist/clis/bloomberg/main.d.ts +1 -0
  40. package/dist/clis/bloomberg/main.js +17 -0
  41. package/dist/clis/bloomberg/markets.d.ts +1 -0
  42. package/dist/clis/bloomberg/markets.js +17 -0
  43. package/dist/clis/bloomberg/news.d.ts +1 -0
  44. package/dist/clis/bloomberg/news.js +105 -0
  45. package/dist/clis/bloomberg/opinions.d.ts +1 -0
  46. package/dist/clis/bloomberg/opinions.js +17 -0
  47. package/dist/clis/bloomberg/politics.d.ts +1 -0
  48. package/dist/clis/bloomberg/politics.js +17 -0
  49. package/dist/clis/bloomberg/tech.d.ts +1 -0
  50. package/dist/clis/bloomberg/tech.js +17 -0
  51. package/dist/clis/bloomberg/utils.d.ts +34 -0
  52. package/dist/clis/bloomberg/utils.js +364 -0
  53. package/dist/clis/bloomberg/utils.test.d.ts +1 -0
  54. package/dist/clis/bloomberg/utils.test.js +129 -0
  55. package/dist/clis/boss/batchgreet.js +2 -2
  56. package/dist/clis/boss/chatlist.js +2 -2
  57. package/dist/clis/boss/detail.js +2 -2
  58. package/dist/clis/boss/greet.js +4 -4
  59. package/dist/clis/boss/search.js +1 -1
  60. package/dist/clis/boss/send.js +1 -1
  61. package/dist/clis/boss/stats.js +2 -2
  62. package/dist/clis/chaoxing/assignments.js +1 -1
  63. package/dist/clis/chaoxing/exams.js +1 -1
  64. package/dist/{chaoxing.d.ts → clis/chaoxing/utils.d.ts} +1 -1
  65. package/dist/{chaoxing.js → clis/chaoxing/utils.js} +0 -2
  66. package/dist/clis/chaoxing/utils.test.d.ts +1 -0
  67. package/dist/{chaoxing.test.js → clis/chaoxing/utils.test.js} +1 -1
  68. package/dist/clis/chatgpt/read.js +1 -1
  69. package/dist/clis/chatwise/export.js +1 -1
  70. package/dist/clis/chatwise/model.js +2 -2
  71. package/dist/clis/chatwise/screenshot.js +1 -1
  72. package/dist/clis/codex/export.js +1 -1
  73. package/dist/clis/codex/model.js +2 -2
  74. package/dist/clis/codex/screenshot.js +1 -1
  75. package/dist/clis/coupang/add-to-cart.js +3 -4
  76. package/dist/clis/coupang/search.js +2 -4
  77. package/dist/clis/coupang/utils.test.d.ts +1 -0
  78. package/dist/{coupang.test.js → clis/coupang/utils.test.js} +1 -1
  79. package/dist/clis/ctrip/search.js +1 -1
  80. package/dist/clis/cursor/export.js +1 -1
  81. package/dist/clis/cursor/model.js +2 -2
  82. package/dist/clis/cursor/screenshot.js +1 -1
  83. package/dist/clis/jike/comment.js +2 -3
  84. package/dist/clis/jike/create.js +1 -2
  85. package/dist/clis/jike/feed.js +0 -1
  86. package/dist/clis/jike/like.js +1 -2
  87. package/dist/clis/jike/notifications.js +0 -1
  88. package/dist/clis/jike/post.yaml +1 -0
  89. package/dist/clis/jike/repost.js +1 -2
  90. package/dist/clis/jike/search.js +2 -3
  91. package/dist/clis/jike/topic.yaml +1 -0
  92. package/dist/clis/jike/user.yaml +1 -0
  93. package/dist/clis/jimeng/history.yaml +0 -1
  94. package/dist/clis/linkedin/search.js +7 -7
  95. package/dist/clis/linux-do/category.yaml +1 -0
  96. package/dist/clis/linux-do/search.yaml +4 -3
  97. package/dist/clis/linux-do/topic.yaml +1 -0
  98. package/dist/clis/notion/export.js +1 -1
  99. package/dist/clis/reddit/comment.js +3 -4
  100. package/dist/clis/reddit/read.js +4 -5
  101. package/dist/clis/reddit/save.js +2 -3
  102. package/dist/clis/reddit/saved.js +0 -1
  103. package/dist/clis/reddit/search.yaml +1 -0
  104. package/dist/clis/reddit/subscribe.js +0 -1
  105. package/dist/clis/reddit/upvote.js +2 -3
  106. package/dist/clis/reddit/upvoted.js +0 -1
  107. package/dist/clis/reddit/user-comments.yaml +1 -0
  108. package/dist/clis/reddit/user-posts.yaml +1 -0
  109. package/dist/clis/reddit/user.yaml +1 -0
  110. package/dist/clis/reuters/search.js +1 -1
  111. package/dist/clis/smzdm/search.js +2 -3
  112. package/dist/clis/stackoverflow/search.yaml +1 -0
  113. package/dist/clis/steam/top-sellers.yaml +29 -0
  114. package/dist/clis/twitter/accept.js +2 -2
  115. package/dist/clis/twitter/article.js +2 -2
  116. package/dist/clis/twitter/block.d.ts +1 -0
  117. package/dist/clis/twitter/block.js +88 -0
  118. package/dist/clis/twitter/delete.js +1 -1
  119. package/dist/clis/twitter/hide-reply.d.ts +1 -0
  120. package/dist/clis/twitter/hide-reply.js +66 -0
  121. package/dist/clis/twitter/like.js +1 -1
  122. package/dist/clis/twitter/post.js +1 -1
  123. package/dist/clis/twitter/reply-dm.js +1 -1
  124. package/dist/clis/twitter/reply.js +2 -2
  125. package/dist/clis/twitter/search.js +1 -1
  126. package/dist/clis/twitter/thread.js +2 -2
  127. package/dist/clis/twitter/trending.d.ts +1 -0
  128. package/dist/clis/twitter/trending.js +91 -0
  129. package/dist/clis/twitter/unblock.d.ts +1 -0
  130. package/dist/clis/twitter/unblock.js +71 -0
  131. package/dist/clis/v2ex/topic.yaml +1 -0
  132. package/dist/clis/weibo/hot.js +0 -1
  133. package/dist/clis/weread/book.js +1 -1
  134. package/dist/clis/weread/highlights.js +1 -1
  135. package/dist/clis/weread/notes.js +1 -1
  136. package/dist/clis/weread/search.js +1 -1
  137. package/dist/clis/wikipedia/search.js +1 -1
  138. package/dist/clis/xiaohongshu/creator-note-detail.d.ts +15 -0
  139. package/dist/clis/xiaohongshu/creator-note-detail.js +69 -5
  140. package/dist/clis/xiaohongshu/creator-note-detail.test.js +80 -33
  141. package/dist/clis/xiaohongshu/creator-notes.js +35 -5
  142. package/dist/clis/xiaohongshu/creator-notes.test.js +35 -6
  143. package/dist/clis/xiaohongshu/creator-profile.js +0 -1
  144. package/dist/clis/xiaohongshu/creator-stats.js +0 -1
  145. package/dist/clis/xiaohongshu/download.js +2 -3
  146. package/dist/clis/xiaohongshu/feed.yaml +0 -1
  147. package/dist/clis/xiaohongshu/notifications.yaml +0 -1
  148. package/dist/clis/xiaohongshu/search.js +2 -2
  149. package/dist/clis/xiaohongshu/user.js +1 -2
  150. package/dist/clis/yahoo-finance/quote.js +0 -1
  151. package/dist/clis/youtube/search.js +1 -1
  152. package/dist/clis/youtube/transcript.js +1 -1
  153. package/dist/clis/youtube/video.js +1 -1
  154. package/dist/clis/zhihu/download.js +1 -2
  155. package/dist/clis/zhihu/question.js +1 -1
  156. package/dist/clis/zhihu/search.yaml +4 -3
  157. package/dist/commanderAdapter.d.ts +21 -0
  158. package/dist/commanderAdapter.js +111 -0
  159. package/dist/{engine.d.ts → discovery.d.ts} +0 -6
  160. package/dist/{engine.js → discovery.js} +1 -98
  161. package/dist/download/index.d.ts +2 -6
  162. package/dist/download/index.js +19 -46
  163. package/dist/engine.test.d.ts +1 -1
  164. package/dist/engine.test.js +8 -7
  165. package/dist/execution.d.ts +22 -0
  166. package/dist/execution.js +129 -0
  167. package/dist/explore.js +121 -107
  168. package/dist/external-clis.yaml +48 -0
  169. package/dist/external.d.ts +7 -2
  170. package/dist/external.js +11 -14
  171. package/dist/main.js +1 -1
  172. package/dist/pipeline/steps/browser.js +8 -2
  173. package/dist/registry.d.ts +2 -0
  174. package/dist/registry.js +2 -0
  175. package/dist/runtime.d.ts +5 -0
  176. package/dist/runtime.js +8 -0
  177. package/dist/serialization.d.ts +34 -0
  178. package/dist/serialization.js +63 -0
  179. package/dist/types.d.ts +4 -1
  180. package/docs/.vitepress/config.mts +14 -3
  181. package/docs/adapters/browser/arxiv.md +27 -0
  182. package/docs/adapters/browser/barchart.md +32 -0
  183. package/docs/adapters/browser/bloomberg.md +70 -0
  184. package/docs/adapters/browser/chaoxing.md +39 -0
  185. package/docs/adapters/browser/grok.md +35 -0
  186. package/docs/adapters/browser/hf.md +42 -0
  187. package/docs/adapters/browser/jike.md +45 -0
  188. package/docs/adapters/browser/jimeng.md +39 -0
  189. package/docs/adapters/browser/linux-do.md +45 -0
  190. package/docs/adapters/browser/sinafinance.md +35 -0
  191. package/docs/adapters/browser/stackoverflow.md +35 -0
  192. package/docs/adapters/browser/steam.md +26 -0
  193. package/docs/adapters/browser/twitter.md +3 -0
  194. package/docs/adapters/browser/weread.md +48 -0
  195. package/docs/adapters/browser/wikipedia.md +30 -0
  196. package/docs/adapters/browser/xiaohongshu.md +5 -1
  197. package/docs/adapters/desktop/chatgpt.md +3 -3
  198. package/docs/adapters/index.md +13 -0
  199. package/docs/advanced/download.md +4 -4
  200. package/docs/developer/architecture.md +17 -4
  201. package/package.json +1 -1
  202. package/scripts/check-doc-coverage.sh +69 -0
  203. package/scripts/copy-yaml.cjs +7 -0
  204. package/src/browser/cdp.ts +6 -1
  205. package/src/browser/page.ts +7 -1
  206. package/src/build-manifest.ts +25 -19
  207. package/src/cli.ts +218 -139
  208. package/src/clis/apple-podcasts/commands.test.ts +95 -0
  209. package/src/clis/apple-podcasts/search.ts +2 -2
  210. package/src/clis/apple-podcasts/top.ts +12 -2
  211. package/src/clis/arxiv/search.ts +1 -1
  212. package/src/clis/bilibili/dynamic.ts +1 -1
  213. package/src/clis/bilibili/favorite.ts +1 -1
  214. package/src/clis/bilibili/feed.ts +1 -1
  215. package/src/clis/bilibili/following.ts +1 -1
  216. package/src/clis/bilibili/history.ts +1 -1
  217. package/src/clis/bilibili/me.ts +1 -1
  218. package/src/clis/bilibili/ranking.ts +1 -1
  219. package/src/clis/bilibili/search.ts +3 -3
  220. package/src/clis/bilibili/subtitle.ts +1 -1
  221. package/src/clis/bilibili/user-videos.ts +1 -1
  222. package/src/{bilibili.ts → clis/bilibili/utils.ts} +1 -1
  223. package/src/clis/bloomberg/businessweek.ts +18 -0
  224. package/src/clis/bloomberg/economics.ts +18 -0
  225. package/src/clis/bloomberg/feeds.ts +16 -0
  226. package/src/clis/bloomberg/industries.ts +18 -0
  227. package/src/clis/bloomberg/main.ts +18 -0
  228. package/src/clis/bloomberg/markets.ts +18 -0
  229. package/src/clis/bloomberg/news.ts +136 -0
  230. package/src/clis/bloomberg/opinions.ts +18 -0
  231. package/src/clis/bloomberg/politics.ts +18 -0
  232. package/src/clis/bloomberg/tech.ts +18 -0
  233. package/src/clis/bloomberg/utils.test.ts +135 -0
  234. package/src/clis/bloomberg/utils.ts +429 -0
  235. package/src/clis/boss/batchgreet.ts +2 -2
  236. package/src/clis/boss/chatlist.ts +2 -2
  237. package/src/clis/boss/detail.ts +2 -2
  238. package/src/clis/boss/greet.ts +4 -4
  239. package/src/clis/boss/search.ts +1 -1
  240. package/src/clis/boss/send.ts +1 -1
  241. package/src/clis/boss/stats.ts +2 -2
  242. package/src/clis/chaoxing/assignments.ts +1 -1
  243. package/src/clis/chaoxing/exams.ts +1 -1
  244. package/src/{chaoxing.test.ts → clis/chaoxing/utils.test.ts} +1 -1
  245. package/src/{chaoxing.ts → clis/chaoxing/utils.ts} +1 -3
  246. package/src/clis/chatgpt/README.zh-CN.md +3 -3
  247. package/src/clis/chatgpt/read.ts +1 -1
  248. package/src/clis/chatwise/export.ts +1 -1
  249. package/src/clis/chatwise/model.ts +2 -2
  250. package/src/clis/chatwise/screenshot.ts +1 -1
  251. package/src/clis/codex/export.ts +1 -1
  252. package/src/clis/codex/model.ts +2 -2
  253. package/src/clis/codex/screenshot.ts +1 -1
  254. package/src/clis/coupang/add-to-cart.ts +3 -4
  255. package/src/clis/coupang/search.ts +2 -4
  256. package/src/{coupang.test.ts → clis/coupang/utils.test.ts} +1 -1
  257. package/src/clis/ctrip/search.ts +1 -1
  258. package/src/clis/cursor/export.ts +1 -1
  259. package/src/clis/cursor/model.ts +2 -2
  260. package/src/clis/cursor/screenshot.ts +1 -1
  261. package/src/clis/jike/comment.ts +2 -3
  262. package/src/clis/jike/create.ts +1 -2
  263. package/src/clis/jike/feed.ts +0 -1
  264. package/src/clis/jike/like.ts +1 -2
  265. package/src/clis/jike/notifications.ts +0 -1
  266. package/src/clis/jike/post.yaml +1 -0
  267. package/src/clis/jike/repost.ts +1 -2
  268. package/src/clis/jike/search.ts +2 -3
  269. package/src/clis/jike/topic.yaml +1 -0
  270. package/src/clis/jike/user.yaml +1 -0
  271. package/src/clis/jimeng/history.yaml +0 -1
  272. package/src/clis/linkedin/search.ts +7 -7
  273. package/src/clis/linux-do/category.yaml +1 -0
  274. package/src/clis/linux-do/search.yaml +4 -3
  275. package/src/clis/linux-do/topic.yaml +1 -0
  276. package/src/clis/notion/export.ts +1 -1
  277. package/src/clis/reddit/comment.ts +3 -4
  278. package/src/clis/reddit/read.ts +4 -5
  279. package/src/clis/reddit/save.ts +2 -3
  280. package/src/clis/reddit/saved.ts +0 -1
  281. package/src/clis/reddit/search.yaml +1 -0
  282. package/src/clis/reddit/subscribe.ts +0 -1
  283. package/src/clis/reddit/upvote.ts +2 -3
  284. package/src/clis/reddit/upvoted.ts +0 -1
  285. package/src/clis/reddit/user-comments.yaml +1 -0
  286. package/src/clis/reddit/user-posts.yaml +1 -0
  287. package/src/clis/reddit/user.yaml +1 -0
  288. package/src/clis/reuters/search.ts +1 -1
  289. package/src/clis/smzdm/search.ts +2 -3
  290. package/src/clis/stackoverflow/search.yaml +1 -0
  291. package/src/clis/steam/top-sellers.yaml +29 -0
  292. package/src/clis/twitter/accept.ts +2 -2
  293. package/src/clis/twitter/article.ts +2 -2
  294. package/src/clis/twitter/block.ts +92 -0
  295. package/src/clis/twitter/delete.ts +1 -1
  296. package/src/clis/twitter/hide-reply.ts +70 -0
  297. package/src/clis/twitter/like.ts +1 -1
  298. package/src/clis/twitter/post.ts +1 -1
  299. package/src/clis/twitter/reply-dm.ts +1 -1
  300. package/src/clis/twitter/reply.ts +2 -2
  301. package/src/clis/twitter/search.ts +1 -1
  302. package/src/clis/twitter/thread.ts +2 -2
  303. package/src/clis/twitter/trending.ts +113 -0
  304. package/src/clis/twitter/unblock.ts +75 -0
  305. package/src/clis/v2ex/topic.yaml +1 -0
  306. package/src/clis/weibo/hot.ts +0 -1
  307. package/src/clis/weread/book.ts +1 -1
  308. package/src/clis/weread/highlights.ts +1 -1
  309. package/src/clis/weread/notes.ts +1 -1
  310. package/src/clis/weread/search.ts +1 -1
  311. package/src/clis/wikipedia/search.ts +1 -1
  312. package/src/clis/xiaohongshu/creator-note-detail.test.ts +82 -33
  313. package/src/clis/xiaohongshu/creator-note-detail.ts +89 -5
  314. package/src/clis/xiaohongshu/creator-notes.test.ts +39 -6
  315. package/src/clis/xiaohongshu/creator-notes.ts +44 -5
  316. package/src/clis/xiaohongshu/creator-profile.ts +0 -1
  317. package/src/clis/xiaohongshu/creator-stats.ts +0 -1
  318. package/src/clis/xiaohongshu/download.ts +2 -3
  319. package/src/clis/xiaohongshu/feed.yaml +0 -1
  320. package/src/clis/xiaohongshu/notifications.yaml +0 -1
  321. package/src/clis/xiaohongshu/search.ts +2 -2
  322. package/src/clis/xiaohongshu/user.ts +1 -2
  323. package/src/clis/yahoo-finance/quote.ts +0 -1
  324. package/src/clis/youtube/search.ts +1 -1
  325. package/src/clis/youtube/transcript.ts +1 -1
  326. package/src/clis/youtube/video.ts +1 -1
  327. package/src/clis/zhihu/download.ts +1 -2
  328. package/src/clis/zhihu/question.ts +1 -1
  329. package/src/clis/zhihu/search.yaml +4 -3
  330. package/src/commanderAdapter.ts +113 -0
  331. package/src/{engine.ts → discovery.ts} +1 -108
  332. package/src/download/index.ts +21 -54
  333. package/src/engine.test.ts +8 -7
  334. package/src/execution.ts +138 -0
  335. package/src/explore.ts +135 -109
  336. package/src/external-clis.yaml +9 -0
  337. package/src/external.ts +15 -12
  338. package/src/main.ts +1 -1
  339. package/src/pipeline/steps/browser.ts +7 -2
  340. package/src/registry.ts +5 -0
  341. package/src/runtime.ts +9 -0
  342. package/src/serialization.ts +79 -0
  343. package/src/types.ts +1 -1
  344. package/tests/e2e/browser-public.test.ts +25 -0
  345. package/tests/e2e/public-commands.test.ts +55 -1
  346. package/dist/clis/twitter/trending.yaml +0 -46
  347. package/docs/public/CNAME +0 -1
  348. package/src/clis/twitter/trending.yaml +0 -46
  349. /package/dist/{bilibili.js → clis/bilibili/utils.js} +0 -0
  350. /package/dist/{chaoxing.test.d.ts → clis/bloomberg/businessweek.d.ts} +0 -0
  351. /package/dist/{coupang.test.d.ts → clis/bloomberg/economics.d.ts} +0 -0
  352. /package/dist/{coupang.d.ts → clis/coupang/utils.d.ts} +0 -0
  353. /package/dist/{coupang.js → clis/coupang/utils.js} +0 -0
  354. /package/src/{coupang.ts → clis/coupang/utils.ts} +0 -0
@@ -0,0 +1,429 @@
1
+ import { CliError } from '../../errors.js';
2
+
3
+ export const BLOOMBERG_FEEDS = {
4
+ main: 'https://feeds.bloomberg.com/news.rss',
5
+ markets: 'https://feeds.bloomberg.com/markets/news.rss',
6
+ economics: 'https://feeds.bloomberg.com/economics/news.rss',
7
+ industries: 'https://feeds.bloomberg.com/industries/news.rss',
8
+ tech: 'https://feeds.bloomberg.com/technology/news.rss',
9
+ politics: 'https://feeds.bloomberg.com/politics/news.rss',
10
+ businessweek: 'https://feeds.bloomberg.com/businessweek/news.rss',
11
+ opinions: 'https://feeds.bloomberg.com/bview/news.rss',
12
+ } as const;
13
+
14
+ const DEFAULT_USER_AGENT = 'Mozilla/5.0 (compatible; opencli)';
15
+
16
+ export type BloombergFeedName = keyof typeof BLOOMBERG_FEEDS;
17
+
18
+ export interface BloombergFeedItem {
19
+ title: string;
20
+ summary: string;
21
+ link: string;
22
+ mediaLinks: string[];
23
+ }
24
+
25
+ export interface BloombergStory {
26
+ headline?: string;
27
+ summary?: string;
28
+ url?: string;
29
+ body?: any;
30
+ lede?: any;
31
+ ledeImageUrl?: string;
32
+ socialImageUrl?: string;
33
+ imageAttachments?: Record<string, any>;
34
+ videoAttachments?: Record<string, any>;
35
+ }
36
+
37
+ export async function fetchBloombergFeed(name: BloombergFeedName, limit: number = 1): Promise<BloombergFeedItem[]> {
38
+ const feedUrl = BLOOMBERG_FEEDS[name];
39
+ if (!feedUrl) {
40
+ throw new CliError('ARGUMENT', `Unknown Bloomberg feed: ${name}`);
41
+ }
42
+
43
+ const resp = await fetch(feedUrl, {
44
+ headers: { 'User-Agent': DEFAULT_USER_AGENT },
45
+ });
46
+
47
+ if (!resp.ok) {
48
+ throw new CliError(
49
+ 'FETCH_ERROR',
50
+ `Bloomberg RSS HTTP ${resp.status}`,
51
+ 'Bloomberg may be temporarily unavailable; try again later.',
52
+ );
53
+ }
54
+
55
+ const xml = await resp.text();
56
+ const items = parseBloombergRss(xml);
57
+ if (!items.length) {
58
+ throw new CliError(
59
+ 'NOT_FOUND',
60
+ 'Bloomberg RSS feed returned no items',
61
+ 'Bloomberg may have changed the feed format.',
62
+ );
63
+ }
64
+
65
+ const count = Math.max(1, Math.min(Number(limit) || 1, 20));
66
+ return items.slice(0, count);
67
+ }
68
+
69
+ export function parseBloombergRss(xml: string): BloombergFeedItem[] {
70
+ const items: BloombergFeedItem[] = [];
71
+ const itemRegex = /<item\b[^>]*>([\s\S]*?)<\/item>/gi;
72
+ let match: RegExpExecArray | null;
73
+
74
+ while ((match = itemRegex.exec(xml))) {
75
+ const block = match[1];
76
+ const title = extractTagText(block, 'title');
77
+ const summary = extractTagText(block, 'description');
78
+ const link = extractTagText(block, 'link') || extractTagText(block, 'guid');
79
+ const mediaLinks = extractMediaLinksFromRssItem(block);
80
+
81
+ if (!title || !link) continue;
82
+ items.push({
83
+ title,
84
+ summary,
85
+ link,
86
+ mediaLinks,
87
+ });
88
+ }
89
+
90
+ return items;
91
+ }
92
+
93
+ export function normalizeBloombergLink(input: string): string {
94
+ const raw = String(input || '').trim();
95
+ if (!raw) {
96
+ throw new CliError('ARGUMENT', 'A Bloomberg link is required');
97
+ }
98
+ if (raw.startsWith('/')) return `https://www.bloomberg.com${raw}`;
99
+ return raw;
100
+ }
101
+
102
+ export function validateBloombergLink(input: string): string {
103
+ const normalized = normalizeBloombergLink(input);
104
+ let url: URL;
105
+ try {
106
+ url = new URL(normalized);
107
+ } catch {
108
+ throw new CliError(
109
+ 'ARGUMENT',
110
+ `Invalid Bloomberg link: ${input}`,
111
+ 'Pass a full https://www.bloomberg.com/... URL or a relative Bloomberg path.',
112
+ );
113
+ }
114
+
115
+ if (!/(?:\.|^)bloomberg\.com$/i.test(url.hostname)) {
116
+ throw new CliError(
117
+ 'ARGUMENT',
118
+ `Expected a bloomberg.com link, got: ${url.hostname}`,
119
+ 'Pass a Bloomberg article URL from bloomberg.com.',
120
+ );
121
+ }
122
+
123
+ return url.toString();
124
+ }
125
+
126
+ export function renderStoryBody(body: any): string {
127
+ const blocks = Array.isArray(body?.content) ? body.content : [];
128
+ const parts = blocks
129
+ .map((block: any) => renderBlock(block, 0))
130
+ .map((part: string) => normalizeBlockText(part))
131
+ .filter(Boolean);
132
+
133
+ return parts.join('\n\n').replace(/\n{3,}/g, '\n\n').trim();
134
+ }
135
+
136
+ export function extractStoryMediaLinks(story: BloombergStory): string[] {
137
+ const urls = new Set<string>();
138
+ collectMediaUrls(story?.ledeImageUrl, urls);
139
+ collectMediaUrls(story?.socialImageUrl, urls);
140
+ collectMediaUrls(story?.lede, urls);
141
+ collectMediaUrls(story?.imageAttachments, urls);
142
+ collectMediaUrls(story?.videoAttachments, urls);
143
+
144
+ const mediaBlocks = Array.isArray(story?.body?.content)
145
+ ? story.body.content.filter((block: any) => block?.type === 'media')
146
+ : [];
147
+ collectMediaUrls(mediaBlocks, urls);
148
+
149
+ return [...urls];
150
+ }
151
+
152
+ function renderBlock(block: any, depth: number): string {
153
+ if (!block || typeof block !== 'object') return '';
154
+
155
+ switch (block.type) {
156
+ case 'paragraph':
157
+ return renderInlineNodes(block.content || []);
158
+ case 'heading': {
159
+ const text = renderInlineNodes(block.content || []);
160
+ if (!text) return '';
161
+ const level = Number(block.data?.level ?? block.data?.weight ?? 2);
162
+ const prefix = level <= 1 ? '# ' : level === 2 ? '## ' : '### ';
163
+ return `${prefix}${text}`;
164
+ }
165
+ case 'blockquote': {
166
+ const text = renderInlineNodes(block.content || []);
167
+ if (!text) return '';
168
+ return text.split('\n').map((line: string) => line ? `> ${line}` : '>').join('\n');
169
+ }
170
+ case 'list':
171
+ return renderListBlock(block, depth);
172
+ case 'tabularData':
173
+ return renderTabularDataBlock(block);
174
+ case 'media':
175
+ return renderMediaBlock(block);
176
+ case 'inline-newsletter':
177
+ case 'newsletter':
178
+ case 'ad':
179
+ return '';
180
+ default: {
181
+ if (Array.isArray(block.content) && block.content.length > 0) {
182
+ const inlineText = renderInlineNodes(block.content);
183
+ if (inlineText) return inlineText;
184
+ const nested = block.content.map((child: any) => renderBlock(child, depth + 1)).filter(Boolean);
185
+ if (nested.length) return nested.join('\n');
186
+ }
187
+ return extractGenericText(block);
188
+ }
189
+ }
190
+ }
191
+
192
+ function renderInlineNodes(nodes: any[]): string {
193
+ return nodes.map((node) => renderInlineNode(node)).join('');
194
+ }
195
+
196
+ function renderInlineNode(node: any): string {
197
+ if (node == null) return '';
198
+ if (typeof node === 'string') return decodeXmlEntities(node);
199
+
200
+ switch (node.type) {
201
+ case 'text':
202
+ return decodeXmlEntities(node.value || '');
203
+ case 'linebreak':
204
+ return '\n';
205
+ case 'link':
206
+ case 'entity':
207
+ case 'strong':
208
+ case 'emphasis':
209
+ case 'italic':
210
+ case 'underline':
211
+ case 'span':
212
+ if (Array.isArray(node.content) && node.content.length > 0) {
213
+ return renderInlineNodes(node.content);
214
+ }
215
+ return decodeXmlEntities(node.value || '');
216
+ default:
217
+ if (Array.isArray(node.content) && node.content.length > 0) {
218
+ return renderInlineNodes(node.content);
219
+ }
220
+ if (typeof node.value === 'string') return decodeXmlEntities(node.value);
221
+ return '';
222
+ }
223
+ }
224
+
225
+ function renderListBlock(block: any, depth: number): string {
226
+ const items = Array.isArray(block.content) ? block.content : [];
227
+ if (!items.length) return '';
228
+
229
+ const listStyle = String(block.subType || block.data?.style || block.data?.listType || '');
230
+ const ordered = /\bordered\b|\bnumber(?:ed)?\b/i.test(listStyle);
231
+ let index = 1;
232
+
233
+ return items
234
+ .map((item: any) => {
235
+ const prefix = ordered ? `${index++}. ` : '- ';
236
+ return renderListItem(item, prefix, depth);
237
+ })
238
+ .filter(Boolean)
239
+ .join('\n');
240
+ }
241
+
242
+ function renderListItem(item: any, prefix: string, depth: number): string {
243
+ const indent = ' '.repeat(depth);
244
+ const body = normalizeBlockText(renderListItemBody(item, depth + 1));
245
+ if (!body) return '';
246
+
247
+ const lines = body.split('\n');
248
+ const head = `${indent}${prefix}${lines[0]}`;
249
+ if (lines.length === 1) return head;
250
+
251
+ const continuationIndent = `${indent}${' '.repeat(prefix.length)}`;
252
+ const tail = lines.slice(1).map((line) => `${continuationIndent}${line}`).join('\n');
253
+ return `${head}\n${tail}`;
254
+ }
255
+
256
+ function renderListItemBody(item: any, depth: number): string {
257
+ if (!item || typeof item !== 'object') return '';
258
+
259
+ if (item.type === 'list-item' && Array.isArray(item.content)) {
260
+ const parts = item.content
261
+ .map((child: any) => child?.type === 'paragraph'
262
+ ? renderInlineNodes(child.content || [])
263
+ : renderBlock(child, depth))
264
+ .map((part: string) => normalizeBlockText(part))
265
+ .filter(Boolean);
266
+ return parts.join('\n');
267
+ }
268
+
269
+ return renderBlock(item, depth);
270
+ }
271
+
272
+ function renderTabularDataBlock(block: any): string {
273
+ const rows = block?.data?.rows ?? block?.data?.table?.rows ?? block?.content;
274
+ if (!Array.isArray(rows) || !rows.length) {
275
+ return extractGenericText(block.data || block.content || block);
276
+ }
277
+
278
+ const lines = rows
279
+ .map((row: any) => extractGenericText(row))
280
+ .map((line) => normalizeBlockText(line))
281
+ .filter(Boolean);
282
+
283
+ return lines.join('\n');
284
+ }
285
+
286
+ function renderMediaBlock(block: any): string {
287
+ const candidates = [
288
+ block?.data?.chart?.caption,
289
+ block?.data?.attachment?.caption,
290
+ block?.data?.attachment?.title,
291
+ block?.data?.attachment?.subtitle,
292
+ block?.data?.video?.caption,
293
+ ];
294
+
295
+ const caption = candidates
296
+ .map((value) => normalizeBlockText(stripHtml(String(value || ''))))
297
+ .find(Boolean);
298
+
299
+ return caption || '';
300
+ }
301
+
302
+ function extractGenericText(value: any): string {
303
+ const parts: string[] = [];
304
+ collectText(value, parts);
305
+ return parts.join(' ').replace(/\s+/g, ' ').trim();
306
+ }
307
+
308
+ function collectText(value: any, out: string[]): void {
309
+ if (value == null) return;
310
+ if (typeof value === 'string') {
311
+ const text = normalizeBlockText(stripHtml(decodeXmlEntities(value)));
312
+ if (text) out.push(text);
313
+ return;
314
+ }
315
+ if (Array.isArray(value)) {
316
+ for (const item of value) collectText(item, out);
317
+ return;
318
+ }
319
+ if (typeof value === 'object') {
320
+ if (typeof value.value === 'string') {
321
+ const text = normalizeBlockText(stripHtml(decodeXmlEntities(value.value)));
322
+ if (text) out.push(text);
323
+ return;
324
+ }
325
+ if (Array.isArray(value.content)) {
326
+ collectText(value.content, out);
327
+ return;
328
+ }
329
+ for (const entry of Object.values(value)) collectText(entry, out);
330
+ }
331
+ }
332
+
333
+ function extractTagText(block: string, tag: string): string {
334
+ const safeTag = escapeRegExp(tag);
335
+ const match = block.match(new RegExp(`<${safeTag}(?:\\s[^>]*)?>([\\s\\S]*?)<\\/${safeTag}>`, 'i'));
336
+ if (!match) return '';
337
+ return normalizeBlockText(stripHtml(decodeXmlEntities(stripCdata(match[1]))));
338
+ }
339
+
340
+ function extractMediaLinksFromRssItem(block: string): string[] {
341
+ const links = new Set<string>();
342
+ const mediaRegex = /<(?:media:content|media:thumbnail|enclosure)\b[^>]*\burl="([^"]+)"[^>]*>/gi;
343
+ let match: RegExpExecArray | null;
344
+ while ((match = mediaRegex.exec(block))) {
345
+ const url = decodeXmlEntities(match[1] || '').trim();
346
+ if (url) links.add(url);
347
+ }
348
+ return [...links];
349
+ }
350
+
351
+ function collectMediaUrls(value: any, out: Set<string>, seen = new WeakSet<object>()): void {
352
+ if (value == null) return;
353
+
354
+ if (typeof value === 'string') {
355
+ const normalized = normalizeMediaUrl(value);
356
+ if (normalized) out.add(normalized);
357
+ return;
358
+ }
359
+
360
+ if (Array.isArray(value)) {
361
+ for (const item of value) collectMediaUrls(item, out, seen);
362
+ return;
363
+ }
364
+
365
+ if (typeof value === 'object') {
366
+ if (seen.has(value)) return;
367
+ seen.add(value);
368
+
369
+ for (const key of ['url', 'src', 'fallback', 'poster']) {
370
+ const candidate = (value as Record<string, any>)[key];
371
+ if (typeof candidate === 'string') {
372
+ const normalized = normalizeMediaUrl(candidate);
373
+ if (normalized) out.add(normalized);
374
+ }
375
+ }
376
+
377
+ for (const entry of Object.values(value)) {
378
+ collectMediaUrls(entry, out, seen);
379
+ }
380
+ }
381
+ }
382
+
383
+ function normalizeMediaUrl(value: string): string | null {
384
+ const url = decodeXmlEntities(String(value || '')).trim();
385
+ if (!/^https?:\/\//i.test(url)) return null;
386
+ if (!looksLikeMediaUrl(url)) return null;
387
+ return url;
388
+ }
389
+
390
+ function looksLikeMediaUrl(url: string): boolean {
391
+ return /(?:assets\.bwbx\.io|resource\.bloomberg\.com|media\.bloomberg\.com)/i.test(url)
392
+ || /\.(?:jpg|jpeg|png|webp|gif|svg|mp4|m3u8)(?:[?#].*)?$/i.test(url);
393
+ }
394
+
395
+ function stripCdata(value: string): string {
396
+ const match = value.match(/^<!\[CDATA\[([\s\S]*?)\]\]>$/);
397
+ return match ? match[1] : value;
398
+ }
399
+
400
+ function stripHtml(value: string): string {
401
+ return String(value || '').replace(/<[^>]+>/g, ' ');
402
+ }
403
+
404
+ function decodeXmlEntities(value: string): string {
405
+ return String(value || '')
406
+ .replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, '$1')
407
+ .replace(/&#(\d+);/g, (_m, code) => String.fromCodePoint(Number(code)))
408
+ .replace(/&#x([0-9a-f]+);/gi, (_m, code) => String.fromCodePoint(parseInt(code, 16)))
409
+ .replace(/&amp;/g, '&')
410
+ .replace(/&lt;/g, '<')
411
+ .replace(/&gt;/g, '>')
412
+ .replace(/&quot;/g, '"')
413
+ .replace(/&#39;/g, "'")
414
+ .replace(/&apos;/g, "'")
415
+ .replace(/&nbsp;/g, ' ');
416
+ }
417
+
418
+ function normalizeBlockText(value: string): string {
419
+ return String(value || '')
420
+ .replace(/\r/g, '')
421
+ .replace(/[ \t]+\n/g, '\n')
422
+ .replace(/\n[ \t]+/g, '\n')
423
+ .replace(/[ \t]{2,}/g, ' ')
424
+ .trim();
425
+ }
426
+
427
+ function escapeRegExp(value: string): string {
428
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
429
+ }
@@ -15,7 +15,7 @@ cli({
15
15
  strategy: Strategy.COOKIE,
16
16
  browser: true,
17
17
  args: [
18
- { name: 'job_id', default: '', help: 'Filter by encrypted job ID (greet all jobs if empty)' },
18
+ { name: 'job-id', default: '', help: 'Filter by encrypted job ID (greet all jobs if empty)' },
19
19
  { name: 'limit', type: 'int', default: 5, help: 'Max candidates to greet' },
20
20
  { name: 'text', default: '', help: 'Custom greeting message (uses default if empty)' },
21
21
  ],
@@ -23,7 +23,7 @@ cli({
23
23
  func: async (page: IPage | null, kwargs) => {
24
24
  if (!page) throw new Error('Browser page required');
25
25
 
26
- const filterJobId = kwargs.job_id || '';
26
+ const filterJobId = kwargs['job-id'] || '';
27
27
  const limit = kwargs.limit || 5;
28
28
  const text = kwargs.text || '你好,请问您对这个职位感兴趣吗?';
29
29
 
@@ -11,14 +11,14 @@ cli({
11
11
  args: [
12
12
  { name: 'page', type: 'int', default: 1, help: 'Page number' },
13
13
  { name: 'limit', type: 'int', default: 20, help: 'Number of results' },
14
- { name: 'job_id', default: '0', help: 'Filter by job ID (0=all)' },
14
+ { name: 'job-id', default: '0', help: 'Filter by job ID (0=all)' },
15
15
  ],
16
16
  columns: ['name', 'job', 'last_msg', 'last_time', 'uid', 'security_id'],
17
17
  func: async (page: IPage | null, kwargs) => {
18
18
  if (!page) throw new Error('Browser page required');
19
19
  await page.goto('https://www.zhipin.com/web/chat/index');
20
20
  await page.wait({ time: 2 });
21
- const jobId = kwargs.job_id || '0';
21
+ const jobId = kwargs['job-id'] || '0';
22
22
  const pageNum = kwargs.page || 1;
23
23
  const limit = kwargs.limit || 20;
24
24
  const targetUrl = `https://www.zhipin.com/wapi/zprelation/friend/getBossFriendListV2.json?page=${pageNum}&status=0&jobId=${jobId}`;
@@ -16,7 +16,7 @@ cli({
16
16
 
17
17
  browser: true,
18
18
  args: [
19
- { name: 'security_id', required: true, help: 'Security ID from search results (securityId field)' },
19
+ { name: 'security-id', required: true, help: 'Security ID from search results (securityId field)' },
20
20
  ],
21
21
  columns: [
22
22
  'name', 'salary', 'experience', 'degree', 'city', 'district',
@@ -28,7 +28,7 @@ cli({
28
28
  func: async (page: IPage | null, kwargs) => {
29
29
  if (!page) throw new Error('Browser page required');
30
30
 
31
- const securityId = kwargs.security_id;
31
+ const securityId = kwargs['security-id'];
32
32
 
33
33
  // Navigate to zhipin.com first to establish cookie context (referrer + cookies)
34
34
  await page.goto('https://www.zhipin.com/web/geek/job');
@@ -20,8 +20,8 @@ cli({
20
20
  browser: true,
21
21
  args: [
22
22
  { name: 'uid', required: true, help: 'Encrypted UID of the candidate (from recommend)' },
23
- { name: 'security_id', required: true, help: 'Security ID of the candidate' },
24
- { name: 'job_id', required: true, help: 'Encrypted job ID' },
23
+ { name: 'security-id', required: true, help: 'Security ID of the candidate' },
24
+ { name: 'job-id', required: true, help: 'Encrypted job ID' },
25
25
  { name: 'text', default: '', help: 'Custom greeting message (uses default template if empty)' },
26
26
  ],
27
27
  columns: ['status', 'detail'],
@@ -29,8 +29,8 @@ cli({
29
29
  if (!page) throw new Error('Browser page required');
30
30
 
31
31
  const uid = kwargs.uid;
32
- const securityId = kwargs.security_id;
33
- const jobId = kwargs.job_id;
32
+ const securityId = kwargs['security-id'];
33
+ const jobId = kwargs['job-id'];
34
34
  const text = kwargs.text;
35
35
 
36
36
  if (process.env.OPENCLI_VERBOSE) {
@@ -72,7 +72,7 @@ cli({
72
72
 
73
73
  browser: true,
74
74
  args: [
75
- { name: 'query', required: true, help: 'Search keyword (e.g. AI agent, 前端)' },
75
+ { name: 'query', required: true, positional: true, help: 'Search keyword (e.g. AI agent, 前端)' },
76
76
  { name: 'city', default: '北京', help: 'City name or code (e.g. 杭州, 上海, 101010100)' },
77
77
  { name: 'experience', default: '', help: 'Experience: 应届/1年以内/1-3年/3-5年/5-10年/10年以上' },
78
78
  { name: 'degree', default: '', help: 'Degree: 大专/本科/硕士/博士' },
@@ -17,7 +17,7 @@ cli({
17
17
  browser: true,
18
18
  args: [
19
19
  { name: 'uid', required: true, help: 'Encrypted UID of the candidate (from chatlist)' },
20
- { name: 'text', required: true, help: 'Message text to send' },
20
+ { name: 'text', required: true, positional: true, help: 'Message text to send' },
21
21
  ],
22
22
  columns: ['status', 'detail'],
23
23
  func: async (page: IPage | null, kwargs) => {
@@ -17,13 +17,13 @@ cli({
17
17
  strategy: Strategy.COOKIE,
18
18
  browser: true,
19
19
  args: [
20
- { name: 'job_id', default: '', help: 'Encrypted job ID (show all if empty)' },
20
+ { name: 'job-id', default: '', help: 'Encrypted job ID (show all if empty)' },
21
21
  ],
22
22
  columns: ['job_name', 'salary', 'city', 'status', 'total_chats', 'encrypt_job_id'],
23
23
  func: async (page: IPage | null, kwargs) => {
24
24
  if (!page) throw new Error('Browser page required');
25
25
 
26
- const filterJobId = kwargs.job_id || '';
26
+ const filterJobId = kwargs['job-id'] || '';
27
27
 
28
28
  if (process.env.OPENCLI_VERBOSE) {
29
29
  console.error('[opencli:boss] Fetching job statistics...');
@@ -3,7 +3,7 @@ import {
3
3
  getCourses, initSession, enterCourse, getTabIframeUrl,
4
4
  parseAssignmentsFromDom, sleep,
5
5
  type AssignmentRow,
6
- } from '../../chaoxing.js';
6
+ } from './utils.js';
7
7
 
8
8
  cli({
9
9
  site: 'chaoxing',
@@ -3,7 +3,7 @@ import {
3
3
  getCourses, initSession, enterCourse, getTabIframeUrl,
4
4
  parseExamsFromDom, sleep,
5
5
  type ExamRow,
6
- } from '../../chaoxing.js';
6
+ } from './utils.js';
7
7
 
8
8
  cli({
9
9
  site: 'chaoxing',
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import { formatTimestamp, workStatusLabel } from './chaoxing.js';
2
+ import { formatTimestamp, workStatusLabel } from './utils.js';
3
3
 
4
4
  function localDatePrefixFromMillis(ts: number): string {
5
5
  const d = new Date(ts);
@@ -6,7 +6,7 @@
6
6
  * course pages loaded as iframes.
7
7
  */
8
8
 
9
- import type { IPage } from './types.js';
9
+ import type { IPage } from '../../types.js';
10
10
 
11
11
  // ── Utilities ────────────────────────────────────────────────────────
12
12
 
@@ -96,7 +96,6 @@ export async function getCourses(page: IPage): Promise<ChaoxingCourse[]> {
96
96
  /** Navigate to the interaction page to establish a Chaoxing session. */
97
97
  export async function initSession(page: IPage): Promise<void> {
98
98
  await page.goto('https://mooc2-ans.chaoxing.com/mooc2-ans/visit/interaction');
99
- await page.wait(3);
100
99
  }
101
100
 
102
101
  /**
@@ -108,7 +107,6 @@ export async function enterCourse(page: IPage, course: ChaoxingCourse): Promise<
108
107
  `https://mooc1.chaoxing.com/visit/stucoursemiddle` +
109
108
  `?courseid=${course.courseId}&clazzid=${course.classId}&cpi=${course.cpi}&ismooc2=1&v=2`;
110
109
  await page.goto(url);
111
- await page.wait(3);
112
110
  }
113
111
 
114
112
  /**
@@ -14,7 +14,7 @@
14
14
  - `opencli chatgpt status`:检查 ChatGPT 应用是否在运行。
15
15
  - `opencli chatgpt new`:激活 ChatGPT 并按 `Cmd+N` 开始新对话。
16
16
  - `opencli chatgpt send "消息"`:将消息复制到剪贴板,激活 ChatGPT,粘贴并提交。
17
- - `opencli chatgpt read`:通过 `Cmd+Shift+C` 复制最后一条 AI 回复并返回文本。
17
+ - `opencli chatgpt read`:通过当前聚焦 ChatGPT 窗口的辅助功能树读取最后一条可见消息并返回文本。
18
18
 
19
19
  ## 方式二:CDP(高级,Electron 调试模式)
20
20
 
@@ -34,11 +34,11 @@ export OPENCLI_CDP_ENDPOINT="http://127.0.0.1:9224"
34
34
 
35
35
  ## 工作原理
36
36
 
37
- - **AppleScript 模式**:使用 `osascript` `pbcopy`/`pbpaste` 进行剪贴板文本传输,无需远程调试端口。
37
+ - **AppleScript 模式**:使用 `osascript` 控制 ChatGPT,发送消息时借助 `pbcopy`/`pbpaste` 粘贴文本,读取消息时通过 macOS 辅助功能树获取当前可见聊天内容。
38
38
  - **CDP 模式**:通过 Chrome DevTools Protocol 连接到 Electron 渲染进程,直接操作 DOM。
39
39
 
40
40
  ## 限制
41
41
 
42
42
  - 仅支持 macOS(AppleScript 依赖)
43
43
  - AppleScript 模式需要辅助功能权限
44
- - `read` 命令复制最后一条回复,更早的消息需手动滚动
44
+ - `read` 返回的是当前聚焦 ChatGPT 窗口里的最后一条可见消息;如果目标消息不在可见区域,需先手动滚动
@@ -6,7 +6,7 @@ import { getVisibleChatMessages } from './ax.js';
6
6
  export const readCommand = cli({
7
7
  site: 'chatgpt',
8
8
  name: 'read',
9
- description: 'Copy the most recent ChatGPT Desktop App response to clipboard and read it',
9
+ description: 'Read the last visible message from the focused ChatGPT Desktop window',
10
10
  domain: 'localhost',
11
11
  strategy: Strategy.PUBLIC,
12
12
  browser: false,
@@ -10,7 +10,7 @@ export const exportCommand = cli({
10
10
  strategy: Strategy.UI,
11
11
  browser: true,
12
12
  args: [
13
- { name: 'output', required: false, positional: true, help: 'Output file (default: /tmp/chatwise-export.md)' },
13
+ { name: 'output', required: false, help: 'Output file (default: /tmp/chatwise-export.md)' },
14
14
  ],
15
15
  columns: ['Status', 'File', 'Messages'],
16
16
  func: async (page: IPage, kwargs: any) => {
@@ -9,11 +9,11 @@ export const modelCommand = cli({
9
9
  strategy: Strategy.UI,
10
10
  browser: true,
11
11
  args: [
12
- { name: 'model_name', required: false, positional: true, help: 'Model to switch to (e.g. gpt-4, claude-3)' },
12
+ { name: 'model-name', required: false, positional: true, help: 'Model to switch to (e.g. gpt-4, claude-3)' },
13
13
  ],
14
14
  columns: ['Status', 'Model'],
15
15
  func: async (page: IPage, kwargs: any) => {
16
- const desiredModel = kwargs.model_name as string | undefined;
16
+ const desiredModel = kwargs['model-name'] as string | undefined;
17
17
 
18
18
  if (!desiredModel) {
19
19
  // Read current model
@@ -10,7 +10,7 @@ export const screenshotCommand = cli({
10
10
  strategy: Strategy.UI,
11
11
  browser: true,
12
12
  args: [
13
- { name: 'output', required: false, positional: true, help: 'Output file path (default: /tmp/chatwise-snapshot)' },
13
+ { name: 'output', required: false, help: 'Output file path (default: /tmp/chatwise-snapshot)' },
14
14
  ],
15
15
  columns: ['Status', 'File'],
16
16
  func: async (page: IPage, kwargs: any) => {
@@ -10,7 +10,7 @@ export const exportCommand = cli({
10
10
  strategy: Strategy.UI,
11
11
  browser: true,
12
12
  args: [
13
- { name: 'output', required: false, positional: true, help: 'Output file (default: /tmp/codex-export.md)' },
13
+ { name: 'output', required: false, help: 'Output file (default: /tmp/codex-export.md)' },
14
14
  ],
15
15
  columns: ['Status', 'File', 'Messages'],
16
16
  func: async (page: IPage, kwargs: any) => {