@duckmind/dm-darwin-x64 0.42.5 → 0.42.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dm
CHANGED
|
Binary file
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{createHash as
|
|
2
|
+
import{createHash as x}from"node:crypto";import{spawn as O}from"node:child_process";import{existsSync as U,mkdirSync as F,readFileSync as P}from"node:fs";import R from"node:http";import{homedir as d,platform as S,tmpdir as N}from"node:os";import{dirname as I,join as H,resolve as K}from"node:path";import{fileURLToPath as v}from"node:url";var w=I(v(import.meta.url)),kf=K(w,".."),c=9322,n=400,T=60000,u=/^(about:|https?:\/\/|file:|chrome-error:\/\/)/i,p={chrome:"Chrome",cloak:"CloakBrowser"};function b(f){let J=Number.parseInt(String(f??""),10);return Number.isInteger(J)&&J>1024&&J<65535?J:null}function i(f){return x("sha256").update(String(f||"default")).digest("hex")}function o(f){let J=String(f||"chrome").trim().toLowerCase();if(J==="cloakbrowser"||J==="cloak-browser")return"cloak";if(J==="chromium"||J==="google-chrome"||J==="chrome")return"chrome";return J.replace(/[^a-z0-9_.-]/g,"-")||"chrome"}function l(f,J){let V=f.DM_CUA_BROWSER_PATH||(J==="cloak"?f.DM_CUA_CLOAK_PATH||f.CLOAKBROWSER_BINARY_PATH:void 0)||f.CHROME_PATH;return V?K(String(V)):void 0}function a(f=process.env,J=process.cwd()){let V=f.DM_CUA_SCOPE||f.DM_CODING_AGENT_DIR||f.PI_CODING_AGENT_DIR||J,j=i(K(String(V||"."))),Z=String(f.DM_CUA_INSTANCE_ID||j.slice(0,12)).replace(/[^a-zA-Z0-9_.-]/g,"-"),Q=o(f.DM_CUA_BROWSER||f.DM_BROWSER_CUA_BROWSER||f.DM_CUA_BROWSER_KIND),z=f.DM_CUA_BROWSER_DISPLAY_NAME||p[Q]||Q,A=l(f,Q),M=b(f.DM_CUA_PORT)??b(f.GREEDY_SEARCH_PORT)??c+Number.parseInt(j.slice(0,8),16)%n,$=Q==="chrome"?"chrome":Q,X=K(f.DM_CUA_PROFILE_DIR||H(N(),`dm-cua-${$}-profile-${Z}`)),q=S()==="win32"?N():"/tmp";return{instanceId:Z,browserKind:Q,browserDisplayName:z,browserExecutablePath:A,port:M,profileDir:X,pidFile:H(X,"chrome.pid"),modeFile:H(X,"chrome-mode"),pagesCache:H(X,"cdp-pages.json"),socketDir:H(q,`dm-cua-cdp-${Z}`)}}var Y=a(),s=Y.port,Df=Y.profileDir;function k(){return{kind:Y.browserKind,displayName:Y.browserDisplayName,executablePath:Y.browserExecutablePath,profileDir:Y.profileDir,port:Y.port}}function t(){if(Y.browserKind==="cloak"&&!Y.browserExecutablePath)throw Error(["CloakBrowser selected for browser CUA, but no executable path was provided.","Install/pre-download CloakBrowser yourself, then set DM_CUA_BROWSER_PATH, DM_CUA_CLOAK_PATH, CLOAKBROWSER_BINARY_PATH, or CHROME_PATH.","DM does not bundle the compiled CloakBrowser binary because its upstream license allows use but not redistribution."].join(" "));if(Y.browserExecutablePath&&!U(Y.browserExecutablePath))throw Error(`${Y.browserDisplayName} executable not found: ${Y.browserExecutablePath}`)}function B(){let J=[K(w,"..","greedysearch-dm"),K(w,"..","..","greedysearch-dm")].map((V)=>({launchScript:H(V,"bin","launch.mjs"),cdpScript:H(V,"bin","cdp.mjs")}));for(let V of J)if(U(V.launchScript)&&U(V.cdpScript))return V;return J[0]}function h(){let f=process.env.GREEDY_SEARCH_VISIBLE??process.env.DM_CUA_VISIBLE??process.env.DM_BROWSER_CUA_VISIBLE??"1";return{...process.env,...Y.browserExecutablePath?{CHROME_PATH:Y.browserExecutablePath}:{},CDP_PROFILE_DIR:Y.profileDir.replace(/\\/g,"/"),CDP_PAGES_CACHE:Y.pagesCache.replace(/\\/g,"/"),CDP_SOCKET_DIR:Y.socketDir.replace(/\\/g,"/"),GREEDY_SEARCH_BROWSER_LABEL:Y.browserDisplayName,GREEDY_SEARCH_PORT:String(Y.port),GREEDY_SEARCH_PROFILE_DIR:Y.profileDir.replace(/\\/g,"/"),GREEDY_SEARCH_PID_FILE:Y.pidFile.replace(/\\/g,"/"),GREEDY_SEARCH_MODE_FILE:Y.modeFile.replace(/\\/g,"/"),GREEDY_SEARCH_ALLOW_PORT_CLEANUP:process.env.GREEDY_SEARCH_ALLOW_PORT_CLEANUP??"0",GREEDY_SEARCH_VISIBLE:f}}function r(f){return u.test(f)}function e(f){return f.split(/\r?\n/).map((J)=>J.trimEnd()).filter(Boolean).map((J)=>{let V=J.trim().split(/\s{2,}/).filter(Boolean);if(V.length===0)return null;let j=V[0];if(V.length===1)return{target:j,title:"",url:""};if(V.length===2&&r(V[1]))return{target:j,title:"",url:V[1]};let Z=V.at(-1)??"",Q=V.slice(1,-1).join(" ");return{target:j,title:Q,url:Z}}).filter(Boolean)}function ff(){let f=process.env.DM_CODING_AGENT_DIR||process.env.PI_CODING_AGENT_DIR;return f?K(f):H(d(),".dm","agent")}function Jf(){let f=H(ff(),"cua","screenshots");F(f,{recursive:!0});let J=new Date().toISOString().replace(/[:.]/g,"-");return H(f,`browser-${J}.png`)}function G(f,J,{env:V=process.env,timeoutMs:j=T}={}){return new Promise((Z,Q)=>{let z=O("node",[f,...J],{env:V,stdio:["ignore","pipe","pipe"]}),A="",M="",$=!1,X=setTimeout(()=>{if($)return;$=!0,z.kill("SIGTERM"),Q(Error(`Timed out running ${f}`))},j);z.stdout.on("data",(q)=>{A+=q.toString()}),z.stderr.on("data",(q)=>{M+=q.toString()}),z.on("error",(q)=>{if($)return;$=!0,clearTimeout(X),Q(q)}),z.on("close",(q)=>{if($)return;if($=!0,clearTimeout(X),q!==0){Q(Error((M||A||`exit ${q}`).trim()));return}Z({stdout:A,stderr:M})})})}function Vf(f,J){return new Promise((V,j)=>{let Z=R.request({host:"127.0.0.1",port:s,path:J,method:f},(Q)=>{let z="";Q.on("data",(A)=>{z+=A.toString()}),Q.on("end",()=>{if((Q.statusCode??500)>=400){j(Error(`Browser CDP HTTP ${Q.statusCode}: ${z.trim()}`));return}try{V(z?JSON.parse(z):{})}catch(A){j(A)}})});Z.on("error",j),Z.end()})}async function $f(f="about:blank"){return Vf("PUT",`/json/new?${encodeURIComponent(f)}`)}async function D(f,J={}){let{cdpScript:V}=B();return G(V,f,{env:h(),timeoutMs:J.timeoutMs??T})}async function W(){let f=await D(["list"]);return e(f.stdout)}function g(f,J){if(!J)return f[0]??null;let V=J.toUpperCase();return f.find((j)=>j.target.toUpperCase().startsWith(V))??null}async function m(){let{launchScript:f,cdpScript:J}=B();if(!U(f)||!U(J))throw Error("Bundled browser helpers are missing. Rebuild DM so dm-cua and greedysearch-dm are staged together.");return{launchScript:f,cdpScript:J}}async function C(){t();let{launchScript:f}=await m();await G(f,[],{env:h(),timeoutMs:T});let J=await W();if(J.length===0)await $f("about:blank"),J=await W();return J}async function L(f){let J=await C(),V=g(J,f);if(!V){if(f)throw Error(`No browser tab matching prefix "${f}". Run browser_cua(list) first.`);throw Error("No browser tab available after browser bootstrap.")}return{tab:V.target,page:V,pages:J}}async function _({action:f,tab:J,url:V,selector:j,x:Z,y:Q,text:z,expression:A,outputPath:M}={}){switch(f){case"list":{let $=await C();return{status:"ok",action:f,browser:k(),pages:$}}case"navigate":{if(!V)throw Error("url is required for action=navigate");let $=await L(J),X=await D(["nav",$.tab,V],{timeoutMs:90000}),q=await W(),y=g(q,$.tab)??$.page;return{status:"ok",action:f,browser:k(),tab:$.tab,url:y?.url||V,title:y?.title||"",message:X.stdout.trim(),pages:q}}case"snapshot":{let $=await L(J),X=await D(["snap",$.tab]);return{status:"ok",action:f,browser:k(),tab:$.tab,title:$.page?.title||"",url:$.page?.url||"",snapshot:X.stdout.trim()}}case"screenshot":{let $=await L(J),X=M?K(M):Jf();F(I(X),{recursive:!0});let q=await D(["shot",$.tab,X],{timeoutMs:120000}),y=P(X).toString("base64");return{status:"ok",action:f,browser:k(),tab:$.tab,title:$.page?.title||"",url:$.page?.url||"",imagePath:X,imageBase64:y,mimeType:"image/png",message:q.stdout.trim()}}case"click":{if(!j)throw Error("selector is required for action=click");let $=await L(J),X=await D(["click",$.tab,j]);return{status:"ok",action:f,browser:k(),tab:$.tab,selector:j,message:X.stdout.trim()}}case"click_xy":{if(typeof Z!=="number"||Number.isNaN(Z)||typeof Q!=="number"||Number.isNaN(Q))throw Error("x and y are required numbers for action=click_xy");let $=await L(J),X=await D(["clickxy",$.tab,String(Z),String(Q)]);return{status:"ok",action:f,browser:k(),tab:$.tab,x:Z,y:Q,message:X.stdout.trim()}}case"type":{if(z==null||z==="")throw Error("text is required for action=type");let $=await L(J),X=await D(["type",$.tab,z]);return{status:"ok",action:f,browser:k(),tab:$.tab,textLength:z.length,message:X.stdout.trim()}}case"evaluate":{if(!A)throw Error("expression is required for action=evaluate");let $=await L(J),X=await D(["eval",$.tab,A]);return{status:"ok",action:f,browser:k(),tab:$.tab,value:X.stdout.trim()}}case"stop":{let{launchScript:$}=await m();try{await D(["stop"])}catch{}let X=await G($,["--kill"],{env:h(),timeoutMs:30000});return{status:"ok",action:f,browser:k(),message:(X.stdout||"Stopped browser CUA lane.").trim()}}default:throw Error(`Unsupported browser action: ${f}`)}}function E(f){switch(f.action){case"list":{if(!Array.isArray(f.pages)||f.pages.length===0)return"Browser CUA is ready, but no pages are open.";return["Open browser tabs:",...f.pages.map((J)=>J.url?`${J.target} ${J.title||"(untitled)"} ${J.url}`:`${J.target} ${J.title||"(untitled)"}`)].join(`
|
|
3
3
|
`)}case"navigate":return[`Navigated browser tab ${f.tab} to ${f.url||"(unknown URL)"}`,f.title?`Title: ${f.title}`:""].filter(Boolean).join(`
|
|
4
4
|
`);case"snapshot":return[`Browser snapshot for ${f.tab}`,f.snapshot||"(empty snapshot)"].join(`
|
|
5
5
|
`);case"screenshot":return[`Saved browser screenshot for ${f.tab} at ${f.imagePath}`,f.title?`Title: ${f.title}`:"",f.url?`URL: ${f.url}`:""].filter(Boolean).join(`
|
|
6
|
-
`);case"click":return`Clicked selector ${JSON.stringify(f.selector)} on browser tab ${f.tab}`;case"click_xy":return`Clicked browser tab ${f.tab} at CSS coordinates (${f.x}, ${f.y})`;case"type":return`Typed ${f.textLength} characters into browser tab ${f.tab}`;case"evaluate":return`Browser evaluate result for ${f.tab}: ${f.value}`;case"stop":return f.message||"Stopped browser CUA lane.";default:return JSON.stringify(f,null,2)}}function
|
|
6
|
+
`);case"click":return`Clicked selector ${JSON.stringify(f.selector)} on browser tab ${f.tab}`;case"click_xy":return`Clicked browser tab ${f.tab} at CSS coordinates (${f.x}, ${f.y})`;case"type":return`Typed ${f.textLength} characters into browser tab ${f.tab}`;case"evaluate":return`Browser evaluate result for ${f.tab}: ${f.value}`;case"stop":return f.message||"Stopped browser CUA lane.";default:return JSON.stringify(f,null,2)}}function jf(){console.log(`dm-cua browser helper
|
|
7
7
|
|
|
8
8
|
Usage:
|
|
9
9
|
node browser-cua.mjs list [--json]
|
|
@@ -21,4 +21,4 @@ Usage:
|
|
|
21
21
|
- Fresh dedicated browser profiles auto-bootstrap a blank tab.
|
|
22
22
|
- Default browser is Chrome/Chromium. To try CloakBrowser, set DM_CUA_BROWSER=cloak
|
|
23
23
|
and point DM_CUA_BROWSER_PATH or CLOAKBROWSER_BINARY_PATH at its Chromium binary.
|
|
24
|
-
- screenshot defaults to ~/.dm/agent/cua/screenshots when --output is omitted.`)}function
|
|
24
|
+
- screenshot defaults to ~/.dm/agent/cua/screenshots when --output is omitted.`)}function Qf(f){let J={},V=[];for(let j=0;j<f.length;j+=1){let Z=f[j];if(!Z.startsWith("--")){V.push(Z);continue}let Q=Z.indexOf("="),z=Q>2,A=z?Z.slice(2,Q):Z.slice(2);if(A==="json"||A==="help"){J[A]=!0;continue}if(z){J[A]=Z.slice(Q+1);continue}let M=f[j+1];if(typeof M>"u"||M.startsWith("--"))throw Error(`Missing value for --${A}`);J[A]=M,j+=1}return{options:J,positionals:V}}async function Xf(){let{options:f,positionals:J}=Qf(process.argv.slice(2));if(f.help||J.length===0){jf();return}let V=J[0],j=await _({action:V,tab:f.tab,url:f.url,selector:f.selector,x:f.x==null?void 0:Number(f.x),y:f.y==null?void 0:Number(f.y),text:f.text,expression:f.expression,outputPath:f.output});if(f.json){console.log(JSON.stringify(j,null,2));return}console.log(E(j))}try{await Xf()}catch(f){let J=f instanceof Error?f.message:String(f),V=!1;try{V=process.argv.slice(2).includes("--json")}catch{}if(V)console.log(JSON.stringify({status:"error",message:J},null,2));else console.error(J);process.exit(1)}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{existsSync as
|
|
1
|
+
import{existsSync as S}from"node:fs";import{Type as X}from"@sinclair/typebox";import{createHash as d}from"node:crypto";import{spawn as O}from"node:child_process";import{existsSync as T,mkdirSync as N,readFileSync as c}from"node:fs";import x from"node:http";import{homedir as P,platform as R,tmpdir as F}from"node:os";import{dirname as B,join as M,resolve as K}from"node:path";import{fileURLToPath as v}from"node:url";var g=B(v(import.meta.url)),Df=K(g,".."),p=9322,a=400,W=60000,n=/^(about:|https?:\/\/|file:|chrome-error:\/\/)/i,l={chrome:"Chrome",cloak:"CloakBrowser"};function I(f){let L=Number.parseInt(String(f??""),10);return Number.isInteger(L)&&L>1024&&L<65535?L:null}function o(f){return d("sha256").update(String(f||"default")).digest("hex")}function u(f){let L=String(f||"chrome").trim().toLowerCase();if(L==="cloakbrowser"||L==="cloak-browser")return"cloak";if(L==="chromium"||L==="google-chrome"||L==="chrome")return"chrome";return L.replace(/[^a-z0-9_.-]/g,"-")||"chrome"}function s(f,L){let $=f.DM_CUA_BROWSER_PATH||(L==="cloak"?f.DM_CUA_CLOAK_PATH||f.CLOAKBROWSER_BINARY_PATH:void 0)||f.CHROME_PATH;return $?K(String($)):void 0}function t(f=process.env,L=process.cwd()){let $=f.DM_CUA_SCOPE||f.DM_CODING_AGENT_DIR||f.PI_CODING_AGENT_DIR||L,Q=o(K(String($||"."))),Z=String(f.DM_CUA_INSTANCE_ID||Q.slice(0,12)).replace(/[^a-zA-Z0-9_.-]/g,"-"),Y=u(f.DM_CUA_BROWSER||f.DM_BROWSER_CUA_BROWSER||f.DM_CUA_BROWSER_KIND),k=f.DM_CUA_BROWSER_DISPLAY_NAME||l[Y]||Y,A=s(f,Y),H=I(f.DM_CUA_PORT)??I(f.GREEDY_SEARCH_PORT)??p+Number.parseInt(Q.slice(0,8),16)%a,J=Y==="chrome"?"chrome":Y,V=K(f.DM_CUA_PROFILE_DIR||M(F(),`dm-cua-${J}-profile-${Z}`)),q=R()==="win32"?F():"/tmp";return{instanceId:Z,browserKind:Y,browserDisplayName:k,browserExecutablePath:A,port:H,profileDir:V,pidFile:M(V,"chrome.pid"),modeFile:M(V,"chrome-mode"),pagesCache:M(V,"cdp-pages.json"),socketDir:M(q,`dm-cua-cdp-${Z}`)}}var z=t(),r=z.port,Hf=z.profileDir;function U(){return{kind:z.browserKind,displayName:z.browserDisplayName,executablePath:z.browserExecutablePath,profileDir:z.profileDir,port:z.port}}function e(){if(z.browserKind==="cloak"&&!z.browserExecutablePath)throw Error(["CloakBrowser selected for browser CUA, but no executable path was provided.","Install/pre-download CloakBrowser yourself, then set DM_CUA_BROWSER_PATH, DM_CUA_CLOAK_PATH, CLOAKBROWSER_BINARY_PATH, or CHROME_PATH.","DM does not bundle the compiled CloakBrowser binary because its upstream license allows use but not redistribution."].join(" "));if(z.browserExecutablePath&&!T(z.browserExecutablePath))throw Error(`${z.browserDisplayName} executable not found: ${z.browserExecutablePath}`)}function w(){let L=[K(g,"..","greedysearch-dm"),K(g,"..","..","greedysearch-dm")].map(($)=>({launchScript:M($,"bin","launch.mjs"),cdpScript:M($,"bin","cdp.mjs")}));for(let $ of L)if(T($.launchScript)&&T($.cdpScript))return $;return L[0]}function h(){let f=process.env.GREEDY_SEARCH_VISIBLE??process.env.DM_CUA_VISIBLE??process.env.DM_BROWSER_CUA_VISIBLE??"1";return{...process.env,...z.browserExecutablePath?{CHROME_PATH:z.browserExecutablePath}:{},CDP_PROFILE_DIR:z.profileDir.replace(/\\/g,"/"),CDP_PAGES_CACHE:z.pagesCache.replace(/\\/g,"/"),CDP_SOCKET_DIR:z.socketDir.replace(/\\/g,"/"),GREEDY_SEARCH_BROWSER_LABEL:z.browserDisplayName,GREEDY_SEARCH_PORT:String(z.port),GREEDY_SEARCH_PROFILE_DIR:z.profileDir.replace(/\\/g,"/"),GREEDY_SEARCH_PID_FILE:z.pidFile.replace(/\\/g,"/"),GREEDY_SEARCH_MODE_FILE:z.modeFile.replace(/\\/g,"/"),GREEDY_SEARCH_ALLOW_PORT_CLEANUP:process.env.GREEDY_SEARCH_ALLOW_PORT_CLEANUP??"0",GREEDY_SEARCH_VISIBLE:f}}function ff(f){return n.test(f)}function Lf(f){return f.split(/\r?\n/).map((L)=>L.trimEnd()).filter(Boolean).map((L)=>{let $=L.trim().split(/\s{2,}/).filter(Boolean);if($.length===0)return null;let Q=$[0];if($.length===1)return{target:Q,title:"",url:""};if($.length===2&&ff($[1]))return{target:Q,title:"",url:$[1]};let Z=$.at(-1)??"",Y=$.slice(1,-1).join(" ");return{target:Q,title:Y,url:Z}}).filter(Boolean)}function $f(){let f=process.env.DM_CODING_AGENT_DIR||process.env.PI_CODING_AGENT_DIR;return f?K(f):M(P(),".dm","agent")}function Jf(){let f=M($f(),"cua","screenshots");N(f,{recursive:!0});let L=new Date().toISOString().replace(/[:.]/g,"-");return M(f,`browser-${L}.png`)}function m(f,L,{env:$=process.env,timeoutMs:Q=W}={}){return new Promise((Z,Y)=>{let k=O("node",[f,...L],{env:$,stdio:["ignore","pipe","pipe"]}),A="",H="",J=!1,V=setTimeout(()=>{if(J)return;J=!0,k.kill("SIGTERM"),Y(Error(`Timed out running ${f}`))},Q);k.stdout.on("data",(q)=>{A+=q.toString()}),k.stderr.on("data",(q)=>{H+=q.toString()}),k.on("error",(q)=>{if(J)return;J=!0,clearTimeout(V),Y(q)}),k.on("close",(q)=>{if(J)return;if(J=!0,clearTimeout(V),q!==0){Y(Error((H||A||`exit ${q}`).trim()));return}Z({stdout:A,stderr:H})})})}function Qf(f,L){return new Promise(($,Q)=>{let Z=x.request({host:"127.0.0.1",port:r,path:L,method:f},(Y)=>{let k="";Y.on("data",(A)=>{k+=A.toString()}),Y.on("end",()=>{if((Y.statusCode??500)>=400){Q(Error(`Browser CDP HTTP ${Y.statusCode}: ${k.trim()}`));return}try{$(k?JSON.parse(k):{})}catch(A){Q(A)}})});Z.on("error",Q),Z.end()})}async function Vf(f="about:blank"){return Qf("PUT",`/json/new?${encodeURIComponent(f)}`)}async function D(f,L={}){let{cdpScript:$}=w();return m($,f,{env:h(),timeoutMs:L.timeoutMs??W})}async function G(){let f=await D(["list"]);return Lf(f.stdout)}function b(f,L){if(!L)return f[0]??null;let $=L.toUpperCase();return f.find((Q)=>Q.target.toUpperCase().startsWith($))??null}async function C(){let{launchScript:f,cdpScript:L}=w();if(!T(f)||!T(L))throw Error("Bundled browser helpers are missing. Rebuild DM so dm-cua and greedysearch-dm are staged together.");return{launchScript:f,cdpScript:L}}async function _(){e();let{launchScript:f}=await C();await m(f,[],{env:h(),timeoutMs:W});let L=await G();if(L.length===0)await Vf("about:blank"),L=await G();return L}async function y(f){let L=await _(),$=b(L,f);if(!$){if(f)throw Error(`No browser tab matching prefix "${f}". Run browser_cua(list) first.`);throw Error("No browser tab available after browser bootstrap.")}return{tab:$.target,page:$,pages:L}}async function i({action:f,tab:L,url:$,selector:Q,x:Z,y:Y,text:k,expression:A,outputPath:H}={}){switch(f){case"list":{let J=await _();return{status:"ok",action:f,browser:U(),pages:J}}case"navigate":{if(!$)throw Error("url is required for action=navigate");let J=await y(L),V=await D(["nav",J.tab,$],{timeoutMs:90000}),q=await G(),j=b(q,J.tab)??J.page;return{status:"ok",action:f,browser:U(),tab:J.tab,url:j?.url||$,title:j?.title||"",message:V.stdout.trim(),pages:q}}case"snapshot":{let J=await y(L),V=await D(["snap",J.tab]);return{status:"ok",action:f,browser:U(),tab:J.tab,title:J.page?.title||"",url:J.page?.url||"",snapshot:V.stdout.trim()}}case"screenshot":{let J=await y(L),V=H?K(H):Jf();N(B(V),{recursive:!0});let q=await D(["shot",J.tab,V],{timeoutMs:120000}),j=c(V).toString("base64");return{status:"ok",action:f,browser:U(),tab:J.tab,title:J.page?.title||"",url:J.page?.url||"",imagePath:V,imageBase64:j,mimeType:"image/png",message:q.stdout.trim()}}case"click":{if(!Q)throw Error("selector is required for action=click");let J=await y(L),V=await D(["click",J.tab,Q]);return{status:"ok",action:f,browser:U(),tab:J.tab,selector:Q,message:V.stdout.trim()}}case"click_xy":{if(typeof Z!=="number"||Number.isNaN(Z)||typeof Y!=="number"||Number.isNaN(Y))throw Error("x and y are required numbers for action=click_xy");let J=await y(L),V=await D(["clickxy",J.tab,String(Z),String(Y)]);return{status:"ok",action:f,browser:U(),tab:J.tab,x:Z,y:Y,message:V.stdout.trim()}}case"type":{if(k==null||k==="")throw Error("text is required for action=type");let J=await y(L),V=await D(["type",J.tab,k]);return{status:"ok",action:f,browser:U(),tab:J.tab,textLength:k.length,message:V.stdout.trim()}}case"evaluate":{if(!A)throw Error("expression is required for action=evaluate");let J=await y(L),V=await D(["eval",J.tab,A]);return{status:"ok",action:f,browser:U(),tab:J.tab,value:V.stdout.trim()}}case"stop":{let{launchScript:J}=await C();try{await D(["stop"])}catch{}let V=await m(J,["--kill"],{env:h(),timeoutMs:30000});return{status:"ok",action:f,browser:U(),message:(V.stdout||"Stopped browser CUA lane.").trim()}}default:throw Error(`Unsupported browser action: ${f}`)}}function E(f){switch(f.action){case"list":{if(!Array.isArray(f.pages)||f.pages.length===0)return"Browser CUA is ready, but no pages are open.";return["Open browser tabs:",...f.pages.map((L)=>L.url?`${L.target} ${L.title||"(untitled)"} ${L.url}`:`${L.target} ${L.title||"(untitled)"}`)].join(`
|
|
2
2
|
`)}case"navigate":return[`Navigated browser tab ${f.tab} to ${f.url||"(unknown URL)"}`,f.title?`Title: ${f.title}`:""].filter(Boolean).join(`
|
|
3
3
|
`);case"snapshot":return[`Browser snapshot for ${f.tab}`,f.snapshot||"(empty snapshot)"].join(`
|
|
4
4
|
`);case"screenshot":return[`Saved browser screenshot for ${f.tab} at ${f.imagePath}`,f.title?`Title: ${f.title}`:"",f.url?`URL: ${f.url}`:""].filter(Boolean).join(`
|
|
5
|
-
`);case"click":return`Clicked selector ${JSON.stringify(f.selector)} on browser tab ${f.tab}`;case"click_xy":return`Clicked browser tab ${f.tab} at CSS coordinates (${f.x}, ${f.y})`;case"type":return`Typed ${f.textLength} characters into browser tab ${f.tab}`;case"evaluate":return`Browser evaluate result for ${f.tab}: ${f.value}`;case"stop":return f.message||"Stopped browser CUA lane.";default:return JSON.stringify(f,null,2)}}var
|
|
5
|
+
`);case"click":return`Clicked selector ${JSON.stringify(f.selector)} on browser tab ${f.tab}`;case"click_xy":return`Clicked browser tab ${f.tab} at CSS coordinates (${f.x}, ${f.y})`;case"type":return`Typed ${f.textLength} characters into browser tab ${f.tab}`;case"evaluate":return`Browser evaluate result for ${f.tab}: ${f.value}`;case"stop":return f.message||"Stopped browser CUA lane.";default:return JSON.stringify(f,null,2)}}var Xf=X.Object({action:X.Union([X.Literal("list"),X.Literal("navigate"),X.Literal("snapshot"),X.Literal("screenshot"),X.Literal("click"),X.Literal("click_xy"),X.Literal("type"),X.Literal("evaluate"),X.Literal("stop")],{description:"Browser action. Start with list, snapshot, or screenshot before mutating actions on unfamiliar pages."}),tab:X.Optional(X.String({description:"Optional target tab prefix from browser_cua(list). If omitted, the first live page in the dedicated profile is used."})),url:X.Optional(X.String({description:"URL for action=navigate."})),selector:X.Optional(X.String({description:"CSS selector for action=click."})),x:X.Optional(X.Number({description:"CSS x coordinate for action=click_xy."})),y:X.Optional(X.Number({description:"CSS y coordinate for action=click_xy."})),text:X.Optional(X.String({description:"Text for action=type."})),expression:X.Optional(X.String({description:"JavaScript expression for action=evaluate."}))},{additionalProperties:!1});function Zf(f){f.on("session_start",async(L,$)=>{let{launchScript:Q,cdpScript:Z}=w();if(!S(Q)||!S(Z))$.ui.notify("dm-cua: bundled browser helpers are missing. Rebuild DM so dm-cua and greedysearch-dm are staged together.","warning")}),f.registerTool({name:"browser_cua",label:"Browser CUA",description:"Drive a dedicated Chrome browser profile through DM's bundled browser CUA lane. Inspect first with list, snapshot, or screenshot. Fresh empty profiles auto-bootstrap a blank tab. Use navigate for deterministic URLs, click/click_xy/type for page interaction, evaluate for focused DOM reads, and stop to close the dedicated browser lane.",promptSnippet:"First-class browser CUA for Chrome. Inspect before acting. Screenshot complex pages before click/scroll loops.",parameters:Xf,async execute(L,$){try{let Q=await i($),Z=[{type:"text",text:E(Q)}];if(Q.imageBase64&&Q.mimeType)Z.push({type:"image",data:Q.imageBase64,mimeType:Q.mimeType});return{content:Z,details:Q}}catch(Q){let Z=Q instanceof Error?Q.message:String(Q);return{content:[{type:"text",text:`browser_cua failed: ${Z}`}],details:{status:"error",action:$?.action??null,message:Z}}}}})}export{Zf as default};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{createHash as
|
|
1
|
+
import{createHash as P}from"node:crypto";import{spawn as O}from"node:child_process";import{existsSync as f,mkdirSync as w,readFileSync as b}from"node:fs";import R from"node:http";import{homedir as m,platform as S,tmpdir as y}from"node:os";import{dirname as C,join as M,resolve as U}from"node:path";import{fileURLToPath as v}from"node:url";var W=C(v(import.meta.url)),HQ=U(W,".."),x=9322,d=400,F=60000,p=/^(about:|https?:\/\/|file:|chrome-error:\/\/)/i,c={chrome:"Chrome",cloak:"CloakBrowser"};function B(Q){let V=Number.parseInt(String(Q??""),10);return Number.isInteger(V)&&V>1024&&V<65535?V:null}function u(Q){return P("sha256").update(String(Q||"default")).digest("hex")}function l(Q){let V=String(Q||"chrome").trim().toLowerCase();if(V==="cloakbrowser"||V==="cloak-browser")return"cloak";if(V==="chromium"||V==="google-chrome"||V==="chrome")return"chrome";return V.replace(/[^a-z0-9_.-]/g,"-")||"chrome"}function i(Q,V){let Z=Q.DM_CUA_BROWSER_PATH||(V==="cloak"?Q.DM_CUA_CLOAK_PATH||Q.CLOAKBROWSER_BINARY_PATH:void 0)||Q.CHROME_PATH;return Z?U(String(Z)):void 0}function n(Q=process.env,V=process.cwd()){let Z=Q.DM_CUA_SCOPE||Q.DM_CODING_AGENT_DIR||Q.PI_CODING_AGENT_DIR||V,Y=u(U(String(Z||"."))),H=String(Q.DM_CUA_INSTANCE_ID||Y.slice(0,12)).replace(/[^a-zA-Z0-9_.-]/g,"-"),J=l(Q.DM_CUA_BROWSER||Q.DM_BROWSER_CUA_BROWSER||Q.DM_CUA_BROWSER_KIND),q=Q.DM_CUA_BROWSER_DISPLAY_NAME||c[J]||J,L=i(Q,J),k=B(Q.DM_CUA_PORT)??B(Q.GREEDY_SEARCH_PORT)??x+Number.parseInt(Y.slice(0,8),16)%d,X=J==="chrome"?"chrome":J,$=U(Q.DM_CUA_PROFILE_DIR||M(y(),`dm-cua-${X}-profile-${H}`)),K=S()==="win32"?y():"/tmp";return{instanceId:H,browserKind:J,browserDisplayName:q,browserExecutablePath:L,port:k,profileDir:$,pidFile:M($,"chrome.pid"),modeFile:M($,"chrome-mode"),pagesCache:M($,"cdp-pages.json"),socketDir:M(K,`dm-cua-cdp-${H}`)}}var z=n(),a=z.port,KQ=z.profileDir;function A(){return{kind:z.browserKind,displayName:z.browserDisplayName,executablePath:z.browserExecutablePath,profileDir:z.profileDir,port:z.port}}function o(){if(z.browserKind==="cloak"&&!z.browserExecutablePath)throw Error(["CloakBrowser selected for browser CUA, but no executable path was provided.","Install/pre-download CloakBrowser yourself, then set DM_CUA_BROWSER_PATH, DM_CUA_CLOAK_PATH, CLOAKBROWSER_BINARY_PATH, or CHROME_PATH.","DM does not bundle the compiled CloakBrowser binary because its upstream license allows use but not redistribution."].join(" "));if(z.browserExecutablePath&&!f(z.browserExecutablePath))throw Error(`${z.browserDisplayName} executable not found: ${z.browserExecutablePath}`)}function E(){let V=[U(W,"..","greedysearch-dm"),U(W,"..","..","greedysearch-dm")].map((Z)=>({launchScript:M(Z,"bin","launch.mjs"),cdpScript:M(Z,"bin","cdp.mjs")}));for(let Z of V)if(f(Z.launchScript)&&f(Z.cdpScript))return Z;return V[0]}function N(){let Q=process.env.GREEDY_SEARCH_VISIBLE??process.env.DM_CUA_VISIBLE??process.env.DM_BROWSER_CUA_VISIBLE??"1";return{...process.env,...z.browserExecutablePath?{CHROME_PATH:z.browserExecutablePath}:{},CDP_PROFILE_DIR:z.profileDir.replace(/\\/g,"/"),CDP_PAGES_CACHE:z.pagesCache.replace(/\\/g,"/"),CDP_SOCKET_DIR:z.socketDir.replace(/\\/g,"/"),GREEDY_SEARCH_BROWSER_LABEL:z.browserDisplayName,GREEDY_SEARCH_PORT:String(z.port),GREEDY_SEARCH_PROFILE_DIR:z.profileDir.replace(/\\/g,"/"),GREEDY_SEARCH_PID_FILE:z.pidFile.replace(/\\/g,"/"),GREEDY_SEARCH_MODE_FILE:z.modeFile.replace(/\\/g,"/"),GREEDY_SEARCH_ALLOW_PORT_CLEANUP:process.env.GREEDY_SEARCH_ALLOW_PORT_CLEANUP??"0",GREEDY_SEARCH_VISIBLE:Q}}function s(Q){return p.test(Q)}function t(Q){return Q.split(/\r?\n/).map((V)=>V.trimEnd()).filter(Boolean).map((V)=>{let Z=V.trim().split(/\s{2,}/).filter(Boolean);if(Z.length===0)return null;let Y=Z[0];if(Z.length===1)return{target:Y,title:"",url:""};if(Z.length===2&&s(Z[1]))return{target:Y,title:"",url:Z[1]};let H=Z.at(-1)??"",J=Z.slice(1,-1).join(" ");return{target:Y,title:J,url:H}}).filter(Boolean)}function r(){let Q=process.env.DM_CODING_AGENT_DIR||process.env.PI_CODING_AGENT_DIR;return Q?U(Q):M(m(),".dm","agent")}function e(){let Q=M(r(),"cua","screenshots");w(Q,{recursive:!0});let V=new Date().toISOString().replace(/[:.]/g,"-");return M(Q,`browser-${V}.png`)}function I(Q,V,{env:Z=process.env,timeoutMs:Y=F}={}){return new Promise((H,J)=>{let q=O("node",[Q,...V],{env:Z,stdio:["ignore","pipe","pipe"]}),L="",k="",X=!1,$=setTimeout(()=>{if(X)return;X=!0,q.kill("SIGTERM"),J(Error(`Timed out running ${Q}`))},Y);q.stdout.on("data",(K)=>{L+=K.toString()}),q.stderr.on("data",(K)=>{k+=K.toString()}),q.on("error",(K)=>{if(X)return;X=!0,clearTimeout($),J(K)}),q.on("close",(K)=>{if(X)return;if(X=!0,clearTimeout($),K!==0){J(Error((k||L||`exit ${K}`).trim()));return}H({stdout:L,stderr:k})})})}function QQ(Q,V){return new Promise((Z,Y)=>{let H=R.request({host:"127.0.0.1",port:a,path:V,method:Q},(J)=>{let q="";J.on("data",(L)=>{q+=L.toString()}),J.on("end",()=>{if((J.statusCode??500)>=400){Y(Error(`Browser CDP HTTP ${J.statusCode}: ${q.trim()}`));return}try{Z(q?JSON.parse(q):{})}catch(L){Y(L)}})});H.on("error",Y),H.end()})}async function VQ(Q="about:blank"){return QQ("PUT",`/json/new?${encodeURIComponent(Q)}`)}async function D(Q,V={}){let{cdpScript:Z}=E();return I(Z,Q,{env:N(),timeoutMs:V.timeoutMs??F})}async function T(){let Q=await D(["list"]);return t(Q.stdout)}function _(Q,V){if(!V)return Q[0]??null;let Z=V.toUpperCase();return Q.find((Y)=>Y.target.toUpperCase().startsWith(Z))??null}async function h(){let{launchScript:Q,cdpScript:V}=E();if(!f(Q)||!f(V))throw Error("Bundled browser helpers are missing. Rebuild DM so dm-cua and greedysearch-dm are staged together.");return{launchScript:Q,cdpScript:V}}async function g(){o();let{launchScript:Q}=await h();await I(Q,[],{env:N(),timeoutMs:F});let V=await T();if(V.length===0)await VQ("about:blank"),V=await T();return V}async function j(Q){let V=await g(),Z=_(V,Q);if(!Z){if(Q)throw Error(`No browser tab matching prefix "${Q}". Run browser_cua(list) first.`);throw Error("No browser tab available after browser bootstrap.")}return{tab:Z.target,page:Z,pages:V}}async function LQ({action:Q,tab:V,url:Z,selector:Y,x:H,y:J,text:q,expression:L,outputPath:k}={}){switch(Q){case"list":{let X=await g();return{status:"ok",action:Q,browser:A(),pages:X}}case"navigate":{if(!Z)throw Error("url is required for action=navigate");let X=await j(V),$=await D(["nav",X.tab,Z],{timeoutMs:90000}),K=await T(),G=_(K,X.tab)??X.page;return{status:"ok",action:Q,browser:A(),tab:X.tab,url:G?.url||Z,title:G?.title||"",message:$.stdout.trim(),pages:K}}case"snapshot":{let X=await j(V),$=await D(["snap",X.tab]);return{status:"ok",action:Q,browser:A(),tab:X.tab,title:X.page?.title||"",url:X.page?.url||"",snapshot:$.stdout.trim()}}case"screenshot":{let X=await j(V),$=k?U(k):e();w(C($),{recursive:!0});let K=await D(["shot",X.tab,$],{timeoutMs:120000}),G=b($).toString("base64");return{status:"ok",action:Q,browser:A(),tab:X.tab,title:X.page?.title||"",url:X.page?.url||"",imagePath:$,imageBase64:G,mimeType:"image/png",message:K.stdout.trim()}}case"click":{if(!Y)throw Error("selector is required for action=click");let X=await j(V),$=await D(["click",X.tab,Y]);return{status:"ok",action:Q,browser:A(),tab:X.tab,selector:Y,message:$.stdout.trim()}}case"click_xy":{if(typeof H!=="number"||Number.isNaN(H)||typeof J!=="number"||Number.isNaN(J))throw Error("x and y are required numbers for action=click_xy");let X=await j(V),$=await D(["clickxy",X.tab,String(H),String(J)]);return{status:"ok",action:Q,browser:A(),tab:X.tab,x:H,y:J,message:$.stdout.trim()}}case"type":{if(q==null||q==="")throw Error("text is required for action=type");let X=await j(V),$=await D(["type",X.tab,q]);return{status:"ok",action:Q,browser:A(),tab:X.tab,textLength:q.length,message:$.stdout.trim()}}case"evaluate":{if(!L)throw Error("expression is required for action=evaluate");let X=await j(V),$=await D(["eval",X.tab,L]);return{status:"ok",action:Q,browser:A(),tab:X.tab,value:$.stdout.trim()}}case"stop":{let{launchScript:X}=await h();try{await D(["stop"])}catch{}let $=await I(X,["--kill"],{env:N(),timeoutMs:30000});return{status:"ok",action:Q,browser:A(),message:($.stdout||"Stopped browser CUA lane.").trim()}}default:throw Error(`Unsupported browser action: ${Q}`)}}function MQ(Q){switch(Q.action){case"list":{if(!Array.isArray(Q.pages)||Q.pages.length===0)return"Browser CUA is ready, but no pages are open.";return["Open browser tabs:",...Q.pages.map((V)=>V.url?`${V.target} ${V.title||"(untitled)"} ${V.url}`:`${V.target} ${V.title||"(untitled)"}`)].join(`
|
|
2
2
|
`)}case"navigate":return[`Navigated browser tab ${Q.tab} to ${Q.url||"(unknown URL)"}`,Q.title?`Title: ${Q.title}`:""].filter(Boolean).join(`
|
|
3
3
|
`);case"snapshot":return[`Browser snapshot for ${Q.tab}`,Q.snapshot||"(empty snapshot)"].join(`
|
|
4
4
|
`);case"screenshot":return[`Saved browser screenshot for ${Q.tab} at ${Q.imagePath}`,Q.title?`Title: ${Q.title}`:"",Q.url?`URL: ${Q.url}`:""].filter(Boolean).join(`
|
|
5
|
-
`);case"click":return`Clicked selector ${JSON.stringify(Q.selector)} on browser tab ${Q.tab}`;case"click_xy":return`Clicked browser tab ${Q.tab} at CSS coordinates (${Q.x}, ${Q.y})`;case"type":return`Typed ${Q.textLength} characters into browser tab ${Q.tab}`;case"evaluate":return`Browser evaluate result for ${Q.tab}: ${Q.value}`;case"stop":return Q.message||"Stopped browser CUA lane.";default:return JSON.stringify(Q,null,2)}}export{
|
|
5
|
+
`);case"click":return`Clicked selector ${JSON.stringify(Q.selector)} on browser tab ${Q.tab}`;case"click_xy":return`Clicked browser tab ${Q.tab} at CSS coordinates (${Q.x}, ${Q.y})`;case"type":return`Typed ${Q.textLength} characters into browser tab ${Q.tab}`;case"evaluate":return`Browser evaluate result for ${Q.tab}: ${Q.value}`;case"stop":return Q.message||"Stopped browser CUA lane.";default:return JSON.stringify(Q,null,2)}}export{LQ as runBrowserAction,n as resolveBrowserRuntimeConfig,E as resolveBrowserHelperPaths,MQ as renderBrowserActionText,t as parseListOutput,g as ensureBrowserReady,KQ as BROWSER_PROFILE_DIR};
|