ripplo 0.0.10 → 0.0.12

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/dist/mcp/index.js CHANGED
@@ -1,56 +1,56 @@
1
1
  #!/usr/bin/env node
2
- var Oo=Object.create;var kt=Object.defineProperty;var Vo=Object.getOwnPropertyDescriptor;var Uo=Object.getOwnPropertyNames;var Fo=Object.getPrototypeOf,Wo=Object.prototype.hasOwnProperty;var Te=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var Lo=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of Uo(t))!Wo.call(e,n)&&n!==r&&kt(e,n,{get:()=>t[n],enumerable:!(o=Vo(t,n))||o.enumerable});return e};var _o=(e,t,r)=>(r=e!=null?Oo(Fo(e)):{},Lo(t||!e||!e.__esModule?kt(r,"default",{value:e,enumerable:!0}):r,e));var qr=Te(Oe=>{"use strict";Object.defineProperty(Oe,"__esModule",{value:!0});Oe.timingSafeEqual=void 0;function Dr(e,t=""){if(!e)throw new Error(t)}function Es(e,t){if(e.byteLength!==t.byteLength)return!1;e instanceof DataView||(e=new DataView(ArrayBuffer.isView(e)?e.buffer:e)),t instanceof DataView||(t=new DataView(ArrayBuffer.isView(t)?t.buffer:t)),Dr(e instanceof DataView),Dr(t instanceof DataView);let r=e.byteLength,o=0,n=-1;for(;++n<r;)o|=e.getUint8(n)^t.getUint8(n);return o===0}Oe.timingSafeEqual=Es});var Gr=Te(D=>{"use strict";var Is=D&&D.__extends||(function(){var e=function(t,r){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(o,n){o.__proto__=n}||function(o,n){for(var a in n)n.hasOwnProperty(a)&&(o[a]=n[a])},e(t,r)};return function(t,r){e(t,r);function o(){this.constructor=t}t.prototype=r===null?Object.create(r):(o.prototype=r.prototype,new o)}})();Object.defineProperty(D,"__esModule",{value:!0});var N=256,pt=(function(){function e(t){t===void 0&&(t="="),this._paddingCharacter=t}return e.prototype.encodedLength=function(t){return this._paddingCharacter?(t+2)/3*4|0:(t*8+5)/6|0},e.prototype.encode=function(t){for(var r="",o=0;o<t.length-2;o+=3){var n=t[o]<<16|t[o+1]<<8|t[o+2];r+=this._encodeByte(n>>>18&63),r+=this._encodeByte(n>>>12&63),r+=this._encodeByte(n>>>6&63),r+=this._encodeByte(n>>>0&63)}var a=t.length-o;if(a>0){var n=t[o]<<16|(a===2?t[o+1]<<8:0);r+=this._encodeByte(n>>>18&63),r+=this._encodeByte(n>>>12&63),a===2?r+=this._encodeByte(n>>>6&63):r+=this._paddingCharacter||"",r+=this._paddingCharacter||""}return r},e.prototype.maxDecodedLength=function(t){return this._paddingCharacter?t/4*3|0:(t*6+7)/8|0},e.prototype.decodedLength=function(t){return this.maxDecodedLength(t.length-this._getPaddingLength(t))},e.prototype.decode=function(t){if(t.length===0)return new Uint8Array(0);for(var r=this._getPaddingLength(t),o=t.length-r,n=new Uint8Array(this.maxDecodedLength(o)),a=0,i=0,c=0,l=0,p=0,g=0,f=0;i<o-4;i+=4)l=this._decodeChar(t.charCodeAt(i+0)),p=this._decodeChar(t.charCodeAt(i+1)),g=this._decodeChar(t.charCodeAt(i+2)),f=this._decodeChar(t.charCodeAt(i+3)),n[a++]=l<<2|p>>>4,n[a++]=p<<4|g>>>2,n[a++]=g<<6|f,c|=l&N,c|=p&N,c|=g&N,c|=f&N;if(i<o-1&&(l=this._decodeChar(t.charCodeAt(i)),p=this._decodeChar(t.charCodeAt(i+1)),n[a++]=l<<2|p>>>4,c|=l&N,c|=p&N),i<o-2&&(g=this._decodeChar(t.charCodeAt(i+2)),n[a++]=p<<4|g>>>2,c|=g&N),i<o-3&&(f=this._decodeChar(t.charCodeAt(i+3)),n[a++]=g<<6|f,c|=f&N),c!==0)throw new Error("Base64Coder: incorrect characters for decoding");return n},e.prototype._encodeByte=function(t){var r=t;return r+=65,r+=25-t>>>8&6,r+=51-t>>>8&-75,r+=61-t>>>8&-15,r+=62-t>>>8&3,String.fromCharCode(r)},e.prototype._decodeChar=function(t){var r=N;return r+=(42-t&t-44)>>>8&-N+t-43+62,r+=(46-t&t-48)>>>8&-N+t-47+63,r+=(47-t&t-58)>>>8&-N+t-48+52,r+=(64-t&t-91)>>>8&-N+t-65+0,r+=(96-t&t-123)>>>8&-N+t-97+26,r},e.prototype._getPaddingLength=function(t){var r=0;if(this._paddingCharacter){for(var o=t.length-1;o>=0&&t[o]===this._paddingCharacter;o--)r++;if(t.length<4||r>2)throw new Error("Base64Coder: incorrect padding")}return r},e})();D.Coder=pt;var ve=new pt;function $s(e){return ve.encode(e)}D.encode=$s;function js(e){return ve.decode(e)}D.decode=js;var Br=(function(e){Is(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype._encodeByte=function(r){var o=r;return o+=65,o+=25-r>>>8&6,o+=51-r>>>8&-75,o+=61-r>>>8&-13,o+=62-r>>>8&49,String.fromCharCode(o)},t.prototype._decodeChar=function(r){var o=N;return o+=(44-r&r-46)>>>8&-N+r-45+62,o+=(94-r&r-96)>>>8&-N+r-95+63,o+=(47-r&r-58)>>>8&-N+r-48+52,o+=(64-r&r-91)>>>8&-N+r-65+0,o+=(96-r&r-123)>>>8&-N+r-97+26,o},t})(pt);D.URLSafeCoder=Br;var Mr=new Br;function Os(e){return Mr.encode(e)}D.encodeURLSafe=Os;function Vs(e){return Mr.decode(e)}D.decodeURLSafe=Vs;D.encodedLength=function(e){return ve.encodedLength(e)};D.maxDecodedLength=function(e){return ve.maxDecodedLength(e)};D.decodedLength=function(e){return ve.decodedLength(e)}});var Hr=Te((zr,Ve)=>{"use strict";(function(e,t){var r={};t(r);var o=r.default;for(var n in r)o[n]=r[n];typeof Ve=="object"&&typeof Ve.exports=="object"?Ve.exports=o:typeof define=="function"&&define.amd?define(function(){return o}):e.sha256=o})(zr,function(e){"use strict";e.__esModule=!0,e.digestLength=32,e.blockSize=64;var t=new Uint32Array([1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298]);function r(f,d,m,y,A){for(var k,v,S,W,E,T,G,I,O,$,ge,ye,Pe;A>=64;){for(k=d[0],v=d[1],S=d[2],W=d[3],E=d[4],T=d[5],G=d[6],I=d[7],$=0;$<16;$++)ge=y+$*4,f[$]=(m[ge]&255)<<24|(m[ge+1]&255)<<16|(m[ge+2]&255)<<8|m[ge+3]&255;for($=16;$<64;$++)O=f[$-2],ye=(O>>>17|O<<15)^(O>>>19|O<<13)^O>>>10,O=f[$-15],Pe=(O>>>7|O<<25)^(O>>>18|O<<14)^O>>>3,f[$]=(ye+f[$-7]|0)+(Pe+f[$-16]|0);for($=0;$<64;$++)ye=(((E>>>6|E<<26)^(E>>>11|E<<21)^(E>>>25|E<<7))+(E&T^~E&G)|0)+(I+(t[$]+f[$]|0)|0)|0,Pe=((k>>>2|k<<30)^(k>>>13|k<<19)^(k>>>22|k<<10))+(k&v^k&S^v&S)|0,I=G,G=T,T=E,E=W+ye|0,W=S,S=v,v=k,k=ye+Pe|0;d[0]+=k,d[1]+=v,d[2]+=S,d[3]+=W,d[4]+=E,d[5]+=T,d[6]+=G,d[7]+=I,y+=64,A-=64}return y}var o=(function(){function f(){this.digestLength=e.digestLength,this.blockSize=e.blockSize,this.state=new Int32Array(8),this.temp=new Int32Array(64),this.buffer=new Uint8Array(128),this.bufferLength=0,this.bytesHashed=0,this.finished=!1,this.reset()}return f.prototype.reset=function(){return this.state[0]=1779033703,this.state[1]=3144134277,this.state[2]=1013904242,this.state[3]=2773480762,this.state[4]=1359893119,this.state[5]=2600822924,this.state[6]=528734635,this.state[7]=1541459225,this.bufferLength=0,this.bytesHashed=0,this.finished=!1,this},f.prototype.clean=function(){for(var d=0;d<this.buffer.length;d++)this.buffer[d]=0;for(var d=0;d<this.temp.length;d++)this.temp[d]=0;this.reset()},f.prototype.update=function(d,m){if(m===void 0&&(m=d.length),this.finished)throw new Error("SHA256: can't update because hash was finished.");var y=0;if(this.bytesHashed+=m,this.bufferLength>0){for(;this.bufferLength<64&&m>0;)this.buffer[this.bufferLength++]=d[y++],m--;this.bufferLength===64&&(r(this.temp,this.state,this.buffer,0,64),this.bufferLength=0)}for(m>=64&&(y=r(this.temp,this.state,d,y,m),m%=64);m>0;)this.buffer[this.bufferLength++]=d[y++],m--;return this},f.prototype.finish=function(d){if(!this.finished){var m=this.bytesHashed,y=this.bufferLength,A=m/536870912|0,k=m<<3,v=m%64<56?64:128;this.buffer[y]=128;for(var S=y+1;S<v-8;S++)this.buffer[S]=0;this.buffer[v-8]=A>>>24&255,this.buffer[v-7]=A>>>16&255,this.buffer[v-6]=A>>>8&255,this.buffer[v-5]=A>>>0&255,this.buffer[v-4]=k>>>24&255,this.buffer[v-3]=k>>>16&255,this.buffer[v-2]=k>>>8&255,this.buffer[v-1]=k>>>0&255,r(this.temp,this.state,this.buffer,0,v),this.finished=!0}for(var S=0;S<8;S++)d[S*4+0]=this.state[S]>>>24&255,d[S*4+1]=this.state[S]>>>16&255,d[S*4+2]=this.state[S]>>>8&255,d[S*4+3]=this.state[S]>>>0&255;return this},f.prototype.digest=function(){var d=new Uint8Array(this.digestLength);return this.finish(d),d},f.prototype._saveState=function(d){for(var m=0;m<this.state.length;m++)d[m]=this.state[m]},f.prototype._restoreState=function(d,m){for(var y=0;y<this.state.length;y++)this.state[y]=d[y];this.bytesHashed=m,this.finished=!1,this.bufferLength=0},f})();e.Hash=o;var n=(function(){function f(d){this.inner=new o,this.outer=new o,this.blockSize=this.inner.blockSize,this.digestLength=this.inner.digestLength;var m=new Uint8Array(this.blockSize);if(d.length>this.blockSize)new o().update(d).finish(m).clean();else for(var y=0;y<d.length;y++)m[y]=d[y];for(var y=0;y<m.length;y++)m[y]^=54;this.inner.update(m);for(var y=0;y<m.length;y++)m[y]^=106;this.outer.update(m),this.istate=new Uint32Array(8),this.ostate=new Uint32Array(8),this.inner._saveState(this.istate),this.outer._saveState(this.ostate);for(var y=0;y<m.length;y++)m[y]=0}return f.prototype.reset=function(){return this.inner._restoreState(this.istate,this.inner.blockSize),this.outer._restoreState(this.ostate,this.outer.blockSize),this},f.prototype.clean=function(){for(var d=0;d<this.istate.length;d++)this.ostate[d]=this.istate[d]=0;this.inner.clean(),this.outer.clean()},f.prototype.update=function(d){return this.inner.update(d),this},f.prototype.finish=function(d){return this.outer.finished?this.outer.finish(d):(this.inner.finish(d),this.outer.update(d,this.digestLength).finish(d)),this},f.prototype.digest=function(){var d=new Uint8Array(this.digestLength);return this.finish(d),d},f})();e.HMAC=n;function a(f){var d=new o().update(f),m=d.digest();return d.clean(),m}e.hash=a,e.default=a;function i(f,d){var m=new n(f).update(d),y=m.digest();return m.clean(),y}e.hmac=i;function c(f,d,m,y){var A=y[0];if(A===0)throw new Error("hkdf: cannot expand more");d.reset(),A>1&&d.update(f),m&&d.update(m),d.update(y),d.finish(f),y[0]++}var l=new Uint8Array(e.digestLength);function p(f,d,m,y){d===void 0&&(d=l),y===void 0&&(y=32);for(var A=new Uint8Array([1]),k=i(d,f),v=new n(k),S=new Uint8Array(v.digestLength),W=S.length,E=new Uint8Array(y),T=0;T<y;T++)W===S.length&&(c(S,v,m,A),W=0),E[T]=S[W++];return v.clean(),S.fill(0),A.fill(0),E}e.hkdf=p;function g(f,d,m,y){for(var A=new n(f),k=A.digestLength,v=new Uint8Array(4),S=new Uint8Array(k),W=new Uint8Array(k),E=new Uint8Array(y),T=0;T*k<y;T++){var G=T+1;v[0]=G>>>24&255,v[1]=G>>>16&255,v[2]=G>>>8&255,v[3]=G>>>0&255,A.reset(),A.update(d),A.update(v),A.finish(W);for(var I=0;I<k;I++)S[I]=W[I];for(var I=2;I<=m;I++){A.reset(),A.update(W).finish(W);for(var O=0;O<k;O++)S[O]^=W[O]}for(var I=0;I<k&&T*k+I<y;I++)E[T*k+I]=S[I]}for(var T=0;T<k;T++)S[T]=W[T]=0;for(var T=0;T<4;T++)v[T]=0;return A.clean(),E}e.pbkdf2=g})});var Yr=Te(pe=>{"use strict";Object.defineProperty(pe,"__esModule",{value:!0});pe.Webhook=pe.WebhookVerificationError=void 0;var Us=qr(),Jr=Gr(),Fs=Hr(),Kr=300,ft=class e extends Error{constructor(t){super(t),Object.setPrototypeOf(this,e.prototype),this.name="ExtendableError",this.stack=new Error(t).stack}},te=class e extends ft{constructor(t){super(t),Object.setPrototypeOf(this,e.prototype),this.name="WebhookVerificationError"}};pe.WebhookVerificationError=te;var Ue=class e{constructor(t,r){if(!t)throw new Error("Secret can't be empty.");if(r?.format==="raw")t instanceof Uint8Array?this.key=t:this.key=Uint8Array.from(t,o=>o.charCodeAt(0));else{if(typeof t!="string")throw new Error("Expected secret to be of type string");t.startsWith(e.prefix)&&(t=t.substring(e.prefix.length)),this.key=Jr.decode(t)}}verify(t,r){let o={};for(let d of Object.keys(r))o[d.toLowerCase()]=r[d];let n=o["webhook-id"],a=o["webhook-signature"],i=o["webhook-timestamp"];if(!a||!n||!i)throw new te("Missing required headers");let c=this.verifyTimestamp(i),p=this.sign(n,c,t).split(",")[1],g=a.split(" "),f=new globalThis.TextEncoder;for(let d of g){let[m,y]=d.split(",");if(m==="v1"&&(0,Us.timingSafeEqual)(f.encode(y),f.encode(p)))return JSON.parse(t.toString())}throw new te("No matching signature found")}sign(t,r,o){if(typeof o!="string")if(o.constructor.name==="Buffer")o=o.toString();else throw new Error("Expected payload to be of type string or Buffer.");let n=new TextEncoder,a=Math.floor(r.getTime()/1e3),i=n.encode(`${t}.${a}.${o}`);return`v1,${Jr.encode(Fs.hmac(this.key,i))}`}verifyTimestamp(t){let r=Math.floor(Date.now()/1e3),o=parseInt(t,10);if(isNaN(o))throw new te("Invalid Signature Headers");if(r-o>Kr)throw new te("Message timestamp too old");if(o>r+Kr)throw new te("Message timestamp too new");return new Date(o*1e3)}};pe.Webhook=Ue;Ue.prefix="whsec_"});import{StdioServerTransport as Ul}from"@modelcontextprotocol/sdk/server/stdio.js";import{McpServer as El}from"@modelcontextprotocol/sdk/server/mcp.js";import{z as H}from"zod";function z({locator:e,page:t}){switch(e.by){case"css":return t.locator(e.value);case"testId":return t.getByTestId(e.value);case"role":return t.getByRole(e.role,{exact:e.exact,name:e.name});case"text":return t.getByText(e.value,{exact:e.exact});case"label":return t.getByLabel(e.value,{exact:e.exact});case"placeholder":return t.getByPlaceholder(e.value);case"altText":return t.getByAltText(e.value)}}import rp from"@anthropic-ai/sdk";import{z as xt}from"zod";import{z as Go}from"zod";async function be({page:e,runStartTime:t,targets:r}){let o=e.viewportSize();if(o==null)throw new Error("Page has no viewport set");let[n,a]=await Promise.all([e.screenshot({type:"png"}),qo(r)]),i=n.toString("base64");return{annotations:a,screenshotBase64:i,snapshotTimestamp:Math.round(performance.now()-t),url:e.url(),viewportHeight:o.height,viewportWidth:o.width}}function Do(e){return"locator"in e}async function qo(e){return(await Promise.all(e.map(r=>Bo(r)))).filter(r=>r!=null)}async function Bo(e){if(!Do(e))return{height:0,label:e.label,type:e.type,width:0,x:0,y:0};let t=await e.locator.boundingBox({timeout:2e3}).catch(()=>null);if(t!=null)return{height:Math.round(t.height),label:e.label,type:e.type,width:Math.round(t.width),x:Math.round(t.x),y:Math.round(t.y)}}function Me({node:e,page:t}){if(e.type==="assertUrl"||e.type==="waitForUrl")return[{label:e.type,type:"urlBar"}];if(e.type==="drag"){let n=z({locator:e.source,page:t}),a=z({locator:e.target,page:t});return[{label:"drag-source",locator:n,type:"action"},{label:"drag-target",locator:a,type:"action"}]}if(!Mo(e))return[];let r=e.type.startsWith("assert")?"assertion":"action",o=z({locator:e.locator,page:t});return[{label:e.type,locator:o,type:r}]}function Mo(e){return"locator"in e&&e.locator!=null}function u({description:e,execute:t,name:r,schema:o}){let n=Go.toJSONSchema(o,{target:"draft-2020-12"});return{anthropicTool:{description:e,input_schema:{...n,type:"object"},name:r},name:r,async execute(a,i){let c=o.parse(i);return{...await Promise.resolve(t(a,c)),kind:"action"}}}}async function h(e){let t=e.specNode==null?[]:Me({node:e.specNode,page:e.page}),r=await be({page:e.page,runStartTime:e.runStartTime,targets:t}),o=[...e.assertions],n={annotations:r.annotations,assertions:o,detail:e.detail,duration:Math.round(e.duration),nodeId:`agent-step-${String(e.stepIndex)}`,nodeType:e.nodeType,screenshotBase64:r.screenshotBase64,snapshotTimestamp:r.snapshotTimestamp,status:e.status,stepIndex:e.stepIndex,title:e.title,url:r.url,viewportHeight:r.viewportHeight,viewportWidth:r.viewportWidth},a=o.length>0?o.map(i=>`${i.status}: ${i.description} \u2014 ${i.detail??""}`).join(`
3
- `):`${e.status}: ${e.title}`;return{specNode:e.specNode,stepResult:n,toolOutput:a}}var zo=xt.object({selector:xt.string().describe("CSS selector for the checkbox or radio button")}),Rt=u({description:"Assert that a checkbox or radio button is checked",name:"assert_checked",schema:zo,execute:async(e,t)=>{let r=performance.now(),o=await e.page.locator(t.selector).isChecked().catch(()=>!1),n=o?"passed":"failed",a=[{description:`Element ${t.selector} is checked`,detail:o?void 0:"Element is not checked",status:n}],c={id:`agent-step-${String(e.stepIndex)}`,locator:{by:"css",value:t.selector},type:"assertChecked"};return h({assertions:a,detail:o?"Element is checked":"Element is not checked",duration:performance.now()-r,nodeType:"assertChecked",page:e.page,runStartTime:e.runStartTime,specNode:c,status:n,stepIndex:e.stepIndex,title:`Assert checked: ${t.selector}`})}});import{z as ce}from"zod";var Ho=ce.object({httpOnly:ce.boolean().optional().describe("Assert httpOnly flag value"),name:ce.string().describe("Cookie name to check"),sameSite:ce.enum(["Strict","Lax","None"]).optional().describe("Assert sameSite value"),secure:ce.boolean().optional().describe("Assert secure flag value"),value:ce.string().optional().describe("Expected cookie value (substring match)")}),Tt=u({description:"Assert properties of a browser cookie. Check existence, value, and security flags (httpOnly, secure, sameSite).",name:"assert_cookie",schema:Ho,execute:async(e,t)=>{let r=performance.now(),n=(await e.page.context().cookies()).find(g=>g.name===t.name),a=n==null?[{description:`Cookie "${t.name}" exists`,detail:"Cookie not found",status:"failed"}]:[{description:`Cookie "${t.name}" exists`,detail:void 0,status:"passed"},...Jo({cookie:n,input:t})],i=a.some(g=>g.status==="failed"),c=i?"failed":"passed",p={id:`agent-step-${String(e.stepIndex)}`,name:{type:"static",value:t.name},type:"assertCookie"};return h({assertions:a,detail:i?a.filter(g=>g.status==="failed").map(g=>g.detail).join("; "):"Cookie OK",duration:performance.now()-r,nodeType:"assertCookie",page:e.page,runStartTime:e.runStartTime,specNode:p,status:c,stepIndex:e.stepIndex,title:`Assert cookie: ${t.name}`})}});function Jo({cookie:e,input:t}){return[Ko(t.value,e.value),Pt("httpOnly",t.httpOnly,e.httpOnly),Pt("secure",t.secure,e.secure),Yo("sameSite",t.sameSite,e.sameSite)].filter(r=>r!=null)}function Ko(e,t){if(e==null)return;let r=t.includes(e);return{description:`Cookie value contains "${e}"`,detail:r?void 0:`Got: "${t}"`,status:r?"passed":"failed"}}function Pt(e,t,r){if(t!=null)return{description:`Cookie ${e} is ${String(t)}`,detail:r===t?void 0:`Got: ${String(r)}`,status:r===t?"passed":"failed"}}function Yo(e,t,r){if(t!=null)return{description:`Cookie ${e} is ${t}`,detail:r===t?void 0:`Got: ${r}`,status:r===t?"passed":"failed"}}import{z as Ct}from"zod";var Qo=Ct.object({selector:Ct.string().describe("CSS selector for the element to check focus on")}),At=u({description:"Assert that an element has keyboard focus. Use for accessibility tab-order testing.",name:"assert_focused",schema:Qo,execute:async(e,t)=>{let r=performance.now(),n=!!await e.page.locator(t.selector).evaluate("(el) => document.activeElement === el").catch(()=>!1),a=n?"passed":"failed",i=[{description:`Element ${t.selector} is focused`,detail:n?void 0:"Element does not have focus",status:a}],l={id:`agent-step-${String(e.stepIndex)}`,locator:{by:"css",value:t.selector},type:"assertFocused"};return h({assertions:i,detail:n?"Element is focused":"Element does not have focus",duration:performance.now()-r,nodeType:"assertFocused",page:e.page,runStartTime:e.runStartTime,specNode:l,status:a,stepIndex:e.stepIndex,title:`Assert focused: ${t.selector}`})}});import{z as de}from"zod";var Xo=de.object({bodyContains:de.string().optional().describe("Assert response body contains this string"),headerName:de.string().optional().describe("Response header name to check"),headerValue:de.string().optional().describe("Expected value the header should contain"),status:de.number().int().optional().describe("Expected HTTP status code"),urlPattern:de.string().describe("URL substring to match the response against")}),Nt=u({description:"Wait for a network response matching the URL pattern and assert on its status, body, or headers. Useful for verifying API calls and security headers (CSP, CORS).",name:"assert_response",schema:Xo,execute:async(e,t)=>{let r=performance.now(),o=await e.page.waitForResponse(p=>p.url().includes(t.urlPattern),{timeout:1e4}),n=[Zo(t.status,o.status()),await en(t.bodyContains,o),tn(t.headerName,t.headerValue,o.headers())].filter(p=>p!=null),a=n.length===0?[{description:`Response matching "${t.urlPattern}" received`,detail:void 0,status:"passed"}]:n,i=a.some(p=>p.status==="failed"),l={id:`agent-step-${String(e.stepIndex)}`,type:"assertResponse",urlPattern:{type:"static",value:t.urlPattern}};return h({assertions:a,detail:i?a.filter(p=>p.status==="failed").map(p=>p.detail).join("; "):"Response OK",duration:performance.now()-r,nodeType:"assertResponse",page:e.page,runStartTime:e.runStartTime,specNode:l,status:i?"failed":"passed",stepIndex:e.stepIndex,title:`Assert response: ${t.urlPattern}`})}});function Zo(e,t){if(e!=null)return{description:`Response status equals ${String(e)}`,detail:t===e?void 0:`Got: ${String(t)}`,status:t===e?"passed":"failed"}}async function en(e,t){if(e==null)return;let o=(await t.text()).includes(e);return{description:`Response body contains "${e}"`,detail:o?void 0:"Not found in response body",status:o?"passed":"failed"}}function tn(e,t,r){if(e==null||t==null)return;let o=r[e.toLowerCase()],n=o!=null&&o.includes(t);return{description:`Response header "${e}" contains "${t}"`,detail:n?void 0:`Got: "${o??"(missing)"}"`,status:n?"passed":"failed"}}import{z as Ge}from"zod";var rn=Ge.object({expected:Ge.string().describe("The expected text content"),selector:Ge.string().describe("CSS selector for the element to check")}),Et=u({description:"Assert that an element's text content matches the expected value",name:"assert_text",schema:rn,execute:async(e,t)=>{let r=performance.now(),o=await e.page.locator(t.selector).textContent({timeout:5e3}).catch(()=>null),n=o!=null&&o.includes(t.expected),a=n?"passed":"failed",i=[{description:`Text contains "${t.expected}"`,detail:`Actual: "${o??"(not found)"}"`,status:a}],c=`agent-step-${String(e.stepIndex)}`,l={expected:{type:"static",value:t.expected},id:c,locator:{by:"css",value:t.selector},operator:"contains",type:"assertText"};return h({assertions:i,detail:n?`Text matches: "${t.expected}"`:`Expected "${t.expected}", got "${o??"(not found)"}"`,duration:performance.now()-r,nodeType:"assertText",page:e.page,runStartTime:e.runStartTime,specNode:l,status:a,stepIndex:e.stepIndex,title:`Assert text: ${t.selector}`})}});import{z as ze}from"zod";var on=ze.object({expected:ze.string().describe("Expected page title or substring"),operator:ze.enum(["equals","contains","startsWith","endsWith"]).describe("Comparison operator").default("contains")}),It=u({description:"Assert that the page title matches an expected value",name:"assert_title",schema:on,execute:async(e,t)=>{let r=performance.now(),o=await e.page.title(),n=nn({actual:o,expected:t.expected,operator:t.operator}),a=n?"passed":"failed",i=[{description:`Title ${t.operator} "${t.expected}"`,detail:n?void 0:`Got: "${o}"`,status:a}],c=`agent-step-${String(e.stepIndex)}`,l={expected:{type:"static",value:t.expected},id:c,operator:t.operator,type:"assertTitle"};return h({assertions:i,detail:n?"Title matches":`Expected title ${t.operator} "${t.expected}", got "${o}"`,duration:performance.now()-r,nodeType:"assertTitle",page:e.page,runStartTime:e.runStartTime,specNode:l,status:a,stepIndex:e.stepIndex,title:`Assert title ${t.operator} "${t.expected}"`})}});function nn({actual:e,expected:t,operator:r}){return r==="equals"?e===t:r==="contains"?e.includes(t):r==="startsWith"?e.startsWith(t):r==="endsWith"?e.endsWith(t):!1}import{z as $t}from"zod";var an=$t.object({selector:$t.string().describe("CSS selector for the element to check")}),jt=u({description:"Assert that an element matching the selector is visible on the page",name:"assert_visible",schema:an,execute:async(e,t)=>{let r=performance.now(),o=await e.page.locator(t.selector).isVisible().catch(()=>!1),n=o?"passed":"failed",a=[{description:`Element ${t.selector} is visible`,detail:o?"Element is visible":"Element is not visible",status:n}],c={id:`agent-step-${String(e.stepIndex)}`,locator:{by:"css",value:t.selector},type:"assertVisible"};return h({assertions:a,detail:o?"Element is visible":"Element is not visible",duration:performance.now()-r,nodeType:"assertVisible",page:e.page,runStartTime:e.runStartTime,specNode:c,status:n,stepIndex:e.stepIndex,title:`Assert visible: ${t.selector}`})}});import{z as Ot}from"zod";var sn=Ot.object({selector:Ot.string().describe("CSS selector for the checkbox to check")}),Vt=u({description:"Check a checkbox matching the CSS selector",name:"check",schema:sn,execute:async(e,t)=>{let r=performance.now();await e.page.locator(t.selector).check({timeout:5e3});let n={id:`agent-step-${String(e.stepIndex)}`,locator:{by:"css",value:t.selector},type:"check"};return h({assertions:[],detail:`Checked ${t.selector}`,duration:performance.now()-r,nodeType:"check",page:e.page,runStartTime:e.runStartTime,specNode:n,status:"passed",stepIndex:e.stepIndex,title:`Check ${t.selector}`})}});import{z as Ut}from"zod";var ln=Ut.object({selector:Ut.string().describe("CSS selector for the input to clear")}),Ft=u({description:"Clear the contents of an input field",name:"clear",schema:ln,execute:async(e,t)=>{let r=performance.now();await e.page.locator(t.selector).clear({timeout:5e3});let n={id:`agent-step-${String(e.stepIndex)}`,locator:{by:"css",value:t.selector},type:"clear"};return h({assertions:[],detail:`Cleared ${t.selector}`,duration:performance.now()-r,nodeType:"clear",page:e.page,runStartTime:e.runStartTime,specNode:n,status:"passed",stepIndex:e.stepIndex,title:`Clear ${t.selector}`})}});import{z as Wt}from"zod";var cn=Wt.object({selector:Wt.string().describe("CSS selector for the element to click")}),Lt=u({description:"Click an element matching the CSS selector",name:"click",schema:cn,execute:async(e,t)=>{let r=performance.now();await e.page.locator(t.selector).click({timeout:5e3});let n={id:`agent-step-${String(e.stepIndex)}`,locator:{by:"css",value:t.selector},type:"click"};return h({assertions:[],detail:`Clicked ${t.selector}`,duration:performance.now()-r,nodeType:"click",page:e.page,runStartTime:e.runStartTime,specNode:n,status:"passed",stepIndex:e.stepIndex,title:`Click ${t.selector}`})}});import{z as He}from"zod";var dn=He.object({action:He.enum(["read","write"]).describe("Whether to read from or write to the clipboard"),value:He.string().optional().describe("Text to write (required for write action)")}),_t=u({description:"Read from or write to the browser clipboard. For write, provide the text value. For read, the clipboard contents are returned.",name:"clipboard",schema:dn,execute:async(e,t)=>{let r=performance.now(),o=`agent-step-${String(e.stepIndex)}`;if(t.action==="write"){if(t.value==null)throw new Error("clipboard write requires a value");let i=JSON.stringify(t.value);await e.page.evaluate(`navigator.clipboard.writeText(${i})`);let c={action:"write",id:o,type:"clipboard",value:{type:"static",value:t.value}};return h({assertions:[],detail:`Wrote to clipboard: "${t.value}"`,duration:performance.now()-r,nodeType:"clipboard",page:e.page,runStartTime:e.runStartTime,specNode:c,status:"passed",stepIndex:e.stepIndex,title:"Clipboard write"})}let n=String(await e.page.evaluate("navigator.clipboard.readText()")),a={action:"read",id:o,type:"clipboard"};return h({assertions:[],detail:`Clipboard contents: "${n}"`,duration:performance.now()-r,nodeType:"clipboard",page:e.page,runStartTime:e.runStartTime,specNode:a,status:"passed",stepIndex:e.stepIndex,title:"Clipboard read"})}});import{z as Ce}from"zod";var Dt=Ce.object({summary:Ce.string().describe("This is the ONLY output the user sees. If the agent profile specifies an Output format, follow those instructions exactly. Otherwise, provide a summary of your findings."),verdict:Ce.enum(["pass","fail"]).describe("Whether the test passed or failed. If the agent profile defines Success Criteria, base this verdict on whether those criteria were met.")}),qt={anthropicTool:{description:"Call this tool when you have finished your evaluation. If the agent profile defines an Output section, the summary MUST follow those output instructions exactly. Otherwise, summarize your findings.",input_schema:{...Ce.toJSONSchema(Dt,{target:"draft-2020-12"}),type:"object"},name:"complete_test"},name:"complete_test",execute(e,t){let r=Dt.parse(t),o={kind:"verdict",summary:r.summary,toolOutput:`Verdict: ${r.verdict}
4
- ${r.summary}`,verdict:r.verdict};return Promise.resolve(o)}};import{z as Bt}from"zod";var un=Bt.object({selector:Bt.string().describe("CSS selector for the element to extract text from")}),Mt=u({description:"Extract the text content of an element matching the CSS selector. Returns the text so you can use it in assertions or decisions.",name:"extract_text",schema:un,execute:async(e,t)=>{let r=performance.now(),n=await e.page.locator(t.selector).textContent({timeout:5e3})??"",i={id:`agent-step-${String(e.stepIndex)}`,locator:{by:"css",value:t.selector},type:"extractText",variable:"extractedText"};return h({assertions:[],detail:`Extracted text: "${n}"`,duration:performance.now()-r,nodeType:"extractText",page:e.page,runStartTime:e.runStartTime,specNode:i,status:"passed",stepIndex:e.stepIndex,title:`Extract text from ${t.selector}`})}});import{z as Je}from"zod";var pn=Je.object({selector:Je.string().describe("CSS selector for the input element"),value:Je.string().describe("The text to type into the element")}),Gt=u({description:"Clear and type text into an input element matching the CSS selector",name:"fill",schema:pn,execute:async(e,t)=>{let r=performance.now();await e.page.locator(t.selector).fill(t.value,{timeout:5e3});let n={id:`agent-step-${String(e.stepIndex)}`,locator:{by:"css",value:t.selector},type:"fill",value:{type:"static",value:t.value}};return h({assertions:[],detail:`Filled ${t.selector} with "${t.value}"`,duration:performance.now()-r,nodeType:"fill",page:e.page,runStartTime:e.runStartTime,specNode:n,status:"passed",stepIndex:e.stepIndex,title:`Fill ${t.selector}`})}});import{z as zt}from"zod";var fn=zt.object({level:zt.string().describe("Filter by log level: 'error', 'warning', 'log', 'info', 'debug', or 'all'").default("all")}),Ht=u({description:"Get console log messages from the browser. Optionally filter by level (error, warning, log, info, debug, all).",name:"get_console_logs",schema:fn,execute:(e,t)=>{let r=t.level==="all"?e.monitor.consoleEntries:e.monitor.consoleEntries.filter(n=>n.level===t.level);if(r.length===0)return{specNode:void 0,stepResult:void 0,toolOutput:`No console ${t.level==="all"?"messages":t.level+" messages"} recorded.`};let o=r.map(n=>mn(n)).join(`
2
+ var Oo=Object.create;var xt=Object.defineProperty;var Vo=Object.getOwnPropertyDescriptor;var Uo=Object.getOwnPropertyNames;var Fo=Object.getPrototypeOf,Wo=Object.prototype.hasOwnProperty;var Te=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var Lo=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of Uo(t))!Wo.call(e,n)&&n!==r&&xt(e,n,{get:()=>t[n],enumerable:!(o=Vo(t,n))||o.enumerable});return e};var _o=(e,t,r)=>(r=e!=null?Oo(Fo(e)):{},Lo(t||!e||!e.__esModule?xt(r,"default",{value:e,enumerable:!0}):r,e));var qr=Te(Oe=>{"use strict";Object.defineProperty(Oe,"__esModule",{value:!0});Oe.timingSafeEqual=void 0;function Dr(e,t=""){if(!e)throw new Error(t)}function Es(e,t){if(e.byteLength!==t.byteLength)return!1;e instanceof DataView||(e=new DataView(ArrayBuffer.isView(e)?e.buffer:e)),t instanceof DataView||(t=new DataView(ArrayBuffer.isView(t)?t.buffer:t)),Dr(e instanceof DataView),Dr(t instanceof DataView);let r=e.byteLength,o=0,n=-1;for(;++n<r;)o|=e.getUint8(n)^t.getUint8(n);return o===0}Oe.timingSafeEqual=Es});var Gr=Te(D=>{"use strict";var Is=D&&D.__extends||(function(){var e=function(t,r){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(o,n){o.__proto__=n}||function(o,n){for(var a in n)n.hasOwnProperty(a)&&(o[a]=n[a])},e(t,r)};return function(t,r){e(t,r);function o(){this.constructor=t}t.prototype=r===null?Object.create(r):(o.prototype=r.prototype,new o)}})();Object.defineProperty(D,"__esModule",{value:!0});var N=256,mt=(function(){function e(t){t===void 0&&(t="="),this._paddingCharacter=t}return e.prototype.encodedLength=function(t){return this._paddingCharacter?(t+2)/3*4|0:(t*8+5)/6|0},e.prototype.encode=function(t){for(var r="",o=0;o<t.length-2;o+=3){var n=t[o]<<16|t[o+1]<<8|t[o+2];r+=this._encodeByte(n>>>18&63),r+=this._encodeByte(n>>>12&63),r+=this._encodeByte(n>>>6&63),r+=this._encodeByte(n>>>0&63)}var a=t.length-o;if(a>0){var n=t[o]<<16|(a===2?t[o+1]<<8:0);r+=this._encodeByte(n>>>18&63),r+=this._encodeByte(n>>>12&63),a===2?r+=this._encodeByte(n>>>6&63):r+=this._paddingCharacter||"",r+=this._paddingCharacter||""}return r},e.prototype.maxDecodedLength=function(t){return this._paddingCharacter?t/4*3|0:(t*6+7)/8|0},e.prototype.decodedLength=function(t){return this.maxDecodedLength(t.length-this._getPaddingLength(t))},e.prototype.decode=function(t){if(t.length===0)return new Uint8Array(0);for(var r=this._getPaddingLength(t),o=t.length-r,n=new Uint8Array(this.maxDecodedLength(o)),a=0,i=0,c=0,l=0,p=0,g=0,f=0;i<o-4;i+=4)l=this._decodeChar(t.charCodeAt(i+0)),p=this._decodeChar(t.charCodeAt(i+1)),g=this._decodeChar(t.charCodeAt(i+2)),f=this._decodeChar(t.charCodeAt(i+3)),n[a++]=l<<2|p>>>4,n[a++]=p<<4|g>>>2,n[a++]=g<<6|f,c|=l&N,c|=p&N,c|=g&N,c|=f&N;if(i<o-1&&(l=this._decodeChar(t.charCodeAt(i)),p=this._decodeChar(t.charCodeAt(i+1)),n[a++]=l<<2|p>>>4,c|=l&N,c|=p&N),i<o-2&&(g=this._decodeChar(t.charCodeAt(i+2)),n[a++]=p<<4|g>>>2,c|=g&N),i<o-3&&(f=this._decodeChar(t.charCodeAt(i+3)),n[a++]=g<<6|f,c|=f&N),c!==0)throw new Error("Base64Coder: incorrect characters for decoding");return n},e.prototype._encodeByte=function(t){var r=t;return r+=65,r+=25-t>>>8&6,r+=51-t>>>8&-75,r+=61-t>>>8&-15,r+=62-t>>>8&3,String.fromCharCode(r)},e.prototype._decodeChar=function(t){var r=N;return r+=(42-t&t-44)>>>8&-N+t-43+62,r+=(46-t&t-48)>>>8&-N+t-47+63,r+=(47-t&t-58)>>>8&-N+t-48+52,r+=(64-t&t-91)>>>8&-N+t-65+0,r+=(96-t&t-123)>>>8&-N+t-97+26,r},e.prototype._getPaddingLength=function(t){var r=0;if(this._paddingCharacter){for(var o=t.length-1;o>=0&&t[o]===this._paddingCharacter;o--)r++;if(t.length<4||r>2)throw new Error("Base64Coder: incorrect padding")}return r},e})();D.Coder=mt;var ve=new mt;function $s(e){return ve.encode(e)}D.encode=$s;function js(e){return ve.decode(e)}D.decode=js;var Br=(function(e){Is(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype._encodeByte=function(r){var o=r;return o+=65,o+=25-r>>>8&6,o+=51-r>>>8&-75,o+=61-r>>>8&-13,o+=62-r>>>8&49,String.fromCharCode(o)},t.prototype._decodeChar=function(r){var o=N;return o+=(44-r&r-46)>>>8&-N+r-45+62,o+=(94-r&r-96)>>>8&-N+r-95+63,o+=(47-r&r-58)>>>8&-N+r-48+52,o+=(64-r&r-91)>>>8&-N+r-65+0,o+=(96-r&r-123)>>>8&-N+r-97+26,o},t})(mt);D.URLSafeCoder=Br;var Mr=new Br;function Os(e){return Mr.encode(e)}D.encodeURLSafe=Os;function Vs(e){return Mr.decode(e)}D.decodeURLSafe=Vs;D.encodedLength=function(e){return ve.encodedLength(e)};D.maxDecodedLength=function(e){return ve.maxDecodedLength(e)};D.decodedLength=function(e){return ve.decodedLength(e)}});var Hr=Te((zr,Ve)=>{"use strict";(function(e,t){var r={};t(r);var o=r.default;for(var n in r)o[n]=r[n];typeof Ve=="object"&&typeof Ve.exports=="object"?Ve.exports=o:typeof define=="function"&&define.amd?define(function(){return o}):e.sha256=o})(zr,function(e){"use strict";e.__esModule=!0,e.digestLength=32,e.blockSize=64;var t=new Uint32Array([1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298]);function r(f,d,m,y,A){for(var k,v,S,W,E,T,G,I,O,$,ge,ye,Pe;A>=64;){for(k=d[0],v=d[1],S=d[2],W=d[3],E=d[4],T=d[5],G=d[6],I=d[7],$=0;$<16;$++)ge=y+$*4,f[$]=(m[ge]&255)<<24|(m[ge+1]&255)<<16|(m[ge+2]&255)<<8|m[ge+3]&255;for($=16;$<64;$++)O=f[$-2],ye=(O>>>17|O<<15)^(O>>>19|O<<13)^O>>>10,O=f[$-15],Pe=(O>>>7|O<<25)^(O>>>18|O<<14)^O>>>3,f[$]=(ye+f[$-7]|0)+(Pe+f[$-16]|0);for($=0;$<64;$++)ye=(((E>>>6|E<<26)^(E>>>11|E<<21)^(E>>>25|E<<7))+(E&T^~E&G)|0)+(I+(t[$]+f[$]|0)|0)|0,Pe=((k>>>2|k<<30)^(k>>>13|k<<19)^(k>>>22|k<<10))+(k&v^k&S^v&S)|0,I=G,G=T,T=E,E=W+ye|0,W=S,S=v,v=k,k=ye+Pe|0;d[0]+=k,d[1]+=v,d[2]+=S,d[3]+=W,d[4]+=E,d[5]+=T,d[6]+=G,d[7]+=I,y+=64,A-=64}return y}var o=(function(){function f(){this.digestLength=e.digestLength,this.blockSize=e.blockSize,this.state=new Int32Array(8),this.temp=new Int32Array(64),this.buffer=new Uint8Array(128),this.bufferLength=0,this.bytesHashed=0,this.finished=!1,this.reset()}return f.prototype.reset=function(){return this.state[0]=1779033703,this.state[1]=3144134277,this.state[2]=1013904242,this.state[3]=2773480762,this.state[4]=1359893119,this.state[5]=2600822924,this.state[6]=528734635,this.state[7]=1541459225,this.bufferLength=0,this.bytesHashed=0,this.finished=!1,this},f.prototype.clean=function(){for(var d=0;d<this.buffer.length;d++)this.buffer[d]=0;for(var d=0;d<this.temp.length;d++)this.temp[d]=0;this.reset()},f.prototype.update=function(d,m){if(m===void 0&&(m=d.length),this.finished)throw new Error("SHA256: can't update because hash was finished.");var y=0;if(this.bytesHashed+=m,this.bufferLength>0){for(;this.bufferLength<64&&m>0;)this.buffer[this.bufferLength++]=d[y++],m--;this.bufferLength===64&&(r(this.temp,this.state,this.buffer,0,64),this.bufferLength=0)}for(m>=64&&(y=r(this.temp,this.state,d,y,m),m%=64);m>0;)this.buffer[this.bufferLength++]=d[y++],m--;return this},f.prototype.finish=function(d){if(!this.finished){var m=this.bytesHashed,y=this.bufferLength,A=m/536870912|0,k=m<<3,v=m%64<56?64:128;this.buffer[y]=128;for(var S=y+1;S<v-8;S++)this.buffer[S]=0;this.buffer[v-8]=A>>>24&255,this.buffer[v-7]=A>>>16&255,this.buffer[v-6]=A>>>8&255,this.buffer[v-5]=A>>>0&255,this.buffer[v-4]=k>>>24&255,this.buffer[v-3]=k>>>16&255,this.buffer[v-2]=k>>>8&255,this.buffer[v-1]=k>>>0&255,r(this.temp,this.state,this.buffer,0,v),this.finished=!0}for(var S=0;S<8;S++)d[S*4+0]=this.state[S]>>>24&255,d[S*4+1]=this.state[S]>>>16&255,d[S*4+2]=this.state[S]>>>8&255,d[S*4+3]=this.state[S]>>>0&255;return this},f.prototype.digest=function(){var d=new Uint8Array(this.digestLength);return this.finish(d),d},f.prototype._saveState=function(d){for(var m=0;m<this.state.length;m++)d[m]=this.state[m]},f.prototype._restoreState=function(d,m){for(var y=0;y<this.state.length;y++)this.state[y]=d[y];this.bytesHashed=m,this.finished=!1,this.bufferLength=0},f})();e.Hash=o;var n=(function(){function f(d){this.inner=new o,this.outer=new o,this.blockSize=this.inner.blockSize,this.digestLength=this.inner.digestLength;var m=new Uint8Array(this.blockSize);if(d.length>this.blockSize)new o().update(d).finish(m).clean();else for(var y=0;y<d.length;y++)m[y]=d[y];for(var y=0;y<m.length;y++)m[y]^=54;this.inner.update(m);for(var y=0;y<m.length;y++)m[y]^=106;this.outer.update(m),this.istate=new Uint32Array(8),this.ostate=new Uint32Array(8),this.inner._saveState(this.istate),this.outer._saveState(this.ostate);for(var y=0;y<m.length;y++)m[y]=0}return f.prototype.reset=function(){return this.inner._restoreState(this.istate,this.inner.blockSize),this.outer._restoreState(this.ostate,this.outer.blockSize),this},f.prototype.clean=function(){for(var d=0;d<this.istate.length;d++)this.ostate[d]=this.istate[d]=0;this.inner.clean(),this.outer.clean()},f.prototype.update=function(d){return this.inner.update(d),this},f.prototype.finish=function(d){return this.outer.finished?this.outer.finish(d):(this.inner.finish(d),this.outer.update(d,this.digestLength).finish(d)),this},f.prototype.digest=function(){var d=new Uint8Array(this.digestLength);return this.finish(d),d},f})();e.HMAC=n;function a(f){var d=new o().update(f),m=d.digest();return d.clean(),m}e.hash=a,e.default=a;function i(f,d){var m=new n(f).update(d),y=m.digest();return m.clean(),y}e.hmac=i;function c(f,d,m,y){var A=y[0];if(A===0)throw new Error("hkdf: cannot expand more");d.reset(),A>1&&d.update(f),m&&d.update(m),d.update(y),d.finish(f),y[0]++}var l=new Uint8Array(e.digestLength);function p(f,d,m,y){d===void 0&&(d=l),y===void 0&&(y=32);for(var A=new Uint8Array([1]),k=i(d,f),v=new n(k),S=new Uint8Array(v.digestLength),W=S.length,E=new Uint8Array(y),T=0;T<y;T++)W===S.length&&(c(S,v,m,A),W=0),E[T]=S[W++];return v.clean(),S.fill(0),A.fill(0),E}e.hkdf=p;function g(f,d,m,y){for(var A=new n(f),k=A.digestLength,v=new Uint8Array(4),S=new Uint8Array(k),W=new Uint8Array(k),E=new Uint8Array(y),T=0;T*k<y;T++){var G=T+1;v[0]=G>>>24&255,v[1]=G>>>16&255,v[2]=G>>>8&255,v[3]=G>>>0&255,A.reset(),A.update(d),A.update(v),A.finish(W);for(var I=0;I<k;I++)S[I]=W[I];for(var I=2;I<=m;I++){A.reset(),A.update(W).finish(W);for(var O=0;O<k;O++)S[O]^=W[O]}for(var I=0;I<k&&T*k+I<y;I++)E[T*k+I]=S[I]}for(var T=0;T<k;T++)S[T]=W[T]=0;for(var T=0;T<4;T++)v[T]=0;return A.clean(),E}e.pbkdf2=g})});var Yr=Te(pe=>{"use strict";Object.defineProperty(pe,"__esModule",{value:!0});pe.Webhook=pe.WebhookVerificationError=void 0;var Us=qr(),Jr=Gr(),Fs=Hr(),Kr=300,ht=class e extends Error{constructor(t){super(t),Object.setPrototypeOf(this,e.prototype),this.name="ExtendableError",this.stack=new Error(t).stack}},te=class e extends ht{constructor(t){super(t),Object.setPrototypeOf(this,e.prototype),this.name="WebhookVerificationError"}};pe.WebhookVerificationError=te;var Ue=class e{constructor(t,r){if(!t)throw new Error("Secret can't be empty.");if(r?.format==="raw")t instanceof Uint8Array?this.key=t:this.key=Uint8Array.from(t,o=>o.charCodeAt(0));else{if(typeof t!="string")throw new Error("Expected secret to be of type string");t.startsWith(e.prefix)&&(t=t.substring(e.prefix.length)),this.key=Jr.decode(t)}}verify(t,r){let o={};for(let d of Object.keys(r))o[d.toLowerCase()]=r[d];let n=o["webhook-id"],a=o["webhook-signature"],i=o["webhook-timestamp"];if(!a||!n||!i)throw new te("Missing required headers");let c=this.verifyTimestamp(i),p=this.sign(n,c,t).split(",")[1],g=a.split(" "),f=new globalThis.TextEncoder;for(let d of g){let[m,y]=d.split(",");if(m==="v1"&&(0,Us.timingSafeEqual)(f.encode(y),f.encode(p)))return JSON.parse(t.toString())}throw new te("No matching signature found")}sign(t,r,o){if(typeof o!="string")if(o.constructor.name==="Buffer")o=o.toString();else throw new Error("Expected payload to be of type string or Buffer.");let n=new TextEncoder,a=Math.floor(r.getTime()/1e3),i=n.encode(`${t}.${a}.${o}`);return`v1,${Jr.encode(Fs.hmac(this.key,i))}`}verifyTimestamp(t){let r=Math.floor(Date.now()/1e3),o=parseInt(t,10);if(isNaN(o))throw new te("Invalid Signature Headers");if(r-o>Kr)throw new te("Message timestamp too old");if(o>r+Kr)throw new te("Message timestamp too new");return new Date(o*1e3)}};pe.Webhook=Ue;Ue.prefix="whsec_"});import{StdioServerTransport as Ul}from"@modelcontextprotocol/sdk/server/stdio.js";import{McpServer as El}from"@modelcontextprotocol/sdk/server/mcp.js";import{z as H}from"zod";function z({locator:e,page:t}){switch(e.by){case"css":return t.locator(e.value);case"testId":return t.getByTestId(e.value);case"role":return t.getByRole(e.role,{exact:e.exact,name:e.name});case"text":return t.getByText(e.value,{exact:e.exact});case"label":return t.getByLabel(e.value,{exact:e.exact});case"placeholder":return t.getByPlaceholder(e.value);case"altText":return t.getByAltText(e.value)}}import rp from"@anthropic-ai/sdk";import{z as Pt}from"zod";import{z as Go}from"zod";async function be({page:e,runStartTime:t,targets:r}){let o=e.viewportSize();if(o==null)throw new Error("Page has no viewport set");let[n,a]=await Promise.all([e.screenshot({type:"png"}),qo(r)]),i=n.toString("base64");return{annotations:a,screenshotBase64:i,snapshotTimestamp:Math.round(performance.now()-t),url:e.url(),viewportHeight:o.height,viewportWidth:o.width}}function Do(e){return"locator"in e}async function qo(e){return(await Promise.all(e.map(r=>Bo(r)))).filter(r=>r!=null)}async function Bo(e){if(!Do(e))return{height:0,label:e.label,type:e.type,width:0,x:0,y:0};let t=await e.locator.boundingBox({timeout:2e3}).catch(()=>null);if(t!=null)return{height:Math.round(t.height),label:e.label,type:e.type,width:Math.round(t.width),x:Math.round(t.x),y:Math.round(t.y)}}function Me({node:e,page:t}){if(e.type==="assertUrl"||e.type==="waitForUrl")return[{label:e.type,type:"urlBar"}];if(e.type==="drag"){let n=z({locator:e.source,page:t}),a=z({locator:e.target,page:t});return[{label:"drag-source",locator:n,type:"action"},{label:"drag-target",locator:a,type:"action"}]}if(!Mo(e))return[];let r=e.type.startsWith("assert")?"assertion":"action",o=z({locator:e.locator,page:t});return[{label:e.type,locator:o,type:r}]}function Mo(e){return"locator"in e&&e.locator!=null}function u({description:e,execute:t,name:r,schema:o}){let n=Go.toJSONSchema(o,{target:"draft-2020-12"});return{anthropicTool:{description:e,input_schema:{...n,type:"object"},name:r},name:r,async execute(a,i){let c=o.parse(i);return{...await Promise.resolve(t(a,c)),kind:"action"}}}}async function h(e){let t=e.specNode==null?[]:Me({node:e.specNode,page:e.page}),r=await be({page:e.page,runStartTime:e.runStartTime,targets:t}),o=[...e.assertions],n={annotations:r.annotations,assertions:o,detail:e.detail,duration:Math.round(e.duration),nodeId:`agent-step-${String(e.stepIndex)}`,nodeType:e.nodeType,screenshotBase64:r.screenshotBase64,snapshotTimestamp:r.snapshotTimestamp,status:e.status,stepIndex:e.stepIndex,title:e.title,url:r.url,viewportHeight:r.viewportHeight,viewportWidth:r.viewportWidth},a=o.length>0?o.map(i=>`${i.status}: ${i.description} \u2014 ${i.detail??""}`).join(`
3
+ `):`${e.status}: ${e.title}`;return{specNode:e.specNode,stepResult:n,toolOutput:a}}var zo=Pt.object({selector:Pt.string().describe("CSS selector for the checkbox or radio button")}),Tt=u({description:"Assert that a checkbox or radio button is checked",name:"assert_checked",schema:zo,execute:async(e,t)=>{let r=performance.now(),o=await e.page.locator(t.selector).isChecked().catch(()=>!1),n=o?"passed":"failed",a=[{description:`Element ${t.selector} is checked`,detail:o?void 0:"Element is not checked",status:n}],c={id:`agent-step-${String(e.stepIndex)}`,locator:{by:"css",value:t.selector},type:"assertChecked"};return h({assertions:a,detail:o?"Element is checked":"Element is not checked",duration:performance.now()-r,nodeType:"assertChecked",page:e.page,runStartTime:e.runStartTime,specNode:c,status:n,stepIndex:e.stepIndex,title:`Assert checked: ${t.selector}`})}});import{z as ce}from"zod";var Ho=ce.object({httpOnly:ce.boolean().optional().describe("Assert httpOnly flag value"),name:ce.string().describe("Cookie name to check"),sameSite:ce.enum(["Strict","Lax","None"]).optional().describe("Assert sameSite value"),secure:ce.boolean().optional().describe("Assert secure flag value"),value:ce.string().optional().describe("Expected cookie value (substring match)")}),At=u({description:"Assert properties of a browser cookie. Check existence, value, and security flags (httpOnly, secure, sameSite).",name:"assert_cookie",schema:Ho,execute:async(e,t)=>{let r=performance.now(),n=(await e.page.context().cookies()).find(g=>g.name===t.name),a=n==null?[{description:`Cookie "${t.name}" exists`,detail:"Cookie not found",status:"failed"}]:[{description:`Cookie "${t.name}" exists`,detail:void 0,status:"passed"},...Jo({cookie:n,input:t})],i=a.some(g=>g.status==="failed"),c=i?"failed":"passed",p={id:`agent-step-${String(e.stepIndex)}`,name:{type:"static",value:t.name},type:"assertCookie"};return h({assertions:a,detail:i?a.filter(g=>g.status==="failed").map(g=>g.detail).join("; "):"Cookie OK",duration:performance.now()-r,nodeType:"assertCookie",page:e.page,runStartTime:e.runStartTime,specNode:p,status:c,stepIndex:e.stepIndex,title:`Assert cookie: ${t.name}`})}});function Jo({cookie:e,input:t}){return[Ko(t.value,e.value),Ct("httpOnly",t.httpOnly,e.httpOnly),Ct("secure",t.secure,e.secure),Yo("sameSite",t.sameSite,e.sameSite)].filter(r=>r!=null)}function Ko(e,t){if(e==null)return;let r=t.includes(e);return{description:`Cookie value contains "${e}"`,detail:r?void 0:`Got: "${t}"`,status:r?"passed":"failed"}}function Ct(e,t,r){if(t!=null)return{description:`Cookie ${e} is ${String(t)}`,detail:r===t?void 0:`Got: ${String(r)}`,status:r===t?"passed":"failed"}}function Yo(e,t,r){if(t!=null)return{description:`Cookie ${e} is ${t}`,detail:r===t?void 0:`Got: ${r}`,status:r===t?"passed":"failed"}}import{z as Nt}from"zod";var Qo=Nt.object({selector:Nt.string().describe("CSS selector for the element to check focus on")}),Et=u({description:"Assert that an element has keyboard focus. Use for accessibility tab-order testing.",name:"assert_focused",schema:Qo,execute:async(e,t)=>{let r=performance.now(),n=!!await e.page.locator(t.selector).evaluate("(el) => document.activeElement === el").catch(()=>!1),a=n?"passed":"failed",i=[{description:`Element ${t.selector} is focused`,detail:n?void 0:"Element does not have focus",status:a}],l={id:`agent-step-${String(e.stepIndex)}`,locator:{by:"css",value:t.selector},type:"assertFocused"};return h({assertions:i,detail:n?"Element is focused":"Element does not have focus",duration:performance.now()-r,nodeType:"assertFocused",page:e.page,runStartTime:e.runStartTime,specNode:l,status:a,stepIndex:e.stepIndex,title:`Assert focused: ${t.selector}`})}});import{z as de}from"zod";var Xo=de.object({bodyContains:de.string().optional().describe("Assert response body contains this string"),headerName:de.string().optional().describe("Response header name to check"),headerValue:de.string().optional().describe("Expected value the header should contain"),status:de.number().int().optional().describe("Expected HTTP status code"),urlPattern:de.string().describe("URL substring to match the response against")}),It=u({description:"Wait for a network response matching the URL pattern and assert on its status, body, or headers. Useful for verifying API calls and security headers (CSP, CORS).",name:"assert_response",schema:Xo,execute:async(e,t)=>{let r=performance.now(),o=await e.page.waitForResponse(p=>p.url().includes(t.urlPattern),{timeout:1e4}),n=[Zo(t.status,o.status()),await en(t.bodyContains,o),tn(t.headerName,t.headerValue,o.headers())].filter(p=>p!=null),a=n.length===0?[{description:`Response matching "${t.urlPattern}" received`,detail:void 0,status:"passed"}]:n,i=a.some(p=>p.status==="failed"),l={id:`agent-step-${String(e.stepIndex)}`,type:"assertResponse",urlPattern:{type:"static",value:t.urlPattern}};return h({assertions:a,detail:i?a.filter(p=>p.status==="failed").map(p=>p.detail).join("; "):"Response OK",duration:performance.now()-r,nodeType:"assertResponse",page:e.page,runStartTime:e.runStartTime,specNode:l,status:i?"failed":"passed",stepIndex:e.stepIndex,title:`Assert response: ${t.urlPattern}`})}});function Zo(e,t){if(e!=null)return{description:`Response status equals ${String(e)}`,detail:t===e?void 0:`Got: ${String(t)}`,status:t===e?"passed":"failed"}}async function en(e,t){if(e==null)return;let o=(await t.text()).includes(e);return{description:`Response body contains "${e}"`,detail:o?void 0:"Not found in response body",status:o?"passed":"failed"}}function tn(e,t,r){if(e==null||t==null)return;let o=r[e.toLowerCase()],n=o!=null&&o.includes(t);return{description:`Response header "${e}" contains "${t}"`,detail:n?void 0:`Got: "${o??"(missing)"}"`,status:n?"passed":"failed"}}import{z as Ge}from"zod";var rn=Ge.object({expected:Ge.string().describe("The expected text content"),selector:Ge.string().describe("CSS selector for the element to check")}),$t=u({description:"Assert that an element's text content matches the expected value",name:"assert_text",schema:rn,execute:async(e,t)=>{let r=performance.now(),o=await e.page.locator(t.selector).textContent({timeout:5e3}).catch(()=>null),n=o!=null&&o.includes(t.expected),a=n?"passed":"failed",i=[{description:`Text contains "${t.expected}"`,detail:`Actual: "${o??"(not found)"}"`,status:a}],c=`agent-step-${String(e.stepIndex)}`,l={expected:{type:"static",value:t.expected},id:c,locator:{by:"css",value:t.selector},operator:"contains",type:"assertText"};return h({assertions:i,detail:n?`Text matches: "${t.expected}"`:`Expected "${t.expected}", got "${o??"(not found)"}"`,duration:performance.now()-r,nodeType:"assertText",page:e.page,runStartTime:e.runStartTime,specNode:l,status:a,stepIndex:e.stepIndex,title:`Assert text: ${t.selector}`})}});import{z as ze}from"zod";var on=ze.object({expected:ze.string().describe("Expected page title or substring"),operator:ze.enum(["equals","contains","startsWith","endsWith"]).describe("Comparison operator").default("contains")}),jt=u({description:"Assert that the page title matches an expected value",name:"assert_title",schema:on,execute:async(e,t)=>{let r=performance.now(),o=await e.page.title(),n=nn({actual:o,expected:t.expected,operator:t.operator}),a=n?"passed":"failed",i=[{description:`Title ${t.operator} "${t.expected}"`,detail:n?void 0:`Got: "${o}"`,status:a}],c=`agent-step-${String(e.stepIndex)}`,l={expected:{type:"static",value:t.expected},id:c,operator:t.operator,type:"assertTitle"};return h({assertions:i,detail:n?"Title matches":`Expected title ${t.operator} "${t.expected}", got "${o}"`,duration:performance.now()-r,nodeType:"assertTitle",page:e.page,runStartTime:e.runStartTime,specNode:l,status:a,stepIndex:e.stepIndex,title:`Assert title ${t.operator} "${t.expected}"`})}});function nn({actual:e,expected:t,operator:r}){return r==="equals"?e===t:r==="contains"?e.includes(t):r==="startsWith"?e.startsWith(t):r==="endsWith"?e.endsWith(t):!1}import{z as Ot}from"zod";var an=Ot.object({selector:Ot.string().describe("CSS selector for the element to check")}),Vt=u({description:"Assert that an element matching the selector is visible on the page",name:"assert_visible",schema:an,execute:async(e,t)=>{let r=performance.now(),o=await e.page.locator(t.selector).isVisible().catch(()=>!1),n=o?"passed":"failed",a=[{description:`Element ${t.selector} is visible`,detail:o?"Element is visible":"Element is not visible",status:n}],c={id:`agent-step-${String(e.stepIndex)}`,locator:{by:"css",value:t.selector},type:"assertVisible"};return h({assertions:a,detail:o?"Element is visible":"Element is not visible",duration:performance.now()-r,nodeType:"assertVisible",page:e.page,runStartTime:e.runStartTime,specNode:c,status:n,stepIndex:e.stepIndex,title:`Assert visible: ${t.selector}`})}});import{z as Ut}from"zod";var sn=Ut.object({selector:Ut.string().describe("CSS selector for the checkbox to check")}),Ft=u({description:"Check a checkbox matching the CSS selector",name:"check",schema:sn,execute:async(e,t)=>{let r=performance.now();await e.page.locator(t.selector).check({timeout:5e3});let n={id:`agent-step-${String(e.stepIndex)}`,locator:{by:"css",value:t.selector},type:"check"};return h({assertions:[],detail:`Checked ${t.selector}`,duration:performance.now()-r,nodeType:"check",page:e.page,runStartTime:e.runStartTime,specNode:n,status:"passed",stepIndex:e.stepIndex,title:`Check ${t.selector}`})}});import{z as Wt}from"zod";var ln=Wt.object({selector:Wt.string().describe("CSS selector for the input to clear")}),Lt=u({description:"Clear the contents of an input field",name:"clear",schema:ln,execute:async(e,t)=>{let r=performance.now();await e.page.locator(t.selector).clear({timeout:5e3});let n={id:`agent-step-${String(e.stepIndex)}`,locator:{by:"css",value:t.selector},type:"clear"};return h({assertions:[],detail:`Cleared ${t.selector}`,duration:performance.now()-r,nodeType:"clear",page:e.page,runStartTime:e.runStartTime,specNode:n,status:"passed",stepIndex:e.stepIndex,title:`Clear ${t.selector}`})}});import{z as _t}from"zod";var cn=_t.object({selector:_t.string().describe("CSS selector for the element to click")}),Dt=u({description:"Click an element matching the CSS selector",name:"click",schema:cn,execute:async(e,t)=>{let r=performance.now();await e.page.locator(t.selector).click({timeout:5e3});let n={id:`agent-step-${String(e.stepIndex)}`,locator:{by:"css",value:t.selector},type:"click"};return h({assertions:[],detail:`Clicked ${t.selector}`,duration:performance.now()-r,nodeType:"click",page:e.page,runStartTime:e.runStartTime,specNode:n,status:"passed",stepIndex:e.stepIndex,title:`Click ${t.selector}`})}});import{z as He}from"zod";var dn=He.object({action:He.enum(["read","write"]).describe("Whether to read from or write to the clipboard"),value:He.string().optional().describe("Text to write (required for write action)")}),qt=u({description:"Read from or write to the browser clipboard. For write, provide the text value. For read, the clipboard contents are returned.",name:"clipboard",schema:dn,execute:async(e,t)=>{let r=performance.now(),o=`agent-step-${String(e.stepIndex)}`;if(t.action==="write"){if(t.value==null)throw new Error("clipboard write requires a value");let i=JSON.stringify(t.value);await e.page.evaluate(`navigator.clipboard.writeText(${i})`);let c={action:"write",id:o,type:"clipboard",value:{type:"static",value:t.value}};return h({assertions:[],detail:`Wrote to clipboard: "${t.value}"`,duration:performance.now()-r,nodeType:"clipboard",page:e.page,runStartTime:e.runStartTime,specNode:c,status:"passed",stepIndex:e.stepIndex,title:"Clipboard write"})}let n=String(await e.page.evaluate("navigator.clipboard.readText()")),a={action:"read",id:o,type:"clipboard"};return h({assertions:[],detail:`Clipboard contents: "${n}"`,duration:performance.now()-r,nodeType:"clipboard",page:e.page,runStartTime:e.runStartTime,specNode:a,status:"passed",stepIndex:e.stepIndex,title:"Clipboard read"})}});import{z as Ce}from"zod";var Bt=Ce.object({summary:Ce.string().describe("This is the ONLY output the user sees. If the agent profile specifies an Output format, follow those instructions exactly. Otherwise, provide a summary of your findings."),verdict:Ce.enum(["pass","fail"]).describe("Whether the test passed or failed. If the agent profile defines Success Criteria, base this verdict on whether those criteria were met.")}),Mt={anthropicTool:{description:"Call this tool when you have finished your evaluation. If the agent profile defines an Output section, the summary MUST follow those output instructions exactly. Otherwise, summarize your findings.",input_schema:{...Ce.toJSONSchema(Bt,{target:"draft-2020-12"}),type:"object"},name:"complete_test"},name:"complete_test",execute(e,t){let r=Bt.parse(t),o={kind:"verdict",summary:r.summary,toolOutput:`Verdict: ${r.verdict}
4
+ ${r.summary}`,verdict:r.verdict};return Promise.resolve(o)}};import{z as Gt}from"zod";var un=Gt.object({selector:Gt.string().describe("CSS selector for the element to extract text from")}),zt=u({description:"Extract the text content of an element matching the CSS selector. Returns the text so you can use it in assertions or decisions.",name:"extract_text",schema:un,execute:async(e,t)=>{let r=performance.now(),n=await e.page.locator(t.selector).textContent({timeout:5e3})??"",i={id:`agent-step-${String(e.stepIndex)}`,locator:{by:"css",value:t.selector},type:"extractText",variable:"extractedText"};return h({assertions:[],detail:`Extracted text: "${n}"`,duration:performance.now()-r,nodeType:"extractText",page:e.page,runStartTime:e.runStartTime,specNode:i,status:"passed",stepIndex:e.stepIndex,title:`Extract text from ${t.selector}`})}});import{z as Je}from"zod";var pn=Je.object({selector:Je.string().describe("CSS selector for the input element"),value:Je.string().describe("The text to type into the element")}),Ht=u({description:"Clear and type text into an input element matching the CSS selector",name:"fill",schema:pn,execute:async(e,t)=>{let r=performance.now();await e.page.locator(t.selector).fill(t.value,{timeout:5e3});let n={id:`agent-step-${String(e.stepIndex)}`,locator:{by:"css",value:t.selector},type:"fill",value:{type:"static",value:t.value}};return h({assertions:[],detail:`Filled ${t.selector} with "${t.value}"`,duration:performance.now()-r,nodeType:"fill",page:e.page,runStartTime:e.runStartTime,specNode:n,status:"passed",stepIndex:e.stepIndex,title:`Fill ${t.selector}`})}});import{z as Jt}from"zod";var fn=Jt.object({level:Jt.string().describe("Filter by log level: 'error', 'warning', 'log', 'info', 'debug', or 'all'").default("all")}),Kt=u({description:"Get console log messages from the browser. Optionally filter by level (error, warning, log, info, debug, all).",name:"get_console_logs",schema:fn,execute:(e,t)=>{let r=t.level==="all"?e.monitor.consoleEntries:e.monitor.consoleEntries.filter(n=>n.level===t.level);if(r.length===0)return{specNode:void 0,stepResult:void 0,toolOutput:`No console ${t.level==="all"?"messages":t.level+" messages"} recorded.`};let o=r.map(n=>mn(n)).join(`
5
5
  `);return{specNode:void 0,stepResult:void 0,toolOutput:`${String(r.length)} console messages:
6
- ${o}`}}});function mn(e){let t=e.url.length>0?` (${e.url})`:"";return`[${e.level.toUpperCase()}] ${e.text}${t}`}import{z as Jt}from"zod";var Ke=50,hn=Jt.object({urlFilter:Jt.string().describe("Optional regex to filter network entries by URL. Omit to see all.").default("")}),Kt=u({description:"Get a summary of network requests/responses. Shows method, URL, status code, and content type. Optionally filter by URL regex.",name:"get_network_log",schema:hn,execute:(e,t)=>{let r=t.urlFilter.length>0?new RegExp(t.urlFilter,"i"):void 0,o=r==null?e.monitor.networkEntries:e.monitor.networkEntries.filter(c=>r.test(c.url));if(o.length===0)return{specNode:void 0,stepResult:void 0,toolOutput:"No network requests recorded."};let n=o.slice(-Ke),a=n.map(c=>{let l=c.statusCode==null?"pending":String(c.statusCode),p=c.responseHeaders["content-type"]??"";return`${c.method} ${l} ${c.url} [${p}]`}).join(`
6
+ ${o}`}}});function mn(e){let t=e.url.length>0?` (${e.url})`:"";return`[${e.level.toUpperCase()}] ${e.text}${t}`}import{z as Yt}from"zod";var Ke=50,hn=Yt.object({urlFilter:Yt.string().describe("Optional regex to filter network entries by URL. Omit to see all.").default("")}),Qt=u({description:"Get a summary of network requests/responses. Shows method, URL, status code, and content type. Optionally filter by URL regex.",name:"get_network_log",schema:hn,execute:(e,t)=>{let r=t.urlFilter.length>0?new RegExp(t.urlFilter,"i"):void 0,o=r==null?e.monitor.networkEntries:e.monitor.networkEntries.filter(c=>r.test(c.url));if(o.length===0)return{specNode:void 0,stepResult:void 0,toolOutput:"No network requests recorded."};let n=o.slice(-Ke),a=n.map(c=>{let l=c.statusCode==null?"pending":String(c.statusCode),p=c.responseHeaders["content-type"]??"";return`${c.method} ${l} ${c.url} [${p}]`}).join(`
7
7
  `),i=o.length>Ke?`
8
8
  (showing last ${String(Ke)} of ${String(o.length)})`:"";return{specNode:void 0,stepResult:void 0,toolOutput:`${String(n.length)} network requests:${i}
9
- ${a}`}}});import{z as Yt}from"zod";var gn=Yt.object({urlPattern:Yt.string().describe("Regex pattern to match the request URL")}),Qt=u({description:"Get the full response body for a specific network request matching the URL pattern. Returns the most recent match.",name:"get_network_response",schema:gn,execute:(e,t)=>{let r=new RegExp(t.urlPattern,"i"),o=e.monitor.networkEntries.filter(l=>r.test(l.url));if(o.length===0)return{specNode:void 0,stepResult:void 0,toolOutput:`No network requests matching: ${t.urlPattern}`};let n=o.at(-1);if(n==null)return{specNode:void 0,stepResult:void 0,toolOutput:`No network requests matching: ${t.urlPattern}`};let a=n.statusCode==null?"pending":String(n.statusCode),i=Object.entries(n.responseHeaders).map(([l,p])=>` ${l}: ${p}`).join(`
9
+ ${a}`}}});import{z as Xt}from"zod";var gn=Xt.object({urlPattern:Xt.string().describe("Regex pattern to match the request URL")}),Zt=u({description:"Get the full response body for a specific network request matching the URL pattern. Returns the most recent match.",name:"get_network_response",schema:gn,execute:(e,t)=>{let r=new RegExp(t.urlPattern,"i"),o=e.monitor.networkEntries.filter(l=>r.test(l.url));if(o.length===0)return{specNode:void 0,stepResult:void 0,toolOutput:`No network requests matching: ${t.urlPattern}`};let n=o.at(-1);if(n==null)return{specNode:void 0,stepResult:void 0,toolOutput:`No network requests matching: ${t.urlPattern}`};let a=n.statusCode==null?"pending":String(n.statusCode),i=Object.entries(n.responseHeaders).map(([l,p])=>` ${l}: ${p}`).join(`
10
10
  `),c=n.responseBody??"(no body captured)";return{specNode:void 0,stepResult:void 0,toolOutput:`${n.method} ${a} ${n.url}
11
11
 
12
12
  Response Headers:
13
13
  ${i}
14
14
 
15
15
  Body:
16
- ${c}`}}});import{z as Xt}from"zod";var yn=Xt.object({selector:Xt.string().optional().describe("CSS selector to scope the tree to a subtree (defaults to full page)")}),Zt=u({description:"Get the accessibility tree (ARIA snapshot) of the current page or a specific element. Use this to verify ARIA roles, labels, states, and the semantic structure of the page.",name:"get_accessibility_tree",schema:yn,execute:async(e,t)=>{let r=t.selector==null?e.page.locator(":root"):e.page.locator(t.selector);try{return{specNode:void 0,stepResult:void 0,toolOutput:await r.ariaSnapshot()}}catch{return{specNode:void 0,stepResult:void 0,toolOutput:"(unable to capture accessibility tree)"}}}});import{z as wn}from"zod";var bn=wn.object({}),er=u({description:"Get a text representation of the current page content for inspection",name:"get_page_content",schema:bn,execute:async(e,t)=>{let r=await e.page.locator("body").textContent({timeout:5e3}),o=e.page.url(),n=await e.page.title();return{specNode:void 0,stepResult:void 0,toolOutput:`URL: ${o}
16
+ ${c}`}}});import{z as er}from"zod";var yn=er.object({selector:er.string().optional().describe("CSS selector to scope the tree to a subtree (defaults to full page)")}),tr=u({description:"Get the accessibility tree (ARIA snapshot) of the current page or a specific element. Use this to verify ARIA roles, labels, states, and the semantic structure of the page.",name:"get_accessibility_tree",schema:yn,execute:async(e,t)=>{let r=t.selector==null?e.page.locator(":root"):e.page.locator(t.selector);try{return{specNode:void 0,stepResult:void 0,toolOutput:await r.ariaSnapshot()}}catch{return{specNode:void 0,stepResult:void 0,toolOutput:"(unable to capture accessibility tree)"}}}});import{z as wn}from"zod";var bn=wn.object({}),rr=u({description:"Get a text representation of the current page content for inspection",name:"get_page_content",schema:bn,execute:async(e,t)=>{let r=await e.page.locator("body").textContent({timeout:5e3}),o=e.page.url(),n=await e.page.title();return{specNode:void 0,stepResult:void 0,toolOutput:`URL: ${o}
17
17
  Title: ${n}
18
18
 
19
- ${r??""}`}}});import{z as Sn}from"zod";var kn=Sn.object({}),tr=u({description:"Get uncaught JavaScript exceptions (page errors) that have occurred. These are runtime errors thrown in the browser that were not caught by error handlers.",name:"get_page_errors",schema:kn,execute:(e,t)=>{if(e.monitor.pageErrors.length===0)return{specNode:void 0,stepResult:void 0,toolOutput:"No page errors recorded."};let r=e.monitor.pageErrors.map((o,n)=>`--- Error ${String(n+1)} ---
19
+ ${r??""}`}}});import{z as Sn}from"zod";var kn=Sn.object({}),or=u({description:"Get uncaught JavaScript exceptions (page errors) that have occurred. These are runtime errors thrown in the browser that were not caught by error handlers.",name:"get_page_errors",schema:kn,execute:(e,t)=>{if(e.monitor.pageErrors.length===0)return{specNode:void 0,stepResult:void 0,toolOutput:"No page errors recorded."};let r=e.monitor.pageErrors.map((o,n)=>`--- Error ${String(n+1)} ---
20
20
  ${o}`).join(`
21
21
 
22
22
  `);return{specNode:void 0,stepResult:void 0,toolOutput:`${String(e.monitor.pageErrors.length)} page errors:
23
23
 
24
- ${r}`}}});import{z as rr}from"zod";var vn=rr.object({type:rr.enum(["all","cookies","localStorage","sessionStorage"]).describe("Which storage to retrieve").default("all")}),nr=u({description:"Get browser storage state: cookies (with httpOnly, secure, sameSite flags), localStorage, and/or sessionStorage. Useful for verifying authentication state, security cookie flags, and stored data.",name:"get_storage",schema:vn,execute:async(e,t)=>({specNode:void 0,stepResult:void 0,toolOutput:[...t.type==="all"||t.type==="cookies"?[await xn(e.page)]:[],...t.type==="all"||t.type==="localStorage"?[await or(e.page,"localStorage")]:[],...t.type==="all"||t.type==="sessionStorage"?[await or(e.page,"sessionStorage")]:[]].join(`
24
+ ${r}`}}});import{z as nr}from"zod";var vn=nr.object({type:nr.enum(["all","cookies","localStorage","sessionStorage"]).describe("Which storage to retrieve").default("all")}),sr=u({description:"Get browser storage state: cookies (with httpOnly, secure, sameSite flags), localStorage, and/or sessionStorage. Useful for verifying authentication state, security cookie flags, and stored data.",name:"get_storage",schema:vn,execute:async(e,t)=>({specNode:void 0,stepResult:void 0,toolOutput:[...t.type==="all"||t.type==="cookies"?[await xn(e.page)]:[],...t.type==="all"||t.type==="localStorage"?[await ar(e.page,"localStorage")]:[],...t.type==="all"||t.type==="sessionStorage"?[await ar(e.page,"sessionStorage")]:[]].join(`
25
25
 
26
26
  `)})});async function xn(e){let t=await e.context().cookies();if(t.length===0)return`## Cookies
27
27
  (none)`;let r=t.map(o=>` ${o.name}=${o.value} (domain=${o.domain}, httpOnly=${String(o.httpOnly)}, secure=${String(o.secure)}, sameSite=${o.sameSite})`);return`## Cookies (${String(t.length)})
28
28
  ${r.join(`
29
- `)}`}async function or(e,t){let r=await e.evaluate(`JSON.stringify(Object.fromEntries(Object.entries(${t})))`);return`## ${t}
30
- ${String(r)}`}import{z as Ye}from"zod";var Rn=Ye.object({action:Ye.enum(["accept","dismiss"]).describe("Whether to accept or dismiss the dialog"),promptText:Ye.string().optional().describe("Text to enter for prompt dialogs")}),ar=u({description:"Set up a handler for the next browser dialog (alert, confirm, prompt). Call this BEFORE the action that triggers the dialog.",name:"handle_dialog",schema:Rn,execute:(e,t)=>{let r=performance.now();e.page.once("dialog",async a=>{t.action==="accept"?await a.accept(t.promptText??void 0):await a.dismiss()});let o=`agent-step-${String(e.stepIndex)}`,n={action:t.action,id:o,type:"handleDialog"};return h({assertions:[],detail:`Dialog handler set: ${t.action}`,duration:performance.now()-r,nodeType:"handleDialog",page:e.page,runStartTime:e.runStartTime,specNode:n,status:"passed",stepIndex:e.stepIndex,title:`Handle dialog: ${t.action}`})}});import{z as sr}from"zod";var Pn=sr.object({selector:sr.string().describe("CSS selector for the element to hover over")}),ir=u({description:"Hover over an element matching the CSS selector",name:"hover",schema:Pn,execute:async(e,t)=>{let r=performance.now();await e.page.locator(t.selector).hover({timeout:5e3});let n={id:`agent-step-${String(e.stepIndex)}`,locator:{by:"css",value:t.selector},type:"hover"};return h({assertions:[],detail:`Hovered over ${t.selector}`,duration:performance.now()-r,nodeType:"hover",page:e.page,runStartTime:e.runStartTime,specNode:n,status:"passed",stepIndex:e.stepIndex,title:`Hover ${t.selector}`})}});import{z as Qe}from"zod";var Tn=Qe.object({permission:Qe.string().describe("Permission name (e.g. geolocation, notifications, camera, clipboard-read)"),state:Qe.enum(["granted","prompt"]).describe("Permission state to set")}),lr=u({description:"Set a browser permission state (geolocation, notifications, camera, etc.)",name:"set_permission",schema:Tn,execute:async(e,t)=>{let r=performance.now(),o=e.page.context();t.state==="granted"?await o.grantPermissions([t.permission]):await o.clearPermissions();let a={id:`agent-step-${String(e.stepIndex)}`,permission:t.permission,state:t.state,type:"setPermission"};return h({assertions:[],detail:`Set ${t.permission} to ${t.state}`,duration:performance.now()-r,nodeType:"setPermission",page:e.page,runStartTime:e.runStartTime,specNode:a,status:"passed",stepIndex:e.stepIndex,title:`Set permission: ${t.permission} \u2192 ${t.state}`})}});import{z as cr}from"zod";var Cn=cr.object({url:cr.string().describe("The URL to navigate to")}),dr=u({description:"Navigate the browser to a URL",name:"navigate",schema:Cn,execute:async(e,t)=>{let r=performance.now();await e.page.goto(t.url,{waitUntil:"domcontentloaded"});let n={id:`agent-step-${String(e.stepIndex)}`,type:"goto",url:{type:"static",value:t.url}};return h({assertions:[],detail:`Navigated to ${t.url}`,duration:performance.now()-r,nodeType:"goto",page:e.page,runStartTime:e.runStartTime,specNode:n,status:"passed",stepIndex:e.stepIndex,title:`Navigate to ${t.url}`})}});import{z as ur}from"zod";var An=ur.object({key:ur.string().describe("Key to press, e.g. 'Enter', 'Tab', 'Escape'")}),pr=u({description:"Press a keyboard key",name:"press",schema:An,execute:async(e,t)=>{let r=performance.now();await e.page.keyboard.press(t.key);let n={id:`agent-step-${String(e.stepIndex)}`,key:t.key,type:"press"};return h({assertions:[],detail:`Pressed ${t.key}`,duration:performance.now()-r,nodeType:"press",page:e.page,runStartTime:e.runStartTime,specNode:n,status:"passed",stepIndex:e.stepIndex,title:`Press ${t.key}`})}});import{z as Xe}from"zod";var Nn=Xe.object({height:Xe.number().int().positive().describe("Viewport height in pixels"),width:Xe.number().int().positive().describe("Viewport width in pixels")}),fr=u({description:"Resize the browser viewport to the specified dimensions. Use this to test responsive layouts at different screen sizes (e.g. mobile: 375x812, tablet: 768x1024, desktop: 1440x900).",name:"resize_viewport",schema:Nn,execute:async(e,t)=>{let r=performance.now();await e.page.setViewportSize({height:t.height,width:t.width});let o=`agent-step-${String(e.stepIndex)}`,n={height:t.height,id:o,type:"setViewport",width:t.width};return h({assertions:[],detail:`Resized viewport to ${String(t.width)}x${String(t.height)}`,duration:performance.now()-r,nodeType:"setViewport",page:e.page,runStartTime:e.runStartTime,specNode:n,status:"passed",stepIndex:e.stepIndex,title:`Resize viewport to ${String(t.width)}x${String(t.height)}`})}});import{z as mr}from"zod";var En=mr.object({selector:mr.string().describe("CSS selector for the element to right-click")}),hr=u({description:"Right-click an element to open the context menu",name:"right_click",schema:En,execute:async(e,t)=>{let r=performance.now();await e.page.locator(t.selector).click({button:"right",timeout:5e3});let n={id:`agent-step-${String(e.stepIndex)}`,locator:{by:"css",value:t.selector},type:"rightClick"};return h({assertions:[],detail:`Right-clicked ${t.selector}`,duration:performance.now()-r,nodeType:"rightClick",page:e.page,runStartTime:e.runStartTime,specNode:n,status:"passed",stepIndex:e.stepIndex,title:`Right-click ${t.selector}`})}});import{z as Ae}from"zod";var In=Ae.object({selector:Ae.string().optional().describe("CSS selector to scroll within (omit to scroll the page)"),x:Ae.number().int().optional().describe("Horizontal scroll offset in pixels").default(0),y:Ae.number().int().optional().describe("Vertical scroll offset in pixels").default(0)}),gr=u({description:"Scroll the page or a specific element by pixel offset. Positive y scrolls down, negative scrolls up.",name:"scroll",schema:In,execute:async(e,t)=>{let r=performance.now();if(t.selector==null)await e.page.mouse.wheel(t.x,t.y);else{let i=await e.page.locator(t.selector).elementHandle();if(i==null)throw new Error("Scroll target element not found");await i.evaluate(`(el) => el.scrollBy(${String(t.x)}, ${String(t.y)})`)}let n={id:`agent-step-${String(e.stepIndex)}`,type:"scroll",x:t.x,y:t.y},a=t.selector??"page";return h({assertions:[],detail:`Scrolled ${a} by (${String(t.x)}, ${String(t.y)})`,duration:performance.now()-r,nodeType:"scroll",page:e.page,runStartTime:e.runStartTime,specNode:n,status:"passed",stepIndex:e.stepIndex,title:`Scroll ${a}`})}});import{z as $n}from"zod";var jn=$n.object({}),yr=u({description:"Take a screenshot of the current page",name:"screenshot",schema:jn,execute:async(e,t)=>{let r=performance.now(),n={id:`agent-step-${String(e.stepIndex)}`,type:"screenshot"};return h({assertions:[],detail:"Screenshot captured",duration:performance.now()-r,nodeType:"screenshot",page:e.page,runStartTime:e.runStartTime,specNode:n,status:"passed",stepIndex:e.stepIndex,title:"Screenshot"})}});import{z as wr}from"zod";var On=wr.object({pattern:wr.string().describe("Regex pattern to search console log messages")}),br=u({description:"Search console log messages for a regex pattern. Returns matching entries.",name:"search_console",schema:On,execute:(e,t)=>{let r=new RegExp(t.pattern,"i"),o=e.monitor.consoleEntries.filter(a=>r.test(a.text));if(o.length===0)return{specNode:void 0,stepResult:void 0,toolOutput:`No console messages matching: ${t.pattern}`};let n=o.map(a=>Vn(a)).join(`
29
+ `)}`}async function ar(e,t){let r=await e.evaluate(`JSON.stringify(Object.fromEntries(Object.entries(${t})))`);return`## ${t}
30
+ ${String(r)}`}import{z as Ye}from"zod";var Rn=Ye.object({action:Ye.enum(["accept","dismiss"]).describe("Whether to accept or dismiss the dialog"),promptText:Ye.string().optional().describe("Text to enter for prompt dialogs")}),ir=u({description:"Set up a handler for the next browser dialog (alert, confirm, prompt). Call this BEFORE the action that triggers the dialog.",name:"handle_dialog",schema:Rn,execute:(e,t)=>{let r=performance.now();e.page.once("dialog",async a=>{t.action==="accept"?await a.accept(t.promptText??void 0):await a.dismiss()});let o=`agent-step-${String(e.stepIndex)}`,n={action:t.action,id:o,type:"handleDialog"};return h({assertions:[],detail:`Dialog handler set: ${t.action}`,duration:performance.now()-r,nodeType:"handleDialog",page:e.page,runStartTime:e.runStartTime,specNode:n,status:"passed",stepIndex:e.stepIndex,title:`Handle dialog: ${t.action}`})}});import{z as lr}from"zod";var Pn=lr.object({selector:lr.string().describe("CSS selector for the element to hover over")}),cr=u({description:"Hover over an element matching the CSS selector",name:"hover",schema:Pn,execute:async(e,t)=>{let r=performance.now();await e.page.locator(t.selector).hover({timeout:5e3});let n={id:`agent-step-${String(e.stepIndex)}`,locator:{by:"css",value:t.selector},type:"hover"};return h({assertions:[],detail:`Hovered over ${t.selector}`,duration:performance.now()-r,nodeType:"hover",page:e.page,runStartTime:e.runStartTime,specNode:n,status:"passed",stepIndex:e.stepIndex,title:`Hover ${t.selector}`})}});import{z as Qe}from"zod";var Tn=Qe.object({permission:Qe.string().describe("Permission name (e.g. geolocation, notifications, camera, clipboard-read)"),state:Qe.enum(["granted","prompt"]).describe("Permission state to set")}),dr=u({description:"Set a browser permission state (geolocation, notifications, camera, etc.)",name:"set_permission",schema:Tn,execute:async(e,t)=>{let r=performance.now(),o=e.page.context();t.state==="granted"?await o.grantPermissions([t.permission]):await o.clearPermissions();let a={id:`agent-step-${String(e.stepIndex)}`,permission:t.permission,state:t.state,type:"setPermission"};return h({assertions:[],detail:`Set ${t.permission} to ${t.state}`,duration:performance.now()-r,nodeType:"setPermission",page:e.page,runStartTime:e.runStartTime,specNode:a,status:"passed",stepIndex:e.stepIndex,title:`Set permission: ${t.permission} \u2192 ${t.state}`})}});import{z as ur}from"zod";var Cn=ur.object({url:ur.string().describe("The URL to navigate to")}),pr=u({description:"Navigate the browser to a URL",name:"navigate",schema:Cn,execute:async(e,t)=>{let r=performance.now();await e.page.goto(t.url,{waitUntil:"domcontentloaded"});let n={id:`agent-step-${String(e.stepIndex)}`,type:"goto",url:{type:"static",value:t.url}};return h({assertions:[],detail:`Navigated to ${t.url}`,duration:performance.now()-r,nodeType:"goto",page:e.page,runStartTime:e.runStartTime,specNode:n,status:"passed",stepIndex:e.stepIndex,title:`Navigate to ${t.url}`})}});import{z as fr}from"zod";var An=fr.object({key:fr.string().describe("Key to press, e.g. 'Enter', 'Tab', 'Escape'")}),mr=u({description:"Press a keyboard key",name:"press",schema:An,execute:async(e,t)=>{let r=performance.now();await e.page.keyboard.press(t.key);let n={id:`agent-step-${String(e.stepIndex)}`,key:t.key,type:"press"};return h({assertions:[],detail:`Pressed ${t.key}`,duration:performance.now()-r,nodeType:"press",page:e.page,runStartTime:e.runStartTime,specNode:n,status:"passed",stepIndex:e.stepIndex,title:`Press ${t.key}`})}});import{z as Xe}from"zod";var Nn=Xe.object({height:Xe.number().int().positive().describe("Viewport height in pixels"),width:Xe.number().int().positive().describe("Viewport width in pixels")}),hr=u({description:"Resize the browser viewport to the specified dimensions. Use this to test responsive layouts at different screen sizes (e.g. mobile: 375x812, tablet: 768x1024, desktop: 1440x900).",name:"resize_viewport",schema:Nn,execute:async(e,t)=>{let r=performance.now();await e.page.setViewportSize({height:t.height,width:t.width});let o=`agent-step-${String(e.stepIndex)}`,n={height:t.height,id:o,type:"setViewport",width:t.width};return h({assertions:[],detail:`Resized viewport to ${String(t.width)}x${String(t.height)}`,duration:performance.now()-r,nodeType:"setViewport",page:e.page,runStartTime:e.runStartTime,specNode:n,status:"passed",stepIndex:e.stepIndex,title:`Resize viewport to ${String(t.width)}x${String(t.height)}`})}});import{z as gr}from"zod";var En=gr.object({selector:gr.string().describe("CSS selector for the element to right-click")}),yr=u({description:"Right-click an element to open the context menu",name:"right_click",schema:En,execute:async(e,t)=>{let r=performance.now();await e.page.locator(t.selector).click({button:"right",timeout:5e3});let n={id:`agent-step-${String(e.stepIndex)}`,locator:{by:"css",value:t.selector},type:"rightClick"};return h({assertions:[],detail:`Right-clicked ${t.selector}`,duration:performance.now()-r,nodeType:"rightClick",page:e.page,runStartTime:e.runStartTime,specNode:n,status:"passed",stepIndex:e.stepIndex,title:`Right-click ${t.selector}`})}});import{z as Ae}from"zod";var In=Ae.object({selector:Ae.string().optional().describe("CSS selector to scroll within (omit to scroll the page)"),x:Ae.number().int().optional().describe("Horizontal scroll offset in pixels").default(0),y:Ae.number().int().optional().describe("Vertical scroll offset in pixels").default(0)}),wr=u({description:"Scroll the page or a specific element by pixel offset. Positive y scrolls down, negative scrolls up.",name:"scroll",schema:In,execute:async(e,t)=>{let r=performance.now();if(t.selector==null)await e.page.mouse.wheel(t.x,t.y);else{let i=await e.page.locator(t.selector).elementHandle();if(i==null)throw new Error("Scroll target element not found");await i.evaluate(`(el) => el.scrollBy(${String(t.x)}, ${String(t.y)})`)}let n={id:`agent-step-${String(e.stepIndex)}`,type:"scroll",x:t.x,y:t.y},a=t.selector??"page";return h({assertions:[],detail:`Scrolled ${a} by (${String(t.x)}, ${String(t.y)})`,duration:performance.now()-r,nodeType:"scroll",page:e.page,runStartTime:e.runStartTime,specNode:n,status:"passed",stepIndex:e.stepIndex,title:`Scroll ${a}`})}});import{z as $n}from"zod";var jn=$n.object({}),br=u({description:"Take a screenshot of the current page",name:"screenshot",schema:jn,execute:async(e,t)=>{let r=performance.now(),n={id:`agent-step-${String(e.stepIndex)}`,type:"screenshot"};return h({assertions:[],detail:"Screenshot captured",duration:performance.now()-r,nodeType:"screenshot",page:e.page,runStartTime:e.runStartTime,specNode:n,status:"passed",stepIndex:e.stepIndex,title:"Screenshot"})}});import{z as Sr}from"zod";var On=Sr.object({pattern:Sr.string().describe("Regex pattern to search console log messages")}),kr=u({description:"Search console log messages for a regex pattern. Returns matching entries.",name:"search_console",schema:On,execute:(e,t)=>{let r=new RegExp(t.pattern,"i"),o=e.monitor.consoleEntries.filter(a=>r.test(a.text));if(o.length===0)return{specNode:void 0,stepResult:void 0,toolOutput:`No console messages matching: ${t.pattern}`};let n=o.map(a=>Vn(a)).join(`
31
31
  `);return{specNode:void 0,stepResult:void 0,toolOutput:`${String(o.length)} matching console messages:
32
- ${n}`}}});function Vn(e){let t=e.url.length>0?` (${e.url})`:"";return`[${e.level.toUpperCase()}] ${e.text}${t}`}import{z as Sr}from"zod";var Un=Sr.object({pattern:Sr.string().describe("Regex pattern to search across network request URLs and response bodies")}),kr=u({description:"Search network requests and response bodies for a regex pattern. Useful for finding API calls, tokens, or specific data in responses.",name:"search_network",schema:Un,execute:(e,t)=>{let r=new RegExp(t.pattern,"i"),o=e.monitor.networkEntries.filter(a=>r.test(a.url)||a.responseBody!=null&&r.test(a.responseBody));if(o.length===0)return{specNode:void 0,stepResult:void 0,toolOutput:`No network entries matching: ${t.pattern}`};let n=o.map(a=>{let i=a.statusCode==null?"pending":String(a.statusCode),c=r.test(a.url)?" [URL match]":"",l=a.responseBody!=null&&r.test(a.responseBody)?" [body match]":"";return`${a.method} ${i} ${a.url}${c}${l}`}).join(`
32
+ ${n}`}}});function Vn(e){let t=e.url.length>0?` (${e.url})`:"";return`[${e.level.toUpperCase()}] ${e.text}${t}`}import{z as vr}from"zod";var Un=vr.object({pattern:vr.string().describe("Regex pattern to search across network request URLs and response bodies")}),xr=u({description:"Search network requests and response bodies for a regex pattern. Useful for finding API calls, tokens, or specific data in responses.",name:"search_network",schema:Un,execute:(e,t)=>{let r=new RegExp(t.pattern,"i"),o=e.monitor.networkEntries.filter(a=>r.test(a.url)||a.responseBody!=null&&r.test(a.responseBody));if(o.length===0)return{specNode:void 0,stepResult:void 0,toolOutput:`No network entries matching: ${t.pattern}`};let n=o.map(a=>{let i=a.statusCode==null?"pending":String(a.statusCode),c=r.test(a.url)?" [URL match]":"",l=a.responseBody!=null&&r.test(a.responseBody)?" [body match]":"";return`${a.method} ${i} ${a.url}${c}${l}`}).join(`
33
33
  `);return{specNode:void 0,stepResult:void 0,toolOutput:`${String(o.length)} matching network entries:
34
- ${n}`}}});import{z as vr}from"zod";var Fn=20,Wn=2,Ln=vr.object({pattern:vr.string().describe("Regex pattern to search for in the page HTML")}),xr=u({description:"Search the current page's full HTML/DOM for a regex pattern. Returns matching lines with context. Useful for finding elements, attributes, text content, or hidden data without reading the entire page.",name:"search_page",schema:Ln,execute:async(e,t)=>{let o=(await e.page.content()).split(`
34
+ ${n}`}}});import{z as Rr}from"zod";var Fn=20,Wn=2,Ln=Rr.object({pattern:Rr.string().describe("Regex pattern to search for in the page HTML")}),Pr=u({description:"Search the current page's full HTML/DOM for a regex pattern. Returns matching lines with context. Useful for finding elements, attributes, text content, or hidden data without reading the entire page.",name:"search_page",schema:Ln,execute:async(e,t)=>{let o=(await e.page.content()).split(`
35
35
  `),n=_n({contextLines:Wn,lines:o,maxResults:Fn,pattern:t.pattern});return n.length===0?{specNode:void 0,stepResult:void 0,toolOutput:`No matches found for pattern: ${t.pattern}`}:{specNode:void 0,stepResult:void 0,toolOutput:n.join(`
36
36
  ---
37
37
  `)}}});function _n({contextLines:e,lines:t,maxResults:r,pattern:o}){let n=new RegExp(o,"i"),a=[];return t.forEach((i,c)=>{if(a.length>=r||!n.test(i))return;let l=Math.max(0,c-e),p=Math.min(t.length-1,c+e),g=t.slice(l,p+1).map((f,d)=>{let m=l+d+1;return`${l+d===c?">":" "} ${String(m)}: ${f}`}).join(`
38
- `);a.push(g)}),a}import{z as Ze}from"zod";var Dn=Ze.object({selector:Ze.string().describe("CSS selector for the select element"),value:Ze.string().describe("The option value to select")}),Rr=u({description:"Select an option from a dropdown/select element",name:"select",schema:Dn,execute:async(e,t)=>{let r=performance.now();await e.page.locator(t.selector).selectOption(t.value,{timeout:5e3});let n={id:`agent-step-${String(e.stepIndex)}`,locator:{by:"css",value:t.selector},type:"select",value:{type:"static",value:t.value}};return h({assertions:[],detail:`Selected "${t.value}" in ${t.selector}`,duration:performance.now()-r,nodeType:"select",page:e.page,runStartTime:e.runStartTime,specNode:n,status:"passed",stepIndex:e.stepIndex,title:`Select ${t.value}`})}});import{z as Pr}from"zod";var qn=Pr.object({selector:Pr.string().describe("CSS selector for the checkbox to uncheck")}),Tr=u({description:"Uncheck a checkbox matching the CSS selector",name:"uncheck",schema:qn,execute:async(e,t)=>{let r=performance.now();await e.page.locator(t.selector).uncheck({timeout:5e3});let n={id:`agent-step-${String(e.stepIndex)}`,locator:{by:"css",value:t.selector},type:"uncheck"};return h({assertions:[],detail:`Unchecked ${t.selector}`,duration:performance.now()-r,nodeType:"uncheck",page:e.page,runStartTime:e.runStartTime,specNode:n,status:"passed",stepIndex:e.stepIndex,title:`Uncheck ${t.selector}`})}});import{z as Cr}from"zod";var Bn=Cr.object({ms:Cr.number().describe("Milliseconds to wait")}),Ar=u({description:"Wait for a specified duration in milliseconds",name:"wait",schema:Bn,execute:async(e,t)=>{let r=performance.now();await e.page.waitForTimeout(t.ms);let o=`agent-step-${String(e.stepIndex)}`,n={duration:t.ms,id:o,type:"wait"};return h({assertions:[],detail:`Waited ${String(t.ms)}ms`,duration:performance.now()-r,nodeType:"wait",page:e.page,runStartTime:e.runStartTime,specNode:n,status:"passed",stepIndex:e.stepIndex,title:`Wait ${String(t.ms)}ms`})}});import{z as et}from"zod";var Mn=et.object({selector:et.string().describe("CSS selector for the element to wait for"),state:et.enum(["visible","hidden","attached","detached"]).default("visible").describe("The state to wait for (default: visible)")}),Nr=u({description:"Wait for an element matching the CSS selector to reach the specified state. More reliable than a blind wait \u2014 use this to wait for elements to appear, disappear, or attach/detach from the DOM.",name:"wait_for",schema:Mn,execute:async(e,t)=>{let r=performance.now();await e.page.locator(t.selector).waitFor({state:t.state,timeout:15e3});let n={id:`agent-step-${String(e.stepIndex)}`,locator:{by:"css",value:t.selector},state:t.state,type:"waitFor"};return h({assertions:[],detail:`Waited for ${t.selector} to be ${t.state}`,duration:performance.now()-r,nodeType:"waitFor",page:e.page,runStartTime:e.runStartTime,specNode:n,status:"passed",stepIndex:e.stepIndex,title:`Wait for ${t.selector} to be ${t.state}`})}});var tt=[dr,Lt,Gt,pr,Vt,Tr,Rr,ir,Ar,Nr,yr,er,xr,Ft,hr,gr,ar,_t,lr,jt,Et,It,Rt,At,Tt,Nt,Ht,Kt,Qt,tr,Zt,nr,br,kr,fr,Mt,qt],Gu=new Map(tt.map(e=>[e.name,e])),Gn=tt.map(e=>e.anthropicTool),zn=tt.map(e=>`- **${e.name}**: ${e.anthropicTool.description??""}`).join(`
39
- `);import Ku from"fs";import Qu from"path";import Hn from"pino";var L=Hn({transport:{options:{ignore:"pid,hostname"},target:"pino-pretty"}});import{execFile as dp}from"child_process";import{createRequire as pp}from"module";import mp from"fs";import gp from"path";import{chromium as wp}from"playwright";import{mkdir as vp,writeFile as xp}from"fs/promises";import Pp from"path";import{readdir as Fp,rm as Wp,stat as Lp}from"fs/promises";import Dp from"os";import ea from"path";import{graphql as ta}from"gql.tada";import{print as Qn}from"graphql";async function X(e){let t=Qn(e.document),r=JSON.stringify({query:t,variables:e.variables}),o=`${e.config.ripploServerUrl}/graphql`,n;try{n=await fetch(o,{body:r,headers:{Authorization:`Bearer ${e.config.token}`,"Content-Type":"application/json"},method:"POST"})}catch(i){let c=i instanceof Error?i.message:String(i);throw new Error(`Failed to connect to Ripplo server at ${e.config.ripploServerUrl}. Make sure \`npx ripplo\` is running and your internet connection is active. Details: ${c}`)}let a=await n.json();if(!Xn(a))throw new Error("Invalid GraphQL response");if(Zn(a))throw new Error(a.errors.map(i=>i.message).join(", "));if(a.data==null)throw new Error("No data returned from server");return a.data}function Xn(e){return typeof e=="object"&&e!=null&&("data"in e||"errors"in e)}function Zn(e){return Array.isArray(e.errors)&&e.errors.length>0}var zp=ea.join(process.cwd(),".ripplo","debug"),Hp=360*60*1e3,Jp=ta(`
38
+ `);a.push(g)}),a}import{z as Ze}from"zod";var Dn=Ze.object({selector:Ze.string().describe("CSS selector for the select element"),value:Ze.string().describe("The option value to select")}),Tr=u({description:"Select an option from a dropdown/select element",name:"select",schema:Dn,execute:async(e,t)=>{let r=performance.now();await e.page.locator(t.selector).selectOption(t.value,{timeout:5e3});let n={id:`agent-step-${String(e.stepIndex)}`,locator:{by:"css",value:t.selector},type:"select",value:{type:"static",value:t.value}};return h({assertions:[],detail:`Selected "${t.value}" in ${t.selector}`,duration:performance.now()-r,nodeType:"select",page:e.page,runStartTime:e.runStartTime,specNode:n,status:"passed",stepIndex:e.stepIndex,title:`Select ${t.value}`})}});import{z as Cr}from"zod";var qn=Cr.object({selector:Cr.string().describe("CSS selector for the checkbox to uncheck")}),Ar=u({description:"Uncheck a checkbox matching the CSS selector",name:"uncheck",schema:qn,execute:async(e,t)=>{let r=performance.now();await e.page.locator(t.selector).uncheck({timeout:5e3});let n={id:`agent-step-${String(e.stepIndex)}`,locator:{by:"css",value:t.selector},type:"uncheck"};return h({assertions:[],detail:`Unchecked ${t.selector}`,duration:performance.now()-r,nodeType:"uncheck",page:e.page,runStartTime:e.runStartTime,specNode:n,status:"passed",stepIndex:e.stepIndex,title:`Uncheck ${t.selector}`})}});import{z as Nr}from"zod";var Bn=Nr.object({ms:Nr.number().describe("Milliseconds to wait")}),Er=u({description:"Wait for a specified duration in milliseconds",name:"wait",schema:Bn,execute:async(e,t)=>{let r=performance.now();await e.page.waitForTimeout(t.ms);let o=`agent-step-${String(e.stepIndex)}`,n={duration:t.ms,id:o,type:"wait"};return h({assertions:[],detail:`Waited ${String(t.ms)}ms`,duration:performance.now()-r,nodeType:"wait",page:e.page,runStartTime:e.runStartTime,specNode:n,status:"passed",stepIndex:e.stepIndex,title:`Wait ${String(t.ms)}ms`})}});import{z as et}from"zod";var Mn=et.object({selector:et.string().describe("CSS selector for the element to wait for"),state:et.enum(["visible","hidden","attached","detached"]).default("visible").describe("The state to wait for (default: visible)")}),Ir=u({description:"Wait for an element matching the CSS selector to reach the specified state. More reliable than a blind wait \u2014 use this to wait for elements to appear, disappear, or attach/detach from the DOM.",name:"wait_for",schema:Mn,execute:async(e,t)=>{let r=performance.now();await e.page.locator(t.selector).waitFor({state:t.state,timeout:15e3});let n={id:`agent-step-${String(e.stepIndex)}`,locator:{by:"css",value:t.selector},state:t.state,type:"waitFor"};return h({assertions:[],detail:`Waited for ${t.selector} to be ${t.state}`,duration:performance.now()-r,nodeType:"waitFor",page:e.page,runStartTime:e.runStartTime,specNode:n,status:"passed",stepIndex:e.stepIndex,title:`Wait for ${t.selector} to be ${t.state}`})}});var tt=[pr,Dt,Ht,mr,Ft,Ar,Tr,cr,Er,Ir,br,rr,Pr,Lt,yr,wr,ir,qt,dr,Vt,$t,jt,Tt,Et,At,It,Kt,Qt,Zt,or,tr,sr,kr,xr,hr,zt,Mt],Gu=new Map(tt.map(e=>[e.name,e])),Gn=tt.map(e=>e.anthropicTool),zn=tt.map(e=>`- **${e.name}**: ${e.anthropicTool.description??""}`).join(`
39
+ `);import Ku from"fs";import Qu from"path";import Hn from"pino";var L=Hn({transport:{options:{ignore:"pid,hostname"},target:"pino-pretty"}});import{execFile as dp}from"child_process";import{createRequire as pp}from"module";import mp from"fs";import gp from"path";import{chromium as wp}from"playwright";import{mkdir as vp,writeFile as xp}from"fs/promises";import Pp from"path";import{readdir as Bp,rm as Mp,stat as Gp}from"fs/promises";import Hp from"os";import ea from"path";import{graphql as ta}from"gql.tada";import{print as Qn}from"graphql";async function X(e){let t=Qn(e.document),r=JSON.stringify({query:t,variables:e.variables}),o=`${e.config.ripploServerUrl}/graphql`,n;try{n=await fetch(o,{body:r,headers:{Authorization:`Bearer ${e.config.token}`,"Content-Type":"application/json"},method:"POST"})}catch(i){let c=i instanceof Error?i.message:String(i);throw new Error(`Failed to connect to Ripplo server at ${e.config.ripploServerUrl}. Make sure \`npx ripplo\` is running and your internet connection is active. Details: ${c}`)}let a=await n.json();if(!Xn(a))throw new Error("Invalid GraphQL response");if(Zn(a))throw new Error(a.errors.map(i=>i.message).join(", "));if(a.data==null)throw new Error("No data returned from server");return a.data}function Xn(e){return typeof e=="object"&&e!=null&&("data"in e||"errors"in e)}function Zn(e){return Array.isArray(e.errors)&&e.errors.length>0}var Xp=ea.join(process.cwd(),".ripplo","debug"),Zp=360*60*1e3,ef=ta(`
40
40
  mutation RevokeCurrentCliToken {
41
41
  revokeCurrentCliToken
42
42
  }
43
- `);import Vr from"fs";import rt from"path";import{z as J}from"zod";var na=J.object({baseUrl:J.string(),cloudBaseUrl:J.string(),preconditionApiPath:J.string(),projectId:J.string()}),aa=J.object({ripploServerUrl:J.string(),token:J.string(),webhookSecret:J.string()}),sa=na.extend(aa.shape),Fr=rt.join(process.cwd(),".ripplo"),ia=rt.join(Fr,"settings.json"),la=rt.join(Fr,"settings.local.json");function Ur(e){if(!Vr.existsSync(e))return null;let t=Vr.readFileSync(e,"utf8");return JSON.parse(t)}function ot(){let e=Ur(ia),t=Ur(la);if(e==null||t==null)return null;let r=sa.safeParse({...e,...t});return r.success?r.data:null}import{z as s}from"zod";import{z as q}from"zod";import{z as R}from"zod";var da=R.object({by:R.literal("css"),value:R.string().min(1)}),ua=R.object({by:R.literal("testId"),value:R.string().min(1)}),pa=R.object({by:R.literal("role"),exact:R.boolean().optional(),name:R.string().optional(),role:R.string().min(1)}),fa=R.object({by:R.literal("text"),exact:R.boolean().optional(),value:R.string().min(1)}),ma=R.object({by:R.literal("label"),exact:R.boolean().optional(),value:R.string().min(1)}),ha=R.object({by:R.literal("placeholder"),value:R.string().min(1)}),ga=R.object({by:R.literal("altText"),value:R.string().min(1)}),b=R.discriminatedUnion("by",[da,ua,pa,fa,ma,ha,ga]);import{z as Wr}from"zod";var U=Wr.enum(["equals","notEquals","contains","startsWith","endsWith","matches"]),K=Wr.enum(["equals","notEquals","greaterThan","greaterThanOrEqual","lessThan","lessThanOrEqual"]);import{z as j}from"zod";var ya=j.object({type:j.literal("static"),value:j.union([j.string(),j.number(),j.boolean()])}),nt=j.object({name:j.string().min(1),type:j.literal("variable")}),Z=j.discriminatedUnion("type",[ya,nt]),x=j.discriminatedUnion("type",[j.object({type:j.literal("static"),value:j.string()}),nt]),oe=j.discriminatedUnion("type",[j.object({type:j.literal("static"),value:j.number().int().nonnegative()}),nt]);var ne=q.discriminatedUnion("type",[q.object({locator:b,type:q.literal("elementVisible")}),q.object({locator:b,type:q.literal("elementNotVisible")}),q.object({expected:x,operator:U,type:q.literal("urlMatch")}),q.object({expected:x,locator:b,operator:U,type:q.literal("textMatch")}),q.object({expected:Z,operator:K,type:q.literal("variableCompare"),variable:q.string().min(1)})]);import{z as _}from"zod";var ue=_.discriminatedUnion("type",[_.object({default:_.string().optional(),type:_.literal("string")}),_.object({default:_.number().optional(),type:_.literal("number")}),_.object({default:_.boolean().optional(),type:_.literal("boolean")}),_.object({key:_.string().min(1),type:_.literal("env")})]);var wa=s.string().min(1),Y=s.array(wa).min(1),w={comment:s.string().optional(),id:s.string().min(1),label:s.string().optional(),next:s.string().optional(),timeout:s.number().int().positive().optional()},ba=s.object({...w,type:s.literal("goto"),url:x}),Sa=s.object({...w,locator:b,type:s.literal("click")}),ka=s.object({...w,locator:b,type:s.literal("fill"),value:x}),va=s.object({...w,locator:b,type:s.literal("select"),value:x}),xa=s.object({...w,locator:b,type:s.literal("hover")}),Ra=s.object({...w,key:s.string().min(1),locator:b.optional(),type:s.literal("press")}),Pa=s.object({...w,locator:b,type:s.literal("check")}),Ta=s.object({...w,locator:b,type:s.literal("uncheck")}),Ca=s.object({...w,type:s.literal("screenshot")}),Aa=s.object({...w,height:s.number().int().positive(),type:s.literal("setViewport"),width:s.number().int().positive()}),Na=s.object({...w,duration:s.number().int().positive(),type:s.literal("wait")}),Ea=s.object({...w,message:s.string().min(1),type:s.literal("fail")}),Ia=s.object({...w,type:s.literal("setVariable"),value:Z,variable:s.string().min(1)}),$a=s.object({...w,locator:b,type:s.literal("extractText"),variable:s.string().min(1)}),ja=s.object({...w,files:s.array(s.string()).min(1),locator:b,type:s.literal("upload")}),Oa=s.object({...w,locator:b,type:s.literal("dblclick")}),Va=s.object({...w,source:b,target:b,type:s.literal("drag")}),Ua=s.object({...w,locator:b,type:s.literal("scrollIntoView")}),Fa=s.object({...w,locator:b,type:s.literal("type"),value:x}),Wa=s.object({...w,locator:b,type:s.literal("focus")}),La=s.object({...w,locator:b,type:s.literal("clear")}),_a=s.object({...w,locator:b,type:s.literal("rightClick")}),Da=s.object({...w,action:s.enum(["accept","dismiss"]),promptText:s.string().optional(),type:s.literal("handleDialog")}),qa=s.object({...w,locator:b.optional(),type:s.literal("scroll"),x:s.number().int().optional(),y:s.number().int().optional()}),Ba=s.object({...w,action:s.enum(["read","write"]),type:s.literal("clipboard"),value:x.optional(),variable:s.string().min(1).optional()}),Ma=s.object({...w,permission:s.string().min(1),state:s.enum(["granted","prompt"]),type:s.literal("setPermission")}),Ga=s.object({...w,locator:b,state:s.enum(["visible","hidden","attached","detached"]).optional(),type:s.literal("waitFor")}),za=s.object({...w,expected:x,operator:U,type:s.literal("waitForUrl")}),Ha=s.object({...w,type:s.literal("waitForResponse"),urlPattern:x}),Ja=s.object({...w,type:s.literal("waitForRequest"),urlPattern:x}),Ka=s.object({...w,locator:b,type:s.literal("assertVisible")}),Ya=s.object({...w,locator:b,type:s.literal("assertNotVisible")}),Qa=s.object({...w,expected:x,locator:b,operator:U,type:s.literal("assertText")}),Xa=s.object({...w,expected:x,operator:U,type:s.literal("assertUrl")}),Za=s.object({...w,expected:oe,locator:b,operator:K,type:s.literal("assertCount")}),es=s.object({...w,expected:x,locator:b,operator:U,type:s.literal("assertValue")}),ts=s.object({...w,attribute:s.string().min(1),expected:x,locator:b,operator:U,type:s.literal("assertAttribute")}),rs=s.object({...w,locator:b,type:s.literal("assertEnabled")}),os=s.object({...w,locator:b,type:s.literal("assertDisabled")}),ns=s.object({...w,expected:x,operator:U,type:s.literal("assertTitle")}),as=s.object({...w,locator:b,type:s.literal("assertChecked")}),ss=s.object({...w,locator:b,type:s.literal("assertNotChecked")}),is=s.object({...w,locator:b,type:s.literal("assertFocused")}),ls=s.object({...w,httpOnly:s.boolean().optional(),name:x,operator:U.optional(),sameSite:s.enum(["Strict","Lax","None"]).optional(),secure:s.boolean().optional(),type:s.literal("assertCookie"),value:x.optional()}),cs=s.object({...w,bodyContains:x.optional(),headerContains:s.object({name:s.string().min(1),value:x}).optional(),status:s.number().int().positive().optional(),type:s.literal("assertResponse"),urlPattern:x}),ds=s.object({...w,alternate:Y.optional(),condition:ne,consequent:Y,type:s.literal("if")}),us=s.object({...w,body:Y,iteratorVar:s.string().min(1).optional(),times:s.number().int().positive(),type:s.literal("loop")}),ps=s.object({...w,body:Y,collection:s.union([s.array(s.record(s.string(),s.unknown())).min(1),s.object({name:s.string().min(1),type:s.literal("variable")})]),iteratorVar:s.string().min(1),type:s.literal("forEach")}),fs=s.object({...w,branches:s.array(Y).min(2),type:s.literal("parallel")}),ms=s.object({...w,body:Y,catch:Y.optional(),finally:Y.optional(),type:s.literal("try")}).refine(e=>e.catch!=null||e.finally!=null,{message:"try node must have at least one of 'catch' or 'finally'"}),hs=s.object({...w,nodes:Y,type:s.literal("group")}),Se=s.discriminatedUnion("type",[ba,Sa,ka,va,xa,Ra,Pa,Ta,Ga,za,Ha,Ja,Ka,Ya,Qa,Xa,Za,es,ts,rs,os,Ca,Aa,Na,Ea,Ia,$a,ja,Oa,Va,Ua,Fa,Wa,La,_a,Da,qa,Ba,Ma,ns,as,ss,is,ls,cs,ds,us,ps,fs,hs]),Lr=s.union([Se,ms]),ae=s.object({entryNode:s.string().min(1),nodes:s.record(s.string(),Lr),variables:s.record(s.string(),ue).optional(),version:s.literal(2)});function Ne(e){let t=ae.safeParse(e);return t.success?{data:t.data,success:!0}:{errors:t.error.issues.map(r=>({message:r.message,path:r.path.join(".")})),success:!1}}import{graphql as Fe,readFragment as ch}from"gql.tada";import{z as Ee}from"zod";var at=Ee.object({from:Ee.string().min(1).describe("Key of the source state in the states record"),to:Ee.string().min(1).describe("Key of the target state in the states record"),workflow:Ee.string().min(1).describe("Filename (without .json) of the workflow in .ripplo/workflows/ that executes this edge")}).describe("A directed edge between two states, executed by a workflow");import{z as ee}from"zod";import{z as C}from"zod";var st=C.object({precondition:C.string().min(1).describe("Name of the precondition to check or execute, e.g. 'auth:admin'")}).describe("Request payload sent by Ripplo to both the check and execute endpoints"),Ie=C.object({satisfied:C.boolean().describe("Whether the precondition is already satisfied. If true, execution is skipped.")}).describe("Response from POST {preconditionApiUrl}/check"),$e=C.object({data:C.record(C.string(),C.string()).optional().describe("Key-value data returned by the precondition. Values are injected as variables that can be interpolated into route patterns ({{key}}) and workflow spec variable references. For example, a data:project precondition should return { projectId: 'clx...' } so the route /projects/{{projectId}}/settings resolves correctly."),error:C.string().optional().describe("Human-readable error message if success is false"),navigateTo:C.string().optional().describe("URL to navigate the browser to after this precondition executes. Use this to set the starting page for the test \u2014 e.g. an auth token URL or the page the workflow begins on. If multiple preconditions return navigateTo, only the last one is used."),success:C.boolean().describe("Whether the precondition was successfully satisfied")}).describe("Response from PUT {preconditionApiUrl}/execute. Set-Cookie headers are automatically captured and injected into the browser context. The data field provides values for route param interpolation and workflow variables."),it=C.object({preconditions:C.array(C.string().min(1)).describe("Names of all preconditions that were executed during this run")}).describe("Request payload sent after a run completes. Fire-and-forget \u2014 errors are logged but don't fail the run."),lt=C.object({error:C.string().optional().describe("Human-readable error message if success is false"),success:C.boolean().describe("Whether teardown completed successfully")}).describe("Response from PUT {preconditionApiUrl}/teardown"),se=C.object({depends:C.array(C.string().min(1)).optional().describe("Names of other preconditions that must be satisfied first. Resolved via topological sort; cycles are rejected at validation time."),description:C.string().min(1).describe("Human-readable description of what this precondition ensures"),returns:C.array(C.string().min(1)).optional().describe("Keys that the execute response's data field will contain. e.g. ['projectId', 'workflowId']. These are used for route param interpolation ({{projectId}}) and workflow variables. Declared here so generated types are strongly typed per precondition.")}).describe("A named precondition declared at the graph level. States reference these by name.");import{z as je}from"zod";var ct=je.object({preconditions:je.array(je.string().min(1)).describe("Ordered list of precondition names to satisfy before entering this state"),route:je.string().min(1).describe("URL pattern with {{placeholders}} for dynamic segments, e.g. '/projects/{{projectId}}/settings'")}).describe("A distinct application state \u2014 a unique combination of location, auth context, and data conditions");var ke=ee.object({edges:ee.array(at).describe("Directed edges between states, each executed by a workflow"),preconditions:ee.record(ee.string(),se).describe("Named preconditions keyed by name (e.g. 'auth:admin', 'data:three-projects'). States reference these by name."),resetPrecondition:ee.string().optional().describe("Name of a precondition to run before every state setup as a global clean slate"),states:ee.record(ee.string(),ct).describe("States keyed by stable ID (kebab-case)"),version:ee.literal(3).describe("Schema version, always 3")}).describe("Ripplo State Graph v3 \u2014 models application states, edges, and executable preconditions");function dt(e){let t=ke.safeParse(e);return t.success?{data:t.data,success:!0}:{errors:t.error.issues.map(r=>({message:r.message,path:r.path.join(".")})),success:!1}}function Ns(e){let t=e.preconditions,r=new Set,o=new Set,n=[];function a(i){if(o.has(i)){n.push(`Precondition "${i}" has a circular dependency`);return}if(r.has(i))return;r.add(i),o.add(i);let c=t[i];c?.depends!=null&&c.depends.forEach(l=>{a(l)}),o.delete(i)}return Object.keys(t).forEach(i=>{a(i)}),n}function ut(e){let t=[],r=new Set(Object.keys(e.preconditions)),o=new Set(Object.keys(e.states));e.edges.forEach((l,p)=>{o.has(l.from)||t.push({message:`References non-existent source state "${l.from}"`,path:`edges[${String(p)}].from`}),o.has(l.to)||t.push({message:`References non-existent target state "${l.to}"`,path:`edges[${String(p)}].to`})}),Object.entries(e.states).forEach(([l,p])=>{p.preconditions.forEach(g=>{r.has(g)||t.push({message:`References non-existent precondition "${g}"`,path:`states.${l}.preconditions`})})}),Object.entries(e.preconditions).forEach(([l,p])=>{p.depends!=null&&p.depends.forEach(g=>{r.has(g)||t.push({message:`Depends on non-existent precondition "${g}"`,path:`preconditions.${l}.depends`})})}),e.resetPrecondition!=null&&!r.has(e.resetPrecondition)&&t.push({message:`References non-existent precondition "${e.resetPrecondition}"`,path:"resetPrecondition"});let n=new Set(e.edges.flatMap(l=>[l.from,l.to]));Object.keys(e.states).forEach(l=>{n.has(l)||t.push({message:"State is not referenced by any edge (orphan)",path:`states.${l}`})});let a=new Set;Object.values(e.states).forEach(l=>{l.preconditions.forEach(p=>a.add(p))}),Object.values(e.preconditions).forEach(l=>{l.depends!=null&&l.depends.forEach(p=>a.add(p))}),e.resetPrecondition!=null&&a.add(e.resetPrecondition),Object.keys(e.preconditions).forEach(l=>{a.has(l)||t.push({message:"Defined but never used by any state or dependency",path:`preconditions.${l}`})});let i=new Set;return e.edges.forEach((l,p)=>{let g=`${l.from}|${l.to}|${l.workflow}`;i.has(g)&&t.push({message:`Duplicate edge from "${l.from}" to "${l.to}" with workflow "${l.workflow}"`,path:`edges[${String(p)}]`}),i.add(g)}),Ns(e).forEach(l=>t.push({message:l,path:"preconditions"})),t}import{parseSetCookie as Hm}from"set-cookie-parser";import{z as Qr}from"zod";var Ws=_o(Yr(),1);import Dm from"crypto";var Qm=Qr.record(Qr.string(),se);import{graphql as mt}from"gql.tada";var rh=mt(`
43
+ `);import Ur from"fs";import nt from"path";import{z as J}from"zod";var na=J.object({baseUrl:J.string(),cloudBaseUrl:J.string(),preconditionApiPath:J.string(),projectId:J.string()}),aa=J.object({ripploServerUrl:J.string(),token:J.string(),webhookSecret:J.string()}),sa=na.extend(aa.shape),Wr=nt.join(process.cwd(),".ripplo"),ia=nt.join(Wr,"settings.json"),la=nt.join(Wr,"settings.local.json");function Fr(e){if(!Ur.existsSync(e))return null;let t=Ur.readFileSync(e,"utf8");return JSON.parse(t)}function at(){let e=Fr(ia),t=Fr(la);if(e==null||t==null)return null;let r=sa.safeParse({...e,...t});return r.success?r.data:null}import{z as s}from"zod";import{z as q}from"zod";import{z as R}from"zod";var da=R.object({by:R.literal("css"),value:R.string().min(1)}),ua=R.object({by:R.literal("testId"),value:R.string().min(1)}),pa=R.object({by:R.literal("role"),exact:R.boolean().optional(),name:R.string().optional(),role:R.string().min(1)}),fa=R.object({by:R.literal("text"),exact:R.boolean().optional(),value:R.string().min(1)}),ma=R.object({by:R.literal("label"),exact:R.boolean().optional(),value:R.string().min(1)}),ha=R.object({by:R.literal("placeholder"),value:R.string().min(1)}),ga=R.object({by:R.literal("altText"),value:R.string().min(1)}),b=R.discriminatedUnion("by",[da,ua,pa,fa,ma,ha,ga]);import{z as Lr}from"zod";var U=Lr.enum(["equals","notEquals","contains","startsWith","endsWith","matches"]),K=Lr.enum(["equals","notEquals","greaterThan","greaterThanOrEqual","lessThan","lessThanOrEqual"]);import{z as j}from"zod";var ya=j.object({type:j.literal("static"),value:j.union([j.string(),j.number(),j.boolean()])}),st=j.object({name:j.string().min(1),type:j.literal("variable")}),Z=j.discriminatedUnion("type",[ya,st]),x=j.discriminatedUnion("type",[j.object({type:j.literal("static"),value:j.string()}),st]),oe=j.discriminatedUnion("type",[j.object({type:j.literal("static"),value:j.number().int().nonnegative()}),st]);var ne=q.discriminatedUnion("type",[q.object({locator:b,type:q.literal("elementVisible")}),q.object({locator:b,type:q.literal("elementNotVisible")}),q.object({expected:x,operator:U,type:q.literal("urlMatch")}),q.object({expected:x,locator:b,operator:U,type:q.literal("textMatch")}),q.object({expected:Z,operator:K,type:q.literal("variableCompare"),variable:q.string().min(1)})]);import{z as _}from"zod";var ue=_.discriminatedUnion("type",[_.object({default:_.string().optional(),type:_.literal("string")}),_.object({default:_.number().optional(),type:_.literal("number")}),_.object({default:_.boolean().optional(),type:_.literal("boolean")}),_.object({key:_.string().min(1),type:_.literal("env")})]);var wa=s.string().min(1),Y=s.array(wa).min(1),w={comment:s.string().optional(),id:s.string().min(1),label:s.string().optional(),next:s.string().optional(),timeout:s.number().int().positive().optional()},ba=s.object({...w,type:s.literal("goto"),url:x}),Sa=s.object({...w,locator:b,type:s.literal("click")}),ka=s.object({...w,locator:b,type:s.literal("fill"),value:x}),va=s.object({...w,locator:b,type:s.literal("select"),value:x}),xa=s.object({...w,locator:b,type:s.literal("hover")}),Ra=s.object({...w,key:s.string().min(1),locator:b.optional(),type:s.literal("press")}),Pa=s.object({...w,locator:b,type:s.literal("check")}),Ta=s.object({...w,locator:b,type:s.literal("uncheck")}),Ca=s.object({...w,type:s.literal("screenshot")}),Aa=s.object({...w,height:s.number().int().positive(),type:s.literal("setViewport"),width:s.number().int().positive()}),Na=s.object({...w,duration:s.number().int().positive(),type:s.literal("wait")}),Ea=s.object({...w,message:s.string().min(1),type:s.literal("fail")}),Ia=s.object({...w,type:s.literal("setVariable"),value:Z,variable:s.string().min(1)}),$a=s.object({...w,locator:b,type:s.literal("extractText"),variable:s.string().min(1)}),ja=s.object({...w,files:s.array(s.string()).min(1),locator:b,type:s.literal("upload")}),Oa=s.object({...w,locator:b,type:s.literal("dblclick")}),Va=s.object({...w,source:b,target:b,type:s.literal("drag")}),Ua=s.object({...w,locator:b,type:s.literal("scrollIntoView")}),Fa=s.object({...w,locator:b,type:s.literal("type"),value:x}),Wa=s.object({...w,locator:b,type:s.literal("focus")}),La=s.object({...w,locator:b,type:s.literal("clear")}),_a=s.object({...w,locator:b,type:s.literal("rightClick")}),Da=s.object({...w,action:s.enum(["accept","dismiss"]),promptText:s.string().optional(),type:s.literal("handleDialog")}),qa=s.object({...w,locator:b.optional(),type:s.literal("scroll"),x:s.number().int().optional(),y:s.number().int().optional()}),Ba=s.object({...w,action:s.enum(["read","write"]),type:s.literal("clipboard"),value:x.optional(),variable:s.string().min(1).optional()}),Ma=s.object({...w,permission:s.string().min(1),state:s.enum(["granted","prompt"]),type:s.literal("setPermission")}),Ga=s.object({...w,locator:b,state:s.enum(["visible","hidden","attached","detached"]).optional(),type:s.literal("waitFor")}),za=s.object({...w,expected:x,operator:U,type:s.literal("waitForUrl")}),Ha=s.object({...w,type:s.literal("waitForResponse"),urlPattern:x}),Ja=s.object({...w,type:s.literal("waitForRequest"),urlPattern:x}),Ka=s.object({...w,locator:b,type:s.literal("assertVisible")}),Ya=s.object({...w,locator:b,type:s.literal("assertNotVisible")}),Qa=s.object({...w,expected:x,locator:b,operator:U,type:s.literal("assertText")}),Xa=s.object({...w,expected:x,operator:U,type:s.literal("assertUrl")}),Za=s.object({...w,expected:oe,locator:b,operator:K,type:s.literal("assertCount")}),es=s.object({...w,expected:x,locator:b,operator:U,type:s.literal("assertValue")}),ts=s.object({...w,attribute:s.string().min(1),expected:x,locator:b,operator:U,type:s.literal("assertAttribute")}),rs=s.object({...w,locator:b,type:s.literal("assertEnabled")}),os=s.object({...w,locator:b,type:s.literal("assertDisabled")}),ns=s.object({...w,expected:x,operator:U,type:s.literal("assertTitle")}),as=s.object({...w,locator:b,type:s.literal("assertChecked")}),ss=s.object({...w,locator:b,type:s.literal("assertNotChecked")}),is=s.object({...w,locator:b,type:s.literal("assertFocused")}),ls=s.object({...w,httpOnly:s.boolean().optional(),name:x,operator:U.optional(),sameSite:s.enum(["Strict","Lax","None"]).optional(),secure:s.boolean().optional(),type:s.literal("assertCookie"),value:x.optional()}),cs=s.object({...w,bodyContains:x.optional(),headerContains:s.object({name:s.string().min(1),value:x}).optional(),status:s.number().int().positive().optional(),type:s.literal("assertResponse"),urlPattern:x}),ds=s.object({...w,alternate:Y.optional(),condition:ne,consequent:Y,type:s.literal("if")}),us=s.object({...w,body:Y,iteratorVar:s.string().min(1).optional(),times:s.number().int().positive(),type:s.literal("loop")}),ps=s.object({...w,body:Y,collection:s.union([s.array(s.record(s.string(),s.unknown())).min(1),s.object({name:s.string().min(1),type:s.literal("variable")})]),iteratorVar:s.string().min(1),type:s.literal("forEach")}),fs=s.object({...w,branches:s.array(Y).min(2),type:s.literal("parallel")}),ms=s.object({...w,body:Y,catch:Y.optional(),finally:Y.optional(),type:s.literal("try")}).refine(e=>e.catch!=null||e.finally!=null,{message:"try node must have at least one of 'catch' or 'finally'"}),hs=s.object({...w,nodes:Y,type:s.literal("group")}),Se=s.discriminatedUnion("type",[ba,Sa,ka,va,xa,Ra,Pa,Ta,Ga,za,Ha,Ja,Ka,Ya,Qa,Xa,Za,es,ts,rs,os,Ca,Aa,Na,Ea,Ia,$a,ja,Oa,Va,Ua,Fa,Wa,La,_a,Da,qa,Ba,Ma,ns,as,ss,is,ls,cs,ds,us,ps,fs,hs]),_r=s.union([Se,ms]),ae=s.object({entryNode:s.string().min(1),nodes:s.record(s.string(),_r),variables:s.record(s.string(),ue).optional(),version:s.literal(2)});function Ne(e){let t=ae.safeParse(e);return t.success?{data:t.data,success:!0}:{errors:t.error.issues.map(r=>({message:r.message,path:r.path.join(".")})),success:!1}}import{graphql as Fe,readFragment as ph}from"gql.tada";import{z as Ee}from"zod";var it=Ee.object({from:Ee.string().min(1).describe("Key of the source state in the states record"),to:Ee.string().min(1).describe("Key of the target state in the states record"),workflow:Ee.string().min(1).describe("Filename (without .json) of the workflow in .ripplo/workflows/ that executes this edge")}).describe("A directed edge between two states, executed by a workflow");import{z as ee}from"zod";import{z as C}from"zod";var lt=C.object({precondition:C.string().min(1).describe("Name of the precondition to check or execute, e.g. 'auth:admin'")}).describe("Request payload sent by Ripplo to both the check and execute endpoints"),Ie=C.object({satisfied:C.boolean().describe("Whether the precondition is already satisfied. If true, execution is skipped.")}).describe("Response from POST {preconditionApiUrl}/check"),$e=C.object({data:C.record(C.string(),C.string()).optional().describe("Key-value data returned by the precondition. Values are injected as variables that can be interpolated into route patterns ({{key}}) and workflow spec variable references. For example, a data:project precondition should return { projectId: 'clx...' } so the route /projects/{{projectId}}/settings resolves correctly."),error:C.string().optional().describe("Human-readable error message if success is false"),navigateTo:C.string().optional().describe("URL to navigate the browser to after this precondition executes. Use this to set the starting page for the test \u2014 e.g. an auth token URL or the page the workflow begins on. If multiple preconditions return navigateTo, only the last one is used."),success:C.boolean().describe("Whether the precondition was successfully satisfied")}).describe("Response from PUT {preconditionApiUrl}/execute. Set-Cookie headers are automatically captured and injected into the browser context. The data field provides values for route param interpolation and workflow variables."),ct=C.object({preconditions:C.array(C.string().min(1)).describe("Names of all preconditions that were executed during this run")}).describe("Request payload sent after a run completes. Fire-and-forget \u2014 errors are logged but don't fail the run."),dt=C.object({error:C.string().optional().describe("Human-readable error message if success is false"),success:C.boolean().describe("Whether teardown completed successfully")}).describe("Response from PUT {preconditionApiUrl}/teardown"),se=C.object({depends:C.array(C.string().min(1)).optional().describe("Names of other preconditions that must be satisfied first. Resolved via topological sort; cycles are rejected at validation time."),description:C.string().min(1).describe("Human-readable description of what this precondition ensures"),returns:C.array(C.string().min(1)).optional().describe("Keys that the execute response's data field will contain. e.g. ['projectId', 'workflowId']. These are used for route param interpolation ({{projectId}}) and workflow variables. Declared here so generated types are strongly typed per precondition.")}).describe("A named precondition declared at the graph level. States reference these by name.");import{z as je}from"zod";var ut=je.object({preconditions:je.array(je.string().min(1)).describe("Ordered list of precondition names to satisfy before entering this state"),route:je.string().min(1).describe("URL pattern with {{placeholders}} for dynamic segments, e.g. '/projects/{{projectId}}/settings'")}).describe("A distinct application state \u2014 a unique combination of location, auth context, and data conditions");var ke=ee.object({edges:ee.array(it).describe("Directed edges between states, each executed by a workflow"),preconditions:ee.record(ee.string(),se).describe("Named preconditions keyed by name (e.g. 'auth:admin', 'data:three-projects'). States reference these by name."),resetPrecondition:ee.string().optional().describe("Name of a precondition to run before every state setup as a global clean slate"),states:ee.record(ee.string(),ut).describe("States keyed by stable ID (kebab-case)"),version:ee.literal(3).describe("Schema version, always 3")}).describe("Ripplo State Graph v3 \u2014 models application states, edges, and executable preconditions");function pt(e){let t=ke.safeParse(e);return t.success?{data:t.data,success:!0}:{errors:t.error.issues.map(r=>({message:r.message,path:r.path.join(".")})),success:!1}}function Ns(e){let t=e.preconditions,r=new Set,o=new Set,n=[];function a(i){if(o.has(i)){n.push(`Precondition "${i}" has a circular dependency`);return}if(r.has(i))return;r.add(i),o.add(i);let c=t[i];c?.depends!=null&&c.depends.forEach(l=>{a(l)}),o.delete(i)}return Object.keys(t).forEach(i=>{a(i)}),n}function ft(e){let t=[],r=new Set(Object.keys(e.preconditions)),o=new Set(Object.keys(e.states));e.edges.forEach((l,p)=>{o.has(l.from)||t.push({message:`References non-existent source state "${l.from}"`,path:`edges[${String(p)}].from`}),o.has(l.to)||t.push({message:`References non-existent target state "${l.to}"`,path:`edges[${String(p)}].to`})}),Object.entries(e.states).forEach(([l,p])=>{p.preconditions.forEach(g=>{r.has(g)||t.push({message:`References non-existent precondition "${g}"`,path:`states.${l}.preconditions`})})}),Object.entries(e.preconditions).forEach(([l,p])=>{p.depends!=null&&p.depends.forEach(g=>{r.has(g)||t.push({message:`Depends on non-existent precondition "${g}"`,path:`preconditions.${l}.depends`})})}),e.resetPrecondition!=null&&!r.has(e.resetPrecondition)&&t.push({message:`References non-existent precondition "${e.resetPrecondition}"`,path:"resetPrecondition"});let n=new Set(e.edges.flatMap(l=>[l.from,l.to]));Object.keys(e.states).forEach(l=>{n.has(l)||t.push({message:"State is not referenced by any edge (orphan)",path:`states.${l}`})});let a=new Set;Object.values(e.states).forEach(l=>{l.preconditions.forEach(p=>a.add(p))}),Object.values(e.preconditions).forEach(l=>{l.depends!=null&&l.depends.forEach(p=>a.add(p))}),e.resetPrecondition!=null&&a.add(e.resetPrecondition),Object.keys(e.preconditions).forEach(l=>{a.has(l)||t.push({message:"Defined but never used by any state or dependency",path:`preconditions.${l}`})});let i=new Set;return e.edges.forEach((l,p)=>{let g=`${l.from}|${l.to}|${l.workflow}`;i.has(g)&&t.push({message:`Duplicate edge from "${l.from}" to "${l.to}" with workflow "${l.workflow}"`,path:`edges[${String(p)}]`}),i.add(g)}),Ns(e).forEach(l=>t.push({message:l,path:"preconditions"})),t}import{parseSetCookie as Qm}from"set-cookie-parser";import{z as Qr}from"zod";var Ws=_o(Yr(),1);import Gm from"crypto";var th=Qr.record(Qr.string(),se);import{graphql as gt}from"gql.tada";var ah=gt(`
44
44
  mutation StartRunCLI($runId: String!, $platform: String!, $agentProfileId: String) {
45
45
  startRun(runId: $runId, platform: $platform, agentProfileId: $agentProfileId) {
46
46
  id
47
47
  }
48
48
  }
49
- `),oh=mt(`
49
+ `),sh=gt(`
50
50
  mutation SubmitRunStepsCLI($runResultId: String!, $steps: [StepInput!]!) {
51
51
  submitRunSteps(runResultId: $runResultId, steps: $steps)
52
52
  }
53
- `),nh=mt(`
53
+ `),ih=gt(`
54
54
  mutation CompleteRunCLI(
55
55
  $runResultId: String!
56
56
  $status: String!
@@ -99,13 +99,13 @@ ${n}`}}});import{z as vr}from"zod";var Fn=20,Wn=2,Ln=vr.object({pattern:vr.strin
99
99
  ...WorkflowRun
100
100
  }
101
101
  }
