publishport-opencli 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (333) hide show
  1. package/README.md +61 -61
  2. package/README.zh-CN.md +68 -68
  3. package/cli-manifest.json +954 -61
  4. package/clis/1688/shared.js +2 -2
  5. package/clis/36kr/news.js +1 -1
  6. package/clis/_atlassian/shared.js +1 -1
  7. package/clis/_shared/article/auth.js +1 -1
  8. package/clis/_shared/article/publish.js +1 -1
  9. package/clis/_shared/content-guard.js +1 -1
  10. package/clis/_shared/self-hosted-sites.js +2 -0
  11. package/clis/_shared/site-auth.js +1 -1
  12. package/clis/_shared/token-auth.js +2 -2
  13. package/clis/_shared/video-publish.js +1 -1
  14. package/clis/aibase/news.js +1 -1
  15. package/clis/amazon/shared.js +2 -2
  16. package/clis/antigravity/SKILL.md +12 -12
  17. package/clis/antigravity/storage.js +1 -1
  18. package/clis/apple-podcasts/episodes.js +1 -1
  19. package/clis/archive/item.js +1 -1
  20. package/clis/archive/search.js +1 -1
  21. package/clis/archive/snapshots.js +1 -1
  22. package/clis/archive/wayback.js +1 -1
  23. package/clis/arxiv/author.js +1 -1
  24. package/clis/baijiahao/publish.js +1 -1
  25. package/clis/bbc/utils.js +1 -1
  26. package/clis/bloomberg/businessweek.js +1 -1
  27. package/clis/bloomberg/utils.js +1 -1
  28. package/clis/booking/search.js +1 -1
  29. package/clis/boss/utils.js +1 -1
  30. package/clis/chatgpt/ask.js +1 -1
  31. package/clis/chatgpt/detail.js +1 -1
  32. package/clis/chatgpt/history.js +1 -1
  33. package/clis/chatgpt/project-file-add.js +1 -1
  34. package/clis/chatgpt/project-list.js +1 -1
  35. package/clis/chatgpt/utils.js +2 -2
  36. package/clis/chess/utils.js +1 -1
  37. package/clis/claude/ask.js +1 -1
  38. package/clis/claude/history.js +1 -1
  39. package/clis/claude/utils.js +1 -1
  40. package/clis/codex/extract-diff.js +1 -1
  41. package/clis/coingecko/coin.js +1 -1
  42. package/clis/crates/utils.js +1 -1
  43. package/clis/csdn/stats.js +1 -1
  44. package/clis/dblp/utils.js +1 -1
  45. package/clis/defillama/utils.js +1 -1
  46. package/clis/devto/publish.js +1 -1
  47. package/clis/discord-app/utils.js +1 -1
  48. package/clis/dockerhub/utils.js +1 -1
  49. package/clis/douban/utils.js +1 -1
  50. package/clis/douyin/_shared/tos-upload.js +1 -1
  51. package/clis/douyin/hashtag.js +1 -1
  52. package/clis/douyin/publish-image.js +1 -1
  53. package/clis/douyin/publish.js +1 -1
  54. package/clis/eastmoney/announcement.js +1 -1
  55. package/clis/eastmoney/rank.js +1 -1
  56. package/clis/eastmoney/sectors.js +1 -1
  57. package/clis/endoflife/utils.js +1 -1
  58. package/clis/flathub/utils.js +1 -1
  59. package/clis/gemini/detail.js +1 -1
  60. package/clis/gemini/read.js +1 -1
  61. package/clis/geogebra/add-circle.js +1 -1
  62. package/clis/geogebra/add-line.js +1 -1
  63. package/clis/geogebra/add-point.js +1 -1
  64. package/clis/geogebra/add-polygon.js +1 -1
  65. package/clis/geogebra/eval.js +1 -1
  66. package/clis/geogebra/hexagon.js +1 -1
  67. package/clis/geogebra/info.js +1 -1
  68. package/clis/geogebra/triangle.js +1 -1
  69. package/clis/ghost/login.js +1 -0
  70. package/clis/ghost/publish.js +1 -1
  71. package/clis/ghost/shared.js +24 -1
  72. package/clis/ghost/sites.js +1 -0
  73. package/clis/ghost/whoami.js +1 -1
  74. package/clis/gitee/user.js +2 -2
  75. package/clis/github-trending/repos.js +1 -1
  76. package/clis/goproxy/utils.js +1 -1
  77. package/clis/grok/export-all.js +1 -1
  78. package/clis/grok/export.js +1 -1
  79. package/clis/hashnode/publish.js +1 -1
  80. package/clis/hf/datasets.js +1 -1
  81. package/clis/hf/models.js +1 -1
  82. package/clis/hf/paper.js +1 -1
  83. package/clis/hf/spaces.js +1 -1
  84. package/clis/homebrew/utils.js +1 -1
  85. package/clis/instagram/note.js +1 -1
  86. package/clis/jimeng/generate.js +1 -1
  87. package/clis/juejin/utils.js +1 -1
  88. package/clis/kuaishou/publish.js +1 -1
  89. package/clis/lesswrong/tag.js +1 -1
  90. package/clis/lichess/utils.js +1 -1
  91. package/clis/linkedin/search.js +2 -2
  92. package/clis/linux-do/feed.js +2 -2
  93. package/clis/lobsters/domain.js +1 -1
  94. package/clis/mastodon/login.js +1 -0
  95. package/clis/mastodon/post.js +1 -1
  96. package/clis/mastodon/shared.js +10 -10
  97. package/clis/mastodon/sites.js +1 -0
  98. package/clis/mastodon/whoami.js +1 -1
  99. package/clis/maven/utils.js +1 -1
  100. package/clis/mdn/search.js +1 -1
  101. package/clis/medium/tag.js +1 -1
  102. package/clis/notebooklm/current.js +1 -1
  103. package/clis/notebooklm/get.js +1 -1
  104. package/clis/notebooklm/history.js +1 -1
  105. package/clis/notebooklm/note-list.js +1 -1
  106. package/clis/notebooklm/notes-get.js +1 -1
  107. package/clis/notebooklm/open.js +1 -1
  108. package/clis/notebooklm/source-fulltext.js +1 -1
  109. package/clis/notebooklm/source-get.js +1 -1
  110. package/clis/notebooklm/source-guide.js +1 -1
  111. package/clis/notebooklm/source-list.js +1 -1
  112. package/clis/notebooklm/summary.js +1 -1
  113. package/clis/notebooklm/utils.js +1 -1
  114. package/clis/npm/utils.js +1 -1
  115. package/clis/nuget/utils.js +1 -1
  116. package/clis/nvd/cve.js +1 -1
  117. package/clis/oeis/utils.js +1 -1
  118. package/clis/ones/common.js +2 -2
  119. package/clis/ones/me.js +1 -1
  120. package/clis/ones/tasks.js +1 -1
  121. package/clis/ones/token-info.js +1 -1
  122. package/clis/ones/worklog.js +1 -1
  123. package/clis/openalex/utils.js +1 -1
  124. package/clis/oschina/stats.js +1 -1
  125. package/clis/osv/utils.js +1 -1
  126. package/clis/packagist/utils.js +1 -1
  127. package/clis/pixiv/detail.js +1 -1
  128. package/clis/pixiv/user.js +1 -1
  129. package/clis/producthunt/utils.js +1 -1
  130. package/clis/pypi/utils.js +1 -1
  131. package/clis/qiita/gql.js +2 -2
  132. package/clis/quora/__fixtures__/dom.html +39 -0
  133. package/clis/quora/answer-comments.js +9 -0
  134. package/clis/quora/answer-detail.js +1 -0
  135. package/clis/quora/answer-queue.js +1 -0
  136. package/clis/quora/comment.js +23 -0
  137. package/clis/quora/create-answer.js +4 -4
  138. package/clis/quora/create-question.js +3 -3
  139. package/clis/quora/follow.js +15 -0
  140. package/clis/quora/followers.js +1 -0
  141. package/clis/quora/question.js +1 -0
  142. package/clis/quora/search.js +1 -0
  143. package/clis/quora/shared.js +26 -11
  144. package/clis/quora/stats.js +1 -0
  145. package/clis/quora/upvote.js +14 -0
  146. package/clis/quora/user-answers.js +1 -0
  147. package/clis/quora/user.js +1 -0
  148. package/clis/rest-countries/utils.js +1 -1
  149. package/clis/rfc/utils.js +1 -1
  150. package/clis/rubygems/utils.js +1 -1
  151. package/clis/semanticscholar/utils.js +1 -1
  152. package/clis/slock/errors.js +1 -1
  153. package/clis/spotify/spotify.js +1 -1
  154. package/clis/spotify/utils.js +1 -1
  155. package/clis/stackoverflow/utils.js +1 -1
  156. package/clis/steam/utils.js +1 -1
  157. package/clis/substack/auth.js +2 -2
  158. package/clis/suno/generate.js +2 -2
  159. package/clis/suno/list.js +3 -3
  160. package/clis/telegram/send.js +1 -1
  161. package/clis/tiktok/creator-videos.js +1 -1
  162. package/clis/tiktok/publish.js +1 -1
  163. package/clis/tiktok/utils.js +1 -1
  164. package/clis/trae-cn/activity.js +1 -1
  165. package/clis/trae-cn/approve.js +1 -1
  166. package/clis/trae-cn/ask.js +1 -1
  167. package/clis/trae-cn/dump.js +1 -1
  168. package/clis/trae-cn/export.js +1 -1
  169. package/clis/trae-cn/model.js +1 -1
  170. package/clis/trae-cn/new.js +1 -1
  171. package/clis/trae-cn/read.js +1 -1
  172. package/clis/trae-cn/screenshot.js +1 -1
  173. package/clis/trae-cn/select-model.js +1 -1
  174. package/clis/trae-cn/send.js +1 -1
  175. package/clis/trae-cn/setup.js +1 -1
  176. package/clis/trae-cn/status.js +1 -1
  177. package/clis/trae-cn/targets.js +1 -1
  178. package/clis/trae-cn/watch.js +1 -1
  179. package/clis/trae-solo/state-fs.js +1 -1
  180. package/clis/tumblr/publish.js +1 -1
  181. package/clis/tvmaze/utils.js +1 -1
  182. package/clis/twitter/bookmark-folder.js +1 -1
  183. package/clis/twitter/download.js +1 -1
  184. package/clis/twitter/followers.js +2 -2
  185. package/clis/twitter/following.js +2 -2
  186. package/clis/twitter/likes.js +1 -1
  187. package/clis/twitter/list-add-batch.js +1 -1
  188. package/clis/twitter/list-add-core.js +1 -1
  189. package/clis/twitter/list-add.js +1 -1
  190. package/clis/twitter/list-create.js +1 -1
  191. package/clis/twitter/list-delete.js +3 -3
  192. package/clis/twitter/list-remove-batch.js +1 -1
  193. package/clis/twitter/list-remove.js +1 -1
  194. package/clis/twitter/list-tweets.js +1 -1
  195. package/clis/twitter/profile.js +1 -1
  196. package/clis/twitter/search.js +1 -1
  197. package/clis/twitter/shared.js +1 -1
  198. package/clis/twitter/tweets.js +1 -1
  199. package/clis/uisdc/news.js +1 -1
  200. package/clis/v2ex/daily.js +2 -2
  201. package/clis/v2ex/me.js +2 -2
  202. package/clis/v2ex/notifications.js +1 -1
  203. package/clis/wikidata/utils.js +1 -1
  204. package/clis/wikipedia/page.js +2 -2
  205. package/clis/wikipedia/summary.js +1 -1
  206. package/clis/wikipedia/utils.js +1 -1
  207. package/clis/wordpress/login.js +1 -1
  208. package/clis/wordpress/publish.js +1 -1
  209. package/clis/wordpress/shared.js +3 -3
  210. package/clis/wordpress/sites.js +1 -0
  211. package/clis/wordpress/whoami.js +1 -1
  212. package/clis/woshipm/stats.js +1 -1
  213. package/clis/xiaoe/content.js +1 -1
  214. package/clis/xiaohongshu/draft-delete.js +1 -1
  215. package/clis/xiaohongshu/draft-open.js +1 -1
  216. package/clis/xiaohongshu/draft-utils.js +2 -2
  217. package/clis/xiaohongshu/publish-video.js +1 -1
  218. package/clis/xiaohongshu/publish.js +1 -1
  219. package/clis/xiaoyuzhou/auth.js +1 -1
  220. package/clis/xiaoyuzhou/transcript.js +1 -1
  221. package/clis/yollomi/background.js +1 -1
  222. package/clis/yollomi/edit.js +1 -1
  223. package/clis/yollomi/generate.js +1 -1
  224. package/clis/yollomi/try-on.js +1 -1
  225. package/clis/youtube/publish.js +1 -1
  226. package/clis/zhihu/answer-comments.js +1 -1
  227. package/clis/zhihu/answer-detail.js +1 -1
  228. package/clis/zhihu/collection.js +2 -2
  229. package/clis/zhihu/collections.js +1 -1
  230. package/clis/zhihu/question.js +1 -1
  231. package/clis/zhihu/search.js +1 -1
  232. package/clis/zhihu/target.js +1 -1
  233. package/clis/zhihu/user-arg.js +1 -1
  234. package/dist/src/adapter-shadow.js +1 -1
  235. package/dist/src/browser/analyze.js +1 -1
  236. package/dist/src/browser/article-extract.d.ts +1 -1
  237. package/dist/src/browser/base-page.js +3 -3
  238. package/dist/src/browser/cdp.js +1 -1
  239. package/dist/src/browser/daemon-client.d.ts +1 -1
  240. package/dist/src/browser/daemon-lifecycle.js +6 -6
  241. package/dist/src/browser/daemon-version.js +1 -1
  242. package/dist/src/browser/dom-snapshot.d.ts +1 -1
  243. package/dist/src/browser/dom-snapshot.js +2 -2
  244. package/dist/src/browser/errors.js +2 -2
  245. package/dist/src/browser/network-cache.js +1 -1
  246. package/dist/src/browser/profile.js +1 -1
  247. package/dist/src/browser/target-resolver.js +4 -4
  248. package/dist/src/browser/verify-fixture.js +1 -1
  249. package/dist/src/browser-session-lock.js +1 -1
  250. package/dist/src/build-manifest.d.ts +1 -1
  251. package/dist/src/build-manifest.js +1 -1
  252. package/dist/src/cli-argv-preprocess.d.ts +4 -4
  253. package/dist/src/cli-argv-preprocess.js +1 -1
  254. package/dist/src/cli.d.ts +1 -1
  255. package/dist/src/cli.js +19 -19
  256. package/dist/src/commanderAdapter.js +1 -1
  257. package/dist/src/commands/auth.js +2 -2
  258. package/dist/src/commands/daemon.d.ts +4 -4
  259. package/dist/src/commands/daemon.js +1 -1
  260. package/dist/src/completion-shared.js +12 -12
  261. package/dist/src/completion.d.ts +2 -2
  262. package/dist/src/constants.d.ts +1 -1
  263. package/dist/src/constants.js +1 -1
  264. package/dist/src/daemon-utils.d.ts +1 -1
  265. package/dist/src/daemon-utils.js +1 -1
  266. package/dist/src/daemon.d.ts +2 -2
  267. package/dist/src/daemon.js +1 -1
  268. package/dist/src/discovery.d.ts +7 -7
  269. package/dist/src/discovery.js +2 -2
  270. package/dist/src/doctor.d.ts +1 -1
  271. package/dist/src/doctor.js +6 -6
  272. package/dist/src/electron-apps.d.ts +1 -1
  273. package/dist/src/electron-apps.js +1 -1
  274. package/dist/src/errors.d.ts +2 -2
  275. package/dist/src/errors.js +1 -1
  276. package/dist/src/execution.js +1 -1
  277. package/dist/src/external.d.ts +1 -1
  278. package/dist/src/external.js +1 -1
  279. package/dist/src/help.d.ts +1 -1
  280. package/dist/src/help.js +3 -3
  281. package/dist/src/hooks.d.ts +1 -1
  282. package/dist/src/launcher.js +2 -2
  283. package/dist/src/logger.d.ts +1 -1
  284. package/dist/src/main.d.ts +1 -1
  285. package/dist/src/main.js +1 -1
  286. package/dist/src/observation/artifact.js +2 -2
  287. package/dist/src/package-paths.js +1 -1
  288. package/dist/src/package-paths.test.d.ts +1 -0
  289. package/dist/src/plugin-manifest.d.ts +12 -3
  290. package/dist/src/plugin-manifest.js +1 -1
  291. package/dist/src/plugin-scaffold.d.ts +2 -2
  292. package/dist/src/plugin-scaffold.js +9 -9
  293. package/dist/src/plugin.d.ts +4 -4
  294. package/dist/src/plugin.js +10 -10
  295. package/dist/src/rate-limit.js +1 -1
  296. package/dist/src/registry-api.d.ts +1 -1
  297. package/dist/src/runtime-detect.d.ts +1 -1
  298. package/dist/src/serialization.js +1 -1
  299. package/dist/src/skills.js +6 -6
  300. package/dist/src/user-runtime.d.ts +8 -0
  301. package/dist/src/user-runtime.js +1 -0
  302. package/dist/src/user-runtime.test.d.ts +1 -0
  303. package/dist/src/validate.js +1 -1
  304. package/dist/src/validate.test.d.ts +1 -1
  305. package/docs/guide/browser-bridge.md +23 -23
  306. package/docs/guide/electron-app-cli.md +3 -3
  307. package/docs/guide/exit-codes.md +3 -3
  308. package/docs/guide/extending-opencli.md +37 -37
  309. package/docs/guide/getting-started.md +19 -19
  310. package/docs/guide/installation.md +6 -6
  311. package/docs/guide/plugins.md +32 -32
  312. package/docs/guide/remote-orchestration.md +5 -5
  313. package/docs/guide/troubleshooting.md +9 -9
  314. package/package.json +3 -2
  315. package/scripts/fetch-adapters.js +6 -6
  316. package/scripts/postinstall.js +14 -14
  317. package/skills/opencli-adapter-author/SKILL.md +24 -24
  318. package/skills/opencli-adapter-author/references/adapter-template.md +13 -13
  319. package/skills/opencli-adapter-author/references/api-discovery.md +25 -25
  320. package/skills/opencli-adapter-author/references/coverage-matrix.md +3 -3
  321. package/skills/opencli-adapter-author/references/field-decode-playbook.md +10 -10
  322. package/skills/opencli-adapter-author/references/jsdom-fixture-pattern.md +2 -2
  323. package/skills/opencli-adapter-author/references/site-memory.md +11 -11
  324. package/skills/opencli-adapter-author/references/site-recon.md +7 -7
  325. package/skills/opencli-adapter-author/references/strategy-selection.md +3 -3
  326. package/skills/opencli-adapter-author/references/success-rate-pitfalls.md +4 -4
  327. package/skills/opencli-adapter-author/references/typed-errors.md +2 -2
  328. package/skills/opencli-autofix/SKILL.md +29 -29
  329. package/skills/opencli-browser/SKILL.md +55 -55
  330. package/skills/opencli-browser-sitemap/SKILL.md +7 -7
  331. package/skills/opencli-sitemap-author/SKILL.md +11 -11
  332. package/skills/opencli-sitemap-author/references/sitemap-schema.md +19 -19
  333. package/skills/opencli-usage/SKILL.md +43 -43
@@ -34,4 +34,4 @@ import{CommandExecutionError as i}from"@jackwener/opencli/errors";import{cli as
34
34
  return { ok: false, error: String(error && error.message || error) };
35
35
  }
36
36
  })()
