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,3 +1,3 @@
1
1
  import{log as w}from"./logger.js";import y from"js-yaml";import{fullName as F,getRegistry as T}from"./registry.js";import{render as A}from"./output.js";import{executeCommand as N,prepareCommandArgs as H}from"./execution.js";import{enforceRateLimit as x}from"./rate-limit.js";import{commandHelpData as S,formatCommandHelpText as R,formatCommandListTerm as f,formatSiteCommandDescription as v,formatSiteHelpText as b,getRequestedHelpFormat as j,renderStructuredHelp as E,siteHelpData as C}from"./help.js";import{CliError as u,EXIT_CODES as g,toEnvelope as p}from"./errors.js";export function registerCommandToProgram(Z,J){if(Z.commands.some((z)=>z.name()===J.name))return;const Q=Z.command(J.name).description(v(J));if(J.aliases?.length)Q.aliases(J.aliases);const P=[];for(const z of J.args)if(z.positional){const U=z.required?`<${z.name}>`:`[${z.name}]`;Q.argument(U,z.help??"");P.push(z)}else{const V=z.required||z.valueRequired?`--${z.name} <value>`:`--${z.name} [value]`;if(z.required)Q.requiredOption(V,z.help??"");else if(z.default!=null)Q.option(V,z.help??"",String(z.default));else Q.option(V,z.help??"")}Q.option("-f, --format <fmt>","Output format: table, plain, json, yaml, md, csv","table").option("--trace <mode>","Trace capture: off, on, retain-on-failure","off").option("-v, --verbose","Debug output",!1);if(J.browser)Q.option("--window <mode>","Browser window mode: foreground or background").option("--site-session <mode>","Adapter site session lifecycle: ephemeral or persistent").option("--keep-tab <bool>","Keep the browser tab lease after the command finishes");const W=Q.helpInformation.bind(Q);Q.helpInformation=(z)=>{const U=j();if(U)return E(S(J),U);W(z);return R(J)};Q.action(async(...z)=>{const U=z[P.length]??{},V=typeof U==="object"&&U!==null?U:{},I=Date.now();try{const Y={};for(let $=0;$<P.length;$++){const L=z[$];if(L!==void 0)Y[P[$].name]=L}for(const $ of J.args){if($.positional)continue;const L=$.name.replace(/-([a-z])/g,(O,K)=>K.toUpperCase()),k=V[$.name]??V[L];if(k!==void 0)Y[$.name]=k}const _={};for(const $ of J.args){if($.positional)continue;const L=$.name.replace(/-([a-z])/g,(O,K)=>K.toUpperCase()),k=Q.getOptionValueSource(L)??Q.getOptionValueSource($.name);if(k==="cli")_[$.name]=k}if(Object.keys(_).length>0)Y.__opencliOptionSources=_;const h=H(J,Y);x(F(J));const B=V.verbose===!0;let D=typeof V.format==="string"?V.format:"table";const M=Q.getOptionValueSource("format")==="cli";if(B)process.env.OPENCLI_VERBOSE="1";const G=typeof Q.optsWithGlobals==="function"?Q.optsWithGlobals():{},q=await N(J,h,B,{prepared:!0,...typeof G.profile==="string"&&G.profile.trim()?{profile:G.profile.trim()}:{},...typeof V.trace==="string"&&V.trace!=="off"?{trace:V.trace}:{},...J.browser&&typeof V.window==="string"?{windowMode:V.window}:{},...J.browser&&typeof V.siteSession==="string"?{siteSession:V.siteSession}:{},...J.browser&&typeof V.keepTab==="string"?{keepTab:V.keepTab}:{}});if(q===null||q===void 0)return;const X=T().get(F(J))??J;if(!M&&D==="table"&&X.defaultFormat)D=X.defaultFormat;if(B&&(!q||Array.isArray(q)&&q.length===0))w.warn("Command returned an empty result.");A(q,{fmt:D,fmtExplicit:M,columns:X.columns,title:`${X.site}/${X.name}`,elapsed:(Date.now()-I)/1000,source:F(X),footerExtra:X.footerExtra?.(h)})}catch(Y){i(Y,F(J),V.verbose===!0,V.trace);process.exitCode=l(Y)}})}function l(Z){if(Z instanceof u)return Z.exitCode;return g.GENERIC_ERROR}function n(Z,J,Q){if(Q==="on"||Q==="retain-on-failure")return Z;const P=J.replace("/"," ");return Z+`# AutoFix: re-run with --trace=retain-on-failure for trace artifact
2
- # opencli ${P} --trace retain-on-failure
2
+ # ppcli ${P} --trace retain-on-failure
3
3
  `}function i(Z,J,Q,P){const W=p(Z);if(Q&&Z instanceof Error&&Z.stack)W.error.stack=Z.stack;let z=y.dump(W,{sortKeys:!1,lineWidth:120,noRefs:!0});const U=W.error.code;if(U==="SELECTOR"||U==="EMPTY_RESULT"||U==="ADAPTER_LOAD"||U==="UNKNOWN")z=n(z,J,P);process.stderr.write(z)}export function registerAllCommands(Z,J){const Q=new Set,P=new Map;for(const[,W]of T()){if(Q.has(W))continue;Q.add(W);const z=P.get(W.site)??[];z.push(W);P.set(W.site,z)}for(const[W,z]of P){let U=J.get(W);if(!U){U=Z.command(W);J.set(W,U)}for(const Y of z)registerCommandToProgram(U,Y);const V=new Map(z.map((Y)=>[Y.name,f(Y)]));U.configureHelp({subcommandTerm:(Y)=>V.get(Y.name())??Y.name()});const I=U.helpInformation.bind(U);U.helpInformation=(Y)=>{const _=j();if(_)return E(C(W,z),_);I(Y);return b(W,z)}}return[...P.keys()].sort((W,z)=>W.localeCompare(z))}
