momentic 0.0.62 → 0.0.63

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/bin/cli.js CHANGED
@@ -97,7 +97,7 @@ You have already executed the following commands successfully (most recent liste
97
97
  `).forEach(l=>n.push(` ${l}`))):n.push("PAGE CONTENT CHANGE: <TOO_LONG_TO_DISPLAY/>"):n.push("PAGE CONTENT CHANGE: <NONE/>")}n.push("-".repeat(10))}),n.push(`STARTING URL: ${this.browser.baseURL}`),n.join(`
98
98
  `)}getListHistory(){return Sl`Here are the commands that you have successfully executed:
99
99
  ${this.commandHistory.filter(e=>e.type==="AI_ACTION").map(e=>`- ${e.serializedCommand}`).join(`
100
- `)}`}async executeCommand(e,t,o,n=!1){let s=this.commandHistory[this.commandHistory.length-1];if(!n&&(!s||s.state!=="PENDING"))throw new A("InternalWebAgentError","Executing command but there is no pending entry in the history");if(this.closed)throw new Error("Attempting to execute command on a closed controller");let a=Date.now(),c=await this.executePresetStep(e,t,o),l=Date.now()-a;return this.logger.debug({result:c,duration:l},"Got execution result"),c.succeedImmediately&&!n&&(this.pendingInstructions.pop(),this.pendingInstructions.length>0&&(c.succeedImmediately=!1)),c.elementInteracted&&"target"in e&&e.target&&!e.target.elementDescriptor&&(e.target.elementDescriptor=c.elementInteracted.trim()),n||(s.generatedStep=e,s.serializedCommand=Le(e),s.state="DONE"),c}async executeAssertion(e){let t=await this.getBrowserState(e.filterByViewport),o=await this.browser.url(),n,s;if(e.useVision)await this.browser.removeAllHighlights(),s=await this.browser.screenshot({}),n={goal:e.assertion,url:o,screenshot:s,browserState:"",history:"",numPrevious:-1,lastCommand:null};else{let c=this.getSerializedHistory(o,t);n={goal:e.assertion,url:o,browserState:t,history:c,lastCommand:this.lastExecutedCommand,numPrevious:this.commandHistory.length}}let a=await this.generator.getAssertionResult(n,e.useVision,e.disableCache);if(a.relevantElements&&Promise.all(Array.from(new Set(a.relevantElements)).slice(0,5).map(c=>this.browser.highlight({id:c}))),!a.result)throw new A("AssertionFailureError",a.thoughts);return{succeedImmediately:!1,thoughts:a.thoughts,urlAfterCommand:o,beforeScreenshotOverride:s}}async wrapMultiElementTargetingCommand(e,t,o,n,s=1){let a=await Promise.all(e.map((c,l)=>this.wrapElementTargetingCommand({description:c,cache:t[l],action:async i=>i,options:n})));try{return{result:await o(...a.map(l=>l.result)),caches:a.map(l=>l.cache)}}catch(c){if(s>0)return this.logger.warn({err:c},"Failed to execute action with multiple cached targets, retrying with AI"),this.wrapMultiElementTargetingCommand(e,e.map(()=>{}),o,n,s-1);throw new A("ActionFailureError",c.message,{cause:c})}}async wrapElementTargetingCommand({description:e,cache:t,action:o,options:n,originalCache:s}){let{useVision:a,disableCache:c,filterByViewport:l,useSelector:i}=n,d=n.retriesWithAI??1;if((!t||c)&&!e)throw new A("ActionFailureError","Cannot target element with no cached data or element descriptor");if(i){let m={id:-1,selector:e};return{result:await o(m),cache:m}}c&&(this.logger.info("Cache explicitly disabled for this step"),t=void 0);let u=!!t&&_t(t);if(!t){this.logger.debug("No cached locator data for target, prompting AI for fresh location"),d--;let m=await this.locateElement({description:e,useVision:a,disableCache:c,filterByViewport:l});if(m.id<=0)throw new Error(`Failed to locate element with AI: ${m.thoughts}`);if(t={id:m.id},await this.browser.resolveTarget(t),s?.serializedHtml&&t.serializedHtml&&bl(s.serializedHtml,t.serializedHtml)/Math.min(s.serializedHtml.length,t.serializedHtml.length)>.2)throw this.logger.error({originalCache:s,cache:t},"Rejecting new target due to high change ratio"),new A("ActionFailureError",`Failed to find element with description: ${e}`)}try{let m=await o(t);return u?this.logger.info("Successfully used cached target to perform action"):this.logger.info("Successfully generated and used new target data"),this.logger.debug({cache:t},"Cache state after action"),{result:m,cache:t}}catch(m){if(d>0&&e)return this.logger.debug({cache:t},"Cache state after failed targeting attempt"),this.logger.warn({err:m},"Failed to execute action with cached target, retrying with AI"),this.wrapElementTargetingCommand({description:e,cache:void 0,action:o,options:{...n,retriesWithAI:d},originalCache:t});if(m instanceof A)throw m;let h=`Failed to find ${e?`${e}`:"element"}: ${m instanceof Error?m.message:m}`;throw this.logger.error({err:m,cache:t},h),new A("ActionFailureError",h,{cause:m})}}async screenshotWithDimensions(e){await this.browser.removeAllHighlights();let t=await this.browser.screenshot(e),o=yl(t);return{buffer:t,...o}}async executePresetStep(e,t,o){let n;try{n=await this.resolveCommandTemplateStrings(e,t)}catch(s){throw new A("ActionFailureError",s.message,{cause:s})}try{let s=this.browser.getOpenPages(),a=await this.browser.url(),c=await this.executePresetStepHelper(e,t,o),l=!0;e.type==="NAVIGATE"&&(l=!1);let i=this.browser.getOpenPages();if(l&&i.length!==s.length)for(let d=i.length-1;d>=0;d--){let u=i[d];if(u!==a){await this.browser.switchToPage(u,d);break}}return c}catch(s){throw this.logger.error({err:s},"Error thrown in web agent controller"),s}finally{this.restoreCommandTemplateReplacements(e,n)}}restoreCommandTemplateReplacements(e,t={}){for(let[o,n]of Object.entries(t))ao(e,o,n)}async resolveCommandTemplateStrings(e,t,o="",n={}){let s=["type","a11yData","thoughts"];for(let a in e){if(s.includes(a))continue;let c=e[a],l=o?`${o}.${a}`:a;if(typeof c=="string"&&c.includes("{{")){let i=await Wr({s:c,state:t.toObjectRef(),logger:this.logger});if(c===i)continue;n[l]=c,e[a]=i}else typeof c=="object"&&c!==null&&!Array.isArray(c)&&await this.resolveCommandTemplateStrings(c,t,l,n)}return n}async executePresetStepHelper(e,t,o){switch(o=o||"disableCache"in e&&e.disableCache,e.type){case"SUCCESS":return e.condition?.assertion.trim()?this.executeAssertion(e.condition):{succeedImmediately:!1,urlAfterCommand:await this.browser.url()};case"AI_ASSERTION":{if(!e.assertion.trim())throw new A("ActionFailureError","Missing assertion");return this.executeAssertion(e)}case"AI_WAIT":{if(!e.assertion.trim())throw new A("ActionFailureError","Missing assertion");let i=Date.now();if(e.timeout&&e.timeout>60)throw new A("AssertionFailureError",`AI wait timeout of ${e.timeout} exceeds the maximum allowed timeout of 60s`);let d=(e.timeout??10)*1e3,u,m,h=0;for(;Date.now()-i<d;)try{u=await this.executeAssertion({...e,type:"AI_ASSERTION"});break}catch(g){m=g instanceof Error?g:new Error(`${g}`),this.logger.warn({err:g},`AI_WAIT assert attempt ${h} failed, retrying...`),h++,await V(d/10)}if(!u){let g=`AI wait still failing after ${d}ms.`;throw m&&(g+=` Latest result: ${m.message}`),new A("AssertionFailureError",g)}return u}case"AI_EXTRACT":{if(!e.goal.trim())throw new A("ActionFailureError","Cannot perform AI extraction without goal");let i=await this.browser.getHTML(),d=await this.generator.getTextExtraction({goal:e.goal,browserState:i,returnSchema:e.schema},o);if(d.result==="NOT_FOUND")throw new A("ActionFailureError","No relevant data found for extraction goal on this page");return{data:d.result,succeedImmediately:!1,urlAfterCommand:await this.browser.url()}}case"NAVIGATE":if(!Yr(e.url)&&!Xr(e.url,this.browser.baseURL))throw new A("ActionFailureError",`Invalid URL provided to navigate command: ${e.url}`);await this.browser.navigate({url:e.url});break;case"DIALOG":this.browser.registerDialogHandler(e.action);break;case"CAPTCHA":let n=await this.browser.solveCaptcha();n&&(await this.wrapElementTargetingCommand({description:"the captcha image solution input",cache:void 0,action:i=>this.browser.click(i),options:{useVision:e.useVision,disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector}}),await this.browser.type(n,{clearContent:!0,pressKeysSequentially:!1}));break;case"GO_BACK":await this.browser.goBack();break;case"GO_FORWARD":await this.browser.goForward();break;case"SCROLL_LEFT":case"SCROLL_RIGHT":case"SCROLL_DOWN":case"SCROLL_UP":let s;if(e.target&&(e.target.elementDescriptor.trim()||e.target.a11yData)){let{result:i,cache:d}=await this.wrapElementTargetingCommand({description:e.target.elementDescriptor,cache:e.cache?.target??e.target.a11yData,action:u=>this.browser.hover(u),options:{useVision:e.useVision,disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector}});s=i,d&&(e.cache={target:d})}switch(e.type){case"SCROLL_UP":await this.browser.scrollUp(e.deltaY);break;case"SCROLL_DOWN":await this.browser.scrollDown(e.deltaY);break;case"SCROLL_LEFT":await this.browser.scrollLeft(e.deltaX);break;case"SCROLL_RIGHT":await this.browser.scrollRight(e.deltaX);break}return{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:s};case"WAIT":await this.browser.wait(e.delay*1e3);break;case"REFRESH":await this.browser.refresh();break;case"CLICK":{let i=await this.browser.url(),d={useVision:e.useVision,disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector},{result:u,cache:m}=await this.wrapElementTargetingCommand({description:e.target.elementDescriptor,cache:e.cache?.target??e.target.a11yData,action:g=>this.browser.click(g,{doubleClick:e.doubleClick,rightClick:e.rightClick,force:e.force}),options:d});m&&(e.cache={target:m});let h={urlAfterCommand:await this.browser.url(),succeedImmediately:!1,elementInteracted:u};return Qe(i,h.urlAfterCommand)&&(h.succeedImmediately=!0,h.succeedImmediatelyReason="URL changed"),h}case"DRAG":{let{result:i,caches:d}=await this.wrapMultiElementTargetingCommand([e.fromTarget.elementDescriptor,e.toTarget.elementDescriptor],[e.cache?.fromTarget,e.cache?.toTarget],(u,m)=>this.browser.dragAndDrop(u,m,{force:e.force}),{useVision:e.useVision,filterByViewport:e.filterByViewport,useSelector:e.useSelector,disableCache:o});return d&&d.every(u=>u)&&(e.cache={fromTarget:d[0],toTarget:d[1]}),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:i}}case"MOUSE_DRAG":{let i;e.target?.elementDescriptor&&(i=(await this.wrapElementTargetingCommand({description:e.target.elementDescriptor,cache:e.cache?.target,action:async g=>g,options:{useVision:e.useVision,filterByViewport:e.filterByViewport,useSelector:e.useSelector,disableCache:o}})).result);let d=parseInt(e.deltaX),u=parseInt(e.deltaY);if(isNaN(d)||isNaN(u))throw new A("ActionFailureError",`Invalid pixel values passed to mouse drag command: (${e.deltaX}, ${e.deltaY})`);let m=await this.browser.mouseDrag(d,u,e.steps,i,{force:e.force});return{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:m}}case"SELECT_OPTION":{let{result:i,cache:d}=await this.wrapElementTargetingCommand({description:e.target.elementDescriptor,cache:e.cache?.target??e.target.a11yData,action:u=>this.browser.selectOption(u,e.option),options:{useVision:!1,disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector}});return d&&(e.cache={target:d}),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:i}}case"TAB":await this.browser.switchToPage(e.url);break;case"NEW_TAB":await this.browser.createNewTab(e.url);break;case"COOKIE":if(!e.value)break;await this.browser.setCookie(e.value);break;case"LOCAL_STORAGE":if(!e.value||!e.key)break;await this.browser.setLocalStorage(e.key,e.value);break;case"JAVASCRIPT":{let i;try{e.environment==="BROWSER"?i=await this.browser.executePageFunction(new Bt(e.fragment?`return ${e.code}`:e.code),void 0):i=await Xe({code:e.code,fragment:!!e.fragment,state:t.toObjectRef(),timeoutMs:e.timeout?e.timeout*1e3:void 0,logger:this.logger})}catch(d){throw new A("ActionFailureError",d instanceof Error?d.message:`${d}`,{cause:d})}try{JSON.stringify(i)}catch(d){throw new A("ActionFailureError",`Return value is not serializable: ${d instanceof Error?d.message:`${d}`}`,{cause:d})}return{urlAfterCommand:await this.browser.url(),succeedImmediately:!1,data:i}}case"TYPE":{let i=await this.browser.url(),d;if(e.target){let{result:m,cache:h}=await this.wrapElementTargetingCommand({description:e.target.elementDescriptor,cache:e.cache?.target??e.target.a11yData,action:g=>this.browser.typeIntoTarget(e.value,g,{force:e.force,clearContent:e.clearContent,pressKeysSequentially:e.pressKeysSequentially}),options:{useVision:e.useVision,disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector}});d=m,h&&(e.cache={target:h})}else await this.browser.type(e.value,{force:e.force,clearContent:e.clearContent,pressKeysSequentially:e.pressKeysSequentially});e.pressEnter&&await this.browser.press("Enter");let u={urlAfterCommand:await this.browser.url(),succeedImmediately:!1,elementInteracted:d};return Qe(i,u.urlAfterCommand)&&(u.succeedImmediately=!0,u.succeedImmediatelyReason="URL changed"),u}case"HOVER":{let{result:i,cache:d}=await this.wrapElementTargetingCommand({description:e.target.elementDescriptor,cache:e.cache?.target??e.target.a11yData,action:u=>this.browser.hover(u),options:{useVision:e.useVision,disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector}});return d&&(e.cache={target:d}),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:i}}case"FOCUS":{let{result:i,cache:d}=await this.wrapElementTargetingCommand({description:e.target.elementDescriptor,cache:e.cache?.target??e.target.a11yData,action:u=>this.browser.focus(u),options:{useVision:e.useVision,disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector}});return d&&(e.cache={target:d}),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:i}}case"BLUR":{let{result:i,cache:d}=await this.wrapElementTargetingCommand({description:e.target.elementDescriptor,cache:e.cache?.target??e.target.a11yData,action:u=>this.browser.blur(u),options:{useVision:e.useVision,disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector}});return d&&(e.cache={target:d}),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:i}}case"PRESS":let a=await this.browser.url();await this.browser.press(e.value);let c={urlAfterCommand:await this.browser.url(),succeedImmediately:!1};return Qe(a,c.urlAfterCommand)&&(c.succeedImmediately=!0,c.succeedImmediatelyReason="URL changed"),c;case"REQUEST":{let i=e.timeout??30,d=null,u=new AbortController,m=Object.fromEntries(Object.entries(e.headers||{}).filter(([v,T])=>v&&T)),h=new URLSearchParams;Object.entries(e.params||{}).filter(([v,T])=>v&&T).forEach(([v,T])=>{h.append(v,T)});let g;if(Yr(e.url)&&(g=e.url),Xr(e.url,this.browser.baseURL)&&(g=new URL(e.url,this.browser.baseURL).toString()),!g)throw new A("ActionFailureError",`Invalid URL: ${e.url}`);let p=async()=>{try{d=await fetch(`${g}?${h.toString()}`,{headers:m,method:e.method,body:e.body,signal:u.signal})}catch(v){this.logger.error({err:v},"Failed to make fetch request")}},S=async()=>{await new Promise(v=>setTimeout(v,i*1e3)),u.abort()};await Promise.race([S(),p()]);let y=d;if(!y)throw new A("ActionFailureError",`Fetch request timed out after ${i} seconds`);if(!y.ok)throw new A("ActionFailureError",`Fetch request failed with status ${y.status}`);let w={};y.headers.forEach((v,T)=>{w[T]=v});let b={status:y.status,headers:w};return y.headers.get("content-type")?.includes("json")?b.json=await y.json():y.headers.get("content-type")?.includes("text")&&(b.text=await y.text()),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),data:b}}case"VISUAL_DIFF":{if(!e.screenshot)throw new A("ActionFailureError","Cannot execute visual diff without saved screenshot");await this.browser.removeAllHighlights();let i=await this.screenshotWithDimensions({target:e.target?.a11yData,hideCaret:!0});if(i.height!==e.screenshot.height||i.width!==e.screenshot.width)throw new A("ActionFailureError","Current screenshot does not match saved screenshot dimensions - did you change the size of the target or the viewport?");let d={data:Buffer.alloc(i.width*i.height*4),width:i.width,height:i.height},u;if(e.screenshot.data.startsWith("https://")){let p=await fetch(e.screenshot.data);u=Buffer.from(await p.arrayBuffer())}else u=Buffer.from(e.screenshot.data,"base64");let h=vl(co.decode(u).data,co.decode(i.buffer).data,d.data,i.width,i.height,{threshold:e.threshold,diffColorAlt:[0,255,0]})/(i.width*i.height)*100,g=h>e.threshold;return{fail:g,thoughts:g?`Visual diff of ${h.toFixed(2)}% detected`:void 0,beforeScreenshotOverride:i.buffer,afterScreenshotOverride:co.encode(d,75).data,succeedImmediately:!1,urlAfterCommand:await this.browser.url()}}default:return(i=>{throw"If Typescript complains about the line below, you missed a case or break in the switch above"})(e)}return{succeedImmediately:!1,urlAfterCommand:await this.browser.url()}}async getReverseMappedTarget(e,t,o){return(await this.generator.getReverseMappedDescription({browserState:e.browserState,goal:`${t}`},o)).phrase}stopRecordMode(){this.recordAbortController?.abort()}async startRecordMode(e){this.recordAbortController=new AbortController;let t=new mt({signal:this.recordAbortController.signal,...e});await this.browser.startRecording(this.recordAbortController.signal,t)}};async function Ws({socket:r,generator:e,storage:t,logger:o,rootState:n,localApp:s,devicePixelRatio:a}){let c=o.child({package:"web-agent"}),l=!0;s==="iframe"&&(l=!1);let i=r.handshake.query.testId,d=r.handshake.query.baseUrl,u=r.handshake.query.viewport?JSON.parse(decodeURIComponent(r.handshake.query.viewport)):void 0;if(!i||!d)throw new Error("Socket connection request is missing testId or baseUrl");let m=await t.getOrgId(i),h=r.handshake.query.environmentName,g=h?await t.fetchEnvironment(m,h,o):void 0,p=El(),S=x=>{l&&r.emit("screenshot",{...x,url:d})},y=n?.controller;if(y)y.browser.setActiveFrame(Kt),y.browser.baseURL=d,await y.resetState({clearCookies:!0,clearStorage:!0,timeout:null}),y.setOpen(),n?.context.resetVariables(d,g?.variables);else{let x={};u&&(x.viewport=u),s==="web"&&a&&(x.deviceScaleFactor=a);let U=await _.init({baseUrl:d,logger:c,takeScreenshots:l,onScreenshot:S,contextArgs:x});y=new Re({browser:U,generator:e,config:tt,logger:c})}let w;l&&(w=Rn(r,p,o));let b=async()=>{clearInterval(w.timer)},v=new ce(d,g?.variables);r.emit("testContext",{state:v.toRedactedDisplayCopy()}),R.registerSession({controller:y,context:v,sessionId:p,cleanup:b});let T=_.USER_AGENT;return r.emit("session",{url:d,userAgent:T,viewport:await y.browser.getViewport(),localApp:s}),{sessionId:p,testId:i,orgId:m}}var js=[Un,Us,Bs,$s,Vs,In,Hs,Mn,Dn,Nn,_n,Pn,kn,Fn,On,xn,zn,Ln];var lo=r=>{let{logger:e}=r,t=new Tl(r.baseServer,{cors:{origin:"*",methods:["GET","POST"]},maxHttpBufferSize:1e7});return t.on("connection",async o=>{let n;try{n=await Ws({...r,socket:o})}catch(s){e.error({event:"connection",type:"websocket",err:s},"Failed to setup connection"),o.emit("error",{message:s instanceof Error?s.message:`${s}`});return}js.forEach(s=>Al(s,{socket:o,metadata:n,...r}))}),t},Al=(r,e)=>{let t=r.createHandler(e),o=(...n)=>{r.event!=="cloudMouseMove"&&e.logger.debug({event:r.event,metadata:e.metadata,args:n},"Websocket event");let s=a=>{e.logger.error({event:r.event,type:"websocket",args:n,err:a instanceof Error?a:new Error(`${a}`)},"Unhandled exception in socket handler"),e.socket.emit("error",{message:a instanceof Error?a.message:`${a}`})};try{let a=t.apply(void 0,n);a&&typeof a.catch=="function"&&a.catch(s)}catch(a){s(a)}};e.socket.on(r.event,o)};import{z as xl}from"zod";import Rl from"fetch-retry";var Il=Rl(global.fetch),ie=class{static API_VERSION="v1";baseURL;apiKey;constructor(e){this.baseURL=e.baseURL,this.apiKey=e.apiKey}async sendRequest(e,t){let o=await Il(`${this.baseURL}${e}`,{retries:1,retryDelay:1e3,method:"POST",body:JSON.stringify(t),headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`}});if(!o.ok)throw new Error(`Request to ${e} failed with status ${o.status}: ${await o.text()}`);return o.json()}};var rt=class extends ie{constructor(e){super(e)}async getScreenshotFromS3(e){let t=await this.sendRequest(`/${ie.API_VERSION}/s3/visual-diff-screenshot`,{url:e});return xl.string().parse(t)}async getElementLocation(e,t){let o=await this.sendRequest(`/${ie.API_VERSION}/web-agent/locate-element`,{browserState:e.browserState,goal:e.goal,disableCache:t});return Br.parse(o)}async getElementLocationWithVision(e,t){let o=await this.sendRequest(`/${ie.API_VERSION}/web-agent/visual-locate`,{goal:e.goal,screenshot:e.screenshot?.toString("base64"),hintActivatedScreenshot:e.hintActivatedScreenshot?.toString("base64"),disableCache:t});return Br.parse(o)}async getAssertionResult(e,t,o){if(t){let s=await this.sendRequest(`/${ie.API_VERSION}/web-agent/assertion`,{url:e.url,goal:e.goal,screenshot:e.screenshot?.toString("base64"),disableCache:o,vision:!0});return $r.parse(s)}let n=await this.sendRequest(`/${ie.API_VERSION}/web-agent/assertion`,{url:e.url,browserState:e.browserState,goal:e.goal,history:e.history,numPrevious:e.numPrevious,lastCommand:e.lastCommand,disableCache:o,vision:!1});return $r.parse(n)}async getProposedCommand(e,t){let o=await this.sendRequest(`/${ie.API_VERSION}/web-agent/next-command`,{url:e.url,browserState:e.browserState,goal:e.goal,history:e.history,numPrevious:e.numPrevious,lastCommand:e.lastCommand,disableCache:t});return mn.parse(o)}async getGranularGoals(e,t){let o=await this.sendRequest(`/${ie.API_VERSION}/web-agent/split-goal`,{url:e.url,goal:e.goal,disableCache:t});return pn.parse(o)}async getReverseMappedDescription(e,t){let o=await this.sendRequest(`/${ie.API_VERSION}/web-agent/reverse-mapped-description`,{goal:e.goal,browserState:e.browserState,disableCache:t});return hn.parse(o)}async getTextExtraction(e,t){let o={goal:e.goal,browserState:e.browserState,returnSchema:e.returnSchema,disableCache:t},n=await this.sendRequest(`/${ie.API_VERSION}/web-agent/text-extraction`,o);return Cr.parse(n)}};import{existsSync as Ol,readFileSync as Ll,writeFileSync as uo}from"fs";import mo from"path";import{parse as Ml,stringify as Nl}from"yaml";var X=process.env.MOMENTIC_DIR??Me;function Gs(r,e){let t=an(r);for(let[s,a]of Object.entries(t.modules)){let c=qe(s);uo(mo.join(X,Ne,`${c}.yaml`),a,"utf-8")}let o=qe(e),n=mo.join(X,`${o}.yaml`);return uo(n,t.test,"utf-8"),`${o}.yaml`}function Ks(r,e){if(!Ol(e))throw new Error(`Test not found at path: ${mo}`);let t=Ll(e,"utf-8"),n={...Ml(t),...r},s=Nl(n);uo(e,s,"utf-8")}import{readFileSync as cr,readdirSync as qs,writeFileSync as Ys}from"fs";import{join as po}from"path";import{v4 as Pl}from"uuid";import{parse as lr,stringify as Xs}from"yaml";var ot=po(X,Ne);function Js(r){let e=tr(ot,r.moduleId).path,t=cr(e,"utf-8"),n={...lr(t),...r},s=Xs(n);Ys(e,s,"utf-8")}function Qs(r,e){let t=qe(r),o=po(ot,`${t}.yaml`),n=Pl(),{stepsToSave:s}=ee({steps:e,moduleId:n,testId:"",orgId:""}),a={fileType:"momentic/module",schemaVersion:j,moduleId:n,name:r,steps:s},c=Xs(a);return Ys(o,c,"utf-8"),a}function Zs(){let r=[];for(let e in qs(ot)){if(!e.endsWith(".yaml"))continue;let t=cr(e,"utf-8"),o=lr(t),n={name:o.name,moduleId:o.moduleId,numSteps:o.steps.length};r.push(n)}return r}function ei(){let r=[];for(let e of qs(ot)){if(!e.endsWith(".yaml"))continue;let t=po(ot,e),o=cr(t,"utf-8"),n=lr(o);try{let s=Et.parse(n);r.push(s)}catch(s){B.warn({err:s},"Error parsing module, skipping...")}}return r}function ti(r){let e=tr(Ne,r).path,t=cr(e,"utf-8"),o=lr(t);return Et.parse(o)}import{Router as zl}from"express";import{existsSync as Dl,readFileSync as _l,readdirSync as kl}from"fs";import{join as ri}from"path";import{parse as Fl}from"yaml";var ho=ri(X,vt);function oi(){let r=[];if(!Dl(ho))return[];for(let e of kl(ho)){if(!e.endsWith(".yaml"))continue;let t=ri(ho,e),o=_l(t,"utf-8"),n=Fl(o);try{let s=ne.parse(n);r.push(s)}catch(s){B.warn({err:s},"Error parsing environment, skipping...")}}return r}function de(r){return function(...e){let t=e[e.length-1],o=r(...e);Promise.resolve(o).catch(t)}}var ni=zl();ni.get("/",de((r,e)=>{let t=oi();e.status(200).json(t)}));var si=ni;import{Router as Ul}from"express";var xt=Ul();xt.get("/",de((r,e)=>{let t=ei();e.status(200).json(t)}));xt.get("/metadata",de((r,e)=>{let t=Zs();e.status(200).json(t)}));xt.post("/",de(async(r,e)=>{let t;try{t=dn.parse(r.body)}catch(n){e.status(400).json({error:`Invalid request body: ${n}`});return}try{Ct(t.name)}catch(n){e.status(400).json({error:`Invalid module name: ${n}`});return}let o=Qs(t.name,t.steps);e.status(201).json(o)}));xt.get("/:moduleId",de((r,e)=>{if(!r.params.moduleId){e.status(400).json({error:"Missing moduleId in url path."});return}let t=ti(r.params.moduleId);e.json(t)}));var ii=xt;import{Router as jl}from"express";import{existsSync as Gl}from"fs";import{join as mi}from"path";import{v4 as Kl}from"uuid";var fo="https://api.momentic.ai",ai=r=>{fo=r},Ot=()=>fo,Hl,ci=r=>{Hl=r};var dr,go,li=async r=>{if(dr)return;let e=new K({baseURL:fo,apiKey:r});try{dr=await e.getOrgId(),go=r}catch(t){throw new Error(`Error checking API key against server: ${t}`)}},ur=()=>{if(!dr)throw new Error("Your organization ID appears invalid. Please contact Momentic Support.");return dr},di=()=>{if(!go)throw new Error("Your API key appears invalid. Please contact Momentic Support.");return go};import ui,{multistream as $l}from"pino";import Bl from"pino-pretty";var yo=new Map,Vl=process.env.NODE_ENV==="production",So=class r{consoleLogger;ddClientToken;hostname;bindingAttributes;site="https://http-intake.logs.us5.datadoghq.com/api/v2/logs";constructor({bindings:e,clientToken:t,hostname:o}){this.ddClientToken=t,this.hostname=o,this.bindingAttributes={...e,env:process.env.NODE_ENV};let n={base:this.bindingAttributes,errorKey:"err",level:"debug"};this.consoleLogger=Vl?ui(n):ui(n,$l([{stream:Bl({colorize:!0})}]))}child(e){return new r({clientToken:this.ddClientToken,bindings:{...this.bindingAttributes,...e},hostname:this.hostname})}flush(e){this.consoleLogger.flush(e)}log(e,t,o,...n){t&&o===void 0&&(o=`${t}`,t={}),this.consoleLogger[e](t,o,...n);let s=Object.assign({},this.bindingAttributes,t&&typeof t=="object"?t:{});n.length>0&&(s.args=n),(async()=>{try{let a=await fetch(this.site,{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json","DD-API-KEY":this.ddClientToken},body:JSON.stringify([{ddsource:this.bindingAttributes.app,hostname:this.hostname??"vercel",service:"momentic",message:{message:o||"",...s,level:e}}])});if(!a.ok)throw new Error(`Failed to log to Datadog: ${a.statusText})}`)}catch(a){this.consoleLogger.warn({obj:t,msg:o,args:n,err:a},"Failed to log to Datadog")}})()}debug(e,t,...o){this.log("debug",e,t,...o)}info(e,t,...o){this.log("info",e,t,...o)}warn(e,t,...o){this.log("warn",e,t,...o)}error(e,t,...o){this.log("error",e,t,...o)}bindings(){return this.bindingAttributes}setMinLevel(e){this.consoleLogger.level=e}},mr=({app:r,clientToken:e,hostname:t})=>{if(!process.env.DD_CLIENT_TOKEN&&!process.env.NEXT_PUBLIC_DD_CLIENT_TOKEN&&!e)throw new Error("Missing DD_CLIENT_TOKEN");return yo.has(r)||yo.set(r,new So({bindings:{app:r},hostname:t,clientToken:e||process.env.NEXT_PUBLIC_DD_CLIENT_TOKEN||process.env.DD_CLIENT_TOKEN})),yo.get(r)};import{hostname as Wl}from"os";var ue=mr({app:"desktop-server",clientToken:"pubcfd7516a5c0ba852b42675cd97bee027",hostname:Wl()});var Lt=jl();Lt.get("/",de((r,e)=>{let t=At(X,ue);e.status(200).json(t)}));Lt.post("/",de((r,e)=>{let t;try{t=ln.parse(r.body)}catch(c){e.status(400).json({error:`Invalid request body: ${c}`});return}try{Ct(t.name)}catch(c){e.status(400).json({error:c.message});return}let n={id:Kl(),name:t.name,baseUrl:t.baseUrl,schemaVersion:j,advanced:{disableAICaching:!1,viewport:t.viewport??We},retries:0,steps:[]};t.environment&&(n.envs=[{name:t.environment,defaultOnLocal:!0}]);let s=Gs(n,t.name),a={...n,testPath:s};e.status(201).json(a)}));Lt.get("/:testPath",de(async(r,e)=>{if(!r.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let t=mi(X,r.params.testPath);if(!Gl(t)){e.status(400).json({error:`Test not found at path: ${t}`});return}let o=await rr(t,ot);e.status(200).json(o)}));Lt.patch("/:testPath",de(async(r,e)=>{if(!r.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let t;try{t=cn.parse(r.body)}catch(i){e.status(400).json({error:`Invalid request body: ${i}`});return}let o=mi(X,r.params.testPath),n;try{n=Jn(o)}catch(i){e.status(400).json({error:`Existing test file on disk is invalid: ${i}`});return}let{stepsToSave:s,moduleUpdates:a,cachesToSave:c}=ee({steps:t.steps,testId:n.id,orgId:ur()});a.forEach(i=>Js(i)),Ks({steps:s},o),await new K({apiKey:di(),baseURL:Ot()}).updateStepCaches({entries:c,testId:n.id}),e.status(201).json({message:"ok"})}));var pi=Lt;async function hi({momenticServerUrl:r,apiKey:e,mode:t,serverPort:o,appPort:n,staticDir:s,assetsDir:a,devicePixelRatio:c}){if(!Xl(X)||!Jl(X).isDirectory()){let p=ed.isAbsolute(X);throw new Error(`Root folder ${X} does not exist${p?"":` in the current directory ("${process.cwd()}")`}. Please run \`npx momentic init\` if you wish to setup Momentic here for the first time.`)}r&&ai(r),B.info("Checking API key"),await li(e);let i=`http://localhost:${o}`;n&&(i=`http://localhost:${n}`);let d=rd(s);await new Promise(p=>{d.listen(o,()=>{B.info(`Server is running at http://localhost:${o}`),p()})});let m=new rt({baseURL:Ot(),apiKey:e}),h=new ut(new K({baseURL:Ot(),apiKey:e}),ur());if(t==="web"){lo({localApp:"web",baseServer:d,generator:m,storage:h,logger:ue,devicePixelRatio:c}),await Zl(i);return}let g=await od({appUrl:i,apiGenerator:m,extensionPath:td(a,"extension"),onClose:async()=>{B.info("Browser closed, closing app."),await g.browser.cleanup(),d.close(()=>{process.exit(0)})}});lo({localApp:"iframe",baseServer:d,generator:m,storage:h,logger:ue,rootState:{controller:g,context:new ce(""),executionCancelled:!1}}),process.on("uncaughtException",p=>{ue.error({err:p},"Uncaught exception in desktop-server")}),process.on("unhandledRejection",p=>{ue.error({err:p},"Unhandled rejection in desktop-server")})}function rd(r){let e=wo();e.use(Yl()),e.use(ql.json({limit:"50mb"}));let t=wo.Router();if(t.use("/tests",pi),t.use("/modules",ii),t.use("/environments",si),e.use("/api",t),e.use((n,s,a)=>{n.path!=="/healthcheck"&&ue.debug({url:n.url,path:n.path,query:n.query,method:n.method,body:n.body,headers:n.rawHeaders,client:n.ip},"Received desktop-server request"),s.on("close",()=>{s.statusCode>=400&&ue.error({url:n.url,method:n.method,statusCode:s.statusCode},"Request completed in error")}),a()}),e.use((n,s,a,c)=>{n instanceof Error&&n.message.includes("BadRequestError: request aborted")||(ue.error({stack:n.stack,msg:n.message,url:s.url,method:s.method},"Unhandled exception leading to 500 on desktop-server"),a.status(500).send("Internal Server Error"),c(n))}),r){let n=wo.static(r);e.use("/",n),e.use("*",n)}return Ql.createServer(e)}async function od({appUrl:r,apiGenerator:e,extensionPath:t,onClose:o}){let n=await _.init({baseUrl:r,logger:ue,browserArgs:{headless:!1,handleSIGTERM:!0},contextArgs:{bypassCSP:!0,viewport:null},waitForLoad:!0,localMode:!0,localAppUrl:r,extensionPath:t,skipPageSetup:!0,timeout:null,onClose:o}),s=new Re({browser:n,config:tt,generator:e,logger:ue});return ci(s),s}import{existsSync as zd}from"fs";import{platform as Ud}from"os";import Vi,{dirname as Hd}from"path";import{fileURLToPath as $d}from"url";var gi="0.0.62";import{hostname as sd}from"os";var C=mr({app:"cli",clientToken:"pub7eb923f18fb3f1d42ac5eba8c5ea13a5",hostname:sd()});import{createHash as wi}from"crypto";import{existsSync as bi,mkdirSync as md,readFileSync as To,writeFileSync as vi}from"fs";import{join as Ci}from"path";import{parse as pd}from"yaml";import{existsSync as bo,mkdirSync as cd,readdirSync as yi,statSync as vo}from"fs";import{homedir as Si}from"os";import{join as Mt,resolve as ld}from"path";import id from"chalk";import ad from"readline/promises";async function he(r,e){if(process.env.CI)return!0;C.flush(),await new Promise(s=>setTimeout(s,500));let t=ad.createInterface({input:process.stdin,output:process.stdout});r=`${r} (y/N) `;let o=e?id.bold.yellow(r):r,n=await t.question(o);return t.close(),n.toLowerCase()==="y"}var Co=Me,nt=Mt(Me,Ne),Eo=Mt(Me,Nr),ve=Mt(Me,vt),dd=Mt(Si(),"momentic",Pr),ud=[dd,nt,Eo,ve];function fi(r){try{return bo(r)&&vo(r).isDirectory()}catch(e){return C.error({err:e},`Error reading path ${r} during directory exists check`),!1}}async function ke(r,e=!1){let t=await r.getOrgId();Si()||(C.error("Your home directory could not be found. Please ensure the HOME environment variable is set for POSIX systems, and the USERPROFILE environment variable is set for Windows systems."),process.exit(1)),fi(Co)||!e&&!await he(`A '${Me}' folder was not found in the current directory. Proceed with creation here? You will need to invoke the Momentic CLI from this directory in the future.`)&&(C.error("Setup cancelled"),process.exit(1));for(let o of ud)fi(o)||(!e&&!await he(`'${o}' folder was not found in the current directory. Create it now?`)&&(C.error("Setup cancelled"),process.exit(1)),cd(o,{recursive:!0}));return yi(ve).length===0&&(!e&&!await he("You do not appear to have any environments configured locally. Pull environments from Momentic Cloud?")&&(C.error("Setup cancelled"),process.exit(1)),await pr({client:r,all:!0,skipPrompts:e})),C.info("CLI initialization complete"),t}function Nt(r,e,t=new Set){for(let o of r){let n=ld(o),s=!1;try{s=bo(n)&&vo(n).isDirectory()}catch(a){C.error({err:a},`Error reading path ${n} during collect paths`)}if(n&&s){let a=yi(n).map(c=>Mt(n,c));Nt(a,e,t);continue}if(n.endsWith(".yaml")){try{if(!bo(n)||!vo(n).isFile()){C.error(`File not found or unreadable: ${n}`);continue}}catch(a){C.error({err:a},`Error reading file ${n} during collect paths`);continue}if(!e(n))continue;t.add(n)}}return t}async function pr({envNames:r,client:e,all:t,skipPrompts:o}){let n=await e.getAllEnvironments();r&&!t&&(n=n.filter(c=>r?.includes(c.name))),bi(ve)||md(ve);let s=0,a=0;for(let c of n){let l=Ci(ve,`${c.name}.yaml`);if(!bi(l))vi(l,kr(c)),s++;else{let i=wi("sha256").update(To(l)).digest("hex"),d=kr(c),u=wi("sha256").update(d).digest("hex");if(i!==u){if(!o&&!await he(`Environment ${c.name} already exists on disk but needs to be updated. Overwrite?`)){C.error("Pull cancelled");return}vi(l,d),a++}}}C.info(`Pulled ${s} new environments and updated ${a} existing environments`)}function hd(r){return r.endsWith(".yaml")?To(r,"utf8").includes("momentic/environment")?!0:(C.warn(`Skipping YAML that is not a Momentic environment: ${r}`),!1):!1}async function Ei({client:r,names:e,all:t,yes:o}){let n;t?n=[ve]:n=e.map(c=>Ci(ve,`${c.toLowerCase()}.yaml`));let s=Nt(n,hd);C.info(`Found ${s.size} environments(s) to push:`),s.forEach(c=>C.info(` - ${c}`)),C.info("Loading file contents");let a=[];for(let c of s){let l=pd(To(c,"utf-8"));try{let i=ne.parse(l);a.push(i)}catch(i){C.error({err:i},`${c} failed to parse as a valid environment file`),process.exit(1)}}if(!o&&!await he("Pushing environments can overwrite variables on cloud and instantly affect scheduled runs. Continue?",!0)){C.error("Push cancelled");return}C.info(`Pushing ${s.size} environment(s)`),await r.updateEnvironments(a),C.info("Push successful!")}import{registry as Ao}from"playwright-core/lib/server";function gd(r){let e=[],t=[];for(let o of r){let n=Ao.findExecutable(o);!n||n.installType==="none"?e.push(o):t.push(n)}return t}async function Ti(){let r=gd(["chromium"]);await Ao.installDeps(r,!1),await Ao.install(r,!1)}import{Argument as hr,Option as Fe}from"commander";var ze=new Fe("--api-key <key>","API key for authentication. If not supplied, attempts to read the MOMENTIC_API_KEY env var.").env("MOMENTIC_API_KEY").makeOptionMandatory(!0),Ue=new Fe("--server <server>","Momentic server to use. Leave unchanged unless using Momentic on-premise.").default("https://api.momentic.ai"),He=new Fe("-y, --yes","Skip confirmation prompts.").env("CI").default(!0),Ai=new Fe("--no-report","Skip reporting test results to Momentic Cloud when running with the --local flag."),Ri=new Fe("--pixel-ratio <number>","Device pixel ratio of your screen or monitor. Mac OS Retina displays and machines marketed as 'HiDPI' should usually set this to 2.").default(1).argParser(r=>parseInt(r,10)),Ii=new Fe("--env <env>","Name of the environment to use when running tests."),Ro=new Fe("-a, --all","Select all tests in your organization from Momentic Cloud. Cannot be used together with <tests> arguments."),Io=new Fe("-a, --all","Select all environments in your organization from Momentic Cloud. Cannot be used together with <paths> arguments."),xi=new hr("<tests...>",`One or more test paths to pull from Momentic Cloud.
100
+ `)}`}async executeCommand(e,t,o,n=!1){let s=this.commandHistory[this.commandHistory.length-1];if(!n&&(!s||s.state!=="PENDING"))throw new A("InternalWebAgentError","Executing command but there is no pending entry in the history");if(this.closed)throw new Error("Attempting to execute command on a closed controller");let a=Date.now(),c=await this.executePresetStep(e,t,o),l=Date.now()-a;return this.logger.debug({result:c,duration:l},"Got execution result"),c.succeedImmediately&&!n&&(this.pendingInstructions.pop(),this.pendingInstructions.length>0&&(c.succeedImmediately=!1)),c.elementInteracted&&"target"in e&&e.target&&!e.target.elementDescriptor&&(e.target.elementDescriptor=c.elementInteracted.trim()),n||(s.generatedStep=e,s.serializedCommand=Le(e),s.state="DONE"),c}async executeAssertion(e){let t=await this.getBrowserState(e.filterByViewport),o=await this.browser.url(),n,s;if(e.useVision)await this.browser.removeAllHighlights(),s=await this.browser.screenshot({}),n={goal:e.assertion,url:o,screenshot:s,browserState:"",history:"",numPrevious:-1,lastCommand:null};else{let c=this.getSerializedHistory(o,t);n={goal:e.assertion,url:o,browserState:t,history:c,lastCommand:this.lastExecutedCommand,numPrevious:this.commandHistory.length}}let a=await this.generator.getAssertionResult(n,e.useVision,e.disableCache);if(a.relevantElements&&Promise.all(Array.from(new Set(a.relevantElements)).slice(0,5).map(c=>this.browser.highlight({id:c}))),!a.result)throw new A("AssertionFailureError",a.thoughts);return{succeedImmediately:!1,thoughts:a.thoughts,urlAfterCommand:o,beforeScreenshotOverride:s}}async wrapMultiElementTargetingCommand(e,t,o,n,s=1){let a=await Promise.all(e.map((c,l)=>this.wrapElementTargetingCommand({description:c,cache:t[l],action:async i=>i,options:n})));try{return{result:await o(...a.map(l=>l.result)),caches:a.map(l=>l.cache)}}catch(c){if(s>0)return this.logger.warn({err:c},"Failed to execute action with multiple cached targets, retrying with AI"),this.wrapMultiElementTargetingCommand(e,e.map(()=>{}),o,n,s-1);throw new A("ActionFailureError",c.message,{cause:c})}}async wrapElementTargetingCommand({description:e,cache:t,action:o,options:n,originalCache:s}){let{useVision:a,disableCache:c,filterByViewport:l,useSelector:i}=n,d=n.retriesWithAI??1;if((!t||c)&&!e)throw new A("ActionFailureError","Cannot target element with no cached data or element descriptor");if(i){let m={id:-1,selector:e};return{result:await o(m),cache:m}}c&&(this.logger.info("Cache explicitly disabled for this step"),t=void 0);let u=!!t&&_t(t);if(!t){this.logger.debug("No cached locator data for target, prompting AI for fresh location"),d--;let m=await this.locateElement({description:e,useVision:a,disableCache:c,filterByViewport:l});if(m.id<=0)throw new Error(`Failed to locate element with AI: ${m.thoughts}`);if(t={id:m.id},await this.browser.resolveTarget(t),s?.serializedHtml&&t.serializedHtml&&bl(s.serializedHtml,t.serializedHtml)/Math.min(s.serializedHtml.length,t.serializedHtml.length)>.2)throw this.logger.error({originalCache:s,cache:t},"Rejecting new target due to high change ratio"),new A("ActionFailureError",`Failed to find element with description: ${e}`)}try{let m=await o(t);return u?this.logger.info("Successfully used cached target to perform action"):this.logger.info("Successfully generated and used new target data"),this.logger.debug({cache:t},"Cache state after action"),{result:m,cache:t}}catch(m){if(d>0&&e)return this.logger.debug({cache:t},"Cache state after failed targeting attempt"),this.logger.warn({err:m},"Failed to execute action with cached target, retrying with AI"),this.wrapElementTargetingCommand({description:e,cache:void 0,action:o,options:{...n,retriesWithAI:d},originalCache:t});if(m instanceof A)throw m;let h=`Failed to find ${e?`${e}`:"element"}: ${m instanceof Error?m.message:m}`;throw this.logger.error({err:m,cache:t},h),new A("ActionFailureError",h,{cause:m})}}async screenshotWithDimensions(e){await this.browser.removeAllHighlights();let t=await this.browser.screenshot(e),o=yl(t);return{buffer:t,...o}}async executePresetStep(e,t,o){let n;try{n=await this.resolveCommandTemplateStrings(e,t)}catch(s){throw new A("ActionFailureError",s.message,{cause:s})}try{let s=this.browser.getOpenPages(),a=await this.browser.url(),c=await this.executePresetStepHelper(e,t,o),l=!0;e.type==="NAVIGATE"&&(l=!1);let i=this.browser.getOpenPages();if(l&&i.length!==s.length)for(let d=i.length-1;d>=0;d--){let u=i[d];if(u!==a){await this.browser.switchToPage(u,d);break}}return c}catch(s){throw this.logger.error({err:s},"Error thrown in web agent controller"),s}finally{this.restoreCommandTemplateReplacements(e,n)}}restoreCommandTemplateReplacements(e,t={}){for(let[o,n]of Object.entries(t))ao(e,o,n)}async resolveCommandTemplateStrings(e,t,o="",n={}){let s=["type","a11yData","thoughts"];for(let a in e){if(s.includes(a))continue;let c=e[a],l=o?`${o}.${a}`:a;if(typeof c=="string"&&c.includes("{{")){let i=await Wr({s:c,state:t.toObjectRef(),logger:this.logger});if(c===i)continue;n[l]=c,e[a]=i}else typeof c=="object"&&c!==null&&!Array.isArray(c)&&await this.resolveCommandTemplateStrings(c,t,l,n)}return n}async executePresetStepHelper(e,t,o){switch(o=o||"disableCache"in e&&e.disableCache,e.type){case"SUCCESS":return e.condition?.assertion.trim()?this.executeAssertion(e.condition):{succeedImmediately:!1,urlAfterCommand:await this.browser.url()};case"AI_ASSERTION":{if(!e.assertion.trim())throw new A("ActionFailureError","Missing assertion");return this.executeAssertion(e)}case"AI_WAIT":{if(!e.assertion.trim())throw new A("ActionFailureError","Missing assertion");let i=Date.now();if(e.timeout&&e.timeout>60)throw new A("AssertionFailureError",`AI wait timeout of ${e.timeout} exceeds the maximum allowed timeout of 60s`);let d=(e.timeout??10)*1e3,u,m,h=0;for(;Date.now()-i<d;)try{u=await this.executeAssertion({...e,type:"AI_ASSERTION"});break}catch(g){m=g instanceof Error?g:new Error(`${g}`),this.logger.warn({err:g},`AI_WAIT assert attempt ${h} failed, retrying...`),h++,await V(d/10)}if(!u){let g=`AI wait still failing after ${d}ms.`;throw m&&(g+=` Latest result: ${m.message}`),new A("AssertionFailureError",g)}return u}case"AI_EXTRACT":{if(!e.goal.trim())throw new A("ActionFailureError","Cannot perform AI extraction without goal");let i=await this.browser.getHTML(),d=await this.generator.getTextExtraction({goal:e.goal,browserState:i,returnSchema:e.schema},o);if(d.result==="NOT_FOUND")throw new A("ActionFailureError","No relevant data found for extraction goal on this page");return{data:d.result,succeedImmediately:!1,urlAfterCommand:await this.browser.url()}}case"NAVIGATE":if(!Yr(e.url)&&!Xr(e.url,this.browser.baseURL))throw new A("ActionFailureError",`Invalid URL provided to navigate command: ${e.url}`);await this.browser.navigate({url:e.url});break;case"DIALOG":this.browser.registerDialogHandler(e.action);break;case"CAPTCHA":let n=await this.browser.solveCaptcha();n&&(await this.wrapElementTargetingCommand({description:"the captcha image solution input",cache:void 0,action:i=>this.browser.click(i),options:{useVision:e.useVision,disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector}}),await this.browser.type(n,{clearContent:!0,pressKeysSequentially:!1}));break;case"GO_BACK":await this.browser.goBack();break;case"GO_FORWARD":await this.browser.goForward();break;case"SCROLL_LEFT":case"SCROLL_RIGHT":case"SCROLL_DOWN":case"SCROLL_UP":let s;if(e.target&&(e.target.elementDescriptor.trim()||e.target.a11yData)){let{result:i,cache:d}=await this.wrapElementTargetingCommand({description:e.target.elementDescriptor,cache:e.cache?.target??e.target.a11yData,action:u=>this.browser.hover(u),options:{useVision:e.useVision,disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector}});s=i,d&&(e.cache={target:d})}switch(e.type){case"SCROLL_UP":await this.browser.scrollUp(e.deltaY);break;case"SCROLL_DOWN":await this.browser.scrollDown(e.deltaY);break;case"SCROLL_LEFT":await this.browser.scrollLeft(e.deltaX);break;case"SCROLL_RIGHT":await this.browser.scrollRight(e.deltaX);break}return{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:s};case"WAIT":await this.browser.wait(e.delay*1e3);break;case"REFRESH":await this.browser.refresh();break;case"CLICK":{let i=await this.browser.url(),d={useVision:e.useVision,disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector},{result:u,cache:m}=await this.wrapElementTargetingCommand({description:e.target.elementDescriptor,cache:e.cache?.target??e.target.a11yData,action:g=>this.browser.click(g,{doubleClick:e.doubleClick,rightClick:e.rightClick,force:e.force}),options:d});m&&(e.cache={target:m});let h={urlAfterCommand:await this.browser.url(),succeedImmediately:!1,elementInteracted:u};return Qe(i,h.urlAfterCommand)&&(h.succeedImmediately=!0,h.succeedImmediatelyReason="URL changed"),h}case"DRAG":{let{result:i,caches:d}=await this.wrapMultiElementTargetingCommand([e.fromTarget.elementDescriptor,e.toTarget.elementDescriptor],[e.cache?.fromTarget,e.cache?.toTarget],(u,m)=>this.browser.dragAndDrop(u,m,{force:e.force}),{useVision:e.useVision,filterByViewport:e.filterByViewport,useSelector:e.useSelector,disableCache:o});return d&&d.every(u=>u)&&(e.cache={fromTarget:d[0],toTarget:d[1]}),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:i}}case"MOUSE_DRAG":{let i;e.target?.elementDescriptor&&(i=(await this.wrapElementTargetingCommand({description:e.target.elementDescriptor,cache:e.cache?.target,action:async g=>g,options:{useVision:e.useVision,filterByViewport:e.filterByViewport,useSelector:e.useSelector,disableCache:o}})).result);let d=parseInt(e.deltaX),u=parseInt(e.deltaY);if(isNaN(d)||isNaN(u))throw new A("ActionFailureError",`Invalid pixel values passed to mouse drag command: (${e.deltaX}, ${e.deltaY})`);let m=await this.browser.mouseDrag(d,u,e.steps,i,{force:e.force});return{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:m}}case"SELECT_OPTION":{let{result:i,cache:d}=await this.wrapElementTargetingCommand({description:e.target.elementDescriptor,cache:e.cache?.target??e.target.a11yData,action:u=>this.browser.selectOption(u,e.option),options:{useVision:!1,disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector}});return d&&(e.cache={target:d}),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:i}}case"TAB":await this.browser.switchToPage(e.url);break;case"NEW_TAB":await this.browser.createNewTab(e.url);break;case"COOKIE":if(!e.value)break;await this.browser.setCookie(e.value);break;case"LOCAL_STORAGE":if(!e.value||!e.key)break;await this.browser.setLocalStorage(e.key,e.value);break;case"JAVASCRIPT":{let i;try{e.environment==="BROWSER"?i=await this.browser.executePageFunction(new Bt(e.fragment?`return ${e.code}`:e.code),void 0):i=await Xe({code:e.code,fragment:!!e.fragment,state:t.toObjectRef(),timeoutMs:e.timeout?e.timeout*1e3:void 0,logger:this.logger})}catch(d){throw new A("ActionFailureError",d instanceof Error?d.message:`${d}`,{cause:d})}try{JSON.stringify(i)}catch(d){throw new A("ActionFailureError",`Return value is not serializable: ${d instanceof Error?d.message:`${d}`}`,{cause:d})}return{urlAfterCommand:await this.browser.url(),succeedImmediately:!1,data:i}}case"TYPE":{let i=await this.browser.url(),d;if(e.target){let{result:m,cache:h}=await this.wrapElementTargetingCommand({description:e.target.elementDescriptor,cache:e.cache?.target??e.target.a11yData,action:g=>this.browser.typeIntoTarget(e.value,g,{force:e.force,clearContent:e.clearContent,pressKeysSequentially:e.pressKeysSequentially}),options:{useVision:e.useVision,disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector}});d=m,h&&(e.cache={target:h})}else await this.browser.type(e.value,{force:e.force,clearContent:e.clearContent,pressKeysSequentially:e.pressKeysSequentially});e.pressEnter&&await this.browser.press("Enter");let u={urlAfterCommand:await this.browser.url(),succeedImmediately:!1,elementInteracted:d};return Qe(i,u.urlAfterCommand)&&(u.succeedImmediately=!0,u.succeedImmediatelyReason="URL changed"),u}case"HOVER":{let{result:i,cache:d}=await this.wrapElementTargetingCommand({description:e.target.elementDescriptor,cache:e.cache?.target??e.target.a11yData,action:u=>this.browser.hover(u),options:{useVision:e.useVision,disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector}});return d&&(e.cache={target:d}),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:i}}case"FOCUS":{let{result:i,cache:d}=await this.wrapElementTargetingCommand({description:e.target.elementDescriptor,cache:e.cache?.target??e.target.a11yData,action:u=>this.browser.focus(u),options:{useVision:e.useVision,disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector}});return d&&(e.cache={target:d}),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:i}}case"BLUR":{let{result:i,cache:d}=await this.wrapElementTargetingCommand({description:e.target.elementDescriptor,cache:e.cache?.target??e.target.a11yData,action:u=>this.browser.blur(u),options:{useVision:e.useVision,disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector}});return d&&(e.cache={target:d}),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:i}}case"PRESS":let a=await this.browser.url();await this.browser.press(e.value);let c={urlAfterCommand:await this.browser.url(),succeedImmediately:!1};return Qe(a,c.urlAfterCommand)&&(c.succeedImmediately=!0,c.succeedImmediatelyReason="URL changed"),c;case"REQUEST":{let i=e.timeout??30,d=null,u=new AbortController,m=Object.fromEntries(Object.entries(e.headers||{}).filter(([v,T])=>v&&T)),h=new URLSearchParams;Object.entries(e.params||{}).filter(([v,T])=>v&&T).forEach(([v,T])=>{h.append(v,T)});let g;if(Yr(e.url)&&(g=e.url),Xr(e.url,this.browser.baseURL)&&(g=new URL(e.url,this.browser.baseURL).toString()),!g)throw new A("ActionFailureError",`Invalid URL: ${e.url}`);let p=async()=>{try{d=await fetch(`${g}?${h.toString()}`,{headers:m,method:e.method,body:e.body,signal:u.signal})}catch(v){this.logger.error({err:v},"Failed to make fetch request")}},S=async()=>{await new Promise(v=>setTimeout(v,i*1e3)),u.abort()};await Promise.race([S(),p()]);let y=d;if(!y)throw new A("ActionFailureError",`Fetch request timed out after ${i} seconds`);if(!y.ok)throw new A("ActionFailureError",`Fetch request failed with status ${y.status}`);let w={};y.headers.forEach((v,T)=>{w[T]=v});let b={status:y.status,headers:w};return y.headers.get("content-type")?.includes("json")?b.json=await y.json():y.headers.get("content-type")?.includes("text")&&(b.text=await y.text()),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),data:b}}case"VISUAL_DIFF":{if(!e.screenshot)throw new A("ActionFailureError","Cannot execute visual diff without saved screenshot");await this.browser.removeAllHighlights();let i=await this.screenshotWithDimensions({target:e.target?.a11yData,hideCaret:!0});if(i.height!==e.screenshot.height||i.width!==e.screenshot.width)throw new A("ActionFailureError","Current screenshot does not match saved screenshot dimensions - did you change the size of the target or the viewport?");let d={data:Buffer.alloc(i.width*i.height*4),width:i.width,height:i.height},u;if(e.screenshot.data.startsWith("https://")){let p=await fetch(e.screenshot.data);u=Buffer.from(await p.arrayBuffer())}else u=Buffer.from(e.screenshot.data,"base64");let h=vl(co.decode(u).data,co.decode(i.buffer).data,d.data,i.width,i.height,{threshold:e.threshold,diffColorAlt:[0,255,0]})/(i.width*i.height)*100,g=h>e.threshold;return{fail:g,thoughts:g?`Visual diff of ${h.toFixed(2)}% detected`:void 0,beforeScreenshotOverride:i.buffer,afterScreenshotOverride:co.encode(d,75).data,succeedImmediately:!1,urlAfterCommand:await this.browser.url()}}default:return(i=>{throw"If Typescript complains about the line below, you missed a case or break in the switch above"})(e)}return{succeedImmediately:!1,urlAfterCommand:await this.browser.url()}}async getReverseMappedTarget(e,t,o){return(await this.generator.getReverseMappedDescription({browserState:e.browserState,goal:`${t}`},o)).phrase}stopRecordMode(){this.recordAbortController?.abort()}async startRecordMode(e){this.recordAbortController=new AbortController;let t=new mt({signal:this.recordAbortController.signal,...e});await this.browser.startRecording(this.recordAbortController.signal,t)}};async function Ws({socket:r,generator:e,storage:t,logger:o,rootState:n,localApp:s,devicePixelRatio:a}){let c=o.child({package:"web-agent"}),l=!0;s==="iframe"&&(l=!1);let i=r.handshake.query.testId,d=r.handshake.query.baseUrl,u=r.handshake.query.viewport?JSON.parse(decodeURIComponent(r.handshake.query.viewport)):void 0;if(!i||!d)throw new Error("Socket connection request is missing testId or baseUrl");let m=await t.getOrgId(i),h=r.handshake.query.environmentName,g=h?await t.fetchEnvironment(m,h,o):void 0,p=El(),S=x=>{l&&r.emit("screenshot",{...x,url:d})},y=n?.controller;if(y)y.browser.setActiveFrame(Kt),y.browser.baseURL=d,await y.resetState({clearCookies:!0,clearStorage:!0,timeout:null}),y.setOpen(),n?.context.resetVariables(d,g?.variables);else{let x={};u&&(x.viewport=u),s==="web"&&a&&(x.deviceScaleFactor=a);let U=await _.init({baseUrl:d,logger:c,takeScreenshots:l,onScreenshot:S,contextArgs:x});y=new Re({browser:U,generator:e,config:tt,logger:c})}let w;l&&(w=Rn(r,p,o));let b=async()=>{clearInterval(w.timer)},v=new ce(d,g?.variables);r.emit("testContext",{state:v.toRedactedDisplayCopy()}),R.registerSession({controller:y,context:v,sessionId:p,cleanup:b});let T=_.USER_AGENT;return r.emit("session",{url:d,userAgent:T,viewport:await y.browser.getViewport(),localApp:s}),{sessionId:p,testId:i,orgId:m}}var js=[Un,Us,Bs,$s,Vs,In,Hs,Mn,Dn,Nn,_n,Pn,kn,Fn,On,xn,zn,Ln];var lo=r=>{let{logger:e}=r,t=new Tl(r.baseServer,{cors:{origin:"*",methods:["GET","POST"]},maxHttpBufferSize:1e7});return t.on("connection",async o=>{let n;try{n=await Ws({...r,socket:o})}catch(s){e.error({event:"connection",type:"websocket",err:s},"Failed to setup connection"),o.emit("error",{message:s instanceof Error?s.message:`${s}`});return}js.forEach(s=>Al(s,{socket:o,metadata:n,...r}))}),t},Al=(r,e)=>{let t=r.createHandler(e),o=(...n)=>{r.event!=="cloudMouseMove"&&e.logger.debug({event:r.event,metadata:e.metadata,args:n},"Websocket event");let s=a=>{e.logger.error({event:r.event,type:"websocket",args:n,err:a instanceof Error?a:new Error(`${a}`)},"Unhandled exception in socket handler"),e.socket.emit("error",{message:a instanceof Error?a.message:`${a}`})};try{let a=t.apply(void 0,n);a&&typeof a.catch=="function"&&a.catch(s)}catch(a){s(a)}};e.socket.on(r.event,o)};import{z as xl}from"zod";import Rl from"fetch-retry";var Il=Rl(global.fetch),ie=class{static API_VERSION="v1";baseURL;apiKey;constructor(e){this.baseURL=e.baseURL,this.apiKey=e.apiKey}async sendRequest(e,t){let o=await Il(`${this.baseURL}${e}`,{retries:1,retryDelay:1e3,method:"POST",body:JSON.stringify(t),headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`}});if(!o.ok)throw new Error(`Request to ${e} failed with status ${o.status}: ${await o.text()}`);return o.json()}};var rt=class extends ie{constructor(e){super(e)}async getScreenshotFromS3(e){let t=await this.sendRequest(`/${ie.API_VERSION}/s3/visual-diff-screenshot`,{url:e});return xl.string().parse(t)}async getElementLocation(e,t){let o=await this.sendRequest(`/${ie.API_VERSION}/web-agent/locate-element`,{browserState:e.browserState,goal:e.goal,disableCache:t});return Br.parse(o)}async getElementLocationWithVision(e,t){let o=await this.sendRequest(`/${ie.API_VERSION}/web-agent/visual-locate`,{goal:e.goal,screenshot:e.screenshot?.toString("base64"),hintActivatedScreenshot:e.hintActivatedScreenshot?.toString("base64"),disableCache:t});return Br.parse(o)}async getAssertionResult(e,t,o){if(t){let s=await this.sendRequest(`/${ie.API_VERSION}/web-agent/assertion`,{url:e.url,goal:e.goal,screenshot:e.screenshot?.toString("base64"),disableCache:o,vision:!0});return $r.parse(s)}let n=await this.sendRequest(`/${ie.API_VERSION}/web-agent/assertion`,{url:e.url,browserState:e.browserState,goal:e.goal,history:e.history,numPrevious:e.numPrevious,lastCommand:e.lastCommand,disableCache:o,vision:!1});return $r.parse(n)}async getProposedCommand(e,t){let o=await this.sendRequest(`/${ie.API_VERSION}/web-agent/next-command`,{url:e.url,browserState:e.browserState,goal:e.goal,history:e.history,numPrevious:e.numPrevious,lastCommand:e.lastCommand,disableCache:t});return mn.parse(o)}async getGranularGoals(e,t){let o=await this.sendRequest(`/${ie.API_VERSION}/web-agent/split-goal`,{url:e.url,goal:e.goal,disableCache:t});return pn.parse(o)}async getReverseMappedDescription(e,t){let o=await this.sendRequest(`/${ie.API_VERSION}/web-agent/reverse-mapped-description`,{goal:e.goal,browserState:e.browserState,disableCache:t});return hn.parse(o)}async getTextExtraction(e,t){let o={goal:e.goal,browserState:e.browserState,returnSchema:e.returnSchema,disableCache:t},n=await this.sendRequest(`/${ie.API_VERSION}/web-agent/text-extraction`,o);return Cr.parse(n)}};import{existsSync as Ol,readFileSync as Ll,writeFileSync as uo}from"fs";import mo from"path";import{parse as Ml,stringify as Nl}from"yaml";var X=process.env.MOMENTIC_DIR??Me;function Gs(r,e){let t=an(r);for(let[s,a]of Object.entries(t.modules)){let c=qe(s);uo(mo.join(X,Ne,`${c}.yaml`),a,"utf-8")}let o=qe(e),n=mo.join(X,`${o}.yaml`);return uo(n,t.test,"utf-8"),`${o}.yaml`}function Ks(r,e){if(!Ol(e))throw new Error(`Test not found at path: ${mo}`);let t=Ll(e,"utf-8"),n={...Ml(t),...r},s=Nl(n);uo(e,s,"utf-8")}import{readFileSync as cr,readdirSync as qs,writeFileSync as Ys}from"fs";import{join as po}from"path";import{v4 as Pl}from"uuid";import{parse as lr,stringify as Xs}from"yaml";var ot=po(X,Ne);function Js(r){let e=tr(ot,r.moduleId).path,t=cr(e,"utf-8"),n={...lr(t),...r},s=Xs(n);Ys(e,s,"utf-8")}function Qs(r,e){let t=qe(r),o=po(ot,`${t}.yaml`),n=Pl(),{stepsToSave:s}=ee({steps:e,moduleId:n,testId:"",orgId:""}),a={fileType:"momentic/module",schemaVersion:j,moduleId:n,name:r,steps:s},c=Xs(a);return Ys(o,c,"utf-8"),a}function Zs(){let r=[];for(let e in qs(ot)){if(!e.endsWith(".yaml"))continue;let t=cr(e,"utf-8"),o=lr(t),n={name:o.name,moduleId:o.moduleId,numSteps:o.steps.length};r.push(n)}return r}function ei(){let r=[];for(let e of qs(ot)){if(!e.endsWith(".yaml"))continue;let t=po(ot,e),o=cr(t,"utf-8"),n=lr(o);try{let s=Et.parse(n);r.push(s)}catch(s){B.warn({err:s},"Error parsing module, skipping...")}}return r}function ti(r){let e=tr(Ne,r).path,t=cr(e,"utf-8"),o=lr(t);return Et.parse(o)}import{Router as zl}from"express";import{existsSync as Dl,readFileSync as _l,readdirSync as kl}from"fs";import{join as ri}from"path";import{parse as Fl}from"yaml";var ho=ri(X,vt);function oi(){let r=[];if(!Dl(ho))return[];for(let e of kl(ho)){if(!e.endsWith(".yaml"))continue;let t=ri(ho,e),o=_l(t,"utf-8"),n=Fl(o);try{let s=ne.parse(n);r.push(s)}catch(s){B.warn({err:s},"Error parsing environment, skipping...")}}return r}function de(r){return function(...e){let t=e[e.length-1],o=r(...e);Promise.resolve(o).catch(t)}}var ni=zl();ni.get("/",de((r,e)=>{let t=oi();e.status(200).json(t)}));var si=ni;import{Router as Ul}from"express";var xt=Ul();xt.get("/",de((r,e)=>{let t=ei();e.status(200).json(t)}));xt.get("/metadata",de((r,e)=>{let t=Zs();e.status(200).json(t)}));xt.post("/",de(async(r,e)=>{let t;try{t=dn.parse(r.body)}catch(n){e.status(400).json({error:`Invalid request body: ${n}`});return}try{Ct(t.name)}catch(n){e.status(400).json({error:`Invalid module name: ${n}`});return}let o=Qs(t.name,t.steps);e.status(201).json(o)}));xt.get("/:moduleId",de((r,e)=>{if(!r.params.moduleId){e.status(400).json({error:"Missing moduleId in url path."});return}let t=ti(r.params.moduleId);e.json(t)}));var ii=xt;import{Router as jl}from"express";import{existsSync as Gl}from"fs";import{join as mi}from"path";import{v4 as Kl}from"uuid";var fo="https://api.momentic.ai",ai=r=>{fo=r},Ot=()=>fo,Hl,ci=r=>{Hl=r};var dr,go,li=async r=>{if(dr)return;let e=new K({baseURL:fo,apiKey:r});try{dr=await e.getOrgId(),go=r}catch(t){throw new Error(`Error checking API key against server: ${t}`)}},ur=()=>{if(!dr)throw new Error("Your organization ID appears invalid. Please contact Momentic Support.");return dr},di=()=>{if(!go)throw new Error("Your API key appears invalid. Please contact Momentic Support.");return go};import ui,{multistream as $l}from"pino";import Bl from"pino-pretty";var yo=new Map,Vl=process.env.NODE_ENV==="production",So=class r{consoleLogger;ddClientToken;hostname;bindingAttributes;site="https://http-intake.logs.us5.datadoghq.com/api/v2/logs";constructor({bindings:e,clientToken:t,hostname:o}){this.ddClientToken=t,this.hostname=o,this.bindingAttributes={...e,env:process.env.NODE_ENV};let n={base:this.bindingAttributes,errorKey:"err",level:"debug"};this.consoleLogger=Vl?ui(n):ui(n,$l([{stream:Bl({colorize:!0})}]))}child(e){return new r({clientToken:this.ddClientToken,bindings:{...this.bindingAttributes,...e},hostname:this.hostname})}flush(e){this.consoleLogger.flush(e)}log(e,t,o,...n){t&&o===void 0&&(o=`${t}`,t={}),this.consoleLogger[e](t,o,...n);let s=Object.assign({},this.bindingAttributes,t&&typeof t=="object"?t:{});n.length>0&&(s.args=n),(async()=>{try{let a=await fetch(this.site,{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json","DD-API-KEY":this.ddClientToken},body:JSON.stringify([{ddsource:this.bindingAttributes.app,hostname:this.hostname??"vercel",service:"momentic",message:{message:o||"",...s,level:e}}])});if(!a.ok)throw new Error(`Failed to log to Datadog: ${a.statusText})}`)}catch(a){this.consoleLogger.warn({obj:t,msg:o,args:n,err:a},"Failed to log to Datadog")}})()}debug(e,t,...o){this.log("debug",e,t,...o)}info(e,t,...o){this.log("info",e,t,...o)}warn(e,t,...o){this.log("warn",e,t,...o)}error(e,t,...o){this.log("error",e,t,...o)}bindings(){return this.bindingAttributes}setMinLevel(e){this.consoleLogger.level=e}},mr=({app:r,clientToken:e,hostname:t})=>{if(!process.env.DD_CLIENT_TOKEN&&!process.env.NEXT_PUBLIC_DD_CLIENT_TOKEN&&!e)throw new Error("Missing DD_CLIENT_TOKEN");return yo.has(r)||yo.set(r,new So({bindings:{app:r},hostname:t,clientToken:e||process.env.NEXT_PUBLIC_DD_CLIENT_TOKEN||process.env.DD_CLIENT_TOKEN})),yo.get(r)};import{hostname as Wl}from"os";var ue=mr({app:"desktop-server",clientToken:"pubcfd7516a5c0ba852b42675cd97bee027",hostname:Wl()});var Lt=jl();Lt.get("/",de((r,e)=>{let t=At(X,ue);e.status(200).json(t)}));Lt.post("/",de((r,e)=>{let t;try{t=ln.parse(r.body)}catch(c){e.status(400).json({error:`Invalid request body: ${c}`});return}try{Ct(t.name)}catch(c){e.status(400).json({error:c.message});return}let n={id:Kl(),name:t.name,baseUrl:t.baseUrl,schemaVersion:j,advanced:{disableAICaching:!1,viewport:t.viewport??We},retries:0,steps:[]};t.environment&&(n.envs=[{name:t.environment,defaultOnLocal:!0}]);let s=Gs(n,t.name),a={...n,testPath:s};e.status(201).json(a)}));Lt.get("/:testPath",de(async(r,e)=>{if(!r.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let t=mi(X,r.params.testPath);if(!Gl(t)){e.status(400).json({error:`Test not found at path: ${t}`});return}let o=await rr(t,ot);e.status(200).json(o)}));Lt.patch("/:testPath",de(async(r,e)=>{if(!r.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let t;try{t=cn.parse(r.body)}catch(i){e.status(400).json({error:`Invalid request body: ${i}`});return}let o=mi(X,r.params.testPath),n;try{n=Jn(o)}catch(i){e.status(400).json({error:`Existing test file on disk is invalid: ${i}`});return}let{stepsToSave:s,moduleUpdates:a,cachesToSave:c}=ee({steps:t.steps,testId:n.id,orgId:ur()});a.forEach(i=>Js(i)),Ks({steps:s},o),await new K({apiKey:di(),baseURL:Ot()}).updateStepCaches({entries:c,testId:n.id}),e.status(201).json({message:"ok"})}));var pi=Lt;async function hi({momenticServerUrl:r,apiKey:e,mode:t,serverPort:o,appPort:n,staticDir:s,assetsDir:a,devicePixelRatio:c}){if(!Xl(X)||!Jl(X).isDirectory()){let p=ed.isAbsolute(X);throw new Error(`Root folder ${X} does not exist${p?"":` in the current directory ("${process.cwd()}")`}. Please run \`npx momentic init\` if you wish to setup Momentic here for the first time.`)}r&&ai(r),B.info("Checking API key"),await li(e);let i=`http://localhost:${o}`;n&&(i=`http://localhost:${n}`);let d=rd(s);await new Promise(p=>{d.listen(o,()=>{B.info(`Server is running at http://localhost:${o}`),p()})});let m=new rt({baseURL:Ot(),apiKey:e}),h=new ut(new K({baseURL:Ot(),apiKey:e}),ur());if(t==="web"){lo({localApp:"web",baseServer:d,generator:m,storage:h,logger:ue,devicePixelRatio:c}),await Zl(i);return}let g=await od({appUrl:i,apiGenerator:m,extensionPath:td(a,"extension"),onClose:async()=>{B.info("Browser closed, closing app."),await g.browser.cleanup(),d.close(()=>{process.exit(0)})}});lo({localApp:"iframe",baseServer:d,generator:m,storage:h,logger:ue,rootState:{controller:g,context:new ce(""),executionCancelled:!1}}),process.on("uncaughtException",p=>{ue.error({err:p},"Uncaught exception in desktop-server")}),process.on("unhandledRejection",p=>{ue.error({err:p},"Unhandled rejection in desktop-server")})}function rd(r){let e=wo();e.use(Yl()),e.use(ql.json({limit:"50mb"}));let t=wo.Router();if(t.use("/tests",pi),t.use("/modules",ii),t.use("/environments",si),e.use("/api",t),e.use((n,s,a)=>{n.path!=="/healthcheck"&&ue.debug({url:n.url,path:n.path,query:n.query,method:n.method,body:n.body,headers:n.rawHeaders,client:n.ip},"Received desktop-server request"),s.on("close",()=>{s.statusCode>=400&&ue.error({url:n.url,method:n.method,statusCode:s.statusCode},"Request completed in error")}),a()}),e.use((n,s,a,c)=>{n instanceof Error&&n.message.includes("BadRequestError: request aborted")||(ue.error({stack:n.stack,msg:n.message,url:s.url,method:s.method},"Unhandled exception leading to 500 on desktop-server"),a.status(500).send("Internal Server Error"),c(n))}),r){let n=wo.static(r);e.use("/",n),e.use("*",n)}return Ql.createServer(e)}async function od({appUrl:r,apiGenerator:e,extensionPath:t,onClose:o}){let n=await _.init({baseUrl:r,logger:ue,browserArgs:{headless:!1,handleSIGTERM:!0},contextArgs:{bypassCSP:!0,viewport:null},waitForLoad:!0,localMode:!0,localAppUrl:r,extensionPath:t,skipPageSetup:!0,timeout:null,onClose:o}),s=new Re({browser:n,config:tt,generator:e,logger:ue});return ci(s),s}import{existsSync as zd}from"fs";import{platform as Ud}from"os";import Vi,{dirname as Hd}from"path";import{fileURLToPath as $d}from"url";var gi="0.0.63";import{hostname as sd}from"os";var C=mr({app:"cli",clientToken:"pub7eb923f18fb3f1d42ac5eba8c5ea13a5",hostname:sd()});import{createHash as wi}from"crypto";import{existsSync as bi,mkdirSync as md,readFileSync as To,writeFileSync as vi}from"fs";import{join as Ci}from"path";import{parse as pd}from"yaml";import{existsSync as bo,mkdirSync as cd,readdirSync as yi,statSync as vo}from"fs";import{homedir as Si}from"os";import{join as Mt,resolve as ld}from"path";import id from"chalk";import ad from"readline/promises";async function he(r,e){if(process.env.CI)return!0;C.flush(),await new Promise(s=>setTimeout(s,500));let t=ad.createInterface({input:process.stdin,output:process.stdout});r=`${r} (y/N) `;let o=e?id.bold.yellow(r):r,n=await t.question(o);return t.close(),n.toLowerCase()==="y"}var Co=Me,nt=Mt(Me,Ne),Eo=Mt(Me,Nr),ve=Mt(Me,vt),dd=Mt(Si(),"momentic",Pr),ud=[dd,nt,Eo,ve];function fi(r){try{return bo(r)&&vo(r).isDirectory()}catch(e){return C.error({err:e},`Error reading path ${r} during directory exists check`),!1}}async function ke(r,e=!1){let t=await r.getOrgId();Si()||(C.error("Your home directory could not be found. Please ensure the HOME environment variable is set for POSIX systems, and the USERPROFILE environment variable is set for Windows systems."),process.exit(1)),fi(Co)||!e&&!await he(`A '${Me}' folder was not found in the current directory. Proceed with creation here? You will need to invoke the Momentic CLI from this directory in the future.`)&&(C.error("Setup cancelled"),process.exit(1));for(let o of ud)fi(o)||(!e&&!await he(`'${o}' folder was not found in the current directory. Create it now?`)&&(C.error("Setup cancelled"),process.exit(1)),cd(o,{recursive:!0}));return yi(ve).length===0&&(!e&&!await he("You do not appear to have any environments configured locally. Pull environments from Momentic Cloud?")&&(C.error("Setup cancelled"),process.exit(1)),await pr({client:r,all:!0,skipPrompts:e})),C.info("CLI initialization complete"),t}function Nt(r,e,t=new Set){for(let o of r){let n=ld(o),s=!1;try{s=bo(n)&&vo(n).isDirectory()}catch(a){C.error({err:a},`Error reading path ${n} during collect paths`)}if(n&&s){let a=yi(n).map(c=>Mt(n,c));Nt(a,e,t);continue}if(n.endsWith(".yaml")){try{if(!bo(n)||!vo(n).isFile()){C.error(`File not found or unreadable: ${n}`);continue}}catch(a){C.error({err:a},`Error reading file ${n} during collect paths`);continue}if(!e(n))continue;t.add(n)}}return t}async function pr({envNames:r,client:e,all:t,skipPrompts:o}){let n=await e.getAllEnvironments();r&&!t&&(n=n.filter(c=>r?.includes(c.name))),bi(ve)||md(ve);let s=0,a=0;for(let c of n){let l=Ci(ve,`${c.name}.yaml`);if(!bi(l))vi(l,kr(c)),s++;else{let i=wi("sha256").update(To(l)).digest("hex"),d=kr(c),u=wi("sha256").update(d).digest("hex");if(i!==u){if(!o&&!await he(`Environment ${c.name} already exists on disk but needs to be updated. Overwrite?`)){C.error("Pull cancelled");return}vi(l,d),a++}}}C.info(`Pulled ${s} new environments and updated ${a} existing environments`)}function hd(r){return r.endsWith(".yaml")?To(r,"utf8").includes("momentic/environment")?!0:(C.warn(`Skipping YAML that is not a Momentic environment: ${r}`),!1):!1}async function Ei({client:r,names:e,all:t,yes:o}){let n;t?n=[ve]:n=e.map(c=>Ci(ve,`${c.toLowerCase()}.yaml`));let s=Nt(n,hd);C.info(`Found ${s.size} environments(s) to push:`),s.forEach(c=>C.info(` - ${c}`)),C.info("Loading file contents");let a=[];for(let c of s){let l=pd(To(c,"utf-8"));try{let i=ne.parse(l);a.push(i)}catch(i){C.error({err:i},`${c} failed to parse as a valid environment file`),process.exit(1)}}if(!o&&!await he("Pushing environments can overwrite variables on cloud and instantly affect scheduled runs. Continue?",!0)){C.error("Push cancelled");return}C.info(`Pushing ${s.size} environment(s)`),await r.updateEnvironments(a),C.info("Push successful!")}import{registry as Ao}from"playwright-core/lib/server";function gd(r){let e=[],t=[];for(let o of r){let n=Ao.findExecutable(o);!n||n.installType==="none"?e.push(o):t.push(n)}return t}async function Ti(){let r=gd(["chromium"]);await Ao.installDeps(r,!1),await Ao.install(r,!1)}import{Argument as hr,Option as Fe}from"commander";var ze=new Fe("--api-key <key>","API key for authentication. If not supplied, attempts to read the MOMENTIC_API_KEY env var.").env("MOMENTIC_API_KEY").makeOptionMandatory(!0),Ue=new Fe("--server <server>","Momentic server to use. Leave unchanged unless using Momentic on-premise.").default("https://api.momentic.ai"),He=new Fe("-y, --yes","Skip confirmation prompts.").env("CI").default(!0),Ai=new Fe("--no-report","Skip reporting test results to Momentic Cloud when running with the --local flag."),Ri=new Fe("--pixel-ratio <number>","Device pixel ratio of your screen or monitor. Mac OS Retina displays and machines marketed as 'HiDPI' should usually set this to 2.").default(1).argParser(r=>parseInt(r,10)),Ii=new Fe("--env <env>","Name of the environment to use when running tests."),Ro=new Fe("-a, --all","Select all tests in your organization from Momentic Cloud. Cannot be used together with <tests> arguments."),Io=new Fe("-a, --all","Select all environments in your organization from Momentic Cloud. Cannot be used together with <paths> arguments."),xi=new hr("<tests...>",`One or more test paths to pull from Momentic Cloud.
101
101
 
102
102
  A test path is a lowercased version of your test name where spaces are replaced with underscores: 'npx momentic pull hello-world'.`).argOptional(),Oi=new hr("<tests...>",`One or more test identifiers.
103
103
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "momentic",
3
- "version": "0.0.62",
3
+ "version": "0.0.63",
4
4
  "description": "The Momentic SDK for Node.js",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",