publishport-opencli 1.0.1 → 1.0.3

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 (317) hide show
  1. package/README.md +61 -61
  2. package/README.zh-CN.md +68 -68
  3. package/cli-manifest.json +502 -59
  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/rest-countries/utils.js +1 -1
  133. package/clis/rfc/utils.js +1 -1
  134. package/clis/rubygems/utils.js +1 -1
  135. package/clis/semanticscholar/utils.js +1 -1
  136. package/clis/slock/errors.js +1 -1
  137. package/clis/spotify/spotify.js +1 -1
  138. package/clis/spotify/utils.js +1 -1
  139. package/clis/stackoverflow/utils.js +1 -1
  140. package/clis/steam/utils.js +1 -1
  141. package/clis/substack/auth.js +2 -2
  142. package/clis/suno/generate.js +2 -2
  143. package/clis/suno/list.js +3 -3
  144. package/clis/telegram/send.js +1 -1
  145. package/clis/tiktok/creator-videos.js +1 -1
  146. package/clis/tiktok/publish.js +1 -1
  147. package/clis/tiktok/utils.js +1 -1
  148. package/clis/trae-cn/activity.js +1 -1
  149. package/clis/trae-cn/approve.js +1 -1
  150. package/clis/trae-cn/ask.js +1 -1
  151. package/clis/trae-cn/dump.js +1 -1
  152. package/clis/trae-cn/export.js +1 -1
  153. package/clis/trae-cn/model.js +1 -1
  154. package/clis/trae-cn/new.js +1 -1
  155. package/clis/trae-cn/read.js +1 -1
  156. package/clis/trae-cn/screenshot.js +1 -1
  157. package/clis/trae-cn/select-model.js +1 -1
  158. package/clis/trae-cn/send.js +1 -1
  159. package/clis/trae-cn/setup.js +1 -1
  160. package/clis/trae-cn/status.js +1 -1
  161. package/clis/trae-cn/targets.js +1 -1
  162. package/clis/trae-cn/watch.js +1 -1
  163. package/clis/trae-solo/state-fs.js +1 -1
  164. package/clis/tumblr/publish.js +1 -1
  165. package/clis/tvmaze/utils.js +1 -1
  166. package/clis/twitter/bookmark-folder.js +1 -1
  167. package/clis/twitter/download.js +1 -1
  168. package/clis/twitter/followers.js +2 -2
  169. package/clis/twitter/following.js +2 -2
  170. package/clis/twitter/likes.js +1 -1
  171. package/clis/twitter/list-add-batch.js +1 -1
  172. package/clis/twitter/list-add-core.js +1 -1
  173. package/clis/twitter/list-add.js +1 -1
  174. package/clis/twitter/list-create.js +1 -1
  175. package/clis/twitter/list-delete.js +3 -3
  176. package/clis/twitter/list-remove-batch.js +1 -1
  177. package/clis/twitter/list-remove.js +1 -1
  178. package/clis/twitter/list-tweets.js +1 -1
  179. package/clis/twitter/profile.js +1 -1
  180. package/clis/twitter/search.js +1 -1
  181. package/clis/twitter/shared.js +1 -1
  182. package/clis/twitter/tweets.js +1 -1
  183. package/clis/uisdc/news.js +1 -1
  184. package/clis/v2ex/daily.js +2 -2
  185. package/clis/v2ex/me.js +2 -2
  186. package/clis/v2ex/notifications.js +1 -1
  187. package/clis/wikidata/utils.js +1 -1
  188. package/clis/wikipedia/page.js +2 -2
  189. package/clis/wikipedia/summary.js +1 -1
  190. package/clis/wikipedia/utils.js +1 -1
  191. package/clis/wordpress/login.js +1 -1
  192. package/clis/wordpress/publish.js +1 -1
  193. package/clis/wordpress/shared.js +3 -3
  194. package/clis/wordpress/sites.js +1 -0
  195. package/clis/wordpress/whoami.js +1 -1
  196. package/clis/woshipm/stats.js +1 -1
  197. package/clis/xiaoe/content.js +1 -1
  198. package/clis/xiaohongshu/draft-delete.js +1 -1
  199. package/clis/xiaohongshu/draft-open.js +1 -1
  200. package/clis/xiaohongshu/draft-utils.js +2 -2
  201. package/clis/xiaohongshu/publish-video.js +1 -1
  202. package/clis/xiaohongshu/publish.js +1 -1
  203. package/clis/xiaoyuzhou/auth.js +1 -1
  204. package/clis/xiaoyuzhou/transcript.js +1 -1
  205. package/clis/yollomi/background.js +1 -1
  206. package/clis/yollomi/edit.js +1 -1
  207. package/clis/yollomi/generate.js +1 -1
  208. package/clis/yollomi/try-on.js +1 -1
  209. package/clis/youtube/publish.js +1 -1
  210. package/clis/zhihu/answer-comments.js +1 -1
  211. package/clis/zhihu/answer-detail.js +1 -1
  212. package/clis/zhihu/collection.js +2 -2
  213. package/clis/zhihu/collections.js +1 -1
  214. package/clis/zhihu/question.js +1 -1
  215. package/clis/zhihu/search.js +1 -1
  216. package/clis/zhihu/target.js +1 -1
  217. package/clis/zhihu/user-arg.js +1 -1
  218. package/dist/src/adapter-shadow.js +1 -1
  219. package/dist/src/browser/analyze.js +1 -1
  220. package/dist/src/browser/article-extract.d.ts +1 -1
  221. package/dist/src/browser/base-page.js +3 -3
  222. package/dist/src/browser/cdp.js +1 -1
  223. package/dist/src/browser/daemon-client.d.ts +1 -1
  224. package/dist/src/browser/daemon-lifecycle.js +6 -6
  225. package/dist/src/browser/daemon-version.js +1 -1
  226. package/dist/src/browser/dom-snapshot.d.ts +1 -1
  227. package/dist/src/browser/dom-snapshot.js +2 -2
  228. package/dist/src/browser/errors.js +2 -2
  229. package/dist/src/browser/network-cache.js +1 -1
  230. package/dist/src/browser/profile.js +1 -1
  231. package/dist/src/browser/target-resolver.js +4 -4
  232. package/dist/src/browser/verify-fixture.js +1 -1
  233. package/dist/src/browser-session-lock.js +1 -1
  234. package/dist/src/build-manifest.d.ts +1 -1
  235. package/dist/src/build-manifest.js +1 -1
  236. package/dist/src/cli-argv-preprocess.d.ts +4 -4
  237. package/dist/src/cli-argv-preprocess.js +1 -1
  238. package/dist/src/cli.d.ts +1 -1
  239. package/dist/src/cli.js +19 -19
  240. package/dist/src/commanderAdapter.js +1 -1
  241. package/dist/src/commands/auth.js +2 -2
  242. package/dist/src/commands/daemon.d.ts +4 -4
  243. package/dist/src/commands/daemon.js +1 -1
  244. package/dist/src/completion-shared.js +12 -12
  245. package/dist/src/completion.d.ts +2 -2
  246. package/dist/src/constants.d.ts +1 -1
  247. package/dist/src/constants.js +1 -1
  248. package/dist/src/daemon-utils.d.ts +1 -1
  249. package/dist/src/daemon-utils.js +1 -1
  250. package/dist/src/daemon.d.ts +2 -2
  251. package/dist/src/daemon.js +1 -1
  252. package/dist/src/discovery.d.ts +7 -7
  253. package/dist/src/discovery.js +2 -2
  254. package/dist/src/doctor.d.ts +1 -1
  255. package/dist/src/doctor.js +6 -6
  256. package/dist/src/electron-apps.d.ts +1 -1
  257. package/dist/src/electron-apps.js +1 -1
  258. package/dist/src/errors.d.ts +2 -2
  259. package/dist/src/errors.js +1 -1
  260. package/dist/src/execution.js +1 -1
  261. package/dist/src/external.d.ts +1 -1
  262. package/dist/src/external.js +1 -1
  263. package/dist/src/help.d.ts +1 -1
  264. package/dist/src/help.js +3 -3
  265. package/dist/src/hooks.d.ts +1 -1
  266. package/dist/src/launcher.js +2 -2
  267. package/dist/src/logger.d.ts +1 -1
  268. package/dist/src/main.d.ts +1 -1
  269. package/dist/src/main.js +1 -1
  270. package/dist/src/observation/artifact.js +2 -2
  271. package/dist/src/package-paths.js +1 -1
  272. package/dist/src/package-paths.test.d.ts +1 -0
  273. package/dist/src/plugin-manifest.d.ts +12 -3
  274. package/dist/src/plugin-manifest.js +1 -1
  275. package/dist/src/plugin-scaffold.d.ts +2 -2
  276. package/dist/src/plugin-scaffold.js +9 -9
  277. package/dist/src/plugin.d.ts +4 -4
  278. package/dist/src/plugin.js +10 -10
  279. package/dist/src/rate-limit.js +1 -1
  280. package/dist/src/registry-api.d.ts +1 -1
  281. package/dist/src/runtime-detect.d.ts +1 -1
  282. package/dist/src/serialization.js +1 -1
  283. package/dist/src/skills.js +6 -6
  284. package/dist/src/user-runtime.d.ts +8 -0
  285. package/dist/src/user-runtime.js +1 -0
  286. package/dist/src/user-runtime.test.d.ts +1 -0
  287. package/dist/src/validate.js +1 -1
  288. package/dist/src/validate.test.d.ts +1 -1
  289. package/docs/guide/browser-bridge.md +23 -23
  290. package/docs/guide/electron-app-cli.md +3 -3
  291. package/docs/guide/exit-codes.md +3 -3
  292. package/docs/guide/extending-opencli.md +37 -37
  293. package/docs/guide/getting-started.md +19 -19
  294. package/docs/guide/installation.md +6 -6
  295. package/docs/guide/plugins.md +32 -32
  296. package/docs/guide/remote-orchestration.md +5 -5
  297. package/docs/guide/troubleshooting.md +9 -9
  298. package/package.json +3 -2
  299. package/scripts/fetch-adapters.js +6 -6
  300. package/scripts/postinstall.js +14 -14
  301. package/skills/opencli-adapter-author/SKILL.md +24 -24
  302. package/skills/opencli-adapter-author/references/adapter-template.md +13 -13
  303. package/skills/opencli-adapter-author/references/api-discovery.md +25 -25
  304. package/skills/opencli-adapter-author/references/coverage-matrix.md +3 -3
  305. package/skills/opencli-adapter-author/references/field-decode-playbook.md +10 -10
  306. package/skills/opencli-adapter-author/references/jsdom-fixture-pattern.md +2 -2
  307. package/skills/opencli-adapter-author/references/site-memory.md +11 -11
  308. package/skills/opencli-adapter-author/references/site-recon.md +7 -7
  309. package/skills/opencli-adapter-author/references/strategy-selection.md +3 -3
  310. package/skills/opencli-adapter-author/references/success-rate-pitfalls.md +4 -4
  311. package/skills/opencli-adapter-author/references/typed-errors.md +2 -2
  312. package/skills/opencli-autofix/SKILL.md +29 -29
  313. package/skills/opencli-browser/SKILL.md +55 -55
  314. package/skills/opencli-browser-sitemap/SKILL.md +7 -7
  315. package/skills/opencli-sitemap-author/SKILL.md +11 -11
  316. package/skills/opencli-sitemap-author/references/sitemap-schema.md +19 -19
  317. package/skills/opencli-usage/SKILL.md +43 -43
