publishport-opencli 1.0.11 → 1.0.13

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 (116) hide show
  1. package/cli-manifest.json +1105 -181
  2. package/clis/antigravity/send.js +1 -1
  3. package/clis/bilibili/comment.js +1 -1
  4. package/clis/bilibili/dm-send.js +1 -1
  5. package/clis/bilibili/dynamic-post.js +1 -1
  6. package/clis/bilibili/upload.js +1 -1
  7. package/clis/bluesky/post.js +1 -1
  8. package/clis/boss/batchgreet.js +1 -1
  9. package/clis/boss/greet.js +1 -1
  10. package/clis/boss/send.js +1 -1
  11. package/clis/chatgpt/ask.js +1 -1
  12. package/clis/chatgpt/image.js +1 -1
  13. package/clis/chatgpt/send.js +1 -1
  14. package/clis/chatgpt-app/ask.js +1 -1
  15. package/clis/chatgpt-app/send.js +1 -1
  16. package/clis/chatwise/ask.js +1 -1
  17. package/clis/chatwise/send.js +1 -1
  18. package/clis/claude/ask.js +1 -1
  19. package/clis/claude/send.js +1 -1
  20. package/clis/codex/ask.js +1 -1
  21. package/clis/codex/send.js +1 -1
  22. package/clis/cursor/ask.js +1 -1
  23. package/clis/cursor/composer.js +1 -1
  24. package/clis/cursor/send.js +1 -1
  25. package/clis/deepseek/ask.js +1 -1
  26. package/clis/deepseek/send.js +1 -1
  27. package/clis/discord-web/send.js +1 -1
  28. package/clis/doubao/ask.js +1 -1
  29. package/clis/doubao-app/ask.js +1 -1
  30. package/clis/doubao-app/send.js +1 -1
  31. package/clis/douyin/draft.js +1 -1
  32. package/clis/douyin/publish.js +1 -1
  33. package/clis/douyin/update.js +1 -1
  34. package/clis/facebook/comment.js +1 -1
  35. package/clis/facebook/post.js +1 -1
  36. package/clis/gemini/ask.js +1 -1
  37. package/clis/gemini/deep-research.js +1 -1
  38. package/clis/gemini/image.js +2 -2
  39. package/clis/grok/ask.js +1 -1
  40. package/clis/grok/image.js +1 -1
  41. package/clis/grok/send.js +1 -1
  42. package/clis/hackernews/comment.js +1 -1
  43. package/clis/hackernews/submit.js +1 -1
  44. package/clis/instagram/comment.js +1 -1
  45. package/clis/instagram/note.js +1 -1
  46. package/clis/instagram/post.js +1 -1
  47. package/clis/instagram/reel.js +1 -1
  48. package/clis/jike/comment.js +1 -1
  49. package/clis/jike/create.js +1 -1
  50. package/clis/jike/repost.js +1 -1
  51. package/clis/jimeng/generate.js +1 -1
  52. package/clis/kimi/chat.js +1 -1
  53. package/clis/kuaishou/publish.js +1 -1
  54. package/clis/linkedin/comment.js +1 -1
  55. package/clis/linkedin/create.js +1 -1
  56. package/clis/linkedin/reply.js +1 -1
  57. package/clis/linkedin/repost.js +1 -1
  58. package/clis/linkedin/safe-send.js +1 -1
  59. package/clis/linkedin/salesnav-message.js +2 -2
  60. package/clis/note/publish.js +1 -1
  61. package/clis/notebooklm/add-source.js +1 -1
  62. package/clis/notebooklm/write-note.js +1 -1
  63. package/clis/nowcoder/comment.js +1 -1
  64. package/clis/nowcoder/dm-send.js +1 -1
  65. package/clis/nowcoder/post.js +1 -1
  66. package/clis/pinterest/publish.js +1 -1
  67. package/clis/qoder/quest.js +1 -1
  68. package/clis/quora/comment.js +1 -1
  69. package/clis/qwen/ask.js +1 -1
  70. package/clis/qwen/image.js +1 -1
  71. package/clis/qwen/send.js +1 -1
  72. package/clis/reddit/comment.js +1 -1
  73. package/clis/reddit/reply.js +1 -1
  74. package/clis/reddit/submit.js +1 -1
  75. package/clis/slock/message-send.js +1 -1
  76. package/clis/substack/publish.js +1 -1
  77. package/clis/suno/generate.js +1 -1
  78. package/clis/telegram/send.js +1 -1
  79. package/clis/threads/post.js +1 -1
  80. package/clis/tiktok/comment.js +1 -1
  81. package/clis/trae-cn/ask.js +1 -1
  82. package/clis/trae-cn/new.js +1 -1
  83. package/clis/twitter/dm-send.js +1 -1
  84. package/clis/twitter/post.js +1 -1
  85. package/clis/twitter/quote.js +1 -1
  86. package/clis/twitter/reply-dm.js +1 -1
  87. package/clis/twitter/schedule.js +1 -1
  88. package/clis/vk/post.js +1 -1
  89. package/clis/wechat-channels/publish.js +1 -1
  90. package/clis/weixin/create-draft.js +1 -1
  91. package/clis/xianyu/chat.js +1 -1
  92. package/clis/xianyu/item-stats.js +2 -0
  93. package/clis/xianyu/manage.js +1 -1
  94. package/clis/xianyu/order.js +1 -1
  95. package/clis/xianyu/promote-stats.js +2 -0
  96. package/clis/xianyu/publish.js +1 -1
  97. package/clis/xianyu/reply.js +1 -1
  98. package/clis/xianyu/seller-data.js +1 -0
  99. package/clis/xianyu/seller-list.js +1 -0
  100. package/clis/yollomi/background.js +1 -1
  101. package/clis/yollomi/edit.js +1 -1
  102. package/clis/yollomi/generate.js +1 -1
  103. package/clis/yollomi/video.js +1 -1
  104. package/clis/youtube/publish.js +1 -1
  105. package/clis/yuanbao/ask.js +1 -1
  106. package/clis/yuanbao/send.js +1 -1
  107. package/dist/src/build-manifest.js +3 -3
  108. package/dist/src/cli-argv-preprocess.d.ts +1 -0
  109. package/dist/src/cli-argv-preprocess.js +1 -1
  110. package/dist/src/execution.d.ts +8 -0
  111. package/dist/src/execution.js +2 -2
  112. package/dist/src/manifest-types.d.ts +5 -0
  113. package/dist/src/registry.d.ts +15 -0
  114. package/dist/src/registry.js +1 -1
  115. package/dist/src/serialization.js +2 -2
  116. package/package.json +1 -1
