momentic 0.0.53 → 0.0.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bin/cli.js +2 -2
  2. package/package.json +1 -1
package/bin/cli.js CHANGED
@@ -27,7 +27,7 @@ You have already executed the following commands successfully (most recent liste
27
27
  `).forEach(c=>n.push(` ${c}`))):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(`
28
28
  `)}getListHistory(){return gl`Here are the commands that you have successfully executed:
29
29
  ${this.commandHistory.filter(e=>e.type==="AI_ACTION").map(e=>`- ${e.serializedCommand}`).join(`
30
- `)}`}async executeCommand(e,t,o,n=!1){let s=this.commandHistory[this.commandHistory.length-1];if(!n&&(!s||s.state!=="PENDING"))throw new T("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 l=Date.now(),a=await this.executePresetStep(e,t,o),c=Date.now()-l;return this.logger.debug({result:a,duration:c},"Got execution result"),a.succeedImmediately&&!n&&(this.pendingInstructions.pop(),this.pendingInstructions.length>0&&(a.succeedImmediately=!1)),a.elementInteracted&&"target"in e&&e.target&&!e.target.elementDescriptor&&(e.target.elementDescriptor=a.elementInteracted.trim()),n||(s.generatedStep=e,s.serializedCommand=Ce(e),s.state="DONE"),a}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 a=this.getSerializedHistory(o,t);n={goal:e.assertion,url:o,browserState:t,history:a,lastCommand:this.lastExecutedCommand,numPrevious:this.commandHistory.length}}let l=await this.generator.getAssertionResult(n,e.useVision,e.disableCache);if(l.relevantElements&&Promise.all(Array.from(new Set(l.relevantElements)).slice(0,5).map(a=>this.browser.highlight({id:a}))),!l.result)throw new T("AssertionFailureError",l.thoughts);return{succeedImmediately:!1,thoughts:l.thoughts,urlAfterCommand:o,beforeScreenshotOverride:s}}async wrapMultiElementTargetingCommand(e,t,o,n,s=1){let l=await Promise.all(e.map((a,c)=>this.wrapElementTargetingCommand(a,t[c],async i=>i,n)));try{return{result:await o(...l.map(c=>c.result)),caches:l.map(c=>c.cache)}}catch(a){if(s>0)return this.logger.warn({err:a},"Failed to execute action with multiple cached targets, retrying with AI"),this.wrapMultiElementTargetingCommand(e,e.map(()=>{}),o,n,s-1);throw new T("ActionFailureError","",{cause:a})}}async wrapElementTargetingCommand(e,t,o,n){let{useVision:s,disableCache:l,filterByViewport:a,useSelector:c}=n,i=n.retriesWithAI??1;if(!t&&!e)throw new T("ActionFailureError","Cannot target element with no cached data or element descriptor");if(c)return{result:await o({id:-1,selector:e})};let d=!!t&&Tt(t);t||(this.logger.info("No cached locator data for target, prompting AI for fresh location"),i--,t=De.parse(await this.locateElement({description:e,useVision:s,disableCache:l,filterByViewport:a})));try{let u=await o(t);return d?this.logger.info({cache:t},"Successfully used cached target to perform action"):this.logger.debug({cache:t},"Successfully generated and used new a11y target information"),{result:u,cache:t}}catch(u){if(i>0&&e)return this.logger.warn({err:u,cache:t},"Failed to execute action with cached target, retrying with AI"),this.wrapElementTargetingCommand(e,void 0,o,{...n,retriesWithAI:i});if(u instanceof T)throw u;let p=`Failed to find ${e?`${e}`:"element"}: ${u instanceof Error?u.message:u}`;throw this.logger.error({err:u,cache:t},p),new T("ActionFailureError",p,{cause:u})}}async screenshotWithDimensions(e){await this.browser.removeAllHighlights();let t=await this.browser.screenshot(e),o=fl(t);return{buffer:t,...o}}async executePresetStep(e,t,o){let n;try{n=await this.resolveCommandTemplateStrings(e,t)}catch(s){throw new T("ActionFailureError",s.message,{cause:s})}try{let s=this.browser.getOpenPages(),l=await this.browser.url(),a=await this.executePresetStepHelper(e,t,o),c=!0;e.type==="NAVIGATE"&&(c=!1);let i=this.browser.getOpenPages();if(c&&i.length!==s.length)for(let d=i.length-1;d>=0;d--){let u=i[d];if(u!==l){await this.browser.switchToPage(u,d);break}}return a}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))Jr(e,o,n)}async resolveCommandTemplateStrings(e,t,o="",n={}){let s=["type","a11yData","thoughts"];for(let l in e){if(s.includes(l))continue;let a=e[l],c=o?`${o}.${l}`:l;if(typeof a=="string"&&a.includes("{{")){let i=await _r({s:a,state:t.toObjectRef(),logger:this.logger});if(a===i)continue;n[c]=a,e[l]=i}else typeof a=="object"&&a!==null&&!Array.isArray(a)&&await this.resolveCommandTemplateStrings(a,t,c,n)}return n}async executePresetStepHelper(e,t,o){switch(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 T("ActionFailureError","Missing assertion");return this.executeAssertion(e)}case"AI_WAIT":{if(!e.assertion.trim())throw new T("ActionFailureError","Missing assertion");let i=Date.now();if(e.timeout&&e.timeout>30)throw new T("AssertionFailureError",`AI wait timeout of ${e.timeout} exceeds the maximum allowed timeout of 30s`);let d=(e.timeout??10)*1e3,u,p,h=0;for(;Date.now()-i<d;)try{u=await this.executeAssertion({...e,type:"AI_ASSERTION"});break}catch(g){p=g instanceof Error?g:new Error(`${g}`),this.logger.warn({err:g},`AI_WAIT assert attempt ${h} failed, retrying...`),h++,await H(d/10)}if(!u){let g=`AI wait still failing after ${d}ms.`;throw p&&(g+=` Latest result: ${p.message}`),new T("AssertionFailureError",g)}return u}case"AI_EXTRACT":{if(!e.goal.trim())throw new T("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},e.disableCache);if(d.result==="NOT_FOUND")throw new T("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(!$r(e.url)&&!Br(e.url,this.browser.baseURL))throw new T("ActionFailureError",`Invalid URL provided to navigate command: ${e.url}`);await this.browser.navigate({url:e.url});break;case"CAPTCHA":let n=await this.browser.solveCaptcha();n&&(await this.wrapElementTargetingCommand("the captcha image solution input",void 0,i=>this.browser.click(i),{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(e.target.elementDescriptor,e.cache?.target??e.target.a11yData,u=>this.browser.hover(u),{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:p}=await this.wrapElementTargetingCommand(e.target.elementDescriptor,e.cache?.target??e.target.a11yData,g=>this.browser.click(g,{doubleClick:e.doubleClick,rightClick:e.rightClick,force:e.force}),d);p&&(e.cache={target:p});let h={urlAfterCommand:await this.browser.url(),succeedImmediately:!1,elementInteracted:u};return Ye(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,p)=>this.browser.dragAndDrop(u,p,{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"SELECT_OPTION":{let{result:i,cache:d}=await this.wrapElementTargetingCommand(e.target.elementDescriptor,e.cache?.target??e.target.a11yData,u=>this.browser.selectOption(u,e.option),{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"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{i=await Ke({code:e.code,fragment:!!e.fragment,state:t.toObjectRef(),timeoutMs:e.timeout?e.timeout*1e3:void 0,logger:this.logger})}catch(d){throw new T("ActionFailureError",d instanceof Error?d.message:`${d}`,{cause:d})}try{i=Sl(i)}catch(d){throw new T("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:p,cache:h}=await this.wrapElementTargetingCommand(e.target.elementDescriptor,e.cache?.target??e.target.a11yData,g=>this.browser.typeIntoTarget(e.value,g,{force:e.force,clearContent:e.clearContent,pressKeysSequentially:e.pressKeysSequentially}),{useVision:e.useVision,disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector});d=p,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 Ye(i,u.urlAfterCommand)&&(u.succeedImmediately=!0,u.succeedImmediatelyReason="URL changed"),u}case"HOVER":{let{result:i,cache:d}=await this.wrapElementTargetingCommand(e.target.elementDescriptor,e.cache?.target??e.target.a11yData,u=>this.browser.hover(u),{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 l=await this.browser.url();await this.browser.press(e.value);let a={urlAfterCommand:await this.browser.url(),succeedImmediately:!1};return Ye(l,a.urlAfterCommand)&&(a.succeedImmediately=!0,a.succeedImmediatelyReason="URL changed"),a;case"REQUEST":{let i=e.timeout??30,d=null,u=new AbortController,p=Object.fromEntries(Object.entries(e.headers||{}).filter(([b,C])=>b&&C)),h=new URLSearchParams;Object.entries(e.params||{}).filter(([b,C])=>b&&C).forEach(([b,C])=>{h.append(b,C)});let g;if($r(e.url)&&(g=e.url),Br(e.url,this.browser.baseURL)&&(g=new URL(e.url,this.browser.baseURL).toString()),!g)throw new T("ActionFailureError",`Invalid URL: ${e.url}`);let y=async()=>{try{d=await fetch(`${g}?${h.toString()}`,{headers:p,method:e.method,body:e.body,signal:u.signal})}catch(b){this.logger.error({err:b},"Failed to make fetch request")}},w=async()=>{await new Promise(b=>setTimeout(b,i*1e3)),u.abort()};await Promise.race([w(),y()]);let v=d;if(!v)throw new T("ActionFailureError",`Fetch request timed out after ${i} seconds`);if(!v.ok)throw new T("ActionFailureError",`Fetch request failed with status ${v.status}`);let f={};v.headers.forEach((b,C)=>{f[C]=b});let S={status:v.status,headers:f};return v.headers.get("content-type")?.includes("json")?S.json=await v.json():v.headers.get("content-type")?.includes("text")&&(S.text=await v.text()),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),data:S}}case"VISUAL_DIFF":{if(!e.screenshot)throw new T("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 T("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 y=await fetch(e.screenshot.data);u=Buffer.from(await y.arrayBuffer())}else u=Buffer.from(e.screenshot.data,"base64");let h=wl(Qr.decode(u).data,Qr.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:Qr.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}async toggleRecordMode({action:e,indices:t,onStepRecord:o}){if(e==="STOP"){this.recordAbortController?.abort();return}if(!t||!o)throw new Error("Indices or step recording callback not provided to toggleRecordMode");this.recordAbortController=new AbortController;let n=new Jt({signal:this.recordAbortController.signal,initialIndices:t,generator:this.generator,onStepRecord:o});await this.browser.startRecording(this.recordAbortController.signal,n)}};async function cs({socket:r,generator:e,storage:t,logger:o,rootState:n,localApp:s,devicePixelRatio:l}){let a=o.child({package:"web-agent"}),c=!0;s==="iframe"&&(c=!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 p=await t.getOrgId(i),h=vl(),g=f=>{c&&r.emit("screenshot",{...f,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.reset(d);else{let f={};u&&(f.viewport=u),s==="web"&&l&&(f.deviceScaleFactor=l);let S=await k.init({baseUrl:d,logger:a,takeScreenshots:c,onScreenshot:g,contextArgs:f});y=new be({browser:S,generator:e,config:Xe,logger:a})}c&&un(r,h,o);let w=new ee(d,[]);r.emit("testContext",{state:w.toObjectRef()}),B.registerSession(y,w,h);let v=k.USER_AGENT;return r.emit("session",{url:d,userAgent:v,viewport:await y.browser.getViewport(),localApp:s}),{sessionId:h,testId:i,orgId:p}}var ds=[pn,ss,is,as,ls,mn];var Zr=r=>{let{logger:e}=r,t=new El(r.baseServer,{cors:{origin:"*",methods:["GET","POST"]},maxHttpBufferSize:1e7});return t.on("connection",async o=>{let n;try{n=await cs({...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}ds.forEach(s=>Al(s,{socket:o,metadata:n,...r}))}),t},Al=(r,e)=>{let t=r.createHandler(e),o=(...n)=>{e.logger.debug({event:r.event,metadata:e.metadata,args:n},"Websocket event");let s=l=>{e.logger.error({event:r.event,type:"websocket",args:n,err:l instanceof Error?l:new Error(`${l}`)},"Unhandled exception in socket handler"),e.socket.emit("error",{message:l instanceof Error?l.message:`${l}`})};try{let l=t.apply(void 0,n);l&&typeof l.catch=="function"&&l.catch(s)}catch(l){s(l)}};e.socket.on(r.event,o)};import{z as Rl}from"zod";import Cl from"fetch-retry";var Tl=Cl(global.fetch),Z=class{static API_VERSION="v1";baseURL;apiKey;constructor(e){this.baseURL=e.baseURL,this.apiKey=e.apiKey}async sendRequest(e,t){let o=await Tl(`${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 Je=class extends Z{constructor(e){super(e)}async getScreenshotFromS3(e){let t=await this.sendRequest(`/${Z.API_VERSION}/s3/visual-diff-screenshot`,{url:e});return Rl.string().parse(t)}async getElementLocation(e,t){let o=await this.sendRequest(`/${Z.API_VERSION}/web-agent/locate-element`,{browserState:e.browserState,goal:e.goal,disableCache:t});return Nr.parse(o)}async getElementLocationWithVision(e,t){let o=await this.sendRequest(`/${Z.API_VERSION}/web-agent/visual-locate`,{goal:e.goal,screenshot:e.screenshot?.toString("base64"),hintActivatedScreenshot:e.hintActivatedScreenshot?.toString("base64"),disableCache:t});return Nr.parse(o)}async getAssertionResult(e,t,o){if(t){let s=await this.sendRequest(`/${Z.API_VERSION}/web-agent/assertion`,{url:e.url,goal:e.goal,screenshot:e.screenshot?.toString("base64"),disableCache:o,vision:!0});return Pr.parse(s)}let n=await this.sendRequest(`/${Z.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 Pr.parse(n)}async getProposedCommand(e,t){let o=await this.sendRequest(`/${Z.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 Jo.parse(o)}async getGranularGoals(e,t){let o=await this.sendRequest(`/${Z.API_VERSION}/web-agent/split-goal`,{url:e.url,goal:e.goal,disableCache:t});return Qo.parse(o)}async getReverseMappedDescription(e,t){let o=await this.sendRequest(`/${Z.API_VERSION}/web-agent/reverse-mapped-description`,{goal:e.goal,browserState:e.browserState,disableCache:t});return Zo.parse(o)}async getTextExtraction(e,t){let o={goal:e.goal,browserState:e.browserState,returnSchema:e.returnSchema,disableCache:t},n=await this.sendRequest(`/${Z.API_VERSION}/web-agent/text-extraction`,o);return pr.parse(n)}};import{existsSync as xl,readFileSync as Il,writeFileSync as eo}from"fs";import to from"path";import{parse as Ll,stringify as Ol}from"yaml";var Y=process.env.MOMENTIC_DIR??Te;function us(r,e){let t=Go(r);for(let[s,l]of Object.entries(t.modules)){let a=Ge(s);eo(to.join(Y,Re,`${a}.yaml`),l,"utf-8")}let o=Ge(e),n=to.join(Y,`${o}.yaml`);return eo(n,t.test,"utf-8"),`${o}.yaml`}function ms(r,e){if(!xl(e))throw new Error(`Test not found at path: ${to}`);let t=Il(e,"utf-8"),n={...Ll(t),...r},s=Ol(n);eo(e,s,"utf-8")}import{readFileSync as Qt,readdirSync as ps,writeFileSync as hs}from"fs";import{join as ro}from"path";import{v4 as Ml}from"uuid";import{parse as Zt,stringify as fs}from"yaml";var Qe=ro(Y,Re);function gs(r){let e=jt(Qe,r.moduleId).path,t=Qt(e,"utf-8"),n={...Zt(t),...r},s=fs(n);hs(e,s,"utf-8")}function ys(r,e){let t=Ge(r),o=ro(Qe,`${t}.yaml`),n={fileType:"momentic/module",schemaVersion:W,moduleId:Ml(),name:r,steps:e},s=fs(n);return hs(o,s,"utf-8"),{moduleId:n.moduleId,name:n.name,parameters:n.parameters,steps:n.steps}}function Ss(){let r=[];for(let e in ps(Qe)){if(!e.endsWith(".yaml"))continue;let t=Qt(e,"utf-8"),o=Zt(t),n={name:o.name,moduleId:o.moduleId,numSteps:o.steps.length};r.push(n)}return r}function ws(){let r=[];for(let e of ps(Qe)){if(!e.endsWith(".yaml"))continue;let t=ro(Qe,e),o=Qt(t,"utf-8"),n=Zt(o);try{let s=gt.parse(n);r.push(s)}catch(s){V.warn({err:s},"Error parsing module, skipping...")}}return r}function bs(r){let e=jt(Re,r).path,t=Qt(e,"utf-8"),o=Zt(t);return gt.parse(o)}import{Router as kl}from"express";import{existsSync as Pl,readFileSync as Nl,readdirSync as Dl}from"fs";import{join as vs}from"path";import{parse as _l}from"yaml";var oo=vs(Y,ht);function Es(){let r=[];if(!Pl(oo))return[];for(let e of Dl(oo)){if(!e.endsWith(".yaml"))continue;let t=vs(oo,e),o=Nl(t,"utf-8"),n=_l(o);try{let s=se.parse(n);r.push(s)}catch(s){V.warn({err:s},"Error parsing environment, skipping...")}}return r}function re(r){return function(...e){let t=e[e.length-1],o=r(...e);Promise.resolve(o).catch(t)}}var As=kl();As.get("/",re((r,e)=>{let t=Es();e.status(200).json(t)}));var Cs=As;import{Router as Fl}from"express";var wt=Fl();wt.get("/",re((r,e)=>{let t=ws();e.status(200).json(t)}));wt.get("/metadata",re((r,e)=>{let t=Ss();e.status(200).json(t)}));wt.post("/",re(async(r,e)=>{let t;try{t=Yo.parse(r.body)}catch(n){e.status(400).json({error:`Invalid request body: ${n}`});return}try{ft(t.name)}catch(n){e.status(400).json({error:`Invalid module name: ${n}`});return}let o=ys(t.name,t.steps);e.status(201).json(o)}));wt.get("/:moduleId",re((r,e)=>{if(!r.params.moduleId){e.status(400).json({error:"Missing moduleId in url path."});return}let t=bs(r.params.moduleId);e.json(t)}));var Ts=wt;import{Router as Hl}from"express";import{existsSync as jl}from"fs";import{join as Ms}from"path";import{v4 as Gl}from"uuid";var so="https://api.momentic.ai",Rs=r=>{so=r},bt=()=>so,zl,xs=r=>{zl=r};var er,no,Is=async r=>{if(er)return;let e=new G({baseURL:so,apiKey:r});try{er=await e.getOrgId(),no=r}catch(t){throw new Error(`Error checking API key against server: ${t}`)}},tr=()=>{if(!er)throw new Error("Your organization ID appears invalid. Please contact Momentic Support.");return er},Ls=()=>{if(!no)throw new Error("Your API key appears invalid. Please contact Momentic Support.");return no};import Os,{multistream as Ul}from"pino";import $l from"pino-pretty";var io=new Map,Bl=process.env.NODE_ENV==="production",ao=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=Bl?Os(n):Os(n,Ul([{stream:$l({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 l=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(!l.ok)throw new Error(`Failed to log to Datadog: ${l.statusText})}`)}catch(l){this.consoleLogger.warn({obj:t,msg:o,args:n,err:l},"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}},rr=({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 io.has(r)||io.set(r,new ao({bindings:{app:r},hostname:t,clientToken:e||process.env.NEXT_PUBLIC_DD_CLIENT_TOKEN||process.env.DD_CLIENT_TOKEN})),io.get(r)};import{hostname as Vl}from"os";var oe=rr({app:"desktop-server",clientToken:"pubcfd7516a5c0ba852b42675cd97bee027",hostname:Vl()});var vt=Hl();vt.get("/",re((r,e)=>{let t=yt(Y,oe);e.status(200).json(t)}));vt.post("/",re((r,e)=>{let t;try{t=qo.parse(r.body)}catch(a){e.status(400).json({error:`Invalid request body: ${a}`});return}try{ft(t.name)}catch(a){e.status(400).json({error:a.message});return}let n={id:Gl(),name:t.name,baseUrl:t.baseUrl,schemaVersion:W,advanced:{disableAICaching:!1,viewport:t.viewport??ze},retries:0,steps:[]};t.environment&&(n.envs=[{name:t.environment,defaultOnLocal:!0}]);let s=us(n,t.name),l={...n,testPath:s};e.status(201).json(l)}));vt.get("/:testPath",re(async(r,e)=>{if(!r.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let t=Ms(Y,r.params.testPath);if(!jl(t)){e.status(400).json({error:`Test not found at path: ${t}`});return}let o=await Gt(t,Qe);e.status(200).json(o)}));vt.patch("/:testPath",re(async(r,e)=>{if(!r.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let t;try{t=Ko.parse(r.body)}catch(i){e.status(400).json({error:`Invalid request body: ${i}`});return}let o=Ms(Y,r.params.testPath),n;try{n=Tn(o)}catch(i){e.status(400).json({error:`Existing test file on disk is invalid: ${i}`});return}let{stepsToSave:s,moduleUpdates:l,cachesToSave:a}=rt({steps:t.steps,testId:n.id,orgId:tr()});l.forEach(i=>gs(i)),ms({steps:s},o),await new G({apiKey:Ls(),baseURL:bt()}).updateStepCaches({entries:a,testId:n.id}),e.status(201).json({message:"ok"})}));var Ps=vt;async function Ns({momenticServerUrl:r,apiKey:e,mode:t,serverPort:o,appPort:n,staticDir:s,assetsDir:l,devicePixelRatio:a}){if(!ql(Y)||!Yl(Y).isDirectory()){let y=Ql.isAbsolute(Y);throw new Error(`Root folder ${Y} does not exist${y?"":` in the current directory ("${process.cwd()}")`}. Please run \`npx momentic init\` if you wish to setup Momentic here for the first time.`)}r&&Rs(r),V.info("Checking API key"),await Is(e);let i=`http://localhost:${o}`;n&&(i=`http://localhost:${n}`);let d=ec(s);await new Promise(y=>{d.listen(o,()=>{V.info(`Server is running at http://localhost:${o}`),y()})});let p=new Je({baseURL:bt(),apiKey:e}),h=new nt(new G({baseURL:bt(),apiKey:e}),tr());if(t==="web"){Zr({localApp:"web",baseServer:d,generator:p,storage:h,logger:oe,devicePixelRatio:a}),await Jl(i);return}let g=await tc({appUrl:i,apiGenerator:p,extensionPath:Zl(l,"extension"),onClose:async()=>{V.info("Browser closed, closing app."),await g.browser.cleanup(),d.close(()=>{process.exit(0)})}});Zr({localApp:"iframe",baseServer:d,generator:p,storage:h,logger:oe,rootState:{controller:g,context:new ee("",[]),executionCancelled:!1}}),process.on("uncaughtException",y=>{oe.error({err:y},"Uncaught exception in desktop-server")}),process.on("unhandledRejection",y=>{oe.error({err:y},"Unhandled rejection in desktop-server")})}function ec(r){let e=lo();e.use(Kl()),e.use(Wl.json({limit:"50mb"}));let t=lo.Router();if(t.use("/tests",Ps),t.use("/modules",Ts),t.use("/environments",Cs),e.use("/api",t),e.use((n,s,l)=>{n.path!=="/healthcheck"&&oe.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&&oe.error({url:n.url,method:n.method,statusCode:s.statusCode},"Request completed in error")}),l()}),e.use((n,s,l,a)=>{n instanceof Error&&n.message.includes("BadRequestError: request aborted")||(oe.error({stack:n.stack,msg:n.message,url:s.url,method:s.method},"Unhandled exception leading to 500 on desktop-server"),l.status(500).send("Internal Server Error"),a(n))}),r){let n=lo.static(r);e.use("/",n),e.use("*",n)}return Xl.createServer(e)}async function tc({appUrl:r,apiGenerator:e,extensionPath:t,onClose:o}){let n=await k.init({baseUrl:r,logger:oe,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 be({browser:n,config:Xe,generator:e,logger:oe});return xs(s),s}import{existsSync as kc}from"fs";import{platform as Fc}from"os";import li,{dirname as zc}from"path";import{fileURLToPath as Uc}from"url";var Ds="0.0.53";import{hostname as oc}from"os";var E=rr({app:"cli",clientToken:"pub7eb923f18fb3f1d42ac5eba8c5ea13a5",hostname:oc()});import{createHash as zs}from"crypto";import{existsSync as Us,mkdirSync as dc,readFileSync as ho,writeFileSync as $s}from"fs";import{join as Bs}from"path";import{parse as uc}from"yaml";import{existsSync as co,mkdirSync as ic,readdirSync as ks,statSync as uo}from"fs";import{homedir as Fs}from"os";import{join as Et,resolve as ac}from"path";import nc from"chalk";import sc from"readline/promises";async function le(r,e){if(process.env.CI)return!0;E.flush(),await new Promise(s=>setTimeout(s,500));let t=sc.createInterface({input:process.stdin,output:process.stdout});r=`${r} (y/N) `;let o=e?nc.bold.yellow(r):r,n=await t.question(o);return t.close(),n.toLowerCase()==="y"}var mo=Te,Ze=Et(Te,Re),po=Et(Te,Er),fe=Et(Te,ht),lc=Et(Fs(),"momentic",Ar),cc=[lc,Ze,po,fe];function _s(r){try{return co(r)&&uo(r).isDirectory()}catch(e){return E.error({err:e},`Error reading path ${r} during directory exists check`),!1}}async function Le(r,e=!1){let t=await r.getOrgId();Fs()||(E.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)),_s(mo)||!e&&!await le(`A '${Te}' 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.`)&&(E.error("Setup cancelled"),process.exit(1));for(let o of cc)_s(o)||(!e&&!await le(`'${o}' folder was not found in the current directory. Create it now?`)&&(E.error("Setup cancelled"),process.exit(1)),ic(o,{recursive:!0}));return ks(fe).length===0&&(!e&&!await le("You do not appear to have any environments configured locally. Pull environments from Momentic Cloud?")&&(E.error("Setup cancelled"),process.exit(1)),await or({client:r,all:!0,skipPrompts:e})),E.info("CLI initialization complete"),t}function At(r,e,t=new Set){for(let o of r){let n=ac(o),s=!1;try{s=co(n)&&uo(n).isDirectory()}catch(l){E.error({err:l},`Error reading path ${n} during collect paths`)}if(n&&s){let l=ks(n).map(a=>Et(n,a));At(l,e,t);continue}if(n.endsWith(".yaml")){try{if(!co(n)||!uo(n).isFile()){E.error(`File not found or unreadable: ${n}`);continue}}catch(l){E.error({err:l},`Error reading file ${n} during collect paths`);continue}if(!e(n))continue;t.add(n)}}return t}async function or({envNames:r,client:e,all:t,skipPrompts:o}){let n=await e.getAllEnvironments();r&&!t&&(n=n.filter(a=>r?.includes(a.name))),Us(fe)||dc(fe);let s=0,l=0;for(let a of n){let c=Bs(fe,`${a.name}.yaml`);if(!Us(c))$s(c,Rr(a)),s++;else{let i=zs("sha256").update(ho(c)).digest("hex"),d=Rr(a),u=zs("sha256").update(d).digest("hex");if(i!==u){if(!o&&!await le(`Environment ${a.name} already exists on disk but needs to be updated. Overwrite?`)){E.error("Pull cancelled");return}$s(c,d),l++}}}E.info(`Pulled ${s} new environments and updated ${l} existing environments`)}function mc(r){return r.endsWith(".yaml")?ho(r,"utf8").includes("momentic/environment")?!0:(E.warn(`Skipping YAML that is not a Momentic environment: ${r}`),!1):!1}async function Vs({client:r,names:e,all:t,yes:o}){let n;t?n=[fe]:n=e.map(a=>Bs(fe,`${a.toLowerCase()}.yaml`));let s=At(n,mc);E.info(`Found ${s.size} environments(s) to push:`),s.forEach(a=>E.info(` - ${a}`)),E.info("Loading file contents");let l=[];for(let a of s){let c=uc(ho(a,"utf-8"));try{let i=se.parse(c);l.push(i)}catch(i){E.error({err:i},`${a} failed to parse as a valid environment file`),process.exit(1)}}if(!o&&!await le("Pushing environments can overwrite variables on cloud and instantly affect scheduled runs. Continue?",!0)){E.error("Push cancelled");return}E.info(`Pushing ${s.size} environment(s)`),await r.updateEnvironments(l),E.info("Push successful!")}import{registry as fo}from"playwright-core/lib/server";function pc(r){let e=[],t=[];for(let o of r){let n=fo.findExecutable(o);!n||n.installType==="none"?e.push(o):t.push(n)}return t}async function Hs(){let r=pc(["chromium"]);await fo.installDeps(r,!1),await fo.install(r,!1)}import{Argument as nr,Option as Oe}from"commander";var Me=new Oe("--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),Pe=new Oe("--server <server>","Momentic server to use. Leave unchanged unless using Momentic on-premise.").default("https://api.momentic.ai"),Ne=new Oe("-y, --yes","Skip confirmation prompts.").env("CI").default(!0),js=new Oe("--no-report","Skip reporting test results to Momentic Cloud when running with the --local flag."),Gs=new Oe("--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)),Ws=new Oe("--env <env>","Name of the environment to use when running tests."),go=new Oe("-a, --all","Select all tests in your organization from Momentic Cloud. Cannot be used together with <tests> arguments."),yo=new Oe("-a, --all","Select all environments in your organization from Momentic Cloud. Cannot be used together with <paths> arguments."),Ks=new nr("<tests...>",`One or more test paths to pull from Momentic Cloud.
30
+ `)}`}async executeCommand(e,t,o,n=!1){let s=this.commandHistory[this.commandHistory.length-1];if(!n&&(!s||s.state!=="PENDING"))throw new T("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 l=Date.now(),a=await this.executePresetStep(e,t,o),c=Date.now()-l;return this.logger.debug({result:a,duration:c},"Got execution result"),a.succeedImmediately&&!n&&(this.pendingInstructions.pop(),this.pendingInstructions.length>0&&(a.succeedImmediately=!1)),a.elementInteracted&&"target"in e&&e.target&&!e.target.elementDescriptor&&(e.target.elementDescriptor=a.elementInteracted.trim()),n||(s.generatedStep=e,s.serializedCommand=Ce(e),s.state="DONE"),a}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 a=this.getSerializedHistory(o,t);n={goal:e.assertion,url:o,browserState:t,history:a,lastCommand:this.lastExecutedCommand,numPrevious:this.commandHistory.length}}let l=await this.generator.getAssertionResult(n,e.useVision,e.disableCache);if(l.relevantElements&&Promise.all(Array.from(new Set(l.relevantElements)).slice(0,5).map(a=>this.browser.highlight({id:a}))),!l.result)throw new T("AssertionFailureError",l.thoughts);return{succeedImmediately:!1,thoughts:l.thoughts,urlAfterCommand:o,beforeScreenshotOverride:s}}async wrapMultiElementTargetingCommand(e,t,o,n,s=1){let l=await Promise.all(e.map((a,c)=>this.wrapElementTargetingCommand(a,t[c],async i=>i,n)));try{return{result:await o(...l.map(c=>c.result)),caches:l.map(c=>c.cache)}}catch(a){if(s>0)return this.logger.warn({err:a},"Failed to execute action with multiple cached targets, retrying with AI"),this.wrapMultiElementTargetingCommand(e,e.map(()=>{}),o,n,s-1);throw new T("ActionFailureError","",{cause:a})}}async wrapElementTargetingCommand(e,t,o,n){let{useVision:s,disableCache:l,filterByViewport:a,useSelector:c}=n,i=n.retriesWithAI??1;if(!t&&!e)throw new T("ActionFailureError","Cannot target element with no cached data or element descriptor");if(c)return{result:await o({id:-1,selector:e})};let d=!!t&&Tt(t);t||(this.logger.info("No cached locator data for target, prompting AI for fresh location"),i--,t=De.parse(await this.locateElement({description:e,useVision:s,disableCache:l,filterByViewport:a})));try{let u=await o(t);return d?this.logger.info({cache:t},"Successfully used cached target to perform action"):this.logger.debug({cache:t},"Successfully generated and used new a11y target information"),{result:u,cache:t}}catch(u){if(i>0&&e)return this.logger.warn({err:u,cache:t},"Failed to execute action with cached target, retrying with AI"),this.wrapElementTargetingCommand(e,void 0,o,{...n,retriesWithAI:i});if(u instanceof T)throw u;let p=`Failed to find ${e?`${e}`:"element"}: ${u instanceof Error?u.message:u}`;throw this.logger.error({err:u,cache:t},p),new T("ActionFailureError",p,{cause:u})}}async screenshotWithDimensions(e){await this.browser.removeAllHighlights();let t=await this.browser.screenshot(e),o=fl(t);return{buffer:t,...o}}async executePresetStep(e,t,o){let n;try{n=await this.resolveCommandTemplateStrings(e,t)}catch(s){throw new T("ActionFailureError",s.message,{cause:s})}try{let s=this.browser.getOpenPages(),l=await this.browser.url(),a=await this.executePresetStepHelper(e,t,o),c=!0;e.type==="NAVIGATE"&&(c=!1);let i=this.browser.getOpenPages();if(c&&i.length!==s.length)for(let d=i.length-1;d>=0;d--){let u=i[d];if(u!==l){await this.browser.switchToPage(u,d);break}}return a}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))Jr(e,o,n)}async resolveCommandTemplateStrings(e,t,o="",n={}){let s=["type","a11yData","thoughts"];for(let l in e){if(s.includes(l))continue;let a=e[l],c=o?`${o}.${l}`:l;if(typeof a=="string"&&a.includes("{{")){let i=await _r({s:a,state:t.toObjectRef(),logger:this.logger});if(a===i)continue;n[c]=a,e[l]=i}else typeof a=="object"&&a!==null&&!Array.isArray(a)&&await this.resolveCommandTemplateStrings(a,t,c,n)}return n}async executePresetStepHelper(e,t,o){switch(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 T("ActionFailureError","Missing assertion");return this.executeAssertion(e)}case"AI_WAIT":{if(!e.assertion.trim())throw new T("ActionFailureError","Missing assertion");let i=Date.now();if(e.timeout&&e.timeout>30)throw new T("AssertionFailureError",`AI wait timeout of ${e.timeout} exceeds the maximum allowed timeout of 30s`);let d=(e.timeout??10)*1e3,u,p,h=0;for(;Date.now()-i<d;)try{u=await this.executeAssertion({...e,type:"AI_ASSERTION"});break}catch(g){p=g instanceof Error?g:new Error(`${g}`),this.logger.warn({err:g},`AI_WAIT assert attempt ${h} failed, retrying...`),h++,await H(d/10)}if(!u){let g=`AI wait still failing after ${d}ms.`;throw p&&(g+=` Latest result: ${p.message}`),new T("AssertionFailureError",g)}return u}case"AI_EXTRACT":{if(!e.goal.trim())throw new T("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},e.disableCache);if(d.result==="NOT_FOUND")throw new T("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(!$r(e.url)&&!Br(e.url,this.browser.baseURL))throw new T("ActionFailureError",`Invalid URL provided to navigate command: ${e.url}`);await this.browser.navigate({url:e.url});break;case"CAPTCHA":let n=await this.browser.solveCaptcha();n&&(await this.wrapElementTargetingCommand("the captcha image solution input",void 0,i=>this.browser.click(i),{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(e.target.elementDescriptor,e.cache?.target??e.target.a11yData,u=>this.browser.hover(u),{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:p}=await this.wrapElementTargetingCommand(e.target.elementDescriptor,e.cache?.target??e.target.a11yData,g=>this.browser.click(g,{doubleClick:e.doubleClick,rightClick:e.rightClick,force:e.force}),d);p&&(e.cache={target:p});let h={urlAfterCommand:await this.browser.url(),succeedImmediately:!1,elementInteracted:u};return Ye(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,p)=>this.browser.dragAndDrop(u,p,{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"SELECT_OPTION":{let{result:i,cache:d}=await this.wrapElementTargetingCommand(e.target.elementDescriptor,e.cache?.target??e.target.a11yData,u=>this.browser.selectOption(u,e.option),{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"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{i=await Ke({code:e.code,fragment:!!e.fragment,state:t.toObjectRef(),timeoutMs:e.timeout?e.timeout*1e3:void 0,logger:this.logger})}catch(d){throw new T("ActionFailureError",d instanceof Error?d.message:`${d}`,{cause:d})}try{i=Sl(i)}catch(d){throw new T("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:p,cache:h}=await this.wrapElementTargetingCommand(e.target.elementDescriptor,e.cache?.target??e.target.a11yData,g=>this.browser.typeIntoTarget(e.value,g,{force:e.force,clearContent:e.clearContent,pressKeysSequentially:e.pressKeysSequentially}),{useVision:e.useVision,disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector});d=p,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 Ye(i,u.urlAfterCommand)&&(u.succeedImmediately=!0,u.succeedImmediatelyReason="URL changed"),u}case"HOVER":{let{result:i,cache:d}=await this.wrapElementTargetingCommand(e.target.elementDescriptor,e.cache?.target??e.target.a11yData,u=>this.browser.hover(u),{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 l=await this.browser.url();await this.browser.press(e.value);let a={urlAfterCommand:await this.browser.url(),succeedImmediately:!1};return Ye(l,a.urlAfterCommand)&&(a.succeedImmediately=!0,a.succeedImmediatelyReason="URL changed"),a;case"REQUEST":{let i=e.timeout??30,d=null,u=new AbortController,p=Object.fromEntries(Object.entries(e.headers||{}).filter(([b,C])=>b&&C)),h=new URLSearchParams;Object.entries(e.params||{}).filter(([b,C])=>b&&C).forEach(([b,C])=>{h.append(b,C)});let g;if($r(e.url)&&(g=e.url),Br(e.url,this.browser.baseURL)&&(g=new URL(e.url,this.browser.baseURL).toString()),!g)throw new T("ActionFailureError",`Invalid URL: ${e.url}`);let y=async()=>{try{d=await fetch(`${g}?${h.toString()}`,{headers:p,method:e.method,body:e.body,signal:u.signal})}catch(b){this.logger.error({err:b},"Failed to make fetch request")}},w=async()=>{await new Promise(b=>setTimeout(b,i*1e3)),u.abort()};await Promise.race([w(),y()]);let v=d;if(!v)throw new T("ActionFailureError",`Fetch request timed out after ${i} seconds`);if(!v.ok)throw new T("ActionFailureError",`Fetch request failed with status ${v.status}`);let f={};v.headers.forEach((b,C)=>{f[C]=b});let S={status:v.status,headers:f};return v.headers.get("content-type")?.includes("json")?S.json=await v.json():v.headers.get("content-type")?.includes("text")&&(S.text=await v.text()),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),data:S}}case"VISUAL_DIFF":{if(!e.screenshot)throw new T("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 T("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 y=await fetch(e.screenshot.data);u=Buffer.from(await y.arrayBuffer())}else u=Buffer.from(e.screenshot.data,"base64");let h=wl(Qr.decode(u).data,Qr.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:Qr.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}async toggleRecordMode({action:e,indices:t,onStepRecord:o}){if(e==="STOP"){this.recordAbortController?.abort();return}if(!t||!o)throw new Error("Indices or step recording callback not provided to toggleRecordMode");this.recordAbortController=new AbortController;let n=new Jt({signal:this.recordAbortController.signal,initialIndices:t,generator:this.generator,onStepRecord:o});await this.browser.startRecording(this.recordAbortController.signal,n)}};async function cs({socket:r,generator:e,storage:t,logger:o,rootState:n,localApp:s,devicePixelRatio:l}){let a=o.child({package:"web-agent"}),c=!0;s==="iframe"&&(c=!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 p=await t.getOrgId(i),h=vl(),g=f=>{c&&r.emit("screenshot",{...f,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.reset(d);else{let f={};u&&(f.viewport=u),s==="web"&&l&&(f.deviceScaleFactor=l);let S=await k.init({baseUrl:d,logger:a,takeScreenshots:c,onScreenshot:g,contextArgs:f});y=new be({browser:S,generator:e,config:Xe,logger:a})}c&&un(r,h,o);let w=new ee(d,[]);r.emit("testContext",{state:w.toObjectRef()}),B.registerSession(y,w,h);let v=k.USER_AGENT;return r.emit("session",{url:d,userAgent:v,viewport:await y.browser.getViewport(),localApp:s}),{sessionId:h,testId:i,orgId:p}}var ds=[pn,ss,is,as,ls,mn];var Zr=r=>{let{logger:e}=r,t=new El(r.baseServer,{cors:{origin:"*",methods:["GET","POST"]},maxHttpBufferSize:1e7});return t.on("connection",async o=>{let n;try{n=await cs({...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}ds.forEach(s=>Al(s,{socket:o,metadata:n,...r}))}),t},Al=(r,e)=>{let t=r.createHandler(e),o=(...n)=>{e.logger.debug({event:r.event,metadata:e.metadata,args:n},"Websocket event");let s=l=>{e.logger.error({event:r.event,type:"websocket",args:n,err:l instanceof Error?l:new Error(`${l}`)},"Unhandled exception in socket handler"),e.socket.emit("error",{message:l instanceof Error?l.message:`${l}`})};try{let l=t.apply(void 0,n);l&&typeof l.catch=="function"&&l.catch(s)}catch(l){s(l)}};e.socket.on(r.event,o)};import{z as Rl}from"zod";import Cl from"fetch-retry";var Tl=Cl(global.fetch),Z=class{static API_VERSION="v1";baseURL;apiKey;constructor(e){this.baseURL=e.baseURL,this.apiKey=e.apiKey}async sendRequest(e,t){let o=await Tl(`${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 Je=class extends Z{constructor(e){super(e)}async getScreenshotFromS3(e){let t=await this.sendRequest(`/${Z.API_VERSION}/s3/visual-diff-screenshot`,{url:e});return Rl.string().parse(t)}async getElementLocation(e,t){let o=await this.sendRequest(`/${Z.API_VERSION}/web-agent/locate-element`,{browserState:e.browserState,goal:e.goal,disableCache:t});return Nr.parse(o)}async getElementLocationWithVision(e,t){let o=await this.sendRequest(`/${Z.API_VERSION}/web-agent/visual-locate`,{goal:e.goal,screenshot:e.screenshot?.toString("base64"),hintActivatedScreenshot:e.hintActivatedScreenshot?.toString("base64"),disableCache:t});return Nr.parse(o)}async getAssertionResult(e,t,o){if(t){let s=await this.sendRequest(`/${Z.API_VERSION}/web-agent/assertion`,{url:e.url,goal:e.goal,screenshot:e.screenshot?.toString("base64"),disableCache:o,vision:!0});return Pr.parse(s)}let n=await this.sendRequest(`/${Z.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 Pr.parse(n)}async getProposedCommand(e,t){let o=await this.sendRequest(`/${Z.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 Jo.parse(o)}async getGranularGoals(e,t){let o=await this.sendRequest(`/${Z.API_VERSION}/web-agent/split-goal`,{url:e.url,goal:e.goal,disableCache:t});return Qo.parse(o)}async getReverseMappedDescription(e,t){let o=await this.sendRequest(`/${Z.API_VERSION}/web-agent/reverse-mapped-description`,{goal:e.goal,browserState:e.browserState,disableCache:t});return Zo.parse(o)}async getTextExtraction(e,t){let o={goal:e.goal,browserState:e.browserState,returnSchema:e.returnSchema,disableCache:t},n=await this.sendRequest(`/${Z.API_VERSION}/web-agent/text-extraction`,o);return pr.parse(n)}};import{existsSync as xl,readFileSync as Il,writeFileSync as eo}from"fs";import to from"path";import{parse as Ll,stringify as Ol}from"yaml";var Y=process.env.MOMENTIC_DIR??Te;function us(r,e){let t=Go(r);for(let[s,l]of Object.entries(t.modules)){let a=Ge(s);eo(to.join(Y,Re,`${a}.yaml`),l,"utf-8")}let o=Ge(e),n=to.join(Y,`${o}.yaml`);return eo(n,t.test,"utf-8"),`${o}.yaml`}function ms(r,e){if(!xl(e))throw new Error(`Test not found at path: ${to}`);let t=Il(e,"utf-8"),n={...Ll(t),...r},s=Ol(n);eo(e,s,"utf-8")}import{readFileSync as Qt,readdirSync as ps,writeFileSync as hs}from"fs";import{join as ro}from"path";import{v4 as Ml}from"uuid";import{parse as Zt,stringify as fs}from"yaml";var Qe=ro(Y,Re);function gs(r){let e=jt(Qe,r.moduleId).path,t=Qt(e,"utf-8"),n={...Zt(t),...r},s=fs(n);hs(e,s,"utf-8")}function ys(r,e){let t=Ge(r),o=ro(Qe,`${t}.yaml`),n={fileType:"momentic/module",schemaVersion:W,moduleId:Ml(),name:r,steps:e},s=fs(n);return hs(o,s,"utf-8"),{moduleId:n.moduleId,name:n.name,parameters:n.parameters,steps:n.steps}}function Ss(){let r=[];for(let e in ps(Qe)){if(!e.endsWith(".yaml"))continue;let t=Qt(e,"utf-8"),o=Zt(t),n={name:o.name,moduleId:o.moduleId,numSteps:o.steps.length};r.push(n)}return r}function ws(){let r=[];for(let e of ps(Qe)){if(!e.endsWith(".yaml"))continue;let t=ro(Qe,e),o=Qt(t,"utf-8"),n=Zt(o);try{let s=gt.parse(n);r.push(s)}catch(s){V.warn({err:s},"Error parsing module, skipping...")}}return r}function bs(r){let e=jt(Re,r).path,t=Qt(e,"utf-8"),o=Zt(t);return gt.parse(o)}import{Router as kl}from"express";import{existsSync as Pl,readFileSync as Nl,readdirSync as Dl}from"fs";import{join as vs}from"path";import{parse as _l}from"yaml";var oo=vs(Y,ht);function Es(){let r=[];if(!Pl(oo))return[];for(let e of Dl(oo)){if(!e.endsWith(".yaml"))continue;let t=vs(oo,e),o=Nl(t,"utf-8"),n=_l(o);try{let s=se.parse(n);r.push(s)}catch(s){V.warn({err:s},"Error parsing environment, skipping...")}}return r}function re(r){return function(...e){let t=e[e.length-1],o=r(...e);Promise.resolve(o).catch(t)}}var As=kl();As.get("/",re((r,e)=>{let t=Es();e.status(200).json(t)}));var Cs=As;import{Router as Fl}from"express";var wt=Fl();wt.get("/",re((r,e)=>{let t=ws();e.status(200).json(t)}));wt.get("/metadata",re((r,e)=>{let t=Ss();e.status(200).json(t)}));wt.post("/",re(async(r,e)=>{let t;try{t=Yo.parse(r.body)}catch(n){e.status(400).json({error:`Invalid request body: ${n}`});return}try{ft(t.name)}catch(n){e.status(400).json({error:`Invalid module name: ${n}`});return}let o=ys(t.name,t.steps);e.status(201).json(o)}));wt.get("/:moduleId",re((r,e)=>{if(!r.params.moduleId){e.status(400).json({error:"Missing moduleId in url path."});return}let t=bs(r.params.moduleId);e.json(t)}));var Ts=wt;import{Router as Hl}from"express";import{existsSync as jl}from"fs";import{join as Ms}from"path";import{v4 as Gl}from"uuid";var so="https://api.momentic.ai",Rs=r=>{so=r},bt=()=>so,zl,xs=r=>{zl=r};var er,no,Is=async r=>{if(er)return;let e=new G({baseURL:so,apiKey:r});try{er=await e.getOrgId(),no=r}catch(t){throw new Error(`Error checking API key against server: ${t}`)}},tr=()=>{if(!er)throw new Error("Your organization ID appears invalid. Please contact Momentic Support.");return er},Ls=()=>{if(!no)throw new Error("Your API key appears invalid. Please contact Momentic Support.");return no};import Os,{multistream as Ul}from"pino";import $l from"pino-pretty";var io=new Map,Bl=process.env.NODE_ENV==="production",ao=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=Bl?Os(n):Os(n,Ul([{stream:$l({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 l=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(!l.ok)throw new Error(`Failed to log to Datadog: ${l.statusText})}`)}catch(l){this.consoleLogger.warn({obj:t,msg:o,args:n,err:l},"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}},rr=({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 io.has(r)||io.set(r,new ao({bindings:{app:r},hostname:t,clientToken:e||process.env.NEXT_PUBLIC_DD_CLIENT_TOKEN||process.env.DD_CLIENT_TOKEN})),io.get(r)};import{hostname as Vl}from"os";var oe=rr({app:"desktop-server",clientToken:"pubcfd7516a5c0ba852b42675cd97bee027",hostname:Vl()});var vt=Hl();vt.get("/",re((r,e)=>{let t=yt(Y,oe);e.status(200).json(t)}));vt.post("/",re((r,e)=>{let t;try{t=qo.parse(r.body)}catch(a){e.status(400).json({error:`Invalid request body: ${a}`});return}try{ft(t.name)}catch(a){e.status(400).json({error:a.message});return}let n={id:Gl(),name:t.name,baseUrl:t.baseUrl,schemaVersion:W,advanced:{disableAICaching:!1,viewport:t.viewport??ze},retries:0,steps:[]};t.environment&&(n.envs=[{name:t.environment,defaultOnLocal:!0}]);let s=us(n,t.name),l={...n,testPath:s};e.status(201).json(l)}));vt.get("/:testPath",re(async(r,e)=>{if(!r.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let t=Ms(Y,r.params.testPath);if(!jl(t)){e.status(400).json({error:`Test not found at path: ${t}`});return}let o=await Gt(t,Qe);e.status(200).json(o)}));vt.patch("/:testPath",re(async(r,e)=>{if(!r.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let t;try{t=Ko.parse(r.body)}catch(i){e.status(400).json({error:`Invalid request body: ${i}`});return}let o=Ms(Y,r.params.testPath),n;try{n=Tn(o)}catch(i){e.status(400).json({error:`Existing test file on disk is invalid: ${i}`});return}let{stepsToSave:s,moduleUpdates:l,cachesToSave:a}=rt({steps:t.steps,testId:n.id,orgId:tr()});l.forEach(i=>gs(i)),ms({steps:s},o),await new G({apiKey:Ls(),baseURL:bt()}).updateStepCaches({entries:a,testId:n.id}),e.status(201).json({message:"ok"})}));var Ps=vt;async function Ns({momenticServerUrl:r,apiKey:e,mode:t,serverPort:o,appPort:n,staticDir:s,assetsDir:l,devicePixelRatio:a}){if(!ql(Y)||!Yl(Y).isDirectory()){let y=Ql.isAbsolute(Y);throw new Error(`Root folder ${Y} does not exist${y?"":` in the current directory ("${process.cwd()}")`}. Please run \`npx momentic init\` if you wish to setup Momentic here for the first time.`)}r&&Rs(r),V.info("Checking API key"),await Is(e);let i=`http://localhost:${o}`;n&&(i=`http://localhost:${n}`);let d=ec(s);await new Promise(y=>{d.listen(o,()=>{V.info(`Server is running at http://localhost:${o}`),y()})});let p=new Je({baseURL:bt(),apiKey:e}),h=new nt(new G({baseURL:bt(),apiKey:e}),tr());if(t==="web"){Zr({localApp:"web",baseServer:d,generator:p,storage:h,logger:oe,devicePixelRatio:a}),await Jl(i);return}let g=await tc({appUrl:i,apiGenerator:p,extensionPath:Zl(l,"extension"),onClose:async()=>{V.info("Browser closed, closing app."),await g.browser.cleanup(),d.close(()=>{process.exit(0)})}});Zr({localApp:"iframe",baseServer:d,generator:p,storage:h,logger:oe,rootState:{controller:g,context:new ee("",[]),executionCancelled:!1}}),process.on("uncaughtException",y=>{oe.error({err:y},"Uncaught exception in desktop-server")}),process.on("unhandledRejection",y=>{oe.error({err:y},"Unhandled rejection in desktop-server")})}function ec(r){let e=lo();e.use(Kl()),e.use(Wl.json({limit:"50mb"}));let t=lo.Router();if(t.use("/tests",Ps),t.use("/modules",Ts),t.use("/environments",Cs),e.use("/api",t),e.use((n,s,l)=>{n.path!=="/healthcheck"&&oe.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&&oe.error({url:n.url,method:n.method,statusCode:s.statusCode},"Request completed in error")}),l()}),e.use((n,s,l,a)=>{n instanceof Error&&n.message.includes("BadRequestError: request aborted")||(oe.error({stack:n.stack,msg:n.message,url:s.url,method:s.method},"Unhandled exception leading to 500 on desktop-server"),l.status(500).send("Internal Server Error"),a(n))}),r){let n=lo.static(r);e.use("/",n),e.use("*",n)}return Xl.createServer(e)}async function tc({appUrl:r,apiGenerator:e,extensionPath:t,onClose:o}){let n=await k.init({baseUrl:r,logger:oe,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 be({browser:n,config:Xe,generator:e,logger:oe});return xs(s),s}import{existsSync as kc}from"fs";import{platform as Fc}from"os";import li,{dirname as zc}from"path";import{fileURLToPath as Uc}from"url";var Ds="0.0.54";import{hostname as oc}from"os";var E=rr({app:"cli",clientToken:"pub7eb923f18fb3f1d42ac5eba8c5ea13a5",hostname:oc()});import{createHash as zs}from"crypto";import{existsSync as Us,mkdirSync as dc,readFileSync as ho,writeFileSync as $s}from"fs";import{join as Bs}from"path";import{parse as uc}from"yaml";import{existsSync as co,mkdirSync as ic,readdirSync as ks,statSync as uo}from"fs";import{homedir as Fs}from"os";import{join as Et,resolve as ac}from"path";import nc from"chalk";import sc from"readline/promises";async function le(r,e){if(process.env.CI)return!0;E.flush(),await new Promise(s=>setTimeout(s,500));let t=sc.createInterface({input:process.stdin,output:process.stdout});r=`${r} (y/N) `;let o=e?nc.bold.yellow(r):r,n=await t.question(o);return t.close(),n.toLowerCase()==="y"}var mo=Te,Ze=Et(Te,Re),po=Et(Te,Er),fe=Et(Te,ht),lc=Et(Fs(),"momentic",Ar),cc=[lc,Ze,po,fe];function _s(r){try{return co(r)&&uo(r).isDirectory()}catch(e){return E.error({err:e},`Error reading path ${r} during directory exists check`),!1}}async function Le(r,e=!1){let t=await r.getOrgId();Fs()||(E.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)),_s(mo)||!e&&!await le(`A '${Te}' 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.`)&&(E.error("Setup cancelled"),process.exit(1));for(let o of cc)_s(o)||(!e&&!await le(`'${o}' folder was not found in the current directory. Create it now?`)&&(E.error("Setup cancelled"),process.exit(1)),ic(o,{recursive:!0}));return ks(fe).length===0&&(!e&&!await le("You do not appear to have any environments configured locally. Pull environments from Momentic Cloud?")&&(E.error("Setup cancelled"),process.exit(1)),await or({client:r,all:!0,skipPrompts:e})),E.info("CLI initialization complete"),t}function At(r,e,t=new Set){for(let o of r){let n=ac(o),s=!1;try{s=co(n)&&uo(n).isDirectory()}catch(l){E.error({err:l},`Error reading path ${n} during collect paths`)}if(n&&s){let l=ks(n).map(a=>Et(n,a));At(l,e,t);continue}if(n.endsWith(".yaml")){try{if(!co(n)||!uo(n).isFile()){E.error(`File not found or unreadable: ${n}`);continue}}catch(l){E.error({err:l},`Error reading file ${n} during collect paths`);continue}if(!e(n))continue;t.add(n)}}return t}async function or({envNames:r,client:e,all:t,skipPrompts:o}){let n=await e.getAllEnvironments();r&&!t&&(n=n.filter(a=>r?.includes(a.name))),Us(fe)||dc(fe);let s=0,l=0;for(let a of n){let c=Bs(fe,`${a.name}.yaml`);if(!Us(c))$s(c,Rr(a)),s++;else{let i=zs("sha256").update(ho(c)).digest("hex"),d=Rr(a),u=zs("sha256").update(d).digest("hex");if(i!==u){if(!o&&!await le(`Environment ${a.name} already exists on disk but needs to be updated. Overwrite?`)){E.error("Pull cancelled");return}$s(c,d),l++}}}E.info(`Pulled ${s} new environments and updated ${l} existing environments`)}function mc(r){return r.endsWith(".yaml")?ho(r,"utf8").includes("momentic/environment")?!0:(E.warn(`Skipping YAML that is not a Momentic environment: ${r}`),!1):!1}async function Vs({client:r,names:e,all:t,yes:o}){let n;t?n=[fe]:n=e.map(a=>Bs(fe,`${a.toLowerCase()}.yaml`));let s=At(n,mc);E.info(`Found ${s.size} environments(s) to push:`),s.forEach(a=>E.info(` - ${a}`)),E.info("Loading file contents");let l=[];for(let a of s){let c=uc(ho(a,"utf-8"));try{let i=se.parse(c);l.push(i)}catch(i){E.error({err:i},`${a} failed to parse as a valid environment file`),process.exit(1)}}if(!o&&!await le("Pushing environments can overwrite variables on cloud and instantly affect scheduled runs. Continue?",!0)){E.error("Push cancelled");return}E.info(`Pushing ${s.size} environment(s)`),await r.updateEnvironments(l),E.info("Push successful!")}import{registry as fo}from"playwright-core/lib/server";function pc(r){let e=[],t=[];for(let o of r){let n=fo.findExecutable(o);!n||n.installType==="none"?e.push(o):t.push(n)}return t}async function Hs(){let r=pc(["chromium"]);await fo.installDeps(r,!1),await fo.install(r,!1)}import{Argument as nr,Option as Oe}from"commander";var Me=new Oe("--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),Pe=new Oe("--server <server>","Momentic server to use. Leave unchanged unless using Momentic on-premise.").default("https://api.momentic.ai"),Ne=new Oe("-y, --yes","Skip confirmation prompts.").env("CI").default(!0),js=new Oe("--no-report","Skip reporting test results to Momentic Cloud when running with the --local flag."),Gs=new Oe("--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)),Ws=new Oe("--env <env>","Name of the environment to use when running tests."),go=new Oe("-a, --all","Select all tests in your organization from Momentic Cloud. Cannot be used together with <tests> arguments."),yo=new Oe("-a, --all","Select all environments in your organization from Momentic Cloud. Cannot be used together with <paths> arguments."),Ks=new nr("<tests...>",`One or more test paths to pull from Momentic Cloud.
31
31
 
32
32
  A test path is a lowercased version of your test name where spaces are replaced with underscores: 'npx momentic pull hello-world'.`).argOptional(),qs=new nr("<tests...>",`One or more test identifiers.
33
33
 
@@ -38,4 +38,4 @@ To use tests stored remotely on Momentic Cloud, pass '--remote' and specify one
38
38
  relaunch with the --pixel-ratio option for a better experience.
39
39
  If you are using a non-Retina monitor, you can ignore this warning.`):V.info(bo`If you are using Momentic on a high-pixel density (HiDPI) monitor,
40
40
  relaunch with the --pixel-ratio option for a better experience.`),V.info("Please visit https://www.mydevice.io/ to confirm your device's pixel-ratio."))}await Ns({momenticServerUrl:n,mode:s,apiKey:e,serverPort:t,appPort:t,staticDir:d,assetsDir:u,devicePixelRatio:l})});ge.command("run").alias("run-tests").addOption(Me).addOption(Pe).addOption(go).addOption(Ne).addOption(new ve("-r, --remote","Run tests remotely. The production version of this test will be queued for execution.").default(!0).conflicts(["start, waitOn, waitOnTimeout, local"]).implies({local:!1})).addOption(js).addOption(new ve("-l, --local","Run tests locally. Useful for accessing apps on localhost. This option does not control where tests are read from (see <tests> argument documentation).").implies({remote:!1,noReport:!0})).addOption(new ve("--start <command>","Arbitrary setup command that will run before Momentic steps begin.")).addOption(new ve("--wait-on <url>","URL to wait to become accessible before Momentic tests begin.")).addOption(Ws).addOption(new ve("--wait-on-timeout <timeout>","Max time to wait for the --wait-on URL to become accessible.").default(60,"one minute")).addOption(new ve("-p, --parallel <parallelization>","When running with the --local flag, the number of tests to run in parallel. Defaults to 1.").default(1)).addArgument(qs).action(async(r,e)=>{let{apiKey:t,server:o,remote:n,local:s,all:l,env:a,yes:c,parallel:i,report:d,start:u,waitOn:p,waitOnTimeout:h}=e;!l&&(!r||!r.length)&&(E.error("You must pass at least one test path or the --all flag"),process.exit(1));let g=new G({baseURL:o,apiKey:t}),y=await Le(g,c);if(s){try{await ii({tests:r,client:g,all:l,start:u,waitOn:p,waitOnTimeout:h,parallel:i,report:d,env:a,orgId:y})}catch(w){E.error(w),process.exit(1)}return}if(n){for(let w of r)(w.endsWith(".yaml")||kc(w))&&(E.error(bo`'${w}' looks like a local file or directory, but the --local flag was not supplied.
41
- Please supply the --local flag to run tests locally, or specify the test path without its file extension to queue tests remotely (e.g. 'hello-world').`),process.exit(1));await ai({tests:r,client:g,all:l,env:a});return}E.error("One of --remote or --local must be specified"),process.exit(1)});ge.command("pull").description("Fetch one or more tests from Momentic Cloud and save it in to local disk in YAML format.").addOption(Me).addOption(Pe).addOption(go).addOption(Ne).addArgument(Ks).action(async(r,e)=>{let{apiKey:t,server:o,all:n,yes:s}=e,l=new G({baseURL:o,apiKey:t});if(await Le(l,s),!n&&!r?.length)throw new Error("At least one test name or --all must be provided");await ti({testsToFetch:r??[],client:l,all:n})});ge.command("push").description("Save one or more tests in YAML format to Momentic Cloud.").addOption(Ne).addOption(Me).addOption(Pe).addArgument(Ys).action(async(r,e)=>{let{apiKey:t,server:o,yes:n}=e;await Le(t,n);let s=new G({baseURL:o,apiKey:t});await ri({paths:r,client:s,yes:n})});var di=ge.command("env").description("Perform operations on Momentic environments");di.command("push").description("Push one or more environments and associated variables to Momentic cloud.").addOption(Ne).addOption(Me).addOption(Pe).addOption(yo).addArgument(So).action(async(r,e)=>{let{apiKey:t,server:o,yes:n,all:s}=e;!r?.length&&!s&&(E.error("At least one environment name or --all must be provided"),process.exit(1)),await Le(t,n);let l=new G({baseURL:o,apiKey:t});await Vs({names:r,client:l,yes:n,all:s})});di.command("pull").description("Pull one or more environments and associated variables from Momentic cloud.").addOption(Ne).addOption(Me).addOption(Pe).addOption(yo).addArgument(So).action(async(r,e)=>{let{apiKey:t,server:o,yes:n,all:s}=e;!r?.length&&!s&&(E.error("At least one environment name or --all must be provided"),process.exit(1)),await Le(t,n);let l=new G({baseURL:o,apiKey:t});await or({envNames:r,client:l,skipPrompts:n,all:s})});async function $c(){try{await ge.parseAsync(process.argv)}catch(r){let e={};try{e.playwrightVersion=ci("npx playwright --version").toString()}catch(t){V.info({err:t},"Error fetching debug information")}E.error({err:r,debugInfo:e},"Uncaught error in CLI")}}$c();
41
+ Please supply the --local flag to run tests locally, or specify the test path without its file extension to queue tests remotely (e.g. 'hello-world').`),process.exit(1));await ai({tests:r,client:g,all:l,env:a});return}E.error("One of --remote or --local must be specified"),process.exit(1)});ge.command("pull").description("Fetch one or more tests from Momentic Cloud and save it in to local disk in YAML format.").addOption(Me).addOption(Pe).addOption(go).addOption(Ne).addArgument(Ks).action(async(r,e)=>{let{apiKey:t,server:o,all:n,yes:s}=e,l=new G({baseURL:o,apiKey:t});if(await Le(l,s),!n&&!r?.length)throw new Error("At least one test name or --all must be provided");await ti({testsToFetch:r??[],client:l,all:n})});ge.command("push").description("Save one or more tests in YAML format to Momentic Cloud.").addOption(Ne).addOption(Me).addOption(Pe).addArgument(Ys).action(async(r,e)=>{let{apiKey:t,server:o,yes:n}=e,s=new G({baseURL:o,apiKey:t});await Le(s,n),await ri({paths:r,client:s,yes:n})});var di=ge.command("env").description("Perform operations on Momentic environments");di.command("push").description("Push one or more environments and associated variables to Momentic cloud.").addOption(Ne).addOption(Me).addOption(Pe).addOption(yo).addArgument(So).action(async(r,e)=>{let{apiKey:t,server:o,yes:n,all:s}=e;!r?.length&&!s&&(E.error("At least one environment name or --all must be provided"),process.exit(1));let l=new G({baseURL:o,apiKey:t});await Le(l,n),await Vs({names:r,client:l,yes:n,all:s})});di.command("pull").description("Pull one or more environments and associated variables from Momentic cloud.").addOption(Ne).addOption(Me).addOption(Pe).addOption(yo).addArgument(So).action(async(r,e)=>{let{apiKey:t,server:o,yes:n,all:s}=e;!r?.length&&!s&&(E.error("At least one environment name or --all must be provided"),process.exit(1));let l=new G({baseURL:o,apiKey:t});await Le(l,n),await or({envNames:r,client:l,skipPrompts:n,all:s})});async function $c(){try{await ge.parseAsync(process.argv)}catch(r){let e={};try{e.playwrightVersion=ci("npx playwright --version").toString()}catch(t){V.info({err:t},"Error fetching debug information")}E.error({err:r,debugInfo:e},"Uncaught error in CLI")}}$c();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "momentic",
3
- "version": "0.0.53",
3
+ "version": "0.0.54",
4
4
  "description": "The Momentic SDK for Node.js",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",