lazycodex-ai 5.1.33 → 5.1.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.ja.md +1 -1
  2. package/README.ko.md +1 -1
  3. package/README.md +1 -1
  4. package/README.ru.md +1 -1
  5. package/README.zh-cn.md +1 -1
  6. package/dist/cli/account-keys/commands.d.ts +9 -0
  7. package/dist/cli/account-keys/keyring.d.ts +16 -0
  8. package/dist/cli/account-keys/keystore.d.ts +34 -0
  9. package/dist/cli/index.js +91493 -90606
  10. package/dist/cli/service-auth/accounts-closed.fixture.d.ts +1 -0
  11. package/dist/cli/service-auth/commands.d.ts +4 -1
  12. package/dist/cli/worktree-sweep/types.d.ts +3 -1
  13. package/dist/cli-node/index.js +91583 -90696
  14. package/package.json +1 -1
  15. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  16. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +1 -0
  17. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  18. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  19. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  20. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  21. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  22. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  23. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  24. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  25. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
  26. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  27. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  28. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  29. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  30. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  31. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  32. package/packages/omo-codex/plugin/components/telemetry/dist/cli.js +2 -1
  33. package/packages/omo-codex/plugin/components/telemetry/dist/posthog.js +2 -1
  34. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  35. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  36. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  37. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  38. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
  39. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  40. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
  41. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  42. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  43. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  44. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  45. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  46. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  47. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  48. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  49. package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
  50. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  51. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  52. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  53. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  54. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  55. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  56. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  57. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  58. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  59. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  60. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  61. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  62. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  63. package/packages/omo-codex/plugin/package-lock.json +12 -12
  64. package/packages/omo-codex/plugin/package.json +1 -1
  65. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/index.js +6 -5
  66. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +3 -3
  67. package/packages/omo-codex/plugin/skills/ulw-research/AGENTS.md +3 -2
  68. package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +3 -1
  69. package/packages/omo-codex/plugin/skills/ulw-research/references/deliverable-phase.md +1 -1
  70. package/packages/omo-codex/plugin/skills/ulw-research/references/report-gates.md +2 -0
  71. package/packages/omo-codex/plugin/skills/ulw-research/references/report-visualization.md +126 -0
  72. package/packages/omo-codex/plugin/skills/ulw-research/scripts/contracts.mjs +3 -1
  73. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-figures.mjs +54 -6
  74. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-static.mjs +2 -2
  75. package/packages/omo-codex/scripts/install-dist/install-local.mjs +51 -3
  76. package/packages/shared-skills/skills/browser/runtime/omowright/index.js +6 -5
  77. package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +3 -3
  78. package/packages/shared-skills/skills/ulw-research/AGENTS.md +3 -2
  79. package/packages/shared-skills/skills/ulw-research/SKILL.md +3 -1
  80. package/packages/shared-skills/skills/ulw-research/references/deliverable-phase.md +1 -1
  81. package/packages/shared-skills/skills/ulw-research/references/report-gates.md +2 -0
  82. package/packages/shared-skills/skills/ulw-research/references/report-visualization.md +126 -0
  83. package/packages/shared-skills/skills/ulw-research/scripts/contracts.mjs +3 -1
  84. package/packages/shared-skills/skills/ulw-research/scripts/contracts.test.ts +2 -0
  85. package/packages/shared-skills/skills/ulw-research/scripts/gates-figures.mjs +54 -6
  86. package/packages/shared-skills/skills/ulw-research/scripts/gates-static.mjs +2 -2
  87. package/packages/shared-skills/skills/ulw-research/scripts/gates-static.test.ts +69 -3
  88. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/bad-report.html +4 -0
@@ -1481,7 +1481,7 @@ Closed tab: ${Vn.data.title??`Unknown`} (${Vn.url})`,{kind:`tab_lifecycle`,mode:
1481
1481
  Viewport fallback also failed: ${formatScreenshotError(xn)}`)}}let Zn=Buffer.from(Yn,`base64`);if(xn.path)if(xn.path.startsWith(`/tmp`)){let jn=this.browser.resolvePath(`tmp`),Vn=path.join(jn,path.basename(xn.path));await mkdir(path.dirname(Vn),{recursive:true}),await writeFile(Vn,Zn),replPrint(`[WARN] Use ${jn} instead of /tmp. Saved it to ${Vn}`)}else{let jn=this.browser.resolvePath(xn.path);await mkdir(path.dirname(jn),{recursive:true}),await writeFile(jn,Zn)}return Zn}async#y(){return await this.evaluate(`window.devicePixelRatio`).catch(()=>1)}async#b(xn,jn,Vn){try{return await this._sendToTarget(`Page.captureScreenshot`,xn,{timeoutMs:jn})}catch(xn){throw rewriteCaptureScreenshotError(xn,jn,Vn)}}async snapshot(xn={}){return await takeSnapshot(this.cdp,this.frameManager,xn)}frames(){return this.frameManager.collectDescendantFrames(this.frameManager.mainFrameId).map(({frameId:xn})=>new OmOFrame(this,xn))}mainFrame(){let xn=this.frameManager.getFrame(this.frameManager.mainFrameId);if(!xn)throw Error(`Main frame is not available`);return new OmOFrame(this,xn.frameId)}async bringToFront(){this.browser.page=this,replPrint(`page is now [${await this.title().catch(()=>``)}](${this.url()})`)}async openPopupUrl(xn){await this.browser.openPopupTab(xn,this.targetId)}get console(){return this.#t??=new PageConsole(this)}video(){return this.#w()}viewportSize(){return this.#i?{...this.#i}:null}async refreshViewportSize(){let xn=await this.evaluate(()=>({width:Math.round(globalThis.visualViewport?.width??globalThis.innerWidth??0),height:Math.round(globalThis.visualViewport?.height??globalThis.innerHeight??0)}));if(xn.width<=0||xn.height<=0)throw Error(`Measured viewport size is invalid: ${JSON.stringify(xn)}`);return this.#i=xn,xn}setCachedViewportSize(xn){this.#i=xn?{...xn}:null}async pdf(xn={}){let jn=xn.path?await this.browser.ensurePath(xn.path,`write`,`page.pdf`):undefined,Vn={...xn,height:formatPdfLength(xn.height),margin:xn.margin?{bottom:formatPdfLength(xn.margin.bottom),left:formatPdfLength(xn.margin.left),right:formatPdfLength(xn.margin.right),top:formatPdfLength(xn.margin.top)}:undefined,printBackground:xn.printBackground??true,transferMode:`ReturnAsBase64`,width:formatPdfLength(xn.width)},{data:qn}=await this._sendToTarget(`Page.printToPDF`,Vn),Jn=Buffer.from(qn,`base64`);return jn&&(await mkdir(path.dirname(jn),{recursive:true}).catch(()=>{}),await writeFile(jn,Jn)),Jn}async close(xn={}){if(await this.#w().stop().catch(()=>{}),xn.runBeforeUnload){let jn=await this.resolveSessionId(this.#n);await this.cdp.send(`Page.close`,undefined,jn).catch(async()=>{await this.browser.closeTab(this,xn)});return}await this.browser.closeTab(this,xn)}async#x(){let{result:xn,exceptionDetails:jn}=await this._sendToTarget(`Runtime.evaluate`,{expression:`globalThis`,serializationOptions:{serialization:`idOnly`}});if(jn)throw Error(formatRuntimeException(jn,`Failed to resolve main world`));if(!xn.objectId)throw Error(`Main world object is not available`);return xn.objectId}async#S(xn){await this._sendToTarget(`Network.enable`).catch(()=>{});let jn=await this.resolveSessionId(this.#n);await new Promise((Vn,qn)=>{let Jn=new Set,Yn=null,Zn=null,Qn=()=>{ti(),ri(),ii(),Yn&&=(clearTimeout(Yn),null),Zn&&=(clearTimeout(Zn),null)},ei=()=>{Yn&&clearTimeout(Yn),Jn.size===0&&(Yn=setTimeout(()=>{Qn(),Vn()},NETWORK_IDLE_MS))},ti=this.cdp.on(`Network.requestWillBeSent`,(xn,Vn)=>{Vn.sessionId===jn&&(xn.request.url.startsWith(`data:`)||Jn.add(xn.requestId))}),ni=(xn)=>{Jn.delete(xn),ei()},ri=this.cdp.on(`Network.loadingFinished`,(xn,Vn)=>{Vn.sessionId===jn&&ni(xn.requestId)}),ii=this.cdp.on(`Network.loadingFailed`,(xn,Vn)=>{Vn.sessionId===jn&&ni(xn.requestId)});Zn=setTimeout(()=>{Qn(),qn(Error(`Timed out waiting for load state "networkidle"`))},xn),ei()})}async#C(){let xn=await this.resolveSessionId(this.#n);await new Promise((jn,Vn)=>{let qn=setTimeout(()=>{Jn(),Vn(Error(`Timed out waiting for navigation`))},DEFAULT_WAIT_TIMEOUT_MS),Jn=this.cdp.on(`Page.frameNavigated`,(Vn,Yn)=>{Yn.sessionId===xn&&(Vn.frame.parentId||(clearTimeout(qn),Jn(),jn()))})})}async _sendToTarget(xn,jn,Vn={}){if(this.#s)throw Error(`Page ${this.targetId} is disposed`);let{preferredSessionId:qn,...Jn}=Vn;if(qn!==undefined){if(!this.frameManager.hasSession(qn))throw Error(`Frame is stale or detached: CDP session ${qn} is unavailable`);return await this.cdp.send(xn,jn,qn,Jn)}let Yn=await this.resolveSessionId(this.#n);return await this.cdp.send(xn,jn,Yn,Jn)}#w(){return this.#o??=new OmOVideo(this),this.#o}}});init_client$6();init_session_manager();init_frame_manager();init_page();class TabRepository{#client;constructor(client){this.#client=client}async listTargets(){return this.#client.discoverTargets()}}class BrowserConnection{#client;#sessions;#pages=new Map;#storageRoot;constructor(client,options={}){this.#client=client;this.#sessions=new SessionManager(client);this.tabs=new TabRepository(client);this.#storageRoot=path2.resolve(options.storageRoot??process.cwd())}async initialize(){await this.#client.ensureConnected();return this}get cdp(){return this.#client}setDialogPolicy(policy){if(typeof this.#client.setDialogPolicy!=="function"){throw new Error("dialog policy is not supported by this transport")}this.#client.setDialogPolicy(policy)}async listTargets(){return this.tabs.listTargets()}async attachPage(targetId){if(this.#pages.has(targetId))return this.#pages.get(targetId).page;const sessionId=await this.#sessions.resolvePageSession(targetId);const frames=new FrameManager(this.#client,targetId,sessionId);await frames.initialize();const entry={frames,page:null};const host=this.#createPageHost(targetId,entry);const page=new OmOPage(this.#client,frames,targetId,sessionId,host);entry.page=page;this.#pages.set(targetId,entry);return page}#createPageHost(targetId,entry){return{daemonSession:{accountId:0,id:"standalone"},extensionBridgeRoute:{},page:null,resolvePageSession:(id,preferred)=>this.#sessions.resolvePageSession(id,preferred),resolvePath:(value)=>path2.resolve(this.#storageRoot,value),ensurePath:async(value)=>{const resolved=path2.resolve(this.#storageRoot,value);await mkdir2(path2.dirname(resolved),{recursive:true});return resolved},registerReadableDownload:(value)=>value,isReadableDownload:()=>false,openPopupTab:async()=>{throw Error("Popup tab control requires the extension bridge")},closeTab:async(_page,options={})=>{if(options.runBeforeUnload){await this.#client.send("Page.close",undefined,await this.#sessions.resolvePageSession(targetId))}else{await this.#client.send("Target.closeTarget",{targetId})}await entry.page?.dispose();await entry.frames.dispose();this.#pages.delete(targetId)}}}async close(){for(const[targetId,entry]of[...this.#pages]){await entry.page.dispose().catch(()=>{});await entry.frames.dispose().catch(()=>{});this.#pages.delete(targetId)}await this.#sessions.dispose();await this.#client.close()}}async function connect(cdpHttpUrl,options={}){if(typeof cdpHttpUrl!=="string"||cdpHttpUrl.length===0){throw new TypeError("connect(cdpHttpUrl) requires a Chromium CDP HTTP URL")}const client=new CdpClient({cdpUrl:cdpHttpUrl,...options.client});return new BrowserConnection(client,options).initialize()}import{spawn}from"node:child_process";function normalizeDialogPolicy(policy){return policy??{accept:true}}function dialogFromOpening(params={}){return{type:params.type,message:params.message,defaultPrompt:params.defaultPrompt??"",url:params.url}}function decisionFromResult(result){if(typeof result==="boolean")return{accept:result};if(result&&typeof result==="object"){const decision={accept:result.accept!==false};if(result.promptText!==undefined)decision.promptText=result.promptText;return decision}return{accept:true}}async function resolveDialogAction(policy,params={}){try{const result=typeof policy==="function"?await policy(dialogFromOpening(params)):policy;return decisionFromResult(result)}catch{return{accept:true}}}init_internal_capability();class Emittery2{#listeners=new Map;on(event,fn){const list=this.#listeners.get(event)??[];list.push(fn);this.#listeners.set(event,list);return()=>{const cur=this.#listeners.get(event)??[];this.#listeners.set(event,cur.filter((f)=>f!==fn))}}async emit(event,data){for(const fn of this.#listeners.get(event)??[])await fn(data)}}var DEFAULT_COMMAND_TIMEOUT_MS2=30000;var DEFAULT_READINESS_TIMEOUT_MS=1e4;var GRACEFUL_CLOSE_TIMEOUT_MS=2000;var STDIO_TAIL_LIMIT=16384;class PipeCdpClient{#child;#writeStream;#readStream;#buffer=Buffer.alloc(0);#seq=1;#pending=new Map;#events=new Emittery2;#connected=false;#commandTimeoutMs;#readinessTimeoutMs;#browserPath;#browserArgs;#spawnOptions;#stdioTail="";#dialogPolicy;transportEvents=new Emittery2;logId;constructor({browserPath,browserArgs=[],spawnOptions={},commandTimeoutMs,readinessTimeoutMs,logId,dialogPolicy}){if(!browserPath)throw new TypeError("PipeCdpClient requires browserPath");this.#browserPath=browserPath;this.#browserArgs=browserArgs;this.#spawnOptions=spawnOptions;this.#commandTimeoutMs=commandTimeoutMs??DEFAULT_COMMAND_TIMEOUT_MS2;this.#readinessTimeoutMs=readinessTimeoutMs??DEFAULT_READINESS_TIMEOUT_MS;this.logId=logId??`cdp-pipe-${Date.now()}`;this.#dialogPolicy=normalizeDialogPolicy(dialogPolicy)}get isConnected(){return this.#connected}get isHeadless(){return this.#browserArgs.some((arg)=>/^--headless(?:=|$)/.test(arg))}get childProcess(){return this.#child}get dialogPolicy(){return this.#dialogPolicy}setDialogPolicy(policy){this.#dialogPolicy=normalizeDialogPolicy(policy)}async ensureConnected(){if(this.#connected)return;const stdio=["ignore","pipe","pipe","pipe","pipe"];this.#child=spawn(this.#browserPath,[...this.#browserArgs,"--remote-debugging-pipe"],{...this.#spawnOptions,stdio});this.#stdioTail="";for(const stream of[this.#child.stdout,this.#child.stderr]){if(!stream)continue;stream.setEncoding("utf8");stream.on("data",(chunk)=>{this.#stdioTail=(this.#stdioTail+chunk).slice(-STDIO_TAIL_LIMIT)});stream.on("error",()=>{})}this.#writeStream=this.#child.stdio[3];this.#readStream=this.#child.stdio[4];this.#readStream.on("data",(chunk)=>this.#onData(chunk));for(const stream of[this.#writeStream,this.#readStream]){stream.on("error",(error)=>this.#failAll(Error(`CDP pipe error: ${error.code??error.message}`)))}this.#child.on("exit",()=>{this.#connected=false;this.#failAll(Error("CDP pipe browser exited"));this.transportEvents.emit("disconnected").catch(()=>{})});this.#connected=true;await this.#awaitReady();await this.send("Target.setDiscoverTargets",{discover:true}).catch(()=>{});await this.transportEvents.emit("connected")}async#awaitReady(){const deadline=Date.now()+this.#readinessTimeoutMs;let lastError;while(Date.now()<deadline){try{await this.send("Browser.getVersion",undefined,undefined,{timeoutMs:1000});return}catch(error){lastError=error;await new Promise((resolve)=>setTimeout(resolve,100))}}const reason=lastError?String(lastError.message??lastError):"no response";const tail=this.#stdioTail.trim();throw Error(`Browser did not become ready over the CDP pipe (${reason})`+(tail?`
1482
1482
  Browser stdio tail:
1483
1483
  ${tail}`:""))}async send(method,params,sessionId,opts={}){if(method==="Target.createTarget"&&opts.capability!==targetCreationCapability)throw Error("Target.createTarget is reserved for createAgentTabs");if(String(method).startsWith("OmO.")){throw Error(`Extension commands are unavailable over pipe transport: ${method}`)}await this.ensureConnected();const id=this.#seq++;const msg={id,method};if(params!==undefined)msg.params=params;if(sessionId)msg.sessionId=sessionId;const{promise,resolve,reject}=Promise.withResolvers();const timeoutMs=opts.timeoutMs??this.#commandTimeoutMs;const timeout=timeoutMs>0?setTimeout(()=>{this.#pending.delete(id);reject(Error(`CDP command timeout: ${method}`))},timeoutMs):undefined;this.#pending.set(id,{method,resolve,reject,timeout});this.#writeStream.write(JSON.stringify(msg)+"\x00");return promise}on(method,fn){return this.#events.on(method,({data})=>fn(data.payload,data.meta))}async discoverTargets(){const{targetInfos}=await this.send("Target.getTargets");return targetInfos.map((t)=>({id:t.targetId,type:t.type,url:t.url??"",title:t.title??"",description:t.description,faviconUrl:t.faviconUrl,parentId:t.openerId}))}async close(){const child=this.#child;this.#child=undefined;if(child&&child.exitCode===null){const exited=new Promise((resolve)=>child.once("exit",resolve));if(this.#connected){await Promise.race([this.send("Browser.close",undefined,undefined,{timeoutMs:GRACEFUL_CLOSE_TIMEOUT_MS}).catch(()=>{}),exited])}this.#connected=false;this.#failAll(Error("CDP pipe client closed"));if(child.exitCode===null){await Promise.race([exited,new Promise((resolve)=>setTimeout(resolve,GRACEFUL_CLOSE_TIMEOUT_MS))])}if(child.exitCode===null){try{child.kill("SIGKILL")}catch{}await exited}return}this.#connected=false;this.#failAll(Error("CDP pipe client closed"))}#onData(chunk){this.#buffer=Buffer.concat([this.#buffer,chunk]);let idx;while((idx=this.#buffer.indexOf(0))!==-1){const raw=this.#buffer.subarray(0,idx).toString("utf8");this.#buffer=this.#buffer.subarray(idx+1);if(raw.length===0)continue;let msg;try{msg=JSON.parse(raw)}catch{continue}this._handleTransportMessage(msg)}}_handleTransportMessage(msg){if(typeof msg.id==="number"){const entry=this.#pending.get(msg.id);if(!entry)return;this.#pending.delete(msg.id);if(entry.timeout)clearTimeout(entry.timeout);if(msg.error){const err=Error(msg.error.message??`CDP command failed: ${entry.method}`);err.code=msg.error.code;err.data=msg.error.data;entry.reject(err)}else{entry.resolve(msg.result)}return}if(typeof msg.method==="string"){const handled=msg.method==="Page.javascriptDialogOpening"&&msg.sessionId?this.#handleJavaScriptDialog(msg).catch(()=>{}):undefined;this.#events.emit(msg.method,{data:{payload:msg.params,meta:{sessionId:msg.sessionId}}}).catch(()=>{});return handled}}async#handleJavaScriptDialog(msg){const params=await resolveDialogAction(this.#dialogPolicy,msg.params);await this.send("Page.handleJavaScriptDialog",params,msg.sessionId)}#failAll(err){for(const entry of this.#pending.values()){if(entry.timeout)clearTimeout(entry.timeout);entry.reject(err)}this.#pending.clear()}}async function connectPipe({browserPath,browserArgs=[],spawnOptions,storageRoot,commandTimeoutMs,readinessTimeoutMs,logId,dialogPolicy}={}){const client=new PipeCdpClient({browserPath,browserArgs,spawnOptions,commandTimeoutMs,readinessTimeoutMs,logId,dialogPolicy});await client.ensureConnected();const connection=new BrowserConnection(client,{storageRoot});await connection.initialize();connection.browserProcess=client.childProcess;connection.newTab=async(url="about:blank")=>{await Promise.resolve().then(() => init_agent_tabs());return(await createAgentTabs(connection).create(url)).page};return connection}import{randomInt}from"node:crypto";import{chmod,mkdir as mkdir3,open as open2,readFile}from"node:fs/promises";import{globSync}from"node:fs";import os from"node:os";import path3 from"node:path";var MIN_FINGERPRINT_SEED=1e4;var MAX_FINGERPRINT_SEED=99999;var METADATA_FILE=".omowright-cloak.json";var DEFAULT_PROFILE_DIR=path3.join(os.homedir(),".local","share","omowright-cloak");function normalizeFingerprintSeed(value){const seed=typeof value==="string"&&value.trim()!==""?Number(value):value;if(!Number.isInteger(seed)||seed<MIN_FINGERPRINT_SEED||seed>MAX_FINGERPRINT_SEED){throw new RangeError(`fingerprint seed must be an integer from ${MIN_FINGERPRINT_SEED} to ${MAX_FINGERPRINT_SEED}`)}return seed}function platformFingerprint(){return process.platform==="darwin"?"macos":"windows"}function identityFlag(value){return/^(--user-data-dir|--fingerprint|--fingerprint-platform)(?:=|$)/.test(value)}function buildCloakBrowserArgs({profileDir,fingerprintSeed,extraArgs=[]}={}){const profile=path3.resolve(profileDir??DEFAULT_PROFILE_DIR);const seed=normalizeFingerprintSeed(fingerprintSeed);const conflicting=extraArgs.filter(identityFlag);if(conflicting.length>0){throw new Error(`extraArgs cannot override fixed profile identity: ${conflicting.join(", ")}`)}return["--no-first-run","--no-default-browser-check","--disable-session-crashed-bubble",`--user-data-dir=${profile}`,`--fingerprint=${seed}`,`--fingerprint-platform=${platformFingerprint()}`,...extraArgs]}async function readMetadata(metadataPath){try{const parsed=JSON.parse(await readFile(metadataPath,"utf8"));return normalizeFingerprintSeed(parsed.fingerprintSeed)}catch(error){if(error?.code==="ENOENT")return null;if(error instanceof SyntaxError){throw new Error(`invalid CloakBrowser profile metadata: ${metadataPath}`)}throw error}}async function writeMetadata(metadataPath,fingerprintSeed){const handle=await open2(metadataPath,"wx",384);try{await handle.writeFile(`${JSON.stringify({fingerprintSeed},null,2)}
