publishport-opencli 1.0.24 → 1.0.26

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.
@@ -1,2 +1,2 @@
1
- import{getRegistry as oe,fullName as g}from"./registry.js";import{pathToFileURL as ie}from"node:url";import*as se from"node:crypto";import*as M from"node:fs";import*as j from"node:os";import*as N from"node:path";import{executePipeline as V}from"./pipeline/index.js";import{adapterLoadError as ae,ArgumentError as d,CommandExecutionError as x,SessionBusyError as ce,attachTraceReceipt as ue,getErrorMessage as D}from"./errors.js";import{shouldUseBrowserSession as le}from"./capabilityRouting.js";import{getBrowserFactory as fe,browserSession as me,runWithTimeout as G,DEFAULT_BROWSER_COMMAND_TIMEOUT as de}from"./runtime.js";import{withBrowserSessionLockIf as pe}from"./browser-session-lock.js";import{resolveProfile as he}from"./browser/profile.js";import{ensureManagedChrome as we}from"./browser/managed-chrome.js";import{generateFingerprintJs as ge}from"./browser/fingerprint.js";import{CDPBridge as ye}from"./browser/index.js";import{clearDaemonRunContext as ve,generateRunId as Se,isUnknownOutcomeError as J,releaseSiteSessionLease as K,setDaemonCommandTimeoutSeconds as be,setDaemonRunContext as Q}from"./browser/daemon-client.js";import{emitHook as k}from"./hooks.js";import{setContentBaseDir as $e}from"./utils.js";import{log as X}from"./logger.js";import{isElectronApp as Ee}from"./electron-apps.js";import{probeCDP as Pe,resolveElectronEndpoint as xe}from"./launcher.js";import{ObservationSession as Ae,exportObservationSession as Ce}from"./observation/index.js";import{resolveAdapterSourcePath as Re}from"./adapter-source.js";const S=new Map,B=new Map,Te=`${j.homedir()}/.ppcli/clis/`;function Ie(e){if(e===void 0||e===null||e===""||e==="off")return"off";if(e==="on"||e==="retain-on-failure")return e;throw new d(`--trace must be one of: off, on, retain-on-failure. Received: "${String(e)}"`)}export function coerceAndValidateArgs(e,t){const n={...t};for(const r of e){const o=n[r.name];if(r.required&&(o===void 0||o===null||o===""))throw new d(`Argument "${r.name}" is required.`,r.help??`Provide a value for --${r.name}`);if(o!==void 0&&o!==null){if(r.type==="int"||r.type==="number"){const u=Number(o);if(!Number.isFinite(u))throw new d(`Argument "${r.name}" must be a valid number. Received: "${o}"`);if(r.type==="int"&&!Number.isInteger(u))throw new d(`Argument "${r.name}" must be a valid integer. Received: "${o}"`);n[r.name]=u}else if(r.type==="boolean"||r.type==="bool")if(typeof o==="string"){const u=o.toLowerCase();if(u==="true"||u==="1")n[r.name]=!0;else if(u==="false"||u==="0")n[r.name]=!1;else throw new d(`Argument "${r.name}" must be a boolean (true/false). Received: "${o}"`)}else n[r.name]=Boolean(o);const s=n[r.name];if(r.choices&&r.choices.length>0){if(!r.choices.map(String).includes(String(s)))throw new d(`Argument "${r.name}" must be one of: ${r.choices.join(", ")}. Received: "${s}"`)}}else if(r.default!==void 0)n[r.name]=r.default}return n}async function U(e,t,n,r){const o=e;if(o._lazy&&o._modulePath){const s=o._modulePath;if(s.startsWith(Te)&&S.has(s))try{const m=M.statSync(s),i=B.get(s);if(i!==void 0&&m.mtimeMs!==i)S.delete(s)}catch{}if(!S.has(s)){const m=ie(s).href,w=import(B.has(s)?`${m}?t=${Date.now()}`:m).then(()=>{try{B.set(s,M.statSync(s).mtimeMs)}catch{}},(p)=>{S.delete(s);throw ae(`Failed to load adapter module ${s}: ${D(p)}`,"Check that the adapter file exists and has no syntax errors.")});S.set(s,w)}await S.get(s);const l=oe().get(g(e));if(l?.func)return Y(l,t,n,r);if(l?.pipeline)return V(t,l.pipeline,{args:n,debug:r})}if(e.func)return Y(e,t,n,r);if(e.pipeline)return V(t,e.pipeline,{args:n,debug:r});throw new x(`Command ${g(e)} has no func or pipeline`,"This is likely a bug in the adapter definition. Please report this issue.")}function Y(e,t,n,r){if(e.browser===!1)return e.func(n,r);if(!t)throw new x(`Command ${g(e)} requires a browser session but none was provided`);return e.func(t,n,r)}function Me(e){if(e.navigateBefore===!1)return null;if(typeof e.navigateBefore==="string")return e.navigateBefore;return null}function Ne(e,t){if(!e||!t)return!1;try{const n=new URL(e).hostname;return n===t||n.endsWith(`.${t}`)}catch{return!1}}function De(e,t){if(!t)return!1;try{const n=new URL(e),r=n.hostname===t||n.hostname.endsWith(`.${t}`),o=n.pathname===""||n.pathname==="/";return r&&o&&n.search===""&&n.hash===""}catch{return!1}}async function ke(e,t,n,r){if(n!=="persistent"||!e.domain)return!0;if(!De(r,e.domain))return!0;const o=await t.getCurrentUrl?.().catch(()=>null);return!Ne(o,e.domain)}export async function executeCommand(e,t,n=!1,r={}){let o;try{o=r.prepared?t:prepareCommandArgs(e,t)}catch(i){if(i instanceof d)throw i;throw new d(D(i))}const s=qe(e,o);be(s);const u=Ie(r.trace),l={command:g(e),args:o,startedAt:Date.now()};await k("onBeforeExecute",l);let m;try{if(le(e)){const i=Ee(e.site),w=he(r.profile),p=!i&&w.kind==="managed"?w:null;let P;const A=Le(e.defaultWindowMode??"background",r.windowMode);let F,W;if(p){P=await we(p.name,p.entry,{windowMode:A});const c=p.entry.fingerprint;if(c?.enabled)F=ge(c);const a=p.entry.proxy;if(a?.username)W={username:a.username,password:a.password??""}}else if(i){const c=process.env.OPENCLI_CDP_ENDPOINT;if(c){const a=Number(new URL(c).port);if(!await Pe(a))throw new x(`CDP not reachable at ${c}`,"Check that the app is running with --remote-debugging-port and the endpoint is correct.");P=c}else P=await xe(e.site)}const re=p?ye:fe(e.site),L=!p&&w.kind==="extension"?w.contextId:void 0,ne=e,b=_e(e,r.siteSession),C=Oe(e,b),q=We(b,r.keepTab),y=b==="persistent"&&e.access==="write"?{runId:Se(),session:C}:null;if(y)Q({runId:y.runId,command:g(e),access:"write"});let z,H=!1,R=null;try{m=await pe(A!=="foreground",()=>me(re,async(c)=>{const a=u==="off"?null:new Ae({scope:{contextId:L??p?.name,session:C,target:c.getActivePage?.(),site:e.site,command:g(e),adapterSourcePath:Re(ne)}});if(a){a.record({stream:"action",name:"command",phase:"start",data:{args:o}});await c.startNetworkCapture?.().catch(()=>!1)}const h=Me(e);if(h&&await ke(e,c,b,h)){a?.record({stream:"action",name:"pre_navigate",phase:"start",data:{url:h}});try{await c.goto(h);a?.record({stream:"action",name:"pre_navigate",phase:"end",data:{url:h}})}catch(f){if(f instanceof ce)throw f;a?.record({stream:"action",name:"pre_navigate",phase:"error",data:{url:h,error:f instanceof Error?f.message:String(f)}});const v=new x(`Pre-navigation to ${h} failed: ${f instanceof Error?f.message:f}`,"Check that the site is reachable and the browser extension is running.");v.cause=f;if(a&&(u==="on"||u==="retain-on-failure")){a.record({stream:"error",message:v.message,stack:v.stack,code:v.code,hint:v.hint});await _(a,c).catch(()=>{});O(a,"failure",v,r.onTraceExport)}throw v}}const E=s!==null?s+Z:de,T=U(e,c,o,n);R=T;let I=!1;T.then(()=>{I=!0},()=>{I=!0});try{const f=await G(T,{timeout:E,label:g(e),hint:te(e,s)});a?.record({stream:"action",name:"command",phase:"end"});if(a&&u==="on"){await _(a,c).catch(()=>{});O(a,"success",void 0,r.onTraceExport)}if(!q)await c.closeWindow?.().catch(()=>{});return f}catch(f){if(!I)H=!0;if(a){a.record({stream:"action",name:"command",phase:"error",data:{error:f instanceof Error?f.message:String(f)}});a.record({stream:"error",message:f instanceof Error?f.message:String(f),stack:f instanceof Error?f.stack:void 0});if(u==="on"||u==="retain-on-failure"){await _(a,c).catch(()=>{});O(a,"failure",f,r.onTraceExport)}}if(!q)await c.closeWindow?.().catch(()=>{});throw f}},{session:C,cdpEndpoint:P,contextId:L,windowMode:A,surface:"adapter",siteSession:b,initScript:F,proxyAuth:W}),p?.name)}catch(c){z=c;throw c}finally{if(y)if(H&&R){const{runId:c,session:a}=y,h=(E)=>{ve(c);if(!J(E))K({runId:c,session:a,surface:"adapter"})};R.then(()=>h(),(E)=>h(E))}else{Q(null);if(!J(z))await K({runId:y.runId,session:y.session,surface:"adapter"})}}}else if(s!==null){const i=s+Z;m=await G(U(e,null,o,n),{timeout:i,label:g(e),hint:te(e,s)})}else m=await U(e,null,o,n)}catch(i){l.error=i;l.finishedAt=Date.now();await k("onAfterExecute",l);throw i}l.finishedAt=Date.now();await k("onAfterExecute",l,m);return m}async function _(e,t){const n=t.getActivePage?.()??e.scope.target,[r,o,s,u,l]=await Promise.all([t.getCurrentUrl?.().catch(()=>null)??Promise.resolve(null),t.snapshot().catch(()=>{return}),t.readNetworkCapture?.().catch(()=>[])??Promise.resolve([]),t.consoleMessages("all").catch(()=>[]),t.screenshot({format:"png"}).catch(()=>{return})]);if(o!==void 0||r!==void 0)e.record({stream:"state",url:r,target:n,snapshot:o,label:"final"});for(const m of Array.isArray(s)?s:[]){const i=m;e.record({stream:"network",url:String(i.url??""),method:typeof i.method==="string"?i.method:void 0,status:typeof i.responseStatus==="number"?i.responseStatus:void 0,contentType:typeof i.responseContentType==="string"?i.responseContentType:void 0,size:typeof i.responseBodyFullSize==="number"?i.responseBodyFullSize:void 0,requestHeaders:i.requestHeaders,responseHeaders:i.responseHeaders,requestBody:i.requestBodyPreview,responseBody:i.responsePreview,ts:typeof i.timestamp==="number"?i.timestamp:void 0})}for(const m of Array.isArray(u)?u:[])if(m&&typeof m==="object"){const i=m;e.record({stream:"console",level:String(i.type??i.level??"log"),text:String(i.text??i.message??""),ts:typeof i.timestamp==="number"?i.timestamp:void 0})}else e.record({stream:"console",level:"log",text:String(m)});if(typeof l==="string"&&l)e.record({stream:"screenshot",format:"png",data:l,label:"final"})}function O(e,t,n,r){try{const o=Ce(e,{error:n,status:t});if(t==="failure"&&n!==void 0)ue(n,o.receipt);else process.stderr.write(`ppcli trace artifact: ${o.dir}
2
- `);try{r?.(o)}catch(s){X.warn(`[trace] Trace export callback failed: ${s instanceof Error?s.message:String(s)}`)}return o}catch(o){X.warn(`[trace] Failed to export trace artifact: ${o instanceof Error?o.message:String(o)}`);return}}export function resolveFileBackedArg(e,t){const n=e.args.find((l)=>l.fromFile);if(!n)return;const r=typeof n.fromFile==="string"?n.fromFile:"file",o=n.positional?`<${n.name}>`:`--${n.name}`,s=t[r]==null?"":String(t[r]).trim(),u=t[n.name]==null?"":String(t[n.name]);if(s){if(u.trim())throw new d(`${o} 和 --${r} 只能二选一,不能同时给`);const l=N.resolve(s.startsWith("~/")?N.join(j.homedir(),s.slice(2)):s);let m;try{m=M.readFileSync(l,"utf-8")}catch(w){throw new d(`--${r} 指向的文件读不到: ${l}(${D(w)})`)}const i=m.trim();if(!i)throw new d(`--${r} 指向的文件内容为空: ${l}`);t[n.name]=i;$e(N.dirname(l));return}if(!u.trim()&&n.requiredUnlessFile)throw new d(`缺少 ${n.name}:${o} 或 --${r} 给其一`)}export function prepareCommandArgs(e,t){const n=coerceAndValidateArgs(e.args,t);resolveFileBackedArg(e,n);e.validateArgs?.(n);return n}const Z=30;function Be(e){if(e===void 0||e===null||e==="")return null;if(e==="ephemeral"||e==="persistent")return e;throw new d(`--site-session must be one of: ephemeral, persistent. Received: "${String(e)}"`)}function Ue(){const e=process.env.PUBLISHPORT_SITE_SESSION;if(e==="persistent"||e==="ephemeral")return e;return null}function _e(e,t){return Be(t)??Ue()??e.siteSession??"persistent"}function Oe(e,t){if(t==="persistent")return`site:${e.site}`;return`site:${e.site}:${se.randomUUID()}`}function Fe(e,t){if(t===void 0||t==="")return null;if(t==="true")return!0;if(t==="false")return!1;throw new d(`${e} must be one of: true, false. Received: "${String(t)}"`)}function We(e,t){if(e==="persistent")return!0;return Fe("--keep-tab",t)??!1}function ee(e,t){if(t===void 0||t==="")return null;if(t==="foreground"||t==="background")return t;throw new d(`${e} must be one of: foreground, background. Received: "${String(t)}"`)}function Le(e="background",t){return ee("--window",t)??ee("OPENCLI_WINDOW",process.env.OPENCLI_WINDOW)??e}function te(e,t){const n=e.access==="write"?"⚠️ 这是写操作,超时不代表没写成功——请求可能已经到达平台。禁止直接重试:"+"先用该平台的列表/统计命令(stats / lists / articles 等)按标题回查,确认没写进去再重发。 ":"";if(t!==null)return`${n}Pass a higher --timeout value (currently ${t}s)`;if(e.args.some((r)=>r.name==="timeout"))return`${n}Try again, or pass a higher --timeout <seconds>`;return n+"This command has no --timeout option. Retry once; if it keeps timing out, "+"the page or browser bridge is likely stuck — have the user reopen the PublishPort client. "+"The global default can be raised with OPENCLI_BROWSER_COMMAND_TIMEOUT."}function qe(e,t){if(!e.args.some((o)=>o.name==="timeout"))return null;const n=t.timeout;if(n===void 0||n===null||n==="")throw new d(`Argument "timeout" must be a positive integer. Received: "${String(n)}"`);const r=Number(n);if(!Number.isInteger(r)||r<=0)throw new d(`Argument "timeout" must be a positive integer. Received: "${String(n)}"`);return r}
1
+ import{getRegistry as oe,fullName as g}from"./registry.js";import{pathToFileURL as ie}from"node:url";import*as se from"node:crypto";import*as M from"node:fs";import*as j from"node:os";import*as N from"node:path";import{executePipeline as K}from"./pipeline/index.js";import{adapterLoadError as ae,ArgumentError as d,CommandExecutionError as A,SessionBusyError as ce,attachTraceReceipt as ue,getErrorMessage as D}from"./errors.js";import{shouldUseBrowserSession as le}from"./capabilityRouting.js";import{getBrowserFactory as fe,browserSession as me,runWithTimeout as V,DEFAULT_BROWSER_COMMAND_TIMEOUT as de}from"./runtime.js";import{withBrowserSessionLockIf as pe}from"./browser-session-lock.js";import{resolveProfile as he}from"./browser/profile.js";import{ensureManagedChrome as we}from"./browser/managed-chrome.js";import{generateFingerprintJs as ge}from"./browser/fingerprint.js";import{CDPBridge as ye}from"./browser/index.js";import{clearDaemonRunContext as ve,generateRunId as Se,isUnknownOutcomeError as G,releaseSiteSessionLease as J,setDaemonCommandTimeoutSeconds as be,setDaemonRunContext as Q}from"./browser/daemon-client.js";import{emitHook as _}from"./hooks.js";import{setContentBaseDir as $e}from"./utils.js";import{log as X}from"./logger.js";import{isElectronApp as Ee}from"./electron-apps.js";import{probeCDP as Pe,resolveElectronEndpoint as Ae}from"./launcher.js";import{ObservationSession as Ce,exportObservationSession as xe}from"./observation/index.js";import{resolveAdapterSourcePath as Ie}from"./adapter-source.js";const S=new Map,k=new Map,Re=`${j.homedir()}/.ppcli/clis/`;function Te(e){if(e===void 0||e===null||e===""||e==="off")return"off";if(e==="on"||e==="retain-on-failure")return e;throw new d(`--trace must be one of: off, on, retain-on-failure. Received: "${String(e)}"`)}export function coerceAndValidateArgs(e,r){const t={...r};for(const n of e){const o=t[n.name];if(n.required&&(o===void 0||o===null||o===""))throw new d(`Argument "${n.name}" is required.`,n.help??`Provide a value for --${n.name}`);if(o!==void 0&&o!==null){if(n.type==="int"||n.type==="number"){const u=Number(o);if(!Number.isFinite(u))throw new d(`Argument "${n.name}" must be a valid number. Received: "${o}"`);if(n.type==="int"&&!Number.isInteger(u))throw new d(`Argument "${n.name}" must be a valid integer. Received: "${o}"`);t[n.name]=u}else if(n.type==="boolean"||n.type==="bool")if(typeof o==="string"){const u=o.toLowerCase();if(u==="true"||u==="1")t[n.name]=!0;else if(u==="false"||u==="0")t[n.name]=!1;else throw new d(`Argument "${n.name}" must be a boolean (true/false). Received: "${o}"`)}else t[n.name]=Boolean(o);const s=t[n.name];if(n.choices&&n.choices.length>0){if(!n.choices.map(String).includes(String(s)))throw new d(`Argument "${n.name}" must be one of: ${n.choices.join(", ")}. Received: "${s}"`)}}else if(n.default!==void 0)t[n.name]=n.default}return t}async function B(e,r,t,n){const o=e;if(o._lazy&&o._modulePath){const s=o._modulePath;if(s.startsWith(Re)&&S.has(s))try{const m=M.statSync(s),i=k.get(s);if(i!==void 0&&m.mtimeMs!==i)S.delete(s)}catch{}if(!S.has(s)){const m=ie(s).href,w=import(k.has(s)?`${m}?t=${Date.now()}`:m).then(()=>{try{k.set(s,M.statSync(s).mtimeMs)}catch{}},(p)=>{S.delete(s);throw ae(`Failed to load adapter module ${s}: ${D(p)}`,"Check that the adapter file exists and has no syntax errors.")});S.set(s,w)}await S.get(s);const l=oe().get(g(e));if(l?.func)return Y(l,r,t,n);if(l?.pipeline)return K(r,l.pipeline,{args:t,debug:n})}if(e.func)return Y(e,r,t,n);if(e.pipeline)return K(r,e.pipeline,{args:t,debug:n});throw new A(`Command ${g(e)} has no func or pipeline`,"This is likely a bug in the adapter definition. Please report this issue.")}function Y(e,r,t,n){if(e.browser===!1)return e.func(t,n);if(!r)throw new A(`Command ${g(e)} requires a browser session but none was provided`);return e.func(r,t,n)}function Me(e){if(e.navigateBefore===!1)return null;if(typeof e.navigateBefore==="string")return e.navigateBefore;return null}function Ne(e,r){if(!e||!r)return!1;try{const t=new URL(e).hostname;return t===r||t.endsWith(`.${r}`)}catch{return!1}}function De(e,r){if(!r)return!1;try{const t=new URL(e),n=t.hostname===r||t.hostname.endsWith(`.${r}`),o=t.pathname===""||t.pathname==="/";return n&&o&&t.search===""&&t.hash===""}catch{return!1}}async function _e(e,r,t,n){if(t!=="persistent"||!e.domain)return!0;if(!De(n,e.domain))return!0;const o=await r.getCurrentUrl?.().catch(()=>null);return!Ne(o,e.domain)}export async function executeCommand(e,r,t=!1,n={}){let o;try{o=n.prepared?r:prepareCommandArgs(e,r)}catch(i){if(i instanceof d)throw i;throw new d(D(i))}const s=qe(e,o);be(s);const u=Te(n.trace),l={command:g(e),args:o,startedAt:Date.now()};await _("onBeforeExecute",l);let m;try{if(le(e)){const i=Ee(e.site),w=he(n.profile),p=!i&&w.kind==="managed"?w:null;process.env.PPCLI_ACTIVE_PROFILE=p?p.name:"";let P;const C=We(e.defaultWindowMode??"background",n.windowMode);let F,L;if(p){P=await we(p.name,p.entry,{windowMode:C});const c=p.entry.fingerprint;if(c?.enabled)F=ge(c);const a=p.entry.proxy;if(a?.username)L={username:a.username,password:a.password??""}}else if(i){const c=process.env.OPENCLI_CDP_ENDPOINT;if(c){const a=Number(new URL(c).port);if(!await Pe(a))throw new A(`CDP not reachable at ${c}`,"Check that the app is running with --remote-debugging-port and the endpoint is correct.");P=c}else P=await Ae(e.site)}const re=p?ye:fe(e.site),W=!p&&w.kind==="extension"?w.contextId:void 0,ne=e,b=Ue(e,n.siteSession),x=Oe(e,b),q=Le(b,n.keepTab),y=b==="persistent"&&e.access==="write"?{runId:Se(),session:x}:null;if(y)Q({runId:y.runId,command:g(e),access:"write"});let z,H=!1,I=null;try{m=await pe(C!=="foreground",()=>me(re,async(c)=>{const a=u==="off"?null:new Ce({scope:{contextId:W??p?.name,session:x,target:c.getActivePage?.(),site:e.site,command:g(e),adapterSourcePath:Ie(ne)}});if(a){a.record({stream:"action",name:"command",phase:"start",data:{args:o}});await c.startNetworkCapture?.().catch(()=>!1)}const h=Me(e);if(h&&await _e(e,c,b,h)){a?.record({stream:"action",name:"pre_navigate",phase:"start",data:{url:h}});try{await c.goto(h);a?.record({stream:"action",name:"pre_navigate",phase:"end",data:{url:h}})}catch(f){if(f instanceof ce)throw f;a?.record({stream:"action",name:"pre_navigate",phase:"error",data:{url:h,error:f instanceof Error?f.message:String(f)}});const v=new A(`Pre-navigation to ${h} failed: ${f instanceof Error?f.message:f}`,"Check that the site is reachable and the browser extension is running.");v.cause=f;if(a&&(u==="on"||u==="retain-on-failure")){a.record({stream:"error",message:v.message,stack:v.stack,code:v.code,hint:v.hint});await U(a,c).catch(()=>{});O(a,"failure",v,n.onTraceExport)}throw v}}const E=s!==null?s+Z:de,R=B(e,c,o,t);I=R;let T=!1;R.then(()=>{T=!0},()=>{T=!0});try{const f=await V(R,{timeout:E,label:g(e),hint:te(e,s)});a?.record({stream:"action",name:"command",phase:"end"});if(a&&u==="on"){await U(a,c).catch(()=>{});O(a,"success",void 0,n.onTraceExport)}if(!q)await c.closeWindow?.().catch(()=>{});return f}catch(f){if(!T)H=!0;if(a){a.record({stream:"action",name:"command",phase:"error",data:{error:f instanceof Error?f.message:String(f)}});a.record({stream:"error",message:f instanceof Error?f.message:String(f),stack:f instanceof Error?f.stack:void 0});if(u==="on"||u==="retain-on-failure"){await U(a,c).catch(()=>{});O(a,"failure",f,n.onTraceExport)}}if(!q)await c.closeWindow?.().catch(()=>{});throw f}},{session:x,cdpEndpoint:P,contextId:W,windowMode:C,surface:"adapter",siteSession:b,initScript:F,proxyAuth:L}),p?.name)}catch(c){z=c;throw c}finally{if(y)if(H&&I){const{runId:c,session:a}=y,h=(E)=>{ve(c);if(!G(E))J({runId:c,session:a,surface:"adapter"})};I.then(()=>h(),(E)=>h(E))}else{Q(null);if(!G(z))await J({runId:y.runId,session:y.session,surface:"adapter"})}}}else if(s!==null){const i=s+Z;m=await V(B(e,null,o,t),{timeout:i,label:g(e),hint:te(e,s)})}else m=await B(e,null,o,t)}catch(i){l.error=i;l.finishedAt=Date.now();await _("onAfterExecute",l);throw i}l.finishedAt=Date.now();await _("onAfterExecute",l,m);return m}async function U(e,r){const t=r.getActivePage?.()??e.scope.target,[n,o,s,u,l]=await Promise.all([r.getCurrentUrl?.().catch(()=>null)??Promise.resolve(null),r.snapshot().catch(()=>{return}),r.readNetworkCapture?.().catch(()=>[])??Promise.resolve([]),r.consoleMessages("all").catch(()=>[]),r.screenshot({format:"png"}).catch(()=>{return})]);if(o!==void 0||n!==void 0)e.record({stream:"state",url:n,target:t,snapshot:o,label:"final"});for(const m of Array.isArray(s)?s:[]){const i=m;e.record({stream:"network",url:String(i.url??""),method:typeof i.method==="string"?i.method:void 0,status:typeof i.responseStatus==="number"?i.responseStatus:void 0,contentType:typeof i.responseContentType==="string"?i.responseContentType:void 0,size:typeof i.responseBodyFullSize==="number"?i.responseBodyFullSize:void 0,requestHeaders:i.requestHeaders,responseHeaders:i.responseHeaders,requestBody:i.requestBodyPreview,responseBody:i.responsePreview,ts:typeof i.timestamp==="number"?i.timestamp:void 0})}for(const m of Array.isArray(u)?u:[])if(m&&typeof m==="object"){const i=m;e.record({stream:"console",level:String(i.type??i.level??"log"),text:String(i.text??i.message??""),ts:typeof i.timestamp==="number"?i.timestamp:void 0})}else e.record({stream:"console",level:"log",text:String(m)});if(typeof l==="string"&&l)e.record({stream:"screenshot",format:"png",data:l,label:"final"})}function O(e,r,t,n){try{const o=xe(e,{error:t,status:r});if(r==="failure"&&t!==void 0)ue(t,o.receipt);else process.stderr.write(`ppcli trace artifact: ${o.dir}
2
+ `);try{n?.(o)}catch(s){X.warn(`[trace] Trace export callback failed: ${s instanceof Error?s.message:String(s)}`)}return o}catch(o){X.warn(`[trace] Failed to export trace artifact: ${o instanceof Error?o.message:String(o)}`);return}}export function resolveFileBackedArg(e,r){const t=e.args.find((l)=>l.fromFile);if(!t)return;const n=typeof t.fromFile==="string"?t.fromFile:"file",o=t.positional?`<${t.name}>`:`--${t.name}`,s=r[n]==null?"":String(r[n]).trim(),u=r[t.name]==null?"":String(r[t.name]);if(s){if(u.trim())throw new d(`${o} 和 --${n} 只能二选一,不能同时给`);const l=N.resolve(s.startsWith("~/")?N.join(j.homedir(),s.slice(2)):s);let m;try{m=M.readFileSync(l,"utf-8")}catch(w){throw new d(`--${n} 指向的文件读不到: ${l}(${D(w)})`)}const i=m.trim();if(!i)throw new d(`--${n} 指向的文件内容为空: ${l}`);r[t.name]=i;$e(N.dirname(l));return}if(!u.trim()&&t.requiredUnlessFile)throw new d(`缺少 ${t.name}:${o} 或 --${n} 给其一`)}export function prepareCommandArgs(e,r){const t=coerceAndValidateArgs(e.args,r);resolveFileBackedArg(e,t);e.validateArgs?.(t);return t}const Z=30;function ke(e){if(e===void 0||e===null||e==="")return null;if(e==="ephemeral"||e==="persistent")return e;throw new d(`--site-session must be one of: ephemeral, persistent. Received: "${String(e)}"`)}function Be(){const e=process.env.PUBLISHPORT_SITE_SESSION;if(e==="persistent"||e==="ephemeral")return e;return null}function Ue(e,r){return ke(r)??Be()??e.siteSession??"persistent"}function Oe(e,r){const t=typeof e.sessionKey==="string"&&e.sessionKey.trim()?`:${e.sessionKey.trim()}`:"";if(r==="persistent")return`site:${e.site}${t}`;return`site:${e.site}${t}:${se.randomUUID()}`}function Fe(e,r){if(r===void 0||r==="")return null;if(r==="true")return!0;if(r==="false")return!1;throw new d(`${e} must be one of: true, false. Received: "${String(r)}"`)}function Le(e,r){if(e==="persistent")return!0;return Fe("--keep-tab",r)??!1}function ee(e,r){if(r===void 0||r==="")return null;if(r==="foreground"||r==="background")return r;throw new d(`${e} must be one of: foreground, background. Received: "${String(r)}"`)}function We(e="background",r){return ee("--window",r)??ee("OPENCLI_WINDOW",process.env.OPENCLI_WINDOW)??e}function te(e,r){const t=e.access==="write"?"⚠️ 这是写操作,超时不代表没写成功——请求可能已经到达平台。禁止直接重试:"+"先用该平台的列表/统计命令(stats / lists / articles 等)按标题回查,确认没写进去再重发。 ":"";if(r!==null)return`${t}Pass a higher --timeout value (currently ${r}s)`;if(e.args.some((n)=>n.name==="timeout"))return`${t}Try again, or pass a higher --timeout <seconds>`;return t+"This command has no --timeout option. Retry once; if it keeps timing out, "+"the page or browser bridge is likely stuck — have the user reopen the PublishPort client. "+"The global default can be raised with OPENCLI_BROWSER_COMMAND_TIMEOUT."}function qe(e,r){if(!e.args.some((o)=>o.name==="timeout"))return null;const t=r.timeout;if(t===void 0||t===null||t==="")throw new d(`Argument "timeout" must be a positive integer. Received: "${String(t)}"`);const n=Number(t);if(!Number.isInteger(n)||n<=0)throw new d(`Argument "timeout" must be a positive integer. Received: "${String(t)}"`);return n}
@@ -1 +1 @@
1
- import*as g from"node:fs";import*as T from"node:path";import{render as w}from"../template.js";import{getErrorMessage as G}from"../../errors.js";import{httpDownload as I,ytdlpDownload as J,saveDocument as K,detectContentType as Q,requiresYtdlp as z,sanitizeFilename as V,generateFilename as W,exportCookiesToNetscape as X,getTempDir as Z,formatCookieHeader as ee}from"../../download/index.js";import{DownloadProgressTracker as te,formatBytes as A}from"../../download/progress.js";import{mapConcurrent as oe}from"../../utils.js";async function se(a,r){try{const e=await a.getCookies({domain:r});return ee(e)}catch{return""}}async function ne(a,r){try{return(await a.getCookies({domain:r})).filter((o)=>o.name).map((o)=>({name:o.name,value:o.value,domain:o.domain,path:o.path??"/",secure:o.secure??!1,httpOnly:o.httpOnly??!1}))}catch{return[]}}function re(a){const r=new Map;for(const e of a)r.set(`${e.domain} ${e.path} ${e.name}`,e);return[...r.values()]}export async function stepDownload(a,r,e,o){const s=typeof r==="object"&&r!==null?r:{},C=typeof r==="string"?r:s.url??"",E=s.dir??"./downloads",_=s.filename??"",L=typeof s.concurrency==="number"?s.concurrency:3,M=s.skip_existing!==!1,O=typeof s.timeout==="number"?s.timeout*1000:30000,D=s.use_ytdlp??!1,U=Array.isArray(s.ytdlp_args)?s.ytdlp_args.map((n)=>String(n)):[],j=s.type??"auto",B=s.progress!==!1,v=s.content,x=s.metadata,F=String(w(E,{args:o,data:e}));g.mkdirSync(F,{recursive:!0});const k=Array.isArray(e)?e:e?[e]:[];if(k.length===0)return[];const S=new te(k.length,B),P=new Map;let h;if(a){if(D||k.some((n,i)=>{const l=String(w(C,{args:o,data:e,item:n,index:i}));return z(l)}))try{const n=[...new Set(k.flatMap((l,f)=>{const u=String(w(C,{args:o,data:e,item:l,index:f}));if(!D&&!z(u))return[];try{return[new URL(u).hostname]}catch{return[]}}))],i=re((await Promise.all(n.map((l)=>ne(a,l)))).flat());if(i.length>0){const l=Z();g.mkdirSync(l,{recursive:!0});h=T.join(l,`cookies_${Date.now()}.txt`);X(i,h)}}catch{}}const R=await oe(k,L,async(n,i)=>{const l=Date.now(),f=String(w(C,{args:o,data:e,item:n,index:i}));if(!f){S.onFileComplete(!1);return{...n,_download:{status:"failed",error:"Empty URL"}}}let u;if(_)u=String(w(_,{args:o,data:e,item:n,index:i}));else u=W(f,i);u=V(u);const y=T.join(F,u);if(M&&g.existsSync(y)){S.onFileComplete(!0,!0);return{...n,_download:{status:"skipped",path:y,size:g.statSync(y).size}}}const c=S.onFileStart(u,i),b=j==="auto"?Q(f):j,Y=D||b==="video"&&z(f);let t;try{if(b==="document"&&v){const p=String(w(v,{args:o,data:e,item:n,index:i})),m=x?Object.fromEntries(Object.entries(x).map(([q,N])=>[q,w(N,{args:o,data:e,item:n,index:i})])):void 0,d=T.extname(u).toLowerCase();t=await K(p,y,d===".json"?"json":d===".html"?"html":"markdown",m);if(c)c.complete(t.success,t.success?A(t.size):void 0)}else if(Y){t=await J(f,y,{cookiesFile:h,extraArgs:U,onProgress:(p)=>{if(c)c.update(p,100)}});if(c)c.complete(t.success,t.success?A(t.size):void 0)}else{let p="";if(a)try{const m=new URL(f).hostname;let d=P.get(m);if(!d){d=se(a,m);P.set(m,d)}p=await d}catch{p=""}t=await I(f,y,{cookies:p,timeout:O,onProgress:(m,d)=>{if(c)c.update(m,d)}});if(c)c.complete(t.success,t.success?A(t.size):void 0)}}catch(p){const m=G(p);t={success:!1,size:0,error:m};if(c)c.fail(m)}S.onFileComplete(t.success);const H=Date.now()-l;return{...n,_download:{status:t.success?"success":"failed",path:t.success?y:void 0,size:t.size,error:t.error,duration:H}}});if(h&&g.existsSync(h))try{g.unlinkSync(h)}catch{}S.finish();return R}
1
+ import*as g from"node:fs";import*as T from"node:path";import{render as w}from"../template.js";import{getErrorMessage as G}from"../../errors.js";import{httpDownload as I,ytdlpDownload as J,saveDocument as K,detectContentType as Q,requiresYtdlp as z,sanitizeFilename as V,generateFilename as W,exportCookiesToNetscape as X,getTempDir as Z,formatCookieHeader as ee}from"../../download/index.js";import{DownloadProgressTracker as te,formatBytes as A}from"../../download/progress.js";import{mapConcurrent as oe}from"../../utils.js";async function se(a,r){try{const e=await a.getCookies({domain:r});return ee(e)}catch{return""}}async function ne(a,r){try{return(await a.getCookies({domain:r})).filter((o)=>o.name).map((o)=>({name:o.name,value:o.value,domain:o.domain,path:o.path??"/",secure:o.secure??!1,httpOnly:o.httpOnly??!1}))}catch{return[]}}function re(a){const r=new Map;for(const e of a)r.set(`${e.domain} ${e.path} ${e.name}`,e);return[...r.values()]}export async function stepDownload(a,r,e,o){const s=typeof r==="object"&&r!==null?r:{},C=typeof r==="string"?r:s.url??"",E=s.dir??"./downloads",_=s.filename??"",L=typeof s.concurrency==="number"?s.concurrency:3,M=s.skip_existing!==!1,O=typeof s.timeout==="number"?s.timeout*1000:30000,D=s.use_ytdlp??!1,U=Array.isArray(s.ytdlp_args)?s.ytdlp_args.map((n)=>String(n)):[],j=s.type??"auto",B=s.progress!==!1,{content:v,metadata:x}=s,F=String(w(E,{args:o,data:e}));g.mkdirSync(F,{recursive:!0});const k=Array.isArray(e)?e:e?[e]:[];if(k.length===0)return[];const S=new te(k.length,B),P=new Map;let h;if(a){if(D||k.some((n,i)=>{const l=String(w(C,{args:o,data:e,item:n,index:i}));return z(l)}))try{const n=[...new Set(k.flatMap((l,f)=>{const u=String(w(C,{args:o,data:e,item:l,index:f}));if(!D&&!z(u))return[];try{return[new URL(u).hostname]}catch{return[]}}))],i=re((await Promise.all(n.map((l)=>ne(a,l)))).flat());if(i.length>0){const l=Z();g.mkdirSync(l,{recursive:!0});h=T.join(l,`cookies_${Date.now()}.txt`);X(i,h)}}catch{}}const R=await oe(k,L,async(n,i)=>{const l=Date.now(),f=String(w(C,{args:o,data:e,item:n,index:i}));if(!f){S.onFileComplete(!1);return{...n,_download:{status:"failed",error:"Empty URL"}}}let u;if(_)u=String(w(_,{args:o,data:e,item:n,index:i}));else u=W(f,i);u=V(u);const y=T.join(F,u);if(M&&g.existsSync(y)){S.onFileComplete(!0,!0);return{...n,_download:{status:"skipped",path:y,size:g.statSync(y).size}}}const c=S.onFileStart(u,i),b=j==="auto"?Q(f):j,Y=D||b==="video"&&z(f);let t;try{if(b==="document"&&v){const p=String(w(v,{args:o,data:e,item:n,index:i})),m=x?Object.fromEntries(Object.entries(x).map(([q,N])=>[q,w(N,{args:o,data:e,item:n,index:i})])):void 0,d=T.extname(u).toLowerCase();t=await K(p,y,d===".json"?"json":d===".html"?"html":"markdown",m);if(c)c.complete(t.success,t.success?A(t.size):void 0)}else if(Y){t=await J(f,y,{cookiesFile:h,extraArgs:U,onProgress:(p)=>{if(c)c.update(p,100)}});if(c)c.complete(t.success,t.success?A(t.size):void 0)}else{let p="";if(a)try{const m=new URL(f).hostname;let d=P.get(m);if(!d){d=se(a,m);P.set(m,d)}p=await d}catch{p=""}t=await I(f,y,{cookies:p,timeout:O,onProgress:(m,d)=>{if(c)c.update(m,d)}});if(c)c.complete(t.success,t.success?A(t.size):void 0)}}catch(p){const m=G(p);t={success:!1,size:0,error:m};if(c)c.fail(m)}S.onFileComplete(t.success);const H=Date.now()-l;return{...n,_download:{status:t.success?"success":"failed",path:t.success?y:void 0,size:t.size,error:t.error,duration:H}}});if(h&&g.existsSync(h))try{g.unlinkSync(h)}catch{}S.finish();return R}
@@ -1 +1 @@
1
- import y from"node:vm";import{isRecord as S}from"../utils.js";import{log as A}from"../logger.js";export function render(r,t){if(typeof r!=="string")return r;const i=r.trim(),e=i.match(/^\$\{\{\s*([^}]*(?:\}[^}][^}]*)*)\s*\}\}$/);if(e&&!i.includes("}}-")&&!i.includes("}}${{"))return evalExpr(e[1].trim(),t);const c=i.match(/^\$\{\{\s*([\s\S]*?)\s*\}\}$/);if(c){const s=c[1];if(!s.includes("${{"))return evalExpr(s.trim(),t)}return r.replace(/\$\{\{\s*(.*?)\s*\}\}/g,(s,n)=>String(evalExpr(n.trim(),t)))}export function evalExpr(r,t){const i=t.args??{},e=t.item??{},c=t.data,s=t.root,n=t.index??0,f=r.split(/(?<!\|)\|(?!\|)/).map((a)=>a.trim());if(f.length>1){let a=evalExpr(f[0],t);for(let p=1;p<f.length;p++)a=C(f[p],a);return a}const d=r.match(/^(['"])(.*)\1$/);if(d)return d[2];if(/^\d+(\.\d+)?$/.test(r))return Number(r);const o=resolvePath(r,{args:i,item:e,data:c,root:s,index:n});if(o!==null&&o!==void 0)return o;return O(r,{args:i,item:e,data:c,root:s,index:n})}function C(r,t){const i=r.match(/^(\w+)(?:\((.+)\))?$/);if(!i)return t;const[,e,c]=i,s=c?.replace(/^['"]|['"]$/g,"")??"";switch(e){case"default":{if(t===null||t===void 0||t===""){const n=parseInt(s,10);if(!Number.isNaN(n)&&String(n)===s.trim())return n;return s}return t}case"join":return Array.isArray(t)?t.join(s||", "):t;case"upper":return typeof t==="string"?t.toUpperCase():t;case"lower":return typeof t==="string"?t.toLowerCase():t;case"trim":return typeof t==="string"?t.trim():t;case"truncate":{const n=parseInt(s,10)||50;return typeof t==="string"&&t.length>n?`${t.slice(0,n)}...`:t}case"replace":{if(typeof t!=="string")return t;if(c==null)return t;const n=c.indexOf(",");if(n===-1)return t;const f=(a)=>a.trim().replace(/^['"]|['"]$/g,""),d=f(c.slice(0,n)),o=f(c.slice(n+1));return t.replaceAll(d,o)}case"keys":return t&&typeof t==="object"?Object.keys(t):t;case"length":return Array.isArray(t)?t.length:typeof t==="string"?t.length:t;case"first":return Array.isArray(t)?t[0]:t;case"last":return Array.isArray(t)?t[t.length-1]:t;case"json":return JSON.stringify(t??null);case"slugify":return typeof t==="string"?t.toLowerCase().replace(/[^\p{L}\p{N}]+/gu,"-").replace(/^-|-$/g,""):t;case"sanitize":return typeof t==="string"?t.replace(/[<>:"/\\|?*\x00-\x1f]/g,"_"):t;case"ext":{if(typeof t!=="string")return t;const n=t.lastIndexOf("."),f=Math.max(t.lastIndexOf("/"),t.lastIndexOf("\\"));return n>f?t.slice(n):""}case"basename":{if(typeof t!=="string")return t;const n=t.split(/[/\\]/);return n[n.length-1]||t}case"urlencode":return typeof t==="string"?encodeURIComponent(t):t;case"urldecode":return typeof t==="string"?decodeURIComponent(t):t;default:return t}}export function resolvePath(r,t){const i=t.args??{},e=t.item??{},c=t.data,s=t.root,n=t.index??0,f=r.split("."),d=f[0];let o,a;if(d==="args"){o=i;a=f.slice(1)}else if(d==="item"){o=e;a=f.slice(1)}else if(d==="data"){o=c;a=f.slice(1)}else if(d==="root"){o=s;a=f.slice(1)}else if(d==="index")return n;else{o=e;a=f}for(const p of a)if(S(o))o=o[p];else if(Array.isArray(o)&&/^\d+$/.test(p))o=o[parseInt(p,10)];else return null;return o}const _=/\b(constructor|__proto__|prototype|globalThis|process|require|import|eval)\b/,h=new WeakMap;function u(r){if(r===null||r===void 0)return r;if(typeof r!=="object"&&typeof r!=="function")return r;const t=r,i=h.get(t);if(i!==void 0)return JSON.parse(i);try{const e=JSON.stringify(r,(c,s)=>typeof s==="bigint"?s.toString():s);h.set(t,e);return JSON.parse(e)}catch(e){A.warn(`[pipeline/template] sanitizeContext failed: ${e instanceof Error?e.message:String(e)}. Returning {} for this branch. Likely cause: circular reference, Symbol, or other non-serializable value in pipeline context.`);return{}}}const b=256,g=new Map;function I(r){let t=g.get(r);if(t)return t;if(g.size>=b){const i=g.keys().next().value;if(i!==void 0)g.delete(i)}t=new y.Script(`(${r})`);g.set(r,t);return t}let m=null,l=null;function N(){if(m&&l)return{sandbox:m,context:l};m={args:{},item:{},data:null,root:null,index:0,encodeURIComponent,decodeURIComponent,JSON,Math,Number,String,Boolean,Array,Date};l=y.createContext(m,{codeGeneration:{strings:!1,wasm:!1}});return{sandbox:m,context:l}}const x=new Set(["args","item","data","root","index","encodeURIComponent","decodeURIComponent","JSON","Math","Number","String","Boolean","Array","Date"]);function O(r,t){if(r.length>2000)return;if(_.test(r))return;try{const i=I(r),{sandbox:e,context:c}=N();for(const s of Object.keys(e))if(!x.has(s))delete e[s];e.args=u(t.args??{});e.item=u(t.item??{});e.data=u(t.data);e.root=u(t.root);e.index=t.index??0;return i.runInContext(c,{timeout:50})}catch{return}}export function normalizeEvaluateSource(r){const t=r.trim();if(!t)return"() => undefined";if(t.startsWith("(")&&t.endsWith(")()"))return`() => (${t})`;if(/^(async\s+)?\([^)]*\)\s*=>/.test(t))return t;if(/^(async\s+)?[A-Za-z_][A-Za-z0-9_]*\s*=>/.test(t))return t;if(t.startsWith("function ")||t.startsWith("async function "))return t;return`() => (${t})`}
1
+ import y from"node:vm";import{isRecord as S}from"../utils.js";import{log as A}from"../logger.js";export function render(r,t){if(typeof r!=="string")return r;const i=r.trim(),e=i.match(/^\$\{\{\s*([^}]*(?:\}[^}][^}]*)*)\s*\}\}$/);if(e&&!i.includes("}}-")&&!i.includes("}}${{"))return evalExpr(e[1].trim(),t);const c=i.match(/^\$\{\{\s*([\s\S]*?)\s*\}\}$/);if(c){const s=c[1];if(!s.includes("${{"))return evalExpr(s.trim(),t)}return r.replace(/\$\{\{\s*(.*?)\s*\}\}/g,(s,n)=>String(evalExpr(n.trim(),t)))}export function evalExpr(r,t){const i=t.args??{},e=t.item??{},{data:c,root:s}=t,n=t.index??0,f=r.split(/(?<!\|)\|(?!\|)/).map((a)=>a.trim());if(f.length>1){let a=evalExpr(f[0],t);for(let p=1;p<f.length;p++)a=C(f[p],a);return a}const d=r.match(/^(['"])(.*)\1$/);if(d)return d[2];if(/^\d+(\.\d+)?$/.test(r))return Number(r);const o=resolvePath(r,{args:i,item:e,data:c,root:s,index:n});if(o!==null&&o!==void 0)return o;return O(r,{args:i,item:e,data:c,root:s,index:n})}function C(r,t){const i=r.match(/^(\w+)(?:\((.+)\))?$/);if(!i)return t;const[,e,c]=i,s=c?.replace(/^['"]|['"]$/g,"")??"";switch(e){case"default":{if(t===null||t===void 0||t===""){const n=parseInt(s,10);if(!Number.isNaN(n)&&String(n)===s.trim())return n;return s}return t}case"join":return Array.isArray(t)?t.join(s||", "):t;case"upper":return typeof t==="string"?t.toUpperCase():t;case"lower":return typeof t==="string"?t.toLowerCase():t;case"trim":return typeof t==="string"?t.trim():t;case"truncate":{const n=parseInt(s,10)||50;return typeof t==="string"&&t.length>n?`${t.slice(0,n)}...`:t}case"replace":{if(typeof t!=="string")return t;if(c==null)return t;const n=c.indexOf(",");if(n===-1)return t;const f=(a)=>a.trim().replace(/^['"]|['"]$/g,""),d=f(c.slice(0,n)),o=f(c.slice(n+1));return t.replaceAll(d,o)}case"keys":return t&&typeof t==="object"?Object.keys(t):t;case"length":return Array.isArray(t)?t.length:typeof t==="string"?t.length:t;case"first":return Array.isArray(t)?t[0]:t;case"last":return Array.isArray(t)?t[t.length-1]:t;case"json":return JSON.stringify(t??null);case"slugify":return typeof t==="string"?t.toLowerCase().replace(/[^\p{L}\p{N}]+/gu,"-").replace(/^-|-$/g,""):t;case"sanitize":return typeof t==="string"?t.replace(/[<>:"/\\|?*\x00-\x1f]/g,"_"):t;case"ext":{if(typeof t!=="string")return t;const n=t.lastIndexOf("."),f=Math.max(t.lastIndexOf("/"),t.lastIndexOf("\\"));return n>f?t.slice(n):""}case"basename":{if(typeof t!=="string")return t;const n=t.split(/[/\\]/);return n[n.length-1]||t}case"urlencode":return typeof t==="string"?encodeURIComponent(t):t;case"urldecode":return typeof t==="string"?decodeURIComponent(t):t;default:return t}}export function resolvePath(r,t){const i=t.args??{},e=t.item??{},{data:c,root:s}=t,n=t.index??0,f=r.split("."),d=f[0];let o,a;if(d==="args"){o=i;a=f.slice(1)}else if(d==="item"){o=e;a=f.slice(1)}else if(d==="data"){o=c;a=f.slice(1)}else if(d==="root"){o=s;a=f.slice(1)}else if(d==="index")return n;else{o=e;a=f}for(const p of a)if(S(o))o=o[p];else if(Array.isArray(o)&&/^\d+$/.test(p))o=o[parseInt(p,10)];else return null;return o}const _=/\b(constructor|__proto__|prototype|globalThis|process|require|import|eval)\b/,h=new WeakMap;function u(r){if(r===null||r===void 0)return r;if(typeof r!=="object"&&typeof r!=="function")return r;const t=r,i=h.get(t);if(i!==void 0)return JSON.parse(i);try{const e=JSON.stringify(r,(c,s)=>typeof s==="bigint"?s.toString():s);h.set(t,e);return JSON.parse(e)}catch(e){A.warn(`[pipeline/template] sanitizeContext failed: ${e instanceof Error?e.message:String(e)}. Returning {} for this branch. Likely cause: circular reference, Symbol, or other non-serializable value in pipeline context.`);return{}}}const b=256,g=new Map;function I(r){let t=g.get(r);if(t)return t;if(g.size>=b){const i=g.keys().next().value;if(i!==void 0)g.delete(i)}t=new y.Script(`(${r})`);g.set(r,t);return t}let m=null,l=null;function N(){if(m&&l)return{sandbox:m,context:l};m={args:{},item:{},data:null,root:null,index:0,encodeURIComponent,decodeURIComponent,JSON,Math,Number,String,Boolean,Array,Date};l=y.createContext(m,{codeGeneration:{strings:!1,wasm:!1}});return{sandbox:m,context:l}}const x=new Set(["args","item","data","root","index","encodeURIComponent","decodeURIComponent","JSON","Math","Number","String","Boolean","Array","Date"]);function O(r,t){if(r.length>2000)return;if(_.test(r))return;try{const i=I(r),{sandbox:e,context:c}=N();for(const s of Object.keys(e))if(!x.has(s))delete e[s];e.args=u(t.args??{});e.item=u(t.item??{});e.data=u(t.data);e.root=u(t.root);e.index=t.index??0;return i.runInContext(c,{timeout:50})}catch{return}}export function normalizeEvaluateSource(r){const t=r.trim();if(!t)return"() => undefined";if(t.startsWith("(")&&t.endsWith(")()"))return`() => (${t})`;if(/^(async\s+)?\([^)]*\)\s*=>/.test(t))return t;if(/^(async\s+)?[A-Za-z_][A-Za-z0-9_]*\s*=>/.test(t))return t;if(t.startsWith("function ")||t.startsWith("async function "))return t;return`() => (${t})`}
@@ -16,5 +16,5 @@ Supported formats:
16
16
  - ${o.errors.join(`
17
17
  - `)}`);V(r);L(e,n,{source:i?.source??{kind:"local",path:r},commitHash:getCommitHash(r)??"local",installedAt:i?.installedAt??new Date().toISOString(),updatedAt:new Date().toISOString()});writeLockFile(e)}function dn(n,t,e,i,r){const o=getMonoreposDir(),c=l.join(o,e),s=a.existsSync(c),u=s?c:n,p=s?P(c):i;if(!p||!D(p))throw new h(`Monorepo manifest missing or invalid at ${u}`);let d=tn(p);if(r){d=d.filter((f)=>f.name===r);if(d.length===0){if(p.plugins?.[r])throw new h(`Sub-plugin "${r}" is disabled in the manifest.`);throw new h(`Sub-plugin "${r}" not found in monorepo. Available: ${Object.keys(p.plugins??{}).join(", ")}`)}}const m=[],b=readLockFile(),g=[];a.mkdirSync(y,{recursive:!0});for(const{name:f,entry:k}of d){const _=x(k);if(_&&!j(_)){S.warn(`Skipping "${f}": requires ppcli ${_}`);continue}let I;try{I=E(u,k.path)}catch{S.warn(`Skipping "${f}": path "${k.path}" escapes repo root.`);continue}if(!a.existsSync(I)){S.warn(`Skipping "${f}": path "${k.path}" not found in repo.`);continue}const z=validatePluginStructure(I);if(!z.valid){S.warn(`Skipping "${f}": invalid structure — ${z.errors.join(", ")}`);continue}const q=l.join(y,f);if(a.existsSync(q)){S.warn(`Skipping "${f}": already installed at ${q}`);continue}g.push({name:f,entry:k})}if(g.length===0)return m;const N=g.map(({name:f,entry:k})=>({name:f,subPath:k.path}));if(s)postInstallMonorepoLifecycle(c,g.map((f)=>E(c,f.entry.path)));else postInstallMonorepoLifecycle(n,g.map((f)=>E(n,f.entry.path)));Y(c,y,N,s?void 0:{stagingDir:n,parentDir:o},(f)=>{for(const{name:k,entry:_}of g){if(f)L(b,k,{source:{kind:"monorepo",url:t,repoName:e,subPath:_.path},commitHash:f});m.push(k)}writeLockFile(b)});return m}function fn(n,t,e,i,r){const o=[];for(const[c,s]of Object.entries(t)){if(s.source.kind!=="monorepo"||s.source.repoName!==n)continue;const u=e.plugins?.[c];if(!u||u.disabled)throw Error(`Installed sub-plugin "${c}" no longer exists in ${i}`);const p=x(u);if(p&&!j(p))throw Error(`Sub-plugin "${c}" requires ppcli ${p}`);const d=E(r,u.path),m=validatePluginStructure(d);if(!m.valid)throw Error(`Updated sub-plugin "${c}" is invalid:
18
18
  - ${m.errors.join(`
19
- - `)}`);o.push({name:c,lockEntry:s,manifestEntry:u})}return o}function mn(n,t,e,i,r){for(const o of t){if(!r)continue;L(n,o.name,{...o.lockEntry,source:{kind:"monorepo",url:e,repoName:i,subPath:o.manifestEntry.path},commitHash:r,updatedAt:new Date().toISOString()})}}function gn(n,t,e,i,r){if(!r)return;L(n,t,{source:{kind:"git",url:e},commitHash:r,installedAt:i?.installedAt??new Date().toISOString(),updatedAt:new Date().toISOString()})}export function uninstallPlugin(n){const t=l.join(y,n);if(!W(t))throw Error(`Plugin "${n}" is not installed.`);const e=readLockFile(),i=e[n];if(isSymlinkSync(t))a.unlinkSync(t);else a.rmSync(t,{recursive:!0,force:!0});if(i?.source.kind==="monorepo"){delete e[n];const o=i.source.repoName;if(!Object.values(e).some((s)=>s.source.kind==="monorepo"&&s.source.repoName===o)){const s=l.join(getMonoreposDir(),o);try{a.rmSync(s,{recursive:!0,force:!0})}catch{}}}else if(e[n])delete e[n];writeLockFile(e)}function isSymlinkSync(n){try{return a.lstatSync(n).isSymbolicLink()}catch{return!1}}export function updatePlugin(n){const t=l.join(y,n);if(!a.existsSync(t))throw Error(`Plugin "${n}" is not installed.`);const e=readLockFile(),i=e[n],r=resolvePluginSource(i,t);if(r?.kind==="local"){pn(n,t,e,i);return}if(r?.kind==="monorepo"){const c=l.join(getMonoreposDir(),r.repoName),s=r.repoName,u=r.url;J(u,(p)=>{const d=P(p);if(!d||!D(d))throw Error(`Updated source is no longer a monorepo: ${u}`);const m=x(d);if(m&&!j(m))throw Error(`Plugin requires ppcli ${m}, but current version is incompatible.`);const b=fn(s,e,d,u,p);if(b.length>0)postInstallMonorepoLifecycle(p,b.map((g)=>E(p,g.manifestEntry.path)));Y(c,y,b.map((g)=>({name:g.name,subPath:g.manifestEntry.path})),{stagingDir:p,parentDir:l.dirname(c)},(g)=>{mn(e,b,u,s,g);writeLockFile(e)})});return}const o=cn(i,t);J(o,(c)=>{const s=P(c);if(s&&D(s))throw Error(`Updated source is now a monorepo: ${o}`);const u=x(s);if(u&&!j(u))throw Error(`Plugin requires ppcli ${u}, but current version is incompatible.`);K(c);Q(c,t,(p)=>{gn(e,n,o,e[n],p);if(p)writeLockFile(e)})})}export function updateAllPlugins(){return listPlugins().map((n)=>{try{updatePlugin(n.name);return{name:n.name,success:!0}}catch(t){return{name:n.name,success:!1,error:v(t)}}})}export function listPlugins(){if(!a.existsSync(y))return[];const n=a.readdirSync(y,{withFileTypes:!0}),t=readLockFile(),e=[];for(const i of n){const r=l.join(y,i.name);if(!(i.isDirectory()||isSymlinkSync(r)))continue;const c=hn(r),s=t[i.name],u=P(r);let p=u?.description,d=u?.version;if(s?.source.kind==="monorepo"&&!p){const b=l.join(getMonoreposDir(),s.source.repoName),N=P(b)?.plugins?.[i.name];if(N){p=p??N.description;d=d??N.version}}const m=resolveStoredPluginSource(s,r);e.push({name:i.name,path:r,commands:c,source:m,version:d??s?.commitHash?.slice(0,7),installedAt:s?.installedAt,monorepoName:s?.source.kind==="monorepo"?s.source.repoName:void 0,description:p})}return e}function hn(n){try{const t=a.readdirSync(n);return[...new Set(t.filter((i)=>i.endsWith(".ts")&&!i.endsWith(".d.ts")&&!i.endsWith(".test.ts")||i.endsWith(".js")&&!i.endsWith(".d.js")).map((i)=>l.basename(i,l.extname(i))))]}catch{return[]}}function yn(n){try{return U("git",["config","--get","remote.origin.url"],{cwd:n,encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).trim()}catch{return}}function parseSource(n){if(n.startsWith("file://"))try{const s=l.resolve(R(n));return{type:"local",localPath:s,name:l.basename(s).replace(/^opencli-plugin-/,"")}}catch{return null}if(l.isAbsolute(n)){const s=l.resolve(n);return{type:"local",localPath:s,name:l.basename(s).replace(/^opencli-plugin-/,"")}}const t=n.match(/^github:([\w.-]+)\/([\w.-]+)\/([\w.-]+)$/);if(t){const[,s,u,p]=t,d=u.replace(/^opencli-plugin-/,"");return{type:"git",cloneUrl:`https://github.com/${s}/${u}.git`,name:d,subPlugin:p}}const e=n.match(/^github:([\w.-]+)\/([\w.-]+)$/);if(e){const[,s,u]=e,p=u.replace(/^opencli-plugin-/,"");return{type:"git",cloneUrl:`https://github.com/${s}/${u}.git`,name:p}}const i=n.match(/^https?:\/\/github\.com\/([\w.-]+)\/([\w.-]+?)(?:\.git)?$/);if(i){const[,s,u]=i,p=u.replace(/^opencli-plugin-/,"");return{type:"git",cloneUrl:`https://github.com/${s}/${u}.git`,name:p}}const r=n.match(/^ssh:\/\/[^/]+\/(.*?)(?:\.git)?$/);if(r){const d=r[1].split("/").pop().replace(/^opencli-plugin-/,"");return{type:"git",cloneUrl:n,name:d}}const o=n.match(/^git@[^:]+:(.+?)(?:\.git)?$/);if(o){const d=o[1].split("/").pop().replace(/^opencli-plugin-/,"");return{type:"git",cloneUrl:n,name:d}}const c=n.match(/^https?:\/\/[^/]+\/(.+?)(?:\.git)?$/);if(c){const d=c[1].split("/").pop().replace(/^opencli-plugin-/,"");return{type:"git",cloneUrl:n.endsWith(".git")?n:`${n}.git`,name:d}}return null}function Sn(n){try{const t=resolveHostOpencliRoot(),e=l.join(n,"node_modules","@jackwener","opencli");if(a.existsSync(e))a.rmSync(e,{recursive:!0,force:!0});a.mkdirSync(l.dirname(e),{recursive:!0});const i=w?"junction":"dir";a.symlinkSync(t,e,i);S.debug(`Linked host ppcli into plugin: ${e} → ${t}`)}catch(t){S.warn(`Failed to link host ppcli into plugin: ${v(t)}`)}}export function resolveEsbuildBin(){const n=resolveHostOpencliRoot();if(w){const e=l.join(n,"node_modules",".bin","esbuild.cmd");if(a.existsSync(e))return e}if(!w)try{const e=import.meta.resolve("esbuild/package.json");if(e.startsWith("file://")){const i=R(e),r=a.readFileSync(i,"utf8"),o=JSON.parse(r);if(o.bin&&typeof o.bin==="object"&&o.bin.esbuild){const c=l.resolve(l.dirname(i),o.bin.esbuild);if(a.existsSync(c))return c}else if(typeof o.bin==="string"){const c=l.resolve(l.dirname(i),o.bin);if(a.existsSync(c))return c}}}catch{}const t=l.join(n,"node_modules",".bin","esbuild");if(a.existsSync(t))return t;try{const i=nn(w?"where esbuild":"which esbuild",{encoding:"utf-8",stdio:"pipe"}).trim().split(`
19
+ - `)}`);o.push({name:c,lockEntry:s,manifestEntry:u})}return o}function mn(n,t,e,i,r){for(const o of t){if(!r)continue;L(n,o.name,{...o.lockEntry,source:{kind:"monorepo",url:e,repoName:i,subPath:o.manifestEntry.path},commitHash:r,updatedAt:new Date().toISOString()})}}function gn(n,t,e,i,r){if(!r)return;L(n,t,{source:{kind:"git",url:e},commitHash:r,installedAt:i?.installedAt??new Date().toISOString(),updatedAt:new Date().toISOString()})}export function uninstallPlugin(n){const t=l.join(y,n);if(!W(t))throw Error(`Plugin "${n}" is not installed.`);const e=readLockFile(),i=e[n];if(isSymlinkSync(t))a.unlinkSync(t);else a.rmSync(t,{recursive:!0,force:!0});if(i?.source.kind==="monorepo"){delete e[n];const o=i.source.repoName;if(!Object.values(e).some((s)=>s.source.kind==="monorepo"&&s.source.repoName===o)){const s=l.join(getMonoreposDir(),o);try{a.rmSync(s,{recursive:!0,force:!0})}catch{}}}else if(e[n])delete e[n];writeLockFile(e)}function isSymlinkSync(n){try{return a.lstatSync(n).isSymbolicLink()}catch{return!1}}export function updatePlugin(n){const t=l.join(y,n);if(!a.existsSync(t))throw Error(`Plugin "${n}" is not installed.`);const e=readLockFile(),i=e[n],r=resolvePluginSource(i,t);if(r?.kind==="local"){pn(n,t,e,i);return}if(r?.kind==="monorepo"){const c=l.join(getMonoreposDir(),r.repoName),{repoName:s,url:u}=r;J(u,(p)=>{const d=P(p);if(!d||!D(d))throw Error(`Updated source is no longer a monorepo: ${u}`);const m=x(d);if(m&&!j(m))throw Error(`Plugin requires ppcli ${m}, but current version is incompatible.`);const b=fn(s,e,d,u,p);if(b.length>0)postInstallMonorepoLifecycle(p,b.map((g)=>E(p,g.manifestEntry.path)));Y(c,y,b.map((g)=>({name:g.name,subPath:g.manifestEntry.path})),{stagingDir:p,parentDir:l.dirname(c)},(g)=>{mn(e,b,u,s,g);writeLockFile(e)})});return}const o=cn(i,t);J(o,(c)=>{const s=P(c);if(s&&D(s))throw Error(`Updated source is now a monorepo: ${o}`);const u=x(s);if(u&&!j(u))throw Error(`Plugin requires ppcli ${u}, but current version is incompatible.`);K(c);Q(c,t,(p)=>{gn(e,n,o,e[n],p);if(p)writeLockFile(e)})})}export function updateAllPlugins(){return listPlugins().map((n)=>{try{updatePlugin(n.name);return{name:n.name,success:!0}}catch(t){return{name:n.name,success:!1,error:v(t)}}})}export function listPlugins(){if(!a.existsSync(y))return[];const n=a.readdirSync(y,{withFileTypes:!0}),t=readLockFile(),e=[];for(const i of n){const r=l.join(y,i.name);if(!(i.isDirectory()||isSymlinkSync(r)))continue;const c=hn(r),s=t[i.name],u=P(r);let p=u?.description,d=u?.version;if(s?.source.kind==="monorepo"&&!p){const b=l.join(getMonoreposDir(),s.source.repoName),N=P(b)?.plugins?.[i.name];if(N){p=p??N.description;d=d??N.version}}const m=resolveStoredPluginSource(s,r);e.push({name:i.name,path:r,commands:c,source:m,version:d??s?.commitHash?.slice(0,7),installedAt:s?.installedAt,monorepoName:s?.source.kind==="monorepo"?s.source.repoName:void 0,description:p})}return e}function hn(n){try{const t=a.readdirSync(n);return[...new Set(t.filter((i)=>i.endsWith(".ts")&&!i.endsWith(".d.ts")&&!i.endsWith(".test.ts")||i.endsWith(".js")&&!i.endsWith(".d.js")).map((i)=>l.basename(i,l.extname(i))))]}catch{return[]}}function yn(n){try{return U("git",["config","--get","remote.origin.url"],{cwd:n,encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).trim()}catch{return}}function parseSource(n){if(n.startsWith("file://"))try{const s=l.resolve(R(n));return{type:"local",localPath:s,name:l.basename(s).replace(/^opencli-plugin-/,"")}}catch{return null}if(l.isAbsolute(n)){const s=l.resolve(n);return{type:"local",localPath:s,name:l.basename(s).replace(/^opencli-plugin-/,"")}}const t=n.match(/^github:([\w.-]+)\/([\w.-]+)\/([\w.-]+)$/);if(t){const[,s,u,p]=t,d=u.replace(/^opencli-plugin-/,"");return{type:"git",cloneUrl:`https://github.com/${s}/${u}.git`,name:d,subPlugin:p}}const e=n.match(/^github:([\w.-]+)\/([\w.-]+)$/);if(e){const[,s,u]=e,p=u.replace(/^opencli-plugin-/,"");return{type:"git",cloneUrl:`https://github.com/${s}/${u}.git`,name:p}}const i=n.match(/^https?:\/\/github\.com\/([\w.-]+)\/([\w.-]+?)(?:\.git)?$/);if(i){const[,s,u]=i,p=u.replace(/^opencli-plugin-/,"");return{type:"git",cloneUrl:`https://github.com/${s}/${u}.git`,name:p}}const r=n.match(/^ssh:\/\/[^/]+\/(.*?)(?:\.git)?$/);if(r){const d=r[1].split("/").pop().replace(/^opencli-plugin-/,"");return{type:"git",cloneUrl:n,name:d}}const o=n.match(/^git@[^:]+:(.+?)(?:\.git)?$/);if(o){const d=o[1].split("/").pop().replace(/^opencli-plugin-/,"");return{type:"git",cloneUrl:n,name:d}}const c=n.match(/^https?:\/\/[^/]+\/(.+?)(?:\.git)?$/);if(c){const d=c[1].split("/").pop().replace(/^opencli-plugin-/,"");return{type:"git",cloneUrl:n.endsWith(".git")?n:`${n}.git`,name:d}}return null}function Sn(n){try{const t=resolveHostOpencliRoot(),e=l.join(n,"node_modules","@jackwener","opencli");if(a.existsSync(e))a.rmSync(e,{recursive:!0,force:!0});a.mkdirSync(l.dirname(e),{recursive:!0});const i=w?"junction":"dir";a.symlinkSync(t,e,i);S.debug(`Linked host ppcli into plugin: ${e} → ${t}`)}catch(t){S.warn(`Failed to link host ppcli into plugin: ${v(t)}`)}}export function resolveEsbuildBin(){const n=resolveHostOpencliRoot();if(w){const e=l.join(n,"node_modules",".bin","esbuild.cmd");if(a.existsSync(e))return e}if(!w)try{const e=import.meta.resolve("esbuild/package.json");if(e.startsWith("file://")){const i=R(e),r=a.readFileSync(i,"utf8"),o=JSON.parse(r);if(o.bin&&typeof o.bin==="object"&&o.bin.esbuild){const c=l.resolve(l.dirname(i),o.bin.esbuild);if(a.existsSync(c))return c}else if(typeof o.bin==="string"){const c=l.resolve(l.dirname(i),o.bin);if(a.existsSync(c))return c}}}catch{}const t=l.join(n,"node_modules",".bin","esbuild");if(a.existsSync(t))return t;try{const i=nn(w?"where esbuild":"which esbuild",{encoding:"utf-8",stdio:"pipe"}).trim().split(`
20
20
  `)[0].trim();if(i&&a.existsSync(i))return i}catch{}return null}function resolveHostOpencliRoot(n=R(import.meta.url)){let t=l.dirname(n);while(!0){const e=l.join(t,"package.json");if(a.existsSync(e))try{if(JSON.parse(a.readFileSync(e,"utf-8"))?.name==="@jackwener/opencli")return t}catch{}const i=l.dirname(t);if(i===t)break;t=i}return l.resolve(l.dirname(n),"..")}function kn(n){try{const t=resolveEsbuildBin();if(!t){S.warn("esbuild not found. TS plugin files will not be transpiled and may fail to load. Install esbuild (`npm i -g esbuild`) or ensure it is available in the ppcli host node_modules.");return}const i=a.readdirSync(n).filter((r)=>r.endsWith(".ts")&&!r.endsWith(".d.ts")&&!r.endsWith(".test.ts"));for(const r of i){const o=r.replace(/\.ts$/,".js"),c=l.join(n,o);if(a.existsSync(c))continue;try{U(t,[r,`--outfile=${o}`,"--format=esm","--platform=node"],{cwd:n,encoding:"utf-8",stdio:["pipe","pipe","pipe"],...w&&{shell:!0}});S.debug(`Transpiled plugin file: ${r} → ${o}`)}catch(s){S.warn(`Failed to transpile ${r}: ${v(s)}`)}}}catch(t){S.warn(`TS transpilation setup failed: ${v(t)}`)}}export{resolveHostOpencliRoot as _resolveHostOpencliRoot,resolveEsbuildBin as _resolveEsbuildBin,getCommitHash as _getCommitHash,installDependencies as _installDependencies,parseSource as _parseSource,postInstallMonorepoLifecycle as _postInstallMonorepoLifecycle,readLockFile as _readLockFile,readLockFileWithWriter as _readLockFileWithWriter,updateAllPlugins as _updateAllPlugins,validatePluginStructure as _validatePluginStructure,writeLockFile as _writeLockFile,writeLockFileWithFs as _writeLockFileWithFs,isSymlinkSync as _isSymlinkSync,getMonoreposDir as _getMonoreposDir,installLocalPlugin as _installLocalPlugin,isLocalPluginSource as _isLocalPluginSource,moveDir as _moveDir,resolvePluginSource as _resolvePluginSource,resolveStoredPluginSource as _resolveStoredPluginSource,toStoredPluginSource as _toStoredPluginSource,toLocalPluginSource as _toLocalPluginSource};
@@ -91,6 +91,15 @@ interface BaseCliCommand {
91
91
  navigateBefore?: boolean | string;
92
92
  /** Site session lifecycle for adapter commands. */
93
93
  siteSession?: SiteSessionMode;
94
+ /**
95
+ * 给 persistent 会话名加一段后缀,把这条命令隔到自己的标签页里。
96
+ *
97
+ * 默认 persistent 会话名是 `site:<site>`,同站点所有命令复用同一个标签页——对
98
+ * 「跑完就走」的命令是省窗口,对**长驻监听**却是致命的:任何一条同站命令一导航,
99
+ * 监听页上的 WebSocket / SSE 长连接就断了。`sessionKey: 'watch'` 让监听页拿到
100
+ * `site:<site>:watch` 这个独立会话,与发布/回复命令互不打扰。
101
+ */
102
+ sessionKey?: string;
94
103
  /** Default browser window mode for commands whose UX requires visibility. */
95
104
  defaultWindowMode?: 'foreground' | 'background';
96
105
  /** Override the default CLI output format when the user does not pass -f/--format. */
@@ -1 +1 @@
1
- export var Strategy;(function(e){e.PUBLIC="public";e.LOCAL="local";e.COOKIE="cookie";e.INTERCEPT="intercept";e.UI="ui"})(Strategy||(Strategy={}));const o=globalThis.__opencli_registry__??=new Map;export function cli(e){const n={site:e.site,name:e.name,aliases:e.aliases,description:e.description??"",access:e.access,example:e.example,domain:e.domain,strategy:e.strategy,browser:e.browser,args:e.args??[],columns:e.columns,func:e.func,pipeline:e.pipeline,footerExtra:e.footerExtra,validateArgs:e.validateArgs,navigateBefore:e.navigateBefore,siteSession:e.siteSession,defaultWindowMode:e.defaultWindowMode,defaultFormat:e.defaultFormat,authStatus:e.authStatus};registerCommand(n);return o.get(fullName(n))}export function getRegistry(){return o}export function fullName(e){return`${e.site}/${e.name}`}export function strategyLabel(e){return e.strategy??Strategy.PUBLIC}function f(e){const n=e.args.filter((a)=>a.fromFile);if(n.length===0)return;const t=`${e.site}/${e.name}`;if(n.length>1)throw Error(`Command ${t}: 只能有一个 fromFile 参数,当前有 ${n.map((a)=>a.name).join(", ")}`);const r=n[0],s=typeof r.fromFile==="string"?r.fromFile:"file",i=e.args.find((a)=>a.name===s);if(i){if(i.generatedFileFor===r.name)return;throw Error(`Command ${t}: fromFile 想生成的 --${s} 与已有参数重名;`+`给 ${r.name} 的 fromFile 指定别的名字(如 '${r.name}-file')`)}const l=r.positional?`<${r.name}>`:`--${r.name}`;e.args=e.args.map((a)=>a===r?{...a,required:!1,requiredUnlessFile:a.required===!0}:a);e.args.push({name:s,generatedFileFor:r.name,help:`从本机文件读取${r.name}(UTF-8),与 ${l} 二选一。`+"长正文建议走文件:命令行上只剩短参数,不受 shell 引号 / 换行 / 编码影响"})}function u(e){c(e);m(e);f(e);const n=e.strategy??(e.browser===!1?Strategy.PUBLIC:Strategy.COOKIE),t=e.browser??(n!==Strategy.PUBLIC&&n!==Strategy.LOCAL);let r=e.navigateBefore;if(r===void 0){if(n===Strategy.COOKIE&&e.domain)r=`https://${e.domain}`;else if(n!==Strategy.PUBLIC&&n!==Strategy.LOCAL)r=!0}return t?{...e,strategy:n,browser:!0,navigateBefore:r}:{...e,strategy:n,browser:!1,navigateBefore:r}}function c(e){if(e.access==="read"||e.access==="write")return;const n=`${e.site}/${e.name}`;throw Error(`Command ${n} must declare access: 'read' | 'write'`)}function m(e){if(e.siteSession===void 0)return;const n=`${e.site}/${e.name}`;if(e.siteSession!=="ephemeral"&&e.siteSession!=="persistent")throw Error(`Command ${n} siteSession must be one of: ephemeral, persistent`)}export function registerCommand(e){const n=u(e),t=fullName(n),r=o.get(t);if(r?.aliases)for(const i of r.aliases)o.delete(`${r.site}/${i}`);const s=g(n.aliases,n.name);n.aliases=s.length>0?s:void 0;o.set(t,n);for(const i of s)o.set(`${n.site}/${i}`,n)}function g(e,n){if(!Array.isArray(e)||e.length===0)return[];const t=new Set,r=[];for(const s of e){const i=typeof s==="string"?s.trim():"";if(!i||i===n||t.has(i))continue;t.add(i);r.push(i)}return r}
1
+ export var Strategy;(function(e){e.PUBLIC="public";e.LOCAL="local";e.COOKIE="cookie";e.INTERCEPT="intercept";e.UI="ui"})(Strategy||(Strategy={}));const o=globalThis.__opencli_registry__??=new Map;export function cli(e){const n={site:e.site,name:e.name,aliases:e.aliases,description:e.description??"",access:e.access,example:e.example,domain:e.domain,strategy:e.strategy,browser:e.browser,args:e.args??[],columns:e.columns,func:e.func,pipeline:e.pipeline,footerExtra:e.footerExtra,validateArgs:e.validateArgs,navigateBefore:e.navigateBefore,siteSession:e.siteSession,sessionKey:e.sessionKey,defaultWindowMode:e.defaultWindowMode,defaultFormat:e.defaultFormat,authStatus:e.authStatus};registerCommand(n);return o.get(fullName(n))}export function getRegistry(){return o}export function fullName(e){return`${e.site}/${e.name}`}export function strategyLabel(e){return e.strategy??Strategy.PUBLIC}function f(e){const n=e.args.filter((a)=>a.fromFile);if(n.length===0)return;const s=`${e.site}/${e.name}`;if(n.length>1)throw Error(`Command ${s}: 只能有一个 fromFile 参数,当前有 ${n.map((a)=>a.name).join(", ")}`);const r=n[0],t=typeof r.fromFile==="string"?r.fromFile:"file",i=e.args.find((a)=>a.name===t);if(i){if(i.generatedFileFor===r.name)return;throw Error(`Command ${s}: fromFile 想生成的 --${t} 与已有参数重名;`+`给 ${r.name} 的 fromFile 指定别的名字(如 '${r.name}-file')`)}const l=r.positional?`<${r.name}>`:`--${r.name}`;e.args=e.args.map((a)=>a===r?{...a,required:!1,requiredUnlessFile:a.required===!0}:a);e.args.push({name:t,generatedFileFor:r.name,help:`从本机文件读取${r.name}(UTF-8),与 ${l} 二选一。`+"长正文建议走文件:命令行上只剩短参数,不受 shell 引号 / 换行 / 编码影响"})}function u(e){c(e);m(e);f(e);const n=e.strategy??(e.browser===!1?Strategy.PUBLIC:Strategy.COOKIE),s=e.browser??(n!==Strategy.PUBLIC&&n!==Strategy.LOCAL);let r=e.navigateBefore;if(r===void 0){if(n===Strategy.COOKIE&&e.domain)r=`https://${e.domain}`;else if(n!==Strategy.PUBLIC&&n!==Strategy.LOCAL)r=!0}return s?{...e,strategy:n,browser:!0,navigateBefore:r}:{...e,strategy:n,browser:!1,navigateBefore:r}}function c(e){if(e.access==="read"||e.access==="write")return;const n=`${e.site}/${e.name}`;throw Error(`Command ${n} must declare access: 'read' | 'write'`)}function m(e){if(e.siteSession===void 0)return;const n=`${e.site}/${e.name}`;if(e.siteSession!=="ephemeral"&&e.siteSession!=="persistent")throw Error(`Command ${n} siteSession must be one of: ephemeral, persistent`)}export function registerCommand(e){const n=u(e),s=fullName(n),r=o.get(s);if(r?.aliases)for(const i of r.aliases)o.delete(`${r.site}/${i}`);const t=g(n.aliases,n.name);n.aliases=t.length>0?t:void 0;o.set(s,n);for(const i of t)o.set(`${n.site}/${i}`,n)}function g(e,n){if(!Array.isArray(e)||e.length===0)return[];const s=new Set,r=[];for(const t of e){const i=typeof t==="string"?t.trim():"";if(!i||i===n||s.has(i))continue;s.add(i);r.push(i)}return r}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "publishport-opencli",
3
- "version": "1.0.24",
3
+ "version": "1.0.26",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "provenance": false