102
- `,[zs]),kh=Fe(`
102
+ `,[zs]),Rh=Fe(`
103
103
  query ProjectRun($projectId: String!) {
104
104
  project(id: $projectId) {
105
105
  ...ProjectRun
106
106
  }
107
107
  }
108
- `,[Hs]),vh=Fe(`
108
+ `,[Hs]),Ph=Fe(`
109
109
  query AgentProfileRun($id: String!) {
110
110
  agentProfile(id: $id) {
111
111
  id
@@ -115,7 +115,7 @@ ${n}`}}});import{z as vr}from"zod";var Fn=20,Wn=2,Ln=vr.object({pattern:vr.strin
115
115
  successCriteria
116
116
  }
117
117
  }
118
- `);function B(e){return{content:[{text:e,type:"text"}]}}async function Xr(e){let t=ot();return t==null?B("Not logged in. Run: ripplo login"):e(t)}import{z as F}from"zod";import Js from"fs";import ig from"path";import{z as fe}from"zod";var xe=fe.object({additionalChecks:fe.array(fe.string()).default([]),description:fe.string(),expectedOutcome:fe.string(),name:fe.string(),spec:ae});function We(e){let t=Js.readFileSync(e,"utf8"),r=JSON.parse(t);return xe.parse(r)}var Ks=F.object({additionalProperties:F.unknown().optional(),properties:F.record(F.string(),F.record(F.string(),F.unknown())).optional(),required:F.array(F.string()).optional(),type:F.string().optional()}),Zr=F.object({oneOf:F.array(Ks).optional()});function eo(){let e=ri(),t={category:"Control Flow",fields:"body, catch?, finally?",typeName:"try"},r=[...e.map(a=>oi(a)),t],o=ni(r);return`# Ripplo Workflow Spec v2
118
+ `);function B(e){return{content:[{text:e,type:"text"}]}}async function Xr(e){let t=at();return t==null?B("Not logged in. Run: ripplo login"):e(t)}import{z as F}from"zod";import Js from"fs";import pg from"path";import{z as fe}from"zod";var xe=fe.object({additionalChecks:fe.array(fe.string()).default([]),description:fe.string(),expectedOutcome:fe.string(),name:fe.string(),spec:ae});function We(e){let t=Js.readFileSync(e,"utf8"),r=JSON.parse(t);return xe.parse(r)}var Ks=F.object({additionalProperties:F.unknown().optional(),properties:F.record(F.string(),F.record(F.string(),F.unknown())).optional(),required:F.array(F.string()).optional(),type:F.string().optional()}),Zr=F.object({oneOf:F.array(Ks).optional()});function eo(){let e=ri(),t={category:"Control Flow",fields:"body, catch?, finally?",typeName:"try"},r=[...e.map(a=>oi(a)),t],o=ni(r);return`# Ripplo Workflow Spec v2
119
119
 
120
120
  ## Node Types
121
121
 
@@ -248,7 +248,7 @@ ${l.summary}`).join(`
248
248
 
249
249
  ---
250
250
 
251
- `);return{results:a,summary:c}}async function pi({config:e,slugs:t}){let r=await X({config:e,document:ci,variables:{projectId:e.projectId}});if(r.project==null)throw new Error("Project not found");if(r.project.devSession==null)throw new Error("No active dev session. Start one by running `ripplo` (or `npx ripplo`) in your terminal, then try again.");let o=(r.project.workflows??[]).map(n=>li(so,n));return t.map(n=>{let a=o.find(i=>i.slug===n);if(a==null)throw new Error(`Workflow "${n}" not found on server. Is the dev session running? (It watches .ripplo/ files and syncs them automatically.)`);return{slug:n,workflowId:a.id}})}function fi({config:e,runId:t,slug:r}){return`${e.ripploServerUrl.replace(/:3000\b/,":3001")}/projects/${e.projectId}/workflows/${r}/runs/${t}`}async function mi({config:e,workflowId:t}){let r=await X({config:e,document:di,variables:{concurrency:1,platforms:["chromium"],workflowId:t}});if(r.createRun==null)throw new Error("Failed to create run");return r.createRun.id}var hi=2e3,ao=150;async function gi({config:e,runId:t}){let r=0;for(;r<ao;){await wi(hi),r+=1;let n=(await X({config:e,document:ui,variables:{id:t}})).run;if(n==null)throw new Error(`Run ${t} not found`);if(!(n.status==="pending"||n.status==="running"))return yi({results:n.results??[],runId:t,status:n.status})}return`Run ${t} timed out after polling ${String(ao)} times.`}function yi({results:e,runId:t,status:r}){let o=[`Run ${t}: ${r.toUpperCase()}`];return e.forEach(n=>{o.push(` ${n.status}: ${String(n.passCount)} passed, ${String(n.failCount)} failed (${String(n.duration??0)}ms)`),n.summary!=null&&o.push(` Summary: ${n.summary}`),n.traceEntries.forEach(a=>{let i=a.status==="passed"?Re.tick:Re.cross,c=a.detail==null?"":` \u2014 ${a.detail}`;o.push(` ${i} ${a.title} (${String(a.duration)}ms)${c}`),a.assertions.forEach(l=>{let p=l.status==="passed"?Re.tick:Re.cross,g=l.detail==null?"":` \u2014 ${l.detail}`;o.push(` ${p} ${l.description}${g}`)})})}),o.join(`
251
+ `);return{results:a,summary:c}}async function pi({config:e,slugs:t}){let r=await X({config:e,document:ci,variables:{projectId:e.projectId}});if(r.project==null)throw new Error("Project not found");if(r.project.devSession==null)throw new Error("No active dev session. Start one by running `ripplo` (or `npx ripplo`) in your terminal, then try again.");let o=(r.project.workflows??[]).map(n=>li(so,n));return t.length===0?o.map(n=>({slug:n.slug,workflowId:n.id})):t.map(n=>{let a=o.find(i=>i.slug===n);if(a==null)throw new Error(`Workflow "${n}" not found on server. Is the dev session running? (It watches .ripplo/ files and syncs them automatically.)`);return{slug:n,workflowId:a.id}})}function fi({config:e,runId:t,slug:r}){return`${e.ripploServerUrl.replace(/:3000\b/,":3001")}/projects/${e.projectId}/workflows/${r}/runs/${t}`}async function mi({config:e,workflowId:t}){let r=await X({config:e,document:di,variables:{concurrency:1,platforms:["chromium"],workflowId:t}});if(r.createRun==null)throw new Error("Failed to create run");return r.createRun.id}var hi=2e3,ao=150;async function gi({config:e,runId:t}){let r=0;for(;r<ao;){await wi(hi),r+=1;let n=(await X({config:e,document:ui,variables:{id:t}})).run;if(n==null)throw new Error(`Run ${t} not found`);if(!(n.status==="pending"||n.status==="running"))return yi({results:n.results??[],runId:t,status:n.status})}return`Run ${t} timed out after polling ${String(ao)} times.`}function yi({results:e,runId:t,status:r}){let o=[`Run ${t}: ${r.toUpperCase()}`];return e.forEach(n=>{o.push(` ${n.status}: ${String(n.passCount)} passed, ${String(n.failCount)} failed (${String(n.duration??0)}ms)`),n.summary!=null&&o.push(` Summary: ${n.summary}`),n.traceEntries.forEach(a=>{let i=a.status==="passed"?Re.tick:Re.cross,c=a.detail==null?"":` \u2014 ${a.detail}`;o.push(` ${i} ${a.title} (${String(a.duration)}ms)${c}`),a.assertions.forEach(l=>{let p=l.status==="passed"?Re.tick:Re.cross,g=l.detail==null?"":` \u2014 ${l.detail}`;o.push(` ${p} ${l.description}${g}`)})})}),o.join(`
252
252
  `)}function wi(e){return new Promise(t=>{setTimeout(t,e)})}import{z as bi}from"zod";function lo(){return Si()}function co(){return ki()}function ie(e,t){let r=bi.toJSONSchema(t);return`### ${e}
253
253
 
254
254
  \`\`\`json
@@ -274,7 +274,7 @@ Dependencies (\`depends\`) are resolved via topological sort. Cycles are rejecte
274
274
 
275
275
  Use \`get_precondition_schema\` for full precondition API schemas, setup guide, and framework-specific auth examples.`].join(`
276
276
 
277
- `)}function ki(){return["# Precondition API \u2014 Full Reference\n\nPreconditions are named capabilities declared at the graph level and referenced by name from states.\n\nRipplo does NOT run any business logic. The user's app exposes a Precondition API with three endpoints, and Ripplo sends the precondition name in the request body.\n\n### Execution flow for each precondition:\n1. **Check**: `POST {preconditionApiPath}/check` \u2014 is it already satisfied?\n2. **Execute** (if not satisfied): `PUT {preconditionApiPath}/execute` \u2014 satisfy it\n3. Set-Cookie headers from execute responses are automatically captured and injected into the browser. All cookie attributes (Domain, Path, SameSite, HttpOnly, Secure, Expires, Max-Age) are preserved. Always include `Path=/` and an appropriate `SameSite` value on auth cookies.\n4. If the execute response includes `navigateTo`, the browser navigates to that URL after all preconditions complete (sets the starting page for the test)\n5. **Teardown** (after run): `PUT {preconditionApiPath}/teardown` \u2014 clean up (fire-and-forget)\n\n### Request signing (Standard Webhooks)\n\nAll precondition requests are signed using the [Standard Webhooks](https://github.com/standard-webhooks/standard-webhooks) specification (HMAC-SHA256). Every request includes three headers:\n- `webhook-id` \u2014 unique message ID\n- `webhook-timestamp` \u2014 Unix timestamp (seconds)\n- `webhook-signature` \u2014 `v1,<base64_hmac>`\n\nThe precondition API **must verify these signatures** to ensure requests come from Ripplo. See the setup guide below for verification instructions.\n\nDependencies (`depends`) are resolved via topological sort with deduplication. Cycles are rejected at validation time.",ie("Precondition (graph-level declaration)",se),ie("Precondition Check/Execute Request",st),ie("Check Response",Ie),ie("Execute Response",$e),ie("Teardown Request",it),ie("Teardown Response",lt),xi()].join(`
277
+ `)}function ki(){return["# Precondition API \u2014 Full Reference\n\nPreconditions are named capabilities declared at the graph level and referenced by name from states.\n\nRipplo does NOT run any business logic. The user's app exposes a Precondition API with three endpoints, and Ripplo sends the precondition name in the request body.\n\n### Execution flow for each precondition:\n1. **Check**: `POST {preconditionApiPath}/check` \u2014 is it already satisfied?\n2. **Execute** (if not satisfied): `PUT {preconditionApiPath}/execute` \u2014 satisfy it\n3. Set-Cookie headers from execute responses are automatically captured and injected into the browser. All cookie attributes (Domain, Path, SameSite, HttpOnly, Secure, Expires, Max-Age) are preserved. Always include `Path=/` and an appropriate `SameSite` value on auth cookies.\n4. If the execute response includes `navigateTo`, the browser navigates to that URL after all preconditions complete (sets the starting page for the test)\n5. **Teardown** (after run): `PUT {preconditionApiPath}/teardown` \u2014 clean up (fire-and-forget)\n\n### Request signing (Standard Webhooks)\n\nAll precondition requests are signed using the [Standard Webhooks](https://github.com/standard-webhooks/standard-webhooks) specification (HMAC-SHA256). Every request includes three headers:\n- `webhook-id` \u2014 unique message ID\n- `webhook-timestamp` \u2014 Unix timestamp (seconds)\n- `webhook-signature` \u2014 `v1,<base64_hmac>`\n\nThe precondition API **must verify these signatures** to ensure requests come from Ripplo. See the setup guide below for verification instructions.\n\nDependencies (`depends`) are resolved via topological sort with deduplication. Cycles are rejected at validation time.",ie("Precondition (graph-level declaration)",se),ie("Precondition Check/Execute Request",lt),ie("Check Response",Ie),ie("Execute Response",$e),ie("Teardown Request",ct),ie("Teardown Response",dt),xi()].join(`
278
278
 
279
279
  `)}function vi(){return`### Framework-specific auth examples
280
280
 
@@ -469,7 +469,7 @@ app.put("/api/test/preconditions/teardown", async (req, res) => {
469
469
  }
470
470
  res.json({ success: true });
471
471
  });
472
- \`\`\``}var uo={assertAttribute:"assertion",assertChecked:"assertion",assertCookie:"assertion",assertCount:"assertion",assertDisabled:"assertion",assertEnabled:"assertion",assertFocused:"assertion",assertNotChecked:"assertion",assertNotVisible:"assertion",assertResponse:"assertion",assertText:"assertion",assertTitle:"assertion",assertUrl:"assertion",assertValue:"assertion",assertVisible:"assertion",check:"action",clear:"action",click:"action",clipboard:"other",dblclick:"action",drag:"action",extractText:"other",fail:"other",fill:"action",focus:"action",forEach:"controlFlow",goto:"other",group:"controlFlow",handleDialog:"action",hover:"action",if:"controlFlow",loop:"controlFlow",parallel:"controlFlow",press:"action",rightClick:"action",screenshot:"other",scroll:"action",scrollIntoView:"action",select:"action",setPermission:"other",setVariable:"other",setViewport:"other",try:"controlFlow",type:"action",uncheck:"action",upload:"action",wait:"other",waitFor:"other",waitForRequest:"other",waitForResponse:"other",waitForUrl:"other"},_e=new Set,ht=new Set;Object.entries(uo).forEach(([e,t])=>{t==="assertion"&&_e.add(e),t==="action"&&ht.add(e)});function po(e){let t=We(e),{spec:r}=t,o=Ei(r),n=Ii(r),a=Pi({orderedNodes:o,spec:r,terminalNodes:n}),i=Ui({flags:a,orderedNodes:o,terminalNodes:n,wf:t});return{flags:a,report:i}}function Pi({orderedNodes:e,spec:t,terminalNodes:r}){let o=e.filter(c=>_e.has(c.type)).length,n=e.filter(c=>ht.has(c.type)).length,a=Object.keys(t.nodes).length,i=r.some(c=>_e.has(c.type));return[...Ti(o),...Ci(i,o),...Ai(n),...Ni(o,a)]}function Ti(e){return e===0?["NO_ASSERTIONS: The workflow has zero assertion nodes. It cannot verify its expectedOutcome."]:[]}function Ci(e,t){return!e&&t>0?["NO_TERMINAL_ASSERTION: No terminal node (end of flow) is an assertion. The workflow may not verify the final state."]:[]}function Ai(e){return e===0?["NO_INTERACTIONS: The workflow has zero interaction nodes (click, fill, select, etc.). It does not exercise any user action."]:[]}function Ni(e,t){if(e>0&&t>3){let r=e/t;if(r<.15){let o=String(Math.round(r*100));return[`LOW_ASSERTION_RATIO: Only ${String(e)}/${String(t)} nodes are assertions (${o}%). Consider adding more verification.`]}}return[]}function Ei(e){let t=new Set,r=[];function o(n){if(t.has(n))return;t.add(n);let a=e.nodes[n];a!=null&&(r.push(a),a.next!=null&&o(a.next),fo(a).forEach(i=>{o(i)}))}return o(e.entryNode),r}function fo(e){switch(e.type){case"if":return[...e.consequent,...e.alternate??[]];case"loop":case"forEach":return[...e.body];case"try":return[...e.body,...e.catch??[],...e.finally??[]];case"group":return[...e.nodes];case"parallel":return e.branches.flat();case"assertVisible":case"assertNotVisible":case"assertText":case"assertUrl":case"assertCount":case"assertValue":case"click":case"fill":case"select":case"check":case"uncheck":case"press":case"hover":case"goto":case"waitFor":case"screenshot":case"setViewport":case"wait":case"fail":case"setVariable":case"extractText":case"waitForUrl":case"waitForResponse":case"waitForRequest":case"upload":case"dblclick":case"drag":case"scrollIntoView":case"type":case"focus":case"assertAttribute":case"assertEnabled":case"assertDisabled":case"assertTitle":case"assertChecked":case"assertNotChecked":case"assertFocused":case"assertCookie":case"assertResponse":case"clear":case"rightClick":case"handleDialog":case"scroll":case"clipboard":case"setPermission":return[]}}function Ii(e){return Object.values(e.nodes).filter(t=>t.next!=null?!1:fo(t).length===0)}function P(e){return e.by==="role"?e.name==null?`role=${e.role}`:`role=${e.role}[${e.name}]`:`${e.by}=${e.value}`}function M(e){return e.type==="static"?`"${e.value}"`:`{{${e.name}}}`}function $i(e){return e.type==="static"?String(e.value):`{{${e.name}}}`}var mo=new Set,ho=new Set;Object.entries(uo).forEach(([e,t])=>{t==="controlFlow"&&mo.add(e),t==="assertion"&&ho.add(e)});function go(e){let t=`[${e.type}]`;return mo.has(e.type)?Vi(e,t):ho.has(e.type)?Oi(e,t):ji(e,t)}function ji(e,t){let r=e;switch(r.type){case"goto":return`${t} Navigate to ${M(r.url)}`;case"click":return`${t} click ${P(r.locator)}`;case"hover":return`${t} hover ${P(r.locator)}`;case"check":return`${t} check ${P(r.locator)}`;case"uncheck":return`${t} uncheck ${P(r.locator)}`;case"fill":return`${t} Fill ${P(r.locator)} with ${M(r.value)}`;case"select":return`${t} Select ${M(r.value)} in ${P(r.locator)}`;case"press":return`${t} Press "${r.key}"`;case"waitFor":{let o=r.state==null?"":` to be ${r.state}`;return`${t} Wait for ${P(r.locator)}${o}`}case"waitForUrl":return`${t} Wait for URL ${r.operator} ${M(r.expected)}`;case"waitForResponse":return`${t} Wait for response matching ${M(r.urlPattern)}`;case"waitForRequest":return`${t} Wait for request matching ${M(r.urlPattern)}`;case"extractText":return`${t} Extract text from ${P(r.locator)} into $${r.variable}`;case"screenshot":case"clipboard":return`${t} ${r.type}`;case"setViewport":return`${t} setViewport ${String(r.width)}x${String(r.height)}`;case"wait":return`${t} wait ${String(r.duration)}ms`;case"fail":return`${t} fail: ${r.message}`;case"setVariable":return`${t} setVariable ${r.variable}`;case"upload":return`${t} Upload files to ${P(r.locator)}`;case"dblclick":return`${t} Double-click ${P(r.locator)}`;case"drag":return`${t} Drag ${P(r.source)} to ${P(r.target)}`;case"scrollIntoView":return`${t} Scroll into view ${P(r.locator)}`;case"type":return`${t} Type ${M(r.value)} into ${P(r.locator)}`;case"focus":return`${t} Focus ${P(r.locator)}`;case"clear":return`${t} Clear ${P(r.locator)}`;case"rightClick":return`${t} Right-click ${P(r.locator)}`;case"handleDialog":return`${t} Handle dialog: ${r.action}`;case"scroll":return r.locator==null?`${t} Scroll page`:`${t} Scroll ${P(r.locator)}`;case"setPermission":return`${t} Set permission ${r.permission} to ${r.state}`}}function Oi(e,t){let r=e;switch(r.type){case"assertVisible":return`${t} Assert visible: ${P(r.locator)}`;case"assertNotVisible":return`${t} Assert not visible: ${P(r.locator)}`;case"assertText":return`${t} Assert text of ${P(r.locator)} ${r.operator} ${M(r.expected)}`;case"assertUrl":return`${t} Assert URL ${r.operator} ${M(r.expected)}`;case"assertCount":return`${t} Assert count of ${P(r.locator)} ${r.operator} ${$i(r.expected)}`;case"assertValue":return`${t} Assert value of ${P(r.locator)} ${r.operator} ${M(r.expected)}`;case"assertAttribute":return`${t} Assert attribute "${r.attribute}" of ${P(r.locator)} ${r.operator} ${M(r.expected)}`;case"assertEnabled":return`${t} Assert enabled: ${P(r.locator)}`;case"assertDisabled":return`${t} Assert disabled: ${P(r.locator)}`}}function Vi(e,t){let r=e;switch(r.type){case"if":return`${t} if (condition)`;case"loop":return`${t} loop ${String(r.times)} times`;case"forEach":return`${t} forEach ${r.iteratorVar}`;case"parallel":return`${t} parallel (${String(r.branches.length)} branches)`;case"group":return`${t} group (${String(r.nodes.length)} nodes)`;case"try":return`${t} try/catch`}}function Ui({flags:e,orderedNodes:t,terminalNodes:r,wf:o}){let n=t.filter(c=>_e.has(c.type)).length,a=t.filter(c=>ht.has(c.type)).length;return[Fi(o),Wi(t),Li(r),_i({actionCount:a,assertionCount:n,orderedNodes:t,terminalNodes:r}),...Di(e),qi].join(`
472
+ \`\`\``}var uo={assertAttribute:"assertion",assertChecked:"assertion",assertCookie:"assertion",assertCount:"assertion",assertDisabled:"assertion",assertEnabled:"assertion",assertFocused:"assertion",assertNotChecked:"assertion",assertNotVisible:"assertion",assertResponse:"assertion",assertText:"assertion",assertTitle:"assertion",assertUrl:"assertion",assertValue:"assertion",assertVisible:"assertion",check:"action",clear:"action",click:"action",clipboard:"other",dblclick:"action",drag:"action",extractText:"other",fail:"other",fill:"action",focus:"action",forEach:"controlFlow",goto:"other",group:"controlFlow",handleDialog:"action",hover:"action",if:"controlFlow",loop:"controlFlow",parallel:"controlFlow",press:"action",rightClick:"action",screenshot:"other",scroll:"action",scrollIntoView:"action",select:"action",setPermission:"other",setVariable:"other",setViewport:"other",try:"controlFlow",type:"action",uncheck:"action",upload:"action",wait:"other",waitFor:"other",waitForRequest:"other",waitForResponse:"other",waitForUrl:"other"},_e=new Set,yt=new Set;Object.entries(uo).forEach(([e,t])=>{t==="assertion"&&_e.add(e),t==="action"&&yt.add(e)});function po(e){let t=We(e),{spec:r}=t,o=Ei(r),n=Ii(r),a=Pi({orderedNodes:o,spec:r,terminalNodes:n}),i=Ui({flags:a,orderedNodes:o,terminalNodes:n,wf:t});return{flags:a,report:i}}function Pi({orderedNodes:e,spec:t,terminalNodes:r}){let o=e.filter(c=>_e.has(c.type)).length,n=e.filter(c=>yt.has(c.type)).length,a=Object.keys(t.nodes).length,i=r.some(c=>_e.has(c.type));return[...Ti(o),...Ci(i,o),...Ai(n),...Ni(o,a)]}function Ti(e){return e===0?["NO_ASSERTIONS: The workflow has zero assertion nodes. It cannot verify its expectedOutcome."]:[]}function Ci(e,t){return!e&&t>0?["NO_TERMINAL_ASSERTION: No terminal node (end of flow) is an assertion. The workflow may not verify the final state."]:[]}function Ai(e){return e===0?["NO_INTERACTIONS: The workflow has zero interaction nodes (click, fill, select, etc.). It does not exercise any user action."]:[]}function Ni(e,t){if(e>0&&t>3){let r=e/t;if(r<.15){let o=String(Math.round(r*100));return[`LOW_ASSERTION_RATIO: Only ${String(e)}/${String(t)} nodes are assertions (${o}%). Consider adding more verification.`]}}return[]}function Ei(e){let t=new Set,r=[];function o(n){if(t.has(n))return;t.add(n);let a=e.nodes[n];a!=null&&(r.push(a),a.next!=null&&o(a.next),fo(a).forEach(i=>{o(i)}))}return o(e.entryNode),r}function fo(e){switch(e.type){case"if":return[...e.consequent,...e.alternate??[]];case"loop":case"forEach":return[...e.body];case"try":return[...e.body,...e.catch??[],...e.finally??[]];case"group":return[...e.nodes];case"parallel":return e.branches.flat();case"assertVisible":case"assertNotVisible":case"assertText":case"assertUrl":case"assertCount":case"assertValue":case"click":case"fill":case"select":case"check":case"uncheck":case"press":case"hover":case"goto":case"waitFor":case"screenshot":case"setViewport":case"wait":case"fail":case"setVariable":case"extractText":case"waitForUrl":case"waitForResponse":case"waitForRequest":case"upload":case"dblclick":case"drag":case"scrollIntoView":case"type":case"focus":case"assertAttribute":case"assertEnabled":case"assertDisabled":case"assertTitle":case"assertChecked":case"assertNotChecked":case"assertFocused":case"assertCookie":case"assertResponse":case"clear":case"rightClick":case"handleDialog":case"scroll":case"clipboard":case"setPermission":return[]}}function Ii(e){return Object.values(e.nodes).filter(t=>t.next!=null?!1:fo(t).length===0)}function P(e){return e.by==="role"?e.name==null?`role=${e.role}`:`role=${e.role}[${e.name}]`:`${e.by}=${e.value}`}function M(e){return e.type==="static"?`"${e.value}"`:`{{${e.name}}}`}function $i(e){return e.type==="static"?String(e.value):`{{${e.name}}}`}var mo=new Set,ho=new Set;Object.entries(uo).forEach(([e,t])=>{t==="controlFlow"&&mo.add(e),t==="assertion"&&ho.add(e)});function go(e){let t=`[${e.type}]`;return mo.has(e.type)?Vi(e,t):ho.has(e.type)?Oi(e,t):ji(e,t)}function ji(e,t){let r=e;switch(r.type){case"goto":return`${t} Navigate to ${M(r.url)}`;case"click":return`${t} click ${P(r.locator)}`;case"hover":return`${t} hover ${P(r.locator)}`;case"check":return`${t} check ${P(r.locator)}`;case"uncheck":return`${t} uncheck ${P(r.locator)}`;case"fill":return`${t} Fill ${P(r.locator)} with ${M(r.value)}`;case"select":return`${t} Select ${M(r.value)} in ${P(r.locator)}`;case"press":return`${t} Press "${r.key}"`;case"waitFor":{let o=r.state==null?"":` to be ${r.state}`;return`${t} Wait for ${P(r.locator)}${o}`}case"waitForUrl":return`${t} Wait for URL ${r.operator} ${M(r.expected)}`;case"waitForResponse":return`${t} Wait for response matching ${M(r.urlPattern)}`;case"waitForRequest":return`${t} Wait for request matching ${M(r.urlPattern)}`;case"extractText":return`${t} Extract text from ${P(r.locator)} into $${r.variable}`;case"screenshot":case"clipboard":return`${t} ${r.type}`;case"setViewport":return`${t} setViewport ${String(r.width)}x${String(r.height)}`;case"wait":return`${t} wait ${String(r.duration)}ms`;case"fail":return`${t} fail: ${r.message}`;case"setVariable":return`${t} setVariable ${r.variable}`;case"upload":return`${t} Upload files to ${P(r.locator)}`;case"dblclick":return`${t} Double-click ${P(r.locator)}`;case"drag":return`${t} Drag ${P(r.source)} to ${P(r.target)}`;case"scrollIntoView":return`${t} Scroll into view ${P(r.locator)}`;case"type":return`${t} Type ${M(r.value)} into ${P(r.locator)}`;case"focus":return`${t} Focus ${P(r.locator)}`;case"clear":return`${t} Clear ${P(r.locator)}`;case"rightClick":return`${t} Right-click ${P(r.locator)}`;case"handleDialog":return`${t} Handle dialog: ${r.action}`;case"scroll":return r.locator==null?`${t} Scroll page`:`${t} Scroll ${P(r.locator)}`;case"setPermission":return`${t} Set permission ${r.permission} to ${r.state}`}}function Oi(e,t){let r=e;switch(r.type){case"assertVisible":return`${t} Assert visible: ${P(r.locator)}`;case"assertNotVisible":return`${t} Assert not visible: ${P(r.locator)}`;case"assertText":return`${t} Assert text of ${P(r.locator)} ${r.operator} ${M(r.expected)}`;case"assertUrl":return`${t} Assert URL ${r.operator} ${M(r.expected)}`;case"assertCount":return`${t} Assert count of ${P(r.locator)} ${r.operator} ${$i(r.expected)}`;case"assertValue":return`${t} Assert value of ${P(r.locator)} ${r.operator} ${M(r.expected)}`;case"assertAttribute":return`${t} Assert attribute "${r.attribute}" of ${P(r.locator)} ${r.operator} ${M(r.expected)}`;case"assertEnabled":return`${t} Assert enabled: ${P(r.locator)}`;case"assertDisabled":return`${t} Assert disabled: ${P(r.locator)}`}}function Vi(e,t){let r=e;switch(r.type){case"if":return`${t} if (condition)`;case"loop":return`${t} loop ${String(r.times)} times`;case"forEach":return`${t} forEach ${r.iteratorVar}`;case"parallel":return`${t} parallel (${String(r.branches.length)} branches)`;case"group":return`${t} group (${String(r.nodes.length)} nodes)`;case"try":return`${t} try/catch`}}function Ui({flags:e,orderedNodes:t,terminalNodes:r,wf:o}){let n=t.filter(c=>_e.has(c.type)).length,a=t.filter(c=>yt.has(c.type)).length;return[Fi(o),Wi(t),Li(r),_i({actionCount:a,assertionCount:n,orderedNodes:t,terminalNodes:r}),...Di(e),qi].join(`
473
473
  `)}function Fi(e){let t=["## Declared Intent",`**Name:** ${e.name}`,`**Description:** ${e.description}`,`**Expected Outcome:** ${e.expectedOutcome}`];if(e.additionalChecks.length>0){let r=e.additionalChecks.map(o=>`- ${o}`).join(`
474
474
  `);t.push(`**Additional Checks:**
475
475
  ${r}`)}return t.join(`
@@ -478,7 +478,7 @@ ${r}`)}return t.join(`
478
478
  `)}function _i({actionCount:e,assertionCount:t,orderedNodes:r,terminalNodes:o}){return["","## Statistics",`- Total nodes: ${String(r.length)}`,`- Action nodes: ${String(e)}`,`- Assertion nodes: ${String(t)}`,`- Terminal nodes: ${String(o.length)}`].join(`
479
479
  `)}function Di(e){return e.length===0?[]:[["","## Flags",...e.map(r=>`- ${r}`)].join(`
480
480
  `)]}var qi=["","## Review Instructions","Compare the **Declared Intent** against the **Workflow Steps** and answer these questions:","","1. Does the workflow actually test the **Expected Outcome**? Look at the assertions \u2014 do they verify that the expected outcome was achieved, or do they only check intermediate states?","2. Does the workflow exercise the flow described in **Description**? Are the right forms filled, buttons clicked, and pages navigated?","3. Are there assertions after the key state-changing action (e.g., after form submission, after deletion, after creation)? The final result should be verified, not just that a button was clicked.","4. If there are **Additional Checks**, does the workflow have assertions that cover them?","5. Could this workflow pass even if the feature is broken? For example, if it only asserts a page loaded but never checks the actual outcome.","","If any answer is NO, revise the workflow to address the gap. Then call `validate` and `review_workflow` again."].join(`
481
- `);import re from"fs";import le from"path";import{chromium as Yi}from"playwright";import De from"fs";import yo from"path";import{z as Q}from"zod";var Bi=Q.object({baseUrl:Q.string().optional(),cloudBaseUrl:Q.string(),preconditionApiPath:Q.string().optional(),projectId:Q.string()});function gt(e){let t=yo.join(e,".ripplo","settings.json");if(!De.existsSync(t))return{baseUrl:void 0,cloudBaseUrl:void 0,errors:[{message:".ripplo/settings.json not found",path:""}],preconditionApiPath:void 0,valid:!1,warnings:[]};let r=Gi(t);if(!r.success)return{baseUrl:void 0,cloudBaseUrl:void 0,errors:[{message:r.error,path:"settings.json"}],preconditionApiPath:void 0,valid:!1,warnings:[]};let o=Bi.safeParse(r.data);if(!o.success)return{baseUrl:void 0,cloudBaseUrl:void 0,errors:o.error.issues.map(c=>({message:c.message,path:c.path.join(".")})),preconditionApiPath:void 0,valid:!1,warnings:[]};let n=[],a=yo.join(e,".ripplo","graph.json");return De.existsSync(a)&&Mi(a)&&(o.data.preconditionApiPath==null||o.data.preconditionApiPath.length===0)&&n.push("Graph has preconditions but preconditionApiPath is not set in settings.json. Preconditions will not be executed. Set preconditionApiPath to the URL where your app serves precondition endpoints (can be a relative path appended to baseUrl, or an absolute URL)."),{baseUrl:o.data.baseUrl,cloudBaseUrl:o.data.cloudBaseUrl,errors:[],preconditionApiPath:o.data.preconditionApiPath,valid:!0,warnings:n}}async function qe(e){try{await fetch(e,{method:"HEAD",signal:AbortSignal.timeout(5e3)});return}catch{return`baseUrl (${e}) is not responding. Make sure your dev server is running on that port.`}}function Mi(e){return yt(e)>0}function yt(e){try{let t=De.readFileSync(e,"utf8"),r=JSON.parse(t),n=Q.object({preconditions:Q.record(Q.string(),Q.unknown()).optional()}).safeParse(r);return n.success?Object.keys(n.data.preconditions??{}).length:0}catch{return 0}}async function wo({baseUrl:e,preconditionApiPath:t}){let r=t.startsWith("http://")||t.startsWith("https://")?t:`${e}${t}`;try{return(await fetch(`${r}/check`,{body:JSON.stringify({precondition:"__ripplo_health_check"}),headers:{"Content-Type":"application/json"},method:"POST",signal:AbortSignal.timeout(5e3)})).ok?"Precondition endpoint accepted an unsigned request \u2014 webhook signature verification may be missing or misconfigured. All precondition endpoints must verify the webhook-id, webhook-signature, and webhook-timestamp headers using the Standard Webhooks spec. See get_precondition_schema for setup instructions.":void 0}catch{return}}function Gi(e){try{let t=De.readFileSync(e,"utf8");return{data:JSON.parse(t),success:!0}}catch(t){return{error:t instanceof Error?t.message:"Failed to read file",success:!1}}}import bt from"fs";import St from"path";import zi from"fs";import $g from"path";import{z as V}from"zod";var wt=V.object({edges:V.array(V.object({from:V.string(),to:V.string(),workflow:V.string()})),preconditions:V.record(V.string(),V.unknown()).optional(),resetPrecondition:V.string().optional(),states:V.record(V.string(),V.object({preconditions:V.array(V.string()),route:V.string()})),version:V.literal(3)});function bo(e){let t=zi.readFileSync(e,"utf8"),r=JSON.parse(t);return wt.parse(r)}function So(e){return{edges:e.edges,preconditions:e.preconditions??{},resetPrecondition:e.resetPrecondition,states:e.states,version:e.version}}function ko(e){let t=Hi(e);if(!t.success)return{errors:[{message:t.error,path:""}],valid:!1};let r=wt.safeParse(t.data);if(!r.success)return{errors:r.error.issues.map(g=>({message:g.message,path:g.path.join(".")})),valid:!1};let o=So(r.data),n=dt(o);if(!n.success)return{errors:n.errors,valid:!1};let a=[...ut(n.data)],i=St.join(St.dirname(e),"workflows"),c=bt.existsSync(i),l=new Set(n.data.edges.map(p=>p.workflow));return n.data.edges.forEach((p,g)=>{if(!c){a.push({message:`References workflow "${p.workflow}" but workflows directory does not exist`,path:`edges[${String(g)}].workflow`});return}let f=St.join(i,`${p.workflow}.json`);bt.existsSync(f)||a.push({message:`References workflow "${p.workflow}" but no file exists at workflows/${p.workflow}.json`,path:`edges[${String(g)}].workflow`})}),c&&bt.readdirSync(i).filter(p=>p.endsWith(".json")).forEach(p=>{let g=p.replace(/\.json$/,"");l.has(g)||a.push({message:`Workflow file "workflows/${p}" is not referenced by any edge`,path:`workflows/${p}`})}),{errors:a,valid:a.length===0}}function Hi(e){try{return{data:bo(e),success:!0}}catch(t){return{error:t instanceof Error?t.message:"Failed to read file",success:!1}}}function xo(e){let t=Ji(e);if(!t.success)return{errors:[{message:t.error,path:""}],valid:!1};let r=xe.safeParse(t.data);if(!r.success)return{errors:r.error.issues.map(i=>({message:i.message,path:i.path.join(".")})),valid:!1};let o=Ne(r.data.spec);if(!o.success)return{errors:o.errors,valid:!1};let n=Ki(o.data);return n.length>0?{errors:n,valid:!1}:{errors:[],valid:!0}}function Ji(e){try{return{data:We(e),success:!0}}catch(t){return{error:t instanceof Error?t.message:"Failed to read file",success:!1}}}function Ki(e){let t=new Set(Object.keys(e.nodes)),r=[];t.has(e.entryNode)||r.push({message:`entryNode "${e.entryNode}" does not exist in nodes`,path:"entryNode"}),Object.entries(e.nodes).forEach(([i,c])=>{vo(c).forEach(l=>{t.has(l.id)||r.push({message:`references non-existent node "${l.id}"`,path:`nodes.${i}.${l.field}`})})});let o=new Set,n=t.has(e.entryNode)?[e.entryNode]:[],a=0;for(;a<n.length;){let i=n[a];if(a+=1,i==null||o.has(i))continue;o.add(i);let c=e.nodes[i];c!=null&&vo(c).forEach(l=>{o.has(l.id)||n.push(l.id)})}return t.forEach(i=>{o.has(i)||r.push({message:"unreachable from entryNode",path:`nodes.${i}`})}),r}function vo(e){let t=[];typeof e.next=="string"&&t.push({field:"next",id:e.next}),["consequent","alternate","body","catch","finally","nodes"].forEach(n=>{let a=e[n];Array.isArray(a)&&a.forEach((i,c)=>{typeof i=="string"&&t.push({field:`${n}[${String(c)}]`,id:i})})});let o=e.branches;return Array.isArray(o)&&o.forEach((n,a)=>{Array.isArray(n)&&n.forEach((i,c)=>{typeof i=="string"&&t.push({field:`branches[${String(a)}][${String(c)}]`,id:i})})}),t}async function Ro(e){let t=Qi(e),r=Xi(e),o=Zi(e),n=el(),a=tl(e),i=rl(e),c=await ol(e,t);return[t,r,o,...c,a,i,n]}function Qi(e){let t=gt(e);return t.valid?{missingFields:[],type:"settings",valid:!0}:{missingFields:t.errors.map(o=>o.path).filter(o=>o.length>0),type:"settings",valid:!1}}function Xi(e){let t=le.join(e,".ripplo","settings.local.json");if(!re.existsSync(t))return{exists:!1,hasToken:!1,hasWebhookSecret:!1,type:"local-settings"};try{let r=re.readFileSync(t,"utf8"),o=JSON.parse(r);if(o==null||typeof o!="object")return{exists:!0,hasToken:!1,hasWebhookSecret:!1,type:"local-settings"};let n=Object.fromEntries(Object.entries(o)),a=typeof n.token=="string"&&n.token.length>0,i=typeof n.webhookSecret=="string"&&n.webhookSecret.length>0;return{exists:!0,hasToken:a,hasWebhookSecret:i,type:"local-settings"}}catch{return{exists:!0,hasToken:!1,hasWebhookSecret:!1,type:"local-settings"}}}function Zi(e){let t=le.join(e,".mcp.json");if(!re.existsSync(t))return{installed:!1,type:"mcp-installed"};try{let r=re.readFileSync(t,"utf8"),o=JSON.parse(r);if(o==null||typeof o!="object")return{installed:!1,type:"mcp-installed"};let a=Object.fromEntries(Object.entries(o)).mcpServers;return a==null||typeof a!="object"?{installed:!1,type:"mcp-installed"}:{installed:"ripplo"in a,type:"mcp-installed"}}catch{return{installed:!1,type:"mcp-installed"}}}function el(){let e=Yi.executablePath();return{installed:re.existsSync(e),type:"browser"}}function tl(e){let t=le.join(e,".ripplo","graph.json");if(!re.existsSync(t))return{errorCount:0,errors:[],found:!1,type:"state-graph",valid:!1};let r=ko(t);return{errorCount:r.errors.length,errors:r.errors,found:!0,type:"state-graph",valid:r.valid}}function rl(e){let t=le.join(e,".ripplo","workflows");if(!re.existsSync(t))return{invalidNames:[],invalidWorkflows:[],total:0,type:"workflows",valid:0};let r=re.readdirSync(t).filter(a=>a.endsWith(".json")).map(a=>le.join(t,a)),o=[],n=[];return r.forEach(a=>{let i=xo(a);if(!i.valid){let c=le.basename(a,".json");o.push(c),n.push({errors:i.errors,name:c})}}),{invalidNames:o,invalidWorkflows:n,total:r.length,type:"workflows",valid:r.length-o.length}}async function ol(e,t){if(!t.valid)return[];let r=gt(e),o=[];if(r.baseUrl!=null){let a=await qe(r.baseUrl)==null;o.push({baseUrl:r.baseUrl,reachable:a,type:"dev-server"})}if(r.cloudBaseUrl!=null){let a=await qe(r.cloudBaseUrl)==null;o.push({reachable:a,type:"cloud-base-url",url:r.cloudBaseUrl})}let n=await nl(r);return o.push(...n),o}async function nl(e){let t=le.join(process.cwd(),".ripplo","graph.json"),r=yt(t),o=e.preconditionApiPath!=null&&e.preconditionApiPath.length>0,n={apiPathConfigured:o,count:r,endpointReachable:void 0,type:"preconditions"};if(r===0||!o)return[n];let a=al(e.baseUrl,e.preconditionApiPath);if(a==null)return[n];let i=await qe(a)==null,c=[];if(c.push({apiPathConfigured:!0,count:r,endpointReachable:i,type:"preconditions"}),Po(e.preconditionApiPath)&&c.push({reachable:i,type:"precondition-endpoint",url:a}),!i||e.baseUrl==null)return c;let l=await wo({baseUrl:e.baseUrl,preconditionApiPath:e.preconditionApiPath});return c.push({rejectsUnsigned:l==null,type:"webhook-verification"}),c}function Po(e){return e.startsWith("http://")||e.startsWith("https://")}function al(e,t){if(Po(t))return t;if(e!=null)return`${e}${t}`}function To(e){switch(e.type){case"settings":return sl(e);case"local-settings":return dl(e);case"dev-server":return il(e);case"preconditions":return ul(e);case"webhook-verification":return ll(e);case"state-graph":return pl(e);case"workflows":return fl(e);case"browser":return cl(e);case"cloud-base-url":return e.reachable?`\u2713 Cloud: ${e.url} is reachable`:`! Cloud: ${e.url} is not reachable (ok if not deployed yet)`;case"precondition-endpoint":return e.reachable?`\u2713 Precondition endpoint: ${e.url} is reachable`:`\u2717 Precondition endpoint: ${e.url} is not reachable`;case"mcp-installed":return e.installed?"\u2713 MCP: Ripplo MCP server configured in .mcp.json":"\u2717 MCP: Ripplo MCP server not found in .mcp.json. Run: ripplo setup-mcp"}}function sl(e){return e.valid?"\u2713 Settings: Project configured":`\u2717 Settings: Missing fields: ${e.missingFields.join(", ")}`}function il(e){return e.reachable?`\u2713 Dev server: ${e.baseUrl} is reachable`:`\u2717 Dev server: ${e.baseUrl} is not responding. Make sure your dev server is running.`}function ll(e){return e.rejectsUnsigned?"\u2713 Webhook verification: Unsigned requests are correctly rejected":"\u2717 Webhook verification: Endpoint accepted an unsigned request \u2014 webhook signature verification may be missing or misconfigured. See get_precondition_schema for setup instructions."}function cl(e){return e.installed?"\u2713 Browser: Chromium installed":"\u2717 Browser: Chromium not installed. Run: npx playwright install chromium"}function dl(e){if(!e.exists)return"\u2717 Auth: settings.local.json not found. Run `ripplo login` to authenticate.";let t=[];return e.hasToken||t.push("token"),e.hasWebhookSecret||t.push("webhookSecret"),t.length===0?"\u2713 Auth: Token and webhook secret configured":`\u2717 Auth: Missing ${t.join(" and ")} in settings.local.json`}function ul(e){return e.count===0?"! Preconditions: None defined in graph":e.apiPathConfigured?e.endpointReachable===void 0?`! Preconditions: ${String(e.count)} defined (could not verify endpoint)`:`\u2713 Preconditions: ${String(e.count)} defined, endpoint configured`:`\u2717 Preconditions: ${String(e.count)} defined but preconditionApiPath is not set in settings.json`}function pl(e){if(!e.found)return"\u2717 State graph: graph.json not found";if(e.valid)return"\u2713 State graph: Valid";let t=e.errors.map(r=>` - ${r.path===""?"":r.path+": "}${r.message}`);return`\u2717 State graph: ${String(e.errorCount)} validation error${e.errorCount===1?"":"s"}
481
+ `);import re from"fs";import le from"path";import{chromium as Yi}from"playwright";import De from"fs";import yo from"path";import{z as Q}from"zod";var Bi=Q.object({baseUrl:Q.string().optional(),cloudBaseUrl:Q.string(),preconditionApiPath:Q.string().optional(),projectId:Q.string()});function wt(e){let t=yo.join(e,".ripplo","settings.json");if(!De.existsSync(t))return{baseUrl:void 0,cloudBaseUrl:void 0,errors:[{message:".ripplo/settings.json not found",path:""}],preconditionApiPath:void 0,valid:!1,warnings:[]};let r=Gi(t);if(!r.success)return{baseUrl:void 0,cloudBaseUrl:void 0,errors:[{message:r.error,path:"settings.json"}],preconditionApiPath:void 0,valid:!1,warnings:[]};let o=Bi.safeParse(r.data);if(!o.success)return{baseUrl:void 0,cloudBaseUrl:void 0,errors:o.error.issues.map(c=>({message:c.message,path:c.path.join(".")})),preconditionApiPath:void 0,valid:!1,warnings:[]};let n=[],a=yo.join(e,".ripplo","graph.json");return De.existsSync(a)&&Mi(a)&&(o.data.preconditionApiPath==null||o.data.preconditionApiPath.length===0)&&n.push("Graph has preconditions but preconditionApiPath is not set in settings.json. Preconditions will not be executed. Set preconditionApiPath to the URL where your app serves precondition endpoints (can be a relative path appended to baseUrl, or an absolute URL)."),{baseUrl:o.data.baseUrl,cloudBaseUrl:o.data.cloudBaseUrl,errors:[],preconditionApiPath:o.data.preconditionApiPath,valid:!0,warnings:n}}async function qe(e){try{await fetch(e,{method:"HEAD",signal:AbortSignal.timeout(5e3)});return}catch{return`baseUrl (${e}) is not responding. Make sure your dev server is running on that port.`}}function Mi(e){return bt(e)>0}function bt(e){try{let t=De.readFileSync(e,"utf8"),r=JSON.parse(t),n=Q.object({preconditions:Q.record(Q.string(),Q.unknown()).optional()}).safeParse(r);return n.success?Object.keys(n.data.preconditions??{}).length:0}catch{return 0}}async function wo({baseUrl:e,preconditionApiPath:t}){let r=t.startsWith("http://")||t.startsWith("https://")?t:`${e}${t}`;try{return(await fetch(`${r}/check`,{body:JSON.stringify({precondition:"__ripplo_health_check"}),headers:{"Content-Type":"application/json"},method:"POST",signal:AbortSignal.timeout(5e3)})).ok?"Precondition endpoint accepted an unsigned request \u2014 webhook signature verification may be missing or misconfigured. All precondition endpoints must verify the webhook-id, webhook-signature, and webhook-timestamp headers using the Standard Webhooks spec. See get_precondition_schema for setup instructions.":void 0}catch{return}}function Gi(e){try{let t=De.readFileSync(e,"utf8");return{data:JSON.parse(t),success:!0}}catch(t){return{error:t instanceof Error?t.message:"Failed to read file",success:!1}}}import kt from"fs";import vt from"path";import zi from"fs";import Fg from"path";import{z as V}from"zod";var St=V.object({edges:V.array(V.object({from:V.string(),to:V.string(),workflow:V.string()})),preconditions:V.record(V.string(),V.unknown()).optional(),resetPrecondition:V.string().optional(),states:V.record(V.string(),V.object({preconditions:V.array(V.string()),route:V.string()})),version:V.literal(3)});function bo(e){let t=zi.readFileSync(e,"utf8"),r=JSON.parse(t);return St.parse(r)}function So(e){return{edges:e.edges,preconditions:e.preconditions??{},resetPrecondition:e.resetPrecondition,states:e.states,version:e.version}}function ko(e){let t=Hi(e);if(!t.success)return{errors:[{message:t.error,path:""}],valid:!1};let r=St.safeParse(t.data);if(!r.success)return{errors:r.error.issues.map(g=>({message:g.message,path:g.path.join(".")})),valid:!1};let o=So(r.data),n=pt(o);if(!n.success)return{errors:n.errors,valid:!1};let a=[...ft(n.data)],i=vt.join(vt.dirname(e),"workflows"),c=kt.existsSync(i),l=new Set(n.data.edges.map(p=>p.workflow));return n.data.edges.forEach((p,g)=>{if(!c){a.push({message:`References workflow "${p.workflow}" but workflows directory does not exist`,path:`edges[${String(g)}].workflow`});return}let f=vt.join(i,`${p.workflow}.json`);kt.existsSync(f)||a.push({message:`References workflow "${p.workflow}" but no file exists at workflows/${p.workflow}.json`,path:`edges[${String(g)}].workflow`})}),c&&kt.readdirSync(i).filter(p=>p.endsWith(".json")).forEach(p=>{let g=p.replace(/\.json$/,"");l.has(g)||a.push({message:`Workflow file "workflows/${p}" is not referenced by any edge`,path:`workflows/${p}`})}),{errors:a,valid:a.length===0}}function Hi(e){try{return{data:bo(e),success:!0}}catch(t){return{error:t instanceof Error?t.message:"Failed to read file",success:!1}}}function xo(e){let t=Ji(e);if(!t.success)return{errors:[{message:t.error,path:""}],valid:!1};let r=xe.safeParse(t.data);if(!r.success)return{errors:r.error.issues.map(i=>({message:i.message,path:i.path.join(".")})),valid:!1};let o=Ne(r.data.spec);if(!o.success)return{errors:o.errors,valid:!1};let n=Ki(o.data);return n.length>0?{errors:n,valid:!1}:{errors:[],valid:!0}}function Ji(e){try{return{data:We(e),success:!0}}catch(t){return{error:t instanceof Error?t.message:"Failed to read file",success:!1}}}function Ki(e){let t=new Set(Object.keys(e.nodes)),r=[];t.has(e.entryNode)||r.push({message:`entryNode "${e.entryNode}" does not exist in nodes`,path:"entryNode"}),Object.entries(e.nodes).forEach(([i,c])=>{vo(c).forEach(l=>{t.has(l.id)||r.push({message:`references non-existent node "${l.id}"`,path:`nodes.${i}.${l.field}`})})});let o=new Set,n=t.has(e.entryNode)?[e.entryNode]:[],a=0;for(;a<n.length;){let i=n[a];if(a+=1,i==null||o.has(i))continue;o.add(i);let c=e.nodes[i];c!=null&&vo(c).forEach(l=>{o.has(l.id)||n.push(l.id)})}return t.forEach(i=>{o.has(i)||r.push({message:"unreachable from entryNode",path:`nodes.${i}`})}),r}function vo(e){let t=[];typeof e.next=="string"&&t.push({field:"next",id:e.next}),["consequent","alternate","body","catch","finally","nodes"].forEach(n=>{let a=e[n];Array.isArray(a)&&a.forEach((i,c)=>{typeof i=="string"&&t.push({field:`${n}[${String(c)}]`,id:i})})});let o=e.branches;return Array.isArray(o)&&o.forEach((n,a)=>{Array.isArray(n)&&n.forEach((i,c)=>{typeof i=="string"&&t.push({field:`branches[${String(a)}][${String(c)}]`,id:i})})}),t}async function Ro(e){let t=Qi(e),r=Xi(e),o=Zi(e),n=el(),a=tl(e),i=rl(e),c=await ol(e,t);return[t,r,o,...c,a,i,n]}function Qi(e){let t=wt(e);return t.valid?{missingFields:[],type:"settings",valid:!0}:{missingFields:t.errors.map(o=>o.path).filter(o=>o.length>0),type:"settings",valid:!1}}function Xi(e){let t=le.join(e,".ripplo","settings.local.json");if(!re.existsSync(t))return{exists:!1,hasToken:!1,hasWebhookSecret:!1,type:"local-settings"};try{let r=re.readFileSync(t,"utf8"),o=JSON.parse(r);if(o==null||typeof o!="object")return{exists:!0,hasToken:!1,hasWebhookSecret:!1,type:"local-settings"};let n=Object.fromEntries(Object.entries(o)),a=typeof n.token=="string"&&n.token.length>0,i=typeof n.webhookSecret=="string"&&n.webhookSecret.length>0;return{exists:!0,hasToken:a,hasWebhookSecret:i,type:"local-settings"}}catch{return{exists:!0,hasToken:!1,hasWebhookSecret:!1,type:"local-settings"}}}function Zi(e){let t=le.join(e,".mcp.json");if(!re.existsSync(t))return{installed:!1,type:"mcp-installed"};try{let r=re.readFileSync(t,"utf8"),o=JSON.parse(r);if(o==null||typeof o!="object")return{installed:!1,type:"mcp-installed"};let a=Object.fromEntries(Object.entries(o)).mcpServers;return a==null||typeof a!="object"?{installed:!1,type:"mcp-installed"}:{installed:"ripplo"in a,type:"mcp-installed"}}catch{return{installed:!1,type:"mcp-installed"}}}function el(){let e=Yi.executablePath();return{installed:re.existsSync(e),type:"browser"}}function tl(e){let t=le.join(e,".ripplo","graph.json");if(!re.existsSync(t))return{errorCount:0,errors:[],found:!1,type:"state-graph",valid:!1};let r=ko(t);return{errorCount:r.errors.length,errors:r.errors,found:!0,type:"state-graph",valid:r.valid}}function rl(e){let t=le.join(e,".ripplo","workflows");if(!re.existsSync(t))return{invalidNames:[],invalidWorkflows:[],total:0,type:"workflows",valid:0};let r=re.readdirSync(t).filter(a=>a.endsWith(".json")).map(a=>le.join(t,a)),o=[],n=[];return r.forEach(a=>{let i=xo(a);if(!i.valid){let c=le.basename(a,".json");o.push(c),n.push({errors:i.errors,name:c})}}),{invalidNames:o,invalidWorkflows:n,total:r.length,type:"workflows",valid:r.length-o.length}}async function ol(e,t){if(!t.valid)return[];let r=wt(e),o=[];if(r.baseUrl!=null){let a=await qe(r.baseUrl)==null;o.push({baseUrl:r.baseUrl,reachable:a,type:"dev-server"})}if(r.cloudBaseUrl!=null){let a=await qe(r.cloudBaseUrl)==null;o.push({reachable:a,type:"cloud-base-url",url:r.cloudBaseUrl})}let n=await nl(r);return o.push(...n),o}async function nl(e){let t=le.join(process.cwd(),".ripplo","graph.json"),r=bt(t),o=e.preconditionApiPath!=null&&e.preconditionApiPath.length>0,n={apiPathConfigured:o,count:r,endpointReachable:void 0,type:"preconditions"};if(r===0||!o)return[n];let a=al(e.baseUrl,e.preconditionApiPath);if(a==null)return[n];let i=await qe(a)==null,c=[];if(c.push({apiPathConfigured:!0,count:r,endpointReachable:i,type:"preconditions"}),Po(e.preconditionApiPath)&&c.push({reachable:i,type:"precondition-endpoint",url:a}),!i||e.baseUrl==null)return c;let l=await wo({baseUrl:e.baseUrl,preconditionApiPath:e.preconditionApiPath});return c.push({rejectsUnsigned:l==null,type:"webhook-verification"}),c}function Po(e){return e.startsWith("http://")||e.startsWith("https://")}function al(e,t){if(Po(t))return t;if(e!=null)return`${e}${t}`}function To(e){switch(e.type){case"settings":return sl(e);case"local-settings":return dl(e);case"dev-server":return il(e);case"preconditions":return ul(e);case"webhook-verification":return ll(e);case"state-graph":return pl(e);case"workflows":return fl(e);case"browser":return cl(e);case"cloud-base-url":return e.reachable?`\u2713 Cloud: ${e.url} is reachable`:`! Cloud: ${e.url} is not reachable (ok if not deployed yet)`;case"precondition-endpoint":return e.reachable?`\u2713 Precondition endpoint: ${e.url} is reachable`:`\u2717 Precondition endpoint: ${e.url} is not reachable`;case"mcp-installed":return e.installed?"\u2713 MCP: Ripplo MCP server configured in .mcp.json":"\u2717 MCP: Ripplo MCP server not found in .mcp.json. Run: ripplo setup-mcp"}}function sl(e){return e.valid?"\u2713 Settings: Project configured":`\u2717 Settings: Missing fields: ${e.missingFields.join(", ")}`}function il(e){return e.reachable?`\u2713 Dev server: ${e.baseUrl} is reachable`:`\u2717 Dev server: ${e.baseUrl} is not responding. Make sure your dev server is running.`}function ll(e){return e.rejectsUnsigned?"\u2713 Webhook verification: Unsigned requests are correctly rejected":"\u2717 Webhook verification: Endpoint accepted an unsigned request \u2014 webhook signature verification may be missing or misconfigured. See get_precondition_schema for setup instructions."}function cl(e){return e.installed?"\u2713 Browser: Chromium installed":"\u2717 Browser: Chromium not installed. Run: npx playwright install chromium"}function dl(e){if(!e.exists)return"\u2717 Auth: settings.local.json not found. Run `ripplo login` to authenticate.";let t=[];return e.hasToken||t.push("token"),e.hasWebhookSecret||t.push("webhookSecret"),t.length===0?"\u2713 Auth: Token and webhook secret configured":`\u2717 Auth: Missing ${t.join(" and ")} in settings.local.json`}function ul(e){return e.count===0?"! Preconditions: None defined in graph":e.apiPathConfigured?e.endpointReachable===void 0?`! Preconditions: ${String(e.count)} defined (could not verify endpoint)`:`\u2713 Preconditions: ${String(e.count)} defined, endpoint configured`:`\u2717 Preconditions: ${String(e.count)} defined but preconditionApiPath is not set in settings.json`}function pl(e){if(!e.found)return"\u2717 State graph: graph.json not found";if(e.valid)return"\u2713 State graph: Valid";let t=e.errors.map(r=>` - ${r.path===""?"":r.path+": "}${r.message}`);return`\u2717 State graph: ${String(e.errorCount)} validation error${e.errorCount===1?"":"s"}
482
482
  ${t.join(`
483
483
  `)}`}function fl(e){if(e.total===0)return"! Workflows: No workflow files found in .ripplo/workflows/";if(e.invalidNames.length===0)return`\u2713 Workflows: ${String(e.total)} valid`;let t=e.invalidWorkflows.map(r=>ml(r));return`\u2717 Workflows: ${String(e.invalidNames.length)} invalid
484
484
  ${t.join(`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ripplo",
3
- "version": "0.0.10",
3
+ "version": "0.0.12",
4
4
  "description": "CLI for Ripplo — AI-powered end-to-end testing",
5
5
  "type": "module",
6
6
  "homepage": "https://ripplo.ai",
@@ -55,9 +55,9 @@
55
55
  "typescript": "^5.9.3",
56
56
  "@ripplo/eslint-config": "0.0.0",
57
57
  "@ripplo/runtime": "^0.0.0",
58
+ "@ripplo/spec": "^0.0.0",
58
59
  "@ripplo/graph": "^0.0.0",
59
- "@ripplo/graphql": "^0.0.0",
60
- "@ripplo/spec": "^0.0.0"
60
+ "@ripplo/graphql": "^0.0.0"
61
61
  },
62
62
  "scripts": {
63
63
  "dev": "tsx watch src/index.ts",