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,4 +1,4 @@
1
- import{cli as v,Strategy as z}from"@jackwener/opencli/registry";export const sendCommand=v({site:"antigravity",name:"send",access:"write",description:"Send a message to Antigravity AI via the internal Lexical editor",domain:"localhost",strategy:z.UI,browser:!0,args:[{name:"message",help:"The message text to send",required:!0,positional:!0}],columns:["Status","Message"],func:async(d,q)=>{const f=q.message;await d.evaluate(`
1
+ import{cli as v,Strategy as z}from"@jackwener/opencli/registry";export const sendCommand=v({site:"antigravity",name:"send",access:"write",description:"Send a message to Antigravity AI via the internal Lexical editor",domain:"localhost",strategy:z.UI,browser:!0,args:[{name:"message",fromFile:!0,help:"The message text to send",required:!0,positional:!0}],columns:["Status","Message"],func:async(d,q)=>{const f=q.message;await d.evaluate(`
2
2
  async () => {
3
3
  const container = document.getElementById('antigravity.agentSidePanelInputBox');
4
4
  if (!container) throw new Error('Could not find antigravity.agentSidePanelInputBox');
@@ -1 +1 @@
1
- import{cli as L,Strategy as S}from"@jackwener/opencli/registry";import{ArgumentError as V,CommandExecutionError as W,EmptyResultError as U}from"@jackwener/opencli/errors";import{apiGet as _,apiPost as h,requireOkPayload as $,resolveBvid as q,resolveUid as O}from"./utils.js";import{assertLiteralContent as R}from"../_shared/content-guard.js";function j(D,z){const f=Number(D);if(!Number.isInteger(f)||f<=0)throw new V(`bilibili comment ${z} must be a positive integer`);return f}L({site:"bilibili",name:"comment",access:"write",description:"在 B站视频下发表评论或回复(官方 API,需登录;消息里的 @用户 会被解析为真实提及)",domain:"www.bilibili.com",strategy:S.COOKIE,args:[{name:"bvid",required:!0,positional:!0,help:"Video BV ID / URL / b23.tv short link"},{name:"message",required:!0,positional:!0,help:"Comment text. Any @username in it is resolved to a real mention"},{name:"parent",type:"int",help:"top-level/root rpid to reply under (omit for a top-level comment)"},{name:"execute",type:"boolean",help:"Actually post the comment. Without it the command refuses to write."}],columns:["rpid","bvid","oid","message","url"],func:async(D,z)=>{if(!D)throw new W("Browser session required for bilibili comment");const f=String(z.message??"").trim();R(f,{label:"评论内容"});if(!f)throw new V("bilibili comment message cannot be empty");if(!z.execute)throw new V("Refusing to post: pass --execute to actually publish this comment");const X=z.parent!=null?j(z.parent,"parent"):null;let H;try{H=await q(z.bvid)}catch(J){throw new V(`Cannot resolve Bilibili BV ID from input: ${String(z.bvid??"")}`,J instanceof Error?J.message:String(J))}const B=await _(D,"/x/web-interface/view",{params:{bvid:H}}),Y=$(B,"view")?.aid;if(!Y)throw new W(`Cannot resolve aid for bvid: ${H}`);const K={};for(const J of f.matchAll(/@([^\s@]+)/g)){const Q=J[1];if(Q in K)continue;try{const F=Number(await O(D,Q));if(!Number.isInteger(F)||F<=0)throw new W(`Bilibili user search returned malformed mid for @${Q}`);K[Q]=F}catch(F){if(!(F instanceof U))throw F}}const G={oid:Y,type:1,message:f,plat:1,...X!=null?{root:X,parent:X}:{},...Object.keys(K).length>0?{at_name_to_mid:JSON.stringify(K)}:{}},I=await h(D,"/x/v2/reply/add",{params:G}),Z=$(I,"reply add")?.rpid;if(!Z)throw new W("Bilibili reply add API did not return rpid for the posted comment");return[{rpid:String(Z),bvid:H,oid:String(Y),message:f,url:`https://www.bilibili.com/video/${H}#reply${Z}`}]}});
1
+ import{cli as L,Strategy as S}from"@jackwener/opencli/registry";import{ArgumentError as V,CommandExecutionError as W,EmptyResultError as U}from"@jackwener/opencli/errors";import{apiGet as _,apiPost as h,requireOkPayload as $,resolveBvid as q,resolveUid as O}from"./utils.js";import{assertLiteralContent as R}from"../_shared/content-guard.js";function j(D,z){const f=Number(D);if(!Number.isInteger(f)||f<=0)throw new V(`bilibili comment ${z} must be a positive integer`);return f}L({site:"bilibili",name:"comment",access:"write",description:"在 B站视频下发表评论或回复(官方 API,需登录;消息里的 @用户 会被解析为真实提及)",domain:"www.bilibili.com",strategy:S.COOKIE,args:[{name:"bvid",required:!0,positional:!0,help:"Video BV ID / URL / b23.tv short link"},{name:"message",fromFile:!0,required:!0,positional:!0,help:"Comment text. Any @username in it is resolved to a real mention"},{name:"parent",type:"int",help:"top-level/root rpid to reply under (omit for a top-level comment)"},{name:"execute",type:"boolean",help:"Actually post the comment. Without it the command refuses to write."}],columns:["rpid","bvid","oid","message","url"],func:async(D,z)=>{if(!D)throw new W("Browser session required for bilibili comment");const f=String(z.message??"").trim();R(f,{label:"评论内容"});if(!f)throw new V("bilibili comment message cannot be empty");if(!z.execute)throw new V("Refusing to post: pass --execute to actually publish this comment");const X=z.parent!=null?j(z.parent,"parent"):null;let H;try{H=await q(z.bvid)}catch(J){throw new V(`Cannot resolve Bilibili BV ID from input: ${String(z.bvid??"")}`,J instanceof Error?J.message:String(J))}const B=await _(D,"/x/web-interface/view",{params:{bvid:H}}),Y=$(B,"view")?.aid;if(!Y)throw new W(`Cannot resolve aid for bvid: ${H}`);const K={};for(const J of f.matchAll(/@([^\s@]+)/g)){const Q=J[1];if(Q in K)continue;try{const F=Number(await O(D,Q));if(!Number.isInteger(F)||F<=0)throw new W(`Bilibili user search returned malformed mid for @${Q}`);K[Q]=F}catch(F){if(!(F instanceof U))throw F}}const G={oid:Y,type:1,message:f,plat:1,...X!=null?{root:X,parent:X}:{},...Object.keys(K).length>0?{at_name_to_mid:JSON.stringify(K)}:{}},I=await h(D,"/x/v2/reply/add",{params:G}),Z=$(I,"reply add")?.rpid;if(!Z)throw new W("Bilibili reply add API did not return rpid for the posted comment");return[{rpid:String(Z),bvid:H,oid:String(Y),message:f,url:`https://www.bilibili.com/video/${H}#reply${Z}`}]}});
@@ -1 +1 @@
1
- import{cli as T,Strategy as W}from"@jackwener/opencli/registry";import{ArgumentError as J,CommandExecutionError as N}from"@jackwener/opencli/errors";import{apiPostAbs as X,getSelfUid as Y,requireOkPayload as Z,resolveUid as $,wbiSign as f}from"./utils.js";import{assertLiteralContent as h}from"../_shared/content-guard.js";const L="A6716E9A-7CE3-47AF-994B-F0B34178D28D";T({site:"bilibili",name:"dm-send",access:"write",description:"给 B站用户发私信(纯文本,官方 API,需登录;不可撤回,需 --execute)",domain:"www.bilibili.com",strategy:W.COOKIE,args:[{name:"target",required:!0,positional:!0,help:"对方 UID / 用户名(dm-list 的 talker_id)"},{name:"message",required:!0,positional:!0,help:"私信内容(纯文本)"},{name:"execute",type:"boolean",help:"Actually send the message. Without it the command refuses to write."}],columns:["receiver_id","msg_key","status"],func:async(z,F)=>{if(!z)throw new N("Browser session required for bilibili dm-send");const G=String(F.message??"").trim();h(G,{label:"私信内容"});if(!G)throw new J("bilibili dm-send message cannot be empty");if(!F.execute)throw new J("Refusing to send: pass --execute to actually send this message");const B=Number(await $(z,String(F.target??"").trim())),H=Number(await Y(z));if(B===H)throw new J("Cannot send a private message to yourself");const Q=await f(z,{w_sender_uid:H,w_receiver_id:B}),R=await X(z,"https://api.vc.bilibili.com/web_im/v1/web_im/send_msg",{query:Q,params:{"msg[sender_uid]":H,"msg[receiver_id]":B,"msg[receiver_type]":1,"msg[msg_type]":1,"msg[msg_status]":0,"msg[content]":JSON.stringify({content:G}),"msg[dev_id]":L,"msg[new_face_version]":0,"msg[timestamp]":Math.floor(Date.now()/1000),from_filework:0,build:0,mobi_app:"web"}}),M=Z(R,"dm send_msg")?.msg_key;if(!M)throw new N("Bilibili send_msg API did not return msg_key for the sent message");return[{receiver_id:String(B),msg_key:String(M),status:"sent"}]}});
1
+ import{cli as T,Strategy as W}from"@jackwener/opencli/registry";import{ArgumentError as J,CommandExecutionError as N}from"@jackwener/opencli/errors";import{apiPostAbs as X,getSelfUid as Y,requireOkPayload as Z,resolveUid as $,wbiSign as f}from"./utils.js";import{assertLiteralContent as h}from"../_shared/content-guard.js";const L="A6716E9A-7CE3-47AF-994B-F0B34178D28D";T({site:"bilibili",name:"dm-send",access:"write",description:"给 B站用户发私信(纯文本,官方 API,需登录;不可撤回,需 --execute)",domain:"www.bilibili.com",strategy:W.COOKIE,args:[{name:"target",required:!0,positional:!0,help:"对方 UID / 用户名(dm-list 的 talker_id)"},{name:"message",fromFile:!0,required:!0,positional:!0,help:"私信内容(纯文本)"},{name:"execute",type:"boolean",help:"Actually send the message. Without it the command refuses to write."}],columns:["receiver_id","msg_key","status"],func:async(z,F)=>{if(!z)throw new N("Browser session required for bilibili dm-send");const G=String(F.message??"").trim();h(G,{label:"私信内容"});if(!G)throw new J("bilibili dm-send message cannot be empty");if(!F.execute)throw new J("Refusing to send: pass --execute to actually send this message");const B=Number(await $(z,String(F.target??"").trim())),H=Number(await Y(z));if(B===H)throw new J("Cannot send a private message to yourself");const Q=await f(z,{w_sender_uid:H,w_receiver_id:B}),R=await X(z,"https://api.vc.bilibili.com/web_im/v1/web_im/send_msg",{query:Q,params:{"msg[sender_uid]":H,"msg[receiver_id]":B,"msg[receiver_type]":1,"msg[msg_type]":1,"msg[msg_status]":0,"msg[content]":JSON.stringify({content:G}),"msg[dev_id]":L,"msg[new_face_version]":0,"msg[timestamp]":Math.floor(Date.now()/1000),from_filework:0,build:0,mobi_app:"web"}}),M=Z(R,"dm send_msg")?.msg_key;if(!M)throw new N("Bilibili send_msg API did not return msg_key for the sent message");return[{receiver_id:String(B),msg_key:String(M),status:"sent"}]}});
@@ -1,4 +1,4 @@
1
- import{cli as K,Strategy as M}from"@jackwener/opencli/registry";import{ArgumentError as J,CommandExecutionError as B}from"@jackwener/opencli/errors";import{assertLiteralContent as N}from"../_shared/content-guard.js";K({site:"bilibili",name:"dynamic-post",access:"write",description:"发布 B站动态(纯文本,官方 API,需登录)",domain:"www.bilibili.com",strategy:M.COOKIE,browser:!0,args:[{name:"text",required:!0,positional:!0,help:"Dynamic text content"},{name:"execute",type:"boolean",help:"Actually publish. Without it the command refuses to write."}],columns:["status","dynamic_id","text","url"],func:async(q,D)=>{if(!q)throw new B("Browser session required for bilibili dynamic-post");const z=String(D.text??"").trim();N(z,{label:"动态内容"});if(!z)throw new J("bilibili dynamic-post text cannot be empty");if(!D.execute)throw new J("Refusing to post: pass --execute to actually publish this dynamic");let G=!1;for(let H=0;H<4;H++){await q.goto("https://t.bilibili.com");await q.wait(2);const I=await q.evaluate("location.href");if(typeof I==="string"&&/^https?:\/\/[^/]*bilibili\.com/.test(I)){G=!0;break}}if(!G)throw new B("Failed to navigate to t.bilibili.com (page did not land on a bilibili origin)");const v=await q.evaluate(`(async () => {
1
+ import{cli as K,Strategy as M}from"@jackwener/opencli/registry";import{ArgumentError as J,CommandExecutionError as B}from"@jackwener/opencli/errors";import{assertLiteralContent as N}from"../_shared/content-guard.js";K({site:"bilibili",name:"dynamic-post",access:"write",description:"发布 B站动态(纯文本,官方 API,需登录)",domain:"www.bilibili.com",strategy:M.COOKIE,browser:!0,args:[{name:"text",fromFile:!0,required:!0,positional:!0,help:"Dynamic text content"},{name:"execute",type:"boolean",help:"Actually publish. Without it the command refuses to write."}],columns:["status","dynamic_id","text","url"],func:async(q,D)=>{if(!q)throw new B("Browser session required for bilibili dynamic-post");const z=String(D.text??"").trim();N(z,{label:"动态内容"});if(!z)throw new J("bilibili dynamic-post text cannot be empty");if(!D.execute)throw new J("Refusing to post: pass --execute to actually publish this dynamic");let G=!1;for(let H=0;H<4;H++){await q.goto("https://t.bilibili.com");await q.wait(2);const I=await q.evaluate("location.href");if(typeof I==="string"&&/^https?:\/\/[^/]*bilibili\.com/.test(I)){G=!0;break}}if(!G)throw new B("Failed to navigate to t.bilibili.com (page did not land on a bilibili origin)");const v=await q.evaluate(`(async () => {
2
2
  var text = ${JSON.stringify(z)};
3
3
  var csrf = (document.cookie.match(/bili_jct=([^;]+)/) || [])[1] || '';
4
4
  var url = 'https://api.bilibili.com/x/dynamic/feed/create/dyn?csrf=' + encodeURIComponent(csrf);
@@ -1 +1 @@
1
- import*as I from"node:fs";import*as P from"node:path";import{cli as E,Strategy as b}from"@jackwener/opencli/registry";import{ArgumentError as D,AuthRequiredError as h,CommandExecutionError as L}from"@jackwener/opencli/errors";import{resolveVideoFile as l,resolveImageFile as u}from"../_shared/video-publish.js";import{setInputFiles as w}from"../_shared/file-inject.js";const U={bda2:{os:"upos",upcdn:"bda2",probe_version:20221109},bldsa:{os:"upos",upcdn:"bldsa",probe_version:20221109},qn:{os:"upos",upcdn:"qn",probe_version:20221109},ws:{os:"upos",upcdn:"ws",probe_version:20221109}},f="bda2",k=["SESSDATA","bili_jct","DedeUserID"],m="https://member.bilibili.com/platform/home",x="__pp_bili_video_input";function n(W){var J=document.getElementById(W.inputId);if(!J||!J.files||!J.files[0])return{error:"文件未就绪(input 为空)"};var Z=J.files[0],Y=Z.size,$=W.line;return async function(){try{var G=new URLSearchParams({profile:"ugcfx/bup",name:Z.name,size:String(Y),r:$.os,ssl:"0",version:"2.14.0",build:"2100400",upcdn:$.upcdn,probe_version:String($.probe_version)}),X=await(await fetch("https://member.bilibili.com/preupload?"+G.toString(),{credentials:"include",headers:{Referer:"https://www.bilibili.com"}})).json();if(X.OK!==1)return{error:"preupload 失败:"+JSON.stringify(X)};var K="https:"+X.endpoint+"/"+String(X.upos_uri).replace(/^upos:\/\//,""),O=new URLSearchParams({uploads:"",output:"json",profile:"ugcfx/bup",filesize:String(Y),partsize:String(X.chunk_size),biz_id:String(X.biz_id)}),H=await(await fetch(K+"?"+O.toString(),{method:"POST",headers:{"x-upos-auth":X.auth}})).json();if(H.OK!==1)return{error:"获取 upload_id 失败:"+JSON.stringify(H)};var T=Math.max(1,Math.ceil(Y/X.chunk_size));return{url:K,auth:X.auth,uploadId:H.upload_id,chunkSize:X.chunk_size,bizId:X.biz_id,size:Y,name:Z.name,total:T}}catch(R){return{error:"preupload 异常:"+String(R&&R.message||R)}}}()}function d(W){var J=document.getElementById(W.inputId);if(!J||!J.files||!J.files[0])return{ok:!1,error:"文件丢失(页面可能已导航)"};var Z=J.files[0],Y=W.idx*W.chunkSize,$=Z.slice(Y,Math.min(Y+W.chunkSize,W.size)),G=$.size,X=new URLSearchParams({partNumber:String(W.idx+1),uploadId:String(W.uploadId),chunk:String(W.idx),chunks:String(W.total),size:String(G),start:String(Y),end:String(Y+G),total:String(W.size)});return async function(){for(var K=0;K<5;K++){try{var O=await fetch(W.url+"?"+X.toString(),{method:"PUT",headers:{"x-upos-auth":W.auth},body:$});if(O.status<400){var H=await O.text();if(H==="MULTIPART_PUT_SUCCESS"||H==="")return{ok:!0}}}catch(T){}await new Promise(function(T){setTimeout(T,800*(K+1))})}return{ok:!1,error:"分块 "+W.idx+" 多次重试仍失败"}}()}function c(W){var J=[];for(var Z=1;Z<=W.total;Z++)J.push({partNumber:Z,eTag:"etag"});var Y=new URLSearchParams({output:"json",name:W.name,profile:"ugcfx/bup",uploadId:String(W.uploadId),biz_id:String(W.bizId)});return async function(){try{var $=await(await fetch(W.url+"?"+Y.toString(),{method:"POST",headers:{"x-upos-auth":W.auth,"content-type":"application/json; charset=UTF-8"},body:JSON.stringify({parts:J})})).json();if($.OK!==1)return{error:"complete 失败:"+JSON.stringify($)};var G=String($.key).replace(/^\//,"").replace(/\.[^.]+$/,"");return{filename:G,cid:W.bizId}}catch(X){return{error:"complete 异常:"+String(X&&X.message||X)}}}()}function j(W){return async function(){try{var J=await fetch("https://member.bilibili.com/x/vupre/web/topic/type?type_id="+encodeURIComponent(W.tid)+"&pn=0&ps=200",{credentials:"include"}),Z=await J.json();if(!Z||Z.code!==0||!Z.data||!Array.isArray(Z.data.topics))return{error:"获取话题列表失败:"+(Z&&Z.message||"code="+(Z&&Z.code))};var Y=null;for(var $=0;$<Z.data.topics.length;$++)if(Number(Z.data.topics[$].topic_id)===Number(W.topicId)){Y=Z.data.topics[$];break}if(!Y)return{error:"topic_id "+W.topicId+" 不在分区 "+W.tid+" 的可用话题里(用 bilibili topics --tid "+W.tid+" 查合法值)"};return{topicId:Number(Y.topic_id),missionId:Y.mission_id!=null?Number(Y.mission_id):null,name:Y.topic_name||""}}catch(G){return{error:"解析话题异常:"+String(G&&G.message||G)}}}()}function o(W){return async function(){var J=document.cookie.match(/(?:^|;\s*)bili_jct=([^;]+)/);if(!J)return{error:"未找到 bili_jct CSRF token"};var Z=decodeURIComponent(J[1]);try{var Y=W.dataUri;if(typeof Y==="string"&&Y.indexOf("data:image/webp")===0){var $=Y.indexOf(",");if($===-1)return{error:"封面 data URI 格式非法"};var G=Y.slice(5,$),X=Y.slice($+1),K;if(G.indexOf("base64")!==-1){var O=atob(X);K=new Uint8Array(O.length);for(var H=0;H<O.length;H++)K[H]=O.charCodeAt(H)}else K=new TextEncoder().encode(decodeURIComponent(X));var T=new Blob([K],{type:"image/webp"}),R=await createImageBitmap(T),S=document.createElement("canvas");S.width=R.width;S.height=R.height;var Q=S.getContext("2d");Q.fillStyle="#ffffff";Q.fillRect(0,0,S.width,S.height);Q.drawImage(R,0,0);var q=await new Promise(function(F){S.toBlob(F,"image/jpeg",0.92)});if(!q)return{error:"封面 webp 转码 JPEG 失败"};Y=await new Promise(function(F,_){var A=new FileReader;A.onload=function(){F(String(A.result))};A.onerror=function(){_(Error("FileReader 读取转码结果失败"))};A.readAsDataURL(q)})}var B=new URLSearchParams;B.append("cover",Y);B.append("csrf",Z);var V=await(await fetch("https://member.bilibili.com/x/vu/web/cover/up",{method:"POST",credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:B.toString()})).json();if(!V||V.code!==0||!V.data||!V.data.url)return{error:"封面上传失败:"+(V&&V.message?V.message:JSON.stringify(V))};return{url:V.data.url}}catch(F){return{error:"封面上传异常:"+String(F&&F.message||F)}}}()}function p(W){return async function(){var J=document.cookie.match(/(?:^|;\s*)bili_jct=([^;]+)/);if(!J)return{error:"未找到 bili_jct CSRF token"};var Z=decodeURIComponent(J[1]),Y=W.meta;for(var $ in Y)if(Y[$]===null||Y[$]===void 0)delete Y[$];Y.csrf=Z;try{var G=await fetch("https://member.bilibili.com/x/vu/web/add/v3?csrf="+encodeURIComponent(Z)+"&t="+Date.now(),{method:"POST",credentials:"include",headers:{"Content-Type":"application/json;charset=UTF-8"},body:JSON.stringify(Y)}),X=await G.json();if(!X||X.code!==0)return{error:"add/v3 提交失败:"+(X&&X.message?X.message:"")+"(code="+(X&&X.code)+" status="+G.status+")"};return{bvid:X.data&&X.data.bvid,aid:X.data&&X.data.aid}}catch(K){return{error:"add/v3 异常:"+String(K&&K.message||K)}}}()}function g(W){return async function(){var J=document.cookie.match(/(?:^|;\s*)bili_jct=([^;]+)/);if(!J)return{error:"未找到 bili_jct CSRF token"};var Z=decodeURIComponent(J[1]),Y=W.body;Y.csrf=Z;try{var $=await fetch("https://member.bilibili.com/x/vupre/web/draft/add?t="+Date.now()+"&csrf="+encodeURIComponent(Z),{method:"POST",credentials:"include",headers:{"Content-Type":"application/json;charset=UTF-8"},body:JSON.stringify(Y)}),G=await $.json();if(!G||G.code!==0)return{error:"draft/add 保存草稿失败:"+(G&&G.message?G.message:"")+"(code="+(G&&G.code)+" status="+$.status+")"};return{data:G.data}}catch(X){return{error:"draft/add 异常:"+String(X&&X.message||X)}}}()}const N=(W,J,Z)=>W.evaluateWithArgs(`(${J.toString()})(a)`,{a:Z});function s(W){const J=u(W),Z=P.extname(J).toLowerCase(),Y=Z===".png"?"image/png":Z===".webp"?"image/webp":Z===".bmp"?"image/bmp":"image/jpeg",$=I.readFileSync(J).toString("base64");return`data:${Y};base64,${$}`}async function t(W){for(let J=0;J<8;J++){await W.goto(m);await W.wait({time:1});let Z="";try{Z=String(await W.evaluate("location.href")||"")}catch{Z=""}if(/^https?:\/\/member\.bilibili\.com\//.test(Z)){const Y=await W.getCookies({url:"https://member.bilibili.com"}),$=new Map;for(const X of Array.isArray(Y)?Y:[])if(X&&typeof X.name==="string")$.set(X.name,String(X.value??""));const G=k.filter((X)=>!$.get(X));if(!G.length)return $;throw new h("member.bilibili.com",`缺少 B站登录 cookie(${G.join(", ")})。请先在客户端登录哔哩哔哩。`)}}throw new h("member.bilibili.com","无法进入 B站创作中心(可能未登录或被风控)。请先在客户端登录哔哩哔哩。")}E({site:"bilibili",name:"upload",access:"write",description:"投稿视频到 B站(在真实浏览器登录态里走 web 投稿:preupload→upos 分块→add/v3)。默认仅 dry-run 校验,加 --execute 才真正上传并提交。",domain:"member.bilibili.com",strategy:b.COOKIE,browser:!0,args:[{name:"file",required:!0,positional:!0,help:"视频文件路径"},{name:"title",help:"稿件标题(默认取文件名)"},{name:"tid",type:"number",required:!0,help:"分区 id(B站 typeid,必填,禁止臆造)。合法值用 `bilibili partitions` 列举后取。"},{name:"tag",required:!0,help:"标签,逗号分隔(B站要求至少 1 个)"},{name:"desc",help:"简介"},{name:"topic",type:"number",help:"参与话题的 topic_id(重要流量入口)。合法值用 `bilibili topics --tid <tid>` 列举,禁止臆造;mission_id 自动匹配。"},{name:"cover",help:"封面图片路径(不传由 B站自动截取)"},{name:"copyright",type:"number",help:"1=自制 2=转载(默认 1)"},{name:"source",help:"转载来源 URL(copyright=2 时必填)"},{name:"dynamic",help:"同步发布的动态文案(可空)"},{name:"no-reprint",type:"boolean",help:"禁止转载(默认允许)"},{name:"dtime",type:"number",help:"定时发布的 10 位 unix 时间戳(可空=立即)"},{name:"line",help:`上传线路:${Object.keys(U).join("/")}(默认 ${f})`},{name:"concurrency",type:"number",help:"分块并发数(默认 3)"},{name:"draft",type:"boolean",help:"存草稿而非直接发布(上传视频后存到创作中心草稿箱,不公开)"},{name:"execute",type:"boolean",help:"真正发布投稿;不带(也不带 --draft)则只做 dry-run 校验"}],columns:["status","title","bvid","url"],func:async(W,J)=>{if(!W)throw new L("bilibili upload 需要浏览器会话");const Z=l(String(J.file??"")),Y=String(J.title??"").trim()||P.basename(Z).replace(/\.[^.]+$/,""),$=String(J.tag??"").split(",").map((z)=>z.trim()).filter(Boolean);if(!$.length)throw new D("B站投稿至少需要 1 个标签,用 --tag 传(逗号分隔)");if(J.tid==null||J.tid===""||!Number.isFinite(Number(J.tid)))throw new D("必须用 --tid 指定分区 id(数字)。合法值用 `bilibili partitions` 列举,禁止臆造。");const G=Number(J.tid),X=J.copyright!=null&&J.copyright!==""?Number(J.copyright):1;if(X!==1&&X!==2)throw new D("--copyright 只能是 1(自制) 或 2(转载)");const K=String(J.source??"").trim();if(X===2&&!K)throw new D("转载(copyright=2)必须用 --source 提供转载来源");if(J.cover)u(String(J.cover));const O=String(J.line??f),H=U[O];if(!H)throw new D(`未知上传线路「${O}」,可选:${Object.keys(U).join("/")}`);const T=J.concurrency!=null&&J.concurrency!==""?Number(J.concurrency):3,R=Boolean(J.draft),S=Boolean(J.execute)&&!R;await t(W);await W.evaluate(`(() => { var id=${JSON.stringify(x)}; var el=document.getElementById(id); if(!el){ el=document.createElement('input'); el.type='file'; el.id=id; el.style.cssText='position:fixed;left:-9999px;top:0;'; document.body.appendChild(el);} el.value=''; return true; })()`);await w(W,`#${x}`,[Z]);if(!R&&!S)return{status:"dry-run",title:Y,bvid:"",url:`校验通过:登录态有效、视频已注入就绪、分区 ${G}、标签 [${$.join(", ")}]、版权 ${X===1?"自制":"转载"}。加 --execute 发布,或 --draft 存草稿。`};const Q=await N(W,n,{inputId:x,line:H});if(!Q||Q.error)throw new L(`B站 preupload 失败:${Q&&Q.error||"无返回"}`);for(let z=0;z<Q.total;z++){const M=await N(W,d,{inputId:x,url:Q.url,auth:Q.auth,uploadId:Q.uploadId,idx:z,total:Q.total,size:Q.size,chunkSize:Q.chunkSize});if(!M||!M.ok)throw new L(`B站分块上传失败(${z+1}/${Q.total}):${M&&M.error||"无返回"}`)}const q=await N(W,c,{url:Q.url,auth:Q.auth,uploadId:Q.uploadId,bizId:Q.bizId,name:Q.name,total:Q.total});if(!q||q.error||!q.filename)throw new L(`B站 complete 失败:${q&&q.error||"未拿到 filename"}`);let B="";if(J.cover){const z=s(String(J.cover)),M=await N(W,o,{dataUri:z});if(!M||M.error||!M.url)throw new L(`B站封面上传失败:${M&&M.error||"未拿到 url"}`);B=M.url}let V=null,F=null;if(J.topic!=null&&J.topic!==""){if(!Number.isFinite(Number(J.topic)))throw new D("--topic 必须是 topic_id 数字,用 `bilibili topics --tid <tid>` 取合法值");const z=await N(W,j,{tid:G,topicId:Number(J.topic)});if(!z||z.error)throw new L(`B站话题解析失败:${z&&z.error||"无返回"}`);V=z.topicId;F=z.missionId}if(R){const z={videos:[{filename:q.filename,title:Y,desc:"",cid:q.cid,is_4k:!1,is_8k:!1,is_hdr:!1}],cover:B,cover43:B,ai_cover:0,is_ab_cover:0,ab_cover_info:null,title:Y,copyright:X,tid:G,tag:$.join(","),desc:String(J.desc??""),recreate:-1,dynamic:String(J.dynamic??""),is_only_self:0,space_hidden:2,watermark:{state:0},no_reprint:J["no-reprint"]?1:0,subtitle:{open:0,lan:""},dolby:0,lossless_music:0,up_selection_reply:!1,up_close_reply:!1,up_close_danmu:!1};if(X===2)z.source=K;if(V!=null){z.topic_id=V;z.topic_detail={from_topic_id:V,from_source:"arc.web.search"};if(F!=null)z.mission_id=F}const M=await N(W,g,{body:z});if(!M||M.error)throw new L(`B站 ${M&&M.error||"draft/add 无返回"}`);const y=M.data&&(M.data.aid!=null?M.data.aid:M.data.draft_id!=null?M.data.draft_id:M.data.id);return{status:"✅ 草稿已保存",title:Y,bvid:y!=null?`draft:${y}`:"",url:"https://member.bilibili.com/platform/upload-manager/article?group=draft"}}const _=J.dtime!=null&&J.dtime!==""?Number(J.dtime):null,A={title:Y,copyright:X,tid:G,tag:$.join(","),mission_id:F,topic_id:V,topic_detail:V!=null?{from_topic_id:V,from_source:"arc.web.recommend"}:null,desc_format_id:9999,desc:String(J.desc??""),dtime:_,recreate:-1,dynamic:String(J.dynamic??""),interactive:0,act_reserve_create:0,no_disturbance:0,porder:null,adorder_type:9,no_reprint:J["no-reprint"]?1:0,subtitle:{open:0,lan:""},neutral_mark:null,dolby:0,lossless_music:0,up_selection_reply:!1,up_close_reply:!1,up_close_danmu:!1,web_os:1,source:X===2?K:null,watermark:{state:0},cover:B,videos:[{title:Y,desc:"",filename:q.filename,cid:q.cid}]},C=await N(W,p,{meta:A});if(!C||C.error)throw new L(`B站 ${C&&C.error||"add/v3 无返回"}`);const v=C.bvid?String(C.bvid):"";return{status:J.dtime?"✅ 定时投稿已提交":"✅ 投稿成功",title:Y,bvid:v,url:v?`https://www.bilibili.com/video/${v}`:"(已提交,稍后在创作中心查看)"}}});
1
+ import*as I from"node:fs";import*as P from"node:path";import{cli as E,Strategy as b}from"@jackwener/opencli/registry";import{ArgumentError as D,AuthRequiredError as h,CommandExecutionError as L}from"@jackwener/opencli/errors";import{resolveVideoFile as l,resolveImageFile as u}from"../_shared/video-publish.js";import{setInputFiles as w}from"../_shared/file-inject.js";const U={bda2:{os:"upos",upcdn:"bda2",probe_version:20221109},bldsa:{os:"upos",upcdn:"bldsa",probe_version:20221109},qn:{os:"upos",upcdn:"qn",probe_version:20221109},ws:{os:"upos",upcdn:"ws",probe_version:20221109}},f="bda2",k=["SESSDATA","bili_jct","DedeUserID"],m="https://member.bilibili.com/platform/home",x="__pp_bili_video_input";function n(W){var J=document.getElementById(W.inputId);if(!J||!J.files||!J.files[0])return{error:"文件未就绪(input 为空)"};var Z=J.files[0],Y=Z.size,$=W.line;return async function(){try{var G=new URLSearchParams({profile:"ugcfx/bup",name:Z.name,size:String(Y),r:$.os,ssl:"0",version:"2.14.0",build:"2100400",upcdn:$.upcdn,probe_version:String($.probe_version)}),X=await(await fetch("https://member.bilibili.com/preupload?"+G.toString(),{credentials:"include",headers:{Referer:"https://www.bilibili.com"}})).json();if(X.OK!==1)return{error:"preupload 失败:"+JSON.stringify(X)};var K="https:"+X.endpoint+"/"+String(X.upos_uri).replace(/^upos:\/\//,""),O=new URLSearchParams({uploads:"",output:"json",profile:"ugcfx/bup",filesize:String(Y),partsize:String(X.chunk_size),biz_id:String(X.biz_id)}),H=await(await fetch(K+"?"+O.toString(),{method:"POST",headers:{"x-upos-auth":X.auth}})).json();if(H.OK!==1)return{error:"获取 upload_id 失败:"+JSON.stringify(H)};var T=Math.max(1,Math.ceil(Y/X.chunk_size));return{url:K,auth:X.auth,uploadId:H.upload_id,chunkSize:X.chunk_size,bizId:X.biz_id,size:Y,name:Z.name,total:T}}catch(R){return{error:"preupload 异常:"+String(R&&R.message||R)}}}()}function d(W){var J=document.getElementById(W.inputId);if(!J||!J.files||!J.files[0])return{ok:!1,error:"文件丢失(页面可能已导航)"};var Z=J.files[0],Y=W.idx*W.chunkSize,$=Z.slice(Y,Math.min(Y+W.chunkSize,W.size)),G=$.size,X=new URLSearchParams({partNumber:String(W.idx+1),uploadId:String(W.uploadId),chunk:String(W.idx),chunks:String(W.total),size:String(G),start:String(Y),end:String(Y+G),total:String(W.size)});return async function(){for(var K=0;K<5;K++){try{var O=await fetch(W.url+"?"+X.toString(),{method:"PUT",headers:{"x-upos-auth":W.auth},body:$});if(O.status<400){var H=await O.text();if(H==="MULTIPART_PUT_SUCCESS"||H==="")return{ok:!0}}}catch(T){}await new Promise(function(T){setTimeout(T,800*(K+1))})}return{ok:!1,error:"分块 "+W.idx+" 多次重试仍失败"}}()}function c(W){var J=[];for(var Z=1;Z<=W.total;Z++)J.push({partNumber:Z,eTag:"etag"});var Y=new URLSearchParams({output:"json",name:W.name,profile:"ugcfx/bup",uploadId:String(W.uploadId),biz_id:String(W.bizId)});return async function(){try{var $=await(await fetch(W.url+"?"+Y.toString(),{method:"POST",headers:{"x-upos-auth":W.auth,"content-type":"application/json; charset=UTF-8"},body:JSON.stringify({parts:J})})).json();if($.OK!==1)return{error:"complete 失败:"+JSON.stringify($)};var G=String($.key).replace(/^\//,"").replace(/\.[^.]+$/,"");return{filename:G,cid:W.bizId}}catch(X){return{error:"complete 异常:"+String(X&&X.message||X)}}}()}function j(W){return async function(){try{var J=await fetch("https://member.bilibili.com/x/vupre/web/topic/type?type_id="+encodeURIComponent(W.tid)+"&pn=0&ps=200",{credentials:"include"}),Z=await J.json();if(!Z||Z.code!==0||!Z.data||!Array.isArray(Z.data.topics))return{error:"获取话题列表失败:"+(Z&&Z.message||"code="+(Z&&Z.code))};var Y=null;for(var $=0;$<Z.data.topics.length;$++)if(Number(Z.data.topics[$].topic_id)===Number(W.topicId)){Y=Z.data.topics[$];break}if(!Y)return{error:"topic_id "+W.topicId+" 不在分区 "+W.tid+" 的可用话题里(用 bilibili topics --tid "+W.tid+" 查合法值)"};return{topicId:Number(Y.topic_id),missionId:Y.mission_id!=null?Number(Y.mission_id):null,name:Y.topic_name||""}}catch(G){return{error:"解析话题异常:"+String(G&&G.message||G)}}}()}function o(W){return async function(){var J=document.cookie.match(/(?:^|;\s*)bili_jct=([^;]+)/);if(!J)return{error:"未找到 bili_jct CSRF token"};var Z=decodeURIComponent(J[1]);try{var Y=W.dataUri;if(typeof Y==="string"&&Y.indexOf("data:image/webp")===0){var $=Y.indexOf(",");if($===-1)return{error:"封面 data URI 格式非法"};var G=Y.slice(5,$),X=Y.slice($+1),K;if(G.indexOf("base64")!==-1){var O=atob(X);K=new Uint8Array(O.length);for(var H=0;H<O.length;H++)K[H]=O.charCodeAt(H)}else K=new TextEncoder().encode(decodeURIComponent(X));var T=new Blob([K],{type:"image/webp"}),R=await createImageBitmap(T),S=document.createElement("canvas");S.width=R.width;S.height=R.height;var Q=S.getContext("2d");Q.fillStyle="#ffffff";Q.fillRect(0,0,S.width,S.height);Q.drawImage(R,0,0);var q=await new Promise(function(F){S.toBlob(F,"image/jpeg",0.92)});if(!q)return{error:"封面 webp 转码 JPEG 失败"};Y=await new Promise(function(F,_){var A=new FileReader;A.onload=function(){F(String(A.result))};A.onerror=function(){_(Error("FileReader 读取转码结果失败"))};A.readAsDataURL(q)})}var B=new URLSearchParams;B.append("cover",Y);B.append("csrf",Z);var V=await(await fetch("https://member.bilibili.com/x/vu/web/cover/up",{method:"POST",credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:B.toString()})).json();if(!V||V.code!==0||!V.data||!V.data.url)return{error:"封面上传失败:"+(V&&V.message?V.message:JSON.stringify(V))};return{url:V.data.url}}catch(F){return{error:"封面上传异常:"+String(F&&F.message||F)}}}()}function p(W){return async function(){var J=document.cookie.match(/(?:^|;\s*)bili_jct=([^;]+)/);if(!J)return{error:"未找到 bili_jct CSRF token"};var Z=decodeURIComponent(J[1]),Y=W.meta;for(var $ in Y)if(Y[$]===null||Y[$]===void 0)delete Y[$];Y.csrf=Z;try{var G=await fetch("https://member.bilibili.com/x/vu/web/add/v3?csrf="+encodeURIComponent(Z)+"&t="+Date.now(),{method:"POST",credentials:"include",headers:{"Content-Type":"application/json;charset=UTF-8"},body:JSON.stringify(Y)}),X=await G.json();if(!X||X.code!==0)return{error:"add/v3 提交失败:"+(X&&X.message?X.message:"")+"(code="+(X&&X.code)+" status="+G.status+")"};return{bvid:X.data&&X.data.bvid,aid:X.data&&X.data.aid}}catch(K){return{error:"add/v3 异常:"+String(K&&K.message||K)}}}()}function g(W){return async function(){var J=document.cookie.match(/(?:^|;\s*)bili_jct=([^;]+)/);if(!J)return{error:"未找到 bili_jct CSRF token"};var Z=decodeURIComponent(J[1]),Y=W.body;Y.csrf=Z;try{var $=await fetch("https://member.bilibili.com/x/vupre/web/draft/add?t="+Date.now()+"&csrf="+encodeURIComponent(Z),{method:"POST",credentials:"include",headers:{"Content-Type":"application/json;charset=UTF-8"},body:JSON.stringify(Y)}),G=await $.json();if(!G||G.code!==0)return{error:"draft/add 保存草稿失败:"+(G&&G.message?G.message:"")+"(code="+(G&&G.code)+" status="+$.status+")"};return{data:G.data}}catch(X){return{error:"draft/add 异常:"+String(X&&X.message||X)}}}()}const N=(W,J,Z)=>W.evaluateWithArgs(`(${J.toString()})(a)`,{a:Z});function s(W){const J=u(W),Z=P.extname(J).toLowerCase(),Y=Z===".png"?"image/png":Z===".webp"?"image/webp":Z===".bmp"?"image/bmp":"image/jpeg",$=I.readFileSync(J).toString("base64");return`data:${Y};base64,${$}`}async function t(W){for(let J=0;J<8;J++){await W.goto(m);await W.wait({time:1});let Z="";try{Z=String(await W.evaluate("location.href")||"")}catch{Z=""}if(/^https?:\/\/member\.bilibili\.com\//.test(Z)){const Y=await W.getCookies({url:"https://member.bilibili.com"}),$=new Map;for(const X of Array.isArray(Y)?Y:[])if(X&&typeof X.name==="string")$.set(X.name,String(X.value??""));const G=k.filter((X)=>!$.get(X));if(!G.length)return $;throw new h("member.bilibili.com",`缺少 B站登录 cookie(${G.join(", ")})。请先在客户端登录哔哩哔哩。`)}}throw new h("member.bilibili.com","无法进入 B站创作中心(可能未登录或被风控)。请先在客户端登录哔哩哔哩。")}E({site:"bilibili",name:"upload",access:"write",description:"投稿视频到 B站(在真实浏览器登录态里走 web 投稿:preupload→upos 分块→add/v3)。默认仅 dry-run 校验,加 --execute 才真正上传并提交。",domain:"member.bilibili.com",strategy:b.COOKIE,browser:!0,args:[{name:"file",required:!0,positional:!0,help:"视频文件路径"},{name:"title",help:"稿件标题(默认取文件名)"},{name:"tid",type:"number",required:!0,help:"分区 id(B站 typeid,必填,禁止臆造)。合法值用 `bilibili partitions` 列举后取。"},{name:"tag",required:!0,help:"标签,逗号分隔(B站要求至少 1 个)"},{name:"desc",fromFile:"desc-file",help:"简介"},{name:"topic",type:"number",help:"参与话题的 topic_id(重要流量入口)。合法值用 `bilibili topics --tid <tid>` 列举,禁止臆造;mission_id 自动匹配。"},{name:"cover",help:"封面图片路径(不传由 B站自动截取)"},{name:"copyright",type:"number",help:"1=自制 2=转载(默认 1)"},{name:"source",help:"转载来源 URL(copyright=2 时必填)"},{name:"dynamic",help:"同步发布的动态文案(可空)"},{name:"no-reprint",type:"boolean",help:"禁止转载(默认允许)"},{name:"dtime",type:"number",help:"定时发布的 10 位 unix 时间戳(可空=立即)"},{name:"line",help:`上传线路:${Object.keys(U).join("/")}(默认 ${f})`},{name:"concurrency",type:"number",help:"分块并发数(默认 3)"},{name:"draft",type:"boolean",help:"存草稿而非直接发布(上传视频后存到创作中心草稿箱,不公开)"},{name:"execute",type:"boolean",help:"真正发布投稿;不带(也不带 --draft)则只做 dry-run 校验"}],columns:["status","title","bvid","url"],func:async(W,J)=>{if(!W)throw new L("bilibili upload 需要浏览器会话");const Z=l(String(J.file??"")),Y=String(J.title??"").trim()||P.basename(Z).replace(/\.[^.]+$/,""),$=String(J.tag??"").split(",").map((z)=>z.trim()).filter(Boolean);if(!$.length)throw new D("B站投稿至少需要 1 个标签,用 --tag 传(逗号分隔)");if(J.tid==null||J.tid===""||!Number.isFinite(Number(J.tid)))throw new D("必须用 --tid 指定分区 id(数字)。合法值用 `bilibili partitions` 列举,禁止臆造。");const G=Number(J.tid),X=J.copyright!=null&&J.copyright!==""?Number(J.copyright):1;if(X!==1&&X!==2)throw new D("--copyright 只能是 1(自制) 或 2(转载)");const K=String(J.source??"").trim();if(X===2&&!K)throw new D("转载(copyright=2)必须用 --source 提供转载来源");if(J.cover)u(String(J.cover));const O=String(J.line??f),H=U[O];if(!H)throw new D(`未知上传线路「${O}」,可选:${Object.keys(U).join("/")}`);const T=J.concurrency!=null&&J.concurrency!==""?Number(J.concurrency):3,R=Boolean(J.draft),S=Boolean(J.execute)&&!R;await t(W);await W.evaluate(`(() => { var id=${JSON.stringify(x)}; var el=document.getElementById(id); if(!el){ el=document.createElement('input'); el.type='file'; el.id=id; el.style.cssText='position:fixed;left:-9999px;top:0;'; document.body.appendChild(el);} el.value=''; return true; })()`);await w(W,`#${x}`,[Z]);if(!R&&!S)return{status:"dry-run",title:Y,bvid:"",url:`校验通过:登录态有效、视频已注入就绪、分区 ${G}、标签 [${$.join(", ")}]、版权 ${X===1?"自制":"转载"}。加 --execute 发布,或 --draft 存草稿。`};const Q=await N(W,n,{inputId:x,line:H});if(!Q||Q.error)throw new L(`B站 preupload 失败:${Q&&Q.error||"无返回"}`);for(let z=0;z<Q.total;z++){const M=await N(W,d,{inputId:x,url:Q.url,auth:Q.auth,uploadId:Q.uploadId,idx:z,total:Q.total,size:Q.size,chunkSize:Q.chunkSize});if(!M||!M.ok)throw new L(`B站分块上传失败(${z+1}/${Q.total}):${M&&M.error||"无返回"}`)}const q=await N(W,c,{url:Q.url,auth:Q.auth,uploadId:Q.uploadId,bizId:Q.bizId,name:Q.name,total:Q.total});if(!q||q.error||!q.filename)throw new L(`B站 complete 失败:${q&&q.error||"未拿到 filename"}`);let B="";if(J.cover){const z=s(String(J.cover)),M=await N(W,o,{dataUri:z});if(!M||M.error||!M.url)throw new L(`B站封面上传失败:${M&&M.error||"未拿到 url"}`);B=M.url}let V=null,F=null;if(J.topic!=null&&J.topic!==""){if(!Number.isFinite(Number(J.topic)))throw new D("--topic 必须是 topic_id 数字,用 `bilibili topics --tid <tid>` 取合法值");const z=await N(W,j,{tid:G,topicId:Number(J.topic)});if(!z||z.error)throw new L(`B站话题解析失败:${z&&z.error||"无返回"}`);V=z.topicId;F=z.missionId}if(R){const z={videos:[{filename:q.filename,title:Y,desc:"",cid:q.cid,is_4k:!1,is_8k:!1,is_hdr:!1}],cover:B,cover43:B,ai_cover:0,is_ab_cover:0,ab_cover_info:null,title:Y,copyright:X,tid:G,tag:$.join(","),desc:String(J.desc??""),recreate:-1,dynamic:String(J.dynamic??""),is_only_self:0,space_hidden:2,watermark:{state:0},no_reprint:J["no-reprint"]?1:0,subtitle:{open:0,lan:""},dolby:0,lossless_music:0,up_selection_reply:!1,up_close_reply:!1,up_close_danmu:!1};if(X===2)z.source=K;if(V!=null){z.topic_id=V;z.topic_detail={from_topic_id:V,from_source:"arc.web.search"};if(F!=null)z.mission_id=F}const M=await N(W,g,{body:z});if(!M||M.error)throw new L(`B站 ${M&&M.error||"draft/add 无返回"}`);const y=M.data&&(M.data.aid!=null?M.data.aid:M.data.draft_id!=null?M.data.draft_id:M.data.id);return{status:"✅ 草稿已保存",title:Y,bvid:y!=null?`draft:${y}`:"",url:"https://member.bilibili.com/platform/upload-manager/article?group=draft"}}const _=J.dtime!=null&&J.dtime!==""?Number(J.dtime):null,A={title:Y,copyright:X,tid:G,tag:$.join(","),mission_id:F,topic_id:V,topic_detail:V!=null?{from_topic_id:V,from_source:"arc.web.recommend"}:null,desc_format_id:9999,desc:String(J.desc??""),dtime:_,recreate:-1,dynamic:String(J.dynamic??""),interactive:0,act_reserve_create:0,no_disturbance:0,porder:null,adorder_type:9,no_reprint:J["no-reprint"]?1:0,subtitle:{open:0,lan:""},neutral_mark:null,dolby:0,lossless_music:0,up_selection_reply:!1,up_close_reply:!1,up_close_danmu:!1,web_os:1,source:X===2?K:null,watermark:{state:0},cover:B,videos:[{title:Y,desc:"",filename:q.filename,cid:q.cid}]},C=await N(W,p,{meta:A});if(!C||C.error)throw new L(`B站 ${C&&C.error||"add/v3 无返回"}`);const v=C.bvid?String(C.bvid):"";return{status:J.dtime?"✅ 定时投稿已提交":"✅ 投稿成功",title:Y,bvid:v,url:v?`https://www.bilibili.com/video/${v}`:"(已提交,稍后在创作中心查看)"}}});
@@ -1 +1 @@
1
- import*as U from"node:fs";import*as y from"node:path";import{cli as f,Strategy as C}from"@jackwener/opencli/registry";import{AuthRequiredError as W,ArgumentError as N,CommandExecutionError as H}from"@jackwener/opencli/errors";import{assertLiteralContent as L}from"../_shared/content-guard.js";import{IMAGES_EMBED_TYPE as R,MAX_BLOB_BYTES as j,MAX_IMAGES as G,MAX_POST_GRAPHEMES as Y,POST_COLLECTION as X,POST_TYPE as O,graphemeLength as B,loadBskySession as I,postUriToWebUrl as T,uploadBlobWithRefresh as x,xrpcJsonWithRefresh as S}from"./shared.js";const b={".jpg":"image/jpeg",".jpeg":"image/jpeg",".png":"image/png",".gif":"image/gif",".webp":"image/webp"};function M(v,K){const Z=String(v).split(",").map((Q)=>Q.trim()).filter(Boolean);if(Z.length===0)return[];if(Z.length>G)throw new N(`图片过多:${Z.length} 张(Bluesky 单帖最多 ${G} 张)`);const D=K!=null&&String(K).length>0?String(K).split(",").map((Q)=>Q.trim()):[];return Z.map((Q,J)=>{const z=y.resolve(Q),$=y.extname(z).toLowerCase(),F=b[$];if(!F)throw new N(`不支持的图片格式 "${$}"。支持:jpg / png / gif / webp`);const V=U.statSync(z,{throwIfNoEntry:!1});if(!V||!V.isFile())throw new N(`不是有效文件:${z}`);if(V.size>j)throw new N(`图片超过 ${j} 字节上限(${z} 为 ${V.size} 字节)。请先本地压缩后再发。`);return{absPath:z,mime:F,alt:D[J]||""}})}async function P(v,K,Z){const D=[];for(const Q of Z){const J=U.readFileSync(Q.absPath).toString("base64"),z=await x(v,K,{base64:J,mime:Q.mime});if(z&&(z.httpStatus===401||z.httpStatus===403))throw new W("bsky.app",`uploadBlob 返回 HTTP ${z.httpStatus}`);const $=z&&z.bodyJson&&z.bodyJson.blob;if(!z||!z.ok||!$){const F=z&&z.bodyJson&&(z.bodyJson.message||z.bodyJson.error)||z?.error||`HTTP ${z?.httpStatus??0}`;throw new H(`图片上传失败(${Q.absPath}):${F}`)}D.push({alt:Q.alt,image:$})}return{$type:R,images:D}}f({site:"bluesky",name:"post",access:"write",description:"发布一条 Bluesky 帖子(支持文字 + 最多 4 张图)",domain:"bsky.app",strategy:C.UI,browser:!0,args:[{name:"text",type:"string",required:!0,positional:!0,help:"帖子正文(≤300 graphemes)"},{name:"images",type:"string",required:!1,help:"图片路径,逗号分隔,最多 4 张(jpg/png/gif/webp,每张≤1MB)"},{name:"alts",type:"string",required:!1,help:"图片 alt 文本,逗号分隔,与 images 一一对应(可选)"},{name:"langs",type:"string",required:!1,help:"语言标签,逗号分隔(如 zh,en);不传则不标注语言"}],columns:["status","message","text","uri","cid","url"],func:async(v,K)=>{if(!v)throw new H("Bluesky post 需要浏览器会话");const Z=String(K.text??"");L(Z,{label:"帖子正文"});if(Z.trim().length===0)throw new N("帖子正文不能为空");const D=B(Z);if(D>Y)throw new N(`帖子正文 ${D} graphemes,超过 Bluesky 上限 ${Y}`);const Q=K.images?M(String(K.images),K.alts):[],J=K.langs!=null&&String(K.langs).length>0?String(K.langs).split(",").map((q)=>q.trim()).filter(Boolean):[],z=await I(v),$={$type:O,text:Z,createdAt:new Date().toISOString()};if(J.length>0)$.langs=J;if(Q.length>0)$.embed=await P(v,z,Q);const F=await S(v,z,{path:"com.atproto.repo.createRecord",method:"POST",body:{repo:z.did,collection:X,record:$}});if(F&&(F.httpStatus===401||F.httpStatus===403))throw new W("bsky.app",`createRecord 返回 HTTP ${F.httpStatus}`);const V=F&&F.bodyJson;if(!F||!F.ok||!V||!V.uri){const q=V&&(V.message||V.error)||F?.error||`HTTP ${F?.httpStatus??0}`;throw new H(`发布失败:${q}`)}return[{status:"success",message:"帖子发布成功",text:Z,uri:String(V.uri),cid:String(V.cid||""),url:T(V.uri,z.handle)}]}});
1
+ import*as U from"node:fs";import*as y from"node:path";import{cli as f,Strategy as C}from"@jackwener/opencli/registry";import{AuthRequiredError as W,ArgumentError as N,CommandExecutionError as H}from"@jackwener/opencli/errors";import{assertLiteralContent as L}from"../_shared/content-guard.js";import{IMAGES_EMBED_TYPE as R,MAX_BLOB_BYTES as j,MAX_IMAGES as G,MAX_POST_GRAPHEMES as Y,POST_COLLECTION as X,POST_TYPE as O,graphemeLength as B,loadBskySession as I,postUriToWebUrl as T,uploadBlobWithRefresh as x,xrpcJsonWithRefresh as S}from"./shared.js";const b={".jpg":"image/jpeg",".jpeg":"image/jpeg",".png":"image/png",".gif":"image/gif",".webp":"image/webp"};function M(v,K){const Z=String(v).split(",").map((Q)=>Q.trim()).filter(Boolean);if(Z.length===0)return[];if(Z.length>G)throw new N(`图片过多:${Z.length} 张(Bluesky 单帖最多 ${G} 张)`);const D=K!=null&&String(K).length>0?String(K).split(",").map((Q)=>Q.trim()):[];return Z.map((Q,J)=>{const z=y.resolve(Q),$=y.extname(z).toLowerCase(),F=b[$];if(!F)throw new N(`不支持的图片格式 "${$}"。支持:jpg / png / gif / webp`);const V=U.statSync(z,{throwIfNoEntry:!1});if(!V||!V.isFile())throw new N(`不是有效文件:${z}`);if(V.size>j)throw new N(`图片超过 ${j} 字节上限(${z} 为 ${V.size} 字节)。请先本地压缩后再发。`);return{absPath:z,mime:F,alt:D[J]||""}})}async function P(v,K,Z){const D=[];for(const Q of Z){const J=U.readFileSync(Q.absPath).toString("base64"),z=await x(v,K,{base64:J,mime:Q.mime});if(z&&(z.httpStatus===401||z.httpStatus===403))throw new W("bsky.app",`uploadBlob 返回 HTTP ${z.httpStatus}`);const $=z&&z.bodyJson&&z.bodyJson.blob;if(!z||!z.ok||!$){const F=z&&z.bodyJson&&(z.bodyJson.message||z.bodyJson.error)||z?.error||`HTTP ${z?.httpStatus??0}`;throw new H(`图片上传失败(${Q.absPath}):${F}`)}D.push({alt:Q.alt,image:$})}return{$type:R,images:D}}f({site:"bluesky",name:"post",access:"write",description:"发布一条 Bluesky 帖子(支持文字 + 最多 4 张图)",domain:"bsky.app",strategy:C.UI,browser:!0,args:[{name:"text",fromFile:!0,type:"string",required:!0,positional:!0,help:"帖子正文(≤300 graphemes)"},{name:"images",type:"string",required:!1,help:"图片路径,逗号分隔,最多 4 张(jpg/png/gif/webp,每张≤1MB)"},{name:"alts",type:"string",required:!1,help:"图片 alt 文本,逗号分隔,与 images 一一对应(可选)"},{name:"langs",type:"string",required:!1,help:"语言标签,逗号分隔(如 zh,en);不传则不标注语言"}],columns:["status","message","text","uri","cid","url"],func:async(v,K)=>{if(!v)throw new H("Bluesky post 需要浏览器会话");const Z=String(K.text??"");L(Z,{label:"帖子正文"});if(Z.trim().length===0)throw new N("帖子正文不能为空");const D=B(Z);if(D>Y)throw new N(`帖子正文 ${D} graphemes,超过 Bluesky 上限 ${Y}`);const Q=K.images?M(String(K.images),K.alts):[],J=K.langs!=null&&String(K.langs).length>0?String(K.langs).split(",").map((q)=>q.trim()).filter(Boolean):[],z=await I(v),$={$type:O,text:Z,createdAt:new Date().toISOString()};if(J.length>0)$.langs=J;if(Q.length>0)$.embed=await P(v,z,Q);const F=await S(v,z,{path:"com.atproto.repo.createRecord",method:"POST",body:{repo:z.did,collection:X,record:$}});if(F&&(F.httpStatus===401||F.httpStatus===403))throw new W("bsky.app",`createRecord 返回 HTTP ${F.httpStatus}`);const V=F&&F.bodyJson;if(!F||!F.ok||!V||!V.uri){const q=V&&(V.message||V.error)||F?.error||`HTTP ${F?.httpStatus??0}`;throw new H(`发布失败:${q}`)}return[{status:"success",message:"帖子发布成功",text:Z,uri:String(V.uri),cid:String(V.cid||""),url:T(V.uri,z.handle)}]}});
@@ -1 +1 @@
1
- import{cli as l,Strategy as u}from"@jackwener/opencli/registry";import{requirePage as f,navigateToChat as h,fetchRecommendList as b,clickCandidateInList as g,typeAndSendMessage as p,verbose as w}from"./utils.js";l({site:"boss",name:"batchgreet",access:"write",description:"BOSS直聘批量向推荐候选人发送招呼",domain:"www.zhipin.com",strategy:u.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"job-id",default:"",help:"Filter by encrypted job ID (greet all jobs if empty)"},{name:"limit",type:"int",default:5,help:"Max candidates to greet"},{name:"text",default:"",help:"Custom greeting message (uses default if empty)"}],columns:["name","status","detail"],func:async(t,n)=>{f(t);const c=n["job-id"]||"",d=n.limit||5,o=n.text||"你好,请问您对这个职位感兴趣吗?";w(`Batch greeting up to ${d} candidates...`);await h(t,3);let e=await b(t);if(c)e=e.filter((i)=>i.encryptJobId===c);e=e.slice(0,d);if(e.length===0)return[{name:"-",status:"⚠️ 无候选人",detail:"当前没有待招呼的推荐候选人"}];const a=[];for(const i of e){const m=i.uid,s=i.name||"候选人";try{if(!await g(t,m)){a.push({name:s,status:"❌ 跳过",detail:"在聊天列表中未找到"});continue}await t.wait({time:2});if(!await p(t,o)){a.push({name:s,status:"❌ 失败",detail:"找不到消息输入框"});continue}await t.wait({time:1.5});a.push({name:s,status:"✅ 已发送",detail:o})}catch(r){a.push({name:s,status:"❌ 失败",detail:r.message?.substring(0,80)||"未知错误"})}}return a}});
1
+ import{cli as l,Strategy as u}from"@jackwener/opencli/registry";import{requirePage as f,navigateToChat as h,fetchRecommendList as b,clickCandidateInList as g,typeAndSendMessage as p,verbose as w}from"./utils.js";l({site:"boss",name:"batchgreet",access:"write",description:"BOSS直聘批量向推荐候选人发送招呼",domain:"www.zhipin.com",strategy:u.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"job-id",default:"",help:"Filter by encrypted job ID (greet all jobs if empty)"},{name:"limit",type:"int",default:5,help:"Max candidates to greet"},{name:"text",fromFile:!0,default:"",help:"Custom greeting message (uses default if empty)"}],columns:["name","status","detail"],func:async(t,n)=>{f(t);const c=n["job-id"]||"",d=n.limit||5,r=n.text||"你好,请问您对这个职位感兴趣吗?";w(`Batch greeting up to ${d} candidates...`);await h(t,3);let e=await b(t);if(c)e=e.filter((i)=>i.encryptJobId===c);e=e.slice(0,d);if(e.length===0)return[{name:"-",status:"⚠️ 无候选人",detail:"当前没有待招呼的推荐候选人"}];const a=[];for(const i of e){const m=i.uid,s=i.name||"候选人";try{if(!await g(t,m)){a.push({name:s,status:"❌ 跳过",detail:"在聊天列表中未找到"});continue}await t.wait({time:2});if(!await p(t,r)){a.push({name:s,status:"❌ 失败",detail:"找不到消息输入框"});continue}await t.wait({time:1.5});a.push({name:s,status:"✅ 已发送",detail:r})}catch(o){a.push({name:s,status:"❌ 失败",detail:o.message?.substring(0,80)||"未知错误"})}}return a}});
@@ -1 +1 @@
1
- import{cli as K,Strategy as O}from"@jackwener/opencli/registry";import{requirePage as Q,navigateToChat as R,findFriendByUid as V,clickCandidateInList as W,typeAndSendMessage as X,verbose as Y}from"./utils.js";K({site:"boss",name:"greet",access:"write",description:"BOSS直聘向新候选人发送招呼(开始聊天)",domain:"www.zhipin.com",strategy:O.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"uid",positional:!0,required:!0,help:"Encrypted UID of the candidate (from recommend)"},{name:"security-id",required:!0,help:"Security ID of the candidate"},{name:"job-id",required:!0,help:"Encrypted job ID"},{name:"text",default:"",help:"Custom greeting message (uses default template if empty)"}],columns:["status","detail"],func:async(z,D)=>{Q(z);Y(`Greeting candidate ${D.uid}...`);await R(z,3);const E=await V(z,D.uid,{maxPages:1,checkGreetList:!0});if(!E)throw Error("未找到该候选人,请确认 uid 是否正确(可从 recommend 命令获取)");const H=E.uid,J=E.name||"候选人";if(!await W(z,H))throw Error("无法在聊天列表中找到该用户,候选人可能不在当前列表中");await z.wait({time:2});const G=D.text||"你好,请问您对这个职位感兴趣吗?";if(!await X(z,G))throw Error("找不到消息输入框");await z.wait({time:1});return[{status:"✅ 招呼已发送",detail:`已向 ${J} 发送: ${G}`}]}});
1
+ import{cli as K,Strategy as O}from"@jackwener/opencli/registry";import{requirePage as Q,navigateToChat as R,findFriendByUid as V,clickCandidateInList as W,typeAndSendMessage as X,verbose as Y}from"./utils.js";K({site:"boss",name:"greet",access:"write",description:"BOSS直聘向新候选人发送招呼(开始聊天)",domain:"www.zhipin.com",strategy:O.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"uid",positional:!0,required:!0,help:"Encrypted UID of the candidate (from recommend)"},{name:"security-id",required:!0,help:"Security ID of the candidate"},{name:"job-id",required:!0,help:"Encrypted job ID"},{name:"text",fromFile:!0,default:"",help:"Custom greeting message (uses default template if empty)"}],columns:["status","detail"],func:async(z,D)=>{Q(z);Y(`Greeting candidate ${D.uid}...`);await R(z,3);const E=await V(z,D.uid,{maxPages:1,checkGreetList:!0});if(!E)throw Error("未找到该候选人,请确认 uid 是否正确(可从 recommend 命令获取)");const H=E.uid,J=E.name||"候选人";if(!await W(z,H))throw Error("无法在聊天列表中找到该用户,候选人可能不在当前列表中");await z.wait({time:2});const G=D.text||"你好,请问您对这个职位感兴趣吗?";if(!await X(z,G))throw Error("找不到消息输入框");await z.wait({time:1});return[{status:"✅ 招呼已发送",detail:`已向 ${J} 发送: ${G}`}]}});
package/clis/boss/send.js CHANGED
@@ -1 +1 @@
1
- import{cli as n,Strategy as o}from"@jackwener/opencli/registry";import{requirePage as d,navigateToChat as c,findFriendByUid as m,clickCandidateInList as u,typeAndSendMessage as h}from"./utils.js";import{EmptyResultError as l,selectorError as a}from"@jackwener/opencli/errors";n({site:"boss",name:"send",access:"write",description:"BOSS直聘发送聊天消息",domain:"www.zhipin.com",strategy:o.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"uid",positional:!0,required:!0,help:"Encrypted UID of the candidate (from chatlist)"},{name:"text",required:!0,positional:!0,help:"Message text to send"}],columns:["status","detail"],func:async(t,e)=>{d(t);await c(t,3);const i=await m(t,e.uid,{maxPages:5});if(!i)throw new l("boss candidate search","请确认 uid 是否正确");const r=i.uid,s=i.name||"候选人";if(!await u(t,r))throw a("聊天列表中的用户","请确认聊天列表中有此人");await t.wait({time:2});if(!await h(t,e.text))throw a("消息输入框","聊天页面 UI 可能已改变");await t.wait({time:1});return[{status:"✅ 发送成功",detail:`已向 ${s} 发送: ${e.text}`}]}});
1
+ import{cli as n,Strategy as o}from"@jackwener/opencli/registry";import{requirePage as d,navigateToChat as c,findFriendByUid as m,clickCandidateInList as u,typeAndSendMessage as l}from"./utils.js";import{EmptyResultError as h,selectorError as a}from"@jackwener/opencli/errors";n({site:"boss",name:"send",access:"write",description:"BOSS直聘发送聊天消息",domain:"www.zhipin.com",strategy:o.COOKIE,navigateBefore:!1,browser:!0,args:[{name:"uid",positional:!0,required:!0,help:"Encrypted UID of the candidate (from chatlist)"},{name:"text",fromFile:!0,required:!0,positional:!0,help:"Message text to send"}],columns:["status","detail"],func:async(t,e)=>{d(t);await c(t,3);const i=await m(t,e.uid,{maxPages:5});if(!i)throw new h("boss candidate search","请确认 uid 是否正确");const r=i.uid,s=i.name||"候选人";if(!await u(t,r))throw a("聊天列表中的用户","请确认聊天列表中有此人");await t.wait({time:2});if(!await l(t,e.text))throw a("消息输入框","聊天页面 UI 可能已改变");await t.wait({time:1});return[{status:"✅ 发送成功",detail:`已向 ${s} 发送: ${e.text}`}]}});
@@ -1 +1 @@
1
- import{cli as j,Strategy as z}from"@jackwener/opencli/registry";import{ArgumentError as f,CommandExecutionError as L}from"@jackwener/opencli/errors";import{CHATGPT_DOMAIN as B,CHATGPT_URL as I,currentChatGPTUrl as M,ensureChatGPTComposer as R,ensureOnChatGPT as A,getChatGPTResponsePairCounts as S,getVisibleMessages as b,normalizeBooleanFlag as Y,openChatGPTConversation as F,requireNonEmptyPrompt as U,requirePositiveInt as E,parseChatGPTConversationId as G,sendChatGPTMessage as v,selectChatGPTTool as d,isGenerating as h,startNewChat as P,navigateToProject as T,waitForChatGPTResponse as C}from"./utils.js";async function m(K,J=30){const Q=Date.now();while(Date.now()-Q<J*1000){const X=await M(K);try{return{conversationId:G(X),conversationUrl:X}}catch{await K.wait(1)}}throw new L("ChatGPT did not create a conversation URL after sending the message.")}export const askCommand=j({site:"chatgpt",name:"ask",access:"write",description:"Send a prompt to ChatGPT web and wait for the response",domain:B,strategy:z.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,args:[{name:"prompt",positional:!0,required:!0,help:"Prompt to send"},{name:"timeout",type:"int",default:120,help:"Max seconds to wait for response"},{name:"new",type:"boolean",default:!1,help:"Start a new chat before sending"},{name:"conversation",valueRequired:!0,help:"Continue an existing ChatGPT conversation ID or /c/<id> URL"},{name:"project",valueRequired:!0,help:"Start a new chat inside a ChatGPT project ID or /g/g-p-<id> URL"},{name:"wait",type:"boolean",default:!0,help:"Wait for the assistant response after sending"},{name:"deep-research",type:"boolean",default:!1,help:"Enable ChatGPT 深度研究 (Deep Research)"},{name:"web-search",type:"boolean",default:!1,help:"Enable ChatGPT 网页搜索 (Web Search)"}],columns:["conversationId","conversationUrl","tool","response"],func:async(K,J)=>{const Q=U(J.prompt,"chatgpt ask"),X=E(Number(J.timeout??120),"chatgpt ask --timeout",'Example: ppcli chatgpt ask "hello" --timeout 120'),Z=Y(J["deep-research"],!1),V=Y(J["web-search"],!1),x=Y(J.wait,!0);if(Z&&V)throw new f("chatgpt ask cannot enable both --deep-research and --web-search","Choose one ChatGPT composer tool for this message.");if(Y(J.new)&&J.conversation)throw new f("chatgpt ask cannot use --new and --conversation together","Choose either a new chat or an existing conversation.");if(J.project&&J.conversation)throw new f("chatgpt ask cannot use --project and --conversation together","Choose either a project new chat or an existing conversation.");const q=Z?"deep-research":V?"web-search":null;if(J.conversation)await F(K,J.conversation);else if(J.project)await T(K,J.project);else if(Y(J.new))await P(K);else await A(K);await R(K,"ChatGPT ask requires a logged-in ChatGPT session with a visible composer.");const H=q?await d(K,q):null,y=Date.now();while(await h(K)){if(Date.now()-y>X*1000)throw new L("ChatGPT conversation is still generating; wait for it to finish before sending another message.");await K.wait(3)}const O=await b(K),D=O.length,N=S(O,Q);if(!await v(K,Q))throw new L("Failed to send message to ChatGPT",`Open ${I} and verify the composer is ready.`);const{conversationId:_,conversationUrl:$}=await m(K);if(!x)return[{conversationId:_,conversationUrl:$,tool:H?.Tool??"",response:""}];const W=await C(K,D,Q,X,{baselinePairCounts:N,conversationUrl:$});return[{conversationId:_,conversationUrl:$,tool:H?.Tool??"",response:W}]}});
1
+ import{cli as j,Strategy as z}from"@jackwener/opencli/registry";import{ArgumentError as f,CommandExecutionError as L}from"@jackwener/opencli/errors";import{CHATGPT_DOMAIN as B,CHATGPT_URL as I,currentChatGPTUrl as M,ensureChatGPTComposer as R,ensureOnChatGPT as A,getChatGPTResponsePairCounts as S,getVisibleMessages as b,normalizeBooleanFlag as Y,openChatGPTConversation as F,requireNonEmptyPrompt as U,requirePositiveInt as E,parseChatGPTConversationId as G,sendChatGPTMessage as v,selectChatGPTTool as d,isGenerating as h,startNewChat as P,navigateToProject as T,waitForChatGPTResponse as C}from"./utils.js";async function m(K,J=30){const Q=Date.now();while(Date.now()-Q<J*1000){const X=await M(K);try{return{conversationId:G(X),conversationUrl:X}}catch{await K.wait(1)}}throw new L("ChatGPT did not create a conversation URL after sending the message.")}export const askCommand=j({site:"chatgpt",name:"ask",access:"write",description:"Send a prompt to ChatGPT web and wait for the response",domain:B,strategy:z.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,args:[{name:"prompt",fromFile:!0,positional:!0,required:!0,help:"Prompt to send"},{name:"timeout",type:"int",default:120,help:"Max seconds to wait for response"},{name:"new",type:"boolean",default:!1,help:"Start a new chat before sending"},{name:"conversation",valueRequired:!0,help:"Continue an existing ChatGPT conversation ID or /c/<id> URL"},{name:"project",valueRequired:!0,help:"Start a new chat inside a ChatGPT project ID or /g/g-p-<id> URL"},{name:"wait",type:"boolean",default:!0,help:"Wait for the assistant response after sending"},{name:"deep-research",type:"boolean",default:!1,help:"Enable ChatGPT 深度研究 (Deep Research)"},{name:"web-search",type:"boolean",default:!1,help:"Enable ChatGPT 网页搜索 (Web Search)"}],columns:["conversationId","conversationUrl","tool","response"],func:async(K,J)=>{const Q=U(J.prompt,"chatgpt ask"),X=E(Number(J.timeout??120),"chatgpt ask --timeout",'Example: ppcli chatgpt ask "hello" --timeout 120'),Z=Y(J["deep-research"],!1),V=Y(J["web-search"],!1),x=Y(J.wait,!0);if(Z&&V)throw new f("chatgpt ask cannot enable both --deep-research and --web-search","Choose one ChatGPT composer tool for this message.");if(Y(J.new)&&J.conversation)throw new f("chatgpt ask cannot use --new and --conversation together","Choose either a new chat or an existing conversation.");if(J.project&&J.conversation)throw new f("chatgpt ask cannot use --project and --conversation together","Choose either a project new chat or an existing conversation.");const q=Z?"deep-research":V?"web-search":null;if(J.conversation)await F(K,J.conversation);else if(J.project)await T(K,J.project);else if(Y(J.new))await P(K);else await A(K);await R(K,"ChatGPT ask requires a logged-in ChatGPT session with a visible composer.");const H=q?await d(K,q):null,y=Date.now();while(await h(K)){if(Date.now()-y>X*1000)throw new L("ChatGPT conversation is still generating; wait for it to finish before sending another message.");await K.wait(3)}const O=await b(K),D=O.length,N=S(O,Q);if(!await v(K,Q))throw new L("Failed to send message to ChatGPT",`Open ${I} and verify the composer is ready.`);const{conversationId:_,conversationUrl:$}=await m(K);if(!x)return[{conversationId:_,conversationUrl:$,tool:H?.Tool??"",response:""}];const W=await C(K,D,Q,X,{baselinePairCounts:N,conversationUrl:$});return[{conversationId:_,conversationUrl:$,tool:H?.Tool??"",response:W}]}});
@@ -1 +1 @@
1
- import*as H from"node:os";import*as $ from"node:path";import*as I from"node:fs";import{cli as f,Strategy as E}from"@jackwener/opencli/registry";import{saveBase64ToFile as T}from"@jackwener/opencli/utils";import{ArgumentError as y,CommandExecutionError as _,EmptyResultError as v}from"@jackwener/opencli/errors";import{clearChatGPTDraft as C,getChatGPTVisibleImageUrls as x,navigateToProject as k,normalizeBooleanFlag as c,prepareChatGPTImagePaths as w,sendChatGPTMessage as P,unwrapEvaluateResult as h,waitForChatGPTImages as d,getChatGPTImageAssets as u,uploadChatGPTImages as n}from"./utils.js";const N="chatgpt.com";function m(q){if(q.includes("png"))return".png";if(q.includes("webp"))return".webp";if(q.includes("gif"))return".gif";return".jpg"}function l(q){const J=H.homedir();return q.startsWith(J)?`~${q.slice(J.length)}`:q}export function resolveOutputDir(q){const J=String(q||"").trim();if(!J)return $.join(H.homedir(),"Pictures","chatgpt");if(J==="~")return H.homedir();if(J.startsWith("~/"))return $.join(H.homedir(),J.slice(2));return $.resolve(J)}export function nextAvailablePath(q,J,z,W=I.existsSync){let Y=$.join(q,`${J}${z}`);for(let X=1;W(Y);X+=1)Y=$.join(q,`${J}_${X}${z}`);return Y}export function parseImagePaths(q){if(Array.isArray(q))return q.flatMap((J)=>parseImagePaths(J));return String(q??"").split(",").map((J)=>J.trim()).filter(Boolean)}function g(q,J){if(J>0)return`Edit the attached image${J===1?"":"s"}: ${q}`;return`Generate an image of: ${q}`}async function F(q){const J=h(await q.evaluate("window.location.href").catch(()=>""));return typeof J==="string"&&J?J:"https://chatgpt.com"}export const imageCommand=f({site:"chatgpt",name:"image",access:"write",description:"Generate images with ChatGPT web and save them locally",domain:N,strategy:E.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,defaultFormat:"plain",args:[{name:"prompt",positional:!0,required:!0,help:"Image prompt to send to ChatGPT"},{name:"image",help:"Local image path to attach before prompting; comma-separated paths are supported"},{name:"project",valueRequired:!0,help:"Start image generation inside a ChatGPT project ID or /g/g-p-<id> URL"},{name:"op",help:"Output directory (default: ~/Pictures/chatgpt)"},{name:"sd",type:"boolean",default:!1,help:"Skip download shorthand; only show ChatGPT link"},{name:"timeout",type:"int",required:!1,default:240,help:"Max seconds for the overall command (default: 240)"}],columns:["status","file","link"],func:async(q,J)=>{const z=J.prompt,W=parseImagePaths(J.image),Y=resolveOutputDir(J.op),X=J.sd,R=X===""||X===!0||c(X),O=J.timeout;if(!Number.isInteger(O)||O<1)throw new y("--timeout must be a positive integer (seconds)");const S=W.length?await w(W):{ok:!0,paths:[]};if(!S.ok)throw new y(S.reason);if(J.project)await k(q,J.project);else await q.goto(`https://${N}/new`,{settleMs:2000});await C(q);if(W.length){let K;try{K=await n(q,S.paths)}catch(Q){throw new _(`Failed to upload image to ChatGPT: ${Q instanceof Error?Q.message:String(Q)}`)}if(!K?.ok)throw new _(K?.reason||"Failed to upload image to ChatGPT")}const A=await x(q);if(!await P(q,g(z,W.length)))throw new _("Failed to send image prompt to ChatGPT",`Open ${await F(q)} and verify the composer is ready.`);let Z="";for(let K=0;K<10;K++){const Q=await F(q);if(Q.includes("/c/")){Z=Q;break}await q.sleep(2)}if(!Z)Z=await F(q);const L=await d(q,A,O,Z),V=Z;if(!L.length)throw new v("chatgpt image",`No generated images were detected before timeout. Open ${V} and verify whether ChatGPT finished generating the image.`);if(R)return[{status:"\uD83C\uDFA8 generated",file:"\uD83D\uDCC1 -",link:`\uD83D\uDD17 ${V}`}];const B=await u(q,L);if(!B.length)throw new _("Failed to export generated ChatGPT image assets",`Open ${V} and verify the generated images are visible, then retry.`);const U=Date.now(),M=[];for(let K=0;K<B.length;K+=1){const Q=B[K],D=Q.dataUrl.replace(/^data:[^;]+;base64,/,""),b=B.length>1?`_${K+1}`:"",G=m(Q.mimeType),j=nextAvailablePath(Y,`chatgpt_${U}${b}`,G);await T(D,j);M.push({status:"✅ saved",file:`\uD83D\uDCC1 ${l(j)}`,link:`\uD83D\uDD17 ${V}`})}return M}});
1
+ import*as H from"node:os";import*as $ from"node:path";import*as I from"node:fs";import{cli as f,Strategy as E}from"@jackwener/opencli/registry";import{saveBase64ToFile as T}from"@jackwener/opencli/utils";import{ArgumentError as y,CommandExecutionError as _,EmptyResultError as v}from"@jackwener/opencli/errors";import{clearChatGPTDraft as C,getChatGPTVisibleImageUrls as x,navigateToProject as k,normalizeBooleanFlag as c,prepareChatGPTImagePaths as w,sendChatGPTMessage as P,unwrapEvaluateResult as h,waitForChatGPTImages as d,getChatGPTImageAssets as u,uploadChatGPTImages as n}from"./utils.js";const N="chatgpt.com";function m(q){if(q.includes("png"))return".png";if(q.includes("webp"))return".webp";if(q.includes("gif"))return".gif";return".jpg"}function l(q){const J=H.homedir();return q.startsWith(J)?`~${q.slice(J.length)}`:q}export function resolveOutputDir(q){const J=String(q||"").trim();if(!J)return $.join(H.homedir(),"Pictures","chatgpt");if(J==="~")return H.homedir();if(J.startsWith("~/"))return $.join(H.homedir(),J.slice(2));return $.resolve(J)}export function nextAvailablePath(q,J,z,W=I.existsSync){let Y=$.join(q,`${J}${z}`);for(let X=1;W(Y);X+=1)Y=$.join(q,`${J}_${X}${z}`);return Y}export function parseImagePaths(q){if(Array.isArray(q))return q.flatMap((J)=>parseImagePaths(J));return String(q??"").split(",").map((J)=>J.trim()).filter(Boolean)}function g(q,J){if(J>0)return`Edit the attached image${J===1?"":"s"}: ${q}`;return`Generate an image of: ${q}`}async function F(q){const J=h(await q.evaluate("window.location.href").catch(()=>""));return typeof J==="string"&&J?J:"https://chatgpt.com"}export const imageCommand=f({site:"chatgpt",name:"image",access:"write",description:"Generate images with ChatGPT web and save them locally",domain:N,strategy:E.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,defaultFormat:"plain",args:[{name:"prompt",fromFile:!0,positional:!0,required:!0,help:"Image prompt to send to ChatGPT"},{name:"image",help:"Local image path to attach before prompting; comma-separated paths are supported"},{name:"project",valueRequired:!0,help:"Start image generation inside a ChatGPT project ID or /g/g-p-<id> URL"},{name:"op",help:"Output directory (default: ~/Pictures/chatgpt)"},{name:"sd",type:"boolean",default:!1,help:"Skip download shorthand; only show ChatGPT link"},{name:"timeout",type:"int",required:!1,default:240,help:"Max seconds for the overall command (default: 240)"}],columns:["status","file","link"],func:async(q,J)=>{const z=J.prompt,W=parseImagePaths(J.image),Y=resolveOutputDir(J.op),X=J.sd,R=X===""||X===!0||c(X),O=J.timeout;if(!Number.isInteger(O)||O<1)throw new y("--timeout must be a positive integer (seconds)");const S=W.length?await w(W):{ok:!0,paths:[]};if(!S.ok)throw new y(S.reason);if(J.project)await k(q,J.project);else await q.goto(`https://${N}/new`,{settleMs:2000});await C(q);if(W.length){let K;try{K=await n(q,S.paths)}catch(Q){throw new _(`Failed to upload image to ChatGPT: ${Q instanceof Error?Q.message:String(Q)}`)}if(!K?.ok)throw new _(K?.reason||"Failed to upload image to ChatGPT")}const A=await x(q);if(!await P(q,g(z,W.length)))throw new _("Failed to send image prompt to ChatGPT",`Open ${await F(q)} and verify the composer is ready.`);let Z="";for(let K=0;K<10;K++){const Q=await F(q);if(Q.includes("/c/")){Z=Q;break}await q.sleep(2)}if(!Z)Z=await F(q);const L=await d(q,A,O,Z),V=Z;if(!L.length)throw new v("chatgpt image",`No generated images were detected before timeout. Open ${V} and verify whether ChatGPT finished generating the image.`);if(R)return[{status:"\uD83C\uDFA8 generated",file:"\uD83D\uDCC1 -",link:`\uD83D\uDD17 ${V}`}];const B=await u(q,L);if(!B.length)throw new _("Failed to export generated ChatGPT image assets",`Open ${V} and verify the generated images are visible, then retry.`);const U=Date.now(),M=[];for(let K=0;K<B.length;K+=1){const Q=B[K],D=Q.dataUrl.replace(/^data:[^;]+;base64,/,""),b=B.length>1?`_${K+1}`:"",G=m(Q.mimeType),j=nextAvailablePath(Y,`chatgpt_${U}${b}`,G);await T(D,j);M.push({status:"✅ saved",file:`\uD83D\uDCC1 ${l(j)}`,link:`\uD83D\uDD17 ${V}`})}return M}});
@@ -1 +1 @@
1
- import{cli as W,Strategy as X}from"@jackwener/opencli/registry";import{ArgumentError as Q,CommandExecutionError as Y}from"@jackwener/opencli/errors";import{CHATGPT_DOMAIN as Z,CHATGPT_URL as $,ensureChatGPTComposer as f,ensureOnChatGPT as q,normalizeBooleanFlag as V,openChatGPTConversation as x,requireNonEmptyPrompt as D,sendChatGPTMessage as I,startNewChat as L,navigateToProject as R}from"./utils.js";export const sendCommand=W({site:"chatgpt",name:"send",access:"write",description:"Send a prompt to ChatGPT web without waiting for the response",domain:Z,strategy:X.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,args:[{name:"prompt",positional:!0,required:!0,help:"Prompt to send"},{name:"new",type:"boolean",default:!1,help:"Start a new chat before sending"},{name:"conversation",valueRequired:!0,help:"Continue an existing ChatGPT conversation ID or /c/<id> URL"},{name:"project",valueRequired:!0,help:"Start a new chat inside a ChatGPT project ID or /g/g-p-<id> URL"}],columns:["Status","InjectedText"],func:async(J,b)=>{const K=D(b.prompt,"chatgpt send");if(V(b.new)&&b.conversation)throw new Q("chatgpt send cannot use --new and --conversation together","Choose either a new chat or an existing conversation.");if(b.project&&b.conversation)throw new Q("chatgpt send cannot use --project and --conversation together","Choose either a project new chat or an existing conversation.");if(b.conversation)await x(J,b.conversation);else if(b.project)await R(J,b.project);else if(V(b.new))await L(J);else await q(J);await f(J,"ChatGPT send requires a logged-in ChatGPT session with a visible composer.");if(!await I(J,K))throw new Y("Failed to send message to ChatGPT",`Open ${$} and verify the composer is ready.`);return[{Status:"Success",InjectedText:K}]}});
1
+ import{cli as W,Strategy as X}from"@jackwener/opencli/registry";import{ArgumentError as Q,CommandExecutionError as Y}from"@jackwener/opencli/errors";import{CHATGPT_DOMAIN as Z,CHATGPT_URL as $,ensureChatGPTComposer as f,ensureOnChatGPT as q,normalizeBooleanFlag as V,openChatGPTConversation as x,requireNonEmptyPrompt as D,sendChatGPTMessage as I,startNewChat as L,navigateToProject as R}from"./utils.js";export const sendCommand=W({site:"chatgpt",name:"send",access:"write",description:"Send a prompt to ChatGPT web without waiting for the response",domain:Z,strategy:X.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,args:[{name:"prompt",fromFile:!0,positional:!0,required:!0,help:"Prompt to send"},{name:"new",type:"boolean",default:!1,help:"Start a new chat before sending"},{name:"conversation",valueRequired:!0,help:"Continue an existing ChatGPT conversation ID or /c/<id> URL"},{name:"project",valueRequired:!0,help:"Start a new chat inside a ChatGPT project ID or /g/g-p-<id> URL"}],columns:["Status","InjectedText"],func:async(J,b)=>{const K=D(b.prompt,"chatgpt send");if(V(b.new)&&b.conversation)throw new Q("chatgpt send cannot use --new and --conversation together","Choose either a new chat or an existing conversation.");if(b.project&&b.conversation)throw new Q("chatgpt send cannot use --project and --conversation together","Choose either a project new chat or an existing conversation.");if(b.conversation)await x(J,b.conversation);else if(b.project)await R(J,b.project);else if(V(b.new))await L(J);else await q(J);await f(J,"ChatGPT send requires a logged-in ChatGPT session with a visible composer.");if(!await I(J,K))throw new Y("Failed to send message to ChatGPT",`Open ${$} and verify the composer is ready.`);return[{Status:"Success",InjectedText:K}]}});
@@ -1 +1 @@
1
- import{statSync as B}from"node:fs";import{cli as N,Strategy as O}from"@jackwener/opencli/registry";import{ArgumentError as U,ConfigError as V,TimeoutError as j}from"@jackwener/opencli/errors";import{activateChatGPT as W,getVisibleChatMessages as R,selectModel as A,MODEL_CHOICES as I,isGenerating as y,sendPrompt as M}from"./ax.js";export const askCommand=N({site:"chatgpt-app",name:"ask",access:"write",description:"Send a prompt and wait for the AI response (send + wait + read)",domain:"localhost",strategy:O.PUBLIC,browser:!1,args:[{name:"text",required:!0,positional:!0,help:"Prompt to send"},{name:"model",required:!1,help:"Model/mode to use: auto, instant, thinking, 5.2-instant, 5.2-thinking",choices:I},{name:"timeout",type:"int",required:!1,help:"Max seconds to wait for response (default: 30)",default:30},{name:"image",required:!1,help:"Path to local image to attach (optional)"}],columns:["Role","Text"],func:async(F)=>{if(process.platform!=="darwin")throw new V("ChatGPT Desktop integration requires macOS (osascript is not available on this platform)");const{text:K,model:X,timeout:J,image:z}=F;if(z){let q;try{q=B(z)}catch{throw new U(`The specified image path does not exist: ${z}`)}if(!q.isFile())throw new U(`The specified image path is not a file: ${z}`)}if(!Number.isInteger(J)||J<1)throw new U("--timeout must be a positive integer (seconds)");if(X){W();A(X)}const Y=R();W();M(K,z);const Z=2,_=Math.ceil(J/Z);let Q="",$=!1;for(let q=0;q<_;q++){await new Promise((H)=>setTimeout(H,Z*1000));if(y()){$=!0;continue}if(!$&&q<3)continue;W(0.3);const D=R();if(D.length>Y.length){const L=[...D.slice(Y.length)].reverse().find((b)=>b!==K);if(L)Q=L}break}if(!Q)throw new j("chatgpt-app/ask",J,"ChatGPT may still be generating; rerun read or increase --timeout");return[{Role:"User",Text:K},{Role:"Assistant",Text:Q}]}});
1
+ import{statSync as B}from"node:fs";import{cli as N,Strategy as O}from"@jackwener/opencli/registry";import{ArgumentError as U,ConfigError as V,TimeoutError as j}from"@jackwener/opencli/errors";import{activateChatGPT as W,getVisibleChatMessages as R,selectModel as A,MODEL_CHOICES as I,isGenerating as y,sendPrompt as M}from"./ax.js";export const askCommand=N({site:"chatgpt-app",name:"ask",access:"write",description:"Send a prompt and wait for the AI response (send + wait + read)",domain:"localhost",strategy:O.PUBLIC,browser:!1,args:[{name:"text",fromFile:!0,required:!0,positional:!0,help:"Prompt to send"},{name:"model",required:!1,help:"Model/mode to use: auto, instant, thinking, 5.2-instant, 5.2-thinking",choices:I},{name:"timeout",type:"int",required:!1,help:"Max seconds to wait for response (default: 30)",default:30},{name:"image",required:!1,help:"Path to local image to attach (optional)"}],columns:["Role","Text"],func:async(F)=>{if(process.platform!=="darwin")throw new V("ChatGPT Desktop integration requires macOS (osascript is not available on this platform)");const{text:K,model:X,timeout:J,image:z}=F;if(z){let q;try{q=B(z)}catch{throw new U(`The specified image path does not exist: ${z}`)}if(!q.isFile())throw new U(`The specified image path is not a file: ${z}`)}if(!Number.isInteger(J)||J<1)throw new U("--timeout must be a positive integer (seconds)");if(X){W();A(X)}const Y=R();W();M(K,z);const Z=2,_=Math.ceil(J/Z);let Q="",$=!1;for(let q=0;q<_;q++){await new Promise((H)=>setTimeout(H,Z*1000));if(y()){$=!0;continue}if(!$&&q<3)continue;W(0.3);const D=R();if(D.length>Y.length){const L=[...D.slice(Y.length)].reverse().find((b)=>b!==K);if(L)Q=L}break}if(!Q)throw new j("chatgpt-app/ask",J,"ChatGPT may still be generating; rerun read or increase --timeout");return[{Role:"User",Text:K},{Role:"Assistant",Text:Q}]}});
@@ -1 +1 @@
1
- import{cli as K,Strategy as N}from"@jackwener/opencli/registry";import{CommandExecutionError as Q,ConfigError as R,getErrorMessage as U}from"@jackwener/opencli/errors";import{activateChatGPT as B,selectModel as V,MODEL_CHOICES as W,sendPrompt as X}from"./ax.js";export const sendCommand=K({site:"chatgpt-app",name:"send",access:"write",description:"Send a message to the active ChatGPT Desktop App window",domain:"localhost",strategy:N.PUBLIC,browser:!1,args:[{name:"text",required:!0,positional:!0,help:"Message to send"},{name:"model",required:!1,help:"Model/mode to use: auto, instant, thinking, 5.2-instant, 5.2-thinking",choices:W}],columns:["Status"],func:async(z)=>{if(process.platform!=="darwin")throw new R("ChatGPT Desktop integration requires macOS (osascript is not available on this platform)");const{text:F,model:A}=z;try{if(A){B();V(A)}B();X(F);return[{Status:"Success"}]}catch(J){throw new Q("Failed to send ChatGPT message: "+U(J))}}});
1
+ import{cli as K,Strategy as N}from"@jackwener/opencli/registry";import{CommandExecutionError as Q,ConfigError as R,getErrorMessage as U}from"@jackwener/opencli/errors";import{activateChatGPT as B,selectModel as V,MODEL_CHOICES as W,sendPrompt as X}from"./ax.js";export const sendCommand=K({site:"chatgpt-app",name:"send",access:"write",description:"Send a message to the active ChatGPT Desktop App window",domain:"localhost",strategy:N.PUBLIC,browser:!1,args:[{name:"text",fromFile:!0,required:!0,positional:!0,help:"Message to send"},{name:"model",required:!1,help:"Model/mode to use: auto, instant, thinking, 5.2-instant, 5.2-thinking",choices:W}],columns:["Status"],func:async(z)=>{if(process.platform!=="darwin")throw new R("ChatGPT Desktop integration requires macOS (osascript is not available on this platform)");const{text:F,model:A}=z;try{if(A){B();V(A)}B();X(F);return[{Status:"Success"}]}catch(J){throw new Q("Failed to send ChatGPT message: "+U(J))}}});
@@ -1 +1 @@
1
- import{cli as c,Strategy as d}from"@jackwener/opencli/registry";import{selectorError as w,TimeoutError as f}from"@jackwener/opencli/errors";import{buildChatwiseInjectTextJs as h,buildChatwiseMessageCountJs as p,buildChatwiseResponseAfterJs as x,requirePositiveTimeout as C}from"./utils.js";export const askCommand=c({site:"chatwise",name:"ask",access:"write",description:"Send a prompt and wait for the AI response (send + wait + read)",domain:"localhost",strategy:d.UI,browser:!0,args:[{name:"text",required:!0,positional:!0,help:"Prompt to send"},{name:"timeout",type:"int",required:!1,help:"Max seconds to wait (default: 30)",default:30}],columns:["Role","Text"],func:async(e,o)=>{const i=o.text,r=C(o.timeout),u=await e.evaluate(p());if(!await e.evaluate(h(i)))throw w("ChatWise input element");await e.wait(0.5);await e.pressKey("Enter");const s=2,m=Math.ceil(r/s);let t="";for(let a=0;a<m;a++){await e.wait(s);const n=await e.evaluate(x(u,i));if(n){const l=String(n).trim();if(l===t)break;t=l}}if(!t)throw new f("ChatWise response",r,"Confirm ChatWise is done generating, then retry with a larger --timeout if needed.");return[{Role:"User",Text:i},{Role:"Assistant",Text:t}]}});
1
+ import{cli as c,Strategy as d}from"@jackwener/opencli/registry";import{selectorError as w,TimeoutError as f}from"@jackwener/opencli/errors";import{buildChatwiseInjectTextJs as h,buildChatwiseMessageCountJs as p,buildChatwiseResponseAfterJs as x,requirePositiveTimeout as C}from"./utils.js";export const askCommand=c({site:"chatwise",name:"ask",access:"write",description:"Send a prompt and wait for the AI response (send + wait + read)",domain:"localhost",strategy:d.UI,browser:!0,args:[{name:"text",fromFile:!0,required:!0,positional:!0,help:"Prompt to send"},{name:"timeout",type:"int",required:!1,help:"Max seconds to wait (default: 30)",default:30}],columns:["Role","Text"],func:async(e,r)=>{const i=r.text,o=C(r.timeout),u=await e.evaluate(p());if(!await e.evaluate(h(i)))throw w("ChatWise input element");await e.wait(0.5);await e.pressKey("Enter");const s=2,m=Math.ceil(o/s);let t="";for(let a=0;a<m;a++){await e.wait(s);const n=await e.evaluate(x(u,i));if(n){const l=String(n).trim();if(l===t)break;t=l}}if(!t)throw new f("ChatWise response",o,"Confirm ChatWise is done generating, then retry with a larger --timeout if needed.");return[{Role:"User",Text:i},{Role:"Assistant",Text:t}]}});
@@ -1 +1 @@
1
- import{cli as a,Strategy as r}from"@jackwener/opencli/registry";import{selectorError as i}from"@jackwener/opencli/errors";import{buildChatwiseInjectTextJs as n}from"./utils.js";export const sendCommand=a({site:"chatwise",name:"send",access:"write",description:"Send a message to the active ChatWise conversation",domain:"localhost",strategy:r.UI,browser:!0,args:[{name:"text",required:!0,positional:!0,help:"Message to send"}],columns:["Status","InjectedText"],func:async(t,s)=>{const e=s.text;if(!await t.evaluate(n(e)))throw i("ChatWise input element");await t.wait(0.5);await t.pressKey("Enter");return[{Status:"Success",InjectedText:e}]}});
1
+ import{cli as r,Strategy as a}from"@jackwener/opencli/registry";import{selectorError as i}from"@jackwener/opencli/errors";import{buildChatwiseInjectTextJs as n}from"./utils.js";export const sendCommand=r({site:"chatwise",name:"send",access:"write",description:"Send a message to the active ChatWise conversation",domain:"localhost",strategy:a.UI,browser:!0,args:[{name:"text",fromFile:!0,required:!0,positional:!0,help:"Message to send"}],columns:["Status","InjectedText"],func:async(e,s)=>{const t=s.text;if(!await e.evaluate(n(t)))throw i("ChatWise input element");await e.wait(0.5);await e.pressKey("Enter");return[{Status:"Success",InjectedText:t}]}});
@@ -1,4 +1,4 @@
1
- import{cli as v,Strategy as x}from"@jackwener/opencli/registry";import{ArgumentError as f,CommandExecutionError as Y,EmptyResultError as j}from"@jackwener/opencli/errors";import{CLAUDE_DOMAIN as F,CLAUDE_URL as L,COMPOSER_SELECTOR as T,MESSAGE_SELECTOR as b,ensureOnClaude as O,selectModel as A,setAdaptiveThinking as S,sendMessage as y,sendWithFile as M,getBubbleCount as D,waitForResponse as P,parseBoolFlag as U,withRetry as K,ensureClaudeComposer as h,requireNonEmptyPrompt as C,requirePositiveInt as c}from"./utils.js";export const askCommand=v({site:"claude",name:"ask",access:"write",description:"Send a prompt to Claude and get the response",domain:F,strategy:x.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,args:[{name:"prompt",positional:!0,required:!0,help:"Prompt to send"},{name:"timeout",type:"int",default:120,help:"Max seconds to wait for response"},{name:"new",type:"boolean",default:!1,help:"Start a new chat before sending"},{name:"model",default:"sonnet",choices:["sonnet","opus","haiku"],help:"Model to use: sonnet, opus, or haiku"},{name:"think",type:"boolean",default:!1,help:"Enable Adaptive thinking"},{name:"file",help:"Attach a file (image, PDF, text) with the prompt"}],columns:["response"],func:async(z,H)=>{const V=C(H.prompt,"claude ask"),Z=c(Number(H.timeout??120),"claude ask --timeout",'Example: ppcli claude ask "hello" --timeout 120'),$=Z*1000,G=U(H.think);if(U(H.new)){await z.goto(L);try{await z.wait({selector:T,timeout:8})}catch{}}else if(await O(z)){await z.evaluate(`(() => {
1
+ import{cli as v,Strategy as x}from"@jackwener/opencli/registry";import{ArgumentError as f,CommandExecutionError as Y,EmptyResultError as j}from"@jackwener/opencli/errors";import{CLAUDE_DOMAIN as F,CLAUDE_URL as L,COMPOSER_SELECTOR as T,MESSAGE_SELECTOR as b,ensureOnClaude as O,selectModel as A,setAdaptiveThinking as S,sendMessage as y,sendWithFile as M,getBubbleCount as D,waitForResponse as P,parseBoolFlag as U,withRetry as K,ensureClaudeComposer as h,requireNonEmptyPrompt as C,requirePositiveInt as c}from"./utils.js";export const askCommand=v({site:"claude",name:"ask",access:"write",description:"Send a prompt to Claude and get the response",domain:F,strategy:x.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,args:[{name:"prompt",fromFile:"prompt-file",positional:!0,required:!0,help:"Prompt to send"},{name:"timeout",type:"int",default:120,help:"Max seconds to wait for response"},{name:"new",type:"boolean",default:!1,help:"Start a new chat before sending"},{name:"model",default:"sonnet",choices:["sonnet","opus","haiku"],help:"Model to use: sonnet, opus, or haiku"},{name:"think",type:"boolean",default:!1,help:"Enable Adaptive thinking"},{name:"file",help:"Attach a file (image, PDF, text) with the prompt"}],columns:["response"],func:async(z,H)=>{const V=C(H.prompt,"claude ask"),Z=c(Number(H.timeout??120),"claude ask --timeout",'Example: ppcli claude ask "hello" --timeout 120'),$=Z*1000,G=U(H.think);if(U(H.new)){await z.goto(L);try{await z.wait({selector:T,timeout:8})}catch{}}else if(await O(z)){await z.evaluate(`(() => {
2
2
  var link = document.querySelector('a[href*="/chat/"]');
3
3
  if (link) link.click();
4
4
  })()`);try{await z.wait({selector:b,timeout:5})}catch{}}await K(()=>h(z,"Claude ask requires a visible composer on the current page."));const W=(await z.evaluate("window.location.href")||"").includes("/chat/"),B=H.__opencliOptionSources?.model==="cli",X=H.model||"sonnet";if(W&&B)throw new f(`Cannot switch to ${X} model inside an existing conversation.`,"Re-run with --new to start a fresh chat before selecting a model.");if(!W){const Q=await K(()=>A(z,X));if(!Q?.ok){if(Q?.upgrade)throw new f(`${X} model requires a paid Claude plan.`,"Pick --model sonnet or --model haiku, or upgrade your account.");throw new Y(`Could not switch to ${X} model`)}}if(!(await K(()=>S(z,G)))?.ok&&G)throw new Y("Could not enable Adaptive thinking");if(H.file){const Q=await K(()=>D(z));try{const J=await M(z,H.file,V);if(J&&!J.ok)throw new Y(J.reason||"Failed to attach file")}catch(J){if(!String(J?.message||J).includes("Promise was collected"))throw J}const N=await P(z,Q,V,$);if(!N)throw new j("claude ask",`No Claude response appeared within ${Z}s. Re-run with a higher --timeout if the model is still generating.`);return[{response:N}]}const _=await K(()=>D(z)),q=await K(()=>y(z,V));if(!q?.ok)throw new Y(q?.reason||"Failed to send message");const I=await P(z,_,V,$);if(!I)throw new j("claude ask",`No Claude response appeared within ${Z}s. Re-run with a higher --timeout if the model is still generating.`);return[{response:I}]}});
@@ -1 +1 @@
1
- import{cli as K,Strategy as Q}from"@jackwener/opencli/registry";import{CommandExecutionError as V}from"@jackwener/opencli/errors";import{CLAUDE_DOMAIN as W,CLAUDE_URL as X,COMPOSER_SELECTOR as Y,ensureOnClaude as Z,sendMessage as $,parseBoolFlag as q,withRetry as J,ensureClaudeComposer as x,requireNonEmptyPrompt as B}from"./utils.js";export const sendCommand=K({site:"claude",name:"send",access:"write",description:"Send a prompt to Claude without waiting for the response",domain:W,strategy:Q.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,args:[{name:"prompt",positional:!0,required:!0,help:"Prompt to send"},{name:"new",type:"boolean",default:!1,help:"Start a new chat before sending"}],columns:["Status","SubmittedBy","InjectedText"],func:async(f,G)=>{const H=B(G.prompt,"claude send");if(q(G.new)){await f.goto(X);try{await f.wait({selector:Y,timeout:8})}catch{}}else await Z(f);await J(()=>x(f,"Claude send requires a visible composer on the current page."));const z=await J(()=>$(f,H));if(!z?.ok)throw new V(z?.reason||"Failed to send message");return[{Status:"Success",SubmittedBy:z.method||"send-button",InjectedText:H}]}});
1
+ import{cli as K,Strategy as Q}from"@jackwener/opencli/registry";import{CommandExecutionError as V}from"@jackwener/opencli/errors";import{CLAUDE_DOMAIN as W,CLAUDE_URL as X,COMPOSER_SELECTOR as Y,ensureOnClaude as Z,sendMessage as $,parseBoolFlag as q,withRetry as J,ensureClaudeComposer as x,requireNonEmptyPrompt as B}from"./utils.js";export const sendCommand=K({site:"claude",name:"send",access:"write",description:"Send a prompt to Claude without waiting for the response",domain:W,strategy:Q.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,args:[{name:"prompt",fromFile:!0,positional:!0,required:!0,help:"Prompt to send"},{name:"new",type:"boolean",default:!1,help:"Start a new chat before sending"}],columns:["Status","SubmittedBy","InjectedText"],func:async(f,G)=>{const H=B(G.prompt,"claude send");if(q(G.new)){await f.goto(X);try{await f.wait({selector:Y,timeout:8})}catch{}}else await Z(f);await J(()=>x(f,"Claude send requires a visible composer on the current page."));const z=await J(()=>$(f,H));if(!z?.ok)throw new V(z?.reason||"Failed to send message");return[{Status:"Success",SubmittedBy:z.method||"send-button",InjectedText:H}]}});
package/clis/codex/ask.js CHANGED
@@ -1,4 +1,4 @@
1
- import{cli as O,Strategy as Q}from"@jackwener/opencli/registry";import{ArgumentError as R,selectorError as T}from"@jackwener/opencli/errors";import{conversationSelectionArgs as U,openCodexConversation as V}from"./sidebar.js";export const askCommand=O({site:"codex",name:"ask",access:"write",description:"Send a prompt to the current or selected Codex conversation and wait for the AI response",domain:"localhost",strategy:Q.UI,browser:!0,args:[{name:"text",required:!0,positional:!0,help:"Prompt to send"},{name:"timeout",type:"int",required:!1,help:"Max seconds to wait for response (default: 60)",default:60},...U],columns:["Role","Project","Conversation","Text"],func:async(z,D)=>{const{text:F,timeout:B}=D;if(!Number.isInteger(B)||B<1)throw new R("--timeout must be a positive integer (seconds)");const q=await V(z,D),M=await z.evaluate(`
1
+ import{cli as O,Strategy as Q}from"@jackwener/opencli/registry";import{ArgumentError as R,selectorError as T}from"@jackwener/opencli/errors";import{conversationSelectionArgs as U,openCodexConversation as V}from"./sidebar.js";export const askCommand=O({site:"codex",name:"ask",access:"write",description:"Send a prompt to the current or selected Codex conversation and wait for the AI response",domain:"localhost",strategy:Q.UI,browser:!0,args:[{name:"text",fromFile:!0,required:!0,positional:!0,help:"Prompt to send"},{name:"timeout",type:"int",required:!1,help:"Max seconds to wait for response (default: 60)",default:60},...U],columns:["Role","Project","Conversation","Text"],func:async(z,D)=>{const{text:F,timeout:B}=D;if(!Number.isInteger(B)||B<1)throw new R("--timeout must be a positive integer (seconds)");const q=await V(z,D),M=await z.evaluate(`
2
2
  (function() {
3
3
  const turns = document.querySelectorAll('[data-content-search-turn-key]');
4
4
  return turns.length;
@@ -1,4 +1,4 @@
1
- import{cli as F,Strategy as G}from"@jackwener/opencli/registry";import{selectorError as H}from"@jackwener/opencli/errors";import{conversationSelectionArgs as J,openCodexConversation as K}from"./sidebar.js";export const sendCommand=F({site:"codex",name:"send",access:"write",description:"Send text/commands to the current or selected Codex AI composer",domain:"localhost",strategy:G.UI,browser:!0,args:[{name:"text",required:!0,positional:!0,help:"Text, command (e.g. /review), or skill (e.g. $imagegen)"},...J],columns:["Status","Project","Conversation","InjectedText"],func:async(q,z)=>{const B=z.text,D=await K(q,z);if(!await q.evaluate(`
1
+ import{cli as F,Strategy as G}from"@jackwener/opencli/registry";import{selectorError as H}from"@jackwener/opencli/errors";import{conversationSelectionArgs as J,openCodexConversation as K}from"./sidebar.js";export const sendCommand=F({site:"codex",name:"send",access:"write",description:"Send text/commands to the current or selected Codex AI composer",domain:"localhost",strategy:G.UI,browser:!0,args:[{name:"text",fromFile:!0,required:!0,positional:!0,help:"Text, command (e.g. /review), or skill (e.g. $imagegen)"},...J],columns:["Status","Project","Conversation","InjectedText"],func:async(q,z)=>{const B=z.text,D=await K(q,z);if(!await q.evaluate(`
2
2
  (function(text) {
3
3
  let composer = document.querySelector('textarea, [contenteditable="true"]');
4
4
 
@@ -1,4 +1,4 @@
1
- import{cli as L,Strategy as M}from"@jackwener/opencli/registry";import{ArgumentError as N,selectorError as O}from"@jackwener/opencli/errors";export const askCommand=L({site:"cursor",name:"ask",access:"write",description:"Send a prompt and wait for the AI response (send + wait + read)",domain:"localhost",strategy:M.UI,browser:!0,args:[{name:"text",required:!0,positional:!0,help:"Prompt to send"},{name:"timeout",type:"int",required:!1,help:"Max seconds to wait for response (default: 30)",default:30}],columns:["Role","Text"],func:async(h,D)=>{const{text:z,timeout:q}=D;if(!Number.isInteger(q)||q<1)throw new N("--timeout must be a positive integer (seconds)");const J=await h.evaluate(`
1
+ import{cli as L,Strategy as M}from"@jackwener/opencli/registry";import{ArgumentError as N,selectorError as O}from"@jackwener/opencli/errors";export const askCommand=L({site:"cursor",name:"ask",access:"write",description:"Send a prompt and wait for the AI response (send + wait + read)",domain:"localhost",strategy:M.UI,browser:!0,args:[{name:"text",fromFile:!0,required:!0,positional:!0,help:"Prompt to send"},{name:"timeout",type:"int",required:!1,help:"Max seconds to wait for response (default: 30)",default:30}],columns:["Role","Text"],func:async(h,D)=>{const{text:z,timeout:q}=D;if(!Number.isInteger(q)||q<1)throw new N("--timeout must be a positive integer (seconds)");const J=await h.evaluate(`
2
2
  document.querySelectorAll('[data-message-role]').length
3
3
  `);if(!await h.evaluate(`(function(text) {
4
4
  let editor = document.querySelector('.aislash-editor-input, [data-lexical-editor="true"], [contenteditable="true"]');
@@ -1,4 +1,4 @@
1
- import{cli as A,Strategy as B}from"@jackwener/opencli/registry";import{selectorError as D}from"@jackwener/opencli/errors";export const composerCommand=A({site:"cursor",name:"composer",access:"write",description:"Send a prompt directly into Cursor Composer (Cmd+I shortcut)",domain:"localhost",strategy:B.UI,browser:!0,args:[{name:"text",required:!0,positional:!0,help:"Text to send into Composer"}],columns:["Status","InjectedText"],func:async(b,z)=>{const q=z.text;await b.pressKey("Meta+I");await b.wait(1);if(!await b.evaluate(`(function(text) {
1
+ import{cli as A,Strategy as B}from"@jackwener/opencli/registry";import{selectorError as D}from"@jackwener/opencli/errors";export const composerCommand=A({site:"cursor",name:"composer",access:"write",description:"Send a prompt directly into Cursor Composer (Cmd+I shortcut)",domain:"localhost",strategy:B.UI,browser:!0,args:[{name:"text",fromFile:!0,required:!0,positional:!0,help:"Text to send into Composer"}],columns:["Status","InjectedText"],func:async(b,z)=>{const q=z.text;await b.pressKey("Meta+I");await b.wait(1);if(!await b.evaluate(`(function(text) {
2
2
  let composer = document.activeElement;
3
3
  if (!composer || !composer.isContentEditable) {
4
4
  composer = document.querySelector('.composer-bar [data-lexical-editor="true"], [id*="composer"] [contenteditable="true"], .aislash-editor-input');
@@ -1,4 +1,4 @@
1
- import{cli as z,Strategy as A}from"@jackwener/opencli/registry";import{selectorError as B}from"@jackwener/opencli/errors";export const sendCommand=z({site:"cursor",name:"send",access:"write",description:"Send a prompt directly into Cursor Composer/Chat",domain:"localhost",strategy:A.UI,browser:!0,args:[{name:"text",required:!0,positional:!0,help:"Text to send into Cursor"}],columns:["Status","InjectedText"],func:async(f,v)=>{const q=v.text;if(!await f.evaluate(`(function(text) {
1
+ import{cli as z,Strategy as A}from"@jackwener/opencli/registry";import{selectorError as B}from"@jackwener/opencli/errors";export const sendCommand=z({site:"cursor",name:"send",access:"write",description:"Send a prompt directly into Cursor Composer/Chat",domain:"localhost",strategy:A.UI,browser:!0,args:[{name:"text",fromFile:!0,required:!0,positional:!0,help:"Text to send into Cursor"}],columns:["Status","InjectedText"],func:async(f,v)=>{const q=v.text;if(!await f.evaluate(`(function(text) {
2
2
  // Find the Lexical editor input for Composer or Chat
3
3
  let composer = document.querySelector('.aislash-editor-input, [data-lexical-editor="true"], [contenteditable="true"]');
4
4
 
@@ -1 +1 @@
1
- import{cli as _,Strategy as x}from"@jackwener/opencli/registry";import{CliError as L,CommandExecutionError as V,EXIT_CODES as U,TimeoutError as X}from"@jackwener/opencli/errors";import{DEEPSEEK_DOMAIN as y,DEEPSEEK_URL as F,ensureOnDeepSeek as S,selectModel as M,setFeature as f,sendMessage as C,sendWithFile as T,getBubbleCount as A,waitForResponse as B,parseBoolFlag as v,withRetry as Y,pickResumeUrl as c,TEXTAREA_SELECTOR as b}from"./utils.js";export const askCommand=_({site:"deepseek",name:"ask",access:"write",description:"Send a prompt to DeepSeek and get the response",domain:y,strategy:x.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,args:[{name:"prompt",positional:!0,required:!0,help:"Prompt to send"},{name:"timeout",type:"int",default:120,help:"Max seconds to wait for response"},{name:"new",type:"boolean",default:!1,help:"Start a new chat before sending"},{name:"model",default:"instant",choices:["instant","expert","vision"],help:"Model to use: instant, expert, or vision"},{name:"think",type:"boolean",default:!1,help:"Enable DeepThink mode"},{name:"search",type:"boolean",default:!1,help:"Enable web search"},{name:"file",help:"Attach a file (PDF, image, text) with the prompt"}],func:async(q,z)=>{const $=z.prompt,K=(z.timeout||120)*1000,Q=v(z.think),N=v(z.search),G=z.model||"instant";if((G==="vision"||G==="expert")&&N)throw new L("ARGUMENT",`DeepSeek ${G} mode does not support --search.`,"Run without --search, or use --model instant for web search.",U.USAGE_ERROR);if(v(z.new)){await q.goto(F);try{await q.wait({selector:b,timeout:8})}catch{}}else if(await S(q)){const H=await c(q);if(!H)throw new V("Workspace was recycled but no prior conversation could be loaded","Pass --new to start a fresh chat, or wait for the sidebar to populate before retrying.");await q.goto(H);try{await q.wait({selector:b,timeout:5})}catch{}}const P=(await q.evaluate("window.location.href")||"").includes("/a/chat/s/"),D=z.__opencliOptionSources?.model==="cli";if(P&&D)throw new L("ARGUMENT",`Cannot switch to ${G} model inside an existing conversation.`,"Re-run with --new to start a fresh chat before selecting a model.",U.USAGE_ERROR);if(!P){if(!(await Y(()=>M(q,G)))?.ok)throw new V(`Could not switch to ${G} model`)}if(!(await Y(()=>f(q,"DeepThink",Q)))?.ok&&Q)throw new V("Could not enable DeepThink");let j;if(G!=="vision"&&G!=="expert"){j=await Y(()=>f(q,"Search",N));if(!j?.ok&&N)throw new V("Could not enable Search")}if(z.file){const W=await Y(()=>A(q));try{const J=await T(q,z.file,$);if(J&&!J.ok)throw new V(J.reason||"Failed to attach file")}catch(J){if(!String(J?.message||J).includes("Promise was collected"))throw J}const H=await B(q,W,$,K,Q);if(!H)throw new X("deepseek ask",z.timeout,"No DeepSeek reply observed before timeout. Retry with --timeout increased.");if(Q&&typeof H==="object"&&H.response!==void 0)return[H];return[{response:H}]}const O=await Y(()=>A(q)),I=await Y(()=>C(q,$));if(!I?.ok)throw new V(I?.reason||"Failed to send message");const Z=await B(q,O,$,K,Q);if(!Z)throw new X("deepseek ask",z.timeout,"No DeepSeek reply observed before timeout. Retry with --timeout increased.");if(Q&&typeof Z==="object"&&Z.response!==void 0)return[Z];return[{response:Z}]}});
1
+ import{cli as _,Strategy as x}from"@jackwener/opencli/registry";import{CliError as L,CommandExecutionError as V,EXIT_CODES as U,TimeoutError as X}from"@jackwener/opencli/errors";import{DEEPSEEK_DOMAIN as y,DEEPSEEK_URL as F,ensureOnDeepSeek as S,selectModel as M,setFeature as f,sendMessage as C,sendWithFile as T,getBubbleCount as A,waitForResponse as B,parseBoolFlag as v,withRetry as Y,pickResumeUrl as c,TEXTAREA_SELECTOR as b}from"./utils.js";export const askCommand=_({site:"deepseek",name:"ask",access:"write",description:"Send a prompt to DeepSeek and get the response",domain:y,strategy:x.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,args:[{name:"prompt",fromFile:"prompt-file",positional:!0,required:!0,help:"Prompt to send"},{name:"timeout",type:"int",default:120,help:"Max seconds to wait for response"},{name:"new",type:"boolean",default:!1,help:"Start a new chat before sending"},{name:"model",default:"instant",choices:["instant","expert","vision"],help:"Model to use: instant, expert, or vision"},{name:"think",type:"boolean",default:!1,help:"Enable DeepThink mode"},{name:"search",type:"boolean",default:!1,help:"Enable web search"},{name:"file",help:"Attach a file (PDF, image, text) with the prompt"}],func:async(q,z)=>{const $=z.prompt,K=(z.timeout||120)*1000,Q=v(z.think),N=v(z.search),G=z.model||"instant";if((G==="vision"||G==="expert")&&N)throw new L("ARGUMENT",`DeepSeek ${G} mode does not support --search.`,"Run without --search, or use --model instant for web search.",U.USAGE_ERROR);if(v(z.new)){await q.goto(F);try{await q.wait({selector:b,timeout:8})}catch{}}else if(await S(q)){const H=await c(q);if(!H)throw new V("Workspace was recycled but no prior conversation could be loaded","Pass --new to start a fresh chat, or wait for the sidebar to populate before retrying.");await q.goto(H);try{await q.wait({selector:b,timeout:5})}catch{}}const P=(await q.evaluate("window.location.href")||"").includes("/a/chat/s/"),D=z.__opencliOptionSources?.model==="cli";if(P&&D)throw new L("ARGUMENT",`Cannot switch to ${G} model inside an existing conversation.`,"Re-run with --new to start a fresh chat before selecting a model.",U.USAGE_ERROR);if(!P){if(!(await Y(()=>M(q,G)))?.ok)throw new V(`Could not switch to ${G} model`)}if(!(await Y(()=>f(q,"DeepThink",Q)))?.ok&&Q)throw new V("Could not enable DeepThink");let j;if(G!=="vision"&&G!=="expert"){j=await Y(()=>f(q,"Search",N));if(!j?.ok&&N)throw new V("Could not enable Search")}if(z.file){const W=await Y(()=>A(q));try{const J=await T(q,z.file,$);if(J&&!J.ok)throw new V(J.reason||"Failed to attach file")}catch(J){if(!String(J?.message||J).includes("Promise was collected"))throw J}const H=await B(q,W,$,K,Q);if(!H)throw new X("deepseek ask",z.timeout,"No DeepSeek reply observed before timeout. Retry with --timeout increased.");if(Q&&typeof H==="object"&&H.response!==void 0)return[H];return[{response:H}]}const O=await Y(()=>A(q)),I=await Y(()=>C(q,$));if(!I?.ok)throw new V(I?.reason||"Failed to send message");const Z=await B(q,O,$,K,Q);if(!Z)throw new X("deepseek ask",z.timeout,"No DeepSeek reply observed before timeout. Retry with --timeout increased.");if(Q&&typeof Z==="object"&&Z.response!==void 0)return[Z];return[{response:Z}]}});
@@ -1,4 +1,4 @@
1
- import{cli as Z,Strategy as $}from"@jackwener/opencli/registry";import{CommandExecutionError as H}from"@jackwener/opencli/errors";import{DEEPSEEK_DOMAIN as h,MESSAGE_SELECTOR as F,TEXTAREA_SELECTOR as V,parseDeepSeekConversationId as G}from"./utils.js";export const sendCommand=Z({site:"deepseek",name:"send",access:"write",description:"Send a prompt to a specific DeepSeek conversation by ID, without waiting for a response",domain:h,strategy:$.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,args:[{name:"id",required:!0,positional:!0,help:"Conversation ID (UUID) or full /a/chat/s/<id> URL"},{name:"prompt",required:!0,positional:!0,help:"Prompt to send"},{name:"timeout",type:"int",required:!1,default:60,help:"Max seconds for the overall command (default: 60)"}],columns:["Status","InjectedText"],func:async(q,z)=>{const B=G(z.id),Q=z.prompt;await q.goto(`https://chat.deepseek.com/a/chat/s/${B}`);await J(q);if(!await q.evaluate(`(() => {
1
+ import{cli as Z,Strategy as $}from"@jackwener/opencli/registry";import{CommandExecutionError as H}from"@jackwener/opencli/errors";import{DEEPSEEK_DOMAIN as h,MESSAGE_SELECTOR as F,TEXTAREA_SELECTOR as V,parseDeepSeekConversationId as G}from"./utils.js";export const sendCommand=Z({site:"deepseek",name:"send",access:"write",description:"Send a prompt to a specific DeepSeek conversation by ID, without waiting for a response",domain:h,strategy:$.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,args:[{name:"id",required:!0,positional:!0,help:"Conversation ID (UUID) or full /a/chat/s/<id> URL"},{name:"prompt",fromFile:!0,required:!0,positional:!0,help:"Prompt to send"},{name:"timeout",type:"int",required:!1,default:60,help:"Max seconds for the overall command (default: 60)"}],columns:["Status","InjectedText"],func:async(q,z)=>{const B=G(z.id),Q=z.prompt;await q.goto(`https://chat.deepseek.com/a/chat/s/${B}`);await J(q);if(!await q.evaluate(`(() => {
2
2
  const box = document.querySelector('${V}');
3
3
  if (!box) return false;
4
4
  box.focus();
@@ -1,4 +1,4 @@
1
- import{ArgumentError as K,AuthRequiredError as $,CommandExecutionError as P}from"@jackwener/opencli/errors";import{cli as q,Strategy as z}from"@jackwener/opencli/registry";const G=/^(?:@me|\d{3,})\/\d{3,}$/;export function normalizeChannelUrl(b){const f=String(b||"").trim();if(!f)throw new K("Channel is required.",'Pass a channel URL like https://discord.com/channels/<guild>/<channel>, or "<guild>/<channel>".');if(/^https?:\/\//i.test(f)){let J;try{J=new URL(f)}catch{throw new K(`Invalid channel URL: ${f}`)}const M=J.hostname.toLowerCase();if(M!=="discord.com"&&!M.endsWith(".discord.com"))throw new K("Channel URL must be a discord.com URL.");const F=J.pathname.split("/").filter(Boolean);if(F[0]!=="channels"||F.length<3)throw new K("Channel URL must look like /channels/<guild>/<channel>.");return`https://discord.com/channels/${F[1]}/${F[2]}`}if(!G.test(f))throw new K(`Invalid channel reference: ${JSON.stringify(f)}`,'Use "<guild>/<channel>" (numeric ids) or "@me/<dm-channel>", or a full channel URL.');return`https://discord.com/channels/${f}`}q({site:"discord-web",name:"send",access:"write",description:'Send a message to a Discord channel (discord.com). Pass the channel URL or "<guild>/<channel>". Multi-line text is sent as one message.',domain:"discord.com",strategy:z.COOKIE,browser:!0,args:[{name:"channel",type:"string",required:!0,positional:!0,help:'Channel URL (https://discord.com/channels/<guild>/<channel>) or "<guild>/<channel>"'},{name:"text",type:"string",required:!0,positional:!0,help:"Message text (multi-line allowed)"}],columns:["status","channel_id","message"],func:async(b,f)=>{if(!b)throw new P("Browser session required");const J=normalizeChannelUrl(f.channel),M=J.split("/").pop(),F=String(f.text??"");if(!F.trim())throw new K("Message text cannot be empty.");await b.goto(J);let W=!1;for(let B=0;B<12;B++){const Z=await b.evaluate(`(() => {
1
+ import{ArgumentError as K,AuthRequiredError as $,CommandExecutionError as P}from"@jackwener/opencli/errors";import{cli as q,Strategy as z}from"@jackwener/opencli/registry";const G=/^(?:@me|\d{3,})\/\d{3,}$/;export function normalizeChannelUrl(b){const f=String(b||"").trim();if(!f)throw new K("Channel is required.",'Pass a channel URL like https://discord.com/channels/<guild>/<channel>, or "<guild>/<channel>".');if(/^https?:\/\//i.test(f)){let J;try{J=new URL(f)}catch{throw new K(`Invalid channel URL: ${f}`)}const M=J.hostname.toLowerCase();if(M!=="discord.com"&&!M.endsWith(".discord.com"))throw new K("Channel URL must be a discord.com URL.");const F=J.pathname.split("/").filter(Boolean);if(F[0]!=="channels"||F.length<3)throw new K("Channel URL must look like /channels/<guild>/<channel>.");return`https://discord.com/channels/${F[1]}/${F[2]}`}if(!G.test(f))throw new K(`Invalid channel reference: ${JSON.stringify(f)}`,'Use "<guild>/<channel>" (numeric ids) or "@me/<dm-channel>", or a full channel URL.');return`https://discord.com/channels/${f}`}q({site:"discord-web",name:"send",access:"write",description:'Send a message to a Discord channel (discord.com). Pass the channel URL or "<guild>/<channel>". Multi-line text is sent as one message.',domain:"discord.com",strategy:z.COOKIE,browser:!0,args:[{name:"channel",type:"string",required:!0,positional:!0,help:'Channel URL (https://discord.com/channels/<guild>/<channel>) or "<guild>/<channel>"'},{name:"text",fromFile:!0,type:"string",required:!0,positional:!0,help:"Message text (multi-line allowed)"}],columns:["status","channel_id","message"],func:async(b,f)=>{if(!b)throw new P("Browser session required");const J=normalizeChannelUrl(f.channel),M=J.split("/").pop(),F=String(f.text??"");if(!F.trim())throw new K("Message text cannot be empty.");await b.goto(J);let W=!1;for(let B=0;B<12;B++){const Z=await b.evaluate(`(() => {
2
2
  if (/\\/login(\\b|\\/|\\?|$)/.test(location.pathname)) return 'login';
3
3
  const ed = document.querySelector('[role="textbox"][data-slate-editor="true"]');
4
4
  if (!ed) return 'no-editor';
@@ -1 +1 @@
1
- import{cli as a,Strategy as u}from"@jackwener/opencli/registry";import{ArgumentError as m}from"@jackwener/opencli/errors";import{DOUBAO_DOMAIN as l,getDoubaoTranscriptLines as c,getDoubaoVisibleTurns as p,sendDoubaoMessage as b,waitForDoubaoResponse as d}from"./utils.js";export const askCommand=a({site:"doubao",name:"ask",access:"write",description:"Send a prompt and wait for the Doubao response",domain:l,strategy:u.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,args:[{name:"text",required:!0,positional:!0,help:"Prompt to send"},{name:"timeout",type:"int",required:!1,help:"Max seconds to wait (default: 60)",default:60}],columns:["Role","Text"],func:async(e,s)=>{const{text:t,timeout:o}=s;if(!Number.isInteger(o)||o<1)throw new m("--timeout must be a positive integer (seconds)");const i=await p(e),n=await c(e);await b(e,t);const r=await d(e,n,i,t,o);if(!r)return[{Role:"User",Text:t},{Role:"System",Text:`No response within ${o}s. Doubao may still be generating.`}];return[{Role:"User",Text:t},{Role:"Assistant",Text:r}]}});
1
+ import{cli as a,Strategy as u}from"@jackwener/opencli/registry";import{ArgumentError as m}from"@jackwener/opencli/errors";import{DOUBAO_DOMAIN as l,getDoubaoTranscriptLines as c,getDoubaoVisibleTurns as p,sendDoubaoMessage as f,waitForDoubaoResponse as b}from"./utils.js";export const askCommand=a({site:"doubao",name:"ask",access:"write",description:"Send a prompt and wait for the Doubao response",domain:l,strategy:u.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,args:[{name:"text",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}],columns:["Role","Text"],func:async(e,s)=>{const{text:t,timeout:o}=s;if(!Number.isInteger(o)||o<1)throw new m("--timeout must be a positive integer (seconds)");const i=await p(e),n=await c(e);await f(e,t);const r=await b(e,n,i,t,o);if(!r)return[{Role:"User",Text:t},{Role:"System",Text:`No response within ${o}s. Doubao may still be generating.`}];return[{Role:"User",Text:t},{Role:"Assistant",Text:r}]}});
@@ -1 +1 @@
1
- import{cli as K,Strategy as M}from"@jackwener/opencli/registry";import{SEL as N,injectTextScript as O,clickSendScript as Q,pollResponseScript as U}from"./utils.js";export const askCommand=K({site:"doubao-app",name:"ask",access:"write",description:"Send a message to Doubao desktop app and wait for the AI response",domain:"doubao-app",strategy:M.UI,browser:!0,args:[{name:"text",required:!0,positional:!0,help:"Prompt to send"},{name:"timeout",type:"int",default:30,help:"Max seconds to wait for response"}],columns:["Role","Text"],func:async(h,B)=>{const z=B.text,D=B.timeout||30,H=await h.evaluate(`document.querySelectorAll('${N.MESSAGE}').length`);if(!(await h.evaluate(O(z)))?.ok)throw Error("Could not find chat input.");await h.wait(0.5);if(!await h.evaluate(Q()))await h.pressKey("Enter");const F=1,J=Math.ceil(D/F);let A="";for(let G=0;G<J;G++){await h.wait(F);const q=await h.evaluate(U(H));if(!q)continue;if(q.phase==="done"&&q.text){A=q.text;break}}if(!A)return[{Role:"User",Text:z},{Role:"System",Text:`No response received within ${D}s.`}];return[{Role:"User",Text:z},{Role:"Assistant",Text:A}]}});
1
+ import{cli as K,Strategy as M}from"@jackwener/opencli/registry";import{SEL as N,injectTextScript as O,clickSendScript as Q,pollResponseScript as U}from"./utils.js";export const askCommand=K({site:"doubao-app",name:"ask",access:"write",description:"Send a message to Doubao desktop app and wait for the AI response",domain:"doubao-app",strategy:M.UI,browser:!0,args:[{name:"text",fromFile:!0,required:!0,positional:!0,help:"Prompt to send"},{name:"timeout",type:"int",default:30,help:"Max seconds to wait for response"}],columns:["Role","Text"],func:async(h,B)=>{const z=B.text,D=B.timeout||30,H=await h.evaluate(`document.querySelectorAll('${N.MESSAGE}').length`);if(!(await h.evaluate(O(z)))?.ok)throw Error("Could not find chat input.");await h.wait(0.5);if(!await h.evaluate(Q()))await h.pressKey("Enter");const F=1,J=Math.ceil(D/F);let A="";for(let G=0;G<J;G++){await h.wait(F);const q=await h.evaluate(U(H));if(!q)continue;if(q.phase==="done"&&q.text){A=q.text;break}}if(!A)return[{Role:"User",Text:z},{Role:"System",Text:`No response received within ${D}s.`}];return[{Role:"User",Text:z},{Role:"Assistant",Text:A}]}});
@@ -1 +1 @@
1
- import{cli as n,Strategy as r}from"@jackwener/opencli/registry";import{injectTextScript as i,clickSendScript as s}from"./utils.js";export const sendCommand=n({site:"doubao-app",name:"send",access:"write",description:"Send a message to Doubao desktop app",domain:"doubao-app",strategy:r.UI,browser:!0,args:[{name:"text",required:!0,positional:!0,help:"Message text to send"}],columns:["Status","Text"],func:async(t,o)=>{const a=o.text,e=await t.evaluate(i(a));if(!e||!e.ok)throw Error("Could not find chat input: "+(e?.error||"unknown"));await t.wait(0.5);if(!await t.evaluate(s()))await t.pressKey("Enter");await t.wait(1);return[{Status:"Sent",Text:a}]}});
1
+ import{cli as r,Strategy as i}from"@jackwener/opencli/registry";import{injectTextScript as n,clickSendScript as s}from"./utils.js";export const sendCommand=r({site:"doubao-app",name:"send",access:"write",description:"Send a message to Doubao desktop app",domain:"doubao-app",strategy:i.UI,browser:!0,args:[{name:"text",fromFile:!0,required:!0,positional:!0,help:"Message text to send"}],columns:["Status","Text"],func:async(t,o)=>{const a=o.text,e=await t.evaluate(n(a));if(!e||!e.ok)throw Error("Could not find chat input: "+(e?.error||"unknown"));await t.wait(0.5);if(!await t.evaluate(s()))await t.pressKey("Enter");await t.wait(1);return[{Status:"Sent",Text:a}]}});
@@ -163,4 +163,4 @@ import*as W from"node:fs";import*as J from"node:path";import{cli as M,Strategy a
163
163
  }`);if(z?.ok)break;await q.wait({time:0.5})}if(!z?.ok)throw new $(`点击草稿按钮失败: ${z?.reason||"unknown"}`);if(!z.creationId)throw new $("点击草稿按钮失败: creation-id-missing");return{text:z.text||"暂存离开",creationId:z.creationId}}async function _(q,G){let z={href:"",bodyText:""};for(let H=0;H<40;H+=1){z=await q.evaluate(`() => ({
164
164
  href: location.href,
165
165
  bodyText: document.body?.innerText || ''
166
- })`);const Q=z.href||"",X=z.bodyText||"",Z=Q.includes("/creator-micro/content/upload")&&/继续编辑/.test(X),K=/草稿保存成功|已保存到草稿|存草稿成功|保存成功/.test(X),Y=/\/creator-micro\/content\/(manage|drafts|works)/.test(Q)||/创作中心/.test(X)&&!Q.includes("/content/upload");if(Z||K||Y)return G;await q.wait({time:1})}throw new $("未检测到抖音草稿保存确认",`当前页面: ${z.href||"unknown"}`)}M({site:"douyin",name:"draft",access:"write",description:"上传视频并保存为草稿",domain:"creator.douyin.com",strategy:f.COOKIE,navigateBefore:!1,defaultWindowMode:"foreground",args:[{name:"video",required:!0,positional:!0,help:"视频文件路径"},{name:"title",required:!0,help:"视频标题(≤30字)"},{name:"caption",default:"",help:"正文内容(≤1000字,支持 #话题)"},{name:"cover",default:"",help:"封面图片路径"},{name:"visibility",default:"public",choices:["public","friends","private"]},{name:"timeout",type:"int",default:180,help:"命令超时(秒),视频上传/转码慢时可调大"}],columns:["status","draft_id"],func:async(q,G)=>{const z=J.resolve(G.video);if(!W.existsSync(z))throw new N(`视频文件不存在: ${z}`);const H=J.extname(z).toLowerCase();if(![".mp4",".mov",".avi",".webm"].includes(H))throw new N(`不支持的视频格式: ${H}(支持 mp4/mov/avi/webm)`);const Q=G.title;if(Q.length>30)throw new N("标题不能超过 30 字");const X=G.caption||"";if(X.length>1000)throw new N("正文不能超过 1000 字");const Z=G.cover;if(Z){if(!W.existsSync(J.resolve(Z)))throw new N(`封面文件不存在: ${J.resolve(Z)}`)}const K=j[G.visibility]??j.public;await q.goto(R);await q.wait({selector:'input[type="file"]',timeout:20});await B(q);await U(q,'input[type="file"]',[z]);await b(q);await x(q,K);await B(q);if(Z){const V=await h(q);await U(q,V,[J.resolve(Z)]);await k(q)}await w(q,{title:Q,caption:X,visibilityLabel:K});await q.wait({time:1});const Y=await S(q);return[{status:"✅ 草稿已保存,可在创作中心继续编辑",draft_id:await _(q,Y.creationId)}]}});
166
+ })`);const Q=z.href||"",X=z.bodyText||"",Z=Q.includes("/creator-micro/content/upload")&&/继续编辑/.test(X),K=/草稿保存成功|已保存到草稿|存草稿成功|保存成功/.test(X),Y=/\/creator-micro\/content\/(manage|drafts|works)/.test(Q)||/创作中心/.test(X)&&!Q.includes("/content/upload");if(Z||K||Y)return G;await q.wait({time:1})}throw new $("未检测到抖音草稿保存确认",`当前页面: ${z.href||"unknown"}`)}M({site:"douyin",name:"draft",access:"write",description:"上传视频并保存为草稿",domain:"creator.douyin.com",strategy:f.COOKIE,navigateBefore:!1,defaultWindowMode:"foreground",args:[{name:"video",required:!0,positional:!0,help:"视频文件路径"},{name:"title",required:!0,help:"视频标题(≤30字)"},{name:"caption",fromFile:!0,default:"",help:"正文内容(≤1000字,支持 #话题)"},{name:"cover",default:"",help:"封面图片路径"},{name:"visibility",default:"public",choices:["public","friends","private"]},{name:"timeout",type:"int",default:180,help:"命令超时(秒),视频上传/转码慢时可调大"}],columns:["status","draft_id"],func:async(q,G)=>{const z=J.resolve(G.video);if(!W.existsSync(z))throw new N(`视频文件不存在: ${z}`);const H=J.extname(z).toLowerCase();if(![".mp4",".mov",".avi",".webm"].includes(H))throw new N(`不支持的视频格式: ${H}(支持 mp4/mov/avi/webm)`);const Q=G.title;if(Q.length>30)throw new N("标题不能超过 30 字");const X=G.caption||"";if(X.length>1000)throw new N("正文不能超过 1000 字");const Z=G.cover;if(Z){if(!W.existsSync(J.resolve(Z)))throw new N(`封面文件不存在: ${J.resolve(Z)}`)}const K=j[G.visibility]??j.public;await q.goto(R);await q.wait({selector:'input[type="file"]',timeout:20});await B(q);await U(q,'input[type="file"]',[z]);await b(q);await x(q,K);await B(q);if(Z){const V=await h(q);await U(q,V,[J.resolve(Z)]);await k(q)}await w(q,{title:Q,caption:X,visibilityLabel:K});await q.wait({time:1});const Y=await S(q);return[{status:"✅ 草稿已保存,可在创作中心继续编辑",draft_id:await _(q,Y.creationId)}]}});
@@ -1,4 +1,4 @@
1
- import*as f from"node:fs";import*as M from"node:path";import{cli as a,Strategy as r}from"@jackwener/opencli/registry";import{ArgumentError as B,CommandExecutionError as G}from"@jackwener/opencli/errors";import{getUploadAuthV5Credentials as e,applyVideoUploadInner as KK,commitVideoUploadInner as QK}from"./_shared/vod-upload.js";import{tosUpload as ZK}from"./_shared/tos-upload.js";import{imagexUpload as $K}from"./_shared/imagex-upload.js";import{browserFetch as u}from"./_shared/browser-fetch.js";import{requireObjectEvaluateResult as k}from"./_shared/evaluate-result.js";import{generateCreationId as zK}from"./_shared/creation-id.js";import{validateTiming as YK,toUnixSeconds as qK}from"./_shared/timing.js";import{parseTextExtra as JK,extractHashtagNames as LK}from"./_shared/text-extra.js";const NK={public:0,friends:1,private:2},w="https://imagex.bytedanceapi.com",c="1147",WK="aid=1128&cookie_enabled=true&screen_width=1512&screen_height=982&browser_language=zh-CN&browser_platform=MacIntel&browser_name=Mozilla&browser_online=true&timezone_name=Asia%2FTokyo&support_h265=1",GK=JSON.stringify({video_cover_source:2,cover_timestamp:0,recommend_timestamp:0,is_cover_edit:0,is_cover_template:0,cover_template_id:"",is_text_template:0,text_template_id:"",text_template_content:"",is_text:0,text_num:0,text_content:"",is_use_sticker:0,sticker_id:"",is_use_filter:0,filter_id:"",is_cover_modify:0,to_status:0,cover_type:0,initial_cover_uri:"",cut_coordinate:""});function HK(Z){const K=Z instanceof Error?Z.message:String(Z);return K.includes("post_assistant/fast_detect")&&(K.includes("Empty response")||K.includes("404")||K.includes("Not Found")||K.includes("Timeout")||K.includes("timed out")||K.includes("Failed to fetch"))}function x(Z){return new Promise((K)=>setTimeout(K,Z))}function h(Z,K){const q=K?.ResponseMetadata?.Error??K?.Error;if(q)throw new G(`${Z}失败: ${JSON.stringify(q)}`)}async function n(Z,K,q,D){let O;for(let Y=1;Y<=3;Y+=1)try{return{ok:!0,value:await u(Z,K,q,D)}}catch($){if(!HK($))throw $;O=$;if(Y<3)await x(500*Y)}return{ok:!1,error:O}}a({site:"douyin",name:"publish",access:"write",description:"定时发布视频到抖音(必须设置 2h ~ 14天后的发布时间)",domain:"creator.douyin.com",strategy:r.COOKIE,defaultWindowMode:"foreground",args:[{name:"video",required:!0,positional:!0,help:"视频文件路径"},{name:"title",required:!0,help:"视频标题(≤30字)"},{name:"schedule",required:!0,help:"定时发布时间(ISO8601 或 Unix 秒,2h ~ 14天后)"},{name:"caption",default:"",help:"正文内容(≤1000字,支持 #话题)"},{name:"cover",default:"",help:"封面图片路径(不提供时使用视频截帧)"},{name:"visibility",default:"public",choices:["public","friends","private"]},{name:"allow_download",type:"bool",default:!1,help:"允许下载"},{name:"collection",default:"",help:"合集 ID"},{name:"activity",default:"",help:"活动 ID"},{name:"poi_id",default:"",help:"地理位置 ID"},{name:"poi_name",default:"",help:"地理位置名称"},{name:"hotspot",default:"",help:"关联热点词"},{name:"no_safety_check",type:"bool",default:!1,help:"跳过内容安全检测"},{name:"sync_toutiao",type:"bool",default:!1,help:"同步发布到头条"}],columns:["status","aweme_id","url","publish_time"],func:async(Z,K)=>{const q=M.resolve(K.video);if(!f.existsSync(q))throw new B(`视频文件不存在: ${q}`);const D=M.extname(q).toLowerCase();if(![".mp4",".mov",".avi",".webm"].includes(D))throw new B(`不支持的视频格式: ${D}(支持 mp4/mov/avi/webm)`);const O=f.statSync(q).size,Y=K.title;if(Y.length>30)throw new B("标题不能超过 30 字");const $=K.caption||"";if($.length>1000)throw new B("正文不能超过 1000 字");const V=qK(K.schedule);YK(V);const m=NK[K.visibility]??0,A=K.cover;if(A){if(!f.existsSync(M.resolve(A)))throw new B(`封面文件不存在: ${M.resolve(A)}`)}const S=await e(Z),U=await KK(O,S);let j="",_=720,b=1280;await ZK({filePath:q,uploadInfo:U,credentials:S,onProgress:(Q,z)=>{const J=Math.round(Q/z*100);process.stderr.write(`\r 上传进度: ${J}%`)}});process.stderr.write(`
1
+ import*as f from"node:fs";import*as M from"node:path";import{cli as a,Strategy as r}from"@jackwener/opencli/registry";import{ArgumentError as B,CommandExecutionError as G}from"@jackwener/opencli/errors";import{getUploadAuthV5Credentials as e,applyVideoUploadInner as KK,commitVideoUploadInner as QK}from"./_shared/vod-upload.js";import{tosUpload as ZK}from"./_shared/tos-upload.js";import{imagexUpload as $K}from"./_shared/imagex-upload.js";import{browserFetch as u}from"./_shared/browser-fetch.js";import{requireObjectEvaluateResult as k}from"./_shared/evaluate-result.js";import{generateCreationId as zK}from"./_shared/creation-id.js";import{validateTiming as YK,toUnixSeconds as qK}from"./_shared/timing.js";import{parseTextExtra as JK,extractHashtagNames as LK}from"./_shared/text-extra.js";const NK={public:0,friends:1,private:2},w="https://imagex.bytedanceapi.com",c="1147",WK="aid=1128&cookie_enabled=true&screen_width=1512&screen_height=982&browser_language=zh-CN&browser_platform=MacIntel&browser_name=Mozilla&browser_online=true&timezone_name=Asia%2FTokyo&support_h265=1",GK=JSON.stringify({video_cover_source:2,cover_timestamp:0,recommend_timestamp:0,is_cover_edit:0,is_cover_template:0,cover_template_id:"",is_text_template:0,text_template_id:"",text_template_content:"",is_text:0,text_num:0,text_content:"",is_use_sticker:0,sticker_id:"",is_use_filter:0,filter_id:"",is_cover_modify:0,to_status:0,cover_type:0,initial_cover_uri:"",cut_coordinate:""});function HK(Z){const K=Z instanceof Error?Z.message:String(Z);return K.includes("post_assistant/fast_detect")&&(K.includes("Empty response")||K.includes("404")||K.includes("Not Found")||K.includes("Timeout")||K.includes("timed out")||K.includes("Failed to fetch"))}function x(Z){return new Promise((K)=>setTimeout(K,Z))}function h(Z,K){const q=K?.ResponseMetadata?.Error??K?.Error;if(q)throw new G(`${Z}失败: ${JSON.stringify(q)}`)}async function n(Z,K,q,D){let O;for(let Y=1;Y<=3;Y+=1)try{return{ok:!0,value:await u(Z,K,q,D)}}catch($){if(!HK($))throw $;O=$;if(Y<3)await x(500*Y)}return{ok:!1,error:O}}a({site:"douyin",name:"publish",access:"write",description:"定时发布视频到抖音(必须设置 2h ~ 14天后的发布时间)",domain:"creator.douyin.com",strategy:r.COOKIE,defaultWindowMode:"foreground",args:[{name:"video",required:!0,positional:!0,help:"视频文件路径"},{name:"title",required:!0,help:"视频标题(≤30字)"},{name:"schedule",required:!0,help:"定时发布时间(ISO8601 或 Unix 秒,2h ~ 14天后)"},{name:"caption",fromFile:!0,default:"",help:"正文内容(≤1000字,支持 #话题)"},{name:"cover",default:"",help:"封面图片路径(不提供时使用视频截帧)"},{name:"visibility",default:"public",choices:["public","friends","private"]},{name:"allow_download",type:"bool",default:!1,help:"允许下载"},{name:"collection",default:"",help:"合集 ID"},{name:"activity",default:"",help:"活动 ID"},{name:"poi_id",default:"",help:"地理位置 ID"},{name:"poi_name",default:"",help:"地理位置名称"},{name:"hotspot",default:"",help:"关联热点词"},{name:"no_safety_check",type:"bool",default:!1,help:"跳过内容安全检测"},{name:"sync_toutiao",type:"bool",default:!1,help:"同步发布到头条"}],columns:["status","aweme_id","url","publish_time"],func:async(Z,K)=>{const q=M.resolve(K.video);if(!f.existsSync(q))throw new B(`视频文件不存在: ${q}`);const D=M.extname(q).toLowerCase();if(![".mp4",".mov",".avi",".webm"].includes(D))throw new B(`不支持的视频格式: ${D}(支持 mp4/mov/avi/webm)`);const O=f.statSync(q).size,Y=K.title;if(Y.length>30)throw new B("标题不能超过 30 字");const $=K.caption||"";if($.length>1000)throw new B("正文不能超过 1000 字");const V=qK(K.schedule);YK(V);const m=NK[K.visibility]??0,A=K.cover;if(A){if(!f.existsSync(M.resolve(A)))throw new B(`封面文件不存在: ${M.resolve(A)}`)}const S=await e(Z),U=await KK(O,S);let j="",_=720,b=1280;await ZK({filePath:q,uploadInfo:U,credentials:S,onProgress:(Q,z)=>{const J=Math.round(Q/z*100);process.stderr.write(`\r 上传进度: ${J}%`)}});process.stderr.write(`
2
2
  `);process.stderr.write(` 提交上传...
3
3
  `);const H=await QK(U,S),C=H.video_id;process.stderr.write(` 上传已提交: ${C}
4
4
  `);_=H.width||_;b=H.height||b;if(!j&&H.poster_uri)j=H.poster_uri;if(K.cover){const Q=M.resolve(K.cover),z=`${w}/?Action=ApplyImageUpload&ServiceId=${c}&Version=2018-08-01&UploadNum=1`,J=`fetch(${JSON.stringify(z)}, { credentials: 'include' }).then(r => r.json())`,X=k(await Z.evaluate(J),"抖音封面申请上传地址响应异常");h("抖音封面申请上传地址",X);const L=X.Result?.UploadAddress?.StoreInfos?.[0];if(!L?.UploadHost||!L?.StoreUri)throw new G(`抖音封面申请上传地址响应缺少 UploadHost/StoreUri: ${JSON.stringify(X).slice(0,500)}`);const P=`https://${L.UploadHost}/${L.StoreUri}`,N=await $K(Q,{upload_url:P,store_uri:L.StoreUri}),T=`${w}/?Action=CommitImageUpload&ServiceId=${c}&Version=2018-08-01`,W=JSON.stringify({SuccessObjKeys:[N]}),o=`
@@ -1 +1 @@
1
- import{cli as o,Strategy as d}from"@jackwener/opencli/registry";import{ArgumentError as r}from"@jackwener/opencli/errors";import{browserFetch as a}from"./_shared/browser-fetch.js";import{toUnixSeconds as m,validateTiming as c}from"./_shared/timing.js";o({site:"douyin",name:"update",access:"write",description:"更新视频信息",domain:"creator.douyin.com",strategy:d.COOKIE,args:[{name:"aweme_id",required:!0,positional:!0,help:"抖音作品 ID(aweme_id,可从作品 URL 末尾获取)"},{name:"reschedule",default:"",help:"新的发布时间(ISO8601 或 Unix 秒)"},{name:"caption",default:"",help:"新的正文内容"}],columns:["status"],func:async(i,e)=>{if(!e.reschedule&&!e.caption)throw new r("必须提供 --reschedule 或 --caption");if(e.reschedule){const t=m(e.reschedule);c(t);await a(i,"POST","https://creator.douyin.com/web/api/media/update/timer/?aid=1128",{body:{aweme_id:e.aweme_id,publish_time:t}})}if(e.caption)await a(i,"POST","https://creator.douyin.com/web/api/media/update/desc/?aid=1128",{body:{aweme_id:e.aweme_id,desc:e.caption}});return[{status:"✅ 更新成功"}]}});
1
+ import{cli as a,Strategy as d}from"@jackwener/opencli/registry";import{ArgumentError as r}from"@jackwener/opencli/errors";import{browserFetch as o}from"./_shared/browser-fetch.js";import{toUnixSeconds as m,validateTiming as c}from"./_shared/timing.js";a({site:"douyin",name:"update",access:"write",description:"更新视频信息",domain:"creator.douyin.com",strategy:d.COOKIE,args:[{name:"aweme_id",required:!0,positional:!0,help:"抖音作品 ID(aweme_id,可从作品 URL 末尾获取)"},{name:"reschedule",default:"",help:"新的发布时间(ISO8601 或 Unix 秒)"},{name:"caption",fromFile:!0,default:"",help:"新的正文内容"}],columns:["status"],func:async(i,e)=>{if(!e.reschedule&&!e.caption)throw new r("必须提供 --reschedule 或 --caption");if(e.reschedule){const t=m(e.reschedule);c(t);await o(i,"POST","https://creator.douyin.com/web/api/media/update/timer/?aid=1128",{body:{aweme_id:e.aweme_id,publish_time:t}})}if(e.caption)await o(i,"POST","https://creator.douyin.com/web/api/media/update/desc/?aid=1128",{body:{aweme_id:e.aweme_id,desc:e.caption}});return[{status:"✅ 更新成功"}]}});
@@ -1,4 +1,4 @@
1
- import{ArgumentError as H,AuthRequiredError as Z,CommandExecutionError as J}from"@jackwener/opencli/errors";import{cli as _,Strategy as $}from"@jackwener/opencli/registry";const Y='[data-lexical-editor="true"][contenteditable="true"]',K='[data-pp-fb-composer="1"]';_({site:"facebook",name:"comment",access:"write",description:"Post a comment on a Facebook post. Pass the post URL and the comment text (multi-line allowed). The comment is published from your logged-in account — write a genuine, on-topic remark, not filler.",domain:"facebook.com",strategy:$.COOKIE,browser:!0,args:[{name:"url",type:"string",required:!0,positional:!0,help:"Post URL (e.g. https://www.facebook.com/<user>/posts/<id> or a permalink)"},{name:"text",type:"string",required:!0,positional:!0,help:"Comment text (multi-line allowed)"}],columns:["status","url","message"],func:async(v,U)=>{if(!v)throw new J("Browser session required");const B=String(U.url??"").trim();if(!B)throw new H("Post URL is required.");let L;try{L=new URL(B)}catch{throw new H(`Invalid post URL: ${B}`)}if(!/facebook\.com$/.test(L.hostname)&&!L.hostname.endsWith(".facebook.com"))throw new H("URL must be a facebook.com post URL.");const G=String(U.text??"");if(!G.trim())throw new H("Comment text cannot be empty.");await v.goto(B,{waitUntil:"none"});let V=!1;for(let z=0;z<15;z++){const X=await v.evaluate(`(() => {
1
+ import{ArgumentError as H,AuthRequiredError as Z,CommandExecutionError as J}from"@jackwener/opencli/errors";import{cli as _,Strategy as $}from"@jackwener/opencli/registry";const Y='[data-lexical-editor="true"][contenteditable="true"]',K='[data-pp-fb-composer="1"]';_({site:"facebook",name:"comment",access:"write",description:"Post a comment on a Facebook post. Pass the post URL and the comment text (multi-line allowed). The comment is published from your logged-in account — write a genuine, on-topic remark, not filler.",domain:"facebook.com",strategy:$.COOKIE,browser:!0,args:[{name:"url",type:"string",required:!0,positional:!0,help:"Post URL (e.g. https://www.facebook.com/<user>/posts/<id> or a permalink)"},{name:"text",fromFile:!0,type:"string",required:!0,positional:!0,help:"Comment text (multi-line allowed)"}],columns:["status","url","message"],func:async(v,U)=>{if(!v)throw new J("Browser session required");const B=String(U.url??"").trim();if(!B)throw new H("Post URL is required.");let L;try{L=new URL(B)}catch{throw new H(`Invalid post URL: ${B}`)}if(!/facebook\.com$/.test(L.hostname)&&!L.hostname.endsWith(".facebook.com"))throw new H("URL must be a facebook.com post URL.");const G=String(U.text??"");if(!G.trim())throw new H("Comment text cannot be empty.");await v.goto(B,{waitUntil:"none"});let V=!1;for(let z=0;z<15;z++){const X=await v.evaluate(`(() => {
2
2
  const path = location.pathname || '';
3
3
  if (/^\\/(login|checkpoint)(\\/|$|\\.php)/.test(path)) return 'login';
4
4
  const dialog = document.querySelector('[role="dialog"]');
@@ -54,7 +54,7 @@ import*as y from"node:fs";import*as f from"node:path";import{ArgumentError as q,
54
54
  if (remaining.length === 0) return { ok: true };
55
55
  if (remaining === collapse(${JSON.stringify(z)})) return { ok: false, remaining };
56
56
  return { ok: true };
57
- })()`}const C='[role="dialog"] input[type="file"], input[type="file"][accept*="image"]';async function O(z,K){await _(z,C,K)}N({site:"facebook",name:"post",access:"write",description:"Publish a new post to your Facebook feed (or a Page you manage). Pass the post text (multi-line allowed); optionally attach local images and target a Page. Published from your logged-in account — write genuine, on-topic content, not filler.",domain:"facebook.com",strategy:A.UI,browser:!0,args:[{name:"text",type:"string",required:!0,positional:!0,help:"Post text (multi-line allowed)"},{name:"images",type:"string",required:!1,help:"Local image paths, comma-separated (jpg/png/gif/webp)"},{name:"target",type:"string",required:!1,help:"Optional Page/profile to post to: a facebook.com URL or a bare vanity. Omit to post to your own feed."}],columns:["status","target","message"],func:async(z,K)=>{if(!z)throw new W("Browser session required for facebook post");const Q=j(K.text,{label:"帖子正文"});if(!Q)throw new q("Post text cannot be empty.");const J=K.images?w(K.images):[],Y=k(K.target);await z.goto(Y,{waitUntil:"none"});let Z=!1;for(let V=0;V<20;V++){const X=await z.evaluate(R());if(X==="login")throw new L("www.facebook.com","Facebook redirected to login/checkpoint — not signed in");if(X==="ready"){Z=!0;break}if(X==="trigger")await z.click(M);await z.wait(1)}if(!Z)throw new W("Post composer not found — the feed publisher may not have loaded, or you may lack permission to post here.");await z.click($);await z.wait(1);await z.pressKey("Control+A");await z.pressKey("Delete");if(J.length>0){await O(z,J);await z.wait(2)}const B=Q.split(`
57
+ })()`}const C='[role="dialog"] input[type="file"], input[type="file"][accept*="image"]';async function O(z,K){await _(z,C,K)}N({site:"facebook",name:"post",access:"write",description:"Publish a new post to your Facebook feed (or a Page you manage). Pass the post text (multi-line allowed); optionally attach local images and target a Page. Published from your logged-in account — write genuine, on-topic content, not filler.",domain:"facebook.com",strategy:A.UI,browser:!0,args:[{name:"text",fromFile:!0,type:"string",required:!0,positional:!0,help:"Post text (multi-line allowed)"},{name:"images",type:"string",required:!1,help:"Local image paths, comma-separated (jpg/png/gif/webp)"},{name:"target",type:"string",required:!1,help:"Optional Page/profile to post to: a facebook.com URL or a bare vanity. Omit to post to your own feed."}],columns:["status","target","message"],func:async(z,K)=>{if(!z)throw new W("Browser session required for facebook post");const Q=j(K.text,{label:"帖子正文"});if(!Q)throw new q("Post text cannot be empty.");const J=K.images?w(K.images):[],Y=k(K.target);await z.goto(Y,{waitUntil:"none"});let Z=!1;for(let V=0;V<20;V++){const X=await z.evaluate(R());if(X==="login")throw new L("www.facebook.com","Facebook redirected to login/checkpoint — not signed in");if(X==="ready"){Z=!0;break}if(X==="trigger")await z.click(M);await z.wait(1)}if(!Z)throw new W("Post composer not found — the feed publisher may not have loaded, or you may lack permission to post here.");await z.click($);await z.wait(1);await z.pressKey("Control+A");await z.pressKey("Delete");if(J.length>0){await O(z,J);await z.wait(2)}const B=Q.split(`
58
58
  `);for(let V=0;V<B.length;V++){if(V>0)await z.pressKey("Shift+Enter");if(B[V].length)await z.typeText($,B[V])}if(await z.evaluate(`(() => !!document.querySelector('[role="listbox"][aria-label], ul[role="listbox"]'))()`))await z.pressKey("Escape");const F=(V)=>String(V||"").replace(/\s+/g," ").trim(),H=await z.evaluate(`(() => {
59
59
  const ed = document.querySelector('${$}');
60
60
  return ed ? (ed.textContent || '') : null;
@@ -1 +1 @@
1
- import{cli as p,Strategy as f}from"@jackwener/opencli/registry";import{ArgumentError as c}from"@jackwener/opencli/errors";import{GEMINI_DOMAIN as l,readGeminiSnapshot as d,sendGeminiMessage as w,startNewGeminiChat as h,waitForGeminiResponse as S,waitForGeminiSubmission as N}from"./utils.js";function G(e){if(typeof e==="boolean")return e;const t=String(e??"").trim().toLowerCase();return t==="true"||t==="1"||t==="yes"||t==="on"}const r="[NO RESPONSE]";export const askCommand=p({site:"gemini",name:"ask",access:"write",description:"Send a prompt to Gemini and return only the assistant response",domain:l,strategy:f.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:"new",required:!1,help:"Start a new chat first (true/false, default: false)",default:"false"}],columns:["response"],func:async(e,t)=>{const{prompt:s,timeout:n}=t;if(!Number.isInteger(n)||n<1)throw new c("--timeout must be a positive integer (seconds)");if(G(t.new))await h(e);const a=await d(e);await w(e,s);const m=Date.now(),i=await N(e,a,n);if(!i)return[{response:`\uD83D\uDCAC ${r} No Gemini response within ${n}s.`}];const u=Math.max(0,n-Math.ceil((Date.now()-m)/1000)),o=await S(e,i,s,u);if(!o)return[{response:`\uD83D\uDCAC ${r} No Gemini response within ${n}s.`}];return[{response:`\uD83D\uDCAC ${o}`}]}});
1
+ import{cli as p,Strategy as f}from"@jackwener/opencli/registry";import{ArgumentError as c}from"@jackwener/opencli/errors";import{GEMINI_DOMAIN as l,readGeminiSnapshot as d,sendGeminiMessage as w,startNewGeminiChat as h,waitForGeminiResponse as S,waitForGeminiSubmission as N}from"./utils.js";function G(e){if(typeof e==="boolean")return e;const t=String(e??"").trim().toLowerCase();return t==="true"||t==="1"||t==="yes"||t==="on"}const r="[NO RESPONSE]";export const askCommand=p({site:"gemini",name:"ask",access:"write",description:"Send a prompt to Gemini and return only the assistant response",domain:l,strategy:f.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:"new",required:!1,help:"Start a new chat first (true/false, default: false)",default:"false"}],columns:["response"],func:async(e,t)=>{const{prompt:s,timeout:n}=t;if(!Number.isInteger(n)||n<1)throw new c("--timeout must be a positive integer (seconds)");if(G(t.new))await h(e);const a=await d(e);await w(e,s);const m=Date.now(),i=await N(e,a,n);if(!i)return[{response:`\uD83D\uDCAC ${r} No Gemini response within ${n}s.`}];const u=Math.max(0,n-Math.ceil((Date.now()-m)/1000)),o=await S(e,i,s,u);if(!o)return[{response:`\uD83D\uDCAC ${r} No Gemini response within ${n}s.`}];return[{response:`\uD83D\uDCAC ${o}`}]}});