publishport-opencli 1.0.19 → 1.0.21

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 (1042) hide show
  1. package/cli-manifest.json +35 -0
  2. package/clis/12306/me.js +2 -2
  3. package/clis/12306/orders.js +3 -3
  4. package/clis/12306/passengers.js +3 -3
  5. package/clis/12306/price.js +1 -1
  6. package/clis/12306/stations.js +1 -1
  7. package/clis/12306/train.js +1 -1
  8. package/clis/12306/trains.js +1 -1
  9. package/clis/12306/utils.js +1 -1
  10. package/clis/1688/download.js +1 -1
  11. package/clis/1688/item.js +4 -4
  12. package/clis/1688/search.js +4 -4
  13. package/clis/1688/store.js +4 -4
  14. package/clis/1point3acres/forum.js +1 -1
  15. package/clis/1point3acres/forums.js +1 -1
  16. package/clis/1point3acres/notifications.js +1 -1
  17. package/clis/1point3acres/search.js +1 -1
  18. package/clis/1point3acres/thread.js +2 -2
  19. package/clis/1point3acres/user.js +1 -1
  20. package/clis/1point3acres/utils.js +2 -2
  21. package/clis/36kr/article.js +2 -2
  22. package/clis/36kr/hot.js +2 -2
  23. package/clis/36kr/news.js +1 -1
  24. package/clis/36kr/search.js +2 -2
  25. package/clis/51job/company.js +2 -2
  26. package/clis/51job/detail.js +2 -2
  27. package/clis/51job/hot.js +1 -1
  28. package/clis/51job/search.js +1 -1
  29. package/clis/51job/utils.js +7 -7
  30. package/clis/_atlassian/shared.js +9 -9
  31. package/clis/_shared/article/auth.js +4 -4
  32. package/clis/_shared/article/douban-md2draft.js +4 -4
  33. package/clis/_shared/article/format.js +1 -1
  34. package/clis/_shared/article/images.js +7 -7
  35. package/clis/_shared/article/login.js +1 -1
  36. package/clis/_shared/article/publish.js +11 -11
  37. package/clis/_shared/common.js +1 -1
  38. package/clis/_shared/content-guard.js +1 -1
  39. package/clis/_shared/desktop-commands.js +1 -1
  40. package/clis/_shared/file-inject.js +12 -12
  41. package/clis/_shared/self-hosted-sites.js +2 -2
  42. package/clis/_shared/site-auth.js +1 -1
  43. package/clis/_shared/token-auth.js +2 -2
  44. package/clis/_shared/video-publish.js +9 -9
  45. package/clis/amazon/offer.js +3 -3
  46. package/clis/amazon/product.js +5 -5
  47. package/clis/amazon/shared.js +6 -6
  48. package/clis/antigravity/_actions.js +13 -13
  49. package/clis/antigravity/audit-extras.js +10 -10
  50. package/clis/antigravity/delete.js +1 -1
  51. package/clis/antigravity/model.js +6 -6
  52. package/clis/antigravity/new.js +2 -2
  53. package/clis/antigravity/read.js +2 -2
  54. package/clis/antigravity/rename.js +1 -1
  55. package/clis/antigravity/send.js +3 -3
  56. package/clis/antigravity/serve.js +17 -17
  57. package/clis/antigravity/storage.js +6 -6
  58. package/clis/antigravity/watch.js +2 -2
  59. package/clis/apple-podcasts/episodes.js +1 -1
  60. package/clis/apple-podcasts/top.js +1 -1
  61. package/clis/apple-podcasts/utils.js +1 -1
  62. package/clis/archive/item.js +1 -1
  63. package/clis/archive/search.js +1 -1
  64. package/clis/archive/snapshots.js +1 -1
  65. package/clis/archive/wayback.js +1 -1
  66. package/clis/arxiv/author.js +1 -1
  67. package/clis/arxiv/utils.js +1 -1
  68. package/clis/autohome/brand.js +1 -1
  69. package/clis/autohome/score.js +1 -1
  70. package/clis/autohome/utils.js +1 -1
  71. package/clis/baijiahao/article.js +1 -1
  72. package/clis/baijiahao/lists.js +1 -1
  73. package/clis/baijiahao/login.js +1 -1
  74. package/clis/baijiahao/publish.js +9 -9
  75. package/clis/baijiahao/stats.js +1 -1
  76. package/clis/baijiahao/whoami.js +1 -1
  77. package/clis/band/bands.js +2 -2
  78. package/clis/band/mentions.js +2 -2
  79. package/clis/band/post.js +4 -4
  80. package/clis/band/posts.js +3 -3
  81. package/clis/barchart/flow.js +3 -3
  82. package/clis/barchart/greeks.js +2 -2
  83. package/clis/barchart/quote.js +3 -3
  84. package/clis/bbc/news.js +1 -1
  85. package/clis/bbc/topic.js +1 -1
  86. package/clis/bbc/utils.js +1 -1
  87. package/clis/bilibili/article.js +1 -1
  88. package/clis/bilibili/categories.js +1 -1
  89. package/clis/bilibili/coin.js +1 -1
  90. package/clis/bilibili/comment-del.js +1 -1
  91. package/clis/bilibili/comment-like.js +1 -1
  92. package/clis/bilibili/comment.js +1 -1
  93. package/clis/bilibili/comments.js +1 -1
  94. package/clis/bilibili/dm-list.js +1 -1
  95. package/clis/bilibili/dm-read.js +1 -1
  96. package/clis/bilibili/dm-send.js +1 -1
  97. package/clis/bilibili/download.js +2 -2
  98. package/clis/bilibili/drafts.js +1 -1
  99. package/clis/bilibili/dynamic-post.js +3 -3
  100. package/clis/bilibili/favorite-add.js +1 -1
  101. package/clis/bilibili/favorite.js +1 -1
  102. package/clis/bilibili/feed.js +2 -2
  103. package/clis/bilibili/follow.js +1 -1
  104. package/clis/bilibili/following.js +1 -1
  105. package/clis/bilibili/like.js +1 -1
  106. package/clis/bilibili/partitions.js +1 -1
  107. package/clis/bilibili/stats.js +1 -1
  108. package/clis/bilibili/subtitle.js +3 -3
  109. package/clis/bilibili/summary.js +1 -1
  110. package/clis/bilibili/topics.js +1 -1
  111. package/clis/bilibili/unfollow.js +1 -1
  112. package/clis/bilibili/upload.js +1 -1
  113. package/clis/bilibili/utils.js +10 -10
  114. package/clis/bilibili/video-drafts.js +1 -1
  115. package/clis/bilibili/video.js +1 -1
  116. package/clis/bloomberg/businessweek.js +4 -4
  117. package/clis/bloomberg/news.js +2 -2
  118. package/clis/bloomberg/utils.js +13 -13
  119. package/clis/bluesky/post.js +1 -1
  120. package/clis/bluesky/shared.js +11 -11
  121. package/clis/bluesky/whoami.js +1 -1
  122. package/clis/booking/search.js +2 -2
  123. package/clis/boss/accept.js +1 -1
  124. package/clis/boss/applied.js +1 -1
  125. package/clis/boss/auth.js +1 -1
  126. package/clis/boss/batchgreet.js +1 -1
  127. package/clis/boss/bossinfo.js +1 -1
  128. package/clis/boss/chatlist.js +1 -1
  129. package/clis/boss/chatmsg.js +1 -1
  130. package/clis/boss/cities.js +1 -1
  131. package/clis/boss/detail.js +1 -1
  132. package/clis/boss/exchange.js +1 -1
  133. package/clis/boss/greet.js +1 -1
  134. package/clis/boss/interviews.js +1 -1
  135. package/clis/boss/invite.js +1 -1
  136. package/clis/boss/joblist.js +1 -1
  137. package/clis/boss/mark.js +1 -1
  138. package/clis/boss/me.js +2 -2
  139. package/clis/boss/recommend.js +1 -1
  140. package/clis/boss/resumedelete.js +1 -1
  141. package/clis/boss/resumelist.js +1 -1
  142. package/clis/boss/resumeupload.js +11 -11
  143. package/clis/boss/search.js +1 -1
  144. package/clis/boss/send.js +1 -1
  145. package/clis/boss/sendresume.js +1 -1
  146. package/clis/boss/stats.js +1 -1
  147. package/clis/boss/unread.js +1 -1
  148. package/clis/boss/utils.js +13 -13
  149. package/clis/chaoxing/assignments.js +1 -1
  150. package/clis/chaoxing/exams.js +1 -1
  151. package/clis/chaoxing/utils.js +9 -9
  152. package/clis/chatgpt/ask.js +1 -1
  153. package/clis/chatgpt/auth.js +2 -2
  154. package/clis/chatgpt/detail.js +1 -1
  155. package/clis/chatgpt/image.js +1 -1
  156. package/clis/chatgpt/read.js +1 -1
  157. package/clis/chatgpt/send.js +1 -1
  158. package/clis/chatgpt/utils.js +59 -59
  159. package/clis/chatgpt-app/ask.js +1 -1
  160. package/clis/chatgpt-app/send.js +1 -1
  161. package/clis/chatwise/model.js +6 -6
  162. package/clis/chess/analyze.js +1 -1
  163. package/clis/chess/game.js +1 -1
  164. package/clis/chess/games.js +1 -1
  165. package/clis/chess/stats.js +1 -1
  166. package/clis/chess/utils.js +1 -1
  167. package/clis/claude/ask.js +2 -2
  168. package/clis/claude/detail.js +1 -1
  169. package/clis/claude/new.js +1 -1
  170. package/clis/claude/read.js +1 -1
  171. package/clis/claude/send.js +1 -1
  172. package/clis/claude/utils.js +23 -23
  173. package/clis/cnblogs/article.js +1 -1
  174. package/clis/cnblogs/login.js +1 -1
  175. package/clis/cnblogs/stats.js +7 -7
  176. package/clis/cnblogs/whoami.js +1 -1
  177. package/clis/codex/_actions.js +3 -3
  178. package/clis/codex/archive.js +1 -1
  179. package/clis/codex/ask.js +6 -6
  180. package/clis/codex/model.js +8 -8
  181. package/clis/codex/pin.js +1 -1
  182. package/clis/codex/rename.js +4 -4
  183. package/clis/codex/send.js +3 -3
  184. package/clis/codex/sidebar.js +1 -1
  185. package/clis/coingecko/categories.js +1 -1
  186. package/clis/coingecko/coin.js +1 -1
  187. package/clis/coingecko/derivatives.js +1 -1
  188. package/clis/coingecko/exchanges.js +1 -1
  189. package/clis/coingecko/global.js +1 -1
  190. package/clis/coingecko/trending.js +1 -1
  191. package/clis/confluence/search.js +1 -1
  192. package/clis/coupang/product.js +3 -3
  193. package/clis/coupang/utils.js +1 -1
  194. package/clis/crates/crate.js +1 -1
  195. package/clis/crates/search.js +1 -1
  196. package/clis/crates/utils.js +1 -1
  197. package/clis/csdn/article.js +13 -13
  198. package/clis/csdn/columns.js +1 -1
  199. package/clis/csdn/login.js +1 -1
  200. package/clis/csdn/stats.js +4 -4
  201. package/clis/csdn/whoami.js +1 -1
  202. package/clis/cto51/article.js +1 -1
  203. package/clis/cto51/whoami.js +1 -1
  204. package/clis/ctrip/flight.js +2 -2
  205. package/clis/ctrip/hotel-search.js +3 -3
  206. package/clis/ctrip/hotel-suggest.js +1 -1
  207. package/clis/ctrip/search.js +1 -1
  208. package/clis/ctrip/utils.js +6 -6
  209. package/clis/cursor/ask.js +5 -5
  210. package/clis/cursor/composer.js +2 -2
  211. package/clis/cursor/model.js +4 -4
  212. package/clis/cursor/send.js +2 -2
  213. package/clis/dblp/author.js +1 -1
  214. package/clis/dblp/paper.js +1 -1
  215. package/clis/dblp/search.js +1 -1
  216. package/clis/dblp/utils.js +1 -1
  217. package/clis/dblp/venue.js +1 -1
  218. package/clis/deepseek/ask.js +1 -1
  219. package/clis/deepseek/auth.js +2 -2
  220. package/clis/deepseek/detail.js +1 -1
  221. package/clis/deepseek/new.js +1 -1
  222. package/clis/deepseek/read.js +1 -1
  223. package/clis/deepseek/send.js +7 -7
  224. package/clis/deepseek/utils.js +25 -25
  225. package/clis/defillama/protocol.js +1 -1
  226. package/clis/defillama/protocols.js +1 -1
  227. package/clis/defillama/utils.js +1 -1
  228. package/clis/devto/auth.js +2 -2
  229. package/clis/devto/latest.js +1 -1
  230. package/clis/devto/publish.js +5 -5
  231. package/clis/devto/read.js +2 -2
  232. package/clis/devto/stats.js +3 -3
  233. package/clis/dianping/auth.js +2 -2
  234. package/clis/dianping/cityResolver.js +2 -2
  235. package/clis/dianping/search.js +1 -1
  236. package/clis/dianping/shop.js +1 -1
  237. package/clis/dianping/utils.js +1 -1
  238. package/clis/discord-app/delete.js +3 -3
  239. package/clis/discord-app/read.js +1 -1
  240. package/clis/discord-app/thread-read.js +1 -1
  241. package/clis/discord-web/auth.js +2 -2
  242. package/clis/discord-web/send.js +5 -5
  243. package/clis/dockerhub/image.js +1 -1
  244. package/clis/dockerhub/search.js +1 -1
  245. package/clis/dockerhub/utils.js +1 -1
  246. package/clis/dongchedi/koubei.js +1 -1
  247. package/clis/dongchedi/models.js +1 -1
  248. package/clis/dongchedi/score.js +1 -1
  249. package/clis/dongchedi/search.js +1 -1
  250. package/clis/dongchedi/series.js +1 -1
  251. package/clis/dongchedi/specs.js +1 -1
  252. package/clis/dongchedi/utils.js +1 -1
  253. package/clis/douban/article.js +3 -3
  254. package/clis/douban/auth.js +2 -2
  255. package/clis/douban/download.js +1 -1
  256. package/clis/douban/utils.js +23 -23
  257. package/clis/doubao/auth.js +3 -3
  258. package/clis/doubao/utils.js +39 -39
  259. package/clis/doubao-app/ask.js +1 -1
  260. package/clis/doubao-app/utils.js +4 -4
  261. package/clis/douyin/_shared/browser-fetch.js +7 -7
  262. package/clis/douyin/_shared/draft-cache.js +1 -1
  263. package/clis/douyin/_shared/imagex-upload.js +1 -1
  264. package/clis/douyin/_shared/sts2.js +1 -1
  265. package/clis/douyin/_shared/text-extra.js +1 -1
  266. package/clis/douyin/_shared/tos-upload.js +4 -4
  267. package/clis/douyin/_shared/transcode.js +1 -1
  268. package/clis/douyin/_shared/vod-upload.js +4 -4
  269. package/clis/douyin/draft.js +18 -18
  270. package/clis/douyin/drafts.js +1 -1
  271. package/clis/douyin/publish-image.js +12 -12
  272. package/clis/douyin/publish.js +9 -9
  273. package/clis/douyin/search.js +3 -3
  274. package/clis/douyin/videos.js +1 -1
  275. package/clis/eastmoney/_secid.js +1 -1
  276. package/clis/eastmoney/announcement.js +1 -1
  277. package/clis/eastmoney/article.js +1 -1
  278. package/clis/eastmoney/convertible.js +1 -1
  279. package/clis/eastmoney/etf.js +1 -1
  280. package/clis/eastmoney/holders.js +1 -1
  281. package/clis/eastmoney/index-board.js +1 -1
  282. package/clis/eastmoney/kline.js +1 -1
  283. package/clis/eastmoney/kuaixun.js +1 -1
  284. package/clis/eastmoney/longhu.js +1 -1
  285. package/clis/eastmoney/money-flow.js +1 -1
  286. package/clis/eastmoney/northbound.js +1 -1
  287. package/clis/eastmoney/quote.js +1 -1
  288. package/clis/eastmoney/rank.js +1 -1
  289. package/clis/eastmoney/sectors.js +1 -1
  290. package/clis/endoflife/product.js +1 -1
  291. package/clis/endoflife/utils.js +1 -1
  292. package/clis/facebook/comment.js +8 -8
  293. package/clis/facebook/feed.js +4 -4
  294. package/clis/facebook/like.js +5 -5
  295. package/clis/facebook/notifications.js +3 -3
  296. package/clis/facebook/post.js +11 -11
  297. package/clis/facebook/search.js +3 -3
  298. package/clis/flathub/app.js +1 -1
  299. package/clis/flathub/search.js +1 -1
  300. package/clis/flathub/utils.js +1 -1
  301. package/clis/flomo/memos.js +1 -1
  302. package/clis/gemini/deep-research-result.js +4 -4
  303. package/clis/gemini/deep-research.js +1 -1
  304. package/clis/gemini/detail.js +1 -1
  305. package/clis/gemini/history.js +1 -1
  306. package/clis/gemini/image.js +2 -2
  307. package/clis/gemini/status.js +1 -1
  308. package/clis/gemini/utils.js +43 -43
  309. package/clis/geogebra/hexagon.js +1 -1
  310. package/clis/geogebra/info.js +3 -3
  311. package/clis/geogebra/triangle.js +1 -1
  312. package/clis/geogebra/utils.js +11 -11
  313. package/clis/ghost/login.js +1 -1
  314. package/clis/ghost/shared.js +2 -2
  315. package/clis/gitee/auth.js +2 -2
  316. package/clis/github-trending/repos.js +1 -1
  317. package/clis/google/news.js +1 -1
  318. package/clis/google/search.js +2 -2
  319. package/clis/google/suggest.js +1 -1
  320. package/clis/google/trends.js +1 -1
  321. package/clis/google/utils.js +1 -1
  322. package/clis/goproxy/module.js +1 -1
  323. package/clis/goproxy/utils.js +1 -1
  324. package/clis/goproxy/versions.js +1 -1
  325. package/clis/gov-policy/recent.js +2 -2
  326. package/clis/gov-policy/search.js +2 -2
  327. package/clis/grok/ask.js +1 -1
  328. package/clis/grok/detail.js +1 -1
  329. package/clis/grok/image.js +8 -8
  330. package/clis/grok/pin.js +1 -1
  331. package/clis/grok/send.js +1 -1
  332. package/clis/grok/status.js +1 -1
  333. package/clis/grok/utils.js +21 -21
  334. package/clis/guazi/browse.js +1 -1
  335. package/clis/guazi/car.js +1 -1
  336. package/clis/guazi/utils.js +1 -1
  337. package/clis/hackernews/auth.js +2 -2
  338. package/clis/hackernews/comment.js +8 -8
  339. package/clis/hackernews/read.js +7 -7
  340. package/clis/hackernews/submit.js +7 -7
  341. package/clis/hashnode/auth.js +3 -3
  342. package/clis/hashnode/publish.js +14 -14
  343. package/clis/hf/auth.js +2 -2
  344. package/clis/hf/models.js +1 -1
  345. package/clis/hf/paper.js +1 -1
  346. package/clis/hf/spaces.js +1 -1
  347. package/clis/hf/top.js +1 -1
  348. package/clis/homebrew/cask.js +1 -1
  349. package/clis/homebrew/formula.js +1 -1
  350. package/clis/homebrew/popular.js +1 -1
  351. package/clis/homebrew/utils.js +1 -1
  352. package/clis/huodongxing/events.js +1 -1
  353. package/clis/hupu/detail.js +4 -4
  354. package/clis/hupu/hot.js +3 -3
  355. package/clis/hupu/reply.js +1 -1
  356. package/clis/hupu/search.js +1 -1
  357. package/clis/hupu/unlike.js +1 -1
  358. package/clis/hupu/utils.js +10 -10
  359. package/clis/imdb/person.js +2 -2
  360. package/clis/imdb/reviews.js +3 -3
  361. package/clis/imdb/search.js +2 -2
  362. package/clis/imdb/title.js +1 -1
  363. package/clis/imdb/top.js +1 -1
  364. package/clis/imdb/trending.js +1 -1
  365. package/clis/imdb/utils.js +13 -13
  366. package/clis/imooc/article.js +6 -6
  367. package/clis/imooc/login.js +1 -1
  368. package/clis/imooc/whoami.js +1 -1
  369. package/clis/indeed/job.js +2 -2
  370. package/clis/indeed/search.js +2 -2
  371. package/clis/indeed/utils.js +1 -1
  372. package/clis/infoq/article.js +2 -2
  373. package/clis/infoq/login.js +1 -1
  374. package/clis/infoq/whoami.js +1 -1
  375. package/clis/instagram/_shared/private-publish.js +4 -4
  376. package/clis/instagram/_shared/protocol-capture.js +9 -9
  377. package/clis/instagram/detail.js +3 -3
  378. package/clis/instagram/post.js +25 -25
  379. package/clis/instagram/reel.js +18 -18
  380. package/clis/instagram/story.js +4 -4
  381. package/clis/instagram/user.js +2 -2
  382. package/clis/jd/item.js +65 -65
  383. package/clis/jianshu/article.js +1 -1
  384. package/clis/jianshu/login.js +1 -1
  385. package/clis/jianshu/notebooks.js +1 -1
  386. package/clis/jianshu/whoami.js +1 -1
  387. package/clis/jianyu/search.js +6 -6
  388. package/clis/jianyu/shared/procurement-contract.js +1 -1
  389. package/clis/jike/api.js +6 -6
  390. package/clis/jike/auth.js +3 -3
  391. package/clis/jike/comment.js +1 -1
  392. package/clis/jike/create.js +6 -6
  393. package/clis/jike/feed.js +3 -3
  394. package/clis/jike/like.js +1 -1
  395. package/clis/jike/notifications.js +3 -3
  396. package/clis/jike/repost.js +13 -13
  397. package/clis/jike/search.js +3 -3
  398. package/clis/jimeng/auth.js +2 -2
  399. package/clis/jimeng/generate.js +10 -10
  400. package/clis/jira/shared.js +1 -1
  401. package/clis/juejin/article.js +4 -4
  402. package/clis/juejin/categories.js +1 -1
  403. package/clis/juejin/hot.js +1 -1
  404. package/clis/juejin/login.js +1 -1
  405. package/clis/juejin/recommend.js +1 -1
  406. package/clis/juejin/stats.js +3 -3
  407. package/clis/juejin/tags.js +1 -1
  408. package/clis/juejin/utils.js +1 -1
  409. package/clis/juejin/whoami.js +1 -1
  410. package/clis/ke/utils.js +4 -4
  411. package/clis/kimi/_utils.js +7 -7
  412. package/clis/kimi/audit-extras.js +14 -14
  413. package/clis/kimi/auth.js +3 -3
  414. package/clis/kimi/chat.js +15 -15
  415. package/clis/kimi/storage.js +5 -5
  416. package/clis/kimi/ui.js +13 -13
  417. package/clis/kimi/usage.js +3 -3
  418. package/clis/kuaishou/auth.js +1 -1
  419. package/clis/kuaishou/publish.js +6 -6
  420. package/clis/lesswrong/_helpers.js +5 -5
  421. package/clis/lesswrong/comments.js +5 -5
  422. package/clis/lichess/top.js +1 -1
  423. package/clis/lichess/user.js +1 -1
  424. package/clis/lichess/utils.js +1 -1
  425. package/clis/linkedin/comment.js +1 -1
  426. package/clis/linkedin/connect.js +11 -11
  427. package/clis/linkedin/create.js +5 -5
  428. package/clis/linkedin/follow.js +1 -1
  429. package/clis/linkedin/inbox.js +1 -1
  430. package/clis/linkedin/job-detail.js +3 -3
  431. package/clis/linkedin/like.js +1 -1
  432. package/clis/linkedin/notifications.js +1 -1
  433. package/clis/linkedin/people-search.js +1 -1
  434. package/clis/linkedin/post-comments.js +1 -1
  435. package/clis/linkedin/post-detail.js +1 -1
  436. package/clis/linkedin/profile-analytics.js +1 -1
  437. package/clis/linkedin/profile-api.js +13 -13
  438. package/clis/linkedin/profile-experience.js +1 -1
  439. package/clis/linkedin/profile-projects.js +1 -1
  440. package/clis/linkedin/profile-read.js +3 -3
  441. package/clis/linkedin/reply.js +1 -1
  442. package/clis/linkedin/repost.js +1 -1
  443. package/clis/linkedin/safe-send.js +8 -8
  444. package/clis/linkedin/salesnav-inbox.js +5 -5
  445. package/clis/linkedin/salesnav-message.js +3 -3
  446. package/clis/linkedin/salesnav-search.js +4 -4
  447. package/clis/linkedin/salesnav-thread.js +1 -1
  448. package/clis/linkedin/search-api.js +3 -3
  449. package/clis/linkedin/search.js +8 -8
  450. package/clis/linkedin/social-api.js +31 -31
  451. package/clis/linkedin/timeline.js +3 -3
  452. package/clis/linkedin-learning/search.js +2 -2
  453. package/clis/linkedin-learning/trending.js +2 -2
  454. package/clis/linux-do/feed.js +4 -4
  455. package/clis/linux-do/topic-content.js +8 -8
  456. package/clis/lobsters/domain.js +1 -1
  457. package/clis/lobsters/read.js +7 -7
  458. package/clis/maimai/auth.js +2 -2
  459. package/clis/maimai/search-talents.js +4 -4
  460. package/clis/manus/_utils.js +1 -1
  461. package/clis/mastodon/login.js +1 -1
  462. package/clis/mastodon/post.js +1 -1
  463. package/clis/mastodon/shared.js +12 -12
  464. package/clis/mastodon/whoami.js +1 -1
  465. package/clis/maven/artifact.js +1 -1
  466. package/clis/maven/search.js +1 -1
  467. package/clis/maven/utils.js +1 -1
  468. package/clis/mdn/search.js +1 -1
  469. package/clis/medium/article.js +5 -5
  470. package/clis/medium/login.js +1 -1
  471. package/clis/medium/stats.js +3 -3
  472. package/clis/medium/tag.js +1 -1
  473. package/clis/medium/whoami.js +1 -1
  474. package/clis/mubu/notes.js +1 -1
  475. package/clis/mubu/utils.js +15 -15
  476. package/clis/note/auth.js +2 -2
  477. package/clis/note/note-utils.js +2 -2
  478. package/clis/note/publish.js +8 -8
  479. package/clis/notebooklm/add-source.js +8 -8
  480. package/clis/notebooklm/create.js +1 -1
  481. package/clis/notebooklm/generate-slides.js +1 -1
  482. package/clis/notebooklm/notes-get.js +1 -1
  483. package/clis/notebooklm/rpc.js +8 -8
  484. package/clis/notebooklm/source-fulltext.js +1 -1
  485. package/clis/notebooklm/utils.js +10 -10
  486. package/clis/nowcoder/auth.js +2 -2
  487. package/clis/nowcoder/comment.js +1 -1
  488. package/clis/nowcoder/comments.js +1 -1
  489. package/clis/nowcoder/dm-list.js +1 -1
  490. package/clis/nowcoder/dm-read.js +1 -1
  491. package/clis/nowcoder/dm-send.js +1 -1
  492. package/clis/nowcoder/drafts.js +1 -1
  493. package/clis/nowcoder/inbox.js +1 -1
  494. package/clis/nowcoder/like.js +1 -1
  495. package/clis/nowcoder/my.js +1 -1
  496. package/clis/nowcoder/post.js +1 -1
  497. package/clis/nowcoder/utils.js +13 -13
  498. package/clis/npm/downloads.js +1 -1
  499. package/clis/npm/package.js +1 -1
  500. package/clis/npm/search.js +1 -1
  501. package/clis/npm/utils.js +1 -1
  502. package/clis/nuget/package.js +1 -1
  503. package/clis/nuget/search.js +1 -1
  504. package/clis/nuget/utils.js +1 -1
  505. package/clis/nvd/cve.js +1 -1
  506. package/clis/oeis/search.js +1 -1
  507. package/clis/oeis/sequence.js +1 -1
  508. package/clis/oeis/utils.js +1 -1
  509. package/clis/ones/common.js +6 -6
  510. package/clis/ones/enrich-tasks.js +1 -1
  511. package/clis/ones/my-tasks.js +1 -1
  512. package/clis/ones/resolve-labels.js +1 -1
  513. package/clis/ones/task-helpers.js +1 -1
  514. package/clis/ones/task.js +1 -1
  515. package/clis/ones/worklog.js +7 -7
  516. package/clis/openalex/search.js +1 -1
  517. package/clis/openalex/utils.js +1 -1
  518. package/clis/openalex/work.js +1 -1
  519. package/clis/openfda/drug-label.js +1 -1
  520. package/clis/openfda/food-recall.js +1 -1
  521. package/clis/openfda/utils.js +1 -1
  522. package/clis/openreview/author.js +1 -1
  523. package/clis/openreview/paper.js +1 -1
  524. package/clis/openreview/reviews.js +3 -3
  525. package/clis/openreview/search.js +1 -1
  526. package/clis/openreview/utils.js +1 -1
  527. package/clis/openreview/venue.js +1 -1
  528. package/clis/oschina/article.js +1 -1
  529. package/clis/oschina/catalogs.js +1 -1
  530. package/clis/oschina/login.js +1 -1
  531. package/clis/oschina/stats.js +6 -6
  532. package/clis/oschina/whoami.js +1 -1
  533. package/clis/osv/query.js +1 -1
  534. package/clis/osv/utils.js +1 -1
  535. package/clis/osv/vulnerability.js +1 -1
  536. package/clis/packagist/package.js +1 -1
  537. package/clis/packagist/search.js +1 -1
  538. package/clis/packagist/utils.js +1 -1
  539. package/clis/pinterest/api.js +1 -1
  540. package/clis/pinterest/auth.js +1 -1
  541. package/clis/pinterest/boards.js +1 -1
  542. package/clis/pinterest/client.js +6 -6
  543. package/clis/pinterest/publish.js +5 -5
  544. package/clis/pixiv/auth.js +2 -2
  545. package/clis/pixiv/download.js +1 -1
  546. package/clis/pixiv/illusts.js +1 -1
  547. package/clis/pixiv/search.js +1 -1
  548. package/clis/pixiv/utils.js +3 -3
  549. package/clis/powerchina/auth.js +2 -2
  550. package/clis/powerchina/search.js +1 -1
  551. package/clis/producthunt/browse.js +2 -2
  552. package/clis/producthunt/hot.js +2 -2
  553. package/clis/producthunt/posts.js +1 -1
  554. package/clis/producthunt/today.js +1 -1
  555. package/clis/producthunt/utils.js +1 -1
  556. package/clis/pypi/downloads.js +1 -1
  557. package/clis/pypi/package.js +1 -1
  558. package/clis/pypi/utils.js +1 -1
  559. package/clis/qiita/auth.js +1 -1
  560. package/clis/qiita/gql.js +6 -6
  561. package/clis/qiita/publish.js +4 -4
  562. package/clis/qiita/stats.js +4 -4
  563. package/clis/qoder/_utils.js +16 -16
  564. package/clis/qoder/composer.js +1 -1
  565. package/clis/qoder/quest.js +1 -1
  566. package/clis/qoder/ui.js +14 -14
  567. package/clis/quark/auth.js +2 -2
  568. package/clis/quark/utils.js +2 -2
  569. package/clis/quora/answer-comments.js +3 -3
  570. package/clis/quora/create-answer.js +4 -4
  571. package/clis/quora/create-question.js +3 -3
  572. package/clis/quora/shared.js +17 -17
  573. package/clis/quora/whoami.js +1 -1
  574. package/clis/qwen/ask.js +1 -1
  575. package/clis/qwen/auth.js +3 -3
  576. package/clis/qwen/detail.js +1 -1
  577. package/clis/qwen/image.js +5 -5
  578. package/clis/qwen/status.js +1 -1
  579. package/clis/qwen/utils.js +16 -16
  580. package/clis/reddit/home.js +3 -3
  581. package/clis/reddit/read.js +10 -10
  582. package/clis/reddit/reply.js +4 -4
  583. package/clis/reddit/submit.js +11 -11
  584. package/clis/reddit/subreddit-info.js +3 -3
  585. package/clis/reddit/subscribed.js +4 -4
  586. package/clis/reddit/whoami.js +2 -2
  587. package/clis/rednote/comments.js +1 -1
  588. package/clis/rednote/download.js +1 -1
  589. package/clis/rednote/feed.js +1 -1
  590. package/clis/rednote/note.js +1 -1
  591. package/clis/rednote/notifications.js +2 -2
  592. package/clis/rednote/search.js +2 -2
  593. package/clis/rest-countries/country.js +1 -1
  594. package/clis/rest-countries/region.js +1 -1
  595. package/clis/rest-countries/utils.js +1 -1
  596. package/clis/reuters/article-detail.js +1 -1
  597. package/clis/reuters/auth.js +3 -3
  598. package/clis/reuters/search.js +1 -1
  599. package/clis/reuters/utils.js +4 -4
  600. package/clis/rfc/rfc.js +1 -1
  601. package/clis/rfc/utils.js +1 -1
  602. package/clis/rubygems/gem.js +1 -1
  603. package/clis/rubygems/search.js +1 -1
  604. package/clis/rubygems/utils.js +1 -1
  605. package/clis/segmentfault/article.js +3 -3
  606. package/clis/segmentfault/channels.js +1 -1
  607. package/clis/segmentfault/login.js +1 -1
  608. package/clis/segmentfault/stats.js +4 -4
  609. package/clis/segmentfault/tags.js +1 -1
  610. package/clis/segmentfault/whoami.js +1 -1
  611. package/clis/semanticscholar/citations.js +1 -1
  612. package/clis/semanticscholar/paper.js +1 -1
  613. package/clis/semanticscholar/recommendations.js +1 -1
  614. package/clis/semanticscholar/search.js +1 -1
  615. package/clis/semanticscholar/utils.js +1 -1
  616. package/clis/sinafinance/news.js +1 -1
  617. package/clis/sinafinance/stock-rank.js +1 -1
  618. package/clis/sinafinance/stock.js +1 -1
  619. package/clis/slock/attachment-download.js +1 -1
  620. package/clis/slock/attachment-upload.js +7 -7
  621. package/clis/slock/attachment-url.js +1 -1
  622. package/clis/slock/auth-verify.js +1 -1
  623. package/clis/slock/bookmark-add.js +1 -1
  624. package/clis/slock/bookmark-remove.js +1 -1
  625. package/clis/slock/channel-action.js +1 -1
  626. package/clis/slock/channel-create.js +1 -1
  627. package/clis/slock/channel-files.js +1 -1
  628. package/clis/slock/channel-list.js +1 -1
  629. package/clis/slock/channel-mark.js +1 -1
  630. package/clis/slock/channel-members.js +1 -1
  631. package/clis/slock/channel-unarchive.js +1 -1
  632. package/clis/slock/dm-list.js +1 -1
  633. package/clis/slock/errors.js +1 -1
  634. package/clis/slock/in-page.js +28 -28
  635. package/clis/slock/inbox-done.js +5 -5
  636. package/clis/slock/inbox.js +1 -1
  637. package/clis/slock/message-read.js +22 -22
  638. package/clis/slock/message-search.js +10 -10
  639. package/clis/slock/message-send.js +25 -25
  640. package/clis/slock/reaction-add.js +1 -1
  641. package/clis/slock/reaction-remove.js +1 -1
  642. package/clis/slock/resolve.js +1 -1
  643. package/clis/slock/server-list.js +2 -2
  644. package/clis/slock/task-claim.js +4 -4
  645. package/clis/slock/task-convert.js +13 -13
  646. package/clis/slock/task-create.js +1 -1
  647. package/clis/slock/task-delete.js +5 -5
  648. package/clis/slock/task-get.js +5 -5
  649. package/clis/slock/task-list-server.js +4 -4
  650. package/clis/slock/task-list.js +5 -5
  651. package/clis/slock/task-status.js +3 -3
  652. package/clis/slock/task-unclaim.js +3 -3
  653. package/clis/slock/thread-follow.js +1 -1
  654. package/clis/slock/thread-state.js +1 -1
  655. package/clis/slock/unread-summary.js +5 -5
  656. package/clis/slock/whoami.js +1 -1
  657. package/clis/smzdm/search.js +3 -3
  658. package/clis/sohu/article.js +1 -1
  659. package/clis/sohu/attributes.js +1 -1
  660. package/clis/sohu/categories.js +1 -1
  661. package/clis/sohu/channels.js +1 -1
  662. package/clis/sohu/columns.js +1 -1
  663. package/clis/sohu/login.js +1 -1
  664. package/clis/sohu/stats.js +3 -3
  665. package/clis/sohu/topics.js +1 -1
  666. package/clis/sohu/whoami.js +1 -1
  667. package/clis/spotify/spotify.js +1 -1
  668. package/clis/stackoverflow/read.js +6 -6
  669. package/clis/stackoverflow/related.js +1 -1
  670. package/clis/stackoverflow/tag.js +1 -1
  671. package/clis/stackoverflow/user.js +1 -1
  672. package/clis/stackoverflow/utils.js +1 -1
  673. package/clis/steam/app.js +1 -1
  674. package/clis/steam/search.js +1 -1
  675. package/clis/steam/utils.js +1 -1
  676. package/clis/substack/auth.js +2 -2
  677. package/clis/substack/post-utils.js +2 -2
  678. package/clis/substack/publish.js +5 -5
  679. package/clis/suno/auth.js +2 -2
  680. package/clis/suno/download.js +5 -5
  681. package/clis/suno/generate.js +3 -3
  682. package/clis/suno/list.js +1 -1
  683. package/clis/suno/status.js +1 -1
  684. package/clis/suno/utils.js +29 -29
  685. package/clis/telegram/send.js +7 -7
  686. package/clis/telegram/shared.js +2 -2
  687. package/clis/telegram/whoami.js +1 -1
  688. package/clis/test-utils.js +1 -1
  689. package/clis/threads/post.js +23 -23
  690. package/clis/threads/shared.js +3 -3
  691. package/clis/threads/whoami.js +1 -1
  692. package/clis/tieba/hot.js +3 -3
  693. package/clis/tieba/posts.js +1 -1
  694. package/clis/tieba/read.js +2 -2
  695. package/clis/tieba/search.js +3 -3
  696. package/clis/tieba/utils.js +1 -1
  697. package/clis/tiktok/comment.js +2 -2
  698. package/clis/tiktok/creator-videos.js +3 -3
  699. package/clis/tiktok/explore.js +3 -3
  700. package/clis/tiktok/follow.js +8 -8
  701. package/clis/tiktok/following.js +4 -4
  702. package/clis/tiktok/friends.js +7 -7
  703. package/clis/tiktok/live.js +3 -3
  704. package/clis/tiktok/notifications.js +6 -6
  705. package/clis/tiktok/publish.js +16 -16
  706. package/clis/tiktok/unfollow.js +4 -4
  707. package/clis/tiktok/user.js +2 -2
  708. package/clis/tiktok/utils.js +1 -1
  709. package/clis/toutiao/article.js +1 -1
  710. package/clis/toutiao/articles.js +2 -2
  711. package/clis/toutiao/auth.js +2 -2
  712. package/clis/toutiao/hot.js +1 -1
  713. package/clis/toutiao/recommend.js +1 -1
  714. package/clis/toutiao/utils.js +2 -2
  715. package/clis/trae-solo/_actions.js +4 -4
  716. package/clis/trae-solo/_fs.js +5 -5
  717. package/clis/trae-solo/_state.js +2 -2
  718. package/clis/trae-solo/automation.js +4 -4
  719. package/clis/trae-solo/mode.js +4 -4
  720. package/clis/trae-solo/model.js +4 -4
  721. package/clis/trae-solo/renderer-storage.js +4 -4
  722. package/clis/trae-solo/settings.js +1 -1
  723. package/clis/trae-solo/skill-fs.js +1 -1
  724. package/clis/trae-solo/skill.js +12 -12
  725. package/clis/trae-solo/state-fs.js +2 -2
  726. package/clis/trae-solo/task-fs.js +5 -5
  727. package/clis/trae-solo/workspaces-fs.js +1 -1
  728. package/clis/tumblr/auth.js +2 -2
  729. package/clis/tumblr/publish.js +3 -3
  730. package/clis/tvmaze/search.js +1 -1
  731. package/clis/tvmaze/show.js +1 -1
  732. package/clis/tvmaze/utils.js +1 -1
  733. package/clis/twitter/accept.js +7 -7
  734. package/clis/twitter/article.js +7 -7
  735. package/clis/twitter/bookmark-folder.js +3 -3
  736. package/clis/twitter/bookmark-folders.js +3 -3
  737. package/clis/twitter/bookmarks.js +4 -4
  738. package/clis/twitter/delete.js +5 -5
  739. package/clis/twitter/device-follow.js +3 -3
  740. package/clis/twitter/dm-list.js +1 -1
  741. package/clis/twitter/dm-read.js +1 -1
  742. package/clis/twitter/dm-send.js +5 -5
  743. package/clis/twitter/dm-shared.js +3 -3
  744. package/clis/twitter/download.js +6 -6
  745. package/clis/twitter/followers.js +5 -5
  746. package/clis/twitter/following.js +2 -2
  747. package/clis/twitter/like.js +3 -3
  748. package/clis/twitter/likes.js +8 -8
  749. package/clis/twitter/list-add-core.js +9 -9
  750. package/clis/twitter/list-create.js +5 -5
  751. package/clis/twitter/list-remove-core.js +9 -9
  752. package/clis/twitter/list-tweets.js +7 -7
  753. package/clis/twitter/lists.js +6 -6
  754. package/clis/twitter/notifications.js +2 -2
  755. package/clis/twitter/post.js +24 -24
  756. package/clis/twitter/profile.js +7 -7
  757. package/clis/twitter/quote.js +7 -7
  758. package/clis/twitter/reply-dm.js +7 -7
  759. package/clis/twitter/reply.js +12 -12
  760. package/clis/twitter/retweet.js +3 -3
  761. package/clis/twitter/scheduled-delete.js +3 -3
  762. package/clis/twitter/scheduled-list.js +2 -2
  763. package/clis/twitter/scheduled-utils.js +3 -3
  764. package/clis/twitter/search.js +5 -5
  765. package/clis/twitter/shared.js +6 -6
  766. package/clis/twitter/thread.js +4 -4
  767. package/clis/twitter/timeline.js +3 -3
  768. package/clis/twitter/trending.js +2 -2
  769. package/clis/twitter/tweets.js +11 -11
  770. package/clis/twitter/unlike.js +3 -3
  771. package/clis/twitter/unretweet.js +3 -3
  772. package/clis/twitter/utils.js +6 -6
  773. package/clis/upwork/detail.js +2 -2
  774. package/clis/upwork/feed.js +2 -2
  775. package/clis/upwork/search.js +2 -2
  776. package/clis/upwork/utils.js +1 -1
  777. package/clis/v2ex/auth.js +2 -2
  778. package/clis/v2ex/daily.js +3 -3
  779. package/clis/v2ex/me.js +2 -2
  780. package/clis/v2ex/notifications.js +2 -2
  781. package/clis/vk/post.js +1 -1
  782. package/clis/vk/shared.js +2 -2
  783. package/clis/vk/whoami.js +1 -1
  784. package/clis/web/read.js +8 -8
  785. package/clis/wechat-channels/auth.js +2 -2
  786. package/clis/wechat-channels/publish.js +30 -30
  787. package/clis/weibo/article.js +1 -1
  788. package/clis/weibo/auth.js +3 -3
  789. package/clis/weibo/comments.js +1 -1
  790. package/clis/weibo/delete.js +2 -2
  791. package/clis/weibo/feed.js +3 -3
  792. package/clis/weibo/hot.js +1 -1
  793. package/clis/weibo/publish.js +10 -10
  794. package/clis/weibo/search.js +2 -2
  795. package/clis/weibo/user-posts.js +3 -3
  796. package/clis/weibo/user.js +2 -2
  797. package/clis/weibo/utils.js +3 -3
  798. package/clis/weixin/article.js +1 -1
  799. package/clis/weixin/create-draft.js +26 -26
  800. package/clis/weixin/download.js +2 -2
  801. package/clis/weixin/login.js +1 -1
  802. package/clis/weixin/search.js +2 -2
  803. package/clis/weixin/stats.js +3 -3
  804. package/clis/weixin/whoami.js +1 -1
  805. package/clis/weread/ai-outline.js +2 -2
  806. package/clis/weread/book-search.js +2 -2
  807. package/clis/weread/book.js +2 -2
  808. package/clis/weread/notebooks.js +1 -1
  809. package/clis/weread/search.js +1 -1
  810. package/clis/weread/shelf.js +1 -1
  811. package/clis/weread/utils.js +6 -6
  812. package/clis/weread-official/book.js +1 -1
  813. package/clis/weread-official/discover.js +1 -1
  814. package/clis/weread-official/list-apis.js +1 -1
  815. package/clis/weread-official/notes.js +1 -1
  816. package/clis/weread-official/readdata.js +1 -1
  817. package/clis/weread-official/review.js +1 -1
  818. package/clis/weread-official/search.js +1 -1
  819. package/clis/weread-official/shelf.js +1 -1
  820. package/clis/weread-official/utils.js +1 -1
  821. package/clis/wikidata/entity.js +1 -1
  822. package/clis/wikidata/search.js +1 -1
  823. package/clis/wikidata/utils.js +1 -1
  824. package/clis/wikipedia/page.js +1 -1
  825. package/clis/wikipedia/trending.js +1 -1
  826. package/clis/wikipedia/utils.js +1 -1
  827. package/clis/wordpress/login.js +1 -1
  828. package/clis/wordpress/publish.js +1 -1
  829. package/clis/wordpress/shared.js +3 -3
  830. package/clis/wordpress/whoami.js +1 -1
  831. package/clis/woshipm/article.js +1 -1
  832. package/clis/woshipm/login.js +1 -1
  833. package/clis/woshipm/stats.js +4 -4
  834. package/clis/woshipm/whoami.js +1 -1
  835. package/clis/wttr/current.js +1 -1
  836. package/clis/wttr/forecast.js +1 -1
  837. package/clis/wttr/utils.js +1 -1
  838. package/clis/xianyu/collect.js +1 -1
  839. package/clis/xianyu/inbox.js +1 -1
  840. package/clis/xianyu/item-stats.js +2 -2
  841. package/clis/xianyu/location.js +1 -1
  842. package/clis/xianyu/manage.js +1 -1
  843. package/clis/xianyu/messages.js +1 -1
  844. package/clis/xianyu/mtop.js +5 -5
  845. package/clis/xianyu/order.js +1 -1
  846. package/clis/xianyu/promote-stats.js +2 -2
  847. package/clis/xianyu/publish.js +17 -17
  848. package/clis/xianyu/rate.js +1 -1
  849. package/clis/xianyu/reply.js +1 -1
  850. package/clis/xianyu/search.js +8 -8
  851. package/clis/xianyu/seller-data.js +1 -1
  852. package/clis/xianyu/seller-list.js +1 -1
  853. package/clis/xianyu/user.js +1 -1
  854. package/clis/xiaoe/catalog.js +2 -2
  855. package/clis/xiaoe/content.js +2 -2
  856. package/clis/xiaoe/courses.js +2 -2
  857. package/clis/xiaohongshu/ask.js +6 -6
  858. package/clis/xiaohongshu/comments.js +6 -6
  859. package/clis/xiaohongshu/creator-note-detail.js +7 -7
  860. package/clis/xiaohongshu/creator-notes-summary.js +1 -1
  861. package/clis/xiaohongshu/creator-notes.js +13 -13
  862. package/clis/xiaohongshu/creator-profile.js +2 -2
  863. package/clis/xiaohongshu/creator-stats.js +2 -2
  864. package/clis/xiaohongshu/delete-note.js +7 -7
  865. package/clis/xiaohongshu/download.js +4 -4
  866. package/clis/xiaohongshu/feed.js +2 -2
  867. package/clis/xiaohongshu/follow.js +3 -3
  868. package/clis/xiaohongshu/human.js +3 -3
  869. package/clis/xiaohongshu/note-helpers.js +1 -1
  870. package/clis/xiaohongshu/note.js +2 -2
  871. package/clis/xiaohongshu/notifications.js +1 -1
  872. package/clis/xiaohongshu/publish-video.js +8 -8
  873. package/clis/xiaohongshu/publish.js +59 -59
  874. package/clis/xiaohongshu/search.js +8 -8
  875. package/clis/xiaohongshu/unfollow.js +4 -4
  876. package/clis/xiaohongshu/user-helpers.js +1 -1
  877. package/clis/xiaohongshu/user.js +2 -2
  878. package/clis/xiaoyuzhou/auth.js +3 -3
  879. package/clis/xiaoyuzhou/download.js +1 -1
  880. package/clis/xiaoyuzhou/utils.js +1 -1
  881. package/clis/xueqiu/article.js +4 -4
  882. package/clis/xueqiu/comments.js +4 -4
  883. package/clis/xueqiu/danjuan-utils.js +4 -4
  884. package/clis/xueqiu/earnings-date.js +1 -1
  885. package/clis/xueqiu/events.js +3 -3
  886. package/clis/xueqiu/hashtags.js +3 -3
  887. package/clis/xueqiu/stocks.js +3 -3
  888. package/clis/xueqiu/users.js +3 -3
  889. package/clis/xueqiu/utils.js +3 -3
  890. package/clis/yahoo-finance/quote.js +1 -1
  891. package/clis/yollomi/background.js +1 -1
  892. package/clis/yollomi/edit.js +1 -1
  893. package/clis/yollomi/face-swap.js +1 -1
  894. package/clis/yollomi/generate.js +1 -1
  895. package/clis/yollomi/object-remover.js +1 -1
  896. package/clis/yollomi/remove-bg.js +1 -1
  897. package/clis/yollomi/restore.js +1 -1
  898. package/clis/yollomi/try-on.js +1 -1
  899. package/clis/yollomi/upload.js +4 -4
  900. package/clis/yollomi/upscale.js +1 -1
  901. package/clis/yollomi/utils.js +4 -4
  902. package/clis/yollomi/video.js +1 -1
  903. package/clis/youtube/channel.js +4 -4
  904. package/clis/youtube/feed.js +2 -2
  905. package/clis/youtube/like.js +5 -5
  906. package/clis/youtube/playlist.js +7 -7
  907. package/clis/youtube/publish.js +11 -11
  908. package/clis/youtube/search.js +3 -3
  909. package/clis/youtube/subscribe.js +7 -7
  910. package/clis/youtube/subscriptions.js +1 -1
  911. package/clis/youtube/transcript-group.js +2 -2
  912. package/clis/youtube/transcript.js +13 -13
  913. package/clis/youtube/unlike.js +5 -5
  914. package/clis/youtube/unsubscribe.js +7 -7
  915. package/clis/youtube/utils.js +3 -3
  916. package/clis/youtube/video.js +4 -4
  917. package/clis/youtube/watch-later.js +2 -2
  918. package/clis/yuanbao/ask.js +18 -18
  919. package/clis/yuanbao/detail.js +1 -1
  920. package/clis/yuanbao/read.js +1 -1
  921. package/clis/yuanbao/shared.js +9 -9
  922. package/clis/yuanbao/status.js +1 -1
  923. package/clis/yuque/article.js +1 -1
  924. package/clis/yuque/whoami.js +1 -1
  925. package/clis/zhihu/answer-comments.js +2 -2
  926. package/clis/zhihu/answer-detail.js +3 -3
  927. package/clis/zhihu/answer.js +1 -1
  928. package/clis/zhihu/article-transform.js +1 -1
  929. package/clis/zhihu/article.js +1 -1
  930. package/clis/zhihu/auth.js +2 -2
  931. package/clis/zhihu/collection.js +3 -3
  932. package/clis/zhihu/collections.js +3 -3
  933. package/clis/zhihu/download.js +2 -2
  934. package/clis/zhihu/image-profile.js +6 -6
  935. package/clis/zhihu/paginate.js +3 -3
  936. package/clis/zhihu/pin.js +4 -4
  937. package/clis/zhihu/question.js +3 -3
  938. package/clis/zhihu/recommend.js +3 -3
  939. package/clis/zhihu/search.js +3 -3
  940. package/clis/zhihu/stats.js +3 -3
  941. package/clis/zhihu/user-arg.js +1 -1
  942. package/clis/zhihu/write-shared.js +7 -7
  943. package/clis/zlibrary/utils.js +6 -6
  944. package/clis/zsxq/auth.js +3 -3
  945. package/clis/zsxq/search.js +1 -1
  946. package/clis/zsxq/topic.js +1 -1
  947. package/clis/zsxq/utils.js +5 -5
  948. package/dist/src/adapter-source.js +1 -1
  949. package/dist/src/browser/analyze.js +1 -1
  950. package/dist/src/browser/article-extract.js +2 -2
  951. package/dist/src/browser/ax-snapshot.js +2 -2
  952. package/dist/src/browser/base-page.js +25 -25
  953. package/dist/src/browser/bridge.js +1 -1
  954. package/dist/src/browser/cdp.js +1 -1
  955. package/dist/src/browser/daemon-client.js +1 -1
  956. package/dist/src/browser/daemon-lifecycle.js +7 -7
  957. package/dist/src/browser/dom-helpers.js +35 -35
  958. package/dist/src/browser/dom-snapshot.js +19 -19
  959. package/dist/src/browser/errors.js +3 -3
  960. package/dist/src/browser/extract.js +5 -5
  961. package/dist/src/browser/find.js +13 -13
  962. package/dist/src/browser/fingerprint.js +2 -2
  963. package/dist/src/browser/html-tree.js +6 -6
  964. package/dist/src/browser/managed-chrome.js +1 -1
  965. package/dist/src/browser/network-cache.js +1 -1
  966. package/dist/src/browser/network-key.js +1 -1
  967. package/dist/src/browser/page.js +7 -7
  968. package/dist/src/browser/profile.js +2 -2
  969. package/dist/src/browser/proxy-test.js +8 -8
  970. package/dist/src/browser/shape-filter.js +1 -1
  971. package/dist/src/browser/shape.js +1 -1
  972. package/dist/src/browser/stealth.js +3 -3
  973. package/dist/src/browser/tabs.js +2 -2
  974. package/dist/src/browser/target-errors.js +1 -1
  975. package/dist/src/browser/target-resolver.js +23 -23
  976. package/dist/src/browser/trace.js +2 -2
  977. package/dist/src/browser/utils.js +1 -1
  978. package/dist/src/browser/verify-fixture.js +2 -2
  979. package/dist/src/browser-session-lock.js +1 -1
  980. package/dist/src/build-manifest.js +11 -11
  981. package/dist/src/capabilityRouting.js +1 -1
  982. package/dist/src/cli-argv-preprocess.js +1 -1
  983. package/dist/src/cli.js +32 -32
  984. package/dist/src/commanderAdapter.js +3 -3
  985. package/dist/src/commands/auth.js +3 -3
  986. package/dist/src/commands/daemon.js +1 -1
  987. package/dist/src/completion-fast.js +1 -1
  988. package/dist/src/completion.js +1 -1
  989. package/dist/src/constants.js +1 -1
  990. package/dist/src/convention-audit.js +2 -2
  991. package/dist/src/daemon-utils.js +1 -1
  992. package/dist/src/daemon.js +1 -1
  993. package/dist/src/discovery.js +2 -2
  994. package/dist/src/doctor.js +9 -9
  995. package/dist/src/download/article-download.js +15 -15
  996. package/dist/src/download/index.js +4 -4
  997. package/dist/src/download/media-download.js +1 -1
  998. package/dist/src/download/progress.js +3 -3
  999. package/dist/src/electron-apps.js +1 -1
  1000. package/dist/src/errors.js +2 -2
  1001. package/dist/src/execution.js +2 -2
  1002. package/dist/src/external.js +1 -1
  1003. package/dist/src/help.js +8 -8
  1004. package/dist/src/hooks.js +1 -1
  1005. package/dist/src/interceptor.js +21 -21
  1006. package/dist/src/launcher.js +7 -7
  1007. package/dist/src/logger.js +8 -8
  1008. package/dist/src/main.js +7 -7
  1009. package/dist/src/observation/artifact.js +5 -5
  1010. package/dist/src/observation/retention.js +1 -1
  1011. package/dist/src/output.js +2 -2
  1012. package/dist/src/package-paths.js +1 -1
  1013. package/dist/src/pipeline/executor.js +1 -1
  1014. package/dist/src/pipeline/registry.js +1 -1
  1015. package/dist/src/pipeline/steps/browser.js +1 -1
  1016. package/dist/src/pipeline/steps/download.js +1 -1
  1017. package/dist/src/pipeline/steps/fetch.js +6 -6
  1018. package/dist/src/pipeline/steps/intercept.js +1 -1
  1019. package/dist/src/pipeline/steps/tap.js +17 -17
  1020. package/dist/src/pipeline/steps/transform.js +1 -1
  1021. package/dist/src/pipeline/template.js +1 -1
  1022. package/dist/src/plugin-manifest.js +1 -1
  1023. package/dist/src/plugin-scaffold.js +19 -19
  1024. package/dist/src/plugin.js +12 -12
  1025. package/dist/src/rate-limit.js +1 -1
  1026. package/dist/src/registry.js +1 -1
  1027. package/dist/src/runtime-detect.js +1 -1
  1028. package/dist/src/runtime.js +1 -1
  1029. package/dist/src/scripts/framework.js +1 -1
  1030. package/dist/src/scripts/interact.js +1 -1
  1031. package/dist/src/scripts/store.js +1 -1
  1032. package/dist/src/serialization.js +2 -2
  1033. package/dist/src/session-lease.js +1 -1
  1034. package/dist/src/snapshotFormatter.js +2 -2
  1035. package/dist/src/tui.js +12 -12
  1036. package/dist/src/user-runtime.js +1 -1
  1037. package/dist/src/utils.d.ts +12 -0
  1038. package/dist/src/utils.js +4 -4
  1039. package/dist/src/validate.js +2 -2
  1040. package/dist/src/verify.js +2 -2
  1041. package/dist/src/version.js +1 -1
  1042. package/package.json +1 -1
