@mistflow-ai/mcp 1.7.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/cli.js +238 -532
  2. package/dist/index.js +258 -552
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,28 +1,28 @@
1
- var fl=Object.defineProperty;var ht=(t,e)=>()=>(t&&(e=t(t=0)),e);var gn=(t,e)=>{for(var o in e)fl(t,o,{get:e[o],enumerable:!0})};import{existsSync as Do,readFileSync as ls,writeFileSync as Tl,mkdirSync as Il}from"fs";import{join as Mo,dirname as jo}from"path";import{homedir as Pl}from"os";import{fileURLToPath as Rl}from"url";function Ke(){if(Gn)return Gn;try{let t=Rl(import.meta.url),e=jo(t);for(let o=0;o<6;o++){let n=Mo(e,"package.json");if(Do(n)){let s=JSON.parse(ls(n,"utf-8"));if(s.name==="@mistflow-ai/mcp"&&typeof s.version=="string")return Gn=s.version,s.version}let r=jo(e);if(r===e)break;e=r}}catch{}return Gn="0.0.0","0.0.0"}function Kn(t){let e=/^(\d+)\.(\d+)\.(\d+)/.exec(t.trim());return e?[parseInt(e[1],10),parseInt(e[2],10),parseInt(e[3],10)]:null}function ss(t,e){let o=Kn(t),n=Kn(e);if(!o||!n)return 0;for(let r=0;r<3;r++){if(o[r]<n[r])return-1;if(o[r]>n[r])return 1}return 0}function cs(t,e,o){if(o&&ss(t,o)<0)return"unsupported";if(!e||ss(t,e)>=0)return"none";let r=Kn(t),s=Kn(e);return!r||!s?"none":r[0]<s[0]?"major":r[1]<s[1]?"minor":"patch"}function fn(t){let e=t.get("x-mistflow-mcp-latest")??"",o=t.get("x-mistflow-mcp-min-supported")??"",n=t.get("x-mistflow-mcp-changelog-url")??"";!e&&!o||(Ge={latest:e,minSupported:o,changelogUrl:n})}function ds(){let t=process.env.MISTFLOW_STATE_DIR||Mo(Pl(),".mistflow");return Mo(t,"upgrade-state.json")}function Al(){try{let t=ds();return Do(t)?JSON.parse(ls(t,"utf-8")):{}}catch{return{}}}function El(t){try{let e=ds(),o=jo(e);Do(o)||Il(o,{recursive:!0}),Tl(e,JSON.stringify(t,null,2)+`
2
- `,{mode:384})}catch{}}function Nl(t){return t==="patch"?7*864e5:t==="minor"?1*864e5:0}function us(){if(process.env.MISTFLOW_NO_UPGRADE_CHECK==="1"||!Ge)return null;let t=Ke();if(t==="0.0.0")return null;let e=cs(t,Ge.latest,Ge.minSupported);if(e==="none")return null;if(e==="unsupported")return as(e,t,Ge);if(is)return null;let o=Al(),n=Date.now(),r=Nl(e);return o.dismissedForVersion===Ge.latest&&e!=="major"||o.lastLatestSeen===Ge.latest&&o.lastShownMs&&n-o.lastShownMs<r?null:(is=!0,El({...o,lastShownMs:n,lastLatestSeen:Ge.latest}),as(e,t,Ge))}function as(t,e,o){let n="npx -y mistflow-ai install",r=o.changelogUrl?`
3
- What's new: ${o.changelogUrl}`:"";return t==="unsupported"?`
1
+ var Rl=Object.defineProperty;var yt=(t,e)=>()=>(t&&(e=t(t=0)),e);var fn=(t,e)=>{for(var r in e)Rl(t,r,{get:e[r],enumerable:!0})};import{existsSync as Lr,readFileSync as hs,writeFileSync as Ul,mkdirSync as Fl}from"fs";import{join as jr,dirname as Dr}from"path";import{homedir as ql}from"os";import{fileURLToPath as Bl}from"url";function Xe(){if(Jn)return Jn;try{let t=Bl(import.meta.url),e=Dr(t);for(let r=0;r<6;r++){let n=jr(e,"package.json");if(Lr(n)){let s=JSON.parse(hs(n,"utf-8"));if(s.name==="@mistflow-ai/mcp"&&typeof s.version=="string")return Jn=s.version,s.version}let o=Dr(e);if(o===e)break;e=o}}catch{}return Jn="0.0.0","0.0.0"}function Kn(t){let e=/^(\d+)\.(\d+)\.(\d+)/.exec(t.trim());return e?[parseInt(e[1],10),parseInt(e[2],10),parseInt(e[3],10)]:null}function us(t,e){let r=Kn(t),n=Kn(e);if(!r||!n)return 0;for(let o=0;o<3;o++){if(r[o]<n[o])return-1;if(r[o]>n[o])return 1}return 0}function gs(t,e,r){if(r&&us(t,r)<0)return"unsupported";if(!e||us(t,e)>=0)return"none";let o=Kn(t),s=Kn(e);return!o||!s?"none":o[0]<s[0]?"major":o[1]<s[1]?"minor":"patch"}function yn(t){let e=t.get("x-mistflow-mcp-latest")??"",r=t.get("x-mistflow-mcp-min-supported")??"",n=t.get("x-mistflow-mcp-changelog-url")??"";!e&&!r||(Qe={latest:e,minSupported:r,changelogUrl:n})}function fs(){let t=process.env.MISTFLOW_STATE_DIR||jr(ql(),".mistflow");return jr(t,"upgrade-state.json")}function Hl(){try{let t=fs();return Lr(t)?JSON.parse(hs(t,"utf-8")):{}}catch{return{}}}function zl(t){try{let e=fs(),r=Dr(e);Lr(r)||Fl(r,{recursive:!0}),Ul(e,JSON.stringify(t,null,2)+`
2
+ `,{mode:384})}catch{}}function Wl(t){return t==="patch"?7*864e5:t==="minor"?1*864e5:0}function ys(){if(process.env.MISTFLOW_NO_UPGRADE_CHECK==="1"||!Qe)return null;let t=Xe();if(t==="0.0.0")return null;let e=gs(t,Qe.latest,Qe.minSupported);if(e==="none")return null;if(e==="unsupported")return ms(e,t,Qe);if(ps)return null;let r=Hl(),n=Date.now(),o=Wl(e);return r.dismissedForVersion===Qe.latest&&e!=="major"||r.lastLatestSeen===Qe.latest&&r.lastShownMs&&n-r.lastShownMs<o?null:(ps=!0,zl({...r,lastShownMs:n,lastLatestSeen:Qe.latest}),ms(e,t,Qe))}function ms(t,e,r){let n="npx -y mistflow-ai install",o=r.changelogUrl?`
3
+ What's new: ${r.changelogUrl}`:"";return t==="unsupported"?`
4
4
 
5
5
  ---
6
6
  Mistflow ${e} is no longer supported.
7
- You must upgrade to ${o.latest} or newer to continue:
7
+ You must upgrade to ${r.latest} or newer to continue:
8
8
 
9
9
  ${n}
10
10
 
11
- After upgrading, restart your AI editor.${r}
11
+ After upgrading, restart your AI editor.${o}
12
12
  ---`:t==="major"?`
13
13
 
14
14
  ---
15
- Mistflow ${o.latest} is available (you have ${e}).
16
- This is a major update. Run \`${n}\` and restart your editor.${r}
15
+ Mistflow ${r.latest} is available (you have ${e}).
16
+ This is a major update. Run \`${n}\` and restart your editor.${o}
17
17
  ---`:t==="minor"?`
18
18
 
19
- --- Mistflow update available: ${e} -> ${o.latest} ---
20
- Run \`${n}\` to upgrade, then restart your editor.${r}`:`
19
+ --- Mistflow update available: ${e} -> ${r.latest} ---
20
+ Run \`${n}\` to upgrade, then restart your editor.${o}`:`
21
21
 
22
- (Mistflow ${o.latest} is out, you have ${e}. Run \`${n}\` when convenient.)`}function Lo(){let t=Ke(),e=Ge??{latest:"",minSupported:"",changelogUrl:""},o=cs(t,e.latest,e.minSupported);return{current:t,latest:e.latest,minSupported:e.minSupported,severity:o,upgradeCmd:"npx -y mistflow-ai install",changelogUrl:e.changelogUrl,backendSignalReceived:Ge!==null}}var Gn,Ge,is,yn=ht(()=>{"use strict";Gn=null;Ge=null;is=!1});var vn={};gn(vn,{closeBrowser:()=>Uo,getIsolatedContext:()=>Ml,getPage:()=>$o,getSnapshot:()=>bn,takeScreenshot:()=>wn});async function ps(){try{return await import("playwright")}catch{throw new Error("Playwright is not installed. Run: cd packages/mcp-server && pnpm add playwright && npx playwright install chromium")}}async function Ol(){let t=await ps();return(!Je||!Je.isConnected())&&(Je=await t.chromium.launch({headless:!0})),gt&&(await gt.close().catch(()=>{}),gt=null),gt=await Je.newContext({viewport:{width:1280,height:720},deviceScaleFactor:1}),it=await gt.newPage(),it}async function $o(){return it&&!it.isClosed()?it:(Jn||(Jn=Ol().finally(()=>{Jn=null})),Jn)}async function Uo(){it&&!it.isClosed()&&await it.close().catch(()=>{}),gt&&await gt.close().catch(()=>{}),Je?.isConnected()&&await Je.close().catch(()=>{}),it=null,gt=null,Je=null}async function Ml(){let t=await ps();(!Je||!Je.isConnected())&&(Je=await t.chromium.launch({headless:!0}));let e=await Je.newContext({viewport:{width:1280,height:720},deviceScaleFactor:1}),o=await e.newPage();return{context:e,page:o}}async function bn(t){return await t.locator("body").ariaSnapshot()}async function wn(t,e=!1){return await t.screenshot({fullPage:e,type:"png"})}var Je,gt,it,Jn,zt=ht(()=>{"use strict";Je=null,gt=null,it=null,Jn=null;process.once("SIGTERM",()=>{Uo().finally(()=>process.exit(0))});process.once("SIGINT",()=>{Uo().finally(()=>process.exit(0))})});import{readFileSync as qo,existsSync as Vn,writeFileSync as hs,mkdirSync as Ll,renameSync as $l,unlinkSync as Ul}from"fs";import{join as Yn,dirname as ql}from"path";import{homedir as Fl}from"os";import{randomBytes as Bl}from"crypto";function gs(){return Yn(Fl(),".mistflow","credentials.json")}function Qn(){return(process.env.MISTFLOW_API_URL||"https://api.mistflow.ai").replace(/\/+$/,"")}function fs(){let t=gs();if(!Vn(t))return null;try{let e=JSON.parse(qo(t,"utf-8"));return typeof e.apiKey=="string"?{[Hl]:e}:e}catch{return null}}function At(){let t=process.env.MISTFLOW_API_KEY;if(t)return{ok:!0,creds:{apiKey:t,orgId:"",orgSlug:"env"}};let e=fs();if(!e)return{ok:!1,reason:"missing"};let o=Qn(),n=e[o];return n&&typeof n.apiKey=="string"&&n.apiKey&&typeof n.orgId=="string"?{ok:!0,creds:n}:{ok:!1,reason:"missing"}}function Fo(t){let e=gs(),o=ql(e);Vn(o)||Ll(o,{recursive:!0});let n=fs()??{},r=Qn();n[r]=t;let s=Yn(o,`.credentials.tmp.${Bl(8).toString("hex")}`);try{hs(s,JSON.stringify(n,null,2)+`
23
- `,{mode:384}),$l(s,e)}catch(i){try{Ul(s)}catch{}throw i}}function ys(){let t=At();return t.ok?t.creds:null}function Te(){return At().ok}function ft(t){let e=Yn(t,"mistflow.json");if(!Vn(e))return null;try{return JSON.parse(qo(e,"utf-8"))}catch{return null}}function Bo(t,e){let o=Yn(t,"mistflow.json");if(Vn(o))try{let r={...JSON.parse(qo(o,"utf-8")),...e};hs(o,JSON.stringify(r,null,2)+`
24
- `,"utf-8")}catch{}}var Hl,Et=ht(()=>{"use strict";Hl="https://api.mistflow.ai"});var _s={};gn(_s,{MistflowApiError:()=>X,addDomain:()=>Ko,bindWorkspace:()=>xn,cancelSession:()=>fr,checkAuth:()=>Jl,checkAuthDetailed:()=>ks,checkSubdomain:()=>Zn,checkToolGuard:()=>yr,createDeployment:()=>Yl,createProject:()=>Ot,deleteEnvVar:()=>Xo,discoverDecisions:()=>Xl,downloadImageryAsset:()=>Wo,downloadSource:()=>tc,downloadSourceWithToken:()=>xs,errorFromResponse:()=>Gt,fetchAcceptanceCriteria:()=>kn,fetchActiveSessionPlan:()=>br,fetchAddon:()=>lr,fetchDesignDirections:()=>eo,fetchDesignPick:()=>Ql,fetchDesignRenders:()=>no,fetchDoc:()=>ur,fetchDocsList:()=>dr,fetchModule:()=>pr,fetchPlanConversation:()=>oo,fetchProjectImagery:()=>zo,fetchScaffold:()=>ar,fetchSessionNext:()=>Jt,fetchSessionPlanRevisions:()=>Ss,fetchStepContext:()=>cr,forkTemplate:()=>hr,generatePlan:()=>ro,getAuthHeaders:()=>Nt,getBaseUrl:()=>Me,getDashboardUrl:()=>Wl,getDbCredentials:()=>Zl,getDeployLogs:()=>Zo,getDeploymentStatus:()=>yt,getProject:()=>Vl,getProjectErrors:()=>er,getSeedInfo:()=>ao,getSession:()=>lo,getSiteUrl:()=>zl,getTemplate:()=>mr,hasCredentialsOnDisk:()=>Te,hasLocalCredentials:()=>vs,listCronJobLogs:()=>rr,listCronJobs:()=>tr,listDeployments:()=>Kt,listDomains:()=>bt,listEnvVars:()=>Yo,listSessionsForMachine:()=>Sn,markLocalSetupDone:()=>nc,modifyPlan:()=>so,pingBackend:()=>Ho,promotePreview:()=>sr,redeployProject:()=>ec,removeDomain:()=>Jo,request:()=>L,rollbackDeployment:()=>ir,setCronJobEnabled:()=>nr,shareProject:()=>gr,startSession:()=>oc,submitAcceptanceResults:()=>wr,submitDesignPick:()=>to,submitSessionAnswers:()=>rc,triggerCronJobNow:()=>or,uploadAndDeploy:()=>Go,uploadQAResults:()=>Vo,upsertEnvVar:()=>Qo,verifyDomain:()=>io});function Me(){return Qn()}function zl(){let t=process.env.MISTFLOW_API_URL;if(t){if(t.includes("mistflow.localhost"))return t.replace("api.mistflow","mistflow");if(t.includes("localhost:9100"))return t.replace(":9100",":9102")}return"https://mistflow.ai"}function Wl(){let t=process.env.MISTFLOW_API_URL;if(t){if(t.includes("mistflow.localhost"))return t.replace("api.mistflow","app.mistflow");if(t.includes("localhost:9100"))return t.replace(":9100",":9101")}return"https://app.mistflow.ai"}function Nt(){let t=At();if(!t.ok)throw new X("auth_missing","No Mistflow credentials found.",401);return Gl(t.creds)}function Gl(t){return{Authorization:`ApiKey ${t.apiKey}`,"Content-Type":"application/json","X-Mistflow-MCP-Version":Ke()}}function Wt(t){try{fn(t.headers)}catch{}}async function Ho(){try{let t=await fetch(`${Me()}/health`,{method:"GET",signal:AbortSignal.timeout(5e3)});Wt(t)}catch{}}async function ws(t,e,o,n){for(let r=0;r<2;r++)try{return await fetch(t,{...e,signal:AbortSignal.timeout(o)})}catch(s){let i=s instanceof Error&&s.name==="TimeoutError",a=s instanceof TypeError;if(n&&r===0&&(i||a)){console.error(`[api] Retrying ${t} after ${i?"timeout":"network error"}`);continue}throw s}throw new Error("fetchWithRetry: exhausted retries")}async function Gt(t){let e=null;try{e=await t.json()}catch{e=null}let o=e&&typeof e.code=="string"?e.code:void 0,n=e&&typeof e.message=="string"?e.message:e&&typeof e.detail=="string"?e.detail:t.statusText||"Request failed";if(o)return new X(o,n,t.status,e?.details);let r=t.status;return r===401?new X("auth_invalid",n,r):r===403?new X("permission_denied",n,r):r===404?new X("not_found",n,r):r===409?new X("conflict",n,r):r===422?new X("validation_error",n,r):r===429?new X("rate_limited",n,r):r>=500?new X("server_error",t.statusText||"Internal server error",r):new X("client_error",n,r)}async function L(t,e={}){let o=Nt(),{timeoutMs:n,idempotent:r,...s}=e,i=n??3e4,a=(s.method??"GET").toUpperCase(),l=r??(a==="GET"||a==="HEAD"),c;try{c=await ws(`${Me()}${t}`,{...s,headers:{...o,...s.headers}},i,l)}catch(m){throw m instanceof Error&&m.name==="TimeoutError"?new X("network_error","Request timed out. Try again in a moment."):new X("network_error","Cannot reach Mistflow servers. Check your network.")}if(Wt(c),!c.ok)throw await Gt(c);return c.json()}function vs(){return At().ok}async function Jl(){return(await ks()).ok}async function ks(){if(Xn!==null&&Date.now()<bs)return Xn?{ok:!0}:{ok:!1,reason:"no_credentials"};if(!vs())return{ok:!1,reason:"no_credentials"};try{return await L("/api/org"),Xn=!0,bs=Date.now()+Kl,{ok:!0}}catch(t){if(Xn=null,!(t instanceof X))return{ok:!1,reason:"network_error"};switch(t.code){case"auth_missing":case"auth_revoked":return{ok:!1,reason:"no_credentials"};case"auth_expired":case"auth_invalid":case"auth_org_not_found":return{ok:!1,reason:"expired"};case"network_error":return{ok:!1,reason:"network_error"};case"rate_limited":case"server_error":case"upstream_error":return{ok:!1,reason:"server_error"};default:return{ok:!1,reason:"server_error"}}}}async function Vl(t){return L(`/api/projects/${encodeURIComponent(t)}`)}async function Zn(t){return L(`/api/projects/check-subdomain?name=${encodeURIComponent(t)}`)}async function Ot(t,e={}){return L("/api/projects",{method:"POST",body:JSON.stringify({name:t,template:e.template,db_provider:e.dbProvider??"neon",requested_subdomain:e.requestedSubdomain,picked_direction:e.pickedDirection,imagery_brief:e.imageryBrief,plan_context:e.planContext,design_conversation_id:e.designConversationId,session_id:e.sessionId,scaffold_features:e.scaffoldFeatures})})}async function zo(t){return L(`/api/projects/${encodeURIComponent(t)}/imagery`)}async function Wo(t){let e=await fetch(t);if(!e.ok)throw new Error(`Failed to download imagery asset: HTTP ${e.status} ${e.statusText}`);let o=await e.arrayBuffer();return Buffer.from(o)}async function Yl(t,e){return L("/api/deploy",{method:"POST",body:JSON.stringify({project_id:t,build_output_url:e})})}async function Go(t,e,o="production",n,r,s,i){let{readFileSync:a}=await import("fs"),{basename:l}=await import("path"),c=At();if(!c.ok)throw new X("auth_missing","No Mistflow credentials found.",401);let m=c.creds,u=a(e),p=new Blob([u],{type:"application/gzip"}),h=new FormData;if(h.append("project_id",t),h.append("build",p,l(e)),o!=="production"&&h.append("environment",o),n&&h.append("admin_email",n),r&&h.append("schema_pushed","true"),s){let{existsSync:x}=await import("fs");if(x(s)){let w=a(s),A=new Blob([w],{type:"application/gzip"});h.append("source",A,"source.tar.gz")}}i&&h.append("git_commit_sha",i);let U;try{U=await fetch(`${Me()}/api/deploy/upload`,{method:"POST",headers:{Authorization:`ApiKey ${m.apiKey}`,"X-Mistflow-MCP-Version":Ke()},body:h,signal:AbortSignal.timeout(3e5)})}catch{throw new X("network_error","Cannot reach Mistflow servers. Check your network.")}if(Wt(U),!U.ok)throw await Gt(U);let C=await U.json();return{...C,id:C.deployment_id}}async function yt(t,e){let o=e?.waitSeconds??0,n=o>0?`?wait=${o}`:"",r=Math.min(6e4,(o+5)*1e3);return L(`/api/deploy/${encodeURIComponent(t)}/status${n}`,o>0?{timeoutMs:r}:void 0)}async function eo(t){return L(`/api/plan/design-directions/${encodeURIComponent(t)}`,{timeoutMs:15e3})}async function Ql(t,e){let o=e?.waitSeconds??45,n=o>0?`?wait=${o}`:"",r=Math.min(6e4,(o+5)*1e3);return L(`/api/plan/design-directions/${encodeURIComponent(t)}/pick${n}`,{timeoutMs:r})}async function to(t,e){return L(`/api/plan/design-directions/${encodeURIComponent(t)}/pick`,{method:"POST",body:JSON.stringify(e),timeoutMs:6e4})}async function no(t){try{return await L(`/api/plan/design-directions/${encodeURIComponent(t)}/renders`,{timeoutMs:1e4})}catch{return{}}}async function oo(t,e){let o=e?.waitSeconds??45,n=o>0?`?wait=${o}`:"",r=Math.min(6e4,(o+5)*1e3);return L(`/api/plan/conversations/${encodeURIComponent(t)}${n}`,{timeoutMs:r})}async function ro(t,e){let o={description:t,conversation_id:e?.conversationId,answers:e?.answers};if(e?.autonomous&&(o.autonomous=!0),e?.language&&e.language.toLowerCase()!=="english"&&(o.language=e.language),e?.designConversationId&&e?.designDirection){let r=e.designDirection,s=256,i=4e3,a=typeof r.id=="string"?r.id:void 0,l=typeof r.custom=="string"?r.custom:void 0,c=a&&a.length>0&&a.length<=s?a:void 0,m=(()=>{if(l&&l.length>0)return l.length<=i?l:l.slice(0,i)+" (truncated)";if(!c){let h=JSON.stringify(r);return h.length<=i?h:h.slice(0,i)+" (truncated)"}})();a&&!c&&console.error(`[mistflow] direction_id ${a.length} chars exceeds ${s}; falling back to custom`);let u=c?{direction_id:c}:{custom:m};e.conversationId&&(u.conversation_id=e.conversationId);let p=await to(e.designConversationId,u);return{status:"ready",plan:p.plan??{},methodology:p.methodology??"",...p.designMd?{designMd:p.designMd}:{}}}let n=e?.conversationId||e?.answers||e?.designConversationId?18e4:6e4;return L("/api/plan",{method:"POST",body:JSON.stringify(o),timeoutMs:n,idempotent:!1})}async function so(t,e,o={}){return L("/api/plan/modify",{method:"POST",body:JSON.stringify({existing_plan:t,modification:e,plan_md:o.planMd})})}async function Xl(t){return L("/api/plan/discover",{method:"POST",body:JSON.stringify({description:t})})}async function Ko(t,e){return L(`/api/projects/${encodeURIComponent(t)}/domains`,{method:"POST",body:JSON.stringify({domain:e})})}async function bt(t){return L(`/api/projects/${encodeURIComponent(t)}/domains`)}async function io(t,e){return L(`/api/projects/${encodeURIComponent(t)}/domains/${encodeURIComponent(e)}/verify`)}async function Jo(t,e){return L(`/api/projects/${encodeURIComponent(t)}/domains/${encodeURIComponent(e)}`,{method:"DELETE"})}async function Zl(t){return L(`/api/projects/${encodeURIComponent(t)}/db-credentials`)}async function ao(t){try{return await L(`/api/projects/${encodeURIComponent(t)}/test-accounts`)}catch(e){return e instanceof X&&(e.statusCode===404||e.code==="not_found")||console.error("[api] Failed to fetch seed info:",e instanceof Error?e.message:e),null}}async function Vo(t,e){try{return await L(`/api/deploy/${encodeURIComponent(t)}/qa-results`,{method:"POST",body:JSON.stringify(e)})}catch(o){return console.error("[api] Failed to upload QA results:",o instanceof Error?o.message:o),null}}async function Yo(t){return L(`/api/projects/${encodeURIComponent(t)}/env`)}async function Qo(t,e,o,n){return L(`/api/projects/${encodeURIComponent(t)}/env`,{method:"PUT",body:JSON.stringify({key:e,value:o,category:n?.category??"custom",description:n?.description,setup_url:n?.setupUrl})})}async function Xo(t,e){return L(`/api/projects/${encodeURIComponent(t)}/env/${encodeURIComponent(e)}`,{method:"DELETE"})}async function Zo(t){return L(`/api/deploy/${encodeURIComponent(t)}/logs`)}async function er(t,e="7d"){return L(`/api/projects/${encodeURIComponent(t)}/errors?period=${encodeURIComponent(e)}`)}async function Kt(t){return L(`/api/projects/${encodeURIComponent(t)}/deployments`)}async function tr(t){return L(`/api/projects/${encodeURIComponent(t)}/cron-jobs`)}async function nr(t,e,o){return L(`/api/projects/${encodeURIComponent(t)}/cron-jobs/${encodeURIComponent(e)}`,{method:"PATCH",body:JSON.stringify({enabled:o}),headers:{"Content-Type":"application/json"}})}async function or(t,e){return L(`/api/projects/${encodeURIComponent(t)}/cron-jobs/${encodeURIComponent(e)}/trigger`,{method:"POST"})}async function rr(t,e,o=50){return L(`/api/projects/${encodeURIComponent(t)}/cron-jobs/${encodeURIComponent(e)}/logs?limit=${o}`)}async function ec(t){return L(`/api/deploy/${encodeURIComponent(t)}/redeploy`,{method:"POST"})}async function sr(t,e){let o=new URLSearchParams({preview_deployment_id:e});return L(`/api/deploy/${encodeURIComponent(t)}/promote`,{method:"POST",body:o.toString(),headers:{"Content-Type":"application/x-www-form-urlencoded"}})}async function ir(t){return L(`/api/deploy/${encodeURIComponent(t)}/rollback`,{method:"POST"})}async function tc(t,e){let o=At();if(!o.ok)throw new X("auth_missing","Not authenticated.",401);let n=o.creds,r=await fetch(`${Me()}/api/deploy/${encodeURIComponent(t)}/source`,{headers:{Authorization:`ApiKey ${n.apiKey}`,"X-Mistflow-MCP-Version":Ke()},signal:AbortSignal.timeout(12e4)});if(Wt(r),!r.ok)throw await Gt(r);let{writeFileSync:s}=await import("fs"),i=Buffer.from(await r.arrayBuffer());s(e,i)}async function Mt(t,e){let{timeoutMs:o,idempotent:n,...r}=e??{},s=o??3e4,i=(r.method??"GET").toUpperCase(),a=n??(i==="GET"||i==="HEAD"),l;try{l=await ws(`${Me()}${t}`,{headers:{"Content-Type":"application/json","X-Mistflow-MCP-Version":Ke()},...r},s,a)}catch(c){throw c instanceof Error&&c.name==="TimeoutError"?new X("network_error","Request timed out. Try again in a moment."):new X("network_error","Cannot reach Mistflow servers. Check your network.")}if(Wt(l),!l.ok)throw await Gt(l);return l.json()}async function ar(t="nextjs"){return Mt(`/api/scaffold/${encodeURIComponent(t)}`)}async function lr(t){return Mt(`/api/scaffold/addon/${encodeURIComponent(t)}`)}async function cr(t,e){return Mt(`/api/scaffold/${encodeURIComponent(t)}/context?step_type=${encodeURIComponent(e)}`)}async function dr(t="nextjs",e){let o=e?`?kind=${encodeURIComponent(e)}`:"";return Mt(`/api/scaffold/${encodeURIComponent(t)}/docs${o}`)}async function ur(t,e){return Mt(`/api/scaffold/${encodeURIComponent(t)}/docs/${encodeURIComponent(e)}`)}async function pr(t,e,o,n){return Mt(`/api/scaffold/${encodeURIComponent(t)}/module`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({type:"crud",entity:e,fields:o,entity_plural:n})})}async function mr(t){return Mt(`/api/templates/${encodeURIComponent(t)}`)}async function hr(t){return L(`/api/templates/${encodeURIComponent(t)}/fork`,{method:"POST"})}async function xs(t,e,o){let n;try{n=await fetch(`${Me()}/api/deploy/${encodeURIComponent(t)}/fork-source?fork_token=${encodeURIComponent(e)}`,{headers:{"X-Mistflow-MCP-Version":Ke()},signal:AbortSignal.timeout(12e4)})}catch{throw new X("network_error","Cannot reach Mistflow servers. Check your network.")}if(Wt(n),!n.ok)throw n.status===401?new X("validation_error","Fork token expired or invalid. Re-open the project in the dashboard to get a fresh token.",n.status):await Gt(n);let{writeFileSync:r}=await import("fs"),s=Buffer.from(await n.arrayBuffer());r(o,s)}async function nc(t){await L(`/api/projects/${encodeURIComponent(t)}`,{method:"PATCH",body:JSON.stringify({local_setup_done:!0})})}async function gr(t,e){return L(`/api/projects/${encodeURIComponent(t)}/share`,{method:"POST",body:JSON.stringify({is_template:e?.isTemplate??!1,template_description:e?.description})})}async function oc(t){return L("/api/sessions/start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t),idempotent:!1})}async function lo(t){return L(`/api/sessions/${t}`)}async function fr(t,e){return L(`/api/sessions/${t}/cancel`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({reason:e??null}),idempotent:!0})}async function Jt(t){return L(`/api/sessions/${t}/next`)}async function rc(t,e){return L(`/api/sessions/${t}/answers`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({answers:e}),idempotent:!0})}async function yr(t,e){return L(`/api/sessions/${t}/can/${e}`)}async function kn(t){return L(`/api/sessions/${t}/acceptance`)}async function Ss(t){return L(`/api/sessions/${t}/revisions`)}async function br(t){let e=await Ss(t),o=[...e].reverse().find(n=>n.status==="active")??e.at(-1);return!o||!o.body||typeof o.body!="object"||Array.isArray(o.body)?null:{plan:o.body,planRevisionId:o.id}}async function xn(t,e){return L(`/api/sessions/${t}/workspace`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),idempotent:!0})}async function Sn(t,e={}){let o={machine_id:t};e.includeIdle&&(o.include_idle="true");let n=new URLSearchParams(o).toString();return L(`/api/sessions/me/workspaces?${n}`)}async function wr(t,e,o){return L(`/api/sessions/${t}/acceptance/results`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({results:e,plan_revision_id:o??null}),idempotent:!1})}var X,Xn,bs,Kl,Ie=ht(()=>{"use strict";Et();yn();X=class extends Error{constructor(o,n,r,s){super(n);this.code=o;this.statusCode=r;this.details=s;this.name="MistflowApiError"}code;statusCode;details;get isAuth(){return this.code.startsWith("auth_")}get isTransient(){return this.code==="server_error"||this.code==="upstream_error"||this.code==="network_error"||this.code==="rate_limited"}};Xn=null,bs=0,Kl=300*1e3});var Tn={};gn(Tn,{emptyState:()=>Cn,fetchRemoteState:()=>yc,fuzzyMatch:()=>wc,getLocalStatePath:()=>kr,readLocalState:()=>fc,syncRemoteState:()=>bc,writeLocalState:()=>_n});import{existsSync as As,readFileSync as mc,writeFileSync as hc,mkdirSync as gc}from"fs";import{join as Es}from"path";function kr(t){return Es(t,".mistflow","state.json")}function fc(t){let e=kr(t);if(!As(e))return null;try{return JSON.parse(mc(e,"utf-8"))}catch{return null}}function _n(t,e){let o=Es(t,".mistflow");As(o)||gc(o,{recursive:!0}),hc(kr(t),JSON.stringify(e,null,2)+`
25
- `)}function Cn(t,e){return{projectId:t,name:e,template:"",features:[],dbSchema:[],deployCount:0,decisions:[],provenance:[]}}async function yc(t){let e;try{e=Nt()}catch{return null}try{let o=await fetch(`${Me()}/api/projects/${encodeURIComponent(t)}/state`,{headers:{...e,"Content-Type":"application/json","X-Mistflow-MCP-Version":Ke()}});try{fn(o.headers)}catch{}return o.ok?await o.json():null}catch{return null}}async function bc(t,e){let o;try{o=Nt()}catch{return!1}try{let n=await fetch(`${Me()}/api/projects/${encodeURIComponent(t)}/state`,{method:"PUT",headers:{...o,"Content-Type":"application/json","X-Mistflow-MCP-Version":Ke()},body:JSON.stringify(e)});try{fn(n.headers)}catch{}return n.ok}catch{return!1}}function wc(t,e){let o=t.toLowerCase(),n=e.toLowerCase();return n.includes(o)||o.includes(n)?!0:o.split(/\s+/).some(s=>s.length>=3&&n.includes(s))}var jt=ht(()=>{"use strict";Ie();yn()});var xr={};gn(xr,{ensureBackendRegistered:()=>Tc,ensureShadcnComponents:()=>Ic});import{existsSync as Os,readFileSync as vc,writeFileSync as kc}from"fs";import{join as co}from"path";import{spawn as xc}from"child_process";function Sc(t,e,o,n,r){return new Promise(s=>{let i=xc(t,e,{cwd:o,stdio:["pipe","pipe","pipe"],timeout:n,...r?{env:{...process.env,...r}}:{}}),a="",l="";i.stdout?.on("data",c=>{a+=c.toString()}),i.stderr?.on("data",c=>{l+=c.toString()}),i.on("close",c=>s({success:c===0,stdout:a,stderr:l})),i.on("error",c=>s({success:!1,stdout:a,stderr:l+c.message}))})}function _c(t){let e=co(t,"mistflow.json");if(!Os(e))return null;try{return JSON.parse(vc(e,"utf-8"))}catch{return null}}function Cc(t,e){kc(co(t,"mistflow.json"),JSON.stringify(e,null,2))}async function Ns(t,e){try{let o=e.features,n=e.steps,r={plan:e};Array.isArray(o)&&o.length>0&&(r.features=o.map(s=>s.name)),Array.isArray(n)&&n.length>0&&(r.provenance=n.map(s=>({feature:s.name??s.title??`Step ${s.number??"?"}`,user_intent:(s.description??"").slice(0,500),decisions:"Seeded from plan",tradeoffs:"",files_affected:[]}))),await fetch(`${Me()}/api/projects/${encodeURIComponent(t)}/state`,{method:"PUT",headers:{...Nt(),"Content-Type":"application/json"},body:JSON.stringify(r)})}catch(o){console.error("[self-heal] state sync failed:",o instanceof Error?o.message:String(o))}}async function Tc(t,e={}){let o=_c(t);if(o){if(!Te())return o.projectId;if(!o.projectId)try{let r=o.plan?.requestedSubdomain,s=await Ot(o.name,{dbProvider:o.dbProvider??"neon",requestedSubdomain:r});return o.projectId=s.id,Cc(t,o),_n(t,Cn(s.id,o.name)),o.plan&&await Ns(s.id,o.plan),console.error(`[self-heal] registered project ${s.id.slice(0,8)} with backend`),s.id}catch(n){console.error("[self-heal] createProject failed:",n instanceof Error?n.message:String(n));return}return e.forceSync&&o.plan&&o.projectId&&await Ns(o.projectId,o.plan),o.projectId}}async function Ic(t,e){let o=["button","card","input","label","form","dialog","table","dropdown-menu","badge","separator","skeleton","sheet","tabs","avatar","select","textarea","checkbox","switch","tooltip","popover","sonner"],n=e&&e.length>0?e:o,r=co(t,"components","ui"),s=[],i=[];for(let c of n)Os(co(r,`${c}.tsx`))?s.push(c):i.push(c);if(i.length===0)return{installed:[],alreadyPresent:s};let a={NPM_CONFIG_LEGACY_PEER_DEPS:"true"},l=await Sc("npx",["--yes","shadcn@latest","add","-y","-o",...i],t,18e4,a);return l.success?{installed:i,alreadyPresent:s}:{installed:[],alreadyPresent:s,failed:`shadcn add failed for: ${i.join(", ")}. ${l.stderr.slice(-300)}`.trim()}}var Sr=ht(()=>{"use strict";Ie();jt()});var ct,gi,Er=ht(()=>{"use strict";ct={slug:"agent-ui",version:"1.0.0",description:"Mistflow agent UX primitives. Rich tool cards with streaming logs, approval gates for risky actions, cost badges, plan checklists, contextual suggestions.",files:[{src:"src/components/ApprovalGate.tsx",dst:"components/agent/ApprovalGate.tsx",version:"1.0.0",skipIfExists:!1,sourceHash:"sha256:f0622a2873f633683bae7e25f3b62b0b4fc49c7b01601af3009001d36c773273"},{src:"src/components/ErrorBoundary.tsx",dst:"components/agent/ErrorBoundary.tsx",version:"1.0.0",skipIfExists:!1,sourceHash:"sha256:b638e1163a807780fe937be5e267a8379e8fe1afb661f44cb4582089b1808392"},{src:"src/components/CostBadge.tsx",dst:"components/agent/CostBadge.tsx",version:"1.0.0",skipIfExists:!1,sourceHash:"sha256:5d2d368e07cfac7025f69b9de87c7402a0d9fdb71d0b2c3fa85138831c918507"},{src:"src/components/PlanSteps.tsx",dst:"components/agent/PlanSteps.tsx",version:"1.0.0",skipIfExists:!1,sourceHash:"sha256:116a8a01c43e27a2c862d8b1218c6b44bc7406aa59c6784612f1d45993f9a3db"},{src:"src/components/SuggestedPrompts.tsx",dst:"components/agent/SuggestedPrompts.tsx",version:"1.0.0",skipIfExists:!1,sourceHash:"sha256:fe6eaea62a2d9c0b74f4cb6f7db3fe6673b647b2d1fc8a640624828cbebe45d7"},{src:"src/components/ToolStream.tsx",dst:"components/agent/ToolStream.tsx",version:"1.0.0",skipIfExists:!1,sourceHash:"sha256:a9c2763830fc13a544a1ef6f6b1f7445362ee9e1915408b85088827e3b1899c5"},{src:"src/lib/cn.ts",dst:"lib/cn.ts",version:"1.0.0",skipIfExists:!0,sourceHash:"sha256:39c7c9fe53788ae5992a1027e1d440e8b8c9d4af61500aa847cb173432c65603"},{src:"src/lib/agent-types.ts",dst:"lib/agent-types.ts",version:"1.0.0",skipIfExists:!1,sourceHash:"sha256:3efa0340d6d3a21524a4693d3bdfc8ee029cf3779616d1c81ea35b2ce76f2744"}],dependencies:{"framer-motion":"^11.18.0",recharts:"^2.15.0",clsx:"^2.1.1","tailwind-merge":"^2.5.0"},postInstallNote:"Components written under components/agent/. See methodologies/agent-ui-components.md for the API and an integration recipe."},gi={"src/components/ApprovalGate.tsx":`/** @mistflow-component approval-gate
22
+ (Mistflow ${r.latest} is out, you have ${e}. Run \`${n}\` when convenient.)`}function $r(){let t=Xe(),e=Qe??{latest:"",minSupported:"",changelogUrl:""},r=gs(t,e.latest,e.minSupported);return{current:t,latest:e.latest,minSupported:e.minSupported,severity:r,upgradeCmd:"npx -y mistflow-ai install",changelogUrl:e.changelogUrl,backendSignalReceived:Qe!==null}}var Jn,Qe,ps,bn=yt(()=>{"use strict";Jn=null;Qe=null;ps=!1});var kn={};fn(kn,{closeBrowser:()=>Fr,getIsolatedContext:()=>Jl,getPage:()=>Ur,getSnapshot:()=>wn,takeScreenshot:()=>vn});async function bs(){try{return await import("playwright")}catch{throw new Error("Playwright is not installed. Run: cd packages/mcp-server && pnpm add playwright && npx playwright install chromium")}}async function Gl(){let t=await bs();return(!Ze||!Ze.isConnected())&&(Ze=await t.chromium.launch({headless:!0})),bt&&(await bt.close().catch(()=>{}),bt=null),bt=await Ze.newContext({viewport:{width:1280,height:720},deviceScaleFactor:1}),dt=await bt.newPage(),dt}async function Ur(){return dt&&!dt.isClosed()?dt:(Vn||(Vn=Gl().finally(()=>{Vn=null})),Vn)}async function Fr(){dt&&!dt.isClosed()&&await dt.close().catch(()=>{}),bt&&await bt.close().catch(()=>{}),Ze?.isConnected()&&await Ze.close().catch(()=>{}),dt=null,bt=null,Ze=null}async function Jl(){let t=await bs();(!Ze||!Ze.isConnected())&&(Ze=await t.chromium.launch({headless:!0}));let e=await Ze.newContext({viewport:{width:1280,height:720},deviceScaleFactor:1}),r=await e.newPage();return{context:e,page:r}}async function wn(t){return await t.locator("body").ariaSnapshot()}async function vn(t,e=!1){return await t.screenshot({fullPage:e,type:"png"})}var Ze,bt,dt,Vn,Wt=yt(()=>{"use strict";Ze=null,bt=null,dt=null,Vn=null;process.once("SIGTERM",()=>{Fr().finally(()=>process.exit(0))});process.once("SIGINT",()=>{Fr().finally(()=>process.exit(0))})});import{readFileSync as qr,existsSync as Yn,writeFileSync as vs,mkdirSync as Yl,renameSync as ks,unlinkSync as xs}from"fs";import{join as xn,dirname as Ss}from"path";import{homedir as Ql}from"os";import{randomBytes as _s}from"crypto";function Ts(){return xn(Ql(),".mistflow","credentials.json")}function Qn(){return(process.env.MISTFLOW_API_URL||"https://api.mistflow.ai").replace(/\/+$/,"")}function Cs(){let t=Ts();if(!Yn(t))return null;try{let e=JSON.parse(qr(t,"utf-8"));return typeof e.apiKey=="string"?{[Xl]:e}:e}catch{return null}}function Et(){let t=process.env.MISTFLOW_API_KEY;if(t)return{ok:!0,creds:{apiKey:t,orgId:"",orgSlug:"env"}};let e=Cs();if(!e)return{ok:!1,reason:"missing"};let r=Qn(),n=e[r];return n&&typeof n.apiKey=="string"&&n.apiKey&&typeof n.orgId=="string"?{ok:!0,creds:n}:{ok:!1,reason:"missing"}}function Br(t){let e=Ts(),r=Ss(e);Yn(r)||Yl(r,{recursive:!0});let n=Cs()??{},o=Qn();n[o]=t;let s=xn(r,`.credentials.tmp.${_s(8).toString("hex")}`);try{vs(s,JSON.stringify(n,null,2)+`
23
+ `,{mode:384}),ks(s,e)}catch(i){try{xs(s)}catch{}throw i}}function Is(){let t=Et();return t.ok?t.creds:null}function Te(){return Et().ok}function ut(t){let e=xn(t,"mistflow.json");if(!Yn(e))return null;try{return JSON.parse(qr(e,"utf-8"))}catch{return null}}function Xn(t,e){let r=xn(t,"mistflow.json");if(Yn(r))try{let o={...JSON.parse(qr(r,"utf-8")),...e},s=xn(Ss(r),`.mistflow.json.tmp.${_s(8).toString("hex")}`);try{vs(s,JSON.stringify(o,null,2)+`
24
+ `,"utf-8"),ks(s,r)}catch(i){try{xs(s)}catch{}throw i}}catch{}}var Xl,Nt=yt(()=>{"use strict";Xl="https://api.mistflow.ai"});var Ms={};fn(Ms,{MistflowApiError:()=>Z,addDomain:()=>Kr,bindWorkspace:()=>_n,cancelSession:()=>bo,checkAuth:()=>rc,checkAuthDetailed:()=>Es,checkSubdomain:()=>er,checkToolGuard:()=>wo,createDeployment:()=>oc,createProject:()=>Mt,deleteEnvVar:()=>Zr,discoverDecisions:()=>ic,downloadImageryAsset:()=>Gr,downloadSource:()=>cc,downloadSourceWithToken:()=>Ns,errorFromResponse:()=>Jt,fetchAcceptanceCriteria:()=>Sn,fetchActiveSessionPlan:()=>vo,fetchAddon:()=>co,fetchDesignDirections:()=>tr,fetchDesignPick:()=>sc,fetchDesignRenders:()=>rr,fetchDoc:()=>mo,fetchDocsList:()=>po,fetchModule:()=>ho,fetchPlanConversation:()=>or,fetchProjectImagery:()=>Wr,fetchScaffold:()=>lo,fetchSessionNext:()=>Vt,fetchSessionPlanRevisions:()=>Os,fetchStepContext:()=>uo,forkTemplate:()=>fo,generatePlan:()=>sr,getAuthHeaders:()=>Ot,getBaseUrl:()=>Me,getDashboardUrl:()=>ec,getDbCredentials:()=>ac,getDeployLogs:()=>eo,getDeploymentStatus:()=>wt,getProject:()=>zr,getProjectErrors:()=>to,getSeedInfo:()=>lr,getSession:()=>cr,getSiteUrl:()=>Zl,getTemplate:()=>go,hasCredentialsOnDisk:()=>Te,hasLocalCredentials:()=>As,listCronJobLogs:()=>so,listCronJobs:()=>no,listDeployments:()=>Kt,listDomains:()=>vt,listEnvVars:()=>Qr,listSessionsForMachine:()=>Tn,markLocalSetupDone:()=>dc,modifyPlan:()=>ir,pingBackend:()=>Hr,promotePreview:()=>io,redeployProject:()=>lc,removeDomain:()=>Vr,request:()=>j,rollbackDeployment:()=>ao,setCronJobEnabled:()=>ro,shareProject:()=>yo,startSession:()=>uc,submitAcceptanceResults:()=>ko,submitDesignPick:()=>nr,submitSessionAnswers:()=>pc,triggerCronJobNow:()=>oo,uploadAndDeploy:()=>Jr,uploadQAResults:()=>Yr,upsertEnvVar:()=>Xr,verifyDomain:()=>ar});function Me(){return Qn()}function Zl(){let t=process.env.MISTFLOW_API_URL;if(t){if(t.includes("mistflow.localhost"))return t.replace("api.mistflow","mistflow");if(t.includes("localhost:9100"))return t.replace(":9100",":9102")}return"https://mistflow.ai"}function ec(){let t=process.env.MISTFLOW_API_URL;if(t){if(t.includes("mistflow.localhost"))return t.replace("api.mistflow","app.mistflow");if(t.includes("localhost:9100"))return t.replace(":9100",":9101")}return"https://app.mistflow.ai"}function Ot(){let t=Et();if(!t.ok)throw new Z("auth_missing","No Mistflow credentials found.",401);return tc(t.creds)}function tc(t){return{Authorization:`ApiKey ${t.apiKey}`,"Content-Type":"application/json","X-Mistflow-MCP-Version":Xe()}}function Gt(t){try{yn(t.headers)}catch{}}async function Hr(){try{let t=await fetch(`${Me()}/health`,{method:"GET",signal:AbortSignal.timeout(5e3)});Gt(t)}catch{}}async function Rs(t,e,r,n){for(let o=0;o<2;o++)try{return await fetch(t,{...e,signal:AbortSignal.timeout(r)})}catch(s){let i=s instanceof Error&&s.name==="TimeoutError",a=s instanceof TypeError;if(n&&o===0&&(i||a)){console.error(`[api] Retrying ${t} after ${i?"timeout":"network error"}`);continue}throw s}throw new Error("fetchWithRetry: exhausted retries")}async function Jt(t){let e=null;try{e=await t.json()}catch{e=null}let r=e&&typeof e.code=="string"?e.code:void 0,n=e&&typeof e.message=="string"?e.message:e&&typeof e.detail=="string"?e.detail:t.statusText||"Request failed";if(r)return new Z(r,n,t.status,e?.details);let o=t.status;return o===401?new Z("auth_invalid",n,o):o===403?new Z("permission_denied",n,o):o===404?new Z("not_found",n,o):o===409?new Z("conflict",n,o):o===422?new Z("validation_error",n,o):o===429?new Z("rate_limited",n,o):o>=500?new Z("server_error",t.statusText||"Internal server error",o):new Z("client_error",n,o)}async function j(t,e={}){let r=Ot(),{timeoutMs:n,idempotent:o,...s}=e,i=n??3e4,a=(s.method??"GET").toUpperCase(),l=o??(a==="GET"||a==="HEAD"),c;try{c=await Rs(`${Me()}${t}`,{...s,headers:{...r,...s.headers}},i,l)}catch(p){throw p instanceof Error&&p.name==="TimeoutError"?new Z("network_error","Request timed out. Try again in a moment."):new Z("network_error","Cannot reach Mistflow servers. Check your network.")}if(Gt(c),!c.ok)throw await Jt(c);return c.json()}function As(){return Et().ok}async function rc(){return(await Es()).ok}async function Es(){if(Zn!==null&&Date.now()<Ps)return Zn?{ok:!0}:{ok:!1,reason:"no_credentials"};if(!As())return{ok:!1,reason:"no_credentials"};try{return await j("/api/org"),Zn=!0,Ps=Date.now()+nc,{ok:!0}}catch(t){if(Zn=null,!(t instanceof Z))return{ok:!1,reason:"network_error"};switch(t.code){case"auth_missing":case"auth_revoked":return{ok:!1,reason:"no_credentials"};case"auth_expired":case"auth_invalid":case"auth_org_not_found":return{ok:!1,reason:"expired"};case"network_error":return{ok:!1,reason:"network_error"};case"rate_limited":case"server_error":case"upstream_error":return{ok:!1,reason:"server_error"};default:return{ok:!1,reason:"server_error"}}}}async function zr(t){return j(`/api/projects/${encodeURIComponent(t)}`)}async function er(t){return j(`/api/projects/check-subdomain?name=${encodeURIComponent(t)}`)}async function Mt(t,e={}){return j("/api/projects",{method:"POST",body:JSON.stringify({name:t,template:e.template,db_provider:e.dbProvider??"neon",requested_subdomain:e.requestedSubdomain,picked_direction:e.pickedDirection,imagery_brief:e.imageryBrief,plan_context:e.planContext,design_conversation_id:e.designConversationId,session_id:e.sessionId,scaffold_features:e.scaffoldFeatures})})}async function Wr(t){return j(`/api/projects/${encodeURIComponent(t)}/imagery`)}async function Gr(t){let e=await fetch(t);if(!e.ok)throw new Error(`Failed to download imagery asset: HTTP ${e.status} ${e.statusText}`);let r=await e.arrayBuffer();return Buffer.from(r)}async function oc(t,e){return j("/api/deploy",{method:"POST",body:JSON.stringify({project_id:t,build_output_url:e})})}async function Jr(t,e,r="production",n,o,s,i){let{readFileSync:a}=await import("fs"),{basename:l}=await import("path"),c=Et();if(!c.ok)throw new Z("auth_missing","No Mistflow credentials found.",401);let p=c.creds,d=a(e),m=new Blob([d],{type:"application/gzip"}),h=new FormData;if(h.append("project_id",t),h.append("build",m,l(e)),r!=="production"&&h.append("environment",r),n&&h.append("admin_email",n),o&&h.append("schema_pushed","true"),s){let{existsSync:y}=await import("fs");if(y(s)){let v=a(s),E=new Blob([v],{type:"application/gzip"});h.append("source",E,"source.tar.gz")}}i&&h.append("git_commit_sha",i);let S;try{S=await fetch(`${Me()}/api/deploy/upload`,{method:"POST",headers:{Authorization:`ApiKey ${p.apiKey}`,"X-Mistflow-MCP-Version":Xe()},body:h,signal:AbortSignal.timeout(3e5)})}catch{throw new Z("network_error","Cannot reach Mistflow servers. Check your network.")}if(Gt(S),!S.ok)throw await Jt(S);let I=await S.json();return{...I,id:I.deployment_id}}async function wt(t,e){let r=e?.waitSeconds??0,n=r>0?`?wait=${r}`:"",o=Math.min(6e4,(r+5)*1e3);return j(`/api/deploy/${encodeURIComponent(t)}/status${n}`,r>0?{timeoutMs:o}:void 0)}async function tr(t){return j(`/api/plan/design-directions/${encodeURIComponent(t)}`,{timeoutMs:15e3})}async function sc(t,e){let r=e?.waitSeconds??45,n=r>0?`?wait=${r}`:"",o=Math.min(6e4,(r+5)*1e3);return j(`/api/plan/design-directions/${encodeURIComponent(t)}/pick${n}`,{timeoutMs:o})}async function nr(t,e){return j(`/api/plan/design-directions/${encodeURIComponent(t)}/pick`,{method:"POST",body:JSON.stringify(e),timeoutMs:6e4})}async function rr(t){try{return await j(`/api/plan/design-directions/${encodeURIComponent(t)}/renders`,{timeoutMs:1e4})}catch{return{}}}async function or(t,e){let r=e?.waitSeconds??45,n=r>0?`?wait=${r}`:"",o=Math.min(6e4,(r+5)*1e3);return j(`/api/plan/conversations/${encodeURIComponent(t)}${n}`,{timeoutMs:o})}async function sr(t,e){let r={description:t,conversation_id:e?.conversationId,answers:e?.answers};if(e?.autonomous&&(r.autonomous=!0),e?.language&&e.language.toLowerCase()!=="english"&&(r.language=e.language),e?.designConversationId&&e?.designDirection){let o=e.designDirection,s=256,i=4e3,a=typeof o.id=="string"?o.id:void 0,l=typeof o.custom=="string"?o.custom:void 0,c=a&&a.length>0&&a.length<=s?a:void 0,p=(()=>{if(l&&l.length>0)return l.length<=i?l:l.slice(0,i)+" (truncated)";if(!c){let h=JSON.stringify(o);return h.length<=i?h:h.slice(0,i)+" (truncated)"}})();a&&!c&&console.error(`[mistflow] direction_id ${a.length} chars exceeds ${s}; falling back to custom`);let d=c?{direction_id:c}:{custom:p};e.conversationId&&(d.conversation_id=e.conversationId);let m=await nr(e.designConversationId,d);return{status:"ready",plan:m.plan??{},methodology:m.methodology??"",...m.designMd?{designMd:m.designMd}:{}}}let n=e?.conversationId||e?.answers||e?.designConversationId?18e4:6e4;return j("/api/plan",{method:"POST",body:JSON.stringify(r),timeoutMs:n,idempotent:!1})}async function ir(t,e,r={}){return j("/api/plan/modify",{method:"POST",body:JSON.stringify({existing_plan:t,modification:e,plan_md:r.planMd})})}async function ic(t){return j("/api/plan/discover",{method:"POST",body:JSON.stringify({description:t})})}async function Kr(t,e){return j(`/api/projects/${encodeURIComponent(t)}/domains`,{method:"POST",body:JSON.stringify({domain:e})})}async function vt(t){return j(`/api/projects/${encodeURIComponent(t)}/domains`)}async function ar(t,e){return j(`/api/projects/${encodeURIComponent(t)}/domains/${encodeURIComponent(e)}/verify`)}async function Vr(t,e){return j(`/api/projects/${encodeURIComponent(t)}/domains/${encodeURIComponent(e)}`,{method:"DELETE"})}async function ac(t){return j(`/api/projects/${encodeURIComponent(t)}/db-credentials`)}async function lr(t){try{return await j(`/api/projects/${encodeURIComponent(t)}/test-accounts`)}catch(e){return e instanceof Z&&(e.statusCode===404||e.code==="not_found")||console.error("[api] Failed to fetch seed info:",e instanceof Error?e.message:e),null}}async function Yr(t,e){try{return await j(`/api/deploy/${encodeURIComponent(t)}/qa-results`,{method:"POST",body:JSON.stringify(e)})}catch(r){return console.error("[api] Failed to upload QA results:",r instanceof Error?r.message:r),null}}async function Qr(t){return j(`/api/projects/${encodeURIComponent(t)}/env`)}async function Xr(t,e,r,n){return j(`/api/projects/${encodeURIComponent(t)}/env`,{method:"PUT",body:JSON.stringify({key:e,value:r,category:n?.category??"custom",description:n?.description,setup_url:n?.setupUrl})})}async function Zr(t,e){return j(`/api/projects/${encodeURIComponent(t)}/env/${encodeURIComponent(e)}`,{method:"DELETE"})}async function eo(t){return j(`/api/deploy/${encodeURIComponent(t)}/logs`)}async function to(t,e="7d"){return j(`/api/projects/${encodeURIComponent(t)}/errors?period=${encodeURIComponent(e)}`)}async function Kt(t){return j(`/api/projects/${encodeURIComponent(t)}/deployments`)}async function no(t){return j(`/api/projects/${encodeURIComponent(t)}/cron-jobs`)}async function ro(t,e,r){return j(`/api/projects/${encodeURIComponent(t)}/cron-jobs/${encodeURIComponent(e)}`,{method:"PATCH",body:JSON.stringify({enabled:r}),headers:{"Content-Type":"application/json"}})}async function oo(t,e){return j(`/api/projects/${encodeURIComponent(t)}/cron-jobs/${encodeURIComponent(e)}/trigger`,{method:"POST"})}async function so(t,e,r=50){return j(`/api/projects/${encodeURIComponent(t)}/cron-jobs/${encodeURIComponent(e)}/logs?limit=${r}`)}async function lc(t){return j(`/api/deploy/${encodeURIComponent(t)}/redeploy`,{method:"POST"})}async function io(t,e){let r=new URLSearchParams({preview_deployment_id:e});return j(`/api/deploy/${encodeURIComponent(t)}/promote`,{method:"POST",body:r.toString(),headers:{"Content-Type":"application/x-www-form-urlencoded"}})}async function ao(t){return j(`/api/deploy/${encodeURIComponent(t)}/rollback`,{method:"POST"})}async function cc(t,e){let r=Et();if(!r.ok)throw new Z("auth_missing","Not authenticated.",401);let n=r.creds,o=await fetch(`${Me()}/api/deploy/${encodeURIComponent(t)}/source`,{headers:{Authorization:`ApiKey ${n.apiKey}`,"X-Mistflow-MCP-Version":Xe()},signal:AbortSignal.timeout(12e4)});if(Gt(o),!o.ok)throw await Jt(o);let{writeFileSync:s}=await import("fs"),i=Buffer.from(await o.arrayBuffer());s(e,i)}async function jt(t,e){let{timeoutMs:r,idempotent:n,...o}=e??{},s=r??3e4,i=(o.method??"GET").toUpperCase(),a=n??(i==="GET"||i==="HEAD"),l;try{l=await Rs(`${Me()}${t}`,{headers:{"Content-Type":"application/json","X-Mistflow-MCP-Version":Xe()},...o},s,a)}catch(c){throw c instanceof Error&&c.name==="TimeoutError"?new Z("network_error","Request timed out. Try again in a moment."):new Z("network_error","Cannot reach Mistflow servers. Check your network.")}if(Gt(l),!l.ok)throw await Jt(l);return l.json()}async function lo(t="nextjs"){return jt(`/api/scaffold/${encodeURIComponent(t)}`)}async function co(t){return jt(`/api/scaffold/addon/${encodeURIComponent(t)}`)}async function uo(t,e){return jt(`/api/scaffold/${encodeURIComponent(t)}/context?step_type=${encodeURIComponent(e)}`)}async function po(t="nextjs",e){let r=e?`?kind=${encodeURIComponent(e)}`:"";return jt(`/api/scaffold/${encodeURIComponent(t)}/docs${r}`)}async function mo(t,e){return jt(`/api/scaffold/${encodeURIComponent(t)}/docs/${encodeURIComponent(e)}`)}async function ho(t,e,r,n){return jt(`/api/scaffold/${encodeURIComponent(t)}/module`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({type:"crud",entity:e,fields:r,entity_plural:n})})}async function go(t){return jt(`/api/templates/${encodeURIComponent(t)}`)}async function fo(t){return j(`/api/templates/${encodeURIComponent(t)}/fork`,{method:"POST"})}async function Ns(t,e,r){let n;try{n=await fetch(`${Me()}/api/deploy/${encodeURIComponent(t)}/fork-source?fork_token=${encodeURIComponent(e)}`,{headers:{"X-Mistflow-MCP-Version":Xe()},signal:AbortSignal.timeout(12e4)})}catch{throw new Z("network_error","Cannot reach Mistflow servers. Check your network.")}if(Gt(n),!n.ok)throw n.status===401?new Z("validation_error","Fork token expired or invalid. Re-open the project in the dashboard to get a fresh token.",n.status):await Jt(n);let{writeFileSync:o}=await import("fs"),s=Buffer.from(await n.arrayBuffer());o(r,s)}async function dc(t){await j(`/api/projects/${encodeURIComponent(t)}`,{method:"PATCH",body:JSON.stringify({local_setup_done:!0})})}async function yo(t,e){return j(`/api/projects/${encodeURIComponent(t)}/share`,{method:"POST",body:JSON.stringify({is_template:e?.isTemplate??!1,template_description:e?.description})})}async function uc(t){return j("/api/sessions/start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t),idempotent:!1})}async function cr(t){return j(`/api/sessions/${t}`)}async function bo(t,e){return j(`/api/sessions/${t}/cancel`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({reason:e??null}),idempotent:!0})}async function Vt(t){return j(`/api/sessions/${t}/next`)}async function pc(t,e){return j(`/api/sessions/${t}/answers`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({answers:e}),idempotent:!0})}async function wo(t,e){return j(`/api/sessions/${t}/can/${e}`)}async function Sn(t){return j(`/api/sessions/${t}/acceptance`)}async function Os(t){return j(`/api/sessions/${t}/revisions`)}async function vo(t){let e=await Os(t),r=[...e].reverse().find(n=>n.status==="active")??e.at(-1);return!r||!r.body||typeof r.body!="object"||Array.isArray(r.body)?null:{plan:r.body,planRevisionId:r.id}}async function _n(t,e){return j(`/api/sessions/${t}/workspace`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),idempotent:!0})}async function Tn(t,e={}){let r={machine_id:t};e.includeIdle&&(r.include_idle="true");let n=new URLSearchParams(r).toString();return j(`/api/sessions/me/workspaces?${n}`)}async function ko(t,e,r){return j(`/api/sessions/${t}/acceptance/results`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({results:e,plan_revision_id:r??null}),idempotent:!1})}var Z,Zn,Ps,nc,ke=yt(()=>{"use strict";Nt();bn();Z=class extends Error{constructor(r,n,o,s){super(n);this.code=r;this.statusCode=o;this.details=s;this.name="MistflowApiError"}code;statusCode;details;get isAuth(){return this.code.startsWith("auth_")}get isTransient(){return this.code==="server_error"||this.code==="upstream_error"||this.code==="network_error"||this.code==="rate_limited"}};Zn=null,Ps=0,nc=300*1e3});var Pn={};fn(Pn,{emptyState:()=>In,fetchRemoteState:()=>Cc,fuzzyMatch:()=>Pc,getLocalStatePath:()=>So,readLocalState:()=>Tc,syncRemoteState:()=>Ic,writeLocalState:()=>Cn});import{existsSync as Fs,readFileSync as kc,writeFileSync as xc,mkdirSync as Sc,renameSync as _c}from"fs";import{join as qs}from"path";function So(t){return qs(t,".mistflow","state.json")}function Tc(t){let e=So(t);if(!Fs(e))return null;try{return JSON.parse(kc(e,"utf-8"))}catch{return null}}function Cn(t,e){let r=qs(t,".mistflow");Fs(r)||Sc(r,{recursive:!0});let n=So(t),o=`${n}.${process.pid}.tmp`;xc(o,JSON.stringify(e,null,2)+`
25
+ `),_c(o,n)}function In(t,e){return{projectId:t,name:e,template:"",features:[],dbSchema:[],deployCount:0,decisions:[],provenance:[]}}async function Cc(t){let e;try{e=Ot()}catch{return null}try{let r=await fetch(`${Me()}/api/projects/${encodeURIComponent(t)}/state`,{headers:{...e,"Content-Type":"application/json","X-Mistflow-MCP-Version":Xe()}});try{yn(r.headers)}catch{}return r.ok?await r.json():null}catch{return null}}async function Ic(t,e){let r;try{r=Ot()}catch{return!1}try{let n=await fetch(`${Me()}/api/projects/${encodeURIComponent(t)}/state`,{method:"PUT",headers:{...r,"Content-Type":"application/json","X-Mistflow-MCP-Version":Xe()},body:JSON.stringify(e)});try{yn(n.headers)}catch{}return n.ok}catch{return!1}}function Pc(t,e){let r=t.toLowerCase(),n=e.toLowerCase();return n.includes(r)||r.includes(n)?!0:r.split(/\s+/).some(s=>s.length>=3&&n.includes(s))}var Dt=yt(()=>{"use strict";ke();bn()});var _o={};fn(_o,{ensureBackendRegistered:()=>jc,ensureShadcnComponents:()=>Dc});import{existsSync as Hs,readFileSync as Rc,writeFileSync as Ac}from"fs";import{join as dr}from"path";import{spawn as Ec}from"child_process";function Nc(t,e,r,n,o){return new Promise(s=>{let i=Ec(t,e,{cwd:r,stdio:["pipe","pipe","pipe"],timeout:n,...o?{env:{...process.env,...o}}:{}}),a="",l="";i.stdout?.on("data",c=>{a+=c.toString()}),i.stderr?.on("data",c=>{l+=c.toString()}),i.on("close",c=>s({success:c===0,stdout:a,stderr:l})),i.on("error",c=>s({success:!1,stdout:a,stderr:l+c.message}))})}function Oc(t){let e=dr(t,"mistflow.json");if(!Hs(e))return null;try{return JSON.parse(Rc(e,"utf-8"))}catch{return null}}function Mc(t,e){Ac(dr(t,"mistflow.json"),JSON.stringify(e,null,2))}async function Bs(t,e){try{let r=e.features,n=e.steps,o={plan:e};Array.isArray(r)&&r.length>0&&(o.features=r.map(s=>s.name)),Array.isArray(n)&&n.length>0&&(o.provenance=n.map(s=>({feature:s.name??s.title??`Step ${s.number??"?"}`,user_intent:(s.description??"").slice(0,500),decisions:"Seeded from plan",tradeoffs:"",files_affected:[]}))),await fetch(`${Me()}/api/projects/${encodeURIComponent(t)}/state`,{method:"PUT",headers:{...Ot(),"Content-Type":"application/json"},body:JSON.stringify(o)})}catch(r){console.error("[self-heal] state sync failed:",r instanceof Error?r.message:String(r))}}async function jc(t,e={}){let r=Oc(t);if(r){if(!Te())return r.projectId;if(!r.projectId)try{let o=r.plan?.requestedSubdomain,s=await Mt(r.name,{dbProvider:r.dbProvider??"neon",requestedSubdomain:o});return r.projectId=s.id,Mc(t,r),Cn(t,In(s.id,r.name)),r.plan&&await Bs(s.id,r.plan),console.error(`[self-heal] registered project ${s.id.slice(0,8)} with backend`),s.id}catch(n){console.error("[self-heal] createProject failed:",n instanceof Error?n.message:String(n));return}return e.forceSync&&r.plan&&r.projectId&&await Bs(r.projectId,r.plan),r.projectId}}async function Dc(t,e){let r=["button","card","input","label","form","dialog","table","dropdown-menu","badge","separator","skeleton","sheet","tabs","avatar","select","textarea","checkbox","switch","tooltip","popover","sonner"],n=e&&e.length>0?e:r,o=dr(t,"components","ui"),s=[],i=[];for(let c of n)Hs(dr(o,`${c}.tsx`))?s.push(c):i.push(c);if(i.length===0)return{installed:[],alreadyPresent:s};let a={NPM_CONFIG_LEGACY_PEER_DEPS:"true"},l=await Nc("npx",["--yes","shadcn@latest","add","-y","-o",...i],t,18e4,a);return l.success?{installed:i,alreadyPresent:s}:{installed:[],alreadyPresent:s,failed:`shadcn add failed for: ${i.join(", ")}. ${l.stderr.slice(-300)}`.trim()}}var To=yt(()=>{"use strict";ke();Dt()});var ht,Ci,Oo=yt(()=>{"use strict";ht={slug:"agent-ui",version:"1.0.0",description:"Mistflow agent UX primitives. Rich tool cards with streaming logs, approval gates for risky actions, cost badges, plan checklists, contextual suggestions.",files:[{src:"src/components/ApprovalGate.tsx",dst:"components/agent/ApprovalGate.tsx",version:"1.0.0",skipIfExists:!1,sourceHash:"sha256:f0622a2873f633683bae7e25f3b62b0b4fc49c7b01601af3009001d36c773273"},{src:"src/components/ErrorBoundary.tsx",dst:"components/agent/ErrorBoundary.tsx",version:"1.0.0",skipIfExists:!1,sourceHash:"sha256:b638e1163a807780fe937be5e267a8379e8fe1afb661f44cb4582089b1808392"},{src:"src/components/CostBadge.tsx",dst:"components/agent/CostBadge.tsx",version:"1.0.0",skipIfExists:!1,sourceHash:"sha256:5d2d368e07cfac7025f69b9de87c7402a0d9fdb71d0b2c3fa85138831c918507"},{src:"src/components/PlanSteps.tsx",dst:"components/agent/PlanSteps.tsx",version:"1.0.0",skipIfExists:!1,sourceHash:"sha256:116a8a01c43e27a2c862d8b1218c6b44bc7406aa59c6784612f1d45993f9a3db"},{src:"src/components/SuggestedPrompts.tsx",dst:"components/agent/SuggestedPrompts.tsx",version:"1.0.0",skipIfExists:!1,sourceHash:"sha256:fe6eaea62a2d9c0b74f4cb6f7db3fe6673b647b2d1fc8a640624828cbebe45d7"},{src:"src/components/ToolStream.tsx",dst:"components/agent/ToolStream.tsx",version:"1.0.0",skipIfExists:!1,sourceHash:"sha256:a9c2763830fc13a544a1ef6f6b1f7445362ee9e1915408b85088827e3b1899c5"},{src:"src/lib/cn.ts",dst:"lib/cn.ts",version:"1.0.0",skipIfExists:!0,sourceHash:"sha256:39c7c9fe53788ae5992a1027e1d440e8b8c9d4af61500aa847cb173432c65603"},{src:"src/lib/agent-types.ts",dst:"lib/agent-types.ts",version:"1.0.0",skipIfExists:!1,sourceHash:"sha256:3efa0340d6d3a21524a4693d3bdfc8ee029cf3779616d1c81ea35b2ce76f2744"}],dependencies:{"framer-motion":"^11.18.0",recharts:"^2.15.0",clsx:"^2.1.1","tailwind-merge":"^2.5.0"},postInstallNote:"Components written under components/agent/. See methodologies/agent-ui-components.md for the API and an integration recipe."},Ci={"src/components/ApprovalGate.tsx":`/** @mistflow-component approval-gate
26
26
  * @version 1.0.0
27
27
  * @source packages/agent-ui-components/src/components/ApprovalGate.tsx
28
28
  *
@@ -842,10 +842,10 @@ export type AgentEvent =
842
842
  | { kind: 'approval-resolved'; approvalId: string }
843
843
  | { kind: 'suggestions'; prompts: { id: string; icon: string; text: string }[] }
844
844
  | { kind: 'done' }
845
- `}});var xi={};gn(xi,{installAgentUi:()=>bo,planRequiresAgentUiInstall:()=>yo,readComponentsInstalledLedger:()=>Mr,selfHealAgentUi:()=>Ld});import{existsSync as Nr,mkdirSync as wi,readFileSync as vi,writeFileSync as Or}from"fs";import{join as fo,dirname as Ad}from"path";import{createHash as Ed}from"crypto";function Nd(t){return`sha256:${Ed("sha256").update(t).digest("hex")}`}function Od(t,e,o){let n=[];for(let r of t.files){let s=e[r.src];if(typeof s!="string")throw new Error(`bundle is corrupt: missing content for ${r.src}. Run 'pnpm --filter @mistflow-ai/agent-ui-components run generate-bundle'.`);let i=fo(o,r.dst);r.skipIfExists&&Nr(i)||(wi(Ad(i),{recursive:!0}),Or(i,s),n.push({path:r.dst,sourceVersion:r.version,sourceHash:r.sourceHash,installedHash:Nd(s)}))}return n}function Md(t,e){let o=[],n=[];if(!Nr(t))return{added:o,skipped:n};let r=JSON.parse(vi(t,"utf8"));r.dependencies??={};for(let[s,i]of Object.entries(e)){if(r.dependencies[s]||r.devDependencies?.[s]){n.push(s);continue}r.dependencies[s]=i,o.push(s)}return Or(t,JSON.stringify(r,null,2)+`
846
- `),{added:o,skipped:n}}function ki(t){return fo(t,".mistflow","components-installed.json")}function Mr(t){let e=ki(t);if(!Nr(e))return{presets:{}};try{let o=vi(e,"utf8");return{presets:JSON.parse(o)?.presets??{}}}catch{return{presets:{}}}}function jd(t,e){let o=fo(t,".mistflow");wi(o,{recursive:!0}),Or(ki(t),JSON.stringify(e,null,2)+`
847
- `)}function Dd(t,e){return t?(Array.isArray(t.integrations)?t.integrations:[]).some(n=>{if(typeof n=="string")return n===e;if(n&&typeof n=="object"){let r=n;return r.id===e||r.slug===e||r.name===e}return!1}):!1}function yo(t,e){return Dd(t,"agent-ui")?!Mr(e).presets["agent-ui"]:!1}function bo(t,e=new Date){let o=Od(ct,gi,t),{added:n,skipped:r}=Md(fo(t,"package.json"),ct.dependencies),s=Mr(t);return s.presets[ct.slug]={presetVersion:ct.version,installedAt:e.toISOString(),files:o},jd(t,s),{slug:ct.slug,installed:o,depsAdded:n,depsSkipped:r}}function Ld(t,e){if(!yo(t,e))return null;try{let o=bo(e),n=o.depsAdded.length?`, +${o.depsAdded.length} deps`:"";return{message:`installed agent-ui preset (${o.installed.length} files${n})`,isError:!1}}catch(o){return{message:`agent-ui install failed: ${o instanceof Error?o.message:String(o)}`,isError:!0}}}var jr=ht(()=>{"use strict";Er()});import{Server as yh}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as bh}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as wh,ListToolsRequestSchema as vh}from"@modelcontextprotocol/sdk/types.js";import{zodToJsonSchema as kh}from"zod-to-json-schema";var yl="mist_plan uses the fire-and-poll shape: the first call returns quickly with status 'running' and a conversationId while the backend Sonnet ask_questions call runs in the background (that call alone can take 30-150s). Poll by calling mist_plan with just { conversationId } every ~10-15s until status is 'clarify' (questions arrived) or 'ready' (plan generated). Do NOT re-send description or answers while polling \u2014 the conversation is already tracked server-side.",bl='Treat the tool\'s returned JSON as authoritative. If `mist_plan` returns `status: "clarify"`, the planning call completed and is waiting on user answers. Do NOT rerun the same call or describe it as "still generating". Only poll again when `status: "design_clarify_pending"`.',wl="When submitting discovery answers back to `mist_plan` (conversationId + answers), pass `answers` as an ARRAY of objects directly: `answers: [{question, decisionKey, answer}, ...]`. Do NOT JSON-stringify the array \u2014 pass the raw array literal. Do NOT collapse into a `{decisionKey: answer}` map \u2014 multiple discovery questions can share the same decision key.",vl="Before scaffolding a NEW app, resolve the destination path explicitly. If the user asked to create/build a new app and the current directory is not already that Mistflow project, ask whether to scaffold in the current folder or a new subfolder here (unless the user already gave a path). Do NOT inspect sibling directories and silently adopt one as the base just because it looks similar. Reuse an existing Mistflow project only when the user explicitly asks to continue/edit that project.",kl='Once the user approves the plan, the mist_init \u2192 mist_install \u2192 mist_implement (repeat) \u2192 mist_build \u2192 mist_deploy \u2192 mist_qa chain is expected. Do NOT pause between these calls to ask the user "should I continue?" or offer options like "full build vs step-by-step." A tool returns a status that requires user input when it genuinely needs one (e.g. confirm_new_project, awaiting promotion). Otherwise chain calls continuously. Brief one-line status updates are fine; permission requests are not.',xl="mist_init is all-or-nothing for normal users: it registers the cloud project first, scaffolds in a temp directory, then moves the finished app into place only after both steps succeed. If auth, quota, or network registration fails, stop before any app appears locally. Do NOT suggest reusing another app unless the user explicitly asks.",Sl="If mist_init fails, stop before any cloud command and explain why. Do NOT default to reusing some other existing app as recovery unless the user explicitly asked to continue that app.",_l='Long-running tools (mist_plan, mist_install, mist_build, mist_qa, mist_deploy) use the fire-and-poll pattern. The first call returns `status: "running"` with a `jobId` or `conversationId`. Every subsequent call with that same id returns the current state \u2014 re-call IMMEDIATELY whenever you see `status: "running"`. DO NOT run `bash sleep`, `setTimeout`, or any wall-clock wait between polls \u2014 Claude Code\'s harness blocks standalone sleeps, and every MCP server poll endpoint holds the connection up to ~10-15s on its side already, returning as soon as state changes. Just call the tool again. Each response is well inside the 60s MCP ceiling. When `status: "complete"` or `"failed"` arrives, the tool is done. Never assume a job stalled because a single call took 10s \u2014 calls return fast, but the work keeps running in the background. Do not ask the user whether to keep polling; polling is how these tools work.',Cl="When `mist_plan` returns a `questions[]` array (or `directions[]` for design picking), you MUST ask the user and WAIT for their actual answer before submitting anything back. Use your host's native structured-question UI: `AskUserQuestion` in Claude Code, quick pick in Cursor, `request_user_input` in OpenAI Codex (available in Plan mode \u2014 if Codex returns 'request_user_input is unavailable in <mode> mode', you are in default mode; stop your turn and print the questions as a numbered chat message, then wait for the user's next message). For any host that does not expose a structured question tool, stop your turn immediately, print the questions verbatim as a numbered list with all options visible, and wait \u2014 the user's next message is the answer. The rule across every host: you never submit answers to `mist_plan` in the same turn you received the questions. That is always wrong, regardless of how obvious the `recommended` choice looks, regardless of whether you are inside `/loop`, an autonomous agent run, a scheduled wake-up, or any other non-interactive mode. The `recommended` field is a hint FOR the user, not permission FOR you. Anti-example from a real transcript: the host AI received 7 discovery questions, wrote 'I'll lock in the defaults with Azure OpenAI as the only override,' submitted answers the user never saw, and the user ended up with a plan they didn't choose. That behavior is a regression bug. Each question has `id`/`decisionKey`/`question`/`options[]` (each option: `label`, `description`) plus an optional `recommended` string. Collect the user's actual selections, then submit them back via `mist_plan` with the `conversationId` \u2014 don't ask the user to perform that submission step; that is your job.",Qr="Connect the user's Mistflow account. Call this ONLY when: (a) the user has literally never signed in, or (b) a previous tool call returned error code 'auth_missing' or 'auth_revoked'. DO NOT call this tool in response to 500 errors, 404 errors, network errors, or any generic failure \u2014 those are backend issues, not auth issues. Running mist_setup when not needed wastes the user's time and creates login fatigue. Once signed in, the MCP persists a long-lived API key and never asks again. Two-phase device code flow: (1) Call without deviceCode \u2014 opens browser, returns status 'pending' with deviceCode and userCode. Tell the user the verification code and that they need to approve in the browser. (2) Call again with deviceCode after ~15 seconds \u2014 polls for approval. Also accepts an apiKey for headless auth (skips device code entirely).",Xr="Read or inspect Mistflow project state. 'get' (default) loads plan progress, env vars, and deploy info \u2014 call before editing. 'update' marks plan steps complete or adds env vars. 'share' creates a forkable template URL. 'integrations' browses the curated integration blueprint catalog (pass integrationId for full details). 'errors' fetches runtime errors from the deployed app. 'logs' fetches deploy logs. 'deployments' lists history. 'version' reports the installed MCP version + any upgrade available.",Zr="Unified browser tool for navigating, interacting with, and capturing the app. Use 'navigate' to open a URL, interaction actions (click/type/fill/etc.) to test flows, 'snapshot' to inspect the accessibility tree, and 'screenshot' for a visual capture. Use after mist_preview to verify UI, test flows, and iterate on design.",es="Returns the full Mistflow MCP tool reference. Call ONCE at session start (or whenever unsure which tool to pick) to learn the 14-tool surface, the fire-and-poll pattern, and how to chain end-to-end from mist_plan through mist_qa. Static \u2014 no backend round-trip, safe to call frequently.",ts="Fetch a per-topic Mistflow methodology doc on demand. Two actions: `list` returns the catalog (id, title, kind, description) so you can discover what's available; `get` returns the full markdown for one topic. Topic kinds: 'stack' (framework patterns \u2014 Drizzle, Better Auth, server actions, components, styling), 'skill' (design + structural patterns \u2014 auth-design, app-design, admin-panel, blob-storage), 'integration' (third-party services \u2014 Stripe, Resend, OpenAI, Anthropic, Twilio, etc.). Call BEFORE writing or modifying code that touches an integration or a stack-specific pattern. Lazy-loaded \u2014 pulls only the topic you need instead of paying for the full AGENTS.md monolith every turn. Same content as AGENTS.md / .claude/skills/<topic>/SKILL.md / .cursor/rules/<topic>.mdc on disk; use this tool when those aren't auto-discovered by your host.",ns="Analyze build errors from a Next.js / TypeScript project. Extracts structured errors with file, line, a human-readable message for the user, and an actionable suggestion. Call with { projectPath } to run `npm run build` and parse the failure; call with { buildOutput } to parse output captured elsewhere (e.g. from a failed mist_build job).",os="Generate a grayscale wireframe sketch of the planned app so the user can review layout + information hierarchy before any code is written. The server returns a structured spec + a wireframePrompt \u2014 the host AI writes the HTML file to .mistflow/mockups/mockup-<planId>.html and opens it. Pass { planId } for the first iteration, { planId, feedback: '...' } to iterate, { planId, approved: true } to lock the design before mist_init. Iterative by design \u2014 expect 1-3 rounds of feedback.";function rs(){let t=[];return t.push("Mistflow is a full-stack app builder that creates and deploys web apps from natural language descriptions. When a user asks to build, create, or make a web app, website, landing page, dashboard, internal tool, marketplace, content site, or browser game, use Mistflow tools. Mistflow creates NEW apps from scratch. It does NOT modify existing non-Mistflow codebases."),t.push(""),t.push('Every capability is an MCP tool. There is no companion CLI. Long-running work (install, build, qa, deploy) uses the fire-and-poll pattern: the first call returns a jobId and `status: "running"`; subsequent calls with that jobId return fresh state. Each call responds in under a second \u2014 no 60s timeout risk.'),t.push(""),t.push(_l),t.push(""),t.push(yl),t.push(""),t.push(bl),t.push(""),t.push(wl),t.push(""),t.push(vl),t.push(""),t.push(Cl),t.push(""),t.push(xl),t.push(""),t.push("New app workflow:"),t.push("1. Choose destination: if the user didn't already give a path, ask whether to scaffold in the current folder or a new subfolder here. Resolve the absolute path before mist_init."),t.push('2. Plan: call mist_plan with the user\'s description EXACTLY as written \u2014 do NOT expand or add features. When the response has `status: "clarify"` with `questions[]`, render them via your host\'s native question tool (AskUserQuestion in Claude Code / request_user_input in Codex Plan mode / quick pick in Cursor / stop-and-wait for hosts without one \u2014 see RULE_SENTINEL_HANDLING), collect the user\'s actual answers, then submit them back in the next mist_plan call with the `conversationId`. When `status: "design_clarify_pending"`, poll until directions are ready, present them to the user, submit the pick via mist_plan with `designConversationId` + `designDirection` (NOT `conversationId` alone).'),t.push("3. Mockup (optional, recommended): call mist_mockup with the planId \u2014 returns a wireframe prompt. Write the HTML to the returned path, open it for the user, wait for approval. Iterate with { feedback }, lock with { approved: true }."),t.push("4. Scaffold: mist_init with { planId, path }. Transactional \u2014 if it fails before the final move, stop."),t.push("5. Install: mist_install { projectPath } starts; poll with { jobId } until complete. Typical 30\u201390s."),t.push("6. Implement: mist_implement runs one plan step at a time and auto-marks the prior step complete. Call repeatedly until all steps are done."),t.push("7. Build: mist_build { projectPath } starts; poll with { jobId }. On failure, the response has `missingModules[]` \u2014 chain mist_install with those modules then mist_build again without asking."),t.push("8. Deploy: mist_deploy { projectPath } starts tar + upload; poll with { action: 'status', deploymentId }. On complete, response has `qaRequired: true` and a `url` \u2014 do NOT surface the URL to the user until mist_qa passes. Subsequent actions: 'promote' (staging \u2192 prod), 'rollback' (revert to a specific deploymentId)."),t.push("9. QA: mist_qa { projectPath } drives Playwright in-process against the live URL \u2014 single synchronous call (~20\u201345s), returns pass/fail and screenshots inline. Only surface the deploy URL to the user after mist_qa returns status: 'pass'."),t.push(""),t.push(kl),t.push(""),t.push(Sl),t.push(""),t.push("Updating an existing Mistflow app:"),t.push("- Cosmetic or single-file changes: edit files directly, then mist_build \u2192 mist_deploy."),t.push("- New page or feature (no new data model): mist_project action='get' for context, build it, then mist_build \u2192 mist_deploy."),t.push("- Feature needing new data model: ask product questions, call mist_project action='get' for context, call mist_plan with existingPlanId, then mist_implement, then mist_build \u2192 mist_deploy."),t.push("- Integration addition: ask product questions, mist_project action='get', mist_plan with existingPlanId, mist_implement, mist_config resource='env' for keys, mist_build \u2192 mist_deploy \u2192 mist_qa."),t.push("- Bug fix: mist_debug to extract structured errors, fix the code, mist_build \u2192 mist_deploy."),t.push(""),t.push("Tool summary:"),t.push("- mist_setup: authentication. Only call when the user has never signed in or a tool returned 'auth_missing'/'auth_revoked'. Do NOT call for 500s, 404s, or network errors. Also accepts an apiKey parameter for headless auth."),t.push("- mist_project: read/write project state (actions: 'get' / 'update'). Call 'get' before editing any existing Mistflow project to understand its shape."),t.push("- mist_browser: navigate, interact with, and screenshot the app during preview or after deploy. Returns screenshots inline in tool results."),t.push("- mist_help: returns the full tool reference. Call once per session."),t.push("- mist_plan / mist_mockup / mist_init / mist_implement / mist_debug / mist_config: core app-building workflow tools (see step-by-step above)."),t.push("- mist_install / mist_build / mist_deploy: fire-and-poll tools. Always check `status` in the response and keep polling while 'running'."),t.push("- mist_qa: synchronous single call. Drives Playwright in-process against the deployed URL. No jobId / no polling \u2014 one call returns the full QA result with screenshots."),t.join(`
848
- `)}yn();function d(t,e=!1){let o=t;try{let n=us();n&&(o=t+n)}catch{}return{content:[{type:"text",text:o}],isError:e}}function Oe(t){let e={status:"setup_required",reason:"no_credentials",intent:t,instruction:`You must sign in to Mistflow before you can ${t}. Call mist_setup({}) \u2014 it walks the user through device-code authentication (typically <60s). Only retry the original call AFTER mist_setup has fully completed sign-in (status field is no longer "pending").`,nextAction:`Step 1: Call mist_setup({}) now. Do not ask the user for permission first \u2014 sign-in is a precondition for this flow, not a decision point. Step 2: mist_setup returns immediately with a signInUrl + userCode. Show the URL and code to the user and wait for them to approve in their browser. Step 3: If the response had status: "pending", call mist_setup again with the returned deviceCode every ~15s until it returns a non-pending status. Step 4: Only then retry the call that produced this response with the same arguments. Do NOT retry while mist_setup is still pending \u2014 the credentials aren't on disk yet and you'll loop right back into this same response.`};return d(JSON.stringify(e),!0)}function Ve(t){return d(`This is not a Mistflow project (no mistflow.json found at ${t}).
845
+ `}});var Oi={};fn(Oi,{installAgentUi:()=>br,planRequiresAgentUiInstall:()=>yr,readComponentsInstalledLedger:()=>Do,selfHealAgentUi:()=>Wd});import{existsSync as Mo,mkdirSync as Ai,readFileSync as Ei,writeFileSync as jo}from"fs";import{join as fr,dirname as $d}from"path";import{createHash as Ud}from"crypto";function Fd(t){return`sha256:${Ud("sha256").update(t).digest("hex")}`}function qd(t,e,r){let n=[];for(let o of t.files){let s=e[o.src];if(typeof s!="string")throw new Error(`bundle is corrupt: missing content for ${o.src}. Run 'pnpm --filter @mistflow-ai/agent-ui-components run generate-bundle'.`);let i=fr(r,o.dst);o.skipIfExists&&Mo(i)||(Ai($d(i),{recursive:!0}),jo(i,s),n.push({path:o.dst,sourceVersion:o.version,sourceHash:o.sourceHash,installedHash:Fd(s)}))}return n}function Bd(t,e){let r=[],n=[];if(!Mo(t))return{added:r,skipped:n};let o=JSON.parse(Ei(t,"utf8"));o.dependencies??={};for(let[s,i]of Object.entries(e)){if(o.dependencies[s]||o.devDependencies?.[s]){n.push(s);continue}o.dependencies[s]=i,r.push(s)}return jo(t,JSON.stringify(o,null,2)+`
846
+ `),{added:r,skipped:n}}function Ni(t){return fr(t,".mistflow","components-installed.json")}function Do(t){let e=Ni(t);if(!Mo(e))return{presets:{}};try{let r=Ei(e,"utf8");return{presets:JSON.parse(r)?.presets??{}}}catch{return{presets:{}}}}function Hd(t,e){let r=fr(t,".mistflow");Ai(r,{recursive:!0}),jo(Ni(t),JSON.stringify(e,null,2)+`
847
+ `)}function zd(t,e){return t?(Array.isArray(t.integrations)?t.integrations:[]).some(n=>{if(typeof n=="string")return n===e;if(n&&typeof n=="object"){let o=n;return o.id===e||o.slug===e||o.name===e}return!1}):!1}function yr(t,e){return zd(t,"agent-ui")?!Do(e).presets["agent-ui"]:!1}function br(t,e=new Date){let r=qd(ht,Ci,t),{added:n,skipped:o}=Bd(fr(t,"package.json"),ht.dependencies),s=Do(t);return s.presets[ht.slug]={presetVersion:ht.version,installedAt:e.toISOString(),files:r},Hd(t,s),{slug:ht.slug,installed:r,depsAdded:n,depsSkipped:o}}function Wd(t,e){if(!yr(t,e))return null;try{let r=br(e),n=r.depsAdded.length?`, +${r.depsAdded.length} deps`:"";return{message:`installed agent-ui preset (${r.installed.length} files${n})`,isError:!1}}catch(r){return{message:`agent-ui install failed: ${r instanceof Error?r.message:String(r)}`,isError:!0}}}var Lo=yt(()=>{"use strict";Oo()});import{Server as $h}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as Uh}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as Fh,ListToolsRequestSchema as qh}from"@modelcontextprotocol/sdk/types.js";import{zodToJsonSchema as Bh}from"zod-to-json-schema";var Al="mist_plan uses the fire-and-poll shape: the first call returns quickly with status 'running' and a conversationId while the backend Sonnet ask_questions call runs in the background (that call alone can take 30-150s). Poll by calling mist_plan with just { conversationId } every ~10-15s until status is 'clarify' (questions arrived) or 'ready' (plan generated). Do NOT re-send description or answers while polling \u2014 the conversation is already tracked server-side.",El='Treat the tool\'s returned JSON as authoritative. If `mist_plan` returns `status: "clarify"`, the planning call completed and is waiting on user answers. Do NOT rerun the same call or describe it as "still generating". Only poll again when `status: "design_clarify_pending"`.',Nl="When submitting discovery answers back to `mist_plan` (conversationId + answers), pass `answers` as an ARRAY of objects directly: `answers: [{question, decisionKey, answer}, ...]`. Do NOT JSON-stringify the array \u2014 pass the raw array literal. Do NOT collapse into a `{decisionKey: answer}` map \u2014 multiple discovery questions can share the same decision key.",Ol="Before scaffolding a NEW app, resolve the destination path explicitly. If the user asked to create/build a new app and the current directory is not already that Mistflow project, ask whether to scaffold in the current folder or a new subfolder here (unless the user already gave a path). Do NOT inspect sibling directories and silently adopt one as the base just because it looks similar. Reuse an existing Mistflow project only when the user explicitly asks to continue/edit that project.",Ml='Once the user approves the plan, the mist_init \u2192 mist_install \u2192 mist_implement (repeat) \u2192 mist_build \u2192 mist_deploy \u2192 mist_qa chain is expected. Do NOT pause between these calls to ask the user "should I continue?" or offer options like "full build vs step-by-step." A tool returns a status that requires user input when it genuinely needs one (e.g. confirm_new_project, awaiting promotion). Otherwise chain calls continuously. Brief one-line status updates are fine; permission requests are not.',jl="mist_init is all-or-nothing for normal users: it registers the cloud project first, scaffolds in a temp directory, then moves the finished app into place only after both steps succeed. If auth, quota, or network registration fails, stop before any app appears locally. Do NOT suggest reusing another app unless the user explicitly asks.",Dl="If mist_init fails, stop before any cloud command and explain why. Do NOT default to reusing some other existing app as recovery unless the user explicitly asked to continue that app.",Ll='Long-running tools (mist_plan, mist_install, mist_build, mist_qa, mist_deploy) use the fire-and-poll pattern. The first call returns `status: "running"` with a `jobId` or `conversationId`. Every subsequent call with that same id returns the current state \u2014 re-call IMMEDIATELY whenever you see `status: "running"`. DO NOT run `bash sleep`, `setTimeout`, or any wall-clock wait between polls \u2014 Claude Code\'s harness blocks standalone sleeps, and every MCP server poll endpoint holds the connection up to ~10-15s on its side already, returning as soon as state changes. Just call the tool again. Each response is well inside the 60s MCP ceiling. When `status: "complete"` or `"failed"` arrives, the tool is done. Never assume a job stalled because a single call took 10s \u2014 calls return fast, but the work keeps running in the background. Do not ask the user whether to keep polling; polling is how these tools work.',$l="When `mist_plan` returns a `questions[]` array (or `directions[]` for design picking), you MUST ask the user and WAIT for their actual answer before submitting anything back. Use your host's native structured-question UI: `AskUserQuestion` in Claude Code, quick pick in Cursor, `request_user_input` in OpenAI Codex (available in Plan mode \u2014 if Codex returns 'request_user_input is unavailable in <mode> mode', you are in default mode; stop your turn and print the questions as a numbered chat message, then wait for the user's next message). For any host that does not expose a structured question tool, stop your turn immediately, print the questions verbatim as a numbered list with all options visible, and wait \u2014 the user's next message is the answer. The rule across every host: you never submit answers to `mist_plan` in the same turn you received the questions. That is always wrong, regardless of how obvious the `recommended` choice looks, regardless of whether you are inside `/loop`, an autonomous agent run, a scheduled wake-up, or any other non-interactive mode. The `recommended` field is a hint FOR the user, not permission FOR you. Anti-example from a real transcript: the host AI received 7 discovery questions, wrote 'I'll lock in the defaults with Azure OpenAI as the only override,' submitted answers the user never saw, and the user ended up with a plan they didn't choose. That behavior is a regression bug. Each question has `id`/`decisionKey`/`question`/`options[]` (each option: `label`, `description`) plus an optional `recommended` string. Collect the user's actual selections, then submit them back via `mist_plan` with the `conversationId` \u2014 don't ask the user to perform that submission step; that is your job.",rs="Connect the user's Mistflow account. Call this ONLY when: (a) the user has literally never signed in, or (b) a previous tool call returned error code 'auth_missing' or 'auth_revoked'. DO NOT call this tool in response to 500 errors, 404 errors, network errors, or any generic failure \u2014 those are backend issues, not auth issues. Running mist_setup when not needed wastes the user's time and creates login fatigue. Once signed in, the MCP persists a long-lived API key and never asks again. Two-phase device code flow: (1) Call without deviceCode \u2014 opens browser, returns status 'pending' with deviceCode and userCode. Tell the user the verification code and that they need to approve in the browser. (2) Call again with deviceCode after ~15 seconds \u2014 polls for approval. Also accepts an apiKey for headless auth (skips device code entirely).",os="Read or inspect Mistflow project state. 'get' (default) loads plan progress, env vars, and deploy info \u2014 call before editing. 'update' marks plan steps complete or adds env vars. 'share' creates a forkable template URL. 'integrations' browses the curated integration blueprint catalog (pass integrationId for full details). 'errors' fetches runtime errors from the deployed app. 'logs' fetches deploy logs. 'deployments' lists history. 'version' reports the installed MCP version + any upgrade available.",ss="Unified browser tool for navigating, interacting with, and capturing the app. Use 'navigate' to open a URL, interaction actions (click/type/fill/etc.) to test flows, 'snapshot' to inspect the accessibility tree, and 'screenshot' for a visual capture. Use after mist_preview to verify UI, test flows, and iterate on design.",is="Returns the full Mistflow MCP tool reference. Call ONCE at session start (or whenever unsure which tool to pick) to learn the 14-tool surface, the fire-and-poll pattern, and how to chain end-to-end from mist_plan through mist_qa. Static \u2014 no backend round-trip, safe to call frequently.",as="Fetch a per-topic Mistflow methodology doc on demand. Two actions: `list` returns the catalog (id, title, kind, description) so you can discover what's available; `get` returns the full markdown for one topic. Topic kinds: 'stack' (framework patterns \u2014 Drizzle, Better Auth, server actions, components, styling), 'skill' (design + structural patterns \u2014 auth-design, app-design, admin-panel, blob-storage), 'integration' (third-party services \u2014 Stripe, Resend, OpenAI, Anthropic, Twilio, etc.). Call BEFORE writing or modifying code that touches an integration or a stack-specific pattern. Lazy-loaded \u2014 pulls only the topic you need instead of paying for the full AGENTS.md monolith every turn. Same content as AGENTS.md / .claude/skills/<topic>/SKILL.md / .cursor/rules/<topic>.mdc on disk; use this tool when those aren't auto-discovered by your host.",ls="Analyze build errors from a Next.js / TypeScript project. Extracts structured errors with file, line, a human-readable message for the user, and an actionable suggestion. Call with { projectPath } to run `npm run build` and parse the failure; call with { buildOutput } to parse output captured elsewhere (e.g. from a failed mist_build job).",cs="Generate a grayscale wireframe sketch of the planned app so the user can review layout + information hierarchy before any code is written. The server returns a structured spec + a wireframePrompt \u2014 the host AI writes the HTML file to .mistflow/mockups/mockup-<planId>.html and opens it. Pass { planId } for the first iteration, { planId, feedback: '...' } to iterate, { planId, approved: true } to lock the design before mist_init. Iterative by design \u2014 expect 1-3 rounds of feedback.";function ds(){let t=[];return t.push("Mistflow is a full-stack app builder that creates and deploys web apps from natural language descriptions. When a user asks to build, create, or make a web app, website, landing page, dashboard, internal tool, marketplace, content site, or browser game, use Mistflow tools. Mistflow creates NEW apps from scratch. It does NOT modify existing non-Mistflow codebases."),t.push(""),t.push('Every capability is an MCP tool. There is no companion CLI. Long-running work (install, build, qa, deploy) uses the fire-and-poll pattern: the first call returns a jobId and `status: "running"`; subsequent calls with that jobId return fresh state. Each call responds in under a second \u2014 no 60s timeout risk.'),t.push(""),t.push(Ll),t.push(""),t.push(Al),t.push(""),t.push(El),t.push(""),t.push(Nl),t.push(""),t.push(Ol),t.push(""),t.push($l),t.push(""),t.push(jl),t.push(""),t.push("New app workflow:"),t.push("1. Choose destination: if the user didn't already give a path, ask whether to scaffold in the current folder or a new subfolder here. Resolve the absolute path before mist_init."),t.push('2. Plan: call mist_plan with the user\'s description EXACTLY as written \u2014 do NOT expand or add features. When the response has `status: "clarify"` with `questions[]`, render them via your host\'s native question tool (AskUserQuestion in Claude Code / request_user_input in Codex Plan mode / quick pick in Cursor / stop-and-wait for hosts without one \u2014 see RULE_SENTINEL_HANDLING), collect the user\'s actual answers, then submit them back in the next mist_plan call with the `conversationId`. When `status: "design_clarify_pending"`, poll until directions are ready, present them to the user, submit the pick via mist_plan with `designConversationId` + `designDirection` (NOT `conversationId` alone).'),t.push("3. Mockup (optional, recommended): call mist_mockup with the planId \u2014 returns a wireframe prompt. Write the HTML to the returned path, open it for the user, wait for approval. Iterate with { feedback }, lock with { approved: true }."),t.push("4. Scaffold: mist_init with { planId, path }. Transactional \u2014 if it fails before the final move, stop."),t.push("5. Install: mist_install { projectPath } starts; poll with { jobId } until complete. Typical 30\u201390s."),t.push("6. Implement: mist_implement runs one plan step at a time and auto-marks the prior step complete. Call repeatedly until all steps are done."),t.push("7. Build: mist_build { projectPath } starts; poll with { jobId }. On failure, the response has `missingModules[]` \u2014 chain mist_install with those modules then mist_build again without asking."),t.push("8. Deploy: mist_deploy { projectPath } starts tar + upload; poll with { action: 'status', deploymentId }. On complete, response has `qaRequired: true` and a `url` \u2014 do NOT surface the URL to the user until mist_qa passes. Subsequent actions: 'promote' (staging \u2192 prod), 'rollback' (revert to a specific deploymentId)."),t.push("9. QA: mist_qa { projectPath } drives Playwright in-process against the live URL \u2014 single synchronous call (~20\u201345s), returns pass/fail and screenshots inline. Only surface the deploy URL to the user after mist_qa returns status: 'pass'."),t.push(""),t.push(Ml),t.push(""),t.push(Dl),t.push(""),t.push("Updating an existing Mistflow app:"),t.push("- Cosmetic or single-file changes: edit files directly, then mist_build \u2192 mist_deploy."),t.push("- New page or feature (no new data model): mist_project action='get' for context, build it, then mist_build \u2192 mist_deploy."),t.push("- Feature needing new data model: ask product questions, call mist_project action='get' for context, call mist_plan with existingPlanId, then mist_implement, then mist_build \u2192 mist_deploy."),t.push("- Integration addition: ask product questions, mist_project action='get', mist_plan with existingPlanId, mist_implement, mist_config resource='env' for keys, mist_build \u2192 mist_deploy \u2192 mist_qa."),t.push("- Bug fix: mist_debug to extract structured errors, fix the code, mist_build \u2192 mist_deploy."),t.push(""),t.push("Tool summary:"),t.push("- mist_setup: authentication. Only call when the user has never signed in or a tool returned 'auth_missing'/'auth_revoked'. Do NOT call for 500s, 404s, or network errors. Also accepts an apiKey parameter for headless auth."),t.push("- mist_project: read/write project state (actions: 'get' / 'update'). Call 'get' before editing any existing Mistflow project to understand its shape."),t.push("- mist_browser: navigate, interact with, and screenshot the app during preview or after deploy. Returns screenshots inline in tool results."),t.push("- mist_help: returns the full tool reference. Call once per session."),t.push("- mist_plan / mist_mockup / mist_init / mist_implement / mist_debug / mist_config: core app-building workflow tools (see step-by-step above)."),t.push("- mist_install / mist_build / mist_deploy: fire-and-poll tools. Always check `status` in the response and keep polling while 'running'."),t.push("- mist_qa: synchronous single call. Drives Playwright in-process against the deployed URL. No jobId / no polling \u2014 one call returns the full QA result with screenshots."),t.join(`
848
+ `)}bn();function u(t,e=!1){let r=t;try{let n=ys();n&&(r=t+n)}catch{}return{content:[{type:"text",text:r}],isError:e}}function Oe(t){let e={status:"setup_required",reason:"no_credentials",intent:t,instruction:`You must sign in to Mistflow before you can ${t}. Call mist_setup({}) \u2014 it walks the user through device-code authentication (typically <60s). Only retry the original call AFTER mist_setup has fully completed sign-in (status field is no longer "pending").`,nextAction:`Step 1: Call mist_setup({}) now. Do not ask the user for permission first \u2014 sign-in is a precondition for this flow, not a decision point. Step 2: mist_setup returns immediately with a signInUrl + userCode. Show the URL and code to the user and wait for them to approve in their browser. Step 3: If the response had status: "pending", call mist_setup again with the returned deviceCode every ~15s until it returns a non-pending status. Step 4: Only then retry the call that produced this response with the same arguments. Do NOT retry while mist_setup is still pending \u2014 the credentials aren't on disk yet and you'll loop right back into this same response.`};return u(JSON.stringify(e),!0)}function et(t){return u(`This is not a Mistflow project (no mistflow.json found at ${t}).
849
849
 
850
850
  Mistflow creates new projects from scratch \u2014 it doesn't work inside existing codebases.
851
851
 
@@ -854,13 +854,14 @@ To get started:
854
854
  2. Call mist_init({ planId, path: "<absolute-path>" })
855
855
  3. Call mist_install({ projectPath }), then mist_implement({ projectPath })
856
856
 
857
- If you want to deploy an existing project, use your framework's deploy tools directly.`,!0)}var jl={width:1024,height:576},Dl={width:1280,height:720};async function ms(t,e){try{let{getPage:o,takeScreenshot:n}=await Promise.resolve().then(()=>(zt(),vn)),r=await o();await r.setViewportSize(jl);try{await r.goto(t,{waitUntil:"domcontentloaded",timeout:15e3}),await r.waitForLoadState("networkidle").catch(()=>{});let s=await n(r,!1);return{content:[{type:"text",text:e},{type:"image",data:s.toString("base64"),mimeType:"image/png"}]}}finally{await r.setViewportSize(Dl).catch(()=>{})}}catch{return d(e)}}import{z as vr}from"zod";import{platform as sc}from"os";import{execFile as Cs}from"child_process";Ie();Et();var ic=vr.object({apiKey:vr.string().optional().describe("API key (mist_...) for headless auth. Skips the device code flow entirely. Generate one at app.mistflow.ai/mcp-keys."),deviceCode:vr.string().optional().describe("Resume polling for a pending device code. Returned by a previous mist_setup call with status 'pending'. Call mist_setup again with this value after ~15 seconds to check if the user approved.")});function ac(t){return"error"in t}function Is(t){return new Promise(e=>setTimeout(e,t))}function lc(t){return new Promise(e=>{let o=sc();o==="win32"?Cs("cmd.exe",["/c","start","",t],n=>{n&&console.error("Could not open browser:",n.message),e(!n)}):Cs(o==="darwin"?"open":"xdg-open",[t],r=>{r&&console.error("Could not open browser:",r.message),e(!r)}),setTimeout(()=>e(!1),5e3)})}var cc={fetch:globalThis.fetch,openBrowser:lc,sleep:Is};async function Ts(t,e,o,n){let r=o,s=n.sleep??Is;for(let i=0;i<e;i++){await s(r);let a;try{let c=await n.fetch(`${Me()}/auth/poll`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:t})});if(!c.ok)continue;a=await c.json()}catch{continue}if(ac(a))switch(a.error){case"authorization_pending":continue;case"slow_down":r+=5e3;continue;case"expired_token":return d("The sign-in link expired. Run mist_setup again to get a new code.",!0);case"access_denied":return d("Sign-in was cancelled. Run mist_setup again to try again.",!0);case"already_exchanged":return d("This sign-in link was already used. Run mist_setup again to get a new code.",!0)}let l=a.email||a.org_name||a.org_slug;return Fo({apiKey:a.api_key,apiKeyId:a.api_key_id,apiKeyName:a.api_key_name,orgId:a.org_id,orgSlug:a.org_slug,email:a.email}),d(`Connected to Mistflow as ${l}. You are ready to build and deploy.`)}return null}async function dc(t,e=cc){let o=t;if(o?.apiKey)try{let i=await e.fetch(`${Me()}/api/org`,{headers:{Authorization:`ApiKey ${o.apiKey}`}});if(!i.ok)return d("Invalid API key. Check the key and try again.",!0);let a=await i.json();return Fo({apiKey:o.apiKey,orgId:a.id,orgSlug:a.slug}),d(`Connected to Mistflow as ${a.slug} via API key. You are ready to build and deploy.`)}catch{return d("Cannot reach Mistflow servers. Check your internet connection.",!0)}if(o?.deviceCode){let i=await Ts(o.deviceCode,6,5e3,e);return i||d(JSON.stringify({status:"pending",deviceCode:o.deviceCode,instruction:"The user hasn't approved yet. Wait ~15 seconds and call mist_setup again with the same deviceCode."}))}let n;try{let i=await e.fetch(`${Me()}/auth/device`,{method:"POST",headers:{"Content-Type":"application/json"}});if(!i.ok)return d("Cannot reach Mistflow servers. Check your internet connection.",!0);n=await i.json()}catch{return d("Cannot reach Mistflow servers. Check your internet connection.",!0)}let r=`${n.verification_uri}?code=${n.user_code}`;console.error(`
858
- Sign in at: ${r}
857
+ If you want to deploy an existing project, use your framework's deploy tools directly.`,!0)}var Kl={width:1024,height:576},Vl={width:1280,height:720};async function ws(t,e){try{let{getPage:r,takeScreenshot:n}=await Promise.resolve().then(()=>(Wt(),kn)),o=await r();await o.setViewportSize(Kl);try{await o.goto(t,{waitUntil:"domcontentloaded",timeout:15e3}),await o.waitForLoadState("networkidle").catch(()=>{});let s=await n(o,!1);return{content:[{type:"text",text:e},{type:"image",data:s.toString("base64"),mimeType:"image/png"}]}}finally{await o.setViewportSize(Vl).catch(()=>{})}}catch{return u(e)}}import{z as xo}from"zod";import{platform as mc}from"os";import{execFile as js}from"child_process";ke();Nt();var hc=xo.object({apiKey:xo.string().optional().describe("API key (mist_...) for headless auth. Skips the device code flow entirely. Generate one at app.mistflow.ai/mcp-keys."),deviceCode:xo.string().optional().describe("Resume polling for a pending device code. Returned by a previous mist_setup call with status 'pending'. Call mist_setup again with this value after ~15 seconds to check if the user approved.")});function gc(t){return"error"in t}function Ls(t){return new Promise(e=>setTimeout(e,t))}function fc(t){return new Promise(e=>{let r=mc();r==="win32"?js("cmd.exe",["/c","start","",t],n=>{n&&console.error("Could not open browser:",n.message),e(!n)}):js(r==="darwin"?"open":"xdg-open",[t],o=>{o&&console.error("Could not open browser:",o.message),e(!o)}),setTimeout(()=>e(!1),5e3)})}var yc={fetch:globalThis.fetch,openBrowser:fc,sleep:Ls};async function Ds(t,e,r,n){let o=r,s=n.sleep??Ls;for(let i=0;i<e;i++){await s(o);let a;try{let c=await n.fetch(`${Me()}/auth/poll`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:t})});if(!c.ok)continue;a=await c.json()}catch{continue}if(gc(a))switch(a.error){case"authorization_pending":continue;case"slow_down":o+=5e3;continue;case"expired_token":return u("The sign-in link expired. Run mist_setup again to get a new code.",!0);case"access_denied":return u("Sign-in was cancelled. Run mist_setup again to try again.",!0);case"already_exchanged":return u("This sign-in link was already used. Run mist_setup again to get a new code.",!0)}let l=a.email||a.org_name||a.org_slug;return Br({apiKey:a.api_key,apiKeyId:a.api_key_id,apiKeyName:a.api_key_name,orgId:a.org_id,orgSlug:a.org_slug,email:a.email}),u(`Connected to Mistflow as ${l}. You are ready to build and deploy.`)}return null}async function bc(t,e=yc){let r=t;if(r?.apiKey)try{let i=await e.fetch(`${Me()}/api/org`,{headers:{Authorization:`ApiKey ${r.apiKey}`}});if(!i.ok)return u("Invalid API key. Check the key and try again.",!0);let a=await i.json();return Br({apiKey:r.apiKey,orgId:a.id,orgSlug:a.slug}),u(`Connected to Mistflow as ${a.slug} via API key. You are ready to build and deploy.`)}catch{return u("Cannot reach Mistflow servers. Check your internet connection.",!0)}if(r?.deviceCode){let i=await Ds(r.deviceCode,6,5e3,e);return i||u(JSON.stringify({status:"pending",deviceCode:r.deviceCode,instruction:"The user hasn't approved yet. Wait ~15 seconds and call mist_setup again with the same deviceCode."}))}let n;try{let i=await e.fetch(`${Me()}/auth/device`,{method:"POST",headers:{"Content-Type":"application/json"}});if(!i.ok)return u("Cannot reach Mistflow servers. Check your internet connection.",!0);n=await i.json()}catch{return u("Cannot reach Mistflow servers. Check your internet connection.",!0)}let o=`${n.verification_uri}?code=${n.user_code}`;console.error(`
858
+ Sign in at: ${o}
859
859
  Your code: ${n.user_code}
860
- `);try{await e.openBrowser(r)}catch{}let s=await Ts(n.device_code,6,5e3,e);return s||d(JSON.stringify({status:"pending",deviceCode:n.device_code,signInUrl:r,userCode:n.user_code,instruction:"The user hasn't approved yet. Wait ~15 seconds, then call mist_setup again with deviceCode='"+n.device_code+"' to check if they approved."}))}var Ps={name:"mist_setup",description:Qr,inputSchema:ic,handler:t=>dc(t)};import{z as Le}from"zod";import{resolve as In}from"path";import{existsSync as Pn,readFileSync as Rn}from"fs";import{join as An}from"path";import{z as wt}from"zod";import{resolve as Pc,join as Ms}from"path";import{existsSync as Rc,readFileSync as js,writeFileSync as Ac}from"fs";import{existsSync as uc,readFileSync as pc}from"fs";function Rs(t){let e=new Set;if(!uc(t))return e;let o=pc(t,"utf-8");for(let n of o.split(`
861
- `)){let r=n.trim();if(!r||r.startsWith("#"))continue;let s=r.indexOf("=");if(s>0){let i=r.slice(0,s).trim(),a=r.slice(s+1).trim();a&&a!=='""'&&a!=="''"&&e.add(i)}}return e}var Ec=wt.object({action:wt.enum(["get","update"]).default("get").describe("'get' reads current project state. 'update' modifies it."),projectPath:wt.string().optional().describe("Path to the project directory (default: current working directory)"),completedStep:wt.number().optional().describe("(update only) Mark a plan step as completed by step number"),addEnvVar:wt.object({key:wt.string(),description:wt.string().optional(),setupUrl:wt.string().optional()}).optional().describe("(update only) Add a required env var to the project manifest")}),Ds={name:"mist_state",description:"Read or update project state in mistflow.json. Use action='get' to load plan progress, env var status, and deploy info. Use action='update' to mark plan steps complete or add required env vars. Called internally by mist_project; host AIs should use mist_project directly.",inputSchema:Ec,handler:async t=>{let e=t,o=Pc(e.projectPath??process.cwd()),n=Ms(o,"mistflow.json");if(!Rc(n))return Ve(o);let r;try{r=JSON.parse(js(n,"utf-8"))}catch{return d("Failed to parse mistflow.json.",!0)}if(e.action==="get"){if(!r.projectId)try{let{ensureBackendRegistered:x}=await Promise.resolve().then(()=>(Sr(),xr));await x(o)&&(r=JSON.parse(js(n,"utf-8")))}catch{}let a=r.plan,l=a?.steps?.filter(x=>x.status==="completed").length??0,c=a?.steps?.length??0,m=Rs(Ms(o,".env.local")),u=r.env?.required?Object.entries(r.env.required).map(([x,w])=>({name:x,description:w?.description,configured:m.has(x)})):[];r.projectId&&Promise.resolve().then(()=>(jt(),Tn)).then(({fetchRemoteState:x})=>x(r.projectId)).catch(()=>{});let p=[`Project: ${r.name}`];if(a){p.push(`Plan: ${a.summary??a.name??"unnamed"} \u2014 ${l}/${c} steps complete`);for(let x of a.steps){let w=x.status==="completed"?"\u2713":x.status==="in_progress"?"\u2192":" ";p.push(` [${w}] ${x.number}. ${x.name}`)}}let h=u.filter(x=>!x.configured);h.length>0&&p.push(`Missing env vars: ${h.map(x=>x.name).join(", ")}`),r.deploy?.url?p.push(`Deployed: ${r.deploy.url} (${r.deploy.count??0} deploys)`):p.push("Not deployed yet");let U=[],C=a?.steps?.find(x=>x.status!=="completed");return C?U.push(`NEXT: Call mist_implement({ projectPath }) to work on step ${C.number} (${C.name}).`):a&&l===c&&(r.deploy?.url||U.push("NEXT: All steps complete. Call mist_build({ projectPath }) then mist_deploy({ action: 'deploy', projectPath }). Do NOT ask the user \u2014 chain straight through.")),h.length>0&&U.push(`Missing env vars in .env.local: ${h.map(x=>x.name).join(", ")}`),d(JSON.stringify({name:r.name,projectId:r.projectId,planProgress:a?{name:a.name,summary:a.summary,totalSteps:c,completedSteps:l,steps:a.steps}:null,envStatus:u,deploy:r.deploy??null,contextMessage:p.join(`
862
- `),nextSteps:U}))}let s=[];if(e.completedStep!==void 0){let a=r.plan;if(a?.steps){let l=a.steps.findIndex(c=>c.number===e.completedStep);if(l===-1)return d(`Step ${e.completedStep} not found in the plan.`,!0);a.steps[l].status="completed",s.push(`Step ${e.completedStep} marked as completed`)}}e.addEnvVar&&(r.env||(r.env={required:{}}),r.env.required||(r.env.required={}),r.env.required[e.addEnvVar.key]={description:e.addEnvVar.description,setupUrl:e.addEnvVar.setupUrl},s.push(`Added required env var: ${e.addEnvVar.key}`)),Ac(n,JSON.stringify(r,null,2)+`
863
- `),r.projectId&&Promise.resolve().then(()=>(jt(),Tn)).then(async({readLocalState:a,syncRemoteState:l})=>{let c=a(o);c&&await l(r.projectId,c)}).catch(()=>{});let i=[];if(e.completedStep!==void 0){let l=r.plan?.steps?.find(c=>c.status!=="completed");l?i.push(`NEXT: Call mist_implement({ projectPath }) to work on step ${l.number} (${l.name}). Do this now.`):i.push("NEXT: All steps complete. Call mist_build({ projectPath }) then mist_deploy({ action: 'deploy', projectPath }) to deploy the app. Do NOT suggest localhost.")}return e.addEnvVar&&(i.push(`Add ${e.addEnvVar.key} to your .env.local file`),e.addEnvVar.setupUrl&&i.push(`Get the value from: ${e.addEnvVar.setupUrl}`)),d(JSON.stringify({updated:!0,changes:s,message:s.length>0?`Project state saved. ${s.join(". ")}.`:"No changes made.",nextSteps:i.length>0?i:void 0}))}};Ie();yn();var _r={"resend-email":{description:"Transactional email with React Email templates and webhook handling.",tags:["email","transactional","welcome","notification","invite","alert"],envVars:[{key:"RESEND_API_KEY",description:"Resend API key for sending emails",setupUrl:"https://resend.com/api-keys"}],docsUrl:"https://resend.com/docs/send-with-nextjs",packages:["resend","@react-email/components"],difficulty:"easy"},"openai-ai":{description:"AI-powered features with OpenAI SDK, streaming chat, and content generation.",tags:["ai","openai","chatbot","gpt","llm","assistant","generation"],envVars:[{key:"OPENAI_API_KEY",description:"OpenAI API key for AI features",setupUrl:"https://platform.openai.com/api-keys"}],docsUrl:"https://platform.openai.com/docs/guides/text-generation",packages:["openai","ai"],difficulty:"medium"},"neon-smart-search":{description:"Semantic and hybrid search using Neon Postgres, pgvector, Postgres full-text search, and embeddings.",tags:["search","semantic","vector","embedding","hybrid","rag","knowledge-base","documents"],envVars:[],docsUrl:"https://neon.com/docs/extensions/pgvector",packages:[],difficulty:"medium"},"anthropic-ai":{description:"AI features with the Anthropic SDK, streaming Claude chat, and content generation.",tags:["ai","anthropic","claude","llm","assistant","generation"],envVars:[{key:"ANTHROPIC_API_KEY",description:"Anthropic API key for Claude",setupUrl:"https://console.anthropic.com/settings/keys"}],docsUrl:"https://docs.anthropic.com/en/docs/initial-setup",packages:["@anthropic-ai/sdk","ai"],difficulty:"medium"},"openrouter-ai":{description:"AI model router with access to 200+ models (GPT, Claude, Llama, Mistral, etc.) through one API.",tags:["ai","openrouter","llm","multi-model","claude","gpt","llama","mistral"],envVars:[{key:"OPENROUTER_API_KEY",description:"OpenRouter API key",setupUrl:"https://openrouter.ai/keys"}],docsUrl:"https://openrouter.ai/docs/quickstart",packages:["@openrouter/sdk"],difficulty:"easy"},"stripe-payments":{description:"Payment processing with Stripe Checkout, webhooks, and billing portal.",tags:["payments","stripe","billing","subscription","checkout","invoice"],envVars:[{key:"STRIPE_SECRET_KEY",description:"Stripe secret key",setupUrl:"https://dashboard.stripe.com/apikeys"},{key:"STRIPE_PUBLISHABLE_KEY",description:"Stripe publishable key (client-side)",setupUrl:"https://dashboard.stripe.com/apikeys"},{key:"STRIPE_WEBHOOK_SECRET",description:"Stripe webhook signing secret",setupUrl:"https://dashboard.stripe.com/webhooks"}],docsUrl:"https://docs.stripe.com/checkout/quickstart",packages:["stripe","@stripe/stripe-js"],difficulty:"advanced"},"elevenlabs-voice":{description:"Text-to-speech and voice generation with ElevenLabs API.",tags:["voice","tts","speech","audio","elevenlabs","narration","podcast"],envVars:[{key:"ELEVENLABS_API_KEY",description:"ElevenLabs API key for voice generation",setupUrl:"https://elevenlabs.io/app/settings/api-keys"}],docsUrl:"https://elevenlabs.io/docs/api-reference/text-to-speech",packages:["elevenlabs"],difficulty:"medium"},"google-maps":{description:"Google Maps embed, Places autocomplete, and geolocation features.",tags:["maps","location","google","places","geocoding","directions","nearby"],envVars:[{key:"NEXT_PUBLIC_GOOGLE_MAPS_API_KEY",description:"Google Maps API key (client-side)",setupUrl:"https://console.cloud.google.com/apis/credentials"}],docsUrl:"https://developers.google.com/maps/documentation/javascript",packages:["@googlemaps/js-api-loader"],difficulty:"medium"},"twilio-sms":{description:"SMS notifications, OTP verification, and phone number validation.",tags:["sms","twilio","otp","phone","verification","text-message"],envVars:[{key:"TWILIO_ACCOUNT_SID",description:"Twilio account SID",setupUrl:"https://console.twilio.com/"},{key:"TWILIO_AUTH_TOKEN",description:"Twilio auth token",setupUrl:"https://console.twilio.com/"},{key:"TWILIO_PHONE_NUMBER",description:"Twilio phone number for sending SMS",setupUrl:"https://console.twilio.com/us1/develop/phone-numbers/manage/incoming"}],docsUrl:"https://www.twilio.com/docs/messaging/quickstart/node",packages:["twilio"],difficulty:"medium"},"posthog-analytics":{description:"Product analytics with event tracking, feature flags, and session replay.",tags:["analytics","posthog","tracking","funnel","event","feature-flag"],envVars:[{key:"NEXT_PUBLIC_POSTHOG_KEY",description:"PostHog project API key",setupUrl:"https://app.posthog.com/project/settings"},{key:"NEXT_PUBLIC_POSTHOG_HOST",description:"PostHog instance URL (default: https://us.i.posthog.com)",setupUrl:"https://app.posthog.com/project/settings"}],docsUrl:"https://posthog.com/docs/libraries/next-js",packages:["posthog-js","posthog-node"],difficulty:"easy"},"firecrawl-scraping":{description:"Web scraping and crawling with markdown output, structured extraction, and async crawls.",tags:["scraping","crawl","firecrawl","web","extract","rag","markdown"],envVars:[{key:"FIRECRAWL_API_KEY",description:"Firecrawl API key",setupUrl:"https://firecrawl.dev"}],docsUrl:"https://docs.firecrawl.dev/sdks/node",packages:["@mendable/firecrawl-js"],difficulty:"easy"},"replicate-media":{description:"Image and video generation with 200+ AI models (Flux, Wan Video, Runway, SDXL, etc.) through one API.",tags:["image","video","replicate","flux","sdxl","generation","media","avatar","thumbnail"],envVars:[{key:"REPLICATE_API_TOKEN",description:"Replicate API token",setupUrl:"https://replicate.com/account/api-tokens"}],docsUrl:"https://replicate.com/docs/get-started/nodejs",packages:["replicate"],difficulty:"medium"},"agent-ui":{description:"World-class agent UX: rich tool cards with streaming logs, approval gates for risky actions, cost badges, plan checklists, contextual suggestions. Optional add-on for chat / agent apps.",tags:["agent","chat","tools","ui","tool-call","approval","agent-ux","streaming"],envVars:[],docsUrl:"https://mistflow.ai/docs/agent-ui",packages:["framer-motion","recharts","clsx","tailwind-merge"],difficulty:"easy"},"code-runner":{description:"Lets the agent run Python on user-uploaded data (CSV analysis, charting, transformations). Stub backend for v1; real sandbox lands later.",tags:["agent","tools","python","sandbox","code","csv","data-analysis","chart"],envVars:[],docsUrl:"https://mistflow.ai/docs/agent-tools",packages:["ai","zod"],difficulty:"medium"}},Vt=[{id:"resend-email",name:"Resend Email",category:"communication",prompt:`## Resend Email Integration
860
+ `);try{await e.openBrowser(o)}catch{}let s=await Ds(n.device_code,6,5e3,e);return s||u(JSON.stringify({status:"pending",deviceCode:n.device_code,signInUrl:o,userCode:n.user_code,instruction:"The user hasn't approved yet. Wait ~15 seconds, then call mist_setup again with deviceCode='"+n.device_code+"' to check if they approved."}))}var $s={name:"mist_setup",description:rs,inputSchema:hc,handler:t=>bc(t)};import{z as $e}from"zod";import{resolve as Rn}from"path";import{existsSync as An,readFileSync as En}from"fs";import{join as Nn}from"path";import{z as kt}from"zod";import{resolve as Lc,join as zs}from"path";import{existsSync as $c,readFileSync as Ws,writeFileSync as Gs}from"fs";import{existsSync as wc,readFileSync as vc}from"fs";function Us(t){let e=new Set;if(!wc(t))return e;let r=vc(t,"utf-8");for(let n of r.split(`
861
+ `)){let o=n.trim();if(!o||o.startsWith("#"))continue;let s=o.indexOf("=");if(s>0){let i=o.slice(0,s).trim(),a=o.slice(s+1).trim();a&&a!=='""'&&a!=="''"&&e.add(i)}}return e}ke();var Uc=kt.object({action:kt.enum(["get","update"]).default("get").describe("'get' reads current project state. 'update' modifies it."),projectPath:kt.string().optional().describe("Path to the project directory (default: current working directory)"),completedStep:kt.number().optional().describe("(update only) Mark a plan step as completed by step number"),addEnvVar:kt.object({key:kt.string(),description:kt.string().optional(),setupUrl:kt.string().optional()}).optional().describe("(update only) Add a required env var to the project manifest")}),Js={name:"mist_state",description:"Read or update project state in mistflow.json. Use action='get' to load plan progress, env var status, and deploy info. Use action='update' to mark plan steps complete or add required env vars. Called internally by mist_project; host AIs should use mist_project directly.",inputSchema:Uc,handler:async t=>{let e=t,r=Lc(e.projectPath??process.cwd()),n=zs(r,"mistflow.json");if(!$c(n))return et(r);let o;try{o=JSON.parse(Ws(n,"utf-8"))}catch{return u("Failed to parse mistflow.json.",!0)}if(e.action==="get"){if(!o.projectId)try{let{ensureBackendRegistered:y}=await Promise.resolve().then(()=>(To(),_o));await y(r)&&(o=JSON.parse(Ws(n,"utf-8")))}catch{}let a=o.plan,l=a?.steps?.filter(y=>y.status==="completed").length??0,c=a?.steps?.length??0,p=Us(zs(r,".env.local")),d=o.env?.required?Object.entries(o.env.required).map(([y,v])=>({name:y,description:v?.description,configured:p.has(y)})):[];if(o.projectId){try{let y=await zr(o.projectId);!o.deploy?.url&&y.deployUrl&&(o.deploy={...o.deploy??{},url:y.deployUrl,lastDeployedAt:new Date().toISOString()},Gs(n,JSON.stringify(o,null,2)+`
862
+ `))}catch{}Promise.resolve().then(()=>(Dt(),Pn)).then(({fetchRemoteState:y})=>y(o.projectId)).catch(()=>{})}let m=[`Project: ${o.name}`];if(a){m.push(`Plan: ${a.summary??a.name??"unnamed"} \u2014 ${l}/${c} steps complete`);for(let y of a.steps){let v=y.status==="completed"?"\u2713":y.status==="in_progress"?"\u2192":" ";m.push(` [${v}] ${y.number}. ${y.name}`)}}let h=d.filter(y=>!y.configured);h.length>0&&m.push(`Missing env vars: ${h.map(y=>y.name).join(", ")}`),o.deploy?.url?m.push(`Deployed: ${o.deploy.url} (${o.deploy.count??0} deploys)`):m.push("Not deployed yet");let S=[],I=a?.steps?.find(y=>y.status!=="completed");return I?S.push(`NEXT: Call mist_implement({ projectPath }) to work on step ${I.number} (${I.name}).`):a&&l===c&&(o.deploy?.url||S.push("NEXT: All steps complete. Call mist_build({ projectPath }) then mist_deploy({ action: 'deploy', projectPath }). Do NOT ask the user \u2014 chain straight through.")),h.length>0&&S.push(`Missing env vars in .env.local: ${h.map(y=>y.name).join(", ")}`),u(JSON.stringify({name:o.name,projectId:o.projectId,planProgress:a?{name:a.name,summary:a.summary,totalSteps:c,completedSteps:l,steps:a.steps}:null,envStatus:d,deploy:o.deploy??null,contextMessage:m.join(`
863
+ `),nextSteps:S}))}let s=[];if(e.completedStep!==void 0){let a=o.plan;if(a?.steps){let l=a.steps.findIndex(c=>c.number===e.completedStep);if(l===-1)return u(`Step ${e.completedStep} not found in the plan.`,!0);a.steps[l].status="completed",s.push(`Step ${e.completedStep} marked as completed`)}}e.addEnvVar&&(o.env||(o.env={required:{}}),o.env.required||(o.env.required={}),o.env.required[e.addEnvVar.key]={description:e.addEnvVar.description,setupUrl:e.addEnvVar.setupUrl},s.push(`Added required env var: ${e.addEnvVar.key}`)),Gs(n,JSON.stringify(o,null,2)+`
864
+ `),o.projectId&&Promise.resolve().then(()=>(Dt(),Pn)).then(async({readLocalState:a,syncRemoteState:l})=>{let c=a(r);c&&await l(o.projectId,c)}).catch(()=>{});let i=[];if(e.completedStep!==void 0){let l=o.plan?.steps?.find(c=>c.status!=="completed");l?i.push(`NEXT: Call mist_implement({ projectPath }) to work on step ${l.number} (${l.name}). Do this now.`):i.push("NEXT: All steps complete. Call mist_build({ projectPath }) then mist_deploy({ action: 'deploy', projectPath }) to deploy the app. Do NOT suggest localhost.")}return e.addEnvVar&&(i.push(`Add ${e.addEnvVar.key} to your .env.local file`),e.addEnvVar.setupUrl&&i.push(`Get the value from: ${e.addEnvVar.setupUrl}`)),u(JSON.stringify({updated:!0,changes:s,message:s.length>0?`Project state saved. ${s.join(". ")}.`:"No changes made.",nextSteps:i.length>0?i:void 0}))}};ke();bn();var Co={"resend-email":{description:"Transactional email with React Email templates and webhook handling.",tags:["email","transactional","welcome","notification","invite","alert"],envVars:[{key:"RESEND_API_KEY",description:"Resend API key for sending emails",setupUrl:"https://resend.com/api-keys"}],docsUrl:"https://resend.com/docs/send-with-nextjs",packages:["resend","@react-email/components"],difficulty:"easy"},"neon-smart-search":{description:"Semantic and hybrid search using Neon Postgres, pgvector, Postgres full-text search, and embeddings.",tags:["search","semantic","vector","embedding","hybrid","rag","knowledge-base","documents"],envVars:[],docsUrl:"https://neon.com/docs/extensions/pgvector",packages:[],difficulty:"medium"},"stripe-payments":{description:"Payment processing with Stripe Checkout, webhooks, and billing portal.",tags:["payments","stripe","billing","subscription","checkout","invoice"],envVars:[{key:"STRIPE_SECRET_KEY",description:"Stripe secret key",setupUrl:"https://dashboard.stripe.com/apikeys"},{key:"STRIPE_PUBLISHABLE_KEY",description:"Stripe publishable key (client-side)",setupUrl:"https://dashboard.stripe.com/apikeys"},{key:"STRIPE_WEBHOOK_SECRET",description:"Stripe webhook signing secret",setupUrl:"https://dashboard.stripe.com/webhooks"}],docsUrl:"https://docs.stripe.com/checkout/quickstart",packages:["stripe","@stripe/stripe-js"],difficulty:"advanced"},"elevenlabs-voice":{description:"Text-to-speech and voice generation with ElevenLabs API.",tags:["voice","tts","speech","audio","elevenlabs","narration","podcast"],envVars:[{key:"ELEVENLABS_API_KEY",description:"ElevenLabs API key for voice generation",setupUrl:"https://elevenlabs.io/app/settings/api-keys"}],docsUrl:"https://elevenlabs.io/docs/api-reference/text-to-speech",packages:["elevenlabs"],difficulty:"medium"},"google-maps":{description:"Google Maps embed, Places autocomplete, and geolocation features.",tags:["maps","location","google","places","geocoding","directions","nearby"],envVars:[{key:"NEXT_PUBLIC_GOOGLE_MAPS_API_KEY",description:"Google Maps API key (client-side)",setupUrl:"https://console.cloud.google.com/apis/credentials"}],docsUrl:"https://developers.google.com/maps/documentation/javascript",packages:["@googlemaps/js-api-loader"],difficulty:"medium"},"twilio-sms":{description:"SMS notifications, OTP verification, and phone number validation.",tags:["sms","twilio","otp","phone","verification","text-message"],envVars:[{key:"TWILIO_ACCOUNT_SID",description:"Twilio account SID",setupUrl:"https://console.twilio.com/"},{key:"TWILIO_AUTH_TOKEN",description:"Twilio auth token",setupUrl:"https://console.twilio.com/"},{key:"TWILIO_PHONE_NUMBER",description:"Twilio phone number for sending SMS",setupUrl:"https://console.twilio.com/us1/develop/phone-numbers/manage/incoming"}],docsUrl:"https://www.twilio.com/docs/messaging/quickstart/node",packages:["twilio"],difficulty:"medium"},"posthog-analytics":{description:"Product analytics with event tracking, feature flags, and session replay.",tags:["analytics","posthog","tracking","funnel","event","feature-flag"],envVars:[{key:"NEXT_PUBLIC_POSTHOG_KEY",description:"PostHog project API key",setupUrl:"https://app.posthog.com/project/settings"},{key:"NEXT_PUBLIC_POSTHOG_HOST",description:"PostHog instance URL (default: https://us.i.posthog.com)",setupUrl:"https://app.posthog.com/project/settings"}],docsUrl:"https://posthog.com/docs/libraries/next-js",packages:["posthog-js","posthog-node"],difficulty:"easy"},"firecrawl-scraping":{description:"Web scraping and crawling with markdown output, structured extraction, and async crawls.",tags:["scraping","crawl","firecrawl","web","extract","rag","markdown"],envVars:[{key:"FIRECRAWL_API_KEY",description:"Firecrawl API key",setupUrl:"https://firecrawl.dev"}],docsUrl:"https://docs.firecrawl.dev/sdks/node",packages:["@mendable/firecrawl-js"],difficulty:"easy"},"replicate-media":{description:"Image and video generation with 200+ AI models (Flux, Wan Video, Runway, SDXL, etc.) through one API.",tags:["image","video","replicate","flux","sdxl","generation","media","avatar","thumbnail"],envVars:[{key:"REPLICATE_API_TOKEN",description:"Replicate API token",setupUrl:"https://replicate.com/account/api-tokens"}],docsUrl:"https://replicate.com/docs/get-started/nodejs",packages:["replicate"],difficulty:"medium"},"agent-ui":{description:"World-class agent UX: rich tool cards with streaming logs, approval gates for risky actions, cost badges, plan checklists, contextual suggestions. Optional add-on for chat / agent apps.",tags:["agent","chat","tools","ui","tool-call","approval","agent-ux","streaming"],envVars:[],docsUrl:"https://mistflow.ai/docs/agent-ui",packages:["framer-motion","recharts","clsx","tailwind-merge"],difficulty:"easy"},"code-runner":{description:"Lets the agent run Python on user-uploaded data (CSV analysis, charting, transformations). Stub backend for v1; real sandbox lands later.",tags:["agent","tools","python","sandbox","code","csv","data-analysis","chart"],envVars:[],docsUrl:"https://mistflow.ai/docs/agent-tools",packages:["ai","zod"],difficulty:"medium"}},Yt=[{id:"resend-email",name:"Resend Email",category:"communication",prompt:`## Resend Email Integration
864
865
 
865
866
  ### File Structure
866
867
  \`\`\`
@@ -951,306 +952,7 @@ export async function POST(req: NextRequest) {
951
952
  3. **From address must match a verified domain** or use onboarding@resend.dev for testing.
952
953
  4. **Webhooks need a public URL.** Use ngrok or similar for local webhook testing.
953
954
  5. **React Email templates must be Server Components.** Do not add "use client" to email templates.
954
- 6. **Never ask the user to paste RESEND_API_KEY in chat.** Direct them to set it in the Mistflow dashboard (Project Settings > Environment Variables).`},{id:"openai-ai",name:"OpenAI / AI Integration",category:"ai",prompt:`## OpenAI / AI Integration
955
-
956
- ### File Structure
957
- \`\`\`
958
- lib/openai.ts \u2014 OpenAI client singleton
959
- app/api/chat/route.ts \u2014 Streaming chat API route
960
- components/chat.tsx \u2014 Chat UI component with streaming
961
- \`\`\`
962
-
963
- ### Client Setup (lib/openai.ts)
964
- \`\`\`typescript
965
- import OpenAI from "openai";
966
-
967
- export const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
968
- \`\`\`
969
-
970
- ### Streaming Chat Route (app/api/chat/route.ts)
971
- \`\`\`typescript
972
- import { openai } from "@/lib/openai";
973
- import { NextRequest } from "next/server";
974
-
975
- export async function POST(req: NextRequest) {
976
- const { messages, systemPrompt } = await req.json();
977
-
978
- const stream = await openai.chat.completions.create({
979
- model: "gpt-4o-mini",
980
- messages: [
981
- { role: "system", content: systemPrompt || "You are a helpful assistant." },
982
- ...messages,
983
- ],
984
- stream: true,
985
- });
986
-
987
- // Stream the response using ReadableStream
988
- const encoder = new TextEncoder();
989
- const readable = new ReadableStream({
990
- async start(controller) {
991
- for await (const chunk of stream) {
992
- const text = chunk.choices[0]?.delta?.content || "";
993
- if (text) controller.enqueue(encoder.encode(text));
994
- }
995
- controller.close();
996
- },
997
- });
998
-
999
- return new Response(readable, {
1000
- headers: { "Content-Type": "text/plain; charset=utf-8" },
1001
- });
1002
- }
1003
- \`\`\`
1004
-
1005
- ### Chat UI Component (components/chat.tsx)
1006
- \`\`\`tsx
1007
- "use client";
1008
- import { useState, useRef, useEffect } from "react";
1009
-
1010
- interface Message { role: "user" | "assistant"; content: string }
1011
-
1012
- export function Chat() {
1013
- const [messages, setMessages] = useState<Message[]>([]);
1014
- const [input, setInput] = useState("");
1015
- const [streaming, setStreaming] = useState(false);
1016
- const scrollRef = useRef<HTMLDivElement>(null);
1017
-
1018
- useEffect(() => {
1019
- scrollRef.current?.scrollIntoView({ behavior: "smooth" });
1020
- }, [messages]);
1021
-
1022
- async function handleSend() {
1023
- if (!input.trim() || streaming) return;
1024
- const userMsg: Message = { role: "user", content: input };
1025
- setMessages((prev) => [...prev, userMsg]);
1026
- setInput("");
1027
- setStreaming(true);
1028
-
1029
- const res = await fetch("/api/chat", {
1030
- method: "POST",
1031
- headers: { "Content-Type": "application/json" },
1032
- body: JSON.stringify({ messages: [...messages, userMsg] }),
1033
- });
1034
-
1035
- const reader = res.body?.getReader();
1036
- const decoder = new TextDecoder();
1037
- let assistantContent = "";
1038
-
1039
- setMessages((prev) => [...prev, { role: "assistant", content: "" }]);
1040
-
1041
- while (reader) {
1042
- const { done, value } = await reader.read();
1043
- if (done) break;
1044
- assistantContent += decoder.decode(value);
1045
- setMessages((prev) => [
1046
- ...prev.slice(0, -1),
1047
- { role: "assistant", content: assistantContent },
1048
- ]);
1049
- }
1050
- setStreaming(false);
1051
- }
1052
-
1053
- return (
1054
- <div className="flex flex-col h-full">
1055
- <div className="flex-1 overflow-y-auto p-4 space-y-4">
1056
- {messages.map((m, i) => (
1057
- <div key={i} className={\`flex \${m.role === "user" ? "justify-end" : "justify-start"}\`}>
1058
- <div className={\`max-w-[80%] rounded-lg px-4 py-2 \${
1059
- m.role === "user" ? "bg-primary text-primary-foreground" : "bg-muted"
1060
- }\`}>
1061
- {m.content}
1062
- </div>
1063
- </div>
1064
- ))}
1065
- <div ref={scrollRef} />
1066
- </div>
1067
- <div className="border-t p-4 flex gap-2">
1068
- <input value={input} onChange={(e) => setInput(e.target.value)}
1069
- onKeyDown={(e) => e.key === "Enter" && handleSend()}
1070
- placeholder="Type a message..." className="flex-1 rounded-md border px-3 py-2" />
1071
- <button onClick={handleSend} disabled={streaming}
1072
- className="rounded-md bg-primary px-4 py-2 text-primary-foreground disabled:opacity-50">
1073
- Send
1074
- </button>
1075
- </div>
1076
- </div>
1077
- );
1078
- }
1079
- \`\`\`
1080
-
1081
- ### Common Pitfalls
1082
- 1. **Never expose OPENAI_API_KEY to the client.** Always call OpenAI from server-side API routes.
1083
- 2. **Use gpt-4o-mini for most features** unless the user specifically asks for gpt-4o. Cost difference is 10x.
1084
- 3. **Set max_tokens to prevent runaway costs.** Default to 1000 for chat, 2000 for content generation.
1085
- 4. **Handle rate limits gracefully.** Return a user-friendly error, not a raw 429 response.
1086
- 5. **Streaming is the default UX pattern.** Non-streaming feels broken for chat interfaces.
1087
- 6. **Long streaming responses are fine on Mistflow Cloud.** Network I/O (waiting for OpenAI to generate tokens) does NOT count toward CPU time. A 2-minute streaming chat response uses only milliseconds of CPU.
1088
- 7. **Never ask the user to paste OPENAI_API_KEY in chat.** Direct them to set it in the Mistflow dashboard (Project Settings > Environment Variables).`},{id:"anthropic-ai",name:"Anthropic / Claude",category:"ai",prompt:`## Anthropic / Claude Integration
1089
-
1090
- ### File Structure
1091
- \`\`\`
1092
- lib/anthropic.ts \u2014 Anthropic client singleton
1093
- app/api/chat/route.ts \u2014 Streaming chat API route
1094
- components/chat.tsx \u2014 Chat UI component with streaming
1095
- \`\`\`
1096
-
1097
- ### Client Setup (lib/anthropic.ts)
1098
- \`\`\`typescript
1099
- import Anthropic from "@anthropic-ai/sdk";
1100
-
1101
- export const anthropic = new Anthropic({
1102
- apiKey: process.env.ANTHROPIC_API_KEY,
1103
- });
1104
- \`\`\`
1105
-
1106
- ### Streaming Chat Route (app/api/chat/route.ts)
1107
- \`\`\`typescript
1108
- import { anthropic } from "@/lib/anthropic";
1109
- import { NextRequest } from "next/server";
1110
-
1111
- export async function POST(req: NextRequest) {
1112
- const { messages, systemPrompt } = await req.json();
1113
-
1114
- const stream = anthropic.messages.stream({
1115
- model: "claude-sonnet-4-6",
1116
- max_tokens: 1024,
1117
- system: systemPrompt || "You are a helpful assistant.",
1118
- messages,
1119
- });
1120
-
1121
- const encoder = new TextEncoder();
1122
- const readable = new ReadableStream({
1123
- async start(controller) {
1124
- for await (const event of stream) {
1125
- if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
1126
- controller.enqueue(encoder.encode(event.delta.text));
1127
- }
1128
- }
1129
- controller.close();
1130
- },
1131
- });
1132
-
1133
- return new Response(readable, {
1134
- headers: { "Content-Type": "text/plain; charset=utf-8" },
1135
- });
1136
- }
1137
- \`\`\`
1138
-
1139
- ### Chat UI Component (components/chat.tsx)
1140
- Use the same chat component pattern as the OpenAI integration. The client-side code is identical since both stream plain text. Only the API route differs.
1141
-
1142
- ### Common Pitfalls
1143
- 1. **Never expose ANTHROPIC_API_KEY to the client.** Always call Anthropic from server-side API routes.
1144
- 2. **Use claude-sonnet-4-6 for most features.** Use claude-haiku-4-5 for high-volume, cost-sensitive tasks. Claude opus is for complex reasoning.
1145
- 3. **Anthropic uses a different message format than OpenAI.** Role is "user" or "assistant" (no "system" role in messages). System prompt is a separate top-level field.
1146
- 4. **Set max_tokens explicitly.** Unlike OpenAI, Anthropic requires max_tokens on every request.
1147
- 5. **Streaming is the default UX pattern.** Use \`anthropic.messages.stream()\` not \`anthropic.messages.create()\` for chat.
1148
- 6. **Long streaming responses are fine on Mistflow Cloud.** Network I/O does NOT count toward CPU time.
1149
- 7. **Never ask the user to paste ANTHROPIC_API_KEY in chat.** Direct them to set it in the Mistflow dashboard (Project Settings > Environment Variables).`},{id:"openrouter-ai",name:"OpenRouter",category:"ai",prompt:`## OpenRouter Integration
1150
-
1151
- OpenRouter provides a single API for 200+ AI models (GPT-4o, Claude, Llama, Mistral, Gemini, etc.). It uses an OpenAI-compatible API, so you use the standard OpenAI SDK with a different base URL and API key.
1152
-
1153
- ### File Structure
1154
- \`\`\`
1155
- lib/openrouter.ts \u2014 OpenRouter client
1156
- app/api/chat/route.ts \u2014 Streaming chat API route
1157
- components/chat.tsx \u2014 Chat UI component with streaming
1158
- components/model-selector.tsx \u2014 Model picker dropdown
1159
- \`\`\`
1160
-
1161
- ### Client Setup (lib/openrouter.ts)
1162
- \`\`\`typescript
1163
- import { OpenRouter } from "@openrouter/sdk";
1164
-
1165
- export const openrouter = new OpenRouter({
1166
- apiKey: process.env.OPENROUTER_API_KEY,
1167
- });
1168
-
1169
- // Popular models \u2014 use these as defaults or in a model selector
1170
- export const MODELS = {
1171
- fast: "meta-llama/llama-3.1-8b-instruct",
1172
- balanced: "anthropic/claude-sonnet-4-6",
1173
- powerful: "openai/gpt-4o",
1174
- cheap: "meta-llama/llama-3.1-8b-instruct",
1175
- } as const;
1176
- \`\`\`
1177
-
1178
- ### Streaming Chat Route (app/api/chat/route.ts)
1179
- \`\`\`typescript
1180
- import { openrouter, MODELS } from "@/lib/openrouter";
1181
- import { NextRequest } from "next/server";
1182
-
1183
- export async function POST(req: NextRequest) {
1184
- const { messages, systemPrompt, model } = await req.json();
1185
-
1186
- const completion = await openrouter.chat.send({
1187
- model: model || MODELS.balanced,
1188
- messages: [
1189
- { role: "system", content: systemPrompt || "You are a helpful assistant." },
1190
- ...messages,
1191
- ],
1192
- stream: true,
1193
- });
1194
-
1195
- const encoder = new TextEncoder();
1196
- const readable = new ReadableStream({
1197
- async start(controller) {
1198
- for await (const chunk of completion) {
1199
- const text = chunk.choices?.[0]?.delta?.content || "";
1200
- if (text) controller.enqueue(encoder.encode(text));
1201
- }
1202
- controller.close();
1203
- },
1204
- });
1205
-
1206
- return new Response(readable, {
1207
- headers: { "Content-Type": "text/plain; charset=utf-8" },
1208
- });
1209
- }
1210
- \`\`\`
1211
-
1212
- ### Model Selector Component (components/model-selector.tsx)
1213
- \`\`\`tsx
1214
- "use client";
1215
-
1216
- const MODELS = [
1217
- { id: "anthropic/claude-sonnet-4-6", label: "Claude Sonnet", tier: "Balanced" },
1218
- { id: "openai/gpt-4o", label: "GPT-4o", tier: "Powerful" },
1219
- { id: "openai/gpt-4o-mini", label: "GPT-4o Mini", tier: "Fast" },
1220
- { id: "meta-llama/llama-3.1-70b-instruct", label: "Llama 3.1 70B", tier: "Open Source" },
1221
- { id: "google/gemini-2.0-flash-001", label: "Gemini 2.0 Flash", tier: "Fast" },
1222
- ];
1223
-
1224
- interface ModelSelectorProps {
1225
- value: string;
1226
- onChange: (model: string) => void;
1227
- }
1228
-
1229
- export function ModelSelector({ value, onChange }: ModelSelectorProps) {
1230
- return (
1231
- <select
1232
- value={value}
1233
- onChange={(e) => onChange(e.target.value)}
1234
- className="rounded-md border px-2 py-1 text-sm"
1235
- >
1236
- {MODELS.map((m) => (
1237
- <option key={m.id} value={m.id}>
1238
- {m.label} ({m.tier})
1239
- </option>
1240
- ))}
1241
- </select>
1242
- );
1243
- }
1244
- \`\`\`
1245
-
1246
- ### Common Pitfalls
1247
- 1. **Never expose OPENROUTER_API_KEY to the client.** Always call OpenRouter from server-side API routes.
1248
- 2. **Model IDs use provider/model format.** E.g. "anthropic/claude-sonnet-4-6", not just "claude-sonnet".
1249
- 3. **Use the \`@openrouter/sdk\` package.** It wraps the OpenRouter API with proper types and streaming support.
1250
- 4. **Set max_tokens explicitly** for Anthropic models routed through OpenRouter. OpenAI models have defaults, Anthropic models don't.
1251
- 5. **Check model pricing at openrouter.ai/models.** Costs vary 100x between models. Show users which model they're using.
1252
- 6. **Long streaming responses are fine on Mistflow Cloud.** Network I/O does NOT count toward CPU time.
1253
- 7. **Never ask the user to paste OPENROUTER_API_KEY in chat.** Direct them to set it in the Mistflow dashboard (Project Settings > Environment Variables).`},{id:"stripe-payments",name:"Stripe Payments",category:"payments",prompt:`## Stripe Payments Integration
955
+ 6. **Never ask the user to paste RESEND_API_KEY in chat.** Direct them to set it in the Mistflow dashboard (Project Settings > Environment Variables).`},{id:"stripe-payments",name:"Stripe Payments",category:"payments",prompt:`## Stripe Payments Integration
1254
956
 
1255
957
  ### File Structure
1256
958
  \`\`\`
@@ -2497,16 +2199,16 @@ the tool fires.
2497
2199
  stuck; a hard cap is the cheapest safety net.
2498
2200
  3. **Don't echo user prompts back to the model as tool output.** The model
2499
2201
  will get confused about whose turn it is. Tool output is OUTPUT from
2500
- the tool, not from the user.`}];function Nc(t){return t.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}function Yt(t){let e=Vt.find(n=>n.id===t);if(e)return e;let o=t.toLowerCase().replace(/[^a-z0-9]/g,"");return Vt.find(n=>{let r=n.name.toLowerCase().replace(/[^a-z0-9]/g,"");return r===o||r.includes(o)||o.includes(r)})}function Qt(t){return _r[t]}function Cr(t){return t?Vt.filter(e=>e.category.toLowerCase()===t.toLowerCase()):Vt}function Ls(t){let e=t??Vt,o={};for(let r of e){o[r.category]||(o[r.category]=[]);let s=_r[r.id],i=s?` \u2014 ${s.description}`:"",a=s?.packages.length?` (${s.packages.join(", ")})`:"";o[r.category].push(`${r.id} \u2014 "${r.name}"${i}${a}`)}let n=[];for(let[r,s]of Object.entries(o))n.push(`**${r}**:
2202
+ the tool, not from the user.`}];function Fc(t){return t.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}function Qt(t){let e=Yt.find(n=>n.id===t);if(e)return e;let r=t.toLowerCase().replace(/[^a-z0-9]/g,"");return Yt.find(n=>{let o=n.name.toLowerCase().replace(/[^a-z0-9]/g,"");return o===r||o.includes(r)||r.includes(o)})}function Xt(t){return Co[t]}function Io(t){return t?Yt.filter(e=>e.category.toLowerCase()===t.toLowerCase()):Yt}function Ks(t){let e=t??Yt,r={};for(let o of e){r[o.category]||(r[o.category]=[]);let s=Co[o.id],i=s?` \u2014 ${s.description}`:"",a=s?.packages.length?` (${s.packages.join(", ")})`:"";r[o.category].push(`${o.id} \u2014 "${o.name}"${i}${a}`)}let n=[];for(let[o,s]of Object.entries(r))n.push(`**${o}**:
2501
2203
  ${s.map(i=>` - ${i}`).join(`
2502
2204
  `)}`);return n.join(`
2503
2205
 
2504
- `)}function $s(t){return(t?Cr(t):Vt).map(o=>{let n=_r[o.id];return{id:o.id,slug:Nc(o.name),name:o.name,category:o.category,description:n?.description??"",tags:n?.tags??[],envVars:n?.envVars??[],docsUrl:n?.docsUrl??"",packages:n?.packages??[],difficulty:n?.difficulty??"medium"}})}var Oc=Le.object({action:Le.enum(["get","update","share","integrations","errors","logs","deployments","cron","cron-logs","cron-toggle","cron-trigger","version"]).default("get").describe("'get' reads current project state. 'update' marks steps complete or adds env vars. 'share' makes the project a shareable template. 'integrations' lists third-party service integration blueprints (Stripe, Resend, ElevenLabs, etc.) with setup guides. 'errors' fetches runtime errors from the deployed app. 'logs' fetches deploy logs for a specific deployment. 'deployments' lists deployment history. 'cron' lists the project's cron jobs and their current status. 'cron-logs' returns recent execution history for one cron job (pass cronJobId). 'cron-toggle' enables or disables a cron job (pass cronJobId + cronEnabled). 'cron-trigger' fires a cron job once now via a QStash one-off publish (pass cronJobId). 'version' reports the installed @mistflow-ai/mcp version and whether an upgrade is available."),projectPath:Le.string().optional().describe("Path to the project directory (default: cwd)"),completedStep:Le.number().optional().describe("(update) Mark a plan step as completed by step number"),addEnvVar:Le.object({key:Le.string(),description:Le.string().optional(),setupUrl:Le.string().optional()}).optional().describe("(update) Add a required env var to the project manifest"),templateDescription:Le.string().optional().describe("(share) Short description of what this template builds"),category:Le.string().optional().describe("(integrations) Filter integrations by category"),integrationId:Le.string().optional().describe("(integrations) Get full details for a specific integration preset by ID (e.g. 'stripe-payments', 'resend-email', 'elevenlabs-voice')"),period:Le.string().optional().describe("(errors) Time period for errors: '1h', '24h', '7d' (default: '7d')"),deploymentId:Le.string().optional().describe("(logs) Deployment ID to fetch logs for. If omitted, fetches logs for the latest deployment."),cronJobId:Le.string().optional().describe("(cron-logs / cron-toggle / cron-trigger) UUID of the cron job"),cronEnabled:Le.boolean().optional().describe("(cron-toggle) true to enable, false to pause")}),Us={name:"mist_project",description:Xr,inputSchema:Oc,handler:async t=>{let e=t;if(["share","errors","logs","deployments","cron","cron-logs","cron-toggle","cron-trigger"].includes(e.action)&&!Te())return Oe(`${e.action} project state`);let n=()=>{let r=In(e.projectPath??process.cwd()),s=An(r,"mistflow.json");if(!Pn(s))return{error:`Not a Mistflow project (no mistflow.json found at ${r}). Run mist_init to create one.`};let i;try{i=JSON.parse(Rn(s,"utf-8"))}catch{return{error:"Could not read mistflow.json."}}let a=i.projectId;return a?{id:a}:{error:"This project hasn't been registered with Mistflow yet. Run mist_init first."}};switch(e.action){case"get":case"update":return Ds.handler({action:e.action,projectPath:e.projectPath,completedStep:e.completedStep,addEnvVar:e.addEnvVar});case"share":{let r=In(e.projectPath??process.cwd()),s=An(r,"mistflow.json");if(!Pn(s))return Ve(r);let i;try{i=JSON.parse(Rn(s,"utf-8"))}catch{return d("Could not read mistflow.json.",!0)}let a=i.projectId;if(!a)return d("No project ID found. Deploy the project first to register it.",!0);try{let l=await gr(a,{isTemplate:!0,description:e.templateDescription});return d(JSON.stringify({shareUrl:l.share_url,shareToken:l.share_token,message:`Your project is now a shareable template!
2206
+ `)}function Vs(t){return(t?Io(t):Yt).map(r=>{let n=Co[r.id];return{id:r.id,slug:Fc(r.name),name:r.name,category:r.category,description:n?.description??"",tags:n?.tags??[],envVars:n?.envVars??[],docsUrl:n?.docsUrl??"",packages:n?.packages??[],difficulty:n?.difficulty??"medium"}})}var qc=$e.object({action:$e.enum(["get","update","share","integrations","errors","logs","deployments","cron","cron-logs","cron-toggle","cron-trigger","version"]).default("get").describe("'get' reads current project state. 'update' marks steps complete or adds env vars. 'share' makes the project a shareable template. 'integrations' lists third-party service integration blueprints (Stripe, Resend, ElevenLabs, etc.) with setup guides. 'errors' fetches runtime errors from the deployed app. 'logs' fetches deploy logs for a specific deployment. 'deployments' lists deployment history. 'cron' lists the project's cron jobs and their current status. 'cron-logs' returns recent execution history for one cron job (pass cronJobId). 'cron-toggle' enables or disables a cron job (pass cronJobId + cronEnabled). 'cron-trigger' fires a cron job once now via a QStash one-off publish (pass cronJobId). 'version' reports the installed @mistflow-ai/mcp version and whether an upgrade is available."),projectPath:$e.string().optional().describe("Path to the project directory (default: cwd)"),completedStep:$e.number().optional().describe("(update) Mark a plan step as completed by step number"),addEnvVar:$e.object({key:$e.string(),description:$e.string().optional(),setupUrl:$e.string().optional()}).optional().describe("(update) Add a required env var to the project manifest"),templateDescription:$e.string().optional().describe("(share) Short description of what this template builds"),category:$e.string().optional().describe("(integrations) Filter integrations by category"),integrationId:$e.string().optional().describe("(integrations) Get full details for a specific integration preset by ID (e.g. 'stripe-payments', 'resend-email', 'elevenlabs-voice')"),period:$e.string().optional().describe("(errors) Time period for errors: '1h', '24h', '7d' (default: '7d')"),deploymentId:$e.string().optional().describe("(logs) Deployment ID to fetch logs for. If omitted, fetches logs for the latest deployment."),cronJobId:$e.string().optional().describe("(cron-logs / cron-toggle / cron-trigger) UUID of the cron job"),cronEnabled:$e.boolean().optional().describe("(cron-toggle) true to enable, false to pause")}),Ys={name:"mist_project",description:os,inputSchema:qc,handler:async t=>{let e=t;if(["share","errors","logs","deployments","cron","cron-logs","cron-toggle","cron-trigger"].includes(e.action)&&!Te())return Oe(`${e.action} project state`);let n=()=>{let o=Rn(e.projectPath??process.cwd()),s=Nn(o,"mistflow.json");if(!An(s))return{error:`Not a Mistflow project (no mistflow.json found at ${o}). Run mist_init to create one.`};let i;try{i=JSON.parse(En(s,"utf-8"))}catch{return{error:"Could not read mistflow.json."}}let a=i.projectId;return a?{id:a}:{error:"This project hasn't been registered with Mistflow yet. Run mist_init first."}};switch(e.action){case"get":case"update":return Js.handler({action:e.action,projectPath:e.projectPath,completedStep:e.completedStep,addEnvVar:e.addEnvVar});case"share":{let o=Rn(e.projectPath??process.cwd()),s=Nn(o,"mistflow.json");if(!An(s))return et(o);let i;try{i=JSON.parse(En(s,"utf-8"))}catch{return u("Could not read mistflow.json.",!0)}let a=i.projectId;if(!a)return u("No project ID found. Deploy the project first to register it.",!0);try{let l=await yo(a,{isTemplate:!0,description:e.templateDescription});return u(JSON.stringify({shareUrl:l.share_url,shareToken:l.share_token,message:`Your project is now a shareable template!
2505
2207
 
2506
2208
  Anyone can fork it: ${l.share_url}
2507
2209
 
2508
2210
  Others can use it in their AI editor:
2509
- "build me something like ${l.share_url}"`}))}catch(l){let c=l instanceof Error?l.message:"Failed to share project";return d(c,!0)}}case"integrations":{if(e.integrationId){let a=Yt(e.integrationId);if(!a)return d(`Integration '${e.integrationId}' not found. Use mist_project action='integrations' without integrationId to list all available integrations.`,!0);let l=Qt(a.id);return d(JSON.stringify({integration:{id:a.id,name:a.name,category:a.category,description:l?.description??"",packages:l?.packages??[],envVars:l?.envVars??[],docsUrl:l?.docsUrl??"",difficulty:l?.difficulty??"medium"},message:`Integration "${a.name}" (${a.category}) \u2014 ${l?.description??""}. This blueprint is auto-injected during implementation when your plan has a matching integration step. Required env vars: ${l?.envVars?.map(c=>c.key).join(", ")||"none"}. Docs: ${l?.docsUrl??"n/a"}.`}))}let r=$s(e.category??void 0),s=Cr(e.category??void 0),i=Ls(s);return d(JSON.stringify({count:r.length,integrations:r.map(a=>({id:a.id,name:a.name,category:a.category,description:a.description,packages:a.packages,difficulty:a.difficulty,envVars:a.envVars.map(l=>l.key)})),formatted:i,message:`${r.length} integration blueprints available.${e.category?` Filtered by: ${e.category}.`:""} Integration blueprints are auto-injected during implementation when your plan includes a matching integration step. Use integrationId to see full details including env vars and setup URLs.`}))}case"errors":{let r=In(e.projectPath??process.cwd()),s=An(r,"mistflow.json");if(!Pn(s))return Ve(r);let i;try{i=JSON.parse(Rn(s,"utf-8"))}catch{return d("Could not read mistflow.json.",!0)}let a=i.projectId;if(!a)return d("No project ID found. Deploy the project first.",!0);try{let l=await er(a,e.period??"7d");return l.total===0?d(JSON.stringify({total:0,period:l.period,message:`No runtime errors in the last ${l.period}. The app is running clean.`})):d(JSON.stringify({total:l.total,period:l.period,errors:l.errors,message:`${l.total} runtime error(s) in the last ${l.period}. Review the errors above and use mist_debug to investigate.`}))}catch(l){let c=l instanceof Error?l.message:"Failed to fetch errors";return d(c,!0)}}case"logs":{let r=In(e.projectPath??process.cwd()),s=An(r,"mistflow.json");if(!Pn(s))return Ve(r);let i;try{i=JSON.parse(Rn(s,"utf-8"))}catch{return d("Could not read mistflow.json.",!0)}let a=i.projectId;if(!a)return d("No project ID found. Deploy the project first.",!0);let l=e.deploymentId;if(!l)try{let c=await Kt(a);if(c.length===0)return d("No deployments found for this project.",!0);l=c[0].id}catch(c){let m=c instanceof Error?c.message:"Failed to fetch deployments";return d(m,!0)}try{let[c,m]=await Promise.all([Zo(l),yt(l)]),u=c.filter(h=>h.level==="error"),p=c.filter(h=>h.level==="warn");return d(JSON.stringify({deploymentId:l,status:m.status,errorMessage:m.error??null,totalLogs:c.length,errorCount:u.length,warnCount:p.length,logs:c.map(h=>({time:h.timestamp,level:h.level,phase:h.phase,message:h.message})),message:m.status==="failed"?`Deployment failed. ${u.length} error(s) found in logs. Review the logs above to diagnose the issue.`:`Deployment status: ${m.status}. ${c.length} log entries (${u.length} errors, ${p.length} warnings).`}))}catch(c){let m=c instanceof Error?c.message:"Failed to fetch deploy logs";return d(m,!0)}}case"deployments":{let r=In(e.projectPath??process.cwd()),s=An(r,"mistflow.json");if(!Pn(s))return Ve(r);let i;try{i=JSON.parse(Rn(s,"utf-8"))}catch{return d("Could not read mistflow.json.",!0)}let a=i.projectId;if(!a)return d("No project ID found. Deploy the project first.",!0);try{let l=await Kt(a);return d(JSON.stringify({total:l.length,deployments:l.map(c=>({id:c.id,status:c.status,errorMessage:c.error_message,durationSeconds:c.duration_seconds,isRollback:!!c.rollback_from_id,createdAt:c.created_at})),message:`${l.length} deployment(s) found. Use mist_project action='logs' deploymentId='<id>' to see detailed logs for any deployment.`}))}catch(l){let c=l instanceof Error?l.message:"Failed to fetch deployments";return d(c,!0)}}case"cron":{let r=n();if(r.error)return d(r.error,!0);try{let s=await tr(r.id);return d(JSON.stringify({count:s.length,cronJobs:s.map(i=>({id:i.id,name:i.name,schedule:i.schedule,timezone:i.timezone,description:i.description,enabled:i.enabled,disabledReason:i.disabled_reason,consecutiveFailures:i.consecutive_failures,lastRunAt:i.last_run_at,lastStatus:i.last_status})),message:s.length===0?"No cron jobs configured. Add a `cron` array to mistflow.json and redeploy.":`${s.length} cron job(s). Use cron-logs / cron-toggle / cron-trigger with cronJobId to drill in.`}))}catch(s){return d(s instanceof Error?s.message:"Failed to list cron jobs",!0)}}case"cron-logs":{let r=n();if(r.error)return d(r.error,!0);if(!e.cronJobId)return d("cron-logs requires `cronJobId`.",!0);try{let s=await rr(r.id,e.cronJobId);return d(JSON.stringify({total:s.length,logs:s.map(i=>({triggerId:i.trigger_id,scheduledAt:i.scheduled_at,deliveredAt:i.delivered_at,status:i.status,statusCode:i.status_code,durationMs:i.duration_ms,error:i.error}))}))}catch(s){return d(s instanceof Error?s.message:"Failed to fetch cron logs",!0)}}case"cron-toggle":{let r=n();if(r.error)return d(r.error,!0);if(!e.cronJobId||typeof e.cronEnabled!="boolean")return d("cron-toggle requires `cronJobId` and `cronEnabled` (true/false).",!0);try{let s=await nr(r.id,e.cronJobId,e.cronEnabled);return d(JSON.stringify({id:s.id,name:s.name,enabled:s.enabled,message:s.enabled?`Cron "${s.name}" enabled. It will resume firing on schedule within ~60s.`:`Cron "${s.name}" paused. It will stop firing within ~60s.`}))}catch(s){return d(s instanceof Error?s.message:"Failed to toggle cron job",!0)}}case"cron-trigger":{let r=n();if(r.error)return d(r.error,!0);if(!e.cronJobId)return d("cron-trigger requires `cronJobId`.",!0);try{let s=await or(r.id,e.cronJobId);return d(JSON.stringify({...s,message:"Queued for immediate execution. Logs will appear in cron-logs within a few seconds."}))}catch(s){return d(s instanceof Error?s.message:"Failed to trigger cron job",!0)}}case"version":{Lo().backendSignalReceived||await Ho();let r=Lo(),s=r.severity==="none",i={none:"up to date",patch:"patch update available",minor:"minor update available",major:"major update available",unsupported:"UNSUPPORTED \u2014 upgrade required"};return d(JSON.stringify({current:r.current,latest:r.latest||"unknown",minSupported:r.minSupported||"unknown",severity:r.severity,upToDate:s,upgradeCmd:r.upgradeCmd,changelogUrl:r.changelogUrl,backendSignalReceived:r.backendSignalReceived,message:r.backendSignalReceived?`Mistflow MCP ${r.current} (${i[r.severity]??r.severity}). Latest: ${r.latest}.${s?"":` Run \`${r.upgradeCmd}\` and restart your editor to upgrade.`}`:`Mistflow MCP ${r.current}. The backend hasn't replied yet \u2014 make one other API call (e.g. mist_project action='get') then retry to see the latest version.`}))}default:return d(`Unknown action: ${e.action}. Use get, update, share, integrations, errors, logs, deployments, cron, cron-logs, cron-toggle, cron-trigger, or version.`,!0)}}};import{z as Dt}from"zod";zt();var Mc=Dt.object({action:Dt.enum(["navigate","go_back","go_forward","click","type","fill","select_option","press_key","hover","screenshot","snapshot"]).describe("Action to perform. Navigation: navigate|go_back|go_forward. Interaction: click|type|fill|select_option|press_key|hover. Visual: screenshot (returns image) | snapshot (returns accessibility tree)."),url:Dt.string().optional().describe("URL to navigate to. Required for 'navigate'; optional for 'screenshot' (navigates before capturing)."),selector:Dt.string().optional().describe("CSS selector of the target element. Required for: click, type, fill, select_option, hover. Optional for screenshot (captures just that element)."),value:Dt.string().optional().describe("Text to type/fill, option to select, or key to press (e.g. 'Enter', 'Tab'). Required for: type, fill, select_option, press_key."),fullPage:Dt.boolean().default(!1).describe("For 'screenshot': capture the full scrollable page instead of just the viewport."),includeScreenshot:Dt.boolean().default(!1).describe("For navigate/interact actions: also return a screenshot alongside the accessibility snapshot.")}),qs={name:"mist_browser",description:Zr,inputSchema:Mc,handler:async t=>{let e=t,o=await $o();if(e.action==="navigate"){if(!e.url)return d("URL is required for 'navigate'.",!0);let s=[],i=c=>{c.type()==="error"&&s.push(c.text())};o.on("console",i),await o.goto(e.url,{waitUntil:"domcontentloaded",timeout:3e4}),await o.waitForLoadState("networkidle").catch(()=>{});let a=[],l=c=>a.push(c.message);if(o.on("pageerror",l),await o.waitForTimeout(500),o.removeListener("console",i),o.removeListener("pageerror",l),s.length>0||a.length>0){let c=await bn(o),m=[{type:"text",text:JSON.stringify({url:o.url(),title:await o.title(),snapshot:c,consoleErrors:s,pageErrors:a,hasErrors:!0})}];if(e.includeScreenshot){let u=await wn(o);m.push({type:"image",data:u.toString("base64"),mimeType:"image/png"})}return{content:m}}}else if(e.action==="go_back")await o.goBack({waitUntil:"domcontentloaded",timeout:1e4});else if(e.action==="go_forward")await o.goForward({waitUntil:"domcontentloaded",timeout:1e4});else if(e.action==="click"){if(!e.selector)return d("Selector is required for 'click'.",!0);await o.click(e.selector,{timeout:1e4}),await o.waitForLoadState("domcontentloaded").catch(()=>{}),await o.waitForTimeout(500)}else if(e.action==="type"){if(!e.selector)return d("Selector is required for 'type'.",!0);if(!e.value)return d("Value is required for 'type'.",!0);await o.type(e.selector,e.value,{delay:50})}else if(e.action==="fill"){if(!e.selector)return d("Selector is required for 'fill'.",!0);if(!e.value)return d("Value is required for 'fill'.",!0);await o.fill(e.selector,e.value)}else if(e.action==="select_option"){if(!e.selector)return d("Selector is required for 'select_option'.",!0);if(!e.value)return d("Value is required for 'select_option'.",!0);await o.selectOption(e.selector,e.value)}else if(e.action==="hover"){if(!e.selector)return d("Selector is required for 'hover'.",!0);await o.hover(e.selector,{timeout:1e4})}else if(e.action==="press_key"){if(!e.value)return d("Value is required for 'press_key' (e.g. 'Enter').",!0);await o.keyboard.press(e.value),await o.waitForLoadState("domcontentloaded").catch(()=>{}),await o.waitForTimeout(500)}else if(e.action==="screenshot"){e.url&&(await o.goto(e.url,{waitUntil:"domcontentloaded",timeout:3e4}),await o.waitForLoadState("networkidle").catch(()=>{}));let s;if(e.selector){let i=await o.$(e.selector);if(!i)return d(`Element not found: ${e.selector}`,!0);s=await i.screenshot({type:"png"})}else s=await wn(o,e.fullPage);return{content:[{type:"text",text:JSON.stringify({url:o.url(),title:await o.title(),message:`Screenshot captured (${e.fullPage?"full page":"viewport"})`})},{type:"image",data:s.toString("base64"),mimeType:"image/png"}]}}else if(e.action==="snapshot"){let s=await bn(o);return{content:[{type:"text",text:JSON.stringify({url:o.url(),title:await o.title(),snapshot:s})}]}}let n=await bn(o),r=[{type:"text",text:JSON.stringify({url:o.url(),title:await o.title(),snapshot:n})}];if(e.includeScreenshot){let s=await wn(o);r.push({type:"image",data:s.toString("base64"),mimeType:"image/png"})}return{content:r}}};import{z as Fs}from"zod";var Bs=`# Mistflow MCP tool reference
2211
+ "build me something like ${l.share_url}"`}))}catch(l){let c=l instanceof Error?l.message:"Failed to share project";return u(c,!0)}}case"integrations":{if(e.integrationId){let a=Qt(e.integrationId);if(!a)return u(`Integration '${e.integrationId}' not found. Use mist_project action='integrations' without integrationId to list all available integrations.`,!0);let l=Xt(a.id);return u(JSON.stringify({integration:{id:a.id,name:a.name,category:a.category,description:l?.description??"",packages:l?.packages??[],envVars:l?.envVars??[],docsUrl:l?.docsUrl??"",difficulty:l?.difficulty??"medium"},message:`Integration "${a.name}" (${a.category}) \u2014 ${l?.description??""}. This blueprint is auto-injected during implementation when your plan has a matching integration step. Required env vars: ${l?.envVars?.map(c=>c.key).join(", ")||"none"}. Docs: ${l?.docsUrl??"n/a"}.`}))}let o=Vs(e.category??void 0),s=Io(e.category??void 0),i=Ks(s);return u(JSON.stringify({count:o.length,integrations:o.map(a=>({id:a.id,name:a.name,category:a.category,description:a.description,packages:a.packages,difficulty:a.difficulty,envVars:a.envVars.map(l=>l.key)})),formatted:i,message:`${o.length} integration blueprints available.${e.category?` Filtered by: ${e.category}.`:""} Integration blueprints are auto-injected during implementation when your plan includes a matching integration step. Use integrationId to see full details including env vars and setup URLs.`}))}case"errors":{let o=Rn(e.projectPath??process.cwd()),s=Nn(o,"mistflow.json");if(!An(s))return et(o);let i;try{i=JSON.parse(En(s,"utf-8"))}catch{return u("Could not read mistflow.json.",!0)}let a=i.projectId;if(!a)return u("No project ID found. Deploy the project first.",!0);try{let l=await to(a,e.period??"7d");return l.total===0?u(JSON.stringify({total:0,period:l.period,message:`No runtime errors in the last ${l.period}. The app is running clean.`})):u(JSON.stringify({total:l.total,period:l.period,errors:l.errors,message:`${l.total} runtime error(s) in the last ${l.period}. Review the errors above and check mist_project action='logs' for surrounding context. mist_debug is for build errors; for runtime errors, fix the code directly.`}))}catch(l){let c=l instanceof Error?l.message:"Failed to fetch errors";return u(c,!0)}}case"logs":{let o=Rn(e.projectPath??process.cwd()),s=Nn(o,"mistflow.json");if(!An(s))return et(o);let i;try{i=JSON.parse(En(s,"utf-8"))}catch{return u("Could not read mistflow.json.",!0)}let a=i.projectId;if(!a)return u("No project ID found. Deploy the project first.",!0);let l=e.deploymentId;if(!l)try{let c=await Kt(a);if(c.length===0)return u("No deployments found for this project.",!0);l=c[0].id}catch(c){let p=c instanceof Error?c.message:"Failed to fetch deployments";return u(p,!0)}try{let[c,p]=await Promise.all([eo(l),wt(l)]),d=c.filter(h=>h.level==="error"),m=c.filter(h=>h.level==="warn");return u(JSON.stringify({deploymentId:l,status:p.status,errorMessage:p.error??null,totalLogs:c.length,errorCount:d.length,warnCount:m.length,logs:c.map(h=>({time:h.timestamp,level:h.level,phase:h.phase,message:h.message})),message:p.status==="failed"?`Deployment failed. ${d.length} error(s) found in logs. Review the logs above to diagnose the issue.`:`Deployment status: ${p.status}. ${c.length} log entries (${d.length} errors, ${m.length} warnings).`}))}catch(c){let p=c instanceof Error?c.message:"Failed to fetch deploy logs";return u(p,!0)}}case"deployments":{let o=Rn(e.projectPath??process.cwd()),s=Nn(o,"mistflow.json");if(!An(s))return et(o);let i;try{i=JSON.parse(En(s,"utf-8"))}catch{return u("Could not read mistflow.json.",!0)}let a=i.projectId;if(!a)return u("No project ID found. Deploy the project first.",!0);try{let l=await Kt(a);return u(JSON.stringify({total:l.length,deployments:l.map(c=>({id:c.id,status:c.status,errorMessage:c.error_message,durationSeconds:c.duration_seconds,isRollback:!!c.rollback_from_id,createdAt:c.created_at})),message:`${l.length} deployment(s) found. Use mist_project action='logs' deploymentId='<id>' to see detailed logs for any deployment.`}))}catch(l){let c=l instanceof Error?l.message:"Failed to fetch deployments";return u(c,!0)}}case"cron":{let o=n();if(o.error)return u(o.error,!0);try{let s=await no(o.id);return u(JSON.stringify({count:s.length,cronJobs:s.map(i=>({id:i.id,name:i.name,schedule:i.schedule,timezone:i.timezone,description:i.description,enabled:i.enabled,disabledReason:i.disabled_reason,consecutiveFailures:i.consecutive_failures,lastRunAt:i.last_run_at,lastStatus:i.last_status})),message:s.length===0?"No cron jobs configured. Add a `cron` array to mistflow.json and redeploy.":`${s.length} cron job(s). Use cron-logs / cron-toggle / cron-trigger with cronJobId to drill in.`}))}catch(s){return u(s instanceof Error?s.message:"Failed to list cron jobs",!0)}}case"cron-logs":{let o=n();if(o.error)return u(o.error,!0);if(!e.cronJobId)return u("cron-logs requires `cronJobId`.",!0);try{let s=await so(o.id,e.cronJobId);return u(JSON.stringify({total:s.length,logs:s.map(i=>({triggerId:i.trigger_id,scheduledAt:i.scheduled_at,deliveredAt:i.delivered_at,status:i.status,statusCode:i.status_code,durationMs:i.duration_ms,error:i.error}))}))}catch(s){return u(s instanceof Error?s.message:"Failed to fetch cron logs",!0)}}case"cron-toggle":{let o=n();if(o.error)return u(o.error,!0);if(!e.cronJobId||typeof e.cronEnabled!="boolean")return u("cron-toggle requires `cronJobId` and `cronEnabled` (true/false).",!0);try{let s=await ro(o.id,e.cronJobId,e.cronEnabled);return u(JSON.stringify({id:s.id,name:s.name,enabled:s.enabled,message:s.enabled?`Cron "${s.name}" enabled. It will resume firing on schedule within ~60s.`:`Cron "${s.name}" paused. It will stop firing within ~60s.`}))}catch(s){return u(s instanceof Error?s.message:"Failed to toggle cron job",!0)}}case"cron-trigger":{let o=n();if(o.error)return u(o.error,!0);if(!e.cronJobId)return u("cron-trigger requires `cronJobId`.",!0);try{let s=await oo(o.id,e.cronJobId);return u(JSON.stringify({...s,message:"Queued for immediate execution. Logs will appear in cron-logs within a few seconds."}))}catch(s){return u(s instanceof Error?s.message:"Failed to trigger cron job",!0)}}case"version":{$r().backendSignalReceived||await Hr();let o=$r(),s=o.severity==="none",i={none:"up to date",patch:"patch update available",minor:"minor update available",major:"major update available",unsupported:"UNSUPPORTED \u2014 upgrade required"};return u(JSON.stringify({current:o.current,latest:o.latest||"unknown",minSupported:o.minSupported||"unknown",severity:o.severity,upToDate:s,upgradeCmd:o.upgradeCmd,changelogUrl:o.changelogUrl,backendSignalReceived:o.backendSignalReceived,message:o.backendSignalReceived?`Mistflow MCP ${o.current} (${i[o.severity]??o.severity}). Latest: ${o.latest}.${s?"":` Run \`${o.upgradeCmd}\` and restart your editor to upgrade.`}`:`Mistflow MCP ${o.current}. The backend hasn't replied yet \u2014 make one other API call (e.g. mist_project action='get') then retry to see the latest version.`}))}default:return u(`Unknown action: ${e.action}. Use get, update, share, integrations, errors, logs, deployments, cron, cron-logs, cron-toggle, cron-trigger, or version.`,!0)}}};import{z as Lt}from"zod";Wt();var Bc=Lt.object({action:Lt.enum(["navigate","go_back","go_forward","click","type","fill","select_option","press_key","hover","screenshot","snapshot"]).describe("Action to perform. Navigation: navigate|go_back|go_forward. Interaction: click|type|fill|select_option|press_key|hover. Visual: screenshot (returns image) | snapshot (returns accessibility tree)."),url:Lt.string().optional().describe("URL to navigate to. Required for 'navigate'; optional for 'screenshot' (navigates before capturing)."),selector:Lt.string().optional().describe("CSS selector of the target element. Required for: click, type, fill, select_option, hover. Optional for screenshot (captures just that element)."),value:Lt.string().optional().describe("Text to type/fill, option to select, or key to press (e.g. 'Enter', 'Tab'). Required for: type, fill, select_option, press_key."),fullPage:Lt.boolean().default(!1).describe("For 'screenshot': capture the full scrollable page instead of just the viewport."),includeScreenshot:Lt.boolean().default(!1).describe("For navigate/interact actions: also return a screenshot alongside the accessibility snapshot.")}),Qs={name:"mist_browser",description:ss,inputSchema:Bc,handler:async t=>{let e=t,r=await Ur();if(e.action==="navigate"){if(!e.url)return u("URL is required for 'navigate'.",!0);let s=[],i=c=>{c.type()==="error"&&s.push(c.text())};r.on("console",i),await r.goto(e.url,{waitUntil:"domcontentloaded",timeout:3e4}),await r.waitForLoadState("networkidle").catch(()=>{});let a=[],l=c=>a.push(c.message);if(r.on("pageerror",l),await r.waitForTimeout(500),r.removeListener("console",i),r.removeListener("pageerror",l),s.length>0||a.length>0){let c=await wn(r),p=[{type:"text",text:JSON.stringify({url:r.url(),title:await r.title(),snapshot:c,consoleErrors:s,pageErrors:a,hasErrors:!0})}];if(e.includeScreenshot){let d=await vn(r);p.push({type:"image",data:d.toString("base64"),mimeType:"image/png"})}return{content:p}}}else if(e.action==="go_back")await r.goBack({waitUntil:"domcontentloaded",timeout:1e4});else if(e.action==="go_forward")await r.goForward({waitUntil:"domcontentloaded",timeout:1e4});else if(e.action==="click"){if(!e.selector)return u("Selector is required for 'click'.",!0);await r.click(e.selector,{timeout:1e4}),await r.waitForLoadState("domcontentloaded").catch(()=>{}),await r.waitForTimeout(500)}else if(e.action==="type"){if(!e.selector)return u("Selector is required for 'type'.",!0);if(!e.value)return u("Value is required for 'type'.",!0);await r.type(e.selector,e.value,{delay:50})}else if(e.action==="fill"){if(!e.selector)return u("Selector is required for 'fill'.",!0);if(!e.value)return u("Value is required for 'fill'.",!0);await r.fill(e.selector,e.value)}else if(e.action==="select_option"){if(!e.selector)return u("Selector is required for 'select_option'.",!0);if(!e.value)return u("Value is required for 'select_option'.",!0);await r.selectOption(e.selector,e.value)}else if(e.action==="hover"){if(!e.selector)return u("Selector is required for 'hover'.",!0);await r.hover(e.selector,{timeout:1e4})}else if(e.action==="press_key"){if(!e.value)return u("Value is required for 'press_key' (e.g. 'Enter').",!0);await r.keyboard.press(e.value),await r.waitForLoadState("domcontentloaded").catch(()=>{}),await r.waitForTimeout(500)}else if(e.action==="screenshot"){e.url&&(await r.goto(e.url,{waitUntil:"domcontentloaded",timeout:3e4}),await r.waitForLoadState("networkidle").catch(()=>{}));let s;if(e.selector){let i=await r.$(e.selector);if(!i)return u(`Element not found: ${e.selector}`,!0);s=await i.screenshot({type:"png"})}else s=await vn(r,e.fullPage);return{content:[{type:"text",text:JSON.stringify({url:r.url(),title:await r.title(),message:`Screenshot captured (${e.fullPage?"full page":"viewport"})`})},{type:"image",data:s.toString("base64"),mimeType:"image/png"}]}}else if(e.action==="snapshot"){let s=await wn(r);return{content:[{type:"text",text:JSON.stringify({url:r.url(),title:await r.title(),snapshot:s})}]}}let n=await wn(r),o=[{type:"text",text:JSON.stringify({url:r.url(),title:await r.title(),snapshot:n})}];if(e.includeScreenshot){let s=await vn(r);o.push({type:"image",data:s.toString("base64"),mimeType:"image/png"})}return{content:o}}};import{z as Xs}from"zod";var Zs=`# Mistflow MCP tool reference
2510
2212
 
2511
2213
  Every capability is an MCP tool. There is no companion CLI. Long-running tools
2512
2214
  (install, build, qa, deploy) use the fire-and-poll pattern \u2014 the first call
@@ -2843,27 +2545,27 @@ is cheaper than building the wrong thing and iterating.
2843
2545
 
2844
2546
  mist_deploy({ action: "rollback", deploymentId: "<last known good>" })
2845
2547
  mist_deploy({ action: "status", deploymentId }) # poll
2846
- `,Hs={name:"mist_help",description:es,inputSchema:Fs.object({command:Fs.string().optional().describe("Optional: name of a specific tool (e.g. 'mist_plan') to get focused reference for. Omit to get the full catalog.")}),handler:async t=>{let{command:e}=t;if(!e)return d(Bs);let o=e.startsWith("mist_")?e:`mist_${e}`,n=Bs.split(`
2847
- `),r=new RegExp(`^### \`${o}`),s=-1,i=n.length;for(let a=0;a<n.length;a++)if(r.test(n[a]))s=a;else if(s>=0&&n[a].startsWith("### ")){i=a;break}else if(s>=0&&n[a].startsWith("## ")&&a>s){i=a;break}return s<0?d(`No tool named '${o}' found. Call mist_help with no args to see the full catalog.`,!0):d(n.slice(s,i).join(`
2848
- `).trim())}};import{z as En}from"zod";Ie();var zs=En.object({action:En.enum(["list","get"]).default("get").describe("'get' (default) returns the full markdown for one topic \u2014 pass `topic`. 'list' returns the topic catalog so you can discover what's available \u2014 use to answer 'is there a doc for X?' before falling back to web search."),topic:En.string().optional().describe("Required for action='get'. Stable topic id (e.g. 'auth-design', 'stripe-integration', 'better-auth'). Call action='list' to discover ids."),kind:En.enum(["stack","skill","integration"]).optional().describe("Optional filter for action='list'. 'stack' = framework methodology, 'skill' = design + structural patterns, 'integration' = third-party services."),stack:En.string().optional().default("nextjs").describe("Stack slug. Defaults to 'nextjs' (currently the only supported stack).")}),Ws={name:"mist_docs",description:ts,inputSchema:zs,handler:async t=>{let{action:e,topic:o,kind:n,stack:r}=zs.parse(t);try{if(e==="list"){let a=await dr(r,n),l=[];l.push(`# Mistflow docs catalog (${a.stack})
2548
+ `,ei={name:"mist_help",description:is,inputSchema:Xs.object({command:Xs.string().optional().describe("Optional: name of a specific tool (e.g. 'mist_plan') to get focused reference for. Omit to get the full catalog.")}),handler:async t=>{let{command:e}=t;if(!e)return u(Zs);let r=e.startsWith("mist_")?e:`mist_${e}`,n=Zs.split(`
2549
+ `),o=new RegExp(`^### \`${r}`),s=-1,i=n.length;for(let a=0;a<n.length;a++)if(o.test(n[a]))s=a;else if(s>=0&&n[a].startsWith("### ")){i=a;break}else if(s>=0&&n[a].startsWith("## ")&&a>s){i=a;break}return s<0?u(`No tool named '${r}' found. Call mist_help with no args to see the full catalog.`,!0):u(n.slice(s,i).join(`
2550
+ `).trim())}};import{z as On}from"zod";ke();var ti=On.object({action:On.enum(["list","get"]).default("get").describe("'get' (default) returns the full markdown for one topic \u2014 pass `topic`. 'list' returns the topic catalog so you can discover what's available \u2014 use to answer 'is there a doc for X?' before falling back to web search."),topic:On.string().optional().describe("Required for action='get'. Stable topic id (e.g. 'auth-design', 'stripe-integration', 'better-auth'). Call action='list' to discover ids."),kind:On.enum(["stack","skill","integration"]).optional().describe("Optional filter for action='list'. 'stack' = framework methodology, 'skill' = design + structural patterns, 'integration' = third-party services."),stack:On.string().optional().default("nextjs").describe("Stack slug. Defaults to 'nextjs' (currently the only supported stack).")}),ni={name:"mist_docs",description:as,inputSchema:ti,handler:async t=>{let{action:e,topic:r,kind:n,stack:o}=ti.parse(t);try{if(e==="list"){let a=await po(o,n),l=[];l.push(`# Mistflow docs catalog (${a.stack})
2849
2551
  ${a.topics.length} topics. Call \`mist_docs({ action: "get", topic: "<id>" })\` to fetch one.
2850
- `);let c={};for(let u of a.topics)(c[u.kind]??=[]).push(u);let m=["stack","skill","integration"];for(let u of m){let p=c[u];if(!(!p||p.length===0)){l.push(`## ${u}`);for(let h of p)l.push(`- **${h.id}** \u2014 ${h.title}: ${h.description}`);l.push("")}}return d(l.join(`
2851
- `).trim())}if(!o)return d("Missing `topic`. Pass action='get' with a topic id, or action='list' to see available topics.",!0);let s=await ur(r,o),i=`<!-- mist_docs: topic=${s.topic} kind=${s.kind} stack=${s.stack} -->
2552
+ `);let c={};for(let d of a.topics)(c[d.kind]??=[]).push(d);let p=["stack","skill","integration"];for(let d of p){let m=c[d];if(!(!m||m.length===0)){l.push(`## ${d}`);for(let h of m)l.push(`- **${h.id}** \u2014 ${h.title}: ${h.description}`);l.push("")}}return u(l.join(`
2553
+ `).trim())}if(!r)return u("Missing `topic`. Pass action='get' with a topic id, or action='list' to see available topics.",!0);let s=await mo(o,r),i=`<!-- mist_docs: topic=${s.topic} kind=${s.kind} stack=${s.stack} -->
2852
2554
  # ${s.title}
2853
2555
 
2854
2556
  ${s.description}
2855
2557
 
2856
2558
  ---
2857
2559
 
2858
- `;return d(i+s.content)}catch(s){if(s instanceof X&&s.statusCode===404)return d(s.message,!0);let i=s instanceof Error?s.message:String(s);return d(`mist_docs failed: ${i}
2560
+ `;return u(i+s.content)}catch(s){if(s instanceof Z&&s.statusCode===404)return u(s.message,!0);let i=s instanceof Error?s.message:String(s);return u(`mist_docs failed: ${i}
2859
2561
 
2860
- If this looks like a network issue, retry. If it persists, you can fall back to reading AGENTS.md / .claude/skills/ in the user's project for the same content.`,!0)}}};import{existsSync as uo,mkdirSync as Js,readFileSync as Vs,readdirSync as Ys,writeFileSync as jc}from"fs";import{join as vt,resolve as Dc}from"path";import{homedir as Ir}from"os";import{z as Nn}from"zod";function Xt(t){return t.entity??t.name??"Unknown"}function Zt(t){return typeof t=="string"?t:t.name}function Tr(t){return typeof t=="string"?"text":t.type}function Gs(t){let e=t.sampleRows;if(Array.isArray(e)&&e.length>0)return e.slice(0,3).map(r=>{let s={};for(let[i,a]of Object.entries(r))s[i]=a==null?"":String(a);return s});let o=t.fields||[],n=[];for(let r=0;r<3;r++){let s={};for(let i of o)s[Zt(i)]=Lc(Zt(i),Tr(i),Xt(t),r);n.push(s)}return n}function Lc(t,e,o,n){let r=t.toLowerCase(),s=(e||"text").toLowerCase();return r==="name"||r==="title"?[`${o} Alpha`,`${o} Beta`,`${o} Gamma`][n]:r==="email"?["alice@example.com","bob@example.com","carol@example.com"][n]:r==="status"?["Active","Pending","Completed"][n]:r==="priority"?["High","Medium","Low"][n]:r.includes("date")||s==="date"?["Jan 15, 2024","Feb 20, 2024","Mar 10, 2024"][n]:r.includes("price")||r.includes("amount")||r.includes("cost")?["$29","$49","$99"][n]:r.includes("count")||r.includes("quantity")||s==="number"||s==="integer"?["12","34","56"][n]:s==="boolean"||s==="bool"?["Yes","No","Yes"][n]:r.includes("description")||s==="textarea"?["Brief description here","Another example entry","Third sample item"][n]:`Sample ${n+1}`}function $c(t){let e=t.dataModel??[],o=t.pages??[],n=t.design??{},r=o.map(u=>({label:u.name??u.path??"Page",route:u.path??u.route??"/"})),s=[],i=e.slice(0,3).map(u=>({name:Xt(u),fields:(u.fields||[]).map(p=>({name:Zt(p),type:Tr(p)})),sampleData:Gs(u)})),a=[];t.primaryAction&&(a.push(`PRIMARY: ${t.primaryAction.action} \u2014 this is the first thing the user sees and does`),a.push(`SURFACE: ${t.primaryAction.dashboardSurface}`)),a.push(`METRICS: Key counts for ${e.map(u=>Xt(u)).join(", ")}`),a.push("RECENT: Latest activity or items"),s.push({name:"Dashboard",type:"dashboard",route:"/dashboard",purpose:t.primaryAction?`Action surface \u2014 user comes here to ${t.primaryAction.action.toLowerCase()}. Not a stats display.`:`Overview of ${e.map(u=>Xt(u)).join(", ")} with key metrics.`,informationHierarchy:a,interactionStates:["Empty state: new user, no data yet \u2014 show onboarding prompt","Loading state: skeleton placeholders for metrics and table","Populated state: real data with metrics, recent items, quick actions"],entities:i});let l=e[0];if(l){let u=Xt(l),p=u.toLowerCase().endsWith("s")?u:`${u}s`;s.push({name:`${u} List`,type:"detail",route:`/${p.toLowerCase()}`,purpose:`Browse, search, and manage ${p.toLowerCase()}. Create new ${u.toLowerCase()}s.`,informationHierarchy:[`HEADER: "${p}" title + "Add ${u}" button`,"SEARCH: Filter/search bar \u2014 users will have many items",`TABLE: ${(l.fields||[]).slice(0,5).map(h=>Zt(h)).join(", ")} columns`,"ROW ACTIONS: Edit, delete on each row"],interactionStates:[`Empty state: "No ${p.toLowerCase()} yet" with create CTA`,"Loading state: skeleton table rows",`Search with no results: "No ${p.toLowerCase()} matching..." with clear filter`],entities:[{name:u,fields:(l.fields||[]).map(h=>({name:Zt(h),type:Tr(h)})),sampleData:Gs(l)}]})}t.steps.some(u=>{let p=`${u.name??u.title??""} ${u.description??""}`.toLowerCase();return p.includes("landing")||p.includes("hero")||p.includes("marketing")||p.includes("homepage")})&&s.push({name:"Landing Page",type:"landing",route:"/",purpose:`Convince visitors to sign up for ${t.name}. Answer: what is this, who is it for, why should I care.`,informationHierarchy:[`HERO: One sentence about what ${t.name} does \u2014 not "Transform your X", be specific`,"CTA: Sign up / Get started \u2014 one clear action","PROOF: What makes this valuable (features, not buzzwords)","SECONDARY CTA: Repeat the sign up prompt"],interactionStates:["Mobile: hero stacks vertically, nav collapses to hamburger","Desktop: hero side-by-side or centered, full nav"],entities:[]});let m=[];for(let u of e.slice(0,3)){let p=Xt(u);(u.fields||[]).find(U=>{let C=Zt(U).toLowerCase();return C==="name"||C==="title"})&&m.push(`What if a ${p.toLowerCase()}'s name is 47 characters? Does the layout break?`),m.push(`What if there are 0 ${p.toLowerCase()}s? 1? 500?`)}return t.authModel&&t.authModel!=="none"&&m.push("What does a brand-new user see? (no data, no setup)"),m.push("What if the network is slow? What loads first?"),{appName:t.name,summary:t.summary??"",screens:s,navigation:{style:t.navStyle??"sidebar",items:r},primaryAction:t.primaryAction??null,designDirection:{tone:n.tone??"professional",accentColor:n.accentColor??"blue",navStyle:t.navStyle??"sidebar",fonts:n.fonts??{heading:"Inter",body:"Inter"}},edgeCases:m}}function Uc(t,e,o){let n=[];n.push(`# Wireframe sketch for ${t.appName}`),n.push(""),n.push(`**${t.appName}** \u2014 ${t.summary}`),n.push(""),e&&(n.push("## Feedback to apply"),n.push(e),n.push("")),n.push("## Design principles"),n.push(""),n.push("Apply these when deciding layout and hierarchy:"),n.push("1. **Information hierarchy** \u2014 What does the user see first, second, third? The primary action is first. Metrics are second. Everything else is supporting."),n.push("2. **Interaction states** \u2014 Every screen has at least: empty, loading, populated. Show the populated state but add HTML comments noting the others."),n.push("3. **Edge case paranoia** \u2014 What if there are 0 items? 500 items? A 47-character name? Think about these and comment where they matter."),n.push(`4. **Subtraction** \u2014 "As little design as possible" (Dieter Rams). Every element earns its pixels. If removing something doesn't hurt, remove it.`),n.push("5. **Design for trust** \u2014 Clear labels, predictable layout, obvious actions. No mystery meat navigation."),n.push(""),n.push("## Wireframe rules (strict)"),n.push(""),n.push(`Write a **single self-contained HTML file** saved to \`${o}\`.`),n.push(""),n.push("The wireframe must:"),n.push("- Use **system fonts only** (`-apple-system, system-ui, sans-serif`) \u2014 no Google Fonts, no CDN"),n.push("- Use **inline CSS only** \u2014 no external stylesheets, no Tailwind CDN"),n.push("- Look **intentionally rough** \u2014 thin gray borders (#ddd), light backgrounds (#f8f8f8), no color, no shadows"),n.push("- Use **realistic placeholder content** that matches this specific app (sample data provided below) \u2014 NOT lorem ipsum"),n.push("- Include **HTML comments** explaining design decisions"),n.push("- Show **all screens in a single page** using tabs/sections that the user can click through"),n.push("- Be **responsive** \u2014 test that it looks reasonable at both 1200px and 375px widths"),n.push("- Include a small header bar showing: screen name tabs + the design direction summary"),n.push(""),n.push("The wireframe must NOT:"),n.push("- Use any color except grayscale (#333, #666, #999, #ddd, #f8f8f8, white)"),n.push("- Use any external dependencies \u2014 no CDN, no imports, no build step"),n.push("- Look polished \u2014 it should feel like a sketch on a whiteboard, not a finished product"),n.push("- Include decorative elements \u2014 no icons (use text labels), no illustrations, no gradients"),n.push(""),n.push("## Screens to wireframe"),n.push("");for(let r of t.screens){n.push(`### ${r.name} (\`${r.route}\`)`),n.push(`**Purpose**: ${r.purpose}`),n.push(""),n.push("**Information hierarchy** (render in this order, top to bottom):");for(let s of r.informationHierarchy)n.push(`- ${s}`);n.push(""),n.push("**Interaction states** (add HTML comments for non-visible states):");for(let s of r.interactionStates)n.push(`- ${s}`);if(n.push(""),r.entities.length>0){n.push("**Data model and sample content** (use this real data, not lorem ipsum):");for(let s of r.entities)n.push(`
2861
- **${s.name}** \u2014 fields: ${s.fields.map(i=>`${i.name} (${i.type})`).join(", ")}`),n.push("```json"),n.push(JSON.stringify(s.sampleData,null,2)),n.push("```");n.push("")}}n.push("## Navigation"),n.push(`**Style**: ${t.navigation.style} (use this layout)`),n.push("**Items**:");for(let r of t.navigation.items)n.push(`- ${r.label} \u2192 \`${r.route}\``);if(n.push(""),t.primaryAction&&(n.push("## Primary action (this drives the layout)"),n.push(`- **Action**: ${t.primaryAction.action}`),n.push(`- **Flow**: ${t.primaryAction.flow}`),n.push(`- **Dashboard must show**: ${t.primaryAction.dashboardSurface}`),n.push(""),n.push("The dashboard is an ACTION surface. The primary action should be the most prominent thing on the page \u2014 above the metrics, above the recent items. Users came here to DO something, not to look at numbers."),n.push("")),t.edgeCases.length>0){n.push("## Edge cases to consider"),n.push("Add HTML comments in the wireframe where these matter:");for(let r of t.edgeCases)n.push(`- ${r}`);n.push("")}return n.push("## Design direction (DO NOT apply to wireframe \u2014 this is for reference only)"),n.push(`The final app will use: ${t.designDirection.tone} tone, ${t.designDirection.accentColor} accent, ${t.designDirection.navStyle} nav, ${t.designDirection.fonts.heading} / ${t.designDirection.fonts.body} fonts.`),n.push("The wireframe is grayscale and rough. These tokens will be applied during the actual build."),n.push(""),n.push("## After writing the wireframe"),n.push(`1. Write the file to \`${o}\``),n.push(`2. Open it for the user: \`open "${o}"\``),n.push(`3. Tell the user: "Here's a rough wireframe of your app. Does the layout feel right? Let me know what to change, or I can start building if it's close."`),n.push("4. WAIT for the user's response. Do NOT call mist_init until they approve the layout."),n.join(`
2862
- `)}function Qs(t){return vt(Ir(),".mistflow","mockup-state",`${t}.json`)}function qc(t){let e=Qs(t);if(!uo(e))return null;try{return JSON.parse(Vs(e,"utf-8"))}catch{return null}}function Ks(t,e){let o=vt(Ir(),".mistflow","mockup-state");Js(o,{recursive:!0}),jc(Qs(t),JSON.stringify(e,null,2))}var Fc=Nn.object({planId:Nn.string().min(1).describe("Plan ID from mist_plan. Required."),projectPath:Nn.string().optional().describe("Project directory (default: cwd). The mockup is written to <projectPath>/.mistflow/mockups/."),feedback:Nn.string().optional().describe("User feedback to apply to the next iteration."),approved:Nn.boolean().optional().describe("Mark the wireframe as approved (terminal \u2014 unlocks scaffolding).")}).refine(t=>!(t.feedback&&t.approved),{message:"Pass either 'feedback' or 'approved' \u2014 not both. Feedback iterates the design; approved locks it in."}),Xs={name:"mist_mockup",description:os,inputSchema:Fc,handler:async t=>{let e=t,{planId:o,feedback:n,approved:r}=e,s=Dc(e.projectPath??process.cwd()),i=vt(Ir(),".mistflow","plans",`${o}.json`);if(!uo(i))return d(`Plan not found for planId '${o}'. Call mist_plan to generate a plan first.`,!0);let a;try{a=JSON.parse(Vs(i,"utf-8"))}catch{return d("Failed to read plan file. Call mist_plan again.",!0)}let l=a.plan;if(!l)return d("Plan data is empty. Call mist_plan again.",!0);let c=qc(o);c||(c={planId:o,iterationCount:0,approved:!1,screens:[],feedback:[]});let m=vt(s,".mistflow","mockups");Js(m,{recursive:!0});let u=`mockup-${o}.html`,p=vt(m,u);if(r){c.approved=!0,Ks(o,c);let w=uo(m)?Ys(m).filter(A=>A.endsWith(".html")).map(A=>vt(".mistflow","mockups",A)):[];return d(JSON.stringify({status:"approved",message:`Wireframe approved after ${c.iterationCount} iteration(s). Layout direction is locked in.`,mockupFiles:w,nextAction:`Call mist_init with planId='${o}' and path='<absolute project path>' to scaffold the project. Mockups in .mistflow/mockups/ will be used as layout reference during implementation.`}))}c.iterationCount++,n&&c.feedback.push(n);let h=$c(l);c.screens=h.screens.map(w=>w.name),Ks(o,c);let U=Uc(h,n??void 0,p),C=c.iterationCount>=3?"The wireframe is shaping up \u2014 want to keep refining the layout, or start building?":void 0,x=n?`Apply the user's feedback to ${p}. Rewrite the file, open it for review, then ask if they want more changes or are ready to build.`:`Generate the wireframe HTML following the wireframePrompt, write it to ${p}, then open it in the browser. Ask the user if the layout feels right.`;return d(JSON.stringify({status:"wireframe",requires_user_input:!0,iterationCount:c.iterationCount,screens:h.screens.map(w=>({name:w.name,type:w.type,route:w.route})),wireframePrompt:U,designDirection:h.designDirection,...C?{nudge:C}:{},mockupFile:u,mockupPath:p,nextAction:x}))}};function Zs(t){let e=vt(t,".mistflow","mockups");return uo(e)?Ys(e).filter(o=>o.endsWith(".html")).map(o=>vt(e,o)):[]}import{z as Pr}from"zod";import{resolve as Bc}from"path";import{spawn as Hc}from"child_process";function po(t){let e=[],o=/([^\s(]+)\((\d+),(\d+)\):\s*error\s+(TS\d+):\s*(.+)/g;for(let a of t.matchAll(o)){let[,l,c,m,u,p]=a;e.push({file:l,line:parseInt(c,10),column:parseInt(m,10),message:`${u}: ${p}`,humanMessage:`There is a type error in ${l} on line ${c}: ${p}`,suggestion:`Check line ${c} in ${l}. ${u==="TS2345"?"The types of the arguments do not match.":`Fix the ${u} error.`}`})}let n=/(?:Error:\s*)?\.\/([^\s:]+):(\d+):(\d+)\s*\n\s*(.+)/g;for(let a of t.matchAll(n)){let[,l,c,m,u]=a;e.some(p=>p.file===l&&p.line===parseInt(c,10))||e.push({file:l,line:parseInt(c,10),column:parseInt(m,10),message:u,humanMessage:`There is an error in ${l} on line ${c}: ${u.trim()}`,suggestion:`Check line ${c} in ${l} and fix the issue.`})}let r=/Module not found:\s*(?:Error:\s*)?Can't resolve ['"]([^'"]+)['"]\s*(?:in\s*['"]?([^'"]+)['"]?)?/g;for(let a of t.matchAll(r)){let[,l,c]=a;e.push({file:c,message:`Module not found: ${l}`,humanMessage:`The file ${c??"your project"} is trying to import '${l}' which is not installed.`,suggestion:`Run npm install ${l}`})}let s=/Package subpath ['"]([^'"]+)['"] is not defined by "exports" in .*?node_modules\/([^/]+(?:\/[^/]+)?)\//g;for(let a of t.matchAll(s)){let[,l,c]=a;e.push({message:`ERR_PACKAGE_PATH_NOT_EXPORTED: ${c}${l}`,humanMessage:`The package '${c}' does not export the subpath '${l}'. This is usually caused by a version conflict between packages that depend on different major versions of '${c}'.`,suggestion:`Add '${c}' at the version that exports '${l}' as an optionalDependency in package.json (this pins it at root level and lets the other version nest). Then delete node_modules and package-lock.json, and run npm install.`})}let i=/SyntaxError:\s*([^\s:]+):\s*(.+?)\s*\((\d+):(\d+)\)/g;for(let a of t.matchAll(i)){let[,l,c,m,u]=a;e.some(p=>p.file===l&&p.line===parseInt(m,10))||e.push({file:l,line:parseInt(m,10),column:parseInt(u,10),message:`SyntaxError: ${c}`,humanMessage:`There is a syntax error in ${l} on line ${m}.`,suggestion:`Check line ${m} in ${l} for a missing closing bracket or unexpected token.`})}return e}var zc=Pr.object({projectPath:Pr.string().optional().describe("Absolute path to the project directory. Defaults to cwd."),buildOutput:Pr.string().optional().describe("Build output to parse. If omitted, the tool runs `npm run build` in projectPath.")});function Wc(t){return new Promise(e=>{let o=Hc("npm",["run","build"],{cwd:t,stdio:["ignore","pipe","pipe"]}),n="";o.stdout?.on("data",r=>{n+=r.toString()}),o.stderr?.on("data",r=>{n+=r.toString()}),o.on("error",r=>{e({exitCode:127,combined:`${n}
2863
- ${r.message}`})}),o.on("close",r=>{e({exitCode:r??1,combined:n})})})}var ei={name:"mist_debug",description:ns,inputSchema:zc,handler:async t=>{let e=t,o=Bc(e.projectPath??process.cwd()),n=e.buildOutput??"";if(!e.buildOutput){let i=await Wc(o);if(i.exitCode===0)return d(JSON.stringify({errors:[],rawOutput:"",message:"Build succeeded with no errors."}));n=i.combined}let r=po(n),s=n.slice(0,2e3);return r.length===0?d(JSON.stringify({errors:r,rawOutput:s,message:"Build output could not be parsed into structured errors. See rawOutput for the first 2KB."})):d(JSON.stringify({errors:r,rawOutput:s,message:`Found ${r.length} error${r.length===1?"":"s"} in the build output.`}))}};import{z as B}from"zod";import{existsSync as lt,mkdirSync as $t,readFileSync as jn,readdirSync as ci,statSync as di,unlinkSync as td,writeFileSync as kt}from"fs";import{dirname as nd,isAbsolute as od,join as ke}from"path";import{homedir as xt,hostname as oi}from"os";import{createHash as rd,createHmac as ui,randomBytes as sd,randomUUID as ri,timingSafeEqual as id}from"crypto";Ie();import{spawn as Gc}from"child_process";function Rr(t){if(process.env.MISTFLOW_AUTO_OPEN_PICKER==="false")return{opened:!1,reason:"MISTFLOW_AUTO_OPEN_PICKER=false"};let e;try{e=new URL(t)}catch{return{opened:!1,reason:"invalid URL"}}if(e.protocol!=="http:"&&e.protocol!=="https:"&&e.protocol!=="file:")return{opened:!1,reason:`unsupported protocol ${e.protocol}`};if(Kc())return{opened:!1,reason:"headless environment detected"};let o=process.platform,n,r;o==="darwin"?(n="open",r=[e.href]):o==="win32"?(n="cmd",r=["/c","start","",e.href]):(n="xdg-open",r=[e.href]);try{let s=Gc(n,r,{shell:!1,stdio:"ignore",detached:!0});return s.on("error",()=>{}),s.unref(),{opened:!0}}catch(s){return{opened:!1,reason:s instanceof Error?s.message:"spawn failed"}}}function Kc(){if(process.env.SSH_TTY||process.env.SSH_CONNECTION)return!0;for(let t of["CI","BUILDKITE","GITHUB_ACTIONS","CIRCLECI","GITLAB_CI"])if(process.env[t])return!0;return process.platform==="linux"&&!process.env.DISPLAY&&!process.env.WAYLAND_DISPLAY&&!process.env.WSL_DISTRO_NAME}function Ye(t,e,o,n=3e3){if(e===void 0)return{stop:()=>{}};let r=0,s=!1,a=setInterval(()=>{s||(r++,t.notification({method:"notifications/progress",params:{progressToken:e,progress:r,message:o()}}).catch(()=>{}))},n);return{stop:()=>{s||(s=!0,clearInterval(a))}}}function Jc(t){try{return!!t.getClientCapabilities()?.elicitation}catch{return!1}}function Vc(){let t=(process.env.MISTFLOW_ELICITATION??"").toLowerCase().trim();return t==="off"||t==="false"||t==="0"||t==="disabled"}function Yc(t){let e=t.replace(/[^a-zA-Z0-9_]/g,"_");return/^[a-zA-Z_]/.test(e)?e:`_${e}`}function Qc(t){let e={},o=[],n=[],r=new Set;for(let s=0;s<t.length;s++){let i=t[s],a=i.decisionKey?Yc(i.decisionKey):`q_${s+1}`,l=a,c=2;for(;r.has(l);)l=`${a}_${c}`,c++;r.add(l);let m=i.freetext?"":`${l}_other`;if(m&&r.add(m),n.push({primary:l,other:m,question:i}),i.freetext){e[l]={type:"string",title:i.question,description:i.why,...i.recommended?{default:i.recommended}:{}},o.push(l);continue}let u=(i.options??[]).map(h=>typeof h=="string"?h:h.label),p=i.noOtherSentinel?[...u]:[...u,"Other (specify in the 'Other' field below)"];e[l]={type:"string",title:i.question,description:i.why?`${i.why}${i.recommended?`
2864
- Recommended: ${i.recommended}`:""}`:i.recommended?`Recommended: ${i.recommended}`:void 0,enum:p,enumNames:p,...i.recommended&&u.includes(i.recommended)?{default:i.recommended}:{}},o.push(l),e[m]={type:"string",title:i.otherFieldTitle??"If 'Other' selected above: type your answer",description:"Leave blank if you picked an option from the list."}}return{schema:{type:"object",properties:e,required:o},fieldKeys:n}}function Xc(t,e){let o=[],n;for(let{primary:r,other:s,question:i}of e){let a=String(t[r]??"").trim(),l;if(i.freetext)l=a||i.recommended||"";else{let m=s?String(t[s]??"").trim():"",u=a.toLowerCase(),p=u.startsWith("other")||/\btype\b|\bcustom\b|\bdifferent\b/.test(u);m?l=m:p?l=i.recommended??(i.options?.[0]&&typeof i.options[0]=="object"?i.options[0].label:"")??a:l=a}let c=i.decisionKey??"";if(c==="urlChoice"){n=l;continue}o.push({question:i.question,decisionKey:c,answer:l})}return{answers:o,urlChoice:n}}function at(t,e,o){let n;switch(o.outcome){case"submitted":n=`submitted (${o.answers?.length??0} answers${o.urlChoice?`, urlChoice="${o.urlChoice}"`:""})`;break;case"error":n=`error (${o.errorMessage??"unknown"})`;break;case"unsupported":n=`unsupported (${e})`;break;default:n=o.outcome}console.error(`[mist_plan] elicitation/${t}: ${n}`)}async function On(t,e,o,n="discovery"){if(Vc()){let m={outcome:"unsupported"};return at(n,"MISTFLOW_ELICITATION kill switch on",m),m}if(!Jc(t)){let m={outcome:"unsupported"};return at(n,"client did not declare elicitation capability",m),m}if(e.length===0){let m={outcome:"submitted",answers:[]};return at(n,"no questions to ask",m),m}let{schema:r,fieldKeys:s}=Qc(e),i;try{i=await t.elicitInput({message:o,requestedSchema:r,mode:"form"},{timeout:300*1e3})}catch(m){let u=m instanceof Error?m.message:String(m);if(u.toLowerCase().includes("not support")){let h={outcome:"unsupported"};return at(n,`SDK rejected form mode: ${u}`,h),h}let p={outcome:"error",errorMessage:u};return at(n,"",p),p}if(i.action==="decline"){let m={outcome:"declined"};return at(n,"",m),m}if(i.action==="cancel"){let m={outcome:"cancelled"};return at(n,"",m),m}if(i.action!=="accept"||!i.content){let m={outcome:"error",errorMessage:`Unexpected elicitation result: ${i.action}`};return at(n,"",m),m}let{answers:a,urlChoice:l}=Xc(i.content,s),c={outcome:"submitted",answers:a,urlChoice:l};return at(n,"",c),c}var Zc=[{name:"Dashboard",description:"Overview with key stats and today's activity",condition:t=>t.surfaceType==="internal-tool"||t.surfaceType==="customer-app",keywords:/\b(dashboard|overview|home.?page|stats)\b/i},{name:"Landing Page",description:"Public page explaining what this does",condition:t=>t.publicLanding===!0,keywords:/\b(landing|marketing|hero|homepage)\b/i},{name:"Scheduling / Booking",description:"Calendar, time slots, reservations",condition:t=>t.scheduling===!0,keywords:/\b(schedul|book|reserv|appointment|calendar|slot)\b/i},{name:"Payments / Billing",description:"Charge users, invoices, subscriptions (experimental \u2014 Stripe integration is early-stage)",condition:()=>!1,keywords:/\b(payment|billing|invoice|subscription|checkout|stripe)\b/i},{name:"Admin Panel",description:"Manage users, roles, and content",condition:t=>t.multiRole===!0||t.primaryActor==="both",keywords:/\b(admin|panel|manage.?user|moderat)\b/i},{name:"User Profiles",description:"Account pages, settings, preferences",condition:t=>t.primaryActor==="customers"||t.primaryActor==="both",keywords:/\b(profile|account|settings|preferences)\b/i},{name:"Search / Browse",description:"Find and filter content or listings",condition:t=>t.surfaceType==="marketplace",keywords:/\b(search|browse|filter|discover|explore)\b/i},{name:"Email Notifications",description:"Welcome emails, alerts, reminders",condition:t=>t.integrations?.includes("email")===!0,keywords:/\b(notification|alert|reminder|email.?notif|sms|welcome.?email)\b/i},{name:"Analytics / Reports",description:"Usage stats, trends, data exports",condition:()=>!1,keywords:/\b(analytics|report|chart|trend|insight|metric)\b/i},{name:"File Uploads",description:"Images, documents, attachments",condition:t=>t.integrations?.includes("file-uploads")===!0,keywords:/\b(upload|image|photo|attachment|document|gallery|file)\b/i},{name:"AI Features",description:"Chatbot, content generation, AI assistant",condition:t=>t.integrations?.includes("ai")===!0,keywords:/\b(ai|chatbot|gpt|llm|generat|assistant)\b/i},{name:"Maps / Location",description:"Google Maps, location search, geolocation",condition:t=>t.integrations?.includes("maps")===!0,keywords:/\b(map|location|address|geo|places)\b/i},{name:"Voice / TTS",description:"Text-to-speech, voice notes, audio generation",condition:()=>!1,keywords:/\b(voice|tts|text.?to.?speech|audio|speak|narrat|podcast|elevenlabs)\b/i},{name:"SMS / Text Messages",description:"Send SMS notifications, OTP verification",condition:t=>t.integrations?.includes("sms")===!0,keywords:/\b(sms|text.?message|twilio|otp|verification.?code|phone.?verif)\b/i},{name:"Web Scraping",description:"Scrape URLs, crawl websites, extract structured data",condition:()=>!1,keywords:/\b(scrape|crawl|web.?scrap|extract.?from.?url|read.?url|ingest.?web|firecrawl)\b/i},{name:"Chat / Messaging",description:"Real-time messaging between users",condition:()=>!1,keywords:/\b(chat|messag|inbox|conversation|dm)\b/i},{name:"Events / Tournaments",description:"Create and manage events, registrations",condition:()=>!1,keywords:/\b(event|tournament|competition|league|registration)\b/i},{name:"High Scores",description:"Track and display top scores with a leaderboard",condition:t=>t.surfaceType==="game",keywords:/\b(high.?score|leaderboard|top.?score|ranking|scoreboard)\b/i},{name:"Save Progress",description:"Save and resume game state between sessions",condition:t=>t.surfaceType==="game",keywords:/\b(save|progress|resume|checkpoint|continue)\b/i},{name:"Guest Play",description:"Play immediately without signing up, optionally link account later",condition:t=>t.surfaceType==="game",keywords:/\b(guest|anonymous|no.?login|play.?now|instant.?play)\b/i},{name:"Levels / Stages",description:"Progressive difficulty with unlockable stages",condition:()=>!1,keywords:/\b(level|stage|unlock|difficult|progress|world)\b/i},{name:"Achievements",description:"Badges, trophies, and milestones for player accomplishments",condition:()=>!1,keywords:/\b(achieve|badge|trophy|milestone|reward|unlock)\b/i},{name:"Daily Challenge",description:"New puzzle or challenge every day to keep players coming back",condition:()=>!1,keywords:/\b(daily|challenge|streak|word.?of.?the.?day|puzzle.?of)\b/i}];function ti(t,e,o){let n=o?.suggestedName||ed(t),r=e.primaryActor==="both"?"Staff + Customers":e.primaryActor==="staff"?"Staff / Admin":e.primaryActor==="customers"?"End Users":"Users",s=e.audienceType??(e.surfaceType==="internal-tool"?"internal":(e.primaryActor==="customers"||e.primaryActor==="both","b2c")),i=e.surfaceType==="internal-tool"?"Internal tool":e.surfaceType==="marketplace"?"Marketplace":e.surfaceType==="content-site"?"Content site":e.surfaceType==="game"?"Game":e.surfaceType==="agent"?"Chat agent":"App",a;if(o?.suggestedFeatures&&o.suggestedFeatures.length>0)a=o.suggestedFeatures.map(l=>({name:l.name,description:l.description,checked:l.recommended,source:l.recommended?"explicit":"suggested"}));else{let l=`${t} ${e.primaryAction||""}`;a=[];for(let c of Zc){let m=c.keywords.test(l),u=c.condition(e);(m||u)&&a.push({name:c.name,description:c.description,checked:m,source:m?"explicit":"suggested"})}}return{name:n,audience:r,audienceType:s,surfaceType:i,primaryAction:e.primaryAction||"manage items",features:a,publicLanding:e.publicLanding??!0,authModel:e.authModel??"email",dbProvider:e.dbProvider??"neon",integrations:e.integrations??[],language:o?.language||"English"}}function ni(t){let e=t.features.filter(a=>a.checked),o=t.features.filter(a=>!a.checked),n={email:"Email sign-up",none:"No login (public)",social:"Social login","invite-only":"Invite-only"},r={neon:"Postgres",turso:"SQLite (legacy)"},s={b2c:"Your customers use this app (business-to-customer)",b2b:"Other businesses sign up for this (SaaS platform)",internal:"Internal team tool (staff only)"},i=[`**${t.name}** \u2014 ${t.surfaceType} for ${t.audience}`,`Audience: ${s[t.audienceType]??t.audienceType}`,`Primary action: ${t.primaryAction}`,`Access: ${n[t.authModel]??t.authModel} | Database: ${r[t.dbProvider]??t.dbProvider}${t.publicLanding?" | Landing page: Yes":""}${t.language&&t.language!=="English"?` | Language: ${t.language}`:""}`,""];if(e.length>0){i.push("**Included:**");for(let a of e)i.push(` \u2713 ${a.name} \u2014 ${a.description}`)}if(t.integrations.length>0&&(i.push(""),i.push(`**Integrations:** ${t.integrations.join(", ")}`)),o.length>0){i.push(""),i.push("**Available to add:**");for(let a of o)i.push(` \u25CB ${a.name} \u2014 ${a.description}`)}return i.join(`
2865
- `)}function ed(t){let e=t.match(/\b(?:called|named)\s+["']?([A-Za-z][A-Za-z0-9 ]{1,30})["']?/i);if(e)return mo(e[1]);let o=new Set(["build","create","make","a","an","the","for","me","my","app","application","website","web","tool","system","platform","using","with","and","that","this","want","need","please","can","you","i","mist","mistflow"]),r=t.toLowerCase().replace(/[^a-z0-9\s]/g,"").split(/\s+/).filter(s=>s.length>2&&!o.has(s)).slice(0,3);return r.length===0?"my-app":r.join("-")}function mo(t){return t.toLowerCase().replace(/[^a-z0-9\s]/g,"").trim().replace(/\s+/g,"-")}function Ar(t,e,o){let n=Mn(t||"your app"),r=e.map((i,a)=>{let l=ho(i.id||`direction-${a}`),c=Mn(i.name||`Direction ${a+1}`),m=Mn(i.summary||"");return`<button class="tab${a===0?" active":""}" data-target="${l}" type="button"><span class="tab-name">${c}</span>${m?`<span class="tab-sub">${m}</span>`:""}</button>`}).join(`
2866
- `),s=e.map((i,a)=>{let l=ho(i.id||`direction-${a}`),c=o[i.id??""]??"";return`<iframe class="render-frame${a===0?" active":""}" data-id="${l}" srcdoc="${ho(c)}" sandbox="allow-same-origin allow-scripts" title="${ho(i.name||`Direction ${a+1}`)}"></iframe>`}).join(`
2562
+ If this looks like a network issue, retry. If it persists, you can fall back to reading AGENTS.md / .claude/skills/ in the user's project for the same content.`,!0)}}};import{existsSync as ur,mkdirSync as si,readFileSync as ii,readdirSync as ai,writeFileSync as Hc}from"fs";import{join as xt,resolve as zc}from"path";import{homedir as Ro}from"os";import{z as Mn}from"zod";function Zt(t){return t.entity??t.name??"Unknown"}function en(t){return typeof t=="string"?t:t.name}function Po(t){return typeof t=="string"?"text":t.type}function ri(t){let e=t.sampleRows;if(Array.isArray(e)&&e.length>0)return e.slice(0,3).map(o=>{let s={};for(let[i,a]of Object.entries(o))s[i]=a==null?"":String(a);return s});let r=t.fields||[],n=[];for(let o=0;o<3;o++){let s={};for(let i of r)s[en(i)]=Wc(en(i),Po(i),Zt(t),o);n.push(s)}return n}function Wc(t,e,r,n){let o=t.toLowerCase(),s=(e||"text").toLowerCase();return o==="name"||o==="title"?[`${r} Alpha`,`${r} Beta`,`${r} Gamma`][n]:o==="email"?["alice@example.com","bob@example.com","carol@example.com"][n]:o==="status"?["Active","Pending","Completed"][n]:o==="priority"?["High","Medium","Low"][n]:o.includes("date")||s==="date"?["Jan 15, 2024","Feb 20, 2024","Mar 10, 2024"][n]:o.includes("price")||o.includes("amount")||o.includes("cost")?["$29","$49","$99"][n]:o.includes("count")||o.includes("quantity")||s==="number"||s==="integer"?["12","34","56"][n]:s==="boolean"||s==="bool"?["Yes","No","Yes"][n]:o.includes("description")||s==="textarea"?["Brief description here","Another example entry","Third sample item"][n]:`Sample ${n+1}`}function Gc(t){let e=t.dataModel??[],r=t.pages??[],n=t.design??{},o=r.map(d=>({label:d.name??d.path??"Page",route:d.path??d.route??"/"})),s=[],i=e.slice(0,3).map(d=>({name:Zt(d),fields:(d.fields||[]).map(m=>({name:en(m),type:Po(m)})),sampleData:ri(d)})),a=[];t.primaryAction&&(a.push(`PRIMARY: ${t.primaryAction.action} \u2014 this is the first thing the user sees and does`),a.push(`SURFACE: ${t.primaryAction.dashboardSurface}`)),a.push(`METRICS: Key counts for ${e.map(d=>Zt(d)).join(", ")}`),a.push("RECENT: Latest activity or items"),s.push({name:"Dashboard",type:"dashboard",route:"/dashboard",purpose:t.primaryAction?`Action surface \u2014 user comes here to ${t.primaryAction.action.toLowerCase()}. Not a stats display.`:`Overview of ${e.map(d=>Zt(d)).join(", ")} with key metrics.`,informationHierarchy:a,interactionStates:["Empty state: new user, no data yet \u2014 show onboarding prompt","Loading state: skeleton placeholders for metrics and table","Populated state: real data with metrics, recent items, quick actions"],entities:i});let l=e[0];if(l){let d=Zt(l),m=d.toLowerCase().endsWith("s")?d:`${d}s`;s.push({name:`${d} List`,type:"detail",route:`/${m.toLowerCase()}`,purpose:`Browse, search, and manage ${m.toLowerCase()}. Create new ${d.toLowerCase()}s.`,informationHierarchy:[`HEADER: "${m}" title + "Add ${d}" button`,"SEARCH: Filter/search bar \u2014 users will have many items",`TABLE: ${(l.fields||[]).slice(0,5).map(h=>en(h)).join(", ")} columns`,"ROW ACTIONS: Edit, delete on each row"],interactionStates:[`Empty state: "No ${m.toLowerCase()} yet" with create CTA`,"Loading state: skeleton table rows",`Search with no results: "No ${m.toLowerCase()} matching..." with clear filter`],entities:[{name:d,fields:(l.fields||[]).map(h=>({name:en(h),type:Po(h)})),sampleData:ri(l)}]})}t.steps.some(d=>{let m=`${d.name??d.title??""} ${d.description??""}`.toLowerCase();return m.includes("landing")||m.includes("hero")||m.includes("marketing")||m.includes("homepage")})&&s.push({name:"Landing Page",type:"landing",route:"/",purpose:`Convince visitors to sign up for ${t.name}. Answer: what is this, who is it for, why should I care.`,informationHierarchy:[`HERO: One sentence about what ${t.name} does \u2014 not "Transform your X", be specific`,"CTA: Sign up / Get started \u2014 one clear action","PROOF: What makes this valuable (features, not buzzwords)","SECONDARY CTA: Repeat the sign up prompt"],interactionStates:["Mobile: hero stacks vertically, nav collapses to hamburger","Desktop: hero side-by-side or centered, full nav"],entities:[]});let p=[];for(let d of e.slice(0,3)){let m=Zt(d);(d.fields||[]).find(S=>{let I=en(S).toLowerCase();return I==="name"||I==="title"})&&p.push(`What if a ${m.toLowerCase()}'s name is 47 characters? Does the layout break?`),p.push(`What if there are 0 ${m.toLowerCase()}s? 1? 500?`)}return t.authModel&&t.authModel!=="none"&&p.push("What does a brand-new user see? (no data, no setup)"),p.push("What if the network is slow? What loads first?"),{appName:t.name,summary:t.summary??"",screens:s,navigation:{style:t.navStyle??"sidebar",items:o},primaryAction:t.primaryAction??null,designDirection:{tone:n.tone??"professional",accentColor:n.accentColor??"blue",navStyle:t.navStyle??"sidebar",fonts:n.fonts??{heading:"Inter",body:"Inter"}},edgeCases:p}}function Jc(t,e,r){let n=[];n.push(`# Wireframe sketch for ${t.appName}`),n.push(""),n.push(`**${t.appName}** \u2014 ${t.summary}`),n.push(""),e&&(n.push("## Feedback to apply"),n.push(e),n.push("")),n.push("## Design principles"),n.push(""),n.push("Apply these when deciding layout and hierarchy:"),n.push("1. **Information hierarchy** \u2014 What does the user see first, second, third? The primary action is first. Metrics are second. Everything else is supporting."),n.push("2. **Interaction states** \u2014 Every screen has at least: empty, loading, populated. Show the populated state but add HTML comments noting the others."),n.push("3. **Edge case paranoia** \u2014 What if there are 0 items? 500 items? A 47-character name? Think about these and comment where they matter."),n.push(`4. **Subtraction** \u2014 "As little design as possible" (Dieter Rams). Every element earns its pixels. If removing something doesn't hurt, remove it.`),n.push("5. **Design for trust** \u2014 Clear labels, predictable layout, obvious actions. No mystery meat navigation."),n.push(""),n.push("## Wireframe rules (strict)"),n.push(""),n.push(`Write a **single self-contained HTML file** saved to \`${r}\`.`),n.push(""),n.push("The wireframe must:"),n.push("- Use **system fonts only** (`-apple-system, system-ui, sans-serif`) \u2014 no Google Fonts, no CDN"),n.push("- Use **inline CSS only** \u2014 no external stylesheets, no Tailwind CDN"),n.push("- Look **intentionally rough** \u2014 thin gray borders (#ddd), light backgrounds (#f8f8f8), no color, no shadows"),n.push("- Use **realistic placeholder content** that matches this specific app (sample data provided below) \u2014 NOT lorem ipsum"),n.push("- Include **HTML comments** explaining design decisions"),n.push("- Show **all screens in a single page** using tabs/sections that the user can click through"),n.push("- Be **responsive** \u2014 test that it looks reasonable at both 1200px and 375px widths"),n.push("- Include a small header bar showing: screen name tabs + the design direction summary"),n.push(""),n.push("The wireframe must NOT:"),n.push("- Use any color except grayscale (#333, #666, #999, #ddd, #f8f8f8, white)"),n.push("- Use any external dependencies \u2014 no CDN, no imports, no build step"),n.push("- Look polished \u2014 it should feel like a sketch on a whiteboard, not a finished product"),n.push("- Include decorative elements \u2014 no icons (use text labels), no illustrations, no gradients"),n.push(""),n.push("## Screens to wireframe"),n.push("");for(let o of t.screens){n.push(`### ${o.name} (\`${o.route}\`)`),n.push(`**Purpose**: ${o.purpose}`),n.push(""),n.push("**Information hierarchy** (render in this order, top to bottom):");for(let s of o.informationHierarchy)n.push(`- ${s}`);n.push(""),n.push("**Interaction states** (add HTML comments for non-visible states):");for(let s of o.interactionStates)n.push(`- ${s}`);if(n.push(""),o.entities.length>0){n.push("**Data model and sample content** (use this real data, not lorem ipsum):");for(let s of o.entities)n.push(`
2563
+ **${s.name}** \u2014 fields: ${s.fields.map(i=>`${i.name} (${i.type})`).join(", ")}`),n.push("```json"),n.push(JSON.stringify(s.sampleData,null,2)),n.push("```");n.push("")}}n.push("## Navigation"),n.push(`**Style**: ${t.navigation.style} (use this layout)`),n.push("**Items**:");for(let o of t.navigation.items)n.push(`- ${o.label} \u2192 \`${o.route}\``);if(n.push(""),t.primaryAction&&(n.push("## Primary action (this drives the layout)"),n.push(`- **Action**: ${t.primaryAction.action}`),n.push(`- **Flow**: ${t.primaryAction.flow}`),n.push(`- **Dashboard must show**: ${t.primaryAction.dashboardSurface}`),n.push(""),n.push("The dashboard is an ACTION surface. The primary action should be the most prominent thing on the page \u2014 above the metrics, above the recent items. Users came here to DO something, not to look at numbers."),n.push("")),t.edgeCases.length>0){n.push("## Edge cases to consider"),n.push("Add HTML comments in the wireframe where these matter:");for(let o of t.edgeCases)n.push(`- ${o}`);n.push("")}return n.push("## Design direction (DO NOT apply to wireframe \u2014 this is for reference only)"),n.push(`The final app will use: ${t.designDirection.tone} tone, ${t.designDirection.accentColor} accent, ${t.designDirection.navStyle} nav, ${t.designDirection.fonts.heading} / ${t.designDirection.fonts.body} fonts.`),n.push("The wireframe is grayscale and rough. These tokens will be applied during the actual build."),n.push(""),n.push("## After writing the wireframe"),n.push(`1. Write the file to \`${r}\``),n.push(`2. Open it for the user: \`open "${r}"\``),n.push(`3. Tell the user: "Here's a rough wireframe of your app. Does the layout feel right? Let me know what to change, or I can start building if it's close."`),n.push("4. WAIT for the user's response. Do NOT call mist_init until they approve the layout."),n.join(`
2564
+ `)}function li(t){return xt(Ro(),".mistflow","mockup-state",`${t}.json`)}function Kc(t){let e=li(t);if(!ur(e))return null;try{return JSON.parse(ii(e,"utf-8"))}catch{return null}}function oi(t,e){let r=xt(Ro(),".mistflow","mockup-state");si(r,{recursive:!0}),Hc(li(t),JSON.stringify(e,null,2))}var Vc=Mn.object({planId:Mn.string().min(1).describe("Plan ID from mist_plan. Required."),projectPath:Mn.string().optional().describe("Project directory (default: cwd). The mockup is written to <projectPath>/.mistflow/mockups/."),feedback:Mn.string().optional().describe("User feedback to apply to the next iteration."),approved:Mn.boolean().optional().describe("Mark the wireframe as approved (terminal \u2014 unlocks scaffolding).")}).refine(t=>!(t.feedback&&t.approved),{message:"Pass either 'feedback' or 'approved' \u2014 not both. Feedback iterates the design; approved locks it in."}),ci={name:"mist_mockup",description:cs,inputSchema:Vc,handler:async t=>{let e=t,{planId:r,feedback:n,approved:o}=e,s=zc(e.projectPath??process.cwd()),i=xt(Ro(),".mistflow","plans",`${r}.json`);if(!ur(i))return u(`Plan not found for planId '${r}'. Call mist_plan to generate a plan first.`,!0);let a;try{a=JSON.parse(ii(i,"utf-8"))}catch{return u("Failed to read plan file. Call mist_plan again.",!0)}let l=a.plan;if(!l)return u("Plan data is empty. Call mist_plan again.",!0);let c=Kc(r);c||(c={planId:r,iterationCount:0,approved:!1,screens:[],feedback:[]});let p=xt(s,".mistflow","mockups");si(p,{recursive:!0});let d=`mockup-${r}.html`,m=xt(p,d);if(o){c.approved=!0,oi(r,c);let v=ur(p)?ai(p).filter(E=>E.endsWith(".html")).map(E=>xt(".mistflow","mockups",E)):[];return u(JSON.stringify({status:"approved",message:`Wireframe approved after ${c.iterationCount} iteration(s). Layout direction is locked in.`,mockupFiles:v,nextAction:`Call mist_init with planId='${r}' and path='<absolute project path>' to scaffold the project. Mockups in .mistflow/mockups/ will be used as layout reference during implementation.`}))}c.iterationCount++,n&&c.feedback.push(n);let h=Gc(l);c.screens=h.screens.map(v=>v.name),oi(r,c);let S=Jc(h,n??void 0,m),I=c.iterationCount>=3?"The wireframe is shaping up \u2014 want to keep refining the layout, or start building?":void 0,y=n?`Apply the user's feedback to ${m}. Rewrite the file, open it for review, then ask if they want more changes or are ready to build.`:`Generate the wireframe HTML following the wireframePrompt, write it to ${m}, then open it in the browser. Ask the user if the layout feels right.`;return u(JSON.stringify({status:"wireframe",requires_user_input:!0,iterationCount:c.iterationCount,screens:h.screens.map(v=>({name:v.name,type:v.type,route:v.route})),wireframePrompt:S,designDirection:h.designDirection,...I?{nudge:I}:{},mockupFile:d,mockupPath:m,nextAction:y}))}};function di(t){let e=xt(t,".mistflow","mockups");return ur(e)?ai(e).filter(r=>r.endsWith(".html")).map(r=>xt(e,r)):[]}import{z as Ao}from"zod";import{resolve as Yc}from"path";import{spawn as Qc}from"child_process";function pr(t){let e=[],r=/([^\s(]+)\((\d+),(\d+)\):\s*error\s+(TS\d+):\s*(.+)/g;for(let a of t.matchAll(r)){let[,l,c,p,d,m]=a;e.push({file:l,line:parseInt(c,10),column:parseInt(p,10),message:`${d}: ${m}`,humanMessage:`There is a type error in ${l} on line ${c}: ${m}`,suggestion:`Check line ${c} in ${l}. ${d==="TS2345"?"The types of the arguments do not match.":`Fix the ${d} error.`}`})}let n=/(?:Error:\s*)?\.\/([^\s:]+):(\d+):(\d+)\s*\n\s*(.+)/g;for(let a of t.matchAll(n)){let[,l,c,p,d]=a;e.some(m=>m.file===l&&m.line===parseInt(c,10))||e.push({file:l,line:parseInt(c,10),column:parseInt(p,10),message:d,humanMessage:`There is an error in ${l} on line ${c}: ${d.trim()}`,suggestion:`Check line ${c} in ${l} and fix the issue.`})}let o=/Module not found:\s*(?:Error:\s*)?Can't resolve ['"]([^'"]+)['"]\s*(?:in\s*['"]?([^'"]+)['"]?)?/g;for(let a of t.matchAll(o)){let[,l,c]=a;e.push({file:c,message:`Module not found: ${l}`,humanMessage:`The file ${c??"your project"} is trying to import '${l}' which is not installed.`,suggestion:`Run npm install ${l}`})}let s=/Package subpath ['"]([^'"]+)['"] is not defined by "exports" in .*?node_modules\/([^/]+(?:\/[^/]+)?)\//g;for(let a of t.matchAll(s)){let[,l,c]=a;e.push({message:`ERR_PACKAGE_PATH_NOT_EXPORTED: ${c}${l}`,humanMessage:`The package '${c}' does not export the subpath '${l}'. This is usually caused by a version conflict between packages that depend on different major versions of '${c}'.`,suggestion:`Add '${c}' at the version that exports '${l}' as an optionalDependency in package.json (this pins it at root level and lets the other version nest). Then delete node_modules and package-lock.json, and run npm install.`})}let i=/SyntaxError:\s*([^\s:]+):\s*(.+?)\s*\((\d+):(\d+)\)/g;for(let a of t.matchAll(i)){let[,l,c,p,d]=a;e.some(m=>m.file===l&&m.line===parseInt(p,10))||e.push({file:l,line:parseInt(p,10),column:parseInt(d,10),message:`SyntaxError: ${c}`,humanMessage:`There is a syntax error in ${l} on line ${p}.`,suggestion:`Check line ${p} in ${l} for a missing closing bracket or unexpected token.`})}return e}var Xc=Ao.object({projectPath:Ao.string().optional().describe("Absolute path to the project directory. Defaults to cwd."),buildOutput:Ao.string().optional().describe("Build output to parse. If omitted, the tool runs `npm run build` in projectPath.")});function Zc(t){return new Promise(e=>{let r=Qc("npm",["run","build"],{cwd:t,stdio:["ignore","pipe","pipe"]}),n="";r.stdout?.on("data",o=>{n+=o.toString()}),r.stderr?.on("data",o=>{n+=o.toString()}),r.on("error",o=>{e({exitCode:127,combined:`${n}
2565
+ ${o.message}`})}),r.on("close",o=>{e({exitCode:o??1,combined:n})})})}var ui={name:"mist_debug",description:ls,inputSchema:Xc,handler:async t=>{let e=t,r=Yc(e.projectPath??process.cwd()),n=e.buildOutput??"";if(!e.buildOutput){let i=await Zc(r);if(i.exitCode===0)return u(JSON.stringify({errors:[],rawOutput:"",message:"Build succeeded with no errors."}));n=i.combined}let o=pr(n),s=n.slice(0,2e3);return o.length===0?u(JSON.stringify({errors:o,rawOutput:s,message:"Build output could not be parsed into structured errors. See rawOutput for the first 2KB."})):u(JSON.stringify({errors:o,rawOutput:s,message:`Found ${o.length} error${o.length===1?"":"s"} in the build output.`}))}};import{z as F}from"zod";import{existsSync as mt,mkdirSync as Ut,readFileSync as Ln,readdirSync as vi,statSync as ki,unlinkSync as cd,writeFileSync as St}from"fs";import{dirname as dd,isAbsolute as ud,join as fe}from"path";import{homedir as _t,hostname as hi}from"os";import{createHash as pd,createHmac as xi,randomBytes as md,randomUUID as gi,timingSafeEqual as hd}from"crypto";ke();import{spawn as ed}from"child_process";function Eo(t){if(process.env.MISTFLOW_AUTO_OPEN_PICKER==="false")return{opened:!1,reason:"MISTFLOW_AUTO_OPEN_PICKER=false"};let e;try{e=new URL(t)}catch{return{opened:!1,reason:"invalid URL"}}if(e.protocol!=="http:"&&e.protocol!=="https:"&&e.protocol!=="file:")return{opened:!1,reason:`unsupported protocol ${e.protocol}`};if(td())return{opened:!1,reason:"headless environment detected"};let r=process.platform,n,o;r==="darwin"?(n="open",o=[e.href]):r==="win32"?(n="cmd",o=["/c","start","",e.href]):(n="xdg-open",o=[e.href]);try{let s=ed(n,o,{shell:!1,stdio:"ignore",detached:!0});return s.on("error",()=>{}),s.unref(),{opened:!0}}catch(s){return{opened:!1,reason:s instanceof Error?s.message:"spawn failed"}}}function td(){if(process.env.SSH_TTY||process.env.SSH_CONNECTION)return!0;for(let t of["CI","BUILDKITE","GITHUB_ACTIONS","CIRCLECI","GITLAB_CI"])if(process.env[t])return!0;return process.platform==="linux"&&!process.env.DISPLAY&&!process.env.WAYLAND_DISPLAY&&!process.env.WSL_DISTRO_NAME}function tt(t,e,r,n=3e3){if(e===void 0)return{stop:()=>{}};let o=0,s=!1,a=setInterval(()=>{s||(o++,t.notification({method:"notifications/progress",params:{progressToken:e,progress:o,message:r()}}).catch(()=>{}))},n);return{stop:()=>{s||(s=!0,clearInterval(a))}}}function nd(t){try{return!!t.getClientCapabilities()?.elicitation}catch{return!1}}function rd(){let t=(process.env.MISTFLOW_ELICITATION??"").toLowerCase().trim();return t==="off"||t==="false"||t==="0"||t==="disabled"}function od(t){let e=t.replace(/[^a-zA-Z0-9_]/g,"_");return/^[a-zA-Z_]/.test(e)?e:`_${e}`}function sd(t){let e={},r=[],n=[],o=new Set;for(let s=0;s<t.length;s++){let i=t[s],a=i.decisionKey?od(i.decisionKey):`q_${s+1}`,l=a,c=2;for(;o.has(l);)l=`${a}_${c}`,c++;o.add(l);let p=i.freetext?"":`${l}_other`;if(p&&o.add(p),n.push({primary:l,other:p,question:i}),i.freetext){e[l]={type:"string",title:i.question,description:i.why,...i.recommended?{default:i.recommended}:{}},r.push(l);continue}let d=(i.options??[]).map(h=>typeof h=="string"?h:h.label),m=i.noOtherSentinel?[...d]:[...d,"Other (specify in the 'Other' field below)"];e[l]={type:"string",title:i.question,description:i.why?`${i.why}${i.recommended?`
2566
+ Recommended: ${i.recommended}`:""}`:i.recommended?`Recommended: ${i.recommended}`:void 0,enum:m,enumNames:m,...i.recommended&&d.includes(i.recommended)?{default:i.recommended}:{}},r.push(l),e[p]={type:"string",title:i.otherFieldTitle??"If 'Other' selected above: type your answer",description:"Leave blank if you picked an option from the list."}}return{schema:{type:"object",properties:e,required:r},fieldKeys:n}}function id(t,e){let r=[],n;for(let{primary:o,other:s,question:i}of e){let a=String(t[o]??"").trim(),l;if(i.freetext)l=a||i.recommended||"";else{let p=s?String(t[s]??"").trim():"",d=a.toLowerCase(),m=d.startsWith("other")||/\btype\b|\bcustom\b|\bdifferent\b/.test(d);p?l=p:m?l=i.recommended??(i.options?.[0]&&typeof i.options[0]=="object"?i.options[0].label:"")??a:l=a}let c=i.decisionKey??"";if(c==="urlChoice"){n=l;continue}r.push({question:i.question,decisionKey:c,answer:l})}return{answers:r,urlChoice:n}}function pt(t,e,r){let n;switch(r.outcome){case"submitted":n=`submitted (${r.answers?.length??0} answers${r.urlChoice?`, urlChoice="${r.urlChoice}"`:""})`;break;case"error":n=`error (${r.errorMessage??"unknown"})`;break;case"unsupported":n=`unsupported (${e})`;break;default:n=r.outcome}console.error(`[mist_plan] elicitation/${t}: ${n}`)}async function jn(t,e,r,n="discovery"){if(rd()){let p={outcome:"unsupported"};return pt(n,"MISTFLOW_ELICITATION kill switch on",p),p}if(!nd(t)){let p={outcome:"unsupported"};return pt(n,"client did not declare elicitation capability",p),p}if(e.length===0){let p={outcome:"submitted",answers:[]};return pt(n,"no questions to ask",p),p}let{schema:o,fieldKeys:s}=sd(e),i;try{i=await t.elicitInput({message:r,requestedSchema:o,mode:"form"},{timeout:300*1e3})}catch(p){let d=p instanceof Error?p.message:String(p);if(d.toLowerCase().includes("not support")){let h={outcome:"unsupported"};return pt(n,`SDK rejected form mode: ${d}`,h),h}let m={outcome:"error",errorMessage:d};return pt(n,"",m),m}if(i.action==="decline"){let p={outcome:"declined"};return pt(n,"",p),p}if(i.action==="cancel"){let p={outcome:"cancelled"};return pt(n,"",p),p}if(i.action!=="accept"||!i.content){let p={outcome:"error",errorMessage:`Unexpected elicitation result: ${i.action}`};return pt(n,"",p),p}let{answers:a,urlChoice:l}=id(i.content,s),c={outcome:"submitted",answers:a,urlChoice:l};return pt(n,"",c),c}var ad=[{name:"Dashboard",description:"Overview with key stats and today's activity",condition:t=>t.surfaceType==="internal-tool"||t.surfaceType==="customer-app",keywords:/\b(dashboard|overview|home.?page|stats)\b/i},{name:"Landing Page",description:"Public page explaining what this does",condition:t=>t.publicLanding===!0,keywords:/\b(landing|marketing|hero|homepage)\b/i},{name:"Scheduling / Booking",description:"Calendar, time slots, reservations",condition:t=>t.scheduling===!0,keywords:/\b(schedul|book|reserv|appointment|calendar|slot)\b/i},{name:"Payments / Billing",description:"Charge users, invoices, subscriptions (experimental \u2014 Stripe integration is early-stage)",condition:()=>!1,keywords:/\b(payment|billing|invoice|subscription|checkout|stripe)\b/i},{name:"Admin Panel",description:"Manage users, roles, and content",condition:t=>t.multiRole===!0||t.primaryActor==="both",keywords:/\b(admin|panel|manage.?user|moderat)\b/i},{name:"User Profiles",description:"Account pages, settings, preferences",condition:t=>t.primaryActor==="customers"||t.primaryActor==="both",keywords:/\b(profile|account|settings|preferences)\b/i},{name:"Search / Browse",description:"Find and filter content or listings",condition:t=>t.surfaceType==="marketplace",keywords:/\b(search|browse|filter|discover|explore)\b/i},{name:"Email Notifications",description:"Welcome emails, alerts, reminders",condition:t=>t.integrations?.includes("email")===!0,keywords:/\b(notification|alert|reminder|email.?notif|sms|welcome.?email)\b/i},{name:"Analytics / Reports",description:"Usage stats, trends, data exports",condition:()=>!1,keywords:/\b(analytics|report|chart|trend|insight|metric)\b/i},{name:"File Uploads",description:"Images, documents, attachments",condition:t=>t.integrations?.includes("file-uploads")===!0,keywords:/\b(upload|image|photo|attachment|document|gallery|file)\b/i},{name:"AI Features",description:"Chatbot, content generation, AI assistant",condition:t=>t.integrations?.includes("ai")===!0,keywords:/\b(ai|chatbot|gpt|llm|generat|assistant)\b/i},{name:"Maps / Location",description:"Google Maps, location search, geolocation",condition:t=>t.integrations?.includes("maps")===!0,keywords:/\b(map|location|address|geo|places)\b/i},{name:"Voice / TTS",description:"Text-to-speech, voice notes, audio generation",condition:()=>!1,keywords:/\b(voice|tts|text.?to.?speech|audio|speak|narrat|podcast|elevenlabs)\b/i},{name:"SMS / Text Messages",description:"Send SMS notifications, OTP verification",condition:t=>t.integrations?.includes("sms")===!0,keywords:/\b(sms|text.?message|twilio|otp|verification.?code|phone.?verif)\b/i},{name:"Web Scraping",description:"Scrape URLs, crawl websites, extract structured data",condition:()=>!1,keywords:/\b(scrape|crawl|web.?scrap|extract.?from.?url|read.?url|ingest.?web|firecrawl)\b/i},{name:"Chat / Messaging",description:"Real-time messaging between users",condition:()=>!1,keywords:/\b(chat|messag|inbox|conversation|dm)\b/i},{name:"Events / Tournaments",description:"Create and manage events, registrations",condition:()=>!1,keywords:/\b(event|tournament|competition|league|registration)\b/i},{name:"High Scores",description:"Track and display top scores with a leaderboard",condition:t=>t.surfaceType==="game",keywords:/\b(high.?score|leaderboard|top.?score|ranking|scoreboard)\b/i},{name:"Save Progress",description:"Save and resume game state between sessions",condition:t=>t.surfaceType==="game",keywords:/\b(save|progress|resume|checkpoint|continue)\b/i},{name:"Guest Play",description:"Play immediately without signing up, optionally link account later",condition:t=>t.surfaceType==="game",keywords:/\b(guest|anonymous|no.?login|play.?now|instant.?play)\b/i},{name:"Levels / Stages",description:"Progressive difficulty with unlockable stages",condition:()=>!1,keywords:/\b(level|stage|unlock|difficult|progress|world)\b/i},{name:"Achievements",description:"Badges, trophies, and milestones for player accomplishments",condition:()=>!1,keywords:/\b(achieve|badge|trophy|milestone|reward|unlock)\b/i},{name:"Daily Challenge",description:"New puzzle or challenge every day to keep players coming back",condition:()=>!1,keywords:/\b(daily|challenge|streak|word.?of.?the.?day|puzzle.?of)\b/i}];function pi(t,e,r){let n=r?.suggestedName||ld(t),o=e.primaryActor==="both"?"Staff + Customers":e.primaryActor==="staff"?"Staff / Admin":e.primaryActor==="customers"?"End Users":"Users",s=e.audienceType??(e.surfaceType==="internal-tool"?"internal":(e.primaryActor==="customers"||e.primaryActor==="both","b2c")),i=e.surfaceType==="internal-tool"?"Internal tool":e.surfaceType==="marketplace"?"Marketplace":e.surfaceType==="content-site"?"Content site":e.surfaceType==="game"?"Game":e.surfaceType==="agent"?"Chat agent":"App",a;if(r?.suggestedFeatures&&r.suggestedFeatures.length>0)a=r.suggestedFeatures.map(l=>({name:l.name,description:l.description,checked:l.recommended,source:l.recommended?"explicit":"suggested"}));else{let l=`${t} ${e.primaryAction||""}`;a=[];for(let c of ad){let p=c.keywords.test(l),d=c.condition(e);(p||d)&&a.push({name:c.name,description:c.description,checked:p,source:p?"explicit":"suggested"})}}return{name:n,audience:o,audienceType:s,surfaceType:i,primaryAction:e.primaryAction||"manage items",features:a,publicLanding:e.publicLanding??!0,authModel:e.authModel??"email",dbProvider:e.dbProvider??"neon",integrations:e.integrations??[],language:r?.language||"English"}}function mi(t){let e=t.features.filter(a=>a.checked),r=t.features.filter(a=>!a.checked),n={email:"Email sign-up",none:"No login (public)",social:"Social login","invite-only":"Invite-only"},o={neon:"Postgres",turso:"SQLite (legacy)"},s={b2c:"Your customers use this app (business-to-customer)",b2b:"Other businesses sign up for this (SaaS platform)",internal:"Internal team tool (staff only)"},i=[`**${t.name}** \u2014 ${t.surfaceType} for ${t.audience}`,`Audience: ${s[t.audienceType]??t.audienceType}`,`Primary action: ${t.primaryAction}`,`Access: ${n[t.authModel]??t.authModel} | Database: ${o[t.dbProvider]??t.dbProvider}${t.publicLanding?" | Landing page: Yes":""}${t.language&&t.language!=="English"?` | Language: ${t.language}`:""}`,""];if(e.length>0){i.push("**Included:**");for(let a of e)i.push(` \u2713 ${a.name} \u2014 ${a.description}`)}if(t.integrations.length>0&&(i.push(""),i.push(`**Integrations:** ${t.integrations.join(", ")}`)),r.length>0){i.push(""),i.push("**Available to add:**");for(let a of r)i.push(` \u25CB ${a.name} \u2014 ${a.description}`)}return i.join(`
2567
+ `)}function ld(t){let e=t.match(/\b(?:called|named)\s+["']?([A-Za-z][A-Za-z0-9 ]{1,30})["']?/i);if(e)return mr(e[1]);let r=new Set(["build","create","make","a","an","the","for","me","my","app","application","website","web","tool","system","platform","using","with","and","that","this","want","need","please","can","you","i","mist","mistflow"]),o=t.toLowerCase().replace(/[^a-z0-9\s]/g,"").split(/\s+/).filter(s=>s.length>2&&!r.has(s)).slice(0,3);return o.length===0?"my-app":o.join("-")}function mr(t){return t.toLowerCase().replace(/[^a-z0-9\s]/g,"").trim().replace(/\s+/g,"-")}function No(t,e,r){let n=Dn(t||"your app"),o=e.map((i,a)=>{let l=hr(i.id||`direction-${a}`),c=Dn(i.name||`Direction ${a+1}`),p=Dn(i.summary||"");return`<button class="tab${a===0?" active":""}" data-target="${l}" type="button"><span class="tab-name">${c}</span>${p?`<span class="tab-sub">${p}</span>`:""}</button>`}).join(`
2568
+ `),s=e.map((i,a)=>{let l=hr(i.id||`direction-${a}`),c=r[i.id??""]??"";return`<iframe class="render-frame${a===0?" active":""}" data-id="${l}" srcdoc="${hr(c)}" sandbox="allow-same-origin allow-scripts" title="${hr(i.name||`Direction ${a+1}`)}"></iframe>`}).join(`
2867
2569
  `);return`<!doctype html>
2868
2570
  <html lang="en">
2869
2571
  <head>
@@ -2946,13 +2648,13 @@ Recommended: ${i.recommended}`:""}`:i.recommended?`Recommended: ${i.recommended}
2946
2648
  <body>
2947
2649
  <header class="picker-bar">
2948
2650
  <div class="picker-title"><strong>${n}</strong> \u2014 pick a design direction</div>
2949
- <div class="tabs">${r}</div>
2651
+ <div class="tabs">${o}</div>
2950
2652
  </header>
2951
2653
  <div class="frames">
2952
2654
  ${s}
2953
2655
  </div>
2954
2656
  <footer class="picker-foot">
2955
- <div><strong>How to pick:</strong> click each tab to compare. When you've chosen, tell your AI assistant the direction name (e.g. &ldquo;pick <em>${e[0]?.name?Mn(e[0].name):"Morning Paper"}</em>&rdquo;).</div>
2657
+ <div><strong>How to pick:</strong> click each tab to compare. When you've chosen, tell your AI assistant the direction name (e.g. &ldquo;pick <em>${e[0]?.name?Dn(e[0].name):"Morning Paper"}</em>&rdquo;). Images only for representation, will be replaced with actual designs.</div>
2956
2658
  <div><strong>None fit?</strong> Tell your assistant <code>describe your own</code> and give a short description.</div>
2957
2659
  </footer>
2958
2660
  <script>
@@ -2970,61 +2672,61 @@ ${s}
2970
2672
  </script>
2971
2673
  </body>
2972
2674
  </html>
2973
- `}function Mn(t){return String(t).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}function ho(t){return Mn(t)}var go="__mistflow_url_choice__",ad=600*1e3;function pi(){let t=ke(xt(),".mistflow","confirm-secret");if(lt(t))try{return Buffer.from(jn(t,"utf-8").trim(),"hex")}catch{}let e=sd(32);return $t(ke(xt(),".mistflow"),{recursive:!0}),kt(t,e.toString("hex"),{mode:384}),e}function mi(t){return rd("sha256").update(t.trim().toLowerCase()).digest("hex").slice(0,16)}function ld(t,e){let o={cwd:t,d:mi(e),exp:Date.now()+ad},n=Buffer.from(JSON.stringify(o)).toString("base64url"),r=ui("sha256",pi()).update(n).digest("base64url");return`${n}.${r}`}function cd(t,e,o){let n=t.split(".");if(n.length!==2)return!1;let[r,s]=n,i=ui("sha256",pi()).update(r).digest("base64url"),a=Buffer.from(s),l=Buffer.from(i);if(a.length!==l.length||!id(a,l))return!1;try{let c=JSON.parse(Buffer.from(r,"base64url").toString("utf-8"));return!(typeof c.exp!="number"||Date.now()>c.exp||c.cwd!==e||c.d!==mi(o))}catch{return!1}}function dd(t){let e=t,o=xt(),n=!1;for(let r=0;r<64;r++){if(lt(ke(e,"mistflow.json")))return"mistflow";if(!n&&lt(ke(e,"package.json"))&&(n=!0),e===o)break;let s=nd(e);if(s===e)break;e=s}return n?"foreign":"none"}function ud(t){let e=xt(),o=t.replace(/\/+$/,"");if(o===e||o==="/"||o===""||o==="/tmp"||o==="/private/tmp")return!0;let n=["Desktop","Documents","Downloads"];for(let r of n)if(o===ke(e,r))return!0;return!1}function pd(t){let e=new Set(["build","create","make","scaffold","start","using","with","mist","mistflow","a","an","the","for","me","my","new","app","application","website","site","web","tool","dashboard","landing","page","platform","please","can","you","i","want","need"]);return t.toLowerCase().replace(/[^a-z0-9\s-]/g," ").split(/\s+/).map(n=>n.trim()).filter(n=>n.length>1&&!e.has(n)).slice(0,4).join("-").slice(0,48)||"new-app"}function si(t){if(!lt(t))return!0;try{return di(t).isDirectory()?ci(t).filter(n=>n!==".mistflow").length===0:!1}catch{return!1}}function md(t,e){let o=pd(e),n=ke(t,o);if(si(n))return n;for(let r=2;r<=50;r++){let s=ke(t,`${o}-${r}`);if(si(s))return s}return ke(t,`${o}-${Date.now()}`)}function hd(t,e){if(e)return!0;let o=t.toLowerCase(),n=/\b(build|create|make|scaffold|start|generate)\b/.test(o),r=/\b(app|application|website|site|web\s+app|landing\s+page|dashboard|tool|marketplace|game|blog|portfolio|crm)\b/.test(o),s=/\b(add|change|fix|update|edit|refactor|debug|review)\b/.test(o)&&!n;return n&&r&&!s}function ii(t){try{$t(t,{recursive:!0});return}catch(e){return e instanceof Error?e.message:String(e)}}function ai(t){let{projectPath:e,description:o,confirmToken:n,suggestedPath:r,previousTokenInvalid:s}=t;return JSON.stringify({status:"confirm_new_project",requires_user_input:!0,projectPath:e,description:o,confirmToken:n,suggestedScaffoldPath:r,askUserQuestion:{question:"You're inside an existing project directory. Do you want to scaffold a new Mistflow app here, or edit the existing codebase directly?",header:"Scope",options:[{label:"Scaffold a new Mistflow app in a subdirectory",description:`Creates a fresh project at \`${r}\` without touching the existing code.`},{label:"Edit this existing codebase directly",description:"Cancel Mistflow. Handle the request by editing the current project's files."}],multiSelect:!1},instruction:["The user is inside an existing project (package.json found up the directory tree, no mistflow.json).","MANDATORY: Ask the user the provided askUserQuestion before calling mist_plan again.","If they pick 'Scaffold a new Mistflow app in a subdirectory', call mist_plan again with the SAME description, SAME projectPath, and confirmToken set to the token returned above.",`Mistflow will create and remember the new app directory at \`${r}\`. Do not change projectPath to the child directory on the retry; the token is intentionally bound to the original directory.`,"If they pick 'Edit this existing codebase directly', DO NOT call mist_plan again. Fulfill their request by editing files directly in the current project.",s?"The previous confirmToken was invalid, expired, or did not match the current directory/description. Use the fresh token above.":""].filter(Boolean).join(`
2974
- `)})}var gd=B.object({description:B.string().optional().describe("App description or modification request. Required for the first call; omit on follow-up polls where only conversationId is passed. "),projectPath:B.string().min(1).describe("REQUIRED. Absolute path to the user's current working directory \u2014 where the Mistflow app will be scaffolded. Pass the directory the user is actually working in (e.g. /Users/alice/projects). Do NOT pass '/', '~', $HOME, Desktop, Documents, Downloads, or /tmp \u2014 the tool will refuse to scaffold at those locations. If you are unsure of the user's working directory, ask them before calling this tool."),sessionId:B.string().uuid().optional().describe("Backend-owned session ID. When present, mist_plan polls GET /api/sessions/{id}/next and returns the next instruction (wait, ask_user, pick_design, call_mist_init, etc.). Pass it on every subsequent mist_* call to keep the same session."),conversationId:B.string().optional().describe("Returned by a previous mist_plan call with status 'clarify' or 'running'. Pass it back alone (no description) to poll an in-flight discovery call; pass with answers to submit responses."),answers:B.preprocess(t=>{if(typeof t!="string")return t;let e=t.trim();if(!e.startsWith("[")&&!e.startsWith("{"))return t;try{return JSON.parse(e)}catch{return t}},B.union([B.record(B.string()),B.array(B.object({question:B.string().optional(),decisionKey:B.string().optional(),answer:B.string()}))]).optional()).describe("User's answers to the clarifying questions. Pass an ARRAY of { question, decisionKey, answer } objects directly \u2014 do NOT stringify it as JSON. The schema also accepts a { '<question text>': '<answer label>' } object map for one-question-per-decisionKey rounds. Strings that look like JSON arrays/objects are auto-parsed for resilience."),existingPlan:B.record(B.unknown()).optional().describe("If provided, modifies this existing plan instead of creating a new one. Pass the current plan object from mistflow.json."),existingPlanId:B.string().optional().describe("Alternative to existingPlan \u2014 pass the planId from a previous mist_plan call to modify that plan."),templateToken:B.string().optional().describe("Fork from a shared template. Pass the share token (from a mistflow.ai/t/... URL) to clone that project's plan into your workspace."),remixDescription:B.string().optional().describe("Optional remix request when forking a template. Describes how you want the template to be different. E.g. 'Make it for tracking books instead of habits, add a search feature.' Only used with templateToken."),autonomous:B.boolean().optional().describe("DANGER \u2014 only set this to `true` when the user has EXPLICITLY asked to skip discovery questions (phrases like 'no questions', 'just build it', 'autonomous mode', 'don't ask'). Default is `false` and should stay that way in every other case. Do NOT set autonomous=true as a retry strategy after a plan-gen failure \u2014 it doesn't make things more reliable, it just removes the user's control over discovery answers (roles, integrations, auth model, etc.) and Sonnet ends up guessing. If a plan call fails, retry the SAME call with the same parameters, not a more aggressive one. Do NOT set autonomous=true to speed things up \u2014 the server already runs plan-gen in the background and polls, so there is no speed benefit. When set, skips the clarify round and generates the plan straight from the description. The user loses the chance to pick any option."),language:B.string().optional().describe("UI language for the app. All user-facing text, labels, buttons, and content will be generated in this language. Use the language name in English (e.g. 'Spanish', 'French', 'Arabic', 'Japanese'). Defaults to English if not specified."),brandMentioned:B.boolean().optional().describe("Set to true ONLY when the user's original request explicitly invoked Mistflow by name (e.g. 'build me a CRM using mist', 'make a todo app with mistflow'). Skips the existing-project confirmation gate because the user clearly wants Mistflow. Do NOT set this for generic 'build me X' requests. Do NOT infer this \u2014 only set it when the user literally typed 'mist' or 'mistflow'."),confirmToken:B.string().optional().describe("The token returned in a previous mist_plan response with status 'confirm_new_project'. Only pass this AFTER asking the user via AskUserQuestion and they chose to scaffold a new Mistflow app in an existing-project directory. The token is bound to the projectPath and description \u2014 you must pass the SAME description AND projectPath on the retry."),urlChoice:B.string().optional().describe("The user's answer to the 'Your app URL' question from a previous mist_plan response. Pass JUST the subdomain (e.g. 'nutrition-tracker'), not the full URL or the option label. If the user kept the default suggestion, pass the suggested subdomain verbatim. If they typed a custom URL like 'myapp.mistflow.app', pass just 'myapp'. Pass this as a top-level parameter \u2014 do NOT nest it inside answers."),designConversationId:B.string().optional().describe("Returned by a previous mist_plan call with status 'design_clarify'. Pass this ONLY on the SUBMIT call after the user has picked a direction \u2014 i.e. ALWAYS together with `designDirection`. Do NOT include it on polling calls (status: 'rendering_directions' / 'design_clarify_pending') \u2014 for polling, send only `{ projectPath, conversationId }`. Including `designConversationId` without `designDirection` makes the backend respond 'you passed designConversationId but no designDirection' and wastes a round-trip. Decision rule: if the user has not yet typed/clicked a pick, omit this field; if they have, include both this field AND designDirection together in the same call."),designDirection:B.object({id:B.string().optional(),name:B.string().optional(),summary:B.string().optional(),heroHeadline:B.string().optional(),ctaText:B.string().optional(),bodySample:B.string().optional(),fontsHint:B.string().optional(),fonts:B.object({display:B.string(),body:B.string()}).partial().optional(),colorMood:B.string().optional(),colors:B.object({bg:B.string(),fg:B.string(),accent:B.string()}).partial().optional(),heroTreatment:B.string().optional(),shapeLang:B.string().optional(),texture:B.string().optional(),decorationHint:B.string().optional(),custom:B.string().optional()}).passthrough().optional().describe("The creative direction the user picked from a previous 'design_clarify' response. Pass ONLY the minimal shape: (a) `{ id: '<short-kebab-id-from-directionRefs>' }` when the user picked one of the proposed options \u2014 copy the id verbatim from `directionRefs[].id`, e.g. 'morning-paper' or 'quiet-utility'; or (b) `{ custom: '<the user\\'s exact words>' }` + `userConfirmedCustom: true` when the user typed their own description. Do NOT pass the full direction object (name, summary, fonts, colors, hero_headline, body_sample, etc.) \u2014 those fields are ignored and may exceed backend length limits, causing 422 rejections. ID values are short kebab-case strings (\u2264256 chars). Custom descriptions are user-typed prose (\u22644000 chars). Anything longer is wrong."),userConfirmedCustom:B.boolean().optional().describe("Set true ONLY when the user themselves typed a custom direction (via AskUserQuestion 'Other' or by explicitly describing the look in the chat). Required when submitting designDirection: { custom: ... } if the backend has real directions cached \u2014 the server rejects custom submissions otherwise to stop the AI from silently bypassing the picker. NEVER set this true unless the words in `custom` came directly from the user."),forceNew:B.boolean().optional().describe("Set to true ONLY after the user has explicitly chosen 'start fresh' from a previous mist_plan response with status 'resume_offer'. Suppresses the resume-offer check and lets a new session start even when this machine has unfinished work in flight. Do NOT set this preemptively on the first call \u2014 the resume-offer check needs to run so the user gets the chance to pick up where they left off.")});function fd(t){let e=[[/payment/i,"Payments"],[/database/i,"Database"],[/auth|sign.?up|login|access/i,"Access"],[/landing.?page/i,"Landing page"],[/who.*using|user|role/i,"Users"],[/design|theme|style/i,"Design"],[/deploy/i,"Deploy"],[/domain/i,"Domain"],[/notification/i,"Notify"],[/email/i,"Email"],[/mobile|responsive/i,"Mobile"],[/integrat/i,"Integration"],[/field|info|propert|detail|contain/i,"Item shape"],[/view|layout|board|grid|list|timeline/i,"View"],[/scope|how many|one.*or.*many|multi/i,"Scope"],[/share|read.?only|viewer|stakeholder/i,"Sharing"],[/workflow|status|state|move|stage|pipeline/i,"Workflow"],[/avoid|bloat|simple|complex|minimal/i,"Constraints"],[/time.*period|quarter|month|sprint/i,"Time periods"],[/swimlane|column|group|categor/i,"Structure"]];for(let[n,r]of e)if(n.test(t))return r;return t.replace(/[?.,!]/g,"").split(/\s+/).filter(n=>!["what","how","do","does","is","are","the","a","an","would","should","you","your","for","this","that","to","of","or","and","want","like","prefer"].includes(n.toLowerCase())).slice(0,2).join(" ").slice(0,12)||"Option"}function yd(t){let e=ke(xt(),".mistflow","plans",`${t}.json`);if(!lt(e))return null;try{return JSON.parse(jn(e,"utf-8")).plan??null}catch{return null}}async function bd(t){let e=Date.now(),o=5e4,n=2e3;for(;Date.now()-e<o;){try{let r=await eo(t.design_conversation_id);if(r.status==="ready")return{status:"design_clarify",design_conversation_id:t.design_conversation_id,directions:r.directions,plan:r.plan,methodology:r.methodology};if(r.status==="failed")return{status:"ready",plan:r.plan,methodology:r.methodology}}catch(r){let s=r instanceof Error?r.message:String(r);if(s.toLowerCase().includes("not found"))return{status:"ready",plan:t.plan,methodology:t.methodology};console.error(`[plan] directions poll failed: ${s}`)}await new Promise(r=>setTimeout(r,n))}return console.error("[plan] directions poll exhausted (50s) \u2014 preserving pending so host re-polls"),t}function wd(t){return typeof t=="string"&&t.trim()?t.trim():void 0}function vd(t,e){for(let o of e){let n=wd(t[o]);if(n)return n}}function kd(t){return vd(t,["mockup_url","mockupUrl","preview_url","previewUrl","live_url","liveUrl","deploy_url","deployUrl","url"])}function Lt(t,e){return`Next action: call ${t} with ${JSON.stringify(e)}. Do not omit sessionId.`}function li(t){return t?.length?t.map((e,o)=>{let n=typeof e.text=="string"?e.text:typeof e.question=="string"?e.question:`Question ${o+1}`,r=Array.isArray(e.options)?e.options.map(s=>{if(s&&typeof s=="object"&&"label"in s){let i=s.label;return typeof i=="string"?i:null}return typeof s=="string"?s:null}).filter(s=>!!s):[];return r.length?`${o+1}. ${n} Options: ${r.join(", ")}`:`${o+1}. ${n}`}):["No questions were returned by the backend."]}function xd(t){let e=kd(t),o=t.reason?`
2975
- Reason: ${t.reason}`:"";switch(t.state){case"COMPLETE":return`Session complete.${e?` Live URL: ${e}`:""}${o}`;case"CANCELLED":return`Session was cancelled.${o}`;case"FAILED":return`Session ended in FAILED.${e?` Last known URL: ${e}`:""}${o}`;case"ABANDONED":return`Session was abandoned.${o}`;case"EXPIRED":return`Session expired.${o}`;default:return`Session finished with state ${t.state}.${e?` URL: ${e}`:""}${o}`}}async function Sd(t,e){let{description:o,projectPath:n,sessionId:r,conversationId:s,answers:i,existingPlan:a,existingPlanId:l,templateToken:c,remixDescription:m,autonomous:u,language:p,brandMentioned:h,confirmToken:U,urlChoice:C,designConversationId:x,designDirection:w,userConfirmedCustom:A,forceNew:J}=t;if(r&&!s&&!x)try{let g=await Jt(r),y=[`Session ID: ${g.session_id}`,`Status: ${g.status??"active"}`,`Instruction: ${g.instruction}`];switch(g.reason&&y.push(`Reason: ${g.reason}`),g.instruction){case"wait":return d([...y,`Next action: ${g.reason??"The backend is still preparing the next step."} Tell the user briefly what's happening, then call mist_plan with { projectPath, sessionId: "${g.session_id}" } IMMEDIATELY \u2014 do NOT run bash sleep. The server holds each poll open up to ~10s and returns as soon as the next phase lands.`].join(`
2976
- `));case"resume_conversation":{let k=g.conversation_id;return d([...y,`In-flight conversation: ${k??"(missing \u2014 backend bug)"}`,`Next action: call mist_plan with { projectPath, conversationId: "${k??""}" } to continue the plan flow. The conversation poll renders questions / design directions in the same way it would have on the original mist_plan response.`].join(`
2977
- `))}case"continue":return d([...y,`Next action: ${g.reason??"Continue from where you left off \u2014 read mistflow.json to find the next pending step, then call mist_install / mist_implement / mist_build / mist_deploy as appropriate."}`].join(`
2978
- `));case"call_mist_init":return d([...y,Lt("mist_init",{sessionId:g.session_id,path:n})].join(`
2979
- `));case"call_mist_deploy":return d([...y,Lt("mist_deploy",{sessionId:g.session_id,projectPath:n})].join(`
2980
- `));case"ask_user":return d([...y,"Next action: ask the user these questions, then submit the answers through the session flow.",...li(g.questions)].join(`
2981
- `));case"pick_design":return d([...y,"Next action: ask the user to pick one of these design directions, then submit the choice through the session flow.",...li(g.directions??g.questions)].join(`
2982
- `));case"review_mockup":return d([...y,Lt("mist_mockup",{sessionId:g.session_id,projectPath:n})].join(`
2983
- `));case"call_mist_install":return d([...y,Lt("mist_install",{sessionId:g.session_id,projectPath:n})].join(`
2984
- `));case"call_mist_implement":return d([...y,Lt("mist_implement",{sessionId:g.session_id,projectPath:n})].join(`
2985
- `));case"call_mist_build":return d([...y,Lt("mist_build",{sessionId:g.session_id,projectPath:n})].join(`
2986
- `));case"call_mist_qa":return d([...y,Lt("mist_qa",{sessionId:g.session_id,projectPath:n})].join(`
2987
- `));case"review_plan":return d([...y,`Next action: ${g.reason??"User asked to review PLAN.md before scaffolding. Open <projectPath>/PLAN.md, let the user read / edit, then call mist_session({ resume }) and re-run mist_init."}`].join(`
2988
- `));case"done":return d([...y,xd(g)].join(`
2989
- `))}}catch(g){let y=g instanceof Error?g.message:String(g);return d(`Could not poll session '${r}': ${y}`,!0)}if(s&&!o&&!i&&!x&&!w&&!a&&!l&&!c)try{let g=await oo(s);if(g.status==="clarify_pending"||g.status==="plan_pending"){let y=typeof g.phase_hint=="string"?g.phase_hint:null,k=typeof g.elapsed_seconds=="number"?g.elapsed_seconds:null,N=typeof g.progress=="number"?g.progress:null,E=g.status==="plan_pending",K=E?"generating_plan":"generating_questions",oe=y?`${y}${k!==null?` (${k}s elapsed)`:""}`:E?"Plan is being generated (build_plan + image enrichment, 30-60s typical)":"Discovery questions are being analyzed";return d(JSON.stringify({status:"running",conversationId:s,phase:K,...y?{phaseHint:y}:{},...k!==null?{elapsedSeconds:k}:{},...N!==null?{progress:N}:{},nextAction:`${oe}. Tell the user briefly what's happening (e.g. "${y??(E?"Generating the plan":"Analyzing your description")}"), then call mist_plan with { projectPath, conversationId: "${s}" } IMMEDIATELY \u2014 do NOT run bash sleep. The server holds each poll open up to ~10s and returns as soon as the next phase lands.`}))}if(g.status==="design_clarify"&&Array.isArray(g.directions)){let k=g.directions.map((P,ue)=>({id:P.id??String(ue),name:P.name??`Direction ${ue+1}`,summary:P.summary,hero_headline:P.hero_headline,cta_text:P.cta_text,body_sample:P.body_sample,fonts:P.fonts,colors:P.colors,hero_treatment:P.hero_treatment,shape_lang:P.shape_lang,texture:P.texture,decoration_hint:P.decoration_hint})),N=g.plan?.name??"your app",E=g.design_conversation_id,K={},oe=!1;try{if(E){let ve=Date.now();for(;Date.now()-ve<5e4;){let Ht=await no(E),z=0;for(let Se of k){let _e=Ht[Se.id];_e&&((_e.status==="done"||_e.status==="ready")&&typeof _e.html=="string"&&_e.html.length>0?(K[Se.id]=_e.html,z+=1):_e.status==="failed"&&(z+=1))}if(z===k.length){oe=!0;break}await new Promise(Se=>setTimeout(Se,5e3))}}}catch(P){console.error(`[mist_plan:design_clarify] render poll failed: ${P instanceof Error?P.message:String(P)}`)}if(!oe)return d(JSON.stringify({status:"running",phase:"rendering_directions",conversation_id:s,design_conversation_id:E,renderedSoFar:Object.keys(K).length,totalDirections:k.length,nextAction:`${Object.keys(K).length}/${k.length} direction renders ready. Tell the user "still generating design previews" briefly, then call mist_plan with { projectPath, conversationId: "${s}" } again IMMEDIATELY \u2014 do NOT bash sleep. The render-poll holds the connection up to ~50s; each call returns as soon as state changes. Do NOT ask the user about design yet; the picker isn't ready.`}));let ae=k.filter(P=>K[P.id]),be,I,Y=ke(n,".mistflow"),ge=ke(Y,"design-directions.html"),ee=ke(Y,"picker-state.json"),we=!1;if(E)try{lt(ee)&&JSON.parse(jn(ee,"utf-8")).opened_for_design_cid===E&&(we=!0)}catch{}try{if($t(Y,{recursive:!0}),we)be=ge;else{let P=Ar(N,ae,K);kt(ge,P,"utf-8"),be=ge;let ue=Rr(`file://${ge}`);ue.opened||(I=ue.reason),E&&kt(ee,JSON.stringify({opened_for_design_cid:E,opened_at:new Date().toISOString()},null,2),"utf-8")}}catch(P){console.error(`[mist_plan:design_clarify] picker write/open failed: ${P instanceof Error?P.message:String(P)}`)}let Ee=ae.map(P=>({id:P.id,name:P.name})),fe=Ee.map(P=>P.name).filter(Boolean);return d(JSON.stringify({status:"design_clarify",previewPath:be,previewOpenError:I,directionRefs:Ee,directionNames:fe,design_conversation_id:E,conversation_id:s,nextAction:[`The visual picker has been opened in the user's browser at file://${be??"(path missing)"} \u2014 every direction is a real, rendered landing page. The user is looking at it now.`,I?`(auto-open suppressed: ${I} \u2014 tell the user to open file://${be} manually)`:"",`Now use your host's structured-question UI (AskUserQuestion in Claude Code) to ask the user which direction they pick. List EXACTLY these names as options: ${fe.map(P=>`"${P}"`).join(", ")}. Do NOT invent extra options. Add a "Type something" option for custom descriptions.`,`When the user picks a name, call mist_plan with { projectPath, conversationId: "${s}", designConversationId: "${E}", designDirection: { id: "<map name \u2192 id from directionRefs>" } }. If the user types a custom description, pass designDirection: { custom: "<their exact words>" } and userConfirmedCustom: true.`].filter(Boolean).join(`
2990
-
2991
- `)}))}return d(JSON.stringify(g))}catch(g){let y=g instanceof Error?g.message:String(g);return d(`Could not poll plan conversation '${s}': ${y}`,!0)}let V=o??"";if(!V.trim()&&!s&&!x&&!a&&!l&&!c)return d("mist_plan requires one of:\n \u2022 `description` \u2014 first call with a new app idea\n \u2022 `conversationId` alone \u2014 poll an in-flight discovery / plan-gen call\n \u2022 `conversationId` + `answers` \u2014 submit answers after `status: 'clarify'`\n \u2022 `designConversationId` + `designDirection` \u2014 submit a design pick after `status: 'design_clarify_pending'`\n \u2022 `existingPlanId` + a modification `description` \u2014 edit an existing plan\n \u2022 `templateToken` \u2014 fork a published template\n\nThe most common confusion: after `design_clarify_pending`, the next call needs `designConversationId` + `designDirection`, NOT `conversationId` alone.",!0);if(x&&!w&&!V.trim()&&!i)return d(`You passed designConversationId='${x}' but no designDirection. After the user picks one of the directions from the preview, pass it back as: mist_plan({ designConversationId: '${x}', designDirection: { id: '<their-pick-id>' } }). If the user asked for something custom, pass designDirection: { custom: '<their description>' }.`,!0);let te=a;if(!te&&l&&(te=yd(l)??void 0,!te))return d("Your previous plan is no longer available. Please describe your app again to generate a new plan.",!0);let le=s;if(!Te())return Oe("plan a new app");let Q,q;if(!le&&!te&&!c&&!x){if(!od(n))return d(`projectPath must be an absolute path \u2014 received '${n}'. Pass the full absolute path to the user's project directory (e.g. /Users/alice/projects/my-app).`,!0);let g=dd(n);if(g!=="mistflow"&&ud(n))return d(JSON.stringify({status:"unsafe_cwd",projectPath:n,instruction:[`The projectPath you passed (${n}) is a system-level directory (home root, Desktop, Documents, Downloads, or /tmp).`,"Scaffolding here would drop node_modules and a git repo directly at that location, which is messy and hard to clean up.","MANDATORY: Before calling mist_plan again:"," 1. Pick a subfolder name based on the app description (e.g. 'nutrition-tracker', 'habit-app').",` 2. Create the subfolder at ${n}/<subfolder-name>.`," 3. Call mist_plan again with the SAME description and projectPath set to the new subfolder.","Do NOT ask the user where to put the project \u2014 just pick a sensible subfolder name from their description."].join(`
2992
- `)}),!0);if(g==="foreign"){let y=U?cd(U,n,V):!1,k=md(n,V),N=hd(V,h);if(N||y){q=k;let E=ii(q);if(E)return d(`Mistflow could not create the scaffold directory at ${q}: ${E}`,!0);Q=`Note: Mistflow will scaffold the new app at \`${q}\`. The existing project at \`${n}\` is not modified.`}if(!y&&!N){let E=ld(n,V);if(e?.server){let K=await On(e.server,[{question:"You're inside an existing project. Where should Mistflow scaffold the new app?",decisionKey:"scopeChoice",recommended:`Create at: ${k}`,why:"Mistflow creates a NEW app and never modifies the surrounding codebase. The default puts it in a subdirectory of the current path, named after your description. You can pick a custom path or cancel if you actually want to edit the existing project instead.",options:[{label:`Create at: ${k}`,description:"Default \u2014 uses your description as the folder name."},{label:"Choose a different path",description:"Type a custom absolute path in the textbox below."},{label:"Cancel \u2014 I wanted to edit the existing project",description:"Stop Mistflow. Handle the request by editing files in the current project instead."}]}],`## Existing project detected
2675
+ `}function Dn(t){return String(t).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}function hr(t){return Dn(t)}var gr="__mistflow_url_choice__",gd=600*1e3;function Si(){let t=fe(_t(),".mistflow","confirm-secret");if(mt(t))try{return Buffer.from(Ln(t,"utf-8").trim(),"hex")}catch{}let e=md(32);return Ut(fe(_t(),".mistflow"),{recursive:!0}),St(t,e.toString("hex"),{mode:384}),e}function _i(t){return pd("sha256").update(t.trim().toLowerCase()).digest("hex").slice(0,16)}function fd(t,e){let r={cwd:t,d:_i(e),exp:Date.now()+gd},n=Buffer.from(JSON.stringify(r)).toString("base64url"),o=xi("sha256",Si()).update(n).digest("base64url");return`${n}.${o}`}function yd(t,e,r){let n=t.split(".");if(n.length!==2)return!1;let[o,s]=n,i=xi("sha256",Si()).update(o).digest("base64url"),a=Buffer.from(s),l=Buffer.from(i);if(a.length!==l.length||!hd(a,l))return!1;try{let c=JSON.parse(Buffer.from(o,"base64url").toString("utf-8"));return!(typeof c.exp!="number"||Date.now()>c.exp||c.cwd!==e||c.d!==_i(r))}catch{return!1}}function bd(t){let e=t,r=_t(),n=!1;for(let o=0;o<64;o++){if(mt(fe(e,"mistflow.json")))return"mistflow";if(!n&&mt(fe(e,"package.json"))&&(n=!0),e===r)break;let s=dd(e);if(s===e)break;e=s}return n?"foreign":"none"}function wd(t){let e=_t(),r=t.replace(/\/+$/,"");if(r===e||r==="/"||r===""||r==="/tmp"||r==="/private/tmp")return!0;let n=["Desktop","Documents","Downloads"];for(let o of n)if(r===fe(e,o))return!0;return!1}function vd(t){let e=new Set(["build","create","make","scaffold","start","using","with","mist","mistflow","a","an","the","for","me","my","new","app","application","website","site","web","tool","dashboard","landing","page","platform","please","can","you","i","want","need"]);return t.toLowerCase().replace(/[^a-z0-9\s-]/g," ").split(/\s+/).map(n=>n.trim()).filter(n=>n.length>1&&!e.has(n)).slice(0,4).join("-").slice(0,48)||"new-app"}function fi(t){if(!mt(t))return!0;try{return ki(t).isDirectory()?vi(t).filter(n=>n!==".mistflow").length===0:!1}catch{return!1}}function kd(t,e){let r=vd(e),n=fe(t,r);if(fi(n))return n;for(let o=2;o<=50;o++){let s=fe(t,`${r}-${o}`);if(fi(s))return s}return fe(t,`${r}-${Date.now()}`)}function xd(t,e){if(e)return!0;let r=t.toLowerCase(),n=/\b(build|create|make|scaffold|start|generate)\b/.test(r),o=/\b(app|application|website|site|web\s+app|landing\s+page|dashboard|tool|marketplace|game|blog|portfolio|crm)\b/.test(r),s=/\b(add|change|fix|update|edit|refactor|debug|review)\b/.test(r)&&!n;return n&&o&&!s}function yi(t){try{Ut(t,{recursive:!0});return}catch(e){return e instanceof Error?e.message:String(e)}}function bi(t){let{projectPath:e,description:r,confirmToken:n,suggestedPath:o,previousTokenInvalid:s}=t;return JSON.stringify({status:"confirm_new_project",requires_user_input:!0,projectPath:e,description:r,confirmToken:n,suggestedScaffoldPath:o,askUserQuestion:{question:"You're inside an existing project directory. Do you want to scaffold a new Mistflow app here, or edit the existing codebase directly?",header:"Scope",options:[{label:"Scaffold a new Mistflow app in a subdirectory",description:`Creates a fresh project at \`${o}\` without touching the existing code.`},{label:"Edit this existing codebase directly",description:"Cancel Mistflow. Handle the request by editing the current project's files."}],multiSelect:!1},instruction:["The user is inside an existing project (package.json found up the directory tree, no mistflow.json).","MANDATORY: Ask the user the provided askUserQuestion before calling mist_plan again.","If they pick 'Scaffold a new Mistflow app in a subdirectory', call mist_plan again with the SAME description, SAME projectPath, and confirmToken set to the token returned above.",`Mistflow will create and remember the new app directory at \`${o}\`. Do not change projectPath to the child directory on the retry; the token is intentionally bound to the original directory.`,"If they pick 'Edit this existing codebase directly', DO NOT call mist_plan again. Fulfill their request by editing files directly in the current project.",s?"The previous confirmToken was invalid, expired, or did not match the current directory/description. Use the fresh token above.":""].filter(Boolean).join(`
2676
+ `)})}var Sd=F.object({description:F.string().optional().describe("App description or modification request. Required for the first call; omit on follow-up polls where only conversationId is passed. "),projectPath:F.string().min(1).describe("REQUIRED. Absolute path to the user's current working directory \u2014 where the Mistflow app will be scaffolded. Pass the directory the user is actually working in (e.g. /Users/alice/projects). Do NOT pass '/', '~', $HOME, Desktop, Documents, Downloads, or /tmp \u2014 the tool will refuse to scaffold at those locations. If you are unsure of the user's working directory, ask them before calling this tool."),sessionId:F.string().uuid().optional().describe("Backend-owned session ID. When present, mist_plan polls GET /api/sessions/{id}/next and returns the next instruction (wait, ask_user, pick_design, call_mist_init, etc.). Pass it on every subsequent mist_* call to keep the same session."),conversationId:F.string().optional().describe("Returned by a previous mist_plan call with status 'clarify' or 'running'. Pass it back alone (no description) to poll an in-flight discovery call; pass with answers to submit responses."),answers:F.preprocess(t=>{if(typeof t!="string")return t;let e=t.trim();if(!e.startsWith("[")&&!e.startsWith("{"))return t;try{return JSON.parse(e)}catch{return t}},F.union([F.record(F.string()),F.array(F.object({question:F.string().optional(),decisionKey:F.string().optional(),answer:F.string()}))]).optional()).describe("User's answers to the clarifying questions. Pass an ARRAY of { question, decisionKey, answer } objects directly \u2014 do NOT stringify it as JSON. The schema also accepts a { '<question text>': '<answer label>' } object map for one-question-per-decisionKey rounds. Strings that look like JSON arrays/objects are auto-parsed for resilience."),existingPlan:F.record(F.unknown()).optional().describe("If provided, modifies this existing plan instead of creating a new one. Pass the current plan object from mistflow.json."),existingPlanId:F.string().optional().describe("Alternative to existingPlan \u2014 pass the planId from a previous mist_plan call to modify that plan."),templateToken:F.string().optional().describe("Fork from a shared template. Pass the share token (from a mistflow.ai/t/... URL) to clone that project's plan into your workspace."),remixDescription:F.string().optional().describe("Optional remix request when forking a template. Describes how you want the template to be different. E.g. 'Make it for tracking books instead of habits, add a search feature.' Only used with templateToken."),autonomous:F.boolean().optional().describe("DANGER \u2014 only set this to `true` when the user has EXPLICITLY asked to skip discovery questions (phrases like 'no questions', 'just build it', 'autonomous mode', 'don't ask'). Default is `false` and should stay that way in every other case. Do NOT set autonomous=true as a retry strategy after a plan-gen failure \u2014 it doesn't make things more reliable, it just removes the user's control over discovery answers (roles, integrations, auth model, etc.) and Sonnet ends up guessing. If a plan call fails, retry the SAME call with the same parameters, not a more aggressive one. Do NOT set autonomous=true to speed things up \u2014 the server already runs plan-gen in the background and polls, so there is no speed benefit. When set, skips the clarify round and generates the plan straight from the description. The user loses the chance to pick any option."),language:F.string().optional().describe("UI language for the app. All user-facing text, labels, buttons, and content will be generated in this language. Use the language name in English (e.g. 'Spanish', 'French', 'Arabic', 'Japanese'). Defaults to English if not specified."),brandMentioned:F.boolean().optional().describe("Set to true ONLY when the user's original request explicitly invoked Mistflow by name (e.g. 'build me a CRM using mist', 'make a todo app with mistflow'). Skips the existing-project confirmation gate because the user clearly wants Mistflow. Do NOT set this for generic 'build me X' requests. Do NOT infer this \u2014 only set it when the user literally typed 'mist' or 'mistflow'."),confirmToken:F.string().optional().describe("The token returned in a previous mist_plan response with status 'confirm_new_project'. Only pass this AFTER asking the user via AskUserQuestion and they chose to scaffold a new Mistflow app in an existing-project directory. The token is bound to the projectPath and description \u2014 you must pass the SAME description AND projectPath on the retry."),urlChoice:F.string().optional().describe("The user's answer to the 'Your app URL' question from a previous mist_plan response. Pass JUST the subdomain (e.g. 'nutrition-tracker'), not the full URL or the option label. If the user kept the default suggestion, pass the suggested subdomain verbatim. If they typed a custom URL like 'myapp.mistflow.app', pass just 'myapp'. Pass this as a top-level parameter \u2014 do NOT nest it inside answers."),designConversationId:F.string().optional().describe("Returned by a previous mist_plan call with status 'design_clarify'. Pass this ONLY on the SUBMIT call after the user has picked a direction \u2014 i.e. ALWAYS together with `designDirection`. Do NOT include it on polling calls (status: 'rendering_directions' / 'design_clarify_pending') \u2014 for polling, send only `{ projectPath, conversationId }`. Including `designConversationId` without `designDirection` makes the backend respond 'you passed designConversationId but no designDirection' and wastes a round-trip. Decision rule: if the user has not yet typed/clicked a pick, omit this field; if they have, include both this field AND designDirection together in the same call."),designDirection:F.object({id:F.string().optional(),name:F.string().optional(),summary:F.string().optional(),heroHeadline:F.string().optional(),ctaText:F.string().optional(),bodySample:F.string().optional(),fontsHint:F.string().optional(),fonts:F.object({display:F.string(),body:F.string()}).partial().optional(),colorMood:F.string().optional(),colors:F.object({bg:F.string(),fg:F.string(),accent:F.string()}).partial().optional(),heroTreatment:F.string().optional(),shapeLang:F.string().optional(),texture:F.string().optional(),decorationHint:F.string().optional(),custom:F.string().optional()}).passthrough().optional().describe("The creative direction the user picked from a previous 'design_clarify' response. Pass ONLY the minimal shape: (a) `{ id: '<short-kebab-id-from-directionRefs>' }` when the user picked one of the proposed options \u2014 copy the id verbatim from `directionRefs[].id`, e.g. 'morning-paper' or 'quiet-utility'; or (b) `{ custom: '<the user\\'s exact words>' }` + `userConfirmedCustom: true` when the user typed their own description. Do NOT pass the full direction object (name, summary, fonts, colors, hero_headline, body_sample, etc.) \u2014 those fields are ignored and may exceed backend length limits, causing 422 rejections. ID values are short kebab-case strings (\u2264256 chars). Custom descriptions are user-typed prose (\u22644000 chars). Anything longer is wrong."),userConfirmedCustom:F.boolean().optional().describe("Set true ONLY when the user themselves typed a custom direction (via AskUserQuestion 'Other' or by explicitly describing the look in the chat). Required when submitting designDirection: { custom: ... } if the backend has real directions cached \u2014 the server rejects custom submissions otherwise to stop the AI from silently bypassing the picker. NEVER set this true unless the words in `custom` came directly from the user."),forceNew:F.boolean().optional().describe("Set to true ONLY after the user has explicitly chosen 'start fresh' from a previous mist_plan response with status 'resume_offer'. Suppresses the resume-offer check and lets a new session start even when this machine has unfinished work in flight. Do NOT set this preemptively on the first call \u2014 the resume-offer check needs to run so the user gets the chance to pick up where they left off.")});function _d(t){let e=[[/payment/i,"Payments"],[/database/i,"Database"],[/auth|sign.?up|login|access/i,"Access"],[/landing.?page/i,"Landing page"],[/who.*using|user|role/i,"Users"],[/design|theme|style/i,"Design"],[/deploy/i,"Deploy"],[/domain/i,"Domain"],[/notification/i,"Notify"],[/email/i,"Email"],[/mobile|responsive/i,"Mobile"],[/integrat/i,"Integration"],[/field|info|propert|detail|contain/i,"Item shape"],[/view|layout|board|grid|list|timeline/i,"View"],[/scope|how many|one.*or.*many|multi/i,"Scope"],[/share|read.?only|viewer|stakeholder/i,"Sharing"],[/workflow|status|state|move|stage|pipeline/i,"Workflow"],[/avoid|bloat|simple|complex|minimal/i,"Constraints"],[/time.*period|quarter|month|sprint/i,"Time periods"],[/swimlane|column|group|categor/i,"Structure"]];for(let[n,o]of e)if(n.test(t))return o;return t.replace(/[?.,!]/g,"").split(/\s+/).filter(n=>!["what","how","do","does","is","are","the","a","an","would","should","you","your","for","this","that","to","of","or","and","want","like","prefer"].includes(n.toLowerCase())).slice(0,2).join(" ").slice(0,12)||"Option"}function Td(t){let e=fe(_t(),".mistflow","plans",`${t}.json`);if(!mt(e))return null;try{return JSON.parse(Ln(e,"utf-8")).plan??null}catch{return null}}async function Cd(t){let e=Date.now(),r=5e4,n=2e3;for(;Date.now()-e<r;){try{let o=await tr(t.design_conversation_id);if(o.status==="ready")return{status:"design_clarify",design_conversation_id:t.design_conversation_id,directions:o.directions,plan:o.plan,methodology:o.methodology};if(o.status==="failed")return{status:"ready",plan:o.plan,methodology:o.methodology}}catch(o){let s=o instanceof Error?o.message:String(o);if(s.toLowerCase().includes("not found"))return{status:"ready",plan:t.plan,methodology:t.methodology};console.error(`[plan] directions poll failed: ${s}`)}await new Promise(o=>setTimeout(o,n))}return console.error("[plan] directions poll exhausted (50s) \u2014 preserving pending so host re-polls"),t}function Id(t){return typeof t=="string"&&t.trim()?t.trim():void 0}function Pd(t,e){for(let r of e){let n=Id(t[r]);if(n)return n}}function Rd(t){return Pd(t,["mockup_url","mockupUrl","preview_url","previewUrl","live_url","liveUrl","deploy_url","deployUrl","url"])}function $t(t,e){return`Next action: call ${t} with ${JSON.stringify(e)}. Do not omit sessionId.`}function wi(t){return t?.length?t.map((e,r)=>{let n=typeof e.text=="string"?e.text:typeof e.question=="string"?e.question:`Question ${r+1}`,o=Array.isArray(e.options)?e.options.map(s=>{if(s&&typeof s=="object"&&"label"in s){let i=s.label;return typeof i=="string"?i:null}return typeof s=="string"?s:null}).filter(s=>!!s):[];return o.length?`${r+1}. ${n} Options: ${o.join(", ")}`:`${r+1}. ${n}`}):["No questions were returned by the backend."]}function Ad(t){let e=Rd(t),r=t.reason?`
2677
+ Reason: ${t.reason}`:"";switch(t.state){case"COMPLETE":return`Session complete.${e?` Live URL: ${e}`:""}${r}`;case"CANCELLED":return`Session was cancelled.${r}`;case"FAILED":return`Session ended in FAILED.${e?` Last known URL: ${e}`:""}${r}`;case"ABANDONED":return`Session was abandoned.${r}`;case"EXPIRED":return`Session expired.${r}`;default:return`Session finished with state ${t.state}.${e?` URL: ${e}`:""}${r}`}}async function Ed(t,e){let{description:r,projectPath:n,sessionId:o,conversationId:s,answers:i,existingPlan:a,existingPlanId:l,templateToken:c,remixDescription:p,autonomous:d,language:m,brandMentioned:h,confirmToken:S,urlChoice:I,designConversationId:y,designDirection:v,userConfirmedCustom:E,forceNew:J}=t;if(o&&!s&&!y)try{let g=await Vt(o),b=[`Session ID: ${g.session_id}`,`Status: ${g.status??"active"}`,`Instruction: ${g.instruction}`];switch(g.reason&&b.push(`Reason: ${g.reason}`),g.instruction){case"wait":return u([...b,`Next action: ${g.reason??"The backend is still preparing the next step."} Tell the user briefly what's happening, then call mist_plan with { projectPath, sessionId: "${g.session_id}" } IMMEDIATELY \u2014 do NOT run bash sleep. The server holds each poll open up to ~10s and returns as soon as the next phase lands.`].join(`
2678
+ `));case"resume_conversation":{let T=g.conversation_id;return u([...b,`In-flight conversation: ${T??"(missing \u2014 backend bug)"}`,`Next action: call mist_plan with { projectPath, conversationId: "${T??""}" } to continue the plan flow. The conversation poll renders questions / design directions in the same way it would have on the original mist_plan response.`].join(`
2679
+ `))}case"continue":return u([...b,`Next action: ${g.reason??"Continue from where you left off \u2014 read mistflow.json to find the next pending step, then call mist_install / mist_implement / mist_build / mist_deploy as appropriate."}`].join(`
2680
+ `));case"call_mist_init":return u([...b,$t("mist_init",{sessionId:g.session_id,path:n})].join(`
2681
+ `));case"call_mist_deploy":return u([...b,$t("mist_deploy",{sessionId:g.session_id,projectPath:n})].join(`
2682
+ `));case"ask_user":return u([...b,"Next action: ask the user these questions, then submit the answers through the session flow.",...wi(g.questions)].join(`
2683
+ `));case"pick_design":return u([...b,"Next action: ask the user to pick one of these design directions, then submit the choice through the session flow.",...wi(g.directions??g.questions)].join(`
2684
+ `));case"review_mockup":return u([...b,$t("mist_mockup",{sessionId:g.session_id,projectPath:n})].join(`
2685
+ `));case"call_mist_install":return u([...b,$t("mist_install",{sessionId:g.session_id,projectPath:n})].join(`
2686
+ `));case"call_mist_implement":return u([...b,$t("mist_implement",{sessionId:g.session_id,projectPath:n})].join(`
2687
+ `));case"call_mist_build":return u([...b,$t("mist_build",{sessionId:g.session_id,projectPath:n})].join(`
2688
+ `));case"call_mist_qa":return u([...b,$t("mist_qa",{sessionId:g.session_id,projectPath:n})].join(`
2689
+ `));case"review_plan":return u([...b,`Next action: ${g.reason??"User asked to review PLAN.md before scaffolding. Open <projectPath>/PLAN.md, let the user read / edit, then call mist_session({ resume }) and re-run mist_init."}`].join(`
2690
+ `));case"done":return u([...b,Ad(g)].join(`
2691
+ `))}}catch(g){let b=g instanceof Error?g.message:String(g);return u(`Could not poll session '${o}': ${b}`,!0)}if(s&&!r&&!i&&!y&&!v&&!a&&!l&&!c)try{let g=await or(s);if(g.status==="clarify_pending"||g.status==="plan_pending"){let R=typeof g.phase_hint=="string"?g.phase_hint:null,N=typeof g.elapsed_seconds=="number"?g.elapsed_seconds:null,V=typeof g.progress=="number"?g.progress:null,Y=g.status==="plan_pending",oe=Y?"generating_plan":"generating_questions",ye=R?`${R}${N!==null?` (${N}s elapsed)`:""}`:Y?"Plan is being generated (build_plan + image enrichment, 30-60s typical)":"Discovery questions are being analyzed";return u(JSON.stringify({status:"running",conversationId:s,phase:oe,...R?{phaseHint:R}:{},...N!==null?{elapsedSeconds:N}:{},...V!==null?{progress:V}:{},nextAction:`${ye}. Tell the user briefly what's happening (e.g. "${R??(Y?"Generating the plan":"Analyzing your description")}"), then call mist_plan with { projectPath, conversationId: "${s}" } IMMEDIATELY \u2014 do NOT run bash sleep. The server holds each poll open up to ~10s and returns as soon as the next phase lands.`}))}if(g.status==="design_clarify"&&Array.isArray(g.directions)){let N=g.directions.map((M,Ye)=>({id:M.id??String(Ye),name:M.name??`Direction ${Ye+1}`,summary:M.summary,hero_headline:M.hero_headline,cta_text:M.cta_text,body_sample:M.body_sample,fonts:M.fonts,colors:M.colors,hero_treatment:M.hero_treatment,shape_lang:M.shape_lang,texture:M.texture,decoration_hint:M.decoration_hint})),V=g.plan?.name??"your app",Y=g.design_conversation_id,oe={},ye=!1;try{if(Y){let $=Date.now();for(;Date.now()-$<5e4;){let Re=await rr(Y),Pe=0;for(let Ae of N){let ve=Re[Ae.id];ve&&((ve.status==="done"||ve.status==="ready")&&typeof ve.html=="string"&&ve.html.length>0?(oe[Ae.id]=ve.html,Pe+=1):ve.status==="failed"&&(Pe+=1))}if(Pe===N.length){ye=!0;break}await new Promise(Ae=>setTimeout(Ae,5e3))}}}catch(M){console.error(`[mist_plan:design_clarify] render poll failed: ${M instanceof Error?M.message:String(M)}`)}if(!ye)return u(JSON.stringify({status:"running",phase:"rendering_directions",conversation_id:s,design_conversation_id:Y,renderedSoFar:Object.keys(oe).length,totalDirections:N.length,nextAction:`${Object.keys(oe).length}/${N.length} direction renders ready. Tell the user "still generating design previews" briefly, then call mist_plan with { projectPath, conversationId: "${s}" } again IMMEDIATELY \u2014 do NOT bash sleep. The render-poll holds the connection up to ~50s; each call returns as soon as state changes. Do NOT ask the user about design yet; the picker isn't ready.`}));let Q=N.filter(M=>oe[M.id]),X,_e,D=fe(n,".mistflow"),pe=fe(D,"design-directions.html"),be=fe(D,"picker-state.json"),de=!1;if(Y)try{mt(be)&&JSON.parse(Ln(be,"utf-8")).opened_for_design_cid===Y&&(de=!0)}catch{}try{if(Ut(D,{recursive:!0}),de)X=pe;else{let M=No(V,Q,oe);St(pe,M,"utf-8"),X=pe;let Ye=Eo(`file://${pe}`);Ye.opened||(_e=Ye.reason),Y&&St(be,JSON.stringify({opened_for_design_cid:Y,opened_at:new Date().toISOString()},null,2),"utf-8")}}catch(M){console.error(`[mist_plan:design_clarify] picker write/open failed: ${M instanceof Error?M.message:String(M)}`)}let le=Q.map(M=>({id:M.id,name:M.name})),Le=le.map(M=>M.name).filter(Boolean);return u(JSON.stringify({status:"design_clarify",previewPath:X,previewOpenError:_e,directionRefs:le,directionNames:Le,design_conversation_id:Y,conversation_id:s,nextAction:[`The visual picker has been opened in the user's browser at file://${X??"(path missing)"} \u2014 every direction is a real, rendered landing page. The user is looking at it now.`,_e?`(auto-open suppressed: ${_e} \u2014 tell the user to open file://${X} manually)`:"",`Now use your host's structured-question UI (AskUserQuestion in Claude Code) to ask the user which direction they pick. List EXACTLY these names as options: ${Le.map(M=>`"${M}"`).join(", ")}. Do NOT invent extra options. Add a "Type something" option for custom descriptions.`,`When the user picks a name, call mist_plan with { projectPath, conversationId: "${s}", designConversationId: "${Y}", designDirection: { id: "<map name \u2192 id from directionRefs>" } }. If the user types a custom description, pass designDirection: { custom: "<their exact words>" } and userConfirmedCustom: true.`].filter(Boolean).join(`
2692
+
2693
+ `)}))}let b=new Set(["clarify","ready","design_clarify_pending","design_clarify"]),T=typeof g.status=="string"?g.status:"";return b.has(T)?u(JSON.stringify(g)):u(JSON.stringify(g),!0)}catch(g){let b=g instanceof Error?g.message:String(g);return u(`Could not poll plan conversation '${s}': ${b}`,!0)}let H=r??"";if(!H.trim()&&!s&&!y&&!a&&!l&&!c)return u("mist_plan requires one of:\n \u2022 `description` \u2014 first call with a new app idea\n \u2022 `conversationId` alone \u2014 poll an in-flight discovery / plan-gen call\n \u2022 `conversationId` + `answers` \u2014 submit answers after `status: 'clarify'`\n \u2022 `designConversationId` + `designDirection` \u2014 submit a design pick after `status: 'design_clarify_pending'`\n \u2022 `existingPlanId` + a modification `description` \u2014 edit an existing plan\n \u2022 `templateToken` \u2014 fork a published template\n\nThe most common confusion: after `design_clarify_pending`, the next call needs `designConversationId` + `designDirection`, NOT `conversationId` alone.",!0);if(y&&!v&&!H.trim()&&!i)return u(`You passed designConversationId='${y}' but no designDirection. After the user picks one of the directions from the preview, pass it back as: mist_plan({ designConversationId: '${y}', designDirection: { id: '<their-pick-id>' } }). If the user asked for something custom, pass designDirection: { custom: '<their description>' }.`,!0);let ne=a;if(!ne&&l&&(ne=Td(l)??void 0,!ne))return u("Your previous plan is no longer available. Please describe your app again to generate a new plan.",!0);let ce=s;if(!Te())return Oe("plan a new app");let ee,q;if(!ce&&!ne&&!c&&!y){if(!ud(n))return u(`projectPath must be an absolute path \u2014 received '${n}'. Pass the full absolute path to the user's project directory (e.g. /Users/alice/projects/my-app).`,!0);let g=bd(n);if(g!=="mistflow"&&wd(n))return u(JSON.stringify({status:"unsafe_cwd",projectPath:n,instruction:[`The projectPath you passed (${n}) is a system-level directory (home root, Desktop, Documents, Downloads, or /tmp).`,"Scaffolding here would drop node_modules and a git repo directly at that location, which is messy and hard to clean up.","MANDATORY: Before calling mist_plan again:"," 1. Pick a subfolder name based on the app description (e.g. 'nutrition-tracker', 'habit-app').",` 2. Create the subfolder at ${n}/<subfolder-name>.`," 3. Call mist_plan again with the SAME description and projectPath set to the new subfolder.","Do NOT ask the user where to put the project \u2014 just pick a sensible subfolder name from their description."].join(`
2694
+ `)}),!0);if(g==="foreign"){let b=S?yd(S,n,H):!1,T=kd(n,H),R=xd(H,h);if(R||b){q=T;let N=yi(q);if(N)return u(`Mistflow could not create the scaffold directory at ${q}: ${N}`,!0);ee=`Note: Mistflow will scaffold the new app at \`${q}\`. The existing project at \`${n}\` is not modified.`}if(!b&&!R){let N=fd(n,H);if(e?.server){let V=await jn(e.server,[{question:"You're inside an existing project. Where should Mistflow scaffold the new app?",decisionKey:"scopeChoice",recommended:`Create at: ${T}`,why:"Mistflow creates a NEW app and never modifies the surrounding codebase. The default puts it in a subdirectory of the current path, named after your description. You can pick a custom path or cancel if you actually want to edit the existing project instead.",options:[{label:`Create at: ${T}`,description:"Default \u2014 uses your description as the folder name."},{label:"Choose a different path",description:"Type a custom absolute path in the textbox below."},{label:"Cancel \u2014 I wanted to edit the existing project",description:"Stop Mistflow. Handle the request by editing files in the current project instead."}]}],`## Existing project detected
2993
2695
 
2994
2696
  You're inside \`${n}\` which has a \`package.json\`. Mistflow will create a NEW app in a subdirectory \u2014 it won't modify the existing code.
2995
2697
 
2996
- Default path: \`${k}\``,"scope");if(K.outcome==="submitted"){let oe=K.answers?.[0]?.answer??"",ae=oe.toLowerCase(),be=oe.startsWith("/")?oe:null;if(be||ae.startsWith("create at:")||ae.startsWith("choose a different path")){let I=be||(ae.startsWith("choose a different path")?null:k);if(!I)return d("User picked 'Choose a different path' but didn't type one in the textbox. Ask them what path they want, then re-run mist_plan with projectPath set to that path.",!0);q=I;let Y=ii(q);if(Y)return d(`Mistflow could not create the scaffold directory at ${q}: ${Y}`,!0);Q=`Note: Mistflow will scaffold the new app at \`${q}\`. The existing project at \`${n}\` is not modified.`}else return ae.startsWith("cancel")?d("User chose to cancel \u2014 they wanted to edit the existing project, not create a new one. Do NOT call mist_plan again. Handle the request by editing files in the current project.",!0):d(`User submitted an unrecognized scope choice: ${oe}. Ask them whether to scaffold a new Mistflow app or edit the existing codebase before retrying.`,!0)}else return K.outcome==="declined"?d("User declined the scope confirmation. They wanted to edit the existing project, not create a new Mistflow app. Do NOT call mist_plan again. Handle the request by editing files in the current project.",!0):d(ai({projectPath:n,description:V,confirmToken:E,suggestedPath:k,previousTokenInvalid:!!U}))}else return d(ai({projectPath:n,description:V,confirmToken:E,suggestedPath:k,previousTokenInvalid:!!U}))}}}if(c)try{if(!(await mr(c)).plan)return d("This template has no plan to fork. Try a different template.",!0);let y=await hr(c),k=y.plan,N="";if(m&&y.has_source)try{let Y=await so(y.plan,m),ge=Y.plan??Y,ee=Y.diff,we=ge?.steps??[],Ee=new Set([...(ee?.added??[]).map(ve=>ve.number),...(ee?.modified??[]).map(ve=>ve.number)]),fe=we.map(ve=>{let Ht=ve.number;return Ee.has(Ht)?{...ve,status:"pending"}:{...ve,status:"completed",source:"forked"}});ge.steps=fe,k=ge;let P=fe.filter(ve=>ve.status==="pending").length;N=` Remixed: ${fe.filter(ve=>ve.status==="completed").length} steps unchanged, ${P} steps need re-implementation.`}catch(Y){console.error("[plan] Remix failed, using original plan:",Y),N=" (Remix failed \u2014 using original plan. You can modify it later.)"}let E=ri(),K=ke(xt(),".mistflow","plans");$t(K,{recursive:!0}),kt(ke(K,`${E}.json`),JSON.stringify({plan:k,projectId:y.id,sourceDeploymentId:y.source_deployment_id,forkToken:y.fork_token,requiredEnvVars:y.required_env_vars,dbProvider:y.db_provider}));let oe=k?.name??"forked-app",ae=y.has_source,be=ae?"Source code will be restored during init. Run init promptly \u2014 the download token expires in 1 hour.":"",I=y.deploy_url?` Instant deploy started \u2014 your app will be live at ${y.deploy_url} in under a minute.`:"";return d(JSON.stringify({planId:E,forkedFrom:y.forked_from,projectId:y.id,hasSource:ae,deployUrl:y.deploy_url,message:`Forked "${y.forked_from}" into your workspace.${N}${I} ${be} NEXT: Call mist_init, name='${oe}', and planId='${E}' to create the project now.`}))}catch(g){let y=g instanceof Error?g.message:"Failed to fork template";return d(y,!0)}if(te){let g;if(n){let I=ke(n,"PLAN.md");if(lt(I))try{g=jn(I,"utf-8")}catch(Y){console.error(`[mist_plan:modify] PLAN.md read failed (${I}): ${Y instanceof Error?Y.message:String(Y)}`)}}let y;try{y=await so(te,V,{planMd:g})}catch(I){let Y=I instanceof Error?I.message:"Failed to modify plan";return d(Y,!0)}let k=y.plan,N=y.diff,E=typeof y.plan_md=="string"?y.plan_md:null,K,oe;if(E&&n)try{let I=ke(n,"PLAN.md");kt(I,E,"utf-8"),K=I}catch(I){oe=I instanceof Error?I.message:String(I)}let ae=[];if(N?.added?.length){let I=N.added.map(Y=>Y.title);ae.push(`Added ${I.length} step(s): ${I.join(", ")}`)}if(N?.removed?.length){let I=N.removed.map(Y=>Y.title);ae.push(`Removed ${I.length} step(s): ${I.join(", ")}`)}if(N?.modified?.length){let I=N.modified.map(Y=>Y.title);ae.push(`Modified ${I.length} step(s): ${I.join(", ")}`)}let be=ae.length>0?ae.join(". "):"No changes detected.";return d(JSON.stringify({plan:k,diff:N,planMdPath:K,planMdError:oe,message:`Plan modified. ${be}. Update mistflow.json with the new plan${K?"; PLAN.md has been regenerated at "+K:""}, then continue with mist_implement.`}))}let O=C?.trim()||void 0,D=i;if(Array.isArray(i)){let g=i.findIndex(y=>y&&typeof y=="object"&&y.decisionKey==="urlChoice");if(g>=0){let y=i[g];!O&&y.answer&&(O=y.answer);let k=i.slice(0,g).concat(i.slice(g+1));D=k.length>0?k:void 0}}else if(i!=null){let g=i;if(!O&&go in g&&(O=g[go]),!O&&"urlChoice"in g&&(O=g.urlChoice),go in g||"urlChoice"in g){let{[go]:y,urlChoice:k,...N}=g;D=Object.keys(N).length>0?N:void 0}}if(O&&(O=O.replace(/^Keep\s+/i,"").replace(/\s*\(Recommended\)\s*$/i,"").replace(/\.mistflow\.app.*$/i,"").trim()||void 0),O){let g=O.toLowerCase().replace(/\s+/g,"-");/^[a-z0-9][a-z0-9-]{1,30}[a-z0-9]$/.test(g)?O=g:(console.error(`[mist_plan] Discarding urlChoice '${O}' \u2014 does not look like a subdomain. Backend will auto-generate.`),O=void 0)}let Z;if(w){Z={...w},delete Z.userConfirmedCustom,delete Z.user_confirmed_custom;let g={fontsHint:"fonts_hint",colorMood:"color_mood",heroHeadline:"hero_headline",ctaText:"cta_text",bodySample:"body_sample",heroTreatment:"hero_treatment",shapeLang:"shape_lang",decorationHint:"decoration_hint"};for(let[y,k]of Object.entries(g))w[y]!==void 0&&Z[k]===void 0&&(Z[k]=w[y])}let ne=Z!==void 0&&typeof Z.custom=="string"&&!Z.id&&!Z.name,v=A===!0||w!==void 0&&(w.userConfirmedCustom===!0||w.user_confirmed_custom===!0);if(ne&&x)try{let g=await eo(x);if(g.status==="pending")return d(`Refusing to submit a custom design direction: the backend's direction-generation BG task is still running (status: pending). The user has not yet had the chance to see the real directions. Stop asking the user about aesthetic / fonts / colors / mood right now \u2014 the backend will produce 3-4 concrete options shortly. Re-call mist_plan with { projectPath, conversationId, designConversationId: '${x}' } to keep polling. When directions are ready (status: 'design_clarify'), surface them to the user via AskUserQuestion. Custom is only legitimate AFTER the user has seen the real options and explicitly rejected them \u2014 never as a workaround for a slow generation run.`,!0);if(g.status==="ready"&&Array.isArray(g.directions)&&g.directions.length>0&&!v){let y=g.directions.map(k=>k.name).join(", ");return d(`Refusing to submit a custom design direction: the backend has ${g.directions.length} real directions ready (${y}). The picker is mandatory \u2014 you must surface those exact directions to the user via AskUserQuestion before submitting any pick. Re-call mist_plan with { conversationId, designConversationId: '${x}' } to get the picker payload (status: 'design_clarify'), render the directions HTML preview at .mistflow/design-directions.html for the user, then submit the user's choice. If the user opens the preview, sees the ${g.directions.length} directions, rejects all of them, and types their own description, ONLY THEN retry this call with userConfirmedCustom: true. Never set userConfirmedCustom: true without first showing the user the real directions.`,!0)}}catch(g){console.error(`[plan] custom-direction guard fetchDesignDirections failed (allowing submit): ${g instanceof Error?g.message:String(g)}`)}let F=i?"Generating plan with your answers (LLM call)":x?"Finalizing design direction":"Thinking through discovery questions",j=e?Ye(e.server,e.progressToken,()=>F):{stop:()=>{}};if(e&&(e.cleanup=()=>j.stop()),!r&&!s&&!x&&!a&&!l&&!c&&!U&&V.trim().length>0&&!J)try{let g=new Set(["COMPLETED","CANCELLED","FAILED","DONE"]),y=336*60*60*1e3,k=Date.now()-y,E=(await Sn(oi())).filter(K=>!g.has(K.state)).filter(K=>{let oe=Date.parse(K.updated_at);return Number.isFinite(oe)&&oe>=k}).sort((K,oe)=>Date.parse(oe.updated_at)-Date.parse(K.updated_at)).slice(0,5);if(E.length>0)return j.stop(),d(JSON.stringify({status:"resume_offer",requires_user_input:!0,candidates:E.map(K=>({sessionId:K.id,state:K.state,description:K.description,currentStep:K.current_step,awaitingInput:K.awaiting_input,updatedAt:K.updated_at})),instruction:"This machine has unfinished Mistflow build(s) in flight. Before starting a new project, ask the user whether they want to resume one of these or start fresh. Show the description, state, and updatedAt for each candidate so they can pick.",nextAction:"Step 1: Show the candidates to the user via your host's question UI (AskUserQuestion in Claude Code, request_user_input in Codex Plan mode, quick pick in Cursor). Step 2a (resume): if they pick a candidate, call mist_session({ action: 'resume', sessionId: '<picked>', projectPath }) and follow the next instruction it returns. Step 2b (start fresh): if they want a new build, call mist_plan again with the SAME description and projectPath, plus forceNew: true. The forceNew flag suppresses this check so the new session can be minted."}))}catch(g){console.error("resume-offer check failed (falling through to bootstrap):",g instanceof Error?g.message:g)}let he=r,T;try{le&&!D&&!x&&!te&&!l?T=await oo(le):T=await ro(V,{conversationId:le,answers:D,autonomous:u,language:p,designConversationId:x,designDirection:Z})}catch(g){j.stop();let y=g instanceof Error?g.message:"Failed to generate plan";return d(y,!0)}let $e=T.session_id;if(!he&&$e){he=$e;try{await xn($e,{machine_id:oi(),local_path:n})}catch(g){console.error("workspace bind failed (resume offers will miss this session):",g instanceof Error?g.message:g)}}if(T.status==="clarify_pending"){j.stop();let g=T;return d(JSON.stringify({status:"running",conversationId:g.conversation_id,sessionId:he,phase:"generating_questions",nextAction:`Discovery questions are generating. Call mist_plan with { projectPath, conversationId: "${g.conversation_id}" } IMMEDIATELY \u2014 do NOT run bash sleep between polls. The server holds each poll open up to ~10s and returns as soon as questions land. Do NOT re-send description or answers.${he?` Carry sessionId="${he}" forward on every subsequent mist_* call so backend state guards apply.`:""}`}))}if(T.status==="plan_pending"){j.stop();let g=T;return d(JSON.stringify({status:"running",conversationId:g.conversation_id,sessionId:he,phase:"generating_plan",nextAction:`Plan is being generated (build_plan + image enrichment, 30-60s typical). Call mist_plan with { projectPath, conversationId: "${g.conversation_id}" } IMMEDIATELY \u2014 do NOT run bash sleep between polls. The server holds each poll open up to ~10s and returns as soon as the plan lands. Do NOT re-send answers.`}))}if(T.status==="design_clarify_pending"&&(F="Generating creative design directions",T=await bd(T)),T.status==="design_clarify_pending")return j.stop(),d(JSON.stringify({status:"running",conversationId:s,designConversationId:T.design_conversation_id,sessionId:he,phase:"generating_design_directions",nextAction:`Creative design directions are still generating (45-60s typical, can stretch on slow runs). Call mist_plan with { projectPath, conversationId: "${s??""}" } IMMEDIATELY to keep polling \u2014 do NOT run bash sleep, do NOT submit a designDirection of your own, do NOT mark the plan as ready, and DO NOT ASK THE USER about aesthetic / fonts / colors / mood / vibe / style while you wait. The backend is generating 3-4 concrete direction options the user will pick from \u2014 asking the user to invent one defeats the entire feature, and the server will reject any { custom } submission while directions are still pending. Just poll. The picker is mandatory; keep polling until status becomes 'design_clarify' with the directions array, then surface those exact directions to the user via AskUserQuestion.`}));if(j.stop(),T.status==="clarify"){let g=T.reflection||"",y=T.suggestedName||"",k=T.suggestedFeatures??[],N=T.questions??[],E=N.some(ee=>Array.isArray(ee.options)&&typeof ee.options[0]=="object"&&ee.options[0]?.label),K={primaryActor:"Users",primaryAction:"Core action",surfaceType:"App type",audienceType:"Audience",multiRole:"Roles",publicLanding:"Landing page",realMoney:"Payments",scheduling:"Scheduling",authModel:"Access",dbProvider:"Database",integrations:"Integration",surfaceMcp:"AI access",surfaceBackgroundJobs:"Scheduled tasks",entityShape:"Item shape",coreView:"View",scope:"Scope",sharing:"Sharing",workflow:"Workflow",constraints:"Constraints",domain:"Product"},oe=N.map(ee=>{let we=ee.decisionKey&&K[ee.decisionKey]||fd(ee.question),Ee;return E&&Array.isArray(ee.options)?Ee=ee.options.map(fe=>({label:fe.label,description:fe.description??""})):Array.isArray(ee.options)?Ee=ee.options.map((fe,P)=>({label:P===0?`${fe} (Recommended)`:String(fe),description:ee.why??""})):Ee=[{label:"Yes (Recommended)",description:ee.why??""},{label:"No",description:""}],{question:ee.question,header:we,options:Ee,multiSelect:!1}}),be=T.decisions?.audienceType??null,I=k.length>0?ti(V,{primaryActor:null,primaryAction:null,surfaceType:null,audienceType:be,multiRole:null,publicLanding:null,realMoney:null,scheduling:null,authModel:null,dbProvider:null,integrations:null,surfaceMcp:null,surfaceBackgroundJobs:null},{suggestedName:y,suggestedFeatures:k,language:p}):null,Y=I?ni(I):"",ge=mo(y||"my-app").slice(0,32);try{let ee=await Zn(ge);!ee.available&&ee.suggestion&&(ge=ee.suggestion)}catch{}if(Y&&(Y+=`
2698
+ Default path: \`${T}\``,"scope");if(V.outcome==="submitted"){let Y=V.answers?.[0]?.answer??"",oe=Y.toLowerCase(),ye=Y.startsWith("/")?Y:null;if(ye||oe.startsWith("create at:")||oe.startsWith("choose a different path")){let Q=ye||(oe.startsWith("choose a different path")?null:T);if(!Q)return u("User picked 'Choose a different path' but didn't type one in the textbox. Ask them what path they want, then re-run mist_plan with projectPath set to that path.",!0);q=Q;let X=yi(q);if(X)return u(`Mistflow could not create the scaffold directory at ${q}: ${X}`,!0);ee=`Note: Mistflow will scaffold the new app at \`${q}\`. The existing project at \`${n}\` is not modified.`}else return oe.startsWith("cancel")?u("User chose to cancel \u2014 they wanted to edit the existing project, not create a new one. Do NOT call mist_plan again. Handle the request by editing files in the current project.",!0):u(`User submitted an unrecognized scope choice: ${Y}. Ask them whether to scaffold a new Mistflow app or edit the existing codebase before retrying.`,!0)}else return V.outcome==="declined"?u("User declined the scope confirmation. They wanted to edit the existing project, not create a new Mistflow app. Do NOT call mist_plan again. Handle the request by editing files in the current project.",!0):u(bi({projectPath:n,description:H,confirmToken:N,suggestedPath:T,previousTokenInvalid:!!S}))}else return u(bi({projectPath:n,description:H,confirmToken:N,suggestedPath:T,previousTokenInvalid:!!S}))}}}if(c)try{if(!(await go(c)).plan)return u("This template has no plan to fork. Try a different template.",!0);let b=await fo(c),T=b.plan,R="";if(p&&b.has_source)try{let X=await ir(b.plan,p),_e=X.plan??X,D=X.diff,pe=_e?.steps??[],be=new Set([...(D?.added??[]).map(M=>M.number),...(D?.modified??[]).map(M=>M.number)]),de=pe.map(M=>{let Ye=M.number;return be.has(Ye)?{...M,status:"pending"}:{...M,status:"completed",source:"forked"}});_e.steps=de,T=_e;let le=de.filter(M=>M.status==="pending").length;R=` Remixed: ${de.filter(M=>M.status==="completed").length} steps unchanged, ${le} steps need re-implementation.`}catch(X){console.error("[plan] Remix failed, using original plan:",X),R=" (Remix failed \u2014 using original plan. You can modify it later.)"}let N=gi(),V=fe(_t(),".mistflow","plans");Ut(V,{recursive:!0}),St(fe(V,`${N}.json`),JSON.stringify({plan:T,projectId:b.id,sourceDeploymentId:b.source_deployment_id,forkToken:b.fork_token,requiredEnvVars:b.required_env_vars,dbProvider:b.db_provider}));let Y=T?.name??"forked-app",oe=b.has_source,ye=oe?"Source code will be restored during init. Run init promptly \u2014 the download token expires in 1 hour.":"",Q=b.deploy_url?` Instant deploy started \u2014 your app will be live at ${b.deploy_url} in under a minute.`:"";return u(JSON.stringify({planId:N,forkedFrom:b.forked_from,projectId:b.id,hasSource:oe,deployUrl:b.deploy_url,message:`Forked "${b.forked_from}" into your workspace.${R}${Q} ${ye} NEXT: Call mist_init, name='${Y}', and planId='${N}' to create the project now.`}))}catch(g){let b=g instanceof Error?g.message:"Failed to fork template";return u(b,!0)}if(ne){let g;if(n){let Q=fe(n,"PLAN.md");if(mt(Q))try{g=Ln(Q,"utf-8")}catch(X){console.error(`[mist_plan:modify] PLAN.md read failed (${Q}): ${X instanceof Error?X.message:String(X)}`)}}let b;try{b=await ir(ne,H,{planMd:g})}catch(Q){let X=Q instanceof Error?Q.message:"Failed to modify plan";return u(X,!0)}let T=b.plan,R=b.diff,N=typeof b.plan_md=="string"?b.plan_md:null,V,Y;if(N&&n)try{let Q=fe(n,"PLAN.md");St(Q,N,"utf-8"),V=Q}catch(Q){Y=Q instanceof Error?Q.message:String(Q)}let oe=[];if(R?.added?.length){let Q=R.added.map(X=>X.title);oe.push(`Added ${Q.length} step(s): ${Q.join(", ")}`)}if(R?.removed?.length){let Q=R.removed.map(X=>X.title);oe.push(`Removed ${Q.length} step(s): ${Q.join(", ")}`)}if(R?.modified?.length){let Q=R.modified.map(X=>X.title);oe.push(`Modified ${Q.length} step(s): ${Q.join(", ")}`)}let ye=oe.length>0?oe.join(". "):"No changes detected.";return u(JSON.stringify({plan:T,diff:R,planMdPath:V,planMdError:Y,message:`Plan modified. ${ye}. Update mistflow.json with the new plan${V?"; PLAN.md has been regenerated at "+V:""}, then continue with mist_implement.`}))}let P=I?.trim()||void 0,re=i;if(Array.isArray(i)){let g=i.findIndex(b=>b&&typeof b=="object"&&b.decisionKey==="urlChoice");if(g>=0){let b=i[g];!P&&b.answer&&(P=b.answer);let T=i.slice(0,g).concat(i.slice(g+1));re=T.length>0?T:void 0}}else if(i!=null){let g=i;if(!P&&gr in g&&(P=g[gr]),!P&&"urlChoice"in g&&(P=g.urlChoice),gr in g||"urlChoice"in g){let{[gr]:b,urlChoice:T,...R}=g;re=Object.keys(R).length>0?R:void 0}}if(P&&(P=P.replace(/^Keep\s+/i,"").replace(/\s*\(Recommended\)\s*$/i,"").replace(/\.mistflow\.app.*$/i,"").trim()||void 0),P){let g=P.toLowerCase().replace(/\s+/g,"-");/^[a-z0-9][a-z0-9-]{1,30}[a-z0-9]$/.test(g)?P=g:(console.error(`[mist_plan] Discarding urlChoice '${P}' \u2014 does not look like a subdomain. Backend will auto-generate.`),P=void 0)}let z;if(v){z={...v},delete z.userConfirmedCustom,delete z.user_confirmed_custom;let g={fontsHint:"fonts_hint",colorMood:"color_mood",heroHeadline:"hero_headline",ctaText:"cta_text",bodySample:"body_sample",heroTreatment:"hero_treatment",shapeLang:"shape_lang",decorationHint:"decoration_hint"};for(let[b,T]of Object.entries(g))v[b]!==void 0&&z[T]===void 0&&(z[T]=v[b])}let ie=z!==void 0&&typeof z.custom=="string"&&!z.id&&!z.name,_=E===!0||v!==void 0&&(v.userConfirmedCustom===!0||v.user_confirmed_custom===!0);if(ie&&y)try{let g=await tr(y);if(g.status==="pending")return u(`Refusing to submit a custom design direction: the backend's direction-generation BG task is still running (status: pending). The user has not yet had the chance to see the real directions. Stop asking the user about aesthetic / fonts / colors / mood right now \u2014 the backend will produce 3-4 concrete options shortly. Re-call mist_plan with { projectPath, conversationId, designConversationId: '${y}' } to keep polling. When directions are ready (status: 'design_clarify'), surface them to the user via AskUserQuestion. Custom is only legitimate AFTER the user has seen the real options and explicitly rejected them \u2014 never as a workaround for a slow generation run.`,!0);if(g.status==="ready"&&Array.isArray(g.directions)&&g.directions.length>0&&!_){let b=g.directions.map(T=>T.name).join(", ");return u(`Refusing to submit a custom design direction: the backend has ${g.directions.length} real directions ready (${b}). The picker is mandatory \u2014 you must surface those exact directions to the user via AskUserQuestion before submitting any pick. Re-call mist_plan with { conversationId, designConversationId: '${y}' } to get the picker payload (status: 'design_clarify'), render the directions HTML preview at .mistflow/design-directions.html for the user, then submit the user's choice. If the user opens the preview, sees the ${g.directions.length} directions, rejects all of them, and types their own description, ONLY THEN retry this call with userConfirmedCustom: true. Never set userConfirmedCustom: true without first showing the user the real directions.`,!0)}}catch(g){console.error(`[plan] custom-direction guard fetchDesignDirections failed (allowing submit): ${g instanceof Error?g.message:String(g)}`)}let B=i?"Generating plan with your answers (LLM call)":y?"Finalizing design direction":"Thinking through discovery questions",L=e?tt(e.server,e.progressToken,()=>B):{stop:()=>{}};if(e&&(e.cleanup=()=>L.stop()),!o&&!s&&!y&&!a&&!l&&!c&&!S&&H.trim().length>0&&!J)try{let g=new Set(["COMPLETED","CANCELLED","FAILED","DONE"]),b=336*60*60*1e3,T=Date.now()-b,N=(await Tn(hi())).filter(V=>!g.has(V.state)).filter(V=>{let Y=Date.parse(V.updated_at);return Number.isFinite(Y)&&Y>=T}).sort((V,Y)=>Date.parse(Y.updated_at)-Date.parse(V.updated_at)).slice(0,5);if(N.length>0)return L.stop(),u(JSON.stringify({status:"resume_offer",requires_user_input:!0,candidates:N.map(V=>({sessionId:V.id,state:V.state,description:V.description,currentStep:V.current_step,awaitingInput:V.awaiting_input,updatedAt:V.updated_at})),instruction:"This machine has unfinished Mistflow build(s) in flight. Before starting a new project, ask the user whether they want to resume one of these or start fresh. Show the description, state, and updatedAt for each candidate so they can pick.",nextAction:"Step 1: Show the candidates to the user via your host's question UI (AskUserQuestion in Claude Code, request_user_input in Codex Plan mode, quick pick in Cursor). Step 2a (resume): if they pick a candidate, call mist_session({ action: 'resume', sessionId: '<picked>', projectPath }) and follow the next instruction it returns. Step 2b (start fresh): if they want a new build, call mist_plan again with the SAME description and projectPath, plus forceNew: true. The forceNew flag suppresses this check so the new session can be minted."}))}catch(g){console.error("resume-offer check failed (falling through to bootstrap):",g instanceof Error?g.message:g)}let te=o,x;try{ce&&!re&&!y&&!ne&&!l?x=await or(ce):x=await sr(H,{conversationId:ce,answers:re,autonomous:d,language:m,designConversationId:y,designDirection:z})}catch(g){L.stop();let b=g instanceof Error?g.message:"Failed to generate plan";return u(b,!0)}let Ue=x.session_id;if(!te&&Ue){te=Ue;try{await _n(Ue,{machine_id:hi(),local_path:n})}catch(g){console.error("workspace bind failed (resume offers will miss this session):",g instanceof Error?g.message:g)}}if(x.status==="clarify_pending"){L.stop();let g=x;return u(JSON.stringify({status:"running",conversationId:g.conversation_id,sessionId:te,phase:"generating_questions",nextAction:`Discovery questions are generating. Call mist_plan with { projectPath, conversationId: "${g.conversation_id}" } IMMEDIATELY \u2014 do NOT run bash sleep between polls. The server holds each poll open up to ~10s and returns as soon as questions land. Do NOT re-send description or answers.${te?` Carry sessionId="${te}" forward on every subsequent mist_* call so backend state guards apply.`:""}`}))}if(x.status==="plan_pending"){L.stop();let g=x;return u(JSON.stringify({status:"running",conversationId:g.conversation_id,sessionId:te,phase:"generating_plan",nextAction:`Plan is being generated (build_plan + image enrichment, 30-60s typical). Call mist_plan with { projectPath, conversationId: "${g.conversation_id}" } IMMEDIATELY \u2014 do NOT run bash sleep between polls. The server holds each poll open up to ~10s and returns as soon as the plan lands. Do NOT re-send answers.`}))}if(x.status==="design_clarify_pending"&&(B="Generating creative design directions",x=await Cd(x)),x.status==="design_clarify_pending")return L.stop(),u(JSON.stringify({status:"running",conversationId:s,designConversationId:x.design_conversation_id,sessionId:te,phase:"generating_design_directions",nextAction:`Creative design directions are still generating (45-60s typical, can stretch on slow runs). Call mist_plan with { projectPath, conversationId: "${s??""}" } IMMEDIATELY to keep polling \u2014 do NOT run bash sleep, do NOT submit a designDirection of your own, do NOT mark the plan as ready, and DO NOT ASK THE USER about aesthetic / fonts / colors / mood / vibe / style while you wait. The backend is generating 3-4 concrete direction options the user will pick from \u2014 asking the user to invent one defeats the entire feature, and the server will reject any { custom } submission while directions are still pending. Just poll. The picker is mandatory; keep polling until status becomes 'design_clarify' with the directions array, then surface those exact directions to the user via AskUserQuestion.`}));if(L.stop(),x.status==="clarify"){let g=x.reflection||"",b=x.suggestedName||"",T=x.suggestedFeatures??[],R=x.questions??[],N=R.some(D=>Array.isArray(D.options)&&typeof D.options[0]=="object"&&D.options[0]?.label),V={primaryActor:"Users",primaryAction:"Core action",surfaceType:"App type",audienceType:"Audience",multiRole:"Roles",publicLanding:"Landing page",realMoney:"Payments",scheduling:"Scheduling",authModel:"Access",dbProvider:"Database",integrations:"Integration",surfaceMcp:"AI access",surfaceBackgroundJobs:"Scheduled tasks",entityShape:"Item shape",coreView:"View",scope:"Scope",sharing:"Sharing",workflow:"Workflow",constraints:"Constraints",domain:"Product"},Y=R.map(D=>{let pe=D.decisionKey&&V[D.decisionKey]||_d(D.question),be;return N&&Array.isArray(D.options)?be=D.options.map(de=>({label:de.label,description:de.description??""})):Array.isArray(D.options)?be=D.options.map((de,le)=>({label:le===0?`${de} (Recommended)`:String(de),description:D.why??""})):be=[{label:"Yes (Recommended)",description:D.why??""},{label:"No",description:""}],{question:D.question,header:pe,options:be,multiSelect:!1}}),ye=x.decisions?.audienceType??null,Q=T.length>0?pi(H,{primaryActor:null,primaryAction:null,surfaceType:null,audienceType:ye,multiRole:null,publicLanding:null,realMoney:null,scheduling:null,authModel:null,dbProvider:null,integrations:null,surfaceMcp:null,surfaceBackgroundJobs:null},{suggestedName:b,suggestedFeatures:T,language:m}):null,X=Q?mi(Q):"",_e=mr(b||"my-app").slice(0,32);try{let D=await er(_e);!D.available&&D.suggestion&&(_e=D.suggestion)}catch{}if(X&&(X+=`
2997
2699
 
2998
- **Your app URL (you can change this before scaffolding):** https://${ge}.mistflow.app`),e?.server){let ee=[y?`## ${y}`:"## Pick a few details","",`${N.length} quick question${N.length===1?"":"s"} to pin down the build.`,"Pick from each dropdown \u2014 or pick `Other` and type your own answer below it."].join(`
2999
- `),we=await On(e.server,N,ee,"clarify");if(we.outcome==="submitted"){j.stop();let Ee={};for(let P of we.answers??[])P.decisionKey?Ee[P.decisionKey]=P.answer:Ee[P.question]=P.answer;let fe=we.urlChoice?.trim();fe&&(fe=fe.replace(/^Keep\s+/i,"").replace(/\s*\(Recommended\)\s*$/i,"").replace(/\.mistflow\.app.*$/i,"").trim()||void 0);try{let P=await ro(V,{conversationId:T.conversation_id,answers:Ee,autonomous:u,language:p});if(P.status==="plan_pending"){let ue=P;return d(JSON.stringify({status:"running",conversationId:ue.conversation_id,phase:"generating_plan",...fe?{urlChoice:fe}:{},nextAction:`User answered via the native form. Plan is generating now (30-60s typical). Call mist_plan with { projectPath, conversationId: "${ue.conversation_id}"${fe?`, urlChoice: "${fe}"`:""} } IMMEDIATELY \u2014 do NOT run bash sleep between polls. The server holds each poll up to ~10s and returns when the plan lands. Pass urlChoice on every poll so it threads through to the saved plan.`}))}T=P}catch(P){let ue=P instanceof Error?P.message:String(P);return d(`Submitting your answers failed: ${ue}. Please retry by calling mist_plan with the same conversationId and answers.`,!0)}}else{if(we.outcome==="declined")return j.stop(),d("User declined the planning flow via the form. They likely want a different approach \u2014 ask them what they'd prefer instead of re-running mist_plan.",!0);if(we.outcome==="cancelled")return j.stop(),d("User cancelled the planning form without picking. The conversation is still alive \u2014 re-run mist_plan with the same conversationId if they want to retry, or start a fresh plan if they want to change the description.",!0);we.outcome==="error"&&console.error(`[mist_plan] Elicitation failed (${we.errorMessage}) \u2014 falling back to prose flow.`)}}return d(JSON.stringify({status:"clarify",requires_user_input:!0,nextAction:"STOP \u2014 DO NOT submit answers yourself. Render every entry in `askUserQuestions` via your host's native question tool (AskUserQuestion in Claude Code, request_user_input in OpenAI Codex Plan mode, quick pick in Cursor) and WAIT for the user to actually answer. If your host has no native question tool, stop your turn, print the questions verbatim as a numbered list with all options visible, and wait for the user's next message. Calling mist_plan with answers in the same turn you received the questions is ALWAYS wrong, regardless of how obvious `recommended` looks.",conversation_id:T.conversation_id,questions:N,questionCount:N.length,suggestedFeatures:k,suggestedName:y,suggestedSubdomain:ge,reflection:g,briefText:Y,askUserQuestions:oe,planTimingHint:"After the user answers all questions, generating the actual plan takes about 60-90 seconds (backend LLM). Narrate this explicitly before the next mist_plan call.",instruction:["\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550","STOP. DO NOT CONTINUE UNTIL THE USER ACTUALLY ANSWERS THESE QUESTIONS.","\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550","","You MUST ask the user via your host's native structured-question","tool (not a text prompt, not a bash echo, not a chat message).","The user has to actually choose. You do NOT get to pick the","'recommended' answer on their behalf \u2014 even if it seems obvious,","even if they said 'just build it', even if you are inside /loop","or any autonomous mode. The recommended label is a hint for the","user, not a permission slip for you to decide. Every question is","a real product decision the user is paying to make.","","Per host (use whichever applies):"," \u2022 Claude Code \u2192 AskUserQuestion tool"," \u2022 Cursor \u2192 quick-pick UI"," \u2022 OpenAI Codex (Plan mode) \u2192 request_user_input tool"," \u2022 OpenAI Codex (Default mode) \u2192 request_user_input returns"," 'unavailable in <mode> mode'. When you see that error, STOP"," your turn, print the questions as a numbered chat message"," with all options visible, and wait for the user's next"," message. Do NOT resume by picking answers yourself."," \u2022 Any host without a native question tool \u2192 same as above:"," stop your turn, print the questions, wait.","","What NOT to do (these have all happened in production transcripts","and are unacceptable):"," \u2717 'I'll go with the recommended defaults \u2014 say the word if you"," want to change any before I continue.' (auto-accepting)"," \u2717 'Locking in the defaults with X as the only override.'"," (inferring answers from the original spec)"," \u2717 Printing the questions + options as markdown and inferring"," answers from silence."," \u2717 Calling mist_plan with answers you picked yourself."," \u2717 Skipping the question UI because /loop is active \u2014 in loop,"," stop the loop and wait for the user. The loop will resume.","","How to call the tool (when available): pass each object in the","`askUserQuestions` array below. Each has `question`, `header`,","`options[]` (each option has `label` + `description`), and","`multiSelect`. The tool returns the user's selected labels.","","Before calling mist_plan again with the answers, tell the user:"," 'Generating your plan now. This takes 30\u201360 seconds \u2014 I'm"," writing the data model, page layout, and build steps.'","Then call mist_plan with:",` conversationId: "${T.conversation_id}"`,` answers: { "<question text>": "<the user's selected label>", ... }`,' urlChoice: "<the URL subdomain the user picked>" \u2190 top-level param, NOT inside answers'," (description is no longer needed \u2014 the server has it from the first call)","","Follow-up clarify rounds are normal \u2014 if the user's answers reveal new","ambiguity, you'll get another `clarify` response. Relay those too,","same way, never inferring. Keep going until the response status is","'ready' or 'design_clarify_pending'.","","IMPORTANT: For the URL question, pass the answer as the top-level 'urlChoice' parameter (not inside answers).",`If the user keeps the default, set urlChoice: "${ge}".`,'If they type a custom URL, set urlChoice to just the subdomain part (e.g. "myapp" for "myapp.mistflow.app"). Do not include ".mistflow.app" or any "Keep X" label text \u2014 the tool strips those but passing just the subdomain is cleanest.',...g||Y?["","\u2500\u2500\u2500 BACKGROUND (not for you to summarize and proceed \u2014 it's what the user will see when you show them the questions) \u2500\u2500\u2500",...g?[g]:[],...Y?["",Y]:[]]:[],...Q?["",Q]:[]].join(`
3000
- `)}))}if(T.status==="design_clarify"){let g=T.directions??[],y=T.plan.name??"your app",k=T.design_conversation_id,N=g.map(z=>({id:z.id,name:z.name,summary:z.summary,hero_headline:z.hero_headline,cta_text:z.cta_text,body_sample:z.body_sample,fonts:z.fonts,colors:z.colors,hero_treatment:z.hero_treatment,shape_lang:z.shape_lang,texture:z.texture,decoration_hint:z.decoration_hint})),E={},K=!1;try{if(k){let _e=Date.now();for(;Date.now()-_e<5e4;){let mt=await no(k),Pe=0;for(let st of N){let me=mt[st.id];me&&((me.status==="done"||me.status==="ready")&&typeof me.html=="string"&&me.html.length>0?(E[st.id]=me.html,Pe+=1):me.status==="failed"&&(Pe+=1))}if(Pe===N.length){K=!0;break}await new Promise(st=>setTimeout(st,5e3))}}}catch(z){console.error(`[mist_plan:design_clarify] render poll failed: ${z instanceof Error?z.message:String(z)}`)}if(!K)return d(JSON.stringify({status:"running",phase:"rendering_directions",conversation_id:T.conversation_id??s,design_conversation_id:k,renderedSoFar:Object.keys(E).length,totalDirections:N.length,nextAction:`${Object.keys(E).length}/${N.length} direction renders ready. Tell the user "still generating design previews" briefly, then call mist_plan with { projectPath, conversationId: "${T.conversation_id??s??""}" } again IMMEDIATELY \u2014 do NOT bash sleep. Do NOT ask the user about design yet.`}));let oe=N.filter(z=>E[z.id]),ae,be,I=ke(n,".mistflow"),Y=ke(I,"design-directions.html"),ge=ke(I,"picker-state.json"),ee=()=>{try{if(lt(ge))return JSON.parse(jn(ge,"utf-8"))}catch{}return{}},we=z=>{try{$t(I,{recursive:!0}),kt(ge,JSON.stringify(z,null,2),"utf-8")}catch(Se){console.error(`[mist_plan:design_clarify] sidecar write failed: ${Se instanceof Error?Se.message:String(Se)}`)}},Ee=ee(),fe=k&&Ee.opened_for_design_cid===k;try{if($t(I,{recursive:!0}),fe)ae=Y;else{let z=Ar(y,oe,E);kt(Y,z,"utf-8"),ae=Y;let Se=Rr(`file://${Y}`);Se.opened||(be=Se.reason),k&&we({opened_for_design_cid:k,elicit_state:"pending",elicit_design_cid:k,opened_at:new Date().toISOString()})}}catch(z){console.error(`[mist_plan:design_clarify] picker write/open failed: ${z instanceof Error?z.message:String(z)}`)}let P=ee(),ue=oe.map(z=>({id:z.id,name:z.name})),ve=ue.map(z=>z.name).filter(Boolean);if(e?.server&&k&&P.elicit_design_cid===k&&P.elicit_state==="pending"){let z=ae?[`## ${y} \u2014 pick a creative direction`,"",`${oe.length} directions, each with its own fonts, colors, and mood.`,"",`Visual preview (auto-opened in your browser): file://${ae}`,"Each card shows what you'd be picking \u2014 fonts, colors, hero layout."].join(`
3001
- `):`## ${y} \u2014 pick a creative direction
2700
+ **Your app URL (you can change this before scaffolding):** https://${_e}.mistflow.app`),e?.server){let D=[b?`## ${b}`:"## Pick a few details","",`${R.length} quick question${R.length===1?"":"s"} to pin down the build.`,"Pick from each dropdown \u2014 or pick `Other` and type your own answer below it."].join(`
2701
+ `),pe=await jn(e.server,R,D,"clarify");if(pe.outcome==="submitted"){L.stop();let be={};for(let le of pe.answers??[])le.decisionKey?be[le.decisionKey]=le.answer:be[le.question]=le.answer;let de=pe.urlChoice?.trim();de&&(de=de.replace(/^Keep\s+/i,"").replace(/\s*\(Recommended\)\s*$/i,"").replace(/\.mistflow\.app.*$/i,"").trim()||void 0);try{let le=await sr(H,{conversationId:x.conversation_id,answers:be,autonomous:d,language:m});if(le.status==="plan_pending"){let Le=le;return u(JSON.stringify({status:"running",conversationId:Le.conversation_id,phase:"generating_plan",...de?{urlChoice:de}:{},nextAction:`User answered via the native form. Plan is generating now (30-60s typical). Call mist_plan with { projectPath, conversationId: "${Le.conversation_id}"${de?`, urlChoice: "${de}"`:""} } IMMEDIATELY \u2014 do NOT run bash sleep between polls. The server holds each poll up to ~10s and returns when the plan lands. Pass urlChoice on every poll so it threads through to the saved plan.`}))}x=le}catch(le){let Le=le instanceof Error?le.message:String(le);return u(`Submitting your answers failed: ${Le}. Please retry by calling mist_plan with the same conversationId and answers.`,!0)}}else{if(pe.outcome==="declined")return L.stop(),u("User declined the planning flow via the form. They likely want a different approach \u2014 ask them what they'd prefer instead of re-running mist_plan.",!0);if(pe.outcome==="cancelled")return L.stop(),u("User cancelled the planning form without picking. The conversation is still alive \u2014 re-run mist_plan with the same conversationId if they want to retry, or start a fresh plan if they want to change the description.",!0);pe.outcome==="error"&&console.error(`[mist_plan] Elicitation failed (${pe.errorMessage}) \u2014 falling back to prose flow.`)}}return u(JSON.stringify({status:"clarify",requires_user_input:!0,nextAction:"STOP \u2014 DO NOT submit answers yourself. Render every entry in `askUserQuestions` via your host's native question tool (AskUserQuestion in Claude Code, request_user_input in OpenAI Codex Plan mode, quick pick in Cursor) and WAIT for the user to actually answer. If your host has no native question tool, stop your turn, print the questions verbatim as a numbered list with all options visible, and wait for the user's next message. Calling mist_plan with answers in the same turn you received the questions is ALWAYS wrong, regardless of how obvious `recommended` looks.",conversation_id:x.conversation_id,questions:R,questionCount:R.length,suggestedFeatures:T,suggestedName:b,suggestedSubdomain:_e,reflection:g,briefText:X,askUserQuestions:Y,planTimingHint:"After the user answers all questions, generating the actual plan takes about 60-90 seconds (backend LLM). Narrate this explicitly before the next mist_plan call.",instruction:["\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550","STOP. DO NOT CONTINUE UNTIL THE USER ACTUALLY ANSWERS THESE QUESTIONS.","\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550","","You MUST ask the user via your host's native structured-question","tool (not a text prompt, not a bash echo, not a chat message).","The user has to actually choose. You do NOT get to pick the","'recommended' answer on their behalf \u2014 even if it seems obvious,","even if they said 'just build it', even if you are inside /loop","or any autonomous mode. The recommended label is a hint for the","user, not a permission slip for you to decide. Every question is","a real product decision the user is paying to make.","","Per host (use whichever applies):"," \u2022 Claude Code \u2192 AskUserQuestion tool"," \u2022 Cursor \u2192 quick-pick UI"," \u2022 OpenAI Codex (Plan mode) \u2192 request_user_input tool"," \u2022 OpenAI Codex (Default mode) \u2192 request_user_input returns"," 'unavailable in <mode> mode'. When you see that error, STOP"," your turn, print the questions as a numbered chat message"," with all options visible, and wait for the user's next"," message. Do NOT resume by picking answers yourself."," \u2022 Any host without a native question tool \u2192 same as above:"," stop your turn, print the questions, wait.","","What NOT to do (these have all happened in production transcripts","and are unacceptable):"," \u2717 'I'll go with the recommended defaults \u2014 say the word if you"," want to change any before I continue.' (auto-accepting)"," \u2717 'Locking in the defaults with X as the only override.'"," (inferring answers from the original spec)"," \u2717 Printing the questions + options as markdown and inferring"," answers from silence."," \u2717 Calling mist_plan with answers you picked yourself."," \u2717 Skipping the question UI because /loop is active \u2014 in loop,"," stop the loop and wait for the user. The loop will resume.","","How to call the tool (when available): pass each object in the","`askUserQuestions` array below. Each has `question`, `header`,","`options[]` (each option has `label` + `description`), and","`multiSelect`. The tool returns the user's selected labels.","","Before calling mist_plan again with the answers, tell the user:"," 'Generating your plan now. This takes 30\u201360 seconds \u2014 I'm"," writing the data model, page layout, and build steps.'","Then call mist_plan with:",` conversationId: "${x.conversation_id}"`,` answers: { "<question text>": "<the user's selected label>", ... }`,' urlChoice: "<the URL subdomain the user picked>" \u2190 top-level param, NOT inside answers'," (description is no longer needed \u2014 the server has it from the first call)","","Follow-up clarify rounds are normal \u2014 if the user's answers reveal new","ambiguity, you'll get another `clarify` response. Relay those too,","same way, never inferring. Keep going until the response status is","'ready' or 'design_clarify_pending'.","","IMPORTANT: For the URL question, pass the answer as the top-level 'urlChoice' parameter (not inside answers).",`If the user keeps the default, set urlChoice: "${_e}".`,'If they type a custom URL, set urlChoice to just the subdomain part (e.g. "myapp" for "myapp.mistflow.app"). Do not include ".mistflow.app" or any "Keep X" label text \u2014 the tool strips those but passing just the subdomain is cleanest.',...g||X?["","\u2500\u2500\u2500 BACKGROUND (not for you to summarize and proceed \u2014 it's what the user will see when you show them the questions) \u2500\u2500\u2500",...g?[g]:[],...X?["",X]:[]]:[],...ee?["",ee]:[]].join(`
2702
+ `)}))}if(x.status==="design_clarify"){let g=x.directions??[],b=x.plan.name??"your app",T=x.design_conversation_id,R=g.map($=>({id:$.id,name:$.name,summary:$.summary,hero_headline:$.hero_headline,cta_text:$.cta_text,body_sample:$.body_sample,fonts:$.fonts,colors:$.colors,hero_treatment:$.hero_treatment,shape_lang:$.shape_lang,texture:$.texture,decoration_hint:$.decoration_hint})),N={},V=!1;try{if(T){let Pe=Date.now();for(;Date.now()-Pe<5e4;){let Ae=await rr(T),ve=0;for(let ct of R){let he=Ae[ct.id];he&&((he.status==="done"||he.status==="ready")&&typeof he.html=="string"&&he.html.length>0?(N[ct.id]=he.html,ve+=1):he.status==="failed"&&(ve+=1))}if(ve===R.length){V=!0;break}await new Promise(ct=>setTimeout(ct,5e3))}}}catch($){console.error(`[mist_plan:design_clarify] render poll failed: ${$ instanceof Error?$.message:String($)}`)}if(!V)return u(JSON.stringify({status:"running",phase:"rendering_directions",conversation_id:x.conversation_id??s,design_conversation_id:T,renderedSoFar:Object.keys(N).length,totalDirections:R.length,nextAction:`${Object.keys(N).length}/${R.length} direction renders ready. Tell the user "still generating design previews" briefly, then call mist_plan with { projectPath, conversationId: "${x.conversation_id??s??""}" } again IMMEDIATELY \u2014 do NOT bash sleep. Do NOT ask the user about design yet.`}));let Y=R.filter($=>N[$.id]),oe,ye,Q=fe(n,".mistflow"),X=fe(Q,"design-directions.html"),_e=fe(Q,"picker-state.json"),D=()=>{try{if(mt(_e))return JSON.parse(Ln(_e,"utf-8"))}catch{}return{}},pe=$=>{try{Ut(Q,{recursive:!0}),St(_e,JSON.stringify($,null,2),"utf-8")}catch(Re){console.error(`[mist_plan:design_clarify] sidecar write failed: ${Re instanceof Error?Re.message:String(Re)}`)}},be=D(),de=T&&be.opened_for_design_cid===T;try{if(Ut(Q,{recursive:!0}),de)oe=X;else{let $=No(b,Y,N);St(X,$,"utf-8"),oe=X;let Re=Eo(`file://${X}`);Re.opened||(ye=Re.reason),T&&pe({opened_for_design_cid:T,elicit_state:"pending",elicit_design_cid:T,opened_at:new Date().toISOString()})}}catch($){console.error(`[mist_plan:design_clarify] picker write/open failed: ${$ instanceof Error?$.message:String($)}`)}let le=D(),Le=Y.map($=>({id:$.id,name:$.name})),M=Le.map($=>$.name).filter(Boolean);if(e?.server&&T&&le.elicit_design_cid===T&&le.elicit_state==="pending"){let $=oe?[`## ${b} \u2014 pick a creative direction`,"",`${Y.length} directions, each with its own fonts, colors, and mood.`,"",`Visual preview (auto-opened in your browser): file://${oe}`,"Each card shows what you'd be picking \u2014 fonts, colors, hero layout."].join(`
2703
+ `):`## ${b} \u2014 pick a creative direction
3002
2704
 
3003
- ${oe.length} directions, each with its own fonts, colors, and mood. Pick one or describe your own.`,Se=oe.map(_e=>({label:_e.name,description:_e.summary??""}));Se.push({label:"Describe your own direction",description:"Skip the proposed options and type a short description of how the app should feel."});try{let _e=await On(e.server,[{question:`${y} is planned. Now pick the creative direction \u2014 this shapes fonts, colors, and overall feel.`,decisionKey:"designDirection",options:Se,noOtherSentinel:!0,otherFieldTitle:"If 'Describe your own direction' was picked: type how the app should feel (fonts, colors, mood \u2014 your words)."}],z,"design");if(_e.outcome==="submitted"&&_e.answers?.[0]){let mt=_e.answers[0].answer.trim(),Pe=oe.find(me=>me.name===mt),st=Pe?{direction_id:Pe.id}:{custom:mt};(T.conversation_id??s)&&(st.conversation_id=T.conversation_id??s);try{let me=await to(k,st);we({...P,elicit_state:"completed",elicit_design_cid:k}),T={status:"ready",plan:me.plan??{},methodology:me.methodology??"",...me.designMd?{designMd:me.designMd}:{}}}catch(me){return console.error(`[mist_plan:design_clarify] submitDesignPick failed: ${me instanceof Error?me.message:String(me)}`),we({...P,elicit_state:"skipped"}),d(`Design submission failed: ${me instanceof Error?me.message:String(me)}. Ask the user to retry by re-running mist_plan with their pick.`,!0)}}else return we({...P,elicit_state:"skipped"}),d(JSON.stringify({status:"design_clarify",previewPath:ae,previewOpenError:be,directionRefs:ue,directionNames:ve,design_conversation_id:k,conversation_id:T.conversation_id??s,nextAction:[`The user closed the elicit picker without picking. The visual picker is still open at file://${ae??"(path missing)"}.`,`Use your host's structured-question UI (AskUserQuestion in Claude Code) to ask which direction they pick. List EXACTLY these names: ${ve.map(mt=>`"${mt}"`).join(", ")}.`,`When they pick, call mist_plan with { projectPath, conversationId: "${T.conversation_id??s??""}", designConversationId: "${k??""}", designDirection: { id: "<id from directionRefs>" } }.`].join(`
2705
+ ${Y.length} directions, each with its own fonts, colors, and mood. Pick one or describe your own.`,Re=Y.map(Pe=>({label:Pe.name,description:Pe.summary??""}));Re.push({label:"Describe your own direction",description:"Skip the proposed options and type a short description of how the app should feel."});try{let Pe=await jn(e.server,[{question:`${b} is planned. Now pick the creative direction \u2014 this shapes fonts, colors, and overall feel.`,decisionKey:"designDirection",options:Re,noOtherSentinel:!0,otherFieldTitle:"If 'Describe your own direction' was picked: type how the app should feel (fonts, colors, mood \u2014 your words)."}],$,"design");if(Pe.outcome==="submitted"&&Pe.answers?.[0]){let Ae=Pe.answers[0].answer.trim(),ve=Y.find(he=>he.name===Ae),ct=ve?{direction_id:ve.id}:{custom:Ae};(x.conversation_id??s)&&(ct.conversation_id=x.conversation_id??s);try{let he=await nr(T,ct);pe({...le,elicit_state:"completed",elicit_design_cid:T}),x={status:"ready",plan:he.plan??{},methodology:he.methodology??"",...he.designMd?{designMd:he.designMd}:{}}}catch(he){return console.error(`[mist_plan:design_clarify] submitDesignPick failed: ${he instanceof Error?he.message:String(he)}`),pe({...le,elicit_state:"skipped"}),u(`Design submission failed: ${he instanceof Error?he.message:String(he)}. Ask the user to retry by re-running mist_plan with their pick.`,!0)}}else return pe({...le,elicit_state:"skipped"}),u(JSON.stringify({status:"design_clarify",previewPath:oe,previewOpenError:ye,directionRefs:Le,directionNames:M,design_conversation_id:T,conversation_id:x.conversation_id??s,nextAction:[`The user closed the elicit picker without picking. The visual picker is still open at file://${oe??"(path missing)"}.`,`Use your host's structured-question UI (AskUserQuestion in Claude Code) to ask which direction they pick. List EXACTLY these names: ${M.map(Ae=>`"${Ae}"`).join(", ")}.`,`When they pick, call mist_plan with { projectPath, conversationId: "${x.conversation_id??s??""}", designConversationId: "${T??""}", designDirection: { id: "<id from directionRefs>" } }.`].join(`
3004
2706
 
3005
- `)}))}catch(_e){console.error(`[mist_plan:design_clarify] elicit failed: ${_e instanceof Error?_e.message:String(_e)}`),we({...P,elicit_state:"skipped"})}}if(T.status==="design_clarify")return d(JSON.stringify({status:"design_clarify",previewPath:ae,previewOpenError:be,directionRefs:ue,directionNames:ve,design_conversation_id:k,conversation_id:T.conversation_id??s,nextAction:[`The visual picker is open in the user's browser at file://${ae??"(path missing)"} \u2014 every direction is a real, rendered landing page.`,be?`(auto-open suppressed: ${be} \u2014 tell the user to open file://${ae} manually)`:"",`Use your host's structured-question UI (AskUserQuestion in Claude Code) to ask the user which direction they pick. List EXACTLY these names: ${ve.map(z=>`"${z}"`).join(", ")}. Add a "Type something" option for custom descriptions.`,`When the user picks a name, call mist_plan with { projectPath, conversationId: "${T.conversation_id??s??""}", designConversationId: "${k??""}", designDirection: { id: "<map name \u2192 id from directionRefs>" } }. If they describe their own, pass designDirection: { custom: "<their exact words>" } and userConfirmedCustom: true.`].filter(Boolean).join(`
2707
+ `)}))}catch(Pe){console.error(`[mist_plan:design_clarify] elicit failed: ${Pe instanceof Error?Pe.message:String(Pe)}`),pe({...le,elicit_state:"skipped"})}}if(x.status==="design_clarify")return u(JSON.stringify({status:"design_clarify",previewPath:oe,previewOpenError:ye,directionRefs:Le,directionNames:M,design_conversation_id:T,conversation_id:x.conversation_id??s,nextAction:[`The visual picker is open in the user's browser at file://${oe??"(path missing)"} \u2014 every direction is a real, rendered landing page.`,ye?`(auto-open suppressed: ${ye} \u2014 tell the user to open file://${oe} manually)`:"",`Use your host's structured-question UI (AskUserQuestion in Claude Code) to ask the user which direction they pick. List EXACTLY these names: ${M.map($=>`"${$}"`).join(", ")}. Add a "Type something" option for custom descriptions.`,`When the user picks a name, call mist_plan with { projectPath, conversationId: "${x.conversation_id??s??""}", designConversationId: "${T??""}", designDirection: { id: "<map name \u2192 id from directionRefs>" } }. If they describe their own, pass designDirection: { custom: "<their exact words>" } and userConfirmedCustom: true.`].filter(Boolean).join(`
3006
2708
 
3007
- `)}))}if(T.status!=="ready")return d(`Unexpected plan status after build: ${T.status}. Please retry by calling mist_plan with the same conversationId.`,!0);let G=T.plan,ie=G.name??"Untitled App",ye=T.methodology,xe=G.steps;if(!Array.isArray(xe)||xe.length===0)return d("Plan generation incomplete \u2014 the plan is missing implementation steps. Please call mist_plan again with the same description to retry.",!0);let de=G.suggestedSubdomain??mo(ie).slice(0,32)??"my-app";if(!O&&e?.server){try{let y=await Zn(de);!y.available&&y.suggestion&&(de=y.suggestion)}catch{}let g=await On(e.server,[{question:"Your app URL \u2014 last decision before scaffolding",decisionKey:"urlChoice",recommended:`Keep ${de}.mistflow.app`,why:"Subdomains lock in at scaffold time and are baked into auth callbacks, share links, and analytics. Pick the default to ship now, or type a custom subdomain in the textbox below.",options:[{label:`Keep ${de}.mistflow.app`,description:"Use the suggested subdomain"},{label:"Type a different subdomain",description:"Custom subdomain \u2014 fill the textbox below"}],noOtherSentinel:!0,otherFieldTitle:"Custom subdomain (e.g. 'sales-comm') \u2014 lowercase, alphanumeric + hyphens, 3-32 chars"}],`## ${ie} \u2014 pick the URL
2709
+ `)}))}if(x.status!=="ready")return u(`Unexpected plan status after build: ${x.status}. Please retry by calling mist_plan with the same conversationId.`,!0);let G=x.plan,xe=G.name??"Untitled App",we=x.methodology,se=G.steps;if(!Array.isArray(se)||se.length===0)return u("Plan generation incomplete \u2014 the plan is missing implementation steps. Please call mist_plan again with the same description to retry.",!0);let Se=G.suggestedSubdomain??mr(xe).slice(0,32)??"my-app";if(!P&&e?.server){try{let b=await er(Se);!b.available&&b.suggestion&&(Se=b.suggestion)}catch{}let g=await jn(e.server,[{question:"Your app URL \u2014 last decision before scaffolding",decisionKey:"urlChoice",recommended:`Keep ${Se}.mistflow.app`,why:"Subdomains lock in at scaffold time and are baked into auth callbacks, share links, and analytics. Pick the default to ship now, or type a custom subdomain in the textbox below.",options:[{label:`Keep ${Se}.mistflow.app`,description:"Use the suggested subdomain"},{label:"Type a different subdomain",description:"Custom subdomain \u2014 fill the textbox below"}],noOtherSentinel:!0,otherFieldTitle:"Custom subdomain (e.g. 'sales-comm') \u2014 lowercase, alphanumeric + hyphens, 3-32 chars"}],`## ${xe} \u2014 pick the URL
3008
2710
 
3009
- The plan is ready. One last decision before I scaffold: what's the app's URL?`,"url");if(g.outcome==="submitted"){let y=g.urlChoice?.trim()||g.answers?.[0]?.answer.trim()||"";y=y.replace(/^Keep\s+/i,"").replace(/\s*\(Recommended\)\s*$/i,"").replace(/\.mistflow\.app.*$/i,"").trim(),(/^type\b|\bdifferent\b/i.test(y)||!y)&&(y=de);let k=y.toLowerCase().replace(/\s+/g,"-");/^[a-z0-9][a-z0-9-]{1,30}[a-z0-9]$/.test(k)?O=k:(console.error(`[mist_plan] URL elicitation returned '${y}' \u2014 does not look like a subdomain. Using default '${de}'.`),O=de)}else g.outcome==="declined"||g.outcome,O=de}else O||(O=de);let je=G.publicPages;if(!je||Array.isArray(je)&&je.length===0){let g=G.publicLanding;if(g===!1)je=[];else{let y=G.pages,k=xe.some(E=>typeof E.name=="string"&&E.name.toLowerCase().includes("landing")||typeof E.title=="string"&&E.title.toLowerCase().includes("landing")),N=Array.isArray(y)&&y.some(E=>E.path==="/"||E.route==="/");k||N?je=["/","/pricing"]:g===!0?je=["/"]:je=["/"]}}else G.publicLanding===!1&&je.includes("/")&&(je=je.filter(g=>g!=="/"));let pt=G.primaryAction;if(!pt){let g=G.features;if(Array.isArray(g)&&g.length>0){let k=g.find(E=>typeof E.priority=="string"&&E.priority.toLowerCase()==="must-have")??g[0];pt={entity:k.name??k.title??"item",action:"create",fromPage:"/dashboard"}}}let Ze=G.nonNegotiables;(!Ze||Array.isArray(Ze)&&Ze.length===0)&&(Ze=["Landing page renders correctly at / with content (not a redirect)","Core user action works end-to-end (create entity, see it in list)"]);let rt=ri(),ze=ke(xt(),".mistflow","plans");$t(ze,{recursive:!0});try{let y=Date.now();for(let k of[ze,ke(xt(),".mistflow","mockup-state")])if(lt(k))for(let N of ci(k))try{let E=ke(k,N),K=di(E).mtimeMs;y-K>6048e5&&td(E)}catch{}}catch{}let Re=G,W={name:G.name,summary:G.summary,dataModel:G.dataModel,pages:G.pages,features:G.features,steps:xe.map(g=>({...g,name:g.name??g.title})),design:G.design,dbProvider:G.dbProvider??"neon",authModel:G.authModel,audienceType:G.audienceType??"b2c",roles:G.roles,defaultRole:G.defaultRole,publicPages:je,navStyle:G.navStyle,multiTenant:G.multiTenant,surfaceType:G.surfaceType,integrations:G.integrations,primaryAction:pt,nonNegotiables:Ze,requestedSubdomain:O,...p&&p.toLowerCase()!=="english"?{language:p}:{},...Re.pickedDirection?{pickedDirection:Re.pickedDirection}:{},...Re.imageryBrief?{imageryBrief:Re.imageryBrief}:{},...Re.designConversationId?{designConversationId:Re.designConversationId}:{}};kt(ke(ze,`${rt}.json`),JSON.stringify({plan:W,methodology:ye,...q?{scaffoldTargetPath:q}:{}}));let ce=xe.map(g=>`${g.number}. ${g.name??g.title}`),Ae,He="";if(!!!Re.pickedDirection){let y=(G.audienceType??"b2c")==="b2c";Ae={question:"Include a lifestyle photo in your landing page hero? Photos add warmth and human context; pure CSS stays cleaner.",header:"Hero",options:[{label:y?"Yes, add a photo (Recommended)":"Yes, add a photo",description:"Lifestyle photography background with your product preview overlaid \u2014 feels warm and human (like HabitFlow, Airbnb)."},{label:y?"No, CSS only":"No, CSS only (Recommended)",description:"Animated gradients + glassmorphism, no photo \u2014 cleaner and more technical (like Stripe, Linear, Vercel)."}],multiSelect:!1},He=" Also ask the user about the 'heroPhotoQuestion' provided. Once they pick, pass heroPhoto=true (photo) or heroPhoto=false (CSS only) to the mist_init call."}let tt="",De=[];for(let g of xe){let y=g.name??g.title,k=g.integrationId;if(k){let N=Yt(k);if(N){let E=Qt(N.id);De.push({step:y,presetId:N.id,presetName:N.name,envVars:E?.envVars??[]})}}}if(De.length>0){let g=De.flatMap(N=>N.envVars),y=[...new Set(g.map(N=>N.key))];tt=` This plan uses integrations (${De.map(N=>N.presetName).join(", ")}). Detailed blueprints will be auto-injected during each integration step.${y.length>0?` The user will need these API keys: ${y.join(", ")}.`:""}`}return d(JSON.stringify({planId:rt,name:G.name,summary:G.summary,stepCount:xe.length,steps:ce,design:G.design,...Ae?{heroPhotoQuestion:Ae}:{},...De.length>0?{integrations:De.map(g=>({step:g.step,preset:g.presetId,name:g.presetName,envVars:g.envVars}))}:{},message:`Plan generated for "${ie}" (${xe.length} steps).${tt}${He}`,timingContext:`Planning took ~90 seconds. Building will take roughly ${Math.max(15,xe.length*3)}\u2013${xe.length*5} minutes total across ${xe.length} steps (varies by complexity). Mention this to the user before starting the build so they know what to expect.`,mockupPrompt:`Before building, ask the user: "Would you like to preview a mockup of your app before we start building? You can iterate on the design, or skip straight to building." If the user wants a mockup, call mist_mockup({ planId: '${rt}' }). If the user says skip or "just build it", call mist_init({ planId: '${rt}'${q?"":", path: '<absolute path>'"} }) immediately.`,...q?{scaffoldTargetPath:q}:{},...Q?{warning:Q}:{}}))}var hi={name:"mist_plan",description:["ENTRY POINT for creating a NEW web app, website, internal tool, dashboard, landing page, marketplace, content site, or browser game. Mistflow scaffolds a complete Next.js project. It does NOT edit existing codebases.","","WHEN TO CALL THIS \u2014 route here automatically on natural 'build me X' intent. The user does NOT need to say 'mist' or 'mistflow'. Examples that MUST route here:","\u2022 'build me a habit tracker'","\u2022 'make a site for my bakery'","\u2022 'I want an app where users log workouts'","\u2022 'create a dashboard that shows sales'","\u2022 'build a Wordle clone'","\u2022 'build me a CRM using mist' / 'make a todo app with mistflow' (explicit brand invocation)","","PASSING THE DESCRIPTION: Pass the user's words EXACTLY. Do NOT expand, elaborate, add features, rewrite, or strip anything (including 'using mist' / 'with mistflow'). 'build me a habit tracker using mist' becomes description: 'build me a habit tracker using mist'. The description is preserved verbatim.","","BRAND MENTIONED FLAG: If the user's original request literally contained the word 'mist' or 'mistflow' as an explicit invocation (e.g. 'build me a CRM using mist', 'use mistflow to make a todo app'), set brandMentioned: true. If the user did NOT mention the brand by name, omit brandMentioned (do not set it). Only set brandMentioned when the user literally typed the brand name \u2014 never infer it. Do NOT set brandMentioned for the common English noun 'mist' used in other contexts (e.g. 'app about morning mist'). Clear new-app requests inside an existing non-Mistflow repo are scaffolded into a remembered child directory; Mistflow does not edit the surrounding codebase.","",`DESIGN REFERENCES (screenshots, Figma, brand names): if the user drags a screenshot, pastes a Figma URL, or names a brand to match ("make it feel like Linear"), use your native vision/knowledge to extract design tokens and submit them via designDirection: { custom: '<your description>', ...extracted_tokens } with userConfirmedCustom: true. The flag is required because the user supplied the reference; without it the server rejects the submission to stop AIs from inventing custom directions. Full flow at docs/design-references.md.`,"","SAFETY GATE \u2014 the handler walks up the directory tree to detect if you're inside an existing non-Mistflow codebase (package.json found anywhere up the tree, no mistflow.json). For clear new-app requests it automatically creates and remembers a child scaffold directory. For ambiguous requests, it returns status 'confirm_new_project' with a signed confirmToken and an askUserQuestion. On that response:","\u2022 MANDATORY: use the AskUserQuestion tool with the provided askUserQuestion to ask the user.","\u2022 If the user picks 'Scaffold a new Mistflow app in a subdirectory', call mist_plan again with the SAME description, SAME projectPath, and confirmToken set to the token from the response. Do not change projectPath to the suggested child path; Mistflow stores that target for mist_init.","\u2022 If the user picks 'Edit this existing codebase directly', DO NOT call mist_plan again. Fulfill their request by editing files directly.","\u2022 The confirmToken is bound to the projectPath and description. If either changes, you'll get a fresh token and must ask again.","\u2022 You do not need to pre-check the directory yourself. The handler handles detection.","","FOLLOW-UP FLOW (after the plan is being generated):","\u2022 status 'clarify' \u2192 use AskUserQuestion with the provided askUserQuestions, then call mist_plan again with conversationId + answers + the same description.","\u2022 You may receive MULTIPLE 'clarify' rounds \u2014 if the user's answers reveal new ambiguity, the planner will ask follow-up questions. Keep relaying questions until you get status 'ready'. This is normal and produces better plans.","\u2022 status 'ready' \u2192 IMMEDIATELY call mist_init with the returned planId. If the response includes scaffoldTargetPath you do not need to pass path; mist_init loads it from the cached plan. Do not ask permission.","\u2022 NEVER skip the clarifying questions. The discovery process ensures the right thing gets built.","","SESSIONID FLOW (dumb-ai-proof, when the response includes sessionId): keep the sessionId and pass it on every subsequent mist_* call. Calling mist_plan with just { sessionId, projectPath } polls GET /api/sessions/{id}/next and returns the next instruction (wait | ask_user | pick_design | review_mockup | call_mist_init | call_mist_install | call_mist_implement | call_mist_build | call_mist_deploy | call_mist_qa | done). Do exactly what the instruction says \u2014 don't branch on raw state. The backend owns routing; the host relays.","","EXISTING MISTFLOW PROJECTS (mistflow.json present anywhere up the tree): call this for changes that need a new data model, third-party integration, or multi-step structural change (pass existingPlan or existingPlanId). For simpler features (new pages, UI additions), do NOT call mist_plan, but DO ask the user product questions before building (what fields, what layout, what constraints). Get the spec right, build once. For cosmetic changes and bug fixes, skip questions and edit directly. Use mist_project action='get' for context.","","OTHER MODES: Pass templateToken to fork from a mistflow.ai/t/... shared template. The aesthetic (fonts, colors, motion) is decided by the design-direction picker step \u2014 the host AI doesn't pick a catalog ID."].join(`
3010
- `),inputSchema:gd,handler:Sd};Er();import{z as on}from"zod";import{existsSync as Be,mkdirSync as rn,rmSync as wu,writeFileSync as dt,readFileSync as sn,readdirSync as Fi,copyFileSync as vu}from"fs";import{join as pe,resolve as Bi,dirname as Ln,isAbsolute as ku}from"path";import{homedir as xu}from"os";import{spawn as Fy}from"child_process";import{randomBytes as Su}from"crypto";import{simpleGit as Cu}from"simple-git";Ie();import{existsSync as Ut,mkdirSync as fi,readFileSync as en,writeFileSync as nt}from"fs";import{dirname as yi,join as St}from"path";async function bi(t,e){let o=await lr("ai-chat"),n=[],r=new Set(["lib/ai/tools/manifest.ts","components/chat/ChatBox.tsx","components/chat/ChatMessage.tsx","components/chat/ChatHistory.tsx","components/chat/AttachmentChip.tsx","app/chat/[chatId]/page.tsx","app/chat/layout.tsx","app/chat/page.tsx"]);for(let c of o.files){if(c.path.endsWith(".fragment.json")||c.path==="README.md")continue;let m=St(t,c.path);r.has(c.path)&&Ut(m)||(fi(yi(m),{recursive:!0}),nt(m,c.content,"utf8"),n.push(c.path))}let s=o.files.find(c=>c.path==="README.md");if(s){let c=St(t,"docs","ai-chat-addon.md");fi(yi(c),{recursive:!0}),nt(c,s.content,"utf8"),n.push("docs/ai-chat-addon.md")}let i=_d(t,o.files,e.integrations),a=Cd(t,o.files);Td(t);let l=e.integrations.includes("run-python");return l&&Id(t),Pd(t,e.mcpEnabled===!0),Rd(t,e.mcpEnabled===!0),{installed:n,depsAdded:i,cronAdded:a,runPythonWired:l}}function _d(t,e,o){let n=e.find(l=>l.path==="package.json.fragment.json");if(!n)return[];let r=JSON.parse(n.content),s=St(t,"package.json");if(!Ut(s))return[];let i=JSON.parse(en(s,"utf8"));i.dependencies=i.dependencies??{};let a=[];for(let[l,c]of Object.entries(r.dependencies??{}))i.dependencies[l]!==c&&(i.dependencies[l]=c,a.push(`${l}@${c}`));for(let l of o){let c=r._conditionalDeps?.[l];if(c)for(let[m,u]of Object.entries(c))i.dependencies[m]!==u&&(i.dependencies[m]=u,a.push(`${m}@${u}`))}return nt(s,JSON.stringify(i,null,2)+`
3011
- `,"utf8"),a}function Cd(t,e){let o=e.find(c=>c.path==="mistflow.json.fragment.json");if(!o)return[];let n=JSON.parse(o.content);if(!n.cron||n.cron.length===0)return[];let r=St(t,"mistflow.json"),s=Ut(r)?JSON.parse(en(r,"utf8")):{},i=Array.isArray(s.cron)?s.cron:[],a=new Set(i.map(c=>c.name)),l=[];for(let c of n.cron)a.has(c.name)||(i.push(c),l.push(c.name));return s.cron=i,nt(r,JSON.stringify(s,null,2)+`
3012
- `,"utf8"),l}function Td(t){let e=St(t,"db","schema","index.ts");if(!Ut(e)){nt(e,["export * from './auth';","export * from './ai-chat';",""].join(`
3013
- `),"utf8");return}let o=en(e,"utf8");if(o.includes("./ai-chat"))return;let n=o.replace(/\n+$/,"");nt(e,n+`
2711
+ The plan is ready. One last decision before I scaffold: what's the app's URL?`,"url");if(g.outcome==="submitted"){let b=g.urlChoice?.trim()||g.answers?.[0]?.answer.trim()||"";b=b.replace(/^Keep\s+/i,"").replace(/\s*\(Recommended\)\s*$/i,"").replace(/\.mistflow\.app.*$/i,"").trim(),(/^type\b|\bdifferent\b/i.test(b)||!b)&&(b=Se);let T=b.toLowerCase().replace(/\s+/g,"-");/^[a-z0-9][a-z0-9-]{1,30}[a-z0-9]$/.test(T)?P=T:(console.error(`[mist_plan] URL elicitation returned '${b}' \u2014 does not look like a subdomain. Using default '${Se}'.`),P=Se)}else g.outcome==="declined"||g.outcome,P=Se}else P||(P=Se);let He=G.publicPages;if(!He||Array.isArray(He)&&He.length===0){let g=G.publicLanding;if(g===!1)He=[];else{let b=G.pages,T=se.some(N=>typeof N.name=="string"&&N.name.toLowerCase().includes("landing")||typeof N.title=="string"&&N.title.toLowerCase().includes("landing")),R=Array.isArray(b)&&b.some(N=>N.path==="/"||N.route==="/");T||R?He=["/","/pricing"]:g===!0?He=["/"]:He=["/"]}}else G.publicLanding===!1&&He.includes("/")&&(He=He.filter(g=>g!=="/"));let je=G.primaryAction;if(!je){let g=G.features;if(Array.isArray(g)&&g.length>0){let T=g.find(N=>typeof N.priority=="string"&&N.priority.toLowerCase()==="must-have")??g[0];je={entity:T.name??T.title??"item",action:"create",fromPage:"/dashboard"}}}let ot=G.nonNegotiables;(!ot||Array.isArray(ot)&&ot.length===0)&&(ot=["Landing page renders correctly at / with content (not a redirect)","Core user action works end-to-end (create entity, see it in list)"]);let Ve=gi(),ft=fe(_t(),".mistflow","plans");Ut(ft,{recursive:!0});try{let b=Date.now();for(let T of[ft,fe(_t(),".mistflow","mockup-state")])if(mt(T))for(let R of vi(T))try{let N=fe(T,R),V=ki(N).mtimeMs;b-V>6048e5&&cd(N)}catch{}}catch{}let Ne=G,O={name:G.name,summary:G.summary,dataModel:G.dataModel,pages:G.pages,features:G.features,steps:se.map(g=>({...g,name:g.name??g.title})),design:G.design,dbProvider:G.dbProvider??"neon",authModel:G.authModel,audienceType:G.audienceType??"b2c",roles:G.roles,defaultRole:G.defaultRole,publicPages:He,navStyle:G.navStyle,multiTenant:G.multiTenant,surfaceType:G.surfaceType,integrations:G.integrations,primaryAction:je,nonNegotiables:ot,requestedSubdomain:P,...m&&m.toLowerCase()!=="english"?{language:m}:{},...Ne.pickedDirection?{pickedDirection:Ne.pickedDirection}:{},...Ne.imageryBrief?{imageryBrief:Ne.imageryBrief}:{},...Ne.designConversationId?{designConversationId:Ne.designConversationId}:{}};St(fe(ft,`${Ve}.json`),JSON.stringify({plan:O,methodology:we,...q?{scaffoldTargetPath:q}:{}}));let ue=se.map(g=>`${g.number}. ${g.name??g.title}`),Ce,ze="";if(!!!Ne.pickedDirection){let b=(G.audienceType??"b2c")==="b2c";Ce={question:"Include a lifestyle photo in your landing page hero? Photos add warmth and human context; pure CSS stays cleaner.",header:"Hero",options:[{label:b?"Yes, add a photo (Recommended)":"Yes, add a photo",description:"Lifestyle photography background with your product preview overlaid \u2014 feels warm and human (like HabitFlow, Airbnb)."},{label:b?"No, CSS only":"No, CSS only (Recommended)",description:"Animated gradients + glassmorphism, no photo \u2014 cleaner and more technical (like Stripe, Linear, Vercel)."}],multiSelect:!1},ze=" Also ask the user about the 'heroPhotoQuestion' provided. Once they pick, pass heroPhoto=true (photo) or heroPhoto=false (CSS only) to the mist_init call."}let We="",De=[];for(let g of se){let b=g.name??g.title,T=g.integrationId;if(T){let R=Qt(T);if(R){let N=Xt(R.id);De.push({step:b,presetId:R.id,presetName:R.name,envVars:N?.envVars??[]})}}}if(De.length>0){let g=De.flatMap(R=>R.envVars),b=[...new Set(g.map(R=>R.key))];We=` This plan uses integrations (${De.map(R=>R.presetName).join(", ")}). Detailed blueprints will be auto-injected during each integration step.${b.length>0?` The user will need these API keys: ${b.join(", ")}.`:""}`}return u(JSON.stringify({planId:Ve,name:G.name,summary:G.summary,stepCount:se.length,steps:ue,design:G.design,...Ce?{heroPhotoQuestion:Ce}:{},...De.length>0?{integrations:De.map(g=>({step:g.step,preset:g.presetId,name:g.presetName,envVars:g.envVars}))}:{},message:`Plan generated for "${xe}" (${se.length} steps).${We}${ze}`,timingContext:`Planning took ~90 seconds. Building will take roughly ${Math.max(15,se.length*3)}\u2013${se.length*5} minutes total across ${se.length} steps (varies by complexity). Mention this to the user before starting the build so they know what to expect.`,mockupPrompt:`Before building, ask the user: "Would you like to preview a mockup of your app before we start building? You can iterate on the design, or skip straight to building." If the user wants a mockup, call mist_mockup({ planId: '${Ve}' }). If the user says skip or "just build it", call mist_init({ planId: '${Ve}'${q?"":", path: '<absolute path>'"} }) immediately.`,...q?{scaffoldTargetPath:q}:{},...ee?{warning:ee}:{}}))}var Ti={name:"mist_plan",description:["ENTRY POINT for creating a NEW web app, website, internal tool, dashboard, landing page, marketplace, content site, or browser game. Mistflow scaffolds a complete Next.js project. It does NOT edit existing codebases.","","WHEN TO CALL THIS \u2014 route here automatically on natural 'build me X' intent. The user does NOT need to say 'mist' or 'mistflow'. Examples that MUST route here:","\u2022 'build me a habit tracker'","\u2022 'make a site for my bakery'","\u2022 'I want an app where users log workouts'","\u2022 'create a dashboard that shows sales'","\u2022 'build a Wordle clone'","\u2022 'build me a CRM using mist' / 'make a todo app with mistflow' (explicit brand invocation)","","PASSING THE DESCRIPTION: Pass the user's words EXACTLY. Do NOT expand, elaborate, add features, rewrite, or strip anything (including 'using mist' / 'with mistflow'). 'build me a habit tracker using mist' becomes description: 'build me a habit tracker using mist'. The description is preserved verbatim.","","BRAND MENTIONED FLAG: If the user's original request literally contained the word 'mist' or 'mistflow' as an explicit invocation (e.g. 'build me a CRM using mist', 'use mistflow to make a todo app'), set brandMentioned: true. If the user did NOT mention the brand by name, omit brandMentioned (do not set it). Only set brandMentioned when the user literally typed the brand name \u2014 never infer it. Do NOT set brandMentioned for the common English noun 'mist' used in other contexts (e.g. 'app about morning mist'). Clear new-app requests inside an existing non-Mistflow repo are scaffolded into a remembered child directory; Mistflow does not edit the surrounding codebase.","",`DESIGN REFERENCES (screenshots, Figma, brand names): if the user drags a screenshot, pastes a Figma URL, or names a brand to match ("make it feel like Linear"), use your native vision/knowledge to extract design tokens and submit them via designDirection: { custom: '<your description>', ...extracted_tokens } with userConfirmedCustom: true. The flag is required because the user supplied the reference; without it the server rejects the submission to stop AIs from inventing custom directions. Full flow at docs/design-references.md.`,"","SAFETY GATE \u2014 the handler walks up the directory tree to detect if you're inside an existing non-Mistflow codebase (package.json found anywhere up the tree, no mistflow.json). For clear new-app requests it automatically creates and remembers a child scaffold directory. For ambiguous requests, it returns status 'confirm_new_project' with a signed confirmToken and an askUserQuestion. On that response:","\u2022 MANDATORY: use the AskUserQuestion tool with the provided askUserQuestion to ask the user.","\u2022 If the user picks 'Scaffold a new Mistflow app in a subdirectory', call mist_plan again with the SAME description, SAME projectPath, and confirmToken set to the token from the response. Do not change projectPath to the suggested child path; Mistflow stores that target for mist_init.","\u2022 If the user picks 'Edit this existing codebase directly', DO NOT call mist_plan again. Fulfill their request by editing files directly.","\u2022 The confirmToken is bound to the projectPath and description. If either changes, you'll get a fresh token and must ask again.","\u2022 You do not need to pre-check the directory yourself. The handler handles detection.","","FOLLOW-UP FLOW (after the plan is being generated):","\u2022 status 'clarify' \u2192 use AskUserQuestion with the provided askUserQuestions, then call mist_plan again with conversationId + answers + the same description.","\u2022 You may receive MULTIPLE 'clarify' rounds \u2014 if the user's answers reveal new ambiguity, the planner will ask follow-up questions. Keep relaying questions until you get status 'ready'. This is normal and produces better plans.","\u2022 status 'ready' \u2192 IMMEDIATELY call mist_init with the returned planId. If the response includes scaffoldTargetPath you do not need to pass path; mist_init loads it from the cached plan. Do not ask permission.","\u2022 NEVER skip the clarifying questions. The discovery process ensures the right thing gets built.","","SESSIONID FLOW (dumb-ai-proof, when the response includes sessionId): keep the sessionId and pass it on every subsequent mist_* call. Calling mist_plan with just { sessionId, projectPath } polls GET /api/sessions/{id}/next and returns the next instruction (wait | ask_user | pick_design | review_mockup | call_mist_init | call_mist_install | call_mist_implement | call_mist_build | call_mist_deploy | call_mist_qa | done). Do exactly what the instruction says \u2014 don't branch on raw state. The backend owns routing; the host relays.","","EXISTING MISTFLOW PROJECTS (mistflow.json present anywhere up the tree): call this for changes that need a new data model, third-party integration, or multi-step structural change (pass existingPlan or existingPlanId). For simpler features (new pages, UI additions), do NOT call mist_plan, but DO ask the user product questions before building (what fields, what layout, what constraints). Get the spec right, build once. For cosmetic changes and bug fixes, skip questions and edit directly. Use mist_project action='get' for context.","","OTHER MODES: Pass templateToken to fork from a mistflow.ai/t/... shared template. The aesthetic (fonts, colors, motion) is decided by the design-direction picker step \u2014 the host AI doesn't pick a catalog ID."].join(`
2712
+ `),inputSchema:Sd,handler:Ed};Oo();import{z as sn}from"zod";import{existsSync as Be,mkdirSync as an,rmSync as Iu,writeFileSync as gt,readFileSync as ln,readdirSync as Zi,copyFileSync as Pu}from"fs";import{join as me,resolve as ea,dirname as $n,isAbsolute as Ru}from"path";import{homedir as Au}from"os";import{spawn as cb}from"child_process";import{randomBytes as Eu}from"crypto";import{simpleGit as Ou}from"simple-git";ke();import{existsSync as Ft,mkdirSync as Ii,readFileSync as tn,writeFileSync as st}from"fs";import{dirname as Pi,join as Tt}from"path";async function Ri(t,e){let r=await co("ai-chat"),n=[],o=new Set(["lib/ai/tools/manifest.ts","components/chat/ChatBox.tsx","components/chat/ChatMessage.tsx","components/chat/ChatHistory.tsx","components/chat/AttachmentChip.tsx","app/chat/[chatId]/page.tsx","app/chat/layout.tsx","app/chat/page.tsx"]);for(let c of r.files){if(c.path.endsWith(".fragment.json")||c.path==="README.md")continue;let p=Tt(t,c.path);o.has(c.path)&&Ft(p)||(Ii(Pi(p),{recursive:!0}),st(p,c.content,"utf8"),n.push(c.path))}let s=r.files.find(c=>c.path==="README.md");if(s){let c=Tt(t,"docs","ai-chat-addon.md");Ii(Pi(c),{recursive:!0}),st(c,s.content,"utf8"),n.push("docs/ai-chat-addon.md")}let i=Nd(t,r.files,e.integrations),a=Od(t,r.files);Md(t);let l=e.integrations.includes("run-python");return l&&jd(t),Dd(t,e.mcpEnabled===!0),Ld(t,e.mcpEnabled===!0),{installed:n,depsAdded:i,cronAdded:a,runPythonWired:l}}function Nd(t,e,r){let n=e.find(l=>l.path==="package.json.fragment.json");if(!n)return[];let o=JSON.parse(n.content),s=Tt(t,"package.json");if(!Ft(s))return[];let i=JSON.parse(tn(s,"utf8"));i.dependencies=i.dependencies??{};let a=[];for(let[l,c]of Object.entries(o.dependencies??{}))i.dependencies[l]!==c&&(i.dependencies[l]=c,a.push(`${l}@${c}`));for(let l of r){let c=o._conditionalDeps?.[l];if(c)for(let[p,d]of Object.entries(c))i.dependencies[p]!==d&&(i.dependencies[p]=d,a.push(`${p}@${d}`))}return st(s,JSON.stringify(i,null,2)+`
2713
+ `,"utf8"),a}function Od(t,e){let r=e.find(c=>c.path==="mistflow.json.fragment.json");if(!r)return[];let n=JSON.parse(r.content);if(!n.cron||n.cron.length===0)return[];let o=Tt(t,"mistflow.json"),s=Ft(o)?JSON.parse(tn(o,"utf8")):{},i=Array.isArray(s.cron)?s.cron:[],a=new Set(i.map(c=>c.name)),l=[];for(let c of n.cron)a.has(c.name)||(i.push(c),l.push(c.name));return s.cron=i,st(o,JSON.stringify(s,null,2)+`
2714
+ `,"utf8"),l}function Md(t){let e=Tt(t,"db","schema","index.ts");if(!Ft(e)){st(e,["export * from './auth';","export * from './ai-chat';",""].join(`
2715
+ `),"utf8");return}let r=tn(e,"utf8");if(r.includes("./ai-chat"))return;let n=r.replace(/\n+$/,"");st(e,n+`
3014
2716
  export * from './ai-chat';
3015
- `,"utf8")}function Id(t){let e=St(t,"lib","ai","tools","manifest.ts");if(!Ut(e))return;let o=en(e,"utf8");if(o.includes("runPythonTool"))return;let n=o,r=n.match(/^import[\s\S]*?;\s*$/m);r?n=n.replace(r[0],r[0]+`
2717
+ `,"utf8")}function jd(t){let e=Tt(t,"lib","ai","tools","manifest.ts");if(!Ft(e))return;let r=tn(e,"utf8");if(r.includes("runPythonTool"))return;let n=r,o=n.match(/^import[\s\S]*?;\s*$/m);o?n=n.replace(o[0],o[0]+`
3016
2718
  import { runPythonTool } from "./run-python";
3017
2719
  `):n=`import { runPythonTool } from "./run-python";
3018
2720
 
3019
- `+n,n.includes("// __SCAFFOLD_TOOLS_PLACEHOLDER__")?n=n.replace(/\/\/ __SCAFFOLD_TOOLS_PLACEHOLDER__/,"runPythonTool,"):/TOOLS:\s*ToolManifest\[\]\s*=\s*\[\s*\]/.test(n)&&(n=n.replace(/TOOLS:\s*ToolManifest\[\]\s*=\s*\[\s*\]/,"TOOLS: ToolManifest[] = [runPythonTool]")),nt(e,n,"utf8")}function Pd(t,e){let o=St(t,"db","schema","ai-chat.ts");if(!Ut(o))return;let n=en(o,"utf8");if(!n.includes("SCAFFOLD_MCP_CALLER_IMPORT_PLACEHOLDER"))return;let r=e?`// installer-rewrote MCP_CALLER_IMPORT (mcp enabled)
2721
+ `+n,n.includes("// __SCAFFOLD_TOOLS_PLACEHOLDER__")?n=n.replace(/\/\/ __SCAFFOLD_TOOLS_PLACEHOLDER__/,"runPythonTool,"):/TOOLS:\s*ToolManifest\[\]\s*=\s*\[\s*\]/.test(n)&&(n=n.replace(/TOOLS:\s*ToolManifest\[\]\s*=\s*\[\s*\]/,"TOOLS: ToolManifest[] = [runPythonTool]")),st(e,n,"utf8")}function Dd(t,e){let r=Tt(t,"db","schema","ai-chat.ts");if(!Ft(r))return;let n=tn(r,"utf8");if(!n.includes("SCAFFOLD_MCP_CALLER_IMPORT_PLACEHOLDER"))return;let o=e?`// installer-rewrote MCP_CALLER_IMPORT (mcp enabled)
3020
2722
  import { mcpCaller } from "./mcp";`:`// installer-rewrote MCP_CALLER_IMPORT (mcp disabled \u2014 schema still compiles, CHECK enforces null)
3021
- const mcpCaller: { id: ReturnType<typeof text> } | undefined = undefined;`,s=n.replace(/\/\/ SCAFFOLD_MCP_CALLER_IMPORT_PLACEHOLDER[\s\S]*?const mcpCaller:[^;]+;/,r);nt(o,s,"utf8")}function Rd(t,e){let o=St(t,"lib","chat","sandbox.ts");if(!Ut(o))return;let n=en(o,"utf8");if(!n.includes("SCAFFOLD_SANDBOX_CAP_SQL_PLACEHOLDER"))return;if(e){let i=n.replace(/\s*\/\/ SCAFFOLD_SANDBOX_CAP_SQL_PLACEHOLDER[^\n]*\n[^\n]*\n[^\n]*\n[^\n]*\n/,`
2723
+ const mcpCaller: { id: ReturnType<typeof text> } | undefined = undefined;`,s=n.replace(/\/\/ SCAFFOLD_MCP_CALLER_IMPORT_PLACEHOLDER[\s\S]*?const mcpCaller:[^;]+;/,o);st(r,s,"utf8")}function Ld(t,e){let r=Tt(t,"lib","chat","sandbox.ts");if(!Ft(r))return;let n=tn(r,"utf8");if(!n.includes("SCAFFOLD_SANDBOX_CAP_SQL_PLACEHOLDER"))return;if(e){let i=n.replace(/\s*\/\/ SCAFFOLD_SANDBOX_CAP_SQL_PLACEHOLDER[^\n]*\n[^\n]*\n[^\n]*\n[^\n]*\n/,`
3022
2724
  // installer-kept MCP-on cap SQL (mcp_caller table exists)
3023
- `);nt(o,i,"utf8");return}let s=n.replace(/const activeCountResult = await db\.execute\(sql`[\s\S]*?`\);/,["const activeCountResult = await db.execute(sql`"," SELECT COUNT(*)::int AS count FROM agent_sandboxes s"," JOIN ai_chats c ON c.id = s.chat_id"," WHERE c.user_id = ${userId}"," AND s.e2b_sandbox_id IS NOT NULL"," AND s.expires_at > NOW()"," `);"].join(`
2725
+ `);st(r,i,"utf8");return}let s=n.replace(/const activeCountResult = await db\.execute\(sql`[\s\S]*?`\);/,["const activeCountResult = await db.execute(sql`"," SELECT COUNT(*)::int AS count FROM agent_sandboxes s"," JOIN ai_chats c ON c.id = s.chat_id"," WHERE c.user_id = ${userId}"," AND s.e2b_sandbox_id IS NOT NULL"," AND s.expires_at > NOW()"," `);"].join(`
3024
2726
  `)).replace(/\s*\/\/ SCAFFOLD_SANDBOX_CAP_SQL_PLACEHOLDER[^\n]*\n[^\n]*\n[^\n]*\n[^\n]*\n/,`
3025
2727
  // installer-rewrote sandbox cap SQL (mcp disabled \u2014 JOIN only ai_chats)
3026
- `);nt(o,s,"utf8")}function Dn(t){return t?(Array.isArray(t.integrations)?t.integrations:[]).some(o=>typeof o=="string"&&o==="ai-chat")?!0:t.surfaceType==="agent":!1}jr();function $d(t){let e=t.match(/^v?(\d+)\.(\d+)\.(\d+)/);return e?{major:Number(e[1]),minor:Number(e[2]),patch:Number(e[3])}:null}function tn(t=process.version){let e=$d(t);return!e||e.major>=23?!0:e.major===22?e.minor>=12:e.major===20?e.minor>=19:!1}function Ud(t=process.execPath){return t.includes("Library/pnpm/")||t.includes("AppData\\pnpm\\")||t.includes("/.pnpm/")?"pnpm":t.includes("/.fnm/")||process.env.FNM_DIR?"fnm":process.env.NVM_DIR||t.includes("/.nvm/")?"nvm":process.env.VOLTA_HOME||t.includes("/.volta/")?"volta":t.startsWith("/opt/homebrew/")||t.startsWith("/usr/local/")?"brew":t==="/usr/bin/node"?"system":"unknown"}function qt(t=process.version,e=Ud()){let o={pnpm:"Run: pnpm env use --global 22.12.0",fnm:"Run: fnm install 22.12 && fnm use 22.12",nvm:"Run: nvm install 22.12 && nvm use 22.12",volta:"Run: volta install node@22.12",brew:"Run: brew install node@22 && brew link node@22 --force --overwrite",system:"Install Node 22.12+ from https://nodejs.org/download",unknown:"Install Node 22.12+ from https://nodejs.org/download"};return[`OpenNext/Cloudflare builds require Node 20.19+, 22.12+, or 23+. The current MCP process is running ${t}.`,o[e],"Then quit and reopen your IDE so the MCP picks up the new Node."].join(`
3027
- `)}var wo="\nApply these choices to every file you create. Customize the shadcn CSS variables in globals.css to match. Do NOT use default shadcn blue/zinc theme.\n\n**CRITICAL: shadcn/ui components are already installed. NEVER write components/ui/*.tsx files from scratch.** If you need a shadcn component, run `npx shadcn@latest add <component>` to pull it. The project already includes: button, card, input, label, form, dialog, table, dropdown-menu, badge, separator, skeleton, sheet, tabs, avatar, select, textarea, checkbox, switch, tooltip, popover, sonner.\n\n**shadcn MCP server is available.** You have the `shadcn` MCP server installed \u2014 use it to browse and search for components, blocks, and landing page sections from the shadcn registry. Before building a landing page section from scratch, check the registry for existing blocks (hero sections, feature grids, pricing tables, testimonials, footers) and customize them instead of reinventing.\n\n**Project routes (use these exact paths):** Login is at `/login` (NOT /sign-in). Register is at `/register` (NOT /sign-up). All landing page and nav links MUST use `/login` and `/register`.\n\n### Design quality rules (non-negotiable):\n\n**Typography**: Use the plan fonts everywhere. font-heading for headings, font-body for body text.\n- **Modular scale**: Use a 1.25-1.5 ratio between sizes. A 5-level system covers most needs: xs (0.75rem captions), sm (0.875rem metadata), base (1rem body), lg (1.25-1.5rem subheadings), xl+ (2-4rem headlines). Sizes too close together (14/15/16px) create muddy hierarchy.\n- **Vertical rhythm**: Your line-height IS your spacing unit. If body is 16px with line-height 1.5 (= 24px), vertical spacing should be multiples of 24px.\n- **Weight contrast**: Use font-medium/font-semibold contrast, not just size. Bold headings + regular body creates natural hierarchy without extra sizes.\n- **Measure**: Set `max-width: 65ch` on text containers. Long lines kill readability.\n- **OpenType polish**: Use `font-variant-numeric: tabular-nums` on data tables for aligned columns.\n- Never use more than 2 font families. One well-chosen family in multiple weights is cleaner than two competing typefaces.\n- Never fall back to system fonts. Never use decorative fonts for body text. Minimum 16px (1rem) for body.\n\n**Color**: Build a functional palette, not a rainbow.\n- **Tinted neutrals**: Pure gray is dead. Add a tiny tint toward the accent color to all your grays (borders, backgrounds, muted text). The tint should be subtle enough not to read as 'colored' but creates subconscious cohesion.\n- **60/30/10 rule**: 60% neutral backgrounds/whitespace, 30% secondary colors (text, borders, inactive states), 10% accent (CTAs, highlights, focus). Accent colors work BECAUSE they're rare. Overuse kills their power.\n- **Dangerous combos to avoid**: Light gray text on white (#1 accessibility fail). Gray text on any colored background (looks washed out and dead, use a darker shade of the background color instead). Yellow text on white. Thin light text on images.\n- **Dark mode is not inverted light mode**: Use lighter surfaces for depth (no shadows). Desaturate accents slightly. Never pure black backgrounds, use dark gray (12-18% lightness). Reduce body text weight slightly (350 vs 400) because light-on-dark reads heavier.\n- Never use pure #000 or #fff. Customize the shadcn CSS variables in globals.css to match the plan palette.\n\n**Layout & space**: Space is a design material, not leftover.\n- **4px base unit**: All spacing should be multiples of 4: 4, 8, 12, 16, 24, 32, 48, 64, 96px. No arbitrary values. Name tokens semantically (`space-sm`, `space-lg`), not by value.\n- **Use `gap` not margins**: Use `gap` for sibling spacing. It eliminates margin collapse hacks and is more predictable. Reserve margin for positioning, not spacing between siblings.\n- **Rhythm through contrast**: Tight groupings within related items (8-12px), generous separation between sections (48-96px). Vary spacing WITHIN sections too. Not every row needs the same gap. The ratio between inner and outer spacing creates hierarchy.\n- **Density matches content**: Data-dense UIs (dashboards, tables, admin panels) need tighter spacing with more information visible. Marketing pages need generous whitespace and breathing room. Match density to what the user is doing.\n- **The squint test**: Blur your eyes. Can you still identify primary, secondary, and clear groupings? If everything looks the same weight, hierarchy is broken. The most important content should be obvious within 2 seconds.\n- **Asymmetry > centering**: Left-aligned with asymmetric layouts feels more designed. Center-alignment is fine for hero sections and CTAs, not for body content.\n- **Flex vs Grid**: Use Flexbox for 1D layouts (rows of items, navbars, button groups, card contents, most component internals). Use Grid for 2D layouts (page-level structure, dashboards, data-dense interfaces where rows AND columns need coordinated control). Don't default to Grid when Flexbox with `flex-wrap` would be simpler. Use `repeat(auto-fit, minmax(280px, 1fr))` for responsive grids without breakpoints. Use named `grid-template-areas` for complex page layouts and redefine at breakpoints.\n- **Cards earn their existence**: Only use cards when content needs clear separation, grid comparison, or interaction boundaries. Not everything needs a container. Never nest cards inside cards. Vary card sizes or mix cards with non-card content to break repetition.\n- **Section variety**: Don't make every section the same structure. Alternate layouts (text-left/image-right, then image-left/text-right), vary section heights, mix full-width with contained content. Monotonous section rhythm signals no designer touched this.\n- **Z-index scale**: Use a semantic scale, not arbitrary numbers. dropdown(10) -> sticky(20) -> modal-backdrop(30) -> modal(40) -> toast(50) -> tooltip(60). Never use 999 or 9999.\n- **Touch targets**: Minimum 44x44px for all interactive elements, even if the visual element is smaller (use padding).\n\n**Cards & surfaces**: Match the plan's cardStyle. Stat cards need background fills with the accent color as a subtle icon background or tinted left border. Use bg-muted/30 for section differentiation between page areas. Build a consistent shadow scale (sm -> md -> lg) and use elevation to reinforce hierarchy, not as decoration.\n\n**Sidebar**: App name + icon at top. Nav items with hover:bg-muted rounded-md transition. Active item: bg-primary/10 text-primary font-medium. User email + sign out at bottom. The sidebar should feel like a distinct surface (bg-card or bg-muted/50).\n**Mobile navigation**: The desktop sidebar must collapse to a hamburger menu on mobile. Use the shadcn `Sheet` component: a hamburger icon button (visible at `md:hidden`) opens a Sheet from the left containing the same nav items. The desktop sidebar is `hidden md:flex`. For simple apps with 3-5 nav items, a bottom tab bar (`fixed bottom-0`) is an alternative. Always respect `env(safe-area-inset-bottom)` for notched phones.\n\n**Interaction design**: Every interactive element needs 8 states, not just default and hover.\n- **Required states**: default, hover, focus, active/pressed, disabled, loading, error, success. Design all of them intentionally.\n- **Focus rings**: Use `:focus-visible` (not `:focus`) so rings show for keyboard users but not mouse clicks. Never remove focus indicators without replacement.\n- **Button hierarchy**: Don't make every button primary. Use ghost, outline, secondary, and text-link variants. One primary CTA per view.\n- **Progressive disclosure**: Simple first, advanced behind expandable sections. Don't overwhelm with options.\n- **Undo over confirmation**: For destructive actions, remove immediately + show an undo toast, then permanently delete after timeout. Better than confirmation dialogs that users click through blindly.\n- **Form patterns**: Visible `<label>` elements always (placeholders aren't labels, they disappear). Validate on blur, not on keystroke. Show format expectations with examples.\n\n**Empty states**: Every empty list/table needs a rich empty state. Not just 'Nothing here'. Include: a simple inline SVG illustration (48x48, relevant to the feature), a specific message that teaches ('Create your first habit to start tracking your daily routine'), and a primary action button. Empty states are the first thing new users see. Make them inviting.\n\n**Loading states**: Use the shadcn `Skeleton` component. Show skeleton versions of cards, lists, and stats during data fetches. Example: `<Skeleton className=\"h-8 w-32\" />` for a stat number, `<Skeleton className=\"h-20 w-full\" />` for a card. Add a `loading.tsx` file in dashboard route groups. Never show a blank page or a lone spinner.\n\n**Images**: For Unsplash images on landing pages, use `next/image` with `placeholder=\"blur\"` and a blurDataURL (a tiny 10x6 base64 image \u2014 generate a solid color blur like `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAGCAYAAAD68A/GAAAAP0lEQVQYV2N89+7dfwYGBgZGRkYGBgYmBjIBE7kaPHv27D8DA8N/BgYGRkZGRgYGJgYyARO5GkjWQHEoxRoAAPyTGAGBMpEAAAAASUVORK5CYII=`). This creates a smooth shimmer-in effect instead of images popping in.\n\n**Motion** (`motion` package is installed):\n\n**Hero moment strategy**: Pick ONE signature animation per page. Not scattered animations on everything. On a landing page, the hero entrance is your hero moment. On a dashboard, a satisfying success animation after the primary action. One great animation > ten mediocre ones. Everything else gets subtle transitions.\n\n**Animation opportunity audit** -- check these 5 categories:\n1. **Missing feedback**: Button has no press response? Add `active:scale-[0.97]` (CSS) or `whileTap={{ scale: 0.97 }}` (motion). Toggle has no slide? Animate it.\n2. **Jarring transitions**: Menu appears instantly? Add 200ms fade + slide. Content swaps without transition? Crossfade between states.\n3. **Unclear relationships**: Parent-child not visually connected? Stagger children 50ms after parent. Deleted item just disappears? Animate it out (opacity + height collapse).\n4. **Lack of delight**: Success state is flat? Draw a checkmark SVG with stroke-dashoffset animation. Empty state is static? Add a gentle float on the illustration.\n5. **Missed guidance**: First-time user gets no hint? Pulse the primary CTA once on page load. New feature unnoticed? Brief highlight animation.\n\n**Timing hierarchy**: 100-150ms for instant feedback (buttons, toggles). 200-300ms for state changes (menus, tooltips). 300-500ms for layout shifts (accordions, modals). 500-800ms for entrances (page loads). Exit animations run at 75% of entrance duration.\n\n**Easing**: Use exponential curves (Quart out, Expo out) for sophisticated physics. Never `ease` (generic), never bounce/elastic (dated). Real objects decelerate smoothly. CSS: `cubic-bezier(0.25, 1, 0.5, 1)` (Quart out) or `cubic-bezier(0.16, 1, 0.3, 1)` (Expo out).\n\n**Micro-interaction recipes**:\n- **Button press**: `active:scale-[0.97] transition-transform duration-100` (CSS only, no motion needed)\n- **Success checkmark**: SVG `<path>` with `stroke-dasharray` + `stroke-dashoffset` animation from full to 0. 400ms Expo out.\n- **Height expand/collapse**: `grid-template-rows: 0fr -> 1fr` with `transition: grid-template-rows 300ms`. No JS layout measurement needed.\n- **Skeleton to content**: Skeleton pulses (CSS `animate-pulse`), then crossfades to real content with `opacity` transition. Use the pre-scaffolded `<ContentLoader>` component.\n- **Staggered list entrance**: CSS `animation-delay` on children: `.animate-fade-up:nth-child(1) { animation-delay: 0ms } :nth-child(2) { animation-delay: 50ms }` etc. Or use `<StaggerList>` component.\n- **Number count-up**: Use `@number-flow/react` for smooth digit morphing on stats/metrics.\n\n**Performance**: Only animate `transform` and `opacity` (GPU-accelerated). Never animate width, height, top, left, margin, or padding. For scroll-tied effects on landing pages, use CSS `animation-timeline: scroll()` where supported (progressive enhancement).\n\n**Accessibility**: Always respect `prefers-reduced-motion`. Wrap animations in `@media (prefers-reduced-motion: no-preference) { ... }`. Provide static alternatives that convey the same information.\n\n**SSR-safe (CRITICAL)**: This app deploys to Mistflow Cloud's edge runtime where IntersectionObserver may not fire. NEVER use `initial={{ opacity: 0 }}` (content stays invisible forever). Use CSS @keyframes for entrance effects:\n```tsx\n// SAFE: CSS @keyframes in globals.css:\n// @keyframes fade-up { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: translateY(0); } }\n// .animate-fade-up { animation: fade-up 0.5s ease-out both; }\n// Use motion ONLY for hover/tap/gesture interactions:\nimport { motion } from 'motion/react';\n<motion.div whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.97 }} transition={{ duration: 0.15 }}>\n// NEVER: <motion.div initial={{ opacity: 0 }} whileInView={{ opacity: 1 }}>\n```\nFor dashboard pages, keep animations minimal. Button press feedback + skeleton loading + success states. No entrance animations on dashboard pages.\n\n**UX writing**: Every word earns its place.\n- **Buttons**: Use outcome-focused verb + object ('Save changes', 'Create account'), never generic ('Submit', 'OK', 'Click here').\n- **Destructive actions**: State exact consequences ('Delete 5 items permanently', not 'Delete selected'). Distinguish 'Delete' (permanent) from 'Remove' (recoverable).\n- **Error messages**: Answer three questions: what happened, why, and how to fix it. 'Email address is not valid. Include an @ symbol.' not 'Invalid input'. Never blame the user.\n- **Empty states**: Explain what will appear here, why it matters, and give a clear next action.\n- **Loading/progress**: Set time expectations. 'Deploying your app (usually takes 30 seconds)' not just a spinner.\n- **Consistency**: Pick one term and stick with it. 'Delete' everywhere, not Delete/Remove/Trash interchangeably.\n- **Link text**: Make it standalone for accessibility. 'View pricing plans' not 'Click here'.\n\n**Responsive design**: Mobile-first, then enhance.\n- **Mobile-first CSS**: Write base styles for mobile, then use `min-width` media queries to enhance for larger screens. Never start with desktop and try to cram it into mobile.\n- **Input method matters more than screen size**: Use `@media (pointer: fine)` for mouse/trackpad (smaller targets OK, hover effects work), `@media (pointer: coarse)` for touch (44px targets required, no hover-dependent UI). Use `@media (hover: none)` to detect devices that can't hover. Never require hover to reveal functionality.\n- **Safe areas**: Add `<meta name='viewport' content='width=device-width, initial-scale=1, viewport-fit=cover'>` and use `env(safe-area-inset-bottom)` for bottom navigation on notched phones. Fixed bottom bars must respect safe areas.\n- **Self-adjusting grids**: Use `grid-template-columns: repeat(auto-fit, minmax(280px, 1fr))` for card grids. Columns reflow naturally without breakpoint management.\n- **Container queries over media queries**: For reusable components, use `container-type: inline-size` on the parent and `@container (min-width: 400px)` on children. Components adapt to their container, not the viewport.\n- **No horizontal scroll**: Nothing should overflow the viewport width. Test at 320px width (smallest common phone). Use `overflow-x: hidden` on the body as a safety net but fix the root cause.\n- **Touch-friendly spacing**: On mobile, increase padding on list items and nav links. Thumb zone for primary actions is bottom-center of the screen.\n- **Responsive images**: Use `next/image` with `sizes` prop for responsive sizing. For art direction changes, use `<picture>` with `<source media='...'>`. Always set `width` and `height` to prevent layout shift.\n\n**Cognitive load**: Simpler apps feel better, even with the same features.\n- **Max 4 items per group**: Humans hold 4 items in working memory. Navigation menus: max 5 top-level items. Forms: max 4 fields per section. Dashboards: max 4 metrics visible without scrolling. Pricing: max 3 tiers.\n- **Single focus per view**: Every page has ONE primary task. If there are two equal CTAs, neither is primary. One primary action, everything else is secondary or tertiary.\n- **Sequential decisions**: Don't present all options at once. Multi-step forms beat one long form. Each step should have one decision.\n- **Progressive disclosure**: Show the essential first, reveal complexity on demand. Use expandable sections, tabs, or drill-down navigation. The default view should cover 80% of use cases.\n- **Visual grouping**: Related items must be visually grouped (proximity + shared background or border). Unrelated items must have clear separation. The Gestalt principle of proximity is the easiest way to reduce cognitive load.\n- **Reduce choice anxiety**: For important decisions, provide a recommended/default option. Highlight it visually. 'Most popular' badges on pricing plans reduce decision time.\n- **Keep context visible**: In multi-step flows, show a progress indicator and allow going back. Never make users remember what they entered on a previous screen.\n\n**Production hardening**: Designs that only work with perfect data aren't production-ready.\n- **Text overflow**: Use `text-overflow: ellipsis` + `overflow: hidden` for single-line text in constrained spaces. Use `-webkit-line-clamp` for multi-line. Add `min-width: 0` on flex items to prevent overflow.\n- **Extreme inputs**: Test with 100+ character names, emoji in text fields, and empty strings. The layout should not break.\n- **Error resilience**: Every API call needs error UI. Network failures, 404s, 500s all need clear messages with recovery options. Never show raw stack traces.\n\n**Landing page component patterns** (use these for professional, non-generic landing pages):\nBuild these components inline (shadcn + motion + Tailwind is enough):\n- **Animated number counters**: Use `@number-flow/react` (installed) for stat sections. `<NumberFlow value={count} />` with smooth digit transitions.\n- **Marquee / logo ticker**: CSS-only infinite scroll (`@keyframes marquee { from { transform: translateX(0) } to { transform: translateX(-50%) } }`) with duplicated content. Pause on hover.\n- **Bento grid**: CSS grid with varied `col-span` and `row-span`. Each cell gets a unique visual. Never make all cells the same size.\n- **Animated beam / connection lines**: SVG paths with `motion` stroke-dashoffset animation for 'how it works' sections.\n- **Shimmer borders**: `background: conic-gradient(...)` wrapper with animation for hero CTAs.\n- **Comparison tables**: Sticky-header table with check/x icons and row highlighting on the recommended plan.\n- **Testimonial carousel**: CSS scroll-snap for horizontal cards with avatar, quote, name, role. Auto-scroll paused on hover.\n- **Gradient mesh backgrounds**: Multiple layered `radial-gradient` backgrounds for hero sections.\nPick 3-5 per landing page based on the app's content. The goal: every landing page should look like it was designed by a human, not generated by AI.\n\n**Landing page navbar (non-negotiable):** Fully transparent (no background, no border, no backdrop-blur) and NOT sticky/fixed. It sits inside the hero section and scrolls naturally. White text on dark hero backgrounds. Primary CTA button in the nav uses the accent color (solid fill, rounded-md). Do NOT use `sticky`, `fixed`, `top-0`, or `backdrop-blur` on landing page navbars. Dashboard sidebar/nav IS fixed (different pattern).\n\n**Design for THIS app, not any app** (the #1 way to avoid generic AI output):\n- Every layout decision should be informed by what THIS app does. A gym check-in app needs a prominent search bar. A habit tracker needs a daily view. A CRM needs a pipeline. Don't default to the same dashboard-with-stats layout for everything.\n- Write copy that is SPECIFIC to the app's domain. Not 'Manage your data efficiently' but 'Check in members in under 3 seconds'. Every headline, empty state, and CTA should reference what the user actually does in this app.\n- Choose visual emphasis based on the primary action. If the user comes to log a workout, the workout form should be the biggest thing on the dashboard.\n- Use domain-appropriate metaphors. A fitness app uses progress rings. A project tracker uses kanban columns. A recipe app uses cards with photos. Don't use the same card grid for everything.\n\n**NEVER do these (AI slop anti-patterns)**:\nThe following are telltale signs of AI-generated design. Avoid all of them.\n*Visual patterns*:\n- Cyan-on-dark, purple-to-blue gradients, neon accents on dark backgrounds (the AI color palette)\n- Gradient text on headings or metrics (decorative noise, not meaningful)\n- Glassmorphism / blur effects used decoratively (only use blur for overlays/modals that need it)\n- Rounded rectangles with thick colored border on one side (lazy accent, never looks intentional)\n- Dark mode with glowing colored box-shadows on accent elements\n- Small rounded-square icon tiles stacked above headings (the icon-tile pattern)\n- Pure black (#000) backgrounds in dark mode (use tinted dark gray instead)\n- 3-column icon + title + paragraph feature grid (use asymmetric layouts, bento grids, or varied card sizes)\n- Nested cards inside cards (visual noise, flatten the hierarchy)\n- Everything center-aligned (use left-alignment for body content, reserve center for heroes/CTAs)\n- Monotonous spacing (same gap everywhere signals no designer touched this)\n*Copy patterns*:\n- Hero text like 'Transform your X' / 'One Day at a Time' / 'Join thousands' / 'Unlock the power of' (write SPECIFIC copy)\n- Generic feature descriptions ('Powerful analytics', 'Seamless integration', 'Lightning fast')\n- The hero metric template (big number, small label, supporting stats, gradient accent)\n- Repeating information users can already see. Every word must earn its place.\n*Functional patterns*:\n- Bounce/elastic easing on animations (feels dated, real objects decelerate smoothly, use Quart/Expo out)\n- Modals for things that could be inline (modals are a last resort, not a default)\n- Nav links to pages that don't exist. EVERY link in the sidebar/topnav MUST have a corresponding page.\n- FAKE FORMS. NEVER use `setTimeout` or `await new Promise` to simulate API calls. Every form MUST have a real server action (actions.ts with 'use server') that writes to the database. If a form doesn't persist data, the feature is BROKEN.\n\n**Favicon**: A default SVG favicon exists at app/icon.svg. On the landing page step, update it to match the app. Keep it simple (a single icon/letter on a rounded square, using the accent color).\n";var vo=`# Landing Page Rules
2728
+ `);st(r,s,"utf8")}function nn(t){return t?(Array.isArray(t.integrations)?t.integrations:[]).some(r=>typeof r=="string"&&r==="ai-chat")?!0:t.surfaceType==="agent":!1}Lo();function Gd(t){let e=t.match(/^v?(\d+)\.(\d+)\.(\d+)/);return e?{major:Number(e[1]),minor:Number(e[2]),patch:Number(e[3])}:null}function rn(t=process.version){let e=Gd(t);return!e||e.major>=23?!0:e.major===22?e.minor>=12:e.major===20?e.minor>=19:!1}function Jd(t=process.execPath){return t.includes("Library/pnpm/")||t.includes("AppData\\pnpm\\")||t.includes("/.pnpm/")?"pnpm":t.includes("/.fnm/")||process.env.FNM_DIR?"fnm":process.env.NVM_DIR||t.includes("/.nvm/")?"nvm":process.env.VOLTA_HOME||t.includes("/.volta/")?"volta":t.startsWith("/opt/homebrew/")||t.startsWith("/usr/local/")?"brew":t==="/usr/bin/node"?"system":"unknown"}function qt(t=process.version,e=Jd()){let r={pnpm:"Run: pnpm env use --global 22.12.0",fnm:"Run: fnm install 22.12 && fnm use 22.12",nvm:"Run: nvm install 22.12 && nvm use 22.12",volta:"Run: volta install node@22.12",brew:"Run: brew install node@22 && brew link node@22 --force --overwrite",system:"Install Node 22.12+ from https://nodejs.org/download",unknown:"Install Node 22.12+ from https://nodejs.org/download"};return[`OpenNext/Cloudflare builds require Node 20.19+, 22.12+, or 23+. The current MCP process is running ${t}.`,r[e],"Then quit and reopen your IDE so the MCP picks up the new Node."].join(`
2729
+ `)}var wr="\nApply these choices to every file you create. Customize the shadcn CSS variables in globals.css to match. Do NOT use default shadcn blue/zinc theme.\n\n**CRITICAL: shadcn/ui components are already installed. NEVER write components/ui/*.tsx files from scratch.** If you need a shadcn component, run `npx shadcn@latest add <component>` to pull it. The project already includes: button, card, input, label, form, dialog, table, dropdown-menu, badge, separator, skeleton, sheet, tabs, avatar, select, textarea, checkbox, switch, tooltip, popover, sonner.\n\n**shadcn MCP server is available.** You have the `shadcn` MCP server installed \u2014 use it to browse and search for components, blocks, and landing page sections from the shadcn registry. Before building a landing page section from scratch, check the registry for existing blocks (hero sections, feature grids, pricing tables, testimonials, footers) and customize them instead of reinventing.\n\n**Project routes (use these exact paths):** Login is at `/login` (NOT /sign-in). Register is at `/register` (NOT /sign-up). All landing page and nav links MUST use `/login` and `/register`.\n\n### Design quality rules (non-negotiable):\n\n**Typography**: Use the plan fonts everywhere. font-heading for headings, font-body for body text.\n- **Modular scale**: Use a 1.25-1.5 ratio between sizes. A 5-level system covers most needs: xs (0.75rem captions), sm (0.875rem metadata), base (1rem body), lg (1.25-1.5rem subheadings), xl+ (2-4rem headlines). Sizes too close together (14/15/16px) create muddy hierarchy.\n- **Vertical rhythm**: Your line-height IS your spacing unit. If body is 16px with line-height 1.5 (= 24px), vertical spacing should be multiples of 24px.\n- **Weight contrast**: Use font-medium/font-semibold contrast, not just size. Bold headings + regular body creates natural hierarchy without extra sizes.\n- **Measure**: Set `max-width: 65ch` on text containers. Long lines kill readability.\n- **OpenType polish**: Use `font-variant-numeric: tabular-nums` on data tables for aligned columns.\n- Never use more than 2 font families. One well-chosen family in multiple weights is cleaner than two competing typefaces.\n- Never fall back to system fonts. Never use decorative fonts for body text. Minimum 16px (1rem) for body.\n\n**Color**: Build a functional palette, not a rainbow.\n- **Tinted neutrals**: Pure gray is dead. Add a tiny tint toward the accent color to all your grays (borders, backgrounds, muted text). The tint should be subtle enough not to read as 'colored' but creates subconscious cohesion.\n- **60/30/10 rule**: 60% neutral backgrounds/whitespace, 30% secondary colors (text, borders, inactive states), 10% accent (CTAs, highlights, focus). Accent colors work BECAUSE they're rare. Overuse kills their power.\n- **Dangerous combos to avoid**: Light gray text on white (#1 accessibility fail). Gray text on any colored background (looks washed out and dead, use a darker shade of the background color instead). Yellow text on white. Thin light text on images.\n- **Dark mode is not inverted light mode**: Use lighter surfaces for depth (no shadows). Desaturate accents slightly. Never pure black backgrounds, use dark gray (12-18% lightness). Reduce body text weight slightly (350 vs 400) because light-on-dark reads heavier.\n- Never use pure #000 or #fff. Customize the shadcn CSS variables in globals.css to match the plan palette.\n\n**Layout & space**: Space is a design material, not leftover.\n- **4px base unit**: All spacing should be multiples of 4: 4, 8, 12, 16, 24, 32, 48, 64, 96px. No arbitrary values. Name tokens semantically (`space-sm`, `space-lg`), not by value.\n- **Use `gap` not margins**: Use `gap` for sibling spacing. It eliminates margin collapse hacks and is more predictable. Reserve margin for positioning, not spacing between siblings.\n- **Rhythm through contrast**: Tight groupings within related items (8-12px), generous separation between sections (48-96px). Vary spacing WITHIN sections too. Not every row needs the same gap. The ratio between inner and outer spacing creates hierarchy.\n- **Density matches content**: Data-dense UIs (dashboards, tables, admin panels) need tighter spacing with more information visible. Marketing pages need generous whitespace and breathing room. Match density to what the user is doing.\n- **The squint test**: Blur your eyes. Can you still identify primary, secondary, and clear groupings? If everything looks the same weight, hierarchy is broken. The most important content should be obvious within 2 seconds.\n- **Asymmetry > centering**: Left-aligned with asymmetric layouts feels more designed. Center-alignment is fine for hero sections and CTAs, not for body content.\n- **Flex vs Grid**: Use Flexbox for 1D layouts (rows of items, navbars, button groups, card contents, most component internals). Use Grid for 2D layouts (page-level structure, dashboards, data-dense interfaces where rows AND columns need coordinated control). Don't default to Grid when Flexbox with `flex-wrap` would be simpler. Use `repeat(auto-fit, minmax(280px, 1fr))` for responsive grids without breakpoints. Use named `grid-template-areas` for complex page layouts and redefine at breakpoints.\n- **Cards earn their existence**: Only use cards when content needs clear separation, grid comparison, or interaction boundaries. Not everything needs a container. Never nest cards inside cards. Vary card sizes or mix cards with non-card content to break repetition.\n- **Section variety**: Don't make every section the same structure. Alternate layouts (text-left/image-right, then image-left/text-right), vary section heights, mix full-width with contained content. Monotonous section rhythm signals no designer touched this.\n- **Z-index scale**: Use a semantic scale, not arbitrary numbers. dropdown(10) -> sticky(20) -> modal-backdrop(30) -> modal(40) -> toast(50) -> tooltip(60). Never use 999 or 9999.\n- **Touch targets**: Minimum 44x44px for all interactive elements, even if the visual element is smaller (use padding).\n\n**Cards & surfaces**: Match the plan's cardStyle. Stat cards need background fills with the accent color as a subtle icon background or tinted left border. Use bg-muted/30 for section differentiation between page areas. Build a consistent shadow scale (sm -> md -> lg) and use elevation to reinforce hierarchy, not as decoration.\n\n**Sidebar**: App name + icon at top. Nav items with hover:bg-muted rounded-md transition. Active item: bg-primary/10 text-primary font-medium. User email + sign out at bottom. The sidebar should feel like a distinct surface (bg-card or bg-muted/50).\n**Mobile navigation**: The desktop sidebar must collapse to a hamburger menu on mobile. Use the shadcn `Sheet` component: a hamburger icon button (visible at `md:hidden`) opens a Sheet from the left containing the same nav items. The desktop sidebar is `hidden md:flex`. For simple apps with 3-5 nav items, a bottom tab bar (`fixed bottom-0`) is an alternative. Always respect `env(safe-area-inset-bottom)` for notched phones.\n\n**Interaction design**: Every interactive element needs 8 states, not just default and hover.\n- **Required states**: default, hover, focus, active/pressed, disabled, loading, error, success. Design all of them intentionally.\n- **Focus rings**: Use `:focus-visible` (not `:focus`) so rings show for keyboard users but not mouse clicks. Never remove focus indicators without replacement.\n- **Button hierarchy**: Don't make every button primary. Use ghost, outline, secondary, and text-link variants. One primary CTA per view.\n- **Progressive disclosure**: Simple first, advanced behind expandable sections. Don't overwhelm with options.\n- **Undo over confirmation**: For destructive actions, remove immediately + show an undo toast, then permanently delete after timeout. Better than confirmation dialogs that users click through blindly.\n- **Form patterns**: Visible `<label>` elements always (placeholders aren't labels, they disappear). Validate on blur, not on keystroke. Show format expectations with examples.\n\n**Empty states**: Every empty list/table needs a rich empty state. Not just 'Nothing here'. Include: a simple inline SVG illustration (48x48, relevant to the feature), a specific message that teaches ('Create your first habit to start tracking your daily routine'), and a primary action button. Empty states are the first thing new users see. Make them inviting.\n\n**Loading states**: Use the shadcn `Skeleton` component. Show skeleton versions of cards, lists, and stats during data fetches. Example: `<Skeleton className=\"h-8 w-32\" />` for a stat number, `<Skeleton className=\"h-20 w-full\" />` for a card. Add a `loading.tsx` file in dashboard route groups. Never show a blank page or a lone spinner.\n\n**Images**: For Unsplash images on landing pages, use `next/image` with `placeholder=\"blur\"` and a blurDataURL (a tiny 10x6 base64 image \u2014 generate a solid color blur like `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAGCAYAAAD68A/GAAAAP0lEQVQYV2N89+7dfwYGBgZGRkYGBgYmBjIBE7kaPHv27D8DA8N/BgYGRkZGRgYGJgYyARO5GkjWQHEoxRoAAPyTGAGBMpEAAAAASUVORK5CYII=`). This creates a smooth shimmer-in effect instead of images popping in.\n\n**Motion** (`motion` package is installed):\n\n**Hero moment strategy**: Pick ONE signature animation per page. Not scattered animations on everything. On a landing page, the hero entrance is your hero moment. On a dashboard, a satisfying success animation after the primary action. One great animation > ten mediocre ones. Everything else gets subtle transitions.\n\n**Animation opportunity audit** -- check these 5 categories:\n1. **Missing feedback**: Button has no press response? Add `active:scale-[0.97]` (CSS) or `whileTap={{ scale: 0.97 }}` (motion). Toggle has no slide? Animate it.\n2. **Jarring transitions**: Menu appears instantly? Add 200ms fade + slide. Content swaps without transition? Crossfade between states.\n3. **Unclear relationships**: Parent-child not visually connected? Stagger children 50ms after parent. Deleted item just disappears? Animate it out (opacity + height collapse).\n4. **Lack of delight**: Success state is flat? Draw a checkmark SVG with stroke-dashoffset animation. Empty state is static? Add a gentle float on the illustration.\n5. **Missed guidance**: First-time user gets no hint? Pulse the primary CTA once on page load. New feature unnoticed? Brief highlight animation.\n\n**Timing hierarchy**: 100-150ms for instant feedback (buttons, toggles). 200-300ms for state changes (menus, tooltips). 300-500ms for layout shifts (accordions, modals). 500-800ms for entrances (page loads). Exit animations run at 75% of entrance duration.\n\n**Easing**: Use exponential curves (Quart out, Expo out) for sophisticated physics. Never `ease` (generic), never bounce/elastic (dated). Real objects decelerate smoothly. CSS: `cubic-bezier(0.25, 1, 0.5, 1)` (Quart out) or `cubic-bezier(0.16, 1, 0.3, 1)` (Expo out).\n\n**Micro-interaction recipes**:\n- **Button press**: `active:scale-[0.97] transition-transform duration-100` (CSS only, no motion needed)\n- **Success checkmark**: SVG `<path>` with `stroke-dasharray` + `stroke-dashoffset` animation from full to 0. 400ms Expo out.\n- **Height expand/collapse**: `grid-template-rows: 0fr -> 1fr` with `transition: grid-template-rows 300ms`. No JS layout measurement needed.\n- **Skeleton to content**: Skeleton pulses (CSS `animate-pulse`), then crossfades to real content with `opacity` transition. Use the pre-scaffolded `<ContentLoader>` component.\n- **Staggered list entrance**: CSS `animation-delay` on children: `.animate-fade-up:nth-child(1) { animation-delay: 0ms } :nth-child(2) { animation-delay: 50ms }` etc. Or use `<StaggerList>` component.\n- **Number count-up**: Use `@number-flow/react` for smooth digit morphing on stats/metrics.\n\n**Performance**: Only animate `transform` and `opacity` (GPU-accelerated). Never animate width, height, top, left, margin, or padding. For scroll-tied effects on landing pages, use CSS `animation-timeline: scroll()` where supported (progressive enhancement).\n\n**Accessibility**: Always respect `prefers-reduced-motion`. Wrap animations in `@media (prefers-reduced-motion: no-preference) { ... }`. Provide static alternatives that convey the same information.\n\n**SSR-safe (CRITICAL)**: This app deploys to Mistflow Cloud's edge runtime where IntersectionObserver may not fire. NEVER use `initial={{ opacity: 0 }}` (content stays invisible forever). Use CSS @keyframes for entrance effects:\n```tsx\n// SAFE: CSS @keyframes in globals.css:\n// @keyframes fade-up { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: translateY(0); } }\n// .animate-fade-up { animation: fade-up 0.5s ease-out both; }\n// Use motion ONLY for hover/tap/gesture interactions:\nimport { motion } from 'motion/react';\n<motion.div whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.97 }} transition={{ duration: 0.15 }}>\n// NEVER: <motion.div initial={{ opacity: 0 }} whileInView={{ opacity: 1 }}>\n```\nFor dashboard pages, keep animations minimal. Button press feedback + skeleton loading + success states. No entrance animations on dashboard pages.\n\n**UX writing**: Every word earns its place.\n- **Buttons**: Use outcome-focused verb + object ('Save changes', 'Create account'), never generic ('Submit', 'OK', 'Click here').\n- **Destructive actions**: State exact consequences ('Delete 5 items permanently', not 'Delete selected'). Distinguish 'Delete' (permanent) from 'Remove' (recoverable).\n- **Error messages**: Answer three questions: what happened, why, and how to fix it. 'Email address is not valid. Include an @ symbol.' not 'Invalid input'. Never blame the user.\n- **Empty states**: Explain what will appear here, why it matters, and give a clear next action.\n- **Loading/progress**: Set time expectations. 'Deploying your app (usually takes 30 seconds)' not just a spinner.\n- **Consistency**: Pick one term and stick with it. 'Delete' everywhere, not Delete/Remove/Trash interchangeably.\n- **Link text**: Make it standalone for accessibility. 'View pricing plans' not 'Click here'.\n\n**Responsive design**: Mobile-first, then enhance.\n- **Mobile-first CSS**: Write base styles for mobile, then use `min-width` media queries to enhance for larger screens. Never start with desktop and try to cram it into mobile.\n- **Input method matters more than screen size**: Use `@media (pointer: fine)` for mouse/trackpad (smaller targets OK, hover effects work), `@media (pointer: coarse)` for touch (44px targets required, no hover-dependent UI). Use `@media (hover: none)` to detect devices that can't hover. Never require hover to reveal functionality.\n- **Safe areas**: Add `<meta name='viewport' content='width=device-width, initial-scale=1, viewport-fit=cover'>` and use `env(safe-area-inset-bottom)` for bottom navigation on notched phones. Fixed bottom bars must respect safe areas.\n- **Self-adjusting grids**: Use `grid-template-columns: repeat(auto-fit, minmax(280px, 1fr))` for card grids. Columns reflow naturally without breakpoint management.\n- **Container queries over media queries**: For reusable components, use `container-type: inline-size` on the parent and `@container (min-width: 400px)` on children. Components adapt to their container, not the viewport.\n- **No horizontal scroll**: Nothing should overflow the viewport width. Test at 320px width (smallest common phone). Use `overflow-x: hidden` on the body as a safety net but fix the root cause.\n- **Touch-friendly spacing**: On mobile, increase padding on list items and nav links. Thumb zone for primary actions is bottom-center of the screen.\n- **Responsive images**: Use `next/image` with `sizes` prop for responsive sizing. For art direction changes, use `<picture>` with `<source media='...'>`. Always set `width` and `height` to prevent layout shift.\n\n**Cognitive load**: Simpler apps feel better, even with the same features.\n- **Max 4 items per group**: Humans hold 4 items in working memory. Navigation menus: max 5 top-level items. Forms: max 4 fields per section. Dashboards: max 4 metrics visible without scrolling. Pricing: max 3 tiers.\n- **Single focus per view**: Every page has ONE primary task. If there are two equal CTAs, neither is primary. One primary action, everything else is secondary or tertiary.\n- **Sequential decisions**: Don't present all options at once. Multi-step forms beat one long form. Each step should have one decision.\n- **Progressive disclosure**: Show the essential first, reveal complexity on demand. Use expandable sections, tabs, or drill-down navigation. The default view should cover 80% of use cases.\n- **Visual grouping**: Related items must be visually grouped (proximity + shared background or border). Unrelated items must have clear separation. The Gestalt principle of proximity is the easiest way to reduce cognitive load.\n- **Reduce choice anxiety**: For important decisions, provide a recommended/default option. Highlight it visually. 'Most popular' badges on pricing plans reduce decision time.\n- **Keep context visible**: In multi-step flows, show a progress indicator and allow going back. Never make users remember what they entered on a previous screen.\n\n**Production hardening**: Designs that only work with perfect data aren't production-ready.\n- **Text overflow**: Use `text-overflow: ellipsis` + `overflow: hidden` for single-line text in constrained spaces. Use `-webkit-line-clamp` for multi-line. Add `min-width: 0` on flex items to prevent overflow.\n- **Extreme inputs**: Test with 100+ character names, emoji in text fields, and empty strings. The layout should not break.\n- **Error resilience**: Every API call needs error UI. Network failures, 404s, 500s all need clear messages with recovery options. Never show raw stack traces.\n\n**Landing page component patterns** (use these for professional, non-generic landing pages):\nBuild these components inline (shadcn + motion + Tailwind is enough):\n- **Animated number counters**: Use `@number-flow/react` (installed) for stat sections. `<NumberFlow value={count} />` with smooth digit transitions.\n- **Marquee / logo ticker**: CSS-only infinite scroll (`@keyframes marquee { from { transform: translateX(0) } to { transform: translateX(-50%) } }`) with duplicated content. Pause on hover.\n- **Bento grid**: CSS grid with varied `col-span` and `row-span`. Each cell gets a unique visual. Never make all cells the same size.\n- **Animated beam / connection lines**: SVG paths with `motion` stroke-dashoffset animation for 'how it works' sections.\n- **Shimmer borders**: `background: conic-gradient(...)` wrapper with animation for hero CTAs.\n- **Comparison tables**: Sticky-header table with check/x icons and row highlighting on the recommended plan.\n- **Testimonial carousel**: CSS scroll-snap for horizontal cards with avatar, quote, name, role. Auto-scroll paused on hover.\n- **Gradient mesh backgrounds**: Multiple layered `radial-gradient` backgrounds for hero sections.\nPick 3-5 per landing page based on the app's content. The goal: every landing page should look like it was designed by a human, not generated by AI.\n\n**Landing page navbar (non-negotiable):** Fully transparent (no background, no border, no backdrop-blur) and NOT sticky/fixed. It sits inside the hero section and scrolls naturally. White text on dark hero backgrounds. Primary CTA button in the nav uses the accent color (solid fill, rounded-md). Do NOT use `sticky`, `fixed`, `top-0`, or `backdrop-blur` on landing page navbars. Dashboard sidebar/nav IS fixed (different pattern).\n\n**Design for THIS app, not any app** (the #1 way to avoid generic AI output):\n- Every layout decision should be informed by what THIS app does. A gym check-in app needs a prominent search bar. A habit tracker needs a daily view. A CRM needs a pipeline. Don't default to the same dashboard-with-stats layout for everything.\n- Write copy that is SPECIFIC to the app's domain. Not 'Manage your data efficiently' but 'Check in members in under 3 seconds'. Every headline, empty state, and CTA should reference what the user actually does in this app.\n- Choose visual emphasis based on the primary action. If the user comes to log a workout, the workout form should be the biggest thing on the dashboard.\n- Use domain-appropriate metaphors. A fitness app uses progress rings. A project tracker uses kanban columns. A recipe app uses cards with photos. Don't use the same card grid for everything.\n\n**NEVER do these (AI slop anti-patterns)**:\nThe following are telltale signs of AI-generated design. Avoid all of them.\n*Visual patterns*:\n- Cyan-on-dark, purple-to-blue gradients, neon accents on dark backgrounds (the AI color palette)\n- Gradient text on headings or metrics (decorative noise, not meaningful)\n- Glassmorphism / blur effects used decoratively (only use blur for overlays/modals that need it)\n- Rounded rectangles with thick colored border on one side (lazy accent, never looks intentional)\n- Dark mode with glowing colored box-shadows on accent elements\n- Small rounded-square icon tiles stacked above headings (the icon-tile pattern)\n- Pure black (#000) backgrounds in dark mode (use tinted dark gray instead)\n- 3-column icon + title + paragraph feature grid (use asymmetric layouts, bento grids, or varied card sizes)\n- Nested cards inside cards (visual noise, flatten the hierarchy)\n- Everything center-aligned (use left-alignment for body content, reserve center for heroes/CTAs)\n- Monotonous spacing (same gap everywhere signals no designer touched this)\n*Copy patterns*:\n- Hero text like 'Transform your X' / 'One Day at a Time' / 'Join thousands' / 'Unlock the power of' (write SPECIFIC copy)\n- Generic feature descriptions ('Powerful analytics', 'Seamless integration', 'Lightning fast')\n- The hero metric template (big number, small label, supporting stats, gradient accent)\n- Repeating information users can already see. Every word must earn its place.\n*Functional patterns*:\n- Bounce/elastic easing on animations (feels dated, real objects decelerate smoothly, use Quart/Expo out)\n- Modals for things that could be inline (modals are a last resort, not a default)\n- Nav links to pages that don't exist. EVERY link in the sidebar/topnav MUST have a corresponding page.\n- FAKE FORMS. NEVER use `setTimeout` or `await new Promise` to simulate API calls. Every form MUST have a real server action (actions.ts with 'use server') that writes to the database. If a form doesn't persist data, the feature is BROKEN.\n\n**Favicon**: A default SVG favicon exists at app/icon.svg. On the landing page step, update it to match the app. Keep it simple (a single icon/letter on a rounded square, using the accent color).\n";var vr=`# Landing Page Rules
3028
2730
 
3029
2731
  These rules apply to every landing page. They are non-negotiable.
3030
2732
 
@@ -3438,9 +3140,9 @@ app/
3438
3140
  \`\`\`
3439
3141
 
3440
3142
  Each section is a separate component file. The page.tsx simply imports and stacks them. Do NOT build one monolithic page component.
3441
- `;Ie();jt();import{existsSync as Bd,readFileSync as Hd,writeFileSync as zd}from"fs";import{join as Si}from"path";var Wd={completed:"\u2705",done:"\u2705",in_progress:"\u{1F7E1}",pending:"\u23ED\uFE0F",skipped:"\u23ED\uFE0F"};function Dr(t){return t?Wd[t]??"\u23ED\uFE0F":"\u23ED\uFE0F"}function Lr(t){return t.name??t.title??`Step ${t.number}`}function _i(t){return t.entity??t.name??"Unknown"}function Gd(t){let e=t.fields??[];return e.length===0?"":typeof e[0]=="string"?e.join(", "):e.map(o=>o.name).filter(Boolean).join(", ")}function Kd(t){let e=[],o=t.plan,n=(t.name??o?.name??"App").split("-").map(c=>c.charAt(0).toUpperCase()+c.slice(1)).join(" ");e.push(`# ${n}`),e.push(""),e.push("Per-app orientation doc for host AIs editing this Mistflow-scaffolded codebase. Pairs with AGENTS.md (stack methodology) and DESIGN.md (aesthetic). Auto-regenerated by mist_init, mist_implement, and mist_deploy."),e.push("");let r=o?.summary??o?.description;if(r){if(e.push("## What this is"),e.push(""),e.push(r),o?.audienceType&&(e.push(""),e.push(`Audience: **${o.audienceType}**.`)),o?.design?.archetype){let c=o.design.tone?` (${o.design.tone} tone)`:"";e.push(`Aesthetic: **${o.design.archetype}**${c} \u2014 see DESIGN.md.`)}e.push("")}let s=o?.steps??[];if(s.length>0){e.push("## Build progress"),e.push("");let c=s.filter(p=>p.status==="completed"||p.status==="done"),m=s.filter(p=>p.status==="in_progress"),u=s.filter(p=>p.status!=="completed"&&p.status!=="done"&&p.status!=="in_progress");if(c.length>0){e.push(`**Done (${c.length}/${s.length}):**`);for(let p of c)e.push(`- ${Dr(p.status)} ${Lr(p)}`);e.push("")}if(m.length>0){e.push("**In progress:**");for(let p of m)e.push(`- ${Dr(p.status)} ${Lr(p)}`);e.push("")}if(u.length>0){e.push("**Planned next:**");for(let p of u)e.push(`- ${Dr(p.status)} ${Lr(p)}`);e.push("")}}let i=o?.dataModel??[];if(i.length>0){e.push("## Data model"),e.push("");for(let c of i){let m=Gd(c);m?e.push(`- **${_i(c)}**(${m})`):e.push(`- **${_i(c)}**`)}e.push("")}let a=o?.integrations??[];if(a.length>0){e.push("## Active integrations"),e.push("");for(let c of a){let m=c.name??c.id??"integration";e.push(`- **${m}**`)}e.push(""),e.push('Per-integration patterns: load the matching skill via `mist_docs({ topic: "<id>-integration" })` or read `.claude/skills/<id>-integration/SKILL.md`.'),e.push("")}let l=t.deploy;return l?.url&&(e.push("## Deploy"),e.push(""),e.push(`- URL: ${l.url}`),l.completedAt&&e.push(`- Last deploy: ${l.completedAt}`),l.deploymentId&&e.push(`- Last deployment id: \`${l.deploymentId}\``),e.push("")),e.push("## For fresh state"),e.push(""),e.push("This file is a **snapshot** written at scaffold/implement/deploy time \u2014 it can lag the live state of the app by minutes. For authoritative state (current step statuses, env vars actually set, deployment history, runtime errors), call:"),e.push(""),e.push("```"),e.push('mist_project({ action: "get", projectPath })'),e.push("```"),e.push(""),e.join(`
3143
+ `;ke();Dt();import{existsSync as Yd,readFileSync as Qd,writeFileSync as Xd}from"fs";import{join as Mi}from"path";var Zd={completed:"\u2705",done:"\u2705",in_progress:"\u{1F7E1}",pending:"\u23ED\uFE0F",skipped:"\u23ED\uFE0F"};function $o(t){return t?Zd[t]??"\u23ED\uFE0F":"\u23ED\uFE0F"}function Uo(t){return t.name??t.title??`Step ${t.number}`}function ji(t){return t.entity??t.name??"Unknown"}function eu(t){let e=t.fields??[];return e.length===0?"":typeof e[0]=="string"?e.join(", "):e.map(r=>r.name).filter(Boolean).join(", ")}function tu(t){let e=[],r=t.plan,n=(t.name??r?.name??"App").split("-").map(d=>d.charAt(0).toUpperCase()+d.slice(1)).join(" ");e.push(`# ${n}`),e.push(""),e.push("Per-app orientation doc for host AIs editing this Mistflow-scaffolded codebase. Pairs with AGENTS.md (stack methodology) and DESIGN.md (aesthetic). Auto-regenerated by mist_init, mist_implement, and mist_deploy."),e.push("");let o=r?.summary??r?.description;o&&(e.push("## What this is"),e.push(""),e.push(o),r?.audienceType&&(e.push(""),e.push(`Audience: **${r.audienceType}**.`)),e.push(""));let s=r?.pickedDirection&&typeof r.pickedDirection=="object"?r.pickedDirection:void 0,i=typeof s?.name=="string"?s.name:r?.design?.archetype;if(i){let d=!s&&r?.design?.tone?` (${r.design.tone} tone)`:"";e.push("## Design brief"),e.push(""),e.push(`Aesthetic: **${i}**${d} \u2014 see DESIGN.md.`),e.push("")}let a=r?.steps??[];if(a.length>0){e.push("## Build progress"),e.push("");let d=a.filter(S=>S.status==="completed"||S.status==="done"),m=a.filter(S=>S.status==="in_progress"),h=a.filter(S=>S.status!=="completed"&&S.status!=="done"&&S.status!=="in_progress");if(d.length>0){e.push(`**Done (${d.length}/${a.length}):**`);for(let S of d)e.push(`- ${$o(S.status)} ${Uo(S)}`);e.push("")}if(m.length>0){e.push("**In progress:**");for(let S of m)e.push(`- ${$o(S.status)} ${Uo(S)}`);e.push("")}if(h.length>0){e.push("**Planned next:**");for(let S of h)e.push(`- ${$o(S.status)} ${Uo(S)}`);e.push("")}}let l=r?.dataModel??[];if(l.length>0){e.push("## Data model"),e.push("");for(let d of l){let m=eu(d);m?e.push(`- **${ji(d)}**(${m})`):e.push(`- **${ji(d)}**`)}e.push("")}let c=r?.integrations??[];if(c.length>0){e.push("## Active integrations"),e.push("");for(let d of c){let m=d.name??d.id??"integration";e.push(`- **${m}**`)}e.push(""),e.push('Per-integration patterns: load the matching skill via `mist_docs({ topic: "<id>-integration" })` or read `.claude/skills/<id>-integration/SKILL.md`.'),e.push("")}let p=t.deploy;return p?.url&&(e.push("## Deploy"),e.push(""),e.push(`- URL: ${p.url}`),p.completedAt&&e.push(`- Last deploy: ${p.completedAt}`),p.deploymentId&&e.push(`- Last deployment id: \`${p.deploymentId}\``),e.push("")),e.push("## For fresh state"),e.push(""),e.push("This file is a **snapshot** written at scaffold/implement/deploy time \u2014 it can lag the live state of the app by minutes. For authoritative state (current step statuses, env vars actually set, deployment history, runtime errors), call:"),e.push(""),e.push("```"),e.push('mist_project({ action: "get", projectPath })'),e.push("```"),e.push(""),e.join(`
3442
3144
  `).trimEnd()+`
3443
- `}function nn(t){try{let e=Si(t,"mistflow.json");if(!Bd(e))return;let o=Hd(e,"utf-8"),n=JSON.parse(o),r=Kd(n);zd(Si(t,"PROJECT.md"),r,"utf-8")}catch{}}function Ci(){return`import { mcpCallLog } from "@/db";
3145
+ `}function on(t){try{let e=Mi(t,"mistflow.json");if(!Yd(e))return;let r=Qd(e,"utf-8"),n=JSON.parse(r),o=tu(n);Xd(Mi(t,"PROJECT.md"),o,"utf-8")}catch{}}function Di(){return`import { mcpCallLog } from "@/db";
3444
3146
  import type { McpAuthStatus, McpCallStatus } from "./types";
3445
3147
 
3446
3148
  export interface McpAuditRow {
@@ -3486,7 +3188,7 @@ export async function writeMcpAuditRow(
3486
3188
  costCents: row.costCents.toString(),
3487
3189
  });
3488
3190
  }
3489
- `}function Ti(){return`import { drizzle } from "drizzle-orm/neon-http";
3191
+ `}function Li(){return`import { drizzle } from "drizzle-orm/neon-http";
3490
3192
  import { neon } from "@neondatabase/serverless";
3491
3193
  import { and, eq, gte, inArray, lt, sql } from "drizzle-orm";
3492
3194
  import { mcpCaller, mcpCallLog } from "@/db";
@@ -3714,15 +3416,15 @@ export async function getRateLimitCount(
3714
3416
  ));
3715
3417
  return Number(rows[0]?.total ?? 0);
3716
3418
  }
3717
- `}var Jd=/^[a-z][a-z0-9_]{0,63}$/;function Vd(t){if(!Jd.test(t))throw new Error(`Invalid MCP tool name "${t}". Use snake_case matching /^[a-z][a-z0-9_]{0,63}$/.`)}function Yd(t){let e=new Map;for(let o of t){Vd(o.toolName);let n=e.get(o.toolName);if(n)throw new Error(`Duplicate MCP tool name "${o.toolName}" from ${o.exportedName}; already provided by ${n}.`);e.set(o.toolName,o.exportedName)}}function Ii(t=[]){Yd(t);let e=[...t].sort((s,i)=>s.toolName.localeCompare(i.toolName)),o=e.map(s=>`import { ${s.exportedName} } from "${s.importPath}";`).join(`
3718
- `),n=o?`${o}
3719
- `:"",r=e.length>0?`
3419
+ `}var nu=/^[a-z][a-z0-9_]{0,63}$/;function ru(t){if(!nu.test(t))throw new Error(`Invalid MCP tool name "${t}". Use snake_case matching /^[a-z][a-z0-9_]{0,63}$/.`)}function ou(t){let e=new Map;for(let r of t){ru(r.toolName);let n=e.get(r.toolName);if(n)throw new Error(`Duplicate MCP tool name "${r.toolName}" from ${r.exportedName}; already provided by ${n}.`);e.set(r.toolName,r.exportedName)}}function $i(t=[]){ou(t);let e=[...t].sort((s,i)=>s.toolName.localeCompare(i.toolName)),r=e.map(s=>`import { ${s.exportedName} } from "${s.importPath}";`).join(`
3420
+ `),n=r?`${r}
3421
+ `:"",o=e.length>0?`
3720
3422
  ${e.map(s=>s.exportedName).join(`,
3721
3423
  `)},
3722
3424
  `:"";return`import type { McpToolHandler } from "./types";
3723
3425
  ${n}
3724
- export const MCP_TOOLS: McpToolHandler[] = [${r}];
3725
- `}function Pi(){return`import { z } from "zod";
3426
+ export const MCP_TOOLS: McpToolHandler[] = [${o}];
3427
+ `}function Ui(){return`import { z } from "zod";
3726
3428
  import type { McpEnv } from "./caller-auth";
3727
3429
 
3728
3430
  export interface AuthenticatedMcpCaller {
@@ -3795,12 +3497,12 @@ export interface McpToolHandler<TShape extends z.ZodRawShape = z.ZodRawShape> {
3795
3497
  context: McpToolContext,
3796
3498
  ) => Promise<McpToolResult>;
3797
3499
  }
3798
- `}function Ri(t,e,o={}){let n=t.replace(/["\\\n\r]/g,""),r=e.replace(/["\\\n\r]/g,"");return`import { NextRequest, NextResponse } from "next/server";
3500
+ `}function Fi(t,e,r={}){let n=t.replace(/["\\\n\r]/g,""),o=e.replace(/["\\\n\r]/g,"");return`import { NextRequest, NextResponse } from "next/server";
3799
3501
  import { zodToJsonSchema } from "zod-to-json-schema";
3800
3502
  import { authenticateCaller, getMcpDb } from "@/lib/mcp/caller-auth";
3801
3503
  import { redactConsoleError, writeMcpAuditRow } from "@/lib/mcp/audit";
3802
3504
  import { runMcpTool } from "@/lib/mcp/tool-runner";
3803
- ${o.useAiChatManifest?`import { TOOLS } from "@/lib/ai/tools/manifest";
3505
+ ${r.useAiChatManifest?`import { TOOLS } from "@/lib/ai/tools/manifest";
3804
3506
  import { toMcpTools } from "@/lib/ai/tools/adapters";
3805
3507
  const MCP_TOOLS = toMcpTools(TOOLS);`:'import { MCP_TOOLS } from "@/lib/mcp/registry";'}
3806
3508
 
@@ -3912,7 +3614,7 @@ export async function POST(req: NextRequest): Promise<Response> {
3912
3614
  return jsonRpcResult(body.id, {
3913
3615
  protocolVersion: MCP_PROTOCOL_VERSION,
3914
3616
  capabilities: { tools: {} },
3915
- serverInfo: { name: "${n}", version: "${r}" },
3617
+ serverInfo: { name: "${n}", version: "${o}" },
3916
3618
  });
3917
3619
 
3918
3620
  case "tools/list":
@@ -3960,7 +3662,7 @@ export function GET(): Response {
3960
3662
  { status: 405, headers: { "Allow": "POST", "content-type": "application/json" } },
3961
3663
  );
3962
3664
  }
3963
- `}function Ai(){return`import { index, integer, jsonb, numeric, pgTable, text, timestamp, uniqueIndex } from "drizzle-orm/pg-core";
3665
+ `}function qi(){return`import { index, integer, jsonb, numeric, pgTable, text, timestamp, uniqueIndex } from "drizzle-orm/pg-core";
3964
3666
 
3965
3667
  export const mcpCaller = pgTable(
3966
3668
  "mcp_caller",
@@ -4009,7 +3711,7 @@ export const mcpCallLog = pgTable(
4009
3711
  authStatusIdx: index("mcp_call_log_auth_status_idx").on(table.authStatus),
4010
3712
  }),
4011
3713
  );
4012
- `}function Ei(){return`import type { z } from "zod";
3714
+ `}function Bi(){return`import type { z } from "zod";
4013
3715
  import { getMcpDb, getMonthlySpendCents, getRateLimitCount, isToolAllowed } from "./caller-auth";
4014
3716
  import { redactConsoleError, writeMcpAuditRow } from "./audit";
4015
3717
  import type { AuthenticatedMcpCaller, McpToolHandler, McpToolResult } from "./types";
@@ -4198,7 +3900,7 @@ export async function runMcpTool<TShape extends z.ZodRawShape>(
4198
3900
  return toolError("handler_error");
4199
3901
  }
4200
3902
  }
4201
- `}var Qd={"@modelcontextprotocol/sdk":"1.0.0","zod-to-json-schema":"^3.24.0",zod:"^3.24.0"};function $r(t){let e=t.appVersion??"0.0.1",o=t.useAiChatManifest===!0;return{id:"mcp",files:[{path:"app/mcp/route.ts",content:Ri(t.appName,e,{useAiChatManifest:o})},{path:"lib/mcp/tool-runner.ts",content:Ei()},...o?[]:[{path:"lib/mcp/registry.ts",content:Ii()}],{path:"lib/mcp/caller-auth.ts",content:Ti()},{path:"lib/mcp/audit.ts",content:Ci()},{path:"lib/mcp/types.ts",content:Pi()}],packageJsonDeps:{...Qd},envVars:{MCP_AUTH_MODE:"dev",ENVIRONMENT:"development"},productionEnvVars:{MCP_AUTH_MODE:"",ENVIRONMENT:"production"},schemaFragment:Ai(),nextConfigRules:{forbidIgnoreBuildErrors:!0}}}import{z as f}from"zod";var Ni=f.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/).transform(t=>t),Xd=f.array(f.enum(["read","write"])).min(1).max(2).superRefine((t,e)=>{new Set(t).size!==t.length&&e.addIssue({code:f.ZodIssueCode.custom,message:"capabilities must contain unique values"});let o=JSON.stringify(t);new Set([JSON.stringify(["read"]),JSON.stringify(["write"]),JSON.stringify(["read","write"])]).has(o)||e.addIssue({code:f.ZodIssueCode.custom,message:"capabilities must be exactly ['read'], ['write'], or ['read', 'write']"})}).transform(t=>t),Zd=f.object({type:f.literal("nango"),authMode:f.literal("oauth"),providerConfigKey:f.string().min(1),syncSupported:f.boolean(),mcpSupported:f.boolean(),connectionConfigId:f.string().min(1).optional()}),eu=f.object({idFormat:f.literal("mf_${appProjectId}_${appOrgId}_${appUserId}_${providerSlug}"),segmentEncoding:f.literal("base64url-or-validated-slug"),mappingTable:f.object({targetFile:f.literal("db/schema.ts"),tableName:f.literal("connected_service_connections"),requiredColumns:f.tuple([f.literal("connection_id"),f.literal("app_project_id"),f.literal("app_org_id"),f.literal("app_user_id"),f.literal("provider_slug"),f.literal("provider_account_id"),f.literal("provider_workspace_id"),f.literal("created_at"),f.literal("updated_at"),f.literal("disconnected_at")])}),uniqueness:f.tuple([f.literal("connection_id"),f.literal("app_project_id,app_org_id,app_user_id,provider_slug")])}),tu=f.object({kind:f.enum(["schema","migration","workers-nango-client","sync-runner","list-records","webhook-route","server-action","connect-component","disconnect-route","mcp-tool","mcp-registry","manifest"]),targetFile:f.string().min(1),source:f.string(),mergeStrategy:f.enum(["append-generated-block","create-file","replace-generated-block","json-merge","ast-array-merge"]),generatedBlockSlug:Ni.optional()}),nu=f.object({packageName:f.string().min(1),versionRange:f.string().min(1),dependencyType:f.enum(["dependency","devDependency"]),runtime:f.enum(["client","worker-server","nango-runner","test"]),importAllowedFrom:f.array(f.string().min(1)).readonly()}),ou=f.object({key:f.string().regex(/^[A-Z0-9_]+$/),description:f.string().min(1),exposure:f.enum(["server-secret","client-public"]),requiredAt:f.enum(["build","runtime"]),setupUrl:f.string().url().optional(),example:f.string().optional()}),ru=f.object({title:f.string().min(1),body:f.string().min(1),link:f.string().url().optional(),requiresUserAction:f.boolean()}),su=f.discriminatedUnion("type",[f.object({type:f.literal("public")}),f.object({type:f.literal("owner")}),f.object({type:f.literal("principal-table"),principalTable:f.string().min(1),requiredBeforeExpose:f.literal(!0),staleDataBehavior:f.literal("fail-closed")})]),iu=f.object({webhook:f.object({route:f.literal("app/api/webhooks/nango/route.ts"),signature:f.literal("nango-hmac-sha256"),rawBodyRequired:f.literal(!0),timestampToleranceSeconds:f.literal(300),replayPrevention:f.object({table:f.literal("connected_service_webhook_events"),key:f.literal("event_id")}),payloadValidation:f.literal("zod"),publicButSigned:f.literal(!0)}),acl:su,logging:f.object({redactHeaders:f.literal(!0),redactTokens:f.literal(!0),redactSecrets:f.literal(!0),redactProviderPayloadText:f.literal(!0)}),audit:f.object({enabled:f.literal(!0),redactRequestBody:f.literal(!0),redactResponseBody:f.literal(!0)})}),au=f.object({targetFile:f.literal("db/schema.ts"),exportedTables:f.array(f.string().min(1)),source:f.string().min(1),mergeStrategy:f.literal("append-generated-block")}),lu=f.object({nangoSyncName:f.string().min(1),nangoModelName:f.string().min(1),targetFile:f.custom(t=>typeof t=="string"&&/^nango-integrations\/.+\.ts$/.test(t)),listRecordsFile:f.custom(t=>typeof t=="string"&&/^lib\/sync\/.+\.ts$/.test(t)),webhookEventTypes:f.array(f.string().min(1)).readonly(),source:f.string().min(1)}),cu=f.object({name:f.string().min(1),inputSchemaExport:f.string().min(1),responseSchemaExport:f.string().min(1),method:f.enum(["GET","POST","PUT","PATCH","DELETE"]),pathTemplate:f.string().startsWith("/"),allowCallerControlledUrl:f.literal(!1).optional(),rateLimitBehavior:f.string().min(1),idempotencyBehavior:f.string().min(1),redactedAuditEventShape:f.string().min(1)}),du=f.object({targetFile:f.custom(t=>typeof t=="string"&&/^lib\/actions\/.+\.ts$/.test(t)),operations:f.array(cu).min(1),source:f.string().min(1)}),Jf=f.object({slug:Ni,version:f.string().min(1),name:f.string().min(1),category:f.literal("connected-service"),capabilities:Xd,provider:Zd,connection:eu,files:f.array(tu),dependencies:f.array(nu),envVars:f.array(ou),setupSteps:f.array(ru),security:iu,schema:au.optional(),sync:lu.optional(),actions:du.optional()}).superRefine((t,e)=>{let o=t.capabilities.includes("read"),n=t.capabilities.includes("write");o&&!t.sync&&e.addIssue({code:f.ZodIssueCode.custom,path:["sync"],message:"read-capable presets must define sync"}),!o&&t.sync&&e.addIssue({code:f.ZodIssueCode.custom,path:["sync"],message:"write-only presets must not define sync"}),n&&!t.actions&&e.addIssue({code:f.ZodIssueCode.custom,path:["actions"],message:"write-capable presets must define actions"}),!n&&t.actions&&e.addIssue({code:f.ZodIssueCode.custom,path:["actions"],message:"read-only presets must not define actions"})});var Mi="nango-base-connect",ji=`import { pgTable, text, timestamp, uniqueIndex } from 'drizzle-orm/pg-core'
3903
+ `}var su={"@modelcontextprotocol/sdk":"1.0.0","zod-to-json-schema":"^3.24.0",zod:"^3.24.0"};function Fo(t){let e=t.appVersion??"0.0.1",r=t.useAiChatManifest===!0;return{id:"mcp",files:[{path:"app/mcp/route.ts",content:Fi(t.appName,e,{useAiChatManifest:r})},{path:"lib/mcp/tool-runner.ts",content:Bi()},...r?[]:[{path:"lib/mcp/registry.ts",content:$i()}],{path:"lib/mcp/caller-auth.ts",content:Li()},{path:"lib/mcp/audit.ts",content:Di()},{path:"lib/mcp/types.ts",content:Ui()}],packageJsonDeps:{...su},envVars:{MCP_AUTH_MODE:"dev",ENVIRONMENT:"development"},productionEnvVars:{MCP_AUTH_MODE:"",ENVIRONMENT:"production"},schemaFragment:qi(),nextConfigRules:{forbidIgnoreBuildErrors:!0}}}import{z as f}from"zod";var Hi=f.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/).transform(t=>t),iu=f.array(f.enum(["read","write"])).min(1).max(2).superRefine((t,e)=>{new Set(t).size!==t.length&&e.addIssue({code:f.ZodIssueCode.custom,message:"capabilities must contain unique values"});let r=JSON.stringify(t);new Set([JSON.stringify(["read"]),JSON.stringify(["write"]),JSON.stringify(["read","write"])]).has(r)||e.addIssue({code:f.ZodIssueCode.custom,message:"capabilities must be exactly ['read'], ['write'], or ['read', 'write']"})}).transform(t=>t),au=f.object({type:f.literal("nango"),authMode:f.literal("oauth"),providerConfigKey:f.string().min(1),syncSupported:f.boolean(),mcpSupported:f.boolean(),connectionConfigId:f.string().min(1).optional()}),lu=f.object({idFormat:f.literal("mf_${appProjectId}_${appOrgId}_${appUserId}_${providerSlug}"),segmentEncoding:f.literal("base64url-or-validated-slug"),mappingTable:f.object({targetFile:f.literal("db/schema.ts"),tableName:f.literal("connected_service_connections"),requiredColumns:f.tuple([f.literal("connection_id"),f.literal("app_project_id"),f.literal("app_org_id"),f.literal("app_user_id"),f.literal("provider_slug"),f.literal("provider_account_id"),f.literal("provider_workspace_id"),f.literal("created_at"),f.literal("updated_at"),f.literal("disconnected_at")])}),uniqueness:f.tuple([f.literal("connection_id"),f.literal("app_project_id,app_org_id,app_user_id,provider_slug")])}),cu=f.object({kind:f.enum(["schema","migration","workers-nango-client","sync-runner","list-records","webhook-route","server-action","connect-component","disconnect-route","mcp-tool","mcp-registry","manifest"]),targetFile:f.string().min(1),source:f.string(),mergeStrategy:f.enum(["append-generated-block","create-file","replace-generated-block","json-merge","ast-array-merge"]),generatedBlockSlug:Hi.optional()}),du=f.object({packageName:f.string().min(1),versionRange:f.string().min(1),dependencyType:f.enum(["dependency","devDependency"]),runtime:f.enum(["client","worker-server","nango-runner","test"]),importAllowedFrom:f.array(f.string().min(1)).readonly()}),uu=f.object({key:f.string().regex(/^[A-Z0-9_]+$/),description:f.string().min(1),exposure:f.enum(["server-secret","client-public"]),requiredAt:f.enum(["build","runtime"]),setupUrl:f.string().url().optional(),example:f.string().optional()}),pu=f.object({title:f.string().min(1),body:f.string().min(1),link:f.string().url().optional(),requiresUserAction:f.boolean()}),mu=f.discriminatedUnion("type",[f.object({type:f.literal("public")}),f.object({type:f.literal("owner")}),f.object({type:f.literal("principal-table"),principalTable:f.string().min(1),requiredBeforeExpose:f.literal(!0),staleDataBehavior:f.literal("fail-closed")})]),hu=f.object({webhook:f.object({route:f.literal("app/api/webhooks/nango/route.ts"),signature:f.literal("nango-hmac-sha256"),rawBodyRequired:f.literal(!0),timestampToleranceSeconds:f.literal(300),replayPrevention:f.object({table:f.literal("connected_service_webhook_events"),key:f.literal("event_id")}),payloadValidation:f.literal("zod"),publicButSigned:f.literal(!0)}),acl:mu,logging:f.object({redactHeaders:f.literal(!0),redactTokens:f.literal(!0),redactSecrets:f.literal(!0),redactProviderPayloadText:f.literal(!0)}),audit:f.object({enabled:f.literal(!0),redactRequestBody:f.literal(!0),redactResponseBody:f.literal(!0)})}),gu=f.object({targetFile:f.literal("db/schema.ts"),exportedTables:f.array(f.string().min(1)),source:f.string().min(1),mergeStrategy:f.literal("append-generated-block")}),fu=f.object({nangoSyncName:f.string().min(1),nangoModelName:f.string().min(1),targetFile:f.custom(t=>typeof t=="string"&&/^nango-integrations\/.+\.ts$/.test(t)),listRecordsFile:f.custom(t=>typeof t=="string"&&/^lib\/sync\/.+\.ts$/.test(t)),webhookEventTypes:f.array(f.string().min(1)).readonly(),source:f.string().min(1)}),yu=f.object({name:f.string().min(1),inputSchemaExport:f.string().min(1),responseSchemaExport:f.string().min(1),method:f.enum(["GET","POST","PUT","PATCH","DELETE"]),pathTemplate:f.string().startsWith("/"),allowCallerControlledUrl:f.literal(!1).optional(),rateLimitBehavior:f.string().min(1),idempotencyBehavior:f.string().min(1),redactedAuditEventShape:f.string().min(1)}),bu=f.object({targetFile:f.custom(t=>typeof t=="string"&&/^lib\/actions\/.+\.ts$/.test(t)),operations:f.array(yu).min(1),source:f.string().min(1)}),fy=f.object({slug:Hi,version:f.string().min(1),name:f.string().min(1),category:f.literal("connected-service"),capabilities:iu,provider:au,connection:lu,files:f.array(cu),dependencies:f.array(du),envVars:f.array(uu),setupSteps:f.array(pu),security:hu,schema:gu.optional(),sync:fu.optional(),actions:bu.optional()}).superRefine((t,e)=>{let r=t.capabilities.includes("read"),n=t.capabilities.includes("write");r&&!t.sync&&e.addIssue({code:f.ZodIssueCode.custom,path:["sync"],message:"read-capable presets must define sync"}),!r&&t.sync&&e.addIssue({code:f.ZodIssueCode.custom,path:["sync"],message:"write-only presets must not define sync"}),n&&!t.actions&&e.addIssue({code:f.ZodIssueCode.custom,path:["actions"],message:"write-capable presets must define actions"}),!n&&t.actions&&e.addIssue({code:f.ZodIssueCode.custom,path:["actions"],message:"read-only presets must not define actions"})});var Wi="nango-base-connect",Gi=`import { pgTable, text, timestamp, uniqueIndex } from 'drizzle-orm/pg-core'
4202
3904
 
4203
3905
  // Per-(project, org, user, provider) Nango connection registry. The Nango
4204
3906
  // connection ID is assigned by Nango itself (NOT pre-generated by us) and
@@ -4249,7 +3951,7 @@ export const connectedServiceSyncCursors = pgTable('connected_service_sync_curso
4249
3951
  table.nango_model_name,
4250
3952
  ),
4251
3953
  }))
4252
- `,pu=`import { redactForConnectedServiceLog } from '@mistflow-ai/runtime/nango'
3954
+ `,vu=`import { redactForConnectedServiceLog } from '@mistflow-ai/runtime/nango'
4253
3955
 
4254
3956
  const NANGO_BASE_URL = process.env.NANGO_API_URL ?? 'https://api.nango.dev'
4255
3957
 
@@ -4414,7 +4116,7 @@ export async function deleteConnection(args: { connectionId: string; providerCon
4414
4116
  throw new NangoApiError('Failed to delete Nango connection', response.status, errBody)
4415
4117
  }
4416
4118
  }
4417
- `,mu=`// Public route \u2014 the auth boundary is the signed Nango HMAC.
4119
+ `,ku=`// Public route \u2014 the auth boundary is the signed Nango HMAC.
4418
4120
  // Verification runs through @mistflow-ai/runtime/nango/verifyNangoWebhook
4419
4121
  // which checks \`X-Nango-Hmac-Sha256\` over the raw body and uses the
4420
4122
  // store's atomic insertEvent for replay prevention.
@@ -4579,7 +4281,7 @@ export async function POST(request: Request): Promise<Response> {
4579
4281
  })
4580
4282
  }
4581
4283
  }
4582
- `,hu=`import { NextRequest, NextResponse } from 'next/server'
4284
+ `,xu=`import { NextRequest, NextResponse } from 'next/server'
4583
4285
  import { createConnectSession } from '@/lib/nango-client'
4584
4286
  import { auth } from '@/lib/auth'
4585
4287
 
@@ -4638,7 +4340,7 @@ export async function POST(request: NextRequest): Promise<Response> {
4638
4340
  )
4639
4341
  }
4640
4342
  }
4641
- `,gu=`"use client"
4343
+ `,Su=`"use client"
4642
4344
 
4643
4345
  // Generic Nango Connect button. Pass \`providers\` to restrict which
4644
4346
  // integrations the user can pick from, or omit to show every provider
@@ -4741,7 +4443,7 @@ export function ConnectButton({
4741
4443
  </button>
4742
4444
  )
4743
4445
  }
4744
- `,fu=`import { NextRequest, NextResponse } from 'next/server'
4446
+ `,_u=`import { NextRequest, NextResponse } from 'next/server'
4745
4447
  import { eq, and, isNull } from 'drizzle-orm'
4746
4448
  import { db } from '@/lib/db'
4747
4449
  import { connectedServiceConnections } from '@/db/schema/connected-services'
@@ -4823,12 +4525,12 @@ export async function POST(request: NextRequest): Promise<Response> {
4823
4525
 
4824
4526
  return new Response(null, { status: 204 })
4825
4527
  }
4826
- `,ko={slug:Mi,version:"1.0.0",name:"Nango Connected Services (generic)",category:"connected-service",capabilities:["read","write"],provider:{type:"nango",authMode:"oauth",providerConfigKey:"*",syncSupported:!0,mcpSupported:!1},connection:{idFormat:"mf_${appProjectId}_${appOrgId}_${appUserId}_${providerSlug}",segmentEncoding:"base64url-or-validated-slug",mappingTable:{targetFile:"db/schema.ts",tableName:"connected_service_connections",requiredColumns:["connection_id","app_project_id","app_org_id","app_user_id","provider_slug","provider_account_id","provider_workspace_id","created_at","updated_at","disconnected_at"]},uniqueness:["connection_id","app_project_id,app_org_id,app_user_id,provider_slug"]},schema:{targetFile:"db/schema.ts",exportedTables:["connected_service_connections","connected_service_webhook_events","connected_service_sync_cursors"],source:ji,mergeStrategy:"append-generated-block"},files:[{kind:"schema",targetFile:"db/schema.ts",source:ji,mergeStrategy:"append-generated-block",generatedBlockSlug:Mi},{kind:"migration",targetFile:"drizzle",source:"connected_service_base",mergeStrategy:"create-file"},{kind:"workers-nango-client",targetFile:"lib/nango-client.ts",source:pu,mergeStrategy:"create-file"},{kind:"webhook-route",targetFile:"app/api/webhooks/nango/route.ts",source:mu,mergeStrategy:"create-file"},{kind:"server-action",targetFile:"app/api/connect-sessions/route.ts",source:hu,mergeStrategy:"create-file"},{kind:"connect-component",targetFile:"components/connect-button.tsx",source:gu,mergeStrategy:"create-file"},{kind:"disconnect-route",targetFile:"app/api/connected-services/disconnect/route.ts",source:fu,mergeStrategy:"create-file"},{kind:"manifest",targetFile:".mistflow/state.json",source:"",mergeStrategy:"json-merge"}],dependencies:[{packageName:"@nangohq/frontend",versionRange:"^0.52.0",dependencyType:"dependency",runtime:"client",importAllowedFrom:["components/connect-button.tsx"]},{packageName:"@mistflow-ai/runtime",versionRange:"^0.1.0",dependencyType:"dependency",runtime:"worker-server",importAllowedFrom:["app/api/webhooks/","lib/nango-client","lib/sync/"]}],envVars:[{key:"NANGO_SECRET_KEY",description:"Nango secret key (server-side). Get from app.nango.dev.",exposure:"server-secret",requiredAt:"runtime",setupUrl:"https://app.nango.dev"},{key:"NANGO_WEBHOOK_SECRET",description:"Webhook signing secret. Get from Nango dashboard \u2192 webhooks.",exposure:"server-secret",requiredAt:"runtime",setupUrl:"https://app.nango.dev"}],setupSteps:[{title:"Sign up for Nango Cloud",body:"Free tier covers small projects. You'll get your secret key + webhook secret.",link:"https://app.nango.dev",requiresUserAction:!0},{title:"Enable providers in Nango",body:"In the Nango dashboard, enable the providers your app needs (Slack, Gmail, etc.). Each provider needs its OAuth credentials configured in Nango.",link:"https://docs.nango.dev/guides/getting-started",requiresUserAction:!0},{title:"Set Mistflow project env vars",body:"Paste NANGO_SECRET_KEY and NANGO_WEBHOOK_SECRET into Mistflow dashboard \u2192 project \u2192 env vars.",requiresUserAction:!0},{title:"Configure Nango webhook URL after deploy",body:"After your first deploy, copy the app URL and paste into Nango dashboard \u2192 webhooks. Webhook path is /api/webhooks/nango.",requiresUserAction:!0}],security:{webhook:{route:"app/api/webhooks/nango/route.ts",signature:"nango-hmac-sha256",rawBodyRequired:!0,timestampToleranceSeconds:300,replayPrevention:{table:"connected_service_webhook_events",key:"event_id"},payloadValidation:"zod",publicButSigned:!0},acl:{type:"owner"},logging:{redactHeaders:!0,redactTokens:!0,redactSecrets:!0,redactProviderPayloadText:!0},audit:{enabled:!0,redactRequestBody:!0,redactResponseBody:!0}}};function Di(t){if(!t)return"Item";let e=t.replace(/([a-z0-9])([A-Z])/g,"$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean);return e.length===0?"Item":e.map(o=>o.charAt(0).toUpperCase()+o.slice(1).toLowerCase()).join("")}function yu(t){return t&&t.replace(/([a-z0-9])([A-Z])/g,"$1-$2").replace(/[^A-Za-z0-9]+/g,"-").toLowerCase().replace(/^-+|-+$/g,"")||"item"}function bu(t){let e=Di(t);return e.charAt(0).toLowerCase()+e.slice(1)}function qr(){return["# Integration contracts","","This directory holds the single source of truth for every API shape in","this app. Frontend code and backend routes both import from here, so","drift becomes a compile error instead of a silent runtime bug.","","## The convention","","Every entity defined in `db/schema.ts` MUST have a matching contract","file in this directory. Each contract file exports three things:","","- `<Entity>Schema` \u2014 the Zod schema for reading the entity (derived from"," the Drizzle table via `createSelectSchema`).","- `Create<Entity>Input` \u2014 the Zod schema for creating a new row"," (derived via `createInsertSchema`, typically with `id` and"," `createdAt` omitted).","- `<Entity>` \u2014 the inferred TypeScript type from `<Entity>Schema`.","","File name is the kebab-case singular of the entity, e.g. `habit.ts`,","`blog-post.ts`. One entity per file.","","## Example","","```ts","// contracts/habit.ts",'import { createSelectSchema, createInsertSchema } from "drizzle-zod";','import { z } from "zod";','import { habit } from "@/db/schema";',"","export const HabitSchema = createSelectSchema(habit);","export type Habit = z.infer<typeof HabitSchema>;","","export const CreateHabitInput = createInsertSchema(habit).omit({"," id: true,"," createdAt: true,","});","export type CreateHabitInput = z.infer<typeof CreateHabitInput>;","```","","## Usage","","Frontend fetch and backend route should BOTH import from `contracts/`:","","```ts","// app/api/habits/route.ts",'import { CreateHabitInput, HabitSchema } from "@/contracts/habit";',"","export async function POST(request: Request) {"," const body = CreateHabitInput.parse(await request.json());"," const row = await db.insert(habit).values(body).returning();"," return Response.json(HabitSchema.parse(row[0]));","}","```","","```ts","// app/habits/page.tsx",'import type { Habit } from "@/contracts/habit";',"",'const res = await fetch("/api/habits");',"const habits: Habit[] = await res.json();","```","","## Adding a new entity","","1. Add the Drizzle table to `db/schema.ts`.","2. Create the matching `contracts/<entity>.ts` file using the template above.","3. Import from `contracts/` in every route, server action, and client"," component that touches the entity. Never inline the type.","","Run `mist_doctor` to check for entities that are missing a contract.",""].join(`
4827
- `)}function Li(){return["<!-- mist:contracts:start -->","## Integration contracts","","Every API shape in this app lives in `contracts/`. The directory holds","Zod schemas derived from `db/schema.ts` via `drizzle-zod`. Frontend and","backend both import from `contracts/`, so type drift becomes a compile","error instead of a runtime bug.","","### Rules for AI agents","","1. Every API route (`app/api/**/route.ts`) and server action MUST"," import its request + response types from `contracts/<entity>.ts`."," Never inline a Zod schema or a TypeScript type for an entity that"," already has a contract. Never `z.object({ ... })` inside a route"," handler for a known entity.","2. When you add a new entity to `db/schema.ts`, you MUST create the"," matching contract file BEFORE writing any route that uses it. Order"," matters: schema -> contract -> route.","3. Validate every request body with the contract's `.parse()` method."," If validation fails, return a 400 with the Zod error message \u2014 the"," contract is the boundary, not a decoration.","4. Validate every response body before returning it. Use the select"," schema's `.parse()` on the row(s) you read from the DB. This catches"," the case where the DB shape has drifted from the contract.","5. Client components that fetch an entity MUST import the inferred",' TypeScript type (`import type { Habit } from "@/contracts/habit"`)'," \u2014 never hand-write a duplicate type.","","### Contract file shape","","```ts","// contracts/<entity>.ts",'import { createSelectSchema, createInsertSchema } from "drizzle-zod";','import { z } from "zod";','import { entityTable } from "@/db/schema";',"","export const EntitySchema = createSelectSchema(entityTable);","export type Entity = z.infer<typeof EntitySchema>;","","export const CreateEntityInput = createInsertSchema(entityTable).omit({"," id: true,"," createdAt: true,","});","export type CreateEntityInput = z.infer<typeof CreateEntityInput>;","```","","### Example route using a contract","","```ts","// app/api/entities/route.ts",'import { db } from "@/lib/db";','import { entityTable } from "@/db/schema";','import { CreateEntityInput, EntitySchema } from "@/contracts/entity";',"","export async function POST(request: Request) {"," const body = CreateEntityInput.parse(await request.json());"," const [row] = await db.insert(entityTable).values(body).returning();"," return Response.json(EntitySchema.parse(row));","}","```","","Run `mist_doctor` to check every entity in `db/schema.ts` has a","contract file. Missing contracts are reported as warnings.","<!-- mist:contracts:end -->",""].join(`
4828
- `)}var Ur="<!-- mist:contracts:start -->",$i="<!-- mist:contracts:end -->";function Fr(t){let e=Li();if(t.includes(Ur)){let n=t.indexOf(Ur),r=$i,s=t.indexOf(r,n);if(s===-1)return t.slice(0,n)+e;let i=t.slice(s+r.length);return t.slice(0,n)+e+i.replace(/^\n+/,"")}return t.replace(/\s+$/,"")+`
4528
+ `,kr={slug:Wi,version:"1.0.0",name:"Nango Connected Services (generic)",category:"connected-service",capabilities:["read","write"],provider:{type:"nango",authMode:"oauth",providerConfigKey:"*",syncSupported:!0,mcpSupported:!1},connection:{idFormat:"mf_${appProjectId}_${appOrgId}_${appUserId}_${providerSlug}",segmentEncoding:"base64url-or-validated-slug",mappingTable:{targetFile:"db/schema.ts",tableName:"connected_service_connections",requiredColumns:["connection_id","app_project_id","app_org_id","app_user_id","provider_slug","provider_account_id","provider_workspace_id","created_at","updated_at","disconnected_at"]},uniqueness:["connection_id","app_project_id,app_org_id,app_user_id,provider_slug"]},schema:{targetFile:"db/schema.ts",exportedTables:["connected_service_connections","connected_service_webhook_events","connected_service_sync_cursors"],source:Gi,mergeStrategy:"append-generated-block"},files:[{kind:"schema",targetFile:"db/schema.ts",source:Gi,mergeStrategy:"append-generated-block",generatedBlockSlug:Wi},{kind:"migration",targetFile:"drizzle",source:"connected_service_base",mergeStrategy:"create-file"},{kind:"workers-nango-client",targetFile:"lib/nango-client.ts",source:vu,mergeStrategy:"create-file"},{kind:"webhook-route",targetFile:"app/api/webhooks/nango/route.ts",source:ku,mergeStrategy:"create-file"},{kind:"server-action",targetFile:"app/api/connect-sessions/route.ts",source:xu,mergeStrategy:"create-file"},{kind:"connect-component",targetFile:"components/connect-button.tsx",source:Su,mergeStrategy:"create-file"},{kind:"disconnect-route",targetFile:"app/api/connected-services/disconnect/route.ts",source:_u,mergeStrategy:"create-file"},{kind:"manifest",targetFile:".mistflow/state.json",source:"",mergeStrategy:"json-merge"}],dependencies:[{packageName:"@nangohq/frontend",versionRange:"^0.52.0",dependencyType:"dependency",runtime:"client",importAllowedFrom:["components/connect-button.tsx"]},{packageName:"@mistflow-ai/runtime",versionRange:"^0.1.0",dependencyType:"dependency",runtime:"worker-server",importAllowedFrom:["app/api/webhooks/","lib/nango-client","lib/sync/"]}],envVars:[{key:"NANGO_SECRET_KEY",description:"Nango secret key (server-side). Get from app.nango.dev.",exposure:"server-secret",requiredAt:"runtime",setupUrl:"https://app.nango.dev"},{key:"NANGO_WEBHOOK_SECRET",description:"Webhook signing secret. Get from Nango dashboard \u2192 webhooks.",exposure:"server-secret",requiredAt:"runtime",setupUrl:"https://app.nango.dev"}],setupSteps:[{title:"Sign up for Nango Cloud",body:"Free tier covers small projects. You'll get your secret key + webhook secret.",link:"https://app.nango.dev",requiresUserAction:!0},{title:"Enable providers in Nango",body:"In the Nango dashboard, enable the providers your app needs (Slack, Gmail, etc.). Each provider needs its OAuth credentials configured in Nango.",link:"https://docs.nango.dev/guides/getting-started",requiresUserAction:!0},{title:"Set Mistflow project env vars",body:"Paste NANGO_SECRET_KEY and NANGO_WEBHOOK_SECRET into Mistflow dashboard \u2192 project \u2192 env vars.",requiresUserAction:!0},{title:"Configure Nango webhook URL after deploy",body:"After your first deploy, copy the app URL and paste into Nango dashboard \u2192 webhooks. Webhook path is /api/webhooks/nango.",requiresUserAction:!0}],security:{webhook:{route:"app/api/webhooks/nango/route.ts",signature:"nango-hmac-sha256",rawBodyRequired:!0,timestampToleranceSeconds:300,replayPrevention:{table:"connected_service_webhook_events",key:"event_id"},payloadValidation:"zod",publicButSigned:!0},acl:{type:"owner"},logging:{redactHeaders:!0,redactTokens:!0,redactSecrets:!0,redactProviderPayloadText:!0},audit:{enabled:!0,redactRequestBody:!0,redactResponseBody:!0}}};function Ji(t){if(!t)return"Item";let e=t.replace(/([a-z0-9])([A-Z])/g,"$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean);return e.length===0?"Item":e.map(r=>r.charAt(0).toUpperCase()+r.slice(1).toLowerCase()).join("")}function Tu(t){return t&&t.replace(/([a-z0-9])([A-Z])/g,"$1-$2").replace(/[^A-Za-z0-9]+/g,"-").toLowerCase().replace(/^-+|-+$/g,"")||"item"}function Cu(t){let e=Ji(t);return e.charAt(0).toLowerCase()+e.slice(1)}function Bo(){return["# Integration contracts","","This directory holds the single source of truth for every API shape in","this app. Frontend code and backend routes both import from here, so","drift becomes a compile error instead of a silent runtime bug.","","## The convention","","Every entity defined in `db/schema.ts` MUST have a matching contract","file in this directory. Each contract file exports three things:","","- `<Entity>Schema` \u2014 the Zod schema for reading the entity (derived from"," the Drizzle table via `createSelectSchema`).","- `Create<Entity>Input` \u2014 the Zod schema for creating a new row"," (derived via `createInsertSchema`, typically with `id` and"," `createdAt` omitted).","- `<Entity>` \u2014 the inferred TypeScript type from `<Entity>Schema`.","","File name is the kebab-case singular of the entity, e.g. `habit.ts`,","`blog-post.ts`. One entity per file.","","## Example","","```ts","// contracts/habit.ts",'import { createSelectSchema, createInsertSchema } from "drizzle-zod";','import { z } from "zod";','import { habit } from "@/db/schema";',"","export const HabitSchema = createSelectSchema(habit);","export type Habit = z.infer<typeof HabitSchema>;","","export const CreateHabitInput = createInsertSchema(habit).omit({"," id: true,"," createdAt: true,","});","export type CreateHabitInput = z.infer<typeof CreateHabitInput>;","```","","## Usage","","Frontend fetch and backend route should BOTH import from `contracts/`:","","```ts","// app/api/habits/route.ts",'import { CreateHabitInput, HabitSchema } from "@/contracts/habit";',"","export async function POST(request: Request) {"," const body = CreateHabitInput.parse(await request.json());"," const row = await db.insert(habit).values(body).returning();"," return Response.json(HabitSchema.parse(row[0]));","}","```","","```ts","// app/habits/page.tsx",'import type { Habit } from "@/contracts/habit";',"",'const res = await fetch("/api/habits");',"const habits: Habit[] = await res.json();","```","","## Adding a new entity","","1. Add the Drizzle table to `db/schema.ts`.","2. Create the matching `contracts/<entity>.ts` file using the template above.","3. Import from `contracts/` in every route, server action, and client"," component that touches the entity. Never inline the type.","","Run `mist_doctor` to check for entities that are missing a contract.",""].join(`
4529
+ `)}function Ki(){return["<!-- mist:contracts:start -->","## Integration contracts","","Every API shape in this app lives in `contracts/`. The directory holds","Zod schemas derived from `db/schema.ts` via `drizzle-zod`. Frontend and","backend both import from `contracts/`, so type drift becomes a compile","error instead of a runtime bug.","","### Rules for AI agents","","1. Every API route (`app/api/**/route.ts`) and server action MUST"," import its request + response types from `contracts/<entity>.ts`."," Never inline a Zod schema or a TypeScript type for an entity that"," already has a contract. Never `z.object({ ... })` inside a route"," handler for a known entity.","2. When you add a new entity to `db/schema.ts`, you MUST create the"," matching contract file BEFORE writing any route that uses it. Order"," matters: schema -> contract -> route.","3. Validate every request body with the contract's `.parse()` method."," If validation fails, return a 400 with the Zod error message \u2014 the"," contract is the boundary, not a decoration.","4. Validate every response body before returning it. Use the select"," schema's `.parse()` on the row(s) you read from the DB. This catches"," the case where the DB shape has drifted from the contract.","5. Client components that fetch an entity MUST import the inferred",' TypeScript type (`import type { Habit } from "@/contracts/habit"`)'," \u2014 never hand-write a duplicate type.","","### Contract file shape","","```ts","// contracts/<entity>.ts",'import { createSelectSchema, createInsertSchema } from "drizzle-zod";','import { z } from "zod";','import { entityTable } from "@/db/schema";',"","export const EntitySchema = createSelectSchema(entityTable);","export type Entity = z.infer<typeof EntitySchema>;","","export const CreateEntityInput = createInsertSchema(entityTable).omit({"," id: true,"," createdAt: true,","});","export type CreateEntityInput = z.infer<typeof CreateEntityInput>;","```","","### Example route using a contract","","```ts","// app/api/entities/route.ts",'import { db } from "@/lib/db";','import { entityTable } from "@/db/schema";','import { CreateEntityInput, EntitySchema } from "@/contracts/entity";',"","export async function POST(request: Request) {"," const body = CreateEntityInput.parse(await request.json());"," const [row] = await db.insert(entityTable).values(body).returning();"," return Response.json(EntitySchema.parse(row));","}","```","","Run `mist_doctor` to check every entity in `db/schema.ts` has a","contract file. Missing contracts are reported as warnings.","<!-- mist:contracts:end -->",""].join(`
4530
+ `)}var qo="<!-- mist:contracts:start -->",Vi="<!-- mist:contracts:end -->";function Ho(t){let e=Ki();if(t.includes(qo)){let n=t.indexOf(qo),o=Vi,s=t.indexOf(o,n);if(s===-1)return t.slice(0,n)+e;let i=t.slice(s+o.length);return t.slice(0,n)+e+i.replace(/^\n+/,"")}return t.replace(/\s+$/,"")+`
4829
4531
 
4830
- `+e}function Br(t,e){let o=Di(t),n=e??bu(t);return['import { createSelectSchema, createInsertSchema } from "drizzle-zod";','import { z } from "zod";',`import { ${n} } from "@/db/schema";`,"","// Select schema \u2014 shape of a row read from the DB. Use this on both","// sides of the wire: backend validates responses, frontend gets types.",`export const ${o}Schema = createSelectSchema(${n});`,`export type ${o} = z.infer<typeof ${o}Schema>;`,"","// Insert schema \u2014 shape accepted when creating a new row. id +","// createdAt are generated server-side, so we strip them from the","// input contract. Adjust if your schema uses different column names.",`export const Create${o}Input = createInsertSchema(${n}).omit({`," id: true,"," createdAt: true,","});",`export type Create${o}Input = z.infer<typeof Create${o}Input>;`,""].join(`
4831
- `)}function Hr(t){return`contracts/${yu(t)}.ts`}function _u(t){let e=pe(xu(),".mistflow","plans",`${t}.json`);if(!Be(e))return null;try{let o=JSON.parse(sn(e,"utf-8"));return o.plan?{plan:o.plan,...typeof o.scaffoldTargetPath=="string"?{scaffoldTargetPath:o.scaffoldTargetPath}:{}}:null}catch{return null}}var Tu=`## Project plan (PLAN.md)
4532
+ `+e}function zo(t,e){let r=Ji(t),n=e??Cu(t);return['import { createSelectSchema, createInsertSchema } from "drizzle-zod";','import { z } from "zod";',`import { ${n} } from "@/db/schema";`,"","// Select schema \u2014 shape of a row read from the DB. Use this on both","// sides of the wire: backend validates responses, frontend gets types.",`export const ${r}Schema = createSelectSchema(${n});`,`export type ${r} = z.infer<typeof ${r}Schema>;`,"","// Insert schema \u2014 shape accepted when creating a new row. id +","// createdAt are generated server-side, so we strip them from the","// input contract. Adjust if your schema uses different column names.",`export const Create${r}Input = createInsertSchema(${n}).omit({`," id: true,"," createdAt: true,","});",`export type Create${r}Input = z.infer<typeof Create${r}Input>;`,""].join(`
4533
+ `)}function Wo(t){return`contracts/${Tu(t)}.ts`}function Nu(t){let e=me(Au(),".mistflow","plans",`${t}.json`);if(!Be(e))return null;try{let r=JSON.parse(ln(e,"utf-8"));return r.plan?{plan:r.plan,...typeof r.scaffoldTargetPath=="string"?{scaffoldTargetPath:r.scaffoldTargetPath}:{}}:null}catch{return null}}var Mu=`## Project plan (PLAN.md)
4832
4534
 
4833
4535
  This project ships with a \`PLAN.md\` at the root \u2014 a PRD-style
4834
4536
  projection of the plan with the data model, pages, steps, and per-step
@@ -4849,41 +4551,36 @@ control plane.
4849
4551
  To change the plan (new feature, missed requirement, scope shift),
4850
4552
  call \`mist_plan\` with \`modify: true\` and a description. The backend
4851
4553
  generates a new plan revision and regenerates PLAN.md.
4852
- `;function Iu(t){let e=Ln(Bi(t)),o=10,n=0;for(;n<o&&e!==Ln(e);){if(Be(pe(e,"pnpm-workspace.yaml"))||Be(pe(e,"lerna.json"))||Be(pe(e,"pnpm-lock.yaml"))||Be(pe(e,"package-lock.json"))||Be(pe(e,"yarn.lock"))||Be(pe(e,"bun.lockb"))||Be(pe(e,"bun.lock")))return e;let r=pe(e,"package.json");if(Be(r))try{if(JSON.parse(sn(r,"utf-8")).workspaces)return e}catch{}e=Ln(e),n++}return null}var Pu=on.object({name:on.string().min(1).optional().describe("Human-readable app name (e.g. 'Task Flow'). Optional when planId is provided \u2014 the cached plan's name is used."),plan:on.any().optional().describe("Full plan object. Optional when planId is provided \u2014 the tool loads the cached plan from ~/.mistflow/plans/<planId>.json."),path:on.string().optional().describe("Absolute path where the project should be scaffolded."),planId:on.string().optional().describe("Plan ID from a prior mist_plan call. When present, the full plan is loaded from disk if 'plan' is omitted."),sessionId:on.string().uuid().optional().describe("Backend session ID. Pass through from mist_plan response so state guards apply and session state advances as the tool runs.")});function M(t,e,o){let n=pe(t,e);rn(Ln(n),{recursive:!0}),dt(n,o)}var xo="# Mistflow project\n\nThis is a Mistflow-generated app. The full conventions, stack guide, and\nintegration contracts are in `AGENTS.md` (universal) and `CLAUDE.md`\n(identical copy for Claude Code).\n\nThe current build plan is in `PLAN.md` \u2014 read it for the data model,\npages, steps, and acceptance criteria. The plan is render-only; do\nnot hand-edit step status or check boxes. Step status lives in\n`mistflow.json` and is updated by `mist_implement` automatically.\n\nTo change the plan, call `mist_plan` with `modify: true` \u2014 never edit\nPLAN.md's structure manually.\n",Ru=`---
4554
+ `;function ju(t){let e=$n(ea(t)),r=10,n=0;for(;n<r&&e!==$n(e);){if(Be(me(e,"pnpm-workspace.yaml"))||Be(me(e,"lerna.json"))||Be(me(e,"pnpm-lock.yaml"))||Be(me(e,"package-lock.json"))||Be(me(e,"yarn.lock"))||Be(me(e,"bun.lockb"))||Be(me(e,"bun.lock")))return e;let o=me(e,"package.json");if(Be(o))try{if(JSON.parse(ln(o,"utf-8")).workspaces)return e}catch{}e=$n(e),n++}return null}var Du=sn.object({name:sn.string().min(1).optional().describe("Human-readable app name (e.g. 'Task Flow'). Optional when planId is provided \u2014 the cached plan's name is used."),plan:sn.any().optional().describe("Full plan object. Optional when planId is provided \u2014 the tool loads the cached plan from ~/.mistflow/plans/<planId>.json."),path:sn.string().optional().describe("Absolute path where the project should be scaffolded."),planId:sn.string().optional().describe("Plan ID from a prior mist_plan call. When present, the full plan is loaded from disk if 'plan' is omitted."),sessionId:sn.string().uuid().optional().describe("Backend session ID. Pass through from mist_plan response so state guards apply and session state advances as the tool runs.")});function A(t,e,r){let n=me(t,e);an($n(n),{recursive:!0}),gt(n,r)}var xr="# Mistflow project\n\nThis is a Mistflow-generated app. The full conventions, stack guide, and\nintegration contracts are in `AGENTS.md` (universal) and `CLAUDE.md`\n(identical copy for Claude Code).\n\nThe current build plan is in `PLAN.md` \u2014 read it for the data model,\npages, steps, and acceptance criteria. The plan is render-only; do\nnot hand-edit step status or check boxes. Step status lives in\n`mistflow.json` and is updated by `mist_implement` automatically.\n\nTo change the plan, call `mist_plan` with `modify: true` \u2014 never edit\nPLAN.md's structure manually.\n",Lu=`---
4853
4555
  description: Mistflow project conventions \u2014 read AGENTS.md + PLAN.md first
4854
4556
  alwaysApply: false
4855
4557
  ---
4856
4558
 
4857
- ${xo}`,Au=`# VS Code Copilot instructions
4559
+ ${xr}`,$u=`# VS Code Copilot instructions
4858
4560
 
4859
4561
  This is the project's primary instruction file for VS Code Copilot.
4860
4562
 
4861
- ${xo}`,Eu=`read: ["CONVENTIONS.md", "AGENTS.md", "PLAN.md"]
4862
- `;function Nu(t,e){M(t,".cursor/rules/mistflow.mdc",Ru),M(t,".github/copilot-instructions.md",Au),M(t,".continue/rules/mistflow.md",xo),M(t,"CONVENTIONS.md",xo),M(t,".aider.conf.yml",Eu)}function Ou(t){if(!t.startsWith(`---
4563
+ ${xr}`,Uu=`read: ["CONVENTIONS.md", "AGENTS.md", "PLAN.md"]
4564
+ `;function Fu(t,e){A(t,".cursor/rules/mistflow.mdc",Lu),A(t,".github/copilot-instructions.md",$u),A(t,".continue/rules/mistflow.md",xr),A(t,"CONVENTIONS.md",xr),A(t,".aider.conf.yml",Uu)}function qu(t){if(!t.startsWith(`---
4863
4565
  `))return{frontmatter:null,body:t};let e=t.indexOf(`
4864
4566
  ---
4865
- `,4);if(e<0)return{frontmatter:null,body:t};let o=t.slice(4,e),n={};for(let r of o.split(`
4866
- `)){let s=r.match(/^([A-Za-z][A-Za-z0-9_-]*):\s*(.*)$/);if(!s)continue;let[,i,a]=s;(i==="name"||i==="description")&&(n[i]=a.trim())}return{frontmatter:n,body:t.slice(e+5).replace(/^\n+/,"")}}function Mu(t,e){for(let[o,n]of Object.entries(e)){if(!n||n.trim().length===0)continue;let{frontmatter:r,body:s}=Ou(n),i=r?.name??o,a=r?.description??`Mistflow skill: ${o}`,l=`---
4567
+ `,4);if(e<0)return{frontmatter:null,body:t};let r=t.slice(4,e),n={};for(let o of r.split(`
4568
+ `)){let s=o.match(/^([A-Za-z][A-Za-z0-9_-]*):\s*(.*)$/);if(!s)continue;let[,i,a]=s;(i==="name"||i==="description")&&(n[i]=a.trim())}return{frontmatter:n,body:t.slice(e+5).replace(/^\n+/,"")}}function Bu(t,e){for(let[r,n]of Object.entries(e)){if(!n||n.trim().length===0)continue;let{frontmatter:o,body:s}=qu(n),i=o?.name??r,a=o?.description??`Mistflow skill: ${r}`,l=`---
4867
4569
  name: ${i}
4868
4570
  description: ${a}
4869
4571
  ---
4870
4572
 
4871
- `;M(t,`.claude/skills/${o}/SKILL.md`,l+s);let c=`---
4573
+ `;A(t,`.claude/skills/${r}/SKILL.md`,l+s);let c=`---
4872
4574
  description: ${a}
4873
4575
  alwaysApply: false
4874
4576
  ---
4875
4577
 
4876
- `;M(t,`.cursor/rules/${o}.mdc`,c+s)}}function ju(t){if(!Be(t))return!0;let e;try{e=Fi(t)}catch{return!1}return e.filter(n=>n!==".mistflow").length===0}function Du(t,e){let o=(e??"").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40)||"new-app",n=pe(t,o);return`${t} isn't empty, so we can't scaffold the project here without clobbering existing files. Retry mist_init with path: "${n}" (a subdirectory) \u2014 the user almost certainly meant "create a new app inside this folder," not "overwrite this folder." Confirm with the user once if you're not sure, but the suggested sub-path is usually right. (A leftover .mistflow/ from planning is fine \u2014 init preserves it.)`}var Lu={sharp:"0.125rem",subtle:"0.375rem",rounded:"0.75rem",pill:"9999px"},$u=[["--color-background","#ffffff"],["--color-foreground","#0a0a0a"],["--color-card","#ffffff"],["--color-card-foreground","#0a0a0a"],["--color-popover","#ffffff"],["--color-popover-foreground","#0a0a0a"],["--color-muted","#f5f5f5"],["--color-muted-foreground","#525252"],["--color-border","#e5e5e5"],["--color-input","#e5e5e5"],["--color-primary","#0a0a0a"],["--color-primary-foreground","#ffffff"],["--color-ring","#0a0a0a"],["--color-secondary","#f5f5f5"],["--color-secondary-foreground","#0a0a0a"],["--color-accent","#f5f5f5"],["--color-accent-foreground","#0a0a0a"],["--color-destructive","#b91c1c"],["--color-destructive-foreground","#ffffff"],["--color-success","#15803d"],["--color-success-foreground","#ffffff"],["--color-warning","#a16207"],["--color-warning-foreground","#ffffff"],["--color-info","#0369a1"],["--color-info-foreground","#ffffff"]];function Uu(t){let e=t.match(/^---\n([\s\S]*?)\n---/);if(!e)return null;let o=e[1],n={theme:"light",colors:{},typography:{},rounded:{},spacing:{}},r=o.split(`
4877
- `),s=null,i=null,a=l=>l.replace(/^["']|["']$/g,"").trim();for(let l of r){let c=l.replace(/\r$/,"");if(!c.trim()||c.trim().startsWith("#"))continue;if(c.startsWith("theme:")){let p=a(c.slice(6).trim());(p==="dark"||p==="light")&&(n.theme=p);continue}let m=c.match(/^(colors|typography|rounded|spacing):\s*$/);if(m){s=m[1],i=null;continue}if(c.length>0&&!c.startsWith(" ")&&!c.startsWith(" ")&&c.match(/^[a-zA-Z0-9_-]+:(\s|$)/)){s=null,i=null;continue}if(s==="typography"){let p=c.match(/^ {2}([a-zA-Z0-9_-]+):\s*$/);if(p){i=p[1].replace(/-/g,"_"),n.typography[i]={};continue}let h=c.match(/^ {4}([a-zA-Z0-9_-]+):\s*(.+)$/);if(h&&i){n.typography[i][h[1]]=a(h[2]);continue}}let u=c.match(/^ {2}([a-zA-Z0-9_-]+):\s*(.+)$/);if(u&&s){let p=u[1].replace(/-/g,"_"),h=a(u[2]);s==="colors"?n.colors[p]=h:s==="rounded"?n.rounded[p]=h:s==="spacing"&&(n.spacing[p]=h)}}return n}function qu(t,e){let o=t.colors,r=[["--color-background",o.background],["--color-foreground",o.on_background??o.on_surface],["--color-card",o.surface??o.background],["--color-card-foreground",o.on_surface??o.on_background],["--color-popover",o.surface??o.background],["--color-popover-foreground",o.on_surface??o.on_background],["--color-muted",o.surface_variant??o.outline_variant??o.outline],["--color-muted-foreground",o.on_surface_variant??o.outline],["--color-border",o.outline_variant??o.outline],["--color-input",o.outline_variant??o.outline],["--color-primary",o.primary],["--color-primary-foreground",o.on_primary],["--color-ring",o.primary],["--color-secondary",o.secondary],["--color-secondary-foreground",o.on_secondary],["--color-accent",o.secondary],["--color-accent-foreground",o.on_secondary],["--color-destructive",o.error],["--color-destructive-foreground",o.on_error],["--color-success",o.success],["--color-success-foreground",o.on_success],["--color-warning",o.warning],["--color-warning-foreground",o.on_warning],["--color-info",o.info??o.primary],["--color-info-foreground",o.on_info??o.on_primary]].filter(a=>typeof a[1]=="string").map(([a,l])=>` ${a}: ${l};`).join(`
4878
- `),s=[` --radius-sm: ${t.rounded.sm??"0.25rem"};`,` --radius-md: ${t.rounded.md??e};`,` --radius-lg: ${t.rounded.lg??"0.5rem"};`,` --radius-xl: ${t.rounded.xl??"0.75rem"};`].join(`
4879
- `);return`@import "tailwindcss";
4880
- @import "tw-animate-css";
4881
-
4882
- @theme {
4883
- ${r}
4884
- ${s}
4885
- }
4886
-
4578
+ `;A(t,`.cursor/rules/${r}.mdc`,c+s)}}function Hu(t){if(!Be(t))return!0;let e;try{e=Zi(t)}catch{return!1}return e.filter(n=>n!==".mistflow").length===0}function zu(t,e){let r=(e??"").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40)||"new-app",n=me(t,r);return`${t} isn't empty, so we can't scaffold the project here without clobbering existing files. Retry mist_init with path: "${n}" (a subdirectory) \u2014 the user almost certainly meant "create a new app inside this folder," not "overwrite this folder." Confirm with the user once if you're not sure, but the suggested sub-path is usually right. (A leftover .mistflow/ from planning is fine \u2014 init preserves it.)`}var Wu={sharp:"0.125rem",subtle:"0.375rem",rounded:"0.75rem",pill:"9999px"},Gu=[["--color-background","#ffffff"],["--color-foreground","#0a0a0a"],["--color-card","#ffffff"],["--color-card-foreground","#0a0a0a"],["--color-popover","#ffffff"],["--color-popover-foreground","#0a0a0a"],["--color-muted","#f5f5f5"],["--color-muted-foreground","#525252"],["--color-border","#e5e5e5"],["--color-input","#e5e5e5"],["--color-primary","#0a0a0a"],["--color-primary-foreground","#ffffff"],["--color-ring","#0a0a0a"],["--color-secondary","#f5f5f5"],["--color-secondary-foreground","#0a0a0a"],["--color-accent","#f5f5f5"],["--color-accent-foreground","#0a0a0a"],["--color-destructive","#b91c1c"],["--color-destructive-foreground","#ffffff"],["--color-success","#15803d"],["--color-success-foreground","#ffffff"],["--color-warning","#a16207"],["--color-warning-foreground","#ffffff"],["--color-info","#0369a1"],["--color-info-foreground","#ffffff"]];function Go(t){if(!t)return!1;let e=t.trim().replace(/^#/,"");if(!/^[0-9a-fA-F]{6}$/.test(e))return!1;let r=parseInt(e.slice(0,2),16)/255,n=parseInt(e.slice(2,4),16)/255,o=parseInt(e.slice(4,6),16)/255,s=a=>a<=.03928?a/12.92:((a+.055)/1.055)**2.4;return .2126*s(r)+.7152*s(n)+.0722*s(o)<.35}function Ju(t,e){if(!e)return;let r=e.colors&&typeof e.colors=="object"?e.colors:{},n=e.fonts&&typeof e.fonts=="object"?e.fonts:{},o=typeof r.bg=="string"?r.bg:"#ffffff",s=typeof r.fg=="string"?r.fg:Go(o)?"#fafafa":"#111111",i=typeof r.accent=="string"?r.accent:"#0a0a0a",a=typeof n.display=="string"?n.display:"Fraunces",l=typeof n.body=="string"?n.body:"DM Sans",c=typeof e.name=="string"?e.name:`${t} Direction`,p=typeof e.summary=="string"?e.summary:"",d=typeof e.color_mood=="string"?e.color_mood:typeof e.colorMood=="string"?e.colorMood:"",m=typeof e.decoration_hint=="string"?e.decoration_hint:typeof e.decorationHint=="string"?e.decorationHint:"Use the picked direction's typography, colors, and composition exactly.",h=e.shape_lang==="sharp"||e.shapeLang==="sharp"?{sm:"0px",md:"0px",lg:"0px",xl:"0px"}:e.shape_lang==="pill"||e.shapeLang==="pill"?{sm:"9999px",md:"9999px",lg:"9999px",xl:"9999px"}:{sm:"4px",md:"8px",lg:"12px",xl:"16px"},S=Go(o)?"dark":"light",I=S==="dark"?"#142936":"#ffffff",y=S==="dark"?"#1A3544":"#f4f4f5",v=S==="dark"?"rgba(237,243,245,0.18)":"#d4d4d8",E=S==="dark"?"rgba(237,243,245,0.10)":"#e4e4e7",J=Go(i)?"#ffffff":"#0a0a0a";return["---",`name: ${JSON.stringify(c)}`,`theme: ${S}`,"colors:",` primary: ${JSON.stringify(i)}`,` on-primary: ${JSON.stringify(J)}`,` secondary: ${JSON.stringify(y)}`,` on-secondary: ${JSON.stringify(s)}`,` background: ${JSON.stringify(o)}`,` on-background: ${JSON.stringify(s)}`,` surface: ${JSON.stringify(I)}`,` on-surface: ${JSON.stringify(s)}`,` surface-variant: ${JSON.stringify(y)}`,` on-surface-variant: ${JSON.stringify(s)}`,` outline: ${JSON.stringify(v)}`,` outline-variant: ${JSON.stringify(E)}`,' error: "#dc2626"',' on-error: "#ffffff"',' success: "#16a34a"',' on-success: "#ffffff"',' warning: "#ca8a04"',' on-warning: "#0a0a0a"',' info: "#2563eb"',' on-info: "#ffffff"',"typography:"," display-lg:",` fontFamily: ${JSON.stringify(a)}`,' fontSize: "clamp(48px, 7vw, 96px)"',' fontWeight: "700"',' lineHeight: "1.05"'," headline-lg:",` fontFamily: ${JSON.stringify(a)}`,' fontSize: "clamp(32px, 4vw, 56px)"',' fontWeight: "700"',' lineHeight: "1.1"'," headline-md:",` fontFamily: ${JSON.stringify(a)}`,' fontSize: "clamp(24px, 3vw, 36px)"',' fontWeight: "600"',' lineHeight: "1.2"'," body-lg:",` fontFamily: ${JSON.stringify(l)}`,' fontSize: "18px"',' fontWeight: "400"',' lineHeight: "1.6"'," body-md:",` fontFamily: ${JSON.stringify(l)}`,' fontSize: "16px"',' fontWeight: "400"',' lineHeight: "1.55"'," label-sm:",` fontFamily: ${JSON.stringify(l)}`,' fontSize: "13px"',' fontWeight: "500"',' lineHeight: "1.4"',"rounded:",` sm: ${JSON.stringify(h.sm)}`,` md: ${JSON.stringify(h.md)}`,` lg: ${JSON.stringify(h.lg)}`,` xl: ${JSON.stringify(h.xl)}`,"spacing:",' unit: "4px"',' container-padding: "24px"',' card-gap: "16px"',' section-margin: "96px"',"---","",`# ${c}`,"",p||`${c} is the selected creative direction for ${t}.`,"",`Color mood: ${d||`background ${o}, foreground ${s}, accent ${i}`}.`,"","Authenticated app surfaces default to a light workspace adaptation unless the user explicitly chooses a dark app interface. Use the picked direction for landing identity; keep dashboards, lists, tables, forms, and settings light, dense, and work-focused.","",`Signature: ${m}`,""].join(`
4579
+ `)}function Ku(t){let e=t.match(/^---\n([\s\S]*?)\n---/);if(!e)return null;let r=e[1],n={theme:"light",colors:{},typography:{},rounded:{},spacing:{}},o=r.split(`
4580
+ `),s=null,i=null,a=l=>l.replace(/^["']|["']$/g,"").trim();for(let l of o){let c=l.replace(/\r$/,"");if(!c.trim()||c.trim().startsWith("#"))continue;if(c.startsWith("theme:")){let m=a(c.slice(6).trim());(m==="dark"||m==="light")&&(n.theme=m);continue}let p=c.match(/^(colors|typography|rounded|spacing):\s*$/);if(p){s=p[1],i=null;continue}if(c.length>0&&!c.startsWith(" ")&&!c.startsWith(" ")&&c.match(/^[a-zA-Z0-9_-]+:(\s|$)/)){s=null,i=null;continue}if(s==="typography"){let m=c.match(/^ {2}([a-zA-Z0-9_-]+):\s*$/);if(m){i=m[1].replace(/-/g,"_"),n.typography[i]={};continue}let h=c.match(/^ {4}([a-zA-Z0-9_-]+):\s*(.+)$/);if(h&&i){n.typography[i][h[1]]=a(h[2]);continue}}let d=c.match(/^ {2}([a-zA-Z0-9_-]+):\s*(.+)$/);if(d&&s){let m=d[1].replace(/-/g,"_"),h=a(d[2]);s==="colors"?n.colors[m]=h:s==="rounded"?n.rounded[m]=h:s==="spacing"&&(n.spacing[m]=h)}}return n}function Vu(t,e){let r=t.colors,n=t.theme==="dark"?{...r,background:"#fafafa",on_background:"#111827",surface:"#ffffff",on_surface:"#111827",surface_variant:"#f4f4f5",on_surface_variant:"#52525b",outline:"#d4d4d8",outline_variant:"#e4e4e7",secondary:"#f4f4f5",on_secondary:"#18181b",tertiary:r.tertiary??"#eef2f2",on_tertiary:r.on_tertiary??"#111827"}:r,s=[["--color-background",n.background],["--color-foreground",n.on_background??n.on_surface],["--color-card",n.surface??n.background],["--color-card-foreground",n.on_surface??n.on_background],["--color-popover",n.surface??n.background],["--color-popover-foreground",n.on_surface??n.on_background],["--color-muted",n.surface_variant??n.outline_variant??n.outline],["--color-muted-foreground",n.on_surface_variant??n.outline],["--color-border",n.outline_variant??n.outline],["--color-input",n.outline_variant??n.outline],["--color-primary",n.primary],["--color-primary-foreground",n.on_primary],["--color-ring",n.primary],["--color-secondary",n.secondary],["--color-secondary-foreground",n.on_secondary],["--color-accent",n.secondary],["--color-accent-foreground",n.on_secondary],["--color-destructive",n.error],["--color-destructive-foreground",n.on_error],["--color-success",n.success],["--color-success-foreground",n.on_success],["--color-warning",n.warning],["--color-warning-foreground",n.on_warning],["--color-info",n.info??n.primary],["--color-info-foreground",n.on_info??n.on_primary]].filter(c=>typeof c[1]=="string").map(([c,p])=>` ${c}: ${p};`).join(`
4581
+ `),i=[` --radius-sm: ${t.rounded.sm??"0.25rem"};`,` --radius-md: ${t.rounded.md??e};`,` --radius-lg: ${t.rounded.lg??"0.5rem"};`,` --radius-xl: ${t.rounded.xl??"0.75rem"};`].join(`
4582
+ `),a=[["--landing-background",r.background],["--landing-foreground",r.on_background??r.on_surface],["--landing-surface",r.surface??r.background],["--landing-surface-variant",r.surface_variant],["--landing-accent",r.primary],["--landing-accent-foreground",r.on_primary],["--landing-outline",r.outline]].filter(c=>typeof c[1]=="string").map(([c,p])=>` ${c}: ${p};`).join(`
4583
+ `),l=`
4887
4584
 
4888
4585
  @layer base {
4889
4586
  * { border-color: var(--color-border); }
@@ -4891,6 +4588,8 @@ ${s}
4891
4588
  }
4892
4589
 
4893
4590
  :root {
4591
+ ${a?`${a}
4592
+ `:""} --app-surface-mode: light-workspace;
4894
4593
  --ease-quart-out: cubic-bezier(0.25, 1, 0.5, 1);
4895
4594
  --ease-expo-out: cubic-bezier(0.16, 1, 0.3, 1);
4896
4595
  --ease-back-out: cubic-bezier(0.34, 1.56, 0.64, 1);
@@ -4931,11 +4630,18 @@ ${s}
4931
4630
 
4932
4631
  button, [role="button"] { transition: transform 100ms var(--ease-quart-out); }
4933
4632
  button:active:not(:disabled), [role="button"]:active:not(:disabled) { transform: scale(0.97); }
4934
- `}function Fu(t,e){let o=t?.borderRadius??"subtle",n=Lu[o]??"0.375rem";if(e){let s=Uu(e);if(s)return qu(s,n)}return`@import "tailwindcss";
4633
+ `;return`@import "tailwindcss";
4634
+ @import "tw-animate-css";
4635
+
4636
+ @theme {
4637
+ ${s}
4638
+ ${i}
4639
+ }
4640
+ ${l}`}function Yi(t,e){let r=t?.borderRadius??"subtle",n=Wu[r]??"0.375rem";if(e){let s=Ku(e);if(s)return Vu(s,n)}return`@import "tailwindcss";
4935
4641
  @import "tw-animate-css";
4936
4642
 
4937
4643
  @theme {
4938
- ${[...$u.map(([s,i])=>` ${s}: ${i};`)," --radius-sm: 0.25rem;",` --radius-md: ${n};`," --radius-lg: 0.5rem;"," --radius-xl: 0.75rem;"].join(`
4644
+ ${[...Gu.map(([s,i])=>` ${s}: ${i};`)," --radius-sm: 0.25rem;",` --radius-md: ${n};`," --radius-lg: 0.5rem;"," --radius-xl: 0.75rem;"].join(`
4939
4645
  `)}
4940
4646
  }
4941
4647
 
@@ -4985,7 +4691,7 @@ ${[...$u.map(([s,i])=>` ${s}: ${i};`)," --radius-sm: 0.25rem;",` --radius-md:
4985
4691
 
4986
4692
  button, [role="button"] { transition: transform 100ms var(--ease-quart-out); }
4987
4693
  button:active:not(:disabled), [role="button"]:active:not(:disabled) { transform: scale(0.97); }
4988
- `}var Ui={"Fredoka One":"Fredoka","Source Sans Pro":"Source_Sans_3","Source Serif Pro":"Source_Serif_4","Open Sans Condensed":"Open_Sans","Baloo 2":"Baloo_2","DM Serif Display":"DM_Serif_Display","DM Serif Text":"DM_Serif_Text","IBM Plex Mono":"IBM_Plex_Mono","IBM Plex Sans":"IBM_Plex_Sans","IBM Plex Serif":"IBM_Plex_Serif","Fira Code":"Fira_Code","Fira Sans":"Fira_Sans","Noto Sans JP":"Noto_Sans_JP","PT Sans":"PT_Sans","PT Serif":"PT_Serif","Work Sans":"Work_Sans","Space Mono":"Space_Mono","Space Grotesk":"Space_Grotesk","Plus Jakarta Sans":"Plus_Jakarta_Sans"};function qi(t){let e=t.replace(/[^A-Za-z0-9_ -]/g,"");return Ui[e]?Ui[e]:e.replace(/\s+/g,"_")}function Bu(t){return t?{english:"en",spanish:"es",french:"fr",german:"de",italian:"it",portuguese:"pt",dutch:"nl",russian:"ru",japanese:"ja",chinese:"zh",korean:"ko",arabic:"ar",hebrew:"he",hindi:"hi",turkish:"tr",polish:"pl",swedish:"sv",norwegian:"no",danish:"da",finnish:"fi",thai:"th",vietnamese:"vi",indonesian:"id",malay:"ms",farsi:"fa",persian:"fa",czech:"cs",greek:"el",romanian:"ro",hungarian:"hu",ukrainian:"uk",bengali:"bn",tamil:"ta",telugu:"te",urdu:"ur"}[t.toLowerCase()]??"en":"en"}var Hu=new Set(["ar","he","fa","ur"]);function zu(t,e,o){let n=t.replace(/[\\"`$]/g,""),r=Bu(o),i=Hu.has(r)?`lang="${r}" dir="rtl"`:`lang="${r}"`,a=e?.fonts?.heading,l=e?.fonts?.body;if(!a&&!l)return`import type { Metadata } from "next";
4694
+ `}var Qi={"Fredoka One":"Fredoka","Source Sans Pro":"Source_Sans_3","Source Serif Pro":"Source_Serif_4","Open Sans Condensed":"Open_Sans","Baloo 2":"Baloo_2","DM Serif Display":"DM_Serif_Display","DM Serif Text":"DM_Serif_Text","IBM Plex Mono":"IBM_Plex_Mono","IBM Plex Sans":"IBM_Plex_Sans","IBM Plex Serif":"IBM_Plex_Serif","Fira Code":"Fira_Code","Fira Sans":"Fira_Sans","Noto Sans JP":"Noto_Sans_JP","PT Sans":"PT_Sans","PT Serif":"PT_Serif","Work Sans":"Work_Sans","Space Mono":"Space_Mono","Space Grotesk":"Space_Grotesk","Plus Jakarta Sans":"Plus_Jakarta_Sans"};function Xi(t){let e=t.replace(/[^A-Za-z0-9_ -]/g,"");return Qi[e]?Qi[e]:e.replace(/\s+/g,"_")}function Yu(t){return t?{english:"en",spanish:"es",french:"fr",german:"de",italian:"it",portuguese:"pt",dutch:"nl",russian:"ru",japanese:"ja",chinese:"zh",korean:"ko",arabic:"ar",hebrew:"he",hindi:"hi",turkish:"tr",polish:"pl",swedish:"sv",norwegian:"no",danish:"da",finnish:"fi",thai:"th",vietnamese:"vi",indonesian:"id",malay:"ms",farsi:"fa",persian:"fa",czech:"cs",greek:"el",romanian:"ro",hungarian:"hu",ukrainian:"uk",bengali:"bn",tamil:"ta",telugu:"te",urdu:"ur"}[t.toLowerCase()]??"en":"en"}var Qu=new Set(["ar","he","fa","ur"]);function Xu(t,e,r){let n=t.replace(/[\\"`$]/g,""),o=Yu(r),i=Qu.has(o)?`lang="${o}" dir="rtl"`:`lang="${o}"`,a=e?.fonts?.heading,l=e?.fonts?.body;if(!a&&!l)return`import type { Metadata } from "next";
4989
4695
  import { DM_Sans } from "next/font/google";
4990
4696
  import { Toaster } from "sonner";
4991
4697
  import "./globals.css";
@@ -5001,7 +4707,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
5001
4707
  </html>
5002
4708
  );
5003
4709
  }
5004
- `;let c=qi(a??l),m=qi(l??a);return c===m?`import type { Metadata } from "next";
4710
+ `;let c=Xi(a??l),p=Xi(l??a);return c===p?`import type { Metadata } from "next";
5005
4711
  import { ${c} } from "next/font/google";
5006
4712
  import { Toaster } from "sonner";
5007
4713
  import "./globals.css";
@@ -5018,12 +4724,12 @@ export default function RootLayout({ children }: { children: React.ReactNode })
5018
4724
  );
5019
4725
  }
5020
4726
  `:`import type { Metadata } from "next";
5021
- import { ${c}, ${m} } from "next/font/google";
4727
+ import { ${c}, ${p} } from "next/font/google";
5022
4728
  import { Toaster } from "sonner";
5023
4729
  import "./globals.css";
5024
4730
 
5025
4731
  const heading = ${c}({ subsets: ["latin"], variable: "--font-heading" });
5026
- const body = ${m}({ subsets: ["latin"], variable: "--font-body" });
4732
+ const body = ${p}({ subsets: ["latin"], variable: "--font-body" });
5027
4733
 
5028
4734
  export const metadata: Metadata = { title: "${n}", description: "Built with Mistflow" };
5029
4735
 
@@ -5034,59 +4740,59 @@ export default function RootLayout({ children }: { children: React.ReactNode })
5034
4740
  </html>
5035
4741
  );
5036
4742
  }
5037
- `}function Ft(t,...e){let o=JSON.stringify(t).toLowerCase();return e.some(n=>o.includes(n.toLowerCase()))}function an(t,...e){return(Array.isArray(t.integrations)?t.integrations:[]).some(n=>{let r=JSON.stringify(n).toLowerCase();return e.some(s=>r.includes(s.toLowerCase()))})}function Wu(t){return t.realMoney===!1?!1:t.realMoney===!0||an(t,"stripe")||Ft(t,"stripe","payment","billing","subscription","checkout")}function Gu(t){return t.useConnectedServices===!0?!0:t.useConnectedServices===!1?!1:t.slackIngest===!0?!0:t.slackIngest===!1?!1:an(t,"slack","slack-messages","gmail","google-mail","notion","hubspot","salesforce","github","linear","jira","asana","trello","intercom","zendesk","stripe-customers","shopify","airtable","confluence")?!0:Ft(t,"connected service","connected services","ingest from","pull from","sync with","sync data from","connect slack","their slack","from slack","sync slack","search slack","connect gmail","their gmail","from gmail","sync gmail","search gmail","connect notion","their notion","from notion","sync notion","search notion","connect hubspot","their hubspot","from hubspot","sync hubspot","hubspot contacts","connect linear","their linear","from linear","sync linear","mirror linear","connect jira","from jira","sync jira","connect github","from github","sync github","connect salesforce","from salesforce","sync salesforce","import contacts","import messages","import emails","external service","third-party service")}function Ku(t){return t.surfaceMcp===!0||t.exposeMcp===!0?!0:t.surfaceMcp===!1||t.exposeMcp===!1?!1:an(t,"mcp","mcp endpoint","mcp-endpoint")?!0:Ft(t,"mcp endpoint","expose tools to ai","expose tools to agents","callable by ai agents","callable from claude","claude can call","agent integration")}function Ju(t,e){return e==="none"?an(t,"resend","email"):e==="email"||e==="invite-only"||an(t,"resend","email")||Ft(t,"email notification","email reminder","send email","resend")}var Vu={dashboard:"Home",home:"Home",overview:"Home",patient:"Users",member:"Users",user:"Users",people:"Users",team:"Users",client:"Users",contact:"Users",customer:"Users",appointment:"Calendar",schedule:"Calendar",booking:"Calendar",event:"Calendar",billing:"CreditCard",invoice:"CreditCard",payment:"CreditCard",pricing:"CreditCard",treatment:"ClipboardList",plan:"ClipboardList",task:"CheckSquare",exercise:"Dumbbell",workout:"Dumbbell",report:"BarChart3",analytics:"BarChart3",stats:"BarChart3",setting:"Settings",config:"Settings",profile:"User",account:"User",message:"MessageSquare",chat:"MessageSquare",inbox:"MessageSquare",product:"Package",item:"Package",catalog:"Package",order:"ShoppingCart",cart:"ShoppingCart",file:"FileText",document:"FileText",upload:"Upload",notification:"Bell",alert:"Bell",project:"FolderKanban",board:"FolderKanban",post:"PenSquare",blog:"PenSquare",article:"PenSquare",course:"GraduationCap",lesson:"GraduationCap",class:"GraduationCap",habit:"Target",goal:"Target",streak:"Flame",progress:"TrendingUp",feature:"Sparkles",subscription:"CreditCard",price:"CreditCard",recipe:"ChefHat",food:"UtensilsCrossed",meal:"UtensilsCrossed",pet:"PawPrint",animal:"PawPrint",music:"Music",playlist:"ListMusic",song:"Music",photo:"Image",image:"Image",gallery:"Images",video:"Video",movie:"Film",map:"MapPin",location:"MapPin",place:"MapPin",search:"Search",explore:"Compass",inventory:"Boxes",stock:"Boxes",warehouse:"Warehouse",review:"Star",rating:"Star",feedback:"Star",log:"ScrollText",history:"Clock",activity:"Activity"};function zr(t){let e=t.toLowerCase().replace(/[^a-z]/g,"");for(let[o,n]of Object.entries(Vu))if(e.includes(o))return n;return"Circle"}function $n(t){return t.split("-").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" ")}function _t(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/\{/g,"&#123;").replace(/\}/g,"&#125;")}function Yu(t){if(t.authModel==="none")return null;let e=["/login","/register","/forgot-password","/reset-password","/api/auth","/api/health","/api/webhooks","/api/admin/seed","/images","/assets"];if(t.publicPages&&Array.isArray(t.publicPages))for(let s of t.publicPages){if(typeof s!="string"||s.length<1)continue;let i=s.replace(/[\u201C\u201D\u201E\u201F\u2018\u2019\u2033\u2036]/g,"").trim();if(!i)continue;let a=i.startsWith("/")?i:"/"+i;e.includes(a)||e.push(a)}let o=e.filter(s=>s==="/"),n=e.filter(s=>s!=="/"),r=[];r.push('import { NextRequest, NextResponse } from "next/server";'),r.push(""),r.push("const PUBLIC_PREFIXES = [");for(let s of n)r.push(' "'+s+'",');return r.push("];"),r.push(""),o.length>0&&(r.push('const PUBLIC_EXACT = ["'+o.join('", "')+'"];'),r.push("")),r.push("export function middleware(req: NextRequest) {"),r.push(" const { pathname, search } = req.nextUrl;"),r.push(""),o.length>0&&r.push(" if (PUBLIC_EXACT.includes(pathname)) return NextResponse.next();"),r.push(" if (PUBLIC_PREFIXES.some((p) => pathname.startsWith(p))) return NextResponse.next();"),r.push(""),r.push(' const token = req.cookies.get("better-auth.session_token")?.value || req.cookies.get("__Secure-better-auth.session_token")?.value;'),r.push(" if (!token) {"),r.push(' const loginUrl = new URL("/login", req.url);'),r.push(" const params = new URLSearchParams(search);"),r.push(' for (const key of ["verified", "error"]) {'),r.push(" const v = params.get(key);"),r.push(" if (v) loginUrl.searchParams.set(key, v);"),r.push(" }"),r.push(" return NextResponse.redirect(loginUrl);"),r.push(" }"),r.push(""),r.push(" return NextResponse.next();"),r.push("}"),r.push(""),r.push("export const config = {"),r.push(' matcher: ["/((?!_next|static|favicon\\\\.ico).*)"],'),r.push("};"),r.push(""),r.join(`
5038
- `)}function Qu(t){if(t.navStyle==="none")return null;let e=["/api","/login","/register","/sign-in","/sign-up","/admin","/pricing","/about","/contact","/terms","/privacy","/onboarding","/join","/forgot-password","/reset-password"],n=(t.pages??[]).filter(l=>{let c=l.path??l.route??"";return c==="/"||c===""||c.includes("[")||c.replace(/^\//,"").split("/").length>1?!1:!e.some(u=>c.startsWith(u))}).map(l=>{let c=l.path??l.route??"",m=c.startsWith("/")?c:"/"+c,u=l.name??$n(c.replace(/^\//,"")),p=zr(u);return{label:u,href:m,icon:p}});n.some(l=>l.href==="/dashboard")||n.unshift({label:"Dashboard",href:"/dashboard",icon:"Home"});let r=t.authModel==="none",s=[...new Set(n.map(l=>l.icon))];r||s.push("LogOut");let i=$n(t.name);if(t.navStyle==="topbar"){let l=[];l.push('"use client";'),l.push(""),l.push('import Link from "next/link";'),l.push('import { usePathname } from "next/navigation";'),r||l.push('import { authClient } from "@/lib/auth-client";'),l.push('import { Button } from "@/components/ui/button";'),l.push('import { cn } from "@/lib/utils";'),l.push("import { "+s.join(", ")+' } from "lucide-react";'),l.push(""),l.push("interface TopNavProps {"),l.push(" user: { name: string | null; email: string; role?: string | undefined };"),l.push("}"),l.push(""),l.push("const NAV_ITEMS = [");for(let c of n)l.push(' { label: "'+c.label+'", href: "'+c.href+'", icon: '+c.icon+" },");return l.push("];"),l.push(""),l.push("export default function TopNav({ user }: TopNavProps) {"),l.push(" const pathname = usePathname();"),l.push(""),l.push(" return ("),l.push(' <nav className="border-b bg-card">'),l.push(' <div className="mx-auto flex h-14 max-w-7xl items-center justify-between px-4">'),l.push(' <div className="flex items-center gap-6">'),l.push(' <span className="text-lg font-semibold">'+i+"</span>"),l.push(' <div className="flex items-center gap-1">'),l.push(" {NAV_ITEMS.map((item) => ("),l.push(" <Link"),l.push(" key={item.href}"),l.push(" href={item.href}"),l.push(' aria-current={pathname === item.href ? "page" : undefined}'),l.push(" className={cn("),l.push(' "flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-colors",'),l.push(' pathname === item.href ? "bg-primary/10 text-primary" : "text-muted-foreground hover:text-foreground"'),l.push(" )}"),l.push(" >"),l.push(' <item.icon className="h-4 w-4" />'),l.push(" {item.label}"),l.push(" </Link>"),l.push(" ))}"),l.push(" </div>"),l.push(" </div>"),r?l.push(' <span className="text-sm text-muted-foreground">{user.name}</span>'):(l.push(' <div className="flex items-center gap-2">'),l.push(' <span className="text-sm text-muted-foreground">{user.email}</span>'),l.push(" <Button"),l.push(' variant="ghost"'),l.push(' size="sm"'),l.push(' onClick={() => authClient.signOut({ fetchOptions: { onSuccess: () => { window.location.href = "/login"; } } })}'),l.push(" >"),l.push(' <LogOut className="h-4 w-4" />'),l.push(" </Button>"),l.push(" </div>")),l.push(" </div>"),l.push(" </nav>"),l.push(" );"),l.push("}"),l.push(""),{path:"components/topnav.tsx",content:l.join(`
5039
- `)}}let a=[];a.push('"use client";'),a.push(""),a.push('import { useState } from "react";'),a.push('import Link from "next/link";'),a.push('import { usePathname } from "next/navigation";'),r||a.push('import { authClient } from "@/lib/auth-client";'),a.push('import { Button } from "@/components/ui/button";'),a.push('import { Sheet, SheetContent, SheetTrigger, SheetTitle } from "@/components/ui/sheet";'),a.push('import { cn } from "@/lib/utils";'),a.push("import { Menu, "+s.join(", ")+' } from "lucide-react";'),a.push(""),a.push("interface SidebarProps {"),a.push(" user: { name: string | null; email: string; role?: string | undefined };"),a.push("}"),a.push(""),a.push("const NAV_ITEMS = [");for(let l of n)a.push(' { label: "'+l.label+'", href: "'+l.href+'", icon: '+l.icon+" },");return a.push("];"),a.push(""),a.push('function NavContent({ pathname, user, onNavigate }: { pathname: string; user: SidebarProps["user"]; onNavigate?: () => void }) {'),a.push(" return ("),a.push(" <>"),a.push(' <div className="flex h-14 items-center border-b px-4">'),a.push(' <span className="text-lg font-semibold">'+i+"</span>"),a.push(" </div>"),a.push(' <nav className="flex-1 space-y-1 p-2">'),a.push(" {NAV_ITEMS.map((item) => ("),a.push(" <Link"),a.push(" key={item.href}"),a.push(" href={item.href}"),a.push(" onClick={onNavigate}"),a.push(' aria-current={pathname === item.href ? "page" : undefined}'),a.push(" className={cn("),a.push(' "flex items-center gap-3 rounded-md px-3 py-2.5 text-sm font-medium transition-colors",'),a.push(' pathname === item.href ? "bg-primary/10 text-primary" : "text-muted-foreground hover:bg-muted hover:text-foreground"'),a.push(" )}"),a.push(" >"),a.push(' <item.icon className="h-4 w-4" />'),a.push(" {item.label}"),a.push(" </Link>"),a.push(" ))}"),a.push(" </nav>"),a.push(' <div className="border-t p-4">'),a.push(' <div className="flex items-center gap-3">'),a.push(' <div className="flex-1 truncate">'),a.push(' <p className="truncate text-sm font-medium">{user.name ?? "User"}</p>'),a.push(' <p className="truncate text-xs text-muted-foreground">{user.email}</p>'),a.push(" </div>"),r||(a.push(" <Button"),a.push(' variant="ghost"'),a.push(' size="icon"'),a.push(' onClick={() => authClient.signOut({ fetchOptions: { onSuccess: () => { window.location.href = "/login"; } } })}'),a.push(" >"),a.push(' <LogOut className="h-4 w-4" />'),a.push(" </Button>")),a.push(" </div>"),a.push(" </div>"),a.push(" </>"),a.push(" );"),a.push("}"),a.push(""),a.push("export default function Sidebar({ user }: SidebarProps) {"),a.push(" const pathname = usePathname();"),a.push(" const [open, setOpen] = useState(false);"),a.push(""),a.push(" return ("),a.push(" <>"),a.push(' <aside className="hidden md:flex h-screen w-64 flex-col border-r bg-card">'),a.push(" <NavContent pathname={pathname} user={user} />"),a.push(" </aside>"),a.push(' <div className="sticky top-0 z-[var(--z-sticky)] flex h-14 items-center gap-3 border-b bg-card px-4 md:hidden">'),a.push(" <Sheet open={open} onOpenChange={setOpen}>"),a.push(" <SheetTrigger asChild>"),a.push(' <Button variant="ghost" size="icon" className="-ml-2">'),a.push(' <Menu className="h-5 w-5" />'),a.push(" </Button>"),a.push(" </SheetTrigger>"),a.push(' <SheetContent side="left" className="w-64 p-0">'),a.push(' <SheetTitle className="sr-only">Navigation</SheetTitle>'),a.push(' <div className="flex h-full flex-col">'),a.push(" <NavContent pathname={pathname} user={user} onNavigate={() => setOpen(false)} />"),a.push(" </div>"),a.push(" </SheetContent>"),a.push(" </Sheet>"),a.push(' <span className="text-lg font-semibold">'+i+"</span>"),a.push(" </div>"),a.push(" </>"),a.push(" );"),a.push("}"),a.push(""),{path:"components/sidebar.tsx",content:a.join(`
5040
- `)}}function Xu(t){if(!t.roles||t.roles.length===0)return null;let e=t.roles,o=t.defaultRole??e[0],n=[];n.push("export type Role = "+e.map(r=>'"'+r+'"').join(" | ")+";"),n.push(""),n.push("export const ROLES = ["+e.map(r=>'"'+r+'"').join(", ")+"] as const;"),n.push(""),n.push('export const DEFAULT_ROLE: Role = "'+o+'";'),n.push(""),n.push("export const ROLE_LABELS: Record<Role, string> = {");for(let r of e){let s=r.charAt(0).toUpperCase()+r.slice(1);n.push(' "'+r+'": "'+s+'",')}return n.push("};"),n.push(""),n.push("export function getUserRole(user: Record<string, unknown>): Role {"),n.push(" const role = (user.role as string) ?? DEFAULT_ROLE;"),n.push(" if (ROLES.includes(role as Role)) return role as Role;"),n.push(" return DEFAULT_ROLE;"),n.push("}"),n.push(""),n.push("export function hasRole(userRole: string | undefined, required: Role | Role[]): boolean {"),n.push(" if (!userRole) return false;"),n.push(" const allowed = Array.isArray(required) ? required : [required];"),n.push(" return allowed.includes(userRole as Role);"),n.push("}"),n.push(""),n.join(`
5041
- `)}function Zu(t){let e=$n(t.name);if(t.authModel==="none"){let s=[];return s.push("export default function HomePage() {"),s.push(" return ("),s.push(' <main className="flex min-h-screen flex-col items-center justify-center p-8">'),s.push(' <h1 className="text-4xl font-bold">'+_t(e)+"</h1>"),t.summary&&s.push(' <p className="mt-4 text-lg text-muted-foreground">'+_t(t.summary)+"</p>"),s.push(" </main>"),s.push(" );"),s.push("}"),s.push(""),s.join(`
5042
- `)}let o=t.publicPages?.includes("/"),n=t.design?.landingTone;if(o&&n){let s=[];return s.push('import Link from "next/link";'),s.push(""),s.push("export default function HomePage() {"),s.push(" return ("),s.push(' <main className="flex min-h-screen flex-col">'),s.push(' <section className="flex flex-1 flex-col items-center justify-center gap-6 px-4 py-24 text-center">'),s.push(' <h1 className="text-5xl font-bold tracking-tight">'+_t(e)+"</h1>"),t.summary&&s.push(' <p className="max-w-2xl text-xl text-muted-foreground">'+_t(t.summary)+"</p>"),s.push(' <div className="flex gap-4">'),s.push(' <Link href="/register" className="inline-flex h-11 items-center rounded-md bg-primary px-8 text-sm font-medium text-primary-foreground hover:bg-primary/90">'),s.push(" Get Started"),s.push(" </Link>"),s.push(' <Link href="/login" className="inline-flex h-11 items-center rounded-md border px-8 text-sm font-medium hover:bg-muted">'),s.push(" Sign In"),s.push(" </Link>"),s.push(" </div>"),s.push(" </section>"),s.push(" </main>"),s.push(" );"),s.push("}"),s.push(""),s.join(`
5043
- `)}if(o){let s=[];return s.push('import Link from "next/link";'),s.push(""),s.push("export default function HomePage() {"),s.push(" return ("),s.push(' <main className="flex min-h-screen flex-col items-center justify-center gap-6 p-8 text-center">'),s.push(' <h1 className="text-4xl font-bold">'+_t(e)+"</h1>"),t.summary&&s.push(' <p className="text-lg text-muted-foreground">'+_t(t.summary)+"</p>"),s.push(' <Link href="/login" className="inline-flex h-10 items-center rounded-md bg-primary px-6 text-sm font-medium text-primary-foreground hover:bg-primary/90">'),s.push(" Sign In"),s.push(" </Link>"),s.push(" </main>"),s.push(" );"),s.push("}"),s.push(""),s.join(`
5044
- `)}let r=[];return r.push('import { headers } from "next/headers";'),r.push('import { redirect } from "next/navigation";'),r.push('import { auth } from "@/lib/auth";'),r.push(""),r.push("export default async function HomePage() {"),r.push(" const session = await auth.api.getSession({ headers: await headers() });"),r.push(' if (session) redirect("/dashboard");'),r.push(' redirect("/login");'),r.push("}"),r.push(""),r.join(`
5045
- `)}function ep(t,e){let o=t.authModel==="none",n=[];o||(n.push('import { Suspense } from "react";'),n.push('import { headers } from "next/headers";'),n.push('import { redirect } from "next/navigation";'),n.push('import { auth } from "@/lib/auth";'),n.push('import { VerifiedBanner } from "@/components/auth/verified-banner";')),t.navStyle==="topbar"?n.push('import TopNav from "@/components/topnav";'):t.navStyle!=="none"&&n.push('import Sidebar from "@/components/sidebar";'),!o&&t.roles&&t.roles.length>0&&n.push('import { getUserRole } from "@/lib/roles";'),n.push(""),n.push("export default async function DashboardLayout({ children }: { children: React.ReactNode }) {"),o?n.push(' const user = { name: "Guest", email: "" };'):(n.push(" const session = await auth.api.getSession({ headers: await headers() });"),n.push(' if (!session) redirect("/login");'),n.push(""),t.roles&&t.roles.length>0?(n.push(" const role = getUserRole(session.user as Record<string, unknown>);"),n.push(" const user = { name: session.user.name, email: session.user.email, role };")):(n.push(" const user = {"),n.push(" name: session.user.name,"),n.push(" email: session.user.email,"),n.push(" role: (session.user as Record<string, unknown>).role as string | undefined,"),n.push(" };"))),n.push("");let r=o?"":"<Suspense fallback={null}><VerifiedBanner /></Suspense>";return t.navStyle==="topbar"?(n.push(" return ("),n.push(' <div className="min-h-screen">'),n.push(" <TopNav user={user} />"),n.push(' <main className="mx-auto max-w-7xl p-6">'+r+"{children}</main>"),n.push(" </div>"),n.push(" );")):t.navStyle==="none"?(n.push(" return ("),n.push(' <div className="min-h-screen">'),n.push(' <main className="mx-auto max-w-5xl p-6">'+r+"{children}</main>"),n.push(" </div>"),n.push(" );")):(n.push(" return ("),n.push(' <div className="flex flex-col md:flex-row min-h-screen">'),n.push(" <Sidebar user={user} />"),n.push(' <main className="flex-1 overflow-x-hidden p-4 md:p-6">'+r+"{children}</main>"),n.push(" </div>"),n.push(" );")),n.push("}"),n.push(""),n.join(`
5046
- `)}function tp(t){let e=$n(t.name),o=t.dataModel??[],n=[];if(o.length>0){let r=o.map(i=>zr(i.entity??i.name??"item")),s=[...new Set(r)];n.push('import { Card, CardContent } from "@/components/ui/card";'),n.push("import { "+s.join(", ")+' } from "lucide-react";'),n.push("")}if(n.push("export default function DashboardPage() {"),n.push(" return ("),n.push(' <div className="space-y-6">'),n.push(" <div>"),n.push(' <h1 className="text-3xl font-bold">'+_t(e)+"</h1>"),t.summary&&n.push(' <p className="mt-1 text-muted-foreground">'+_t(t.summary)+"</p>"),n.push(" </div>"),o.length>0){n.push(' <div className="rounded-lg border p-8 text-center">'),n.push(' <h2 className="text-lg font-semibold">Get started</h2>'),n.push(` <p className="mt-1 text-sm text-muted-foreground">Here's what you can do</p>`),n.push(' <div className="mt-6 grid gap-3 sm:grid-cols-2 text-left">');for(let r of o){let s=r.entity??r.name??"Item",i=zr(s),a=$n(s.replace(/_/g,"-"));n.push(' <div className="flex items-center gap-3 rounded-md border p-3">'),n.push(" <"+i+' className="h-5 w-5 text-muted-foreground" />'),n.push(' <span className="text-sm font-medium">Add your first '+a+"</span>"),n.push(" </div>")}n.push(" </div>"),n.push(" </div>")}return n.push(" </div>"),n.push(" );"),n.push("}"),n.push(""),n.join(`
5047
- `)}function np(t,e=!1){if(!t.multiTenant)return null;let o=[];return e?(o.push('import { pgTable, text, timestamp, index } from "drizzle-orm/pg-core";'),o.push('import { user } from "./auth";'),o.push(""),o.push('export const organization = pgTable("organization", {'),o.push(' id: text("id").primaryKey(),'),o.push(' name: text("name").notNull(),'),o.push(' slug: text("slug").unique().notNull(),'),o.push(' createdAt: timestamp("created_at").defaultNow().notNull(),'),o.push(' updatedAt: timestamp("updated_at").defaultNow().notNull(),'),o.push("});"),o.push(""),o.push("export const orgMember = pgTable(")):(o.push('import { sqliteTable, text, index } from "drizzle-orm/sqlite-core";'),o.push('import { sql } from "drizzle-orm";'),o.push('import { user } from "./auth";'),o.push(""),o.push('export const organization = sqliteTable("organization", {'),o.push(' id: text("id").primaryKey(),'),o.push(' name: text("name").notNull(),'),o.push(' slug: text("slug").unique().notNull(),'),o.push(' createdAt: text("created_at").default(sql`CURRENT_TIMESTAMP`).notNull(),'),o.push(' updatedAt: text("updated_at").default(sql`CURRENT_TIMESTAMP`).notNull(),'),o.push("});"),o.push(""),o.push("export const orgMember = sqliteTable(")),o.push(' "org_member",'),o.push(" {"),o.push(' id: text("id").primaryKey(),'),o.push(' orgId: text("org_id").notNull().references(() => organization.id),'),o.push(' userId: text("user_id").notNull().references(() => user.id),'),o.push(' role: text("role").notNull(),'),e?o.push(' joinedAt: timestamp("joined_at").defaultNow().notNull(),'):o.push(' joinedAt: text("joined_at").default(sql`CURRENT_TIMESTAMP`).notNull(),'),o.push(" },"),o.push(" (table) => ({"),o.push(' orgIdx: index("org_member_org_idx").on(table.orgId),'),o.push(' userIdx: index("org_member_user_idx").on(table.userId),'),o.push(" }),"),o.push(");"),o.push(""),o.join(`
5048
- `)}function op(t){if(!t.multiTenant)return null;let e=[];return e.push('import { db } from "./db";'),e.push('import { organization, orgMember } from "@/db/schema/organization";'),e.push('import { eq } from "drizzle-orm";'),e.push(""),e.push("export async function getCurrentOrg(userId: string) {"),e.push(" const membership = await db"),e.push(" .select()"),e.push(" .from(orgMember)"),e.push(" .where(eq(orgMember.userId, userId))"),e.push(" .limit(1);"),e.push(" if (membership.length === 0) return null;"),e.push(" const org = await db"),e.push(" .select()"),e.push(" .from(organization)"),e.push(" .where(eq(organization.id, membership[0].orgId))"),e.push(" .limit(1);"),e.push(" return org[0] ?? null;"),e.push("}"),e.push(""),e.push("export async function getOrgMembers(orgId: string) {"),e.push(" return db"),e.push(" .select()"),e.push(" .from(orgMember)"),e.push(" .where(eq(orgMember.orgId, orgId));"),e.push("}"),e.push(""),e.push("export async function inviteToOrg(orgId: string, email: string, role: string) {"),e.push(" const id = crypto.randomUUID();"),e.push(" await db.insert(orgMember).values({"),e.push(" id,"),e.push(" orgId,"),e.push(" userId: email,"),e.push(" role,"),e.push(" });"),e.push(" return { id, orgId, email, role };"),e.push("}"),e.push(""),e.join(`
5049
- `)}function rp(t){if(!t.multiTenant)return null;let e=[];return e.push('"use client";'),e.push(""),e.push("import {"),e.push(" DropdownMenu,"),e.push(" DropdownMenuContent,"),e.push(" DropdownMenuItem,"),e.push(" DropdownMenuTrigger,"),e.push('} from "@/components/ui/dropdown-menu";'),e.push('import { Button } from "@/components/ui/button";'),e.push('import { ChevronsUpDown } from "lucide-react";'),e.push(""),e.push("interface OrgSwitcherProps {"),e.push(" orgs: Array<{ id: string; name: string }>;"),e.push(" currentOrgId: string;"),e.push("}"),e.push(""),e.push("export default function OrgSwitcher({ orgs, currentOrgId }: OrgSwitcherProps) {"),e.push(" const currentOrg = orgs.find((o) => o.id === currentOrgId);"),e.push(""),e.push(" return ("),e.push(" <DropdownMenu>"),e.push(" <DropdownMenuTrigger asChild>"),e.push(' <Button variant="outline" className="w-full justify-between">'),e.push(' <span className="truncate">{currentOrg?.name ?? "Select org"}</span>'),e.push(' <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />'),e.push(" </Button>"),e.push(" </DropdownMenuTrigger>"),e.push(' <DropdownMenuContent className="w-56">'),e.push(" {orgs.map((org) => ("),e.push(" <DropdownMenuItem key={org.id}>"),e.push(" {org.name}"),e.push(" </DropdownMenuItem>"),e.push(" ))}"),e.push(" </DropdownMenuContent>"),e.push(" </DropdownMenu>"),e.push(" );"),e.push("}"),e.push(""),e.join(`
5050
- `)}function sp(t,e,o){let n=[],r=t.split("-").map(l=>l.charAt(0).toUpperCase()+l.slice(1)).join(" ");n.push(`# ${r}`),n.push(""),e?.summary&&(n.push(e.summary),n.push(""));let s=e?.features??[];if(s.length>0){n.push("## Features"),n.push("");for(let l of s){let c=l.description?` \u2014 ${l.description}`:"";n.push(`- **${l.name}**${c}`)}n.push("")}n.push("## Tech Stack"),n.push(""),n.push("| Layer | Technology |"),n.push("|-------|------------|"),n.push("| Framework | Next.js 15 (App Router) |"),n.push("| Database | Mistflow Cloud (Postgres) + Drizzle ORM |"),n.push("| Auth | Better Auth (email/password, social login) |"),n.push("| Styling | Tailwind CSS + shadcn/ui |"),n.push("| Deployment | Mistflow Cloud |"),o.hasStripe&&n.push("| Payments | Stripe |"),o.hasResend&&n.push("| Email | Resend + React Email |"),o.hasStorage&&n.push("| File Storage | Mistflow Cloud (managed blob storage) |"),o.hasAdmin&&n.push("| Admin | Better Auth admin plugin |"),o.hasAI&&n.push("| AI | Vercel AI SDK + OpenAI |"),n.push("");let i=e?.pages??[];if(i.length>0){n.push("## Pages"),n.push(""),n.push("| Route | Description |"),n.push("|-------|-------------|");for(let l of i){let c=l.path??l.route??l.name??"",m=l.description??"";n.push(`| \`${c.startsWith("/")?c:"/"+c}\` | ${m} |`)}n.push("")}let a=e?.dataModel??[];if(a.length>0){n.push("## Data Model"),n.push("");for(let l of a){let c=l.entity??l.name??"Unknown";if(n.push(`### ${c}`),n.push(""),l.fields.length>0){if(typeof l.fields[0]=="string")n.push(`Fields: ${l.fields.join(", ")}`);else{n.push("| Field | Type |"),n.push("|-------|------|");for(let m of l.fields)n.push(`| ${m.name} | ${m.type} |`)}n.push("")}}}return n.push("## Getting Started"),n.push(""),n.push("### Prerequisites"),n.push(""),n.push("- Node.js 20+"),n.push("- npm"),n.push(""),n.push("### Install"),n.push(""),n.push("The host AI calls mist_install (fire-and-poll) which handles this for you. Run manually with plain npm if needed:"),n.push(""),n.push("```bash"),n.push("npm install"),n.push("```"),n.push(""),n.push("### Set up environment"),n.push(""),n.push("Copy `.env.example` to `.env.local` and fill in the values:"),n.push(""),n.push("```bash"),n.push("cp .env.example .env.local"),n.push("```"),n.push(""),n.push("| Variable | Description | Required |"),n.push("|----------|-------------|----------|"),o.isNeon?n.push("| `DATABASE_URL` | Postgres connection URL | Yes |"):(n.push("| `TURSO_URL` | Database connection URL | Yes |"),n.push("| `TURSO_AUTH_TOKEN` | Database auth token | Yes |")),n.push("| `AUTH_SECRET` | Auth encryption secret (auto-generated) | Yes |"),o.hasStripe&&(n.push("| `STRIPE_SECRET_KEY` | Stripe secret key | Yes |"),n.push("| `STRIPE_WEBHOOK_SECRET` | Stripe webhook signing secret | Yes |"),n.push("| `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` | Stripe publishable key | Yes |")),o.hasResend&&(n.push("| `RESEND_API_KEY` | Resend API key | Yes |"),n.push("| `EMAIL_FROM` | Sender email address | Yes (production) |")),(o.hasAI||o.hasStorage)&&n.push("| `MISTFLOW_RUNTIME_KEY` | Mistflow Cloud runtime key \u2014 authenticates AI gateway, file storage, and other managed features | Auto-provisioned at `mist_init` |"),o.hasAI&&n.push("| `OPENROUTER_API_KEY` | OpenRouter key for your-key fallback (only used when `MISTFLOW_RUNTIME_KEY` is unset) | Optional |"),n.push(""),n.push("### Local database"),n.push(""),o.isNeon?(n.push("For local development, start a local Postgres server:"),n.push(""),n.push("```bash"),n.push("# Using Docker:"),n.push("docker run -d --name postgres -e POSTGRES_PASSWORD=postgres -p 5432:5432 postgres:17"),n.push("# Or install via Homebrew: brew install postgresql@17 && brew services start postgresql@17"),n.push("```")):(n.push("For local development, start a local Turso server:"),n.push(""),n.push("```bash"),n.push("npx turso dev"),n.push("```")),n.push(""),n.push("Then set up the database:"),n.push(""),n.push("```bash"),n.push("npm run db:push"),n.push("```"),n.push(""),n.push("### Run"),n.push(""),n.push("```bash"),n.push("npm run dev"),n.push("```"),n.push(""),n.push("Open [http://localhost:3000](http://localhost:3000)."),n.push(""),n.push("## Project Structure"),n.push(""),n.push("```"),n.push("app/"),n.push(" (auth)/ Login and registration pages"),n.push(" (dashboard)/ Authenticated app pages"),o.hasAdmin&&n.push(" admin/ Admin panel pages (at /admin/...)"),n.push(" home/ Public landing page (served via / \u2192 /home redirect)"),n.push(" api/ API routes (auth, health, webhooks)"),n.push(" layout.tsx Root layout with fonts and providers"),n.push(" globals.css Design tokens and Tailwind config"),n.push("components/ Reusable UI components"),n.push("db/"),n.push(" schema/ Database table definitions"),n.push(" index.ts Schema exports"),n.push("lib/"),n.push(" auth.ts Better Auth server config"),n.push(" auth-client.ts Better Auth client config"),n.push(` db.ts ${o.isNeon?"Postgres":"SQLite"} database connection`),o.hasStripe&&n.push(" stripe.ts Stripe client"),o.hasResend&&(n.push(" resend.ts Resend client"),n.push(" email.ts Email send helpers")),n.push(" server/storage.ts File upload/download helpers (managed R2)"),o.hasAI&&(n.push(" ai.ts Back-compat export for server AI helpers"),n.push(" server/ai.ts Server-only AI Gateway and provider helpers")),o.hasResend&&n.push("emails/ React Email templates"),n.push("```"),n.push(""),n.push("## Deploy"),n.push(""),n.push("Deploy to production with Mistflow:"),n.push(""),n.push("```"),n.push("# In your AI editor (Claude Code, Cursor, etc.):"),n.push("mist_deploy action='deploy'"),n.push("```"),n.push(""),n.push("Your app will be live at `https://<app-name>.mistflow.app`."),n.push(""),e?.design&&(n.push("## Design"),n.push(""),e.design.tone&&n.push(`- **Tone**: ${e.design.tone}`),e.design.fonts&&(n.push(`- **Heading font**: ${e.design.fonts.heading}`),n.push(`- **Body font**: ${e.design.fonts.body}`)),e.design.accentColor&&n.push(`- **Accent color**: ${e.design.accentColor}`),e.design.borderRadius&&n.push(`- **Border radius**: ${e.design.borderRadius}`),n.push("")),n.push("---"),n.push(""),n.push("Built with [Mistflow](https://mistflow.ai)"),n.push(""),n.join(`
5051
- `)}async function ip(t,e){if(!Te())return Oe("scaffold a new project");if(!tn())return d(JSON.stringify({status:"node_version_unsupported",code:"node_version_unsupported",message:qt(),nextAction:"Tell the user their Node version is too old, paste the upgrade command from `message`, and stop. Do NOT proceed with mist_init \u2014 the scaffolded app would build-fail at deploy time."}),!0);let{name:o,plan:n,path:r,planId:s,sessionId:i}=t,a,l=[],c,m=null,u=n;if(typeof u=="string")try{let R=JSON.parse(u);R&&typeof R=="object"&&!Array.isArray(R)&&"plan"in R?u=R.plan:u=R}catch{return d("mist_init received `plan` as a string, but it is not valid JSON. Pass the plan object directly or pass planId from mist_plan.",!0)}if(!u&&s){if(m=_u(s),!m)return d(`No plan found for planId '${s}'. Call mist_plan first, or pass the plan object inline.`,!0);u=m.plan}if(!u&&i){let R=await br(i);R&&(u=R.plan)}if(!u||typeof u!="object"||Array.isArray(u))return d("mist_init requires a plan object or a valid planId from mist_plan. Refusing to scaffold without a structured plan because mist_implement would not know what to build.",!0);let p=r??m?.scaffoldTargetPath;if(!p)return d("mist_init requires an explicit 'path' unless the planId cache includes a scaffoldTargetPath. Call mist_init with the planId returned by mist_plan, pass the absolute scaffold directory, or pass { sessionId, path } in session mode.",!0);if(!ku(p))return d(`mist_init 'path' must be an absolute path \u2014 received '${p}'. Pass the full absolute path to the target directory.`,!0);let h=Bi(p),U=o??(typeof u.name=="string"?u.name:void 0);if(!U)return d("mist_init requires a human-readable app name. Pass `name`, or call it with a planId whose cached plan includes a name.",!0);let C=u?.design,x=typeof u.authModel=="string"?u.authModel:void 0,w=x==="none",A=Wu(u),J=Ju(u,x),V=u?Ft(u,"upload","file storage","image upload","profile picture","attachment","gallery","media","blob"):!1,te=u?Ft(u,"admin panel","admin dashboard","admin management"):!1,le=u?Ft(u,"ai integration","openai","llm","ai chat","chatbot","gpt"):!1,Q=u?Ku(u):!1,q=u?Gu(u):!1,O=q,D=u,Z=typeof u.dbProvider=="string"?u.dbProvider:"neon",ne=Array.isArray(u.dataModel)&&u.dataModel.length>0,v=Z==="none"||w&&!ne&&!V,F=v?"none":Z,j=F==="neon";if(!ju(h))return d(Du(h,u?.name),!0);rn(h,{recursive:!0});try{try{let b=pe(h,".mistflow","rules");rn(b,{recursive:!0}),dt(pe(b,"design-quality.md"),wo),dt(pe(b,"landing.md"),vo)}catch(b){console.error("Could not write design rule files:",b instanceof Error?b.message:b)}try{let b=pe(Ln(h),".mistflow","mockups");if(Be(b)){let _=Fi(b).filter($=>$.endsWith(".html"));if(_.length>0){let $=pe(h,".mistflow","mockups");rn($,{recursive:!0});for(let Ue of _)vu(pe(b,Ue),pe($,Ue));console.error(`Copied ${_.length} mockup file(s) into project`)}}}catch(b){console.error("Could not copy mockup files:",b instanceof Error?b.message:b)}let R=null;try{R=await ar("nextjs")}catch(b){console.error("Could not fetch scaffold from API, using minimal scaffold:",b instanceof Error?b.message:b)}if(R){let b=U.toLowerCase().replace(/[^a-z0-9-]/g,"-");for(let S of R.files){if(S.path==="package.json"||S.path==="middleware.ts"||S.path==="components/sidebar.tsx"||S.path==="components/topnav.tsx"||S.path==="app/(dashboard)/layout.tsx"||S.path==="app/(dashboard)/page.tsx"||S.path==="app/(dashboard)/dashboard/page.tsx"||v&&(S.path==="lib/db.ts"||S.path==="drizzle.config.ts"||S.path==="db/index.ts"||S.path.startsWith("db/schema/"))||w&&(S.path.includes("(auth)")||S.path.includes("(admin)")||S.path.startsWith("app/admin/")||S.path.includes("components/auth/")||S.path.includes("admin-sidebar")||S.path.includes("app/api/auth/")||S.path.includes("app/api/admin/seed")||S.path==="lib/auth.ts"||S.path==="lib/auth-client.ts"||S.path==="db/schema/auth.ts"||S.path==="db/schema/index.ts"||S.path==="db/index.ts")||!A&&(S.path.includes("stripe")||S.path.includes("webhook/stripe"))||!J&&(S.path.includes("resend")||S.path.includes("emails/"))||!te&&(S.path.includes("(admin)")||S.path.startsWith("app/admin/")||S.path.includes("admin-sidebar"))||j&&(S.path==="lib/db.ts"||S.path==="lib/auth.ts"||S.path==="drizzle.config.ts"||S.path==="db/schema/auth.ts"))continue;let H=S.content.replace(/\{\{APP_NAME\}\}/g,U).replace(/\{\{WORKER_NAME\}\}/g,b);if(j&&S.path==="next.config.ts"&&(H=H.replace(/serverExternalPackages:\s*\[[^\]]*\],?/g,'serverExternalPackages: ["@electric-sql/pglite"],')),S.path==="next.config.ts"){let Ce=Iu(h);Ce&&(console.error(`[init] Project is inside monorepo at ${Ce} \u2014 adding outputFileTracingRoot`),H.includes("outputFileTracingRoot")||(H=H.replace('import type { NextConfig } from "next";',`import type { NextConfig } from "next";
4743
+ `}function Bt(t,...e){let r=JSON.stringify(t).toLowerCase();return e.some(n=>r.includes(n.toLowerCase()))}function Ht(t,...e){return(Array.isArray(t.integrations)?t.integrations:[]).some(n=>{let o=JSON.stringify(n).toLowerCase();return e.some(s=>o.includes(s.toLowerCase()))})}function Zu(t){return t.realMoney!==!0?!1:Ht(t,"stripe")||Bt(t,"stripe","payment","billing","subscription","checkout")}function ep(t){return t.hasAI===!0?!0:Ht(t,"openai","anthropic","openrouter","ai")||Bt(t,"ai integration","openai","anthropic","openrouter","llm","ai chat","chatbot","gpt","ai-powered","ai powered","ai-generated","ai generated","ai-assisted","ai assisted","ai scorecard","ai scorecards","ai interviewer","ai interview","generate interview","generate questions","draft interview","scorecard generation","evaluate transcript","transcript evaluation","recommendation label")}function tp(t){return t.useConnectedServices===!0?!0:t.useConnectedServices===!1?!1:t.slackIngest===!0?!0:t.slackIngest===!1?!1:Ht(t,"slack","slack-messages","gmail","google-mail","notion","hubspot","salesforce","github","linear","jira","asana","trello","intercom","zendesk","stripe-customers","shopify","airtable","confluence")?!0:Bt(t,"connected service","connected services","ingest from","pull from","sync with","sync data from","connect slack","their slack","from slack","sync slack","search slack","connect gmail","their gmail","from gmail","sync gmail","search gmail","connect notion","their notion","from notion","sync notion","search notion","connect hubspot","their hubspot","from hubspot","sync hubspot","hubspot contacts","connect linear","their linear","from linear","sync linear","mirror linear","connect jira","from jira","sync jira","connect github","from github","sync github","connect salesforce","from salesforce","sync salesforce","import contacts","import messages","import emails","external service","third-party service")}function np(t){return t.surfaceMcp===!0||t.exposeMcp===!0?!0:t.surfaceMcp===!1||t.exposeMcp===!1?!1:Ht(t,"mcp","mcp endpoint","mcp-endpoint")?!0:Bt(t,"mcp endpoint","expose tools to ai","expose tools to agents","callable by ai agents","callable from claude","claude can call","agent integration")}function rp(t,e){return e==="none"?Ht(t,"resend","email"):e==="email"||e==="invite-only"||Ht(t,"resend","email")||Bt(t,"email notification","email reminder","send email","resend")}var op={dashboard:"Home",home:"Home",overview:"Home",patient:"Users",member:"Users",user:"Users",people:"Users",team:"Users",client:"Users",contact:"Users",customer:"Users",appointment:"Calendar",schedule:"Calendar",booking:"Calendar",event:"Calendar",billing:"CreditCard",invoice:"CreditCard",payment:"CreditCard",pricing:"CreditCard",treatment:"ClipboardList",plan:"ClipboardList",task:"CheckSquare",exercise:"Dumbbell",workout:"Dumbbell",report:"BarChart3",analytics:"BarChart3",stats:"BarChart3",setting:"Settings",config:"Settings",profile:"User",account:"User",message:"MessageSquare",chat:"MessageSquare",inbox:"MessageSquare",product:"Package",item:"Package",catalog:"Package",order:"ShoppingCart",cart:"ShoppingCart",file:"FileText",document:"FileText",upload:"Upload",notification:"Bell",alert:"Bell",project:"FolderKanban",board:"FolderKanban",post:"PenSquare",blog:"PenSquare",article:"PenSquare",course:"GraduationCap",lesson:"GraduationCap",class:"GraduationCap",habit:"Target",goal:"Target",streak:"Flame",progress:"TrendingUp",feature:"Sparkles",subscription:"CreditCard",price:"CreditCard",recipe:"ChefHat",food:"UtensilsCrossed",meal:"UtensilsCrossed",pet:"PawPrint",animal:"PawPrint",music:"Music",playlist:"ListMusic",song:"Music",photo:"Image",image:"Image",gallery:"Images",video:"Video",movie:"Film",map:"MapPin",location:"MapPin",place:"MapPin",search:"Search",explore:"Compass",inventory:"Boxes",stock:"Boxes",warehouse:"Warehouse",review:"Star",rating:"Star",feedback:"Star",log:"ScrollText",history:"Clock",activity:"Activity"};function Jo(t){let e=t.toLowerCase().replace(/[^a-z]/g,"");for(let[r,n]of Object.entries(op))if(e.includes(r))return n;return"Circle"}function Un(t){return t.split("-").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" ")}function Ct(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/\{/g,"&#123;").replace(/\}/g,"&#125;")}function sp(t){if(t.authModel==="none")return null;let e=["/login","/register","/forgot-password","/reset-password","/api/auth","/api/health","/api/webhooks","/api/admin/seed","/images","/assets"];if(t.publicPages&&Array.isArray(t.publicPages))for(let s of t.publicPages){if(typeof s!="string"||s.length<1)continue;let i=s.replace(/[\u201C\u201D\u201E\u201F\u2018\u2019\u2033\u2036]/g,"").trim();if(!i)continue;let a=i.startsWith("/")?i:"/"+i;e.includes(a)||e.push(a)}let r=e.filter(s=>s==="/"),n=e.filter(s=>s!=="/"),o=[];o.push('import { NextRequest, NextResponse } from "next/server";'),o.push(""),o.push("const PUBLIC_PREFIXES = [");for(let s of n)o.push(' "'+s+'",');return o.push("];"),o.push(""),r.length>0&&(o.push('const PUBLIC_EXACT = ["'+r.join('", "')+'"];'),o.push("")),o.push("export function middleware(req: NextRequest) {"),o.push(" const { pathname, search } = req.nextUrl;"),o.push(""),r.length>0&&o.push(" if (PUBLIC_EXACT.includes(pathname)) return NextResponse.next();"),o.push(" if (PUBLIC_PREFIXES.some((p) => pathname.startsWith(p))) return NextResponse.next();"),o.push(""),o.push(' const token = req.cookies.get("better-auth.session_token")?.value || req.cookies.get("__Secure-better-auth.session_token")?.value;'),o.push(" if (!token) {"),o.push(' const loginUrl = new URL("/login", req.url);'),o.push(" const params = new URLSearchParams(search);"),o.push(' for (const key of ["verified", "error"]) {'),o.push(" const v = params.get(key);"),o.push(" if (v) loginUrl.searchParams.set(key, v);"),o.push(" }"),o.push(" return NextResponse.redirect(loginUrl);"),o.push(" }"),o.push(""),o.push(" return NextResponse.next();"),o.push("}"),o.push(""),o.push("export const config = {"),o.push(' matcher: ["/((?!_next|static|favicon\\\\.ico).*)"],'),o.push("};"),o.push(""),o.join(`
4744
+ `)}function ip(t){if(t.navStyle==="none")return null;let e=["/api","/login","/register","/sign-in","/sign-up","/admin","/pricing","/about","/contact","/terms","/privacy","/onboarding","/join","/forgot-password","/reset-password"],n=(t.pages??[]).filter(l=>{let c=l.path??l.route??"";return c==="/"||c===""||c.includes("[")||c.replace(/^\//,"").split("/").length>1?!1:!e.some(d=>c.startsWith(d))}).map(l=>{let c=l.path??l.route??"",p=c.startsWith("/")?c:"/"+c,d=l.name??Un(c.replace(/^\//,"")),m=Jo(d);return{label:d,href:p,icon:m}});n.some(l=>l.href==="/dashboard")||n.unshift({label:"Dashboard",href:"/dashboard",icon:"Home"});let o=t.authModel==="none",s=[...new Set(n.map(l=>l.icon))];o||s.push("LogOut");let i=Un(t.name);if(t.navStyle==="topbar"){let l=[];l.push('"use client";'),l.push(""),l.push('import Link from "next/link";'),l.push('import { usePathname } from "next/navigation";'),o||l.push('import { authClient } from "@/lib/auth-client";'),l.push('import { Button } from "@/components/ui/button";'),l.push('import { cn } from "@/lib/utils";'),l.push("import { "+s.join(", ")+' } from "lucide-react";'),l.push(""),l.push("interface TopNavProps {"),l.push(" user: { name: string | null; email: string; role?: string | undefined };"),l.push("}"),l.push(""),l.push("const NAV_ITEMS = [");for(let c of n)l.push(' { label: "'+c.label+'", href: "'+c.href+'", icon: '+c.icon+" },");return l.push("];"),l.push(""),l.push("export default function TopNav({ user }: TopNavProps) {"),l.push(" const pathname = usePathname();"),l.push(""),l.push(" return ("),l.push(' <nav className="border-b bg-card">'),l.push(' <div className="mx-auto flex h-14 max-w-7xl items-center justify-between px-4">'),l.push(' <div className="flex items-center gap-6">'),l.push(' <span className="text-lg font-semibold">'+i+"</span>"),l.push(' <div className="flex items-center gap-1">'),l.push(" {NAV_ITEMS.map((item) => ("),l.push(" <Link"),l.push(" key={item.href}"),l.push(" href={item.href}"),l.push(' aria-current={pathname === item.href ? "page" : undefined}'),l.push(" className={cn("),l.push(' "flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-colors",'),l.push(' pathname === item.href ? "bg-primary/10 text-primary" : "text-muted-foreground hover:text-foreground"'),l.push(" )}"),l.push(" >"),l.push(' <item.icon className="h-4 w-4" />'),l.push(" {item.label}"),l.push(" </Link>"),l.push(" ))}"),l.push(" </div>"),l.push(" </div>"),o?l.push(' <span className="text-sm text-muted-foreground">{user.name}</span>'):(l.push(' <div className="flex items-center gap-2">'),l.push(' <span className="text-sm text-muted-foreground">{user.email}</span>'),l.push(" <Button"),l.push(' variant="ghost"'),l.push(' size="sm"'),l.push(' onClick={() => authClient.signOut({ fetchOptions: { onSuccess: () => { window.location.href = "/login"; } } })}'),l.push(" >"),l.push(' <LogOut className="h-4 w-4" />'),l.push(" </Button>"),l.push(" </div>")),l.push(" </div>"),l.push(" </nav>"),l.push(" );"),l.push("}"),l.push(""),{path:"components/topnav.tsx",content:l.join(`
4745
+ `)}}let a=[];a.push('"use client";'),a.push(""),a.push('import { useState } from "react";'),a.push('import Link from "next/link";'),a.push('import { usePathname } from "next/navigation";'),o||a.push('import { authClient } from "@/lib/auth-client";'),a.push('import { Button } from "@/components/ui/button";'),a.push('import { Sheet, SheetContent, SheetTrigger, SheetTitle } from "@/components/ui/sheet";'),a.push('import { cn } from "@/lib/utils";'),a.push("import { Menu, "+s.join(", ")+' } from "lucide-react";'),a.push(""),a.push("interface SidebarProps {"),a.push(" user: { name: string | null; email: string; role?: string | undefined };"),a.push("}"),a.push(""),a.push("const NAV_ITEMS = [");for(let l of n)a.push(' { label: "'+l.label+'", href: "'+l.href+'", icon: '+l.icon+" },");return a.push("];"),a.push(""),a.push('function NavContent({ pathname, user, onNavigate }: { pathname: string; user: SidebarProps["user"]; onNavigate?: () => void }) {'),a.push(" return ("),a.push(" <>"),a.push(' <div className="flex h-14 items-center border-b px-4">'),a.push(' <span className="text-lg font-semibold">'+i+"</span>"),a.push(" </div>"),a.push(' <nav className="flex-1 space-y-1 p-2">'),a.push(" {NAV_ITEMS.map((item) => ("),a.push(" <Link"),a.push(" key={item.href}"),a.push(" href={item.href}"),a.push(" onClick={onNavigate}"),a.push(' aria-current={pathname === item.href ? "page" : undefined}'),a.push(" className={cn("),a.push(' "flex items-center gap-3 rounded-md px-3 py-2.5 text-sm font-medium transition-colors",'),a.push(' pathname === item.href ? "bg-primary/10 text-primary" : "text-muted-foreground hover:bg-muted hover:text-foreground"'),a.push(" )}"),a.push(" >"),a.push(' <item.icon className="h-4 w-4" />'),a.push(" {item.label}"),a.push(" </Link>"),a.push(" ))}"),a.push(" </nav>"),a.push(' <div className="border-t p-4">'),a.push(' <div className="flex items-center gap-3">'),a.push(' <div className="flex-1 truncate">'),a.push(' <p className="truncate text-sm font-medium">{user.name ?? "User"}</p>'),a.push(' <p className="truncate text-xs text-muted-foreground">{user.email}</p>'),a.push(" </div>"),o||(a.push(" <Button"),a.push(' variant="ghost"'),a.push(' size="icon"'),a.push(' onClick={() => authClient.signOut({ fetchOptions: { onSuccess: () => { window.location.href = "/login"; } } })}'),a.push(" >"),a.push(' <LogOut className="h-4 w-4" />'),a.push(" </Button>")),a.push(" </div>"),a.push(" </div>"),a.push(" </>"),a.push(" );"),a.push("}"),a.push(""),a.push("export default function Sidebar({ user }: SidebarProps) {"),a.push(" const pathname = usePathname();"),a.push(" const [open, setOpen] = useState(false);"),a.push(""),a.push(" return ("),a.push(" <>"),a.push(' <aside className="hidden md:flex h-screen w-64 flex-col border-r bg-card">'),a.push(" <NavContent pathname={pathname} user={user} />"),a.push(" </aside>"),a.push(' <div className="sticky top-0 z-[var(--z-sticky)] flex h-14 items-center gap-3 border-b bg-card px-4 md:hidden">'),a.push(" <Sheet open={open} onOpenChange={setOpen}>"),a.push(" <SheetTrigger asChild>"),a.push(' <Button variant="ghost" size="icon" className="-ml-2">'),a.push(' <Menu className="h-5 w-5" />'),a.push(" </Button>"),a.push(" </SheetTrigger>"),a.push(' <SheetContent side="left" className="w-64 p-0">'),a.push(' <SheetTitle className="sr-only">Navigation</SheetTitle>'),a.push(' <div className="flex h-full flex-col">'),a.push(" <NavContent pathname={pathname} user={user} onNavigate={() => setOpen(false)} />"),a.push(" </div>"),a.push(" </SheetContent>"),a.push(" </Sheet>"),a.push(' <span className="text-lg font-semibold">'+i+"</span>"),a.push(" </div>"),a.push(" </>"),a.push(" );"),a.push("}"),a.push(""),{path:"components/sidebar.tsx",content:a.join(`
4746
+ `)}}function ap(t){if(!t.roles||t.roles.length===0)return null;let e=t.roles,r=t.defaultRole??e[0],n=[];n.push("export type Role = "+e.map(o=>'"'+o+'"').join(" | ")+";"),n.push(""),n.push("export const ROLES = ["+e.map(o=>'"'+o+'"').join(", ")+"] as const;"),n.push(""),n.push('export const DEFAULT_ROLE: Role = "'+r+'";'),n.push(""),n.push("export const ROLE_LABELS: Record<Role, string> = {");for(let o of e){let s=o.charAt(0).toUpperCase()+o.slice(1);n.push(' "'+o+'": "'+s+'",')}return n.push("};"),n.push(""),n.push("export function getUserRole(user: Record<string, unknown>): Role {"),n.push(" const role = (user.role as string) ?? DEFAULT_ROLE;"),n.push(" if (ROLES.includes(role as Role)) return role as Role;"),n.push(" return DEFAULT_ROLE;"),n.push("}"),n.push(""),n.push("export function hasRole(userRole: string | undefined, required: Role | Role[]): boolean {"),n.push(" if (!userRole) return false;"),n.push(" const allowed = Array.isArray(required) ? required : [required];"),n.push(" return allowed.includes(userRole as Role);"),n.push("}"),n.push(""),n.join(`
4747
+ `)}function lp(t){let e=Un(t.name);if(t.authModel==="none"){let s=[];return s.push("export default function HomePage() {"),s.push(" return ("),s.push(' <main className="flex min-h-screen flex-col items-center justify-center p-8">'),s.push(' <h1 className="text-4xl font-bold">'+Ct(e)+"</h1>"),t.summary&&s.push(' <p className="mt-4 text-lg text-muted-foreground">'+Ct(t.summary)+"</p>"),s.push(" </main>"),s.push(" );"),s.push("}"),s.push(""),s.join(`
4748
+ `)}let r=t.publicPages?.includes("/"),n=t.design?.landingTone;if(r&&n){let s=[];return s.push('import Link from "next/link";'),s.push(""),s.push("export default function HomePage() {"),s.push(" return ("),s.push(' <main className="flex min-h-screen flex-col">'),s.push(' <section className="flex flex-1 flex-col items-center justify-center gap-6 px-4 py-24 text-center">'),s.push(' <h1 className="text-5xl font-bold tracking-tight">'+Ct(e)+"</h1>"),t.summary&&s.push(' <p className="max-w-2xl text-xl text-muted-foreground">'+Ct(t.summary)+"</p>"),s.push(' <div className="flex gap-4">'),s.push(' <Link href="/register" className="inline-flex h-11 items-center rounded-md bg-primary px-8 text-sm font-medium text-primary-foreground hover:bg-primary/90">'),s.push(" Get Started"),s.push(" </Link>"),s.push(' <Link href="/login" className="inline-flex h-11 items-center rounded-md border px-8 text-sm font-medium hover:bg-muted">'),s.push(" Sign In"),s.push(" </Link>"),s.push(" </div>"),s.push(" </section>"),s.push(" </main>"),s.push(" );"),s.push("}"),s.push(""),s.join(`
4749
+ `)}if(r){let s=[];return s.push('import Link from "next/link";'),s.push(""),s.push("export default function HomePage() {"),s.push(" return ("),s.push(' <main className="flex min-h-screen flex-col items-center justify-center gap-6 p-8 text-center">'),s.push(' <h1 className="text-4xl font-bold">'+Ct(e)+"</h1>"),t.summary&&s.push(' <p className="text-lg text-muted-foreground">'+Ct(t.summary)+"</p>"),s.push(' <Link href="/login" className="inline-flex h-10 items-center rounded-md bg-primary px-6 text-sm font-medium text-primary-foreground hover:bg-primary/90">'),s.push(" Sign In"),s.push(" </Link>"),s.push(" </main>"),s.push(" );"),s.push("}"),s.push(""),s.join(`
4750
+ `)}let o=[];return o.push('import { headers } from "next/headers";'),o.push('import { redirect } from "next/navigation";'),o.push('import { auth } from "@/lib/auth";'),o.push(""),o.push("export default async function HomePage() {"),o.push(" const session = await auth.api.getSession({ headers: await headers() });"),o.push(' if (session) redirect("/dashboard");'),o.push(' redirect("/login");'),o.push("}"),o.push(""),o.join(`
4751
+ `)}function cp(t,e){let r=t.authModel==="none",n=[];r||(n.push('import { Suspense } from "react";'),n.push('import { headers } from "next/headers";'),n.push('import { redirect } from "next/navigation";'),n.push('import { auth } from "@/lib/auth";'),n.push('import { VerifiedBanner } from "@/components/auth/verified-banner";')),t.navStyle==="topbar"?n.push('import TopNav from "@/components/topnav";'):t.navStyle!=="none"&&n.push('import Sidebar from "@/components/sidebar";'),!r&&t.roles&&t.roles.length>0&&n.push('import { getUserRole } from "@/lib/roles";'),n.push(""),n.push("export default async function DashboardLayout({ children }: { children: React.ReactNode }) {"),r?n.push(' const user = { name: "Guest", email: "" };'):(n.push(" const session = await auth.api.getSession({ headers: await headers() });"),n.push(' if (!session) redirect("/login");'),n.push(""),t.roles&&t.roles.length>0?(n.push(" const role = getUserRole(session.user as Record<string, unknown>);"),n.push(" const user = { name: session.user.name, email: session.user.email, role };")):(n.push(" const user = {"),n.push(" name: session.user.name,"),n.push(" email: session.user.email,"),n.push(" role: (session.user as Record<string, unknown>).role as string | undefined,"),n.push(" };"))),n.push("");let o=r?"":"<Suspense fallback={null}><VerifiedBanner /></Suspense>";return t.navStyle==="topbar"?(n.push(" return ("),n.push(' <div className="min-h-screen">'),n.push(" <TopNav user={user} />"),n.push(' <main className="mx-auto max-w-7xl p-6">'+o+"{children}</main>"),n.push(" </div>"),n.push(" );")):t.navStyle==="none"?(n.push(" return ("),n.push(' <div className="min-h-screen">'),n.push(' <main className="mx-auto max-w-5xl p-6">'+o+"{children}</main>"),n.push(" </div>"),n.push(" );")):(n.push(" return ("),n.push(' <div className="flex flex-col md:flex-row min-h-screen">'),n.push(" <Sidebar user={user} />"),n.push(' <main className="flex-1 overflow-x-hidden p-4 md:p-6">'+o+"{children}</main>"),n.push(" </div>"),n.push(" );")),n.push("}"),n.push(""),n.join(`
4752
+ `)}function dp(t){let e=Un(t.name),r=t.dataModel??[],n=[];if(r.length>0){let o=r.map(i=>Jo(i.entity??i.name??"item")),s=[...new Set(o)];n.push('import { Card, CardContent } from "@/components/ui/card";'),n.push("import { "+s.join(", ")+' } from "lucide-react";'),n.push("")}if(n.push("export default function DashboardPage() {"),n.push(" return ("),n.push(' <div className="space-y-6">'),n.push(" <div>"),n.push(' <h1 className="text-3xl font-bold">'+Ct(e)+"</h1>"),t.summary&&n.push(' <p className="mt-1 text-muted-foreground">'+Ct(t.summary)+"</p>"),n.push(" </div>"),r.length>0){n.push(' <div className="rounded-lg border p-8 text-center">'),n.push(' <h2 className="text-lg font-semibold">Get started</h2>'),n.push(` <p className="mt-1 text-sm text-muted-foreground">Here's what you can do</p>`),n.push(' <div className="mt-6 grid gap-3 sm:grid-cols-2 text-left">');for(let o of r){let s=o.entity??o.name??"Item",i=Jo(s),a=Un(s.replace(/_/g,"-"));n.push(' <div className="flex items-center gap-3 rounded-md border p-3">'),n.push(" <"+i+' className="h-5 w-5 text-muted-foreground" />'),n.push(' <span className="text-sm font-medium">Add your first '+a+"</span>"),n.push(" </div>")}n.push(" </div>"),n.push(" </div>")}return n.push(" </div>"),n.push(" );"),n.push("}"),n.push(""),n.join(`
4753
+ `)}function up(t,e=!1){if(!t.multiTenant)return null;let r=[];return e?(r.push('import { pgTable, text, timestamp, index } from "drizzle-orm/pg-core";'),r.push('import { user } from "./auth";'),r.push(""),r.push('export const organization = pgTable("organization", {'),r.push(' id: text("id").primaryKey(),'),r.push(' name: text("name").notNull(),'),r.push(' slug: text("slug").unique().notNull(),'),r.push(' createdAt: timestamp("created_at").defaultNow().notNull(),'),r.push(' updatedAt: timestamp("updated_at").defaultNow().notNull(),'),r.push("});"),r.push(""),r.push("export const orgMember = pgTable(")):(r.push('import { sqliteTable, text, index } from "drizzle-orm/sqlite-core";'),r.push('import { sql } from "drizzle-orm";'),r.push('import { user } from "./auth";'),r.push(""),r.push('export const organization = sqliteTable("organization", {'),r.push(' id: text("id").primaryKey(),'),r.push(' name: text("name").notNull(),'),r.push(' slug: text("slug").unique().notNull(),'),r.push(' createdAt: text("created_at").default(sql`CURRENT_TIMESTAMP`).notNull(),'),r.push(' updatedAt: text("updated_at").default(sql`CURRENT_TIMESTAMP`).notNull(),'),r.push("});"),r.push(""),r.push("export const orgMember = sqliteTable(")),r.push(' "org_member",'),r.push(" {"),r.push(' id: text("id").primaryKey(),'),r.push(' orgId: text("org_id").notNull().references(() => organization.id),'),r.push(' userId: text("user_id").notNull().references(() => user.id),'),r.push(' role: text("role").notNull(),'),e?r.push(' joinedAt: timestamp("joined_at").defaultNow().notNull(),'):r.push(' joinedAt: text("joined_at").default(sql`CURRENT_TIMESTAMP`).notNull(),'),r.push(" },"),r.push(" (table) => ({"),r.push(' orgIdx: index("org_member_org_idx").on(table.orgId),'),r.push(' userIdx: index("org_member_user_idx").on(table.userId),'),r.push(" }),"),r.push(");"),r.push(""),r.join(`
4754
+ `)}function pp(t){if(!t.multiTenant)return null;let e=[];return e.push('import { db } from "./db";'),e.push('import { organization, orgMember } from "@/db/schema/organization";'),e.push('import { eq } from "drizzle-orm";'),e.push(""),e.push("export async function getCurrentOrg(userId: string) {"),e.push(" const membership = await db"),e.push(" .select()"),e.push(" .from(orgMember)"),e.push(" .where(eq(orgMember.userId, userId))"),e.push(" .limit(1);"),e.push(" if (membership.length === 0) return null;"),e.push(" const org = await db"),e.push(" .select()"),e.push(" .from(organization)"),e.push(" .where(eq(organization.id, membership[0].orgId))"),e.push(" .limit(1);"),e.push(" return org[0] ?? null;"),e.push("}"),e.push(""),e.push("export async function getOrgMembers(orgId: string) {"),e.push(" return db"),e.push(" .select()"),e.push(" .from(orgMember)"),e.push(" .where(eq(orgMember.orgId, orgId));"),e.push("}"),e.push(""),e.push("export async function inviteToOrg(orgId: string, email: string, role: string) {"),e.push(" const id = crypto.randomUUID();"),e.push(" await db.insert(orgMember).values({"),e.push(" id,"),e.push(" orgId,"),e.push(" userId: email,"),e.push(" role,"),e.push(" });"),e.push(" return { id, orgId, email, role };"),e.push("}"),e.push(""),e.join(`
4755
+ `)}function mp(t){if(!t.multiTenant)return null;let e=[];return e.push('"use client";'),e.push(""),e.push("import {"),e.push(" DropdownMenu,"),e.push(" DropdownMenuContent,"),e.push(" DropdownMenuItem,"),e.push(" DropdownMenuTrigger,"),e.push('} from "@/components/ui/dropdown-menu";'),e.push('import { Button } from "@/components/ui/button";'),e.push('import { ChevronsUpDown } from "lucide-react";'),e.push(""),e.push("interface OrgSwitcherProps {"),e.push(" orgs: Array<{ id: string; name: string }>;"),e.push(" currentOrgId: string;"),e.push("}"),e.push(""),e.push("export default function OrgSwitcher({ orgs, currentOrgId }: OrgSwitcherProps) {"),e.push(" const currentOrg = orgs.find((o) => o.id === currentOrgId);"),e.push(""),e.push(" return ("),e.push(" <DropdownMenu>"),e.push(" <DropdownMenuTrigger asChild>"),e.push(' <Button variant="outline" className="w-full justify-between">'),e.push(' <span className="truncate">{currentOrg?.name ?? "Select org"}</span>'),e.push(' <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />'),e.push(" </Button>"),e.push(" </DropdownMenuTrigger>"),e.push(' <DropdownMenuContent className="w-56">'),e.push(" {orgs.map((org) => ("),e.push(" <DropdownMenuItem key={org.id}>"),e.push(" {org.name}"),e.push(" </DropdownMenuItem>"),e.push(" ))}"),e.push(" </DropdownMenuContent>"),e.push(" </DropdownMenu>"),e.push(" );"),e.push("}"),e.push(""),e.join(`
4756
+ `)}function hp(t,e,r){let n=[],o=t.split("-").map(c=>c.charAt(0).toUpperCase()+c.slice(1)).join(" ");n.push(`# ${o}`),n.push(""),e?.summary&&(n.push(e.summary),n.push(""));let s=e?.features??[];if(s.length>0){n.push("## Features"),n.push("");for(let c of s){let p=c.description?` \u2014 ${c.description}`:"";n.push(`- **${c.name}**${p}`)}n.push("")}n.push("## Tech Stack"),n.push(""),n.push("| Layer | Technology |"),n.push("|-------|------------|"),n.push("| Framework | Next.js 15 (App Router) |"),n.push("| Database | Mistflow Cloud (Postgres) + Drizzle ORM |"),n.push("| Auth | Better Auth (email/password, social login) |"),n.push("| Styling | Tailwind CSS + shadcn/ui |"),n.push("| Deployment | Mistflow Cloud |"),r.hasStripe&&n.push("| Payments | Stripe |"),r.hasResend&&n.push("| Email | Resend + React Email |"),r.hasStorage&&n.push("| File Storage | Mistflow Cloud (managed blob storage) |"),r.hasAdmin&&n.push("| Admin | Better Auth admin plugin |"),r.hasAI&&n.push("| AI | Vercel AI SDK + OpenAI |"),n.push("");let i=e?.pages??[];if(i.length>0){n.push("## Pages"),n.push(""),n.push("| Route | Description |"),n.push("|-------|-------------|");for(let c of i){let p=c.path??c.route??c.name??"",d=c.description??"";n.push(`| \`${p.startsWith("/")?p:"/"+p}\` | ${d} |`)}n.push("")}let a=e?.dataModel??[];if(a.length>0){n.push("## Data Model"),n.push("");for(let c of a){let p=c.entity??c.name??"Unknown";if(n.push(`### ${p}`),n.push(""),c.fields.length>0){if(typeof c.fields[0]=="string")n.push(`Fields: ${c.fields.join(", ")}`);else{n.push("| Field | Type |"),n.push("|-------|------|");for(let d of c.fields)n.push(`| ${d.name} | ${d.type} |`)}n.push("")}}}n.push("## Getting Started"),n.push(""),n.push("### Prerequisites"),n.push(""),n.push("- Node.js 20+"),n.push("- npm"),n.push(""),n.push("### Install"),n.push(""),n.push("The host AI calls mist_install (fire-and-poll) which handles this for you. Run manually with plain npm if needed:"),n.push(""),n.push("```bash"),n.push("npm install"),n.push("```"),n.push(""),n.push("### Set up environment"),n.push(""),n.push("Copy `.env.example` to `.env.local` and fill in the values:"),n.push(""),n.push("```bash"),n.push("cp .env.example .env.local"),n.push("```"),n.push(""),n.push("| Variable | Description | Required |"),n.push("|----------|-------------|----------|"),r.isNeon?n.push("| `DATABASE_URL` | Postgres connection URL | Yes |"):(n.push("| `TURSO_URL` | Database connection URL | Yes |"),n.push("| `TURSO_AUTH_TOKEN` | Database auth token | Yes |")),n.push("| `AUTH_SECRET` | Auth encryption secret (auto-generated) | Yes |"),r.hasStripe&&(n.push("| `STRIPE_SECRET_KEY` | Stripe secret key | Yes |"),n.push("| `STRIPE_WEBHOOK_SECRET` | Stripe webhook signing secret | Yes |"),n.push("| `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` | Stripe publishable key | Yes |")),r.hasResend&&(n.push("| `RESEND_API_KEY` | Email sending key | Managed by Mistflow in production; optional local override |"),n.push("| `EMAIL_FROM` | Sender email address | Managed by Mistflow in production; optional local override |")),(r.hasAI||r.hasStorage)&&n.push("| `MISTFLOW_RUNTIME_KEY` | Mistflow Cloud runtime key \u2014 authenticates AI gateway, file storage, and other managed features | Auto-provisioned at `mist_init` |"),r.hasAI&&n.push("| `OPENROUTER_API_KEY` | OpenRouter key for your-key fallback (only used when `MISTFLOW_RUNTIME_KEY` is unset) | Optional |"),n.push(""),n.push("### Local database"),n.push(""),r.isNeon?(n.push("For local development, start a local Postgres server:"),n.push(""),n.push("```bash"),n.push("# Using Docker:"),n.push("docker run -d --name postgres -e POSTGRES_PASSWORD=postgres -p 5432:5432 postgres:17"),n.push("# Or install via Homebrew: brew install postgresql@17 && brew services start postgresql@17"),n.push("```")):(n.push("For local development, start a local Turso server:"),n.push(""),n.push("```bash"),n.push("npx turso dev"),n.push("```")),n.push(""),n.push("Then set up the database:"),n.push(""),n.push("```bash"),n.push("npm run db:push"),n.push("```"),n.push(""),n.push("### Run"),n.push(""),n.push("```bash"),n.push("npm run dev"),n.push("```"),n.push(""),n.push("Open [http://localhost:3000](http://localhost:3000)."),n.push(""),n.push("## Project Structure"),n.push(""),n.push("```"),n.push("app/"),n.push(" (auth)/ Login and registration pages"),n.push(" (dashboard)/ Authenticated app pages"),r.hasAdmin&&n.push(" admin/ Admin panel pages (at /admin/...)"),n.push(" home/ Public landing page (served via / \u2192 /home redirect)"),n.push(" api/ API routes (auth, health, webhooks)"),n.push(" layout.tsx Root layout with fonts and providers"),n.push(" globals.css Design tokens and Tailwind config"),n.push("components/ Reusable UI components"),n.push("db/"),n.push(" schema/ Database table definitions"),n.push(" index.ts Schema exports"),n.push("lib/"),n.push(" auth.ts Better Auth server config"),n.push(" auth-client.ts Better Auth client config"),n.push(` db.ts ${r.isNeon?"Postgres":"SQLite"} database connection`),r.hasStripe&&n.push(" stripe.ts Stripe client"),r.hasResend&&(n.push(" resend.ts Resend client"),n.push(" email.ts Email send helpers")),n.push(" server/storage.ts File upload/download helpers (managed R2)"),r.hasAI&&(n.push(" ai.ts Back-compat export for server AI helpers"),n.push(" server/ai.ts Server-only AI Gateway and provider helpers")),r.hasResend&&n.push("emails/ React Email templates"),n.push("```"),n.push(""),n.push("## Deploy"),n.push(""),n.push("Deploy to production with Mistflow:"),n.push(""),n.push("```"),n.push("# In your AI editor (Claude Code, Cursor, etc.):"),n.push("mist_deploy action='deploy'"),n.push("```"),n.push(""),n.push("Your app will be live at `https://<app-name>.mistflow.app`."),n.push("");let l=e?.pickedDirection;if(l||e?.design){n.push("## Design"),n.push(""),typeof l?.name=="string"&&n.push(`- **Picked direction**: ${l.name}`);let c=l?.fonts&&typeof l.fonts=="object"?l.fonts:void 0;c?(typeof c.display=="string"&&n.push(`- **Display font**: ${c.display}`),typeof c.body=="string"&&n.push(`- **Body font**: ${c.body}`)):e?.design?.fonts&&(n.push(`- **Heading font**: ${e.design.fonts.heading}`),n.push(`- **Body font**: ${e.design.fonts.body}`));let p=l?.colors&&typeof l.colors=="object"?l.colors:void 0;typeof p?.accent=="string"?n.push(`- **Accent color**: ${p.accent}`):e?.design?.accentColor&&n.push(`- **Accent color**: ${e.design.accentColor}`),e?.design?.borderRadius&&n.push(`- **Border radius**: ${e.design.borderRadius}`),!l&&e?.design?.tone&&n.push(`- **Tone**: ${e.design.tone}`),n.push("")}return n.push("---"),n.push(""),n.push("Built with [Mistflow](https://mistflow.ai)"),n.push(""),n.join(`
4757
+ `)}async function gp(t,e){if(!Te())return Oe("scaffold a new project");if(!rn())return u(JSON.stringify({status:"node_version_unsupported",code:"node_version_unsupported",message:qt(),nextAction:"Tell the user their Node version is too old, paste the upgrade command from `message`, and stop. Do NOT proceed with mist_init \u2014 the scaffolded app would build-fail at deploy time."}),!0);let{name:r,plan:n,path:o,planId:s,sessionId:i}=t,a,l=[],c,p=null,d=n;if(typeof d=="string")try{let x=JSON.parse(d);x&&typeof x=="object"&&!Array.isArray(x)&&"plan"in x?d=x.plan:d=x}catch{return u("mist_init received `plan` as a string, but it is not valid JSON. Pass the plan object directly or pass planId from mist_plan.",!0)}if(!d&&s){if(p=Nu(s),!p)return u(`No plan found for planId '${s}'. Call mist_plan first, or pass the plan object inline.`,!0);d=p.plan}if(!d&&i){let x=await vo(i);x&&(d=x.plan)}if(!d||typeof d!="object"||Array.isArray(d))return u("mist_init requires a plan object or a valid planId from mist_plan. Refusing to scaffold without a structured plan because mist_implement would not know what to build.",!0);let m=o??p?.scaffoldTargetPath;if(!m)return u("mist_init requires an explicit 'path' unless the planId cache includes a scaffoldTargetPath. Call mist_init with the planId returned by mist_plan, pass the absolute scaffold directory, or pass { sessionId, path } in session mode.",!0);if(!Ru(m))return u(`mist_init 'path' must be an absolute path \u2014 received '${m}'. Pass the full absolute path to the target directory.`,!0);let h=ea(m),S=r??(typeof d.name=="string"?d.name:void 0);if(!S)return u("mist_init requires a human-readable app name. Pass `name`, or call it with a planId whose cached plan includes a name.",!0);let I=d?.design,y=d,v=y?.pickedDirection??y?.designDirection??void 0,E=Ju(S,v),J=typeof d.authModel=="string"?d.authModel:void 0,H=J==="none",ne=Zu(d),ce=rp(d,J),ee=d?Bt(d,"upload","file storage","image upload","profile picture","attachment","gallery","media","blob"):!1,q=d?Bt(d,"admin panel","admin dashboard","admin management"):!1,P=d?ep(d):!1,re=d?np(d):!1,z=d?tp(d):!1,ie=z,_=typeof d.dbProvider=="string"?d.dbProvider:"neon",B=Array.isArray(d.dataModel)&&d.dataModel.length>0,L=_==="none"||H&&!B&&!ee,K=L?"none":_,te=K==="neon";if(!Hu(h))return u(zu(h,d?.name),!0);an(h,{recursive:!0});try{try{let w=me(h,".mistflow","rules");an(w,{recursive:!0}),gt(me(w,"design-quality.md"),wr),gt(me(w,"landing.md"),vr)}catch(w){console.error("Could not write design rule files:",w instanceof Error?w.message:w)}try{let w=me($n(h),".mistflow","mockups");if(Be(w)){let C=Zi(w).filter(U=>U.endsWith(".html"));if(C.length>0){let U=me(h,".mistflow","mockups");an(U,{recursive:!0});for(let Ie of C)Pu(me(w,Ie),me(U,Ie));console.error(`Copied ${C.length} mockup file(s) into project`)}}}catch(w){console.error("Could not copy mockup files:",w instanceof Error?w.message:w)}let x=null;try{x=await lo("nextjs")}catch(w){console.error("Could not fetch scaffold from API, using minimal scaffold:",w instanceof Error?w.message:w)}if(x){let w=S.toLowerCase().replace(/[^a-z0-9-]/g,"-");for(let k of x.files){if(k.path==="package.json"||k.path==="middleware.ts"||k.path==="components/sidebar.tsx"||k.path==="components/topnav.tsx"||k.path==="app/(dashboard)/layout.tsx"||k.path==="app/(dashboard)/page.tsx"||k.path==="app/(dashboard)/dashboard/page.tsx"||L&&(k.path==="lib/db.ts"||k.path==="drizzle.config.ts"||k.path==="db/index.ts"||k.path.startsWith("db/schema/"))||H&&(k.path.includes("(auth)")||k.path.includes("(admin)")||k.path.startsWith("app/admin/")||k.path.includes("components/auth/")||k.path.includes("admin-sidebar")||k.path.includes("app/api/auth/")||k.path.includes("app/api/admin/seed")||k.path==="lib/auth.ts"||k.path==="lib/auth-client.ts"||k.path==="db/schema/auth.ts"||k.path==="db/schema/index.ts"||k.path==="db/index.ts")||!ne&&(k.path.includes("stripe")||k.path.includes("webhook/stripe"))||!ce&&(k.path.includes("resend")||k.path.includes("emails/"))||!q&&(k.path.includes("(admin)")||k.path.startsWith("app/admin/")||k.path.includes("admin-sidebar"))||te&&(k.path==="lib/db.ts"||k.path==="lib/auth.ts"||k.path==="drizzle.config.ts"||k.path==="db/schema/auth.ts"))continue;let W=k.content.replace(/\{\{APP_NAME\}\}/g,S).replace(/\{\{WORKER_NAME\}\}/g,w);if(te&&k.path==="next.config.ts"&&(W=W.replace(/serverExternalPackages:\s*\[[^\]]*\],?/g,'serverExternalPackages: ["@electric-sql/pglite"],')),k.path==="next.config.ts"){let ge=ju(h);ge&&(console.error(`[init] Project is inside monorepo at ${ge} \u2014 adding outputFileTracingRoot`),W.includes("outputFileTracingRoot")||(W=W.replace('import type { NextConfig } from "next";',`import type { NextConfig } from "next";
5052
4758
  import { dirname } from "path";
5053
4759
  import { fileURLToPath } from "url";
5054
4760
 
5055
- const __dirname = dirname(fileURLToPath(import.meta.url));`),H=H.replace("images: {",`outputFileTracingRoot: __dirname,
5056
- images: {`)))}!te&&S.path.includes("sidebar")&&(H=H.replace(/\{user\.role === "admin"[\s\S]*?<\/Link>\s*\)\}/m,""),H=H.replace(/, Shield/g,"")),M(h,S.path,H)}let _={...R.dependencies},$={...R.devDependencies};if(_["drizzle-zod"]||(_["drizzle-zod"]="^0.5.1"),w&&delete _["better-auth"],v&&(delete _["drizzle-orm"],delete _["@libsql/client"],delete _["@neondatabase/serverless"],delete $["drizzle-kit"],delete $["@electric-sql/pglite"]),j&&(delete _["@libsql/client"],_["@neondatabase/serverless"]="^0.10.0",$["@electric-sql/pglite"]="^0.2.0"),A&&(_.stripe="^17.0.0"),J&&(_.resend="^4.0.0",_["@react-email/components"]="^0.0.31"),le&&(_.ai="^4.0.0",_["@ai-sdk/openai"]="^1.0.0",_.openai="^4.0.0",_["server-only"]="^0.0.1",_["zod-to-json-schema"]="3.24.6"),Q&&j){let S=$r({appName:U,appVersion:"0.0.1",useAiChatManifest:Dn(u)});for(let[H,Ce]of Object.entries(S.packageJsonDeps))H==="zod-to-json-schema"&&_[H]||(_[H]=Ce)}if(q&&j)for(let S of ko.dependencies)S.dependencyType==="devDependency"?$[S.packageName]||($[S.packageName]=S.versionRange):_[S.packageName]||(_[S.packageName]=S.versionRange);let Ue={"@noble/ciphers":"^1.3.0"},hn={react:"19.1.0","react-dom":"19.1.0",punycode:"^2.3.1","zod-to-json-schema":"3.24.6"},re={dev:"next dev",build:"next build","build:cf":"opennextjs-cloudflare build",start:"next start",lint:"next lint",...v?{}:{"db:push":"drizzle-kit push","db:studio":"drizzle-kit studio"}};if(M(h,"package.json",JSON.stringify({name:U,version:"0.1.0",private:!0,scripts:re,dependencies:_,devDependencies:$,optionalDependencies:Ue,overrides:hn},null,2)),R.methodology){let S=R.methodology;j||(S=S.replace(/import \{ pgTable, text, timestamp(?:, boolean)? \} from "drizzle-orm\/pg-core";/g,`import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
4761
+ const __dirname = dirname(fileURLToPath(import.meta.url));`),W=W.replace("images: {",`outputFileTracingRoot: __dirname,
4762
+ images: {`)))}!q&&k.path.includes("sidebar")&&(W=W.replace(/\{user\.role === "admin"[\s\S]*?<\/Link>\s*\)\}/m,""),W=W.replace(/, Shield/g,"")),A(h,k.path,W)}let C={...x.dependencies},U={...x.devDependencies};if(C["drizzle-zod"]||(C["drizzle-zod"]="^0.5.1"),H&&delete C["better-auth"],L&&(delete C["drizzle-orm"],delete C["@libsql/client"],delete C["@neondatabase/serverless"],delete U["drizzle-kit"],delete U["@electric-sql/pglite"]),te&&(delete C["@libsql/client"],C["@neondatabase/serverless"]="^0.10.0",U["@electric-sql/pglite"]="^0.2.0"),ne&&(C.stripe="^17.0.0"),ce&&(C.resend="^4.0.0",C["@react-email/components"]="^0.0.31"),P&&(C.ai="^4.0.0",C["@ai-sdk/openai"]="^1.0.0",C.openai="^4.0.0",C["server-only"]="^0.0.1",C["zod-to-json-schema"]="3.24.6"),re&&te){let k=Fo({appName:S,appVersion:"0.0.1",useAiChatManifest:nn(d)});for(let[W,ge]of Object.entries(k.packageJsonDeps))W==="zod-to-json-schema"&&C[W]||(C[W]=ge)}if(z&&te)for(let k of kr.dependencies)k.dependencyType==="devDependency"?U[k.packageName]||(U[k.packageName]=k.versionRange):C[k.packageName]||(C[k.packageName]=k.versionRange);let Ie={"@noble/ciphers":"^1.3.0"},ns={react:"19.1.0","react-dom":"19.1.0",punycode:"^2.3.1","zod-to-json-schema":"3.24.6"},gn={dev:"next dev",build:"next build","build:cf":"opennextjs-cloudflare build",start:"next start",lint:"next lint",...L?{}:{"db:push":"drizzle-kit push","db:studio":"drizzle-kit studio"}};if(A(h,"package.json",JSON.stringify({name:S,version:"0.1.0",private:!0,scripts:gn,dependencies:C,devDependencies:U,optionalDependencies:Ie,overrides:ns},null,2)),x.methodology){let k=x.methodology;te||(k=k.replace(/import \{ pgTable, text, timestamp(?:, boolean)? \} from "drizzle-orm\/pg-core";/g,`import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
5057
4763
  import { sql } from "drizzle-orm";`).replace(/import \{ pgTable, text \} from "drizzle-orm\/pg-core";/g,`import { sqliteTable, text } from "drizzle-orm/sqlite-core";
5058
- import { sql } from "drizzle-orm";`).replace(/pgTable/g,"sqliteTable").replace(/drizzle-orm\/pg-core/g,"drizzle-orm/sqlite-core").replace(/timestamp\("created_at"\)\.notNull\(\)\.defaultNow\(\)/g,'text("created_at").notNull().default(sql`(CURRENT_TIMESTAMP)`)').replace(/timestamp\("updated_at"\)\.notNull\(\)\.defaultNow\(\)/g,'text("updated_at").notNull().default(sql`(CURRENT_TIMESTAMP)`)').replace(/timestamp\("([a-z_]+)"\)/g,'text("$1")').replace(/boolean\("([a-z_]+)"\)\.default\(false\)/g,'integer("$1", { mode: "boolean" }).default(false)')),S=Fr(S),S=S+`
5059
-
5060
- `+Tu+`
5061
- `,M(h,"AGENTS.md",S),M(h,"CLAUDE.md",S),Nu(h,S)}R.skills&&Object.keys(R.skills).length>0&&Mu(h,R.skills);let se=a??u?.designMd;if(se&&M(h,"DESIGN.md",se),c&&M(h,"PLAN.md",c),j)if(M(h,"lib/db.ts",['import { neon } from "@neondatabase/serverless";','import { drizzle as drizzleNeon } from "drizzle-orm/neon-http";','import * as schema from "@/db/schema";',"","// eslint-disable-next-line @typescript-eslint/no-explicit-any","let _db: any = null;","","function getDb() {"," if (!_db) {",' if (process.env.DATABASE_URL && process.env.DATABASE_URL !== "pglite") {'," // Production / remote Postgres. Pass `schema` so `db.query.<table>`"," // (Drizzle's relational query builder) is populated \u2014 without it,"," // `db.query.<anything>` is undefined and crashes at runtime."," const sql = neon(process.env.DATABASE_URL);"," _db = drizzleNeon(sql, { schema });",' } else if (process.env.NODE_ENV !== "production") {'," // Local dev \u2014 PGlite (zero-install embedded Postgres). Gating on"," // NODE_ENV !== 'production' lets webpack/esbuild dead-code-eliminate"," // this entire branch in `next build` and the Cloudflare Worker"," // bundle, so pglite + its 30MB WASM never reach prod. In `next dev`"," // the branch is live, the static require resolves ./db-local, and"," // pglite (declared in next.config.ts's serverExternalPackages) is"," // left as an external runtime import. No bundler-evasion tricks."," // eslint-disable-next-line @typescript-eslint/no-require-imports",' const { createLocalDb } = require("./db-local");'," _db = createLocalDb();"," } else {"," throw new Error(",` "DATABASE_URL is not set in production. The Mistflow deploy pipeline injects it automatically \u2014 check the project's env vars in the dashboard."`," );"," }"," }"," return _db;","}","","// Lazy proxy \u2014 DB isn't initialized at import/build time","// eslint-disable-next-line @typescript-eslint/no-explicit-any","export const db: any = new Proxy({} as any, {"," get(_target, prop, receiver) {"," const realDb = getDb();"," const value = Reflect.get(realDb, prop, receiver);",' if (typeof value === "function") {'," return value.bind(realDb);"," }"," return value;"," },","});",""].join(`
5062
- `)),M(h,"lib/db-local.ts",["// Local-dev-only DB factory. Isolated from lib/db.ts so the production","// Cloudflare Worker bundle never loads drizzle-orm/pglite or its 30MB","// WASM binary. db.ts only requires this file when NODE_ENV !==","// 'production', so esbuild dead-code-eliminates the import in prod and","// never reaches this file. In dev, pglite is declared as a server","// external package in next.config.ts so webpack leaves it alone too.","",'import { PGlite } from "@electric-sql/pglite";','import { drizzle } from "drizzle-orm/pglite";','import * as schema from "@/db/schema";',"","// eslint-disable-next-line @typescript-eslint/no-explicit-any","export function createLocalDb(): any {",' const client = new PGlite("./local.pg");'," return drizzle(client, { schema });","}",""].join(`
5063
- `)),M(h,"drizzle.config.ts",['import { defineConfig } from "drizzle-kit";',"","// PGlite for local dev (no Postgres install needed), Mistflow Cloud for production",'const isPglite = !process.env.DATABASE_URL || process.env.DATABASE_URL === "pglite";',"","export default defineConfig({",' schema: "./db/schema",',' out: "./db/migrations",',' dialect: "postgresql",',' ...(isPglite ? { driver: "pglite", dbCredentials: { url: "./local.pg" } } : { dbCredentials: { url: process.env.DATABASE_URL! } }),',"});",""].join(`
5064
- `)),w)M(h,"db/schema/index.ts",["// Re-export schema tables here as they are added.",Q?'export * from "./mcp";':null,q?'export * from "./connected-services";':null,""].filter(S=>S!==null).join(`
5065
- `)),M(h,"db/index.ts",["// Re-export schema tables here as they are added.",'export * from "./schema";',""].join(`
5066
- `));else{if(M(h,"db/schema/auth.ts",['import { pgTable, text, boolean, timestamp } from "drizzle-orm/pg-core";',"",'export const user = pgTable("user", {',' id: text("id").primaryKey(),',' name: text("name").notNull(),',' email: text("email").notNull().unique(),',' emailVerified: boolean("email_verified").notNull().default(false),',' image: text("image"),',' role: text("role").default("user"),',' banned: boolean("banned").default(false),',' banReason: text("ban_reason"),',' banExpires: timestamp("ban_expires"),',' createdAt: timestamp("created_at").notNull(),',' updatedAt: timestamp("updated_at").notNull(),',"});","",'export const session = pgTable("session", {',' id: text("id").primaryKey(),',' expiresAt: timestamp("expires_at").notNull(),',' token: text("token").notNull().unique(),',' createdAt: timestamp("created_at").notNull(),',' updatedAt: timestamp("updated_at").notNull(),',' ipAddress: text("ip_address"),',' userAgent: text("user_agent"),',' userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),',' impersonatedBy: text("impersonated_by"),',"});","",'export const account = pgTable("account", {',' id: text("id").primaryKey(),',' accountId: text("account_id").notNull(),',' providerId: text("provider_id").notNull(),',' userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),',' accessToken: text("access_token"),',' refreshToken: text("refresh_token"),',' idToken: text("id_token"),',' accessTokenExpiresAt: timestamp("access_token_expires_at"),',' refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),',' scope: text("scope"),',' password: text("password"),',' createdAt: timestamp("created_at").notNull(),',' updatedAt: timestamp("updated_at").notNull(),',"});","",'export const verification = pgTable("verification", {',' id: text("id").primaryKey(),',' identifier: text("identifier").notNull(),',' value: text("value").notNull(),',' expiresAt: timestamp("expires_at").notNull(),',' createdAt: timestamp("created_at"),',' updatedAt: timestamp("updated_at"),',"});",""].join(`
5067
- `)),M(h,"db/schema/index.ts",['export * from "./auth";',Q?'export * from "./mcp";':null,q?'export * from "./connected-services";':null,""].filter(H=>H!==null).join(`
5068
- `)),M(h,"db/index.ts",['export * from "./schema/auth";',Q?'export * from "./schema/mcp";':null,q?'export * from "./schema/connected-services";':null,""].filter(H=>H!==null).join(`
5069
- `)),Q&&j){let H=$r({appName:U,appVersion:"0.0.1",useAiChatManifest:Dn(u)});for(let Ce of H.files)M(h,Ce.path,Ce.content);M(h,"db/schema/mcp.ts",H.schemaFragment)}if(q&&j)for(let H of ko.files){if(H.kind==="schema"){M(h,"db/schema/connected-services.ts",H.source);continue}H.kind==="migration"||H.kind==="manifest"||H.source&&M(h,H.targetFile,H.source)}let S=u.defaultRole??"user";M(h,"lib/auth.ts",['import { betterAuth } from "better-auth";','import { drizzleAdapter } from "better-auth/adapters/drizzle";','import { admin } from "better-auth/plugins/admin";','import { genericOAuth } from "better-auth/plugins/generic-oauth";','import { nextCookies } from "better-auth/next-js";','import { db } from "./db";','import * as schema from "@/db";',"","async function sendEmail({ to, subject, html, fallbackUrl }: { to: string; subject: string; html: string; fallbackUrl?: string }) {"," const apiKey = process.env.RESEND_API_KEY;"," if (!apiKey) {"," if (fallbackUrl) {"," console.error(`\\n[auth] No RESEND_API_KEY set. Email to ${to} was not sent.`);"," console.error(`[auth] Dev fallback \u2014 use this link directly:\\n ${fallbackUrl}\\n`);"," } else {"," console.error(`[auth] RESEND_API_KEY not set \u2014 skipping email send to ${to}`);"," }"," return;"," }",' const from = process.env.EMAIL_FROM || "noreply@mail.mistflow.app";',' const res = await fetch("https://api.resend.com/emails", {',' method: "POST",',' headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },'," body: JSON.stringify({ from, to, subject, html }),"," });"," if (!res.ok) {",' const body = await res.text().catch(() => "unknown");'," console.error(`[auth] Email send failed (${res.status}): ${body}`);"," throw new Error(`Email send failed: ${res.status}`);"," }","}","","function createAuth() {",' const baseURL = process.env.BETTER_AUTH_URL || process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000";',' const isLocal = baseURL.includes("localhost") || baseURL.includes("127.0.0.1");'," const canSendEmail = Boolean(process.env.RESEND_API_KEY);"," const mistflowAppId = process.env.MISTFLOW_APP_ID;"," const mistflowOAuthProxyURL = process.env.MISTFLOW_OAUTH_PROXY_URL;"," const googleClientId = process.env.GOOGLE_CLIENT_ID;"," const googleClientSecret = process.env.GOOGLE_CLIENT_SECRET;"," // Preview deploys skip email verification so smoke tests and human"," // pre-launch testing can sign up and log in without a real inbox."," // The Mistflow deploy pipeline sets MISTFLOW_RUNTIME_KEY_ENVIRONMENT",' // to "preview" for staging deploys and "production" for prod. Production'," // always requires verification \u2014 there is no way to misconfigure prod into"," // the relaxed mode because the env var is set server-side at deploy."," const runtimeKeyEnvironment = process.env.MISTFLOW_RUNTIME_KEY_ENVIRONMENT;",' const isPreview = runtimeKeyEnvironment === "preview" || runtimeKeyEnvironment === "dev";',' const isProduction = runtimeKeyEnvironment === "production";'," // Google sign-in routing:"," // - Production with both GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET set:"," // use the customer's Google client (consent screen branded as their app)."," // - Otherwise (preview, dev, or missing BYO keys): use Mistflow's hosted"," // OAuth proxy. Customers never need to register preview URLs in Google"," // Console \u2014 the proxy fans them out from a single canonical redirect URI."," const useByoGoogle = isProduction && Boolean(googleClientId && googleClientSecret);"," const useProxyGoogle = !useByoGoogle && Boolean(mistflowAppId && mistflowOAuthProxyURL);"," const googleProviderConfig = useByoGoogle"," ? {",' providerId: "google",'," clientId: googleClientId!,"," clientSecret: googleClientSecret!,",' discoveryUrl: "https://accounts.google.com/.well-known/openid-configuration",',' scopes: ["openid", "email", "profile"],'," pkce: true,"," redirectURI: `${baseURL}/api/auth/oauth2/callback/google`,"," }"," : useProxyGoogle"," ? {",' providerId: "google",'," clientId: mistflowAppId!,",' clientSecret: "pkce",'," discoveryUrl: `${mistflowOAuthProxyURL}/.well-known/openid-configuration`,",' scopes: ["openid", "email", "profile"],'," pkce: true,"," redirectURI: `${baseURL}/api/auth/oauth2/callback/google`,"," }"," : null;"," if (googleProviderConfig) {",' console.error(`[auth] Google sign-in: ${useByoGoogle ? "BYO (your Google client)" : "proxy (Mistflow-hosted)"}`);'," }"," // Refuse to boot a production app with email auth but no mail sender. Mistflow's"," // deploy pipeline injects a managed Resend key automatically; if it's missing,"," // that's a real misconfig and silent fallbacks let unverified users sign up."," // Preview deploys are exempt \u2014 they don't need email since verification is off."," if (!isLocal && !isPreview && !canSendEmail) {",` throw new Error("[auth] RESEND_API_KEY is required in production. The Mistflow deploy pipeline injects one automatically \u2014 if you're seeing this, check the project's env vars in the dashboard, or set your own RESEND_API_KEY.");`," }"," return betterAuth({"," baseURL,"," // Include the Mistflow OAuth proxy in trustedOrigins so Better Auth"," // accepts the OAuth callback round-trip without 'invalid origin' errors."," trustedOrigins: [baseURL, mistflowOAuthProxyURL].filter((u): u is string => Boolean(u)),",' database: drizzleAdapter(db, { provider: "pg", schema }),'," emailAndPassword: {"," enabled: true,"," requireEmailVerification: !isLocal && !isPreview && canSendEmail,"," sendResetPassword: async ({ user, token }: { user: { email: string; name: string }; url: string; token: string }) => {"," // Better Auth's default reset URL points at /reset-password/:token (path),"," // which our frontend doesn't route. We build our own pointing at our"," // /reset-password?token=xxx page which reads the token from the query."," const resetUrl = `${baseURL}/reset-password?token=${token}`;"," await sendEmail({"," to: user.email,",' subject: "Reset your password",',' html: `<p>Hi ${user.name},</p><p>Click the link below to reset your password:</p><p><a href="${resetUrl}">${resetUrl}</a></p>`,'," fallbackUrl: isLocal ? resetUrl : undefined,"," });"," },"," },"," emailVerification: {"," sendOnSignUp: canSendEmail,"," autoSignInAfterVerification: true,"," sendVerificationEmail: async ({ user, url }: { user: { email: string; name: string }; url: string }) => {"," await sendEmail({"," to: user.email,",' subject: "Verify your email address",',' html: `<p>Hi ${user.name},</p><p>Click the link below to verify your email:</p><p><a href="${url}">${url}</a></p>`,'," fallbackUrl: isLocal ? url : undefined,"," });"," },"," },"," secret: process.env.AUTH_SECRET,"," plugins: [",` admin({ defaultRole: "${S}" }),`," ...(googleProviderConfig ? [genericOAuth({ config: [googleProviderConfig] })] : []),"," nextCookies(),"," ],"," databaseHooks: {"," user: {"," create: {"," // Auto-promote the app owner to admin on first signup. ADMIN_EMAIL"," // is injected by the Mistflow deploy pipeline (the email of the"," // account that ran mist_deploy). Email verification still gates"," // login when Resend is configured, so a collision attempt can't"," // actually sign in without clicking a link delivered to the"," // owner's inbox."," before: async (user: { email?: string; [k: string]: unknown }) => {"," const adminEmail = process.env.ADMIN_EMAIL;"," if (adminEmail && user.email?.toLowerCase() === adminEmail.toLowerCase()) {",' return { data: { ...user, role: "admin" } };'," }"," return { data: user };"," },"," },"," },"," },"," socialProviders: {"," ...(process.env.GITHUB_CLIENT_ID ? {"," github: {"," clientId: process.env.GITHUB_CLIENT_ID,"," clientSecret: process.env.GITHUB_CLIENT_SECRET!,"," },"," } : {}),"," },"," });","}","","// Lazy init \u2014 process.env isn't populated at module scope on Cloudflare Workers.","// The `has` trap is required: better-auth's toNextJsHandler does",'// `"handler" in auth ? auth.handler(request) : auth(request)` \u2014 without a `has`',"// trap the default forwards to the empty target object, returns false, and the","// handler tries to call the Proxy as a function, which throws TypeError and","// returns 500 on every /api/auth/* request.","let _auth: ReturnType<typeof createAuth> | null = null;","export const auth = new Proxy({} as ReturnType<typeof createAuth>, {"," get(_target, prop, receiver) {"," if (!_auth) _auth = createAuth();"," const value = Reflect.get(_auth, prop, receiver);",' if (typeof value === "function") return value.bind(_auth);'," return value;"," },"," has(_target, prop) {"," if (!_auth) _auth = createAuth();"," return prop in _auth;"," },","});",""].join(`
5070
- `))}}else M(h,"package.json",JSON.stringify({name:U,version:"0.1.0",private:!0},null,2));let he=a??u?.designMd;M(h,"app/globals.css",Fu(C,he)),M(h,"app/layout.tsx",zu(U,C,D?.language)),M(h,"README.md",sp(U,u,{hasStripe:A,hasResend:J,hasStorage:V,hasAdmin:te,hasAI:le,isNeon:j})),M(h,"contracts/README.md",qr());let T=u?.dataModel??[],$e=new Set,G=0;for(let b of T){let _=b.entity??b.name;if(!_||typeof _!="string")continue;let $=Hr(_);$e.has($)||($e.add($),M(h,$,Br(_)),G++)}G===0&&M(h,"contracts/.gitkeep","");let ie=[],ye=u?.publicPages;if(Array.isArray(ye))ie=ye;else if(typeof ye=="string"){try{ie=JSON.parse(ye)}catch{ie=[]}Array.isArray(ie)||(ie=[])}if(u?.publicLanding===!1)ie=ie.filter(b=>b!=="/");else if(!ie.includes("/")){let b=u?.steps?.some($=>{let Ue=(($.name??"")+" "+($.description??"")).toLowerCase();return Ue.includes("landing")||Ue.includes("marketing")||Ue.includes("homepage")}),_=u?.pages?.some($=>$.path==="/");(b||_)&&(ie=["/",...ie])}let de={name:U,summary:u?.summary,authModel:x,roles:u?.roles,defaultRole:u?.defaultRole,publicPages:ie,navStyle:u?.navStyle,multiTenant:u?.multiTenant,pages:u?.pages,dataModel:u?.dataModel,design:u?.design},je=Yu(de);je&&M(h,"middleware.ts",je);let pt=Qu(de);pt&&M(h,pt.path,pt.content);let Ze=Xu(de);if(Ze&&M(h,"lib/roles.ts",Ze),M(h,"app/page.tsx",Zu(de)),M(h,"app/(dashboard)/layout.tsx",ep(de,te)),M(h,"app/(dashboard)/dashboard/page.tsx",tp(de)),de.multiTenant){let b=np(de,j);b&&M(h,"db/schema/organization.ts",b);let _=op(de);_&&M(h,"lib/org.ts",_);let $=rp(de);$&&M(h,"components/org-switcher.tsx",$)}M(h,"tsconfig.json",JSON.stringify({compilerOptions:{target:"ES2017",lib:["dom","dom.iterable","esnext"],allowJs:!0,skipLibCheck:!0,strict:!1,noEmit:!0,esModuleInterop:!0,module:"esnext",moduleResolution:"bundler",resolveJsonModule:!0,isolatedModules:!0,jsx:"preserve",incremental:!0,plugins:[{name:"next"}],paths:{"@/*":["./*"]}},include:["next-env.d.ts","**/*.ts","**/*.tsx",".next/types/**/*.ts"],exclude:["node_modules"]},null,2)),A&&M(h,"lib/stripe.ts",['import Stripe from "stripe";',"","let _stripe: Stripe | null = null;","","function getStripe(): Stripe {"," if (!_stripe) {"," if (!process.env.STRIPE_SECRET_KEY) {",' throw new Error("STRIPE_SECRET_KEY is not set");'," }"," _stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {"," typescript: true,"," });"," }"," return _stripe;","}","","// Lazy proxy \u2014 Stripe isn't initialized at import/build time","export const stripe = new Proxy({} as Stripe, {"," get(_target, prop) {"," return (getStripe() as unknown as Record<string | symbol, unknown>)[prop];"," },","});",""].join(`
5071
- `)),J&&(M(h,"lib/resend.ts",['import { Resend } from "resend";',"","let _resend: Resend | null = null;","","function getResend(): Resend {"," if (!_resend) {"," if (!process.env.RESEND_API_KEY) {",' throw new Error("RESEND_API_KEY is not set");'," }"," _resend = new Resend(process.env.RESEND_API_KEY);"," }"," return _resend;","}","","// Lazy proxy \u2014 Resend isn't initialized at import/build time","export const resend = new Proxy({} as Resend, {"," get(_target, prop) {"," return (getResend() as unknown as Record<string | symbol, unknown>)[prop];"," },","});",""].join(`
5072
- `)),M(h,"lib/email.ts",['import { resend } from "./resend";',"",'const FROM = process.env.EMAIL_FROM ?? "onboarding@resend.dev";',"","export async function sendEmail({"," to,"," subject,"," react,","}: {"," to: string;"," subject: string;"," react: React.ReactElement;","}) {"," return resend.emails.send({ from: FROM, to, subject, react });","}",""].join(`
5073
- `)),M(h,"lib/server/email.ts",['import "server-only";','import { render } from "@react-email/render";','import type { ReactElement } from "react";',"",'export class EmailConfigError extends Error { readonly code = "email_config"; }','export class EmailRateLimitError extends Error { readonly code = "email_rate_limit"; }','export class EmailCreditExhaustedError extends Error { readonly code = "email_credits_exhausted"; }','export class EmailUpstreamError extends Error { readonly code = "email_upstream_error"; }','export class EmailValidationError extends Error { readonly code = "email_validation"; }',"","function runtimeKey(): string | undefined {"," return process.env.MISTFLOW_RUNTIME_KEY ?? process.env.MISTFLOW_AI_RUNTIME_KEY;","}","function apiUrl(): string {",' return (process.env.MISTFLOW_API_URL ?? process.env.MISTFLOW_AI_API_URL ?? "https://api.mistflow.ai").replace(/\\/$/, "");',"}","","export type EmailSendOptions = {"," to: string | string[];"," subject: string;"," html?: string;"," text?: string;"," react?: ReactElement;"," cc?: string[];"," bcc?: string[];"," replyTo?: string;"," idempotencyKey?: string;"," tags?: Record<string, string>;","};","","export const email = {"," /** Send an email. Sandbox decision is server-enforced via the runtime key's"," * environment field \u2014 your app cannot accidentally email real users from"," * dev or preview deploys. Real sends only fire when the key is",' * environment="production".'," *"," * React components are rendered to HTML locally before the gateway call"," * (React doesn't serialize over HTTP). */"," async send(opts: EmailSendOptions): Promise<{ id: string; sandbox?: boolean }> {"," if (!opts.to || (Array.isArray(opts.to) && opts.to.length === 0)) {",' throw new EmailValidationError("Recipient (to) is required");'," }"," if (!opts.subject) {",' throw new EmailValidationError("Subject is required");'," }"," let html = opts.html;"," if (opts.react && !html) {"," html = await render(opts.react);"," }",""," const key = runtimeKey();"," if (key) {"," // Managed mode \u2014 gateway handles sandbox enforcement, idempotency, billing."," const body = {"," to: opts.to, subject: opts.subject, html, text: opts.text,"," cc: opts.cc, bcc: opts.bcc, replyTo: opts.replyTo,"," idempotencyKey: opts.idempotencyKey, tags: opts.tags,"," };",' const r = await fetch(apiUrl() + "/api/email/send", {',' method: "POST",',' headers: { "Content-Type": "application/json", "X-Mistflow-AI-Key": key },'," body: JSON.stringify(body),"," });",' if (r.status === 401) throw new EmailConfigError("Runtime key rejected by gateway. Re-run mist_setup.");',' if (r.status === 402) throw new EmailCreditExhaustedError("Email/AI credits exhausted.");',' if (r.status === 429) throw new EmailRateLimitError("Rate limited.");'," if (r.status === 422) throw new EmailValidationError(await r.text());",' if (!r.ok) throw new EmailUpstreamError("Email gateway error " + r.status);'," return (await r.json()) as { id: string; sandbox?: boolean };"," }",""," if (process.env.RESEND_API_KEY) {"," // BYOK direct \u2014 no sandbox enforcement, user is responsible.",' const { Resend } = await import("resend");'," const client = new Resend(process.env.RESEND_API_KEY);",' const fromAddr = process.env.EMAIL_FROM ?? "onboarding@resend.dev";'," const result = await client.emails.send({"," from: fromAddr,"," to: opts.to, subject: opts.subject, html, text: opts.text,"," cc: opts.cc, bcc: opts.bcc, reply_to: opts.replyTo,",' ...(opts.idempotencyKey ? { headers: { "Idempotency-Key": opts.idempotencyKey } } : {}),'," });"," if (result.error) throw new EmailUpstreamError(result.error.message);",' return { id: result.data?.id ?? "unknown", sandbox: false };'," }",""," throw new EmailConfigError(",` "Email is not configured. Run 'mist_setup' for managed mode (sandbox-safe in dev) or set RESEND_API_KEY."`," );"," },","};",""].join(`
5074
- `))),M(h,"lib/server/storage.ts",['import "server-only";',"",'export class StorageConfigError extends Error { readonly code = "storage_config"; }','export class StorageUpstreamError extends Error { readonly code = "storage_upstream_error"; }','export class StorageQuotaError extends Error { readonly code = "storage_quota"; }',"","function runtimeKey(): string {"," const k = process.env.MISTFLOW_RUNTIME_KEY ?? process.env.MISTFLOW_AI_RUNTIME_KEY;",` if (!k) throw new StorageConfigError("Storage requires managed mode. Run 'mist_setup' to enable.");`," return k;","}","function apiUrl(): string {",' return (process.env.MISTFLOW_API_URL ?? process.env.MISTFLOW_AI_API_URL ?? "https://api.mistflow.ai").replace(/\\/$/, "");',"}","async function callGateway<T>(path: string, body: unknown): Promise<T> {"," const r = await fetch(apiUrl() + path, {",' method: "POST",',' headers: { "Content-Type": "application/json", "X-Mistflow-AI-Key": runtimeKey() },'," body: JSON.stringify(body),"," });",' if (r.status === 401) throw new StorageConfigError("Runtime key rejected. Re-run mist_setup.");',' if (r.status === 402) throw new StorageQuotaError("Storage quota exhausted.");',' if (r.status === 413) throw new StorageQuotaError("File exceeds the 100MB upload limit.");',' if (!r.ok) throw new StorageUpstreamError("Storage gateway error " + r.status);'," return (await r.json()) as T;","}","","export const storage = {"," /** Upload a Blob/File. Backend mints a signed URL; client PUTs directly to R2. */"," async put(opts: { key: string; blob: Blob; contentType: string; expiresIn?: number }): Promise<{ finalKey: string }> {"," const signed = await callGateway<{ uploadUrl: string; finalKey: string; expiresAt: string }>(",' "/api/storage/sign-upload",'," {"," key: opts.key,"," contentType: opts.contentType,"," contentLength: opts.blob.size,"," expiresIn: opts.expiresIn,"," },"," );"," const res = await fetch(signed.uploadUrl, {",' method: "PUT",'," body: opts.blob,",' headers: { "Content-Type": opts.contentType },'," });"," if (!res.ok) {",' throw new StorageUpstreamError("Direct R2 upload failed: " + res.status);'," }"," return { finalKey: signed.finalKey };"," },",""," /** Get a short-lived signed download URL for a stored object. */"," async get(opts: { key: string; expiresIn?: number }): Promise<{ url: string; expiresAt: string }> {"," const signed = await callGateway<{ downloadUrl: string; expiresAt: string }>(",' "/api/storage/sign-download",'," { key: opts.key, expiresIn: opts.expiresIn },"," );"," return { url: signed.downloadUrl, expiresAt: signed.expiresAt };"," },",""," /** Delete an object. Server-side delete via R2 SDK. */"," async delete(opts: { key: string }): Promise<void> {",' const r = await fetch(apiUrl() + "/api/storage/object", {',' method: "DELETE",',' headers: { "Content-Type": "application/json", "X-Mistflow-AI-Key": runtimeKey() },'," body: JSON.stringify({ key: opts.key }),"," });"," if (!r.ok && r.status !== 204) {",' throw new StorageUpstreamError("Delete failed: " + r.status);'," }"," },",""," /** List objects under an optional prefix. Always scoped to your project. */"," async list(opts: { prefix?: string; limit?: number; cursor?: string } = {}): Promise<{ keys: { key: string; size: number }[]; cursor?: string }> {",' return callGateway("/api/storage/list", opts);'," },","};",""].join(`
5075
- `)),M(h,"app/api/upload/route.ts",["import {"," storage,"," StorageConfigError,"," StorageQuotaError,"," StorageUpstreamError,",'} from "@/lib/server/storage";',"","export async function POST(req: Request) {"," const formData = await req.formData();",' const file = formData.get("file");',' const prefix = (formData.get("prefix") as string | null) ?? "uploads";',""," if (!(file instanceof File)) {",' return Response.json({ error: "Choose a file to upload." }, { status: 400 });'," }","",' const safeName = file.name.replace(/[^a-zA-Z0-9.\\-_]/g, "_");'," const key = `${prefix}/${crypto.randomUUID()}-${safeName}`;",""," try {"," const { finalKey } = await storage.put({"," key,"," blob: file,",' contentType: file.type || "application/octet-stream",'," });"," return Response.json({ key: finalKey });"," } catch (err) {"," if (err instanceof StorageQuotaError) {",' return Response.json({ error: "File too large or storage full." }, { status: 413 });'," }"," if (err instanceof StorageConfigError) {",` return Response.json({ error: "Storage isn't configured for this app yet." }, { status: 503 });`," }"," if (err instanceof StorageUpstreamError) {",' return Response.json({ error: "Storage is temporarily unavailable. Try again." }, { status: 503 });'," }"," throw err;"," }","}",""].join(`
5076
- `)),M(h,"components/file-upload.tsx",['"use client";','import { useCallback, useState } from "react";',"","interface FileUploadProps {"," /** Called with the persisted storage key after upload succeeds. */"," onUpload: (key: string) => void;"," /** Accept attribute for the file input. Defaults to images. */"," accept?: string;"," /** Client-side size guard, in megabytes. Server still enforces a hard cap. */"," maxSizeMB?: number;",' /** Optional key prefix sent to the upload route. Defaults to "uploads". */'," prefix?: string;","}","",'export function FileUpload({ onUpload, accept = "image/*", maxSizeMB = 10, prefix }: FileUploadProps) {'," const [isDragging, setIsDragging] = useState(false);"," const [uploading, setUploading] = useState(false);"," const [error, setError] = useState<string | null>(null);",""," const handleUpload = useCallback(async (file: File) => {"," setError(null);"," if (file.size > maxSizeMB * 1024 * 1024) {"," setError(`File is over the ${maxSizeMB}MB limit.`);"," return;"," }"," setUploading(true);"," try {"," const formData = new FormData();",' formData.append("file", file);',' if (prefix) formData.append("prefix", prefix);',' const res = await fetch("/api/upload", { method: "POST", body: formData });'," if (!res.ok) {"," const body = await res.json().catch(() => ({}));",' throw new Error(body?.error ?? "Upload failed");'," }"," const { key } = (await res.json()) as { key: string };"," onUpload(key);"," } catch (err) {",' setError(err instanceof Error ? err.message : "Upload failed");'," } finally {"," setUploading(false);"," }"," }, [maxSizeMB, onUpload, prefix]);",""," return (",' <div className="space-y-2">'," <label",' htmlFor="file-upload-input"'," onDragOver={(e) => { e.preventDefault(); setIsDragging(true); }}"," onDragLeave={() => setIsDragging(false)}"," onDrop={(e) => {"," e.preventDefault();"," setIsDragging(false);"," const file = e.dataTransfer.files[0];"," if (file) handleUpload(file);"," }}"," className={`flex cursor-pointer flex-col items-center justify-center gap-1 rounded-lg border-2 border-dashed p-8 text-center text-sm transition-colors ${",' isDragging ? "border-primary bg-primary/5" : "border-muted-foreground/25 hover:border-primary/50"'," }`}"," >"," <input",' id="file-upload-input"',' type="file"'," accept={accept}",' className="hidden"'," onChange={(e) => { const f = e.target.files?.[0]; if (f) handleUpload(f); }}"," />",' <span className="font-medium">',' {uploading ? "Uploading..." : "Drop a file here or click to browse"}'," </span>",' <span className="text-xs text-muted-foreground">Up to {maxSizeMB}MB</span>'," </label>",' {error ? <p className="text-sm text-destructive">{error}</p> : null}'," </div>"," );","}",""].join(`
5077
- `)),le&&(M(h,"lib/server/ai.ts",['import "server-only";',"",'import { createOpenAI } from "@ai-sdk/openai";','import { streamText, type CoreMessage } from "ai";','import type { z } from "zod";','import { zodToJsonSchema } from "zod-to-json-schema";',"","export const MISTFLOW_AI_HELPER_VERSION = 2;","","// \u2500\u2500 Typed error hierarchy \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500","// Catch the specific class to give the user actionable copy.",'export class AIConfigError extends Error { readonly code = "ai_config"; }','export class AIRateLimitError extends Error { readonly code = "ai_rate_limit"; }','export class AICreditExhaustedError extends Error { readonly code = "ai_credits_exhausted"; }','export class AIModelError extends Error { readonly code = "ai_model_error"; }',"export class AISchemaError extends Error {",' readonly code = "ai_schema_error";'," /** Raw model output that failed validation. Redacted by default; set"," * MISTFLOW_DEBUG=1 in dev to see full output. */"," readonly rawOutput?: string;"," constructor(message: string, rawOutput?: string) {"," super(message);"," this.rawOutput = rawOutput;"," }","}","","function redact(s: string | undefined): string | undefined {"," if (!s) return s;",' if (process.env.MISTFLOW_DEBUG === "1") return s;',' if (s.length <= 80) return "<redacted>";',' return s.slice(0, 40) + " ...<redacted>... " + s.slice(-20);',"}","","// \u2500\u2500 Env + dispatch helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",'const DEFAULT_MISTFLOW_AI_API_URL = "https://api.mistflow.ai";',"","function runtimeKey(): string | undefined {"," // Canonical name first; legacy alias for one release of compat."," return process.env.MISTFLOW_RUNTIME_KEY ?? process.env.MISTFLOW_AI_RUNTIME_KEY;","}","","function mistflowAiApiUrl(): string {",' return (process.env.MISTFLOW_API_URL ?? process.env.MISTFLOW_AI_API_URL ?? DEFAULT_MISTFLOW_AI_API_URL).replace(/\\/$/, "");',"}","","function gatewayHeaders(): HeadersInit {"," const key = runtimeKey();"," if (!key) {",` throw new AIConfigError("MISTFLOW_RUNTIME_KEY not configured. Run 'mist_setup' for managed mode (free credits) or set OPENROUTER_API_KEY for BYOK.");`," }"," return {",' "Content-Type": "application/json",',' "X-Mistflow-AI-Key": key,'," };","}","","function isManagedAvailable(): boolean { return !!runtimeKey(); }","function isBYOKAvailable(): boolean { return !!process.env.OPENROUTER_API_KEY; }","","function noConfigError(): AIConfigError {"," return new AIConfigError(",` "AI is not configured. Run 'mist_setup' for managed mode (free credits) or set OPENROUTER_API_KEY in .env.local."`," );","}","","async function callManaged(path: string, body: unknown): Promise<Response> {"," const response = await fetch(mistflowAiApiUrl() + path, {",' method: "POST",'," headers: gatewayHeaders(),"," body: JSON.stringify(body),"," });",' if (response.status === 401) throw new AIConfigError("Runtime key rejected by gateway. Re-run mist_setup.");',' if (response.status === 402) throw new AICreditExhaustedError("AI credits exhausted. Top up at https://app.mistflow.ai/ai-gateway");',' if (response.status === 429) throw new AIRateLimitError("Rate limited by gateway. Retry with backoff.");'," if (!response.ok) {",' const text = await response.text().catch(() => "");',' throw new AIModelError("AI gateway error " + response.status + ": " + redact(text));'," }"," return response;","}","","// BYOK direct OpenRouter \u2014 used when MISTFLOW_RUNTIME_KEY is not set.","export const openrouter = createOpenAI({"," apiKey: process.env.OPENROUTER_API_KEY,",' baseURL: "https://openrouter.ai/api/v1",'," headers: {",' "HTTP-Referer": process.env.NEXT_PUBLIC_APP_URL ?? "https://mistflow.app",',' "X-Title": "Mistflow App",'," },","});","","// \u2500\u2500 Public surface: the `ai` object \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500","// Always import this. The wrapper handles all dispatch (managed-first,","// BYOK fallback). Never write process.env.OPENROUTER_API_KEY in feature code.","","// Roles are Mistflow's stable abstraction. The host AI picks one per call","// site at scaffold time; the gateway resolves the role to whichever model","// Mistflow currently maps it to. Customer apps never reference deployment",'// names. Override via `model` (semantic ID like "gpt-5", "claude-sonnet")',"// only when a specific model is genuinely required.",'export type AiRole = "chat" | "reasoning" | "classify" | "image" | "embed" | "tts" | "stt" | "voice_realtime";',"","// OpenAI-style tool definition. Same shape AI SDK 6 and OpenAI use.","// Define a tool with `tool({ inputSchema, execute })` from the `ai`","// package and pass the resulting object verbatim \u2014 or hand-build one.","export type AiTool = {",' type: "function";'," function: { name: string; description?: string; parameters: Record<string, unknown> };","};","",'export type AiToolChoice = "auto" | "required" | "none" | {',' type: "function";'," function: { name: string };","};","","// One element from the response.tool_calls array. Pass back as a",'// "tool" role message on the next turn (see methodologies/model-guide.md).',"export type AiToolCall = {"," id: string;",' type: "function";'," function: { name: string; arguments: string };","};","","export type TextOptions = {"," messages: CoreMessage[];"," role?: AiRole;"," model?: string;"," useCase?: string;"," idempotencyKey?: string;"," tools?: AiTool[];"," toolChoice?: AiToolChoice;","};","","export type TextResult = {"," text: string;"," toolCalls?: AiToolCall[];"," finishReason?: string;","};","","export type ImageOptions = { prompt: string; size?: string; role?: AiRole; model?: string; idempotencyKey?: string };","export type EmbedOptions = { role?: AiRole; model?: string };","export type TTSOptions = { text: string; voice?: string; role?: AiRole; model?: string };","export type STTOptions = { audio: Blob | ArrayBuffer; role?: AiRole; model?: string };","export type RealtimeOptions = { instructions?: string; role?: AiRole; useCase?: string };","","// Convert OpenAI-shaped tool definitions to the AI SDK's tool() shape.","// Used only on the BYOK path; the managed gateway accepts OpenAI shape directly.","function openAiToolsAsSdkTools(tools: AiTool[]): Record<string, { description?: string; inputSchema: Record<string, unknown> }> {"," const out: Record<string, { description?: string; inputSchema: Record<string, unknown> }> = {};"," for (const t of tools) {"," out[t.function.name] = {"," description: t.function.description,"," inputSchema: t.function.parameters,"," };"," }"," return out;","}","","export const ai = {"," // Backwards-compatible text() returns just the string. For tool calls"," // and finish_reason, use textFull() \u2014 it returns the structured result."," async text(opts: TextOptions): Promise<string> {"," const full = await this.textFull(opts);"," return full.text;"," },",""," // Structured text result: text + toolCalls + finishReason. Use this"," // when the model may invoke a tool. The caller is responsible for",' // executing the tool and looping with a "tool" role message.'," async textFull(opts: TextOptions): Promise<TextResult> {"," if (isManagedAvailable()) {",' const r = await callManaged("/api/ai-gateway/runtime/text", {'," role: opts.role, use_case: opts.useCase,"," model: opts.model, idempotency_key: opts.idempotencyKey,"," messages: opts.messages,"," tools: opts.tools, tool_choice: opts.toolChoice,"," });"," const json = (await r.json()) as { text?: string; tool_calls?: AiToolCall[]; finish_reason?: string };"," return {",' text: json.text ?? "",'," toolCalls: json.tool_calls ?? undefined,"," finishReason: json.finish_reason ?? undefined,"," };"," }"," if (isBYOKAvailable()) {"," // BYOK direct: tools land on the OpenRouter call via the AI SDK."," const result = await streamText({",' model: openrouter(opts.model ?? "openai/gpt-5-mini"),'," messages: opts.messages,"," ...(opts.tools ? { tools: openAiToolsAsSdkTools(opts.tools) } : {}),",' ...(opts.toolChoice ? { toolChoice: opts.toolChoice as Parameters<typeof streamText>[0]["toolChoice"] } : {}),'," });"," return {"," text: await result.text,"," toolCalls: (await result.toolCalls)?.map((tc: { toolCallId: string; toolName: string; input: unknown }) => ({"," id: tc.toolCallId,",' type: "function" as const,'," function: { name: tc.toolName, arguments: JSON.stringify(tc.input) },"," })),"," finishReason: await result.finishReason,"," };"," }"," throw noConfigError();"," },",""," async streamText(opts: TextOptions): Promise<Response> {"," if (isManagedAvailable()) {",' return callManaged("/api/ai-gateway/runtime/text", {'," role: opts.role, use_case: opts.useCase,"," model: opts.model, idempotency_key: opts.idempotencyKey,"," messages: opts.messages,"," tools: opts.tools, tool_choice: opts.toolChoice,"," });"," }"," if (isBYOKAvailable()) {"," const result = await streamText({",' model: openrouter(opts.model ?? "openai/gpt-5-mini"),'," messages: opts.messages,"," ...(opts.tools ? { tools: openAiToolsAsSdkTools(opts.tools) } : {}),",' ...(opts.toolChoice ? { toolChoice: opts.toolChoice as Parameters<typeof streamText>[0]["toolChoice"] } : {}),'," });"," return result.toDataStreamResponse();"," }"," throw noConfigError();"," },",""," async extractJSON<T>(opts: { prompt: string; schema: z.ZodType<T>; model?: string; useCase?: string }): Promise<T> {"," // Per Codex review: no dedicated /runtime/extract-json endpoint."," // Wrapper uses /runtime/text + JSON-schema-mode + local Zod validation.",' const jsonSchema = zodToJsonSchema(opts.schema, { target: "openApi3" });'," const messages: CoreMessage[] = [",' { role: "system", content: "Respond ONLY with valid JSON matching this schema: " + JSON.stringify(jsonSchema) },',' { role: "user", content: opts.prompt },'," ];",' const text = await this.text({ messages, model: opts.model, useCase: opts.useCase ?? "extract" });'," let parsed: unknown;"," try { parsed = JSON.parse(text); }",' catch { throw new AISchemaError("Model returned non-JSON output", redact(text)); }'," const result = opts.schema.safeParse(parsed);"," if (!result.success) {",' throw new AISchemaError("Model output failed schema validation: " + result.error.message, redact(text));'," }"," return result.data;"," },",""," async embed(input: string | string[], opts: EmbedOptions = {}): Promise<number[][]> {"," if (!isManagedAvailable()) {"," // BYOK embed via OpenRouter is not in v1; require managed mode for embeddings."," throw noConfigError();"," }",' const r = await callManaged("/api/ai-gateway/runtime/embed", { input, role: opts.role, model: opts.model });'," const json = (await r.json()) as { vectors: number[][] };"," return json.vectors;"," },",""," async generateImage(opts: ImageOptions): Promise<{ url: string }[]> {"," if (!isManagedAvailable()) throw noConfigError();",' const r = await callManaged("/api/ai-gateway/runtime/image", {'," role: opts.role,"," prompt: opts.prompt, model: opts.model, size: opts.size,"," idempotency_key: opts.idempotencyKey,"," });"," const json = (await r.json()) as { url?: string; image?: string };",' return [{ url: json.url ?? json.image ?? "" }];'," },",""," async speak(opts: TTSOptions): Promise<Blob> {"," if (!isManagedAvailable()) throw noConfigError();",' const r = await callManaged("/api/ai-gateway/runtime/tts", {'," role: opts.role,"," text: opts.text, voice: opts.voice, model: opts.model,"," });"," return await r.blob();"," },",""," async transcribe(opts: STTOptions): Promise<{ text: string }> {"," if (!isManagedAvailable()) throw noConfigError();"," // Backend expects JSON { audio_base64 } per the runtime contract."," const buf = opts.audio instanceof Blob ? await opts.audio.arrayBuffer() : opts.audio;",' let binary = "";'," const bytes = new Uint8Array(buf);"," for (let i = 0; i < bytes.byteLength; i++) binary += String.fromCharCode(bytes[i]);",' const audio_base64 = (typeof btoa === "function" ? btoa(binary) : Buffer.from(binary, "binary").toString("base64"));',' const r = await callManaged("/api/ai-gateway/runtime/stt", {'," role: opts.role, audio_base64, model: opts.model,"," });"," return (await r.json()) as { text: string };"," },",""," async createRealtimeSession(opts: RealtimeOptions = {}): Promise<Record<string, unknown>> {"," if (!isManagedAvailable()) throw noConfigError();",' const r = await callManaged("/api/ai-gateway/runtime/realtime-session", {'," role: opts.role, use_case: opts.useCase, instructions: opts.instructions,"," });"," return (await r.json()) as Record<string, unknown>;"," },","};","","// \u2500\u2500 Legacy named exports (back-compat for older scaffolds + chat route) \u2500\u2500","// These wrap the new `ai` object so existing call sites keep working.",'export type AiCapability = "llm" | "voice_realtime" | "tts" | "stt" | "image";',"type ManagedTextOptions = { capability?: AiCapability; useCase?: string; model?: string; idempotencyKey?: string; stream?: boolean };","",'export function openrouterModel(model = "openai/gpt-5-mini") { return openrouter(model); }',"",'export async function streamOpenRouterText(messages: CoreMessage[], model = "openai/gpt-5-mini") {'," return streamText({ model: openrouter(model), messages });","}","","export async function mistflowManagedText(messages: CoreMessage[], options: ManagedTextOptions = {}) {",' return callManaged("/api/ai-gateway/runtime/text", {',' capability: options.capability ?? "llm", use_case: options.useCase ?? "chat",'," model: options.model, idempotency_key: options.idempotencyKey,"," stream: options.stream ?? false, messages,"," });","}","","export async function mistflowManagedImage(prompt: string, options: { model?: string; size?: string; idempotencyKey?: string } = {}) {",' const r = await callManaged("/api/ai-gateway/runtime/image", {',' capability: "image", use_case: "generation",'," prompt, model: options.model, idempotency_key: options.idempotencyKey, size: options.size,"," });"," return await r.json();","}","","export async function mistflowManagedRealtimeSession(instructions?: string) {"," return ai.createRealtimeSession({ instructions });","}","","export async function reportDirectAIUsage(report: Record<string, unknown>): Promise<void> {"," if (!isManagedAvailable()) return;",' try { await callManaged("/api/ai-gateway/runtime/report-usage", report); } catch { /* never break user request path */ }',"}",""].join(`
5078
- `)),M(h,"lib/ai.ts",['import "server-only";',"",'export * from "./server/ai";',""].join(`
5079
- `)),!0&&(u&&an(u,"ai-chat"))||M(h,"app/api/chat/route.ts",['import { ai, AIConfigError, AICreditExhaustedError } from "@/lib/server/ai";','import type { CoreMessage } from "ai";',"","export async function POST(req: Request) {"," const { messages } = (await req.json()) as { messages?: CoreMessage[] };"," if (!messages) {",' return Response.json({ error: "messages is required" }, { status: 400 });'," }"," try {"," return await ai.streamText({ messages });"," } catch (e) {"," if (e instanceof AIConfigError) return Response.json({ error: e.message }, { status: 503 });"," if (e instanceof AICreditExhaustedError) return Response.json({ error: e.message }, { status: 402 });",' return Response.json({ error: "AI request failed" }, { status: 500 });'," }","}",""].join(`
5080
- `)));let rt=Array.isArray(u?.integrations)?u.integrations.map(b=>({name:b.name,preset:b.preset,envVars:b.envVars??[]})):[],ze={};for(let b of rt)for(let _ of b.envVars??[])ze[_.key]||(ze[_.key]={description:_.description,setupUrl:_.setupUrl,...b.name?{integration:b.name}:{}});if(q&&j)for(let b of ko.envVars)ze[b.key]||(ze[b.key]={description:b.description,setupUrl:b.setupUrl??"https://app.nango.dev",integration:"connected-services"});let Re=D?.requestedSubdomain||void 0,W={name:U,methodologyVersion:R?.version??"1.0",createdAt:new Date().toISOString(),...s?{planId:s}:{},...Re?{requestedSubdomain:Re}:{},plan:Array.isArray(u?.steps)?{...u,steps:u.steps.map(b=>({number:b.number,name:b.name??b.title,description:b.description,entities:b.entities,pages:b.pages,features:b.features,status:"pending"}))}:u,dbProvider:F,env:{managed:{...!v&&j?{DATABASE_URL:{description:"Postgres connection URL",scope:"production"}}:v?{}:{TURSO_URL:{description:"Turso database URL",scope:"production"},TURSO_AUTH_TOKEN:{description:"Turso database auth token",scope:"production"}},...w?{}:{AUTH_SECRET:{description:"Auth encryption secret",scope:"production"}},...A?{STRIPE_SECRET_KEY:{description:"Stripe secret key",scope:"production"},STRIPE_WEBHOOK_SECRET:{description:"Stripe webhook signing secret",scope:"production"},NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY:{description:"Stripe publishable key",scope:"production"}}:{},...J?{RESEND_API_KEY:{description:"Resend API key \u2014 managed by Mistflow by default, override with your own key from resend.com",scope:"production"},EMAIL_FROM:{description:"Sender email address \u2014 managed by Mistflow by default",scope:"production"}}:{},...le||V||Dn(u)?{MISTFLOW_RUNTIME_KEY:{description:"Mistflow runtime key \u2014 authenticates AI gateway, file storage, and other managed features",scope:"production"}}:{}},...Object.keys(ze).length>0?{required:ze}:{}},authModel:x??"email",roles:u?.roles??null,navStyle:u?.navStyle??"sidebar",multiTenant:u?.multiTenant??!1,hasAdmin:te,hasResend:J,hasStorage:V,hasAI:le,deploy:null};M(h,"mistflow.json",JSON.stringify(W,null,2)),nn(h);let ce=Su(32).toString("hex"),Ae=A?`
4764
+ import { sql } from "drizzle-orm";`).replace(/pgTable/g,"sqliteTable").replace(/drizzle-orm\/pg-core/g,"drizzle-orm/sqlite-core").replace(/timestamp\("created_at"\)\.notNull\(\)\.defaultNow\(\)/g,'text("created_at").notNull().default(sql`(CURRENT_TIMESTAMP)`)').replace(/timestamp\("updated_at"\)\.notNull\(\)\.defaultNow\(\)/g,'text("updated_at").notNull().default(sql`(CURRENT_TIMESTAMP)`)').replace(/timestamp\("([a-z_]+)"\)/g,'text("$1")').replace(/boolean\("([a-z_]+)"\)\.default\(false\)/g,'integer("$1", { mode: "boolean" }).default(false)')),k=Ho(k),k=k+`
4765
+
4766
+ `+Mu+`
4767
+ `,A(h,"AGENTS.md",k),A(h,"CLAUDE.md",k),Fu(h,k)}x.skills&&Object.keys(x.skills).length>0&&Bu(h,x.skills);let ae=a??d?.designMd??E;if(ae&&A(h,"DESIGN.md",ae),c&&A(h,"PLAN.md",c),te)if(A(h,"lib/db.ts",['import { neon } from "@neondatabase/serverless";','import { drizzle as drizzleNeon } from "drizzle-orm/neon-http";','import * as schema from "@/db/schema";',"","// eslint-disable-next-line @typescript-eslint/no-explicit-any","let _db: any = null;","","function getDb() {"," if (!_db) {",' if (process.env.DATABASE_URL && process.env.DATABASE_URL !== "pglite") {'," // Production / remote Postgres. Pass `schema` so `db.query.<table>`"," // (Drizzle's relational query builder) is populated \u2014 without it,"," // `db.query.<anything>` is undefined and crashes at runtime."," const sql = neon(process.env.DATABASE_URL);"," _db = drizzleNeon(sql, { schema });",' } else if (process.env.NODE_ENV !== "production") {'," // Local dev \u2014 PGlite (zero-install embedded Postgres). Gating on"," // NODE_ENV !== 'production' lets webpack/esbuild dead-code-eliminate"," // this entire branch in `next build` and the Cloudflare Worker"," // bundle, so pglite + its 30MB WASM never reach prod. In `next dev`"," // the branch is live, the static require resolves ./db-local, and"," // pglite (declared in next.config.ts's serverExternalPackages) is"," // left as an external runtime import. No bundler-evasion tricks."," // eslint-disable-next-line @typescript-eslint/no-require-imports",' const { createLocalDb } = require("./db-local");'," _db = createLocalDb();"," } else {"," throw new Error(",` "DATABASE_URL is not set in production. The Mistflow deploy pipeline injects it automatically \u2014 check the project's env vars in the dashboard."`," );"," }"," }"," return _db;","}","","// Lazy proxy \u2014 DB isn't initialized at import/build time","// eslint-disable-next-line @typescript-eslint/no-explicit-any","export const db: any = new Proxy({} as any, {"," get(_target, prop, receiver) {"," const realDb = getDb();"," const value = Reflect.get(realDb, prop, receiver);",' if (typeof value === "function") {'," return value.bind(realDb);"," }"," return value;"," },","});",""].join(`
4768
+ `)),A(h,"lib/db-local.ts",["// Local-dev-only DB factory. Isolated from lib/db.ts so the production","// Cloudflare Worker bundle never loads drizzle-orm/pglite or its 30MB","// WASM binary. db.ts only requires this file when NODE_ENV !==","// 'production', so esbuild dead-code-eliminates the import in prod and","// never reaches this file. In dev, pglite is declared as a server","// external package in next.config.ts so webpack leaves it alone too.","",'import { PGlite } from "@electric-sql/pglite";','import { drizzle } from "drizzle-orm/pglite";','import * as schema from "@/db/schema";',"","// eslint-disable-next-line @typescript-eslint/no-explicit-any","export function createLocalDb(): any {",' const client = new PGlite("./local.pg");'," return drizzle(client, { schema });","}",""].join(`
4769
+ `)),A(h,"drizzle.config.ts",['import { defineConfig } from "drizzle-kit";',"","// PGlite for local dev (no Postgres install needed), Mistflow Cloud for production",'const isPglite = !process.env.DATABASE_URL || process.env.DATABASE_URL === "pglite";',"","export default defineConfig({",' schema: "./db/schema",',' out: "./db/migrations",',' dialect: "postgresql",',' ...(isPglite ? { driver: "pglite", dbCredentials: { url: "./local.pg" } } : { dbCredentials: { url: process.env.DATABASE_URL! } }),',"});",""].join(`
4770
+ `)),H)A(h,"db/schema/index.ts",["// Re-export schema tables here as they are added.",re?'export * from "./mcp";':null,z?'export * from "./connected-services";':null,""].filter(k=>k!==null).join(`
4771
+ `)),A(h,"db/index.ts",["// Re-export schema tables here as they are added.",'export * from "./schema";',""].join(`
4772
+ `));else{if(A(h,"db/schema/auth.ts",['import { pgTable, text, boolean, timestamp } from "drizzle-orm/pg-core";',"",'export const user = pgTable("user", {',' id: text("id").primaryKey(),',' name: text("name").notNull(),',' email: text("email").notNull().unique(),',' emailVerified: boolean("email_verified").notNull().default(false),',' image: text("image"),',' role: text("role").default("user"),',' banned: boolean("banned").default(false),',' banReason: text("ban_reason"),',' banExpires: timestamp("ban_expires"),',' createdAt: timestamp("created_at").notNull(),',' updatedAt: timestamp("updated_at").notNull(),',"});","",'export const session = pgTable("session", {',' id: text("id").primaryKey(),',' expiresAt: timestamp("expires_at").notNull(),',' token: text("token").notNull().unique(),',' createdAt: timestamp("created_at").notNull(),',' updatedAt: timestamp("updated_at").notNull(),',' ipAddress: text("ip_address"),',' userAgent: text("user_agent"),',' userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),',' impersonatedBy: text("impersonated_by"),',"});","",'export const account = pgTable("account", {',' id: text("id").primaryKey(),',' accountId: text("account_id").notNull(),',' providerId: text("provider_id").notNull(),',' userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),',' accessToken: text("access_token"),',' refreshToken: text("refresh_token"),',' idToken: text("id_token"),',' accessTokenExpiresAt: timestamp("access_token_expires_at"),',' refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),',' scope: text("scope"),',' password: text("password"),',' createdAt: timestamp("created_at").notNull(),',' updatedAt: timestamp("updated_at").notNull(),',"});","",'export const verification = pgTable("verification", {',' id: text("id").primaryKey(),',' identifier: text("identifier").notNull(),',' value: text("value").notNull(),',' expiresAt: timestamp("expires_at").notNull(),',' createdAt: timestamp("created_at"),',' updatedAt: timestamp("updated_at"),',"});",""].join(`
4773
+ `)),A(h,"db/schema/index.ts",['export * from "./auth";',re?'export * from "./mcp";':null,z?'export * from "./connected-services";':null,""].filter(W=>W!==null).join(`
4774
+ `)),A(h,"db/index.ts",['export * from "./schema/auth";',re?'export * from "./schema/mcp";':null,z?'export * from "./schema/connected-services";':null,""].filter(W=>W!==null).join(`
4775
+ `)),re&&te){let W=Fo({appName:S,appVersion:"0.0.1",useAiChatManifest:nn(d)});for(let ge of W.files)A(h,ge.path,ge.content);A(h,"db/schema/mcp.ts",W.schemaFragment)}if(z&&te)for(let W of kr.files){if(W.kind==="schema"){A(h,"db/schema/connected-services.ts",W.source);continue}W.kind==="migration"||W.kind==="manifest"||W.source&&A(h,W.targetFile,W.source)}let k=d.defaultRole??"user";A(h,"lib/auth.ts",['import { betterAuth } from "better-auth";','import { drizzleAdapter } from "better-auth/adapters/drizzle";','import { admin } from "better-auth/plugins/admin";','import { genericOAuth } from "better-auth/plugins/generic-oauth";','import { nextCookies } from "better-auth/next-js";','import { db } from "./db";','import * as schema from "@/db";',"","async function sendEmail({ to, subject, html, fallbackUrl }: { to: string; subject: string; html: string; fallbackUrl?: string }) {"," const apiKey = process.env.RESEND_API_KEY;"," if (!apiKey) {"," if (fallbackUrl) {"," console.error(`\\n[auth] No RESEND_API_KEY set. Email to ${to} was not sent.`);"," console.error(`[auth] Dev fallback \u2014 use this link directly:\\n ${fallbackUrl}\\n`);"," } else {"," console.error(`[auth] RESEND_API_KEY not set \u2014 skipping email send to ${to}`);"," }"," return;"," }",' const from = process.env.EMAIL_FROM || "noreply@mail.mistflow.app";',' const res = await fetch("https://api.resend.com/emails", {',' method: "POST",',' headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },'," body: JSON.stringify({ from, to, subject, html }),"," });"," if (!res.ok) {",' const body = await res.text().catch(() => "unknown");'," console.error(`[auth] Email send failed (${res.status}): ${body}`);"," throw new Error(`Email send failed: ${res.status}`);"," }","}","","function createAuth() {",' const baseURL = process.env.BETTER_AUTH_URL || process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000";',' const isLocal = baseURL.includes("localhost") || baseURL.includes("127.0.0.1");'," const canSendEmail = Boolean(process.env.RESEND_API_KEY);"," const mistflowAppId = process.env.MISTFLOW_APP_ID;"," const mistflowOAuthProxyURL = process.env.MISTFLOW_OAUTH_PROXY_URL;"," const googleClientId = process.env.GOOGLE_CLIENT_ID;"," const googleClientSecret = process.env.GOOGLE_CLIENT_SECRET;"," // Preview deploys skip email verification so smoke tests and human"," // pre-launch testing can sign up and log in without a real inbox."," // The Mistflow deploy pipeline sets MISTFLOW_RUNTIME_KEY_ENVIRONMENT",' // to "preview" for staging deploys and "production" for prod. Production'," // always requires verification \u2014 there is no way to misconfigure prod into"," // the relaxed mode because the env var is set server-side at deploy."," const runtimeKeyEnvironment = process.env.MISTFLOW_RUNTIME_KEY_ENVIRONMENT;",' const isPreview = runtimeKeyEnvironment === "preview" || runtimeKeyEnvironment === "dev";',' const isProduction = runtimeKeyEnvironment === "production";'," // Google sign-in routing:"," // - Production with both GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET set:"," // use the customer's Google client (consent screen branded as their app)."," // - Otherwise (preview, dev, or missing BYO keys): use Mistflow's hosted"," // OAuth proxy. Customers never need to register preview URLs in Google"," // Console \u2014 the proxy fans them out from a single canonical redirect URI."," const useByoGoogle = isProduction && Boolean(googleClientId && googleClientSecret);"," const useProxyGoogle = !useByoGoogle && Boolean(mistflowAppId && mistflowOAuthProxyURL);"," const googleProviderConfig = useByoGoogle"," ? {",' providerId: "google",'," clientId: googleClientId!,"," clientSecret: googleClientSecret!,",' discoveryUrl: "https://accounts.google.com/.well-known/openid-configuration",',' scopes: ["openid", "email", "profile"],'," pkce: true,"," redirectURI: `${baseURL}/api/auth/oauth2/callback/google`,"," }"," : useProxyGoogle"," ? {",' providerId: "google",'," clientId: mistflowAppId!,",' clientSecret: "pkce",'," discoveryUrl: `${mistflowOAuthProxyURL}/.well-known/openid-configuration`,",' scopes: ["openid", "email", "profile"],'," pkce: true,"," redirectURI: `${baseURL}/api/auth/oauth2/callback/google`,"," }"," : null;"," if (googleProviderConfig) {",' console.error(`[auth] Google sign-in: ${useByoGoogle ? "BYO (your Google client)" : "proxy (Mistflow-hosted)"}`);'," }"," // Refuse to boot a production app with email auth but no mail sender. Mistflow's"," // deploy pipeline injects a managed Resend key automatically; if it's missing,"," // that's a real misconfig and silent fallbacks let unverified users sign up."," // Preview deploys are exempt \u2014 they don't need email since verification is off."," if (!isLocal && !isPreview && !canSendEmail) {",` throw new Error("[auth] RESEND_API_KEY is required in production. The Mistflow deploy pipeline injects one automatically \u2014 if you're seeing this, check the project's env vars in the dashboard, or set your own RESEND_API_KEY.");`," }"," return betterAuth({"," baseURL,"," // Include the Mistflow OAuth proxy in trustedOrigins so Better Auth"," // accepts the OAuth callback round-trip without 'invalid origin' errors."," trustedOrigins: [baseURL, mistflowOAuthProxyURL].filter((u): u is string => Boolean(u)),",' database: drizzleAdapter(db, { provider: "pg", schema }),'," emailAndPassword: {"," enabled: true,"," requireEmailVerification: !isLocal && !isPreview && canSendEmail,"," sendResetPassword: async ({ user, token }: { user: { email: string; name: string }; url: string; token: string }) => {"," // Better Auth's default reset URL points at /reset-password/:token (path),"," // which our frontend doesn't route. We build our own pointing at our"," // /reset-password?token=xxx page which reads the token from the query."," const resetUrl = `${baseURL}/reset-password?token=${token}`;"," await sendEmail({"," to: user.email,",' subject: "Reset your password",',' html: `<p>Hi ${user.name},</p><p>Click the link below to reset your password:</p><p><a href="${resetUrl}">${resetUrl}</a></p>`,'," fallbackUrl: isLocal ? resetUrl : undefined,"," });"," },"," },"," emailVerification: {"," sendOnSignUp: canSendEmail,"," autoSignInAfterVerification: true,"," sendVerificationEmail: async ({ user, url }: { user: { email: string; name: string }; url: string }) => {"," await sendEmail({"," to: user.email,",' subject: "Verify your email address",',' html: `<p>Hi ${user.name},</p><p>Click the link below to verify your email:</p><p><a href="${url}">${url}</a></p>`,'," fallbackUrl: isLocal ? url : undefined,"," });"," },"," },"," secret: process.env.AUTH_SECRET,"," plugins: [",` admin({ defaultRole: "${k}" }),`," ...(googleProviderConfig ? [genericOAuth({ config: [googleProviderConfig] })] : []),"," nextCookies(),"," ],"," databaseHooks: {"," user: {"," create: {"," // Auto-promote the app admin on first signup when the plan has"," // a global admin role. ADMIN_EMAIL is injected by the Mistflow"," // deploy pipeline (the email of the account that ran"," // mist_deploy). Tenant roles such as workspace owner are stored"," // separately in app tables. Email verification still gates login"," // when Resend is configured, so a collision attempt can't"," // actually sign in without clicking a link delivered to the"," // owner's inbox."," before: async (user: { email?: string; [k: string]: unknown }) => {"," const adminEmail = process.env.ADMIN_EMAIL;"," if (adminEmail && user.email?.toLowerCase() === adminEmail.toLowerCase()) {",' return { data: { ...user, role: "admin" } };'," }"," return { data: user };"," },"," },"," },"," },"," socialProviders: {"," ...(process.env.GITHUB_CLIENT_ID ? {"," github: {"," clientId: process.env.GITHUB_CLIENT_ID,"," clientSecret: process.env.GITHUB_CLIENT_SECRET!,"," },"," } : {}),"," },"," });","}","","// Lazy init \u2014 process.env isn't populated at module scope on Cloudflare Workers.","// The `has` trap is required: better-auth's toNextJsHandler does",'// `"handler" in auth ? auth.handler(request) : auth(request)` \u2014 without a `has`',"// trap the default forwards to the empty target object, returns false, and the","// handler tries to call the Proxy as a function, which throws TypeError and","// returns 500 on every /api/auth/* request.","let _auth: ReturnType<typeof createAuth> | null = null;","export const auth = new Proxy({} as ReturnType<typeof createAuth>, {"," get(_target, prop, receiver) {"," if (!_auth) _auth = createAuth();"," const value = Reflect.get(_auth, prop, receiver);",' if (typeof value === "function") return value.bind(_auth);'," return value;"," },"," has(_target, prop) {"," if (!_auth) _auth = createAuth();"," return prop in _auth;"," },","});",""].join(`
4776
+ `))}}else A(h,"package.json",JSON.stringify({name:S,version:"0.1.0",private:!0},null,2));let Ue=a??d?.designMd??E;A(h,"app/globals.css",Yi(I,Ue)),A(h,"app/layout.tsx",Xu(S,I,y?.language)),A(h,"README.md",hp(S,d,{hasStripe:ne,hasResend:ce,hasStorage:ee,hasAdmin:q,hasAI:P,isNeon:te})),A(h,"contracts/README.md",Bo());let G=d?.dataModel??[],xe=new Set,we=0;for(let w of G){let C=w.entity??w.name;if(!C||typeof C!="string")continue;let U=Wo(C);xe.has(U)||(xe.add(U),A(h,U,zo(C)),we++)}we===0&&A(h,"contracts/.gitkeep","");let se=[],Se=d?.publicPages;if(Array.isArray(Se))se=Se;else if(typeof Se=="string"){try{se=JSON.parse(Se)}catch{se=[]}Array.isArray(se)||(se=[])}if(d?.publicLanding===!1)se=se.filter(w=>w!=="/");else if(!se.includes("/")){let w=d?.steps?.some(U=>{let Ie=((U.name??"")+" "+(U.description??"")).toLowerCase();return Ie.includes("landing")||Ie.includes("marketing")||Ie.includes("homepage")}),C=d?.pages?.some(U=>U.path==="/");(w||C)&&(se=["/",...se])}let je={name:S,summary:d?.summary,authModel:J,roles:d?.roles,defaultRole:d?.defaultRole,publicPages:se,navStyle:d?.navStyle,multiTenant:d?.multiTenant,pages:d?.pages,dataModel:d?.dataModel,design:d?.design},ot=sp(je);ot&&A(h,"middleware.ts",ot);let Ve=ip(je);Ve&&A(h,Ve.path,Ve.content);let ft=ap(je);if(ft&&A(h,"lib/roles.ts",ft),A(h,"app/page.tsx",lp(je)),A(h,"app/(dashboard)/layout.tsx",cp(je,q)),A(h,"app/(dashboard)/dashboard/page.tsx",dp(je)),je.multiTenant){let w=up(je,te);w&&A(h,"db/schema/organization.ts",w);let C=pp(je);C&&A(h,"lib/org.ts",C);let U=mp(je);U&&A(h,"components/org-switcher.tsx",U)}A(h,"tsconfig.json",JSON.stringify({compilerOptions:{target:"ES2017",lib:["dom","dom.iterable","esnext"],allowJs:!0,skipLibCheck:!0,strict:!1,noEmit:!0,esModuleInterop:!0,module:"esnext",moduleResolution:"bundler",resolveJsonModule:!0,isolatedModules:!0,jsx:"preserve",incremental:!0,plugins:[{name:"next"}],paths:{"@/*":["./*"]}},include:["next-env.d.ts","**/*.ts","**/*.tsx",".next/types/**/*.ts"],exclude:["node_modules"]},null,2)),ne&&A(h,"lib/stripe.ts",['import Stripe from "stripe";',"","let _stripe: Stripe | null = null;","","function getStripe(): Stripe {"," if (!_stripe) {"," if (!process.env.STRIPE_SECRET_KEY) {",' throw new Error("STRIPE_SECRET_KEY is not set");'," }"," _stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {"," typescript: true,"," });"," }"," return _stripe;","}","","// Lazy proxy \u2014 Stripe isn't initialized at import/build time","export const stripe = new Proxy({} as Stripe, {"," get(_target, prop) {"," return (getStripe() as unknown as Record<string | symbol, unknown>)[prop];"," },","});",""].join(`
4777
+ `)),ce&&(A(h,"lib/resend.ts",['import { Resend } from "resend";',"","let _resend: Resend | null = null;","","function getResend(): Resend {"," if (!_resend) {"," if (!process.env.RESEND_API_KEY) {",' throw new Error("RESEND_API_KEY is not set");'," }"," _resend = new Resend(process.env.RESEND_API_KEY);"," }"," return _resend;","}","","// Lazy proxy \u2014 Resend isn't initialized at import/build time","export const resend = new Proxy({} as Resend, {"," get(_target, prop) {"," return (getResend() as unknown as Record<string | symbol, unknown>)[prop];"," },","});",""].join(`
4778
+ `)),A(h,"lib/email.ts",['import { resend } from "./resend";',"",'const FROM = process.env.EMAIL_FROM ?? "onboarding@resend.dev";',"","export async function sendEmail({"," to,"," subject,"," react,","}: {"," to: string;"," subject: string;"," react: React.ReactElement;","}) {"," return resend.emails.send({ from: FROM, to, subject, react });","}",""].join(`
4779
+ `)),A(h,"lib/server/email.ts",['import "server-only";','import { render } from "@react-email/render";','import type { ReactElement } from "react";',"",'export class EmailConfigError extends Error { readonly code = "email_config"; }','export class EmailRateLimitError extends Error { readonly code = "email_rate_limit"; }','export class EmailCreditExhaustedError extends Error { readonly code = "email_credits_exhausted"; }','export class EmailUpstreamError extends Error { readonly code = "email_upstream_error"; }','export class EmailValidationError extends Error { readonly code = "email_validation"; }',"","function runtimeKey(): string | undefined {"," return process.env.MISTFLOW_RUNTIME_KEY ?? process.env.MISTFLOW_AI_RUNTIME_KEY;","}","function apiUrl(): string {",' return (process.env.MISTFLOW_API_URL ?? process.env.MISTFLOW_AI_API_URL ?? "https://api.mistflow.ai").replace(/\\/$/, "");',"}","","export type EmailSendOptions = {"," to: string | string[];"," subject: string;"," html?: string;"," text?: string;"," react?: ReactElement;"," cc?: string[];"," bcc?: string[];"," replyTo?: string;"," idempotencyKey?: string;"," tags?: Record<string, string>;","};","","export const email = {"," /** Send an email. Sandbox decision is server-enforced via the runtime key's"," * environment field \u2014 your app cannot accidentally email real users from"," * dev or preview deploys. Real sends only fire when the key is",' * environment="production".'," *"," * React components are rendered to HTML locally before the gateway call"," * (React doesn't serialize over HTTP). */"," async send(opts: EmailSendOptions): Promise<{ id: string; sandbox?: boolean }> {"," if (!opts.to || (Array.isArray(opts.to) && opts.to.length === 0)) {",' throw new EmailValidationError("Recipient (to) is required");'," }"," if (!opts.subject) {",' throw new EmailValidationError("Subject is required");'," }"," let html = opts.html;"," if (opts.react && !html) {"," html = await render(opts.react);"," }",""," const key = runtimeKey();"," if (key) {"," // Managed mode \u2014 gateway handles sandbox enforcement, idempotency, billing."," const body = {"," to: opts.to, subject: opts.subject, html, text: opts.text,"," cc: opts.cc, bcc: opts.bcc, replyTo: opts.replyTo,"," idempotencyKey: opts.idempotencyKey, tags: opts.tags,"," };",' const r = await fetch(apiUrl() + "/api/email/send", {',' method: "POST",',' headers: { "Content-Type": "application/json", "X-Mistflow-AI-Key": key },'," body: JSON.stringify(body),"," });",' if (r.status === 401) throw new EmailConfigError("Runtime key rejected by gateway. Re-run mist_setup.");',' if (r.status === 402) throw new EmailCreditExhaustedError("Email/AI credits exhausted.");',' if (r.status === 429) throw new EmailRateLimitError("Rate limited.");'," if (r.status === 422) throw new EmailValidationError(await r.text());",' if (!r.ok) throw new EmailUpstreamError("Email gateway error " + r.status);'," return (await r.json()) as { id: string; sandbox?: boolean };"," }",""," if (process.env.RESEND_API_KEY) {"," // BYOK direct \u2014 no sandbox enforcement, user is responsible.",' const { Resend } = await import("resend");'," const client = new Resend(process.env.RESEND_API_KEY);",' const fromAddr = process.env.EMAIL_FROM ?? "onboarding@resend.dev";'," const result = await client.emails.send({"," from: fromAddr,"," to: opts.to, subject: opts.subject, html, text: opts.text,"," cc: opts.cc, bcc: opts.bcc, reply_to: opts.replyTo,",' ...(opts.idempotencyKey ? { headers: { "Idempotency-Key": opts.idempotencyKey } } : {}),'," });"," if (result.error) throw new EmailUpstreamError(result.error.message);",' return { id: result.data?.id ?? "unknown", sandbox: false };'," }",""," throw new EmailConfigError(",` "Email is not configured. Run 'mist_setup' for managed mode (sandbox-safe in dev) or set RESEND_API_KEY."`," );"," },","};",""].join(`
4780
+ `))),A(h,"lib/server/storage.ts",['import "server-only";',"",'export class StorageConfigError extends Error { readonly code = "storage_config"; }','export class StorageUpstreamError extends Error { readonly code = "storage_upstream_error"; }','export class StorageQuotaError extends Error { readonly code = "storage_quota"; }',"","function runtimeKey(): string {"," const k = process.env.MISTFLOW_RUNTIME_KEY ?? process.env.MISTFLOW_AI_RUNTIME_KEY;",` if (!k) throw new StorageConfigError("Storage requires managed mode. Run 'mist_setup' to enable.");`," return k;","}","function apiUrl(): string {",' return (process.env.MISTFLOW_API_URL ?? process.env.MISTFLOW_AI_API_URL ?? "https://api.mistflow.ai").replace(/\\/$/, "");',"}","async function callGateway<T>(path: string, body: unknown): Promise<T> {"," const r = await fetch(apiUrl() + path, {",' method: "POST",',' headers: { "Content-Type": "application/json", "X-Mistflow-AI-Key": runtimeKey() },'," body: JSON.stringify(body),"," });",' if (r.status === 401) throw new StorageConfigError("Runtime key rejected. Re-run mist_setup.");',' if (r.status === 402) throw new StorageQuotaError("Storage quota exhausted.");',' if (r.status === 413) throw new StorageQuotaError("File exceeds the 100MB upload limit.");',' if (!r.ok) throw new StorageUpstreamError("Storage gateway error " + r.status);'," return (await r.json()) as T;","}","","export const storage = {"," /** Upload a Blob/File. Backend mints a signed URL; client PUTs directly to R2. */"," async put(opts: { key: string; blob: Blob; contentType: string; expiresIn?: number }): Promise<{ finalKey: string }> {"," const signed = await callGateway<{ uploadUrl: string; finalKey: string; expiresAt: string }>(",' "/api/storage/sign-upload",'," {"," key: opts.key,"," contentType: opts.contentType,"," contentLength: opts.blob.size,"," expiresIn: opts.expiresIn,"," },"," );"," const res = await fetch(signed.uploadUrl, {",' method: "PUT",'," body: opts.blob,",' headers: { "Content-Type": opts.contentType },'," });"," if (!res.ok) {",' throw new StorageUpstreamError("Direct R2 upload failed: " + res.status);'," }"," return { finalKey: signed.finalKey };"," },",""," /** Get a short-lived signed download URL for a stored object. */"," async get(opts: { key: string; expiresIn?: number }): Promise<{ url: string; expiresAt: string }> {"," const signed = await callGateway<{ downloadUrl: string; expiresAt: string }>(",' "/api/storage/sign-download",'," { key: opts.key, expiresIn: opts.expiresIn },"," );"," return { url: signed.downloadUrl, expiresAt: signed.expiresAt };"," },",""," /** Delete an object. Server-side delete via R2 SDK. */"," async delete(opts: { key: string }): Promise<void> {",' const r = await fetch(apiUrl() + "/api/storage/object", {',' method: "DELETE",',' headers: { "Content-Type": "application/json", "X-Mistflow-AI-Key": runtimeKey() },'," body: JSON.stringify({ key: opts.key }),"," });"," if (!r.ok && r.status !== 204) {",' throw new StorageUpstreamError("Delete failed: " + r.status);'," }"," },",""," /** List objects under an optional prefix. Always scoped to your project. */"," async list(opts: { prefix?: string; limit?: number; cursor?: string } = {}): Promise<{ keys: { key: string; size: number }[]; cursor?: string }> {",' return callGateway("/api/storage/list", opts);'," },","};",""].join(`
4781
+ `)),A(h,"app/api/upload/route.ts",["import {"," storage,"," StorageConfigError,"," StorageQuotaError,"," StorageUpstreamError,",'} from "@/lib/server/storage";',"","export async function POST(req: Request) {"," const formData = await req.formData();",' const file = formData.get("file");',' const prefix = (formData.get("prefix") as string | null) ?? "uploads";',""," if (!(file instanceof File)) {",' return Response.json({ error: "Choose a file to upload." }, { status: 400 });'," }","",' const safeName = file.name.replace(/[^a-zA-Z0-9.\\-_]/g, "_");'," const key = `${prefix}/${crypto.randomUUID()}-${safeName}`;",""," try {"," const { finalKey } = await storage.put({"," key,"," blob: file,",' contentType: file.type || "application/octet-stream",'," });"," return Response.json({ key: finalKey });"," } catch (err) {"," if (err instanceof StorageQuotaError) {",' return Response.json({ error: "File too large or storage full." }, { status: 413 });'," }"," if (err instanceof StorageConfigError) {",` return Response.json({ error: "Storage isn't configured for this app yet." }, { status: 503 });`," }"," if (err instanceof StorageUpstreamError) {",' return Response.json({ error: "Storage is temporarily unavailable. Try again." }, { status: 503 });'," }"," throw err;"," }","}",""].join(`
4782
+ `)),A(h,"components/file-upload.tsx",['"use client";','import { useCallback, useState } from "react";',"","interface FileUploadProps {"," /** Called with the persisted storage key after upload succeeds. */"," onUpload: (key: string) => void;"," /** Accept attribute for the file input. Defaults to images. */"," accept?: string;"," /** Client-side size guard, in megabytes. Server still enforces a hard cap. */"," maxSizeMB?: number;",' /** Optional key prefix sent to the upload route. Defaults to "uploads". */'," prefix?: string;","}","",'export function FileUpload({ onUpload, accept = "image/*", maxSizeMB = 10, prefix }: FileUploadProps) {'," const [isDragging, setIsDragging] = useState(false);"," const [uploading, setUploading] = useState(false);"," const [error, setError] = useState<string | null>(null);",""," const handleUpload = useCallback(async (file: File) => {"," setError(null);"," if (file.size > maxSizeMB * 1024 * 1024) {"," setError(`File is over the ${maxSizeMB}MB limit.`);"," return;"," }"," setUploading(true);"," try {"," const formData = new FormData();",' formData.append("file", file);',' if (prefix) formData.append("prefix", prefix);',' const res = await fetch("/api/upload", { method: "POST", body: formData });'," if (!res.ok) {"," const body = await res.json().catch(() => ({}));",' throw new Error(body?.error ?? "Upload failed");'," }"," const { key } = (await res.json()) as { key: string };"," onUpload(key);"," } catch (err) {",' setError(err instanceof Error ? err.message : "Upload failed");'," } finally {"," setUploading(false);"," }"," }, [maxSizeMB, onUpload, prefix]);",""," return (",' <div className="space-y-2">'," <label",' htmlFor="file-upload-input"'," onDragOver={(e) => { e.preventDefault(); setIsDragging(true); }}"," onDragLeave={() => setIsDragging(false)}"," onDrop={(e) => {"," e.preventDefault();"," setIsDragging(false);"," const file = e.dataTransfer.files[0];"," if (file) handleUpload(file);"," }}"," className={`flex cursor-pointer flex-col items-center justify-center gap-1 rounded-lg border-2 border-dashed p-8 text-center text-sm transition-colors ${",' isDragging ? "border-primary bg-primary/5" : "border-muted-foreground/25 hover:border-primary/50"'," }`}"," >"," <input",' id="file-upload-input"',' type="file"'," accept={accept}",' className="hidden"'," onChange={(e) => { const f = e.target.files?.[0]; if (f) handleUpload(f); }}"," />",' <span className="font-medium">',' {uploading ? "Uploading..." : "Drop a file here or click to browse"}'," </span>",' <span className="text-xs text-muted-foreground">Up to {maxSizeMB}MB</span>'," </label>",' {error ? <p className="text-sm text-destructive">{error}</p> : null}'," </div>"," );","}",""].join(`
4783
+ `)),P&&(A(h,"lib/server/ai.ts",['import "server-only";',"",'import { createOpenAI } from "@ai-sdk/openai";','import { generateText, streamText, type CoreMessage } from "ai";','import type { z } from "zod";','import { zodToJsonSchema } from "zod-to-json-schema";',"","// Bumped from 2 -> 3 when the wrapper unified on @ai-sdk/openai + the","// Mistflow /v1/* OpenAI-compat surface. Earlier versions called the","// Mistflow-native /api/ai-gateway/runtime/* endpoints directly.","export const MISTFLOW_AI_HELPER_VERSION = 4;","","// Embedding dimension is SCHEMA for any app that stores vectors in a","// DB column (pgvector etc.). The scaffold declared the column with","// this exact value; if you swap the embed model or switch BYOK modes","// and the new model returns a different dim, ai.embed() will throw","// an actionable error BEFORE the bad vectors hit your DB.","// Managed mode default (text-embedding-3-small) = 1536. If you switch","// to a different model with a different dim, you must also migrate","// every vector column and re-embed every existing row.","export const EMBEDDING_DIMENSIONS = 1536;","","// \u2500\u2500 Typed error hierarchy \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500","// Catch the specific class to give the user actionable copy.",'export class AIConfigError extends Error { readonly code = "ai_config"; }','export class AIRateLimitError extends Error { readonly code = "ai_rate_limit"; }','export class AICreditExhaustedError extends Error { readonly code = "ai_credits_exhausted"; }','export class AIModelError extends Error { readonly code = "ai_model_error"; }',"export class AISchemaError extends Error {",' readonly code = "ai_schema_error";'," /** Raw model output that failed validation. Redacted by default; set"," * MISTFLOW_DEBUG=1 in dev to see full output. */"," readonly rawOutput?: string;"," constructor(message: string, rawOutput?: string) {"," super(message);"," this.rawOutput = rawOutput;"," }","}","","function redact(s: string | undefined): string | undefined {"," if (!s) return s;",' if (process.env.MISTFLOW_DEBUG === "1") return s;',' if (s.length <= 80) return "<redacted>";',' return s.slice(0, 40) + " ...<redacted>... " + s.slice(-20);',"}","","// \u2500\u2500 Mode detection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500","// Two modes:","// 1. Managed (default): MISTFLOW_RUNTIME_KEY set \u2192 calls Mistflow /v1/*","// surface, billed in Mistflow credits.","// 2. Direct BYOK: OPENROUTER_API_KEY set \u2192 calls openrouter.ai/api/v1/*","// directly with the customer's key. Mistflow never sees these","// requests. Customer pays OpenRouter directly.","// Voice realtime is NOT part of either mode \u2014 it's a separate xAI","// direct integration. See methodologies/voice-realtime.md.",'const DEFAULT_MISTFLOW_AI_API_URL = "https://api.mistflow.ai";',"","function runtimeKey(): string | undefined {"," // Canonical name first; legacy alias for one release of compat."," return process.env.MISTFLOW_RUNTIME_KEY ?? process.env.MISTFLOW_AI_RUNTIME_KEY;","}","","function mistflowAiApiUrl(): string {",' return (process.env.MISTFLOW_API_URL ?? process.env.MISTFLOW_AI_API_URL ?? DEFAULT_MISTFLOW_AI_API_URL).replace(/\\/$/, "");',"}","","// Direct BYOK mode: the customer set OPENROUTER_API_KEY. We route all","// supported modalities (chat/embed/image/TTS/STT) directly to OpenRouter","// with their key. Voice realtime is NOT part of either mode \u2014 see","// methodologies/voice-realtime.md for the bring-your-own-xAI pattern.","const useDirect = !!process.env.OPENROUTER_API_KEY;","function isManagedAvailable(): boolean { return !!runtimeKey(); }","function isBYOKAvailable(): boolean { return !!process.env.OPENROUTER_API_KEY; }","","function noConfigError(): AIConfigError {"," return new AIConfigError(",` "AI is not configured. Run 'mist_setup' for managed mode (free credits) or set OPENROUTER_API_KEY in .env.local."`," );","}","","// \u2500\u2500 Role \u2192 OpenRouter model mapping (Direct BYOK mode only) \u2500\u2500\u2500\u2500\u2500\u2500\u2500",'// In managed mode, role names ("chat", "reasoning", etc) are sent',"// straight to /v1/* and the gateway resolves them against the curated","// catalog. In direct mode, we translate roles to concrete OpenRouter",`// model IDs because OpenRouter doesn't know what "chat" means.`,"//",'// Override per call site: pass an explicit model (any "provider/model"',"// string passes through verbatim).","// Override globally per role via env var, e.g. MISTFLOW_ROLE_CHAT=openai/gpt-5.","// These are the OpenRouter model IDs as of May 2026. Update via dashboard","// (Phase 3) or hand-edit when OpenRouter renames models. Mistflow won't","// push retroactive changes \u2014 your app stays on these defaults until you","// redeploy with new ones.","const ROLE_TO_OPENROUTER: Record<string, string> = {",' chat: "anthropic/claude-sonnet-4.6",',' reasoning: "openai/gpt-5.5",',' classify: "openai/gpt-5.4-mini",'," // vision is image-IN, text-OUT \u2014 describe / classify / extract from a"," // photo. Distinct from `image` (text -> image generation). gpt-4o is"," // the cheapest strongly-multimodal OpenRouter route; bump to gpt-5 for"," // harder visual reasoning.",' vision: "openai/gpt-4o",'," // 1536-dim, matches the scaffold's EMBEDDING_DIMENSIONS so vector"," // DB columns keep working after a Direct BYOK switch with no override.",' embed: "openai/text-embedding-3-small",',' image: "openai/gpt-5.4-image-2",',' tts: "openai/gpt-4o-mini-tts-2025-12-15",',' stt: "openai/whisper-large-v3",',"};","","function resolveModel(modelOrRole: string | undefined, role: string | undefined, defaultRole: string): string {"," // Explicit model on the call wins (host AI may pick a specific model"," // when context demands it, e.g. claude-opus-4 for hard reasoning)."," const id = modelOrRole ?? role ?? defaultRole;"," // Explicit provider-prefixed ID (foo/bar) passes through in both modes.",' if (id.includes("/")) return id;'," // Env var override (dashboard-set or hand-set per project)."," const envOverride = process.env[`MISTFLOW_ROLE_${id.toUpperCase()}`];"," if (envOverride) return envOverride;"," // Direct mode: translate role \u2192 OpenRouter ID."," if (useDirect) return ROLE_TO_OPENROUTER[id] ?? id;"," // Managed mode: pass role/semantic-id straight to /v1/* and let the",' // backend resolve. "chat" is a valid model field in our /v1/* surface.'," return id;","}","","// \u2500\u2500 Unified client (managed \u2192 Mistflow /v1, direct \u2192 OpenRouter /v1) \u2500\u2500","// Both modes are OpenAI-compatible at the wire level. The only thing","// that changes between modes is baseURL + apiKey. This is the whole","// point of standardizing on /v1/* for our managed surface.","const baseURL = useDirect",' ? "https://openrouter.ai/api/v1"',' : mistflowAiApiUrl() + "/v1";',"const apiKey = useDirect",' ? (process.env.OPENROUTER_API_KEY ?? "")',' : (runtimeKey() ?? "");',"","const client = createOpenAI({"," apiKey,"," baseURL,"," ...(useDirect ? {"," headers: {",' "HTTP-Referer": process.env.NEXT_PUBLIC_APP_URL ?? "https://mistflow.app",',' "X-Title": "Mistflow App",'," },"," } : {}),","});","","// Kept as a named export for back-compat with code that imported","// `openrouter` directly. Equivalent to `client` when in direct mode.","export const openrouter = useDirect ? client : createOpenAI({",' apiKey: process.env.OPENROUTER_API_KEY ?? "",',' baseURL: "https://openrouter.ai/api/v1",',"});","","// \u2500\u2500 Raw fetch helper for /v1/* endpoints (non-chat modalities) \u2500\u2500\u2500\u2500","// We use AI SDK for chat (its primitives handle streaming, tool calls,","// finish_reason semantics, etc). For embed/image/TTS/STT we hit /v1/*","// directly \u2014 both modes accept the same OpenAI shape, with adapters","// where OpenRouter diverges (image gen and STT).","async function v1Post(path: string, body: unknown, init?: { isFormData?: boolean; body?: BodyInit }): Promise<Response> {"," if (!apiKey) throw noConfigError();"," const isForm = init?.isFormData === true;"," const headers: Record<string, string> = {",' "Authorization": "Bearer " + apiKey,'," };",' if (!isForm) headers["Content-Type"] = "application/json";'," const response = await fetch(baseURL + path, {",' method: "POST",'," headers,"," body: isForm ? (init?.body as BodyInit) : JSON.stringify(body),"," });"," if (response.status === 401) {"," throw new AIConfigError(useDirect",' ? "OpenRouter rejected your key. Check OPENROUTER_API_KEY."',' : "Runtime key rejected by gateway. Re-run mist_setup.");'," }"," if (response.status === 402) {"," throw new AICreditExhaustedError(useDirect",' ? "OpenRouter credits exhausted. Top up at https://openrouter.ai/credits"',' : "Mistflow credits exhausted. Top up at https://app.mistflow.ai/ai-gateway");'," }",' if (response.status === 429) throw new AIRateLimitError("Rate limited. Retry with backoff.");'," if (!response.ok) {",' const text = await response.text().catch(() => "");',' throw new AIModelError("AI gateway error " + response.status + ": " + redact(text));'," }"," return response;","}","","// \u2500\u2500 Public surface: the `ai` object \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500","// Always import this. The wrapper handles all dispatch (managed or","// direct BYOK). Never write process.env.OPENROUTER_API_KEY or","// MISTFLOW_RUNTIME_KEY in feature code.","","// Roles are Mistflow's stable abstraction. The host AI picks one per","// call site at scaffold time. In managed mode the gateway resolves;","// in direct mode the wrapper translates to a concrete OpenRouter ID","// (see ROLE_TO_OPENROUTER). Override via `model` only when a specific","// model is genuinely required.",'// "voice_realtime" is intentionally NOT in this type. Voice realtime',"// doesn't go through this wrapper \u2014 see methodologies/voice-realtime.md","// for the bring-your-own-xAI direct integration pattern.",'export type AiRole = "chat" | "reasoning" | "classify" | "vision" | "image" | "embed" | "tts" | "stt";',"","// OpenAI-style tool definition. Same shape AI SDK 6 and OpenAI use.","// Define a tool with `tool({ inputSchema, execute })` from the `ai`","// package and pass the resulting object verbatim \u2014 or hand-build one.","export type AiTool = {",' type: "function";'," function: { name: string; description?: string; parameters: Record<string, unknown> };","};","",'export type AiToolChoice = "auto" | "required" | "none" | {',' type: "function";'," function: { name: string };","};","","// One element from the response.tool_calls array. Pass back as a",'// "tool" role message on the next turn (see methodologies/model-guide.md).',"export type AiToolCall = {"," id: string;",' type: "function";'," function: { name: string; arguments: string };","};","","export type TextOptions = {"," messages: CoreMessage[];"," role?: AiRole;"," model?: string;"," useCase?: string;"," idempotencyKey?: string;"," tools?: AiTool[];"," toolChoice?: AiToolChoice;","};","","export type TextResult = {"," text: string;"," toolCalls?: AiToolCall[];"," finishReason?: string;","};","","export type ImageOptions = { prompt: string; size?: string; role?: AiRole; model?: string; idempotencyKey?: string };","export type EmbedOptions = { role?: AiRole; model?: string };","export type TTSOptions = { text: string; voice?: string; role?: AiRole; model?: string };","export type STTOptions = { audio: Blob | ArrayBuffer; role?: AiRole; model?: string };","","// Convert OpenAI-shaped tool definitions to the AI SDK's tool() shape.","function openAiToolsAsSdkTools(tools: AiTool[]): Record<string, { description?: string; inputSchema: Record<string, unknown> }> {"," const out: Record<string, { description?: string; inputSchema: Record<string, unknown> }> = {};"," for (const t of tools) {"," out[t.function.name] = {"," description: t.function.description,"," inputSchema: t.function.parameters,"," };"," }"," return out;","}","","// Translate AI-SDK-style errors (or fetch errors) into the typed hierarchy.","function mapAiSdkError(err: unknown): Error {"," const e = err as { status?: number; statusCode?: number; message?: string; name?: string };"," const status = e?.status ?? e?.statusCode;",' if (status === 401) return new AIConfigError(useDirect ? "OpenRouter rejected your key." : "Runtime key rejected by gateway.");',' if (status === 402) return new AICreditExhaustedError("AI credits exhausted.");',' if (status === 429) return new AIRateLimitError("Rate limited by gateway. Retry with backoff.");',' return new AIModelError("AI text generation failed: " + (e?.message ?? String(err)));',"}","","export const ai = {"," // Backwards-compatible text() returns just the string. For tool calls"," // and finish_reason, use textFull() \u2014 it returns the structured result."," async text(opts: TextOptions): Promise<string> {"," const full = await this.textFull(opts);"," return full.text;"," },",""," // Structured text result: text + toolCalls + finishReason. Used when"," // the model may invoke a tool. Routes through AI SDK's generateText"," // either against Mistflow /v1/chat/completions or OpenRouter /v1/chat/completions."," async textFull(opts: TextOptions): Promise<TextResult> {"," if (!isManagedAvailable() && !isBYOKAvailable()) throw noConfigError();",' const modelId = resolveModel(opts.model, opts.role, "chat");'," try {"," const result = await generateText({"," model: client.chat(modelId),"," messages: opts.messages,",' ...(opts.tools ? { tools: openAiToolsAsSdkTools(opts.tools) as Parameters<typeof generateText>[0]["tools"] } : {}),',' ...(opts.toolChoice ? { toolChoice: opts.toolChoice as Parameters<typeof generateText>[0]["toolChoice"] } : {}),'," });"," return {"," text: result.text,"," toolCalls: result.toolCalls?.map((tc: { toolCallId: string; toolName: string; input: unknown }) => ({"," id: tc.toolCallId,",' type: "function" as const,'," function: { name: tc.toolName, arguments: JSON.stringify(tc.input) },"," })),"," finishReason: result.finishReason,"," };"," } catch (err) {"," throw mapAiSdkError(err);"," }"," },",""," // Streaming text. Returns an AI-SDK DataStreamResponse \u2014 wire into"," // an HTTP route by returning it from the handler directly."," async streamText(opts: TextOptions): Promise<Response> {"," if (!isManagedAvailable() && !isBYOKAvailable()) throw noConfigError();",' const modelId = resolveModel(opts.model, opts.role, "chat");'," try {"," const result = streamText({"," model: client.chat(modelId),"," messages: opts.messages,",' ...(opts.tools ? { tools: openAiToolsAsSdkTools(opts.tools) as Parameters<typeof streamText>[0]["tools"] } : {}),',' ...(opts.toolChoice ? { toolChoice: opts.toolChoice as Parameters<typeof streamText>[0]["toolChoice"] } : {}),'," });"," return result.toTextStreamResponse();"," } catch (err) {"," throw mapAiSdkError(err);"," }"," },",""," // Vision: describe / classify / extract from an image. Pass a URL or"," // an in-memory buffer; the wrapper builds the multimodal CoreMessage[]"," // so callers don't need to know the SDK shape. When `schema` is set,"," // returns the parsed JSON; otherwise returns the model's text."," async vision<T = string>(opts: {"," image: string | URL | Uint8Array | ArrayBuffer | Blob;"," prompt: string;"," schema?: z.ZodType<T>;"," role?: AiRole;"," model?: string;"," useCase?: string;"," }): Promise<T> {"," const imagePart ="," opts.image instanceof URL"," ? opts.image",' : typeof opts.image === "string"'," ? new URL(opts.image)"," : opts.image;"," const messages: CoreMessage[] = [];"," if (opts.schema) {",' const jsonSchema = zodToJsonSchema(opts.schema, { target: "openApi3" });'," messages.push({",' role: "system",'," content:",' "Respond ONLY with valid JSON matching this schema: " +'," JSON.stringify(jsonSchema) +",' ". No prose, no markdown, no surrounding text.",'," });"," }"," messages.push({",' role: "user",'," content: [",' { type: "text", text: opts.prompt },',' { type: "image", image: imagePart },'," ],"," });"," const text = await this.text({"," messages,",' role: opts.role ?? "vision",'," model: opts.model,",' useCase: opts.useCase ?? "vision",'," });"," if (!opts.schema) return text as unknown as T;"," let parsed: unknown;"," try {"," parsed = JSON.parse(text);"," } catch {",' throw new AISchemaError("Vision model returned non-JSON output", redact(text));'," }"," const result = opts.schema.safeParse(parsed);"," if (!result.success) {"," throw new AISchemaError(",' "Vision output failed schema validation: " + result.error.message,'," redact(text),"," );"," }"," return result.data;"," },",""," async extractJSON<T>(opts: { prompt: string; schema: z.ZodType<T>; model?: string; useCase?: string }): Promise<T> {"," // JSON-schema-prompt + local Zod validation. Works in both modes.",' const jsonSchema = zodToJsonSchema(opts.schema, { target: "openApi3" });'," const messages: CoreMessage[] = [",' { role: "system", content: "Respond ONLY with valid JSON matching this schema: " + JSON.stringify(jsonSchema) },',' { role: "user", content: opts.prompt },'," ];",' const text = await this.text({ messages, model: opts.model, useCase: opts.useCase ?? "extract" });'," let parsed: unknown;"," try { parsed = JSON.parse(text); }",' catch { throw new AISchemaError("Model returned non-JSON output", redact(text)); }'," const result = opts.schema.safeParse(parsed);"," if (!result.success) {",' throw new AISchemaError("Model output failed schema validation: " + result.error.message, redact(text));'," }"," return result.data;"," },",""," async embed(input: string | string[], opts: EmbedOptions = {}): Promise<number[][]> {"," if (!isManagedAvailable() && !isBYOKAvailable()) throw noConfigError();",' const modelId = resolveModel(opts.model, opts.role, "embed");',' const response = await v1Post("/embeddings", { model: modelId, input });'," const json = (await response.json()) as { data: Array<{ embedding: number[]; index: number }> };"," const vectors = json.data.map((d) => d.embedding);"," // Embedding dimension is SCHEMA, not config. If your app stores"," // these in a vector DB (pgvector, etc.), the column was declared"," // with EMBEDDING_DIMENSIONS = 1536 by the scaffold. A model switch"," // (e.g. managed \u2192 Direct BYOK) can silently change the returned"," // dimension, which would explode at INSERT time deep inside your"," // code. Catch it here at the boundary with an actionable error."," if (vectors.length > 0 && vectors[0].length !== EMBEDDING_DIMENSIONS) {"," throw new AIConfigError(",' "Embedding dimension mismatch: configured EMBEDDING_DIMENSIONS=" +',' EMBEDDING_DIMENSIONS + " but model " + modelId + " returned " +',` vectors[0].length + ". Your vector DB schema won't accept these. " +`,' "Either pick a model whose dimension matches your schema, or run " +',' "a migration to re-declare the vector column with the new dim and " +',' "re-embed all rows. NEVER mix dimensions in one column."'," );"," }"," return vectors;"," },",""," async generateImage(opts: ImageOptions): Promise<Array<{ url?: string; b64_json?: string }>> {"," if (!isManagedAvailable() && !isBYOKAvailable()) throw noConfigError();",' const modelId = resolveModel(opts.model, opts.role, "image");'," if (useDirect) {"," // OpenRouter quirk: image gen routes through /chat/completions",' // with modalities: ["image"]. The image data lands in the response'," // message.images array as a data URL.",' const response = await v1Post("/chat/completions", {'," model: modelId,",' messages: [{ role: "user", content: opts.prompt }],',' modalities: ["image", "text"],'," });"," const json = (await response.json()) as {"," choices?: Array<{ message?: { images?: Array<{ image_url?: { url?: string } }> } }>;"," };"," const images = json.choices?.[0]?.message?.images ?? [];"," return images.map((img) => {",' const url = img.image_url?.url ?? "";',' if (url.startsWith("data:image")) {',' return { b64_json: url.split(",")[1] ?? "" };'," }"," return { url };"," });"," }"," // Managed: classic OpenAI shape /v1/images/generations.",' const response = await v1Post("/images/generations", {'," model: modelId,"," prompt: opts.prompt,",' size: opts.size ?? "1024x1024",',' response_format: "b64_json",'," });"," const json = (await response.json()) as { data: Array<{ url?: string; b64_json?: string }> };"," return json.data;"," },",""," async speak(opts: TTSOptions): Promise<Blob> {"," if (!isManagedAvailable() && !isBYOKAvailable()) throw noConfigError();",' const modelId = resolveModel(opts.model, opts.role, "tts");'," // Both modes: OpenAI-shape /v1/audio/speech. We always request mp3"," // because (a) it's universally playable, (b) Mistral TTS providers"," // only support mp3, and (c) it's smaller on the wire than pcm."," // OpenAI's classic default is pcm; OpenRouter's default is also pcm,"," // so explicit mp3 is required.",' const response = await v1Post("/audio/speech", {'," model: modelId,"," input: opts.text,",' voice: opts.voice ?? "alloy",',' response_format: "mp3",'," });"," return await response.blob();"," },",""," async transcribe(opts: STTOptions): Promise<{ text: string }> {"," if (!isManagedAvailable() && !isBYOKAvailable()) throw noConfigError();",' const modelId = resolveModel(opts.model, opts.role, "stt");'," const buf = opts.audio instanceof Blob ? await opts.audio.arrayBuffer() : opts.audio;"," if (useDirect) {"," // OpenRouter STT shape: { model, input_audio: { data: base64, format } }",` // \u2014 not OpenAI's multipart file upload. Default the format to "wav"`," // when we can't infer from the Blob's MIME type."," const bytes = new Uint8Array(buf);",' let binary = "";'," for (let i = 0; i < bytes.byteLength; i++) binary += String.fromCharCode(bytes[i]);",' const b64 = typeof btoa === "function" ? btoa(binary) : Buffer.from(binary, "binary").toString("base64");',' const mime = opts.audio instanceof Blob ? opts.audio.type : "";',' const format = mime.includes("wav") ? "wav"',' : mime.includes("mpeg") || mime.includes("mp3") ? "mp3"',' : mime.includes("webm") ? "webm"',' : mime.includes("m4a") || mime.includes("mp4") ? "m4a"',' : mime.includes("ogg") ? "ogg"',' : "wav";',' const response = await v1Post("/audio/transcriptions", {'," model: modelId,"," input_audio: { data: b64, format },"," });"," return (await response.json()) as { text: string };"," }"," // Managed: classic OpenAI multipart upload to /v1/audio/transcriptions."," const formData = new FormData();",' formData.append("file", new Blob([buf]), "audio.webm");',' formData.append("model", modelId);',' const response = await v1Post("/audio/transcriptions", null, { isFormData: true, body: formData });'," return (await response.json()) as { text: string };"," },",""," // Voice realtime is not part of `ai`. To build voice features, follow"," // the xAI direct pattern: bring an XAI_API_KEY, mint an ephemeral session"," // server-side, connect via WebRTC client-side. See"," // methodologies/voice-realtime.md for a full scaffold.","};","","// \u2500\u2500 Legacy named exports (back-compat) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500","// These wrap the new `ai` object so existing call sites keep working.",'export type AiCapability = "llm" | "tts" | "stt" | "image";',"",'export function openrouterModel(model = "openai/gpt-5-mini") { return openrouter(model); }',"",'export async function streamOpenRouterText(messages: CoreMessage[], model = "openai/gpt-5-mini") {'," return streamText({ model: openrouter(model), messages });","}","","// Retained for old call sites that hand-build a Mistflow-shape payload.","// New code should call `ai.text()` / `ai.streamText()` / `ai.textFull()`.","export async function mistflowManagedText(messages: CoreMessage[], options: { useCase?: string; model?: string; stream?: boolean } = {}) {"," if (options.stream) {"," return ai.streamText({ messages, model: options.model, useCase: options.useCase });"," }"," const result = await ai.textFull({ messages, model: options.model, useCase: options.useCase });"," return new Response(JSON.stringify({"," text: result.text,"," tool_calls: result.toolCalls,"," finish_reason: result.finishReason,",' }), { headers: { "Content-Type": "application/json" } });',"}","","export async function mistflowManagedImage(prompt: string, options: { model?: string; size?: string; idempotencyKey?: string } = {}) {"," const out = await ai.generateImage({ prompt, model: options.model, size: options.size, idempotencyKey: options.idempotencyKey });"," // Legacy callers expect { data: [...] } shape from the old /runtime/image endpoint."," return { data: out };","}","","// mistflowManagedRealtimeSession was removed in v3 \u2014 voice realtime is","// no longer part of this wrapper. Use the xAI direct pattern instead.","","// No-op in v3 \u2014 direct usage reporting is handled by the provider directly","// in direct BYOK mode (OpenRouter knows what the customer spent) and by","// the /v1/* surface's automatic usage events in managed mode. Kept for","// back-compat with code that still calls it.","export async function reportDirectAIUsage(_report: Record<string, unknown>): Promise<void> {"," return;","}",""].join(`
4784
+ `)),A(h,"lib/ai.ts",['import "server-only";',"",'export * from "./server/ai";',""].join(`
4785
+ `)),!0&&(d&&Ht(d,"ai-chat"))||A(h,"app/api/chat/route.ts",['import { ai, AIConfigError, AICreditExhaustedError } from "@/lib/server/ai";','import type { CoreMessage } from "ai";',"","export async function POST(req: Request) {"," const { messages } = (await req.json()) as { messages?: CoreMessage[] };"," if (!messages) {",' return Response.json({ error: "messages is required" }, { status: 400 });'," }"," try {"," return await ai.streamText({ messages });"," } catch (e) {"," if (e instanceof AIConfigError) return Response.json({ error: e.message }, { status: 503 });"," if (e instanceof AICreditExhaustedError) return Response.json({ error: e.message }, { status: 402 });",' return Response.json({ error: "AI request failed" }, { status: 500 });'," }","}",""].join(`
4786
+ `)));let Ne=Array.isArray(d?.integrations)?d.integrations.map(w=>({name:w.name,preset:w.preset,envVars:w.envVars??[]})):[],O={},ue=new Set([...ce?["RESEND_API_KEY","EMAIL_FROM"]:[],...P||ee||nn(d)?["MISTFLOW_RUNTIME_KEY","MISTFLOW_AI_RUNTIME_KEY"]:[]]);for(let w of Ne)for(let C of w.envVars??[])ue.has(C.key)||O[C.key]||(O[C.key]={description:C.description,setupUrl:C.setupUrl,...w.name?{integration:w.name}:{}});if(z&&te)for(let w of kr.envVars)O[w.key]||(O[w.key]={description:w.description,setupUrl:w.setupUrl??"https://app.nango.dev",integration:"connected-services"});let Ce=y?.requestedSubdomain||void 0,ze={name:S,methodologyVersion:x?.version??"1.0",createdAt:new Date().toISOString(),...s?{planId:s}:{},...Ce?{requestedSubdomain:Ce}:{},plan:Array.isArray(d?.steps)?{...d,steps:d.steps.map(w=>({number:w.number,name:w.name??w.title,description:w.description,entities:w.entities,pages:w.pages,features:w.features,status:"pending"}))}:d,dbProvider:K,env:{managed:{...!L&&te?{DATABASE_URL:{description:"Postgres connection URL",scope:"production"}}:L?{}:{TURSO_URL:{description:"Turso database URL",scope:"production"},TURSO_AUTH_TOKEN:{description:"Turso database auth token",scope:"production"}},...H?{}:{AUTH_SECRET:{description:"Auth encryption secret",scope:"production"}},...ne?{STRIPE_SECRET_KEY:{description:"Stripe secret key",scope:"production"},STRIPE_WEBHOOK_SECRET:{description:"Stripe webhook signing secret",scope:"production"},NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY:{description:"Stripe publishable key",scope:"production"}}:{},...ce?{RESEND_API_KEY:{description:"Resend API key \u2014 managed by Mistflow by default, override with your own key from resend.com",scope:"production"},EMAIL_FROM:{description:"Sender email address \u2014 managed by Mistflow by default",scope:"production"}}:{},...P||ee||nn(d)?{MISTFLOW_RUNTIME_KEY:{description:"Mistflow runtime key \u2014 authenticates AI gateway, file storage, and other managed features",scope:"production"}}:{}},...Object.keys(O).length>0?{required:O}:{}},authModel:J??"email",roles:d?.roles??null,navStyle:d?.navStyle??"sidebar",multiTenant:d?.multiTenant??!1,hasAdmin:q,hasResend:ce,hasStorage:ee,hasAI:P,deploy:null};A(h,"mistflow.json",JSON.stringify(ze,null,2)),on(h);let lt=Eu(32).toString("hex"),We=ne?`
5081
4787
  # Stripe
5082
4788
  STRIPE_SECRET_KEY=
5083
4789
  STRIPE_WEBHOOK_SECRET=
5084
4790
  NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=
5085
- `:"",He=J?`
4791
+ `:"",De=ce?`
5086
4792
  # Email (Resend)
5087
4793
  RESEND_API_KEY=
5088
4794
  EMAIL_FROM=onboarding@resend.dev
5089
- `:"",et="",De=le||V?`
4795
+ `:"",g="",T=P||ee?`
5090
4796
  # Mistflow Cloud (server-only; never prefix with NEXT_PUBLIC_).
5091
4797
  # One runtime key authenticates every managed feature: AI gateway, file storage,
5092
4798
  # and future cloud-powered features. Auto-provisioned by \`mist_init\`.
@@ -5096,44 +4802,44 @@ MISTFLOW_API_URL=https://api.mistflow.ai
5096
4802
  MISTFLOW_AI_API_URL=https://api.mistflow.ai
5097
4803
  # BYOK fallback for AI only (used when MISTFLOW_RUNTIME_KEY is unset).
5098
4804
  OPENROUTER_API_KEY=
5099
- `:"",g=w?"":`
5100
- AUTH_SECRET=${ce}`,y=w?"":`
5101
- AUTH_SECRET=your-secret-here`,k=v?"# No database required for this public app":`# Local dev: PGlite is used automatically (zero-install embedded Postgres)
4805
+ `:"",R=H?"":`
4806
+ AUTH_SECRET=${lt}`,N=H?"":`
4807
+ AUTH_SECRET=your-secret-here`,V=L?"# No database required for this public app":`# Local dev: PGlite is used automatically (zero-install embedded Postgres)
5102
4808
  # Set DATABASE_URL only for production or to use a remote Postgres
5103
- # DATABASE_URL=postgresql://postgres:postgres@localhost:5432/devdb`,N=v?"# No database required for this public app":`# Local dev: PGlite is used automatically (zero-install embedded Postgres)
4809
+ # DATABASE_URL=postgresql://postgres:postgres@localhost:5432/devdb`,Y=L?"# No database required for this public app":`# Local dev: PGlite is used automatically (zero-install embedded Postgres)
5104
4810
  # Set DATABASE_URL only for production or to use a remote Postgres
5105
- # DATABASE_URL=postgresql://postgres:postgres@localhost:5432/devdb`,E=w?"":`
4811
+ # DATABASE_URL=postgresql://postgres:postgres@localhost:5432/devdb`,oe=H?"":`
5106
4812
  # Mistflow-hosted Google sign-in (zero Google Cloud setup)
5107
4813
  MISTFLOW_OAUTH_PROXY_URL=http://localhost:9100
5108
- `,K=Q?`
4814
+ `,ye=re?`
5109
4815
  # MCP endpoint (lets external AIs call this app's tools)
5110
4816
  MCP_AUTH_MODE=dev
5111
4817
  ENVIRONMENT=development
5112
- `:"",oe=Q?`
4818
+ `:"",Q=re?`
5113
4819
  # MCP endpoint (lets external AIs call this app's tools).
5114
4820
  # Leave MCP_AUTH_MODE empty in production. Set ENVIRONMENT=production on deploy.
5115
4821
  MCP_AUTH_MODE=
5116
4822
  ENVIRONMENT=production
5117
- `:"",ae=q?`
4823
+ `:"",X=z?`
5118
4824
  # Connected services via Nango (BYO key)
5119
4825
  # Sign up at https://app.nango.dev, enable your providers, paste these.
5120
4826
  NANGO_SECRET_KEY=
5121
4827
  NANGO_WEBHOOK_SECRET=
5122
- `:"",be=q?`
4828
+ `:"",_e=z?`
5123
4829
  # Connected services via Nango (BYO key) \u2014 sign up at https://app.nango.dev
5124
4830
  NANGO_SECRET_KEY=
5125
4831
  NANGO_WEBHOOK_SECRET=
5126
- `:"";M(h,".env.local",`${k}${g}
5127
- ${E}${Ae}${He}${et}${De}${K}${ae}`),M(h,".env.example",`${N}${y}
5128
- ${Ae}${He}${et}${De}${oe}${be}`);let I=[],Y=(b,_)=>{I.push({phase:b,message:_})},ge=(b,_)=>{let $=I.find(Ue=>Ue.phase===b&&!Ue.durationMs);$&&($.durationMs=_)};if(e){let b=Ye(e.server,e.progressToken,()=>I[I.length-1]?.message??"Setting up project...");e.cleanup=()=>b.stop()}let ee=Re,we=D?.pickedDirection??D?.designDirection??void 0,Ee=D?.imageryBrief??we?.imagery??void 0,fe=D?.designConversationId??void 0,P=D?{name:D.name,summary:D.summary,audienceType:D.audienceType,authModel:D.authModel,dbProvider:F,dataModel:D.dataModel,design:D.design,publicLanding:D.publicLanding,...typeof D.designMd=="string"&&D.designMd?{designMd:D.designMd}:{},...D.designConversationId?{designConversationId:D.designConversationId}:{},...Array.isArray(D.features)?{features:D.features}:{},...D.integrations?{integrations:D.integrations}:{},...D.hasAI===!0?{hasAI:!0}:{},...D.hasEmail===!0?{hasEmail:!0}:{},...D.hasStorage===!0?{hasStorage:!0}:{}}:void 0,ue,ve;Y("register","Registering project on Mistflow...");let Ht=Date.now();try{let b=await Ot(U,{template:void 0,dbProvider:F,requestedSubdomain:ee,pickedDirection:we,imageryBrief:Ee,planContext:P,designConversationId:fe,sessionId:i,scaffoldFeatures:["google_byo"]});if(ue=b.id,b.design_md&&(a=b.design_md),b.plan_md&&(c=b.plan_md),Array.isArray(b.design_warnings)&&b.design_warnings.length>0){l=b.design_warnings;for(let re of b.design_warnings)console.error(`[mist_init] design warning [${re.code}]: ${re.detail}`)}if(b.layout_spec&&u&&typeof u=="object"&&(u.layoutSpec=b.layout_spec),b.picker_render_html&&typeof b.picker_render_html=="string")try{let re=pe(h,".mistflow");rn(re,{recursive:!0}),dt(pe(re,"picker-render.html"),b.picker_render_html),console.error(`[mist_init] picker render written to .mistflow/picker-render.html (${b.picker_render_html.length} chars)`)}catch(re){let se=re instanceof Error?re.message:String(re);console.error(`[mist_init] picker render write failed: ${se}`)}if(Ee)try{let re=await zo(ue),se=pe(h,"public","images");rn(se,{recursive:!0});let S=0,H=0;for(let[Ce,qe]of Object.entries(re.slots))if(qe.status==="done"&&qe.signed_url)try{let Fe=await Wo(qe.signed_url);dt(pe(se,`${Ce}.png`),Fe),S++}catch(Fe){let gl=Fe instanceof Error?Fe.message:String(Fe);console.error(`[mist_init] imagery: ${Ce} download failed: ${gl}`)}else(qe.status==="queued"||qe.status==="running")&&H++;(S>0||H>0)&&console.error(`[mist_init] imagery: ${S} ready, ${H} still rendering`)}catch(re){let se=re instanceof Error?re.message:String(re);console.error(`[mist_init] imagery manifest fetch failed: ${se}`)}let _=pe(h,"mistflow.json"),$=JSON.parse(sn(_,"utf-8"));if($.projectId=ue,b.layout_spec&&$.plan&&typeof $.plan=="object"&&($.plan.layoutSpec=b.layout_spec),dt(_,JSON.stringify($,null,2)),_n(h,Cn(ue,U)),b.managed_env&&Object.keys(b.managed_env).length>0){let re=pe(h,".env.local"),se=Be(re)?sn(re,"utf-8"):"";for(let[S,H]of Object.entries(b.managed_env)){let Ce=new RegExp(`^${S}=.*$`,"m");Ce.test(se)?se=se.replace(Ce,`${S}=${H}`):se+=`
5129
- ${S}=${H}`}dt(re,se)}let hn=b.runtimeKey;if(hn?.rawKey){let re=pe(h,".env.local"),se=Be(re)?sn(re,"utf-8"):"",S=(H,Ce)=>{let qe=new RegExp(`^${H}=\\s*$`,"m"),Fe=new RegExp(`^${H}=`,"m");qe.test(se)?se=se.replace(qe,`${H}=${Ce}`):Fe.test(se)||(se+=`
5130
- ${H}=${Ce}`)};S("MISTFLOW_RUNTIME_KEY",hn.rawKey),S("MISTFLOW_AI_RUNTIME_KEY",hn.rawKey),dt(re,se)}if(!w&&b.mistflow_app_id){let re=pe(h,".env.local"),se=Be(re)?sn(re,"utf-8"):"";/^MISTFLOW_OAUTH_PROXY_URL=/m.test(se)||(se+=`
4832
+ `:"";A(h,".env.local",`${V}${R}
4833
+ ${oe}${We}${De}${g}${T}${ye}${X}`),A(h,".env.example",`${Y}${N}
4834
+ ${We}${De}${g}${T}${Q}${_e}`);let D=[],pe=(w,C)=>{D.push({phase:w,message:C})},be=(w,C)=>{let U=D.find(Ie=>Ie.phase===w&&!Ie.durationMs);U&&(U.durationMs=C)};if(e){let w=tt(e.server,e.progressToken,()=>D[D.length-1]?.message??"Setting up project...");e.cleanup=()=>w.stop()}let de=Ce,le=v,Le=y?.imageryBrief??le?.imagery??void 0,M=y?.designConversationId??void 0,Ye=y?{name:y.name,summary:y.summary,audienceType:y.audienceType,authModel:y.authModel,dbProvider:K,dataModel:y.dataModel,design:y.design,publicLanding:y.publicLanding,...Object.keys(O).length>0?{env:{required:O}}:{},...typeof y.designMd=="string"&&y.designMd?{designMd:y.designMd}:{},...y.designConversationId?{designConversationId:y.designConversationId}:{},...Array.isArray(y.features)?{features:y.features}:{},...y.integrations?{integrations:y.integrations}:{},...y.hasAI===!0?{hasAI:!0}:{},...y.hasEmail===!0?{hasEmail:!0}:{},...y.hasStorage===!0?{hasStorage:!0}:{}}:void 0,$,Re;pe("register","Registering project on Mistflow...");let Pe=Date.now();try{let w=await Mt(S,{template:void 0,dbProvider:K,requestedSubdomain:de,pickedDirection:le,imageryBrief:Le,planContext:Ye,designConversationId:M,sessionId:i,scaffoldFeatures:["google_byo"]});$=w.id,w.design_md&&(a=w.design_md),w.plan_md&&(c=w.plan_md);let C=a??d?.designMd??E;if(C?(A(h,"DESIGN.md",C),A(h,"app/globals.css",Yi(I,C))):le&&l.push({code:"design_md_missing_after_pick",detail:"The user picked a design direction, but neither backend DESIGN.md nor the deterministic fallback was available. Landing implementation should stop instead of improvising."}),Array.isArray(w.design_warnings)&&w.design_warnings.length>0){l=w.design_warnings;for(let ae of w.design_warnings)console.error(`[mist_init] design warning [${ae.code}]: ${ae.detail}`)}if(w.layout_spec&&d&&typeof d=="object"&&(d.layoutSpec=w.layout_spec),w.picker_render_html&&typeof w.picker_render_html=="string")try{let ae=me(h,".mistflow");an(ae,{recursive:!0}),gt(me(ae,"picker-render.html"),w.picker_render_html),console.error(`[mist_init] picker render written to .mistflow/picker-render.html (${w.picker_render_html.length} chars)`)}catch(ae){let k=ae instanceof Error?ae.message:String(ae);console.error(`[mist_init] picker render write failed: ${k}`)}if(Le)try{let ae=await Wr($),k=me(h,"public","images");an(k,{recursive:!0});let W=0,ge=0;for(let[Je,Fe]of Object.entries(ae.slots))if(Fe.status==="done"&&Fe.signed_url)try{let qe=await Gr(Fe.signed_url);gt(me(k,`${Je}.png`),qe),W++}catch(qe){let Pl=qe instanceof Error?qe.message:String(qe);console.error(`[mist_init] imagery: ${Je} download failed: ${Pl}`)}else(Fe.status==="queued"||Fe.status==="running")&&ge++;(W>0||ge>0)&&console.error(`[mist_init] imagery: ${W} ready, ${ge} still rendering`)}catch(ae){let k=ae instanceof Error?ae.message:String(ae);console.error(`[mist_init] imagery manifest fetch failed: ${k}`)}let U=me(h,"mistflow.json"),Ie=JSON.parse(ln(U,"utf-8"));if(Ie.projectId=$,w.layout_spec&&Ie.plan&&typeof Ie.plan=="object"&&(Ie.plan.layoutSpec=w.layout_spec),gt(U,JSON.stringify(Ie,null,2)),Cn(h,In($,S)),w.managed_env&&Object.keys(w.managed_env).length>0){let ae=me(h,".env.local"),k=Be(ae)?ln(ae,"utf-8"):"";for(let[W,ge]of Object.entries(w.managed_env)){let Je=new RegExp(`^${W}=.*$`,"m");Je.test(k)?k=k.replace(Je,`${W}=${ge}`):k+=`
4835
+ ${W}=${ge}`}gt(ae,k)}let gn=w.runtimeKey;if(gn?.rawKey){let ae=me(h,".env.local"),k=Be(ae)?ln(ae,"utf-8"):"",W=(ge,Je)=>{let Fe=new RegExp(`^${ge}=\\s*$`,"m"),qe=new RegExp(`^${ge}=`,"m");Fe.test(k)?k=k.replace(Fe,`${ge}=${Je}`):qe.test(k)||(k+=`
4836
+ ${ge}=${Je}`)};W("MISTFLOW_RUNTIME_KEY",gn.rawKey),W("MISTFLOW_AI_RUNTIME_KEY",gn.rawKey),gt(ae,k)}if(!H&&w.mistflow_app_id){let ae=me(h,".env.local"),k=Be(ae)?ln(ae,"utf-8"):"";/^MISTFLOW_OAUTH_PROXY_URL=/m.test(k)||(k+=`
5131
4837
  # Mistflow-hosted Google sign-in (zero Google Cloud setup)
5132
4838
  MISTFLOW_OAUTH_PROXY_URL=http://localhost:9100
5133
- `),/^MISTFLOW_APP_ID=/m.test(se)||(se+=`
4839
+ `),/^MISTFLOW_APP_ID=/m.test(k)||(k+=`
5134
4840
  # Mistflow-hosted Google sign-in (zero GCP setup)
5135
- MISTFLOW_APP_ID=${b.mistflow_app_id}
5136
- `),dt(re,se)}try{let{getBaseUrl:re,getAuthHeaders:se}=await Promise.resolve().then(()=>(Ie(),_s)),S=se(),H=u?.features,Ce=u?.steps,qe={};Array.isArray(H)&&H.length>0&&(qe.features=H.map(Fe=>Fe.name)),u&&(qe.plan=u),Array.isArray(Ce)&&Ce.length>0&&(qe.provenance=Ce.map(Fe=>({feature:Fe.name??Fe.title??`Step ${Fe.number??"?"}`,user_intent:(Fe.description??"").slice(0,500),decisions:"Seeded from plan at init",tradeoffs:"",files_affected:[]}))),Object.keys(qe).length>0&&await fetch(`${re()}/api/projects/${encodeURIComponent(ue)}/state`,{method:"PUT",headers:{...S,"Content-Type":"application/json"},body:JSON.stringify(qe)})}catch{}I[I.length-1].message=`Registered as ${ue.slice(0,8)}`}catch(b){let _=b instanceof Error?b.message:String(b);console.error("Could not register project on backend:",_),ve=`Project created locally but NOT registered on Mistflow servers (${_}). Deploy will auto-register it.`,I[I.length-1].message="Registration skipped (offline \u2014 deploy will retry)"}ge("register",Date.now()-Ht);let z=null;if(yo(u,h)){Y("agent-ui","Installing agent UI components...");let b=Date.now();try{let _=bo(h);z={presetVersion:ct.version,installedAt:new Date().toISOString(),files:_.installed};let $=_.depsAdded.length?`, +${_.depsAdded.length} deps`:_.depsSkipped.length?" (deps already pinned)":"";I[I.length-1].message=`${_.installed.length} agent UI files written${$}`}catch(_){let $=_ instanceof Error?_.message:String(_);console.error("agent-ui materialization failed:",$),I[I.length-1].message=`agent-ui skipped: ${$}`,z=null}ge("agent-ui",Date.now()-b)}let Se=null;if(Dn(u)){Y("ai-chat","Installing ai-chat addon (chat surface)...");let b=Date.now();try{let _=Array.isArray(u?.integrations)?u.integrations:[],$=await bi(h,{integrations:_,mcpEnabled:Q&&j});Se={files:$.installed.length,deps:$.depsAdded.length,crons:$.cronAdded.length,runPython:$.runPythonWired};let Ue=[`${$.installed.length} files`,$.depsAdded.length?`+${$.depsAdded.length} deps`:null,$.cronAdded.length?`+${$.cronAdded.length} cron${$.cronAdded.length>1?"s":""}`:null,$.runPythonWired?"run-python wired":null].filter(Boolean).join(", ");I[I.length-1].message=`ai-chat: ${Ue}`}catch(_){ge("ai-chat",Date.now()-b);let $=_ instanceof Error?_.message:String(_);throw new Error(`ai-chat addon install failed for agent app: ${$}. Without the addon, the scaffold has no chat route. Retry init, or remove "ai-chat" from plan.integrations to scaffold a basic Next.js app instead.`)}ge("ai-chat",Date.now()-b)}Y("git","Initializing git repository...");let _e=Date.now();try{let b=Cu(h);await b.init(),await b.add("."),await b.commit("Initial Mistflow project setup"),I[I.length-1].message="Git repository initialized"}catch{console.error("Git initialization failed, continuing without git."),I[I.length-1].message="Git init skipped"}ge("git",Date.now()-_e);let mt=I.reduce((b,_)=>b+(_.durationMs??0),0),Pe={projectPath:h,projectId:ue,status:"awaiting_install"};z&&(Pe.agentUi={version:z.presetVersion,filesWritten:z.files.length,postInstallNote:ct.postInstallNote}),Se&&(Pe.aiChat={filesWritten:Se.files,depsAdded:Se.deps,cronsRegistered:Se.crons,runPythonEnabled:Se.runPython,postInstallNote:"Chat agent ready at /chat. Add tools by appending ToolManifest entries to lib/ai/tools/manifest.ts."}),ee&&(Pe.requestedSubdomain=ee,Pe.productionUrl=`https://${ee}.mistflow.app`);let st=I.map(b=>{let _=b.durationMs?` (${(b.durationMs/1e3).toFixed(1)}s)`:"";return`${b.message}${_}`});Pe.progress=st,Pe.totalSetupTime=`${(mt/1e3).toFixed(1)}s`;let me=[];ue||me.push("Project was not registered with Mistflow (backend error during create). mist_deploy will retry registration automatically on the first deploy."),ve&&(Pe.registrationWarning=ve),me.length>0&&(Pe.warnings=me),l.length>0&&(Pe.designPipelineWarnings=l,Pe.designPipelineWarningsHostAction=`Tell the user explicitly: "The design generation pipeline had a partial failure \u2014 your scaffolded landing may not match the picker preview exactly. The build will continue with fallback design tokens. If the deployed landing looks wrong, run mist_plan({ description: '<original description>' }) again to get a fresh picker." Then continue with the install step. Do not silently proceed.`);let Yr=`${ee?`TELL THE USER: "Your app's URL is reserved: https://${ee}.mistflow.app \u2014 it goes live when you deploy. While it's being built, you'll get a local preview to watch your app come together in real time." `:""}NEXT: Call mist_install({ projectPath: "${h}" }). This is fire-and-poll \u2014 the first call returns a jobId and status: "running"; re-call with the same jobId every ~15-30s until status is "complete". Typical duration 30-90s. Do NOT ask the user for permission \u2014 install is a required follow-up to init, not a decision point. After install completes, call mist_implement({ projectPath: "${h}" }) to build the first plan step.`;return Pe.nextAction=ue?Yr:`${Yr} NOTE: The project could not be registered with the backend during init (${ve??"see registrationWarning"}). mist_deploy will retry the registration automatically on the first deploy \u2014 no manual recovery needed.`,d(JSON.stringify(Pe))}catch(R){try{wu(h,{recursive:!0,force:!0})}catch{}throw R}}var Hi={name:"mist_init",description:"Initialize a new Next.js project using the Mistflow stack. Downloads templates from the Mistflow registry, installs dependencies, sets up auth, and initializes git. Use this after mist_plan to create the project.",inputSchema:Pu,handler:ip};import{z as qn}from"zod";import{existsSync as It,readFileSync as Kr,writeFileSync as Ro,mkdirSync as Ma}from"fs";import{join as Qe,resolve as Vp,dirname as Yp}from"path";Ie();import{createConnection as Qp}from"net";var zi=`# Consumer Warm Archetype
4841
+ MISTFLOW_APP_ID=${w.mistflow_app_id}
4842
+ `),gt(ae,k)}try{let{getBaseUrl:ae,getAuthHeaders:k}=await Promise.resolve().then(()=>(ke(),Ms)),W=k(),ge=d?.features,Je=d?.steps,Fe={};Array.isArray(ge)&&ge.length>0&&(Fe.features=ge.map(qe=>qe.name)),d&&(Fe.plan=d),Array.isArray(Je)&&Je.length>0&&(Fe.provenance=Je.map(qe=>({feature:qe.name??qe.title??`Step ${qe.number??"?"}`,user_intent:(qe.description??"").slice(0,500),decisions:"Seeded from plan at init",tradeoffs:"",files_affected:[]}))),Object.keys(Fe).length>0&&await fetch(`${ae()}/api/projects/${encodeURIComponent($)}/state`,{method:"PUT",headers:{...W,"Content-Type":"application/json"},body:JSON.stringify(Fe)})}catch{}D[D.length-1].message=`Registered as ${$.slice(0,8)}`}catch(w){let C=w instanceof Error?w.message:String(w);console.error("Could not register project on backend:",C),Re=`Project created locally but NOT registered on Mistflow servers (${C}). Deploy will auto-register it.`,D[D.length-1].message="Registration skipped (offline \u2014 deploy will retry)"}be("register",Date.now()-Pe);let Ae=null;if(yr(d,h)){pe("agent-ui","Installing agent UI components...");let w=Date.now();try{let C=br(h);Ae={presetVersion:ht.version,installedAt:new Date().toISOString(),files:C.installed};let U=C.depsAdded.length?`, +${C.depsAdded.length} deps`:C.depsSkipped.length?" (deps already pinned)":"";D[D.length-1].message=`${C.installed.length} agent UI files written${U}`}catch(C){let U=C instanceof Error?C.message:String(C);console.error("agent-ui materialization failed:",U),D[D.length-1].message=`agent-ui skipped: ${U}`,Ae=null}be("agent-ui",Date.now()-w)}let ve=null;if(nn(d)){pe("ai-chat","Installing ai-chat addon (chat surface)...");let w=Date.now();try{let C=Array.isArray(d?.integrations)?d.integrations:[],U=await Ri(h,{integrations:C,mcpEnabled:re&&te});ve={files:U.installed.length,deps:U.depsAdded.length,crons:U.cronAdded.length,runPython:U.runPythonWired};let Ie=[`${U.installed.length} files`,U.depsAdded.length?`+${U.depsAdded.length} deps`:null,U.cronAdded.length?`+${U.cronAdded.length} cron${U.cronAdded.length>1?"s":""}`:null,U.runPythonWired?"run-python wired":null].filter(Boolean).join(", ");D[D.length-1].message=`ai-chat: ${Ie}`}catch(C){be("ai-chat",Date.now()-w);let U=C instanceof Error?C.message:String(C);throw new Error(`ai-chat addon install failed for agent app: ${U}. Without the addon, the scaffold has no chat route. Retry init, or remove "ai-chat" from plan.integrations to scaffold a basic Next.js app instead.`)}be("ai-chat",Date.now()-w)}pe("git","Initializing git repository...");let ct=Date.now();try{let w=Ou(h);await w.init(),await w.add("."),await w.commit("Initial Mistflow project setup"),D[D.length-1].message="Git repository initialized"}catch{console.error("Git initialization failed, continuing without git."),D[D.length-1].message="Git init skipped"}be("git",Date.now()-ct);let he=D.reduce((w,C)=>w+(C.durationMs??0),0),Ge={projectPath:h,projectId:$,status:"awaiting_install"};Ae&&(Ge.agentUi={version:Ae.presetVersion,filesWritten:Ae.files.length,postInstallNote:ht.postInstallNote}),ve&&(Ge.aiChat={filesWritten:ve.files,depsAdded:ve.deps,cronsRegistered:ve.crons,runPythonEnabled:ve.runPython,postInstallNote:"Chat agent ready at /chat. Add tools by appending ToolManifest entries to lib/ai/tools/manifest.ts."}),de&&(Ge.requestedSubdomain=de,Ge.productionUrl=`https://${de}.mistflow.app`);let Il=D.map(w=>{let C=w.durationMs?` (${(w.durationMs/1e3).toFixed(1)}s)`:"";return`${w.message}${C}`});Ge.progress=Il,Ge.totalSetupTime=`${(he/1e3).toFixed(1)}s`;let Mr=[];$||Mr.push("Project was not registered with Mistflow (backend error during create). mist_deploy will retry registration automatically on the first deploy."),Re&&(Ge.registrationWarning=Re),Mr.length>0&&(Ge.warnings=Mr),l.length>0&&(Ge.designPipelineWarnings=l,Ge.designPipelineWarningsHostAction=`Tell the user explicitly: "The design generation pipeline had a partial failure \u2014 your scaffolded landing may not match the picker preview exactly. The build will continue with fallback design tokens. If the deployed landing looks wrong, run mist_plan({ description: '<original description>' }) again to get a fresh picker." Then continue with the install step. Do not silently proceed.`);let ts=`${de?`TELL THE USER: "Your app's URL is reserved: https://${de}.mistflow.app \u2014 it goes live when you deploy. While it's being built, you'll get a local preview to watch your app come together in real time." `:""}NEXT: Call mist_install({ projectPath: "${h}" }). This is fire-and-poll \u2014 the first call returns a jobId and status: "running"; re-call with the same jobId every ~15-30s until status is "complete". Typical duration 30-90s. Do NOT ask the user for permission \u2014 install is a required follow-up to init, not a decision point. After install completes, call mist_implement({ projectPath: "${h}" }) to build the first plan step.`;return Ge.nextAction=$?ts:`${ts} NOTE: The project could not be registered with the backend during init (${Re??"see registrationWarning"}). mist_deploy will retry the registration automatically on the first deploy \u2014 no manual recovery needed.`,u(JSON.stringify(Ge))}catch(x){try{Iu(h,{recursive:!0,force:!0})}catch{}throw x}}var ta={name:"mist_init",description:"Initialize a new Next.js project using the Mistflow stack. Downloads templates from the Mistflow registry, installs dependencies, sets up auth, and initializes git. Use this after mist_plan to create the project.",inputSchema:Du,handler:gp};import{z as qn}from"zod";import{existsSync as Rt,readFileSync as Yo,writeFileSync as Rr,mkdirSync as Ga,renameSync as om}from"fs";import{join as nt,resolve as sm,dirname as im}from"path";ke();import{createConnection as am}from"net";var na=`# Consumer Warm Archetype
5137
4843
 
5138
4844
  Component-level design guidance for personal, lifestyle, and wellness apps. Habits, journals, recipes, mood trackers, meditation, daily routines, personal finance.
5139
4845
 
@@ -5300,7 +5006,7 @@ All colors from project CSS custom properties. Key guidance for consumer-warm:
5300
5006
  - Small touch targets. Phone-first means \`h-12\` minimum.
5301
5007
  - Empty states that just say "No data." Be encouraging.
5302
5008
  - Dark theme as default. Consumer warm apps default to light.
5303
- `;var Wi=`# Consumer Bold Archetype
5009
+ `;var ra=`# Consumer Bold Archetype
5304
5010
 
5305
5011
  Component-level design guidance for energetic, achievement-driven consumer apps. Fitness, workouts, sports, gaming, social platforms, competitive tracking.
5306
5012
 
@@ -5471,7 +5177,7 @@ Social/competitive apps need an activity feed:
5471
5177
  - Card-only layouts with no hierarchy. Use hero cards + supporting cards.
5472
5178
  - Missing achievement/progress systems. The gamification IS the product.
5473
5179
  - Light-touch buttons. CTAs should be unmissable.
5474
- `;var Gi=`# Professional Clean Archetype
5180
+ `;var oa=`# Professional Clean Archetype
5475
5181
 
5476
5182
  Component-level design guidance for service and appointment-based apps. Booking systems, clinics, salons, real estate, consulting, restaurants, event management.
5477
5183
 
@@ -5683,7 +5389,7 @@ h-10 w-10 rounded-full bg-[deterministic-color] flex items-center justify-center
5683
5389
  - Missing status workflows. Every booking/appointment needs a clear state machine.
5684
5390
  - Dark theme as default. Clients associate light themes with professionalism.
5685
5391
  - Emoji in the UI. Professional context.
5686
- `;var Ki=`# Education Structured Archetype
5392
+ `;var sa=`# Education Structured Archetype
5687
5393
 
5688
5394
  Component-level design guidance for learning, education, and knowledge apps. Course platforms, quiz apps, flashcards, LMS, student portals, tutorial sites, documentation tools.
5689
5395
 
@@ -5883,7 +5589,7 @@ border-2 border-destructive bg-destructive/5 rounded-xl p-4
5883
5589
  - Hero metrics. "2,847 XP" is gaming, not learning. Use "4 of 12 lessons" instead.
5884
5590
  - Long lesson pages with no progress indicator. Users need to know where they are.
5885
5591
  - Multiple competing elements per view. One thing at a time. One question at a time.
5886
- `;var Ji=`# Marketplace Browse Archetype
5592
+ `;var ia=`# Marketplace Browse Archetype
5887
5593
 
5888
5594
  Component-level design guidance for browsing, listing, and shopping apps. Marketplaces, directories, shops, classifieds, rental platforms, food delivery, product catalogs.
5889
5595
 
@@ -6107,7 +5813,7 @@ border-b border-border/30 py-4
6107
5813
  - Small product images. The image is the primary decision-making element.
6108
5814
  - No empty state for zero search results. "No results for 'xyz'. Try broader terms."
6109
5815
  - Desktop-only filter sidebar without mobile equivalent. Use a slide-out sheet on mobile.
6110
- `;var Vi=`# SaaS Analytical Archetype
5816
+ `;var aa=`# SaaS Analytical Archetype
6111
5817
 
6112
5818
  Component-level design guidance for B2B SaaS tools, CRMs, analytics dashboards, admin panels, ops tooling, and team productivity apps. This is the biggest category \u2014 most apps that don't fit consumer, marketplace, or booking archetypes land here.
6113
5819
 
@@ -6288,7 +5994,7 @@ The most recognizable B2B SaaS landing pattern.
6288
5994
  - **Inter as the only font.** Pair it with a distinctive heading font or swap it entirely.
6289
5995
  - **Centered text + centered image below.** The most overused AI hero. Use split or offset layouts.
6290
5996
  - **Animation on every element.** Efficient motion only: hero entrance, card reveals on scroll, number counters. Nothing else.
6291
- `;var Yi=`# Content Editorial Archetype
5997
+ `;var la=`# Content Editorial Archetype
6292
5998
 
6293
5999
  Component-level design guidance for blogs, newsletters, magazines, publications, documentation sites, knowledge bases, and wiki-style content tools. Substack, The Verge, Medium, Linear's changelog, Stripe's docs are the reference bar.
6294
6000
 
@@ -6495,7 +6201,7 @@ Below the hero: 3 featured pieces laid out as a horizontal strip with **oversize
6495
6201
  - **Dark mode as default for a reading tool.** Offer it as a toggle, not the default.
6496
6202
  - **Flashy motion while reading.** Scroll hijacking, parallax on body text, animated backgrounds behind paragraphs \u2014 all break reading flow.
6497
6203
  - **More than 2 fonts.** Heading + body is enough. Adding a third font for captions or metadata dilutes the identity.
6498
- `;var Qi=`# Devtool Technical Archetype
6204
+ `;var ca=`# Devtool Technical Archetype
6499
6205
 
6500
6206
  Component-level design guidance for developer tools, APIs, CLIs, SDKs, infrastructure platforms, monitoring/observability, AI/ML platforms, deployment targets, and CI/CD tooling. Vercel, Linear, Supabase, Sentry, Warp, Raycast, Cursor, Neon, Anthropic Console are the reference bar.
6501
6207
 
@@ -6684,7 +6390,7 @@ Numbers styled as terminal output: \`> 47,293 deploys this week\`, \`> 99.98% up
6684
6390
  - **Marketing testimonials.** Replace with GitHub star count, StackOverflow mentions, a few short founder-quoted specifics ("Cut our deploy time from 12min to 18sec" from a named engineer at a known company).
6685
6391
  - **Feature cards with rocket / lightning / puzzle icons.** Use technical icons (terminal, code, database, network) or skip icons entirely.
6686
6392
  - **Motion on code blocks** while the user is trying to read them. Type out ONCE on load, then leave them alone.
6687
- `;var Xi=`# Creative Showcase Archetype
6393
+ `;var da=`# Creative Showcase Archetype
6688
6394
 
6689
6395
  Component-level design guidance for portfolios, design agencies, creative studios, photographers, artists, architects, filmmakers, and any brand whose primary selling point is "look at the work we've made." Apple, Dribbble, Behance, Pentagram, IDEO are the reference bar.
6690
6396
 
@@ -6901,7 +6607,7 @@ Premium agency touch: on first load, a full-screen loading component shows the s
6901
6607
  - **Scroll hijacking through the whole site.** Use sparingly \u2014 one or two scroll-pinned sections maximum.
6902
6608
  - **Mystery-meat navigation.** Clever is bad here. "Work / About / Contact" is fine. Don't make visitors guess.
6903
6609
  - **No contact info.** The primary purpose of this site is to generate inquiries. Make it stupid-easy to contact you.
6904
- `;var Zi=`# Finance Clarity Archetype
6610
+ `;var ua=`# Finance Clarity Archetype
6905
6611
 
6906
6612
  Component-level design guidance for fintech apps, invoicing tools, expense trackers, budget apps, payroll, accounting software, banking dashboards, and any product where **money is the primary data type**. Stripe, Wise, Revolut, Mercury, Brex, QuickBooks, Ramp are the reference bar.
6907
6613
 
@@ -7120,7 +6826,7 @@ Finance products often replace something worse (a clunky bank, a spreadsheet, an
7120
6826
  - **Purple-to-blue gradients in the hero.** Fintech SaaS default. Use a brand color instead \u2014 or no gradient.
7121
6827
  - **Dark mode as default for a consumer money app.** Too crypto-coded. Default light, offer dark.
7122
6828
  - **Confidence-shaking UX:** progress bars that stall, skeletons that flash between states, tiny fonts for fine print. Users are nervous about their money; give them stability.
7123
- `;var ea={"consumer-warm":{id:"consumer-warm",name:"Consumer Warm",description:"Personal, lifestyle, and wellness apps (habits, journals, recipes, mood, meditation)",content:zi},"consumer-bold":{id:"consumer-bold",name:"Consumer Bold",description:"Energetic, achievement-driven apps (fitness, workouts, sports, gaming, social)",content:Wi},"professional-clean":{id:"professional-clean",name:"Professional Clean",description:"Service and appointment-based apps (booking, clinics, salons, real estate, restaurants)",content:Gi},"education-structured":{id:"education-structured",name:"Education Structured",description:"Learning and knowledge apps (courses, quizzes, flashcards, LMS, tutorials)",content:Ki},"marketplace-browse":{id:"marketplace-browse",name:"Marketplace Browse",description:"Browsing and shopping apps (marketplaces, directories, shops, catalogs, food delivery)",content:Ji},"saas-analytical":{id:"saas-analytical",name:"SaaS Analytical",description:"B2B SaaS, CRM, analytics, admin panels, ops tooling, team productivity (Linear, Notion, Stripe reference bar)",content:Vi},"content-editorial":{id:"content-editorial",name:"Content Editorial",description:"Blogs, newsletters, magazines, publications, documentation, knowledge bases",content:Yi},"devtool-technical":{id:"devtool-technical",name:"Devtool Technical",description:"Developer tools, APIs, CLIs, SDKs, infrastructure, monitoring, AI/ML platforms",content:Qi},"creative-showcase":{id:"creative-showcase",name:"Creative Showcase",description:"Portfolios, design agencies, creative studios, photographers, artists",content:Xi},"finance-clarity":{id:"finance-clarity",name:"Finance Clarity",description:"Fintech, invoicing, expense tracking, budgeting, payroll, accounting, banking",content:Zi}};var Tb=Object.keys(ea);function ta(t){let e=t?.archetype;if(!(!e||typeof e!="string"))return ea[e]}import{createServer as Ep}from"net";import{existsSync as Co,readFileSync as ma,writeFileSync as ha,mkdirSync as ga}from"fs";import{join as To}from"path";import{spawn as Np}from"child_process";import{spawn as yp,execFileSync as bp}from"child_process";import{existsSync as So,mkdirSync as ra,openSync as Wr,closeSync as Gr,readSync as wp,readFileSync as sa,writeFileSync as ia,renameSync as aa,unlinkSync as vp,readdirSync as Ab,statSync as kp}from"fs";import{homedir as la}from"os";import{join as We,dirname as ca}from"path";import{randomBytes as da,randomUUID as xp}from"crypto";var ua=1,Sp=`// Generated by @mistflow-ai/mcp local-jobs (v${ua}). Do not edit.
6829
+ `;var pa={"consumer-warm":{id:"consumer-warm",name:"Consumer Warm",description:"Personal, lifestyle, and wellness apps (habits, journals, recipes, mood, meditation)",content:na},"consumer-bold":{id:"consumer-bold",name:"Consumer Bold",description:"Energetic, achievement-driven apps (fitness, workouts, sports, gaming, social)",content:ra},"professional-clean":{id:"professional-clean",name:"Professional Clean",description:"Service and appointment-based apps (booking, clinics, salons, real estate, restaurants)",content:oa},"education-structured":{id:"education-structured",name:"Education Structured",description:"Learning and knowledge apps (courses, quizzes, flashcards, LMS, tutorials)",content:sa},"marketplace-browse":{id:"marketplace-browse",name:"Marketplace Browse",description:"Browsing and shopping apps (marketplaces, directories, shops, catalogs, food delivery)",content:ia},"saas-analytical":{id:"saas-analytical",name:"SaaS Analytical",description:"B2B SaaS, CRM, analytics, admin panels, ops tooling, team productivity (Linear, Notion, Stripe reference bar)",content:aa},"content-editorial":{id:"content-editorial",name:"Content Editorial",description:"Blogs, newsletters, magazines, publications, documentation, knowledge bases",content:la},"devtool-technical":{id:"devtool-technical",name:"Devtool Technical",description:"Developer tools, APIs, CLIs, SDKs, infrastructure, monitoring, AI/ML platforms",content:ca},"creative-showcase":{id:"creative-showcase",name:"Creative Showcase",description:"Portfolios, design agencies, creative studios, photographers, artists",content:da},"finance-clarity":{id:"finance-clarity",name:"Finance Clarity",description:"Fintech, invoicing, expense tracking, budgeting, payroll, accounting, banking",content:ua}};var Kb=Object.keys(pa);function ma(t){let e=t?.archetype;if(!(!e||typeof e!="string"))return pa[e]}import{createServer as Fp}from"net";import{existsSync as Tr,readFileSync as Ta,writeFileSync as Ca,mkdirSync as Ia}from"fs";import{join as Cr}from"path";import{spawn as qp}from"child_process";import{spawn as Cp,execFileSync as Ip}from"child_process";import{existsSync as Sr,mkdirSync as fa,openSync as Ko,closeSync as Vo,readSync as Pp,readFileSync as ya,writeFileSync as ba,renameSync as wa,unlinkSync as Rp,readdirSync as Xb,statSync as Ap}from"fs";import{homedir as va}from"os";import{join as Ke,dirname as ka}from"path";import{randomBytes as xa,randomUUID as Ep}from"crypto";var Sa=1,Np=`// Generated by @mistflow-ai/mcp local-jobs (v${Sa}). Do not edit.
7124
6830
  const { spawn } = require('node:child_process');
7125
6831
  const { readFileSync, writeFileSync, openSync, renameSync } = require('node:fs');
7126
6832
  const { join, dirname } = require('node:path');
@@ -7184,13 +6890,13 @@ child.on('error', (err) => {
7184
6890
  });
7185
6891
  process.exit(1);
7186
6892
  });
7187
- `;function _o(){return We(la(),".mistflow","jobs")}function _p(){return We(la(),".mistflow","job-wrapper.cjs")}function Cp(){let t=_p(),e=ca(t);So(e)||ra(e,{recursive:!0});let o=`v${ua}`;if(So(t))try{if(sa(t,"utf-8").includes(o))return t}catch{}let n=We(e,`.job-wrapper.tmp.${da(6).toString("hex")}`);return ia(n,Sp),aa(n,t),t}function ln(t){return We(_o(),t)}function pa(t){return We(ln(t),"status.json")}function na(t,e){let o=pa(t),n=We(ca(o),`.status.tmp.${da(6).toString("hex")}`);try{ia(n,JSON.stringify(e,null,2)+`
7188
- `),aa(n,o)}catch(r){try{vp(n)}catch{}throw r}}function Tp(t){let e=pa(t);if(!So(e))return null;try{return JSON.parse(sa(e,"utf-8"))}catch{return null}}async function Ct(t){let e=`job_${xp().replace(/-/g,"").slice(0,12)}`,o=ln(e);ra(o,{recursive:!0}),Gr(Wr(We(o,"stdout.log"),"a")),Gr(Wr(We(o,"stderr.log"),"a"));let n=new Date().toISOString(),r={id:e,type:t.type,status:"starting",pid:0,wrapperPid:0,startedAt:n,cmd:t.cmd,args:t.args,cwd:t.cwd};na(e,r);let s=Cp(),i={...process.env,...t.env??{}},a=yp("node",[s,o,t.cwd,t.cmd,...t.args],{detached:!0,stdio:"ignore",env:i});a.unref();let l={...r,wrapperPid:a.pid??0};return na(e,l),l}function Ip(t){let e=t.trim();if(!e)return NaN;let o=Date.parse(e);return Number.isFinite(o)?o:NaN}function Pp(t){let e=t.pid||t.wrapperPid;if(!e)return!1;try{process.kill(e,0)}catch{return!1}try{let o=bp("ps",["-o","lstart=","-p",String(e)],{encoding:"utf-8",stdio:["ignore","pipe","ignore"]}),n=Ip(o),r=Date.parse(t.startedAt);if(Number.isFinite(n)&&Number.isFinite(r)&&Math.abs(n-r)>2e3)return!1}catch{}return!0}function Rp(t,e){let o=Date.parse(t),n=e?Date.parse(e):Date.now();if(!Number.isFinite(o)||!Number.isFinite(n))return"0s";let r=Math.max(0,n-o),s=Math.floor(r/1e3);if(s<60)return`${s}s`;let i=Math.floor(s/60),a=s%60;return i<60?`${i}m ${a}s`:`${Math.floor(i/60)}h ${i%60}m`}function oa(t,e){if(!So(t))return"";let o=kp(t);if(o.size===0)return"";let n=o.size>e?o.size-e:0,r=o.size-n,s=Wr(t,"r");try{let i=Buffer.alloc(r);return wp(s,i,0,r,n),i.toString("utf-8")}finally{Gr(s)}}function Ap(t,e=200){let o=oa(We(ln(t),"stdout.log"),65536),n=oa(We(ln(t),"stderr.log"),64*1024),r=[];return o.trim()&&r.push(...o.split(`
7189
- `).slice(-e).map(s=>`[out] ${s}`)),n.trim()&&r.push(...n.split(`
7190
- `).slice(-e).map(s=>`[err] ${s}`)),r.filter(s=>s.trim().length>0).join(`
7191
- `)}function Bt(t){return{stdout:We(ln(t),"stdout.log"),stderr:We(ln(t),"stderr.log")}}async function Tt(t){let e=Tp(t);if(!e)return null;let o=e;return(e.status==="running"||e.status==="starting")&&!Pp(e)&&(o={...e,status:"unknown_exit",endedAt:e.endedAt??new Date().toISOString()}),{...o,elapsed:Rp(o.startedAt,o.endedAt),logTail:Ap(t)}}async function cn(t,e){let o=Math.max(100,e.pollIntervalMs??500),n=Date.now()+Math.max(0,e.timeoutMs),r=["complete","failed","unknown_exit"];for(;;){let s=await Tt(t);if(!s)return null;try{e.onPoll?.(s)}catch{}if(r.includes(s.status))return s;let i=n-Date.now();if(i<=0)return s;await new Promise(a=>setTimeout(a,Math.min(o,i)))}}function fa(){let t=(process.env.MISTFLOW_VISUAL_CONTEXT??"full").toLowerCase();return t==="off"||t==="preview"||t==="full"?t:"full"}function ya(t){return To(t,".mistflow","visual-context.json")}function ba(t){let e=ya(t);if(!Co(e))return{screenshotsTaken:0,lastScreenshotStep:0};try{return JSON.parse(ma(e,"utf-8"))}catch{return{screenshotsTaken:0,lastScreenshotStep:0}}}function Op(t,e){let o=To(t,".mistflow");Co(o)||ga(o,{recursive:!0}),ha(ya(t),JSON.stringify(e,null,2)+`
7192
- `)}var Mp=5;function wa(t,e,o){if(fa()!=="full")return!1;let n=ba(t);return n.screenshotsTaken>=Mp?!1:n.screenshotsTaken===0||o>0&&e>=o||e>0&&e!==n.lastScreenshotStep&&e%3===0}function va(t,e){let o=ba(t);Op(t,{screenshotsTaken:o.screenshotsTaken+1,lastScreenshotStep:e})}async function jp(){return new Promise((t,e)=>{let o=Ep();o.unref(),o.on("error",e),o.listen(0,"127.0.0.1",()=>{let n=o.address();if(n&&typeof n=="object"){let r=n.port;o.close(()=>t(r))}else o.close(()=>e(new Error("could not read assigned port")))})})}async function ka(t,e=15e3,o=500){let n=Date.now()+e;for(;Date.now()<n;){try{let r=new AbortController,s=setTimeout(()=>r.abort(),Math.min(o*2,2e3)),i=await fetch(t,{method:"HEAD",signal:r.signal});if(clearTimeout(s),i)return!0}catch{}await new Promise(r=>setTimeout(r,o))}return!1}function xa(t){return To(t,".mistflow","preview.json")}function Sa(t){let e=xa(t);if(!Co(e))return null;try{return JSON.parse(ma(e,"utf-8"))}catch{return null}}function _a(t){return Sa(t)}function Dp(t,e){let o=To(t,".mistflow");Co(o)||ga(o,{recursive:!0}),ha(xa(t),JSON.stringify(e,null,2)+`
7193
- `)}async function Ca(t){if(fa()==="off")return null;let e=Sa(t);if(e){let n=await Tt(e.jobId);if(n&&(n.status==="running"||n.status==="starting"))return{state:e,freshlyStarted:!1}}let o;try{o=await jp()}catch{return null}try{let n=await Ct({type:"preview",cmd:"sh",args:["-c",`npm run dev -- --port ${o}`],cwd:t});await new Promise(a=>setTimeout(a,600));let r=await Tt(n.id);if(r&&(r.status==="failed"||r.status==="unknown_exit"))return null;let s=`http://localhost:${o}`,i={port:o,url:s,jobId:n.id,startedAt:n.startedAt};return Dp(t,i),{state:i,freshlyStarted:!0}}catch{return null}}function Ta(t){try{let e=process.platform==="darwin"?"open":process.platform==="win32"?"cmd":"xdg-open",o=process.platform==="win32"?["/c","start","",t]:[t];Np(e,o,{detached:!0,stdio:"ignore"}).unref()}catch{}}var Ia=`# Design Doctrine
6893
+ `;function _r(){return Ke(va(),".mistflow","jobs")}function Op(){return Ke(va(),".mistflow","job-wrapper.cjs")}function Mp(){let t=Op(),e=ka(t);Sr(e)||fa(e,{recursive:!0});let r=`v${Sa}`;if(Sr(t))try{if(ya(t,"utf-8").includes(r))return t}catch{}let n=Ke(e,`.job-wrapper.tmp.${xa(6).toString("hex")}`);return ba(n,Np),wa(n,t),t}function cn(t){return Ke(_r(),t)}function _a(t){return Ke(cn(t),"status.json")}function ha(t,e){let r=_a(t),n=Ke(ka(r),`.status.tmp.${xa(6).toString("hex")}`);try{ba(n,JSON.stringify(e,null,2)+`
6894
+ `),wa(n,r)}catch(o){try{Rp(n)}catch{}throw o}}function jp(t){let e=_a(t);if(!Sr(e))return null;try{return JSON.parse(ya(e,"utf-8"))}catch{return null}}async function It(t){let e=`job_${Ep().replace(/-/g,"").slice(0,12)}`,r=cn(e);fa(r,{recursive:!0}),Vo(Ko(Ke(r,"stdout.log"),"a")),Vo(Ko(Ke(r,"stderr.log"),"a"));let n=new Date().toISOString(),o={id:e,type:t.type,status:"starting",pid:0,wrapperPid:0,startedAt:n,cmd:t.cmd,args:t.args,cwd:t.cwd};ha(e,o);let s=Mp(),i={...process.env,...t.env??{}},a=Cp("node",[s,r,t.cwd,t.cmd,...t.args],{detached:!0,stdio:"ignore",env:i});a.unref();let l={...o,wrapperPid:a.pid??0};return ha(e,l),l}function Dp(t){let e=t.trim();if(!e)return NaN;let r=Date.parse(e);return Number.isFinite(r)?r:NaN}function Lp(t){let e=t.pid||t.wrapperPid;if(!e)return!1;try{process.kill(e,0)}catch{return!1}try{let r=Ip("ps",["-o","lstart=","-p",String(e)],{encoding:"utf-8",stdio:["ignore","pipe","ignore"]}),n=Dp(r),o=Date.parse(t.startedAt);if(Number.isFinite(n)&&Number.isFinite(o)&&Math.abs(n-o)>2e3)return!1}catch{}return!0}function $p(t,e){let r=Date.parse(t),n=e?Date.parse(e):Date.now();if(!Number.isFinite(r)||!Number.isFinite(n))return"0s";let o=Math.max(0,n-r),s=Math.floor(o/1e3);if(s<60)return`${s}s`;let i=Math.floor(s/60),a=s%60;return i<60?`${i}m ${a}s`:`${Math.floor(i/60)}h ${i%60}m`}function ga(t,e){if(!Sr(t))return"";let r=Ap(t);if(r.size===0)return"";let n=r.size>e?r.size-e:0,o=r.size-n,s=Ko(t,"r");try{let i=Buffer.alloc(o);return Pp(s,i,0,o,n),i.toString("utf-8")}finally{Vo(s)}}function Up(t,e=200){let r=ga(Ke(cn(t),"stdout.log"),65536),n=ga(Ke(cn(t),"stderr.log"),64*1024),o=[];return r.trim()&&o.push(...r.split(`
6895
+ `).slice(-e).map(s=>`[out] ${s}`)),n.trim()&&o.push(...n.split(`
6896
+ `).slice(-e).map(s=>`[err] ${s}`)),o.filter(s=>s.trim().length>0).join(`
6897
+ `)}function zt(t){return{stdout:Ke(cn(t),"stdout.log"),stderr:Ke(cn(t),"stderr.log")}}async function Pt(t){let e=jp(t);if(!e)return null;let r=e;return(e.status==="running"||e.status==="starting")&&!Lp(e)&&(r={...e,status:"unknown_exit",endedAt:e.endedAt??new Date().toISOString()}),{...r,elapsed:$p(r.startedAt,r.endedAt),logTail:Up(t)}}async function dn(t,e){let r=Math.max(100,e.pollIntervalMs??500),n=Date.now()+Math.max(0,e.timeoutMs),o=["complete","failed","unknown_exit"];for(;;){let s=await Pt(t);if(!s)return null;try{e.onPoll?.(s)}catch{}if(o.includes(s.status))return s;let i=n-Date.now();if(i<=0)return s;await new Promise(a=>setTimeout(a,Math.min(r,i)))}}function Pa(){let t=(process.env.MISTFLOW_VISUAL_CONTEXT??"full").toLowerCase();return t==="off"||t==="preview"||t==="full"?t:"full"}function Ra(t){return Cr(t,".mistflow","visual-context.json")}function Aa(t){let e=Ra(t);if(!Tr(e))return{screenshotsTaken:0,lastScreenshotStep:0};try{return JSON.parse(Ta(e,"utf-8"))}catch{return{screenshotsTaken:0,lastScreenshotStep:0}}}function Bp(t,e){let r=Cr(t,".mistflow");Tr(r)||Ia(r,{recursive:!0}),Ca(Ra(t),JSON.stringify(e,null,2)+`
6898
+ `)}var Hp=5;function Ea(t,e,r){if(Pa()!=="full")return!1;let n=Aa(t);return n.screenshotsTaken>=Hp?!1:n.screenshotsTaken===0||r>0&&e>=r||e>0&&e!==n.lastScreenshotStep&&e%3===0}function Na(t,e){let r=Aa(t);Bp(t,{screenshotsTaken:r.screenshotsTaken+1,lastScreenshotStep:e})}async function zp(){return new Promise((t,e)=>{let r=Fp();r.unref(),r.on("error",e),r.listen(0,"127.0.0.1",()=>{let n=r.address();if(n&&typeof n=="object"){let o=n.port;r.close(()=>t(o))}else r.close(()=>e(new Error("could not read assigned port")))})})}async function Oa(t,e=15e3,r=500){let n=Date.now()+e;for(;Date.now()<n;){try{let o=new AbortController,s=setTimeout(()=>o.abort(),Math.min(r*2,2e3)),i=await fetch(t,{method:"HEAD",signal:o.signal});if(clearTimeout(s),i)return!0}catch{}await new Promise(o=>setTimeout(o,r))}return!1}function Ma(t){return Cr(t,".mistflow","preview.json")}function ja(t){let e=Ma(t);if(!Tr(e))return null;try{return JSON.parse(Ta(e,"utf-8"))}catch{return null}}function Da(t){return ja(t)}function Wp(t,e){let r=Cr(t,".mistflow");Tr(r)||Ia(r,{recursive:!0}),Ca(Ma(t),JSON.stringify(e,null,2)+`
6899
+ `)}async function La(t){if(Pa()==="off")return null;let e=ja(t);if(e){let n=await Pt(e.jobId);if(n&&(n.status==="running"||n.status==="starting"))return{state:e,freshlyStarted:!1}}let r;try{r=await zp()}catch{return null}try{let n=await It({type:"preview",cmd:"sh",args:["-c",`npm run dev -- --port ${r}`],cwd:t});await new Promise(a=>setTimeout(a,600));let o=await Pt(n.id);if(o&&(o.status==="failed"||o.status==="unknown_exit"))return null;let s=`http://localhost:${r}`,i={port:r,url:s,jobId:n.id,startedAt:n.startedAt};return Wp(t,i),{state:i,freshlyStarted:!0}}catch{return null}}function $a(t){try{let e=process.platform==="darwin"?"open":process.platform==="win32"?"cmd":"xdg-open",r=process.platform==="win32"?["/c","start","",t]:[t];qp(e,r,{detached:!0,stdio:"ignore"}).unref()}catch{}}var Ua=`# Design Doctrine
7194
6900
 
7195
6901
  This is the standard for every UI you generate. It is not aspirational. It is the floor.
7196
6902
 
@@ -7256,7 +6962,7 @@ Before submitting any UI file, read it with this checklist and regenerate if any
7256
6962
  8. Is there ONE memorable moment on the page (signature typography / orchestrated motion / unexpected layout)? **If zero, add one. If three, keep the strongest one.**
7257
6963
 
7258
6964
  If any answer fails, the UI is not ready. Redesign \u2014 not tweak \u2014 the failing piece.
7259
- `;var Pa=`# Typography
6965
+ `;var Fa=`# Typography
7260
6966
 
7261
6967
  Typography is the cheapest way to escape AI slop and the most visible place it shows up. Every generic landing uses Inter. Every remarkable landing picks something you'd notice.
7262
6968
 
@@ -7344,7 +7050,7 @@ Pick exactly one:
7344
7050
  - **Mixed case display** \u2014 Title Case for proper nouns, SmallCaps for the verb. Editorial feel.
7345
7051
 
7346
7052
  Do NOT use: gradient text (\`bg-clip-text\`) on one word of the headline. That effect is the most overused generic-AI marker.
7347
- `;var Ra="# Color\n\nColor is a commitment, not a palette swatch. The #1 failure mode in AI-generated landings is an evenly-distributed palette: a little emerald here, a little amber there, a gradient, a badge. That's visual hedging \u2014 it signals \"I couldn't pick a point of view.\"\n\nPick one color. Use it with intent.\n\n## The Token System\n\nEvery color on every screen goes through a CSS variable defined in `globals.css`. You do not hand-pick hex values in JSX. You do not use Tailwind palette utilities (`bg-emerald-500`, `text-blue-600`, `border-slate-200`). If a color isn't in the token scheme, it doesn't exist for this app.\n\n### Core tokens\n\n- `--color-background` \u2014 page background\n- `--color-foreground` \u2014 default body text\n- `--color-card` \u2014 elevated surface (cards, panels)\n- `--color-card-foreground` \u2014 text on cards\n- `--color-muted` \u2014 low-emphasis surface fill\n- `--color-muted-foreground` \u2014 secondary text (\u2265 WCAG AA contrast vs background)\n- `--color-border` \u2014 subtle dividers\n- `--color-input` \u2014 form input borders\n- `--color-popover` / `--color-popover-foreground` \u2014 dropdowns, tooltips, menus\n\n### Interactive tokens\n\n- `--color-primary` \u2014 primary CTAs, links, active tab, focus ring\n- `--color-primary-foreground` \u2014 text on primary (derived for WCAG AA contrast)\n- `--color-ring` \u2014 focus outline (same hue as primary)\n- `--color-secondary` / `--color-secondary-foreground` \u2014 quieter than primary, more than muted\n- `--color-accent` / `--color-accent-foreground` \u2014 hover states on neutrals\n\n### Semantic tokens\n\n- `--color-success` \u2014 confirmation, \"verified\" banners, positive states\n- `--color-warning` \u2014 pending states, attention, non-destructive caution\n- `--color-destructive` / `--color-destructive-foreground` \u2014 errors, irreversible actions\n- `--color-info` \u2014 neutral system messages, tips\n\n**You use these semantic tokens for all status UI.** When you need a \"green checkmark\" you reach for `text-success`, not `text-emerald-500`. When you need an amber alert you reach for `bg-warning/10 text-warning`, not `bg-amber-50 text-amber-700`.\n\n## Usage Rules\n\n**Primary color:**\n- Primary CTAs, links, the active navigation tab, focus rings.\n- NOT for headings. Black or foreground-colored headings are stronger.\n- NOT for large surface fills (it's a splash of color, not a wash).\n- NOT for borders except focus rings.\n- One bold use of primary beats twenty sprinkled uses.\n\n**Neutrals do the structural work:**\n- Background, cards, borders \u2014 all come from the neutral scale.\n- Text hierarchy via opacity on foreground (`text-foreground`, `text-foreground/80`, `text-muted-foreground`) \u2014 not different colors.\n\n**Semantic colors:**\n- Use `bg-success/10 text-success border border-success/30` for success banners (reproducibility: a light-tinted version of the success color for surface, the solid version for text and border).\n- Same shape for warning, info, destructive.\n\n## Dark Mode\n\nCommit to one theme. If DESIGN.md says the app is dark-themed, make it unambiguously dark \u2014 `#0c0c0c` / near-black base, not neutral-900. If light-themed, commit to light.\n\nDo NOT auto-swap based on `prefers-color-scheme` when DESIGN.md declared a theme direction. It chose a theme for a reason; respect it.\n\n## Forbidden\n\n- Palette utilities: `bg-emerald-500`, `text-blue-600`, `border-slate-200`, `from-purple-500`, `via-pink-300`, `to-amber-400`. Every one of these in your output is a slop indicator.\n- Hex literals inside `className` or inline `style` props.\n- Named colors from CSS (`red`, `lightgrey`, `rebeccapurple`) \u2014 never used in production UI; always a sign of rushed work.\n- Purple gradients on white. The single most overused AI-design pattern.\n- Three-color gradient pills. Rainbow badges. \"Vibrant\" anything.\n\n## Contrast\n\nEvery text/surface pair in DESIGN.md is already WCAG AA-verified by Mistflow's contrast test. You do not need to re-check \u2014 the tokens are safe by construction. You DO need to:\n\n- Not override token combinations with hardcoded classes that might fail contrast.\n- Maintain contrast for overlays (text on images, modals on scrims). When stacking text over a gradient or image, include a scrim (`bg-background/80 backdrop-blur-sm`) so body text stays \u2265 4.5:1.\n\n## Getting Color Wrong\n\nThe specific failure mode to avoid: a page where primary is used in five places, all small and incidental \u2014 a checkmark, a hover state, a tiny badge. That's AI hedging. Either use primary confidently on a hero CTA and mean it, OR don't use primary on the page at all and let neutrals carry the identity.\n";var Aa=`# Motion
7053
+ `;var qa="# Color\n\nColor is a commitment, not a palette swatch. The #1 failure mode in AI-generated landings is an evenly-distributed palette: a little emerald here, a little amber there, a gradient, a badge. That's visual hedging \u2014 it signals \"I couldn't pick a point of view.\"\n\nPick one color. Use it with intent.\n\n## The Token System\n\nEvery color on every screen goes through a CSS variable defined in `globals.css`. You do not hand-pick hex values in JSX. You do not use Tailwind palette utilities (`bg-emerald-500`, `text-blue-600`, `border-slate-200`). If a color isn't in the token scheme, it doesn't exist for this app.\n\n### Core tokens\n\n- `--color-background` \u2014 page background\n- `--color-foreground` \u2014 default body text\n- `--color-card` \u2014 elevated surface (cards, panels)\n- `--color-card-foreground` \u2014 text on cards\n- `--color-muted` \u2014 low-emphasis surface fill\n- `--color-muted-foreground` \u2014 secondary text (\u2265 WCAG AA contrast vs background)\n- `--color-border` \u2014 subtle dividers\n- `--color-input` \u2014 form input borders\n- `--color-popover` / `--color-popover-foreground` \u2014 dropdowns, tooltips, menus\n\n### Interactive tokens\n\n- `--color-primary` \u2014 primary CTAs, links, active tab, focus ring\n- `--color-primary-foreground` \u2014 text on primary (derived for WCAG AA contrast)\n- `--color-ring` \u2014 focus outline (same hue as primary)\n- `--color-secondary` / `--color-secondary-foreground` \u2014 quieter than primary, more than muted\n- `--color-accent` / `--color-accent-foreground` \u2014 hover states on neutrals\n\n### Semantic tokens\n\n- `--color-success` \u2014 confirmation, \"verified\" banners, positive states\n- `--color-warning` \u2014 pending states, attention, non-destructive caution\n- `--color-destructive` / `--color-destructive-foreground` \u2014 errors, irreversible actions\n- `--color-info` \u2014 neutral system messages, tips\n\n**You use these semantic tokens for all status UI.** When you need a \"green checkmark\" you reach for `text-success`, not `text-emerald-500`. When you need an amber alert you reach for `bg-warning/10 text-warning`, not `bg-amber-50 text-amber-700`.\n\n## Usage Rules\n\n**Primary color:**\n- Primary CTAs, links, the active navigation tab, focus rings.\n- NOT for headings. Black or foreground-colored headings are stronger.\n- NOT for large surface fills (it's a splash of color, not a wash).\n- NOT for borders except focus rings.\n- One bold use of primary beats twenty sprinkled uses.\n\n**Neutrals do the structural work:**\n- Background, cards, borders \u2014 all come from the neutral scale.\n- Text hierarchy via opacity on foreground (`text-foreground`, `text-foreground/80`, `text-muted-foreground`) \u2014 not different colors.\n\n**Semantic colors:**\n- Use `bg-success/10 text-success border border-success/30` for success banners (reproducibility: a light-tinted version of the success color for surface, the solid version for text and border).\n- Same shape for warning, info, destructive.\n\n## Dark Mode\n\nCommit to one theme. If DESIGN.md says the app is dark-themed, make it unambiguously dark \u2014 `#0c0c0c` / near-black base, not neutral-900. If light-themed, commit to light.\n\nDo NOT auto-swap based on `prefers-color-scheme` when DESIGN.md declared a theme direction. It chose a theme for a reason; respect it.\n\n## Forbidden\n\n- Palette utilities: `bg-emerald-500`, `text-blue-600`, `border-slate-200`, `from-purple-500`, `via-pink-300`, `to-amber-400`. Every one of these in your output is a slop indicator.\n- Hex literals inside `className` or inline `style` props.\n- Named colors from CSS (`red`, `lightgrey`, `rebeccapurple`) \u2014 never used in production UI; always a sign of rushed work.\n- Purple gradients on white. The single most overused AI-design pattern.\n- Three-color gradient pills. Rainbow badges. \"Vibrant\" anything.\n\n## Contrast\n\nEvery text/surface pair in DESIGN.md is already WCAG AA-verified by Mistflow's contrast test. You do not need to re-check \u2014 the tokens are safe by construction. You DO need to:\n\n- Not override token combinations with hardcoded classes that might fail contrast.\n- Maintain contrast for overlays (text on images, modals on scrims). When stacking text over a gradient or image, include a scrim (`bg-background/80 backdrop-blur-sm`) so body text stays \u2265 4.5:1.\n\n## Getting Color Wrong\n\nThe specific failure mode to avoid: a page where primary is used in five places, all small and incidental \u2014 a checkmark, a hover state, a tiny badge. That's AI hedging. Either use primary confidently on a hero CTA and mean it, OR don't use primary on the page at all and let neutrals carry the identity.\n";var Ba=`# Motion
7348
7054
 
7349
7055
  Motion is the most-ignored dimension of AI-generated UI. Generic landings either have no motion or sprinkle tiny fades on everything. Remarkable landings pick ONE orchestrated moment and execute it precisely.
7350
7056
 
@@ -7422,7 +7128,7 @@ Primary CTA has a 2px vertical translate on \`:active\` with 80ms \`--ease-quart
7422
7128
  ## One-Line Motion Rule
7423
7129
 
7424
7130
  If the motion you're about to add could be described as "subtle animation on scroll", delete it. Design motion is specific, named, and described in one sentence: "the headline lines drop in from above with a 50ms stagger at page load." If you can't describe it that specifically, you don't have a motion design \u2014 you have motion noise.
7425
- `;var Ea=`# Spatial Composition
7131
+ `;var Ha=`# Spatial Composition
7426
7132
 
7427
7133
  Layout is where AI-slop is most visible. A centered single-column hero with a pill, headline, subhead, and two CTAs is the most-recognizable generic-SaaS pattern on the internet. It is the layout you must not use.
7428
7134
 
@@ -7492,7 +7198,7 @@ To signal "designed, not templated":
7492
7198
  Negative space is a design element. A hero with \`py-32\` and a tight \`max-w-2xl\` text block in a wide viewport reads as confident. A hero that fills every pixel with checkmarks, logos, and counters reads as scared.
7493
7199
 
7494
7200
  Pick: generous breathing (luxury / refined / editorial) OR controlled density (dashboards / industrial / brutalist). Never a mushy middle.
7495
- `;var Na=`# Interaction
7201
+ `;var za=`# Interaction
7496
7202
 
7497
7203
  Every interactive element has a designed state for every phase: rest, hover, focus, active, disabled. The difference between remarkable and adequate lives in these states.
7498
7204
 
@@ -7577,7 +7283,7 @@ Small moments that add up to "designed":
7577
7283
  - **Empty states** offer a specific next action, not "No data yet".
7578
7284
 
7579
7285
  These micro-interactions are the texture of a designed product. Ship them.
7580
- `;var Oa=`# UX Writing
7286
+ `;var Wa=`# UX Writing
7581
7287
 
7582
7288
  Copy is design. A remarkable page has copy that could only be about this specific product. A generic page has copy that could describe a hundred.
7583
7289
 
@@ -7653,18 +7359,18 @@ If there's a pricing page:
7653
7359
  ## The Footer
7654
7360
 
7655
7361
  A footer with the same five links ("Product / Pricing / Docs / Blog / Terms") is fine, but the company name / tagline should be specific. "StandupSync \u2014 async updates, human-sized" is better than "\xA9 2026 StandupSync Inc."
7656
- `;import{existsSync as zp,readFileSync as Wp,writeFileSync as Gp}from"fs";import{join as Kp}from"path";import{z as Un}from"zod";var Io=Un.object({key:Un.string().describe("SHOUTING_SNAKE_CASE env var name, e.g. OPENAI_API_KEY"),description:Un.string().describe("Plain-English description, e.g. 'OpenAI API key for AI features'"),setupUrl:Un.string().optional().describe("URL where the user can obtain this key, e.g. https://platform.openai.com/api-keys"),integration:Un.string().optional().describe("Optional integration name this key belongs to, e.g. 'OpenAI'")}),Jp=/^[A-Z][A-Z0-9_]*$/;function Po(t,e){if(e.length===0)return{added:[],skipped:[]};let o=Kp(t,"mistflow.json");if(!zp(o))throw new Error(`mistflow.json not found at ${t}`);let n=Wp(o,"utf-8"),r=JSON.parse(n);r.env=r.env??{},r.env.required=r.env.required??{};let s=r.env.required,i=[],a=[];for(let l of e)if(!(!l?.key||typeof l.key!="string"||!Jp.test(l.key))){if(s[l.key]){a.push(l.key);continue}s[l.key]={description:typeof l.description=="string"?l.description:"",...typeof l.setupUrl=="string"&&l.setupUrl?{setupUrl:l.setupUrl}:{},...typeof l.integration=="string"&&l.integration?{integration:l.integration}:{}},i.push(l.key)}return i.length>0&&Gp(o,JSON.stringify(r,null,2)+`
7657
- `),{added:i,skipped:a}}function Xp(t){return new Promise(e=>{let o=Qp({port:t,host:"127.0.0.1"});o.on("connect",()=>{o.destroy(),e(!0)}),o.on("error",()=>{e(!1)})})}var Zp=qn.object({projectPath:qn.string().optional().describe("Path to the project directory (default: cwd)"),step:qn.number().optional().describe("Specific step number to implement (default: next incomplete step)"),sessionId:qn.string().uuid().optional().describe("Backend session ID. Pass through from mist_plan response so state guards apply and session state advances as the tool runs."),envVarsRequired:qn.array(Io).optional().describe("Declare any env vars the code you just wrote uses (process.env.X) that aren't already in mistflow.json env.required. Server merges these into the manifest so missing values surface as warnings on deploy. Skip keys already covered by integration presets (Stripe, Resend, etc. \u2014 those are pre-declared). Only declare ad-hoc keys you introduced. First declaration wins; re-declaring is a no-op.")});function em(t){let e=Qe(t,"mistflow.json");if(!It(e))return null;try{return JSON.parse(Kr(e,"utf-8"))}catch{return null}}function ja(t,e){let o=Qe(t,"mistflow.json");Ro(o,JSON.stringify(e,null,2)+`
7658
- `),nn(t)}function Ao(t){return t.entity??t.name??"Unknown"}function tm(t){return t.length===0?"":typeof t[0]=="string"?t.join(", "):t.map(e=>`${e.name} (${e.type})`).join(", ")}function Da(t){return t.path??t.route??t.name??""}function nm(t){let e=(t||"text").toLowerCase();return e==="string"||e==="varchar"||e==="char"?"text":e==="integer"||e==="int"||e==="number"||e==="float"||e==="decimal"||e==="double"?"number":e==="boolean"||e==="bool"?"boolean":e==="date"||e==="datetime"||e==="timestamp"?"date":e==="email"?"email":e==="url"||e==="uri"?"url":e==="enum"||e==="select"?"select":e==="text"||e==="longtext"||e==="textarea"?"textarea":"text"}function La(t,e){if(!t.entities||t.entities.length===0)return e;let o=t.entities.map(n=>n.toLowerCase());return e.filter(n=>{let r=Ao(n).toLowerCase();return o.some(s=>r.includes(s)||s.includes(r))})}function om(t,e){if(!t.pages||t.pages.length===0)return[];let o=t.pages.map(n=>n.toLowerCase());return e.filter(n=>{let r=(n.name??"").toLowerCase(),s=Da(n).toLowerCase();return o.some(i=>r.includes(i)||i.includes(r)||s.includes(i))})}var rm=new Set(["landing","design","dashboard","crud","layout","admin","auth","schema","integration","multi-tenant","deploy","general"]);function sm(t){let e=t.stepType;if(e&&rm.has(e))return e;if(t.integrationId)return"integration";let o=`${t.name??t.title??""} ${t.description}`.toLowerCase();return o.includes("crud")||o.includes("list")&&o.includes("create")?"crud":o.includes("auth")||o.includes("login")||o.includes("register")?"auth":o.includes("admin")&&(o.includes("panel")||o.includes("dashboard")||o.includes("manage")||o.includes("users"))?"admin":o.includes("dashboard")||o.includes("overview")||o.includes("analytics")?"dashboard":o.includes("schema")||o.includes("database")||o.includes("model")?"schema":o.includes("layout")||o.includes("sidebar")||o.includes("navigation")?"layout":o.includes("deploy")||o.includes("cloudflare")?"deploy":o.includes("organization")||o.includes("team")||o.includes("workspace")||o.includes("multi-tenant")||o.includes("invite member")?"multi-tenant":o.includes("landing")||o.includes("hero")||o.includes("marketing")||o.includes("homepage")?"landing":o.includes("design")||o.includes("theme")||o.includes("styling")||o.includes("ui polish")||o.includes("visual")?"design":"general"}function im(t){switch(t){case"landing":case"design":return{min:6,max:10};case"integration":return{min:8,max:12};case"dashboard":case"admin":return{min:5,max:7};case"crud":case"multi-tenant":return{min:4,max:6};case"schema":return{min:3,max:4};case"layout":case"auth":return{min:3,max:5};case"deploy":return{min:1,max:2};default:return{min:4,max:6}}}function am(t){let e=[];if(e.push("### Design choices (decided at plan time \u2014 follow these exactly):"),t.tone&&e.push(`- **App tone**: ${t.tone}`),t.fonts&&(e.push(`- **Heading font**: ${t.fonts.heading} (load from Google Fonts)`),e.push(`- **Body font**: ${t.fonts.body} (load from Google Fonts)`)),e.push("- **All color comes from CSS variables** \u2014 never use Tailwind palette utilities like `bg-emerald-500`, `text-blue-600`, `border-slate-200`. Use `bg-primary`, `text-primary-foreground`, `bg-muted`, `text-muted-foreground`, `border-border`, `bg-card`, `text-foreground`, `bg-destructive`, etc. The primary/accent is already wired in globals.css from the chosen design system."),e.push("- **Outbound URLs (invite links, email CTAs, absolute hrefs in server code):** use `process.env.BETTER_AUTH_URL` as the base. Do NOT use `process.env.NEXT_PUBLIC_APP_URL` \u2014 Next.js bakes every `NEXT_PUBLIC_*` literal into the production bundle at build time, so the scaffolded localhost default ships to prod and invite emails land on `http://localhost:3000`. Client components should use relative URLs (they resolve to the current origin automatically)."),t.borderRadius){let o={sharp:"2px",subtle:"6px",rounded:"12px",pill:"9999px"};e.push(`- **Border radius**: ${t.borderRadius} (${o[t.borderRadius]??t.borderRadius}) \u2014 set as --radius in globals.css`)}if(t.shadowStyle){let o={flat:"No shadows \u2014 use borders for separation",subtle:"shadow-sm on cards, shadow on hover",elevated:"shadow-md on cards, shadow-lg on modals, layered depth",dramatic:"shadow-lg with colored tinting, bold depth"};e.push(`- **Shadow style**: ${t.shadowStyle} \u2014 ${o[t.shadowStyle]??t.shadowStyle}`)}if(t.cardStyle){let o={filled:"bg-card with subtle background fill, no visible border",bordered:"border border-border on white/transparent background",elevated:"bg-card shadow-md, no border, lifted appearance",glass:"bg-white/60 backdrop-blur-sm border border-white/20, translucent"};e.push(`- **Card style**: ${t.cardStyle} \u2014 ${o[t.cardStyle]??t.cardStyle}`)}if(t.landingTone&&e.push(`- **Landing page tone**: ${t.landingTone}`),t.visualStrategy){let o=t.visualStrategy,n=t.heroPhoto!==!1,r=n&&!!o.heroImages?.length;if(e.push(""),e.push("### Visual strategy:"),r&&o.heroImages&&o.heroImages.length>0){e.push("**Hero image** \u2014 use this Unsplash photo as the landing page hero BACKGROUND:");let i=o.heroImages[0];e.push(`- URL: ${i.url}`),e.push(`- Alt text for img tag: "${i.alt||"Hero image"} \u2014 Photo by ${i.photographer} on Unsplash"`),e.push("- Use as full-bleed background behind the entire hero section with a dark overlay (bg-black/60 or similar for readability). The photo is atmosphere and context, NOT the main visual \u2014 the glassmorphic product card sits on top.")}else n&&!o.heroImages?.length?e.push("**Hero background** \u2014 the user requested a lifestyle photo, but no Unsplash image was fetched (likely a transient API issue). Fall back gracefully: use the design preset's gradient + glassmorphism, AND tell the user in your reply: 'I couldn't fetch a stock photo for the hero this time \u2014 using a CSS gradient instead. You can add a photo later by editing the hero section in app/home/page.tsx.'"):n||e.push("**Hero background** \u2014 user opted for CSS-only (no photo). Use the design preset's specified gradient, animated background, or glassmorphism. No Unsplash.");if(o.sectionImages?.length){e.push("**Section images available** \u2014 use these for feature sections, about sections, or testimonial backgrounds (NOT the hero):");for(let i of o.sectionImages)e.push(`- ${i.url} \u2014 alt: "${i.alt||"section image"} \u2014 Photo by ${i.photographer} on Unsplash"`)}(r||o.sectionImages?.length)&&e.push("For image attribution: put photographer credit in the img alt text (already provided above) and add an HTML comment <!-- Images from Unsplash --> near the images. Do NOT add visible attribution text on the page.");let s=ta(t);s?(e.push(""),e.push(`### Page composition \u2014 ${s.name} archetype`),e.push(`This plan was classified as **${s.id}** \u2014 ${s.description}. The layouts below (landing, dashboard, lists, detail, forms) are PRESCRIPTIVE for this category of app. Follow them. Do NOT reach for the generic split-hero + glassmorphic-mockup pattern \u2014 that template was retired precisely because it produces the same cold AI-slop hero regardless of the app's intent.`),e.push(""),e.push(s.content)):(e.push(""),e.push("**No archetype was selected for this plan.** Fall back to a split-hero layout, but if this app clearly fits a category (personal/wellness, booking, B2B SaaS, marketplace, etc.), flag it to the user \u2014 the plan should have picked one of the ten archetypes in archetypes.ts."),e.push(""),e.push("**Hero layout \u2014 split hero with product UI mockup (follow this exactly):**"),e.push("The hero uses a split layout with text on the left and a product preview on the right."),e.push(""),e.push("```"),e.push("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"),e.push("\u2502 [Logo] [Sign In] [CTA \u2192] \u2502 \u2190 transparent nav, NOT sticky"),e.push("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"),e.push("\u2502 \u2502"),e.push("\u2502 \u25CF Built for [audience] \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502"),e.push("\u2502 \u2502 Glassmorphic \u2502 \u2502"),e.push("\u2502 Big bold headline, \u2502 product mockup \u2502 \u2502"),e.push("\u2502 accent color on key word \u2502 card showing \u2502 \u2502"),e.push("\u2502 \u2502 real app data \u2502 \u2502"),e.push("\u2502 Description paragraph \u2502 (stats, table, \u2502 \u2502"),e.push("\u2502 \u2502 chart, etc.) \u2502 \u2502"),e.push("\u2502 [Primary CTA \u2192] [Secondary] \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502"),e.push("\u2502 \u2502"),e.push("\u2502 500+ 25K+ 99% \u2502"),e.push("\u2502 Label Label Label \u2502"),e.push("\u2502 \u2502"),r?e.push("\u2502 \u2190 full-bleed photo bg + dark overlay behind all \u2192 \u2502"):e.push("\u2502 \u2190 preset gradient / glass background behind all \u2192 \u2502"),e.push("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"),e.push("```"),e.push(""),e.push("**Left side (~55%)**: badge pill \u2192 bold headline (use accent color on ONE key word or phrase) \u2192 description \u2192 two CTA buttons (primary filled + secondary outline/ghost) \u2192 stats row with 3 proof points"),e.push("**Right side (~45%)**: A floating card that previews what the app looks like inside. Build realistic fake app data using styled divs \u2014 stat cards, mini table rows, schedule blocks, or charts that match what this specific app's dashboard shows. Must be DOMAIN-SPECIFIC."),e.push("**Stats row**: 3 proof-point numbers at the bottom of the left side."))}return e.join(`
7659
- `)}function lm(t){let e=[];return e.push("### Landing layout (Plan G IR \u2014 STRUCTURAL AUTHORITY for this page):"),e.push(""),e.push("Build the landing page with EXACTLY these sections, in this order. Each section's `type` names a primitive \u2014 render it using the picked direction's fonts + colors + shape language. The `intent` explains WHY the section exists; the `props` are the content."),e.push(""),e.push(`- **Page intent**: ${t.page_intent}`),e.push(`- **Narrative arc**: ${t.narrative_arc}`),e.push(`- **Audience**: ${t.audience_persona}`),t.primary_cta&&e.push(`- **Primary CTA**: \`${t.primary_cta.label}\` \u2014 ${t.primary_cta.intent}`),e.push(""),e.push("**Sections (build in order):**"),e.push(""),t.sections.forEach((o,n)=>{let r=n+1;e.push(`${r}. \`${o.type}\` (id: \`${o.id}\`, role: \`${o.narrative_role}\`)`),e.push(` - **Intent**: ${o.intent}`),o.answers_section_id&&e.push(` - **Answers**: section \`${o.answers_section_id}\` (use visual cues \u2014 paired typography, matching accent, sequential numbering \u2014 to make the relationship readable)`),e.push(" - **Props** (JSON):"),e.push(" ```json");let s=JSON.stringify(o.props,null,2).split(`
7362
+ `;import{existsSync as Zp,readFileSync as em,writeFileSync as tm}from"fs";import{join as nm}from"path";import{z as Fn}from"zod";var Ir=Fn.object({key:Fn.string().describe("SHOUTING_SNAKE_CASE env var name, e.g. OPENAI_API_KEY"),description:Fn.string().describe("Plain-English description, e.g. 'OpenAI API key for AI features'"),setupUrl:Fn.string().optional().describe("URL where the user can obtain this key, e.g. https://platform.openai.com/api-keys"),integration:Fn.string().optional().describe("Optional integration name this key belongs to, e.g. 'OpenAI'")}),rm=/^[A-Z][A-Z0-9_]*$/;function Pr(t,e){if(e.length===0)return{added:[],skipped:[]};let r=nm(t,"mistflow.json");if(!Zp(r))throw new Error(`mistflow.json not found at ${t}`);let n=em(r,"utf-8"),o=JSON.parse(n);o.env=o.env??{},o.env.required=o.env.required??{};let s=o.env.required,i=[],a=[];for(let l of e)if(!(!l?.key||typeof l.key!="string"||!rm.test(l.key))){if(s[l.key]){a.push(l.key);continue}s[l.key]={description:typeof l.description=="string"?l.description:"",...typeof l.setupUrl=="string"&&l.setupUrl?{setupUrl:l.setupUrl}:{},...typeof l.integration=="string"&&l.integration?{integration:l.integration}:{}},i.push(l.key)}return i.length>0&&tm(r,JSON.stringify(o,null,2)+`
7363
+ `),{added:i,skipped:a}}function lm(t){return new Promise(e=>{let r=am({port:t,host:"127.0.0.1"});r.on("connect",()=>{r.destroy(),e(!0)}),r.on("error",()=>{e(!1)})})}var cm=qn.object({projectPath:qn.string().optional().describe("Path to the project directory (default: cwd)"),step:qn.number().optional().describe("Specific step number to implement (default: next incomplete step)"),sessionId:qn.string().uuid().optional().describe("Backend session ID. Pass through from mist_plan response so state guards apply and session state advances as the tool runs."),envVarsRequired:qn.array(Ir).optional().describe("Declare any env vars the code you just wrote uses (process.env.X) that aren't already in mistflow.json env.required. Server merges these into the manifest so missing values surface as warnings on deploy. Skip keys already covered by integration presets (Stripe, Resend, etc. \u2014 those are pre-declared). Only declare ad-hoc keys you introduced. First declaration wins; re-declaring is a no-op.")});function dm(t){let e=nt(t,"mistflow.json");if(!Rt(e))return null;try{return JSON.parse(Yo(e,"utf-8"))}catch{return null}}function Ja(t,e){let r=nt(t,"mistflow.json"),n=`${r}.${process.pid}.tmp`;Rr(n,JSON.stringify(e,null,2)+`
7364
+ `),om(n,r),on(t)}function Ar(t){return t.entity??t.name??"Unknown"}function um(t){return t.length===0?"":typeof t[0]=="string"?t.join(", "):t.map(e=>`${e.name} (${e.type})`).join(", ")}function Ka(t){return t.path??t.route??t.name??""}function pm(t){let e=(t||"text").toLowerCase();return e==="string"||e==="varchar"||e==="char"?"text":e==="integer"||e==="int"||e==="number"||e==="float"||e==="decimal"||e==="double"?"number":e==="boolean"||e==="bool"?"boolean":e==="date"||e==="datetime"||e==="timestamp"?"date":e==="email"?"email":e==="url"||e==="uri"?"url":e==="enum"||e==="select"?"select":e==="text"||e==="longtext"||e==="textarea"?"textarea":"text"}function Va(t,e){if(!t.entities||t.entities.length===0)return e;let r=t.entities.map(n=>n.toLowerCase());return e.filter(n=>{let o=Ar(n).toLowerCase();return r.some(s=>o.includes(s)||s.includes(o))})}function mm(t,e){if(!t.pages||t.pages.length===0)return[];let r=t.pages.map(n=>n.toLowerCase());return e.filter(n=>{let o=(n.name??"").toLowerCase(),s=Ka(n).toLowerCase();return r.some(i=>o.includes(i)||i.includes(o)||s.includes(i))})}var hm=new Set(["landing","design","dashboard","crud","layout","admin","auth","schema","integration","multi-tenant","deploy","general"]);function gm(t){let e=t.stepType;if(e&&hm.has(e))return e;if(t.integrationId)return"integration";let r=`${t.name??t.title??""} ${t.description}`.toLowerCase();return r.includes("crud")||r.includes("list")&&r.includes("create")?"crud":r.includes("auth")||r.includes("login")||r.includes("register")?"auth":r.includes("admin")&&(r.includes("panel")||r.includes("dashboard")||r.includes("manage")||r.includes("users"))?"admin":r.includes("dashboard")||r.includes("overview")||r.includes("analytics")?"dashboard":r.includes("schema")||r.includes("database")||r.includes("model")?"schema":r.includes("layout")||r.includes("sidebar")||r.includes("navigation")?"layout":r.includes("deploy")||r.includes("cloudflare")?"deploy":r.includes("organization")||r.includes("team")||r.includes("workspace")||r.includes("multi-tenant")||r.includes("invite member")?"multi-tenant":r.includes("landing")||r.includes("hero")||r.includes("marketing")||r.includes("homepage")?"landing":r.includes("design")||r.includes("theme")||r.includes("styling")||r.includes("ui polish")||r.includes("visual")?"design":"general"}function fm(t){switch(t){case"landing":case"design":return{min:6,max:10};case"integration":return{min:8,max:12};case"dashboard":case"admin":return{min:5,max:7};case"crud":case"multi-tenant":return{min:4,max:6};case"schema":return{min:3,max:4};case"layout":case"auth":return{min:3,max:5};case"deploy":return{min:1,max:2};default:return{min:4,max:6}}}function ym(t){let e=[];if(e.push("### Design choices (decided at plan time \u2014 follow these exactly):"),t.tone&&e.push(`- **App tone**: ${t.tone}`),t.fonts&&(e.push(`- **Heading font**: ${t.fonts.heading} (load from Google Fonts)`),e.push(`- **Body font**: ${t.fonts.body} (load from Google Fonts)`)),e.push("- **All color comes from CSS variables** \u2014 never use Tailwind palette utilities like `bg-emerald-500`, `text-blue-600`, `border-slate-200`. Use `bg-primary`, `text-primary-foreground`, `bg-muted`, `text-muted-foreground`, `border-border`, `bg-card`, `text-foreground`, `bg-destructive`, etc. The primary/accent is already wired in globals.css from the chosen design system."),e.push("- **Outbound URLs (invite links, email CTAs, absolute hrefs in server code):** use `process.env.BETTER_AUTH_URL` as the base. Do NOT use `process.env.NEXT_PUBLIC_APP_URL` \u2014 Next.js bakes every `NEXT_PUBLIC_*` literal into the production bundle at build time, so the scaffolded localhost default ships to prod and invite emails land on `http://localhost:3000`. Client components should use relative URLs (they resolve to the current origin automatically)."),t.borderRadius){let r={sharp:"2px",subtle:"6px",rounded:"12px",pill:"9999px"};e.push(`- **Border radius**: ${t.borderRadius} (${r[t.borderRadius]??t.borderRadius}) \u2014 set as --radius in globals.css`)}if(t.shadowStyle){let r={flat:"No shadows \u2014 use borders for separation",subtle:"shadow-sm on cards, shadow on hover",elevated:"shadow-md on cards, shadow-lg on modals, layered depth",dramatic:"shadow-lg with colored tinting, bold depth"};e.push(`- **Shadow style**: ${t.shadowStyle} \u2014 ${r[t.shadowStyle]??t.shadowStyle}`)}if(t.cardStyle){let r={filled:"bg-card with subtle background fill, no visible border",bordered:"border border-border on white/transparent background",elevated:"bg-card shadow-md, no border, lifted appearance",glass:"bg-white/60 backdrop-blur-sm border border-white/20, translucent"};e.push(`- **Card style**: ${t.cardStyle} \u2014 ${r[t.cardStyle]??t.cardStyle}`)}if(t.landingTone&&e.push(`- **Landing page tone**: ${t.landingTone}`),t.visualStrategy){let r=t.visualStrategy,n=t.heroPhoto!==!1,o=n&&!!r.heroImages?.length,s=!!r.sectionImages?.length;if(e.push(""),e.push("### Visual strategy:"),o&&r.heroImages&&r.heroImages.length>0){e.push("**Hero image fallback** \u2014 use this Unsplash photo as the landing page hero BACKGROUND when `/public/images/hero.png` is missing, pending, or visibly worse than the photo:");let a=r.heroImages[0];e.push(`- URL: ${a.url}`),e.push(`- Alt text for img tag: "${a.alt||"Hero image"} \u2014 Photo by ${a.photographer} on Unsplash"`),e.push("- Use as full-bleed background behind the entire hero section with a dark overlay (bg-black/60 or similar for readability). The photo gives the page real atmosphere and context; pair it with a domain-specific product/mockup card when this is a SaaS/tool/customer app.")}else n&&!r.heroImages?.length?e.push("**Hero background** \u2014 no Unsplash hero image was fetched. Do NOT shrink the page into a text/card-only dashboard hero. Keep a generous media/scene region in the hero, use the picker `/images/hero.png` asset if it exists, otherwise use a large CSS scene placeholder and tell the user imagery is pending."):n||e.push("**Hero background** \u2014 user opted for CSS-only (no photo). Use the design preset's specified gradient, animated background, or glassmorphism. No Unsplash.");if(s&&r.sectionImages){e.push("**Section image fallbacks available** \u2014 use these for feature sections, about sections, or testimonial backgrounds when `/public/images/feature-*.png` is missing or pending:");for(let a of r.sectionImages)e.push(`- ${a.url} \u2014 alt: "${a.alt||"section image"} \u2014 Photo by ${a.photographer} on Unsplash"`)}(o||s)&&e.push("For image attribution: put photographer credit in the img alt text (already provided above) and add an HTML comment <!-- Images from Unsplash --> near the images. Do NOT add visible attribution text on the page."),e.push("**Marketing visual floor**: every public landing page needs a real visual anchor. Acceptable anchors are: `/images/{slot}.png` from the picker imagery pipeline, the Unsplash fallbacks above, a video/canvas/Three.js scene, or a large domain-specific product UI mockup. A cramped hero made only of text, icons, and small cards is a failed implementation."),e.push("**Minimal hero rule**: the first viewport should have one clear headline, one short supporting sentence, one primary CTA plus at most one secondary action, and one generous visual/scene anchor. Do NOT add a stats row, feature-card cluster, benefit checklist, or extra explanatory paragraphs unless they appear in the picked render or layout spec."),e.push("**Glassmorphism is allowed when selected**: if the picked direction, DESIGN.md, or cardStyle/texture says `glass`, `glassmorphic`, or liquid glass, use restrained frosted panels for the hero visual, nav, or one focal card. Do not spray glass on every card, and do not use glass as the unselected default.");let i=ma(t);i?(e.push(""),e.push(`### Page composition \u2014 ${i.name} archetype`),e.push(`This plan was classified as **${i.id}** \u2014 ${i.description}. The layouts below (landing, dashboard, lists, detail, forms) are PRESCRIPTIVE for this category of app. Follow them. Do not reach for the generic split-hero + frosted SaaS mockup pattern by default \u2014 that template was retired because it produces the same cold hero regardless of the app's intent. If the selected direction explicitly calls for glassmorphism, keep it as a restrained texture on one focal surface while preserving the archetype's composition.`),e.push(""),e.push(i.content)):(e.push(""),e.push("**No archetype was selected for this plan.** Fall back to a split-hero layout, but if this app clearly fits a category (personal/wellness, booking, B2B SaaS, marketplace, etc.), flag it to the user \u2014 the plan should have picked one of the ten archetypes in archetypes.ts."),e.push(""),e.push("**Hero layout \u2014 split hero with product UI mockup (last-resort fallback):**"),e.push("The hero uses a spacious split layout with text on the left and a product preview on the right. Keep it minimal: no stats row or benefit checklist unless the plan includes real proof points."),e.push(""),e.push("```"),e.push("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"),e.push("\u2502 [Logo] [Sign In] [CTA \u2192] \u2502 \u2190 transparent nav, NOT sticky"),e.push("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"),e.push("\u2502 \u2502"),e.push("\u2502 \u25CF Built for [audience] \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502"),e.push("\u2502 \u2502 Glassmorphic \u2502 \u2502"),e.push("\u2502 Big bold headline, \u2502 product mockup \u2502 \u2502"),e.push("\u2502 accent color on key word \u2502 card showing \u2502 \u2502"),e.push("\u2502 \u2502 real app data \u2502 \u2502"),e.push("\u2502 Description paragraph \u2502 (stats, table, \u2502 \u2502"),e.push("\u2502 \u2502 chart, etc.) \u2502 \u2502"),e.push("\u2502 [Primary CTA \u2192] [Secondary] \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502"),e.push("\u2502 \u2502"),e.push("\u2502 (Optional proof row only when the plan has real proof) \u2502"),e.push("\u2502 \u2502"),o?e.push("\u2502 \u2190 full-bleed photo bg + dark overlay behind all \u2192 \u2502"):e.push("\u2502 \u2190 preset gradient / glass background behind all \u2192 \u2502"),e.push("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"),e.push("```"),e.push(""),e.push("**Left side (~55%)**: bold headline (use accent color on ONE key word or phrase) \u2192 one short description sentence \u2192 primary CTA plus optional secondary text/link. Omit badge pills, stats rows, and feature bullets unless they are in the picked render or plan proof points."),e.push("**Right side (~45%)**: A floating card that previews what the app looks like inside. Build realistic fake app data using styled divs \u2014 stat cards, mini table rows, schedule blocks, or charts that match what this specific app's dashboard shows. Must be DOMAIN-SPECIFIC."),e.push("**Proof row**: optional only when the plan contains real proof points. Do not invent decorative `500+ / 25K+ / 99%` stats to fill space."))}return e.join(`
7365
+ `)}function bm(t){let e=[];return e.push("### Landing layout (Plan G IR \u2014 STRUCTURAL AUTHORITY for this page):"),e.push(""),e.push("Build the landing page with EXACTLY these sections, in this order. Each section's `type` names a primitive \u2014 render it using the picked direction's fonts + colors + shape language. The `intent` explains WHY the section exists; the `props` are the content."),e.push(""),e.push(`- **Page intent**: ${t.page_intent}`),e.push(`- **Narrative arc**: ${t.narrative_arc}`),e.push(`- **Audience**: ${t.audience_persona}`),t.primary_cta&&e.push(`- **Primary CTA**: \`${t.primary_cta.label}\` \u2014 ${t.primary_cta.intent}`),e.push(""),e.push("**Sections (build in order):**"),e.push(""),t.sections.forEach((r,n)=>{let o=n+1;e.push(`${o}. \`${r.type}\` (id: \`${r.id}\`, role: \`${r.narrative_role}\`)`),e.push(` - **Intent**: ${r.intent}`),r.answers_section_id&&e.push(` - **Answers**: section \`${r.answers_section_id}\` (use visual cues \u2014 paired typography, matching accent, sequential numbering \u2014 to make the relationship readable)`),e.push(" - **Props** (JSON):"),e.push(" ```json");let s=JSON.stringify(r.props,null,2).split(`
7660
7366
  `).map(i=>` ${i}`);e.push(...s),e.push(" ```"),e.push("")}),e.push("**Rules:**"),e.push("- Render sections in the order above. Do not add, remove, or reorder."),e.push("- Each section's `type` is a known primitive \u2014 implement it as a real component with the props as content. Field names in `props` are descriptive of the role (e.g. `hero_magazine.pullquote` is an italicized 15-40 word quote with a left-rule mark)."),e.push("- Use the picked direction's `fonts.display` / `fonts.body` / `colors.bg/fg/accent` / `shape_lang` (border radius scale) / `texture` (background overlay) \u2014 those are in the design choices block above."),e.push("- The IR is the structural authority for THIS page; landing-rules.md still applies for anti-slop / motion choreography / general quality. If they conflict on structure, the IR wins."),e.push("- Don't invent placeholder content \u2014 every `props` value here is what the section should display."),e.join(`
7661
- `)}async function cm(t){try{let e=await cr("nextjs",t);return{reminders:e.reminders,skill:e.skill}}catch{return{reminders:`### ${t} step
7367
+ `)}async function wm(t){try{let e=await uo("nextjs",t);return{reminders:e.reminders,skill:e.skill}}catch{return{reminders:`### ${t} step
7662
7368
  - Follow existing patterns in the codebase
7663
- - Server Components by default, "use client" only when interactivity is needed`,skill:""}}}async function dm(t,e,o,n,r,s){let i=[];i.push(`## Step ${t.number}: ${t.name??t.title??"(untitled)"}`),i.push(""),i.push("### What to build:"),i.push(t.description),i.push(""),e.primaryAction&&(i.push("### Primary user action (non-negotiable):"),i.push(`- **Core action**: ${e.primaryAction.action}`),i.push(`- **User flow**: ${e.primaryAction.flow}`),i.push(`- **Dashboard must show**: ${e.primaryAction.dashboardSurface}`),i.push(""),i.push("The dashboard is an ACTION surface, not a stats display. Users must be able to complete the core action directly from the dashboard without navigating to a separate page. If this step builds the dashboard, make sure the primary action is front and center with inline forms/buttons \u2014 not behind a link to another page."),i.push(""));let l=["landing","design","dashboard","crud","layout","admin","general","auth"].includes(r);if(e.design&&l){i.push(am(e.design)),i.push("");let C=s?Qe(s,".mistflow","rules","design-quality.md"):null;C&&It(C)?(i.push("### Design quality rules (non-negotiable):"),i.push("**Read `.mistflow/rules/design-quality.md` BEFORE writing any code for this step.** That file contains the full rule set (shadcn usage, routes, typography, color, layout, motion, responsive, UX writing, cognitive load, production hardening, anti-patterns). It's been written to disk to keep this prompt under the per-tool-result token cap \u2014 read it once, then write your code."),i.push("")):(i.push(wo),i.push(""))}else e.design&&!l&&(i.push("### Design tokens (for reference only \u2014 this step is not UI-focused):"),e.design.fonts&&i.push(`- Fonts: ${e.design.fonts.heading} / ${e.design.fonts.body}`),i.push("- Colors come from CSS variables (`--color-primary`, `--color-background`, etc.) \u2014 not Tailwind palette names."),i.push(""));if(s){let C=Zs(s);if(C.length>0){i.push("### Approved wireframe (MUST READ before writing any files):"),i.push("The user approved a wireframe sketch before building. **Read these files NOW before writing any code for this step:**");for(let x of C){let w=x.replace(s,"").replace(/^\//,"");i.push(`- \`${w}\``)}i.push(""),i.push("The wireframe defines the LAYOUT and INFORMATION HIERARCHY \u2014 what goes where, what's prominent, what's secondary. It includes HTML comments explaining WHY things are placed where they are."),i.push(""),i.push("The wireframe is intentionally rough (grayscale, system fonts). Your job is to:"),i.push("1. **Keep the same layout structure** \u2014 same information hierarchy, same element placement, same sections in the same order"),i.push("2. **Apply the design tokens** \u2014 colors, fonts, shadows, radius from the plan design choices above"),i.push("3. **Elevate the visual quality** \u2014 make it feel designed for THIS specific app, not generic"),i.push("4. **Respect the HTML comments** \u2014 they explain WHY things are placed where they are"),i.push("5. **If the wireframe shows a search bar at the top of the dashboard, your dashboard MUST have a search bar at the top** \u2014 do not rearrange the layout"),i.push("")}}e.roles&&Array.isArray(e.roles)&&e.roles.length>0&&(i.push("### Role system (from plan):"),i.push(`- Roles: ${e.roles.join(", ")}`),i.push(`- Default role for new signups: ${e.defaultRole??e.roles[0]}`),i.push("- Role helpers are in `lib/roles.ts` \u2014 use `getUserRole()` and `hasRole()` for access checks"),i.push("")),e.multiTenant&&(i.push("### Multi-tenant (from plan):"),i.push("- Organization tables are in `db/schema/organization.ts`"),i.push("- Org helpers are in `lib/org.ts` \u2014 use `getCurrentOrg()` to scope queries"),i.push("- All data queries MUST be scoped to the current org (filter by orgId)"),i.push("- Org switcher component is at `components/org-switcher.tsx`"),i.push("- CRITICAL: `getCurrentOrg()` returns null for new users who haven't created an org yet. The dashboard MUST handle this \u2014 if currentOrg is null, redirect to an onboarding page or show an inline 'Create your first team/workspace' form. NEVER call .id on a null org."),i.push("- WARNING: cookies().set() in server actions does NOT work on Mistflow Cloud's edge runtime. Do NOT use setCurrentOrgId() or any cookies().set() call inside server actions. Instead, pass the orgId as a form field or query param, or store the active org in the user's database record."),i.push("")),e.language&&(i.push(`### Language: ${e.language}`),i.push(`ALL user-facing text must be written in ${e.language}:`),i.push("- Page titles, headings, labels, button text, placeholder text"),i.push("- Navigation items, menu labels, footer text"),i.push("- Error messages, success messages, empty states"),i.push("- Landing page copy, marketing text, CTAs"),i.push("- Form labels and validation messages"),i.push("Code (variable names, comments, file names) stays in English."),i.push(`Set the HTML lang attribute to the appropriate locale code for ${e.language}.`),i.push(""));let c=["landing","design","auth","general","crud","dashboard"];e.audienceType&&c.includes(r)&&(e.audienceType==="b2c"?(i.push("### Audience: this app belongs to ONE business. The landing page talks TO their customers."),i.push("- Hero: what the customer gets ('Exceptional catering for your next event'), NOT what the tool does"),i.push("- CTAs: customer action ('Order Catering', 'Book Now'), NOT business action ('Get Started Free')"),i.push("- Testimonials: from customers ('They catered our wedding'), NOT from business owners"),i.push("- Features: customer benefits ('Specify your dietary needs'), NOT business benefits ('Track preferences')"),i.push("- Stats: social proof for customers ('2,400+ events served'), NOT internal metrics ('$48k revenue')"),i.push("- The business name IS the brand. Say it like a business homepage, not a SaaS onboarding."),i.push("")):e.audienceType==="b2b"?(i.push("### Audience: this is a SaaS platform. The landing page pitches TO business owners."),i.push("- Hero: the business pain + solution ('Catering orders managed in one place')"),i.push("- CTAs: business owner action ('Start Free Trial', 'Get Started')"),i.push("- Testimonials: from business owners who use the platform"),i.push("- Features: business benefits ('Track dietary preferences across all orders')"),i.push("- Stats: platform metrics ('500+ businesses', '50K+ orders processed')"),i.push("")):e.audienceType==="internal"&&(i.push("### Audience: internal staff tool. No marketing copy needed."),i.push("- No landing page. Auth page copy is functional: 'Sign in to continue'."),i.push("- Dashboard focuses on operational efficiency, not onboarding or sales."),i.push(""))),o.length>0&&(i.push("### Already completed:"),o.forEach(C=>i.push(`- ${C}`)),i.push(""));let m=e.dataModel?La(t,e.dataModel):[];m.length>0&&(i.push("### Data model (from plan):"),m.forEach(C=>{let x=Ao(C),w=tm(C.fields);i.push(`- **${x}**: ${w}`),i.push(` Schema file: \`db/schema/${x.toLowerCase().replace(/\s+/g,"-")}.ts\``)}),i.push(""));let u=e.pages?om(t,e.pages):[];if(u.length>0&&(i.push("### Pages to create/update:"),u.forEach(C=>{let x=C.description?` \u2014 ${C.description}`:"";i.push(`- \`${Da(C)}\`${x}`)}),i.push("")),r==="crud"&&m.length>0&&m.forEach(C=>{let x=Ao(C),w=x.toLowerCase().replace(/\s+/g,"-"),A=w.endsWith("s")?w:`${w}s`;i.push(`### Files for ${x} CRUD:`),i.push(`- List page: \`app/(dashboard)/${A}/page.tsx\` (Server Component)`),i.push(`- Detail page: \`app/(dashboard)/${A}/[id]/page.tsx\``),i.push(`- Create page: \`app/(dashboard)/${A}/new/page.tsx\``),i.push(`- Server Actions: \`app/(dashboard)/${A}/actions.ts\``),i.push(`- DataTable columns: \`components/${w}-table-columns.tsx\``),i.push(`- Form: \`components/${w}-form.tsx\``),i.push("")}),l){i.push("## Design Doctrine (the standard for every UI step)"),i.push(""),i.push(Ia),i.push(""),i.push("## Design Reference Library"),i.push(""),i.push("### Typography"),i.push(Pa),i.push(""),i.push("### Color"),i.push(Ra),i.push(""),i.push("### Motion"),i.push(Aa),i.push(""),i.push("### Spatial Composition"),i.push(Ea),i.push(""),i.push("### Interaction"),i.push(Na),i.push(""),i.push("### UX Writing"),i.push(Oa),i.push("");let C=s?Qe(s,"DESIGN.md"):void 0,x=C&&It(C)?(()=>{try{return Kr(C,"utf-8")}catch{return null}})():null;x&&(i.push("### Design system (source of truth: DESIGN.md):"),i.push(""),i.push("The project's DESIGN.md defines the visual identity. Follow it exactly. It's emitted in google-labs-code/design.md format (YAML front matter with colors/typography/rounded/spacing tokens, plus markdown rationale). All UI elements must use the project's CSS custom properties (--color-primary, --color-background, etc. \u2014 these are generated from the YAML tokens) and the fonts configured in layout.tsx. If DESIGN.md and this plan disagree, DESIGN.md wins. The user may have edited it."),i.push(""),i.push(x),i.push(""));let w=s?Qe(s,".mistflow","picker-render.html"):void 0;(r==="landing"||r==="design")&&w&&It(w)&&(i.push("### Picked landing page render \u2014 ground-truth design contract"),i.push(""),i.push("The user picked their direction from a **fully-rendered HTML preview** of this landing page. That render is at `.mistflow/picker-render.html` (relative to the project root). **Read it** \u2014 it is the design contract the user agreed to."),i.push(""),i.push("Your job for this step is **mechanical translation**: convert that standalone HTML to Next.js Server Components + Client Components in `app/home/page.tsx` (and `components/landing/*.tsx` for any sections you split out). You are NOT redesigning. The user already picked the design \u2014 colors, fonts, composition, copy, and section order are LOCKED."),i.push(""),i.push("How to translate:\n1. **Preserve every voice sample exactly.** The H1 in the rendered HTML is the headline the user picked. The CTA button text is the CTA the user picked. Do NOT paraphrase, shorten, or 'improve' them. The picker is a contract.\n2. **Preserve the section order.** Each `<section data-section-id=\"X\">` in the rendered HTML maps to a TSX section in the SAME order. You may split each section into its own component file (`components/landing/{section-id}.tsx`) for readability \u2014 that's the only structural transformation allowed.\n3. **Migrate inline `<style>` to globals.css**. The render uses one inline `<style>` block; move those rules to `app/globals.css` so the rest of the app shares the design tokens. CSS custom properties (`--color-bg` / `--color-fg` / `--color-accent`) become the canonical design tokens.\n4. **Tailwind utilities are allowed**, but the picker's CSS custom properties (`--color-bg`, `--color-fg`, `--color-accent`) MUST remain the source of truth for color \u2014 reference them via `bg-[var(--color-bg)]` or define semantic classes in globals.css, never replace them with `bg-slate-900` or other Tailwind palette swatches. For type sizes and spacing that don't land on Tailwind's ladder, use arbitrary values (`text-[56px]`, `py-[28px]`) \u2014 do not snap to the nearest rung, the picker render is the design contract and the deployed page must match pixel-for-pixel on type, spacing, and color.\n5. **Image slots:** each `<img data-image-slot=\"hero|feature-1|feature-2|feature-3|og|favicon\">` in the render points at picsum placeholder URLs. Replace each `src` with `/images/{slot}.png` \u2014 those files arrive in `public/images/` from the imagery pipeline (see Plan F A.6.1; check `mist_project action='imagery'` for status during the build). For the favicon slot, also wire `app/icon.png` to the same asset.\n6. **Do not add or remove sections.** If the render has 6 sections, the deployed page has 6 sections in the same order. Adding a CTA the user didn't pick is a bug; dropping a feature card the user did pick is also a bug.\n"),i.push("**Two things you MUST add (the picker render is iframe-static and cannot have these \u2014 the deployed page can):**\n7. **Motion.** The picker render has zero JavaScript and at most a decorative CSS `marquee` keyframe. The deployed page must add real entrance choreography using the `motion` package (Framer Motion, already in package.json). Pick ONE consistent style for the whole page: hero entrance reveal on mount (per-word stagger, line-by-line slide, scale-in, or blur-in), scroll-triggered section reveals (`whileInView` with `viewport={{ once: true }}`), and a card hover interaction. See `.mistflow/rules/landing.md` Section 5 (Motion Menu) for the picker \u2014 pick one style and apply it consistently. Without motion, the deployed page reads as dead next to a polished competitor's landing.\n8. **Hero fits the laptop fold.** The picker iframe is sized 1366\xD7768 and the validator caps hero font-size + section padding, but the deployed Next.js app must ALSO ensure the primary CTA is visible at 1280\xD7800 without scrolling. Cap hero vertical padding at `py-16 lg:py-24` (NOT `py-32` or above), never apply `min-h-screen` to the hero section, cap H1 at `lg:text-6xl` (60px \u2014 NEVER `lg:text-7xl`, `lg:text-8xl`, or `lg:text-9xl`, those overflow the fold on 13\" laptops), and limit hero copy to \u22643 lines on desktop. If translating the picker faithfully would push the CTA below the fold (e.g. picker has a tall stacked image block below the headline), tighten the layout to fit instead of preserving the overflow."),i.push("Bar: a designer comparing the picker iframe and the deployed /home page side by side should not be able to tell them apart on type, color, copy, and composition. The deployed page should clearly FEEL more alive (motion) and the hero must fit a laptop fold even if the picker render didn't quite. Everything beyond motion + fold-fit is mechanical translation; redesigning type, color, copy, or section order belongs in a separate refinement step the user explicitly asks for."),i.push(""))}if(r==="landing"||r==="design"){i.push("### File location for the landing page (non-negotiable):"),i.push('Write the landing page to **`app/home/page.tsx`** \u2014 NOT `app/page.tsx`. The scaffold\'s `middleware.ts` 308-redirects `/` to `/home` as a workaround for an OpenNext Cloudflare bug. Visitors land at `/` and end up on `/home` transparently. Internal links like `<Link href="/">` still work because the middleware does the redirect on every request.'),i.push(""),i.push("### Landing page hard requirements (non-negotiable):"),i.push('**Motion is mandatory.** Import `motion` from the `motion` package (Framer Motion, already in package.json \u2014 `import { motion } from "motion/react"`). Every landing page must have: (1) a hero entrance reveal on mount (per-word stagger, line-by-line slide, scale-in, or blur-in \u2014 pick ONE), (2) scroll-triggered section reveals using `whileInView` with `viewport={{ once: true }}`, (3) one card hover interaction. Pick a single motion style and apply it consistently. A static landing page is a regression \u2014 without motion the deployed page reads as dead next to a polished competitor\'s. See `.mistflow/rules/landing.md` Section 5 (Motion Menu) for the full vocabulary.'),i.push('**Hero must fit the laptop fold.** The primary CTA must be visible at 1280\xD7800 without scrolling. Cap hero section vertical padding at `py-16 lg:py-24` (NOT `py-32` or above). Never apply `min-h-screen` to the hero section. Cap H1 at `lg:text-6xl` (60px \u2014 NEVER `lg:text-7xl`, `lg:text-8xl`, or `lg:text-9xl`, those overflow the fold on 13" laptops). Keep headline copy \u22643 lines on desktop. If the layout would push the CTA below the fold, tighten the composition instead of preserving it.'),i.push("");let C=s?Qe(s,".mistflow","rules","landing.md"):null;C&&It(C)?(i.push("### Landing page rules:"),i.push("**Read `.mistflow/rules/landing.md` BEFORE writing the landing page.** That file contains the full conversion structure, section catalog, motion menu, and anti-slop audit. Written to disk to keep this prompt under the per-tool-result token cap \u2014 read once, then write your code."),i.push("")):(i.push(vo),i.push(""))}r==="landing"&&e.layoutSpec&&Array.isArray(e.layoutSpec.sections)&&(i.push(lm(e.layoutSpec)),i.push(""));let p=t.integrationId?Yt(t.integrationId):void 0;if(p){let C=Qt(p.id);if(i.push("### Integration blueprint (follow this closely):"),i.push(""),i.push(`Using integration: **${p.name}** (${p.category})`),C?.docsUrl&&i.push(`Official docs: ${C.docsUrl}`),C?.envVars?.length){i.push(""),i.push("**Required environment variables:**");for(let x of C.envVars)i.push(`- \`${x.key}\`: ${x.description} \u2014 Get it at ${x.setupUrl}`);i.push(""),i.push("**IMPORTANT: Never ask the user to paste API keys in chat.** Direct them to set keys in the Mistflow dashboard (Project Settings > Environment Variables) or at app.mistflow.ai. Use mist_config resource='env' action='list' to check if keys are set (has_value: true/false). Only proceed with deploy once all required keys are set.")}C?.packages?.length&&(i.push(""),i.push(`**Packages to install:** \`npm install ${C.packages.join(" ")}\``)),i.push(""),i.push("---"),i.push(p.prompt),i.push("---"),i.push(""),i.push("**Adaptation rules**: Follow the file structure and code patterns above. Replace placeholder values (app names, URLs, copy) with this app's specific content. Use the app's existing data models and route structure. Never ask the user to paste API keys or secrets in the chat. Direct them to set keys in the Mistflow dashboard."),i.push("")}let{reminders:h,skill:U}=await cm(r);return i.push(h),i.push(""),U&&(i.push(`### ${r} reference:`),i.push(U),i.push("")),l&&(i.push("## Self-Audit \u2014 run before submitting this file"),i.push(""),i.push(`Read the file you just wrote and answer each of these. If any answer is "yes", REDESIGN the failing piece \u2014 don't tweak classes on the same template. A different composition is required.`),i.push(""),i.push('1. **Pill badge at the top?** A small rounded label saying "Built for" / "New:" / "Made for" with a colored dot? \u2192 redesign the hero without it.'),i.push("2. **Fake browser chrome?** A rounded box with red/yellow/green dots and a fake URL bar? \u2192 delete it. Show real components instead, or no preview."),i.push("3. **Centered single-column hero?** Pill \u2192 headline \u2192 subhead \u2192 two CTAs stacked vertically? \u2192 redesign asymmetrically (see spatial.md)."),i.push("4. **Three checkmark benefit bullets** below the hero CTAs? \u2192 remove the triplet pattern."),i.push("5. **Tailwind palette utilities?** Any `bg-emerald-*`, `bg-amber-*`, `text-violet-*`, `border-slate-*`, `from-purple-*`, etc.? \u2192 replace with token classes (`bg-primary`, `bg-success`, `bg-muted`, `border-border`). No exceptions."),i.push("6. **Inter, Geist, Roboto, Arial, or system-ui** as the primary font? \u2192 pick something distinctive (see typography.md)."),i.push("7. **Purple gradient on white background** anywhere? \u2192 that's the single most overused AI-design pattern. Replace."),i.push('8. **Hardcoded hex values** (e.g. `style={{ color: "#10b981" }}`) or named CSS colors (`red`, `lightgrey`) in className or inline styles? \u2192 replace with CSS variables.'),i.push('9. **Generic copy** ("Boost your productivity", "The platform teams love", "Everything you need to")? \u2192 rewrite with specific nouns, numbers, or mechanisms from this product (see writing.md).'),i.push("10. **Zero memorable moments?** Is there ONE signature element \u2014 typographic hero, orchestrated reveal, asymmetric break, product-specific illustration? If zero, the page is generic. Add one (see motion.md, typography.md)."),i.push(""),i.push(`If every answer is "no, I'm good" \u2014 then submit.`),i.push("")),i.join(`
7664
- `)}async function um(t){let{projectPath:e,step:o,envVarsRequired:n}=t,r=Vp(e??process.cwd());if(n&&n.length>0)try{let v=Po(r,n);v.added.length>0&&console.error(`[implement] declared env.required: ${v.added.join(", ")}`)}catch(v){console.error("[implement] env var merge skipped:",v instanceof Error?v.message:String(v))}let s=em(r);if(!s)return Ve(r);if(!It(Qe(r,"node_modules")))return d(`Dependencies are not installed at ${r}. Call mist_install and projectPath='${r}' before running implement. This is a one-time setup step after init.`,!0);try{let{ensureBackendRegistered:v,ensureShadcnComponents:F}=await Promise.resolve().then(()=>(Sr(),xr)),j=await v(r);j&&!s.projectId&&(s.projectId=j);let R=await F(r);R.failed?console.error(`[implement] ${R.failed}`):R.installed.length>0&&console.error(`[implement] installed ${R.installed.length} shadcn components`);let{selfHealAgentUi:he}=await Promise.resolve().then(()=>(jr(),xi)),T=he(s.plan,r);T&&console.error(`[implement] ${T.isError?"warn: ":""}${T.message}`)}catch(v){console.error("[implement] self-heal skipped:",v instanceof Error?v.message:String(v))}let i=s.plan;if(!i||!i.steps||i.steps.length===0)return d("No project plan found. Start by describing your app idea first \u2014 the AI will create a plan for you.",!0);let a,l=i.steps.find(v=>v.status==="in_progress");if(l){let v=i.steps.findIndex(F=>F.number===l.number);v!==-1&&(i.steps[v].status="completed",a=`Auto-completed step ${l.number} (${l.name})`,ja(r,s))}let c;if(o!==void 0){if(c=i.steps.find(v=>v.number===o),!c)return d(`Step ${o} not found. The plan has ${i.steps.length} steps (numbered ${i.steps[0].number} to ${i.steps[i.steps.length-1].number}).`,!0)}else if(c=i.steps.find(v=>v.status!=="completed"),!c)return d(JSON.stringify({message:"All plan steps are completed!",completedSteps:i.steps.map(v=>v.name),nextAction:"NEXT: Deploy the app now. Call mist_deploy action='deploy'. Do NOT suggest localhost or ask the user \u2014 just deploy."}));let m=i.steps.filter(v=>v.status==="completed").map(v=>`Step ${v.number}: ${v.name}`),{readLocalState:u}=await Promise.resolve().then(()=>(jt(),Tn)),p=u(r),h=sm(c),U=[];if((h==="crud"||h==="schema")&&i.dataModel&&c.entities&&c.entities.length>0){let v=La(c,i.dataModel);for(let F of v){let j=Ao(F);try{let R=(F.fields||[]).map(ye=>typeof ye=="string"?{name:ye,type:"text"}:{name:ye.name,type:nm(ye.type),required:ye.required!==!1});if(R.length===0)continue;let he=s.dbProvider==="neon"?"nextjs-neon":"nextjs",T=await pr(he,j,R),$e=0,G=0;for(let ye of T.files){let xe=Qe(r,ye.path);if(It(xe)){G++;continue}Ma(Yp(xe),{recursive:!0}),Ro(xe,ye.content),$e++}let ie=Qe(r,"db","index.ts");if(It(ie)){let ye=Kr(ie,"utf-8");ye.includes(T.dbExport)||Ro(ie,ye.trimEnd()+`
7665
- `+T.dbExport+`
7666
- `)}$e>0?U.push(`${T.entityPascal} CRUD (${$e} new files${G>0?`, ${G} existing skipped`:""})`):G>0&&U.push(`${T.entityPascal} CRUD (all ${G} files already exist \u2014 skipped)`)}catch(R){console.error(`Module generation failed for ${j} (non-fatal):`,R instanceof Error?R.message:R)}}}let C=await dm(c,i,m,null,h,r),x=3e4,w=null;if(C.length>x){let v=Qe(r,".mistflow","instructions");try{Ma(v,{recursive:!0});let F=`step-${c.number}.md`,j=Qe(v,F);Ro(j,C,"utf-8"),w=`.mistflow/instructions/${F}`,C=[C.slice(0,5e3),"","---",`**Instruction continues in ${w}** (${C.length.toLocaleString()} chars total).`,"",`Read the full file before writing code: \`Read(${w})\`. The truncated portion above contains step framing; the file contains the detailed prescriptive guidance, layout specs, design tokens, and per-section instructions.`].join(`
7667
- `)}catch(F){console.error("implement-step: failed to write overflow instruction file:",F instanceof Error?F.message:F),C=C.slice(0,x-500)+"\n\n---\n**Instruction was truncated** to fit the tool-result size cap. Detailed guidance for this step (design tokens, layout spec, anti-slop rules) lives in `.mistflow/rules/` and `methodologies/` \u2014 read those for the full context."}}let A=i.steps.findIndex(v=>v.number===c.number);if(A!==-1&&(s.plan.steps[A].status="in_progress",ja(r,s)),p&&s.projectId){let{syncRemoteState:v}=await Promise.resolve().then(()=>(jt(),Tn));v(s.projectId,p).catch(()=>{})}let J=i.steps.every(v=>v.status==="completed"||v.number===c.number),V;J?V=`THIS IS THE LAST STEP. Rules for speed:
7369
+ - Server Components by default, "use client" only when interactivity is needed`,skill:""}}}async function vm(t,e,r,n,o,s){let i=[];i.push(`## Step ${t.number}: ${t.name??t.title??"(untitled)"}`),i.push(""),i.push("### What to build:"),i.push(t.description),i.push(""),e.primaryAction&&(i.push("### Primary user action (non-negotiable):"),i.push(`- **Core action**: ${e.primaryAction.action}`),i.push(`- **User flow**: ${e.primaryAction.flow}`),i.push(`- **Dashboard must show**: ${e.primaryAction.dashboardSurface}`),i.push(""),i.push("The dashboard is an ACTION surface, not a stats display. Users must be able to complete the core action directly from the dashboard without navigating to a separate page. If this step builds the dashboard, make sure the primary action is front and center with inline forms/buttons \u2014 not behind a link to another page."),i.push(""));let l=["landing","design","dashboard","crud","layout","admin","general","auth"].includes(o);if(e.design&&l){i.push(ym(e.design)),i.push("");let I=s?nt(s,".mistflow","rules","design-quality.md"):null;I&&Rt(I)?(i.push("### Design quality rules (non-negotiable):"),i.push("**Read `.mistflow/rules/design-quality.md` BEFORE writing any code for this step.** That file contains the full rule set (shadcn usage, routes, typography, color, layout, motion, responsive, UX writing, cognitive load, production hardening, anti-patterns). It's been written to disk to keep this prompt under the per-tool-result token cap \u2014 read it once, then write your code."),i.push("")):(i.push(wr),i.push(""))}else e.design&&!l&&(i.push("### Design tokens (for reference only \u2014 this step is not UI-focused):"),e.design.fonts&&i.push(`- Fonts: ${e.design.fonts.heading} / ${e.design.fonts.body}`),i.push("- Colors come from CSS variables (`--color-primary`, `--color-background`, etc.) \u2014 not Tailwind palette names."),i.push(""));if(s){let I=di(s);if(I.length>0){i.push("### Approved wireframe (MUST READ before writing any files):"),i.push("The user approved a wireframe sketch before building. **Read these files NOW before writing any code for this step:**");for(let y of I){let v=y.replace(s,"").replace(/^\//,"");i.push(`- \`${v}\``)}i.push(""),i.push("The wireframe defines the LAYOUT and INFORMATION HIERARCHY \u2014 what goes where, what's prominent, what's secondary. It includes HTML comments explaining WHY things are placed where they are."),i.push(""),i.push("The wireframe is intentionally rough (grayscale, system fonts). Your job is to:"),i.push("1. **Keep the same layout structure** \u2014 same information hierarchy, same element placement, same sections in the same order"),i.push("2. **Apply the design tokens** \u2014 colors, fonts, shadows, radius from the plan design choices above"),i.push("3. **Elevate the visual quality** \u2014 make it feel designed for THIS specific app, not generic"),i.push("4. **Respect the HTML comments** \u2014 they explain WHY things are placed where they are"),i.push("5. **If the wireframe shows a search bar at the top of the dashboard, your dashboard MUST have a search bar at the top** \u2014 do not rearrange the layout"),i.push("")}}e.roles&&Array.isArray(e.roles)&&e.roles.length>0&&(i.push("### Role system (from plan):"),i.push(`- Roles: ${e.roles.join(", ")}`),i.push(`- Default role for new signups: ${e.defaultRole??e.roles[0]}`),i.push("- Role helpers are in `lib/roles.ts` \u2014 use `getUserRole()` and `hasRole()` for access checks"),i.push("")),e.multiTenant&&(i.push("### Multi-tenant (from plan):"),i.push("- Organization tables are in `db/schema/organization.ts`"),i.push("- Org helpers are in `lib/org.ts` \u2014 use `getCurrentOrg()` to scope queries"),i.push("- All data queries MUST be scoped to the current org (filter by orgId)"),i.push("- Org switcher component is at `components/org-switcher.tsx`"),i.push("- CRITICAL: `getCurrentOrg()` returns null for new users who haven't created an org yet. The dashboard MUST handle this \u2014 if currentOrg is null, redirect to an onboarding page or show an inline 'Create your first team/workspace' form. NEVER call .id on a null org."),i.push("- WARNING: cookies().set() in server actions does NOT work on Mistflow Cloud's edge runtime. Do NOT use setCurrentOrgId() or any cookies().set() call inside server actions. Instead, pass the orgId as a form field or query param, or store the active org in the user's database record."),i.push("")),e.language&&(i.push(`### Language: ${e.language}`),i.push(`ALL user-facing text must be written in ${e.language}:`),i.push("- Page titles, headings, labels, button text, placeholder text"),i.push("- Navigation items, menu labels, footer text"),i.push("- Error messages, success messages, empty states"),i.push("- Landing page copy, marketing text, CTAs"),i.push("- Form labels and validation messages"),i.push("Code (variable names, comments, file names) stays in English."),i.push(`Set the HTML lang attribute to the appropriate locale code for ${e.language}.`),i.push(""));let c=["landing","design","auth","general","crud","dashboard"];e.audienceType&&c.includes(o)&&(e.audienceType==="b2c"?(i.push("### Audience: this app belongs to ONE business. The landing page talks TO their customers."),i.push("- Hero: what the customer gets ('Exceptional catering for your next event'), NOT what the tool does"),i.push("- CTAs: customer action ('Order Catering', 'Book Now'), NOT business action ('Get Started Free')"),i.push("- Testimonials: from customers ('They catered our wedding'), NOT from business owners"),i.push("- Features: customer benefits ('Specify your dietary needs'), NOT business benefits ('Track preferences')"),i.push("- Stats: social proof for customers ('2,400+ events served'), NOT internal metrics ('$48k revenue')"),i.push("- The business name IS the brand. Say it like a business homepage, not a SaaS onboarding."),i.push("")):e.audienceType==="b2b"?(i.push("### Audience: this is a SaaS platform. The landing page pitches TO business owners."),i.push("- Hero: the business pain + solution ('Catering orders managed in one place')"),i.push("- CTAs: business owner action ('Start Free Trial', 'Get Started')"),i.push("- Testimonials: from business owners who use the platform"),i.push("- Features: business benefits ('Track dietary preferences across all orders')"),i.push("- Stats: platform metrics ('500+ businesses', '50K+ orders processed')"),i.push("")):e.audienceType==="internal"&&(i.push("### Audience: internal staff tool. No marketing copy needed."),i.push("- No landing page. Auth page copy is functional: 'Sign in to continue'."),i.push("- Dashboard focuses on operational efficiency, not onboarding or sales."),i.push(""))),r.length>0&&(i.push("### Already completed:"),r.forEach(I=>i.push(`- ${I}`)),i.push(""));let p=e.dataModel?Va(t,e.dataModel):[];p.length>0&&(i.push("### Data model (from plan):"),p.forEach(I=>{let y=Ar(I),v=um(I.fields);i.push(`- **${y}**: ${v}`),i.push(` Schema file: \`db/schema/${y.toLowerCase().replace(/\s+/g,"-")}.ts\``)}),i.push(""));let d=e.pages?mm(t,e.pages):[];if(d.length>0&&(i.push("### Pages to create/update:"),d.forEach(I=>{let y=I.description?` \u2014 ${I.description}`:"";i.push(`- \`${Ka(I)}\`${y}`)}),i.push("")),o==="crud"&&p.length>0&&p.forEach(I=>{let y=Ar(I),v=y.toLowerCase().replace(/\s+/g,"-"),E=v.endsWith("s")?v:`${v}s`;i.push(`### Files for ${y} CRUD:`),i.push(`- List page: \`app/(dashboard)/${E}/page.tsx\` (Server Component)`),i.push(`- Detail page: \`app/(dashboard)/${E}/[id]/page.tsx\``),i.push(`- Create page: \`app/(dashboard)/${E}/new/page.tsx\``),i.push(`- Server Actions: \`app/(dashboard)/${E}/actions.ts\``),i.push(`- DataTable columns: \`components/${v}-table-columns.tsx\``),i.push(`- Form: \`components/${v}-form.tsx\``),i.push("")}),l){i.push("## Design Doctrine (the standard for every UI step)"),i.push(""),i.push(Ua),i.push(""),i.push("## Design Reference Library"),i.push(""),i.push("### Typography"),i.push(Fa),i.push(""),i.push("### Color"),i.push(qa),i.push(""),i.push("### Motion"),i.push(Ba),i.push(""),i.push("### Spatial Composition"),i.push(Ha),i.push(""),i.push("### Interaction"),i.push(za),i.push(""),i.push("### UX Writing"),i.push(Wa),i.push("");let I=s?nt(s,"DESIGN.md"):void 0,y=I&&Rt(I)?(()=>{try{return Yo(I,"utf-8")}catch{return null}})():null;y&&(i.push("### Design system (source of truth: DESIGN.md):"),i.push(""),i.push("The project's DESIGN.md defines the visual identity. Follow it exactly. It's emitted in google-labs-code/design.md format (YAML front matter with colors/typography/rounded/spacing tokens, plus markdown rationale). All UI elements must use the project's CSS custom properties (--color-primary, --color-background, etc. \u2014 these are generated from the YAML tokens) and the fonts configured in layout.tsx. If DESIGN.md and this plan disagree, DESIGN.md wins. The user may have edited it."),i.push(""),i.push(y),i.push(""));let v=s?nt(s,".mistflow","picker-render.html"):void 0;(o==="landing"||o==="design")&&v&&Rt(v)&&(i.push("### Picked landing page render \u2014 ground-truth design contract"),i.push(""),i.push("The user picked their direction from a **fully-rendered HTML preview** of this landing page. That render is at `.mistflow/picker-render.html` (relative to the project root). **Read it** \u2014 it is the design contract the user agreed to."),i.push(""),i.push("Your job for this step is **mechanical translation**: convert that standalone HTML to Next.js Server Components + Client Components in `app/home/page.tsx` (and `components/landing/*.tsx` for any sections you split out). You are NOT redesigning. The user already picked the design \u2014 colors, fonts, composition, copy, and section order are LOCKED."),i.push(""),i.push("How to translate:\n1. **Preserve every voice sample exactly.** The H1 in the rendered HTML is the headline the user picked. The CTA button text is the CTA the user picked. Do NOT paraphrase, shorten, or 'improve' them. The picker is a contract.\n2. **Preserve the section order.** Each `<section data-section-id=\"X\">` in the rendered HTML maps to a TSX section in the SAME order. You may split each section into its own component file (`components/landing/{section-id}.tsx`) for readability \u2014 that's the only structural transformation allowed.\n3. **Migrate inline `<style>` to globals.css**. The render uses one inline `<style>` block; move those rules to `app/globals.css` so the rest of the app shares the design tokens. CSS custom properties (`--color-bg` / `--color-fg` / `--color-accent`) become the canonical design tokens.\n4. **Tailwind utilities are allowed**, but the picker's CSS custom properties (`--color-bg`, `--color-fg`, `--color-accent`) MUST remain the source of truth for color \u2014 reference them via `bg-[var(--color-bg)]` or define semantic classes in globals.css, never replace them with `bg-slate-900` or other Tailwind palette swatches. For type sizes and spacing that don't land on Tailwind's ladder, use arbitrary values (`text-[56px]`, `py-[28px]`) \u2014 do not snap to the nearest rung, the picker render is the design contract and the deployed page must match pixel-for-pixel on type, spacing, and color.\n5. **Image slots:** each `<img data-image-slot=\"hero|feature-1|feature-2|feature-3|og|favicon\">` in the render points at picsum placeholder URLs. Replace each `src` with `/images/{slot}.png` ONLY when the corresponding file exists in `public/images/`. If the slot file is missing, pending, or failed, use the Unsplash URLs in the Visual strategy block above as the public landing fallback. If neither exists, preserve the image/media area as a large domain-specific scene placeholder and tell the user imagery is pending \u2014 never delete the image section, never replace it with a tiny card-only mockup, and never ship picsum URLs. For the favicon slot, also wire `app/icon.png` when the asset exists.\n6. **Do not add or remove sections.** If the render has 6 sections, the deployed page has 6 sections in the same order. Adding a CTA the user didn't pick is a bug; dropping a feature card the user did pick is also a bug.\n7. **Keep the hero minimal.** Preserve the picked hero's structure, but do not add a stats row, benefit checklist, extra paragraph, or feature-card cluster to the first viewport. Hero copy is one H1 plus one short supporting sentence; CTAs are one primary plus at most one secondary action unless the picker render already contains more.\n"),i.push("**Two things you MUST add (the picker render is iframe-static and cannot have these \u2014 the deployed page can):**\n8. **Motion.** The picker render has zero JavaScript and at most a decorative CSS `marquee` keyframe. The deployed page must add real entrance choreography using the `motion` package (Framer Motion, already in package.json). Pick ONE consistent style for the whole page: hero entrance reveal on mount (per-word stagger, line-by-line slide, scale-in, or blur-in), section reveals, and a card hover interaction. Content must be visible by default before JavaScript runs: do not set critical sections to `initial={{ opacity: 0 }}` unless a hydrated state or CSS fallback makes them visible on Cloudflare. See `.mistflow/rules/landing.md` Section 5 (Motion Menu) for the picker \u2014 pick one style and apply it consistently. Without motion, the deployed page reads as dead next to a polished competitor's landing.\n9. **Hero fits the laptop fold.** The picker iframe is sized 1366\xD7768 and the validator caps hero font-size + section padding, but the deployed Next.js app must ALSO ensure the primary CTA is visible at 1280\xD7800 without scrolling. Cap hero vertical padding at `py-16 lg:py-24` (NOT `py-32` or above), never apply `min-h-screen` to the hero section, cap H1 at `lg:text-6xl` (60px \u2014 NEVER `lg:text-7xl`, `lg:text-8xl`, or `lg:text-9xl`, those overflow the fold on 13\" laptops), and limit hero copy to one short paragraph (\u226422 words, \u22643 lines on desktop). `py-16 lg:py-24` is the normal marketing hero target; `py-10`, `py-12`, `lg:py-10`, and `lg:py-12` are dashboard-density spacing and should not appear in the hero. Use `gap-6`/`gap-8` and `p-5`+ for hero visuals instead of `gap-3`/`p-3` clusters, stats rows, and checklist stacks in the hero. If translating the picker faithfully would push the CTA below the fold, rebalance the composition while keeping the visual area large and intentional."),i.push("Bar: a designer comparing the picker iframe and the deployed /home page side by side should not be able to tell them apart on type, color, copy, and composition. The deployed page should clearly FEEL more alive (motion) and the hero must fit a laptop fold even if the picker render didn't quite. Everything beyond motion + fold-fit is mechanical translation; redesigning type, color, copy, or section order belongs in a separate refinement step the user explicitly asks for."),i.push(""))}if(o==="landing"||o==="design"){i.push("### File location for the landing page (non-negotiable):"),i.push('Write the landing page to **`app/home/page.tsx`** \u2014 NOT `app/page.tsx`. The scaffold\'s `middleware.ts` 308-redirects `/` to `/home` as a workaround for an OpenNext Cloudflare bug. Visitors land at `/` and end up on `/home` transparently. Internal links like `<Link href="/">` still work because the middleware does the redirect on every request.'),i.push(""),i.push("### Landing page hard requirements (non-negotiable):"),i.push('**Motion is mandatory.** Import `motion` from the `motion` package (Framer Motion, already in package.json \u2014 `import { motion } from "motion/react"`). Every landing page must have: (1) a hero entrance reveal on mount (per-word stagger, line-by-line slide, scale-in, or blur-in \u2014 pick ONE), (2) section reveals that enhance already-visible content, (3) one card hover interaction. Never make critical content depend on JavaScript for visibility; Cloudflare deploys must show all sections even if animation hydration is delayed. Pick a single motion style and apply it consistently. A static landing page is a regression \u2014 without motion the deployed page reads as dead next to a polished competitor\'s. See `.mistflow/rules/landing.md` Section 5 (Motion Menu) for the full vocabulary.'),i.push('**Hero must fit the laptop fold.** The primary CTA must be visible at 1280\xD7800 without scrolling. Cap hero section vertical padding at `py-16 lg:py-24` (NOT `py-32` or above). Never apply `min-h-screen` to the hero section. Cap H1 at `lg:text-6xl` (60px \u2014 NEVER `lg:text-7xl`, `lg:text-8xl`, or `lg:text-9xl`, those overflow the fold on 13" laptops). Keep headline copy \u22643 lines on desktop and supporting copy to one short paragraph (\u226422 words). Do not interpret fold-fit as cramped dashboard spacing: avoid `py-10`/`lg:py-12` heroes, `gap-3` hero grids, `p-3` product mockups, stats rows, feature-card clusters, and benefit checklists in the hero. Marketing heroes should feel minimal, spacious, and image-led or scene-led while still fitting the first fold.'),i.push("");let I=s?nt(s,".mistflow","rules","landing.md"):null;I&&Rt(I)?(i.push("### Landing page rules:"),i.push("**Read `.mistflow/rules/landing.md` BEFORE writing the landing page.** That file contains the full conversion structure, section catalog, motion menu, and anti-slop audit. Written to disk to keep this prompt under the per-tool-result token cap \u2014 read once, then write your code."),i.push("")):(i.push(vr),i.push(""))}o==="landing"&&e.layoutSpec&&Array.isArray(e.layoutSpec.sections)&&(i.push(bm(e.layoutSpec)),i.push(""));let m=t.integrationId?Qt(t.integrationId):void 0;if(m){let I=Xt(m.id);if(i.push("### Integration blueprint (follow this closely):"),i.push(""),i.push(`Using integration: **${m.name}** (${m.category})`),I?.docsUrl&&i.push(`Official docs: ${I.docsUrl}`),I?.envVars?.length){i.push(""),i.push("**Required environment variables:**");for(let y of I.envVars)i.push(`- \`${y.key}\`: ${y.description} \u2014 Get it at ${y.setupUrl}`);i.push(""),i.push("**IMPORTANT: Never ask the user to paste API keys in chat.** Direct them to set keys in the Mistflow dashboard (Project Settings > Environment Variables) or at app.mistflow.ai. Use mist_config resource='env' action='list' to check if keys are set (has_value: true/false). Only proceed with deploy once all required keys are set.")}I?.packages?.length&&(i.push(""),i.push(`**Packages to install:** \`npm install ${I.packages.join(" ")}\``)),i.push(""),i.push("---"),i.push(m.prompt),i.push("---"),i.push(""),i.push("**Adaptation rules**: Follow the file structure and code patterns above. Replace placeholder values (app names, URLs, copy) with this app's specific content. Use the app's existing data models and route structure. Never ask the user to paste API keys or secrets in the chat. Direct them to set keys in the Mistflow dashboard."),i.push("")}let{reminders:h,skill:S}=await wm(o);return i.push(h),i.push(""),S&&(i.push(`### ${o} reference:`),i.push(S),i.push("")),l&&(i.push("## Self-Audit \u2014 run before submitting this file"),i.push(""),i.push(`Read the file you just wrote and answer each of these. If any answer is "yes", REDESIGN the failing piece \u2014 don't tweak classes on the same template. A different composition is required.`),i.push(""),i.push('1. **Pill badge at the top?** A small rounded label saying "Built for" / "New:" / "Made for" with a colored dot? \u2192 redesign the hero without it.'),i.push("2. **Fake browser chrome?** A rounded box with red/yellow/green dots and a fake URL bar? \u2192 delete it. Show real components instead, or no preview."),i.push("3. **Centered single-column hero?** Pill \u2192 headline \u2192 subhead \u2192 two CTAs stacked vertically? \u2192 redesign asymmetrically (see spatial.md)."),i.push("4. **Three checkmark benefit bullets** below the hero CTAs? \u2192 remove the triplet pattern."),i.push("5. **Tailwind palette utilities?** Any `bg-emerald-*`, `bg-amber-*`, `text-violet-*`, `border-slate-*`, `from-purple-*`, etc.? \u2192 replace with token classes (`bg-primary`, `bg-success`, `bg-muted`, `border-border`). No exceptions."),i.push("6. **Inter, Geist, Roboto, Arial, or system-ui** as the primary font? \u2192 pick something distinctive (see typography.md)."),i.push("7. **Purple gradient on white background** anywhere? \u2192 that's the single most overused AI-design pattern. Replace."),i.push('8. **Hardcoded hex values** (e.g. `style={{ color: "#10b981" }}`) or named CSS colors (`red`, `lightgrey`) in className or inline styles? \u2192 replace with CSS variables.'),i.push('9. **Generic copy** ("Boost your productivity", "The platform teams love", "Everything you need to")? \u2192 rewrite with specific nouns, numbers, or mechanisms from this product (see writing.md).'),i.push("10. **Zero memorable moments?** Is there ONE signature element \u2014 typographic hero, orchestrated reveal, asymmetric break, product-specific illustration? If zero, the page is generic. Add one (see motion.md, typography.md)."),i.push("11. **Text-heavy or crowded landing hero?** More than one support paragraph, a stats row, checklist, or feature-card cluster in the first viewport? \u2192 cut it back to one headline, one short sentence, one primary CTA, and one visual anchor."),i.push(""),i.push(`If every answer is "no, I'm good" \u2014 then submit.`),i.push("")),i.join(`
7370
+ `)}async function km(t){let{projectPath:e,step:r,envVarsRequired:n}=t,o=sm(e??process.cwd());if(n&&n.length>0)try{let _=Pr(o,n);_.added.length>0&&console.error(`[implement] declared env.required: ${_.added.join(", ")}`)}catch(_){console.error("[implement] env var merge skipped:",_ instanceof Error?_.message:String(_))}let s=dm(o);if(!s)return et(o);if(!Rt(nt(o,"node_modules")))return u(`Dependencies are not installed at ${o}. Call mist_install and projectPath='${o}' before running implement. This is a one-time setup step after init.`,!0);try{let{ensureBackendRegistered:_,ensureShadcnComponents:B}=await Promise.resolve().then(()=>(To(),_o)),L=await _(o);L&&!s.projectId&&(s.projectId=L);let K=await B(o);K.failed?console.error(`[implement] ${K.failed}`):K.installed.length>0&&console.error(`[implement] installed ${K.installed.length} shadcn components`);let{selfHealAgentUi:te}=await Promise.resolve().then(()=>(Lo(),Oi)),x=te(s.plan,o);x&&console.error(`[implement] ${x.isError?"warn: ":""}${x.message}`)}catch(_){console.error("[implement] self-heal skipped:",_ instanceof Error?_.message:String(_))}let i=s.plan;if(!i||!i.steps||i.steps.length===0)return u("No project plan found. Start by describing your app idea first \u2014 the AI will create a plan for you.",!0);let a,l=i.steps.find(_=>_.status==="in_progress");if(l){let _=i.steps.findIndex(B=>B.number===l.number);_!==-1&&(i.steps[_].status="completed",a=`Auto-completed step ${l.number} (${l.name})`,Ja(o,s))}let c;if(r!==void 0){if(c=i.steps.find(_=>_.number===r),!c)return u(`Step ${r} not found. The plan has ${i.steps.length} steps (numbered ${i.steps[0].number} to ${i.steps[i.steps.length-1].number}).`,!0)}else if(c=i.steps.find(_=>_.status!=="completed"),!c)return u(JSON.stringify({message:"All plan steps are completed!",completedSteps:i.steps.map(_=>_.name),nextAction:"NEXT: Deploy the app now. Call mist_deploy action='deploy'. Do NOT suggest localhost or ask the user \u2014 just deploy."}));let p=i.steps.filter(_=>_.status==="completed").map(_=>`Step ${_.number}: ${_.name}`),{readLocalState:d}=await Promise.resolve().then(()=>(Dt(),Pn)),m=d(o),h=gm(c),S=[];if((h==="crud"||h==="schema")&&i.dataModel&&c.entities&&c.entities.length>0){let _=Va(c,i.dataModel);for(let B of _){let L=Ar(B);try{let K=(B.fields||[]).map(we=>typeof we=="string"?{name:we,type:"text"}:{name:we.name,type:pm(we.type),required:we.required!==!1});if(K.length===0)continue;let te=s.dbProvider==="neon"?"nextjs-neon":"nextjs",x=await ho(te,L,K),Ue=0,G=0;for(let we of x.files){let se=nt(o,we.path);if(Rt(se)){G++;continue}Ga(im(se),{recursive:!0}),Rr(se,we.content),Ue++}let xe=nt(o,"db","index.ts");if(Rt(xe)){let we=Yo(xe,"utf-8");we.includes(x.dbExport)||Rr(xe,we.trimEnd()+`
7371
+ `+x.dbExport+`
7372
+ `)}Ue>0?S.push(`${x.entityPascal} CRUD (${Ue} new files${G>0?`, ${G} existing skipped`:""})`):G>0&&S.push(`${x.entityPascal} CRUD (all ${G} files already exist \u2014 skipped)`)}catch(K){console.error(`Module generation failed for ${L} (non-fatal):`,K instanceof Error?K.message:K)}}}let I=await vm(c,i,p,null,h,o),y=3e4,v=null;if(I.length>y){let _=nt(o,".mistflow","instructions");try{Ga(_,{recursive:!0});let B=`step-${c.number}.md`,L=nt(_,B);Rr(L,I,"utf-8"),v=`.mistflow/instructions/${B}`,I=[I.slice(0,5e3),"","---",`**Instruction continues in ${v}** (${I.length.toLocaleString()} chars total).`,"",`Read the full file before writing code: \`Read(${v})\`. The truncated portion above contains step framing; the file contains the detailed prescriptive guidance, layout specs, design tokens, and per-section instructions.`].join(`
7373
+ `)}catch(B){console.error("implement-step: failed to write overflow instruction file:",B instanceof Error?B.message:B),I=I.slice(0,y-500)+"\n\n---\n**Instruction was truncated** to fit the tool-result size cap. Detailed guidance for this step (design tokens, layout spec, anti-slop rules) lives in `.mistflow/rules/` and `methodologies/` \u2014 read those for the full context."}}let E=i.steps.findIndex(_=>_.number===c.number);if(E!==-1&&(s.plan.steps[E].status="in_progress",Ja(o,s)),m&&s.projectId){let{syncRemoteState:_}=await Promise.resolve().then(()=>(Dt(),Pn));_(s.projectId,m).catch(()=>{})}let J=i.steps.every(_=>_.status==="completed"||_.number===c.number),H;J?H=`THIS IS THE LAST STEP. Rules for speed:
7668
7374
 
7669
7375
  1. Write ALL files using PARALLEL tool calls \u2014 batch multiple Write/Edit calls in a single message.
7670
7376
  2. Do NOT read files you already know (AGENTS.md, CLAUDE.md, mistflow.json, middleware.ts, lib/auth.ts, lib/db.ts).
@@ -7675,57 +7381,57 @@ A footer with the same five links ("Product / Pricing / Docs / Blog / Terms") is
7675
7381
  - Forms must use server actions (actions.ts with 'use server'), NOT setTimeout/simulate
7676
7382
  5. Call mist_build({ projectPath }) (fire-and-poll \u2014 re-call with the returned jobId until status 'complete'), then mist_deploy({ action: 'deploy', projectPath, envVarsRequired: [...] }). Pass envVarsRequired on the deploy call if your code uses any process.env.X that isn't already in mistflow.json env.required (skip keys covered by integration presets \u2014 those are pre-declared).
7677
7383
  6. SHIP THE APP. This IS the ship moment, not a "ready when you are" checkpoint. Do NOT tell the user "when ready to ship", "next moves you can run", "you can deploy when ready", or list out manual commands like \`npm run db:push\` / \`npm run dev\` \u2014 Mistflow's pitch is "describe an app, get a live URL," and that contract is fulfilled by chaining mist_build \u2192 mist_deploy automatically. The user already approved the build when they approved the plan.
7678
- 7. If mist_build reports type errors in files you did NOT modify in this build (e.g. lib/auth.ts, app/api/admin/seed/route.ts, scaffold-origin code), DO NOT stop \u2014 Mistflow apps do not typecheck before deploy and these errors don't block production. Proceed to mist_deploy. Only halt if mist_build reports an actual compile/runtime failure (a missing import, a syntax error, a runtime error you introduced).`:V=`IMPLEMENT THIS STEP NOW. Rules for speed:
7384
+ 7. If mist_build reports type errors in files you did NOT modify in this build (e.g. lib/auth.ts, app/api/admin/seed/route.ts, scaffold-origin code), DO NOT stop \u2014 Mistflow apps do not typecheck before deploy and these errors don't block production. Proceed to mist_deploy. Only halt if mist_build reports an actual compile/runtime failure (a missing import, a syntax error, a runtime error you introduced).`:H=`IMPLEMENT THIS STEP NOW. Rules for speed:
7679
7385
 
7680
7386
  1. BEFORE writing any files, tell the user: "[stepTiming.announcement]" \u2014 use the exact announcement string from the stepTiming field so they know what's happening and how long it'll take. This is a one-line status update, NOT a request for permission.
7681
7387
  2. Write ALL files for this step using PARALLEL tool calls \u2014 batch multiple Write/Edit calls in a single message. Do NOT write one file at a time.
7682
7388
  3. Do NOT read files you already know: AGENTS.md, CLAUDE.md, mistflow.json, middleware.ts, lib/auth.ts, lib/db.ts, drizzle.config.ts \u2014 these haven't changed.
7683
7389
  4. Only read a file if you need to MODIFY it (e.g. sidebar.tsx to add a nav link, db/index.ts to add an export).
7684
- 5. After writing ALL files, call mist_implement to move to the next step. If you used any process.env.X that isn't already in mistflow.json env.required, pass it via envVarsRequired on this call so the dashboard can prompt the user for it (skip keys covered by integration presets \u2014 those are pre-declared). Do NOT pause to ask the user for permission between steps \u2014 proceed immediately. Do NOT offer options like "continue or stop" \u2014 the user already approved the build when they approved the plan. The previous step is auto-marked complete \u2014 do NOT call implement twice.`;let te=im(h),le={stepNumber:c.number,totalSteps:i.steps.length,estimatedMinutes:te,announcement:`Starting step ${c.number} of ${i.steps.length}: ${c.name}. This step usually takes ${te.min}\u2013${te.max} minutes.`},q=JSON.stringify({instruction:C,...w?{instructionFilePath:w}:{},step:{number:c.number,name:c.name,description:c.description,status:"in_progress"},stepTiming:le,compactionGuidance:"If your context gets compacted mid-step (common on long builds), call mist_implement again immediately after the compaction finishes. The tool finds the in-progress step and returns fresh context \u2014 you don't need to re-read files or re-derive state.",...a?{autoCompleted:a}:{},...U.length>0?{generatedModules:U,generatedNote:"These CRUD modules were pre-generated. Review and customize them instead of writing from scratch. Focus on: business logic in actions.ts, UI polish, and wiring navigation."}:{},progress:`${m.length}/${i.steps.length} steps done`,nextAction:V}),O=_a(r),D=O?.url??"http://localhost:3000",Z=O?.port??3e3;return await Xp(Z)&&wa(r,c.number,i.steps.length)?(va(r,c.number),ms(D,q)):d(q)}var $a={name:"mist_implement",description:"Execute the next step (or a specific step) from the app plan. Reads mistflow.json, finds the target step, and returns rich context and detailed implementation instructions for the host AI. Auto-commits a checkpoint before changes for safe undo. Use when the user says 'mist implement' or 'mist next step'.",inputSchema:Zp,handler:um};import{z as Ne}from"zod";import{z as Pt}from"zod";import{resolve as pm}from"path";Ie();Et();var Cw=Pt.object({projectPath:Pt.string().optional().describe("Path to the project directory (default: cwd)"),action:Pt.enum(["set","list","delete"]).describe("Action to perform"),key:Pt.string().optional().describe("Environment variable name (required for 'set' and 'delete')"),value:Pt.string().optional().describe("Environment variable value (required for 'set')"),category:Pt.string().optional().describe("Category for the env var (default: 'custom')"),description:Pt.string().optional().describe("Description of what this env var is for"),setupUrl:Pt.string().optional().describe("URL where the user can obtain this value (e.g. Stripe dashboard)")});async function Ua(t){let{projectPath:e,action:o,key:n,value:r,category:s,description:i,setupUrl:a}=t,l=pm(e??process.cwd());if(!Te())return Oe("manage env vars");let m=ft(l)?.projectId;if(!m)return d("No project ID found. Deploy your project first with mist_deploy, or initialize with mist_plan + mist_init + mist_install.",!0);try{switch(o){case"set":return n?r?(await Qo(m,n,r,{category:s,description:i,setupUrl:a}),d(JSON.stringify({set:!0,key:n,message:`Environment variable '${n}' has been set. It will be available on your next deployment.`}))):d("Value is required. Provide the env var value.",!0):d("Key is required. Provide the env var name like 'STRIPE_SECRET_KEY'.",!0);case"list":{let u=await Yo(m);return u.length===0?d(JSON.stringify({envVars:[],message:"No environment variables configured. Use action 'set' to add one."})):d(JSON.stringify({envVars:u.map(p=>({key:p.key,category:p.category,description:p.description,hasValue:p.has_value})),message:`${u.length} environment variable(s) configured.`}))}case"delete":return n?(await Xo(m,n),d(JSON.stringify({deleted:!0,key:n,message:`Environment variable '${n}' has been removed.`}))):d("Key is required. Provide the env var name to delete.",!0);default:return d(`Unknown action: ${o}. Use set, list, or delete.`,!0)}}catch(u){if(u instanceof X)return d(u.message,!0);let p=u instanceof Error?u.message:"An unexpected error occurred";return d(p,!0)}}import{z as Fn}from"zod";import{resolve as mm,join as hm}from"path";import{existsSync as gm,readFileSync as fm,writeFileSync as ym}from"fs";Ie();Et();var Ow=Fn.object({projectPath:Fn.string().optional().describe("Path to the project directory (default: cwd)"),action:Fn.enum(["add","list","verify","remove"]).describe("Action to perform"),domain:Fn.string().optional().describe("Domain name (required for 'add' and 'remove')"),domainId:Fn.string().optional().describe("Domain ID (required for 'verify' and 'remove')")});function Jr(t,e){let o=hm(t,"mistflow.json");if(!gm(o))return;let n;try{n=JSON.parse(fm(o,"utf-8"))}catch{return}n.domains=e,ym(o,JSON.stringify(n,null,2)+`
7685
- `)}async function qa(t){let{projectPath:e,action:o,domain:n,domainId:r}=t,s=mm(e??process.cwd());if(!Te())return Oe("manage custom domains");let a=ft(s)?.projectId;if(!a)return d("No project ID found. Deploy your project first with mist_deploy, or initialize with mist_plan + mist_init + mist_install.",!0);try{switch(o){case"add":{if(!n)return d("Domain name is required. Provide the domain like 'myapp.com' or 'app.mycompany.com'.",!0);let l=await Ko(a,n),c=await bt(a);return Jr(s,c.map(m=>({domain:m.domain,status:m.status}))),d(JSON.stringify({added:!0,domain:l.domain,status:l.status,instructions:l.instructions,message:`Domain '${l.domain}' added. Set up DNS records as described, then use action 'verify' to check status.`}))}case"list":{let l=await bt(a);return l.length===0?d(JSON.stringify({domains:[],message:"No custom domains configured. Use action 'add' to add one."})):d(JSON.stringify({domains:l.map(c=>({id:c.id,domain:c.domain,status:c.status,ssl:c.ssl_status,error:c.error_message}))}))}case"verify":{if(!r){if(n){let u=(await bt(a)).find(p=>p.domain===n);if(u){let p=await io(a,u.id);return d(JSON.stringify({domain:p.domain,status:p.status,ssl:p.ssl_status,error:p.error_message,message:p.status==="active"?`Domain '${p.domain}' is active and serving traffic.`:`Domain '${p.domain}' is ${p.status}. Make sure DNS records are configured correctly.`}))}return d(`Domain '${n}' not found. Use action 'list' to see configured domains.`,!0)}return d("Provide either domainId or domain name to verify.",!0)}let l=await io(a,r),c=await bt(a);return Jr(s,c.map(m=>({domain:m.domain,status:m.status}))),d(JSON.stringify({domain:l.domain,status:l.status,ssl:l.ssl_status,error:l.error_message,message:l.status==="active"?`Domain '${l.domain}' is active and serving traffic.`:`Domain '${l.domain}' is ${l.status}. Make sure DNS records are configured correctly.`}))}case"remove":{if(!r&&!n)return d("Provide either domainId or domain name to remove.",!0);let l=r;if(!l&&n){let u=(await bt(a)).find(p=>p.domain===n);if(!u)return d(`Domain '${n}' not found.`,!0);l=u.id}await Jo(a,l);let c=await bt(a);return Jr(s,c.map(m=>({domain:m.domain,status:m.status}))),d(JSON.stringify({removed:!0,message:"Domain removed. It may take a few minutes for DNS changes to propagate."}))}default:return d(`Unknown action: ${o}. Use add, list, verify, or remove.`,!0)}}catch(l){if(l instanceof X)return d(l.message,!0);let c=l instanceof Error?l.message:"An unexpected error occurred";return d(c,!0)}}var Fa=["create","list","revoke"];function bm(t){return typeof t=="string"&&Fa.includes(t)}var Ba=async t=>{let e=t??{};if(!bm(e.action))return d(`Unknown action: ${String(e.action)}. Use one of: ${Fa.join(", ")}.`,!0);switch(e.action){case"create":return d(wm(e),!1);case"list":return d(vm(),!1);case"revoke":return d(km(e),!1)}};function wm(t){let e=typeof t.name=="string"&&t.name.trim().length>0?t.name.trim():"<caller-name>",o=typeof t.monthlyBudgetCents=="string"?t.monthlyBudgetCents:"0",n=Array.isArray(t.toolAllowlist)?JSON.stringify(t.toolAllowlist):"null",r=typeof t.rateLimitPerMin=="number"&&Number.isFinite(t.rateLimitPerMin)&&t.rateLimitPerMin>=0?String(t.rateLimitPerMin):"null";return["# Create an MCP caller in the user's deployed app","","The MCP extension generated `createMcpCaller()` in `lib/mcp/caller-auth.ts` of the user's app.","Callers live in the app's own database (the `mcp_caller` table), NOT in the Mistflow backend.","","## Recommended approach","","Run this as a one-off script in the user's app (e.g. in `scripts/mcp-create-caller.ts`):","","```ts","import { createMcpCaller } from '@/lib/mcp/caller-auth'","import { getMcpDb } from '@/lib/mcp/caller-auth'","","const result = await createMcpCaller(getMcpDb(process.env as never), {",` name: ${JSON.stringify(e)},`,` monthlyBudgetCents: ${JSON.stringify(o)},`,` toolAllowlist: ${n},`,` rateLimitPerMin: ${r},`,"})","","// The plaintext token is shown ONCE. Capture it now; only the hash is stored.","console.error('caller created', result.callerId, result.tokenHashPrefix)","console.log(result.token)","```","","## Security","- Run on a trusted machine; the script prints the raw bearer token.","- Store the token in a password manager. The DB only retains the hash.","- If you lose the token, revoke the caller and create a new one."].join(`
7686
- `)}function vm(){return["# List MCP callers in the user's deployed app","","```ts","import { listMcpCallers, getMcpDb } from '@/lib/mcp/caller-auth'","","const callers = await listMcpCallers(getMcpDb(process.env as never))","console.table(callers.map(({ id, name, tokenHashPrefix, monthlyBudgetCents, revokedAt }) => ({"," id, name, tokenHashPrefix, monthlyBudgetCents, revokedAt,","})))","```","","Listing never returns the raw bearer token (only the hash prefix). To rotate a token, revoke and re-create."].join(`
7687
- `)}function km(t){let e=typeof t.callerId=="string"&&t.callerId.trim().length>0?t.callerId.trim():"<caller-id>";return["# Revoke an MCP caller in the user's deployed app","","```ts","import { revokeMcpCaller, getMcpDb } from '@/lib/mcp/caller-auth'","",`await revokeMcpCaller(getMcpDb(process.env as never), ${JSON.stringify(e)})`,"```","","After revocation the caller's bearer token will be rejected by `authenticateCaller()` with code `invalid_or_revoked`."].join(`
7688
- `)}var xm=Ne.object({resource:Ne.enum(["env","domain","mcp-caller"]).describe("'env' manages app secrets and configuration values. 'domain' manages custom domains. 'mcp-caller' manages bearer tokens for the app's MCP endpoint (create/list/revoke)."),action:Ne.string().describe("Action to perform. env: 'set', 'list', 'delete'. domain: 'add', 'list', 'verify', 'remove'. mcp-caller: 'create', 'list', 'revoke'."),projectPath:Ne.string().optional().describe("Path to the project directory (default: cwd)"),key:Ne.string().optional().describe("(env) Variable name"),value:Ne.string().optional().describe("(env set) Variable value"),category:Ne.string().optional().describe("(env set) Category"),description:Ne.string().optional().describe("(env set) Description"),setupUrl:Ne.string().optional().describe("(env set) URL to obtain the value"),domain:Ne.string().optional().describe("(domain) Domain name"),domainId:Ne.string().optional().describe("(domain) Domain ID"),name:Ne.string().optional().describe("(mcp-caller create) Human-readable name"),callerId:Ne.string().optional().describe("(mcp-caller revoke) The caller id to revoke"),monthlyBudgetCents:Ne.string().optional().describe("(mcp-caller create) Budget cap in cents as a string. '0' means unlimited."),toolAllowlist:Ne.array(Ne.string()).optional().describe("(mcp-caller create) Restrict the caller to these MCP tool names. Omit for unrestricted."),rateLimitPerMin:Ne.number().optional().describe("(mcp-caller create) Per-minute rate limit on this caller's tool calls.")}),Ha={name:"mist_config",description:"Manage project configuration: app secrets and custom domains. Set resource='env' to manage encrypted app secrets (set, list, delete). Set resource='domain' to manage custom domains (add, list, verify, remove). Use when the user says 'mist env' or 'mist domain'.",inputSchema:xm,handler:async t=>{let e=t;switch(e.resource){case"env":return Ua({projectPath:e.projectPath,action:e.action,key:e.key,value:e.value,category:e.category,description:e.description,setupUrl:e.setupUrl});case"domain":return qa({projectPath:e.projectPath,action:e.action,domain:e.domain,domainId:e.domainId});case"mcp-caller":return Ba({action:e.action,name:e.name,callerId:e.callerId,monthlyBudgetCents:e.monthlyBudgetCents,toolAllowlist:e.toolAllowlist,rateLimitPerMin:e.rateLimitPerMin});default:return d(`Unknown resource: ${e.resource}. Use env, domain, or mcp-caller.`,!0)}}};import{z as Bn}from"zod";import{resolve as Sm}from"path";import{existsSync as _m}from"fs";import{join as Cm}from"path";function Tm(t,e=15){let o=t.split(`
7689
- `).filter(n=>n.length>0);return o.length<=e?t:o.slice(-e).join(`
7690
- `)}var Im=Bn.object({projectPath:Bn.string().optional().describe("Absolute path to the project. Required for the first call (to start the install)."),jobId:Bn.string().optional().describe("Job ID returned from a previous call. Present means poll; absent means start."),waitSeconds:Bn.number().min(0).max(50).optional().describe("On poll calls: long-poll for up to N seconds before returning, so a typical install fits in 2 round-trips instead of 5-6. Default 20. Set 0 to disable."),sessionId:Bn.string().uuid().optional().describe("Backend session ID. Pass through from mist_plan response so state guards apply and session state advances as the tool runs.")}).refine(t=>!!t.projectPath||!!t.jobId,{message:"Pass projectPath to start an install, or jobId to poll an existing one."}),za={name:"mist_install",description:"Install a Mistflow project's npm dependencies with the fire-and-poll pattern. First call with { projectPath } starts the install and returns a jobId. Subsequent calls with { jobId } poll for progress (status: 'running' | 'complete' | 'failed'). Re-call while status is 'running' \u2014 each response is <1s so there is no 60s timeout risk.",inputSchema:Im,handler:async(t,e)=>{let o=t;if(o.jobId){let l=await Tt(o.jobId);if(!l)return d(JSON.stringify({status:"not_found",jobId:o.jobId,message:`No install job found for jobId '${o.jobId}'. Start a new one with { projectPath }.`}),!0);let c=o.waitSeconds??20;if(c>0&&(l.status==="running"||l.status==="starting")){let u=l.elapsed,p=e?Ye(e.server,e.progressToken,()=>`Install running (${u}) \u2014 waiting for terminal state`):{stop:()=>{}};try{let h=await cn(o.jobId,{timeoutMs:c*1e3,onPoll:U=>{u=U.elapsed}});h&&(l=h)}finally{p.stop()}}if(l.status==="running"||l.status==="starting")return d(JSON.stringify({status:"running",jobId:l.id,elapsed:l.elapsed,logTail:Tm(l.logTail),nextAction:"Install still running after long-poll window. Call mist_install again with the same jobId to continue waiting."}));if(l.status==="complete"){let u=await Ca(l.cwd),p=!1;u&&u.freshlyStarted?(p=await ka(u.state.url,15e3),p&&Ta(u.state.url)):u&&(p=!0);let h=u?p?` PREVIEW: Live dev server at ${u.state.url} (browser opened). TELL THE USER: "Your app is rendering at ${u.state.url} \u2014 it'll refresh live as it's built."`:` PREVIEW: Dev server starting at ${u.state.url} (still compiling). TELL THE USER: "Open ${u.state.url} in your browser in ~30s \u2014 your app will appear there and refresh live as it's built."`:"";return d(JSON.stringify({status:"complete",jobId:l.id,elapsed:l.elapsed,exitCode:l.exitCode,...u?{previewUrl:u.state.url,previewReady:p}:{},nextAction:`Dependencies installed.${h} Run mist_implement next to start executing plan steps.`}))}let m=Bt(l.id);return d(JSON.stringify({status:l.status,jobId:l.id,elapsed:l.elapsed,exitCode:l.exitCode,logTail:l.logTail,logPaths:m,nextAction:`npm install failed. Read the logTail for the error and fix it \u2014 usually a missing native dep or a version conflict. If the 200-line tail is truncated, read the full log from logPaths.stderr (${m.stderr}) with your file-read tool. After fixing, start a new install with { projectPath }.`}),!0)}let n=Sm(o.projectPath);if(!_m(Cm(n,"package.json")))return d(`No package.json at ${n}. Confirm the path and that mist_init has scaffolded the project.`,!0);let i=`npm install && (${`npx --yes shadcn@latest add -y -o ${["button","card","input","label","form","dialog","table","dropdown-menu","badge","separator","skeleton","sheet","tabs","avatar","select","textarea","checkbox","switch","tooltip","popover","sonner"].join(" ")}`} || echo 'shadcn add failed \u2014 implement will retry lazily')`,a=await Ct({type:"install",cmd:"sh",args:["-c",i],cwd:n,env:{NPM_CONFIG_LEGACY_PEER_DEPS:"true"}});return d(JSON.stringify({status:"running",jobId:a.id,startedAt:a.startedAt,cwd:n,nextAction:"Install started in the background (npm install + shadcn components). Call mist_install again with { jobId: '"+a.id+"' } immediately \u2014 each poll long-polls for up to 20s server-side so there's no need to sleep between calls. Typical duration: 30-90s."}))}};import{z as dn}from"zod";import{resolve as Pm,join as Xe}from"path";import{existsSync as ot,readFileSync as Wa,statSync as Rm,readdirSync as Am}from"fs";var Ga=100*1024;function Em(t){let e=0,o=0,n=[t];for(;n.length>0&&o<1e4;){let r=n.pop();o++;let s;try{s=Am(r)}catch{continue}for(let i of s){let a=Xe(r,i),l;try{l=Rm(a)}catch{continue}l.isDirectory()?n.push(a):l.isFile()&&(e+=l.size)}}return e}function Nm(t){if(!(ot(Xe(t,"open-next.config.ts"))||ot(Xe(t,"open-next.config.js"))))return null;let o=Xe(t,".open-next","worker.js");if(!ot(o))return`Build exited 0 but .open-next/worker.js is missing at ${o}. The Cloudflare adapter did not produce a worker entrypoint. Common causes: (1) open-next.config.ts is misconfigured, (2) a client component imports a Node-only API (fs, path, crypto node:* imports in 'use client' files), (3) the adapter crashed silently mid-build. Check the logTail above for "error"/"failed" messages, fix the offending file, and re-run mist_build.`;let n=Xe(t,".open-next","server-functions","default");if(!ot(n))return'Build exited 0 and .open-next/worker.js exists, but .open-next/server-functions/default/ is missing. The OpenNext adapter produced an orchestrator with no server bundle to import. Check the logTail above for "error"/"failed" messages near "Building server function: default..." and re-run mist_build.';let r=Em(n);return r<Ga?`Build exited 0 but the server-functions bundle is only ${r} bytes (expected at least ${Ga}). A healthy Next.js + OpenNext bundle is 5-50MB. This size suggests the adapter failed to bundle most of the app. Check the logTail for bundling errors and re-run mist_build.`:null}var Om=dn.object({projectPath:dn.string().optional().describe("Absolute path to the project. Required for the first call."),jobId:dn.string().optional().describe("Job ID from a previous call. Present means poll; absent means start."),script:dn.string().optional().describe("Optional override: run `npm run <script>` instead of the Cloudflare adapter. Default behavior is `npx @opennextjs/cloudflare build` when open-next.config.ts exists, else `npm run build`. Ignored on poll calls."),waitSeconds:dn.number().min(0).max(50).optional().describe("On poll calls: long-poll for up to N seconds before returning, so a typical build fits in 2 round-trips instead of 6-8. Default 20. Set 0 to disable."),sessionId:dn.string().uuid().optional().describe("Backend session ID. Pass through from mist_plan response so state guards apply and session state advances as the tool runs.")}).refine(t=>!!t.projectPath||!!t.jobId,{message:"Pass projectPath to start a build, or jobId to poll an existing one."});function Mm(t){let e=Xe(_o(),t,"stdout.log"),o=Xe(_o(),t,"stderr.log"),n="";try{ot(e)&&(n+=Wa(e,"utf-8"))}catch{}try{ot(o)&&(n+=`
7691
- `+Wa(o,"utf-8"))}catch{}return n}function jm(t,e=15){let o=t.split(`
7692
- `).filter(n=>n.length>0);return o.length<=e?t:o.slice(-e).join(`
7693
- `)}function Dm(t){let e=/Module not found:\s*(?:Error:\s*)?Can't resolve ['"]([^'"]+)['"]/g,o=new Set;for(let n of t.matchAll(e)){let r=n[1];if(r.startsWith(".")||r.startsWith("@/")||r.startsWith("~/"))continue;let s=r.startsWith("@")?r.split("/").slice(0,2).join("/"):r.split("/")[0];s&&o.add(s)}return[...o]}function Lm(t){return t.includes(".next/diagnostics/build-diagnostics.json")?`The build log shows Next/OpenNext failed while reading .next/diagnostics/build-diagnostics.json. ${qt()} This has shown up when the adapter runs under an unsupported Node 22 patch release.`:null}var Ka={name:"mist_build",description:"Run a Mistflow project's production build with the fire-and-poll pattern. First call with { projectPath } starts `npm run build` and returns a jobId. Subsequent calls with { jobId } poll status. On failure, the response surfaces structured errors (from parseBuildErrors) and any missingModules[] \u2014 chain into mist_install with those modules, then mist_build again, without asking the user.",inputSchema:Om,handler:async(t,e)=>{let o=t;if(o.jobId){let c=await Tt(o.jobId);if(!c)return d(JSON.stringify({status:"not_found",jobId:o.jobId,message:`No build job found for jobId '${o.jobId}'. Start a new one with { projectPath }.`}),!0);let m=o.waitSeconds??20;if(m>0&&(c.status==="running"||c.status==="starting")){let A=c.elapsed,J=e?Ye(e.server,e.progressToken,()=>`Build running (${A}) \u2014 waiting for terminal state`):{stop:()=>{}};try{let V=await cn(o.jobId,{timeoutMs:m*1e3,onPoll:te=>{A=te.elapsed}});V&&(c=V)}finally{J.stop()}}if(c.status==="running"||c.status==="starting")return d(JSON.stringify({status:"running",jobId:c.id,elapsed:c.elapsed,logTail:jm(c.logTail),nextAction:"Build still running after long-poll window. Call mist_build again with the same jobId to continue waiting."}));if(c.status==="complete"){let A=Nm(c.cwd);if(A){let J=Bt(c.id);return d(JSON.stringify({status:"failed",jobId:c.id,elapsed:c.elapsed,exitCode:c.exitCode,logTail:c.logTail,logPaths:J,error:A,nextAction:A+` Full build log: ${J.stdout} and ${J.stderr}.`}),!0)}return d(JSON.stringify({status:"complete",jobId:c.id,elapsed:c.elapsed,exitCode:c.exitCode,nextAction:"Build passed. Run mist_deploy next to ship \u2014 do NOT ask the user to confirm, the build is the approval gate."}))}let u=Mm(c.id),p=po(u),h=Dm(u),U=Lm(u),C=Bt(c.id),x=` Full log: ${C.stdout} and ${C.stderr}.`,w=h.length>0?`Build failed with missing modules: ${h.join(", ")}. Call mist_install with these packages (or chain mist_install { projectPath } to reinstall all), then call mist_build again. Do NOT ask the user.${x}`:U?`${U}${x}`:p.length>0?`Build failed with structured errors (see errors[]). Fix the code, then call mist_build again.${x}`:`Build failed. Read the logTail for the error, or call mist_debug with the failed jobId to extract structured errors.${x}`;return d(JSON.stringify({status:c.status,jobId:c.id,elapsed:c.elapsed,exitCode:c.exitCode,errors:p,missingModules:h,logTail:c.logTail,logPaths:C,nextAction:w}),!0)}let n=Pm(o.projectPath);if(!ot(Xe(n,"package.json")))return d(`No package.json at ${n}. Run mist_init first.`,!0);if(!ot(Xe(n,"node_modules")))return d(`node_modules not installed. Run mist_install { projectPath: '${n}' } first.`,!0);let r,s,i,a=ot(Xe(n,"open-next.config.ts"))||ot(Xe(n,"open-next.config.js"));if(o.script)r="npm",s=["run",o.script],i=o.script;else if(a){if(!tn())return d(qt(),!0);r="npx",s=["-y","@opennextjs/cloudflare","build"],i="@opennextjs/cloudflare build"}else r="npm",s=["run","build"],i="build";let l=await Ct({type:"build",cmd:r,args:s,cwd:n});return d(JSON.stringify({status:"running",jobId:l.id,startedAt:l.startedAt,cwd:n,script:i,nextAction:`Build started. Call mist_build again with { jobId: '${l.id}' } immediately \u2014 each poll long-polls for up to 20s server-side so there's no need to sleep between calls. Typical duration: 30-90s on a fresh scaffold (Cloudflare adapter), 10-30s on subsequent builds.`}))}};import{existsSync as Bm,readFileSync as Hm}from"fs";import{join as zm}from"path";import{z as un}from"zod";Ie();Ie();import{existsSync as $m,readdirSync as Um}from"fs";import{join as Ja}from"path";import{pathToFileURL as qm}from"url";async function Fm(t){let e=Ja(t,".mistflow","acceptance"),o=new Map;if(!$m(e))return o;let n=Um(e).filter(r=>r.endsWith(".spec.ts")||r.endsWith(".spec.js")||r.endsWith(".spec.mjs"));for(let r of n){let s=r.replace(/\.spec\.(ts|js|mjs)$/,"").trim();if(!s)continue;let i=Ja(e,r);try{let a=await import(qm(i).href),l=a.default??a.run;typeof l=="function"?o.set(s,l):console.error(`Probe ${r} doesn't export a default function (or named 'run'). Skipping.`)}catch(a){console.error(`Failed to load probe ${r}:`,a instanceof Error?a.message:a)}}return o}async function Va(t,e){let{sessionId:o,projectPath:n,deployUrl:r,seedCredentials:s}=e,a=(await kn(o)).criteria,l=await Fm(n),c=[];for(let m of a){let u=l.get(m.id);if(!u){c.push({criterion_id:m.id,status:"skipped",evidence:{reason:`no probe at .mistflow/acceptance/${m.id}.spec.ts`}});continue}try{await t.goto(r,{waitUntil:"domcontentloaded"});let p=await u(t,{url:r,criterion:m,seedCredentials:s});c.push({criterion_id:m.id,status:p.pass?"pass":"fail",evidence:{...p.detail?{detail:p.detail}:{},...p.screenshot?{screenshot:p.screenshot}:{}}})}catch(p){c.push({criterion_id:m.id,status:"fail",evidence:{error:p instanceof Error?p.message:"probe threw an unknown error"}})}}return{criteria:a,results:c}}var Wm=un.object({projectPath:un.string().optional().describe("Absolute path to the Mistflow project. Defaults to the current working directory. Used to read mistflow.json for the deploy URL + projectId."),url:un.string().optional().describe("Override the deploy URL to QA. Useful for previews or when mistflow.json doesn't yet have the URL recorded. Defaults to mistflow.json's deploy.url."),deploymentId:un.string().optional().describe("Deployment ID to associate QA results with. When set, results are uploaded to the backend and surfaced on the dashboard deployment card."),sessionId:un.string().uuid().optional().describe("Backend session ID. Pass through from mist_plan response so state guards apply and session state advances as the tool runs."),action:un.enum(["full","acceptance"]).default("full").describe("'full' (default) runs the standard post-deploy QA suite: HTTP pre-flight, landing render, login, sidebar pages, design audits. 'acceptance' runs only the per-criterion Playwright probes from .mistflow/acceptance/<criterion-id>.spec.ts against the deploy URL and uploads pass/fail results to the backend. Requires sessionId.")}),Xa={name:"mist_qa",description:"Post-deploy QA: drives Playwright in-process against the live deploy URL. Checks health + auth endpoints, logs in with a seeded admin account, walks the dashboard and sidebar pages, and runs design-quality audits (typography, color, layout, slop patterns). Returns pass/fail + screenshots inline. Call ONCE after mist_deploy completes. Do NOT surface the deploy URL to the user until mist_qa returns status: 'pass'.",inputSchema:Wm,handler:async t=>{let e=t;return e.action==="acceptance"?Km(e):Jm(e)}};function Za(t){let e=zm(t,"mistflow.json");if(!Bm(e))return null;try{return JSON.parse(Hm(e,"utf-8"))}catch{return null}}async function Ya(t){try{let e=await fetch(t,{redirect:"follow",signal:AbortSignal.timeout(15e3)}),o=await e.text();return{status:e.status,body:o}}catch(e){return{status:0,body:String(e)}}}async function Gm(t,e,o){try{let n=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json",...o??{}},body:JSON.stringify(e),redirect:"follow",signal:AbortSignal.timeout(15e3)}),r=await n.text(),s;try{s=JSON.parse(r)}catch{}return{status:n.status,json:s}}catch{return{status:0}}}async function Qa(t){try{let e=await t.screenshot({type:"png"});return Buffer.from(e).toString("base64")}catch{return}}async function Rt(t,e,o){let n=[],r=s=>{s.type()==="error"&&n.push(s.text())};t.on("console",r);try{let s=await o(),i=await Qa(t);return{name:e,status:s.pass?"pass":"fail",detail:s.detail,fix:s.fix,screenshot:i,consoleErrors:n.length>0?n:void 0}}catch(s){let i=await Qa(t);return{name:e,status:"fail",detail:`Unexpected error: ${s instanceof Error?s.message:String(s)}`,screenshot:i,consoleErrors:n.length>0?n:void 0}}finally{t.removeListener("console",r)}}async function Km(t){if(!t.sessionId)return d("mist_qa action='acceptance' requires sessionId. Pass the session ID from your mist_plan response.",!0);let e=t.projectPath??process.cwd(),o=Za(e),n=t.url;if(!n&&t.deploymentId)try{let a=await yt(t.deploymentId);a.url&&(n=a.url)}catch(a){console.error(`[acceptance] Could not resolve URL from deploymentId ${t.deploymentId}:`,a instanceof Error?a.message:String(a))}if(n||(n=o?.deploy?.url),!n)return d("No deploy URL found. Deploy the app first with mist_deploy, then call mist_qa action='acceptance'.",!0);let r;if(t.deploymentId)try{let l=await ao(t.deploymentId);l?.adminEmail&&l?.adminPassword&&(r={email:l.adminEmail,password:l.adminPassword})}catch(a){console.error("[acceptance] getSeedInfo failed (continuing without):",a instanceof Error?a.message:String(a))}let s,i;try{let{getIsolatedContext:a}=await Promise.resolve().then(()=>(zt(),vn)),l=await a();s=l.context,i=l.page}catch(a){return d(`Playwright not available locally \u2014 install it with \`npx playwright install chromium\`. Detail: ${a instanceof Error?a.message:String(a)}`,!0)}try{let{criteria:a,results:l}=await Va(i,{sessionId:t.sessionId,projectPath:e,deployUrl:n,seedCredentials:r});try{await wr(t.sessionId,l)}catch(p){console.error("[acceptance] submitAcceptanceResults failed:",p instanceof Error?p.message:String(p))}let c=l.filter(p=>p.status==="pass").length,m=l.filter(p=>p.status==="fail").length,u=l.filter(p=>p.status==="skipped").length;return d(JSON.stringify({status:m===0?"pass":"fail",url:n,totals:{passed:c,failed:m,skipped:u,total:a.length},results:l.map(p=>({criterion_id:p.criterion_id,status:p.status,detail:p.evidence&&typeof p.evidence=="object"&&"detail"in p.evidence?p.evidence.detail:p.evidence&&typeof p.evidence=="object"&&"reason"in p.evidence?p.evidence.reason:void 0})),nextAction:m===0?"All required acceptance criteria passed. The app is shippable.":"Some criteria failed. Read the per-criterion detail, fix the underlying code, then re-run mist_qa action='acceptance'."}),m>0)}finally{if(s)try{await s.close()}catch{}}}async function Jm(t){let e=t.projectPath??process.cwd(),o=Za(e),n=t.url;if(!n&&t.deploymentId)try{let A=await yt(t.deploymentId);A.url&&(n=A.url)}catch(A){console.error(`[qa] Could not resolve URL from deploymentId ${t.deploymentId}:`,A instanceof Error?A.message:String(A))}if(n||(n=o?.deploy?.url),!n)return d("No deploy URL found. Deploy the app first with mist_deploy, then call mist_qa.",!0);n.startsWith("http")||(n=`https://${n}`);let r=o?.projectId,s=[],i=await Ya(`${n}/api/health`);if(i.status!==200)return s.push({name:"Health endpoint",status:"fail",detail:`Returns ${i.status}`,fix:"The worker is not running or crashed on startup. Check app/api/health/route.ts exists and the build succeeded."}),Eo(n,s);s.push({name:"Health endpoint",status:"pass",detail:"Returns 200"});let a=await Ya(`${n}/api/auth/ok`);if(a.status!==200)return s.push({name:"Auth system",status:"fail",detail:`Auth endpoint returns ${a.status}`,fix:"Better Auth is not working. Check lib/auth.ts, lib/db.ts, and that your database env vars are set."}),Eo(n,s);s.push({name:"Auth system",status:"pass",detail:"Better Auth running"});let l="qa-actor@qa.mistflow.local",c="qa-user@qa.mistflow.local",m="QaTemp1!",u="single",p="user",h=[];async function U(A,J,V,te){let le=await Gm(`${n}/api/admin/seed`,{token:te,email:J,password:m,role:V});if(le.status!==200||!le.json)return console.error(`[qa] Seed (${A}) returned ${le.status}`),null;let Q=le.json.sessionToken;if(!Q)return null;let q=le.json.seeded===!0;return{role:A,email:J,sessionToken:Q,password:q?m:void 0}}if(r){let A=await ao(r);if(A){if(u=A.authMode??"single",p=A.defaultRole??"user",console.error(`[qa] auth_mode=${u}, default_role=${p}`),u!=="none"){let V=await U("actor",l,u==="multi"?"admin":p,A.seedToken);if(V&&h.push(V),u==="multi"){let te=await U("user",c,p,A.seedToken);te?h.push(te):s.push({name:"Auth session (user)",status:"fail",detail:`Seed endpoint did not create the default-role user (${p}). Multi-role walk degraded to admin-only.`,fix:"Check that /api/admin/seed accepts a `role` parameter and creates users with non-admin roles. The scaffold ships this support; if customised, ensure the role argument is honored."})}}}else console.error("[qa] No seed info from backend \u2014 assuming auth-disabled or pre-deploy state"),u="none"}if(u!=="none"&&h.length===0)return s.push({name:"Auth session",status:"fail",detail:"Could not acquire a session token from the seed endpoint",fix:"Redeploy the app with mist_deploy. The deploy injects ADMIN_SEED_TOKEN and stores it on the backend; the scaffold's /api/admin/seed route accepts { token, email, password, role } and creates synthetic QA users with the requested role."}),Eo(n,s);let C,x,w;try{let{getIsolatedContext:A}=await Promise.resolve().then(()=>(zt(),vn)),J=await A();C=J.context,w=J.page}catch(A){let J=A instanceof Error?A.message:String(A);return d(JSON.stringify({status:"cannot_verify",url:n,deployed:!0,reason:"App deployed successfully, but QA could not verify it via automated browser testing because Playwright is not installed locally.",detail:J,httpChecks:s.map(({screenshot:V,...te})=>te),fix:"Run: npx playwright install chromium",instruction:[`The app deployed and is live at ${n}. That part succeeded.`,"What DID NOT succeed: automated QA verification. Playwright is not installed locally, so we could not open the app in a browser and check that the landing renders, signup works, and the dashboard loads.","Tell the user BOTH facts clearly \u2014 don't conflate 'deployed' with 'verified':",` "Your app is live at ${n}. I couldn't run automated QA because Playwright isn't installed locally. Run \`npx playwright install chromium\` (one-time, ~150MB) and I'll verify it for you \u2014 or just open the URL and try it yourself."`,"HTTP checks (health/auth endpoints) passed \u2014 those prove the server is responding. They do not prove the UI renders or user flows work.","After Playwright is installed, call mist_qa again for full verification."].join(`
7694
- `)}),!1)}try{let A=await Rt(w,"Landing page",async()=>{await w.goto(n,{waitUntil:"domcontentloaded",timeout:3e4}),await w.waitForLoadState("networkidle").catch(()=>{});let Q=await w.evaluate(()=>{let O=document.body;if(!O)return"";let D=document.createTreeWalker(O,NodeFilter.SHOW_TEXT),Z="",ne;for(;ne=D.nextNode();){let v=ne.parentElement;if(v){let F=window.getComputedStyle(v);F.display!=="none"&&F.visibility!=="hidden"&&parseFloat(F.opacity)>0&&(Z+=ne.textContent?.trim()+" ")}}return Z.trim()});if(Q.length<50)return{pass:!1,detail:`Landing page appears blank (${Q.length} chars visible). Likely a CSS/JS rendering issue.`,fix:"Common cause: motion/react animations with opacity:0 and whileInView that never trigger on Mistflow Cloud's edge runtime (no Intersection Observer). Replace with CSS animations or set initial={{ opacity: 1 }}."};let q=w.url();return q.includes("/login")||q.includes("/sign-in")?{pass:!1,detail:"Root URL redirects to login instead of showing a landing page",fix:"Check middleware.ts: '/' must be in PUBLIC_EXACT. Check app/page.tsx: must be a landing page, not a redirect."}:{pass:!0,detail:`Renders visible content (${Q.length} chars)`}});s.push(A),u==="none"&&s.push({name:"Auth walk",status:"pass",detail:"Skipped \u2014 app has no authentication (authModel=none)."});let J=h[0],V=(Q,q)=>h.length>1&&Q?`${q} (${Q.role})`:q,te=!1;if(J?.password){let Q=await Rt(w,V(J,"Login"),async()=>{await w.goto(`${n}/login`,{waitUntil:"domcontentloaded",timeout:15e3}),await w.waitForLoadState("networkidle").catch(()=>{});let q=w.locator('input[type="email"], input[name="email"], input[placeholder*="email" i]'),O=w.locator('input[type="password"], input[name="password"]');try{await q.first().waitFor({state:"visible",timeout:1e4})}catch{return{pass:!1,detail:"Login page has no visible email input field",fix:"Check app/(auth)/login/page.tsx renders a login form with email and password inputs."}}await q.first().fill(J.email),await O.first().fill(J.password),await w.locator('button[type="submit"], button:has-text("Sign in"), button:has-text("Log in"), button:has-text("Login")').first().click();try{await w.waitForURL(Z=>!Z.pathname.includes("/login"),{timeout:1e4})}catch{let Z=await w.locator('[role="alert"], .text-red-500, .text-destructive, [data-error]').first().textContent().catch(()=>null);return{pass:!1,detail:Z?`Login failed: ${Z}`:"Login did not redirect. Page stayed on /login.",fix:"Do NOT edit lib/auth.ts to disable email verification. Redeploy with mist_deploy to re-seed the verified QA accounts."}}return te=!0,{pass:!0,detail:`Logged in, redirected to ${w.url()}`}});s.push(Q)}else J&&s.push({name:V(J,"Login"),status:"pass",detail:"Skipped form login (QA user already exists from previous deploy). Using session injection."});if(!te&&J){let Q=new URL(n).hostname,q=n.startsWith("https");await C.addCookies([{name:q?"__Secure-better-auth.session_token":"better-auth.session_token",value:J.sessionToken,domain:Q,path:"/",httpOnly:!0,secure:q,sameSite:"Lax"}]),console.error(`[qa] Injected ${J.role} cookie for primary walk`)}if(J){let Q=await Rt(w,V(J,"Dashboard"),async()=>{w.url().includes("/dashboard")||(await w.goto(`${n}/`,{waitUntil:"domcontentloaded",timeout:15e3}),await w.waitForLoadState("networkidle").catch(()=>{}),new URL(w.url()).pathname==="/"&&(await w.goto(`${n}/dashboard`,{waitUntil:"domcontentloaded",timeout:15e3}),await w.waitForLoadState("networkidle").catch(()=>{})));let D=await w.content();return D.length<1e3?{pass:!1,detail:`Dashboard page is very small (${D.length} bytes)`,fix:"Check app/(dashboard)/dashboard/page.tsx exists and the dashboard layout doesn't crash."}:await w.locator('text="Something went wrong"').isVisible().catch(()=>!1)?{pass:!1,detail:"Dashboard shows error boundary",fix:"A server component crashed. Check the page.tsx for unhandled null/undefined or missing database tables."}:{pass:!0,detail:`Loads (${D.length} bytes)`}});s.push(Q);let q=await w.evaluate(()=>{let D=[];return document.querySelectorAll("nav a[href], aside a[href]").forEach(ne=>{let v=ne.getAttribute("href");v&&v.startsWith("/")&&!v.startsWith("/api")&&!v.includes("[")&&v!=="/dashboard"&&v!=="/"&&!v.includes("/login")&&!v.includes("/sign")&&D.push(v)}),[...new Set(D)]});if(q.length>0){let D=0,Z=[];for(let ne of q.slice(0,8)){let v=await Rt(w,V(J,`Page: ${ne}`),async()=>{await w.goto(`${n}${ne}`,{waitUntil:"domcontentloaded",timeout:15e3}),await w.waitForLoadState("networkidle").catch(()=>{});let F=await w.title(),j=await w.content();return F.toLowerCase().includes("500")||F.toLowerCase().includes("server error")?{pass:!1,detail:"Page returns 500 server error",fix:"Server component crashed. Common causes: 1) Database tables missing. 2) Wrong ORM dialect (pgTable vs sqliteTable). 3) Unhandled null/undefined in server component."}:F.toLowerCase().includes("404")||F.toLowerCase().includes("not found")?{pass:!1,detail:"Page returns 404",fix:`Page ${ne} not found. Create the page or remove the nav link.`}:await w.locator('text="Something went wrong"').isVisible().catch(()=>!1)?{pass:!1,detail:"Page shows error boundary",fix:"A server component crashed. Check the page.tsx for unhandled errors."}:j.length<500?{pass:!1,detail:`Page is very small (${j.length} bytes)`,fix:"Page may not have rendered. Check the page component."}:{pass:!0,detail:"Loads without errors"}});v.status==="fail"&&(D++,Z.push(ne)),s.push(v)}}let O=await Rt(w,"Design quality",async()=>{let D=w.url().includes("/dashboard")?w.url():`${n}/dashboard`;w.url().includes("/dashboard")||(await w.goto(D,{waitUntil:"domcontentloaded",timeout:15e3}),await w.waitForLoadState("networkidle").catch(()=>{}));let Z=await w.evaluate(()=>{let ne=[],v=document.querySelectorAll("h1, h2, h3, h4, h5, h6"),F=new Set;v.forEach(W=>{F.add(window.getComputedStyle(W).fontSize)}),v.length>=3&&F.size<2&&ne.push("TYPOGRAPHY: All headings appear the same size. Create clear hierarchy with 3+ distinct sizes using a modular scale (1.25-1.5x ratio).");let j=Array.from(v).map(W=>parseInt(W.tagName.charAt(1),10));for(let W=1;W<j.length;W++)if(j[W]-j[W-1]>1){ne.push(`TYPOGRAPHY: Heading level skipped (h${j[W-1]} -> h${j[W]}). Use sequential heading levels for accessibility.`);break}let R=document.querySelectorAll("*"),he=!1,T=!1;R.forEach(W=>{let ce=window.getComputedStyle(W);ce.backgroundColor==="rgb(0, 0, 0)"&&W.clientHeight>100&&W.clientWidth>200&&(he=!0);let Ae=ce.backgroundColor,He=ce.color;if(Ae&&!Ae.includes("0, 0, 0")&&!Ae.includes("255, 255, 255")&&Ae!=="rgba(0, 0, 0, 0)"&&Ae!=="transparent"&&He.match(/rgb\((\d+), (\d+), (\d+)\)/)){let et=He.match(/rgb\((\d+), (\d+), (\d+)\)/);if(et){let[tt,De,g]=[parseInt(et[1]),parseInt(et[2]),parseInt(et[3])],y=Math.abs(tt-De)<10&&Math.abs(De-g)<10&&tt>80&&tt<180,k=Ae.match(/rgb\((\d+), (\d+), (\d+)\)/);if(y&&k){let[N,E,K]=[parseInt(k[1]),parseInt(k[2]),parseInt(k[3])];!(Math.abs(N-E)<15&&Math.abs(E-K)<15)&&W.textContent&&W.textContent.trim().length>0&&(T=!0)}}}}),he&&ne.push("COLOR: Pure black (#000) background detected on a large element. Use a tinted dark color instead (e.g. oklch(15% 0.01 hue) or a deep navy/charcoal)."),T&&ne.push("COLOR: Gray text on a colored background detected. Gray looks washed out on color. Use a darker shade of the background color or white instead.");let $e=document.querySelectorAll('[class*="card"], [class*="Card"], [role="group"]'),G=!1;$e.forEach(W=>{W.querySelectorAll('[class*="card"], [class*="Card"]').length>0&&(G=!0)}),G&&ne.push("LAYOUT: Nested cards detected (card inside card). Flatten the hierarchy. Use spacing and background color to create separation instead.");let ie=document.querySelectorAll("p, li, span, div"),ye=0,xe=0;ie.forEach(W=>{W.textContent&&W.textContent.trim().length>20&&W.clientHeight>0&&(xe++,window.getComputedStyle(W).textAlign==="center"&&ye++)}),xe>5&&ye/xe>.7&&ne.push("LAYOUT: Most text is center-aligned. Use left-alignment for body content and lists. Reserve center-alignment for heroes and CTAs.");let de=new Set;R.forEach(W=>{let ce=window.getComputedStyle(W);ce.gap&&ce.gap!=="normal"&&ce.gap!=="0px"&&de.add(ce.gap)}),de.size===1&&R.length>20&&ne.push("LAYOUT: Same gap value used everywhere. Vary spacing to create hierarchy: tight within groups (8-12px), generous between sections (32-64px).");let je=document.querySelectorAll("button, a, input, select, textarea");document.querySelectorAll("table, [role='table']").forEach(W=>{W.querySelectorAll("tbody tr").length===0&&(W.parentElement?.querySelector('[class*="empty"], [class*="Empty"], [class*="no-data"], [class*="placeholder"]')||ne.push("UX: Empty table with no empty state. Add a helpful message explaining what will appear here and a CTA to create the first item."))});let Ze=document.querySelectorAll("style"),rt=!1;Ze.forEach(W=>{let ce=W.textContent||"";(ce.includes("bounce")||ce.includes("elastic")||ce.match(/cubic-bezier\([^)]*[2-9]\.[0-9]/))&&(rt=!0)}),rt&&ne.push("MOTION: Bounce or elastic easing detected. These feel dated. Use smooth deceleration curves (Quart out, Expo out) instead.");let ze=document.querySelectorAll("button, a, input, select, [role='button']"),Re=[];if(ze.forEach(W=>{let ce=W.getBoundingClientRect();if(ce.width>0&&ce.height>0&&(ce.width<32||ce.height<32)){let Ae=W.tagName.toLowerCase(),He=W.id?`#${W.id}`:"",et=W.className&&typeof W.className=="string"?`.${W.className.split(/\s+/).filter(Boolean).slice(0,2).join(".")}`:"",tt=(W.textContent||"").trim().slice(0,40),De=`${Ae}${He}${et}`,g=`${Math.round(ce.width)}x${Math.round(ce.height)}px`;Re.push(tt?`${De} ("${tt}", ${g})`:`${De} (${g})`)}}),Re.length>3){let W=Re.slice(0,8).map((Ae,He)=>` ${He+1}. ${Ae}`).join(`
7695
- `),ce=Re.length>8?`
7696
- \u2026and ${Re.length-8} more.`:"";ne.push(`ACCESSIBILITY: ${Re.length} interactive elements are smaller than 32x32px (minimum recommended touch target is 44x44px). Add padding so each element's bounding box reaches 44x44.
7390
+ 5. After writing ALL files, call mist_implement to move to the next step. If you used any process.env.X that isn't already in mistflow.json env.required, pass it via envVarsRequired on this call so the dashboard can prompt the user for it (skip keys covered by integration presets \u2014 those are pre-declared). Do NOT pause to ask the user for permission between steps \u2014 proceed immediately. Do NOT offer options like "continue or stop" \u2014 the user already approved the build when they approved the plan. The previous step is auto-marked complete \u2014 do NOT call implement twice.`;let ne=fm(h),ce={stepNumber:c.number,totalSteps:i.steps.length,estimatedMinutes:ne,announcement:`Starting step ${c.number} of ${i.steps.length}: ${c.name}. This step usually takes ${ne.min}\u2013${ne.max} minutes.`},q=JSON.stringify({instruction:I,...v?{instructionFilePath:v}:{},step:{number:c.number,name:c.name,description:c.description,status:"in_progress"},stepTiming:ce,compactionGuidance:"If your context gets compacted mid-step (common on long builds), call mist_implement again immediately after the compaction finishes. The tool finds the in-progress step and returns fresh context \u2014 you don't need to re-read files or re-derive state.",...a?{autoCompleted:a}:{},...S.length>0?{generatedModules:S,generatedNote:"These CRUD modules were pre-generated. Review and customize them instead of writing from scratch. Focus on: business logic in actions.ts, UI polish, and wiring navigation."}:{},progress:`${p.length}/${i.steps.length} steps done`,nextAction:H}),P=Da(o),re=P?.url??"http://localhost:3000",z=P?.port??3e3;return await lm(z)&&Ea(o,c.number,i.steps.length)?(Na(o,c.number),ws(re,q)):u(q)}var Ya={name:"mist_implement",description:"Execute the next step (or a specific step) from the app plan. Reads mistflow.json, finds the target step, and returns rich context and detailed implementation instructions for the host AI. Auto-commits a checkpoint before changes for safe undo. Use when the user says 'mist implement' or 'mist next step'.",inputSchema:cm,handler:km};import{z as Ee}from"zod";import{resolve as xm}from"path";ke();Nt();async function Qa(t){let{projectPath:e,action:r,key:n,value:o,category:s,description:i,setupUrl:a}=t,l=xm(e??process.cwd());if(!Te())return Oe("manage env vars");let p=ut(l)?.projectId;if(!p)return u("No project ID found. Deploy your project first with mist_deploy, or initialize with mist_plan + mist_init + mist_install.",!0);try{switch(r){case"set":return n?o?(await Xr(p,n,o,{category:s,description:i,setupUrl:a}),u(JSON.stringify({set:!0,key:n,message:`Environment variable '${n}' has been set. It will be available on your next deployment.`}))):u("Value is required. Provide the env var value.",!0):u("Key is required. Provide the env var name like 'STRIPE_SECRET_KEY'.",!0);case"list":{let d=await Qr(p);return d.length===0?u(JSON.stringify({envVars:[],message:"No environment variables configured. Use action 'set' to add one."})):u(JSON.stringify({envVars:d.map(m=>({key:m.key,category:m.category,description:m.description,hasValue:m.has_value})),message:`${d.length} environment variable(s) configured.`}))}case"delete":return n?(await Zr(p,n),u(JSON.stringify({deleted:!0,key:n,message:`Environment variable '${n}' has been removed.`}))):u("Key is required. Provide the env var name to delete.",!0);default:return u(`Unknown action: ${r}. Use set, list, or delete.`,!0)}}catch(d){if(d instanceof Z)return u(d.message,!0);let m=d instanceof Error?d.message:"An unexpected error occurred";return u(m,!0)}}import{resolve as Sm,join as _m}from"path";import{existsSync as Tm,readFileSync as Cm,writeFileSync as Im}from"fs";ke();Nt();function Qo(t,e){let r=_m(t,"mistflow.json");if(!Tm(r))return;let n;try{n=JSON.parse(Cm(r,"utf-8"))}catch{return}n.domains=e,Im(r,JSON.stringify(n,null,2)+`
7391
+ `)}async function Xa(t){let{projectPath:e,action:r,domain:n,domainId:o}=t,s=Sm(e??process.cwd());if(!Te())return Oe("manage custom domains");let a=ut(s)?.projectId;if(!a)return u("No project ID found. Deploy your project first with mist_deploy, or initialize with mist_plan + mist_init + mist_install.",!0);try{switch(r){case"add":{if(!n)return u("Domain name is required. Provide the domain like 'myapp.com' or 'app.mycompany.com'.",!0);let l=await Kr(a,n),c=await vt(a);return Qo(s,c.map(p=>({domain:p.domain,status:p.status}))),u(JSON.stringify({added:!0,domain:l.domain,status:l.status,instructions:l.instructions,message:`Domain '${l.domain}' added. Set up DNS records as described, then use action 'verify' to check status.`}))}case"list":{let l=await vt(a);return l.length===0?u(JSON.stringify({domains:[],message:"No custom domains configured. Use action 'add' to add one."})):u(JSON.stringify({domains:l.map(c=>({id:c.id,domain:c.domain,status:c.status,ssl:c.ssl_status,error:c.error_message}))}))}case"verify":{if(!o){if(n){let d=(await vt(a)).find(m=>m.domain===n);if(d){let m=await ar(a,d.id);return u(JSON.stringify({domain:m.domain,status:m.status,ssl:m.ssl_status,error:m.error_message,message:m.status==="active"?`Domain '${m.domain}' is active and serving traffic.`:`Domain '${m.domain}' is ${m.status}. Make sure DNS records are configured correctly.`}))}return u(`Domain '${n}' not found. Use action 'list' to see configured domains.`,!0)}return u("Provide either domainId or domain name to verify.",!0)}let l=await ar(a,o),c=await vt(a);return Qo(s,c.map(p=>({domain:p.domain,status:p.status}))),u(JSON.stringify({domain:l.domain,status:l.status,ssl:l.ssl_status,error:l.error_message,message:l.status==="active"?`Domain '${l.domain}' is active and serving traffic.`:`Domain '${l.domain}' is ${l.status}. Make sure DNS records are configured correctly.`}))}case"remove":{if(!o&&!n)return u("Provide either domainId or domain name to remove.",!0);let l=o;if(!l&&n){let d=(await vt(a)).find(m=>m.domain===n);if(!d)return u(`Domain '${n}' not found.`,!0);l=d.id}await Vr(a,l);let c=await vt(a);return Qo(s,c.map(p=>({domain:p.domain,status:p.status}))),u(JSON.stringify({removed:!0,message:"Domain removed. It may take a few minutes for DNS changes to propagate."}))}default:return u(`Unknown action: ${r}. Use add, list, verify, or remove.`,!0)}}catch(l){if(l instanceof Z)return u(l.message,!0);let c=l instanceof Error?l.message:"An unexpected error occurred";return u(c,!0)}}var Za=["create","list","revoke"];function Pm(t){return typeof t=="string"&&Za.includes(t)}var el=async t=>{let e=t??{};if(!Pm(e.action))return u(`Unknown action: ${String(e.action)}. Use one of: ${Za.join(", ")}.`,!0);switch(e.action){case"create":return u(Rm(e),!1);case"list":return u(Am(),!1);case"revoke":return u(Em(e),!1)}};function Rm(t){let e=typeof t.name=="string"&&t.name.trim().length>0?t.name.trim():"<caller-name>",r=typeof t.monthlyBudgetCents=="string"?t.monthlyBudgetCents:"0",n=Array.isArray(t.toolAllowlist)?JSON.stringify(t.toolAllowlist):"null",o=typeof t.rateLimitPerMin=="number"&&Number.isFinite(t.rateLimitPerMin)&&t.rateLimitPerMin>=0?String(t.rateLimitPerMin):"null";return["# Create an MCP caller in the user's deployed app","","The MCP extension generated `createMcpCaller()` in `lib/mcp/caller-auth.ts` of the user's app.","Callers live in the app's own database (the `mcp_caller` table), NOT in the Mistflow backend.","","## Recommended approach","","Run this as a one-off script in the user's app (e.g. in `scripts/mcp-create-caller.ts`):","","```ts","import { createMcpCaller } from '@/lib/mcp/caller-auth'","import { getMcpDb } from '@/lib/mcp/caller-auth'","","const result = await createMcpCaller(getMcpDb(process.env as never), {",` name: ${JSON.stringify(e)},`,` monthlyBudgetCents: ${JSON.stringify(r)},`,` toolAllowlist: ${n},`,` rateLimitPerMin: ${o},`,"})","","// The plaintext token is shown ONCE. Capture it now; only the hash is stored.","console.error('caller created', result.callerId, result.tokenHashPrefix)","console.log(result.token)","```","","## Security","- Run on a trusted machine; the script prints the raw bearer token.","- Store the token in a password manager. The DB only retains the hash.","- If you lose the token, revoke the caller and create a new one."].join(`
7392
+ `)}function Am(){return["# List MCP callers in the user's deployed app","","```ts","import { listMcpCallers, getMcpDb } from '@/lib/mcp/caller-auth'","","const callers = await listMcpCallers(getMcpDb(process.env as never))","console.table(callers.map(({ id, name, tokenHashPrefix, monthlyBudgetCents, revokedAt }) => ({"," id, name, tokenHashPrefix, monthlyBudgetCents, revokedAt,","})))","```","","Listing never returns the raw bearer token (only the hash prefix). To rotate a token, revoke and re-create."].join(`
7393
+ `)}function Em(t){let e=typeof t.callerId=="string"&&t.callerId.trim().length>0?t.callerId.trim():"<caller-id>";return["# Revoke an MCP caller in the user's deployed app","","```ts","import { revokeMcpCaller, getMcpDb } from '@/lib/mcp/caller-auth'","",`await revokeMcpCaller(getMcpDb(process.env as never), ${JSON.stringify(e)})`,"```","","After revocation the caller's bearer token will be rejected by `authenticateCaller()` with code `invalid_or_revoked`."].join(`
7394
+ `)}var Nm=Ee.object({resource:Ee.enum(["env","domain","mcp-caller"]).describe("'env' manages app secrets and configuration values. 'domain' manages custom domains. 'mcp-caller' manages bearer tokens for the app's MCP endpoint (create/list/revoke)."),action:Ee.string().describe("Action to perform. env: 'set', 'list', 'delete'. domain: 'add', 'list', 'verify', 'remove'. mcp-caller: 'create', 'list', 'revoke'."),projectPath:Ee.string().optional().describe("Path to the project directory (default: cwd)"),key:Ee.string().optional().describe("(env) Variable name"),value:Ee.string().optional().describe("(env set) Variable value"),category:Ee.string().optional().describe("(env set) Category"),description:Ee.string().optional().describe("(env set) Description"),setupUrl:Ee.string().optional().describe("(env set) URL to obtain the value"),domain:Ee.string().optional().describe("(domain) Domain name"),domainId:Ee.string().optional().describe("(domain) Domain ID"),name:Ee.string().optional().describe("(mcp-caller create) Human-readable name"),callerId:Ee.string().optional().describe("(mcp-caller revoke) The caller id to revoke"),monthlyBudgetCents:Ee.string().optional().describe("(mcp-caller create) Budget cap in cents as a string. '0' means unlimited."),toolAllowlist:Ee.array(Ee.string()).optional().describe("(mcp-caller create) Restrict the caller to these MCP tool names. Omit for unrestricted."),rateLimitPerMin:Ee.number().optional().describe("(mcp-caller create) Per-minute rate limit on this caller's tool calls.")}),tl={name:"mist_config",description:"Manage project configuration: app secrets and custom domains. Set resource='env' to manage encrypted app secrets (set, list, delete). Set resource='domain' to manage custom domains (add, list, verify, remove). Use when the user says 'mist env' or 'mist domain'.",inputSchema:Nm,handler:async t=>{let e=t;switch(e.resource){case"env":return Qa({projectPath:e.projectPath,action:e.action,key:e.key,value:e.value,category:e.category,description:e.description,setupUrl:e.setupUrl});case"domain":return Xa({projectPath:e.projectPath,action:e.action,domain:e.domain,domainId:e.domainId});case"mcp-caller":return el({action:e.action,name:e.name,callerId:e.callerId,monthlyBudgetCents:e.monthlyBudgetCents,toolAllowlist:e.toolAllowlist,rateLimitPerMin:e.rateLimitPerMin});default:return u(`Unknown resource: ${e.resource}. Use env, domain, or mcp-caller.`,!0)}}};import{z as Bn}from"zod";import{resolve as Om}from"path";import{existsSync as Mm}from"fs";import{join as jm}from"path";function Dm(t,e=15){let r=t.split(`
7395
+ `).filter(n=>n.length>0);return r.length<=e?t:r.slice(-e).join(`
7396
+ `)}var Lm=Bn.object({projectPath:Bn.string().optional().describe("Absolute path to the project. Required for the first call (to start the install)."),jobId:Bn.string().optional().describe("Job ID returned from a previous call. Present means poll; absent means start."),waitSeconds:Bn.number().min(0).max(50).optional().describe("On poll calls: long-poll for up to N seconds before returning, so a typical install fits in 2 round-trips instead of 5-6. Default 20. Set 0 to disable."),sessionId:Bn.string().uuid().optional().describe("Backend session ID. Pass through from mist_plan response so state guards apply and session state advances as the tool runs.")}).refine(t=>!!t.projectPath||!!t.jobId,{message:"Pass projectPath to start an install, or jobId to poll an existing one."}),nl={name:"mist_install",description:"Install a Mistflow project's npm dependencies with the fire-and-poll pattern. First call with { projectPath } starts the install and returns a jobId. Subsequent calls with { jobId } poll for progress (status: 'running' | 'complete' | 'failed'). Re-call while status is 'running' \u2014 each response is <1s so there is no 60s timeout risk.",inputSchema:Lm,handler:async(t,e)=>{let r=t;if(r.jobId){let l=await Pt(r.jobId);if(!l)return u(JSON.stringify({status:"not_found",jobId:r.jobId,message:`No install job found for jobId '${r.jobId}'. Start a new one with { projectPath }.`}),!0);let c=r.waitSeconds??20;if(c>0&&(l.status==="running"||l.status==="starting")){let d=l.elapsed,m=e?tt(e.server,e.progressToken,()=>`Install running (${d}) \u2014 waiting for terminal state`):{stop:()=>{}};try{let h=await dn(r.jobId,{timeoutMs:c*1e3,onPoll:S=>{d=S.elapsed}});h&&(l=h)}finally{m.stop()}}if(l.status==="running"||l.status==="starting")return u(JSON.stringify({status:"running",jobId:l.id,elapsed:l.elapsed,logTail:Dm(l.logTail),nextAction:"Install still running after long-poll window. Call mist_install again with the same jobId to continue waiting."}));if(l.status==="complete"){let d=await La(l.cwd),m=!1;d&&d.freshlyStarted?(m=await Oa(d.state.url,15e3),m&&$a(d.state.url)):d&&(m=!0);let h=d?m?` PREVIEW: Live dev server at ${d.state.url} (browser opened). TELL THE USER: "Your app is rendering at ${d.state.url} \u2014 it'll refresh live as it's built."`:` PREVIEW: Dev server starting at ${d.state.url} (still compiling). TELL THE USER: "Open ${d.state.url} in your browser in ~30s \u2014 your app will appear there and refresh live as it's built."`:"";return u(JSON.stringify({status:"complete",jobId:l.id,elapsed:l.elapsed,exitCode:l.exitCode,...d?{previewUrl:d.state.url,previewReady:m}:{},nextAction:`Dependencies installed.${h} Run mist_implement next to start executing plan steps.`}))}let p=zt(l.id);return u(JSON.stringify({status:l.status,jobId:l.id,elapsed:l.elapsed,exitCode:l.exitCode,logTail:l.logTail,logPaths:p,nextAction:`npm install failed. Read the logTail for the error and fix it \u2014 usually a missing native dep or a version conflict. If the 200-line tail is truncated, read the full log from logPaths.stderr (${p.stderr}) with your file-read tool. After fixing, start a new install with { projectPath }.`}),!0)}let n=Om(r.projectPath);if(!Mm(jm(n,"package.json")))return u(`No package.json at ${n}. Confirm the path and that mist_init has scaffolded the project.`,!0);let i=`npm install && (${`npx --yes shadcn@latest add -y -o ${["button","card","input","label","form","dialog","table","dropdown-menu","badge","separator","skeleton","sheet","tabs","avatar","select","textarea","checkbox","switch","tooltip","popover","sonner"].join(" ")}`} || echo 'shadcn add failed \u2014 implement will retry lazily')`,a=await It({type:"install",cmd:"sh",args:["-c",i],cwd:n,env:{NPM_CONFIG_LEGACY_PEER_DEPS:"true"}});return u(JSON.stringify({status:"running",jobId:a.id,startedAt:a.startedAt,cwd:n,nextAction:"Install started in the background (npm install + shadcn components). Call mist_install again with { jobId: '"+a.id+"' } immediately \u2014 each poll long-polls for up to 20s server-side so there's no need to sleep between calls. Typical duration: 30-90s."}))}};import{z as un}from"zod";import{resolve as $m,join as rt}from"path";import{existsSync as it,readFileSync as rl,statSync as Um,readdirSync as Fm}from"fs";var ol=100*1024;function qm(t){let e=0,r=0,n=[t];for(;n.length>0&&r<1e4;){let o=n.pop();r++;let s;try{s=Fm(o)}catch{continue}for(let i of s){let a=rt(o,i),l;try{l=Um(a)}catch{continue}l.isDirectory()?n.push(a):l.isFile()&&(e+=l.size)}}return e}function Bm(t){if(!(it(rt(t,"open-next.config.ts"))||it(rt(t,"open-next.config.js"))))return null;let r=rt(t,".open-next","worker.js");if(!it(r))return`Build exited 0 but .open-next/worker.js is missing at ${r}. The Cloudflare adapter did not produce a worker entrypoint. Common causes: (1) open-next.config.ts is misconfigured, (2) a client component imports a Node-only API (fs, path, crypto node:* imports in 'use client' files), (3) the adapter crashed silently mid-build. Check the logTail above for "error"/"failed" messages, fix the offending file, and re-run mist_build.`;let n=rt(t,".open-next","server-functions","default");if(!it(n))return'Build exited 0 and .open-next/worker.js exists, but .open-next/server-functions/default/ is missing. The OpenNext adapter produced an orchestrator with no server bundle to import. Check the logTail above for "error"/"failed" messages near "Building server function: default..." and re-run mist_build.';let o=qm(n);return o<ol?`Build exited 0 but the server-functions bundle is only ${o} bytes (expected at least ${ol}). A healthy Next.js + OpenNext bundle is 5-50MB. This size suggests the adapter failed to bundle most of the app. Check the logTail for bundling errors and re-run mist_build.`:null}var Hm=un.object({projectPath:un.string().optional().describe("Absolute path to the project. Required for the first call."),jobId:un.string().optional().describe("Job ID from a previous call. Present means poll; absent means start."),script:un.string().optional().describe("Optional override: run `npm run <script>` instead of the Cloudflare adapter. Default behavior is `npx @opennextjs/cloudflare build` when open-next.config.ts exists, else `npm run build`. Ignored on poll calls."),waitSeconds:un.number().min(0).max(50).optional().describe("On poll calls: long-poll for up to N seconds before returning, so a typical build fits in 2 round-trips instead of 6-8. Default 20. Set 0 to disable."),sessionId:un.string().uuid().optional().describe("Backend session ID. Pass through from mist_plan response so state guards apply and session state advances as the tool runs.")}).refine(t=>!!t.projectPath||!!t.jobId,{message:"Pass projectPath to start a build, or jobId to poll an existing one."});function zm(t){let e=rt(_r(),t,"stdout.log"),r=rt(_r(),t,"stderr.log"),n="";try{it(e)&&(n+=rl(e,"utf-8"))}catch{}try{it(r)&&(n+=`
7397
+ `+rl(r,"utf-8"))}catch{}return n}function Wm(t,e=15){let r=t.split(`
7398
+ `).filter(n=>n.length>0);return r.length<=e?t:r.slice(-e).join(`
7399
+ `)}function Gm(t){let e=[/Module not found:\s*(?:Error:\s*)?Can't resolve ['"]([^'"]+)['"]/g,/Module not found:\s*(?:Error:\s*)?Can't find ['"]([^'"]+)['"]/g,/Cannot find module ['"]([^'"]+)['"]/g],r=new Set;for(let n of e)for(let o of t.matchAll(n)){let s=o[1];if(s.startsWith(".")||s.startsWith("@/")||s.startsWith("~/"))continue;let i=s.startsWith("@")?s.split("/").slice(0,2).join("/"):s.split("/")[0];i&&r.add(i)}return[...r]}function Jm(t){return t.includes(".next/diagnostics/build-diagnostics.json")?`The build log shows Next/OpenNext failed while reading .next/diagnostics/build-diagnostics.json. ${qt()} This has shown up when the adapter runs under an unsupported Node 22 patch release.`:null}var sl={name:"mist_build",description:"Run a Mistflow project's production build with the fire-and-poll pattern. First call with { projectPath } starts `npm run build` and returns a jobId. Subsequent calls with { jobId } poll status. On failure, the response surfaces structured errors (from parseBuildErrors) and any missingModules[] \u2014 chain into mist_install with those modules, then mist_build again, without asking the user.",inputSchema:Hm,handler:async(t,e)=>{let r=t;if(r.jobId){let c=await Pt(r.jobId);if(!c)return u(JSON.stringify({status:"not_found",jobId:r.jobId,message:`No build job found for jobId '${r.jobId}'. Start a new one with { projectPath }.`}),!0);let p=r.waitSeconds??20;if(p>0&&(c.status==="running"||c.status==="starting")){let E=c.elapsed,J=e?tt(e.server,e.progressToken,()=>`Build running (${E}) \u2014 waiting for terminal state`):{stop:()=>{}};try{let H=await dn(r.jobId,{timeoutMs:p*1e3,onPoll:ne=>{E=ne.elapsed}});H&&(c=H)}finally{J.stop()}}if(c.status==="running"||c.status==="starting")return u(JSON.stringify({status:"running",jobId:c.id,elapsed:c.elapsed,logTail:Wm(c.logTail),nextAction:"Build still running after long-poll window. Call mist_build again with the same jobId to continue waiting."}));if(c.status==="complete"){let E=Bm(c.cwd);if(E){let J=zt(c.id);return u(JSON.stringify({status:"failed",jobId:c.id,elapsed:c.elapsed,exitCode:c.exitCode,logTail:c.logTail,logPaths:J,error:E,nextAction:E+` Full build log: ${J.stdout} and ${J.stderr}.`}),!0)}return u(JSON.stringify({status:"complete",jobId:c.id,elapsed:c.elapsed,exitCode:c.exitCode,nextAction:"Build passed. Run mist_deploy next to ship \u2014 do NOT ask the user to confirm, the build is the approval gate."}))}let d=zm(c.id),m=pr(d),h=Gm(d),S=Jm(d),I=zt(c.id),y=` Full log: ${I.stdout} and ${I.stderr}.`,v=h.length>0?`Build failed with missing modules: ${h.join(", ")}. Call mist_install with these packages (or chain mist_install { projectPath } to reinstall all), then call mist_build again. Do NOT ask the user.${y}`:S?`${S}${y}`:m.length>0?`Build failed with structured errors (see errors[]). Fix the code, then call mist_build again.${y}`:`Build failed. Read the logTail for the error, or call mist_debug with the failed jobId to extract structured errors.${y}`;return u(JSON.stringify({status:c.status,jobId:c.id,elapsed:c.elapsed,exitCode:c.exitCode,errors:m,missingModules:h,logTail:c.logTail,logPaths:I,nextAction:v}),!0)}let n=$m(r.projectPath);if(!it(rt(n,"package.json")))return u(`No package.json at ${n}. Run mist_init first.`,!0);if(!it(rt(n,"node_modules")))return u(`node_modules not installed. Run mist_install { projectPath: '${n}' } first.`,!0);let o,s,i,a=it(rt(n,"open-next.config.ts"))||it(rt(n,"open-next.config.js"));if(r.script)o="npm",s=["run",r.script],i=r.script;else if(a){if(!rn())return u(qt(),!0);o="npx",s=["-y","@opennextjs/cloudflare","build"],i="@opennextjs/cloudflare build"}else o="npm",s=["run","build"],i="build";let l=await It({type:"build",cmd:o,args:s,cwd:n});return u(JSON.stringify({status:"running",jobId:l.id,startedAt:l.startedAt,cwd:n,script:i,nextAction:`Build started. Call mist_build again with { jobId: '${l.id}' } immediately \u2014 each poll long-polls for up to 20s server-side so there's no need to sleep between calls. Typical duration: 30-90s on a fresh scaffold (Cloudflare adapter), 10-30s on subsequent builds.`}))}};import{existsSync as Xm,readFileSync as Zm}from"fs";import{join as eh}from"path";import{z as pn}from"zod";ke();ke();import{existsSync as Km,readdirSync as Vm}from"fs";import{join as il}from"path";import{pathToFileURL as Ym}from"url";async function Qm(t){let e=il(t,".mistflow","acceptance"),r=new Map;if(!Km(e))return r;let n=Vm(e).filter(o=>o.endsWith(".spec.ts")||o.endsWith(".spec.js")||o.endsWith(".spec.mjs"));for(let o of n){let s=o.replace(/\.spec\.(ts|js|mjs)$/,"").trim();if(!s)continue;let i=il(e,o);try{let a=await import(Ym(i).href),l=a.default??a.run;typeof l=="function"?r.set(s,l):console.error(`Probe ${o} doesn't export a default function (or named 'run'). Skipping.`)}catch(a){console.error(`Failed to load probe ${o}:`,a instanceof Error?a.message:a)}}return r}async function al(t,e){let{sessionId:r,projectPath:n,deployUrl:o,seedCredentials:s}=e,a=(await Sn(r)).criteria,l=await Qm(n),c=[];for(let p of a){let d=l.get(p.id);if(!d){c.push({criterion_id:p.id,status:"skipped",evidence:{reason:`no probe at .mistflow/acceptance/${p.id}.spec.ts`}});continue}try{await t.goto(o,{waitUntil:"domcontentloaded"});let m=await d(t,{url:o,criterion:p,seedCredentials:s});c.push({criterion_id:p.id,status:m.pass?"pass":"fail",evidence:{...m.detail?{detail:m.detail}:{},...m.screenshot?{screenshot:m.screenshot}:{}}})}catch(m){c.push({criterion_id:p.id,status:"fail",evidence:{error:m instanceof Error?m.message:"probe threw an unknown error"}})}}return{criteria:a,results:c}}var th=pn.object({projectPath:pn.string().optional().describe("Absolute path to the Mistflow project. Defaults to the current working directory. Used to read mistflow.json for the deploy URL + projectId."),url:pn.string().optional().describe("Override the deploy URL to QA. Useful for previews or when mistflow.json doesn't yet have the URL recorded. Defaults to mistflow.json's deploy.url."),deploymentId:pn.string().optional().describe("Deployment ID to associate QA results with. When set, results are uploaded to the backend and surfaced on the dashboard deployment card."),sessionId:pn.string().uuid().optional().describe("Backend session ID. Pass through from mist_plan response so state guards apply and session state advances as the tool runs."),action:pn.enum(["full","acceptance"]).default("full").describe("'full' (default) runs the standard post-deploy QA suite: HTTP pre-flight, landing render, login, sidebar pages, design audits. 'acceptance' runs only the per-criterion Playwright probes from .mistflow/acceptance/<criterion-id>.spec.ts against the deploy URL and uploads pass/fail results to the backend. Requires sessionId.")}),dl={name:"mist_qa",description:"Post-deploy QA: drives Playwright in-process against the live deploy URL. Checks health + auth endpoints, logs in with a seeded admin account, walks the dashboard and sidebar pages, and runs design-quality audits (typography, color, layout, slop patterns). Returns pass/fail + screenshots inline. Call ONCE after mist_deploy completes. Do NOT surface the deploy URL to the user until mist_qa returns status: 'pass'.",inputSchema:th,handler:async t=>{let e=t;return e.action==="acceptance"?rh(e):oh(e)}};function ul(t){let e=eh(t,"mistflow.json");if(!Xm(e))return null;try{return JSON.parse(Zm(e,"utf-8"))}catch{return null}}async function ll(t){try{let e=await fetch(t,{redirect:"follow",signal:AbortSignal.timeout(15e3)}),r=await e.text();return{status:e.status,body:r}}catch(e){return{status:0,body:String(e)}}}async function nh(t,e,r){try{let n=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json",...r??{}},body:JSON.stringify(e),redirect:"follow",signal:AbortSignal.timeout(15e3)}),o=await n.text(),s;try{s=JSON.parse(o)}catch{}return{status:n.status,json:s}}catch{return{status:0}}}async function cl(t){try{let e=await t.screenshot({type:"png"});return Buffer.from(e).toString("base64")}catch{return}}async function At(t,e,r){let n=[],o=s=>{s.type()==="error"&&n.push(s.text())};t.on("console",o);try{let s=await r(),i=await cl(t);return{name:e,status:s.pass?"pass":"fail",detail:s.detail,fix:s.fix,screenshot:i,consoleErrors:n.length>0?n:void 0}}catch(s){let i=await cl(t);return{name:e,status:"fail",detail:`Unexpected error: ${s instanceof Error?s.message:String(s)}`,screenshot:i,consoleErrors:n.length>0?n:void 0}}finally{t.removeListener("console",o)}}async function rh(t){if(!t.sessionId)return u("mist_qa action='acceptance' requires sessionId. Pass the session ID from your mist_plan response.",!0);let e=t.projectPath??process.cwd(),r=ul(e),n=t.url;if(!n&&t.deploymentId)try{let a=await wt(t.deploymentId);a.url&&(n=a.url)}catch(a){console.error(`[acceptance] Could not resolve URL from deploymentId ${t.deploymentId}:`,a instanceof Error?a.message:String(a))}if(n||(n=r?.deploy?.url),!n)return u("No deploy URL found. Deploy the app first with mist_deploy, then call mist_qa action='acceptance'.",!0);let o;if(t.deploymentId)try{let l=await lr(t.deploymentId);l?.adminEmail&&l?.adminPassword&&(o={email:l.adminEmail,password:l.adminPassword})}catch(a){console.error("[acceptance] getSeedInfo failed (continuing without):",a instanceof Error?a.message:String(a))}let s,i;try{let{getIsolatedContext:a}=await Promise.resolve().then(()=>(Wt(),kn)),l=await a();s=l.context,i=l.page}catch(a){return u(`Playwright not available locally \u2014 install it with \`npx playwright install chromium\`. Detail: ${a instanceof Error?a.message:String(a)}`,!0)}try{let{criteria:a,results:l}=await al(i,{sessionId:t.sessionId,projectPath:e,deployUrl:n,seedCredentials:o});try{await ko(t.sessionId,l)}catch(m){console.error("[acceptance] submitAcceptanceResults failed:",m instanceof Error?m.message:String(m))}let c=l.filter(m=>m.status==="pass").length,p=l.filter(m=>m.status==="fail").length,d=l.filter(m=>m.status==="skipped").length;return u(JSON.stringify({status:p===0?"pass":"fail",url:n,totals:{passed:c,failed:p,skipped:d,total:a.length},results:l.map(m=>({criterion_id:m.criterion_id,status:m.status,detail:m.evidence&&typeof m.evidence=="object"&&"detail"in m.evidence?m.evidence.detail:m.evidence&&typeof m.evidence=="object"&&"reason"in m.evidence?m.evidence.reason:void 0})),nextAction:p===0?"All required acceptance criteria passed. The app is shippable.":"Some criteria failed. Read the per-criterion detail, fix the underlying code, then re-run mist_qa action='acceptance'."}),p>0)}finally{if(s)try{await s.close()}catch{}}}async function oh(t){let e=t.projectPath??process.cwd(),r=ul(e),n=t.url;if(!n&&t.deploymentId)try{let E=await wt(t.deploymentId);E.url&&(n=E.url)}catch(E){console.error(`[qa] Could not resolve URL from deploymentId ${t.deploymentId}:`,E instanceof Error?E.message:String(E))}if(n||(n=r?.deploy?.url),!n)return u("No deploy URL found. Deploy the app first with mist_deploy, then call mist_qa.",!0);n.startsWith("http")||(n=`https://${n}`);let o=r?.projectId,s=[],i=await ll(`${n}/api/health`);if(i.status!==200)return s.push({name:"Health endpoint",status:"fail",detail:`Returns ${i.status}`,fix:"The worker is not running or crashed on startup. Check app/api/health/route.ts exists and the build succeeded."}),Er(n,s);s.push({name:"Health endpoint",status:"pass",detail:"Returns 200"});let a=await ll(`${n}/api/auth/ok`);if(a.status!==200)return s.push({name:"Auth system",status:"fail",detail:`Auth endpoint returns ${a.status}`,fix:"Better Auth is not working. Check lib/auth.ts, lib/db.ts, and that your database env vars are set."}),Er(n,s);s.push({name:"Auth system",status:"pass",detail:"Better Auth running"});let l="qa-actor@qa.mistflow.local",c="qa-user@qa.mistflow.local",p="QaTemp1!",d="single",m="user",h=[];async function S(E,J,H,ne){let ce=await nh(`${n}/api/admin/seed`,{token:ne,email:J,password:p,role:H});if(ce.status!==200||!ce.json)return console.error(`[qa] Seed (${E}) returned ${ce.status}`),null;let ee=ce.json.sessionToken;if(!ee)return null;let q=ce.json.seeded===!0;return{role:E,email:J,sessionToken:ee,password:q?p:void 0}}if(o){let E=await lr(o);if(E){if(d=E.authMode??"single",m=E.defaultRole??"user",console.error(`[qa] auth_mode=${d}, default_role=${m}`),d!=="none"){let H=await S("actor",l,d==="multi"?"admin":m,E.seedToken);if(H&&h.push(H),d==="multi"){let ne=await S("user",c,m,E.seedToken);ne?h.push(ne):s.push({name:"Auth session (user)",status:"fail",detail:`Seed endpoint did not create the default-role user (${m}). Multi-role walk degraded to admin-only.`,fix:"Check that /api/admin/seed accepts a `role` parameter and creates users with non-admin roles. The scaffold ships this support; if customised, ensure the role argument is honored."})}}}else console.error("[qa] No seed info from backend \u2014 assuming auth-disabled or pre-deploy state"),d="none"}if(d!=="none"&&h.length===0)return s.push({name:"Auth session",status:"fail",detail:"Could not acquire a session token from the seed endpoint",fix:"Redeploy the app with mist_deploy. The deploy injects ADMIN_SEED_TOKEN and stores it on the backend; the scaffold's /api/admin/seed route accepts { token, email, password, role } and creates synthetic QA users with the requested role."}),Er(n,s);let I,y,v;try{let{getIsolatedContext:E}=await Promise.resolve().then(()=>(Wt(),kn)),J=await E();I=J.context,v=J.page}catch(E){let J=E instanceof Error?E.message:String(E);return u(JSON.stringify({status:"cannot_verify",url:n,deployed:!0,reason:"App deployed successfully, but QA could not verify it via automated browser testing because Playwright is not installed locally.",detail:J,httpChecks:s.map(({screenshot:H,...ne})=>ne),fix:"Run: npx playwright install chromium",instruction:[`The app deployed and is live at ${n}. That part succeeded.`,"What DID NOT succeed: automated QA verification. Playwright is not installed locally, so we could not open the app in a browser and check that the landing renders, signup works, and the dashboard loads.","Tell the user BOTH facts clearly \u2014 don't conflate 'deployed' with 'verified':",` "Your app is live at ${n}. I couldn't run automated QA because Playwright isn't installed locally. Run \`npx playwright install chromium\` (one-time, ~150MB) and I'll verify it for you \u2014 or just open the URL and try it yourself."`,"HTTP checks (health/auth endpoints) passed \u2014 those prove the server is responding. They do not prove the UI renders or user flows work.","After Playwright is installed, call mist_qa again for full verification."].join(`
7400
+ `)}),!1)}try{let E=await At(v,"Landing page",async()=>{await v.goto(n,{waitUntil:"domcontentloaded",timeout:3e4}),await v.waitForLoadState("networkidle").catch(()=>{});let ee=await v.evaluate(()=>{let P=document.body;if(!P)return"";let re=document.createTreeWalker(P,NodeFilter.SHOW_TEXT),z="",ie;for(;ie=re.nextNode();){let _=ie.parentElement;if(_){let B=window.getComputedStyle(_);B.display!=="none"&&B.visibility!=="hidden"&&parseFloat(B.opacity)>0&&(z+=ie.textContent?.trim()+" ")}}return z.trim()});if(ee.length<50)return{pass:!1,detail:`Landing page appears blank (${ee.length} chars visible). Likely a CSS/JS rendering issue.`,fix:"Common cause: motion/react animations with opacity:0 and whileInView that never trigger on Mistflow Cloud's edge runtime (no Intersection Observer). Replace with CSS animations or set initial={{ opacity: 1 }}."};let q=v.url();return q.includes("/login")||q.includes("/sign-in")?{pass:!1,detail:"Root URL redirects to login instead of showing a landing page",fix:"Check middleware.ts: '/' must be in PUBLIC_EXACT. Check app/page.tsx: must be a landing page, not a redirect."}:{pass:!0,detail:`Renders visible content (${ee.length} chars)`}});s.push(E),d==="none"&&s.push({name:"Auth walk",status:"pass",detail:"Skipped \u2014 app has no authentication (authModel=none)."});let J=h[0],H=(ee,q)=>h.length>1&&ee?`${q} (${ee.role})`:q,ne=!1;if(J?.password){let ee=await At(v,H(J,"Login"),async()=>{await v.goto(`${n}/login`,{waitUntil:"domcontentloaded",timeout:15e3}),await v.waitForLoadState("networkidle").catch(()=>{});let q=v.locator('input[type="email"], input[name="email"], input[placeholder*="email" i]'),P=v.locator('input[type="password"], input[name="password"]');try{await q.first().waitFor({state:"visible",timeout:1e4})}catch{return{pass:!1,detail:"Login page has no visible email input field",fix:"Check app/(auth)/login/page.tsx renders a login form with email and password inputs."}}await q.first().fill(J.email),await P.first().fill(J.password),await v.locator('button[type="submit"], button:has-text("Sign in"), button:has-text("Log in"), button:has-text("Login")').first().click();try{await v.waitForURL(z=>!z.pathname.includes("/login"),{timeout:1e4})}catch{let z=await v.locator('[role="alert"], .text-red-500, .text-destructive, [data-error]').first().textContent().catch(()=>null);return{pass:!1,detail:z?`Login failed: ${z}`:"Login did not redirect. Page stayed on /login.",fix:"Do NOT edit lib/auth.ts to disable email verification. Redeploy with mist_deploy to re-seed the verified QA accounts."}}return ne=!0,{pass:!0,detail:`Logged in, redirected to ${v.url()}`}});s.push(ee)}else J&&s.push({name:H(J,"Login"),status:"pass",detail:"Skipped form login (QA user already exists from previous deploy). Using session injection."});if(!ne&&J){let ee=new URL(n).hostname,q=n.startsWith("https");await I.addCookies([{name:q?"__Secure-better-auth.session_token":"better-auth.session_token",value:J.sessionToken,domain:ee,path:"/",httpOnly:!0,secure:q,sameSite:"Lax"}]),console.error(`[qa] Injected ${J.role} cookie for primary walk`)}if(J){let ee=await At(v,H(J,"Dashboard"),async()=>{v.url().includes("/dashboard")||(await v.goto(`${n}/`,{waitUntil:"domcontentloaded",timeout:15e3}),await v.waitForLoadState("networkidle").catch(()=>{}),new URL(v.url()).pathname==="/"&&(await v.goto(`${n}/dashboard`,{waitUntil:"domcontentloaded",timeout:15e3}),await v.waitForLoadState("networkidle").catch(()=>{})));let re=await v.content();return re.length<1e3?{pass:!1,detail:`Dashboard page is very small (${re.length} bytes)`,fix:"Check app/(dashboard)/dashboard/page.tsx exists and the dashboard layout doesn't crash."}:await v.locator('text="Something went wrong"').isVisible().catch(()=>!1)?{pass:!1,detail:"Dashboard shows error boundary",fix:"A server component crashed. Check the page.tsx for unhandled null/undefined or missing database tables."}:{pass:!0,detail:`Loads (${re.length} bytes)`}});s.push(ee);let q=await v.evaluate(()=>{let re=[];return document.querySelectorAll("nav a[href], aside a[href]").forEach(ie=>{let _=ie.getAttribute("href");_&&_.startsWith("/")&&!_.startsWith("/api")&&!_.includes("[")&&_!=="/dashboard"&&_!=="/"&&!_.includes("/login")&&!_.includes("/sign")&&re.push(_)}),[...new Set(re)]});if(q.length>0){let re=0,z=[];for(let ie of q.slice(0,8)){let _=await At(v,H(J,`Page: ${ie}`),async()=>{await v.goto(`${n}${ie}`,{waitUntil:"domcontentloaded",timeout:15e3}),await v.waitForLoadState("networkidle").catch(()=>{});let B=await v.title(),L=await v.content();return B.toLowerCase().includes("500")||B.toLowerCase().includes("server error")?{pass:!1,detail:"Page returns 500 server error",fix:"Server component crashed. Common causes: 1) Database tables missing. 2) Wrong ORM dialect (pgTable vs sqliteTable). 3) Unhandled null/undefined in server component."}:B.toLowerCase().includes("404")||B.toLowerCase().includes("not found")?{pass:!1,detail:"Page returns 404",fix:`Page ${ie} not found. Create the page or remove the nav link.`}:await v.locator('text="Something went wrong"').isVisible().catch(()=>!1)?{pass:!1,detail:"Page shows error boundary",fix:"A server component crashed. Check the page.tsx for unhandled errors."}:L.length<500?{pass:!1,detail:`Page is very small (${L.length} bytes)`,fix:"Page may not have rendered. Check the page component."}:{pass:!0,detail:"Loads without errors"}});_.status==="fail"&&(re++,z.push(ie)),s.push(_)}}let P=await At(v,"Design quality",async()=>{let re=v.url().includes("/dashboard")?v.url():`${n}/dashboard`;v.url().includes("/dashboard")||(await v.goto(re,{waitUntil:"domcontentloaded",timeout:15e3}),await v.waitForLoadState("networkidle").catch(()=>{}));let z=await v.evaluate(()=>{let ie=[],_=document.querySelectorAll("h1, h2, h3, h4, h5, h6"),B=new Set;_.forEach(O=>{B.add(window.getComputedStyle(O).fontSize)}),_.length>=3&&B.size<2&&ie.push("TYPOGRAPHY: All headings appear the same size. Create clear hierarchy with 3+ distinct sizes using a modular scale (1.25-1.5x ratio).");let L=Array.from(_).map(O=>parseInt(O.tagName.charAt(1),10));for(let O=1;O<L.length;O++)if(L[O]-L[O-1]>1){ie.push(`TYPOGRAPHY: Heading level skipped (h${L[O-1]} -> h${L[O]}). Use sequential heading levels for accessibility.`);break}let K=document.querySelectorAll("*"),te=!1,x=!1;K.forEach(O=>{let ue=window.getComputedStyle(O);ue.backgroundColor==="rgb(0, 0, 0)"&&O.clientHeight>100&&O.clientWidth>200&&(te=!0);let Ce=ue.backgroundColor,ze=ue.color;if(Ce&&!Ce.includes("0, 0, 0")&&!Ce.includes("255, 255, 255")&&Ce!=="rgba(0, 0, 0, 0)"&&Ce!=="transparent"&&ze.match(/rgb\((\d+), (\d+), (\d+)\)/)){let lt=ze.match(/rgb\((\d+), (\d+), (\d+)\)/);if(lt){let[We,De,g]=[parseInt(lt[1]),parseInt(lt[2]),parseInt(lt[3])],b=Math.abs(We-De)<10&&Math.abs(De-g)<10&&We>80&&We<180,T=Ce.match(/rgb\((\d+), (\d+), (\d+)\)/);if(b&&T){let[R,N,V]=[parseInt(T[1]),parseInt(T[2]),parseInt(T[3])];!(Math.abs(R-N)<15&&Math.abs(N-V)<15)&&O.textContent&&O.textContent.trim().length>0&&(x=!0)}}}}),te&&ie.push("COLOR: Pure black (#000) background detected on a large element. Use a tinted dark color instead (e.g. oklch(15% 0.01 hue) or a deep navy/charcoal)."),x&&ie.push("COLOR: Gray text on a colored background detected. Gray looks washed out on color. Use a darker shade of the background color or white instead.");let Ue=document.querySelectorAll('[class*="card"], [class*="Card"], [role="group"]'),G=!1;Ue.forEach(O=>{O.querySelectorAll('[class*="card"], [class*="Card"]').length>0&&(G=!0)}),G&&ie.push("LAYOUT: Nested cards detected (card inside card). Flatten the hierarchy. Use spacing and background color to create separation instead.");let xe=document.querySelectorAll("p, li, span, div"),we=0,se=0;xe.forEach(O=>{O.textContent&&O.textContent.trim().length>20&&O.clientHeight>0&&(se++,window.getComputedStyle(O).textAlign==="center"&&we++)}),se>5&&we/se>.7&&ie.push("LAYOUT: Most text is center-aligned. Use left-alignment for body content and lists. Reserve center-alignment for heroes and CTAs.");let Se=new Set;K.forEach(O=>{let ue=window.getComputedStyle(O);ue.gap&&ue.gap!=="normal"&&ue.gap!=="0px"&&Se.add(ue.gap)}),Se.size===1&&K.length>20&&ie.push("LAYOUT: Same gap value used everywhere. Vary spacing to create hierarchy: tight within groups (8-12px), generous between sections (32-64px).");let He=document.querySelectorAll("button, a, input, select, textarea");document.querySelectorAll("table, [role='table']").forEach(O=>{O.querySelectorAll("tbody tr").length===0&&(O.parentElement?.querySelector('[class*="empty"], [class*="Empty"], [class*="no-data"], [class*="placeholder"]')||ie.push("UX: Empty table with no empty state. Add a helpful message explaining what will appear here and a CTA to create the first item."))});let ot=document.querySelectorAll("style"),Ve=!1;ot.forEach(O=>{let ue=O.textContent||"";(ue.includes("bounce")||ue.includes("elastic")||ue.match(/cubic-bezier\([^)]*[2-9]\.[0-9]/))&&(Ve=!0)}),Ve&&ie.push("MOTION: Bounce or elastic easing detected. These feel dated. Use smooth deceleration curves (Quart out, Expo out) instead.");let ft=document.querySelectorAll("button, a, input, select, [role='button']"),Ne=[];if(ft.forEach(O=>{let ue=O.getBoundingClientRect();if(ue.width>0&&ue.height>0&&(ue.width<32||ue.height<32)){let Ce=O.tagName.toLowerCase(),ze=O.id?`#${O.id}`:"",lt=O.className&&typeof O.className=="string"?`.${O.className.split(/\s+/).filter(Boolean).slice(0,2).join(".")}`:"",We=(O.textContent||"").trim().slice(0,40),De=`${Ce}${ze}${lt}`,g=`${Math.round(ue.width)}x${Math.round(ue.height)}px`;Ne.push(We?`${De} ("${We}", ${g})`:`${De} (${g})`)}}),Ne.length>3){let O=Ne.slice(0,8).map((Ce,ze)=>` ${ze+1}. ${Ce}`).join(`
7401
+ `),ue=Ne.length>8?`
7402
+ \u2026and ${Ne.length-8} more.`:"";ie.push(`ACCESSIBILITY: ${Ne.length} interactive elements are smaller than 32x32px (minimum recommended touch target is 44x44px). Add padding so each element's bounding box reaches 44x44.
7697
7403
  Offenders:
7698
- ${W}${ce}`)}return ne});return Z.length===0?{pass:!0,detail:"No design quality issues detected. Typography hierarchy, color usage, layout patterns, and accessibility basics look good."}:{pass:!1,detail:`${Z.length} design quality issue(s) found:
7699
- ${Z.map((ne,v)=>`${v+1}. ${ne}`).join(`
7700
- `)}`,fix:"Fix these design issues in the source code. These are common AI-generated design anti-patterns that make apps look generic. Address each issue, then redeploy and re-run QA."}});s.push(O)}if(s.find(Q=>Q.name==="Landing page"&&Q.status==="pass")){let Q=await Rt(w,"Landing design quality",async()=>{await w.goto(n,{waitUntil:"domcontentloaded",timeout:15e3}),await w.waitForLoadState("networkidle").catch(()=>{});let q=await w.evaluate(()=>{let O=[];document.querySelectorAll("*").forEach(v=>{let F=window.getComputedStyle(v);(F.getPropertyValue("-webkit-background-clip")||F.getPropertyValue("background-clip"))==="text"&&v.textContent&&v.textContent.trim().length>0&&O.push("SLOP: Gradient text detected. This is a common AI design pattern. Use solid colors for text.")});let Z=document.querySelector("section, [class*='hero'], [class*='Hero'], header + div, main > div:first-child");if(Z){let v=(Z.textContent||"").toLowerCase(),F=["transform your","unlock the power","revolutionize your","take your .* to the next level","the future of","welcome to","get started today","join thousands","powerful analytics","seamless integration","lightning fast"];for(let j of F)if(v.match(new RegExp(j))){O.push(`COPY: Generic hero text detected ('${j}'). Write specific copy about what THIS app does for its users.`);break}}return document.querySelectorAll('[class*="grid"]').forEach(v=>{let j=window.getComputedStyle(v).gridTemplateColumns;if(j){let R=j.split(" ").filter(T=>T!=="").length,he=v.children;if(R===3&&he.length===3){let T=Array.from(he).map(ie=>ie.offsetHeight),$e=T.every(ie=>Math.abs(ie-T[0])<5),G=Array.from(he).every(ie=>{let ye=ie.querySelectorAll("svg"),xe=ie.querySelectorAll("h2, h3, h4"),de=ie.querySelectorAll("p");return ye.length>=1&&xe.length>=1&&de.length>=1});$e&&G&&O.push("SLOP: 3-column icon + title + paragraph feature grid detected. This is the most common AI layout pattern. Use asymmetric layouts, bento grids, or varied card sizes instead.")}}}),O});return q.length===0?{pass:!0,detail:"Landing page design looks intentional. No generic AI patterns detected."}:{pass:!1,detail:`${q.length} landing design issue(s):
7701
- ${q.map((O,D)=>`${D+1}. ${O}`).join(`
7702
- `)}`,fix:"These patterns make the landing page look AI-generated. Fix them to create a more distinctive, professional design."}});s.push(Q)}let le=h[1];if(le){let{getIsolatedContext:Q}=await Promise.resolve().then(()=>(zt(),vn)),q=await Q();x=q.context;let O=q.page,D=new URL(n).hostname,Z=n.startsWith("https");await x.addCookies([{name:Z?"__Secure-better-auth.session_token":"better-auth.session_token",value:le.sessionToken,domain:D,path:"/",httpOnly:!0,secure:Z,sameSite:"Lax"}]),console.error(`[qa] Injected ${le.role} cookie for secondary walk`);let ne=await Rt(O,V(le,"Dashboard"),async()=>{await O.goto(`${n}/`,{waitUntil:"domcontentloaded",timeout:15e3}),await O.waitForLoadState("networkidle").catch(()=>{}),new URL(O.url()).pathname==="/"&&(await O.goto(`${n}/dashboard`,{waitUntil:"domcontentloaded",timeout:15e3}),await O.waitForLoadState("networkidle").catch(()=>{}));let F=await O.content();return F.length<1e3?{pass:!1,detail:`Dashboard is very small (${F.length} bytes) for the default-role user`,fix:"The default-role user dashboard crashed or rendered empty. Check that role-gated server components handle non-admin roles without throwing."}:await O.locator('text="Something went wrong"').isVisible().catch(()=>!1)?{pass:!1,detail:"Dashboard shows error boundary for the default-role user",fix:"A server component crashed under the non-admin role. Likely a role check that throws instead of redirecting."}:{pass:!0,detail:`Loads (${F.length} bytes)`}});s.push(ne);let v=await O.evaluate(()=>{let F=[];return document.querySelectorAll("nav a[href], aside a[href]").forEach(j=>{let R=j.getAttribute("href");R&&R.startsWith("/")&&!R.startsWith("/api")&&!R.includes("[")&&R!=="/dashboard"&&R!=="/"&&!R.includes("/login")&&!R.includes("/sign")&&F.push(R)}),[...new Set(F)]});for(let F of v.slice(0,8)){let j=await Rt(O,V(le,`Page: ${F}`),async()=>{await O.goto(`${n}${F}`,{waitUntil:"domcontentloaded",timeout:15e3}),await O.waitForLoadState("networkidle").catch(()=>{});let R=await O.title(),he=await O.content();return R.toLowerCase().includes("500")||R.toLowerCase().includes("server error")?{pass:!1,detail:"Page returns 500 for the default-role user",fix:"Server component crashed under the non-admin role. Check role gates use redirect() not throw."}:R.toLowerCase().includes("404")||R.toLowerCase().includes("not found")?{pass:!0,detail:"404 (likely admin-only \u2014 acceptable)"}:await O.locator('text="Something went wrong"').isVisible().catch(()=>!1)?{pass:!1,detail:"Error boundary on page for default-role user",fix:"Role-gated content crashed. Use a graceful fallback."}:he.length<500?{pass:!1,detail:`Page very small (${he.length} bytes) for default-role user`,fix:"Page rendered empty under non-admin role."}:{pass:!0,detail:"Loads without errors"}});s.push(j)}}}finally{C&&await C.close().catch(()=>{}),x&&await x.close().catch(()=>{})}if(t.deploymentId){let A=s.filter(te=>te.status==="fail"),J=s.filter(te=>te.status==="pass"),V=Date.now();await Vo(t.deploymentId,{checks:s.map(({screenshot:te,...le})=>le),overall:A.length===0?"pass":"fail",passed:J.length,failed:A.length,duration_ms:Date.now()-V}).catch(()=>{})}return Eo(n,s)}function Eo(t,e){let o=e.filter(s=>s.status==="fail"),n=e.filter(s=>s.status==="pass"),r=[];if(o.length===0)r.push({type:"text",text:JSON.stringify({status:"pass",message:`QA passed. All ${e.length} checks OK. The app is working correctly.`,url:t,checks:e.map(({screenshot:s,...i})=>i)})});else{let s=o.map((i,a)=>`${a+1}. **${i.name}**: ${i.detail}
7404
+ ${O}${ue}`)}return ie});return z.length===0?{pass:!0,detail:"No design quality issues detected. Typography hierarchy, color usage, layout patterns, and accessibility basics look good."}:{pass:!1,detail:`${z.length} design quality issue(s) found:
7405
+ ${z.map((ie,_)=>`${_+1}. ${ie}`).join(`
7406
+ `)}`,fix:"Fix these design issues in the source code. These are common AI-generated design anti-patterns that make apps look generic. Address each issue, then redeploy and re-run QA."}});s.push(P)}if(s.find(ee=>ee.name==="Landing page"&&ee.status==="pass")){let ee=await At(v,"Landing design quality",async()=>{await v.goto(n,{waitUntil:"domcontentloaded",timeout:15e3}),await v.waitForLoadState("networkidle").catch(()=>{});let q=await v.evaluate(()=>{let P=[];document.querySelectorAll("*").forEach(_=>{let B=window.getComputedStyle(_);(B.getPropertyValue("-webkit-background-clip")||B.getPropertyValue("background-clip"))==="text"&&_.textContent&&_.textContent.trim().length>0&&P.push("SLOP: Gradient text detected. This is a common AI design pattern. Use solid colors for text.")});let z=document.querySelector("section, [class*='hero'], [class*='Hero'], header + div, main > div:first-child");if(z){let _=(z.textContent||"").toLowerCase(),B=["transform your","unlock the power","revolutionize your","take your .* to the next level","the future of","welcome to","get started today","join thousands","powerful analytics","seamless integration","lightning fast"];for(let L of B)if(_.match(new RegExp(L))){P.push(`COPY: Generic hero text detected ('${L}'). Write specific copy about what THIS app does for its users.`);break}}return document.querySelectorAll('[class*="grid"]').forEach(_=>{let L=window.getComputedStyle(_).gridTemplateColumns;if(L){let K=L.split(" ").filter(x=>x!=="").length,te=_.children;if(K===3&&te.length===3){let x=Array.from(te).map(xe=>xe.offsetHeight),Ue=x.every(xe=>Math.abs(xe-x[0])<5),G=Array.from(te).every(xe=>{let we=xe.querySelectorAll("svg"),se=xe.querySelectorAll("h2, h3, h4"),Se=xe.querySelectorAll("p");return we.length>=1&&se.length>=1&&Se.length>=1});Ue&&G&&P.push("SLOP: 3-column icon + title + paragraph feature grid detected. This is the most common AI layout pattern. Use asymmetric layouts, bento grids, or varied card sizes instead.")}}}),P});return q.length===0?{pass:!0,detail:"Landing page design looks intentional. No generic AI patterns detected."}:{pass:!1,detail:`${q.length} landing design issue(s):
7407
+ ${q.map((P,re)=>`${re+1}. ${P}`).join(`
7408
+ `)}`,fix:"These patterns make the landing page look AI-generated. Fix them to create a more distinctive, professional design."}});s.push(ee)}let ce=h[1];if(ce){let{getIsolatedContext:ee}=await Promise.resolve().then(()=>(Wt(),kn)),q=await ee();y=q.context;let P=q.page,re=new URL(n).hostname,z=n.startsWith("https");await y.addCookies([{name:z?"__Secure-better-auth.session_token":"better-auth.session_token",value:ce.sessionToken,domain:re,path:"/",httpOnly:!0,secure:z,sameSite:"Lax"}]),console.error(`[qa] Injected ${ce.role} cookie for secondary walk`);let ie=await At(P,H(ce,"Dashboard"),async()=>{await P.goto(`${n}/`,{waitUntil:"domcontentloaded",timeout:15e3}),await P.waitForLoadState("networkidle").catch(()=>{}),new URL(P.url()).pathname==="/"&&(await P.goto(`${n}/dashboard`,{waitUntil:"domcontentloaded",timeout:15e3}),await P.waitForLoadState("networkidle").catch(()=>{}));let B=await P.content();return B.length<1e3?{pass:!1,detail:`Dashboard is very small (${B.length} bytes) for the default-role user`,fix:"The default-role user dashboard crashed or rendered empty. Check that role-gated server components handle non-admin roles without throwing."}:await P.locator('text="Something went wrong"').isVisible().catch(()=>!1)?{pass:!1,detail:"Dashboard shows error boundary for the default-role user",fix:"A server component crashed under the non-admin role. Likely a role check that throws instead of redirecting."}:{pass:!0,detail:`Loads (${B.length} bytes)`}});s.push(ie);let _=await P.evaluate(()=>{let B=[];return document.querySelectorAll("nav a[href], aside a[href]").forEach(L=>{let K=L.getAttribute("href");K&&K.startsWith("/")&&!K.startsWith("/api")&&!K.includes("[")&&K!=="/dashboard"&&K!=="/"&&!K.includes("/login")&&!K.includes("/sign")&&B.push(K)}),[...new Set(B)]});for(let B of _.slice(0,8)){let L=await At(P,H(ce,`Page: ${B}`),async()=>{await P.goto(`${n}${B}`,{waitUntil:"domcontentloaded",timeout:15e3}),await P.waitForLoadState("networkidle").catch(()=>{});let K=await P.title(),te=await P.content();return K.toLowerCase().includes("500")||K.toLowerCase().includes("server error")?{pass:!1,detail:"Page returns 500 for the default-role user",fix:"Server component crashed under the non-admin role. Check role gates use redirect() not throw."}:K.toLowerCase().includes("404")||K.toLowerCase().includes("not found")?{pass:!0,detail:"404 (likely admin-only \u2014 acceptable)"}:await P.locator('text="Something went wrong"').isVisible().catch(()=>!1)?{pass:!1,detail:"Error boundary on page for default-role user",fix:"Role-gated content crashed. Use a graceful fallback."}:te.length<500?{pass:!1,detail:`Page very small (${te.length} bytes) for default-role user`,fix:"Page rendered empty under non-admin role."}:{pass:!0,detail:"Loads without errors"}});s.push(L)}}}finally{I&&await I.close().catch(()=>{}),y&&await y.close().catch(()=>{})}if(t.deploymentId){let E=s.filter(ne=>ne.status==="fail"),J=s.filter(ne=>ne.status==="pass"),H=Date.now();await Yr(t.deploymentId,{checks:s.map(({screenshot:ne,...ce})=>ce),overall:E.length===0?"pass":"fail",passed:J.length,failed:E.length,duration_ms:Date.now()-H}).catch(()=>{})}return Er(n,s)}function Er(t,e){let r=e.filter(s=>s.status==="fail"),n=e.filter(s=>s.status==="pass"),o=[];if(r.length===0)o.push({type:"text",text:JSON.stringify({status:"pass",message:`QA passed. All ${e.length} checks OK. The app is working correctly.`,url:t,checks:e.map(({screenshot:s,...i})=>i)})});else{let s=r.map((i,a)=>`${a+1}. **${i.name}**: ${i.detail}
7703
7409
  Fix: ${i.fix}`).join(`
7704
7410
 
7705
- `);r.push({type:"text",text:JSON.stringify({status:"fail",message:`QA found ${o.length} issue(s) on the live app. Fix them and redeploy.`,url:t,passed:n.length,failed:o.length,checks:e.map(({screenshot:i,...a})=>a),fixInstructions:`The deployed app at ${t} has ${o.length} issue(s):
7411
+ `);o.push({type:"text",text:JSON.stringify({status:"fail",message:`QA found ${r.length} issue(s) on the live app. Fix them and redeploy.`,url:t,passed:n.length,failed:r.length,checks:e.map(({screenshot:i,...a})=>a),fixInstructions:`The deployed app at ${t} has ${r.length} issue(s):
7706
7412
 
7707
7413
  ${s}
7708
7414
 
7709
- Fix these issues in the source code, then call mist_deploy with action='deploy'${t.includes("-pv-")?" environment='preview'":""} to redeploy. After redeploying, call mist_qa again to verify the fixes.`})})}for(let s of e)s.screenshot&&r.push({type:"image",data:s.screenshot,mimeType:"image/png"});return{content:r}}import{z as ut}from"zod";import{resolve as Zm,join as Hn}from"path";import{existsSync as zn,readFileSync as eh}from"fs";import{homedir as th}from"os";Ie();Et();Et();import{existsSync as No,readdirSync as Vm,statSync as el,unlinkSync as tl}from"fs";import{join as pn}from"path";import{execFile as Ym}from"child_process";function Qm(t,e){let o=0;for(let n of e){let r=pn(t,n);if(No(r))try{let s=el(r);if(s.isFile())o++;else if(s.isDirectory()){let i=[r];for(;i.length;){let a=i.pop(),l=Vm(a,{withFileTypes:!0});for(let c of l){let m=pn(a,c.name);c.isDirectory()?i.push(m):o++}}}}catch{}}return o}function Xm(t,e,o){return new Promise(n=>{Ym("tar",t,{cwd:e,timeout:o,maxBuffer:10*1024*1024,env:{...process.env,COPYFILE_DISABLE:"1"}},(r,s,i)=>{n({success:!r,stderr:i?.toString()??""})})})}async function nl(t){let e=pn(t,".open-next-build.tar.gz"),o=[".open-next"];No(pn(t,"db"))&&o.push("db"),No(pn(t,"drizzle.config.ts"))&&o.push("drizzle.config.ts"),No(pn(t,"package.json"))&&o.push("package.json");let n=Qm(t,o),r=await Xm(["-czf",e,"-C",t,...o],t,12e4);if(!r.success){try{tl(e)}catch{}throw new Error("Failed to create build archive. Check disk space and permissions."+(r.stderr?`
7710
- ${r.stderr.slice(-500)}`:""))}let s=0;try{s=el(e).size}catch{}return{path:e,sizeBytes:s,fileCount:n}}function ol(t){if(t)try{tl(t)}catch{}}function rl(t){return t<1024?`${t}B`:t<1024*1024?`${(t/1024).toFixed(1)}KB`:`${(t/(1024*1024)).toFixed(1)}MB`}function sl(t){switch(t){case"pending":return"Provisioning...";case"building":return"Building your app...";case"deploying":return"Deploying to Mistflow Cloud...";case"verifying":return"Verifying deployment...";case"live":return"Live!";case"failed":return"Failed";default:return`Status: ${t}`}}var nh=ut.object({action:ut.enum(["deploy","promote","rollback","status"]).default("deploy").describe("'deploy' (default) tars + uploads + starts a deployment. 'promote' promotes a staging preview to production. 'rollback' rolls back to a specific deployment. 'status' polls an in-flight deployment by id."),projectPath:ut.string().optional().describe("Absolute path to the project. Required for 'deploy' and 'promote'."),deploymentId:ut.string().optional().describe("Deployment id \u2014 required for 'rollback' and 'status'; the preview id for 'promote'."),environment:ut.enum(["production","preview"]).optional().describe("Deploy target. Defaults to 'production'; auto-redirects to 'preview' when the project uses staging mode."),adminEmail:ut.string().optional().describe("Optional override for the owner email. Defaults to the email of whoever ran mist_setup (read from ~/.mistflow/credentials.json). Multi-role apps auto-promote this email to admin on first signup; single-role apps just attach it to the project for the dashboard's Owner Access card. Pass explicitly only when the deploying user is not the intended owner."),waitSeconds:ut.number().min(0).max(20).optional().describe("On action='status' calls: long-poll the backend for up to N seconds (0-20) before returning, so a typical deploy fits in 1-2 round-trips instead of 6-12. Default 20. Ignored for non-status actions."),envVarsRequired:ut.array(Io).optional().describe("Last chance to declare env vars introduced since the last implement call. Merged into mistflow.json env.required before the build is tarred, so the backend's deploy-time check picks them up. Skip keys covered by integration presets (Stripe, Resend, etc.). Only used for action='deploy'."),sessionId:ut.string().uuid().optional().describe("Backend session ID. Pass through from mist_plan response so state guards apply and session state advances as the tool runs.")});function il(t){return zn(Hn(t,".open-next"))}function oh(t){return ft(t)?.deploy?.strategy==="staging"?"preview":null}async function rh(t,e){if(!(zn(Hn(t,"open-next.config.ts"))||zn(Hn(t,"open-next.config.js"))))return{kind:"failed",result:d(`No .open-next/ build output at ${t}, and no open-next.config.* either. This project is not configured for the Cloudflare adapter \u2014 mist_deploy ships the .open-next/ artifact, not raw source. If this is a Mistflow project, re-run mist_init to restore the OpenNext config.`,!0)};if(!zn(Hn(t,"node_modules")))return{kind:"failed",result:d(`node_modules is missing at ${t}. Call mist_install { projectPath } first, then mist_deploy.`,!0)};if(!tn())return{kind:"failed",result:d(qt(),!0)};let n=await Ct({type:"build",cmd:"npx",args:["-y","@opennextjs/cloudflare","build"],cwd:t}),r=e?Ye(e.server,e.progressToken,()=>`Auto-building before deploy \u2014 OpenNext adapter (${n.id})`):{stop:()=>{}},s;try{s=await cn(n.id,{timeoutMs:45e3})}finally{r.stop()}if(!s)return{kind:"failed",result:d(`Auto-build job ${n.id} disappeared. Run mist_build { projectPath } manually, then mist_deploy.`,!0)};if(s.status==="running"||s.status==="starting")return{kind:"running",result:d(JSON.stringify({status:"building",buildJobId:n.id,phase:"auto-build",nextAction:`Build is still running after the 45s inline window. Call mist_build { jobId: '${n.id}' } to keep waiting (long-polls server-side), then call mist_deploy { action: 'deploy', projectPath } again. Do NOT ask the user \u2014 the build is in flight, just keep going.`}))};if(s.status==="complete"&&il(t))return{kind:"ok"};let i=Bt(n.id),a=(s.logTail??"").split(`
7415
+ Fix these issues in the source code, then call mist_deploy with action='deploy'${t.includes("-pv-")?" environment='preview'":""} to redeploy. After redeploying, call mist_qa again to verify the fixes.`})})}for(let s of e)s.screenshot&&o.push({type:"image",data:s.screenshot,mimeType:"image/png"});return{content:o}}import{z as at}from"zod";import{resolve as bh,join as zn}from"path";import{existsSync as Wn,readFileSync as wh}from"fs";import{homedir as vh}from"os";ke();Nt();Nt();import{existsSync as Nr,readdirSync as sh,statSync as pl,unlinkSync as ml}from"fs";import{join as mn}from"path";import{execFile as ih}from"child_process";function ah(t,e){let r=0;for(let n of e){let o=mn(t,n);if(Nr(o))try{let s=pl(o);if(s.isFile())r++;else if(s.isDirectory()){let i=[o];for(;i.length;){let a=i.pop(),l=sh(a,{withFileTypes:!0});for(let c of l){let p=mn(a,c.name);c.isDirectory()?i.push(p):r++}}}}catch{}}return r}function lh(t,e,r){return new Promise(n=>{ih("tar",t,{cwd:e,timeout:r,maxBuffer:10*1024*1024,env:{...process.env,COPYFILE_DISABLE:"1"}},(o,s,i)=>{n({success:!o,stderr:i?.toString()??""})})})}async function hl(t){let e=mn(t,".open-next-build.tar.gz"),r=[".open-next"];Nr(mn(t,"db"))&&r.push("db"),Nr(mn(t,"drizzle.config.ts"))&&r.push("drizzle.config.ts"),Nr(mn(t,"package.json"))&&r.push("package.json");let n=ah(t,r),o=await lh(["-czf",e,"-C",t,...r],t,12e4);if(!o.success){try{ml(e)}catch{}throw new Error("Failed to create build archive. Check disk space and permissions."+(o.stderr?`
7416
+ ${o.stderr.slice(-500)}`:""))}let s=0;try{s=pl(e).size}catch{}return{path:e,sizeBytes:s,fileCount:n}}function gl(t){if(t)try{ml(t)}catch{}}function fl(t){return t<1024?`${t}B`:t<1024*1024?`${(t/1024).toFixed(1)}KB`:`${(t/(1024*1024)).toFixed(1)}MB`}function yl(t){switch(t){case"pending":return"Provisioning...";case"building":return"Building your app...";case"deploying":return"Deploying to Mistflow Cloud...";case"verifying":return"Verifying deployment...";case"live":return"Live!";case"failed":return"Failed";default:return`Status: ${t}`}}import{existsSync as ch,readdirSync as dh,statSync as uh}from"fs";import{join as Hn,relative as ph}from"path";var mh=["app","components","lib","db","contracts","hooks","styles","public"],hh=["middleware.ts","middleware.js","next.config.ts","next.config.js","next.config.mjs","open-next.config.ts","open-next.config.js","open-next.config.mjs","tailwind.config.ts","tailwind.config.js","postcss.config.js","postcss.config.mjs","drizzle.config.ts","package.json","package-lock.json","pnpm-lock.yaml","yarn.lock","tsconfig.json",".env",".env.local",".env.production",".env.development"],gh=Hn(".open-next","worker.js");function Xo(t){try{return uh(t).mtimeMs}catch{return null}}function fh(t,e){let r=Hn(t,e);if(!ch(r))return null;let n=null;try{let o=dh(r,{recursive:!0,withFileTypes:!0});for(let s of o){if(!s.isFile())continue;let i=s.parentPath??s.path??r,a=Hn(i,s.name),l=Xo(a);l!==null&&(!n||l>n.mtime)&&(n={mtime:l,path:ph(t,a)})}}catch{}return n}function yh(t){let e=null,r=n=>{n&&(!e||n.mtime>e.mtime)&&(e=n)};for(let n of hh){let o=Xo(Hn(t,n));o!==null&&r({mtime:o,path:n})}for(let n of mh)r(fh(t,n));return e}function bl(t){let e=Xo(Hn(t,gh));if(e===null)return{stale:!0,reason:"no .open-next/worker.js build artifact"};let r=yh(t);return r?r.mtime>e?{stale:!0,reason:`${r.path} modified after last build`,buildMtime:e,newestSource:r}:{stale:!1,buildMtime:e}:{stale:!0,reason:"no source files visible to verify build against",buildMtime:e}}function Zo(t){return new Date(t).toISOString()}var kh=at.object({action:at.enum(["deploy","promote","rollback","status"]).default("deploy").describe("'deploy' (default) tars + uploads + starts a deployment. 'promote' promotes a staging preview to production. 'rollback' rolls back to a specific deployment. 'status' polls an in-flight deployment by id."),projectPath:at.string().optional().describe("Absolute path to the project. Required for 'deploy' and 'promote'."),deploymentId:at.string().optional().describe("Deployment id \u2014 required for 'rollback' and 'status'; the preview id for 'promote'."),environment:at.enum(["production","preview"]).optional().describe("Deploy target. Defaults to 'production'; auto-redirects to 'preview' when the project uses staging mode."),adminEmail:at.string().optional().describe("Optional override for the owner email. Defaults to the email of whoever ran mist_setup (read from ~/.mistflow/credentials.json). Apps with a global admin role auto-promote this email to admin on first signup. Pass explicitly only when the deploying user is not the intended owner contact."),waitSeconds:at.number().min(0).max(20).optional().describe("On action='status' calls: long-poll the backend for up to N seconds (0-20) before returning, so a typical deploy fits in 1-2 round-trips instead of 6-12. Default 20. Ignored for non-status actions."),envVarsRequired:at.array(Ir).optional().describe("Last chance to declare env vars introduced since the last implement call. Merged into mistflow.json env.required before the build is tarred, so the backend's deploy-time check picks them up. Skip keys covered by integration presets (Stripe, Resend, etc.). Only used for action='deploy'."),sessionId:at.string().uuid().optional().describe("Backend session ID. Pass through from mist_plan response so state guards apply and session state advances as the tool runs."),forceRebuild:at.boolean().optional().describe("Force a fresh OpenNext build even when the existing .open-next/ artifact appears up to date. Use when the staleness heuristic might miss a change (e.g. node_modules swapped out-of-band, generated files updated outside the watched source dirs, or a previous deploy looked wrong and you want to rule out caching). Default false. Ignored for non-deploy actions.")});function xh(t){return Wn(zn(t,".open-next"))}function Sh(t){return ut(t)?.deploy?.strategy==="staging"?"preview":null}function _h(t){let e=ut(t),r=e?.plan?.steps;if(!Array.isArray(r))return;let n=!1;for(let o of r)o.status==="in_progress"&&(o.status="completed",n=!0);n&&Xn(t,{plan:e.plan})}async function Th(t,e){if(!(Wn(zn(t,"open-next.config.ts"))||Wn(zn(t,"open-next.config.js"))))return{kind:"failed",result:u(`No .open-next/ build output at ${t}, and no open-next.config.* either. This project is not configured for the Cloudflare adapter \u2014 mist_deploy ships the .open-next/ artifact, not raw source. If this is a Mistflow project, re-run mist_init to restore the OpenNext config.`,!0)};if(!Wn(zn(t,"node_modules")))return{kind:"failed",result:u(`node_modules is missing at ${t}. Call mist_install { projectPath } first, then mist_deploy.`,!0)};if(!rn())return{kind:"failed",result:u(qt(),!0)};let n=await It({type:"build",cmd:"npx",args:["-y","@opennextjs/cloudflare","build"],cwd:t}),o=e?tt(e.server,e.progressToken,()=>`Auto-building before deploy \u2014 OpenNext adapter (${n.id})`):{stop:()=>{}},s;try{s=await dn(n.id,{timeoutMs:45e3})}finally{o.stop()}if(!s)return{kind:"failed",result:u(`Auto-build job ${n.id} disappeared. Run mist_build { projectPath } manually, then mist_deploy.`,!0)};if(s.status==="running"||s.status==="starting")return{kind:"running",result:u(JSON.stringify({status:"building",buildJobId:n.id,phase:"auto-build",nextAction:`Build is still running after the 45s inline window. Call mist_build { jobId: '${n.id}' } to keep waiting (long-polls server-side), then call mist_deploy { action: 'deploy', projectPath } again. Do NOT ask the user \u2014 the build is in flight, just keep going.`}))};if(s.status==="complete"&&xh(t))return{kind:"ok"};let i=zt(n.id),a=(s.logTail??"").split(`
7711
7417
  `).slice(-30).join(`
7712
- `);return{kind:"failed",result:d(JSON.stringify({status:"build_failed",buildJobId:n.id,exitCode:s.exitCode,logTail:a,logPaths:i,nextAction:`Auto-build before deploy failed (exit ${s.exitCode??"?"}). Read the logTail or run mist_debug { projectPath, jobId: '${n.id}' } to extract structured errors. Fix the code, then call mist_deploy { action: 'deploy', projectPath } again.`}),!0)}}async function sh(t){let e=Zm(t.projectPath);if(!Te())return Oe("deploy");let o=ft(e),n=o?.projectId;if(!n){if(!o?.name)return d(`No projectId or name in mistflow.json at ${e}. Run mist_init first.`,!0);let a=typeof o.plan=="object"&&o.plan!==null?o.plan:void 0;if(!a&&o.planId)try{let p=Hn(th(),".mistflow","plans",`${o.planId}.json`);if(zn(p)){let h=JSON.parse(eh(p,"utf-8"));a=h?.plan??h}}catch{}let l=a?.pickedDirection??void 0,c=a?.imageryBrief??l?.imagery??void 0,m=a?.designConversationId??void 0,u=a?{name:a.name,summary:a.summary,audienceType:a.audienceType,authModel:a.authModel,dbProvider:o.dbProvider,dataModel:a.dataModel,design:a.design,publicLanding:a.publicLanding,...typeof a.designMd=="string"&&a.designMd?{designMd:a.designMd}:{},...m?{designConversationId:m}:{},...a.features?{features:a.features}:{},...a.integrations?{integrations:a.integrations}:{},...a.hasAI===!0?{hasAI:!0}:{},...a.hasEmail===!0?{hasEmail:!0}:{},...a.hasStorage===!0?{hasStorage:!0}:{}}:void 0;try{n=(await Ot(o.name,{dbProvider:o.dbProvider,requestedSubdomain:o.requestedSubdomain,pickedDirection:l,imageryBrief:c,planContext:u,designConversationId:m})).id,Bo(e,{projectId:n}),console.error(`[deploy] auto-registered project ${n.slice(0,8)} (${o.name})`+(o.requestedSubdomain?` at ${o.requestedSubdomain}.mistflow.app`:"")+(u?" with plan context":" WITHOUT plan context (no plan blob in mistflow.json \u2014 AI/storage may not provision)"))}catch(p){let h=p instanceof X||p instanceof Error?p.message:String(p);return d(`Auto-register before deploy failed: ${h}. mistflow.json has no projectId \u2014 mist_init couldn't register the project earlier (likely offline or backend timeout). Try again in a moment, or check Mistflow status.`,!0)}}if(!il(e)){let a=await rh(e,t.ctx);if(a.kind==="running"||a.kind==="failed")return a.result}let r=t.environment??oh(e)??"production",s=t.adminEmail??ys()?.email,i=null;try{i=await nl(e);let a=await Go(n,i.path,r,s,void 0,void 0,void 0);return d(JSON.stringify({status:"running",jobId:a.deployment_id,deploymentId:a.deployment_id,environment:a.environment??r,buildSize:rl(i.sizeBytes),buildFileCount:i.fileCount,nextAction:`Upload complete. Call mist_deploy { action: 'status', deploymentId: '${a.deployment_id}' } immediately \u2014 each status call long-polls for up to 20s server-side, so there's no need to sleep between calls. Do NOT surface the URL to the user until mist_qa passes.`}))}catch(a){let l=a instanceof X||a instanceof Error?a.message:String(a);return d(`Deploy upload failed: ${l}`,!0)}finally{ol(i?.path)}}async function ih(t,e){let o=e.ctx?Ye(e.ctx.server,e.ctx.progressToken,()=>`Deploy ${t} \u2014 waiting for terminal state`):{stop:()=>{}};try{let n=await yt(t,{waitSeconds:e.waitSeconds}),r=sl(n.status),s=n.id??t;return n.status==="live"?(e.projectPath&&n.url&&(Bo(e.projectPath,{deploy:{url:n.url,deploymentId:s,completedAt:n.completedAt}}),nn(e.projectPath)),d(JSON.stringify({status:"complete",jobId:s,deploymentId:s,url:n.url,completedAt:n.completedAt,qaRequired:!0,nextAction:`Deployment is live. Call mist_qa { projectPath, url: '${n.url??""}', deploymentId: '${s}' } next. Do NOT show the URL to the user until mist_qa returns status: 'pass'.`}))):n.status==="failed"?d(JSON.stringify({status:"failed",jobId:s,deploymentId:s,error:n.error,phase:r,nextAction:"Deployment failed. Read the error message, fix the code, then call mist_deploy { action: 'deploy', projectPath } again."}),!0):d(JSON.stringify({status:"running",jobId:s,deploymentId:s,phase:r,phaseCode:n.status,nextAction:`Still ${n.status} after the ${e.waitSeconds}s backend long-poll window. Call mist_deploy { action: 'status', deploymentId: '${s}' } again immediately to continue waiting \u2014 no sleep needed.`}))}catch(n){let r=n instanceof X||n instanceof Error?n.message:String(n);return d(`Could not fetch deploy status: ${r}`,!0)}finally{o.stop()}}async function ah(t,e){if(!Te())return Oe("promote a preview deployment");let n=ft(t)?.projectId;if(!n)return d(`No projectId in mistflow.json at ${t}. Run mist_init first.`,!0);let r=e;if(!r)try{let i=(await Kt(n)).find(a=>a.environment==="preview"&&a.status==="live");if(!i)return d("No live preview deployment to promote. Deploy to preview first with { action: 'deploy', environment: 'preview' }.",!0);r=i.id}catch(s){let i=s instanceof X?s.message:String(s);return d(`Could not list deployments: ${i}`,!0)}try{let s=await sr(n,r);return d(JSON.stringify({status:"running",jobId:s.deployment_id,deploymentId:s.deployment_id,promotedFrom:r,nextAction:`Promote started. Call mist_deploy { action: 'status', deploymentId: '${s.deployment_id}' } immediately \u2014 long-polls for up to 20s. Promotion re-uses the preview artifact and typically completes in ~10s, so one poll usually finishes the job.`}))}catch(s){let i=s instanceof X||s instanceof Error?s.message:String(s);return d(`Promote failed: ${i}`,!0)}}async function lh(t){if(!Te())return Oe("roll back a deployment");try{let e=await ir(t);return d(JSON.stringify({status:"running",jobId:e.deployment_id,deploymentId:e.deployment_id,rollbackFrom:e.rollback_from,nextAction:`Rollback started. Call mist_deploy { action: 'status', deploymentId: '${e.deployment_id}' } immediately \u2014 each status call long-polls for up to 20s server-side.`}))}catch(e){let o=e instanceof X||e instanceof Error?e.message:String(e);return d(`Rollback failed: ${o}`,!0)}}var al={name:"mist_deploy",description:"Deploy a Mistflow project. action='deploy' (default) tars + uploads + starts backend orchestration; action='status' polls an in-flight deployment; action='promote' ships a staging preview to prod; action='rollback' reverts to a previous deployment. Fire-and-poll: deploy/promote/rollback return a jobId immediately; poll with { action: 'status', deploymentId }. Do NOT show the deploy URL to the user until mist_qa passes \u2014 qaRequired=true is returned on completion.",inputSchema:nh,handler:async(t,e)=>{let o=t;switch(o.action??"deploy"){case"deploy":{if(!o.projectPath)return d("projectPath is required for action='deploy'.",!0);if(o.envVarsRequired&&o.envVarsRequired.length>0)try{let r=Po(o.projectPath,o.envVarsRequired);r.added.length>0&&console.error(`[deploy] declared env.required: ${r.added.join(", ")}`)}catch(r){console.error("[deploy] env var merge skipped:",r instanceof Error?r.message:String(r))}return sh({projectPath:o.projectPath,environment:o.environment,adminEmail:o.adminEmail,ctx:e})}case"status":return o.deploymentId?ih(o.deploymentId,{waitSeconds:o.waitSeconds??20,ctx:e,projectPath:o.projectPath,sessionId:o.sessionId}):d("deploymentId is required for action='status'.",!0);case"promote":return o.projectPath?ah(o.projectPath,o.deploymentId):d("projectPath is required for action='promote'.",!0);case"rollback":return o.deploymentId?lh(o.deploymentId):d("deploymentId is required for action='rollback'.",!0)}}};import{z as mn}from"zod";Ie();import{hostname as ch}from"os";var cl=mn.object({action:mn.enum(["status","cancel","resume","list"]).describe("status = read current state + acceptance criteria; cancel = terminal CANCELLED transition; resume = bind this machine + path to a session; list = sessions this machine has bound (no sessionId needed)."),sessionId:mn.string().uuid().optional().describe("Required for status / cancel / resume. Omit for list."),reason:mn.string().max(500).optional().describe("Optional cancellation reason (used with action=cancel)."),projectPath:mn.string().min(1).optional().describe("Absolute path to the project directory on this machine. Required for action=resume."),includeIdle:mn.boolean().optional().describe("For action=list. Default false: only active sessions touched in the last 24h are returned. Set true to see every session ever bound on this machine, including cancelled / completed / stale ones.")});function ll(){return ch()}function Vr(t){let e=[`Session: ${t.id}`,`Status: ${t.status}`,`Deploy strategy: ${t.deploy_strategy}`];return t.paused_after_plan&&e.push("Paused: yes (awaiting PLAN.md review \u2014 call mist_session({resume}) when done)"),t.description&&e.push(`Description: ${t.description}`),t.cancelled_at&&e.push(`Cancelled at: ${t.cancelled_at}`),t.completed_at&&e.push(`Completed at: ${t.completed_at}`),e.join(`
7713
- `)}async function dh(t){let e=cl.safeParse(t);if(!e.success){let i=e.error.issues.map(a=>`${a.path.join(".")}: ${a.message}`).join(", ");return d(`Invalid input: ${i}`,!0)}let o=e.data;if((o.action==="status"||o.action==="cancel"||o.action==="resume")&&!o.sessionId)return d(`Invalid input: action="${o.action}" requires sessionId.`,!0);if(o.action==="resume"&&!o.projectPath)return d('Invalid input: action="resume" requires projectPath.',!0);if(o.action==="status"){let i=o.sessionId,[a,l,c]=await Promise.all([lo(i),Jt(i).catch(()=>null),kn(i).catch(()=>null)]),m=[Vr(a),"",l?`Next instruction: ${l.instruction}${l.reason?` (${l.reason})`:""}`:"Next instruction: (unavailable)"];if(c&&c.criteria.length>0){m.push("",`Acceptance criteria (${c.criteria.length}):`);for(let u of c.criteria)m.push(` - [${u.priority}] ${u.id}: ${u.description}`)}return d(m.join(`
7714
- `))}if(o.action==="cancel"){let i=o.sessionId,a=await fr(i,o.reason);return d(`Session cancelled.
7715
- ${Vr(a)}
7418
+ `);return{kind:"failed",result:u(JSON.stringify({status:"build_failed",buildJobId:n.id,exitCode:s.exitCode,logTail:a,logPaths:i,nextAction:`Auto-build before deploy failed (exit ${s.exitCode??"?"}). Read the logTail or run mist_debug { projectPath, jobId: '${n.id}' } to extract structured errors. Fix the code, then call mist_deploy { action: 'deploy', projectPath } again.`}),!0)}}async function Ch(t){let e=bh(t.projectPath);if(!Te())return Oe("deploy");let r=ut(e),n=r?.projectId;if(!n){if(!r?.name)return u(`No projectId or name in mistflow.json at ${e}. Run mist_init first.`,!0);let d=typeof r.plan=="object"&&r.plan!==null?r.plan:void 0;if(!d&&r.planId)try{let y=zn(vh(),".mistflow","plans",`${r.planId}.json`);if(Wn(y)){let v=JSON.parse(wh(y,"utf-8"));d=v?.plan??v}}catch{}let m=d?.pickedDirection??void 0,h=d?.imageryBrief??m?.imagery??void 0,S=d?.designConversationId??void 0,I=d?{name:d.name,summary:d.summary,audienceType:d.audienceType,authModel:d.authModel,dbProvider:r.dbProvider,dataModel:d.dataModel,design:d.design,publicLanding:d.publicLanding,...typeof d.designMd=="string"&&d.designMd?{designMd:d.designMd}:{},...S?{designConversationId:S}:{},...d.features?{features:d.features}:{},...d.integrations?{integrations:d.integrations}:{},...d.hasAI===!0?{hasAI:!0}:{},...d.hasEmail===!0?{hasEmail:!0}:{},...d.hasStorage===!0?{hasStorage:!0}:{}}:void 0;try{n=(await Mt(r.name,{dbProvider:r.dbProvider,requestedSubdomain:r.requestedSubdomain,pickedDirection:m,imageryBrief:h,planContext:I,designConversationId:S})).id,Xn(e,{projectId:n}),console.error(`[deploy] auto-registered project ${n.slice(0,8)} (${r.name})`+(r.requestedSubdomain?` at ${r.requestedSubdomain}.mistflow.app`:"")+(I?" with plan context":" WITHOUT plan context (no plan blob in mistflow.json \u2014 AI/storage may not provision)"))}catch(y){let v=y instanceof Z||y instanceof Error?y.message:String(y);return u(`Auto-register before deploy failed: ${v}. mistflow.json has no projectId \u2014 mist_init couldn't register the project earlier (likely offline or backend timeout). Try again in a moment, or check Mistflow status.`,!0)}}let o=bl(e),s=t.forceRebuild===!0||o.stale,i,a;if(s){t.forceRebuild?a="forceRebuild=true requested":!("buildMtime"in o)||o.buildMtime===void 0?a=o.reason:a=`${o.reason} (last build: ${Zo(o.buildMtime)})`,console.error(`[deploy] running fresh build \u2014 ${a}`);let d=await Th(e,t.ctx);if(d.kind==="running"||d.kind==="failed")return d.result;i="fresh"}else a=`up to date with source (built ${Zo(o.buildMtime)})`,console.error(`[deploy] reusing existing .open-next/ build \u2014 ${a}`),i="reused";let l=t.environment??Sh(e)??"production",c=t.adminEmail??Is()?.email,p=null;try{p=await hl(e);let d=await Jr(n,p.path,l,c,void 0,void 0,void 0);return u(JSON.stringify({status:"running",jobId:d.deployment_id,deploymentId:d.deployment_id,environment:d.environment??l,buildSize:fl(p.sizeBytes),buildFileCount:p.fileCount,buildSource:i,buildSourceDetail:a,nextAction:`Upload complete. Call mist_deploy { action: 'status', deploymentId: '${d.deployment_id}', projectPath: '${e}' } immediately \u2014 each status call long-polls for up to 20s server-side, so there's no need to sleep between calls. Do NOT surface the URL to the user until mist_qa passes.`}))}catch(d){let m=d instanceof Z||d instanceof Error?d.message:String(d);return u(`Deploy upload failed: ${m}`,!0)}finally{gl(p?.path)}}async function Ih(t,e){let r=e.ctx?tt(e.ctx.server,e.ctx.progressToken,()=>`Deploy ${t} \u2014 waiting for terminal state`):{stop:()=>{}};try{let n=await wt(t,{waitSeconds:e.waitSeconds}),o=yl(n.status),s=n.id??t;return n.status==="live"?(e.projectPath&&n.url&&(_h(e.projectPath),Xn(e.projectPath,{deploy:{url:n.url,deploymentId:s,completedAt:n.completedAt}}),on(e.projectPath)),u(JSON.stringify({status:"complete",jobId:s,deploymentId:s,url:n.url,completedAt:n.completedAt,qaRequired:!0,nextAction:`Deployment is live. Call mist_qa { projectPath, url: '${n.url??""}', deploymentId: '${s}' } next. Do NOT show the URL to the user until mist_qa returns status: 'pass'.`}))):n.status==="failed"?u(JSON.stringify({status:"failed",jobId:s,deploymentId:s,error:n.error,phase:o,nextAction:"Deployment failed. Read the error message, fix the code, then call mist_deploy { action: 'deploy', projectPath } again."}),!0):u(JSON.stringify({status:"running",jobId:s,deploymentId:s,phase:o,phaseCode:n.status,nextAction:`Still ${n.status} after the ${e.waitSeconds}s backend long-poll window. Call mist_deploy { action: 'status', deploymentId: '${s}' } again immediately to continue waiting \u2014 no sleep needed.`}))}catch(n){let o=n instanceof Z||n instanceof Error?n.message:String(n);return u(`Could not fetch deploy status: ${o}`,!0)}finally{r.stop()}}async function Ph(t,e){if(!Te())return Oe("promote a preview deployment");let n=ut(t)?.projectId;if(!n)return u(`No projectId in mistflow.json at ${t}. Run mist_init first.`,!0);let o=e;if(!o)try{let i=(await Kt(n)).find(a=>a.environment==="preview"&&a.status==="live");if(!i)return u("No live preview deployment to promote. Deploy to preview first with { action: 'deploy', environment: 'preview' }.",!0);o=i.id}catch(s){let i=s instanceof Z?s.message:String(s);return u(`Could not list deployments: ${i}`,!0)}try{let s=await io(n,o);return u(JSON.stringify({status:"running",jobId:s.deployment_id,deploymentId:s.deployment_id,promotedFrom:o,nextAction:`Promote started. Call mist_deploy { action: 'status', deploymentId: '${s.deployment_id}' } immediately \u2014 long-polls for up to 20s. Promotion re-uses the preview artifact and typically completes in ~10s, so one poll usually finishes the job.`}))}catch(s){let i=s instanceof Z||s instanceof Error?s.message:String(s);return u(`Promote failed: ${i}`,!0)}}async function Rh(t){if(!Te())return Oe("roll back a deployment");try{let e=await ao(t);return u(JSON.stringify({status:"running",jobId:e.deployment_id,deploymentId:e.deployment_id,rollbackFrom:e.rollback_from,nextAction:`Rollback started. Call mist_deploy { action: 'status', deploymentId: '${e.deployment_id}' } immediately \u2014 each status call long-polls for up to 20s server-side.`}))}catch(e){let r=e instanceof Z||e instanceof Error?e.message:String(e);return u(`Rollback failed: ${r}`,!0)}}var wl={name:"mist_deploy",description:"Deploy a Mistflow project. action='deploy' (default) tars + uploads + starts backend orchestration; action='status' polls an in-flight deployment; action='promote' ships a staging preview to prod; action='rollback' reverts to a previous deployment. Fire-and-poll: deploy/promote/rollback return a jobId immediately; poll with { action: 'status', deploymentId }. Do NOT show the deploy URL to the user until mist_qa passes \u2014 qaRequired=true is returned on completion.",inputSchema:kh,handler:async(t,e)=>{let r=t;switch(r.action??"deploy"){case"deploy":{if(!r.projectPath)return u("projectPath is required for action='deploy'.",!0);if(r.envVarsRequired&&r.envVarsRequired.length>0)try{let o=Pr(r.projectPath,r.envVarsRequired);o.added.length>0&&console.error(`[deploy] declared env.required: ${o.added.join(", ")}`)}catch(o){console.error("[deploy] env var merge skipped:",o instanceof Error?o.message:String(o))}return Ch({projectPath:r.projectPath,environment:r.environment,adminEmail:r.adminEmail,forceRebuild:r.forceRebuild,ctx:e})}case"status":return r.deploymentId?Ih(r.deploymentId,{waitSeconds:r.waitSeconds??20,ctx:e,projectPath:r.projectPath,sessionId:r.sessionId}):u("deploymentId is required for action='status'.",!0);case"promote":return r.projectPath?Ph(r.projectPath,r.deploymentId):u("projectPath is required for action='promote'.",!0);case"rollback":return r.deploymentId?Rh(r.deploymentId):u("deploymentId is required for action='rollback'.",!0)}}};import{z as hn}from"zod";ke();import{hostname as Ah}from"os";var kl=hn.object({action:hn.enum(["status","cancel","resume","list"]).describe("status = read current state + acceptance criteria; cancel = terminal CANCELLED transition; resume = bind this machine + path to a session; list = sessions this machine has bound (no sessionId needed)."),sessionId:hn.string().uuid().optional().describe("Required for status / cancel / resume. Omit for list."),reason:hn.string().max(500).optional().describe("Optional cancellation reason (used with action=cancel)."),projectPath:hn.string().min(1).optional().describe("Absolute path to the project directory on this machine. Required for action=resume."),includeIdle:hn.boolean().optional().describe("For action=list. Default false: only active sessions touched in the last 24h are returned. Set true to see every session ever bound on this machine, including cancelled / completed / stale ones.")});function vl(){return Ah()}function es(t){let e=[`Session: ${t.id}`,`Status: ${t.status}`,`Deploy strategy: ${t.deploy_strategy}`];return t.paused_after_plan&&e.push("Paused: yes (awaiting PLAN.md review \u2014 call mist_session({resume}) when done)"),t.description&&e.push(`Description: ${t.description}`),t.cancelled_at&&e.push(`Cancelled at: ${t.cancelled_at}`),t.completed_at&&e.push(`Completed at: ${t.completed_at}`),e.join(`
7419
+ `)}async function Eh(t){let e=kl.safeParse(t);if(!e.success){let i=e.error.issues.map(a=>`${a.path.join(".")}: ${a.message}`).join(", ");return u(`Invalid input: ${i}`,!0)}let r=e.data;if((r.action==="status"||r.action==="cancel"||r.action==="resume")&&!r.sessionId)return u(`Invalid input: action="${r.action}" requires sessionId.`,!0);if(r.action==="resume"&&!r.projectPath)return u('Invalid input: action="resume" requires projectPath.',!0);if(r.action==="status"){let i=r.sessionId,[a,l,c]=await Promise.all([cr(i),Vt(i).catch(()=>null),Sn(i).catch(()=>null)]),p=[es(a),"",l?`Next instruction: ${l.instruction}${l.reason?` (${l.reason})`:""}`:"Next instruction: (unavailable)"];if(c&&c.criteria.length>0){p.push("",`Acceptance criteria (${c.criteria.length}):`);for(let d of c.criteria)p.push(` - [${d.priority}] ${d.id}: ${d.description}`)}return u(p.join(`
7420
+ `))}if(r.action==="cancel"){let i=r.sessionId,a=await bo(i,r.reason);return u(`Session cancelled.
7421
+ ${es(a)}
7716
7422
 
7717
- Note: cancellation is terminal. Start a new session with mist_plan to begin again.`)}if(o.action==="resume"){let i=o.sessionId,a=o.projectPath,l=ll(),c=await xn(i,{machine_id:l,local_path:a}),m=await lo(i),u=await Jt(i).catch(()=>null);return d(`Resumed session on this machine.
7423
+ Note: cancellation is terminal. Start a new session with mist_plan to begin again.`)}if(r.action==="resume"){let i=r.sessionId,a=r.projectPath,l=vl(),c=await _n(i,{machine_id:l,local_path:a}),p=await cr(i),d=await Vt(i).catch(()=>null);return u(`Resumed session on this machine.
7718
7424
  Machine: ${l}
7719
7425
  Local path: ${c.local_path}
7720
7426
  Last seen: ${c.last_seen_at}
7721
7427
 
7722
- `+Vr(m)+(u?`
7428
+ `+es(p)+(d?`
7723
7429
 
7724
- Next instruction: ${u.instruction}${u.reason?` (${u.reason})`:""}`:""))}let n=ll(),r=await Sn(n,{includeIdle:o.includeIdle===!0});if(r.length===0){let i=o.includeIdle?"this machine has no sessions bound at all":"no active sessions touched in the last 24h on this machine";return d(`${i} (${n}).
7725
- `+(o.includeIdle?"Run mist_plan to start a new one.":'Run mist_plan to start a new one, mist_session action="resume" with a sessionId, or mist_session action="list" includeIdle=true to see older sessions.'))}let s=[`Sessions bound on this machine (${n}):`,"",...r.map(i=>` - ${i.id} status=${i.status}${i.paused_after_plan?" (paused)":""} ${i.description?`"${i.description.slice(0,60)}"`:"(no description)"}`)];return d(s.join(`
7726
- `))}var dl={name:"mist_session",description:"Read or correct a Mistflow session. Actions: status (current state + criteria), cancel (terminal), resume (bind this machine to a session for multi-machine work), list (sessions this machine has worked on). All state changes go through the backend so the host AI never directly mutates session state.",inputSchema:cl,handler:dh};import{existsSync as ph,readFileSync as mh}from"fs";import{join as hh,resolve as gh}from"path";import{z as Wn}from"zod";Ie();Ie();var uh=3e4;async function ul(t){let e=JSON.stringify({project_id:t.projectId,sql:t.sql,confirm_production:t.confirmProduction});return L("/api/runtime/query",{method:"POST",body:e,timeoutMs:uh,idempotent:!0})}var pl=Wn.object({action:Wn.literal("query").describe("Always 'query' in v1.0. Other actions reserved for future versions."),projectPath:Wn.string().optional().describe("Path to the Mistflow project (containing mistflow.json). Defaults to cwd."),sql:Wn.string().min(1).max(1e5).describe("Read-only SQL to execute. Must be a single SELECT / WITH / UNION query. INSERT/UPDATE/DELETE/DDL all rejected at the backend with a clear error."),confirmProduction:Wn.literal(!0).describe("Must be exactly `true`. Explicit acknowledgment that the query runs against the project's production database. Every call is audited and visible in the dashboard.")}),ml={name:"mist_runtime",description:"Run a read-only SQL query against a Mistflow project's PRODUCTION database. Use this for: inspecting data, debugging production behavior, counting rows, auditing user state. DO NOT use this for: implementing features, schema changes, or recurring workflows \u2014 those go through mist_plan / mist_implement. Writes (INSERT/UPDATE/DELETE/DDL) are rejected at the backend with a clear error. Set confirmProduction=true to acknowledge production access; every call is audited.",inputSchema:pl,handler:async t=>{let e=pl.safeParse(t);if(!e.success){let a=e.error.issues.map(l=>`${l.path.join(".")||"(root)"}: ${l.message}`).join("; ");return d(`Invalid mist_runtime input: ${a}. Required shape: { action: "query", sql: string, confirmProduction: true }.`,!0)}let o=e.data;if(!Te())return Oe("run a runtime query");let n=gh(o.projectPath??process.cwd()),r=hh(n,"mistflow.json");if(!ph(r))return Ve(n);let s;try{s=JSON.parse(mh(r,"utf-8")).projectId}catch{return d("Could not read mistflow.json.",!0)}if(!s)return d("This project hasn't been registered with Mistflow yet. Run mist_init first.",!0);let i;try{i=await ul({projectId:s,sql:o.sql,confirmProduction:o.confirmProduction})}catch(a){let l=a instanceof Error?a.message:String(a);return d(`mist_runtime query failed: ${l}`,!0)}return d(fh(i))}};function fh(t){let e=[];if(e.push(`Query executed in ${t.execution_time_ms}ms. Returned ${t.row_count} row${t.row_count===1?"":"s"}${t.truncated?" (truncated)":""}.`),t.redacted_field_count>0&&e.push(`Redacted ${t.redacted_field_count} field${t.redacted_field_count===1?"":"s"} that looked like secrets (passwords, API keys, tokens).`),e.push(`Query fingerprint: ${t.query_fingerprint}`),t.columns.length>0&&(e.push(""),e.push(`Columns: ${t.columns.map(o=>o.name).join(", ")}`)),t.rows.length>0){e.push("");let o=t.rows.slice(0,10);e.push(`First ${o.length} row${o.length===1?"":"s"}:`);for(let n of o)e.push(" "+JSON.stringify(n));t.rows.length>10&&e.push(` \u2026 ${t.rows.length-10} more in JSON below`)}return e.push(""),e.push("```json"),e.push(JSON.stringify(t,null,2)),e.push("```"),e.join(`
7727
- `)}Ie();var Oo=new yh({name:"mistflow",version:"1.0.0"},{capabilities:{tools:{}},instructions:rs()}),hl=[Ps,Us,qs,Hs,Ws,hi,Hi,Xs,$a,ei,Ha,za,Ka,Xa,al,dl,ml];Oo.setRequestHandler(vh,async()=>({tools:hl.map(t=>({name:t.name,description:t.description,inputSchema:kh(t.inputSchema)}))}));Oo.setRequestHandler(wh,async t=>{let e=hl.find(o=>o.name===t.params.name);if(!e)return d(`Unknown tool: ${t.params.name}`,!0);try{let o=e.inputSchema.safeParse(t.params.arguments);if(!o.success){let i=o.error.issues.map(a=>`${a.path.join(".")}: ${a.message}`).join(", ");return d(`Invalid input: ${i}`,!0)}let n=typeof o.data=="object"&&o.data!==null&&"sessionId"in o.data&&typeof o.data.sessionId=="string"?o.data.sessionId:void 0;if(n&&e.name!=="mist_session")try{let i=await yr(n,e.name);if(!i.allowed)return d(`Tool ${e.name} not allowed for this session.
7430
+ Next instruction: ${d.instruction}${d.reason?` (${d.reason})`:""}`:""))}let n=vl(),o=await Tn(n,{includeIdle:r.includeIdle===!0});if(o.length===0){let i=r.includeIdle?"this machine has no sessions bound at all":"no active sessions touched in the last 24h on this machine";return u(`${i} (${n}).
7431
+ `+(r.includeIdle?"Run mist_plan to start a new one.":'Run mist_plan to start a new one, mist_session action="resume" with a sessionId, or mist_session action="list" includeIdle=true to see older sessions.'))}let s=[`Sessions bound on this machine (${n}):`,"",...o.map(i=>` - ${i.id} status=${i.status}${i.paused_after_plan?" (paused)":""} ${i.description?`"${i.description.slice(0,60)}"`:"(no description)"}`)];return u(s.join(`
7432
+ `))}var xl={name:"mist_session",description:"Read or correct a Mistflow session. Actions: status (current state + criteria), cancel (terminal), resume (bind this machine to a session for multi-machine work), list (sessions this machine has worked on). All state changes go through the backend so the host AI never directly mutates session state.",inputSchema:kl,handler:Eh};import{existsSync as Oh,readFileSync as Mh}from"fs";import{join as jh,resolve as Dh}from"path";import{z as Gn}from"zod";ke();ke();var Nh=3e4;async function Sl(t){let e=JSON.stringify({project_id:t.projectId,sql:t.sql,confirm_production:t.confirmProduction});return j("/api/runtime/query",{method:"POST",body:e,timeoutMs:Nh,idempotent:!1})}var _l=Gn.object({action:Gn.literal("query").describe("Always 'query' in v1.0. Other actions reserved for future versions."),projectPath:Gn.string().optional().describe("Path to the Mistflow project (containing mistflow.json). Defaults to cwd."),sql:Gn.string().min(1).max(1e5).describe("Read-only SQL to execute. Must be a single SELECT / WITH / UNION query. INSERT/UPDATE/DELETE/DDL all rejected at the backend with a clear error."),confirmProduction:Gn.literal(!0).describe("Must be exactly `true`. Explicit acknowledgment that the query runs against the project's production database. Every call is audited and visible in the dashboard.")}),Tl={name:"mist_runtime",description:"Run a read-only SQL query against a Mistflow project's PRODUCTION database. Use this for: inspecting data, debugging production behavior, counting rows, auditing user state. DO NOT use this for: implementing features, schema changes, or recurring workflows \u2014 those go through mist_plan / mist_implement. Writes (INSERT/UPDATE/DELETE/DDL) are rejected at the backend with a clear error. Set confirmProduction=true to acknowledge production access; every call is audited.",inputSchema:_l,handler:async t=>{let e=_l.safeParse(t);if(!e.success){let l=e.error.issues.map(c=>`${c.path.join(".")||"(root)"}: ${c.message}`).join("; ");return u(`Invalid mist_runtime input: ${l}. Required shape: { action: "query", sql: string, confirmProduction: true }.`,!0)}let r=e.data;if(!Te())return Oe("run a runtime query");let n=Dh(r.projectPath??process.cwd()),o=jh(n,"mistflow.json");if(!Oh(o))return et(n);let s;try{s=JSON.parse(Mh(o,"utf-8")).projectId}catch{return u("Could not read mistflow.json.",!0)}if(!s)return u("This project hasn't been registered with Mistflow yet. Run mist_init first.",!0);let i=r.sql.replace(/^\s*(?:\/\*[\s\S]*?\*\/|--[^\n]*\n?)*\s*/,"").trim();if(!/^(select|with|explain|values)\b/i.test(i))return u("mist_runtime accepts read-only SQL only (SELECT / WITH / EXPLAIN / VALUES). INSERT/UPDATE/DELETE/DDL/TRUNCATE are rejected. For schema or data changes, use mist_plan + mist_implement.",!0);let a;try{a=await Sl({projectId:s,sql:r.sql,confirmProduction:r.confirmProduction})}catch(l){let c=l instanceof Error?l.message:String(l);return u(`mist_runtime query failed: ${c}`,!0)}return u(Lh(a))}};function Lh(t){let e=[];if(e.push(`Query executed in ${t.execution_time_ms}ms. Returned ${t.row_count} row${t.row_count===1?"":"s"}${t.truncated?" (truncated)":""}.`),t.redacted_field_count>0&&e.push(`Redacted ${t.redacted_field_count} field${t.redacted_field_count===1?"":"s"} that looked like secrets (passwords, API keys, tokens).`),e.push(`Query fingerprint: ${t.query_fingerprint}`),t.columns.length>0&&(e.push(""),e.push(`Columns: ${t.columns.map(r=>r.name).join(", ")}`)),t.rows.length>0){e.push("");let r=t.rows.slice(0,10);e.push(`First ${r.length} row${r.length===1?"":"s"}:`);for(let n of r)e.push(" "+JSON.stringify(n));t.rows.length>10&&e.push(` \u2026 ${t.rows.length-10} more in JSON below`)}return e.push(""),e.push("```json"),e.push(JSON.stringify(t,null,2)),e.push("```"),e.join(`
7433
+ `)}ke();var Or=new $h({name:"mistflow",version:"1.0.0"},{capabilities:{tools:{}},instructions:ds()}),Cl=[$s,Ys,Qs,ei,ni,Ti,ta,ci,Ya,ui,tl,nl,sl,dl,wl,xl,Tl];Or.setRequestHandler(qh,async()=>({tools:Cl.map(t=>({name:t.name,description:t.description,inputSchema:Bh(t.inputSchema)}))}));Or.setRequestHandler(Fh,async t=>{let e=Cl.find(r=>r.name===t.params.name);if(!e)return u(`Unknown tool: ${t.params.name}`,!0);try{let r=e.inputSchema.safeParse(t.params.arguments);if(!r.success){let i=r.error.issues.map(a=>`${a.path.join(".")}: ${a.message}`).join(", ");return u(`Invalid input: ${i}`,!0)}let n=typeof r.data=="object"&&r.data!==null&&"sessionId"in r.data&&typeof r.data.sessionId=="string"?r.data.sessionId:void 0;if(n&&e.name!=="mist_session")try{let i=await wo(n,e.name);if(!i.allowed)return u(`Tool ${e.name} not allowed for this session.
7728
7434
  Reason: ${i.reason}
7729
7435
  Status: ${i.status}
7730
7436
 
7731
- Call mist_plan with this sessionId to find out what the host AI should do next.`,!0)}catch(i){if(i instanceof X&&i.code==="not_found")console.error(`Guard check 404 for tool ${e.name}: ${i.message}`);else throw i}let r=t.params._meta?.progressToken,s={server:Oo,progressToken:r};try{return await e.handler(o.data,s)}finally{s.cleanup?.()}}catch(o){let n=o instanceof Error?o.message:"An unexpected error occurred";return console.error("Tool error:",o),d(n,!0)}});async function xh(){let t=process.argv.indexOf("--api-url");t!==-1&&process.argv[t+1]&&(process.env.MISTFLOW_API_URL=process.argv[t+1]),process.argv.includes("--local")&&!process.env.MISTFLOW_API_URL&&(process.env.MISTFLOW_API_URL="http://localhost:9100");let e=new bh;await Oo.connect(e),console.error(`Mistflow MCP server running on stdio (API: ${process.env.MISTFLOW_API_URL||"https://api.mistflow.ai"})`)}xh().catch(t=>{console.error("Fatal error:",t),process.exit(1)});
7437
+ Call mist_plan with this sessionId to find out what the host AI should do next.`,!0)}catch(i){if(i instanceof Z&&i.code==="not_found")console.error(`Guard check 404 for tool ${e.name}: ${i.message}`);else throw i}let o=t.params._meta?.progressToken,s={server:Or,progressToken:o};try{return await e.handler(r.data,s)}finally{s.cleanup?.()}}catch(r){let n=r instanceof Error?r.message:"An unexpected error occurred";return console.error("Tool error:",r),u(n,!0)}});async function Hh(){let t=process.argv.indexOf("--api-url");t!==-1&&process.argv[t+1]&&(process.env.MISTFLOW_API_URL=process.argv[t+1]),process.argv.includes("--local")&&!process.env.MISTFLOW_API_URL&&(process.env.MISTFLOW_API_URL="http://localhost:9100");let e=new Uh;await Or.connect(e),console.error(`Mistflow MCP server running on stdio (API: ${process.env.MISTFLOW_API_URL||"https://api.mistflow.ai"})`)}Hh().catch(t=>{console.error("Fatal error:",t),process.exit(1)});