@@ -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) {
@@ -1 +1 @@
1
- import{WebSocket as I}from"ws";import{request as W}from"node:http";import{request as F}from"node:https";import{buildEvaluateExpression as _}from"./utils.js";import{generateStealthJs as z}from"./stealth.js";import{waitForDomStableJs as S}from"./dom-helpers.js";import{isRecord as H,saveBase64ToFile as M}from"../utils.js";import{getAllElectronApps as w}from"../electron-apps.js";import{BasePage as E}from"./base-page.js";const R=30000;export const CDP_RESPONSE_BODY_CAPTURE_LIMIT=8388608;export class CDPBridge{_ws=null;_idCounter=0;_pending=new Map;_eventListeners=new Map;async connect(G){if(this._ws)throw Error("CDPBridge is already connected. Call close() before reconnecting.");const V=G?.cdpEndpoint??process.env.OPENCLI_CDP_ENDPOINT;if(!V)throw Error("CDP endpoint not provided (pass cdpEndpoint or set OPENCLI_CDP_ENDPOINT)");let Q=V;if(V.startsWith("http")){const K=await j(`${V.replace(/\/$/,"")}/json`),X=U(K);if(!X||!X.webSocketDebuggerUrl)throw Error("No inspectable targets found at CDP endpoint");Q=X.webSocketDebuggerUrl}return new Promise((K,X)=>{const Z=new I(Q),$=(G?.timeout??10)*1000,J=setTimeout(()=>{this._ws=null;Z.close();X(Error("CDP connect timeout"))},$);Z.on("open",async()=>{clearTimeout(J);this._ws=Z;try{await this.send("Page.enable");this.on("Page.javascriptDialogOpening",(L)=>{if(L?.type==="beforeunload")this.send("Page.handleJavaScriptDialog",{accept:!0}).catch(()=>{})});await this.send("Page.addScriptToEvaluateOnNewDocument",{source:z()});if(G?.initScript)await this.send("Page.addScriptToEvaluateOnNewDocument",{source:G.initScript});if(G?.proxyAuth){this.on("Fetch.requestPaused",(L)=>{const Y=L;if(Y.requestId)this.send("Fetch.continueRequest",{requestId:Y.requestId}).catch(()=>{})});this.on("Fetch.authRequired",(L)=>{const Y=L;if(!Y.requestId)return;this.send("Fetch.continueWithAuth",{requestId:Y.requestId,authChallengeResponse:{response:"ProvideCredentials",username:G.proxyAuth.username,password:G.proxyAuth.password}}).catch(()=>{})});await this.send("Fetch.enable",{handleAuthRequests:!0})}}catch(L){Z.close();X(L instanceof Error?L:Error(String(L)));return}K(new O(this))});Z.on("error",(L)=>{clearTimeout(J);X(L)});Z.on("message",(L)=>{try{const Y=JSON.parse(L.toString());if(Y.id&&this._pending.has(Y.id)){const A=this._pending.get(Y.id);clearTimeout(A.timer);this._pending.delete(Y.id);if(Y.error)A.reject(Error(Y.error.message));else A.resolve(Y.result)}if(Y.method){const A=this._eventListeners.get(Y.method);if(A)for(const B of A)B(Y.params)}}catch(Y){if(process.env.OPENCLI_VERBOSE)console.error("[cdp] Failed to parse WebSocket message:",Y instanceof Error?Y.message:Y)}})})}async close(){if(this._ws){this._ws.close();this._ws=null}for(const G of this._pending.values()){clearTimeout(G.timer);G.reject(Error("CDP connection closed"))}this._pending.clear();this._eventListeners.clear()}async send(G,V={},Q=R){if(!this._ws||this._ws.readyState!==I.OPEN)throw Error("CDP connection is not open");const K=++this._idCounter;return new Promise((X,Z)=>{const $=setTimeout(()=>{this._pending.delete(K);const J=G.startsWith("Page.")?"(页面可能被 JS 弹窗挡住:试 `opencli browser dialog accept`;托管 profile 可 `opencli profile stop <name>` 关闭后重跑命令自动重启会话)":"";Z(Error(`CDP command '${G}' timed out after ${Q/1000}s${J}`))},Q);this._pending.set(K,{resolve:X,reject:Z,timer:$});this._ws.send(JSON.stringify({id:K,method:G,params:V}))})}on(G,V){let Q=this._eventListeners.get(G);if(!Q){Q=new Set;this._eventListeners.set(G,Q)}Q.add(V)}off(G,V){this._eventListeners.get(G)?.delete(V)}waitForEvent(G,V=15000){return new Promise((Q,K)=>{const X=setTimeout(()=>{this.off(G,Z);K(Error(`Timed out waiting for CDP event '${G}'`))},V),Z=($)=>{clearTimeout(X);this.off(G,Z);Q($)};this.on(G,Z)})}}class O extends E{bridge;_pageEnabled=!1;_networkCapturing=!1;_networkCapturePattern="";_networkEntries=[];_pendingRequests=new Map;_pendingBodyFetches=new Set;_consoleMessages=[];_consoleCapturing=!1;constructor(G){super();this.bridge=G}async goto(G,V){if(!this._pageEnabled){await this.bridge.send("Page.enable");this._pageEnabled=!0}const Q=this.bridge.waitForEvent("Page.loadEventFired",30000).catch(()=>{});await this.bridge.send("Page.navigate",{url:G});await Q;this._lastUrl=G;if(V?.waitUntil!=="none"){const K=V?.settleMs??1000;await this.evaluate(S(K,Math.min(500,K)))}}async evaluate(G,...V){const Q=_(G,V),K=await this.bridge.send("Runtime.evaluate",{expression:Q,returnByValue:!0,awaitPromise:!0});if(K.exceptionDetails)throw Error("Evaluate error: "+(K.exceptionDetails.exception?.description||"Unknown exception"));return K.result?.value}async getCookies(G={}){let V;try{const K=await this.bridge.send("Storage.getCookies");V=H(K)&&Array.isArray(K.cookies)?K.cookies:[]}catch{const K=await this.bridge.send("Network.getCookies",G.url?{urls:[G.url]}:{});V=H(K)&&Array.isArray(K.cookies)?K.cookies:[]}const Q=G.domain??(G.url?T(G.url):void 0);return Q?V.filter((K)=>N(K)&&C(K.domain,Q)):V.filter(N)}async screenshot(G={}){const V=G.fullPage===!0,Q=G.width&&G.width>0?Math.ceil(G.width):void 0,K=!V&&G.height&&G.height>0?Math.ceil(G.height):void 0,X=Q!==void 0||K!==void 0;if(X){if(Q!==void 0&&V)await this.bridge.send("Emulation.setDeviceMetricsOverride",{mobile:!1,width:Q,height:0,deviceScaleFactor:1});let Z=Q??0,$=K??0;if(V){const J=await this.bridge.send("Page.getLayoutMetrics"),L=H(J)?J:{},Y=H(L.cssContentSize)?L.cssContentSize:void 0,A=H(L.contentSize)?L.contentSize:void 0,B=Y??A;if(B&&typeof B.width==="number"&&typeof B.height==="number"){if(Z===0)Z=Math.ceil(B.width);$=Math.ceil(B.height)}}await this.bridge.send("Emulation.setDeviceMetricsOverride",{mobile:!1,width:Z,height:$,deviceScaleFactor:1})}try{const Z=await this.bridge.send("Page.captureScreenshot",{format:G.format??"png",quality:G.format==="jpeg"?G.quality??80:void 0,captureBeyondViewport:!X&&V}),$=H(Z)&&typeof Z.data==="string"?Z.data:"";if(G.path)await M($,G.path);return $}finally{if(X)await this.bridge.send("Emulation.clearDeviceMetricsOverride").catch(()=>{})}}async startNetworkCapture(G=""){this._networkCapturePattern=G;if(!this._networkCapturing){this._networkEntries=[];this._pendingRequests.clear();this._pendingBodyFetches.clear();await this.bridge.send("Network.enable");this.bridge.on("Network.requestWillBeSent",(V)=>{const Q=V;if(!this._networkCapturePattern||Q.request.url.includes(this._networkCapturePattern)){const K=this._networkEntries.push({url:Q.request.url,method:Q.request.method,timestamp:Date.now()})-1;this._pendingRequests.set(Q.requestId,K)}});this.bridge.on("Network.responseReceived",(V)=>{const Q=V,K=this._pendingRequests.get(Q.requestId);if(K!==void 0){this._networkEntries[K].responseStatus=Q.response.status;this._networkEntries[K].responseContentType=Q.response.mimeType||""}});this.bridge.on("Network.loadingFinished",(V)=>{const Q=V,K=this._pendingRequests.get(Q.requestId);if(K!==void 0){const X=this.bridge.send("Network.getResponseBody",{requestId:Q.requestId}).then((Z)=>{const $=Z;if(typeof $?.body==="string"){const J=$.body.length,L=J>CDP_RESPONSE_BODY_CAPTURE_LIMIT,Y=L?$.body.slice(0,CDP_RESPONSE_BODY_CAPTURE_LIMIT):$.body;this._networkEntries[K].responsePreview=$.base64Encoded?`base64:${Y}`:Y;this._networkEntries[K].responseBodyFullSize=J;this._networkEntries[K].responseBodyTruncated=L}}).catch((Z)=>{if(process.env.OPENCLI_VERBOSE)console.error(`[cdp] getResponseBody failed for ${Q.requestId}:`,Z instanceof Error?Z.message:Z)}).finally(()=>{this._pendingBodyFetches.delete(X)});this._pendingBodyFetches.add(X);this._pendingRequests.delete(Q.requestId)}});this._networkCapturing=!0}return!0}async readNetworkCapture(){if(this._pendingBodyFetches.size>0)await Promise.all([...this._pendingBodyFetches]);const G=[...this._networkEntries];this._networkEntries=[];return G}async consoleMessages(G="all"){if(!this._consoleCapturing){await this.bridge.send("Runtime.enable");this.bridge.on("Runtime.consoleAPICalled",(V)=>{const Q=V,K=(Q.args||[]).map((X)=>X.value!==void 0?String(X.value):X.description||"").join(" ");this._consoleMessages.push({type:Q.type,text:K,timestamp:Date.now()});if(this._consoleMessages.length>500)this._consoleMessages.shift()});this.bridge.on("Runtime.exceptionThrown",(V)=>{const Q=V,K=Q.exceptionDetails?.exception?.description||Q.exceptionDetails?.text||"Unknown exception";this._consoleMessages.push({type:"error",text:K,timestamp:Date.now()});if(this._consoleMessages.length>500)this._consoleMessages.shift()});this._consoleCapturing=!0}if(G==="all")return[...this._consoleMessages];if(G==="error")return this._consoleMessages.filter((V)=>V.type==="error"||V.type==="warning");return this._consoleMessages.filter((V)=>V.type===G)}async tabs(){return[]}async selectTab(G){}async cdp(G,V={}){return this.bridge.send(G,V)}async handleJavaScriptDialog(G,V){await this.cdp("Page.handleJavaScriptDialog",{accept:G,...V!==void 0&&{promptText:V}})}async nativeClick(G,V){await this.cdp("Input.dispatchMouseEvent",{type:"mouseMoved",x:G,y:V});await this.cdp("Input.dispatchMouseEvent",{type:"mousePressed",x:G,y:V,button:"left",clickCount:1});await this.cdp("Input.dispatchMouseEvent",{type:"mouseReleased",x:G,y:V,button:"left",clickCount:1})}async nativeType(G){await this.cdp("Input.insertText",{text:G})}async insertText(G){await this.nativeType(G)}async nativeKeyPress(G,V=[]){let Q=0;for(const K of V){if(K==="Alt")Q|=1;if(K==="Ctrl"||K==="Control")Q|=2;if(K==="Meta")Q|=4;if(K==="Shift")Q|=8}await this.cdp("Input.dispatchKeyEvent",{type:"keyDown",key:G,modifiers:Q});await this.cdp("Input.dispatchKeyEvent",{type:"keyUp",key:G,modifiers:Q})}}function T(G){try{return new URL(G).hostname}catch{return}}function N(G){return H(G)&&typeof G.name==="string"&&typeof G.value==="string"&&typeof G.domain==="string"}function C(G,V){const Q=G.replace(/^\./,"").toLowerCase(),K=V.replace(/^\./,"").toLowerCase();return K===Q||K.endsWith(`.${Q}`)}function U(G){const V=b(process.env.OPENCLI_CDP_TARGET);return G.map((K,X)=>({target:K,index:X,score:q(K,V)})).filter(({score:K})=>Number.isFinite(K)).sort((K,X)=>{if(X.score!==K.score)return X.score-K.score;return K.index-X.index})[0]?.target}function q(G,V){if(!G.webSocketDebuggerUrl)return Number.NEGATIVE_INFINITY;const Q=(G.type??"").toLowerCase(),K=(G.url??"").toLowerCase(),X=(G.title??"").toLowerCase(),Z=`${X} ${K}`;if(!Z.trim()&&!Q)return Number.NEGATIVE_INFINITY;if(Z.includes("devtools"))return Number.NEGATIVE_INFINITY;if(Q==="background_page"||Q==="service_worker")return Number.NEGATIVE_INFINITY;let $=0;if(V&&V.test(Z))$+=1000;if(Q==="app")$+=120;else if(Q==="webview")$+=100;else if(Q==="page")$+=80;else if(Q==="iframe")$+=20;if(K.startsWith("http://localhost")||K.startsWith("https://localhost"))$+=90;if(K.startsWith("file://"))$+=60;if(K.startsWith("http://127.0.0.1")||K.startsWith("https://127.0.0.1"))$+=50;if(K.startsWith("about:blank"))$-=120;if(K===""||K==="about:blank")$-=40;if(X&&X!=="devtools")$+=25;const J=Object.values(w()).map((L)=>(L.displayName??L.processName).toLowerCase());for(const L of J)if(X.includes(L)){$+=120;break}for(const L of J)if(K.includes(L)){$+=100;break}return $}function b(G){const V=G?.trim();if(!V)return;return new RegExp(D(V.toLowerCase()))}function D(G){return G.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}export const __test__={selectCDPTarget:U,scoreCDPTarget:q};function j(G){return new Promise((V,Q)=>{const K=new URL(G),X=(K.protocol==="https:"?F:W)(K,(Z)=>{const $=Z.statusCode??0;if($<200||$>=300){Z.resume();Q(Error(`Failed to fetch CDP targets: HTTP ${$}`));return}const J=[];Z.on("data",(L)=>J.push(Buffer.isBuffer(L)?L:Buffer.from(L)));Z.on("end",()=>{try{V(JSON.parse(Buffer.concat(J).toString("utf8")))}catch(L){Q(L instanceof Error?L:Error(String(L)))}})});X.on("error",Q);X.setTimeout(1e4,()=>X.destroy(Error("Timed out fetching CDP targets")));X.end()})}
1
+ import{WebSocket as I}from"ws";import{request as W}from"node:http";import{request as F}from"node:https";import{buildEvaluateExpression as _}from"./utils.js";import{generateStealthJs as z}from"./stealth.js";import{waitForDomStableJs as S}from"./dom-helpers.js";import{isRecord as H,saveBase64ToFile as M}from"../utils.js";import{getAllElectronApps as w}from"../electron-apps.js";import{BasePage as E}from"./base-page.js";const R=30000;export const CDP_RESPONSE_BODY_CAPTURE_LIMIT=8388608;export class CDPBridge{_ws=null;_idCounter=0;_pending=new Map;_eventListeners=new Map;async connect(G){if(this._ws)throw Error("CDPBridge is already connected. Call close() before reconnecting.");const V=G?.cdpEndpoint??process.env.OPENCLI_CDP_ENDPOINT;if(!V)throw Error("CDP endpoint not provided (pass cdpEndpoint or set OPENCLI_CDP_ENDPOINT)");let Q=V;if(V.startsWith("http")){const K=await j(`${V.replace(/\/$/,"")}/json`),X=U(K);if(!X||!X.webSocketDebuggerUrl)throw Error("No inspectable targets found at CDP endpoint");Q=X.webSocketDebuggerUrl}return new Promise((K,X)=>{const Z=new I(Q),$=(G?.timeout??10)*1000,J=setTimeout(()=>{this._ws=null;Z.close();X(Error("CDP connect timeout"))},$);Z.on("open",async()=>{clearTimeout(J);this._ws=Z;try{await this.send("Page.enable");this.on("Page.javascriptDialogOpening",(L)=>{if(L?.type==="beforeunload")this.send("Page.handleJavaScriptDialog",{accept:!0}).catch(()=>{})});await this.send("Page.addScriptToEvaluateOnNewDocument",{source:z()});if(G?.initScript)await this.send("Page.addScriptToEvaluateOnNewDocument",{source:G.initScript});if(G?.proxyAuth){this.on("Fetch.requestPaused",(L)=>{const Y=L;if(Y.requestId)this.send("Fetch.continueRequest",{requestId:Y.requestId}).catch(()=>{})});this.on("Fetch.authRequired",(L)=>{const Y=L;if(!Y.requestId)return;this.send("Fetch.continueWithAuth",{requestId:Y.requestId,authChallengeResponse:{response:"ProvideCredentials",username:G.proxyAuth.username,password:G.proxyAuth.password}}).catch(()=>{})});await this.send("Fetch.enable",{handleAuthRequests:!0})}}catch(L){Z.close();X(L instanceof Error?L:Error(String(L)));return}K(new O(this))});Z.on("error",(L)=>{clearTimeout(J);X(L)});Z.on("message",(L)=>{try{const Y=JSON.parse(L.toString());if(Y.id&&this._pending.has(Y.id)){const A=this._pending.get(Y.id);clearTimeout(A.timer);this._pending.delete(Y.id);if(Y.error)A.reject(Error(Y.error.message));else A.resolve(Y.result)}if(Y.method){const A=this._eventListeners.get(Y.method);if(A)for(const B of A)B(Y.params)}}catch(Y){if(process.env.OPENCLI_VERBOSE)console.error("[cdp] Failed to parse WebSocket message:",Y instanceof Error?Y.message:Y)}})})}async close(){if(this._ws){this._ws.close();this._ws=null}for(const G of this._pending.values()){clearTimeout(G.timer);G.reject(Error("CDP connection closed"))}this._pending.clear();this._eventListeners.clear()}async send(G,V={},Q=R){if(!this._ws||this._ws.readyState!==I.OPEN)throw Error("CDP connection is not open");const K=++this._idCounter;return new Promise((X,Z)=>{const $=setTimeout(()=>{this._pending.delete(K);const J=G.startsWith("Page.")?"(页面可能被 JS 弹窗挡住:试 `ppcli browser dialog accept`;托管 profile 可 `ppcli profile stop <name>` 关闭后重跑命令自动重启会话)":"";Z(Error(`CDP command '${G}' timed out after ${Q/1000}s${J}`))},Q);this._pending.set(K,{resolve:X,reject:Z,timer:$});this._ws.send(JSON.stringify({id:K,method:G,params:V}))})}on(G,V){let Q=this._eventListeners.get(G);if(!Q){Q=new Set;this._eventListeners.set(G,Q)}Q.add(V)}off(G,V){this._eventListeners.get(G)?.delete(V)}waitForEvent(G,V=15000){return new Promise((Q,K)=>{const X=setTimeout(()=>{this.off(G,Z);K(Error(`Timed out waiting for CDP event '${G}'`))},V),Z=($)=>{clearTimeout(X);this.off(G,Z);Q($)};this.on(G,Z)})}}class O extends E{bridge;_pageEnabled=!1;_networkCapturing=!1;_networkCapturePattern="";_networkEntries=[];_pendingRequests=new Map;_pendingBodyFetches=new Set;_consoleMessages=[];_consoleCapturing=!1;constructor(G){super();this.bridge=G}async goto(G,V){if(!this._pageEnabled){await this.bridge.send("Page.enable");this._pageEnabled=!0}const Q=this.bridge.waitForEvent("Page.loadEventFired",30000).catch(()=>{});await this.bridge.send("Page.navigate",{url:G});await Q;this._lastUrl=G;if(V?.waitUntil!=="none"){const K=V?.settleMs??1000;await this.evaluate(S(K,Math.min(500,K)))}}async evaluate(G,...V){const Q=_(G,V),K=await this.bridge.send("Runtime.evaluate",{expression:Q,returnByValue:!0,awaitPromise:!0});if(K.exceptionDetails)throw Error("Evaluate error: "+(K.exceptionDetails.exception?.description||"Unknown exception"));return K.result?.value}async getCookies(G={}){let V;try{const K=await this.bridge.send("Storage.getCookies");V=H(K)&&Array.isArray(K.cookies)?K.cookies:[]}catch{const K=await this.bridge.send("Network.getCookies",G.url?{urls:[G.url]}:{});V=H(K)&&Array.isArray(K.cookies)?K.cookies:[]}const Q=G.domain??(G.url?T(G.url):void 0);return Q?V.filter((K)=>N(K)&&C(K.domain,Q)):V.filter(N)}async screenshot(G={}){const V=G.fullPage===!0,Q=G.width&&G.width>0?Math.ceil(G.width):void 0,K=!V&&G.height&&G.height>0?Math.ceil(G.height):void 0,X=Q!==void 0||K!==void 0;if(X){if(Q!==void 0&&V)await this.bridge.send("Emulation.setDeviceMetricsOverride",{mobile:!1,width:Q,height:0,deviceScaleFactor:1});let Z=Q??0,$=K??0;if(V){const J=await this.bridge.send("Page.getLayoutMetrics"),L=H(J)?J:{},Y=H(L.cssContentSize)?L.cssContentSize:void 0,A=H(L.contentSize)?L.contentSize:void 0,B=Y??A;if(B&&typeof B.width==="number"&&typeof B.height==="number"){if(Z===0)Z=Math.ceil(B.width);$=Math.ceil(B.height)}}await this.bridge.send("Emulation.setDeviceMetricsOverride",{mobile:!1,width:Z,height:$,deviceScaleFactor:1})}try{const Z=await this.bridge.send("Page.captureScreenshot",{format:G.format??"png",quality:G.format==="jpeg"?G.quality??80:void 0,captureBeyondViewport:!X&&V}),$=H(Z)&&typeof Z.data==="string"?Z.data:"";if(G.path)await M($,G.path);return $}finally{if(X)await this.bridge.send("Emulation.clearDeviceMetricsOverride").catch(()=>{})}}async startNetworkCapture(G=""){this._networkCapturePattern=G;if(!this._networkCapturing){this._networkEntries=[];this._pendingRequests.clear();this._pendingBodyFetches.clear();await this.bridge.send("Network.enable");this.bridge.on("Network.requestWillBeSent",(V)=>{const Q=V;if(!this._networkCapturePattern||Q.request.url.includes(this._networkCapturePattern)){const K=this._networkEntries.push({url:Q.request.url,method:Q.request.method,timestamp:Date.now()})-1;this._pendingRequests.set(Q.requestId,K)}});this.bridge.on("Network.responseReceived",(V)=>{const Q=V,K=this._pendingRequests.get(Q.requestId);if(K!==void 0){this._networkEntries[K].responseStatus=Q.response.status;this._networkEntries[K].responseContentType=Q.response.mimeType||""}});this.bridge.on("Network.loadingFinished",(V)=>{const Q=V,K=this._pendingRequests.get(Q.requestId);if(K!==void 0){const X=this.bridge.send("Network.getResponseBody",{requestId:Q.requestId}).then((Z)=>{const $=Z;if(typeof $?.body==="string"){const J=$.body.length,L=J>CDP_RESPONSE_BODY_CAPTURE_LIMIT,Y=L?$.body.slice(0,CDP_RESPONSE_BODY_CAPTURE_LIMIT):$.body;this._networkEntries[K].responsePreview=$.base64Encoded?`base64:${Y}`:Y;this._networkEntries[K].responseBodyFullSize=J;this._networkEntries[K].responseBodyTruncated=L}}).catch((Z)=>{if(process.env.OPENCLI_VERBOSE)console.error(`[cdp] getResponseBody failed for ${Q.requestId}:`,Z instanceof Error?Z.message:Z)}).finally(()=>{this._pendingBodyFetches.delete(X)});this._pendingBodyFetches.add(X);this._pendingRequests.delete(Q.requestId)}});this._networkCapturing=!0}return!0}async readNetworkCapture(){if(this._pendingBodyFetches.size>0)await Promise.all([...this._pendingBodyFetches]);const G=[...this._networkEntries];this._networkEntries=[];return G}async consoleMessages(G="all"){if(!this._consoleCapturing){await this.bridge.send("Runtime.enable");this.bridge.on("Runtime.consoleAPICalled",(V)=>{const Q=V,K=(Q.args||[]).map((X)=>X.value!==void 0?String(X.value):X.description||"").join(" ");this._consoleMessages.push({type:Q.type,text:K,timestamp:Date.now()});if(this._consoleMessages.length>500)this._consoleMessages.shift()});this.bridge.on("Runtime.exceptionThrown",(V)=>{const Q=V,K=Q.exceptionDetails?.exception?.description||Q.exceptionDetails?.text||"Unknown exception";this._consoleMessages.push({type:"error",text:K,timestamp:Date.now()});if(this._consoleMessages.length>500)this._consoleMessages.shift()});this._consoleCapturing=!0}if(G==="all")return[...this._consoleMessages];if(G==="error")return this._consoleMessages.filter((V)=>V.type==="error"||V.type==="warning");return this._consoleMessages.filter((V)=>V.type===G)}async tabs(){return[]}async selectTab(G){}async cdp(G,V={}){return this.bridge.send(G,V)}async handleJavaScriptDialog(G,V){await this.cdp("Page.handleJavaScriptDialog",{accept:G,...V!==void 0&&{promptText:V}})}async nativeClick(G,V){await this.cdp("Input.dispatchMouseEvent",{type:"mouseMoved",x:G,y:V});await this.cdp("Input.dispatchMouseEvent",{type:"mousePressed",x:G,y:V,button:"left",clickCount:1});await this.cdp("Input.dispatchMouseEvent",{type:"mouseReleased",x:G,y:V,button:"left",clickCount:1})}async nativeType(G){await this.cdp("Input.insertText",{text:G})}async insertText(G){await this.nativeType(G)}async nativeKeyPress(G,V=[]){let Q=0;for(const K of V){if(K==="Alt")Q|=1;if(K==="Ctrl"||K==="Control")Q|=2;if(K==="Meta")Q|=4;if(K==="Shift")Q|=8}await this.cdp("Input.dispatchKeyEvent",{type:"keyDown",key:G,modifiers:Q});await this.cdp("Input.dispatchKeyEvent",{type:"keyUp",key:G,modifiers:Q})}}function T(G){try{return new URL(G).hostname}catch{return}}function N(G){return H(G)&&typeof G.name==="string"&&typeof G.value==="string"&&typeof G.domain==="string"}function C(G,V){const Q=G.replace(/^\./,"").toLowerCase(),K=V.replace(/^\./,"").toLowerCase();return K===Q||K.endsWith(`.${Q}`)}function U(G){const V=b(process.env.OPENCLI_CDP_TARGET);return G.map((K,X)=>({target:K,index:X,score:q(K,V)})).filter(({score:K})=>Number.isFinite(K)).sort((K,X)=>{if(X.score!==K.score)return X.score-K.score;return K.index-X.index})[0]?.target}function q(G,V){if(!G.webSocketDebuggerUrl)return Number.NEGATIVE_INFINITY;const Q=(G.type??"").toLowerCase(),K=(G.url??"").toLowerCase(),X=(G.title??"").toLowerCase(),Z=`${X} ${K}`;if(!Z.trim()&&!Q)return Number.NEGATIVE_INFINITY;if(Z.includes("devtools"))return Number.NEGATIVE_INFINITY;if(Q==="background_page"||Q==="service_worker")return Number.NEGATIVE_INFINITY;let $=0;if(V&&V.test(Z))$+=1000;if(Q==="app")$+=120;else if(Q==="webview")$+=100;else if(Q==="page")$+=80;else if(Q==="iframe")$+=20;if(K.startsWith("http://localhost")||K.startsWith("https://localhost"))$+=90;if(K.startsWith("file://"))$+=60;if(K.startsWith("http://127.0.0.1")||K.startsWith("https://127.0.0.1"))$+=50;if(K.startsWith("about:blank"))$-=120;if(K===""||K==="about:blank")$-=40;if(X&&X!=="devtools")$+=25;const J=Object.values(w()).map((L)=>(L.displayName??L.processName).toLowerCase());for(const L of J)if(X.includes(L)){$+=120;break}for(const L of J)if(K.includes(L)){$+=100;break}return $}function b(G){const V=G?.trim();if(!V)return;return new RegExp(D(V.toLowerCase()))}function D(G){return G.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}export const __test__={selectCDPTarget:U,scoreCDPTarget:q};function j(G){return new Promise((V,Q)=>{const K=new URL(G),X=(K.protocol==="https:"?F:W)(K,(Z)=>{const $=Z.statusCode??0;if($<200||$>=300){Z.resume();Q(Error(`Failed to fetch CDP targets: HTTP ${$}`));return}const J=[];Z.on("data",(L)=>J.push(Buffer.isBuffer(L)?L:Buffer.from(L)));Z.on("end",()=>{try{V(JSON.parse(Buffer.concat(J).toString("utf8")))}catch(L){Q(L instanceof Error?L:Error(String(L)))}})});X.on("error",Q);X.setTimeout(1e4,()=>X.destroy(Error("Timed out fetching CDP targets")));X.end()})}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * HTTP client for communicating with the opencli daemon.
2
+ * HTTP client for communicating with the ppcli daemon.
3
3
  *
4
4
  * Provides a typed send() function that posts a Command and returns a Result.
5
5
  */
@@ -1,12 +1,12 @@
1
1
  import{spawn as C}from"node:child_process";import{fileURLToPath as F}from"node:url";import*as M from"node:fs";import*as K from"node:path";import{DEFAULT_DAEMON_PORT}from"../constants.js";import{BrowserConnectError as J}from"../errors.js";import{PKG_VERSION as N}from"../version.js";import{waitForBridgeReady as b}from"./bridge-readiness.js";import{fetchDaemonStatus as B,getDaemonHealth as g,requestDaemonShutdown as O}from"./daemon-transport.js";export function resolveDaemonLaunchSpec(){const z=K.dirname(F(import.meta.url)),Q=K.resolve(z,".."),W=K.join(Q,"daemon.ts"),Z=K.join(Q,"daemon.js"),Y=M.existsSync(W),X=Y?W:Z,$=!!process.versions?.bun,U=Y&&!$;return{binary:process.execPath,args:U?["--import","tsx/esm",X]:[X],scriptPath:X}}export function spawnDaemonProcess(){const z=resolveDaemonLaunchSpec(),Q=C(z.binary,z.args,{detached:!0,stdio:"ignore",env:{...process.env}});Q.unref();return Q}export async function waitForDaemonStop(z){const Q=Date.now()+z;while(Date.now()<Q){await x(200);if((await g()).state==="stopped")return!0}return!1}export async function waitForDaemonStatus(z){const Q=Date.now()+z;while(Date.now()<Q){const W=await B({timeout:Math.min(1000,Math.max(100,Q-Date.now()))});if(W)return W;await x(200)}return null}export const daemonLifecycleHooks={requestDaemonShutdown:O,spawnDaemonProcess,waitForDaemonStop};export async function restartDaemon(z={}){const Q=await B();let W=Q===null;if(Q){W=await O()&&await waitForDaemonStop(z.stopTimeoutMs??3000);if(!W)return{previousStatus:Q,status:Q,stopped:!1,spawned:!1}}spawnDaemonProcess();const Z=await waitForDaemonStatus(z.startTimeoutMs??5000);return{previousStatus:Q,status:Z,stopped:W,spawned:!0}}export async function ensureBrowserBridgeReady(z={}){const W=(z.timeoutSeconds&&z.timeoutSeconds>0?z.timeoutSeconds:10)*1000,Z=z.verbose??!0,Y=z.contextId,X=await g({contextId:Y}),$=X.status?.daemonVersion,U=!!X.status&&(!$||$!==N);let q=!1,k=null;if(U){const y=$?`v${$} ≠ v${N}`:`pre-version daemon, CLI is v${N}`;if(Z&&(process.env.OPENCLI_VERBOSE||process.stderr.isTTY))process.stderr.write(`⚠️ Stale daemon detected (${y}). Restarting...
2
2
  `);let j=await daemonLifecycleHooks.requestDaemonShutdown()&&await daemonLifecycleHooks.waitForDaemonStop(3000);if(!j){const G=X.status?.pid;if(typeof G==="number"&&Number.isInteger(G)&&G>0){try{process.kill(G,"SIGKILL")}catch{}j=await daemonLifecycleHooks.waitForDaemonStop(2000)}}if(!j)throw new J("Stale daemon could not be replaced",`A stale daemon (${y}) is running but did not shut down (graceful + SIGKILL both failed).
3
- Run manually: opencli daemon stop`,"daemon-not-running");q=!0}if(!q&&X.state==="ready")return{health:X,spawnedProcess:k};if(!q&&X.state==="profile-required")throw V(X,Y);if(q||X.state==="stopped"){if(Z&&(process.env.OPENCLI_VERBOSE||process.stderr.isTTY))process.stderr.write(`⏳ Starting daemon...
3
+ Run manually: ppcli daemon stop`,"daemon-not-running");q=!0}if(!q&&X.state==="ready")return{health:X,spawnedProcess:k};if(!q&&X.state==="profile-required")throw V(X,Y);if(q||X.state==="stopped"){if(Z&&(process.env.OPENCLI_VERBOSE||process.stderr.isTTY))process.stderr.write(`⏳ Starting daemon...
4
4
  `);k=daemonLifecycleHooks.spawnDaemonProcess()}else if(Z&&(process.env.OPENCLI_VERBOSE||process.stderr.isTTY)){process.stderr.write(`⏳ Waiting for Chrome/Chromium extension to connect...
5
- `);process.stderr.write(` Make sure Chrome or Chromium is open and the OpenCLI extension is enabled.
6
- `)}const A=await b(g,{timeoutMs:W,contextId:Y});if(A.state==="ready")return{health:A,spawnedProcess:k};throw V(A,Y)}function V(z,Q){if(z.state==="profile-required")return new J("Multiple Browser Bridge profiles are connected",`Select one with --profile <name>, OPENCLI_PROFILE=<name>, or opencli profile use <name>.
7
- Run opencli profile list to see connected profiles.`,"profile-required");if(z.state==="profile-disconnected"){const W=Q??z.status.contextId??"unknown";return new J(`Browser profile "${W}" is not connected`,"Open the matching Chrome profile and make sure the OpenCLI extension is enabled, or choose another profile with opencli profile use <name>.","profile-disconnected")}if(z.state==="no-extension")return new J("Browser Bridge extension not connected",`Make sure Chrome/Chromium is open and the OpenCLI extension is enabled.
5
+ `);process.stderr.write(` Make sure Chrome or Chromium is open and the ppcli extension is enabled.
6
+ `)}const A=await b(g,{timeoutMs:W,contextId:Y});if(A.state==="ready")return{health:A,spawnedProcess:k};throw V(A,Y)}function V(z,Q){if(z.state==="profile-required")return new J("Multiple Browser Bridge profiles are connected",`Select one with --profile <name>, OPENCLI_PROFILE=<name>, or ppcli profile use <name>.
7
+ Run ppcli profile list to see connected profiles.`,"profile-required");if(z.state==="profile-disconnected"){const W=Q??z.status.contextId??"unknown";return new J(`Browser profile "${W}" is not connected`,"Open the matching Chrome profile and make sure the ppcli extension is enabled, or choose another profile with ppcli profile use <name>.","profile-disconnected")}if(z.state==="no-extension")return new J("Browser Bridge extension not connected",`Make sure Chrome/Chromium is open and the ppcli extension is enabled.
8
8
  If not installed:
9
- 1. Download: https://github.com/jackwener/opencli/releases
10
- `+" 2. Open chrome://extensions → Developer Mode → Load unpacked","extension-not-connected");return new J("Failed to start opencli daemon",`Try running manually:
9
+ 1. Download: https://github.com/jackwener/OpenCLI/releases
10
+ `+" 2. Open chrome://extensions → Developer Mode → Load unpacked","extension-not-connected");return new J("Failed to start ppcli daemon",`Try running manually:
11
11
  node ${resolveDaemonLaunchSpec().scriptPath}
12
12
  Make sure port ${DEFAULT_DAEMON_PORT} is available.`,"daemon-not-running")}function x(z){return new Promise((Q)=>setTimeout(Q,z))}export{DEFAULT_DAEMON_PORT};
