publishport-opencli 1.0.7 → 1.0.9

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.
package/dist/src/cli.js CHANGED
@@ -93,7 +93,7 @@ Examples:
93
93
  }
94
94
  return { kind: 'ok', html: document.documentElement ? document.documentElement.outerHTML : null };
95
95
  })()`);if(N.kind==="invalid_selector"){console.log(JSON.stringify({error:{code:"invalid_selector",message:`Selector "${K.selector}" is not a valid CSS selector: ${N.reason}`}},null,2));process.exitCode=J.USAGE_ERROR;return}const U=N.html;if(U===null){if(K.selector){console.log(JSON.stringify({error:{code:"selector_not_found",message:`Selector "${K.selector}" matched 0 elements.`}},null,2));process.exitCode=J.USAGE_ERROR;return}console.log("(empty)");return}if(Y>0&&U.length>Y){console.log(`<!-- ppcli: truncated ${Y} of ${U.length} chars; re-run without --max (or --max 0) for full -->
96
- ${U.slice(0,Y)}`);return}console.log(U)}));E(m(Hq.command("attributes")).argument("[target]","Numeric ref (from browser state / find), CSS selector, or omit when using --role/--name/etc.").option("--nth <n>","Pick the nth match (0-based) when <target> is a multi-match CSS selector").description("Element attributes — JSON envelope {value, matches_n}")).action(D(async(q,K,X)=>Tq(q,K,X??{},FQ(),"attributes")));function t(q){const K=P(q);if(K&&typeof K==="object"&&"error"in K)return K;if(typeof K==="number")return{opts:{nth:K}};return{opts:{}}}function dK(q){const K=Array.isArray(q)?q:[];if(K.length===0)return{error:{code:"usage_error",message:"At least one file path is required.",hint:'Example: ppcli browser upload "input[type=file]" ./receipt.pdf'}};const X=[];for(const Q of K){const Y=String(Q),Z=Y==="~"||Y.startsWith(`~${M.sep}`)?M.join(Nq.homedir(),Y.slice(2)):Y,N=M.resolve(Z);if(!I.existsSync(N))return{error:{code:"file_not_found",message:`File not found: ${N}`}};if(!I.statSync(N).isFile())return{error:{code:"not_a_file",message:`Not a regular file: ${N}`}};X.push(N)}return{files:X}}function QK(q,K){const X=P(q);if(X&&typeof X==="object"&&"error"in X)return{error:X.error.replace("--nth",K)};if(typeof X==="number")return{opts:{nth:X}};return{opts:{}}}E(m(j.command("click")).argument("[target]","Numeric ref (from browser state / find), CSS selector, or omit when using --role/--name/etc.").option("--nth <n>","When <target> is a multi-match CSS selector, pick the nth match (0-based)").description("Click element — JSON envelope {clicked, target, matches_n}")).action(D(async(q,K,X)=>{const Q=await bq(q,K,X??{},"write");if(typeof Q!=="string"){console.log(JSON.stringify(Q,null,2));process.exitCode=J.USAGE_ERROR;return}const Y=t(X?.nth);if("error"in Y){console.log(JSON.stringify({error:{code:"usage_error",message:Y.error}},null,2));process.exitCode=J.USAGE_ERROR;return}const{matches_n:Z,match_level:N}=await q.click(Q,Y.opts);console.log(JSON.stringify({clicked:!0,target:Q,matches_n:Z,match_level:N},null,2))}));E(m(j.command("type")).argument("[targetOrText]","Numeric ref/CSS target, or text when using --role/--name/etc.").argument("[text]","Text to type").option("--nth <n>","When <target> is a multi-match CSS selector, pick the nth match (0-based)").description("Click element, then type text — JSON envelope {typed, text, target, matches_n, autocomplete}")).action(D(async(q,K,X,Q)=>{const Y=await Cq(q,K,X,Q??{},"text");if(!Y)return;const Z=t(Q?.nth);if("error"in Z){console.log(JSON.stringify({error:{code:"usage_error",message:Z.error}},null,2));process.exitCode=J.USAGE_ERROR;return}await q.click(Y.target,Z.opts);await q.wait(0.3);const{matches_n:N,match_level:U}=await q.typeText(Y.target,Y.value,Z.opts),G=await q.evaluate(EQ());if(G)await q.wait(0.4);console.log(JSON.stringify({typed:!0,text:Y.value,target:Y.target,matches_n:N,match_level:U,autocomplete:!!G},null,2))}));E(m(j.command("hover")).argument("[target]","Numeric ref (from browser state / find), CSS selector, or omit when using --role/--name/etc.").option("--nth <n>","When <target> is a multi-match CSS selector, pick the nth match (0-based)").description("Move the mouse over an element — JSON envelope {hovered, target, matches_n}")).action(D(async(q,K,X)=>{if(typeof q.hover!=="function")throw Error("browser hover is not supported by this browser backend");const Q=await $q(q,K,X??{});if(!Q)return;const Y=t(X?.nth);if("error"in Y){console.log(JSON.stringify({error:{code:"usage_error",message:Y.error}},null,2));process.exitCode=J.USAGE_ERROR;return}const{matches_n:Z,match_level:N}=await q.hover(Q,Y.opts);console.log(JSON.stringify({hovered:!0,target:Q,matches_n:Z,match_level:N},null,2))}));E(m(j.command("focus")).argument("[target]","Numeric ref (from browser state / find), CSS selector, or omit when using --role/--name/etc.").option("--nth <n>","When <target> is a multi-match CSS selector, pick the nth match (0-based)").description("Focus an element — JSON envelope {focused, target, matches_n}")).action(D(async(q,K,X)=>{if(typeof q.focus!=="function")throw Error("browser focus is not supported by this browser backend");const Q=await $q(q,K,X??{});if(!Q)return;const Y=t(X?.nth);if("error"in Y){console.log(JSON.stringify({error:{code:"usage_error",message:Y.error}},null,2));process.exitCode=J.USAGE_ERROR;return}const{focused:Z,matches_n:N,match_level:U}=await q.focus(Q,Y.opts);console.log(JSON.stringify({focused:Z,target:Q,matches_n:N,match_level:U},null,2))}));E(m(j.command("dblclick")).argument("[target]","Numeric ref (from browser state / find), CSS selector, or omit when using --role/--name/etc.").option("--nth <n>","When <target> is a multi-match CSS selector, pick the nth match (0-based)").description("Double-click element — JSON envelope {dblclicked, target, matches_n}")).action(D(async(q,K,X)=>{if(typeof q.dblClick!=="function")throw Error("browser dblclick is not supported by this browser backend");const Q=await $q(q,K,X??{});if(!Q)return;const Y=t(X?.nth);if("error"in Y){console.log(JSON.stringify({error:{code:"usage_error",message:Y.error}},null,2));process.exitCode=J.USAGE_ERROR;return}const{matches_n:Z,match_level:N}=await q.dblClick(Q,Y.opts);console.log(JSON.stringify({dblclicked:!0,target:Q,matches_n:Z,match_level:N},null,2))}));const XK=async(q,K,X,Q)=>{if(typeof q.setChecked!=="function")throw Error(`browser ${Q?"check":"uncheck"} is not supported by this browser backend`);const Y=await $q(q,K,X);if(!Y)return;const Z=t(X?.nth);if("error"in Z){console.log(JSON.stringify({error:{code:"usage_error",message:Z.error}},null,2));process.exitCode=J.USAGE_ERROR;return}const N=await q.setChecked(Y,Q,Z.opts);console.log(JSON.stringify({checked:N.checked,changed:N.changed,target:Y,matches_n:N.matches_n,match_level:N.match_level,...N.kind?{kind:N.kind}:{}},null,2))};E(m(j.command("check")).argument("[target]","Numeric ref (from browser state / find), CSS selector, or omit when using --role/--name/etc.").option("--nth <n>","When <target> is a multi-match CSS selector, pick the nth match (0-based)").description("Ensure a checkbox/radio/aria-checked control is checked — JSON envelope {checked, changed, target, matches_n}")).action(D(async(q,K,X)=>{await XK(q,K,X??{},!0)}));E(m(j.command("uncheck")).argument("[target]","Numeric ref (from browser state / find), CSS selector, or omit when using --role/--name/etc.").option("--nth <n>","When <target> is a multi-match CSS selector, pick the nth match (0-based)").description("Ensure a checkbox/aria-checked control is unchecked — JSON envelope {checked, changed, target, matches_n}")).action(D(async(q,K,X)=>{await XK(q,K,X??{},!1)}));E(m(j.command("upload")).argument("[targetOrFile]","Numeric ref/CSS target, or first file when using --role/--name/etc.").argument("[files...]","Local file path(s) to attach").option("--nth <n>","When <target> is a multi-match CSS selector, pick the nth match (0-based)").description("Attach local files to a file input — JSON envelope {uploaded, files, file_names, target, matches_n}")).action(D(async(q,K,X,Q)=>{if(typeof q.uploadFiles!=="function")throw Error("browser upload is not supported by this browser backend");const Y=!!Lq(Q??{}),N=await $q(q,Y?void 0:K,Q??{});if(!N)return;const U=t(Q?.nth);if("error"in U){console.log(JSON.stringify({error:{code:"usage_error",message:U.error}},null,2));process.exitCode=J.USAGE_ERROR;return}const G=Y?[K,...Array.isArray(X)?X:[]].filter((F)=>F!==void 0):X,W=dK(G);if("error"in W){console.log(JSON.stringify({error:W.error},null,2));process.exitCode=J.USAGE_ERROR;return}const _=await q.uploadFiles(N,W.files,U.opts);console.log(JSON.stringify(_,null,2))}));E(eq(eq(j.command("drag"),"from"),"to").argument("[source]","Numeric ref/CSS selector to drag from, or omit with --from-role/--from-name/etc.").argument("[target]","Numeric ref/CSS selector to drop onto, or omit with --to-role/--to-name/etc.").option("--from-nth <n>","When <source> is a multi-match CSS selector, pick the nth match (0-based)").option("--to-nth <n>","When <target> is a multi-match CSS selector, pick the nth match (0-based)").description("Drag one element to another — JSON envelope {dragged, source, target, source_matches_n, target_matches_n}")).action(D(async(q,K,X,Q)=>{if(typeof q.drag!=="function")throw Error("browser drag is not supported by this browser backend");const Y=await KK(q,K,Q??{},"from","source");if(!Y)return;const Z=await KK(q,X,Q??{},"to","target");if(!Z)return;const N=QK(Q?.fromNth,"--from-nth");if("error"in N){console.log(JSON.stringify({error:{code:"usage_error",message:N.error}},null,2));process.exitCode=J.USAGE_ERROR;return}const U=QK(Q?.toNth,"--to-nth");if("error"in U){console.log(JSON.stringify({error:{code:"usage_error",message:U.error}},null,2));process.exitCode=J.USAGE_ERROR;return}const G=await q.drag(Y,Z,{from:N.opts,to:U.opts});console.log(JSON.stringify(G,null,2))}));E(m(j.command("fill")).argument("[targetOrText]","Numeric ref/CSS target, or text when using --role/--name/etc.").argument("[text]","Text to set exactly").option("--nth <n>","When <target> is a multi-match CSS selector, pick the nth match (0-based)").description("Set input/textarea/contenteditable text exactly and verify the value — JSON envelope {filled, verified, text, actual}")).action(D(async(q,K,X,Q)=>{const Y=await Cq(q,K,X,Q??{},"text");if(!Y)return;const Z=t(Q?.nth);if("error"in Z){console.log(JSON.stringify({error:{code:"usage_error",message:Z.error}},null,2));process.exitCode=J.USAGE_ERROR;return}const N=await q.fillText(Y.target,Y.value,Z.opts);if(!N.verified)process.exitCode=J.GENERIC_ERROR;console.log(JSON.stringify({filled:N.filled,verified:N.verified,target:Y.target,text:Y.value,actual:N.actual,length:N.length,matches_n:N.matches_n,match_level:N.match_level,...N.mode?{mode:N.mode}:{}},null,2))}));E(m(j.command("select")).argument("[targetOrOption]","Numeric ref/CSS target, or option text when using --role/--name/etc.").argument("[option]","Option text (or value) to select").option("--nth <n>","When <target> is a multi-match CSS selector, pick the nth match (0-based)").description("Select dropdown option — JSON envelope {selected, target, matches_n}")).action(D(async(q,K,X,Q)=>{const Y=await Cq(q,K,X,Q??{},"option");if(!Y)return;const Z=t(Q?.nth);if("error"in Z){console.log(JSON.stringify({error:{code:"usage_error",message:Z.error}},null,2));process.exitCode=J.USAGE_ERROR;return}const{matches_n:N,match_level:U}=await R(q,Y.target,Z.opts),G=await q.evaluate(RQ(Y.value));if(G?.error){console.log(JSON.stringify({error:{code:G.error==="Not a <select>"?"not_a_select":"option_not_found",message:G.error,...G.available&&{available:G.available},matches_n:N}},null,2));process.exitCode=J.GENERIC_ERROR;return}console.log(JSON.stringify({selected:G?.selected??Y.value,target:Y.target,matches_n:N,match_level:U},null,2))}));E(j.command("keys").argument("<key>","Key to press (Enter, Escape, Tab, Control+a)")).description("Press keyboard key").action(D(async(q,K)=>{await q.pressKey(K);console.log(`Pressed: ${K}`)}));const YK=j.command("dialog").description("Handle a blocking JavaScript alert/confirm/prompt dialog");E(YK.command("accept").option("--text <text>","Prompt text to submit for prompt() dialogs").description("Accept the currently open JavaScript dialog")).action(D(async(q,K)=>{if(!q.handleJavaScriptDialog)throw Error("This browser session does not support JavaScript dialog handling");try{await q.handleJavaScriptDialog(!0,K?.text)}catch(X){if(h(X).toLowerCase().includes("no dialog")){console.log(JSON.stringify({error:{code:"no_javascript_dialog",message:"No JavaScript dialog is currently open."}},null,2));process.exitCode=J.USAGE_ERROR;return}throw X}console.log(JSON.stringify({handled:!0,action:"accept",...K?.text!==void 0&&{text:K.text}},null,2))}));E(YK.command("dismiss").description("Dismiss the currently open JavaScript dialog")).action(D(async(q)=>{if(!q.handleJavaScriptDialog)throw Error("This browser session does not support JavaScript dialog handling");try{await q.handleJavaScriptDialog(!1)}catch(K){if(h(K).toLowerCase().includes("no dialog")){console.log(JSON.stringify({error:{code:"no_javascript_dialog",message:"No JavaScript dialog is currently open."}},null,2));process.exitCode=J.USAGE_ERROR;return}throw K}console.log(JSON.stringify({handled:!0,action:"dismiss"},null,2))}));E(j.command("wait")).argument("<type>","selector, text, time, xhr, or download").argument("[value]","CSS selector, text string, seconds, XHR URL regex, or download filename/URL pattern").option("--timeout <ms>","Timeout in milliseconds","10000").description('Wait for selector, text, time, matching XHR, or browser download (e.g. wait selector ".loaded", wait text "Success", wait time 3, wait xhr "/api/search", wait download receipt.pdf)').action(D(async(q,K,X,Q)=>{const Y=parseInt(Q.timeout,10);if(K==="time"){const Z=parseFloat(X??"2");await q.wait(Z);console.log(`Waited ${Z}s`)}else if(K==="selector"){if(!X){console.error("Missing CSS selector");process.exitCode=J.USAGE_ERROR;return}await q.wait({selector:X,timeout:Y/1000});console.log(`Element "${X}" appeared`)}else if(K==="text"){if(!X){console.error("Missing text");process.exitCode=J.USAGE_ERROR;return}await q.wait({text:X,timeout:Y/1000});console.log(`Text "${X}" appeared`)}else if(K==="xhr"){if(!X){console.error("Missing XHR URL regex");process.exitCode=J.USAGE_ERROR;return}let Z;try{Z=new RegExp(X)}catch(_){console.error(`Invalid regex "${X}": ${_ instanceof Error?_.message:String(_)}`);process.exitCode=J.USAGE_ERROR;return}if(!(await q.startNetworkCapture?.()??!1))try{await q.evaluate(Kq)}catch{}await Wq(q);const U=Date.now()+Y,G=400;let W=null;while(Date.now()<U&&!W){W=(await Wq(q)).find((F)=>Z.test(F.url))??null;if(!W)await new Promise((F)=>setTimeout(F,G))}if(!W){console.log(JSON.stringify({error:{code:"xhr_not_seen",message:`No captured XHR matched /${X}/ within ${Y}ms`,hint:"Check the pattern against `browser network` output; the endpoint may not have fired yet, or capture is disabled."}},null,2));process.exitCode=J.GENERIC_ERROR;return}console.log(JSON.stringify({matched:{url:W.url,status:W.status,contentType:W.ct}},null,2))}else if(K==="download"){if(typeof q.waitForDownload!=="function"){console.log(JSON.stringify({error:{code:"download_wait_unavailable",message:"The active browser backend does not support download lifecycle waits.",hint:"Use the Browser Bridge extension version 1.0.8 or newer, then retry the command."}},null,2));process.exitCode=J.GENERIC_ERROR;return}const Z=await q.waitForDownload(String(X??""),Y);if(!Z.downloaded){const N=Z.state==="interrupted"&&Z.id!==void 0?"download_failed":"download_not_seen";console.log(JSON.stringify({error:{code:N,message:Z.error??`No download matched "${X??"*"}" within ${Y}ms`,hint:"Check the pattern against the expected filename or URL; use a longer --timeout if the download starts slowly."},download:Z},null,2));process.exitCode=J.GENERIC_ERROR;return}console.log(JSON.stringify(Z,null,2))}else{console.error(`Unknown wait type "${K}". Use: selector, text, time, xhr, or download`);process.exitCode=J.USAGE_ERROR}}));E(j.command("eval").argument("<js>","JavaScript code").option("--frame <index>",'Cross-origin iframe index from "browser frames"').description("Execute JS in page context, return result")).action(D(async(q,K,X)=>{let Q;if(X.frame!==void 0){const Y=Number.parseInt(X.frame,10);if(!Number.isInteger(Y)||Y<0){console.error(`Invalid frame index "${X.frame}". Use a 0-based index from "browser frames".`);process.exitCode=J.USAGE_ERROR;return}if(!q.evaluateInFrame)throw Error("This browser session does not support frame-targeted evaluation");Q=await q.evaluateInFrame(K,Y)}else Q=await q.evaluate(K);if(typeof Q==="string")console.log(Q);else console.log(JSON.stringify(Q,null,2))}));E(j.command("extract").option("--selector <css>","CSS selector scope; defaults to <main>/<article>/<body>").option("--chunk-size <chars>","Target chunk size in chars","20000").option("--start <char>","Start offset (use next_start_char from a previous extract)","0").description("Extract page content as markdown, paragraph-aware chunks for long pages")).action(D(async(q,K)=>{const X=String(K.chunkSize??"20000");if(!/^\d+$/.test(X)||Number.parseInt(X,10)<=0){console.log(JSON.stringify({error:{code:"invalid_chunk_size",message:`--chunk-size must be a positive integer, got "${K.chunkSize}"`}},null,2));process.exitCode=J.USAGE_ERROR;return}const Q=String(K.start??"0");if(!/^\d+$/.test(Q)){console.log(JSON.stringify({error:{code:"invalid_start",message:`--start must be a non-negative integer, got "${K.start}"`}},null,2));process.exitCode=J.USAGE_ERROR;return}const Y=Number.parseInt(X,10),Z=Number.parseInt(Q,10),N=typeof K.selector==="string"&&K.selector.length>0?K.selector:null,U=CQ(N),G=await q.evaluate(U);if(!G){console.log(JSON.stringify({error:{code:"extract_failed",message:"Page returned no root element."}},null,2));process.exitCode=J.USAGE_ERROR;return}if("invalidSelector"in G){console.log(JSON.stringify({error:{code:"invalid_selector",message:`Selector "${N}" is not a valid CSS selector: ${G.reason}`}},null,2));process.exitCode=J.USAGE_ERROR;return}if("notFound"in G){console.log(JSON.stringify({error:{code:"selector_not_found",message:N?`Selector "${N}" matched 0 elements.`:"Page has no body/main/article element."}},null,2));process.exitCode=J.USAGE_ERROR;return}const W=TQ({html:G.html,url:G.url,title:G.title,selector:N,start:Z,chunkSize:Y});console.log(JSON.stringify(W,null,2))}));E(j.command("network")).option("--detail <key>","Emit full body for the entry with this key").option("--all","Include static resources (js/css/images/telemetry)").option("--raw","Emit full bodies for every entry (skip shape preview)").option("--filter <fields>","Comma-separated field names; keep only entries whose body shape has ALL names as path segments").option("--since <duration>","Only include entries from the last duration (for example: 30s, 2m)").option("--until <duration>","Only include entries older than the duration from now").option("--follow","Continuously print new matching entries as JSON lines",!1).option("--failed","Only include failed HTTP requests (status 0 or >= 400)",!1).option("--max-body <chars>","With --detail: cap the emitted body at N chars (0 = unlimited, default)","0").option("--ttl <ms>","Cache TTL in ms for --detail lookups",String(OK)).description("Capture network requests as shape previews; retrieve full bodies by key").action(D(async(q,K)=>{const X=xK(K.ttl,"ttl",OK),Q=tq(q),Y=typeof K.detail==="string"&&K.detail.length>0,Z=typeof K.filter==="string",N=kq(K.since,"since"),U=kq(K.until,"until");if(N&&typeof N==="object"){p("invalid_since",N.error);return}if(U&&typeof U==="object"){p("invalid_until",U.error);return}if(Y&&Z){p("invalid_args","--filter and --detail cannot be used together (one narrows a list, the other fetches a specific entry).");return}let G=null;if(Z){const L=MQ(K.filter);if("reason"in L){p("invalid_filter",L.reason);return}G=L.fields}if(Y&&K.follow){p("invalid_args","--follow cannot be used with --detail.");return}if(Y){const L=kQ(Q,{ttlMs:X});if(L.status==="missing"){p("cache_missing",`No cached capture. Run "browser network" first (in session "${Q}").`);return}if(L.status==="expired"){p("cache_expired",`Cache is stale (age ${L.ageMs}ms > ttl ${X}ms). Re-run "browser network" to refresh.`);return}if(L.status==="corrupt"||!L.file){p("cache_corrupt",'Cache file is malformed; re-run "browser network" to regenerate.');return}const O=OQ(L.file,K.detail);if(!O){p("key_not_found",`Key "${K.detail}" not in cache.`,{available_keys:L.file.entries.map((a)=>a.key)});return}const s=String(K.maxBody??"0");if(!/^\d+$/.test(s)){p("invalid_max_body",`--max-body must be a non-negative integer, got "${K.maxBody}"`);return}const A=Number.parseInt(s,10);let b=O.body,x=!1;if(A>0&&typeof O.body==="string"&&O.body.length>A){b=O.body.slice(0,A);x=!0}const u=O.body_truncated===!0,c={key:O.key,url:O.url,method:O.method,status:O.status,ct:O.ct,size:O.size,...typeof O.timestamp==="number"?{timestamp:Gq(O.timestamp)}:{},shape:EK(O.body),body:b};if(u||x){c.body_truncated=!0;c.body_full_size=O.body_full_size??O.size;c.body_truncation_reason=u?"capture-limit":"max-body"}console.log(JSON.stringify(c,null,2));return}if(K.follow){if(!await q.startNetworkCapture?.())try{await q.evaluate(Kq)}catch{}while(!0){const L=await Wq(q).catch((A)=>{p("capture_failed",`Could not read network capture: ${A.message}`);return[]});let O=K.all?L:SK(L);O=iq(O,{sinceMs:N,untilMs:U});if(K.failed)O=O.filter((A)=>A.status===0||A.status>=400);const s=PK(O);for(const A of s)console.log(JSON.stringify({key:A.key,timestamp:Gq(A.timestamp),method:A.method,status:A.status,url:A.url,ct:A.ct,size:A.size,...A.bodyTruncated?{body_truncated:!0}:{}}));await new Promise((A)=>setTimeout(A,MK))}}let W;try{W=await Wq(q)}catch(L){p("capture_failed",`Could not read network capture: ${L.message}`);return}let _=K.all?W:SK(W);_=iq(_,{sinceMs:N,untilMs:U});if(K.failed)_=_.filter((L)=>L.status===0||L.status>=400);const F=W.length-_.length,f=PK(_).map((L)=>({key:L.key,url:L.url,method:L.method,status:L.status,size:L.size,ct:L.ct,body:L.body,...typeof L.timestamp==="number"?{timestamp:L.timestamp}:{},...L.bodyTruncated?{body_truncated:!0}:{},...L.bodyTruncated&&typeof L.bodyFullSize==="number"?{body_full_size:L.bodyFullSize}:{}}));let y=null;try{IQ(Q,f)}catch(L){y=`Could not persist capture cache: ${L.message}. --detail lookups may miss this capture.`}const w=f.map((L)=>({entry:L,shape:EK(L.body)})),T=G?w.filter((L)=>SQ(L.shape,G)):w,d=G?w.length-T.length:0,g={session:Q,captured_at:new Date().toISOString(),count:T.length,filtered_out:F};if(G){g.filter=G;g.filter_dropped=d}if(y)g.cache_warning=y;const Qq=T.filter((L)=>L.entry.body_truncated).length;if(Qq>0){g.body_truncated_count=Qq;g.body_truncated_hint="Some bodies exceeded the capture limit; their `shape` reflects only the captured prefix."}if(K.raw)g.entries=T.map((L)=>({...L.entry,...typeof L.entry.timestamp==="number"?{timestamp:Gq(L.entry.timestamp)}:{}}));else{g.entries=T.map((L)=>({key:L.entry.key,method:L.entry.method,...typeof L.entry.timestamp==="number"?{timestamp:Gq(L.entry.timestamp)}:{},status:L.entry.status,url:L.entry.url,ct:L.entry.ct,size:L.entry.size,shape:L.shape,...L.entry.body_truncated?{body_truncated:!0}:{}}));g.detail_hint='Run "browser network --detail <key>" for full body.'}console.log(JSON.stringify(g,null,2))}));j.command("init").argument("<name>","Adapter name in site/command format (e.g. hn/top)").description("Generate adapter scaffold in ~/.ppcli/clis/").action(async(q)=>{try{const K=q.split("/");if(K.length!==2||!K[0]||!K[1]){console.error("Name must be site/command format (e.g. hn/top)");process.exitCode=J.USAGE_ERROR;return}const[X,Q]=K;if(!/^[a-zA-Z0-9_-]+$/.test(X)||!/^[a-zA-Z0-9_-]+$/.test(Q)){console.error("Name parts must be alphanumeric/dash/underscore only");process.exitCode=J.USAGE_ERROR;return}const Y=await import("node:os"),Z=await import("node:fs"),N=await import("node:path"),U=N.join(Y.homedir(),".ppcli","clis",X),G=N.join(U,`${Q}.js`);if(Z.existsSync(G)){console.log(`Adapter already exists: ${G}`);return}const _=`import { cli, Strategy } from '@jackwener/opencli/registry';
96
+ ${U.slice(0,Y)}`);return}console.log(U)}));E(m(Hq.command("attributes")).argument("[target]","Numeric ref (from browser state / find), CSS selector, or omit when using --role/--name/etc.").option("--nth <n>","Pick the nth match (0-based) when <target> is a multi-match CSS selector").description("Element attributes — JSON envelope {value, matches_n}")).action(D(async(q,K,X)=>Tq(q,K,X??{},FQ(),"attributes")));function t(q){const K=P(q);if(K&&typeof K==="object"&&"error"in K)return K;if(typeof K==="number")return{opts:{nth:K}};return{opts:{}}}function dK(q){const K=Array.isArray(q)?q:[];if(K.length===0)return{error:{code:"usage_error",message:"At least one file path is required.",hint:'Example: ppcli browser upload "input[type=file]" ./receipt.pdf'}};const X=[];for(const Q of K){const Y=String(Q),Z=Y==="~"||Y.startsWith(`~${M.sep}`)?M.join(Nq.homedir(),Y.slice(2)):Y,N=M.resolve(Z);if(!I.existsSync(N))return{error:{code:"file_not_found",message:`File not found: ${N}`}};if(!I.statSync(N).isFile())return{error:{code:"not_a_file",message:`Not a regular file: ${N}`}};X.push(N)}return{files:X}}function QK(q,K){const X=P(q);if(X&&typeof X==="object"&&"error"in X)return{error:X.error.replace("--nth",K)};if(typeof X==="number")return{opts:{nth:X}};return{opts:{}}}E(m(j.command("click")).argument("[target]","Numeric ref (from browser state / find), CSS selector, or omit when using --role/--name/etc.").option("--nth <n>","When <target> is a multi-match CSS selector, pick the nth match (0-based)").description("Click element — JSON envelope {clicked, target, matches_n}")).action(D(async(q,K,X)=>{const Q=await bq(q,K,X??{},"write");if(typeof Q!=="string"){console.log(JSON.stringify(Q,null,2));process.exitCode=J.USAGE_ERROR;return}const Y=t(X?.nth);if("error"in Y){console.log(JSON.stringify({error:{code:"usage_error",message:Y.error}},null,2));process.exitCode=J.USAGE_ERROR;return}const{matches_n:Z,match_level:N,click_method:U,hit:G,retargeted:W}=await q.click(Q,Y.opts);console.log(JSON.stringify({clicked:!0,target:Q,matches_n:Z,match_level:N,...U&&{click_method:U},...G&&{hit:G},...W&&{retargeted:W}},null,2))}));E(m(j.command("type")).argument("[targetOrText]","Numeric ref/CSS target, or text when using --role/--name/etc.").argument("[text]","Text to type").option("--nth <n>","When <target> is a multi-match CSS selector, pick the nth match (0-based)").description("Click element, then type text — JSON envelope {typed, text, target, matches_n, autocomplete}")).action(D(async(q,K,X,Q)=>{const Y=await Cq(q,K,X,Q??{},"text");if(!Y)return;const Z=t(Q?.nth);if("error"in Z){console.log(JSON.stringify({error:{code:"usage_error",message:Z.error}},null,2));process.exitCode=J.USAGE_ERROR;return}await q.click(Y.target,Z.opts);await q.wait(0.3);const{matches_n:N,match_level:U}=await q.typeText(Y.target,Y.value,Z.opts),G=await q.evaluate(EQ());if(G)await q.wait(0.4);console.log(JSON.stringify({typed:!0,text:Y.value,target:Y.target,matches_n:N,match_level:U,autocomplete:!!G},null,2))}));E(m(j.command("hover")).argument("[target]","Numeric ref (from browser state / find), CSS selector, or omit when using --role/--name/etc.").option("--nth <n>","When <target> is a multi-match CSS selector, pick the nth match (0-based)").description("Move the mouse over an element — JSON envelope {hovered, target, matches_n}")).action(D(async(q,K,X)=>{if(typeof q.hover!=="function")throw Error("browser hover is not supported by this browser backend");const Q=await $q(q,K,X??{});if(!Q)return;const Y=t(X?.nth);if("error"in Y){console.log(JSON.stringify({error:{code:"usage_error",message:Y.error}},null,2));process.exitCode=J.USAGE_ERROR;return}const{matches_n:Z,match_level:N}=await q.hover(Q,Y.opts);console.log(JSON.stringify({hovered:!0,target:Q,matches_n:Z,match_level:N},null,2))}));E(m(j.command("focus")).argument("[target]","Numeric ref (from browser state / find), CSS selector, or omit when using --role/--name/etc.").option("--nth <n>","When <target> is a multi-match CSS selector, pick the nth match (0-based)").description("Focus an element — JSON envelope {focused, target, matches_n}")).action(D(async(q,K,X)=>{if(typeof q.focus!=="function")throw Error("browser focus is not supported by this browser backend");const Q=await $q(q,K,X??{});if(!Q)return;const Y=t(X?.nth);if("error"in Y){console.log(JSON.stringify({error:{code:"usage_error",message:Y.error}},null,2));process.exitCode=J.USAGE_ERROR;return}const{focused:Z,matches_n:N,match_level:U}=await q.focus(Q,Y.opts);console.log(JSON.stringify({focused:Z,target:Q,matches_n:N,match_level:U},null,2))}));E(m(j.command("dblclick")).argument("[target]","Numeric ref (from browser state / find), CSS selector, or omit when using --role/--name/etc.").option("--nth <n>","When <target> is a multi-match CSS selector, pick the nth match (0-based)").description("Double-click element — JSON envelope {dblclicked, target, matches_n}")).action(D(async(q,K,X)=>{if(typeof q.dblClick!=="function")throw Error("browser dblclick is not supported by this browser backend");const Q=await $q(q,K,X??{});if(!Q)return;const Y=t(X?.nth);if("error"in Y){console.log(JSON.stringify({error:{code:"usage_error",message:Y.error}},null,2));process.exitCode=J.USAGE_ERROR;return}const{matches_n:Z,match_level:N}=await q.dblClick(Q,Y.opts);console.log(JSON.stringify({dblclicked:!0,target:Q,matches_n:Z,match_level:N},null,2))}));const XK=async(q,K,X,Q)=>{if(typeof q.setChecked!=="function")throw Error(`browser ${Q?"check":"uncheck"} is not supported by this browser backend`);const Y=await $q(q,K,X);if(!Y)return;const Z=t(X?.nth);if("error"in Z){console.log(JSON.stringify({error:{code:"usage_error",message:Z.error}},null,2));process.exitCode=J.USAGE_ERROR;return}const N=await q.setChecked(Y,Q,Z.opts);console.log(JSON.stringify({checked:N.checked,changed:N.changed,target:Y,matches_n:N.matches_n,match_level:N.match_level,...N.kind?{kind:N.kind}:{}},null,2))};E(m(j.command("check")).argument("[target]","Numeric ref (from browser state / find), CSS selector, or omit when using --role/--name/etc.").option("--nth <n>","When <target> is a multi-match CSS selector, pick the nth match (0-based)").description("Ensure a checkbox/radio/aria-checked control is checked — JSON envelope {checked, changed, target, matches_n}")).action(D(async(q,K,X)=>{await XK(q,K,X??{},!0)}));E(m(j.command("uncheck")).argument("[target]","Numeric ref (from browser state / find), CSS selector, or omit when using --role/--name/etc.").option("--nth <n>","When <target> is a multi-match CSS selector, pick the nth match (0-based)").description("Ensure a checkbox/aria-checked control is unchecked — JSON envelope {checked, changed, target, matches_n}")).action(D(async(q,K,X)=>{await XK(q,K,X??{},!1)}));E(m(j.command("upload")).argument("[targetOrFile]","Numeric ref/CSS target, or first file when using --role/--name/etc.").argument("[files...]","Local file path(s) to attach").option("--nth <n>","When <target> is a multi-match CSS selector, pick the nth match (0-based)").description("Attach local files to a file input — JSON envelope {uploaded, files, file_names, target, matches_n}")).action(D(async(q,K,X,Q)=>{if(typeof q.uploadFiles!=="function")throw Error("browser upload is not supported by this browser backend");const Y=!!Lq(Q??{}),N=await $q(q,Y?void 0:K,Q??{});if(!N)return;const U=t(Q?.nth);if("error"in U){console.log(JSON.stringify({error:{code:"usage_error",message:U.error}},null,2));process.exitCode=J.USAGE_ERROR;return}const G=Y?[K,...Array.isArray(X)?X:[]].filter((F)=>F!==void 0):X,W=dK(G);if("error"in W){console.log(JSON.stringify({error:W.error},null,2));process.exitCode=J.USAGE_ERROR;return}const _=await q.uploadFiles(N,W.files,U.opts);console.log(JSON.stringify(_,null,2))}));E(eq(eq(j.command("drag"),"from"),"to").argument("[source]","Numeric ref/CSS selector to drag from, or omit with --from-role/--from-name/etc.").argument("[target]","Numeric ref/CSS selector to drop onto, or omit with --to-role/--to-name/etc.").option("--from-nth <n>","When <source> is a multi-match CSS selector, pick the nth match (0-based)").option("--to-nth <n>","When <target> is a multi-match CSS selector, pick the nth match (0-based)").description("Drag one element to another — JSON envelope {dragged, source, target, source_matches_n, target_matches_n}")).action(D(async(q,K,X,Q)=>{if(typeof q.drag!=="function")throw Error("browser drag is not supported by this browser backend");const Y=await KK(q,K,Q??{},"from","source");if(!Y)return;const Z=await KK(q,X,Q??{},"to","target");if(!Z)return;const N=QK(Q?.fromNth,"--from-nth");if("error"in N){console.log(JSON.stringify({error:{code:"usage_error",message:N.error}},null,2));process.exitCode=J.USAGE_ERROR;return}const U=QK(Q?.toNth,"--to-nth");if("error"in U){console.log(JSON.stringify({error:{code:"usage_error",message:U.error}},null,2));process.exitCode=J.USAGE_ERROR;return}const G=await q.drag(Y,Z,{from:N.opts,to:U.opts});console.log(JSON.stringify(G,null,2))}));E(m(j.command("fill")).argument("[targetOrText]","Numeric ref/CSS target, or text when using --role/--name/etc.").argument("[text]","Text to set exactly").option("--nth <n>","When <target> is a multi-match CSS selector, pick the nth match (0-based)").description("Set input/textarea/contenteditable text exactly and verify the value — JSON envelope {filled, verified, text, actual}")).action(D(async(q,K,X,Q)=>{const Y=await Cq(q,K,X,Q??{},"text");if(!Y)return;const Z=t(Q?.nth);if("error"in Z){console.log(JSON.stringify({error:{code:"usage_error",message:Z.error}},null,2));process.exitCode=J.USAGE_ERROR;return}const N=await q.fillText(Y.target,Y.value,Z.opts);if(!N.verified)process.exitCode=J.GENERIC_ERROR;console.log(JSON.stringify({filled:N.filled,verified:N.verified,target:Y.target,text:Y.value,actual:N.actual,length:N.length,matches_n:N.matches_n,match_level:N.match_level,...N.mode?{mode:N.mode}:{}},null,2))}));E(m(j.command("select")).argument("[targetOrOption]","Numeric ref/CSS target, or option text when using --role/--name/etc.").argument("[option]","Option text (or value) to select").option("--nth <n>","When <target> is a multi-match CSS selector, pick the nth match (0-based)").description("Select dropdown option — JSON envelope {selected, target, matches_n}")).action(D(async(q,K,X,Q)=>{const Y=await Cq(q,K,X,Q??{},"option");if(!Y)return;const Z=t(Q?.nth);if("error"in Z){console.log(JSON.stringify({error:{code:"usage_error",message:Z.error}},null,2));process.exitCode=J.USAGE_ERROR;return}const{matches_n:N,match_level:U}=await R(q,Y.target,Z.opts),G=await q.evaluate(RQ(Y.value));if(G?.error){console.log(JSON.stringify({error:{code:G.error==="Not a <select>"?"not_a_select":"option_not_found",message:G.error,...G.available&&{available:G.available},matches_n:N}},null,2));process.exitCode=J.GENERIC_ERROR;return}console.log(JSON.stringify({selected:G?.selected??Y.value,target:Y.target,matches_n:N,match_level:U},null,2))}));E(j.command("keys").argument("<key>","Key to press (Enter, Escape, Tab, Control+a)")).description("Press keyboard key").action(D(async(q,K)=>{await q.pressKey(K);console.log(`Pressed: ${K}`)}));const YK=j.command("dialog").description("Handle a blocking JavaScript alert/confirm/prompt dialog");E(YK.command("accept").option("--text <text>","Prompt text to submit for prompt() dialogs").description("Accept the currently open JavaScript dialog")).action(D(async(q,K)=>{if(!q.handleJavaScriptDialog)throw Error("This browser session does not support JavaScript dialog handling");try{await q.handleJavaScriptDialog(!0,K?.text)}catch(X){if(h(X).toLowerCase().includes("no dialog")){console.log(JSON.stringify({error:{code:"no_javascript_dialog",message:"No JavaScript dialog is currently open."}},null,2));process.exitCode=J.USAGE_ERROR;return}throw X}console.log(JSON.stringify({handled:!0,action:"accept",...K?.text!==void 0&&{text:K.text}},null,2))}));E(YK.command("dismiss").description("Dismiss the currently open JavaScript dialog")).action(D(async(q)=>{if(!q.handleJavaScriptDialog)throw Error("This browser session does not support JavaScript dialog handling");try{await q.handleJavaScriptDialog(!1)}catch(K){if(h(K).toLowerCase().includes("no dialog")){console.log(JSON.stringify({error:{code:"no_javascript_dialog",message:"No JavaScript dialog is currently open."}},null,2));process.exitCode=J.USAGE_ERROR;return}throw K}console.log(JSON.stringify({handled:!0,action:"dismiss"},null,2))}));E(j.command("wait")).argument("<type>","selector, text, time, xhr, or download").argument("[value]","CSS selector, text string, seconds, XHR URL regex, or download filename/URL pattern").option("--timeout <ms>","Timeout in milliseconds","10000").description('Wait for selector, text, time, matching XHR, or browser download (e.g. wait selector ".loaded", wait text "Success", wait time 3, wait xhr "/api/search", wait download receipt.pdf)').action(D(async(q,K,X,Q)=>{const Y=parseInt(Q.timeout,10);if(K==="time"){const Z=parseFloat(X??"2");await q.wait(Z);console.log(`Waited ${Z}s`)}else if(K==="selector"){if(!X){console.error("Missing CSS selector");process.exitCode=J.USAGE_ERROR;return}await q.wait({selector:X,timeout:Y/1000});console.log(`Element "${X}" appeared`)}else if(K==="text"){if(!X){console.error("Missing text");process.exitCode=J.USAGE_ERROR;return}await q.wait({text:X,timeout:Y/1000});console.log(`Text "${X}" appeared`)}else if(K==="xhr"){if(!X){console.error("Missing XHR URL regex");process.exitCode=J.USAGE_ERROR;return}let Z;try{Z=new RegExp(X)}catch(_){console.error(`Invalid regex "${X}": ${_ instanceof Error?_.message:String(_)}`);process.exitCode=J.USAGE_ERROR;return}if(!(await q.startNetworkCapture?.()??!1))try{await q.evaluate(Kq)}catch{}await Wq(q);const U=Date.now()+Y,G=400;let W=null;while(Date.now()<U&&!W){W=(await Wq(q)).find((F)=>Z.test(F.url))??null;if(!W)await new Promise((F)=>setTimeout(F,G))}if(!W){console.log(JSON.stringify({error:{code:"xhr_not_seen",message:`No captured XHR matched /${X}/ within ${Y}ms`,hint:"Check the pattern against `browser network` output; the endpoint may not have fired yet, or capture is disabled."}},null,2));process.exitCode=J.GENERIC_ERROR;return}console.log(JSON.stringify({matched:{url:W.url,status:W.status,contentType:W.ct}},null,2))}else if(K==="download"){if(typeof q.waitForDownload!=="function"){console.log(JSON.stringify({error:{code:"download_wait_unavailable",message:"The active browser backend does not support download lifecycle waits.",hint:"Use the Browser Bridge extension version 1.0.8 or newer, then retry the command."}},null,2));process.exitCode=J.GENERIC_ERROR;return}const Z=await q.waitForDownload(String(X??""),Y);if(!Z.downloaded){const N=Z.state==="interrupted"&&Z.id!==void 0?"download_failed":"download_not_seen";console.log(JSON.stringify({error:{code:N,message:Z.error??`No download matched "${X??"*"}" within ${Y}ms`,hint:"Check the pattern against the expected filename or URL; use a longer --timeout if the download starts slowly."},download:Z},null,2));process.exitCode=J.GENERIC_ERROR;return}console.log(JSON.stringify(Z,null,2))}else{console.error(`Unknown wait type "${K}". Use: selector, text, time, xhr, or download`);process.exitCode=J.USAGE_ERROR}}));E(j.command("eval").argument("<js>","JavaScript code").option("--frame <index>",'Cross-origin iframe index from "browser frames"').description("Execute JS in page context, return result")).action(D(async(q,K,X)=>{let Q;if(X.frame!==void 0){const Y=Number.parseInt(X.frame,10);if(!Number.isInteger(Y)||Y<0){console.error(`Invalid frame index "${X.frame}". Use a 0-based index from "browser frames".`);process.exitCode=J.USAGE_ERROR;return}if(!q.evaluateInFrame)throw Error("This browser session does not support frame-targeted evaluation");Q=await q.evaluateInFrame(K,Y)}else Q=await q.evaluate(K);if(typeof Q==="string")console.log(Q);else console.log(JSON.stringify(Q,null,2))}));E(j.command("extract").option("--selector <css>","CSS selector scope; defaults to <main>/<article>/<body>").option("--chunk-size <chars>","Target chunk size in chars","20000").option("--start <char>","Start offset (use next_start_char from a previous extract)","0").description("Extract page content as markdown, paragraph-aware chunks for long pages")).action(D(async(q,K)=>{const X=String(K.chunkSize??"20000");if(!/^\d+$/.test(X)||Number.parseInt(X,10)<=0){console.log(JSON.stringify({error:{code:"invalid_chunk_size",message:`--chunk-size must be a positive integer, got "${K.chunkSize}"`}},null,2));process.exitCode=J.USAGE_ERROR;return}const Q=String(K.start??"0");if(!/^\d+$/.test(Q)){console.log(JSON.stringify({error:{code:"invalid_start",message:`--start must be a non-negative integer, got "${K.start}"`}},null,2));process.exitCode=J.USAGE_ERROR;return}const Y=Number.parseInt(X,10),Z=Number.parseInt(Q,10),N=typeof K.selector==="string"&&K.selector.length>0?K.selector:null,U=CQ(N),G=await q.evaluate(U);if(!G){console.log(JSON.stringify({error:{code:"extract_failed",message:"Page returned no root element."}},null,2));process.exitCode=J.USAGE_ERROR;return}if("invalidSelector"in G){console.log(JSON.stringify({error:{code:"invalid_selector",message:`Selector "${N}" is not a valid CSS selector: ${G.reason}`}},null,2));process.exitCode=J.USAGE_ERROR;return}if("notFound"in G){console.log(JSON.stringify({error:{code:"selector_not_found",message:N?`Selector "${N}" matched 0 elements.`:"Page has no body/main/article element."}},null,2));process.exitCode=J.USAGE_ERROR;return}const W=TQ({html:G.html,url:G.url,title:G.title,selector:N,start:Z,chunkSize:Y});console.log(JSON.stringify(W,null,2))}));E(j.command("network")).option("--detail <key>","Emit full body for the entry with this key").option("--all","Include static resources (js/css/images/telemetry)").option("--raw","Emit full bodies for every entry (skip shape preview)").option("--filter <fields>","Comma-separated field names; keep only entries whose body shape has ALL names as path segments").option("--since <duration>","Only include entries from the last duration (for example: 30s, 2m)").option("--until <duration>","Only include entries older than the duration from now").option("--follow","Continuously print new matching entries as JSON lines",!1).option("--failed","Only include failed HTTP requests (status 0 or >= 400)",!1).option("--max-body <chars>","With --detail: cap the emitted body at N chars (0 = unlimited, default)","0").option("--ttl <ms>","Cache TTL in ms for --detail lookups",String(OK)).description("Capture network requests as shape previews; retrieve full bodies by key").action(D(async(q,K)=>{const X=xK(K.ttl,"ttl",OK),Q=tq(q),Y=typeof K.detail==="string"&&K.detail.length>0,Z=typeof K.filter==="string",N=kq(K.since,"since"),U=kq(K.until,"until");if(N&&typeof N==="object"){p("invalid_since",N.error);return}if(U&&typeof U==="object"){p("invalid_until",U.error);return}if(Y&&Z){p("invalid_args","--filter and --detail cannot be used together (one narrows a list, the other fetches a specific entry).");return}let G=null;if(Z){const L=MQ(K.filter);if("reason"in L){p("invalid_filter",L.reason);return}G=L.fields}if(Y&&K.follow){p("invalid_args","--follow cannot be used with --detail.");return}if(Y){const L=kQ(Q,{ttlMs:X});if(L.status==="missing"){p("cache_missing",`No cached capture. Run "browser network" first (in session "${Q}").`);return}if(L.status==="expired"){p("cache_expired",`Cache is stale (age ${L.ageMs}ms > ttl ${X}ms). Re-run "browser network" to refresh.`);return}if(L.status==="corrupt"||!L.file){p("cache_corrupt",'Cache file is malformed; re-run "browser network" to regenerate.');return}const O=OQ(L.file,K.detail);if(!O){p("key_not_found",`Key "${K.detail}" not in cache.`,{available_keys:L.file.entries.map((a)=>a.key)});return}const s=String(K.maxBody??"0");if(!/^\d+$/.test(s)){p("invalid_max_body",`--max-body must be a non-negative integer, got "${K.maxBody}"`);return}const A=Number.parseInt(s,10);let b=O.body,x=!1;if(A>0&&typeof O.body==="string"&&O.body.length>A){b=O.body.slice(0,A);x=!0}const u=O.body_truncated===!0,c={key:O.key,url:O.url,method:O.method,status:O.status,ct:O.ct,size:O.size,...typeof O.timestamp==="number"?{timestamp:Gq(O.timestamp)}:{},shape:EK(O.body),body:b};if(u||x){c.body_truncated=!0;c.body_full_size=O.body_full_size??O.size;c.body_truncation_reason=u?"capture-limit":"max-body"}console.log(JSON.stringify(c,null,2));return}if(K.follow){if(!await q.startNetworkCapture?.())try{await q.evaluate(Kq)}catch{}while(!0){const L=await Wq(q).catch((A)=>{p("capture_failed",`Could not read network capture: ${A.message}`);return[]});let O=K.all?L:SK(L);O=iq(O,{sinceMs:N,untilMs:U});if(K.failed)O=O.filter((A)=>A.status===0||A.status>=400);const s=PK(O);for(const A of s)console.log(JSON.stringify({key:A.key,timestamp:Gq(A.timestamp),method:A.method,status:A.status,url:A.url,ct:A.ct,size:A.size,...A.bodyTruncated?{body_truncated:!0}:{}}));await new Promise((A)=>setTimeout(A,MK))}}let W;try{W=await Wq(q)}catch(L){p("capture_failed",`Could not read network capture: ${L.message}`);return}let _=K.all?W:SK(W);_=iq(_,{sinceMs:N,untilMs:U});if(K.failed)_=_.filter((L)=>L.status===0||L.status>=400);const F=W.length-_.length,f=PK(_).map((L)=>({key:L.key,url:L.url,method:L.method,status:L.status,size:L.size,ct:L.ct,body:L.body,...typeof L.timestamp==="number"?{timestamp:L.timestamp}:{},...L.bodyTruncated?{body_truncated:!0}:{},...L.bodyTruncated&&typeof L.bodyFullSize==="number"?{body_full_size:L.bodyFullSize}:{}}));let y=null;try{IQ(Q,f)}catch(L){y=`Could not persist capture cache: ${L.message}. --detail lookups may miss this capture.`}const w=f.map((L)=>({entry:L,shape:EK(L.body)})),T=G?w.filter((L)=>SQ(L.shape,G)):w,d=G?w.length-T.length:0,g={session:Q,captured_at:new Date().toISOString(),count:T.length,filtered_out:F};if(G){g.filter=G;g.filter_dropped=d}if(y)g.cache_warning=y;const Qq=T.filter((L)=>L.entry.body_truncated).length;if(Qq>0){g.body_truncated_count=Qq;g.body_truncated_hint="Some bodies exceeded the capture limit; their `shape` reflects only the captured prefix."}if(K.raw)g.entries=T.map((L)=>({...L.entry,...typeof L.entry.timestamp==="number"?{timestamp:Gq(L.entry.timestamp)}:{}}));else{g.entries=T.map((L)=>({key:L.entry.key,method:L.entry.method,...typeof L.entry.timestamp==="number"?{timestamp:Gq(L.entry.timestamp)}:{},status:L.entry.status,url:L.entry.url,ct:L.entry.ct,size:L.entry.size,shape:L.shape,...L.entry.body_truncated?{body_truncated:!0}:{}}));g.detail_hint='Run "browser network --detail <key>" for full body.'}console.log(JSON.stringify(g,null,2))}));j.command("init").argument("<name>","Adapter name in site/command format (e.g. hn/top)").description("Generate adapter scaffold in ~/.ppcli/clis/").action(async(q)=>{try{const K=q.split("/");if(K.length!==2||!K[0]||!K[1]){console.error("Name must be site/command format (e.g. hn/top)");process.exitCode=J.USAGE_ERROR;return}const[X,Q]=K;if(!/^[a-zA-Z0-9_-]+$/.test(X)||!/^[a-zA-Z0-9_-]+$/.test(Q)){console.error("Name parts must be alphanumeric/dash/underscore only");process.exitCode=J.USAGE_ERROR;return}const Y=await import("node:os"),Z=await import("node:fs"),N=await import("node:path"),U=N.join(Y.homedir(),".ppcli","clis",X),G=N.join(U,`${Q}.js`);if(Z.existsSync(G)){console.log(`Adapter already exists: ${G}`);return}const _=`import { cli, Strategy } from '@jackwener/opencli/registry';
97
97
 