@@ -1 +1 @@
1
- import*as w from"node:path";import{cli as b,Strategy as I}from"@jackwener/opencli/registry";import{CliError as c}from"@jackwener/opencli/errors";import{log as f}from"@jackwener/opencli/logger";import{YOLLOMI_DOMAIN as U,yollomiPost as z,downloadOutput as D,fmtBytes as E,MODEL_ROUTES as L}from"./utils.js";function P(p){const e={"1:1":[1024,1024],"16:9":[1344,768],"9:16":[768,1344],"4:3":[1152,896],"3:4":[896,1152]},[n,t]=e[p]||[1024,1024];return{width:n,height:t}}b({site:"yollomi",name:"generate",access:"write",description:"Generate images with AI (text-to-image or image-to-image)",domain:U,strategy:I.COOKIE,args:[{name:"prompt",positional:!0,required:!0,help:"Text prompt describing the image"},{name:"model",default:"z-image-turbo",help:"Model ID (z-image-turbo, flux-schnell, nano-banana, flux-2-pro, ...)"},{name:"ratio",default:"1:1",choices:["1:1","16:9","9:16","4:3","3:4"],help:"Aspect ratio"},{name:"image",help:'Input image URL for image-to-image (upload via "ppcli yollomi upload" first)'},{name:"output",default:"./yollomi-output",help:"Output directory"},{name:"no-download",type:"boolean",default:!1,help:"Only show URLs, skip download"}],columns:["index","status","file","size","url"],func:async(p,e)=>{const{prompt:n,model:t,ratio:l}=e,d=L[t];if(!d)throw new c("INVALID_MODEL",`Unknown model: ${t}`,'Run "ppcli yollomi models --type image" to see available models');let i;if(t==="z-image-turbo"){const{width:o,height:a}=P(l);i={prompt:n,width:o,height:a,output_format:"jpg",output_quality:85,guidance_scale:0,num_inference_steps:8}}else if(t==="flux-2-pro"){i={prompt:n,aspectRatio:l,outputNumber:1};if(e.image)i.imageUrl=e.image}else if(t==="flux-kontext-pro"){i={prompt:n,output_format:"jpg"};if(e.image)i.imageUrl=e.image;if(l!=="1:1")i.aspect_ratio=l}else{i={prompt:n,aspect_ratio:l};if(e.image)i.imageUrl=e.image}f.status(`Generating with ${t}...`);const r=await z(p,d,i),u=r.images||(r.image?[r.image]:[]);if(!u.length)throw new c("EMPTY_RESPONSE","No images returned","Try a different prompt or model");const g=e["no-download"],h=e.output,m=[];for(let o=0;o<u.length;o++){const a=u[o];if(g){m.push({index:o+1,status:"generated",file:"-",size:"-",url:a});continue}try{const s=(()=>{try{return new URL(a).pathname}catch{return a}})(),y=s.endsWith(".png")||s.endsWith(".webp")?s.slice(s.lastIndexOf(".")):".jpg",x=`yollomi_${t}_${Date.now()}_${o+1}${y}`,{path:O,size:_}=await D(a,h,x);m.push({index:o+1,status:"saved",file:w.relative(".",O),size:E(_),url:a})}catch{m.push({index:o+1,status:"download-failed",file:"-",size:"-",url:a})}}if(r.remainingCredits!==void 0)f.status(`Credits remaining: ${r.remainingCredits}`);return m}});
1
+ import*as w from"node:path";import{cli as b,Strategy as I}from"@jackwener/opencli/registry";import{CliError as c}from"@jackwener/opencli/errors";import{log as f}from"@jackwener/opencli/logger";import{YOLLOMI_DOMAIN as U,yollomiPost as z,downloadOutput as D,fmtBytes as E,MODEL_ROUTES as L}from"./utils.js";function P(p){const e={"1:1":[1024,1024],"16:9":[1344,768],"9:16":[768,1344],"4:3":[1152,896],"3:4":[896,1152]},[n,t]=e[p]||[1024,1024];return{width:n,height:t}}b({site:"yollomi",name:"generate",access:"write",description:"Generate images with AI (text-to-image or image-to-image)",domain:U,strategy:I.COOKIE,args:[{name:"prompt",fromFile:!0,positional:!0,required:!0,help:"Text prompt describing the image"},{name:"model",default:"z-image-turbo",help:"Model ID (z-image-turbo, flux-schnell, nano-banana, flux-2-pro, ...)"},{name:"ratio",default:"1:1",choices:["1:1","16:9","9:16","4:3","3:4"],help:"Aspect ratio"},{name:"image",help:'Input image URL for image-to-image (upload via "ppcli yollomi upload" first)'},{name:"output",default:"./yollomi-output",help:"Output directory"},{name:"no-download",type:"boolean",default:!1,help:"Only show URLs, skip download"}],columns:["index","status","file","size","url"],func:async(p,e)=>{const{prompt:n,model:t,ratio:l}=e,d=L[t];if(!d)throw new c("INVALID_MODEL",`Unknown model: ${t}`,'Run "ppcli yollomi models --type image" to see available models');let i;if(t==="z-image-turbo"){const{width:o,height:a}=P(l);i={prompt:n,width:o,height:a,output_format:"jpg",output_quality:85,guidance_scale:0,num_inference_steps:8}}else if(t==="flux-2-pro"){i={prompt:n,aspectRatio:l,outputNumber:1};if(e.image)i.imageUrl=e.image}else if(t==="flux-kontext-pro"){i={prompt:n,output_format:"jpg"};if(e.image)i.imageUrl=e.image;if(l!=="1:1")i.aspect_ratio=l}else{i={prompt:n,aspect_ratio:l};if(e.image)i.imageUrl=e.image}f.status(`Generating with ${t}...`);const r=await z(p,d,i),u=r.images||(r.image?[r.image]:[]);if(!u.length)throw new c("EMPTY_RESPONSE","No images returned","Try a different prompt or model");const g=e["no-download"],h=e.output,m=[];for(let o=0;o<u.length;o++){const a=u[o];if(g){m.push({index:o+1,status:"generated",file:"-",size:"-",url:a});continue}try{const s=(()=>{try{return new URL(a).pathname}catch{return a}})(),y=s.endsWith(".png")||s.endsWith(".webp")?s.slice(s.lastIndexOf(".")):".jpg",x=`yollomi_${t}_${Date.now()}_${o+1}${y}`,{path:O,size:_}=await D(a,h,x);m.push({index:o+1,status:"saved",file:w.relative(".",O),size:E(_),url:a})}catch{m.push({index:o+1,status:"download-failed",file:"-",size:"-",url:a})}}if(r.remainingCredits!==void 0)f.status(`Credits remaining: ${r.remainingCredits}`);return m}});
@@ -1 +1 @@
1
- import*as h from"node:path";import{cli as v,Strategy as g}from"@jackwener/opencli/registry";import{CliError as y}from"@jackwener/opencli/errors";import{log as n}from"@jackwener/opencli/logger";import{YOLLOMI_DOMAIN as w,yollomiPost as O,downloadOutput as I,fmtBytes as z}from"./utils.js";v({site:"yollomi",name:"video",access:"write",description:"Generate videos with AI (text-to-video or image-to-video)",domain:w,strategy:g.COOKIE,args:[{name:"prompt",positional:!0,required:!0,help:"Text prompt describing the video"},{name:"model",default:"kling-2-1",help:"Model (kling-2-1, openai-sora-2, google-veo-3-1, wan-2-5-t2v, ...)"},{name:"image",help:"Input image URL for image-to-video"},{name:"ratio",default:"16:9",choices:["1:1","16:9","9:16","4:3","3:4"],help:"Aspect ratio"},{name:"output",default:"./yollomi-output",help:"Output directory"},{name:"no-download",type:"boolean",default:!1,help:"Only show URL, skip download"}],columns:["status","file","size","credits","url"],func:async(d,e)=>{const{prompt:l,model:i}=e,a={aspect_ratio:e.ratio};if(e.image)a.image=e.image;const s={modelId:i,prompt:l,inputs:a};n.status(`Generating video with ${i} (may take a while)...`);const r=await O(d,"/api/ai/video",s),t=r.video||"";if(!t)throw new y("EMPTY_RESPONSE","No video returned","Try a different prompt or model");const o=r.remainingCredits,m=e["no-download"],p=e.output;if(m)return[{status:"generated",file:"-",size:"-",credits:o??"-",url:t}];try{const u=`yollomi_${i}_${Date.now()}.mp4`,{path:c,size:f}=await I(t,p,u);if(o!==void 0)n.status(`Credits remaining: ${o}`);return[{status:"saved",file:h.relative(".",c),size:z(f),credits:o??"-",url:t}]}catch{return[{status:"download-failed",file:"-",size:"-",credits:o??"-",url:t}]}}});
1
+ import*as h from"node:path";import{cli as v,Strategy as g}from"@jackwener/opencli/registry";import{CliError as y}from"@jackwener/opencli/errors";import{log as n}from"@jackwener/opencli/logger";import{YOLLOMI_DOMAIN as w,yollomiPost as O,downloadOutput as I,fmtBytes as z}from"./utils.js";v({site:"yollomi",name:"video",access:"write",description:"Generate videos with AI (text-to-video or image-to-video)",domain:w,strategy:g.COOKIE,args:[{name:"prompt",fromFile:!0,positional:!0,required:!0,help:"Text prompt describing the video"},{name:"model",default:"kling-2-1",help:"Model (kling-2-1, openai-sora-2, google-veo-3-1, wan-2-5-t2v, ...)"},{name:"image",help:"Input image URL for image-to-video"},{name:"ratio",default:"16:9",choices:["1:1","16:9","9:16","4:3","3:4"],help:"Aspect ratio"},{name:"output",default:"./yollomi-output",help:"Output directory"},{name:"no-download",type:"boolean",default:!1,help:"Only show URL, skip download"}],columns:["status","file","size","credits","url"],func:async(d,e)=>{const{prompt:l,model:i}=e,a={aspect_ratio:e.ratio};if(e.image)a.image=e.image;const s={modelId:i,prompt:l,inputs:a};n.status(`Generating video with ${i} (may take a while)...`);const r=await O(d,"/api/ai/video",s),t=r.video||"";if(!t)throw new y("EMPTY_RESPONSE","No video returned","Try a different prompt or model");const o=r.remainingCredits,m=e["no-download"],p=e.output;if(m)return[{status:"generated",file:"-",size:"-",credits:o??"-",url:t}];try{const u=`yollomi_${i}_${Date.now()}.mp4`,{path:c,size:f}=await I(t,p,u);if(o!==void 0)n.status(`Credits remaining: ${o}`);return[{status:"saved",file:h.relative(".",c),size:z(f),credits:o??"-",url:t}]}catch{return[{status:"download-failed",file:"-",size:"-",credits:o??"-",url:t}]}}});
@@ -48,7 +48,7 @@ import{cli as U,Strategy as _}from"@jackwener/opencli/registry";import{ArgumentE
48
48
  }
49
49
  return '';
50
50
  })()
