momentic 0.0.33 → 0.0.35
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=>r.push(` ${l}`))):r.push("PAGE CONTENT CHANGE: <TOO_LONG_TO_DISPLAY/>"):r.push("PAGE CONTENT CHANGE: <NONE/>")}r.push("-".repeat(10))}),r.push(`STARTING URL: ${this.browser.baseURL}`),r.join(`
|
|
27
27
|
`)}getListHistory(){return Xn`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,a=!1){let r=this.commandHistory[this.commandHistory.length-1];if(!a&&(!r||r.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&&!a&&(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()),a||(r.generatedStep=e,r.serializedCommand=Ie(e),r.state="DONE"),n}async executeAssertion(e){let i=await this.getBrowserState(),a=await this.browser.url(),r;if(e.useVision)r={goal:e.assertion,url:a,screenshot:await this.browser.screenshot(),browserState:"",history:"",numPrevious:-1,lastCommand:null};else{let n=this.getSerializedHistory(a,i);r={goal:e.assertion,url:a,browserState:i,history:n,lastCommand:this.lastExecutedCommand,numPrevious:this.commandHistory.length}}let t=await this.generator.getAssertionResult(r,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:a}}async wrapElementTargetingCommand(e,i,a,r,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&&is(e.a11yData);e.a11yData||(this.logger.info("No cached locator data for target, prompting AI for fresh location"),t--,e.a11yData=ss.parse(await this.locateElement(e.elementDescriptor,i,a)));try{let o=await r(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,a,r,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(a){throw a instanceof x?a:new x("InternalWebAgentError",a instanceof Error?a.message:`${a}`,{cause:a})}}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 F(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(!Js(e.url)&&!Qs(e.url,this.browser.baseURL))throw new x("ActionFailureError",`Invalid URL: ${e.url}`);await this.browser.navigate({url:e.url});break;case"CAPTCHA":let a=await this.browser.solveCaptcha();a&&(await this.wrapElementTargetingCommand({elementDescriptor:"the captcha image solution input"},e.useVision,i,l=>this.browser.click(l)),await this.browser.type(a,{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 r;switch(e.target&&(e.target.elementDescriptor.trim()||e.target.a11yData)&&(r=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:r};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 xe(l,d.urlAfterCommand)&&(d.succeedImmediately=!0,d.succeedImmediatelyReason="URL changed"),d}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 Ja({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 xe(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 xe(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(Js(e.url)&&(u=e.url),Qs(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,a){return(await this.generator.getReverseMappedDescription({browserState:e.browserState,goal:`${i}`},a)).phrase}async toggleRecordMode({action:e,indices:i,onStepRecord:a}){if(e==="STOP"){this.recordAbortController?.abort();return}if(!i||!a)throw new Error("Indices or step recording callback not provided to toggleRecordMode");this.recordAbortController=new AbortController;let r=new js({signal:this.recordAbortController.signal,initialIndices:i,generator:this.generator,onStepRecord:a});await this.browser.startRecording(this.recordAbortController.signal,r)}};async function Qa({socket:s,generator:e,logger:i,rootController:a,localApp:r}){let t=i.child({package:"web-agent"}),n=!0;r==="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=Zn(),p=h=>{s.emit("screenshot",{...h,url:l})},g=a;if(g)g.browser.setActiveFrame(us),g.browser.baseURL=l,await g.resetState({clearCookies:!0,clearStorage:!0,timeout:null}),g.setOpen();else{let h=await I.init({baseUrl:l,logger:t,takeScreenshots:n,onScreenshot:p});g=new oe({browser:h,generator:e,config:Se,logger:t})}n&&Si(s,d,i),_.registerSession(g,d);let u=I.USER_AGENT;return s.emit("session",{url:l,userAgent:u,viewport:await g.browser.viewport(),localApp:r}),{sessionId:d,testId:o,orgId:c}}var Za=[Ba,Ei,Va,Ga,Wa];var er=s=>{let{logger:e}=s,i=new eo(s.baseServer,{cors:{origin:"*",methods:["GET","POST"]}});return i.on("connection",async a=>{let r;try{r=await Qa({...s,socket:a})}catch(t){e.error({event:"connection",type:"websocket",err:t},"Failed to setup connection"),a.emit("error",{message:`${t}`});return}Za.forEach(t=>so(t,{socket:a,metadata:r,...s}))}),i},so=(s,e)=>{let i=s.createHandler(e),a=(...r)=>{e.logger.debug({event:s.event,metadata:e.metadata,args:r},"Websocket event");let t=n=>{e.logger.error({event:s.event,type:"websocket",args:r,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,r);n&&typeof n.catch=="function"&&n.catch(t)}catch(n){t(n)}};e.socket.on(s.event,a)};import io from"fetch-retry";var ao=io(global.fetch),me="v1",Ee=class{baseURL;apiKey;constructor(e){this.baseURL=e.baseURL,this.apiKey=e.apiKey}async getElementLocation(e,i){let a=await this.sendRequest(`/${me}/web-agent/locate-element`,{browserState:e.browserState,goal:e.goal,disableCache:i});return Bs.parse(a)}async getElementLocationWithVision(e,i){let a=await this.sendRequest(`/${me}/web-agent/visual-locate`,{goal:e.goal,screenshot:e.screenshot?.toString("base64"),hintActivatedScreenshot:e.hintActivatedScreenshot?.toString("base64"),disableCache:i});return Bs.parse(a)}async getAssertionResult(e,i,a){if(i){let t=await this.sendRequest(`/${me}/web-agent/assertion`,{url:e.url,goal:e.goal,screenshot:e.screenshot?.toString("base64"),disableCache:a,vision:!0});return Hs.parse(t)}let r=await this.sendRequest(`/${me}/web-agent/assertion`,{url:e.url,browserState:e.browserState,goal:e.goal,history:e.history,numPrevious:e.numPrevious,lastCommand:e.lastCommand,disableCache:a,vision:!1});return Hs.parse(r)}async getProposedCommand(e,i){let a=await this.sendRequest(`/${me}/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 Ki.parse(a)}async getGranularGoals(e,i){let a=await this.sendRequest(`/${me}/web-agent/split-goal`,{url:e.url,goal:e.goal,disableCache:i});return Yi.parse(a)}async getReverseMappedDescription(e,i){let a=await this.sendRequest(`/${me}/web-agent/reverse-mapped-description`,{goal:e.goal,browserState:e.browserState,disableCache:i});return Xi.parse(a)}async getTextExtraction(e,i){let a={goal:e.goal,browserState:e.browserState,returnSchema:e.returnSchema,disableCache:i},r=await this.sendRequest(`/${me}/web-agent/text-extraction`,a);return Ls.parse(r)}async sendRequest(e,i){let a=await ao(`${this.baseURL}${e}`,{retries:1,retryDelay:1e3,method:"POST",body:JSON.stringify(i),headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`}});if(!a.ok)throw new Error(`Request to ${e} failed with status ${a.status}: ${await a.text()}`);return a.json()}};import{existsSync as ro,readFileSync as to,writeFileSync as li}from"fs";import ci from"path";import{parse as no,stringify as oo}from"yaml";var D=process.env.MOMENTIC_DIR??ue;function sr(s,e){let i=na(s);for(let[t,n]of Object.entries(i.modules)){let o=Ge(t);li(ci.join(D,re,`${o}.yaml`),n,"utf-8")}let a=Ge(e),r=ci.join(D,`${a}.yaml`);return li(r,i.test,"utf-8"),`${a}.yaml`}function ir(s,e){if(!ro(e))throw new Error(`Test not found at path: ${ci}`);let i=to(e,"utf-8"),r={...no(i),...s},t=oo(r);li(e,t,"utf-8")}import{readFileSync as qs,readdirSync as ar,writeFileSync as rr}from"fs";import{join as di}from"path";import{v4 as lo}from"uuid";import{parse as Ss,stringify as tr}from"yaml";var Ke=di(D,re);function nr(s,e){let i=ys(Ke,e).path,a=qs(i,"utf-8"),t={...Ss(a),...s},n=tr(t);rr(i,n,"utf-8")}function or(s,e){let i=Ge(s),a=di(Ke,`${i}.yaml`),r={fileType:"momentic/module",schemaVersion:U,moduleId:lo(),name:s,steps:e},t=tr(r);return rr(a,t,"utf-8"),{type:"RESOLVED_MODULE",moduleId:r.moduleId,name:r.name,steps:r.steps}}function lr(){let s=[];for(let e in ar(Ke)){if(!e.endsWith(".yaml"))continue;let i=qs(e,"utf-8"),a=Ss(i),r={name:a.name,moduleId:a.moduleId,numSteps:a.steps.length};s.push(r)}return s}function cr(){let s=[];for(let e of ar(Ke)){if(!e.endsWith(".yaml"))continue;let i=di(Ke,e),a=qs(i,"utf-8"),r=Ss(a);try{let t=Ws.parse(r);s.push(t)}catch(t){b.warn({err:t},"Error parsing module, skipping...")}}return s}function dr(s){let e=ys(re,s).path,i=qs(e,"utf-8"),a=Ss(i);return Ws.parse(a)}import{Router as go}from"express";import{existsSync as co,readFileSync as uo,readdirSync as po}from"fs";import{join as ur}from"path";import{parse as mo}from"yaml";var ui=ur(D,ds);function pr(){let s=[];if(!co(ui))return[];for(let e of po(ui)){if(!e.endsWith(".yaml"))continue;let i=ur(ui,e),a=uo(i,"utf-8"),r=mo(a);try{let t=V.parse(r);s.push(t)}catch(t){b.warn({err:t},"Error parsing environment, skipping...")}}return s}function W(s){return function(...e){let i=e[e.length-1],a=s(...e);Promise.resolve(a).catch(i)}}var mr=go();mr.get("/",W((s,e)=>{let i=pr();e.status(200).json(i)}));var gr=mr;import{Router as ho}from"express";var Ye=ho();Ye.get("/",W((s,e)=>{let a=cr().map(r=>({...r,type:"RESOLVED_MODULE"}));e.status(200).json(a)}));Ye.get("/metadata",W((s,e)=>{let i=lr();e.status(200).json(i)}));Ye.post("/",W((s,e)=>{let i;try{i=Gi.parse(s.body)}catch(r){e.status(400).json({error:`Invalid request body: ${r}`});return}try{Be(i.name)}catch(r){e.status(400).json({error:`Invalid module name: ${r}`});return}let a=or(i.name,i.steps);e.status(201).json(a)}));Ye.get("/:moduleId",W((s,e)=>{if(!s.params.moduleId){e.status(400).json({error:"Missing moduleId in url path."});return}let i=dr(s.params.moduleId);e.json(i)}));var hr=Ye;import{Router as bo}from"express";import{existsSync as yo}from"fs";import{join as pi}from"path";import{v4 as fo}from"uuid";var Xe=bo();Xe.get("/",W((s,e)=>{let i=We(D).filter(a=>a.localOnly);e.status(200).json(i)}));Xe.post("/",W((s,e)=>{let i;try{i=Bi.parse(s.body)}catch(o){e.status(400).json({error:`Invalid request body: ${o}`});return}try{Be(i.name)}catch(o){e.status(400).json({error:o.message});return}let r={id:fo(),name:i.name,baseUrl:i.baseUrl,schemaVersion:U,advanced:{disableAICaching:!1,availableAsModule:!0},retries:0,steps:[],localOnly:!0};i.environment&&(r.envs=[{name:i.environment,defaultOnLocal:!0}]);let t=sr(r,i.name),n={...r,testPath:t};e.status(201).json(n)}));Xe.get("/:testPath",W(async(s,e)=>{if(!s.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let i=pi(D,s.params.testPath);if(!yo(i)){e.status(400).json({error:`Test not found at path: ${i}`});return}let a=await fs(i,pi(D,re));e.status(200).json(a)}));Xe.patch("/:testPath",W((s,e)=>{if(!s.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let i;try{i=Hi.parse(s.body)}catch(t){e.status(400).json({error:`Invalid request body: ${t}`});return}let a=[],r={};for(let t of i.steps){if(t.type!=="RESOLVED_MODULE"){a.push(t);continue}a.push({type:"MODULE",moduleId:t.moduleId}),r[t.moduleId]=t.steps}for(let[t,n]of Object.entries(r))nr({steps:n},t);ir({steps:a},pi(D,s.params.testPath)),e.status(201).json({message:"ok"})}));var br=Xe;var mi="https://api.momentic.ai",fr=s=>{mi=s},vr=()=>mi,vo,kr=s=>{vo=s};var yr,wr=async s=>{if(yr)return;let e=await fetch(`${mi}/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:a}=ra.parse(i);yr=a};import zr,{multistream as ko}from"pino";import wo from"pino-pretty";var gi=new Map,zo=process.env.NODE_ENV==="production",hi=class s{consoleLogger;ddClientToken;hostname;bindingAttributes;site="https://http-intake.logs.us5.datadoghq.com/api/v2/logs";constructor({bindings:e,clientToken:i,hostname:a}){this.ddClientToken=i,this.hostname=a,this.bindingAttributes={...e,env:process.env.NODE_ENV};let r={base:this.bindingAttributes,errorKey:"err",level:"debug"};this.consoleLogger=zo?zr(r):zr(r,ko([{stream:wo({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,a,...r){this.consoleLogger[e](i,a,...r);let t=Object.assign({},this.bindingAttributes,i&&typeof i=="object"?i:{});r.length>0&&(t.args=r),(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,ddtags:Object.keys(t).map(o=>`${o}:${t[o]}`).join(","),hostname:this.hostname??"vercel",message:a||"",service:"momentic"}])});if(!n.ok)throw new Error(`Failed to log to DD: ${n.statusText})}`)}catch(n){this.consoleLogger.warn({obj:i,msg:a,args:r,err:n},"Failed to log to DD")}})()}debug(e,i,...a){this.log("debug",e,i,...a)}info(e,i,...a){this.log("info",e,i,...a)}warn(e,i,...a){this.log("warn",e,i,...a)}error(e,i,...a){this.log("error",e,i,...a)}bindings(){return this.bindingAttributes}},xr=({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 gi.has(s)||gi.set(s,new hi({bindings:{app:s},hostname:i,clientToken:e||process.env.NEXT_PUBLIC_DD_CLIENT_TOKEN||process.env.DD_CLIENT_TOKEN})),gi.get(s)};import{hostname as xo}from"os";var J=xr({app:"desktop-server",clientToken:"pubcfd7516a5c0ba852b42675cd97bee027",hostname:xo()});async function qr({momenticServerUrl:s,apiKey:e,mode:i,serverPort:a,appPort:r,staticDir:t,assetsDir:n}){if(!So(D)||!Eo(D).isDirectory()){let u=Co.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&&fr(s),b.info("Checking API key"),await wr(e);let l=`http://localhost:${a}`;r&&(l=`http://localhost:${r}`);let c=Io(t);await new Promise(u=>{c.listen(a,()=>{b.info(`Server is running at http://localhost:${a}`),u()})});let p=new Ee({baseURL:vr(),apiKey:e});if(i==="web"){await jr("web",c,p,J),await To(l);return}let g=await Lo({appUrl:l,apiGenerator:p,extensionPath:Ro(n,"extension"),onClose:async()=>{b.info("Browser closed, closing app."),await g.browser.cleanup(),c.close(()=>{process.exit(0)})}});await jr("iframe",c,p,J,g),process.on("uncaughtException",u=>{J.error({err:u},"Uncaught exception in desktop-server")}),process.on("unhandledRejection",u=>{J.error({err:u},"Unhandled rejection in desktop-server")})}function Io(s){let e=bi();e.use(qo()),e.use(jo.json({limit:"50mb"}));let i=bi.Router();if(i.use("/tests",br),i.use("/modules",hr),i.use("/environments",gr),e.use("/api",i),e.use((r,t,n)=>{r.path!=="/healthcheck"&&J.debug({url:r.url,path:r.path,query:r.query,method:r.method,body:r.body,headers:r.rawHeaders,client:r.ip},"Received desktop-server request"),t.on("close",()=>{t.statusCode>=400&&J.error({url:r.url,method:r.method,statusCode:t.statusCode},"Request completed in error")}),n()}),e.use((r,t,n,o)=>{J.error({stack:r.stack,msg:r.message,url:t.url,method:t.method},"Unhandled exception leading to 500 on desktop-server"),n.status(500).send("Internal Server Error"),o(r)}),s){let r=bi.static(s);e.use("/",r),e.use("*",r)}return Ao.createServer(e)}async function Lo({appUrl:s,apiGenerator:e,extensionPath:i,onClose:a}){let r=await I.init({baseUrl:s,logger:J,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:a}),t=new oe({browser:r,config:Se,generator:e,logger:J});return kr(t),t}async function jr(s,e,i,a,r){er({baseServer:e,generator:i,logger:a,localApp:s,rootController:r})}import{existsSync as ul}from"fs";import Jr,{dirname as pl}from"path";import{fileURLToPath as ml}from"url";var Sr="0.0.33";var K="v1",Q=class{baseURL;apiKey;constructor(e){this.baseURL=e.baseURL,this.apiKey=e.apiKey}async getRun(e){let i=await this.sendRequest(`/${K}/runs/${e}`,{method:"GET"});return ia.parse(i)}async createRun(e){let i=await this.sendRequest(`/${K}/runs`,{method:"POST",body:e});return sa.parse(i)}async updateRun(e,i){await this.sendRequest(`/${K}/runs/${e}`,{method:"PATCH",body:i})}async getTest(e){let i=await this.sendRequest(`/${K}/tests/${e}`,{method:"GET"});return Qi.parse(i)}async getAllTestIds(){let e=await this.sendRequest(`/${K}/tests`,{method:"GET"});return Zi.parse(e)}async getTestYAMLExport(e){let i=await this.sendRequest(`/${K}/tests/export`,{method:"POST",body:e});return ea.parse(i)}async updateTestWithYAML(e){await this.sendRequest(`/${K}/tests/update`,{method:"POST",body:e})}async queueTests(e){let i=await this.sendRequest(`/${K}/tests/queue`,{method:"POST",body:e});return Ji.parse(i)}async uploadScreenshot(e){let i=await this.sendRequest(`/${K}/screenshots`,{method:"POST",body:e});return aa.parse(i)}async getAllEnvironments(){let e=await this.sendRequest(`/${K}/environments`,{method:"GET"});return ta.parse(e)}async updateEnvironments(e){await this.sendRequest(`/${K}/environments`,{method:"POST",body:e})}async sendRequest(e,i){let a=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(!a.ok)throw new Error(`Request to ${e} failed with status ${a.status}: ${await a.text()}`);return a.status===204?a.text():a.json()}};import{createHash as Tr}from"crypto";import{existsSync as Cr,mkdirSync as Fo,readFileSync as ki,writeFileSync as Rr}from"fs";import{join as Ir}from"path";import{parse as Uo}from"yaml";import{existsSync as yi,mkdirSync as Je,readdirSync as Ar,statSync as fi}from"fs";import{homedir as Po}from"os";import{join as Qe,resolve as Do}from"path";import Mo from"chalk";import No from"readline/promises";async function Z(s,e){if(process.env.CI)return!0;let i=No.createInterface({input:process.stdin,output:process.stdout});s=`${s} (y/N) `;let a=e?Mo.bold.yellow(s):s,r=await i.question(a);return i.close(),r.toLowerCase()==="y"}var Es=ue,Ae=Qe(ue,re),vi=Qe(ue,$i),Y=Qe(ue,ds),_o=Qe(Po(),"momentic","chromium");function Er(s){return yi(s)&&fi(s).isDirectory()}async function ge(s,e=!1){Er(Es)||(!e&&!await Z(`A '${ue}' folder was not found in the current directory. Setup the required Momentic folder structure?`)&&(b.error("Setup cancelled"),process.exit(1)),Je(Es),Je(Ae),Je(vi),Je(Y),Je(_o,{recursive:!0})),(!Er(Y)||Ar(Y).length===0)&&(!e&&!await Z("You do not appear to have any environments configured locally. Pull environments from Momentic Cloud?")&&(b.error("Setup cancelled"),process.exit(1)),await As({client:s,all:!0,skipPrompts:e})),b.info("Setup complete!")}function Ze(s,e,i=new Set){for(let a of s){let r=Do(a);if(r&&yi(r)&&fi(r).isDirectory()){let t=Ar(r).map(n=>Qe(r,n));Ze(t,e,i);continue}if(r.endsWith(".yaml")){if(!yi(r)||!fi(r).isFile())throw new Error(`File not found or unreadable: ${r}`);if(!e(r))continue;i.add(r)}}return i}async function As({envNames:s,client:e,all:i,skipPrompts:a}){let r=await e.getAllEnvironments();s&&!i&&(r=r.filter(o=>s?.includes(o.name))),Cr(Y)||Fo(Y);let t=0,n=0;for(let o of r){let l=Ir(Y,`${o.name}.yaml`);if(!Cr(l))Rr(l,Vs(o)),t++;else{let c=Tr("sha256").update(ki(l)).digest("hex"),d=Vs(o),p=Tr("sha256").update(d).digest("hex");if(c!==p){if(!a&&!await Z(`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 $o(s){return s.endsWith(".yaml")?ki(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:a}){let r;i?r=[Y]:r=e.map(o=>Ir(Y,`${o.toLowerCase()}.yaml`));let t=Ze(r,$o);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=Uo(ki(o,"utf-8"));try{let c=V.parse(l);n.push(c)}catch(c){b.error({err:c},`${o} failed to parse as a valid environment file`),process.exit(1)}}if(!a&&!await Z("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 wi}from"playwright-core/lib/server";function Ho(s){let e=[],i=[];for(let a of s){let r=wi.findExecutable(a);!r||r.installType==="none"?e.push(a):i.push(r)}return i}async function Or(){let s=Ho(["chromium"]);await wi.installDeps(s,!1),await wi.install(s,!1)}import{Argument as Ts,Option as Te}from"commander";var he=new Te("--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),be=new Te("--server <server>","Momentic server to use. Leave unchanged unless using Momentic on-premise.").default("https://api.momentic.ai"),ye=new Te("-y, --yes","Skip confirmation prompts.").env("CI").default(!1),Mr=new Te("--no-report","Skip reporting test results to Momentic Cloud when running with the --local flag.").default(!0),Nr=new Te("--env <env>","Name of the environment to use when running tests."),zi=new Te("-a --all","Select all tests in your organization from Momentic Cloud. Cannot be used together with <tests> arguments.").default(!1).preset(!0),xi=new Te("-a --all","Select all environments in your organization from Momentic Cloud. Cannot be used together with <paths> arguments.").default(!1).preset(!0),Pr=new Ts("<tests...>",`One or more test paths to pull from Momentic Cloud.
|
|
29
|
+
`)}`}async executeCommand(e,i,a=!1){let r=this.commandHistory[this.commandHistory.length-1];if(!a&&(!r||r.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&&!a&&(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()),a||(r.generatedStep=e,r.serializedCommand=Ie(e),r.state="DONE"),n}async executeAssertion(e){let i=await this.getBrowserState(),a=await this.browser.url(),r;if(e.useVision)r={goal:e.assertion,url:a,screenshot:await this.browser.screenshot(),browserState:"",history:"",numPrevious:-1,lastCommand:null};else{let n=this.getSerializedHistory(a,i);r={goal:e.assertion,url:a,browserState:i,history:n,lastCommand:this.lastExecutedCommand,numPrevious:this.commandHistory.length}}let t=await this.generator.getAssertionResult(r,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:a}}async wrapElementTargetingCommand(e,i,a,r,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&&is(e.a11yData);e.a11yData||(this.logger.info("No cached locator data for target, prompting AI for fresh location"),t--,e.a11yData=ss.parse(await this.locateElement(e.elementDescriptor,i,a)));try{let o=await r(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,a,r,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(a){throw a instanceof x?a:new x("InternalWebAgentError",a instanceof Error?a.message:`${a}`,{cause:a})}}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 F(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(!Js(e.url)&&!Qs(e.url,this.browser.baseURL))throw new x("ActionFailureError",`Invalid URL: ${e.url}`);await this.browser.navigate({url:e.url});break;case"CAPTCHA":let a=await this.browser.solveCaptcha();a&&(await this.wrapElementTargetingCommand({elementDescriptor:"the captcha image solution input"},e.useVision,i,l=>this.browser.click(l)),await this.browser.type(a,{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 r;switch(e.target&&(e.target.elementDescriptor.trim()||e.target.a11yData)&&(r=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:r};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 xe(l,d.urlAfterCommand)&&(d.succeedImmediately=!0,d.succeedImmediatelyReason="URL changed"),d}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 Ja({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 xe(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 xe(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(Js(e.url)&&(u=e.url),Qs(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,a){return(await this.generator.getReverseMappedDescription({browserState:e.browserState,goal:`${i}`},a)).phrase}async toggleRecordMode({action:e,indices:i,onStepRecord:a}){if(e==="STOP"){this.recordAbortController?.abort();return}if(!i||!a)throw new Error("Indices or step recording callback not provided to toggleRecordMode");this.recordAbortController=new AbortController;let r=new js({signal:this.recordAbortController.signal,initialIndices:i,generator:this.generator,onStepRecord:a});await this.browser.startRecording(this.recordAbortController.signal,r)}};async function Qa({socket:s,generator:e,logger:i,rootController:a,localApp:r}){let t=i.child({package:"web-agent"}),n=!0;r==="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=Zn(),p=h=>{s.emit("screenshot",{...h,url:l})},g=a;if(g)g.browser.setActiveFrame(us),g.browser.baseURL=l,await g.resetState({clearCookies:!0,clearStorage:!0,timeout:null}),g.setOpen();else{let h=await I.init({baseUrl:l,logger:t,takeScreenshots:n,onScreenshot:p});g=new oe({browser:h,generator:e,config:Se,logger:t})}n&&Si(s,d,i),_.registerSession(g,d);let u=I.USER_AGENT;return s.emit("session",{url:l,userAgent:u,viewport:await g.browser.viewport(),localApp:r}),{sessionId:d,testId:o,orgId:c}}var Za=[Ba,Ei,Va,Ga,Wa];var er=s=>{let{logger:e}=s,i=new eo(s.baseServer,{cors:{origin:"*",methods:["GET","POST"]}});return i.on("connection",async a=>{let r;try{r=await Qa({...s,socket:a})}catch(t){e.error({event:"connection",type:"websocket",err:t},"Failed to setup connection"),a.emit("error",{message:`${t}`});return}Za.forEach(t=>so(t,{socket:a,metadata:r,...s}))}),i},so=(s,e)=>{let i=s.createHandler(e),a=(...r)=>{e.logger.debug({event:s.event,metadata:e.metadata,args:r},"Websocket event");let t=n=>{e.logger.error({event:s.event,type:"websocket",args:r,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,r);n&&typeof n.catch=="function"&&n.catch(t)}catch(n){t(n)}};e.socket.on(s.event,a)};import io from"fetch-retry";var ao=io(global.fetch),me="v1",Ee=class{baseURL;apiKey;constructor(e){this.baseURL=e.baseURL,this.apiKey=e.apiKey}async getElementLocation(e,i){let a=await this.sendRequest(`/${me}/web-agent/locate-element`,{browserState:e.browserState,goal:e.goal,disableCache:i});return Bs.parse(a)}async getElementLocationWithVision(e,i){let a=await this.sendRequest(`/${me}/web-agent/visual-locate`,{goal:e.goal,screenshot:e.screenshot?.toString("base64"),hintActivatedScreenshot:e.hintActivatedScreenshot?.toString("base64"),disableCache:i});return Bs.parse(a)}async getAssertionResult(e,i,a){if(i){let t=await this.sendRequest(`/${me}/web-agent/assertion`,{url:e.url,goal:e.goal,screenshot:e.screenshot?.toString("base64"),disableCache:a,vision:!0});return Hs.parse(t)}let r=await this.sendRequest(`/${me}/web-agent/assertion`,{url:e.url,browserState:e.browserState,goal:e.goal,history:e.history,numPrevious:e.numPrevious,lastCommand:e.lastCommand,disableCache:a,vision:!1});return Hs.parse(r)}async getProposedCommand(e,i){let a=await this.sendRequest(`/${me}/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 Ki.parse(a)}async getGranularGoals(e,i){let a=await this.sendRequest(`/${me}/web-agent/split-goal`,{url:e.url,goal:e.goal,disableCache:i});return Yi.parse(a)}async getReverseMappedDescription(e,i){let a=await this.sendRequest(`/${me}/web-agent/reverse-mapped-description`,{goal:e.goal,browserState:e.browserState,disableCache:i});return Xi.parse(a)}async getTextExtraction(e,i){let a={goal:e.goal,browserState:e.browserState,returnSchema:e.returnSchema,disableCache:i},r=await this.sendRequest(`/${me}/web-agent/text-extraction`,a);return Ls.parse(r)}async sendRequest(e,i){let a=await ao(`${this.baseURL}${e}`,{retries:1,retryDelay:1e3,method:"POST",body:JSON.stringify(i),headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`}});if(!a.ok)throw new Error(`Request to ${e} failed with status ${a.status}: ${await a.text()}`);return a.json()}};import{existsSync as ro,readFileSync as to,writeFileSync as li}from"fs";import ci from"path";import{parse as no,stringify as oo}from"yaml";var D=process.env.MOMENTIC_DIR??ue;function sr(s,e){let i=na(s);for(let[t,n]of Object.entries(i.modules)){let o=Ge(t);li(ci.join(D,re,`${o}.yaml`),n,"utf-8")}let a=Ge(e),r=ci.join(D,`${a}.yaml`);return li(r,i.test,"utf-8"),`${a}.yaml`}function ir(s,e){if(!ro(e))throw new Error(`Test not found at path: ${ci}`);let i=to(e,"utf-8"),r={...no(i),...s},t=oo(r);li(e,t,"utf-8")}import{readFileSync as qs,readdirSync as ar,writeFileSync as rr}from"fs";import{join as di}from"path";import{v4 as lo}from"uuid";import{parse as Ss,stringify as tr}from"yaml";var Ke=di(D,re);function nr(s,e){let i=ys(Ke,e).path,a=qs(i,"utf-8"),t={...Ss(a),...s},n=tr(t);rr(i,n,"utf-8")}function or(s,e){let i=Ge(s),a=di(Ke,`${i}.yaml`),r={fileType:"momentic/module",schemaVersion:U,moduleId:lo(),name:s,steps:e},t=tr(r);return rr(a,t,"utf-8"),{type:"RESOLVED_MODULE",moduleId:r.moduleId,name:r.name,steps:r.steps}}function lr(){let s=[];for(let e in ar(Ke)){if(!e.endsWith(".yaml"))continue;let i=qs(e,"utf-8"),a=Ss(i),r={name:a.name,moduleId:a.moduleId,numSteps:a.steps.length};s.push(r)}return s}function cr(){let s=[];for(let e of ar(Ke)){if(!e.endsWith(".yaml"))continue;let i=di(Ke,e),a=qs(i,"utf-8"),r=Ss(a);try{let t=Ws.parse(r);s.push(t)}catch(t){b.warn({err:t},"Error parsing module, skipping...")}}return s}function dr(s){let e=ys(re,s).path,i=qs(e,"utf-8"),a=Ss(i);return Ws.parse(a)}import{Router as go}from"express";import{existsSync as co,readFileSync as uo,readdirSync as po}from"fs";import{join as ur}from"path";import{parse as mo}from"yaml";var ui=ur(D,ds);function pr(){let s=[];if(!co(ui))return[];for(let e of po(ui)){if(!e.endsWith(".yaml"))continue;let i=ur(ui,e),a=uo(i,"utf-8"),r=mo(a);try{let t=V.parse(r);s.push(t)}catch(t){b.warn({err:t},"Error parsing environment, skipping...")}}return s}function W(s){return function(...e){let i=e[e.length-1],a=s(...e);Promise.resolve(a).catch(i)}}var mr=go();mr.get("/",W((s,e)=>{let i=pr();e.status(200).json(i)}));var gr=mr;import{Router as ho}from"express";var Ye=ho();Ye.get("/",W((s,e)=>{let a=cr().map(r=>({...r,type:"RESOLVED_MODULE"}));e.status(200).json(a)}));Ye.get("/metadata",W((s,e)=>{let i=lr();e.status(200).json(i)}));Ye.post("/",W((s,e)=>{let i;try{i=Gi.parse(s.body)}catch(r){e.status(400).json({error:`Invalid request body: ${r}`});return}try{Be(i.name)}catch(r){e.status(400).json({error:`Invalid module name: ${r}`});return}let a=or(i.name,i.steps);e.status(201).json(a)}));Ye.get("/:moduleId",W((s,e)=>{if(!s.params.moduleId){e.status(400).json({error:"Missing moduleId in url path."});return}let i=dr(s.params.moduleId);e.json(i)}));var hr=Ye;import{Router as bo}from"express";import{existsSync as yo}from"fs";import{join as pi}from"path";import{v4 as fo}from"uuid";var Xe=bo();Xe.get("/",W((s,e)=>{let i=We(D).filter(a=>a.localOnly);e.status(200).json(i)}));Xe.post("/",W((s,e)=>{let i;try{i=Bi.parse(s.body)}catch(o){e.status(400).json({error:`Invalid request body: ${o}`});return}try{Be(i.name)}catch(o){e.status(400).json({error:o.message});return}let r={id:fo(),name:i.name,baseUrl:i.baseUrl,schemaVersion:U,advanced:{disableAICaching:!1,availableAsModule:!0},retries:0,steps:[],localOnly:!0};i.environment&&(r.envs=[{name:i.environment,defaultOnLocal:!0}]);let t=sr(r,i.name),n={...r,testPath:t};e.status(201).json(n)}));Xe.get("/:testPath",W(async(s,e)=>{if(!s.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let i=pi(D,s.params.testPath);if(!yo(i)){e.status(400).json({error:`Test not found at path: ${i}`});return}let a=await fs(i,pi(D,re));e.status(200).json(a)}));Xe.patch("/:testPath",W((s,e)=>{if(!s.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let i;try{i=Hi.parse(s.body)}catch(t){e.status(400).json({error:`Invalid request body: ${t}`});return}let a=[],r={};for(let t of i.steps){if(t.type!=="RESOLVED_MODULE"){a.push(t);continue}a.push({type:"MODULE",moduleId:t.moduleId}),r[t.moduleId]=t.steps}for(let[t,n]of Object.entries(r))nr({steps:n},t);ir({steps:a},pi(D,s.params.testPath)),e.status(201).json({message:"ok"})}));var br=Xe;var mi="https://api.momentic.ai",fr=s=>{mi=s},vr=()=>mi,vo,kr=s=>{vo=s};var yr,wr=async s=>{if(yr)return;let e=await fetch(`${mi}/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:a}=ra.parse(i);yr=a};import zr,{multistream as ko}from"pino";import wo from"pino-pretty";var gi=new Map,zo=process.env.NODE_ENV==="production",hi=class s{consoleLogger;ddClientToken;hostname;bindingAttributes;site="https://http-intake.logs.us5.datadoghq.com/api/v2/logs";constructor({bindings:e,clientToken:i,hostname:a}){this.ddClientToken=i,this.hostname=a,this.bindingAttributes={...e,env:process.env.NODE_ENV};let r={base:this.bindingAttributes,errorKey:"err",level:"debug"};this.consoleLogger=zo?zr(r):zr(r,ko([{stream:wo({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,a,...r){this.consoleLogger[e](i,a,...r);let t=Object.assign({},this.bindingAttributes,i&&typeof i=="object"?i:{});r.length>0&&(t.args=r),(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,ddtags:Object.keys(t).map(o=>`${o}:${t[o]}`).join(","),hostname:this.hostname??"vercel",message:a||"",service:"momentic"}])});if(!n.ok)throw new Error(`Failed to log to DD: ${n.statusText})}`)}catch(n){this.consoleLogger.warn({obj:i,msg:a,args:r,err:n},"Failed to log to DD")}})()}debug(e,i,...a){this.log("debug",e,i,...a)}info(e,i,...a){this.log("info",e,i,...a)}warn(e,i,...a){this.log("warn",e,i,...a)}error(e,i,...a){this.log("error",e,i,...a)}bindings(){return this.bindingAttributes}},xr=({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 gi.has(s)||gi.set(s,new hi({bindings:{app:s},hostname:i,clientToken:e||process.env.NEXT_PUBLIC_DD_CLIENT_TOKEN||process.env.DD_CLIENT_TOKEN})),gi.get(s)};import{hostname as xo}from"os";var J=xr({app:"desktop-server",clientToken:"pubcfd7516a5c0ba852b42675cd97bee027",hostname:xo()});async function qr({momenticServerUrl:s,apiKey:e,mode:i,serverPort:a,appPort:r,staticDir:t,assetsDir:n}){if(!So(D)||!Eo(D).isDirectory()){let u=Co.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&&fr(s),b.info("Checking API key"),await wr(e);let l=`http://localhost:${a}`;r&&(l=`http://localhost:${r}`);let c=Io(t);await new Promise(u=>{c.listen(a,()=>{b.info(`Server is running at http://localhost:${a}`),u()})});let p=new Ee({baseURL:vr(),apiKey:e});if(i==="web"){await jr("web",c,p,J),await To(l);return}let g=await Lo({appUrl:l,apiGenerator:p,extensionPath:Ro(n,"extension"),onClose:async()=>{b.info("Browser closed, closing app."),await g.browser.cleanup(),c.close(()=>{process.exit(0)})}});await jr("iframe",c,p,J,g),process.on("uncaughtException",u=>{J.error({err:u},"Uncaught exception in desktop-server")}),process.on("unhandledRejection",u=>{J.error({err:u},"Unhandled rejection in desktop-server")})}function Io(s){let e=bi();e.use(qo()),e.use(jo.json({limit:"50mb"}));let i=bi.Router();if(i.use("/tests",br),i.use("/modules",hr),i.use("/environments",gr),e.use("/api",i),e.use((r,t,n)=>{r.path!=="/healthcheck"&&J.debug({url:r.url,path:r.path,query:r.query,method:r.method,body:r.body,headers:r.rawHeaders,client:r.ip},"Received desktop-server request"),t.on("close",()=>{t.statusCode>=400&&J.error({url:r.url,method:r.method,statusCode:t.statusCode},"Request completed in error")}),n()}),e.use((r,t,n,o)=>{J.error({stack:r.stack,msg:r.message,url:t.url,method:t.method},"Unhandled exception leading to 500 on desktop-server"),n.status(500).send("Internal Server Error"),o(r)}),s){let r=bi.static(s);e.use("/",r),e.use("*",r)}return Ao.createServer(e)}async function Lo({appUrl:s,apiGenerator:e,extensionPath:i,onClose:a}){let r=await I.init({baseUrl:s,logger:J,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:a}),t=new oe({browser:r,config:Se,generator:e,logger:J});return kr(t),t}async function jr(s,e,i,a,r){er({baseServer:e,generator:i,logger:a,localApp:s,rootController:r})}import{existsSync as ul}from"fs";import Jr,{dirname as pl}from"path";import{fileURLToPath as ml}from"url";var Sr="0.0.35";var K="v1",Q=class{baseURL;apiKey;constructor(e){this.baseURL=e.baseURL,this.apiKey=e.apiKey}async getRun(e){let i=await this.sendRequest(`/${K}/runs/${e}`,{method:"GET"});return ia.parse(i)}async createRun(e){let i=await this.sendRequest(`/${K}/runs`,{method:"POST",body:e});return sa.parse(i)}async updateRun(e,i){await this.sendRequest(`/${K}/runs/${e}`,{method:"PATCH",body:i})}async getTest(e){let i=await this.sendRequest(`/${K}/tests/${e}`,{method:"GET"});return Qi.parse(i)}async getAllTestIds(){let e=await this.sendRequest(`/${K}/tests`,{method:"GET"});return Zi.parse(e)}async getTestYAMLExport(e){let i=await this.sendRequest(`/${K}/tests/export`,{method:"POST",body:e});return ea.parse(i)}async updateTestWithYAML(e){await this.sendRequest(`/${K}/tests/update`,{method:"POST",body:e})}async queueTests(e){let i=await this.sendRequest(`/${K}/tests/queue`,{method:"POST",body:e});return Ji.parse(i)}async uploadScreenshot(e){let i=await this.sendRequest(`/${K}/screenshots`,{method:"POST",body:e});return aa.parse(i)}async getAllEnvironments(){let e=await this.sendRequest(`/${K}/environments`,{method:"GET"});return ta.parse(e)}async updateEnvironments(e){await this.sendRequest(`/${K}/environments`,{method:"POST",body:e})}async sendRequest(e,i){let a=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(!a.ok)throw new Error(`Request to ${e} failed with status ${a.status}: ${await a.text()}`);return a.status===204?a.text():a.json()}};import{createHash as Tr}from"crypto";import{existsSync as Cr,mkdirSync as Fo,readFileSync as ki,writeFileSync as Rr}from"fs";import{join as Ir}from"path";import{parse as Uo}from"yaml";import{existsSync as yi,mkdirSync as Je,readdirSync as Ar,statSync as fi}from"fs";import{homedir as Po}from"os";import{join as Qe,resolve as Do}from"path";import Mo from"chalk";import No from"readline/promises";async function Z(s,e){if(process.env.CI)return!0;let i=No.createInterface({input:process.stdin,output:process.stdout});s=`${s} (y/N) `;let a=e?Mo.bold.yellow(s):s,r=await i.question(a);return i.close(),r.toLowerCase()==="y"}var Es=ue,Ae=Qe(ue,re),vi=Qe(ue,$i),Y=Qe(ue,ds),_o=Qe(Po(),"momentic","chromium");function Er(s){return yi(s)&&fi(s).isDirectory()}async function ge(s,e=!1){Er(Es)||(!e&&!await Z(`A '${ue}' folder was not found in the current directory. Setup the required Momentic folder structure?`)&&(b.error("Setup cancelled"),process.exit(1)),Je(Es),Je(Ae),Je(vi),Je(Y),Je(_o,{recursive:!0})),(!Er(Y)||Ar(Y).length===0)&&(!e&&!await Z("You do not appear to have any environments configured locally. Pull environments from Momentic Cloud?")&&(b.error("Setup cancelled"),process.exit(1)),await As({client:s,all:!0,skipPrompts:e})),b.info("Setup complete!")}function Ze(s,e,i=new Set){for(let a of s){let r=Do(a);if(r&&yi(r)&&fi(r).isDirectory()){let t=Ar(r).map(n=>Qe(r,n));Ze(t,e,i);continue}if(r.endsWith(".yaml")){if(!yi(r)||!fi(r).isFile())throw new Error(`File not found or unreadable: ${r}`);if(!e(r))continue;i.add(r)}}return i}async function As({envNames:s,client:e,all:i,skipPrompts:a}){let r=await e.getAllEnvironments();s&&!i&&(r=r.filter(o=>s?.includes(o.name))),Cr(Y)||Fo(Y);let t=0,n=0;for(let o of r){let l=Ir(Y,`${o.name}.yaml`);if(!Cr(l))Rr(l,Vs(o)),t++;else{let c=Tr("sha256").update(ki(l)).digest("hex"),d=Vs(o),p=Tr("sha256").update(d).digest("hex");if(c!==p){if(!a&&!await Z(`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 $o(s){return s.endsWith(".yaml")?ki(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:a}){let r;i?r=[Y]:r=e.map(o=>Ir(Y,`${o.toLowerCase()}.yaml`));let t=Ze(r,$o);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=Uo(ki(o,"utf-8"));try{let c=V.parse(l);n.push(c)}catch(c){b.error({err:c},`${o} failed to parse as a valid environment file`),process.exit(1)}}if(!a&&!await Z("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 wi}from"playwright-core/lib/server";function Ho(s){let e=[],i=[];for(let a of s){let r=wi.findExecutable(a);!r||r.installType==="none"?e.push(a):i.push(r)}return i}async function Or(){let s=Ho(["chromium"]);await wi.installDeps(s,!1),await wi.install(s,!1)}import{Argument as Ts,Option as Te}from"commander";var he=new Te("--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),be=new Te("--server <server>","Momentic server to use. Leave unchanged unless using Momentic on-premise.").default("https://api.momentic.ai"),ye=new Te("-y, --yes","Skip confirmation prompts.").env("CI").default(!1),Mr=new Te("--no-report","Skip reporting test results to Momentic Cloud when running with the --local flag.").default(!0),Nr=new Te("--env <env>","Name of the environment to use when running tests."),zi=new Te("-a --all","Select all tests in your organization from Momentic Cloud. Cannot be used together with <tests> arguments.").default(!1).preset(!0),xi=new Te("-a --all","Select all environments in your organization from Momentic Cloud. Cannot be used together with <paths> arguments.").default(!1).preset(!0),Pr=new Ts("<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 Ts("<tests...>",`One or more test identifiers.
|
|
32
32
|
|