37
- `}f({site:"xiaohongshu",name:"draft-delete",access:"write",description:"删除一条小红书本地草稿",domain:"creator.xiaohongshu.com",strategy:u.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"id",positional:!0,required:!0,help:"Draft id returned by `opencli xiaohongshu drafts`"},{name:"type",default:"image",help:"Draft type: image, video, article, audio"},{name:"execute",type:"bool",default:!1,help:"Actually delete the local draft. Default is dry-run verification only."}],columns:["status","id","type","message"],func:async(e,t)=>{const a=g(t.id),r=h(t.type),d=t.execute===!0;await p(e);const c=await b(e,r),o=x(c,a);if(!o)throw w(a,r,"xiaohongshu/draft-delete");const n=D(o.row,o.key,r,1);if(!d)return[{status:"dry-run",id:n.id,type:r,message:"Draft exists. Re-run with --execute to delete."}];const l=m[r],s=q(await e.evaluate(S(l,o.key)));if(!s?.ok)throw new i(s?.error||`Failed to delete draft ${n.id}`);if(!s.deleted)throw new i(`Draft ${n.id} still exists after delete`);return[{status:"deleted",id:n.id,type:r,message:"Draft deleted."}]}});
37
+ `}f({site:"xiaohongshu",name:"draft-delete",access:"write",description:"删除一条小红书本地草稿",domain:"creator.xiaohongshu.com",strategy:u.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"id",positional:!0,required:!0,help:"Draft id returned by `ppcli xiaohongshu drafts`"},{name:"type",default:"image",help:"Draft type: image, video, article, audio"},{name:"execute",type:"bool",default:!1,help:"Actually delete the local draft. Default is dry-run verification only."}],columns:["status","id","type","message"],func:async(e,t)=>{const a=g(t.id),r=h(t.type),d=t.execute===!0;await p(e);const c=await b(e,r),o=x(c,a);if(!o)throw w(a,r,"xiaohongshu/draft-delete");const n=D(o.row,o.key,r,1);if(!d)return[{status:"dry-run",id:n.id,type:r,message:"Draft exists. Re-run with --execute to delete."}];const l=m[r],s=q(await e.evaluate(S(l,o.key)));if(!s?.ok)throw new i(s?.error||`Failed to delete draft ${n.id}`);if(!s.deleted)throw new i(`Draft ${n.id} still exists after delete`);return[{status:"deleted",id:n.id,type:r,message:"Draft deleted."}]}});
@@ -1 +1 @@
1
- import{cli as d,Strategy as s}from"@jackwener/opencli/registry";import{draftNotFound as f,ensureDraftDbPage as c,findDraftEntry as p,normalizeDraftId as m,normalizeDraftRecord as u,normalizeDraftType as l,readDraftEntries as y}from"./draft-utils.js";d({site:"xiaohongshu",name:"draft-open",access:"read",description:"读取一条小红书本地草稿详情",domain:"creator.xiaohongshu.com",strategy:s.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"id",positional:!0,required:!0,help:"Draft id returned by `opencli xiaohongshu drafts`"},{name:"type",default:"image",help:"Draft type: image, video, article, audio"}],columns:["id","type","title","updated_at","images","content"],func:async(r,i)=>{const o=m(i.id),e=l(i.type);await c(r);const n=await y(r,e),a=p(n,o);if(!a)throw f(o,e,"xiaohongshu/draft-open");const t=u(a.row,a.key,e,1,{contentLimit:500});return[{id:t.id,type:t.type,title:t.title,updated_at:t.updated_at,images:t.images,content:t.text_preview}]}});
1
+ import{cli as d,Strategy as s}from"@jackwener/opencli/registry";import{draftNotFound as f,ensureDraftDbPage as p,findDraftEntry as c,normalizeDraftId as m,normalizeDraftRecord as u,normalizeDraftType as l,readDraftEntries as y}from"./draft-utils.js";d({site:"xiaohongshu",name:"draft-open",access:"read",description:"读取一条小红书本地草稿详情",domain:"creator.xiaohongshu.com",strategy:s.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"id",positional:!0,required:!0,help:"Draft id returned by `ppcli xiaohongshu drafts`"},{name:"type",default:"image",help:"Draft type: image, video, article, audio"}],columns:["id","type","title","updated_at","images","content"],func:async(r,i)=>{const o=m(i.id),e=l(i.type);await p(r);const n=await y(r,e),a=c(n,o);if(!a)throw f(o,e,"xiaohongshu/draft-open");const t=u(a.row,a.key,e,1,{contentLimit:500});return[{id:t.id,type:t.type,title:t.title,updated_at:t.updated_at,images:t.images,content:t.text_preview}]}});
@@ -1,4 +1,4 @@
1
- import{ArgumentError as a,CommandExecutionError as c,EmptyResultError as p}from"@jackwener/opencli/errors";export const PUBLISH_URL="https://creator.xiaohongshu.com/publish/publish?from=menu_left&target=image",DRAFT_DB_NAME="draft-database-v1",STORE_NAME_MAP={image:"image-draft",video:"video-draft",article:"article-draft",audio:"audio-draft"};export function unwrapBrowserResult(e){if(e&&typeof e==="object"&&typeof e.session==="string"&&Object.prototype.hasOwnProperty.call(e,"data"))return e.data;return e}export function normalizeDraftType(e,{allowAll:t=!1}={}){const r=String(e??"image").trim().toLowerCase();if(t&&r==="all")return r;if(!STORE_NAME_MAP[r]){const o=t?"image, video, article, audio, all":Object.keys(STORE_NAME_MAP).join(", ");throw new a(`Unsupported draft type "${r}". Expected one of: ${o}`)}return r}export function normalizeDraftId(e){const t=String(e??"").trim();if(!t)throw new a("Draft id is required");return t}export function encodeDraftKey(e){const t=typeof e;if(t==="string")return`s:${e}`;if(t==="number")return`n:${String(e)}`;if(t==="boolean")return`b:${String(e)}`;try{return`j:${encodeURIComponent(JSON.stringify(e))}`}catch{return`s:${String(e)}`}}export function findDraftEntry(e,t){return e.find((r)=>encodeDraftKey(r?.key)===t)||null}export function normalizeDraftRecord(e,t,r,o,{contentLimit:d=80}={}){const n=e?.content??{},l=n?.contextStore?.liveContext??{},i=n?.draftStore??{},f=i?.title||l?.title||e?.title||e?.noteTitle||"",u=n?.editorContent?.text||n?.editorContent?.plainText||"",s=e?.updatedAt||e?.updateTime||e?.mtime||n?.updateTime||"",g=i?.imgList?.length||n?.noteImageConfig?.items?.length||n?.noteImageConfig?.imageList?.length||e?.images?.length||e?.imageList?.length||0;return{rank:o,id:encodeDraftKey(t),type:r,title:f||"(untitled)",updated_at:s?String(s):"",images:g,text_preview:String(u||"").replace(/\s+/g," ").trim().slice(0,d)}}export async function ensureDraftDbPage(e){await e.goto(PUBLISH_URL);await e.wait({time:2})}function m(e){return`
1
+ import{ArgumentError as a,CommandExecutionError as c,EmptyResultError as g}from"@jackwener/opencli/errors";export const PUBLISH_URL="https://creator.xiaohongshu.com/publish/publish?from=menu_left&target=image",DRAFT_DB_NAME="draft-database-v1",STORE_NAME_MAP={image:"image-draft",video:"video-draft",article:"article-draft",audio:"audio-draft"};export function unwrapBrowserResult(e){if(e&&typeof e==="object"&&typeof e.session==="string"&&Object.prototype.hasOwnProperty.call(e,"data"))return e.data;return e}export function normalizeDraftType(e,{allowAll:t=!1}={}){const r=String(e??"image").trim().toLowerCase();if(t&&r==="all")return r;if(!STORE_NAME_MAP[r]){const o=t?"image, video, article, audio, all":Object.keys(STORE_NAME_MAP).join(", ");throw new a(`Unsupported draft type "${r}". Expected one of: ${o}`)}return r}export function normalizeDraftId(e){const t=String(e??"").trim();if(!t)throw new a("Draft id is required");return t}export function encodeDraftKey(e){const t=typeof e;if(t==="string")return`s:${e}`;if(t==="number")return`n:${String(e)}`;if(t==="boolean")return`b:${String(e)}`;try{return`j:${encodeURIComponent(JSON.stringify(e))}`}catch{return`s:${String(e)}`}}export function findDraftEntry(e,t){return e.find((r)=>encodeDraftKey(r?.key)===t)||null}export function normalizeDraftRecord(e,t,r,o,{contentLimit:d=80}={}){const n=e?.content??{},l=n?.contextStore?.liveContext??{},i=n?.draftStore??{},f=i?.title||l?.title||e?.title||e?.noteTitle||"",u=n?.editorContent?.text||n?.editorContent?.plainText||"",s=e?.updatedAt||e?.updateTime||e?.mtime||n?.updateTime||"",p=i?.imgList?.length||n?.noteImageConfig?.items?.length||n?.noteImageConfig?.imageList?.length||e?.images?.length||e?.imageList?.length||0;return{rank:o,id:encodeDraftKey(t),type:r,title:f||"(untitled)",updated_at:s?String(s):"",images:p,text_preview:String(u||"").replace(/\s+/g," ").trim().slice(0,d)}}export async function ensureDraftDbPage(e){await e.goto(PUBLISH_URL);await e.wait({time:2})}function m(e){return`
2
2
  (async () => {
3
3
  const openDb = () => new Promise((resolve, reject) => {
4
4
  const req = indexedDB.open(${JSON.stringify(DRAFT_DB_NAME)});
@@ -33,4 +33,4 @@ import{ArgumentError as a,CommandExecutionError as c,EmptyResultError as p}from"
33
33
  return { ok: false, error: String(error && error.message || error) };
34
34
  }
35
35
  })()
36
- `}export async function readDraftEntries(e,t){const r=STORE_NAME_MAP[t],o=unwrapBrowserResult(await e.evaluate(m(r)));if(!o?.ok)throw new c(o?.error||`Failed to read ${t} drafts`);if(!Array.isArray(o.entries))throw new c(`Malformed ${t} draft payload`);return o.entries}export function draftNotFound(e,t,r){return new p(r,`Draft ${e} was not found in ${t} drafts. Run opencli xiaohongshu drafts --type ${t} to list current ids.`)}
36
+ `}export async function readDraftEntries(e,t){const r=STORE_NAME_MAP[t],o=unwrapBrowserResult(await e.evaluate(m(r)));if(!o?.ok)throw new c(o?.error||`Failed to read ${t} drafts`);if(!Array.isArray(o.entries))throw new c(`Malformed ${t} draft payload`);return o.entries}export function draftNotFound(e,t,r){return new g(r,`Draft ${e} was not found in ${t} drafts. Run ppcli xiaohongshu drafts --type ${t} to list current ids.`)}
@@ -73,7 +73,7 @@ import*as X from"node:fs";import*as Y from"node:path";import{cli as h,Strategy a
73
73
  }
74
74
  return { ok: false, via: 'none', hosts: hosts.length, lastMethodError };
75
75
  })(${JSON.stringify({isDraftMode:z,publishNames:_,draftNames:u,labels:G})})
76
- `)}h({site:"xiaohongshu",name:"publish-video",access:"write",description:"小红书发布视频笔记(creator center UI 自动化)",domain:"creator.xiaohongshu.com",strategy:D.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"content",required:!1,positional:!0,help:"笔记正文(字面文本,不展开 @文件 引用;长正文用 --file)"},{name:"file",required:!1,help:"从本机文件读取正文(UTF-8),与位置参数 <content> 二选一"},{name:"title",required:!0,help:"笔记标题(≤20字)"},{name:"video",required:!0,help:"视频文件路径 (mp4/mov/mkv/...)"},{name:"topics",required:!1,help:"话题标签,逗号分隔,不含 # 号,最多10个"},{name:"draft",type:"bool",default:!1,help:"暂存为草稿,不直接发布"}],columns:["status","detail"],func:async(q,z)=>{if(!q)throw Error("Browser page required");const G=String(z.title??"").trim(),K=z.file?String(z.file).trim():"";let J=String(z.content??"").trim();if(K){if(J)throw new $("正文位置参数和 --file 只能二选一");const Z=Y.resolve(K);if(!X.existsSync(Z))throw new $(`--file 指向的文件不存在: ${Z}`);J=X.readFileSync(Z,"utf-8").trim();if(!J)throw new $(`--file 指向的文件内容为空: ${Z}`)}const Q=z.topics?String(z.topics).split(",").map((Z)=>Z.trim().replace(/^#/,"")).filter(Boolean):[],v=Boolean(z.draft);if(!G)throw new $("--title 必填");if(G.length>k)throw new $(`标题 ${G.length} 字,需 ≤ ${k} 字`);if(!J)throw new $("缺少正文:给位置参数 <content>,或用 --file <本机文件路径>");C(J,{fileFlag:"--file"});if(J.length>x)throw new $(`正文 ${J.length} 字,需 ≤ ${x} 字`);if(!z.video)throw new $("--video 必填");if(Q.length>R)throw new $(`话题过多: ${Q.length}(最多 ${R} 个)`);const N=T(String(z.video));await q.goto(O);let y="";for(let Z=0;Z<30;Z++){await q.wait({time:0.5});y=await q.evaluate("() => location.href");if(y.includes("creator.xiaohongshu.com"))break}if(!y.includes("creator.xiaohongshu.com")){await q.screenshot({path:"/tmp/xhs_video_redirect_debug.png"});throw new j(`被重定向离开创作者中心(当前 ${y})——登录态可能已过期。`+"用 opencli xiaohongshu creator-profile 重新登录。截图: /tmp/xhs_video_redirect_debug.png")}const I=await d(q,N);if(!I.ok){await q.screenshot({path:"/tmp/xhs_video_upload_debug.png"});throw new j(`视频上传失败: ${I.error??"unknown"}。截图: /tmp/xhs_video_upload_debug.png`)}if(!await l(q)){await q.screenshot({path:"/tmp/xhs_video_form_debug.png"});throw new j("上传后编辑表单未就绪(标题/正文未出现)。截图: /tmp/xhs_video_form_debug.png")}process.stderr.write(` 等待视频转码(可发布状态)...
76
+ `)}h({site:"xiaohongshu",name:"publish-video",access:"write",description:"小红书发布视频笔记(creator center UI 自动化)",domain:"creator.xiaohongshu.com",strategy:D.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"content",required:!1,positional:!0,help:"笔记正文(字面文本,不展开 @文件 引用;长正文用 --file)"},{name:"file",required:!1,help:"从本机文件读取正文(UTF-8),与位置参数 <content> 二选一"},{name:"title",required:!0,help:"笔记标题(≤20字)"},{name:"video",required:!0,help:"视频文件路径 (mp4/mov/mkv/...)"},{name:"topics",required:!1,help:"话题标签,逗号分隔,不含 # 号,最多10个"},{name:"draft",type:"bool",default:!1,help:"暂存为草稿,不直接发布"}],columns:["status","detail"],func:async(q,z)=>{if(!q)throw Error("Browser page required");const G=String(z.title??"").trim(),K=z.file?String(z.file).trim():"";let J=String(z.content??"").trim();if(K){if(J)throw new $("正文位置参数和 --file 只能二选一");const Z=Y.resolve(K);if(!X.existsSync(Z))throw new $(`--file 指向的文件不存在: ${Z}`);J=X.readFileSync(Z,"utf-8").trim();if(!J)throw new $(`--file 指向的文件内容为空: ${Z}`)}const Q=z.topics?String(z.topics).split(",").map((Z)=>Z.trim().replace(/^#/,"")).filter(Boolean):[],v=Boolean(z.draft);if(!G)throw new $("--title 必填");if(G.length>k)throw new $(`标题 ${G.length} 字,需 ≤ ${k} 字`);if(!J)throw new $("缺少正文:给位置参数 <content>,或用 --file <本机文件路径>");C(J,{fileFlag:"--file"});if(J.length>x)throw new $(`正文 ${J.length} 字,需 ≤ ${x} 字`);if(!z.video)throw new $("--video 必填");if(Q.length>R)throw new $(`话题过多: ${Q.length}(最多 ${R} 个)`);const N=T(String(z.video));await q.goto(O);let y="";for(let Z=0;Z<30;Z++){await q.wait({time:0.5});y=await q.evaluate("() => location.href");if(y.includes("creator.xiaohongshu.com"))break}if(!y.includes("creator.xiaohongshu.com")){await q.screenshot({path:"/tmp/xhs_video_redirect_debug.png"});throw new j(`被重定向离开创作者中心(当前 ${y})——登录态可能已过期。`+"用 ppcli xiaohongshu creator-profile 重新登录。截图: /tmp/xhs_video_redirect_debug.png")}const I=await d(q,N);if(!I.ok){await q.screenshot({path:"/tmp/xhs_video_upload_debug.png"});throw new j(`视频上传失败: ${I.error??"unknown"}。截图: /tmp/xhs_video_upload_debug.png`)}if(!await l(q)){await q.screenshot({path:"/tmp/xhs_video_form_debug.png"});throw new j("上传后编辑表单未就绪(标题/正文未出现)。截图: /tmp/xhs_video_form_debug.png")}process.stderr.write(` 等待视频转码(可发布状态)...
77
77
  `);if(!await c(q)){await q.screenshot({path:"/tmp/xhs_video_transcode_debug.png"});throw new j("视频转码超时(10min),发布按钮未解禁。截图: /tmp/xhs_video_transcode_debug.png")}await E(q,G);await q.wait({time:0.4});await n(q,J);await q.wait({time:0.5});let F=[];if(Q.length){F=await o(q,Q);const Z=Q.filter((S)=>!F.includes(S));if(Z.length){await q.screenshot({path:"/tmp/xhs_video_topic_debug.png"});throw new j(`话题未能生成实体: ${Z.join(", ")}(联想下拉未出现或无匹配项)。截图: /tmp/xhs_video_topic_debug.png`)}}const b=await q.evaluate("(() => { const b = document.querySelector('xhs-publish-btn'); return !!b && b.getAttribute('submit-disabled') === 'false'; })()");if(!v&&!b){await q.screenshot({path:"/tmp/xhs_video_prepublish_debug.png"});throw new j("发布前复查:发布按钮不可用(submit-disabled≠false)。截图: /tmp/xhs_video_prepublish_debug.png")}const W=await s(q,v);if(!W?.ok){await q.screenshot({path:"/tmp/xhs_video_submit_debug.png"});throw new j(`找不到「${v?"暂存离开":"发布"}」触发方式(hosts=${W?.hosts??0}${W?.lastMethodError?`, lastMethodError=${W.lastMethodError}`:""})。截图: /tmp/xhs_video_submit_debug.png`)}await q.wait({time:4});let H=await q.evaluate("() => location.href");const w=v?["草稿已保存","暂存成功","保存成功","保存于"]:["发布成功","上传成功"],B=await q.evaluate(`