1484
- `,"utf8")}finally{await handle.close()}await chmod(metadataPath,384)}async function resolveCloakProfile({profileDir=DEFAULT_PROFILE_DIR,fingerprintSeed,randomSeed=()=>randomInt(MIN_FINGERPRINT_SEED,MAX_FINGERPRINT_SEED+1)}={}){const resolvedProfile=path3.resolve(profileDir);const metadataPath=path3.join(resolvedProfile,METADATA_FILE);await mkdir3(resolvedProfile,{recursive:true,mode:448});await chmod(resolvedProfile,448);const requested=fingerprintSeed===undefined?null:normalizeFingerprintSeed(fingerprintSeed);let stored=await readMetadata(metadataPath);if(stored===null){const candidate=requested??normalizeFingerprintSeed(randomSeed());try{await writeMetadata(metadataPath,candidate);stored=candidate}catch(error){if(error?.code!=="EEXIST")throw error;stored=await readMetadata(metadataPath);if(stored===null){throw new Error(`CloakBrowser profile seed metadata disappeared: ${metadataPath}`)}}}if(requested!==null&&requested!==stored){throw new Error(`fingerprint seed mismatch: profile is fixed at ${stored}, requested ${requested}`)}await chmod(metadataPath,384);return{profileDir:resolvedProfile,metadataPath,fingerprintSeed:stored,browserArgs:buildCloakBrowserArgs({profileDir:resolvedProfile,fingerprintSeed:stored})}}function findCloakBrowserPath({browserRoot=path3.join(os.homedir(),".cloakbrowser")}={}){const candidates=globSync(path3.join(path3.resolve(browserRoot),"chromium-*","Chromium.app","Contents","MacOS","Chromium")).sort();const browserPath=candidates.at(-1);if(!browserPath){throw new Error(`CloakBrowser binary not found under ${path3.resolve(browserRoot)}; set CLOAKBROWSER_BIN`)}return browserPath}async function connectCloakProfile({browserPath=process.env.CLOAKBROWSER_BIN??findCloakBrowserPath(),profileDir=process.env.CLOAK_PROFILE_DIR??DEFAULT_PROFILE_DIR,fingerprintSeed=process.env.CLOAK_FINGERPRINT_SEED,browserArgs=[],storageRoot,...options}={}){const profile=await resolveCloakProfile({profileDir,fingerprintSeed});return connectPipe({browserPath,browserArgs:buildCloakBrowserArgs({profileDir:profile.profileDir,fingerprintSeed:profile.fingerprintSeed,extraArgs:browserArgs}),storageRoot:storageRoot??profile.profileDir,...options})}var SNAPSHOT_PARSE_ERROR="compactSnapshot expected a snapshot() result (JSON string or {tree, refs} object)";function compactSnapshot(snapshot){const obj=typeof snapshot==="string"?JSON.parse(snapshot):snapshot;if(!obj||typeof obj.tree!=="string")throw new TypeError(SNAPSHOT_PARSE_ERROR);return obj.tree}function snapshotTokens(snapshot){const text=typeof snapshot==="string"?snapshot:JSON.stringify(snapshot);return{bytes:Buffer.byteLength(text,"utf8"),estTokens:Math.ceil(Buffer.byteLength(text,"utf8")/4)}}var toolSchemas=[{name:"newTab",description:"Open a tab and return its page. Waits for interactivity.",parameters:{type:"object",properties:{url:{type:"string"}},required:["url"]}},{name:"attachPage",description:"Attach to an existing tab by targetId from listTargets().",parameters:{type:"object",properties:{targetId:{type:"string"}},required:["targetId"]}},{name:"page.goto",description:"Navigate the page; resolves when the page looks interactive.",parameters:{type:"object",properties:{url:{type:"string"}},required:["url"]}},{name:"page.snapshot",description:"PRIMARY page read. Returns a compact a11y tree; interactive elements carry [ref=eN] ids for page.locator. Options: maxDepth, maxChars, interactive, showHidden, selector, ref.",parameters:{type:"object",properties:{options:{type:"object"}}}},{name:"page.locator",description:"Resolve a ref id (e.g. 'e1') or CSS selector to a Locator: click, fill, press, hover, check, selectOption, dragTo, setInputFiles, textContent, isVisible, waitFor.",parameters:{type:"object",properties:{selectorOrRef:{type:"string"}},required:["selectorOrRef"]}},{name:"page.evaluate",description:"Run JS in the page and return the result.",parameters:{type:"object",properties:{expression:{type:"string"}},required:["expression"]}},{name:"page.screenshot",description:"Capture page image bytes (path, fullPage, clip, type, quality).",parameters:{type:"object",properties:{options:{type:"object"}}}},{name:"page.pdf",description:"Print the page to PDF bytes (path, format, margin, printBackground).",parameters:{type:"object",properties:{options:{type:"object"}}}},{name:"compactSnapshot",description:"Pass page.snapshot() output through this before reading: returns the raw tree without the refs map (refs resolve in-page; the map is ~54% of bytes on real pages).",parameters:{type:"object",properties:{snapshot:{}},required:["snapshot"]}},{name:"createCua",description:"Coordinate fallback for UI that refs cannot target: click/doubleClick/drag/move/scroll/type/keypress at viewport points, getVisibleScreenshot() as base64 PNG.",parameters:{type:"object",properties:{page:{}},required:["page"]}},{name:"createCaptcha",description:"Captcha helpers: click(bounds) and drag(from,to) return a post-action snapshot tree; readText(bounds?) OCRs a screenshot region (macOS Vision default, custom ocr injectable).",parameters:{type:"object",properties:{page:{},options:{type:"object"}},required:["page"]}},{name:"createChromeApi",description:"Chrome MV3-shaped APIs: tabs.query/get, windows.*, bookmarks/history/topSites (read-only from profile files), downloads.search/download. Writes throw UnsupportedOperationError.",parameters:{type:"object",properties:{connection:{},options:{type:"object"}},required:["connection"]}},{name:"injectCookies",description:"Inject raw cookie exports into a page with CDP sanitization handled (negative expires dropped, __Host- forced secure+root path+url-scoped, SameSite=None dropped when not secure, name+domain deduped).",parameters:{type:"object",properties:{page:{},cookies:{type:"array"}},required:["page","cookies"]}},{name:"createNetworkSnoop",description:"Buffer a page's CDP network traffic and return {pop,peek,popJson,waitFor,summary,dispose}; match is a predicate or {url,method,mimeType,resourceType}.",parameters:{type:"object",properties:{page:{},match:{},bodies:{type:"boolean"},maxEntries:{type:"number"},maxBodyBytes:{type:"number"}},required:["page"]}},{name:"snoop.waitFor",description:"Wait until a finished entry matches a predicate or {url,method,mimeType,resourceType}; timeoutMs defaults to 30000.",parameters:{type:"object",properties:{match:{},timeoutMs:{type:"number"}}}},{name:"snoop.popJson",description:"Drain the buffer and parse JSON bodies; skips non-JSON mime types and payloads that fail to parse.",parameters:{type:"object",properties:{}}},{name:"snoop.summary",description:"Return a compact text view of buffered entries (method, status, mimeType, size, url) capped by {max}.",parameters:{type:"object",properties:{max:{type:"number"}}}},{name:"collectWhileScrolling",description:"Async generator that scrolls the page, drains snoop.popJson(), and yields extracted items until minItems, maxScrolls, or noGrowth.",parameters:{type:"object",properties:{page:{},snoop:{},minItems:{type:"number"},maxScrolls:{type:"number"},extract:{},scroll:{type:"string",enum:["wheel","end"]},settleMs:{type:"number"}},required:["page","snoop"]}},{name:"createTrace",description:"Start a page flight recorder that writes dir/trace.jsonl, dir/trace.har, dir/summary.json and screenshots; never sends Runtime.enable.",parameters:{type:"object",properties:{page:{},dir:{type:"string"},screenshots:{type:"boolean"},network:{type:"boolean"},console:{type:"boolean"},maxBodyBytes:{type:"number"},events:{}},required:["page","dir"]}},{name:"trace.step",description:"Run named fn as a traced step with optional before/after screenshots; records the error and rethrows on failure.",parameters:{type:"object",properties:{name:{type:"string"},fn:{}},required:["name","fn"]}},{name:"trace.stop",description:"Flush listeners, write summary.json and trace.har, dispose the snoop, and return the summary for dir.",parameters:{type:"object",properties:{}}},{name:"reconcileFrames",description:"Adopt OOPIF iframe targets missing from FrameManager so later snapshots include them; returns the frames that were added.",parameters:{type:"object",properties:{page:{}},required:["page"]}},{name:"snapshotWithFrames",description:"Reconcile missing OOPIF frames then snapshot; refs stay page-level: page.locator(ref), never frame.locator.",parameters:{type:"object",properties:{page:{},options:{type:"object"}},required:["page"]}},{name:"connectPipe.dialogPolicy",description:"Wire connectPipe({dialogPolicy}) and client.setDialogPolicy to {accept,promptText} or (dialog)=>boolean|{accept,promptText}; throwing policy accepts. WebSocket connect() keeps auto-accept.",parameters:{type:"object",properties:{accept:{type:"boolean"},promptText:{type:"string"}}}},{name:"requestHuman",description:"Pause for a human: inject a shadow-DOM Done banner, bringToFront first, and remove the banner on every path; until is {url}|{selector}|async predicate.",parameters:{type:"object",properties:{page:{},prompt:{type:"string"},until:{},timeoutMs:{type:"number"},pollMs:{type:"number"},signal:{}},required:["page"]}},{name:"describeLayers",description:"Sample a viewport grid and return {blocking,candidates,viewport} so overlay coverage and selectorHint are visible.",parameters:{type:"object",properties:{page:{},grid:{type:"number"}},required:["page"]}},{name:"snapshotWithLayers",description:"Prepend a @layers header to the snapshot tree and attach a layers field so overlays are visible before the a11y tree.",parameters:{type:"object",properties:{page:{},options:{type:"object"}},required:["page"]}},{name:"emulate",description:"Apply a DEVICE_PRESETS name, a custom metrics object, or null to restore the original UA captured on first call.",parameters:{type:"object",properties:{page:{},preset:{}},required:["page","preset"]}},{name:"createRoutes",description:"Install page request interception; Fetch.enable is sent only on the first route(); interception is fingerprintable, keep it off by default.",parameters:{type:"object",properties:{page:{}},required:["page"]}},{name:"route.continue",description:"Continue an intercepted request, optionally overriding url, method, headers, or postData.",parameters:{type:"object",properties:{url:{type:"string"},method:{type:"string"},headers:{type:"object"},postData:{}}}},{name:"route.fulfill",description:"Fulfill an intercepted request with {status,headers,contentType,body} instead of hitting the network.",parameters:{type:"object",properties:{status:{type:"number"},headers:{type:"object"},contentType:{type:"string"},body:{}}}},{name:"route.abort",description:"Abort an intercepted request with a CDP errorReason (defaults to Failed).",parameters:{type:"object",properties:{reason:{type:"string"}}}}];var MODIFIER_KEYS2=new Set(["Alt","Control","Meta","Shift"]);function splitModifiers(keys=[]){const modifiers=keys.filter((key)=>MODIFIER_KEYS2.has(key)||/^(cmd|command|ctrl|option|controlormeta)$/i.test(key));const rest=keys.filter((key)=>!modifiers.includes(key));return{modifiers,rest}}function createCua(page){async function holdModifiers(keys,fn){const{modifiers}=splitModifiers(keys);for(const key of modifiers)await page.keyboard.down(key);try{return await fn()}finally{for(const key of[...modifiers].reverse())await page.keyboard.up(key)}}return{page,async click({x,y,button="left",keypress}){await holdModifiers(keypress,()=>page.mouse.click(x,y,{button}))},async doubleClick({x,y,keypress}){await holdModifiers(keypress,()=>page.mouse.click(x,y,{clickCount:2}))},async drag({path,keys}){if(!Array.isArray(path)||path.length<2){throw new TypeError("cua.drag requires path with at least two points")}await holdModifiers(keys,async()=>{const[first,...rest]=path;await page.mouse.move(first.x,first.y);await page.mouse.down();try{for(const point of rest)await page.mouse.move(point.x,point.y)}finally{await page.mouse.up()}})},async getVisibleScreenshot(){const buffer=await page.screenshot({type:"png"});return buffer.toString("base64")},async keypress({keys}){if(!Array.isArray(keys)||keys.length===0){throw new TypeError("cua.keypress requires a non-empty keys array")}await page.keyboard.press(keys.join("+"))},async move({x,y,keys}){await holdModifiers(keys,()=>page.mouse.move(x,y))},async scroll({x,y,scrollX,scrollY,keypress}){await holdModifiers(keypress,async()=>{await page.mouse.move(x,y);await page.mouse.wheel(scrollX,scrollY)})},async type({text}){await page.keyboard.type(text)}}}import{spawn as spawn2}from"node:child_process";import{mkdtempSync,writeFileSync,rmSync}from"node:fs";import{tmpdir as tmpdir2}from"node:os";import path4 from"node:path";var DEFAULT_DRAG_STEPS=20;var POST_ACTION_SETTLE_MS=3000;var MACOS_VISION_OCR_SWIFT=`
1484
+ `,"utf8")}finally{await handle.close()}await chmod(metadataPath,384)}async function resolveCloakProfile({profileDir=DEFAULT_PROFILE_DIR,fingerprintSeed,randomSeed=()=>randomInt(MIN_FINGERPRINT_SEED,MAX_FINGERPRINT_SEED+1)}={}){const resolvedProfile=path3.resolve(profileDir);const metadataPath=path3.join(resolvedProfile,METADATA_FILE);await mkdir3(resolvedProfile,{recursive:true,mode:448});await chmod(resolvedProfile,448);const requested=fingerprintSeed===undefined?null:normalizeFingerprintSeed(fingerprintSeed);let stored=await readMetadata(metadataPath);if(stored===null){const candidate=requested??normalizeFingerprintSeed(randomSeed());try{await writeMetadata(metadataPath,candidate);stored=candidate}catch(error){if(error?.code!=="EEXIST")throw error;stored=await readMetadata(metadataPath);if(stored===null){throw new Error(`CloakBrowser profile seed metadata disappeared: ${metadataPath}`)}}}if(requested!==null&&requested!==stored){throw new Error(`fingerprint seed mismatch: profile is fixed at ${stored}, requested ${requested}`)}await chmod(metadataPath,384);return{profileDir:resolvedProfile,metadataPath,fingerprintSeed:stored,browserArgs:buildCloakBrowserArgs({profileDir:resolvedProfile,fingerprintSeed:stored})}}function findCloakBrowserPath({browserRoot=path3.join(os.homedir(),".cloakbrowser")}={}){const candidates=globSync(path3.join(path3.resolve(browserRoot),"chromium-*","Chromium.app","Contents","MacOS","Chromium")).sort();const browserPath=candidates.at(-1);if(!browserPath){throw new Error(`CloakBrowser binary not found under ${path3.resolve(browserRoot)}; set CLOAKBROWSER_BIN`)}return browserPath}async function connectCloakProfile({browserPath=process.env.CLOAKBROWSER_BIN??findCloakBrowserPath(),profileDir=process.env.CLOAK_PROFILE_DIR??DEFAULT_PROFILE_DIR,fingerprintSeed=process.env.CLOAK_FINGERPRINT_SEED,browserArgs=[],storageRoot,...options}={}){const profile=await resolveCloakProfile({profileDir,fingerprintSeed});return connectPipe({browserPath,browserArgs:buildCloakBrowserArgs({profileDir:profile.profileDir,fingerprintSeed:profile.fingerprintSeed,extraArgs:browserArgs}),storageRoot:storageRoot??profile.profileDir,...options})}var SNAPSHOT_PARSE_ERROR="compactSnapshot expected a snapshot() result (JSON string or {tree, refs} object)";function compactSnapshot(snapshot){const obj=typeof snapshot==="string"?JSON.parse(snapshot):snapshot;if(!obj||typeof obj.tree!=="string")throw new TypeError(SNAPSHOT_PARSE_ERROR);return obj.tree}function snapshotTokens(snapshot){const text=typeof snapshot==="string"?snapshot:JSON.stringify(snapshot);return{bytes:Buffer.byteLength(text,"utf8"),estTokens:Math.ceil(Buffer.byteLength(text,"utf8")/4)}}var toolSchemas=[{name:"newTab",description:"Open a tab and return its page. Waits for interactivity.",parameters:{type:"object",properties:{url:{type:"string"}},required:["url"]}},{name:"attachPage",description:"Attach to an existing tab by targetId from listTargets().",parameters:{type:"object",properties:{targetId:{type:"string"}},required:["targetId"]}},{name:"page.goto",description:"Navigate the page; resolves when the page looks interactive.",parameters:{type:"object",properties:{url:{type:"string"}},required:["url"]}},{name:"page.snapshot",description:"PRIMARY page read. Returns a compact a11y tree; interactive elements carry [ref=eN] ids for page.locator. Options: maxDepth, maxChars, interactive, showHidden, selector, ref.",parameters:{type:"object",properties:{options:{type:"object"}}}},{name:"page.locator",description:"Resolve a ref id (e.g. 'e1') or CSS selector to a Locator: click, fill, press, hover, check, selectOption, dragTo, setInputFiles, textContent, isVisible, waitFor.",parameters:{type:"object",properties:{selectorOrRef:{type:"string"}},required:["selectorOrRef"]}},{name:"page.evaluate",description:"Run JS in the page and return the result.",parameters:{type:"object",properties:{expression:{type:"string"}},required:["expression"]}},{name:"page.screenshot",description:"Capture page image bytes (path, fullPage, clip, type, quality).",parameters:{type:"object",properties:{options:{type:"object"}}}},{name:"page.pdf",description:"Print the page to PDF bytes (path, format, margin, printBackground).",parameters:{type:"object",properties:{options:{type:"object"}}}},{name:"compactSnapshot",description:"Pass page.snapshot() output through this before reading: returns the raw tree without the refs map (refs resolve in-page; the map is ~54% of bytes on real pages).",parameters:{type:"object",properties:{snapshot:{}},required:["snapshot"]}},{name:"createCua",description:"Coordinate fallback for UI that refs cannot target: click/doubleClick/drag/move/scroll/type/keypress at viewport points, getVisibleScreenshot() as base64 PNG.",parameters:{type:"object",properties:{page:{}},required:["page"]}},{name:"createCaptcha",description:"Captcha helpers: click(bounds) and drag(from,to) return a post-action snapshot tree; readText(bounds?) OCRs a screenshot region (macOS Vision default, custom ocr injectable).",parameters:{type:"object",properties:{page:{},options:{type:"object"}},required:["page"]}},{name:"createChromeApi",description:"Chrome MV3-shaped APIs: tabs.query/get, windows.*, bookmarks/history/topSites (read-only from profile files), downloads.search/download. Writes throw UnsupportedOperationError.",parameters:{type:"object",properties:{connection:{},options:{type:"object"}},required:["connection"]}},{name:"injectCookies",description:"Inject raw cookie exports into a page with CDP sanitization handled (negative expires dropped, __Host- forced secure+root path+url-scoped, SameSite=None dropped when not secure, name+domain deduped).",parameters:{type:"object",properties:{page:{},cookies:{type:"array"}},required:["page","cookies"]}},{name:"createNetworkSnoop",description:"Buffer a page's CDP network traffic and return {pop,peek,popJson,waitFor,summary,dispose}; match is a predicate or {url,method,mimeType,resourceType}.",parameters:{type:"object",properties:{page:{},match:{},bodies:{type:"boolean"},maxEntries:{type:"number"},maxBodyBytes:{type:"number"}},required:["page"]}},{name:"snoop.waitFor",description:"Wait until a finished entry matches a predicate or {url,method,mimeType,resourceType}; timeoutMs defaults to 30000.",parameters:{type:"object",properties:{match:{},timeoutMs:{type:"number"}}}},{name:"snoop.popJson",description:"Drain the buffer and parse JSON bodies; skips non-JSON mime types and payloads that fail to parse.",parameters:{type:"object",properties:{}}},{name:"snoop.summary",description:"Return a compact text view of buffered entries (method, status, mimeType, size, url) capped by {max}.",parameters:{type:"object",properties:{max:{type:"number"}}}},{name:"collectWhileScrolling",description:"Async generator that scrolls the page, drains snoop.popJson(), and yields extracted items until minItems, maxScrolls, or noGrowth.",parameters:{type:"object",properties:{page:{},snoop:{},minItems:{type:"number"},maxScrolls:{type:"number"},extract:{},scroll:{type:"string",enum:["wheel","end"]},settleMs:{type:"number"}},required:["page","snoop"]}},{name:"createTrace",description:"Start a page flight recorder that writes dir/trace.jsonl, dir/trace.har, dir/summary.json and screenshots; never sends Runtime.enable.",parameters:{type:"object",properties:{page:{},dir:{type:"string"},screenshots:{type:"boolean"},network:{type:"boolean"},console:{type:"boolean"},maxBodyBytes:{type:"number"},events:{}},required:["page","dir"]}},{name:"trace.step",description:"Run named fn as a traced step with optional before/after screenshots; records the error and rethrows on failure.",parameters:{type:"object",properties:{name:{type:"string"},fn:{}},required:["name","fn"]}},{name:"trace.stop",description:"Flush listeners, write summary.json and trace.har, dispose the snoop, and return the summary for dir.",parameters:{type:"object",properties:{}}},{name:"reconcileFrames",description:"Adopt OOPIF iframe targets missing from FrameManager so later snapshots include them; returns the frames that were added.",parameters:{type:"object",properties:{page:{}},required:["page"]}},{name:"snapshotWithFrames",description:"Reconcile missing OOPIF frames then snapshot; refs stay page-level: page.locator(ref), never frame.locator.",parameters:{type:"object",properties:{page:{},options:{type:"object"}},required:["page"]}},{name:"connectPipe.dialogPolicy",description:"Wire connectPipe({dialogPolicy}) and client.setDialogPolicy to {accept,promptText} or (dialog)=>boolean|{accept,promptText}; throwing policy accepts. WebSocket connect() keeps auto-accept.",parameters:{type:"object",properties:{accept:{type:"boolean"},promptText:{type:"string"}}}},{name:"requestHuman",description:"Pause for a human: inject a shadow-DOM Done banner, bringToFront first, and remove the banner on every path; until is {url}|{selector}|async predicate.",parameters:{type:"object",properties:{page:{},prompt:{type:"string"},until:{},timeoutMs:{type:"number"},pollMs:{type:"number"},signal:{}},required:["page"]}},{name:"describeLayers",description:"Sample a viewport grid and return {blocking,candidates,viewport} so overlay coverage and selectorHint are visible.",parameters:{type:"object",properties:{page:{},grid:{type:"number"}},required:["page"]}},{name:"snapshotWithLayers",description:"Prepend a @layers header to the snapshot tree and attach a layers field so overlays are visible before the a11y tree.",parameters:{type:"object",properties:{page:{},options:{type:"object"}},required:["page"]}},{name:"emulate",description:"Apply a DEVICE_PRESETS name, a custom metrics object, or null to restore the original UA captured on first call.",parameters:{type:"object",properties:{page:{},preset:{}},required:["page","preset"]}},{name:"createRoutes",description:"Install page request interception; Fetch.enable is sent only on the first route(); interception is fingerprintable, keep it off by default.",parameters:{type:"object",properties:{page:{}},required:["page"]}},{name:"route.continue",description:"Continue an intercepted request, optionally overriding url, method, headers, or postData.",parameters:{type:"object",properties:{url:{type:"string"},method:{type:"string"},headers:{type:"object"},postData:{}}}},{name:"route.fulfill",description:"Fulfill an intercepted request with {status,headers,contentType,body} instead of hitting the network.",parameters:{type:"object",properties:{status:{type:"number"},headers:{type:"object"},contentType:{type:"string"},body:{}}}},{name:"route.abort",description:"Abort an intercepted request with a CDP errorReason (defaults to Failed).",parameters:{type:"object",properties:{reason:{type:"string"}}}}];var MODIFIER_KEYS2=new Set(["Alt","Control","Meta","Shift"]);function splitModifiers(keys=[]){const modifiers=keys.filter((key)=>MODIFIER_KEYS2.has(key)||/^(cmd|command|ctrl|option|controlormeta)$/i.test(key));const rest=keys.filter((key)=>!modifiers.includes(key));return{modifiers,rest}}function createCua(page){async function holdModifiers(keys,fn){const{modifiers}=splitModifiers(keys);for(const key of modifiers)await page.keyboard.down(key);try{return await fn()}finally{for(const key of[...modifiers].reverse())await page.keyboard.up(key)}}return{page,async click({x,y,button="left",keypress}){await holdModifiers(keypress,()=>page.mouse.click(x,y,{button}))},async doubleClick({x,y,keypress}){await holdModifiers(keypress,()=>page.mouse.click(x,y,{clickCount:2}))},async drag({path,keys}){if(!Array.isArray(path)||path.length<2){throw new TypeError("cua.drag requires path with at least two points")}await holdModifiers(keys,async()=>{const[first,...rest]=path;await page.mouse.move(first.x,first.y);await page.mouse.down();try{for(const point of rest)await page.mouse.move(point.x,point.y)}finally{await page.mouse.up()}})},async getVisibleScreenshot(){const buffer=await page.screenshot({type:"png"});return buffer.toString("base64")},async keypress({keys}){if(!Array.isArray(keys)||keys.length===0){throw new TypeError("cua.keypress requires a non-empty keys array")}await page.keyboard.press(keys.join("+"))},async move({x,y,keys}){await holdModifiers(keys,()=>page.mouse.move(x,y))},async scroll({x,y,scrollX,scrollY,keypress}){await holdModifiers(keypress,async()=>{await page.mouse.move(x,y);await page.mouse.wheel(scrollX,scrollY)})},async type({text}){await page.keyboard.type(text)}}}import{spawn as spawn2}from"node:child_process";import{mkdtempSync,writeFileSync,rmSync}from"node:fs";import{tmpdir as tmpdir2}from"node:os";import path4 from"node:path";var MAX_TIMER_MS=2147483647;var STOP=Symbol("wait stopped");async function waitForCaptcha(page,{until,timeoutMs=1e4,pollMs=100,signal}={}){if(typeof until!=="function")throw new TypeError("until must be a function");if(!Number.isFinite(timeoutMs)||timeoutMs<0||timeoutMs>MAX_TIMER_MS){throw new RangeError("timeoutMs must be between 0 and 2147483647")}if(!Number.isFinite(pollMs)||pollMs<=0||pollMs>MAX_TIMER_MS){throw new RangeError("pollMs must be greater than 0 and at most 2147483647")}if(signal!==undefined&&(typeof signal?.aborted!=="boolean"||typeof signal.addEventListener!=="function"||typeof signal.removeEventListener!=="function")){throw new TypeError("signal must be AbortSignal-compatible")}const started=performance.now();const deadline=started+timeoutMs;const operation=new AbortController;let deadlineTimer;let pollTimer;let stop;const stopped=new Promise((resolve)=>{stop=()=>resolve(STOP)});const terminal=()=>signal?.aborted?"cancelled":performance.now()>=deadline?"timed_out":null;const result=(outcome)=>({outcome,elapsedMs:performance.now()-started});const scheduleDeadline=()=>{const remaining=deadline-performance.now();if(remaining<=0)stop();else deadlineTimer=setTimeout(scheduleDeadline,Math.ceil(remaining))};try{const initial=terminal();if(initial)return result(initial);signal?.addEventListener("abort",stop,{once:true});scheduleDeadline();for(;;){let accepted;try{accepted=await Promise.race([Promise.resolve().then(()=>terminal()?STOP:until(page,{signal:operation.signal})),stopped])}catch(error){const outcome=terminal();if(outcome)return result(outcome);throw error}const outcome=terminal();if(outcome)return result(outcome);if(accepted===true)return result("matched");await Promise.race([new Promise((resolve)=>{pollTimer=setTimeout(resolve,Math.ceil(Math.min(pollMs,deadline-performance.now())))}),stopped]);clearTimeout(pollTimer);const afterDelay=terminal();if(afterDelay)return result(afterDelay)}}finally{clearTimeout(deadlineTimer);clearTimeout(pollTimer);signal?.removeEventListener("abort",stop);operation.abort();stop()}}var DEFAULT_DRAG_STEPS=20;var POST_ACTION_SETTLE_MS=3000;function validatePoint(point){if(!Number.isFinite(point?.x)||!Number.isFinite(point?.y)){throw new TypeError("point coordinates must be finite numbers")}}function validateSteps(steps){if(!Number.isSafeInteger(steps)||steps<=0){throw new RangeError("steps must be a positive safe integer")}}function validateSettleMs(value){if(!Number.isFinite(value)||value<0||value>2147483647){throw new RangeError("settleMs must be between 0 and 2147483647")}}var MACOS_VISION_OCR_SWIFT=`
1485
1485
  import Vision
1486
1486
  import AppKit
1487
1487
  let path = CommandLine.arguments[1]
