momentic 0.0.36 → 0.0.37

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
@@ -26,7 +26,7 @@ You have already executed the following commands successfully (most recent liste
26
26
  `).forEach(l=>a.push(` ${l}`))):a.push("PAGE CONTENT CHANGE: <TOO_LONG_TO_DISPLAY/>"):a.push("PAGE CONTENT CHANGE: <NONE/>")}a.push("-".repeat(10))}),a.push(`STARTING URL: ${this.browser.baseURL}`),a.join(`
27
27
  `)}getListHistory(){return Jn`Here are the commands that you have successfully executed:
28
28
  ${this.commandHistory.filter(e=>e.type==="AI_ACTION").map(e=>`- ${e.serializedCommand}`).join(`
29
- `)}`}async executeCommand(e,i,r=!1){let a=this.commandHistory[this.commandHistory.length-1];if(!r&&(!a||a.state!=="PENDING"))throw new x("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 t=Date.now(),n=await this.executePresetStep(e,i),o=Date.now()-t;return this.logger.debug({result:n,duration:o},"Got execution result"),n.succeedImmediately&&!r&&(this.pendingInstructions.pop(),this.pendingInstructions.length>0&&(n.succeedImmediately=!1)),n.elementInteracted&&"target"in e&&e.target&&!e.target.elementDescriptor&&(e.target.elementDescriptor=n.elementInteracted.trim()),r||(a.generatedStep=e,a.serializedCommand=Me(e),a.state="DONE"),n}async executeAssertion(e){let i=await this.getBrowserState(),r=await this.browser.url(),a;if(e.useVision)a={goal:e.assertion,url:r,screenshot:await this.browser.screenshot(),browserState:"",history:"",numPrevious:-1,lastCommand:null};else{let n=this.getSerializedHistory(r,i);a={goal:e.assertion,url:r,browserState:i,history:n,lastCommand:this.lastExecutedCommand,numPrevious:this.commandHistory.length}}let t=await this.generator.getAssertionResult(a,e.useVision,e.disableCache);if(t.relevantElements&&Promise.all(t.relevantElements.map(n=>this.browser.highlight({id:n}))),!t.result)throw new x("AssertionFailureError",t.thoughts);return{succeedImmediately:!1,thoughts:t.thoughts,urlAfterCommand:r}}async wrapMultiElementTargetingCommand(e,i,r,...a){let t=await Promise.all(a.map(n=>this.wrapElementTargetingCommand(n,i.useVision,i.disableCache,async o=>o)));try{return await e(...t)}catch(n){if(r>0)return this.logger.warn({err:n},"Failed to execute action with multiple cached targets, retrying with AI"),a.forEach(o=>o.a11yData=void 0),this.wrapMultiElementTargetingCommand(e,i,r-1,...a);throw new x("ActionFailureError","",{cause:n})}}async wrapElementTargetingCommand(e,i,r,a,t=1){if(!e.a11yData&&!e.elementDescriptor)throw new x("ActionFailureError","Cannot target element with no target data or element descriptor");let n=e.a11yData&&as(e.a11yData);e.a11yData||(this.logger.info("No cached locator data for target, prompting AI for fresh location"),t--,e.a11yData=is.parse(await this.locateElement(e.elementDescriptor,i,r)));try{let o=await a(e.a11yData);return n?this.logger.info({target:e},"Successfully used cached target to perform action"):this.logger.debug({target:e},"Successfully generated and used new a11y target information"),o}catch(o){if(t>0&&e.elementDescriptor)return this.logger.warn({err:o,target:e},"Failed to execute action with cached target, retrying with AI"),e.a11yData=void 0,this.wrapElementTargetingCommand(e,i,r,a,t);if(o instanceof x)throw o;let l=`Failed to find ${e.elementDescriptor?`${e.elementDescriptor}`:"element"}: ${o instanceof Error?o.message:o}`;throw this.logger.error({err:o,target:e},l),new x("ActionFailureError",l,{cause:o})}}async executePresetStep(e,i){try{return await this.executePresetStepHelper(e,i)}catch(r){throw r instanceof x?r:new x("InternalWebAgentError","",{cause:r})}}async executePresetStepHelper(e,i){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 x("ActionFailureError","Missing assertion");return this.executeAssertion(e)}case"AI_WAIT":{if(!e.assertion.trim())throw new x("ActionFailureError","Missing assertion");let l=Date.now();if(e.timeout&&e.timeout>30)throw new x("AssertionFailureError",`AI wait timeout of ${e.timeout} exceeds the maximum allowed timeout of 30s`);let c=(e.timeout??10)*1e3,d,p,g=0;for(;Date.now()-l<c;)try{d=await this.executeAssertion({...e,type:"AI_ASSERTION"});break}catch(u){p=u instanceof Error?u:new Error(`${u}`),this.logger.warn({err:u},`AI_WAIT assert attempt ${g} failed, retrying...`),g++,await U(c/10)}if(!d){let u=`AI wait still failing after ${c}ms.`;throw p&&(u+=` Latest result: ${p.message}`),new x("AssertionFailureError",u)}return d}case"AI_EXTRACT":{if(!e.goal.trim())throw new x("ActionFailureError","Cannot perform AI extraction without goal");let l=await this.browser.getHTML(),c=await this.generator.getTextExtraction({goal:e.goal,browserState:l,returnSchema:e.schema},e.disableCache);if(c.result==="NOT_FOUND")throw new x("ActionFailureError","No relevant data found for extraction goal on this page");return{data:c.result,succeedImmediately:!1,urlAfterCommand:await this.browser.url()}}case"NAVIGATE":if(!Zs(e.url)&&!ei(e.url,this.browser.baseURL))throw new x("ActionFailureError",`Invalid URL: ${e.url}`);await this.browser.navigate({url:e.url});break;case"CAPTCHA":let r=await this.browser.solveCaptcha();r&&(await this.wrapElementTargetingCommand({elementDescriptor:"the captcha image solution input"},e.useVision,i,l=>this.browser.click(l)),await this.browser.type(r,{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 a;switch(e.target&&(e.target.elementDescriptor.trim()||e.target.a11yData)&&(a=await this.wrapElementTargetingCommand(e.target,e.useVision,i,l=>this.browser.hover(l))),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:a};case"WAIT":await this.browser.wait(e.delay*1e3);break;case"REFRESH":await this.browser.refresh();break;case"CLICK":{let l=await this.browser.url(),c=await this.wrapElementTargetingCommand(e.target,e.useVision,i,p=>this.browser.click(p,{doubleClick:e.doubleClick,rightClick:e.rightClick,force:e.force})),d={urlAfterCommand:await this.browser.url(),succeedImmediately:!1,elementInteracted:c};return qe(l,d.urlAfterCommand)&&(d.succeedImmediately=!0,d.succeedImmediatelyReason="URL changed"),d}case"DRAG":{let l=await this.wrapMultiElementTargetingCommand((c,d)=>this.browser.dragAndDrop(c,d,{force:e.force}),{useVision:e.useVision,disableCache:i},1,e.fromTarget,e.toTarget);return{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:l}}case"SELECT_OPTION":{let l=await this.wrapElementTargetingCommand(e.target,!1,i,c=>this.browser.selectOption(c,e.option));return{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:l}}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 l;try{l=await Qa({code:e.code,fragment:!!e.fragment,timeoutMs:e.timeout?e.timeout*1e3:void 0,logger:this.logger})}catch(c){throw new x("ActionFailureError",c instanceof Error?c.message:`${c}`,{cause:c})}try{l=l===void 0?void 0:JSON.parse(JSON.stringify(l))}catch(c){throw new x("ActionFailureError",`Return value is not serializable: ${c instanceof Error?c.message:`${c}`}`,{cause:c})}return{urlAfterCommand:await this.browser.url(),succeedImmediately:!1,data:l}}case"TYPE":{let l=await this.browser.url(),c;e.target&&(c=await this.wrapElementTargetingCommand(e.target,e.useVision,i,p=>this.browser.click(p,{force:e.force}))),await this.browser.type(e.value,{clearContent:e.clearContent,pressKeysSequentially:e.pressKeysSequentially}),e.pressEnter&&await this.browser.press("Enter");let d={urlAfterCommand:await this.browser.url(),succeedImmediately:!1,elementInteracted:c};return qe(l,d.urlAfterCommand)&&(d.succeedImmediately=!0,d.succeedImmediatelyReason="URL changed"),d}case"HOVER":{let l=await this.wrapElementTargetingCommand(e.target,e.useVision,i,c=>this.browser.hover(c));return{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:l}}case"PRESS":let t=await this.browser.url();await this.browser.press(e.value);let n={urlAfterCommand:await this.browser.url(),succeedImmediately:!1};return qe(t,n.urlAfterCommand)&&(n.succeedImmediately=!0,n.succeedImmediatelyReason="URL changed"),n;case"REQUEST":{let l=e.timeout??30,c=null,d=new AbortController,p=Object.fromEntries(Object.entries(e.headers||{}).filter(([E,w])=>E&&w)),g=new URLSearchParams;Object.entries(e.params||{}).filter(([E,w])=>E&&w).forEach(([E,w])=>{g.append(E,w)});let u;if(Zs(e.url)&&(u=e.url),ei(e.url,this.browser.baseURL)&&(u=new URL(e.url,this.browser.baseURL).toString()),!u)throw new x("ActionFailureError",`Invalid URL: ${e.url}`);let h=async()=>{try{c=await fetch(`${u}?${g.toString()}`,{headers:p,method:e.method,body:e.body,signal:d.signal})}catch(E){this.logger.error({err:E},"Failed to make fetch request")}},k=async()=>{await new Promise(E=>setTimeout(E,l*1e3)),d.abort()};await Promise.race([k(),h()]);let f=c;if(!f)throw new x("ActionFailureError",`Fetch request timed out after ${l} seconds`);if(!f.ok)throw new x("ActionFailureError",`Fetch request failed with status ${f.status}`);let z={};f.headers.forEach((E,w)=>{z[w]=E});let v={status:f.status,headers:z};return f.headers.get("content-type")?.includes("json")?v.json=await f.json():f.headers.get("content-type")?.includes("text")&&(v.text=await f.text()),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),data:v}}default:return(l=>{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,i,r){return(await this.generator.getReverseMappedDescription({browserState:e.browserState,goal:`${i}`},r)).phrase}async toggleRecordMode({action:e,indices:i,onStepRecord:r}){if(e==="STOP"){this.recordAbortController?.abort();return}if(!i||!r)throw new Error("Indices or step recording callback not provided to toggleRecordMode");this.recordAbortController=new AbortController;let a=new qs({signal:this.recordAbortController.signal,initialIndices:i,generator:this.generator,onStepRecord:r});await this.browser.startRecording(this.recordAbortController.signal,a)}};async function Za({socket:s,generator:e,logger:i,rootController:r,localApp:a}){let t=i.child({package:"web-agent"}),n=!0;a==="iframe"&&(n=!1);let o=s.handshake.query.testId,l=s.handshake.query.baseUrl;if(!o)throw new Error("Socket connection is missing testID");let c=s.handshake.query.orgId,d=eo(),p=h=>{s.emit("screenshot",{...h,url:l})},g=r;if(g)g.browser.setActiveFrame(ps),g.browser.baseURL=l,await g.resetState({clearCookies:!0,clearStorage:!0,timeout:null}),g.setOpen();else{let h=await R.init({baseUrl:l,logger:t,takeScreenshots:n,onScreenshot:p});g=new ce({browser:h,generator:e,config:Te,logger:t})}n&&Ai(s,d,i),_.registerSession(g,d);let u=R.USER_AGENT;return s.emit("session",{url:l,userAgent:u,viewport:await g.browser.viewport(),localApp:a}),{sessionId:d,testId:o,orgId:c}}var er=[Ga,Ti,Ka,Wa,Va];var sr=s=>{let{logger:e}=s,i=new so(s.baseServer,{cors:{origin:"*",methods:["GET","POST"]}});return i.on("connection",async r=>{let a;try{a=await Za({...s,socket:r})}catch(t){e.error({event:"connection",type:"websocket",err:t},"Failed to setup connection"),r.emit("error",{message:`${t}`});return}er.forEach(t=>io(t,{socket:r,metadata:a,...s}))}),i},io=(s,e)=>{let i=s.createHandler(e),r=(...a)=>{e.logger.debug({event:s.event,metadata:e.metadata,args:a},"Websocket event");let t=n=>{e.logger.error({event:s.event,type:"websocket",args:a,err:n instanceof Error?n:new Error(`${n}`)},"Unhandled exception in socket handler"),e.socket.emit("error",{message:`${n}`})};try{let n=i.apply(void 0,a);n&&typeof n.catch=="function"&&n.catch(t)}catch(n){t(n)}};e.socket.on(s.event,r)};import ao from"fetch-retry";var ro=ao(global.fetch),ge="v1",Ce=class{baseURL;apiKey;constructor(e){this.baseURL=e.baseURL,this.apiKey=e.apiKey}async getElementLocation(e,i){let r=await this.sendRequest(`/${ge}/web-agent/locate-element`,{browserState:e.browserState,goal:e.goal,disableCache:i});return Ws.parse(r)}async getElementLocationWithVision(e,i){let r=await this.sendRequest(`/${ge}/web-agent/visual-locate`,{goal:e.goal,screenshot:e.screenshot?.toString("base64"),hintActivatedScreenshot:e.hintActivatedScreenshot?.toString("base64"),disableCache:i});return Ws.parse(r)}async getAssertionResult(e,i,r){if(i){let t=await this.sendRequest(`/${ge}/web-agent/assertion`,{url:e.url,goal:e.goal,screenshot:e.screenshot?.toString("base64"),disableCache:r,vision:!0});return Gs.parse(t)}let a=await this.sendRequest(`/${ge}/web-agent/assertion`,{url:e.url,browserState:e.browserState,goal:e.goal,history:e.history,numPrevious:e.numPrevious,lastCommand:e.lastCommand,disableCache:r,vision:!1});return Gs.parse(a)}async getProposedCommand(e,i){let r=await this.sendRequest(`/${ge}/web-agent/next-command`,{url:e.url,browserState:e.browserState,goal:e.goal,history:e.history,numPrevious:e.numPrevious,lastCommand:e.lastCommand,disableCache:i});return Yi.parse(r)}async getGranularGoals(e,i){let r=await this.sendRequest(`/${ge}/web-agent/split-goal`,{url:e.url,goal:e.goal,disableCache:i});return Xi.parse(r)}async getReverseMappedDescription(e,i){let r=await this.sendRequest(`/${ge}/web-agent/reverse-mapped-description`,{goal:e.goal,browserState:e.browserState,disableCache:i});return Ji.parse(r)}async getTextExtraction(e,i){let r={goal:e.goal,browserState:e.browserState,returnSchema:e.returnSchema,disableCache:i},a=await this.sendRequest(`/${ge}/web-agent/text-extraction`,r);return Ms.parse(a)}async sendRequest(e,i){let r=await ro(`${this.baseURL}${e}`,{retries:1,retryDelay:1e3,method:"POST",body:JSON.stringify(i),headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`}});if(!r.ok)throw new Error(`Request to ${e} failed with status ${r.status}: ${await r.text()}`);return r.json()}};import{existsSync as to,readFileSync as no,writeFileSync as di}from"fs";import ui from"path";import{parse as oo,stringify as lo}from"yaml";var D=process.env.MOMENTIC_DIR??pe;function ir(s,e){let i=oa(s);for(let[t,n]of Object.entries(i.modules)){let o=xe(t);di(ui.join(D,ne,`${o}.yaml`),n,"utf-8")}let r=xe(e),a=ui.join(D,`${r}.yaml`);return di(a,i.test,"utf-8"),`${r}.yaml`}function ar(s,e){if(!to(e))throw new Error(`Test not found at path: ${ui}`);let i=no(e,"utf-8"),a={...oo(i),...s},t=lo(a);di(e,t,"utf-8")}import{readFileSync as Ss,readdirSync as rr,writeFileSync as tr}from"fs";import{join as pi}from"path";import{v4 as co}from"uuid";import{parse as Es,stringify as nr}from"yaml";var Ye=pi(D,ne);function or(s,e){let i=fs(Ye,e).path,r=Ss(i,"utf-8"),t={...Es(r),...s},n=nr(t);tr(i,n,"utf-8")}function lr(s,e){let i=xe(s),r=pi(Ye,`${i}.yaml`),a={fileType:"momentic/module",schemaVersion:$,moduleId:co(),name:s,steps:e},t=nr(a);return tr(r,t,"utf-8"),{type:"RESOLVED_MODULE",moduleId:a.moduleId,name:a.name,steps:a.steps}}function cr(){let s=[];for(let e in rr(Ye)){if(!e.endsWith(".yaml"))continue;let i=Ss(e,"utf-8"),r=Es(i),a={name:r.name,moduleId:r.moduleId,numSteps:r.steps.length};s.push(a)}return s}function dr(){let s=[];for(let e of rr(Ye)){if(!e.endsWith(".yaml"))continue;let i=pi(Ye,e),r=Ss(i,"utf-8"),a=Es(r);try{let t=Ks.parse(a);s.push(t)}catch(t){F.warn({err:t},"Error parsing module, skipping...")}}return s}function ur(s){let e=fs(ne,s).path,i=Ss(e,"utf-8"),r=Es(i);return Ks.parse(r)}import{Router as ho}from"express";import{existsSync as uo,readFileSync as po,readdirSync as mo}from"fs";import{join as pr}from"path";import{parse as go}from"yaml";var mi=pr(D,us);function mr(){let s=[];if(!uo(mi))return[];for(let e of mo(mi)){if(!e.endsWith(".yaml"))continue;let i=pr(mi,e),r=po(i,"utf-8"),a=go(r);try{let t=K.parse(a);s.push(t)}catch(t){F.warn({err:t},"Error parsing environment, skipping...")}}return s}function V(s){return function(...e){let i=e[e.length-1],r=s(...e);Promise.resolve(r).catch(i)}}var gr=ho();gr.get("/",V((s,e)=>{let i=mr();e.status(200).json(i)}));var hr=gr;import{Router as bo}from"express";var Xe=bo();Xe.get("/",V((s,e)=>{let r=dr().map(a=>({...a,type:"RESOLVED_MODULE"}));e.status(200).json(r)}));Xe.get("/metadata",V((s,e)=>{let i=cr();e.status(200).json(i)}));Xe.post("/",V((s,e)=>{let i;try{i=Vi.parse(s.body)}catch(a){e.status(400).json({error:`Invalid request body: ${a}`});return}try{We(i.name)}catch(a){e.status(400).json({error:`Invalid module name: ${a}`});return}let r=lr(i.name,i.steps);e.status(201).json(r)}));Xe.get("/:moduleId",V((s,e)=>{if(!s.params.moduleId){e.status(400).json({error:"Missing moduleId in url path."});return}let i=ur(s.params.moduleId);e.json(i)}));var br=Xe;import{Router as yo}from"express";import{existsSync as fo}from"fs";import{join as gi}from"path";import{v4 as vo}from"uuid";var Je=yo();Je.get("/",V((s,e)=>{let i=Ve(D);e.status(200).json(i)}));Je.post("/",V((s,e)=>{let i;try{i=Wi.parse(s.body)}catch(o){e.status(400).json({error:`Invalid request body: ${o}`});return}try{We(i.name)}catch(o){e.status(400).json({error:o.message});return}let a={id:vo(),name:i.name,baseUrl:i.baseUrl,schemaVersion:$,advanced:{disableAICaching:!1,availableAsModule:!0},retries:0,steps:[]};i.environment&&(a.envs=[{name:i.environment,defaultOnLocal:!0}]);let t=ir(a,i.name),n={...a,testPath:t};e.status(201).json(n)}));Je.get("/:testPath",V(async(s,e)=>{if(!s.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let i=gi(D,s.params.testPath);if(!fo(i)){e.status(400).json({error:`Test not found at path: ${i}`});return}let r=await vs(i,gi(D,ne));e.status(200).json(r)}));Je.patch("/:testPath",V((s,e)=>{if(!s.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let i;try{i=Gi.parse(s.body)}catch(t){e.status(400).json({error:`Invalid request body: ${t}`});return}let r=[],a={};for(let t of i.steps){if(t.type!=="RESOLVED_MODULE"){r.push(t);continue}r.push({type:"MODULE",moduleId:t.moduleId}),a[t.moduleId]=t.steps}for(let[t,n]of Object.entries(a))or({steps:n},t);ar({steps:r},gi(D,s.params.testPath)),e.status(201).json({message:"ok"})}));var yr=Je;var hi="https://api.momentic.ai",vr=s=>{hi=s},kr=()=>hi,ko,wr=s=>{ko=s};var fr,zr=async s=>{if(fr)return;let e=await fetch(`${hi}/v1/auth/check`,{headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`}});if(!e.ok)throw new Error(`Error checking API key with server (code ${e.status}): ${await e.text()}`);let i=await e.json(),{orgId:r}=ta.parse(i);fr=r};import xr,{multistream as wo}from"pino";import zo from"pino-pretty";var bi=new Map,xo=process.env.NODE_ENV==="production",yi=class s{consoleLogger;ddClientToken;hostname;bindingAttributes;site="https://http-intake.logs.us5.datadoghq.com/api/v2/logs";constructor({bindings:e,clientToken:i,hostname:r}){this.ddClientToken=i,this.hostname=r,this.bindingAttributes={...e,env:process.env.NODE_ENV};let a={base:this.bindingAttributes,errorKey:"err",level:"debug"};this.consoleLogger=xo?xr(a):xr(a,wo([{stream:zo({colorize:!0})}]))}child(e){return new s({clientToken:this.ddClientToken,bindings:{...this.bindingAttributes,...e},hostname:this.hostname})}flush(e){this.consoleLogger.flush(e)}log(e,i,r,...a){i&&r===void 0&&(r=`${i}`,i={}),this.consoleLogger[e](i,r,...a);let t=Object.assign({},this.bindingAttributes,i&&typeof i=="object"?i:{});a.length>0&&(t.args=a),(async()=>{try{let n=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:r||"",...t}}])});if(!n.ok)throw new Error(`Failed to log to Datadog: ${n.statusText})}`)}catch(n){this.consoleLogger.warn({obj:i,msg:r,args:a,err:n},"Failed to log to Datadog")}})()}debug(e,i,...r){this.log("debug",e,i,...r)}info(e,i,...r){this.log("info",e,i,...r)}warn(e,i,...r){this.log("warn",e,i,...r)}error(e,i,...r){this.log("error",e,i,...r)}bindings(){return this.bindingAttributes}setMinLevel(e){this.consoleLogger.level=e}},As=({app:s,clientToken:e,hostname:i})=>{if(!process.env.DD_CLIENT_TOKEN&&!process.env.NEXT_PUBLIC_DD_CLIENT_TOKEN&&!e)throw new Error("Missing DD_CLIENT_TOKEN");return bi.has(s)||bi.set(s,new yi({bindings:{app:s},hostname:i,clientToken:e||process.env.NEXT_PUBLIC_DD_CLIENT_TOKEN||process.env.DD_CLIENT_TOKEN})),bi.get(s)};import{hostname as jo}from"os";var Z=As({app:"desktop-server",clientToken:"pubcfd7516a5c0ba852b42675cd97bee027",hostname:jo()});async function qr({momenticServerUrl:s,apiKey:e,mode:i,serverPort:r,appPort:a,staticDir:t,assetsDir:n}){if(!Eo(D)||!Ao(D).isDirectory()){let u=Ro.isAbsolute(D);throw new Error(`Root folder ${D} does not exist${u?"":` in the current directory ("${process.cwd()}")`}. Please run \`npx momentic init\` if you wish to setup Momentic here for the first time.`)}s&&vr(s),F.info("Checking API key"),await zr(e);let l=`http://localhost:${r}`;a&&(l=`http://localhost:${a}`);let c=Lo(t);await new Promise(u=>{c.listen(r,()=>{F.info(`Server is running at http://localhost:${r}`),u()})});let p=new Ce({baseURL:kr(),apiKey:e});if(i==="web"){await jr("web",c,p,Z),await Co(l);return}let g=await Oo({appUrl:l,apiGenerator:p,extensionPath:Io(n,"extension"),onClose:async()=>{F.info("Browser closed, closing app."),await g.browser.cleanup(),c.close(()=>{process.exit(0)})}});await jr("iframe",c,p,Z,g),process.on("uncaughtException",u=>{Z.error({err:u},"Uncaught exception in desktop-server")}),process.on("unhandledRejection",u=>{Z.error({err:u},"Unhandled rejection in desktop-server")})}function Lo(s){let e=fi();e.use(So()),e.use(qo.json({limit:"50mb"}));let i=fi.Router();if(i.use("/tests",yr),i.use("/modules",br),i.use("/environments",hr),e.use("/api",i),e.use((a,t,n)=>{a.path!=="/healthcheck"&&Z.debug({url:a.url,path:a.path,query:a.query,method:a.method,body:a.body,headers:a.rawHeaders,client:a.ip},"Received desktop-server request"),t.on("close",()=>{t.statusCode>=400&&Z.error({url:a.url,method:a.method,statusCode:t.statusCode},"Request completed in error")}),n()}),e.use((a,t,n,o)=>{Z.error({stack:a.stack,msg:a.message,url:t.url,method:t.method},"Unhandled exception leading to 500 on desktop-server"),n.status(500).send("Internal Server Error"),o(a)}),s){let a=fi.static(s);e.use("/",a),e.use("*",a)}return To.createServer(e)}async function Oo({appUrl:s,apiGenerator:e,extensionPath:i,onClose:r}){let a=await R.init({baseUrl:s,logger:Z,browserArgs:{headless:!1,handleSIGTERM:!0},contextArgs:{bypassCSP:!0,viewport:null,deviceScaleFactor:void 0},waitForLoad:!0,localMode:!0,localAppUrl:s,extensionPath:i,skipPageSetup:!0,timeout:null,onClose:r}),t=new ce({browser:a,config:Te,generator:e,logger:Z});return wr(t),t}async function jr(s,e,i,r,a){sr({baseServer:e,generator:i,logger:r,localApp:s,rootController:a})}import{existsSync as ml}from"fs";import Jr,{dirname as gl}from"path";import{fileURLToPath as hl}from"url";var Sr="0.0.36";var Y="v1",ee=class{baseURL;apiKey;constructor(e){this.baseURL=e.baseURL,this.apiKey=e.apiKey}async getRun(e){let i=await this.sendRequest(`/${Y}/runs/${e}`,{method:"GET"});return aa.parse(i)}async createRun(e){let i=await this.sendRequest(`/${Y}/runs`,{method:"POST",body:e});return ia.parse(i)}async updateRun(e,i){await this.sendRequest(`/${Y}/runs/${e}`,{method:"PATCH",body:i})}async getTest(e){let i=await this.sendRequest(`/${Y}/tests/${e}`,{method:"GET"});return Zi.parse(i)}async getAllTestIds(){let e=await this.sendRequest(`/${Y}/tests`,{method:"GET"});return ea.parse(e)}async getTestYAMLExport(e){let i=await this.sendRequest(`/${Y}/tests/export`,{method:"POST",body:e});return sa.parse(i)}async updateTestWithYAML(e){await this.sendRequest(`/${Y}/tests/update`,{method:"POST",body:e})}async queueTests(e){let i=await this.sendRequest(`/${Y}/tests/queue`,{method:"POST",body:e});return Qi.parse(i)}async uploadScreenshot(e){let i=await this.sendRequest(`/${Y}/screenshots`,{method:"POST",body:e});return ra.parse(i)}async getAllEnvironments(){let e=await this.sendRequest(`/${Y}/environments`,{method:"GET"});return na.parse(e)}async updateEnvironments(e){await this.sendRequest(`/${Y}/environments`,{method:"POST",body:e})}async sendRequest(e,i){let r=await fetch(`${this.baseURL}${e}`,{method:i.method,body:i.body?JSON.stringify(i.body):void 0,headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`}});if(!r.ok)throw new Error(`Request to ${e} failed with status ${r.status}: ${await r.text()}`);return r.status===204?r.text():r.json()}};import{hostname as No}from"os";var b=As({app:"cli",clientToken:"pub7eb923f18fb3f1d42ac5eba8c5ea13a5",hostname:No()});import{createHash as Tr}from"crypto";import{existsSync as Cr,mkdirSync as $o,readFileSync as zi,writeFileSync as Rr}from"fs";import{join as Ir}from"path";import{parse as Ho}from"yaml";import{existsSync as vi,mkdirSync as Qe,readdirSync as Ar,statSync as ki}from"fs";import{homedir as _o}from"os";import{join as Ze,resolve as Fo}from"path";import Po from"chalk";import Do from"readline/promises";async function se(s,e){if(process.env.CI)return!0;let i=Do.createInterface({input:process.stdin,output:process.stdout});s=`${s} (y/N) `;let r=e?Po.bold.yellow(s):s,a=await i.question(r);return i.close(),a.toLowerCase()==="y"}var Ts=pe,Re=Ze(pe,ne),wi=Ze(pe,Bi),X=Ze(pe,us),Uo=Ze(_o(),"momentic","chromium");function Er(s){return vi(s)&&ki(s).isDirectory()}async function he(s,e=!1){Er(Ts)||(!e&&!await se(`A '${pe}' folder was not found in the current directory. Setup the required Momentic folder structure?`)&&(b.error("Setup cancelled"),process.exit(1)),Qe(Ts),Qe(Re),Qe(wi),Qe(X),Qe(Uo,{recursive:!0})),(!Er(X)||Ar(X).length===0)&&(!e&&!await se("You do not appear to have any environments configured locally. Pull environments from Momentic Cloud?")&&(b.error("Setup cancelled"),process.exit(1)),await Cs({client:s,all:!0,skipPrompts:e})),b.info("CLI initialization complete")}function es(s,e,i=new Set){for(let r of s){let a=Fo(r);if(a&&vi(a)&&ki(a).isDirectory()){let t=Ar(a).map(n=>Ze(a,n));es(t,e,i);continue}if(a.endsWith(".yaml")){if(!vi(a)||!ki(a).isFile())throw new Error(`File not found or unreadable: ${a}`);if(!e(a))continue;i.add(a)}}return i}async function Cs({envNames:s,client:e,all:i,skipPrompts:r}){let a=await e.getAllEnvironments();s&&!i&&(a=a.filter(o=>s?.includes(o.name))),Cr(X)||$o(X);let t=0,n=0;for(let o of a){let l=Ir(X,`${o.name}.yaml`);if(!Cr(l))Rr(l,Ys(o)),t++;else{let c=Tr("sha256").update(zi(l)).digest("hex"),d=Ys(o),p=Tr("sha256").update(d).digest("hex");if(c!==p){if(!r&&!await se(`Environment ${o.name} already exists on disk but needs to be updated. Overwrite?`)){b.error("Pull cancelled");return}Rr(l,d),n++}}}b.info(`Pulled ${t} new environments and updated ${n} existing environments`)}function Bo(s){return s.endsWith(".yaml")?zi(s,"utf8").includes("momentic/environment")?!0:(b.warn(`Skipping YAML that is not a Momentic environment: ${s}`),!1):!1}async function Lr({client:s,names:e,all:i,yes:r}){let a;i?a=[X]:a=e.map(o=>Ir(X,`${o.toLowerCase()}.yaml`));let t=es(a,Bo);b.info(`Found ${t.size} environments(s) to push:`),t.forEach(o=>b.info(` - ${o}`)),b.info("Loading file contents");let n=[];for(let o of t){let l=Ho(zi(o,"utf-8"));try{let c=K.parse(l);n.push(c)}catch(c){b.error({err:c},`${o} failed to parse as a valid environment file`),process.exit(1)}}if(!r&&!await se("Pushing environments can overwrite variables on cloud and instantly affect scheduled runs. Continue?",!0)){b.error("Push cancelled");return}b.info(`Pushing ${t.size} environment(s)`),await s.updateEnvironments(n),b.info("Push successful!")}import{registry as xi}from"playwright-core/lib/server";function Go(s){let e=[],i=[];for(let r of s){let a=xi.findExecutable(r);!a||a.installType==="none"?e.push(r):i.push(a)}return i}async function Or(){let s=Go(["chromium"]);await xi.installDeps(s,!1),await xi.install(s,!1)}import{Argument as Rs,Option as Ie}from"commander";var be=new Ie("--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),ye=new Ie("--server <server>","Momentic server to use. Leave unchanged unless using Momentic on-premise.").default("https://api.momentic.ai"),fe=new Ie("-y, --yes","Skip confirmation prompts.").env("CI").default(!1),Mr=new Ie("--no-report","Skip reporting test results to Momentic Cloud when running with the --local flag.").default(!0),Nr=new Ie("--env <env>","Name of the environment to use when running tests."),ji=new Ie("-a, --all","Select all tests in your organization from Momentic Cloud. Cannot be used together with <tests> arguments."),qi=new Ie("-a, --all","Select all environments in your organization from Momentic Cloud. Cannot be used together with <paths> arguments."),Pr=new Rs("<tests...>",`One or more test paths to pull from Momentic Cloud.
29
+ `)}`}async executeCommand(e,i,r=!1){let a=this.commandHistory[this.commandHistory.length-1];if(!r&&(!a||a.state!=="PENDING"))throw new x("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 t=Date.now(),n=await this.executePresetStep(e,i),o=Date.now()-t;return this.logger.debug({result:n,duration:o},"Got execution result"),n.succeedImmediately&&!r&&(this.pendingInstructions.pop(),this.pendingInstructions.length>0&&(n.succeedImmediately=!1)),n.elementInteracted&&"target"in e&&e.target&&!e.target.elementDescriptor&&(e.target.elementDescriptor=n.elementInteracted.trim()),r||(a.generatedStep=e,a.serializedCommand=Me(e),a.state="DONE"),n}async executeAssertion(e){let i=await this.getBrowserState(),r=await this.browser.url(),a;if(e.useVision)a={goal:e.assertion,url:r,screenshot:await this.browser.screenshot(),browserState:"",history:"",numPrevious:-1,lastCommand:null};else{let n=this.getSerializedHistory(r,i);a={goal:e.assertion,url:r,browserState:i,history:n,lastCommand:this.lastExecutedCommand,numPrevious:this.commandHistory.length}}let t=await this.generator.getAssertionResult(a,e.useVision,e.disableCache);if(t.relevantElements&&Promise.all(t.relevantElements.map(n=>this.browser.highlight({id:n}))),!t.result)throw new x("AssertionFailureError",t.thoughts);return{succeedImmediately:!1,thoughts:t.thoughts,urlAfterCommand:r}}async wrapMultiElementTargetingCommand(e,i,r,...a){let t=await Promise.all(a.map(n=>this.wrapElementTargetingCommand(n,i.useVision,i.disableCache,async o=>o)));try{return await e(...t)}catch(n){if(r>0)return this.logger.warn({err:n},"Failed to execute action with multiple cached targets, retrying with AI"),a.forEach(o=>o.a11yData=void 0),this.wrapMultiElementTargetingCommand(e,i,r-1,...a);throw new x("ActionFailureError","",{cause:n})}}async wrapElementTargetingCommand(e,i,r,a,t=1){if(!e.a11yData&&!e.elementDescriptor)throw new x("ActionFailureError","Cannot target element with no target data or element descriptor");let n=e.a11yData&&as(e.a11yData);e.a11yData||(this.logger.info("No cached locator data for target, prompting AI for fresh location"),t--,e.a11yData=is.parse(await this.locateElement(e.elementDescriptor,i,r)));try{let o=await a(e.a11yData);return n?this.logger.info({target:e},"Successfully used cached target to perform action"):this.logger.debug({target:e},"Successfully generated and used new a11y target information"),o}catch(o){if(t>0&&e.elementDescriptor)return this.logger.warn({err:o,target:e},"Failed to execute action with cached target, retrying with AI"),e.a11yData=void 0,this.wrapElementTargetingCommand(e,i,r,a,t);if(o instanceof x)throw o;let l=`Failed to find ${e.elementDescriptor?`${e.elementDescriptor}`:"element"}: ${o instanceof Error?o.message:o}`;throw this.logger.error({err:o,target:e},l),new x("ActionFailureError",l,{cause:o})}}async executePresetStep(e,i){try{return await this.executePresetStepHelper(e,i)}catch(r){throw r instanceof x?r:new x("InternalWebAgentError","",{cause:r})}}async executePresetStepHelper(e,i){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 x("ActionFailureError","Missing assertion");return this.executeAssertion(e)}case"AI_WAIT":{if(!e.assertion.trim())throw new x("ActionFailureError","Missing assertion");let l=Date.now();if(e.timeout&&e.timeout>30)throw new x("AssertionFailureError",`AI wait timeout of ${e.timeout} exceeds the maximum allowed timeout of 30s`);let c=(e.timeout??10)*1e3,d,p,g=0;for(;Date.now()-l<c;)try{d=await this.executeAssertion({...e,type:"AI_ASSERTION"});break}catch(u){p=u instanceof Error?u:new Error(`${u}`),this.logger.warn({err:u},`AI_WAIT assert attempt ${g} failed, retrying...`),g++,await U(c/10)}if(!d){let u=`AI wait still failing after ${c}ms.`;throw p&&(u+=` Latest result: ${p.message}`),new x("AssertionFailureError",u)}return d}case"AI_EXTRACT":{if(!e.goal.trim())throw new x("ActionFailureError","Cannot perform AI extraction without goal");let l=await this.browser.getHTML(),c=await this.generator.getTextExtraction({goal:e.goal,browserState:l,returnSchema:e.schema},e.disableCache);if(c.result==="NOT_FOUND")throw new x("ActionFailureError","No relevant data found for extraction goal on this page");return{data:c.result,succeedImmediately:!1,urlAfterCommand:await this.browser.url()}}case"NAVIGATE":if(!Zs(e.url)&&!ei(e.url,this.browser.baseURL))throw new x("ActionFailureError",`Invalid URL: ${e.url}`);await this.browser.navigate({url:e.url});break;case"CAPTCHA":let r=await this.browser.solveCaptcha();r&&(await this.wrapElementTargetingCommand({elementDescriptor:"the captcha image solution input"},e.useVision,i,l=>this.browser.click(l)),await this.browser.type(r,{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 a;switch(e.target&&(e.target.elementDescriptor.trim()||e.target.a11yData)&&(a=await this.wrapElementTargetingCommand(e.target,e.useVision,i,l=>this.browser.hover(l))),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:a};case"WAIT":await this.browser.wait(e.delay*1e3);break;case"REFRESH":await this.browser.refresh();break;case"CLICK":{let l=await this.browser.url(),c=await this.wrapElementTargetingCommand(e.target,e.useVision,i,p=>this.browser.click(p,{doubleClick:e.doubleClick,rightClick:e.rightClick,force:e.force})),d={urlAfterCommand:await this.browser.url(),succeedImmediately:!1,elementInteracted:c};return qe(l,d.urlAfterCommand)&&(d.succeedImmediately=!0,d.succeedImmediatelyReason="URL changed"),d}case"DRAG":{let l=await this.wrapMultiElementTargetingCommand((c,d)=>this.browser.dragAndDrop(c,d,{force:e.force}),{useVision:e.useVision,disableCache:i},1,e.fromTarget,e.toTarget);return{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:l}}case"SELECT_OPTION":{let l=await this.wrapElementTargetingCommand(e.target,!1,i,c=>this.browser.selectOption(c,e.option));return{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:l}}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 l;try{l=await Qa({code:e.code,fragment:!!e.fragment,timeoutMs:e.timeout?e.timeout*1e3:void 0,logger:this.logger})}catch(c){throw new x("ActionFailureError",c instanceof Error?c.message:`${c}`,{cause:c})}try{l=l===void 0?void 0:JSON.parse(JSON.stringify(l))}catch(c){throw new x("ActionFailureError",`Return value is not serializable: ${c instanceof Error?c.message:`${c}`}`,{cause:c})}return{urlAfterCommand:await this.browser.url(),succeedImmediately:!1,data:l}}case"TYPE":{let l=await this.browser.url(),c;e.target&&(c=await this.wrapElementTargetingCommand(e.target,e.useVision,i,p=>this.browser.click(p,{force:e.force}))),await this.browser.type(e.value,{clearContent:e.clearContent,pressKeysSequentially:e.pressKeysSequentially}),e.pressEnter&&await this.browser.press("Enter");let d={urlAfterCommand:await this.browser.url(),succeedImmediately:!1,elementInteracted:c};return qe(l,d.urlAfterCommand)&&(d.succeedImmediately=!0,d.succeedImmediatelyReason="URL changed"),d}case"HOVER":{let l=await this.wrapElementTargetingCommand(e.target,e.useVision,i,c=>this.browser.hover(c));return{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:l}}case"PRESS":let t=await this.browser.url();await this.browser.press(e.value);let n={urlAfterCommand:await this.browser.url(),succeedImmediately:!1};return qe(t,n.urlAfterCommand)&&(n.succeedImmediately=!0,n.succeedImmediatelyReason="URL changed"),n;case"REQUEST":{let l=e.timeout??30,c=null,d=new AbortController,p=Object.fromEntries(Object.entries(e.headers||{}).filter(([E,w])=>E&&w)),g=new URLSearchParams;Object.entries(e.params||{}).filter(([E,w])=>E&&w).forEach(([E,w])=>{g.append(E,w)});let u;if(Zs(e.url)&&(u=e.url),ei(e.url,this.browser.baseURL)&&(u=new URL(e.url,this.browser.baseURL).toString()),!u)throw new x("ActionFailureError",`Invalid URL: ${e.url}`);let h=async()=>{try{c=await fetch(`${u}?${g.toString()}`,{headers:p,method:e.method,body:e.body,signal:d.signal})}catch(E){this.logger.error({err:E},"Failed to make fetch request")}},k=async()=>{await new Promise(E=>setTimeout(E,l*1e3)),d.abort()};await Promise.race([k(),h()]);let f=c;if(!f)throw new x("ActionFailureError",`Fetch request timed out after ${l} seconds`);if(!f.ok)throw new x("ActionFailureError",`Fetch request failed with status ${f.status}`);let z={};f.headers.forEach((E,w)=>{z[w]=E});let v={status:f.status,headers:z};return f.headers.get("content-type")?.includes("json")?v.json=await f.json():f.headers.get("content-type")?.includes("text")&&(v.text=await f.text()),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),data:v}}default:return(l=>{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,i,r){return(await this.generator.getReverseMappedDescription({browserState:e.browserState,goal:`${i}`},r)).phrase}async toggleRecordMode({action:e,indices:i,onStepRecord:r}){if(e==="STOP"){this.recordAbortController?.abort();return}if(!i||!r)throw new Error("Indices or step recording callback not provided to toggleRecordMode");this.recordAbortController=new AbortController;let a=new qs({signal:this.recordAbortController.signal,initialIndices:i,generator:this.generator,onStepRecord:r});await this.browser.startRecording(this.recordAbortController.signal,a)}};async function Za({socket:s,generator:e,logger:i,rootController:r,localApp:a}){let t=i.child({package:"web-agent"}),n=!0;a==="iframe"&&(n=!1);let o=s.handshake.query.testId,l=s.handshake.query.baseUrl;if(!o)throw new Error("Socket connection is missing testID");let c=s.handshake.query.orgId,d=eo(),p=h=>{s.emit("screenshot",{...h,url:l})},g=r;if(g)g.browser.setActiveFrame(ps),g.browser.baseURL=l,await g.resetState({clearCookies:!0,clearStorage:!0,timeout:null}),g.setOpen();else{let h=await R.init({baseUrl:l,logger:t,takeScreenshots:n,onScreenshot:p});g=new ce({browser:h,generator:e,config:Te,logger:t})}n&&Ai(s,d,i),_.registerSession(g,d);let u=R.USER_AGENT;return s.emit("session",{url:l,userAgent:u,viewport:await g.browser.viewport(),localApp:a}),{sessionId:d,testId:o,orgId:c}}var er=[Ga,Ti,Ka,Wa,Va];var sr=s=>{let{logger:e}=s,i=new so(s.baseServer,{cors:{origin:"*",methods:["GET","POST"]}});return i.on("connection",async r=>{let a;try{a=await Za({...s,socket:r})}catch(t){e.error({event:"connection",type:"websocket",err:t},"Failed to setup connection"),r.emit("error",{message:`${t}`});return}er.forEach(t=>io(t,{socket:r,metadata:a,...s}))}),i},io=(s,e)=>{let i=s.createHandler(e),r=(...a)=>{e.logger.debug({event:s.event,metadata:e.metadata,args:a},"Websocket event");let t=n=>{e.logger.error({event:s.event,type:"websocket",args:a,err:n instanceof Error?n:new Error(`${n}`)},"Unhandled exception in socket handler"),e.socket.emit("error",{message:`${n}`})};try{let n=i.apply(void 0,a);n&&typeof n.catch=="function"&&n.catch(t)}catch(n){t(n)}};e.socket.on(s.event,r)};import ao from"fetch-retry";var ro=ao(global.fetch),ge="v1",Ce=class{baseURL;apiKey;constructor(e){this.baseURL=e.baseURL,this.apiKey=e.apiKey}async getElementLocation(e,i){let r=await this.sendRequest(`/${ge}/web-agent/locate-element`,{browserState:e.browserState,goal:e.goal,disableCache:i});return Ws.parse(r)}async getElementLocationWithVision(e,i){let r=await this.sendRequest(`/${ge}/web-agent/visual-locate`,{goal:e.goal,screenshot:e.screenshot?.toString("base64"),hintActivatedScreenshot:e.hintActivatedScreenshot?.toString("base64"),disableCache:i});return Ws.parse(r)}async getAssertionResult(e,i,r){if(i){let t=await this.sendRequest(`/${ge}/web-agent/assertion`,{url:e.url,goal:e.goal,screenshot:e.screenshot?.toString("base64"),disableCache:r,vision:!0});return Gs.parse(t)}let a=await this.sendRequest(`/${ge}/web-agent/assertion`,{url:e.url,browserState:e.browserState,goal:e.goal,history:e.history,numPrevious:e.numPrevious,lastCommand:e.lastCommand,disableCache:r,vision:!1});return Gs.parse(a)}async getProposedCommand(e,i){let r=await this.sendRequest(`/${ge}/web-agent/next-command`,{url:e.url,browserState:e.browserState,goal:e.goal,history:e.history,numPrevious:e.numPrevious,lastCommand:e.lastCommand,disableCache:i});return Yi.parse(r)}async getGranularGoals(e,i){let r=await this.sendRequest(`/${ge}/web-agent/split-goal`,{url:e.url,goal:e.goal,disableCache:i});return Xi.parse(r)}async getReverseMappedDescription(e,i){let r=await this.sendRequest(`/${ge}/web-agent/reverse-mapped-description`,{goal:e.goal,browserState:e.browserState,disableCache:i});return Ji.parse(r)}async getTextExtraction(e,i){let r={goal:e.goal,browserState:e.browserState,returnSchema:e.returnSchema,disableCache:i},a=await this.sendRequest(`/${ge}/web-agent/text-extraction`,r);return Ms.parse(a)}async sendRequest(e,i){let r=await ro(`${this.baseURL}${e}`,{retries:1,retryDelay:1e3,method:"POST",body:JSON.stringify(i),headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`}});if(!r.ok)throw new Error(`Request to ${e} failed with status ${r.status}: ${await r.text()}`);return r.json()}};import{existsSync as to,readFileSync as no,writeFileSync as di}from"fs";import ui from"path";import{parse as oo,stringify as lo}from"yaml";var D=process.env.MOMENTIC_DIR??pe;function ir(s,e){let i=oa(s);for(let[t,n]of Object.entries(i.modules)){let o=xe(t);di(ui.join(D,ne,`${o}.yaml`),n,"utf-8")}let r=xe(e),a=ui.join(D,`${r}.yaml`);return di(a,i.test,"utf-8"),`${r}.yaml`}function ar(s,e){if(!to(e))throw new Error(`Test not found at path: ${ui}`);let i=no(e,"utf-8"),a={...oo(i),...s},t=lo(a);di(e,t,"utf-8")}import{readFileSync as Ss,readdirSync as rr,writeFileSync as tr}from"fs";import{join as pi}from"path";import{v4 as co}from"uuid";import{parse as Es,stringify as nr}from"yaml";var Ye=pi(D,ne);function or(s,e){let i=fs(Ye,e).path,r=Ss(i,"utf-8"),t={...Es(r),...s},n=nr(t);tr(i,n,"utf-8")}function lr(s,e){let i=xe(s),r=pi(Ye,`${i}.yaml`),a={fileType:"momentic/module",schemaVersion:$,moduleId:co(),name:s,steps:e},t=nr(a);return tr(r,t,"utf-8"),{type:"RESOLVED_MODULE",moduleId:a.moduleId,name:a.name,steps:a.steps}}function cr(){let s=[];for(let e in rr(Ye)){if(!e.endsWith(".yaml"))continue;let i=Ss(e,"utf-8"),r=Es(i),a={name:r.name,moduleId:r.moduleId,numSteps:r.steps.length};s.push(a)}return s}function dr(){let s=[];for(let e of rr(Ye)){if(!e.endsWith(".yaml"))continue;let i=pi(Ye,e),r=Ss(i,"utf-8"),a=Es(r);try{let t=Ks.parse(a);s.push(t)}catch(t){F.warn({err:t},"Error parsing module, skipping...")}}return s}function ur(s){let e=fs(ne,s).path,i=Ss(e,"utf-8"),r=Es(i);return Ks.parse(r)}import{Router as ho}from"express";import{existsSync as uo,readFileSync as po,readdirSync as mo}from"fs";import{join as pr}from"path";import{parse as go}from"yaml";var mi=pr(D,us);function mr(){let s=[];if(!uo(mi))return[];for(let e of mo(mi)){if(!e.endsWith(".yaml"))continue;let i=pr(mi,e),r=po(i,"utf-8"),a=go(r);try{let t=K.parse(a);s.push(t)}catch(t){F.warn({err:t},"Error parsing environment, skipping...")}}return s}function V(s){return function(...e){let i=e[e.length-1],r=s(...e);Promise.resolve(r).catch(i)}}var gr=ho();gr.get("/",V((s,e)=>{let i=mr();e.status(200).json(i)}));var hr=gr;import{Router as bo}from"express";var Xe=bo();Xe.get("/",V((s,e)=>{let r=dr().map(a=>({...a,type:"RESOLVED_MODULE"}));e.status(200).json(r)}));Xe.get("/metadata",V((s,e)=>{let i=cr();e.status(200).json(i)}));Xe.post("/",V((s,e)=>{let i;try{i=Vi.parse(s.body)}catch(a){e.status(400).json({error:`Invalid request body: ${a}`});return}try{We(i.name)}catch(a){e.status(400).json({error:`Invalid module name: ${a}`});return}let r=lr(i.name,i.steps);e.status(201).json(r)}));Xe.get("/:moduleId",V((s,e)=>{if(!s.params.moduleId){e.status(400).json({error:"Missing moduleId in url path."});return}let i=ur(s.params.moduleId);e.json(i)}));var br=Xe;import{Router as yo}from"express";import{existsSync as fo}from"fs";import{join as gi}from"path";import{v4 as vo}from"uuid";var Je=yo();Je.get("/",V((s,e)=>{let i=Ve(D);e.status(200).json(i)}));Je.post("/",V((s,e)=>{let i;try{i=Wi.parse(s.body)}catch(o){e.status(400).json({error:`Invalid request body: ${o}`});return}try{We(i.name)}catch(o){e.status(400).json({error:o.message});return}let a={id:vo(),name:i.name,baseUrl:i.baseUrl,schemaVersion:$,advanced:{disableAICaching:!1,availableAsModule:!0},retries:0,steps:[]};i.environment&&(a.envs=[{name:i.environment,defaultOnLocal:!0}]);let t=ir(a,i.name),n={...a,testPath:t};e.status(201).json(n)}));Je.get("/:testPath",V(async(s,e)=>{if(!s.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let i=gi(D,s.params.testPath);if(!fo(i)){e.status(400).json({error:`Test not found at path: ${i}`});return}let r=await vs(i,gi(D,ne));e.status(200).json(r)}));Je.patch("/:testPath",V((s,e)=>{if(!s.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let i;try{i=Gi.parse(s.body)}catch(t){e.status(400).json({error:`Invalid request body: ${t}`});return}let r=[],a={};for(let t of i.steps){if(t.type!=="RESOLVED_MODULE"){r.push(t);continue}r.push({type:"MODULE",moduleId:t.moduleId}),a[t.moduleId]=t.steps}for(let[t,n]of Object.entries(a))or({steps:n},t);ar({steps:r},gi(D,s.params.testPath)),e.status(201).json({message:"ok"})}));var yr=Je;var hi="https://api.momentic.ai",vr=s=>{hi=s},kr=()=>hi,ko,wr=s=>{ko=s};var fr,zr=async s=>{if(fr)return;let e=await fetch(`${hi}/v1/auth/check`,{headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`}});if(!e.ok)throw new Error(`Error checking API key with server (code ${e.status}): ${await e.text()}`);let i=await e.json(),{orgId:r}=ta.parse(i);fr=r};import xr,{multistream as wo}from"pino";import zo from"pino-pretty";var bi=new Map,xo=process.env.NODE_ENV==="production",yi=class s{consoleLogger;ddClientToken;hostname;bindingAttributes;site="https://http-intake.logs.us5.datadoghq.com/api/v2/logs";constructor({bindings:e,clientToken:i,hostname:r}){this.ddClientToken=i,this.hostname=r,this.bindingAttributes={...e,env:process.env.NODE_ENV};let a={base:this.bindingAttributes,errorKey:"err",level:"debug"};this.consoleLogger=xo?xr(a):xr(a,wo([{stream:zo({colorize:!0})}]))}child(e){return new s({clientToken:this.ddClientToken,bindings:{...this.bindingAttributes,...e},hostname:this.hostname})}flush(e){this.consoleLogger.flush(e)}log(e,i,r,...a){i&&r===void 0&&(r=`${i}`,i={}),this.consoleLogger[e](i,r,...a);let t=Object.assign({},this.bindingAttributes,i&&typeof i=="object"?i:{});a.length>0&&(t.args=a),(async()=>{try{let n=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:r||"",...t}}])});if(!n.ok)throw new Error(`Failed to log to Datadog: ${n.statusText})}`)}catch(n){this.consoleLogger.warn({obj:i,msg:r,args:a,err:n},"Failed to log to Datadog")}})()}debug(e,i,...r){this.log("debug",e,i,...r)}info(e,i,...r){this.log("info",e,i,...r)}warn(e,i,...r){this.log("warn",e,i,...r)}error(e,i,...r){this.log("error",e,i,...r)}bindings(){return this.bindingAttributes}setMinLevel(e){this.consoleLogger.level=e}},As=({app:s,clientToken:e,hostname:i})=>{if(!process.env.DD_CLIENT_TOKEN&&!process.env.NEXT_PUBLIC_DD_CLIENT_TOKEN&&!e)throw new Error("Missing DD_CLIENT_TOKEN");return bi.has(s)||bi.set(s,new yi({bindings:{app:s},hostname:i,clientToken:e||process.env.NEXT_PUBLIC_DD_CLIENT_TOKEN||process.env.DD_CLIENT_TOKEN})),bi.get(s)};import{hostname as jo}from"os";var Z=As({app:"desktop-server",clientToken:"pubcfd7516a5c0ba852b42675cd97bee027",hostname:jo()});async function qr({momenticServerUrl:s,apiKey:e,mode:i,serverPort:r,appPort:a,staticDir:t,assetsDir:n}){if(!Eo(D)||!Ao(D).isDirectory()){let u=Ro.isAbsolute(D);throw new Error(`Root folder ${D} does not exist${u?"":` in the current directory ("${process.cwd()}")`}. Please run \`npx momentic init\` if you wish to setup Momentic here for the first time.`)}s&&vr(s),F.info("Checking API key"),await zr(e);let l=`http://localhost:${r}`;a&&(l=`http://localhost:${a}`);let c=Lo(t);await new Promise(u=>{c.listen(r,()=>{F.info(`Server is running at http://localhost:${r}`),u()})});let p=new Ce({baseURL:kr(),apiKey:e});if(i==="web"){await jr("web",c,p,Z),await Co(l);return}let g=await Oo({appUrl:l,apiGenerator:p,extensionPath:Io(n,"extension"),onClose:async()=>{F.info("Browser closed, closing app."),await g.browser.cleanup(),c.close(()=>{process.exit(0)})}});await jr("iframe",c,p,Z,g),process.on("uncaughtException",u=>{Z.error({err:u},"Uncaught exception in desktop-server")}),process.on("unhandledRejection",u=>{Z.error({err:u},"Unhandled rejection in desktop-server")})}function Lo(s){let e=fi();e.use(So()),e.use(qo.json({limit:"50mb"}));let i=fi.Router();if(i.use("/tests",yr),i.use("/modules",br),i.use("/environments",hr),e.use("/api",i),e.use((a,t,n)=>{a.path!=="/healthcheck"&&Z.debug({url:a.url,path:a.path,query:a.query,method:a.method,body:a.body,headers:a.rawHeaders,client:a.ip},"Received desktop-server request"),t.on("close",()=>{t.statusCode>=400&&Z.error({url:a.url,method:a.method,statusCode:t.statusCode},"Request completed in error")}),n()}),e.use((a,t,n,o)=>{Z.error({stack:a.stack,msg:a.message,url:t.url,method:t.method},"Unhandled exception leading to 500 on desktop-server"),n.status(500).send("Internal Server Error"),o(a)}),s){let a=fi.static(s);e.use("/",a),e.use("*",a)}return To.createServer(e)}async function Oo({appUrl:s,apiGenerator:e,extensionPath:i,onClose:r}){let a=await R.init({baseUrl:s,logger:Z,browserArgs:{headless:!1,handleSIGTERM:!0},contextArgs:{bypassCSP:!0,viewport:null,deviceScaleFactor:void 0},waitForLoad:!0,localMode:!0,localAppUrl:s,extensionPath:i,skipPageSetup:!0,timeout:null,onClose:r}),t=new ce({browser:a,config:Te,generator:e,logger:Z});return wr(t),t}async function jr(s,e,i,r,a){sr({baseServer:e,generator:i,logger:r,localApp:s,rootController:a})}import{existsSync as ml}from"fs";import Jr,{dirname as gl}from"path";import{fileURLToPath as hl}from"url";var Sr="0.0.37";var Y="v1",ee=class{baseURL;apiKey;constructor(e){this.baseURL=e.baseURL,this.apiKey=e.apiKey}async getRun(e){let i=await this.sendRequest(`/${Y}/runs/${e}`,{method:"GET"});return aa.parse(i)}async createRun(e){let i=await this.sendRequest(`/${Y}/runs`,{method:"POST",body:e});return ia.parse(i)}async updateRun(e,i){await this.sendRequest(`/${Y}/runs/${e}`,{method:"PATCH",body:i})}async getTest(e){let i=await this.sendRequest(`/${Y}/tests/${e}`,{method:"GET"});return Zi.parse(i)}async getAllTestIds(){let e=await this.sendRequest(`/${Y}/tests`,{method:"GET"});return ea.parse(e)}async getTestYAMLExport(e){let i=await this.sendRequest(`/${Y}/tests/export`,{method:"POST",body:e});return sa.parse(i)}async updateTestWithYAML(e){await this.sendRequest(`/${Y}/tests/update`,{method:"POST",body:e})}async queueTests(e){let i=await this.sendRequest(`/${Y}/tests/queue`,{method:"POST",body:e});return Qi.parse(i)}async uploadScreenshot(e){let i=await this.sendRequest(`/${Y}/screenshots`,{method:"POST",body:e});return ra.parse(i)}async getAllEnvironments(){let e=await this.sendRequest(`/${Y}/environments`,{method:"GET"});return na.parse(e)}async updateEnvironments(e){await this.sendRequest(`/${Y}/environments`,{method:"POST",body:e})}async sendRequest(e,i){let r=await fetch(`${this.baseURL}${e}`,{method:i.method,body:i.body?JSON.stringify(i.body):void 0,headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`}});if(!r.ok)throw new Error(`Request to ${e} failed with status ${r.status}: ${await r.text()}`);return r.status===204?r.text():r.json()}};import{hostname as No}from"os";var b=As({app:"cli",clientToken:"pub7eb923f18fb3f1d42ac5eba8c5ea13a5",hostname:No()});import{createHash as Tr}from"crypto";import{existsSync as Cr,mkdirSync as $o,readFileSync as zi,writeFileSync as Rr}from"fs";import{join as Ir}from"path";import{parse as Ho}from"yaml";import{existsSync as vi,mkdirSync as Qe,readdirSync as Ar,statSync as ki}from"fs";import{homedir as _o}from"os";import{join as Ze,resolve as Fo}from"path";import Po from"chalk";import Do from"readline/promises";async function se(s,e){if(process.env.CI)return!0;let i=Do.createInterface({input:process.stdin,output:process.stdout});s=`${s} (y/N) `;let r=e?Po.bold.yellow(s):s,a=await i.question(r);return i.close(),a.toLowerCase()==="y"}var Ts=pe,Re=Ze(pe,ne),wi=Ze(pe,Bi),X=Ze(pe,us),Uo=Ze(_o(),"momentic","chromium");function Er(s){return vi(s)&&ki(s).isDirectory()}async function he(s,e=!1){Er(Ts)||(!e&&!await se(`A '${pe}' folder was not found in the current directory. Setup the required Momentic folder structure?`)&&(b.error("Setup cancelled"),process.exit(1)),Qe(Ts),Qe(Re),Qe(wi),Qe(X),Qe(Uo,{recursive:!0})),(!Er(X)||Ar(X).length===0)&&(!e&&!await se("You do not appear to have any environments configured locally. Pull environments from Momentic Cloud?")&&(b.error("Setup cancelled"),process.exit(1)),await Cs({client:s,all:!0,skipPrompts:e})),b.info("CLI initialization complete")}function es(s,e,i=new Set){for(let r of s){let a=Fo(r);if(a&&vi(a)&&ki(a).isDirectory()){let t=Ar(a).map(n=>Ze(a,n));es(t,e,i);continue}if(a.endsWith(".yaml")){if(!vi(a)||!ki(a).isFile())throw new Error(`File not found or unreadable: ${a}`);if(!e(a))continue;i.add(a)}}return i}async function Cs({envNames:s,client:e,all:i,skipPrompts:r}){let a=await e.getAllEnvironments();s&&!i&&(a=a.filter(o=>s?.includes(o.name))),Cr(X)||$o(X);let t=0,n=0;for(let o of a){let l=Ir(X,`${o.name}.yaml`);if(!Cr(l))Rr(l,Ys(o)),t++;else{let c=Tr("sha256").update(zi(l)).digest("hex"),d=Ys(o),p=Tr("sha256").update(d).digest("hex");if(c!==p){if(!r&&!await se(`Environment ${o.name} already exists on disk but needs to be updated. Overwrite?`)){b.error("Pull cancelled");return}Rr(l,d),n++}}}b.info(`Pulled ${t} new environments and updated ${n} existing environments`)}function Bo(s){return s.endsWith(".yaml")?zi(s,"utf8").includes("momentic/environment")?!0:(b.warn(`Skipping YAML that is not a Momentic environment: ${s}`),!1):!1}async function Lr({client:s,names:e,all:i,yes:r}){let a;i?a=[X]:a=e.map(o=>Ir(X,`${o.toLowerCase()}.yaml`));let t=es(a,Bo);b.info(`Found ${t.size} environments(s) to push:`),t.forEach(o=>b.info(` - ${o}`)),b.info("Loading file contents");let n=[];for(let o of t){let l=Ho(zi(o,"utf-8"));try{let c=K.parse(l);n.push(c)}catch(c){b.error({err:c},`${o} failed to parse as a valid environment file`),process.exit(1)}}if(!r&&!await se("Pushing environments can overwrite variables on cloud and instantly affect scheduled runs. Continue?",!0)){b.error("Push cancelled");return}b.info(`Pushing ${t.size} environment(s)`),await s.updateEnvironments(n),b.info("Push successful!")}import{registry as xi}from"playwright-core/lib/server";function Go(s){let e=[],i=[];for(let r of s){let a=xi.findExecutable(r);!a||a.installType==="none"?e.push(r):i.push(a)}return i}async function Or(){let s=Go(["chromium"]);await xi.installDeps(s,!1),await xi.install(s,!1)}import{Argument as Rs,Option as Ie}from"commander";var be=new Ie("--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),ye=new Ie("--server <server>","Momentic server to use. Leave unchanged unless using Momentic on-premise.").default("https://api.momentic.ai"),fe=new Ie("-y, --yes","Skip confirmation prompts.").env("CI").default(!1),Mr=new Ie("--no-report","Skip reporting test results to Momentic Cloud when running with the --local flag.").default(!0),Nr=new Ie("--env <env>","Name of the environment to use when running tests."),ji=new Ie("-a, --all","Select all tests in your organization from Momentic Cloud. Cannot be used together with <tests> arguments."),qi=new Ie("-a, --all","Select all environments in your organization from Momentic Cloud. Cannot be used together with <paths> arguments."),Pr=new Rs("<tests...>",`One or more test paths to pull from Momentic Cloud.
30
30
 
31
31
  A test path is a lowercased version of your test name where spaces are replaced with underscores: 'npx momentic pull hello-world'.`).argOptional(),Dr=new Rs("<tests...>",`One or more test identifiers.
32
32
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "momentic",
3
- "version": "0.0.36",
3
+ "version": "0.0.37",
4
4
  "description": "The Momentic SDK for Node.js",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",