@duckmind/dm-windows-x64 0.53.7 → 0.59.0

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.exe CHANGED
Binary file
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "status": "ok",
3
- "prepared_at": "2026-07-23T06:36:46.219447+00:00",
3
+ "prepared_at": "2026-08-03T02:55:20.310618+00:00",
4
4
  "managed_entries": [
5
5
  {
6
6
  "id": "dm-context",
@@ -86,8 +86,8 @@
86
86
  "src/utils.js"
87
87
  ],
88
88
  "file_count": 3,
89
- "before_bytes": 25960,
90
- "after_bytes": 11302,
89
+ "before_bytes": 26171,
90
+ "after_bytes": 11362,
91
91
  "externals": [
92
92
  "@duckmind/dm-agent-core",
93
93
  "@duckmind/dm-ai",
@@ -632,11 +632,12 @@
632
632
  "files": [
633
633
  "bin/browser-cua.mjs",
634
634
  "index.js",
635
- "src/browser-cua-lib.mjs"
635
+ "src/browser-cua-lib.mjs",
636
+ "src/browser-install.mjs"
636
637
  ],
637
- "file_count": 3,
638
- "before_bytes": 22185,
639
- "after_bytes": 32604,
638
+ "file_count": 4,
639
+ "before_bytes": 34448,
640
+ "after_bytes": 57436,
640
641
  "externals": [
641
642
  "@duckmind/dm-agent-core",
642
643
  "@duckmind/dm-ai",
@@ -1 +1 @@
1
- import{DynamicBorder as d}from"@duckmind/dm-coding-agent";import{Container as n,Text as W,Spacer as w}from"@duckmind/dm-tui";var A=(N)=>{if(N==null)return"N/A";if(N>=1e6)return(N/1e6).toFixed(1)+"M";if(N>=1000)return Math.round(N/1000)+"k";return N.toString()};function a(N){N.registerCommand("context",{description:"Show context usage visualization",handler:async(s,O)=>{let Q=await O.getContextUsage();if(!Q){O.ui.notify("Context usage info not available.","warning");return}let R=O.sessionManager.getBranch(),g=O.getSystemPrompt(),h=N.getActiveTools(),r=N.getAllTools().filter((z)=>h.includes(z.name)),q=(z)=>Math.ceil(z.length/4),K=0,Z=0,G=0;for(let z of R)if(z.type==="message"){let _=z.message;if(_.role==="user"){if(typeof _.content==="string")K+=q(_.content);else if(Array.isArray(_.content)){for(let F of _.content)if(F.type==="text")K+=q(F.text)}}else if(_.role==="assistant"){if(typeof _.content==="string")K+=q(_.content);else if(Array.isArray(_.content))for(let F of _.content){if(F.type==="text")K+=q(F.text);if(F.type==="toolCall")Z+=q(JSON.stringify(F))}}else if(_.role==="toolResult"){if(Array.isArray(_.content)){for(let F of _.content)if(F.type==="text")G+=q(F.text)}}else if(_.role==="bashExecution")Z+=q(_.command||"")}else if(z.type==="branch_summary"||z.type==="compaction")K+=q(z.summary||"");let f=q(g),B=q(JSON.stringify(r)),V=Q.tokens,$=Q.contextWindow;if(V==null||$==null||Q.percent==null){O.ui.notify("Context usage info not available.","warning");return}let L=f+B+K+Z+G,X=L>0?V/L:1,S=Math.round(f*X),U=Math.round(B*X),b=Math.round(K*X),D=Math.round(Z*X),y=Math.round(G*X);await O.ui.custom((z,_,F,T)=>{let J=new n;J.addChild(new d((M)=>_.fg("accent",M))),J.addChild(new W(_.fg("accent",_.bold(" Context Usage")),1,0)),J.addChild(new w(1));let j=[{label:"System Prompt",value:S,color:"muted"},{label:"System Tools",value:U,color:"dim"},{label:"Tool Call",value:D+y,color:"success"},{label:"Messages",value:b,color:"accent"}],u=Math.max(0,V-(S+U+b+D+y));if(u>10)j.push({label:"Other",value:u,color:"dim"});j.push({label:"Available",value:Math.max(0,$-V),color:"borderMuted"});let v=10,x=5,P=v*x,Y=[];j.forEach((M)=>{if(M.label==="Available")return;let E=Math.round(M.value/$*P);if(E===0&&M.value>0)E=1;for(let I=0;I<E&&Y.length<P;I++)Y.push({color:M.color,filled:!0})});while(Y.length<P)Y.push({color:"borderMuted",filled:!1});let H=[];for(let M=0;M<x;M++){let E="";for(let I=0;I<v;I++){let C=Y[M*v+I];E+=_.fg(C.color,C.filled?"■ ":"□ ")}H.push(E.trimEnd())}let k=`${_.fg("text",_.bold("Total Usage".padEnd(16)))} ${_.fg("text",_.bold(A(V).padStart(7)))} ${_.fg("text",_.bold(`(${Q.percent.toFixed(1).padStart(5)}%)`))}`,l=j.map((M)=>{let E=M.label.padEnd(14),I=A(M.value).padStart(7),C=(M.value/$*100).toFixed(1).padStart(5),m=M.label==="Available"?"□":"■";return`${_.fg(M.color,m)} ${_.fg("text",E)} ${_.fg("accent",I)} (${C}%)`}),p=[k,"",...l],c=20,i=Math.max(H.length,p.length);for(let M=0;M<i;M++){let E=(H[M]||"").padEnd(c),I=p[M]||"";J.addChild(new W(` ${E} ${I}`,1,0))}return J.addChild(new w(1)),J.addChild(new W(_.fg("dim"," Press any key to close"),1,0)),J.addChild(new d((M)=>_.fg("accent",M))),{render:(M)=>J.render(M),invalidate:()=>J.invalidate(),handleInput:(M)=>T(void 0)}},{overlay:!0})}})}export{a as default};
1
+ import{DynamicBorder as d}from"@duckmind/dm-coding-agent";import{Container as a,Text as W,Spacer as w}from"@duckmind/dm-tui";var A=(N)=>{if(N==null)return"N/A";if(N>=1e6)return(N/1e6).toFixed(1)+"M";if(N>=1000)return Math.round(N/1000)+"k";return N.toString()};function s(N){N.registerCommand("context",{description:"Show context usage visualization",handler:async(o,O)=>{let Q=await O.getContextUsage();if(!Q){O.ui.notify("Context usage info not available.","warning");return}let R=O.sessionManager.getBranch(),g=O.getSystemPrompt(),h=N.getActiveTools(),r=N.getAllTools().filter((z)=>h.includes(z.name)),q=(z)=>Math.ceil(z.length/4),K=0,Z=0,G=0;for(let z of R)if(z.type==="message"){let _=z.message;if(_.role==="user"){if(typeof _.content==="string")K+=q(_.content);else if(Array.isArray(_.content)){for(let F of _.content)if(F.type==="text")K+=q(F.text)}}else if(_.role==="assistant"){if(typeof _.content==="string")K+=q(_.content);else if(Array.isArray(_.content))for(let F of _.content){if(F.type==="text")K+=q(F.text);if(F.type==="toolCall")Z+=q(JSON.stringify(F))}}else if(_.role==="toolResult"){if(Array.isArray(_.content)){for(let F of _.content)if(F.type==="text")G+=q(F.text)}}else if(_.role==="bashExecution")Z+=q(_.command||"")}else if(z.type==="branch_summary"||z.type==="compaction")K+=q(z.summary||"");let f=q(g),B=q(JSON.stringify(r)),V=Q.tokens,$=Q.contextWindow;if(V==null||$==null||Q.percent==null){O.ui.notify("Context usage info not available.","warning");return}let T=Q.percent,L=f+B+K+Z+G,X=L>0?V/L:1,S=Math.round(f*X),U=Math.round(B*X),b=Math.round(K*X),D=Math.round(Z*X),y=Math.round(G*X);await O.ui.custom((z,_,F,k)=>{let J=new a;J.addChild(new d((M)=>_.fg("accent",M))),J.addChild(new W(_.fg("accent",_.bold(" Context Usage")),1,0)),J.addChild(new w(1));let j=[{label:"System Prompt",value:S,color:"muted"},{label:"System Tools",value:U,color:"dim"},{label:"Tool Call",value:D+y,color:"success"},{label:"Messages",value:b,color:"accent"}],u=Math.max(0,V-(S+U+b+D+y));if(u>10)j.push({label:"Other",value:u,color:"dim"});j.push({label:"Available",value:Math.max(0,$-V),color:"borderMuted"});let v=10,x=5,H=v*x,Y=[];j.forEach((M)=>{if(M.label==="Available")return;let E=Math.round(M.value/$*H);if(E===0&&M.value>0)E=1;for(let I=0;I<E&&Y.length<H;I++)Y.push({color:M.color,filled:!0})});while(Y.length<H)Y.push({color:"borderMuted",filled:!1});let P=[];for(let M=0;M<x;M++){let E="";for(let I=0;I<v;I++){let C=Y[M*v+I];E+=_.fg(C.color,C.filled?"■ ":"□ ")}P.push(E.trimEnd())}let l=`${_.fg("text",_.bold("Total Usage".padEnd(16)))} ${_.fg("text",_.bold(A(V).padStart(7)))} ${_.fg("text",_.bold(`(${T.toFixed(1).padStart(5)}%)`))}`,c=j.map((M)=>{let E=M.label.padEnd(14),I=A(M.value).padStart(7),C=(M.value/$*100).toFixed(1).padStart(5),n=M.label==="Available"?"□":"■";return`${_.fg(M.color,n)} ${_.fg("text",E)} ${_.fg("accent",I)} (${C}%)`}),p=[l,"",...c],i=20,m=Math.max(P.length,p.length);for(let M=0;M<m;M++){let E=(P[M]||"").padEnd(i),I=p[M]||"";J.addChild(new W(` ${E} ${I}`,1,0))}return J.addChild(new w(1)),J.addChild(new W(_.fg("dim"," Press any key to close"),1,0)),J.addChild(new d((M)=>_.fg("accent",M))),{render:(M)=>J.render(M),invalidate:()=>J.invalidate(),handleInput:(M)=>k(void 0)}},{overlay:!0})}})}export{s as default};
@@ -1,7 +1,7 @@
1
- import{Type as U}from"@sinclair/typebox";var k=(z)=>{if(z==null)return"N/A";if(z>=1e6)return(z/1e6).toFixed(1)+"M";if(z>=1000)return Math.round(z/1000)+"k";return z.toString()};var g=["context_tag","context_log","context_checkout"],L=null,R=null,v=(z)=>g.includes(z),h=(z,Y)=>{if(Y.toLowerCase()==="root"){let A=z.getTree();return A.length>0?A[0].entry.id:Y}if(/^[0-9a-f]{8,}$/i.test(Y))return Y;let O=[...z.getTree()];while(O.length>0){let A=O.pop();if(z.getLabel(A.entry.id)===Y)return A.entry.id;if(A.children?.length)O.push(...A.children)}return Y},d=U.Object({limit:U.Optional(U.Number({description:"History limit for visible entries (default: 50)."})),verbose:U.Optional(U.Boolean({description:"If true, show ALL messages. If false (default), collapses intermediate AI steps and only shows 'milestones': User messages, Tags, Branch Points, and Summaries."}))}),p=U.Object({target:U.String({description:"Where to jump/squash to. Can be a tag name (e.g., 'task-start'), a commit ID, or 'root'. This is the base for your new branch."}),message:U.String({description:"The 'Carryover Message' for the new branch. A summary of your *current* progress/lessons that you want to bring with you to the new state. This ensures you don't lose key information when switching contexts. Good summary message: '[Status] + [Reason] + [Important Changes] + [Carryover Data]'"}),backupTag:U.Optional(U.String({description:"Optional tag name to apply to the CURRENT state before checking out. Use this to create an automatic backup of the history you are about to leave/squash."}))}),y=U.Object({name:U.String({description:"The tag/milestone name. Use meaningful names."}),target:U.Optional(U.String({description:"The commit ID to tag. Defaults to HEAD (current state)."}))});function c(z){z.registerCommand("acm",{description:"Enable agentic context management for the current session",handler:async(O,A)=>{if(L=A,A.ui.notify("Agentic Context Management enabled.","info"),z.sendMessage({customType:"dm-context",content:"use context-management skill",display:!1},{deliverAs:"followUp"}),O)z.sendUserMessage(O)}});let Y=(O,A,E)=>{for(let w of A){if(O.getLabel(w.entry.id)===E)return w.entry.id;let W=Y(O,w.children,E);if(W)return W}return null};z.registerTool({name:"context_tag",label:"Context Tag",description:"Creates a 'Save Point' (Bookmark) in the history. Use this before trying risky changes or when a feature is stable. 'Untagged progress is risky'.",parameters:y,async execute(O,A,E,w,W){let F=W.sessionManager,J=Y(F,F.getTree(),A.name);if(J)return{content:[{type:"text",text:`Error: Tag '${A.name}' already exists at ${J}. Tag names must be unique. Use a different name or delete the existing tag first.`}],details:{}};let G=A.target?h(F,A.target):void 0;if(!G){let Z=F.getBranch();for(let $=Z.length-1;$>=0;$--){let Q=Z[$];if(Q.type==="message"&&Q.message.role==="toolResult"){let X=Q.message;if(v(X.toolName))continue;G=Q.id;break}if(Q.type==="message"&&Q.message.role==="assistant"){if(!Q.message.content.some((f)=>f.type==="toolCall"&&v(f.name))){G=Q.id;break}}G=Q.id;break}if(!G)G=F.getLeafId()??""}return z.setLabel(G,A.name),{content:[{type:"text",text:`Created tag '${A.name}' at ${G}`}],details:{}}}}),z.registerTool({name:"context_log",label:"Context Log",description:"Show the entire history structure (status, message, tags, milestones). Analogous to 'git log --graph --oneline --decorate'",parameters:d,async execute(O,A,E,w,W){let F=W.sessionManager,J=F.getBranch(),G=F.getLeafId(),Z=A.verbose??!1,$=A.limit??50,Q=new Set(J.map((_)=>_.id)),X=[];J.forEach((_)=>{X.push(_),F.getChildren(_.id).forEach((D)=>{if((D.type==="branch_summary"||D.type==="compaction")&&!Q.has(D.id))X.push(D)})});let I=(_)=>{if(_.type==="branch_summary"||_.type==="compaction")return _.summary||"[No summary provided]";if(_.type==="label")return`tag: ${_.label}`;if(_.type==="message"){let B=_.message;if(B.role==="toolResult"){let D=B;if(!Z&&v(D.toolName))return"";let K=((u)=>{return u.map((N)=>N.type==="text"?N.text:"").join(" ").trim()})(D.content),V=D.details;if((D.toolName==="read"||D.toolName==="edit")&&V&&"path"in V&&typeof V.path==="string")K=`${V.path}: ${K}`;return`(${D.toolName}) ${K}`}if(B.role==="bashExecution")return`[Bash] ${B.command}`;if(B.role==="user"||B.role==="assistant"){let D="";if(typeof B.content==="string")D=B.content;else if(Array.isArray(B.content))D=B.content.map((K)=>{if(typeof K==="object"&&K!==null&&"text"in K)return K.text;return""}).join(" ").trim();let j="";if(B.role==="assistant")j=B.content.filter((V)=>V.type==="toolCall").filter((V)=>Z||!v(V.name)).map((V)=>`call: ${V.name}(${JSON.stringify(V.arguments)})`).join("; ");return[D,j].filter(Boolean).join(" ")}}return""},f=(_)=>{if(_.id===G)return!0;if(J.length>0&&_.id===J[0].id)return!0;if(F.getLabel(_.id))return!0;if(_.type==="label")return!1;if(_.type==="branch_summary"||_.type==="compaction")return!0;if(F.getChildren(_.id).length>1)return!0;if(_.type==="message"&&_.message.role==="user")return!0;return!1},H=new Set;X.forEach((_)=>{if(Z||f(_))H.add(_.id)});let b=X.filter((_)=>H.has(_.id));if(b.length>$){let _=new Set(b.slice(-$).map((B)=>B.id));H.clear(),_.forEach((B)=>H.add(B))}let S=[],M=0;if(X.forEach((_)=>{if(!H.has(_.id)){M++;return}if(M>0)S.push(` : ... (${M} hidden messages) ...`),M=0;let B=_.id===G,D=F.getLabel(_.id),j=I(_).replace(/\s+/g," "),K=_.type.toUpperCase();if(_.type==="message"){let P=_.message;K=P.role==="assistant"?"AI":P.role==="user"?"USER":P.role==="bashExecution"?"BASH":"TOOL"}else if(_.type==="branch_summary"||_.type==="compaction")K="SUMMARY";if(K==="CUSTOM_MESSAGE")return;let V=_.id,N=[J.length>0&&_.id===J[0].id?"ROOT":null,B?"HEAD":null,D?`tag: ${D}`:null].filter(Boolean).join(", "),l=j.length>100?j.slice(0,100)+"...":j,o=B?"*":K==="USER"?"•":"|";S.push(`${o} ${V}${N?` (${N})`:""} [${K}] ${l}`)}),M>0)S.push(` : ... (${M} hidden messages) ...`);let q=await W.getContextUsage(),T="Unknown";if(q)T=`${q.percent.toFixed(1)}% (${k(q.tokens)}/${k(q.contextWindow)})`;let C=0,x="None";for(let _=J.length-1;_>=0;_--){let B=J[_].id,D=F.getLabel(B);if(D){x=D;break}C++}return{content:[{type:"text",text:["[Context Dashboard]",`• Context Usage: ${T}`,`• Segment Size: ${C} steps since last tag '${x}'`,"---------------------------------------------------"].join(`
1
+ import{Type as U}from"@sinclair/typebox";var f=(z)=>{if(z==null)return"N/A";if(z>=1e6)return(z/1e6).toFixed(1)+"M";if(z>=1000)return Math.round(z/1000)+"k";return z.toString()};var g=["context_tag","context_log","context_checkout"],v=null,R=null,P=(z)=>g.includes(z),u=(z,Y)=>{if(Y.toLowerCase()==="root"){let A=z.getTree();return A.length>0?A[0].entry.id:Y}if(/^[0-9a-f]{8,}$/i.test(Y))return Y;let O=[...z.getTree()];while(O.length>0){let A=O.pop();if(z.getLabel(A.entry.id)===Y)return A.entry.id;if(A.children?.length)O.push(...A.children)}return Y},d=U.Object({limit:U.Optional(U.Number({description:"History limit for visible entries (default: 50)."})),verbose:U.Optional(U.Boolean({description:"If true, show ALL messages. If false (default), collapses intermediate AI steps and only shows 'milestones': User messages, Tags, Branch Points, and Summaries."}))}),p=U.Object({target:U.String({description:"Where to jump/squash to. Can be a tag name (e.g., 'task-start'), a commit ID, or 'root'. This is the base for your new branch."}),message:U.String({description:"The 'Carryover Message' for the new branch. A summary of your *current* progress/lessons that you want to bring with you to the new state. This ensures you don't lose key information when switching contexts. Good summary message: '[Status] + [Reason] + [Important Changes] + [Carryover Data]'"}),backupTag:U.Optional(U.String({description:"Optional tag name to apply to the CURRENT state before checking out. Use this to create an automatic backup of the history you are about to leave/squash."}))}),y=U.Object({name:U.String({description:"The tag/milestone name. Use meaningful names."}),target:U.Optional(U.String({description:"The commit ID to tag. Defaults to HEAD (current state)."}))});function c(z){z.registerCommand("acm",{description:"Enable agentic context management for the current session",handler:async(O,A)=>{if(v=A,A.ui.notify("Agentic Context Management enabled.","info"),z.sendMessage({customType:"dm-context",content:"use context-management skill",display:!1},{deliverAs:"followUp"}),O)z.sendUserMessage(O)}});let Y=(O,A,H)=>{for(let w of A){if(O.getLabel(w.entry.id)===H)return w.entry.id;let W=Y(O,w.children,H);if(W)return W}return null};z.registerTool({name:"context_tag",label:"Context Tag",description:"Creates a 'Save Point' (Bookmark) in the history. Use this before trying risky changes or when a feature is stable. 'Untagged progress is risky'.",parameters:y,async execute(O,A,H,w,W){let F=W.sessionManager,J=Y(F,F.getTree(),A.name);if(J)return{content:[{type:"text",text:`Error: Tag '${A.name}' already exists at ${J}. Tag names must be unique. Use a different name or delete the existing tag first.`}],details:{}};let G=A.target?u(F,A.target):void 0;if(!G){let Z=F.getBranch();for(let $=Z.length-1;$>=0;$--){let Q=Z[$];if(Q.type==="message"&&Q.message.role==="toolResult"){let X=Q.message;if(P(X.toolName))continue;G=Q.id;break}if(Q.type==="message"&&Q.message.role==="assistant"){if(!Q.message.content.some((q)=>q.type==="toolCall"&&P(q.name))){G=Q.id;break}}G=Q.id;break}if(!G)G=F.getLeafId()??""}return z.setLabel(G,A.name),{content:[{type:"text",text:`Created tag '${A.name}' at ${G}`}],details:{}}}}),z.registerTool({name:"context_log",label:"Context Log",description:"Show the entire history structure (status, message, tags, milestones). Analogous to 'git log --graph --oneline --decorate'",parameters:d,async execute(O,A,H,w,W){let F=W.sessionManager,J=F.getBranch(),G=F.getLeafId(),Z=A.verbose??!1,$=A.limit??50,Q=new Set(J.map((_)=>_.id)),X=[];J.forEach((_)=>{X.push(_),F.getChildren(_.id).forEach((D)=>{if((D.type==="branch_summary"||D.type==="compaction")&&!Q.has(D.id))X.push(D)})});let k=(_)=>{if(_.type==="branch_summary"||_.type==="compaction")return _.summary||"[No summary provided]";if(_.type==="label")return`tag: ${_.label}`;if(_.type==="message"){let B=_.message;if(B.role==="toolResult"){let D=B;if(!Z&&P(D.toolName))return"";let K=((h)=>{return h.map((S)=>S.type==="text"?S.text:"").join(" ").trim()})(D.content),V=D.details;if((D.toolName==="read"||D.toolName==="edit")&&V&&"path"in V&&typeof V.path==="string")K=`${V.path}: ${K}`;return`(${D.toolName}) ${K}`}if(B.role==="bashExecution")return`[Bash] ${B.command}`;if(B.role==="user"||B.role==="assistant"){let D="";if(typeof B.content==="string")D=B.content;else if(Array.isArray(B.content))D=B.content.map((K)=>{if(typeof K==="object"&&K!==null&&"text"in K)return K.text;return""}).join(" ").trim();let j="";if(B.role==="assistant")j=B.content.filter((V)=>V.type==="toolCall").filter((V)=>Z||!P(V.name)).map((V)=>`call: ${V.name}(${JSON.stringify(V.arguments)})`).join("; ");return[D,j].filter(Boolean).join(" ")}}return""},q=(_)=>{if(_.id===G)return!0;if(J.length>0&&_.id===J[0].id)return!0;if(F.getLabel(_.id))return!0;if(_.type==="label")return!1;if(_.type==="branch_summary"||_.type==="compaction")return!0;if(F.getChildren(_.id).length>1)return!0;if(_.type==="message"&&_.message.role==="user")return!0;return!1},N=new Set;X.forEach((_)=>{if(Z||q(_))N.add(_.id)});let b=X.filter((_)=>N.has(_.id));if(b.length>$){let _=new Set(b.slice(-$).map((B)=>B.id));N.clear(),_.forEach((B)=>N.add(B))}let L=[],M=0;if(X.forEach((_)=>{if(!N.has(_.id)){M++;return}if(M>0)L.push(` : ... (${M} hidden messages) ...`),M=0;let B=_.id===G,D=F.getLabel(_.id),j=k(_).replace(/\s+/g," "),K=_.type.toUpperCase();if(_.type==="message"){let I=_.message;K=I.role==="assistant"?"AI":I.role==="user"?"USER":I.role==="bashExecution"?"BASH":"TOOL"}else if(_.type==="branch_summary"||_.type==="compaction")K="SUMMARY";if(K==="CUSTOM_MESSAGE")return;let V=_.id,S=[J.length>0&&_.id===J[0].id?"ROOT":null,B?"HEAD":null,D?`tag: ${D}`:null].filter(Boolean).join(", "),l=j.length>100?j.slice(0,100)+"...":j,o=B?"*":K==="USER"?"•":"|";L.push(`${o} ${V}${S?` (${S})`:""} [${K}] ${l}`)}),M>0)L.push(` : ... (${M} hidden messages) ...`);let E=await W.getContextUsage(),C="Unknown";if(E){let _=E.percent;C=_==null?`${f(E.tokens)}/${f(E.contextWindow)}`:`${_.toFixed(1)}% (${f(E.tokens)}/${f(E.contextWindow)})`}let x=0,T="None";for(let _=J.length-1;_>=0;_--){let B=J[_].id,D=F.getLabel(B);if(D){T=D;break}x++}return{content:[{type:"text",text:["[Context Dashboard]",`• Context Usage: ${C}`,`• Segment Size: ${x} steps since last tag '${T}'`,"---------------------------------------------------"].join(`
2
2
  `)+`
3
- `+(S.join(`
4
- `)||"(Root Path Only)")}],details:{}}}}),z.registerTool({name:"context_checkout",label:"Context Checkout",description:"Navigate to ANY point in the conversation history. This checkout only resets *conversation history*, NOT disk files. ALWAYS provide a detailed 'message' to bridge context.",parameters:p,async execute(O,A,E,w,W){if(!L)return W.ui.setEditorText(`/acm ${W.ui.getEditorText()||"continue"}`),{content:[{type:"text",text:"Agentic context management is not enabled. Ask the user to run `/acm` in DM to enable it, then retry."}],details:{}};let F=W.sessionManager,J=h(F,A.target),G=F.getLeafId();if(G===J)return{content:[{type:"text",text:`Already at target ${J}`}],details:{}};if(A.backupTag&&G)z.setLabel(G,A.backupTag);let Z=G?F.getLabel(G):void 0,Q=`(summary from ${Z?`tag: ${Z}`:G||"unknown"})
5
- ${A.message}`,X=await F.branchWithSummary(J,Q);return R=A,R.nid=X,R.tid=J,R.enrichedMessage=Q,{content:[{type:"text",text:"checkout start"}],details:{}}}}),z.on("turn_end",async(O,A)=>{if(!R)return;A.abort()}),z.on("agent_end",async(O,A)=>{if(!R)return;if(!L)return;await L.navigateTree(R.nid,{summarize:!1}),A.ui.notify(`Checked out ${R.target}${R.target===R.tid?"":`(${R.tid})`}
3
+ `+(L.join(`
4
+ `)||"(Root Path Only)")}],details:{}}}}),z.registerTool({name:"context_checkout",label:"Context Checkout",description:"Navigate to ANY point in the conversation history. This checkout only resets *conversation history*, NOT disk files. ALWAYS provide a detailed 'message' to bridge context.",parameters:p,async execute(O,A,H,w,W){if(!v)return W.ui.setEditorText(`/acm ${W.ui.getEditorText()||"continue"}`),{content:[{type:"text",text:"Agentic context management is not enabled. Ask the user to run `/acm` in DM to enable it, then retry."}],details:{}};let F=W.sessionManager,J=u(F,A.target),G=F.getLeafId();if(G===J)return{content:[{type:"text",text:`Already at target ${J}`}],details:{}};if(A.backupTag&&G)z.setLabel(G,A.backupTag);let Z=G?F.getLabel(G):void 0,Q=`(summary from ${Z?`tag: ${Z}`:G||"unknown"})
5
+ ${A.message}`,X=await F.branchWithSummary(J,Q);return R=A,R.nid=X,R.tid=J,R.enrichedMessage=Q,{content:[{type:"text",text:"checkout start"}],details:{}}}}),z.on("turn_end",async(O,A)=>{if(!R)return;A.abort()}),z.on("agent_end",async(O,A)=>{if(!R)return;if(!v)return;await v.navigateTree(R.nid,{summarize:!1}),A.ui.notify(`Checked out ${R.target}${R.target===R.tid?"":`(${R.tid})`}
6
6
  Backup tag created: ${R.backupTag||"none"}
7
7
  message: ${R.enrichedMessage}`,"info"),R=null,z.sendMessage({customType:"dm-context",content:"context_checkout complete. A summary of your previous branch was injected above. Read it to understand your new state. Execute the 'Next Step' from the summary",display:!1},{triggerTurn:!0})})}export{c as default};
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import{createHash as R}from"node:crypto";import{spawn as x}from"node:child_process";import{existsSync as W,mkdirSync as h,readFileSync as S}from"node:fs";import P from"node:http";import{homedir as g,platform as d,tmpdir as T}from"node:os";import{dirname as B,join as k,resolve as L}from"node:path";import{fileURLToPath as v}from"node:url";var y=B(v(import.meta.url)),K2=L(y,".."),u=9322,c=400,N=60000,p=/^(about:|https?:\/\/|file:|chrome-error:\/\/)/i,l={chrome:"Chrome",cloak:"CloakBrowser"};function I(J){let V=Number.parseInt(String(J??""),10);return Number.isInteger(V)&&V>1024&&V<65535?V:null}function n(J){return R("sha256").update(String(J||"default")).digest("hex")}function i(J){let V=String(J||"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 o(J,V){let $=J.DM_CUA_BROWSER_PATH||(V==="cloak"?J.DM_CUA_CLOAK_PATH||J.CLOAKBROWSER_BINARY_PATH:void 0)||J.CHROME_PATH;return $?L(String($)):void 0}function a(J=process.env,V=process.cwd()){let $=J.DM_CUA_SCOPE||J.DM_CODING_AGENT_DIR||J.PI_CODING_AGENT_DIR||V,Q=n(L(String($||"."))),Z=String(J.DM_CUA_INSTANCE_ID||Q.slice(0,12)).replace(/[^a-zA-Z0-9_.-]/g,"-"),X=i(J.DM_CUA_BROWSER||J.DM_BROWSER_CUA_BROWSER||J.DM_CUA_BROWSER_KIND),z=J.DM_CUA_BROWSER_DISPLAY_NAME||l[X]||X,A=o(J,X),H=I(J.DM_CUA_PORT)??I(J.GREEDY_SEARCH_PORT)??u+Number.parseInt(Q.slice(0,8),16)%c,j=X==="chrome"?"chrome":X,Y=L(J.DM_CUA_PROFILE_DIR||k(T(),`dm-cua-${j}-profile-${Z}`)),q=d()==="win32"?T():"/tmp";return{instanceId:Z,browserKind:X,browserDisplayName:z,browserExecutablePath:A,port:H,profileDir:Y,pidFile:k(Y,"chrome.pid"),modeFile:k(Y,"chrome-mode"),pagesCache:k(Y,"cdp-pages.json"),socketDir:k(q,`dm-cua-cdp-${Z}`)}}var M=a(),s=M.port,L2=M.profileDir;function D(){return{kind:M.browserKind,displayName:M.browserDisplayName,executablePath:M.browserExecutablePath,profileDir:M.profileDir,port:M.port}}function t(){if(M.browserKind==="cloak"&&!M.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(M.browserExecutablePath&&!W(M.browserExecutablePath))throw Error(`${M.browserDisplayName} executable not found: ${M.browserExecutablePath}`)}function C(){let V=[L(y,"..","greedysearch-dm"),L(y,"..","..","greedysearch-dm")].map(($)=>({launchScript:k($,"bin","launch.mjs"),cdpScript:k($,"bin","cdp.mjs")}));for(let $ of V)if(W($.launchScript)&&W($.cdpScript))return $;return V[0]}function r(J=M,V=process.env){let $=V.GREEDY_SEARCH_VISIBLE??V.DM_CUA_VISIBLE??V.DM_BROWSER_CUA_VISIBLE;return{...V,...J.browserExecutablePath?{CHROME_PATH:J.browserExecutablePath}:{},CDP_PROFILE_DIR:J.profileDir.replace(/\\/g,"/"),CDP_PAGES_CACHE:J.pagesCache.replace(/\\/g,"/"),CDP_SOCKET_DIR:J.socketDir.replace(/\\/g,"/"),GREEDY_SEARCH_BROWSER_LABEL:J.browserDisplayName,GREEDY_SEARCH_PORT:String(J.port),GREEDY_SEARCH_PROFILE_DIR:J.profileDir.replace(/\\/g,"/"),GREEDY_SEARCH_PID_FILE:J.pidFile.replace(/\\/g,"/"),GREEDY_SEARCH_MODE_FILE:J.modeFile.replace(/\\/g,"/"),GREEDY_SEARCH_ALLOW_PORT_CLEANUP:V.GREEDY_SEARCH_ALLOW_PORT_CLEANUP??"0",...$===void 0?{}:{GREEDY_SEARCH_VISIBLE:$}}}function w(){return r(M,process.env)}function e(J){return p.test(J)}function J2(J){return J.split(/\r?\n/).map((V)=>V.trimEnd()).filter(Boolean).map((V)=>{let $=V.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&&e($[1]))return{target:Q,title:"",url:$[1]};let Z=$.at(-1)??"",X=$.slice(1,-1).join(" ");return{target:Q,title:X,url:Z}}).filter(Boolean)}function V2(){let J=process.env.DM_CODING_AGENT_DIR||process.env.PI_CODING_AGENT_DIR;return J?L(J):k(g(),".dm","agent")}function $2(){let J=k(V2(),"cua","screenshots");h(J,{recursive:!0});let V=new Date().toISOString().replace(/[:.]/g,"-");return k(J,`browser-${V}.png`)}function F(J,V,{env:$=process.env,timeoutMs:Q=N}={}){return new Promise((Z,X)=>{let z=x("node",[J,...V],{env:$,stdio:["ignore","pipe","pipe"]}),A="",H="",j=!1,Y=setTimeout(()=>{if(j)return;j=!0,z.kill("SIGTERM"),X(Error(`Timed out running ${J}`))},Q);z.stdout.on("data",(q)=>{A+=q.toString()}),z.stderr.on("data",(q)=>{H+=q.toString()}),z.on("error",(q)=>{if(j)return;j=!0,clearTimeout(Y),X(q)}),z.on("close",(q)=>{if(j)return;if(j=!0,clearTimeout(Y),q!==0){X(Error((H||A||`exit ${q}`).trim()));return}Z({stdout:A,stderr:H})})})}function j2(J,V){return new Promise(($,Q)=>{let Z=P.request({host:"127.0.0.1",port:s,path:V,method:J},(X)=>{let z="";X.on("data",(A)=>{z+=A.toString()}),X.on("end",()=>{if((X.statusCode??500)>=400){Q(Error(`Browser CDP HTTP ${X.statusCode}: ${z.trim()}`));return}try{$(z?JSON.parse(z):{})}catch(A){Q(A)}})});Z.on("error",Q),Z.end()})}async function Q2(J="about:blank"){return j2("PUT",`/json/new?${encodeURIComponent(J)}`)}async function K(J,V={}){let{cdpScript:$}=C();return F($,J,{env:w(),timeoutMs:V.timeoutMs??N})}async function G(){let J=await K(["list"]);return J2(J.stdout)}function b(J,V){if(!V)return J[0]??null;let $=V.toUpperCase();return J.find((Q)=>Q.target.toUpperCase().startsWith($))??null}async function _(){let{launchScript:J,cdpScript:V}=C();if(!W(J)||!W(V))throw Error("Bundled browser helpers are missing. Rebuild DM so dm-cua and greedysearch-dm are staged together.");return{launchScript:J,cdpScript:V}}async function E(){t();let{launchScript:J}=await _();await F(J,[],{env:w(),timeoutMs:N});let V=await G();if(V.length===0)await Q2("about:blank"),V=await G();return V}async function U(J){let V=await E(),$=b(V,J);if(!$){if(J)throw Error(`No browser tab matching prefix "${J}". Run browser_cua(list) first.`);throw Error("No browser tab available after browser bootstrap.")}return{tab:$.target,page:$,pages:V}}async function O({action:J,tab:V,url:$,selector:Q,x:Z,y:X,text:z,expression:A,outputPath:H}={}){switch(J){case"list":{let j=await E();return{status:"ok",action:J,browser:D(),pages:j}}case"navigate":{if(!$)throw Error("url is required for action=navigate");let j=await U(V),Y=await K(["nav",j.tab,$],{timeoutMs:90000}),q=await G(),f=b(q,j.tab)??j.page;return{status:"ok",action:J,browser:D(),tab:j.tab,url:f?.url||$,title:f?.title||"",message:Y.stdout.trim(),pages:q}}case"snapshot":{let j=await U(V),Y=await K(["snap",j.tab]);return{status:"ok",action:J,browser:D(),tab:j.tab,title:j.page?.title||"",url:j.page?.url||"",snapshot:Y.stdout.trim()}}case"screenshot":{let j=await U(V),Y=H?L(H):$2();h(B(Y),{recursive:!0});let q=await K(["shot",j.tab,Y],{timeoutMs:120000}),f=S(Y).toString("base64");return{status:"ok",action:J,browser:D(),tab:j.tab,title:j.page?.title||"",url:j.page?.url||"",imagePath:Y,imageBase64:f,mimeType:"image/png",message:q.stdout.trim()}}case"click":{if(!Q)throw Error("selector is required for action=click");let j=await U(V),Y=await K(["click",j.tab,Q]);return{status:"ok",action:J,browser:D(),tab:j.tab,selector:Q,message:Y.stdout.trim()}}case"click_xy":{if(typeof Z!=="number"||Number.isNaN(Z)||typeof X!=="number"||Number.isNaN(X))throw Error("x and y are required numbers for action=click_xy");let j=await U(V),Y=await K(["clickxy",j.tab,String(Z),String(X)]);return{status:"ok",action:J,browser:D(),tab:j.tab,x:Z,y:X,message:Y.stdout.trim()}}case"type":{if(z==null||z==="")throw Error("text is required for action=type");let j=await U(V),Y=await K(["type",j.tab,z]);return{status:"ok",action:J,browser:D(),tab:j.tab,textLength:z.length,message:Y.stdout.trim()}}case"evaluate":{if(!A)throw Error("expression is required for action=evaluate");let j=await U(V),Y=await K(["eval",j.tab,A]);return{status:"ok",action:J,browser:D(),tab:j.tab,value:Y.stdout.trim()}}case"stop":{let{launchScript:j}=await _();try{await K(["stop"])}catch{}let Y=await F(j,["--kill"],{env:w(),timeoutMs:30000});return{status:"ok",action:J,browser:D(),message:(Y.stdout||"Stopped browser CUA lane.").trim()}}default:throw Error(`Unsupported browser action: ${J}`)}}function m(J){switch(J.action){case"list":{if(!Array.isArray(J.pages)||J.pages.length===0)return"Browser CUA is ready, but no pages are open.";return["Open browser tabs:",...J.pages.map((V)=>V.url?`${V.target} ${V.title||"(untitled)"} ${V.url}`:`${V.target} ${V.title||"(untitled)"}`)].join(`
3
- `)}case"navigate":return[`Navigated browser tab ${J.tab} to ${J.url||"(unknown URL)"}`,J.title?`Title: ${J.title}`:""].filter(Boolean).join(`
4
- `);case"snapshot":return[`Browser snapshot for ${J.tab}`,J.snapshot||"(empty snapshot)"].join(`
5
- `);case"screenshot":return[`Saved browser screenshot for ${J.tab} at ${J.imagePath}`,J.title?`Title: ${J.title}`:"",J.url?`URL: ${J.url}`:""].filter(Boolean).join(`
6
- `);case"click":return`Clicked selector ${JSON.stringify(J.selector)} on browser tab ${J.tab}`;case"click_xy":return`Clicked browser tab ${J.tab} at CSS coordinates (${J.x}, ${J.y})`;case"type":return`Typed ${J.textLength} characters into browser tab ${J.tab}`;case"evaluate":return`Browser evaluate result for ${J.tab}: ${J.value}`;case"stop":return J.message||"Stopped browser CUA lane.";default:return JSON.stringify(J,null,2)}}function X2(){console.log(`dm-cua browser helper
2
+ import{createHash as to}from"node:crypto";import{spawn as ho}from"node:child_process";import{existsSync as _,mkdirSync as E,readFileSync as uo}from"node:fs";import co from"node:http";import{homedir as lo,platform as fo,tmpdir as Z}from"node:os";import{dirname as O,join as r,resolve as C}from"node:path";import{fileURLToPath as Ao}from"node:url";import{spawn as S}from"node:child_process";import{existsSync as x}from"node:fs";import{homedir as I,platform as D}from"node:os";import b from"node:path";var R=900000,z=16384,V=5,P=1000;function j(...o){return b.win32.join(...o)}function L(o,n){if(n&&!o.includes(n))o.push(n)}function U(o,n,m){let t=o.PATH??o.Path??o.path??"",d=n==="win32"?";":b.delimiter,a=n==="win32"?(o.PATHEXT??".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean):[""],h=[];for(let u of t.split(d).filter(Boolean))for(let c of m){if(n==="win32"&&b.win32.extname(c)){L(h,j(u,c));continue}for(let s of a){let i=n==="win32"?`${c}${s.toLowerCase()}`:c;L(h,n==="win32"?j(u,i):b.join(u,i))}}return h}function v({env:o=process.env,platformName:n=D(),homeDir:m=I(),exists:t=x}={}){let d=[];if(n==="win32"){for(let a of[o.LOCALAPPDATA,o.ProgramFiles,o["ProgramFiles(x86)"]]){if(!a)continue;L(d,j(a,"Google","Chrome","Application","chrome.exe")),L(d,j(a,"Chromium","Application","chrome.exe"))}L(d,m?j(m,"AppData","Local","Google","Chrome","Application","chrome.exe"):void 0),d.push(...U(o,n,["chrome","chromium"]))}else if(n==="darwin"){for(let a of["/Applications",b.join(m,"Applications")])L(d,b.join(a,"Google Chrome.app","Contents","MacOS","Google Chrome")),L(d,b.join(a,"Chromium.app","Contents","MacOS","Chromium"));d.push(...U(o,n,["google-chrome-stable","google-chrome","chromium","chromium-browser"]))}else if(n==="linux"){for(let a of["/usr/bin/google-chrome-stable","/usr/bin/google-chrome","/usr/bin/chromium","/usr/bin/chromium-browser","/snap/bin/chromium","/usr/local/bin/google-chrome","/usr/local/bin/chromium"])L(d,a);d.push(...U(o,n,["google-chrome-stable","google-chrome","chromium","chromium-browser"]))}return d.find((a)=>t(a))}function f(o,...n){return{command:o,args:n}}function M(o,n){if(n===0)return o;return o.map(({command:m,args:t})=>f("sudo","-n",m,...t))}function oo({platformName:o=D(),uid:n=typeof process.getuid==="function"?process.getuid():void 0}={}){if(o==="darwin")return[{id:"homebrew-google-chrome",commands:[f("brew","install","--cask","google-chrome")]}];if(o==="win32"){let m=["install","--id","Google.Chrome","-e","--silent","--accept-package-agreements","--accept-source-agreements","--disable-interactivity"];return[{id:"winget-user-google-chrome",commands:[f("winget",...m.slice(0,4),"--scope","user",...m.slice(4))]},{id:"winget-machine-google-chrome",commands:[f("winget",...m)]},{id:"chocolatey-google-chrome",commands:[f("choco","install","googlechrome","-y","--no-progress")]}]}if(o==="linux")return[{id:"apt-chromium",commands:M([f("apt-get","update"),f("apt-get","install","-y","chromium")],n)},{id:"apt-chromium-browser",commands:M([f("apt-get","update"),f("apt-get","install","-y","chromium-browser")],n)},{id:"dnf-chromium",commands:M([f("dnf","install","-y","chromium")],n)},{id:"yum-chromium",commands:M([f("yum","install","-y","chromium")],n)},{id:"pacman-chromium",commands:M([f("pacman","-S","--noconfirm","--needed","chromium")],n)},{id:"zypper-chromium",commands:M([f("zypper","--non-interactive","install","chromium")],n)},{id:"apk-chromium",commands:M([f("apk","add","chromium")],n)}];return[]}function no(o=process.env){let n=o.DM_CUA_AUTO_INSTALL;if(n===void 0)return!0;return!["0","false","no","off"].includes(n.trim().toLowerCase())}function mo(o){let n=Number(o.DM_CUA_INSTALL_TIMEOUT_MS);return Number.isFinite(n)&&n>0?n:R}function H(o,n){let m=`${o}${n}`;if(Buffer.byteLength(m)<=z)return m;return m.slice(-z)}function io(o,n,{env:m=process.env,timeoutMs:t=mo(m)}={}){return new Promise((d)=>{let a=!1,h="",u,c=S(o,n,{env:m,shell:!1,stdio:["ignore","pipe","pipe"],windowsHide:!0}),s=(i)=>{if(a)return;a=!0,clearTimeout(u),d(i)};c.stdout?.on("data",(i)=>{h=H(h,i.toString())}),c.stderr?.on("data",(i)=>{h=H(h,i.toString())}),c.on("error",(i)=>{s({status:i.code==="ENOENT"?"unavailable":"failed",exitCode:null,message:`${i.message}${h?`
3
+ ${h.trim()}`:""}`})}),c.on("close",(i,l)=>{s({status:i===0?"ok":"failed",exitCode:i,message:h.trim()||(l?`installer terminated by signal ${l}`:"")})}),u=setTimeout(()=>{c.kill(),s({status:"timeout",exitCode:null,message:`installer timed out after ${t}ms`})},t)})}function so(o){if(o.startsWith("homebrew-"))return"Homebrew";if(o.startsWith("winget-"))return"winget";return o.split("-")[0]}function ao(o){let n=o.message?`: ${o.message}`:"";return`${o.id} ${o.status}${n}`}async function Q({env:o=process.env,platformName:n=D(),uid:m=typeof process.getuid==="function"?process.getuid():void 0,findExecutable:t=()=>v({env:o,platformName:n,homeDir:I()}),runCommand:d=(h,u)=>io(h,u,{env:o}),sleep:a=(h)=>new Promise((u)=>{setTimeout(u,h)})}={}){let h=t();if(h)return{status:"existing",path:h,method:null,attempts:[]};if(!no(o))throw Error("Chrome was not found and automatic installation is disabled by DM_CUA_AUTO_INSTALL.");let u=oo({platformName:n,uid:m});if(u.length===0)throw Error(`Chrome was not found and no automatic Chrome installer is defined for ${n}.`);let c=[];for(let i of u){let l={status:"ok",exitCode:0,message:""};for(let $ of i.commands)if(l=await d($.command,$.args),l.status!=="ok")break;if(l.status!=="ok"){c.push({id:i.id,status:l.status,exitCode:l.exitCode,message:l.message});continue}let w;for(let $=0;$<V;$+=1){if(w=t(),w)break;if($<V-1)await a(P)}if(w)return c.push({id:i.id,status:"installed",exitCode:l.exitCode,message:l.message}),{status:"installed",path:w,method:i.id,attempts:c};c.push({id:i.id,status:"missing-after-success",exitCode:l.exitCode,message:`${so(i.id)} reported success, but Chrome was not found`})}let s=c.map(ao).join("; ");throw Error(`Chrome automatic installation failed. ${s}`)}var g=O(Ao(import.meta.url)),Fo=C(g,".."),ro=9322,wo=400,X=60000,To=/^(about:|https?:\/\/|file:|chrome-error:\/\/)/i,eo={chrome:"Chrome",cloak:"CloakBrowser"};function q(o){let n=Number.parseInt(String(o??""),10);return Number.isInteger(n)&&n>1024&&n<65535?n:null}function Lo(o){return to("sha256").update(String(o||"default")).digest("hex")}function Co(o){let n=String(o||"chrome").trim().toLowerCase();if(n==="cloakbrowser"||n==="cloak-browser")return"cloak";if(n==="chromium"||n==="google-chrome"||n==="chrome")return"chrome";return n.replace(/[^a-z0-9_.-]/g,"-")||"chrome"}function Mo(o,n){let m=o.DM_CUA_BROWSER_PATH||(n==="cloak"?o.DM_CUA_CLOAK_PATH||o.CLOAKBROWSER_BINARY_PATH:void 0)||o.CHROME_PATH;return m?C(String(m)):void 0}function bo(o=process.env,n=process.cwd()){let m=o.DM_CUA_SCOPE||o.DM_CODING_AGENT_DIR||o.PI_CODING_AGENT_DIR||n,t=Lo(C(String(m||"."))),d=String(o.DM_CUA_INSTANCE_ID||t.slice(0,12)).replace(/[^a-zA-Z0-9_.-]/g,"-"),a=Co(o.DM_CUA_BROWSER||o.DM_BROWSER_CUA_BROWSER||o.DM_CUA_BROWSER_KIND),h=o.DM_CUA_BROWSER_DISPLAY_NAME||eo[a]||a,u=Mo(o,a),c=q(o.DM_CUA_PORT)??q(o.GREEDY_SEARCH_PORT)??ro+Number.parseInt(t.slice(0,8),16)%wo,s=a==="chrome"?"chrome":a,i=C(o.DM_CUA_PROFILE_DIR||r(Z(),`dm-cua-${s}-profile-${d}`)),l=fo()==="win32"?Z():"/tmp";return{instanceId:d,browserKind:a,browserDisplayName:h,browserExecutablePath:u,browserExecutableIsExplicit:Boolean(u),browserInstallation:void 0,port:c,profileDir:i,pidFile:r(i,"chrome.pid"),modeFile:r(i,"chrome-mode"),pagesCache:r(i,"cdp-pages.json"),socketDir:r(l,`dm-cua-cdp-${d}`)}}var A=bo(),yo=A.port,So=A.profileDir;function T(){return{kind:A.browserKind,displayName:A.browserDisplayName,executablePath:A.browserExecutablePath,installation:A.browserInstallation??null,profileDir:A.profileDir,port:A.port}}async function $o(o=A,{env:n=process.env,exists:m=_,ensureChrome:t=Q}={}){if(o.browserKind==="cloak"&&!o.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(o.browserExecutablePath&&!m(o.browserExecutablePath))throw Error(`${o.browserDisplayName} executable not found: ${o.browserExecutablePath}`);if(o.browserExecutablePath)return o;if(o.browserKind!=="chrome")throw Error(`${o.browserDisplayName} browser CUA requires an explicit executable path via DM_CUA_BROWSER_PATH.`);let d=await t({env:n});return o.browserExecutablePath=d.path,o.browserInstallation={status:d.status,method:d.method,attempts:d.attempts.map(({id:a,status:h,exitCode:u})=>({id:a,status:h,exitCode:u}))},o}var k;async function jo(){if(!k)k=$o(A).catch((o)=>{throw k=void 0,o});return k}function W(){let n=[C(g,"..","greedysearch-dm"),C(g,"..","..","greedysearch-dm")].map((m)=>({launchScript:r(m,"bin","launch.mjs"),cdpScript:r(m,"bin","cdp.mjs")}));for(let m of n)if(_(m.launchScript)&&_(m.cdpScript))return m;return n[0]}function _o(o=A,n=process.env){let m=n.GREEDY_SEARCH_VISIBLE??n.DM_CUA_VISIBLE??n.DM_BROWSER_CUA_VISIBLE;return{...n,...o.browserExecutablePath?{CHROME_PATH:o.browserExecutablePath}:{},CDP_PROFILE_DIR:o.profileDir.replace(/\\/g,"/"),CDP_PAGES_CACHE:o.pagesCache.replace(/\\/g,"/"),CDP_SOCKET_DIR:o.socketDir.replace(/\\/g,"/"),GREEDY_SEARCH_BROWSER_LABEL:o.browserDisplayName,GREEDY_SEARCH_PORT:String(o.port),GREEDY_SEARCH_PROFILE_DIR:o.profileDir.replace(/\\/g,"/"),GREEDY_SEARCH_PID_FILE:o.pidFile.replace(/\\/g,"/"),GREEDY_SEARCH_MODE_FILE:o.modeFile.replace(/\\/g,"/"),GREEDY_SEARCH_ALLOW_PORT_CLEANUP:n.GREEDY_SEARCH_ALLOW_PORT_CLEANUP??"0",...m===void 0?{}:{GREEDY_SEARCH_VISIBLE:m}}}function Y(){return _o(A,process.env)}function ko(o){return To.test(o)}function Uo(o){return o.split(/\r?\n/).map((n)=>n.trimEnd()).filter(Boolean).map((n)=>{let m=n.trim().split(/\s{2,}/).filter(Boolean);if(m.length===0)return null;let t=m[0];if(m.length===1)return{target:t,title:"",url:""};if(m.length===2&&ko(m[1]))return{target:t,title:"",url:m[1]};let d=m.at(-1)??"",a=m.slice(1,-1).join(" ");return{target:t,title:a,url:d}}).filter(Boolean)}function Do(){let o=process.env.DM_CODING_AGENT_DIR||process.env.PI_CODING_AGENT_DIR;return o?C(o):r(lo(),".dm","agent")}function go(){let o=r(Do(),"cua","screenshots");E(o,{recursive:!0});let n=new Date().toISOString().replace(/[:.]/g,"-");return r(o,`browser-${n}.png`)}function p(o,n,{env:m=process.env,timeoutMs:t=X}={}){return new Promise((d,a)=>{let h=ho("node",[o,...n],{env:m,stdio:["ignore","pipe","pipe"]}),u="",c="",s=!1,i=setTimeout(()=>{if(s)return;s=!0,h.kill("SIGTERM"),a(Error(`Timed out running ${o}`))},t);h.stdout.on("data",(l)=>{u+=l.toString()}),h.stderr.on("data",(l)=>{c+=l.toString()}),h.on("error",(l)=>{if(s)return;s=!0,clearTimeout(i),a(l)}),h.on("close",(l)=>{if(s)return;if(s=!0,clearTimeout(i),l!==0){a(Error((c||u||`exit ${l}`).trim()));return}d({stdout:u,stderr:c})})})}function Jo(o,n){return new Promise((m,t)=>{let d=co.request({host:"127.0.0.1",port:yo,path:n,method:o},(a)=>{let h="";a.on("data",(u)=>{h+=u.toString()}),a.on("end",()=>{if((a.statusCode??500)>=400){t(Error(`Browser CDP HTTP ${a.statusCode}: ${h.trim()}`));return}try{m(h?JSON.parse(h):{})}catch(u){t(u)}})});d.on("error",t),d.end()})}async function Xo(o="about:blank"){return Jo("PUT",`/json/new?${encodeURIComponent(o)}`)}async function e(o,n={}){let{cdpScript:m}=W();return p(m,o,{env:Y(),timeoutMs:n.timeoutMs??X})}async function J(){let o=await e(["list"]);return Uo(o.stdout)}function G(o,n){if(!n)return o[0]??null;let m=n.toUpperCase();return o.find((t)=>t.target.toUpperCase().startsWith(m))??null}async function K(){let{launchScript:o,cdpScript:n}=W();if(!_(o)||!_(n))throw Error("Bundled browser helpers are missing. Rebuild DM so dm-cua and greedysearch-dm are staged together.");return{launchScript:o,cdpScript:n}}async function N(){let{launchScript:o}=await K();await jo(),await p(o,[],{env:Y(),timeoutMs:X});let n=await J();if(n.length===0)await Xo("about:blank"),n=await J();return n}async function y(o){let n=await N(),m=G(n,o);if(!m){if(o)throw Error(`No browser tab matching prefix "${o}". Run browser_cua(list) first.`);throw Error("No browser tab available after browser bootstrap.")}return{tab:m.target,page:m,pages:n}}async function B({action:o,tab:n,url:m,selector:t,x:d,y:a,text:h,expression:u,outputPath:c}={}){switch(o){case"list":{let s=await N();return{status:"ok",action:o,browser:T(),pages:s}}case"navigate":{if(!m)throw Error("url is required for action=navigate");let s=await y(n),i=await e(["nav",s.tab,m],{timeoutMs:90000}),l=await J(),w=G(l,s.tab)??s.page;return{status:"ok",action:o,browser:T(),tab:s.tab,url:w?.url||m,title:w?.title||"",message:i.stdout.trim(),pages:l}}case"snapshot":{let s=await y(n),i=await e(["snap",s.tab]);return{status:"ok",action:o,browser:T(),tab:s.tab,title:s.page?.title||"",url:s.page?.url||"",snapshot:i.stdout.trim()}}case"screenshot":{let s=await y(n),i=c?C(c):go();E(O(i),{recursive:!0});let l=await e(["shot",s.tab,i],{timeoutMs:120000}),w=uo(i).toString("base64");return{status:"ok",action:o,browser:T(),tab:s.tab,title:s.page?.title||"",url:s.page?.url||"",imagePath:i,imageBase64:w,mimeType:"image/png",message:l.stdout.trim()}}case"click":{if(!t)throw Error("selector is required for action=click");let s=await y(n),i=await e(["click",s.tab,t]);return{status:"ok",action:o,browser:T(),tab:s.tab,selector:t,message:i.stdout.trim()}}case"click_xy":{if(typeof d!=="number"||Number.isNaN(d)||typeof a!=="number"||Number.isNaN(a))throw Error("x and y are required numbers for action=click_xy");let s=await y(n),i=await e(["clickxy",s.tab,String(d),String(a)]);return{status:"ok",action:o,browser:T(),tab:s.tab,x:d,y:a,message:i.stdout.trim()}}case"type":{if(h==null||h==="")throw Error("text is required for action=type");let s=await y(n),i=await e(["type",s.tab,h]);return{status:"ok",action:o,browser:T(),tab:s.tab,textLength:h.length,message:i.stdout.trim()}}case"evaluate":{if(!u)throw Error("expression is required for action=evaluate");let s=await y(n),i=await e(["eval",s.tab,u]);return{status:"ok",action:o,browser:T(),tab:s.tab,value:i.stdout.trim()}}case"stop":{let{launchScript:s}=await K();try{await e(["stop"])}catch{}let i=await p(s,["--kill"],{env:Y(),timeoutMs:30000});return{status:"ok",action:o,browser:T(),message:(i.stdout||"Stopped browser CUA lane.").trim()}}default:throw Error(`Unsupported browser action: ${o}`)}}function F(o){switch(o.action){case"list":{if(!Array.isArray(o.pages)||o.pages.length===0)return"Browser CUA is ready, but no pages are open.";return["Open browser tabs:",...o.pages.map((n)=>n.url?`${n.target} ${n.title||"(untitled)"} ${n.url}`:`${n.target} ${n.title||"(untitled)"}`)].join(`
4
+ `)}case"navigate":return[`Navigated browser tab ${o.tab} to ${o.url||"(unknown URL)"}`,o.title?`Title: ${o.title}`:""].filter(Boolean).join(`
5
+ `);case"snapshot":return[`Browser snapshot for ${o.tab}`,o.snapshot||"(empty snapshot)"].join(`
6
+ `);case"screenshot":return[`Saved browser screenshot for ${o.tab} at ${o.imagePath}`,o.title?`Title: ${o.title}`:"",o.url?`URL: ${o.url}`:""].filter(Boolean).join(`
7
+ `);case"click":return`Clicked selector ${JSON.stringify(o.selector)} on browser tab ${o.tab}`;case"click_xy":return`Clicked browser tab ${o.tab} at CSS coordinates (${o.x}, ${o.y})`;case"type":return`Typed ${o.textLength} characters into browser tab ${o.tab}`;case"evaluate":return`Browser evaluate result for ${o.tab}: ${o.value}`;case"stop":return o.message||"Stopped browser CUA lane.";default:return JSON.stringify(o,null,2)}}function Yo(){console.log(`dm-cua browser helper
7
8
 
8
9
  Usage:
9
10
  node browser-cua.mjs list [--json]
@@ -19,6 +20,8 @@ Usage:
19
20
  Notes:
20
21
  - This helper reuses DM's bundled greedysearch-dm CDP sidecar.
21
22
  - Fresh dedicated browser profiles auto-bootstrap a blank tab.
22
- - Default browser is Chrome/Chromium. To try CloakBrowser, set DM_CUA_BROWSER=cloak
23
+ - Default browser is Chrome/Chromium. If absent, DM attempts a non-interactive
24
+ package-manager install; set DM_CUA_AUTO_INSTALL=0 to opt out.
25
+ - To try CloakBrowser, set DM_CUA_BROWSER=cloak
23
26
  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 Y2(J){let V={},$=[];for(let Q=0;Q<J.length;Q+=1){let Z=J[Q];if(!Z.startsWith("--")){$.push(Z);continue}let X=Z.indexOf("="),z=X>2,A=z?Z.slice(2,X):Z.slice(2);if(A==="json"||A==="help"){V[A]=!0;continue}if(z){V[A]=Z.slice(X+1);continue}let H=J[Q+1];if(typeof H>"u"||H.startsWith("--"))throw Error(`Missing value for --${A}`);V[A]=H,Q+=1}return{options:V,positionals:$}}async function Z2(){let{options:J,positionals:V}=Y2(process.argv.slice(2));if(J.help||V.length===0){X2();return}let $=V[0],Q=await O({action:$,tab:J.tab,url:J.url,selector:J.selector,x:J.x==null?void 0:Number(J.x),y:J.y==null?void 0:Number(J.y),text:J.text,expression:J.expression,outputPath:J.output});if(J.json){console.log(JSON.stringify(Q,null,2));return}console.log(m(Q))}try{await Z2()}catch(J){let V=J instanceof Error?J.message:String(J),$=!1;try{$=process.argv.slice(2).includes("--json")}catch{}if($)console.log(JSON.stringify({status:"error",message:V},null,2));else console.error(V);process.exit(1)}
27
+ - screenshot defaults to ~/.dm/agent/cua/screenshots when --output is omitted.`)}function po(o){let n={},m=[];for(let t=0;t<o.length;t+=1){let d=o[t];if(!d.startsWith("--")){m.push(d);continue}let a=d.indexOf("="),h=a>2,u=h?d.slice(2,a):d.slice(2);if(u==="json"||u==="help"){n[u]=!0;continue}if(h){n[u]=d.slice(a+1);continue}let c=o[t+1];if(typeof c>"u"||c.startsWith("--"))throw Error(`Missing value for --${u}`);n[u]=c,t+=1}return{options:n,positionals:m}}async function zo(){let{options:o,positionals:n}=po(process.argv.slice(2));if(o.help||n.length===0){Yo();return}let m=n[0],t=await B({action:m,tab:o.tab,url:o.url,selector:o.selector,x:o.x==null?void 0:Number(o.x),y:o.y==null?void 0:Number(o.y),text:o.text,expression:o.expression,outputPath:o.output});if(o.json){console.log(JSON.stringify(t,null,2));return}console.log(F(t))}try{await zo()}catch(o){let n=o instanceof Error?o.message:String(o),m=!1;try{m=process.argv.slice(2).includes("--json")}catch{}if(m)console.log(JSON.stringify({status:"error",message:n},null,2));else console.error(n);process.exit(1)}
@@ -1,5 +1,6 @@
1
- import{existsSync as R}from"node:fs";import{Type as Z}from"@sinclair/typebox";import{createHash as d}from"node:crypto";import{spawn as g}from"node:child_process";import{existsSync as G,mkdirSync as C,readFileSync as x}from"node:fs";import P from"node:http";import{homedir as i,platform as c,tmpdir as B}from"node:os";import{dirname as m,join as U,resolve as f}from"node:path";import{fileURLToPath as p}from"node:url";var F=m(p(import.meta.url)),K4=f(F,".."),v=9322,a=400,N=60000,l=/^(about:|https?:\/\/|file:|chrome-error:\/\/)/i,u={chrome:"Chrome",cloak:"CloakBrowser"};function h(L){let $=Number.parseInt(String(L??""),10);return Number.isInteger($)&&$>1024&&$<65535?$:null}function o(L){return d("sha256").update(String(L||"default")).digest("hex")}function n(L){let $=String(L||"chrome").trim().toLowerCase();if($==="cloakbrowser"||$==="cloak-browser")return"cloak";if($==="chromium"||$==="google-chrome"||$==="chrome")return"chrome";return $.replace(/[^a-z0-9_.-]/g,"-")||"chrome"}function s(L,$){let J=L.DM_CUA_BROWSER_PATH||($==="cloak"?L.DM_CUA_CLOAK_PATH||L.CLOAKBROWSER_BINARY_PATH:void 0)||L.CHROME_PATH;return J?f(String(J)):void 0}function t(L=process.env,$=process.cwd()){let J=L.DM_CUA_SCOPE||L.DM_CODING_AGENT_DIR||L.PI_CODING_AGENT_DIR||$,V=o(f(String(J||"."))),z=String(L.DM_CUA_INSTANCE_ID||V.slice(0,12)).replace(/[^a-zA-Z0-9_.-]/g,"-"),Y=n(L.DM_CUA_BROWSER||L.DM_BROWSER_CUA_BROWSER||L.DM_CUA_BROWSER_KIND),k=L.DM_CUA_BROWSER_DISPLAY_NAME||u[Y]||Y,M=s(L,Y),K=h(L.DM_CUA_PORT)??h(L.GREEDY_SEARCH_PORT)??v+Number.parseInt(V.slice(0,8),16)%a,Q=Y==="chrome"?"chrome":Y,X=f(L.DM_CUA_PROFILE_DIR||U(B(),`dm-cua-${Q}-profile-${z}`)),q=c()==="win32"?B():"/tmp";return{instanceId:z,browserKind:Y,browserDisplayName:k,browserExecutablePath:M,port:K,profileDir:X,pidFile:U(X,"chrome.pid"),modeFile:U(X,"chrome-mode"),pagesCache:U(X,"cdp-pages.json"),socketDir:U(q,`dm-cua-cdp-${z}`)}}var A=t(),r=A.port,f4=A.profileDir;function D(){return{kind:A.browserKind,displayName:A.browserDisplayName,executablePath:A.browserExecutablePath,profileDir:A.profileDir,port:A.port}}function e(){if(A.browserKind==="cloak"&&!A.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(A.browserExecutablePath&&!G(A.browserExecutablePath))throw Error(`${A.browserDisplayName} executable not found: ${A.browserExecutablePath}`)}function T(){let $=[f(F,"..","greedysearch-dm"),f(F,"..","..","greedysearch-dm")].map((J)=>({launchScript:U(J,"bin","launch.mjs"),cdpScript:U(J,"bin","cdp.mjs")}));for(let J of $)if(G(J.launchScript)&&G(J.cdpScript))return J;return $[0]}function L4(L=A,$=process.env){let J=$.GREEDY_SEARCH_VISIBLE??$.DM_CUA_VISIBLE??$.DM_BROWSER_CUA_VISIBLE;return{...$,...L.browserExecutablePath?{CHROME_PATH:L.browserExecutablePath}:{},CDP_PROFILE_DIR:L.profileDir.replace(/\\/g,"/"),CDP_PAGES_CACHE:L.pagesCache.replace(/\\/g,"/"),CDP_SOCKET_DIR:L.socketDir.replace(/\\/g,"/"),GREEDY_SEARCH_BROWSER_LABEL:L.browserDisplayName,GREEDY_SEARCH_PORT:String(L.port),GREEDY_SEARCH_PROFILE_DIR:L.profileDir.replace(/\\/g,"/"),GREEDY_SEARCH_PID_FILE:L.pidFile.replace(/\\/g,"/"),GREEDY_SEARCH_MODE_FILE:L.modeFile.replace(/\\/g,"/"),GREEDY_SEARCH_ALLOW_PORT_CLEANUP:$.GREEDY_SEARCH_ALLOW_PORT_CLEANUP??"0",...J===void 0?{}:{GREEDY_SEARCH_VISIBLE:J}}}function j(){return L4(A,process.env)}function $4(L){return l.test(L)}function J4(L){return L.split(/\r?\n/).map(($)=>$.trimEnd()).filter(Boolean).map(($)=>{let J=$.trim().split(/\s{2,}/).filter(Boolean);if(J.length===0)return null;let V=J[0];if(J.length===1)return{target:V,title:"",url:""};if(J.length===2&&$4(J[1]))return{target:V,title:"",url:J[1]};let z=J.at(-1)??"",Y=J.slice(1,-1).join(" ");return{target:V,title:Y,url:z}}).filter(Boolean)}function Q4(){let L=process.env.DM_CODING_AGENT_DIR||process.env.PI_CODING_AGENT_DIR;return L?f(L):U(i(),".dm","agent")}function V4(){let L=U(Q4(),"cua","screenshots");C(L,{recursive:!0});let $=new Date().toISOString().replace(/[:.]/g,"-");return U(L,`browser-${$}.png`)}function w(L,$,{env:J=process.env,timeoutMs:V=N}={}){return new Promise((z,Y)=>{let k=g("node",[L,...$],{env:J,stdio:["ignore","pipe","pipe"]}),M="",K="",Q=!1,X=setTimeout(()=>{if(Q)return;Q=!0,k.kill("SIGTERM"),Y(Error(`Timed out running ${L}`))},V);k.stdout.on("data",(q)=>{M+=q.toString()}),k.stderr.on("data",(q)=>{K+=q.toString()}),k.on("error",(q)=>{if(Q)return;Q=!0,clearTimeout(X),Y(q)}),k.on("close",(q)=>{if(Q)return;if(Q=!0,clearTimeout(X),q!==0){Y(Error((K||M||`exit ${q}`).trim()));return}z({stdout:M,stderr:K})})})}function X4(L,$){return new Promise((J,V)=>{let z=P.request({host:"127.0.0.1",port:r,path:$,method:L},(Y)=>{let k="";Y.on("data",(M)=>{k+=M.toString()}),Y.on("end",()=>{if((Y.statusCode??500)>=400){V(Error(`Browser CDP HTTP ${Y.statusCode}: ${k.trim()}`));return}try{J(k?JSON.parse(k):{})}catch(M){V(M)}})});z.on("error",V),z.end()})}async function Z4(L="about:blank"){return X4("PUT",`/json/new?${encodeURIComponent(L)}`)}async function H(L,$={}){let{cdpScript:J}=T();return w(J,L,{env:j(),timeoutMs:$.timeoutMs??N})}async function I(){let L=await H(["list"]);return J4(L.stdout)}function _(L,$){if(!$)return L[0]??null;let J=$.toUpperCase();return L.find((V)=>V.target.toUpperCase().startsWith(J))??null}async function O(){let{launchScript:L,cdpScript:$}=T();if(!G(L)||!G($))throw Error("Bundled browser helpers are missing. Rebuild DM so dm-cua and greedysearch-dm are staged together.");return{launchScript:L,cdpScript:$}}async function b(){e();let{launchScript:L}=await O();await w(L,[],{env:j(),timeoutMs:N});let $=await I();if($.length===0)await Z4("about:blank"),$=await I();return $}async function y(L){let $=await b(),J=_($,L);if(!J){if(L)throw Error(`No browser tab matching prefix "${L}". Run browser_cua(list) first.`);throw Error("No browser tab available after browser bootstrap.")}return{tab:J.target,page:J,pages:$}}async function S({action:L,tab:$,url:J,selector:V,x:z,y:Y,text:k,expression:M,outputPath:K}={}){switch(L){case"list":{let Q=await b();return{status:"ok",action:L,browser:D(),pages:Q}}case"navigate":{if(!J)throw Error("url is required for action=navigate");let Q=await y($),X=await H(["nav",Q.tab,J],{timeoutMs:90000}),q=await I(),W=_(q,Q.tab)??Q.page;return{status:"ok",action:L,browser:D(),tab:Q.tab,url:W?.url||J,title:W?.title||"",message:X.stdout.trim(),pages:q}}case"snapshot":{let Q=await y($),X=await H(["snap",Q.tab]);return{status:"ok",action:L,browser:D(),tab:Q.tab,title:Q.page?.title||"",url:Q.page?.url||"",snapshot:X.stdout.trim()}}case"screenshot":{let Q=await y($),X=K?f(K):V4();C(m(X),{recursive:!0});let q=await H(["shot",Q.tab,X],{timeoutMs:120000}),W=x(X).toString("base64");return{status:"ok",action:L,browser:D(),tab:Q.tab,title:Q.page?.title||"",url:Q.page?.url||"",imagePath:X,imageBase64:W,mimeType:"image/png",message:q.stdout.trim()}}case"click":{if(!V)throw Error("selector is required for action=click");let Q=await y($),X=await H(["click",Q.tab,V]);return{status:"ok",action:L,browser:D(),tab:Q.tab,selector:V,message:X.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 Q=await y($),X=await H(["clickxy",Q.tab,String(z),String(Y)]);return{status:"ok",action:L,browser:D(),tab:Q.tab,x:z,y:Y,message:X.stdout.trim()}}case"type":{if(k==null||k==="")throw Error("text is required for action=type");let Q=await y($),X=await H(["type",Q.tab,k]);return{status:"ok",action:L,browser:D(),tab:Q.tab,textLength:k.length,message:X.stdout.trim()}}case"evaluate":{if(!M)throw Error("expression is required for action=evaluate");let Q=await y($),X=await H(["eval",Q.tab,M]);return{status:"ok",action:L,browser:D(),tab:Q.tab,value:X.stdout.trim()}}case"stop":{let{launchScript:Q}=await O();try{await H(["stop"])}catch{}let X=await w(Q,["--kill"],{env:j(),timeoutMs:30000});return{status:"ok",action:L,browser:D(),message:(X.stdout||"Stopped browser CUA lane.").trim()}}default:throw Error(`Unsupported browser action: ${L}`)}}function E(L){switch(L.action){case"list":{if(!Array.isArray(L.pages)||L.pages.length===0)return"Browser CUA is ready, but no pages are open.";return["Open browser tabs:",...L.pages.map(($)=>$.url?`${$.target} ${$.title||"(untitled)"} ${$.url}`:`${$.target} ${$.title||"(untitled)"}`)].join(`
2
- `)}case"navigate":return[`Navigated browser tab ${L.tab} to ${L.url||"(unknown URL)"}`,L.title?`Title: ${L.title}`:""].filter(Boolean).join(`
3
- `);case"snapshot":return[`Browser snapshot for ${L.tab}`,L.snapshot||"(empty snapshot)"].join(`
4
- `);case"screenshot":return[`Saved browser screenshot for ${L.tab} at ${L.imagePath}`,L.title?`Title: ${L.title}`:"",L.url?`URL: ${L.url}`:""].filter(Boolean).join(`
5
- `);case"click":return`Clicked selector ${JSON.stringify(L.selector)} on browser tab ${L.tab}`;case"click_xy":return`Clicked browser tab ${L.tab} at CSS coordinates (${L.x}, ${L.y})`;case"type":return`Typed ${L.textLength} characters into browser tab ${L.tab}`;case"evaluate":return`Browser evaluate result for ${L.tab}: ${L.value}`;case"stop":return L.message||"Stopped browser CUA lane.";default:return JSON.stringify(L,null,2)}}var z4=Z.Object({action:Z.Union([Z.Literal("list"),Z.Literal("navigate"),Z.Literal("snapshot"),Z.Literal("screenshot"),Z.Literal("click"),Z.Literal("click_xy"),Z.Literal("type"),Z.Literal("evaluate"),Z.Literal("stop")],{description:"Browser action. Start with list, snapshot, or screenshot before mutating actions on unfamiliar pages."}),tab:Z.Optional(Z.String({description:"Optional target tab prefix from browser_cua(list). If omitted, the first live page in the dedicated profile is used."})),url:Z.Optional(Z.String({description:"URL for action=navigate."})),selector:Z.Optional(Z.String({description:"CSS selector for action=click."})),x:Z.Optional(Z.Number({description:"CSS x coordinate for action=click_xy."})),y:Z.Optional(Z.Number({description:"CSS y coordinate for action=click_xy."})),text:Z.Optional(Z.String({description:"Text for action=type."})),expression:Z.Optional(Z.String({description:"JavaScript expression for action=evaluate."}))},{additionalProperties:!1});function Y4(L){L.on("session_start",async($,J)=>{let{launchScript:V,cdpScript:z}=T();if(!R(V)||!R(z))J.ui.notify("dm-cua: bundled browser helpers are missing. Rebuild DM so dm-cua and greedysearch-dm are staged together.","warning")}),L.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:z4,async execute($,J){try{let V=await S(J),z=[{type:"text",text:E(V)}];if(V.imageBase64&&V.mimeType)z.push({type:"image",data:V.imageBase64,mimeType:V.mimeType});return{content:z,details:V}}catch(V){let z=V instanceof Error?V.message:String(V);return{content:[{type:"text",text:`browser_cua failed: ${z}`}],details:{status:"error",action:J?.action??null,message:z}}}}})}export{Y4 as default};
1
+ import{existsSync as x}from"node:fs";import{Type as l}from"@sinclair/typebox";import{createHash as ho}from"node:crypto";import{spawn as ro}from"node:child_process";import{existsSync as k,mkdirSync as E,readFileSync as eo}from"node:fs";import lo from"node:http";import{homedir as uo,platform as fo,tmpdir as q}from"node:os";import{dirname as F,join as w,resolve as C}from"node:path";import{fileURLToPath as wo}from"node:url";import{spawn as R}from"node:child_process";import{existsSync as P}from"node:fs";import{homedir as H,platform as S}from"node:os";import M from"node:path";var v=900000,Q=16384,V=5,oo=1000;function g(...o){return M.win32.join(...o)}function p(o,i){if(i&&!o.includes(i))o.push(i)}function D(o,i,m){let a=o.PATH??o.Path??o.path??"",s=i==="win32"?";":M.delimiter,d=i==="win32"?(o.PATHEXT??".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean):[""],c=[];for(let h of a.split(s).filter(Boolean))for(let e of m){if(i==="win32"&&M.win32.extname(e)){p(c,g(h,e));continue}for(let t of d){let n=i==="win32"?`${e}${t.toLowerCase()}`:e;p(c,i==="win32"?g(h,n):M.join(h,n))}}return c}function io({env:o=process.env,platformName:i=S(),homeDir:m=H(),exists:a=P}={}){let s=[];if(i==="win32"){for(let d of[o.LOCALAPPDATA,o.ProgramFiles,o["ProgramFiles(x86)"]]){if(!d)continue;p(s,g(d,"Google","Chrome","Application","chrome.exe")),p(s,g(d,"Chromium","Application","chrome.exe"))}p(s,m?g(m,"AppData","Local","Google","Chrome","Application","chrome.exe"):void 0),s.push(...D(o,i,["chrome","chromium"]))}else if(i==="darwin"){for(let d of["/Applications",M.join(m,"Applications")])p(s,M.join(d,"Google Chrome.app","Contents","MacOS","Google Chrome")),p(s,M.join(d,"Chromium.app","Contents","MacOS","Chromium"));s.push(...D(o,i,["google-chrome-stable","google-chrome","chromium","chromium-browser"]))}else if(i==="linux"){for(let d of["/usr/bin/google-chrome-stable","/usr/bin/google-chrome","/usr/bin/chromium","/usr/bin/chromium-browser","/snap/bin/chromium","/usr/local/bin/google-chrome","/usr/local/bin/chromium"])p(s,d);s.push(...D(o,i,["google-chrome-stable","google-chrome","chromium","chromium-browser"]))}return s.find((d)=>a(d))}function u(o,...i){return{command:o,args:i}}function y(o,i){if(i===0)return o;return o.map(({command:m,args:a})=>u("sudo","-n",m,...a))}function mo({platformName:o=S(),uid:i=typeof process.getuid==="function"?process.getuid():void 0}={}){if(o==="darwin")return[{id:"homebrew-google-chrome",commands:[u("brew","install","--cask","google-chrome")]}];if(o==="win32"){let m=["install","--id","Google.Chrome","-e","--silent","--accept-package-agreements","--accept-source-agreements","--disable-interactivity"];return[{id:"winget-user-google-chrome",commands:[u("winget",...m.slice(0,4),"--scope","user",...m.slice(4))]},{id:"winget-machine-google-chrome",commands:[u("winget",...m)]},{id:"chocolatey-google-chrome",commands:[u("choco","install","googlechrome","-y","--no-progress")]}]}if(o==="linux")return[{id:"apt-chromium",commands:y([u("apt-get","update"),u("apt-get","install","-y","chromium")],i)},{id:"apt-chromium-browser",commands:y([u("apt-get","update"),u("apt-get","install","-y","chromium-browser")],i)},{id:"dnf-chromium",commands:y([u("dnf","install","-y","chromium")],i)},{id:"yum-chromium",commands:y([u("yum","install","-y","chromium")],i)},{id:"pacman-chromium",commands:y([u("pacman","-S","--noconfirm","--needed","chromium")],i)},{id:"zypper-chromium",commands:y([u("zypper","--non-interactive","install","chromium")],i)},{id:"apk-chromium",commands:y([u("apk","add","chromium")],i)}];return[]}function no(o=process.env){let i=o.DM_CUA_AUTO_INSTALL;if(i===void 0)return!0;return!["0","false","no","off"].includes(i.trim().toLowerCase())}function to(o){let i=Number(o.DM_CUA_INSTALL_TIMEOUT_MS);return Number.isFinite(i)&&i>0?i:v}function Z(o,i){let m=`${o}${i}`;if(Buffer.byteLength(m)<=Q)return m;return m.slice(-Q)}function ao(o,i,{env:m=process.env,timeoutMs:a=to(m)}={}){return new Promise((s)=>{let d=!1,c="",h,e=R(o,i,{env:m,shell:!1,stdio:["ignore","pipe","pipe"],windowsHide:!0}),t=(n)=>{if(d)return;d=!0,clearTimeout(h),s(n)};e.stdout?.on("data",(n)=>{c=Z(c,n.toString())}),e.stderr?.on("data",(n)=>{c=Z(c,n.toString())}),e.on("error",(n)=>{t({status:n.code==="ENOENT"?"unavailable":"failed",exitCode:null,message:`${n.message}${c?`
2
+ ${c.trim()}`:""}`})}),e.on("close",(n,r)=>{t({status:n===0?"ok":"failed",exitCode:n,message:c.trim()||(r?`installer terminated by signal ${r}`:"")})}),h=setTimeout(()=>{e.kill(),t({status:"timeout",exitCode:null,message:`installer timed out after ${a}ms`})},a)})}function so(o){if(o.startsWith("homebrew-"))return"Homebrew";if(o.startsWith("winget-"))return"winget";return o.split("-")[0]}function co(o){let i=o.message?`: ${o.message}`:"";return`${o.id} ${o.status}${i}`}async function O({env:o=process.env,platformName:i=S(),uid:m=typeof process.getuid==="function"?process.getuid():void 0,findExecutable:a=()=>io({env:o,platformName:i,homeDir:H()}),runCommand:s=(c,h)=>ao(c,h,{env:o}),sleep:d=(c)=>new Promise((h)=>{setTimeout(h,c)})}={}){let c=a();if(c)return{status:"existing",path:c,method:null,attempts:[]};if(!no(o))throw Error("Chrome was not found and automatic installation is disabled by DM_CUA_AUTO_INSTALL.");let h=mo({platformName:i,uid:m});if(h.length===0)throw Error(`Chrome was not found and no automatic Chrome installer is defined for ${i}.`);let e=[];for(let n of h){let r={status:"ok",exitCode:0,message:""};for(let b of n.commands)if(r=await s(b.command,b.args),r.status!=="ok")break;if(r.status!=="ok"){e.push({id:n.id,status:r.status,exitCode:r.exitCode,message:r.message});continue}let L;for(let b=0;b<V;b+=1){if(L=a(),L)break;if(b<V-1)await d(oo)}if(L)return e.push({id:n.id,status:"installed",exitCode:r.exitCode,message:r.message}),{status:"installed",path:L,method:n.id,attempts:e};e.push({id:n.id,status:"missing-after-success",exitCode:r.exitCode,message:`${so(n.id)} reported success, but Chrome was not found`})}let t=e.map(co).join("; ");throw Error(`Chrome automatic installation failed. ${t}`)}var X=F(wo(import.meta.url)),No=C(X,".."),Lo=9322,Ao=400,Y=60000,To=/^(about:|https?:\/\/|file:|chrome-error:\/\/)/i,po={chrome:"Chrome",cloak:"CloakBrowser"};function j(o){let i=Number.parseInt(String(o??""),10);return Number.isInteger(i)&&i>1024&&i<65535?i:null}function Co(o){return ho("sha256").update(String(o||"default")).digest("hex")}function yo(o){let i=String(o||"chrome").trim().toLowerCase();if(i==="cloakbrowser"||i==="cloak-browser")return"cloak";if(i==="chromium"||i==="google-chrome"||i==="chrome")return"chrome";return i.replace(/[^a-z0-9_.-]/g,"-")||"chrome"}function Mo(o,i){let m=o.DM_CUA_BROWSER_PATH||(i==="cloak"?o.DM_CUA_CLOAK_PATH||o.CLOAKBROWSER_BINARY_PATH:void 0)||o.CHROME_PATH;return m?C(String(m)):void 0}function $o(o=process.env,i=process.cwd()){let m=o.DM_CUA_SCOPE||o.DM_CODING_AGENT_DIR||o.PI_CODING_AGENT_DIR||i,a=Co(C(String(m||"."))),s=String(o.DM_CUA_INSTANCE_ID||a.slice(0,12)).replace(/[^a-zA-Z0-9_.-]/g,"-"),d=yo(o.DM_CUA_BROWSER||o.DM_BROWSER_CUA_BROWSER||o.DM_CUA_BROWSER_KIND),c=o.DM_CUA_BROWSER_DISPLAY_NAME||po[d]||d,h=Mo(o,d),e=j(o.DM_CUA_PORT)??j(o.GREEDY_SEARCH_PORT)??Lo+Number.parseInt(a.slice(0,8),16)%Ao,t=d==="chrome"?"chrome":d,n=C(o.DM_CUA_PROFILE_DIR||w(q(),`dm-cua-${t}-profile-${s}`)),r=fo()==="win32"?q():"/tmp";return{instanceId:s,browserKind:d,browserDisplayName:c,browserExecutablePath:h,browserExecutableIsExplicit:Boolean(h),browserInstallation:void 0,port:e,profileDir:n,pidFile:w(n,"chrome.pid"),modeFile:w(n,"chrome-mode"),pagesCache:w(n,"cdp-pages.json"),socketDir:w(r,`dm-cua-cdp-${s}`)}}var f=$o(),bo=f.port,xo=f.profileDir;function A(){return{kind:f.browserKind,displayName:f.browserDisplayName,executablePath:f.browserExecutablePath,installation:f.browserInstallation??null,profileDir:f.profileDir,port:f.port}}async function go(o=f,{env:i=process.env,exists:m=k,ensureChrome:a=O}={}){if(o.browserKind==="cloak"&&!o.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(o.browserExecutablePath&&!m(o.browserExecutablePath))throw Error(`${o.browserDisplayName} executable not found: ${o.browserExecutablePath}`);if(o.browserExecutablePath)return o;if(o.browserKind!=="chrome")throw Error(`${o.browserDisplayName} browser CUA requires an explicit executable path via DM_CUA_BROWSER_PATH.`);let s=await a({env:i});return o.browserExecutablePath=s.path,o.browserInstallation={status:s.status,method:s.method,attempts:s.attempts.map(({id:d,status:c,exitCode:h})=>({id:d,status:c,exitCode:h}))},o}var _;async function ko(){if(!_)_=go(f).catch((o)=>{throw _=void 0,o});return _}function U(){let i=[C(X,"..","greedysearch-dm"),C(X,"..","..","greedysearch-dm")].map((m)=>({launchScript:w(m,"bin","launch.mjs"),cdpScript:w(m,"bin","cdp.mjs")}));for(let m of i)if(k(m.launchScript)&&k(m.cdpScript))return m;return i[0]}function _o(o=f,i=process.env){let m=i.GREEDY_SEARCH_VISIBLE??i.DM_CUA_VISIBLE??i.DM_BROWSER_CUA_VISIBLE;return{...i,...o.browserExecutablePath?{CHROME_PATH:o.browserExecutablePath}:{},CDP_PROFILE_DIR:o.profileDir.replace(/\\/g,"/"),CDP_PAGES_CACHE:o.pagesCache.replace(/\\/g,"/"),CDP_SOCKET_DIR:o.socketDir.replace(/\\/g,"/"),GREEDY_SEARCH_BROWSER_LABEL:o.browserDisplayName,GREEDY_SEARCH_PORT:String(o.port),GREEDY_SEARCH_PROFILE_DIR:o.profileDir.replace(/\\/g,"/"),GREEDY_SEARCH_PID_FILE:o.pidFile.replace(/\\/g,"/"),GREEDY_SEARCH_MODE_FILE:o.modeFile.replace(/\\/g,"/"),GREEDY_SEARCH_ALLOW_PORT_CLEANUP:i.GREEDY_SEARCH_ALLOW_PORT_CLEANUP??"0",...m===void 0?{}:{GREEDY_SEARCH_VISIBLE:m}}}function z(){return _o(f,process.env)}function Uo(o){return To.test(o)}function Do(o){return o.split(/\r?\n/).map((i)=>i.trimEnd()).filter(Boolean).map((i)=>{let m=i.trim().split(/\s{2,}/).filter(Boolean);if(m.length===0)return null;let a=m[0];if(m.length===1)return{target:a,title:"",url:""};if(m.length===2&&Uo(m[1]))return{target:a,title:"",url:m[1]};let s=m.at(-1)??"",d=m.slice(1,-1).join(" ");return{target:a,title:d,url:s}}).filter(Boolean)}function So(){let o=process.env.DM_CODING_AGENT_DIR||process.env.PI_CODING_AGENT_DIR;return o?C(o):w(uo(),".dm","agent")}function Xo(){let o=w(So(),"cua","screenshots");E(o,{recursive:!0});let i=new Date().toISOString().replace(/[:.]/g,"-");return w(o,`browser-${i}.png`)}function I(o,i,{env:m=process.env,timeoutMs:a=Y}={}){return new Promise((s,d)=>{let c=ro("node",[o,...i],{env:m,stdio:["ignore","pipe","pipe"]}),h="",e="",t=!1,n=setTimeout(()=>{if(t)return;t=!0,c.kill("SIGTERM"),d(Error(`Timed out running ${o}`))},a);c.stdout.on("data",(r)=>{h+=r.toString()}),c.stderr.on("data",(r)=>{e+=r.toString()}),c.on("error",(r)=>{if(t)return;t=!0,clearTimeout(n),d(r)}),c.on("close",(r)=>{if(t)return;if(t=!0,clearTimeout(n),r!==0){d(Error((e||h||`exit ${r}`).trim()));return}s({stdout:h,stderr:e})})})}function Jo(o,i){return new Promise((m,a)=>{let s=lo.request({host:"127.0.0.1",port:bo,path:i,method:o},(d)=>{let c="";d.on("data",(h)=>{c+=h.toString()}),d.on("end",()=>{if((d.statusCode??500)>=400){a(Error(`Browser CDP HTTP ${d.statusCode}: ${c.trim()}`));return}try{m(c?JSON.parse(c):{})}catch(h){a(h)}})});s.on("error",a),s.end()})}async function Yo(o="about:blank"){return Jo("PUT",`/json/new?${encodeURIComponent(o)}`)}async function T(o,i={}){let{cdpScript:m}=U();return I(m,o,{env:z(),timeoutMs:i.timeoutMs??Y})}async function J(){let o=await T(["list"]);return Do(o.stdout)}function G(o,i){if(!i)return o[0]??null;let m=i.toUpperCase();return o.find((a)=>a.target.toUpperCase().startsWith(m))??null}async function B(){let{launchScript:o,cdpScript:i}=U();if(!k(o)||!k(i))throw Error("Bundled browser helpers are missing. Rebuild DM so dm-cua and greedysearch-dm are staged together.");return{launchScript:o,cdpScript:i}}async function K(){let{launchScript:o}=await B();await ko(),await I(o,[],{env:z(),timeoutMs:Y});let i=await J();if(i.length===0)await Yo("about:blank"),i=await J();return i}async function $(o){let i=await K(),m=G(i,o);if(!m){if(o)throw Error(`No browser tab matching prefix "${o}". Run browser_cua(list) first.`);throw Error("No browser tab available after browser bootstrap.")}return{tab:m.target,page:m,pages:i}}async function W({action:o,tab:i,url:m,selector:a,x:s,y:d,text:c,expression:h,outputPath:e}={}){switch(o){case"list":{let t=await K();return{status:"ok",action:o,browser:A(),pages:t}}case"navigate":{if(!m)throw Error("url is required for action=navigate");let t=await $(i),n=await T(["nav",t.tab,m],{timeoutMs:90000}),r=await J(),L=G(r,t.tab)??t.page;return{status:"ok",action:o,browser:A(),tab:t.tab,url:L?.url||m,title:L?.title||"",message:n.stdout.trim(),pages:r}}case"snapshot":{let t=await $(i),n=await T(["snap",t.tab]);return{status:"ok",action:o,browser:A(),tab:t.tab,title:t.page?.title||"",url:t.page?.url||"",snapshot:n.stdout.trim()}}case"screenshot":{let t=await $(i),n=e?C(e):Xo();E(F(n),{recursive:!0});let r=await T(["shot",t.tab,n],{timeoutMs:120000}),L=eo(n).toString("base64");return{status:"ok",action:o,browser:A(),tab:t.tab,title:t.page?.title||"",url:t.page?.url||"",imagePath:n,imageBase64:L,mimeType:"image/png",message:r.stdout.trim()}}case"click":{if(!a)throw Error("selector is required for action=click");let t=await $(i),n=await T(["click",t.tab,a]);return{status:"ok",action:o,browser:A(),tab:t.tab,selector:a,message:n.stdout.trim()}}case"click_xy":{if(typeof s!=="number"||Number.isNaN(s)||typeof d!=="number"||Number.isNaN(d))throw Error("x and y are required numbers for action=click_xy");let t=await $(i),n=await T(["clickxy",t.tab,String(s),String(d)]);return{status:"ok",action:o,browser:A(),tab:t.tab,x:s,y:d,message:n.stdout.trim()}}case"type":{if(c==null||c==="")throw Error("text is required for action=type");let t=await $(i),n=await T(["type",t.tab,c]);return{status:"ok",action:o,browser:A(),tab:t.tab,textLength:c.length,message:n.stdout.trim()}}case"evaluate":{if(!h)throw Error("expression is required for action=evaluate");let t=await $(i),n=await T(["eval",t.tab,h]);return{status:"ok",action:o,browser:A(),tab:t.tab,value:n.stdout.trim()}}case"stop":{let{launchScript:t}=await B();try{await T(["stop"])}catch{}let n=await I(t,["--kill"],{env:z(),timeoutMs:30000});return{status:"ok",action:o,browser:A(),message:(n.stdout||"Stopped browser CUA lane.").trim()}}default:throw Error(`Unsupported browser action: ${o}`)}}function N(o){switch(o.action){case"list":{if(!Array.isArray(o.pages)||o.pages.length===0)return"Browser CUA is ready, but no pages are open.";return["Open browser tabs:",...o.pages.map((i)=>i.url?`${i.target} ${i.title||"(untitled)"} ${i.url}`:`${i.target} ${i.title||"(untitled)"}`)].join(`
3
+ `)}case"navigate":return[`Navigated browser tab ${o.tab} to ${o.url||"(unknown URL)"}`,o.title?`Title: ${o.title}`:""].filter(Boolean).join(`
4
+ `);case"snapshot":return[`Browser snapshot for ${o.tab}`,o.snapshot||"(empty snapshot)"].join(`
5
+ `);case"screenshot":return[`Saved browser screenshot for ${o.tab} at ${o.imagePath}`,o.title?`Title: ${o.title}`:"",o.url?`URL: ${o.url}`:""].filter(Boolean).join(`
6
+ `);case"click":return`Clicked selector ${JSON.stringify(o.selector)} on browser tab ${o.tab}`;case"click_xy":return`Clicked browser tab ${o.tab} at CSS coordinates (${o.x}, ${o.y})`;case"type":return`Typed ${o.textLength} characters into browser tab ${o.tab}`;case"evaluate":return`Browser evaluate result for ${o.tab}: ${o.value}`;case"stop":return o.message||"Stopped browser CUA lane.";default:return JSON.stringify(o,null,2)}}var zo=l.Object({action:l.Union([l.Literal("list"),l.Literal("navigate"),l.Literal("snapshot"),l.Literal("screenshot"),l.Literal("click"),l.Literal("click_xy"),l.Literal("type"),l.Literal("evaluate"),l.Literal("stop")],{description:"Browser action. Start with list, snapshot, or screenshot before mutating actions on unfamiliar pages."}),tab:l.Optional(l.String({description:"Optional target tab prefix from browser_cua(list). If omitted, the first live page in the dedicated profile is used."})),url:l.Optional(l.String({description:"URL for action=navigate."})),selector:l.Optional(l.String({description:"CSS selector for action=click."})),x:l.Optional(l.Number({description:"CSS x coordinate for action=click_xy."})),y:l.Optional(l.Number({description:"CSS y coordinate for action=click_xy."})),text:l.Optional(l.String({description:"Text for action=type."})),expression:l.Optional(l.String({description:"JavaScript expression for action=evaluate."}))},{additionalProperties:!1});function Io(o){o.on("session_start",async(i,m)=>{let{launchScript:a,cdpScript:s}=U();if(!x(a)||!x(s))m.ui.notify("dm-cua: bundled browser helpers are missing. Rebuild DM so dm-cua and greedysearch-dm are staged together.","warning")}),o.registerTool({name:"browser_cua",label:"Browser CUA",description:"Drive a dedicated Chrome browser profile through DM's bundled browser CUA lane. If Chrome/Chromium is absent, DM attempts a non-interactive OS package-manager install before launch. 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 with automatic browser bootstrap. Inspect before acting. Screenshot complex pages before click/scroll loops.",parameters:zo,async execute(i,m){try{let a=await W(m),s=[{type:"text",text:N(a)}];if(a.imageBase64&&a.mimeType)s.push({type:"image",data:a.imageBase64,mimeType:a.mimeType});return{content:s,details:a}}catch(a){let s=a instanceof Error?a.message:String(a);return{content:[{type:"text",text:`browser_cua failed: ${s}`}],details:{status:"error",action:m?.action??null,message:s}}}}})}export{Io as default};
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dm-cua",
3
3
  "version": "0.1.0",
4
- "description": "First-class browser CUA extension for DM using a dedicated Chrome/CDP-compatible profile",
4
+ "description": "First-class browser CUA for DM with a dedicated profile and automatic Chrome bootstrap",
5
5
  "type": "module",
6
6
  "keywords": [
7
7
  "duckmind",
@@ -16,4 +16,6 @@ Rules:
16
16
  - Prefer `navigate` for deterministic URLs, including search URLs.
17
17
  - Use `screenshot` before complex click loops or when spatial layout matters.
18
18
  - Fresh dedicated browser profiles auto-bootstrap a blank tab, so the first browser action should not fail just because the page list is empty.
19
+ - If the default Chrome/Chromium browser is absent, `browser_cua` attempts a non-interactive OS package-manager install before launch. Set `DM_CUA_AUTO_INSTALL=0` only when machine changes are not allowed.
20
+ - An explicit browser path remains authoritative and fails closed when invalid; do not hide a bad path by installing another browser.
19
21
  - Default browser is the bundled Chrome/Chromium-compatible CDP lane. To evaluate CloakBrowser, install/pre-download CloakBrowser outside DM, then run with `DM_CUA_BROWSER=cloak` and `DM_CUA_BROWSER_PATH` or `CLOAKBROWSER_BINARY_PATH` pointing at its Chromium executable. DM must not bundle the compiled CloakBrowser binary because upstream permits use but not redistribution.
@@ -1,5 +1,6 @@
1
- import{createHash as h}from"node:crypto";import{spawn as P}from"node:child_process";import{existsSync as W,mkdirSync as E,readFileSync as S}from"node:fs";import b from"node:http";import{homedir as m,platform as x,tmpdir as y}from"node:os";import{dirname as _,join as M,resolve as U}from"node:path";import{fileURLToPath as g}from"node:url";var j=_(g(import.meta.url)),KQ=U(j,".."),v=9322,d=400,I=60000,p=/^(about:|https?:\/\/|file:|chrome-error:\/\/)/i,u={chrome:"Chrome",cloak:"CloakBrowser"};function C(Q){let V=Number.parseInt(String(Q??""),10);return Number.isInteger(V)&&V>1024&&V<65535?V:null}function c(Q){return h("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 X=Q.DM_CUA_BROWSER_PATH||(V==="cloak"?Q.DM_CUA_CLOAK_PATH||Q.CLOAKBROWSER_BINARY_PATH:void 0)||Q.CHROME_PATH;return X?U(String(X)):void 0}function a(Q=process.env,V=process.cwd()){let X=Q.DM_CUA_SCOPE||Q.DM_CODING_AGENT_DIR||Q.PI_CODING_AGENT_DIR||V,J=c(U(String(X||"."))),q=String(Q.DM_CUA_INSTANCE_ID||J.slice(0,12)).replace(/[^a-zA-Z0-9_.-]/g,"-"),z=l(Q.DM_CUA_BROWSER||Q.DM_BROWSER_CUA_BROWSER||Q.DM_CUA_BROWSER_KIND),Y=Q.DM_CUA_BROWSER_DISPLAY_NAME||u[z]||z,L=i(Q,z),k=C(Q.DM_CUA_PORT)??C(Q.GREEDY_SEARCH_PORT)??v+Number.parseInt(J.slice(0,8),16)%d,Z=z==="chrome"?"chrome":z,$=U(Q.DM_CUA_PROFILE_DIR||M(y(),`dm-cua-${Z}-profile-${q}`)),H=x()==="win32"?y():"/tmp";return{instanceId:q,browserKind:z,browserDisplayName:Y,browserExecutablePath:L,port:k,profileDir:$,pidFile:M($,"chrome.pid"),modeFile:M($,"chrome-mode"),pagesCache:M($,"cdp-pages.json"),socketDir:M(H,`dm-cua-cdp-${q}`)}}var K=a(),n=K.port,LQ=K.profileDir;function A(){return{kind:K.browserKind,displayName:K.browserDisplayName,executablePath:K.browserExecutablePath,profileDir:K.profileDir,port:K.port}}function o(){if(K.browserKind==="cloak"&&!K.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(K.browserExecutablePath&&!W(K.browserExecutablePath))throw Error(`${K.browserDisplayName} executable not found: ${K.browserExecutablePath}`)}function O(){let V=[U(j,"..","greedysearch-dm"),U(j,"..","..","greedysearch-dm")].map((X)=>({launchScript:M(X,"bin","launch.mjs"),cdpScript:M(X,"bin","cdp.mjs")}));for(let X of V)if(W(X.launchScript)&&W(X.cdpScript))return X;return V[0]}function s(Q=K,V=process.env){let X=V.GREEDY_SEARCH_VISIBLE??V.DM_CUA_VISIBLE??V.DM_BROWSER_CUA_VISIBLE;return{...V,...Q.browserExecutablePath?{CHROME_PATH:Q.browserExecutablePath}:{},CDP_PROFILE_DIR:Q.profileDir.replace(/\\/g,"/"),CDP_PAGES_CACHE:Q.pagesCache.replace(/\\/g,"/"),CDP_SOCKET_DIR:Q.socketDir.replace(/\\/g,"/"),GREEDY_SEARCH_BROWSER_LABEL:Q.browserDisplayName,GREEDY_SEARCH_PORT:String(Q.port),GREEDY_SEARCH_PROFILE_DIR:Q.profileDir.replace(/\\/g,"/"),GREEDY_SEARCH_PID_FILE:Q.pidFile.replace(/\\/g,"/"),GREEDY_SEARCH_MODE_FILE:Q.modeFile.replace(/\\/g,"/"),GREEDY_SEARCH_ALLOW_PORT_CLEANUP:V.GREEDY_SEARCH_ALLOW_PORT_CLEANUP??"0",...X===void 0?{}:{GREEDY_SEARCH_VISIBLE:X}}}function T(){return s(K,process.env)}function t(Q){return p.test(Q)}function r(Q){return Q.split(/\r?\n/).map((V)=>V.trimEnd()).filter(Boolean).map((V)=>{let X=V.trim().split(/\s{2,}/).filter(Boolean);if(X.length===0)return null;let J=X[0];if(X.length===1)return{target:J,title:"",url:""};if(X.length===2&&t(X[1]))return{target:J,title:"",url:X[1]};let q=X.at(-1)??"",z=X.slice(1,-1).join(" ");return{target:J,title:z,url:q}}).filter(Boolean)}function e(){let Q=process.env.DM_CODING_AGENT_DIR||process.env.PI_CODING_AGENT_DIR;return Q?U(Q):M(m(),".dm","agent")}function QQ(){let Q=M(e(),"cua","screenshots");E(Q,{recursive:!0});let V=new Date().toISOString().replace(/[:.]/g,"-");return M(Q,`browser-${V}.png`)}function B(Q,V,{env:X=process.env,timeoutMs:J=I}={}){return new Promise((q,z)=>{let Y=P("node",[Q,...V],{env:X,stdio:["ignore","pipe","pipe"]}),L="",k="",Z=!1,$=setTimeout(()=>{if(Z)return;Z=!0,Y.kill("SIGTERM"),z(Error(`Timed out running ${Q}`))},J);Y.stdout.on("data",(H)=>{L+=H.toString()}),Y.stderr.on("data",(H)=>{k+=H.toString()}),Y.on("error",(H)=>{if(Z)return;Z=!0,clearTimeout($),z(H)}),Y.on("close",(H)=>{if(Z)return;if(Z=!0,clearTimeout($),H!==0){z(Error((k||L||`exit ${H}`).trim()));return}q({stdout:L,stderr:k})})})}function VQ(Q,V){return new Promise((X,J)=>{let q=b.request({host:"127.0.0.1",port:n,path:V,method:Q},(z)=>{let Y="";z.on("data",(L)=>{Y+=L.toString()}),z.on("end",()=>{if((z.statusCode??500)>=400){J(Error(`Browser CDP HTTP ${z.statusCode}: ${Y.trim()}`));return}try{X(Y?JSON.parse(Y):{})}catch(L){J(L)}})});q.on("error",J),q.end()})}async function XQ(Q="about:blank"){return VQ("PUT",`/json/new?${encodeURIComponent(Q)}`)}async function D(Q,V={}){let{cdpScript:X}=O();return B(X,Q,{env:T(),timeoutMs:V.timeoutMs??I})}async function N(){let Q=await D(["list"]);return r(Q.stdout)}function w(Q,V){if(!V)return Q[0]??null;let X=V.toUpperCase();return Q.find((J)=>J.target.toUpperCase().startsWith(X))??null}async function R(){let{launchScript:Q,cdpScript:V}=O();if(!W(Q)||!W(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 f(){o();let{launchScript:Q}=await R();await B(Q,[],{env:T(),timeoutMs:I});let V=await N();if(V.length===0)await XQ("about:blank"),V=await N();return V}async function G(Q){let V=await f(),X=w(V,Q);if(!X){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:X.target,page:X,pages:V}}async function MQ({action:Q,tab:V,url:X,selector:J,x:q,y:z,text:Y,expression:L,outputPath:k}={}){switch(Q){case"list":{let Z=await f();return{status:"ok",action:Q,browser:A(),pages:Z}}case"navigate":{if(!X)throw Error("url is required for action=navigate");let Z=await G(V),$=await D(["nav",Z.tab,X],{timeoutMs:90000}),H=await N(),F=w(H,Z.tab)??Z.page;return{status:"ok",action:Q,browser:A(),tab:Z.tab,url:F?.url||X,title:F?.title||"",message:$.stdout.trim(),pages:H}}case"snapshot":{let Z=await G(V),$=await D(["snap",Z.tab]);return{status:"ok",action:Q,browser:A(),tab:Z.tab,title:Z.page?.title||"",url:Z.page?.url||"",snapshot:$.stdout.trim()}}case"screenshot":{let Z=await G(V),$=k?U(k):QQ();E(_($),{recursive:!0});let H=await D(["shot",Z.tab,$],{timeoutMs:120000}),F=S($).toString("base64");return{status:"ok",action:Q,browser:A(),tab:Z.tab,title:Z.page?.title||"",url:Z.page?.url||"",imagePath:$,imageBase64:F,mimeType:"image/png",message:H.stdout.trim()}}case"click":{if(!J)throw Error("selector is required for action=click");let Z=await G(V),$=await D(["click",Z.tab,J]);return{status:"ok",action:Q,browser:A(),tab:Z.tab,selector:J,message:$.stdout.trim()}}case"click_xy":{if(typeof q!=="number"||Number.isNaN(q)||typeof z!=="number"||Number.isNaN(z))throw Error("x and y are required numbers for action=click_xy");let Z=await G(V),$=await D(["clickxy",Z.tab,String(q),String(z)]);return{status:"ok",action:Q,browser:A(),tab:Z.tab,x:q,y:z,message:$.stdout.trim()}}case"type":{if(Y==null||Y==="")throw Error("text is required for action=type");let Z=await G(V),$=await D(["type",Z.tab,Y]);return{status:"ok",action:Q,browser:A(),tab:Z.tab,textLength:Y.length,message:$.stdout.trim()}}case"evaluate":{if(!L)throw Error("expression is required for action=evaluate");let Z=await G(V),$=await D(["eval",Z.tab,L]);return{status:"ok",action:Q,browser:A(),tab:Z.tab,value:$.stdout.trim()}}case"stop":{let{launchScript:Z}=await R();try{await D(["stop"])}catch{}let $=await B(Z,["--kill"],{env:T(),timeoutMs:30000});return{status:"ok",action:Q,browser:A(),message:($.stdout||"Stopped browser CUA lane.").trim()}}default:throw Error(`Unsupported browser action: ${Q}`)}}function AQ(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
- `)}case"navigate":return[`Navigated browser tab ${Q.tab} to ${Q.url||"(unknown URL)"}`,Q.title?`Title: ${Q.title}`:""].filter(Boolean).join(`
3
- `);case"snapshot":return[`Browser snapshot for ${Q.tab}`,Q.snapshot||"(empty snapshot)"].join(`
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{MQ as runBrowserAction,a as resolveBrowserRuntimeConfig,s as resolveBrowserProcessEnv,O as resolveBrowserHelperPaths,AQ as renderBrowserActionText,r as parseListOutput,f as ensureBrowserReady,LQ as BROWSER_PROFILE_DIR};
1
+ import{createHash as dm}from"node:crypto";import{spawn as nm}from"node:child_process";import{existsSync as Q,mkdirSync as g,readFileSync as Am}from"node:fs";import Tm from"node:http";import{homedir as Lm,platform as wm,tmpdir as b}from"node:os";import{dirname as N,join as f,resolve as _}from"node:path";import{fileURLToPath as $m}from"node:url";import{spawn as P}from"node:child_process";import{existsSync as p}from"node:fs";import{homedir as W,platform as H}from"node:os";import z from"node:path";var x=900000,E=16384,K=5,t=1000;function D(...m){return z.win32.join(...m)}function X(m,o){if(o&&!m.includes(o))m.push(o)}function Z(m,o,i){let L=m.PATH??m.Path??m.path??"",T=o==="win32"?";":z.delimiter,n=o==="win32"?(m.PATHEXT??".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean):[""],A=[];for(let w of L.split(T).filter(Boolean))for(let M of i){if(o==="win32"&&z.win32.extname(M)){X(A,D(w,M));continue}for(let d of n){let h=o==="win32"?`${M}${d.toLowerCase()}`:M;X(A,o==="win32"?D(w,h):z.join(w,h))}}return A}function v({env:m=process.env,platformName:o=H(),homeDir:i=W(),exists:L=p}={}){let T=[];if(o==="win32"){for(let n of[m.LOCALAPPDATA,m.ProgramFiles,m["ProgramFiles(x86)"]]){if(!n)continue;X(T,D(n,"Google","Chrome","Application","chrome.exe")),X(T,D(n,"Chromium","Application","chrome.exe"))}X(T,i?D(i,"AppData","Local","Google","Chrome","Application","chrome.exe"):void 0),T.push(...Z(m,o,["chrome","chromium"]))}else if(o==="darwin"){for(let n of["/Applications",z.join(i,"Applications")])X(T,z.join(n,"Google Chrome.app","Contents","MacOS","Google Chrome")),X(T,z.join(n,"Chromium.app","Contents","MacOS","Chromium"));T.push(...Z(m,o,["google-chrome-stable","google-chrome","chromium","chromium-browser"]))}else if(o==="linux"){for(let n of["/usr/bin/google-chrome-stable","/usr/bin/google-chrome","/usr/bin/chromium","/usr/bin/chromium-browser","/snap/bin/chromium","/usr/local/bin/google-chrome","/usr/local/bin/chromium"])X(T,n);T.push(...Z(m,o,["google-chrome-stable","google-chrome","chromium","chromium-browser"]))}return T.find((n)=>L(n))}function C(m,...o){return{command:m,args:o}}function Y(m,o){if(o===0)return m;return m.map(({command:i,args:L})=>C("sudo","-n",i,...L))}function r({platformName:m=H(),uid:o=typeof process.getuid==="function"?process.getuid():void 0}={}){if(m==="darwin")return[{id:"homebrew-google-chrome",commands:[C("brew","install","--cask","google-chrome")]}];if(m==="win32"){let i=["install","--id","Google.Chrome","-e","--silent","--accept-package-agreements","--accept-source-agreements","--disable-interactivity"];return[{id:"winget-user-google-chrome",commands:[C("winget",...i.slice(0,4),"--scope","user",...i.slice(4))]},{id:"winget-machine-google-chrome",commands:[C("winget",...i)]},{id:"chocolatey-google-chrome",commands:[C("choco","install","googlechrome","-y","--no-progress")]}]}if(m==="linux")return[{id:"apt-chromium",commands:Y([C("apt-get","update"),C("apt-get","install","-y","chromium")],o)},{id:"apt-chromium-browser",commands:Y([C("apt-get","update"),C("apt-get","install","-y","chromium-browser")],o)},{id:"dnf-chromium",commands:Y([C("dnf","install","-y","chromium")],o)},{id:"yum-chromium",commands:Y([C("yum","install","-y","chromium")],o)},{id:"pacman-chromium",commands:Y([C("pacman","-S","--noconfirm","--needed","chromium")],o)},{id:"zypper-chromium",commands:Y([C("zypper","--non-interactive","install","chromium")],o)},{id:"apk-chromium",commands:Y([C("apk","add","chromium")],o)}];return[]}function e(m=process.env){let o=m.DM_CUA_AUTO_INSTALL;if(o===void 0)return!0;return!["0","false","no","off"].includes(o.trim().toLowerCase())}function mm(m){let o=Number(m.DM_CUA_INSTALL_TIMEOUT_MS);return Number.isFinite(o)&&o>0?o:x}function F(m,o){let i=`${m}${o}`;if(Buffer.byteLength(i)<=E)return i;return i.slice(-E)}function om(m,o,{env:i=process.env,timeoutMs:L=mm(i)}={}){return new Promise((T)=>{let n=!1,A="",w,M=P(m,o,{env:i,shell:!1,stdio:["ignore","pipe","pipe"],windowsHide:!0}),d=(h)=>{if(n)return;n=!0,clearTimeout(w),T(h)};M.stdout?.on("data",(h)=>{A=F(A,h.toString())}),M.stderr?.on("data",(h)=>{A=F(A,h.toString())}),M.on("error",(h)=>{d({status:h.code==="ENOENT"?"unavailable":"failed",exitCode:null,message:`${h.message}${A?`
2
+ ${A.trim()}`:""}`})}),M.on("close",(h,$)=>{d({status:h===0?"ok":"failed",exitCode:h,message:A.trim()||($?`installer terminated by signal ${$}`:"")})}),w=setTimeout(()=>{M.kill(),d({status:"timeout",exitCode:null,message:`installer timed out after ${L}ms`})},L)})}function im(m){if(m.startsWith("homebrew-"))return"Homebrew";if(m.startsWith("winget-"))return"winget";return m.split("-")[0]}function hm(m){let o=m.message?`: ${m.message}`:"";return`${m.id} ${m.status}${o}`}async function c({env:m=process.env,platformName:o=H(),uid:i=typeof process.getuid==="function"?process.getuid():void 0,findExecutable:L=()=>v({env:m,platformName:o,homeDir:W()}),runCommand:T=(A,w)=>om(A,w,{env:m}),sleep:n=(A)=>new Promise((w)=>{setTimeout(w,A)})}={}){let A=L();if(A)return{status:"existing",path:A,method:null,attempts:[]};if(!e(m))throw Error("Chrome was not found and automatic installation is disabled by DM_CUA_AUTO_INSTALL.");let w=r({platformName:o,uid:i});if(w.length===0)throw Error(`Chrome was not found and no automatic Chrome installer is defined for ${o}.`);let M=[];for(let h of w){let $={status:"ok",exitCode:0,message:""};for(let y of h.commands)if($=await T(y.command,y.args),$.status!=="ok")break;if($.status!=="ok"){M.push({id:h.id,status:$.status,exitCode:$.exitCode,message:$.message});continue}let a;for(let y=0;y<K;y+=1){if(a=L(),a)break;if(y<K-1)await n(t)}if(a)return M.push({id:h.id,status:"installed",exitCode:$.exitCode,message:$.message}),{status:"installed",path:a,method:h.id,attempts:M};M.push({id:h.id,status:"missing-after-success",exitCode:$.exitCode,message:`${im(h.id)} reported success, but Chrome was not found`})}let d=M.map(hm).join("; ");throw Error(`Chrome automatic installation failed. ${d}`)}var q=N($m(import.meta.url)),Nm=_(q,".."),Mm=9322,Cm=400,j=60000,um=/^(about:|https?:\/\/|file:|chrome-error:\/\/)/i,fm={chrome:"Chrome",cloak:"CloakBrowser"};function O(m){let o=Number.parseInt(String(m??""),10);return Number.isInteger(o)&&o>1024&&o<65535?o:null}function am(m){return dm("sha256").update(String(m||"default")).digest("hex")}function Um(m){let o=String(m||"chrome").trim().toLowerCase();if(o==="cloakbrowser"||o==="cloak-browser")return"cloak";if(o==="chromium"||o==="google-chrome"||o==="chrome")return"chrome";return o.replace(/[^a-z0-9_.-]/g,"-")||"chrome"}function km(m,o){let i=m.DM_CUA_BROWSER_PATH||(o==="cloak"?m.DM_CUA_CLOAK_PATH||m.CLOAKBROWSER_BINARY_PATH:void 0)||m.CHROME_PATH;return i?_(String(i)):void 0}function Xm(m=process.env,o=process.cwd()){let i=m.DM_CUA_SCOPE||m.DM_CODING_AGENT_DIR||m.PI_CODING_AGENT_DIR||o,L=am(_(String(i||"."))),T=String(m.DM_CUA_INSTANCE_ID||L.slice(0,12)).replace(/[^a-zA-Z0-9_.-]/g,"-"),n=Um(m.DM_CUA_BROWSER||m.DM_BROWSER_CUA_BROWSER||m.DM_CUA_BROWSER_KIND),A=m.DM_CUA_BROWSER_DISPLAY_NAME||fm[n]||n,w=km(m,n),M=O(m.DM_CUA_PORT)??O(m.GREEDY_SEARCH_PORT)??Mm+Number.parseInt(L.slice(0,8),16)%Cm,d=n==="chrome"?"chrome":n,h=_(m.DM_CUA_PROFILE_DIR||f(b(),`dm-cua-${d}-profile-${T}`)),$=wm()==="win32"?b():"/tmp";return{instanceId:T,browserKind:n,browserDisplayName:A,browserExecutablePath:w,browserExecutableIsExplicit:Boolean(w),browserInstallation:void 0,port:M,profileDir:h,pidFile:f(h,"chrome.pid"),modeFile:f(h,"chrome-mode"),pagesCache:f(h,"cdp-pages.json"),socketDir:f($,`dm-cua-cdp-${T}`)}}var u=Xm(),_m=u.port,Bm=u.profileDir;function U(){return{kind:u.browserKind,displayName:u.browserDisplayName,executablePath:u.browserExecutablePath,installation:u.browserInstallation??null,profileDir:u.profileDir,port:u.port}}async function Ym(m=u,{env:o=process.env,exists:i=Q,ensureChrome:L=c}={}){if(m.browserKind==="cloak"&&!m.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(m.browserExecutablePath&&!i(m.browserExecutablePath))throw Error(`${m.browserDisplayName} executable not found: ${m.browserExecutablePath}`);if(m.browserExecutablePath)return m;if(m.browserKind!=="chrome")throw Error(`${m.browserDisplayName} browser CUA requires an explicit executable path via DM_CUA_BROWSER_PATH.`);let T=await L({env:o});return m.browserExecutablePath=T.path,m.browserInstallation={status:T.status,method:T.method,attempts:T.attempts.map(({id:n,status:A,exitCode:w})=>({id:n,status:A,exitCode:w}))},m}var V;async function zm(){if(!V)V=Ym(u).catch((m)=>{throw V=void 0,m});return V}function B(){let o=[_(q,"..","greedysearch-dm"),_(q,"..","..","greedysearch-dm")].map((i)=>({launchScript:f(i,"bin","launch.mjs"),cdpScript:f(i,"bin","cdp.mjs")}));for(let i of o)if(Q(i.launchScript)&&Q(i.cdpScript))return i;return o[0]}function Jm(m=u,o=process.env){let i=o.GREEDY_SEARCH_VISIBLE??o.DM_CUA_VISIBLE??o.DM_BROWSER_CUA_VISIBLE;return{...o,...m.browserExecutablePath?{CHROME_PATH:m.browserExecutablePath}:{},CDP_PROFILE_DIR:m.profileDir.replace(/\\/g,"/"),CDP_PAGES_CACHE:m.pagesCache.replace(/\\/g,"/"),CDP_SOCKET_DIR:m.socketDir.replace(/\\/g,"/"),GREEDY_SEARCH_BROWSER_LABEL:m.browserDisplayName,GREEDY_SEARCH_PORT:String(m.port),GREEDY_SEARCH_PROFILE_DIR:m.profileDir.replace(/\\/g,"/"),GREEDY_SEARCH_PID_FILE:m.pidFile.replace(/\\/g,"/"),GREEDY_SEARCH_MODE_FILE:m.modeFile.replace(/\\/g,"/"),GREEDY_SEARCH_ALLOW_PORT_CLEANUP:o.GREEDY_SEARCH_ALLOW_PORT_CLEANUP??"0",...i===void 0?{}:{GREEDY_SEARCH_VISIBLE:i}}}function s(){return Jm(u,process.env)}function ym(m){return um.test(m)}function Dm(m){return m.split(/\r?\n/).map((o)=>o.trimEnd()).filter(Boolean).map((o)=>{let i=o.trim().split(/\s{2,}/).filter(Boolean);if(i.length===0)return null;let L=i[0];if(i.length===1)return{target:L,title:"",url:""};if(i.length===2&&ym(i[1]))return{target:L,title:"",url:i[1]};let T=i.at(-1)??"",n=i.slice(1,-1).join(" ");return{target:L,title:n,url:T}}).filter(Boolean)}function Qm(){let m=process.env.DM_CODING_AGENT_DIR||process.env.PI_CODING_AGENT_DIR;return m?_(m):f(Lm(),".dm","agent")}function Vm(){let m=f(Qm(),"cua","screenshots");g(m,{recursive:!0});let o=new Date().toISOString().replace(/[:.]/g,"-");return f(m,`browser-${o}.png`)}function G(m,o,{env:i=process.env,timeoutMs:L=j}={}){return new Promise((T,n)=>{let A=nm("node",[m,...o],{env:i,stdio:["ignore","pipe","pipe"]}),w="",M="",d=!1,h=setTimeout(()=>{if(d)return;d=!0,A.kill("SIGTERM"),n(Error(`Timed out running ${m}`))},L);A.stdout.on("data",($)=>{w+=$.toString()}),A.stderr.on("data",($)=>{M+=$.toString()}),A.on("error",($)=>{if(d)return;d=!0,clearTimeout(h),n($)}),A.on("close",($)=>{if(d)return;if(d=!0,clearTimeout(h),$!==0){n(Error((M||w||`exit ${$}`).trim()));return}T({stdout:w,stderr:M})})})}function Zm(m,o){return new Promise((i,L)=>{let T=Tm.request({host:"127.0.0.1",port:_m,path:o,method:m},(n)=>{let A="";n.on("data",(w)=>{A+=w.toString()}),n.on("end",()=>{if((n.statusCode??500)>=400){L(Error(`Browser CDP HTTP ${n.statusCode}: ${A.trim()}`));return}try{i(A?JSON.parse(A):{})}catch(w){L(w)}})});T.on("error",L),T.end()})}async function Hm(m="about:blank"){return Zm("PUT",`/json/new?${encodeURIComponent(m)}`)}async function k(m,o={}){let{cdpScript:i}=B();return G(i,m,{env:s(),timeoutMs:o.timeoutMs??j})}async function I(){let m=await k(["list"]);return Dm(m.stdout)}function l(m,o){if(!o)return m[0]??null;let i=o.toUpperCase();return m.find((L)=>L.target.toUpperCase().startsWith(i))??null}async function S(){let{launchScript:m,cdpScript:o}=B();if(!Q(m)||!Q(o))throw Error("Bundled browser helpers are missing. Rebuild DM so dm-cua and greedysearch-dm are staged together.");return{launchScript:m,cdpScript:o}}async function R(){let{launchScript:m}=await S();await zm(),await G(m,[],{env:s(),timeoutMs:j});let o=await I();if(o.length===0)await Hm("about:blank"),o=await I();return o}async function J(m){let o=await R(),i=l(o,m);if(!i){if(m)throw Error(`No browser tab matching prefix "${m}". Run browser_cua(list) first.`);throw Error("No browser tab available after browser bootstrap.")}return{tab:i.target,page:i,pages:o}}async function lm({action:m,tab:o,url:i,selector:L,x:T,y:n,text:A,expression:w,outputPath:M}={}){switch(m){case"list":{let d=await R();return{status:"ok",action:m,browser:U(),pages:d}}case"navigate":{if(!i)throw Error("url is required for action=navigate");let d=await J(o),h=await k(["nav",d.tab,i],{timeoutMs:90000}),$=await I(),a=l($,d.tab)??d.page;return{status:"ok",action:m,browser:U(),tab:d.tab,url:a?.url||i,title:a?.title||"",message:h.stdout.trim(),pages:$}}case"snapshot":{let d=await J(o),h=await k(["snap",d.tab]);return{status:"ok",action:m,browser:U(),tab:d.tab,title:d.page?.title||"",url:d.page?.url||"",snapshot:h.stdout.trim()}}case"screenshot":{let d=await J(o),h=M?_(M):Vm();g(N(h),{recursive:!0});let $=await k(["shot",d.tab,h],{timeoutMs:120000}),a=Am(h).toString("base64");return{status:"ok",action:m,browser:U(),tab:d.tab,title:d.page?.title||"",url:d.page?.url||"",imagePath:h,imageBase64:a,mimeType:"image/png",message:$.stdout.trim()}}case"click":{if(!L)throw Error("selector is required for action=click");let d=await J(o),h=await k(["click",d.tab,L]);return{status:"ok",action:m,browser:U(),tab:d.tab,selector:L,message:h.stdout.trim()}}case"click_xy":{if(typeof T!=="number"||Number.isNaN(T)||typeof n!=="number"||Number.isNaN(n))throw Error("x and y are required numbers for action=click_xy");let d=await J(o),h=await k(["clickxy",d.tab,String(T),String(n)]);return{status:"ok",action:m,browser:U(),tab:d.tab,x:T,y:n,message:h.stdout.trim()}}case"type":{if(A==null||A==="")throw Error("text is required for action=type");let d=await J(o),h=await k(["type",d.tab,A]);return{status:"ok",action:m,browser:U(),tab:d.tab,textLength:A.length,message:h.stdout.trim()}}case"evaluate":{if(!w)throw Error("expression is required for action=evaluate");let d=await J(o),h=await k(["eval",d.tab,w]);return{status:"ok",action:m,browser:U(),tab:d.tab,value:h.stdout.trim()}}case"stop":{let{launchScript:d}=await S();try{await k(["stop"])}catch{}let h=await G(d,["--kill"],{env:s(),timeoutMs:30000});return{status:"ok",action:m,browser:U(),message:(h.stdout||"Stopped browser CUA lane.").trim()}}default:throw Error(`Unsupported browser action: ${m}`)}}function Sm(m){switch(m.action){case"list":{if(!Array.isArray(m.pages)||m.pages.length===0)return"Browser CUA is ready, but no pages are open.";return["Open browser tabs:",...m.pages.map((o)=>o.url?`${o.target} ${o.title||"(untitled)"} ${o.url}`:`${o.target} ${o.title||"(untitled)"}`)].join(`
3
+ `)}case"navigate":return[`Navigated browser tab ${m.tab} to ${m.url||"(unknown URL)"}`,m.title?`Title: ${m.title}`:""].filter(Boolean).join(`
4
+ `);case"snapshot":return[`Browser snapshot for ${m.tab}`,m.snapshot||"(empty snapshot)"].join(`
5
+ `);case"screenshot":return[`Saved browser screenshot for ${m.tab} at ${m.imagePath}`,m.title?`Title: ${m.title}`:"",m.url?`URL: ${m.url}`:""].filter(Boolean).join(`
6
+ `);case"click":return`Clicked selector ${JSON.stringify(m.selector)} on browser tab ${m.tab}`;case"click_xy":return`Clicked browser tab ${m.tab} at CSS coordinates (${m.x}, ${m.y})`;case"type":return`Typed ${m.textLength} characters into browser tab ${m.tab}`;case"evaluate":return`Browser evaluate result for ${m.tab}: ${m.value}`;case"stop":return m.message||"Stopped browser CUA lane.";default:return JSON.stringify(m,null,2)}}export{lm as runBrowserAction,Xm as resolveBrowserRuntimeConfig,Jm as resolveBrowserProcessEnv,B as resolveBrowserHelperPaths,Sm as renderBrowserActionText,Dm as parseListOutput,Ym as ensureBrowserSelectionUsable,R as ensureBrowserReady,Bm as BROWSER_PROFILE_DIR};
@@ -0,0 +1,2 @@
1
+ import{spawn as y}from"node:child_process";import{existsSync as P}from"node:fs";import{homedir as _,platform as A}from"node:os";import g from"node:path";var S=900000,T=16384,x=5,M=1000;function w(...e){return g.win32.join(...e)}function d(e,o){if(o&&!e.includes(o))e.push(o)}function b(e,o,n){let l=e.PATH??e.Path??e.path??"",r=o==="win32"?";":g.delimiter,s=o==="win32"?(e.PATHEXT??".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean):[""],i=[];for(let m of l.split(r).filter(Boolean))for(let a of n){if(o==="win32"&&g.win32.extname(a)){d(i,w(m,a));continue}for(let h of s){let t=o==="win32"?`${a}${h.toLowerCase()}`:a;d(i,o==="win32"?w(m,t):g.join(m,t))}}return i}function $({env:e=process.env,platformName:o=A(),homeDir:n=_(),exists:l=P}={}){let r=[];if(o==="win32"){for(let s of[e.LOCALAPPDATA,e.ProgramFiles,e["ProgramFiles(x86)"]]){if(!s)continue;d(r,w(s,"Google","Chrome","Application","chrome.exe")),d(r,w(s,"Chromium","Application","chrome.exe"))}d(r,n?w(n,"AppData","Local","Google","Chrome","Application","chrome.exe"):void 0),r.push(...b(e,o,["chrome","chromium"]))}else if(o==="darwin"){for(let s of["/Applications",g.join(n,"Applications")])d(r,g.join(s,"Google Chrome.app","Contents","MacOS","Google Chrome")),d(r,g.join(s,"Chromium.app","Contents","MacOS","Chromium"));r.push(...b(e,o,["google-chrome-stable","google-chrome","chromium","chromium-browser"]))}else if(o==="linux"){for(let s of["/usr/bin/google-chrome-stable","/usr/bin/google-chrome","/usr/bin/chromium","/usr/bin/chromium-browser","/snap/bin/chromium","/usr/local/bin/google-chrome","/usr/local/bin/chromium"])d(r,s);r.push(...b(e,o,["google-chrome-stable","google-chrome","chromium","chromium-browser"]))}return r.find((s)=>l(s))}function c(e,...o){return{command:e,args:o}}function f(e,o){if(o===0)return e;return e.map(({command:n,args:l})=>c("sudo","-n",n,...l))}function E({platformName:e=A(),uid:o=typeof process.getuid==="function"?process.getuid():void 0}={}){if(e==="darwin")return[{id:"homebrew-google-chrome",commands:[c("brew","install","--cask","google-chrome")]}];if(e==="win32"){let n=["install","--id","Google.Chrome","-e","--silent","--accept-package-agreements","--accept-source-agreements","--disable-interactivity"];return[{id:"winget-user-google-chrome",commands:[c("winget",...n.slice(0,4),"--scope","user",...n.slice(4))]},{id:"winget-machine-google-chrome",commands:[c("winget",...n)]},{id:"chocolatey-google-chrome",commands:[c("choco","install","googlechrome","-y","--no-progress")]}]}if(e==="linux")return[{id:"apt-chromium",commands:f([c("apt-get","update"),c("apt-get","install","-y","chromium")],o)},{id:"apt-chromium-browser",commands:f([c("apt-get","update"),c("apt-get","install","-y","chromium-browser")],o)},{id:"dnf-chromium",commands:f([c("dnf","install","-y","chromium")],o)},{id:"yum-chromium",commands:f([c("yum","install","-y","chromium")],o)},{id:"pacman-chromium",commands:f([c("pacman","-S","--noconfirm","--needed","chromium")],o)},{id:"zypper-chromium",commands:f([c("zypper","--non-interactive","install","chromium")],o)},{id:"apk-chromium",commands:f([c("apk","add","chromium")],o)}];return[]}function O(e=process.env){let o=e.DM_CUA_AUTO_INSTALL;if(o===void 0)return!0;return!["0","false","no","off"].includes(o.trim().toLowerCase())}function k(e){let o=Number(e.DM_CUA_INSTALL_TIMEOUT_MS);return Number.isFinite(o)&&o>0?o:S}function L(e,o){let n=`${e}${o}`;if(Buffer.byteLength(n)<=T)return n;return n.slice(-T)}function I(e,o,{env:n=process.env,timeoutMs:l=k(n)}={}){return new Promise((r)=>{let s=!1,i="",m,a=y(e,o,{env:n,shell:!1,stdio:["ignore","pipe","pipe"],windowsHide:!0}),h=(t)=>{if(s)return;s=!0,clearTimeout(m),r(t)};a.stdout?.on("data",(t)=>{i=L(i,t.toString())}),a.stderr?.on("data",(t)=>{i=L(i,t.toString())}),a.on("error",(t)=>{h({status:t.code==="ENOENT"?"unavailable":"failed",exitCode:null,message:`${t.message}${i?`
2
+ ${i.trim()}`:""}`})}),a.on("close",(t,u)=>{h({status:t===0?"ok":"failed",exitCode:t,message:i.trim()||(u?`installer terminated by signal ${u}`:"")})}),m=setTimeout(()=>{a.kill(),h({status:"timeout",exitCode:null,message:`installer timed out after ${l}ms`})},l)})}function U(e){if(e.startsWith("homebrew-"))return"Homebrew";if(e.startsWith("winget-"))return"winget";return e.split("-")[0]}function D(e){let o=e.message?`: ${e.message}`:"";return`${e.id} ${e.status}${o}`}async function H({env:e=process.env,platformName:o=A(),uid:n=typeof process.getuid==="function"?process.getuid():void 0,findExecutable:l=()=>$({env:e,platformName:o,homeDir:_()}),runCommand:r=(i,m)=>I(i,m,{env:e}),sleep:s=(i)=>new Promise((m)=>{setTimeout(m,i)})}={}){let i=l();if(i)return{status:"existing",path:i,method:null,attempts:[]};if(!O(e))throw Error("Chrome was not found and automatic installation is disabled by DM_CUA_AUTO_INSTALL.");let m=E({platformName:o,uid:n});if(m.length===0)throw Error(`Chrome was not found and no automatic Chrome installer is defined for ${o}.`);let a=[];for(let t of m){let u={status:"ok",exitCode:0,message:""};for(let p of t.commands)if(u=await r(p.command,p.args),u.status!=="ok")break;if(u.status!=="ok"){a.push({id:t.id,status:u.status,exitCode:u.exitCode,message:u.message});continue}let C;for(let p=0;p<x;p+=1){if(C=l(),C)break;if(p<x-1)await s(M)}if(C)return a.push({id:t.id,status:"installed",exitCode:u.exitCode,message:u.message}),{status:"installed",path:C,method:t.id,attempts:a};a.push({id:t.id,status:"missing-after-success",exitCode:u.exitCode,message:`${U(t.id)} reported success, but Chrome was not found`})}let h=a.map(D).join("; ");throw Error(`Chrome automatic installation failed. ${h}`)}export{I as runInstallCommand,E as resolveChromeInstallCandidates,$ as resolveChromeExecutable,O as isChromeAutoInstallEnabled,H as ensureChromeAvailable};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duckmind/dm-windows-x64",
3
- "version": "0.53.7",
3
+ "version": "0.59.0",
4
4
  "description": "DuckMind (dm) binary payload for windows x64",
5
5
  "license": "MIT",
6
6
  "os": [