@@ -1496,7 +1496,7 @@ let text = (request.results ?? [])
1496
1496
  .compactMap { $0.topCandidates(1).first?.string }
1497
1497
  .joined(separator: " ")
1498
1498
  print(text)
1499
- `;async function macOSVisionOcr(pngBuffer){if(process.platform!=="darwin"){throw new Error("macOSVisionOcr is only available on macOS; pass a custom ocr(buffer) instead")}const dir=mkdtempSync(path4.join(tmpdir2(),"omowright-ocr-"));try{const imagePath=path4.join(dir,"region.png");const scriptPath=path4.join(dir,"ocr.swift");writeFileSync(imagePath,pngBuffer);writeFileSync(scriptPath,MACOS_VISION_OCR_SWIFT);return await new Promise((resolve,reject)=>{const child=spawn2("swift",[scriptPath,imagePath],{stdio:["ignore","pipe","pipe"]});let stdout="";let stderr="";child.stdout.on("data",(chunk)=>{stdout+=chunk});child.stderr.on("data",(chunk)=>{stderr+=chunk});child.on("error",reject);child.on("close",(code)=>{if(code===0){const text=stdout.trim();resolve(text.length>0?text:null)}else{reject(new Error(`Vision OCR failed (exit ${code}): ${stderr.trim()}`))}})})}finally{rmSync(dir,{recursive:true,force:true})}}function createCaptcha(page,options={}){const ocr=options.ocr??macOSVisionOcr;function centerOf(bounds){return{x:bounds.x+bounds.width/2,y:bounds.y+bounds.height/2}}async function settleAndSnapshot(settleMs){await new Promise((resolve)=>setTimeout(resolve,settleMs));return compactSnapshot(await page.snapshot())}return{page,async click(bounds,opts={}){const point=centerOf(bounds);await page.mouse.click(point.x,point.y);return settleAndSnapshot(opts.settleMs??POST_ACTION_SETTLE_MS)},async drag(from,to,opts={}){const steps=opts.steps??DEFAULT_DRAG_STEPS;await page.mouse.move(from.x,from.y);await page.mouse.down();try{await page.mouse.move(to.x,to.y,{steps})}finally{await page.mouse.up()}return settleAndSnapshot(opts.settleMs??POST_ACTION_SETTLE_MS)},async readText(bounds){const buffer=await page.screenshot(bounds?{type:"png",clip:bounds}:{type:"png"});return ocr(buffer)}}}var EVENT_NAMES=["tabOpened","tabClosed","popupOpened","downloadStarted","downloadProgress","downloadFinished"];var BROWSER_EVENT_NAMES=Object.freeze([...EVENT_NAMES]);var DEFAULT_TIMEOUT=30000;var TERMINAL_LIMIT=4096;function pageTarget(info){return info?.type==="page"&&info?.targetId?info:null}function normalizeTarget(info){return{targetId:String(info.targetId),type:"page",url:info.url??"about:blank",title:info.title??"",openerId:info.openerId??null,openerFrameId:info.openerFrameId??null,browserContextId:info.browserContextId??null,attached:Boolean(info.attached),subtype:info.subtype??null}}function errorValue(value){return value instanceof Error?value:Error(String(value))}class BrowserEventBridge{#cdp;#options;#listeners=new Map;#unsubscribers=[];#targetInfos=new Map;#crashes=new Map;#downloads=new Map;#terminalDownloads=new Map;#terminal=new Set;#frameToTarget=new Map;#sessionToTarget=new Map;#waits=new Set;#disposed=false;#explicitDispose=false;ready;constructor(connection,options){if(!connection?.cdp)throw new TypeError("createEvents requires a browser connection");this.#cdp=connection.cdp;this.#options=this.#validate(options);this.#register();this.ready=this.#initialize()}#validate(options){const behavior=options.downloadBehavior??"default";if(!["default","allow","deny","allowAndName"].includes(behavior))throw new TypeError("Invalid downloadBehavior");if(behavior==="allow"&&!pathIsAbsolute(options.downloadPath))throw new TypeError("downloadPath must be an absolute path for allow");return{...options,downloadBehavior:behavior}}#register(){const on=(method,fn)=>this.#unsubscribers.push(this.#cdp.on(method,fn));on("Target.targetCreated",(info)=>this.#created(info,false));on("Target.targetDestroyed",(info)=>this.#destroyed(info));on("Target.targetInfoChanged",(info)=>{const target=pageTarget(info?.targetInfo);if(target&&this.#targetInfos.has(target.targetId))this.#targetInfos.set(target.targetId,normalizeTarget(target))});on("Target.targetCrashed",(info)=>{if(info?.targetId)this.#crashes.set(info.targetId,{status:info.status??"",errorCode:info.errorCode??0})});on("Target.attachedToTarget",(info,meta)=>{const targetId=info?.targetInfo?.targetId;const sessionId=info?.sessionId??meta?.sessionId;if(targetId&&sessionId){this.#sessionToTarget.set(sessionId,targetId)}});on("Target.detachedFromTarget",(info,meta)=>{const sessionId=info?.sessionId??meta?.sessionId;if(sessionId)this.#sessionToTarget.delete(sessionId)});on("Browser.downloadWillBegin",(info)=>this.#downloadBegin(info));on("Browser.downloadProgress",(info)=>this.#downloadProgress(info));on("Page.frameAttached",(info,meta)=>{const sessionId=info?.sessionId??meta?.sessionId;if(sessionId&&info?.frameId)this.#mapFrame(info.frameId,sessionId)});on("Page.frameNavigated",(info,meta)=>{const sessionId=info?.sessionId??meta?.sessionId;if(sessionId&&info?.frame?.id)this.#mapFrame(info.frame.id,sessionId)});if(this.#cdp.transportEvents?.on){this.#unsubscribers.push(this.#cdp.transportEvents.on("disconnected",()=>{this.#disconnectDownloads();this.#cancelWaits(Error("Browser transport disconnected"))}));this.#unsubscribers.push(this.#cdp.transportEvents.on("reconnected",()=>{this.#reconcile().catch((error)=>console.warn("[events] target reconciliation failed",error));this.#configureDownloads().catch((error)=>console.warn("[events] download reconfiguration failed",error))}))}}async#initialize(){await this.#cdp.ensureConnected();await this.#cdp.send("Target.setDiscoverTargets",{discover:true});const result=await this.#cdp.send("Target.getTargets");await this.#configureDownloads();const targets=(result?.targetInfos??[]).filter((info)=>pageTarget(info));targets.sort((a,b)=>Boolean(a.openerId)-Boolean(b.openerId));for(const info of targets)this.#created(info,true)}async#reconcile(){const result=await this.#cdp.send("Target.getTargets");const current=new Set((result?.targetInfos??[]).filter((info)=>pageTarget(info)).map((info)=>String(info.targetId)));for(const id of[...this.#targetInfos.keys()])if(!current.has(id))this.#destroyed({targetId:id});const targets=(result?.targetInfos??[]).filter((info)=>pageTarget(info));targets.sort((a,b)=>Boolean(a.openerId)-Boolean(b.openerId));for(const info of targets)this.#created(info,true)}async#configureDownloads(){const params={behavior:this.#options.downloadBehavior,eventsEnabled:true};if(this.#options.downloadBehavior==="allow"||this.#options.downloadBehavior==="allowAndName")params.downloadPath=this.#options.downloadPath;await this.#cdp.send("Browser.setDownloadBehavior",params)}#mapFrame(frameId,sessionId){const targetId=this.#sessionToTarget.get(sessionId)??[...this.#targetInfos.values()].find((info)=>info.attached&&info.targetId===sessionId)?.targetId;if(targetId)this.#frameToTarget.set(frameId,targetId)}#created(info,initial){const target=pageTarget(info?.targetInfo??info);if(!target)return;const normalized=normalizeTarget(target);const id=normalized.targetId;if(this.#targetInfos.has(id))return;this.#targetInfos.set(id,normalized);const popup=Boolean(normalized.openerId&&this.#targetInfos.has(normalized.openerId));this.#dispatch("tabOpened",{...normalized,popup,initial:Boolean(initial)});if(popup)this.#dispatch("popupOpened",{targetId:id,openerTargetId:normalized.openerId,openerFrameId:normalized.openerFrameId,url:normalized.url,title:normalized.title,initial:Boolean(initial)})}#destroyed(info){const id=info?.targetId;const last=this.#targetInfos.get(id);if(!last)return;this.#targetInfos.delete(id);const crash=this.#crashes.get(id)??null;this.#crashes.delete(id);for(const[frame,targetId]of this.#frameToTarget)if(targetId===id)this.#frameToTarget.delete(frame);this.#dispatch("tabClosed",{targetId:id,reason:crash?"crashed":"destroyed",lastKnown:last,crash})}#rememberTerminal(guid){this.#terminal.delete(guid);this.#terminal.add(guid);if(this.#terminal.size>TERMINAL_LIMIT){const oldest=this.#terminal.values().next().value;this.#terminal.delete(oldest);this.#terminalDownloads.delete(oldest)}}#downloadBegin(info={}){if(!info.guid||this.#terminal.has(info.guid))return;const record={guid:info.guid,url:info.url??null,suggestedFilename:info.suggestedFilename??"",frameId:info.frameId??null,targetId:this.#frameToTarget.get(info.frameId)??null,totalBytes:0,receivedBytes:0,filePath:null};this.#downloads.set(info.guid,record);this.#dispatch("downloadStarted",{guid:record.guid,url:record.url,suggestedFilename:record.suggestedFilename,frameId:record.frameId,targetId:record.targetId})}#downloadProgress(info={}){if(!info.guid)return;const alreadyTerminal=this.#terminal.has(info.guid);const record=this.#downloads.get(info.guid)??this.#terminalDownloads.get(info.guid);if(!record)return;Object.assign(record,{totalBytes:Number(info.totalBytes??0),receivedBytes:Number(info.receivedBytes??0),filePath:info.filePath??null});this.#dispatch("downloadProgress",{guid:info.guid,totalBytes:record.totalBytes,receivedBytes:record.receivedBytes,state:info.state,filePath:record.filePath,targetId:record.targetId});if((info.state==="completed"||info.state==="canceled")&&!alreadyTerminal){this.#rememberTerminal(info.guid);this.#downloads.delete(info.guid);this.#terminalDownloads.set(info.guid,record);this.#dispatch("downloadFinished",{guid:info.guid,totalBytes:record.totalBytes,receivedBytes:record.receivedBytes,state:info.state,cdpState:info.state,filePath:record.filePath,targetId:record.targetId,error:null})}}#disconnectDownloads(){if(this.#explicitDispose)return;for(const record of this.#downloads.values()){this.#rememberTerminal(record.guid);this.#dispatch("downloadFinished",{guid:record.guid,totalBytes:record.totalBytes,receivedBytes:record.receivedBytes,state:"failed",cdpState:null,filePath:record.filePath,targetId:record.targetId,error:{code:"browserDisconnected",message:"Browser transport disconnected"}})}this.#downloads.clear()}#dispatch(name,payload){const listeners=[...this.#listeners.get(name)??[]];Promise.allSettled(listeners.map((listener)=>Promise.resolve().then(()=>listener(payload)))).then((results)=>results.filter((r)=>r.status==="rejected").forEach((r)=>console.warn(`[events] ${name} listener failed`,r.reason)))}on(name,listener){this.#assertName(name);if(typeof listener!=="function")throw new TypeError("listener must be a function");const set=this.#listeners.get(name)??new Set;set.add(listener);this.#listeners.set(name,set);return()=>this.off(name,listener)}off(name,listener){this.#assertName(name);this.#listeners.get(name)?.delete(listener)}#cancelWaits(error){for(const cancel of this.#waits)cancel(error);this.#waits.clear()}waitForEvent(name,options={}){this.#assertName(name);if(this.#disposed)return Promise.reject(Error("Events bridge is disposed"));const timeout=options.timeout??DEFAULT_TIMEOUT;return new Promise((resolve,reject)=>{let done=false;let timer;const off=this.on(name,async(value)=>{try{if(options.predicate&&!await options.predicate(value))return;if(done)return;done=true;clearTimeout(timer);this.#waits.delete(cancel);off();resolve(value)}catch(error){if(!done){done=true;clearTimeout(timer);this.#waits.delete(cancel);off();reject(errorValue(error))}}});const cancel=(error)=>{if(!done){done=true;clearTimeout(timer);this.#waits.delete(cancel);off();reject(error??Error("Events bridge disposed"))}};this.#waits.add(cancel);timer=setTimeout(()=>cancel(Error(`Timed out waiting for browser event "${name}"`)),timeout)})}#assertName(name){if(!EVENT_NAMES.includes(name))throw new TypeError(`Unknown browser event: ${String(name)}`)}async dispose(){if(this.#disposed)return;this.#disposed=true;this.#explicitDispose=true;this.#cancelWaits();for(const off of this.#unsubscribers)off();this.#unsubscribers=[];this.#listeners.clear();await this.ready.catch(()=>{})}}function pathIsAbsolute(value){return typeof value==="string"&&(value.startsWith("/")||/^[A-Za-z]:[\\/]/.test(value))}function createEvents(connection,options={}){return new BrowserEventBridge(connection,options)}init_agent_tabs();init_viewport();import{randomUUID}from"node:crypto";import path7 from"node:path";import{mkdir as mkdir4,open as open3,readFile as readFile2,rename,lstat,chmod as chmod2}from"node:fs/promises";import{homedir}from"node:os";import{TextDecoder as TextDecoder2}from"node:util";import{mkdirSync}from"node:fs";import path6 from"node:path";import{tmpdir as tmpdir3}from"node:os";import{existsSync,readFileSync as readFileSync2}from"node:fs";import path5 from"node:path";var DatabaseCtor;async function loadSqlite(){if(DatabaseCtor)return DatabaseCtor;try{const mod=await import("node:sqlite");DatabaseCtor=(file)=>new mod.DatabaseSync(file,{readOnly:true,readBigInts:true})}catch{const mod=await import("bun:sqlite");DatabaseCtor=(file)=>new mod.Database(file,{readonly:true})}return DatabaseCtor}var CHROME_EPOCH_OFFSET_MS=11644473600000;var chromeTimeToMs=(time)=>Number(BigInt(time)/1000n)-CHROME_EPOCH_OFFSET_MS;var msToChromeTime=(ms)=>BigInt(Math.floor((ms+CHROME_EPOCH_OFFSET_MS)*1000));var EXTENSION_BRIDGE_REQUIRED="requires the extension bridge in standalone OmOWright";function createProfileApis(profilePath){function profileDir(){if(!profilePath){throw new UnsupportedOperationError("chrome.bookmarks/history/topSites need a profilePath (the browser's user-data-dir); pass it to createChromeApi(connection, { profilePath })")}return path5.join(profilePath,"Default")}async function openDb(fileName){const file=path5.join(profileDir(),fileName);if(!existsSync(file))return null;const ctor=await loadSqlite();return ctor(file)}async function withDb(fileName,fn){const db=await openDb(fileName);if(!db)return[];try{return fn(db)}finally{db.close()}}function bookmarkRoots(){const file=path5.join(profileDir(),"Bookmarks");if(!existsSync(file))return[];const parsed=JSON.parse(readFileSync2(file,"utf8"));return Object.values(parsed.roots??{}).filter((node)=>node&&typeof node==="object"&&node.id)}function walkBookmarks(nodes,visit,acc=[]){for(const node of nodes){visit(node,acc);if(Array.isArray(node.children))walkBookmarks(node.children,visit,acc)}return acc}function findBookmark(id){for(const root of bookmarkRoots()){const found=walkBookmarks([root],(node,acc)=>{if(node.id===String(id))acc.push(node)});if(found.length>0)return found[0]}return null}function toBookmark(node){const out={id:node.id,title:node.name??"",type:node.type};if(node.url)out.url=node.url;if(node.date_added)out.dateAdded=chromeTimeToMs(node.date_added);if(node.children)out.children=node.children.map(toBookmark);return out}const bookmarks={async get(idOrIdList){const ids=Array.isArray(idOrIdList)?idOrIdList:[idOrIdList];return ids.map(findBookmark).filter(Boolean).map(toBookmark)},async getChildren(id){const node=findBookmark(id);return(node?.children??[]).map(toBookmark)},async getRecent(numberOfItems){const urls=walkBookmarks(bookmarkRoots(),(node,acc)=>{if(node.type==="url")acc.push(node)});return urls.sort((a,b)=>Number(b.date_added??0)-Number(a.date_added??0)).slice(0,numberOfItems).map(toBookmark)},async getSubTree(id){const node=findBookmark(id);return node?[toBookmark(node)]:[]},async getTree(){return bookmarkRoots().map(toBookmark)},async search(query){const needle=typeof query==="string"?query.toLowerCase():(query.query??"").toLowerCase();const url=typeof query==="object"?query.url?.toLowerCase():undefined;const title=typeof query==="object"?query.title?.toLowerCase():undefined;return walkBookmarks(bookmarkRoots(),(node,acc)=>{if(node.type!=="url")return;const name=(node.name??"").toLowerCase();const href=(node.url??"").toLowerCase();const ok=needle&&(name.includes(needle)||href.includes(needle))||url&&href.includes(url)||title&&name.includes(title);if(ok)acc.push(node)}).map(toBookmark)},async create(){throw new UnsupportedOperationError(`chrome.bookmarks.create ${EXTENSION_BRIDGE_REQUIRED}`)},async update(){throw new UnsupportedOperationError(`chrome.bookmarks.update ${EXTENSION_BRIDGE_REQUIRED}`)},async move(){throw new UnsupportedOperationError(`chrome.bookmarks.move ${EXTENSION_BRIDGE_REQUIRED}`)},async remove(){throw new UnsupportedOperationError(`chrome.bookmarks.remove ${EXTENSION_BRIDGE_REQUIRED}`)},async removeTree(){throw new UnsupportedOperationError(`chrome.bookmarks.removeTree ${EXTENSION_BRIDGE_REQUIRED}`)}};const history={async search(query){const text=`%${query.text??""}%`;const start=query.startTime!=null?msToChromeTime(query.startTime):0n;const end=query.endTime!=null?msToChromeTime(query.endTime):99999999999999999n;const max=query.maxResults??100;return await withDb("History",(db)=>db.prepare(`SELECT id, url, title, visit_count, last_visit_time FROM urls
1499
+ `;async function macOSVisionOcr(pngBuffer){if(process.platform!=="darwin"){throw new Error("macOSVisionOcr is only available on macOS; pass a custom ocr(buffer) instead")}const dir=mkdtempSync(path4.join(tmpdir2(),"omowright-ocr-"));try{const imagePath=path4.join(dir,"region.png");const scriptPath=path4.join(dir,"ocr.swift");writeFileSync(imagePath,pngBuffer);writeFileSync(scriptPath,MACOS_VISION_OCR_SWIFT);return await new Promise((resolve,reject)=>{const child=spawn2("swift",[scriptPath,imagePath],{stdio:["ignore","pipe","pipe"]});let stdout="";let stderr="";child.stdout.on("data",(chunk)=>{stdout+=chunk});child.stderr.on("data",(chunk)=>{stderr+=chunk});child.on("error",reject);child.on("close",(code)=>{if(code===0){const text=stdout.trim();resolve(text.length>0?text:null)}else{reject(new Error(`Vision OCR failed (exit ${code}): ${stderr.trim()}`))}})})}finally{rmSync(dir,{recursive:true,force:true})}}function createCaptcha(page,options={}){const ocr=options.ocr??macOSVisionOcr;function centerOf(bounds){validatePoint(bounds);if(!Number.isFinite(bounds.width)||bounds.width<=0||!Number.isFinite(bounds.height)||bounds.height<=0||!Number.isFinite(bounds.x+bounds.width)||!Number.isFinite(bounds.y+bounds.height)){throw new RangeError("bounds must have positive finite dimensions and finite edges")}return{x:bounds.x+bounds.width/2,y:bounds.y+bounds.height/2}}async function settleAndSnapshot(settleMs){await new Promise((resolve)=>setTimeout(resolve,settleMs));return compactSnapshot(await page.snapshot())}return{page,waitFor(opts){return waitForCaptcha(page,opts)},async click(bounds,opts={}){const center=centerOf(bounds);const point=opts.point===undefined?center:opts.point;validatePoint(point);if(point.x<bounds.x||point.x>=bounds.x+bounds.width||point.y<bounds.y||point.y>=bounds.y+bounds.height){throw new RangeError("point must be inside bounds")}const settleMs=opts.settleMs===undefined?POST_ACTION_SETTLE_MS:opts.settleMs;validateSettleMs(settleMs);if(opts.approachSteps!==undefined){validateSteps(opts.approachSteps);await page.mouse.move(point.x,point.y,{steps:opts.approachSteps})}await page.mouse.click(point.x,point.y);return settleAndSnapshot(settleMs)},async drag(from,to,opts={}){validatePoint(from);validatePoint(to);const steps=opts.steps===undefined?DEFAULT_DRAG_STEPS:opts.steps;validateSteps(steps);const settleMs=opts.settleMs===undefined?POST_ACTION_SETTLE_MS:opts.settleMs;validateSettleMs(settleMs);await page.mouse.move(from.x,from.y);await page.mouse.down();try{await page.mouse.move(to.x,to.y,{steps})}finally{await page.mouse.up()}return settleAndSnapshot(settleMs)},async readText(bounds){const buffer=await page.screenshot(bounds?{type:"png",clip:bounds}:{type:"png"});return ocr(buffer)}}}var EVENT_NAMES=["tabOpened","tabClosed","popupOpened","downloadStarted","downloadProgress","downloadFinished"];var BROWSER_EVENT_NAMES=Object.freeze([...EVENT_NAMES]);var DEFAULT_TIMEOUT=30000;var TERMINAL_LIMIT=4096;function pageTarget(info){return info?.type==="page"&&info?.targetId?info:null}function normalizeTarget(info){return{targetId:String(info.targetId),type:"page",url:info.url??"about:blank",title:info.title??"",openerId:info.openerId??null,openerFrameId:info.openerFrameId??null,browserContextId:info.browserContextId??null,attached:Boolean(info.attached),subtype:info.subtype??null}}function errorValue(value){return value instanceof Error?value:Error(String(value))}class BrowserEventBridge{#cdp;#options;#listeners=new Map;#unsubscribers=[];#targetInfos=new Map;#crashes=new Map;#downloads=new Map;#terminalDownloads=new Map;#terminal=new Set;#frameToTarget=new Map;#sessionToTarget=new Map;#waits=new Set;#disposed=false;#explicitDispose=false;ready;constructor(connection,options){if(!connection?.cdp)throw new TypeError("createEvents requires a browser connection");this.#cdp=connection.cdp;this.#options=this.#validate(options);this.#register();this.ready=this.#initialize()}#validate(options){const behavior=options.downloadBehavior??"default";if(!["default","allow","deny","allowAndName"].includes(behavior))throw new TypeError("Invalid downloadBehavior");if(behavior==="allow"&&!pathIsAbsolute(options.downloadPath))throw new TypeError("downloadPath must be an absolute path for allow");return{...options,downloadBehavior:behavior}}#register(){const on=(method,fn)=>this.#unsubscribers.push(this.#cdp.on(method,fn));on("Target.targetCreated",(info)=>this.#created(info,false));on("Target.targetDestroyed",(info)=>this.#destroyed(info));on("Target.targetInfoChanged",(info)=>{const target=pageTarget(info?.targetInfo);if(target&&this.#targetInfos.has(target.targetId))this.#targetInfos.set(target.targetId,normalizeTarget(target))});on("Target.targetCrashed",(info)=>{if(info?.targetId)this.#crashes.set(info.targetId,{status:info.status??"",errorCode:info.errorCode??0})});on("Target.attachedToTarget",(info,meta)=>{const targetId=info?.targetInfo?.targetId;const sessionId=info?.sessionId??meta?.sessionId;if(targetId&&sessionId){this.#sessionToTarget.set(sessionId,targetId)}});on("Target.detachedFromTarget",(info,meta)=>{const sessionId=info?.sessionId??meta?.sessionId;if(sessionId)this.#sessionToTarget.delete(sessionId)});on("Browser.downloadWillBegin",(info)=>this.#downloadBegin(info));on("Browser.downloadProgress",(info)=>this.#downloadProgress(info));on("Page.frameAttached",(info,meta)=>{const sessionId=info?.sessionId??meta?.sessionId;if(sessionId&&info?.frameId)this.#mapFrame(info.frameId,sessionId)});on("Page.frameNavigated",(info,meta)=>{const sessionId=info?.sessionId??meta?.sessionId;if(sessionId&&info?.frame?.id)this.#mapFrame(info.frame.id,sessionId)});if(this.#cdp.transportEvents?.on){this.#unsubscribers.push(this.#cdp.transportEvents.on("disconnected",()=>{this.#disconnectDownloads();this.#cancelWaits(Error("Browser transport disconnected"))}));this.#unsubscribers.push(this.#cdp.transportEvents.on("reconnected",()=>{this.#reconcile().catch((error)=>console.warn("[events] target reconciliation failed",error));this.#configureDownloads().catch((error)=>console.warn("[events] download reconfiguration failed",error))}))}}async#initialize(){await this.#cdp.ensureConnected();await this.#cdp.send("Target.setDiscoverTargets",{discover:true});const result=await this.#cdp.send("Target.getTargets");await this.#configureDownloads();const targets=(result?.targetInfos??[]).filter((info)=>pageTarget(info));targets.sort((a,b)=>Boolean(a.openerId)-Boolean(b.openerId));for(const info of targets)this.#created(info,true)}async#reconcile(){const result=await this.#cdp.send("Target.getTargets");const current=new Set((result?.targetInfos??[]).filter((info)=>pageTarget(info)).map((info)=>String(info.targetId)));for(const id of[...this.#targetInfos.keys()])if(!current.has(id))this.#destroyed({targetId:id});const targets=(result?.targetInfos??[]).filter((info)=>pageTarget(info));targets.sort((a,b)=>Boolean(a.openerId)-Boolean(b.openerId));for(const info of targets)this.#created(info,true)}async#configureDownloads(){const params={behavior:this.#options.downloadBehavior,eventsEnabled:true};if(this.#options.downloadBehavior==="allow"||this.#options.downloadBehavior==="allowAndName")params.downloadPath=this.#options.downloadPath;await this.#cdp.send("Browser.setDownloadBehavior",params)}#mapFrame(frameId,sessionId){const targetId=this.#sessionToTarget.get(sessionId)??[...this.#targetInfos.values()].find((info)=>info.attached&&info.targetId===sessionId)?.targetId;if(targetId)this.#frameToTarget.set(frameId,targetId)}#created(info,initial){const target=pageTarget(info?.targetInfo??info);if(!target)return;const normalized=normalizeTarget(target);const id=normalized.targetId;if(this.#targetInfos.has(id))return;this.#targetInfos.set(id,normalized);const popup=Boolean(normalized.openerId&&this.#targetInfos.has(normalized.openerId));this.#dispatch("tabOpened",{...normalized,popup,initial:Boolean(initial)});if(popup)this.#dispatch("popupOpened",{targetId:id,openerTargetId:normalized.openerId,openerFrameId:normalized.openerFrameId,url:normalized.url,title:normalized.title,initial:Boolean(initial)})}#destroyed(info){const id=info?.targetId;const last=this.#targetInfos.get(id);if(!last)return;this.#targetInfos.delete(id);const crash=this.#crashes.get(id)??null;this.#crashes.delete(id);for(const[frame,targetId]of this.#frameToTarget)if(targetId===id)this.#frameToTarget.delete(frame);this.#dispatch("tabClosed",{targetId:id,reason:crash?"crashed":"destroyed",lastKnown:last,crash})}#rememberTerminal(guid){this.#terminal.delete(guid);this.#terminal.add(guid);if(this.#terminal.size>TERMINAL_LIMIT){const oldest=this.#terminal.values().next().value;this.#terminal.delete(oldest);this.#terminalDownloads.delete(oldest)}}#downloadBegin(info={}){if(!info.guid||this.#terminal.has(info.guid))return;const record={guid:info.guid,url:info.url??null,suggestedFilename:info.suggestedFilename??"",frameId:info.frameId??null,targetId:this.#frameToTarget.get(info.frameId)??null,totalBytes:0,receivedBytes:0,filePath:null};this.#downloads.set(info.guid,record);this.#dispatch("downloadStarted",{guid:record.guid,url:record.url,suggestedFilename:record.suggestedFilename,frameId:record.frameId,targetId:record.targetId})}#downloadProgress(info={}){if(!info.guid)return;const alreadyTerminal=this.#terminal.has(info.guid);const record=this.#downloads.get(info.guid)??this.#terminalDownloads.get(info.guid);if(!record)return;Object.assign(record,{totalBytes:Number(info.totalBytes??0),receivedBytes:Number(info.receivedBytes??0),filePath:info.filePath??null});this.#dispatch("downloadProgress",{guid:info.guid,totalBytes:record.totalBytes,receivedBytes:record.receivedBytes,state:info.state,filePath:record.filePath,targetId:record.targetId});if((info.state==="completed"||info.state==="canceled")&&!alreadyTerminal){this.#rememberTerminal(info.guid);this.#downloads.delete(info.guid);this.#terminalDownloads.set(info.guid,record);this.#dispatch("downloadFinished",{guid:info.guid,totalBytes:record.totalBytes,receivedBytes:record.receivedBytes,state:info.state,cdpState:info.state,filePath:record.filePath,targetId:record.targetId,error:null})}}#disconnectDownloads(){if(this.#explicitDispose)return;for(const record of this.#downloads.values()){this.#rememberTerminal(record.guid);this.#dispatch("downloadFinished",{guid:record.guid,totalBytes:record.totalBytes,receivedBytes:record.receivedBytes,state:"failed",cdpState:null,filePath:record.filePath,targetId:record.targetId,error:{code:"browserDisconnected",message:"Browser transport disconnected"}})}this.#downloads.clear()}#dispatch(name,payload){const listeners=[...this.#listeners.get(name)??[]];Promise.allSettled(listeners.map((listener)=>Promise.resolve().then(()=>listener(payload)))).then((results)=>results.filter((r)=>r.status==="rejected").forEach((r)=>console.warn(`[events] ${name} listener failed`,r.reason)))}on(name,listener){this.#assertName(name);if(typeof listener!=="function")throw new TypeError("listener must be a function");const set=this.#listeners.get(name)??new Set;set.add(listener);this.#listeners.set(name,set);return()=>this.off(name,listener)}off(name,listener){this.#assertName(name);this.#listeners.get(name)?.delete(listener)}#cancelWaits(error){for(const cancel of this.#waits)cancel(error);this.#waits.clear()}waitForEvent(name,options={}){this.#assertName(name);if(this.#disposed)return Promise.reject(Error("Events bridge is disposed"));const timeout=options.timeout??DEFAULT_TIMEOUT;return new Promise((resolve,reject)=>{let done=false;let timer;const off=this.on(name,async(value)=>{try{if(options.predicate&&!await options.predicate(value))return;if(done)return;done=true;clearTimeout(timer);this.#waits.delete(cancel);off();resolve(value)}catch(error){if(!done){done=true;clearTimeout(timer);this.#waits.delete(cancel);off();reject(errorValue(error))}}});const cancel=(error)=>{if(!done){done=true;clearTimeout(timer);this.#waits.delete(cancel);off();reject(error??Error("Events bridge disposed"))}};this.#waits.add(cancel);timer=setTimeout(()=>cancel(Error(`Timed out waiting for browser event "${name}"`)),timeout)})}#assertName(name){if(!EVENT_NAMES.includes(name))throw new TypeError(`Unknown browser event: ${String(name)}`)}async dispose(){if(this.#disposed)return;this.#disposed=true;this.#explicitDispose=true;this.#cancelWaits();for(const off of this.#unsubscribers)off();this.#unsubscribers=[];this.#listeners.clear();await this.ready.catch(()=>{})}}function pathIsAbsolute(value){return typeof value==="string"&&(value.startsWith("/")||/^[A-Za-z]:[\\/]/.test(value))}function createEvents(connection,options={}){return new BrowserEventBridge(connection,options)}init_agent_tabs();init_viewport();import{randomUUID}from"node:crypto";import path7 from"node:path";import{mkdir as mkdir4,open as open3,readFile as readFile2,rename,lstat,chmod as chmod2}from"node:fs/promises";import{homedir}from"node:os";import{TextDecoder as TextDecoder2}from"node:util";import{mkdirSync}from"node:fs";import path6 from"node:path";import{tmpdir as tmpdir3}from"node:os";import{existsSync,readFileSync as readFileSync2}from"node:fs";import path5 from"node:path";var DatabaseCtor;async function loadSqlite(){if(DatabaseCtor)return DatabaseCtor;try{const mod=await import("node:sqlite");DatabaseCtor=(file)=>new mod.DatabaseSync(file,{readOnly:true,readBigInts:true})}catch{const mod=await import("bun:sqlite");DatabaseCtor=(file)=>new mod.Database(file,{readonly:true})}return DatabaseCtor}var CHROME_EPOCH_OFFSET_MS=11644473600000;var chromeTimeToMs=(time)=>Number(BigInt(time)/1000n)-CHROME_EPOCH_OFFSET_MS;var msToChromeTime=(ms)=>BigInt(Math.floor((ms+CHROME_EPOCH_OFFSET_MS)*1000));var EXTENSION_BRIDGE_REQUIRED="requires the extension bridge in standalone OmOWright";function createProfileApis(profilePath){function profileDir(){if(!profilePath){throw new UnsupportedOperationError("chrome.bookmarks/history/topSites need a profilePath (the browser's user-data-dir); pass it to createChromeApi(connection, { profilePath })")}return path5.join(profilePath,"Default")}async function openDb(fileName){const file=path5.join(profileDir(),fileName);if(!existsSync(file))return null;const ctor=await loadSqlite();return ctor(file)}async function withDb(fileName,fn){const db=await openDb(fileName);if(!db)return[];try{return fn(db)}finally{db.close()}}function bookmarkRoots(){const file=path5.join(profileDir(),"Bookmarks");if(!existsSync(file))return[];const parsed=JSON.parse(readFileSync2(file,"utf8"));return Object.values(parsed.roots??{}).filter((node)=>node&&typeof node==="object"&&node.id)}function walkBookmarks(nodes,visit,acc=[]){for(const node of nodes){visit(node,acc);if(Array.isArray(node.children))walkBookmarks(node.children,visit,acc)}return acc}function findBookmark(id){for(const root of bookmarkRoots()){const found=walkBookmarks([root],(node,acc)=>{if(node.id===String(id))acc.push(node)});if(found.length>0)return found[0]}return null}function toBookmark(node){const out={id:node.id,title:node.name??"",type:node.type};if(node.url)out.url=node.url;if(node.date_added)out.dateAdded=chromeTimeToMs(node.date_added);if(node.children)out.children=node.children.map(toBookmark);return out}const bookmarks={async get(idOrIdList){const ids=Array.isArray(idOrIdList)?idOrIdList:[idOrIdList];return ids.map(findBookmark).filter(Boolean).map(toBookmark)},async getChildren(id){const node=findBookmark(id);return(node?.children??[]).map(toBookmark)},async getRecent(numberOfItems){const urls=walkBookmarks(bookmarkRoots(),(node,acc)=>{if(node.type==="url")acc.push(node)});return urls.sort((a,b)=>Number(b.date_added??0)-Number(a.date_added??0)).slice(0,numberOfItems).map(toBookmark)},async getSubTree(id){const node=findBookmark(id);return node?[toBookmark(node)]:[]},async getTree(){return bookmarkRoots().map(toBookmark)},async search(query){const needle=typeof query==="string"?query.toLowerCase():(query.query??"").toLowerCase();const url=typeof query==="object"?query.url?.toLowerCase():undefined;const title=typeof query==="object"?query.title?.toLowerCase():undefined;return walkBookmarks(bookmarkRoots(),(node,acc)=>{if(node.type!=="url")return;const name=(node.name??"").toLowerCase();const href=(node.url??"").toLowerCase();const ok=needle&&(name.includes(needle)||href.includes(needle))||url&&href.includes(url)||title&&name.includes(title);if(ok)acc.push(node)}).map(toBookmark)},async create(){throw new UnsupportedOperationError(`chrome.bookmarks.create ${EXTENSION_BRIDGE_REQUIRED}`)},async update(){throw new UnsupportedOperationError(`chrome.bookmarks.update ${EXTENSION_BRIDGE_REQUIRED}`)},async move(){throw new UnsupportedOperationError(`chrome.bookmarks.move ${EXTENSION_BRIDGE_REQUIRED}`)},async remove(){throw new UnsupportedOperationError(`chrome.bookmarks.remove ${EXTENSION_BRIDGE_REQUIRED}`)},async removeTree(){throw new UnsupportedOperationError(`chrome.bookmarks.removeTree ${EXTENSION_BRIDGE_REQUIRED}`)}};const history={async search(query){const text=`%${query.text??""}%`;const start=query.startTime!=null?msToChromeTime(query.startTime):0n;const end=query.endTime!=null?msToChromeTime(query.endTime):99999999999999999n;const max=query.maxResults??100;return await withDb("History",(db)=>db.prepare(`SELECT id, url, title, visit_count, last_visit_time FROM urls
1500
1500
  WHERE (url LIKE ? OR title LIKE ?) AND last_visit_time BETWEEN ? AND ?