@@ -1,2 +1,2 @@
1
- import{mkdir as y,readFile as w,writeFile as g}from"node:fs/promises";import{homedir as h}from"node:os";import{dirname as l,join as u}from"node:path";import{pathToFileURL as n}from"node:url";import{InvalidArgumentError as I,Option as p}from"commander";import{AuthRequiredError as P,CliError as F,getErrorMessage as M}from"../errors.js";import{executeCommand as N}from"../execution.js";import{ensureBrowserBridgeReady as i}from"../browser/daemon-lifecycle.js";import{resolveProfile as a}from"../browser/profile.js";import{isElectronApp as U}from"../electron-apps.js";import{fullName as c,getRegistry as _}from"../registry.js";import{render as x}from"../output.js";const V=1,q=86400000;function j(G,J,X){if(G===void 0||G===null||G==="")return X;const Z=Number(G);if(!Number.isInteger(Z)||Z<=0)throw new I(`${J} must be a positive integer. Received: "${String(G)}"`);return Z}function C(G){if(!G||!G.trim())return null;const J=G.split(",").map((X)=>X.trim()).filter(Boolean);return J.length>0?new Set(J):null}function m(){return u(h(),".opencli","auth-refresh.json")}function d(){return{version:V,sites:{}}}async function o(G){try{const J=JSON.parse(await w(G,"utf8"));if(J&&J.version===V&&J.sites&&typeof J.sites==="object")return{version:V,sites:J.sites}}catch(J){if(J.code!=="ENOENT")throw J}return d()}async function s(G,J){await y(l(G),{recursive:!0});await g(G,`${JSON.stringify(J,null,2)}
2
- `,"utf8")}function T(G){if(!G?.last_touched_at)return null;const J=Date.parse(G.last_touched_at);return Number.isFinite(J)?J:null}function r(G,J){const X=T(G);return X!==null&&J.getTime()-X<q}function B(G){const J=T(G);return J===null?"":new Date(J+q).toISOString()}function b(){const G=new Set;return[..._().values()].filter((J)=>{if(G.has(J))return!1;G.add(J);return J.name==="whoami"&&J.access==="read"}).sort((J,X)=>J.site.localeCompare(X.site))}async function O(G){const J=G;if(!J._lazy||!J._modulePath)return G;await import(n(J._modulePath).href);return _().get(c(G))??G}function z(G,J){const X=G.args.some((Z)=>Z.name==="timeout");return{...G,args:X?G.args:[...G.args,{name:"timeout",type:"int",default:J,help:"Per-site auth command timeout in seconds"}]}}function t(G,J){if(G.browser!==!0||typeof G.authStatus?.quickCheck!=="function")return null;return z({...G,func:G.authStatus.quickCheck,navigateBefore:!1,siteSession:"ephemeral",defaultWindowMode:"background"},J)}function v(G){if(typeof G==="boolean")return G;if(G&&typeof G==="object"&&!Array.isArray(G)){const J=G.logged_in;if(typeof J==="boolean")return J}return null}function k(G){if(G===void 0||G===null)return"";if(typeof G==="string"||typeof G==="number"||typeof G==="boolean")return String(G);return""}function S(G){if(!G||typeof G!=="object"||Array.isArray(G))return"";const J=G,X=/(?:email|phone|real.?name|first.?name|last.?name|cookie|token|session|secret|password|csrf|jwt|bearer|wt2)/i;for(const Z of["username","handle","user_id","id","name","nickname","user_type","url"]){if(X.test(Z))continue;const Y=k(J[Z]);if(Y)return Y}for(const[Z,Y]of Object.entries(J)){if(Z==="site"||Z==="logged_in"||X.test(Z))continue;const $=k(Y);if($)return $}return""}function H(G){return G instanceof P||typeof G==="object"&&G!==null&&G.code==="AUTH_REQUIRED"}function A(G,J,X){if(H(X))return{site:G,status:"not_logged_in",logged_in:!1,identity:"",checked:J,error:""};const Z=X instanceof F?X.code:"",Y=M(X);return{site:G,status:"error",logged_in:"",identity:"",checked:J,error:Z?`${Z}: ${Y}`:Y}}function e(G,J){if(G.browser!==!0)return null;let X=G.authStatus?.refresh;if(typeof X!=="function"){const Z=G.authStatus?.quickCheck;if(typeof Z!=="function"||!G.domain)return null;const Y=G.domain.startsWith("http://")||G.domain.startsWith("https://")?G.domain:`https://${G.domain}`;X=async($,D,Q)=>{await $.goto(Y);await $.wait(1);if(v(await Z($,D,Q))!==!0)throw new P(G.domain??G.site,`Auth refresh quickCheck failed for ${G.site}`);return{status:"touched"}}}return z({...G,func:X,navigateBefore:!1,siteSession:"persistent",defaultWindowMode:"background"},J)}function GG(G){if(G&&typeof G==="object"&&!Array.isArray(G)){const J=G;if(J.status==="refreshed"||J.refreshed===!0)return"refreshed"}return"touched"}function JG(G,J,X){if(H(X))return{site:G,status:"not_logged_in",last_touched_at:J?.last_touched_at??"",next_refresh_at:B(J),error:""};const Z=X instanceof F?X.code:"",Y=M(X);return{site:G,status:"error",last_touched_at:J?.last_touched_at??"",next_refresh_at:B(J),error:Z?`${Z}: ${Y}`:Y}}function E(){return process.env.PUBLISHPORT_SITE_SESSION==="ephemeral"?{siteSession:"ephemeral",keepTab:"false"}:{siteSession:"persistent",keepTab:"true"}}async function XG(G,J){try{const X=await O(G);if(X.browser!==!0){const D=z(X,J.timeoutSeconds),Q=await N(D,{timeout:J.timeoutSeconds},!1,{...J.profile?{profile:J.profile}:{}});return{site:G.site,status:"logged_in",logged_in:!0,identity:S(Q),checked:"quick",error:""}}if(X.authStatus?.quickCheckRequiresNavigation===!0)return{site:G.site,status:"unknown",logged_in:"",identity:"",checked:"navigation_required",error:""};const Z=t(X,J.timeoutSeconds);if(!Z)return{site:G.site,status:"unknown",logged_in:"",identity:"",checked:"skipped",error:"quickCheck not implemented; use --full to run whoami"};const Y=await N(Z,{timeout:J.timeoutSeconds},!1,{...E(),windowMode:"background",...J.profile?{profile:J.profile}:{}}),$=v(Y);if($===!0)return{site:G.site,status:"logged_in",logged_in:!0,identity:"",checked:"quick",error:""};if($===!1)return{site:G.site,status:"not_logged_in",logged_in:!1,identity:"",checked:"quick",error:""};return{site:G.site,status:"unknown",logged_in:"",identity:"",checked:"quick",error:"quickCheck returned no boolean logged_in signal"}}catch(X){return A(G.site,"quick",X)}}async function YG(G,J){try{const X=await O(G),Z=z(X,J.timeoutSeconds),Y=await N(Z,{timeout:J.timeoutSeconds},!1,{...E(),windowMode:"background",...J.profile?{profile:J.profile}:{}});return{site:G.site,status:"logged_in",logged_in:!0,identity:S(Y),checked:"full",error:""}}catch(X){return A(G.site,"full",X)}}function ZG(G,J){return J?`${G}@${J}`:G}async function $G(G,J){const X=ZG(G.site,J.profile),Z=J.state.sites[X];if(!J.force&&r(Z,J.now))return{site:G.site,status:"skipped",last_touched_at:Z?.last_touched_at??"",next_refresh_at:B(Z),error:""};const Y=J.now.toISOString();try{const $=await O(G),D=e($,J.timeoutSeconds);if(!D){J.state.sites[X]={...Z,last_attempt_at:Y,last_status:"unsupported"};return{site:G.site,status:"unsupported",last_touched_at:Z?.last_touched_at??"",next_refresh_at:B(Z),error:"refresh probe is not available for this site"}}const Q=await N(D,{timeout:J.timeoutSeconds},!1,{siteSession:"persistent",keepTab:"true",windowMode:"background",...J.profile?{profile:J.profile}:{}}),K=GG(Q);J.state.sites[X]={...Z,last_attempt_at:Y,last_touched_at:Y,last_status:K};return{site:G.site,status:K,last_touched_at:Y,next_refresh_at:new Date(J.now.getTime()+q).toISOString(),error:""}}catch($){const D=H($)?"not_logged_in":"error";J.state.sites[X]={...Z,last_attempt_at:Y,last_status:D};return JG(G.site,Z,$)}}async function R(G,J){const X=a(J);if(X.kind!=="extension")return null;if(!G.some((Y)=>Y.browser===!0&&!U(Y.site)))return null;try{await i({timeoutSeconds:15,contextId:X.contextId,verbose:!1});return null}catch(Y){return M(Y)}}function DG(G,J,X){return{site:G,status:"error",logged_in:"",identity:"",checked:J,error:`BROWSER_CONNECT: ${X}`}}async function f(G,J,X){const Z=Array(G.length);let Y=0;const $=Array.from({length:Math.min(J,G.length)},async()=>{while(Y<G.length){const D=Y++;Z[D]=await X(G[D])}});await Promise.all($);return Z}export async function collectAuthStatus(G){const J=C(G.sites),X=G.full?"full":"quick",Z=j(G.concurrency,"--concurrency",X==="full"?3:8),Y=j(G.timeout,"--timeout",X==="full"?20:8),$=String(G.only??"all");if(!["all","logged-in","not-logged-in","unknown","error"].includes($))throw new I("--only must be one of: all, logged-in, not-logged-in, unknown, error");const D=b().filter((W)=>!J||J.has(W.site)),Q=await R(D,G.profile),K=await f(D,Z,(W)=>Q&&W.browser===!0&&!U(W.site)?Promise.resolve(DG(W.site,X,Q)):X==="full"?YG(W,{timeoutSeconds:Y,profile:G.profile}):XG(W,{timeoutSeconds:Y,profile:G.profile})),L=$.replace(/-/g,"_");return L==="all"?K:K.filter((W)=>W.status===L)}export async function collectAuthRefresh(G){const J=C(G.sites),X=j(G.concurrency,"--concurrency",3),Z=j(G.timeout,"--timeout",20),Y=G.statePath??m(),$=G.now??new Date,D=await o(Y),Q=b().filter((W)=>!J||J.has(W.site)),K=await R(Q,G.profile),L=await f(Q,X,(W)=>K&&W.browser===!0&&!U(W.site)?Promise.resolve({site:W.site,status:"error",last_touched_at:D.sites[W.site]?.last_touched_at??"",next_refresh_at:B(D.sites[W.site]),error:`BROWSER_CONNECT: ${K}`}):$G(W,{timeoutSeconds:Z,profile:G.profile,now:$,state:D,force:G.all===!0}));await s(Y,D);return L}export function registerAuthCommands(G){const J=G.command("auth").description("Inspect website login status"),X=J.command("status").description("Show login status for sites with auth adapters").option("--site <sites>","Comma-separated site names to check, e.g. github,chatgpt").option("--full","Run full per-site whoami probes instead of quick no-navigation checks",!1).option("--concurrency <n>","Maximum sites to check at once").option("--timeout <seconds>","Per-site timeout in seconds").addOption(new p("--only <status>","Filter rows by status").choices(["all","logged-in","not-logged-in","unknown","error"]).default("all")).option("-f, --format <fmt>","Output format: table, plain, json, yaml, md, csv","table").action(async(Y)=>{const $=typeof X.optsWithGlobals==="function"?X.optsWithGlobals():{},D=await collectAuthStatus({sites:Y.site,full:Y.full===!0,concurrency:Y.concurrency,timeout:Y.timeout,only:Y.only,profile:typeof $.profile==="string"&&$.profile.trim()?$.profile.trim():void 0}),Q=typeof Y.format==="string"?Y.format:"table";x(D,{fmt:Q,fmtExplicit:X.getOptionValueSource("format")==="cli",columns:["site","status","identity","checked","error"],title:"opencli/auth status",source:Y.full?"full whoami probe":"quick auth check"})}),Z=J.command("refresh").description("Touch logged-in site sessions to keep browser auth fresh").option("--site <sites>","Comma-separated site names to refresh, e.g. github,claude").option("--all","Ignore the 24h refresh throttle and force every selected site",!1).option("--concurrency <n>","Maximum sites to refresh at once").option("--timeout <seconds>","Per-site timeout in seconds").option("-f, --format <fmt>","Output format: table, plain, json, yaml, md, csv","table").action(async(Y)=>{const $=typeof Z.optsWithGlobals==="function"?Z.optsWithGlobals():{},D=await collectAuthRefresh({sites:Y.site,all:Y.all===!0,concurrency:Y.concurrency,timeout:Y.timeout,profile:typeof $.profile==="string"&&$.profile.trim()?$.profile.trim():void 0}),Q=typeof Y.format==="string"?Y.format:"table";x(D,{fmt:Q,fmtExplicit:Z.getOptionValueSource("format")==="cli",columns:["site","status","last_touched_at","next_refresh_at","error"],title:"opencli/auth refresh",source:Y.all?"forced persistent touch":"persistent touch with 24h throttle"})});return J}
1
+ import{mkdir as y,readFile as w,writeFile as g}from"node:fs/promises";import{homedir as h}from"node:os";import{dirname as l,join as u}from"node:path";import{pathToFileURL as n}from"node:url";import{InvalidArgumentError as I,Option as p}from"commander";import{AuthRequiredError as P,CliError as F,getErrorMessage as M}from"../errors.js";import{executeCommand as N}from"../execution.js";import{ensureBrowserBridgeReady as i}from"../browser/daemon-lifecycle.js";import{resolveProfile as a}from"../browser/profile.js";import{isElectronApp as U}from"../electron-apps.js";import{fullName as c,getRegistry as _}from"../registry.js";import{render as x}from"../output.js";const V=1,q=86400000;function j(G,J,X){if(G===void 0||G===null||G==="")return X;const Z=Number(G);if(!Number.isInteger(Z)||Z<=0)throw new I(`${J} must be a positive integer. Received: "${String(G)}"`);return Z}function C(G){if(!G||!G.trim())return null;const J=G.split(",").map((X)=>X.trim()).filter(Boolean);return J.length>0?new Set(J):null}function m(){return u(h(),".ppcli","auth-refresh.json")}function d(){return{version:V,sites:{}}}async function o(G){try{const J=JSON.parse(await w(G,"utf8"));if(J&&J.version===V&&J.sites&&typeof J.sites==="object")return{version:V,sites:J.sites}}catch(J){if(J.code!=="ENOENT")throw J}return d()}async function s(G,J){await y(l(G),{recursive:!0});await g(G,`${JSON.stringify(J,null,2)}
2
+ `,"utf8")}function T(G){if(!G?.last_touched_at)return null;const J=Date.parse(G.last_touched_at);return Number.isFinite(J)?J:null}function r(G,J){const X=T(G);return X!==null&&J.getTime()-X<q}function B(G){const J=T(G);return J===null?"":new Date(J+q).toISOString()}function b(){const G=new Set;return[..._().values()].filter((J)=>{if(G.has(J))return!1;G.add(J);return J.name==="whoami"&&J.access==="read"}).sort((J,X)=>J.site.localeCompare(X.site))}async function O(G){const J=G;if(!J._lazy||!J._modulePath)return G;await import(n(J._modulePath).href);return _().get(c(G))??G}function z(G,J){const X=G.args.some((Z)=>Z.name==="timeout");return{...G,args:X?G.args:[...G.args,{name:"timeout",type:"int",default:J,help:"Per-site auth command timeout in seconds"}]}}function t(G,J){if(G.browser!==!0||typeof G.authStatus?.quickCheck!=="function")return null;return z({...G,func:G.authStatus.quickCheck,navigateBefore:!1,siteSession:"ephemeral",defaultWindowMode:"background"},J)}function v(G){if(typeof G==="boolean")return G;if(G&&typeof G==="object"&&!Array.isArray(G)){const J=G.logged_in;if(typeof J==="boolean")return J}return null}function k(G){if(G===void 0||G===null)return"";if(typeof G==="string"||typeof G==="number"||typeof G==="boolean")return String(G);return""}function S(G){if(!G||typeof G!=="object"||Array.isArray(G))return"";const J=G,X=/(?:email|phone|real.?name|first.?name|last.?name|cookie|token|session|secret|password|csrf|jwt|bearer|wt2)/i;for(const Z of["username","handle","user_id","id","name","nickname","user_type","url"]){if(X.test(Z))continue;const Y=k(J[Z]);if(Y)return Y}for(const[Z,Y]of Object.entries(J)){if(Z==="site"||Z==="logged_in"||X.test(Z))continue;const $=k(Y);if($)return $}return""}function H(G){return G instanceof P||typeof G==="object"&&G!==null&&G.code==="AUTH_REQUIRED"}function A(G,J,X){if(H(X))return{site:G,status:"not_logged_in",logged_in:!1,identity:"",checked:J,error:""};const Z=X instanceof F?X.code:"",Y=M(X);return{site:G,status:"error",logged_in:"",identity:"",checked:J,error:Z?`${Z}: ${Y}`:Y}}function e(G,J){if(G.browser!==!0)return null;let X=G.authStatus?.refresh;if(typeof X!=="function"){const Z=G.authStatus?.quickCheck;if(typeof Z!=="function"||!G.domain)return null;const Y=G.domain.startsWith("http://")||G.domain.startsWith("https://")?G.domain:`https://${G.domain}`;X=async($,D,Q)=>{await $.goto(Y);await $.wait(1);if(v(await Z($,D,Q))!==!0)throw new P(G.domain??G.site,`Auth refresh quickCheck failed for ${G.site}`);return{status:"touched"}}}return z({...G,func:X,navigateBefore:!1,siteSession:"persistent",defaultWindowMode:"background"},J)}function GG(G){if(G&&typeof G==="object"&&!Array.isArray(G)){const J=G;if(J.status==="refreshed"||J.refreshed===!0)return"refreshed"}return"touched"}function JG(G,J,X){if(H(X))return{site:G,status:"not_logged_in",last_touched_at:J?.last_touched_at??"",next_refresh_at:B(J),error:""};const Z=X instanceof F?X.code:"",Y=M(X);return{site:G,status:"error",last_touched_at:J?.last_touched_at??"",next_refresh_at:B(J),error:Z?`${Z}: ${Y}`:Y}}function E(){return process.env.PUBLISHPORT_SITE_SESSION==="ephemeral"?{siteSession:"ephemeral",keepTab:"false"}:{siteSession:"persistent",keepTab:"true"}}async function XG(G,J){try{const X=await O(G);if(X.browser!==!0){const D=z(X,J.timeoutSeconds),Q=await N(D,{timeout:J.timeoutSeconds},!1,{...J.profile?{profile:J.profile}:{}});return{site:G.site,status:"logged_in",logged_in:!0,identity:S(Q),checked:"quick",error:""}}if(X.authStatus?.quickCheckRequiresNavigation===!0)return{site:G.site,status:"unknown",logged_in:"",identity:"",checked:"navigation_required",error:""};const Z=t(X,J.timeoutSeconds);if(!Z)return{site:G.site,status:"unknown",logged_in:"",identity:"",checked:"skipped",error:"quickCheck not implemented; use --full to run whoami"};const Y=await N(Z,{timeout:J.timeoutSeconds},!1,{...E(),windowMode:"background",...J.profile?{profile:J.profile}:{}}),$=v(Y);if($===!0)return{site:G.site,status:"logged_in",logged_in:!0,identity:"",checked:"quick",error:""};if($===!1)return{site:G.site,status:"not_logged_in",logged_in:!1,identity:"",checked:"quick",error:""};return{site:G.site,status:"unknown",logged_in:"",identity:"",checked:"quick",error:"quickCheck returned no boolean logged_in signal"}}catch(X){return A(G.site,"quick",X)}}async function YG(G,J){try{const X=await O(G),Z=z(X,J.timeoutSeconds),Y=await N(Z,{timeout:J.timeoutSeconds},!1,{...E(),windowMode:"background",...J.profile?{profile:J.profile}:{}});return{site:G.site,status:"logged_in",logged_in:!0,identity:S(Y),checked:"full",error:""}}catch(X){return A(G.site,"full",X)}}function ZG(G,J){return J?`${G}@${J}`:G}async function $G(G,J){const X=ZG(G.site,J.profile),Z=J.state.sites[X];if(!J.force&&r(Z,J.now))return{site:G.site,status:"skipped",last_touched_at:Z?.last_touched_at??"",next_refresh_at:B(Z),error:""};const Y=J.now.toISOString();try{const $=await O(G),D=e($,J.timeoutSeconds);if(!D){J.state.sites[X]={...Z,last_attempt_at:Y,last_status:"unsupported"};return{site:G.site,status:"unsupported",last_touched_at:Z?.last_touched_at??"",next_refresh_at:B(Z),error:"refresh probe is not available for this site"}}const Q=await N(D,{timeout:J.timeoutSeconds},!1,{siteSession:"persistent",keepTab:"true",windowMode:"background",...J.profile?{profile:J.profile}:{}}),K=GG(Q);J.state.sites[X]={...Z,last_attempt_at:Y,last_touched_at:Y,last_status:K};return{site:G.site,status:K,last_touched_at:Y,next_refresh_at:new Date(J.now.getTime()+q).toISOString(),error:""}}catch($){const D=H($)?"not_logged_in":"error";J.state.sites[X]={...Z,last_attempt_at:Y,last_status:D};return JG(G.site,Z,$)}}async function R(G,J){const X=a(J);if(X.kind!=="extension")return null;if(!G.some((Y)=>Y.browser===!0&&!U(Y.site)))return null;try{await i({timeoutSeconds:15,contextId:X.contextId,verbose:!1});return null}catch(Y){return M(Y)}}function DG(G,J,X){return{site:G,status:"error",logged_in:"",identity:"",checked:J,error:`BROWSER_CONNECT: ${X}`}}async function f(G,J,X){const Z=Array(G.length);let Y=0;const $=Array.from({length:Math.min(J,G.length)},async()=>{while(Y<G.length){const D=Y++;Z[D]=await X(G[D])}});await Promise.all($);return Z}export async function collectAuthStatus(G){const J=C(G.sites),X=G.full?"full":"quick",Z=j(G.concurrency,"--concurrency",X==="full"?3:8),Y=j(G.timeout,"--timeout",X==="full"?20:8),$=String(G.only??"all");if(!["all","logged-in","not-logged-in","unknown","error"].includes($))throw new I("--only must be one of: all, logged-in, not-logged-in, unknown, error");const D=b().filter((W)=>!J||J.has(W.site)),Q=await R(D,G.profile),K=await f(D,Z,(W)=>Q&&W.browser===!0&&!U(W.site)?Promise.resolve(DG(W.site,X,Q)):X==="full"?YG(W,{timeoutSeconds:Y,profile:G.profile}):XG(W,{timeoutSeconds:Y,profile:G.profile})),L=$.replace(/-/g,"_");return L==="all"?K:K.filter((W)=>W.status===L)}export async function collectAuthRefresh(G){const J=C(G.sites),X=j(G.concurrency,"--concurrency",3),Z=j(G.timeout,"--timeout",20),Y=G.statePath??m(),$=G.now??new Date,D=await o(Y),Q=b().filter((W)=>!J||J.has(W.site)),K=await R(Q,G.profile),L=await f(Q,X,(W)=>K&&W.browser===!0&&!U(W.site)?Promise.resolve({site:W.site,status:"error",last_touched_at:D.sites[W.site]?.last_touched_at??"",next_refresh_at:B(D.sites[W.site]),error:`BROWSER_CONNECT: ${K}`}):$G(W,{timeoutSeconds:Z,profile:G.profile,now:$,state:D,force:G.all===!0}));await s(Y,D);return L}export function registerAuthCommands(G){const J=G.command("auth").description("Inspect website login status"),X=J.command("status").description("Show login status for sites with auth adapters").option("--site <sites>","Comma-separated site names to check, e.g. github,chatgpt").option("--full","Run full per-site whoami probes instead of quick no-navigation checks",!1).option("--concurrency <n>","Maximum sites to check at once").option("--timeout <seconds>","Per-site timeout in seconds").addOption(new p("--only <status>","Filter rows by status").choices(["all","logged-in","not-logged-in","unknown","error"]).default("all")).option("-f, --format <fmt>","Output format: table, plain, json, yaml, md, csv","table").action(async(Y)=>{const $=typeof X.optsWithGlobals==="function"?X.optsWithGlobals():{},D=await collectAuthStatus({sites:Y.site,full:Y.full===!0,concurrency:Y.concurrency,timeout:Y.timeout,only:Y.only,profile:typeof $.profile==="string"&&$.profile.trim()?$.profile.trim():void 0}),Q=typeof Y.format==="string"?Y.format:"table";x(D,{fmt:Q,fmtExplicit:X.getOptionValueSource("format")==="cli",columns:["site","status","identity","checked","error"],title:"ppcli/auth status",source:Y.full?"full whoami probe":"quick auth check"})}),Z=J.command("refresh").description("Touch logged-in site sessions to keep browser auth fresh").option("--site <sites>","Comma-separated site names to refresh, e.g. github,claude").option("--all","Ignore the 24h refresh throttle and force every selected site",!1).option("--concurrency <n>","Maximum sites to refresh at once").option("--timeout <seconds>","Per-site timeout in seconds").option("-f, --format <fmt>","Output format: table, plain, json, yaml, md, csv","table").action(async(Y)=>{const $=typeof Z.optsWithGlobals==="function"?Z.optsWithGlobals():{},D=await collectAuthRefresh({sites:Y.site,all:Y.all===!0,concurrency:Y.concurrency,timeout:Y.timeout,profile:typeof $.profile==="string"&&$.profile.trim()?$.profile.trim():void 0}),Q=typeof Y.format==="string"?Y.format:"table";x(D,{fmt:Q,fmtExplicit:Z.getOptionValueSource("format")==="cli",columns:["site","status","last_touched_at","next_refresh_at","error"],title:"ppcli/auth refresh",source:Y.all?"forced persistent touch":"persistent touch with 24h throttle"})});return J}
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * CLI commands for daemon lifecycle:
3
- * opencli daemon status — show daemon state
4
- * opencli daemon stop — graceful shutdown
5
- * opencli daemon restart — graceful shutdown, then start a fresh daemon
6
- * opencli daemon warm — [pp-only] 预热后台自动化窗口(挂一个持久空白标签)
3
+ * ppcli daemon status — show daemon state
4
+ * ppcli daemon stop — graceful shutdown
5
+ * ppcli daemon restart — graceful shutdown, then start a fresh daemon
6
+ * ppcli daemon warm — [pp-only] 预热后台自动化窗口(挂一个持久空白标签)
7
7
  */
8
8
  export declare function daemonStatus(): Promise<void>;
9
9
  export declare function daemonStop(): Promise<void>;