78
78
  ((markers) => {
79
79
  for (const el of document.querySelectorAll('*')) {
@@ -546,7 +546,7 @@ import*as b from"node:fs";import*as D from"node:path";import{CommandExecutionErr
546
546
  }
547
547
  return removed;
548
548
  })(${JSON.stringify(J)})
549
- `));return Number(K)||0}function _z(z){const J=String(z&&(z.url||z.key)||""),K=String(z&&z.method||"").toUpperCase(),Z=Number(z&&z.status||0);if(K!=="PUT")return null;if(!/ros-upload[^/]*\/spectrum\//.test(J))return null;if(Z<200||Z>=300)return null;const Q=J.match(/\/spectrum\/([^?#]+)/);return Q?Q[1]:J}function Qz(z){return Math.min(45,6+z*4)}async function Zz(z,J,K,Z=90000){if(J<=0)return{ok:!0,completed:0};if(!K||typeof z.readNetworkCapture!=="function"){await z.wait({time:Qz(J)});return{ok:!0,completed:null,viaFallback:!0}}const Q=1500,$=Math.ceil(9000/Q),j=Math.ceil(Z/Q),Y=new Set;for(let U=0;U<j;U++){const f=await z.readNetworkCapture().catch(()=>[]);for(const B of Array.isArray(f)?f:[]){const G=_z(B);if(G)Y.add(G)}if(Y.size>=J)return{ok:!0,completed:Y.size};if(U>=$&&Y.size===0){await z.wait({time:Qz(J)});return{ok:!0,completed:null,viaFallback:!0}}await z.wait({time:Q/1000})}return{ok:!1,completed:Y.size}}Vz({site:"xiaohongshu",name:"publish",access:"write",description:"小红书发布图文笔记 (creator center UI automation)",domain:"creator.xiaohongshu.com",strategy:Hz.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"content",required:!1,positional:!0,help:"笔记正文(字面文本,不会展开 @文件 引用;长正文建议用 --file)"},{name:"file",required:!1,help:"从本机文件读取笔记正文(UTF-8),与位置参数 <content> 二选一"},{name:"title",required:!0,help:"笔记标题 (最多20字)"},{name:"images",required:!1,help:"图片路径,逗号分隔,最多9张 (jpg/png/gif/webp)"},{name:"card-text",required:!1,help:`文字配图卡片文字,多张卡片用 ${c} 分隔,卡内换行用 \\n`},{name:"card-style",required:!1,help:`文字配图卡片样式,运行时按页面实际选项匹配;找不到会失败。省略时使用${N}。可选: ${d.map(([z,J])=>`${z}(${J})`).join(" ")}`},{name:"topics",required:!1,help:"话题标签,逗号分隔,不含 # 号"},{name:"draft",type:"bool",default:!1,help:"保存为草稿,不直接发布"}],columns:["status","detail"],func:async(z,J)=>{if(!z)throw Error("Browser page required");const K=String(J.title??"").trim(),Z=J.file?String(J.file).trim():"";let Q=String(J.content??"").trim();if(Z){if(Q)throw new F("正文位置参数和 --file 只能二选一,不能同时给");const V=D.resolve(Z);if(!b.existsSync(V))throw new F(`--file 指向的文件不存在: ${V}`);Q=b.readFileSync(V,"utf-8").trim();if(!Q)throw new F(`--file 指向的文件内容为空: ${V}`)}const q=J.images?String(J.images).split(",").map((V)=>V.trim()).filter(Boolean):[],$=J.topics?String(J.topics).split(",").map((V)=>V.trim()).filter(Boolean):[],j=Boolean(J.draft),Y=J["card-text"]?String(J["card-text"]):"",U=Y?Y.split(c).map((V)=>V.trim()).filter(Boolean):[],f=J["card-style"]?String(J["card-style"]).trim():"",B=U.length>0;if(!K)throw new F("--title is required");if(K.length>E)throw new F(`Title is ${K.length} chars — must be ≤ ${E}`);if(!Q)throw new F("缺少笔记正文:给位置参数 <content>(字面文本),或用 --file <本机文件路径>");Gz(Q,{fileFlag:"--file"});if(!B&&q.length===0)throw new F("Provide --card-text (text-image mode) or --images (upload mode); neither was given.");if(q.length>x)throw new F(`Too many images: ${q.length} (max ${x})`);if(B&&q.some((V)=>D.extname(V).toLowerCase()===".gif"))throw new F("文字配图模式追加的图片不支持 .gif(编辑器图片入口只接受 jpg/jpeg/png/webp)");const G=Fz(q);await z.goto(Yz);await z.wait({time:1});await z.evaluate("() => { try { window.onbeforeunload = null; } catch (e) {} location.reload(); }").catch(()=>{return});let h="";for(let V=0;V<30;V++){await z.wait({time:0.5});h=await z.evaluate("() => location.href");if(h.includes("creator.xiaohongshu.com"))break}if(!h.includes("creator.xiaohongshu.com")){await z.screenshot({path:"/tmp/xhs_publish_redirect_debug.png"});throw Error(`Redirected away from creator center (landed on ${h}) — session may have expired. `+"Re-capture browser login via: opencli xiaohongshu creator-profile. Debug screenshot: /tmp/xhs_publish_redirect_debug.png")}const L=await vz(z);if((await bz(z,L?.ok?5000:2000)).state==="video_surface"){await z.screenshot({path:"/tmp/xhs_publish_tab_debug.png"});const V=L?.ok?`clicked "${L.text}"`:`visible candidates: ${(L?.visibleTexts||[]).join(" | ")||"none"}`;throw Error("Still on the video publish page after trying to select 图文. "+`Details: ${V}. Debug screenshot: /tmp/xhs_publish_tab_debug.png`)}const P=await Rz(z);let I=f;if(B)I=await wz(z,U,f);else{const V=typeof z.startNetworkCapture==="function"?await z.startNetworkCapture("").catch(()=>!1):!1,O=await r(z,G);if(!O.ok){await z.screenshot({path:"/tmp/xhs_publish_upload_debug.png"});throw new H(`图片注入失败:${O.error??"unknown"}。`+"直接重新运行本 publish 命令即可(会自动重置发布页重传);请勿手动修改当前页面。"+"Debug: /tmp/xhs_publish_upload_debug.png")}await z.wait({time:u/1000});const v=await Zz(z,G.length,V);if(!v.ok){await z.screenshot({path:"/tmp/xhs_publish_upload_debug.png"});throw new H(`图片上传未全部完成:只确认 ${v.completed}/${G.length} 张传到小红书 CDN,`+"继续发布会丢图(通常只剩封面)。直接重新运行本 publish 命令即可(会自动重置发布页重传);"+"请勿手动修改当前页面。Debug: /tmp/xhs_publish_upload_debug.png")}await g(z);if(P.length){await Kz(z,P);await z.wait({time:0.5})}const w=await e(z);if(w&&typeof w.count==="number"&&w.count>G.length){await z.screenshot({path:"/tmp/xhs_publish_dup_debug.png"});throw new H(`清掉恢复态旧图后编辑器内仍有 ${w.count} 张图,超过本次注入的 ${G.length} 张。`+"直接重新运行本 publish 命令即可(会自动重置发布页);请勿手动修改当前页面。Debug: /tmp/xhs_publish_dup_debug.png")}}if(!await yz(z)){await z.screenshot({path:"/tmp/xhs_publish_form_debug.png"});throw new H("Editing form did not appear after image acquisition. The page layout may have changed. Debug screenshot: /tmp/xhs_publish_form_debug.png")}if(B)await zz(z,U.length,"文字配图 generated images");if(B&&G.length>0){const V=typeof z.startNetworkCapture==="function"?await z.startNetworkCapture("").catch(()=>!1):!1,O=await r(z,G);if(!O.ok){await z.screenshot({path:"/tmp/xhs_publish_append_debug.png"});throw new H(`追加图片失败:${O.error??"unknown"}。`+"直接重新运行本 publish 命令即可(会自动重置发布页重传);请勿手动修改当前页面。"+"Debug: /tmp/xhs_publish_append_debug.png")}await z.wait({time:u/1000});const v=await Zz(z,G.length,V);if(!v.ok){await z.screenshot({path:"/tmp/xhs_publish_append_debug.png"});throw new H(`追加图片上传未全部完成:只确认 ${v.completed}/${G.length} 张传到小红书 CDN。`+"直接重新运行本 publish 命令即可(会自动重置发布页重传);请勿手动修改当前页面。"+"Debug: /tmp/xhs_publish_append_debug.png")}await g(z);await zz(z,U.length+G.length,"文字配图 appended images")}if(B&&P.length){await Kz(z,P);await z.wait({time:0.5})}await p(z,A,K,"title");await z.wait({time:0.5});await p(z,l,Q,"content");await z.wait({time:0.5});let y=[];if($.length)y=await Bz(z,l,$);const T=j?["暂存离开","存草稿"]:["发布","发布笔记"],X=await z.evaluate(`
549
+ `));return Number(K)||0}function _z(z){const J=String(z&&(z.url||z.key)||""),K=String(z&&z.method||"").toUpperCase(),Z=Number(z&&z.status||0);if(K!=="PUT")return null;if(!/ros-upload[^/]*\/spectrum\//.test(J))return null;if(Z<200||Z>=300)return null;const Q=J.match(/\/spectrum\/([^?#]+)/);return Q?Q[1]:J}function Qz(z){return Math.min(45,6+z*4)}async function Zz(z,J,K,Z=90000){if(J<=0)return{ok:!0,completed:0};if(!K||typeof z.readNetworkCapture!=="function"){await z.wait({time:Qz(J)});return{ok:!0,completed:null,viaFallback:!0}}const Q=1500,$=Math.ceil(9000/Q),j=Math.ceil(Z/Q),Y=new Set;for(let U=0;U<j;U++){const f=await z.readNetworkCapture().catch(()=>[]);for(const B of Array.isArray(f)?f:[]){const G=_z(B);if(G)Y.add(G)}if(Y.size>=J)return{ok:!0,completed:Y.size};if(U>=$&&Y.size===0){await z.wait({time:Qz(J)});return{ok:!0,completed:null,viaFallback:!0}}await z.wait({time:Q/1000})}return{ok:!1,completed:Y.size}}Vz({site:"xiaohongshu",name:"publish",access:"write",description:"小红书发布图文笔记 (creator center UI automation)",domain:"creator.xiaohongshu.com",strategy:Hz.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"content",required:!1,positional:!0,help:"笔记正文(字面文本,不会展开 @文件 引用;长正文建议用 --file)"},{name:"file",required:!1,help:"从本机文件读取笔记正文(UTF-8),与位置参数 <content> 二选一"},{name:"title",required:!0,help:"笔记标题 (最多20字)"},{name:"images",required:!1,help:"图片路径,逗号分隔,最多9张 (jpg/png/gif/webp)"},{name:"card-text",required:!1,help:`文字配图卡片文字,多张卡片用 ${c} 分隔,卡内换行用 \\n`},{name:"card-style",required:!1,help:`文字配图卡片样式,运行时按页面实际选项匹配;找不到会失败。省略时使用${N}。可选: ${d.map(([z,J])=>`${z}(${J})`).join(" ")}`},{name:"topics",required:!1,help:"话题标签,逗号分隔,不含 # 号"},{name:"draft",type:"bool",default:!1,help:"保存为草稿,不直接发布"}],columns:["status","detail"],func:async(z,J)=>{if(!z)throw Error("Browser page required");const K=String(J.title??"").trim(),Z=J.file?String(J.file).trim():"";let Q=String(J.content??"").trim();if(Z){if(Q)throw new F("正文位置参数和 --file 只能二选一,不能同时给");const V=D.resolve(Z);if(!b.existsSync(V))throw new F(`--file 指向的文件不存在: ${V}`);Q=b.readFileSync(V,"utf-8").trim();if(!Q)throw new F(`--file 指向的文件内容为空: ${V}`)}const q=J.images?String(J.images).split(",").map((V)=>V.trim()).filter(Boolean):[],$=J.topics?String(J.topics).split(",").map((V)=>V.trim()).filter(Boolean):[],j=Boolean(J.draft),Y=J["card-text"]?String(J["card-text"]):"",U=Y?Y.split(c).map((V)=>V.trim()).filter(Boolean):[],f=J["card-style"]?String(J["card-style"]).trim():"",B=U.length>0;if(!K)throw new F("--title is required");if(K.length>E)throw new F(`Title is ${K.length} chars — must be ≤ ${E}`);if(!Q)throw new F("缺少笔记正文:给位置参数 <content>(字面文本),或用 --file <本机文件路径>");Gz(Q,{fileFlag:"--file"});if(!B&&q.length===0)throw new F("Provide --card-text (text-image mode) or --images (upload mode); neither was given.");if(q.length>x)throw new F(`Too many images: ${q.length} (max ${x})`);if(B&&q.some((V)=>D.extname(V).toLowerCase()===".gif"))throw new F("文字配图模式追加的图片不支持 .gif(编辑器图片入口只接受 jpg/jpeg/png/webp)");const G=Fz(q);await z.goto(Yz);await z.wait({time:1});await z.evaluate("() => { try { window.onbeforeunload = null; } catch (e) {} location.reload(); }").catch(()=>{return});let h="";for(let V=0;V<30;V++){await z.wait({time:0.5});h=await z.evaluate("() => location.href");if(h.includes("creator.xiaohongshu.com"))break}if(!h.includes("creator.xiaohongshu.com")){await z.screenshot({path:"/tmp/xhs_publish_redirect_debug.png"});throw Error(`Redirected away from creator center (landed on ${h}) — session may have expired. `+"Re-capture browser login via: ppcli xiaohongshu creator-profile. Debug screenshot: /tmp/xhs_publish_redirect_debug.png")}const L=await vz(z);if((await bz(z,L?.ok?5000:2000)).state==="video_surface"){await z.screenshot({path:"/tmp/xhs_publish_tab_debug.png"});const V=L?.ok?`clicked "${L.text}"`:`visible candidates: ${(L?.visibleTexts||[]).join(" | ")||"none"}`;throw Error("Still on the video publish page after trying to select 图文. "+`Details: ${V}. Debug screenshot: /tmp/xhs_publish_tab_debug.png`)}const P=await Rz(z);let I=f;if(B)I=await wz(z,U,f);else{const V=typeof z.startNetworkCapture==="function"?await z.startNetworkCapture("").catch(()=>!1):!1,O=await r(z,G);if(!O.ok){await z.screenshot({path:"/tmp/xhs_publish_upload_debug.png"});throw new H(`图片注入失败:${O.error??"unknown"}。`+"直接重新运行本 publish 命令即可(会自动重置发布页重传);请勿手动修改当前页面。"+"Debug: /tmp/xhs_publish_upload_debug.png")}await z.wait({time:u/1000});const v=await Zz(z,G.length,V);if(!v.ok){await z.screenshot({path:"/tmp/xhs_publish_upload_debug.png"});throw new H(`图片上传未全部完成:只确认 ${v.completed}/${G.length} 张传到小红书 CDN,`+"继续发布会丢图(通常只剩封面)。直接重新运行本 publish 命令即可(会自动重置发布页重传);"+"请勿手动修改当前页面。Debug: /tmp/xhs_publish_upload_debug.png")}await g(z);if(P.length){await Kz(z,P);await z.wait({time:0.5})}const w=await e(z);if(w&&typeof w.count==="number"&&w.count>G.length){await z.screenshot({path:"/tmp/xhs_publish_dup_debug.png"});throw new H(`清掉恢复态旧图后编辑器内仍有 ${w.count} 张图,超过本次注入的 ${G.length} 张。`+"直接重新运行本 publish 命令即可(会自动重置发布页);请勿手动修改当前页面。Debug: /tmp/xhs_publish_dup_debug.png")}}if(!await yz(z)){await z.screenshot({path:"/tmp/xhs_publish_form_debug.png"});throw new H("Editing form did not appear after image acquisition. The page layout may have changed. Debug screenshot: /tmp/xhs_publish_form_debug.png")}if(B)await zz(z,U.length,"文字配图 generated images");if(B&&G.length>0){const V=typeof z.startNetworkCapture==="function"?await z.startNetworkCapture("").catch(()=>!1):!1,O=await r(z,G);if(!O.ok){await z.screenshot({path:"/tmp/xhs_publish_append_debug.png"});throw new H(`追加图片失败:${O.error??"unknown"}。`+"直接重新运行本 publish 命令即可(会自动重置发布页重传);请勿手动修改当前页面。"+"Debug: /tmp/xhs_publish_append_debug.png")}await z.wait({time:u/1000});const v=await Zz(z,G.length,V);if(!v.ok){await z.screenshot({path:"/tmp/xhs_publish_append_debug.png"});throw new H(`追加图片上传未全部完成:只确认 ${v.completed}/${G.length} 张传到小红书 CDN。`+"直接重新运行本 publish 命令即可(会自动重置发布页重传);请勿手动修改当前页面。"+"Debug: /tmp/xhs_publish_append_debug.png")}await g(z);await zz(z,U.length+G.length,"文字配图 appended images")}if(B&&P.length){await Kz(z,P);await z.wait({time:0.5})}await p(z,A,K,"title");await z.wait({time:0.5});await p(z,l,Q,"content");await z.wait({time:0.5});let y=[];if($.length)y=await Bz(z,l,$);const T=j?["暂存离开","存草稿"]:["发布","发布笔记"],X=await z.evaluate(`
550
550
  (cfg => {
551
551
  const { isDraftMode, publishNames, draftNames, labels } = cfg;
552
552
  const isVisible = (el) => {
@@ -1,3 +1,3 @@
1
- import*as Q from"node:fs";import*as z from"node:os";import*as W from"node:path";import{CliError as L,CommandExecutionError as M,ConfigError as S,EXIT_CODES as X,getErrorMessage as N}from"@jackwener/opencli/errors";export const XIAOYUZHOU_API_BASE_URL="https://api.xiaoyuzhoufm.com",XIAOYUZHOU_TOKEN_TTL_MS=1200000,XIAOYUZHOU_REFRESH_SKEW_MS=60000,XIAOYUZHOU_DEFAULT_DEVICE_ID="81ADBFD6-6921-482B-9AB9-A29E7CC7BB55",XIAOYUZHOU_DEFAULT_DEVICE_PROPERTIES="",XIAOYUZHOU_DEFAULT_USER_AGENT="Xiaoyuzhou/2.98.0 (build:2908; iOS 26.2.1)";function Y(){return Date.now()}export function getXiaoyuzhouCredentialFile(){return W.join(z.homedir(),".opencli","xiaoyuzhou.json")}function V(j){return new L("AUTH_REQUIRED",j,`Update ${getXiaoyuzhouCredentialFile()} with fresh Xiaoyuzhou credentials before retrying.`,X.NOPERM)}function Z(j){const D=Number(j);return Number.isFinite(D)?D:0}export function normalizeXiaoyuzhouCredentials(j={}){const D=Z(j.last_updated_ts??j.lastUpdatedTs);let F=Z(j.expires_at??j.expiresAt);if(F>0&&F<10000000000)F*=1000;if(!F&&D>0)F=D*1000+XIAOYUZHOU_TOKEN_TTL_MS;return{access_token:String(j.access_token??j.accessToken??"").trim(),refresh_token:String(j.refresh_token??j.refreshToken??"").trim(),expires_at:F,device_id:String(j.device_id??j.deviceId??XIAOYUZHOU_DEFAULT_DEVICE_ID).trim()||XIAOYUZHOU_DEFAULT_DEVICE_ID,device_properties:String(j.device_properties??j.deviceProperties??XIAOYUZHOU_DEFAULT_DEVICE_PROPERTIES)}}export function loadXiaoyuzhouCredentials(){const j=getXiaoyuzhouCredentialFile();if(Q.existsSync(j))try{const D=JSON.parse(Q.readFileSync(j,"utf-8")),F=normalizeXiaoyuzhouCredentials(D);if(!F.access_token||!F.refresh_token)throw new S(`Xiaoyuzhou credential file is missing access_token or refresh_token: ${j}`,"Recreate the file with valid credentials.");return F}catch(D){if(D instanceof S)throw D;throw new S(`Failed to parse Xiaoyuzhou credential file: ${j}`,`Ensure ${j} contains valid JSON. (${N(D)})`)}throw new S(`Missing Xiaoyuzhou credentials. Expected ${j}`,`Create ${j} with access_token and refresh_token.`)}export function saveXiaoyuzhouCredentials(j){const D=getXiaoyuzhouCredentialFile();Q.mkdirSync(W.dirname(D),{recursive:!0});Q.writeFileSync(D,`${JSON.stringify({access_token:j.access_token,refresh_token:j.refresh_token,expires_at:j.expires_at,device_id:j.device_id,device_properties:j.device_properties},null,2)}
1
+ import*as Q from"node:fs";import*as z from"node:os";import*as W from"node:path";import{CliError as L,CommandExecutionError as M,ConfigError as S,EXIT_CODES as X,getErrorMessage as N}from"@jackwener/opencli/errors";export const XIAOYUZHOU_API_BASE_URL="https://api.xiaoyuzhoufm.com",XIAOYUZHOU_TOKEN_TTL_MS=1200000,XIAOYUZHOU_REFRESH_SKEW_MS=60000,XIAOYUZHOU_DEFAULT_DEVICE_ID="81ADBFD6-6921-482B-9AB9-A29E7CC7BB55",XIAOYUZHOU_DEFAULT_DEVICE_PROPERTIES="",XIAOYUZHOU_DEFAULT_USER_AGENT="Xiaoyuzhou/2.98.0 (build:2908; iOS 26.2.1)";function Y(){return Date.now()}export function getXiaoyuzhouCredentialFile(){return W.join(z.homedir(),".ppcli","xiaoyuzhou.json")}function V(j){return new L("AUTH_REQUIRED",j,`Update ${getXiaoyuzhouCredentialFile()} with fresh Xiaoyuzhou credentials before retrying.`,X.NOPERM)}function Z(j){const D=Number(j);return Number.isFinite(D)?D:0}export function normalizeXiaoyuzhouCredentials(j={}){const D=Z(j.last_updated_ts??j.lastUpdatedTs);let F=Z(j.expires_at??j.expiresAt);if(F>0&&F<10000000000)F*=1000;if(!F&&D>0)F=D*1000+XIAOYUZHOU_TOKEN_TTL_MS;return{access_token:String(j.access_token??j.accessToken??"").trim(),refresh_token:String(j.refresh_token??j.refreshToken??"").trim(),expires_at:F,device_id:String(j.device_id??j.deviceId??XIAOYUZHOU_DEFAULT_DEVICE_ID).trim()||XIAOYUZHOU_DEFAULT_DEVICE_ID,device_properties:String(j.device_properties??j.deviceProperties??XIAOYUZHOU_DEFAULT_DEVICE_PROPERTIES)}}export function loadXiaoyuzhouCredentials(){const j=getXiaoyuzhouCredentialFile();if(Q.existsSync(j))try{const D=JSON.parse(Q.readFileSync(j,"utf-8")),F=normalizeXiaoyuzhouCredentials(D);if(!F.access_token||!F.refresh_token)throw new S(`Xiaoyuzhou credential file is missing access_token or refresh_token: ${j}`,"Recreate the file with valid credentials.");return F}catch(D){if(D instanceof S)throw D;throw new S(`Failed to parse Xiaoyuzhou credential file: ${j}`,`Ensure ${j} contains valid JSON. (${N(D)})`)}throw new S(`Missing Xiaoyuzhou credentials. Expected ${j}`,`Create ${j} with access_token and refresh_token.`)}export function saveXiaoyuzhouCredentials(j){const D=getXiaoyuzhouCredentialFile();Q.mkdirSync(W.dirname(D),{recursive:!0});Q.writeFileSync(D,`${JSON.stringify({access_token:j.access_token,refresh_token:j.refresh_token,expires_at:j.expires_at,device_id:j.device_id,device_properties:j.device_properties},null,2)}
2
2
  `,"utf-8")}export function shouldRefreshXiaoyuzhouCredentials(j,D=Y()){return Number.isFinite(j.expires_at)&&j.expires_at>0&&D>=j.expires_at-XIAOYUZHOU_REFRESH_SKEW_MS}export function buildXiaoyuzhouHeaders(j,D={}){const{contentType:F="application/json",includeLocalTime:H=!1,includeRefreshToken:G=!1}=D,J={"Content-Type":F,Host:"api.xiaoyuzhoufm.com","User-Agent":XIAOYUZHOU_DEFAULT_USER_AGENT,Market:"AppStore","App-BuildNo":"2908",OS:"ios",Manufacturer:"Apple",BundleID:"app.podcast.cosmos",Connection:"keep-alive","abtest-info":'{"old_user_discovery_feed":"enable"}',"Accept-Language":"en-HK;q=1.0, zh-Hans-HK;q=0.9",Model:"iPhone18,1","app-permissions":"100000",Accept:"*/*","App-Version":"2.98.0",WifiConnected:"true","OS-Version":"26.2.1","x-custom-xiaoyuzhou-app-dev":"","x-jike-device-id":j.device_id||XIAOYUZHOU_DEFAULT_DEVICE_ID,"x-jike-device-properties":j.device_properties??XIAOYUZHOU_DEFAULT_DEVICE_PROPERTIES};if(j.access_token)J["x-jike-access-token"]=j.access_token;if(G&&j.refresh_token)J["x-jike-refresh-token"]=j.refresh_token;if(H){J["Local-Time"]=new Date().toISOString();J.Timezone=Intl.DateTimeFormat().resolvedOptions().timeZone||"UTC"}return J}export async function refreshXiaoyuzhouCredentials(j,D=fetch){if(!j.refresh_token)throw V("Xiaoyuzhou refresh token is missing");let F;try{F=await D(`${XIAOYUZHOU_API_BASE_URL}/app_auth_tokens.refresh`,{method:"POST",headers:buildXiaoyuzhouHeaders(j,{contentType:"application/x-www-form-urlencoded; charset=utf-8",includeLocalTime:!0,includeRefreshToken:!0}),signal:AbortSignal.timeout(20000)})}catch(K){throw new M(`Failed to refresh Xiaoyuzhou credentials: ${N(K)}`)}const H=await F.text();if(!F.ok)throw V(`Xiaoyuzhou token refresh failed with HTTP ${F.status}${H?`: ${H}`:""}`);let G;try{G=JSON.parse(H)}catch(K){throw new M(`Xiaoyuzhou refresh returned invalid JSON: ${N(K)}`)}if(!G?.success)throw V("Xiaoyuzhou refresh API returned success=false");const J=normalizeXiaoyuzhouCredentials({...j,access_token:G["x-jike-access-token"]||"",refresh_token:G["x-jike-refresh-token"]||"",expires_at:Y()+XIAOYUZHOU_TOKEN_TTL_MS});if(!J.access_token||!J.refresh_token)throw V("Xiaoyuzhou refresh API returned empty access_token or refresh_token");saveXiaoyuzhouCredentials(J);return J}function B(j,D){const F=new URL(j,XIAOYUZHOU_API_BASE_URL);if(D){for(const[H,G]of Object.entries(D))if(G!==void 0&&G!==null&&G!=="")F.searchParams.set(H,String(G))}return F.toString()}async function _(j,D,F,H){const{method:G="GET",query:J,body:K}=D;let O;try{O=await H(B(j,J),{method:G,headers:buildXiaoyuzhouHeaders(F,{contentType:"application/json",includeLocalTime:!0}),body:K===void 0?void 0:JSON.stringify(K),signal:AbortSignal.timeout(20000)})}catch($){throw new M(`Failed to reach Xiaoyuzhou API: ${N($)}`)}return O}export async function requestXiaoyuzhouJson(j,D={},F=fetch){let H=D.credentials??loadXiaoyuzhouCredentials();if(shouldRefreshXiaoyuzhouCredentials(H))H=await refreshXiaoyuzhouCredentials(H,F);let G=await _(j,D,H,F);if(G.status===401){H=await refreshXiaoyuzhouCredentials(H,F);G=await _(j,D,H,F)}const J=await G.text();if(!G.ok)throw new M(`Xiaoyuzhou API request failed with HTTP ${G.status}${J?`: ${J}`:""}`);let K;try{K=JSON.parse(J)}catch(O){throw new M(`Xiaoyuzhou API returned invalid JSON: ${N(O)}`)}if(K?.success===!1)throw new M(K?.message||K?.msg||"Xiaoyuzhou API returned success=false");return{credentials:H,raw:K,data:K?.data}}export async function fetchXiaoyuzhouTranscriptBody(j,D=fetch){let F;try{F=await D(j,{method:"GET",headers:{"User-Agent":XIAOYUZHOU_DEFAULT_USER_AGENT,Accept:"*/*",Market:"AppStore"},signal:AbortSignal.timeout(20000)})}catch(G){throw new M(`Failed to fetch Xiaoyuzhou transcript content: ${N(G)}`)}const H=await F.text();if(!F.ok)throw new M(`Xiaoyuzhou transcript download failed with HTTP ${F.status}${H?`: ${H}`:""}`);return H}export function extractTranscriptText(j){let D;try{D=JSON.parse(j)}catch{return{text:"",segmentCount:0}}let F=[];if(Array.isArray(D))F=D;else if(D&&typeof D==="object"){for(const G of["segments","data","transcript","items"])if(Array.isArray(D[G])){F=D[G];break}if(F.length===0){const G=typeof D.text==="string"?D.text.trim():"";if(G)return{text:G,segmentCount:1}}}const H=[];for(const G of F){if(!G||typeof G!=="object"||typeof G.text!=="string")continue;const J=G.text.trim();if(J)H.push(J)}return{text:H.join(`
3
3
  `),segmentCount:H.length}}
@@ -1 +1 @@
1
- import*as s from"node:fs";import*as n from"node:path";import{cli as y,Strategy as S}from"@jackwener/opencli/registry";import{ArgumentError as R,CliError as i}from"@jackwener/opencli/errors";import{loadXiaoyuzhouCredentials as j,requestXiaoyuzhouJson as h,fetchXiaoyuzhouTranscriptBody as w,extractTranscriptText as E}from"./auth.js";y({site:"xiaoyuzhou",name:"transcript",access:"read",description:"Download Xiaoyuzhou transcript as JSON and text (requires local credentials)",domain:"www.xiaoyuzhoufm.com",strategy:S.PUBLIC,browser:!1,args:[{name:"id",positional:!0,required:!0,help:"Episode ID (eid from podcast-episodes output)"},{name:"output",default:"./xiaoyuzhou-transcripts",help:"Output directory"},{name:"json",type:"boolean",default:!0,help:"Save transcript JSON file"},{name:"text",type:"boolean",default:!0,help:"Save extracted transcript text file"}],columns:["title","podcast","status","segments","json_file","text_file"],func:async(t)=>{if(t.json===!1&&t.text===!1)throw new R("At least one of --json or --text must be enabled","Example: opencli xiaoyuzhou transcript 69dd0c98e2c8be31551f6a33 --text true");let o=j();const d=await h("/v1/episode/get",{query:{eid:t.id},credentials:o});o=d.credentials;const e=d.data;if(!e)throw new i("NOT_FOUND","Episode not found","Please check the episode ID");const p=String(e.transcript?.mediaId||e.media?.id||e.transcriptMediaId||"").trim();if(!p)throw new i("PARSE_ERROR","mediaId not found in episode payload","Transcript metadata requires episode.transcript.mediaId, episode.media.id, or episode.transcriptMediaId");const c=(await h("/v1/episode-transcript/get",{method:"POST",body:{eid:t.id,mediaId:p},credentials:o})).data,u=String(c?.transcriptUrl||c?.url||"").trim();if(!u)throw new i("EMPTY_RESULT","Transcript URL not found","This episode may not have transcript data available");const r=await w(u),{text:l,segmentCount:x}=E(r);if(t.text!==!1&&r.trim()&&!l.trim())throw new i("PARSE_ERROR","Failed to extract transcript text","Transcript payload format is unsupported. Re-run with --json true to inspect the raw payload.");const a=n.join(String(t.output||"./xiaoyuzhou-transcripts"),String(t.id));s.mkdirSync(a,{recursive:!0});const f=n.join(a,"transcript.json"),m=n.join(a,"transcript.txt");if(t.json!==!1)s.writeFileSync(f,r,"utf-8");if(t.text!==!1)s.writeFileSync(m,l,"utf-8");return[{title:e.title||"episode",podcast:e.podcast?.title||"",status:"success",segments:t.text===!1?"-":String(x),json_file:t.json===!1?"-":f,text_file:t.text===!1?"-":m}]}});
1
+ import*as s from"node:fs";import*as n from"node:path";import{cli as y,Strategy as S}from"@jackwener/opencli/registry";import{ArgumentError as R,CliError as i}from"@jackwener/opencli/errors";import{loadXiaoyuzhouCredentials as j,requestXiaoyuzhouJson as h,fetchXiaoyuzhouTranscriptBody as w,extractTranscriptText as E}from"./auth.js";y({site:"xiaoyuzhou",name:"transcript",access:"read",description:"Download Xiaoyuzhou transcript as JSON and text (requires local credentials)",domain:"www.xiaoyuzhoufm.com",strategy:S.PUBLIC,browser:!1,args:[{name:"id",positional:!0,required:!0,help:"Episode ID (eid from podcast-episodes output)"},{name:"output",default:"./xiaoyuzhou-transcripts",help:"Output directory"},{name:"json",type:"boolean",default:!0,help:"Save transcript JSON file"},{name:"text",type:"boolean",default:!0,help:"Save extracted transcript text file"}],columns:["title","podcast","status","segments","json_file","text_file"],func:async(t)=>{if(t.json===!1&&t.text===!1)throw new R("At least one of --json or --text must be enabled","Example: ppcli xiaoyuzhou transcript 69dd0c98e2c8be31551f6a33 --text true");let o=j();const d=await h("/v1/episode/get",{query:{eid:t.id},credentials:o});o=d.credentials;const e=d.data;if(!e)throw new i("NOT_FOUND","Episode not found","Please check the episode ID");const p=String(e.transcript?.mediaId||e.media?.id||e.transcriptMediaId||"").trim();if(!p)throw new i("PARSE_ERROR","mediaId not found in episode payload","Transcript metadata requires episode.transcript.mediaId, episode.media.id, or episode.transcriptMediaId");const c=(await h("/v1/episode-transcript/get",{method:"POST",body:{eid:t.id,mediaId:p},credentials:o})).data,u=String(c?.transcriptUrl||c?.url||"").trim();if(!u)throw new i("EMPTY_RESULT","Transcript URL not found","This episode may not have transcript data available");const r=await w(u),{text:l,segmentCount:x}=E(r);if(t.text!==!1&&r.trim()&&!l.trim())throw new i("PARSE_ERROR","Failed to extract transcript text","Transcript payload format is unsupported. Re-run with --json true to inspect the raw payload.");const a=n.join(String(t.output||"./xiaoyuzhou-transcripts"),String(t.id));s.mkdirSync(a,{recursive:!0});const f=n.join(a,"transcript.json"),m=n.join(a,"transcript.txt");if(t.json!==!1)s.writeFileSync(f,r,"utf-8");if(t.text!==!1)s.writeFileSync(m,l,"utf-8");return[{title:e.title||"episode",podcast:e.podcast?.title||"",status:"success",segments:t.text===!1?"-":String(x),json_file:t.json===!1?"-":f,text_file:t.text===!1?"-":m}]}});
@@ -1 +1 @@
1
- import*as u from"node:path";import{cli as m,Strategy as p}from"@jackwener/opencli/registry";import{CliError as d}from"@jackwener/opencli/errors";import{log as c}from"@jackwener/opencli/logger";import{YOLLOMI_DOMAIN as f,yollomiPost as g,downloadOutput as y,fmtBytes as O}from"./utils.js";m({site:"yollomi",name:"background",access:"write",description:"Generate AI background for a product/object image (5 credits)",domain:f,strategy:p.COOKIE,args:[{name:"image",positional:!0,required:!0,help:'Image URL (upload via "opencli yollomi upload" first)'},{name:"prompt",default:"",help:"Background description (optional)"},{name:"output",default:"./yollomi-output",help:"Output directory"},{name:"no-download",type:"boolean",default:!1,help:"Only show URL"}],columns:["status","file","size","url"],func:async(a,e)=>{const{image:i,prompt:r}=e;c.status("Generating background...");const o=await g(a,"/api/ai/ai-background-generator",{images:[i],prompt:r||void 0,aspect_ratio:"1:1"}),t=o.image||o.images?.[0];if(!t)throw new d("EMPTY_RESPONSE","No result","Try a different image");if(e["no-download"])return[{status:"generated",file:"-",size:"-",url:t}];try{const n=`yollomi_bg_${Date.now()}.png`,{path:l,size:s}=await y(t,e.output,n);return[{status:"saved",file:u.relative(".",l),size:O(s),url:t}]}catch{return[{status:"download-failed",file:"-",size:"-",url:t}]}}});
1
+ import*as u from"node:path";import{cli as p,Strategy as m}from"@jackwener/opencli/registry";import{CliError as d}from"@jackwener/opencli/errors";import{log as c}from"@jackwener/opencli/logger";import{YOLLOMI_DOMAIN as f,yollomiPost as g,downloadOutput as y,fmtBytes as O}from"./utils.js";p({site:"yollomi",name:"background",access:"write",description:"Generate AI background for a product/object image (5 credits)",domain:f,strategy:m.COOKIE,args:[{name:"image",positional:!0,required:!0,help:'Image URL (upload via "ppcli yollomi upload" first)'},{name:"prompt",default:"",help:"Background description (optional)"},{name:"output",default:"./yollomi-output",help:"Output directory"},{name:"no-download",type:"boolean",default:!1,help:"Only show URL"}],columns:["status","file","size","url"],func:async(a,e)=>{const{image:i,prompt:r}=e;c.status("Generating background...");const o=await g(a,"/api/ai/ai-background-generator",{images:[i],prompt:r||void 0,aspect_ratio:"1:1"}),t=o.image||o.images?.[0];if(!t)throw new d("EMPTY_RESPONSE","No result","Try a different image");if(e["no-download"])return[{status:"generated",file:"-",size:"-",url:t}];try{const n=`yollomi_bg_${Date.now()}.png`,{path:l,size:s}=await y(t,e.output,n);return[{status:"saved",file:u.relative(".",l),size:O(s),url:t}]}catch{return[{status:"download-failed",file:"-",size:"-",url:t}]}}});
@@ -1 +1 @@
1
- import*as f from"node:path";import{cli as w,Strategy as h}from"@jackwener/opencli/registry";import{CliError as y}from"@jackwener/opencli/errors";import{log as l}from"@jackwener/opencli/logger";import{YOLLOMI_DOMAIN as q,yollomiPost as O,downloadOutput as E,fmtBytes as I}from"./utils.js";w({site:"yollomi",name:"edit",access:"write",description:"Edit images with AI text prompts (Qwen image edit)",domain:q,strategy:h.COOKIE,args:[{name:"image",positional:!0,required:!0,help:'Input image URL (upload via "opencli yollomi upload" first)'},{name:"prompt",positional:!0,required:!0,help:'Editing instruction (e.g. "Make it look vintage")'},{name:"model",default:"qwen-image-edit",choices:["qwen-image-edit","qwen-image-edit-plus"],help:"Edit model"},{name:"output",default:"./yollomi-output",help:"Output directory"},{name:"no-download",type:"boolean",default:!1,help:"Only show URL"}],columns:["status","file","size","credits","url"],func:async(d,e)=>{const{image:s,prompt:m,model:a}=e;let n;if(a==="qwen-image-edit-plus")n={prompt:m,images:[s]};else n={image:s,prompt:m,go_fast:!0,output_format:"png"};const p=a==="qwen-image-edit-plus"?"/api/ai/qwen-image-edit-plus":"/api/ai/qwen-image-edit";l.status(`Editing with ${a}...`);const i=await O(d,p,n),r=i.images||(i.image?[i.image]:[]);if(!r.length)throw new y("EMPTY_RESPONSE","No result","Try a different prompt");const t=i.remainingCredits,o=r[0];if(e["no-download"])return[{status:"edited",file:"-",size:"-",credits:t??"-",url:o}];try{const u=`yollomi_edit_${Date.now()}.png`,{path:g,size:c}=await E(o,e.output,u);if(t!==void 0)l.status(`Credits remaining: ${t}`);return[{status:"saved",file:f.relative(".",g),size:I(c),credits:t??"-",url:o}]}catch{return[{status:"download-failed",file:"-",size:"-",credits:t??"-",url:o}]}}});
1
+ import*as f from"node:path";import{cli as w,Strategy as h}from"@jackwener/opencli/registry";import{CliError as y}from"@jackwener/opencli/errors";import{log as l}from"@jackwener/opencli/logger";import{YOLLOMI_DOMAIN as q,yollomiPost as O,downloadOutput as E,fmtBytes as I}from"./utils.js";w({site:"yollomi",name:"edit",access:"write",description:"Edit images with AI text prompts (Qwen image edit)",domain:q,strategy:h.COOKIE,args:[{name:"image",positional:!0,required:!0,help:'Input image URL (upload via "ppcli yollomi upload" first)'},{name:"prompt",positional:!0,required:!0,help:'Editing instruction (e.g. "Make it look vintage")'},{name:"model",default:"qwen-image-edit",choices:["qwen-image-edit","qwen-image-edit-plus"],help:"Edit model"},{name:"output",default:"./yollomi-output",help:"Output directory"},{name:"no-download",type:"boolean",default:!1,help:"Only show URL"}],columns:["status","file","size","credits","url"],func:async(d,e)=>{const{image:s,prompt:m,model:a}=e;let n;if(a==="qwen-image-edit-plus")n={prompt:m,images:[s]};else n={image:s,prompt:m,go_fast:!0,output_format:"png"};const p=a==="qwen-image-edit-plus"?"/api/ai/qwen-image-edit-plus":"/api/ai/qwen-image-edit";l.status(`Editing with ${a}...`);const i=await O(d,p,n),r=i.images||(i.image?[i.image]:[]);if(!r.length)throw new y("EMPTY_RESPONSE","No result","Try a different prompt");const t=i.remainingCredits,o=r[0];if(e["no-download"])return[{status:"edited",file:"-",size:"-",credits:t??"-",url:o}];try{const u=`yollomi_edit_${Date.now()}.png`,{path:g,size:c}=await E(o,e.output,u);if(t!==void 0)l.status(`Credits remaining: ${t}`);return[{status:"saved",file:f.relative(".",g),size:I(c),credits:t??"-",url:o}]}catch{return[{status:"download-failed",file:"-",size:"-",credits:t??"-",url:o}]}}});
@@ -1 +1 @@
1
- import*as w from"node:path";import{cli as b,Strategy as I}from"@jackwener/opencli/registry";import{CliError as c}from"@jackwener/opencli/errors";import{log as f}from"@jackwener/opencli/logger";import{YOLLOMI_DOMAIN as U,yollomiPost as z,downloadOutput as D,fmtBytes as E,MODEL_ROUTES as L}from"./utils.js";function P(p){const e={"1:1":[1024,1024],"16:9":[1344,768],"9:16":[768,1344],"4:3":[1152,896],"3:4":[896,1152]},[n,t]=e[p]||[1024,1024];return{width:n,height:t}}b({site:"yollomi",name:"generate",access:"write",description:"Generate images with AI (text-to-image or image-to-image)",domain:U,strategy:I.COOKIE,args:[{name:"prompt",positional:!0,required:!0,help:"Text prompt describing the image"},{name:"model",default:"z-image-turbo",help:"Model ID (z-image-turbo, flux-schnell, nano-banana, flux-2-pro, ...)"},{name:"ratio",default:"1:1",choices:["1:1","16:9","9:16","4:3","3:4"],help:"Aspect ratio"},{name:"image",help:'Input image URL for image-to-image (upload via "opencli yollomi upload" first)'},{name:"output",default:"./yollomi-output",help:"Output directory"},{name:"no-download",type:"boolean",default:!1,help:"Only show URLs, skip download"}],columns:["index","status","file","size","url"],func:async(p,e)=>{const{prompt:n,model:t,ratio:l}=e,d=L[t];if(!d)throw new c("INVALID_MODEL",`Unknown model: ${t}`,'Run "opencli yollomi models --type image" to see available models');let i;if(t==="z-image-turbo"){const{width:o,height:a}=P(l);i={prompt:n,width:o,height:a,output_format:"jpg",output_quality:85,guidance_scale:0,num_inference_steps:8}}else if(t==="flux-2-pro"){i={prompt:n,aspectRatio:l,outputNumber:1};if(e.image)i.imageUrl=e.image}else if(t==="flux-kontext-pro"){i={prompt:n,output_format:"jpg"};if(e.image)i.imageUrl=e.image;if(l!=="1:1")i.aspect_ratio=l}else{i={prompt:n,aspect_ratio:l};if(e.image)i.imageUrl=e.image}f.status(`Generating with ${t}...`);const r=await z(p,d,i),u=r.images||(r.image?[r.image]:[]);if(!u.length)throw new c("EMPTY_RESPONSE","No images returned","Try a different prompt or model");const g=e["no-download"],h=e.output,m=[];for(let o=0;o<u.length;o++){const a=u[o];if(g){m.push({index:o+1,status:"generated",file:"-",size:"-",url:a});continue}try{const s=(()=>{try{return new URL(a).pathname}catch{return a}})(),y=s.endsWith(".png")||s.endsWith(".webp")?s.slice(s.lastIndexOf(".")):".jpg",x=`yollomi_${t}_${Date.now()}_${o+1}${y}`,{path:O,size:_}=await D(a,h,x);m.push({index:o+1,status:"saved",file:w.relative(".",O),size:E(_),url:a})}catch{m.push({index:o+1,status:"download-failed",file:"-",size:"-",url:a})}}if(r.remainingCredits!==void 0)f.status(`Credits remaining: ${r.remainingCredits}`);return m}});
1
+ import*as w from"node:path";import{cli as b,Strategy as I}from"@jackwener/opencli/registry";import{CliError as c}from"@jackwener/opencli/errors";import{log as f}from"@jackwener/opencli/logger";import{YOLLOMI_DOMAIN as U,yollomiPost as z,downloadOutput as D,fmtBytes as E,MODEL_ROUTES as L}from"./utils.js";function P(p){const e={"1:1":[1024,1024],"16:9":[1344,768],"9:16":[768,1344],"4:3":[1152,896],"3:4":[896,1152]},[n,t]=e[p]||[1024,1024];return{width:n,height:t}}b({site:"yollomi",name:"generate",access:"write",description:"Generate images with AI (text-to-image or image-to-image)",domain:U,strategy:I.COOKIE,args:[{name:"prompt",positional:!0,required:!0,help:"Text prompt describing the image"},{name:"model",default:"z-image-turbo",help:"Model ID (z-image-turbo, flux-schnell, nano-banana, flux-2-pro, ...)"},{name:"ratio",default:"1:1",choices:["1:1","16:9","9:16","4:3","3:4"],help:"Aspect ratio"},{name:"image",help:'Input image URL for image-to-image (upload via "ppcli yollomi upload" first)'},{name:"output",default:"./yollomi-output",help:"Output directory"},{name:"no-download",type:"boolean",default:!1,help:"Only show URLs, skip download"}],columns:["index","status","file","size","url"],func:async(p,e)=>{const{prompt:n,model:t,ratio:l}=e,d=L[t];if(!d)throw new c("INVALID_MODEL",`Unknown model: ${t}`,'Run "ppcli yollomi models --type image" to see available models');let i;if(t==="z-image-turbo"){const{width:o,height:a}=P(l);i={prompt:n,width:o,height:a,output_format:"jpg",output_quality:85,guidance_scale:0,num_inference_steps:8}}else if(t==="flux-2-pro"){i={prompt:n,aspectRatio:l,outputNumber:1};if(e.image)i.imageUrl=e.image}else if(t==="flux-kontext-pro"){i={prompt:n,output_format:"jpg"};if(e.image)i.imageUrl=e.image;if(l!=="1:1")i.aspect_ratio=l}else{i={prompt:n,aspect_ratio:l};if(e.image)i.imageUrl=e.image}f.status(`Generating with ${t}...`);const r=await z(p,d,i),u=r.images||(r.image?[r.image]:[]);if(!u.length)throw new c("EMPTY_RESPONSE","No images returned","Try a different prompt or model");const g=e["no-download"],h=e.output,m=[];for(let o=0;o<u.length;o++){const a=u[o];if(g){m.push({index:o+1,status:"generated",file:"-",size:"-",url:a});continue}try{const s=(()=>{try{return new URL(a).pathname}catch{return a}})(),y=s.endsWith(".png")||s.endsWith(".webp")?s.slice(s.lastIndexOf(".")):".jpg",x=`yollomi_${t}_${Date.now()}_${o+1}${y}`,{path:O,size:_}=await D(a,h,x);m.push({index:o+1,status:"saved",file:w.relative(".",O),size:E(_),url:a})}catch{m.push({index:o+1,status:"download-failed",file:"-",size:"-",url:a})}}if(r.remainingCredits!==void 0)f.status(`Credits remaining: ${r.remainingCredits}`);return m}});
@@ -1 +1 @@
1
- import*as n from"node:path";import{cli as s,Strategy as u}from"@jackwener/opencli/registry";import{CliError as p}from"@jackwener/opencli/errors";import{log as m}from"@jackwener/opencli/logger";import{YOLLOMI_DOMAIN as c,yollomiPost as h,downloadOutput as y,fmtBytes as d}from"./utils.js";s({site:"yollomi",name:"try-on",access:"write",description:"Virtual try-on — see how clothes look on a person (3 credits)",domain:c,strategy:u.COOKIE,args:[{name:"person",required:!0,help:'Person photo URL (upload via "opencli yollomi upload" first)'},{name:"cloth",required:!0,help:"Clothing image URL"},{name:"cloth-type",default:"upper",choices:["upper","lower","overall"],help:"Clothing type"},{name:"output",default:"./yollomi-output",help:"Output directory"},{name:"no-download",type:"boolean",default:!1,help:"Only show URL"}],columns:["status","file","size","url"],func:async(l,t)=>{m.status("Processing virtual try-on...");const e=await h(l,"/api/ai/virtual-try-on",{person_image:t.person,cloth_image:t.cloth,cloth_type:t["cloth-type"],output_format:"png",output_quality:100}),o=e.image||e.images?.[0];if(!o)throw new p("EMPTY_RESPONSE","No result","Check both images have clear subjects");if(t["no-download"])return[{status:"generated",file:"-",size:"-",url:o}];try{const a=`yollomi_tryon_${Date.now()}.png`,{path:i,size:r}=await y(o,t.output,a);return[{status:"saved",file:n.relative(".",i),size:d(r),url:o}]}catch{return[{status:"download-failed",file:"-",size:"-",url:o}]}}});
1
+ import*as n from"node:path";import{cli as s,Strategy as u}from"@jackwener/opencli/registry";import{CliError as p}from"@jackwener/opencli/errors";import{log as m}from"@jackwener/opencli/logger";import{YOLLOMI_DOMAIN as c,yollomiPost as h,downloadOutput as y,fmtBytes as d}from"./utils.js";s({site:"yollomi",name:"try-on",access:"write",description:"Virtual try-on — see how clothes look on a person (3 credits)",domain:c,strategy:u.COOKIE,args:[{name:"person",required:!0,help:'Person photo URL (upload via "ppcli yollomi upload" first)'},{name:"cloth",required:!0,help:"Clothing image URL"},{name:"cloth-type",default:"upper",choices:["upper","lower","overall"],help:"Clothing type"},{name:"output",default:"./yollomi-output",help:"Output directory"},{name:"no-download",type:"boolean",default:!1,help:"Only show URL"}],columns:["status","file","size","url"],func:async(l,t)=>{m.status("Processing virtual try-on...");const e=await h(l,"/api/ai/virtual-try-on",{person_image:t.person,cloth_image:t.cloth,cloth_type:t["cloth-type"],output_format:"png",output_quality:100}),o=e.image||e.images?.[0];if(!o)throw new p("EMPTY_RESPONSE","No result","Check both images have clear subjects");if(t["no-download"])return[{status:"generated",file:"-",size:"-",url:o}];try{const a=`yollomi_tryon_${Date.now()}.png`,{path:i,size:r}=await y(o,t.output,a);return[{status:"saved",file:n.relative(".",i),size:d(r),url:o}]}catch{return[{status:"download-failed",file:"-",size:"-",url:o}]}}});
@@ -48,7 +48,7 @@ import{cli as U,Strategy as _}from"@jackwener/opencli/registry";import{ArgumentE
48
48
  }
49
49
  return '';
50
50
  })()
51
- `))||"");if(W&&H.test(W))return!0;await q.wait({time:5})}return!1}U({site:"youtube",name:"publish",access:"write",description:"Publish a video to YouTube via Studio (one command: upload + metadata + publish)",domain:"www.youtube.com",strategy:_.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"video",required:!0,positional:!0,help:"Video file path"},{name:"title",required:!0,help:"Video title (≤100 chars)"},{name:"description",default:"",help:"Video description"},{name:"tags",default:"",help:"Comma-separated tags"},{name:"thumbnail",default:"",help:"Thumbnail image path (optional)"},{name:"playlist",default:"",help:"Playlist name to add to (optional)"},{name:"visibility",default:"public",choices:["public","unlisted","private"],help:"Visibility"}],columns:["status","title","visibility","url"],func:async(q,z)=>{const H=String(z.title??"").trim();if(!H)throw new C("--title is required");const Q=h(z.video),W=String(z.description||""),Y=z.thumbnail?y(z.thumbnail):"",$=String(z.playlist||"").trim(),j=O[z.visibility]?z.visibility:"public";await q.goto(F);await q.wait({time:3});const A=await v(q);if(/accounts\.google\.com/.test(A)||/signin/i.test(A))throw new X("YouTube 登录态失效,请先 opencli youtube login");if(!await D(q,'input[type="file"]',60000))throw new X("YouTube 上传文件输入框未出现");await B(q,['input[type="file"]'],[Q]);if(!await D(q,"#title-textarea",120000))throw new X("YouTube 视频详情对话框未出现");await R(q,"#title-textarea #textbox",H.slice(0,100));if(W.trim())await R(q,"#description-textarea #textbox",W);if(Y)try{await B(q,["#file-loader input[type='file']","ytcp-thumbnail-uploader input[type='file']"],[Y]);await q.wait({time:2})}catch{}if($)try{await G(q,"#basics ytcp-text-dropdown-trigger, ytcp-video-metadata-playlists ytcp-dropdown-trigger",8000);await q.wait({time:1.2});const K=`tp-yt-paper-checkbox:has-text('${$}'), ytcp-checkbox-group:has-text('${$}')`;if(await M(q,K))await G(q,K,3000);else if(await G(q,"ytcp-button:has-text('New playlist'), ytcp-button:has-text('创建播放列表')",4000)){await q.wait({time:0.8});await G(q,"tp-yt-paper-item:has-text('New playlist'), tp-yt-paper-item:has-text('新建播放列表')",3000);const J="ytcp-playlist-metadata-editor #textbox, #create-playlist-form #textbox";if(await D(q,J.split(",")[0].trim(),4000)||await M(q,J)){await G(q,J,2000);await q.insertText($);await G(q,"ytcp-button#create-button, tp-yt-paper-dialog ytcp-button:has-text('Create'), tp-yt-paper-dialog ytcp-button:has-text('创建')",4000)}}}catch{}finally{await G(q,"ytcp-playlist-dialog #save-button, ytcp-button:has-text('Done'), ytcp-button:has-text('完成')",3000);await q.pressKey("Escape");await q.wait({time:0.6})}if(!await G(q,"tp-yt-paper-radio-button[name='VIDEO_MADE_FOR_KIDS_NOT_MFK']",1e4))await G(q,"tp-yt-paper-radio-button:has-text('not made for kids'), tp-yt-paper-radio-button:has-text('不是面向儿童')",6000);if(z.tags){const K=String(z.tags).split(",").map((J)=>J.trim()).filter(Boolean).join(",");try{await G(q,"#toggle-button",6000);await q.wait({time:0.8});const J="#tags-container #text-input, ytcp-form-input-container#tags-container input";if(await M(q,J)){await G(q,J,3000);await q.insertText(`${K.slice(0,500)},`)}}catch{}}for(let K=0;K<5;K++){if(await M(q,"tp-yt-paper-radio-button[name='PUBLIC']"))break;if(!await G(q,"#next-button",6000))await q.wait({time:1.2});await q.wait({time:1})}await G(q,`tp-yt-paper-radio-button[name='${O[j]}']`,1e4);await L(q);await q.wait({time:1.2});let N="";if(!await G(q,"#done-button",15000))throw new X("未找到 YouTube 发布按钮(上传可能未到可发布进度),请在窗口内手动发布");await q.wait({time:4});N=String(Z(await q.evaluate(`
51
+ `))||"");if(W&&H.test(W))return!0;await q.wait({time:5})}return!1}U({site:"youtube",name:"publish",access:"write",description:"Publish a video to YouTube via Studio (one command: upload + metadata + publish)",domain:"www.youtube.com",strategy:_.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"video",required:!0,positional:!0,help:"Video file path"},{name:"title",required:!0,help:"Video title (≤100 chars)"},{name:"description",default:"",help:"Video description"},{name:"tags",default:"",help:"Comma-separated tags"},{name:"thumbnail",default:"",help:"Thumbnail image path (optional)"},{name:"playlist",default:"",help:"Playlist name to add to (optional)"},{name:"visibility",default:"public",choices:["public","unlisted","private"],help:"Visibility"}],columns:["status","title","visibility","url"],func:async(q,z)=>{const H=String(z.title??"").trim();if(!H)throw new C("--title is required");const Q=h(z.video),W=String(z.description||""),Y=z.thumbnail?y(z.thumbnail):"",$=String(z.playlist||"").trim(),j=O[z.visibility]?z.visibility:"public";await q.goto(F);await q.wait({time:3});const A=await v(q);if(/accounts\.google\.com/.test(A)||/signin/i.test(A))throw new X("YouTube 登录态失效,请先 ppcli youtube login");if(!await D(q,'input[type="file"]',60000))throw new X("YouTube 上传文件输入框未出现");await B(q,['input[type="file"]'],[Q]);if(!await D(q,"#title-textarea",120000))throw new X("YouTube 视频详情对话框未出现");await R(q,"#title-textarea #textbox",H.slice(0,100));if(W.trim())await R(q,"#description-textarea #textbox",W);if(Y)try{await B(q,["#file-loader input[type='file']","ytcp-thumbnail-uploader input[type='file']"],[Y]);await q.wait({time:2})}catch{}if($)try{await G(q,"#basics ytcp-text-dropdown-trigger, ytcp-video-metadata-playlists ytcp-dropdown-trigger",8000);await q.wait({time:1.2});const K=`tp-yt-paper-checkbox:has-text('${$}'), ytcp-checkbox-group:has-text('${$}')`;if(await M(q,K))await G(q,K,3000);else if(await G(q,"ytcp-button:has-text('New playlist'), ytcp-button:has-text('创建播放列表')",4000)){await q.wait({time:0.8});await G(q,"tp-yt-paper-item:has-text('New playlist'), tp-yt-paper-item:has-text('新建播放列表')",3000);const J="ytcp-playlist-metadata-editor #textbox, #create-playlist-form #textbox";if(await D(q,J.split(",")[0].trim(),4000)||await M(q,J)){await G(q,J,2000);await q.insertText($);await G(q,"ytcp-button#create-button, tp-yt-paper-dialog ytcp-button:has-text('Create'), tp-yt-paper-dialog ytcp-button:has-text('创建')",4000)}}}catch{}finally{await G(q,"ytcp-playlist-dialog #save-button, ytcp-button:has-text('Done'), ytcp-button:has-text('完成')",3000);await q.pressKey("Escape");await q.wait({time:0.6})}if(!await G(q,"tp-yt-paper-radio-button[name='VIDEO_MADE_FOR_KIDS_NOT_MFK']",1e4))await G(q,"tp-yt-paper-radio-button:has-text('not made for kids'), tp-yt-paper-radio-button:has-text('不是面向儿童')",6000);if(z.tags){const K=String(z.tags).split(",").map((J)=>J.trim()).filter(Boolean).join(",");try{await G(q,"#toggle-button",6000);await q.wait({time:0.8});const J="#tags-container #text-input, ytcp-form-input-container#tags-container input";if(await M(q,J)){await G(q,J,3000);await q.insertText(`${K.slice(0,500)},`)}}catch{}}for(let K=0;K<5;K++){if(await M(q,"tp-yt-paper-radio-button[name='PUBLIC']"))break;if(!await G(q,"#next-button",6000))await q.wait({time:1.2});await q.wait({time:1})}await G(q,`tp-yt-paper-radio-button[name='${O[j]}']`,1e4);await L(q);await q.wait({time:1.2});let N="";if(!await G(q,"#done-button",15000))throw new X("未找到 YouTube 发布按钮(上传可能未到可发布进度),请在窗口内手动发布");await q.wait({time:4});N=String(Z(await q.evaluate(`
52
52
  (() => {
53
53
  const a = document.querySelector("a[href*='youtu.be'], a[href*='watch?v=']");
54
54
  return a ? (a.getAttribute('href') || '') : '';
@@ -1,4 +1,4 @@
1
- import{cli as S,Strategy as L}from"@jackwener/opencli/registry";import{ArgumentError as g,AuthRequiredError as N,CommandExecutionError as c,EmptyResultError as _}from"@jackwener/opencli/errors";import{stripHtml as T}from"./text.js";function E(e){return T(e,{preserveBlocks:!0})}const Z=/^\d+$/,C=/^answer:(\d+):(\d+)$/,I=/^\/question\/(\d+)\/answer\/(\d+)\/?$/,U=/^\/answer\/(\d+)\/?$/;function $(e){const n=String(e??"").trim();if(!n)return null;if(Z.test(n))return{answerId:n,questionId:""};const t=n.match(C);if(t)return{questionId:t[1],answerId:t[2]};try{const r=new URL(n);if(r.protocol!=="https:"||r.username||r.password||r.port||r.hostname!=="www.zhihu.com"&&r.hostname!=="zhihu.com")return null;let i=r.pathname.match(I);if(i)return{questionId:i[1],answerId:i[2]};i=r.pathname.match(U);if(i)return{answerId:i[1],questionId:""}}catch{return null}return null}function q(e){const n=String(e??"").trim();if(!n)return"";try{const t=new URL(n);if(t.protocol!=="https:"||t.hostname!=="www.zhihu.com"&&t.hostname!=="zhihu.com")return"";return t.pathname.match(I)?.[1]||""}catch{return""}}function x(e){return Number.isInteger(e)&&e>=0?e:0}function k(e){return typeof e==="number"&&Number.isFinite(e)&&e>0?new Date(e*1000).toISOString():""}function b(e){return e?.member?.name||e?.name||""}function v(e){if(typeof e==="string")return e.trim();if(typeof e==="number"&&Number.isSafeInteger(e)&&e>=0)return String(e);return""}function H(e,n,t,r){if(n&&t&&r)return`https://www.zhihu.com/question/${n}/answer/${t}#comment-${r}`;return typeof e==="string"?e:""}function R(e,n){if(typeof e!=="string"||!e)return"";try{const t=new URL(e),r=`/api/v4/answers/${n}/comments`,i=`/answers/${n}/comments`;if(t.protocol!=="https:"||t.username||t.password||t.port)return"";if(t.hostname==="www.zhihu.com"&&t.pathname===r)return t.toString();if(t.hostname==="api.zhihu.com"&&t.pathname===i)return`https://www.zhihu.com${r}${t.search}`}catch{return""}return""}function y(e,{answerId:n,questionId:t,topLevelLimit:r,repliesLimit:i}){const a=[];let w=0,l=0,u=0,f=!1,h=0;for(const s of e){if(!s||typeof s!=="object"||Array.isArray(s)){h+=1;continue}const o=v(s.id);if(!o){h+=1;continue}const d=b(s.author),p=b(s.reply_to_author),m=Boolean(p);if(!m){if(w>=r){f=!0;break}w+=1;l=w;u=0}else if(!l||u>=i)continue;else u+=1;a.push({rank:a.length+1,comment_rank:l,reply_rank:m?u:0,depth:0,id:o,parent_id:"",author:d||"anonymous",reply_to:p,likes:x(s.vote_count),created_at:k(s.created_time),url:H(s.url,t,n,o),content:E(s.content||"")})}return{rows:a,topLevelCount:w,reachedTopLevelLimit:f,malformedComments:h}}const A=1000,z=100,P=20;S({site:"zhihu",name:"answer-comments",access:"read",description:"知乎回答评论列表",domain:"www.zhihu.com",strategy:L.COOKIE,args:[{name:"id",required:!0,positional:!0,help:"Answer ID, full Zhihu answer URL, or typed target (answer:<qid>:<aid>)"},{name:"limit",type:"int",default:20,help:"Number of top-level comments (max 1000)"},{name:"replies-limit",type:"int",default:3,help:"Number of replies to include per top-level comment (max 100)"}],columns:["rank","comment_rank","reply_rank","depth","id","parent_id","author","reply_to","likes","created_at","url","content"],func:async(e,n)=>{const t=$(n.id);if(!t)throw new g("Answer ID must be a numeric id, a Zhihu answer URL, or answer:<qid>:<aid>","Example: opencli zhihu answer-comments 1937205528846655537");const r=Number(n.limit??20);if(!Number.isInteger(r)||r<=0||r>A)throw new g(`--limit must be a positive integer no greater than ${A}`);const i=Number(n["replies-limit"]??3);if(!Number.isInteger(i)||i<0||i>z)throw new g(`--replies-limit must be an integer between 0 and ${z}`);const{answerId:a}=t;try{await e.goto(`https://www.zhihu.com/answer/${a}`)}catch(o){throw new c(`Failed to open Zhihu answer ${a}: ${o instanceof Error?o.message:String(o)}`,"Open the answer URL in Chrome and retry after the page is reachable.")}const w=e.getCurrentUrl?q(await e.getCurrentUrl().catch(()=>"")):"",l=t.questionId||w;let u=`https://www.zhihu.com/api/v4/answers/${a}/comments?order=normal&limit=${P}&offset=0&status=open`;const f=[],h=new Set;while(u&&!h.has(u)){h.add(u);const o=await e.evaluate(`
1
+ import{cli as S,Strategy as L}from"@jackwener/opencli/registry";import{ArgumentError as g,AuthRequiredError as N,CommandExecutionError as c,EmptyResultError as _}from"@jackwener/opencli/errors";import{stripHtml as T}from"./text.js";function E(e){return T(e,{preserveBlocks:!0})}const Z=/^\d+$/,C=/^answer:(\d+):(\d+)$/,I=/^\/question\/(\d+)\/answer\/(\d+)\/?$/,U=/^\/answer\/(\d+)\/?$/;function $(e){const n=String(e??"").trim();if(!n)return null;if(Z.test(n))return{answerId:n,questionId:""};const t=n.match(C);if(t)return{questionId:t[1],answerId:t[2]};try{const r=new URL(n);if(r.protocol!=="https:"||r.username||r.password||r.port||r.hostname!=="www.zhihu.com"&&r.hostname!=="zhihu.com")return null;let i=r.pathname.match(I);if(i)return{questionId:i[1],answerId:i[2]};i=r.pathname.match(U);if(i)return{answerId:i[1],questionId:""}}catch{return null}return null}function q(e){const n=String(e??"").trim();if(!n)return"";try{const t=new URL(n);if(t.protocol!=="https:"||t.hostname!=="www.zhihu.com"&&t.hostname!=="zhihu.com")return"";return t.pathname.match(I)?.[1]||""}catch{return""}}function x(e){return Number.isInteger(e)&&e>=0?e:0}function k(e){return typeof e==="number"&&Number.isFinite(e)&&e>0?new Date(e*1000).toISOString():""}function b(e){return e?.member?.name||e?.name||""}function v(e){if(typeof e==="string")return e.trim();if(typeof e==="number"&&Number.isSafeInteger(e)&&e>=0)return String(e);return""}function H(e,n,t,r){if(n&&t&&r)return`https://www.zhihu.com/question/${n}/answer/${t}#comment-${r}`;return typeof e==="string"?e:""}function R(e,n){if(typeof e!=="string"||!e)return"";try{const t=new URL(e),r=`/api/v4/answers/${n}/comments`,i=`/answers/${n}/comments`;if(t.protocol!=="https:"||t.username||t.password||t.port)return"";if(t.hostname==="www.zhihu.com"&&t.pathname===r)return t.toString();if(t.hostname==="api.zhihu.com"&&t.pathname===i)return`https://www.zhihu.com${r}${t.search}`}catch{return""}return""}function y(e,{answerId:n,questionId:t,topLevelLimit:r,repliesLimit:i}){const a=[];let w=0,l=0,u=0,f=!1,h=0;for(const s of e){if(!s||typeof s!=="object"||Array.isArray(s)){h+=1;continue}const o=v(s.id);if(!o){h+=1;continue}const d=b(s.author),p=b(s.reply_to_author),m=Boolean(p);if(!m){if(w>=r){f=!0;break}w+=1;l=w;u=0}else if(!l||u>=i)continue;else u+=1;a.push({rank:a.length+1,comment_rank:l,reply_rank:m?u:0,depth:0,id:o,parent_id:"",author:d||"anonymous",reply_to:p,likes:x(s.vote_count),created_at:k(s.created_time),url:H(s.url,t,n,o),content:E(s.content||"")})}return{rows:a,topLevelCount:w,reachedTopLevelLimit:f,malformedComments:h}}const A=1000,z=100,P=20;S({site:"zhihu",name:"answer-comments",access:"read",description:"知乎回答评论列表",domain:"www.zhihu.com",strategy:L.COOKIE,args:[{name:"id",required:!0,positional:!0,help:"Answer ID, full Zhihu answer URL, or typed target (answer:<qid>:<aid>)"},{name:"limit",type:"int",default:20,help:"Number of top-level comments (max 1000)"},{name:"replies-limit",type:"int",default:3,help:"Number of replies to include per top-level comment (max 100)"}],columns:["rank","comment_rank","reply_rank","depth","id","parent_id","author","reply_to","likes","created_at","url","content"],func:async(e,n)=>{const t=$(n.id);if(!t)throw new g("Answer ID must be a numeric id, a Zhihu answer URL, or answer:<qid>:<aid>","Example: ppcli zhihu answer-comments 1937205528846655537");const r=Number(n.limit??20);if(!Number.isInteger(r)||r<=0||r>A)throw new g(`--limit must be a positive integer no greater than ${A}`);const i=Number(n["replies-limit"]??3);if(!Number.isInteger(i)||i<0||i>z)throw new g(`--replies-limit must be an integer between 0 and ${z}`);const{answerId:a}=t;try{await e.goto(`https://www.zhihu.com/answer/${a}`)}catch(o){throw new c(`Failed to open Zhihu answer ${a}: ${o instanceof Error?o.message:String(o)}`,"Open the answer URL in Chrome and retry after the page is reachable.")}const w=e.getCurrentUrl?q(await e.getCurrentUrl().catch(()=>"")):"",l=t.questionId||w;let u=`https://www.zhihu.com/api/v4/answers/${a}/comments?order=normal&limit=${P}&offset=0&status=open`;const f=[],h=new Set;while(u&&!h.has(u)){h.add(u);const o=await e.evaluate(`
2
2
  (async () => {
3
3
  const r = await fetch(${JSON.stringify(u)}, { credentials: 'include' });
4
4
  if (!r.ok) return { __httpError: r.status };
@@ -1,4 +1,4 @@
1
- import{cli as y,Strategy as N}from"@jackwener/opencli/registry";import{ArgumentError as O,AuthRequiredError as P,CommandExecutionError as $,EmptyResultError as q}from"@jackwener/opencli/errors";import{stripHtml as H}from"./text.js";function j(G){return H(G,{preserveBlocks:!0})}const U=/^\d+$/,S=/^answer:(\d+):(\d+)$/,F=/^\/question\/(\d+)\/answer\/(\d+)\/?$/,T=/^\/answer\/(\d+)\/?$/,R=/^\/question\/(\d+)\/?$/,C=/^\/api\/v4\/questions\/(\d+)\/?$/;function D(G){const X=String(G??"").trim();if(!X)return null;if(U.test(X))return{answerId:X,questionId:""};const L=X.match(S);if(L)return{questionId:L[1],answerId:L[2]};try{const K=new URL(X);if(K.protocol!=="https:"||K.username||K.password||K.port||K.hostname!=="www.zhihu.com"&&K.hostname!=="zhihu.com")return null;let Z=K.pathname.match(F);if(Z)return{questionId:Z[1],answerId:Z[2]};Z=K.pathname.match(T);if(Z)return{answerId:Z[1],questionId:""}}catch{return null}return null}function _(G){return D(G)?.answerId??null}function M(G){const X=String(G??"").trim();if(!X)return"";try{const L=new URL(X);if(L.protocol!=="https:"||L.hostname!=="www.zhihu.com"&&L.hostname!=="zhihu.com")return"";return L.pathname.match(F)?.[1]||L.pathname.match(R)?.[1]||L.pathname.match(C)?.[1]||""}catch{return""}}function z(G){return Number.isInteger(G)&&G>=0?G:0}function h(G){return typeof G==="number"&&Number.isFinite(G)&&G>0?new Date(G*1000).toISOString():""}y({site:"zhihu",name:"answer-detail",access:"read",description:"知乎单个回答完整内容(按 answer ID 获取)",domain:"www.zhihu.com",strategy:N.COOKIE,args:[{name:"id",required:!0,positional:!0,help:"Answer ID, full Zhihu answer URL, or typed target (answer:<qid>:<aid>)"},{name:"max-content",type:"int",default:0,help:"Optional cap on stripped content length in characters (0 = no truncation, return the full answer)"}],columns:["id","author","votes","comments","question_id","question_title","url","created_at","updated_at","content"],func:async(G,X)=>{const L=D(X.id);if(!L)throw new O("Answer ID must be a numeric id, a Zhihu answer URL, or answer:<qid>:<aid>","Example: opencli zhihu answer-detail 1937205528846655537");const{answerId:K}=L,Z=X["max-content"],b=Z==null?0:Number(Z);if(!Number.isInteger(b)||b<0)throw new O("--max-content must be a non-negative integer (0 = no cap, full content)","Example: --max-content 2000");try{await G.goto(`https://www.zhihu.com/answer/${K}`)}catch(V){throw new $(`Failed to open Zhihu answer ${K}: ${V instanceof Error?V.message:String(V)}`,"Open the answer URL in Chrome and retry after the page is reachable.")}const W=G.getCurrentUrl?M(await G.getCurrentUrl().catch(()=>"")):"",k=`https://www.zhihu.com/api/v4/answers/${K}?include=content,voteup_count,comment_count,author,created_time,updated_time,question`,J=await G.evaluate(`
1
+ import{cli as y,Strategy as N}from"@jackwener/opencli/registry";import{ArgumentError as O,AuthRequiredError as P,CommandExecutionError as $,EmptyResultError as q}from"@jackwener/opencli/errors";import{stripHtml as H}from"./text.js";function j(G){return H(G,{preserveBlocks:!0})}const U=/^\d+$/,S=/^answer:(\d+):(\d+)$/,F=/^\/question\/(\d+)\/answer\/(\d+)\/?$/,T=/^\/answer\/(\d+)\/?$/,R=/^\/question\/(\d+)\/?$/,C=/^\/api\/v4\/questions\/(\d+)\/?$/;function D(G){const X=String(G??"").trim();if(!X)return null;if(U.test(X))return{answerId:X,questionId:""};const L=X.match(S);if(L)return{questionId:L[1],answerId:L[2]};try{const K=new URL(X);if(K.protocol!=="https:"||K.username||K.password||K.port||K.hostname!=="www.zhihu.com"&&K.hostname!=="zhihu.com")return null;let Z=K.pathname.match(F);if(Z)return{questionId:Z[1],answerId:Z[2]};Z=K.pathname.match(T);if(Z)return{answerId:Z[1],questionId:""}}catch{return null}return null}function _(G){return D(G)?.answerId??null}function M(G){const X=String(G??"").trim();if(!X)return"";try{const L=new URL(X);if(L.protocol!=="https:"||L.hostname!=="www.zhihu.com"&&L.hostname!=="zhihu.com")return"";return L.pathname.match(F)?.[1]||L.pathname.match(R)?.[1]||L.pathname.match(C)?.[1]||""}catch{return""}}function z(G){return Number.isInteger(G)&&G>=0?G:0}function h(G){return typeof G==="number"&&Number.isFinite(G)&&G>0?new Date(G*1000).toISOString():""}y({site:"zhihu",name:"answer-detail",access:"read",description:"知乎单个回答完整内容(按 answer ID 获取)",domain:"www.zhihu.com",strategy:N.COOKIE,args:[{name:"id",required:!0,positional:!0,help:"Answer ID, full Zhihu answer URL, or typed target (answer:<qid>:<aid>)"},{name:"max-content",type:"int",default:0,help:"Optional cap on stripped content length in characters (0 = no truncation, return the full answer)"}],columns:["id","author","votes","comments","question_id","question_title","url","created_at","updated_at","content"],func:async(G,X)=>{const L=D(X.id);if(!L)throw new O("Answer ID must be a numeric id, a Zhihu answer URL, or answer:<qid>:<aid>","Example: ppcli zhihu answer-detail 1937205528846655537");const{answerId:K}=L,Z=X["max-content"],b=Z==null?0:Number(Z);if(!Number.isInteger(b)||b<0)throw new O("--max-content must be a non-negative integer (0 = no cap, full content)","Example: --max-content 2000");try{await G.goto(`https://www.zhihu.com/answer/${K}`)}catch(V){throw new $(`Failed to open Zhihu answer ${K}: ${V instanceof Error?V.message:String(V)}`,"Open the answer URL in Chrome and retry after the page is reachable.")}const W=G.getCurrentUrl?M(await G.getCurrentUrl().catch(()=>"")):"",k=`https://www.zhihu.com/api/v4/answers/${K}?include=content,voteup_count,comment_count,author,created_time,updated_time,question`,J=await G.evaluate(`
2
2
  (async () => {
3
3
  const r = await fetch(${JSON.stringify(k)}, { credentials: 'include' });
4
4
  if (!r.ok) return { __httpError: r.status };
@@ -1,7 +1,7 @@
1
- import{cli as y,Strategy as I}from"@jackwener/opencli/registry";import{ArgumentError as R,AuthRequiredError as L,CommandExecutionError as j,EmptyResultError as v}from"@jackwener/opencli/errors";import{log as w}from"@jackwener/opencli/logger";import{stripHtml as _}from"./text.js";function H(M,D){const z=Number(M);if(!Number.isInteger(z)||z<=0)throw new R(`zhihu collection --${D} must be a positive integer`,"Example: opencli zhihu collection 83283292 --limit 20");return z}function C(M,D){const z=Number(M);if(!Number.isInteger(z)||z<0)throw new R(`zhihu collection --${D} must be a non-negative integer`,"Example: opencli zhihu collection 83283292 --offset 0");return z}async function O(M,D,z,T){const V=`https://www.zhihu.com/api/v4/collections/${D}/items?offset=${z}&limit=${T}`,G=await M.evaluate(`
1
+ import{cli as y,Strategy as I}from"@jackwener/opencli/registry";import{ArgumentError as R,AuthRequiredError as L,CommandExecutionError as j,EmptyResultError as v}from"@jackwener/opencli/errors";import{log as w}from"@jackwener/opencli/logger";import{stripHtml as _}from"./text.js";function H(M,D){const z=Number(M);if(!Number.isInteger(z)||z<=0)throw new R(`zhihu collection --${D} must be a positive integer`,"Example: ppcli zhihu collection 83283292 --limit 20");return z}function C(M,D){const z=Number(M);if(!Number.isInteger(z)||z<0)throw new R(`zhihu collection --${D} must be a non-negative integer`,"Example: ppcli zhihu collection 83283292 --offset 0");return z}async function O(M,D,z,T){const V=`https://www.zhihu.com/api/v4/collections/${D}/items?offset=${z}&limit=${T}`,G=await M.evaluate(`
2
2
  (async () => {
3
3
  const r = await fetch(${JSON.stringify(V)}, { credentials: 'include' });
4
4
  if (!r.ok) return { __httpError: r.status };
5
5
  return await r.json();
6
6
  })()
7
- `);if(!G||G.__httpError){const B=G?.__httpError;if(B===401||B===403)throw new L("www.zhihu.com","Failed to fetch collection data from Zhihu. Please ensure you are logged in.");throw new j(B?`Zhihu collection request failed (HTTP ${B})`:"Zhihu collection request failed","Try again later or rerun with -v for more detail")}return G}function P(M){const D=M?.content||{};return`${D.type||""}:${D.id||D.url||JSON.stringify(D).slice(0,80)}`}function h(M,D){const z=M.content||{},T=z.type||"";if(!["answer","article","pin"].includes(T))throw new j(`Zhihu collection returned unsupported content type: ${T||"missing"}`,"Collection items require a supported content.type so the row identity, title, and URL are not silently blank.");let V="",G="",B="",Q="",W=0;if(T==="answer"){const J=z.question||{};V=J.title||"";G=_(z.content||"").substring(0,150);B=z.url||`https://www.zhihu.com/question/${J.id}/answer/${z.id}`;Q=z.author?.name||"匿名用户";W=z.voteup_count||0}else if(T==="article"){V=z.title||"";G=_(z.content||"").substring(0,150);B=z.url||`https://zhuanlan.zhihu.com/p/${z.id}`;Q=z.author?.name||"匿名用户";W=z.voteup_count||0}else if(T==="pin"){V="想法";G=_((z.content||[]).map((J)=>J.content||"").join(" ")).substring(0,150);B=z.url||`https://www.zhihu.com/pin/${z.id}`;Q=z.author?.name||"匿名用户";W=z.reaction_count||0}if(!String(V||"").trim()||!String(B||"").trim()||B.includes("undefined"))throw new j("Zhihu collection returned a malformed item without title or URL identity","Collection item rows require type, title, and URL so malformed payloads do not become blank listing rows.");return{rank:D,type:T,title:_(V).substring(0,100),author:Q,votes:W,excerpt:G,url:B}}y({site:"zhihu",name:"collection",access:"read",description:"知乎收藏夹内容列表(需要登录)",domain:"www.zhihu.com",strategy:I.COOKIE,browser:!0,args:[{name:"id",positional:!0,required:!0,help:"收藏夹 ID (数字,可从收藏夹 URL 中获取)"},{name:"offset",type:"int",default:0,help:"起始偏移量(用于分页)"},{name:"limit",type:"int",default:20,help:"每页数量(最大 20)"}],columns:["rank","type","title","author","votes","excerpt","url"],func:async(M,D)=>{const{id:z,offset:T=0,limit:V=20}=D,G=String(z);if(!/^\d+$/.test(G))throw new R("Collection ID must be numeric","Example: opencli zhihu collection 83283292");const B=C(T,"offset"),Q=H(V,"limit"),W=Math.min(Q,20);await M.goto("https://www.zhihu.com");const J=[],A=new Set;let X=0,Y=B;const q=Math.ceil(Q/W)+2;for(let $=0;$<q&&J.length<Q;$+=1){const S=Math.min(W,Q-J.length),K=await O(M,G,Y,S),U=Array.isArray(K.data)?K.data:[],b=K.paging||{};X=Number(b.totals||X||0);for(const F of U){const Z=P(F);if(!A.has(Z)){A.add(Z);J.push(F)}if(J.length>=Q)break}if(U.length===0||b.is_end||J.length>=Q)break;if(typeof b.next==="string")try{const F=new URL(b.next),Z=Number(F.searchParams.get("offset"));if(Number.isInteger(Z)&&Z>Y){Y=Z;continue}}catch{}if(U.length<S)break;const N=Y+U.length;if(N<=Y)break;Y=N;if(X&&Y>=X)break}const k=Math.ceil(X/W),E=Math.floor(B/W)+1;if(X>0){w.info(`收藏夹共有 ${X} 条内容,共 ${k} 页`);w.info(`当前第 ${E} 页,显示第 ${B+1} - ${Math.min(B+J.length,X)} 条`)}if(J.length===0)throw new v("zhihu collection",`No items found for collection ${G}. The collection may be empty, private, or the offset may be out of range.`);return J.slice(0,Q).map(($,S)=>h($,B+S+1))}});export const __test__={stripHtml:_,validatePositiveInt:H,validateNonNegativeInt:C,itemKey:P,mapCollectionItem:h};
7
+ `);if(!G||G.__httpError){const B=G?.__httpError;if(B===401||B===403)throw new L("www.zhihu.com","Failed to fetch collection data from Zhihu. Please ensure you are logged in.");throw new j(B?`Zhihu collection request failed (HTTP ${B})`:"Zhihu collection request failed","Try again later or rerun with -v for more detail")}return G}function P(M){const D=M?.content||{};return`${D.type||""}:${D.id||D.url||JSON.stringify(D).slice(0,80)}`}function h(M,D){const z=M.content||{},T=z.type||"";if(!["answer","article","pin"].includes(T))throw new j(`Zhihu collection returned unsupported content type: ${T||"missing"}`,"Collection items require a supported content.type so the row identity, title, and URL are not silently blank.");let V="",G="",B="",Q="",W=0;if(T==="answer"){const J=z.question||{};V=J.title||"";G=_(z.content||"").substring(0,150);B=z.url||`https://www.zhihu.com/question/${J.id}/answer/${z.id}`;Q=z.author?.name||"匿名用户";W=z.voteup_count||0}else if(T==="article"){V=z.title||"";G=_(z.content||"").substring(0,150);B=z.url||`https://zhuanlan.zhihu.com/p/${z.id}`;Q=z.author?.name||"匿名用户";W=z.voteup_count||0}else if(T==="pin"){V="想法";G=_((z.content||[]).map((J)=>J.content||"").join(" ")).substring(0,150);B=z.url||`https://www.zhihu.com/pin/${z.id}`;Q=z.author?.name||"匿名用户";W=z.reaction_count||0}if(!String(V||"").trim()||!String(B||"").trim()||B.includes("undefined"))throw new j("Zhihu collection returned a malformed item without title or URL identity","Collection item rows require type, title, and URL so malformed payloads do not become blank listing rows.");return{rank:D,type:T,title:_(V).substring(0,100),author:Q,votes:W,excerpt:G,url:B}}y({site:"zhihu",name:"collection",access:"read",description:"知乎收藏夹内容列表(需要登录)",domain:"www.zhihu.com",strategy:I.COOKIE,browser:!0,args:[{name:"id",positional:!0,required:!0,help:"收藏夹 ID (数字,可从收藏夹 URL 中获取)"},{name:"offset",type:"int",default:0,help:"起始偏移量(用于分页)"},{name:"limit",type:"int",default:20,help:"每页数量(最大 20)"}],columns:["rank","type","title","author","votes","excerpt","url"],func:async(M,D)=>{const{id:z,offset:T=0,limit:V=20}=D,G=String(z);if(!/^\d+$/.test(G))throw new R("Collection ID must be numeric","Example: ppcli zhihu collection 83283292");const B=C(T,"offset"),Q=H(V,"limit"),W=Math.min(Q,20);await M.goto("https://www.zhihu.com");const J=[],A=new Set;let X=0,Y=B;const q=Math.ceil(Q/W)+2;for(let $=0;$<q&&J.length<Q;$+=1){const S=Math.min(W,Q-J.length),K=await O(M,G,Y,S),U=Array.isArray(K.data)?K.data:[],b=K.paging||{};X=Number(b.totals||X||0);for(const F of U){const Z=P(F);if(!A.has(Z)){A.add(Z);J.push(F)}if(J.length>=Q)break}if(U.length===0||b.is_end||J.length>=Q)break;if(typeof b.next==="string")try{const F=new URL(b.next),Z=Number(F.searchParams.get("offset"));if(Number.isInteger(Z)&&Z>Y){Y=Z;continue}}catch{}if(U.length<S)break;const N=Y+U.length;if(N<=Y)break;Y=N;if(X&&Y>=X)break}const k=Math.ceil(X/W),E=Math.floor(B/W)+1;if(X>0){w.info(`收藏夹共有 ${X} 条内容,共 ${k} 页`);w.info(`当前第 ${E} 页,显示第 ${B+1} - ${Math.min(B+J.length,X)} 条`)}if(J.length===0)throw new v("zhihu collection",`No items found for collection ${G}. The collection may be empty, private, or the offset may be out of range.`);return J.slice(0,Q).map(($,S)=>h($,B+S+1))}});export const __test__={stripHtml:_,validatePositiveInt:H,validateNonNegativeInt:C,itemKey:P,mapCollectionItem:h};
@@ -1,4 +1,4 @@
1
- import{cli as P,Strategy as I}from"@jackwener/opencli/registry";import{ArgumentError as h,AuthRequiredError as w,CommandExecutionError as F,EmptyResultError as O}from"@jackwener/opencli/errors";import{log as v}from"@jackwener/opencli/logger";function J(B,V){const D=Number(B);if(!Number.isInteger(D)||D<=0)throw new h(`zhihu collections --${V} must be a positive integer`,"Example: opencli zhihu collections --limit 20");return D}async function K(B,V,D){const z=await B.evaluate(`
1
+ import{cli as P,Strategy as I}from"@jackwener/opencli/registry";import{ArgumentError as h,AuthRequiredError as w,CommandExecutionError as F,EmptyResultError as O}from"@jackwener/opencli/errors";import{log as v}from"@jackwener/opencli/logger";function J(B,V){const D=Number(B);if(!Number.isInteger(D)||D<=0)throw new h(`zhihu collections --${V} must be a positive integer`,"Example: ppcli zhihu collections --limit 20");return D}async function K(B,V,D){const z=await B.evaluate(`
2
2
  (async () => {
3
3
  const r = await fetch(${JSON.stringify(V)}, { credentials: 'include' });
4
4
  if (!r.ok) return { __httpError: r.status };
@@ -1,4 +1,4 @@
1
- import{cli as H,Strategy as K}from"@jackwener/opencli/registry";import{AuthRequiredError as R,CliError as W}from"@jackwener/opencli/errors";import{stripHtml as S}from"./text.js";function f(b){if(typeof b!=="string")return"";try{const z=new URL(b);if(z.hostname!=="www.zhihu.com"&&z.hostname!=="zhihu.com")return"";return z.pathname.match(/^\/question\/\d+\/answer\/(\d+)\/?$/)?.[1]||z.pathname.match(/^\/api\/v4\/answers\/(\d+)\/?$/)?.[1]||z.pathname.match(/^\/answer\/(\d+)\/?$/)?.[1]||""}catch{return""}}function $(b){const z=f(b.url);if(z)return z;if(typeof b.id==="string"&&/^\d+$/.test(b.id))return b.id;if(typeof b.id==="number"&&Number.isSafeInteger(b.id)&&b.id>0)return String(b.id);return""}function h(b){const z=$(b);if(z)return`id:${z}`;return`fallback:${b.author?.name||"anonymous"}:${b.content||""}`}const x=1000;H({site:"zhihu",name:"question",access:"read",description:"知乎问题详情和回答",domain:"www.zhihu.com",strategy:K.COOKIE,args:[{name:"id",required:!0,positional:!0,help:"Question ID (numeric)"},{name:"limit",type:"int",default:5,help:"Number of answers (max 1000; use normal-sized requests)"},{name:"sort",default:"default",choices:["default","created"],help:"Answer order: default or created"}],columns:["rank","id","author","votes","url","content"],func:async(b,z)=>{const{id:D,limit:F=5}=z,N=String(D);if(!/^\d+$/.test(N))throw new W("INVALID_INPUT","Question ID must be numeric","Example: opencli zhihu question 123456789");const O=Number(F);if(!Number.isInteger(O)||O<=0||O>x)throw new W("INVALID_INPUT",`Limit must be a positive integer no greater than ${x}`,"Use a normal-sized limit to avoid slow requests or Zhihu risk controls");const Q=String(z.sort||"default");if(Q!=="default"&&Q!=="created")throw new W("INVALID_INPUT","Sort must be one of: default, created","Example: opencli zhihu question 123456789 --sort created");await b.goto(Q==="created"?`https://www.zhihu.com/question/${N}/answers/updated`:`https://www.zhihu.com/question/${N}`);let P=`https://www.zhihu.com/api/v4/questions/${N}/answers?limit=${20}&offset=0&sort_by=${Q}&include=data[*].content,url,voteup_count,comment_count,author`;const V=[],Y=new Set,Z=new Set;while(P&&V.length<O&&!Z.has(P)){Z.add(P);const B=await b.evaluate(`
1
+ import{cli as H,Strategy as K}from"@jackwener/opencli/registry";import{AuthRequiredError as R,CliError as W}from"@jackwener/opencli/errors";import{stripHtml as S}from"./text.js";function f(b){if(typeof b!=="string")return"";try{const z=new URL(b);if(z.hostname!=="www.zhihu.com"&&z.hostname!=="zhihu.com")return"";return z.pathname.match(/^\/question\/\d+\/answer\/(\d+)\/?$/)?.[1]||z.pathname.match(/^\/api\/v4\/answers\/(\d+)\/?$/)?.[1]||z.pathname.match(/^\/answer\/(\d+)\/?$/)?.[1]||""}catch{return""}}function $(b){const z=f(b.url);if(z)return z;if(typeof b.id==="string"&&/^\d+$/.test(b.id))return b.id;if(typeof b.id==="number"&&Number.isSafeInteger(b.id)&&b.id>0)return String(b.id);return""}function h(b){const z=$(b);if(z)return`id:${z}`;return`fallback:${b.author?.name||"anonymous"}:${b.content||""}`}const x=1000;H({site:"zhihu",name:"question",access:"read",description:"知乎问题详情和回答",domain:"www.zhihu.com",strategy:K.COOKIE,args:[{name:"id",required:!0,positional:!0,help:"Question ID (numeric)"},{name:"limit",type:"int",default:5,help:"Number of answers (max 1000; use normal-sized requests)"},{name:"sort",default:"default",choices:["default","created"],help:"Answer order: default or created"}],columns:["rank","id","author","votes","url","content"],func:async(b,z)=>{const{id:D,limit:F=5}=z,N=String(D);if(!/^\d+$/.test(N))throw new W("INVALID_INPUT","Question ID must be numeric","Example: ppcli zhihu question 123456789");const O=Number(F);if(!Number.isInteger(O)||O<=0||O>x)throw new W("INVALID_INPUT",`Limit must be a positive integer no greater than ${x}`,"Use a normal-sized limit to avoid slow requests or Zhihu risk controls");const Q=String(z.sort||"default");if(Q!=="default"&&Q!=="created")throw new W("INVALID_INPUT","Sort must be one of: default, created","Example: ppcli zhihu question 123456789 --sort created");await b.goto(Q==="created"?`https://www.zhihu.com/question/${N}/answers/updated`:`https://www.zhihu.com/question/${N}`);let P=`https://www.zhihu.com/api/v4/questions/${N}/answers?limit=${20}&offset=0&sort_by=${Q}&include=data[*].content,url,voteup_count,comment_count,author`;const V=[],Y=new Set,Z=new Set;while(P&&V.length<O&&!Z.has(P)){Z.add(P);const B=await b.evaluate(`
2
2
  (async () => {
3
3
  const r = await fetch(${JSON.stringify(P)}, { credentials: 'include' });
4
4
  if (!r.ok) return { __httpError: r.status };
@@ -1,4 +1,4 @@
1
- import{cli as v,Strategy as h}from"@jackwener/opencli/registry";import{ArgumentError as K,AuthRequiredError as S,CommandExecutionError as $,EmptyResultError as q}from"@jackwener/opencli/errors";import{stripHtml as M}from"./text.js";function U(D){const B=D.object||{};if(B.id!=null)return`${B.type||""}:${B.id}`;return null}function A(D){const B=D.id==null?"":String(D.id);if(D.type==="answer"){const N=D.question?.id==null?"":String(D.question.id);return N&&B?`https://www.zhihu.com/question/${N}/answer/${B}`:""}if(D.type==="article")return B?`https://zhuanlan.zhihu.com/p/${B}`:"";if(D.type==="question")return B?`https://www.zhihu.com/question/${B}`:"";return""}function L(D){if(typeof D!=="string"||!D)return"";try{const B=new URL(D);if(B.hostname==="api.zhihu.com"&&B.pathname==="/search_v3")return`https://www.zhihu.com/api/v4/search_v3${B.search}`;if(B.hostname==="www.zhihu.com"&&B.pathname==="/api/v4/search_v3")return B.toString()}catch{return""}return""}const C=1000,w=20,Q=["all","answer","article","question"];function R(D){const B=Number(D??10);if(!Number.isInteger(B)||B<=0||B>C)throw new K(`zhihu search --limit must be a positive integer no greater than ${C}`,"Use a normal-sized limit to avoid slow requests or Zhihu risk controls");return B}function z(D){const B=String(D||"").trim();if(!B)throw new K("zhihu search query must not be empty","Example: opencli zhihu search codex");return B}function I(D){const B=String(D||"all");if(!Q.includes(B))throw new K(`zhihu search --type must be one of: ${Q.join(", ")}`,"Example: opencli zhihu search codex --type answer");return B}function H(D){if(D&&typeof D==="object"&&"data"in D&&"session"in D)return D.data;return D}function P(D,B){const N=H(D);if(!N||typeof N!=="object"||Array.isArray(N))throw new $("Zhihu search returned malformed payload");if(N.__httpError){const O=N.__httpError;if(O===401||O===403)throw new S("www.zhihu.com","Failed to fetch search results from Zhihu");throw new $(`Zhihu search request failed${O?` (HTTP ${O})`:""}`,"Try again later or rerun with -v for more detail")}if(N.__fetchError)throw new $("Zhihu search request failed",String(N.__fetchError));if(!Array.isArray(N.data))throw new $("Zhihu search returned malformed data list",`URL: ${B}`);if(!N.paging||typeof N.paging!=="object")throw new $("Zhihu search returned malformed paging data",`URL: ${B}`);return N}function T(D){return`
1
+ import{cli as v,Strategy as h}from"@jackwener/opencli/registry";import{ArgumentError as K,AuthRequiredError as S,CommandExecutionError as $,EmptyResultError as q}from"@jackwener/opencli/errors";import{stripHtml as M}from"./text.js";function U(D){const B=D.object||{};if(B.id!=null)return`${B.type||""}:${B.id}`;return null}function A(D){const B=D.id==null?"":String(D.id);if(D.type==="answer"){const N=D.question?.id==null?"":String(D.question.id);return N&&B?`https://www.zhihu.com/question/${N}/answer/${B}`:""}if(D.type==="article")return B?`https://zhuanlan.zhihu.com/p/${B}`:"";if(D.type==="question")return B?`https://www.zhihu.com/question/${B}`:"";return""}function L(D){if(typeof D!=="string"||!D)return"";try{const B=new URL(D);if(B.hostname==="api.zhihu.com"&&B.pathname==="/search_v3")return`https://www.zhihu.com/api/v4/search_v3${B.search}`;if(B.hostname==="www.zhihu.com"&&B.pathname==="/api/v4/search_v3")return B.toString()}catch{return""}return""}const C=1000,w=20,Q=["all","answer","article","question"];function R(D){const B=Number(D??10);if(!Number.isInteger(B)||B<=0||B>C)throw new K(`zhihu search --limit must be a positive integer no greater than ${C}`,"Use a normal-sized limit to avoid slow requests or Zhihu risk controls");return B}function z(D){const B=String(D||"").trim();if(!B)throw new K("zhihu search query must not be empty","Example: ppcli zhihu search codex");return B}function I(D){const B=String(D||"all");if(!Q.includes(B))throw new K(`zhihu search --type must be one of: ${Q.join(", ")}`,"Example: ppcli zhihu search codex --type answer");return B}function H(D){if(D&&typeof D==="object"&&"data"in D&&"session"in D)return D.data;return D}function P(D,B){const N=H(D);if(!N||typeof N!=="object"||Array.isArray(N))throw new $("Zhihu search returned malformed payload");if(N.__httpError){const O=N.__httpError;if(O===401||O===403)throw new S("www.zhihu.com","Failed to fetch search results from Zhihu");throw new $(`Zhihu search request failed${O?` (HTTP ${O})`:""}`,"Try again later or rerun with -v for more detail")}if(N.__fetchError)throw new $("Zhihu search request failed",String(N.__fetchError));if(!Array.isArray(N.data))throw new $("Zhihu search returned malformed data list",`URL: ${B}`);if(!N.paging||typeof N.paging!=="object")throw new $("Zhihu search returned malformed paging data",`URL: ${B}`);return N}function T(D){return`
2
2
  (async () => {
3
3
  try {
4
4
  const r = await fetch(${JSON.stringify(D)}, { credentials: 'include' });
@@ -1 +1 @@
1
- import{CliError as o}from"@jackwener/opencli/errors";const c=/^user:([A-Za-z0-9_-]+)$/,w=/^question:(\d+)$/,u=/^answer:(\d+):(\d+)$/,l=/^article:(\d+)$/,p=/^\/people\/([A-Za-z0-9_-]+)\/?$/,d=/^\/question\/(\d+)\/?$/,m=/^\/question\/(\d+)\/answer\/(\d+)\/?$/,E=/^\/p\/(\d+)\/?$/,_=/^https:\/\/(?::)?@/i;function f(n){return n.protocol==="https:"&&n.username===""&&n.password===""&&n.port===""}export function parseTarget(n){const e=String(n).trim();if(_.test(e))throw new o("INVALID_INPUT","Zhihu write commands require a normal HTTPS Zhihu URL without malformed authority","Example: https://www.zhihu.com/question/123456");if(e.startsWith("answer:")&&!u.test(e))throw new o("INVALID_INPUT","Invalid answer target, expected answer:<questionId>:<answerId>","Example: opencli zhihu like answer:123:456 --execute");let t=e.match(c);if(t)return{kind:"user",slug:t[1],url:`https://www.zhihu.com/people/${t[1]}`};t=e.match(w);if(t)return{kind:"question",id:t[1],url:`https://www.zhihu.com/question/${t[1]}`};t=e.match(u);if(t)return{kind:"answer",questionId:t[1],id:t[2],url:`https://www.zhihu.com/question/${t[1]}/answer/${t[2]}`};t=e.match(l);if(t)return{kind:"article",id:t[1],url:`https://zhuanlan.zhihu.com/p/${t[1]}`};try{const i=new URL(e);if(!f(i))throw Error("unsupported zhihu url variant");if(i.hostname==="www.zhihu.com"){const r=i.pathname.match(p);if(r){const a=r[1];return{kind:"user",slug:a,url:`https://www.zhihu.com/people/${a}`}}const h=i.pathname.match(d);if(h)return{kind:"question",id:h[1],url:`https://www.zhihu.com/question/${h[1]}`};const s=i.pathname.match(m);if(s)return{kind:"answer",questionId:s[1],id:s[2],url:`https://www.zhihu.com/question/${s[1]}/answer/${s[2]}`}}if(i.hostname==="zhuanlan.zhihu.com"){const r=i.pathname.match(E);if(r)return{kind:"article",id:r[1],url:`https://zhuanlan.zhihu.com/p/${r[1]}`}}}catch{}throw new o("INVALID_INPUT","Zhihu write commands require a Zhihu URL or typed target like question:123 or answer:123:456","Example: opencli zhihu like answer:123:456 --execute")}export function assertAllowedKinds(n,e){if(!{follow:["user","question"],like:["answer","article"],favorite:["answer","article"],comment:["answer","article"],answer:["question"]}[n]?.includes(e.kind))throw new o("UNSUPPORTED_TARGET",`zhihu ${n} does not support ${e.kind} targets`);return e}export const __test__={parseTarget,assertAllowedKinds};
1
+ import{CliError as o}from"@jackwener/opencli/errors";const c=/^user:([A-Za-z0-9_-]+)$/,w=/^question:(\d+)$/,u=/^answer:(\d+):(\d+)$/,l=/^article:(\d+)$/,p=/^\/people\/([A-Za-z0-9_-]+)\/?$/,d=/^\/question\/(\d+)\/?$/,m=/^\/question\/(\d+)\/answer\/(\d+)\/?$/,E=/^\/p\/(\d+)\/?$/,_=/^https:\/\/(?::)?@/i;function f(n){return n.protocol==="https:"&&n.username===""&&n.password===""&&n.port===""}export function parseTarget(n){const e=String(n).trim();if(_.test(e))throw new o("INVALID_INPUT","Zhihu write commands require a normal HTTPS Zhihu URL without malformed authority","Example: https://www.zhihu.com/question/123456");if(e.startsWith("answer:")&&!u.test(e))throw new o("INVALID_INPUT","Invalid answer target, expected answer:<questionId>:<answerId>","Example: ppcli zhihu like answer:123:456 --execute");let t=e.match(c);if(t)return{kind:"user",slug:t[1],url:`https://www.zhihu.com/people/${t[1]}`};t=e.match(w);if(t)return{kind:"question",id:t[1],url:`https://www.zhihu.com/question/${t[1]}`};t=e.match(u);if(t)return{kind:"answer",questionId:t[1],id:t[2],url:`https://www.zhihu.com/question/${t[1]}/answer/${t[2]}`};t=e.match(l);if(t)return{kind:"article",id:t[1],url:`https://zhuanlan.zhihu.com/p/${t[1]}`};try{const i=new URL(e);if(!f(i))throw Error("unsupported zhihu url variant");if(i.hostname==="www.zhihu.com"){const r=i.pathname.match(p);if(r){const a=r[1];return{kind:"user",slug:a,url:`https://www.zhihu.com/people/${a}`}}const h=i.pathname.match(d);if(h)return{kind:"question",id:h[1],url:`https://www.zhihu.com/question/${h[1]}`};const s=i.pathname.match(m);if(s)return{kind:"answer",questionId:s[1],id:s[2],url:`https://www.zhihu.com/question/${s[1]}/answer/${s[2]}`}}if(i.hostname==="zhuanlan.zhihu.com"){const r=i.pathname.match(E);if(r)return{kind:"article",id:r[1],url:`https://zhuanlan.zhihu.com/p/${r[1]}`}}}catch{}throw new o("INVALID_INPUT","Zhihu write commands require a Zhihu URL or typed target like question:123 or answer:123:456","Example: ppcli zhihu like answer:123:456 --execute")}export function assertAllowedKinds(n,e){if(!{follow:["user","question"],like:["answer","article"],favorite:["answer","article"],comment:["answer","article"],answer:["question"]}[n]?.includes(e.kind))throw new o("UNSUPPORTED_TARGET",`zhihu ${n} does not support ${e.kind} targets`);return e}export const __test__={parseTarget,assertAllowedKinds};
@@ -1 +1 @@
1
- import{CliError as J}from"@jackwener/opencli/errors";const N=/^[A-Za-z0-9_-]+$/,Q=/^user:([A-Za-z0-9_-]+)$/,V=/^\/people\/([A-Za-z0-9_-]+)\/?$/;export function parseZhihuUser(K){const q=String(K??"").trim();if(!q)throw new J("INVALID_INPUT","A Zhihu user is required","Example: opencli zhihu user wen-jie-16-47");const B=q.match(Q);if(B)return B[1];if(N.test(q))return q;try{const z=new URL(q);if(z.protocol==="https:"&&z.hostname==="www.zhihu.com"){const D=z.pathname.match(V);if(D)return D[1]}}catch{}throw new J("INVALID_INPUT",`Invalid Zhihu user: ${q}`,"Use a url_token (wen-jie-16-47) or a people URL (https://www.zhihu.com/people/wen-jie-16-47)")}
1
+ import{CliError as J}from"@jackwener/opencli/errors";const N=/^[A-Za-z0-9_-]+$/,Q=/^user:([A-Za-z0-9_-]+)$/,V=/^\/people\/([A-Za-z0-9_-]+)\/?$/;export function parseZhihuUser(K){const q=String(K??"").trim();if(!q)throw new J("INVALID_INPUT","A Zhihu user is required","Example: ppcli zhihu user wen-jie-16-47");const B=q.match(Q);if(B)return B[1];if(N.test(q))return q;try{const z=new URL(q);if(z.protocol==="https:"&&z.hostname==="www.zhihu.com"){const D=z.pathname.match(V);if(D)return D[1]}}catch{}throw new J("INVALID_INPUT",`Invalid Zhihu user: ${q}`,"Use a url_token (wen-jie-16-47) or a people URL (https://www.zhihu.com/people/wen-jie-16-47)")}
@@ -1,2 +1,2 @@
1
- import*as d from"node:fs";import*as y from"node:os";import*as o from"node:path";import{fileURLToPath as j}from"node:url";import{findPackageRoot as w,getCliManifestPath as S}from"./package-paths.js";function g(){return o.join(w(j(import.meta.url)),"clis")}function f(e){try{return d.readdirSync(e,{withFileTypes:!0})}catch{return[]}}function C(e){try{const n=d.readFileSync(S(e),"utf-8"),t=JSON.parse(n),i=new Set;for(const s of t){const r=s.sourceFile??s.modulePath;if(r)i.add(o.resolve(e,r))}return i}catch{return new Set}}export function findShadowedUserAdapters(e={}){const n=e.userClisDir??o.join(y.homedir(),".opencli","clis"),t=e.builtinClisDir??g(),i=C(t),s=[];for(const r of f(n)){if(!r.isDirectory())continue;const a=r.name,l=o.join(n,a),m=o.join(t,a);for(const c of f(l)){if(!c.isFile()||!c.name.endsWith(".js"))continue;const p=o.join(l,c.name),u=o.join(m,c.name),h=o.resolve(u);if(!i.has(h))continue;s.push({name:`${a}/${c.name.replace(/\.js$/,"")}`,userPath:p,builtinPath:u})}}return s.sort((r,a)=>r.name.localeCompare(a.name))}export function formatAdapterShadowIssue(e){const n=e.slice(0,10),t=["Local adapter overrides shadow packaged adapters:"];for(const i of n)t.push(` ${i.name}: ${i.userPath} overrides ${i.builtinPath}`);if(e.length>n.length)t.push(` ... and ${e.length-n.length} more`);t.push("Remove the local ~/.opencli/clis copy, or run opencli adapter reset <site>, when you want packaged updates.");return t.join(`
1
+ import*as d from"node:fs";import*as y from"node:os";import*as o from"node:path";import{fileURLToPath as j}from"node:url";import{findPackageRoot as w,getCliManifestPath as S}from"./package-paths.js";function g(){return o.join(w(j(import.meta.url)),"clis")}function f(e){try{return d.readdirSync(e,{withFileTypes:!0})}catch{return[]}}function C(e){try{const n=d.readFileSync(S(e),"utf-8"),t=JSON.parse(n),i=new Set;for(const s of t){const r=s.sourceFile??s.modulePath;if(r)i.add(o.resolve(e,r))}return i}catch{return new Set}}export function findShadowedUserAdapters(e={}){const n=e.userClisDir??o.join(y.homedir(),".ppcli","clis"),t=e.builtinClisDir??g(),i=C(t),s=[];for(const r of f(n)){if(!r.isDirectory())continue;const a=r.name,l=o.join(n,a),m=o.join(t,a);for(const c of f(l)){if(!c.isFile()||!c.name.endsWith(".js"))continue;const p=o.join(l,c.name),u=o.join(m,c.name),h=o.resolve(u);if(!i.has(h))continue;s.push({name:`${a}/${c.name.replace(/\.js$/,"")}`,userPath:p,builtinPath:u})}}return s.sort((r,a)=>r.name.localeCompare(a.name))}export function formatAdapterShadowIssue(e){const n=e.slice(0,10),t=["Local adapter overrides shadow packaged adapters:"];for(const i of n)t.push(` ${i.name}: ${i.userPath} overrides ${i.builtinPath}`);if(e.length>n.length)t.push(` ... and ${e.length-n.length} more`);t.push("Remove the local ~/.ppcli/clis copy, or run ppcli adapter reset <site>, when you want packaged updates.");return t.join(`
2
2
  `)}
@@ -1 +1 @@
1
- const H=[{vendor:"aliyun_waf",cookiePatterns:[/^acw_sc__v2$/,/^acw_tc$/,/^ssxmod_itna/],bodyPatterns:[/arg1\s*=\s*['"][0-9A-F]{30,}/,/\/ntc_captcha\//i],implication:"Direct Node-side fetch/curl will return the slider HTML. Validate the endpoint in browser context first; HTML COOKIE adapters still finish with Node-side fetch + page.getCookies."},{vendor:"cloudflare",cookiePatterns:[/^__cf_bm$/,/^cf_clearance$/,/^__cfduid$/],bodyPatterns:[/Cloudflare Ray ID/i,/Checking your browser before accessing/i,/cf-chl-/i],implication:"Cloudflare bot check. Start from a real browser session; probe in browser context first. HTML COOKIE adapters still finish with Node-side fetch + page.getCookies."},{vendor:"akamai",cookiePatterns:[/^_abck$/,/^bm_sz$/,/^bm_sv$/],bodyPatterns:[/akamai/i],implication:"Akamai Bot Manager. Probe in browser context first; keep final HTML COOKIE adapters on Node-side fetch + page.getCookies."},{vendor:"geetest",cookiePatterns:[],bodyPatterns:[/geetest/i,/gt_captcha/i],implication:"Geetest slider/puzzle captcha. Agent cannot bypass programmatically — requires UI strategy or human-in-loop."}];export function detectAntiBot(q){const M=[];let Q=null;for(const V of H){const Z=[];for(const $ of V.cookiePatterns){const X=q.cookieNames.find((O)=>$.test(O));if(X)Z.push(`cookie:${X}`)}for(const $ of V.bodyPatterns)for(const X of q.networkEntries)if(X.bodyPreview&&$.test(X.bodyPreview)){Z.push(`body:${X.url}`);break}if(Z.length>0&&!Q){Q=V;M.push(...Z)}}if(!Q)return{detected:!1,vendor:null,evidence:[],implication:"No known anti-bot signatures. Try Node-side COOKIE fetch first; if endpoint validation is blocked, retry from browser context."};return{detected:!0,vendor:Q.vendor,evidence:M,implication:Q.implication}}const I=/(?:analytics|beacon|collect|telemetry|tracking|sentry|doubleclick|google-analytics|googletagmanager|adservice|\/ads?(?:[/?#]|$)|metrics?|pixel|personalization|experiment|\/events?(?:[/?#]|$))/i,U=/^(?:data|items?|results?|records?|list|rows?|edges?|nodes?|timeline|users?|title|name|text|content|body|price|amount|id|url|avatar|nickname|desc|comments?|likes?|shares?|total|page|cursor|next|rank|score|date|time|author)$/i,B=/^(?:event|events|trace|traceid|sessionid|clientid|visitorid|experiment|abtest|beacon|analytics|metrics?|pixel|log|logs)$/i;function P(q){return Math.max(0,Math.min(1,Math.round(q*100)/100))}function R(q){if(!q)return null;const M=q.trim();if(!M)return null;try{return JSON.parse(M)}catch{return M}}function Y(q,M="$",Q=[],V=0){if(V>4||Q.length>=24)return Q;if(Array.isArray(q)){Q.push(`${M}:array(${q.length})`);if(q.length>0)Y(q[0],`${M}[0]`,Q,V+1);return Q}if(!q||typeof q!=="object"){Q.push(`${M}:${typeof q}`);return Q}const Z=Object.entries(q).slice(0,12);for(const[$,X]of Z){Q.push(`${M}.${$}`);if(Array.isArray(X))Q.push(`${M}.${$}:array(${X.length})`);else if(X&&typeof X==="object")Y(X,`${M}.${$}`,Q,V+1);else Q.push(`${M}.${$}:${typeof X}`)}return Q}function G(q,M,Q=0){if(Q>4||!q||typeof q!=="object")return 0;if(Array.isArray(q))return q.slice(0,3).reduce((V,Z)=>V+G(Z,M,Q+1),0);return Object.entries(q).reduce((V,[Z,$])=>V+(M(Z)?1:0)+G($,M,Q+1),0)}function T(q,M=0){if(M>4||!q||typeof q!=="object")return!1;if(Array.isArray(q))return q.length>0;return Object.values(q).some((Q)=>T(Q,M+1))}export function scoreEndpointEvidence(q){const M=[],Q=R(q.bodyPreview);let V=0;if(q.status>=200&&q.status<300){V+=0.15;M.push("2xx status")}else if(q.status===401||q.status===403)return{url:q.url,status:q.status,contentType:q.contentType,real_data_score:0.05,verdict:"blocked",reasons:["auth-blocked status"],sample_paths:[]};else M.push(`non-2xx status ${q.status}`);if(/json/i.test(q.contentType)){V+=0.2;M.push("json content-type")}else if(/html/i.test(q.contentType)){V-=0.25;M.push("html content-type")}else if(/javascript|text/i.test(q.contentType)){V+=0.05;M.push("text/script content-type")}if(I.test(q.url)){V-=0.3;M.push("telemetry-like url")}const Z=Y(Q).slice(0,8);if(typeof Q==="string"){if(/^\s*</.test(Q)||/<!doctype html|<html/i.test(Q)){V-=0.25;M.push("html body")}else if(Q.trim().length>20){V+=0.05;M.push("non-empty text body")}}else if(Array.isArray(Q))if(Q.length>0){V+=0.3;M.push("non-empty top-level array")}else{V-=0.15;M.push("empty array")}else if(Q&&typeof Q==="object"){if(Object.keys(Q).length===0){V-=0.15;M.push("empty object")}else{V+=0.12;M.push("json object body")}const L=G(Q,(W)=>U.test(W));if(L>0){V+=Math.min(0.3,L*0.05);M.push(`${L} business-like key${L===1?"":"s"}`)}if(T(Q)){V+=0.2;M.push("nested non-empty array")}const z=G(Q,(W)=>B.test(W));if(z>0&&L===0){V-=Math.min(0.25,z*0.08);M.push(`${z} tracking-like key${z===1?"":"s"} without business keys`)}}const $=P(V),X=$>=0.65?"likely_data":$>=0.35?"maybe_data":"noise";return{url:q.url,status:q.status,contentType:q.contentType,real_data_score:$,verdict:X,reasons:M,sample_paths:Z}}export function scoreNetworkEvidence(q){return q.networkEntries.map(scoreEndpointEvidence).filter((M)=>M.verdict!=="noise"||M.real_data_score>0).sort((M,Q)=>Q.real_data_score-M.real_data_score).slice(0,8)}export function classifyPattern(q){const M=q.networkEntries.filter((X)=>/json/i.test(X.contentType)),V=scoreNetworkEvidence(q).filter((X)=>X.verdict==="likely_data"||X.verdict==="maybe_data").length,Z=q.networkEntries.filter((X)=>X.status===401||X.status===403).length,$=q.initialState.__INITIAL_STATE__||q.initialState.__NUXT__||q.initialState.__NEXT_DATA__||q.initialState.__APOLLO_STATE__;if(Z>=2&&M.length>=1)return{pattern:"D",reason:`${Z} auth-failing API responses seen — endpoint is token-gated`,json_responses:M.length,real_data_candidates:V,auth_failures:Z};if($)return{pattern:"B",reason:`SSR state global present: ${Object.entries(q.initialState).filter(([,O])=>O).map(([O])=>O).join(", ")}`,json_responses:M.length,real_data_candidates:V,auth_failures:Z};if(V>=1)return{pattern:"A",reason:`${V} captured response${V===1?"":"s"} look like real data — inspect api_candidates before choosing a strategy`,json_responses:M.length,real_data_candidates:V,auth_failures:Z};return{pattern:"C",reason:M.length>0?`${M.length} JSON response${M.length===1?"":"s"} observed, but none look like target data — likely telemetry/side-channel; treat as HTML/DOM until an endpoint validates`:"No JSON XHR and no SSR state — HTML scrape (Pattern C); escalate to E manually if WebSocket traffic appears",json_responses:M.length,real_data_candidates:V,auth_failures:Z}}export function findNearestAdapter(q,M){let Q;try{Q=new URL(q).hostname}catch{return null}const V=Q.replace(/^www\./,""),Z=V.split("."),$=Z.slice(-2).join("."),X=Z.length>1?Z[Z.length-2]:V,O=new Map;for(const z of M.values()){const W=z.domain?.toLowerCase();if(W&&(V.endsWith(W)||W.endsWith($))||z.site.toLowerCase()===X?.toLowerCase()||V.includes(z.site.toLowerCase())){const J=O.get(z.site)??[];J.push(z);O.set(z.site,J)}}if(O.size===0)return null;let L=null;for(const z of O)if(!L||z[1].length>L[1].length)L=z;if(!L)return null;return{site:L[0],example_commands:L[1].slice(0,5).map((z)=>`${z.site} ${z.name}`),reason:`${L[1].length} existing adapter${L[1].length===1?"":"s"} target this site — reuse strategy/cookie config`}}export function analyzeSite(q,M){const Q=classifyPattern(q),V=detectAntiBot(q),Z=scoreNetworkEvidence(q),$=findNearestAdapter(q.finalUrl,M);let X;if(V.detected)X=V.implication;else if(Q.pattern==="A")X="Inspect `api_candidates`, then replay the best endpoint and record the status/content-type/sample shape in your strategy note; do not choose API strategy from XHR count alone.";else if(Q.pattern==="B")X='Read the SSR global via `opencli browser eval "JSON.stringify(window.__INITIAL_STATE__ ?? window.__NUXT__ ?? window.__NEXT_DATA__ ?? window.__APOLLO_STATE__)"` — no API needed.';else if(Q.pattern==="C")X="No API visible — use SSR HTML scrape (e.g. `opencli browser extract`) against the rendered page.";else if(Q.pattern==="D")X="Endpoints need auth. Re-open the page from a signed-in session, then retry analyze; see `field-decode-playbook` §4 for token tracing.";else if(Q.pattern==="E")X="WebSocket stream detected — find the underlying HTTP poll/long-poll endpoint; raw WS is not supported.";else X="No strong signal. Manually inspect `opencli browser network --all` and pick a pattern.";return{requested_url:q.requestedUrl,final_url:q.finalUrl,title:q.title,pattern:Q,anti_bot:V,initial_state:q.initialState,api_candidates:Z,nearest_adapter:$,recommended_next_step:X}}
1
+ const H=[{vendor:"aliyun_waf",cookiePatterns:[/^acw_sc__v2$/,/^acw_tc$/,/^ssxmod_itna/],bodyPatterns:[/arg1\s*=\s*['"][0-9A-F]{30,}/,/\/ntc_captcha\//i],implication:"Direct Node-side fetch/curl will return the slider HTML. Validate the endpoint in browser context first; HTML COOKIE adapters still finish with Node-side fetch + page.getCookies."},{vendor:"cloudflare",cookiePatterns:[/^__cf_bm$/,/^cf_clearance$/,/^__cfduid$/],bodyPatterns:[/Cloudflare Ray ID/i,/Checking your browser before accessing/i,/cf-chl-/i],implication:"Cloudflare bot check. Start from a real browser session; probe in browser context first. HTML COOKIE adapters still finish with Node-side fetch + page.getCookies."},{vendor:"akamai",cookiePatterns:[/^_abck$/,/^bm_sz$/,/^bm_sv$/],bodyPatterns:[/akamai/i],implication:"Akamai Bot Manager. Probe in browser context first; keep final HTML COOKIE adapters on Node-side fetch + page.getCookies."},{vendor:"geetest",cookiePatterns:[],bodyPatterns:[/geetest/i,/gt_captcha/i],implication:"Geetest slider/puzzle captcha. Agent cannot bypass programmatically — requires UI strategy or human-in-loop."}];export function detectAntiBot(q){const M=[];let Q=null;for(const V of H){const Z=[];for(const $ of V.cookiePatterns){const X=q.cookieNames.find((O)=>$.test(O));if(X)Z.push(`cookie:${X}`)}for(const $ of V.bodyPatterns)for(const X of q.networkEntries)if(X.bodyPreview&&$.test(X.bodyPreview)){Z.push(`body:${X.url}`);break}if(Z.length>0&&!Q){Q=V;M.push(...Z)}}if(!Q)return{detected:!1,vendor:null,evidence:[],implication:"No known anti-bot signatures. Try Node-side COOKIE fetch first; if endpoint validation is blocked, retry from browser context."};return{detected:!0,vendor:Q.vendor,evidence:M,implication:Q.implication}}const I=/(?:analytics|beacon|collect|telemetry|tracking|sentry|doubleclick|google-analytics|googletagmanager|adservice|\/ads?(?:[/?#]|$)|metrics?|pixel|personalization|experiment|\/events?(?:[/?#]|$))/i,U=/^(?:data|items?|results?|records?|list|rows?|edges?|nodes?|timeline|users?|title|name|text|content|body|price|amount|id|url|avatar|nickname|desc|comments?|likes?|shares?|total|page|cursor|next|rank|score|date|time|author)$/i,B=/^(?:event|events|trace|traceid|sessionid|clientid|visitorid|experiment|abtest|beacon|analytics|metrics?|pixel|log|logs)$/i;function P(q){return Math.max(0,Math.min(1,Math.round(q*100)/100))}function R(q){if(!q)return null;const M=q.trim();if(!M)return null;try{return JSON.parse(M)}catch{return M}}function Y(q,M="$",Q=[],V=0){if(V>4||Q.length>=24)return Q;if(Array.isArray(q)){Q.push(`${M}:array(${q.length})`);if(q.length>0)Y(q[0],`${M}[0]`,Q,V+1);return Q}if(!q||typeof q!=="object"){Q.push(`${M}:${typeof q}`);return Q}const Z=Object.entries(q).slice(0,12);for(const[$,X]of Z){Q.push(`${M}.${$}`);if(Array.isArray(X))Q.push(`${M}.${$}:array(${X.length})`);else if(X&&typeof X==="object")Y(X,`${M}.${$}`,Q,V+1);else Q.push(`${M}.${$}:${typeof X}`)}return Q}function G(q,M,Q=0){if(Q>4||!q||typeof q!=="object")return 0;if(Array.isArray(q))return q.slice(0,3).reduce((V,Z)=>V+G(Z,M,Q+1),0);return Object.entries(q).reduce((V,[Z,$])=>V+(M(Z)?1:0)+G($,M,Q+1),0)}function T(q,M=0){if(M>4||!q||typeof q!=="object")return!1;if(Array.isArray(q))return q.length>0;return Object.values(q).some((Q)=>T(Q,M+1))}export function scoreEndpointEvidence(q){const M=[],Q=R(q.bodyPreview);let V=0;if(q.status>=200&&q.status<300){V+=0.15;M.push("2xx status")}else if(q.status===401||q.status===403)return{url:q.url,status:q.status,contentType:q.contentType,real_data_score:0.05,verdict:"blocked",reasons:["auth-blocked status"],sample_paths:[]};else M.push(`non-2xx status ${q.status}`);if(/json/i.test(q.contentType)){V+=0.2;M.push("json content-type")}else if(/html/i.test(q.contentType)){V-=0.25;M.push("html content-type")}else if(/javascript|text/i.test(q.contentType)){V+=0.05;M.push("text/script content-type")}if(I.test(q.url)){V-=0.3;M.push("telemetry-like url")}const Z=Y(Q).slice(0,8);if(typeof Q==="string"){if(/^\s*</.test(Q)||/<!doctype html|<html/i.test(Q)){V-=0.25;M.push("html body")}else if(Q.trim().length>20){V+=0.05;M.push("non-empty text body")}}else if(Array.isArray(Q))if(Q.length>0){V+=0.3;M.push("non-empty top-level array")}else{V-=0.15;M.push("empty array")}else if(Q&&typeof Q==="object"){if(Object.keys(Q).length===0){V-=0.15;M.push("empty object")}else{V+=0.12;M.push("json object body")}const L=G(Q,(W)=>U.test(W));if(L>0){V+=Math.min(0.3,L*0.05);M.push(`${L} business-like key${L===1?"":"s"}`)}if(T(Q)){V+=0.2;M.push("nested non-empty array")}const z=G(Q,(W)=>B.test(W));if(z>0&&L===0){V-=Math.min(0.25,z*0.08);M.push(`${z} tracking-like key${z===1?"":"s"} without business keys`)}}const $=P(V),X=$>=0.65?"likely_data":$>=0.35?"maybe_data":"noise";return{url:q.url,status:q.status,contentType:q.contentType,real_data_score:$,verdict:X,reasons:M,sample_paths:Z}}export function scoreNetworkEvidence(q){return q.networkEntries.map(scoreEndpointEvidence).filter((M)=>M.verdict!=="noise"||M.real_data_score>0).sort((M,Q)=>Q.real_data_score-M.real_data_score).slice(0,8)}export function classifyPattern(q){const M=q.networkEntries.filter((X)=>/json/i.test(X.contentType)),V=scoreNetworkEvidence(q).filter((X)=>X.verdict==="likely_data"||X.verdict==="maybe_data").length,Z=q.networkEntries.filter((X)=>X.status===401||X.status===403).length,$=q.initialState.__INITIAL_STATE__||q.initialState.__NUXT__||q.initialState.__NEXT_DATA__||q.initialState.__APOLLO_STATE__;if(Z>=2&&M.length>=1)return{pattern:"D",reason:`${Z} auth-failing API responses seen — endpoint is token-gated`,json_responses:M.length,real_data_candidates:V,auth_failures:Z};if($)return{pattern:"B",reason:`SSR state global present: ${Object.entries(q.initialState).filter(([,O])=>O).map(([O])=>O).join(", ")}`,json_responses:M.length,real_data_candidates:V,auth_failures:Z};if(V>=1)return{pattern:"A",reason:`${V} captured response${V===1?"":"s"} look like real data — inspect api_candidates before choosing a strategy`,json_responses:M.length,real_data_candidates:V,auth_failures:Z};return{pattern:"C",reason:M.length>0?`${M.length} JSON response${M.length===1?"":"s"} observed, but none look like target data — likely telemetry/side-channel; treat as HTML/DOM until an endpoint validates`:"No JSON XHR and no SSR state — HTML scrape (Pattern C); escalate to E manually if WebSocket traffic appears",json_responses:M.length,real_data_candidates:V,auth_failures:Z}}export function findNearestAdapter(q,M){let Q;try{Q=new URL(q).hostname}catch{return null}const V=Q.replace(/^www\./,""),Z=V.split("."),$=Z.slice(-2).join("."),X=Z.length>1?Z[Z.length-2]:V,O=new Map;for(const z of M.values()){const W=z.domain?.toLowerCase();if(W&&(V.endsWith(W)||W.endsWith($))||z.site.toLowerCase()===X?.toLowerCase()||V.includes(z.site.toLowerCase())){const J=O.get(z.site)??[];J.push(z);O.set(z.site,J)}}if(O.size===0)return null;let L=null;for(const z of O)if(!L||z[1].length>L[1].length)L=z;if(!L)return null;return{site:L[0],example_commands:L[1].slice(0,5).map((z)=>`${z.site} ${z.name}`),reason:`${L[1].length} existing adapter${L[1].length===1?"":"s"} target this site — reuse strategy/cookie config`}}export function analyzeSite(q,M){const Q=classifyPattern(q),V=detectAntiBot(q),Z=scoreNetworkEvidence(q),$=findNearestAdapter(q.finalUrl,M);let X;if(V.detected)X=V.implication;else if(Q.pattern==="A")X="Inspect `api_candidates`, then replay the best endpoint and record the status/content-type/sample shape in your strategy note; do not choose API strategy from XHR count alone.";else if(Q.pattern==="B")X='Read the SSR global via `ppcli browser eval "JSON.stringify(window.__INITIAL_STATE__ ?? window.__NUXT__ ?? window.__NEXT_DATA__ ?? window.__APOLLO_STATE__)"` — no API needed.';else if(Q.pattern==="C")X="No API visible — use SSR HTML scrape (e.g. `ppcli browser extract`) against the rendered page.";else if(Q.pattern==="D")X="Endpoints need auth. Re-open the page from a signed-in session, then retry analyze; see `field-decode-playbook` §4 for token tracing.";else if(Q.pattern==="E")X="WebSocket stream detected — find the underlying HTTP poll/long-poll endpoint; raw WS is not supported.";else X="No strong signal. Manually inspect `ppcli browser network --all` and pick a pattern.";return{requested_url:q.requestedUrl,final_url:q.finalUrl,title:q.title,pattern:Q,anti_bot:V,initial_state:q.initialState,api_candidates:Z,nearest_adapter:$,recommended_next_step:X}}
@@ -22,7 +22,7 @@
22
22
  *
23
23
  * Readability runs in the page's own window because it needs real DOM APIs
24
24
  * (getComputedStyle, treeWalker). Running it Node-side would require jsdom —
25
- * a heavy dep the rest of OpenCLI doesn't need.
25
+ * a heavy dep the rest of ppcli doesn't need.
26
26
  */
27
27
  export interface ExtractArticleOptions {
28
28
  /** CSS selectors removed from the cloned document before Readability runs. */
@@ -132,7 +132,7 @@ ${G}
132
132
  }
133
133
  return { ok: false, reason: 'not_checkable', tag, role };
134
134
  })()
135
- `)}async setChecked(G,H,W={}){const Q=await Y(this,G,W),Z=await this.readCheckableState();if(Z?.ok!==!0)throw new q({code:"not_checkable",message:`Target "${G}" is not a checkbox, radio, switch, or aria-checked control.`,hint:"Use `opencli browser state` or `browser find` to pick an input[type=checkbox], input[type=radio], or role=checkbox/switch target."});if(Z.disabled)throw new q({code:"not_checkable",message:`Target "${G}" is disabled and cannot be ${H?"checked":"unchecked"}.`,hint:"Pick an enabled control, or inspect the form state before retrying."});if((Z.kind==="radio"||Z.kind==="menuitemradio")&&!H)throw new q({code:"not_checkable",message:`Target "${G}" is a radio button and cannot be unchecked directly.`,hint:"Select another radio option in the same group instead."});if(Z.checked===H)return{...Q,checked:H,changed:!1,...Z.kind?{kind:Z.kind}:{}};const _=await this.click(G,W),$=await this.readCheckableState();if($?.ok!==!0||$.checked!==H)throw new q({code:"not_checkable",message:`Target "${G}" did not become ${H?"checked":"unchecked"} after click.`,hint:"The control may be custom, disabled by app logic, or require a different target such as its visible label."});return{matches_n:_.matches_n,match_level:_.match_level,checked:H,changed:!0,...$.kind?{kind:$.kind}:{}}}async setFileInputBySelector(G,H){const W=this.setFileInput;if(typeof W==="function"){await W.call(this,G,H);return}const Q=this.cdp;if(typeof Q!=="function")throw Error("File upload requires setFileInput or CDP support from the active browser backend.");await Q.call(this,"DOM.enable",{}).catch(()=>{return});const _=(await Q.call(this,"DOM.getDocument",{}))?.root?.nodeId;if(typeof _!=="number")throw Error("DOM.getDocument returned no root node.");const D=(await Q.call(this,"DOM.querySelector",{nodeId:_,selector:H}))?.nodeId;if(typeof D!=="number"||D<=0)throw Error(`No element found matching selector: ${H}`);await Q.call(this,"DOM.setFileInputFiles",{files:G,nodeId:D})}async uploadFiles(G,H,W={}){if(!Array.isArray(H)||H.length===0)throw new q({code:"not_file_input",message:"No files were provided for upload.",hint:"Pass one or more local file paths after the target."});const Q=await Y(this,G,W),Z="data-opencli-upload-target",_=`${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`,$=`[${Z}="${_}"]`;let D=!1,B=null;try{B=await this.evaluateWithArgs(`
135
+ `)}async setChecked(G,H,W={}){const Q=await Y(this,G,W),Z=await this.readCheckableState();if(Z?.ok!==!0)throw new q({code:"not_checkable",message:`Target "${G}" is not a checkbox, radio, switch, or aria-checked control.`,hint:"Use `ppcli browser state` or `browser find` to pick an input[type=checkbox], input[type=radio], or role=checkbox/switch target."});if(Z.disabled)throw new q({code:"not_checkable",message:`Target "${G}" is disabled and cannot be ${H?"checked":"unchecked"}.`,hint:"Pick an enabled control, or inspect the form state before retrying."});if((Z.kind==="radio"||Z.kind==="menuitemradio")&&!H)throw new q({code:"not_checkable",message:`Target "${G}" is a radio button and cannot be unchecked directly.`,hint:"Select another radio option in the same group instead."});if(Z.checked===H)return{...Q,checked:H,changed:!1,...Z.kind?{kind:Z.kind}:{}};const _=await this.click(G,W),$=await this.readCheckableState();if($?.ok!==!0||$.checked!==H)throw new q({code:"not_checkable",message:`Target "${G}" did not become ${H?"checked":"unchecked"} after click.`,hint:"The control may be custom, disabled by app logic, or require a different target such as its visible label."});return{matches_n:_.matches_n,match_level:_.match_level,checked:H,changed:!0,...$.kind?{kind:$.kind}:{}}}async setFileInputBySelector(G,H){const W=this.setFileInput;if(typeof W==="function"){await W.call(this,G,H);return}const Q=this.cdp;if(typeof Q!=="function")throw Error("File upload requires setFileInput or CDP support from the active browser backend.");await Q.call(this,"DOM.enable",{}).catch(()=>{return});const _=(await Q.call(this,"DOM.getDocument",{}))?.root?.nodeId;if(typeof _!=="number")throw Error("DOM.getDocument returned no root node.");const D=(await Q.call(this,"DOM.querySelector",{nodeId:_,selector:H}))?.nodeId;if(typeof D!=="number"||D<=0)throw Error(`No element found matching selector: ${H}`);await Q.call(this,"DOM.setFileInputFiles",{files:G,nodeId:D})}async uploadFiles(G,H,W={}){if(!Array.isArray(H)||H.length===0)throw new q({code:"not_file_input",message:"No files were provided for upload.",hint:"Pass one or more local file paths after the target."});const Q=await Y(this,G,W),Z="data-opencli-upload-target",_=`${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`,$=`[${Z}="${_}"]`;let D=!1,B=null;try{B=await this.evaluateWithArgs(`
136
136
  (() => {
137
137
  const el = window.__resolved;
138
138
  if (!el || el.nodeType !== 1) return { ok: false, reason: 'not_file_input' };
@@ -146,7 +146,7 @@ ${G}
146
146
  accept: el.getAttribute('accept') || '',
147
147
  };
148
148
  })()
149
- `,{markerAttr:Z,markerValue:_});D=B?.ok===!0;if(!D)throw new q({code:"not_file_input",message:`Target "${G}" is not an input[type=file].`,hint:'Use `opencli browser find --css "input[type=file]"` or inspect `compound` output from browser state/find.'});if(H.length>1&&!B?.multiple)throw new q({code:"not_file_input",message:`Target "${G}" does not allow multiple files, but ${H.length} files were provided.`,hint:"Pass one file, or choose a file input with the multiple attribute."});await this.setFileInputBySelector(H,$);const X=await this.evaluate(`
149
+ `,{markerAttr:Z,markerValue:_});D=B?.ok===!0;if(!D)throw new q({code:"not_file_input",message:`Target "${G}" is not an input[type=file].`,hint:'Use `ppcli browser find --css "input[type=file]"` or inspect `compound` output from browser state/find.'});if(H.length>1&&!B?.multiple)throw new q({code:"not_file_input",message:`Target "${G}" does not allow multiple files, but ${H.length} files were provided.`,hint:"Pass one file, or choose a file input with the multiple attribute."});await this.setFileInputBySelector(H,$);const X=await this.evaluate(`
150
150
  (() => {
151
151
  const el = window.__resolved;
152
152
  const names = [];
@@ -195,7 +195,7 @@ ${G}
195
195
  };
196
196
  return { source: measure(sourceEl), target: measure(targetEl) };
197
197
  })()
198
- `);if(B?.source?.visible!==!0)throw Error(`Drag source "${G}" is not visible at drag time.`);if(B?.target?.visible!==!0)throw Error(`Drag target "${H}" has no visible bounding box.`);if(!await this.tryNativeDrag({x:B.source.x,y:B.source.y},{x:B.target.x,y:B.target.y}))throw Error("Native drag requires CDP Input.dispatchMouseEvent support.");return{dragged:!0,source:G,target:H,source_matches_n:Q.matches_n,target_matches_n:$.matches_n,source_match_level:Q.match_level,target_match_level:$.match_level}}finally{await this.evaluate("delete window.__opencli_drag_source").catch(()=>{})}}async fillText(G,H,W={}){const Q=await Y(this,G,W);let Z=!1,_=!1;try{Z=await this.tryCdpOnResolvedElement("DOM.scrollIntoViewIfNeeded");_=await this.tryCdpOnResolvedElement("DOM.focus")}catch{}if((await this.evaluate(z({skipScroll:Z,skipFocus:_})))?.ok!==!0)throw new q({code:"not_editable",message:`Target "${G}" is not a fillable input, textarea, or contenteditable element.`,hint:"Use `opencli browser state` to pick an editable target, or use `browser type` for keyboard-like interactions."});const D=await this.tryNativeType(H);if(!D)await this.evaluate(V(H));let B=await this.evaluate(U(H));if(D&&B?.ok!==!0){await this.evaluate(V(H));B=await this.evaluate(U(H))}const X=B&&"actual"in B?B.actual:"",L=B&&"mode"in B?B.mode:void 0;return{...Q,filled:!0,verified:B?.ok===!0,expected:H,actual:X,length:X.length,...L?{mode:L}:{}}}async pressKey(G){const H=f(G);if(!await this.tryNativeKeyPress(H.key,H.modifiers))await this.evaluate(I(H.key,H.modifiers))}async scrollTo(G,H={}){const W=await Y(this,G,H),Q=await this.tryCdpOnResolvedElement("DOM.scrollIntoViewIfNeeded"),Z=await this.evaluate(y({skipScroll:Q}));if(Z&&typeof Z==="object")return{...Z,matches_n:W.matches_n,match_level:W.match_level};return{matches_n:W.matches_n,match_level:W.match_level}}async getFormState(){return await this.evaluate(E())}async scroll(G="down",H=500){await this.evaluate(T(G,H))}async autoScroll(G){const H=G?.times??3,W=G?.delayMs??2000;await this.evaluate(h(H,W))}async networkRequests(G=!1){const H=await this.evaluate(R(G));return Array.isArray(H)?H:[]}async consoleMessages(G="info"){return[]}async wait(G){if(typeof G==="number"){if(G>=1)try{const H=G*1000;await this.evaluate(g(H,Math.min(500,H)));return}catch{}await new Promise((H)=>setTimeout(H,G*1000));return}if(typeof G.time==="number"){await new Promise((H)=>setTimeout(H,G.time*1000));return}if(G.selector){const H=(G.timeout??10)*1000;await this.evaluate(J(G.selector,H));return}if(G.text){const H=(G.timeout??30)*1000;await this.evaluate(S(G.text,H))}}async snapshot(G={}){if(G.source==="ax"){const W=this.cdp;if(typeof W!=="function")throw new M("BROWSER_AX_UNAVAILABLE","AX snapshot requires CDP support from the active browser backend.","Use the default DOM state, or update/reload the Browser Bridge extension.");const Q=await this.collectAxSnapshotTrees(W),Z=F(Q,{maxDepth:G.maxDepth,interactiveOnly:G.interactive});this._axRefs=Z.refs;return Z.text}this._axRefs.clear();const H=N({viewportExpand:G.viewportExpand??2000,maxDepth:Math.max(1,Math.min(Number(G.maxDepth)||50,200)),interactiveOnly:G.interactive??!1,maxTextLength:G.maxTextLength??120,includeScrollInfo:!0,bboxDedup:!0,previousHashes:this._prevSnapshotHashes});try{const W=await this.evaluate(H);try{const Q=await this.evaluate("window.__opencli_prev_hashes");this._prevSnapshotHashes=typeof Q==="string"?Q:null}catch{}return W}catch(W){if(process.env.DEBUG_SNAPSHOT)process.stderr.write(`[snapshot] DOM snapshot failed, falling back to accessibility tree: ${W?.message?.slice(0,200)}
198
+ `);if(B?.source?.visible!==!0)throw Error(`Drag source "${G}" is not visible at drag time.`);if(B?.target?.visible!==!0)throw Error(`Drag target "${H}" has no visible bounding box.`);if(!await this.tryNativeDrag({x:B.source.x,y:B.source.y},{x:B.target.x,y:B.target.y}))throw Error("Native drag requires CDP Input.dispatchMouseEvent support.");return{dragged:!0,source:G,target:H,source_matches_n:Q.matches_n,target_matches_n:$.matches_n,source_match_level:Q.match_level,target_match_level:$.match_level}}finally{await this.evaluate("delete window.__opencli_drag_source").catch(()=>{})}}async fillText(G,H,W={}){const Q=await Y(this,G,W);let Z=!1,_=!1;try{Z=await this.tryCdpOnResolvedElement("DOM.scrollIntoViewIfNeeded");_=await this.tryCdpOnResolvedElement("DOM.focus")}catch{}if((await this.evaluate(z({skipScroll:Z,skipFocus:_})))?.ok!==!0)throw new q({code:"not_editable",message:`Target "${G}" is not a fillable input, textarea, or contenteditable element.`,hint:"Use `ppcli browser state` to pick an editable target, or use `browser type` for keyboard-like interactions."});const D=await this.tryNativeType(H);if(!D)await this.evaluate(V(H));let B=await this.evaluate(U(H));if(D&&B?.ok!==!0){await this.evaluate(V(H));B=await this.evaluate(U(H))}const X=B&&"actual"in B?B.actual:"",L=B&&"mode"in B?B.mode:void 0;return{...Q,filled:!0,verified:B?.ok===!0,expected:H,actual:X,length:X.length,...L?{mode:L}:{}}}async pressKey(G){const H=f(G);if(!await this.tryNativeKeyPress(H.key,H.modifiers))await this.evaluate(I(H.key,H.modifiers))}async scrollTo(G,H={}){const W=await Y(this,G,H),Q=await this.tryCdpOnResolvedElement("DOM.scrollIntoViewIfNeeded"),Z=await this.evaluate(y({skipScroll:Q}));if(Z&&typeof Z==="object")return{...Z,matches_n:W.matches_n,match_level:W.match_level};return{matches_n:W.matches_n,match_level:W.match_level}}async getFormState(){return await this.evaluate(E())}async scroll(G="down",H=500){await this.evaluate(T(G,H))}async autoScroll(G){const H=G?.times??3,W=G?.delayMs??2000;await this.evaluate(h(H,W))}async networkRequests(G=!1){const H=await this.evaluate(R(G));return Array.isArray(H)?H:[]}async consoleMessages(G="info"){return[]}async wait(G){if(typeof G==="number"){if(G>=1)try{const H=G*1000;await this.evaluate(g(H,Math.min(500,H)));return}catch{}await new Promise((H)=>setTimeout(H,G*1000));return}if(typeof G.time==="number"){await new Promise((H)=>setTimeout(H,G.time*1000));return}if(G.selector){const H=(G.timeout??10)*1000;await this.evaluate(J(G.selector,H));return}if(G.text){const H=(G.timeout??30)*1000;await this.evaluate(S(G.text,H))}}async snapshot(G={}){if(G.source==="ax"){const W=this.cdp;if(typeof W!=="function")throw new M("BROWSER_AX_UNAVAILABLE","AX snapshot requires CDP support from the active browser backend.","Use the default DOM state, or update/reload the Browser Bridge extension.");const Q=await this.collectAxSnapshotTrees(W),Z=F(Q,{maxDepth:G.maxDepth,interactiveOnly:G.interactive});this._axRefs=Z.refs;return Z.text}this._axRefs.clear();const H=N({viewportExpand:G.viewportExpand??2000,maxDepth:Math.max(1,Math.min(Number(G.maxDepth)||50,200)),interactiveOnly:G.interactive??!1,maxTextLength:G.maxTextLength??120,includeScrollInfo:!0,bboxDedup:!0,previousHashes:this._prevSnapshotHashes});try{const W=await this.evaluate(H);try{const Q=await this.evaluate("window.__opencli_prev_hashes");this._prevSnapshotHashes=typeof Q==="string"?Q:null}catch{}return W}catch(W){if(process.env.DEBUG_SNAPSHOT)process.stderr.write(`[snapshot] DOM snapshot failed, falling back to accessibility tree: ${W?.message?.slice(0,200)}
199
199
  `);return this._basicSnapshot(G)}}async collectAxSnapshotTrees(G){await G.call(this,"Accessibility.enable",{});const W=[{tree:await G.call(this,"Accessibility.getFullAXTree",{})}],Q=await G.call(this,"Page.getFrameTree",{}).catch(()=>null),Z=c(Q);for(const _ of Z){if(_.sessionId)await G.call(this,"Accessibility.enable",C(_)).catch(()=>null);const $=await G.call(this,"Accessibility.getFullAXTree",j(_)).catch(()=>null);if($)W.push({tree:$,frame:_})}return W}async getCurrentUrl(){if(this._lastUrl)return this._lastUrl;try{const G=await this.evaluate("window.location.href");if(typeof G==="string"&&G){this._lastUrl=G;return G}}catch{}return null}async installInterceptor(G){const{generateInterceptorJs:H}=await import("../interceptor.js");await this.evaluate(H(JSON.stringify(G),{arrayName:"__opencli_xhr",patchGuard:"__opencli_interceptor_patched"}))}async getInterceptedRequests(){const{generateReadInterceptedJs:G}=await import("../interceptor.js"),H=await this.evaluate(G("__opencli_xhr"));return Array.isArray(H)?H:[]}async waitForCapture(G=10){const H=G*1000;await this.evaluate(b(H))}async _basicSnapshot(G={}){const W=`
200
200
  (async () => {
201
201
  function buildTree(node, depth) {