@@ -1 +1 @@
1
- import{cli as G,Strategy as K}from"@jackwener/opencli/registry";import{CliError as L,CommandExecutionError as Q}from"@jackwener/opencli/errors";import{gotoWritePage as T}from"../_shared/article/publish.js";import{sohuProfile as V}from"./article.js";import{SOHU_MPBP_HEADERS_JS as X}from"./mpbp.js";G({site:"sohu",name:"categories",access:"read",description:"列出某个搜狐号频道下的分类(id + 名称),供 `sohu article --category` 取合法分类名。须用 --channel 指定频道名。",domain:"mp.sohu.com",strategy:K.COOKIE,browser:!0,args:[{name:"channel",required:!0,help:"频道名(精确匹配),合法值用 `sohu channels` 列举"}],columns:["category_id","category_name"],func:async(q,F)=>{if(!q)throw new Q("搜狐号分类列举需要浏览器会话");const v=String(F.channel??"").trim();if(!v)throw new L("INVALID_INPUT","请用 --channel 指定频道名");await T(q,V.home);const z=await q.evaluate("(async () => {"+X+"const ar = await fetch('https://mp.sohu.com/mpbp/bp/account/list?_=' + Date.now(), { method: 'GET', credentials: 'include', headers: HDRS });const aj = await ar.json();"+"if (!aj || aj.code !== 2000000 || !(aj.data && aj.data.data && aj.data.data.length)) throw new Error('获取搜狐账号失败,请确认已登录搜狐号');"+"let accountId = '';for (const g of aj.data.data) { if (g.accounts && g.accounts.length) { accountId = String(g.accounts[0].id); break; } }"+"if (!accountId) throw new Error('搜狐号子账号列表为空');"+"const cr = await fetch('https://mp.sohu.com/mpbp/bp/account/common/channels-data-api?status=1&accountId=' + accountId, { method: 'GET', credentials: 'include', headers: HDRS });const cj = await cr.json();"+"if (!cj || cj.success === false) throw new Error('频道列举失败:' + ((cj && cj.msg) || '未登录或无权限'));"+"const channels = Array.isArray(cj) ? cj : ((cj && cj.data) || []);const ch = channels.find(c => String(c.name) === "+JSON.stringify(v)+");"+"if (!ch) throw new Error('未找到频道「"+v.replace(/"/g,"\\\"")+"」,可选:' + (channels.map(c => c.name).join(' / ') || '(空)'));"+"const channelId = ch.id != null ? ch.id : ch.channelId;const r = await fetch('https://mp.sohu.com/mpbp/bp/account/common/channels/' + channelId + '/categories?accountId=' + accountId, { method: 'GET', credentials: 'include', headers: HDRS });const j = await r.json();"+"if (!j || j.success === false) throw new Error('分类列举失败:' + ((j && j.msg) || '未登录或无权限'));"+"const list = Array.isArray(j) ? j : ((j && j.data) || []);return list.map(c => ({ category_id: String(c.id), category_name: c.name || '' }));})()");return Array.isArray(z)?z:[]}});
1
+ import{cli as r,Strategy as o}from"@jackwener/opencli/registry";import{CliError as e,CommandExecutionError as s}from"@jackwener/opencli/errors";import{gotoWritePage as i}from"../_shared/article/publish.js";import{sohuProfile as h}from"./article.js";import{SOHU_MPBP_HEADERS_JS as d}from"./mpbp.js";r({site:"sohu",name:"categories",access:"read",description:"列出某个搜狐号频道下的分类(id + 名称),供 `sohu article --category` 取合法分类名。须用 --channel 指定频道名。",domain:"mp.sohu.com",strategy:o.COOKIE,browser:!0,args:[{name:"channel",required:!0,help:"频道名(精确匹配),合法值用 `sohu channels` 列举"}],columns:["category_id","category_name"],func:async(a,n)=>{if(!a)throw new s("搜狐号分类列举需要浏览器会话");const c=String(n.channel??"").trim();if(!c)throw new e("INVALID_INPUT","请用 --channel 指定频道名");await i(a,h.home);const t=await a.evaluate("(async () => {"+d+"const ar = await fetch('https://mp.sohu.com/mpbp/bp/account/list?_=' + Date.now(), { method: 'GET', credentials: 'include', headers: HDRS });const aj = await ar.json();"+"if (!aj || aj.code !== 2000000 || !(aj.data && aj.data.data && aj.data.data.length)) throw new Error('获取搜狐账号失败,请确认已登录搜狐号');"+"let accountId = '';for (const g of aj.data.data) { if (g.accounts && g.accounts.length) { accountId = String(g.accounts[0].id); break; } }"+"if (!accountId) throw new Error('搜狐号子账号列表为空');"+"const cr = await fetch('https://mp.sohu.com/mpbp/bp/account/common/channels-data-api?status=1&accountId=' + accountId, { method: 'GET', credentials: 'include', headers: HDRS });const cj = await cr.json();"+"if (!cj || cj.success === false) throw new Error('频道列举失败:' + ((cj && cj.msg) || '未登录或无权限'));"+"const channels = Array.isArray(cj) ? cj : ((cj && cj.data) || []);const ch = channels.find(c => String(c.name) === "+JSON.stringify(c)+");"+"if (!ch) throw new Error('未找到频道「"+c.replace(/"/g,"\\\"")+"」,可选:' + (channels.map(c => c.name).join(' / ') || '(空)'));"+"const channelId = ch.id != null ? ch.id : ch.channelId;const r = await fetch('https://mp.sohu.com/mpbp/bp/account/common/channels/' + channelId + '/categories?accountId=' + accountId, { method: 'GET', credentials: 'include', headers: HDRS });const j = await r.json();"+"if (!j || j.success === false) throw new Error('分类列举失败:' + ((j && j.msg) || '未登录或无权限'));"+"const list = Array.isArray(j) ? j : ((j && j.data) || []);return list.map(c => ({ category_id: String(c.id), category_name: c.name || '' }));})()");return Array.isArray(t)?t:[]}});
@@ -1 +1 @@
1
- import{cli as z,Strategy as F}from"@jackwener/opencli/registry";import{CommandExecutionError as G}from"@jackwener/opencli/errors";import{gotoWritePage as I}from"../_shared/article/publish.js";import{sohuProfile as K}from"./article.js";import{SOHU_MPBP_HEADERS_JS as L}from"./mpbp.js";z({site:"sohu",name:"channels",access:"read",description:"列出当前搜狐号可发布的频道(id + 名称),供 `sohu article --channel` 取合法频道名。",domain:"mp.sohu.com",strategy:F.COOKIE,browser:!0,columns:["channel_id","channel_name"],func:async(q)=>{if(!q)throw new G("搜狐号频道列举需要浏览器会话");await I(q,K.home);const v=await q.evaluate("(async () => {"+L+"const ar = await fetch('https://mp.sohu.com/mpbp/bp/account/list?_=' + Date.now(), { method: 'GET', credentials: 'include', headers: HDRS });const aj = await ar.json();"+"if (!aj || aj.code !== 2000000 || !(aj.data && aj.data.data && aj.data.data.length)) throw new Error('获取搜狐账号失败,请确认已登录搜狐号');"+"let accountId = '';for (const g of aj.data.data) { if (g.accounts && g.accounts.length) { accountId = String(g.accounts[0].id); break; } }"+"if (!accountId) throw new Error('搜狐号子账号列表为空');"+"const r = await fetch('https://mp.sohu.com/mpbp/bp/account/common/channels-data-api?status=1&accountId=' + accountId, { method: 'GET', credentials: 'include', headers: HDRS });const j = await r.json();"+"if (!j || j.success === false) throw new Error('频道列举失败:' + ((j && j.msg) || '未登录或无权限'));"+"const list = Array.isArray(j) ? j : ((j && j.data) || []);return list.map(c => ({ channel_id: String(c.id != null ? c.id : c.channelId), channel_name: c.name || '' }));})()");return Array.isArray(v)?v:[]}});
1
+ import{cli as n,Strategy as o}from"@jackwener/opencli/registry";import{CommandExecutionError as c}from"@jackwener/opencli/errors";import{gotoWritePage as r}from"../_shared/article/publish.js";import{sohuProfile as e}from"./article.js";import{SOHU_MPBP_HEADERS_JS as s}from"./mpbp.js";n({site:"sohu",name:"channels",access:"read",description:"列出当前搜狐号可发布的频道(id + 名称),供 `sohu article --channel` 取合法频道名。",domain:"mp.sohu.com",strategy:o.COOKIE,browser:!0,columns:["channel_id","channel_name"],func:async(a)=>{if(!a)throw new c("搜狐号频道列举需要浏览器会话");await r(a,e.home);const t=await a.evaluate("(async () => {"+s+"const ar = await fetch('https://mp.sohu.com/mpbp/bp/account/list?_=' + Date.now(), { method: 'GET', credentials: 'include', headers: HDRS });const aj = await ar.json();"+"if (!aj || aj.code !== 2000000 || !(aj.data && aj.data.data && aj.data.data.length)) throw new Error('获取搜狐账号失败,请确认已登录搜狐号');"+"let accountId = '';for (const g of aj.data.data) { if (g.accounts && g.accounts.length) { accountId = String(g.accounts[0].id); break; } }"+"if (!accountId) throw new Error('搜狐号子账号列表为空');"+"const r = await fetch('https://mp.sohu.com/mpbp/bp/account/common/channels-data-api?status=1&accountId=' + accountId, { method: 'GET', credentials: 'include', headers: HDRS });const j = await r.json();"+"if (!j || j.success === false) throw new Error('频道列举失败:' + ((j && j.msg) || '未登录或无权限'));"+"const list = Array.isArray(j) ? j : ((j && j.data) || []);return list.map(c => ({ channel_id: String(c.id != null ? c.id : c.channelId), channel_name: c.name || '' }));})()");return Array.isArray(t)?t:[]}});
@@ -1 +1 @@
1
- import{cli as z,Strategy as F}from"@jackwener/opencli/registry";import{CommandExecutionError as G}from"@jackwener/opencli/errors";import{gotoWritePage as I}from"../_shared/article/publish.js";import{sohuProfile as K}from"./article.js";import{SOHU_MPBP_HEADERS_JS as L}from"./mpbp.js";z({site:"sohu",name:"columns",access:"read",description:"列出当前搜狐号的专栏(id + 名称),供 `sohu article --column` 取合法专栏名。",domain:"mp.sohu.com",strategy:F.COOKIE,browser:!0,columns:["column_id","column_name"],func:async(q)=>{if(!q)throw new G("搜狐号专栏列举需要浏览器会话");await I(q,K.home);const v=await q.evaluate("(async () => {"+L+"const ar = await fetch('https://mp.sohu.com/mpbp/bp/account/list?_=' + Date.now(), { method: 'GET', credentials: 'include', headers: HDRS });const aj = await ar.json();"+"if (!aj || aj.code !== 2000000 || !(aj.data && aj.data.data && aj.data.data.length)) throw new Error('获取搜狐账号失败,请确认已登录搜狐号');"+"let accountId = '';for (const g of aj.data.data) { if (g.accounts && g.accounts.length) { accountId = String(g.accounts[0].id); break; } }"+"if (!accountId) throw new Error('搜狐号子账号列表为空');"+"const r = await fetch('https://mp.sohu.com/mpbp/bp/account/column/v2/list?accountId=' + accountId + '&page=1&size=100', { method: 'GET', credentials: 'include', headers: HDRS });const j = await r.json();"+"if (!j || j.success === false) throw new Error('专栏列举失败:' + ((j && j.msg) || '未登录或无权限'));"+"const raw = Array.isArray(j) ? j : ((j && j.data) || []);const list = Array.isArray(raw) ? raw : (Array.isArray(raw.data) ? raw.data : []);return list.map(c => ({ column_id: String(c.id), column_name: c.name || '' }));})()");return Array.isArray(v)?v:[]}});
1
+ import{cli as r,Strategy as o}from"@jackwener/opencli/registry";import{CommandExecutionError as c}from"@jackwener/opencli/errors";import{gotoWritePage as n}from"../_shared/article/publish.js";import{sohuProfile as s}from"./article.js";import{SOHU_MPBP_HEADERS_JS as e}from"./mpbp.js";r({site:"sohu",name:"columns",access:"read",description:"列出当前搜狐号的专栏(id + 名称),供 `sohu article --column` 取合法专栏名。",domain:"mp.sohu.com",strategy:o.COOKIE,browser:!0,columns:["column_id","column_name"],func:async(a)=>{if(!a)throw new c("搜狐号专栏列举需要浏览器会话");await n(a,s.home);const t=await a.evaluate("(async () => {"+e+"const ar = await fetch('https://mp.sohu.com/mpbp/bp/account/list?_=' + Date.now(), { method: 'GET', credentials: 'include', headers: HDRS });const aj = await ar.json();"+"if (!aj || aj.code !== 2000000 || !(aj.data && aj.data.data && aj.data.data.length)) throw new Error('获取搜狐账号失败,请确认已登录搜狐号');"+"let accountId = '';for (const g of aj.data.data) { if (g.accounts && g.accounts.length) { accountId = String(g.accounts[0].id); break; } }"+"if (!accountId) throw new Error('搜狐号子账号列表为空');"+"const r = await fetch('https://mp.sohu.com/mpbp/bp/account/column/v2/list?accountId=' + accountId + '&page=1&size=100', { method: 'GET', credentials: 'include', headers: HDRS });const j = await r.json();"+"if (!j || j.success === false) throw new Error('专栏列举失败:' + ((j && j.msg) || '未登录或无权限'));"+"const raw = Array.isArray(j) ? j : ((j && j.data) || []);const list = Array.isArray(raw) ? raw : (Array.isArray(raw.data) ? raw.data : []);return list.map(c => ({ column_id: String(c.id), column_name: c.name || '' }));})()");return Array.isArray(t)?t:[]}});
@@ -1 +1 @@
1
- import{registerArticleLogin as k}from"../_shared/article/login.js";import{sohuAuthProfile as b}from"./article.js";k({site:"sohu",domain:"mp.sohu.com",profile:{home:b.home,checkAuth:b.checkAuth},loginDescription:"打开搜狐号登录页并等待浏览器完成登录(供桌面客户端引导登录)。"});
1
+ import{registerArticleLogin as i}from"../_shared/article/login.js";import{sohuAuthProfile as o}from"./article.js";i({site:"sohu",domain:"mp.sohu.com",profile:{home:o.home,checkAuth:o.checkAuth},loginDescription:"打开搜狐号登录页并等待浏览器完成登录(供桌面客户端引导登录)。"});
@@ -1,5 +1,5 @@
1
- import{cli as C,Strategy as G}from"@jackwener/opencli/registry";import{CommandExecutionError as R,EmptyResultError as P}from"@jackwener/opencli/errors";import{gotoWritePage as T}from"../_shared/article/publish.js";const _="https://mp.sohu.com/mpfe/v4/contentManagement/news/articlelist",v=10;function W(O){if(!O)return"";const H=new Date(O+28800000),J=(Q)=>String(Q).padStart(2,"0");return`${H.getUTCFullYear()}-${J(H.getUTCMonth()+1)}-${J(H.getUTCDate())} ${J(H.getUTCHours())}:${J(H.getUTCMinutes())}`}function Z(O){const H=new Date(Date.now()+28800000-O*86400000),J=(Q)=>String(Q).padStart(2,"0");return`${H.getUTCFullYear()}-${J(H.getUTCMonth()+1)}-${J(H.getUTCDate())}`}C({site:"sohu",name:"stats",access:"read",description:"搜狐号文章数据(每篇阅读/评论/播放等全量运营指标;点赞/分享/投票仅覆盖最近30天已发布内容)",domain:"mp.sohu.com",strategy:G.COOKIE,browser:!0,args:[{name:"limit",type:"number",default:20,help:"返回条数"}],columns:["id","type","title","url","public_url","published_at","status","views","likes","comments","collects","shares","video_plays","votes","extra"],func:async(O,H)=>{if(!O)throw new R("搜狐号文章数据需要浏览器会话");const J=Number(H.limit)>0?Math.floor(Number(H.limit)):20;await T(O,_);const Q={limit:J,pageSize:v,startDate:Z(30),endDate:Z(0)},z=await O.evaluate(`(async () => {
2
- const P = ${JSON.stringify(Q)};
1
+ import{cli as I,Strategy as b}from"@jackwener/opencli/registry";import{CommandExecutionError as g,EmptyResultError as S}from"@jackwener/opencli/errors";import{gotoWritePage as j}from"../_shared/article/publish.js";const v="https://mp.sohu.com/mpfe/v4/contentManagement/news/articlelist",C=10;function f(c){if(!c)return"";const a=new Date(c+28800000),n=(i)=>String(i).padStart(2,"0");return`${a.getUTCFullYear()}-${n(a.getUTCMonth()+1)}-${n(a.getUTCDate())} ${n(a.getUTCHours())}:${n(a.getUTCMinutes())}`}function w(c){const a=new Date(Date.now()+28800000-c*86400000),n=(i)=>String(i).padStart(2,"0");return`${a.getUTCFullYear()}-${n(a.getUTCMonth()+1)}-${n(a.getUTCDate())}`}I({site:"sohu",name:"stats",access:"read",description:"搜狐号文章数据(每篇阅读/评论/播放等全量运营指标;点赞/分享/投票仅覆盖最近30天已发布内容)",domain:"mp.sohu.com",strategy:b.COOKIE,browser:!0,args:[{name:"limit",type:"number",default:20,help:"返回条数"}],columns:["id","type","title","url","public_url","published_at","status","views","likes","comments","collects","shares","video_plays","votes","extra"],func:async(c,a)=>{if(!c)throw new g("搜狐号文章数据需要浏览器会话");const n=Number(a.limit)>0?Math.floor(Number(a.limit)):20;await j(c,v);const i={limit:n,pageSize:C,startDate:w(30),endDate:w(0)},e=await c.evaluate(`(async () => {
2
+ const P = ${JSON.stringify(i)};
3
3
 
4
4
  // ── 请求头:dv-id / sp-cm 优先取页面自己存的值,逐字复刻 ──
5
5
  let dvId = '', spCm = '';
@@ -105,4 +105,4 @@ import{cli as C,Strategy as G}from"@jackwener/opencli/registry";import{CommandEx
105
105
  }
106
106
 
107
107
  return { accountId, news: news.slice(0, P.limit), totalCount: total, publishedIds, statRows, statError, channelMap };
108
- })()`);if(!z||z.err)throw new R(z&&z.err||"搜狐号文章数据获取失败:页面未返回任何数据(请确认已登录 mp.sohu.com)");const X=Array.isArray(z.news)?z.news:[];if(!X.length)throw new P("sohu stats","该搜狐号名下没有任何内容(或列表接口返回为空)。请先在 mp.sohu.com 发布内容后再查数据。");const Y=new Map,$=new Map;for(const q of Array.isArray(z.statRows)?z.statRows:[]){if(q&&q.id!=null)Y.set(String(q.id),q);if(q&&typeof q.title==="string"&&q.title)$.set(q.title,q)}const U=(q,K)=>{if(!q)return"";for(const M of K)if(typeof q[M]==="number")return q[M];return""},A=new Set(Array.isArray(z.publishedIds)?z.publishedIds:[]);return X.map((q)=>{const K=q.id!=null?String(q.id):"",M=Y.get(K)||$.get(q.title||"")||null,V=A.has(K),F={};if(q.auditStatus!=null)F.audit_status=q.auditStatus;if(q.status!=null)F.status_raw=q.status;if(q.rejectReason)F.reject_reason=q.rejectReason;if(q.original!=null)F.original=q.original;if(q.star!=null)F.star=q.star;if(q.secureScore!=null)F.secure_score=q.secureScore;if(q.channelId!=null){const L=z.channelMap&&z.channelMap[String(q.channelId)]||"";F.display_channel=L?L+"("+q.channelId+")":String(q.channelId);F.display_channel_note="搜狐后台按内容自动归类的展示频道,可能与发布时 --channel 所选不同"}if(q.modifiedTime)F.modified_at=W(q.modifiedTime);if(q.brief)F.brief=String(q.brief).slice(0,80);if(M)F.stat=M;if(z.statError)F.stat_error="单篇统计接口失败:"+z.statError;return{id:K,type:q.type!=null?String(q.type):"",title:q.title||q.mobileTitle||"",url:K?"https://mp.sohu.com/mpfe/v4/contentManagement/news/addarticle?spm=smmp.articlelist.0.0&contentStatus="+(V?1:2)+"&id="+K:"",public_url:K&&V&&z.accountId?"https://www.sohu.com/a/"+K+"_"+z.accountId:"",published_at:W(q.postTime||q.createdTime),status:V?"已发布":"未发布",views:typeof q.pv==="number"?q.pv:"",likes:U(M,["digg","diggCount","like","likeCount","praise","zan"]),comments:typeof q.cmt==="number"?q.cmt:U(M,["cmt","comment","commentCount"]),collects:"",shares:U(M,["share","shareCount","forward","forwardCount"]),video_plays:typeof q.videoPlayPv==="number"?q.videoPlayPv:"",votes:U(M,["vote","voteCount"]),extra:Object.keys(F).length?JSON.stringify(F):""}})}});
108
+ })()`);if(!e||e.err)throw new g(e&&e.err||"搜狐号文章数据获取失败:页面未返回任何数据(请确认已登录 mp.sohu.com)");const d=Array.isArray(e.news)?e.news:[];if(!d.length)throw new S("sohu stats","该搜狐号名下没有任何内容(或列表接口返回为空)。请先在 mp.sohu.com 发布内容后再查数据。");const p=new Map,m=new Map;for(const t of Array.isArray(e.statRows)?e.statRows:[]){if(t&&t.id!=null)p.set(String(t.id),t);if(t&&typeof t.title==="string"&&t.title)m.set(t.title,t)}const l=(t,o)=>{if(!t)return"";for(const r of o)if(typeof t[r]==="number")return t[r];return""},y=new Set(Array.isArray(e.publishedIds)?e.publishedIds:[]);return d.map((t)=>{const o=t.id!=null?String(t.id):"",r=p.get(o)||m.get(t.title||"")||null,u=y.has(o),s={};if(t.auditStatus!=null)s.audit_status=t.auditStatus;if(t.status!=null)s.status_raw=t.status;if(t.rejectReason)s.reject_reason=t.rejectReason;if(t.original!=null)s.original=t.original;if(t.star!=null)s.star=t.star;if(t.secureScore!=null)s.secure_score=t.secureScore;if(t.channelId!=null){const h=e.channelMap&&e.channelMap[String(t.channelId)]||"";s.display_channel=h?h+"("+t.channelId+")":String(t.channelId);s.display_channel_note="搜狐后台按内容自动归类的展示频道,可能与发布时 --channel 所选不同"}if(t.modifiedTime)s.modified_at=f(t.modifiedTime);if(t.brief)s.brief=String(t.brief).slice(0,80);if(r)s.stat=r;if(e.statError)s.stat_error="单篇统计接口失败:"+e.statError;return{id:o,type:t.type!=null?String(t.type):"",title:t.title||t.mobileTitle||"",url:o?"https://mp.sohu.com/mpfe/v4/contentManagement/news/addarticle?spm=smmp.articlelist.0.0&contentStatus="+(u?1:2)+"&id="+o:"",public_url:o&&u&&e.accountId?"https://www.sohu.com/a/"+o+"_"+e.accountId:"",published_at:f(t.postTime||t.createdTime),status:u?"已发布":"未发布",views:typeof t.pv==="number"?t.pv:"",likes:l(r,["digg","diggCount","like","likeCount","praise","zan"]),comments:typeof t.cmt==="number"?t.cmt:l(r,["cmt","comment","commentCount"]),collects:"",shares:l(r,["share","shareCount","forward","forwardCount"]),video_plays:typeof t.videoPlayPv==="number"?t.videoPlayPv:"",votes:l(r,["vote","voteCount"]),extra:Object.keys(s).length?JSON.stringify(s):""}})}});
@@ -1 +1 @@
1
- import{cli as G,Strategy as I}from"@jackwener/opencli/registry";import{CommandExecutionError as K}from"@jackwener/opencli/errors";import{gotoWritePage as L}from"../_shared/article/publish.js";import{sohuProfile as N}from"./article.js";import{SOHU_MPBP_HEADERS_JS as Q}from"./mpbp.js";G({site:"sohu",name:"topics",access:"read",description:"列举/搜索搜狐号话题(id + 名称 + 类型),供 `sohu article --topics` 取确切话题名。不传 --keyword 为推荐话题,传则按关键词搜索。",domain:"mp.sohu.com",strategy:I.COOKIE,browser:!0,args:[{name:"keyword",help:"搜索关键词;不传则返回推荐话题列表"}],columns:["topic_id","topic_name","topic_type"],func:async(q,v)=>{if(!q)throw new K("搜狐号话题列举需要浏览器会话");const F=typeof v.keyword==="string"?v.keyword.trim():"";await L(q,N.home);const z=await q.evaluate("(async () => {"+Q+"const ar = await fetch('https://mp.sohu.com/mpbp/bp/account/list?_=' + Date.now(), { method: 'GET', credentials: 'include', headers: HDRS });const aj = await ar.json();"+"if (!aj || aj.code !== 2000000 || !(aj.data && aj.data.data && aj.data.data.length)) throw new Error('获取搜狐账号失败,请确认已登录搜狐号');"+"let accountId = '';for (const g of aj.data.data) { if (g.accounts && g.accounts.length) { accountId = String(g.accounts[0].id); break; } }"+"if (!accountId) throw new Error('搜狐号子账号列表为空');"+"const kw = "+JSON.stringify(F)+";const url = kw ? 'https://mp.sohu.com/mpbp/bp/news/v4/label/topic/search?accountId=' + accountId + '&keyword=' + encodeURIComponent(kw) : 'https://mp.sohu.com/mpbp/bp/news/v4/label/topic/list?accountId=' + accountId;const r = await fetch(url, { method: 'GET', credentials: 'include', headers: HDRS });const j = await r.json();"+"if (!j || j.success === false) throw new Error('话题接口失败:' + ((j && j.msg) || '未登录或无权限'));"+"const list = Array.isArray(j) ? j : ((j && j.data) || []);return list.map(t => ({ topic_id: String(t.id), topic_name: t.name || '', topic_type: t.type != null ? String(t.type) : '' }));})()");return Array.isArray(z)?z:[]}});
1
+ import{cli as c,Strategy as e}from"@jackwener/opencli/registry";import{CommandExecutionError as n}from"@jackwener/opencli/errors";import{gotoWritePage as i}from"../_shared/article/publish.js";import{sohuProfile as s}from"./article.js";import{SOHU_MPBP_HEADERS_JS as d}from"./mpbp.js";c({site:"sohu",name:"topics",access:"read",description:"列举/搜索搜狐号话题(id + 名称 + 类型),供 `sohu article --topics` 取确切话题名。不传 --keyword 为推荐话题,传则按关键词搜索。",domain:"mp.sohu.com",strategy:e.COOKIE,browser:!0,args:[{name:"keyword",help:"搜索关键词;不传则返回推荐话题列表"}],columns:["topic_id","topic_name","topic_type"],func:async(t,o)=>{if(!t)throw new n("搜狐号话题列举需要浏览器会话");const r=typeof o.keyword==="string"?o.keyword.trim():"";await i(t,s.home);const a=await t.evaluate("(async () => {"+d+"const ar = await fetch('https://mp.sohu.com/mpbp/bp/account/list?_=' + Date.now(), { method: 'GET', credentials: 'include', headers: HDRS });const aj = await ar.json();"+"if (!aj || aj.code !== 2000000 || !(aj.data && aj.data.data && aj.data.data.length)) throw new Error('获取搜狐账号失败,请确认已登录搜狐号');"+"let accountId = '';for (const g of aj.data.data) { if (g.accounts && g.accounts.length) { accountId = String(g.accounts[0].id); break; } }"+"if (!accountId) throw new Error('搜狐号子账号列表为空');"+"const kw = "+JSON.stringify(r)+";const url = kw ? 'https://mp.sohu.com/mpbp/bp/news/v4/label/topic/search?accountId=' + accountId + '&keyword=' + encodeURIComponent(kw) : 'https://mp.sohu.com/mpbp/bp/news/v4/label/topic/list?accountId=' + accountId;const r = await fetch(url, { method: 'GET', credentials: 'include', headers: HDRS });const j = await r.json();"+"if (!j || j.success === false) throw new Error('话题接口失败:' + ((j && j.msg) || '未登录或无权限'));"+"const list = Array.isArray(j) ? j : ((j && j.data) || []);return list.map(t => ({ topic_id: String(t.id), topic_name: t.name || '', topic_type: t.type != null ? String(t.type) : '' }));})()");return Array.isArray(a)?a:[]}});
@@ -1 +1 @@
1
- import{cli as q,Strategy as v}from"@jackwener/opencli/registry";import{checkLogin as x,cookieQuickCheck as z}from"../_shared/article/auth.js";import{sohuAuthProfile as B}from"./article.js";q({site:"sohu",name:"whoami",access:"read",description:"查看当前搜狐号登录账号信息(用户名、用户 ID)。",domain:"mp.sohu.com",strategy:v.COOKIE,browser:!0,columns:["logged_in","user_id","username"],authStatus:{quickCheck:z("https://mp.sohu.com",["pprdig"])},func:async(m)=>{const b=await x(m,B);return[{logged_in:b.isAuthenticated,user_id:b.userId||"",username:b.username||""}]}});
1
+ import{cli as r,Strategy as i}from"@jackwener/opencli/registry";import{checkLogin as s,cookieQuickCheck as t}from"../_shared/article/auth.js";import{sohuAuthProfile as u}from"./article.js";r({site:"sohu",name:"whoami",access:"read",description:"查看当前搜狐号登录账号信息(用户名、用户 ID)。",domain:"mp.sohu.com",strategy:i.COOKIE,browser:!0,columns:["logged_in","user_id","username"],authStatus:{quickCheck:t("https://mp.sohu.com",["pprdig"])},func:async(o)=>{const e=await s(o,u);return[{logged_in:e.isAuthenticated,user_id:e.userId||"",username:e.username||""}]}});
@@ -1 +1 @@
1
- import{cli as X,Strategy as Y}from"@jackwener/opencli/registry";import{CliError as Z}from"@jackwener/opencli/errors";import{readFileSync as q,writeFileSync as j,mkdirSync as R,existsSync as f}from"fs";import{createServer as h}from"http";import{homedir as P}from"os";import{join as x}from"path";import{exec as m}from"child_process";import{assertSpotifyCredentialsConfigured as I,getFirstSpotifyTrack as v,mapSpotifyTrackResults as p,parseDotEnv as u,resolveSpotifyCredentials as C}from"./utils.js";const K=x(P(),".ppcli","spotify.env");function w(){if(!f(K))return{};return u(q(K,"utf-8"))}const T=w(),O=C(T),V=O.clientId,D=O.clientSecret,U="http://127.0.0.1:8888/callback",S=["user-read-playback-state","user-modify-playback-state","user-read-currently-playing","playlist-read-private"].join(" "),F=x(P(),".ppcli","spotify-tokens.json");function y(){try{return JSON.parse(q(F,"utf-8"))}catch{return null}}function L(z){R(x(P(),".ppcli"),{recursive:!0});j(F,JSON.stringify(z,null,2))}async function E(z){const G=await fetch("https://accounts.spotify.com/api/token",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Authorization:"Basic "+Buffer.from(`${V}:${D}`).toString("base64")},body:new URLSearchParams({grant_type:"refresh_token",refresh_token:z})});if(!G.ok){const J=await G.json().catch(()=>({}));throw new Z("REFRESH_FAILED",J?.error_description||`Token refresh failed (${G.status})`)}const H=await G.json(),M={access_token:H.access_token,refresh_token:H.refresh_token||z,expires_at:Date.now()+H.expires_in*1000};L(M);return M.access_token}async function g(){const z=y();if(!z)throw new Z("AUTH_REQUIRED","Not authenticated. Run: ppcli spotify auth");if(!z.access_token||!z.refresh_token||!(z.expires_at>0))throw new Z("AUTH_CORRUPTED","Token file is corrupted. Run: ppcli spotify auth");if(Date.now()>z.expires_at-60000)return E(z.refresh_token);return z.access_token}async function Q(z,G,H){const M=await g(),J=await fetch(`https://api.spotify.com/v1${G}`,{method:z,headers:{Authorization:`Bearer ${M}`,"Content-Type":"application/json"},body:H?JSON.stringify(H):void 0});if(J.status===204||J.status===202)return null;if(!J.ok){const W=await J.json().catch(()=>({}));throw new Z("API_ERROR",W?.error?.message||`Spotify API error ${J.status}`)}return J.json()}async function N(z){const G=await Q("GET",`/search?q=${encodeURIComponent(z)}&type=track&limit=1`),H=v(G);if(!H)throw new Z("EMPTY_RESULT",`No track found for: ${z}`);return H}function d(z){const G=process.platform==="win32"?`start "" "${z}"`:process.platform==="darwin"?`open "${z}"`:`xdg-open "${z}"`;m(G)}X({site:"spotify",name:"auth",access:"write",description:"Authenticate with Spotify (OAuth — run once)",strategy:Y.PUBLIC,browser:!1,args:[],columns:["status"],func:async()=>{I(O,K);return new Promise((z,G)=>{const H=h(async(J,W)=>{try{const $=new URL(J.url,"http://localhost:8888");if($.pathname!=="/callback"){W.end();return}const b=$.searchParams.get("code");if(!b){W.end("Missing code");return}const A=await fetch("https://accounts.spotify.com/api/token",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Authorization:"Basic "+Buffer.from(`${V}:${D}`).toString("base64")},body:new URLSearchParams({grant_type:"authorization_code",code:b,redirect_uri:U})});if(!A.ok){const _=await A.json().catch(()=>({}));H.close();G(new Z("AUTH_FAILED",_?.error_description||`Token exchange failed (${A.status})`));return}const B=await A.json();L({access_token:B.access_token,refresh_token:B.refresh_token,expires_at:Date.now()+B.expires_in*1000});W.writeHead(200,{"Content-Type":"text/html"});W.end("<h2>Spotify authenticated! You can close this tab.</h2>");H.close();z([{status:"Authenticated successfully"}])}catch($){H.close();G($)}});H.on("error",(J)=>{if(J.code==="EADDRINUSE")G(new Z("PORT_IN_USE","Port 8888 is already in use. Stop the other process and retry."));else G(J)});const M=setTimeout(()=>{H.close();G(new Z("AUTH_TIMEOUT","Authentication timed out after 5 minutes"))},300000);H.listen(8888,()=>{const J=`https://accounts.spotify.com/authorize?${new URLSearchParams({client_id:V,response_type:"code",redirect_uri:U,scope:S})}`;console.log("Opening browser for Spotify login...");console.log("If it does not open, visit:",J);d(J)});H.on("close",()=>clearTimeout(M))})}});X({site:"spotify",name:"status",access:"read",description:"Show current playback status",strategy:Y.PUBLIC,browser:!1,args:[],columns:["track","artist","album","status","progress"],func:async()=>{const z=await Q("GET","/me/player");if(!z||!z.item)return[{track:"Nothing playing",artist:"",album:"",status:"",progress:""}];const G=z.item;if(G.type!=="track")return[{track:G.name,artist:"",album:G.show?.name??"",status:z.is_playing?"playing":"paused",progress:""}];const H=(z.progress_ms??0)/1000|0,M=G.duration_ms/1000|0,J=(W)=>`${W/60|0}:${String(W%60).padStart(2,"0")}`;return[{track:G.name,artist:G.artists.map((W)=>W.name).join(", "),album:G.album.name,status:z.is_playing?"playing":"paused",progress:`${J(H)} / ${J(M)}`}]}});X({site:"spotify",name:"play",access:"write",description:"Resume playback or search and play a track/artist",strategy:Y.PUBLIC,browser:!1,args:[{name:"query",type:"str",default:"",positional:!0,help:"Track or artist to play (optional)"}],columns:["track","artist","status"],func:async(z)=>{if(z.query){const{uri:G,name:H,artist:M}=await N(z.query);await Q("PUT","/me/player/play",{uris:[G]});return[{track:H,artist:M,status:"playing"}]}await Q("PUT","/me/player/play");return[{track:"",artist:"",status:"resumed"}]}});X({site:"spotify",name:"pause",access:"write",description:"Pause playback",strategy:Y.PUBLIC,browser:!1,args:[],columns:["status"],func:async()=>{await Q("PUT","/me/player/pause");return[{status:"paused"}]}});X({site:"spotify",name:"next",access:"write",description:"Skip to next track",strategy:Y.PUBLIC,browser:!1,args:[],columns:["status"],func:async()=>{await Q("POST","/me/player/next");return[{status:"skipped to next"}]}});X({site:"spotify",name:"prev",access:"write",description:"Skip to previous track",strategy:Y.PUBLIC,browser:!1,args:[],columns:["status"],func:async()=>{await Q("POST","/me/player/previous");return[{status:"skipped to previous"}]}});X({site:"spotify",name:"volume",access:"write",description:"Set playback volume (0-100)",strategy:Y.PUBLIC,browser:!1,args:[{name:"level",type:"int",default:50,positional:!0,required:!0,help:"Volume 0–100"}],columns:["volume"],func:async(z)=>{const G=Math.round(z.level);if(G<0||G>100)throw new Z("INVALID_ARGS","Volume must be between 0 and 100");await Q("PUT",`/me/player/volume?volume_percent=${G}`);return[{volume:`${G}%`}]}});X({site:"spotify",name:"search",access:"read",description:"Search for tracks",strategy:Y.PUBLIC,browser:!1,args:[{name:"query",type:"str",required:!0,positional:!0,help:"Search query"},{name:"limit",type:"int",default:10,help:"Number of results (default: 10)"}],columns:["track","artist","album","uri"],func:async(z)=>{const G=Math.min(50,Math.max(1,Math.round(z.limit))),H=await Q("GET",`/search?q=${encodeURIComponent(z.query)}&type=track&limit=${G}`),M=p(H);if(!M.length)throw new Z("EMPTY_RESULT",`No results found for: ${z.query}`);return M}});X({site:"spotify",name:"queue",access:"write",description:"Add a track to the playback queue",strategy:Y.PUBLIC,browser:!1,args:[{name:"query",type:"str",required:!0,positional:!0,help:"Track to add to queue"}],columns:["track","artist","status"],func:async(z)=>{const{uri:G,name:H,artist:M}=await N(z.query);await Q("POST",`/me/player/queue?uri=${encodeURIComponent(G)}`);return[{track:H,artist:M,status:"added to queue"}]}});X({site:"spotify",name:"shuffle",access:"write",description:"Toggle shuffle on/off",strategy:Y.PUBLIC,browser:!1,args:[{name:"state",type:"str",default:"on",positional:!0,choices:["on","off"],help:"on or off"}],columns:["shuffle"],func:async(z)=>{await Q("PUT",`/me/player/shuffle?state=${z.state==="on"}`);return[{shuffle:z.state}]}});X({site:"spotify",name:"repeat",access:"write",description:"Set repeat mode (off / track / context)",strategy:Y.PUBLIC,browser:!1,args:[{name:"mode",type:"str",default:"context",positional:!0,choices:["off","track","context"],help:"off / track / context"}],columns:["repeat"],func:async(z)=>{await Q("PUT",`/me/player/repeat?state=${z.mode}`);return[{repeat:z.mode}]}});
1
+ import{cli as i,Strategy as c}from"@jackwener/opencli/registry";import{CliError as u}from"@jackwener/opencli/errors";import{readFileSync as g,writeFileSync as E,mkdirSync as I,existsSync as R}from"fs";import{createServer as C}from"http";import{homedir as m}from"os";import{join as y}from"path";import{exec as v}from"child_process";import{assertSpotifyCredentialsConfigured as L,getFirstSpotifyTrack as x,mapSpotifyTrackResults as A,parseDotEnv as q,resolveSpotifyCredentials as B}from"./utils.js";const d=y(m(),".ppcli","spotify.env");function O(){if(!R(d))return{};return q(g(d,"utf-8"))}const N=O(),h=B(N),w=h.clientId,_=h.clientSecret,T="http://127.0.0.1:8888/callback",D=["user-read-playback-state","user-modify-playback-state","user-read-currently-playing","playlist-read-private"].join(" "),S=y(m(),".ppcli","spotify-tokens.json");function F(){try{return JSON.parse(g(S,"utf-8"))}catch{return null}}function U(t){I(y(m(),".ppcli"),{recursive:!0});E(S,JSON.stringify(t,null,2))}async function M(t){const e=await fetch("https://accounts.spotify.com/api/token",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Authorization:"Basic "+Buffer.from(`${w}:${_}`).toString("base64")},body:new URLSearchParams({grant_type:"refresh_token",refresh_token:t})});if(!e.ok){const r=await e.json().catch(()=>({}));throw new u("REFRESH_FAILED",r?.error_description||`Token refresh failed (${e.status})`)}const s=await e.json(),a={access_token:s.access_token,refresh_token:s.refresh_token||t,expires_at:Date.now()+s.expires_in*1000};U(a);return a.access_token}async function j(){const t=F();if(!t)throw new u("AUTH_REQUIRED","Not authenticated. Run: ppcli spotify auth");if(!t.access_token||!t.refresh_token||!(t.expires_at>0))throw new u("AUTH_CORRUPTED","Token file is corrupted. Run: ppcli spotify auth");if(Date.now()>t.expires_at-60000)return M(t.refresh_token);return t.access_token}async function o(t,e,s){const a=await j(),r=await fetch(`https://api.spotify.com/v1${e}`,{method:t,headers:{Authorization:`Bearer ${a}`,"Content-Type":"application/json"},body:s?JSON.stringify(s):void 0});if(r.status===204||r.status===202)return null;if(!r.ok){const n=await r.json().catch(()=>({}));throw new u("API_ERROR",n?.error?.message||`Spotify API error ${r.status}`)}return r.json()}async function P(t){const e=await o("GET",`/search?q=${encodeURIComponent(t)}&type=track&limit=1`),s=x(e);if(!s)throw new u("EMPTY_RESULT",`No track found for: ${t}`);return s}function H(t){const e=process.platform==="win32"?`start "" "${t}"`:process.platform==="darwin"?`open "${t}"`:`xdg-open "${t}"`;v(e)}i({site:"spotify",name:"auth",access:"write",description:"Authenticate with Spotify (OAuth — run once)",strategy:c.PUBLIC,browser:!1,args:[],columns:["status"],func:async()=>{L(h,d);return new Promise((t,e)=>{const s=C(async(r,n)=>{try{const p=new URL(r.url,"http://localhost:8888");if(p.pathname!=="/callback"){n.end();return}const k=p.searchParams.get("code");if(!k){n.end("Missing code");return}const l=await fetch("https://accounts.spotify.com/api/token",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Authorization:"Basic "+Buffer.from(`${w}:${_}`).toString("base64")},body:new URLSearchParams({grant_type:"authorization_code",code:k,redirect_uri:T})});if(!l.ok){const b=await l.json().catch(()=>({}));s.close();e(new u("AUTH_FAILED",b?.error_description||`Token exchange failed (${l.status})`));return}const f=await l.json();U({access_token:f.access_token,refresh_token:f.refresh_token,expires_at:Date.now()+f.expires_in*1000});n.writeHead(200,{"Content-Type":"text/html"});n.end("<h2>Spotify authenticated! You can close this tab.</h2>");s.close();t([{status:"Authenticated successfully"}])}catch(p){s.close();e(p)}});s.on("error",(r)=>{if(r.code==="EADDRINUSE")e(new u("PORT_IN_USE","Port 8888 is already in use. Stop the other process and retry."));else e(r)});const a=setTimeout(()=>{s.close();e(new u("AUTH_TIMEOUT","Authentication timed out after 5 minutes"))},300000);s.listen(8888,()=>{const r=`https://accounts.spotify.com/authorize?${new URLSearchParams({client_id:w,response_type:"code",redirect_uri:T,scope:D})}`;console.log("Opening browser for Spotify login...");console.log("If it does not open, visit:",r);H(r)});s.on("close",()=>clearTimeout(a))})}});i({site:"spotify",name:"status",access:"read",description:"Show current playback status",strategy:c.PUBLIC,browser:!1,args:[],columns:["track","artist","album","status","progress"],func:async()=>{const t=await o("GET","/me/player");if(!t||!t.item)return[{track:"Nothing playing",artist:"",album:"",status:"",progress:""}];const e=t.item;if(e.type!=="track")return[{track:e.name,artist:"",album:e.show?.name??"",status:t.is_playing?"playing":"paused",progress:""}];const s=(t.progress_ms??0)/1000|0,a=e.duration_ms/1000|0,r=(n)=>`${n/60|0}:${String(n%60).padStart(2,"0")}`;return[{track:e.name,artist:e.artists.map((n)=>n.name).join(", "),album:e.album.name,status:t.is_playing?"playing":"paused",progress:`${r(s)} / ${r(a)}`}]}});i({site:"spotify",name:"play",access:"write",description:"Resume playback or search and play a track/artist",strategy:c.PUBLIC,browser:!1,args:[{name:"query",type:"str",default:"",positional:!0,help:"Track or artist to play (optional)"}],columns:["track","artist","status"],func:async(t)=>{if(t.query){const{uri:e,name:s,artist:a}=await P(t.query);await o("PUT","/me/player/play",{uris:[e]});return[{track:s,artist:a,status:"playing"}]}await o("PUT","/me/player/play");return[{track:"",artist:"",status:"resumed"}]}});i({site:"spotify",name:"pause",access:"write",description:"Pause playback",strategy:c.PUBLIC,browser:!1,args:[],columns:["status"],func:async()=>{await o("PUT","/me/player/pause");return[{status:"paused"}]}});i({site:"spotify",name:"next",access:"write",description:"Skip to next track",strategy:c.PUBLIC,browser:!1,args:[],columns:["status"],func:async()=>{await o("POST","/me/player/next");return[{status:"skipped to next"}]}});i({site:"spotify",name:"prev",access:"write",description:"Skip to previous track",strategy:c.PUBLIC,browser:!1,args:[],columns:["status"],func:async()=>{await o("POST","/me/player/previous");return[{status:"skipped to previous"}]}});i({site:"spotify",name:"volume",access:"write",description:"Set playback volume (0-100)",strategy:c.PUBLIC,browser:!1,args:[{name:"level",type:"int",default:50,positional:!0,required:!0,help:"Volume 0–100"}],columns:["volume"],func:async(t)=>{const e=Math.round(t.level);if(e<0||e>100)throw new u("INVALID_ARGS","Volume must be between 0 and 100");await o("PUT",`/me/player/volume?volume_percent=${e}`);return[{volume:`${e}%`}]}});i({site:"spotify",name:"search",access:"read",description:"Search for tracks",strategy:c.PUBLIC,browser:!1,args:[{name:"query",type:"str",required:!0,positional:!0,help:"Search query"},{name:"limit",type:"int",default:10,help:"Number of results (default: 10)"}],columns:["track","artist","album","uri"],func:async(t)=>{const e=Math.min(50,Math.max(1,Math.round(t.limit))),s=await o("GET",`/search?q=${encodeURIComponent(t.query)}&type=track&limit=${e}`),a=A(s);if(!a.length)throw new u("EMPTY_RESULT",`No results found for: ${t.query}`);return a}});i({site:"spotify",name:"queue",access:"write",description:"Add a track to the playback queue",strategy:c.PUBLIC,browser:!1,args:[{name:"query",type:"str",required:!0,positional:!0,help:"Track to add to queue"}],columns:["track","artist","status"],func:async(t)=>{const{uri:e,name:s,artist:a}=await P(t.query);await o("POST",`/me/player/queue?uri=${encodeURIComponent(e)}`);return[{track:s,artist:a,status:"added to queue"}]}});i({site:"spotify",name:"shuffle",access:"write",description:"Toggle shuffle on/off",strategy:c.PUBLIC,browser:!1,args:[{name:"state",type:"str",default:"on",positional:!0,choices:["on","off"],help:"on or off"}],columns:["shuffle"],func:async(t)=>{await o("PUT",`/me/player/shuffle?state=${t.state==="on"}`);return[{shuffle:t.state}]}});i({site:"spotify",name:"repeat",access:"write",description:"Set repeat mode (off / track / context)",strategy:c.PUBLIC,browser:!1,args:[{name:"mode",type:"str",default:"context",positional:!0,choices:["off","track","context"],help:"off / track / context"}],columns:["repeat"],func:async(t)=>{await o("PUT",`/me/player/repeat?state=${t.mode}`);return[{repeat:t.mode}]}});
@@ -1,13 +1,13 @@
1
- import{cli as h,Strategy as x}from"@jackwener/opencli/registry";import{ArgumentError as f,CommandExecutionError as G,EmptyResultError as B}from"@jackwener/opencli/errors";const O="https://api.stackexchange.com/2.3",R="stackoverflow",M=100;async function X(F,H){let V;try{V=await fetch(F)}catch(Y){const $=Y instanceof Error?Y.message:String(Y);throw new G(`Network failure fetching ${H}: ${$}`,"Check connectivity to api.stackexchange.com")}if(V.status===404)throw new B(H,`${H} not found`);if(!V.ok)throw new G(`Stack Exchange API HTTP ${V.status} for ${H}`,"Check the question id and quota (300/day per IP)");let K;try{K=await V.json()}catch(Y){const $=Y instanceof Error?Y.message:String(Y);throw new G(`Malformed JSON from Stack Exchange API for ${H}: ${$}`,"The API returned a non-JSON body — likely a transient outage")}if(K&&K.error_id)throw new G(`Stack Exchange API error ${K.error_id} (${K.error_name}) for ${H}: ${K.error_message||""}`,"Common causes: invalid filter, throttled, or quota exhausted");return K}function v(F){if(F===void 0||F===null||F==="")return NaN;const H=typeof F==="number"?F:Number(F);return Number.isFinite(H)&&Number.isInteger(H)?H:NaN}function A(F,H,V){const K=v(F);if(!Number.isInteger(K)||K<H)throw new f(`${V} must be an integer >= ${H}, got ${JSON.stringify(F)}`);return K}function C(F,H,V,K){const Y=v(F);if(!Number.isInteger(Y)||Y<H||Y>V)throw new f(`${K} must be an integer between ${H} and ${V}, got ${JSON.stringify(F)}`);return Y}function E(F,H){const V=F.accepted_answer_id;return H.slice().sort((K,Y)=>{const $=K.is_accepted||V&&K.answer_id===V,W=Y.is_accepted||V&&Y.answer_id===V;if($!==W)return $?-1:1;return(Y.score??0)-(K.score??0)})}async function u(F,H,V){const K=F.accepted_answer_id;if(!K||H.some((W)=>W.answer_id===K))return H;const $=((await X(`${O}/answers/${K}?site=${R}&filter=withbody`,`${V}/accepted-answer`)).items||[])[0];return $?H.concat($):H}async function w(F,H,V){const K=new Map;if(F.length===0)return K;const Y=F.map((Q)=>Q.answer_id).join(";"),$=Math.min(M,F.length*H),W=await X(`${O}/answers/${Y}/comments?site=${R}&filter=withbody&order=asc&sort=creation&pagesize=${$}`,`${V}/answer-comments`);for(const Q of W.items||[]){if(!Q.post_id)continue;if(!K.has(Q.post_id))K.set(Q.post_id,[]);K.get(Q.post_id).push(Q)}if(W.has_more){if(F.some((z)=>{return(K.get(z.answer_id)||[]).length<H}))throw new G(`Stack Exchange answer comments for ${V} exceed one API page`,"Lower --answers-limit or --comments-limit; refusing to return a partial answer-comment set.")}return K}const d={amp:"&",lt:"<",gt:">",quot:'"',apos:"'",nbsp:" ",hellip:"…",mdash:"—",ndash:"–",laquo:"«",raquo:"»",copy:"©",reg:"®",trade:"™",euro:"€",pound:"£",yen:"¥",rsquo:"’",lsquo:"‘",rdquo:"”",ldquo:"“"};function L(F){if(!F)return"";return String(F).replace(/&#x([0-9a-fA-F]+);/g,(H,V)=>{const K=parseInt(V,16);return Number.isFinite(K)?String.fromCodePoint(K):""}).replace(/&#(\d+);/g,(H,V)=>{const K=parseInt(V,10);return Number.isFinite(K)?String.fromCodePoint(K):""}).replace(/&([a-zA-Z]+);/g,(H,V)=>d[V]??H)}function Z(F){if(!F)return"";const H=String(F).replace(/<pre[^>]*><code[^>]*>([\s\S]*?)<\/code><\/pre>/gi,`
1
+ import{cli as v,Strategy as F}from"@jackwener/opencli/registry";import{ArgumentError as _,CommandExecutionError as u,EmptyResultError as E}from"@jackwener/opencli/errors";const p="https://api.stackexchange.com/2.3",f="stackoverflow",A=100;async function w(e,t){let r;try{r=await fetch(e)}catch(o){const s=o instanceof Error?o.message:String(o);throw new u(`Network failure fetching ${t}: ${s}`,"Check connectivity to api.stackexchange.com")}if(r.status===404)throw new E(t,`${t} not found`);if(!r.ok)throw new u(`Stack Exchange API HTTP ${r.status} for ${t}`,"Check the question id and quota (300/day per IP)");let n;try{n=await r.json()}catch(o){const s=o instanceof Error?o.message:String(o);throw new u(`Malformed JSON from Stack Exchange API for ${t}: ${s}`,"The API returned a non-JSON body — likely a transient outage")}if(n&&n.error_id)throw new u(`Stack Exchange API error ${n.error_id} (${n.error_name}) for ${t}: ${n.error_message||""}`,"Common causes: invalid filter, throttled, or quota exhausted");return n}function k(e){if(e===void 0||e===null||e==="")return NaN;const t=typeof e==="number"?e:Number(e);return Number.isFinite(t)&&Number.isInteger(t)?t:NaN}function j(e,t,r){const n=k(e);if(!Number.isInteger(n)||n<t)throw new _(`${r} must be an integer >= ${t}, got ${JSON.stringify(e)}`);return n}function N(e,t,r,n){const o=k(e);if(!Number.isInteger(o)||o<t||o>r)throw new _(`${n} must be an integer between ${t} and ${r}, got ${JSON.stringify(e)}`);return o}function z(e,t){const r=e.accepted_answer_id;return t.slice().sort((n,o)=>{const s=n.is_accepted||r&&n.answer_id===r,d=o.is_accepted||r&&o.answer_id===r;if(s!==d)return s?-1:1;return(o.score??0)-(n.score??0)})}async function J(e,t,r){const n=e.accepted_answer_id;if(!n||t.some((d)=>d.answer_id===n))return t;const s=((await w(`${p}/answers/${n}?site=${f}&filter=withbody`,`${r}/accepted-answer`)).items||[])[0];return s?t.concat(s):t}async function L(e,t,r){const n=new Map;if(e.length===0)return n;const o=e.map((i)=>i.answer_id).join(";"),s=Math.min(A,e.length*t),d=await w(`${p}/answers/${o}/comments?site=${f}&filter=withbody&order=asc&sort=creation&pagesize=${s}`,`${r}/answer-comments`);for(const i of d.items||[]){if(!i.post_id)continue;if(!n.has(i.post_id))n.set(i.post_id,[]);n.get(i.post_id).push(i)}if(d.has_more){if(e.some((c)=>{return(n.get(c.answer_id)||[]).length<t}))throw new u(`Stack Exchange answer comments for ${r} exceed one API page`,"Lower --answers-limit or --comments-limit; refusing to return a partial answer-comment set.")}return n}const R={amp:"&",lt:"<",gt:">",quot:'"',apos:"'",nbsp:" ",hellip:"…",mdash:"—",ndash:"–",laquo:"«",raquo:"»",copy:"©",reg:"®",trade:"™",euro:"€",pound:"£",yen:"¥",rsquo:"’",lsquo:"‘",rdquo:"”",ldquo:"“"};function b(e){if(!e)return"";return String(e).replace(/&#x([0-9a-fA-F]+);/g,(t,r)=>{const n=parseInt(r,16);return Number.isFinite(n)?String.fromCodePoint(n):""}).replace(/&#(\d+);/g,(t,r)=>{const n=parseInt(r,10);return Number.isFinite(n)?String.fromCodePoint(n):""}).replace(/&([a-zA-Z]+);/g,(t,r)=>R[r]??t)}function l(e){if(!e)return"";const t=String(e).replace(/<pre[^>]*><code[^>]*>([\s\S]*?)<\/code><\/pre>/gi,`
2
2
  $1
3
3
  `).replace(/<code[^>]*>(.*?)<\/code>/gi,"`$1`").replace(/<p[^>]*>/gi,`
4
4
 
5
5
  `).replace(/<\/p>/gi,"").replace(/<br\s*\/?>/gi,`
6
6
  `).replace(/<li[^>]*>/gi,`
7
- - `).replace(/<\/li>/gi,"").replace(/<a[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>/gi,"$2 ($1)").replace(/<[^>]+>/g,"");return L(H).replace(/\n{3,}/g,`
7
+ - `).replace(/<\/li>/gi,"").replace(/<a[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>/gi,"$2 ($1)").replace(/<[^>]+>/g,"");return b(t).replace(/\n{3,}/g,`
8
8
 
9
- `).trim()}function J(F){return L(F?.display_name||"")||"[deleted]"}function D(F,H){if(!F||F.length<=H)return F||"";return F.slice(0,H)+" ... [truncated]"}function T(F,H){if(H===0)return F;const K=`${" ".repeat(H)}> `;return F.split(`
10
- `).map((Y)=>K+Y).join(`
11
- `)}h({site:"stackoverflow",name:"read",access:"read",description:"Read a Stack Overflow question with answers and comments",domain:"stackoverflow.com",strategy:x.PUBLIC,browser:!1,args:[{name:"id",required:!0,positional:!0,help:"Stack Overflow question id (numeric, e.g. 79935770)"},{name:"answers-limit",type:"int",default:10,help:"Max answers to include (1-100; accepted answer always included first)"},{name:"comments-limit",type:"int",default:5,help:"Max comments per question/answer (1-100)"},{name:"max-length",type:"int",default:4000,help:"Max characters per body / answer / comment (min 100)"}],columns:["type","author","score","accepted","text"],func:async(F)=>{const H=String(F.id||"").trim();if(!/^\d+$/.test(H))throw new f(`Invalid Stack Overflow question id: ${F.id}`,"Pass a numeric id like 79935770");const V=C(F["answers-limit"]??10,1,M,"stackoverflow read --answers-limit"),K=C(F["comments-limit"]??5,1,M,"stackoverflow read --comments-limit"),Y=A(F["max-length"]??4000,100,"stackoverflow read --max-length"),$=`stackoverflow/${H}`,W=`${O}/questions/${H}?site=${R}&filter=withbody`,z=((await X(W,$)).items||[])[0];if(!z)throw new B($,"Question not found");const[P,q]=await Promise.all([X(`${O}/questions/${H}/comments?site=${R}&filter=withbody&order=asc&sort=creation&pagesize=${K}`,`${$}/comments`),X(`${O}/questions/${H}/answers?site=${R}&filter=withbody&order=desc&sort=votes&pagesize=${V}`,`${$}/answers`)]),g=await u(z,q.items||[],$),y=E(z,g).slice(0,V),_=await w(y,K,$),k=[],p=Z(z.body||""),S=[z.title||"",p,z.link||""].filter(Boolean);k.push({type:"POST",author:J(z.owner),score:z.score??0,accepted:"",text:D(S.join(`
9
+ `).trim()}function h(e){return b(e?.display_name||"")||"[deleted]"}function g(e,t){if(!e||e.length<=t)return e||"";return e.slice(0,t)+" ... [truncated]"}function x(e,t){if(t===0)return e;const n=`${" ".repeat(t)}> `;return e.split(`
10
+ `).map((o)=>n+o).join(`
11
+ `)}v({site:"stackoverflow",name:"read",access:"read",description:"Read a Stack Overflow question with answers and comments",domain:"stackoverflow.com",strategy:F.PUBLIC,browser:!1,args:[{name:"id",required:!0,positional:!0,help:"Stack Overflow question id (numeric, e.g. 79935770)"},{name:"answers-limit",type:"int",default:10,help:"Max answers to include (1-100; accepted answer always included first)"},{name:"comments-limit",type:"int",default:5,help:"Max comments per question/answer (1-100)"},{name:"max-length",type:"int",default:4000,help:"Max characters per body / answer / comment (min 100)"}],columns:["type","author","score","accepted","text"],func:async(e)=>{const t=String(e.id||"").trim();if(!/^\d+$/.test(t))throw new _(`Invalid Stack Overflow question id: ${e.id}`,"Pass a numeric id like 79935770");const r=N(e["answers-limit"]??10,1,A,"stackoverflow read --answers-limit"),n=N(e["comments-limit"]??5,1,A,"stackoverflow read --comments-limit"),o=j(e["max-length"]??4000,100,"stackoverflow read --max-length"),s=`stackoverflow/${t}`,d=`${p}/questions/${t}?site=${f}&filter=withbody`,c=((await w(d,s)).items||[])[0];if(!c)throw new E(s,"Question not found");const[q,C]=await Promise.all([w(`${p}/questions/${t}/comments?site=${f}&filter=withbody&order=asc&sort=creation&pagesize=${n}`,`${s}/comments`),w(`${p}/questions/${t}/answers?site=${f}&filter=withbody&order=desc&sort=votes&pagesize=${r}`,`${s}/answers`)]),P=await J(c,C.items||[],s),I=z(c,P).slice(0,r),M=await L(I,n,s),m=[],T=l(c.body||""),O=[c.title||"",T,c.link||""].filter(Boolean);m.push({type:"POST",author:h(c.owner),score:c.score??0,accepted:"",text:g(O.join(`
12
12
 
13
- `),Y)});const I=(P.items||[]).slice(0,K);for(const U of I){const j=T(Z(U.body||""),1);k.push({type:"Q-COMMENT",author:J(U.owner),score:U.score??0,accepted:"",text:D(j,Y)})}for(const U of y){k.push({type:"ANSWER",author:J(U.owner),score:U.score??0,accepted:U.is_accepted?"true":"",text:D(Z(U.body||""),Y)});const j=(_.get(U.answer_id)||[]).slice(0,K);for(const N of j){const b=T(Z(N.body||""),1);k.push({type:"A-COMMENT",author:J(N.owner),score:N.score??0,accepted:"",text:D(b,Y)})}}return k}});
13
+ `),o)});const D=(q.items||[]).slice(0,n);for(const a of D){const y=x(l(a.body||""),1);m.push({type:"Q-COMMENT",author:h(a.owner),score:a.score??0,accepted:"",text:g(y,o)})}for(const a of I){m.push({type:"ANSWER",author:h(a.owner),score:a.score??0,accepted:a.is_accepted?"true":"",text:g(l(a.body||""),o)});const y=(M.get(a.answer_id)||[]).slice(0,n);for(const S of y){const B=x(l(S.body||""),1);m.push({type:"A-COMMENT",author:h(S.owner),score:S.score??0,accepted:"",text:g(B,o)})}}return m}});
@@ -1 +1 @@
1
- import{cli as u,Strategy as m}from"@jackwener/opencli/registry";import{ArgumentError as a}from"@jackwener/opencli/errors";import{seFetch as w,normalizeLimit as f,epochToDate as n,ensureItems as p,decodeHtmlEntities as l}from"./utils.js";const r=["rank","activity","votes","creation"];u({site:"stackoverflow",name:"related",access:"read",description:"List Stack Overflow questions related to a given question id.",domain:"stackoverflow.com",strategy:m.PUBLIC,browser:!1,args:[{name:"id",positional:!0,required:!0,type:"string",help:"Stack Overflow question id (numeric, e.g. 79935770)."},{name:"sort",type:"string",default:"rank",help:`Sort key: ${r.join(", ")} (rank = SO relevance default).`},{name:"limit",type:"int",default:20,help:"Max related questions (1-100)."}],columns:["rank","id","title","score","answers","views","isAnswered","tags","author","createdAt","lastActivityAt","url"],func:async(t)=>{const i=String(t.id??"").trim();if(!/^\d+$/.test(i))throw new a(`stackoverflow related id must be a numeric question id, got ${JSON.stringify(t.id)}`);const s=String(t.sort??"rank").toLowerCase();if(!r.includes(s))throw new a(`stackoverflow related sort must be one of ${r.join(", ")}`);const o=f(t.limit,20,100,"limit"),c=await w(`/questions/${encodeURIComponent(i)}/related`,{searchParams:{order:"desc",sort:s,pagesize:o}});return p(c,`stackoverflow related ${i}`).slice(0,o).map((e,d)=>({rank:d+1,id:e.question_id,title:l(e.title||""),score:e.score??0,answers:e.answer_count??0,views:e.view_count??0,isAnswered:Boolean(e.is_answered),tags:Array.isArray(e.tags)?e.tags.join(", "):"",author:l(e.owner?.display_name||""),createdAt:n(e.creation_date),lastActivityAt:n(e.last_activity_date),url:e.link||(e.question_id?`https://stackoverflow.com/questions/${e.question_id}`:"")}))}});
1
+ import{cli as m,Strategy as u}from"@jackwener/opencli/registry";import{ArgumentError as a}from"@jackwener/opencli/errors";import{seFetch as w,normalizeLimit as f,epochToDate as n,ensureItems as v,decodeHtmlEntities as c}from"./utils.js";const r=["rank","activity","votes","creation"];m({site:"stackoverflow",name:"related",access:"read",description:"List Stack Overflow questions related to a given question id.",domain:"stackoverflow.com",strategy:u.PUBLIC,browser:!1,args:[{name:"id",positional:!0,required:!0,type:"string",help:"Stack Overflow question id (numeric, e.g. 79935770)."},{name:"sort",type:"string",default:"rank",help:`Sort key: ${r.join(", ")} (rank = SO relevance default).`},{name:"limit",type:"int",default:20,help:"Max related questions (1-100)."}],columns:["rank","id","title","score","answers","views","isAnswered","tags","author","createdAt","lastActivityAt","url"],func:async(t)=>{const i=String(t.id??"").trim();if(!/^\d+$/.test(i))throw new a(`stackoverflow related id must be a numeric question id, got ${JSON.stringify(t.id)}`);const s=String(t.sort??"rank").toLowerCase();if(!r.includes(s))throw new a(`stackoverflow related sort must be one of ${r.join(", ")}`);const o=f(t.limit,20,100,"limit"),l=await w(`/questions/${encodeURIComponent(i)}/related`,{searchParams:{order:"desc",sort:s,pagesize:o}});return v(l,`stackoverflow related ${i}`).slice(0,o).map((e,d)=>({rank:d+1,id:e.question_id,title:c(e.title||""),score:e.score??0,answers:e.answer_count??0,views:e.view_count??0,isAnswered:Boolean(e.is_answered),tags:Array.isArray(e.tags)?e.tags.join(", "):"",author:c(e.owner?.display_name||""),createdAt:n(e.creation_date),lastActivityAt:n(e.last_activity_date),url:e.link||(e.question_id?`https://stackoverflow.com/questions/${e.question_id}`:"")}))}});
@@ -1 +1 @@
1
- import{cli as m,Strategy as g}from"@jackwener/opencli/registry";import{ArgumentError as u}from"@jackwener/opencli/errors";import{seFetch as d,normalizeLimit as w,requireString as v,epochToDate as r,ensureItems as p,decodeHtmlEntities as n}from"./utils.js";const i=["activity","votes","creation","hot","week","month"];m({site:"stackoverflow",name:"tag",access:"read",description:"List Stack Overflow questions tagged with a given tag (most active first).",domain:"stackoverflow.com",strategy:g.PUBLIC,browser:!1,args:[{name:"tag",positional:!0,required:!0,type:"string",help:"Tag slug (e.g. python, rust, typescript)."},{name:"sort",type:"string",default:"activity",help:`Sort key: ${i.join(", ")}`},{name:"limit",type:"int",default:20,help:"Max questions to return (max 100)."}],columns:["rank","id","title","score","answers","views","isAnswered","tags","author","createdAt","lastActivityAt","url"],func:async(e)=>{const s=v(e.tag,"tag").toLowerCase(),a=String(e.sort??"activity").toLowerCase();if(!i.includes(a))throw new u(`sort must be one of ${i.join(", ")}`);const o=w(e.limit,20,100,"limit"),c=await d("/questions",{searchParams:{tagged:s,order:"desc",sort:a,pagesize:o}});return p(c,`stackoverflow tag "${s}"`).slice(0,o).map((t,l)=>({rank:l+1,id:t.question_id,title:n(t.title||""),score:t.score??0,answers:t.answer_count??0,views:t.view_count??0,isAnswered:Boolean(t.is_answered),tags:Array.isArray(t.tags)?t.tags.join(", "):"",author:n(t.owner?.display_name||""),createdAt:r(t.creation_date),lastActivityAt:r(t.last_activity_date),url:t.link||(t.question_id?`https://stackoverflow.com/questions/${t.question_id}`:"")}))}});
1
+ import{cli as m,Strategy as u}from"@jackwener/opencli/registry";import{ArgumentError as d}from"@jackwener/opencli/errors";import{seFetch as g,normalizeLimit as w,requireString as p,epochToDate as r,ensureItems as y,decodeHtmlEntities as n}from"./utils.js";const i=["activity","votes","creation","hot","week","month"];m({site:"stackoverflow",name:"tag",access:"read",description:"List Stack Overflow questions tagged with a given tag (most active first).",domain:"stackoverflow.com",strategy:u.PUBLIC,browser:!1,args:[{name:"tag",positional:!0,required:!0,type:"string",help:"Tag slug (e.g. python, rust, typescript)."},{name:"sort",type:"string",default:"activity",help:`Sort key: ${i.join(", ")}`},{name:"limit",type:"int",default:20,help:"Max questions to return (max 100)."}],columns:["rank","id","title","score","answers","views","isAnswered","tags","author","createdAt","lastActivityAt","url"],func:async(e)=>{const s=p(e.tag,"tag").toLowerCase(),a=String(e.sort??"activity").toLowerCase();if(!i.includes(a))throw new d(`sort must be one of ${i.join(", ")}`);const o=w(e.limit,20,100,"limit"),c=await g("/questions",{searchParams:{tagged:s,order:"desc",sort:a,pagesize:o}});return y(c,`stackoverflow tag "${s}"`).slice(0,o).map((t,l)=>({rank:l+1,id:t.question_id,title:n(t.title||""),score:t.score??0,answers:t.answer_count??0,views:t.view_count??0,isAnswered:Boolean(t.is_answered),tags:Array.isArray(t.tags)?t.tags.join(", "):"",author:n(t.owner?.display_name||""),createdAt:r(t.creation_date),lastActivityAt:r(t.last_activity_date),url:t.link||(t.question_id?`https://stackoverflow.com/questions/${t.question_id}`:"")}))}});
@@ -1 +1 @@
1
- import{cli as n,Strategy as c}from"@jackwener/opencli/registry";import{seFetch as l,normalizeLimit as d,requireString as m,epochToDate as a,ensureItems as u,decodeHtmlEntities as r}from"./utils.js";n({site:"stackoverflow",name:"user",access:"read",description:"Find Stack Overflow users by display name (highest reputation first).",domain:"stackoverflow.com",strategy:c.PUBLIC,browser:!1,args:[{name:"name",positional:!0,required:!0,type:"string",help:"Display name (or substring) to search."},{name:"limit",type:"int",default:10,help:"Max users to return (max 100)."}],columns:["userId","displayName","reputation","goldBadges","silverBadges","bronzeBadges","location","createdAt","lastAccessAt","url"],func:async(s)=>{const i=m(s.name,"name"),t=d(s.limit,10,100,"limit"),o=await l("/users",{searchParams:{inname:i,order:"desc",sort:"reputation",pagesize:t}});return u(o,"stackoverflow user").slice(0,t).map((e)=>({userId:e.user_id,displayName:r(e.display_name||""),reputation:e.reputation??0,goldBadges:e.badge_counts?.gold??0,silverBadges:e.badge_counts?.silver??0,bronzeBadges:e.badge_counts?.bronze??0,location:r(e.location||""),createdAt:a(e.creation_date),lastAccessAt:a(e.last_access_date),url:e.link||(e.user_id?`https://stackoverflow.com/users/${e.user_id}`:"")}))}});
1
+ import{cli as n,Strategy as c}from"@jackwener/opencli/registry";import{seFetch as l,normalizeLimit as d,requireString as m,epochToDate as a,ensureItems as p,decodeHtmlEntities as r}from"./utils.js";n({site:"stackoverflow",name:"user",access:"read",description:"Find Stack Overflow users by display name (highest reputation first).",domain:"stackoverflow.com",strategy:c.PUBLIC,browser:!1,args:[{name:"name",positional:!0,required:!0,type:"string",help:"Display name (or substring) to search."},{name:"limit",type:"int",default:10,help:"Max users to return (max 100)."}],columns:["userId","displayName","reputation","goldBadges","silverBadges","bronzeBadges","location","createdAt","lastAccessAt","url"],func:async(t)=>{const i=m(t.name,"name"),s=d(t.limit,10,100,"limit"),o=await l("/users",{searchParams:{inname:i,order:"desc",sort:"reputation",pagesize:s}});return p(o,"stackoverflow user").slice(0,s).map((e)=>({userId:e.user_id,displayName:r(e.display_name||""),reputation:e.reputation??0,goldBadges:e.badge_counts?.gold??0,silverBadges:e.badge_counts?.silver??0,bronzeBadges:e.badge_counts?.bronze??0,location:r(e.location||""),createdAt:a(e.creation_date),lastAccessAt:a(e.last_access_date),url:e.link||(e.user_id?`https://stackoverflow.com/users/${e.user_id}`:"")}))}});
@@ -1 +1 @@
1
- import{ArgumentError as W,CommandExecutionError as O,EmptyResultError as X}from"@jackwener/opencli/errors";export const SE_API="https://api.stackexchange.com/2.3",SE_SITE="stackoverflow";const Z="opencli-stackoverflow (+https://github.com/jackwener/OpenCLI)";export function normalizeLimit(q,z,B,G="limit"){const F=Number(q??z);if(!Number.isInteger(F)||F<=0)throw new W(`${G} must be a positive integer`);if(F>B)throw new W(`${G} must be <= ${B}`);return F}export function requireString(q,z){const B=String(q??"").trim();if(!B)throw new W(`${z} cannot be empty`);return B}export async function seFetch(q,{searchParams:z}={}){const B=new URL(q.startsWith("http")?q:`${SE_API}${q.startsWith("/")?"":"/"}${q}`);if(z)for(const[F,Q]of Object.entries(z)){if(Q==null||Q==="")continue;B.searchParams.set(F,String(Q))}if(!B.searchParams.has("site"))B.searchParams.set("site",SE_SITE);let G;try{G=await fetch(B,{headers:{Accept:"application/json","Accept-Encoding":"gzip","User-Agent":Z}})}catch(F){throw new O(`stack exchange request failed: ${F?.message||F}`)}if(G.status===429)throw new O("stack exchange returned HTTP 429 (rate limited)","Wait a few seconds and retry, or lower --limit.");if(!G.ok){let F="";try{F=(await G.json())?.error_message||""}catch{}throw new O(`stack exchange HTTP ${G.status}: ${F||G.statusText}`)}let K;try{K=await G.json()}catch(F){throw new O(`stack exchange returned malformed JSON: ${F?.message||F}`)}if(K?.error_id)throw new O(`stack exchange API error: ${K.error_message||K.error_name}`,"Inspect the URL in a browser for the canonical error context.");return K}export function epochToDate(q){if(q==null||q==="")return"";const z=Number(q);if(!Number.isFinite(z)||z<=0)return"";return new Date(z*1000).toISOString().slice(0,10)}export function ensureItems(q,z){const B=Array.isArray(q?.items)?q.items:[];if(B.length===0)throw new X(z,`${z} returned no items.`);return B}const $={"&amp;":"&","&lt;":"<","&gt;":">","&quot;":'"',"&#39;":"'","&apos;":"'","&nbsp;":" "};export function decodeHtmlEntities(q){if(q==null)return"";return String(q).replace(/&#x([0-9a-fA-F]+);/g,(z,B)=>String.fromCodePoint(parseInt(B,16))).replace(/&#(\d+);/g,(z,B)=>String.fromCodePoint(parseInt(B,10))).replace(/&(amp|lt|gt|quot|#39|apos|nbsp);/g,(z)=>$[z]||z)}
1
+ import{ArgumentError as a,CommandExecutionError as i,EmptyResultError as m}from"@jackwener/opencli/errors";export const SE_API="https://api.stackexchange.com/2.3",SE_SITE="stackoverflow";const u="opencli-stackoverflow (+https://github.com/jackwener/OpenCLI)";export function normalizeLimit(t,e,r,o="limit"){const n=Number(t??e);if(!Number.isInteger(n)||n<=0)throw new a(`${o} must be a positive integer`);if(n>r)throw new a(`${o} must be <= ${r}`);return n}export function requireString(t,e){const r=String(t??"").trim();if(!r)throw new a(`${e} cannot be empty`);return r}export async function seFetch(t,{searchParams:e}={}){const r=new URL(t.startsWith("http")?t:`${SE_API}${t.startsWith("/")?"":"/"}${t}`);if(e)for(const[n,c]of Object.entries(e)){if(c==null||c==="")continue;r.searchParams.set(n,String(c))}if(!r.searchParams.has("site"))r.searchParams.set("site",SE_SITE);let o;try{o=await fetch(r,{headers:{Accept:"application/json","Accept-Encoding":"gzip","User-Agent":u}})}catch(n){throw new i(`stack exchange request failed: ${n?.message||n}`)}if(o.status===429)throw new i("stack exchange returned HTTP 429 (rate limited)","Wait a few seconds and retry, or lower --limit.");if(!o.ok){let n="";try{n=(await o.json())?.error_message||""}catch{}throw new i(`stack exchange HTTP ${o.status}: ${n||o.statusText}`)}let s;try{s=await o.json()}catch(n){throw new i(`stack exchange returned malformed JSON: ${n?.message||n}`)}if(s?.error_id)throw new i(`stack exchange API error: ${s.error_message||s.error_name}`,"Inspect the URL in a browser for the canonical error context.");return s}export function epochToDate(t){if(t==null||t==="")return"";const e=Number(t);if(!Number.isFinite(e)||e<=0)return"";return new Date(e*1000).toISOString().slice(0,10)}export function ensureItems(t,e){const r=Array.isArray(t?.items)?t.items:[];if(r.length===0)throw new m(e,`${e} returned no items.`);return r}const p={"&amp;":"&","&lt;":"<","&gt;":">","&quot;":'"',"&#39;":"'","&apos;":"'","&nbsp;":" "};export function decodeHtmlEntities(t){if(t==null)return"";return String(t).replace(/&#x([0-9a-fA-F]+);/g,(e,r)=>String.fromCodePoint(parseInt(r,16))).replace(/&#(\d+);/g,(e,r)=>String.fromCodePoint(parseInt(r,10))).replace(/&(amp|lt|gt|quot|#39|apos|nbsp);/g,(e)=>p[e]||e)}
package/clis/steam/app.js CHANGED
@@ -1 +1 @@
1
- import{cli as d,Strategy as l}from"@jackwener/opencli/registry";import{EmptyResultError as u}from"@jackwener/opencli/errors";import{STEAM_STORE as s,decodeHtmlEntities as o,priceCents as f,requireAppId as g,requireCountryCode as y,steamFetch as b}from"./utils.js";function n(t){if(!Array.isArray(t))return"";return t.map((r)=>r&&typeof r==="object"?r.description??r.name??"":String(r)).filter(Boolean).join(", ")}d({site:"steam",name:"app",access:"read",description:"Steam storefront detail for a single app id",domain:"store.steampowered.com",strategy:l.PUBLIC,browser:!1,args:[{name:"id",positional:!0,required:!0,help:'Numeric Steam app id (e.g. "620" for Portal 2)'},{name:"currency",default:"us",help:"Storefront country code (e.g. us / cn / jp / de)"}],columns:["id","name","type","isFree","releaseDate","developers","publishers","price","currency","metacritic","recommendations","genres","categories","shortDescription","website","url"],func:async(t)=>{const r=g(t.id),p=y(t.currency),c=`${s}/api/appdetails?appids=${r}&l=en&cc=${encodeURIComponent(p)}`,a=(await b(c,`steam app ${r}`))?.[r];if(!a||a.success!==!0||!a.data)throw new u("steam app",`Steam app id ${r} returned no data (may be region-locked or removed).`);const e=a.data,i=e.is_free===!0,m=f(e?.price_overview?.final??null);return[{id:String(e.steam_appid??r),name:o(e.name??""),type:String(e.type??""),isFree:i,releaseDate:String(e?.release_date?.date??""),developers:Array.isArray(e.developers)?e.developers.join(", "):"",publishers:Array.isArray(e.publishers)?e.publishers.join(", "):"",price:i?0:m,currency:String(e?.price_overview?.currency??"").toUpperCase(),metacritic:e?.metacritic?.score!=null?Number(e.metacritic.score):null,recommendations:e?.recommendations?.total!=null?Number(e.recommendations.total):null,genres:n(e.genres),categories:n(e.categories),shortDescription:o(e.short_description??""),website:String(e.website??""),url:`${s}/app/${e.steam_appid??r}/`}]}});
1
+ import{cli as d,Strategy as l}from"@jackwener/opencli/registry";import{EmptyResultError as u}from"@jackwener/opencli/errors";import{STEAM_STORE as o,decodeHtmlEntities as s,priceCents as y,requireAppId as g,requireCountryCode as f,steamFetch as b}from"./utils.js";function n(t){if(!Array.isArray(t))return"";return t.map((r)=>r&&typeof r==="object"?r.description??r.name??"":String(r)).filter(Boolean).join(", ")}d({site:"steam",name:"app",access:"read",description:"Steam storefront detail for a single app id",domain:"store.steampowered.com",strategy:l.PUBLIC,browser:!1,args:[{name:"id",positional:!0,required:!0,help:'Numeric Steam app id (e.g. "620" for Portal 2)'},{name:"currency",default:"us",help:"Storefront country code (e.g. us / cn / jp / de)"}],columns:["id","name","type","isFree","releaseDate","developers","publishers","price","currency","metacritic","recommendations","genres","categories","shortDescription","website","url"],func:async(t)=>{const r=g(t.id),c=f(t.currency),p=`${o}/api/appdetails?appids=${r}&l=en&cc=${encodeURIComponent(c)}`,i=(await b(p,`steam app ${r}`))?.[r];if(!i||i.success!==!0||!i.data)throw new u("steam app",`Steam app id ${r} returned no data (may be region-locked or removed).`);const e=i.data,a=e.is_free===!0,m=y(e?.price_overview?.final??null);return[{id:String(e.steam_appid??r),name:s(e.name??""),type:String(e.type??""),isFree:a,releaseDate:String(e?.release_date?.date??""),developers:Array.isArray(e.developers)?e.developers.join(", "):"",publishers:Array.isArray(e.publishers)?e.publishers.join(", "):"",price:a?0:m,currency:String(e?.price_overview?.currency??"").toUpperCase(),metacritic:e?.metacritic?.score!=null?Number(e.metacritic.score):null,recommendations:e?.recommendations?.total!=null?Number(e.recommendations.total):null,genres:n(e.genres),categories:n(e.categories),shortDescription:s(e.short_description??""),website:String(e.website??""),url:`${o}/app/${e.steam_appid??r}/`}]}});
@@ -1 +1 @@
1
- import{cli as u,Strategy as p}from"@jackwener/opencli/registry";import{EmptyResultError as l}from"@jackwener/opencli/errors";import{STEAM_STORE as s,decodeHtmlEntities as d,priceCents as h,requireBoundedInt as y,requireCountryCode as f,requireString as w,steamFetch as S}from"./utils.js";function g(r){if(!r||typeof r!=="object")return"";return["windows","mac","linux"].filter((t)=>r[t]).join(",")}u({site:"steam",name:"search",access:"read",description:"Search the Steam storefront by name keyword",domain:"store.steampowered.com",strategy:p.PUBLIC,browser:!1,args:[{name:"query",positional:!0,required:!0,help:'Search keyword (e.g. "portal", "stardew")'},{name:"limit",type:"int",default:20,help:"Max results (1-50)"},{name:"currency",default:"us",help:"Storefront country code (e.g. us / cn / jp / de)"}],columns:["rank","id","name","price","currency","metascore","platforms","url"],func:async(r)=>{const t=w(r.query,"query"),n=y(r.limit,20,50),c=f(r.currency),i=`${s}/api/storesearch/?term=${encodeURIComponent(t)}&l=en&cc=${encodeURIComponent(c)}`,o=await S(i,"steam search"),a=Array.isArray(o?.items)?o.items:[];if(!a.length)throw new l("steam search",`No Steam results matched "${t}".`);return a.slice(0,n).map((e,m)=>({rank:m+1,id:String(e.id??""),name:d(e.name??""),price:h(e?.price?.final??null),currency:String(e?.price?.currency??"").toUpperCase(),metascore:e.metascore!=null&&e.metascore!==""?Number(e.metascore):null,platforms:g(e.platforms),url:e.id?`${s}/app/${e.id}/`:""}))}});
1
+ import{cli as m,Strategy as l}from"@jackwener/opencli/registry";import{EmptyResultError as p}from"@jackwener/opencli/errors";import{STEAM_STORE as c,decodeHtmlEntities as d,priceCents as y,requireBoundedInt as f,requireCountryCode as h,requireString as S,steamFetch as w}from"./utils.js";function q(r){if(!r||typeof r!=="object")return"";return["windows","mac","linux"].filter((t)=>r[t]).join(",")}m({site:"steam",name:"search",access:"read",description:"Search the Steam storefront by name keyword",domain:"store.steampowered.com",strategy:l.PUBLIC,browser:!1,args:[{name:"query",positional:!0,required:!0,help:'Search keyword (e.g. "portal", "stardew")'},{name:"limit",type:"int",default:20,help:"Max results (1-50)"},{name:"currency",default:"us",help:"Storefront country code (e.g. us / cn / jp / de)"}],columns:["rank","id","name","price","currency","metascore","platforms","url"],func:async(r)=>{const t=S(r.query,"query"),a=f(r.limit,20,50),s=h(r.currency),i=`${c}/api/storesearch/?term=${encodeURIComponent(t)}&l=en&cc=${encodeURIComponent(s)}`,n=await w(i,"steam search"),o=Array.isArray(n?.items)?n.items:[];if(!o.length)throw new p("steam search",`No Steam results matched "${t}".`);return o.slice(0,a).map((e,u)=>({rank:u+1,id:String(e.id??""),name:d(e.name??""),price:y(e?.price?.final??null),currency:String(e?.price?.currency??"").toUpperCase(),metascore:e.metascore!=null&&e.metascore!==""?Number(e.metascore):null,platforms:q(e.platforms),url:e.id?`${c}/app/${e.id}/`:""}))}});
@@ -1 +1 @@
1
- import{ArgumentError as s,CommandExecutionError as a,EmptyResultError as u}from"@jackwener/opencli/errors";export const STEAM_STORE="https://store.steampowered.com";const c="opencli-steam-adapter (+https://github.com/jackwener/OpenCLI)";export function requireString(e,t){const r=String(e??"").trim();if(!r)throw new s(`steam ${t} cannot be empty`);return r}export function requireCountryCode(e,t="us"){const o=String(e===void 0||e===null?t:e).trim().toLowerCase();if(!/^[a-z]{2}$/.test(o))throw new s(`steam currency must be a two-letter storefront country code (got "${e}")`,"Examples: us, cn, jp, de. This controls Steam regional pricing and availability.");return o}export function requireBoundedInt(e,t,r,o="limit"){const n=e??t,i=typeof n==="number"?n:Number(n);if(!Number.isInteger(i)||i<=0)throw new s(`steam ${o} must be a positive integer`);if(i>r)throw new s(`steam ${o} must be <= ${r}`);return i}export function requireAppId(e){const t=String(e??"").trim();if(!t)throw new s('steam app id is required (e.g. "620" for Portal 2)');if(!/^\d+$/.test(t))throw new s(`steam app id "${e}" must be a positive integer`,"Copy the numeric id from `steam search` or the URL `store.steampowered.com/app/<id>/`.");return t}export async function steamFetch(e,t){let r;try{r=await fetch(e,{headers:{"user-agent":c,accept:"application/json"}})}catch(n){throw new a(`${t} request failed: ${n?.message??n}`,"Check that store.steampowered.com is reachable from this network.")}if(r.status===429)throw new a(`${t} returned HTTP 429 (rate limited)`,"Steam throttles bursty traffic; wait a few seconds and retry.");if(r.status===404)throw new u(t,"Steam returned 404 — the resource does not exist.");if(!r.ok)throw new a(`${t} returned HTTP ${r.status}`);let o;try{o=await r.json()}catch(n){throw new a(`${t} returned malformed JSON: ${n?.message??n}`)}return o}export function asString(e){return e==null?"":String(e)}const m={"&amp;":"&","&lt;":"<","&gt;":">","&quot;":'"',"&apos;":"'","&#39;":"'","&nbsp;":" "};export function decodeHtmlEntities(e){return String(e??"").replace(/&#x([0-9a-fA-F]+);/g,(t,r)=>String.fromCodePoint(parseInt(r,16))).replace(/&#(\d+);/g,(t,r)=>String.fromCodePoint(parseInt(r,10))).replace(/&(amp|lt|gt|quot|apos|#39|nbsp);/g,(t)=>m[t]||t)}export function priceCents(e){if(e==null||e==="")return null;const t=Number(e);if(!Number.isFinite(t))return null;return Number((t/100).toFixed(2))}
1
+ import{ArgumentError as i,CommandExecutionError as a,EmptyResultError as u}from"@jackwener/opencli/errors";export const STEAM_STORE="https://store.steampowered.com";const c="opencli-steam-adapter (+https://github.com/jackwener/OpenCLI)";export function requireString(e,t){const r=String(e??"").trim();if(!r)throw new i(`steam ${t} cannot be empty`);return r}export function requireCountryCode(e,t="us"){const o=String(e===void 0||e===null?t:e).trim().toLowerCase();if(!/^[a-z]{2}$/.test(o))throw new i(`steam currency must be a two-letter storefront country code (got "${e}")`,"Examples: us, cn, jp, de. This controls Steam regional pricing and availability.");return o}export function requireBoundedInt(e,t,r,o="limit"){const n=e??t,s=typeof n==="number"?n:Number(n);if(!Number.isInteger(s)||s<=0)throw new i(`steam ${o} must be a positive integer`);if(s>r)throw new i(`steam ${o} must be <= ${r}`);return s}export function requireAppId(e){const t=String(e??"").trim();if(!t)throw new i('steam app id is required (e.g. "620" for Portal 2)');if(!/^\d+$/.test(t))throw new i(`steam app id "${e}" must be a positive integer`,"Copy the numeric id from `steam search` or the URL `store.steampowered.com/app/<id>/`.");return t}export async function steamFetch(e,t){let r;try{r=await fetch(e,{headers:{"user-agent":c,accept:"application/json"}})}catch(n){throw new a(`${t} request failed: ${n?.message??n}`,"Check that store.steampowered.com is reachable from this network.")}if(r.status===429)throw new a(`${t} returned HTTP 429 (rate limited)`,"Steam throttles bursty traffic; wait a few seconds and retry.");if(r.status===404)throw new u(t,"Steam returned 404 — the resource does not exist.");if(!r.ok)throw new a(`${t} returned HTTP ${r.status}`);let o;try{o=await r.json()}catch(n){throw new a(`${t} returned malformed JSON: ${n?.message??n}`)}return o}export function asString(e){return e==null?"":String(e)}const m={"&amp;":"&","&lt;":"<","&gt;":">","&quot;":'"',"&apos;":"'","&#39;":"'","&nbsp;":" "};export function decodeHtmlEntities(e){return String(e??"").replace(/&#x([0-9a-fA-F]+);/g,(t,r)=>String.fromCodePoint(parseInt(r,16))).replace(/&#(\d+);/g,(t,r)=>String.fromCodePoint(parseInt(r,10))).replace(/&(amp|lt|gt|quot|apos|#39|nbsp);/g,(t)=>m[t]||t)}export function priceCents(e){if(e==null||e==="")return null;const t=Number(e);if(!Number.isFinite(t))return null;return Number((t/100).toFixed(2))}
@@ -1,4 +1,4 @@
1
- import{AuthRequiredError as i,CommandExecutionError as o}from"@jackwener/opencli/errors";import{registerSiteAuthCommands as n}from"../_shared/site-auth.js";import{publicationContext as c}from"./post-utils.js";async function a(s){return(await s.getCookies({url:"https://substack.com"})).some((e)=>e.name==="substack.sid"||e.name==="connect.sid")}async function r(s){if(!await a(s))throw new i("substack.com","Substack session cookie missing");await s.goto("https://substack.com/");const t=await s.evaluate(`(async () => {
1
+ import{AuthRequiredError as i,CommandExecutionError as a}from"@jackwener/opencli/errors";import{registerSiteAuthCommands as n}from"../_shared/site-auth.js";import{publicationContext as c}from"./post-utils.js";async function o(e){return(await e.getCookies({url:"https://substack.com"})).some((s)=>s.name==="substack.sid"||s.name==="connect.sid")}async function r(e){if(!await o(e))throw new i("substack.com","Substack session cookie missing");await e.goto("https://substack.com/");const t=await e.evaluate(`(async () => {
2
2
  try {
3
3
  const res = await fetch('/api/v1/user/profile/self', { credentials: 'include', headers: { 'Accept': 'application/json' } });
4
4
  if (res.status === 401 || res.status === 403) return { kind: 'auth', detail: 'profile/self HTTP ' + res.status };
@@ -8,4 +8,4 @@ import{AuthRequiredError as i,CommandExecutionError as o}from"@jackwener/opencli
8
8
  if (!data || data.id == null) return { kind: 'auth', detail: 'profile/self returned no user — anonymous' };
9
9
  return { ok: true, profile: data };
10
10
  } catch (e) { return { kind: 'exception', detail: String(e && e.message || e) }; }
11
- })()`);if(t?.kind==="auth")throw new i("substack.com",t.detail);if(t?.kind==="http")throw new o(`HTTP ${t.httpStatus} from profile/self`);if(t?.kind==="exception")throw new o(`Substack whoami failed: ${t.detail}`);if(!t?.ok)throw new o(`Unexpected Substack probe: ${JSON.stringify(t)}`);const e=c(t.profile);return{user_id:String(e.userId),publication:e.publication,url:e.host}}n({site:"substack",domain:"substack.com",loginUrl:"https://substack.com/sign-in",columns:["user_id","publication","url"],quickCheck:a,verify:r,poll:async(s)=>{if(!await a(s))throw new i("substack.com","Waiting for Substack session cookie");return r(s)}});
11
+ })()`);if(t?.kind==="auth")throw new i("substack.com",t.detail);if(t?.kind==="http")throw new a(`HTTP ${t.httpStatus} from profile/self`);if(t?.kind==="exception")throw new a(`Substack whoami failed: ${t.detail}`);if(!t?.ok)throw new a(`Unexpected Substack probe: ${JSON.stringify(t)}`);const s=c(t.profile);return{user_id:String(s.userId),publication:s.publication,url:s.host}}n({site:"substack",domain:"substack.com",loginUrl:"https://substack.com/sign-in",columns:["user_id","publication","url"],quickCheck:o,verify:r,poll:async(e)=>{if(!await o(e))throw new i("substack.com","Waiting for Substack session cookie");return r(e)}});
@@ -1,2 +1,2 @@
1
- import{AuthRequiredError as P,ArgumentError as M,CommandExecutionError as Q}from"@jackwener/opencli/errors";const O=new Set(["everyone","only_paid"]);export function publicationContext(z){if(!z||z.id==null)throw new P("substack.com","Not logged in to Substack (profile has no id)");const F=Array.isArray(z.publicationUsers)?z.publicationUsers:[],H=F.find((K)=>K&&K.is_primary)||F[0],G=H&&H.publication;if(!G||!G.subdomain)throw new Q("Substack account has no publication to publish to");return{host:G.custom_domain&&!G.custom_domain_optional?`https://${G.custom_domain}`:`https://${G.subdomain}.substack.com`,userId:z.id,publication:G.name||G.subdomain,subdomain:G.subdomain}}export function validateAudience(z){const F=String(z??"everyone");if(!O.has(F))throw new M(`Invalid --audience "${F}" (allowed: everyone, only_paid)`);return F}export function imageDataUri(z,F){return`data:image/${F||"jpeg"};base64,${z}`}export function textToParagraphs(z){const F=String(z??"").replace(/\r\n/g,`
2
- `).trim();if(!F)return[];return F.split(/\n{2,}/).map((H)=>H.trim()).filter(Boolean)}export function paragraphNode(z){return{type:"paragraph",content:String(z??"").length?[{type:"text",text:String(z)}]:[]}}export function imageBlock(z,{width:F=1456,height:H=819}={}){return{type:"captionedImage",content:[{type:"image2",attrs:{src:z,fullscreen:!1,imageSize:"normal",height:H,width:F,resizeWidth:Math.round(F/2),bytes:null,alt:null,title:null,type:null,href:null,belowTheFold:!1,internalRedirect:null}}]}}export function buildDraftBody(z){return{type:"doc",content:Array.isArray(z)?z:[]}}export function buildDraftPayload({title:z,subtitle:F="",bodyDoc:H,userId:G,audience:J="everyone",sectionId:K=null}){return{draft_title:String(z??""),draft_subtitle:String(F??""),draft_body:JSON.stringify(H??buildDraftBody([])),draft_bylines:[{id:G,is_guest:!1}],audience:J,draft_section_id:K,section_chosen:K!=null,write_comment_permissions:"everyone"}}export function collectSections(z){const F=z&&Array.isArray(z.publications)?z.publications:[],H=[];for(const G of F)for(const J of Array.isArray(G?.sections)?G.sections:[])if(J&&J.id!=null&&J.name)H.push({id:J.id,name:String(J.name)});return H}export function resolveSectionId(z,F){const H=String(F??"").trim().toLowerCase(),G=(z||[]).find((J)=>String(J.name).toLowerCase()===H);if(!G){const J=(z||[]).map((K)=>K.name).join(", ")||"(none)";throw new M(`Substack section "${F}" not found. Available: ${J}`)}return G.id}export function classifyImages(z){if(!z)return[];return String(z).split(",").map((F)=>F.trim()).filter(Boolean).map((F)=>({kind:/^https?:\/\//i.test(F)?"url":"path",value:F}))}export const __test__={VALID_AUDIENCES:O};
1
+ import{AuthRequiredError as u,ArgumentError as a,CommandExecutionError as c}from"@jackwener/opencli/errors";const s=new Set(["everyone","only_paid"]);export function publicationContext(t){if(!t||t.id==null)throw new u("substack.com","Not logged in to Substack (profile has no id)");const n=Array.isArray(t.publicationUsers)?t.publicationUsers:[],o=n.find((i)=>i&&i.is_primary)||n[0],e=o&&o.publication;if(!e||!e.subdomain)throw new c("Substack account has no publication to publish to");return{host:e.custom_domain&&!e.custom_domain_optional?`https://${e.custom_domain}`:`https://${e.subdomain}.substack.com`,userId:t.id,publication:e.name||e.subdomain,subdomain:e.subdomain}}export function validateAudience(t){const n=String(t??"everyone");if(!s.has(n))throw new a(`Invalid --audience "${n}" (allowed: everyone, only_paid)`);return n}export function imageDataUri(t,n){return`data:image/${n||"jpeg"};base64,${t}`}export function textToParagraphs(t){const n=String(t??"").replace(/\r\n/g,`
2
+ `).trim();if(!n)return[];return n.split(/\n{2,}/).map((o)=>o.trim()).filter(Boolean)}export function paragraphNode(t){return{type:"paragraph",content:String(t??"").length?[{type:"text",text:String(t)}]:[]}}export function imageBlock(t,{width:n=1456,height:o=819}={}){return{type:"captionedImage",content:[{type:"image2",attrs:{src:t,fullscreen:!1,imageSize:"normal",height:o,width:n,resizeWidth:Math.round(n/2),bytes:null,alt:null,title:null,type:null,href:null,belowTheFold:!1,internalRedirect:null}}]}}export function buildDraftBody(t){return{type:"doc",content:Array.isArray(t)?t:[]}}export function buildDraftPayload({title:t,subtitle:n="",bodyDoc:o,userId:e,audience:r="everyone",sectionId:i=null}){return{draft_title:String(t??""),draft_subtitle:String(n??""),draft_body:JSON.stringify(o??buildDraftBody([])),draft_bylines:[{id:e,is_guest:!1}],audience:r,draft_section_id:i,section_chosen:i!=null,write_comment_permissions:"everyone"}}export function collectSections(t){const n=t&&Array.isArray(t.publications)?t.publications:[],o=[];for(const e of n)for(const r of Array.isArray(e?.sections)?e.sections:[])if(r&&r.id!=null&&r.name)o.push({id:r.id,name:String(r.name)});return o}export function resolveSectionId(t,n){const o=String(n??"").trim().toLowerCase(),e=(t||[]).find((r)=>String(r.name).toLowerCase()===o);if(!e){const r=(t||[]).map((i)=>i.name).join(", ")||"(none)";throw new a(`Substack section "${n}" not found. Available: ${r}`)}return e.id}export function classifyImages(t){if(!t)return[];return String(t).split(",").map((n)=>n.trim()).filter(Boolean).map((n)=>({kind:/^https?:\/\//i.test(n)?"url":"path",value:n}))}export const __test__={VALID_AUDIENCES:s};
@@ -1,10 +1,10 @@
1
- import*as T from"node:fs";import*as A from"node:path";import{cli as f,Strategy as E}from"@jackwener/opencli/registry";import{AuthRequiredError as w,ArgumentError as N,CommandExecutionError as $}from"@jackwener/opencli/errors";import{publicationContext as b,validateAudience as k,imageDataUri as h,textToParagraphs as m,paragraphNode as D,imageBlock as y,buildDraftBody as u,buildDraftPayload as n,collectSections as c,resolveSectionId as i,classifyImages as l}from"./post-utils.js";const P={".jpg":"jpeg",".jpeg":"jpeg",".png":"png",".gif":"gif",".webp":"webp"};function d(z){if(z&&typeof z==="object"&&!Array.isArray(z)&&"session"in z&&"data"in z)return z.data;return z}async function V(z,{url:G,method:H="GET",headers:L={},body:O=null}){const J=`(async () => {
1
+ import*as x from"node:fs";import*as P from"node:path";import{cli as C,Strategy as A}from"@jackwener/opencli/registry";import{AuthRequiredError as J,ArgumentError as b,CommandExecutionError as d}from"@jackwener/opencli/errors";import{publicationContext as H,validateAudience as q,imageDataUri as B,textToParagraphs as D,paragraphNode as O,imageBlock as F,buildDraftBody as U,buildDraftPayload as M,collectSections as R,resolveSectionId as G,classifyImages as K}from"./post-utils.js";const g={".jpg":"jpeg",".jpeg":"jpeg",".png":"png",".gif":"gif",".webp":"webp"};function L(t){if(t&&typeof t==="object"&&!Array.isArray(t)&&"session"in t&&"data"in t)return t.data;return t}async function l(t,{url:e,method:s="GET",headers:o={},body:r=null}){const a=`(async () => {
2
2
  try {
3
- const opts = { method: ${JSON.stringify(H)}, credentials: 'include', headers: ${JSON.stringify({Accept:"application/json",...L})} };
4
- ${O!=null?`opts.body = ${JSON.stringify(O)};`:""}
5
- const res = await fetch(${JSON.stringify(G)}, opts);
3
+ const opts = { method: ${JSON.stringify(s)}, credentials: 'include', headers: ${JSON.stringify({Accept:"application/json",...o})} };
4
+ ${r!=null?`opts.body = ${JSON.stringify(r)};`:""}
5
+ const res = await fetch(${JSON.stringify(e)}, opts);
6
6
  const text = await res.text();
7
7
  let data = null; try { data = JSON.parse(text); } catch {}
8
8
  return { ok: res.ok, status: res.status, data, text: text.slice(0, 300) };
9
9
  } catch (e) { return { ok: false, status: 0, data: null, text: String(e && e.message || e) }; }
10
- })()`;return d(await z.evaluate(J))}function F(z){return l(z).map((G)=>{if(G.kind==="url")return G;const H=A.resolve(G.value),L=A.extname(H).toLowerCase();if(!P[L])throw new N(`Unsupported image "${L}" (jpg/png/gif/webp): ${G.value}`);const O=T.statSync(H,{throwIfNoEntry:!1});if(!O||!O.isFile())throw new N(`Not a file: ${H}`);return{kind:"path",value:H,mime:P[L]}})}f({site:"substack",name:"publish",access:"write",description:"Publish a post to your Substack publication (or save a draft with --draft)",domain:"substack.com",strategy:E.COOKIE,browser:!0,args:[{name:"title",type:"string",required:!0,positional:!0,help:"Post title"},{name:"body",fromFile:!0,type:"string",help:"Post body text (blank lines split paragraphs)"},{name:"subtitle",type:"string",help:"Optional subtitle"},{name:"images",type:"string",help:"Images: comma-separated local paths and/or http(s) urls"},{name:"audience",type:"string",default:"everyone",help:"Visibility: everyone | only_paid"},{name:"section",type:"string",help:"Section name to file the post under"},{name:"send",type:"boolean",default:!1,help:"Also email subscribers on publish"},{name:"draft",type:"boolean",default:!1,help:"Create the draft but do not publish"}],columns:["status","publication","draft_id","url"],func:async(z,G)=>{if(!z)throw new $("Browser session required for substack publish");const H=String(G.title??"").trim();if(!H)throw new N("substack publish: title is required");const L=k(G.audience),O=F(G.images);await z.goto("https://substack.com/");const J=await V(z,{url:"https://substack.com/api/v1/user/profile/self"});if(J.status===401||J.status===403||!J.data||J.data.id==null)throw new w("substack.com",`Not logged in (profile/self HTTP ${J.status})`);const{host:K,userId:M,publication:S}=b(J.data);await z.goto(`${K}/`);const U=[];for(const Q of O){let B=Q.value;if(Q.kind==="path"){const I=T.readFileSync(Q.value).toString("base64"),Z=await V(z,{url:`${K}/api/v1/image`,method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`image=${encodeURIComponent(h(I,Q.mime))}`});if(!Z.ok||!Z.data?.url)throw new $(`Substack image upload failed: HTTP ${Z.status} ${Z.text}`);B=Z.data.url}U.push(y(B))}const R=[...m(G.body).map(D),...U];if(!R.length)R.push(D(""));const C=u(R);let X=null;if(G.section){const Q=await V(z,{url:`${K}/api/v1/subscriptions`});X=i(c(Q.data),G.section)}const x=n({title:H,subtitle:G.subtitle,bodyDoc:C,userId:M,audience:L,sectionId:X}),W=await V(z,{url:`${K}/api/v1/drafts`,method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(x)});if(!W.ok||W.data?.id==null)throw new $(`Substack create-draft failed: HTTP ${W.status} ${W.text}`);const Y=W.data.id,_=`${K}/publish/post/${Y}`;if(G.draft)return{status:"draft",publication:S,draft_id:String(Y),url:_};const j=await V(z,{url:`${K}/api/v1/drafts/${Y}/prepublish`});if(!j.ok)throw new $(`Substack prepublish failed: HTTP ${j.status} ${j.text}`);const q=await V(z,{url:`${K}/api/v1/drafts/${Y}/publish`,method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({send:!!G.send,share_automatically:!1})});if(!q.ok)throw new $(`Substack publish failed: HTTP ${q.status} ${q.text}`);const v=q.data?.slug;return{status:"published",publication:S,draft_id:String(Y),url:v?`${K}/p/${v}`:_}}});export const __test__={resolveImageEntries:F,EXT_MIME:P};
10
+ })()`;return L(await t.evaluate(a))}function I(t){return K(t).map((e)=>{if(e.kind==="url")return e;const s=P.resolve(e.value),o=P.extname(s).toLowerCase();if(!g[o])throw new b(`Unsupported image "${o}" (jpg/png/gif/webp): ${e.value}`);const r=x.statSync(s,{throwIfNoEntry:!1});if(!r||!r.isFile())throw new b(`Not a file: ${s}`);return{kind:"path",value:s,mime:g[o]}})}C({site:"substack",name:"publish",access:"write",description:"Publish a post to your Substack publication (or save a draft with --draft)",domain:"substack.com",strategy:A.COOKIE,browser:!0,args:[{name:"title",type:"string",required:!0,positional:!0,help:"Post title"},{name:"body",fromFile:!0,type:"string",help:"Post body text (blank lines split paragraphs)"},{name:"subtitle",type:"string",help:"Optional subtitle"},{name:"images",type:"string",help:"Images: comma-separated local paths and/or http(s) urls"},{name:"audience",type:"string",default:"everyone",help:"Visibility: everyone | only_paid"},{name:"section",type:"string",help:"Section name to file the post under"},{name:"send",type:"boolean",default:!1,help:"Also email subscribers on publish"},{name:"draft",type:"boolean",default:!1,help:"Create the draft but do not publish"}],columns:["status","publication","draft_id","url"],func:async(t,e)=>{if(!t)throw new d("Browser session required for substack publish");const s=String(e.title??"").trim();if(!s)throw new b("substack publish: title is required");const o=q(e.audience),r=I(e.images);await t.goto("https://substack.com/");const a=await l(t,{url:"https://substack.com/api/v1/user/profile/self"});if(a.status===401||a.status===403||!a.data||a.data.id==null)throw new J("substack.com",`Not logged in (profile/self HTTP ${a.status})`);const{host:i,userId:N,publication:y}=H(a.data);await t.goto(`${i}/`);const w=[];for(const n of r){let k=n.value;if(n.kind==="path"){const j=x.readFileSync(n.value).toString("base64"),p=await l(t,{url:`${i}/api/v1/image`,method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`image=${encodeURIComponent(B(j,n.mime))}`});if(!p.ok||!p.data?.url)throw new d(`Substack image upload failed: HTTP ${p.status} ${p.text}`);k=p.data.url}w.push(F(k))}const m=[...D(e.body).map(O),...w];if(!m.length)m.push(O(""));const E=U(m);let S=null;if(e.section){const n=await l(t,{url:`${i}/api/v1/subscriptions`});S=G(R(n.data),e.section)}const _=M({title:s,subtitle:e.subtitle,bodyDoc:E,userId:N,audience:o,sectionId:S}),u=await l(t,{url:`${i}/api/v1/drafts`,method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(_)});if(!u.ok||u.data?.id==null)throw new d(`Substack create-draft failed: HTTP ${u.status} ${u.text}`);const c=u.data.id,v=`${i}/publish/post/${c}`;if(e.draft)return{status:"draft",publication:y,draft_id:String(c),url:v};const h=await l(t,{url:`${i}/api/v1/drafts/${c}/prepublish`});if(!h.ok)throw new d(`Substack prepublish failed: HTTP ${h.status} ${h.text}`);const f=await l(t,{url:`${i}/api/v1/drafts/${c}/publish`,method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({send:!!e.send,share_automatically:!1})});if(!f.ok)throw new d(`Substack publish failed: HTTP ${f.status} ${f.text}`);const T=f.data?.slug;return{status:"published",publication:y,draft_id:String(c),url:T?`${i}/p/${T}`:v}}});export const __test__={resolveImageEntries:I,EXT_MIME:g};
package/clis/suno/auth.js CHANGED
@@ -1,4 +1,4 @@
1
- import{AuthRequiredError as D,CommandExecutionError as F}from"@jackwener/opencli/errors";import{registerSiteAuthCommands as K}from"../_shared/site-auth.js";async function G(B){return(await B.getCookies({url:"https://clerk.suno.com"})).some((H)=>H.name==="__session"&&H.value)}async function J(B){if(!await G(B))throw new D("suno.com","Suno Clerk __session/__client cookie missing");await B.goto("https://suno.com/");await B.wait(2);const z=await B.evaluate(`(async () => {
1
+ import{AuthRequiredError as o,CommandExecutionError as n}from"@jackwener/opencli/errors";import{registerSiteAuthCommands as u}from"../_shared/site-auth.js";async function s(i){return(await i.getCookies({url:"https://clerk.suno.com"})).some((r)=>r.name==="__session"&&r.value)}async function t(i){if(!await s(i))throw new o("suno.com","Suno Clerk __session/__client cookie missing");await i.goto("https://suno.com/");await i.wait(2);const e=await i.evaluate(`(async () => {
2
2
  try {
3
3
  for (let i = 0; i < 20; i += 1) {
4
4
  if (window.Clerk && window.Clerk.loaded) break;
@@ -18,4 +18,4 @@ import{AuthRequiredError as D,CommandExecutionError as F}from"@jackwener/opencli
18
18
  } catch (e) {
19
19
  return { kind: 'exception', detail: String(e && e.message || e) };
20
20
  }
21
- })()`);if(z?.kind==="auth")throw new D("suno.com",z.detail);if(z?.kind==="http")throw new F(`HTTP ${z.httpStatus} from clerk.suno.com`);if(z?.kind==="exception")throw new F(`Suno whoami failed: ${z.detail}`);if(!z?.ok)throw new F(`Unexpected Suno probe: ${JSON.stringify(z)}`);return{user_id:z.user_id,name:z.name}}K({site:"suno",domain:"suno.com",loginUrl:"https://suno.com/?sign-in=true",columns:["user_id","name"],quickCheck:G,verify:J,poll:async(B)=>{if(!await G(B))throw new D("suno.com","Waiting for Suno Clerk __session/__client cookie");return J(B)}});
21
+ })()`);if(e?.kind==="auth")throw new o("suno.com",e.detail);if(e?.kind==="http")throw new n(`HTTP ${e.httpStatus} from clerk.suno.com`);if(e?.kind==="exception")throw new n(`Suno whoami failed: ${e.detail}`);if(!e?.ok)throw new n(`Unexpected Suno probe: ${JSON.stringify(e)}`);return{user_id:e.user_id,name:e.name}}u({site:"suno",domain:"suno.com",loginUrl:"https://suno.com/?sign-in=true",columns:["user_id","name"],quickCheck:s,verify:t,poll:async(i)=>{if(!await s(i))throw new o("suno.com","Waiting for Suno Clerk __session/__client cookie");return t(i)}});
@@ -1,17 +1,17 @@
1
- import{cli as N,Strategy as D}from"@jackwener/opencli/registry";import{ArgumentError as b,CommandExecutionError as Y,EmptyResultError as C}from"@jackwener/opencli/errors";import{STUDIO_API as U,SUNO_DOMAIN as v,SUNO_URL as B,downloadSunoClip as y,ensureSunoSession as A,normalizeBooleanFlag as O,parseFormats as _,resolveSunoOutputDir as R,unwrapEvaluateResult as I}from"./utils.js";import*as S from"node:os";function E(J){if(!J)return"-";const H=S.homedir();return J.startsWith(H)?`~${J.slice(H.length)}`:J}function k(J){const H=String(J||"").trim();if(!H)throw new b("clip-id required (UUID or /song/<id> URL)");if(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(H))return H.toLowerCase();try{const V=new URL(H),W=V.pathname.match(/^\/song\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\/?$/i);if(V.protocol==="https:"&&V.hostname==="suno.com"&&W)return W[1].toLowerCase()}catch{}throw new b(`Invalid clip-id: ${H}`,"Pass a UUID or a https://suno.com/song/<uuid> URL.")}export const downloadCommand=N({site:"suno",name:"download",access:"write",description:"Download an existing Suno clip (MP3 + optional WAV/M4A/video) by id",domain:v,strategy:D.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,defaultFormat:"plain",args:[{name:"clip",positional:!0,required:!0,help:"Clip UUID or https://suno.com/song/<id> URL"},{name:"formats",help:"Comma-separated formats: mp3, m4a, wav, video, cover, metadata. Default: mp3,metadata"},{name:"op",help:"Output directory (default: ~/Music/suno)"},{name:"confirm-paid",type:"boolean",default:!1,help:"Required to allow paid downloads (wav). Without it, paid formats are skipped with a warning."}],columns:["status","clip","title","files","link"],func:async(J,H)=>{const K=k(H.clip),V=_(H.formats),W=O(H["confirm-paid"]),L=new Set(["wav"]),Z=[],q=V.filter((G)=>{if(L.has(G)&&!W){Z.push(G);return!1}return!0});if(!q.length)throw new b("All requested formats require --confirm-paid true","Add --confirm-paid true or include a free format such as mp3 or metadata.");const T=R(H.op),z=(await A(J)).deviceId,X=I(await J.evaluate(`(async () => {
1
+ import{cli as v,Strategy as A}from"@jackwener/opencli/registry";import{ArgumentError as u,CommandExecutionError as l,EmptyResultError as C}from"@jackwener/opencli/errors";import{STUDIO_API as O,SUNO_DOMAIN as U,SUNO_URL as f,downloadSunoClip as I,ensureSunoSession as D,normalizeBooleanFlag as R,parseFormats as T,resolveSunoOutputDir as b,unwrapEvaluateResult as P}from"./utils.js";import*as E from"node:os";function F(e){if(!e)return"-";const t=E.homedir();return e.startsWith(t)?`~${e.slice(t.length)}`:e}function L(e){const t=String(e||"").trim();if(!t)throw new u("clip-id required (UUID or /song/<id> URL)");if(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(t))return t.toLowerCase();try{const i=new URL(t),n=i.pathname.match(/^\/song\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\/?$/i);if(i.protocol==="https:"&&i.hostname==="suno.com"&&n)return n[1].toLowerCase()}catch{}throw new u(`Invalid clip-id: ${t}`,"Pass a UUID or a https://suno.com/song/<uuid> URL.")}export const downloadCommand=v({site:"suno",name:"download",access:"write",description:"Download an existing Suno clip (MP3 + optional WAV/M4A/video) by id",domain:U,strategy:A.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,defaultFormat:"plain",args:[{name:"clip",positional:!0,required:!0,help:"Clip UUID or https://suno.com/song/<id> URL"},{name:"formats",help:"Comma-separated formats: mp3, m4a, wav, video, cover, metadata. Default: mp3,metadata"},{name:"op",help:"Output directory (default: ~/Music/suno)"},{name:"confirm-paid",type:"boolean",default:!1,help:"Required to allow paid downloads (wav). Without it, paid formats are skipped with a warning."}],columns:["status","clip","title","files","link"],func:async(e,t)=>{const a=L(t.clip),i=T(t.formats),n=R(t["confirm-paid"]),w=new Set(["wav"]),d=[],c=i.filter((o)=>{if(w.has(o)&&!n){d.push(o);return!1}return!0});if(!c.length)throw new u("All requested formats require --confirm-paid true","Add --confirm-paid true or include a free format such as mp3 or metadata.");const h=b(t.op),m=(await D(e)).deviceId,s=P(await e.evaluate(`(async () => {
2
2
  const browserToken = JSON.stringify({ token: btoa(JSON.stringify({ timestamp: Date.now() })) });
3
- const res = await fetch('${U}/api/feed/v3', {
3
+ const res = await fetch('${O}/api/feed/v3', {
4
4
  method: 'POST',
5
5
  headers: {
6
6
  'Authorization': 'Bearer ' + (await window.Clerk.session.getToken()),
7
7
  'browser-token': browserToken,
8
- 'device-id': ${JSON.stringify(z)},
8
+ 'device-id': ${JSON.stringify(m)},
9
9
  'Content-Type': 'application/json',
10
10
  },
11
- body: JSON.stringify({ clip_ids: ['${K}'] }),
11
+ body: JSON.stringify({ clip_ids: ['${a}'] }),
12
12
  });
13
13
  if (!res.ok) return { ok: false, error: 'HTTP ' + res.status };
14
14
  const payload = await res.json().catch(() => null);
15
15
  if (!payload || !Array.isArray(payload.clips)) return { ok: false, error: 'malformed clips payload' };
16
16
  return { ok: true, clips: payload.clips };
17
- })()`));if(!X?.ok)throw new Y(`Suno feed lookup failed: ${X?.error||"unknown"}`);if(!Array.isArray(X.clips))throw new Y("Suno feed lookup returned malformed clips payload");const Q=X.clips.find((G)=>G.id===K);if(!Q)throw new C("suno download",`Clip ${K} not found in your account. Confirm at ${B}/song/${K}.`);if(Q.status!=="complete")throw new Y(`Clip ${K} status is "${Q.status}" — not complete yet. Retry once generation finishes.`);const $=await y(J,Q,T,q,z);if(!$.written.some((G)=>G.ok))throw new Y(`Suno download wrote no files for clip ${K}`);const j=`${B}/song/${Q.id}`,x=$.written.map((G)=>G.ok?`${G.format}:${E(G.file)}`:`${G.format}:✗(${G.reason})`).join(" | "),F=Z.length?` | skipped(needs --confirm-paid):${Z.join(",")}`:"",M=`${x}${F}`;return[{status:$.written.some((G)=>!G.ok)?"⚠ partial":"✅ saved",clip:Q.id.slice(0,8),title:Q.title||"(untitled)",files:`\uD83D\uDCC1 ${M}`,link:`\uD83D\uDD17 ${j}`}]}});
17
+ })()`));if(!s?.ok)throw new l(`Suno feed lookup failed: ${s?.error||"unknown"}`);if(!Array.isArray(s.clips))throw new l("Suno feed lookup returned malformed clips payload");const r=s.clips.find((o)=>o.id===a);if(!r)throw new C("suno download",`Clip ${a} not found in your account. Confirm at ${f}/song/${a}.`);if(r.status!=="complete")throw new l(`Clip ${a} status is "${r.status}" — not complete yet. Retry once generation finishes.`);const p=await I(e,r,h,c,m);if(!p.written.some((o)=>o.ok))throw new l(`Suno download wrote no files for clip ${a}`);const y=`${f}/song/${r.id}`,S=p.written.map((o)=>o.ok?`${o.format}:${F(o.file)}`:`${o.format}:✗(${o.reason})`).join(" | "),k=d.length?` | skipped(needs --confirm-paid):${d.join(",")}`:"",g=`${S}${k}`;return[{status:p.written.some((o)=>!o.ok)?"⚠ partial":"✅ saved",clip:r.id.slice(0,8),title:r.title||"(untitled)",files:`\uD83D\uDCC1 ${g}`,link:`\uD83D\uDD17 ${y}`}]}});
@@ -1,4 +1,4 @@
1
- import{cli as c,Strategy as n}from"@jackwener/opencli/registry";import{ArgumentError as z,CommandExecutionError as V}from"@jackwener/opencli/errors";import{DEFAULT_SUNO_MODEL as y,SUNO_DOMAIN as t,SUNO_MODELS as v,SUNO_URL as Z,checkSunoCaptcha as o,clampSlider as _,downloadSunoClip as i,ensureSunoSession as e,normalizeBooleanFlag as A,parseFormats as a,pollSunoClips as s,requirePositiveInt as r,resolveSunoOutputDir as HH,submitSunoGeneration as JH}from"./utils.js";import*as C from"node:crypto";import*as KH from"node:os";function QH(K){if(!K)return"-";const J=KH.homedir();return K.startsWith(J)?`~${K.slice(J.length)}`:K}export const generateCommand=c({site:"suno",name:"generate",access:"write",description:"Generate music with Suno (V5.5 chirp-fenix by default) and download clips locally",domain:t,strategy:n.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,defaultFormat:"plain",args:[{name:"prompt",fromFile:!0,positional:!0,required:!1,help:"Simple-mode description (ignored when --lyrics is provided)"},{name:"lyrics",help:"Custom-mode lyrics (with [Verse]/[Chorus] metatags). Triggers Custom mode."},{name:"tags",help:"Custom-mode style tags (genre, BPM, instruments...). Used with --lyrics."},{name:"negative-tags",help:'Custom-mode style exclusions (e.g. "no vocals, no autotune"). Used with --lyrics.'},{name:"title",help:"Song title (default: auto-derived from prompt)"},{name:"instrumental",type:"boolean",default:!1,help:"No vocals"},{name:"model",help:`Model id: ${v.join(", ")}. Default: ${y}`},{name:"weirdness",help:"Creative weirdness slider (0..1). Default: 0.5"},{name:"style-weight",help:"Style adherence slider (0..1). Default: 0.5"},{name:"formats",help:"Comma-separated download formats: mp3, m4a, wav, video, cover, metadata. Default: mp3,metadata"},{name:"op",help:"Output directory (default: ~/Music/suno)"},{name:"timeout",type:"int",default:300,help:"Max seconds to wait for clips to finish (default: 300)"},{name:"sd",type:"boolean",default:!1,help:"Skip download; only print clip ids and Suno URLs"},{name:"confirm-paid",type:"boolean",default:!1,help:"Required to allow paid downloads (wav). Without it, paid formats are skipped with a warning."}],columns:["status","clip","title","files","link"],func:async(K,J)=>{const B=J.lyrics?String(J.lyrics):"",M=J.tags?String(J.tags):"",D=J["negative-tags"]?String(J["negative-tags"]):"",R=J.prompt?String(J.prompt):"",P=J.title?String(J.title):"",T=J.model?String(J.model).trim():y;if(!v.includes(T))throw new z(`Unsupported --model "${T}"`,`Choices: ${v.join(", ")}`);const $=B.trim()!=="";if(!$&&!R.trim())throw new z("Either provide a Simple-mode prompt as the positional argument, or pass --lyrics for Custom mode.",`Examples:
1
+ import{cli as H,Strategy as K}from"@jackwener/opencli/registry";import{ArgumentError as u,CommandExecutionError as n}from"@jackwener/opencli/errors";import{DEFAULT_SUNO_MODEL as I,SUNO_DOMAIN as z,SUNO_MODELS as C,SUNO_URL as a,checkSunoCaptcha as J,clampSlider as T,downloadSunoClip as Q,ensureSunoSession as X,normalizeBooleanFlag as b,parseFormats as Y,pollSunoClips as Z,requirePositiveInt as ee,resolveSunoOutputDir as te,submitSunoGeneration as oe}from"./utils.js";import*as q from"node:crypto";import*as ie from"node:os";function ne(o){if(!o)return"-";const t=ie.homedir();return o.startsWith(t)?`~${o.slice(t.length)}`:o}export const generateCommand=H({site:"suno",name:"generate",access:"write",description:"Generate music with Suno (V5.5 chirp-fenix by default) and download clips locally",domain:z,strategy:K.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,defaultFormat:"plain",args:[{name:"prompt",fromFile:!0,positional:!0,required:!1,help:"Simple-mode description (ignored when --lyrics is provided)"},{name:"lyrics",help:"Custom-mode lyrics (with [Verse]/[Chorus] metatags). Triggers Custom mode."},{name:"tags",help:"Custom-mode style tags (genre, BPM, instruments...). Used with --lyrics."},{name:"negative-tags",help:'Custom-mode style exclusions (e.g. "no vocals, no autotune"). Used with --lyrics.'},{name:"title",help:"Song title (default: auto-derived from prompt)"},{name:"instrumental",type:"boolean",default:!1,help:"No vocals"},{name:"model",help:`Model id: ${C.join(", ")}. Default: ${I}`},{name:"weirdness",help:"Creative weirdness slider (0..1). Default: 0.5"},{name:"style-weight",help:"Style adherence slider (0..1). Default: 0.5"},{name:"formats",help:"Comma-separated download formats: mp3, m4a, wav, video, cover, metadata. Default: mp3,metadata"},{name:"op",help:"Output directory (default: ~/Music/suno)"},{name:"timeout",type:"int",default:300,help:"Max seconds to wait for clips to finish (default: 300)"},{name:"sd",type:"boolean",default:!1,help:"Skip download; only print clip ids and Suno URLs"},{name:"confirm-paid",type:"boolean",default:!1,help:"Required to allow paid downloads (wav). Without it, paid formats are skipped with a warning."}],columns:["status","clip","title","files","link"],func:async(o,t)=>{const f=t.lyrics?String(t.lyrics):"",h=t.tags?String(t.tags):"",U=t["negative-tags"]?String(t["negative-tags"]):"",y=t.prompt?String(t.prompt):"",E=t.title?String(t.title):"",w=t.model?String(t.model).trim():I;if(!C.includes(w))throw new u(`Unsupported --model "${w}"`,`Choices: ${C.join(", ")}`);const l=f.trim()!=="";if(!l&&!y.trim())throw new u("Either provide a Simple-mode prompt as the positional argument, or pass --lyrics for Custom mode.",`Examples:
2
2
  ppcli suno generate "lo-fi study beat, 80 bpm"
3
- ppcli suno generate --lyrics "[Verse]\\n..." --tags "synthwave, 120 bpm"`);if(!$&&(M||D))throw new z("--tags and --negative-tags only apply in Custom mode (alongside --lyrics).");const f=a(J.formats),h=A(J["confirm-paid"]),I=A(J.sd),O=new Set(["wav"]),b=[],L=f.filter((H)=>{if(O.has(H)&&!h){b.push(H);return!1}return!0});if(!I&&!L.length)throw new z("All requested formats require --confirm-paid true","Add --confirm-paid true or include a free format such as mp3 or metadata.");const U=HH(J.op),E=r(J.timeout,"--timeout"),S=A(J.instrumental),u=_(J.weirdness,"--weirdness",0.5),m=_(J["style-weight"],"--style-weight",0.5),d=(P||($?M||B.split(`
4
- `)[0]:R)).replace(/\s+/g," ").trim().slice(0,60)||"Untitled",X=await e(K);if(!X.planId)throw new V(`Suno generation needs a resolved plan id for the user_tier field, but billing/info did not surface one for this account (subscription_type=${X.planKey}). Verify the account is active at ${Z}/account, then retry.`);const G=X.deviceId,W=await o(K,G);if(!W?.ok)throw new V(`Suno captcha pre-flight failed${W?.status?` (HTTP ${W.status})`:""}.`,`Open ${Z}/create in Chrome and verify the account is ready, then retry.`);if(W?.required)throw new V("Suno requires a CAPTCHA challenge for this account/IP right now.",`Open ${Z}/create in Chrome, solve a Create challenge once, then retry.`);if(X.totalCreditsAvailable<10){const H=X.breakdown;throw new V(`Suno generation needs ~10 credits; you have ${X.totalCreditsAvailable} (monthly ${H.monthlyRemaining}/${H.monthlyLimit} + packs ${H.purchasedPacks} + leftover ${H.pack}). Top up at ${Z}/account.`)}const k=C.randomUUID(),p=C.randomUUID(),N=await JH(K,{mode:$?"custom":"simple",model:T,title:d,lyrics:B,tags:M,negativeTags:D,description:R,makeInstrumental:S,weirdness:u,styleWeight:m,userTier:X.planId,createSessionToken:p,transactionUuid:k,deviceId:G});if(!Array.isArray(N.clips))throw new V("Suno generation returned malformed clips payload.");const j=N.clips.map((H)=>H?.id);if(j.some((H)=>!H))throw new V("Suno generation returned malformed clip identity.");if(!j.length)throw new V("Suno accepted the request but returned no clip ids.");const x=await s(K,j,E,G);if(!x.filter((H)=>H.status==="complete").length){const H=x.map((Y)=>`${Y.id.slice(0,8)}:${Y.status}`).join(", ");throw new V(`All Suno clips failed (${H}). Open ${Z}/song/${j[0]} to inspect.`)}const q=[];for(const H of x){const Y=`${Z}/song/${H.id}`;if(H.status!=="complete"){q.push({status:`❌ ${H.status}`,clip:H.id.slice(0,8),title:H.title||"(untitled)",files:"-",link:`\uD83D\uDD17 ${Y}`});continue}if(I){q.push({status:"\uD83C\uDFB5 generated",clip:H.id.slice(0,8),title:H.title||"(untitled)",files:"\uD83D\uDCC1 -",link:`\uD83D\uDD17 ${Y}`});continue}const F=await i(K,H,U,L,G);if(!F.written.some((Q)=>Q.ok))throw new V(`Suno download wrote no files for clip ${H.id}`);const w=F.written.map((Q)=>Q.ok?`${Q.format}:${QH(Q.file)}`:`${Q.format}:✗(${Q.reason})`).join(" | "),g=b.length?` | skipped(needs --confirm-paid):${b.join(",")}`:"",l=F.written.some((Q)=>!Q.ok);q.push({status:l?"⚠ partial":"✅ saved",clip:H.id.slice(0,8),title:H.title||"(untitled)",files:`\uD83D\uDCC1 ${w}${g}`,link:`\uD83D\uDD17 ${Y}`})}return q}});
3
+ ppcli suno generate --lyrics "[Verse]\\n..." --tags "synthwave, 120 bpm"`);if(!l&&(h||U))throw new u("--tags and --negative-tags only apply in Custom mode (alongside --lyrics).");const F=Y(t.formats),M=b(t["confirm-paid"]),A=b(t.sd),_=new Set(["wav"]),g=[],O=F.filter((e)=>{if(_.has(e)&&!M){g.push(e);return!1}return!0});if(!A&&!O.length)throw new u("All requested formats require --confirm-paid true","Add --confirm-paid true or include a free format such as mp3 or metadata.");const P=te(t.op),x=ee(t.timeout,"--timeout"),L=b(t.instrumental),N=T(t.weirdness,"--weirdness",0.5),j=T(t["style-weight"],"--style-weight",0.5),R=(E||(l?h||f.split(`
4
+ `)[0]:y)).replace(/\s+/g," ").trim().slice(0,60)||"Untitled",s=await X(o);if(!s.planId)throw new n(`Suno generation needs a resolved plan id for the user_tier field, but billing/info did not surface one for this account (subscription_type=${s.planKey}). Verify the account is active at ${a}/account, then retry.`);const c=s.deviceId,d=await J(o,c);if(!d?.ok)throw new n(`Suno captcha pre-flight failed${d?.status?` (HTTP ${d.status})`:""}.`,`Open ${a}/create in Chrome and verify the account is ready, then retry.`);if(d?.required)throw new n("Suno requires a CAPTCHA challenge for this account/IP right now.",`Open ${a}/create in Chrome, solve a Create challenge once, then retry.`);if(s.totalCreditsAvailable<10){const e=s.breakdown;throw new n(`Suno generation needs ~10 credits; you have ${s.totalCreditsAvailable} (monthly ${e.monthlyRemaining}/${e.monthlyLimit} + packs ${e.purchasedPacks} + leftover ${e.pack}). Top up at ${a}/account.`)}const V=q.randomUUID(),k=q.randomUUID(),D=await oe(o,{mode:l?"custom":"simple",model:w,title:R,lyrics:f,tags:h,negativeTags:U,description:y,makeInstrumental:L,weirdness:N,styleWeight:j,userTier:s.planId,createSessionToken:k,transactionUuid:V,deviceId:c});if(!Array.isArray(D.clips))throw new n("Suno generation returned malformed clips payload.");const m=D.clips.map((e)=>e?.id);if(m.some((e)=>!e))throw new n("Suno generation returned malformed clip identity.");if(!m.length)throw new n("Suno accepted the request but returned no clip ids.");const S=await Z(o,m,x,c);if(!S.filter((e)=>e.status==="complete").length){const e=S.map((r)=>`${r.id.slice(0,8)}:${r.status}`).join(", ");throw new n(`All Suno clips failed (${e}). Open ${a}/song/${m[0]} to inspect.`)}const p=[];for(const e of S){const r=`${a}/song/${e.id}`;if(e.status!=="complete"){p.push({status:`❌ ${e.status}`,clip:e.id.slice(0,8),title:e.title||"(untitled)",files:"-",link:`\uD83D\uDD17 ${r}`});continue}if(A){p.push({status:"\uD83C\uDFB5 generated",clip:e.id.slice(0,8),title:e.title||"(untitled)",files:"\uD83D\uDCC1 -",link:`\uD83D\uDD17 ${r}`});continue}const v=await Q(o,e,P,O,c);if(!v.written.some((i)=>i.ok))throw new n(`Suno download wrote no files for clip ${e.id}`);const B=v.written.map((i)=>i.ok?`${i.format}:${ne(i.file)}`:`${i.format}:✗(${i.reason})`).join(" | "),W=g.length?` | skipped(needs --confirm-paid):${g.join(",")}`:"",G=v.written.some((i)=>!i.ok);p.push({status:G?"⚠ partial":"✅ saved",clip:e.id.slice(0,8),title:e.title||"(untitled)",files:`\uD83D\uDCC1 ${B}${W}`,link:`\uD83D\uDD17 ${r}`})}return p}});