@@ -1,2 +1,2 @@
1
1
  export function isDaemonStale(e,n){if(!e||!n)return!1;return!e.daemonVersion||e.daemonVersion!==n}export function formatDaemonVersion(e){return e?.daemonVersion?`v${e.daemonVersion}`:"version unknown"}export function staleDaemonIssue(e,n){return`Stale daemon detected: daemon ${formatDaemonVersion(e)} != CLI v${n}.
2
- Run: opencli daemon restart`}
2
+ Run: ppcli daemon restart`}
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * DOM Snapshot Engine — Advanced DOM pruning for LLM consumption.
3
3
  *
4
- * Inspired by browser-use's multi-layer pruning pipeline, adapted for opencli's
4
+ * Inspired by browser-use's multi-layer pruning pipeline, adapted for ppcli's
5
5
  * Chrome Extension + CDP architecture. Runs entirely in-page via Runtime.evaluate.
6
6
  *
7
7
  * Pipeline:
@@ -735,7 +735,7 @@ import{COMPOUND_INFO_JS as U}from"./compound.js";export function scrollToRefJs(q
735
735
  if (!doc || !doc.body) {
736
736
  const attrs = serializeAttrs(el);
737
737
  const frameLabel = '[F' + crossOriginIndex + ']';
738
- lines.push(indent + '|iframe|' + frameLabel + '<iframe' + (attrs ? ' ' + attrs : '') + ' /> (cross-origin, use: opencli browser frames + browser eval --frame <index>)');
738
+ lines.push(indent + '|iframe|' + frameLabel + '<iframe' + (attrs ? ' ' + attrs : '') + ' /> (cross-origin, use: ppcli browser frames + browser eval --frame <index>)');
739
739
  crossOriginIndex++;
740
740
  return false;
741
741
  }
@@ -750,7 +750,7 @@ import{COMPOUND_INFO_JS as U}from"./compound.js";export function scrollToRefJs(q
750
750
  } catch {
751
751
  const attrs = serializeAttrs(el);
752
752
  const frameLabel = '[F' + crossOriginIndex + ']';
753
- lines.push(indent + '|iframe|' + frameLabel + '<iframe' + (attrs ? ' ' + attrs : '') + ' /> (blocked, use: opencli browser frames + browser eval --frame <index>)');
753
+ lines.push(indent + '|iframe|' + frameLabel + '<iframe' + (attrs ? ' ' + attrs : '') + ' /> (blocked, use: ppcli browser frames + browser eval --frame <index>)');
754
754
  crossOriginIndex++;
755
755
  return false;
756
756
  }
@@ -1,5 +1,5 @@
1
- import{BrowserConnectError as z}from"../errors.js";import{DEFAULT_DAEMON_PORT as J}from"../constants.js";const K=["Extension disconnected","Extension not connected","attach failed","Detached while handling command","Debugger is not attached to the tab","no longer exists","No tab with id","CDP connection","Daemon command failed","No window with id"],Q=["Inspected target navigated or closed"];function W(q){return q instanceof Error?q.message:String(q)}export function classifyBrowserError(q){const x=W(q);if(K.some((H)=>x.includes(H)))return{kind:"extension-transient",retryable:!0,delayMs:1500};if(Q.some((H)=>x.includes(H)))return{kind:"target-navigation",retryable:!0,delayMs:200};if(x.includes("-32000")&&/target|context/i.test(x))return{kind:"target-navigation",retryable:!0,delayMs:200};return{kind:"non-retryable",retryable:!1,delayMs:0}}export function isTransientBrowserError(q){return classifyBrowserError(q).retryable}export function formatBrowserConnectError(q,x){switch(q){case"daemon-not-running":return new z("Cannot connect to opencli daemon."+(x?`
1
+ import{BrowserConnectError as z}from"../errors.js";import{DEFAULT_DAEMON_PORT as J}from"../constants.js";const K=["Extension disconnected","Extension not connected","attach failed","Detached while handling command","Debugger is not attached to the tab","no longer exists","No tab with id","CDP connection","Daemon command failed","No window with id"],Q=["Inspected target navigated or closed"];function W(q){return q instanceof Error?q.message:String(q)}export function classifyBrowserError(q){const x=W(q);if(K.some((H)=>x.includes(H)))return{kind:"extension-transient",retryable:!0,delayMs:1500};if(Q.some((H)=>x.includes(H)))return{kind:"target-navigation",retryable:!0,delayMs:200};if(x.includes("-32000")&&/target|context/i.test(x))return{kind:"target-navigation",retryable:!0,delayMs:200};return{kind:"non-retryable",retryable:!1,delayMs:0}}export function isTransientBrowserError(q){return classifyBrowserError(q).retryable}export function formatBrowserConnectError(q,x){switch(q){case"daemon-not-running":return new z("Cannot connect to ppcli daemon."+(x?`
2
2
 
3
- ${x}`:""),`Run \`opencli doctor\` to diagnose, or \`opencli daemon restart\` to force a fresh daemon. Default port is ${J}.`,q);case"extension-not-connected":return new z("Browser Bridge extension is not connected."+(x?`
3
+ ${x}`:""),`Run \`ppcli doctor\` to diagnose, or \`ppcli daemon restart\` to force a fresh daemon. Default port is ${J}.`,q);case"extension-not-connected":return new z("Browser Bridge extension is not connected."+(x?`
4
4
 
5
5
  ${x}`:""),"Install the extension from GitHub Releases, then reload.",q);case"command-failed":return new z(`Browser command failed: ${x??"unknown error"}`,void 0,q);default:return new z(x??"Failed to connect to browser",void 0,q)}}