@@ -1 +1 @@
1
- import{fetchDaemonStatus as M,requestDaemonShutdown as X}from"../browser/daemon-client.js";import{restartDaemon as Y}from"../browser/daemon-lifecycle.js";import{formatDuration as Z}from"../download/progress.js";import{log as C}from"../logger.js";import{PKG_VERSION as U}from"../version.js";import{formatDaemonVersion as W,isDaemonStale as $}from"../browser/daemon-version.js";import{browserSession as q,getBrowserFactory as T}from"../runtime.js";export async function daemonStatus(){const k=await M();if(!k){console.log("Daemon: not running");return}let z;if(k.extensionConnected)z=k.extensionVersion?`connected (v${k.extensionVersion})`:"connected (version unknown)";else if(k.profileRequired){const A=k.profiles?.length??0;z=`${A} ${A===1?"profile":"profiles"} connected, none selected — run \`opencli profile use <name>\``}else if(k.profileDisconnected)z="requested profile not connected — run `opencli profile use <name>`";else z="disconnected";const H=W(k),J=$(k,U);console.log(`Daemon: ${J?"stale":"running"} (PID ${k.pid})`);console.log(`Version: ${H}${J?` (CLI v${U}; run: opencli daemon restart)`:""}`);console.log(`Uptime: ${Z(Math.round(k.uptime*1000))}`);console.log(`Extension: ${z}`);if(k.profiles&&k.profiles.length>0)console.log(`Profiles: ${k.profiles.map((A)=>{const Q=A.extensionVersion?` v${A.extensionVersion}`:"";return`${A.contextId}${Q}`}).join(", ")}`);console.log(`Memory: ${k.memoryMB} MB`);console.log(`Port: ${k.port}`)}export async function daemonStop(){if(!await M()){C.info("Daemon is not running.");return}if(await X())C.success("Daemon stopped.");else{C.error("Failed to stop daemon.");process.exitCode=1}}export async function daemonRestart(){const k=await M();if(k?.profiles&&k.profiles.length>0)C.warn(`Restarting daemon will disconnect ${k.profiles.length} browser profile(s); the extension should reconnect automatically.`);const z=await Y();if(!z.stopped){C.error("Failed to stop daemon before restart.");process.exitCode=1;return}if(!z.status){C.error("Daemon restart timed out before the new daemon reported status.");process.exitCode=1;return}const H=z.previousStatus?"restarted":"started",J=W(z.status);C.success(`Daemon ${H} on port ${z.status.port} (${J}).`);if(z.status.extensionConnected){const A=z.status.profiles?.length??0,Q=A>0?`; profiles connected: ${A}`:"";C.status(`Extension connected${Q}.`)}else C.warn("Daemon is running, but the Browser Bridge extension has not connected yet.")}export async function daemonWarm(k){const z=await M();if(!z){C.warn("Daemon/extension not ready; skip warm (retry after the browser extension connects).");return}if(!z.extensionConnected){C.warn("Browser extension not connected yet; skip warm.");return}const H=k&&/^https?:\/\//i.test(k)?k:"about:blank",J=T("__warm__");try{await q(J,async(A)=>{await A.goto(H).catch(()=>{})},{session:"site:__warm__",windowMode:"background",surface:"adapter",siteSession:"persistent"});C.success(`Automation window warmed (${H}, kept alive for reuse).`)}catch(A){C.warn(`Warm failed (non-fatal): ${A instanceof Error?A.message:String(A)}`)}}
1
+ import{fetchDaemonStatus as M,requestDaemonShutdown as X}from"../browser/daemon-client.js";import{restartDaemon as Y}from"../browser/daemon-lifecycle.js";import{formatDuration as Z}from"../download/progress.js";import{log as C}from"../logger.js";import{PKG_VERSION as U}from"../version.js";import{formatDaemonVersion as W,isDaemonStale as $}from"../browser/daemon-version.js";import{browserSession as q,getBrowserFactory as T}from"../runtime.js";export async function daemonStatus(){const k=await M();if(!k){console.log("Daemon: not running");return}let z;if(k.extensionConnected)z=k.extensionVersion?`connected (v${k.extensionVersion})`:"connected (version unknown)";else if(k.profileRequired){const A=k.profiles?.length??0;z=`${A} ${A===1?"profile":"profiles"} connected, none selected — run \`ppcli profile use <name>\``}else if(k.profileDisconnected)z="requested profile not connected — run `ppcli profile use <name>`";else z="disconnected";const H=W(k),J=$(k,U);console.log(`Daemon: ${J?"stale":"running"} (PID ${k.pid})`);console.log(`Version: ${H}${J?` (CLI v${U}; run: ppcli daemon restart)`:""}`);console.log(`Uptime: ${Z(Math.round(k.uptime*1000))}`);console.log(`Extension: ${z}`);if(k.profiles&&k.profiles.length>0)console.log(`Profiles: ${k.profiles.map((A)=>{const Q=A.extensionVersion?` v${A.extensionVersion}`:"";return`${A.contextId}${Q}`}).join(", ")}`);console.log(`Memory: ${k.memoryMB} MB`);console.log(`Port: ${k.port}`)}export async function daemonStop(){if(!await M()){C.info("Daemon is not running.");return}if(await X())C.success("Daemon stopped.");else{C.error("Failed to stop daemon.");process.exitCode=1}}export async function daemonRestart(){const k=await M();if(k?.profiles&&k.profiles.length>0)C.warn(`Restarting daemon will disconnect ${k.profiles.length} browser profile(s); the extension should reconnect automatically.`);const z=await Y();if(!z.stopped){C.error("Failed to stop daemon before restart.");process.exitCode=1;return}if(!z.status){C.error("Daemon restart timed out before the new daemon reported status.");process.exitCode=1;return}const H=z.previousStatus?"restarted":"started",J=W(z.status);C.success(`Daemon ${H} on port ${z.status.port} (${J}).`);if(z.status.extensionConnected){const A=z.status.profiles?.length??0,Q=A>0?`; profiles connected: ${A}`:"";C.status(`Extension connected${Q}.`)}else C.warn("Daemon is running, but the Browser Bridge extension has not connected yet.")}export async function daemonWarm(k){const z=await M();if(!z){C.warn("Daemon/extension not ready; skip warm (retry after the browser extension connects).");return}if(!z.extensionConnected){C.warn("Browser extension not connected yet; skip warm.");return}const H=k&&/^https?:\/\//i.test(k)?k:"about:blank",J=T("__warm__");try{await q(J,async(A)=>{await A.goto(H).catch(()=>{})},{session:"site:__warm__",windowMode:"background",surface:"adapter",siteSession:"persistent"});C.success(`Automation window warmed (${H}, kept alive for reuse).`)}catch(A){C.warn(`Warm failed (non-fatal): ${A instanceof Error?A.message:String(A)}`)}}
@@ -1,30 +1,30 @@
1
- export const BUILTIN_COMMANDS=["list","validate","verify","auth","browser","tab","doctor","plugin","external","completion"];export function bashCompletionScript(){return`# Bash completion for opencli
2
- # Add to ~/.bashrc: eval "$(opencli completion bash)"
1
+ export const BUILTIN_COMMANDS=["list","validate","verify","auth","browser","tab","doctor","plugin","external","completion"];export function bashCompletionScript(){return`# Bash completion for ppcli
2
+ # Add to ~/.bashrc: eval "$(ppcli completion bash)"
3
3
  _opencli_completions() {
4
4
  local cur words cword
5
5
  _get_comp_words_by_ref -n : cur words cword
6
6
 
7
7
  local completions
8
- completions=$(opencli --get-completions --cursor "$cword" "\${words[@]:1}" 2>/dev/null)
8
+ completions=$(ppcli --get-completions --cursor "$cword" "\${words[@]:1}" 2>/dev/null)
9
9
 
10
10
  COMPREPLY=( $(compgen -W "$completions" -- "$cur") )
11
11
  __ltrim_colon_completions "$cur"
12
12
  }
13
- complete -F _opencli_completions opencli
14
- `}export function zshCompletionScript(){return`# Zsh completion for opencli
15
- # Add to ~/.zshrc: eval "$(opencli completion zsh)"
13
+ complete -F _opencli_completions ppcli
14
+ `}export function zshCompletionScript(){return`# Zsh completion for ppcli
15
+ # Add to ~/.zshrc: eval "$(ppcli completion zsh)"
16
16
  _opencli() {
17
17
  local -a completions
18
18
  local cword=$((CURRENT - 1))
19
- completions=(\${(f)"$(opencli --get-completions --cursor "$cword" "\${words[@]:1}" 2>/dev/null)"})
19
+ completions=(\${(f)"$(ppcli --get-completions --cursor "$cword" "\${words[@]:1}" 2>/dev/null)"})
20
20
  compadd -a completions
21
21
  }
22
- compdef _opencli opencli
23
- `}export function fishCompletionScript(){return`# Fish completion for opencli
24
- # Add to ~/.config/fish/config.fish: opencli completion fish | source
25
- complete -c opencli -f -a '(
22
+ compdef _opencli ppcli
23
+ `}export function fishCompletionScript(){return`# Fish completion for ppcli
24
+ # Add to ~/.config/fish/config.fish: ppcli completion fish | source
25
+ complete -c ppcli -f -a '(
26
26
  set -l tokens (commandline -cop)
27
27
  set -l cursor (count (commandline -cop))
28
- opencli --get-completions --cursor $cursor $tokens[2..] 2>/dev/null
28
+ ppcli --get-completions --cursor $cursor $tokens[2..] 2>/dev/null
29
29
  )'
30
30
  `}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Shell tab-completion support for opencli.
2
+ * Shell tab-completion support for ppcli.
3
3
  *
4
4
  * Provides:
5
5
  * - Shell script generators for bash, zsh, and fish
@@ -11,7 +11,7 @@ export { bashCompletionScript, zshCompletionScript, fishCompletionScript };
11
11
  * Return completion candidates given the current command-line words and cursor index.
12
12
  * Requires full CLI discovery to have been run (uses getRegistry()).
13
13
  *
14
- * @param words - The argv after 'opencli' (words[0] is the first arg, e.g. site name)
14
+ * @param words - The argv after 'ppcli' (words[0] is the first arg, e.g. site name)
15
15
  * @param cursor - 1-based position of the word being completed (1 = first arg)
16
16
  */
17
17
  export declare function getCompletions(words: string[], cursor: number): string[];
@@ -6,7 +6,7 @@ export declare const DEFAULT_DAEMON_PORT = 19825;
6
6
  /**
7
7
  * PublishPort 自有插件的专用端口。daemon 同时监听两个端口:19826 供 PublishPort 官方
8
8
  * 插件优先连接,19825 作为回退端口(同一 /ext 升级)。daemon 只接受官方插件(hello 带
9
- * brand:'publishport'),非官方 / 旧 opencli 插件即使连上任一端口也会在握手时被拒。
9
+ * brand:'publishport'),非官方 / 旧 ppcli 插件即使连上任一端口也会在握手时被拒。
10
10
  */
11
11
  export declare const PUBLISHPORT_DAEMON_PORT = 19826;
12
12
  export declare function unsupportedDaemonPortEnvMessage(value?: string): string;
@@ -1 +1 @@
1
- export const DEFAULT_DAEMON_PORT=19825,PUBLISHPORT_DAEMON_PORT=19826;export function unsupportedDaemonPortEnvMessage(b){return`OPENCLI_DAEMON_PORT is no longer supported${b?` (received ${b})`:""}. The OpenCLI Chrome extension can only connect to localhost:${DEFAULT_DAEMON_PORT}. Unset OPENCLI_DAEMON_PORT and rerun opencli.`}export const VOLATILE_PARAMS=new Set(["w_rid","wts","_","callback","timestamp","t","nonce","sign"]),SEARCH_PARAMS=new Set(["q","query","keyword","search","wd","kw","search_query","w"]),PAGINATION_PARAMS=new Set(["page","pn","offset","cursor","next","page_num"]),LIMIT_PARAMS=new Set(["limit","count","size","per_page","page_size","ps","num"]),FIELD_ROLES={title:["title","name","text","content","desc","description","headline","subject"],url:["url","uri","link","href","permalink","jump_url","web_url","share_url"],author:["author","username","user_name","nickname","nick","owner","creator","up_name","uname"],score:["score","hot","heat","likes","like_count","view_count","views","play","favorite_count","reply_count"],time:["time","created_at","publish_time","pub_time","date","ctime","mtime","pubdate","created"],id:["id","aid","bvid","mid","uid","oid","note_id","item_id"],cover:["cover","pic","image","thumbnail","poster","avatar"],category:["category","tag","type","tname","channel","section"]};
1
+ export const DEFAULT_DAEMON_PORT=19825,PUBLISHPORT_DAEMON_PORT=19826;export function unsupportedDaemonPortEnvMessage(b){return`OPENCLI_DAEMON_PORT is no longer supported${b?` (received ${b})`:""}. The ppcli Chrome extension can only connect to localhost:${DEFAULT_DAEMON_PORT}. Unset OPENCLI_DAEMON_PORT and rerun ppcli.`}export const VOLATILE_PARAMS=new Set(["w_rid","wts","_","callback","timestamp","t","nonce","sign"]),SEARCH_PARAMS=new Set(["q","query","keyword","search","wd","kw","search_query","w"]),PAGINATION_PARAMS=new Set(["page","pn","offset","cursor","next","page_num"]),LIMIT_PARAMS=new Set(["limit","count","size","per_page","page_size","ps","num"]),FIELD_ROLES={title:["title","name","text","content","desc","description","headline","subject"],url:["url","uri","link","href","permalink","jump_url","web_url","share_url"],author:["author","username","user_name","nickname","nick","owner","creator","up_name","uname"],score:["score","hot","heat","likes","like_count","view_count","views","play","favorite_count","reply_count"],time:["time","created_at","publish_time","pub_time","date","ctime","mtime","pubdate","created"],id:["id","aid","bvid","mid","uid","oid","note_id","item_id"],cover:["cover","pic","image","thumbnail","poster","avatar"],category:["category","tag","type","tname","channel","section"]};
@@ -1,6 +1,6 @@
1
1
  export declare const COMMAND_RESULT_UNKNOWN_CODE = "command_result_unknown";
2
2
  export declare const COMMAND_RESULT_UNKNOWN_HINT = "Inspect the browser/session state before retrying. Do not blindly retry write commands such as navigate, click, type, or eval.";
3
- export declare const PROFILE_DISCONNECTED_HINT = "Open that Chrome profile and make sure the OpenCLI extension is enabled, or choose another profile with opencli profile use <name>.";
3
+ export declare const PROFILE_DISCONNECTED_HINT = "Open that Chrome profile and make sure the ppcli extension is enabled, or choose another profile with ppcli profile use <name>.";
4
4
  export type DaemonFailureContract = {
5
5
  message: string;
6
6
  errorCode: string;
@@ -1 +1 @@
1
- export const COMMAND_RESULT_UNKNOWN_CODE="command_result_unknown",COMMAND_RESULT_UNKNOWN_HINT="Inspect the browser/session state before retrying. Do not blindly retry write commands such as navigate, click, type, or eval.",PROFILE_DISCONNECTED_HINT="Open that Chrome profile and make sure the OpenCLI extension is enabled, or choose another profile with opencli profile use <name>.";export function commandResultUnknownMessage(e){return`Browser connection dropped after the ${e} command was dispatched; it may have completed.`}export function buildExtensionDisconnectFailure(e){if(e.dispatched)return{message:commandResultUnknownMessage(e.action),errorCode:COMMAND_RESULT_UNKNOWN_CODE,errorHint:COMMAND_RESULT_UNKNOWN_HINT,status:503,countAsCommandResultUnknown:!0};return buildCommandDispatchFailure(e.contextId)}export function buildCommandDispatchFailure(e){return{message:`Browser profile "${e}" disconnected before command dispatch`,errorCode:"profile_disconnected",errorHint:PROFILE_DISCONNECTED_HINT,status:503,countAsCommandResultUnknown:!1}}export function getResponseCorsHeaders(e,n){if(e!=="/ping")return;if(!n||!n.startsWith("chrome-extension://"))return;return{"Access-Control-Allow-Origin":n,Vary:"Origin"}}
1
+ export const COMMAND_RESULT_UNKNOWN_CODE="command_result_unknown",COMMAND_RESULT_UNKNOWN_HINT="Inspect the browser/session state before retrying. Do not blindly retry write commands such as navigate, click, type, or eval.",PROFILE_DISCONNECTED_HINT="Open that Chrome profile and make sure the ppcli extension is enabled, or choose another profile with ppcli profile use <name>.";export function commandResultUnknownMessage(e){return`Browser connection dropped after the ${e} command was dispatched; it may have completed.`}export function buildExtensionDisconnectFailure(e){if(e.dispatched)return{message:commandResultUnknownMessage(e.action),errorCode:COMMAND_RESULT_UNKNOWN_CODE,errorHint:COMMAND_RESULT_UNKNOWN_HINT,status:503,countAsCommandResultUnknown:!0};return buildCommandDispatchFailure(e.contextId)}export function buildCommandDispatchFailure(e){return{message:`Browser profile "${e}" disconnected before command dispatch`,errorCode:"profile_disconnected",errorHint:PROFILE_DISCONNECTED_HINT,status:503,countAsCommandResultUnknown:!1}}export function getResponseCorsHeaders(e,n){if(e!=="/ping")return;if(!n||!n.startsWith("chrome-extension://"))return;return{"Access-Control-Allow-Origin":n,Vary:"Origin"}}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * opencli micro-daemon — HTTP + WebSocket bridge between CLI and Chrome Extension.
2
+ * ppcli micro-daemon — HTTP + WebSocket bridge between CLI and Chrome Extension.
3
3
  *
4
4
  * Architecture:
5
5
  * CLI → HTTP POST /command → daemon → WebSocket → Extension
@@ -15,7 +15,7 @@
15
15
  * 5. WebSocket verifyClient — reject upgrade before connection is established
16
16
  *
17
17
  * Lifecycle:
18
- * - Auto-spawned by opencli on first browser command
18
+ * - Auto-spawned by ppcli on first browser command
19
19
  * - Persistent — stays alive until explicit shutdown, SIGTERM, or uninstall
20
20
  * - Listens on localhost:19825
21
21
  */
@@ -1 +1 @@
1
- import{createServer as b}from"node:http";import{WebSocketServer as u,WebSocket as v}from"ws";import{DEFAULT_DAEMON_PORT as p,PUBLISHPORT_DAEMON_PORT as g,unsupportedDaemonPortEnvMessage as c}from"./constants.js";import{EXIT_CODES as S}from"./errors.js";import{log as G}from"./logger.js";import{PKG_VERSION as l}from"./version.js";import{DEFAULT_CONTEXT_ID as d}from"./browser/profile.js";import{buildCommandDispatchFailure as a,buildExtensionDisconnectFailure as i,getResponseCorsHeaders as t}from"./daemon-utils.js";import*as L from"./browser/trace.js";const B=p,h=g;if(process.env.OPENCLI_DAEMON_PORT){G.error(c(process.env.OPENCLI_DAEMON_PORT));process.exit(S.USAGE_ERROR)}const W=new Map,N=new Map;let I=0;const n=200,k=[];class E extends Error{errorCode;errorHint;status;constructor(J,K,Z,z=400){super(J);this.errorCode=K;this.errorHint=Z;this.status=z;this.name="DaemonCommandFailure"}}function s(J){k.push(J);if(k.length>n)k.shift()}function j(){return[...W.values()].filter((J)=>J.ws.readyState===v.OPEN)}function q(J){const K=typeof J==="string"&&J.trim()?J.trim():void 0;if(K){const z=W.get(K);if(z?.ws.readyState===v.OPEN)return{connection:z};const Q=j();if(Q.length===1){G.warn(`[daemon] Requested profile "${K}" not connected; falling back to the only connected profile "${Q[0].contextId}"`);return{connection:Q[0]}}return{errorCode:"profile_disconnected",error:`Browser profile "${K}" is not connected.`,errorHint:"Open that Chrome profile and make sure the OpenCLI extension is enabled, or choose another profile with opencli profile use <name>."}}const Z=j();if(Z.length===1)return{connection:Z[0]};if(Z.length>1)return{errorCode:"profile_required",error:"Multiple Browser Bridge profiles are connected; choose one with --profile.",errorHint:"Run opencli profile list, then use opencli --profile <name> ... or opencli profile use <name>."};return{errorCode:"extension_not_connected",error:"Extension not connected. Please install the opencli Browser Bridge extension."}}function o(J,K){const Z=typeof K==="string"&&K.trim()?K.trim():d,z=W.get(Z);if(z&&z.ws!==J)z.ws.close();const Q=[...W.entries()].find(([,A])=>A.ws===J);if(Q&&Q[0]!==Z)W.delete(Q[0]);const $=W.get(Z),V={contextId:Z,ws:J,extensionVersion:$?.ws===J?$.extensionVersion:null,extensionCompatRange:$?.ws===J?$.extensionCompatRange:null,lastSeenAt:Date.now()};W.set(Z,V);return V}function x(J){for(const[K,Z]of W.entries()){if(Z.ws!==J)continue;W.delete(K);L.traceExtDisconnect(K);for(const[z,Q]of N){if(Q.contextId!==K)continue;clearTimeout(Q.timer);const $=i({contextId:K,action:Q.action,dispatched:Q.dispatched});if($.countAsCommandResultUnknown){I++;G.warn(`[daemon] Command result unknown after extension disconnect (id=${z}, action=${Q.action}, context=${K})`)}L.traceResult(z,!1,Date.now()-Q.dispatchTs,$.errorCode);Q.reject(new E($.message,$.errorCode,$.errorHint,$.status));N.delete(z)}}}const w=33554432;class P extends Error{status=413;constructor(){super(`请求体超过 ${w/1024/1024} MB 上限(正文内联图片过多/过大?可减少单次发布的图片体积)`);this.name="BodyTooLargeError"}}function r(J){return new Promise((K,Z)=>{const z=[];let Q=0,$=!1;J.on("data",(V)=>{if($)return;Q+=V.length;if(Q>w){$=!0;z.length=0;Z(new P);return}z.push(V)});J.on("end",()=>{if(!$)K(Buffer.concat(z).toString("utf-8"))});J.on("error",(V)=>{if(!$)Z(V)})})}function Y(J,K,Z,z){J.writeHead(K,{"Content-Type":"application/json",...z});J.end(JSON.stringify(Z))}async function f(J,K){const Z=J.headers.origin;if(Z&&!Z.startsWith("chrome-extension://")){Y(K,403,{ok:!1,error:"Forbidden: cross-origin request blocked"});return}if(J.method==="OPTIONS"){K.writeHead(204);K.end();return}const z=J.url??"/",Q=z.split("?")[0];if(J.method==="GET"&&Q==="/ping"){Y(K,200,{ok:!0},t(Q,Z));return}if(!J.headers["x-opencli"]){Y(K,403,{ok:!1,error:"Forbidden: missing X-OpenCLI header"});return}if(J.method==="GET"&&Q==="/status"){const $=process.uptime(),V=process.memoryUsage(),F=new URL(z,`http://localhost:${B}`).searchParams.get("contextId")?.trim()||void 0,X=q(F),_=j().map((M)=>({contextId:M.contextId,extensionConnected:!0,extensionVersion:M.extensionVersion??void 0,extensionCompatRange:M.extensionCompatRange??void 0,pending:[...N.values()].filter((H)=>H.contextId===M.contextId).length,lastSeenAt:M.lastSeenAt}));Y(K,200,{ok:!0,pid:process.pid,uptime:$,daemonVersion:l,extensionConnected:!!X.connection,extensionVersion:X.connection?.extensionVersion??void 0,extensionCompatRange:X.connection?.extensionCompatRange??void 0,contextId:X.connection?.contextId??F,profileRequired:X.errorCode==="profile_required",profileDisconnected:X.errorCode==="profile_disconnected",profiles:_,pending:N.size,commandResultUnknown:I,memoryMB:Math.round(V.rss/1024/1024*10)/10,port:B});return}if(J.method==="GET"&&Q==="/logs"){const V=new URL(z,`http://localhost:${B}`).searchParams.get("level"),A=V?k.filter((F)=>F.level===V):k;Y(K,200,{ok:!0,logs:A});return}if(J.method==="DELETE"&&Q==="/logs"){k.length=0;Y(K,200,{ok:!0});return}if(J.method==="POST"&&Q==="/shutdown"){Y(K,200,{ok:!0,message:"Shutting down"});setTimeout(()=>R(),100);return}if(J.method==="POST"&&z==="/command"){try{const $=JSON.parse(await r(J));if(!$.id){Y(K,400,{ok:!1,error:"Missing command id"});return}const V=q(typeof $.contextId==="string"?$.contextId:void 0);if(!V.connection){Y(K,V.errorCode==="profile_required"?409:503,{id:$.id,ok:!1,errorCode:V.errorCode,error:V.error,...V.errorHint?{errorHint:V.errorHint}:{}});return}const A=typeof $.timeout==="number"&&$.timeout>0?$.timeout*1000:120000;if(N.has($.id)){Y(K,409,{id:$.id,ok:!1,error:"Duplicate command id already pending; retry"});return}const F=await new Promise((X,_)=>{const M=setTimeout(()=>{N.delete($.id);L.traceTimeout($.id,A,typeof $.action==="string"?$.action:void 0);_(Error(`Command timeout (${A/1000}s)`))},A),H={contextId:V.connection.contextId,action:typeof $.action==="string"?$.action:"unknown",dispatched:!1,dispatchTs:Date.now(),resolve:X,reject:_,timer:M};N.set($.id,H);const y=(U)=>{if(N.get($.id)!==H)return;const D=a(H.contextId);clearTimeout(M);N.delete($.id);_(new E(D.message,D.errorCode,D.errorHint,D.status));G.warn(`[daemon] Failed to dispatch command ${$.id}: ${U instanceof Error?U.message:String(U)}`)};try{V.connection.ws.send(JSON.stringify($),(U)=>{if(U&&!H.dispatched)y(U)});H.dispatched=!0;L.traceDispatch($)}catch(U){y(U)}});Y(K,200,F)}catch($){const V=$ instanceof E?$:null;if($ instanceof P){Y(K,413,{ok:!1,error:$.message,errorCode:"body_too_large"});return}Y(K,V?.status??($ instanceof Error&&$.message.includes("timeout")?408:400),{ok:!1,error:$ instanceof Error?$.message:"Invalid request",...V?.errorCode?{errorCode:V.errorCode}:{},...V?.errorHint?{errorHint:V.errorHint}:{}})}return}Y(K,404,{error:"Not found"})}const O=b((J,K)=>{f(J,K).catch(()=>{K.writeHead(500);K.end()})}),T=b((J,K)=>{f(J,K).catch(()=>{K.writeHead(500);K.end()})}),C=new u({noServer:!0});function m(J,K,Z){if((J.url??"/").split("?")[0]!=="/ext"){K.destroy();return}const Q=J.headers.origin;if(Q&&!Q.startsWith("chrome-extension://")){K.destroy();return}C.handleUpgrade(J,K,Z,($)=>{C.emit("connection",$,J)})}O.on("upgrade",m);T.on("upgrade",m);C.on("connection",(J)=>{G.info("[daemon] Extension connected");let K=0;const Z=setInterval(()=>{if(J.readyState!==v.OPEN){clearInterval(Z);return}if(K>=2){G.warn("[daemon] Extension heartbeat lost, closing connection");clearInterval(Z);J.terminate();return}K++;J.ping()},15000);J.on("pong",()=>{K=0});J.on("message",(z)=>{try{const Q=JSON.parse(z.toString());if(Q.type==="hello"){if(Q.brand!=="publishport"){G.warn(`[daemon] 拒绝非 PublishPort 官方扩展连接(brand=${JSON.stringify(Q.brand)})`);J.close(4003,"Only the official PublishPort extension is supported");return}const V=o(J,Q.contextId);V.extensionVersion=typeof Q.version==="string"?Q.version:null;V.extensionCompatRange=typeof Q.compatRange==="string"?Q.compatRange:null;V.lastSeenAt=Date.now();G.info(`[daemon] Extension profile connected: ${V.contextId}`);L.traceExtConnect(V.contextId,V.extensionVersion);return}if(Q.type==="log"){if(Q.level==="error")G.error(`[ext] ${Q.msg}`);else if(Q.level==="warn")G.warn(`[ext] ${Q.msg}`);else G.info(`[ext] ${Q.msg}`);s({level:Q.level,msg:Q.msg,ts:Q.ts??Date.now()});L.traceExtLog(Q.level,Q.msg);return}const $=N.get(Q.id);if($){clearTimeout($.timer);N.delete(Q.id);L.traceResult(Q.id,!!Q.ok,Date.now()-$.dispatchTs,typeof Q.errorCode==="string"?Q.errorCode:void 0,typeof Q.page==="string"?Q.page:void 0);$.resolve(Q)}}catch(Q){const $=z.toString().slice(0,200);G.warn(`[daemon] Ignoring malformed WS message from extension: ${Q instanceof Error?Q.message:String(Q)} (first 200 chars: ${JSON.stringify($)})`)}});J.on("close",()=>{G.info("[daemon] Extension disconnected");clearInterval(Z);x(J)});J.on("error",()=>{clearInterval(Z);x(J)})});O.listen(B,"127.0.0.1",()=>{G.info(`[daemon] Listening on http://127.0.0.1:${B}`)});O.on("error",(J)=>{if(J.code==="EADDRINUSE"){G.error(`[daemon] Port ${B} already in use — another daemon is likely running. Exiting.`);process.exit(S.SERVICE_UNAVAIL)}G.error(`[daemon] Server error: ${J.message}`);process.exit(S.GENERIC_ERROR)});T.listen(h,"127.0.0.1",()=>{G.info(`[daemon] Listening on http://127.0.0.1:${h} (PublishPort extension)`)});T.on("error",(J)=>{G.warn(`[daemon] PublishPort port ${h} unavailable (${J.message}) — extension will fall back to ${B}`)});function R(){for(const[,J]of N){clearTimeout(J.timer);J.reject(Error("Daemon shutting down"))}N.clear();for(const J of W.values())J.ws.close();O.close();T.close();process.exit(S.SUCCESS)}process.on("SIGTERM",R);process.on("SIGINT",R);
1
+ import{createServer as b}from"node:http";import{WebSocketServer as u,WebSocket as v}from"ws";import{DEFAULT_DAEMON_PORT as p,PUBLISHPORT_DAEMON_PORT as g,unsupportedDaemonPortEnvMessage as c}from"./constants.js";import{EXIT_CODES as S}from"./errors.js";import{log as G}from"./logger.js";import{PKG_VERSION as l}from"./version.js";import{DEFAULT_CONTEXT_ID as d}from"./browser/profile.js";import{buildCommandDispatchFailure as a,buildExtensionDisconnectFailure as i,getResponseCorsHeaders as t}from"./daemon-utils.js";import*as L from"./browser/trace.js";const B=p,h=g;if(process.env.OPENCLI_DAEMON_PORT){G.error(c(process.env.OPENCLI_DAEMON_PORT));process.exit(S.USAGE_ERROR)}const W=new Map,N=new Map;let I=0;const n=200,k=[];class E extends Error{errorCode;errorHint;status;constructor(J,K,Z,z=400){super(J);this.errorCode=K;this.errorHint=Z;this.status=z;this.name="DaemonCommandFailure"}}function s(J){k.push(J);if(k.length>n)k.shift()}function j(){return[...W.values()].filter((J)=>J.ws.readyState===v.OPEN)}function q(J){const K=typeof J==="string"&&J.trim()?J.trim():void 0;if(K){const z=W.get(K);if(z?.ws.readyState===v.OPEN)return{connection:z};const Q=j();if(Q.length===1){G.warn(`[daemon] Requested profile "${K}" not connected; falling back to the only connected profile "${Q[0].contextId}"`);return{connection:Q[0]}}return{errorCode:"profile_disconnected",error:`Browser profile "${K}" is not connected.`,errorHint:"Open that Chrome profile and make sure the ppcli extension is enabled, or choose another profile with ppcli profile use <name>."}}const Z=j();if(Z.length===1)return{connection:Z[0]};if(Z.length>1)return{errorCode:"profile_required",error:"Multiple Browser Bridge profiles are connected; choose one with --profile.",errorHint:"Run ppcli profile list, then use ppcli --profile <name> ... or ppcli profile use <name>."};return{errorCode:"extension_not_connected",error:"Extension not connected. Please install the ppcli Browser Bridge extension."}}function o(J,K){const Z=typeof K==="string"&&K.trim()?K.trim():d,z=W.get(Z);if(z&&z.ws!==J)z.ws.close();const Q=[...W.entries()].find(([,A])=>A.ws===J);if(Q&&Q[0]!==Z)W.delete(Q[0]);const $=W.get(Z),V={contextId:Z,ws:J,extensionVersion:$?.ws===J?$.extensionVersion:null,extensionCompatRange:$?.ws===J?$.extensionCompatRange:null,lastSeenAt:Date.now()};W.set(Z,V);return V}function x(J){for(const[K,Z]of W.entries()){if(Z.ws!==J)continue;W.delete(K);L.traceExtDisconnect(K);for(const[z,Q]of N){if(Q.contextId!==K)continue;clearTimeout(Q.timer);const $=i({contextId:K,action:Q.action,dispatched:Q.dispatched});if($.countAsCommandResultUnknown){I++;G.warn(`[daemon] Command result unknown after extension disconnect (id=${z}, action=${Q.action}, context=${K})`)}L.traceResult(z,!1,Date.now()-Q.dispatchTs,$.errorCode);Q.reject(new E($.message,$.errorCode,$.errorHint,$.status));N.delete(z)}}}const w=33554432;class P extends Error{status=413;constructor(){super(`请求体超过 ${w/1024/1024} MB 上限(正文内联图片过多/过大?可减少单次发布的图片体积)`);this.name="BodyTooLargeError"}}function r(J){return new Promise((K,Z)=>{const z=[];let Q=0,$=!1;J.on("data",(V)=>{if($)return;Q+=V.length;if(Q>w){$=!0;z.length=0;Z(new P);return}z.push(V)});J.on("end",()=>{if(!$)K(Buffer.concat(z).toString("utf-8"))});J.on("error",(V)=>{if(!$)Z(V)})})}function Y(J,K,Z,z){J.writeHead(K,{"Content-Type":"application/json",...z});J.end(JSON.stringify(Z))}async function f(J,K){const Z=J.headers.origin;if(Z&&!Z.startsWith("chrome-extension://")){Y(K,403,{ok:!1,error:"Forbidden: cross-origin request blocked"});return}if(J.method==="OPTIONS"){K.writeHead(204);K.end();return}const z=J.url??"/",Q=z.split("?")[0];if(J.method==="GET"&&Q==="/ping"){Y(K,200,{ok:!0},t(Q,Z));return}if(!J.headers["x-opencli"]){Y(K,403,{ok:!1,error:"Forbidden: missing X-OpenCLI header"});return}if(J.method==="GET"&&Q==="/status"){const $=process.uptime(),V=process.memoryUsage(),F=new URL(z,`http://localhost:${B}`).searchParams.get("contextId")?.trim()||void 0,X=q(F),_=j().map((M)=>({contextId:M.contextId,extensionConnected:!0,extensionVersion:M.extensionVersion??void 0,extensionCompatRange:M.extensionCompatRange??void 0,pending:[...N.values()].filter((H)=>H.contextId===M.contextId).length,lastSeenAt:M.lastSeenAt}));Y(K,200,{ok:!0,pid:process.pid,uptime:$,daemonVersion:l,extensionConnected:!!X.connection,extensionVersion:X.connection?.extensionVersion??void 0,extensionCompatRange:X.connection?.extensionCompatRange??void 0,contextId:X.connection?.contextId??F,profileRequired:X.errorCode==="profile_required",profileDisconnected:X.errorCode==="profile_disconnected",profiles:_,pending:N.size,commandResultUnknown:I,memoryMB:Math.round(V.rss/1024/1024*10)/10,port:B});return}if(J.method==="GET"&&Q==="/logs"){const V=new URL(z,`http://localhost:${B}`).searchParams.get("level"),A=V?k.filter((F)=>F.level===V):k;Y(K,200,{ok:!0,logs:A});return}if(J.method==="DELETE"&&Q==="/logs"){k.length=0;Y(K,200,{ok:!0});return}if(J.method==="POST"&&Q==="/shutdown"){Y(K,200,{ok:!0,message:"Shutting down"});setTimeout(()=>R(),100);return}if(J.method==="POST"&&z==="/command"){try{const $=JSON.parse(await r(J));if(!$.id){Y(K,400,{ok:!1,error:"Missing command id"});return}const V=q(typeof $.contextId==="string"?$.contextId:void 0);if(!V.connection){Y(K,V.errorCode==="profile_required"?409:503,{id:$.id,ok:!1,errorCode:V.errorCode,error:V.error,...V.errorHint?{errorHint:V.errorHint}:{}});return}const A=typeof $.timeout==="number"&&$.timeout>0?$.timeout*1000:120000;if(N.has($.id)){Y(K,409,{id:$.id,ok:!1,error:"Duplicate command id already pending; retry"});return}const F=await new Promise((X,_)=>{const M=setTimeout(()=>{N.delete($.id);L.traceTimeout($.id,A,typeof $.action==="string"?$.action:void 0);_(Error(`Command timeout (${A/1000}s)`))},A),H={contextId:V.connection.contextId,action:typeof $.action==="string"?$.action:"unknown",dispatched:!1,dispatchTs:Date.now(),resolve:X,reject:_,timer:M};N.set($.id,H);const y=(U)=>{if(N.get($.id)!==H)return;const D=a(H.contextId);clearTimeout(M);N.delete($.id);_(new E(D.message,D.errorCode,D.errorHint,D.status));G.warn(`[daemon] Failed to dispatch command ${$.id}: ${U instanceof Error?U.message:String(U)}`)};try{V.connection.ws.send(JSON.stringify($),(U)=>{if(U&&!H.dispatched)y(U)});H.dispatched=!0;L.traceDispatch($)}catch(U){y(U)}});Y(K,200,F)}catch($){const V=$ instanceof E?$:null;if($ instanceof P){Y(K,413,{ok:!1,error:$.message,errorCode:"body_too_large"});return}Y(K,V?.status??($ instanceof Error&&$.message.includes("timeout")?408:400),{ok:!1,error:$ instanceof Error?$.message:"Invalid request",...V?.errorCode?{errorCode:V.errorCode}:{},...V?.errorHint?{errorHint:V.errorHint}:{}})}return}Y(K,404,{error:"Not found"})}const O=b((J,K)=>{f(J,K).catch(()=>{K.writeHead(500);K.end()})}),T=b((J,K)=>{f(J,K).catch(()=>{K.writeHead(500);K.end()})}),C=new u({noServer:!0});function m(J,K,Z){if((J.url??"/").split("?")[0]!=="/ext"){K.destroy();return}const Q=J.headers.origin;if(Q&&!Q.startsWith("chrome-extension://")){K.destroy();return}C.handleUpgrade(J,K,Z,($)=>{C.emit("connection",$,J)})}O.on("upgrade",m);T.on("upgrade",m);C.on("connection",(J)=>{G.info("[daemon] Extension connected");let K=0;const Z=setInterval(()=>{if(J.readyState!==v.OPEN){clearInterval(Z);return}if(K>=2){G.warn("[daemon] Extension heartbeat lost, closing connection");clearInterval(Z);J.terminate();return}K++;J.ping()},15000);J.on("pong",()=>{K=0});J.on("message",(z)=>{try{const Q=JSON.parse(z.toString());if(Q.type==="hello"){if(Q.brand!=="publishport"){G.warn(`[daemon] 拒绝非 PublishPort 官方扩展连接(brand=${JSON.stringify(Q.brand)})`);J.close(4003,"Only the official PublishPort extension is supported");return}const V=o(J,Q.contextId);V.extensionVersion=typeof Q.version==="string"?Q.version:null;V.extensionCompatRange=typeof Q.compatRange==="string"?Q.compatRange:null;V.lastSeenAt=Date.now();G.info(`[daemon] Extension profile connected: ${V.contextId}`);L.traceExtConnect(V.contextId,V.extensionVersion);return}if(Q.type==="log"){if(Q.level==="error")G.error(`[ext] ${Q.msg}`);else if(Q.level==="warn")G.warn(`[ext] ${Q.msg}`);else G.info(`[ext] ${Q.msg}`);s({level:Q.level,msg:Q.msg,ts:Q.ts??Date.now()});L.traceExtLog(Q.level,Q.msg);return}const $=N.get(Q.id);if($){clearTimeout($.timer);N.delete(Q.id);L.traceResult(Q.id,!!Q.ok,Date.now()-$.dispatchTs,typeof Q.errorCode==="string"?Q.errorCode:void 0,typeof Q.page==="string"?Q.page:void 0);$.resolve(Q)}}catch(Q){const $=z.toString().slice(0,200);G.warn(`[daemon] Ignoring malformed WS message from extension: ${Q instanceof Error?Q.message:String(Q)} (first 200 chars: ${JSON.stringify($)})`)}});J.on("close",()=>{G.info("[daemon] Extension disconnected");clearInterval(Z);x(J)});J.on("error",()=>{clearInterval(Z);x(J)})});O.listen(B,"127.0.0.1",()=>{G.info(`[daemon] Listening on http://127.0.0.1:${B}`)});O.on("error",(J)=>{if(J.code==="EADDRINUSE"){G.error(`[daemon] Port ${B} already in use — another daemon is likely running. Exiting.`);process.exit(S.SERVICE_UNAVAIL)}G.error(`[daemon] Server error: ${J.message}`);process.exit(S.GENERIC_ERROR)});T.listen(h,"127.0.0.1",()=>{G.info(`[daemon] Listening on http://127.0.0.1:${h} (PublishPort extension)`)});T.on("error",(J)=>{G.warn(`[daemon] PublishPort port ${h} unavailable (${J.message}) — extension will fall back to ${B}`)});function R(){for(const[,J]of N){clearTimeout(J.timer);J.reject(Error("Daemon shutting down"))}N.clear();for(const J of W.values())J.ws.close();O.close();T.close();process.exit(S.SUCCESS)}process.on("SIGTERM",R);process.on("SIGINT",R);
@@ -7,15 +7,15 @@
7
7
  * when their command is executed.
8
8
  * 2. FALLBACK (filesystem scan): Traditional runtime discovery for development.
9
9
  */
10
- /** User runtime directory: ~/.opencli */
10
+ /** User runtime directory: ~/.ppcli */
11
11
  export declare const USER_OPENCLI_DIR: string;
12
- /** User CLIs directory: ~/.opencli/clis */
12
+ /** User CLIs directory: ~/.ppcli/clis */
13
13
  export declare const USER_CLIS_DIR: string;
14
- /** Plugins directory: ~/.opencli/plugins/ */
14
+ /** Plugins directory: ~/.ppcli/plugins/ */
15
15
  export declare const PLUGINS_DIR: string;
16
16
  /**
17
- * Ensure ~/.opencli/node_modules/@jackwener/opencli symlink exists so that
18
- * user CLIs in ~/.opencli/clis/ can `import { cli } from '@jackwener/opencli/registry'`.
17
+ * Ensure ~/.ppcli/node_modules/@jackwener/opencli symlink exists so that
18
+ * user CLIs in ~/.ppcli/clis/ can `import { cli } from '@jackwener/opencli/registry'`.
19
19
  *
20
20
  * This is the sole resolution mechanism — adapters use package exports
21
21
  * (e.g. `@jackwener/opencli/registry`, `@jackwener/opencli/errors`) and
@@ -25,7 +25,7 @@ export declare function ensureUserCliCompatShims(baseDir?: string): Promise<void
25
25
  /**
26
26
  * Ensure the user adapters directory exists.
27
27
  *
28
- * With smart sync, ~/.opencli/clis/ only holds files that differ from the
28
+ * With smart sync, ~/.ppcli/clis/ only holds files that differ from the
29
29
  * package baseline (upstream-synced cache + autofix output + user overrides).
30
30
  * Built-in adapters are loaded directly from the installed package.
31
31
  */
@@ -36,7 +36,7 @@ export declare function ensureUserAdapters(): Promise<void>;
36
36
  */
37
37
  export declare function discoverClis(...dirs: string[]): Promise<void>;
38
38
  /**
39
- * Discover and register plugins from ~/.opencli/plugins/.
39
+ * Discover and register plugins from ~/.ppcli/plugins/.
40
40
  * Each subdirectory is treated as a plugin (site = directory name).
41
41
  * Files inside are scanned flat (no nested site subdirs).
42
42
  */
@@ -1,2 +1,2 @@
1
- import*as H from"node:fs";import*as G from"node:os";import*as Y from"node:path";import{fileURLToPath as F,pathToFileURL as v}from"node:url";import{Strategy as A,registerCommand as J}from"./registry.js";import{getErrorMessage as $}from"./errors.js";import{log as Z}from"./logger.js";import{findPackageRoot as O,getCliManifestPath as N}from"./package-paths.js";export const USER_OPENCLI_DIR=Y.join(G.homedir(),".opencli"),USER_CLIS_DIR=Y.join(USER_OPENCLI_DIR,"clis"),PLUGINS_DIR=Y.join(USER_OPENCLI_DIR,"plugins");const T=/\b(?:cli|registerSiteAuthCommands|onStartup|onBeforeExecute|onAfterExecute)\s*\(/;function b(z,x=A.COOKIE){if(!z)return x;const B=z.toUpperCase();return A[B]??x}const L=O(F(import.meta.url));export async function ensureUserCliCompatShims(z=USER_OPENCLI_DIR){await H.promises.mkdir(z,{recursive:!0});const x=Y.join(z,"package.json"),B=`${JSON.stringify({name:"opencli-user-runtime",private:!0,type:"module"},null,2)}
2
- `;try{if(await H.promises.readFile(x,"utf-8")!==B)await H.promises.writeFile(x,B,"utf-8")}catch{await H.promises.writeFile(x,B,"utf-8")}const V=L,q=Y.join(z,"node_modules","@jackwener"),Q=Y.join(q,"opencli");try{let W=!0;try{if(await H.promises.readlink(Q)===V)W=!1}catch{}if(W){await H.promises.mkdir(q,{recursive:!0});try{await H.promises.rm(Q,{recursive:!0,force:!0})}catch{}const X=process.platform==="win32"?"junction":"dir";await H.promises.symlink(V,Q,X)}}catch(W){Z.warn(`Could not create symlink at ${Q}: ${$(W)}`)}}export async function ensureUserAdapters(){await H.promises.mkdir(USER_CLIS_DIR,{recursive:!0})}export async function discoverClis(...z){for(const x of z){const B=N(x);try{await H.promises.access(B);if(await M(B,x))continue}catch{}await C(x)}}async function M(z,x){try{const B=await H.promises.readFile(z,"utf-8"),V=JSON.parse(B);for(const q of V){if(!q.modulePath)continue;const Q=Y.resolve(x,q.modulePath),W={site:q.site,name:q.name,aliases:q.aliases,description:q.description??"",access:q.access,example:q.example,domain:q.domain,strategy:b(q.strategy),browser:q.browser,args:q.args??[],columns:q.columns,defaultFormat:q.defaultFormat,pipeline:q.pipeline,source:q.sourceFile?Y.resolve(x,q.sourceFile):Q,navigateBefore:q.navigateBefore,siteSession:q.siteSession,defaultWindowMode:q.defaultWindowMode,_lazy:!0,_modulePath:Q};J(W)}return!0}catch(B){Z.warn(`Failed to load manifest ${z}: ${$(B)}`);return!1}}async function C(z){try{await H.promises.access(z)}catch{return}const B=(await H.promises.readdir(z,{withFileTypes:!0})).filter((V)=>V.isDirectory()).map(async(V)=>{const q=V.name,Q=Y.join(z,q),W=await H.promises.readdir(Q);await Promise.all(W.map(async(X)=>{const K=Y.join(Q,X);if(X.endsWith(".yaml")||X.endsWith(".yml"))return;if(X.endsWith(".ts")&&!X.endsWith(".d.ts")&&!X.endsWith(".test.ts")){Z.warn(`Ignoring TypeScript adapter ${K} — .ts adapters are no longer loaded. Rename to .js or convert to JavaScript.`);return}if(X.endsWith(".js")&&!X.endsWith(".d.js")&&!X.endsWith(".test.js")){if(!await j(K))return;await import(v(K).href).catch((w)=>{Z.warn(`Failed to load module ${K}: ${$(w)}`)})}}))});await Promise.all(B)}export async function discoverPlugins(){try{await H.promises.access(PLUGINS_DIR)}catch{return}const z=await H.promises.readdir(PLUGINS_DIR,{withFileTypes:!0});await Promise.all(z.map(async(x)=>{const B=Y.join(PLUGINS_DIR,x.name);if(!await _(x,B))return;await U(B,x.name)}))}async function U(z,x){let B;try{B=await H.promises.readdir(z)}catch(q){const Q=q.code;if(Q!=="ENOENT"&&Q!=="ENOTDIR")Z.warn(`Failed to scan plugin directory ${z}: ${$(q)}`);return}const V=new Set(B);await Promise.all(B.map(async(q)=>{const Q=Y.join(z,q);if(q.endsWith(".yaml")||q.endsWith(".yml"))return;if(q.endsWith(".js")&&!q.endsWith(".d.js")){if(!await j(Q))return;await import(v(Q).href).catch((W)=>{Z.warn(`Plugin ${x}/${q}: ${$(W)}`)})}else if(q.endsWith(".ts")&&!q.endsWith(".d.ts")&&!q.endsWith(".test.ts")){const W=q.replace(/\.ts$/,".js");if(V.has(W))return;Z.warn(`Plugin ${x}/${q}: no compiled .js found. Run "opencli plugin update ${x}" to re-transpile, or install esbuild.`)}}))}async function j(z){try{const x=await H.promises.readFile(z,"utf-8");return T.test(x)}catch(x){Z.warn(`Failed to inspect module ${z}: ${$(x)}`);return!1}}async function _(z,x){if(z.isDirectory())return!0;if(!z.isSymbolicLink())return!1;try{return(await H.promises.stat(x)).isDirectory()}catch(B){const V=B.code;if(V!=="ENOENT"&&V!=="ENOTDIR")Z.warn(`Failed to inspect plugin link ${x}: ${$(B)}`);return!1}}
1
+ import*as H from"node:fs";import*as j from"node:os";import*as Y from"node:path";import{fileURLToPath as F,pathToFileURL as v}from"node:url";import{Strategy as A,registerCommand as J}from"./registry.js";import{getErrorMessage as $}from"./errors.js";import{log as Z}from"./logger.js";import{findPackageRoot as O,getCliManifestPath as N}from"./package-paths.js";import{migrateLegacyUserRuntime as T}from"./user-runtime.js";T();export const USER_OPENCLI_DIR=Y.join(j.homedir(),".ppcli"),USER_CLIS_DIR=Y.join(USER_OPENCLI_DIR,"clis"),PLUGINS_DIR=Y.join(USER_OPENCLI_DIR,"plugins");const b=/\b(?:cli|registerSiteAuthCommands|onStartup|onBeforeExecute|onAfterExecute)\s*\(/;function M(z,x=A.COOKIE){if(!z)return x;const B=z.toUpperCase();return A[B]??x}const C=O(F(import.meta.url));export async function ensureUserCliCompatShims(z=USER_OPENCLI_DIR){await H.promises.mkdir(z,{recursive:!0});const x=Y.join(z,"package.json"),B=`${JSON.stringify({name:"opencli-user-runtime",private:!0,type:"module"},null,2)}
2
+ `;try{if(await H.promises.readFile(x,"utf-8")!==B)await H.promises.writeFile(x,B,"utf-8")}catch{await H.promises.writeFile(x,B,"utf-8")}const V=C,q=Y.join(z,"node_modules","@jackwener"),Q=Y.join(q,"opencli");try{let W=!0;try{if(await H.promises.readlink(Q)===V)W=!1}catch{}if(W){await H.promises.mkdir(q,{recursive:!0});try{await H.promises.rm(Q,{recursive:!0,force:!0})}catch{}const X=process.platform==="win32"?"junction":"dir";await H.promises.symlink(V,Q,X)}}catch(W){Z.warn(`Could not create symlink at ${Q}: ${$(W)}`)}}export async function ensureUserAdapters(){await H.promises.mkdir(USER_CLIS_DIR,{recursive:!0})}export async function discoverClis(...z){for(const x of z){const B=N(x);try{await H.promises.access(B);if(await L(B,x))continue}catch{}await _(x)}}async function L(z,x){try{const B=await H.promises.readFile(z,"utf-8"),V=JSON.parse(B);for(const q of V){if(!q.modulePath)continue;const Q=Y.resolve(x,q.modulePath),W={site:q.site,name:q.name,aliases:q.aliases,description:q.description??"",access:q.access,example:q.example,domain:q.domain,strategy:M(q.strategy),browser:q.browser,args:q.args??[],columns:q.columns,defaultFormat:q.defaultFormat,pipeline:q.pipeline,source:q.sourceFile?Y.resolve(x,q.sourceFile):Q,navigateBefore:q.navigateBefore,siteSession:q.siteSession,defaultWindowMode:q.defaultWindowMode,_lazy:!0,_modulePath:Q};J(W)}return!0}catch(B){Z.warn(`Failed to load manifest ${z}: ${$(B)}`);return!1}}async function _(z){try{await H.promises.access(z)}catch{return}const B=(await H.promises.readdir(z,{withFileTypes:!0})).filter((V)=>V.isDirectory()).map(async(V)=>{const q=V.name,Q=Y.join(z,q),W=await H.promises.readdir(Q);await Promise.all(W.map(async(X)=>{const K=Y.join(Q,X);if(X.endsWith(".yaml")||X.endsWith(".yml"))return;if(X.endsWith(".ts")&&!X.endsWith(".d.ts")&&!X.endsWith(".test.ts")){Z.warn(`Ignoring TypeScript adapter ${K} — .ts adapters are no longer loaded. Rename to .js or convert to JavaScript.`);return}if(X.endsWith(".js")&&!X.endsWith(".d.js")&&!X.endsWith(".test.js")){if(!await w(K))return;await import(v(K).href).catch((G)=>{Z.warn(`Failed to load module ${K}: ${$(G)}`)})}}))});await Promise.all(B)}export async function discoverPlugins(){try{await H.promises.access(PLUGINS_DIR)}catch{return}const z=await H.promises.readdir(PLUGINS_DIR,{withFileTypes:!0});await Promise.all(z.map(async(x)=>{const B=Y.join(PLUGINS_DIR,x.name);if(!await U(x,B))return;await I(B,x.name)}))}async function I(z,x){let B;try{B=await H.promises.readdir(z)}catch(q){const Q=q.code;if(Q!=="ENOENT"&&Q!=="ENOTDIR")Z.warn(`Failed to scan plugin directory ${z}: ${$(q)}`);return}const V=new Set(B);await Promise.all(B.map(async(q)=>{const Q=Y.join(z,q);if(q.endsWith(".yaml")||q.endsWith(".yml"))return;if(q.endsWith(".js")&&!q.endsWith(".d.js")){if(!await w(Q))return;await import(v(Q).href).catch((W)=>{Z.warn(`Plugin ${x}/${q}: ${$(W)}`)})}else if(q.endsWith(".ts")&&!q.endsWith(".d.ts")&&!q.endsWith(".test.ts")){const W=q.replace(/\.ts$/,".js");if(V.has(W))return;Z.warn(`Plugin ${x}/${q}: no compiled .js found. Run "ppcli plugin update ${x}" to re-transpile, or install esbuild.`)}}))}async function w(z){try{const x=await H.promises.readFile(z,"utf-8");return b.test(x)}catch(x){Z.warn(`Failed to inspect module ${z}: ${$(x)}`);return!1}}async function U(z,x){if(z.isDirectory())return!0;if(!z.isSymbolicLink())return!1;try{return(await H.promises.stat(x)).isDirectory()}catch(B){const V=B.code;if(V!=="ENOENT"&&V!=="ENOTDIR")Z.warn(`Failed to inspect plugin link ${x}: ${$(B)}`);return!1}}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * opencli doctor — diagnose browser connectivity.
2
+ * ppcli doctor — diagnose browser connectivity.
3
3
  *
4
4
  * Simplified for the daemon-based architecture.
5
5
  */
@@ -1,13 +1,13 @@
1
1
  import{DEFAULT_DAEMON_PORT as L}from"./constants.js";import{BrowserBridge as M}from"./browser/index.js";import{getDaemonHealth as k}from"./browser/daemon-client.js";import{getErrorMessage as _}from"./errors.js";import{getRuntimeLabel as F}from"./runtime-detect.js";import{aliasForContextId as E,loadProfileConfig as T}from"./browser/profile.js";import{formatDaemonVersion as P,isDaemonStale as I,staleDaemonIssue as O}from"./browser/daemon-version.js";import{findShadowedUserAdapters as b,formatAdapterShadowIssue as j}from"./adapter-shadow.js";const w=8,y="__doctor__";function N(q){const z=q.replace(/^v/,"").split("-")[0].split(".").map(Number);if(z.length<3||z.some(isNaN))return null;return[z[0],z[1],z[2]]}function D(q,z){const G=z.match(/^(>=?)\s*(\S+)$/);if(!G)return!0;const[,K,$]=G,X=N(q),Y=N($);if(!X||!Y)return!0;const Q=X[0]-Y[0]||X[1]-Y[1]||X[2]-Y[2];return K===">="?Q>=0:Q>0}export async function checkConnectivity(q){const z=Date.now();try{const G=new M,K=await G.connect({timeout:q?.timeout??w,session:y,surface:"browser"});try{await K.evaluate("1 + 1");await K.closeWindow?.()}finally{await G.close()}return{ok:!0,durationMs:Date.now()-z}}catch(G){return{ok:!1,error:_(G),durationMs:Date.now()-z}}}export async function runBrowserDoctor(q={}){const z=await checkConnectivity(),G=await k(),K=G.state!=="stopped",$=G.state==="ready",X=z.ok&&!K,Y=z.ok&&K&&!$,Q=I(G.status,q.cliVersion),Z=G.status?.profiles,W=G.status?.extensionVersion,H=b(),J=[];if(X)J.push(`Daemon connectivity is unstable. The live browser test succeeded, but the daemon was no longer running immediately afterward.
2
- This usually means the daemon crashed or exited right after serving the live probe.`);else if(!K)J.push("Daemon is not running. It should start automatically when you run an opencli browser command.");if(Q&&q.cliVersion)J.push(O(G.status,q.cliVersion));if(Y)J.push(`Extension connection is unstable. The live browser test succeeded, but the daemon reported the extension disconnected immediately afterward.
2
+ This usually means the daemon crashed or exited right after serving the live probe.`);else if(!K)J.push("Daemon is not running. It should start automatically when you run an ppcli browser command.");if(Q&&q.cliVersion)J.push(O(G.status,q.cliVersion));if(Y)J.push(`Extension connection is unstable. The live browser test succeeded, but the daemon reported the extension disconnected immediately afterward.
3
3
  This usually means the Browser Bridge service worker is reconnecting slowly or Chrome suspended it.`);else if(K&&!$)if(G.state==="profile-required")J.push(`Multiple Chrome profiles are connected to the daemon, but no default profile was selected.
4
- Run opencli profile list, then opencli profile use <name>, or pass --profile <name>.`);else if(G.state==="profile-disconnected")J.push(`Selected browser profile is not connected: ${G.status?.contextId??"unknown"}.
5
- Open that Chrome profile and make sure the OpenCLI extension is enabled.`);else J.push(`Daemon is running but the Chrome/Chromium extension is not connected.
6
- If the extension is already installed, try: opencli daemon restart
4
+ Run ppcli profile list, then ppcli profile use <name>, or pass --profile <name>.`);else if(G.state==="profile-disconnected")J.push(`Selected browser profile is not connected: ${G.status?.contextId??"unknown"}.
5
+ Open that Chrome profile and make sure the ppcli extension is enabled.`);else J.push(`Daemon is running but the Chrome/Chromium extension is not connected.
6
+ If the extension is already installed, try: ppcli daemon restart
7
7
  If the extension is not installed, install the PublishPort browser extension
8
8
  from the PublishPort desktop app.`);if($&&!W)J.push(`Extension is connected but did not report a version.
9
9
  This usually means an outdated Browser Bridge extension.
10
10
  Reload or reinstall the PublishPort browser extension via the PublishPort desktop app.`);if(!z.ok)J.push(`Browser connectivity test failed: ${z.error??"unknown"}`);const B=G.status?.extensionCompatRange;if(W&&q.cliVersion&&B){if(!D(q.cliVersion,B))J.push(`CLI version incompatible with extension: extension v${W} requires CLI ${B}, but CLI is v${q.cliVersion}
11
- Update via the PublishPort desktop app (or: npm install -g publishport-opencli).`)}else if(W&&q.cliVersion){const U=W.split(".")[0],A=q.cliVersion.split(".")[0];if(U!==A)J.push(`Extension major version mismatch: extension v${W} ≠ CLI v${q.cliVersion}
12
- Update the PublishPort browser extension via the PublishPort desktop app.`)}if(H.length>0)J.push(j(H));return{cliVersion:q.cliVersion,daemonRunning:K,daemonFlaky:X,daemonStale:Q,daemonVersion:G.status?.daemonVersion,extensionConnected:$,extensionFlaky:Y,extensionVersion:W,connectivity:z,profiles:Z,adapterShadows:H,issues:J}}export function renderBrowserDoctorReport(q){const z=[`opencli v${q.cliVersion??"unknown"} doctor (${F()})`,""],G=q.daemonFlaky?"[WARN]":q.daemonStale?"[WARN]":q.daemonRunning?"[OK]":"[MISSING]",K=q.daemonFlaky?"unstable (running during live check, then stopped)":q.daemonRunning?`running on port ${L} (${q.daemonStale?`${P(q)}, stale; CLI v${q.cliVersion??"unknown"}`:P(q)})`:"not running";z.push(`${G} Daemon: ${K}`);const $=q.extensionFlaky||q.extensionConnected&&!q.extensionVersion?"[WARN]":q.extensionConnected?"[OK]":"[MISSING]",X=!q.extensionConnected?"":q.extensionVersion?` (v${q.extensionVersion})`:" (version unknown)",Y=q.extensionFlaky?"unstable (connected during live check, then disconnected)":q.extensionConnected?"connected":"not connected";z.push(`${$} Extension: ${Y}${X}`);if(q.profiles&&q.profiles.length>0){const Q=T();z.push("","Profiles:");for(const Z of q.profiles){const W=E(Q,Z.contextId),H=W?` (${W})`:"",J=Q.defaultContextId===Z.contextId?", default":"",B=Z.extensionVersion?`v${Z.extensionVersion}`:"version unknown";z.push(` • ${Z.contextId}${H}: connected ${B}${J}`)}}if(q.connectivity){const Q=q.connectivity.ok?"[OK]":"[FAIL]",Z=q.connectivity.ok?`connected in ${(q.connectivity.durationMs/1000).toFixed(1)}s`:`failed (${q.connectivity.error??"unknown"})`;z.push(`${Q} Connectivity: ${Z}`)}if(q.issues.length){z.push("","Issues:");for(const Q of q.issues)z.push(` • ${Q}`)}else if(q.daemonRunning&&q.extensionConnected)z.push("","Everything looks good!");return z.join(`
11
+ Update via the PublishPort desktop app (or: npm install -g publishport-ppcli).`)}else if(W&&q.cliVersion){const U=W.split(".")[0],A=q.cliVersion.split(".")[0];if(U!==A)J.push(`Extension major version mismatch: extension v${W} ≠ CLI v${q.cliVersion}
12
+ Update the PublishPort browser extension via the PublishPort desktop app.`)}if(H.length>0)J.push(j(H));return{cliVersion:q.cliVersion,daemonRunning:K,daemonFlaky:X,daemonStale:Q,daemonVersion:G.status?.daemonVersion,extensionConnected:$,extensionFlaky:Y,extensionVersion:W,connectivity:z,profiles:Z,adapterShadows:H,issues:J}}export function renderBrowserDoctorReport(q){const z=[`ppcli v${q.cliVersion??"unknown"} doctor (${F()})`,""],G=q.daemonFlaky?"[WARN]":q.daemonStale?"[WARN]":q.daemonRunning?"[OK]":"[MISSING]",K=q.daemonFlaky?"unstable (running during live check, then stopped)":q.daemonRunning?`running on port ${L} (${q.daemonStale?`${P(q)}, stale; CLI v${q.cliVersion??"unknown"}`:P(q)})`:"not running";z.push(`${G} Daemon: ${K}`);const $=q.extensionFlaky||q.extensionConnected&&!q.extensionVersion?"[WARN]":q.extensionConnected?"[OK]":"[MISSING]",X=!q.extensionConnected?"":q.extensionVersion?` (v${q.extensionVersion})`:" (version unknown)",Y=q.extensionFlaky?"unstable (connected during live check, then disconnected)":q.extensionConnected?"connected":"not connected";z.push(`${$} Extension: ${Y}${X}`);if(q.profiles&&q.profiles.length>0){const Q=T();z.push("","Profiles:");for(const Z of q.profiles){const W=E(Q,Z.contextId),H=W?` (${W})`:"",J=Q.defaultContextId===Z.contextId?", default":"",B=Z.extensionVersion?`v${Z.extensionVersion}`:"version unknown";z.push(` • ${Z.contextId}${H}: connected ${B}${J}`)}}if(q.connectivity){const Q=q.connectivity.ok?"[OK]":"[FAIL]",Z=q.connectivity.ok?`connected in ${(q.connectivity.durationMs/1000).toFixed(1)}s`:`failed (${q.connectivity.error??"unknown"})`;z.push(`${Q} Connectivity: ${Z}`)}if(q.issues.length){z.push("","Issues:");for(const Q of q.issues)z.push(` • ${Q}`)}else if(q.daemonRunning&&q.extensionConnected)z.push("","Everything looks good!");return z.join(`
13
13
  `)}
@@ -2,7 +2,7 @@
2
2
  * Electron app registry — maps site names to launch metadata.
3
3
  *
4
4
  * Builtin apps are defined here. User-defined apps are loaded
5
- * from ~/.opencli/apps.yaml (additive only, does not override builtins).
5
+ * from ~/.ppcli/apps.yaml (additive only, does not override builtins).
6
6
  */
7
7
  export interface ElectronAppEntry {
8
8
  /** CDP debug port (unique per app) */
@@ -1 +1 @@
1
- import*as B from"node:fs";import*as C from"node:path";import*as D from"node:os";import E from"js-yaml";export const builtinApps={cursor:{port:9226,processName:"Cursor",bundleId:"com.todesktop.runtime.Cursor",displayName:"Cursor"},codex:{port:9238,processName:"Codex",bundleId:"com.openai.codex",displayName:"Codex"},chatwise:{port:9228,processName:"ChatWise",bundleId:"com.chatwise.app",displayName:"ChatWise"},"discord-app":{port:9232,processName:"Discord",bundleId:"com.discord.app",displayName:"Discord"},"doubao-app":{port:9225,processName:"Doubao",bundleId:"com.volcengine.doubao",displayName:"Doubao"},antigravity:{port:9234,processName:"Antigravity",executableNames:["Electron","Antigravity"],bundleId:"dev.antigravity.app",displayName:"Antigravity"},"chatgpt-app":{port:9236,processName:"ChatGPT",bundleId:"com.openai.chat",displayName:"ChatGPT"},qoder:{port:9237,processName:"Qoder",executableNames:["Electron"],bundleId:"com.qoder.ide",displayName:"Qoder"},"trae-solo":{port:9235,processName:"TRAE SOLO",executableNames:["Electron","TRAE SOLO"],bundleId:"com.trae.solo.app",displayName:"Trae SOLO"},"trae-cn":{port:39240,processName:"Trae CN",executableNames:["Electron"],bundleId:"cn.trae.app",displayName:"Trae CN"}};export function loadApps(k){const q={...builtinApps};if(k){for(const[w,z]of Object.entries(k))if(!(w in q))q[w]=z}return q}let v=null;function x(){if(v)return v;let k;try{const q=C.join(D.homedir(),".opencli","apps.yaml");if(B.existsSync(q)){const w=B.readFileSync(q,"utf-8");k=E.load(w)?.apps}}catch{}v=loadApps(k);return v}export function getElectronApp(k){return x()[k]}export function isElectronApp(k){return k in x()}export function getAllElectronApps(){return x()}export function _resetRegistry(){v=null}
1
+ import*as B from"node:fs";import*as C from"node:path";import*as D from"node:os";import E from"js-yaml";export const builtinApps={cursor:{port:9226,processName:"Cursor",bundleId:"com.todesktop.runtime.Cursor",displayName:"Cursor"},codex:{port:9238,processName:"Codex",bundleId:"com.openai.codex",displayName:"Codex"},chatwise:{port:9228,processName:"ChatWise",bundleId:"com.chatwise.app",displayName:"ChatWise"},"discord-app":{port:9232,processName:"Discord",bundleId:"com.discord.app",displayName:"Discord"},"doubao-app":{port:9225,processName:"Doubao",bundleId:"com.volcengine.doubao",displayName:"Doubao"},antigravity:{port:9234,processName:"Antigravity",executableNames:["Electron","Antigravity"],bundleId:"dev.antigravity.app",displayName:"Antigravity"},"chatgpt-app":{port:9236,processName:"ChatGPT",bundleId:"com.openai.chat",displayName:"ChatGPT"},qoder:{port:9237,processName:"Qoder",executableNames:["Electron"],bundleId:"com.qoder.ide",displayName:"Qoder"},"trae-solo":{port:9235,processName:"TRAE SOLO",executableNames:["Electron","TRAE SOLO"],bundleId:"com.trae.solo.app",displayName:"Trae SOLO"},"trae-cn":{port:39240,processName:"Trae CN",executableNames:["Electron"],bundleId:"cn.trae.app",displayName:"Trae CN"}};export function loadApps(k){const q={...builtinApps};if(k){for(const[w,z]of Object.entries(k))if(!(w in q))q[w]=z}return q}let v=null;function x(){if(v)return v;let k;try{const q=C.join(D.homedir(),".ppcli","apps.yaml");if(B.existsSync(q)){const w=B.readFileSync(q,"utf-8");k=E.load(w)?.apps}}catch{}v=loadApps(k);return v}export function getElectronApp(k){return x()[k]}export function isElectronApp(k){return k in x()}export function getAllElectronApps(){return x()}export function _resetRegistry(){v=null}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Unified error types for opencli.
2
+ * Unified error types for ppcli.
3
3
  *
4
4
  * All errors thrown by the framework should extend CliError so that
5
5
  * the top-level handler in commanderAdapter.ts can render consistent,
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * ## Exit codes
9
9
  *
10
- * opencli follows Unix conventions (sysexits.h) for process exit codes:
10
+ * ppcli follows Unix conventions (sysexits.h) for process exit codes:
11
11
  *
12
12
  * 0 Success
13
13
  * 1 Generic / unexpected error
@@ -1,2 +1,2 @@
1
- export const EXIT_CODES={SUCCESS:0,GENERIC_ERROR:1,USAGE_ERROR:2,EMPTY_RESULT:66,SERVICE_UNAVAIL:69,TEMPFAIL:75,NOPERM:77,CONFIG_ERROR:78,INTERRUPTED:130};export class CliError extends Error{code;hint;exitCode;constructor(j,q,F,G=EXIT_CODES.GENERIC_ERROR){super(q);this.name=new.target.name;this.code=j;this.hint=F;this.exitCode=G}}const J=Symbol.for("opencli.traceReceipt");export function attachTraceReceipt(j,q){if(!j||typeof j!=="object"&&typeof j!=="function")return;try{Object.defineProperty(j,J,{value:q,enumerable:!1,configurable:!0})}catch{}}export function getTraceReceipt(j){if(!j||typeof j!=="object"&&typeof j!=="function")return;return j[J]}export class BrowserConnectError extends CliError{kind;constructor(j,q,F="unknown"){super("BROWSER_CONNECT",j,q,EXIT_CODES.SERVICE_UNAVAIL);this.kind=F}}export class CommandExecutionError extends CliError{constructor(j,q){super("COMMAND_EXEC",j,q,EXIT_CODES.GENERIC_ERROR)}}export class ConfigError extends CliError{constructor(j,q){super("CONFIG",j,q,EXIT_CODES.CONFIG_ERROR)}}export class AuthRequiredError extends CliError{domain;constructor(j,q){super("AUTH_REQUIRED",q??`Not logged in to ${j}`,`Please open Chrome or Chromium and log in to https://${j}`,EXIT_CODES.NOPERM);this.domain=j}}export class TimeoutError extends CliError{constructor(j,q,F){super("TIMEOUT",`${j} timed out after ${q}s`,F??"Try again, or increase timeout with --timeout <seconds> (or OPENCLI_BROWSER_COMMAND_TIMEOUT for the global default)",EXIT_CODES.TEMPFAIL)}}export class RateLimitError extends CliError{constructor(j,q,F){const G=N(q);super("RATE_LIMITED",`Rate limit exceeded for ${j}`,`Retry after ${G}. To change this limit, edit ${F}.`,EXIT_CODES.TEMPFAIL)}}export class ArgumentError extends CliError{constructor(j,q){super("ARGUMENT",j,q,EXIT_CODES.USAGE_ERROR)}}export class EmptyResultError extends CliError{constructor(j,q){super("EMPTY_RESULT",`${j} returned no data`,q??"The page structure may have changed, or you may need to log in",EXIT_CODES.EMPTY_RESULT)}}export function adapterLoadError(j,q){return new CliError("ADAPTER_LOAD",j,q,EXIT_CODES.SERVICE_UNAVAIL)}export function selectorError(j,q){return new CliError("SELECTOR",`Could not find element: ${j}`,q??"The page UI may have changed. Please report this issue.",EXIT_CODES.GENERIC_ERROR)}export class PluginError extends CliError{constructor(j,q){super("PLUGIN",j,q,EXIT_CODES.GENERIC_ERROR)}}function N(j){const q=Math.max(0,Math.ceil(j));if(q<1000)return`${q}ms`;const F=Math.ceil(q/1000);if(F<60)return`${F}s`;const G=Math.ceil(F/60);if(G<60)return`${G}m`;return`${Math.ceil(G/60)}h`}export class LoginWallError extends CliError{status;url;bodyPreview;constructor(j,q,F,G,H){super("LOGIN_WALL",j,H??"The server returned an HTML page instead of JSON — likely a login wall, rate limit, or WAF challenge. Try re-logging in via your browser, or wait a few minutes before retrying.",EXIT_CODES.NOPERM);this.status=q;this.url=F;this.bodyPreview=G}}export function getErrorMessage(j){return j instanceof Error?j.message:String(j)}function K(j,q=0){if(q>10)return"(cause chain truncated)";if(j instanceof Error){const F=[j.message];if(j.cause)F.push(` caused by: ${K(j.cause,q+1)}`);return F.join(`
1
+ export const EXIT_CODES={SUCCESS:0,GENERIC_ERROR:1,USAGE_ERROR:2,EMPTY_RESULT:66,SERVICE_UNAVAIL:69,TEMPFAIL:75,NOPERM:77,CONFIG_ERROR:78,INTERRUPTED:130};export class CliError extends Error{code;hint;exitCode;constructor(j,q,F,G=EXIT_CODES.GENERIC_ERROR){super(q);this.name=new.target.name;this.code=j;this.hint=F;this.exitCode=G}}const J=Symbol.for("ppcli.traceReceipt");export function attachTraceReceipt(j,q){if(!j||typeof j!=="object"&&typeof j!=="function")return;try{Object.defineProperty(j,J,{value:q,enumerable:!1,configurable:!0})}catch{}}export function getTraceReceipt(j){if(!j||typeof j!=="object"&&typeof j!=="function")return;return j[J]}export class BrowserConnectError extends CliError{kind;constructor(j,q,F="unknown"){super("BROWSER_CONNECT",j,q,EXIT_CODES.SERVICE_UNAVAIL);this.kind=F}}export class CommandExecutionError extends CliError{constructor(j,q){super("COMMAND_EXEC",j,q,EXIT_CODES.GENERIC_ERROR)}}export class ConfigError extends CliError{constructor(j,q){super("CONFIG",j,q,EXIT_CODES.CONFIG_ERROR)}}export class AuthRequiredError extends CliError{domain;constructor(j,q){super("AUTH_REQUIRED",q??`Not logged in to ${j}`,`Please open Chrome or Chromium and log in to https://${j}`,EXIT_CODES.NOPERM);this.domain=j}}export class TimeoutError extends CliError{constructor(j,q,F){super("TIMEOUT",`${j} timed out after ${q}s`,F??"Try again, or increase timeout with --timeout <seconds> (or OPENCLI_BROWSER_COMMAND_TIMEOUT for the global default)",EXIT_CODES.TEMPFAIL)}}export class RateLimitError extends CliError{constructor(j,q,F){const G=N(q);super("RATE_LIMITED",`Rate limit exceeded for ${j}`,`Retry after ${G}. To change this limit, edit ${F}.`,EXIT_CODES.TEMPFAIL)}}export class ArgumentError extends CliError{constructor(j,q){super("ARGUMENT",j,q,EXIT_CODES.USAGE_ERROR)}}export class EmptyResultError extends CliError{constructor(j,q){super("EMPTY_RESULT",`${j} returned no data`,q??"The page structure may have changed, or you may need to log in",EXIT_CODES.EMPTY_RESULT)}}export function adapterLoadError(j,q){return new CliError("ADAPTER_LOAD",j,q,EXIT_CODES.SERVICE_UNAVAIL)}export function selectorError(j,q){return new CliError("SELECTOR",`Could not find element: ${j}`,q??"The page UI may have changed. Please report this issue.",EXIT_CODES.GENERIC_ERROR)}export class PluginError extends CliError{constructor(j,q){super("PLUGIN",j,q,EXIT_CODES.GENERIC_ERROR)}}function N(j){const q=Math.max(0,Math.ceil(j));if(q<1000)return`${q}ms`;const F=Math.ceil(q/1000);if(F<60)return`${F}s`;const G=Math.ceil(F/60);if(G<60)return`${G}m`;return`${Math.ceil(G/60)}h`}export class LoginWallError extends CliError{status;url;bodyPreview;constructor(j,q,F,G,H){super("LOGIN_WALL",j,H??"The server returned an HTML page instead of JSON — likely a login wall, rate limit, or WAF challenge. Try re-logging in via your browser, or wait a few minutes before retrying.",EXIT_CODES.NOPERM);this.status=q;this.url=F;this.bodyPreview=G}}export function getErrorMessage(j){return j instanceof Error?j.message:String(j)}function K(j,q=0){if(q>10)return"(cause chain truncated)";if(j instanceof Error){const F=[j.message];if(j.cause)F.push(` caused by: ${K(j.cause,q+1)}`);return F.join(`
2
2
  `)}return String(j)}export function toEnvelope(j){const q=j instanceof Error&&j.cause?K(j.cause):void 0,F=getTraceReceipt(j),G=F?{traceId:F.traceId,dir:F.traceDir,summaryPath:F.summaryPath,receiptPath:F.receiptPath,status:F.status}:void 0;if(j instanceof CliError)return{ok:!1,error:{code:j.code,message:j.message,...j.hint?{help:j.hint}:{},exitCode:j.exitCode,...q?{cause:q}:{}},...G?{trace:G}:{}};return{ok:!1,error:{code:"UNKNOWN",message:getErrorMessage(j),exitCode:EXIT_CODES.GENERIC_ERROR,...q?{cause:q}:{}},...G?{trace:G}:{}}}
@@ -1,2 +1,2 @@
1
- import{getRegistry as i,fullName as R}from"./registry.js";import{pathToFileURL as m}from"node:url";import*as n from"node:crypto";import*as h from"node:fs";import*as t from"node:os";import{executePipeline as b}from"./pipeline/index.js";import{adapterLoadError as d,ArgumentError as F,CommandExecutionError as U,attachTraceReceipt as s,getErrorMessage as f}from"./errors.js";import{shouldUseBrowserSession as o}from"./capabilityRouting.js";import{getBrowserFactory as a,browserSession as r,runWithTimeout as v,DEFAULT_BROWSER_COMMAND_TIMEOUT as e}from"./runtime.js";import{withBrowserSessionLockIf as qq}from"./browser-session-lock.js";import{resolveProfile as Qq}from"./browser/profile.js";import{ensureManagedChrome as Xq}from"./browser/managed-chrome.js";import{generateFingerprintJs as Yq}from"./browser/fingerprint.js";import{CDPBridge as Zq}from"./browser/index.js";import{emitHook as C}from"./hooks.js";import{log as w}from"./logger.js";import{isElectronApp as $q}from"./electron-apps.js";import{probeCDP as Jq,resolveElectronEndpoint as Gq}from"./launcher.js";import{ObservationSession as Hq,exportObservationSession as Vq}from"./observation/index.js";import{resolveAdapterSourcePath as Lq}from"./adapter-source.js";const j=new Map,N=new Map,zq=`${t.homedir()}/.opencli/clis/`;function Wq(q){if(q===void 0||q===null||q===""||q==="off")return"off";if(q==="on"||q==="retain-on-failure")return q;throw new F(`--trace must be one of: off, on, retain-on-failure. Received: "${String(q)}"`)}export function coerceAndValidateArgs(q,Q){const Y={...Q};for(const X of q){const Z=Y[X.name];if(X.required&&(Z===void 0||Z===null||Z===""))throw new F(`Argument "${X.name}" is required.`,X.help??`Provide a value for --${X.name}`);if(Z!==void 0&&Z!==null){if(X.type==="int"||X.type==="number"){const V=Number(Z);if(Number.isNaN(V))throw new F(`Argument "${X.name}" must be a valid number. Received: "${Z}"`);Y[X.name]=V}else if(X.type==="boolean"||X.type==="bool")if(typeof Z==="string"){const V=Z.toLowerCase();if(V==="true"||V==="1")Y[X.name]=!0;else if(V==="false"||V==="0")Y[X.name]=!1;else throw new F(`Argument "${X.name}" must be a boolean (true/false). Received: "${Z}"`)}else Y[X.name]=Boolean(Z);const J=Y[X.name];if(X.choices&&X.choices.length>0){if(!X.choices.map(String).includes(String(J)))throw new F(`Argument "${X.name}" must be one of: ${X.choices.join(", ")}. Received: "${J}"`)}}else if(X.default!==void 0)Y[X.name]=X.default}return Y}async function x(q,Q,Y,X){const Z=q;if(Z._lazy&&Z._modulePath){const J=Z._modulePath;if(J.startsWith(zq)&&j.has(J))try{const z=h.statSync(J),$=N.get(J);if($!==void 0&&z.mtimeMs!==$)j.delete(J)}catch{}if(!j.has(J)){const z=m(J).href,y=import(N.has(J)?`${z}?t=${Date.now()}`:z).then(()=>{try{N.set(J,h.statSync(J).mtimeMs)}catch{}},(K)=>{j.delete(J);throw d(`Failed to load adapter module ${J}: ${f(K)}`,"Check that the adapter file exists and has no syntax errors.")});j.set(J,y)}await j.get(J);const W=i().get(R(q));if(W?.func)return u(W,Q,Y,X);if(W?.pipeline)return b(Q,W.pipeline,{args:Y,debug:X})}if(q.func)return u(q,Q,Y,X);if(q.pipeline)return b(Q,q.pipeline,{args:Y,debug:X});throw new U(`Command ${R(q)} has no func or pipeline`,"This is likely a bug in the adapter definition. Please report this issue.")}function u(q,Q,Y,X){if(q.browser===!1)return q.func(Y,X);if(!Q)throw new U(`Command ${R(q)} requires a browser session but none was provided`);return q.func(Q,Y,X)}function Fq(q){if(q.navigateBefore===!1)return null;if(typeof q.navigateBefore==="string")return q.navigateBefore;return null}function Kq(q,Q){if(!q||!Q)return!1;try{const Y=new URL(q).hostname;return Y===Q||Y.endsWith(`.${Q}`)}catch{return!1}}function Bq(q,Q){if(!Q)return!1;try{const Y=new URL(q),X=Y.hostname===Q||Y.hostname.endsWith(`.${Q}`),Z=Y.pathname===""||Y.pathname==="/";return X&&Z&&Y.search===""&&Y.hash===""}catch{return!1}}async function Iq(q,Q,Y,X){if(Y!=="persistent"||!q.domain)return!0;if(!Bq(X,q.domain))return!0;const Z=await Q.getCurrentUrl?.().catch(()=>null);return!Kq(Z,q.domain)}export async function executeCommand(q,Q,Y=!1,X={}){let Z;try{Z=X.prepared?Q:prepareCommandArgs(q,Q)}catch($){if($ instanceof F)throw $;throw new F(f($))}const J=Nq(q,Z),V=Wq(X.trace),W={command:R(q),args:Z,startedAt:Date.now()};await C("onBeforeExecute",W);let z;try{if(o(q)){const $=$q(q.site),y=Qq(X.profile),K=!$&&y.kind==="managed"?y:null;let O,S,D;if(K){O=await Xq(K.name,K.entry);const L=K.entry.fingerprint;if(L?.enabled)S=Yq(L);const G=K.entry.proxy;if(G?.username)D={username:G.username,password:G.password??""}}else if($){const L=process.env.OPENCLI_CDP_ENDPOINT;if(L){const G=Number(new URL(L).port);if(!await Jq(G))throw new U(`CDP not reachable at ${L}`,"Check that the app is running with --remote-debugging-port and the endpoint is correct.");O=L}else O=await Gq(q.site)}const l=K?Zq:a(q.site),M=!K&&y.kind==="extension"?y.contextId:void 0,c=q,_=jq(q,X.siteSession),k=Oq(q,_),P=Uq(_,X.keepTab),E=Cq(q.defaultWindowMode??"background",X.windowMode);z=await qq(E!=="foreground",()=>r(l,async(L)=>{const G=V==="off"?null:new Hq({scope:{contextId:M??K?.name,session:k,target:L.getActivePage?.(),site:q.site,command:R(q),adapterSourcePath:Lq(c)}});if(G){G.record({stream:"action",name:"command",phase:"start",data:{args:Z}});await L.startNetworkCapture?.().catch(()=>!1)}const I=Fq(q);if(I&&await Iq(q,L,_,I)){G?.record({stream:"action",name:"pre_navigate",phase:"start",data:{url:I}});try{await L.goto(I);G?.record({stream:"action",name:"pre_navigate",phase:"end",data:{url:I}})}catch(H){G?.record({stream:"action",name:"pre_navigate",phase:"error",data:{url:I,error:H instanceof Error?H.message:String(H)}});const B=new U(`Pre-navigation to ${I} failed: ${H instanceof Error?H.message:H}`,"Check that the site is reachable and the browser extension is running.");if(G&&(V==="on"||V==="retain-on-failure")){G.record({stream:"error",message:B.message,stack:B.stack,code:B.code,hint:B.hint});await A(G,L).catch(()=>{});T(G,"failure",B,X.onTraceExport)}throw B}}try{const H=J!==null?J+p:e,B=await v(x(q,L,Z,Y),{timeout:H,label:R(q)});G?.record({stream:"action",name:"command",phase:"end"});if(G&&V==="on"){await A(G,L).catch(()=>{});T(G,"success",void 0,X.onTraceExport)}if(!P)await L.closeWindow?.().catch(()=>{});return B}catch(H){if(G){G.record({stream:"action",name:"command",phase:"error",data:{error:H instanceof Error?H.message:String(H)}});G.record({stream:"error",message:H instanceof Error?H.message:String(H),stack:H instanceof Error?H.stack:void 0});if(V==="on"||V==="retain-on-failure"){await A(G,L).catch(()=>{});T(G,"failure",H,X.onTraceExport)}}if(!P)await L.closeWindow?.().catch(()=>{});throw H}},{session:k,cdpEndpoint:O,contextId:M,windowMode:E,surface:"adapter",siteSession:_,initScript:S,proxyAuth:D}),K?.name)}else if(J!==null){const $=J+p;z=await v(x(q,null,Z,Y),{timeout:$,label:R(q),hint:`Pass a higher --timeout value (currently ${J}s)`})}else z=await x(q,null,Z,Y)}catch($){W.error=$;W.finishedAt=Date.now();await C("onAfterExecute",W);throw $}W.finishedAt=Date.now();await C("onAfterExecute",W,z);return z}async function A(q,Q){const Y=Q.getActivePage?.()??q.scope.target,[X,Z,J,V,W]=await Promise.all([Q.getCurrentUrl?.().catch(()=>null)??Promise.resolve(null),Q.snapshot().catch(()=>{return}),Q.readNetworkCapture?.().catch(()=>[])??Promise.resolve([]),Q.consoleMessages("all").catch(()=>[]),Q.screenshot({format:"png"}).catch(()=>{return})]);if(Z!==void 0||X!==void 0)q.record({stream:"state",url:X,target:Y,snapshot:Z,label:"final"});for(const z of Array.isArray(J)?J:[]){const $=z;q.record({stream:"network",url:String($.url??""),method:typeof $.method==="string"?$.method:void 0,status:typeof $.responseStatus==="number"?$.responseStatus:void 0,contentType:typeof $.responseContentType==="string"?$.responseContentType:void 0,size:typeof $.responseBodyFullSize==="number"?$.responseBodyFullSize:void 0,requestHeaders:$.requestHeaders,responseHeaders:$.responseHeaders,requestBody:$.requestBodyPreview,responseBody:$.responsePreview,ts:typeof $.timestamp==="number"?$.timestamp:void 0})}for(const z of Array.isArray(V)?V:[])if(z&&typeof z==="object"){const $=z;q.record({stream:"console",level:String($.type??$.level??"log"),text:String($.text??$.message??""),ts:typeof $.timestamp==="number"?$.timestamp:void 0})}else q.record({stream:"console",level:"log",text:String(z)});if(typeof W==="string"&&W)q.record({stream:"screenshot",format:"png",data:W,label:"final"})}function T(q,Q,Y,X){try{const Z=Vq(q,{error:Y,status:Q});if(Q==="failure"&&Y!==void 0)s(Y,Z.receipt);else process.stderr.write(`OpenCLI trace artifact: ${Z.dir}
1
+ import{getRegistry as i,fullName as R}from"./registry.js";import{pathToFileURL as m}from"node:url";import*as n from"node:crypto";import*as h from"node:fs";import*as t from"node:os";import{executePipeline as b}from"./pipeline/index.js";import{adapterLoadError as d,ArgumentError as F,CommandExecutionError as U,attachTraceReceipt as s,getErrorMessage as f}from"./errors.js";import{shouldUseBrowserSession as o}from"./capabilityRouting.js";import{getBrowserFactory as a,browserSession as r,runWithTimeout as v,DEFAULT_BROWSER_COMMAND_TIMEOUT as e}from"./runtime.js";import{withBrowserSessionLockIf as qq}from"./browser-session-lock.js";import{resolveProfile as Qq}from"./browser/profile.js";import{ensureManagedChrome as Xq}from"./browser/managed-chrome.js";import{generateFingerprintJs as Yq}from"./browser/fingerprint.js";import{CDPBridge as Zq}from"./browser/index.js";import{emitHook as C}from"./hooks.js";import{log as w}from"./logger.js";import{isElectronApp as $q}from"./electron-apps.js";import{probeCDP as Jq,resolveElectronEndpoint as Gq}from"./launcher.js";import{ObservationSession as Hq,exportObservationSession as Vq}from"./observation/index.js";import{resolveAdapterSourcePath as Lq}from"./adapter-source.js";const j=new Map,N=new Map,zq=`${t.homedir()}/.ppcli/clis/`;function Wq(q){if(q===void 0||q===null||q===""||q==="off")return"off";if(q==="on"||q==="retain-on-failure")return q;throw new F(`--trace must be one of: off, on, retain-on-failure. Received: "${String(q)}"`)}export function coerceAndValidateArgs(q,Q){const Y={...Q};for(const X of q){const Z=Y[X.name];if(X.required&&(Z===void 0||Z===null||Z===""))throw new F(`Argument "${X.name}" is required.`,X.help??`Provide a value for --${X.name}`);if(Z!==void 0&&Z!==null){if(X.type==="int"||X.type==="number"){const V=Number(Z);if(Number.isNaN(V))throw new F(`Argument "${X.name}" must be a valid number. Received: "${Z}"`);Y[X.name]=V}else if(X.type==="boolean"||X.type==="bool")if(typeof Z==="string"){const V=Z.toLowerCase();if(V==="true"||V==="1")Y[X.name]=!0;else if(V==="false"||V==="0")Y[X.name]=!1;else throw new F(`Argument "${X.name}" must be a boolean (true/false). Received: "${Z}"`)}else Y[X.name]=Boolean(Z);const J=Y[X.name];if(X.choices&&X.choices.length>0){if(!X.choices.map(String).includes(String(J)))throw new F(`Argument "${X.name}" must be one of: ${X.choices.join(", ")}. Received: "${J}"`)}}else if(X.default!==void 0)Y[X.name]=X.default}return Y}async function x(q,Q,Y,X){const Z=q;if(Z._lazy&&Z._modulePath){const J=Z._modulePath;if(J.startsWith(zq)&&j.has(J))try{const z=h.statSync(J),$=N.get(J);if($!==void 0&&z.mtimeMs!==$)j.delete(J)}catch{}if(!j.has(J)){const z=m(J).href,y=import(N.has(J)?`${z}?t=${Date.now()}`:z).then(()=>{try{N.set(J,h.statSync(J).mtimeMs)}catch{}},(K)=>{j.delete(J);throw d(`Failed to load adapter module ${J}: ${f(K)}`,"Check that the adapter file exists and has no syntax errors.")});j.set(J,y)}await j.get(J);const W=i().get(R(q));if(W?.func)return u(W,Q,Y,X);if(W?.pipeline)return b(Q,W.pipeline,{args:Y,debug:X})}if(q.func)return u(q,Q,Y,X);if(q.pipeline)return b(Q,q.pipeline,{args:Y,debug:X});throw new U(`Command ${R(q)} has no func or pipeline`,"This is likely a bug in the adapter definition. Please report this issue.")}function u(q,Q,Y,X){if(q.browser===!1)return q.func(Y,X);if(!Q)throw new U(`Command ${R(q)} requires a browser session but none was provided`);return q.func(Q,Y,X)}function Fq(q){if(q.navigateBefore===!1)return null;if(typeof q.navigateBefore==="string")return q.navigateBefore;return null}function Kq(q,Q){if(!q||!Q)return!1;try{const Y=new URL(q).hostname;return Y===Q||Y.endsWith(`.${Q}`)}catch{return!1}}function Bq(q,Q){if(!Q)return!1;try{const Y=new URL(q),X=Y.hostname===Q||Y.hostname.endsWith(`.${Q}`),Z=Y.pathname===""||Y.pathname==="/";return X&&Z&&Y.search===""&&Y.hash===""}catch{return!1}}async function Iq(q,Q,Y,X){if(Y!=="persistent"||!q.domain)return!0;if(!Bq(X,q.domain))return!0;const Z=await Q.getCurrentUrl?.().catch(()=>null);return!Kq(Z,q.domain)}export async function executeCommand(q,Q,Y=!1,X={}){let Z;try{Z=X.prepared?Q:prepareCommandArgs(q,Q)}catch($){if($ instanceof F)throw $;throw new F(f($))}const J=Nq(q,Z),V=Wq(X.trace),W={command:R(q),args:Z,startedAt:Date.now()};await C("onBeforeExecute",W);let z;try{if(o(q)){const $=$q(q.site),y=Qq(X.profile),K=!$&&y.kind==="managed"?y:null;let O,S,D;if(K){O=await Xq(K.name,K.entry);const L=K.entry.fingerprint;if(L?.enabled)S=Yq(L);const G=K.entry.proxy;if(G?.username)D={username:G.username,password:G.password??""}}else if($){const L=process.env.OPENCLI_CDP_ENDPOINT;if(L){const G=Number(new URL(L).port);if(!await Jq(G))throw new U(`CDP not reachable at ${L}`,"Check that the app is running with --remote-debugging-port and the endpoint is correct.");O=L}else O=await Gq(q.site)}const l=K?Zq:a(q.site),M=!K&&y.kind==="extension"?y.contextId:void 0,c=q,_=jq(q,X.siteSession),k=Oq(q,_),P=Uq(_,X.keepTab),E=Cq(q.defaultWindowMode??"background",X.windowMode);z=await qq(E!=="foreground",()=>r(l,async(L)=>{const G=V==="off"?null:new Hq({scope:{contextId:M??K?.name,session:k,target:L.getActivePage?.(),site:q.site,command:R(q),adapterSourcePath:Lq(c)}});if(G){G.record({stream:"action",name:"command",phase:"start",data:{args:Z}});await L.startNetworkCapture?.().catch(()=>!1)}const I=Fq(q);if(I&&await Iq(q,L,_,I)){G?.record({stream:"action",name:"pre_navigate",phase:"start",data:{url:I}});try{await L.goto(I);G?.record({stream:"action",name:"pre_navigate",phase:"end",data:{url:I}})}catch(H){G?.record({stream:"action",name:"pre_navigate",phase:"error",data:{url:I,error:H instanceof Error?H.message:String(H)}});const B=new U(`Pre-navigation to ${I} failed: ${H instanceof Error?H.message:H}`,"Check that the site is reachable and the browser extension is running.");if(G&&(V==="on"||V==="retain-on-failure")){G.record({stream:"error",message:B.message,stack:B.stack,code:B.code,hint:B.hint});await A(G,L).catch(()=>{});T(G,"failure",B,X.onTraceExport)}throw B}}try{const H=J!==null?J+p:e,B=await v(x(q,L,Z,Y),{timeout:H,label:R(q)});G?.record({stream:"action",name:"command",phase:"end"});if(G&&V==="on"){await A(G,L).catch(()=>{});T(G,"success",void 0,X.onTraceExport)}if(!P)await L.closeWindow?.().catch(()=>{});return B}catch(H){if(G){G.record({stream:"action",name:"command",phase:"error",data:{error:H instanceof Error?H.message:String(H)}});G.record({stream:"error",message:H instanceof Error?H.message:String(H),stack:H instanceof Error?H.stack:void 0});if(V==="on"||V==="retain-on-failure"){await A(G,L).catch(()=>{});T(G,"failure",H,X.onTraceExport)}}if(!P)await L.closeWindow?.().catch(()=>{});throw H}},{session:k,cdpEndpoint:O,contextId:M,windowMode:E,surface:"adapter",siteSession:_,initScript:S,proxyAuth:D}),K?.name)}else if(J!==null){const $=J+p;z=await v(x(q,null,Z,Y),{timeout:$,label:R(q),hint:`Pass a higher --timeout value (currently ${J}s)`})}else z=await x(q,null,Z,Y)}catch($){W.error=$;W.finishedAt=Date.now();await C("onAfterExecute",W);throw $}W.finishedAt=Date.now();await C("onAfterExecute",W,z);return z}async function A(q,Q){const Y=Q.getActivePage?.()??q.scope.target,[X,Z,J,V,W]=await Promise.all([Q.getCurrentUrl?.().catch(()=>null)??Promise.resolve(null),Q.snapshot().catch(()=>{return}),Q.readNetworkCapture?.().catch(()=>[])??Promise.resolve([]),Q.consoleMessages("all").catch(()=>[]),Q.screenshot({format:"png"}).catch(()=>{return})]);if(Z!==void 0||X!==void 0)q.record({stream:"state",url:X,target:Y,snapshot:Z,label:"final"});for(const z of Array.isArray(J)?J:[]){const $=z;q.record({stream:"network",url:String($.url??""),method:typeof $.method==="string"?$.method:void 0,status:typeof $.responseStatus==="number"?$.responseStatus:void 0,contentType:typeof $.responseContentType==="string"?$.responseContentType:void 0,size:typeof $.responseBodyFullSize==="number"?$.responseBodyFullSize:void 0,requestHeaders:$.requestHeaders,responseHeaders:$.responseHeaders,requestBody:$.requestBodyPreview,responseBody:$.responsePreview,ts:typeof $.timestamp==="number"?$.timestamp:void 0})}for(const z of Array.isArray(V)?V:[])if(z&&typeof z==="object"){const $=z;q.record({stream:"console",level:String($.type??$.level??"log"),text:String($.text??$.message??""),ts:typeof $.timestamp==="number"?$.timestamp:void 0})}else q.record({stream:"console",level:"log",text:String(z)});if(typeof W==="string"&&W)q.record({stream:"screenshot",format:"png",data:W,label:"final"})}function T(q,Q,Y,X){try{const Z=Vq(q,{error:Y,status:Q});if(Q==="failure"&&Y!==void 0)s(Y,Z.receipt);else process.stderr.write(`ppcli trace artifact: ${Z.dir}
2
2
  `);try{X?.(Z)}catch(J){w.warn(`[trace] Trace export callback failed: ${J instanceof Error?J.message:String(J)}`)}return Z}catch(Z){w.warn(`[trace] Failed to export trace artifact: ${Z instanceof Error?Z.message:String(Z)}`);return}}export function prepareCommandArgs(q,Q){const Y=coerceAndValidateArgs(q.args,Q);q.validateArgs?.(Y);return Y}const p=30;function Rq(q){if(q===void 0||q===null||q==="")return null;if(q==="ephemeral"||q==="persistent")return q;throw new F(`--site-session must be one of: ephemeral, persistent. Received: "${String(q)}"`)}function yq(){const q=process.env.PUBLISHPORT_SITE_SESSION;if(q==="persistent"||q==="ephemeral")return q;return null}function jq(q,Q){return Rq(Q)??yq()??q.siteSession??"persistent"}function Oq(q,Q){if(Q==="persistent")return`site:${q.site}`;return`site:${q.site}:${n.randomUUID()}`}function _q(q,Q){if(Q===void 0||Q==="")return null;if(Q==="true")return!0;if(Q==="false")return!1;throw new F(`${q} must be one of: true, false. Received: "${String(Q)}"`)}function Uq(q,Q){if(q==="persistent")return!0;return _q("--keep-tab",Q)??!1}function g(q,Q){if(Q===void 0||Q==="")return null;if(Q==="foreground"||Q==="background")return Q;throw new F(`${q} must be one of: foreground, background. Received: "${String(Q)}"`)}function Cq(q="background",Q){return g("--window",Q)??g("OPENCLI_WINDOW",process.env.OPENCLI_WINDOW)??q}function Nq(q,Q){if(!q.args.some((Z)=>Z.name==="timeout"))return null;const Y=Q.timeout;if(Y===void 0||Y===null||Y==="")throw new F(`Argument "timeout" must be a positive integer. Received: "${String(Y)}"`);const X=Number(Y);if(!Number.isInteger(X)||X<=0)throw new F(`Argument "timeout" must be a positive integer. Received: "${String(Y)}"`);return X}
@@ -5,7 +5,7 @@ export interface ExternalCliInstall {
5
5
  default?: string;
6
6
  }
7
7
  export interface ExternalCliConfig {
8
- /** User-facing OpenCLI subcommand and, by default, the executable name. */
8
+ /** User-facing ppcli subcommand and, by default, the executable name. */
9
9
  name: string;
10
10
  binary: string;
11
11
  /**
@@ -1 +1 @@
1
- import*as Q from"node:fs";import*as Y from"node:path";import*as V from"node:os";import{fileURLToPath as M}from"node:url";import{spawnSync as U,execFileSync as L}from"node:child_process";import $ from"js-yaml";import{log as K}from"./logger.js";import{EXIT_CODES as v,getErrorMessage as W}from"./errors.js";import{enforceRateLimit as j}from"./rate-limit.js";const O=Y.dirname(M(import.meta.url));function k(){const q=V.homedir();return Y.join(q,".opencli","external-clis.yaml")}let Z=null;export function loadExternalClis(){if(Z)return Z;const q=new Map,z=Y.resolve(O,"external-clis.yaml");try{if(Q.existsSync(z)){const H=Q.readFileSync(z,"utf8"),A=$.load(H)||[];for(const J of A)q.set(J.name,J)}}catch(H){K.warn(`Failed to parse built-in external-clis.yaml: ${W(H)}`)}const G=k();try{if(Q.existsSync(G)){const H=Q.readFileSync(G,"utf8"),A=$.load(H)||[];for(const J of A)q.set(J.name,J)}}catch(H){K.warn(`Failed to parse user external-clis.yaml: ${W(H)}`)}Z=Array.from(q.values()).sort((H,A)=>H.name.localeCompare(A.name));return Z}export function isBinaryInstalled(q){try{const z=V.platform()==="win32";L(z?"where":"which",[q],{stdio:"ignore"});return!0}catch{return!1}}export function getInstallCmd(q){if(!q)return null;const z=V.platform();if(z==="darwin"&&q.mac)return q.mac;if(z==="linux"&&q.linux)return q.linux;if(z==="win32"&&q.windows)return q.windows;if(q.default)return q.default;return null}export function formatExternalCliLabel(q){return q.package&&q.package!==q.name?`${q.name}(${q.package})`:q.name}export function parseCommand(q){if(/&&|\|\|?|;|[><`$#\n\r]|\$\(/.test(q))throw Error(`Install command contains unsafe shell operators and cannot be executed securely: "${q}". Please install the tool manually.`);const G=[],H=/(?:"([^"]*)")|(?:'([^']*)')|(\S+)/g;let A;while((A=H.exec(q))!==null)G.push(A[1]??A[2]??A[3]);if(G.length===0)throw Error("Install command is empty.");const[J,...N]=G;return{binary:J,args:N}}function X(q,z){const G=z instanceof Error?z.code:void 0;return V.platform()==="win32"&&!Y.extname(q)&&G==="ENOENT"}function R(q){const{binary:z,args:G}=parseCommand(q);try{L(z,G,{stdio:"inherit"})}catch(H){if(X(z,H)){L(`${z}.cmd`,G,{stdio:"inherit"});return}throw H}}export function installExternalCli(q){if(!q.install){K.error(`No auto-install command configured for '${q.name}'.`);K.info(`Please install '${q.binary}' manually.`);return!1}const z=getInstallCmd(q.install);if(!z){K.error(`No install command for your platform (${V.platform()}) for '${q.name}'.`);if(q.homepage)K.info(`See: ${q.homepage}`);return!1}K.info(`'${q.name}' is not installed. Auto-installing...`);K.verbose(`$ ${z}`);try{R(z);K.success(`Installed '${q.name}' successfully.`);return!0}catch(G){K.error(`Failed to install '${q.name}': ${W(G)}`);return!1}}export function executeExternalCli(q,z,G){const A=(G??loadExternalClis()).find((N)=>N.name===q);if(!A)throw Error(`External CLI '${q}' not found in registry.`);j(q);if(!isBinaryInstalled(A.binary)){if(!installExternalCli(A)){process.exitCode=v.SERVICE_UNAVAIL;return}}const J=U(A.binary,z,{stdio:"inherit"});if(J.error){K.error(`Failed to execute '${A.binary}': ${J.error.message}`);process.exitCode=v.GENERIC_ERROR;return}if(J.status!==null)process.exitCode=J.status;else if(J.signal){const N=J.signal;process.exitCode=N==="SIGINT"?v.INTERRUPTED:V.constants.signals[N]?128+V.constants.signals[N]:v.GENERIC_ERROR}}export function registerExternalCli(q,z){const G=k(),H=Y.dirname(G);if(!Q.existsSync(H))Q.mkdirSync(H,{recursive:!0});let A=[];if(Q.existsSync(G))try{const B=Q.readFileSync(G,"utf8");A=$.load(B)||[]}catch{}const J=A.findIndex((B)=>B.name===q),N={name:q,binary:z?.binary||q};if(z?.description)N.description=z.description;if(z?.install)N.install={default:z.install};if(J>=0){A[J]={...A[J],...N};K.success(`Updated '${q}' in user registry.`)}else{A.push(N);K.success(`Registered '${q}' in user registry.`)}const F=$.dump(A,{indent:2,sortKeys:!0});Q.writeFileSync(G,F,"utf8");Z=null;K.verbose(G)}
1
+ import*as Q from"node:fs";import*as Y from"node:path";import*as V from"node:os";import{fileURLToPath as M}from"node:url";import{spawnSync as U,execFileSync as L}from"node:child_process";import $ from"js-yaml";import{log as K}from"./logger.js";import{EXIT_CODES as v,getErrorMessage as W}from"./errors.js";import{enforceRateLimit as j}from"./rate-limit.js";const O=Y.dirname(M(import.meta.url));function k(){const q=V.homedir();return Y.join(q,".ppcli","external-clis.yaml")}let Z=null;export function loadExternalClis(){if(Z)return Z;const q=new Map,z=Y.resolve(O,"external-clis.yaml");try{if(Q.existsSync(z)){const H=Q.readFileSync(z,"utf8"),A=$.load(H)||[];for(const J of A)q.set(J.name,J)}}catch(H){K.warn(`Failed to parse built-in external-clis.yaml: ${W(H)}`)}const G=k();try{if(Q.existsSync(G)){const H=Q.readFileSync(G,"utf8"),A=$.load(H)||[];for(const J of A)q.set(J.name,J)}}catch(H){K.warn(`Failed to parse user external-clis.yaml: ${W(H)}`)}Z=Array.from(q.values()).sort((H,A)=>H.name.localeCompare(A.name));return Z}export function isBinaryInstalled(q){try{const z=V.platform()==="win32";L(z?"where":"which",[q],{stdio:"ignore"});return!0}catch{return!1}}export function getInstallCmd(q){if(!q)return null;const z=V.platform();if(z==="darwin"&&q.mac)return q.mac;if(z==="linux"&&q.linux)return q.linux;if(z==="win32"&&q.windows)return q.windows;if(q.default)return q.default;return null}export function formatExternalCliLabel(q){return q.package&&q.package!==q.name?`${q.name}(${q.package})`:q.name}export function parseCommand(q){if(/&&|\|\|?|;|[><`$#\n\r]|\$\(/.test(q))throw Error(`Install command contains unsafe shell operators and cannot be executed securely: "${q}". Please install the tool manually.`);const G=[],H=/(?:"([^"]*)")|(?:'([^']*)')|(\S+)/g;let A;while((A=H.exec(q))!==null)G.push(A[1]??A[2]??A[3]);if(G.length===0)throw Error("Install command is empty.");const[J,...N]=G;return{binary:J,args:N}}function X(q,z){const G=z instanceof Error?z.code:void 0;return V.platform()==="win32"&&!Y.extname(q)&&G==="ENOENT"}function R(q){const{binary:z,args:G}=parseCommand(q);try{L(z,G,{stdio:"inherit"})}catch(H){if(X(z,H)){L(`${z}.cmd`,G,{stdio:"inherit"});return}throw H}}export function installExternalCli(q){if(!q.install){K.error(`No auto-install command configured for '${q.name}'.`);K.info(`Please install '${q.binary}' manually.`);return!1}const z=getInstallCmd(q.install);if(!z){K.error(`No install command for your platform (${V.platform()}) for '${q.name}'.`);if(q.homepage)K.info(`See: ${q.homepage}`);return!1}K.info(`'${q.name}' is not installed. Auto-installing...`);K.verbose(`$ ${z}`);try{R(z);K.success(`Installed '${q.name}' successfully.`);return!0}catch(G){K.error(`Failed to install '${q.name}': ${W(G)}`);return!1}}export function executeExternalCli(q,z,G){const A=(G??loadExternalClis()).find((N)=>N.name===q);if(!A)throw Error(`External CLI '${q}' not found in registry.`);j(q);if(!isBinaryInstalled(A.binary)){if(!installExternalCli(A)){process.exitCode=v.SERVICE_UNAVAIL;return}}const J=U(A.binary,z,{stdio:"inherit"});if(J.error){K.error(`Failed to execute '${A.binary}': ${J.error.message}`);process.exitCode=v.GENERIC_ERROR;return}if(J.status!==null)process.exitCode=J.status;else if(J.signal){const N=J.signal;process.exitCode=N==="SIGINT"?v.INTERRUPTED:V.constants.signals[N]?128+V.constants.signals[N]:v.GENERIC_ERROR}}export function registerExternalCli(q,z){const G=k(),H=Y.dirname(G);if(!Q.existsSync(H))Q.mkdirSync(H,{recursive:!0});let A=[];if(Q.existsSync(G))try{const B=Q.readFileSync(G,"utf8");A=$.load(B)||[]}catch{}const J=A.findIndex((B)=>B.name===q),N={name:q,binary:z?.binary||q};if(z?.description)N.description=z.description;if(z?.install)N.install={default:z.install};if(J>=0){A[J]={...A[J],...N};K.success(`Updated '${q}' in user registry.`)}else{A.push(N);K.success(`Registered '${q}' in user registry.`)}const F=$.dump(A,{indent:2,sortKeys:!0});Q.writeFileSync(G,F,"utf8");Z=null;K.verbose(G)}
@@ -59,7 +59,7 @@ export declare function formatRootAdapterHelpText(groups: RootAdapterGroups): st
59
59
  *
60
60
  * Example: `browser` declares `.usage('<session> <command> [options]')`,
61
61
  * so `commanderPath(browserClickCmd)` becomes
62
- * `['opencli', 'browser', '<session>', 'click']`.
62
+ * `['ppcli', 'browser', '<session>', 'click']`.
63
63
  */
64
64
  export declare function leadingPositionalFromUsage(command: Command): string | null;
65
65
  export declare function commanderNamespaceHelpData(namespaceRoot: Command, opts?: {