1501
1501
  ORDER BY last_visit_time DESC LIMIT ?`).all(text,text,start,end,max).map((row)=>({id:String(row.id),url:row.url,title:row.title??"",visitCount:Number(row.visit_count),lastVisitTime:chromeTimeToMs(row.last_visit_time)})))},async getVisits(details){return await withDb("History",(db)=>db.prepare(`SELECT v.id, v.visit_time, v.transition FROM visits v
1502
1502
  JOIN urls u ON u.id = v.url WHERE u.url = ?
@@ -1519,7 +1519,8 @@ ${snapshot.tree}`}}var DEFAULT_TIMEOUT_MS=30000;var JSON_MIME=/^application\/jso
1519
1519
  `)}function dispose(){if(disposed)return;disposed=true;for(const off of unsubscribers.splice(0,unsubscribers.length))off();for(const waiter of[...waiters])waiter.reject(Error("Network snoop disposed"));waiters.clear();inflight.clear()}return{pop,peek,popJson,waitFor,summary,dispose}}function globToRegExp(glob){let source="";for(const char of glob){if(char==="*")source+=".*";else if(char==="?")source+=".";else if("\\^$+{}[]()|.".includes(char))source+=`\\${char}`;else source+=char}return new RegExp(`^${source}$`)}function hasGlobChars(value){return value.includes("*")||value.includes("?")}function matches(match,request){if(typeof match==="function"){return Boolean(match({url:request.url,method:request.method,resourceType:request.resourceType,headers:request.headers}))}if(match instanceof RegExp)return match.test(request.url);if(typeof match==="string"){if(hasGlobChars(match))return globToRegExp(match).test(request.url);return request.url.includes(match)}return false}function sameMatch(left,right){if(left===right)return true;if(left instanceof RegExp&&right instanceof RegExp)return left.source===right.source&&left.flags===right.flags;return false}function toHeaderEntries(headers){if(!headers)return[];if(Array.isArray(headers)){return headers.map((header)=>({name:String(header.name),value:String(header.value??"")}))}return Object.entries(headers).map(([name,value])=>({name,value:String(value??"")}))}function encodeBody(body){if(body==null)return"";if(Buffer.isBuffer(body))return body.toString("base64");if(body instanceof Uint8Array)return Buffer.from(body).toString("base64");return Buffer.from(String(body)).toString("base64")}function buildContinue(requestId,{url,method,headers,postData}={}){const params={requestId};if(url!==undefined)params.url=url;if(method!==undefined)params.method=method;if(headers!==undefined)params.headers=toHeaderEntries(headers);if(postData!==undefined)params.postData=encodeBody(postData);return params}function buildFulfill(requestId,{status=200,headers={},contentType,body=""}={}){const responseHeaders=toHeaderEntries(headers);if(contentType!=null&&String(contentType).length>0){const hasType=responseHeaders.some((header)=>header.name.toLowerCase()==="content-type");if(!hasType)responseHeaders.push({name:"content-type",value:String(contentType)})}return{requestId,responseCode:status,responseHeaders,body:encodeBody(body)}}function createRoutes(page){if(!page?.cdp)throw new TypeError("createRoutes requires a page with cdp");const entries=[];const warned=new WeakSet;const settledIds=new Set;let enabled=false;let sessionId;let unsubscribe=null;let enablePromise=null;function warnOnce(handler,error){if(typeof handler==="function"){if(warned.has(handler))return;warned.add(handler)}if(error)console.warn("[routes] route handler failed; continuing request",error);else console.warn("[routes] route handler did not settle the request; continuing")}function findHandler(request){for(const entry of entries){if(matches(entry.match,request))return entry.handler}return}async function handlePaused(params,meta){if(!sessionId||meta?.sessionId!==sessionId)return;const requestId=params?.requestId;if(!requestId||settledIds.has(requestId))return;const request={url:params.request?.url??"",method:params.request?.method??"GET",headers:params.request?.headers??{},postData:params.request?.postData,resourceType:params.resourceType};let settled=false;const settle=(method,cdpParams)=>{if(settled)return Promise.resolve();settled=true;settledIds.add(requestId);return page.cdp.send(method,cdpParams,sessionId)};const route={request,continue(overrides={}){return settle("Fetch.continueRequest",buildContinue(requestId,overrides))},fulfill(options={}){return settle("Fetch.fulfillRequest",buildFulfill(requestId,options))},abort(errorReason="Failed"){return settle("Fetch.failRequest",{requestId,errorReason})}};const handler=findHandler(request);if(!handler){await settle("Fetch.continueRequest",{requestId});return}try{await handler(route)}catch(error){warnOnce(handler,error)}if(!settled){warnOnce(handler);await settle("Fetch.continueRequest",{requestId})}}function onPaused(params,meta){handlePaused(params,meta).catch((error)=>{console.warn("[routes] requestPaused handling failed",error)})}async function ensureEnabled(){if(enabled)return;if(!enablePromise){enablePromise=(async()=>{sessionId=await page.resolveSessionId();if(!unsubscribe)unsubscribe=page.cdp.on("Fetch.requestPaused",onPaused);await page.cdp.send("Fetch.enable",{patterns:[{urlPattern:"*",requestStage:"Request"}]},sessionId);enabled=true})()}try{await enablePromise}catch(error){enablePromise=null;throw error}}async function disableFetch(){const pending=enablePromise;enablePromise=null;if(pending)await pending.catch(()=>{});if(unsubscribe){unsubscribe();unsubscribe=null}if(!enabled)return;enabled=false;await page.cdp.send("Fetch.disable",{},sessionId)}async function route(match,handler){if(typeof handler!=="function")throw new TypeError("handler must be a function");entries.push({match,handler});await ensureEnabled()}async function unroute(match){if(match===undefined)entries.length=0;else{for(let index=entries.length-1;index>=0;index-=1){if(sameMatch(entries[index].match,match))entries.splice(index,1)}}if(entries.length===0)await disableFetch()}async function dispose(){entries.length=0;await disableFetch()}return{route,unroute,dispose,get enabled(){return enabled}}}async function viewportOf(page){return page.viewportSize()??await page.evaluate(()=>({width:innerWidth,height:innerHeight}))}async function scrollOnce(page,mode){if(mode==="end"){await page.evaluate(()=>scrollTo(0,document.scrollingElement.scrollHeight));return}const viewport=await viewportOf(page);await page.mouse.move(Math.floor(viewport.width/2),Math.floor(viewport.height/2));await page.mouse.wheel(0,Math.floor(viewport.height*0.9))}async function*collectWhileScrolling(page,snoop,options={}){const{minItems=Infinity,maxScrolls=10,extract=(json)=>Array.isArray(json)?json:[],scroll="wheel",settleMs=800}=options;if(scroll!=="wheel"&&scroll!=="end")throw new TypeError(`Unknown scroll mode: ${String(scroll)}`);let rounds=0;let total=0;let lastHeight=null;let unchanged=0;let stoppedBecause="maxScrolls";for(;;){for(const json of snoop.popJson()){for(const item of extract(json)){total+=1;yield item}}if(total>=minItems){stoppedBecause="minItems";break}if(rounds>=maxScrolls){stoppedBecause="maxScrolls";break}const height=await page.evaluate(()=>document.scrollingElement.scrollHeight);if(lastHeight!==null&&height===lastHeight){unchanged+=1;if(unchanged>=2){stoppedBecause="noGrowth";break}}else{unchanged=0}lastHeight=height;await scrollOnce(page,scroll);rounds+=1;await new Promise((resolve)=>setTimeout(resolve,settleMs))}return{rounds,total,stoppedBecause}}import{mkdir as mkdir5,writeFile as writeFile2}from"node:fs/promises";import path8 from"node:path";var SLUG_MAX=48;var SNOOP_MAX_ENTRIES=5000;function slug(name){const value=String(name??"").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"");return(value||"step").slice(0,SLUG_MAX)}function messageOf(error){return error instanceof Error?error.message:String(error)}async function readConsole(page){const api=page.console;const entries=typeof api==="function"?await api.call(page):await api?.logs?.();return Array.isArray(entries)?entries:[]}function createTrace(page,options={}){if(!page?.cdp||typeof page.resolveSessionId!=="function"){throw new TypeError("createTrace requires a page with cdp and resolveSessionId")}const{dir,screenshots=true,network=true,console:consoleLogs=true,maxBodyBytes=1e6,events}=options;if(typeof dir!=="string"||dir.length===0)throw new TypeError("createTrace requires a dir");const shotDir=path8.join(dir,"screenshots");const ready=mkdir5(shotDir,{recursive:true});const startedAt=Date.now();const records=[];const netEntries=[];const unsubscribers=[];const snoop=network?createNetworkSnoop(page,{bodies:true,maxBodyBytes,maxEntries:SNOOP_MAX_ENTRIES}):null;let seq=0;let consoleSeen=0;let sessionId=null;let stopped=false;let stopping=null;function record(event){const entry={...event,ts:Date.now(),seq:seq++};records.push(entry);return entry}const sessionReady=Promise.resolve(page.resolveSessionId()).then((id)=>{sessionId=id});sessionReady.catch((error)=>record({kind:"warning",scope:"session",message:messageOf(error)}));unsubscribers.push(page.cdp.on("Page.frameNavigated",(params,meta)=>{if(stopped||params?.frame?.parentId)return;const add=()=>record({kind:"navigation",url:params?.frame?.url??"",frameId:params?.frame?.id??null});if(sessionId===null){sessionReady.then(()=>{if(!stopped&&meta?.sessionId===sessionId)add()}).catch(()=>{});return}if(meta?.sessionId===sessionId)add()}));unsubscribers.push(page.on("dialog",(dialog)=>record({kind:"dialog",type:dialog?.type??null,message:dialog?.message??"",defaultPrompt:dialog?.defaultPrompt??"",url:dialog?.url??null})));if(events){unsubscribers.push(events.on("downloadStarted",(payload)=>record({kind:"download",phase:"started",...payload})));unsubscribers.push(events.on("downloadFinished",(payload)=>record({kind:"download",phase:"finished",...payload})))}async function capture(step,phase){const file=path8.join(shotDir,`${step.seq}-${slug(step.name)}-${phase}.png`);try{await writeFile2(file,await page.screenshot({type:"png"}));step.screenshots[phase]=file}catch(error){record({kind:"warning",scope:`screenshot:${phase}`,step:step.seq,message:messageOf(error)})}}function drainNetwork(){for(const entry of snoop?.pop()??[]){if(typeof entry.body==="string"&&Buffer.byteLength(entry.body)>maxBodyBytes){entry.body=null;entry.bodySkipped=true}netEntries.push(entry);record({kind:"network",...entry})}}async function drainConsole(){if(!consoleLogs)return;let entries;try{entries=await readConsole(page)}catch{return}if(entries.length<consoleSeen)consoleSeen=0;for(const entry of entries.slice(consoleSeen))record({kind:"console",...entry});consoleSeen=entries.length}async function drain(){drainNetwork();await drainConsole()}async function step(name,fn){if(typeof fn!=="function")throw new TypeError("trace.step requires a function");await ready;const entry=record({kind:"step",name:String(name),startedAt:Date.now(),screenshots:{}});if(screenshots)await capture(entry,"before");try{return await fn()}catch(error){entry.error=messageOf(error);throw error}finally{entry.endedAt=Date.now();entry.durationMs=entry.endedAt-entry.startedAt;if(screenshots)await capture(entry,"after");await drain()}}function mark(name,data){return record({kind:"mark",name:String(name),data:data??null})}async function finish(){await ready;await drain();stopped=true;for(const off of unsubscribers.splice(0,unsubscribers.length))off();snoop?.dispose();const ordered=[...records].sort((a,b)=>a.seq-b.seq);const summary={steps:ordered.filter((entry)=>entry.kind==="step").length,events:ordered.length,network:netEntries.length,startedAt,endedAt:Date.now(),url:page.url()};await writeFile2(path8.join(dir,"trace.jsonl"),ordered.map((entry)=>`${JSON.stringify(entry)}
1520
1520
  `).join(""));await writeFile2(path8.join(dir,"trace.har"),`${JSON.stringify(toHar(netEntries),null,2)}
1521
1521
  `);await writeFile2(path8.join(dir,"summary.json"),`${JSON.stringify(summary,null,2)}
1522
- `);return summary}function stop(){return stopping??=finish()}return{step,mark,stop,get dir(){return dir}}}import{connect as connect2}from"node:net";import{randomBytes}from"node:crypto";import{existsSync as existsSync2,readFileSync as readFileSync3}from"node:fs";import{homedir as homedir2}from"node:os";import path9 from"node:path";import{spawn as spawn3}from"node:child_process";function resolveBskHome(env=process.env){const override=env.BSK_HOME;if(typeof override==="string"&&override.length>0)return override;const home=env.HOME&&env.HOME.length>0?env.HOME:homedir2();return path9.join(home,".bsk")}function daemonInfoPath(home){return path9.join(home,"daemon.json")}function readDaemonInfo(home){let raw;try{raw=readFileSync3(daemonInfoPath(home),"utf8")}catch{return null}let info;try{info=JSON.parse(raw)}catch{return null}if(!info||typeof info.sock_path!=="string"||info.sock_path.length===0)return null;return info}function findBskBinary(env=process.env){const home=env.HOME&&env.HOME.length>0?env.HOME:homedir2();const candidates=[env.BSK_BIN,path9.join(home,".local","bin",process.platform==="win32"?"bsk.exe":"bsk")].filter(Boolean);for(const candidate of candidates)if(existsSync2(candidate))return candidate;return"bsk"}function startDaemonWithCli({home,env=process.env,bskBin=findBskBinary(env),timeoutMs=20000}={}){return new Promise((resolve,reject)=>{const child=spawn3(bskBin,["status","--json"],{env:{...env,BSK_HOME:home},stdio:["ignore","pipe","pipe"],windowsHide:true});let stderr="";child.stderr.on("data",(chunk)=>{stderr=(stderr+chunk).slice(-4096)});child.stdout.on("data",()=>{});const timer=setTimeout(()=>{child.kill("SIGKILL");reject(new Error(`bsk status did not return within ${timeoutMs}ms`))},timeoutMs);child.on("error",(error)=>{clearTimeout(timer);reject(new Error(`could not run ${bskBin}: ${error.message}`))});child.on("exit",(code)=>{clearTimeout(timer);if(code===0)resolve();else reject(new Error(`bsk status exited with ${code}${stderr?`: ${stderr.trim()}`:""}`))})})}var DEFAULT_TIMEOUT_MS2=35000;var DISCOVERY_RETRY_MS=250;var DISCOVERY_BUDGET_MS=15000;class BskRpcError extends Error{constructor(code,message,data){super(message);this.name="BskRpcError";this.code=code;this.data=data}}function shortId(){return randomBytes(6).toString("hex")}function sleep2(ms){return new Promise((resolve)=>setTimeout(resolve,ms))}class BskIpcClient{#home;#sockPath;#autoStart;#startDaemon;#timeoutMs;#starting=null;constructor({home,env=process.env,sockPath,autoStart,startDaemon,timeoutMs=DEFAULT_TIMEOUT_MS2,bskBin}={}){this.#home=home??resolveBskHome(env);this.#sockPath=sockPath??null;this.#autoStart=autoStart??env.BSK_AUTO_START!=="0";this.#startDaemon=startDaemon??(()=>startDaemonWithCli({home:this.#home,env,bskBin}));this.#timeoutMs=timeoutMs}get home(){return this.#home}async resolveSocket({isAborted=()=>false}={}){if(this.#sockPath)return this.#sockPath;if(process.platform==="win32"){throw new BskRpcError("unsupported","Windows named-pipe discovery is not implemented; pass { sockPath: '\\\\\\\\.\\\\pipe\\\\...' } explicitly")}const found=readDaemonInfo(this.#home);if(found)return found.sock_path;if(!this.#autoStart){throw new BskRpcError("no_daemon",`no BrowserSkill daemon published at ${this.#home}/daemon.json and automatic start is disabled (BSK_AUTO_START=0); run \`bsk daemon start\` in the environment that owns it`)}if(!this.#starting)this.#starting=this.#startDaemon().finally(()=>{this.#starting=null});await this.#starting;const deadline=Date.now()+DISCOVERY_BUDGET_MS;while(Date.now()<deadline&&!isAborted()){const info=readDaemonInfo(this.#home);if(info)return info.sock_path;await sleep2(DISCOVERY_RETRY_MS)}if(isAborted())throw new BskRpcError("cancelled","cancelled while waiting for the daemon to publish daemon.json");throw new BskRpcError("no_daemon",`daemon start was requested but ${this.#home}/daemon.json never appeared`)}call(method,params,options={}){return this.callWithHandle(method,params,options).promise}callWithHandle(method,params,{idPrefix=method.replace(/[^a-z0-9]+/gi,"-"),timeoutMs=this.#timeoutMs}={}){const rpcId=`${idPrefix}-${shortId()}`;const frame={id:rpcId,method};if(params!==undefined)frame.params=params;let aborted=false;let abortSocket=()=>{};const promise=(async()=>{const sockPath=await this.resolveSocket({isAborted:()=>aborted});if(aborted)throw new BskRpcError("cancelled",`${method} cancelled by caller`);return new Promise((resolve,reject)=>{const socket=connect2(sockPath);let buffer="";let settled=false;const finish=(fn,value)=>{if(settled)return;settled=true;clearTimeout(timer);socket.destroy();fn(value)};const timer=setTimeout(()=>finish(reject,new BskRpcError("timeout",`${method} did not answer within ${timeoutMs}ms`)),timeoutMs);abortSocket=()=>finish(reject,new BskRpcError("cancelled",`${method} cancelled by caller`));socket.on("error",(error)=>finish(reject,new BskRpcError("no_daemon",`IPC socket ${sockPath}: ${error.code??error.message}`)));socket.on("connect",()=>socket.write(JSON.stringify(frame)+`
1522
+ `);return summary}function stop(){return stopping??=finish()}return{step,mark,stop,get dir(){return dir}}}import{connect as connect2}from"node:net";import{existsSync as existsSync3}from"node:fs";import path10 from"node:path";import{randomBytes}from"node:crypto";import{existsSync as existsSync2,readFileSync as readFileSync3}from"node:fs";import{homedir as homedir2}from"node:os";import path9 from"node:path";import{spawn as spawn3}from"node:child_process";import{createHash}from"node:crypto";function resolveBskHome(env=process.env){const override=env.BSK_HOME;if(typeof override==="string"&&override.length>0)return override;const home=env.HOME&&env.HOME.length>0?env.HOME:homedir2();return path9.join(home,".bsk")}function daemonInfoPath(home){return path9.join(home,"daemon.json")}function readDaemonInfo(home){let raw;try{raw=readFileSync3(daemonInfoPath(home),"utf8")}catch{return null}let info;try{info=JSON.parse(raw)}catch{return null}if(!info||typeof info.sock_path!=="string"||info.sock_path.length===0)return null;return info}function findBskBinary(env=process.env){const home=env.HOME&&env.HOME.length>0?env.HOME:homedir2();const candidates=[env.BSK_BIN,path9.join(home,".local","bin",process.platform==="win32"?"bsk.exe":"bsk")].filter(Boolean);for(const candidate of candidates)if(existsSync2(candidate))return candidate;return"bsk"}var NO_BREAKAWAY=/Job Object/;function windowsDaemonTaskName(home){return`bsk-daemon-${createHash("sha256").update(path9.resolve(home).toLowerCase()).digest("hex").slice(0,12)}`}function windowsDaemonLaunch(home,bskBin){const literal=(value)=>`'${value.replace(/['\u2018\u2019\u201A\u201B]/g,(quote)=>quote+quote)}'`;const script=`$env:BSK_HOME = ${literal(home)}; & ${literal(bskBin)} daemon start --foreground; exit $LASTEXITCODE`;return`--headless powershell.exe -NoProfile -NonInteractive -EncodedCommand ${Buffer.from(script,"utf16le").toString("base64")}`}function startDaemonInWindowsTask({home,bskBin,timeoutMs}){const script=["$action = New-ScheduledTaskAction -Execute (Join-Path $env:SystemRoot 'System32\\conhost.exe') -Argument $env:BSK_TASK_ARGUMENT","$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit ([TimeSpan]::Zero) -MultipleInstances IgnoreNew","Register-ScheduledTask -TaskName $env:BSK_TASK_NAME -Action $action -Settings $settings -Force | Out-Null","Start-ScheduledTask -TaskName $env:BSK_TASK_NAME"].join("; ");return runToExit("powershell.exe",["-NoProfile","-NonInteractive","-Command",script],{env:{...process.env,BSK_TASK_NAME:windowsDaemonTaskName(home),BSK_TASK_ARGUMENT:windowsDaemonLaunch(home,bskBin)},timeoutMs,label:"registering the bsk daemon task"})}function runToExit(command,args,{env,timeoutMs,label}){return new Promise((resolve,reject)=>{const child=spawn3(command,args,{env,stdio:["ignore","pipe","pipe"],windowsHide:true});let output="";const keep=(chunk)=>{output=(output+chunk).slice(-4096)};child.stdout.on("data",keep);child.stderr.on("data",keep);const timer=setTimeout(()=>{child.kill("SIGKILL");reject(new Error(`${label} did not return within ${timeoutMs}ms`))},timeoutMs);child.on("error",(error)=>{clearTimeout(timer);reject(new Error(`could not run ${command}: ${error.message}`))});child.on("exit",(code)=>{clearTimeout(timer);resolve({code,output:output.trim()})})})}async function startDaemonWithCli({home,env=process.env,bskBin=findBskBinary(env),timeoutMs=20000}={}){const status=await runToExit(bskBin,["status","--json"],{env:{...env,BSK_HOME:home},timeoutMs,label:"bsk status"});if(status.code===0)return;if(process.platform==="win32"&&NO_BREAKAWAY.test(status.output)){const task=await startDaemonInWindowsTask({home,bskBin,timeoutMs});if(task.code===0)return;throw new Error(`registering the bsk daemon task exited with ${task.code}${task.output?`: ${task.output}`:""}`)}throw new Error(`bsk status exited with ${status.code}${status.output?`: ${status.output}`:""}`)}var DEFAULT_TIMEOUT_MS2=35000;var DISCOVERY_RETRY_MS=250;var DISCOVERY_BUDGET_MS=15000;var TOOL_TIMEOUT_GRACE_MS=5000;var HUMAN_WAIT_METHODS=new Set(["tool.request_help","tool.tab_borrow"]);var HUMAN_WAIT_BACKSTOP_MS=3600000;function ownBudgetMs(params){const budgets=[params?.timeout_ms,params?.confirmation_timeout_ms].filter((value)=>typeof value==="number");return budgets.length>0?Math.max(...budgets)+TOOL_TIMEOUT_GRACE_MS:undefined}var PROBE_TIMEOUT_MS=2000;function defaultSocketPath(home){return path10.join(home,"run","daemon.sock")}function probeSocket(sockPath){if(process.platform==="win32"||!existsSync3(sockPath))return Promise.resolve(false);return new Promise((resolve)=>{const socket=connect2(sockPath);const done=(alive)=>{clearTimeout(timer);socket.destroy();resolve(alive)};const timer=setTimeout(()=>done(false),PROBE_TIMEOUT_MS);socket.on("error",()=>done(false));socket.on("connect",()=>socket.write(JSON.stringify({id:"probe-orphan",method:"system.ping"})+`
1523
+ `));socket.on("data",()=>done(true))})}class BskRpcError extends Error{constructor(code,message,data){super(message);this.name="BskRpcError";this.code=code;this.data=data}}function shortId(){return randomBytes(6).toString("hex")}function sleep2(ms){return new Promise((resolve)=>setTimeout(resolve,ms))}class BskIpcClient{#home;#sockPath;#autoStart;#startDaemon;#timeoutMs;#starting=null;constructor({home,env=process.env,sockPath,autoStart,startDaemon,timeoutMs=DEFAULT_TIMEOUT_MS2,bskBin}={}){this.#home=home??resolveBskHome(env);this.#sockPath=sockPath??null;this.#autoStart=autoStart??env.BSK_AUTO_START!=="0";this.#startDaemon=startDaemon??(()=>startDaemonWithCli({home:this.#home,env,bskBin}));this.#timeoutMs=timeoutMs}get home(){return this.#home}async resolveSocket({isAborted=()=>false}={}){if(this.#sockPath)return this.#sockPath;const found=readDaemonInfo(this.#home);if(found)return found.sock_path;const orphan=defaultSocketPath(this.#home);if(existsSync3(orphan)){if(await probeSocket(orphan))return orphan;if(isAborted())throw new BskRpcError("cancelled","cancelled before the daemon was located")}if(!this.#autoStart){throw new BskRpcError("no_daemon",`no BrowserSkill daemon published at ${this.#home}/daemon.json and automatic start is disabled (BSK_AUTO_START=0); run \`bsk daemon start\` in the environment that owns it`)}if(!this.#starting)this.#starting=this.#startDaemon().finally(()=>{this.#starting=null});await this.#starting;const deadline=Date.now()+DISCOVERY_BUDGET_MS;while(Date.now()<deadline&&!isAborted()){const info=readDaemonInfo(this.#home);if(info)return info.sock_path;await sleep2(DISCOVERY_RETRY_MS)}if(isAborted())throw new BskRpcError("cancelled","cancelled while waiting for the daemon to publish daemon.json");throw new BskRpcError("no_daemon",`daemon start was requested but ${this.#home}/daemon.json never appeared`)}call(method,params,options={}){return this.callWithHandle(method,params,options).promise}callWithHandle(method,params,{idPrefix=method.replace(/[^a-z0-9]+/gi,"-"),timeoutMs}={}){const ownBudget=ownBudgetMs(params);const humanWait=HUMAN_WAIT_METHODS.has(method);const waitMs=humanWait&&ownBudget===undefined&&timeoutMs===undefined?HUMAN_WAIT_BACKSTOP_MS:Math.max(timeoutMs??this.#timeoutMs,ownBudget??0);const rpcId=`${idPrefix}-${shortId()}`;const frame={id:rpcId,method};if(params!==undefined)frame.params=params;let aborted=false;let abortSocket=()=>{};const promise=(async()=>{const sockPath=await this.resolveSocket({isAborted:()=>aborted});if(aborted)throw new BskRpcError("cancelled",`${method} cancelled by caller`);return new Promise((resolve,reject)=>{const socket=connect2(sockPath);let buffer="";let settled=false;const finish=(fn,value)=>{if(settled)return;settled=true;clearTimeout(timer);socket.destroy();fn(value)};const timer=setTimeout(()=>{const cancels=method!=="cancel"&&!humanWait;finish(reject,new BskRpcError("timeout",`${method} did not answer within ${waitMs}ms${cancels?"; a cancel was requested at the daemon":""}`));if(cancels)this.cancel(rpcId).catch(()=>{})},waitMs);abortSocket=()=>finish(reject,new BskRpcError("cancelled",`${method} cancelled by caller`));socket.on("error",(error)=>finish(reject,new BskRpcError("no_daemon",`IPC socket ${sockPath}: ${error.code??error.message}`)));socket.on("connect",()=>socket.write(JSON.stringify(frame)+`
1523
1524
  `));socket.on("data",(chunk)=>{buffer+=chunk.toString("utf8");const idx=buffer.indexOf(`
1524
1525
  `);if(idx===-1)return;try{finish(resolve,decodeResponse(buffer.slice(0,idx),rpcId,method))}catch(error){finish(reject,error)}});socket.on("close",()=>finish(reject,new BskRpcError("protocol_error",`${method}: daemon closed the connection without a reply`)))})})();return{rpcId,promise,abort:()=>{aborted=true;abortSocket()}}}async cancel(rpcId){return this.call("cancel",{rpc_id:rpcId},{idPrefix:"cancel",timeoutMs:2000})}}function decodeResponse(line,rpcId,method){let reply;try{reply=JSON.parse(line)}catch{throw new BskRpcError("protocol_error",`${method}: daemon reply is not JSON: ${line.slice(0,120)}`)}const undecodableFrame=reply.id==="0"&&reply.error;if(reply.id!==rpcId&&!undecodableFrame){throw new BskRpcError("protocol_error",`${method}: reply id ${reply.id} does not match request ${rpcId}`)}if(reply.error){const{code="protocol_error",message="daemon error",data}=reply.error;throw new BskRpcError(code,message,data)}return reply.result}var REF_SHAPE=/^@?e\d+$/;function resolveTarget(target){if(typeof target==="string"){return REF_SHAPE.test(target)?{ref:target}:{selector:target}}if(target&&typeof target==="object"){if(typeof target.ref==="string")return{ref:target.ref};if(typeof target.selector==="string")return{selector:target.selector};if(typeof target.captureId==="string"){return{capture_id:target.captureId,image_x:target.x,image_y:target.y}}}throw new TypeError("target must be a ref ('e3' / '@e3'), a CSS selector, or {captureId, x, y}")}function optionalTarget(target){return target===undefined?{}:resolveTarget(target)}function compact(params){const out={};for(const[key,value]of Object.entries(params))if(value!==undefined)out[key]=value;return out}async function captureScreenshot(session,{ref,tabId,fullPage=false,scope,timeoutMs}={}){if(!fullPage){const result=await session.tool("screenshot",{ref,tab_id:tabId});return{buffer:Buffer.from(result.image_base64,"base64"),width:result.width,height:result.height,format:result.format,captureId:result.capture_id??null,captureUnavailable:result.capture_unavailable??null,tabId:result.tab_id,dialogs:result.dialogs??[]}}const started=await session.tool("screenshot_full_page",{scope,tab_id:tabId,timeout_ms:timeoutMs});const buffer=await readCapture(session,started.capture_id,started.byte_size);return{buffer,width:started.width,height:started.height,format:started.format,captureId:started.capture_id,scope:started.scope??scope??null,tabId:started.tab_id,dialogs:started.dialogs??[]}}async function readCapture(session,captureId,byteSize){const chunks=[];let offset=0;try{for(;;){const chunk=await session.tool("screenshot_read",{capture_id:captureId,offset});const bytes=Buffer.from(chunk.data_base64??"","base64");chunks.push(bytes);if(chunk.eof||bytes.length===0)break;if(chunk.next_offset<=offset)throw new Error(`screenshot_read did not advance past offset ${offset}`);offset=chunk.next_offset}}finally{await session.tool("screenshot_release",{capture_id:captureId}).catch(()=>{})}const buffer=Buffer.concat(chunks);if(typeof byteSize==="number"&&buffer.length!==byteSize){throw new Error(`screenshot capture ${captureId}: expected ${byteSize} bytes, assembled ${buffer.length}`)}return buffer}var SESSION_STOP_TIMEOUT_MS=3600000;class BskSession{#client;#stopped=false;constructor(client,{sessionId,browserInstanceId=null,agentWindowId=null,interaction=null}={}){if(typeof sessionId!=="string"||sessionId.length===0)throw new TypeError("BskSession requires a sessionId");this.#client=client;this.sessionId=sessionId;this.browserInstanceId=browserInstanceId;this.agentWindowId=agentWindowId;this.interaction=interaction}get client(){return this.#client}get stopped(){return this.#stopped}tool(name,params={}){if(this.#stopped)return Promise.reject(new Error(`BrowserSkill session ${this.sessionId} is stopped`));return this.#client.call(`tool.${name}`,compact({session_id:this.sessionId,...params}),{idPrefix:name.replace(/_/g,"-")})}navigate(url,{tabId,waitUntil,timeoutMs}={}){return this.tool("navigate",{url,tab_id:tabId,wait_until:waitUntil,timeout_ms:timeoutMs})}back({tabId,waitUntil,timeoutMs}={}){return this.tool("navigate_back",{tab_id:tabId,wait_until:waitUntil,timeout_ms:timeoutMs})}forward({tabId,waitUntil,timeoutMs}={}){return this.tool("navigate_forward",{tab_id:tabId,wait_until:waitUntil,timeout_ms:timeoutMs})}reload({tabId,waitUntil,timeoutMs,hard}={}){return this.tool("reload",{tab_id:tabId,wait_until:waitUntil,timeout_ms:timeoutMs,hard})}observe({cursor,tabId,maxDepth,maxTokens,debugSurfaces,probeHover}={}){return this.tool("observe",{cursor,tab_id:tabId,max_depth:maxDepth,max_tokens:maxTokens,debug_surfaces:debugSurfaces,probe_hover:probeHover})}snapshot({tabId,maxDepth,maxTokens}={}){return this.tool("snapshot",{tab_id:tabId,max_depth:maxDepth,max_tokens:maxTokens})}getHtml({ref,tabId,maxBytes}={}){return this.tool("get_html",{ref,tab_id:tabId,max_bytes:maxBytes})}screenshot(options){return captureScreenshot(this,options)}click(target,{button,clickCount,modifiers,tabId,timeoutMs}={}){return this.tool("click",{...resolveTarget(target),button,click_count:clickCount,modifiers,tab_id:tabId,timeout_ms:timeoutMs})}hover(target,{modifiers,settleMs,tabId,timeoutMs}={}){return this.tool("hover",{...resolveTarget(target),modifiers,settle_ms:settleMs,tab_id:tabId,timeout_ms:timeoutMs})}focus(target,{tabId,timeoutMs}={}){return this.tool("focus",{...resolveTarget(target),tab_id:tabId,timeout_ms:timeoutMs})}blur(target,{tabId,timeoutMs}={}){return this.tool("blur",{...resolveTarget(target),tab_id:tabId,timeout_ms:timeoutMs})}fill(target,value,{clearBefore,tabId,timeoutMs}={}){return this.tool("fill",{...resolveTarget(target),value,clear_before:clearBefore,tab_id:tabId,timeout_ms:timeoutMs})}press(key,{target,modifiers,holdMs,tabId,timeoutMs}={}){return this.tool("press",{key,...optionalTarget(target),modifiers,hold_ms:holdMs,tab_id:tabId,timeout_ms:timeoutMs})}select(target,values,{tabId,timeoutMs}={}){return this.tool("select",{...resolveTarget(target),values:Array.isArray(values)?values:[values],tab_id:tabId,timeout_ms:timeoutMs})}scrollTo(target,{tabId,timeoutMs}={}){return this.tool("scroll_to",{...resolveTarget(target),tab_id:tabId,timeout_ms:timeoutMs})}wheel({deltaX=0,deltaY=0,target,modifiers,tabId,timeoutMs}={}){return this.tool("wheel",{...optionalTarget(target),delta_x:deltaX,delta_y:deltaY,modifiers,tab_id:tabId,timeout_ms:timeoutMs})}evaluate(expression,{tabId,awaitPromise,returnByValue,timeoutMs}={}){return this.tool("evaluate",{expression,tab_id:tabId,await_promise:awaitPromise,return_by_value:returnByValue,timeout_ms:timeoutMs})}tabList({scope}={}){return this.tool("tab_list",{scope})}tabCreate({url,active,index}={}){return this.tool("tab_create",{url,active,index})}tabClose(tabId){return this.tool("tab_close",{tab_id:tabId})}tabSelect(tabId){return this.tool("tab_select",{tab_id:tabId})}tabBorrow(tabId,{confirmationTimeoutMs}={}){return this.tool("tab_borrow",{tab_id:tabId,confirmation_timeout_ms:confirmationTimeoutMs})}tabReturn(tabId){return this.tool("tab_return",{tab_id:tabId})}waitForNavigation({tabId,waitUntil,timeoutMs}={}){return this.tool("wait_for_navigation",{tab_id:tabId,wait_until:waitUntil,timeout_ms:timeoutMs})}requestHelp({prompt,title,targets,completionCriteria,tabId,timeoutMs}={}){return this.tool("request_help",{prompt,title,tab_id:tabId,timeout_ms:timeoutMs,targets:targets?.map((t)=>resolveTarget(t)),completion_criteria:completionCriteria})}console({tabId,since,limit,maxTextChars,includeStack}={}){return this.tool("console",{tab_id:tabId,since,limit,max_text_chars:maxTextChars,include_stack:includeStack})}network({tabId,since,limit,maxTextChars}={}){return this.tool("network",{tab_id:tabId,since,limit,max_text_chars:maxTextChars})}resize(width,height){return this.tool("window_resize",{width,height})}emulate({tabId,off,overrides}={}){return this.tool("emulate",{tab_id:tabId,off,overrides})}async stop(){if(this.#stopped)return null;this.#stopped=true;return this.#client.call("session.stop",{session_id:this.sessionId},{idPrefix:"session-stop",timeoutMs:SESSION_STOP_TIMEOUT_MS})}}var DEFAULT_WAIT_FOR_BROWSER_MS=15000;async function listBrowsers(client,{waitForBrowserMs}={}){const status=await client.call("system.status",compact({wait_for_browser_ms:waitForBrowserMs}),{idPrefix:"status"});return status.browsers??[]}async function connectBrowserSkill({client,home,env,sockPath,autoStart,timeoutMs,bskBin,name,browser,width,height,focused=false,waitForBrowserMs=DEFAULT_WAIT_FOR_BROWSER_MS}={}){const ipc=client??new BskIpcClient({home,env,sockPath,autoStart,timeoutMs,bskBin});const browsers=await listBrowsers(ipc,{waitForBrowserMs});if(browsers.length===0){throw new BskRpcError("no_browser_connected","the BrowserSkill daemon is up but no browser extension is connected; open the browser that has the BrowserSkill extension enabled (bskDoctor() explains the remaining step) instead of falling back to a headless browser")}const started=await ipc.call("session.start",compact({task_name:name,browser_instance_id:browser,width,height,focused}),{idPrefix:"session-start"});return new BskSession(ipc,{sessionId:started.session_id,browserInstanceId:started.browser_instance_id??null,agentWindowId:started.agent_window_id??null,interaction:started.interaction??null})}var CSS_PATH_FN=String(function cssPath(el){if(!(el instanceof Element))return null;if(el.getRootNode()!==document)return null;const parts=[];let node=el;while(node&&node.nodeType===1&&node!==document.documentElement){let part=node.localName;if(node.id&&/^[A-Za-z_][\w-]*$/.test(node.id)&&document.querySelectorAll("#"+node.id).length===1){parts.unshift("#"+node.id);return parts.join(" > ")}const parent=node.parentElement;if(parent){const same=Array.from(parent.children).filter((c)=>c.localName===node.localName);if(same.length>1)part+=`:nth-of-type(${same.indexOf(node)+1})`}parts.unshift(part);node=parent}parts.unshift("html");return parts.join(" > ")});function buildSnapshotExpression(options={}){return`(function () {
1525
1526
  const globalThis = {};
@@ -1530,5 +1531,5 @@ ${snapshot.tree}`}}var DEFAULT_TIMEOUT_MS=30000;var JSON_MIME=/^application\/jso
1530
1531
  const css = {};
1531
1532
  for (const ref of Object.keys(result.refs || {})) css[ref] = cssPath(api.elementRegistry.get(ref));
1532
1533
  return { tree: result.tree, refs: result.refs || {}, css, error: result.error };
1533
- })()`}async function bskSnapshot(session,{tabId,timeoutMs,...snapshotOptions}={}){const reply=await session.evaluate(buildSnapshotExpression(snapshotOptions),{tabId,timeoutMs,awaitPromise:false,returnByValue:true});if(!reply||reply.ok===false){const text=reply?.error?.text??"unknown error";throw new Error(`bskSnapshot failed inside the page: ${text}`)}const value=reply.value;if(!value||typeof value.tree!=="string")throw new Error("bskSnapshot: page returned no tree");if(value.error)throw new Error(`bskSnapshot: ${value.error}`);return{tree:value.tree,refs:value.refs,css:value.css}}import{existsSync as existsSync4,mkdirSync as mkdirSync2,readFileSync as readFileSync4,rmSync as rmSync2,writeFileSync as writeFileSync2}from"node:fs";import{spawn as spawn4}from"node:child_process";import path11 from"node:path";import{existsSync as existsSync3}from"node:fs";import{homedir as homedir3}from"node:os";import path10 from"node:path";var BROWSERSKILL_EXTENSION_IDS=Object.freeze({chrome:"hhcmgoofomhgciiibhipgmgkgnoenaoi",edge:"emacgiaaaiojkkpkddmmdfhmokgmnikg"});var STORE_UPDATE_URLS=Object.freeze({chrome:"https://clients2.google.com/service/update2/crx",edge:"https://edge.microsoft.com/extensionwebstorebase/v1/crx"});var STORE_PAGE_URLS=Object.freeze({chrome:`https://chromewebstore.google.com/detail/${BROWSERSKILL_EXTENSION_IDS.chrome}`,edge:`https://microsoftedge.microsoft.com/addons/detail/browserskill/${BROWSERSKILL_EXTENSION_IDS.edge}`});var NON_CHROMIUM_DEFAULTS=Object.freeze([{id:"safari",label:"Safari",match:["com.apple.safari","safarihtml"]},{id:"firefox",label:"Firefox",match:["org.mozilla.firefox","firefox.desktop","firefoxurl","firefoxhtml"]},{id:"zen",label:"Zen",match:["app.zen-browser.zen","zen.desktop","zenhtml"]},{id:"orion",label:"Orion",match:["com.kagi.kagimacos"]}]);var MAC=[{id:"chrome",label:"Google Chrome",store:"chrome",userData:"Library/Application Support/Google/Chrome",app:"Google Chrome",bundleId:"com.google.Chrome"},{id:"edge",label:"Microsoft Edge",store:"edge",userData:"Library/Application Support/Microsoft Edge",app:"Microsoft Edge",bundleId:"com.microsoft.edgemac"},{id:"brave",label:"Brave",store:"chrome",userData:"Library/Application Support/BraveSoftware/Brave-Browser",app:"Brave Browser",bundleId:"com.brave.Browser"},{id:"chromium",label:"Chromium",store:"chrome",userData:"Library/Application Support/Chromium",app:"Chromium",bundleId:"org.chromium.Chromium"},{id:"arc",label:"Arc",store:"chrome",userData:"Library/Application Support/Arc/User Data",app:"Arc",bundleId:"company.thebrowser.Browser"},{id:"dia",label:"Dia",store:"chrome",userData:"Library/Application Support/Dia/User Data",app:"Dia",bundleId:"company.thebrowser.dia"},{id:"vivaldi",label:"Vivaldi",store:"chrome",userData:"Library/Application Support/Vivaldi",app:"Vivaldi",bundleId:"com.vivaldi.Vivaldi"},{id:"opera",label:"Opera",store:"chrome",userData:"Library/Application Support/com.operasoftware.Opera",app:"Opera",bundleId:"com.operasoftware.Opera",externalExtensions:false},{id:"comet",label:"Comet",store:"chrome",userData:"Library/Application Support/Comet",app:"Comet",bundleId:"ai.perplexity.comet"},{id:"aside",label:"Aside",store:"chrome",userData:"Library/Application Support/Aside",app:"Aside",bundleId:"at.studio.AsideBrowser"},{id:"whale",label:"Naver Whale",store:"chrome",userData:"Library/Application Support/Naver/Whale",app:"Naver Whale",bundleId:"com.naver.Whale"}];var LINUX=[{id:"chrome",label:"Google Chrome",store:"chrome",userData:".config/google-chrome",binaries:["/usr/bin/google-chrome","/usr/bin/google-chrome-stable","/opt/google/chrome/chrome"],externalDir:"/opt/google/chrome/extensions",userExternalDir:false,desktopIds:["google-chrome.desktop","google-chrome-stable.desktop","com.google.Chrome.desktop"],processNames:["chrome","google-chrome"]},{id:"chromium",label:"Chromium",store:"chrome",userData:".config/chromium",binaries:["/usr/bin/chromium","/usr/bin/chromium-browser","/snap/bin/chromium"],externalDir:"/usr/share/chromium/extensions",userExternalDir:true,desktopIds:["chromium.desktop","chromium-browser.desktop","chromium_chromium.desktop","org.chromium.Chromium.desktop"],processNames:["chromium","chromium-browse"]},{id:"edge",label:"Microsoft Edge",store:"edge",userData:".config/microsoft-edge",binaries:["/usr/bin/microsoft-edge","/opt/microsoft/msedge/msedge"],externalDir:"/usr/share/microsoft-edge/extensions",userExternalDir:false,desktopIds:["microsoft-edge.desktop","microsoft-edge-stable.desktop","com.microsoft.Edge.desktop"],processNames:["msedge"]},{id:"brave",label:"Brave",store:"chrome",userData:".config/BraveSoftware/Brave-Browser",binaries:["/usr/bin/brave-browser","/opt/brave.com/brave/brave"],externalDir:"/usr/share/brave/extensions",userExternalDir:false,desktopIds:["brave-browser.desktop","com.brave.Browser.desktop"],processNames:["brave"]},{id:"vivaldi",label:"Vivaldi",store:"chrome",userData:".config/vivaldi",binaries:["/usr/bin/vivaldi","/opt/vivaldi/vivaldi"],externalDir:null,userExternalDir:false,externalExtensions:false,desktopIds:["vivaldi-stable.desktop","vivaldi.desktop"],processNames:["vivaldi-bin"]},{id:"opera",label:"Opera",store:"chrome",userData:".config/opera",binaries:["/usr/bin/opera"],externalDir:null,userExternalDir:false,externalExtensions:false,desktopIds:["opera.desktop"],processNames:["opera"]}];var WINDOWS=[{id:"chrome",label:"Google Chrome",store:"chrome",userData:"Google/Chrome/User Data",registryKey:"HKCU\\Software\\Google\\Chrome\\Extensions",binaryRel:"Google/Chrome/Application/chrome.exe",progIds:["chromehtml"],processNames:["chrome.exe"]},{id:"edge",label:"Microsoft Edge",store:"edge",userData:"Microsoft/Edge/User Data",registryKey:"HKCU\\Software\\Microsoft\\Edge\\Extensions",binaryRel:"Microsoft/Edge/Application/msedge.exe",progIds:["msedgehtm"],processNames:["msedge.exe"]},{id:"brave",label:"Brave",store:"chrome",userData:"BraveSoftware/Brave-Browser/User Data",registryKey:"HKCU\\Software\\BraveSoftware\\Brave-Browser\\Extensions",binaryRel:"BraveSoftware/Brave-Browser/Application/brave.exe",progIds:["bravehtml"],processNames:["brave.exe"]},{id:"chromium",label:"Chromium",store:"chrome",userData:"Chromium/User Data",registryKey:"HKCU\\Software\\Chromium\\Extensions",binaryRel:"Chromium/Application/chrome.exe",progIds:["chromiumhtm"],processNames:[]},{id:"vivaldi",label:"Vivaldi",store:"chrome",userData:"Vivaldi/User Data",registryKey:null,externalExtensions:false,binaryRel:"Vivaldi/Application/vivaldi.exe",progIds:["vivaldihtm"],processNames:["vivaldi.exe"]},{id:"whale",label:"Naver Whale",store:"chrome",userData:"Naver/Naver Whale/User Data",registryKey:null,externalExtensions:false,binaryRel:"Naver/Naver Whale/Application/whale.exe",progIds:["whalehtml"],processNames:["whale.exe"]}];var BROWSER_IDS=Object.freeze([...new Set([...MAC,...LINUX,...WINDOWS].map((b)=>b.id))]);function macRow(b,home,exists){const apps=[path10.join("/Applications",`${b.app}.app`),path10.join(home,"Applications",`${b.app}.app`)];const appPath=apps.find((p)=>exists(p))??null;const userDataDir=path10.join(home,b.userData);return{id:b.id,label:b.label,store:b.store,userDataDir,binary:path10.join(appPath??apps[0],"Contents","MacOS",b.app),bundleId:b.bundleId,processNames:apps.map((a)=>path10.join(a,"Contents","MacOS",b.app)),defaultIds:[b.bundleId.toLowerCase()],externalExtensions:b.externalExtensions!==false,installed:appPath!==null||exists(userDataDir),hasProfile:exists(userDataDir)}}function linuxRow(b,home,exists){const userDataDir=path10.join(home,b.userData);const binary=b.binaries.find((p)=>exists(p))??null;return{id:b.id,label:b.label,store:b.store,userDataDir,binary,externalDir:b.externalDir,userExternalDir:b.userExternalDir,processNames:b.processNames,defaultIds:b.desktopIds.map((d)=>d.toLowerCase()),externalExtensions:b.externalExtensions!==false,installed:binary!==null||exists(userDataDir),hasProfile:exists(userDataDir)}}function windowsRow(b,env,home,exists){const localAppData=env.LOCALAPPDATA??path10.join(home,"AppData","Local");const programFiles=[localAppData,env.PROGRAMFILES,env["PROGRAMFILES(X86)"],"C:\\Program Files","C:\\Program Files (x86)"].filter(Boolean);const userDataDir=path10.join(localAppData,b.userData);const binary=programFiles.map((root)=>path10.join(root,b.binaryRel)).find((p)=>exists(p))??null;return{id:b.id,label:b.label,store:b.store,userDataDir,registryKey:b.registryKey,binary,processNames:b.processNames,defaultIds:b.progIds,externalExtensions:b.externalExtensions!==false,installed:binary!==null||exists(userDataDir),hasProfile:exists(userDataDir)}}function catalogBrowsers({platform=process.platform,home=homedir3(),env=process.env,exists=existsSync3}={}){if(platform==="darwin")return MAC.map((b)=>macRow(b,home,exists));if(platform==="linux")return LINUX.map((b)=>linuxRow(b,home,exists));if(platform==="win32")return WINDOWS.map((b)=>windowsRow(b,env,home,exists));return[]}function detectBrowsers(options={}){return catalogBrowsers(options).filter((b)=>b.installed)}var ENABLE_STEP=(browser)=>`Quit ${browser} completely and open it again; a dialog offers to enable "BrowserSkill" — click Enable.`;var BADGE_STEP=(browser)=>`In ${browser}, open the menu (the badge on the toolbar) and enable "BrowserSkill" when prompted; no restart is needed.`;var storeStep=(browser,label)=>`Open ${STORE_PAGE_URLS[browser.store]} in ${label} and click the "Add" button.`;function externalExtensionEntry({platform=process.platform,browser}){const id=BROWSERSKILL_EXTENSION_IDS[browser.store];const updateUrl=STORE_UPDATE_URLS[browser.store];const label=browser.label??browser.id.charAt(0).toUpperCase()+browser.id.slice(1);if(browser.externalExtensions===false){return{kind:"store",needsRestart:false,storeUrl:STORE_PAGE_URLS[browser.store],humanStep:storeStep(browser,label)}}if(platform==="win32"){const key=`${browser.registryKey}\\${id}`;return{kind:"registry",key,needsRestart:false,storeUrl:STORE_PAGE_URLS[browser.store],regAddArgs:["add",key,"/v","update_url","/t","REG_SZ","/d",updateUrl,"/f"],regDeleteArgs:["delete",key,"/f"],humanStep:BADGE_STEP(label)}}const content=JSON.stringify({external_update_url:updateUrl},null,2)+`
1534
- `;if(platform==="linux"&&!browser.userExternalDir){return{kind:"file",path:path11.join(browser.externalDir,`${id}.json`),content,needsRestart:true,storeUrl:STORE_PAGE_URLS[browser.store],humanStep:`Quit ${label} completely and open it again; the extension installs without a prompt.`}}return{kind:"file",path:path11.join(browser.userDataDir,"External Extensions",`${id}.json`),content,needsRestart:true,storeUrl:STORE_PAGE_URLS[browser.store],humanStep:platform==="linux"?`Quit ${label} completely and open it again; the extension installs without a prompt.`:ENABLE_STEP(label)}}function isBlocklisted(browser,id,{readFile=readFileSync4}={}){if(!browser.userDataDir)return false;for(const profile of["Default","Profile 1","Profile 2","Profile 3"]){const file=path11.join(browser.userDataDir,profile,"Preferences");let prefs;try{prefs=JSON.parse(readFile(file,"utf8"))}catch{continue}if(Array.isArray(prefs?.extensions?.external_uninstalls)&&prefs.extensions.external_uninstalls.includes(id))return true}return false}function runRegDefault(args){return new Promise((resolve)=>{const child=spawn4("reg",args,{stdio:["ignore","ignore","pipe"],windowsHide:true});let stderr="";child.stderr.on("data",(c)=>{stderr+=c});child.on("error",(error)=>resolve({code:-1,stderr:error.message}));child.on("exit",(code)=>resolve({code,stderr}))})}async function registerExternalExtension({platform=process.platform,browser,writeFile=writeFileSync2,readFile=readFileSync4,runReg=runRegDefault}={}){const entry=externalExtensionEntry({platform,browser});const id=BROWSERSKILL_EXTENSION_IDS[browser.store];const label=browser.label??browser.id;const storeStep=`Open ${entry.storeUrl} in ${label} and click the "Add" button.`;if(entry.kind==="store")return{...entry,registered:false,alreadyPresent:false,reason:"store_only"};if(isBlocklisted(browser,id,{readFile})){return{...entry,registered:false,alreadyPresent:false,reason:"blocklisted",humanStep:`The extension was removed from ${label} before, so external registration is ignored. ${storeStep}`}}if(entry.kind==="registry"){const result=await runReg(entry.regAddArgs);if(result.code!==0)return{...entry,registered:false,alreadyPresent:false,reason:"reg_failed",detail:result.stderr,humanStep:storeStep};return{...entry,registered:true,alreadyPresent:false}}let alreadyPresent=false;try{alreadyPresent=readFile(entry.path,"utf8")===entry.content}catch{}if(alreadyPresent)return{...entry,registered:true,alreadyPresent:true};try{mkdirSync2(path11.dirname(entry.path),{recursive:true});writeFile(entry.path,entry.content)}catch(error){if(error.code==="EACCES"||error.code==="EPERM"||error.code==="EROFS"){return{...entry,registered:false,alreadyPresent:false,reason:"not_writable",humanStep:`${entry.path} is not writable without administrator rights. ${storeStep}`}}throw error}return{...entry,registered:true,alreadyPresent:false}}async function unregisterExternalExtension({platform=process.platform,browser,runReg=runRegDefault}={}){const entry=externalExtensionEntry({platform,browser});if(entry.kind==="store")return{...entry,removed:false};if(entry.kind==="registry"){const result=await runReg(entry.regDeleteArgs);return{...entry,removed:result.code===0,detail:result.stderr}}const present=existsSync4(entry.path);if(present)rmSync2(entry.path,{force:true});return{...entry,removed:present}}import{execFile}from"node:child_process";import{statSync}from"node:fs";import{homedir as homedir4}from"node:os";import path12 from"node:path";var PROBE_TIMEOUT_MS=3000;function runDefault(command,args){return new Promise((resolve)=>{execFile(command,args,{timeout:PROBE_TIMEOUT_MS,windowsHide:true,maxBuffer:8*1024*1024},(error,stdout)=>{resolve(error?null:String(stdout))})})}function parseMacDefaultBrowser(plistJson){let handlers;try{handlers=JSON.parse(plistJson)?.LSHandlers}catch{return null}if(!Array.isArray(handlers))return null;const pick=(scheme)=>handlers.find((h)=>h?.LSHandlerURLScheme===scheme&&typeof h.LSHandlerRoleAll==="string");const handler=pick("https")??pick("http");return handler?handler.LSHandlerRoleAll.toLowerCase():"com.apple.safari"}function parseWindowsProgId(regOutput){const match=/ProgId\s+REG_SZ\s+(\S+)/i.exec(regOutput??"");return match?match[1].toLowerCase():null}function parseProcessNames(platform,output){const names=new Set;for(const raw of String(output??"").split(/\r?\n/)){const line=raw.trim();if(!line)continue;if(platform==="win32"){const image=/^"([^"]+)"/.exec(line)?.[1];if(image)names.add(image.toLowerCase())}else if(platform==="darwin"){if(/\.app\/Contents\/MacOS\/[^/]+$/.test(line))names.add(line)}else{names.add(path12.basename(line))}}return[...names]}async function probeDefault({platform,home,run}){if(platform==="darwin"){const plist=path12.join(home,"Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist");const json=await run("plutil",["-convert","json","-o","-",plist]);return json===null?null:parseMacDefaultBrowser(json)}if(platform==="linux"){const out=await run("xdg-settings",["get","default-web-browser"]);return out?.trim()?out.trim().toLowerCase():null}if(platform==="win32"){const key="HKCU\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\https\\UserChoice";return parseWindowsProgId(await run("reg",["query",key,"/v","ProgId"]))}return null}async function probeRunning({platform,run}){const out=platform==="win32"?await run("tasklist",["/fo","csv","/nh"]):await run("ps",platform==="darwin"?["-axo","comm="]:["-eo","comm="]);return out===null?null:parseProcessNames(platform,out)}function lastUsedAt(browser,stat){let latest=null;for(const rel of["Local State",path12.join("Default","History"),path12.join("Default","Preferences")]){try{const mtime=stat(path12.join(browser.userDataDir,rel)).mtimeMs;if(latest===null||mtime>latest)latest=mtime}catch{}}return latest}async function probeBrowserSignals({platform=process.platform,home=homedir4(),browsers=[],run=runDefault,stat=statSync}={}){const[defaultBrowser,running]=await Promise.all([probeDefault({platform,home,run}),probeRunning({platform,run})]);const lastUsed=Object.fromEntries(browsers.map((b)=>[b.id,lastUsedAt(b,stat)]));return{defaultBrowser,running,lastUsed}}var RECENT_USE_MS=7*24*60*60*1000;function matchesDefault(browser,defaultId){if(!defaultId)return false;return browser.defaultIds.some((id)=>defaultId===id||defaultId.startsWith(id))}function describeDefault(defaultId,catalog){if(defaultId===null||defaultId===undefined)return{raw:null,id:null,label:null,supported:null};const hit=catalog.find((b)=>matchesDefault(b,defaultId));if(hit)return{raw:defaultId,id:hit.id,label:hit.label,supported:true};const other=NON_CHROMIUM_DEFAULTS.find((b)=>b.match.some((m)=>defaultId===m||defaultId.startsWith(m)));return other?{raw:defaultId,id:other.id,label:other.label,supported:false}:{raw:defaultId,id:null,label:defaultId,supported:false}}function candidateRow(browser,{defaultBrowser,running,lastUsed},now){const isDefault=defaultBrowser.id===browser.id;const isRunning=Array.isArray(running)?browser.processNames.some((n)=>running.includes(n)||running.includes(n.toLowerCase())):null;const last=lastUsed?.[browser.id]??null;const recentlyUsed=last!==null&&now-last<=RECENT_USE_MS;const signals=[isDefault&&"default browser",isRunning&&"running now",recentlyUsed&&"used in the last 7 days"].filter(Boolean);return{id:browser.id,label:browser.label,installed:browser.installed,isDefault,running:isRunning,lastUsedAt:last===null?null:new Date(last).toISOString(),recentlyUsed,signals,browser}}function choose(candidates,primary,confidence,reason,defaultBrowser){return{primary:primary.browser,primaryId:primary.id,confidence,needsChoice:false,reason,defaultBrowser,candidates:candidates.map(({browser,...row})=>row)}}function ask(candidates,reason,defaultBrowser,suggested=null){return{primary:null,primaryId:null,confidence:null,needsChoice:true,reason,suggested,defaultBrowser,candidates:candidates.map(({browser,...row})=>row)}}function identifyBrowser({catalog,signals={},explicit=null,now=Date.now()}){const defaultBrowser=describeDefault(signals.defaultBrowser??null,catalog);const installed=catalog.filter((b)=>b.installed||b.id===explicit);const candidates=installed.map((b)=>candidateRow(b,{defaultBrowser,running:signals.running??null,lastUsed:signals.lastUsed},now));if(explicit){const hit=candidates.find((c)=>c.id===explicit);if(hit)return choose(candidates,hit,"explicit",`${hit.label} was chosen explicitly`,defaultBrowser);return ask(candidates,`"${explicit}" is not a Chromium-family browser this library knows; pick one of the candidates`,defaultBrowser)}if(candidates.length===0)return ask(candidates,"no Chromium-family browser is installed",defaultBrowser);const inUse=candidates.filter((c)=>c.running||c.recentlyUsed);const def=candidates.find((c)=>c.isDefault);if(def&&(def.running||def.recentlyUsed))return choose(candidates,def,"default-and-in-use",`${def.label} is the default browser and ${def.signals.slice(1).join(", ")}`,defaultBrowser);if(def&&inUse.length===0)return choose(candidates,def,"default",`${def.label} is the default browser`,defaultBrowser);if(def)return ask(candidates,`${def.label} is the default browser, but ${inUse.map((c)=>c.label).join(" and ")} ${inUse.length===1?"is":"are"} the one in use`,defaultBrowser);if(defaultBrowser.supported===false){const suggested=inUse.length===1?inUse[0].id:null;return ask(candidates,`the default browser is ${defaultBrowser.label}, which BrowserSkill cannot drive; the user has to name the Chromium browser to use`,defaultBrowser,suggested)}const runningNow=inUse.filter((c)=>c.running);if(inUse.length===1)return choose(candidates,inUse[0],"only-in-use",`${inUse[0].label} is the only browser ${inUse[0].signals.join(", ")}`,defaultBrowser);if(runningNow.length===1)return choose(candidates,runningNow[0],"only-running",`${runningNow[0].label} is the only browser running now`,defaultBrowser);if(inUse.length===0&&candidates.length===1)return choose(candidates,candidates[0],"only-installed",`${candidates[0].label} is the only Chromium-family browser installed`,defaultBrowser);return ask(candidates,inUse.length>1?`several browsers are in use (${inUse.map((c)=>c.label).join(", ")})`:"no browser shows recent use",defaultBrowser)}import{existsSync as existsSync5,readFileSync as readFileSync5,rmSync as rmSync3,writeFileSync as writeFileSync3}from"node:fs";import{homedir as homedir5}from"node:os";import path13 from"node:path";import{spawn as spawn5}from"node:child_process";var INSTALL_SH="https://raw.githubusercontent.com/Tencent/BrowserSkill/main/install.sh";var INSTALL_PS1="https://raw.githubusercontent.com/Tencent/BrowserSkill/main/install.ps1";var RC_FILES=[".zshrc",".bashrc",".profile"];function cliInstallCommand({platform=process.platform,installDir,version}={}){const env={BSK_INSTALL_DIR:installDir};if(version)env.BSK_VERSION=version;if(platform==="win32"){return{command:"powershell",args:["-NoProfile","-ExecutionPolicy","Bypass","-Command",`irm ${INSTALL_PS1} | iex`],env}}return{command:"sh",args:["-c",`curl -fsSL ${INSTALL_SH} | sh`],env}}function runDefault2(command,args,env){return new Promise((resolve)=>{const child=spawn5(command,args,{env:{...process.env,...env},stdio:["ignore","pipe","pipe"],windowsHide:true});let stdout="",stderr="";child.stdout.on("data",(c)=>{stdout=(stdout+c).slice(-8192)});child.stderr.on("data",(c)=>{stderr=(stderr+c).slice(-8192)});child.on("error",(error)=>resolve({code:-1,stdout,stderr:error.message}));child.on("exit",(code)=>resolve({code,stdout,stderr}))})}function snapshotRcFiles(home){const snapshot=new Map;for(const name of RC_FILES){const file=path13.join(home,name);snapshot.set(file,existsSync5(file)?readFileSync5(file):null)}return snapshot}function restoreRcFiles(snapshot){const restored=[];for(const[file,before]of snapshot){const after=existsSync5(file)?readFileSync5(file):null;if(before===null&&after===null)continue;if(before!==null&&after!==null&&before.equals(after))continue;if(before===null)rmSync3(file,{force:true});else writeFileSync3(file,before);restored.push(file)}return restored}async function installBskCli({platform=process.platform,home=homedir5(),installDir=path13.join(home,".local","bin"),version,run=runDefault2}={}){const spec=cliInstallCommand({platform,installDir,version});const rcBefore=platform==="win32"?new Map:snapshotRcFiles(home);const result=await run(spec.command,spec.args,spec.env);const restoredRcFiles=restoreRcFiles(rcBefore);const bskBin=path13.join(installDir,platform==="win32"?"bsk.exe":"bsk");const installed=result.code===0&&existsSync5(bskBin);return{installed,bskBin:installed?bskBin:null,code:result.code,stdout:result.stdout,stderr:result.stderr,restoredRcFiles}}function cliVersionDefault(bskBin){return new Promise((resolve)=>{const child=spawn5(bskBin,["--version"],{stdio:["ignore","pipe","ignore"],windowsHide:true});let out="";child.stdout.on("data",(c)=>{out+=c});child.on("error",()=>resolve(null));child.on("exit",(code)=>resolve(code===0?out.trim():null))})}import{homedir as homedir6}from"node:os";import{existsSync as existsSync6,readFileSync as readFileSync6}from"node:fs";var DEFAULT_WAIT_TOTAL_MS=180000;function resolveBskBin(bskBin,env){if(bskBin===null)return null;return bskBin??findBskBinary(env)}async function cliReport({bskBin,env,cliVersion}){const bin=resolveBskBin(bskBin,env);if(!bin)return{installed:false,bskBin:null,version:null};const raw=await cliVersion(bin);return raw?{installed:true,bskBin:bin,version:raw.replace(/^bsk\s+/,"")}:{installed:false,bskBin:bin,version:null}}async function daemonReport(client,waitForBrowserMs){try{const status=await client.call("system.status",waitForBrowserMs>0?{wait_for_browser_ms:waitForBrowserMs}:{},{idPrefix:"status"});return{running:true,version:status.daemon_version,protocolVersion:status.protocol_version,browsers:status.browsers??[],sessions:status.sessions??[]}}catch(error){return{running:false,error:error.message,code:error.code,browsers:[],sessions:[]}}}var NO_BROWSER_STEP=`No Chromium-family browser was found. Install one (Chrome, Edge, Brave, Arc, ...), then add the extension from ${STORE_PAGE_URLS.chrome}.`;function withRegistration(platform,browser){const entry=externalExtensionEntry({platform,browser});const extensionRegistered=entry.kind==="file"&&existsSync6(entry.path)&&readFileSync6(entry.path,"utf8")===entry.content;return{...browser,registration:entry,extensionRegistered}}async function identify({platform,home,env,exists,browser,signals,probeSignals,now}){const catalog=catalogBrowsers({platform,home,env,exists}).map((b)=>withRegistration(platform,b));const observed=signals??await probeSignals({platform,home,browsers:catalog.filter((b)=>b.hasProfile)});const explicit=browser??env.OMOWRIGHT_BROWSER??null;const{primary,...identification}=identifyBrowser({catalog,signals:observed,explicit:explicit||null,now});const browsers=catalog.filter((b)=>b.installed);const registeredElsewhere=browsers.filter((b)=>b.extensionRegistered&&b.id!==identification.primaryId).map((b)=>b.id);return{browsers,identification:{...identification,registeredElsewhere},primary}}function choiceStep(identification){const listed=identification.candidates.map((c)=>`${c.label} [${c.id}]${c.signals.length?` — ${c.signals.join(", ")}`:""}`).join("; ");const hint=identification.suggested?` Likely ${identification.suggested}, but confirm first.`:"";return`Ask the user which browser they actually use (${identification.reason}). Candidates: ${listed}.${hint} Then re-run with browser: "<id>"; register nothing until they answer.`}function nextStepFor({cli,daemon,browsers,browsersConnected,identification,primary}){if(browsersConnected.length>0)return null;if(!cli.installed)return"Install the bsk CLI (bskOnboard() runs the official installer).";if(!daemon.running)return"Start the BrowserSkill daemon: run `bsk status` once, or `bsk daemon start` in the environment that owns it.";if(browsers.length===0&&!primary)return NO_BROWSER_STEP;if(identification.needsChoice)return choiceStep(identification);if(!primary.extensionRegistered&&primary.registration.kind!=="store")return`Register the BrowserSkill extension for ${primary.label} (bskOnboard() does this), then let the user enable it.`;return primary.registration.humanStep}async function bskDoctor({platform=process.platform,home=homedir6(),env=process.env,client,bskBin,cliVersion=cliVersionDefault,waitForBrowserMs=0,exists=existsSync6,browser,signals,probeSignals=probeBrowserSignals,now=Date.now()}={}){const cli=await cliReport({bskBin,env,cliVersion});const ipc=client??new BskIpcClient({env,bskBin:cli.bskBin??undefined,autoStart:false});const daemon=await daemonReport(ipc,waitForBrowserMs);const{browsers,identification,primary}=await identify({platform,home,env,exists,browser,signals,probeSignals,now});const browsersConnected=daemon.browsers;const report={cli,daemon,browsers,primary:primary??null,identification,browsersConnected,ready:browsersConnected.length>0};report.nextStep=nextStepFor(report);return report}async function bskOnboard({platform=process.platform,home=homedir6(),env=process.env,client,bskBin,installDir,installCli=installBskCli,cliVersion=cliVersionDefault,onHumanStep=()=>{},waitForBrowserMs=15000,waitTotalMs=DEFAULT_WAIT_TOTAL_MS,exists=existsSync6,browser,signals,probeSignals=probeBrowserSignals,now=Date.now()}={}){let cli=await cliReport({bskBin,env,cliVersion});let install=null;if(!cli.installed){install=await installCli({platform,home,installDir});cli=install.installed?await cliReport({bskBin:install.bskBin,env,cliVersion}):cli}const ipc=client??new BskIpcClient({env,bskBin:cli.bskBin??undefined,autoStart:true});let daemon=await daemonReport(ipc,0);const{browsers,identification,primary}=await identify({platform,home,env,exists,browser,signals,probeSignals,now});const registrations=[];let humanStep=null;let needsChoice=false;if(daemon.browsers.length===0){if(browsers.length===0&&!primary){humanStep=NO_BROWSER_STEP}else if(identification.needsChoice){needsChoice=true;humanStep=choiceStep(identification)}else{const registration=await registerExternalExtension({platform,browser:primary});registrations.push({browser:primary.id,...registration});humanStep=registration.humanStep;onHumanStep({browser:primary.id,step:registration.humanStep,registered:registration.registered,needsRestart:registration.needsRestart})}}const deadline=Date.now()+waitTotalMs;let browsersConnected=daemon.browsers;while(!needsChoice&&browsersConnected.length===0&&daemon.running&&Date.now()<deadline){browsersConnected=await listBrowsers(ipc,{waitForBrowserMs});if(waitForBrowserMs===0)break}daemon={...daemon,browsers:browsersConnected};const ready=browsersConnected.length>0;return{cli,install,daemon,browsers,primary:primary??null,identification,needsChoice,registrations,humanStep:ready?null:humanStep,browsersConnected,ready}}export{AgentTabManager,BROWSERSKILL_EXTENSION_IDS,BROWSER_EVENT_NAMES,BridgeProtocolError,BridgeUnavailableError,BrowserCdpCommandError,BrowserConnection,BskIpcClient,BskRpcError,BskSession,CdpClient,CommandSchema,DEFAULT_AGENT_VIEWPORT,DEVICE_PRESETS,ElementHandle,EventSchema,ExtensionHelloSchema,FrameLocator,FrameManager,HostHelloSchema,Locator,ModifierState,OmOKeyboard,OmOMouse,OmOPage,PipeCdpClient,ResponseSchema,STORE_PAGE_URLS,SessionManager,TabRepository,UnsupportedOperationError,bskDoctor,bskOnboard,bskSnapshot,buildCloakBrowserArgs,buildSnapshotExpression,catalogBrowsers,collectWhileScrolling,compactSnapshot,connect,connectBrowserSkill,connectCloakProfile,connectPipe,createAgentTabs,createBridgeClient,createCaptcha,createChromeApi2 as createChromeApi,createCua,createEvents,createNativeMessagingHost,createNetworkSnoop,createRoutes,createTrace,decodeFrames,describeLayers,detectBrowsers,emulate,encodeFrame,externalExtensionEntry,findCloakBrowserPath,identifyBrowser,injectCookies,installBskCli,installNativeMessagingHost,layersHeader,listBrowsers,macOSVisionOcr,normalizeDialogPolicy,pageBundle,probeBrowserSignals,readDaemonInfo,reconcileFrames,registerExternalExtension,repinViewport,requestHuman,resolveBskHome,resolveCloakProfile,resolveDialogAction,sanitizeCookies,snapshotTokens,snapshotWithFrames,snapshotWithLayers,takeSnapshot,toHar,toolSchemas,unregisterExternalExtension};
1534
+ })()`}async function bskSnapshot(session,{tabId,timeoutMs,...snapshotOptions}={}){const reply=await session.evaluate(buildSnapshotExpression(snapshotOptions),{tabId,timeoutMs,awaitPromise:false,returnByValue:true});if(!reply||reply.ok===false){const text=reply?.error?.text??"unknown error";throw new Error(`bskSnapshot failed inside the page: ${text}`)}const value=reply.value;if(!value||typeof value.tree!=="string")throw new Error("bskSnapshot: page returned no tree");if(value.error)throw new Error(`bskSnapshot: ${value.error}`);return{tree:value.tree,refs:value.refs,css:value.css}}import{existsSync as existsSync5,mkdirSync as mkdirSync2,readFileSync as readFileSync4,rmSync as rmSync2,writeFileSync as writeFileSync2}from"node:fs";import{spawn as spawn4}from"node:child_process";import path12 from"node:path";import{existsSync as existsSync4}from"node:fs";import{homedir as homedir3}from"node:os";import path11 from"node:path";var BROWSERSKILL_EXTENSION_IDS=Object.freeze({chrome:"hhcmgoofomhgciiibhipgmgkgnoenaoi",edge:"emacgiaaaiojkkpkddmmdfhmokgmnikg"});var STORE_UPDATE_URLS=Object.freeze({chrome:"https://clients2.google.com/service/update2/crx",edge:"https://edge.microsoft.com/extensionwebstorebase/v1/crx"});var STORE_PAGE_URLS=Object.freeze({chrome:`https://chromewebstore.google.com/detail/${BROWSERSKILL_EXTENSION_IDS.chrome}`,edge:`https://microsoftedge.microsoft.com/addons/detail/browserskill/${BROWSERSKILL_EXTENSION_IDS.edge}`});var NON_CHROMIUM_DEFAULTS=Object.freeze([{id:"safari",label:"Safari",match:["com.apple.safari","safarihtml"]},{id:"firefox",label:"Firefox",match:["org.mozilla.firefox","firefox.desktop","firefoxurl","firefoxhtml"]},{id:"zen",label:"Zen",match:["app.zen-browser.zen","zen.desktop","zenhtml"]},{id:"orion",label:"Orion",match:["com.kagi.kagimacos"]}]);var MAC=[{id:"chrome",label:"Google Chrome",store:"chrome",userData:"Library/Application Support/Google/Chrome",app:"Google Chrome",bundleId:"com.google.Chrome"},{id:"edge",label:"Microsoft Edge",store:"edge",userData:"Library/Application Support/Microsoft Edge",app:"Microsoft Edge",bundleId:"com.microsoft.edgemac"},{id:"brave",label:"Brave",store:"chrome",userData:"Library/Application Support/BraveSoftware/Brave-Browser",app:"Brave Browser",bundleId:"com.brave.Browser"},{id:"chromium",label:"Chromium",store:"chrome",userData:"Library/Application Support/Chromium",app:"Chromium",bundleId:"org.chromium.Chromium"},{id:"arc",label:"Arc",store:"chrome",userData:"Library/Application Support/Arc/User Data",app:"Arc",bundleId:"company.thebrowser.Browser"},{id:"dia",label:"Dia",store:"chrome",userData:"Library/Application Support/Dia/User Data",app:"Dia",bundleId:"company.thebrowser.dia"},{id:"vivaldi",label:"Vivaldi",store:"chrome",userData:"Library/Application Support/Vivaldi",app:"Vivaldi",bundleId:"com.vivaldi.Vivaldi"},{id:"opera",label:"Opera",store:"chrome",userData:"Library/Application Support/com.operasoftware.Opera",app:"Opera",bundleId:"com.operasoftware.Opera",externalExtensions:false},{id:"comet",label:"Comet",store:"chrome",userData:"Library/Application Support/Comet",app:"Comet",bundleId:"ai.perplexity.comet"},{id:"aside",label:"Aside",store:"chrome",userData:"Library/Application Support/Aside",app:"Aside",bundleId:"at.studio.AsideBrowser"},{id:"whale",label:"Naver Whale",store:"chrome",userData:"Library/Application Support/Naver/Whale",app:"Naver Whale",bundleId:"com.naver.Whale"}];var LINUX=[{id:"chrome",label:"Google Chrome",store:"chrome",userData:".config/google-chrome",binaries:["/usr/bin/google-chrome","/usr/bin/google-chrome-stable","/opt/google/chrome/chrome"],externalDir:"/opt/google/chrome/extensions",userExternalDir:false,desktopIds:["google-chrome.desktop","google-chrome-stable.desktop","com.google.Chrome.desktop"],processNames:["chrome","google-chrome"]},{id:"chromium",label:"Chromium",store:"chrome",userData:".config/chromium",binaries:["/usr/bin/chromium","/usr/bin/chromium-browser","/snap/bin/chromium"],externalDir:"/usr/share/chromium/extensions",userExternalDir:true,desktopIds:["chromium.desktop","chromium-browser.desktop","chromium_chromium.desktop","org.chromium.Chromium.desktop"],processNames:["chromium","chromium-browse"]},{id:"edge",label:"Microsoft Edge",store:"edge",userData:".config/microsoft-edge",binaries:["/usr/bin/microsoft-edge","/opt/microsoft/msedge/msedge"],externalDir:"/usr/share/microsoft-edge/extensions",userExternalDir:false,desktopIds:["microsoft-edge.desktop","microsoft-edge-stable.desktop","com.microsoft.Edge.desktop"],processNames:["msedge"]},{id:"brave",label:"Brave",store:"chrome",userData:".config/BraveSoftware/Brave-Browser",binaries:["/usr/bin/brave-browser","/opt/brave.com/brave/brave"],externalDir:"/usr/share/brave/extensions",userExternalDir:false,desktopIds:["brave-browser.desktop","com.brave.Browser.desktop"],processNames:["brave"]},{id:"vivaldi",label:"Vivaldi",store:"chrome",userData:".config/vivaldi",binaries:["/usr/bin/vivaldi","/opt/vivaldi/vivaldi"],externalDir:null,userExternalDir:false,externalExtensions:false,desktopIds:["vivaldi-stable.desktop","vivaldi.desktop"],processNames:["vivaldi-bin"]},{id:"opera",label:"Opera",store:"chrome",userData:".config/opera",binaries:["/usr/bin/opera"],externalDir:null,userExternalDir:false,externalExtensions:false,desktopIds:["opera.desktop"],processNames:["opera"]}];var WINDOWS=[{id:"chrome",label:"Google Chrome",store:"chrome",userData:"Google/Chrome/User Data",registryKey:"HKCU\\Software\\Google\\Chrome\\Extensions",binaryRel:"Google/Chrome/Application/chrome.exe",progIds:["chromehtml"],processNames:["chrome.exe"]},{id:"edge",label:"Microsoft Edge",store:"edge",userData:"Microsoft/Edge/User Data",registryKey:"HKCU\\Software\\Microsoft\\Edge\\Extensions",binaryRel:"Microsoft/Edge/Application/msedge.exe",progIds:["msedgehtm"],processNames:["msedge.exe"]},{id:"brave",label:"Brave",store:"chrome",userData:"BraveSoftware/Brave-Browser/User Data",registryKey:"HKCU\\Software\\BraveSoftware\\Brave-Browser\\Extensions",binaryRel:"BraveSoftware/Brave-Browser/Application/brave.exe",progIds:["bravehtml"],processNames:["brave.exe"]},{id:"chromium",label:"Chromium",store:"chrome",userData:"Chromium/User Data",registryKey:"HKCU\\Software\\Chromium\\Extensions",binaryRel:"Chromium/Application/chrome.exe",progIds:["chromiumhtm"],processNames:[]},{id:"vivaldi",label:"Vivaldi",store:"chrome",userData:"Vivaldi/User Data",registryKey:null,externalExtensions:false,binaryRel:"Vivaldi/Application/vivaldi.exe",progIds:["vivaldihtm"],processNames:["vivaldi.exe"]},{id:"whale",label:"Naver Whale",store:"chrome",userData:"Naver/Naver Whale/User Data",registryKey:null,externalExtensions:false,binaryRel:"Naver/Naver Whale/Application/whale.exe",progIds:["whalehtml"],processNames:["whale.exe"]}];var BROWSER_IDS=Object.freeze([...new Set([...MAC,...LINUX,...WINDOWS].map((b)=>b.id))]);function macRow(b,home,exists){const apps=[path11.join("/Applications",`${b.app}.app`),path11.join(home,"Applications",`${b.app}.app`)];const appPath=apps.find((p)=>exists(p))??null;const userDataDir=path11.join(home,b.userData);return{id:b.id,label:b.label,store:b.store,userDataDir,binary:path11.join(appPath??apps[0],"Contents","MacOS",b.app),bundleId:b.bundleId,processNames:apps.map((a)=>path11.join(a,"Contents","MacOS",b.app)),defaultIds:[b.bundleId.toLowerCase()],externalExtensions:b.externalExtensions!==false,installed:appPath!==null||exists(userDataDir),hasProfile:exists(userDataDir)}}function linuxRow(b,home,exists){const userDataDir=path11.join(home,b.userData);const binary=b.binaries.find((p)=>exists(p))??null;return{id:b.id,label:b.label,store:b.store,userDataDir,binary,externalDir:b.externalDir,userExternalDir:b.userExternalDir,processNames:b.processNames,defaultIds:b.desktopIds.map((d)=>d.toLowerCase()),externalExtensions:b.externalExtensions!==false,installed:binary!==null||exists(userDataDir),hasProfile:exists(userDataDir)}}function windowsRow(b,env,home,exists){const localAppData=env.LOCALAPPDATA??path11.join(home,"AppData","Local");const programFiles=[localAppData,env.PROGRAMFILES,env["PROGRAMFILES(X86)"],"C:\\Program Files","C:\\Program Files (x86)"].filter(Boolean);const userDataDir=path11.join(localAppData,b.userData);const binary=programFiles.map((root)=>path11.join(root,b.binaryRel)).find((p)=>exists(p))??null;return{id:b.id,label:b.label,store:b.store,userDataDir,registryKey:b.registryKey,binary,processNames:b.processNames,defaultIds:b.progIds,externalExtensions:b.externalExtensions!==false,installed:binary!==null||exists(userDataDir),hasProfile:exists(userDataDir)}}function catalogBrowsers({platform=process.platform,home=homedir3(),env=process.env,exists=existsSync4}={}){if(platform==="darwin")return MAC.map((b)=>macRow(b,home,exists));if(platform==="linux")return LINUX.map((b)=>linuxRow(b,home,exists));if(platform==="win32")return WINDOWS.map((b)=>windowsRow(b,env,home,exists));return[]}function detectBrowsers(options={}){return catalogBrowsers(options).filter((b)=>b.installed)}var ENABLE_STEP=(browser)=>`Quit ${browser} completely and open it again; a dialog offers to enable "BrowserSkill" — click Enable.`;var BADGE_STEP=(browser)=>`In ${browser}, open the menu (the badge on the toolbar) and enable "BrowserSkill" when prompted; no restart is needed.`;var storeStep=(browser,label)=>`Open ${STORE_PAGE_URLS[browser.store]} in ${label} and click the "Add" button.`;function externalExtensionEntry({platform=process.platform,browser}){const id=BROWSERSKILL_EXTENSION_IDS[browser.store];const updateUrl=STORE_UPDATE_URLS[browser.store];const label=browser.label??browser.id.charAt(0).toUpperCase()+browser.id.slice(1);if(browser.externalExtensions===false){return{kind:"store",needsRestart:false,storeUrl:STORE_PAGE_URLS[browser.store],humanStep:storeStep(browser,label)}}if(platform==="win32"){const key=`${browser.registryKey}\\${id}`;return{kind:"registry",key,needsRestart:false,storeUrl:STORE_PAGE_URLS[browser.store],regAddArgs:["add",key,"/v","update_url","/t","REG_SZ","/d",updateUrl,"/f"],regDeleteArgs:["delete",key,"/f"],humanStep:BADGE_STEP(label)}}const content=JSON.stringify({external_update_url:updateUrl},null,2)+`
1535
+ `;if(platform==="linux"&&!browser.userExternalDir){return{kind:"file",path:path12.join(browser.externalDir,`${id}.json`),content,needsRestart:true,storeUrl:STORE_PAGE_URLS[browser.store],humanStep:`Quit ${label} completely and open it again; the extension installs without a prompt.`}}return{kind:"file",path:path12.join(browser.userDataDir,"External Extensions",`${id}.json`),content,needsRestart:true,storeUrl:STORE_PAGE_URLS[browser.store],humanStep:platform==="linux"?`Quit ${label} completely and open it again; the extension installs without a prompt.`:ENABLE_STEP(label)}}function isBlocklisted(browser,id,{readFile=readFileSync4}={}){if(!browser.userDataDir)return false;for(const profile of["Default","Profile 1","Profile 2","Profile 3"]){const file=path12.join(browser.userDataDir,profile,"Preferences");let prefs;try{prefs=JSON.parse(readFile(file,"utf8"))}catch{continue}if(Array.isArray(prefs?.extensions?.external_uninstalls)&&prefs.extensions.external_uninstalls.includes(id))return true}return false}function runRegDefault(args){return new Promise((resolve)=>{const child=spawn4("reg",args,{stdio:["ignore","ignore","pipe"],windowsHide:true});let stderr="";child.stderr.on("data",(c)=>{stderr+=c});child.on("error",(error)=>resolve({code:-1,stderr:error.message}));child.on("exit",(code)=>resolve({code,stderr}))})}async function registerExternalExtension({platform=process.platform,browser,writeFile=writeFileSync2,readFile=readFileSync4,runReg=runRegDefault}={}){const entry=externalExtensionEntry({platform,browser});const id=BROWSERSKILL_EXTENSION_IDS[browser.store];const label=browser.label??browser.id;const storeStep=`Open ${entry.storeUrl} in ${label} and click the "Add" button.`;if(entry.kind==="store")return{...entry,registered:false,alreadyPresent:false,reason:"store_only"};if(isBlocklisted(browser,id,{readFile})){return{...entry,registered:false,alreadyPresent:false,reason:"blocklisted",humanStep:`The extension was removed from ${label} before, so external registration is ignored. ${storeStep}`}}if(entry.kind==="registry"){const result=await runReg(entry.regAddArgs);if(result.code!==0)return{...entry,registered:false,alreadyPresent:false,reason:"reg_failed",detail:result.stderr,humanStep:storeStep};return{...entry,registered:true,alreadyPresent:false}}let alreadyPresent=false;try{alreadyPresent=readFile(entry.path,"utf8")===entry.content}catch{}if(alreadyPresent)return{...entry,registered:true,alreadyPresent:true};try{mkdirSync2(path12.dirname(entry.path),{recursive:true});writeFile(entry.path,entry.content)}catch(error){if(error.code==="EACCES"||error.code==="EPERM"||error.code==="EROFS"){return{...entry,registered:false,alreadyPresent:false,reason:"not_writable",humanStep:`${entry.path} is not writable without administrator rights. ${storeStep}`}}throw error}return{...entry,registered:true,alreadyPresent:false}}async function unregisterExternalExtension({platform=process.platform,browser,runReg=runRegDefault}={}){const entry=externalExtensionEntry({platform,browser});if(entry.kind==="store")return{...entry,removed:false};if(entry.kind==="registry"){const result=await runReg(entry.regDeleteArgs);return{...entry,removed:result.code===0,detail:result.stderr}}const present=existsSync5(entry.path);if(present)rmSync2(entry.path,{force:true});return{...entry,removed:present}}import{execFile}from"node:child_process";import{statSync}from"node:fs";import{homedir as homedir4}from"node:os";import path13 from"node:path";var PROBE_TIMEOUT_MS2=3000;function runDefault(command,args){return new Promise((resolve)=>{execFile(command,args,{timeout:PROBE_TIMEOUT_MS2,windowsHide:true,maxBuffer:8*1024*1024},(error,stdout)=>{resolve(error?null:String(stdout))})})}function parseMacDefaultBrowser(plistJson){let handlers;try{handlers=JSON.parse(plistJson)?.LSHandlers}catch{return null}if(!Array.isArray(handlers))return null;const pick=(scheme)=>handlers.find((h)=>h?.LSHandlerURLScheme===scheme&&typeof h.LSHandlerRoleAll==="string");const handler=pick("https")??pick("http");return handler?handler.LSHandlerRoleAll.toLowerCase():"com.apple.safari"}function parseWindowsProgId(regOutput){const match=/ProgId\s+REG_SZ\s+(\S+)/i.exec(regOutput??"");return match?match[1].toLowerCase():null}function parseProcessNames(platform,output){const names=new Set;for(const raw of String(output??"").split(/\r?\n/)){const line=raw.trim();if(!line)continue;if(platform==="win32"){const image=/^"([^"]+)"/.exec(line)?.[1];if(image)names.add(image.toLowerCase())}else if(platform==="darwin"){if(/\.app\/Contents\/MacOS\/[^/]+$/.test(line))names.add(line)}else{names.add(path13.basename(line))}}return[...names]}async function probeDefault({platform,home,run}){if(platform==="darwin"){const plist=path13.join(home,"Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist");const json=await run("plutil",["-convert","json","-o","-",plist]);return json===null?null:parseMacDefaultBrowser(json)}if(platform==="linux"){const out=await run("xdg-settings",["get","default-web-browser"]);return out?.trim()?out.trim().toLowerCase():null}if(platform==="win32"){const key="HKCU\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\https\\UserChoice";return parseWindowsProgId(await run("reg",["query",key,"/v","ProgId"]))}return null}async function probeRunning({platform,run}){const out=platform==="win32"?await run("tasklist",["/fo","csv","/nh"]):await run("ps",platform==="darwin"?["-axo","comm="]:["-eo","comm="]);return out===null?null:parseProcessNames(platform,out)}function lastUsedAt(browser,stat){let latest=null;for(const rel of["Local State",path13.join("Default","History"),path13.join("Default","Preferences")]){try{const mtime=stat(path13.join(browser.userDataDir,rel)).mtimeMs;if(latest===null||mtime>latest)latest=mtime}catch{}}return latest}async function probeBrowserSignals({platform=process.platform,home=homedir4(),browsers=[],run=runDefault,stat=statSync}={}){const[defaultBrowser,running]=await Promise.all([probeDefault({platform,home,run}),probeRunning({platform,run})]);const lastUsed=Object.fromEntries(browsers.map((b)=>[b.id,lastUsedAt(b,stat)]));return{defaultBrowser,running,lastUsed}}var RECENT_USE_MS=7*24*60*60*1000;function matchesDefault(browser,defaultId){if(!defaultId)return false;return browser.defaultIds.some((id)=>defaultId===id||defaultId.startsWith(id))}function describeDefault(defaultId,catalog){if(defaultId===null||defaultId===undefined)return{raw:null,id:null,label:null,supported:null};const hit=catalog.find((b)=>matchesDefault(b,defaultId));if(hit)return{raw:defaultId,id:hit.id,label:hit.label,supported:true};const other=NON_CHROMIUM_DEFAULTS.find((b)=>b.match.some((m)=>defaultId===m||defaultId.startsWith(m)));return other?{raw:defaultId,id:other.id,label:other.label,supported:false}:{raw:defaultId,id:null,label:defaultId,supported:false}}function candidateRow(browser,{defaultBrowser,running,lastUsed},now){const isDefault=defaultBrowser.id===browser.id;const isRunning=Array.isArray(running)?browser.processNames.some((n)=>running.includes(n)||running.includes(n.toLowerCase())):null;const last=lastUsed?.[browser.id]??null;const recentlyUsed=last!==null&&now-last<=RECENT_USE_MS;const signals=[isDefault&&"default browser",isRunning&&"running now",recentlyUsed&&"used in the last 7 days"].filter(Boolean);return{id:browser.id,label:browser.label,installed:browser.installed,isDefault,running:isRunning,lastUsedAt:last===null?null:new Date(last).toISOString(),recentlyUsed,signals,browser}}function choose(candidates,primary,confidence,reason,defaultBrowser){return{primary:primary.browser,primaryId:primary.id,confidence,needsChoice:false,reason,defaultBrowser,candidates:candidates.map(({browser,...row})=>row)}}function ask(candidates,reason,defaultBrowser,suggested=null){return{primary:null,primaryId:null,confidence:null,needsChoice:true,reason,suggested,defaultBrowser,candidates:candidates.map(({browser,...row})=>row)}}function identifyBrowser({catalog,signals={},explicit=null,now=Date.now()}){const defaultBrowser=describeDefault(signals.defaultBrowser??null,catalog);const installed=catalog.filter((b)=>b.installed||b.id===explicit);const candidates=installed.map((b)=>candidateRow(b,{defaultBrowser,running:signals.running??null,lastUsed:signals.lastUsed},now));if(explicit){const hit=candidates.find((c)=>c.id===explicit);if(hit)return choose(candidates,hit,"explicit",`${hit.label} was chosen explicitly`,defaultBrowser);return ask(candidates,`"${explicit}" is not a Chromium-family browser this library knows; pick one of the candidates`,defaultBrowser)}if(candidates.length===0)return ask(candidates,"no Chromium-family browser is installed",defaultBrowser);const inUse=candidates.filter((c)=>c.running||c.recentlyUsed);const def=candidates.find((c)=>c.isDefault);if(def&&(def.running||def.recentlyUsed))return choose(candidates,def,"default-and-in-use",`${def.label} is the default browser and ${def.signals.slice(1).join(", ")}`,defaultBrowser);if(def&&inUse.length===0)return choose(candidates,def,"default",`${def.label} is the default browser`,defaultBrowser);if(def)return ask(candidates,`${def.label} is the default browser, but ${inUse.map((c)=>c.label).join(" and ")} ${inUse.length===1?"is":"are"} the one in use`,defaultBrowser);if(defaultBrowser.supported===false){const suggested=inUse.length===1?inUse[0].id:null;return ask(candidates,`the default browser is ${defaultBrowser.label}, which BrowserSkill cannot drive; the user has to name the Chromium browser to use`,defaultBrowser,suggested)}const runningNow=inUse.filter((c)=>c.running);if(inUse.length===1)return choose(candidates,inUse[0],"only-in-use",`${inUse[0].label} is the only browser ${inUse[0].signals.join(", ")}`,defaultBrowser);if(runningNow.length===1)return choose(candidates,runningNow[0],"only-running",`${runningNow[0].label} is the only browser running now`,defaultBrowser);if(inUse.length===0&&candidates.length===1)return choose(candidates,candidates[0],"only-installed",`${candidates[0].label} is the only Chromium-family browser installed`,defaultBrowser);return ask(candidates,inUse.length>1?`several browsers are in use (${inUse.map((c)=>c.label).join(", ")})`:"no browser shows recent use",defaultBrowser)}import{existsSync as existsSync6,readFileSync as readFileSync5,rmSync as rmSync3,writeFileSync as writeFileSync3}from"node:fs";import{homedir as homedir5}from"node:os";import path14 from"node:path";import{spawn as spawn5}from"node:child_process";var INSTALL_SH="https://raw.githubusercontent.com/Tencent/BrowserSkill/main/install.sh";var INSTALL_PS1="https://raw.githubusercontent.com/Tencent/BrowserSkill/main/install.ps1";var RC_FILES=[".zshrc",".bashrc",".profile"];function cliInstallCommand({platform=process.platform,installDir,version}={}){const env={BSK_INSTALL_DIR:installDir};if(version)env.BSK_VERSION=version;if(platform==="win32"){return{command:"powershell",args:["-NoProfile","-ExecutionPolicy","Bypass","-Command",`irm ${INSTALL_PS1} | iex`],env}}return{command:"sh",args:["-c",`curl -fsSL ${INSTALL_SH} | sh`],env}}function runDefault2(command,args,env){return new Promise((resolve)=>{const child=spawn5(command,args,{env:{...process.env,...env},stdio:["ignore","pipe","pipe"],windowsHide:true});let stdout="",stderr="";child.stdout.on("data",(c)=>{stdout=(stdout+c).slice(-8192)});child.stderr.on("data",(c)=>{stderr=(stderr+c).slice(-8192)});child.on("error",(error)=>resolve({code:-1,stdout,stderr:error.message}));child.on("exit",(code)=>resolve({code,stdout,stderr}))})}function snapshotRcFiles(home){const snapshot=new Map;for(const name of RC_FILES){const file=path14.join(home,name);snapshot.set(file,existsSync6(file)?readFileSync5(file):null)}return snapshot}function restoreRcFiles(snapshot){const restored=[];for(const[file,before]of snapshot){const after=existsSync6(file)?readFileSync5(file):null;if(before===null&&after===null)continue;if(before!==null&&after!==null&&before.equals(after))continue;if(before===null)rmSync3(file,{force:true});else writeFileSync3(file,before);restored.push(file)}return restored}async function installBskCli({platform=process.platform,home=homedir5(),installDir=path14.join(home,".local","bin"),version,run=runDefault2}={}){const spec=cliInstallCommand({platform,installDir,version});const rcBefore=platform==="win32"?new Map:snapshotRcFiles(home);const result=await run(spec.command,spec.args,spec.env);const restoredRcFiles=restoreRcFiles(rcBefore);const bskBin=path14.join(installDir,platform==="win32"?"bsk.exe":"bsk");const installed=result.code===0&&existsSync6(bskBin);return{installed,bskBin:installed?bskBin:null,code:result.code,stdout:result.stdout,stderr:result.stderr,restoredRcFiles}}function cliVersionDefault(bskBin){return new Promise((resolve)=>{const child=spawn5(bskBin,["--version"],{stdio:["ignore","pipe","ignore"],windowsHide:true});let out="";child.stdout.on("data",(c)=>{out+=c});child.on("error",()=>resolve(null));child.on("exit",(code)=>resolve(code===0?out.trim():null))})}import{homedir as homedir6}from"node:os";import{existsSync as existsSync7,readFileSync as readFileSync6}from"node:fs";var DEFAULT_WAIT_TOTAL_MS=180000;function resolveBskBin(bskBin,env){if(bskBin===null)return null;return bskBin??findBskBinary(env)}async function cliReport({bskBin,env,cliVersion}){const bin=resolveBskBin(bskBin,env);if(!bin)return{installed:false,bskBin:null,version:null};const raw=await cliVersion(bin);return raw?{installed:true,bskBin:bin,version:raw.replace(/^bsk\s+/,"")}:{installed:false,bskBin:bin,version:null}}async function daemonReport(client,waitForBrowserMs){try{const status=await client.call("system.status",waitForBrowserMs>0?{wait_for_browser_ms:waitForBrowserMs}:{},{idPrefix:"status"});return{running:true,version:status.daemon_version,protocolVersion:status.protocol_version,browsers:status.browsers??[],sessions:status.sessions??[]}}catch(error){return{running:false,error:error.message,code:error.code,browsers:[],sessions:[]}}}var NO_BROWSER_STEP=`No Chromium-family browser was found. Install one (Chrome, Edge, Brave, Arc, ...), then add the extension from ${STORE_PAGE_URLS.chrome}.`;function withRegistration(platform,browser){const entry=externalExtensionEntry({platform,browser});const extensionRegistered=entry.kind==="file"&&existsSync7(entry.path)&&readFileSync6(entry.path,"utf8")===entry.content;return{...browser,registration:entry,extensionRegistered}}async function identify({platform,home,env,exists,browser,signals,probeSignals,now}){const catalog=catalogBrowsers({platform,home,env,exists}).map((b)=>withRegistration(platform,b));const observed=signals??await probeSignals({platform,home,browsers:catalog.filter((b)=>b.hasProfile)});const explicit=browser??env.OMOWRIGHT_BROWSER??null;const{primary,...identification}=identifyBrowser({catalog,signals:observed,explicit:explicit||null,now});const browsers=catalog.filter((b)=>b.installed);const registeredElsewhere=browsers.filter((b)=>b.extensionRegistered&&b.id!==identification.primaryId).map((b)=>b.id);return{browsers,identification:{...identification,registeredElsewhere},primary}}function choiceStep(identification){const listed=identification.candidates.map((c)=>`${c.label} [${c.id}]${c.signals.length?` — ${c.signals.join(", ")}`:""}`).join("; ");const hint=identification.suggested?` Likely ${identification.suggested}, but confirm first.`:"";return`Ask the user which browser they actually use (${identification.reason}). Candidates: ${listed}.${hint} Then re-run with browser: "<id>"; register nothing until they answer.`}function nextStepFor({cli,daemon,browsers,browsersConnected,identification,primary}){if(browsersConnected.length>0)return null;if(!cli.installed)return"Install the bsk CLI (bskOnboard() runs the official installer).";if(!daemon.running)return"Start the BrowserSkill daemon: run `bsk status` once, or `bsk daemon start` in the environment that owns it.";if(browsers.length===0&&!primary)return NO_BROWSER_STEP;if(identification.needsChoice)return choiceStep(identification);if(!primary.extensionRegistered&&primary.registration.kind!=="store")return`Register the BrowserSkill extension for ${primary.label} (bskOnboard() does this), then let the user enable it.`;return primary.registration.humanStep}async function bskDoctor({platform=process.platform,home=homedir6(),env=process.env,client,bskBin,cliVersion=cliVersionDefault,waitForBrowserMs=0,exists=existsSync7,browser,signals,probeSignals=probeBrowserSignals,now=Date.now()}={}){const cli=await cliReport({bskBin,env,cliVersion});const ipc=client??new BskIpcClient({env,bskBin:cli.bskBin??undefined,autoStart:false});const daemon=await daemonReport(ipc,waitForBrowserMs);const{browsers,identification,primary}=await identify({platform,home,env,exists,browser,signals,probeSignals,now});const browsersConnected=daemon.browsers;const report={cli,daemon,browsers,primary:primary??null,identification,browsersConnected,ready:browsersConnected.length>0};report.nextStep=nextStepFor(report);return report}async function bskOnboard({platform=process.platform,home=homedir6(),env=process.env,client,bskBin,installDir,installCli=installBskCli,cliVersion=cliVersionDefault,onHumanStep=()=>{},waitForBrowserMs=15000,waitTotalMs=DEFAULT_WAIT_TOTAL_MS,exists=existsSync7,browser,signals,probeSignals=probeBrowserSignals,now=Date.now()}={}){let cli=await cliReport({bskBin,env,cliVersion});let install=null;if(!cli.installed){install=await installCli({platform,home,installDir});cli=install.installed?await cliReport({bskBin:install.bskBin,env,cliVersion}):cli}const ipc=client??new BskIpcClient({env,bskBin:cli.bskBin??undefined,autoStart:true});let daemon=await daemonReport(ipc,0);const{browsers,identification,primary}=await identify({platform,home,env,exists,browser,signals,probeSignals,now});const registrations=[];let humanStep=null;let needsChoice=false;if(daemon.browsers.length===0){if(browsers.length===0&&!primary){humanStep=NO_BROWSER_STEP}else if(identification.needsChoice){needsChoice=true;humanStep=choiceStep(identification)}else{const registration=await registerExternalExtension({platform,browser:primary});registrations.push({browser:primary.id,...registration});humanStep=registration.humanStep;onHumanStep({browser:primary.id,step:registration.humanStep,registered:registration.registered,needsRestart:registration.needsRestart})}}const deadline=Date.now()+waitTotalMs;let browsersConnected=daemon.browsers;while(!needsChoice&&browsersConnected.length===0&&daemon.running&&Date.now()<deadline){browsersConnected=await listBrowsers(ipc,{waitForBrowserMs});if(waitForBrowserMs===0)break}daemon={...daemon,browsers:browsersConnected};const ready=browsersConnected.length>0;return{cli,install,daemon,browsers,primary:primary??null,identification,needsChoice,registrations,humanStep:ready?null:humanStep,browsersConnected,ready}}export{AgentTabManager,BROWSERSKILL_EXTENSION_IDS,BROWSER_EVENT_NAMES,BridgeProtocolError,BridgeUnavailableError,BrowserCdpCommandError,BrowserConnection,BskIpcClient,BskRpcError,BskSession,CdpClient,CommandSchema,DEFAULT_AGENT_VIEWPORT,DEVICE_PRESETS,ElementHandle,EventSchema,ExtensionHelloSchema,FrameLocator,FrameManager,HostHelloSchema,Locator,ModifierState,OmOKeyboard,OmOMouse,OmOPage,PipeCdpClient,ResponseSchema,STORE_PAGE_URLS,SessionManager,TabRepository,UnsupportedOperationError,bskDoctor,bskOnboard,bskSnapshot,buildCloakBrowserArgs,buildSnapshotExpression,catalogBrowsers,collectWhileScrolling,compactSnapshot,connect,connectBrowserSkill,connectCloakProfile,connectPipe,createAgentTabs,createBridgeClient,createCaptcha,createChromeApi2 as createChromeApi,createCua,createEvents,createNativeMessagingHost,createNetworkSnoop,createRoutes,createTrace,decodeFrames,describeLayers,detectBrowsers,emulate,encodeFrame,externalExtensionEntry,findCloakBrowserPath,identifyBrowser,injectCookies,installBskCli,installNativeMessagingHost,layersHeader,listBrowsers,macOSVisionOcr,normalizeDialogPolicy,pageBundle,probeBrowserSignals,readDaemonInfo,reconcileFrames,registerExternalExtension,repinViewport,requestHuman,resolveBskHome,resolveCloakProfile,resolveDialogAction,sanitizeCookies,snapshotTokens,snapshotWithFrames,snapshotWithLayers,takeSnapshot,toHar,toolSchemas,unregisterExternalExtension};
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "version": "0.0.1",
3
- "sourceDigest": "167dcc331041e909a54c6e9695ea8d09bbb96bc351d62333d7ac628f780b83f6",
3
+ "sourceDigest": "646863e33d2a1af1fdd3651ee157ada9ba24337e82430edef94342774c80f0f6",
4
4
  "files": {
5
- "index.js": "b6b064fab277031c6c51f5a58b84b31040f9960bf767bf646adc0540c1d25c1b",
5
+ "index.js": "f577c49fe18bcc78293e136e112a83861e86cca5d908ebceaaec524dff8b8ccc",
6
6
  "page-bundle.js": "bfd51e8383cf47200d57387bbbed258bce5a3a3d00c38fc108034d2888e23f0c"
7
7
  },
8
- "stagedAtUtc": "2026-10-13T16:37:13.215Z"
8
+ "stagedAtUtc": "2026-10-17T03:46:57.473Z"
9
9
  }
@@ -12,6 +12,7 @@ ulw-research/
12
12
  ├── references/
13
13
  │ ├── deliverable-phase.md # lanes, state, destination defaults, interview, memory episodes, design spec, gates, repair, manifest, command reference
14
14
  │ ├── report-gates.md # one row per defect code: severity, integrity, what it detects, how to fix
15
+ │ ├── report-visualization.md # which visual fits which finding, render commands (charts, Mermaid, PlotNeuralNet), quality bar, visual QA checklist
15
16
  │ └── latex-report.md
16
17
  └── scripts/
17
18
  ├── report-tools.mjs # CLI entry + dispatcher (COMMANDS, run, main)
@@ -22,7 +23,7 @@ ulw-research/
22
23
  ├── repair-tracker.mjs # bounded repair decisions (attempts, plateau, oscillation, wall clock, integrity codes)
23
24
  ├── html-lite.mjs / entities.mjs # HTML tokenizer, selector paths, prose text
24
25
  ├── css-lite.mjs / design-spec.mjs # CSS scanning, palette + lineage parsing, design-spec rendering
25
- ├── gates-static.mjs + gates-text.mjs / gates-figures.mjs / gates-structure.mjs # static gates G1-G15
26
+ ├── gates-static.mjs + gates-text.mjs / gates-figures.mjs / gates-structure.mjs # static gates G1-G17
26
27
  ├── layout-probe.mjs / gates-layout.mjs # in-page probe source + layout gates L1-L5 over its output
27
28
  ├── format-extract.mjs / format-extract-css.mjs # design-spec draft from a pointed-at HTML or Markdown document
28
29
  ├── *.test.ts # co-located bun tests, one per module (unshipped)
@@ -40,7 +41,7 @@ ulw-research/
40
41
  - The CLI never launches a browser. The layout probe is printed as one async expression; the orchestrator evaluates it through the browser skill's owned headless engine and passes the result back with `check --layout`.
41
42
  - Time is always injected (`--now`, `startedAt`); no module reads the clock inside a decision.
42
43
  - Tests are co-located given/when/then bun tests; CLI tests spawn `node` with bun removed from `PATH`. Fixtures live only under `scripts/tests/fixtures/`, are depersonalized, and never ship.
43
- - The senpi source dir (`packages/omo-senpi/skills/ulw-research/`) intentionally has no `scripts/`, `references/report-gates.md`, or `references/deliverable-phase.md`: the senpi sync overlays them from here byte-for-byte (`sharedAssets` in `packages/omo-senpi/plugin/scripts/native-skill-sources.mjs`), and both editions' sync suites assert the shipped copies equal these sources.
44
+ - The senpi source dir (`packages/omo-senpi/skills/ulw-research/`) intentionally has no `scripts/`, `references/report-gates.md`, `references/deliverable-phase.md`, or `references/report-visualization.md`: the senpi sync overlays them from here byte-for-byte (`sharedAssets` in `packages/omo-senpi/plugin/scripts/native-skill-sources.mjs`), and both editions' sync suites assert the shipped copies equal these sources.
44
45
  - References and scripts carry no Hangul (Korean match strings are `\u` escapes) and never three consecutive newlines (the senpi sync normalizes those and would break byte equality).
45
46
 
46
47
  ## ANTI-PATTERNS
@@ -332,6 +332,8 @@ Asset workers (background, parallel, each fed `design-spec.md`) — visuals are
332
332
  - **Generated visuals through the imagegen skill whenever the session has it:** a cover plus a concept illustration per major theme, prompted from the spec's style, palette, and mood — document-styled illustration, never generic stock art dropped into a designed page.
333
333
  - **Full-page screenshots** of the top 5-10 sources, saved by the web workers while reading, as provenance you can show.
334
334
 
335
+ **Every final report is visual — binding.** It carries at least one data visualization of the run's own findings (a chart drawn from the data table, next to the claim it supports), a diagram wherever structure matters, and the topic's own screenshots and video, embedded or linked with attribution; every figure is captioned, sourced, and given alt text. Mermaid for flows, timelines, and relations; generated charts for numbers; image generation only where an illustration genuinely helps; PlotNeuralNet for paper-format reports that describe model architectures. Every asset lane reads [references/report-visualization.md](references/report-visualization.md) first: which tool fits which finding, the render commands, the quality bar, and the checklist the gates apply. A light-tier post still carries one visual when it reports numbers or structure.
336
+
335
337
  **Verify the asset manifest before rendering.** List every asset the document references, assert each file exists and is non-empty on disk, and re-render whatever is missing. A document that renders with three broken diagrams is a document you will publish twice.
336
338
 
337
339
  Assembly worker — `task(category="deep-low", load_skills=["frontend", "visual-qa", "open-design", "data-scientist", "imagegen", "ulw-loop"], run_in_background=true, ...)`: before writing, read every available design and visualization skill and apply it — the report is a designed artifact, not a text dump; the worker's prompt carries `design-spec.md`. Use the template the user approved; absent a stronger house style the default skeleton is executive summary → key findings by theme → detailed analysis (quotes under 20 words with attribution, charts, Mermaid graphs, generated visuals, SHA-pinned permalinks, verification results) → comparative analysis when options compete → numbered sources with access dates → methodology appendix (workers, waves, searches, verifications, debate rounds) → correction log naming what verification overturned. Write it long and specific: every claim cites `[Source N]`, and the sources section lists every source the run actually used rather than a curated few.
@@ -340,7 +342,7 @@ Assembly worker — `task(category="deep-low", load_skills=["frontend", "visual-
340
342
 
341
343
  Nothing reaches the user until the gates pass. Record each result with `node "$SKILL_DIR/scripts/report-tools.mjs" outcome gate <static|layout|visual|proofread> <pass|fail|not_run> --session-dir "$SESSION_DIR"`; a gate this harness does not provide is recorded `not_run`. A chat or post answer (light tier) runs the static gates and one render; every paginated or web deliverable runs them all (the reference's sections 3 and 7).
342
344
 
343
- 1. **Static gates (always, before anyone looks at pixels).** `node "$SKILL_DIR/scripts/report-tools.mjs" check "$SESSION_DIR/report.html" --design-spec "$SESSION_DIR/design-spec.md" > "$SESSION_DIR/defects.json"`: keep-all, palette tokens, emoji, em dashes, heading length, unsourced numbers, figure containers, chart text, gothic-only fonts, citations, the closing and sources sections, broken assets. Codes and fixes: [references/report-gates.md](references/report-gates.md).
345
+ 1. **Static gates (always, before anyone looks at pixels).** `node "$SKILL_DIR/scripts/report-tools.mjs" check "$SESSION_DIR/report.html" --design-spec "$SESSION_DIR/design-spec.md" > "$SESSION_DIR/defects.json"`: keep-all, palette tokens, emoji, em dashes, heading length, unsourced numbers, figure containers, chart text, a report with no visual, images without alt text, gothic-only fonts, citations, the closing and sources sections, broken assets. Codes and fixes: [references/report-gates.md](references/report-gates.md).
344
346
  2. **Layout gates.** Print the probe with `node "$SKILL_DIR/scripts/report-tools.mjs" layout-probe --json`, evaluate its `source` in the rendered page through the `browser` skill's owned headless engine, save the result as `$SESSION_DIR/boxes.json`, and re-run `check` with `--layout "$SESSION_DIR/boxes.json"` (overflow, clipped text, distorted images, overlapping siblings).
345
347
  3. **Visual QA.** Render the produced artifact back to images — PDF pages to PNG, the HTML through the browser skill at desktop and phone widths — and look at them: missing or broken figures, images stretched or spilling their containers, diagram or chart text rendered off the spec's font or palette, clipped tables, overflowing CJK text, blank pages, unlabeled chart values, wrong page breaks. Reading the source markup is not visual QA; inspect the pixels.
346
348
  4. **Proofread gate — `task(category="writing", ...)`.** Hand the final text to a dedicated `writing` worker whose only job is language: grammar, spelling, punctuation, terminology consistency, and whether the prose reads NATIVELY in the report's own language. It proofreads only and never composes. It returns a defect list; fix every item and re-run the gate on the delta. Deliver only on a clean pass — this gate runs BEFORE the first delivery, not after the user finds the typo.
@@ -122,7 +122,7 @@ Nothing reaches the requester until the gates for its QA tier pass, in this orde
122
122
  ```bash
123
123
  node "$SKILL_DIR/scripts/report-tools.mjs" check "$SESSION_DIR/report.html" --design-spec "$SESSION_DIR/design-spec.md" > "$SESSION_DIR/defects.json"
124
124
  ```
125
- It checks keep-all, palette tokens, emoji, em dashes in prose (en dashes outside numeric ranges are advisory), heading length and generic headings, unsourced numbers (section 6), figure containers and captions, chart SVG text, Korean serif fonts, sections without citations, the closing section, the sources section, broken local assets, and any `--require-section <regex>` the brief requires. Every code, severity, and fix is in [report-gates.md](report-gates.md).
125
+ It checks keep-all, palette tokens, emoji, em dashes in prose (en dashes outside numeric ranges are advisory), heading length and generic headings, unsourced numbers (section 6), figure containers and captions, chart SVG text, a report with no visual at all, images without alt text, Korean serif fonts, sections without citations, the closing section, the sources section, broken local assets, and any `--require-section <regex>` the brief requires. Every code, severity, and fix is in [report-gates.md](report-gates.md).
126
126
  2. **Layout gates.** Print the probe, evaluate it in the rendered page through the browser skill's owned headless engine, save the returned object as `$SESSION_DIR/boxes.json`, and re-run `check` with `--layout "$SESSION_DIR/boxes.json"`. It adds overflow past the container, clipped text, scroll containers, distorted images, and overlapping siblings.
127
127
  ```bash
128
128
  node "$SKILL_DIR/scripts/report-tools.mjs" layout-probe --json # .source is one async expression
@@ -19,6 +19,8 @@ This reference catalogs every defect code the report gates (`static`, `layout`,
19
19
  | `chart_without_figure` | major | false | An image or chart lacks a figure wrapper with a caption. Uncaptioned charts isolate visuals from narrative flow. | Wrap the image or chart in a figure container with a caption. |
20
20
  | `chart_svg_no_text` | major | false | An SVG chart or diagram lacks a title, axis labels, units, or value labels. Visuals cannot stand alone without text guidance. | Give the chart a title, axis labels, units, and value labels as SVG text. |
21
21
  | `chart_svg_missing_labels` | minor | false | An SVG chart has some labels (e.g., title) but is missing others (e.g., axis ticks or unit). | Add the missing title, axis ticks, or unit label to the chart. |
22
+ | `report_without_visual` | major | false | The report body has no chart, diagram, or figure image at all (header logos, icons, and images marked decorative do not count; only rendered output counts, so a Mermaid source block or an image with any zero dimension (attribute, inline style, or viewBox) does not; an element with no declared size counts, because the browser sizes it, and the layout gate is where rendering is proven). A text-only report hides structure and trends the reader would see at a glance. | Add at least one captioned visual of the findings: a chart from the data table, a rendered Mermaid diagram, or a figure image (references/report-visualization.md). |
23
+ | `image_missing_alt` | minor | false | A figure image has a missing or empty `alt` and is not marked decorative. Screen readers and failed image loads leave the reader with nothing. An image is decorative only with an empty `alt` plus `role="presentation"` (or `role="none"`, or `aria-hidden="true"`): it raises nothing here and does not count as a visual. | Give the image an alt attribute that states what it shows, not its file name; mark a purely decorative image alt="" with role="presentation". |
22
24
  | `korean_serif_font` | major | false | A serif or brush Korean font family is applied to Korean text. The design spec mandates gothic (sans) Korean typefaces. | Use a gothic (sans) Korean family; serif and brush families are banned by the design spec. |
23
25
  | `section_without_citation` | major | false | A major section or subsection contains no in-text citations or reference marks. Source attribution is missing. | Cite at least one source inside this section or merge it into a cited one. |
24
26
  | `missing_closing_section` | blocker | true | The report lacks a closing section explaining the methodology, sources, and gate outcomes. Delivery is incomplete without narrative closure. | Add the closing section that explains how the report was made (method, sources, gates). |