@@ -1 +1 @@
1
- import*as z from"node:fs";import*as V from"node:os";import*as O from"node:path";export const DEFAULT_TTL_MS=86400000;function W(){return process.env.OPENCLI_CACHE_DIR||O.join(V.homedir(),".opencli","cache")}export function getCachePath(B,q=W()){const G=B.replace(/[^a-zA-Z0-9_-]+/g,"_");return O.join(q,"browser-network",`${G}.json`)}export function saveNetworkCache(B,q,G){const J=getCachePath(B,G);z.mkdirSync(O.dirname(J),{recursive:!0});const I={version:1,session:B,savedAt:new Date().toISOString(),entries:q};let H;try{H=z.openSync(J,z.constants.O_WRONLY|z.constants.O_CREAT|z.constants.O_TRUNC,384);z.fchmodSync(H,384);z.writeFileSync(H,JSON.stringify(I),"utf8")}finally{if(H!==void 0)z.closeSync(H)}}export function loadNetworkCache(B,q={}){const G=getCachePath(B,q.baseDir);let J;try{J=z.readFileSync(G,"utf-8")}catch{return{status:"missing"}}let I;try{const K=JSON.parse(J);if(!K||K.version!==1||!Array.isArray(K.entries))return{status:"corrupt"};I=K}catch{return{status:"corrupt"}}const H=q.ttlMs??DEFAULT_TTL_MS,R=q.now??Date.now(),Q=Date.parse(I.savedAt);if(!Number.isFinite(Q))return{status:"corrupt"};const N=R-Q;if(N>H)return{status:"expired",file:I,ageMs:N};return{status:"ok",file:I,ageMs:N}}export function findEntry(B,q){return B.entries.find((G)=>G.key===q)??null}
1
+ import*as z from"node:fs";import*as V from"node:os";import*as O from"node:path";export const DEFAULT_TTL_MS=86400000;function W(){return process.env.OPENCLI_CACHE_DIR||O.join(V.homedir(),".ppcli","cache")}export function getCachePath(B,q=W()){const G=B.replace(/[^a-zA-Z0-9_-]+/g,"_");return O.join(q,"browser-network",`${G}.json`)}export function saveNetworkCache(B,q,G){const J=getCachePath(B,G);z.mkdirSync(O.dirname(J),{recursive:!0});const I={version:1,session:B,savedAt:new Date().toISOString(),entries:q};let H;try{H=z.openSync(J,z.constants.O_WRONLY|z.constants.O_CREAT|z.constants.O_TRUNC,384);z.fchmodSync(H,384);z.writeFileSync(H,JSON.stringify(I),"utf8")}finally{if(H!==void 0)z.closeSync(H)}}export function loadNetworkCache(B,q={}){const G=getCachePath(B,q.baseDir);let J;try{J=z.readFileSync(G,"utf-8")}catch{return{status:"missing"}}let I;try{const K=JSON.parse(J);if(!K||K.version!==1||!Array.isArray(K.entries))return{status:"corrupt"};I=K}catch{return{status:"corrupt"}}const H=q.ttlMs??DEFAULT_TTL_MS,R=q.now??Date.now(),Q=Date.parse(I.savedAt);if(!Number.isFinite(Q))return{status:"corrupt"};const N=R-Q;if(N>H)return{status:"expired",file:I,ageMs:N};return{status:"ok",file:I,ageMs:N}}export function findEntry(B,q){return B.entries.find((G)=>G.key===q)??null}
@@ -1,2 +1,2 @@
1
- import*as Q from"node:fs";import*as S from"node:os";import*as O from"node:path";export const DEFAULT_CONTEXT_ID="default";function U(){const F=process.env.OPENCLI_CONFIG_DIR||O.join(S.homedir(),".opencli");return O.join(F,"browser-profiles.json")}export function managedProfilesBaseDir(){const F=process.env.OPENCLI_CONFIG_DIR||O.join(S.homedir(),".opencli");return O.join(F,"chrome-profiles")}export function normalizeContextId(F){return F?.trim()||void 0}export function emptyProfileConfig(){return{version:1,aliases:{}}}function V(F){return typeof F==="object"&&F!==null&&typeof F.userDataDir==="string"&&F.userDataDir.trim().length>0&&typeof F.port==="number"&&Number.isInteger(F.port)}export function loadProfileConfig(){try{const F=Q.readFileSync(U(),"utf-8"),j=JSON.parse(F),B=j.aliases&&typeof j.aliases==="object"?Object.fromEntries(Object.entries(j.aliases).filter((J)=>{const[K,L]=J;return typeof K==="string"&&K.trim().length>0&&typeof L==="string"&&L.trim().length>0})):{},H=j.managed&&typeof j.managed==="object"?Object.fromEntries(Object.entries(j.managed).filter((J)=>{const[K,L]=J;return typeof K==="string"&&K.trim().length>0&&V(L)})):void 0;return{version:1,aliases:B,...typeof j.defaultContextId==="string"&&j.defaultContextId.trim()?{defaultContextId:j.defaultContextId.trim()}:{},...H&&Object.keys(H).length>0?{managed:H}:{}}}catch{return emptyProfileConfig()}}export function saveProfileConfig(F){const j=U();Q.mkdirSync(O.dirname(j),{recursive:!0});Q.writeFileSync(j,JSON.stringify(F,null,2)+`
1
+ import*as Q from"node:fs";import*as S from"node:os";import*as O from"node:path";export const DEFAULT_CONTEXT_ID="default";function U(){const F=process.env.OPENCLI_CONFIG_DIR||O.join(S.homedir(),".ppcli");return O.join(F,"browser-profiles.json")}export function managedProfilesBaseDir(){const F=process.env.OPENCLI_CONFIG_DIR||O.join(S.homedir(),".ppcli");return O.join(F,"chrome-profiles")}export function normalizeContextId(F){return F?.trim()||void 0}export function emptyProfileConfig(){return{version:1,aliases:{}}}function V(F){return typeof F==="object"&&F!==null&&typeof F.userDataDir==="string"&&F.userDataDir.trim().length>0&&typeof F.port==="number"&&Number.isInteger(F.port)}export function loadProfileConfig(){try{const F=Q.readFileSync(U(),"utf-8"),j=JSON.parse(F),B=j.aliases&&typeof j.aliases==="object"?Object.fromEntries(Object.entries(j.aliases).filter((J)=>{const[K,L]=J;return typeof K==="string"&&K.trim().length>0&&typeof L==="string"&&L.trim().length>0})):{},H=j.managed&&typeof j.managed==="object"?Object.fromEntries(Object.entries(j.managed).filter((J)=>{const[K,L]=J;return typeof K==="string"&&K.trim().length>0&&V(L)})):void 0;return{version:1,aliases:B,...typeof j.defaultContextId==="string"&&j.defaultContextId.trim()?{defaultContextId:j.defaultContextId.trim()}:{},...H&&Object.keys(H).length>0?{managed:H}:{}}}catch{return emptyProfileConfig()}}export function saveProfileConfig(F){const j=U();Q.mkdirSync(O.dirname(j),{recursive:!0});Q.writeFileSync(j,JSON.stringify(F,null,2)+`
2
2
  `,"utf-8")}function W(F){const j=loadProfileConfig();return normalizeContextId(F)??normalizeContextId(process.env.OPENCLI_PROFILE)??normalizeContextId(j.defaultContextId)}export function resolveProfile(F){const j=loadProfileConfig(),B=W(F);if(!B)return{kind:"extension"};const H=j.managed?.[B];if(H)return{kind:"managed",name:B,entry:H};return{kind:"extension",contextId:j.aliases[B]??B}}export function resolveProfileContextId(F){const j=resolveProfile(F);if(j.kind==="managed")return;return j.contextId}export function getManagedProfile(F){return loadProfileConfig().managed?.[normalizeContextId(F)??""]}export function listManagedProfiles(){const F=loadProfileConfig().managed??{};return Object.entries(F).map(([j,B])=>({name:j,entry:B})).sort((j,B)=>j.name.localeCompare(B.name))}const X=/^[A-Za-z0-9][A-Za-z0-9_-]{0,31}$/;export function addManagedProfile(F,j){const B=normalizeContextId(F);if(!B)throw Error("profile 名不能为空");if(!X.test(B))throw Error("profile 名只能包含字母/数字/下划线/连字符,且以字母或数字开头(最长 32 字符)");const H=loadProfileConfig();if(H.managed?.[B])throw Error(`托管 profile "${B}" 已存在`);if(H.aliases[B])throw Error(`"${B}" 已被扩展 profile 别名占用,换个名字`);H.managed={...H.managed,[B]:j};saveProfileConfig(H);return H}export function updateManagedProfile(F,j){const B=normalizeContextId(F);if(!B)throw Error("profile 名不能为空");const H=loadProfileConfig();if(!H.managed?.[B])throw Error(`托管 profile "${B}" 不存在`);H.managed={...H.managed,[B]:j};saveProfileConfig(H);return H}export function removeManagedProfileEntry(F){const j=normalizeContextId(F),B=loadProfileConfig(),H=j?B.managed?.[j]:void 0;if(!j||!H||!B.managed)throw Error(`托管 profile "${F}" 不存在`);delete B.managed[j];if(Object.keys(B.managed).length===0)delete B.managed;if(B.defaultContextId===j)delete B.defaultContextId;saveProfileConfig(B);return H}export function aliasForContextId(F,j){for(const[B,H]of Object.entries(F.aliases))if(H===j)return B;return}export function renameProfile(F,j){const B=normalizeContextId(F),H=normalizeContextId(j);if(!B)throw Error("profile contextId is required");if(!H)throw Error("profile alias is required");const J=loadProfileConfig();if(J.managed?.[H])throw Error(`"${H}" 已被托管 profile 占用,换个别名`);for(const[K,L]of Object.entries(J.aliases))if(K!==H&&L===B)delete J.aliases[K];J.aliases[H]=B;saveProfileConfig(J);return J}export function setDefaultProfile(F){const j=normalizeContextId(F);if(!j)throw Error("profile is required");const B=loadProfileConfig();B.defaultContextId=B.managed?.[j]?j:B.aliases[j]??j;saveProfileConfig(B);return B}
@@ -111,7 +111,7 @@ export function resolveTargetJs(j,g={}){const q=JSON.stringify(j),w=g.nth!==void
111
111
  ok: false,
112
112
  code: 'not_found',
113
113
  message: 'ref=' + ref + ' not found in DOM',
114
- hint: 'The element may have been removed. Re-run \`opencli browser state\` to get a fresh snapshot.',
114
+ hint: 'The element may have been removed. Re-run \`ppcli browser state\` to get a fresh snapshot.',
115
115
  };
116
116
  }
117
117
 
@@ -148,7 +148,7 @@ export function resolveTargetJs(j,g={}){const q=JSON.stringify(j),w=g.nth!==void
148
148
  code: 'stale_ref',
149
149
  message: 'ref=' + ref + ' was <' + fp.tag + '>' + (fp.text ? '"' + fp.text + '"' : '')
150
150
  + ' but now points to <' + liveFp.tag + '>' + (liveFp.text ? '"' + liveFp.text.slice(0, 30) + '"' : ''),
151
- hint: 'The page has changed since the last snapshot. Re-run \`opencli browser state\` to refresh.',
151
+ hint: 'The page has changed since the last snapshot. Re-run \`ppcli browser state\` to refresh.',
152
152
  };
153
153
  }
154
154
 
@@ -171,7 +171,7 @@ export function resolveTargetJs(j,g={}){const q=JSON.stringify(j),w=g.nth!==void
171
171
  ok: false,
172
172
  code: 'selector_not_found',
173
173
  message: 'CSS selector "' + ref + '" matched 0 elements',
174
- hint: 'The element may not exist or may be hidden. Re-run \`opencli browser state\` to check, or use \`opencli browser find --css\` to explore candidates.',
174
+ hint: 'The element may not exist or may be hidden. Re-run \`ppcli browser state\` to check, or use \`ppcli browser find --css\` to explore candidates.',
175
175
  matches_n: 0,
176
176
  };
177
177
  }
@@ -204,7 +204,7 @@ export function resolveTargetJs(j,g={}){const q=JSON.stringify(j),w=g.nth!==void
204
204
  ok: false,
205
205
  code: 'selector_ambiguous',
206
206
  message: 'CSS selector "' + ref + '" matched ' + matches.length + ' elements',
207
- hint: 'Pass --nth <n> (0-based) to pick one, or use a more specific selector. Use \`opencli browser find --css\` to list all candidates.',
207
+ hint: 'Pass --nth <n> (0-based) to pick one, or use a more specific selector. Use \`ppcli browser find --css\` to list all candidates.',
208
208
  candidates: candidates,
209
209
  matches_n: matches.length,
210
210
  };
@@ -1,2 +1,2 @@
1
- import*as O from"node:fs";import*as b from"node:os";import*as M from"node:path";const I=12,K=1,S=[/^id$/i,/_id$/i,/Id$/,/^short_id$/i,/^bvid$/i,/^aid$/i,/^tid$/i,/^asin$/i,/^sku$/i,/^isbn$/i,/^doi$/i,/^slug$/i,/^hn_id$/i,/^username$/i,/^handle$/i,/^uri$/i];export function fixturePath(z,G){return M.join(b.homedir(),".opencli","sites",z,"verify",`${G}.json`)}export function loadFixture(z,G){const q=fixturePath(z,G);if(!O.existsSync(q))return null;try{const B=O.readFileSync(q,"utf-8");return JSON.parse(B)}catch(B){throw Error(`Failed to parse fixture ${q}: ${B instanceof Error?B.message:String(B)}`)}}export function writeFixture(z,G,q){const B=fixturePath(z,G);O.mkdirSync(M.dirname(B),{recursive:!0});O.writeFileSync(B,`${JSON.stringify(q,null,2)}
1
+ import*as O from"node:fs";import*as b from"node:os";import*as M from"node:path";const I=12,K=1,S=[/^id$/i,/_id$/i,/Id$/,/^short_id$/i,/^bvid$/i,/^aid$/i,/^tid$/i,/^asin$/i,/^sku$/i,/^isbn$/i,/^doi$/i,/^slug$/i,/^hn_id$/i,/^username$/i,/^handle$/i,/^uri$/i];export function fixturePath(z,G){return M.join(b.homedir(),".ppcli","sites",z,"verify",`${G}.json`)}export function loadFixture(z,G){const q=fixturePath(z,G);if(!O.existsSync(q))return null;try{const B=O.readFileSync(q,"utf-8");return JSON.parse(B)}catch(B){throw Error(`Failed to parse fixture ${q}: ${B instanceof Error?B.message:String(B)}`)}}export function writeFixture(z,G,q){const B=fixturePath(z,G);O.mkdirSync(M.dirname(B),{recursive:!0});O.writeFileSync(B,`${JSON.stringify(q,null,2)}
2
2
  `,"utf-8");return B}export function deriveFixture(z,G){const q={};if(z.length===0){q.rowCount={min:0};return{...G?{args:G}:{},expect:q}}q.rowCount={min:1};const B=z[0],Z=Object.keys(B);q.columns=Z;const R={};for(const V of Z){const F=new Set;for(const H of z){const Q=H[V];F.add(j(Q))}R[V]=[...F].sort().join("|")}q.types=R;return{...G?{args:G}:{},expect:q}}export function validateRows(z,G){const q=[],B=G.expect;if(!B)return q;if(B.rowCount){const{min:Q,max:W}=B.rowCount;if(typeof Q==="number"&&z.length<Q)q.push({rule:"rowCount",detail:`got ${z.length} rows, expected at least ${Q}`});if(typeof W==="number"&&z.length>W)q.push({rule:"rowCount",detail:`got ${z.length} rows, expected at most ${W}`})}const Z=B.columns??[],R=B.types??{},V=B.patterns??{},F=B.notEmpty??[],H={};for(const[Q,W]of Object.entries(V))try{H[Q]=new RegExp(W)}catch(J){q.push({rule:"pattern",detail:`pattern for "${Q}" invalid: ${J instanceof Error?J.message:String(J)}`})}z.forEach((Q,W)=>{for(const J of Z)if(!(J in Q))q.push({rule:"column",detail:`missing column "${J}"`,rowIndex:W});for(const[J,$]of Object.entries(R)){if(!(J in Q))continue;const C=j(Q[J]);if(!A(C,$))q.push({rule:"type",detail:`"${J}" is ${C}, expected ${$}`,rowIndex:W})}for(const[J,$]of Object.entries(H)){if(!(J in Q))continue;const C=Q[J];if(C===null||C===void 0)continue;if(!$.test(String(C)))q.push({rule:"pattern",detail:`"${J}"=${JSON.stringify(String(C).slice(0,60))} does not match /${$.source}/`,rowIndex:W})}for(const J of F){const $=Q[J];if($===null||$===void 0||String($).trim()==="")q.push({rule:"notEmpty",detail:`"${J}" is empty`,rowIndex:W})}for(const[J,$]of Object.entries(B.mustNotContain??{})){if(!(J in Q))continue;const C=Q[J];if(C===null||C===void 0)continue;const N=String(C);for(const Y of $)if(N.includes(Y))q.push({rule:"mustNotContain",detail:`"${J}" contains forbidden substring ${JSON.stringify(Y)}`,rowIndex:W})}for(const J of B.mustBeTruthy??[]){if(!(J in Q))continue;if(!Q[J])q.push({rule:"mustBeTruthy",detail:`"${J}" is falsy (${JSON.stringify(Q[J])}) — likely silent fallback`,rowIndex:W})}});return q}export function validateRowShape(z,G={}){const q=[],B=G.maxTopLevelKeys??I,Z=G.maxNestedDepth??K;z.forEach((R,V)=>{const F=Object.keys(R);if(F.length>B)q.push({rule:"shapeKeyCount",detail:`row has ${F.length} top-level keys, expected at most ${B}`,rowIndex:V});for(const[H,Q]of Object.entries(R)){const W=U(Q);if(W>Z)q.push({rule:"shapeDepth",detail:`"${H}" nesting depth is ${W}, expected at most ${Z}`,rowIndex:V});for(const J of X(Q,H))q.push({rule:"shapeNestedId",detail:`id-shaped field "${J}" must be a top-level row key`,rowIndex:V})}});return q}export function expandFixtureArgs(z){if(!z)return[];if(Array.isArray(z))return z.map((q)=>String(q));const G=[];for(const[q,B]of Object.entries(z))G.push(`--${q}`,String(B));return G}export function parseSeedArgs(z){if(z===void 0)return;const G=z.trim();if(!G)return;try{const q=JSON.parse(G);if(Array.isArray(q))return q;if(q!==null&&typeof q==="object")return q;return[q]}catch{return[z]}}function j(z){if(z===null)return"null";if(Array.isArray(z))return"array";return typeof z}function U(z){if(z===null||z===void 0||typeof z!=="object")return 0;if(Array.isArray(z)){if(z.length===0)return 1;return 1+Math.max(...z.map(U))}const G=Object.values(z);if(G.length===0)return 1;return 1+Math.max(...G.map(U))}function X(z,G){if(z===null||z===void 0||typeof z!=="object")return[];const q=[];if(Array.isArray(z)){z.forEach((B,Z)=>{q.push(...X(B,`${G}[${Z}]`))});return q}for(const[B,Z]of Object.entries(z)){const R=`${G}.${B}`;if(L(B))q.push(R);q.push(...X(Z,R))}return q}function L(z){return S.some((G)=>G.test(z))}function A(z,G){const q=G.split("|").map((B)=>B.trim()).filter(Boolean);if(q.length===0)return!0;if(q.includes("any"))return!0;return q.includes(z)}
@@ -1 +1 @@
1
- import{openSync as q,closeSync as H,writeSync as b,readFileSync as j,unlinkSync as Y,statSync as v,mkdirSync as D,utimesSync as Q}from"node:fs";import{join as N}from"node:path";import{homedir as F}from"node:os";const V=N(F(),".opencli"),Z=N(V,"browser-adapter-session.lock");function m(z){if(!z)return Z;const x=z.replace(/[^A-Za-z0-9_-]/g,"_");return N(V,`browser-adapter-session.${x}.lock`)}const W=180000,$=600000,u=60;function w(z){return new Promise((x)=>setTimeout(x,z))}function K(z){try{process.kill(z,0);return!0}catch(x){return x?.code==="EPERM"}}function U(z){try{const x=parseInt(j(z,"utf8").trim(),10);return Number.isFinite(x)?x:null}catch{return null}}function g(z){try{const x=q(z,"wx");try{b(x,String(process.pid))}finally{H(x)}return!0}catch(x){if(x?.code==="EEXIST")return!1;throw x}}function C(z){try{const x=U(z),G=Date.now()-v(z).mtimeMs;if(x!==null&&!K(x)||G>$)Y(z)}catch{}}export async function withBrowserSessionLock(z,x){try{D(V,{recursive:!0})}catch{}const G=m(x),B=Date.now()+W;while(!g(G)){C(G);if(g(G))break;if(Date.now()>=B)throw Error(`browser session lock busy: waited ${Math.round(W/1000)}s for another browser command to finish`);await w(u)}const J=setInterval(()=>{try{const X=new Date;Q(G,X,X)}catch{}},60000);if(typeof J.unref==="function")J.unref();try{return await z()}finally{clearInterval(J);try{Y(G)}catch{}}}export async function withBrowserSessionLockIf(z,x,G){return z?withBrowserSessionLock(x,G):x()}export const __lockInternals={LOCK_PATH:Z,ACQUIRE_TIMEOUT_MS:W,STALE_AFTER_MS:$};
1
+ import{openSync as q,closeSync as H,writeSync as b,readFileSync as j,unlinkSync as Y,statSync as v,mkdirSync as D,utimesSync as Q}from"node:fs";import{join as N}from"node:path";import{homedir as F}from"node:os";const V=N(F(),".ppcli"),Z=N(V,"browser-adapter-session.lock");function m(z){if(!z)return Z;const x=z.replace(/[^A-Za-z0-9_-]/g,"_");return N(V,`browser-adapter-session.${x}.lock`)}const W=180000,$=600000,u=60;function w(z){return new Promise((x)=>setTimeout(x,z))}function K(z){try{process.kill(z,0);return!0}catch(x){return x?.code==="EPERM"}}function U(z){try{const x=parseInt(j(z,"utf8").trim(),10);return Number.isFinite(x)?x:null}catch{return null}}function g(z){try{const x=q(z,"wx");try{b(x,String(process.pid))}finally{H(x)}return!0}catch(x){if(x?.code==="EEXIST")return!1;throw x}}function C(z){try{const x=U(z),G=Date.now()-v(z).mtimeMs;if(x!==null&&!K(x)||G>$)Y(z)}catch{}}export async function withBrowserSessionLock(z,x){try{D(V,{recursive:!0})}catch{}const G=m(x),B=Date.now()+W;while(!g(G)){C(G);if(g(G))break;if(Date.now()>=B)throw Error(`browser session lock busy: waited ${Math.round(W/1000)}s for another browser command to finish`);await w(u)}const J=setInterval(()=>{try{const X=new Date;Q(G,X,X)}catch{}},60000);if(typeof J.unref==="function")J.unref();try{return await z()}finally{clearInterval(J);try{Y(G)}catch{}}}export async function withBrowserSessionLockIf(z,x,G){return z?withBrowserSessionLock(x,G):x()}export const __lockInternals={LOCK_PATH:Z,ACQUIRE_TIMEOUT_MS:W,STALE_AFTER_MS:$};
@@ -70,7 +70,7 @@ export declare function serializeManifest(manifest: ManifestEntry[]): string;
70
70
  * Metadata audit: every positional arg must carry a non-empty `help` string.
71
71
  *
72
72
  * Why this is a hard gate (not advisory):
73
- * - `opencli twitter followers --help` rendered `Arguments:\n user ` with
73
+ * - `ppcli twitter followers --help` rendered `Arguments:\n user ` with
74
74
  * an empty trailing column. Agents and humans both saw a blank field —
75
75
  * impossible to recover the parameter's purpose without reading source.
76
76
  * - This is metadata completeness, not stylistic taste; failing closed is
@@ -8,7 +8,7 @@ Manifest NOT written. Likely cause: stale dist/ or a broken adapter import.
8
8
  Always run via tsx (\`npm run build-manifest\`), not against compiled dist/.
9
9
  `);process.exit(1)}const X=findManifestMetadataIssues(J);if(X.length>0){process.stderr.write(`❌ ${X.length} positional arg(s) missing \`help\` text:
10
10
  `);for(const Q of X){const Y=Q.sourceFile?` (${Q.sourceFile})`:"";process.stderr.write(` - ${Q.site}/${Q.command} positional "${Q.arg}"${Y}
11
- `)}process.stderr.write("\nEvery positional arg must declare a non-empty `help` string so\n`opencli <site> <cmd> --help` shows callers what the parameter is for.\nAdd `help: '...'` to each arg above and re-run the build.\n");process.exit(1)}const G=v(H);if(G){const Q=diffRemovedEntries(G,J);if(Q.length>q.allowRemovals){process.stderr.write(`❌ ${Q.length} manifest entries would be removed; refusing to overwrite.
11
+ `)}process.stderr.write("\nEvery positional arg must declare a non-empty `help` string so\n`ppcli <site> <cmd> --help` shows callers what the parameter is for.\nAdd `help: '...'` to each arg above and re-run the build.\n");process.exit(1)}const G=v(H);if(G){const Q=diffRemovedEntries(G,J);if(Q.length>q.allowRemovals){process.stderr.write(`❌ ${Q.length} manifest entries would be removed; refusing to overwrite.
12
12
  `);const Y=Q.slice(0,20);for(const Z of Y)process.stderr.write(` - ${Z}
13
13
  `);if(Q.length>Y.length)process.stderr.write(` ... ${Q.length-Y.length} more
14
14
  `);process.stderr.write(`
@@ -1,6 +1,6 @@
1
1
  /**
2
- * argv preprocessing: rewrite `opencli browser <session> <subcommand> ...`
3
- * into `opencli browser --session <session> <subcommand> ...` so commander
2
+ * argv preprocessing: rewrite `ppcli browser <session> <subcommand> ...`
3
+ * into `ppcli browser --session <session> <subcommand> ...` so commander
4
4
  * (which can't combine a parent positional with subcommand dispatch) can parse it.
5
5
  *
6
6
  * The user-facing form is positional; the internal form uses --session. Help text
@@ -17,7 +17,7 @@ export declare function getBrowserSubcommandNames(): ReadonlySet<string>;
17
17
  *
18
18
  * Only acts when `browser` is the root command (i.e. the first non-flag token
19
19
  * after any leading root options), so it can't mis-interpret occurrences of
20
- * the literal word `browser` deeper in the argv (e.g. `opencli adapter init
20
+ * the literal word `browser` deeper in the argv (e.g. `ppcli adapter init
21
21
  * browser/x`, or a URL value containing `browser`).
22
22
  *
23
23
  * Leaves argv unchanged when:
@@ -52,7 +52,7 @@ export interface DashPositionalManifestEntry {
52
52
  browser?: boolean;
53
53
  }
54
54
  /**
55
- * `opencli boss detail -abc123def` fails because commander parses
55
+ * `ppcli boss detail -abc123def` fails because commander parses
56
56
  * `-abc123def` as an unknown option rather than the required
57
57
  * `<security-id>` positional. BOSS 直聘 securityId tokens are opaque
58
58
  * strings that can legitimately start with `-` (issue #1160), and the
@@ -1 +1 @@
1
- const C=new Set(["analyze","back","bind","check","click","close","console","dblclick","dialog","drag","eval","extract","fill","find","focus","frames","get","help","hover","init","keys","network","open","screenshot","scroll","select","state","tab","type","unbind","uncheck","upload","verify","wait"]),W=new Set(["--profile"]);export function getBrowserSubcommandNames(){return C}export function rewriteBrowserArgv(X){const H=[...X];let z=0;while(z<H.length){const Z=H[z];if(!Z.startsWith("-"))break;if(Z.includes("=")){z+=1;continue}if(W.has(Z)&&z+1<H.length)z+=2;else z+=1}if(H[z]!=="browser")return H;const J=z+1,Q=H[J];if(Q===void 0)return H;if(Q==="--session"||Q==="--session="||Q.startsWith("--session="))throw new BrowserSessionArgvError("The `--session` flag is no longer a public option. Use the positional form: opencli browser <session> <command>");if(Q.startsWith("-"))return H;if(C.has(Q))return H;H.splice(J,1,"--session",Q);f(H,J+2);return H}function f(X,H){const z=X.findIndex((J,Q)=>Q>=H&&C.has(J));if(z===-1)return;for(let J=z+1;J<X.length;J+=1){const Q=X[J];if(Q==="--")return;if(Q.startsWith("--window=")){const Z=X.splice(J,1);X.splice(z,0,...Z);return}if(Q==="--window"){const Z=X[J+1];if(Z===void 0||Z==="--")return;const G=X.splice(J,2);X.splice(z,0,...G);return}}}export class BrowserSessionArgvError extends Error{constructor(X){super(X);this.name="BrowserSessionArgvError"}}function q(X){const H=new Map([["-h","none"],["--help","none"],["-v","none"],["--verbose","none"],["-f","required"],["--format","required"],["--trace","required"]]);if(X.browser){H.set("--window","required");H.set("--site-session","required");H.set("--keep-tab","required")}for(const z of X.args??[]){if(z.positional)continue;H.set(`--${z.name}`,z.required||z.valueRequired?"required":"optional")}return H}function U(X,H,z){const J=X[H];if(!J||J==="--")return null;const Q=J.indexOf("="),Z=Q===-1?J:J.slice(0,Q),G=z.get(Z);if(!G&&Q===-1&&J.startsWith("-")&&!J.startsWith("--")&&J.length>2){const V=J.slice(0,2);if(z.get(V)==="required")return{values:[J],nextIndex:H+1}}if(!G)return null;if(Q!==-1||G==="none")return{values:[J],nextIndex:H+1};const T=X[H+1];if(G==="required")return T===void 0?{values:[J],nextIndex:H+1}:{values:[J,T],nextIndex:H+2};if(T!==void 0&&!T.startsWith("-"))return{values:[J,T],nextIndex:H+2};return{values:[J],nextIndex:H+1}}export function escapeLeadingDashPositional(X,H){const z=[...X],J=new Map;for(const Y of H)if(Y.args?.find((y)=>y.positional)?.required)J.set(Y.site+"/"+Y.name,Y);let Q=0;while(Q<z.length){const Y=z[Q];if(!Y.startsWith("-"))break;if(Y.includes("=")){Q+=1;continue}if(W.has(Y)&&Q+1<z.length)Q+=2;else Q+=1}const Z=z[Q],G=z[Q+1],T=Q+2;if(!Z||!G||T>=z.length)return z;const V=J.get(Z+"/"+G);if(!V)return z;const K=q(V),F=[];let $=T;while($<z.length){const Y=z[$];if(Y==="--")return z;const D=U(z,$,K);if(D){F.push(...D.values);$=D.nextIndex;continue}if(!Y.startsWith("-"))return z;if(Y.startsWith("--"))return z;break}if($>=z.length)return z;const N=z[$],L=[],P=[];$+=1;while($<z.length){const Y=U(z,$,K);if(Y){L.push(...Y.values);$=Y.nextIndex;continue}P.push(z[$]);$+=1}return[...z.slice(0,T),...F,...L,"--",N,...P]}
1
+ const C=new Set(["analyze","back","bind","check","click","close","console","dblclick","dialog","drag","eval","extract","fill","find","focus","frames","get","help","hover","init","keys","network","open","screenshot","scroll","select","state","tab","type","unbind","uncheck","upload","verify","wait"]),W=new Set(["--profile"]);export function getBrowserSubcommandNames(){return C}export function rewriteBrowserArgv(X){const H=[...X];let z=0;while(z<H.length){const Z=H[z];if(!Z.startsWith("-"))break;if(Z.includes("=")){z+=1;continue}if(W.has(Z)&&z+1<H.length)z+=2;else z+=1}if(H[z]!=="browser")return H;const J=z+1,Q=H[J];if(Q===void 0)return H;if(Q==="--session"||Q==="--session="||Q.startsWith("--session="))throw new BrowserSessionArgvError("The `--session` flag is no longer a public option. Use the positional form: ppcli browser <session> <command>");if(Q.startsWith("-"))return H;if(C.has(Q))return H;H.splice(J,1,"--session",Q);f(H,J+2);return H}function f(X,H){const z=X.findIndex((J,Q)=>Q>=H&&C.has(J));if(z===-1)return;for(let J=z+1;J<X.length;J+=1){const Q=X[J];if(Q==="--")return;if(Q.startsWith("--window=")){const Z=X.splice(J,1);X.splice(z,0,...Z);return}if(Q==="--window"){const Z=X[J+1];if(Z===void 0||Z==="--")return;const G=X.splice(J,2);X.splice(z,0,...G);return}}}export class BrowserSessionArgvError extends Error{constructor(X){super(X);this.name="BrowserSessionArgvError"}}function q(X){const H=new Map([["-h","none"],["--help","none"],["-v","none"],["--verbose","none"],["-f","required"],["--format","required"],["--trace","required"]]);if(X.browser){H.set("--window","required");H.set("--site-session","required");H.set("--keep-tab","required")}for(const z of X.args??[]){if(z.positional)continue;H.set(`--${z.name}`,z.required||z.valueRequired?"required":"optional")}return H}function U(X,H,z){const J=X[H];if(!J||J==="--")return null;const Q=J.indexOf("="),Z=Q===-1?J:J.slice(0,Q),G=z.get(Z);if(!G&&Q===-1&&J.startsWith("-")&&!J.startsWith("--")&&J.length>2){const V=J.slice(0,2);if(z.get(V)==="required")return{values:[J],nextIndex:H+1}}if(!G)return null;if(Q!==-1||G==="none")return{values:[J],nextIndex:H+1};const T=X[H+1];if(G==="required")return T===void 0?{values:[J],nextIndex:H+1}:{values:[J,T],nextIndex:H+2};if(T!==void 0&&!T.startsWith("-"))return{values:[J,T],nextIndex:H+2};return{values:[J],nextIndex:H+1}}export function escapeLeadingDashPositional(X,H){const z=[...X],J=new Map;for(const Y of H)if(Y.args?.find((y)=>y.positional)?.required)J.set(Y.site+"/"+Y.name,Y);let Q=0;while(Q<z.length){const Y=z[Q];if(!Y.startsWith("-"))break;if(Y.includes("=")){Q+=1;continue}if(W.has(Y)&&Q+1<z.length)Q+=2;else Q+=1}const Z=z[Q],G=z[Q+1],T=Q+2;if(!Z||!G||T>=z.length)return z;const V=J.get(Z+"/"+G);if(!V)return z;const K=q(V),F=[];let $=T;while($<z.length){const Y=z[$];if(Y==="--")return z;const D=U(z,$,K);if(D){F.push(...D.values);$=D.nextIndex;continue}if(!Y.startsWith("-"))return z;if(Y.startsWith("--"))return z;break}if($>=z.length)return z;const N=z[$],L=[],P=[];$+=1;while($<z.length){const Y=U(z,$,K);if(Y){L.push(...Y.values);$=Y.nextIndex;continue}P.push(z[$]);$+=1}return[...z.slice(0,T),...F,...L,"--",N,...P]}
package/dist/src/cli.d.ts CHANGED
@@ -15,7 +15,7 @@ export declare function selectFreshByTimestamp<T extends {
15
15
  };
16
16
  /**
17
17
  * Check whether the site-memory scaffolding exists under
18
- * ~/.opencli/sites/<site>/. Agents have a strong tendency to forget to write
18
+ * ~/.ppcli/sites/<site>/. Agents have a strong tendency to forget to write
19
19
  * endpoints.json / notes.md after a successful verify, which dooms the next
20
20
  * agent to redo recon from scratch. Surfacing the current state as part of
21
21
  * verify's final report converts that "silent skip" into a visible nudge;