98
98
  cli({
99
99
  site: '${X}',
@@ -1 +1 @@
1
- import*as Q from"node:fs";import*as Y from"node:path";import*as V from"node:os";import{fileURLToPath as M}from"node:url";import{spawnSync as U,execFileSync as L}from"node:child_process";import $ from"js-yaml";import{log as K}from"./logger.js";import{EXIT_CODES as v,getErrorMessage as W}from"./errors.js";import{enforceRateLimit as j}from"./rate-limit.js";const O=Y.dirname(M(import.meta.url));function k(){const q=V.homedir();return Y.join(q,".ppcli","external-clis.yaml")}let Z=null;export function loadExternalClis(){if(Z)return Z;const q=new Map,z=Y.resolve(O,"external-clis.yaml");try{if(Q.existsSync(z)){const H=Q.readFileSync(z,"utf8"),A=$.load(H)||[];for(const J of A)q.set(J.name,J)}}catch(H){K.warn(`Failed to parse built-in external-clis.yaml: ${W(H)}`)}const G=k();try{if(Q.existsSync(G)){const H=Q.readFileSync(G,"utf8"),A=$.load(H)||[];for(const J of A)q.set(J.name,J)}}catch(H){K.warn(`Failed to parse user external-clis.yaml: ${W(H)}`)}Z=Array.from(q.values()).sort((H,A)=>H.name.localeCompare(A.name));return Z}export function isBinaryInstalled(q){try{const z=V.platform()==="win32";L(z?"where":"which",[q],{stdio:"ignore"});return!0}catch{return!1}}export function getInstallCmd(q){if(!q)return null;const z=V.platform();if(z==="darwin"&&q.mac)return q.mac;if(z==="linux"&&q.linux)return q.linux;if(z==="win32"&&q.windows)return q.windows;if(q.default)return q.default;return null}export function formatExternalCliLabel(q){return q.package&&q.package!==q.name?`${q.name}(${q.package})`:q.name}export function parseCommand(q){if(/&&|\|\|?|;|[><`$#\n\r]|\$\(/.test(q))throw Error(`Install command contains unsafe shell operators and cannot be executed securely: "${q}". Please install the tool manually.`);const G=[],H=/(?:"([^"]*)")|(?:'([^']*)')|(\S+)/g;let A;while((A=H.exec(q))!==null)G.push(A[1]??A[2]??A[3]);if(G.length===0)throw Error("Install command is empty.");const[J,...N]=G;return{binary:J,args:N}}function X(q,z){const G=z instanceof Error?z.code:void 0;return V.platform()==="win32"&&!Y.extname(q)&&G==="ENOENT"}function R(q){const{binary:z,args:G}=parseCommand(q);try{L(z,G,{stdio:"inherit"})}catch(H){if(X(z,H)){L(`${z}.cmd`,G,{stdio:"inherit"});return}throw H}}export function installExternalCli(q){if(!q.install){K.error(`No auto-install command configured for '${q.name}'.`);K.info(`Please install '${q.binary}' manually.`);return!1}const z=getInstallCmd(q.install);if(!z){K.error(`No install command for your platform (${V.platform()}) for '${q.name}'.`);if(q.homepage)K.info(`See: ${q.homepage}`);return!1}K.info(`'${q.name}' is not installed. Auto-installing...`);K.verbose(`$ ${z}`);try{R(z);K.success(`Installed '${q.name}' successfully.`);return!0}catch(G){K.error(`Failed to install '${q.name}': ${W(G)}`);return!1}}export function executeExternalCli(q,z,G){const A=(G??loadExternalClis()).find((N)=>N.name===q);if(!A)throw Error(`External CLI '${q}' not found in registry.`);j(q);if(!isBinaryInstalled(A.binary)){if(!installExternalCli(A)){process.exitCode=v.SERVICE_UNAVAIL;return}}const J=U(A.binary,z,{stdio:"inherit"});if(J.error){K.error(`Failed to execute '${A.binary}': ${J.error.message}`);process.exitCode=v.GENERIC_ERROR;return}if(J.status!==null)process.exitCode=J.status;else if(J.signal){const N=J.signal;process.exitCode=N==="SIGINT"?v.INTERRUPTED:V.constants.signals[N]?128+V.constants.signals[N]:v.GENERIC_ERROR}}export function registerExternalCli(q,z){const G=k(),H=Y.dirname(G);if(!Q.existsSync(H))Q.mkdirSync(H,{recursive:!0});let A=[];if(Q.existsSync(G))try{const B=Q.readFileSync(G,"utf8");A=$.load(B)||[]}catch{}const J=A.findIndex((B)=>B.name===q),N={name:q,binary:z?.binary||q};if(z?.description)N.description=z.description;if(z?.install)N.install={default:z.install};if(J>=0){A[J]={...A[J],...N};K.success(`Updated '${q}' in user registry.`)}else{A.push(N);K.success(`Registered '${q}' in user registry.`)}const F=$.dump(A,{indent:2,sortKeys:!0});Q.writeFileSync(G,F,"utf8");Z=null;K.verbose(G)}
1
+ import*as V from"node:fs";import*as Z from"node:path";import*as Y from"node:os";import{fileURLToPath as F}from"node:url";import{spawnSync as M,execFileSync as L}from"node:child_process";import q from"js-yaml";import{log as N}from"./logger.js";import{EXIT_CODES as v,getErrorMessage as W}from"./errors.js";import{enforceRateLimit as O}from"./rate-limit.js";const X=Z.dirname(F(import.meta.url));function U(){const z=Y.homedir();return Z.join(z,".ppcli","external-clis.yaml")}let $=null;export function loadExternalClis(){if($)return $;const z=new Map,A=Z.resolve(X,"external-clis.yaml");try{if(V.existsSync(A)){const J=V.readFileSync(A,"utf8"),G=q.load(J)||[];for(const K of G)z.set(K.name,K)}}catch(J){N.warn(`Failed to parse built-in external-clis.yaml: ${W(J)}`)}const H=U();try{if(V.existsSync(H)){const J=V.readFileSync(H,"utf8"),G=q.load(J)||[];for(const K of G)z.set(K.name,K)}}catch(J){N.warn(`Failed to parse user external-clis.yaml: ${W(J)}`)}$=Array.from(z.values()).sort((J,G)=>J.name.localeCompare(G.name));return $}export function isBinaryInstalled(z){try{const A=Y.platform()==="win32";L(A?"where":"which",[z],{stdio:"ignore"});return!0}catch{return!1}}export function getInstallCmd(z){if(!z)return null;const A=Y.platform();if(A==="darwin"&&z.mac)return z.mac;if(A==="linux"&&z.linux)return z.linux;if(A==="win32"&&z.windows)return z.windows;if(z.default)return z.default;return null}export function formatExternalCliLabel(z){return z.package&&z.package!==z.name?`${z.name}(${z.package})`:z.name}export function parseCommand(z){if(/&&|\|\|?|;|[><`$#\n\r]|\$\(/.test(z))throw Error(`Install command contains unsafe shell operators and cannot be executed securely: "${z}". Please install the tool manually.`);const H=[],J=/(?:"([^"]*)")|(?:'([^']*)')|(\S+)/g;let G;while((G=J.exec(z))!==null)H.push(G[1]??G[2]??G[3]);if(H.length===0)throw Error("Install command is empty.");const[K,...Q]=H;return{binary:K,args:Q}}function R(z,A){const H=A instanceof Error?A.code:void 0;return Y.platform()==="win32"&&!Z.extname(z)&&H==="ENOENT"}function T(z){const{binary:A,args:H}=parseCommand(z);try{L(A,H,{stdio:"inherit"})}catch(J){if(R(A,J)){L(`${A}.cmd`,H,{stdio:"inherit"});return}throw J}}export function installExternalCli(z){if(!z.install){N.error(`No auto-install command configured for '${z.name}'.`);N.info(`Please install '${z.binary}' manually.`);return!1}const A=getInstallCmd(z.install);if(!A){N.error(`No install command for your platform (${Y.platform()}) for '${z.name}'.`);if(z.homepage)N.info(`See: ${z.homepage}`);return!1}N.info(`'${z.name}' is not installed. Auto-installing...`);N.verbose(`$ ${A}`);try{T(A);N.success(`Installed '${z.name}' successfully.`);return!0}catch(H){N.error(`Failed to install '${z.name}': ${W(H)}`);return!1}}export function executeExternalCli(z,A,H){const G=(H??loadExternalClis()).find((Q)=>Q.name===z);if(!G)throw Error(`External CLI '${z}' not found in registry.`);O(z);if(!isBinaryInstalled(G.binary)){if(!installExternalCli(G)){process.exitCode=v.SERVICE_UNAVAIL;return}}const K=k(G.binary,A);if(K.error){N.error(`Failed to execute '${G.binary}': ${K.error.message}`);process.exitCode=v.GENERIC_ERROR;return}if(K.status!==null)process.exitCode=K.status;else if(K.signal){const Q=K.signal;process.exitCode=Q==="SIGINT"?v.INTERRUPTED:Y.constants.signals[Q]?128+Y.constants.signals[Q]:v.GENERIC_ERROR}}function D(z){if(z!==""&&!/[\s"^&|<>%()]/.test(z))return z;return`"${z.replace(/"/g,'""')}"`}function k(z,A){const H=M(z,A,{stdio:"inherit"}),J=H.error?.code;if(Y.platform()==="win32"&&(J==="EINVAL"||J==="ENOENT")){const G=[z,...A].map(D).join(" ");return M(G,{stdio:"inherit",shell:!0})}return H}export function registerExternalCli(z,A){const H=U(),J=Z.dirname(H);if(!V.existsSync(J))V.mkdirSync(J,{recursive:!0});let G=[];if(V.existsSync(H))try{const B=V.readFileSync(H,"utf8");G=q.load(B)||[]}catch{}const K=G.findIndex((B)=>B.name===z),Q={name:z,binary:A?.binary||z};if(A?.description)Q.description=A.description;if(A?.install)Q.install={default:A.install};if(K>=0){G[K]={...G[K],...Q};N.success(`Updated '${z}' in user registry.`)}else{G.push(Q);N.success(`Registered '${z}' in user registry.`)}const j=q.dump(G,{indent:2,sortKeys:!0});V.writeFileSync(H,j,"utf8");$=null;N.verbose(H)}
@@ -87,6 +87,9 @@ export interface IPage {
87
87
  }): Promise<{
88
88
  matches_n: number;
89
89
  match_level: 'exact' | 'stable' | 'reidentified';
90
+ click_method?: 'cdp' | 'js' | 'ax';
91
+ hit?: 'target' | 'ancestor' | 'other' | 'none';
92
+ retargeted?: boolean;
90
93
  }>;
91
94
  dblClick?(ref: string, opts?: {
92
95
  nth?: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "publishport-opencli",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "provenance": false