51
- `))||"");if(W&&H.test(W))return!0;await q.wait({time:5})}return!1}U({site:"youtube",name:"publish",access:"write",description:"Publish a video to YouTube via Studio (one command: upload + metadata + publish)",domain:"www.youtube.com",strategy:_.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"video",required:!0,positional:!0,help:"Video file path"},{name:"title",required:!0,help:"Video title (≤100 chars)"},{name:"description",default:"",help:"Video description"},{name:"tags",default:"",help:"Comma-separated tags"},{name:"thumbnail",default:"",help:"Thumbnail image path (optional)"},{name:"playlist",default:"",help:"Playlist name to add to (optional)"},{name:"visibility",default:"public",choices:["public","unlisted","private"],help:"Visibility"}],columns:["status","title","visibility","url"],func:async(q,z)=>{const H=String(z.title??"").trim();if(!H)throw new C("--title is required");const Q=h(z.video),W=String(z.description||""),Y=z.thumbnail?y(z.thumbnail):"",$=String(z.playlist||"").trim(),j=O[z.visibility]?z.visibility:"public";await q.goto(F);await q.wait({time:3});const A=await v(q);if(/accounts\.google\.com/.test(A)||/signin/i.test(A))throw new X("YouTube 登录态失效,请先 ppcli youtube login");if(!await D(q,'input[type="file"]',60000))throw new X("YouTube 上传文件输入框未出现");await B(q,['input[type="file"]'],[Q]);if(!await D(q,"#title-textarea",120000))throw new X("YouTube 视频详情对话框未出现");await R(q,"#title-textarea #textbox",H.slice(0,100));if(W.trim())await R(q,"#description-textarea #textbox",W);if(Y)try{await B(q,["#file-loader input[type='file']","ytcp-thumbnail-uploader input[type='file']"],[Y]);await q.wait({time:2})}catch{}if($)try{await G(q,"#basics ytcp-text-dropdown-trigger, ytcp-video-metadata-playlists ytcp-dropdown-trigger",8000);await q.wait({time:1.2});const K=`tp-yt-paper-checkbox:has-text('${$}'), ytcp-checkbox-group:has-text('${$}')`;if(await M(q,K))await G(q,K,3000);else if(await G(q,"ytcp-button:has-text('New playlist'), ytcp-button:has-text('创建播放列表')",4000)){await q.wait({time:0.8});await G(q,"tp-yt-paper-item:has-text('New playlist'), tp-yt-paper-item:has-text('新建播放列表')",3000);const J="ytcp-playlist-metadata-editor #textbox, #create-playlist-form #textbox";if(await D(q,J.split(",")[0].trim(),4000)||await M(q,J)){await G(q,J,2000);await q.insertText($);await G(q,"ytcp-button#create-button, tp-yt-paper-dialog ytcp-button:has-text('Create'), tp-yt-paper-dialog ytcp-button:has-text('创建')",4000)}}}catch{}finally{await G(q,"ytcp-playlist-dialog #save-button, ytcp-button:has-text('Done'), ytcp-button:has-text('完成')",3000);await q.pressKey("Escape");await q.wait({time:0.6})}if(!await G(q,"tp-yt-paper-radio-button[name='VIDEO_MADE_FOR_KIDS_NOT_MFK']",1e4))await G(q,"tp-yt-paper-radio-button:has-text('not made for kids'), tp-yt-paper-radio-button:has-text('不是面向儿童')",6000);if(z.tags){const K=String(z.tags).split(",").map((J)=>J.trim()).filter(Boolean).join(",");try{await G(q,"#toggle-button",6000);await q.wait({time:0.8});const J="#tags-container #text-input, ytcp-form-input-container#tags-container input";if(await M(q,J)){await G(q,J,3000);await q.insertText(`${K.slice(0,500)},`)}}catch{}}for(let K=0;K<5;K++){if(await M(q,"tp-yt-paper-radio-button[name='PUBLIC']"))break;if(!await G(q,"#next-button",6000))await q.wait({time:1.2});await q.wait({time:1})}await G(q,`tp-yt-paper-radio-button[name='${O[j]}']`,1e4);await L(q);await q.wait({time:1.2});let N="";if(!await G(q,"#done-button",15000))throw new X("未找到 YouTube 发布按钮(上传可能未到可发布进度),请在窗口内手动发布");await q.wait({time:4});N=String(Z(await q.evaluate(`
51
+ `))||"");if(W&&H.test(W))return!0;await q.wait({time:5})}return!1}U({site:"youtube",name:"publish",access:"write",description:"Publish a video to YouTube via Studio (one command: upload + metadata + publish)",domain:"www.youtube.com",strategy:_.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"video",required:!0,positional:!0,help:"Video file path"},{name:"title",required:!0,help:"Video title (≤100 chars)"},{name:"description",fromFile:!0,default:"",help:"Video description"},{name:"tags",default:"",help:"Comma-separated tags"},{name:"thumbnail",default:"",help:"Thumbnail image path (optional)"},{name:"playlist",default:"",help:"Playlist name to add to (optional)"},{name:"visibility",default:"public",choices:["public","unlisted","private"],help:"Visibility"}],columns:["status","title","visibility","url"],func:async(q,z)=>{const H=String(z.title??"").trim();if(!H)throw new C("--title is required");const Q=h(z.video),W=String(z.description||""),Y=z.thumbnail?y(z.thumbnail):"",$=String(z.playlist||"").trim(),j=O[z.visibility]?z.visibility:"public";await q.goto(F);await q.wait({time:3});const A=await v(q);if(/accounts\.google\.com/.test(A)||/signin/i.test(A))throw new X("YouTube 登录态失效,请先 ppcli youtube login");if(!await D(q,'input[type="file"]',60000))throw new X("YouTube 上传文件输入框未出现");await B(q,['input[type="file"]'],[Q]);if(!await D(q,"#title-textarea",120000))throw new X("YouTube 视频详情对话框未出现");await R(q,"#title-textarea #textbox",H.slice(0,100));if(W.trim())await R(q,"#description-textarea #textbox",W);if(Y)try{await B(q,["#file-loader input[type='file']","ytcp-thumbnail-uploader input[type='file']"],[Y]);await q.wait({time:2})}catch{}if($)try{await G(q,"#basics ytcp-text-dropdown-trigger, ytcp-video-metadata-playlists ytcp-dropdown-trigger",8000);await q.wait({time:1.2});const K=`tp-yt-paper-checkbox:has-text('${$}'), ytcp-checkbox-group:has-text('${$}')`;if(await M(q,K))await G(q,K,3000);else if(await G(q,"ytcp-button:has-text('New playlist'), ytcp-button:has-text('创建播放列表')",4000)){await q.wait({time:0.8});await G(q,"tp-yt-paper-item:has-text('New playlist'), tp-yt-paper-item:has-text('新建播放列表')",3000);const J="ytcp-playlist-metadata-editor #textbox, #create-playlist-form #textbox";if(await D(q,J.split(",")[0].trim(),4000)||await M(q,J)){await G(q,J,2000);await q.insertText($);await G(q,"ytcp-button#create-button, tp-yt-paper-dialog ytcp-button:has-text('Create'), tp-yt-paper-dialog ytcp-button:has-text('创建')",4000)}}}catch{}finally{await G(q,"ytcp-playlist-dialog #save-button, ytcp-button:has-text('Done'), ytcp-button:has-text('完成')",3000);await q.pressKey("Escape");await q.wait({time:0.6})}if(!await G(q,"tp-yt-paper-radio-button[name='VIDEO_MADE_FOR_KIDS_NOT_MFK']",1e4))await G(q,"tp-yt-paper-radio-button:has-text('not made for kids'), tp-yt-paper-radio-button:has-text('不是面向儿童')",6000);if(z.tags){const K=String(z.tags).split(",").map((J)=>J.trim()).filter(Boolean).join(",");try{await G(q,"#toggle-button",6000);await q.wait({time:0.8});const J="#tags-container #text-input, ytcp-form-input-container#tags-container input";if(await M(q,J)){await G(q,J,3000);await q.insertText(`${K.slice(0,500)},`)}}catch{}}for(let K=0;K<5;K++){if(await M(q,"tp-yt-paper-radio-button[name='PUBLIC']"))break;if(!await G(q,"#next-button",6000))await q.wait({time:1.2});await q.wait({time:1})}await G(q,`tp-yt-paper-radio-button[name='${O[j]}']`,1e4);await L(q);await q.wait({time:1.2});let N="";if(!await G(q,"#done-button",15000))throw new X("未找到 YouTube 发布按钮(上传可能未到可发布进度),请在窗口内手动发布");await q.wait({time:4});N=String(Z(await q.evaluate(`
52
52
  (() => {
53
53
  const a = document.querySelector("a[href*='youtu.be'], a[href*='watch?v=']");
54
54
  return a ? (a.getAttribute('href') || '') : '';
@@ -142,4 +142,4 @@ ${q}
142
142
  .find((node) => isVisible(node));
143
143
 
144
144
  if (button instanceof HTMLElement) button.click();
145
- })()`);await j.wait(0.5)}async function c(j,y,q,F,G){const J=Date.now();let W="",K=0,V="";while(Date.now()-J<G*1000){await j.wait(L);if(await P(j))return"blocked";const X=await E(j),$=pickLatestYuanbaoAssistantCandidate(X,y,F)||collectYuanbaoTranscriptAdditions(q,await B(j),F);if(!$)continue;V=$;const D=updateStableState(W,K,$);W=D.previousText;K=D.stableCount;if(Date.now()-J>=w&&K>=C)return $}return V||null}export const askCommand=N({site:"yuanbao",name:"ask",access:"write",description:"Send a prompt to Yuanbao web chat and wait for the assistant response",domain:Y,strategy:U.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,defaultFormat:"plain",args:[{name:"prompt",required:!0,positional:!0,help:"Prompt to send"},{name:"timeout",type:"int",required:!1,help:"Max seconds to wait (default: 60)",default:60},{name:"search",type:"boolean",required:!1,help:"Enable Yuanbao internet search (default: true)",default:!0},{name:"think",type:"boolean",required:!1,help:"Enable Yuanbao deep thinking (default: false)",default:!1}],columns:["Role","Text"],func:async(j,y)=>{const{prompt:q,timeout:F}=y;if(!Number.isInteger(F)||F<1)throw new _("--timeout must be a positive integer (seconds)");const G=Q(y.search,!0),J=Q(y.think,!1);await M(j);if(await P(j))throw H("Yuanbao opened a login gate before sending the prompt.");await v(j,G);await T(j,J);const W=await E(j),K=await B(j),V=await k(j,q);if(!V?.ok){if(await P(j))throw H("Yuanbao opened a login gate instead of accepting the prompt.");throw S(V?.reason,V?.detail)}const X=await c(j,W.length,K,q,F);if(X==="blocked")throw H("Yuanbao opened a login gate instead of returning a chat response.");if(!X)throw new R("yuanbao ask",F,"No Yuanbao response was observed before the timeout. Retry with --timeout, and verify the current browser session is still interactive.");return[{Role:"User",Text:q},{Role:"Assistant",Text:X}]}}),__test__={collectYuanbaoTranscriptAdditions,convertYuanbaoHtmlToMarkdown,isOnYuanbao:z,normalizeBooleanFlag:Q,pickLatestYuanbaoAssistantCandidate,sanitizeYuanbaoResponseText,updateStableState};
145
+ })()`);await j.wait(0.5)}async function c(j,y,q,F,G){const J=Date.now();let W="",K=0,V="";while(Date.now()-J<G*1000){await j.wait(L);if(await P(j))return"blocked";const X=await E(j),$=pickLatestYuanbaoAssistantCandidate(X,y,F)||collectYuanbaoTranscriptAdditions(q,await B(j),F);if(!$)continue;V=$;const D=updateStableState(W,K,$);W=D.previousText;K=D.stableCount;if(Date.now()-J>=w&&K>=C)return $}return V||null}export const askCommand=N({site:"yuanbao",name:"ask",access:"write",description:"Send a prompt to Yuanbao web chat and wait for the assistant response",domain:Y,strategy:U.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,defaultFormat:"plain",args:[{name:"prompt",fromFile:!0,required:!0,positional:!0,help:"Prompt to send"},{name:"timeout",type:"int",required:!1,help:"Max seconds to wait (default: 60)",default:60},{name:"search",type:"boolean",required:!1,help:"Enable Yuanbao internet search (default: true)",default:!0},{name:"think",type:"boolean",required:!1,help:"Enable Yuanbao deep thinking (default: false)",default:!1}],columns:["Role","Text"],func:async(j,y)=>{const{prompt:q,timeout:F}=y;if(!Number.isInteger(F)||F<1)throw new _("--timeout must be a positive integer (seconds)");const G=Q(y.search,!0),J=Q(y.think,!1);await M(j);if(await P(j))throw H("Yuanbao opened a login gate before sending the prompt.");await v(j,G);await T(j,J);const W=await E(j),K=await B(j),V=await k(j,q);if(!V?.ok){if(await P(j))throw H("Yuanbao opened a login gate instead of accepting the prompt.");throw S(V?.reason,V?.detail)}const X=await c(j,W.length,K,q,F);if(X==="blocked")throw H("Yuanbao opened a login gate instead of returning a chat response.");if(!X)throw new R("yuanbao ask",F,"No Yuanbao response was observed before the timeout. Retry with --timeout, and verify the current browser session is still interactive.");return[{Role:"User",Text:q},{Role:"Assistant",Text:X}]}}),__test__={collectYuanbaoTranscriptAdditions,convertYuanbaoHtmlToMarkdown,isOnYuanbao:z,normalizeBooleanFlag:Q,pickLatestYuanbaoAssistantCandidate,sanitizeYuanbaoResponseText,updateStableState};
@@ -1 +1 @@
1
- import{cli as s,Strategy as p}from"@jackwener/opencli/registry";import{ArgumentError as u,CommandExecutionError as m}from"@jackwener/opencli/errors";import{YUANBAO_DOMAIN as d,authRequired as o,ensureYuanbaoPage as l,hasLoginGate as r,normalizeBooleanFlag as c,sendYuanbaoMessage as w,startNewYuanbaoChat as f}from"./shared.js";s({site:"yuanbao",name:"send",access:"write",description:"Fire-and-forget: send a prompt to Yuanbao without waiting for the reply",domain:d,strategy:p.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,args:[{name:"prompt",positional:!0,required:!0,help:"Prompt to send to Yuanbao"},{name:"new",type:"boolean",default:!1,help:"Start a new chat before sending"}],columns:["Status","Prompt"],func:async(t,n)=>{const a=String(n.prompt||"").trim();if(!a)throw new u("prompt","is required");const i=c(n.new,!1);await l(t);if(await r(t))throw o("Yuanbao opened a login gate before sending the prompt.");if(i){if(await f(t)==="blocked")throw o("Yuanbao opened a login gate while starting a new chat.")}const e=await w(t,a);if(!e?.ok){if(await r(t))throw o("Yuanbao opened a login gate instead of accepting the prompt.");throw new m(e?.reason||"Failed to send Yuanbao prompt",e?.detail?`Detail: ${e.detail}`:"Make sure the Yuanbao chat composer is visible and not in a disabled state.")}return[{Status:"sent",Prompt:a}]}});
1
+ import{cli as s,Strategy as p}from"@jackwener/opencli/registry";import{ArgumentError as u,CommandExecutionError as m}from"@jackwener/opencli/errors";import{YUANBAO_DOMAIN as d,authRequired as o,ensureYuanbaoPage as l,hasLoginGate as r,normalizeBooleanFlag as c,sendYuanbaoMessage as f,startNewYuanbaoChat as w}from"./shared.js";s({site:"yuanbao",name:"send",access:"write",description:"Fire-and-forget: send a prompt to Yuanbao without waiting for the reply",domain:d,strategy:p.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,args:[{name:"prompt",fromFile:!0,positional:!0,required:!0,help:"Prompt to send to Yuanbao"},{name:"new",type:"boolean",default:!1,help:"Start a new chat before sending"}],columns:["Status","Prompt"],func:async(e,n)=>{const a=String(n.prompt||"").trim();if(!a)throw new u("prompt","is required");const i=c(n.new,!1);await l(e);if(await r(e))throw o("Yuanbao opened a login gate before sending the prompt.");if(i){if(await w(e)==="blocked")throw o("Yuanbao opened a login gate while starting a new chat.")}const t=await f(e,a);if(!t?.ok){if(await r(e))throw o("Yuanbao opened a login gate instead of accepting the prompt.");throw new m(t?.reason||"Failed to send Yuanbao prompt",t?.detail?`Detail: ${t.detail}`:"Make sure the Yuanbao chat composer is visible and not in a disabled state.")}return[{Status:"sent",Prompt:a}]}});
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import*as V from"node:fs";import*as $ from"node:path";import{fileURLToPath as A,pathToFileURL as L}from"node:url";import{getErrorMessage as O}from"./errors.js";import{fullName as C,getRegistry as b}from"./registry.js";import{findPackageRoot as k,getCliManifestPath as E}from"./package-paths.js";import{isRecord as U}from"./utils.js";const j=k(A(import.meta.url)),w=$.join(j,"clis"),H=E(w),I=/\bcli\s*\(|\bregisterSiteAuthCommands\s*\(|\bregisterArticleLogin\s*\(|\bregisterTokenAuth\s*\(|\bmake[A-Z]\w*Command\s*\(/;export class ManifestImportError extends Error{filePath;cause;constructor(q,J){super(`failed to scan ${q}: ${O(J)}`);this.filePath=q;this.cause=J;this.name="ManifestImportError"}}function T(q){return q.map((J)=>({name:J.name,type:J.type??"str",default:J.default,required:!!J.required,valueRequired:!!J.valueRequired||void 0,positional:J.positional||void 0,help:J.help??"",choices:J.choices}))}function D(q,J){const W=$.basename(q,$.extname(q));return`${J}/${W}.js`}export function normalizeManifestPath(q){return q.replace(/\\/g,"/")}function M(q,J){return normalizeManifestPath($.relative(J,q))}function y(q,J){return U(q)&&typeof q.site==="string"&&q.site===J&&typeof q.name==="string"&&(q.access==="read"||q.access==="write")&&Array.isArray(q.args)}function R(q,J,W){return{site:q.site,name:q.name,aliases:q.aliases,description:q.description??"",access:q.access,example:q.example,domain:q.domain,strategy:(q.strategy??"public").toString().toLowerCase(),browser:q.browser??!0,args:T(q.args),columns:q.columns,defaultFormat:q.defaultFormat,type:"js",modulePath:J,sourceFile:W,navigateBefore:q.navigateBefore,siteSession:q.siteSession,defaultWindowMode:q.defaultWindowMode}}export async function loadManifestEntries(q,J,W=(G)=>import(G),X=w){let G;try{G=V.readFileSync(q,"utf-8")}catch(Q){throw new ManifestImportError(q,Q)}if(!I.test(G))return[];try{const Q=D(q,J),Y=b(),Z=new Map(Y.entries()),F=await W(L(q).href),z=Object.values(U(F)?F:{}).filter((B)=>y(B,J)),N=z.length>0?z:[...Y.entries()].filter(([B,S])=>{if(S.site!==J)return!1;const x=Z.get(B);return!x||x!==S}).map(([,B])=>B),K=M(q,X),_=new Set;return N.filter((B)=>{const S=C(B);if(_.has(S))return!1;_.add(S);return!0}).sort((B,S)=>B.name.localeCompare(S.name)).map((B)=>R(B,Q,K))}catch(Q){throw new ManifestImportError(q,Q)}}export async function scanClisDir(q,J=(W)=>import(W)){const W=new Map,X=[];if(!V.existsSync(q))return{entries:[],failures:X};for(const Q of V.readdirSync(q)){const Y=$.join(q,Q);if(!V.statSync(Y).isDirectory())continue;for(const Z of V.readdirSync(Y))if(Z.endsWith(".js")&&!Z.endsWith(".d.js")&&!Z.endsWith(".test.js")&&Z!=="index.js"){const F=$.join(Y,Z);try{const z=await loadManifestEntries(F,Q,J,q);for(const N of z){const K=`${N.site}/${N.name}`;W.set(K,N)}}catch(z){if(z instanceof ManifestImportError){X.push(z);continue}throw z}}}return{entries:[...W.values()].sort((Q,Y)=>Q.site.localeCompare(Y.site)||Q.name.localeCompare(Y.name)),failures:X}}export async function buildManifest(){return scanClisDir(w)}export function serializeManifest(q){return`${JSON.stringify(q,null,2)}
3
- `}export function findManifestMetadataIssues(q){const J=[];for(const W of q){if(!Array.isArray(W.args))continue;for(const X of W.args){if(!X.positional)continue;if((typeof X.help==="string"?X.help.trim():"")==="")J.push({site:W.site,command:W.name,arg:X.name,sourceFile:W.sourceFile,reason:"positional arg missing non-empty `help` text"})}}return J}export function diffRemovedEntries(q,J){const W=new Set(J.map((X)=>`${X.site}/${X.name}`));return q.map((X)=>`${X.site}/${X.name}`).filter((X)=>!W.has(X)).sort()}export function parseBuildManifestArgs(q){let J=0;for(const W of q){if(W==="--allow-removals"){J=Number.POSITIVE_INFINITY;continue}const X=W.match(/^--allow-removals=(\d+)$/);if(X){J=Number.parseInt(X[1],10);continue}}return{allowRemovals:J}}function v(q){try{if(!V.existsSync(q))return null;const J=V.readFileSync(q,"utf-8"),W=JSON.parse(J);return Array.isArray(W)?W:null}catch{return null}}async function g(){if(A(import.meta.url).includes(`${$.sep}dist${$.sep}`)){process.stderr.write(`❌ Refusing to run build-manifest from dist/.
2
+ import*as V from"node:fs";import*as $ from"node:path";import{fileURLToPath as A,pathToFileURL as L}from"node:url";import{getErrorMessage as O}from"./errors.js";import{fullName as C,getRegistry as b}from"./registry.js";import{findPackageRoot as k,getCliManifestPath as E}from"./package-paths.js";import{isRecord as U}from"./utils.js";const j=k(A(import.meta.url)),w=$.join(j,"clis"),H=E(w),I=/\bcli\s*\(|\bregisterSiteAuthCommands\s*\(|\bregisterArticleLogin\s*\(|\bregisterTokenAuth\s*\(|\bmake[A-Z]\w*Command\s*\(/;export class ManifestImportError extends Error{filePath;cause;constructor(q,J){super(`failed to scan ${q}: ${O(J)}`);this.filePath=q;this.cause=J;this.name="ManifestImportError"}}function T(q){return q.map((J)=>({name:J.name,type:J.type??"str",default:J.default,required:!!J.required,valueRequired:!!J.valueRequired||void 0,positional:J.positional||void 0,help:J.help??"",choices:J.choices,fromFile:J.fromFile||void 0,requiredUnlessFile:J.requiredUnlessFile||void 0}))}function D(q,J){const W=$.basename(q,$.extname(q));return`${J}/${W}.js`}export function normalizeManifestPath(q){return q.replace(/\\/g,"/")}function M(q,J){return normalizeManifestPath($.relative(J,q))}function y(q,J){return U(q)&&typeof q.site==="string"&&q.site===J&&typeof q.name==="string"&&(q.access==="read"||q.access==="write")&&Array.isArray(q.args)}function R(q,J,W){return{site:q.site,name:q.name,aliases:q.aliases,description:q.description??"",access:q.access,example:q.example,domain:q.domain,strategy:(q.strategy??"public").toString().toLowerCase(),browser:q.browser??!0,args:T(q.args),columns:q.columns,defaultFormat:q.defaultFormat,type:"js",modulePath:J,sourceFile:W,navigateBefore:q.navigateBefore,siteSession:q.siteSession,defaultWindowMode:q.defaultWindowMode}}export async function loadManifestEntries(q,J,W=(G)=>import(G),X=w){let G;try{G=V.readFileSync(q,"utf-8")}catch(Q){throw new ManifestImportError(q,Q)}if(!I.test(G))return[];try{const Q=D(q,J),Y=b(),Z=new Map(Y.entries()),F=await W(L(q).href),z=Object.values(U(F)?F:{}).filter((B)=>y(B,J)),N=z.length>0?z:[...Y.entries()].filter(([B,S])=>{if(S.site!==J)return!1;const x=Z.get(B);return!x||x!==S}).map(([,B])=>B),K=M(q,X),_=new Set;return N.filter((B)=>{const S=C(B);if(_.has(S))return!1;_.add(S);return!0}).sort((B,S)=>B.name.localeCompare(S.name)).map((B)=>R(B,Q,K))}catch(Q){throw new ManifestImportError(q,Q)}}export async function scanClisDir(q,J=(W)=>import(W)){const W=new Map,X=[];if(!V.existsSync(q))return{entries:[],failures:X};for(const Q of V.readdirSync(q)){const Y=$.join(q,Q);if(!V.statSync(Y).isDirectory())continue;for(const Z of V.readdirSync(Y))if(Z.endsWith(".js")&&!Z.endsWith(".d.js")&&!Z.endsWith(".test.js")&&Z!=="index.js"){const F=$.join(Y,Z);try{const z=await loadManifestEntries(F,Q,J,q);for(const N of z){const K=`${N.site}/${N.name}`;W.set(K,N)}}catch(z){if(z instanceof ManifestImportError){X.push(z);continue}throw z}}}return{entries:[...W.values()].sort((Q,Y)=>Q.site.localeCompare(Y.site)||Q.name.localeCompare(Y.name)),failures:X}}export async function buildManifest(){return scanClisDir(w)}export function serializeManifest(q){return`${JSON.stringify(q,null,2)}
3
+ `}export function findManifestMetadataIssues(q){const J=[];for(const W of q){if(!Array.isArray(W.args))continue;for(const X of W.args){if(!X.positional)continue;if((typeof X.help==="string"?X.help.trim():"")==="")J.push({site:W.site,command:W.name,arg:X.name,sourceFile:W.sourceFile,reason:"positional arg missing non-empty `help` text"})}}return J}export function diffRemovedEntries(q,J){const W=new Set(J.map((X)=>`${X.site}/${X.name}`));return q.map((X)=>`${X.site}/${X.name}`).filter((X)=>!W.has(X)).sort()}export function parseBuildManifestArgs(q){let J=0;for(const W of q){if(W==="--allow-removals"){J=Number.POSITIVE_INFINITY;continue}const X=W.match(/^--allow-removals=(\d+)$/);if(X){J=Number.parseInt(X[1],10);continue}}return{allowRemovals:J}}function v(q){try{if(!V.existsSync(q))return null;const J=V.readFileSync(q,"utf-8"),W=JSON.parse(J);return Array.isArray(W)?W:null}catch{return null}}async function p(){if(A(import.meta.url).includes(`${$.sep}dist${$.sep}`)){process.stderr.write(`❌ Refusing to run build-manifest from dist/.
4
4
  `+" Stale compiled output silently drops adapters that import renamed/removed exports.\n Run `npm run build-manifest` (or `tsx src/build-manifest.ts`) from the source tree instead.\n");process.exit(1)}const q=parseBuildManifestArgs(process.argv.slice(2)),{entries:J,failures:W}=await buildManifest();if(W.length>0){process.stderr.write(`❌ ${W.length} adapter(s) failed to load:
5
5
  `);for(const Q of W){const Y=$.relative(j,Q.filePath)||Q.filePath;process.stderr.write(` - ${Y}: ${O(Q.cause)}
6
6
  `)}process.stderr.write(`
@@ -13,4 +13,4 @@ Always run via tsx (\`npm run build-manifest\`), not against compiled dist/.
13
13
  `);if(Q.length>Y.length)process.stderr.write(` ... ${Q.length-Y.length} more
14
14
  `);process.stderr.write(`
15
15
  If this removal is intentional, rerun with \`--allow-removals=${Q.length}\` (or \`--allow-removals\` to disable the check).
16
- `);process.exit(1)}}V.mkdirSync($.dirname(H),{recursive:!0});V.writeFileSync(H,serializeManifest(J));console.log(`✅ Manifest compiled: ${J.length} entries → ${H}`);if(process.platform!=="win32"){const Q=j,Y=$.resolve(Q,"package.json");try{const Z=JSON.parse(V.readFileSync(Y,"utf-8")),F=typeof Z.bin==="string"?{[Z.name??"cli"]:Z.bin}:Z.bin??{};for(const z of Object.values(F)){const N=$.resolve(Q,z);if(V.existsSync(N)){V.chmodSync(N,493);console.log(`✅ Restored executable permission: ${z}`)}}}catch{}}}const p=process.argv[1]?L($.resolve(process.argv[1])).href:null;if(p===import.meta.url)g();
16
+ `);process.exit(1)}}V.mkdirSync($.dirname(H),{recursive:!0});V.writeFileSync(H,serializeManifest(J));console.log(`✅ Manifest compiled: ${J.length} entries → ${H}`);if(process.platform!=="win32"){const Q=j,Y=$.resolve(Q,"package.json");try{const Z=JSON.parse(V.readFileSync(Y,"utf-8")),F=typeof Z.bin==="string"?{[Z.name??"cli"]:Z.bin}:Z.bin??{};for(const z of Object.values(F)){const N=$.resolve(Q,z);if(V.existsSync(N)){V.chmodSync(N,493);console.log(`✅ Restored executable permission: ${z}`)}}}catch{}}}const P=process.argv[1]?L($.resolve(process.argv[1])).href:null;if(P===import.meta.url)p();
@@ -46,6 +46,7 @@ export interface DashPositionalManifestEntry {
46
46
  name: string;
47
47
  positional?: boolean;
48
48
  required?: boolean;
49
+ requiredUnlessFile?: boolean;
49
50
  valueRequired?: boolean;
50
51
  default?: unknown;
51
52
  }>;
@@ -1 +1 @@
1
- const C=new Set(["analyze","back","bind","check","click","close","console","dblclick","dialog","drag","eval","extract","fill","find","focus","frames","get","help","hover","init","keys","network","open","screenshot","scroll","select","state","tab","type","unbind","uncheck","upload","verify","wait"]),W=new Set(["--profile"]);export function getBrowserSubcommandNames(){return C}export function rewriteBrowserArgv(X){const H=[...X];let z=0;while(z<H.length){const Z=H[z];if(!Z.startsWith("-"))break;if(Z.includes("=")){z+=1;continue}if(W.has(Z)&&z+1<H.length)z+=2;else z+=1}if(H[z]!=="browser")return H;const J=z+1,Q=H[J];if(Q===void 0)return H;if(Q==="--session"||Q==="--session="||Q.startsWith("--session="))throw new BrowserSessionArgvError("The `--session` flag is no longer a public option. Use the positional form: ppcli browser <session> <command>");if(Q.startsWith("-"))return H;if(C.has(Q))return H;H.splice(J,1,"--session",Q);f(H,J+2);return H}function f(X,H){const z=X.findIndex((J,Q)=>Q>=H&&C.has(J));if(z===-1)return;for(let J=z+1;J<X.length;J+=1){const Q=X[J];if(Q==="--")return;if(Q.startsWith("--window=")){const Z=X.splice(J,1);X.splice(z,0,...Z);return}if(Q==="--window"){const Z=X[J+1];if(Z===void 0||Z==="--")return;const G=X.splice(J,2);X.splice(z,0,...G);return}}}export class BrowserSessionArgvError extends Error{constructor(X){super(X);this.name="BrowserSessionArgvError"}}function q(X){const H=new Map([["-h","none"],["--help","none"],["-v","none"],["--verbose","none"],["-f","required"],["--format","required"],["--trace","required"]]);if(X.browser){H.set("--window","required");H.set("--site-session","required");H.set("--keep-tab","required")}for(const z of X.args??[]){if(z.positional)continue;H.set(`--${z.name}`,z.required||z.valueRequired?"required":"optional")}return H}function U(X,H,z){const J=X[H];if(!J||J==="--")return null;const Q=J.indexOf("="),Z=Q===-1?J:J.slice(0,Q),G=z.get(Z);if(!G&&Q===-1&&J.startsWith("-")&&!J.startsWith("--")&&J.length>2){const V=J.slice(0,2);if(z.get(V)==="required")return{values:[J],nextIndex:H+1}}if(!G)return null;if(Q!==-1||G==="none")return{values:[J],nextIndex:H+1};const T=X[H+1];if(G==="required")return T===void 0?{values:[J],nextIndex:H+1}:{values:[J,T],nextIndex:H+2};if(T!==void 0&&!T.startsWith("-"))return{values:[J,T],nextIndex:H+2};return{values:[J],nextIndex:H+1}}export function escapeLeadingDashPositional(X,H){const z=[...X],J=new Map;for(const Y of H)if(Y.args?.find((y)=>y.positional)?.required)J.set(Y.site+"/"+Y.name,Y);let Q=0;while(Q<z.length){const Y=z[Q];if(!Y.startsWith("-"))break;if(Y.includes("=")){Q+=1;continue}if(W.has(Y)&&Q+1<z.length)Q+=2;else Q+=1}const Z=z[Q],G=z[Q+1],T=Q+2;if(!Z||!G||T>=z.length)return z;const V=J.get(Z+"/"+G);if(!V)return z;const K=q(V),F=[];let $=T;while($<z.length){const Y=z[$];if(Y==="--")return z;const D=U(z,$,K);if(D){F.push(...D.values);$=D.nextIndex;continue}if(!Y.startsWith("-"))return z;if(Y.startsWith("--"))return z;break}if($>=z.length)return z;const N=z[$],L=[],P=[];$+=1;while($<z.length){const Y=U(z,$,K);if(Y){L.push(...Y.values);$=Y.nextIndex;continue}P.push(z[$]);$+=1}return[...z.slice(0,T),...F,...L,"--",N,...P]}
1
+ const K=new Set(["analyze","back","bind","check","click","close","console","dblclick","dialog","drag","eval","extract","fill","find","focus","frames","get","help","hover","init","keys","network","open","screenshot","scroll","select","state","tab","type","unbind","uncheck","upload","verify","wait"]),W=new Set(["--profile"]);export function getBrowserSubcommandNames(){return K}export function rewriteBrowserArgv(X){const H=[...X];let z=0;while(z<H.length){const Z=H[z];if(!Z.startsWith("-"))break;if(Z.includes("=")){z+=1;continue}if(W.has(Z)&&z+1<H.length)z+=2;else z+=1}if(H[z]!=="browser")return H;const J=z+1,Q=H[J];if(Q===void 0)return H;if(Q==="--session"||Q==="--session="||Q.startsWith("--session="))throw new BrowserSessionArgvError("The `--session` flag is no longer a public option. Use the positional form: ppcli browser <session> <command>");if(Q.startsWith("-"))return H;if(K.has(Q))return H;H.splice(J,1,"--session",Q);q(H,J+2);return H}function q(X,H){const z=X.findIndex((J,Q)=>Q>=H&&K.has(J));if(z===-1)return;for(let J=z+1;J<X.length;J+=1){const Q=X[J];if(Q==="--")return;if(Q.startsWith("--window=")){const Z=X.splice(J,1);X.splice(z,0,...Z);return}if(Q==="--window"){const Z=X[J+1];if(Z===void 0||Z==="--")return;const h=X.splice(J,2);X.splice(z,0,...h);return}}}export class BrowserSessionArgvError extends Error{constructor(X){super(X);this.name="BrowserSessionArgvError"}}function F(X){const H=new Map([["-h","none"],["--help","none"],["-v","none"],["--verbose","none"],["-f","required"],["--format","required"],["--trace","required"]]);if(X.browser){H.set("--window","required");H.set("--site-session","required");H.set("--keep-tab","required")}for(const z of X.args??[]){if(z.positional)continue;H.set(`--${z.name}`,z.required||z.valueRequired?"required":"optional")}return H}function y(X,H,z){const J=X[H];if(!J||J==="--")return null;const Q=J.indexOf("="),Z=Q===-1?J:J.slice(0,Q),h=z.get(Z);if(!h&&Q===-1&&J.startsWith("-")&&!J.startsWith("--")&&J.length>2){const V=J.slice(0,2);if(z.get(V)==="required")return{values:[J],nextIndex:H+1}}if(!h)return null;if(Q!==-1||h==="none")return{values:[J],nextIndex:H+1};const G=X[H+1];if(h==="required")return G===void 0?{values:[J],nextIndex:H+1}:{values:[J,G],nextIndex:H+2};if(G!==void 0&&!G.startsWith("-"))return{values:[J,G],nextIndex:H+2};return{values:[J],nextIndex:H+1}}export function escapeLeadingDashPositional(X,H){const z=[...X],J=new Map;for(const Y of H){const T=Y.args?.find((b)=>b.positional);if(T?.required||T?.requiredUnlessFile)J.set(Y.site+"/"+Y.name,Y)}let Q=0;while(Q<z.length){const Y=z[Q];if(!Y.startsWith("-"))break;if(Y.includes("=")){Q+=1;continue}if(W.has(Y)&&Q+1<z.length)Q+=2;else Q+=1}const Z=z[Q],h=z[Q+1],G=Q+2;if(!Z||!h||G>=z.length)return z;const V=J.get(Z+"/"+h);if(!V)return z;const D=F(V),C=[];let $=G;while($<z.length){const Y=z[$];if(Y==="--")return z;const T=y(z,$,D);if(T){C.push(...T.values);$=T.nextIndex;continue}if(!Y.startsWith("-"))return z;if(Y.startsWith("--"))return z;break}if($>=z.length)return z;const U=z[$],L=[],P=[];$+=1;while($<z.length){const Y=y(z,$,D);if(Y){L.push(...Y.values);$=Y.nextIndex;continue}P.push(z[$]);$+=1}return[...z.slice(0,G),...C,...L,"--",U,...P]}
@@ -21,4 +21,12 @@ export declare function executeCommand(cmd: CliCommand, rawKwargs: CommandArgs,
21
21
  siteSession?: string;
22
22
  onTraceExport?: (trace: ObservationExportResult) => void;
23
23
  }): Promise<unknown>;
24
+ /**
25
+ * [pp-only] 把 `--file`(或 fromFile 指定的名字)读出来,填进它背书的那个文本参数。
26
+ *
27
+ * 与 registry 的 applyFromFile 成对:那边装配入口、降级 required,这边在参数解析完
28
+ * 之后接管取值与必填校验。两个来源只能给一个 —— 同时给多半是调用方搞混了,静默取
29
+ * 其一会发出用户没预期的内容。
30
+ */
31
+ export declare function resolveFileBackedArg(cmd: CliCommand, kwargs: CommandArgs): void;
24
32
  export declare function prepareCommandArgs(cmd: CliCommand, rawKwargs: CommandArgs): CommandArgs;
@@ -1,2 +1,2 @@
1
- import{getRegistry as qq,fullName as y}from"./registry.js";import{pathToFileURL as Qq}from"node:url";import*as Xq from"node:crypto";import*as u from"node:fs";import*as Yq from"node:os";import{executePipeline as l}from"./pipeline/index.js";import{adapterLoadError as Zq,ArgumentError as F,CommandExecutionError as A,SessionBusyError as $q,attachTraceReceipt as Jq,getErrorMessage as c}from"./errors.js";import{shouldUseBrowserSession as Gq}from"./capabilityRouting.js";import{getBrowserFactory as Hq,browserSession as Vq,runWithTimeout as i,DEFAULT_BROWSER_COMMAND_TIMEOUT as zq}from"./runtime.js";import{withBrowserSessionLockIf as Lq}from"./browser-session-lock.js";import{resolveProfile as Wq}from"./browser/profile.js";import{ensureManagedChrome as Fq}from"./browser/managed-chrome.js";import{generateFingerprintJs as Kq}from"./browser/fingerprint.js";import{CDPBridge as Bq}from"./browser/index.js";import{clearDaemonRunContext as yq,generateRunId as Iq,isUnknownOutcomeError as m,releaseSiteSessionLease as n,setDaemonCommandTimeoutSeconds as jq,setDaemonRunContext as d}from"./browser/daemon-client.js";import{emitHook as D}from"./hooks.js";import{log as t}from"./logger.js";import{isElectronApp as _q}from"./electron-apps.js";import{probeCDP as Oq,resolveElectronEndpoint as Uq}from"./launcher.js";import{ObservationSession as Cq,exportObservationSession as Nq}from"./observation/index.js";import{resolveAdapterSourcePath as Aq}from"./adapter-source.js";const O=new Map,k=new Map,xq=`${Yq.homedir()}/.ppcli/clis/`;function Tq(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 z=Number(Z);if(Number.isNaN(z))throw new F(`Argument "${X.name}" must be a valid number. Received: "${Z}"`);Y[X.name]=z}else if(X.type==="boolean"||X.type==="bool")if(typeof Z==="string"){const z=Z.toLowerCase();if(z==="true"||z==="1")Y[X.name]=!0;else if(z==="false"||z==="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 S(q,Q,Y,X){const Z=q;if(Z._lazy&&Z._modulePath){const J=Z._modulePath;if(J.startsWith(xq)&&O.has(J))try{const L=u.statSync(J),$=k.get(J);if($!==void 0&&L.mtimeMs!==$)O.delete(J)}catch{}if(!O.has(J)){const L=Qq(J).href,_=import(k.has(J)?`${L}?t=${Date.now()}`:L).then(()=>{try{k.set(J,u.statSync(J).mtimeMs)}catch{}},(K)=>{O.delete(J);throw Zq(`Failed to load adapter module ${J}: ${c(K)}`,"Check that the adapter file exists and has no syntax errors.")});O.set(J,_)}await O.get(J);const W=qq().get(y(q));if(W?.func)return s(W,Q,Y,X);if(W?.pipeline)return l(Q,W.pipeline,{args:Y,debug:X})}if(q.func)return s(q,Q,Y,X);if(q.pipeline)return l(Q,q.pipeline,{args:Y,debug:X});throw new A(`Command ${y(q)} has no func or pipeline`,"This is likely a bug in the adapter definition. Please report this issue.")}function s(q,Q,Y,X){if(q.browser===!1)return q.func(Y,X);if(!Q)throw new A(`Command ${y(q)} requires a browser session but none was provided`);return q.func(Q,Y,X)}function Rq(q){if(q.navigateBefore===!1)return null;if(typeof q.navigateBefore==="string")return q.navigateBefore;return null}function Mq(q,Q){if(!q||!Q)return!1;try{const Y=new URL(q).hostname;return Y===Q||Y.endsWith(`.${Q}`)}catch{return!1}}function Pq(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 Dq(q,Q,Y,X){if(Y!=="persistent"||!q.domain)return!0;if(!Pq(X,q.domain))return!0;const Z=await Q.getCurrentUrl?.().catch(()=>null);return!Mq(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(c($))}const J=wq(q,Z);jq(J);const z=Tq(X.trace),W={command:y(q),args:Z,startedAt:Date.now()};await D("onBeforeExecute",W);let L;try{if(Gq(q)){const $=_q(q.site),_=Wq(X.profile),K=!$&&_.kind==="managed"?_:null;let N;const x=vq(q.defaultWindowMode??"background",X.windowMode);let b,f;if(K){N=await Fq(K.name,K.entry,{windowMode:x});const H=K.entry.fingerprint;if(H?.enabled)b=Kq(H);const G=K.entry.proxy;if(G?.username)f={username:G.username,password:G.password??""}}else if($){const H=process.env.OPENCLI_CDP_ENDPOINT;if(H){const G=Number(new URL(H).port);if(!await Oq(G))throw new A(`CDP not reachable at ${H}`,"Check that the app is running with --remote-debugging-port and the endpoint is correct.");N=H}else N=await Uq(q.site)}const r=K?Bq:Hq(q.site),v=!K&&_.kind==="extension"?_.contextId:void 0,e=q,U=hq(q,X.siteSession),T=Eq(q,U),w=fq(U,X.keepTab),I=U==="persistent"&&q.access==="write"?{runId:Iq(),session:T}:null;if(I)d({runId:I.runId,command:y(q),access:"write"});let p,g=!1,R=null;try{L=await Lq(x!=="foreground",()=>Vq(r,async(H)=>{const G=z==="off"?null:new Cq({scope:{contextId:v??K?.name,session:T,target:H.getActivePage?.(),site:q.site,command:y(q),adapterSourcePath:Aq(e)}});if(G){G.record({stream:"action",name:"command",phase:"start",data:{args:Z}});await H.startNetworkCapture?.().catch(()=>!1)}const B=Rq(q);if(B&&await Dq(q,H,U,B)){G?.record({stream:"action",name:"pre_navigate",phase:"start",data:{url:B}});try{await H.goto(B);G?.record({stream:"action",name:"pre_navigate",phase:"end",data:{url:B}})}catch(V){if(V instanceof $q)throw V;G?.record({stream:"action",name:"pre_navigate",phase:"error",data:{url:B,error:V instanceof Error?V.message:String(V)}});const j=new A(`Pre-navigation to ${B} failed: ${V instanceof Error?V.message:V}`,"Check that the site is reachable and the browser extension is running.");j.cause=V;if(G&&(z==="on"||z==="retain-on-failure")){G.record({stream:"error",message:j.message,stack:j.stack,code:j.code,hint:j.hint});await h(G,H).catch(()=>{});E(G,"failure",j,X.onTraceExport)}throw j}}const C=J!==null?J+o:zq,M=S(q,H,Z,Y);R=M;let P=!1;M.then(()=>{P=!0},()=>{P=!0});try{const V=await i(M,{timeout:C,label:y(q)});G?.record({stream:"action",name:"command",phase:"end"});if(G&&z==="on"){await h(G,H).catch(()=>{});E(G,"success",void 0,X.onTraceExport)}if(!w)await H.closeWindow?.().catch(()=>{});return V}catch(V){if(!P)g=!0;if(G){G.record({stream:"action",name:"command",phase:"error",data:{error:V instanceof Error?V.message:String(V)}});G.record({stream:"error",message:V instanceof Error?V.message:String(V),stack:V instanceof Error?V.stack:void 0});if(z==="on"||z==="retain-on-failure"){await h(G,H).catch(()=>{});E(G,"failure",V,X.onTraceExport)}}if(!w)await H.closeWindow?.().catch(()=>{});throw V}},{session:T,cdpEndpoint:N,contextId:v,windowMode:x,surface:"adapter",siteSession:U,initScript:b,proxyAuth:f}),K?.name)}catch(H){p=H;throw H}finally{if(I)if(g&&R){const{runId:H,session:G}=I,B=(C)=>{yq(H);if(!m(C))n({runId:H,session:G,surface:"adapter"})};R.then(()=>B(),(C)=>B(C))}else{d(null);if(!m(p))await n({runId:I.runId,session:I.session,surface:"adapter"})}}}else if(J!==null){const $=J+o;L=await i(S(q,null,Z,Y),{timeout:$,label:y(q),hint:`Pass a higher --timeout value (currently ${J}s)`})}else L=await S(q,null,Z,Y)}catch($){W.error=$;W.finishedAt=Date.now();await D("onAfterExecute",W);throw $}W.finishedAt=Date.now();await D("onAfterExecute",W,L);return L}async function h(q,Q){const Y=Q.getActivePage?.()??q.scope.target,[X,Z,J,z,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 L of Array.isArray(J)?J:[]){const $=L;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 L of Array.isArray(z)?z:[])if(L&&typeof L==="object"){const $=L;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(L)});if(typeof W==="string"&&W)q.record({stream:"screenshot",format:"png",data:W,label:"final"})}function E(q,Q,Y,X){try{const Z=Nq(q,{error:Y,status:Q});if(Q==="failure"&&Y!==void 0)Jq(Y,Z.receipt);else process.stderr.write(`ppcli trace artifact: ${Z.dir}
2
- `);try{X?.(Z)}catch(J){t.warn(`[trace] Trace export callback failed: ${J instanceof Error?J.message:String(J)}`)}return Z}catch(Z){t.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 o=30;function kq(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 Sq(){const q=process.env.PUBLISHPORT_SITE_SESSION;if(q==="persistent"||q==="ephemeral")return q;return null}function hq(q,Q){return kq(Q)??Sq()??q.siteSession??"persistent"}function Eq(q,Q){if(Q==="persistent")return`site:${q.site}`;return`site:${q.site}:${Xq.randomUUID()}`}function bq(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 fq(q,Q){if(q==="persistent")return!0;return bq("--keep-tab",Q)??!1}function a(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 vq(q="background",Q){return a("--window",Q)??a("OPENCLI_WINDOW",process.env.OPENCLI_WINDOW)??q}function wq(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}
1
+ import{getRegistry as Xq,fullName as I}from"./registry.js";import{pathToFileURL as Yq}from"node:url";import*as Zq from"node:crypto";import*as P from"node:fs";import*as c from"node:os";import*as i from"node:path";import{executePipeline as m}from"./pipeline/index.js";import{adapterLoadError as $q,ArgumentError as K,CommandExecutionError as R,SessionBusyError as Jq,attachTraceReceipt as Gq,getErrorMessage as S}from"./errors.js";import{shouldUseBrowserSession as Hq}from"./capabilityRouting.js";import{getBrowserFactory as Vq,browserSession as zq,runWithTimeout as n,DEFAULT_BROWSER_COMMAND_TIMEOUT as Lq}from"./runtime.js";import{withBrowserSessionLockIf as Wq}from"./browser-session-lock.js";import{resolveProfile as Kq}from"./browser/profile.js";import{ensureManagedChrome as Fq}from"./browser/managed-chrome.js";import{generateFingerprintJs as Bq}from"./browser/fingerprint.js";import{CDPBridge as yq}from"./browser/index.js";import{clearDaemonRunContext as Iq,generateRunId as jq,isUnknownOutcomeError as d,releaseSiteSessionLease as t,setDaemonCommandTimeoutSeconds as _q,setDaemonRunContext as s}from"./browser/daemon-client.js";import{emitHook as k}from"./hooks.js";import{log as o}from"./logger.js";import{isElectronApp as Oq}from"./electron-apps.js";import{probeCDP as Uq,resolveElectronEndpoint as Cq}from"./launcher.js";import{ObservationSession as Tq,exportObservationSession as Rq}from"./observation/index.js";import{resolveAdapterSourcePath as xq}from"./adapter-source.js";const O=new Map,E=new Map,Aq=`${c.homedir()}/.ppcli/clis/`;function Nq(q){if(q===void 0||q===null||q===""||q==="off")return"off";if(q==="on"||q==="retain-on-failure")return q;throw new K(`--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 K(`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 z=Number(Z);if(Number.isNaN(z))throw new K(`Argument "${X.name}" must be a valid number. Received: "${Z}"`);Y[X.name]=z}else if(X.type==="boolean"||X.type==="bool")if(typeof Z==="string"){const z=Z.toLowerCase();if(z==="true"||z==="1")Y[X.name]=!0;else if(z==="false"||z==="0")Y[X.name]=!1;else throw new K(`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 K(`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 h(q,Q,Y,X){const Z=q;if(Z._lazy&&Z._modulePath){const J=Z._modulePath;if(J.startsWith(Aq)&&O.has(J))try{const W=P.statSync(J),$=E.get(J);if($!==void 0&&W.mtimeMs!==$)O.delete(J)}catch{}if(!O.has(J)){const W=Yq(J).href,y=import(E.has(J)?`${W}?t=${Date.now()}`:W).then(()=>{try{E.set(J,P.statSync(J).mtimeMs)}catch{}},(F)=>{O.delete(J);throw $q(`Failed to load adapter module ${J}: ${S(F)}`,"Check that the adapter file exists and has no syntax errors.")});O.set(J,y)}await O.get(J);const L=Xq().get(I(q));if(L?.func)return a(L,Q,Y,X);if(L?.pipeline)return m(Q,L.pipeline,{args:Y,debug:X})}if(q.func)return a(q,Q,Y,X);if(q.pipeline)return m(Q,q.pipeline,{args:Y,debug:X});throw new R(`Command ${I(q)} has no func or pipeline`,"This is likely a bug in the adapter definition. Please report this issue.")}function a(q,Q,Y,X){if(q.browser===!1)return q.func(Y,X);if(!Q)throw new R(`Command ${I(q)} requires a browser session but none was provided`);return q.func(Q,Y,X)}function Mq(q){if(q.navigateBefore===!1)return null;if(typeof q.navigateBefore==="string")return q.navigateBefore;return null}function Dq(q,Q){if(!q||!Q)return!1;try{const Y=new URL(q).hostname;return Y===Q||Y.endsWith(`.${Q}`)}catch{return!1}}function Pq(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 Sq(q,Q,Y,X){if(Y!=="persistent"||!q.domain)return!0;if(!Pq(X,q.domain))return!0;const Z=await Q.getCurrentUrl?.().catch(()=>null);return!Dq(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 K)throw $;throw new K(S($))}const J=pq(q,Z);_q(J);const z=Nq(X.trace),L={command:I(q),args:Z,startedAt:Date.now()};await k("onBeforeExecute",L);let W;try{if(Hq(q)){const $=Oq(q.site),y=Kq(X.profile),F=!$&&y.kind==="managed"?y:null;let T;const x=wq(q.defaultWindowMode??"background",X.windowMode);let f,w;if(F){T=await Fq(F.name,F.entry,{windowMode:x});const H=F.entry.fingerprint;if(H?.enabled)f=Bq(H);const G=F.entry.proxy;if(G?.username)w={username:G.username,password:G.password??""}}else if($){const H=process.env.OPENCLI_CDP_ENDPOINT;if(H){const G=Number(new URL(H).port);if(!await Uq(G))throw new R(`CDP not reachable at ${H}`,"Check that the app is running with --remote-debugging-port and the endpoint is correct.");T=H}else T=await Cq(q.site)}const qq=F?yq:Vq(q.site),p=!F&&y.kind==="extension"?y.contextId:void 0,Qq=q,U=hq(q,X.siteSession),A=bq(q,U),u=fq(U,X.keepTab),j=U==="persistent"&&q.access==="write"?{runId:jq(),session:A}:null;if(j)s({runId:j.runId,command:I(q),access:"write"});let g,l=!1,N=null;try{W=await Wq(x!=="foreground",()=>zq(qq,async(H)=>{const G=z==="off"?null:new Tq({scope:{contextId:p??F?.name,session:A,target:H.getActivePage?.(),site:q.site,command:I(q),adapterSourcePath:xq(Qq)}});if(G){G.record({stream:"action",name:"command",phase:"start",data:{args:Z}});await H.startNetworkCapture?.().catch(()=>!1)}const B=Mq(q);if(B&&await Sq(q,H,U,B)){G?.record({stream:"action",name:"pre_navigate",phase:"start",data:{url:B}});try{await H.goto(B);G?.record({stream:"action",name:"pre_navigate",phase:"end",data:{url:B}})}catch(V){if(V instanceof Jq)throw V;G?.record({stream:"action",name:"pre_navigate",phase:"error",data:{url:B,error:V instanceof Error?V.message:String(V)}});const _=new R(`Pre-navigation to ${B} failed: ${V instanceof Error?V.message:V}`,"Check that the site is reachable and the browser extension is running.");_.cause=V;if(G&&(z==="on"||z==="retain-on-failure")){G.record({stream:"error",message:_.message,stack:_.stack,code:_.code,hint:_.hint});await b(G,H).catch(()=>{});v(G,"failure",_,X.onTraceExport)}throw _}}const C=J!==null?J+r:Lq,M=h(q,H,Z,Y);N=M;let D=!1;M.then(()=>{D=!0},()=>{D=!0});try{const V=await n(M,{timeout:C,label:I(q)});G?.record({stream:"action",name:"command",phase:"end"});if(G&&z==="on"){await b(G,H).catch(()=>{});v(G,"success",void 0,X.onTraceExport)}if(!u)await H.closeWindow?.().catch(()=>{});return V}catch(V){if(!D)l=!0;if(G){G.record({stream:"action",name:"command",phase:"error",data:{error:V instanceof Error?V.message:String(V)}});G.record({stream:"error",message:V instanceof Error?V.message:String(V),stack:V instanceof Error?V.stack:void 0});if(z==="on"||z==="retain-on-failure"){await b(G,H).catch(()=>{});v(G,"failure",V,X.onTraceExport)}}if(!u)await H.closeWindow?.().catch(()=>{});throw V}},{session:A,cdpEndpoint:T,contextId:p,windowMode:x,surface:"adapter",siteSession:U,initScript:f,proxyAuth:w}),F?.name)}catch(H){g=H;throw H}finally{if(j)if(l&&N){const{runId:H,session:G}=j,B=(C)=>{Iq(H);if(!d(C))t({runId:H,session:G,surface:"adapter"})};N.then(()=>B(),(C)=>B(C))}else{s(null);if(!d(g))await t({runId:j.runId,session:j.session,surface:"adapter"})}}}else if(J!==null){const $=J+r;W=await n(h(q,null,Z,Y),{timeout:$,label:I(q),hint:`Pass a higher --timeout value (currently ${J}s)`})}else W=await h(q,null,Z,Y)}catch($){L.error=$;L.finishedAt=Date.now();await k("onAfterExecute",L);throw $}L.finishedAt=Date.now();await k("onAfterExecute",L,W);return W}async function b(q,Q){const Y=Q.getActivePage?.()??q.scope.target,[X,Z,J,z,L]=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 W of Array.isArray(J)?J:[]){const $=W;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 W of Array.isArray(z)?z:[])if(W&&typeof W==="object"){const $=W;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(W)});if(typeof L==="string"&&L)q.record({stream:"screenshot",format:"png",data:L,label:"final"})}function v(q,Q,Y,X){try{const Z=Rq(q,{error:Y,status:Q});if(Q==="failure"&&Y!==void 0)Gq(Y,Z.receipt);else process.stderr.write(`ppcli trace artifact: ${Z.dir}
2
+ `);try{X?.(Z)}catch(J){o.warn(`[trace] Trace export callback failed: ${J instanceof Error?J.message:String(J)}`)}return Z}catch(Z){o.warn(`[trace] Failed to export trace artifact: ${Z instanceof Error?Z.message:String(Z)}`);return}}export function resolveFileBackedArg(q,Q){const Y=q.args.find((L)=>L.fromFile);if(!Y)return;const X=typeof Y.fromFile==="string"?Y.fromFile:"file",Z=Y.positional?`<${Y.name}>`:`--${Y.name}`,J=Q[X]==null?"":String(Q[X]).trim(),z=Q[Y.name]==null?"":String(Q[Y.name]);if(J){if(z.trim())throw new K(`${Z} 和 --${X} 只能二选一,不能同时给`);const L=i.resolve(J.startsWith("~/")?i.join(c.homedir(),J.slice(2)):J);let W;try{W=P.readFileSync(L,"utf-8")}catch(y){throw new K(`--${X} 指向的文件读不到: ${L}(${S(y)})`)}const $=W.trim();if(!$)throw new K(`--${X} 指向的文件内容为空: ${L}`);Q[Y.name]=$;return}if(!z.trim()&&Y.requiredUnlessFile)throw new K(`缺少 ${Y.name}:${Z} 或 --${X} 给其一`)}export function prepareCommandArgs(q,Q){const Y=coerceAndValidateArgs(q.args,Q);resolveFileBackedArg(q,Y);q.validateArgs?.(Y);return Y}const r=30;function kq(q){if(q===void 0||q===null||q==="")return null;if(q==="ephemeral"||q==="persistent")return q;throw new K(`--site-session must be one of: ephemeral, persistent. Received: "${String(q)}"`)}function Eq(){const q=process.env.PUBLISHPORT_SITE_SESSION;if(q==="persistent"||q==="ephemeral")return q;return null}function hq(q,Q){return kq(Q)??Eq()??q.siteSession??"persistent"}function bq(q,Q){if(Q==="persistent")return`site:${q.site}`;return`site:${q.site}:${Zq.randomUUID()}`}function vq(q,Q){if(Q===void 0||Q==="")return null;if(Q==="true")return!0;if(Q==="false")return!1;throw new K(`${q} must be one of: true, false. Received: "${String(Q)}"`)}function fq(q,Q){if(q==="persistent")return!0;return vq("--keep-tab",Q)??!1}function e(q,Q){if(Q===void 0||Q==="")return null;if(Q==="foreground"||Q==="background")return Q;throw new K(`${q} must be one of: foreground, background. Received: "${String(Q)}"`)}function wq(q="background",Q){return e("--window",Q)??e("OPENCLI_WINDOW",process.env.OPENCLI_WINDOW)??q}function pq(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 K(`Argument "timeout" must be a positive integer. Received: "${String(Y)}"`);const X=Number(Y);if(!Number.isInteger(X)||X<=0)throw new K(`Argument "timeout" must be a positive integer. Received: "${String(Y)}"`);return X}
@@ -25,6 +25,11 @@ export interface ManifestEntry {
25
25
  positional?: boolean;
26
26
  help?: string;
27
27
  choices?: string[];
28
+ /** [pp-only] 见 Arg.fromFile —— 必须进 manifest:命令执行前拿到的是 manifest
29
+ * 反序列化出的命令壳,声明丢了的话文件入口就只在 --help 里好看、不生效。 */
30
+ fromFile?: boolean | string;
31
+ /** [pp-only] 见 Arg.requiredUnlessFile */
32
+ requiredUnlessFile?: boolean;
28
33
  }>;
29
34
  columns?: string[];
30
35
  pipeline?: Record<string, unknown>[];
@@ -18,6 +18,21 @@ export interface Arg {
18
18
  positional?: boolean;
19
19
  help?: string;
20
20
  choices?: string[];
21
+ /**
22
+ * [pp-only] 给这个文本参数配一个「从本机文件读」的入口。`true` 生成 `--file`,
23
+ * 传字符串则用该名字(命令已有 `--file` 作他用时必须显式改名,如 'prompt-file')。
24
+ *
25
+ * 为什么要有:正文一旦走命令行,就得穿过调用方的 shell,各家引号 / 转义 / 编码
26
+ * 规则互不相同 —— Windows 上尤其惨烈(cmd.exe 解析批处理只认第一行,带换行的正文
27
+ * 被直接截断,连排在后面的参数也一起丢)。让长正文走文件,命令行上只剩短参数,
28
+ * 这一整类问题从源头消失,且跨平台一致。装配细节见 applyFromFile。
29
+ */
30
+ fromFile?: boolean | string;
31
+ /**
32
+ * [pp-only] applyFromFile 内部用:原本 `required: true` 的参数在有文件入口后
33
+ * 会被降级(否则解析阶段就因缺参报错),必填校验改由 resolveFileBackedArg 接管。
34
+ */
35
+ requiredUnlessFile?: boolean;
21
36
  }
22
37
  export type CommandArgs = Record<string, any>;
23
38
  export type BrowserCommandFunc = (page: IPage, kwargs: CommandArgs, debug?: boolean) => Promise<unknown>;
@@ -1 +1 @@
1
- export var Strategy;(function(j){j.PUBLIC="public";j.LOCAL="local";j.COOKIE="cookie";j.INTERCEPT="intercept";j.UI="ui"})(Strategy||(Strategy={}));const H=globalThis.__opencli_registry__??=new Map;export function cli(j){const q={site:j.site,name:j.name,aliases:j.aliases,description:j.description??"",access:j.access,example:j.example,domain:j.domain,strategy:j.strategy,browser:j.browser,args:j.args??[],columns:j.columns,func:j.func,pipeline:j.pipeline,footerExtra:j.footerExtra,validateArgs:j.validateArgs,navigateBefore:j.navigateBefore,siteSession:j.siteSession,defaultWindowMode:j.defaultWindowMode,defaultFormat:j.defaultFormat,authStatus:j.authStatus};registerCommand(q);return H.get(fullName(q))}export function getRegistry(){return H}export function fullName(j){return`${j.site}/${j.name}`}export function strategyLabel(j){return j.strategy??Strategy.PUBLIC}function I(j){J(j);L(j);const q=j.strategy??(j.browser===!1?Strategy.PUBLIC:Strategy.COOKIE),F=j.browser??(q!==Strategy.PUBLIC&&q!==Strategy.LOCAL);let D=j.navigateBefore;if(D===void 0){if(q===Strategy.COOKIE&&j.domain)D=`https://${j.domain}`;else if(q!==Strategy.PUBLIC&&q!==Strategy.LOCAL)D=!0}return F?{...j,strategy:q,browser:!0,navigateBefore:D}:{...j,strategy:q,browser:!1,navigateBefore:D}}function J(j){if(j.access==="read"||j.access==="write")return;const q=`${j.site}/${j.name}`;throw Error(`Command ${q} must declare access: 'read' | 'write'`)}function L(j){if(j.siteSession===void 0)return;const q=`${j.site}/${j.name}`;if(j.siteSession!=="ephemeral"&&j.siteSession!=="persistent")throw Error(`Command ${q} siteSession must be one of: ephemeral, persistent`)}export function registerCommand(j){const q=I(j),F=fullName(q),D=H.get(F);if(D?.aliases)for(const E of D.aliases)H.delete(`${D.site}/${E}`);const G=M(q.aliases,q.name);q.aliases=G.length>0?G:void 0;H.set(F,q);for(const E of G)H.set(`${q.site}/${E}`,q)}function M(j,q){if(!Array.isArray(j)||j.length===0)return[];const F=new Set,D=[];for(const G of j){const E=typeof G==="string"?G.trim():"";if(!E||E===q||F.has(E))continue;F.add(E);D.push(E)}return D}
1
+ export var Strategy;(function(j){j.PUBLIC="public";j.LOCAL="local";j.COOKIE="cookie";j.INTERCEPT="intercept";j.UI="ui"})(Strategy||(Strategy={}));const J=globalThis.__opencli_registry__??=new Map;export function cli(j){const q={site:j.site,name:j.name,aliases:j.aliases,description:j.description??"",access:j.access,example:j.example,domain:j.domain,strategy:j.strategy,browser:j.browser,args:j.args??[],columns:j.columns,func:j.func,pipeline:j.pipeline,footerExtra:j.footerExtra,validateArgs:j.validateArgs,navigateBefore:j.navigateBefore,siteSession:j.siteSession,defaultWindowMode:j.defaultWindowMode,defaultFormat:j.defaultFormat,authStatus:j.authStatus};registerCommand(q);return J.get(fullName(q))}export function getRegistry(){return J}export function fullName(j){return`${j.site}/${j.name}`}export function strategyLabel(j){return j.strategy??Strategy.PUBLIC}function L(j){const q=j.args.filter((I)=>I.fromFile);if(q.length===0)return;const G=`${j.site}/${j.name}`;if(q.length>1)throw Error(`Command ${G}: 只能有一个 fromFile 参数,当前有 ${q.map((I)=>I.name).join(", ")}`);const D=q[0],H=typeof D.fromFile==="string"?D.fromFile:"file";if(j.args.some((I)=>I.name===H))throw Error(`Command ${G}: fromFile 想生成的 --${H} 与已有参数重名;`+`给 ${D.name} 的 fromFile 指定别的名字(如 '${D.name}-file')`);const E=D.positional?`<${D.name}>`:`--${D.name}`;j.args=j.args.map((I)=>I===D?{...I,required:!1,requiredUnlessFile:I.required===!0}:I);j.args.push({name:H,help:`从本机文件读取${D.name}(UTF-8),与 ${E} 二选一。`+"长正文建议走文件:命令行上只剩短参数,不受 shell 引号 / 换行 / 编码影响"})}function M(j){O(j);Q(j);L(j);const q=j.strategy??(j.browser===!1?Strategy.PUBLIC:Strategy.COOKIE),G=j.browser??(q!==Strategy.PUBLIC&&q!==Strategy.LOCAL);let D=j.navigateBefore;if(D===void 0){if(q===Strategy.COOKIE&&j.domain)D=`https://${j.domain}`;else if(q!==Strategy.PUBLIC&&q!==Strategy.LOCAL)D=!0}return G?{...j,strategy:q,browser:!0,navigateBefore:D}:{...j,strategy:q,browser:!1,navigateBefore:D}}function O(j){if(j.access==="read"||j.access==="write")return;const q=`${j.site}/${j.name}`;throw Error(`Command ${q} must declare access: 'read' | 'write'`)}function Q(j){if(j.siteSession===void 0)return;const q=`${j.site}/${j.name}`;if(j.siteSession!=="ephemeral"&&j.siteSession!=="persistent")throw Error(`Command ${q} siteSession must be one of: ephemeral, persistent`)}export function registerCommand(j){const q=M(j),G=fullName(q),D=J.get(G);if(D?.aliases)for(const E of D.aliases)J.delete(`${D.site}/${E}`);const H=R(q.aliases,q.name);q.aliases=H.length>0?H:void 0;J.set(G,q);for(const E of H)J.set(`${q.site}/${E}`,q)}function R(j,q){if(!Array.isArray(j)||j.length===0)return[];const G=new Set,D=[];for(const H of j){const E=typeof H==="string"?H.trim():"";if(!E||E===q||G.has(E))continue;G.add(E);D.push(E)}return D}
@@ -1,4 +1,4 @@
1
- import{fullName as J,strategyLabel as K}from"./registry.js";export function serializeArg(k){return{name:k.name,type:k.type??"string",required:!!k.required,valueRequired:!!k.valueRequired,positional:!!k.positional,choices:k.choices??[],default:k.default??null,help:k.help??""}}export function serializeCommand(k){return{command:J(k),site:k.site,name:k.name,aliases:k.aliases??[],description:k.description,access:k.access,strategy:K(k),browser:!!k.browser,args:k.args.map(serializeArg),columns:k.columns??[],domain:k.domain??null,example:formatCommandExample(k),defaultFormat:k.defaultFormat??null,siteSession:k.siteSession??null}}export function formatArgSummary(k){return k.map((q)=>{if(q.positional)return q.required?`<${q.name}>`:`[${q.name}]`;return q.required?`--${q.name}`:`[--${q.name}]`}).join(" ")}function M(k){if(k.length<=4)return k.join(", ");return`${k.slice(0,4).join(", ")}, ... (+${k.length-4} more)`}function F(k){return`<${k}>`}export function formatCommandExample(k){if(k.example?.trim())return k.example.trim();const q=["ppcli",k.site,k.name];for(const v of k.args)if(v.positional&&v.required)q.push(F(v.name));for(const v of k.args){if(v.positional||!v.required)continue;q.push(`--${v.name}`);if(v.type!=="bool"&&v.type!=="boolean")q.push(F(v.name))}q.push("-f","yaml");return q.join(" ")}export function formatRegistryHelpText(k){const q=[],v=k.args.filter((B)=>B.choices?.length);for(const B of v){const H=B.positional?`<${B.name}>`:`--${B.name}`,I=B.default!=null?` (default: ${B.default})`:"";q.push(` ${H}: ${M(B.choices)}${I}`)}const D=[];D.push(`Access: ${k.access}`);D.push(`Browser: ${k.browser?"yes":"no"}`);if(k.domain)D.push(`Domain: ${k.domain}`);if(k.defaultFormat)D.push(`Default format: ${k.defaultFormat}`);if(k.aliases?.length)D.push(`Aliases: ${k.aliases.join(", ")}`);q.push(D.join(" | "));q.push(`Example: ${formatCommandExample(k)}`);if(k.columns?.length)q.push(`Output columns: ${k.columns.join(", ")}`);return`
2
- `+q.join(`
1
+ import{fullName as J,strategyLabel as K}from"./registry.js";export function serializeArg(k){return{name:k.name,type:k.type??"string",required:!!k.required,valueRequired:!!k.valueRequired,positional:!!k.positional,choices:k.choices??[],default:k.default??null,help:k.help??""}}export function serializeCommand(k){return{command:J(k),site:k.site,name:k.name,aliases:k.aliases??[],description:k.description,access:k.access,strategy:K(k),browser:!!k.browser,args:k.args.map(serializeArg),columns:k.columns??[],domain:k.domain??null,example:formatCommandExample(k),defaultFormat:k.defaultFormat??null,siteSession:k.siteSession??null}}export function formatArgSummary(k){return k.map((v)=>{if(v.positional)return v.required?`<${v.name}>`:`[${v.name}]`;return v.required?`--${v.name}`:`[--${v.name}]`}).join(" ")}function M(k){if(k.length<=4)return k.join(", ");return`${k.slice(0,4).join(", ")}, ... (+${k.length-4} more)`}function F(k){return`<${k}>`}export function formatCommandExample(k){if(k.example?.trim())return k.example.trim();const v=["ppcli",k.site,k.name],D=(q)=>q.required||q.requiredUnlessFile;for(const q of k.args)if(q.positional&&D(q))v.push(F(q.name));for(const q of k.args){if(q.positional||!D(q))continue;v.push(`--${q.name}`);if(q.type!=="bool"&&q.type!=="boolean")v.push(F(q.name))}v.push("-f","yaml");return v.join(" ")}export function formatRegistryHelpText(k){const v=[],D=k.args.filter((B)=>B.choices?.length);for(const B of D){const H=B.positional?`<${B.name}>`:`--${B.name}`,I=B.default!=null?` (default: ${B.default})`:"";v.push(` ${H}: ${M(B.choices)}${I}`)}const q=[];q.push(`Access: ${k.access}`);q.push(`Browser: ${k.browser?"yes":"no"}`);if(k.domain)q.push(`Domain: ${k.domain}`);if(k.defaultFormat)q.push(`Default format: ${k.defaultFormat}`);if(k.aliases?.length)q.push(`Aliases: ${k.aliases.join(", ")}`);v.push(q.join(" | "));v.push(`Example: ${formatCommandExample(k)}`);if(k.columns?.length)v.push(`Output columns: ${k.columns.join(", ")}`);return`
2
+ `+v.join(`
3
3
  `)+`
4
4
  `}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "publishport-opencli",
3
- "version": "1.0.11",
3
+ "version": "1.0.13",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "provenance": false