@mistflow-ai/mcp 0.1.7 → 0.3.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.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import{a as Ct,d as Tt,e as Fe}from"./chunk-ANYHR4WA.js";import{b as gr,c as ia,d as na,e as mr}from"./chunk-6ISXH6NJ.js";import{A as Ka,B as qe,C as oa,D as Ya,E as Ja,F as Qa,G as Xa,H as Za,I as er,J as tr,K as ar,L as rr,M as pt,N as or,O as ir,P as nr,Q as sr,R as Dt,S as lr,T as dr,U as cr,V as pr,W as ur,Y as hr,b as Oa,c as Ga,d as St,e as Wa,f as aa,g as Pt,h as ce,i as De,j as Ve,k as Ae,l as _a,m as he,n as ja,r as za,s as $a,t as Bt,v as Va,w as Ue,x as qa,y as ra}from"./chunk-FKB6Y7BA.js";import{Server as Wl}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as _l}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as jl,ListToolsRequestSchema as zl}from"@modelcontextprotocol/sdk/types.js";import{zodToJsonSchema as $l}from"zod-to-json-schema";function p(r,e=!1){let t=r;try{let a=Ga();a&&(t=r+a)}catch{}return{content:[{type:"text",text:t}],isError:e}}function Te(r){return p(`This is not a Mistflow project (no mistflow.json found at ${r}).
1
+ import{a as Tt,d as Ct,e as Ne}from"./chunk-ANYHR4WA.js";import{b as fr,c as la,d as da,e as br}from"./chunk-3BQGCB7F.js";import{B as Ja,C as ze,D as sa,E as Qa,F as Xa,G as Za,H as er,I as tr,J as ar,K as rr,L as or,M as ir,N as lt,O as nr,P as sr,Q as lr,R as dr,S as Bt,T as cr,U as pr,V as ur,W as hr,X as gr,Z as mr,b as Oa,c as Ga,d as st,e as Wa,f as ia,g as Pt,h as ue,i as Se,j as je,k as Ae,l as _a,m as me,n as ja,o as za,s as $a,t as Va,u as qa,w as Ka,x as Le,y as Ya,z as na}from"./chunk-MAAENV5J.js";import{Server as td}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as ad}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as rd,ListToolsRequestSchema as od}from"@modelcontextprotocol/sdk/types.js";import{zodToJsonSchema as id}from"zod-to-json-schema";function p(r,e=!1){let a=r;try{let t=Ga();t&&(a=r+t)}catch{}return{content:[{type:"text",text:a}],isError:e}}function Te(r){return p(`This is not a Mistflow project (no mistflow.json found at ${r}).
2
2
 
3
3
  Mistflow creates new projects from scratch \u2014 it doesn't work inside existing codebases.
4
4
 
@@ -7,14 +7,14 @@ To get started:
7
7
  2. Run mist_build (action: 'init') to create a new project in a subdirectory
8
8
  3. Run mist_build (action: 'implement') to build each step
9
9
 
10
- If you want to deploy an existing project, use your framework's deploy tools directly.`,!0)}async function Ee(r,e){try{let{getPage:t,takeScreenshot:a}=await import("./browser-manager-K5BT5YXO.js"),i=await t();await i.goto(r,{waitUntil:"domcontentloaded",timeout:15e3}),await i.waitForLoadState("networkidle").catch(()=>{});let n=await a(i,!1);return{content:[{type:"text",text:e},{type:"image",data:n.toString("base64"),mimeType:"image/png"}]}}catch{return p(e)}}import{z as sa}from"zod";import{platform as bi}from"os";import{execFile as fr}from"child_process";var yi=sa.object({apiKey:sa.string().optional().describe("API key (mist_...) for headless auth. Skips the device code flow entirely. Generate one at app.mistflow.ai/mcp-keys."),deviceCode:sa.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 xi(r){return"error"in r}function wi(r){return new Promise(e=>setTimeout(e,r))}function vi(r){return new Promise(e=>{let t=bi();t==="win32"?fr("cmd.exe",["/c","start","",r],a=>{a&&console.error("Could not open browser:",a.message),e(!a)}):fr(t==="darwin"?"open":"xdg-open",[r],i=>{i&&console.error("Could not open browser:",i.message),e(!i)}),setTimeout(()=>e(!1),5e3)})}var ki={fetch:globalThis.fetch,openBrowser:vi};async function br(r,e,t,a){let i=t;for(let n=0;n<e;n++){await wi(i);let o;try{let l=await a.fetch(`${Ve()}/auth/poll`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:r})});if(!l.ok)continue;o=await l.json()}catch{continue}if(xi(o))switch(o.error){case"authorization_pending":continue;case"slow_down":i+=5e3;continue;case"expired_token":return p("The sign-in link expired. Run mist_setup again to get a new code.",!0);case"access_denied":return p("Sign-in was cancelled. Run mist_setup again to try again.",!0);case"already_exchanged":return p("This sign-in link was already used. Run mist_setup again to get a new code.",!0)}let s=o.email||o.org_name||o.org_slug;return aa({apiKey:o.api_key,apiKeyId:o.api_key_id,apiKeyName:o.api_key_name,orgId:o.org_id,orgSlug:o.org_slug,email:o.email}),p(`Connected to Mistflow as ${s}. You are ready to build and deploy.`)}return null}async function Si(r,e=ki){let t=r;if(t?.apiKey)try{let o=await e.fetch(`${Ve()}/api/org`,{headers:{Authorization:`ApiKey ${t.apiKey}`}});if(!o.ok)return p("Invalid API key. Check the key and try again.",!0);let s=await o.json();return aa({apiKey:t.apiKey,orgId:s.id,orgSlug:s.slug}),p(`Connected to Mistflow as ${s.slug} via API key. You are ready to build and deploy.`)}catch{return p("Cannot reach Mistflow servers. Check your internet connection.",!0)}if(t?.deviceCode){let o=await br(t.deviceCode,6,5e3,e);return o||p(JSON.stringify({status:"pending",deviceCode:t.deviceCode,instruction:"The user hasn't approved yet. Wait ~15 seconds and call mist_setup again with the same deviceCode."}))}let a;try{let o=await e.fetch(`${Ve()}/auth/device`,{method:"POST",headers:{"Content-Type":"application/json"}});if(!o.ok)return p("Cannot reach Mistflow servers. Check your internet connection.",!0);a=await o.json()}catch{return p("Cannot reach Mistflow servers. Check your internet connection.",!0)}let i=`${a.verification_uri}?code=${a.user_code}`;console.error(`
10
+ If you want to deploy an existing project, use your framework's deploy tools directly.`,!0)}async function Fe(r,e){try{let{getPage:a,takeScreenshot:t}=await import("./browser-manager-K5BT5YXO.js"),i=await a();await i.goto(r,{waitUntil:"domcontentloaded",timeout:15e3}),await i.waitForLoadState("networkidle").catch(()=>{});let s=await t(i,!1);return{content:[{type:"text",text:e},{type:"image",data:s.toString("base64"),mimeType:"image/png"}]}}catch{return p(e)}}import{z as ca}from"zod";import{platform as Pi}from"os";import{execFile as yr}from"child_process";var Bi=ca.object({apiKey:ca.string().optional().describe("API key (mist_...) for headless auth. Skips the device code flow entirely. Generate one at app.mistflow.ai/mcp-keys."),deviceCode:ca.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 Ai(r){return"error"in r}function Di(r){return new Promise(e=>setTimeout(e,r))}function Ii(r){return new Promise(e=>{let a=Pi();a==="win32"?yr("cmd.exe",["/c","start","",r],t=>{t&&console.error("Could not open browser:",t.message),e(!t)}):yr(a==="darwin"?"open":"xdg-open",[r],i=>{i&&console.error("Could not open browser:",i.message),e(!i)}),setTimeout(()=>e(!1),5e3)})}var Mi={fetch:globalThis.fetch,openBrowser:Ii};async function xr(r,e,a,t){let i=a;for(let s=0;s<e;s++){await Di(i);let o;try{let l=await t.fetch(`${je()}/auth/poll`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:r})});if(!l.ok)continue;o=await l.json()}catch{continue}if(Ai(o))switch(o.error){case"authorization_pending":continue;case"slow_down":i+=5e3;continue;case"expired_token":return p("The sign-in link expired. Run mist_setup again to get a new code.",!0);case"access_denied":return p("Sign-in was cancelled. Run mist_setup again to try again.",!0);case"already_exchanged":return p("This sign-in link was already used. Run mist_setup again to get a new code.",!0)}let n=o.email||o.org_name||o.org_slug;return ia({apiKey:o.api_key,apiKeyId:o.api_key_id,apiKeyName:o.api_key_name,orgId:o.org_id,orgSlug:o.org_slug,email:o.email}),p(`Connected to Mistflow as ${n}. You are ready to build and deploy.`)}return null}async function Ri(r,e=Mi){let a=r;if(a?.apiKey)try{let o=await e.fetch(`${je()}/api/org`,{headers:{Authorization:`ApiKey ${a.apiKey}`}});if(!o.ok)return p("Invalid API key. Check the key and try again.",!0);let n=await o.json();return ia({apiKey:a.apiKey,orgId:n.id,orgSlug:n.slug}),p(`Connected to Mistflow as ${n.slug} via API key. You are ready to build and deploy.`)}catch{return p("Cannot reach Mistflow servers. Check your internet connection.",!0)}if(a?.deviceCode){let o=await xr(a.deviceCode,6,5e3,e);return o||p(JSON.stringify({status:"pending",deviceCode:a.deviceCode,instruction:"The user hasn't approved yet. Wait ~15 seconds and call mist_setup again with the same deviceCode."}))}let t;try{let o=await e.fetch(`${je()}/auth/device`,{method:"POST",headers:{"Content-Type":"application/json"}});if(!o.ok)return p("Cannot reach Mistflow servers. Check your internet connection.",!0);t=await o.json()}catch{return p("Cannot reach Mistflow servers. Check your internet connection.",!0)}let i=`${t.verification_uri}?code=${t.user_code}`;console.error(`
11
11
  Sign in at: ${i}
12
- Your code: ${a.user_code}
13
- `);try{await e.openBrowser(i)}catch{}let n=await br(a.device_code,6,5e3,e);return n||p(JSON.stringify({status:"pending",deviceCode:a.device_code,signInUrl:i,userCode:a.user_code,instruction:"The user hasn't approved yet. Wait ~15 seconds, then call mist_setup again with deviceCode='"+a.device_code+"' to check if they approved."}))}var yr={name:"mist_setup",description:"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).",inputSchema:yi,handler:r=>Si(r)};import{z as te}from"zod";import{existsSync as ht,mkdirSync as ma,readFileSync as Lr,readdirSync as Di,statSync as Ai,unlinkSync as Ii,writeFileSync as fa}from"fs";import{dirname as Mi,isAbsolute as Ri,join as be}from"path";import{homedir as He}from"os";import{createHash as Li,createHmac as Nr,randomBytes as Ni,randomUUID as Rr,timingSafeEqual as Fi}from"crypto";var la={},Ze=[];function et(r){let e=Ze.find(a=>a.id===r);if(e)return e;let t=r.toLowerCase().replace(/[^a-z0-9]/g,"");return Ze.find(a=>{let i=a.title.toLowerCase().replace(/[^a-z0-9]/g,"");return i===t||i.includes(t)||t.includes(i)})}function xr(r){return la[r]}function da(r){return r?Ze.filter(e=>e.category.toLowerCase()===r.toLowerCase()):Ze}function wr(r){let e=r??Ze;if(e.length===0)return"Landing page presets have been replaced by the tone-based system. The landing page tone is now auto-selected based on your app's description during planning.";let t={};for(let i of e){t[i.category]||(t[i.category]=[]);let n=la[i.id],o=n?` \u2014 ${n.description}`:"";t[i.category].push(`${i.id} \u2014 "${i.title}"${o}`)}let a=[];for(let[i,n]of Object.entries(t))a.push(`**${i}**:
14
- ${n.map(o=>` \u2022 ${o}`).join(`
15
- `)}`);return a.join(`
12
+ Your code: ${t.user_code}
13
+ `);try{await e.openBrowser(i)}catch{}let s=await xr(t.device_code,6,5e3,e);return s||p(JSON.stringify({status:"pending",deviceCode:t.device_code,signInUrl:i,userCode:t.user_code,instruction:"The user hasn't approved yet. Wait ~15 seconds, then call mist_setup again with deviceCode='"+t.device_code+"' to check if they approved."}))}var wr={name:"mist_setup",description:"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).",inputSchema:Bi,handler:r=>Ri(r)};import{z as ie}from"zod";import{existsSync as ct,mkdirSync as ya,readFileSync as Fr,readdirSync as Ui,statSync as Hi,unlinkSync as Oi,writeFileSync as xa}from"fs";import{dirname as Gi,isAbsolute as Wi,join as ye}from"path";import{homedir as Ee}from"os";import{createHash as _i,createHmac as Er,randomBytes as ji,randomUUID as Lr,timingSafeEqual as zi}from"crypto";var pa={},Ye=[];function Je(r){let e=Ye.find(t=>t.id===r);if(e)return e;let a=r.toLowerCase().replace(/[^a-z0-9]/g,"");return Ye.find(t=>{let i=t.title.toLowerCase().replace(/[^a-z0-9]/g,"");return i===a||i.includes(a)||a.includes(i)})}function vr(r){return pa[r]}function ua(r){return r?Ye.filter(e=>e.category.toLowerCase()===r.toLowerCase()):Ye}function kr(r){let e=r??Ye;if(e.length===0)return"Landing page presets have been replaced by the tone-based system. The landing page tone is now auto-selected based on your app's description during planning.";let a={};for(let i of e){a[i.category]||(a[i.category]=[]);let s=pa[i.id],o=s?` \u2014 ${s.description}`:"";a[i.category].push(`${i.id} \u2014 "${i.title}"${o}`)}let t=[];for(let[i,s]of Object.entries(a))t.push(`**${i}**:
14
+ ${s.map(o=>` \u2022 ${o}`).join(`
15
+ `)}`);return t.join(`
16
16
 
17
- `)}function Ci(r){return r.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}function vr(r){return(r?da(r):Ze).map(t=>{let a=la[t.id];return{id:t.id,slug:Ci(t.title),title:t.title,category:t.category,description:a?.description??"",tags:a?.tags??[],theme:a?.theme??"dark",colors:a?.colors??[],style:a?.style??""}})}function kr(r,e){return[]}var Cr={"SF Pro":"Inter","SF Pro Display":"Inter","SF Pro Text":"Inter","SF Pro Rounded":"Nunito Sans","SF Mono":"JetBrains Mono","system-ui":"Inter","ui-sans-serif":"Inter",Arial:"DM Sans","Inter Variable":"Inter",inter:"Inter","sohne-var":"Inter",sohne:"Inter",S\u00F6hne:"Inter",s\u00F6hne:"Inter",CohereText:"Source Serif 4","Unica77 Cohere Web":"Inter",Waldenburg:"Playfair Display",waldenburgNormal:"Playfair Display","rb-freigeist-neue":"Space Grotesk","basier-square":"Inter",Basier:"Inter","The Future":"Outfit",GeistMono:"JetBrains Mono",universalSans:"Inter",CursorGothic:"DM Serif Display",jjannon:"Crimson Text","Camera Plain Variable":"DM Sans",domaine:"DM Serif Display","Dammit Sans":"Rubik","Super Sans VF":"DM Sans","Matter Regular":"Inter","Degular Display":"DM Sans",abcDiatype:"DM Sans","ABC Diatype":"DM Sans","ABC Diatype Mono":"JetBrains Mono",__hashicorpSans_96f0ca:"Inter","MongoDB Value Serif":"Source Serif 4","Euclid Circular A":"DM Sans",Haas:"Inter","Cal Sans":"DM Sans",figmaSans:"Inter","GT Walsheim Framer Medium":"DM Sans","GT Walsheim":"DM Sans","GT Walsheim Pro":"DM Sans",Saans:"Inter",MediumLL:"Inter",NotionInter:"Inter",Roobert:"DM Sans","Roobert PRO Medium":"DM Sans","Pin Sans":"DM Sans",CoinbaseDisplay:"DM Sans",CoinbaseSans:"Inter","Kraken-Brand":"Space Grotesk","Kraken-Product":"Inter","Aeonik Pro":"DM Sans","Wise Sans":"DM Sans","BMWTypeNextLatin Light":"Inter",BMWTypeNextLatin:"Inter","IBM Plex Sans":"IBM Plex Sans","NVIDIA-EMEA":"Inter",SpotifyMixUI:"DM Sans","Uber Move":"DM Sans","Uber Move Text":"DM Sans",UberMoveText:"DM Sans","Airbnb Cereal VF":"Nunito Sans","Airbnb Cereal":"Nunito Sans",Cereal:"Nunito Sans","Noto Sans":"Noto Sans","D-DIN":"DM Sans","D-DIN-Bold":"DM Sans","Berkeley Mono":"JetBrains Mono","Circular Std":"DM Sans",Circular:"DM Sans",Graphik:"Inter","Neue Montreal":"Inter"},Sr={landing:["visual theme","color","typography","component","layout","do","agent prompt"],design:["visual theme","color","typography","component","layout","depth","do","responsive","agent prompt"],dashboard:["color","typography","component","layout","depth","agent prompt"],crud:["component","layout","depth","agent prompt"],auth:["typography","component","agent prompt"],layout:["color","typography","layout","responsive","agent prompt"],admin:["color","typography","component","layout","depth","agent prompt"],general:["color","typography","component","layout","agent prompt"]},At=[{id:"cohere",name:"Cohere",category:"AI & Machine Learning",sections:[{title:"1. Visual Theme & Atmosphere",content:`Cohere's interface is a polished enterprise command deck \u2014 confident, clean, and designed to make AI feel like serious infrastructure rather than a consumer toy. The experience lives on a bright white canvas where content is organized into generously rounded cards (22px radius) that create an organic, cloud-like containment language. This is a site that speaks to CTOs and enterprise architects: professional without being cold, sophisticated without being intimidating.
17
+ `)}function Ni(r){return r.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}function Sr(r){return(r?ua(r):Ye).map(a=>{let t=pa[a.id];return{id:a.id,slug:Ni(a.title),title:a.title,category:a.category,description:t?.description??"",tags:t?.tags??[],theme:t?.theme??"dark",colors:t?.colors??[],style:t?.style??""}})}function Tr(r,e){return[]}var Pr={"SF Pro":"Inter","SF Pro Display":"Inter","SF Pro Text":"Inter","SF Pro Rounded":"Nunito Sans","SF Mono":"JetBrains Mono","system-ui":"Inter","ui-sans-serif":"Inter",Arial:"DM Sans","Inter Variable":"Inter",inter:"Inter","sohne-var":"Inter",sohne:"Inter",S\u00F6hne:"Inter",s\u00F6hne:"Inter",CohereText:"Source Serif 4","Unica77 Cohere Web":"Inter",Waldenburg:"Playfair Display",waldenburgNormal:"Playfair Display","rb-freigeist-neue":"Space Grotesk","basier-square":"Inter",Basier:"Inter","The Future":"Outfit",GeistMono:"JetBrains Mono",universalSans:"Inter",CursorGothic:"DM Serif Display",jjannon:"Crimson Text","Camera Plain Variable":"DM Sans",domaine:"DM Serif Display","Dammit Sans":"Rubik","Super Sans VF":"DM Sans","Matter Regular":"Inter","Degular Display":"DM Sans",abcDiatype:"DM Sans","ABC Diatype":"DM Sans","ABC Diatype Mono":"JetBrains Mono",__hashicorpSans_96f0ca:"Inter","MongoDB Value Serif":"Source Serif 4","Euclid Circular A":"DM Sans",Haas:"Inter","Cal Sans":"DM Sans",figmaSans:"Inter","GT Walsheim Framer Medium":"DM Sans","GT Walsheim":"DM Sans","GT Walsheim Pro":"DM Sans",Saans:"Inter",MediumLL:"Inter",NotionInter:"Inter",Roobert:"DM Sans","Roobert PRO Medium":"DM Sans","Pin Sans":"DM Sans",CoinbaseDisplay:"DM Sans",CoinbaseSans:"Inter","Kraken-Brand":"Space Grotesk","Kraken-Product":"Inter","Aeonik Pro":"DM Sans","Wise Sans":"DM Sans","BMWTypeNextLatin Light":"Inter",BMWTypeNextLatin:"Inter","IBM Plex Sans":"IBM Plex Sans","NVIDIA-EMEA":"Inter",SpotifyMixUI:"DM Sans","Uber Move":"DM Sans","Uber Move Text":"DM Sans",UberMoveText:"DM Sans","Airbnb Cereal VF":"Nunito Sans","Airbnb Cereal":"Nunito Sans",Cereal:"Nunito Sans","Noto Sans":"Noto Sans","D-DIN":"DM Sans","D-DIN-Bold":"DM Sans","Berkeley Mono":"JetBrains Mono","Circular Std":"DM Sans",Circular:"DM Sans",Graphik:"Inter","Neue Montreal":"Inter"},Cr={landing:["visual theme","color","typography","component","layout","do","agent prompt"],design:["visual theme","color","typography","component","layout","depth","do","responsive","agent prompt"],dashboard:["color","typography","component","layout","depth","agent prompt"],crud:["component","layout","depth","agent prompt"],auth:["typography","component","agent prompt"],layout:["color","typography","layout","responsive","agent prompt"],admin:["color","typography","component","layout","depth","agent prompt"],general:["color","typography","component","layout","agent prompt"]},At=[{id:"cohere",name:"Cohere",category:"AI & Machine Learning",sections:[{title:"1. Visual Theme & Atmosphere",content:`Cohere's interface is a polished enterprise command deck \u2014 confident, clean, and designed to make AI feel like serious infrastructure rather than a consumer toy. The experience lives on a bright white canvas where content is organized into generously rounded cards (22px radius) that create an organic, cloud-like containment language. This is a site that speaks to CTOs and enterprise architects: professional without being cold, sophisticated without being intimidating.
18
18
 
19
19
  The design language bridges two worlds with a dual-typeface system: CohereText, a custom display serif with tight tracking, gives headlines the gravitas of a technology manifesto, while Unica77 Cohere Web handles all body and UI text with geometric Swiss precision. This serif/sans pairing creates a "confident authority meets engineering clarity" personality that perfectly reflects an enterprise AI platform.
20
20
 
@@ -8794,14 +8794,14 @@ What makes Uber's design truly distinctive is its use of full-bleed photography
8794
8794
  5. For shadows, use "whisper shadow (rgba(0,0,0,0.12) 0px 4px 16px)" -- never heavy drop shadows
8795
8795
  6. Keep layouts compact and information-dense -- Uber is efficient, not airy
8796
8796
  7. Illustrations should be warm and human -- describe "stylized people in warm tones" not abstract shapes
8797
- 8. Pair black CTAs with white secondaries for balanced dual-action layouts`}]}],ca={cohere:{description:"Cohere's interface is a polished enterprise command deck \u2014 confident, clean, and designed to make AI feel like serious infrastructure rather than a consumer toy. The experience lives on a bright wh...",category:"AI & Machine Learning",theme:"light",colors:["#000000","#212121","#17171c","#1863dc","#4c6ee6","#9b60aa"],fonts:{heading:"CohereText",body:"Unica77 Cohere Web"},tags:["light","ai-machine-learning","minimal","bold","enterprise","gradient"]},elevenlabs:{description:"ElevenLabs' website is a study in restrained elegance \u2014 a near-white canvas (`#ffffff`, `#f5f5f5`) where typography and subtle shadows do all the heavy lifting. The design feels like a premium audi...",category:"AI & Machine Learning",theme:"light",colors:["#ffffff","#f5f5f5","#f5f2ef","#000000","#4e4e4e","#777169"],fonts:{heading:"Waldenburg",body:"Inter"},tags:["light","ai-machine-learning","minimal","bold"]},minimax:{description:"MiniMax's website is a clean, product-showcase platform for a Chinese AI technology company that bridges consumer-friendly appeal with technical credibility. The design language is predominantly wh...",category:"AI & Machine Learning",theme:"light",colors:["#1456f0","#3daeff","#ea5ec1","#bfdbfe","#60a5fa","#3b82f6"],fonts:{heading:"DM Sans",body:"DM Sans"},tags:["light","ai-machine-learning","minimal","bold","playful","gradient"]},mistral:{description:"Mistral AI's interface is a sun-drenched landscape rendered in code \u2014 a warm, bold, unapologetically European design that trades the typical blue-screen AI aesthetic for golden amber, burnt orange,...",category:"AI & Machine Learning",theme:"light",colors:["#fa520f","#fb6424","#ff8105","#ff8a00","#ffa110","#ffb83e"],fonts:{heading:"Arial",body:"Inter"},tags:["light","ai-machine-learning","minimal","bold","elegant","gradient"]},ollama:{description:"Ollama's interface is radical minimalism taken to its logical conclusion \u2014 a pure-white void where content floats without decoration, shadow, or color. The design philosophy mirrors the product its...",category:"AI & Machine Learning",theme:"light",colors:["#000000","#262626","#090909","#ffffff","#fafafa","#e5e5e5"],fonts:{heading:"SF Pro Rounded",body:"ui-sans-serif"},tags:["light","ai-machine-learning","minimal","playful"]},opencode:{description:"OpenCode's website embodies a terminal-native, monospace-first aesthetic that reflects its identity as an open source AI coding agent. The entire visual system is built on a stark dark-on-light con...",category:"AI & Machine Learning",theme:"light",colors:["#201d1d","#fdfcfc","#9a9898","#302c2c","#646262","#f1eeee"],fonts:{heading:"Inter",body:"16px (1.00rem)"},tags:["light","ai-machine-learning","minimal","bold"]},replicate:{description:"Replicate's interface is a developer playground crackling with creative energy \u2014 a bold, high-contrast design that feels more like a music festival poster than a typical API platform. The hero sect...",category:"AI & Machine Learning",theme:"dark",colors:["#202020","#ea2804","#dd4425","#2b9a66","#24292e","#ffffff"],fonts:{heading:"rb-freigeist-neue",body:"basier-square"},tags:["dark","ai-machine-learning","minimal","bold","playful","gradient"]},runwayml:{description:"Runway's interface is a cinematic reel brought to life as a website \u2014 a dark, editorial, film-production-grade design where full-bleed photography and video ARE the primary UI elements. This is not...",category:"AI & Machine Learning",theme:"dark",colors:["#000000","#030303","#1a1a1a","#ffffff","#fefefe","#e9ecf2"],fonts:{heading:"Inter",body:"Inter"},tags:["dark","ai-machine-learning","minimal","bold"]},together:{description:"Together AI's interface is a pastel-gradient dreamscape built for enterprise AI infrastructure \u2014 a design that somehow makes GPU clusters and model inference feel light, airy, and optimistic. The h...",category:"AI & Machine Learning",theme:"light",colors:["#ef2cc1","#fc4c02","#010120","#bdbbff","#ffffff","#000000"],fonts:{heading:"The Future",body:"Inter"},tags:["light","ai-machine-learning","minimal","playful","enterprise","modern","elegant","data-dense","gradient"]},voltagent:{description:"VoltAgent's interface is a deep-space command terminal for the AI age \u2014 a developer-facing darkness built on near-pure-black surfaces (`#050507`) where the only interruption is the electric pulse o...",category:"AI & Machine Learning",theme:"dark",colors:["#00d992","#2fd6a1","#10b981","#818cf8","#306cce","#2554a0"],fonts:{heading:"system-ui",body:"Inter"},tags:["dark","ai-machine-learning","playful","data-dense"]},x:{description:"xAI's website is a masterclass in dark-first, monospace-driven brutalist minimalism -- a design system that feels like it was built by engineers who understand that restraint is the ultimate form o...",category:"AI & Machine Learning",theme:"dark",colors:["#ffffff","#1f2228","#000000"],fonts:{heading:"GeistMono",body:"universalSans"},tags:["dark","ai-machine-learning","minimal","elegant","data-dense","gradient"]},cursor:{description:"Cursor's website is a study in warm minimalism meets code-editor elegance. The entire experience is built on a warm off-white canvas (`#f2f1ed`) with dark warm-brown text (`#26251e`) -- not pure bl...",category:"Developer Tools",theme:"light",colors:["#26251e","#f2f1ed","#e6e5e0","#ffffff","#000000","#f54e00"],fonts:{heading:"CursorGothic",body:"jjannon"},tags:["light","developer-tools","minimal"]},expo:{description:"Expo's interface is a luminous, confidence-radiating developer platform built on the premise that tools for building apps should feel as polished as the apps themselves. The entire experience lives...",category:"Developer Tools",theme:"dark",colors:["#000000","#1c2024","#0d74ce","#476cff","#47c2ff","#8145b5"],fonts:{heading:"Inter",body:"Inter"},tags:["dark","developer-tools","playful"]},linear:{description:"Linear's website is a masterclass in dark-mode-first product design \u2014 a near-black canvas (`#08090a`) where content emerges from darkness like starlight. The overall impression is one of extreme pr...",category:"Developer Tools",theme:"dark",colors:["#010102","#08090a","#0f1011","#191a1b","#28282c","#f7f8f8"],fonts:{heading:"Inter Variable",body:"Inter Variable"},tags:["dark","developer-tools","minimal","bold"]},lovable:{description:"Lovable's website radiates warmth through restraint. The entire page sits on a creamy, parchment-toned background (`#f7f4ed`) that immediately separates it from the cold-white conventions of most d...",category:"Developer Tools",theme:"light",colors:["#f7f4ed","#1c1c1c","#fcfbf8","#5f5f5d","#eceae4","#3b82f6"],fonts:{heading:"Camera Plain Variable",body:"Camera Plain Variable"},tags:["light","developer-tools","minimal"]},mintlify:{description:"Mintlify's website is a study in documentation-as-product design \u2014 a white, airy, information-rich surface that treats clarity as its highest aesthetic value. The page opens with a luminous white (...",category:"Developer Tools",theme:"light",colors:["#0d0d0d","#ffffff","#18e299","#d4fae8","#0fa76e","#c37d0d"],fonts:{heading:"Inter",body:"Inter"},tags:["light","developer-tools","minimal","bold","gradient"]},posthog:{description:"PostHog's website feels like a startup's internal wiki that escaped into the wild \u2014 warm, irreverent, and deliberately anti-corporate. The background isn't the expected crisp white or dark void of ...",category:"Developer Tools",theme:"light",colors:["#4d4f46","#23251d","#f54e00","#f7a501","#b17816","#3b82f6"],fonts:{heading:"IBM Plex Sans Variable",body:"IBM Plex Sans Variable"},tags:["light","developer-tools","bold","playful","modern","gradient"]},raycast:{description:"Raycast's marketing site feels like the dark interior of a precision instrument \u2014 a Swiss watch case carved from obsidian. The background isn't just dark, it's an almost-black blue-tint (`#07080a`)...",category:"Developer Tools",theme:"dark",colors:["#07080a","#ffffff","#ff6363","#55b3ff","#5fc992","#ffbc33"],fonts:{heading:"Inter",body:"16px"},tags:["dark","developer-tools","minimal","data-dense","gradient"]},resend:{description:"Resend's website is a dark, cinematic canvas that treats email infrastructure like a luxury product. The entire page is draped in pure black (`#000000`) with text that glows in near-white (`#f0f0f0...",category:"Developer Tools",theme:"dark",colors:["#000000","#f0f0f0","#ffffff","#ff5900","#ff801f","#ffa057"],fonts:{heading:"domaine",body:"inter"},tags:["dark","developer-tools","minimal","elegant"]},sentry:{description:"Sentry's website is a dark-mode-first developer tool interface that speaks the language of code editors and terminal windows. The entire aesthetic is rooted in deep purple-black backgrounds (`#1f16...",category:"Developer Tools",theme:"dark",colors:["#1f1633","#150f23","#362d59","#6a5fc1","#79628c","#422082"],fonts:{heading:"Dammit Sans",body:"Rubik"},tags:["dark","developer-tools","bold"]},supabase:{description:"Supabase's website is a dark-mode-native developer platform that channels the aesthetic of a premium code editor \u2014 deep black backgrounds (`#0f0f0f`, `#171717`) with emerald green accents (`#3ecf8e...",category:"Developer Tools",theme:"dark",colors:["#3ecf8e","#00c573","#0f0f0f","#171717","#242424","#2e2e2e"],fonts:{heading:"DM Sans",body:"DM Sans"},tags:["dark","developer-tools","minimal","data-dense"]},superhuman:{description:"Superhuman's website feels like opening a luxury envelope \u2014 predominantly white, immaculately clean, with a single dramatic gesture of color that commands attention. The hero section is a cinematic...",category:"Developer Tools",theme:"light",colors:["#1b1938","#cbb7fb","#292827","#714cb6","#ffffff","#e9e5dd"],fonts:{heading:"Super Sans VF",body:"Super Sans VF"},tags:["light","developer-tools","minimal","bold","elegant","data-dense","gradient"]},vercel:{description:"Vercel's website is the visual thesis of developer infrastructure made invisible \u2014 a design system so restrained it borders on philosophical. The page is overwhelmingly white (`#ffffff`) with near-...",category:"Developer Tools",theme:"light",colors:["#171717","#ffffff","#000000","#ff5b4f","#de1d8d","#0a72ef"],fonts:{heading:"Geist",body:"Geist"},tags:["light","developer-tools","minimal"]},warp:{description:"Warp's website feels like sitting at a campfire in a deep forest \u2014 warm, dark, and alive with quiet confidence. Unlike the cold, blue-tinted blacks favored by most developer tools, Warp wraps every...",category:"Developer Tools",theme:"light",colors:["#faf9f6","#353534","#868584","#afaeac","#666469","#454545"],fonts:{heading:"Matter Regular",body:"Matter Regular"},tags:["light","developer-tools","minimal","bold"]},zapier:{description:"Zapier's website radiates warm, approachable professionalism. It rejects the cold monochrome minimalism of developer tools in favor of a cream-tinted canvas (`#fffefb`) that feels like unbleached p...",category:"Developer Tools",theme:"light",colors:["#201515","#fffefb","#fffdf9","#ff4f00","#36342e","#939084"],fonts:{heading:"Degular Display",body:"Inter"},tags:["light","developer-tools","minimal","bold","enterprise","modern","elegant"]},clickhouse:{description:`ClickHouse's interface is a high-performance cockpit rendered in acid yellow-green on obsidian black \u2014 a design that screams "speed" before you read a single word. The entire experience lives in da...`,category:"Infrastructure",theme:"dark",colors:["#faff69","#166534","#14572f","#f4f692","#4f5100","#161600"],fonts:{heading:"Inter",body:"Basier"},tags:["dark","infrastructure"]},composio:{description:"Composio's interface is a nocturnal command center \u2014 a dense, developer-focused darkness punctuated by electric cyan and deep cobalt signals. The entire experience is built on an almost-pure-black ...",category:"Infrastructure",theme:"dark",colors:["#0007cd","#00ffff","#0089ff","#0096ff","#0f0f0f","#000000"],fonts:{heading:"abcDiatype",body:"Inter"},tags:["dark","infrastructure","minimal","data-dense","gradient"]},hashicorp:{description:"HashiCorp's website is enterprise infrastructure made tangible \u2014 a design system that must communicate the complexity of cloud infrastructure management while remaining approachable. The visual lan...",category:"Infrastructure",theme:"dark",colors:["#000000","#15181e","#0d0e12","#f1f2f3","#d5d7db","#b2b6bd"],fonts:{heading:"__hashicorpSans_96f0ca",body:"system-ui"},tags:["dark","infrastructure","minimal","bold","enterprise","data-dense"]},mongodb:{description:"MongoDB's website is a deep-forest-meets-terminal experience \u2014 a design system rooted in the darkest teal-black (`#001e2b`) that evokes both the density of a database and the depth of a forest cano...",category:"Infrastructure",theme:"dark",colors:["#001e2b","#00ed64","#00684a","#006cfa","#3860be","#1eaedb"],fonts:{heading:"MongoDB Value Serif",body:"Euclid Circular A"},tags:["dark","infrastructure","bold","enterprise"]},sanity:{description:"Sanity's website is a developer-content platform rendered as a nocturnal command center -- dark, precise, and deeply structured. The entire experience sits on a near-black canvas (`#0b0b0b`) that r...",category:"Infrastructure",theme:"dark",colors:["#0b0b0b","#000000","#f36458","#0052ef","#55beff","#afe3ff"],fonts:{heading:"waldenburgNormal",body:"waldenburgNormal"},tags:["dark","infrastructure","playful"]},stripe:{description:"Stripe's website is the gold standard of fintech design -- a system that manages to feel simultaneously technical and luxurious, precise and warm. The page opens on a clean white canvas (`#ffffff`)...",category:"Infrastructure",theme:"light",colors:["#533afd","#061b31","#ffffff","#1c1e54","#0d253d","#ea2261"],fonts:{heading:"Inter",body:"Inter"},tags:["light","infrastructure","minimal","bold","enterprise","modern","data-dense","gradient"]},airtable:{description:'Airtable\'s website is a clean, enterprise-friendly platform that communicates "sophisticated simplicity" through a white canvas with deep navy text (`#181d26`) and Airtable Blue (`#1b61c9`) as the ...',category:"Design & Productivity",theme:"light",colors:["#181d26","#1b61c9","#ffffff","#006400","#333333","#254fad"],fonts:{heading:"Haas",body:"Haas"},tags:["light","design-productivity","minimal","playful","enterprise"]},cal:{description:"Cal.com's website is a masterclass in monochromatic restraint \u2014 a grayscale world where boldness comes not from color but from the sheer confidence of black text on white space. Inspired by Uber's ...",category:"Design & Productivity",theme:"light",colors:["#242424","#111111","#ffffff","#0099ff","#3b82f6","#0000ee"],fonts:{heading:"Cal Sans",body:"Inter"},tags:["light","design-productivity","minimal","bold","modern","data-dense"]},clay:{description:"Clay's website is a warm, playful celebration of color that treats B2B data enrichment like a craft rather than an enterprise chore. The design language is built on a foundation of warm cream backg...",category:"Design & Productivity",theme:"light",colors:["#000000","#ffffff","#faf9f7","#84e7a5","#078a52","#02492a"],fonts:{heading:"DM Sans",body:"DM Sans"},tags:["light","design-productivity","playful","enterprise"]},figma:{description:"Figma's interface is the design tool that designed itself \u2014 a masterclass in typographic sophistication where a custom variable font (figmaSans) modulates between razor-thin (weight 320) and bold (...",category:"Design & Productivity",theme:"light",colors:["#000000","#ffffff"],fonts:{heading:"figmaSans",body:"Inter"},tags:["light","design-productivity","bold","gradient"]},framer:{description:"Framer's website is a cinematic, tool-obsessed dark canvas that radiates the confidence of a design tool built by designers who worship craft. The entire experience is drenched in pure black \u2014 not ...",category:"Design & Productivity",theme:"dark",colors:["#000000","#ffffff","#0099ff","#a6a6a6","#090909","#0000ee"],fonts:{heading:"GT Walsheim Framer Medium",body:"Inter Variable"},tags:["dark","design-productivity"]},intercom:{description:'Intercom\'s website is a warm, confident customer service platform that communicates "AI-first helpdesk" through a clean, editorial design language. The page operates on a warm off-white canvas (`#f...',category:"Design & Productivity",theme:"light",colors:["#111111","#ffffff","#faf9f6","#ff5600","#fe4c02","#65b5ff"],fonts:{heading:"Saans",body:"MediumLL"},tags:["light","design-productivity","minimal"]},miro:{description:`Miro's website is a clean, collaborative-tool-forward platform that communicates "visual thinking" through generous whitespace, pastel accent colors, and a confident geometric font. The design uses...`,category:"Design & Productivity",theme:"light",colors:["#1c1c1e","#ffffff","#5b76fe","#2a41b6","#ffc6c6","#600000"],fonts:{heading:"Roobert PRO Medium",body:"Noto Sans"},tags:["light","design-productivity","minimal","modern"]},notion:{description:"Notion's website embodies the philosophy of the tool itself: a blank canvas that gets out of your way. The design system is built on warm neutrals rather than cold grays, creating a distinctly appr...",category:"Design & Productivity",theme:"light",colors:["#ffffff","#0075de","#213183","#005bab","#f6f5f4","#31302e"],fonts:{heading:"NotionInter",body:"NotionInter"},tags:["light","design-productivity","minimal","bold"]},pinterest:{description:"Pinterest's website is a warm, inspiration-driven canvas that treats visual discovery like a lifestyle magazine. The design operates on a soft, slightly warm white background with Pinterest Red (`#...",category:"Design & Productivity",theme:"light",colors:["#e60023","#103c25","#0b2819","#211922","#000000","#62625b"],fonts:{heading:"Pin Sans",body:"Pin Sans"},tags:["light","design-productivity","bold"]},webflow:{description:'Webflow\'s website is a visually rich, tool-forward platform that communicates "design without code" through clean white surfaces, the signature Webflow Blue (`#146ef5`), and a rich secondary color ...',category:"Design & Productivity",theme:"light",colors:["#080808","#146ef5","#3b89ff","#006acc","#0055d4","#7a3dff"],fonts:{heading:"Inter",body:"20px"},tags:["light","design-productivity","minimal"]},coinbase:{description:"Coinbase's website is a clean, trustworthy crypto platform that communicates financial reliability through a blue-and-white binary palette. The design uses Coinbase Blue (`#0052ff`) \u2014 a deep, satur...",category:"Fintech",theme:"light",colors:["#0052ff","#ffffff","#0a0b0d","#eef0f3","#578bfa","#0667d0"],fonts:{heading:"CoinbaseDisplay",body:"CoinbaseSans"},tags:["light","fintech","minimal","enterprise"]},kraken:{description:"Kraken's website is a clean, trustworthy crypto exchange that uses purple as its commanding brand color. The design operates on white backgrounds with Kraken Purple (`#7132f5`, `#5741d8`, `#5b1ecf`...",category:"Fintech",theme:"light",colors:["#7132f5","#5741d8","#5b1ecf","#101114","#686b82","#9497a9"],fonts:{heading:"Kraken-Brand",body:"Kraken-Product"},tags:["light","fintech","minimal","bold","enterprise"]},revolut:{description:`Revolut's website is fintech confidence distilled into pixels \u2014 a design system that communicates "your money is in capable hands" through massive typography, generous whitespace, and a disciplined...`,category:"Fintech",theme:"light",colors:["#191c1f","#ffffff","#f4f4f4","#494fdf","#4f55f1","#376cd5"],fonts:{heading:"Aeonik Pro",body:"Inter"},tags:["light","fintech","modern"]},wise:{description:`Wise's website is a bold, confident fintech platform that communicates "money without borders" through massive typography and a distinctive lime-green accent. The design operates on a warm off-whit...`,category:"Fintech",theme:"light",colors:["#0e0f0c","#9fe870","#163300","#e2f6d5","#cdffad","#054d28"],fonts:{heading:"Wise Sans",body:"Inter"},tags:["light","fintech","minimal","bold","data-dense"]},airbnb:{description:"Airbnb's website is a warm, photography-forward marketplace that feels like flipping through a travel magazine where every page invites you to book. The design operates on a foundation of pure whit...",category:"Enterprise & Consumer",theme:"light",colors:["#ff385c","#e00b41","#c13515","#b32505","#460479","#92174d"],fonts:{heading:"Nunito Sans",body:"Inter"},tags:["light","enterprise-consumer","minimal","bold"]},apple:{description:"Apple's website is a masterclass in controlled drama \u2014 vast expanses of pure black and near-white serve as cinematic backdrops for products that are photographed as if they were sculptures in a gal...",category:"Enterprise & Consumer",theme:"dark",colors:["#000000","#f5f5f7","#1d1d1f","#0071e3","#0066cc","#2997ff"],fonts:{heading:"Inter",body:"Inter"},tags:["dark","enterprise-consumer","minimal","gradient"]},bmw:{description:"BMW's website is automotive engineering made visual \u2014 a design system that communicates precision, performance, and German industrial confidence. The page alternates between deep dark hero sections...",category:"Enterprise & Consumer",theme:"light",colors:["#ffffff","#1c69d4","#0653b6","#262626","#757575","#bbbbbb"],fonts:{heading:"BMWTypeNextLatin Light",body:"BMWTypeNextLatin"},tags:["light","enterprise-consumer","minimal","elegant"]},ibm:{description:"IBM's website is the digital embodiment of enterprise authority built on the Carbon Design System \u2014 a design language so methodically structured it reads like an engineering specification rendered ...",category:"Enterprise & Consumer",theme:"dark",colors:["#0f62fe","#ffffff","#161616","#262626","#393939","#525252"],fonts:{heading:"IBM Plex Sans",body:"Inter"},tags:["dark","enterprise-consumer","minimal","playful","enterprise"]},nvidia:{description:"NVIDIA's website is a high-contrast, technology-forward experience that communicates raw computational power through design restraint. The page is built on a stark black (`#000000`) and white (`#ff...",category:"Enterprise & Consumer",theme:"dark",colors:["#76b900","#000000","#ffffff","#bff230","#df6500","#ef9100"],fonts:{heading:"NVIDIA-EMEA",body:"NVIDIA-EMEA"},tags:["dark","enterprise-consumer","minimal","bold","playful","data-dense"]},spacex:{description:"SpaceX's website is a full-screen cinematic experience that treats aerospace engineering like a film \u2014 every section is a scene, every photograph is a frame, and the interface disappears entirely b...",category:"Enterprise & Consumer",theme:"dark",colors:["#000000","#f0f0fa"],fonts:{heading:"DM Sans",body:"DM Sans"},tags:["dark","enterprise-consumer","minimal","bold"]},spotify:{description:"Spotify's web interface is a dark, immersive music player that wraps listeners in a near-black cocoon (`#121212`, `#181818`, `#1f1f1f`) where album art and content become the primary source of colo...",category:"Enterprise & Consumer",theme:"dark",colors:["#1ed760","#121212","#181818","#1f1f1f","#ffffff","#b3b3b3"],fonts:{heading:"Inter",body:"SpotifyMixUI"},tags:["dark","enterprise-consumer","bold"]},uber:{description:"Uber's design language is a masterclass in confident minimalism -- a black-and-white universe where every pixel serves a purpose and nothing decorates without earning its place. The entire experien...",category:"Enterprise & Consumer",theme:"dark",colors:["#000000","#ffffff","#e2e2e2","#f3f3f3","#efefef","#4b4b4b"],fonts:{heading:"Inter",body:"UberMoveText"},tags:["dark","enterprise-consumer","minimal","bold","playful","data-dense"]}};function It(r){return r.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}function fe(r){let e=r.toLowerCase().replace(/[^a-z0-9]/g,"");return At.find(t=>t.id===r||t.id.toLowerCase().replace(/[^a-z0-9]/g,"")===e||t.name.toLowerCase().replace(/[^a-z0-9]/g,"")===e)}function Ie(r){return ca[r]}function pa(r){return!r||r==="All"?At:At.filter(e=>e.category.toLowerCase()===r.toLowerCase())}function ut(r){return pa(r).map(t=>{let a=ca[t.id];return{id:t.id,slug:It(t.name),name:t.name,category:t.category,description:a?.description??"",theme:a?.theme??"light",colors:a?.colors??[],fonts:a?.fonts??{heading:"Inter",body:"Inter"},tags:a?.tags??[]}})}function Tr(r){let e=r??At,t={};for(let i of e)(t[i.category]??=[]).push(i);let a=[];for(let[i,n]of Object.entries(t)){a.push(`**${i}**`);for(let o of n){let s=ca[o.id];a.push(` - ${o.name} (${o.id}) \u2014 ${s?.theme??"light"}, ${s?.description?.slice(0,60)??""}`)}}return a.join(`
8798
- `)}function Pr(r,e){let t=fe(r);if(!t)return null;let a=Sr[e]??Sr.general;if(!a)return null;let i=t.sections.filter(n=>a.some(o=>n.title.toLowerCase().includes(o)));return i.length===0?null:i.map(n=>`## ${n.title}
8797
+ 8. Pair black CTAs with white secondaries for balanced dual-action layouts`}]}],ha={cohere:{description:"Cohere's interface is a polished enterprise command deck \u2014 confident, clean, and designed to make AI feel like serious infrastructure rather than a consumer toy. The experience lives on a bright wh...",category:"AI & Machine Learning",theme:"light",colors:["#000000","#212121","#17171c","#1863dc","#4c6ee6","#9b60aa"],fonts:{heading:"CohereText",body:"Unica77 Cohere Web"},tags:["light","ai-machine-learning","minimal","bold","enterprise","gradient"]},elevenlabs:{description:"ElevenLabs' website is a study in restrained elegance \u2014 a near-white canvas (`#ffffff`, `#f5f5f5`) where typography and subtle shadows do all the heavy lifting. The design feels like a premium audi...",category:"AI & Machine Learning",theme:"light",colors:["#ffffff","#f5f5f5","#f5f2ef","#000000","#4e4e4e","#777169"],fonts:{heading:"Waldenburg",body:"Inter"},tags:["light","ai-machine-learning","minimal","bold"]},minimax:{description:"MiniMax's website is a clean, product-showcase platform for a Chinese AI technology company that bridges consumer-friendly appeal with technical credibility. The design language is predominantly wh...",category:"AI & Machine Learning",theme:"light",colors:["#1456f0","#3daeff","#ea5ec1","#bfdbfe","#60a5fa","#3b82f6"],fonts:{heading:"DM Sans",body:"DM Sans"},tags:["light","ai-machine-learning","minimal","bold","playful","gradient"]},mistral:{description:"Mistral AI's interface is a sun-drenched landscape rendered in code \u2014 a warm, bold, unapologetically European design that trades the typical blue-screen AI aesthetic for golden amber, burnt orange,...",category:"AI & Machine Learning",theme:"light",colors:["#fa520f","#fb6424","#ff8105","#ff8a00","#ffa110","#ffb83e"],fonts:{heading:"Arial",body:"Inter"},tags:["light","ai-machine-learning","minimal","bold","elegant","gradient"]},ollama:{description:"Ollama's interface is radical minimalism taken to its logical conclusion \u2014 a pure-white void where content floats without decoration, shadow, or color. The design philosophy mirrors the product its...",category:"AI & Machine Learning",theme:"light",colors:["#000000","#262626","#090909","#ffffff","#fafafa","#e5e5e5"],fonts:{heading:"SF Pro Rounded",body:"ui-sans-serif"},tags:["light","ai-machine-learning","minimal","playful"]},opencode:{description:"OpenCode's website embodies a terminal-native, monospace-first aesthetic that reflects its identity as an open source AI coding agent. The entire visual system is built on a stark dark-on-light con...",category:"AI & Machine Learning",theme:"light",colors:["#201d1d","#fdfcfc","#9a9898","#302c2c","#646262","#f1eeee"],fonts:{heading:"Inter",body:"16px (1.00rem)"},tags:["light","ai-machine-learning","minimal","bold"]},replicate:{description:"Replicate's interface is a developer playground crackling with creative energy \u2014 a bold, high-contrast design that feels more like a music festival poster than a typical API platform. The hero sect...",category:"AI & Machine Learning",theme:"dark",colors:["#202020","#ea2804","#dd4425","#2b9a66","#24292e","#ffffff"],fonts:{heading:"rb-freigeist-neue",body:"basier-square"},tags:["dark","ai-machine-learning","minimal","bold","playful","gradient"]},runwayml:{description:"Runway's interface is a cinematic reel brought to life as a website \u2014 a dark, editorial, film-production-grade design where full-bleed photography and video ARE the primary UI elements. This is not...",category:"AI & Machine Learning",theme:"dark",colors:["#000000","#030303","#1a1a1a","#ffffff","#fefefe","#e9ecf2"],fonts:{heading:"Inter",body:"Inter"},tags:["dark","ai-machine-learning","minimal","bold"]},together:{description:"Together AI's interface is a pastel-gradient dreamscape built for enterprise AI infrastructure \u2014 a design that somehow makes GPU clusters and model inference feel light, airy, and optimistic. The h...",category:"AI & Machine Learning",theme:"light",colors:["#ef2cc1","#fc4c02","#010120","#bdbbff","#ffffff","#000000"],fonts:{heading:"The Future",body:"Inter"},tags:["light","ai-machine-learning","minimal","playful","enterprise","modern","elegant","data-dense","gradient"]},voltagent:{description:"VoltAgent's interface is a deep-space command terminal for the AI age \u2014 a developer-facing darkness built on near-pure-black surfaces (`#050507`) where the only interruption is the electric pulse o...",category:"AI & Machine Learning",theme:"dark",colors:["#00d992","#2fd6a1","#10b981","#818cf8","#306cce","#2554a0"],fonts:{heading:"system-ui",body:"Inter"},tags:["dark","ai-machine-learning","playful","data-dense"]},x:{description:"xAI's website is a masterclass in dark-first, monospace-driven brutalist minimalism -- a design system that feels like it was built by engineers who understand that restraint is the ultimate form o...",category:"AI & Machine Learning",theme:"dark",colors:["#ffffff","#1f2228","#000000"],fonts:{heading:"GeistMono",body:"universalSans"},tags:["dark","ai-machine-learning","minimal","elegant","data-dense","gradient"]},cursor:{description:"Cursor's website is a study in warm minimalism meets code-editor elegance. The entire experience is built on a warm off-white canvas (`#f2f1ed`) with dark warm-brown text (`#26251e`) -- not pure bl...",category:"Developer Tools",theme:"light",colors:["#26251e","#f2f1ed","#e6e5e0","#ffffff","#000000","#f54e00"],fonts:{heading:"CursorGothic",body:"jjannon"},tags:["light","developer-tools","minimal"]},expo:{description:"Expo's interface is a luminous, confidence-radiating developer platform built on the premise that tools for building apps should feel as polished as the apps themselves. The entire experience lives...",category:"Developer Tools",theme:"dark",colors:["#000000","#1c2024","#0d74ce","#476cff","#47c2ff","#8145b5"],fonts:{heading:"Inter",body:"Inter"},tags:["dark","developer-tools","playful"]},linear:{description:"Linear's website is a masterclass in dark-mode-first product design \u2014 a near-black canvas (`#08090a`) where content emerges from darkness like starlight. The overall impression is one of extreme pr...",category:"Developer Tools",theme:"dark",colors:["#010102","#08090a","#0f1011","#191a1b","#28282c","#f7f8f8"],fonts:{heading:"Inter Variable",body:"Inter Variable"},tags:["dark","developer-tools","minimal","bold"]},lovable:{description:"Lovable's website radiates warmth through restraint. The entire page sits on a creamy, parchment-toned background (`#f7f4ed`) that immediately separates it from the cold-white conventions of most d...",category:"Developer Tools",theme:"light",colors:["#f7f4ed","#1c1c1c","#fcfbf8","#5f5f5d","#eceae4","#3b82f6"],fonts:{heading:"Camera Plain Variable",body:"Camera Plain Variable"},tags:["light","developer-tools","minimal"]},mintlify:{description:"Mintlify's website is a study in documentation-as-product design \u2014 a white, airy, information-rich surface that treats clarity as its highest aesthetic value. The page opens with a luminous white (...",category:"Developer Tools",theme:"light",colors:["#0d0d0d","#ffffff","#18e299","#d4fae8","#0fa76e","#c37d0d"],fonts:{heading:"Inter",body:"Inter"},tags:["light","developer-tools","minimal","bold","gradient"]},posthog:{description:"PostHog's website feels like a startup's internal wiki that escaped into the wild \u2014 warm, irreverent, and deliberately anti-corporate. The background isn't the expected crisp white or dark void of ...",category:"Developer Tools",theme:"light",colors:["#4d4f46","#23251d","#f54e00","#f7a501","#b17816","#3b82f6"],fonts:{heading:"IBM Plex Sans Variable",body:"IBM Plex Sans Variable"},tags:["light","developer-tools","bold","playful","modern","gradient"]},raycast:{description:"Raycast's marketing site feels like the dark interior of a precision instrument \u2014 a Swiss watch case carved from obsidian. The background isn't just dark, it's an almost-black blue-tint (`#07080a`)...",category:"Developer Tools",theme:"dark",colors:["#07080a","#ffffff","#ff6363","#55b3ff","#5fc992","#ffbc33"],fonts:{heading:"Inter",body:"16px"},tags:["dark","developer-tools","minimal","data-dense","gradient"]},resend:{description:"Resend's website is a dark, cinematic canvas that treats email infrastructure like a luxury product. The entire page is draped in pure black (`#000000`) with text that glows in near-white (`#f0f0f0...",category:"Developer Tools",theme:"dark",colors:["#000000","#f0f0f0","#ffffff","#ff5900","#ff801f","#ffa057"],fonts:{heading:"domaine",body:"inter"},tags:["dark","developer-tools","minimal","elegant"]},sentry:{description:"Sentry's website is a dark-mode-first developer tool interface that speaks the language of code editors and terminal windows. The entire aesthetic is rooted in deep purple-black backgrounds (`#1f16...",category:"Developer Tools",theme:"dark",colors:["#1f1633","#150f23","#362d59","#6a5fc1","#79628c","#422082"],fonts:{heading:"Dammit Sans",body:"Rubik"},tags:["dark","developer-tools","bold"]},supabase:{description:"Supabase's website is a dark-mode-native developer platform that channels the aesthetic of a premium code editor \u2014 deep black backgrounds (`#0f0f0f`, `#171717`) with emerald green accents (`#3ecf8e...",category:"Developer Tools",theme:"dark",colors:["#3ecf8e","#00c573","#0f0f0f","#171717","#242424","#2e2e2e"],fonts:{heading:"DM Sans",body:"DM Sans"},tags:["dark","developer-tools","minimal","data-dense"]},superhuman:{description:"Superhuman's website feels like opening a luxury envelope \u2014 predominantly white, immaculately clean, with a single dramatic gesture of color that commands attention. The hero section is a cinematic...",category:"Developer Tools",theme:"light",colors:["#1b1938","#cbb7fb","#292827","#714cb6","#ffffff","#e9e5dd"],fonts:{heading:"Super Sans VF",body:"Super Sans VF"},tags:["light","developer-tools","minimal","bold","elegant","data-dense","gradient"]},vercel:{description:"Vercel's website is the visual thesis of developer infrastructure made invisible \u2014 a design system so restrained it borders on philosophical. The page is overwhelmingly white (`#ffffff`) with near-...",category:"Developer Tools",theme:"light",colors:["#171717","#ffffff","#000000","#ff5b4f","#de1d8d","#0a72ef"],fonts:{heading:"Geist",body:"Geist"},tags:["light","developer-tools","minimal"]},warp:{description:"Warp's website feels like sitting at a campfire in a deep forest \u2014 warm, dark, and alive with quiet confidence. Unlike the cold, blue-tinted blacks favored by most developer tools, Warp wraps every...",category:"Developer Tools",theme:"light",colors:["#faf9f6","#353534","#868584","#afaeac","#666469","#454545"],fonts:{heading:"Matter Regular",body:"Matter Regular"},tags:["light","developer-tools","minimal","bold"]},zapier:{description:"Zapier's website radiates warm, approachable professionalism. It rejects the cold monochrome minimalism of developer tools in favor of a cream-tinted canvas (`#fffefb`) that feels like unbleached p...",category:"Developer Tools",theme:"light",colors:["#201515","#fffefb","#fffdf9","#ff4f00","#36342e","#939084"],fonts:{heading:"Degular Display",body:"Inter"},tags:["light","developer-tools","minimal","bold","enterprise","modern","elegant"]},clickhouse:{description:`ClickHouse's interface is a high-performance cockpit rendered in acid yellow-green on obsidian black \u2014 a design that screams "speed" before you read a single word. The entire experience lives in da...`,category:"Infrastructure",theme:"dark",colors:["#faff69","#166534","#14572f","#f4f692","#4f5100","#161600"],fonts:{heading:"Inter",body:"Basier"},tags:["dark","infrastructure"]},composio:{description:"Composio's interface is a nocturnal command center \u2014 a dense, developer-focused darkness punctuated by electric cyan and deep cobalt signals. The entire experience is built on an almost-pure-black ...",category:"Infrastructure",theme:"dark",colors:["#0007cd","#00ffff","#0089ff","#0096ff","#0f0f0f","#000000"],fonts:{heading:"abcDiatype",body:"Inter"},tags:["dark","infrastructure","minimal","data-dense","gradient"]},hashicorp:{description:"HashiCorp's website is enterprise infrastructure made tangible \u2014 a design system that must communicate the complexity of cloud infrastructure management while remaining approachable. The visual lan...",category:"Infrastructure",theme:"dark",colors:["#000000","#15181e","#0d0e12","#f1f2f3","#d5d7db","#b2b6bd"],fonts:{heading:"__hashicorpSans_96f0ca",body:"system-ui"},tags:["dark","infrastructure","minimal","bold","enterprise","data-dense"]},mongodb:{description:"MongoDB's website is a deep-forest-meets-terminal experience \u2014 a design system rooted in the darkest teal-black (`#001e2b`) that evokes both the density of a database and the depth of a forest cano...",category:"Infrastructure",theme:"dark",colors:["#001e2b","#00ed64","#00684a","#006cfa","#3860be","#1eaedb"],fonts:{heading:"MongoDB Value Serif",body:"Euclid Circular A"},tags:["dark","infrastructure","bold","enterprise"]},sanity:{description:"Sanity's website is a developer-content platform rendered as a nocturnal command center -- dark, precise, and deeply structured. The entire experience sits on a near-black canvas (`#0b0b0b`) that r...",category:"Infrastructure",theme:"dark",colors:["#0b0b0b","#000000","#f36458","#0052ef","#55beff","#afe3ff"],fonts:{heading:"waldenburgNormal",body:"waldenburgNormal"},tags:["dark","infrastructure","playful"]},stripe:{description:"Stripe's website is the gold standard of fintech design -- a system that manages to feel simultaneously technical and luxurious, precise and warm. The page opens on a clean white canvas (`#ffffff`)...",category:"Infrastructure",theme:"light",colors:["#533afd","#061b31","#ffffff","#1c1e54","#0d253d","#ea2261"],fonts:{heading:"Inter",body:"Inter"},tags:["light","infrastructure","minimal","bold","enterprise","modern","data-dense","gradient"]},airtable:{description:'Airtable\'s website is a clean, enterprise-friendly platform that communicates "sophisticated simplicity" through a white canvas with deep navy text (`#181d26`) and Airtable Blue (`#1b61c9`) as the ...',category:"Design & Productivity",theme:"light",colors:["#181d26","#1b61c9","#ffffff","#006400","#333333","#254fad"],fonts:{heading:"Haas",body:"Haas"},tags:["light","design-productivity","minimal","playful","enterprise"]},cal:{description:"Cal.com's website is a masterclass in monochromatic restraint \u2014 a grayscale world where boldness comes not from color but from the sheer confidence of black text on white space. Inspired by Uber's ...",category:"Design & Productivity",theme:"light",colors:["#242424","#111111","#ffffff","#0099ff","#3b82f6","#0000ee"],fonts:{heading:"Cal Sans",body:"Inter"},tags:["light","design-productivity","minimal","bold","modern","data-dense"]},clay:{description:"Clay's website is a warm, playful celebration of color that treats B2B data enrichment like a craft rather than an enterprise chore. The design language is built on a foundation of warm cream backg...",category:"Design & Productivity",theme:"light",colors:["#000000","#ffffff","#faf9f7","#84e7a5","#078a52","#02492a"],fonts:{heading:"DM Sans",body:"DM Sans"},tags:["light","design-productivity","playful","enterprise"]},figma:{description:"Figma's interface is the design tool that designed itself \u2014 a masterclass in typographic sophistication where a custom variable font (figmaSans) modulates between razor-thin (weight 320) and bold (...",category:"Design & Productivity",theme:"light",colors:["#000000","#ffffff"],fonts:{heading:"figmaSans",body:"Inter"},tags:["light","design-productivity","bold","gradient"]},framer:{description:"Framer's website is a cinematic, tool-obsessed dark canvas that radiates the confidence of a design tool built by designers who worship craft. The entire experience is drenched in pure black \u2014 not ...",category:"Design & Productivity",theme:"dark",colors:["#000000","#ffffff","#0099ff","#a6a6a6","#090909","#0000ee"],fonts:{heading:"GT Walsheim Framer Medium",body:"Inter Variable"},tags:["dark","design-productivity"]},intercom:{description:'Intercom\'s website is a warm, confident customer service platform that communicates "AI-first helpdesk" through a clean, editorial design language. The page operates on a warm off-white canvas (`#f...',category:"Design & Productivity",theme:"light",colors:["#111111","#ffffff","#faf9f6","#ff5600","#fe4c02","#65b5ff"],fonts:{heading:"Saans",body:"MediumLL"},tags:["light","design-productivity","minimal"]},miro:{description:`Miro's website is a clean, collaborative-tool-forward platform that communicates "visual thinking" through generous whitespace, pastel accent colors, and a confident geometric font. The design uses...`,category:"Design & Productivity",theme:"light",colors:["#1c1c1e","#ffffff","#5b76fe","#2a41b6","#ffc6c6","#600000"],fonts:{heading:"Roobert PRO Medium",body:"Noto Sans"},tags:["light","design-productivity","minimal","modern"]},notion:{description:"Notion's website embodies the philosophy of the tool itself: a blank canvas that gets out of your way. The design system is built on warm neutrals rather than cold grays, creating a distinctly appr...",category:"Design & Productivity",theme:"light",colors:["#ffffff","#0075de","#213183","#005bab","#f6f5f4","#31302e"],fonts:{heading:"NotionInter",body:"NotionInter"},tags:["light","design-productivity","minimal","bold"]},pinterest:{description:"Pinterest's website is a warm, inspiration-driven canvas that treats visual discovery like a lifestyle magazine. The design operates on a soft, slightly warm white background with Pinterest Red (`#...",category:"Design & Productivity",theme:"light",colors:["#e60023","#103c25","#0b2819","#211922","#000000","#62625b"],fonts:{heading:"Pin Sans",body:"Pin Sans"},tags:["light","design-productivity","bold"]},webflow:{description:'Webflow\'s website is a visually rich, tool-forward platform that communicates "design without code" through clean white surfaces, the signature Webflow Blue (`#146ef5`), and a rich secondary color ...',category:"Design & Productivity",theme:"light",colors:["#080808","#146ef5","#3b89ff","#006acc","#0055d4","#7a3dff"],fonts:{heading:"Inter",body:"20px"},tags:["light","design-productivity","minimal"]},coinbase:{description:"Coinbase's website is a clean, trustworthy crypto platform that communicates financial reliability through a blue-and-white binary palette. The design uses Coinbase Blue (`#0052ff`) \u2014 a deep, satur...",category:"Fintech",theme:"light",colors:["#0052ff","#ffffff","#0a0b0d","#eef0f3","#578bfa","#0667d0"],fonts:{heading:"CoinbaseDisplay",body:"CoinbaseSans"},tags:["light","fintech","minimal","enterprise"]},kraken:{description:"Kraken's website is a clean, trustworthy crypto exchange that uses purple as its commanding brand color. The design operates on white backgrounds with Kraken Purple (`#7132f5`, `#5741d8`, `#5b1ecf`...",category:"Fintech",theme:"light",colors:["#7132f5","#5741d8","#5b1ecf","#101114","#686b82","#9497a9"],fonts:{heading:"Kraken-Brand",body:"Kraken-Product"},tags:["light","fintech","minimal","bold","enterprise"]},revolut:{description:`Revolut's website is fintech confidence distilled into pixels \u2014 a design system that communicates "your money is in capable hands" through massive typography, generous whitespace, and a disciplined...`,category:"Fintech",theme:"light",colors:["#191c1f","#ffffff","#f4f4f4","#494fdf","#4f55f1","#376cd5"],fonts:{heading:"Aeonik Pro",body:"Inter"},tags:["light","fintech","modern"]},wise:{description:`Wise's website is a bold, confident fintech platform that communicates "money without borders" through massive typography and a distinctive lime-green accent. The design operates on a warm off-whit...`,category:"Fintech",theme:"light",colors:["#0e0f0c","#9fe870","#163300","#e2f6d5","#cdffad","#054d28"],fonts:{heading:"Wise Sans",body:"Inter"},tags:["light","fintech","minimal","bold","data-dense"]},airbnb:{description:"Airbnb's website is a warm, photography-forward marketplace that feels like flipping through a travel magazine where every page invites you to book. The design operates on a foundation of pure whit...",category:"Enterprise & Consumer",theme:"light",colors:["#ff385c","#e00b41","#c13515","#b32505","#460479","#92174d"],fonts:{heading:"Nunito Sans",body:"Inter"},tags:["light","enterprise-consumer","minimal","bold"]},apple:{description:"Apple's website is a masterclass in controlled drama \u2014 vast expanses of pure black and near-white serve as cinematic backdrops for products that are photographed as if they were sculptures in a gal...",category:"Enterprise & Consumer",theme:"dark",colors:["#000000","#f5f5f7","#1d1d1f","#0071e3","#0066cc","#2997ff"],fonts:{heading:"Inter",body:"Inter"},tags:["dark","enterprise-consumer","minimal","gradient"]},bmw:{description:"BMW's website is automotive engineering made visual \u2014 a design system that communicates precision, performance, and German industrial confidence. The page alternates between deep dark hero sections...",category:"Enterprise & Consumer",theme:"light",colors:["#ffffff","#1c69d4","#0653b6","#262626","#757575","#bbbbbb"],fonts:{heading:"BMWTypeNextLatin Light",body:"BMWTypeNextLatin"},tags:["light","enterprise-consumer","minimal","elegant"]},ibm:{description:"IBM's website is the digital embodiment of enterprise authority built on the Carbon Design System \u2014 a design language so methodically structured it reads like an engineering specification rendered ...",category:"Enterprise & Consumer",theme:"dark",colors:["#0f62fe","#ffffff","#161616","#262626","#393939","#525252"],fonts:{heading:"IBM Plex Sans",body:"Inter"},tags:["dark","enterprise-consumer","minimal","playful","enterprise"]},nvidia:{description:"NVIDIA's website is a high-contrast, technology-forward experience that communicates raw computational power through design restraint. The page is built on a stark black (`#000000`) and white (`#ff...",category:"Enterprise & Consumer",theme:"dark",colors:["#76b900","#000000","#ffffff","#bff230","#df6500","#ef9100"],fonts:{heading:"NVIDIA-EMEA",body:"NVIDIA-EMEA"},tags:["dark","enterprise-consumer","minimal","bold","playful","data-dense"]},spacex:{description:"SpaceX's website is a full-screen cinematic experience that treats aerospace engineering like a film \u2014 every section is a scene, every photograph is a frame, and the interface disappears entirely b...",category:"Enterprise & Consumer",theme:"dark",colors:["#000000","#f0f0fa"],fonts:{heading:"DM Sans",body:"DM Sans"},tags:["dark","enterprise-consumer","minimal","bold"]},spotify:{description:"Spotify's web interface is a dark, immersive music player that wraps listeners in a near-black cocoon (`#121212`, `#181818`, `#1f1f1f`) where album art and content become the primary source of colo...",category:"Enterprise & Consumer",theme:"dark",colors:["#1ed760","#121212","#181818","#1f1f1f","#ffffff","#b3b3b3"],fonts:{heading:"Inter",body:"SpotifyMixUI"},tags:["dark","enterprise-consumer","bold"]},uber:{description:"Uber's design language is a masterclass in confident minimalism -- a black-and-white universe where every pixel serves a purpose and nothing decorates without earning its place. The entire experien...",category:"Enterprise & Consumer",theme:"dark",colors:["#000000","#ffffff","#e2e2e2","#f3f3f3","#efefef","#4b4b4b"],fonts:{heading:"Inter",body:"UberMoveText"},tags:["dark","enterprise-consumer","minimal","bold","playful","data-dense"]}};function Dt(r){return r.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}function be(r){let e=r.toLowerCase().replace(/[^a-z0-9]/g,"");return At.find(a=>a.id===r||a.id.toLowerCase().replace(/[^a-z0-9]/g,"")===e||a.name.toLowerCase().replace(/[^a-z0-9]/g,"")===e)}function De(r){return ha[r]}function ga(r){return!r||r==="All"?At:At.filter(e=>e.category.toLowerCase()===r.toLowerCase())}function dt(r){return ga(r).map(a=>{let t=ha[a.id];return{id:a.id,slug:Dt(a.name),name:a.name,category:a.category,description:t?.description??"",theme:t?.theme??"light",colors:t?.colors??[],fonts:t?.fonts??{heading:"Inter",body:"Inter"},tags:t?.tags??[]}})}function Br(r){let e=r??At,a={};for(let i of e)(a[i.category]??=[]).push(i);let t=[];for(let[i,s]of Object.entries(a)){t.push(`**${i}**`);for(let o of s){let n=ha[o.id];t.push(` - ${o.name} (${o.id}) \u2014 ${n?.theme??"light"}, ${n?.description?.slice(0,60)??""}`)}}return t.join(`
8798
+ `)}function Ar(r,e){let a=be(r);if(!a)return null;let t=Cr[e]??Cr.general;if(!t)return null;let i=a.sections.filter(s=>t.some(o=>s.title.toLowerCase().includes(o)));return i.length===0?null:i.map(s=>`## ${s.title}
8799
8799
 
8800
- ${n.content}`).join(`
8800
+ ${s.content}`).join(`
8801
8801
 
8802
8802
  ---
8803
8803
 
8804
- `)}function Br(r,e){let t=e?.maxResults??3,a=r.toLowerCase(),i=[[/\bcohere\b/i,"cohere"],[/\belevenlabs\b/i,"elevenlabs"],[/\bminimax\b/i,"minimax"],[/\bmistral ai\b/i,"mistral"],[/\bollama\b/i,"ollama"],[/\bopencode ai\b/i,"opencode"],[/\breplicate\b/i,"replicate"],[/\brunwayml\b/i,"runwayml"],[/\btogether ai\b/i,"together"],[/\bvoltagent\b/i,"voltagent"],[/\bxai\b/i,"x"],[/\bcursor\b/i,"cursor"],[/\bexpo\b/i,"expo"],[/\blinear\b/i,"linear"],[/\blovable\b/i,"lovable"],[/\bmintlify\b/i,"mintlify"],[/\bposthog\b/i,"posthog"],[/\braycast\b/i,"raycast"],[/\bresend\b/i,"resend"],[/\bsentry\b/i,"sentry"],[/\bsupabase\b/i,"supabase"],[/\bsuperhuman\b/i,"superhuman"],[/\bvercel\b/i,"vercel"],[/\bwarp\b/i,"warp"],[/\bzapier\b/i,"zapier"],[/\bclickhouse\b/i,"clickhouse"],[/\bcomposio\b/i,"composio"],[/\bhashicorp\b/i,"hashicorp"],[/\bmongodb\b/i,"mongodb"],[/\bsanity\b/i,"sanity"],[/\bstripe\b/i,"stripe"],[/\bairtable\b/i,"airtable"],[/\bcal\.com\b/i,"cal"],[/\bclay\b/i,"clay"],[/\bfigma\b/i,"figma"],[/\bframer\b/i,"framer"],[/\bintercom\b/i,"intercom"],[/\bmiro\b/i,"miro"],[/\bnotion\b/i,"notion"],[/\bpinterest\b/i,"pinterest"],[/\bwebflow\b/i,"webflow"],[/\bcoinbase\b/i,"coinbase"],[/\bkraken\b/i,"kraken"],[/\brevolut\b/i,"revolut"],[/\bwise\b/i,"wise"],[/\bairbnb\b/i,"airbnb"],[/\bapple\b/i,"apple"],[/\bbmw\b/i,"bmw"],[/\bibm\b/i,"ibm"],[/\bnvidia\b/i,"nvidia"],[/\bspacex\b/i,"spacex"],[/\bspotify\b/i,"spotify"],[/\buber\b/i,"uber"]],n=[[/\bdashboard|analytics|saas|admin\b/i,["linear","vercel","posthog","supabase","sentry"],3],[/\bai|machine.?learning|llm|chatbot\b/i,["mistral","opencode","cohere","replicate"],3],[/\bfintech|payment|banking|wallet\b/i,["stripe","revolut","wise","coinbase"],3],[/\bhabit|fitness|gym|health|wellness|recipe|cook|meal|journal|diary|mood|meditation|yoga|workout\b/i,["airbnb","spotify","pinterest","cal","notion"],3],[/\bbooking|reserv|appointment|salon|clinic|dental|barber|schedule\b/i,["cal","airbnb","stripe","notion"],3],[/\bminimal|clean|simple\b/i,["notion","cal","resend","airbnb"],2],[/\bbold|cinematic|dramatic\b/i,["spacex","bmw","nvidia","apple"],2],[/\bcreative|design|portfolio\b/i,["figma","framer","webflow","pinterest"],2],[/\bproductivity|workspace|docs|notes\b/i,["notion","airtable","miro","superhuman"],2],[/\be-?commerce|marketplace|shop|store\b/i,["airbnb","spotify","uber","stripe"],2],[/\bdev.?tool|developer|api|cli\b/i,["vercel","linear","supabase","raycast","warp"],2],[/\bmusic|media|streaming\b/i,["spotify","elevenlabs","runwayml"],2],[/\bdark.?mode|dark.?theme\b/i,["linear","cursor","warp","raycast","spacex"],1],[/\blight|bright|white\b/i,["notion","airbnb","stripe","cal"],1]],o={};for(let[h,c]of i)h.test(a)&&(o[c]=(o[c]??0)+10);for(let[h,c,g]of n)if(h.test(a))for(let b of c)o[b]=(o[b]??0)+g;let l=ut().map(h=>({...h,score:o[h.id]??0})).sort((h,c)=>c.score+Math.random()*.5-(h.score+Math.random()*.5)),d=3,u=l.filter(h=>h.score>=d);return u.length===0?[]:u.slice(0,t)}var ua={"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"},"r2-storage":{description:"File uploads with drag-and-drop UI, stored in Mistflow Cloud.",tags:["storage","upload","file","image","media","attachment","avatar"],envVars:[],docsUrl:"https://developers.cloudflare.com/r2/",packages:[],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"},"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"}},tt=[{id:"resend-email",name:"Resend Email",category:"communication",prompt:`## Resend Email Integration
8804
+ `)}function Dr(r,e){let a=e?.maxResults??3,t=r.toLowerCase(),i=[[/\bcohere\b/i,"cohere"],[/\belevenlabs\b/i,"elevenlabs"],[/\bminimax\b/i,"minimax"],[/\bmistral ai\b/i,"mistral"],[/\bollama\b/i,"ollama"],[/\bopencode ai\b/i,"opencode"],[/\breplicate\b/i,"replicate"],[/\brunwayml\b/i,"runwayml"],[/\btogether ai\b/i,"together"],[/\bvoltagent\b/i,"voltagent"],[/\bxai\b/i,"x"],[/\bcursor\b/i,"cursor"],[/\bexpo\b/i,"expo"],[/\blinear\b/i,"linear"],[/\blovable\b/i,"lovable"],[/\bmintlify\b/i,"mintlify"],[/\bposthog\b/i,"posthog"],[/\braycast\b/i,"raycast"],[/\bresend\b/i,"resend"],[/\bsentry\b/i,"sentry"],[/\bsupabase\b/i,"supabase"],[/\bsuperhuman\b/i,"superhuman"],[/\bvercel\b/i,"vercel"],[/\bwarp\b/i,"warp"],[/\bzapier\b/i,"zapier"],[/\bclickhouse\b/i,"clickhouse"],[/\bcomposio\b/i,"composio"],[/\bhashicorp\b/i,"hashicorp"],[/\bmongodb\b/i,"mongodb"],[/\bsanity\b/i,"sanity"],[/\bstripe\b/i,"stripe"],[/\bairtable\b/i,"airtable"],[/\bcal\.com\b/i,"cal"],[/\bclay\b/i,"clay"],[/\bfigma\b/i,"figma"],[/\bframer\b/i,"framer"],[/\bintercom\b/i,"intercom"],[/\bmiro\b/i,"miro"],[/\bnotion\b/i,"notion"],[/\bpinterest\b/i,"pinterest"],[/\bwebflow\b/i,"webflow"],[/\bcoinbase\b/i,"coinbase"],[/\bkraken\b/i,"kraken"],[/\brevolut\b/i,"revolut"],[/\bwise\b/i,"wise"],[/\bairbnb\b/i,"airbnb"],[/\bapple\b/i,"apple"],[/\bbmw\b/i,"bmw"],[/\bibm\b/i,"ibm"],[/\bnvidia\b/i,"nvidia"],[/\bspacex\b/i,"spacex"],[/\bspotify\b/i,"spotify"],[/\buber\b/i,"uber"]],s=[[/\bdashboard|analytics|saas|admin\b/i,["linear","vercel","posthog","supabase","sentry"],3],[/\bai|machine.?learning|llm|chatbot\b/i,["mistral","opencode","cohere","replicate"],3],[/\bfintech|payment|banking|wallet\b/i,["stripe","revolut","wise","coinbase"],3],[/\bhabit|fitness|gym|health|wellness|recipe|cook|meal|journal|diary|mood|meditation|yoga|workout\b/i,["airbnb","spotify","pinterest","cal","notion"],3],[/\bbooking|reserv|appointment|salon|clinic|dental|barber|schedule\b/i,["cal","airbnb","stripe","notion"],3],[/\bminimal|clean|simple\b/i,["notion","cal","resend","airbnb"],2],[/\bbold|cinematic|dramatic\b/i,["spacex","bmw","nvidia","apple"],2],[/\bcreative|design|portfolio\b/i,["figma","framer","webflow","pinterest"],2],[/\bproductivity|workspace|docs|notes\b/i,["notion","airtable","miro","superhuman"],2],[/\be-?commerce|marketplace|shop|store\b/i,["airbnb","spotify","uber","stripe"],2],[/\bdev.?tool|developer|api|cli\b/i,["vercel","linear","supabase","raycast","warp"],2],[/\bmusic|media|streaming\b/i,["spotify","elevenlabs","runwayml"],2],[/\bdark.?mode|dark.?theme\b/i,["linear","cursor","warp","raycast","spacex"],1],[/\blight|bright|white\b/i,["notion","airbnb","stripe","cal"],1]],o={};for(let[h,c]of i)h.test(t)&&(o[c]=(o[c]??0)+10);for(let[h,c,g]of s)if(h.test(t))for(let b of c)o[b]=(o[b]??0)+g;let l=dt().map(h=>({...h,score:o[h.id]??0})).sort((h,c)=>c.score+Math.random()*.5-(h.score+Math.random()*.5)),d=3,u=l.filter(h=>h.score>=d);return u.length===0?[]:u.slice(0,a)}var ma={"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"},"r2-storage":{description:"File uploads with drag-and-drop UI, stored in Mistflow Cloud.",tags:["storage","upload","file","image","media","attachment","avatar"],envVars:[],docsUrl:"https://developers.cloudflare.com/r2/",packages:[],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"},"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"}},Qe=[{id:"resend-email",name:"Resend Email",category:"communication",prompt:`## Resend Email Integration
8805
8805
 
8806
8806
  ### File Structure
8807
8807
  \`\`\`
@@ -10221,31 +10221,31 @@ export function ImageGenerator() {
10221
10221
  5. **Replicate charges per prediction.** Flux Schnell is ~$0.003/image. Flux Pro is ~$0.05/image. Video models are $0.05-$0.50/generation. Show generation cost to users.
10222
10222
  6. **Cache generated images.** Store output URLs in your database. Replicate URLs expire after a few hours. Download and re-host on R2 for permanent storage.
10223
10223
  7. **Network I/O does NOT count as CPU time on Workers.** Image generation wait time is all network I/O.
10224
- 8. **Never ask the user to paste REPLICATE_API_TOKEN in chat.** Direct them to set it in the Mistflow dashboard (Project Settings > Environment Variables).`}];function Ti(r){return r.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}function at(r){let e=tt.find(a=>a.id===r);if(e)return e;let t=r.toLowerCase().replace(/[^a-z0-9]/g,"");return tt.find(a=>{let i=a.name.toLowerCase().replace(/[^a-z0-9]/g,"");return i===t||i.includes(t)||t.includes(i)})}function rt(r){return ua[r]}function ha(r){return r?tt.filter(e=>e.category.toLowerCase()===r.toLowerCase()):tt}function Dr(r){let e=r??tt,t={};for(let i of e){t[i.category]||(t[i.category]=[]);let n=ua[i.id],o=n?` \u2014 ${n.description}`:"",s=n?.packages.length?` (${n.packages.join(", ")})`:"";t[i.category].push(`${i.id} \u2014 "${i.name}"${o}${s}`)}let a=[];for(let[i,n]of Object.entries(t))a.push(`**${i}**:
10225
- ${n.map(o=>` - ${o}`).join(`
10226
- `)}`);return a.join(`
10227
-
10228
- `)}function Ar(r){return(r?ha(r):tt).map(t=>{let a=ua[t.id];return{id:t.id,slug:Ti(t.name),name:t.name,category:t.category,description:a?.description??"",tags:a?.tags??[],envVars:a?.envVars??[],docsUrl:a?.docsUrl??"",packages:a?.packages??[],difficulty:a?.difficulty??"medium"}})}var Pi=[{name:"Dashboard",description:"Overview with key stats and today's activity",condition:r=>r.surfaceType==="internal-tool"||r.surfaceType==="customer-app",keywords:/\b(dashboard|overview|home.?page|stats)\b/i},{name:"Landing Page",description:"Public page explaining what this does",condition:r=>r.publicLanding===!0,keywords:/\b(landing|marketing|hero|homepage)\b/i},{name:"Scheduling / Booking",description:"Calendar, time slots, reservations",condition:r=>r.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:r=>r.multiRole===!0||r.primaryActor==="both",keywords:/\b(admin|panel|manage.?user|moderat)\b/i},{name:"User Profiles",description:"Account pages, settings, preferences",condition:r=>r.primaryActor==="customers"||r.primaryActor==="both",keywords:/\b(profile|account|settings|preferences)\b/i},{name:"Search / Browse",description:"Find and filter content or listings",condition:r=>r.surfaceType==="marketplace",keywords:/\b(search|browse|filter|discover|explore)\b/i},{name:"Email Notifications",description:"Welcome emails, alerts, reminders",condition:r=>r.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:r=>r.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:r=>r.integrations?.includes("ai")===!0,keywords:/\b(ai|chatbot|gpt|llm|generat|assistant)\b/i},{name:"Maps / Location",description:"Google Maps, location search, geolocation",condition:r=>r.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:r=>r.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:r=>r.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:r=>r.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:r=>r.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 Ir(r,e,t){let a=t?.suggestedName||Bi(r),i=e.primaryActor==="both"?"Staff + Customers":e.primaryActor==="staff"?"Staff / Admin":e.primaryActor==="customers"?"End Users":"Users",n=e.audienceType??(e.surfaceType==="internal-tool"?"internal":(e.primaryActor==="customers"||e.primaryActor==="both","b2c")),o=e.surfaceType==="internal-tool"?"Internal tool":e.surfaceType==="marketplace"?"Marketplace":e.surfaceType==="content-site"?"Content site":e.surfaceType==="game"?"Game":"App",s;if(t?.suggestedFeatures&&t.suggestedFeatures.length>0)s=t.suggestedFeatures.map(l=>({name:l.name,description:l.description,checked:l.recommended,source:l.recommended?"explicit":"suggested"}));else{let l=`${r} ${e.primaryAction||""}`;s=[];for(let d of Pi){let u=d.keywords.test(l),h=d.condition(e);(u||h)&&s.push({name:d.name,description:d.description,checked:u,source:u?"explicit":"suggested"})}}return{name:a,audience:i,audienceType:n,surfaceType:o,primaryAction:e.primaryAction||"manage items",features:s,publicLanding:e.publicLanding??!0,authModel:e.authModel??"email",dbProvider:e.dbProvider??"neon",integrations:e.integrations??[],language:t?.language||"English"}}function Mr(r){let e=r.features.filter(s=>s.checked),t=r.features.filter(s=>!s.checked),a={email:"Email sign-up",none:"No login (public)",social:"Social login","invite-only":"Invite-only"},i={neon:"Postgres",turso:"SQLite (legacy)"},n={b2c:"Your customers use this app (business-to-customer)",b2b:"Other businesses sign up for this (SaaS platform)",internal:"Internal team tool (staff only)"},o=[`**${r.name}** \u2014 ${r.surfaceType} for ${r.audience}`,`Audience: ${n[r.audienceType]??r.audienceType}`,`Primary action: ${r.primaryAction}`,`Access: ${a[r.authModel]??r.authModel} | Database: ${i[r.dbProvider]??r.dbProvider}${r.publicLanding?" | Landing page: Yes":""}${r.language&&r.language!=="English"?` | Language: ${r.language}`:""}`,""];if(e.length>0){o.push("**Included:**");for(let s of e)o.push(` \u2713 ${s.name} \u2014 ${s.description}`)}if(r.integrations.length>0&&(o.push(""),o.push(`**Integrations:** ${r.integrations.join(", ")}`)),t.length>0){o.push(""),o.push("**Available to add:**");for(let s of t)o.push(` \u25CB ${s.name} \u2014 ${s.description}`)}return o.join(`
10229
- `)}function Bi(r){let e=r.match(/\b(?:called|named)\s+["']?([A-Za-z][A-Za-z0-9 ]{1,30})["']?/i);if(e)return ga(e[1]);let t=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"]),i=r.toLowerCase().replace(/[^a-z0-9\s]/g,"").split(/\s+/).filter(n=>n.length>2&&!t.has(n)).slice(0,3);return i.length===0?"my-app":i.join("-")}function ga(r){return r.toLowerCase().replace(/[^a-z0-9\s]/g,"").trim().replace(/\s+/g,"-")}var Mt="__mistflow_url_choice__",Ui=600*1e3;function Fr(){let r=be(He(),".mistflow","confirm-secret");if(ht(r))try{return Buffer.from(Lr(r,"utf-8").trim(),"hex")}catch{}let e=Ni(32);return ma(be(He(),".mistflow"),{recursive:!0}),fa(r,e.toString("hex"),{mode:384}),e}function Ur(r){return Li("sha256").update(r.trim().toLowerCase()).digest("hex").slice(0,16)}function Ei(r,e){let t={cwd:r,d:Ur(e),exp:Date.now()+Ui},a=Buffer.from(JSON.stringify(t)).toString("base64url"),i=Nr("sha256",Fr()).update(a).digest("base64url");return`${a}.${i}`}function Hi(r,e,t){let a=r.split(".");if(a.length!==2)return!1;let[i,n]=a,o=Nr("sha256",Fr()).update(i).digest("base64url"),s=Buffer.from(n),l=Buffer.from(o);if(s.length!==l.length||!Fi(s,l))return!1;try{let d=JSON.parse(Buffer.from(i,"base64url").toString("utf-8"));return!(typeof d.exp!="number"||Date.now()>d.exp||d.cwd!==e||d.d!==Ur(t))}catch{return!1}}function Oi(r){let e=r,t=He(),a=!1;for(let i=0;i<64;i++){if(ht(be(e,"mistflow.json")))return"mistflow";if(!a&&ht(be(e,"package.json"))&&(a=!0),e===t)break;let n=Mi(e);if(n===e)break;e=n}return a?"foreign":"none"}function Gi(r){let e=He(),t=r.replace(/\/+$/,"");if(t===e||t==="/"||t===""||t==="/tmp"||t==="/private/tmp")return!0;let a=["Desktop","Documents","Downloads"];for(let i of a)if(t===be(e,i))return!0;return!1}var Wi=te.object({description:te.string().min(1,"App description or modification request"),projectPath:te.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."),conversationId:te.string().optional().describe("Returned by a previous mist_plan call with status 'clarify'. Pass it back to continue the conversation."),answers:te.record(te.string()).optional().describe("User's answers to the clarifying questions from the previous round. Keys are the questions, values are the answers."),existingPlan:te.record(te.unknown()).optional().describe("If provided, modifies this existing plan instead of creating a new one. Pass the current plan object from mistflow.json."),existingPlanId:te.string().optional().describe("Alternative to existingPlan \u2014 pass the planId from a previous mist_plan call to modify that plan."),templateToken:te.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:te.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:te.boolean().optional().describe("Skip clarifying questions and generate the plan immediately"),landingDesign:te.string().optional().describe("ID of a curated landing page design for the hero section. When set, the design's detailed blueprint (colors, fonts, layout, animations) is injected during the landing page implementation step. Use mist_project with action='landing-designs' to browse available landing designs."),appStyle:te.string().optional().describe("ID of a full-app style (e.g. 'stripe', 'linear', 'vercel', 'notion'). When set, the style's color palette, typography, component specs, shadows, and layout rules are injected during ALL implementation steps for consistent brand-quality design across every page. Use mist_project with action='app-styles' to browse available app styles."),language:te.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:te.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:te.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:te.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. The answers-dict magic key is deprecated and unreliable.")});function _i(r){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[a,i]of e)if(a.test(r))return i;return r.replace(/[?.,!]/g,"").split(/\s+/).filter(a=>!["what","how","do","does","is","are","the","a","an","would","should","you","your","for","this","that","to","of","or","and","want","like","prefer"].includes(a.toLowerCase())).slice(0,2).join(" ").slice(0,12)||"Option"}function ji(r){let e=be(He(),".mistflow","plans",`${r}.json`);if(!ht(e))return null;try{return JSON.parse(Lr(e,"utf-8")).plan??null}catch{return null}}async function zi(r){let{description:e,projectPath:t,conversationId:a,answers:i,existingPlan:n,existingPlanId:o,templateToken:s,remixDescription:l,autonomous:d,language:u,landingDesign:h,appStyle:c,brandMentioned:g,confirmToken:b,urlChoice:y}=r,f=n;if(!f&&o&&(f=ji(o)??void 0,!f))return p("Your previous plan is no longer available. Please describe your app again to generate a new plan.",!0);let v=a;if(!ce())return p("No Mistflow credentials found. Run mist_setup to connect your account.",!0);let C;if(!v&&!f&&!s){if(!Ri(t))return p(`projectPath must be an absolute path \u2014 received '${t}'. Pass the full absolute path to the user's project directory (e.g. /Users/alice/projects/my-app).`,!0);let k=Oi(t);if(k!=="mistflow"&&Gi(t))return p(JSON.stringify({status:"unsafe_cwd",projectPath:t,instruction:[`The projectPath you passed (${t}) 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 ${t}/<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(`
10230
- `)}),!0);if(k==="foreign"&&!g){if(!(b?Hi(b,t,e):!1)){let x=Ei(t,e);return p(JSON.stringify({status:"confirm_new_project",projectPath:t,description:e,confirmToken:x,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 in this folder 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) and did NOT explicitly invoke Mistflow by name.","MANDATORY: Use the AskUserQuestion tool with the provided askUserQuestion to confirm their intent before calling mist_plan again.","If they pick 'Scaffold a new Mistflow app in a subdirectory', call mist_plan again with the SAME description and confirmToken set to the token returned above.","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.",b?"The previous confirmToken was invalid, expired, or did not match the current directory/description. Use the fresh token above.":""].filter(Boolean).join(`
10231
- `)}))}C="Note: You're inside an existing project. Mistflow will create the new app in a subdirectory. It won't modify this codebase."}else k==="foreign"&&g&&(C="Note: You're inside an existing project. Mistflow will create the new app in a subdirectory. It won't modify this codebase.")}if(s)try{if(!(await cr(s)).plan)return p("This template has no plan to fork. Try a different template.",!0);let m=await pr(s),x=m.plan,S="";if(l&&m.has_source)try{let ke=await ra(m.plan,l),Se=ke.plan??ke,kt=ke.diff,ee=Se?.steps??[],ta=new Set([...(kt?.added??[]).map(Ce=>Ce.number),...(kt?.modified??[]).map(Ce=>Ce.number)]),Ne=ee.map(Ce=>{let fi=Ce.number;return ta.has(fi)?{...Ce,status:"pending"}:{...Ce,status:"completed",source:"forked"}});Se.steps=Ne,x=Se;let $e=Ne.filter(Ce=>Ce.status==="pending").length;S=` Remixed: ${Ne.filter(Ce=>Ce.status==="completed").length} steps unchanged, ${$e} steps need re-implementation.`}catch(ke){console.error("[plan] Remix failed, using original plan:",ke),S=" (Remix failed \u2014 using original plan. You can modify it later.)"}let G=Rr(),D=be(He(),".mistflow","plans");ma(D,{recursive:!0}),fa(be(D,`${G}.json`),JSON.stringify({plan:x,projectId:m.id,sourceDeploymentId:m.source_deployment_id,forkToken:m.fork_token,requiredEnvVars:m.required_env_vars,dbProvider:m.db_provider}));let j=x?.name??"forked-app",ue=m.has_source,ve=ue?"Source code will be restored during init. Run init promptly \u2014 the download token expires in 1 hour.":"",Xe=m.deploy_url?` Instant deploy started \u2014 your app will be live at ${m.deploy_url} in under a minute.`:"";return p(JSON.stringify({planId:G,forkedFrom:m.forked_from,projectId:m.id,hasSource:ue,deployUrl:m.deploy_url,message:`Forked "${m.forked_from}" into your workspace.${S}${Xe} ${ve} NEXT: Call mist_build with action='init', name='${j}', and planId='${G}' to create the project now.`}))}catch(k){let m=k instanceof Error?k.message:"Failed to fork template";return p(m,!0)}if(f){let k;try{k=await ra(f,e)}catch(D){let j=D instanceof Error?D.message:"Failed to modify plan";return p(j,!0)}let m=k.plan,x=k.diff,S=[];if(x?.added?.length){let D=x.added.map(j=>j.title);S.push(`Added ${D.length} step(s): ${D.join(", ")}`)}if(x?.removed?.length){let D=x.removed.map(j=>j.title);S.push(`Removed ${D.length} step(s): ${D.join(", ")}`)}if(x?.modified?.length){let D=x.modified.map(j=>j.title);S.push(`Modified ${D.length} step(s): ${D.join(", ")}`)}let G=S.length>0?S.join(". "):"No changes detected.";return p(JSON.stringify({plan:m,diff:x,message:`Plan modified. ${G}. Update mistflow.json with the new plan, then continue with mist_build (action: 'implement').`}))}let P=y?.trim()||void 0,A=i;if(!P&&i&&Mt in i&&(P=i[Mt]),i&&Mt in i){let{[Mt]:k,...m}=i;A=Object.keys(m).length>0?m:void 0}if(P&&(P=P.replace(/^Keep\s+/i,"").replace(/\s*\(Recommended\)\s*$/i,"").replace(/\.mistflow\.app.*$/i,"").trim()||void 0),P){let k=P.toLowerCase().replace(/\s+/g,"-");/^[a-z0-9][a-z0-9-]{1,30}[a-z0-9]$/.test(k)?P=k:(console.error(`[mist_plan] Discarding urlChoice '${P}' \u2014 does not look like a subdomain. Backend will auto-generate.`),P=void 0)}let B;try{B=await qa(e,{conversationId:v,answers:A,autonomous:d,language:u})}catch(k){let m=k instanceof Error?k.message:"Failed to generate plan";return p(m,!0)}if(B.status==="clarify"){let k=B.reflection||"",m=B.suggestedName||"",x=B.suggestedFeatures??[],S=B.questions??[],G=S.some(ee=>Array.isArray(ee.options)&&typeof ee.options[0]=="object"&&ee.options[0]?.label),D={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",entityShape:"Item shape",coreView:"View",scope:"Scope",sharing:"Sharing",workflow:"Workflow",constraints:"Constraints",domain:"Product"},j=S.map(ee=>{let ta=ee.decisionKey&&D[ee.decisionKey]||_i(ee.question),Ne;return G&&Array.isArray(ee.options)?Ne=ee.options.map($e=>({label:$e.label,description:$e.description??""})):Array.isArray(ee.options)?Ne=ee.options.map(($e,Ha)=>({label:Ha===0?`${$e} (Recommended)`:String($e),description:ee.why??""})):Ne=[{label:"Yes (Recommended)",description:ee.why??""},{label:"No",description:""}],{question:ee.question,header:ta,options:Ne,multiSelect:!1}}),ve=B.decisions?.audienceType??null,Xe=x.length>0?Ir(e,{primaryActor:null,primaryAction:null,surfaceType:null,audienceType:ve,multiRole:null,publicLanding:null,realMoney:null,scheduling:null,authModel:null,dbProvider:null,integrations:null},{suggestedName:m,suggestedFeatures:x,language:u}):null,ke=Xe?Mr(Xe):"",Se=ga(m||"my-app").slice(0,32);try{let ee=await $a(Se);!ee.available&&ee.suggestion&&(Se=ee.suggestion)}catch{}ke&&(ke+=`
10232
-
10233
- **Your app URL:** https://${Se}.mistflow.app`);let kt={question:`Your app will be at ${Se}.mistflow.app \u2014 want to customize the URL?`,header:"URL",options:[{label:`Keep ${Se}.mistflow.app (Recommended)`,description:"This URL is available"},{label:"Choose a different URL",description:"Type your preferred subdomain"}],multiSelect:!1};return j.push(kt),p(JSON.stringify({status:"clarify",conversation_id:B.conversation_id,questions:S,questionCount:S.length,suggestedFeatures:x,suggestedName:m,suggestedSubdomain:Se,reflection:k,briefText:ke,askUserQuestions:j,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:[...C?[C,""]:[],k?`${k}
10234
- `:"",ke?`Here's what I'd build:
10235
-
10236
- ${ke}
10237
- `:"",`I have ${S.length} quick question${S.length===1?"":"s"} to pin down the details.`,"","MANDATORY: Use the AskUserQuestion tool to present these questions. Do NOT present them as text.","","IF you have access to the AskUserQuestion tool (Claude Code):"," Use it to present questions with selectable options. Pass the 'askUserQuestions' array directly.","","OTHERWISE (Cursor, Codex, or other hosts):"," Present each question conversationally with the options listed.","","BEFORE calling mist_plan for the final plan generation, tell the user:"," 'Generating your plan now. This usually takes about 60\u201390 seconds \u2014 I'm designing the data model, laying out pages, and writing build steps.'","This primes the user for the silent wait. Do NOT call mist_plan without saying this first.","","Once you have all answers, call mist_plan again with:",` conversationId: "${B.conversation_id}"`,' answers: { "<question text>": "<user answer>", ... }'," description: (same description as before)",' urlChoice: "<the URL subdomain the user picked>" \u2190 pass as a top-level param, NOT inside answers',"","NOTE: You may receive another 'clarify' response with follow-up questions if the user's answers","revealed new ambiguity. This is normal \u2014 relay those questions too. Keep going until you get 'ready'.","","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: "${Se}".`,'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.'].join(`
10238
- `)}))}let I=B.plan,M=I.name??"Untitled App",O=B.methodology,E=I.steps;if(!Array.isArray(E)||E.length===0)return p("Plan generation incomplete \u2014 the plan is missing implementation steps. Please call mist_plan again with the same description to retry.",!0);let H=I.publicPages;if(!H||Array.isArray(H)&&H.length===0){let k=I.pages,m=E.some(S=>typeof S.name=="string"&&S.name.toLowerCase().includes("landing")||typeof S.title=="string"&&S.title.toLowerCase().includes("landing")),x=Array.isArray(k)&&k.some(S=>S.path==="/"||S.route==="/");m||x?H=["/","/pricing"]:H=["/"]}let re=I.primaryAction;if(!re){let k=I.features;if(Array.isArray(k)&&k.length>0){let x=k.find(G=>typeof G.priority=="string"&&G.priority.toLowerCase()==="must-have")??k[0];re={entity:x.name??x.title??"item",action:"create",fromPage:"/dashboard"}}}let z=I.nonNegotiables;(!z||Array.isArray(z)&&z.length===0)&&(z=["Landing page renders correctly at / with content (not a redirect)","Core user action works end-to-end (create entity, see it in list)"]);let K=Rr(),U=be(He(),".mistflow","plans");ma(U,{recursive:!0});try{let m=Date.now();for(let x of[U,be(He(),".mistflow","mockup-state")])if(ht(x))for(let S of Di(x))try{let G=be(x,S),D=Ai(G).mtimeMs;m-D>6048e5&&Ii(G)}catch{}}catch{}let _;if(h){let k=et(h);k?_=k.id:console.error(`Landing design '${h}' not found \u2014 ignoring. Use mist_project action='landing-designs' to browse available landing designs.`)}let F;if(c){let k=fe(c);k?F=k.id:console.error(`App style '${c}' not found \u2014 ignoring. Use mist_project action='app-styles' to browse available app styles.`)}let Q=!!_,le=E.some(k=>{let m=`${k.name??k.title} ${k.description??""}`.toLowerCase();return m.includes("landing")||m.includes("hero")||m.includes("marketing")||m.includes("homepage")}),T=!Q&&le?kr(e,{maxResults:2}):[];T.length>0&&(_=T[0].id,console.error(`Auto-assigned landing layout preset (default): ${T[0].title} (${_})`));let R={name:I.name,summary:I.summary,dataModel:I.dataModel,pages:I.pages,features:I.features,steps:E.map(k=>({...k,name:k.name??k.title})),design:I.design,landingDesign:_,appStyle:F,dbProvider:I.dbProvider??"neon",authModel:I.authModel,audienceType:I.audienceType??"b2c",roles:I.roles,defaultRole:I.defaultRole,publicPages:H,navStyle:I.navStyle,multiTenant:I.multiTenant,primaryAction:re,nonNegotiables:z,requestedSubdomain:P,...u&&u.toLowerCase()!=="english"?{language:u}:{}};fa(be(U,`${K}.json`),JSON.stringify({plan:R,methodology:O}));let L=E.map(k=>`${k.number}. ${k.name??k.title}`),oe="",ie=[],w;!Q&&T.length>0&&(ie=T.map(m=>({id:m.id,slug:m.slug,title:m.title,description:m.description,url:`${Ae()}/designs?tab=landing-designs`})),w={question:"What landing page style fits this app?",header:"Landing Design",options:[...T.map((m,x)=>({label:x===0?`${m.title} (Recommended)`:m.title,description:m.description})),{label:"Design from scratch (Creative)",description:"No template \u2014 the AI designs a bespoke hero from your app's concept using 3D, scroll-driven animation, or particle effects. Higher creative ceiling, takes ~10 min longer, occasionally needs a second pass."},{label:"Browse all landing designs",description:`Not sure? See all landing designs at ${Ae()}/designs?tab=landing-designs and pass the ID back.`}],multiSelect:!1},oe=` Also ask the user about landing design using the AskUserQuestion tool with the 'landingDesignQuestion' object. Recommended: ${T.map(m=>m.title).join(" or ")}. If they pick "Design from scratch", pass landingDesign='freeform' to mist_build init. If they pick a specific preset title, look up its ID via mist_project action='landing-designs' and pass that. If they pick the recommended option (or don't answer), '${T[0].id}' is already set as the plan default \u2014 no action needed.`);let N="",q=[],me;F||(q=Br(e,{maxResults:3}).map(x=>({id:x.id,slug:It(x.name),name:x.name,description:x.description,theme:x.theme,url:`${Ae()}/designs/app-styles/${It(x.name)}`})),me={question:"What design style should your app have? Choose a design system for consistent, brand-quality UI across all pages.",header:"App Design",options:[...q.map((x,S)=>({label:S===0?`${x.name} (Recommended)`:x.name,description:`${x.theme} theme \u2014 ${x.description} Preview: ${x.url}`})),{label:"See more app styles",description:`Not sure? Browse all 53 app styles at ${Ae()}/designs?tab=app-styles and pick one.`},{label:"Skip \u2014 use default styling",description:"Proceed without a design system"}],multiSelect:!1},N=` Ask the user which app style they want using the AskUserQuestion tool with the 'appStyleQuestion' object provided. Recommended: ${q.map(x=>`[${x.name}](${x.url})`).join(", ")}. In your message, say these are top picks based on their description and that if none feel right they can browse all 53 app styles at ${Ae()}/designs?tab=app-styles (include this link verbatim). Once the user picks, pass appStyle='<id>' to the mist_build init call. If user says "skip", proceed without one.`);let Z=(I.audienceType??"b2c")==="b2c",xe={question:"Include a lifestyle photo in your landing page hero? Photos add warmth and human context; pure CSS stays cleaner.",header:"Hero",options:[{label:Z?"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:Z?"No, CSS only":"No, CSS only (Recommended)",description:"Animated gradients + glassmorphism, no photo \u2014 cleaner and more technical (like Stripe, Linear, Vercel)."}],multiSelect:!1},we=" Also ask the user about the 'heroPhotoQuestion' provided. Once they pick, pass heroPhoto=true (photo) or heroPhoto=false (CSS only) to the mist_build init call.",de="",$=[];for(let k of E){let m=k.name??k.title,x=k.integrationId;if(x){let S=at(x);if(S){let G=rt(S.id);$.push({step:m,presetId:S.id,presetName:S.name,envVars:G?.envVars??[]})}}}if($.length>0){let k=$.flatMap(S=>S.envVars),m=[...new Set(k.map(S=>S.key))];de=` This plan uses integrations (${$.map(S=>S.presetName).join(", ")}). Detailed blueprints will be auto-injected during each integration step.${m.length>0?` The user will need these API keys: ${m.join(", ")}.`:""}`}return p(JSON.stringify({planId:K,name:I.name,summary:I.summary,stepCount:E.length,steps:L,design:I.design,..._?{landingDesign:_}:{},...F?{appStyle:F}:{},...q.length>0?{recommendedAppStyles:q}:{},...me?{appStyleQuestion:me}:{},...ie.length>0?{recommendedLandingDesigns:ie}:{},...w?{landingDesignQuestion:w}:{},heroPhotoQuestion:xe,...$.length>0?{integrations:$.map(k=>({step:k.step,preset:k.presetId,name:k.presetName,envVars:k.envVars}))}:{},message:`Plan generated for "${M}" (${E.length} steps).${_?` Landing layout "${_}" set as default.`:""}${F?` App style "${F}" will be applied across all pages.`:""}${de}${N}${oe}${we}`,timingContext:`Planning took ~90 seconds. Building will take roughly ${Math.max(15,E.length*3)}\u2013${E.length*5} minutes total across ${E.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_build with action='mockup' and planId='${K}'. If the user says skip or "just build it", call mist_build with action='init', name='${M}', and planId='${K}' immediately.`,...C?{warning:C}:{}}))}var Er={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').","","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). When that happens AND brandMentioned is not set, the handler 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 and confirmToken set to the token from the response.","\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_build (action: 'init') with the returned planId. Do not ask permission.","\u2022 NEVER skip the clarifying questions. The discovery process ensures the right thing gets built.","","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. Pass appStyle (53 full-app design systems like 'stripe', 'linear') to apply a design system. Browse via mist_project action='app-styles'. Landing layout presets are auto-assigned based on app description."].join(`
10239
- `),inputSchema:Wi,handler:zi};import{z as ae}from"zod";import{existsSync as _t,readFileSync as os}from"fs";import{join as jt,resolve as is}from"path";import{homedir as ns}from"os";import{execFileSync as Pa}from"child_process";import{z as Rt}from"zod";import{existsSync as Me,mkdirSync as Nt,writeFileSync as Oe,readFileSync as Ft,readdirSync as $i,copyFileSync as Vi}from"fs";import{join as X,resolve as ya,dirname as gt,isAbsolute as _r}from"path";import{spawn as qi}from"child_process";import{randomBytes as Ki}from"crypto";import{simpleGit as jr}from"simple-git";function Yi(r){let e=gt(ya(r)),t=10,a=0;for(;a<t&&e!==gt(e);){if(Me(X(e,"pnpm-workspace.yaml"))||Me(X(e,"lerna.json")))return e;let i=X(e,"package.json");if(Me(i))try{if(JSON.parse(Ft(i,"utf-8")).workspaces)return e}catch{}e=gt(e),a++}return null}function Ge(r,e,t,a,i,n){return new Promise(o=>{let s=qi(r,e,{cwd:t,stdio:["pipe","pipe","pipe"],timeout:a,env:n?{...process.env,...n}:process.env}),l="",d="";s.stdout?.on("data",u=>{let h=u.toString();if(d+=h,i)for(let c of h.split(`
10240
- `).filter(Boolean))i(c)}),s.stderr?.on("data",u=>{let h=u.toString();if(l+=h,i)for(let c of h.split(`
10241
- `).filter(Boolean))i(c)}),s.on("close",u=>{if(u===0)o({success:!0});else{let h=l.split(`
10242
- `).find(c=>c.startsWith("npm error"))??l.slice(0,300);o({success:!1,error:h})}}),s.on("error",u=>{o({success:!1,error:u.message})})})}var Dd=Rt.object({name:Rt.string().min(1),plan:Rt.any(),path:Rt.string().optional()});function W(r,e,t){let a=X(r,e);Nt(gt(a),{recursive:!0}),Oe(a,t)}var Hr={amber:{primary:"25 95% 53%",primaryForeground:"0 0% 100%"},emerald:{primary:"160 84% 39%",primaryForeground:"0 0% 100%"},indigo:{primary:"239 84% 67%",primaryForeground:"0 0% 100%"},rose:{primary:"347 77% 50%",primaryForeground:"0 0% 100%"},cyan:{primary:"189 94% 43%",primaryForeground:"0 0% 100%"},violet:{primary:"263 70% 50%",primaryForeground:"0 0% 100%"},orange:{primary:"21 90% 48%",primaryForeground:"0 0% 100%"},teal:{primary:"168 76% 42%",primaryForeground:"0 0% 100%"},sky:{primary:"199 89% 48%",primaryForeground:"0 0% 100%"}},Ji={sharp:"0.125rem",subtle:"0.375rem",rounded:"0.75rem",pill:"9999px"};function Ut(r){let e=r.replace("#","");if(e.length!==6&&e.length!==3)return null;let t=e.length===3?e[0]+e[0]+e[1]+e[1]+e[2]+e[2]:e,a=parseInt(t,16);return isNaN(a)?null:{r:a>>16&255,g:a>>8&255,b:a&255}}function Et(r,e,t){r/=255,e/=255,t/=255;let a=Math.max(r,e,t),i=Math.min(r,e,t),n=(a+i)/2;if(a===i)return{h:0,s:0,l:Math.round(n*100)};let o=a-i,s=n>.5?o/(2-a-i):o/(a+i),l=0;return a===r?l=((e-t)/o+(e<t?6:0))/6:a===e?l=((t-r)/o+2)/6:l=((r-e)/o+4)/6,{h:Math.round(l*360),s:Math.round(s*100),l:Math.round(n*100)}}function ot(r){let e=Ut(r);if(!e)return"0 0% 50%";let{h:t,s:a,l:i}=Et(e.r,e.g,e.b);return`${t} ${a}% ${i}%`}function zr(r){let e=Ut(r);return e?Et(e.r,e.g,e.b).l:50}function $r(r){let e=Ut(r);return e?Et(e.r,e.g,e.b).s:0}function Qi(r){let e=fe(r);if(!e)return null;let t=e.sections.find(g=>g.title.toLowerCase().includes("color palette"));if(!t)return null;let a=t.content,i={},n=a.split(/^### /m);for(let g of n){if(!g.trim())continue;let b=g.indexOf(`
10243
- `);if(b===-1)continue;let y=g.slice(0,b).trim().toLowerCase();i[y]=g.slice(b)}let o=g=>g.match(/#[0-9a-fA-F]{6}\b/)?.[0],s,l=["interactive","accent","action","cta","brand"];for(let g of l){if(s)break;for(let b of Object.keys(i))if(b.includes(g)&&(s=o(i[b]),s))break}if(!s){for(let g of Object.keys(i))if(g.includes("secondary")&&(s=o(i[g]),s))break}let d;for(let g of Object.keys(i))if((g.includes("surface")||g.includes("background")||g.includes("canvas"))&&(d=o(i[g]),d))break;let u;for(let g of Object.keys(i))if((g==="primary"||g.includes("text")||g.includes("neutral"))&&(u=o(i[g]),u))break;let h;for(let g of Object.keys(i))if(g.includes("neutral")||g.includes("muted")||g.includes("tertiary")){let y=(i[g].match(/#[0-9a-fA-F]{6}\b/g)??[]).map(v=>({hex:v,l:zr(v)})).sort((v,C)=>v.l-C.l),f=y[Math.floor(y.length/2)];if(f&&(h=f.hex),h)break}let c;for(let g of Object.keys(i))if((g.includes("border")||g.includes("divider")||g.includes("separator"))&&(c=o(i[g]),c))break;if(!c){let g=a.match(/[Bb]order[^`]*`(#[0-9a-fA-F]{6})`/);g&&(c=g[1])}return s&&$r(s)<10&&(s=void 0),!s&&!d&&!u?null:{background:d,foreground:u,accent:s,muted:h,border:c}}function Xi(r,e){let t=Qi(e),a=t?.accent??r.colors.find(y=>$r(y)>20)??r.colors[0]??"#6366f1",i=ot(a),n=zr(a),o=t?.background??(r.theme==="light"?"#ffffff":"#0a0a0a"),s=Ut(o),l=s?Et(s.r,s.g,s.b):{h:0,s:0,l:100},d=l.s>3?l.h:0,u=Math.min(l.s,15),h=t?.foreground??"#0f172a",c=t?.muted,g=t?.border,b=Math.min(u,10);return{light:{background:`${d} ${u}% 99%`,foreground:ot(h),card:`${d} ${u}% 100%`,cardForeground:ot(h),muted:c?ot(c):`${d} ${Math.min(u,10)}% 96%`,mutedForeground:`${d} ${Math.min(u,8)}% 45%`,border:g?ot(g):`${d} ${Math.min(u,10)}% 90%`,input:g?ot(g):`${d} ${Math.min(u,10)}% 90%`,ring:i},dark:{background:`${d} ${b}% 4%`,foreground:"0 0% 95%",card:`${d} ${b}% 6%`,cardForeground:"0 0% 95%",muted:`${d} ${Math.min(b,6)}% 16%`,mutedForeground:`${d} ${Math.min(b,6)}% 65%`,border:`${d} ${Math.min(b,6)}% 16%`,input:`${d} ${Math.min(b,6)}% 16%`,ring:i},primary:i,primaryForeground:n>60?"0 0% 5%":"0 0% 100%"}}function Y(r){let e=r.trim().split(/\s+/),t=parseFloat(e[0]),a=parseFloat(e[1])/100,i=parseFloat(e[2])/100,n=a*Math.min(i,1-i),o=E=>{let H=(E+t/30)%12;return i-n*Math.max(-1,Math.min(H-3,9-H,1))},s=o(0),l=o(8),d=o(4),u=E=>E<=.04045?E/12.92:Math.pow((E+.055)/1.055,2.4),h=u(s),c=u(l),g=u(d),b=Math.cbrt(.4122214708*h+.5363325363*c+.0514459929*g),y=Math.cbrt(.2119034982*h+.6806995451*c+.1073969566*g),f=Math.cbrt(.0883024619*h+.2817188376*c+.6299787005*g),v=.2104542553*b+.793617785*y-.0040720468*f,C=1.9779984951*b-2.428592205*y+.4505937099*f,P=.0259040371*b+.7827717662*y-.808675766*f,A=Math.sqrt(C*C+P*P),B=Math.atan2(P,C)*180/Math.PI;B<0&&(B+=360);let I=v.toFixed(4),M=A.toFixed(4),O=A<1e-4?"0":B.toFixed(2);return`${I} ${M} ${O}`}function Zi(r,e){let t=r?.borderRadius??"subtle",a=Ji[t]??"0.375rem",i,n,o,s,l=e?Ie(e):void 0,d=!1;if(l){let c=Xi(l,e);d=l.theme==="dark",i=d?c.dark:c.light,n=d?c.light:c.dark,o=c.primary,s=c.primaryForeground}else{let c=r?.accentColor??"indigo",g=Hr[c]??Hr.indigo;i={background:"0 0% 100%",foreground:"240 10% 4%",card:"0 0% 100%",cardForeground:"240 10% 4%",muted:"240 5% 96%",mutedForeground:"240 4% 46%",border:"240 6% 90%",input:"240 6% 90%",ring:"240 5% 65%"},n={background:"240 10% 4%",foreground:"0 0% 95%",card:"240 10% 6%",cardForeground:"0 0% 95%",muted:"240 4% 16%",mutedForeground:"240 5% 65%",border:"240 4% 16%",input:"240 4% 16%",ring:"240 5% 65%"},o=g.primary,s=g.primaryForeground}let u=[...Object.entries(i).map(([c,g])=>` --color-${Or(c)}: oklch(${Y(g)});`),` --color-primary: oklch(${Y(o)});`,` --color-primary-foreground: oklch(${Y(s)});`,` --color-secondary: oklch(${Y(i.muted)});`,` --color-secondary-foreground: oklch(${Y(i.foreground)});`,` --color-accent: oklch(${Y(i.muted)});`,` --color-accent-foreground: oklch(${Y(i.foreground)});`,` --color-destructive: oklch(${Y(d?"0 72% 51%":"0 84% 60%")});`,` --color-destructive-foreground: oklch(${Y("0 0% 100%")});`,` --color-popover: oklch(${Y(i.card)});`,` --color-popover-foreground: oklch(${Y(i.cardForeground)});`," --radius-sm: 0.25rem;",` --radius-md: ${a};`," --radius-lg: 0.5rem;"," --radius-xl: 0.75rem;"].join(`
10224
+ 8. **Never ask the user to paste REPLICATE_API_TOKEN in chat.** Direct them to set it in the Mistflow dashboard (Project Settings > Environment Variables).`}];function Li(r){return r.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}function Xe(r){let e=Qe.find(t=>t.id===r);if(e)return e;let a=r.toLowerCase().replace(/[^a-z0-9]/g,"");return Qe.find(t=>{let i=t.name.toLowerCase().replace(/[^a-z0-9]/g,"");return i===a||i.includes(a)||a.includes(i)})}function Ze(r){return ma[r]}function fa(r){return r?Qe.filter(e=>e.category.toLowerCase()===r.toLowerCase()):Qe}function Ir(r){let e=r??Qe,a={};for(let i of e){a[i.category]||(a[i.category]=[]);let s=ma[i.id],o=s?` \u2014 ${s.description}`:"",n=s?.packages.length?` (${s.packages.join(", ")})`:"";a[i.category].push(`${i.id} \u2014 "${i.name}"${o}${n}`)}let t=[];for(let[i,s]of Object.entries(a))t.push(`**${i}**:
10225
+ ${s.map(o=>` - ${o}`).join(`
10226
+ `)}`);return t.join(`
10227
+
10228
+ `)}function Mr(r){return(r?fa(r):Qe).map(a=>{let t=ma[a.id];return{id:a.id,slug:Li(a.name),name:a.name,category:a.category,description:t?.description??"",tags:t?.tags??[],envVars:t?.envVars??[],docsUrl:t?.docsUrl??"",packages:t?.packages??[],difficulty:t?.difficulty??"medium"}})}var Fi=[{name:"Dashboard",description:"Overview with key stats and today's activity",condition:r=>r.surfaceType==="internal-tool"||r.surfaceType==="customer-app",keywords:/\b(dashboard|overview|home.?page|stats)\b/i},{name:"Landing Page",description:"Public page explaining what this does",condition:r=>r.publicLanding===!0,keywords:/\b(landing|marketing|hero|homepage)\b/i},{name:"Scheduling / Booking",description:"Calendar, time slots, reservations",condition:r=>r.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:r=>r.multiRole===!0||r.primaryActor==="both",keywords:/\b(admin|panel|manage.?user|moderat)\b/i},{name:"User Profiles",description:"Account pages, settings, preferences",condition:r=>r.primaryActor==="customers"||r.primaryActor==="both",keywords:/\b(profile|account|settings|preferences)\b/i},{name:"Search / Browse",description:"Find and filter content or listings",condition:r=>r.surfaceType==="marketplace",keywords:/\b(search|browse|filter|discover|explore)\b/i},{name:"Email Notifications",description:"Welcome emails, alerts, reminders",condition:r=>r.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:r=>r.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:r=>r.integrations?.includes("ai")===!0,keywords:/\b(ai|chatbot|gpt|llm|generat|assistant)\b/i},{name:"Maps / Location",description:"Google Maps, location search, geolocation",condition:r=>r.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:r=>r.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:r=>r.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:r=>r.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:r=>r.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 Rr(r,e,a){let t=a?.suggestedName||Ei(r),i=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")),o=e.surfaceType==="internal-tool"?"Internal tool":e.surfaceType==="marketplace"?"Marketplace":e.surfaceType==="content-site"?"Content site":e.surfaceType==="game"?"Game":"App",n;if(a?.suggestedFeatures&&a.suggestedFeatures.length>0)n=a.suggestedFeatures.map(l=>({name:l.name,description:l.description,checked:l.recommended,source:l.recommended?"explicit":"suggested"}));else{let l=`${r} ${e.primaryAction||""}`;n=[];for(let d of Fi){let u=d.keywords.test(l),h=d.condition(e);(u||h)&&n.push({name:d.name,description:d.description,checked:u,source:u?"explicit":"suggested"})}}return{name:t,audience:i,audienceType:s,surfaceType:o,primaryAction:e.primaryAction||"manage items",features:n,publicLanding:e.publicLanding??!0,authModel:e.authModel??"email",dbProvider:e.dbProvider??"neon",integrations:e.integrations??[],language:a?.language||"English"}}function Nr(r){let e=r.features.filter(n=>n.checked),a=r.features.filter(n=>!n.checked),t={email:"Email sign-up",none:"No login (public)",social:"Social login","invite-only":"Invite-only"},i={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)"},o=[`**${r.name}** \u2014 ${r.surfaceType} for ${r.audience}`,`Audience: ${s[r.audienceType]??r.audienceType}`,`Primary action: ${r.primaryAction}`,`Access: ${t[r.authModel]??r.authModel} | Database: ${i[r.dbProvider]??r.dbProvider}${r.publicLanding?" | Landing page: Yes":""}${r.language&&r.language!=="English"?` | Language: ${r.language}`:""}`,""];if(e.length>0){o.push("**Included:**");for(let n of e)o.push(` \u2713 ${n.name} \u2014 ${n.description}`)}if(r.integrations.length>0&&(o.push(""),o.push(`**Integrations:** ${r.integrations.join(", ")}`)),a.length>0){o.push(""),o.push("**Available to add:**");for(let n of a)o.push(` \u25CB ${n.name} \u2014 ${n.description}`)}return o.join(`
10229
+ `)}function Ei(r){let e=r.match(/\b(?:called|named)\s+["']?([A-Za-z][A-Za-z0-9 ]{1,30})["']?/i);if(e)return ba(e[1]);let a=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"]),i=r.toLowerCase().replace(/[^a-z0-9\s]/g,"").split(/\s+/).filter(s=>s.length>2&&!a.has(s)).slice(0,3);return i.length===0?"my-app":i.join("-")}function ba(r){return r.toLowerCase().replace(/[^a-z0-9\s]/g,"").trim().replace(/\s+/g,"-")}var It="__mistflow_url_choice__",$i=600*1e3;function Ur(){let r=ye(Ee(),".mistflow","confirm-secret");if(ct(r))try{return Buffer.from(Fr(r,"utf-8").trim(),"hex")}catch{}let e=ji(32);return ya(ye(Ee(),".mistflow"),{recursive:!0}),xa(r,e.toString("hex"),{mode:384}),e}function Hr(r){return _i("sha256").update(r.trim().toLowerCase()).digest("hex").slice(0,16)}function Vi(r,e){let a={cwd:r,d:Hr(e),exp:Date.now()+$i},t=Buffer.from(JSON.stringify(a)).toString("base64url"),i=Er("sha256",Ur()).update(t).digest("base64url");return`${t}.${i}`}function qi(r,e,a){let t=r.split(".");if(t.length!==2)return!1;let[i,s]=t,o=Er("sha256",Ur()).update(i).digest("base64url"),n=Buffer.from(s),l=Buffer.from(o);if(n.length!==l.length||!zi(n,l))return!1;try{let d=JSON.parse(Buffer.from(i,"base64url").toString("utf-8"));return!(typeof d.exp!="number"||Date.now()>d.exp||d.cwd!==e||d.d!==Hr(a))}catch{return!1}}function Ki(r){let e=r,a=Ee(),t=!1;for(let i=0;i<64;i++){if(ct(ye(e,"mistflow.json")))return"mistflow";if(!t&&ct(ye(e,"package.json"))&&(t=!0),e===a)break;let s=Gi(e);if(s===e)break;e=s}return t?"foreign":"none"}function Yi(r){let e=Ee(),a=r.replace(/\/+$/,"");if(a===e||a==="/"||a===""||a==="/tmp"||a==="/private/tmp")return!0;let t=["Desktop","Documents","Downloads"];for(let i of t)if(a===ye(e,i))return!0;return!1}var Ji=ie.object({description:ie.string().min(1,"App description or modification request"),projectPath:ie.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."),conversationId:ie.string().optional().describe("Returned by a previous mist_plan call with status 'clarify'. Pass it back to continue the conversation."),answers:ie.record(ie.string()).optional().describe("User's answers to the clarifying questions from the previous round. Keys are the questions, values are the answers."),existingPlan:ie.record(ie.unknown()).optional().describe("If provided, modifies this existing plan instead of creating a new one. Pass the current plan object from mistflow.json."),existingPlanId:ie.string().optional().describe("Alternative to existingPlan \u2014 pass the planId from a previous mist_plan call to modify that plan."),templateToken:ie.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:ie.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:ie.boolean().optional().describe("Skip clarifying questions and generate the plan immediately"),landingDesign:ie.string().optional().describe("ID of a curated landing page design for the hero section. When set, the design's detailed blueprint (colors, fonts, layout, animations) is injected during the landing page implementation step. Use mist_project with action='landing-designs' to browse available landing designs."),appStyle:ie.string().optional().describe("ID of a full-app style (e.g. 'stripe', 'linear', 'vercel', 'notion'). When set, the style's color palette, typography, component specs, shadows, and layout rules are injected during ALL implementation steps for consistent brand-quality design across every page. Use mist_project with action='app-styles' to browse available app styles."),language:ie.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:ie.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:ie.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:ie.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. The answers-dict magic key is deprecated and unreliable.")});function Qi(r){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[t,i]of e)if(t.test(r))return i;return r.replace(/[?.,!]/g,"").split(/\s+/).filter(t=>!["what","how","do","does","is","are","the","a","an","would","should","you","your","for","this","that","to","of","or","and","want","like","prefer"].includes(t.toLowerCase())).slice(0,2).join(" ").slice(0,12)||"Option"}function Xi(r){let e=ye(Ee(),".mistflow","plans",`${r}.json`);if(!ct(e))return null;try{return JSON.parse(Fr(e,"utf-8")).plan??null}catch{return null}}async function Zi(r){let{description:e,projectPath:a,conversationId:t,answers:i,existingPlan:s,existingPlanId:o,templateToken:n,remixDescription:l,autonomous:d,language:u,landingDesign:h,appStyle:c,brandMentioned:g,confirmToken:b,urlChoice:f}=r,m=s;if(!m&&o&&(m=Xi(o)??void 0,!m))return p("Your previous plan is no longer available. Please describe your app again to generate a new plan.",!0);let y=t;if(!ue())return p("No Mistflow credentials found. Run mist_setup to connect your account.",!0);let T;if(!y&&!m&&!n){if(!Wi(a))return p(`projectPath must be an absolute path \u2014 received '${a}'. Pass the full absolute path to the user's project directory (e.g. /Users/alice/projects/my-app).`,!0);let w=Ki(a);if(w!=="mistflow"&&Yi(a))return p(JSON.stringify({status:"unsafe_cwd",projectPath:a,instruction:[`The projectPath you passed (${a}) 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 ${a}/<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(`
10230
+ `)}),!0);if(w==="foreign"&&!g){if(!(b?qi(b,a,e):!1)){let A=Vi(a,e);return p(JSON.stringify({status:"confirm_new_project",projectPath:a,description:e,confirmToken:A,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 in this folder 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) and did NOT explicitly invoke Mistflow by name.","MANDATORY: Use the AskUserQuestion tool with the provided askUserQuestion to confirm their intent before calling mist_plan again.","If they pick 'Scaffold a new Mistflow app in a subdirectory', call mist_plan again with the SAME description and confirmToken set to the token returned above.","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.",b?"The previous confirmToken was invalid, expired, or did not match the current directory/description. Use the fresh token above.":""].filter(Boolean).join(`
10231
+ `)}))}T="Note: You're inside an existing project. Mistflow will create the new app in a subdirectory. It won't modify this codebase."}else w==="foreign"&&g&&(T="Note: You're inside an existing project. Mistflow will create the new app in a subdirectory. It won't modify this codebase.")}if(n)try{if(!(await ur(n)).plan)return p("This template has no plan to fork. Try a different template.",!0);let v=await hr(n),A=v.plan,D="";if(l&&v.has_source)try{let we=await na(v.plan,l),ve=we.plan??we,St=we.diff,ae=ve?.steps??[],oa=new Set([...(St?.added??[]).map(ke=>ke.number),...(St?.modified??[]).map(ke=>ke.number)]),Re=ae.map(ke=>{let Ci=ke.number;return oa.has(Ci)?{...ke,status:"pending"}:{...ke,status:"completed",source:"forked"}});ve.steps=Re,A=ve;let _e=Re.filter(ke=>ke.status==="pending").length;D=` Remixed: ${Re.filter(ke=>ke.status==="completed").length} steps unchanged, ${_e} steps need re-implementation.`}catch(we){console.error("[plan] Remix failed, using original plan:",we),D=" (Remix failed \u2014 using original plan. You can modify it later.)"}let z=Lr(),X=ye(Ee(),".mistflow","plans");ya(X,{recursive:!0}),xa(ye(X,`${z}.json`),JSON.stringify({plan:A,projectId:v.id,sourceDeploymentId:v.source_deployment_id,forkToken:v.fork_token,requiredEnvVars:v.required_env_vars,dbProvider:v.db_provider}));let re=A?.name??"forked-app",aa=v.has_source,ra=aa?"Source code will be restored during init. Run init promptly \u2014 the download token expires in 1 hour.":"",kt=v.deploy_url?` Instant deploy started \u2014 your app will be live at ${v.deploy_url} in under a minute.`:"";return p(JSON.stringify({planId:z,forkedFrom:v.forked_from,projectId:v.id,hasSource:aa,deployUrl:v.deploy_url,message:`Forked "${v.forked_from}" into your workspace.${D}${kt} ${ra} NEXT: Call mist_build with action='init', name='${re}', and planId='${z}' to create the project now.`}))}catch(w){let v=w instanceof Error?w.message:"Failed to fork template";return p(v,!0)}if(m){let w;try{w=await na(m,e)}catch(X){let re=X instanceof Error?X.message:"Failed to modify plan";return p(re,!0)}let v=w.plan,A=w.diff,D=[];if(A?.added?.length){let X=A.added.map(re=>re.title);D.push(`Added ${X.length} step(s): ${X.join(", ")}`)}if(A?.removed?.length){let X=A.removed.map(re=>re.title);D.push(`Removed ${X.length} step(s): ${X.join(", ")}`)}if(A?.modified?.length){let X=A.modified.map(re=>re.title);D.push(`Modified ${X.length} step(s): ${X.join(", ")}`)}let z=D.length>0?D.join(". "):"No changes detected.";return p(JSON.stringify({plan:v,diff:A,message:`Plan modified. ${z}. Update mistflow.json with the new plan, then continue with mist_build (action: 'implement').`}))}let k=f?.trim()||void 0,I=i;if(!k&&i&&It in i&&(k=i[It]),i&&It in i){let{[It]:w,...v}=i;I=Object.keys(v).length>0?v:void 0}if(k&&(k=k.replace(/^Keep\s+/i,"").replace(/\s*\(Recommended\)\s*$/i,"").replace(/\.mistflow\.app.*$/i,"").trim()||void 0),k){let w=k.toLowerCase().replace(/\s+/g,"-");/^[a-z0-9][a-z0-9-]{1,30}[a-z0-9]$/.test(w)?k=w:(console.error(`[mist_plan] Discarding urlChoice '${k}' \u2014 does not look like a subdomain. Backend will auto-generate.`),k=void 0)}let B;try{B=await Ya(e,{conversationId:y,answers:I,autonomous:d,language:u})}catch(w){let v=w instanceof Error?w.message:"Failed to generate plan";return p(v,!0)}if(B.status==="clarify"){let w=B.reflection||"",v=B.suggestedName||"",A=B.suggestedFeatures??[],D=B.questions??[],z=D.some(ae=>Array.isArray(ae.options)&&typeof ae.options[0]=="object"&&ae.options[0]?.label),X={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",entityShape:"Item shape",coreView:"View",scope:"Scope",sharing:"Sharing",workflow:"Workflow",constraints:"Constraints",domain:"Product"},re=D.map(ae=>{let oa=ae.decisionKey&&X[ae.decisionKey]||Qi(ae.question),Re;return z&&Array.isArray(ae.options)?Re=ae.options.map(_e=>({label:_e.label,description:_e.description??""})):Array.isArray(ae.options)?Re=ae.options.map((_e,Ha)=>({label:Ha===0?`${_e} (Recommended)`:String(_e),description:ae.why??""})):Re=[{label:"Yes (Recommended)",description:ae.why??""},{label:"No",description:""}],{question:ae.question,header:oa,options:Re,multiSelect:!1}}),ra=B.decisions?.audienceType??null,kt=A.length>0?Rr(e,{primaryActor:null,primaryAction:null,surfaceType:null,audienceType:ra,multiRole:null,publicLanding:null,realMoney:null,scheduling:null,authModel:null,dbProvider:null,integrations:null},{suggestedName:v,suggestedFeatures:A,language:u}):null,we=kt?Nr(kt):"",ve=ba(v||"my-app").slice(0,32);try{let ae=await Va(ve);!ae.available&&ae.suggestion&&(ve=ae.suggestion)}catch{}we&&(we+=`
10232
+
10233
+ **Your app URL:** https://${ve}.mistflow.app`);let St={question:`Your app will be at ${ve}.mistflow.app \u2014 want to customize the URL?`,header:"URL",options:[{label:`Keep ${ve}.mistflow.app (Recommended)`,description:"This URL is available"},{label:"Choose a different URL",description:"Type your preferred subdomain"}],multiSelect:!1};return re.push(St),p(JSON.stringify({status:"clarify",conversation_id:B.conversation_id,questions:D,questionCount:D.length,suggestedFeatures:A,suggestedName:v,suggestedSubdomain:ve,reflection:w,briefText:we,askUserQuestions:re,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:[...T?[T,""]:[],w?`${w}
10234
+ `:"",we?`Here's what I'd build:
10235
+
10236
+ ${we}
10237
+ `:"",`I have ${D.length} quick question${D.length===1?"":"s"} to pin down the details.`,"","MANDATORY: Use the AskUserQuestion tool to present these questions. Do NOT present them as text.","","IF you have access to the AskUserQuestion tool (Claude Code):"," Use it to present questions with selectable options. Pass the 'askUserQuestions' array directly.","","OTHERWISE (Cursor, Codex, or other hosts):"," Present each question conversationally with the options listed.","","BEFORE calling mist_plan for the final plan generation, tell the user:"," 'Generating your plan now. This usually takes about 60\u201390 seconds \u2014 I'm designing the data model, laying out pages, and writing build steps.'","This primes the user for the silent wait. Do NOT call mist_plan without saying this first.","","Once you have all answers, call mist_plan again with:",` conversationId: "${B.conversation_id}"`,' answers: { "<question text>": "<user answer>", ... }'," description: (same description as before)",' urlChoice: "<the URL subdomain the user picked>" \u2190 pass as a top-level param, NOT inside answers',"","NOTE: You may receive another 'clarify' response with follow-up questions if the user's answers","revealed new ambiguity. This is normal \u2014 relay those questions too. Keep going until you get 'ready'.","","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: "${ve}".`,'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.'].join(`
10238
+ `)}))}let M=B.plan,C=M.name??"Untitled App",j=B.methodology,L=M.steps;if(!Array.isArray(L)||L.length===0)return p("Plan generation incomplete \u2014 the plan is missing implementation steps. Please call mist_plan again with the same description to retry.",!0);let G=M.publicPages;if(!G||Array.isArray(G)&&G.length===0){let w=M.pages,v=L.some(D=>typeof D.name=="string"&&D.name.toLowerCase().includes("landing")||typeof D.title=="string"&&D.title.toLowerCase().includes("landing")),A=Array.isArray(w)&&w.some(D=>D.path==="/"||D.route==="/");v||A?G=["/","/pricing"]:G=["/"]}let de=M.primaryAction;if(!de){let w=M.features;if(Array.isArray(w)&&w.length>0){let A=w.find(z=>typeof z.priority=="string"&&z.priority.toLowerCase()==="must-have")??w[0];de={entity:A.name??A.title??"item",action:"create",fromPage:"/dashboard"}}}let $=M.nonNegotiables;(!$||Array.isArray($)&&$.length===0)&&($=["Landing page renders correctly at / with content (not a redirect)","Core user action works end-to-end (create entity, see it in list)"]);let Q=Lr(),V=ye(Ee(),".mistflow","plans");ya(V,{recursive:!0});try{let v=Date.now();for(let A of[V,ye(Ee(),".mistflow","mockup-state")])if(ct(A))for(let D of Ui(A))try{let z=ye(A,D),X=Hi(z).mtimeMs;v-X>6048e5&&Oi(z)}catch{}}catch{}let F;if(h){let w=Je(h);w?F=w.id:console.error(`Landing design '${h}' not found \u2014 ignoring. Use mist_project action='landing-designs' to browse available landing designs.`)}let H;if(c){let w=be(c);w?H=w.id:console.error(`App style '${c}' not found \u2014 ignoring. Use mist_project action='app-styles' to browse available app styles.`)}let oe=!!F,he=L.some(w=>{let v=`${w.name??w.title} ${w.description??""}`.toLowerCase();return v.includes("landing")||v.includes("hero")||v.includes("marketing")||v.includes("homepage")}),Y=!oe&&he?Tr(e,{maxResults:2}):[];Y.length>0&&(F=Y[0].id,console.error(`Auto-assigned landing layout preset (default): ${Y[0].title} (${F})`));let pe={name:M.name,summary:M.summary,dataModel:M.dataModel,pages:M.pages,features:M.features,steps:L.map(w=>({...w,name:w.name??w.title})),design:M.design,landingDesign:F,appStyle:H,dbProvider:M.dbProvider??"neon",authModel:M.authModel,audienceType:M.audienceType??"b2c",roles:M.roles,defaultRole:M.defaultRole,publicPages:G,navStyle:M.navStyle,multiTenant:M.multiTenant,primaryAction:de,nonNegotiables:$,requestedSubdomain:k,...u&&u.toLowerCase()!=="english"?{language:u}:{}};xa(ye(V,`${Q}.json`),JSON.stringify({plan:pe,methodology:j}));let P=L.map(w=>`${w.number}. ${w.name??w.title}`),U="",O=[],x;!oe&&Y.length>0&&(O=Y.map(v=>({id:v.id,slug:v.slug,title:v.title,description:v.description,url:`${Ae()}/designs?tab=landing-designs`})),x={question:"What landing page style fits this app?",header:"Landing Design",options:[...Y.map((v,A)=>({label:A===0?`${v.title} (Recommended)`:v.title,description:v.description})),{label:"Design from scratch (Creative)",description:"No template \u2014 the AI designs a bespoke hero from your app's concept using 3D, scroll-driven animation, or particle effects. Higher creative ceiling, takes ~10 min longer, occasionally needs a second pass."},{label:"Browse all landing designs",description:`Not sure? See all landing designs at ${Ae()}/designs?tab=landing-designs and pass the ID back.`}],multiSelect:!1},U=` REQUIRED: ask the user which landing design they want using the AskUserQuestion tool with the 'landingDesignQuestion' object before calling mist_build init. Do NOT assume the recommended option \u2014 the user may want a different style than we inferred. Recommended: ${Y.map(v=>v.title).join(" or ")}. If they pick "Design from scratch", pass landingDesign='freeform' to mist_build init. If they pick a specific preset title, look up its ID via mist_project action='landing-designs' and pass that. If they pick the recommended option, pass landingDesign='${Y[0].id}' explicitly.`);let E="",q=[],ge;H||(q=Dr(e,{maxResults:3}).map(A=>({id:A.id,slug:Dt(A.name),name:A.name,description:A.description,theme:A.theme,url:`${Ae()}/designs/app-styles/${Dt(A.name)}`})),ge={question:"What design style should your app have? Choose a design system for consistent, brand-quality UI across all pages.",header:"App Design",options:[...q.map((A,D)=>({label:D===0?`${A.name} (Recommended)`:A.name,description:`${A.theme} theme \u2014 ${A.description} Preview: ${A.url}`})),{label:"See more app styles",description:`Not sure? Browse all 53 app styles at ${Ae()}/designs?tab=app-styles and pick one.`},{label:"Skip \u2014 use default styling",description:"Proceed without a design system"}],multiSelect:!1},E=` REQUIRED: ask the user which app style they want using the AskUserQuestion tool with the 'appStyleQuestion' object before calling mist_build init. Do NOT pick for them \u2014 design is a taste call and the user hasn't seen these options yet. Recommended: ${q.map(A=>`[${A.name}](${A.url})`).join(", ")}. In your message, say these are top picks based on their description and that if none feel right they can browse all 53 app styles at ${Ae()}/designs?tab=app-styles (include this link verbatim). Once the user picks, pass appStyle='<id>' to the mist_build init call. If user explicitly says "skip design", proceed without one.`);let N=(M.audienceType??"b2c")==="b2c",W={question:"Include a lifestyle photo in your landing page hero? Photos add warmth and human context; pure CSS stays cleaner.",header:"Hero",options:[{label:N?"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:N?"No, CSS only":"No, CSS only (Recommended)",description:"Animated gradients + glassmorphism, no photo \u2014 cleaner and more technical (like Stripe, Linear, Vercel)."}],multiSelect:!1},J=" Also ask the user about the 'heroPhotoQuestion' provided. Once they pick, pass heroPhoto=true (photo) or heroPhoto=false (CSS only) to the mist_build init call.",se="",R=[];for(let w of L){let v=w.name??w.title,A=w.integrationId;if(A){let D=Xe(A);if(D){let z=Ze(D.id);R.push({step:v,presetId:D.id,presetName:D.name,envVars:z?.envVars??[]})}}}if(R.length>0){let w=R.flatMap(D=>D.envVars),v=[...new Set(w.map(D=>D.key))];se=` This plan uses integrations (${R.map(D=>D.presetName).join(", ")}). Detailed blueprints will be auto-injected during each integration step.${v.length>0?` The user will need these API keys: ${v.join(", ")}.`:""}`}return p(JSON.stringify({planId:Q,name:M.name,summary:M.summary,stepCount:L.length,steps:P,design:M.design,...F?{landingDesign:F}:{},...H?{appStyle:H}:{},...q.length>0?{recommendedAppStyles:q}:{},...ge?{appStyleQuestion:ge}:{},...O.length>0?{recommendedLandingDesigns:O}:{},...x?{landingDesignQuestion:x}:{},heroPhotoQuestion:W,...R.length>0?{integrations:R.map(w=>({step:w.step,preset:w.presetId,name:w.presetName,envVars:w.envVars}))}:{},message:`Plan generated for "${C}" (${L.length} steps).${F?` Landing layout "${F}" set as default.`:""}${H?` App style "${H}" will be applied across all pages.`:""}${se}${E}${U}${J}`,timingContext:`Planning took ~90 seconds. Building will take roughly ${Math.max(15,L.length*3)}\u2013${L.length*5} minutes total across ${L.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_build with action='mockup' and planId='${Q}'. If the user says skip or "just build it", call mist_build with action='init', name='${C}', and planId='${Q}' immediately.`,...T?{warning:T}:{}}))}var Or={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').","","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). When that happens AND brandMentioned is not set, the handler 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 and confirmToken set to the token from the response.","\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_build (action: 'init') with the returned planId. Do not ask permission.","\u2022 NEVER skip the clarifying questions. The discovery process ensures the right thing gets built.","","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. Pass appStyle (53 full-app design systems like 'stripe', 'linear') to apply a design system. Browse via mist_project action='app-styles'. Landing layout presets are auto-assigned based on app description."].join(`
10239
+ `),inputSchema:Ji,handler:Zi};import{z as ne}from"zod";import{existsSync as _t,readFileSync as ys}from"fs";import{join as jt,resolve as xs}from"path";import{homedir as ws}from"os";import{execFileSync as Aa}from"child_process";import{z as Mt}from"zod";import{existsSync as Ce,mkdirSync as Nt,writeFileSync as Ue,readFileSync as Lt,readdirSync as en,copyFileSync as tn}from"fs";import{join as Z,resolve as Ft,dirname as ut,isAbsolute as va}from"path";import{spawn as an}from"child_process";import{randomBytes as rn}from"crypto";import{simpleGit as zr}from"simple-git";function pt(r,e,a,t=3e3){if(e===void 0)return{stop:()=>{}};let i=0,s=!1,n=setInterval(()=>{s||(i++,r.notification({method:"notifications/progress",params:{progressToken:e,progress:i,message:a()}}).catch(()=>{}))},t);return{stop:()=>{s||(s=!0,clearInterval(n))}}}function on(r){let e=ut(Ft(r)),a=10,t=0;for(;t<a&&e!==ut(e);){if(Ce(Z(e,"pnpm-workspace.yaml"))||Ce(Z(e,"lerna.json")))return e;let i=Z(e,"package.json");if(Ce(i))try{if(JSON.parse(Lt(i,"utf-8")).workspaces)return e}catch{}e=ut(e),t++}return null}function ht(r,e,a,t,i,s){return new Promise(o=>{let n=an(r,e,{cwd:a,stdio:["pipe","pipe","pipe"],timeout:t,env:s?{...process.env,...s}:process.env}),l="",d="";n.stdout?.on("data",u=>{let h=u.toString();if(d+=h,i)for(let c of h.split(`
10240
+ `).filter(Boolean))i(c)}),n.stderr?.on("data",u=>{let h=u.toString();if(l+=h,i)for(let c of h.split(`
10241
+ `).filter(Boolean))i(c)}),n.on("close",u=>{if(u===0)o({success:!0});else{let h=l.split(`
10242
+ `).find(c=>c.startsWith("npm error"))??l.slice(0,300);o({success:!1,error:h})}}),n.on("error",u=>{o({success:!1,error:u.message})})})}var zd=Mt.object({name:Mt.string().min(1),plan:Mt.any(),path:Mt.string().optional()});function _(r,e,a){let t=Z(r,e);Nt(ut(t),{recursive:!0}),Ue(t,a)}var Gr={amber:{primary:"25 95% 53%",primaryForeground:"0 0% 100%"},emerald:{primary:"160 84% 39%",primaryForeground:"0 0% 100%"},indigo:{primary:"239 84% 67%",primaryForeground:"0 0% 100%"},rose:{primary:"347 77% 50%",primaryForeground:"0 0% 100%"},cyan:{primary:"189 94% 43%",primaryForeground:"0 0% 100%"},violet:{primary:"263 70% 50%",primaryForeground:"0 0% 100%"},orange:{primary:"21 90% 48%",primaryForeground:"0 0% 100%"},teal:{primary:"168 76% 42%",primaryForeground:"0 0% 100%"},sky:{primary:"199 89% 48%",primaryForeground:"0 0% 100%"}},nn={sharp:"0.125rem",subtle:"0.375rem",rounded:"0.75rem",pill:"9999px"};function Et(r){let e=r.replace("#","");if(e.length!==6&&e.length!==3)return null;let a=e.length===3?e[0]+e[0]+e[1]+e[1]+e[2]+e[2]:e,t=parseInt(a,16);return isNaN(t)?null:{r:t>>16&255,g:t>>8&255,b:t&255}}function Ut(r,e,a){r/=255,e/=255,a/=255;let t=Math.max(r,e,a),i=Math.min(r,e,a),s=(t+i)/2;if(t===i)return{h:0,s:0,l:Math.round(s*100)};let o=t-i,n=s>.5?o/(2-t-i):o/(t+i),l=0;return t===r?l=((e-a)/o+(e<a?6:0))/6:t===e?l=((a-r)/o+2)/6:l=((r-e)/o+4)/6,{h:Math.round(l*360),s:Math.round(n*100),l:Math.round(s*100)}}function et(r){let e=Et(r);if(!e)return"0 0% 50%";let{h:a,s:t,l:i}=Ut(e.r,e.g,e.b);return`${a} ${t}% ${i}%`}function $r(r){let e=Et(r);return e?Ut(e.r,e.g,e.b).l:50}function Vr(r){let e=Et(r);return e?Ut(e.r,e.g,e.b).s:0}function sn(r){let e=be(r);if(!e)return null;let a=e.sections.find(g=>g.title.toLowerCase().includes("color palette"));if(!a)return null;let t=a.content,i={},s=t.split(/^### /m);for(let g of s){if(!g.trim())continue;let b=g.indexOf(`
10243
+ `);if(b===-1)continue;let f=g.slice(0,b).trim().toLowerCase();i[f]=g.slice(b)}let o=g=>g.match(/#[0-9a-fA-F]{6}\b/)?.[0],n,l=["interactive","accent","action","cta","brand"];for(let g of l){if(n)break;for(let b of Object.keys(i))if(b.includes(g)&&(n=o(i[b]),n))break}if(!n){for(let g of Object.keys(i))if(g.includes("secondary")&&(n=o(i[g]),n))break}let d;for(let g of Object.keys(i))if((g.includes("surface")||g.includes("background")||g.includes("canvas"))&&(d=o(i[g]),d))break;let u;for(let g of Object.keys(i))if((g==="primary"||g.includes("text")||g.includes("neutral"))&&(u=o(i[g]),u))break;let h;for(let g of Object.keys(i))if(g.includes("neutral")||g.includes("muted")||g.includes("tertiary")){let f=(i[g].match(/#[0-9a-fA-F]{6}\b/g)??[]).map(y=>({hex:y,l:$r(y)})).sort((y,T)=>y.l-T.l),m=f[Math.floor(f.length/2)];if(m&&(h=m.hex),h)break}let c;for(let g of Object.keys(i))if((g.includes("border")||g.includes("divider")||g.includes("separator"))&&(c=o(i[g]),c))break;if(!c){let g=t.match(/[Bb]order[^`]*`(#[0-9a-fA-F]{6})`/);g&&(c=g[1])}return n&&Vr(n)<10&&(n=void 0),!n&&!d&&!u?null:{background:d,foreground:u,accent:n,muted:h,border:c}}function ln(r,e){let a=sn(e),t=a?.accent??r.colors.find(f=>Vr(f)>20)??r.colors[0]??"#6366f1",i=et(t),s=$r(t),o=a?.background??(r.theme==="light"?"#ffffff":"#0a0a0a"),n=Et(o),l=n?Ut(n.r,n.g,n.b):{h:0,s:0,l:100},d=l.s>3?l.h:0,u=Math.min(l.s,15),h=a?.foreground??"#0f172a",c=a?.muted,g=a?.border,b=Math.min(u,10);return{light:{background:`${d} ${u}% 99%`,foreground:et(h),card:`${d} ${u}% 100%`,cardForeground:et(h),muted:c?et(c):`${d} ${Math.min(u,10)}% 96%`,mutedForeground:`${d} ${Math.min(u,8)}% 45%`,border:g?et(g):`${d} ${Math.min(u,10)}% 90%`,input:g?et(g):`${d} ${Math.min(u,10)}% 90%`,ring:i},dark:{background:`${d} ${b}% 4%`,foreground:"0 0% 95%",card:`${d} ${b}% 6%`,cardForeground:"0 0% 95%",muted:`${d} ${Math.min(b,6)}% 16%`,mutedForeground:`${d} ${Math.min(b,6)}% 65%`,border:`${d} ${Math.min(b,6)}% 16%`,input:`${d} ${Math.min(b,6)}% 16%`,ring:i},primary:i,primaryForeground:s>60?"0 0% 5%":"0 0% 100%"}}function ee(r){let e=r.trim().split(/\s+/),a=parseFloat(e[0]),t=parseFloat(e[1])/100,i=parseFloat(e[2])/100,s=t*Math.min(i,1-i),o=L=>{let G=(L+a/30)%12;return i-s*Math.max(-1,Math.min(G-3,9-G,1))},n=o(0),l=o(8),d=o(4),u=L=>L<=.04045?L/12.92:Math.pow((L+.055)/1.055,2.4),h=u(n),c=u(l),g=u(d),b=Math.cbrt(.4122214708*h+.5363325363*c+.0514459929*g),f=Math.cbrt(.2119034982*h+.6806995451*c+.1073969566*g),m=Math.cbrt(.0883024619*h+.2817188376*c+.6299787005*g),y=.2104542553*b+.793617785*f-.0040720468*m,T=1.9779984951*b-2.428592205*f+.4505937099*m,k=.0259040371*b+.7827717662*f-.808675766*m,I=Math.sqrt(T*T+k*k),B=Math.atan2(k,T)*180/Math.PI;B<0&&(B+=360);let M=y.toFixed(4),C=I.toFixed(4),j=I<1e-4?"0":B.toFixed(2);return`${M} ${C} ${j}`}function dn(r,e){let a=r?.borderRadius??"subtle",t=nn[a]??"0.375rem",i,s,o,n,l=e?De(e):void 0,d=!1;if(l){let c=ln(l,e);d=l.theme==="dark",i=d?c.dark:c.light,s=d?c.light:c.dark,o=c.primary,n=c.primaryForeground}else{let c=r?.accentColor??"indigo",g=Gr[c]??Gr.indigo;i={background:"0 0% 100%",foreground:"240 10% 4%",card:"0 0% 100%",cardForeground:"240 10% 4%",muted:"240 5% 96%",mutedForeground:"240 4% 46%",border:"240 6% 90%",input:"240 6% 90%",ring:"240 5% 65%"},s={background:"240 10% 4%",foreground:"0 0% 95%",card:"240 10% 6%",cardForeground:"0 0% 95%",muted:"240 4% 16%",mutedForeground:"240 5% 65%",border:"240 4% 16%",input:"240 4% 16%",ring:"240 5% 65%"},o=g.primary,n=g.primaryForeground}let u=[...Object.entries(i).map(([c,g])=>` --color-${Wr(c)}: oklch(${ee(g)});`),` --color-primary: oklch(${ee(o)});`,` --color-primary-foreground: oklch(${ee(n)});`,` --color-secondary: oklch(${ee(i.muted)});`,` --color-secondary-foreground: oklch(${ee(i.foreground)});`,` --color-accent: oklch(${ee(i.muted)});`,` --color-accent-foreground: oklch(${ee(i.foreground)});`,` --color-destructive: oklch(${ee(d?"0 72% 51%":"0 84% 60%")});`,` --color-destructive-foreground: oklch(${ee("0 0% 100%")});`,` --color-popover: oklch(${ee(i.card)});`,` --color-popover-foreground: oklch(${ee(i.cardForeground)});`," --radius-sm: 0.25rem;",` --radius-md: ${t};`," --radius-lg: 0.5rem;"," --radius-xl: 0.75rem;"].join(`
10244
10244
  `),h="";return l||(h=`
10245
10245
 
10246
10246
  @media (prefers-color-scheme: dark) {
10247
10247
  :root {
10248
- ${[...Object.entries(n).map(([g,b])=>` --color-${Or(g)}: oklch(${Y(b)});`),` --color-primary: oklch(${Y(o)});`,` --color-primary-foreground: oklch(${Y(s)});`,` --color-secondary: oklch(${Y(n.muted)});`,` --color-secondary-foreground: oklch(${Y(n.foreground)});`,` --color-accent: oklch(${Y(n.muted)});`,` --color-accent-foreground: oklch(${Y(n.foreground)});`,` --color-destructive: oklch(${Y("0 72% 51%")});`,` --color-destructive-foreground: oklch(${Y("0 0% 100%")});`,` --color-popover: oklch(${Y(n.card)});`,` --color-popover-foreground: oklch(${Y(n.cardForeground)});`].join(`
10248
+ ${[...Object.entries(s).map(([g,b])=>` --color-${Wr(g)}: oklch(${ee(b)});`),` --color-primary: oklch(${ee(o)});`,` --color-primary-foreground: oklch(${ee(n)});`,` --color-secondary: oklch(${ee(s.muted)});`,` --color-secondary-foreground: oklch(${ee(s.foreground)});`,` --color-accent: oklch(${ee(s.muted)});`,` --color-accent-foreground: oklch(${ee(s.foreground)});`,` --color-destructive: oklch(${ee("0 72% 51%")});`,` --color-destructive-foreground: oklch(${ee("0 0% 100%")});`,` --color-popover: oklch(${ee(s.card)});`,` --color-popover-foreground: oklch(${ee(s.cardForeground)});`].join(`
10249
10249
  `)}
10250
10250
  }
10251
10251
  }`),`@import "tailwindcss";
@@ -10316,14 +10316,14 @@ button, [role="button"] {
10316
10316
  button:active:not(:disabled), [role="button"]:active:not(:disabled) {
10317
10317
  transform: scale(0.97);
10318
10318
  }
10319
- `}function Or(r){return r.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`)}var Gr={"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 Wr(r){let e=r.replace(/[^A-Za-z0-9_ -]/g,"");return Gr[e]?Gr[e]:e.replace(/\s+/g,"_")}function en(r){return r?{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"}[r.toLowerCase()]??"en":"en"}var tn=new Set(["ar","he","fa","ur"]);function an(r,e,t){let a=r.replace(/[\\"`$]/g,""),i=en(t),o=tn.has(i)?`lang="${i}" dir="rtl"`:`lang="${i}"`,s=e?.fonts?.heading,l=e?.fonts?.body;if(!s&&!l)return`import type { Metadata } from "next";
10319
+ `}function Wr(r){return r.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`)}var _r={"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 jr(r){let e=r.replace(/[^A-Za-z0-9_ -]/g,"");return _r[e]?_r[e]:e.replace(/\s+/g,"_")}function cn(r){return r?{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"}[r.toLowerCase()]??"en":"en"}var pn=new Set(["ar","he","fa","ur"]);function un(r,e,a){let t=r.replace(/[\\"`$]/g,""),i=cn(a),o=pn.has(i)?`lang="${i}" dir="rtl"`:`lang="${i}"`,n=e?.fonts?.heading,l=e?.fonts?.body;if(!n&&!l)return`import type { Metadata } from "next";
10320
10320
  import { DM_Sans } from "next/font/google";
10321
10321
  import { Toaster } from "sonner";
10322
10322
  import "./globals.css";
10323
10323
 
10324
10324
  const body = DM_Sans({ subsets: ["latin"], variable: "--font-body" });
10325
10325
 
10326
- export const metadata: Metadata = { title: "${a}", description: "Built with Mistflow" };
10326
+ export const metadata: Metadata = { title: "${t}", description: "Built with Mistflow" };
10327
10327
 
10328
10328
  export default function RootLayout({ children }: { children: React.ReactNode }) {
10329
10329
  return (
@@ -10332,14 +10332,14 @@ export default function RootLayout({ children }: { children: React.ReactNode })
10332
10332
  </html>
10333
10333
  );
10334
10334
  }
10335
- `;let d=Wr(s??l),u=Wr(l??s);return d===u?`import type { Metadata } from "next";
10335
+ `;let d=jr(n??l),u=jr(l??n);return d===u?`import type { Metadata } from "next";
10336
10336
  import { ${d} } from "next/font/google";
10337
10337
  import { Toaster } from "sonner";
10338
10338
  import "./globals.css";
10339
10339
 
10340
10340
  const font = ${d}({ subsets: ["latin"], variable: "--font-body" });
10341
10341
 
10342
- export const metadata: Metadata = { title: "${a}", description: "Built with Mistflow" };
10342
+ export const metadata: Metadata = { title: "${t}", description: "Built with Mistflow" };
10343
10343
 
10344
10344
  export default function RootLayout({ children }: { children: React.ReactNode }) {
10345
10345
  return (
@@ -10356,7 +10356,7 @@ import "./globals.css";
10356
10356
  const heading = ${d}({ subsets: ["latin"], variable: "--font-heading" });
10357
10357
  const body = ${u}({ subsets: ["latin"], variable: "--font-body" });
10358
10358
 
10359
- export const metadata: Metadata = { title: "${a}", description: "Built with Mistflow" };
10359
+ export const metadata: Metadata = { title: "${t}", description: "Built with Mistflow" };
10360
10360
 
10361
10361
  export default function RootLayout({ children }: { children: React.ReactNode }) {
10362
10362
  return (
@@ -10365,69 +10365,70 @@ export default function RootLayout({ children }: { children: React.ReactNode })
10365
10365
  </html>
10366
10366
  );
10367
10367
  }
10368
- `}function Lt(r,...e){let t=JSON.stringify(r).toLowerCase();return e.some(a=>t.includes(a.toLowerCase()))}var rn={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 ba(r){let e=r.toLowerCase().replace(/[^a-z]/g,"");for(let[t,a]of Object.entries(rn))if(e.includes(t))return a;return"Circle"}function mt(r){return r.split("-").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" ")}function on(r){if(r.authModel==="none")return null;let e=["/login","/register","/api/auth","/api/health","/api/webhooks","/api/admin/seed"];if(r.publicPages&&Array.isArray(r.publicPages))for(let n of r.publicPages){if(typeof n!="string"||n.length<1)continue;let o=n.replace(/[\u201C\u201D\u201E\u201F\u2018\u2019\u2033\u2036]/g,"").trim();if(!o)continue;let s=o.startsWith("/")?o:"/"+o;e.includes(s)||e.push(s)}let t=e.filter(n=>n==="/"),a=e.filter(n=>n!=="/"),i=[];i.push('import { NextRequest, NextResponse } from "next/server";'),i.push(""),i.push("const PUBLIC_PREFIXES = [");for(let n of a)i.push(' "'+n+'",');return i.push("];"),i.push(""),t.length>0&&(i.push('const PUBLIC_EXACT = ["'+t.join('", "')+'"];'),i.push("")),i.push("export function middleware(req: NextRequest) {"),i.push(" const { pathname } = req.nextUrl;"),i.push(""),t.length>0&&i.push(" if (PUBLIC_EXACT.includes(pathname)) return NextResponse.next();"),i.push(" if (PUBLIC_PREFIXES.some((p) => pathname.startsWith(p))) return NextResponse.next();"),i.push(""),i.push(' const token = req.cookies.get("better-auth.session_token")?.value || req.cookies.get("__Secure-better-auth.session_token")?.value;'),i.push(" if (!token) {"),i.push(' return NextResponse.redirect(new URL("/login", req.url));'),i.push(" }"),i.push(""),i.push(" return NextResponse.next();"),i.push("}"),i.push(""),i.push("export const config = {"),i.push(' matcher: ["/((?!_next|static|favicon\\\\.ico).*)"],'),i.push("};"),i.push(""),i.join(`
10369
- `)}function nn(r){if(r.navStyle==="none")return null;let e=["/api","/login","/register","/sign-in","/sign-up","/admin"],a=(r.pages??[]).filter(l=>{let d=l.path??l.route??"";return d==="/"||d===""||d.includes("[")||d.replace(/^\//,"").split("/").length>1?!1:!e.some(h=>d.startsWith(h))}).map(l=>{let d=l.path??l.route??"",u=d.startsWith("/")?d:"/"+d,h=l.name??mt(d.replace(/^\//,"")),c=ba(h);return{label:h,href:u,icon:c}});a.some(l=>l.href==="/dashboard")||a.unshift({label:"Dashboard",href:"/dashboard",icon:"Home"});let i=r.authModel==="none",n=[...new Set(a.map(l=>l.icon))];i||n.push("LogOut");let o=mt(r.name);if(r.navStyle==="topbar"){let l=[];l.push('"use client";'),l.push(""),l.push('import Link from "next/link";'),l.push('import { usePathname } from "next/navigation";'),i||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 { "+n.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 d of a)l.push(' { label: "'+d.label+'", href: "'+d.href+'", icon: '+d.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">'+o+"</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>"),i?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(`
10370
- `)}}let s=[];s.push('"use client";'),s.push(""),s.push('import { useState } from "react";'),s.push('import Link from "next/link";'),s.push('import { usePathname } from "next/navigation";'),i||s.push('import { authClient } from "@/lib/auth-client";'),s.push('import { Button } from "@/components/ui/button";'),s.push('import { Sheet, SheetContent, SheetTrigger, SheetTitle } from "@/components/ui/sheet";'),s.push('import { cn } from "@/lib/utils";'),s.push("import { Menu, "+n.join(", ")+' } from "lucide-react";'),s.push(""),s.push("interface SidebarProps {"),s.push(" user: { name: string | null; email: string; role?: string | undefined };"),s.push("}"),s.push(""),s.push("const NAV_ITEMS = [");for(let l of a)s.push(' { label: "'+l.label+'", href: "'+l.href+'", icon: '+l.icon+" },");return s.push("];"),s.push(""),s.push('function NavContent({ pathname, user, onNavigate }: { pathname: string; user: SidebarProps["user"]; onNavigate?: () => void }) {'),s.push(" return ("),s.push(" <>"),s.push(' <div className="flex h-14 items-center border-b px-4">'),s.push(' <span className="text-lg font-semibold">'+o+"</span>"),s.push(" </div>"),s.push(' <nav className="flex-1 space-y-1 p-2">'),s.push(" {NAV_ITEMS.map((item) => ("),s.push(" <Link"),s.push(" key={item.href}"),s.push(" href={item.href}"),s.push(" onClick={onNavigate}"),s.push(' aria-current={pathname === item.href ? "page" : undefined}'),s.push(" className={cn("),s.push(' "flex items-center gap-3 rounded-md px-3 py-2.5 text-sm font-medium transition-colors",'),s.push(' pathname === item.href ? "bg-primary/10 text-primary" : "text-muted-foreground hover:bg-muted hover:text-foreground"'),s.push(" )}"),s.push(" >"),s.push(' <item.icon className="h-4 w-4" />'),s.push(" {item.label}"),s.push(" </Link>"),s.push(" ))}"),s.push(" </nav>"),s.push(' <div className="border-t p-4">'),s.push(' <div className="flex items-center gap-3">'),s.push(' <div className="flex-1 truncate">'),s.push(' <p className="truncate text-sm font-medium">{user.name ?? "User"}</p>'),s.push(' <p className="truncate text-xs text-muted-foreground">{user.email}</p>'),s.push(" </div>"),i||(s.push(" <Button"),s.push(' variant="ghost"'),s.push(' size="icon"'),s.push(' onClick={() => authClient.signOut({ fetchOptions: { onSuccess: () => { window.location.href = "/login"; } } })}'),s.push(" >"),s.push(' <LogOut className="h-4 w-4" />'),s.push(" </Button>")),s.push(" </div>"),s.push(" </div>"),s.push(" </>"),s.push(" );"),s.push("}"),s.push(""),s.push("export default function Sidebar({ user }: SidebarProps) {"),s.push(" const pathname = usePathname();"),s.push(" const [open, setOpen] = useState(false);"),s.push(""),s.push(" return ("),s.push(" <>"),s.push(' <aside className="hidden md:flex h-screen w-64 flex-col border-r bg-card">'),s.push(" <NavContent pathname={pathname} user={user} />"),s.push(" </aside>"),s.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">'),s.push(" <Sheet open={open} onOpenChange={setOpen}>"),s.push(" <SheetTrigger asChild>"),s.push(' <Button variant="ghost" size="icon" className="-ml-2">'),s.push(' <Menu className="h-5 w-5" />'),s.push(" </Button>"),s.push(" </SheetTrigger>"),s.push(' <SheetContent side="left" className="w-64 p-0">'),s.push(' <SheetTitle className="sr-only">Navigation</SheetTitle>'),s.push(' <div className="flex h-full flex-col">'),s.push(" <NavContent pathname={pathname} user={user} onNavigate={() => setOpen(false)} />"),s.push(" </div>"),s.push(" </SheetContent>"),s.push(" </Sheet>"),s.push(' <span className="text-lg font-semibold">'+o+"</span>"),s.push(" </div>"),s.push(" </>"),s.push(" );"),s.push("}"),s.push(""),{path:"components/sidebar.tsx",content:s.join(`
10371
- `)}}function sn(r){if(!r.roles||r.roles.length===0)return null;let e=r.roles,t=r.defaultRole??e[0],a=[];a.push("export type Role = "+e.map(i=>'"'+i+'"').join(" | ")+";"),a.push(""),a.push("export const ROLES = ["+e.map(i=>'"'+i+'"').join(", ")+"] as const;"),a.push(""),a.push('export const DEFAULT_ROLE: Role = "'+t+'";'),a.push(""),a.push("export const ROLE_LABELS: Record<Role, string> = {");for(let i of e){let n=i.charAt(0).toUpperCase()+i.slice(1);a.push(' "'+i+'": "'+n+'",')}return a.push("};"),a.push(""),a.push("export function getUserRole(user: Record<string, unknown>): Role {"),a.push(" const role = (user.role as string) ?? DEFAULT_ROLE;"),a.push(" if (ROLES.includes(role as Role)) return role as Role;"),a.push(" return DEFAULT_ROLE;"),a.push("}"),a.push(""),a.push("export function hasRole(userRole: string | undefined, required: Role | Role[]): boolean {"),a.push(" if (!userRole) return false;"),a.push(" const allowed = Array.isArray(required) ? required : [required];"),a.push(" return allowed.includes(userRole as Role);"),a.push("}"),a.push(""),a.join(`
10372
- `)}function ln(r){let e=mt(r.name);if(r.authModel==="none"){let n=[];return n.push("export default function HomePage() {"),n.push(" return ("),n.push(' <main className="flex min-h-screen flex-col items-center justify-center p-8">'),n.push(' <h1 className="text-4xl font-bold">'+e+"</h1>"),r.summary&&n.push(' <p className="mt-4 text-lg text-muted-foreground">'+r.summary+"</p>"),n.push(" </main>"),n.push(" );"),n.push("}"),n.push(""),n.join(`
10373
- `)}let t=r.publicPages?.includes("/"),a=r.design?.landingTone;if(t&&a){let n=[];return n.push('import Link from "next/link";'),n.push(""),n.push("export default function HomePage() {"),n.push(" return ("),n.push(' <main className="flex min-h-screen flex-col">'),n.push(' <section className="flex flex-1 flex-col items-center justify-center gap-6 px-4 py-24 text-center">'),n.push(' <h1 className="text-5xl font-bold tracking-tight">'+e+"</h1>"),r.summary&&n.push(' <p className="max-w-2xl text-xl text-muted-foreground">'+r.summary+"</p>"),n.push(' <div className="flex gap-4">'),n.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">'),n.push(" Get Started"),n.push(" </Link>"),n.push(' <Link href="/login" className="inline-flex h-11 items-center rounded-md border px-8 text-sm font-medium hover:bg-muted">'),n.push(" Sign In"),n.push(" </Link>"),n.push(" </div>"),n.push(" </section>"),n.push(" </main>"),n.push(" );"),n.push("}"),n.push(""),n.join(`
10374
- `)}if(t){let n=[];return n.push('import Link from "next/link";'),n.push(""),n.push("export default function HomePage() {"),n.push(" return ("),n.push(' <main className="flex min-h-screen flex-col items-center justify-center gap-6 p-8 text-center">'),n.push(' <h1 className="text-4xl font-bold">'+e+"</h1>"),r.summary&&n.push(' <p className="text-lg text-muted-foreground">'+r.summary+"</p>"),n.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">'),n.push(" Sign In"),n.push(" </Link>"),n.push(" </main>"),n.push(" );"),n.push("}"),n.push(""),n.join(`
10368
+ `}function Rt(r,...e){let a=JSON.stringify(r).toLowerCase();return e.some(t=>a.includes(t.toLowerCase()))}var hn={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 wa(r){let e=r.toLowerCase().replace(/[^a-z]/g,"");for(let[a,t]of Object.entries(hn))if(e.includes(a))return t;return"Circle"}function gt(r){return r.split("-").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" ")}function gn(r){if(r.authModel==="none")return null;let e=["/login","/register","/api/auth","/api/health","/api/webhooks","/api/admin/seed"];if(r.publicPages&&Array.isArray(r.publicPages))for(let s of r.publicPages){if(typeof s!="string"||s.length<1)continue;let o=s.replace(/[\u201C\u201D\u201E\u201F\u2018\u2019\u2033\u2036]/g,"").trim();if(!o)continue;let n=o.startsWith("/")?o:"/"+o;e.includes(n)||e.push(n)}let a=e.filter(s=>s==="/"),t=e.filter(s=>s!=="/"),i=[];i.push('import { NextRequest, NextResponse } from "next/server";'),i.push(""),i.push("const PUBLIC_PREFIXES = [");for(let s of t)i.push(' "'+s+'",');return i.push("];"),i.push(""),a.length>0&&(i.push('const PUBLIC_EXACT = ["'+a.join('", "')+'"];'),i.push("")),i.push("export function middleware(req: NextRequest) {"),i.push(" const { pathname } = req.nextUrl;"),i.push(""),a.length>0&&i.push(" if (PUBLIC_EXACT.includes(pathname)) return NextResponse.next();"),i.push(" if (PUBLIC_PREFIXES.some((p) => pathname.startsWith(p))) return NextResponse.next();"),i.push(""),i.push(' const token = req.cookies.get("better-auth.session_token")?.value || req.cookies.get("__Secure-better-auth.session_token")?.value;'),i.push(" if (!token) {"),i.push(' return NextResponse.redirect(new URL("/login", req.url));'),i.push(" }"),i.push(""),i.push(" return NextResponse.next();"),i.push("}"),i.push(""),i.push("export const config = {"),i.push(' matcher: ["/((?!_next|static|favicon\\\\.ico).*)"],'),i.push("};"),i.push(""),i.join(`
10369
+ `)}function mn(r){if(r.navStyle==="none")return null;let e=["/api","/login","/register","/sign-in","/sign-up","/admin"],t=(r.pages??[]).filter(l=>{let d=l.path??l.route??"";return d==="/"||d===""||d.includes("[")||d.replace(/^\//,"").split("/").length>1?!1:!e.some(h=>d.startsWith(h))}).map(l=>{let d=l.path??l.route??"",u=d.startsWith("/")?d:"/"+d,h=l.name??gt(d.replace(/^\//,"")),c=wa(h);return{label:h,href:u,icon:c}});t.some(l=>l.href==="/dashboard")||t.unshift({label:"Dashboard",href:"/dashboard",icon:"Home"});let i=r.authModel==="none",s=[...new Set(t.map(l=>l.icon))];i||s.push("LogOut");let o=gt(r.name);if(r.navStyle==="topbar"){let l=[];l.push('"use client";'),l.push(""),l.push('import Link from "next/link";'),l.push('import { usePathname } from "next/navigation";'),i||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 d of t)l.push(' { label: "'+d.label+'", href: "'+d.href+'", icon: '+d.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">'+o+"</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>"),i?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(`
10370
+ `)}}let n=[];n.push('"use client";'),n.push(""),n.push('import { useState } from "react";'),n.push('import Link from "next/link";'),n.push('import { usePathname } from "next/navigation";'),i||n.push('import { authClient } from "@/lib/auth-client";'),n.push('import { Button } from "@/components/ui/button";'),n.push('import { Sheet, SheetContent, SheetTrigger, SheetTitle } from "@/components/ui/sheet";'),n.push('import { cn } from "@/lib/utils";'),n.push("import { Menu, "+s.join(", ")+' } from "lucide-react";'),n.push(""),n.push("interface SidebarProps {"),n.push(" user: { name: string | null; email: string; role?: string | undefined };"),n.push("}"),n.push(""),n.push("const NAV_ITEMS = [");for(let l of t)n.push(' { label: "'+l.label+'", href: "'+l.href+'", icon: '+l.icon+" },");return n.push("];"),n.push(""),n.push('function NavContent({ pathname, user, onNavigate }: { pathname: string; user: SidebarProps["user"]; onNavigate?: () => void }) {'),n.push(" return ("),n.push(" <>"),n.push(' <div className="flex h-14 items-center border-b px-4">'),n.push(' <span className="text-lg font-semibold">'+o+"</span>"),n.push(" </div>"),n.push(' <nav className="flex-1 space-y-1 p-2">'),n.push(" {NAV_ITEMS.map((item) => ("),n.push(" <Link"),n.push(" key={item.href}"),n.push(" href={item.href}"),n.push(" onClick={onNavigate}"),n.push(' aria-current={pathname === item.href ? "page" : undefined}'),n.push(" className={cn("),n.push(' "flex items-center gap-3 rounded-md px-3 py-2.5 text-sm font-medium transition-colors",'),n.push(' pathname === item.href ? "bg-primary/10 text-primary" : "text-muted-foreground hover:bg-muted hover:text-foreground"'),n.push(" )}"),n.push(" >"),n.push(' <item.icon className="h-4 w-4" />'),n.push(" {item.label}"),n.push(" </Link>"),n.push(" ))}"),n.push(" </nav>"),n.push(' <div className="border-t p-4">'),n.push(' <div className="flex items-center gap-3">'),n.push(' <div className="flex-1 truncate">'),n.push(' <p className="truncate text-sm font-medium">{user.name ?? "User"}</p>'),n.push(' <p className="truncate text-xs text-muted-foreground">{user.email}</p>'),n.push(" </div>"),i||(n.push(" <Button"),n.push(' variant="ghost"'),n.push(' size="icon"'),n.push(' onClick={() => authClient.signOut({ fetchOptions: { onSuccess: () => { window.location.href = "/login"; } } })}'),n.push(" >"),n.push(' <LogOut className="h-4 w-4" />'),n.push(" </Button>")),n.push(" </div>"),n.push(" </div>"),n.push(" </>"),n.push(" );"),n.push("}"),n.push(""),n.push("export default function Sidebar({ user }: SidebarProps) {"),n.push(" const pathname = usePathname();"),n.push(" const [open, setOpen] = useState(false);"),n.push(""),n.push(" return ("),n.push(" <>"),n.push(' <aside className="hidden md:flex h-screen w-64 flex-col border-r bg-card">'),n.push(" <NavContent pathname={pathname} user={user} />"),n.push(" </aside>"),n.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">'),n.push(" <Sheet open={open} onOpenChange={setOpen}>"),n.push(" <SheetTrigger asChild>"),n.push(' <Button variant="ghost" size="icon" className="-ml-2">'),n.push(' <Menu className="h-5 w-5" />'),n.push(" </Button>"),n.push(" </SheetTrigger>"),n.push(' <SheetContent side="left" className="w-64 p-0">'),n.push(' <SheetTitle className="sr-only">Navigation</SheetTitle>'),n.push(' <div className="flex h-full flex-col">'),n.push(" <NavContent pathname={pathname} user={user} onNavigate={() => setOpen(false)} />"),n.push(" </div>"),n.push(" </SheetContent>"),n.push(" </Sheet>"),n.push(' <span className="text-lg font-semibold">'+o+"</span>"),n.push(" </div>"),n.push(" </>"),n.push(" );"),n.push("}"),n.push(""),{path:"components/sidebar.tsx",content:n.join(`
10371
+ `)}}function fn(r){if(!r.roles||r.roles.length===0)return null;let e=r.roles,a=r.defaultRole??e[0],t=[];t.push("export type Role = "+e.map(i=>'"'+i+'"').join(" | ")+";"),t.push(""),t.push("export const ROLES = ["+e.map(i=>'"'+i+'"').join(", ")+"] as const;"),t.push(""),t.push('export const DEFAULT_ROLE: Role = "'+a+'";'),t.push(""),t.push("export const ROLE_LABELS: Record<Role, string> = {");for(let i of e){let s=i.charAt(0).toUpperCase()+i.slice(1);t.push(' "'+i+'": "'+s+'",')}return t.push("};"),t.push(""),t.push("export function getUserRole(user: Record<string, unknown>): Role {"),t.push(" const role = (user.role as string) ?? DEFAULT_ROLE;"),t.push(" if (ROLES.includes(role as Role)) return role as Role;"),t.push(" return DEFAULT_ROLE;"),t.push("}"),t.push(""),t.push("export function hasRole(userRole: string | undefined, required: Role | Role[]): boolean {"),t.push(" if (!userRole) return false;"),t.push(" const allowed = Array.isArray(required) ? required : [required];"),t.push(" return allowed.includes(userRole as Role);"),t.push("}"),t.push(""),t.join(`
10372
+ `)}function bn(r){let e=gt(r.name);if(r.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">'+e+"</h1>"),r.summary&&s.push(' <p className="mt-4 text-lg text-muted-foreground">'+r.summary+"</p>"),s.push(" </main>"),s.push(" );"),s.push("}"),s.push(""),s.join(`
10373
+ `)}let a=r.publicPages?.includes("/"),t=r.design?.landingTone;if(a&&t){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">'+e+"</h1>"),r.summary&&s.push(' <p className="max-w-2xl text-xl text-muted-foreground">'+r.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(`
10374
+ `)}if(a){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">'+e+"</h1>"),r.summary&&s.push(' <p className="text-lg text-muted-foreground">'+r.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(`
10375
10375
  `)}let i=[];return i.push('import { headers } from "next/headers";'),i.push('import { redirect } from "next/navigation";'),i.push('import { auth } from "@/lib/auth";'),i.push(""),i.push("export default async function HomePage() {"),i.push(" const session = await auth.api.getSession({ headers: await headers() });"),i.push(' if (session) redirect("/dashboard");'),i.push(' redirect("/login");'),i.push("}"),i.push(""),i.join(`
10376
- `)}function dn(r,e){let t=r.authModel==="none",a=[];return t||(a.push('import { headers } from "next/headers";'),a.push('import { redirect } from "next/navigation";'),a.push('import { auth } from "@/lib/auth";')),r.navStyle==="topbar"?a.push('import TopNav from "@/components/topnav";'):r.navStyle!=="none"&&a.push('import Sidebar from "@/components/sidebar";'),!t&&r.roles&&r.roles.length>0&&a.push('import { getUserRole } from "@/lib/roles";'),a.push(""),a.push("export default async function DashboardLayout({ children }: { children: React.ReactNode }) {"),t?a.push(' const user = { name: "Guest", email: "" };'):(a.push(" const session = await auth.api.getSession({ headers: await headers() });"),a.push(' if (!session) redirect("/login");'),a.push(""),r.roles&&r.roles.length>0?(a.push(" const role = getUserRole(session.user as Record<string, unknown>);"),a.push(" const user = { name: session.user.name, email: session.user.email, role };")):(a.push(" const user = {"),a.push(" name: session.user.name,"),a.push(" email: session.user.email,"),a.push(" role: (session.user as Record<string, unknown>).role as string | undefined,"),a.push(" };"))),a.push(""),r.navStyle==="topbar"?(a.push(" return ("),a.push(' <div className="min-h-screen">'),a.push(" <TopNav user={user} />"),a.push(' <main className="mx-auto max-w-7xl p-6">{children}</main>'),a.push(" </div>"),a.push(" );")):r.navStyle==="none"?(a.push(" return ("),a.push(' <div className="min-h-screen">'),a.push(' <main className="mx-auto max-w-5xl p-6">{children}</main>'),a.push(" </div>"),a.push(" );")):(a.push(" return ("),a.push(' <div className="flex flex-col md:flex-row min-h-screen">'),a.push(" <Sidebar user={user} />"),a.push(' <main className="flex-1 overflow-x-hidden p-4 md:p-6">{children}</main>'),a.push(" </div>"),a.push(" );")),a.push("}"),a.push(""),a.join(`
10377
- `)}function cn(r){let e=mt(r.name),t=r.dataModel??[],a=[];if(t.length>0){let i=t.map(o=>ba(o.entity??o.name??"item")),n=[...new Set(i)];a.push('import { Card, CardContent } from "@/components/ui/card";'),a.push("import { "+n.join(", ")+' } from "lucide-react";'),a.push("")}if(a.push("export default function DashboardPage() {"),a.push(" return ("),a.push(' <div className="space-y-6">'),a.push(" <div>"),a.push(' <h1 className="text-3xl font-bold">'+e+"</h1>"),r.summary&&a.push(' <p className="mt-1 text-muted-foreground">'+r.summary+"</p>"),a.push(" </div>"),t.length>0){a.push(' <div className="rounded-lg border p-8 text-center">'),a.push(' <h2 className="text-lg font-semibold">Get started</h2>'),a.push(` <p className="mt-1 text-sm text-muted-foreground">Here's what you can do</p>`),a.push(' <div className="mt-6 grid gap-3 sm:grid-cols-2 text-left">');for(let i of t){let n=i.entity??i.name??"Item",o=ba(n),s=mt(n.replace(/_/g,"-"));a.push(' <div className="flex items-center gap-3 rounded-md border p-3">'),a.push(" <"+o+' className="h-5 w-5 text-muted-foreground" />'),a.push(' <span className="text-sm font-medium">Add your first '+s+"</span>"),a.push(" </div>")}a.push(" </div>"),a.push(" </div>")}return a.push(" </div>"),a.push(" );"),a.push("}"),a.push(""),a.join(`
10378
- `)}function pn(r,e=!1){if(!r.multiTenant)return null;let t=[];return e?(t.push('import { pgTable, text, timestamp, index } from "drizzle-orm/pg-core";'),t.push('import { user } from "./auth";'),t.push(""),t.push('export const organization = pgTable("organization", {'),t.push(' id: text("id").primaryKey(),'),t.push(' name: text("name").notNull(),'),t.push(' slug: text("slug").unique().notNull(),'),t.push(' createdAt: timestamp("created_at").defaultNow().notNull(),'),t.push(' updatedAt: timestamp("updated_at").defaultNow().notNull(),'),t.push("});"),t.push(""),t.push("export const orgMember = pgTable(")):(t.push('import { sqliteTable, text, index } from "drizzle-orm/sqlite-core";'),t.push('import { sql } from "drizzle-orm";'),t.push('import { user } from "./auth";'),t.push(""),t.push('export const organization = sqliteTable("organization", {'),t.push(' id: text("id").primaryKey(),'),t.push(' name: text("name").notNull(),'),t.push(' slug: text("slug").unique().notNull(),'),t.push(' createdAt: text("created_at").default(sql`CURRENT_TIMESTAMP`).notNull(),'),t.push(' updatedAt: text("updated_at").default(sql`CURRENT_TIMESTAMP`).notNull(),'),t.push("});"),t.push(""),t.push("export const orgMember = sqliteTable(")),t.push(' "org_member",'),t.push(" {"),t.push(' id: text("id").primaryKey(),'),t.push(' orgId: text("org_id").notNull().references(() => organization.id),'),t.push(' userId: text("user_id").notNull().references(() => user.id),'),t.push(' role: text("role").notNull(),'),e?t.push(' joinedAt: timestamp("joined_at").defaultNow().notNull(),'):t.push(' joinedAt: text("joined_at").default(sql`CURRENT_TIMESTAMP`).notNull(),'),t.push(" },"),t.push(" (table) => ({"),t.push(' orgIdx: index("org_member_org_idx").on(table.orgId),'),t.push(' userIdx: index("org_member_user_idx").on(table.userId),'),t.push(" }),"),t.push(");"),t.push(""),t.join(`
10379
- `)}function un(r){if(!r.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(`
10380
- `)}function hn(r){if(!r.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(`
10381
- `)}function gn(r,e,t){let a=[],i=r.split("-").map(l=>l.charAt(0).toUpperCase()+l.slice(1)).join(" ");a.push(`# ${i}`),a.push(""),e?.summary&&(a.push(e.summary),a.push(""));let n=e?.features??[];if(n.length>0){a.push("## Features"),a.push("");for(let l of n){let d=l.description?` \u2014 ${l.description}`:"";a.push(`- **${l.name}**${d}`)}a.push("")}a.push("## Tech Stack"),a.push(""),a.push("| Layer | Technology |"),a.push("|-------|------------|"),a.push("| Framework | Next.js 15 (App Router) |"),a.push("| Database | Mistflow Cloud (Postgres) + Drizzle ORM |"),a.push("| Auth | Better Auth (email/password, social login) |"),a.push("| Styling | Tailwind CSS + shadcn/ui |"),a.push("| Deployment | Mistflow Cloud |"),t.hasStripe&&a.push("| Payments | Stripe |"),t.hasResend&&a.push("| Email | Resend + React Email |"),t.hasStorage&&a.push("| File Storage | Mistflow Cloud (managed blob storage) |"),t.hasAdmin&&a.push("| Admin | Better Auth admin plugin |"),t.hasAI&&a.push("| AI | Vercel AI SDK + OpenAI |"),a.push("");let o=e?.pages??[];if(o.length>0){a.push("## Pages"),a.push(""),a.push("| Route | Description |"),a.push("|-------|-------------|");for(let l of o){let d=l.path??l.route??l.name??"",u=l.description??"";a.push(`| \`${d.startsWith("/")?d:"/"+d}\` | ${u} |`)}a.push("")}let s=e?.dataModel??[];if(s.length>0){a.push("## Data Model"),a.push("");for(let l of s){let d=l.entity??l.name??"Unknown";if(a.push(`### ${d}`),a.push(""),l.fields.length>0){if(typeof l.fields[0]=="string")a.push(`Fields: ${l.fields.join(", ")}`);else{a.push("| Field | Type |"),a.push("|-------|------|");for(let u of l.fields)a.push(`| ${u.name} | ${u.type} |`)}a.push("")}}}return a.push("## Getting Started"),a.push(""),a.push("### Prerequisites"),a.push(""),a.push("- Node.js 20+"),a.push("- npm"),a.push(""),a.push("### Install"),a.push(""),a.push("```bash"),a.push("npm install"),a.push("```"),a.push(""),a.push("### Set up environment"),a.push(""),a.push("Copy `.env.example` to `.env.local` and fill in the values:"),a.push(""),a.push("```bash"),a.push("cp .env.example .env.local"),a.push("```"),a.push(""),a.push("| Variable | Description | Required |"),a.push("|----------|-------------|----------|"),t.isNeon?a.push("| `DATABASE_URL` | Postgres connection URL | Yes |"):(a.push("| `TURSO_URL` | Database connection URL | Yes |"),a.push("| `TURSO_AUTH_TOKEN` | Database auth token | Yes |")),a.push("| `AUTH_SECRET` | Auth encryption secret (auto-generated) | Yes |"),t.hasStripe&&(a.push("| `STRIPE_SECRET_KEY` | Stripe secret key | Yes |"),a.push("| `STRIPE_WEBHOOK_SECRET` | Stripe webhook signing secret | Yes |"),a.push("| `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` | Stripe publishable key | Yes |")),t.hasResend&&(a.push("| `RESEND_API_KEY` | Resend API key | Yes |"),a.push("| `EMAIL_FROM` | Sender email address | Yes (production) |")),t.hasStorage&&(a.push("| `MISTFLOW_API_KEY` | Mistflow API key for file storage | Yes |"),a.push("| `MISTFLOW_PROJECT_ID` | Mistflow project ID | Yes |")),t.hasAI&&a.push("| `OPENAI_API_KEY` | OpenAI API key | Yes |"),a.push(""),a.push("### Local database"),a.push(""),t.isNeon?(a.push("For local development, start a local Postgres server:"),a.push(""),a.push("```bash"),a.push("# Using Docker:"),a.push("docker run -d --name postgres -e POSTGRES_PASSWORD=postgres -p 5432:5432 postgres:17"),a.push("# Or install via Homebrew: brew install postgresql@17 && brew services start postgresql@17"),a.push("```")):(a.push("For local development, start a local Turso server:"),a.push(""),a.push("```bash"),a.push("npx turso dev"),a.push("```")),a.push(""),a.push("Then set up the database:"),a.push(""),a.push("```bash"),a.push("npm run db:push"),a.push("```"),a.push(""),a.push("### Run"),a.push(""),a.push("```bash"),a.push("npm run dev"),a.push("```"),a.push(""),a.push("Open [http://localhost:3000](http://localhost:3000)."),a.push(""),a.push("## Project Structure"),a.push(""),a.push("```"),a.push("app/"),a.push(" (auth)/ Login and registration pages"),a.push(" (dashboard)/ Authenticated app pages"),t.hasAdmin&&a.push(" (admin)/ Admin panel pages"),a.push(" api/ API routes (auth, health, webhooks)"),a.push(" layout.tsx Root layout with fonts and providers"),a.push(" globals.css Design tokens and Tailwind config"),a.push("components/ Reusable UI components"),a.push("db/"),a.push(" schema/ Database table definitions"),a.push(" index.ts Schema exports"),a.push("lib/"),a.push(" auth.ts Better Auth server config"),a.push(" auth-client.ts Better Auth client config"),a.push(` db.ts ${t.isNeon?"Postgres":"SQLite"} database connection`),t.hasStripe&&a.push(" stripe.ts Stripe client"),t.hasResend&&(a.push(" resend.ts Resend client"),a.push(" email.ts Email send helpers")),t.hasStorage&&a.push(" storage.ts File upload/download helpers"),t.hasAI&&a.push(" ai.ts AI client (Vercel AI SDK + OpenAI)"),t.hasResend&&a.push("emails/ React Email templates"),a.push("```"),a.push(""),a.push("## Deploy"),a.push(""),a.push("Deploy to production with Mistflow:"),a.push(""),a.push("```"),a.push("# In your AI editor (Claude Code, Cursor, etc.):"),a.push("mist_deploy action='deploy'"),a.push("```"),a.push(""),a.push("Your app will be live at `https://<app-name>.mistflow.app`."),a.push(""),e?.design&&(a.push("## Design"),a.push(""),e.design.tone&&a.push(`- **Tone**: ${e.design.tone}`),e.design.fonts&&(a.push(`- **Heading font**: ${e.design.fonts.heading}`),a.push(`- **Body font**: ${e.design.fonts.body}`)),e.design.accentColor&&a.push(`- **Accent color**: ${e.design.accentColor}`),e.design.borderRadius&&a.push(`- **Border radius**: ${e.design.borderRadius}`),a.push("")),a.push("---"),a.push(""),a.push("Built with [Mistflow](https://mistflow.ai)"),a.push(""),a.join(`
10382
- `)}async function Vr(r){let{name:e,plan:t,path:a,planId:i}=r;if(!a)return p("mist_build init requires an explicit 'path' \u2014 the absolute directory where the project should be scaffolded. Pass the user's project directory (e.g. /Users/alice/projects/my-app). Do not rely on a default.",!0);if(!_r(a))return p(`mist_build init 'path' must be an absolute path \u2014 received '${a}'. Pass the full absolute path to the target directory.`,!0);let n=ya(a),o=t?.design,s=t?.appStyle;if(s&&o){let m=Ie(s);if(m?.fonts){let x=S=>Cr[S]??S;o.fonts={heading:x(m.fonts.heading),body:x(m.fonts.body)}}}let l=t?Lt(t,"stripe","payment","billing","subscription","checkout","pricing"):!1,d=!0,u=t?Lt(t,"upload","file storage","image upload","profile picture","attachment","gallery","media","blob"):!1,h=t?Lt(t,"admin panel","admin dashboard","admin management"):!1,c=t?Lt(t,"ai integration","openai","llm","ai chat","chatbot","gpt"):!1,g=t,b=!0;if(Me(n))return p(`A project already exists at this location (${n}). Choose a different name, or delete the existing folder first.`,!0);Nt(n,{recursive:!0});try{let m=X(gt(n),".mistflow","mockups");if(Me(m)){let x=$i(m).filter(S=>S.endsWith(".html"));if(x.length>0){let S=X(n,".mistflow","mockups");Nt(S,{recursive:!0});for(let G of x)Vi(X(m,G),X(S,G));console.error(`Copied ${x.length} mockup file(s) into project`)}}}catch(m){console.error("Could not copy mockup files:",m instanceof Error?m.message:m)}let y=null;try{y=await Dt("nextjs")}catch(m){console.error("Could not fetch scaffold from API, using minimal scaffold:",m instanceof Error?m.message:m)}if(y){let m=e.toLowerCase().replace(/[^a-z0-9-]/g,"-");for(let D of y.files){if(D.path==="package.json"||D.path==="middleware.ts"||D.path==="components/sidebar.tsx"||D.path==="components/topnav.tsx"||D.path==="app/(dashboard)/layout.tsx"||D.path==="app/(dashboard)/page.tsx"||D.path==="app/(dashboard)/dashboard/page.tsx"||!l&&(D.path.includes("stripe")||D.path.includes("webhook/stripe"))||!d&&(D.path.includes("resend")||D.path.includes("emails/"))||!h&&(D.path.includes("(admin)")||D.path.includes("admin-sidebar"))||b&&(D.path==="lib/db.ts"||D.path==="lib/auth.ts"||D.path==="drizzle.config.ts"||D.path==="db/schema/auth.ts"))continue;let j=D.content.replace(/\{\{APP_NAME\}\}/g,e).replace(/\{\{WORKER_NAME\}\}/g,m);if(b&&D.path==="next.config.ts"&&(j=j.replace(/serverExternalPackages:\s*\[[^\]]*\],?/g,'serverExternalPackages: ["@electric-sql/pglite"],')),D.path==="next.config.ts"){let ue=Yi(n);ue&&(console.error(`[init] Project is inside monorepo at ${ue} \u2014 adding outputFileTracingRoot`),j.includes("outputFileTracingRoot")||(j=j.replace('import type { NextConfig } from "next";',`import type { NextConfig } from "next";
10376
+ `)}function yn(r,e){let a=r.authModel==="none",t=[];return a||(t.push('import { headers } from "next/headers";'),t.push('import { redirect } from "next/navigation";'),t.push('import { auth } from "@/lib/auth";')),r.navStyle==="topbar"?t.push('import TopNav from "@/components/topnav";'):r.navStyle!=="none"&&t.push('import Sidebar from "@/components/sidebar";'),!a&&r.roles&&r.roles.length>0&&t.push('import { getUserRole } from "@/lib/roles";'),t.push(""),t.push("export default async function DashboardLayout({ children }: { children: React.ReactNode }) {"),a?t.push(' const user = { name: "Guest", email: "" };'):(t.push(" const session = await auth.api.getSession({ headers: await headers() });"),t.push(' if (!session) redirect("/login");'),t.push(""),r.roles&&r.roles.length>0?(t.push(" const role = getUserRole(session.user as Record<string, unknown>);"),t.push(" const user = { name: session.user.name, email: session.user.email, role };")):(t.push(" const user = {"),t.push(" name: session.user.name,"),t.push(" email: session.user.email,"),t.push(" role: (session.user as Record<string, unknown>).role as string | undefined,"),t.push(" };"))),t.push(""),r.navStyle==="topbar"?(t.push(" return ("),t.push(' <div className="min-h-screen">'),t.push(" <TopNav user={user} />"),t.push(' <main className="mx-auto max-w-7xl p-6">{children}</main>'),t.push(" </div>"),t.push(" );")):r.navStyle==="none"?(t.push(" return ("),t.push(' <div className="min-h-screen">'),t.push(' <main className="mx-auto max-w-5xl p-6">{children}</main>'),t.push(" </div>"),t.push(" );")):(t.push(" return ("),t.push(' <div className="flex flex-col md:flex-row min-h-screen">'),t.push(" <Sidebar user={user} />"),t.push(' <main className="flex-1 overflow-x-hidden p-4 md:p-6">{children}</main>'),t.push(" </div>"),t.push(" );")),t.push("}"),t.push(""),t.join(`
10377
+ `)}function xn(r){let e=gt(r.name),a=r.dataModel??[],t=[];if(a.length>0){let i=a.map(o=>wa(o.entity??o.name??"item")),s=[...new Set(i)];t.push('import { Card, CardContent } from "@/components/ui/card";'),t.push("import { "+s.join(", ")+' } from "lucide-react";'),t.push("")}if(t.push("export default function DashboardPage() {"),t.push(" return ("),t.push(' <div className="space-y-6">'),t.push(" <div>"),t.push(' <h1 className="text-3xl font-bold">'+e+"</h1>"),r.summary&&t.push(' <p className="mt-1 text-muted-foreground">'+r.summary+"</p>"),t.push(" </div>"),a.length>0){t.push(' <div className="rounded-lg border p-8 text-center">'),t.push(' <h2 className="text-lg font-semibold">Get started</h2>'),t.push(` <p className="mt-1 text-sm text-muted-foreground">Here's what you can do</p>`),t.push(' <div className="mt-6 grid gap-3 sm:grid-cols-2 text-left">');for(let i of a){let s=i.entity??i.name??"Item",o=wa(s),n=gt(s.replace(/_/g,"-"));t.push(' <div className="flex items-center gap-3 rounded-md border p-3">'),t.push(" <"+o+' className="h-5 w-5 text-muted-foreground" />'),t.push(' <span className="text-sm font-medium">Add your first '+n+"</span>"),t.push(" </div>")}t.push(" </div>"),t.push(" </div>")}return t.push(" </div>"),t.push(" );"),t.push("}"),t.push(""),t.join(`
10378
+ `)}function wn(r,e=!1){if(!r.multiTenant)return null;let a=[];return e?(a.push('import { pgTable, text, timestamp, index } from "drizzle-orm/pg-core";'),a.push('import { user } from "./auth";'),a.push(""),a.push('export const organization = pgTable("organization", {'),a.push(' id: text("id").primaryKey(),'),a.push(' name: text("name").notNull(),'),a.push(' slug: text("slug").unique().notNull(),'),a.push(' createdAt: timestamp("created_at").defaultNow().notNull(),'),a.push(' updatedAt: timestamp("updated_at").defaultNow().notNull(),'),a.push("});"),a.push(""),a.push("export const orgMember = pgTable(")):(a.push('import { sqliteTable, text, index } from "drizzle-orm/sqlite-core";'),a.push('import { sql } from "drizzle-orm";'),a.push('import { user } from "./auth";'),a.push(""),a.push('export const organization = sqliteTable("organization", {'),a.push(' id: text("id").primaryKey(),'),a.push(' name: text("name").notNull(),'),a.push(' slug: text("slug").unique().notNull(),'),a.push(' createdAt: text("created_at").default(sql`CURRENT_TIMESTAMP`).notNull(),'),a.push(' updatedAt: text("updated_at").default(sql`CURRENT_TIMESTAMP`).notNull(),'),a.push("});"),a.push(""),a.push("export const orgMember = sqliteTable(")),a.push(' "org_member",'),a.push(" {"),a.push(' id: text("id").primaryKey(),'),a.push(' orgId: text("org_id").notNull().references(() => organization.id),'),a.push(' userId: text("user_id").notNull().references(() => user.id),'),a.push(' role: text("role").notNull(),'),e?a.push(' joinedAt: timestamp("joined_at").defaultNow().notNull(),'):a.push(' joinedAt: text("joined_at").default(sql`CURRENT_TIMESTAMP`).notNull(),'),a.push(" },"),a.push(" (table) => ({"),a.push(' orgIdx: index("org_member_org_idx").on(table.orgId),'),a.push(' userIdx: index("org_member_user_idx").on(table.userId),'),a.push(" }),"),a.push(");"),a.push(""),a.join(`
10379
+ `)}function vn(r){if(!r.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(`
10380
+ `)}function kn(r){if(!r.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(`
10381
+ `)}function Sn(r,e,a){let t=[],i=r.split("-").map(l=>l.charAt(0).toUpperCase()+l.slice(1)).join(" ");t.push(`# ${i}`),t.push(""),e?.summary&&(t.push(e.summary),t.push(""));let s=e?.features??[];if(s.length>0){t.push("## Features"),t.push("");for(let l of s){let d=l.description?` \u2014 ${l.description}`:"";t.push(`- **${l.name}**${d}`)}t.push("")}t.push("## Tech Stack"),t.push(""),t.push("| Layer | Technology |"),t.push("|-------|------------|"),t.push("| Framework | Next.js 15 (App Router) |"),t.push("| Database | Mistflow Cloud (Postgres) + Drizzle ORM |"),t.push("| Auth | Better Auth (email/password, social login) |"),t.push("| Styling | Tailwind CSS + shadcn/ui |"),t.push("| Deployment | Mistflow Cloud |"),a.hasStripe&&t.push("| Payments | Stripe |"),a.hasResend&&t.push("| Email | Resend + React Email |"),a.hasStorage&&t.push("| File Storage | Mistflow Cloud (managed blob storage) |"),a.hasAdmin&&t.push("| Admin | Better Auth admin plugin |"),a.hasAI&&t.push("| AI | Vercel AI SDK + OpenAI |"),t.push("");let o=e?.pages??[];if(o.length>0){t.push("## Pages"),t.push(""),t.push("| Route | Description |"),t.push("|-------|-------------|");for(let l of o){let d=l.path??l.route??l.name??"",u=l.description??"";t.push(`| \`${d.startsWith("/")?d:"/"+d}\` | ${u} |`)}t.push("")}let n=e?.dataModel??[];if(n.length>0){t.push("## Data Model"),t.push("");for(let l of n){let d=l.entity??l.name??"Unknown";if(t.push(`### ${d}`),t.push(""),l.fields.length>0){if(typeof l.fields[0]=="string")t.push(`Fields: ${l.fields.join(", ")}`);else{t.push("| Field | Type |"),t.push("|-------|------|");for(let u of l.fields)t.push(`| ${u.name} | ${u.type} |`)}t.push("")}}}return t.push("## Getting Started"),t.push(""),t.push("### Prerequisites"),t.push(""),t.push("- Node.js 20+"),t.push("- npm"),t.push(""),t.push("### Install"),t.push(""),t.push("```bash"),t.push("npm install"),t.push("```"),t.push(""),t.push("### Set up environment"),t.push(""),t.push("Copy `.env.example` to `.env.local` and fill in the values:"),t.push(""),t.push("```bash"),t.push("cp .env.example .env.local"),t.push("```"),t.push(""),t.push("| Variable | Description | Required |"),t.push("|----------|-------------|----------|"),a.isNeon?t.push("| `DATABASE_URL` | Postgres connection URL | Yes |"):(t.push("| `TURSO_URL` | Database connection URL | Yes |"),t.push("| `TURSO_AUTH_TOKEN` | Database auth token | Yes |")),t.push("| `AUTH_SECRET` | Auth encryption secret (auto-generated) | Yes |"),a.hasStripe&&(t.push("| `STRIPE_SECRET_KEY` | Stripe secret key | Yes |"),t.push("| `STRIPE_WEBHOOK_SECRET` | Stripe webhook signing secret | Yes |"),t.push("| `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` | Stripe publishable key | Yes |")),a.hasResend&&(t.push("| `RESEND_API_KEY` | Resend API key | Yes |"),t.push("| `EMAIL_FROM` | Sender email address | Yes (production) |")),a.hasStorage&&(t.push("| `MISTFLOW_API_KEY` | Mistflow API key for file storage | Yes |"),t.push("| `MISTFLOW_PROJECT_ID` | Mistflow project ID | Yes |")),a.hasAI&&t.push("| `OPENAI_API_KEY` | OpenAI API key | Yes |"),t.push(""),t.push("### Local database"),t.push(""),a.isNeon?(t.push("For local development, start a local Postgres server:"),t.push(""),t.push("```bash"),t.push("# Using Docker:"),t.push("docker run -d --name postgres -e POSTGRES_PASSWORD=postgres -p 5432:5432 postgres:17"),t.push("# Or install via Homebrew: brew install postgresql@17 && brew services start postgresql@17"),t.push("```")):(t.push("For local development, start a local Turso server:"),t.push(""),t.push("```bash"),t.push("npx turso dev"),t.push("```")),t.push(""),t.push("Then set up the database:"),t.push(""),t.push("```bash"),t.push("npm run db:push"),t.push("```"),t.push(""),t.push("### Run"),t.push(""),t.push("```bash"),t.push("npm run dev"),t.push("```"),t.push(""),t.push("Open [http://localhost:3000](http://localhost:3000)."),t.push(""),t.push("## Project Structure"),t.push(""),t.push("```"),t.push("app/"),t.push(" (auth)/ Login and registration pages"),t.push(" (dashboard)/ Authenticated app pages"),a.hasAdmin&&t.push(" (admin)/ Admin panel pages"),t.push(" api/ API routes (auth, health, webhooks)"),t.push(" layout.tsx Root layout with fonts and providers"),t.push(" globals.css Design tokens and Tailwind config"),t.push("components/ Reusable UI components"),t.push("db/"),t.push(" schema/ Database table definitions"),t.push(" index.ts Schema exports"),t.push("lib/"),t.push(" auth.ts Better Auth server config"),t.push(" auth-client.ts Better Auth client config"),t.push(` db.ts ${a.isNeon?"Postgres":"SQLite"} database connection`),a.hasStripe&&t.push(" stripe.ts Stripe client"),a.hasResend&&(t.push(" resend.ts Resend client"),t.push(" email.ts Email send helpers")),a.hasStorage&&t.push(" storage.ts File upload/download helpers"),a.hasAI&&t.push(" ai.ts AI client (Vercel AI SDK + OpenAI)"),a.hasResend&&t.push("emails/ React Email templates"),t.push("```"),t.push(""),t.push("## Deploy"),t.push(""),t.push("Deploy to production with Mistflow:"),t.push(""),t.push("```"),t.push("# In your AI editor (Claude Code, Cursor, etc.):"),t.push("mist_deploy action='deploy'"),t.push("```"),t.push(""),t.push("Your app will be live at `https://<app-name>.mistflow.app`."),t.push(""),e?.design&&(t.push("## Design"),t.push(""),e.design.tone&&t.push(`- **Tone**: ${e.design.tone}`),e.design.fonts&&(t.push(`- **Heading font**: ${e.design.fonts.heading}`),t.push(`- **Body font**: ${e.design.fonts.body}`)),e.design.accentColor&&t.push(`- **Accent color**: ${e.design.accentColor}`),e.design.borderRadius&&t.push(`- **Border radius**: ${e.design.borderRadius}`),t.push("")),t.push("---"),t.push(""),t.push("Built with [Mistflow](https://mistflow.ai)"),t.push(""),t.join(`
10382
+ `)}async function qr(r,e){let{name:a,plan:t,path:i,planId:s}=r;if(!i)return p("mist_build init requires an explicit 'path' \u2014 the absolute directory where the project should be scaffolded. Pass the user's project directory (e.g. /Users/alice/projects/my-app). Do not rely on a default.",!0);if(!va(i))return p(`mist_build init 'path' must be an absolute path \u2014 received '${i}'. Pass the full absolute path to the target directory.`,!0);let o=Ft(i),n=t?.design,l=t?.appStyle;if(l&&n){let S=De(l);if(S?.fonts){let N=W=>Pr[W]??W;n.fonts={heading:N(S.fonts.heading),body:N(S.fonts.body)}}}let d=t?Rt(t,"stripe","payment","billing","subscription","checkout","pricing"):!1,u=!0,h=t?Rt(t,"upload","file storage","image upload","profile picture","attachment","gallery","media","blob"):!1,c=t?Rt(t,"admin panel","admin dashboard","admin management"):!1,g=t?Rt(t,"ai integration","openai","llm","ai chat","chatbot","gpt"):!1,b=t,f=!0;if(Ce(o))return p(`A project already exists at this location (${o}). Choose a different name, or delete the existing folder first.`,!0);Nt(o,{recursive:!0});try{let S=Z(ut(o),".mistflow","mockups");if(Ce(S)){let N=en(S).filter(W=>W.endsWith(".html"));if(N.length>0){let W=Z(o,".mistflow","mockups");Nt(W,{recursive:!0});for(let J of N)tn(Z(S,J),Z(W,J));console.error(`Copied ${N.length} mockup file(s) into project`)}}}catch(S){console.error("Could not copy mockup files:",S instanceof Error?S.message:S)}let m=null;try{m=await Bt("nextjs")}catch(S){console.error("Could not fetch scaffold from API, using minimal scaffold:",S instanceof Error?S.message:S)}if(m){let S=a.toLowerCase().replace(/[^a-z0-9-]/g,"-");for(let R of m.files){if(R.path==="package.json"||R.path==="middleware.ts"||R.path==="components/sidebar.tsx"||R.path==="components/topnav.tsx"||R.path==="app/(dashboard)/layout.tsx"||R.path==="app/(dashboard)/page.tsx"||R.path==="app/(dashboard)/dashboard/page.tsx"||!d&&(R.path.includes("stripe")||R.path.includes("webhook/stripe"))||!u&&(R.path.includes("resend")||R.path.includes("emails/"))||!c&&(R.path.includes("(admin)")||R.path.includes("admin-sidebar"))||f&&(R.path==="lib/db.ts"||R.path==="lib/auth.ts"||R.path==="drizzle.config.ts"||R.path==="db/schema/auth.ts"))continue;let w=R.content.replace(/\{\{APP_NAME\}\}/g,a).replace(/\{\{WORKER_NAME\}\}/g,S);if(f&&R.path==="next.config.ts"&&(w=w.replace(/serverExternalPackages:\s*\[[^\]]*\],?/g,'serverExternalPackages: ["@electric-sql/pglite"],')),R.path==="next.config.ts"){let v=on(o);v&&(console.error(`[init] Project is inside monorepo at ${v} \u2014 adding outputFileTracingRoot`),w.includes("outputFileTracingRoot")||(w=w.replace('import type { NextConfig } from "next";',`import type { NextConfig } from "next";
10383
10383
  import { dirname } from "path";
10384
10384
  import { fileURLToPath } from "url";
10385
10385
 
10386
- const __dirname = dirname(fileURLToPath(import.meta.url));`),j=j.replace("images: {",`outputFileTracingRoot: __dirname,
10387
- images: {`)))}!h&&D.path.includes("sidebar")&&(j=j.replace(/\{user\.role === "admin"[\s\S]*?<\/Link>\s*\)\}/m,""),j=j.replace(/, Shield/g,"")),W(n,D.path,j)}let x={...y.dependencies};b&&(delete x["@libsql/client"],x["@neondatabase/serverless"]="^0.10.0",x["@electric-sql/pglite"]="^0.2.0"),l&&(x.stripe="^17.0.0"),d&&(x.resend="^4.0.0",x["@react-email/components"]="^0.0.31"),c&&(x.ai="^4.0.0",x["@ai-sdk/openai"]="^1.0.0",x.openai="^4.0.0");let S={"@noble/ciphers":"^1.3.0"},G={react:"19.1.0","react-dom":"19.1.0",punycode:"^2.3.1"};if(W(n,"package.json",JSON.stringify({name:e,version:"0.1.0",private:!0,scripts:{dev:"next dev",build:"next build",start:"next start",lint:"next lint","db:push":"drizzle-kit push","db:studio":"drizzle-kit studio"},dependencies:x,devDependencies:y.devDependencies,optionalDependencies:S,overrides:G},null,2)),y.methodology){let D=y.methodology;b&&(D=D.replace(/sqliteTable/g,"pgTable").replace(/drizzle-orm\/sqlite-core/g,"drizzle-orm/pg-core").replace(/Use `text` for dates \(SQLite stores dates as text\)/g,"Use `timestamp` for dates and `boolean` for booleans (native Postgres types)").replace(/text\("created_at"\)\.notNull\(\)\.default\(sql`\(CURRENT_TIMESTAMP\)`\)/g,'timestamp("created_at").notNull().defaultNow()').replace(/text\("updated_at"\)\.notNull\(\)\.default\(sql`\(CURRENT_TIMESTAMP\)`\)/g,'timestamp("updated_at").notNull().defaultNow()').replace(/import { sqliteTable, text, integer } from "drizzle-orm\/sqlite-core";\nimport { sql } from "drizzle-orm";/g,'import { pgTable, text, timestamp, boolean } from "drizzle-orm/pg-core";').replace(/`drizzle-kit push` for SQLite\/Turso/g,"`drizzle-kit push` for Postgres")),W(n,"AGENTS.md",D),W(n,"CLAUDE.md",D)}b&&(W(n,"lib/db.ts",['import { neon } from "@neondatabase/serverless";','import { drizzle as drizzleNeon } from "drizzle-orm/neon-http";','import { drizzle as drizzlePglite } from "drizzle-orm/pglite";',"","// 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"," const sql = neon(process.env.DATABASE_URL);"," _db = drizzleNeon(sql);"," } else {"," // Local dev \u2014 PGlite (zero-install embedded Postgres)"," // eslint-disable-next-line @typescript-eslint/no-require-imports",' const { PGlite } = require("@electric-sql/pglite");',' const client = new PGlite("./local.pg");'," _db = drizzlePglite(client);"," }"," }"," 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(`
10388
- `)),W(n,"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(`
10389
- `)),W(n,"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(`
10390
- `)),W(n,"lib/auth.ts",['import { betterAuth } from "better-auth";','import { drizzleAdapter } from "better-auth/adapters/drizzle";','import { admin } from "better-auth/plugins/admin";','import { nextCookies } from "better-auth/next-js";','import { db } from "./db";','import * as schema from "@/db";',"","async function sendEmail({ to, subject, html }: { to: string; subject: string; html: string }) {"," const apiKey = process.env.RESEND_API_KEY;"," if (!apiKey) {",' console.error("[auth] RESEND_API_KEY not set \u2014 skipping email send");'," return;"," }",' const from = process.env.EMAIL_FROM || "noreply@mail.mistflow.app";'," console.log(`[auth] Sending email to ${to} from ${from}`);",' 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}`);"," } else {"," console.log(`[auth] Email sent successfully to ${to}`);"," }","}","","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);"," if (!isLocal && !canSendEmail) {",' console.error("[auth] RESEND_API_KEY not set \u2014 disabling email verification to avoid telling users to check an inbox that will stay empty");'," }"," return betterAuth({"," baseURL,"," trustedOrigins: [baseURL],",' database: drizzleAdapter(db, { provider: "pg", schema }),'," emailAndPassword: {"," enabled: true,"," requireEmailVerification: !isLocal && canSendEmail,"," sendResetPassword: async ({ user, url }: { user: { email: string; name: string }; url: string }) => {"," 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="${url}">${url}</a></p>`,'," });"," },"," },"," emailVerification: {"," sendOnSignUp: canSendEmail,"," 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>`,'," });"," },"," },"," secret: process.env.AUTH_SECRET,",' plugins: [admin({ defaultRole: "user" }), nextCookies()],'," socialProviders: {"," ...(process.env.GOOGLE_CLIENT_ID ? {"," google: {"," clientId: process.env.GOOGLE_CLIENT_ID,"," clientSecret: process.env.GOOGLE_CLIENT_SECRET!,"," },"," } : {}),"," ...(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(`
10391
- `)))}else W(n,"package.json",JSON.stringify({name:e,version:"0.1.0",private:!0},null,2));W(n,"app/globals.css",Zi(o,s)),W(n,"app/layout.tsx",an(e,o,g?.language)),W(n,"README.md",gn(e,t,{hasStripe:l,hasResend:d,hasStorage:u,hasAdmin:h,hasAI:c,isNeon:b}));let f=[],v=t?.publicPages;if(Array.isArray(v))f=v;else if(typeof v=="string"){try{f=JSON.parse(v)}catch{f=[]}Array.isArray(f)||(f=[])}if(!f.includes("/")){let m=t?.steps?.some(S=>{let G=((S.name??"")+" "+(S.description??"")).toLowerCase();return G.includes("landing")||G.includes("marketing")||G.includes("homepage")}),x=t?.pages?.some(S=>S.path==="/");(m||x)&&(f=["/",...f])}let C={name:e,summary:t?.summary,authModel:t?.authModel,roles:t?.roles,defaultRole:t?.defaultRole,publicPages:f,navStyle:t?.navStyle,multiTenant:t?.multiTenant,pages:t?.pages,dataModel:t?.dataModel,design:t?.design},P=on(C);P&&W(n,"middleware.ts",P);let A=nn(C);A&&W(n,A.path,A.content);let B=sn(C);if(B&&W(n,"lib/roles.ts",B),W(n,"app/page.tsx",ln(C)),W(n,"app/(dashboard)/layout.tsx",dn(C,h)),W(n,"app/(dashboard)/dashboard/page.tsx",cn(C)),C.multiTenant){let m=pn(C,b);m&&W(n,"db/schema/organization.ts",m);let x=un(C);x&&W(n,"lib/org.ts",x);let S=hn(C);S&&W(n,"components/org-switcher.tsx",S)}W(n,"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)),l&&W(n,"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(`
10392
- `)),d&&(W(n,"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(`
10393
- `)),W(n,"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(`
10394
- `))),u&&(W(n,"lib/storage.ts",['const MISTFLOW_API = process.env.MISTFLOW_API_URL ?? "https://api.mistflow.ai";',"const MISTFLOW_API_KEY = process.env.MISTFLOW_API_KEY;","const PROJECT_ID = process.env.MISTFLOW_PROJECT_ID;","","interface UploadResult {"," upload_url: string;"," download_url: string;"," key: string;","}","","function authHeaders(): Record<string, string> {"," return {",' "Content-Type": "application/json",',' "Authorization": `ApiKey ${MISTFLOW_API_KEY}`,'," };","}","",'export async function getUploadUrl(filename: string, contentType: string = "application/octet-stream"): Promise<UploadResult> {'," const res = await fetch(`${MISTFLOW_API}/api/storage/upload-url`, {",' method: "POST",'," headers: authHeaders(),"," body: JSON.stringify({ project_id: PROJECT_ID, filename, content_type: contentType }),"," });"," if (!res.ok) throw new Error(`Storage error: ${res.status}`);"," return res.json();","}","","export async function getDownloadUrl(filename: string): Promise<string> {"," const res = await fetch(`${MISTFLOW_API}/api/storage/download-url`, {",' method: "POST",'," headers: authHeaders(),"," body: JSON.stringify({ project_id: PROJECT_ID, filename }),"," });"," if (!res.ok) throw new Error(`Storage error: ${res.status}`);"," const data = await res.json();"," return data.download_url;","}","","export async function deleteFile(filename: string): Promise<void> {"," await fetch(`${MISTFLOW_API}/api/storage/delete`, {",' method: "POST",'," headers: authHeaders(),"," body: JSON.stringify({ project_id: PROJECT_ID, filename }),"," });","}","","export async function uploadFile(file: File): Promise<string> {"," const { upload_url, download_url } = await getUploadUrl(file.name, file.type);",' await fetch(upload_url, { method: "PUT", body: file, headers: { "Content-Type": file.type } });'," return download_url;","}",""].join(`
10395
- `)),W(n,"app/api/upload/route.ts",['import { NextRequest, NextResponse } from "next/server";','import { getUploadUrl } from "@/lib/storage";',"","export async function POST(req: NextRequest) {"," const { filename, contentType } = await req.json();"," if (!filename) {",' return NextResponse.json({ error: "filename is required" }, { status: 400 });'," }"," try {",' const result = await getUploadUrl(filename, contentType ?? "application/octet-stream");'," return NextResponse.json(result);"," } catch {",' return NextResponse.json({ error: "Failed to get upload URL" }, { status: 500 });'," }","}",""].join(`
10396
- `))),c&&(W(n,"lib/ai.ts",['import { createOpenAI } from "@ai-sdk/openai";',"","export const openai = createOpenAI({"," apiKey: process.env.OPENAI_API_KEY,","});",""].join(`
10397
- `)),W(n,"app/api/chat/route.ts",['import { openai } from "@/lib/ai";','import { streamText } from "ai";',"","export async function POST(req: Request) {"," const { messages } = await req.json();",""," const result = streamText({",' model: openai("gpt-4o"),'," messages,"," });",""," return result.toDataStreamResponse();","}",""].join(`
10398
- `)));let I={name:e,methodologyVersion:y?.version??"1.0",createdAt:new Date().toISOString(),...i?{planId:i}:{},plan:Array.isArray(t?.steps)?{...t,steps:t.steps.map(m=>({number:m.number,name:m.name??m.title,description:m.description,entities:m.entities,pages:m.pages,features:m.features,status:"pending"}))}:t,dbProvider:"neon",env:{managed:{DATABASE_URL:{description:"Postgres connection URL",scope:"production"},AUTH_SECRET:{description:"Auth encryption secret",scope:"production"},...l?{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"}}:{},...d?{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"}}:{},...u?{MISTFLOW_API_KEY:{description:"Mistflow API key for file storage",scope:"production"},MISTFLOW_PROJECT_ID:{description:"Mistflow project ID",scope:"production"}}:{}},...c?{required:{OPENAI_API_KEY:{description:"OpenAI API key",setupUrl:"https://platform.openai.com/api-keys"}}}:{}},authModel:t?.authModel??"email",roles:t?.roles??null,navStyle:t?.navStyle??"sidebar",multiTenant:t?.multiTenant??!1,hasAdmin:h,hasResend:d,hasStorage:u,hasAI:c,deploy:null};W(n,"mistflow.json",JSON.stringify(I,null,2));let M=Ki(32).toString("hex"),O=l?`
10386
+ const __dirname = dirname(fileURLToPath(import.meta.url));`),w=w.replace("images: {",`outputFileTracingRoot: __dirname,
10387
+ images: {`)))}!c&&R.path.includes("sidebar")&&(w=w.replace(/\{user\.role === "admin"[\s\S]*?<\/Link>\s*\)\}/m,""),w=w.replace(/, Shield/g,"")),_(o,R.path,w)}let N={...m.dependencies},W={...m.devDependencies};if(f&&(delete N["@libsql/client"],N["@neondatabase/serverless"]="^0.10.0",W["@electric-sql/pglite"]="^0.2.0"),d&&(N.stripe="^17.0.0"),u&&(N.resend="^4.0.0",N["@react-email/components"]="^0.0.31"),g&&(N.ai="^4.0.0",N["@ai-sdk/openai"]="^1.0.0",N.openai="^4.0.0"),_(o,"package.json",JSON.stringify({name:a,version:"0.1.0",private:!0,scripts:{dev:"next dev",build:"next build",start:"next start",lint:"next lint","db:push":"drizzle-kit push","db:studio":"drizzle-kit studio"},dependencies:N,devDependencies:W,optionalDependencies:{"@noble/ciphers":"^1.3.0"},overrides:{react:"19.1.0","react-dom":"19.1.0",punycode:"^2.3.1"}},null,2)),m.methodology){let R=m.methodology;f&&(R=R.replace(/sqliteTable/g,"pgTable").replace(/drizzle-orm\/sqlite-core/g,"drizzle-orm/pg-core").replace(/Use `text` for dates \(SQLite stores dates as text\)/g,"Use `timestamp` for dates and `boolean` for booleans (native Postgres types)").replace(/text\("created_at"\)\.notNull\(\)\.default\(sql`\(CURRENT_TIMESTAMP\)`\)/g,'timestamp("created_at").notNull().defaultNow()').replace(/text\("updated_at"\)\.notNull\(\)\.default\(sql`\(CURRENT_TIMESTAMP\)`\)/g,'timestamp("updated_at").notNull().defaultNow()').replace(/import { sqliteTable, text, integer } from "drizzle-orm\/sqlite-core";\nimport { sql } from "drizzle-orm";/g,'import { pgTable, text, timestamp, boolean } from "drizzle-orm/pg-core";').replace(/`drizzle-kit push` for SQLite\/Turso/g,"`drizzle-kit push` for Postgres")),_(o,"AGENTS.md",R),_(o,"CLAUDE.md",R)}f&&(_(o,"lib/db.ts",['import { neon } from "@neondatabase/serverless";','import { drizzle as drizzleNeon } from "drizzle-orm/neon-http";',"","// 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"," const sql = neon(process.env.DATABASE_URL);"," _db = drizzleNeon(sql);"," } else {"," // Local dev \u2014 PGlite (zero-install embedded Postgres). Lives in"," // lib/db-local.ts and is loaded through a runtime-only require"," // whose path is built from a variable, so esbuild's static"," // analysis can't follow it. Keeps pglite + its 30MB WASM out of"," // the Worker bundle.",' const localPath: string = "./" + "db-local";'," // eslint-disable-next-line @typescript-eslint/no-require-imports"," const { createLocalDb } = require(localPath);"," _db = createLocalDb();"," }"," }"," 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(`
10388
+ `)),_(o,"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","// transitive 30MB WASM binary. Loaded via runtime-only require() from","// db.ts, where the path is built from a variable to defeat static analysis.","",'const pglitePkg: string = ["@electric-sql", "pglite"].join("/");',"// eslint-disable-next-line @typescript-eslint/no-require-imports","const { PGlite } = require(pglitePkg);","",'const drizzlePath: string = "drizzle-orm/" + "pglite";',"// eslint-disable-next-line @typescript-eslint/no-require-imports","const { drizzle } = require(drizzlePath);","","// eslint-disable-next-line @typescript-eslint/no-explicit-any","export function createLocalDb(): any {",' const client = new PGlite("./local.pg");'," return drizzle(client);","}",""].join(`
10389
+ `)),_(o,"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(`
10390
+ `)),_(o,"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(`
10391
+ `)),_(o,"lib/auth.ts",['import { betterAuth } from "better-auth";','import { drizzleAdapter } from "better-auth/adapters/drizzle";','import { admin } from "better-auth/plugins/admin";','import { nextCookies } from "better-auth/next-js";','import { db } from "./db";','import * as schema from "@/db";',"","async function sendEmail({ to, subject, html }: { to: string; subject: string; html: string }) {"," const apiKey = process.env.RESEND_API_KEY;"," if (!apiKey) {",' console.error("[auth] RESEND_API_KEY not set \u2014 skipping email send");'," return;"," }",' const from = process.env.EMAIL_FROM || "noreply@mail.mistflow.app";'," console.log(`[auth] Sending email to ${to} from ${from}`);",' 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}`);"," } else {"," console.log(`[auth] Email sent successfully to ${to}`);"," }","}","","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);"," if (!isLocal && !canSendEmail) {",' console.error("[auth] RESEND_API_KEY not set \u2014 disabling email verification to avoid telling users to check an inbox that will stay empty");'," }"," return betterAuth({"," baseURL,"," trustedOrigins: [baseURL],",' database: drizzleAdapter(db, { provider: "pg", schema }),'," emailAndPassword: {"," enabled: true,"," requireEmailVerification: !isLocal && canSendEmail,"," sendResetPassword: async ({ user, url }: { user: { email: string; name: string }; url: string }) => {"," 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="${url}">${url}</a></p>`,'," });"," },"," },"," emailVerification: {"," sendOnSignUp: canSendEmail,"," 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>`,'," });"," },"," },"," secret: process.env.AUTH_SECRET,",' plugins: [admin({ defaultRole: "user" }), nextCookies()],'," socialProviders: {"," ...(process.env.GOOGLE_CLIENT_ID ? {"," google: {"," clientId: process.env.GOOGLE_CLIENT_ID,"," clientSecret: process.env.GOOGLE_CLIENT_SECRET!,"," },"," } : {}),"," ...(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(`
10392
+ `)))}else _(o,"package.json",JSON.stringify({name:a,version:"0.1.0",private:!0},null,2));_(o,"app/globals.css",dn(n,l)),_(o,"app/layout.tsx",un(a,n,b?.language)),_(o,"README.md",Sn(a,t,{hasStripe:d,hasResend:u,hasStorage:h,hasAdmin:c,hasAI:g,isNeon:f}));let y=[],T=t?.publicPages;if(Array.isArray(T))y=T;else if(typeof T=="string"){try{y=JSON.parse(T)}catch{y=[]}Array.isArray(y)||(y=[])}if(!y.includes("/")){let S=t?.steps?.some(W=>{let J=((W.name??"")+" "+(W.description??"")).toLowerCase();return J.includes("landing")||J.includes("marketing")||J.includes("homepage")}),N=t?.pages?.some(W=>W.path==="/");(S||N)&&(y=["/",...y])}let k={name:a,summary:t?.summary,authModel:t?.authModel,roles:t?.roles,defaultRole:t?.defaultRole,publicPages:y,navStyle:t?.navStyle,multiTenant:t?.multiTenant,pages:t?.pages,dataModel:t?.dataModel,design:t?.design},I=gn(k);I&&_(o,"middleware.ts",I);let B=mn(k);B&&_(o,B.path,B.content);let M=fn(k);if(M&&_(o,"lib/roles.ts",M),_(o,"app/page.tsx",bn(k)),_(o,"app/(dashboard)/layout.tsx",yn(k,c)),_(o,"app/(dashboard)/dashboard/page.tsx",xn(k)),k.multiTenant){let S=wn(k,f);S&&_(o,"db/schema/organization.ts",S);let N=vn(k);N&&_(o,"lib/org.ts",N);let W=kn(k);W&&_(o,"components/org-switcher.tsx",W)}_(o,"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)),d&&_(o,"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(`
10393
+ `)),u&&(_(o,"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(`
10394
+ `)),_(o,"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(`
10395
+ `))),h&&(_(o,"lib/storage.ts",['const MISTFLOW_API = process.env.MISTFLOW_API_URL ?? "https://api.mistflow.ai";',"const MISTFLOW_API_KEY = process.env.MISTFLOW_API_KEY;","const PROJECT_ID = process.env.MISTFLOW_PROJECT_ID;","","interface UploadResult {"," upload_url: string;"," download_url: string;"," key: string;","}","","function authHeaders(): Record<string, string> {"," return {",' "Content-Type": "application/json",',' "Authorization": `ApiKey ${MISTFLOW_API_KEY}`,'," };","}","",'export async function getUploadUrl(filename: string, contentType: string = "application/octet-stream"): Promise<UploadResult> {'," const res = await fetch(`${MISTFLOW_API}/api/storage/upload-url`, {",' method: "POST",'," headers: authHeaders(),"," body: JSON.stringify({ project_id: PROJECT_ID, filename, content_type: contentType }),"," });"," if (!res.ok) throw new Error(`Storage error: ${res.status}`);"," return res.json();","}","","export async function getDownloadUrl(filename: string): Promise<string> {"," const res = await fetch(`${MISTFLOW_API}/api/storage/download-url`, {",' method: "POST",'," headers: authHeaders(),"," body: JSON.stringify({ project_id: PROJECT_ID, filename }),"," });"," if (!res.ok) throw new Error(`Storage error: ${res.status}`);"," const data = await res.json();"," return data.download_url;","}","","export async function deleteFile(filename: string): Promise<void> {"," await fetch(`${MISTFLOW_API}/api/storage/delete`, {",' method: "POST",'," headers: authHeaders(),"," body: JSON.stringify({ project_id: PROJECT_ID, filename }),"," });","}","","export async function uploadFile(file: File): Promise<string> {"," const { upload_url, download_url } = await getUploadUrl(file.name, file.type);",' await fetch(upload_url, { method: "PUT", body: file, headers: { "Content-Type": file.type } });'," return download_url;","}",""].join(`
10396
+ `)),_(o,"app/api/upload/route.ts",['import { NextRequest, NextResponse } from "next/server";','import { getUploadUrl } from "@/lib/storage";',"","export async function POST(req: NextRequest) {"," const { filename, contentType } = await req.json();"," if (!filename) {",' return NextResponse.json({ error: "filename is required" }, { status: 400 });'," }"," try {",' const result = await getUploadUrl(filename, contentType ?? "application/octet-stream");'," return NextResponse.json(result);"," } catch {",' return NextResponse.json({ error: "Failed to get upload URL" }, { status: 500 });'," }","}",""].join(`
10397
+ `))),g&&(_(o,"lib/ai.ts",['import { createOpenAI } from "@ai-sdk/openai";',"","export const openai = createOpenAI({"," apiKey: process.env.OPENAI_API_KEY,","});",""].join(`
10398
+ `)),_(o,"app/api/chat/route.ts",['import { openai } from "@/lib/ai";','import { streamText } from "ai";',"","export async function POST(req: Request) {"," const { messages } = await req.json();",""," const result = streamText({",' model: openai("gpt-4o"),'," messages,"," });",""," return result.toDataStreamResponse();","}",""].join(`
10399
+ `)));let C={name:a,methodologyVersion:m?.version??"1.0",createdAt:new Date().toISOString(),...s?{planId:s}:{},plan:Array.isArray(t?.steps)?{...t,steps:t.steps.map(S=>({number:S.number,name:S.name??S.title,description:S.description,entities:S.entities,pages:S.pages,features:S.features,status:"pending"}))}:t,dbProvider:"neon",env:{managed:{DATABASE_URL:{description:"Postgres connection URL",scope:"production"},AUTH_SECRET:{description:"Auth encryption secret",scope:"production"},...d?{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"}}:{},...u?{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"}}:{},...h?{MISTFLOW_API_KEY:{description:"Mistflow API key for file storage",scope:"production"},MISTFLOW_PROJECT_ID:{description:"Mistflow project ID",scope:"production"}}:{}},...g?{required:{OPENAI_API_KEY:{description:"OpenAI API key",setupUrl:"https://platform.openai.com/api-keys"}}}:{}},authModel:t?.authModel??"email",roles:t?.roles??null,navStyle:t?.navStyle??"sidebar",multiTenant:t?.multiTenant??!1,hasAdmin:c,hasResend:u,hasStorage:h,hasAI:g,deploy:null};_(o,"mistflow.json",JSON.stringify(C,null,2));let j=rn(32).toString("hex"),L=d?`
10399
10400
  # Stripe
10400
10401
  STRIPE_SECRET_KEY=
10401
10402
  STRIPE_WEBHOOK_SECRET=
10402
10403
  NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=
10403
10404
  NEXT_PUBLIC_APP_URL=http://localhost:3000
10404
- `:"",E=d?`
10405
+ `:"",G=u?`
10405
10406
  # Email (Resend)
10406
10407
  RESEND_API_KEY=
10407
10408
  EMAIL_FROM=onboarding@resend.dev
10408
- `:"",H=u?`
10409
+ `:"",de=h?`
10409
10410
  # File Storage (Mistflow managed)
10410
10411
  MISTFLOW_API_KEY=
10411
10412
  MISTFLOW_PROJECT_ID=
10412
- `:"",re=c?`
10413
+ `:"",$=g?`
10413
10414
  # AI (get your key at https://platform.openai.com/api-keys)
10414
10415
  OPENAI_API_KEY=
10415
- `:"",z=`# Local dev: PGlite is used automatically (zero-install embedded Postgres)
10416
+ `:"",Q=`# Local dev: PGlite is used automatically (zero-install embedded Postgres)
10416
10417
  # Set DATABASE_URL only for production or to use a remote Postgres
10417
- # DATABASE_URL=postgresql://postgres:postgres@localhost:5432/devdb`,K=`# Local dev: PGlite is used automatically (zero-install embedded Postgres)
10418
+ # DATABASE_URL=postgresql://postgres:postgres@localhost:5432/devdb`,V=`# Local dev: PGlite is used automatically (zero-install embedded Postgres)
10418
10419
  # Set DATABASE_URL only for production or to use a remote Postgres
10419
- # DATABASE_URL=postgresql://postgres:postgres@localhost:5432/devdb`;W(n,".env.local",`${z}
10420
- AUTH_SECRET=${M}
10421
- ${O}${E}${H}${re}`),W(n,".env.example",`${K}
10420
+ # DATABASE_URL=postgresql://postgres:postgres@localhost:5432/devdb`;_(o,".env.local",`${Q}
10421
+ AUTH_SECRET=${j}
10422
+ ${L}${G}${de}${$}`),_(o,".env.example",`${V}
10422
10423
  AUTH_SECRET=your-secret-here
10423
- ${O}${E}${H}${re}`);let U=[],_=(m,x)=>{U.push({phase:m,message:x})},F=(m,x)=>{let S=U.find(G=>G.phase===m&&!G.durationMs);S&&(S.durationMs=x)},Q={NPM_CONFIG_LEGACY_PEER_DEPS:"true"};_("install","Installing packages...");let le=Date.now(),T=0,R=await Ge("npm",["install"],n,12e4,m=>{let x=m.match(/added (\d+) packages/);x&&(T=parseInt(x[1],10))},Q);if(R.success||(console.error("[init] npm install failed, retrying..."),R=await Ge("npm",["install"],n,12e4,m=>{let x=m.match(/added (\d+) packages/);x&&(T=parseInt(x[1],10))},Q)),!R.success)return p(`Project files were created at ${n} but package installation failed after 2 attempts: ${R.error}. Run "npm install" manually to retry.`,!0);F("install",Date.now()-le),U[U.length-1].message=`Installed ${T||"all"} packages`;let L=y?.shadcnComponents??["button","card","input","label","form","dialog","table","dropdown-menu","badge","separator","skeleton","sheet","tabs","avatar","select","textarea","checkbox","switch","tooltip","popover","sonner"];_("ui",`Adding ${L.length} UI components...`);let oe=Date.now(),ie=await Ge("npx",["--yes","shadcn@latest","add","-y","-o",...L],n,12e4,void 0,Q);if(ie.success||(console.error("[init] shadcn install failed, retrying..."),ie=await Ge("npx",["--yes","shadcn@latest","add","-y","-o",...L],n,12e4,void 0,Q)),ie.success)U[U.length-1].message=`Added ${L.length} UI components (Button, Card, Dialog, Table, ...)`;else return p(`Project created at ${n} but UI component installation failed after 2 attempts: ${ie.error}. Run "npx shadcn@latest add -y -o ${L.join(" ")}" manually (set NPM_CONFIG_LEGACY_PEER_DEPS=true if npm complains about peer dependencies) to retry.`,!0);F("ui",Date.now()-oe),_("db","Setting up database...");let w=Date.now(),N,q=await Ge("npx",["drizzle-kit","push"],n,3e4);q.success?U[U.length-1].message="Database ready":(console.error("DB push failed:",q.error),N="Database setup failed \u2014 login won't work yet. Try deploying again. If it keeps failing, contact support.",U[U.length-1].message=b?"Database setup skipped (configure DATABASE_URL to enable)":"Database setup skipped (configure TURSO_URL to enable)"),F("db",Date.now()-w),_("git","Initializing git repository...");let me=Date.now();try{let m=jr(n);await m.init(),await m.add("."),await m.commit("Initial Mistflow project setup"),U[U.length-1].message="Git repository initialized"}catch{console.error("Git initialization failed, continuing without git."),U[U.length-1].message="Git init skipped"}F("git",Date.now()-me);let pe=g?.requestedSubdomain||void 0,Z,xe;try{let m=await Bt(e,void 0,"neon",pe);Z=m.id;let x=X(n,"mistflow.json"),S=JSON.parse(Ft(x,"utf-8"));if(S.projectId=Z,Oe(x,JSON.stringify(S,null,2)),ia(n,na(Z,e)),m.managed_env&&Object.keys(m.managed_env).length>0){let G=X(n,".env.local"),D=Me(G)?Ft(G,"utf-8"):"";for(let[j,ue]of Object.entries(m.managed_env)){let ve=new RegExp(`^${j}=.*$`,"m");ve.test(D)?D=D.replace(ve,`${j}=${ue}`):D+=`
10424
- ${j}=${ue}`}Oe(G,D)}if(Z)try{let{getBaseUrl:G,getAuthHeaders:D}=await import("./api-client-UL3OJVLP.js"),j=D(),ue=t?.features,ve={};Array.isArray(ue)&&ue.length>0&&(ve.features=ue.map(Xe=>Xe.name)),t&&(ve.plan=t),Object.keys(ve).length>0&&await fetch(`${G()}/api/projects/${encodeURIComponent(Z)}/state`,{method:"PUT",headers:{...j,"Content-Type":"application/json"},body:JSON.stringify(ve)})}catch{}}catch(m){let x=m instanceof Error?m.message:String(m);console.error("Could not register project on backend:",x),xe=`Project created locally but NOT registered on Mistflow servers (${x}). Deploy will auto-register it.`}let we=U.reduce((m,x)=>m+(x.durationMs??0),0),de={projectPath:n,projectId:Z},$=U.map(m=>{let x=m.durationMs?` (${(m.durationMs/1e3).toFixed(1)}s)`:"";return`${m.message}${x}`});de.progress=$,de.totalSetupTime=`${(we/1e3).toFixed(1)}s`;let k=[];return N&&k.push(N),Z||k.push("Project was not registered with Mistflow (not signed in). Run mist_setup to sign in BEFORE deploying \u2014 deploy will fail without it."),xe&&(de.registrationWarning=xe),k.length>0&&(de.warnings=k),Z?de.nextAction="NEXT: Call mist_build with action='implement' to start building the first plan step. Do this immediately \u2014 do NOT start a dev server or suggest localhost.":de.nextAction="NEXT: Call mist_build with action='implement' to start building the first plan step. IMPORTANT: You MUST run mist_setup to sign in before deploying \u2014 the project could not be registered because auth is missing.",p(JSON.stringify(de))}async function qr(r){let{name:e,plan:t,projectId:a,sourceDeploymentId:i,forkToken:n,requiredEnvVars:o,dbProvider:s,planId:l}=r;if(!r.path)return p("mist_build init requires an explicit 'path' \u2014 the absolute directory where the forked project should be scaffolded. Pass the user's project directory (e.g. /Users/alice/projects/my-app). Do not rely on a default.",!0);if(!_r(r.path))return p(`mist_build init 'path' must be an absolute path \u2014 received '${r.path}'. Pass the full absolute path to the target directory.`,!0);let d=ya(r.path);if(Me(d))return p(`A project already exists at this location (${d}). Choose a different name, or delete the existing folder first.`,!0);let{mkdtempSync:u,renameSync:h,rmSync:c,cpSync:g}=await import("fs"),{tmpdir:b}=await import("os"),y=u(X(b(),"mistflow-fork-")),f=X(y,"project");Nt(f,{recursive:!0});let v=[],C=(A,B)=>v.push({phase:A,message:B}),P=(A,B)=>{let I=v.find(M=>M.phase===A);I&&(I.durationMs=B)};try{C("download","Downloading source code from template...");let A=Date.now(),B=X(y,"source.tar.gz");try{await ur(i,n,B)}catch(w){c(y,{recursive:!0,force:!0});let N=w instanceof Error?w.message:"Source download failed";return p(`Source code download failed: ${N}. You can still build from the plan \u2014 run mist_build init without the source (omit planId and pass the plan directly).`,!0)}P("download",Date.now()-A),v[v.length-1].message="Source code downloaded",C("extract","Extracting source code...");let I=Date.now(),M=await Ge("tar",["-xzf",B,"-C",f,"--exclude","node_modules","--exclude",".git"],f,6e4);if(!M.success)return c(y,{recursive:!0,force:!0}),p(`Failed to extract source archive: ${M.error}`,!0);if(P("extract",Date.now()-I),!Me(X(f,"package.json")))return c(y,{recursive:!0,force:!0}),p("Source archive does not contain a package.json. The template may be corrupted.",!0);let O=[".mistflow",".env.local",".env.example","local.db","local.pg"];for(let w of O){let N=X(f,w);Me(N)&&c(N,{recursive:!0,force:!0})}let E={name:e,projectId:a,template:"nextjs",createdAt:new Date().toISOString(),planId:l??void 0,plan:t,dbProvider:s};Oe(X(f,"mistflow.json"),JSON.stringify(E,null,2));let H=o.map(w=>{let N=w.description?`# ${w.description}`:`# ${w.key}`,q=w.setup_url?` (${w.setup_url})`:"";return`${N}${q}
10425
- ${w.key}=`});Oe(X(f,".env.local"),H.join(`
10424
+ ${L}${G}${de}${$}`);let F=[],H=(S,N)=>{F.push({phase:S,message:N})},oe=(S,N)=>{let W=F.find(J=>J.phase===S&&!J.durationMs);W&&(W.durationMs=N)};if(e){let S=pt(e.server,e.progressToken,()=>F[F.length-1]?.message??"Setting up project...");e.cleanup=()=>S.stop()}let he=b?.requestedSubdomain||void 0,Y,pe;H("register","Registering project on Mistflow...");let P=Date.now();try{let S=await qa(a,void 0,"neon",he);Y=S.id;let N=Z(o,"mistflow.json"),W=JSON.parse(Lt(N,"utf-8"));if(W.projectId=Y,Ue(N,JSON.stringify(W,null,2)),la(o,da(Y,a)),S.managed_env&&Object.keys(S.managed_env).length>0){let J=Z(o,".env.local"),se=Ce(J)?Lt(J,"utf-8"):"";for(let[R,w]of Object.entries(S.managed_env)){let v=new RegExp(`^${R}=.*$`,"m");v.test(se)?se=se.replace(v,`${R}=${w}`):se+=`
10425
+ ${R}=${w}`}Ue(J,se)}try{let{getBaseUrl:J,getAuthHeaders:se}=await import("./api-client-GWLQOOJZ.js"),R=se(),w=t?.features,v=t?.steps,A={};Array.isArray(w)&&w.length>0&&(A.features=w.map(D=>D.name)),t&&(A.plan=t),Array.isArray(v)&&v.length>0&&(A.provenance=v.map(D=>({feature:D.name??D.title??`Step ${D.number??"?"}`,user_intent:(D.description??"").slice(0,500),decisions:"Seeded from plan at init",tradeoffs:"",files_affected:[]}))),Object.keys(A).length>0&&await fetch(`${J()}/api/projects/${encodeURIComponent(Y)}/state`,{method:"PUT",headers:{...R,"Content-Type":"application/json"},body:JSON.stringify(A)})}catch{}F[F.length-1].message=`Registered as ${Y.slice(0,8)}`}catch(S){let N=S instanceof Error?S.message:String(S);console.error("Could not register project on backend:",N),pe=`Project created locally but NOT registered on Mistflow servers (${N}). Deploy will auto-register it.`,F[F.length-1].message="Registration skipped (offline \u2014 deploy will retry)"}oe("register",Date.now()-P),H("git","Initializing git repository...");let U=Date.now();try{let S=zr(o);await S.init(),await S.add("."),await S.commit("Initial Mistflow project setup"),F[F.length-1].message="Git repository initialized"}catch{console.error("Git initialization failed, continuing without git."),F[F.length-1].message="Git init skipped"}oe("git",Date.now()-U);let O=F.reduce((S,N)=>S+(N.durationMs??0),0),x={projectPath:o,projectId:Y,status:"awaiting_install"},E=F.map(S=>{let N=S.durationMs?` (${(S.durationMs/1e3).toFixed(1)}s)`:"";return`${S.message}${N}`});x.progress=E,x.totalSetupTime=`${(O/1e3).toFixed(1)}s`;let q=[];Y||q.push("Project was not registered with Mistflow (not signed in). Run mist_setup to sign in BEFORE deploying \u2014 deploy will fail without it."),pe&&(x.registrationWarning=pe),q.length>0&&(x.warnings=q);let ge=`NEXT: Call mist_build with action='install' and projectPath='${o}' immediately. Do NOT ask the user for permission \u2014 install is a required follow-up to init, not a decision point. This runs npm install (~1-2 min) as a separate tool call so it never blocks the init response. After install completes, call mist_build with action='implement' to build the first plan step \u2014 also without asking.`;return x.nextAction=Y?ge:`${ge} IMPORTANT: You MUST also run mist_setup to sign in before deploying \u2014 the project could not be registered because auth is missing.`,p(JSON.stringify(x))}async function Kr(r,e){let{projectPath:a}=r;if(!a)return p("mist_build install requires 'projectPath' \u2014 the absolute path to the project that was just initialized. The value is returned as `projectPath` in the mist_build init response.",!0);if(!va(a))return p(`mist_build install 'projectPath' must be an absolute path \u2014 received '${a}'.`,!0);let t=Ft(a);if(!Ce(Z(t,"package.json")))return p(`No package.json found at ${t}. Run mist_build init first to scaffold the project.`,!0);if(Ce(Z(t,"node_modules")))return p(JSON.stringify({status:"already_installed",projectPath:t,nextAction:"node_modules already exists. Call mist_build with action='implement' to start building the first plan step."}));let i="Installing packages...";if(e){let u=pt(e.server,e.progressToken,()=>i);e.cleanup=()=>u.stop()}let s={NPM_CONFIG_LEGACY_PEER_DEPS:"true"},o=Date.now(),n=0,l=await ht("npm",["install"],t,12e4,u=>{let h=u.match(/added (\d+) packages/);h&&(n=parseInt(h[1],10))},s);if(l.success||(console.error("[install] npm install failed, retrying..."),i="Install failed, retrying...",l=await ht("npm",["install"],t,12e4,u=>{let h=u.match(/added (\d+) packages/);h&&(n=parseInt(h[1],10))},s)),!l.success)return p(`npm install failed after 2 attempts at ${t}: ${l.error}. Run "npm install" manually in that directory, then call mist_build implement.`,!0);let d=((Date.now()-o)/1e3).toFixed(1);return p(JSON.stringify({status:"installed",projectPath:t,packagesInstalled:n||null,durationSec:d,nextAction:"NEXT: Call mist_build with action='implement' to start building the first plan step. Do this immediately \u2014 do NOT start a dev server or suggest localhost."}))}async function Yr(r){let{name:e,plan:a,projectId:t,sourceDeploymentId:i,forkToken:s,requiredEnvVars:o,dbProvider:n,planId:l}=r;if(!r.path)return p("mist_build init requires an explicit 'path' \u2014 the absolute directory where the forked project should be scaffolded. Pass the user's project directory (e.g. /Users/alice/projects/my-app). Do not rely on a default.",!0);if(!va(r.path))return p(`mist_build init 'path' must be an absolute path \u2014 received '${r.path}'. Pass the full absolute path to the target directory.`,!0);let d=Ft(r.path);if(Ce(d))return p(`A project already exists at this location (${d}). Choose a different name, or delete the existing folder first.`,!0);let{mkdtempSync:u,renameSync:h,rmSync:c,cpSync:g}=await import("fs"),{tmpdir:b}=await import("os"),f=u(Z(b(),"mistflow-fork-")),m=Z(f,"project");Nt(m,{recursive:!0});let y=[],T=(I,B)=>y.push({phase:I,message:B}),k=(I,B)=>{let M=y.find(C=>C.phase===I);M&&(M.durationMs=B)};try{T("download","Downloading source code from template...");let I=Date.now(),B=Z(f,"source.tar.gz");try{await gr(i,s,B)}catch(x){c(f,{recursive:!0,force:!0});let E=x instanceof Error?x.message:"Source download failed";return p(`Source code download failed: ${E}. You can still build from the plan \u2014 run mist_build init without the source (omit planId and pass the plan directly).`,!0)}k("download",Date.now()-I),y[y.length-1].message="Source code downloaded",T("extract","Extracting source code...");let M=Date.now(),C=await ht("tar",["-xzf",B,"-C",m,"--exclude","node_modules","--exclude",".git"],m,6e4);if(!C.success)return c(f,{recursive:!0,force:!0}),p(`Failed to extract source archive: ${C.error}`,!0);if(k("extract",Date.now()-M),!Ce(Z(m,"package.json")))return c(f,{recursive:!0,force:!0}),p("Source archive does not contain a package.json. The template may be corrupted.",!0);let j=[".mistflow",".env.local",".env.example","local.db","local.pg"];for(let x of j){let E=Z(m,x);Ce(E)&&c(E,{recursive:!0,force:!0})}let L={name:e,projectId:t,template:"nextjs",createdAt:new Date().toISOString(),planId:l??void 0,plan:a,dbProvider:n};Ue(Z(m,"mistflow.json"),JSON.stringify(L,null,2));let G=o.map(x=>{let E=x.description?`# ${x.description}`:`# ${x.key}`,q=x.setup_url?` (${x.setup_url})`:"";return`${E}${q}
10426
+ ${x.key}=`});Ue(Z(m,".env.local"),G.join(`
10426
10427
 
10427
10428
  `)+`
10428
- `);let re=o.map(w=>`${w.key}=`);Oe(X(f,".env.example"),re.join(`
10429
+ `);let de=o.map(x=>`${x.key}=`);Ue(Z(m,".env.example"),de.join(`
10429
10430
  `)+`
10430
- `),Oe(X(f,"README.md"),`# ${e}
10431
+ `),Ue(Z(m,"README.md"),`# ${e}
10431
10432
 
10432
10433
  Forked from a Mistflow template. Built with Next.js, Drizzle ORM, and Better Auth.
10433
10434
 
@@ -10436,11 +10437,11 @@ Forked from a Mistflow template. Built with Next.js, Drizzle ORM, and Better Aut
10436
10437
  1. Copy \`.env.example\` to \`.env.local\` and fill in your values
10437
10438
  2. Run \`npm run dev\`
10438
10439
  3. Deploy with \`mist_deploy\`
10439
- `),C("setup","Setting up project directory...");let z=Date.now();try{h(f,d)}catch(w){if(w.code==="EXDEV")g(f,d,{recursive:!0}),c(f,{recursive:!0,force:!0});else throw w}P("setup",Date.now()-z),C("install","Installing packages...");let K=Date.now(),U=0,_=await Ge("npm",["install"],d,12e4,w=>{let N=w.match(/added (\d+) packages/);N&&(U=parseInt(N[1],10))});if(_.success||(console.error("[initFromSource] npm install failed, retrying..."),_=await Ge("npm",["install"],d,12e4,w=>{let N=w.match(/added (\d+) packages/);N&&(U=parseInt(N[1],10))})),!_.success)return p(`Source code was restored at ${d} but package installation failed: ${_.error}. Run "npm install" manually.`,!0);P("install",Date.now()-K),v[v.length-1].message=`Installed ${U||"all"} packages`,C("git","Initializing git repository...");let F=Date.now();try{let w=jr(d);await w.init(),await w.add("."),await w.commit("Forked from Mistflow template"),v[v.length-1].message="Git repository initialized"}catch{console.error("Git initialization failed, continuing without git."),v[v.length-1].message="Git init skipped"}P("git",Date.now()-F);try{let{markLocalSetupDone:w}=await import("./api-client-UL3OJVLP.js");await w(a)}catch{console.error("[initFromSource] Could not mark local_setup_done on backend, continuing.")}let Q=JSON.parse(Ft(X(d,"mistflow.json"),"utf-8"));Q.projectId=a,Oe(X(d,"mistflow.json"),JSON.stringify(Q,null,2)),ia(d,na(a,e));let le=t.steps,T=le?.filter(w=>w.status==="completed").length??0,R=le?.filter(w=>w.status==="pending").length??0,L=le?.length??0,oe=o.length>0?`
10440
+ `),T("setup","Setting up project directory...");let $=Date.now();try{h(m,d)}catch(x){if(x.code==="EXDEV")g(m,d,{recursive:!0}),c(m,{recursive:!0,force:!0});else throw x}k("setup",Date.now()-$),T("install","Installing packages...");let Q=Date.now(),V=0,F=await ht("npm",["install"],d,12e4,x=>{let E=x.match(/added (\d+) packages/);E&&(V=parseInt(E[1],10))});if(F.success||(console.error("[initFromSource] npm install failed, retrying..."),F=await ht("npm",["install"],d,12e4,x=>{let E=x.match(/added (\d+) packages/);E&&(V=parseInt(E[1],10))})),!F.success)return p(`Source code was restored at ${d} but package installation failed: ${F.error}. Run "npm install" manually.`,!0);k("install",Date.now()-Q),y[y.length-1].message=`Installed ${V||"all"} packages`,T("git","Initializing git repository...");let H=Date.now();try{let x=zr(d);await x.init(),await x.add("."),await x.commit("Forked from Mistflow template"),y[y.length-1].message="Git repository initialized"}catch{console.error("Git initialization failed, continuing without git."),y[y.length-1].message="Git init skipped"}k("git",Date.now()-H);try{let{markLocalSetupDone:x}=await import("./api-client-GWLQOOJZ.js");await x(t)}catch{console.error("[initFromSource] Could not mark local_setup_done on backend, continuing.")}let oe=JSON.parse(Lt(Z(d,"mistflow.json"),"utf-8"));oe.projectId=t,Ue(Z(d,"mistflow.json"),JSON.stringify(oe,null,2)),la(d,da(t,e));let he=a.steps,Y=he?.filter(x=>x.status==="completed").length??0,pe=he?.filter(x=>x.status==="pending").length??0,P=he?.length??0,U=o.length>0?`
10440
10441
 
10441
10442
  Environment variables needed:
10442
- `+o.map(w=>` \u2022 ${w.key}${w.description?` \u2014 ${w.description}`:""}`).join(`
10443
- `):"",ie=R>0?`Source code restored with ${T}/${L} steps complete. ${R} steps need implementation \u2014 call mist_build with action='implement' to apply your changes.`:`Source code fully restored (${L} steps complete). Configure your .env.local, then deploy with mist_deploy.`;return p(JSON.stringify({status:"success",projectPath:d,projectId:a,planStepsCompleted:T,planStepsTotal:L,pendingSteps:R,progress:v,nextAction:ie+oe}))}finally{try{c(y,{recursive:!0,force:!0})}catch{}}}import{z as ka}from"zod";import{existsSync as Sa,readFileSync as wo,writeFileSync as Ca,mkdirSync as En}from"fs";import{join as Ot,resolve as Hn,dirname as On}from"path";import{createConnection as Gn}from"net";var Kr=`# Editorial Tone -- Full Page Composition
10443
+ `+o.map(x=>` \u2022 ${x.key}${x.description?` \u2014 ${x.description}`:""}`).join(`
10444
+ `):"",O=pe>0?`Source code restored with ${Y}/${P} steps complete. ${pe} steps need implementation \u2014 call mist_build with action='implement' to apply your changes.`:`Source code fully restored (${P} steps complete). Configure your .env.local, then deploy with mist_deploy.`;return p(JSON.stringify({status:"success",projectPath:d,projectId:t,planStepsCompleted:Y,planStepsTotal:P,pendingSteps:pe,progress:y,nextAction:O+U}))}finally{try{c(f,{recursive:!0,force:!0})}catch{}}}import{z as Ca}from"zod";import{existsSync as Ot,readFileSync as Ao,writeFileSync as Pa,mkdirSync as Jn}from"fs";import{join as mt,resolve as Qn,dirname as Xn}from"path";import{createConnection as Zn}from"net";var Jr=`# Editorial Tone -- Full Page Composition
10444
10445
 
10445
10446
  Magazine-like. Strong typography hierarchy. Generous whitespace. The content IS the design. This tone works best for content-heavy apps, media platforms, blogs, knowledge tools, and professional services.
10446
10447
 
@@ -10513,7 +10514,7 @@ Pick ONE. It should feel intentional and confident, not decorative.
10513
10514
  - Dense card grids (editorial = space, not density)
10514
10515
  - Casual copy ("Hey!", "Let's go!", "Super easy!")
10515
10516
  - More than one gradient text accent per page
10516
- `;var Yr=`# Brutalist Tone -- Full Page Composition
10517
+ `;var Qr=`# Brutalist Tone -- Full Page Composition
10517
10518
 
10518
10519
  Raw. High-contrast. Visible structure. This tone works for creative agencies, portfolios, art platforms, experimental tools, and products that want to signal "we're not like everyone else."
10519
10520
 
@@ -10588,7 +10589,7 @@ Pick ONE. It should feel intentional and aggressive, not decorative.
10588
10589
  - Card hover lift effects
10589
10590
  - Blur or glassmorphism
10590
10591
  - Any word from the Tier 1 banned phrases list (brutalist copy is already naturally specific)
10591
- `;var Jr=`# Soft SaaS Tone -- Full Page Composition
10592
+ `;var Xr=`# Soft SaaS Tone -- Full Page Composition
10592
10593
 
10593
10594
  Rounded. Warm. Friendly. Approachable. This is the most common landing page tone -- which means it has the highest risk of looking generic. The key is warmth and specificity, not safe defaults. This tone works for B2B tools, productivity apps, team collaboration, CRM, project management.
10594
10595
 
@@ -10674,7 +10675,7 @@ Pick ONE. It should demonstrate the product, not just decorate.
10674
10675
  - Stock photography of "diverse teams collaborating around laptops"
10675
10676
  - Monospace fonts (wrong tone)
10676
10677
  - Dark theme (wrong energy for soft-saas)
10677
- `;var Qr=`# Dark Terminal Tone -- Full Page Composition
10678
+ `;var Zr=`# Dark Terminal Tone -- Full Page Composition
10678
10679
 
10679
10680
  Dark background. Mono fonts for accents. Neon or vivid accent highlights. Code-like elements. This tone works for developer tools, APIs, CLIs, AI/ML products, infrastructure, and anything that signals "built by engineers, for engineers."
10680
10681
 
@@ -10761,7 +10762,7 @@ Pick ONE. It should demonstrate the product's developer experience, not just dec
10761
10762
  - Long paragraphs of marketing copy (show code instead)
10762
10763
  - Light theme (betrays the tone promise)
10763
10764
  - Gradient text using purple-to-pink (the universal AI slop gradient)
10764
- `;var Xr=`# Luxury Tone -- Full Page Composition
10765
+ `;var eo=`# Luxury Tone -- Full Page Composition
10765
10766
 
10766
10767
  Serif headlines. Muted palette. Generous whitespace. Slow, graceful motion. Every element earns its place. This tone works for premium services, fashion, hospitality, high-end consumer products, professional services (law, consulting, finance), and anything that signals exclusivity and craft.
10767
10768
 
@@ -10839,7 +10840,7 @@ Pick ONE. It should feel effortless and inevitable, never showy.
10839
10840
  - Stats sections with big flashy numbers (too commercial)
10840
10841
  - Any motion faster than 0.5s (too abrupt)
10841
10842
  - Copy that "sells" hard -- luxury lets the product exist and invites you in
10842
- `;var Zr=`# Playful Tone -- Full Page Composition
10843
+ `;var to=`# Playful Tone -- Full Page Composition
10843
10844
 
10844
10845
  Bright. Energetic. Personality-forward. This tone works for consumer apps, kids/education, games, social platforms, creative tools, casual productivity apps, and anything that should feel fun to use.
10845
10846
 
@@ -10916,7 +10917,7 @@ Pick ONE. It should make someone smile, not just look at it.
10916
10917
  - Stock photography of business settings
10917
10918
  - Uniform card grids with no size variation (boring)
10918
10919
  - Taking itself too seriously
10919
- `;var eo=`# Split Hero Tone -- Full Page Composition
10920
+ `;var ao=`# Split Hero Tone -- Full Page Composition
10920
10921
 
10921
10922
  Text left, visual right. Asymmetric. Product-focused. This is a STRUCTURAL tone -- it defines layout, not personality. The aesthetic personality comes from the app's \`design.tone\` (clean-saas, warm-minimal, etc.) and the design system. This tone works for any app type that benefits from showing the product alongside the pitch: SaaS dashboards, booking tools, management apps, marketplaces.
10922
10923
 
@@ -11020,7 +11021,7 @@ Pick ONE animation inside the card. The card itself is the star -- make it feel
11020
11021
  - Hero that stacks on mobile with the image on top (text should come first on mobile: \`flex-col-reverse lg:flex-row\` for the card, or \`flex-col lg:flex-row\` with text first)
11021
11022
  - Product preview that looks like a different product than what the landing page describes
11022
11023
  - More than one glass card in the hero (one is premium, two is cluttered)
11023
- `;var kn={editorial:Kr,brutalist:Yr,"soft-saas":Jr,"dark-terminal":Qr,luxury:Xr,playful:Zr,"split-hero":eo};function to(r){return kn[r]}var ao=`# Consumer Warm Archetype
11024
+ `;var Mn={editorial:Jr,brutalist:Qr,"soft-saas":Xr,"dark-terminal":Zr,luxury:eo,playful:to,"split-hero":ao};function ro(r){return Mn[r]}var oo=`# Consumer Warm Archetype
11024
11025
 
11025
11026
  Component-level design guidance for personal, lifestyle, and wellness apps. Habits, journals, recipes, mood trackers, meditation, daily routines, personal finance.
11026
11027
 
@@ -11187,7 +11188,7 @@ All colors from project CSS custom properties. Key guidance for consumer-warm:
11187
11188
  - Small touch targets. Phone-first means \`h-12\` minimum.
11188
11189
  - Empty states that just say "No data." Be encouraging.
11189
11190
  - Dark theme as default. Consumer warm apps default to light.
11190
- `;var ro=`# Consumer Bold Archetype
11191
+ `;var io=`# Consumer Bold Archetype
11191
11192
 
11192
11193
  Component-level design guidance for energetic, achievement-driven consumer apps. Fitness, workouts, sports, gaming, social platforms, competitive tracking.
11193
11194
 
@@ -11358,7 +11359,7 @@ Social/competitive apps need an activity feed:
11358
11359
  - Card-only layouts with no hierarchy. Use hero cards + supporting cards.
11359
11360
  - Missing achievement/progress systems. The gamification IS the product.
11360
11361
  - Light-touch buttons. CTAs should be unmissable.
11361
- `;var oo=`# Professional Clean Archetype
11362
+ `;var no=`# Professional Clean Archetype
11362
11363
 
11363
11364
  Component-level design guidance for service and appointment-based apps. Booking systems, clinics, salons, real estate, consulting, restaurants, event management.
11364
11365
 
@@ -11570,7 +11571,7 @@ h-10 w-10 rounded-full bg-[deterministic-color] flex items-center justify-center
11570
11571
  - Missing status workflows. Every booking/appointment needs a clear state machine.
11571
11572
  - Dark theme as default. Clients associate light themes with professionalism.
11572
11573
  - Emoji in the UI. Professional context.
11573
- `;var io=`# Education Structured Archetype
11574
+ `;var so=`# Education Structured Archetype
11574
11575
 
11575
11576
  Component-level design guidance for learning, education, and knowledge apps. Course platforms, quiz apps, flashcards, LMS, student portals, tutorial sites, documentation tools.
11576
11577
 
@@ -11770,7 +11771,7 @@ border-2 border-destructive bg-destructive/5 rounded-xl p-4
11770
11771
  - Hero metrics. "2,847 XP" is gaming, not learning. Use "4 of 12 lessons" instead.
11771
11772
  - Long lesson pages with no progress indicator. Users need to know where they are.
11772
11773
  - Multiple competing elements per view. One thing at a time. One question at a time.
11773
- `;var no=`# Marketplace Browse Archetype
11774
+ `;var lo=`# Marketplace Browse Archetype
11774
11775
 
11775
11776
  Component-level design guidance for browsing, listing, and shopping apps. Marketplaces, directories, shops, classifieds, rental platforms, food delivery, product catalogs.
11776
11777
 
@@ -11994,9 +11995,1022 @@ border-b border-border/30 py-4
11994
11995
  - Small product images. The image is the primary decision-making element.
11995
11996
  - No empty state for zero search results. "No results for 'xyz'. Try broader terms."
11996
11997
  - Desktop-only filter sidebar without mobile equivalent. Use a slide-out sheet on mobile.
11997
- `;var Dn={"consumer-warm":{id:"consumer-warm",name:"Consumer Warm",description:"Personal, lifestyle, and wellness apps (habits, journals, recipes, mood, meditation)",content:ao},"consumer-bold":{id:"consumer-bold",name:"Consumer Bold",description:"Energetic, achievement-driven apps (fitness, workouts, sports, gaming, social)",content:ro},"professional-clean":{id:"professional-clean",name:"Professional Clean",description:"Service and appointment-based apps (booking, clinics, salons, real estate, restaurants)",content:oo},"education-structured":{id:"education-structured",name:"Education Structured",description:"Learning and knowledge apps (courses, quizzes, flashcards, LMS, tutorials)",content:io},"marketplace-browse":{id:"marketplace-browse",name:"Marketplace Browse",description:"Browsing and shopping apps (marketplaces, directories, shops, catalogs, food delivery)",content:no}};function so(r){return Dn[r]?.content}function lo(r,e){let t=r.toLowerCase(),a=e?.tone??"",i=e?.audienceType??"";if(!/\bdev.?tool|developer|api|cli|sdk|infrastructure|ci.?cd|monitoring|observability\b/i.test(t)){if(/\bhabit|journal|diary|mood|meditation|yoga|mindful|gratitude|daily.?routine|self.?care|wellness\b/i.test(t))return"consumer-warm";if(/\bfitness|workout|exercise|gym|training|sport|running|cycling|weight|muscle|cardio|athletic\b/i.test(t))return"consumer-bold";if(/\bcourse|quiz|flashcard|lesson|tutorial|learning|education|lms|student|teach|curriculum|exam\b/i.test(t))return"education-structured";if(/\bmarketplace|shop|store|catalog|listing|directory|classified|rental|e.?commerce|food.?delivery|menu\b/i.test(t))return"marketplace-browse";if(/\bbooking|appointment|reserv|salon|clinic|dental|barber|spa|hotel|restaurant|event|venue|schedule\b/i.test(t))return"professional-clean";if(a==="warm-minimal"||a==="playful")return/\brecipe|cook|meal|food|pet|plant|garden|travel|photo|music|social|community|family\b/i.test(t)?"consumer-warm":/\bgame|compet|score|rank|challenge|achieve|badge|level|quest\b/i.test(t)?"consumer-bold":"consumer-warm";if(i==="b2c")return"consumer-warm"}}import{existsSync as Ht,mkdirSync as po,readFileSync as uo,readdirSync as ho,writeFileSync as An}from"fs";import{join as nt,resolve as In}from"path";import{homedir as va}from"os";function it(r){return r.entity??r.name??"Unknown"}function Ke(r){return typeof r=="string"?r:r.name}function wa(r){return typeof r=="string"?"text":r.type}function co(r){let e=r.fields||[],t=[];for(let a=0;a<3;a++){let i={};for(let n of e)i[Ke(n)]=Mn(Ke(n),wa(n),it(r),a);t.push(i)}return t}function Mn(r,e,t,a){let i=r.toLowerCase(),n=(e||"text").toLowerCase();return i==="name"||i==="title"?[`${t} Alpha`,`${t} Beta`,`${t} Gamma`][a]:i==="email"?["alice@example.com","bob@example.com","carol@example.com"][a]:i==="status"?["Active","Pending","Completed"][a]:i==="priority"?["High","Medium","Low"][a]:i.includes("date")||n==="date"?["Jan 15, 2024","Feb 20, 2024","Mar 10, 2024"][a]:i.includes("price")||i.includes("amount")||i.includes("cost")?["$29","$49","$99"][a]:i.includes("count")||i.includes("quantity")||n==="number"||n==="integer"?["12","34","56"][a]:n==="boolean"||n==="bool"?["Yes","No","Yes"][a]:i.includes("description")||n==="textarea"?["Brief description here","Another example entry","Third sample item"][a]:[`Sample ${a+1}`][0]}function Rn(r){let e=r.dataModel??[],t=r.pages??[],a=r.design??{},i=t.map(h=>({label:h.name??h.path??"Page",route:h.path??h.route??"/"})),n=[],o=e.slice(0,3).map(h=>({name:it(h),fields:(h.fields||[]).map(c=>({name:Ke(c),type:wa(c)})),sampleData:co(h)})),s=[];r.primaryAction&&(s.push(`PRIMARY: ${r.primaryAction.action} \u2014 this is the first thing the user sees and does`),s.push(`SURFACE: ${r.primaryAction.dashboardSurface}`)),s.push(`METRICS: Key counts for ${e.map(h=>it(h)).join(", ")}`),s.push("RECENT: Latest activity or items"),n.push({name:"Dashboard",type:"dashboard",route:"/dashboard",purpose:r.primaryAction?`Action surface \u2014 user comes here to ${r.primaryAction.action.toLowerCase()}. Not a stats display.`:`Overview of ${e.map(h=>it(h)).join(", ")} with key metrics.`,informationHierarchy:s,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:o});let l=e[0];if(l){let h=it(l),c=h.toLowerCase().endsWith("s")?h:`${h}s`;n.push({name:`${h} List`,type:"detail",route:`/${c.toLowerCase()}`,purpose:`Browse, search, and manage ${c.toLowerCase()}. Create new ${h.toLowerCase()}s.`,informationHierarchy:[`HEADER: "${c}" title + "Add ${h}" button`,"SEARCH: Filter/search bar \u2014 users will have many items",`TABLE: ${(l.fields||[]).slice(0,5).map(g=>Ke(g)).join(", ")} columns`,"ROW ACTIONS: Edit, delete on each row"],interactionStates:[`Empty state: "No ${c.toLowerCase()} yet" with create CTA`,"Loading state: skeleton table rows",`Search with no results: "No ${c.toLowerCase()} matching..." with clear filter`],entities:[{name:h,fields:(l.fields||[]).map(g=>({name:Ke(g),type:wa(g)})),sampleData:co(l)}]})}r.steps.some(h=>{let c=`${h.name??h.title??""} ${h.description??""}`.toLowerCase();return c.includes("landing")||c.includes("hero")||c.includes("marketing")||c.includes("homepage")})&&n.push({name:"Landing Page",type:"landing",route:"/",purpose:`Convince visitors to sign up for ${r.name}. Answer: what is this, who is it for, why should I care.`,informationHierarchy:[`HERO: One sentence about what ${r.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 u=[];for(let h of e.slice(0,3)){let c=it(h);(h.fields||[]).find(b=>Ke(b).toLowerCase()==="name"||Ke(b).toLowerCase()==="title")&&u.push(`What if a ${c.toLowerCase()}'s name is 47 characters? Does the layout break?`),u.push(`What if there are 0 ${c.toLowerCase()}s? 1? 500?`)}return r.authModel&&r.authModel!=="none"&&u.push("What does a brand-new user see? (no data, no setup)"),u.push("What if the network is slow? What loads first?"),{appName:r.name,summary:r.summary??"",screens:n,navigation:{style:r.navStyle??"sidebar",items:i},primaryAction:r.primaryAction??null,designDirection:{tone:a.tone??"professional",accentColor:a.accentColor??"blue",navStyle:r.navStyle??"sidebar",fonts:a.fonts??{heading:"Inter",body:"Inter"}},edgeCases:u}}function Ln(r,e){let t=[];t.push(`# Wireframe sketch for ${r.appName}`),t.push(""),t.push(`**${r.appName}** \u2014 ${r.summary}`),t.push(""),e&&(t.push("## Feedback to apply"),t.push(e),t.push("")),t.push("## Design principles"),t.push(""),t.push("Apply these when deciding layout and hierarchy:"),t.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."),t.push("2. **Interaction states** \u2014 Every screen has at least: empty, loading, populated. Show the populated state but add HTML comments noting the others."),t.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."),t.push(`4. **Subtraction** \u2014 "As little design as possible" (Dieter Rams). Every element earns its pixels. If removing something doesn't hurt, remove it.`),t.push("5. **Design for trust** \u2014 Clear labels, predictable layout, obvious actions. No mystery meat navigation."),t.push(""),t.push("## Wireframe rules (strict)"),t.push(""),t.push("Write a **single self-contained HTML file** saved to `.mistflow/mockups/mockup-{planId}.html` (replace `{planId}` with the actual plan ID)."),t.push(""),t.push("The wireframe must:"),t.push("- Use **system fonts only** (`-apple-system, system-ui, sans-serif`) \u2014 no Google Fonts, no CDN"),t.push("- Use **inline CSS only** \u2014 no external stylesheets, no Tailwind CDN"),t.push("- Look **intentionally rough** \u2014 thin gray borders (#ddd), light backgrounds (#f8f8f8), no color, no shadows"),t.push("- Use **realistic placeholder content** that matches this specific app (sample data provided below) \u2014 NOT lorem ipsum"),t.push("- Include **HTML comments** explaining design decisions (e.g., `<!-- Primary action is prominent because users come here to check in members -->`)"),t.push("- Show **all screens in a single page** using tabs/sections that the user can click through"),t.push("- Be **responsive** \u2014 test that it looks reasonable at both 1200px and 375px widths"),t.push("- Include a small header bar showing: screen name tabs + the design direction summary"),t.push(""),t.push("The wireframe must NOT:"),t.push("- Use any color except grayscale (#333, #666, #999, #ddd, #f8f8f8, white)"),t.push("- Use any external dependencies \u2014 no CDN, no imports, no build step"),t.push("- Look polished \u2014 it should feel like a sketch on a whiteboard, not a finished product"),t.push("- Include decorative elements \u2014 no icons (use text labels), no illustrations, no gradients"),t.push(""),t.push("## Screens to wireframe"),t.push("");for(let a of r.screens){t.push(`### ${a.name} (\`${a.route}\`)`),t.push(`**Purpose**: ${a.purpose}`),t.push(""),t.push("**Information hierarchy** (render in this order, top to bottom):");for(let i of a.informationHierarchy)t.push(`- ${i}`);t.push(""),t.push("**Interaction states** (add HTML comments for non-visible states):");for(let i of a.interactionStates)t.push(`- ${i}`);if(t.push(""),a.entities.length>0){t.push("**Data model and sample content** (use this real data, not lorem ipsum):");for(let i of a.entities)t.push(`
11998
- **${i.name}** \u2014 fields: ${i.fields.map(n=>`${n.name} (${n.type})`).join(", ")}`),t.push("```json"),t.push(JSON.stringify(i.sampleData,null,2)),t.push("```");t.push("")}}t.push("## Navigation"),t.push(`**Style**: ${r.navigation.style} (use this layout)`),t.push("**Items**:");for(let a of r.navigation.items)t.push(`- ${a.label} \u2192 \`${a.route}\``);if(t.push(""),r.primaryAction&&(t.push("## Primary action (this drives the layout)"),t.push(`- **Action**: ${r.primaryAction.action}`),t.push(`- **Flow**: ${r.primaryAction.flow}`),t.push(`- **Dashboard must show**: ${r.primaryAction.dashboardSurface}`),t.push(""),t.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."),t.push("")),r.edgeCases.length>0){t.push("## Edge cases to consider"),t.push("Add HTML comments in the wireframe where these matter:");for(let a of r.edgeCases)t.push(`- ${a}`);t.push("")}return t.push("## Design direction (DO NOT apply to wireframe \u2014 this is for reference only)"),t.push(`The final app will use: ${r.designDirection.tone} tone, ${r.designDirection.accentColor} accent, ${r.designDirection.navStyle} nav, ${r.designDirection.fonts.heading} / ${r.designDirection.fonts.body} fonts.`),t.push("The wireframe is grayscale and rough. These tokens will be applied during the actual build."),t.push(""),t.push("## After writing the wireframe"),t.push(""),t.push("1. Write the file to `.mistflow/mockups/mockup-{planId}.html` (replace `{planId}` with the actual plan ID)"),t.push("2. Open it: `open .mistflow/mockups/mockup-{planId}.html`"),t.push(`3. Tell the user: "Here's a rough wireframe of your app. This shows the layout and information hierarchy \u2014 not the final visual design (colors, fonts, polish come during the build). Does the layout feel right? Want to move things around, or shall we start building?"`),t.join(`
11999
- `)}function go(r){return nt(va(),".mistflow","mockup-state",`${r}.json`)}function Nn(r){let e=go(r);if(!Ht(e))return null;try{return JSON.parse(uo(e,"utf-8"))}catch{return null}}function xa(r,e){let t=nt(va(),".mistflow","mockup-state");po(t,{recursive:!0}),An(go(r),JSON.stringify(e,null,2))}async function mo(r){let{planId:e,feedback:t,approved:a}=r,i=In(r.projectPath??process.cwd()),n=nt(va(),".mistflow","plans",`${e}.json`);if(!Ht(n))return p(`Plan not found for planId '${e}'. Call mist_plan to generate a plan first.`,!0);let o;try{o=JSON.parse(uo(n,"utf-8"))}catch{return p("Failed to read plan file. Call mist_plan again.",!0)}let s=o.plan;if(!s)return p("Plan data is empty. Call mist_plan again.",!0);let l=Nn(e);l||(l={planId:e,iterationCount:0,approved:!1,screens:[],feedback:[]});let d=nt(i,".mistflow","mockups");if(po(d,{recursive:!0}),a){l.approved=!0,xa(e,l);let g=Ht(d)?ho(d).filter(b=>b.endsWith(".html")):[];return p(JSON.stringify({status:"approved",message:`Wireframe approved after ${l.iterationCount} iteration(s). Layout direction is locked in.`,mockupFiles:g.map(b=>`.mistflow/mockups/${b}`),nextAction:`NEXT: Call mist_build with action='init', name='${s.name}', and planId='${e}' to create the project. The wireframe in .mistflow/mockups/ will be used as layout reference during implementation.`}))}t?(l.iterationCount++,l.feedback.push(t)):l.iterationCount++,xa(e,l);let u=Rn(s);l.screens=u.screens.map(g=>g.name),xa(e,l);let h=Ln(u,t??void 0),c;return l.iterationCount>=3&&(c="The wireframe is shaping up \u2014 want to keep refining the layout, or start building?"),p(JSON.stringify({status:"wireframe",iterationCount:l.iterationCount,screens:u.screens.map(g=>({name:g.name,type:g.type,route:g.route})),wireframePrompt:h,designDirection:u.designDirection,...c?{nudge:c}:{},mockupFile:`mockup-${e}.html`,nextAction:t?`Apply the user's feedback to the wireframe. Rewrite .mistflow/mockups/mockup-${e}.html with the changes. Open it in the browser for review. Ask if they want more changes or are ready to build.`:`Generate the wireframe HTML following the wireframePrompt instructions above. Write it to .mistflow/mockups/mockup-${e}.html, then open it in the browser. Ask the user if the layout feels right.`}))}function fo(r){let e=nt(r,".mistflow","mockups");return Ht(e)?ho(e).filter(t=>t.endsWith(".html")).map(t=>nt(e,t)):[]}var bo="\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 yo=`# Landing Page Rules
11998
+ `;var co=`# SaaS Analytical Archetype
11999
+
12000
+ 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.
12001
+
12002
+ These apps are where WORK HAPPENS. The user has the product open 4+ hours a day. The UI must feel efficient, dense-but-scannable, and professional. Linear, Notion, Vercel, Stripe, Posthog are the reference bar.
12003
+
12004
+ ---
12005
+
12006
+ ## Theme
12007
+
12008
+ Default: **either** \u2014 follows the app's \`design.tone\`. \`clean-saas\` and \`dense-professional\` lean light (Notion, Stripe), \`dark-first\` leans dark (Linear, Vercel, Posthog). All CSS in this archetype uses semantic tokens (\`bg-background\`, \`text-foreground\`, \`border-border\`) so the visual flips cleanly when the theme flips. Never hardcode hex values.
12009
+
12010
+ ---
12011
+
12012
+ ## Page Composition
12013
+
12014
+ ### Dashboard / Overview
12015
+
12016
+ The dashboard is a scoreboard AND an action surface. Users want to know the state of their world and complete the next action without navigating.
12017
+
12018
+ - **KPI strip at the top.** 3\u20134 key metrics in a horizontal row. Each metric: big number + delta vs last period + sparkline. \`tabular-nums\` for all numbers.
12019
+ - **Primary workspace panel.** The main thing the user does: pipeline, task list, recent activity feed, chart. Takes 60-70% of the grid.
12020
+ - **Secondary rail on the right.** Recent activity, notifications, or quick-add form. 30-40% width, sticky on scroll.
12021
+ - **Density over decoration.** Small padding (\`p-4\` on cards), compact typography (\`text-sm\` body, \`text-xs\` metadata), generous information per pixel.
12022
+
12023
+ Layout:
12024
+ \`\`\`
12025
+ \u250C\u2500 header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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
12026
+ \u2502 Overview [+ Quick Action] \u2502
12027
+ \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\u2524
12028
+ \u2502 \u250C\u2500 KPI strip (4 cols) \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 \u2502
12029
+ \u2502 \u2502 $47.2k 1,847 92% 23 min \u2502 \u2502
12030
+ \u2502 \u2502 +12% +8% +2% -15% \u2502 \u2502
12031
+ \u2502 \u2502 MRR Users Uptime Response \u2502 \u2502
12032
+ \u2502 \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\u2518 \u2502
12033
+ \u2502 \u250C\u2500 main panel (60%) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500 right rail \u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
12034
+ \u2502 \u2502 Revenue chart / pipeline \u2502 Recent activity \u2502 \u2502
12035
+ \u2502 \u2502 [line chart w/ axis] \u2502 \xB7 Deal closed \u2502 \u2502
12036
+ \u2502 \u2502 \u2502 \xB7 New signup \u2502 \u2502
12037
+ \u2502 \u2502 \u2502 \xB7 Invoice paid \u2502 \u2502
12038
+ \u2502 \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\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
12039
+ \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\u2518
12040
+ \`\`\`
12041
+
12042
+ ### Data Tables
12043
+
12044
+ The core UI pattern. Most B2B apps are "table + filters + detail pane" under the hood.
12045
+
12046
+ - **shadcn DataTable with sorting, filtering, pagination.** Not a plain \`<table>\`.
12047
+ - **Checkbox selection column on the left** for bulk actions.
12048
+ - **Status badges** in their own column: \`variant="secondary"\` for neutral, custom Tailwind for brand states.
12049
+ - **Row hover: \`bg-muted/40\`.** Click-through opens a detail sheet (desktop) or navigates (mobile).
12050
+ - **Filter bar above:** segmented control for common filters + "More filters" dropdown for power users.
12051
+ - **Empty state:** illustration or dashed border box with clear CTA ("No deals yet. Create your first deal.")
12052
+
12053
+ ### Detail / Drill-down Pages
12054
+
12055
+ - **Header row** with breadcrumbs, title, primary action button, secondary actions menu.
12056
+ - **Tabs** for sub-sections (Overview / Activity / Notes / Files). Tabs in a horizontal row below the header.
12057
+ - **Two-column layout on the Overview tab:** main content 70%, metadata sidebar 30%.
12058
+ - **Metadata sidebar** uses definition-list style: small label, value below. Grouped into cards.
12059
+ - **Activity feed** on the Activity tab: vertical timeline with event icons, actor, timestamp.
12060
+
12061
+ ### Forms
12062
+
12063
+ - **Multi-section forms** separated by \`<Separator />\` or card boundaries, NOT one long scroll.
12064
+ - **Inputs stacked vertically**, labels above, helper text below. 2-column grid only for short paired fields (first name / last name, city / zip).
12065
+ - **Inline validation**: red border + icon + short message beneath the input.
12066
+ - **Primary action sticky at the bottom** on long forms. "Save changes" or "Create X."
12067
+
12068
+ ## Component Patterns
12069
+
12070
+ ### Cards
12071
+
12072
+ Clean, functional, borderless-leaning:
12073
+
12074
+ \`\`\`
12075
+ bg-card border border-border rounded-lg p-5
12076
+ \`\`\`
12077
+
12078
+ - \`rounded-lg\` \u2014 professional, not overly friendly.
12079
+ - \`border\` over shadow for light mode. \`border-border/50 bg-card/60\` with backdrop-blur for dark mode (glass aesthetic).
12080
+ - Header row: title (\`text-sm font-medium\`) + action button or menu trigger aligned right.
12081
+ - Body: data, chart, or list. Minimal decoration.
12082
+
12083
+ ### Metrics and Stats
12084
+
12085
+ The bread and butter of this archetype:
12086
+
12087
+ - **Hero metric:** \`text-3xl md:text-4xl font-semibold tabular-nums\`. NOT \`font-extrabold\` (too loud for B2B).
12088
+ - **Delta indicator:** inline \`text-xs\` with color (green for positive, red for negative) and arrow icon (\`\u2191\` \`\u2193\`). Always include the baseline period: "+12% vs last month."
12089
+ - **Sparkline:** thin line (\`strokeWidth={1.5}\`), no fill, muted color. 40-60px height.
12090
+ - **Unit labels:** below the number, \`text-sm text-muted-foreground\`.
12091
+
12092
+ ### Navigation
12093
+
12094
+ - **Sidebar** is standard for B2B: 240-280px wide, collapsible, with sections.
12095
+ - **Section headers** in the sidebar: \`text-xs uppercase tracking-wider text-muted-foreground\`.
12096
+ - **Active link:** \`bg-muted\` + \`text-foreground\`, inactive: \`text-muted-foreground hover:bg-muted/50\`.
12097
+ - **Top bar** is thin (48-56px): breadcrumbs left, search middle (\`cmdk\` command palette), user menu right.
12098
+ - **Command palette** (\`\u2318K\`) is a must. Power users live in it. Actions, navigation, recent items.
12099
+
12100
+ ### Typography
12101
+
12102
+ - **Sans-serif heading font** \u2014 Inter is overused; prefer Geist, S\xF6hne (or IBM Plex Sans free alternative), or Manrope.
12103
+ - **Body:** same font or a humanist sans (Inter is acceptable for body, not heading).
12104
+ - **Numeric data:** always \`font-variant-numeric: tabular-nums\` or \`tabular-nums\` class.
12105
+ - **Scale:** \`text-xs\` metadata \u2192 \`text-sm\` body \u2192 \`text-base\` subheads \u2192 \`text-lg/xl\` section titles \u2192 \`text-2xl+\` page titles. Tight hierarchy.
12106
+
12107
+ ### Motion
12108
+
12109
+ - Scroll reveals: **fade-up, 200ms, staggered 40ms per card.** Fast, efficient \u2014 don't linger.
12110
+ - Hover: \`transition-colors\` 150ms on rows, cards, nav items.
12111
+ - Numbers on dashboard load: \`@number-flow/react\` counts up. Satisfying but not slow.
12112
+ - Skip entrance animations entirely on return visits if possible (the user opens this app 20\xD7/day).
12113
+ - Avoid: bouncy spring physics, floating decorative shapes, loading screens with branding.
12114
+
12115
+ ---
12116
+
12117
+ ## Landing Page Signature
12118
+
12119
+ For marketing pages, this archetype uses **product-first** patterns. The UI IS the marketing. Restore these from the old prism-dashboard / obsidian-tech / daylight-saas / crystal-clear presets.
12120
+
12121
+ ### Signature 1: Dashboard preview with parallax
12122
+
12123
+ The most recognizable B2B SaaS landing pattern.
12124
+
12125
+ - **Full-viewport hero** with headline left, dashboard screenshot right (or full-width below).
12126
+ - Dashboard image in a **browser frame** or bare with subtle border + shadow.
12127
+ - **Parallax scroll effect**: as user scrolls past the hero, dashboard moves slower than the text. \`useTransform(scrollY, [0, viewport.height], [0, -120])\`.
12128
+ - Optional: dashboard image renders with \`mixBlendMode: "luminosity"\` to tint it with the page background, then fades back to full color on scroll.
12129
+ - Fill the dashboard with **real-looking domain-specific data** \u2014 never generic "Sample Chart." If it's a CRM, show deal names; analytics, show metric names specific to the product.
12130
+
12131
+ \`\`\`tsx
12132
+ <motion.img
12133
+ src="/dashboard-preview.png"
12134
+ style={{ y: useTransform(scrollY, [0, 600], [0, -120]) }}
12135
+ className="rounded-2xl border border-border/60 shadow-2xl"
12136
+ />
12137
+ \`\`\`
12138
+
12139
+ ### Signature 2: Glass navbar + logo marquee
12140
+
12141
+ - **Fixed glass navbar** at top: \`fixed top-0 w-full backdrop-blur-md bg-background/80 border-b border-border/40 z-50\`.
12142
+ - Logo left, nav links center (3-5 items max), sign-in + primary CTA right.
12143
+ - **Logo marquee** below the hero: "Trusted by 2,847 teams" + infinite horizontal scroll of customer logos via \`motion\` with \`x: -100%\` loop. Logos in grayscale: \`opacity-50 brightness-0 [filter:invert(var(--invert-for-theme))]\`.
12144
+
12145
+ ### Signature 3: Alternating feature rows with product screenshots
12146
+
12147
+ - 3-4 feature sections below the hero, alternating image/text sides.
12148
+ - Each row: product screenshot (real UI, not a marketing mockup) in \`rounded-xl border shadow-xl\` on one side, feature name + 2-sentence benefit + bullet list on the other.
12149
+ - Scroll reveal: text fades up first, image slides in from its side (\`x: \xB130, opacity: 0 \u2192 1\`).
12150
+
12151
+ ### Signature 4: Pricing calculator section (ember-pricing pattern)
12152
+
12153
+ - Full-width section with 3 pricing tiers. The "Pro" tier has \`ring-2 ring-primary\` and a "Recommended" badge.
12154
+ - Interactive slider above the tiers that adjusts the price based on usage (seats, API calls, GB). Numbers animate with \`NumberFlow\`.
12155
+ - Feature list per tier: checkmarks in primary color, cross in muted for absent features.
12156
+
12157
+ ---
12158
+
12159
+ ## Copy Voice
12160
+
12161
+ - **Confident, specific, outcome-focused.** "Close 40% more deals" not "Boost sales productivity."
12162
+ - Numbers everywhere. Real ones if you have them; plausible-sounding if you don't.
12163
+ - No "revolutionize," "seamless," "leverage," "empower" \u2014 see landing-rules Tier 1 bans.
12164
+ - Feature names sound like UI elements: "Pipeline view," "Timeline export," "Saved filters." Not marketing phrases.
12165
+
12166
+ ---
12167
+
12168
+ ## Anti-patterns
12169
+
12170
+ - **Gradient-heavy hero backgrounds.** B2B looks cheap with aggressive purple/blue gradients. Use subtle mesh or none.
12171
+ - **Testimonials without specifics.** "Great product!" is worse than no testimonial. Every quote needs a name + company + concrete outcome.
12172
+ - **Floating abstract 3D shapes.** Signal of AI-generated landing. Skip.
12173
+ - **"Welcome to [Product]"** as hero headline. Lead with a specific value prop.
12174
+ - **Icon cards with generic icons** (rocket, lightning bolt, checkmark) as the feature section. Use real product screenshots or specific domain icons.
12175
+ - **Padding everywhere.** B2B users want density. Give them density.
12176
+ - **Inter as the only font.** Pair it with a distinctive heading font or swap it entirely.
12177
+ - **Centered text + centered image below.** The most overused AI hero. Use split or offset layouts.
12178
+ - **Animation on every element.** Efficient motion only: hero entrance, card reveals on scroll, number counters. Nothing else.
12179
+ `;var po=`# Content Editorial Archetype
12180
+
12181
+ 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.
12182
+
12183
+ These apps are about READING and WRITING words. The design must serve the text \u2014 never compete with it. Typography is the hero. Color is restrained. Everything else exists to make a 2,000-word piece a pleasure to read on a phone in bed.
12184
+
12185
+ ---
12186
+
12187
+ ## Theme
12188
+
12189
+ Default: **light-leaning.** Long-form reading on dark backgrounds causes eye strain for most readers, and the editorial convention (NYT, New Yorker, Stripe docs) is light. However, dark mode is a required toggle for any serious reading tool \u2014 the user decides, not you. Use semantic tokens so both themes render correctly.
12190
+
12191
+ Override to dark if the app's \`design.tone\` is \`dark-first\` (rare for content) or the brand explicitly wants cinematic/moody vibe (dark-mode-only magazine, music-industry publication).
12192
+
12193
+ ---
12194
+
12195
+ ## Page Composition
12196
+
12197
+ ### Home / Index Page
12198
+
12199
+ The front page is a **curated reading list**, not a dashboard.
12200
+
12201
+ - **Featured article** at the top: large thumbnail, display-size headline, 1-line summary, author + date + reading time. One hero piece.
12202
+ - **Secondary list** below: 3-column grid on desktop, single column on mobile. Smaller thumbnails, standard headline + summary.
12203
+ - **Categories or tags** as a horizontal pill row, not a sidebar. Sticky on scroll if long.
12204
+ - **Newsletter signup** embedded above the fold OR at the end of each article. Not a modal popup.
12205
+ - **No dashboard.** No KPIs. No activity feed. This isn't a SaaS app.
12206
+
12207
+ Layout:
12208
+ \`\`\`
12209
+ \u250C\u2500 header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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
12210
+ \u2502 Publication Name [Subscribe] [Sign in] \u2502
12211
+ \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\u2524
12212
+ \u2502 [Design] [Engineering] [Culture] [Longform] \u2502
12213
+ \u2502 \u2502
12214
+ \u2502 \u250C\u2500 featured article \u2500\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 \u2502
12215
+ \u2502 \u2502 [wide image, 16:9] \u2502 \u2502
12216
+ \u2502 \u2502 On Making Things That Last \u2502 \u2502
12217
+ \u2502 \u2502 by Jamie Chen \xB7 8 min read \xB7 Apr 12 \u2502 \u2502
12218
+ \u2502 \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\u2518 \u2502
12219
+ \u2502 \u2502
12220
+ \u2502 \u250C\u2500\u2500 3-up grid of recent articles \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
12221
+ \u2502 \u2502 [img] Title 1 [img] Title 2 [img] T3 \u2502 \u2502
12222
+ \u2502 \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\u2518 \u2502
12223
+ \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\u2518
12224
+ \`\`\`
12225
+
12226
+ ### Article / Post Page
12227
+
12228
+ The main event. This is where the archetype lives or dies.
12229
+
12230
+ - **Wide reading column:** \`max-w-prose\` (~65ch) for body text. No wider.
12231
+ - **Generous line-height:** \`leading-relaxed\` or \`leading-loose\` on body paragraphs.
12232
+ - **Sidebar TOC on desktop only** (hide below \`lg\`): sticky on scroll, active section highlighted via IntersectionObserver.
12233
+ - **Metadata header:** author avatar + name, date, reading time. Small, muted, above the title.
12234
+ - **Drop cap** on the first paragraph of longform pieces (\`::first-letter\` pseudo). Optional \u2014 use for signature pieces.
12235
+ - **Inline images** break out of the reading column: \`-mx-8 md:-mx-16\` with caption below in small italic.
12236
+ - **Pull quotes** in a larger serif face: \`text-2xl font-serif italic border-l-4 border-primary pl-6 my-12\`.
12237
+
12238
+ ### List / Archive Views
12239
+
12240
+ - **Chronological reverse-chron list** for archive.
12241
+ - Each row: thumbnail (square, 120-160px), headline, excerpt (1-2 lines, truncated), metadata row.
12242
+ - Category filter as a horizontal pill row above.
12243
+ - Pagination at the bottom (page numbers, not infinite scroll \u2014 readers want a sense of size).
12244
+
12245
+ ### Search
12246
+
12247
+ - \`/search\` page with a prominent search box at the top.
12248
+ - Results list: highlight matched terms in the excerpt with \`<mark className="bg-primary/10">\`.
12249
+ - Facets on the left: category, author, date range.
12250
+ - Empty state is warm: "No matches. Try another keyword, or browse by category."
12251
+
12252
+ ### Newsletter / Subscribe Pages
12253
+
12254
+ - Dedicated \`/subscribe\` page that pitches the newsletter \u2014 NOT a modal popup.
12255
+ - Value proposition: "What you'll get." Frequency: "Twice a month." Preview: link to an example issue.
12256
+ - Email input + submit button. That's it. No checkboxes for "marketing consent" unless legally required.
12257
+
12258
+ ## Component Patterns
12259
+
12260
+ ### Typography
12261
+
12262
+ This is the whole archetype. Get this right, everything else follows.
12263
+
12264
+ - **Heading font: a serif or refined sans.** Good pairings:
12265
+ - \`Fraunces\` (heading) + \`Inter\` (body) \u2014 modern editorial
12266
+ - \`Playfair Display\` (heading) + \`Source Serif 4\` (body) \u2014 classical
12267
+ - \`Instrument Serif\` (heading) + \`Geist Sans\` (body) \u2014 contemporary
12268
+ - \`Bricolage Grotesque\` (heading) + \`Source Serif 4\` (body) \u2014 expressive
12269
+ - Avoid: Inter as both heading and body. Editorial publications need typographic personality.
12270
+ - **Scale:** \`text-5xl md:text-6xl lg:text-7xl\` for article titles, \`text-lg md:text-xl\` for body, \`text-2xl\` for subheads in the article.
12271
+ - **Line height:** body is \`leading-relaxed\` (1.625) to \`leading-loose\` (2). Headlines are \`leading-tight\`.
12272
+ - **Tracking:** headlines \`tracking-tight\`, body default, small caps section markers \`tracking-widest uppercase\`.
12273
+ - **Serif italic for accent** on a single key word in the title (stolen from navy-serif preset): \`<span className="font-serif italic">that</span>\`.
12274
+
12275
+ ### Cards
12276
+
12277
+ Minimal, borderless, text-led:
12278
+
12279
+ \`\`\`
12280
+ no background, no border \u2014 just typography + spacing + optional thumbnail
12281
+ \`\`\`
12282
+
12283
+ - Thumbnail if present: \`aspect-[16/9] rounded-md\`, subtle hover scale.
12284
+ - Title: heading font, \`text-xl md:text-2xl tracking-tight\`.
12285
+ - Excerpt: \`text-muted-foreground leading-relaxed\`.
12286
+ - Metadata: \`text-xs uppercase tracking-wider text-muted-foreground mt-2\`.
12287
+ - No shadow, no border on cards. Spacing is the separator.
12288
+
12289
+ ### Navigation
12290
+
12291
+ - **Simple top nav.** Logo (publication name, often wordmark) left. 4-6 category links right. Search icon + subscribe CTA.
12292
+ - **No sidebar** except for the article TOC.
12293
+ - On mobile: hamburger for categories, search icon, subscribe CTA stays visible.
12294
+ - Breadcrumbs in archive pages: "Home \u203A Design \u203A Article Title."
12295
+
12296
+ ### Author bylines
12297
+
12298
+ - Small avatar + name + date + reading time.
12299
+ - On article page, appears both above the title and in a larger author-bio card at the end (with photo, bio, "more from this author" links).
12300
+
12301
+ ### Motion
12302
+
12303
+ - **Scroll reveals on section content,** NOT on every paragraph. Just the article header (fade-up 500ms) and any section breaks.
12304
+ - **Reading progress bar** at the top: thin line (\`h-0.5 bg-primary\`) that fills as user scrolls through the article.
12305
+ - **Image fade-in** on lazy-load with 200ms duration.
12306
+ - **Hover on article cards:** \`text-foreground\` on the title (link color transition). No lift, no scale.
12307
+ - Avoid: flashy animations during reading. Respect the reader.
12308
+
12309
+ ---
12310
+
12311
+ ## Landing Page Signature
12312
+
12313
+ For publications/newsletters with a dedicated marketing page (rare \u2014 the home page IS the marketing for most content sites).
12314
+
12315
+ ### Signature 1: Editorial hero (ivory-cinema / inkwell pattern)
12316
+
12317
+ - **Large display headline,** 2-3 lines, serif or refined sans. \`text-6xl md:text-8xl lg:text-9xl\` \u2014 unusually huge. Generous line-height.
12318
+ - One word of the headline is in **serif italic** accent (navy-serif pattern): \`<span className="font-serif italic text-primary">words</span>\`.
12319
+ - Subheadline in the body font, 1 sentence, muted.
12320
+ - Single CTA: "Subscribe" or "Read the archive." No secondary button.
12321
+ - Optional: large full-bleed photograph below, in grayscale with primary tint applied via \`mix-blend-color\`.
12322
+
12323
+ ### Signature 2: Monochrome editorial (inkwell pattern)
12324
+
12325
+ Strict grayscale palette with ONE accent color used sparingly. The whole landing is literally one color family.
12326
+
12327
+ - Background: pure white or cream (\`bg-background\`).
12328
+ - Text: all in \`foreground\` with \`/80\`, \`/60\`, \`/40\` opacity variations for hierarchy. No colored text except on hover states.
12329
+ - Dividers: \`border-border/60\` hairlines between sections.
12330
+ - Accent color only on links, the Subscribe button, and the occasional pull-quote border.
12331
+ - This looks sophisticated BECAUSE it's restrained. Don't add more colors.
12332
+
12333
+ ### Signature 3: Liquid-glass buttons (navy-serif pattern)
12334
+
12335
+ - CTAs use liquid-glass styling on a light background:
12336
+
12337
+ \`\`\`css
12338
+ .editorial-glass-btn {
12339
+ background: rgba(var(--foreground), 0.03);
12340
+ backdrop-filter: blur(12px);
12341
+ border: 1px solid rgba(var(--foreground), 0.08);
12342
+ border-radius: 9999px;
12343
+ padding: 0.75rem 1.5rem;
12344
+ }
12345
+ .editorial-glass-btn::before {
12346
+ content: "";
12347
+ position: absolute;
12348
+ inset: 0;
12349
+ border-radius: inherit;
12350
+ padding: 1px;
12351
+ background: linear-gradient(135deg, rgba(var(--foreground), 0.15), transparent);
12352
+ -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
12353
+ -webkit-mask-composite: xor;
12354
+ mask-composite: exclude;
12355
+ }
12356
+ \`\`\`
12357
+
12358
+ - Refined, premium, doesn't shout. Great for longform publication CTAs.
12359
+
12360
+ ### Signature 4: Featured-articles strip with oversized type
12361
+
12362
+ Below the hero: 3 featured pieces laid out as a horizontal strip with **oversized serial numbers** (01, 02, 03) next to each title. Rest of the card is minimal \u2014 just headline + byline + date.
12363
+
12364
+ ---
12365
+
12366
+ ## Copy Voice
12367
+
12368
+ - **Conversational, confident, literary.** Write like a human who cares about the topic.
12369
+ - Headlines can be long and declarative: "The Case for Making Things That Break" is better than "Quality Over Quantity."
12370
+ - Byline matters. "by Jamie Chen" always present, even if fictional for the landing.
12371
+ - No "revolutionize," "leverage," corporate filler. This is the one archetype where you actively AVOID SaaS copy patterns.
12372
+
12373
+ ---
12374
+
12375
+ ## Anti-patterns
12376
+
12377
+ - **Sans-serif everything.** The whole point of editorial is typographic character. Use a serif somewhere \u2014 headline, pull quote, drop cap, byline.
12378
+ - **Narrow reading column wider than \`max-w-prose\`.** Eye tracking suffers over ~75 characters per line.
12379
+ - **Cards with borders AND shadows AND backgrounds.** Editorial cards are minimal \u2014 text + spacing, nothing more.
12380
+ - **Newsletter popup on load.** Readers haven't read anything yet. Put the subscribe block after they finish an article (end-of-post) or at the end of the home page.
12381
+ - **Auto-playing video or audio** in article cards. Respect the reader's focus.
12382
+ - **Ads/sponsored content** indistinguishable from real articles. If sponsored, label clearly.
12383
+ - **Dark mode as default for a reading tool.** Offer it as a toggle, not the default.
12384
+ - **Flashy motion while reading.** Scroll hijacking, parallax on body text, animated backgrounds behind paragraphs \u2014 all break reading flow.
12385
+ - **More than 2 fonts.** Heading + body is enough. Adding a third font for captions or metadata dilutes the identity.
12386
+ `;var uo=`# Devtool Technical Archetype
12387
+
12388
+ 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.
12389
+
12390
+ These apps serve DEVELOPERS. The user is technical, skeptical, allergic to marketing fluff, and wants to see the API/CLI/code before signing up. The UI must look like it was built by someone who ships code every day.
12391
+
12392
+ ---
12393
+
12394
+ ## Theme
12395
+
12396
+ Default: **dark-leaning.** Developer tools overwhelmingly default to dark \u2014 it's the terminal convention, reduces eye strain during long sessions, and signals "we're for people who hack at night." However, a light mode toggle is essential (not every dev runs dark) and must be well-tuned, not an afterthought.
12397
+
12398
+ Override to light if the app's \`design.tone\` is explicitly \`warm-minimal\` or \`clean-saas\` (rare for dev tools).
12399
+
12400
+ All CSS uses semantic tokens. Glow, neon, and heavy shadow effects should ONLY appear when the resolved theme is dark \u2014 they look tacky on light.
12401
+
12402
+ ---
12403
+
12404
+ ## Page Composition
12405
+
12406
+ ### Dashboard / Console
12407
+
12408
+ The dashboard mimics a terminal's functional density. No decoration, maximum information.
12409
+
12410
+ - **Project / resource list at the top.** Compact cards or a table. Filter by status (running, errored, deployed).
12411
+ - **Logs / events stream as the primary panel.** Terminal-styled: monospace font, line numbers, syntax-highlighted JSON, timestamps in ISO format.
12412
+ - **Metrics strip** (optional): request count, error rate, p95 latency. \`tabular-nums\`, \`text-sm\`, no fancy charts.
12413
+ - **Keyboard shortcut hints** visible everywhere: \`g d\` to go to deployments, \`\u2318K\` for palette, \`?\` for help.
12414
+
12415
+ Layout:
12416
+ \`\`\`
12417
+ \u250C\u2500 header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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
12418
+ \u2502 my-project \u25B8 production [Deploy] [\u2318K] \u2502
12419
+ \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\u2524
12420
+ \u2502 \u25CF Deployment \xB7 3m ago \xB7 api.example.com \u2502
12421
+ \u2502 \u250C\u2500 logs stream \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 \u2502
12422
+ \u2502 \u2502 12:34:01.122 GET /api/users 200 42ms\u2502 \u2502
12423
+ \u2502 \u2502 12:34:01.156 POST /api/auth/login 401 12ms\u2502 \u2502
12424
+ \u2502 \u2502 12:34:02.003 GET /api/health 200 3ms\u2502 \u2502
12425
+ \u2502 \u2502 12:34:02.541 ERROR: connection timeout \u2502 \u2502
12426
+ \u2502 \u2502 \u2514\u2500 stack trace (click to expand) \u2502 \u2502
12427
+ \u2502 \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\u2518\u2502
12428
+ \u2502 [filter: errors only] [pause stream] \u2502
12429
+ \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\u2518
12430
+ \`\`\`
12431
+
12432
+ ### Code / API Reference View
12433
+
12434
+ If the app ships an SDK or API, the docs/reference page IS a first-class product surface.
12435
+
12436
+ - **Two-column layout:** prose left (description, params table), code right (sticky on scroll).
12437
+ - **Code blocks** with syntax highlighting (\`shiki\` or \`prism-react-renderer\`), line numbers, copy button, language tabs (JavaScript / Python / curl / Rust).
12438
+ - **Inline code** (\`<code>\` spans) in \`font-mono text-sm bg-muted/50 px-1 py-0.5 rounded\`.
12439
+ - **API endpoint signatures:** \`GET /api/users/:id\` in monospace with colored method (green for GET, blue for POST, orange for PATCH, red for DELETE).
12440
+
12441
+ ### Detail / Drill-down (logs, traces, errors)
12442
+
12443
+ - **Header** with breadcrumbs, title, filter controls.
12444
+ - **Details in a key-value grid:** \`grid-cols-[max-content_1fr] gap-x-4\` for aligned labels and values.
12445
+ - **Raw JSON / payload** in a collapsible code block at the bottom.
12446
+ - **Timeline view** for traces: horizontal bars per span, labels, duration.
12447
+ - **Copy commands** everywhere (CLI snippets, curl commands, IDs).
12448
+
12449
+ ### Forms
12450
+
12451
+ Dev-tool forms are functional, not decorated:
12452
+
12453
+ - **Monospace for technical inputs**: environment variables, API keys, commands, URLs.
12454
+ - **Placeholder inputs show real examples**: \`KEY=value\`, \`https://api.example.com\`, \`const x = 42\`.
12455
+ - **Keyboard shortcuts** in labels: "Deploy (\u2318\u23CE)."
12456
+ - **Validation**: inline, specific, technical. "Key must match /^[A-Z_][A-Z0-9_]*$/" not "Invalid format."
12457
+
12458
+ ## Component Patterns
12459
+
12460
+ ### Cards
12461
+
12462
+ Dark-mode glass, light-mode flat bordered:
12463
+
12464
+ \`\`\`
12465
+ dark: bg-card/40 backdrop-blur-sm border border-border/50 rounded-lg p-5
12466
+ light: bg-card border border-border rounded-lg p-5
12467
+ \`\`\`
12468
+
12469
+ - Status indicator as a colored dot (\`\u25CF\`) on the left \u2014 not a badge. \`bg-green-500\` running, \`bg-yellow-500\` building, \`bg-red-500\` errored, \`bg-muted\` idle.
12470
+ - Header: technical name in monospace (project slug, resource ID), human-friendly name below in regular font.
12471
+ - Metadata row: created date in relative time ("3m ago"), owner, region.
12472
+
12473
+ ### Typography
12474
+
12475
+ - **Heading font:** geometric sans (Geist, Inter, S\xF6hne, Departure Mono for novelty). Never a serif.
12476
+ - **Body font:** same as heading or a humanist sans.
12477
+ - **Monospace font:** JetBrains Mono, Geist Mono, IBM Plex Mono, or Berkeley Mono. Used for: code, API endpoints, IDs, keyboard shortcuts, tabular data.
12478
+ - **Scale:** smaller than SaaS-analytical. \`text-xs\` for metadata, \`text-sm\` for body, tight hierarchy.
12479
+ - **Weight:** \`font-medium\` default for headings, \`font-normal\` for body. Avoid \`font-bold\` except for critical emphasis \u2014 developers read a lot of type, heavy weights fatigue.
12480
+
12481
+ ### Metrics and Data
12482
+
12483
+ - **Numbers in monospace** (\`tabular-nums\` minimum, full monospace font for raw data).
12484
+ - **Unit suffixes in muted color:** "42" in foreground, "ms" or "MB" after in \`text-muted-foreground\`.
12485
+ - **Sparklines with event markers:** dots on the line where deploys happened, errors occurred.
12486
+ - **Relative timestamps with absolute on hover:** "3m ago" \u2192 tooltip shows "2026-04-19 12:34:01 UTC."
12487
+
12488
+ ### Navigation
12489
+
12490
+ - **Sidebar** with sections: Overview, Deployments, Logs, Metrics, Settings.
12491
+ - **Environment switcher** at the top of the sidebar: "production \u25BE" dropdown.
12492
+ - **Keyboard shortcuts** next to each sidebar link (small, muted, right-aligned): "Deploy \u2318\u23CE."
12493
+ - **Command palette** (\`\u2318K\`) is MANDATORY. All navigation and actions accessible.
12494
+ - **Top bar:** breadcrumbs left (click to navigate up), deploy status indicator center, user menu right.
12495
+
12496
+ ### Motion
12497
+
12498
+ - **Minimal, functional.** Fade-ins under 150ms. No bounces, no spring physics, no floating.
12499
+ - **Log streams:** new rows append with a brief background flash (\`bg-primary/5\` fading out over 600ms) \u2014 highlights recency without being distracting.
12500
+ - **Status dot pulse:** \`animate-pulse\` on "running" or "live" states. Nothing else pulses.
12501
+ - **Command palette:** opens with a subtle scale-in (\`scale-95 \u2192 1\`, 120ms).
12502
+ - Avoid: any animation that slows down workflow. Developers are impatient.
12503
+
12504
+ ---
12505
+
12506
+ ## Landing Page Signature
12507
+
12508
+ For dev tool marketing pages. Restore patterns from obsidian-tech / spectrum-ai / neural-edge / chain-zero / cobalt-builder presets.
12509
+
12510
+ ### Signature 1: Terminal / code block hero (obsidian-tech pattern)
12511
+
12512
+ The single most effective dev-tool landing pattern: show the code.
12513
+
12514
+ - **Split-panel hero:** headline + subheadline left (~45%), animated terminal or code block right (~55%).
12515
+ - Code block styled as a terminal: \`bg-black/90 rounded-xl border border-border/40 p-6 font-mono text-sm\`. macOS-style window chrome on top (3 dots: red / yellow / green).
12516
+ - Inside: type out the install command or key API call character-by-character (typewriter effect from typewriter-intro). Then output appears below.
12517
+ - Code is REAL and copy-pastable. Not fake syntax.
12518
+
12519
+ \`\`\`tsx
12520
+ <pre className="bg-black/90 rounded-xl border border-border/40 p-6 font-mono text-sm">
12521
+ <div className="flex gap-2 mb-4">
12522
+ <span className="w-3 h-3 rounded-full bg-red-500/80" />
12523
+ <span className="w-3 h-3 rounded-full bg-yellow-500/80" />
12524
+ <span className="w-3 h-3 rounded-full bg-green-500/80" />
12525
+ </div>
12526
+ <code>
12527
+ <span className="text-green-400">$</span>{" "}
12528
+ <TypewriterText text="npx @your-sdk/cli init my-app" />
12529
+ </code>
12530
+ </pre>
12531
+ \`\`\`
12532
+
12533
+ ### Signature 2: Glass navbar with badge row (obsidian-tech pattern)
12534
+
12535
+ - **Fixed glass navbar:** \`fixed backdrop-blur-md bg-background/80 border-b border-border/40\`.
12536
+ - **Below the hero:** row of 3 "Integrated with..." badges in liquid-glass style, each with a partner logo and short label. Horizontal, centered.
12537
+
12538
+ ### Signature 3: Gradient headline + logo marquee (gradient-titan / spectrum-ai pattern)
12539
+
12540
+ - **Massive headline** (\`text-6xl md:text-8xl lg:text-9xl font-medium tracking-[-2px]\`) with a gradient on ONE keyword: \`bg-gradient-to-r from-primary to-primary/60 bg-clip-text text-transparent\`.
12541
+ - **Logo marquee** just below the CTA: "Trusted by teams at..." with infinite horizontal scroll of customer logos. Grayscale, \`opacity-50 hover:opacity-100\`.
12542
+ - Background: subtle mesh gradient with primary color at 5-10% opacity, positioned asymmetrically.
12543
+
12544
+ ### Signature 4: Code example section with language tabs
12545
+
12546
+ Below the hero: a "how it works" section featuring a large code example with tabs for different languages/frameworks. Real code, syntax highlighted.
12547
+
12548
+ ### Signature 5: Stats section with terminal output
12549
+
12550
+ Numbers styled as terminal output: \`> 47,293 deploys this week\`, \`> 99.98% uptime last 30d\`, \`> 12ms p50 latency\`. All in monospace, \`text-muted-foreground\` prefix, \`text-foreground\` number.
12551
+
12552
+ ---
12553
+
12554
+ ## Copy Voice
12555
+
12556
+ - **Technical, confident, specific.** "Ship in 30 seconds. No config." not "Accelerate your development workflow."
12557
+ - **Lead with the command or API call.** \`npx create-my-app\` as the hero, with prose wrapped around it.
12558
+ - **Numbers are p50/p95/p99, request counts, bundle sizes, MB/s.** Real technical metrics, not "ROI" or "productivity."
12559
+ - **Name-drop specific technologies** your tool works with: "Ships with TypeScript. Works with Next.js, Remix, Astro."
12560
+ - Avoid: "empower," "enterprise-grade" (except for enterprise sales), "revolutionize," anything sales-flavored.
12561
+
12562
+ ---
12563
+
12564
+ ## Anti-patterns
12565
+
12566
+ - **Stock photos of diverse happy teams.** Developer tools don't use lifestyle photography. Use code, terminal output, architecture diagrams.
12567
+ - **Gradient blobs with no code shown.** The landing page must show code. Hiding what the product actually is makes developers bounce.
12568
+ - **Light mode with heavy neon effects.** Neon/glow looks cheap on light backgrounds. If you want glow, ship dark mode as default.
12569
+ - **Purple/blue gradients as the primary visual.** Devtool clich\xE9. If you use a gradient, make it unusual (green, amber, teal) or skip it.
12570
+ - **Hamburger menus on desktop.** Developer tools show the full nav \u2014 we're not hiding complexity from power users.
12571
+ - **Onboarding gated behind a sign-up.** Offer a "try without account" or "read the docs" path. Developers want to evaluate before committing.
12572
+ - **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).
12573
+ - **Feature cards with rocket / lightning / puzzle icons.** Use technical icons (terminal, code, database, network) or skip icons entirely.
12574
+ - **Motion on code blocks** while the user is trying to read them. Type out ONCE on load, then leave them alone.
12575
+ `;var ho=`# Creative Showcase Archetype
12576
+
12577
+ 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.
12578
+
12579
+ These apps are about VISUAL CRAFT. The design is itself the portfolio. Every pixel signals taste. The user is usually a prospective client evaluating whether you can do great work for them \u2014 the site answers that question.
12580
+
12581
+ ---
12582
+
12583
+ ## Theme
12584
+
12585
+ Default: **either** \u2014 creative work splits evenly between dark (cinematic, moody, premium) and light (editorial, minimal, refined). Pick based on the work being showcased:
12586
+ - **Photography, architecture, filmmaking, luxury brands:** default dark for drama.
12587
+ - **Product design, illustration, type design, agencies:** default light for clarity.
12588
+
12589
+ Both render well. Override to follow the app's \`design.tone\`. This archetype is the one where pushing the design boundary is most encouraged \u2014 break your own rules if the craft calls for it.
12590
+
12591
+ ---
12592
+
12593
+ ## Page Composition
12594
+
12595
+ ### Home / Index Page
12596
+
12597
+ The home is a **work reel**. Visitors see the caliber of work in the first 5 seconds or they leave.
12598
+
12599
+ - **Immediate visual impact.** Hero is a single striking image, video loop, or animated composition \u2014 NOT a headline-first layout. The visual IS the value prop.
12600
+ - **Work grid** fills most of the page: mixed aspect ratios (bento style), large imagery, minimal text overlays.
12601
+ - **Short intro paragraph** (1-2 sentences, oversized body type) positioned unusually \u2014 offset, rotated, or bleeding over the work grid.
12602
+ - **Contact / Inquire CTA** is the primary action. Everything else is secondary.
12603
+ - **Minimal navigation** \u2014 Work / About / Contact. That's often enough.
12604
+
12605
+ Layout:
12606
+ \`\`\`
12607
+ \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\u2510
12608
+ \u2502 STUDIO NAME [Work] [Say hi]\u2502
12609
+ \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\u2524
12610
+ \u2502 \u2502
12611
+ \u2502 \u250C\u2500 fullscreen hero image/video \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
12612
+ \u2502 \u2502 \u2502 \u2502
12613
+ \u2502 \u2502 [cinematic image or looped video] \u2502 \u2502
12614
+ \u2502 \u2502 \u2502 \u2502
12615
+ \u2502 \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\u2518 \u2502
12616
+ \u2502 \u2502
12617
+ \u2502 A studio that makes things \u2502
12618
+ \u2502 that feel inevitable. \u2502
12619
+ \u2502 \u2502
12620
+ \u2502 \u250C\u2500 bento work grid (mixed sizes) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
12621
+ \u2502 \u2502 [wide] \u2502 [sqr] \u2502 \u2502
12622
+ \u2502 \u2502 Project One \u2502 Project Two \u2502 \u2502
12623
+ \u2502 \u2502 \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\u2524 \u2502
12624
+ \u2502 \u2502 \u2502 [tall] \u2502 \u2502
12625
+ \u2502 \u2502 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524 Project Three \u2502 \u2502
12626
+ \u2502 \u2502 [sqr] [sqr] \u2502 \u2502 \u2502
12627
+ \u2502 \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\u2518 \u2502
12628
+ \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\u2518
12629
+ \`\`\`
12630
+
12631
+ ### Project / Case Study Page
12632
+
12633
+ When the visitor clicks a project, they expect depth.
12634
+
12635
+ - **Hero image** full-bleed, often with the project title overlaid at the bottom.
12636
+ - **Metadata strip:** year, client, role (Design + Creative Direction), team (if relevant).
12637
+ - **Long scroll** with alternating wide images, 2-up grids, text blocks, and occasional video embeds.
12638
+ - **Pull quotes** from the creative brief or client in oversized serif italic.
12639
+ - **Generous whitespace** \u2014 120-200px between sections. The content breathes.
12640
+ - **Navigation between projects** at the bottom: "Previous" / "Next" with project thumbnails.
12641
+
12642
+ ### About / Studio Page
12643
+
12644
+ - **Portrait photo or team image** \u2014 large, cinematic, not a LinkedIn corporate shot.
12645
+ - **Manifesto-style writing:** confident voice, specific beliefs, short paragraphs.
12646
+ - **Capabilities list:** what the studio does. Often in small caps or oversized numbered list.
12647
+ - **Client list:** logos in grayscale, or wordmarks in a single-weight text list. No starred ratings, no testimonial cards.
12648
+
12649
+ ### Contact / Inquire Page
12650
+
12651
+ - **Simple form** with 4-5 fields: name, email, project type (select), budget range, message.
12652
+ - OR: a large email address with an arrow icon, "Send a note \u2192." No form at all \u2014 just an email link.
12653
+ - **Availability status:** "Currently booking Q2 2026 projects." Small, matter-of-fact.
12654
+
12655
+ ## Component Patterns
12656
+
12657
+ ### Cards (Work Grid)
12658
+
12659
+ The work grid is the CORE component. It must be great.
12660
+
12661
+ \`\`\`
12662
+ bento grid: 3-column, mixed sizes, minimal chrome
12663
+ \`\`\`
12664
+
12665
+ - **No card borders or shadows.** The image is the card. Rounded corners (\`rounded-xl\` or \`rounded-2xl\`) on the image itself.
12666
+ - **Metadata overlays** appear on hover: project title + client slides up from the bottom with a subtle gradient overlay. Otherwise, image only.
12667
+ - **Aspect ratios vary:** 16:9, 1:1, 4:5, 3:2. Grid uses CSS grid with \`grid-template-rows: masonry\` (or fallback to explicit row spans).
12668
+ - **Hover effect:** subtle scale (\`hover:scale-[1.02]\`) + shadow appearance + overlay. NOT rotation or skew.
12669
+
12670
+ ### Typography
12671
+
12672
+ This is where creative-showcase distinguishes itself from SaaS-analytical.
12673
+
12674
+ - **Heading font: distinctive and dramatic.** Options:
12675
+ - \`Cabinet Grotesque\` / \`Clash Display\` / \`Satoshi\` \u2014 contemporary geometric
12676
+ - \`Editorial New\` / \`Fraunces\` / \`Playfair Display\` \u2014 editorial serif
12677
+ - \`Neue Haas Grotesk\` / \`S\xF6hne\` \u2014 Swiss-inspired classic
12678
+ - \`Monument Grotesk\` / \`S\xF6hne Breit\` \u2014 extended display
12679
+ - **Body font:** humanist sans (S\xF6hne, Inter, DM Sans) OR a readable serif (Source Serif 4).
12680
+ - **Scale is dramatic:** \`text-8xl md:text-9xl\` for hero titles, sometimes even larger with \`clamp()\`. Tight leading.
12681
+ - **Oversized numerals** as graphic elements: project numbers, year markers, section counts. \`text-[clamp(4rem,15vw,12rem)] font-medium tabular-nums\`.
12682
+ - **Serif italic accent** on one or two words in display text.
12683
+
12684
+ ### Navigation
12685
+
12686
+ - **Minimal top nav.** Logo (wordmark or monogram) left. 2-3 links right. Often no hamburger \u2014 just the links.
12687
+ - **On scroll:** nav remains visible but sometimes shrinks or changes background (from transparent to glass).
12688
+ - **Page transitions** are common for agencies: fade or slide to next page via a page-transition library (e.g., framer-motion AnimatePresence or similar).
12689
+
12690
+ ### Motion
12691
+
12692
+ Motion is a STORYTELLING tool for this archetype. More allowed than elsewhere, but must be purposeful.
12693
+
12694
+ - **Scroll-driven reveals:** images fade + scale up as they enter viewport. Use \`useScroll\` + \`useTransform\` for the effect.
12695
+ - **Text reveals:** words or lines slide up with stagger (\`BlurText\` pattern from onyx-studio \u2014 word-by-word blur-to-clear).
12696
+ - **Page transitions:** fade or slide between routes.
12697
+ - **Hover on work tiles:** scale + overlay appearance, 300-400ms ease-out.
12698
+ - **Scroll-pinned sections:** GSAP ScrollTrigger for hero sections that stay fixed while content scrolls over them (common in agency sites).
12699
+ - **Cursor effects** (optional): custom cursor on work grid that shows "View project" or project name when hovering tiles.
12700
+
12701
+ ---
12702
+
12703
+ ## Landing Page Signature
12704
+
12705
+ The home page IS the landing page for this archetype. But if there's a dedicated \`/services\` or \`/about\` marketing page, here's the signature. Restore patterns from onyx-studio / aurora-glass / stellar-folio / studio-blanc / volt-agency presets.
12706
+
12707
+ ### Signature 1: Liquid-glass agency (onyx-studio pattern)
12708
+
12709
+ Premium Apple-inspired aesthetic with \`.liquid-glass\` CSS classes applied to major elements.
12710
+
12711
+ \`\`\`css
12712
+ .liquid-glass {
12713
+ background: rgba(var(--foreground-rgb), 0.03);
12714
+ backdrop-filter: blur(20px);
12715
+ -webkit-backdrop-filter: blur(20px);
12716
+ border-radius: 1.5rem;
12717
+ position: relative;
12718
+ }
12719
+ .liquid-glass::before {
12720
+ content: "";
12721
+ position: absolute;
12722
+ inset: 0;
12723
+ border-radius: inherit;
12724
+ padding: 1px;
12725
+ background: linear-gradient(135deg,
12726
+ rgba(var(--foreground-rgb), 0.2),
12727
+ rgba(var(--foreground-rgb), 0.02));
12728
+ -webkit-mask:
12729
+ linear-gradient(#fff 0 0) content-box,
12730
+ linear-gradient(#fff 0 0);
12731
+ -webkit-mask-composite: xor;
12732
+ mask-composite: exclude;
12733
+ pointer-events: none;
12734
+ }
12735
+ .liquid-glass-strong { /* same but with blur(30px) and stronger border */ }
12736
+ \`\`\`
12737
+
12738
+ Apply \`.liquid-glass\` to: navbar, nav pill menus, CTA buttons, stat containers, feature cards.
12739
+
12740
+ ### Signature 2: Split-panel liquid glass (frost-bloom pattern)
12741
+
12742
+ Two-panel hero with the right side being a liquid-glass-strong overlay on a dark mesh gradient background.
12743
+
12744
+ - Left panel (52%): headline + subtext + CTA + optional feature pills below.
12745
+ - Right panel (48%): liquid glass container with community card, feature cards, or social icons in glass pills. Hidden on mobile.
12746
+ - Absolute inset-4 rounded-3xl on the glass panels.
12747
+
12748
+ ### Signature 3: GSAP bento portfolio (stellar-folio pattern)
12749
+
12750
+ - **Hero:** cinematic full-bleed with massive display type. Maybe a scroll-hijacked intro where text assembles.
12751
+ - **Work section:** bento grid with mixed aspect ratios. Each tile reveals on scroll with a clip-path reveal or BlurText effect.
12752
+ - **Scroll-pinned testimonial:** quote scales up + fades in as it crosses viewport center. Author + role appear with a delay.
12753
+ - **Stats section:** large serif italic numbers (\`text-8xl md:text-9xl font-serif italic\`), small labels below.
12754
+
12755
+ ### Signature 4: Typewriter intro / loading screen (typewriter-intro pattern)
12756
+
12757
+ Premium agency touch: on first load, a full-screen loading component shows the studio name being typed character-by-character, then a brief pause, then fades out to reveal the site.
12758
+
12759
+ - Only on first load \u2014 use \`sessionStorage\` to skip on subsequent navigations.
12760
+ - 1.5-2 seconds total duration. Not longer.
12761
+ - Falls back to no-intro on \`prefers-reduced-motion\`.
12762
+
12763
+ ### Signature 5: Cinematic serif headline (navy-serif / ivory-cinema pattern)
12764
+
12765
+ - Full-viewport hero with display serif type, one word in italic.
12766
+ - Subtle gradient glow behind the text (if dark) or desaturated photo background (if light).
12767
+ - Single CTA in liquid-glass style.
12768
+
12769
+ ---
12770
+
12771
+ ## Copy Voice
12772
+
12773
+ - **Confident, understated, specific.** "We help brands feel inevitable" not "We build cutting-edge creative solutions."
12774
+ - **First person plural** ("We design...") or third person ("The studio makes..."). Either works.
12775
+ - Client names and deliverables over abstractions: "Identity and packaging for Oatly. Motion design for Apple." Specifics > adjectives.
12776
+ - Manifesto beliefs are welcome: "We believe the web can feel handmade." A point of view is the product.
12777
+
12778
+ ---
12779
+
12780
+ ## Anti-patterns
12781
+
12782
+ - **Stock photos of abstract people in coworking spaces.** Real work, real photography only.
12783
+ - **"Award-winning" as a tagline without specific awards.** Name the awards or delete the word.
12784
+ - **Long paragraphs of marketing copy on the home page.** Show the work. Talk about it only if asked (About page).
12785
+ - **Carousel of client logos that auto-rotates.** Static grid is better \u2014 visitor can read at their own pace.
12786
+ - **Every project identical in the grid.** Vary aspect ratios and size. Make the grid compositional, not a simple 3-up.
12787
+ - **Starred testimonials or review widgets.** Too B2B. Use pull quotes from specific clients instead.
12788
+ - **Purple-to-blue gradient anywhere.** Worst possible signal of "I used a default."
12789
+ - **Scroll hijacking through the whole site.** Use sparingly \u2014 one or two scroll-pinned sections maximum.
12790
+ - **Mystery-meat navigation.** Clever is bad here. "Work / About / Contact" is fine. Don't make visitors guess.
12791
+ - **No contact info.** The primary purpose of this site is to generate inquiries. Make it stupid-easy to contact you.
12792
+ `;var go=`# Finance Clarity Archetype
12793
+
12794
+ 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.
12795
+
12796
+ These apps handle the user's MONEY. The UI must feel trustworthy, precise, calm. A single misaligned number or sloppy alignment undermines the whole product. Numbers are the hero \u2014 design around them.
12797
+
12798
+ ---
12799
+
12800
+ ## Theme
12801
+
12802
+ Default: **light-leaning.** Money apps overwhelmingly default to light (Stripe, Wise, QuickBooks) because clarity and trust are the job. Light mode signals transparency. Dark mode is offered as a toggle but rarely default.
12803
+
12804
+ Exceptions: crypto / trading platforms often default dark (Binance, Coinbase Pro) because the audience expects terminal aesthetics. If the app is crypto-specific and \`design.tone\` is \`dark-first\`, go dark.
12805
+
12806
+ All numeric display uses \`tabular-nums\` in BOTH themes \u2014 misaligned digits ruin credibility.
12807
+
12808
+ ---
12809
+
12810
+ ## Page Composition
12811
+
12812
+ ### Dashboard / Overview
12813
+
12814
+ The dashboard is a **financial statement at a glance**. Calm, organized, trustworthy.
12815
+
12816
+ - **Primary balance / total at the top.** Huge, centered or left-aligned. "Your balance: $12,847.53" in \`text-5xl md:text-6xl font-semibold tabular-nums\`.
12817
+ - **Delta row below:** "+$842 this month" with sparkline showing the trend. Small, matter-of-fact.
12818
+ - **Account / category list:** vertical list of sub-totals that roll up to the primary number. Each row: name + amount + optional progress bar.
12819
+ - **Recent transactions panel:** chronological list. Icon + merchant + amount + category. Negative in red, positive in green (or the app's semantic tokens).
12820
+ - **Primary actions:** "Send," "Request," "Pay bill," "Add transaction" \u2014 floating buttons or a horizontal row.
12821
+
12822
+ Layout:
12823
+ \`\`\`
12824
+ \u250C\u2500 header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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
12825
+ \u2502 Dashboard Apr 19 \xB7 [Add entry] \u2502
12826
+ \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\u2524
12827
+ \u2502 \u2502
12828
+ \u2502 Balance \u2502
12829
+ \u2502 $12,847.53 \u2502
12830
+ \u2502 +$842.12 this month \u2581\u2582\u2583\u2582\u2584\u2585\u2586\u2585\u2586 \u2502
12831
+ \u2502 \u2502
12832
+ \u2502 \u250C\u2500 accounts (list) \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 \u2502
12833
+ \u2502 \u2502 \u25FC Checking $8,402.18 \u2502 \u2502
12834
+ \u2502 \u2502 \u25FC Savings $4,242.10 \u2502 \u2502
12835
+ \u2502 \u2502 \u25FC Credit card -$203.25 [Pay] \u2502 \u2502
12836
+ \u2502 \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\u2518 \u2502
12837
+ \u2502 \u2502
12838
+ \u2502 \u250C\u2500 recent transactions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
12839
+ \u2502 \u2502 Today \u2502 \u2502
12840
+ \u2502 \u2502 \u25CB Blue Bottle Coffee -$4.75 \u2502 \u2502
12841
+ \u2502 \u2502 \u25CB Spotify Premium -$10.99 \u2502 \u2502
12842
+ \u2502 \u2502 Yesterday \u2502 \u2502
12843
+ \u2502 \u2502 \u25CB Salary - Acme Corp +$4,250.00 \u2502 \u2502
12844
+ \u2502 \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\u2518 \u2502
12845
+ \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\u2518
12846
+ \`\`\`
12847
+
12848
+ ### Transaction / Detail View
12849
+
12850
+ - **Amount hero:** the transaction amount in huge type at the top. Signed (+ or \u2212) with color.
12851
+ - **Metadata grid:** Date, Time, Category, Account, Reference. Two columns, key-value layout.
12852
+ - **Receipt / description:** if available, shown below. Merchant logo (or fallback initials) as a 48px circle.
12853
+ - **Related transactions:** "From this merchant" list or "Same category this month" sum.
12854
+ - **Action buttons:** "Dispute," "Categorize," "Export," "Split." Grouped at the bottom.
12855
+
12856
+ ### Lists / Tables (Transactions, Invoices, Expenses)
12857
+
12858
+ - **shadcn DataTable with sticky totals row at the bottom.** Running total always visible.
12859
+ - **Columns:** Date, Description, Category (badge), Amount (right-aligned, \`tabular-nums\`).
12860
+ - **Row hover:** subtle \`bg-muted/40\`. Click opens the detail sheet.
12861
+ - **Filter bar:** date range (preset chips: "This month", "Last 30 days", "YTD"), category multi-select, amount range slider.
12862
+ - **Bulk actions** when rows are selected: categorize, export, delete. Toolbar appears above the table.
12863
+ - **Empty state:** "No transactions in this period." With CTA to add one or change the filter.
12864
+
12865
+ ### Forms (Transaction Entry, Invoice Creation)
12866
+
12867
+ - **Amount input is the protagonist.** Big, centered, formatted as currency with live input mask. \`text-4xl font-semibold tabular-nums\`.
12868
+ - **Date / time pickers** use shadcn Calendar. Default to today.
12869
+ - **Category picker:** icon grid or searchable dropdown with common categories listed first.
12870
+ - **Description / notes:** textarea, smaller.
12871
+ - **Attachments:** receipt upload with drag-drop zone. Preview thumbnail on upload.
12872
+ - **Submit button at the bottom, full-width on mobile:** "Add transaction" or "Save invoice."
12873
+
12874
+ ### Reports / Analytics
12875
+
12876
+ - **Charts are informative, not decorative.** Line charts for trends, bar charts for comparisons, pie charts for category breakdowns (though bar is usually better).
12877
+ - **Recharts or visx** with semantic token colors \u2014 never hardcoded.
12878
+ - **Always include the source data** as a table below the chart. Users audit charts against raw numbers.
12879
+ - **Export options:** CSV, PDF, Excel. Button group above the chart.
12880
+
12881
+ ## Component Patterns
12882
+
12883
+ ### Numeric Display (THE core pattern)
12884
+
12885
+ Every number in this archetype follows strict rules:
12886
+
12887
+ - **\`tabular-nums\` everywhere.** Either the Tailwind class or \`font-variant-numeric: tabular-nums\` in CSS. Non-negotiable \u2014 without it, columns of numbers wobble.
12888
+ - **Currency formatting via \`Intl.NumberFormat\`:**
12889
+ \`\`\`ts
12890
+ const formatCurrency = (cents: number, currency = "USD") =>
12891
+ new Intl.NumberFormat("en-US", {
12892
+ style: "currency",
12893
+ currency,
12894
+ minimumFractionDigits: 2,
12895
+ }).format(cents / 100);
12896
+ \`\`\`
12897
+ - **Signed amounts:** always show \`+\` for positive, \`\u2212\` (minus, not hyphen) for negative. Color-coded.
12898
+ - **Currency symbol alignment:** \`$1,234.56\` with symbol attached, not spaced. Right-align amounts in tables.
12899
+ - **Decimals always shown** (2 places for most currencies). Never "$12" when it's actually "$12.00" \u2014 loses trust.
12900
+ - **Pending / estimate indicators:** "~" prefix or a subtle clock icon for pending amounts.
12901
+
12902
+ ### Cards
12903
+
12904
+ Clean, bordered, minimal shadow:
12905
+
12906
+ \`\`\`
12907
+ bg-card border border-border rounded-lg p-6
12908
+ \`\`\`
12909
+
12910
+ - \`rounded-lg\` \u2014 professional, not friendly.
12911
+ - Light mode: thin border, no shadow. Dark mode: subtle \`bg-card/60\` with slight backdrop-blur.
12912
+ - Header: label (\`text-sm text-muted-foreground uppercase tracking-wide\`).
12913
+ - Primary content: large number + delta.
12914
+ - Minimal visual noise. Money apps look cheap when over-designed.
12915
+
12916
+ ### Typography
12917
+
12918
+ - **Heading font:** geometric sans for modernity (Geist, S\xF6hne, Inter, IBM Plex Sans) or a refined serif for premium/wealth management feel (Source Serif 4, Fraunces).
12919
+ - **Body font:** same family or a humanist sans.
12920
+ - **Numeric font:** same as body IF it has great tabular figures. Otherwise, switch to a font with proper tabular support (e.g., \`font-variant-numeric: tabular-nums\` on Geist, Inter Tight, IBM Plex Mono for data-heavy).
12921
+ - **Scale:** headlines are restrained \u2014 this archetype doesn't shout. \`text-xl\` section headers, \`text-2xl/3xl\` page titles. The HUGE size goes on the money numbers, not the copy.
12922
+
12923
+ ### Status & Categories
12924
+
12925
+ - **Category badges:** pill-shaped, subtle background, category-specific icon. \`bg-primary/10 text-primary rounded-full px-2 py-1 text-xs\`.
12926
+ - **Transaction status:** small dot indicator + label. "\u25CF Pending" (yellow), "\u25CF Cleared" (green), "\u25CF Failed" (red). Plain, functional.
12927
+ - **Icons for transaction types:** lucide icons \u2014 \`ArrowUpRight\` for outgoing, \`ArrowDownLeft\` for incoming, \`Repeat\` for recurring, \`CreditCard\` for card payments.
12928
+
12929
+ ### Navigation
12930
+
12931
+ - **Sidebar** for desktop, bottom tab bar for mobile. Never hamburger-only \u2014 money apps need primary sections always visible.
12932
+ - **Sidebar items:** Dashboard, Transactions, Budgets, Goals, Reports, Settings. 5-7 items max.
12933
+ - **Top bar:** account switcher (if multi-account) + search + notifications bell + user menu.
12934
+ - **Global add button** (floating action button or prominent header button): "Add transaction / Create invoice."
12935
+
12936
+ ### Motion
12937
+
12938
+ - **Restrained.** Money apps are not the place for bouncy springs. Use \`ease-out\` with durations under 250ms.
12939
+ - **Number transitions use \`@number-flow/react\`** for satisfying count-up on dashboard load. Also on filter changes ("5 transactions" \u2192 "12 transactions").
12940
+ - **Sparkline draw animation** on mount: line draws from left to right over 600ms, \`strokeDasharray\` trick.
12941
+ - **Avoid:** large parallax, rotation, scale-on-hover for cards. This archetype values stability.
12942
+
12943
+ ---
12944
+
12945
+ ## Landing Page Signature
12946
+
12947
+ For fintech marketing pages. Restore patterns from ember-pricing / daylight-saas / crystal-clear and draw from Stripe / Wise / Mercury landing pages.
12948
+
12949
+ ### Signature 1: Hero with massive number (trust-building pattern)
12950
+
12951
+ - **Headline + subheadline left (~45%):** benefit-focused copy, restrained sans, one word emphasized (e.g., "Your money, clearly.").
12952
+ - **Big stat / chart preview right (~55%):** a dashboard snippet showing a real-looking balance + sparkline. OR a large isolated number: "$2.4B processed" with a primary-colored gradient backdrop.
12953
+ - Product UI preview in a bordered frame (\`rounded-xl border shadow-2xl\`), showing fake but realistic data (actual dollar amounts, merchant names, categories).
12954
+
12955
+ ### Signature 2: Pricing calculator (ember-pricing pattern)
12956
+
12957
+ For fintech with usage-based pricing:
12958
+
12959
+ - Full-width section with an **interactive pricing calculator** \u2014 slider or input for transaction volume, account count, or monthly charges.
12960
+ - Live-updating price display next to it, animated with \`NumberFlow\`.
12961
+ - 3 tier columns below the calculator, with the "Recommended" tier highlighted by \`ring-2 ring-primary\` + badge.
12962
+ - Each tier: price, "What's included" checkmark list, CTA button.
12963
+
12964
+ ### Signature 3: Trust markers strip
12965
+
12966
+ Below the hero, a horizontal strip of trust signals:
12967
+
12968
+ - Regulatory badges (FDIC insured, SOC 2, PCI DSS, GDPR) \u2014 small logos with labels.
12969
+ - Customer logos (small, grayscale) with a line like "Trusted by 5,000+ small businesses."
12970
+ - Security anchor: "Bank-level encryption. Never stored unencrypted."
12971
+ - Position BELOW the fold, not in the hero (it's supporting evidence, not a headline).
12972
+
12973
+ ### Signature 4: Comparison / before-after table
12974
+
12975
+ Finance products often replace something worse (a clunky bank, a spreadsheet, an old tool). Make the comparison explicit:
12976
+
12977
+ - Two-column "Before / After" layout OR a comparison table.
12978
+ - Before column: "Hours spent reconciling," "Stacks of receipts," "Paper invoices," etc.
12979
+ - After column: specific time / money savings with real numbers.
12980
+
12981
+ ### Signature 5: Mesh gradient background (subtle)
12982
+
12983
+ - ONE primary-color mesh gradient as background ambiance, at low opacity. 2-3 radial gradients positioned asymmetrically.
12984
+ - NOT aggressive purple/blue \u2014 usually a brand color (Stripe's indigo, Wise's bright green, Mercury's purple used subtly).
12985
+ - Stays behind everything; doesn't compete with content.
12986
+
12987
+ ---
12988
+
12989
+ ## Copy Voice
12990
+
12991
+ - **Clear, trustworthy, specific.** "Get paid in 3 days, not 30" not "Accelerate your cashflow solutions."
12992
+ - **Real dollar amounts and percentages.** "Save $1,200/year on fees" > "Save significantly."
12993
+ - **Regulatory context when relevant:** "FDIC insured up to $250k." "Funds held by a US-regulated bank partner." Transparency = trust.
12994
+ - **Plain-English financial terms:** "Interest earned" not "Yield accumulation." Users of money apps are not finance experts by default.
12995
+ - Avoid: "revolutionize," "fintech disruption," "next-generation banking." Dull but trustworthy beats hip.
12996
+
12997
+ ---
12998
+
12999
+ ## Anti-patterns
13000
+
13001
+ - **Non-tabular numbers in data tables.** Digits wobble \u2192 instant credibility loss.
13002
+ - **Charts without axis labels / source data.** Trust requires auditability.
13003
+ - **Aggressive animation around money displays.** Springs, bounces, scale pulses \u2014 all make the app feel tacky.
13004
+ - **Gradient text on dollar amounts.** Money should be crisp and readable, not decorative.
13005
+ - **Hidden decimals ("$12" instead of "$12.00").** Users notice. Feels imprecise.
13006
+ - **Red for ALL negative numbers everywhere.** In transaction lists, keep all amounts in \`foreground\` \u2014 use red only for losses on a P&L or refunds on a receipt. Overusing red creates alarm fatigue.
13007
+ - **Stock photos of business people shaking hands.** Dead fintech clich\xE9. Use product screenshots or geometric illustrations.
13008
+ - **Purple-to-blue gradients in the hero.** Fintech SaaS default. Use a brand color instead \u2014 or no gradient.
13009
+ - **Dark mode as default for a consumer money app.** Too crypto-coded. Default light, offer dark.
13010
+ - **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.
13011
+ `;var mo={"consumer-warm":{id:"consumer-warm",name:"Consumer Warm",description:"Personal, lifestyle, and wellness apps (habits, journals, recipes, mood, meditation)",content:oo},"consumer-bold":{id:"consumer-bold",name:"Consumer Bold",description:"Energetic, achievement-driven apps (fitness, workouts, sports, gaming, social)",content:io},"professional-clean":{id:"professional-clean",name:"Professional Clean",description:"Service and appointment-based apps (booking, clinics, salons, real estate, restaurants)",content:no},"education-structured":{id:"education-structured",name:"Education Structured",description:"Learning and knowledge apps (courses, quizzes, flashcards, LMS, tutorials)",content:so},"marketplace-browse":{id:"marketplace-browse",name:"Marketplace Browse",description:"Browsing and shopping apps (marketplaces, directories, shops, catalogs, food delivery)",content:lo},"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:co},"content-editorial":{id:"content-editorial",name:"Content Editorial",description:"Blogs, newsletters, magazines, publications, documentation, knowledge bases",content:po},"devtool-technical":{id:"devtool-technical",name:"Devtool Technical",description:"Developer tools, APIs, CLIs, SDKs, infrastructure, monitoring, AI/ML platforms",content:uo},"creative-showcase":{id:"creative-showcase",name:"Creative Showcase",description:"Portfolios, design agencies, creative studios, photographers, artists",content:ho},"finance-clarity":{id:"finance-clarity",name:"Finance Clarity",description:"Fintech, invoicing, expense tracking, budgeting, payroll, accounting, banking",content:go}};function fo(r){return mo[r]?.content}function bo(r,e){let a=e?.archetype;if(a&&a in mo)return a;if(a===null)return;let t=r.toLowerCase(),i=e?.tone??"",s=e?.audienceType??"";return/\bbooking|appointment|reserv|salon|clinic|dental|barber|spa|hotel|restaurant|event|venue|schedule|court|racquet|tee.?time\b/i.test(t)?"professional-clean":/\binvoic|expense|budget|payroll|accounting|bookkeep|payment|banking|wallet|fintech|cashflow|reimbursement|tax.?filing\b/i.test(t)?"finance-clarity":/\bblog|newsletter|publication|magazine|docs?\b|documentation|knowledge.?base|wiki|\barticle|editorial|cms|longform|writing.?platform\b/i.test(t)?"content-editorial":/\bportfolio|\bagency\b|\bstudio\b|freelance|photographer|illustrator|gallery|architect|filmmaker\b/i.test(t)?"creative-showcase":/\bdev.?tool|developer.?(?:tool|platform|portal)|\bapi\b|\bcli\b|\bsdk\b|infrastructure|ci.?cd|\bmonitoring\b|observability|\bdeployment\b|serverless|edge.?compute|kubernetes|\bdocker\b|terraform|ai.?platform|ml.?platform|mlops|error.?tracking/i.test(t)?"devtool-technical":/\bcrm\b|analytics|\bdashboard\b|admin.?panel|workflow|pipeline|ticket|issue.?track|project.?mgmt|team.?mgmt|\bsaas\b|internal.?tool|\bops\b|reporting|bi.?tool/i.test(t)?"saas-analytical":/\bhabit|journal|diary|mood|meditation|yoga|mindful|gratitude|daily.?routine|self.?care|wellness\b/i.test(t)?"consumer-warm":/\bfitness|workout|exercise|gym|training|sport|running|cycling|weight|muscle|cardio|athletic|leaderboard|\bgame\b/i.test(t)?"consumer-bold":/\bcourse|quiz|flashcard|lesson|tutorial|learning|education|\blms\b|student|teach|curriculum|exam|classroom\b/i.test(t)?"education-structured":/\bmarketplace|\bshop\b|\bstore\b|catalog|listing|directory|classified|rental|e.?commerce|food.?delivery|menu\b/i.test(t)?"marketplace-browse":i==="warm-minimal"||i==="playful"?/\brecipe|cook|meal|food|pet|plant|garden|travel|photo|music|social|community|family\b/i.test(t)?"consumer-warm":/\bcompet|score|rank|challenge|achieve|badge|level|quest\b/i.test(t)?"consumer-bold":"consumer-warm":s==="b2c"?"consumer-warm":"saas-analytical"}import{existsSync as Ht,mkdirSync as xo,readFileSync as wo,readdirSync as vo,writeFileSync as _n}from"fs";import{join as at,resolve as jn}from"path";import{homedir as Ta}from"os";function tt(r){return r.entity??r.name??"Unknown"}function $e(r){return typeof r=="string"?r:r.name}function Sa(r){return typeof r=="string"?"text":r.type}function yo(r){let e=r.sampleRows;if(Array.isArray(e)&&e.length>0)return e.slice(0,3).map(i=>{let s={};for(let[o,n]of Object.entries(i))s[o]=n==null?"":String(n);return s});let a=r.fields||[],t=[];for(let i=0;i<3;i++){let s={};for(let o of a)s[$e(o)]=zn($e(o),Sa(o),tt(r),i);t.push(s)}return t}function zn(r,e,a,t){let i=r.toLowerCase(),s=(e||"text").toLowerCase();return i==="name"||i==="title"?[`${a} Alpha`,`${a} Beta`,`${a} Gamma`][t]:i==="email"?["alice@example.com","bob@example.com","carol@example.com"][t]:i==="status"?["Active","Pending","Completed"][t]:i==="priority"?["High","Medium","Low"][t]:i.includes("date")||s==="date"?["Jan 15, 2024","Feb 20, 2024","Mar 10, 2024"][t]:i.includes("price")||i.includes("amount")||i.includes("cost")?["$29","$49","$99"][t]:i.includes("count")||i.includes("quantity")||s==="number"||s==="integer"?["12","34","56"][t]:s==="boolean"||s==="bool"?["Yes","No","Yes"][t]:i.includes("description")||s==="textarea"?["Brief description here","Another example entry","Third sample item"][t]:[`Sample ${t+1}`][0]}function $n(r){let e=r.dataModel??[],a=r.pages??[],t=r.design??{},i=a.map(h=>({label:h.name??h.path??"Page",route:h.path??h.route??"/"})),s=[],o=e.slice(0,3).map(h=>({name:tt(h),fields:(h.fields||[]).map(c=>({name:$e(c),type:Sa(c)})),sampleData:yo(h)})),n=[];r.primaryAction&&(n.push(`PRIMARY: ${r.primaryAction.action} \u2014 this is the first thing the user sees and does`),n.push(`SURFACE: ${r.primaryAction.dashboardSurface}`)),n.push(`METRICS: Key counts for ${e.map(h=>tt(h)).join(", ")}`),n.push("RECENT: Latest activity or items"),s.push({name:"Dashboard",type:"dashboard",route:"/dashboard",purpose:r.primaryAction?`Action surface \u2014 user comes here to ${r.primaryAction.action.toLowerCase()}. Not a stats display.`:`Overview of ${e.map(h=>tt(h)).join(", ")} with key metrics.`,informationHierarchy:n,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:o});let l=e[0];if(l){let h=tt(l),c=h.toLowerCase().endsWith("s")?h:`${h}s`;s.push({name:`${h} List`,type:"detail",route:`/${c.toLowerCase()}`,purpose:`Browse, search, and manage ${c.toLowerCase()}. Create new ${h.toLowerCase()}s.`,informationHierarchy:[`HEADER: "${c}" title + "Add ${h}" button`,"SEARCH: Filter/search bar \u2014 users will have many items",`TABLE: ${(l.fields||[]).slice(0,5).map(g=>$e(g)).join(", ")} columns`,"ROW ACTIONS: Edit, delete on each row"],interactionStates:[`Empty state: "No ${c.toLowerCase()} yet" with create CTA`,"Loading state: skeleton table rows",`Search with no results: "No ${c.toLowerCase()} matching..." with clear filter`],entities:[{name:h,fields:(l.fields||[]).map(g=>({name:$e(g),type:Sa(g)})),sampleData:yo(l)}]})}r.steps.some(h=>{let c=`${h.name??h.title??""} ${h.description??""}`.toLowerCase();return c.includes("landing")||c.includes("hero")||c.includes("marketing")||c.includes("homepage")})&&s.push({name:"Landing Page",type:"landing",route:"/",purpose:`Convince visitors to sign up for ${r.name}. Answer: what is this, who is it for, why should I care.`,informationHierarchy:[`HERO: One sentence about what ${r.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 u=[];for(let h of e.slice(0,3)){let c=tt(h);(h.fields||[]).find(b=>$e(b).toLowerCase()==="name"||$e(b).toLowerCase()==="title")&&u.push(`What if a ${c.toLowerCase()}'s name is 47 characters? Does the layout break?`),u.push(`What if there are 0 ${c.toLowerCase()}s? 1? 500?`)}return r.authModel&&r.authModel!=="none"&&u.push("What does a brand-new user see? (no data, no setup)"),u.push("What if the network is slow? What loads first?"),{appName:r.name,summary:r.summary??"",screens:s,navigation:{style:r.navStyle??"sidebar",items:i},primaryAction:r.primaryAction??null,designDirection:{tone:t.tone??"professional",accentColor:t.accentColor??"blue",navStyle:r.navStyle??"sidebar",fonts:t.fonts??{heading:"Inter",body:"Inter"}},edgeCases:u}}function Vn(r,e){let a=[];a.push(`# Wireframe sketch for ${r.appName}`),a.push(""),a.push(`**${r.appName}** \u2014 ${r.summary}`),a.push(""),e&&(a.push("## Feedback to apply"),a.push(e),a.push("")),a.push("## Design principles"),a.push(""),a.push("Apply these when deciding layout and hierarchy:"),a.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."),a.push("2. **Interaction states** \u2014 Every screen has at least: empty, loading, populated. Show the populated state but add HTML comments noting the others."),a.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."),a.push(`4. **Subtraction** \u2014 "As little design as possible" (Dieter Rams). Every element earns its pixels. If removing something doesn't hurt, remove it.`),a.push("5. **Design for trust** \u2014 Clear labels, predictable layout, obvious actions. No mystery meat navigation."),a.push(""),a.push("## Wireframe rules (strict)"),a.push(""),a.push("Write a **single self-contained HTML file** saved to `.mistflow/mockups/mockup-{planId}.html` (replace `{planId}` with the actual plan ID)."),a.push(""),a.push("The wireframe must:"),a.push("- Use **system fonts only** (`-apple-system, system-ui, sans-serif`) \u2014 no Google Fonts, no CDN"),a.push("- Use **inline CSS only** \u2014 no external stylesheets, no Tailwind CDN"),a.push("- Look **intentionally rough** \u2014 thin gray borders (#ddd), light backgrounds (#f8f8f8), no color, no shadows"),a.push("- Use **realistic placeholder content** that matches this specific app (sample data provided below) \u2014 NOT lorem ipsum"),a.push("- Include **HTML comments** explaining design decisions (e.g., `<!-- Primary action is prominent because users come here to check in members -->`)"),a.push("- Show **all screens in a single page** using tabs/sections that the user can click through"),a.push("- Be **responsive** \u2014 test that it looks reasonable at both 1200px and 375px widths"),a.push("- Include a small header bar showing: screen name tabs + the design direction summary"),a.push(""),a.push("The wireframe must NOT:"),a.push("- Use any color except grayscale (#333, #666, #999, #ddd, #f8f8f8, white)"),a.push("- Use any external dependencies \u2014 no CDN, no imports, no build step"),a.push("- Look polished \u2014 it should feel like a sketch on a whiteboard, not a finished product"),a.push("- Include decorative elements \u2014 no icons (use text labels), no illustrations, no gradients"),a.push(""),a.push("## Screens to wireframe"),a.push("");for(let t of r.screens){a.push(`### ${t.name} (\`${t.route}\`)`),a.push(`**Purpose**: ${t.purpose}`),a.push(""),a.push("**Information hierarchy** (render in this order, top to bottom):");for(let i of t.informationHierarchy)a.push(`- ${i}`);a.push(""),a.push("**Interaction states** (add HTML comments for non-visible states):");for(let i of t.interactionStates)a.push(`- ${i}`);if(a.push(""),t.entities.length>0){a.push("**Data model and sample content** (use this real data, not lorem ipsum):");for(let i of t.entities)a.push(`
13012
+ **${i.name}** \u2014 fields: ${i.fields.map(s=>`${s.name} (${s.type})`).join(", ")}`),a.push("```json"),a.push(JSON.stringify(i.sampleData,null,2)),a.push("```");a.push("")}}a.push("## Navigation"),a.push(`**Style**: ${r.navigation.style} (use this layout)`),a.push("**Items**:");for(let t of r.navigation.items)a.push(`- ${t.label} \u2192 \`${t.route}\``);if(a.push(""),r.primaryAction&&(a.push("## Primary action (this drives the layout)"),a.push(`- **Action**: ${r.primaryAction.action}`),a.push(`- **Flow**: ${r.primaryAction.flow}`),a.push(`- **Dashboard must show**: ${r.primaryAction.dashboardSurface}`),a.push(""),a.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."),a.push("")),r.edgeCases.length>0){a.push("## Edge cases to consider"),a.push("Add HTML comments in the wireframe where these matter:");for(let t of r.edgeCases)a.push(`- ${t}`);a.push("")}return a.push("## Design direction (DO NOT apply to wireframe \u2014 this is for reference only)"),a.push(`The final app will use: ${r.designDirection.tone} tone, ${r.designDirection.accentColor} accent, ${r.designDirection.navStyle} nav, ${r.designDirection.fonts.heading} / ${r.designDirection.fonts.body} fonts.`),a.push("The wireframe is grayscale and rough. These tokens will be applied during the actual build."),a.push(""),a.push("## After writing the wireframe"),a.push(""),a.push("1. Write the file to `.mistflow/mockups/mockup-{planId}.html` (replace `{planId}` with the actual plan ID)"),a.push("2. Open it: `open .mistflow/mockups/mockup-{planId}.html`"),a.push(`3. Tell the user: "Here's a rough wireframe of your app. This shows the layout and information hierarchy \u2014 not the final visual design (colors, fonts, polish come during the build). Does the layout feel right? Want to move things around, or shall we start building?"`),a.join(`
13013
+ `)}function ko(r){return at(Ta(),".mistflow","mockup-state",`${r}.json`)}function qn(r){let e=ko(r);if(!Ht(e))return null;try{return JSON.parse(wo(e,"utf-8"))}catch{return null}}function ka(r,e){let a=at(Ta(),".mistflow","mockup-state");xo(a,{recursive:!0}),_n(ko(r),JSON.stringify(e,null,2))}async function So(r){let{planId:e,feedback:a,approved:t}=r,i=jn(r.projectPath??process.cwd()),s=at(Ta(),".mistflow","plans",`${e}.json`);if(!Ht(s))return p(`Plan not found for planId '${e}'. Call mist_plan to generate a plan first.`,!0);let o;try{o=JSON.parse(wo(s,"utf-8"))}catch{return p("Failed to read plan file. Call mist_plan again.",!0)}let n=o.plan;if(!n)return p("Plan data is empty. Call mist_plan again.",!0);let l=qn(e);l||(l={planId:e,iterationCount:0,approved:!1,screens:[],feedback:[]});let d=at(i,".mistflow","mockups");if(xo(d,{recursive:!0}),t){l.approved=!0,ka(e,l);let g=Ht(d)?vo(d).filter(b=>b.endsWith(".html")):[];return p(JSON.stringify({status:"approved",message:`Wireframe approved after ${l.iterationCount} iteration(s). Layout direction is locked in.`,mockupFiles:g.map(b=>`.mistflow/mockups/${b}`),nextAction:`NEXT: Call mist_build with action='init', name='${n.name}', and planId='${e}' to create the project. The wireframe in .mistflow/mockups/ will be used as layout reference during implementation.`}))}a?(l.iterationCount++,l.feedback.push(a)):l.iterationCount++,ka(e,l);let u=$n(n);l.screens=u.screens.map(g=>g.name),ka(e,l);let h=Vn(u,a??void 0),c;return l.iterationCount>=3&&(c="The wireframe is shaping up \u2014 want to keep refining the layout, or start building?"),p(JSON.stringify({status:"wireframe",iterationCount:l.iterationCount,screens:u.screens.map(g=>({name:g.name,type:g.type,route:g.route})),wireframePrompt:h,designDirection:u.designDirection,...c?{nudge:c}:{},mockupFile:`mockup-${e}.html`,nextAction:a?`Apply the user's feedback to the wireframe. Rewrite .mistflow/mockups/mockup-${e}.html with the changes. Open it in the browser for review. Ask if they want more changes or are ready to build.`:`Generate the wireframe HTML following the wireframePrompt instructions above. Write it to .mistflow/mockups/mockup-${e}.html, then open it in the browser. Ask the user if the layout feels right.`}))}function To(r){let e=at(r,".mistflow","mockups");return Ht(e)?vo(e).filter(a=>a.endsWith(".html")).map(a=>at(e,a)):[]}var Co="\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 Po=`# Landing Page Rules
12000
13014
 
12001
13015
  These rules apply to every landing page. They are non-negotiable.
12002
13016
 
@@ -12383,15 +13397,15 @@ app/
12383
13397
  \`\`\`
12384
13398
 
12385
13399
  Each section is a separate component file. The page.tsx simply imports and stacks them. Do NOT build one monolithic page component.
12386
- `;function Wn(r){return new Promise(e=>{let t=Gn({port:r,host:"127.0.0.1"});t.on("connect",()=>{t.destroy(),e(!0)}),t.on("error",()=>{e(!1)})})}var Cc=ka.object({projectPath:ka.string().optional().describe("Path to the project directory (default: cwd)"),step:ka.number().optional().describe("Specific step number to implement (default: next incomplete step)")});function _n(r){let e=Ot(r,"mistflow.json");if(!Sa(e))return null;try{return JSON.parse(wo(e,"utf-8"))}catch{return null}}function xo(r,e){let t=Ot(r,"mistflow.json");Ca(t,JSON.stringify(e,null,2)+`
12387
- `)}function Gt(r){return r.entity??r.name??"Unknown"}function jn(r){return r.length===0?"":typeof r[0]=="string"?r.join(", "):r.map(e=>`${e.name} (${e.type})`).join(", ")}function vo(r){return r.path??r.route??r.name??""}function zn(r){let e=(r||"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 ko(r,e){if(!r.entities||r.entities.length===0)return e;let t=r.entities.map(a=>a.toLowerCase());return e.filter(a=>{let i=Gt(a).toLowerCase();return t.some(n=>i.includes(n)||n.includes(i))})}function $n(r,e){if(!r.pages||r.pages.length===0)return[];let t=r.pages.map(a=>a.toLowerCase());return e.filter(a=>{let i=(a.name??"").toLowerCase(),n=vo(a).toLowerCase();return t.some(o=>i.includes(o)||o.includes(i)||n.includes(o))})}function Vn(r){if(r.integrationId)return"integration";let e=`${r.name} ${r.description}`.toLowerCase();return e.includes("crud")||e.includes("list")&&e.includes("create")?"crud":e.includes("auth")||e.includes("login")||e.includes("register")?"auth":e.includes("admin")&&(e.includes("panel")||e.includes("dashboard")||e.includes("manage")||e.includes("users"))?"admin":e.includes("dashboard")||e.includes("overview")||e.includes("analytics")?"dashboard":e.includes("schema")||e.includes("database")||e.includes("model")?"schema":e.includes("layout")||e.includes("sidebar")||e.includes("navigation")?"layout":e.includes("deploy")||e.includes("cloudflare")?"deploy":e.includes("organization")||e.includes("team")||e.includes("workspace")||e.includes("multi-tenant")||e.includes("invite member")?"multi-tenant":e.includes("landing")||e.includes("hero")||e.includes("marketing")||e.includes("homepage")?"landing":e.includes("design")||e.includes("theme")||e.includes("styling")||e.includes("ui polish")||e.includes("visual")?"design":"general"}function qn(r){switch(r){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 Kn(r,e){let t=[];if(t.push("### Design choices (decided at plan time \u2014 follow these exactly):"),r.tone&&t.push(`- **App tone**: ${r.tone}`),r.fonts&&(t.push(`- **Heading font**: ${r.fonts.heading} (load from Google Fonts)`),t.push(`- **Body font**: ${r.fonts.body} (load from Google Fonts)`)),r.accentColor&&t.push(`- **Accent color**: ${r.accentColor} (use for primary buttons, active states, highlights only)`),r.borderRadius){let a={sharp:"2px",subtle:"6px",rounded:"12px",pill:"9999px"};t.push(`- **Border radius**: ${r.borderRadius} (${a[r.borderRadius]??r.borderRadius}) \u2014 set as --radius in globals.css`)}if(r.shadowStyle){let a={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"};t.push(`- **Shadow style**: ${r.shadowStyle} \u2014 ${a[r.shadowStyle]??r.shadowStyle}`)}if(r.cardStyle){let a={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"};t.push(`- **Card style**: ${r.cardStyle} \u2014 ${a[r.cardStyle]??r.cardStyle}`)}if(r.landingTone&&t.push(`- **Landing page tone**: ${r.landingTone}`),r.visualStrategy){let a=r.visualStrategy,i=r.heroPhoto!==!1,n=i&&!!a.heroImages?.length;if(t.push(""),t.push("### Visual strategy:"),n&&a.heroImages&&a.heroImages.length>0){t.push("**Hero image** \u2014 use this Unsplash photo as the landing page hero BACKGROUND:");let o=a.heroImages[0];t.push(`- URL: ${o.url}`),t.push(`- Alt text for img tag: "${o.alt||"Hero image"} \u2014 Photo by ${o.photographer} on Unsplash"`),t.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 i&&!a.heroImages?.length?t.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/page.tsx.'"):i||t.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(a.sectionImages?.length){t.push("**Section images available** \u2014 use these for feature sections, about sections, or testimonial backgrounds (NOT the hero):");for(let o of a.sectionImages)t.push(`- ${o.url} \u2014 alt: "${o.alt||"section image"} \u2014 Photo by ${o.photographer} on Unsplash"`)}(n||a.sectionImages?.length)&&t.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."),t.push(""),t.push("**Hero layout \u2014 split hero with product UI mockup (follow this exactly):**"),t.push("The hero uses a split layout with text on the left and a product preview on the right. This is non-negotiable for consumer apps and SaaS/tool apps alike."),t.push(""),t.push("```"),t.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"),t.push("\u2502 [Logo] [Sign In] [CTA \u2192] \u2502 \u2190 transparent nav, NOT sticky"),t.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"),t.push("\u2502 \u2502"),t.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"),t.push("\u2502 \u2502 Glassmorphic \u2502 \u2502"),t.push("\u2502 Big bold headline, \u2502 product mockup \u2502 \u2502"),t.push("\u2502 accent color on key word \u2502 card showing \u2502 \u2502"),t.push("\u2502 \u2502 real app data \u2502 \u2502"),t.push("\u2502 Description paragraph \u2502 (stats, table, \u2502 \u2502"),t.push("\u2502 \u2502 chart, etc.) \u2502 \u2502"),t.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"),t.push("\u2502 \u2502"),t.push("\u2502 500+ 25K+ 99% \u2502"),t.push("\u2502 Label Label Label \u2502"),t.push("\u2502 \u2502"),n?t.push("\u2502 \u2190 full-bleed photo bg + dark overlay behind all \u2192 \u2502"):t.push("\u2502 \u2190 preset gradient / glass background behind all \u2192 \u2502"),t.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"),t.push("```"),t.push(""),t.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"),t.push("**Right side (~45%)**: A glassmorphic floating card that previews what the app looks like inside:"),t.push("- Style: `bg-white/10 backdrop-blur-lg border border-white/20 rounded-2xl shadow-2xl p-6` (over dark/photo bg) OR `bg-white/60 backdrop-blur-lg border border-black/5 rounded-2xl shadow-2xl p-6` (over light bg)"),t.push("- Content: 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"),t.push("- Must be DOMAIN-SPECIFIC: a library app shows book catalog rows, a gym app shows check-in stats, a CRM shows pipeline cards"),t.push("- Add subtle inner elements with `bg-white/5` or `bg-white/10` for depth"),t.push("**Stats row**: 3 proof-point numbers at the bottom of the left side (e.g., '500+ Gyms', '25K+ Members', '99% Uptime'). Match text color to the background contrast.")}return t.push(""),t.push(bo),t.join(`
12388
- `)}async function Yn(r){try{let e=await lr("nextjs",r);return{reminders:e.reminders,skill:e.skill}}catch{return{reminders:`### ${r} step
13400
+ `;function es(r){return new Promise(e=>{let a=Zn({port:r,host:"127.0.0.1"});a.on("connect",()=>{a.destroy(),e(!0)}),a.on("error",()=>{e(!1)})})}var Jc=Ca.object({projectPath:Ca.string().optional().describe("Path to the project directory (default: cwd)"),step:Ca.number().optional().describe("Specific step number to implement (default: next incomplete step)")});function ts(r){let e=mt(r,"mistflow.json");if(!Ot(e))return null;try{return JSON.parse(Ao(e,"utf-8"))}catch{return null}}function Bo(r,e){let a=mt(r,"mistflow.json");Pa(a,JSON.stringify(e,null,2)+`
13401
+ `)}function Gt(r){return r.entity??r.name??"Unknown"}function as(r){return r.length===0?"":typeof r[0]=="string"?r.join(", "):r.map(e=>`${e.name} (${e.type})`).join(", ")}function Do(r){return r.path??r.route??r.name??""}function rs(r){let e=(r||"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 Io(r,e){if(!r.entities||r.entities.length===0)return e;let a=r.entities.map(t=>t.toLowerCase());return e.filter(t=>{let i=Gt(t).toLowerCase();return a.some(s=>i.includes(s)||s.includes(i))})}function os(r,e){if(!r.pages||r.pages.length===0)return[];let a=r.pages.map(t=>t.toLowerCase());return e.filter(t=>{let i=(t.name??"").toLowerCase(),s=Do(t).toLowerCase();return a.some(o=>i.includes(o)||o.includes(i)||s.includes(o))})}var is=new Set(["landing","design","dashboard","crud","layout","admin","auth","schema","integration","multi-tenant","deploy","general"]);function ns(r){let e=r.stepType;if(e&&is.has(e))return e;if(r.integrationId)return"integration";let a=`${r.name} ${r.description}`.toLowerCase();return a.includes("crud")||a.includes("list")&&a.includes("create")?"crud":a.includes("auth")||a.includes("login")||a.includes("register")?"auth":a.includes("admin")&&(a.includes("panel")||a.includes("dashboard")||a.includes("manage")||a.includes("users"))?"admin":a.includes("dashboard")||a.includes("overview")||a.includes("analytics")?"dashboard":a.includes("schema")||a.includes("database")||a.includes("model")?"schema":a.includes("layout")||a.includes("sidebar")||a.includes("navigation")?"layout":a.includes("deploy")||a.includes("cloudflare")?"deploy":a.includes("organization")||a.includes("team")||a.includes("workspace")||a.includes("multi-tenant")||a.includes("invite member")?"multi-tenant":a.includes("landing")||a.includes("hero")||a.includes("marketing")||a.includes("homepage")?"landing":a.includes("design")||a.includes("theme")||a.includes("styling")||a.includes("ui polish")||a.includes("visual")?"design":"general"}function ss(r){switch(r){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 ls(r,e){let a=[];if(a.push("### Design choices (decided at plan time \u2014 follow these exactly):"),r.tone&&a.push(`- **App tone**: ${r.tone}`),r.fonts&&(a.push(`- **Heading font**: ${r.fonts.heading} (load from Google Fonts)`),a.push(`- **Body font**: ${r.fonts.body} (load from Google Fonts)`)),r.accentColor&&a.push(`- **Accent color**: ${r.accentColor} (use for primary buttons, active states, highlights only)`),r.borderRadius){let t={sharp:"2px",subtle:"6px",rounded:"12px",pill:"9999px"};a.push(`- **Border radius**: ${r.borderRadius} (${t[r.borderRadius]??r.borderRadius}) \u2014 set as --radius in globals.css`)}if(r.shadowStyle){let t={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"};a.push(`- **Shadow style**: ${r.shadowStyle} \u2014 ${t[r.shadowStyle]??r.shadowStyle}`)}if(r.cardStyle){let t={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"};a.push(`- **Card style**: ${r.cardStyle} \u2014 ${t[r.cardStyle]??r.cardStyle}`)}if(r.landingTone&&a.push(`- **Landing page tone**: ${r.landingTone}`),r.visualStrategy){let t=r.visualStrategy,i=r.heroPhoto!==!1,s=i&&!!t.heroImages?.length;if(a.push(""),a.push("### Visual strategy:"),s&&t.heroImages&&t.heroImages.length>0){a.push("**Hero image** \u2014 use this Unsplash photo as the landing page hero BACKGROUND:");let o=t.heroImages[0];a.push(`- URL: ${o.url}`),a.push(`- Alt text for img tag: "${o.alt||"Hero image"} \u2014 Photo by ${o.photographer} on Unsplash"`),a.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 i&&!t.heroImages?.length?a.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/page.tsx.'"):i||a.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(t.sectionImages?.length){a.push("**Section images available** \u2014 use these for feature sections, about sections, or testimonial backgrounds (NOT the hero):");for(let o of t.sectionImages)a.push(`- ${o.url} \u2014 alt: "${o.alt||"section image"} \u2014 Photo by ${o.photographer} on Unsplash"`)}(s||t.sectionImages?.length)&&a.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."),a.push(""),a.push("**Hero layout \u2014 split hero with product UI mockup (follow this exactly):**"),a.push("The hero uses a split layout with text on the left and a product preview on the right. This is non-negotiable for consumer apps and SaaS/tool apps alike."),a.push(""),a.push("```"),a.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"),a.push("\u2502 [Logo] [Sign In] [CTA \u2192] \u2502 \u2190 transparent nav, NOT sticky"),a.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"),a.push("\u2502 \u2502"),a.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"),a.push("\u2502 \u2502 Glassmorphic \u2502 \u2502"),a.push("\u2502 Big bold headline, \u2502 product mockup \u2502 \u2502"),a.push("\u2502 accent color on key word \u2502 card showing \u2502 \u2502"),a.push("\u2502 \u2502 real app data \u2502 \u2502"),a.push("\u2502 Description paragraph \u2502 (stats, table, \u2502 \u2502"),a.push("\u2502 \u2502 chart, etc.) \u2502 \u2502"),a.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"),a.push("\u2502 \u2502"),a.push("\u2502 500+ 25K+ 99% \u2502"),a.push("\u2502 Label Label Label \u2502"),a.push("\u2502 \u2502"),s?a.push("\u2502 \u2190 full-bleed photo bg + dark overlay behind all \u2192 \u2502"):a.push("\u2502 \u2190 preset gradient / glass background behind all \u2192 \u2502"),a.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"),a.push("```"),a.push(""),a.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"),a.push("**Right side (~45%)**: A glassmorphic floating card that previews what the app looks like inside:"),a.push("- Style: `bg-white/10 backdrop-blur-lg border border-white/20 rounded-2xl shadow-2xl p-6` (over dark/photo bg) OR `bg-white/60 backdrop-blur-lg border border-black/5 rounded-2xl shadow-2xl p-6` (over light bg)"),a.push("- Content: 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"),a.push("- Must be DOMAIN-SPECIFIC: a library app shows book catalog rows, a gym app shows check-in stats, a CRM shows pipeline cards"),a.push("- Add subtle inner elements with `bg-white/5` or `bg-white/10` for depth"),a.push("**Stats row**: 3 proof-point numbers at the bottom of the left side (e.g., '500+ Gyms', '25K+ Members', '99% Uptime'). Match text color to the background contrast.")}return a.push(""),a.push(Co),a.join(`
13402
+ `)}async function ds(r){try{let e=await cr("nextjs",r);return{reminders:e.reminders,skill:e.skill}}catch{return{reminders:`### ${r} step
12389
13403
  - Follow existing patterns in the codebase
12390
- - Server Components by default, "use client" only when interactivity is needed`,skill:""}}}async function Jn(r,e,t,a,i,n){let o=[];o.push(`## Step ${r.number}: ${r.name}`),o.push(""),o.push("### What to build:"),o.push(r.description),o.push(""),e.primaryAction&&(o.push("### Primary user action (non-negotiable):"),o.push(`- **Core action**: ${e.primaryAction.action}`),o.push(`- **User flow**: ${e.primaryAction.flow}`),o.push(`- **Dashboard must show**: ${e.primaryAction.dashboardSurface}`),o.push(""),o.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."),o.push(""));let l=["landing","design","dashboard","crud","layout","admin","general","auth"].includes(i);if(e.design&&l?(o.push(Kn(e.design,{hasDesignPreset:!!e.landingDesign&&(i==="landing"||i==="design")})),o.push("")):e.design&&!l&&(o.push("### Design tokens (for reference only \u2014 this step is not UI-focused):"),e.design.accentColor&&o.push(`- Accent color: ${e.design.accentColor}`),e.design.fonts&&o.push(`- Fonts: ${e.design.fonts.heading} / ${e.design.fonts.body}`),o.push("")),n){let f=fo(n);if(f.length>0){o.push("### Approved wireframe (MUST READ before writing any files):"),o.push("The user approved a wireframe sketch before building. **Read these files NOW before writing any code for this step:**");for(let v of f){let C=v.replace(n,"").replace(/^\//,"");o.push(`- \`${C}\``)}o.push(""),o.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."),o.push(""),o.push("The wireframe is intentionally rough (grayscale, system fonts). Your job is to:"),o.push("1. **Keep the same layout structure** \u2014 same information hierarchy, same element placement, same sections in the same order"),o.push("2. **Apply the design tokens** \u2014 colors, fonts, shadows, radius from the plan design choices above"),o.push("3. **Elevate the visual quality** \u2014 make it feel designed for THIS specific app, not generic"),o.push("4. **Respect the HTML comments** \u2014 they explain WHY things are placed where they are"),o.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"),o.push("")}}e.roles&&Array.isArray(e.roles)&&e.roles.length>0&&(o.push("### Role system (from plan):"),o.push(`- Roles: ${e.roles.join(", ")}`),o.push(`- Default role for new signups: ${e.defaultRole??e.roles[0]}`),o.push("- Role helpers are in `lib/roles.ts` \u2014 use `getUserRole()` and `hasRole()` for access checks"),o.push("")),e.multiTenant&&(o.push("### Multi-tenant (from plan):"),o.push("- Organization tables are in `db/schema/organization.ts`"),o.push("- Org helpers are in `lib/org.ts` \u2014 use `getCurrentOrg()` to scope queries"),o.push("- All data queries MUST be scoped to the current org (filter by orgId)"),o.push("- Org switcher component is at `components/org-switcher.tsx`"),o.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."),o.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."),o.push("")),e.language&&(o.push(`### Language: ${e.language}`),o.push(`ALL user-facing text must be written in ${e.language}:`),o.push("- Page titles, headings, labels, button text, placeholder text"),o.push("- Navigation items, menu labels, footer text"),o.push("- Error messages, success messages, empty states"),o.push("- Landing page copy, marketing text, CTAs"),o.push("- Form labels and validation messages"),o.push("Code (variable names, comments, file names) stays in English."),o.push(`Set the HTML lang attribute to the appropriate locale code for ${e.language}.`),o.push(""));let d=["landing","design","auth","general","crud","dashboard"];e.audienceType&&d.includes(i)&&(e.audienceType==="b2c"?(o.push("### Audience: this app belongs to ONE business. The landing page talks TO their customers."),o.push("- Hero: what the customer gets ('Exceptional catering for your next event'), NOT what the tool does"),o.push("- CTAs: customer action ('Order Catering', 'Book Now'), NOT business action ('Get Started Free')"),o.push("- Testimonials: from customers ('They catered our wedding'), NOT from business owners"),o.push("- Features: customer benefits ('Specify your dietary needs'), NOT business benefits ('Track preferences')"),o.push("- Stats: social proof for customers ('2,400+ events served'), NOT internal metrics ('$48k revenue')"),o.push("- The business name IS the brand. Say it like a business homepage, not a SaaS onboarding."),o.push("")):e.audienceType==="b2b"?(o.push("### Audience: this is a SaaS platform. The landing page pitches TO business owners."),o.push("- Hero: the business pain + solution ('Catering orders managed in one place')"),o.push("- CTAs: business owner action ('Start Free Trial', 'Get Started')"),o.push("- Testimonials: from business owners who use the platform"),o.push("- Features: business benefits ('Track dietary preferences across all orders')"),o.push("- Stats: platform metrics ('500+ businesses', '50K+ orders processed')"),o.push("")):e.audienceType==="internal"&&(o.push("### Audience: internal staff tool. No marketing copy needed."),o.push("- No landing page. Auth page copy is functional: 'Sign in to continue'."),o.push("- Dashboard focuses on operational efficiency, not onboarding or sales."),o.push(""))),t.length>0&&(o.push("### Already completed:"),t.forEach(f=>o.push(`- ${f}`)),o.push(""));let u=e.dataModel?ko(r,e.dataModel):[];u.length>0&&(o.push("### Data model (from plan):"),u.forEach(f=>{let v=Gt(f),C=jn(f.fields);o.push(`- **${v}**: ${C}`),o.push(` Schema file: \`db/schema/${v.toLowerCase().replace(/\s+/g,"-")}.ts\``)}),o.push(""));let h=e.pages?$n(r,e.pages):[];h.length>0&&(o.push("### Pages to create/update:"),h.forEach(f=>{let v=f.description?` \u2014 ${f.description}`:"";o.push(`- \`${vo(f)}\`${v}`)}),o.push("")),i==="crud"&&u.length>0&&u.forEach(f=>{let v=Gt(f),C=v.toLowerCase().replace(/\s+/g,"-"),P=C.endsWith("s")?C:`${C}s`;o.push(`### Files for ${v} CRUD:`),o.push(`- List page: \`app/(dashboard)/${P}/page.tsx\` (Server Component)`),o.push(`- Detail page: \`app/(dashboard)/${P}/[id]/page.tsx\``),o.push(`- Create page: \`app/(dashboard)/${P}/new/page.tsx\``),o.push(`- Server Actions: \`app/(dashboard)/${P}/actions.ts\``),o.push(`- DataTable columns: \`components/${C}-table-columns.tsx\``),o.push(`- Form: \`components/${C}-form.tsx\``),o.push("")});let c=e.appStyle;if(c&&l){let f=Pr(c,i);if(f){let v=fe(c),C=Ie(c);o.push(`### App style: ${v?.name??c}`),o.push(""),o.push(`Applying the **${v?.name}** app style across this app. Follow these design specifications for consistent, brand-quality UI. The app style defines the visual identity (colors, fonts, spacing). All UI elements must use the project's CSS custom properties (--color-primary, --color-background, etc.) and the fonts configured in layout.tsx.`),C?.fonts&&o.push(`
12391
- **Fonts**: Use \`${C.fonts.heading}\` for headings and \`${C.fonts.body}\` for body text (Google Fonts equivalents if the originals are proprietary).`),o.push(""),o.push(f),o.push("")}}if(l){let f=lo(e.summary??e.name,{tone:e.design?.tone,audienceType:e.audienceType});if(f){let v=so(f);v&&(o.push(`### App archetype: ${f}`),o.push(""),o.push(`This app matches the **${f}** archetype. Follow these component-level patterns for page composition, cards, navigation, spacing, and motion. `+(c?`The app style (${c}) provides visual identity (colors, fonts). The archetype provides layout and component patterns. Use both.`:"All visual styling comes from the project's CSS custom properties and fonts in layout.tsx.")),o.push(""),o.push(v),o.push(""))}}if(i==="landing"||i==="design"){o.push(yo),o.push("");let f=e.design?.landingTone;if(f){let v=to(f);v&&(o.push(`### Landing tone: ${f}`),o.push(""),o.push(v),o.push(""))}}let g=r.integrationId?at(r.integrationId):void 0;if(g){let f=rt(g.id);if(o.push("### Integration blueprint (follow this closely):"),o.push(""),o.push(`Using integration: **${g.name}** (${g.category})`),f?.docsUrl&&o.push(`Official docs: ${f.docsUrl}`),f?.envVars?.length){o.push(""),o.push("**Required environment variables:**");for(let v of f.envVars)o.push(`- \`${v.key}\`: ${v.description} \u2014 Get it at ${v.setupUrl}`);o.push(""),o.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.")}f?.packages?.length&&(o.push(""),o.push(`**Packages to install:** \`npm install ${f.packages.join(" ")}\``)),o.push(""),o.push("---"),o.push(g.prompt),o.push("---"),o.push(""),o.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."),o.push("")}let{reminders:b,skill:y}=await Yn(i);return o.push(b),o.push(""),y&&!(i==="landing"&&e.landingDesign==="freeform")&&(o.push(`### ${i} reference:`),o.push(y),o.push("")),o.join(`
12392
- `)}async function So(r){let{projectPath:e,step:t}=r,a=Hn(e??process.cwd()),i=_n(a);if(!i)return Te(a);let n=i.plan;if(!n||!n.steps||n.steps.length===0)return p("No project plan found. Start by describing your app idea first \u2014 the AI will create a plan for you.",!0);let o,s=n.steps.find(M=>M.status==="in_progress");if(s){let M=n.steps.findIndex(O=>O.number===s.number);M!==-1&&(n.steps[M].status="completed",o=`Auto-completed step ${s.number} (${s.name})`,xo(a,i))}let l;if(t!==void 0){if(l=n.steps.find(M=>M.number===t),!l)return p(`Step ${t} not found. The plan has ${n.steps.length} steps (numbered ${n.steps[0].number} to ${n.steps[n.steps.length-1].number}).`,!0)}else if(l=n.steps.find(M=>M.status!=="completed"),!l)return p(JSON.stringify({message:"All plan steps are completed!",completedSteps:n.steps.map(M=>M.name),nextAction:"NEXT: Deploy the app now. Call mist_deploy with action='deploy'. Do NOT suggest localhost or ask the user \u2014 just deploy."}));let d=n.steps.filter(M=>M.status==="completed").map(M=>`Step ${M.number}: ${M.name}`),{readLocalState:u}=await import("./state-manager-XY6GQEFE.js"),h=u(a),c=Vn(l),g=[];if((c==="crud"||c==="schema")&&n.dataModel&&l.entities&&l.entities.length>0){let M=ko(l,n.dataModel);for(let O of M){let E=Gt(O);try{let H=(O.fields||[]).map(F=>typeof F=="string"?{name:F,type:"text"}:{name:F.name,type:zn(F.type),required:F.required!==!1});if(H.length===0)continue;let re=i.dbProvider==="neon"?"nextjs-neon":"nextjs",z=await dr(re,E,H),K=0,U=0;for(let F of z.files){let Q=Ot(a,F.path);if(Sa(Q)){U++;continue}En(On(Q),{recursive:!0}),Ca(Q,F.content),K++}let _=Ot(a,"db","index.ts");if(Sa(_)){let F=wo(_,"utf-8");F.includes(z.dbExport)||Ca(_,F.trimEnd()+`
12393
- `+z.dbExport+`
12394
- `)}K>0?g.push(`${z.entityPascal} CRUD (${K} new files${U>0?`, ${U} existing skipped`:""})`):U>0&&g.push(`${z.entityPascal} CRUD (all ${U} files already exist \u2014 skipped)`)}catch(H){console.error(`Module generation failed for ${E} (non-fatal):`,H instanceof Error?H.message:H)}}}let b=await Jn(l,n,d,null,c,a),y=n.steps.findIndex(M=>M.number===l.number);if(y!==-1&&(i.plan.steps[y].status="in_progress",xo(a,i)),h&&i.projectId){let{syncRemoteState:M}=await import("./state-manager-XY6GQEFE.js");M(i.projectId,h).catch(()=>{})}let f=n.steps.every(M=>M.status==="completed"||M.number===l.number),v;if(f){let M=n.primaryAction?.action??"the core action",O=n.primaryAction?.flow??"sign up \u2192 reach dashboard \u2192 complete primary action";v=`THIS IS THE LAST STEP. Rules for speed:
13404
+ - Server Components by default, "use client" only when interactivity is needed`,skill:""}}}async function cs(r,e,a,t,i,s){let o=[];o.push(`## Step ${r.number}: ${r.name}`),o.push(""),o.push("### What to build:"),o.push(r.description),o.push(""),e.primaryAction&&(o.push("### Primary user action (non-negotiable):"),o.push(`- **Core action**: ${e.primaryAction.action}`),o.push(`- **User flow**: ${e.primaryAction.flow}`),o.push(`- **Dashboard must show**: ${e.primaryAction.dashboardSurface}`),o.push(""),o.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."),o.push(""));let l=["landing","design","dashboard","crud","layout","admin","general","auth"].includes(i);if(e.design&&l?(o.push(ls(e.design,{hasDesignPreset:!!e.landingDesign&&(i==="landing"||i==="design")})),o.push("")):e.design&&!l&&(o.push("### Design tokens (for reference only \u2014 this step is not UI-focused):"),e.design.accentColor&&o.push(`- Accent color: ${e.design.accentColor}`),e.design.fonts&&o.push(`- Fonts: ${e.design.fonts.heading} / ${e.design.fonts.body}`),o.push("")),s){let m=To(s);if(m.length>0){o.push("### Approved wireframe (MUST READ before writing any files):"),o.push("The user approved a wireframe sketch before building. **Read these files NOW before writing any code for this step:**");for(let y of m){let T=y.replace(s,"").replace(/^\//,"");o.push(`- \`${T}\``)}o.push(""),o.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."),o.push(""),o.push("The wireframe is intentionally rough (grayscale, system fonts). Your job is to:"),o.push("1. **Keep the same layout structure** \u2014 same information hierarchy, same element placement, same sections in the same order"),o.push("2. **Apply the design tokens** \u2014 colors, fonts, shadows, radius from the plan design choices above"),o.push("3. **Elevate the visual quality** \u2014 make it feel designed for THIS specific app, not generic"),o.push("4. **Respect the HTML comments** \u2014 they explain WHY things are placed where they are"),o.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"),o.push("")}}e.roles&&Array.isArray(e.roles)&&e.roles.length>0&&(o.push("### Role system (from plan):"),o.push(`- Roles: ${e.roles.join(", ")}`),o.push(`- Default role for new signups: ${e.defaultRole??e.roles[0]}`),o.push("- Role helpers are in `lib/roles.ts` \u2014 use `getUserRole()` and `hasRole()` for access checks"),o.push("")),e.multiTenant&&(o.push("### Multi-tenant (from plan):"),o.push("- Organization tables are in `db/schema/organization.ts`"),o.push("- Org helpers are in `lib/org.ts` \u2014 use `getCurrentOrg()` to scope queries"),o.push("- All data queries MUST be scoped to the current org (filter by orgId)"),o.push("- Org switcher component is at `components/org-switcher.tsx`"),o.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."),o.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."),o.push("")),e.language&&(o.push(`### Language: ${e.language}`),o.push(`ALL user-facing text must be written in ${e.language}:`),o.push("- Page titles, headings, labels, button text, placeholder text"),o.push("- Navigation items, menu labels, footer text"),o.push("- Error messages, success messages, empty states"),o.push("- Landing page copy, marketing text, CTAs"),o.push("- Form labels and validation messages"),o.push("Code (variable names, comments, file names) stays in English."),o.push(`Set the HTML lang attribute to the appropriate locale code for ${e.language}.`),o.push(""));let d=["landing","design","auth","general","crud","dashboard"];e.audienceType&&d.includes(i)&&(e.audienceType==="b2c"?(o.push("### Audience: this app belongs to ONE business. The landing page talks TO their customers."),o.push("- Hero: what the customer gets ('Exceptional catering for your next event'), NOT what the tool does"),o.push("- CTAs: customer action ('Order Catering', 'Book Now'), NOT business action ('Get Started Free')"),o.push("- Testimonials: from customers ('They catered our wedding'), NOT from business owners"),o.push("- Features: customer benefits ('Specify your dietary needs'), NOT business benefits ('Track preferences')"),o.push("- Stats: social proof for customers ('2,400+ events served'), NOT internal metrics ('$48k revenue')"),o.push("- The business name IS the brand. Say it like a business homepage, not a SaaS onboarding."),o.push("")):e.audienceType==="b2b"?(o.push("### Audience: this is a SaaS platform. The landing page pitches TO business owners."),o.push("- Hero: the business pain + solution ('Catering orders managed in one place')"),o.push("- CTAs: business owner action ('Start Free Trial', 'Get Started')"),o.push("- Testimonials: from business owners who use the platform"),o.push("- Features: business benefits ('Track dietary preferences across all orders')"),o.push("- Stats: platform metrics ('500+ businesses', '50K+ orders processed')"),o.push("")):e.audienceType==="internal"&&(o.push("### Audience: internal staff tool. No marketing copy needed."),o.push("- No landing page. Auth page copy is functional: 'Sign in to continue'."),o.push("- Dashboard focuses on operational efficiency, not onboarding or sales."),o.push(""))),a.length>0&&(o.push("### Already completed:"),a.forEach(m=>o.push(`- ${m}`)),o.push(""));let u=e.dataModel?Io(r,e.dataModel):[];u.length>0&&(o.push("### Data model (from plan):"),u.forEach(m=>{let y=Gt(m),T=as(m.fields);o.push(`- **${y}**: ${T}`),o.push(` Schema file: \`db/schema/${y.toLowerCase().replace(/\s+/g,"-")}.ts\``)}),o.push(""));let h=e.pages?os(r,e.pages):[];h.length>0&&(o.push("### Pages to create/update:"),h.forEach(m=>{let y=m.description?` \u2014 ${m.description}`:"";o.push(`- \`${Do(m)}\`${y}`)}),o.push("")),i==="crud"&&u.length>0&&u.forEach(m=>{let y=Gt(m),T=y.toLowerCase().replace(/\s+/g,"-"),k=T.endsWith("s")?T:`${T}s`;o.push(`### Files for ${y} CRUD:`),o.push(`- List page: \`app/(dashboard)/${k}/page.tsx\` (Server Component)`),o.push(`- Detail page: \`app/(dashboard)/${k}/[id]/page.tsx\``),o.push(`- Create page: \`app/(dashboard)/${k}/new/page.tsx\``),o.push(`- Server Actions: \`app/(dashboard)/${k}/actions.ts\``),o.push(`- DataTable columns: \`components/${T}-table-columns.tsx\``),o.push(`- Form: \`components/${T}-form.tsx\``),o.push("")});let c=e.appStyle;if(c&&l){let m=Ar(c,i);if(m){let y=be(c),T=De(c);o.push(`### App style: ${y?.name??c}`),o.push(""),o.push(`Applying the **${y?.name}** app style across this app. Follow these design specifications for consistent, brand-quality UI. The app style defines the visual identity (colors, fonts, spacing). All UI elements must use the project's CSS custom properties (--color-primary, --color-background, etc.) and the fonts configured in layout.tsx.`),T?.fonts&&o.push(`
13405
+ **Fonts**: Use \`${T.fonts.heading}\` for headings and \`${T.fonts.body}\` for body text (Google Fonts equivalents if the originals are proprietary).`),o.push(""),o.push(m),o.push("")}}if(l){let m=bo(e.summary??e.name,{tone:e.design?.tone,audienceType:e.audienceType,archetype:e.design?.archetype});if(m){let y=fo(m);y&&(o.push(`### App archetype: ${m}`),o.push(""),o.push(`This app matches the **${m}** archetype. Follow these component-level patterns for page composition, cards, navigation, spacing, and motion. `+(c?`The app style (${c}) provides visual identity (colors, fonts). The archetype provides layout and component patterns. Use both.`:"All visual styling comes from the project's CSS custom properties and fonts in layout.tsx.")),o.push(""),o.push(y),o.push(""))}}if(i==="landing"||i==="design"){o.push(Po),o.push("");let m=e.design?.landingTone;if(m){let y=ro(m);y&&(o.push(`### Landing tone: ${m}`),o.push(""),o.push(y),o.push(""))}}let g=r.integrationId?Xe(r.integrationId):void 0;if(g){let m=Ze(g.id);if(o.push("### Integration blueprint (follow this closely):"),o.push(""),o.push(`Using integration: **${g.name}** (${g.category})`),m?.docsUrl&&o.push(`Official docs: ${m.docsUrl}`),m?.envVars?.length){o.push(""),o.push("**Required environment variables:**");for(let y of m.envVars)o.push(`- \`${y.key}\`: ${y.description} \u2014 Get it at ${y.setupUrl}`);o.push(""),o.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.")}m?.packages?.length&&(o.push(""),o.push(`**Packages to install:** \`npm install ${m.packages.join(" ")}\``)),o.push(""),o.push("---"),o.push(g.prompt),o.push("---"),o.push(""),o.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."),o.push("")}let{reminders:b,skill:f}=await ds(i);return o.push(b),o.push(""),f&&!(i==="landing"&&e.landingDesign==="freeform")&&(o.push(`### ${i} reference:`),o.push(f),o.push("")),o.join(`
13406
+ `)}async function Mo(r){let{projectPath:e,step:a}=r,t=Qn(e??process.cwd()),i=ts(t);if(!i)return Te(t);if(!Ot(mt(t,"node_modules")))return p(`Dependencies are not installed at ${t}. Call mist_build with action='install' and projectPath='${t}' before running implement. This is a one-time setup step after init.`,!0);try{let{ensureBackendRegistered:C,ensureShadcnComponents:j}=await import("./self-heal-OGLEOZC4.js");await C(t);let L=await j(t);L.failed?console.error(`[implement] ${L.failed}`):L.installed.length>0&&console.error(`[implement] installed ${L.installed.length} shadcn components`)}catch(C){console.error("[implement] self-heal skipped:",C instanceof Error?C.message:String(C))}let s=i.plan;if(!s||!s.steps||s.steps.length===0)return p("No project plan found. Start by describing your app idea first \u2014 the AI will create a plan for you.",!0);let o,n=s.steps.find(C=>C.status==="in_progress");if(n){let C=s.steps.findIndex(j=>j.number===n.number);C!==-1&&(s.steps[C].status="completed",o=`Auto-completed step ${n.number} (${n.name})`,Bo(t,i))}let l;if(a!==void 0){if(l=s.steps.find(C=>C.number===a),!l)return p(`Step ${a} not found. The plan has ${s.steps.length} steps (numbered ${s.steps[0].number} to ${s.steps[s.steps.length-1].number}).`,!0)}else if(l=s.steps.find(C=>C.status!=="completed"),!l)return p(JSON.stringify({message:"All plan steps are completed!",completedSteps:s.steps.map(C=>C.name),nextAction:"NEXT: Deploy the app now. Call mist_deploy with action='deploy'. Do NOT suggest localhost or ask the user \u2014 just deploy."}));let d=s.steps.filter(C=>C.status==="completed").map(C=>`Step ${C.number}: ${C.name}`),{readLocalState:u}=await import("./state-manager-6NJO66MS.js"),h=u(t),c=ns(l),g=[];if((c==="crud"||c==="schema")&&s.dataModel&&l.entities&&l.entities.length>0){let C=Io(l,s.dataModel);for(let j of C){let L=Gt(j);try{let G=(j.fields||[]).map(H=>typeof H=="string"?{name:H,type:"text"}:{name:H.name,type:rs(H.type),required:H.required!==!1});if(G.length===0)continue;let de=i.dbProvider==="neon"?"nextjs-neon":"nextjs",$=await pr(de,L,G),Q=0,V=0;for(let H of $.files){let oe=mt(t,H.path);if(Ot(oe)){V++;continue}Jn(Xn(oe),{recursive:!0}),Pa(oe,H.content),Q++}let F=mt(t,"db","index.ts");if(Ot(F)){let H=Ao(F,"utf-8");H.includes($.dbExport)||Pa(F,H.trimEnd()+`
13407
+ `+$.dbExport+`
13408
+ `)}Q>0?g.push(`${$.entityPascal} CRUD (${Q} new files${V>0?`, ${V} existing skipped`:""})`):V>0&&g.push(`${$.entityPascal} CRUD (all ${V} files already exist \u2014 skipped)`)}catch(G){console.error(`Module generation failed for ${L} (non-fatal):`,G instanceof Error?G.message:G)}}}let b=await cs(l,s,d,null,c,t),f=s.steps.findIndex(C=>C.number===l.number);if(f!==-1&&(i.plan.steps[f].status="in_progress",Bo(t,i)),h&&i.projectId){let{syncRemoteState:C}=await import("./state-manager-6NJO66MS.js");C(i.projectId,h).catch(()=>{})}let m=s.steps.every(C=>C.status==="completed"||C.number===l.number),y;if(m){let C=s.primaryAction?.action??"the core action",j=s.primaryAction?.flow??"sign up \u2192 reach dashboard \u2192 complete primary action";y=`THIS IS THE LAST STEP. Rules for speed:
12395
13409
 
12396
13410
  1. Write ALL files using PARALLEL tool calls \u2014 batch multiple Write/Edit calls in a single message.
12397
13411
  2. Do NOT read files you already know (AGENTS.md, CLAUDE.md, mistflow.json, middleware.ts, lib/auth.ts, lib/db.ts).
@@ -12400,42 +13414,42 @@ Each section is a separate component file. The page.tsx simply imports and stack
12400
13414
  - app/page.tsx must be a real landing page, NOT a redirect to /login
12401
13415
  - middleware.ts must have "/" in PUBLIC_EXACT or PUBLIC_PREFIXES
12402
13416
  - Forms must use server actions (actions.ts with 'use server'), NOT setTimeout/simulate
12403
- 5. Call mist_build with action='build' to verify a clean production build, then call mist_deploy with action='deploy' to ship it.`}else v=`IMPLEMENT THIS STEP NOW. Rules for speed:
13417
+ 5. Call mist_build with action='build' to verify a clean production build, then call mist_deploy with action='deploy' to ship it. Do NOT pause between these two calls to ask the user \u2014 the build is expected, the deploy is expected, just chain them.`}else y=`IMPLEMENT THIS STEP NOW. Rules for speed:
12404
13418
 
12405
- 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.
13419
+ 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.
12406
13420
  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.
12407
13421
  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.
12408
13422
  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).
12409
- 5. After writing ALL files, call mist_build with action='implement' to move to the next step. The previous step is auto-marked complete \u2014 do NOT call implement twice.`;let C=qn(c),P={stepNumber:l.number,totalSteps:n.steps.length,estimatedMinutes:C,announcement:`Starting step ${l.number} of ${n.steps.length}: ${l.name}. This step usually takes ${C.min}\u2013${C.max} minutes.`},B=JSON.stringify({instruction:b,step:{number:l.number,name:l.name,description:l.description,status:"in_progress"},stepTiming:P,compactionGuidance:"If your context gets compacted mid-step (common on long builds), call mist_build action='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.",...o?{autoCompleted:o}:{},...g.length>0?{generatedModules:g,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:`${d.length}/${n.steps.length} steps done`,nextAction:v});return await Wn(3e3)?Ee("http://localhost:3000",B):p(B)}import{z as Ta}from"zod";import{resolve as Qn}from"path";import{execFileSync as Xn}from"child_process";var Ic=Ta.object({projectPath:Ta.string().optional().describe("Path to the project directory (default: current working directory)"),buildOutput:Ta.string().optional().describe("Build output to parse (if not provided, runs npm run build)")});function ft(r){let e=[],t=/([^\s(]+)\((\d+),(\d+)\):\s*error\s+(TS\d+):\s*(.+)/g,a;for(;(a=t.exec(r))!==null;){let[,l,d,u,h,c]=a;e.push({file:l,line:parseInt(d,10),column:parseInt(u,10),message:`${h}: ${c}`,humanMessage:`There is a type error in ${l} on line ${d}: ${c}`,suggestion:`Check line ${d} in ${l}. ${h==="TS2345"?"The types of the arguments do not match.":`Fix the ${h} error.`}`})}let i=/(?:Error:\s*)?\.\/([^\s:]+):(\d+):(\d+)\s*\n\s*(.+)/g;for(;(a=i.exec(r))!==null;){let[,l,d,u,h]=a;e.some(c=>c.file===l&&c.line===parseInt(d,10))||e.push({file:l,line:parseInt(d,10),column:parseInt(u,10),message:h,humanMessage:`There is an error in ${l} on line ${d}: ${h.trim()}`,suggestion:`Check line ${d} in ${l} and fix the issue.`})}let n=/Module not found:\s*(?:Error:\s*)?Can't resolve ['"]([^'"]+)['"]\s*(?:in\s*['"]?([^'"]+)['"]?)?/g;for(;(a=n.exec(r))!==null;){let[,l,d]=a;e.push({file:d,message:`Module not found: ${l}`,humanMessage:`The file ${d??"your project"} is trying to import '${l}' which is not installed.`,suggestion:`Run npm install ${l}`})}let o=/Package subpath ['"]([^'"]+)['"] is not defined by "exports" in .*?node_modules\/([^/]+(?:\/[^/]+)?)\//g;for(;(a=o.exec(r))!==null;){let[,l,d]=a;e.push({message:`ERR_PACKAGE_PATH_NOT_EXPORTED: ${d}${l}`,humanMessage:`The package '${d}' does not export the subpath '${l}'. This is usually caused by a version conflict between packages that depend on different major versions of '${d}'.`,suggestion:`Add '${d}' 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 s=/SyntaxError:\s*([^\s:]+):\s*(.+?)\s*\((\d+):(\d+)\)/g;for(;(a=s.exec(r))!==null;){let[,l,d,u,h]=a;e.some(c=>c.file===l&&c.line===parseInt(u,10))||e.push({file:l,line:parseInt(u,10),column:parseInt(h,10),message:`SyntaxError: ${d}`,humanMessage:`There is a syntax error in ${l} on line ${u}.`,suggestion:`Check line ${u} in ${l} for a missing closing bracket or unexpected token.`})}return e}async function Co(r){let{projectPath:e,buildOutput:t}=r,a=Qn(e??process.cwd()),i=t??"";if(!t)try{return Xn("npm",["run","build"],{cwd:a,stdio:["pipe","pipe","pipe"],timeout:12e4}),p(JSON.stringify({errors:[],rawOutput:"",message:"Build succeeded with no errors."}))}catch(o){let s=o instanceof Error&&"stderr"in o?String(o.stderr):"",l=o instanceof Error&&"stdout"in o?String(o.stdout):"";i=s+`
12410
- `+l}let n=ft(i);return n.length===0?p(JSON.stringify({errors:[],rawOutput:i.slice(0,2e3),message:"Build failed but I could not extract specific errors. Here is the raw output."})):p(JSON.stringify({errors:n,rawOutput:i.slice(0,2e3),message:`Found ${n.length} error${n.length===1?"":"s"} in the build output.`}))}import{existsSync as Zn,readFileSync as es}from"fs";import{join as ts}from"path";function as(r){let e=ts(r,"mistflow.json");if(!Zn(e))return null;try{return JSON.parse(es(e,"utf-8"))}catch{return null}}async function To(r){try{let e=await fetch(r,{redirect:"follow",signal:AbortSignal.timeout(15e3)}),t=await e.text();return{status:e.status,body:t}}catch(e){return{status:0,body:String(e)}}}async function rs(r,e,t){try{let a=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json",...t??{}},body:JSON.stringify(e),redirect:"follow",signal:AbortSignal.timeout(15e3)}),i=await a.text(),n;try{n=JSON.parse(i)}catch{}return{status:a.status,json:n}}catch{return{status:0}}}async function Po(r){try{let e=await r.screenshot({type:"png"});return Buffer.from(e).toString("base64")}catch{return}}async function st(r,e,t){let a=[],i=n=>{n.type()==="error"&&a.push(n.text())};r.on("console",i);try{let n=await t(),o=await Po(r);return{name:e,status:n.pass?"pass":"fail",detail:n.detail,fix:n.fix,screenshot:o,consoleErrors:a.length>0?a:void 0}}catch(n){let o=await Po(r);return{name:e,status:"fail",detail:`Unexpected error: ${n instanceof Error?n.message:String(n)}`,screenshot:o,consoleErrors:a.length>0?a:void 0}}finally{r.removeListener("console",i)}}async function Bo(r){let e=r.projectPath??process.cwd(),t=as(e),a=r.url;if(a||(a=t?.deploy?.url),!a)return p("No deploy URL found. Deploy the app first with mist_deploy, then call mist_build action='qa'.",!0);a.startsWith("http")||(a=`https://${a}`);let i=t?.projectId,n=[],o=await To(`${a}/api/health`);if(o.status!==200)return n.push({name:"Health endpoint",status:"fail",detail:`Returns ${o.status}`,fix:"The worker is not running or crashed on startup. Check app/api/health/route.ts exists and the build succeeded."}),Wt(a,n);n.push({name:"Health endpoint",status:"pass",detail:"Returns 200"});let s=await To(`${a}/api/auth/ok`);if(s.status!==200)return n.push({name:"Auth system",status:"fail",detail:`Auth endpoint returns ${s.status}`,fix:"Better Auth is not working. Check lib/auth.ts, lib/db.ts, and that your database env vars are set."}),Wt(a,n);n.push({name:"Auth system",status:"pass",detail:"Better Auth running"});let l,d,u;if(i){let g=await Qa(i);if(g){console.error("[qa] Calling seed endpoint for session token");let b=await rs(`${a}/api/admin/seed`,{token:g.seedToken,email:g.email,password:"QaTemp1!"});b.status===200&&b.json?(l=b.json.sessionToken,d=b.json.email,b.json.seeded?(u=b.json.password,console.error("[qa] New admin seeded \u2014 login form test available")):console.error("[qa] Admin already exists \u2014 session injection only")):console.error(`[qa] Seed endpoint returned ${b.status}`)}}if(!l)return n.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 process injects ADMIN_SEED_TOKEN into the worker env and stores the seed token on the backend. If the seed endpoint at /api/admin/seed is missing, ensure app/api/admin/seed/route.ts exists in the scaffold."}),Wt(a,n);let h,c;try{let{getIsolatedContext:g,takeScreenshot:b}=await import("./browser-manager-K5BT5YXO.js"),y=await g();h=y.context,c=y.page}catch(g){let b=g instanceof Error?g.message:String(g);return p(JSON.stringify({status:"cannot_verify",url:a,deployed:!0,reason:"App deployed successfully, but QA could not verify it via automated browser testing because Playwright is not installed locally.",detail:b,httpChecks:n.map(({screenshot:y,...f})=>f),fix:"Run: npx playwright install chromium",instruction:[`The app deployed and is live at ${a}. 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 ${a}. 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_build action='qa' again for full verification."].join(`
12411
- `)}),!1)}try{let g=await st(c,"Landing page",async()=>{await c.goto(a,{waitUntil:"domcontentloaded",timeout:3e4}),await c.waitForLoadState("networkidle").catch(()=>{});let y=await c.evaluate(()=>{let v=document.body;if(!v)return"";let C=document.createTreeWalker(v,NodeFilter.SHOW_TEXT),P="",A;for(;A=C.nextNode();){let B=A.parentElement;if(B){let I=window.getComputedStyle(B);I.display!=="none"&&I.visibility!=="hidden"&&parseFloat(I.opacity)>0&&(P+=A.textContent?.trim()+" ")}}return P.trim()});if(y.length<50)return{pass:!1,detail:`Landing page appears blank (${y.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 f=c.url();return f.includes("/login")||f.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 (${y.length} chars)`}});n.push(g);let b=!1;if(u){let y=await st(c,"Login",async()=>{await c.goto(`${a}/login`,{waitUntil:"domcontentloaded",timeout:15e3}),await c.waitForLoadState("networkidle").catch(()=>{});let f=c.locator('input[type="email"], input[name="email"], input[placeholder*="email" i]'),v=c.locator('input[type="password"], input[name="password"]');try{await f.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 f.first().fill(d),await v.first().fill(u),await c.locator('button[type="submit"], button:has-text("Sign in"), button:has-text("Log in"), button:has-text("Login")').first().click();try{await c.waitForURL(P=>!P.pathname.includes("/login"),{timeout:1e4})}catch{let P=await c.locator('[role="alert"], .text-red-500, .text-destructive, [data-error]').first().textContent().catch(()=>null);return{pass:!1,detail:P?`Login failed: ${P}`:"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 admin account."}}return b=!0,{pass:!0,detail:`Logged in, redirected to ${c.url()}`}});n.push(y)}else n.push({name:"Login",status:"pass",detail:"Skipped form login (redeploy, password unavailable). Using session injection."});if(!b&&l){let y=new URL(a).hostname,f=a.startsWith("https");await h.addCookies([{name:f?"__Secure-better-auth.session_token":"better-auth.session_token",value:l,domain:y,path:"/",httpOnly:!0,secure:f,sameSite:"Lax"}]),console.error("[qa] Injected session cookie for dashboard checks")}{let y=await st(c,"Dashboard",async()=>{c.url().includes("/dashboard")||(await c.goto(`${a}/dashboard`,{waitUntil:"domcontentloaded",timeout:15e3}),await c.waitForLoadState("networkidle").catch(()=>{}));let P=await c.content();return P.length<1e3?{pass:!1,detail:`Dashboard page is very small (${P.length} bytes)`,fix:"Check app/(dashboard)/dashboard/page.tsx exists and the dashboard layout doesn't crash."}:await c.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 (${P.length} bytes)`}});n.push(y);let f=await c.evaluate(()=>{let C=[];return document.querySelectorAll("nav a[href], aside a[href]").forEach(A=>{let B=A.getAttribute("href");B&&B.startsWith("/")&&!B.startsWith("/api")&&!B.includes("[")&&B!=="/dashboard"&&B!=="/"&&!B.includes("/login")&&!B.includes("/sign")&&C.push(B)}),[...new Set(C)]});if(f.length>0){let C=0,P=[];for(let A of f.slice(0,8)){let B=await st(c,`Page: ${A}`,async()=>{await c.goto(`${a}${A}`,{waitUntil:"domcontentloaded",timeout:15e3}),await c.waitForLoadState("networkidle").catch(()=>{});let I=await c.title(),M=await c.content();return I.toLowerCase().includes("500")||I.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."}:I.toLowerCase().includes("404")||I.toLowerCase().includes("not found")?{pass:!1,detail:"Page returns 404",fix:`Page ${A} not found. Create the page or remove the nav link.`}:await c.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."}:M.length<500?{pass:!1,detail:`Page is very small (${M.length} bytes)`,fix:"Page may not have rendered. Check the page component."}:{pass:!0,detail:"Loads without errors"}});B.status==="fail"&&(C++,P.push(A)),n.push(B)}}let v=await st(c,"Design quality",async()=>{let C=c.url().includes("/dashboard")?c.url():`${a}/dashboard`;c.url().includes("/dashboard")||(await c.goto(C,{waitUntil:"domcontentloaded",timeout:15e3}),await c.waitForLoadState("networkidle").catch(()=>{}));let P=await c.evaluate(()=>{let A=[],B=document.querySelectorAll("h1, h2, h3, h4, h5, h6"),I=new Set;B.forEach(w=>{I.add(window.getComputedStyle(w).fontSize)}),B.length>=3&&I.size<2&&A.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 M=Array.from(B).map(w=>parseInt(w.tagName.charAt(1),10));for(let w=1;w<M.length;w++)if(M[w]-M[w-1]>1){A.push(`TYPOGRAPHY: Heading level skipped (h${M[w-1]} -> h${M[w]}). Use sequential heading levels for accessibility.`);break}let O=document.querySelectorAll("*"),E=!1,H=!1;O.forEach(w=>{let N=window.getComputedStyle(w);N.backgroundColor==="rgb(0, 0, 0)"&&w.clientHeight>100&&w.clientWidth>200&&(E=!0);let q=N.backgroundColor,me=N.color;if(q&&!q.includes("0, 0, 0")&&!q.includes("255, 255, 255")&&q!=="rgba(0, 0, 0, 0)"&&q!=="transparent"&&me.match(/rgb\((\d+), (\d+), (\d+)\)/)){let pe=me.match(/rgb\((\d+), (\d+), (\d+)\)/);if(pe){let[Z,xe,we]=[parseInt(pe[1]),parseInt(pe[2]),parseInt(pe[3])],de=Math.abs(Z-xe)<10&&Math.abs(xe-we)<10&&Z>80&&Z<180,$=q.match(/rgb\((\d+), (\d+), (\d+)\)/);if(de&&$){let[k,m,x]=[parseInt($[1]),parseInt($[2]),parseInt($[3])];!(Math.abs(k-m)<15&&Math.abs(m-x)<15)&&w.textContent&&w.textContent.trim().length>0&&(H=!0)}}}}),E&&A.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)."),H&&A.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 re=document.querySelectorAll('[class*="card"], [class*="Card"], [role="group"]'),z=!1;re.forEach(w=>{w.querySelectorAll('[class*="card"], [class*="Card"]').length>0&&(z=!0)}),z&&A.push("LAYOUT: Nested cards detected (card inside card). Flatten the hierarchy. Use spacing and background color to create separation instead.");let K=document.querySelectorAll("p, li, span, div"),U=0,_=0;K.forEach(w=>{w.textContent&&w.textContent.trim().length>20&&w.clientHeight>0&&(_++,window.getComputedStyle(w).textAlign==="center"&&U++)}),_>5&&U/_>.7&&A.push("LAYOUT: Most text is center-aligned. Use left-alignment for body content and lists. Reserve center-alignment for heroes and CTAs.");let F=new Set;O.forEach(w=>{let N=window.getComputedStyle(w);N.gap&&N.gap!=="normal"&&N.gap!=="0px"&&F.add(N.gap)}),F.size===1&&O.length>20&&A.push("LAYOUT: Same gap value used everywhere. Vary spacing to create hierarchy: tight within groups (8-12px), generous between sections (32-64px).");let Q=document.querySelectorAll("button, a, input, select, textarea"),le=!1;Q.forEach(w=>{let N=window.getComputedStyle(w);if(N.outline==="none"||N.outline==="0px none rgb(0, 0, 0)"){let q=w;(q.style.outline==="none"||q.style.outline==="0")&&(le=!0)}}),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"]')||A.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 R=document.querySelectorAll("style"),L=!1;R.forEach(w=>{let N=w.textContent||"";(N.includes("bounce")||N.includes("elastic")||N.match(/cubic-bezier\([^)]*[2-9]\.[0-9]/))&&(L=!0)}),L&&A.push("MOTION: Bounce or elastic easing detected. These feel dated. Use smooth deceleration curves (Quart out, Expo out) instead.");let oe=document.querySelectorAll("button, a, input, select, [role='button']"),ie=0;return oe.forEach(w=>{let N=w.getBoundingClientRect();N.width>0&&N.height>0&&(N.width<32||N.height<32)&&ie++}),ie>3&&A.push(`ACCESSIBILITY: ${ie} interactive elements are smaller than 32x32px. Minimum recommended touch target is 44x44px. Add padding to increase tap area.`),A});return P.length===0?{pass:!0,detail:"No design quality issues detected. Typography hierarchy, color usage, layout patterns, and accessibility basics look good."}:{pass:!1,detail:`${P.length} design quality issue(s) found:
12412
- ${P.map((A,B)=>`${B+1}. ${A}`).join(`
12413
- `)}`,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."}});if(n.push(v),n.find(C=>C.name==="Landing page"&&C.status==="pass")){let C=await st(c,"Landing design quality",async()=>{await c.goto(a,{waitUntil:"domcontentloaded",timeout:15e3}),await c.waitForLoadState("networkidle").catch(()=>{});let P=await c.evaluate(()=>{let A=[];document.querySelectorAll("*").forEach(O=>{let E=window.getComputedStyle(O);(E.getPropertyValue("-webkit-background-clip")||E.getPropertyValue("background-clip"))==="text"&&O.textContent&&O.textContent.trim().length>0&&A.push("SLOP: Gradient text detected. This is a common AI design pattern. Use solid colors for text.")});let I=document.querySelector("section, [class*='hero'], [class*='Hero'], header + div, main > div:first-child");if(I){let O=(I.textContent||"").toLowerCase(),E=["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 H of E)if(O.match(new RegExp(H))){A.push(`COPY: Generic hero text detected ('${H}'). Write specific copy about what THIS app does for its users.`);break}}return document.querySelectorAll('[class*="grid"]').forEach(O=>{let H=window.getComputedStyle(O).gridTemplateColumns;if(H){let re=H.split(" ").filter(K=>K!=="").length,z=O.children;if(re===3&&z.length===3){let K=Array.from(z).map(F=>F.offsetHeight),U=K.every(F=>Math.abs(F-K[0])<5),_=Array.from(z).every(F=>{let Q=F.querySelectorAll("svg"),le=F.querySelectorAll("h2, h3, h4"),T=F.querySelectorAll("p");return Q.length>=1&&le.length>=1&&T.length>=1});U&&_&&A.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.")}}}),A});return P.length===0?{pass:!0,detail:"Landing page design looks intentional. No generic AI patterns detected."}:{pass:!1,detail:`${P.length} landing design issue(s):
12414
- ${P.map((A,B)=>`${B+1}. ${A}`).join(`
12415
- `)}`,fix:"These patterns make the landing page look AI-generated. Fix them to create a more distinctive, professional design."}});n.push(C)}}}finally{h&&await h.close().catch(()=>{})}if(r.deploymentId){let g=n.filter(f=>f.status==="fail"),b=n.filter(f=>f.status==="pass"),y=Date.now();await Xa(r.deploymentId,{checks:n.map(({screenshot:f,...v})=>v),overall:g.length===0?"pass":"fail",passed:b.length,failed:g.length,duration_ms:Date.now()-y}).catch(()=>{})}return Wt(a,n)}function Wt(r,e){let t=e.filter(n=>n.status==="fail"),a=e.filter(n=>n.status==="pass"),i=[];if(t.length===0)i.push({type:"text",text:JSON.stringify({status:"pass",message:`QA passed. All ${e.length} checks OK. The app is working correctly.`,url:r,checks:e.map(({screenshot:n,...o})=>o)})});else{let n=t.map((o,s)=>`${s+1}. **${o.name}**: ${o.detail}
13423
+ 5. After writing ALL files, call mist_build with action='implement' to move to the next step. 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 T=ss(c),k={stepNumber:l.number,totalSteps:s.steps.length,estimatedMinutes:T,announcement:`Starting step ${l.number} of ${s.steps.length}: ${l.name}. This step usually takes ${T.min}\u2013${T.max} minutes.`},B=JSON.stringify({instruction:b,step:{number:l.number,name:l.name,description:l.description,status:"in_progress"},stepTiming:k,compactionGuidance:"If your context gets compacted mid-step (common on long builds), call mist_build action='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.",...o?{autoCompleted:o}:{},...g.length>0?{generatedModules:g,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:`${d.length}/${s.steps.length} steps done`,nextAction:y});return await es(3e3)?Fe("http://localhost:3000",B):p(B)}import{z as Ba}from"zod";import{resolve as ps}from"path";import{execFileSync as us}from"child_process";var ap=Ba.object({projectPath:Ba.string().optional().describe("Path to the project directory (default: current working directory)"),buildOutput:Ba.string().optional().describe("Build output to parse (if not provided, runs npm run build)")});function ft(r){let e=[],a=/([^\s(]+)\((\d+),(\d+)\):\s*error\s+(TS\d+):\s*(.+)/g,t;for(;(t=a.exec(r))!==null;){let[,l,d,u,h,c]=t;e.push({file:l,line:parseInt(d,10),column:parseInt(u,10),message:`${h}: ${c}`,humanMessage:`There is a type error in ${l} on line ${d}: ${c}`,suggestion:`Check line ${d} in ${l}. ${h==="TS2345"?"The types of the arguments do not match.":`Fix the ${h} error.`}`})}let i=/(?:Error:\s*)?\.\/([^\s:]+):(\d+):(\d+)\s*\n\s*(.+)/g;for(;(t=i.exec(r))!==null;){let[,l,d,u,h]=t;e.some(c=>c.file===l&&c.line===parseInt(d,10))||e.push({file:l,line:parseInt(d,10),column:parseInt(u,10),message:h,humanMessage:`There is an error in ${l} on line ${d}: ${h.trim()}`,suggestion:`Check line ${d} in ${l} and fix the issue.`})}let s=/Module not found:\s*(?:Error:\s*)?Can't resolve ['"]([^'"]+)['"]\s*(?:in\s*['"]?([^'"]+)['"]?)?/g;for(;(t=s.exec(r))!==null;){let[,l,d]=t;e.push({file:d,message:`Module not found: ${l}`,humanMessage:`The file ${d??"your project"} is trying to import '${l}' which is not installed.`,suggestion:`Run npm install ${l}`})}let o=/Package subpath ['"]([^'"]+)['"] is not defined by "exports" in .*?node_modules\/([^/]+(?:\/[^/]+)?)\//g;for(;(t=o.exec(r))!==null;){let[,l,d]=t;e.push({message:`ERR_PACKAGE_PATH_NOT_EXPORTED: ${d}${l}`,humanMessage:`The package '${d}' does not export the subpath '${l}'. This is usually caused by a version conflict between packages that depend on different major versions of '${d}'.`,suggestion:`Add '${d}' 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 n=/SyntaxError:\s*([^\s:]+):\s*(.+?)\s*\((\d+):(\d+)\)/g;for(;(t=n.exec(r))!==null;){let[,l,d,u,h]=t;e.some(c=>c.file===l&&c.line===parseInt(u,10))||e.push({file:l,line:parseInt(u,10),column:parseInt(h,10),message:`SyntaxError: ${d}`,humanMessage:`There is a syntax error in ${l} on line ${u}.`,suggestion:`Check line ${u} in ${l} for a missing closing bracket or unexpected token.`})}return e}async function Ro(r){let{projectPath:e,buildOutput:a}=r,t=ps(e??process.cwd()),i=a??"";if(!a)try{return us("npm",["run","build"],{cwd:t,stdio:["pipe","pipe","pipe"],timeout:12e4}),p(JSON.stringify({errors:[],rawOutput:"",message:"Build succeeded with no errors."}))}catch(o){let n=o instanceof Error&&"stderr"in o?String(o.stderr):"",l=o instanceof Error&&"stdout"in o?String(o.stdout):"";i=n+`
13424
+ `+l}let s=ft(i);return s.length===0?p(JSON.stringify({errors:[],rawOutput:i.slice(0,2e3),message:"Build failed but I could not extract specific errors. Here is the raw output."})):p(JSON.stringify({errors:s,rawOutput:i.slice(0,2e3),message:`Found ${s.length} error${s.length===1?"":"s"} in the build output.`}))}import{existsSync as hs,readFileSync as gs}from"fs";import{join as ms}from"path";function fs(r){let e=ms(r,"mistflow.json");if(!hs(e))return null;try{return JSON.parse(gs(e,"utf-8"))}catch{return null}}async function No(r){try{let e=await fetch(r,{redirect:"follow",signal:AbortSignal.timeout(15e3)}),a=await e.text();return{status:e.status,body:a}}catch(e){return{status:0,body:String(e)}}}async function bs(r,e,a){try{let t=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json",...a??{}},body:JSON.stringify(e),redirect:"follow",signal:AbortSignal.timeout(15e3)}),i=await t.text(),s;try{s=JSON.parse(i)}catch{}return{status:t.status,json:s}}catch{return{status:0}}}async function Lo(r){try{let e=await r.screenshot({type:"png"});return Buffer.from(e).toString("base64")}catch{return}}async function rt(r,e,a){let t=[],i=s=>{s.type()==="error"&&t.push(s.text())};r.on("console",i);try{let s=await a(),o=await Lo(r);return{name:e,status:s.pass?"pass":"fail",detail:s.detail,fix:s.fix,screenshot:o,consoleErrors:t.length>0?t:void 0}}catch(s){let o=await Lo(r);return{name:e,status:"fail",detail:`Unexpected error: ${s instanceof Error?s.message:String(s)}`,screenshot:o,consoleErrors:t.length>0?t:void 0}}finally{r.removeListener("console",i)}}async function Fo(r){let e=r.projectPath??process.cwd(),a=fs(e),t=r.url;if(t||(t=a?.deploy?.url),!t)return p("No deploy URL found. Deploy the app first with mist_deploy, then call mist_build action='qa'.",!0);t.startsWith("http")||(t=`https://${t}`);let i=a?.projectId,s=[],o=await No(`${t}/api/health`);if(o.status!==200)return s.push({name:"Health endpoint",status:"fail",detail:`Returns ${o.status}`,fix:"The worker is not running or crashed on startup. Check app/api/health/route.ts exists and the build succeeded."}),Wt(t,s);s.push({name:"Health endpoint",status:"pass",detail:"Returns 200"});let n=await No(`${t}/api/auth/ok`);if(n.status!==200)return s.push({name:"Auth system",status:"fail",detail:`Auth endpoint returns ${n.status}`,fix:"Better Auth is not working. Check lib/auth.ts, lib/db.ts, and that your database env vars are set."}),Wt(t,s);s.push({name:"Auth system",status:"pass",detail:"Better Auth running"});let l,d,u;if(i){let g=await Za(i);if(g){console.error("[qa] Calling seed endpoint for session token");let b=await bs(`${t}/api/admin/seed`,{token:g.seedToken,email:g.email,password:"QaTemp1!"});b.status===200&&b.json?(l=b.json.sessionToken,d=b.json.email,b.json.seeded?(u=b.json.password,console.error("[qa] New admin seeded \u2014 login form test available")):console.error("[qa] Admin already exists \u2014 session injection only")):console.error(`[qa] Seed endpoint returned ${b.status}`)}}if(!l)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 process injects ADMIN_SEED_TOKEN into the worker env and stores the seed token on the backend. If the seed endpoint at /api/admin/seed is missing, ensure app/api/admin/seed/route.ts exists in the scaffold."}),Wt(t,s);let h,c;try{let{getIsolatedContext:g,takeScreenshot:b}=await import("./browser-manager-K5BT5YXO.js"),f=await g();h=f.context,c=f.page}catch(g){let b=g instanceof Error?g.message:String(g);return p(JSON.stringify({status:"cannot_verify",url:t,deployed:!0,reason:"App deployed successfully, but QA could not verify it via automated browser testing because Playwright is not installed locally.",detail:b,httpChecks:s.map(({screenshot:f,...m})=>m),fix:"Run: npx playwright install chromium",instruction:[`The app deployed and is live at ${t}. 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 ${t}. 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_build action='qa' again for full verification."].join(`
13425
+ `)}),!1)}try{let g=await rt(c,"Landing page",async()=>{await c.goto(t,{waitUntil:"domcontentloaded",timeout:3e4}),await c.waitForLoadState("networkidle").catch(()=>{});let f=await c.evaluate(()=>{let y=document.body;if(!y)return"";let T=document.createTreeWalker(y,NodeFilter.SHOW_TEXT),k="",I;for(;I=T.nextNode();){let B=I.parentElement;if(B){let M=window.getComputedStyle(B);M.display!=="none"&&M.visibility!=="hidden"&&parseFloat(M.opacity)>0&&(k+=I.textContent?.trim()+" ")}}return k.trim()});if(f.length<50)return{pass:!1,detail:`Landing page appears blank (${f.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 m=c.url();return m.includes("/login")||m.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 (${f.length} chars)`}});s.push(g);let b=!1;if(u){let f=await rt(c,"Login",async()=>{await c.goto(`${t}/login`,{waitUntil:"domcontentloaded",timeout:15e3}),await c.waitForLoadState("networkidle").catch(()=>{});let m=c.locator('input[type="email"], input[name="email"], input[placeholder*="email" i]'),y=c.locator('input[type="password"], input[name="password"]');try{await m.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 m.first().fill(d),await y.first().fill(u),await c.locator('button[type="submit"], button:has-text("Sign in"), button:has-text("Log in"), button:has-text("Login")').first().click();try{await c.waitForURL(k=>!k.pathname.includes("/login"),{timeout:1e4})}catch{let k=await c.locator('[role="alert"], .text-red-500, .text-destructive, [data-error]').first().textContent().catch(()=>null);return{pass:!1,detail:k?`Login failed: ${k}`:"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 admin account."}}return b=!0,{pass:!0,detail:`Logged in, redirected to ${c.url()}`}});s.push(f)}else s.push({name:"Login",status:"pass",detail:"Skipped form login (redeploy, password unavailable). Using session injection."});if(!b&&l){let f=new URL(t).hostname,m=t.startsWith("https");await h.addCookies([{name:m?"__Secure-better-auth.session_token":"better-auth.session_token",value:l,domain:f,path:"/",httpOnly:!0,secure:m,sameSite:"Lax"}]),console.error("[qa] Injected session cookie for dashboard checks")}{let f=await rt(c,"Dashboard",async()=>{c.url().includes("/dashboard")||(await c.goto(`${t}/dashboard`,{waitUntil:"domcontentloaded",timeout:15e3}),await c.waitForLoadState("networkidle").catch(()=>{}));let k=await c.content();return k.length<1e3?{pass:!1,detail:`Dashboard page is very small (${k.length} bytes)`,fix:"Check app/(dashboard)/dashboard/page.tsx exists and the dashboard layout doesn't crash."}:await c.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 (${k.length} bytes)`}});s.push(f);let m=await c.evaluate(()=>{let T=[];return document.querySelectorAll("nav a[href], aside a[href]").forEach(I=>{let B=I.getAttribute("href");B&&B.startsWith("/")&&!B.startsWith("/api")&&!B.includes("[")&&B!=="/dashboard"&&B!=="/"&&!B.includes("/login")&&!B.includes("/sign")&&T.push(B)}),[...new Set(T)]});if(m.length>0){let T=0,k=[];for(let I of m.slice(0,8)){let B=await rt(c,`Page: ${I}`,async()=>{await c.goto(`${t}${I}`,{waitUntil:"domcontentloaded",timeout:15e3}),await c.waitForLoadState("networkidle").catch(()=>{});let M=await c.title(),C=await c.content();return M.toLowerCase().includes("500")||M.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."}:M.toLowerCase().includes("404")||M.toLowerCase().includes("not found")?{pass:!1,detail:"Page returns 404",fix:`Page ${I} not found. Create the page or remove the nav link.`}:await c.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."}:C.length<500?{pass:!1,detail:`Page is very small (${C.length} bytes)`,fix:"Page may not have rendered. Check the page component."}:{pass:!0,detail:"Loads without errors"}});B.status==="fail"&&(T++,k.push(I)),s.push(B)}}let y=await rt(c,"Design quality",async()=>{let T=c.url().includes("/dashboard")?c.url():`${t}/dashboard`;c.url().includes("/dashboard")||(await c.goto(T,{waitUntil:"domcontentloaded",timeout:15e3}),await c.waitForLoadState("networkidle").catch(()=>{}));let k=await c.evaluate(()=>{let I=[],B=document.querySelectorAll("h1, h2, h3, h4, h5, h6"),M=new Set;B.forEach(x=>{M.add(window.getComputedStyle(x).fontSize)}),B.length>=3&&M.size<2&&I.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 C=Array.from(B).map(x=>parseInt(x.tagName.charAt(1),10));for(let x=1;x<C.length;x++)if(C[x]-C[x-1]>1){I.push(`TYPOGRAPHY: Heading level skipped (h${C[x-1]} -> h${C[x]}). Use sequential heading levels for accessibility.`);break}let j=document.querySelectorAll("*"),L=!1,G=!1;j.forEach(x=>{let E=window.getComputedStyle(x);E.backgroundColor==="rgb(0, 0, 0)"&&x.clientHeight>100&&x.clientWidth>200&&(L=!0);let q=E.backgroundColor,ge=E.color;if(q&&!q.includes("0, 0, 0")&&!q.includes("255, 255, 255")&&q!=="rgba(0, 0, 0, 0)"&&q!=="transparent"&&ge.match(/rgb\((\d+), (\d+), (\d+)\)/)){let S=ge.match(/rgb\((\d+), (\d+), (\d+)\)/);if(S){let[N,W,J]=[parseInt(S[1]),parseInt(S[2]),parseInt(S[3])],se=Math.abs(N-W)<10&&Math.abs(W-J)<10&&N>80&&N<180,R=q.match(/rgb\((\d+), (\d+), (\d+)\)/);if(se&&R){let[w,v,A]=[parseInt(R[1]),parseInt(R[2]),parseInt(R[3])];!(Math.abs(w-v)<15&&Math.abs(v-A)<15)&&x.textContent&&x.textContent.trim().length>0&&(G=!0)}}}}),L&&I.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)."),G&&I.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 de=document.querySelectorAll('[class*="card"], [class*="Card"], [role="group"]'),$=!1;de.forEach(x=>{x.querySelectorAll('[class*="card"], [class*="Card"]').length>0&&($=!0)}),$&&I.push("LAYOUT: Nested cards detected (card inside card). Flatten the hierarchy. Use spacing and background color to create separation instead.");let Q=document.querySelectorAll("p, li, span, div"),V=0,F=0;Q.forEach(x=>{x.textContent&&x.textContent.trim().length>20&&x.clientHeight>0&&(F++,window.getComputedStyle(x).textAlign==="center"&&V++)}),F>5&&V/F>.7&&I.push("LAYOUT: Most text is center-aligned. Use left-alignment for body content and lists. Reserve center-alignment for heroes and CTAs.");let H=new Set;j.forEach(x=>{let E=window.getComputedStyle(x);E.gap&&E.gap!=="normal"&&E.gap!=="0px"&&H.add(E.gap)}),H.size===1&&j.length>20&&I.push("LAYOUT: Same gap value used everywhere. Vary spacing to create hierarchy: tight within groups (8-12px), generous between sections (32-64px).");let oe=document.querySelectorAll("button, a, input, select, textarea"),he=!1;oe.forEach(x=>{let E=window.getComputedStyle(x);if(E.outline==="none"||E.outline==="0px none rgb(0, 0, 0)"){let q=x;(q.style.outline==="none"||q.style.outline==="0")&&(he=!0)}}),document.querySelectorAll("table, [role='table']").forEach(x=>{x.querySelectorAll("tbody tr").length===0&&(x.parentElement?.querySelector('[class*="empty"], [class*="Empty"], [class*="no-data"], [class*="placeholder"]')||I.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 pe=document.querySelectorAll("style"),P=!1;pe.forEach(x=>{let E=x.textContent||"";(E.includes("bounce")||E.includes("elastic")||E.match(/cubic-bezier\([^)]*[2-9]\.[0-9]/))&&(P=!0)}),P&&I.push("MOTION: Bounce or elastic easing detected. These feel dated. Use smooth deceleration curves (Quart out, Expo out) instead.");let U=document.querySelectorAll("button, a, input, select, [role='button']"),O=0;return U.forEach(x=>{let E=x.getBoundingClientRect();E.width>0&&E.height>0&&(E.width<32||E.height<32)&&O++}),O>3&&I.push(`ACCESSIBILITY: ${O} interactive elements are smaller than 32x32px. Minimum recommended touch target is 44x44px. Add padding to increase tap area.`),I});return k.length===0?{pass:!0,detail:"No design quality issues detected. Typography hierarchy, color usage, layout patterns, and accessibility basics look good."}:{pass:!1,detail:`${k.length} design quality issue(s) found:
13426
+ ${k.map((I,B)=>`${B+1}. ${I}`).join(`
13427
+ `)}`,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."}});if(s.push(y),s.find(T=>T.name==="Landing page"&&T.status==="pass")){let T=await rt(c,"Landing design quality",async()=>{await c.goto(t,{waitUntil:"domcontentloaded",timeout:15e3}),await c.waitForLoadState("networkidle").catch(()=>{});let k=await c.evaluate(()=>{let I=[];document.querySelectorAll("*").forEach(j=>{let L=window.getComputedStyle(j);(L.getPropertyValue("-webkit-background-clip")||L.getPropertyValue("background-clip"))==="text"&&j.textContent&&j.textContent.trim().length>0&&I.push("SLOP: Gradient text detected. This is a common AI design pattern. Use solid colors for text.")});let M=document.querySelector("section, [class*='hero'], [class*='Hero'], header + div, main > div:first-child");if(M){let j=(M.textContent||"").toLowerCase(),L=["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 G of L)if(j.match(new RegExp(G))){I.push(`COPY: Generic hero text detected ('${G}'). Write specific copy about what THIS app does for its users.`);break}}return document.querySelectorAll('[class*="grid"]').forEach(j=>{let G=window.getComputedStyle(j).gridTemplateColumns;if(G){let de=G.split(" ").filter(Q=>Q!=="").length,$=j.children;if(de===3&&$.length===3){let Q=Array.from($).map(H=>H.offsetHeight),V=Q.every(H=>Math.abs(H-Q[0])<5),F=Array.from($).every(H=>{let oe=H.querySelectorAll("svg"),he=H.querySelectorAll("h2, h3, h4"),Y=H.querySelectorAll("p");return oe.length>=1&&he.length>=1&&Y.length>=1});V&&F&&I.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.")}}}),I});return k.length===0?{pass:!0,detail:"Landing page design looks intentional. No generic AI patterns detected."}:{pass:!1,detail:`${k.length} landing design issue(s):
13428
+ ${k.map((I,B)=>`${B+1}. ${I}`).join(`
13429
+ `)}`,fix:"These patterns make the landing page look AI-generated. Fix them to create a more distinctive, professional design."}});s.push(T)}}}finally{h&&await h.close().catch(()=>{})}if(r.deploymentId){let g=s.filter(m=>m.status==="fail"),b=s.filter(m=>m.status==="pass"),f=Date.now();await er(r.deploymentId,{checks:s.map(({screenshot:m,...y})=>y),overall:g.length===0?"pass":"fail",passed:b.length,failed:g.length,duration_ms:Date.now()-f}).catch(()=>{})}return Wt(t,s)}function Wt(r,e){let a=e.filter(s=>s.status==="fail"),t=e.filter(s=>s.status==="pass"),i=[];if(a.length===0)i.push({type:"text",text:JSON.stringify({status:"pass",message:`QA passed. All ${e.length} checks OK. The app is working correctly.`,url:r,checks:e.map(({screenshot:s,...o})=>o)})});else{let s=a.map((o,n)=>`${n+1}. **${o.name}**: ${o.detail}
12416
13430
  Fix: ${o.fix}`).join(`
12417
13431
 
12418
- `);i.push({type:"text",text:JSON.stringify({status:"fail",message:`QA found ${t.length} issue(s) on the live app. Fix them and redeploy.`,url:r,passed:a.length,failed:t.length,checks:e.map(({screenshot:o,...s})=>s),fixInstructions:`The deployed app at ${r} has ${t.length} issue(s):
13432
+ `);i.push({type:"text",text:JSON.stringify({status:"fail",message:`QA found ${a.length} issue(s) on the live app. Fix them and redeploy.`,url:r,passed:t.length,failed:a.length,checks:e.map(({screenshot:o,...n})=>n),fixInstructions:`The deployed app at ${r} has ${a.length} issue(s):
12419
13433
 
12420
- ${n}
13434
+ ${s}
12421
13435
 
12422
- Fix these issues in the source code, then call mist_deploy with action='deploy'${r.includes("-pv-")?" environment='preview'":""} to redeploy. After redeploying, call mist_build action='qa' again to verify the fixes.`})})}for(let n of e)n.screenshot&&i.push({type:"image",data:n.screenshot,mimeType:"image/png"});return{content:i}}function ss(r){let e=jt(ns(),".mistflow","plans",`${r}.json`);if(!_t(e))return null;try{let t=JSON.parse(os(e,"utf-8"));return t.plan?t:null}catch{return null}}var ls=ae.object({action:ae.enum(["init","implement","debug","build","qa","mockup"]).describe("'mockup' generates visual mockup HTML files from a plan for user preview and approval. 'init' creates and sets up a new project from your plan. 'implement' executes the next (or specific) plan step. 'debug' analyzes build or runtime errors \u2014 for runtime errors, call mist_project errors first then pass the output as buildOutput. 'build' runs the full production build locally (Mistflow Cloud edge runtime) without deploying. 'qa' tests the live deployed app \u2014 checks landing page, signup, login, dashboard, and nav links. Call after mist_deploy. If issues found, fix and redeploy, then call qa again."),name:ae.string().optional().describe("(init) Project name"),planId:ae.string().optional().describe("(init/mockup) Plan ID from mist_plan"),plan:ae.any().optional().describe("(init) Full plan object \u2014 use planId instead when available"),path:ae.string().optional().describe("(init) REQUIRED for action='init'. Absolute path to the target directory where the project should be scaffolded (e.g. /Users/alice/projects/my-app). Do not omit \u2014 there is no default. If unsure, ask the user for their working directory."),landingDesign:ae.string().optional().describe("(init) Landing design ID to apply to the landing page. Can be set here if not set during mist_plan. Use mist_project action='landing-designs' to browse."),appStyle:ae.string().optional().describe("(init) App style ID to apply across all pages (e.g. 'stripe', 'linear'). Can be set here if not set during mist_plan. Use mist_project action='app-styles' to browse."),confirmDarkTheme:ae.boolean().optional().describe("(init) Set to true only after confirming with the user that a dark-themed app style is intentional for a consumer (b2c) app. Skips the dark-on-consumer-app warning."),heroPhoto:ae.boolean().optional().describe("(init) Whether the landing page hero uses a lifestyle photo background. true = Unsplash photo + overlaid glass card (HabitFlow, Airbnb). false = pure CSS gradients + glassmorphism (Stripe, Linear). Pass the user's answer to the heroPhotoQuestion from mist_plan. If omitted, defaults based on the plan's audienceType (b2c \u2192 photo, else \u2192 CSS)."),projectPath:ae.string().optional().describe("Path to the project directory (default: cwd)"),step:ae.number().optional().describe("(implement) Specific step number to implement"),buildOutput:ae.string().optional().describe("(debug) Build output to parse instead of running a build"),feedback:ae.string().optional().describe("(mockup) User feedback on the current mockup \u2014 describe what to change."),approved:ae.boolean().optional().describe("(mockup) Set to true when the user approves the mockup. Locks in the design direction."),url:ae.string().optional().describe("(qa) URL to test. Defaults to deploy URL from mistflow.json"),deploymentId:ae.string().optional().describe("(qa) Deployment ID to associate QA results with. Passed from mist_deploy output.")}),Do={name:"mist_build",description:"STEP 2-3 of the Mistflow workflow. Build and develop a Mistflow project. Actions: 'mockup' generates visual HTML mockups from a plan for user preview \u2014 call with planId after plan approval if user wants to preview. Pass feedback to iterate, approved=true to lock in the design. 'init' creates and sets up a new project from a plan. Pass the planId returned by mist_plan \u2014 do NOT pass the full plan object. 'implement' executes the next plan step \u2014 call repeatedly until all steps are done. 'build' runs the full production build locally (Mistflow Cloud edge runtime) to verify before deploying. 'debug' analyzes build or runtime errors \u2014 for runtime errors from production, call mist_project errors first to fetch them, then pass the output as buildOutput. 'qa' tests the LIVE deployed app \u2014 call AFTER mist_deploy to verify everything works. If qa finds issues, fix them and redeploy, then call qa again. The full workflow is: mist_plan \u2192 (optional) mist_build mockup \u2192 mist_build init \u2192 mist_build implement (repeat) \u2192 mist_deploy \u2192 mist_build qa (loop until pass).",inputSchema:ls,handler:async r=>{let e=r;switch(e.action){case"init":{if(!e.name)return p("Project name is required for init.",!0);let t=e.plan,a=null;if(e.planId){if(a=ss(e.planId),!a)return p(`Plan not found for planId '${e.planId}'. The plan may have expired. Call mist_plan again to generate a new plan.`,!0);t=a.plan}if(!t)return p("No plan provided. Pass the planId returned by mist_plan, or call mist_plan first to generate a plan.",!0);if(!Array.isArray(t?.steps)||t.steps.length===0)return p("The plan is missing a 'steps' array. This usually means the plan generation was incomplete. Call mist_plan again with the same description to get a complete plan with implementation steps.",!0);if(a?.sourceDeploymentId&&a?.forkToken&&a?.projectId)return qr({name:e.name,plan:t,path:e.path,projectId:a.projectId,sourceDeploymentId:a.sourceDeploymentId,forkToken:a.forkToken,requiredEnvVars:a.requiredEnvVars??[],dbProvider:a.dbProvider??"neon",planId:e.planId});if(e.landingDesign){let d=et(e.landingDesign);d?t.landingDesign=d.id:console.error(`Landing design '${e.landingDesign}' not found \u2014 ignoring.`)}if(e.appStyle){let d=fe(e.appStyle);d?t.appStyle=d.id:console.error(`App style '${e.appStyle}' not found \u2014 ignoring.`)}let i=t.design,o=t.audienceType==="b2c",s=e.heroPhoto??o;t.design={...i??{},heroPhoto:s};let l=t.appStyle;if(l&&!e.confirmDarkTheme){let d=fe(l),u=d?Ie(d.id):void 0,h=t.audienceType;if(d&&u?.theme==="dark"&&h==="b2c"){let c=ut(u.category).filter(b=>b.theme==="light"&&b.id!==d.id).slice(0,3),g=c.map(b=>`'${b.id}'`).join(", ");return p(JSON.stringify({status:"confirm_dark_theme",picked:{id:d.id,name:d.name,theme:"dark"},audience:"consumer app (b2c)",lightAlternatives:c.map(b=>({id:b.id,name:b.name,description:b.description})),askUserQuestion:{question:`You picked ${d.name}'s dark aesthetic. Dark themes tend to read as "developer tool" \u2014 for a consumer app, a light palette usually feels friendlier. Keep ${d.name} dark, or switch to a light style?`,header:"Theme",options:[{label:`Keep ${d.name} (dark)`,description:"I want the dark aesthetic. Proceed with the build."},...c.map(b=>({label:`Switch to ${b.name} (light)`,description:(b.description??"").slice(0,140)}))],multiSelect:!1},instruction:[`The user picked a dark-themed design system (${d.name}) for a consumer app.`,"MANDATORY: Use the AskUserQuestion tool with the askUserQuestion object above before calling mist_build init again.",`If the user keeps dark: call mist_build init with the SAME appStyle='${d.id}' AND confirmDarkTheme=true.`,g?`If the user picks a light alternative: call mist_build init with appStyle set to one of: ${g}.`:""].filter(Boolean).join(`
12423
- `)}))}}return Vr({name:e.name,plan:t,path:e.path,planId:e.planId})}case"implement":return So({projectPath:e.projectPath,step:e.step});case"debug":return Co({projectPath:e.projectPath,buildOutput:e.buildOutput});case"build":{let t=is(e.projectPath??process.cwd());if(!_t(jt(t,"mistflow.json")))return p("Not a Mistflow project \u2014 mistflow.json not found. Run mist_build init first.",!0);if(!_t(jt(t,"node_modules")))try{Pa("npm",["install"],{cwd:t,stdio:["pipe","pipe","pipe"],timeout:12e4})}catch{return p("npm install failed. Check package.json for issues.",!0)}let i=process.platform==="win32"?"npx.cmd":"npx",n=0,o=2;for(;;){n++;try{Pa(i,["@opennextjs/cloudflare","build"],{cwd:t,stdio:["pipe","pipe","pipe"],timeout:18e4});let s=_t(jt(t,".open-next"));return p(JSON.stringify({success:!0,buildDir:".open-next",message:s?"Production build succeeded. Ready to deploy with mist_deploy.":"Build completed but .open-next/ directory not found. Check your OpenNext config."}))}catch(s){let l=s instanceof Error&&"stderr"in s?String(s.stderr):"",d=s instanceof Error&&"stdout"in s?String(s.stdout):"",u=l+`
12424
- `+d;if(n<o){let c=[],g=/Module not found:\s*(?:Error:\s*)?Can't resolve ['"]([^'"]+)['"]/g,b;for(;(b=g.exec(u))!==null;){let y=b[1];if(!y.startsWith(".")&&!y.startsWith("@/")&&!y.startsWith("~/")){let f=y.startsWith("@")?y.split("/").slice(0,2).join("/"):y.split("/")[0];c.includes(f)||c.push(f)}}if(c.length>0){console.error(`[build] Auto-installing missing packages: ${c.join(", ")}`);try{Pa("npm",["install",...c],{cwd:t,stdio:["pipe","pipe","pipe"],timeout:6e4});continue}catch{console.error("[build] Auto-install failed, reporting build errors")}}}let h=ft(u);return p(JSON.stringify({success:!1,errors:h,rawOutput:u.slice(0,3e3),message:h.length>0?`Build failed with ${h.length} error${h.length===1?"":"s"}:
13436
+ Fix these issues in the source code, then call mist_deploy with action='deploy'${r.includes("-pv-")?" environment='preview'":""} to redeploy. After redeploying, call mist_build action='qa' again to verify the fixes.`})})}for(let s of e)s.screenshot&&i.push({type:"image",data:s.screenshot,mimeType:"image/png"});return{content:i}}function vs(r){let e=jt(ws(),".mistflow","plans",`${r}.json`);if(!_t(e))return null;try{let a=JSON.parse(ys(e,"utf-8"));return a.plan?a:null}catch{return null}}var ks=ne.object({action:ne.enum(["init","install","implement","debug","build","qa","mockup"]).describe("'mockup' generates visual mockup HTML files from a plan for user preview and approval. 'init' creates and sets up a new project from your plan \u2014 writes files, registers the project, initializes git. Returns fast; does NOT run npm install. 'install' runs npm install in the project created by init. Pass projectPath. Always call this after init returns with status='awaiting_install'. 'implement' executes the next (or specific) plan step. 'debug' analyzes build or runtime errors \u2014 for runtime errors, call mist_project errors first then pass the output as buildOutput. 'build' runs the full production build locally (Mistflow Cloud edge runtime) without deploying. 'qa' tests the live deployed app \u2014 checks landing page, signup, login, dashboard, and nav links. Call after mist_deploy. If issues found, fix and redeploy, then call qa again."),name:ne.string().optional().describe("(init) Project name"),planId:ne.string().optional().describe("(init/mockup) Plan ID from mist_plan"),plan:ne.any().optional().describe("(init) Full plan object \u2014 use planId instead when available"),path:ne.string().optional().describe("(init) REQUIRED for action='init'. Absolute path to the target directory where the project should be scaffolded (e.g. /Users/alice/projects/my-app). Do not omit \u2014 there is no default. If unsure, ask the user for their working directory."),landingDesign:ne.string().optional().describe("(init) Landing design ID to apply to the landing page. Can be set here if not set during mist_plan. Use mist_project action='landing-designs' to browse."),appStyle:ne.string().optional().describe("(init) App style ID to apply across all pages (e.g. 'stripe', 'linear'). Can be set here if not set during mist_plan. Use mist_project action='app-styles' to browse."),confirmDarkTheme:ne.boolean().optional().describe("(init) Set to true only after confirming with the user that a dark-themed app style is intentional for a consumer (b2c) app. Skips the dark-on-consumer-app warning."),heroPhoto:ne.boolean().optional().describe("(init) Whether the landing page hero uses a lifestyle photo background. true = Unsplash photo + overlaid glass card (HabitFlow, Airbnb). false = pure CSS gradients + glassmorphism (Stripe, Linear). Pass the user's answer to the heroPhotoQuestion from mist_plan. If omitted, defaults based on the plan's audienceType (b2c \u2192 photo, else \u2192 CSS)."),projectPath:ne.string().optional().describe("Path to the project directory (default: cwd)"),step:ne.number().optional().describe("(implement) Specific step number to implement"),buildOutput:ne.string().optional().describe("(debug) Build output to parse instead of running a build"),feedback:ne.string().optional().describe("(mockup) User feedback on the current mockup \u2014 describe what to change."),approved:ne.boolean().optional().describe("(mockup) Set to true when the user approves the mockup. Locks in the design direction."),url:ne.string().optional().describe("(qa) URL to test. Defaults to deploy URL from mistflow.json"),deploymentId:ne.string().optional().describe("(qa) Deployment ID to associate QA results with. Passed from mist_deploy output.")}),Eo={name:"mist_build",description:"STEP 2-3 of the Mistflow workflow. Build and develop a Mistflow project. Actions: 'mockup' generates visual HTML mockups from a plan for user preview \u2014 call with planId after plan approval if user wants to preview. Pass feedback to iterate, approved=true to lock in the design. 'init' creates and sets up a new project from a plan \u2014 writes files, registers, initializes git. Returns fast (~10s) without running npm install. Pass the planId returned by mist_plan \u2014 do NOT pass the full plan object. 'install' runs npm install in the project init just created. Always call this after init (takes ~1-2 min). 'implement' executes the next plan step \u2014 call repeatedly until all steps are done. 'build' runs the full production build locally (Mistflow Cloud edge runtime) to verify before deploying. 'debug' analyzes build or runtime errors \u2014 for runtime errors from production, call mist_project errors first to fetch them, then pass the output as buildOutput. 'qa' tests the LIVE deployed app \u2014 call AFTER mist_deploy to verify everything works. If qa finds issues, fix them and redeploy, then call qa again. The full workflow is: mist_plan \u2192 (optional) mist_build mockup \u2192 mist_build init \u2192 mist_build install \u2192 mist_build implement (repeat) \u2192 mist_deploy \u2192 mist_build qa (loop until pass).",inputSchema:ks,handler:async(r,e)=>{let a=r;switch(a.action){case"init":{if(!a.name)return p("Project name is required for init.",!0);let t=a.plan,i=null;if(a.planId){if(i=vs(a.planId),!i)return p(`Plan not found for planId '${a.planId}'. The plan may have expired. Call mist_plan again to generate a new plan.`,!0);t=i.plan}if(!t)return p("No plan provided. Pass the planId returned by mist_plan, or call mist_plan first to generate a plan.",!0);if(!Array.isArray(t?.steps)||t.steps.length===0)return p("The plan is missing a 'steps' array. This usually means the plan generation was incomplete. Call mist_plan again with the same description to get a complete plan with implementation steps.",!0);if(i?.sourceDeploymentId&&i?.forkToken&&i?.projectId)return Yr({name:a.name,plan:t,path:a.path,projectId:i.projectId,sourceDeploymentId:i.sourceDeploymentId,forkToken:i.forkToken,requiredEnvVars:i.requiredEnvVars??[],dbProvider:i.dbProvider??"neon",planId:a.planId});if(a.landingDesign){let u=Je(a.landingDesign);u?t.landingDesign=u.id:console.error(`Landing design '${a.landingDesign}' not found \u2014 ignoring.`)}if(a.appStyle){let u=be(a.appStyle);u?t.appStyle=u.id:console.error(`App style '${a.appStyle}' not found \u2014 ignoring.`)}let s=t.design,n=t.audienceType==="b2c",l=a.heroPhoto??n;t.design={...s??{},heroPhoto:l};let d=t.appStyle;if(d&&!a.confirmDarkTheme){let u=be(d),h=u?De(u.id):void 0,c=t.audienceType;if(u&&h?.theme==="dark"&&c==="b2c"){let g=dt(h.category).filter(f=>f.theme==="light"&&f.id!==u.id).slice(0,3),b=g.map(f=>`'${f.id}'`).join(", ");return p(JSON.stringify({status:"confirm_dark_theme",picked:{id:u.id,name:u.name,theme:"dark"},audience:"consumer app (b2c)",lightAlternatives:g.map(f=>({id:f.id,name:f.name,description:f.description})),askUserQuestion:{question:`You picked ${u.name}'s dark aesthetic. Dark themes tend to read as "developer tool" \u2014 for a consumer app, a light palette usually feels friendlier. Keep ${u.name} dark, or switch to a light style?`,header:"Theme",options:[{label:`Keep ${u.name} (dark)`,description:"I want the dark aesthetic. Proceed with the build."},...g.map(f=>({label:`Switch to ${f.name} (light)`,description:(f.description??"").slice(0,140)}))],multiSelect:!1},instruction:[`The user picked a dark-themed design system (${u.name}) for a consumer app.`,"MANDATORY: Use the AskUserQuestion tool with the askUserQuestion object above before calling mist_build init again.",`If the user keeps dark: call mist_build init with the SAME appStyle='${u.id}' AND confirmDarkTheme=true.`,b?`If the user picks a light alternative: call mist_build init with appStyle set to one of: ${b}.`:""].filter(Boolean).join(`
13437
+ `)}))}}return qr({name:a.name,plan:t,path:a.path,planId:a.planId},e)}case"install":return Kr({projectPath:a.projectPath},e);case"implement":return Mo({projectPath:a.projectPath,step:a.step});case"debug":return Ro({projectPath:a.projectPath,buildOutput:a.buildOutput});case"build":{let t=xs(a.projectPath??process.cwd());if(!_t(jt(t,"mistflow.json")))return p("Not a Mistflow project \u2014 mistflow.json not found. Run mist_build init first.",!0);if(!_t(jt(t,"node_modules")))try{Aa("npm",["install"],{cwd:t,stdio:["pipe","pipe","pipe"],timeout:12e4})}catch{return p("npm install failed. Check package.json for issues.",!0)}let s=process.platform==="win32"?"npx.cmd":"npx",o=0,n=2;for(;;){o++;try{Aa(s,["@opennextjs/cloudflare","build"],{cwd:t,stdio:["pipe","pipe","pipe"],timeout:18e4});let l=_t(jt(t,".open-next"));return p(JSON.stringify({success:!0,buildDir:".open-next",message:l?"Production build succeeded.":"Build completed but .open-next/ directory not found. Check your OpenNext config.",nextAction:l?"NEXT: Call mist_deploy with action='deploy' immediately. Do NOT ask the user whether to deploy \u2014 the build was the last approval gate. Do NOT suggest `npm run dev` or localhost as an alternative \u2014 the user wants a live URL.":void 0}))}catch(l){let d=l instanceof Error&&"stderr"in l?String(l.stderr):"",u=l instanceof Error&&"stdout"in l?String(l.stdout):"",h=d+`
13438
+ `+u;if(o<n){let g=[],b=/Module not found:\s*(?:Error:\s*)?Can't resolve ['"]([^'"]+)['"]/g,f;for(;(f=b.exec(h))!==null;){let m=f[1];if(!m.startsWith(".")&&!m.startsWith("@/")&&!m.startsWith("~/")){let y=m.startsWith("@")?m.split("/").slice(0,2).join("/"):m.split("/")[0];g.includes(y)||g.push(y)}}if(g.length>0){console.error(`[build] Auto-installing missing packages: ${g.join(", ")}`);try{Aa("npm",["install",...g],{cwd:t,stdio:["pipe","pipe","pipe"],timeout:6e4});continue}catch{console.error("[build] Auto-install failed, reporting build errors")}}}let c=ft(h);return p(JSON.stringify({success:!1,errors:c,rawOutput:h.slice(0,3e3),message:c.length>0?`Build failed with ${c.length} error${c.length===1?"":"s"}:
12425
13439
 
12426
- `+h.map((c,g)=>{let b=c.file?`${c.file}${c.line?`:${c.line}`:""}`:"unknown";return`${g+1}. [${b}] ${c.humanMessage}
12427
- Fix: ${c.suggestion}`}).join(`
13440
+ `+c.map((g,b)=>{let f=g.file?`${g.file}${g.line?`:${g.line}`:""}`:"unknown";return`${b+1}. [${f}] ${g.humanMessage}
13441
+ Fix: ${g.suggestion}`}).join(`
12428
13442
 
12429
13443
  `)+`
12430
13444
 
12431
- Fix these and run mist_build build again.`:"Build failed. Run mist_build with action='debug' for detailed analysis."}),!0)}}}case"qa":return Bo({projectPath:e.projectPath,url:e.url,deploymentId:e.deploymentId});case"mockup":return e.planId?mo({planId:e.planId,projectPath:e.projectPath,feedback:e.feedback,approved:e.approved}):p("planId is required for mockup. Pass the planId from mist_plan.",!0);default:return p(`Unknown action: ${e.action}. Use mockup, init, implement, build, debug, or qa.`,!0)}}};import{z as Je}from"zod";import{z as Vt}from"zod";import{resolve as Ho,join as J,dirname as qt,basename as Bs}from"path";import{existsSync as ne,readFileSync as La,writeFileSync as Oo,unlinkSync as Ro,mkdirSync as Ds,cpSync as Ia,rmSync as Ma,readdirSync as As}from"fs";import{execFileSync as Is}from"child_process";import{spawn as Ms}from"child_process";import{tmpdir as Rs}from"os";function Ao(r,e){if(r instanceof he)switch(r.code){case"auth_missing":return p("No Mistflow credentials found. Run mist_setup to connect your account.",!0);case"auth_revoked":return p("Your Mistflow credentials were revoked. Run mist_setup to reconnect.",!0);case"auth_expired":case"auth_invalid":case"auth_org_not_found":return p("Your Mistflow session needs to be refreshed. Run mist_setup to reconnect.",!0);case"permission_denied":return p(r.message||"You don't have permission for that action.",!0);case"rate_limited":return p("You're hitting rate limits. Wait 30 seconds and try again.",!0);case"quota_exceeded":return p(`${r.message} Upgrade at https://app.mistflow.ai/pricing to lift limits.`,!0);case"validation_error":case"conflict":return p(r.message,!0);case"not_found":{let a=Pt(),i=a?.orgSlug?.replace(/^user_user_/,"")||void 0,n=a?.email||i;return n&&(r.message.toLowerCase().includes("project")||e.toLowerCase().includes("project"))?p(`Project not found. You are signed in as ${n}. This project may belong to a different account. Run mist_setup to sign in with the account that owns this project.`,!0):p(r.message,!0)}case"server_error":case"upstream_error":return p(`${e} failed: the Mistflow backend returned an error. Try again in 30 seconds.`,!0);case"network_error":return p(`${e} failed: cannot reach Mistflow servers. Check your network and try again.`,!0);default:return p(`${e} failed: ${r.message}`,!0)}let t=r instanceof Error?r.message:String(r);return p(`${e} failed: ${t}`,!0)}import{existsSync as We,readFileSync as bt,readdirSync as Ba,statSync as Da}from"fs";import{join as ye,relative as zt}from"path";import{execFileSync as ip}from"child_process";function ds(r){let e=ye(r,".env.local"),t=[];return We(e)?(bt(e,"utf-8").match(/^AUTH_SECRET=(.*)$/m)?.[1]?.trim()||t.push({check:"env",message:"AUTH_SECRET is missing from .env.local. Add a random secret for authentication.",file:".env.local"}),t):(t.push({check:"env",message:"Missing .env.local file. Create one with your local development environment variables (AUTH_SECRET, etc.).",file:".env.local"}),t)}function cs(r){let e=ye(r,"app","api","auth","[...all]","route.ts");return We(e)?[]:[{check:"auth-routes",message:"Missing Better Auth catch-all route at app/api/auth/[...all]/route.ts. Create this file to handle authentication.",file:"app/api/auth/[...all]/route.ts"}]}function ps(r){let e=ye(r,"app","api","health","route.ts");return We(e)?[]:[{check:"health-endpoint",message:"Missing health check endpoint at app/api/health/route.ts. Create this file so Mistflow can verify your deployment.",file:"app/api/health/route.ts"}]}function us(r){let e=ye(r,"mistflow.json");if(!We(e))return[];let t;try{t=JSON.parse(bt(e,"utf-8"))}catch{return[]}let a=t.env;if(!a?.required||typeof a.required!="object")return[];let i=[],n=ye(r,".env"),o="";try{We(n)&&(o=bt(n,"utf-8"))}catch{}for(let[s,l]of Object.entries(a.required)){let d=!!process.env[s],u=o.includes(`${s}=`);if(d||u)continue;let h=l?.description?` for '${l.description}'`:"";i.push({check:"required-env",message:`${s} is required${h}. Run mist_config to set it if you haven't already.`})}return i}function hs(r){let e=ye(r,"app");if(!We(e))return[];let t=[];function a(o){let s;try{s=Ba(o)}catch{return}for(let l of s){if(l==="node_modules"||l===".next"||l===".open-next"||l==="api")continue;let d=ye(o,l),u;try{u=Da(d)}catch{continue}if(u.isDirectory())a(d);else if(l==="page.tsx"||l==="page.ts"||l==="page.jsx"||l==="page.js"){let c="/"+zt(e,d).replace(/\\/g,"/").replace(/\([^)]+\)\//g,"").replace(/\/page\.(tsx?|jsx?)$/,"").replace(/^page\.(tsx?|jsx?)$/,"");t.push({file:zt(r,d),resolvedRoute:c||"/"})}}}a(e);let i=new Map;for(let o of t){let s=i.get(o.resolvedRoute)??[];s.push(o.file),i.set(o.resolvedRoute,s)}let n=[];for(let[o,s]of i)s.length>1&&n.push({check:"route-collision",message:`Route collision: ${s.join(" and ")} both resolve to "${o}". Next.js will fail with "two parallel pages that resolve to the same path". Move one into a real path segment (e.g. rename (admin)/page.tsx to admin/page.tsx).`,file:s[1]});return n}function gs(r){let e=ye(r,"app");if(!We(e))return[];let t=[];function a(i){let n;try{n=Ba(i)}catch{return}for(let o of n){if(o==="node_modules"||o===".next"||o===".open-next")continue;let s=ye(i,o),l;try{l=Da(s)}catch{continue}if(l.isDirectory())a(s);else if(o==="actions.ts"||o==="actions.tsx"){let d=bt(s,"utf-8");d.includes("use server")&&d.includes("cookies()")&&d.includes(".set(")&&t.push(zt(r,s))}}}return a(e),t.length>0?[{check:"cookies-in-actions",message:`${t.length} server action(s) use cookies().set() which crashes on Mistflow Cloud's edge runtime: ${t.join(", ")}. Use a database field or form parameter instead.`,file:t[0]}]:[]}function ms(r){let e=/\b(useState|useEffect|useRef|useCallback|useMemo|useReducer|useContext|useLayoutEffect|useTransition)\s*\(/,t=[];function a(i){if(!We(i))return;let n;try{n=Ba(i)}catch{return}for(let o of n){if(o==="node_modules"||o===".next"||o===".open-next"||o==="ui")continue;let s=ye(i,o),l;try{l=Da(s)}catch{continue}if(l.isDirectory())a(s);else if(o.endsWith(".tsx")||o.endsWith(".jsx")){if(o==="layout.tsx"||o==="loading.tsx"||o==="error.tsx"||o==="not-found.tsx")continue;let d=bt(s,"utf-8");if(d.trimStart().startsWith('"use client"')||d.trimStart().startsWith("'use client'")||d.trimStart().startsWith('"use server"')||d.trimStart().startsWith("'use server'"))continue;if(e.test(d)){let u=zt(r,s);t.push({check:"missing-use-client",message:`${u} uses React hooks but is missing "use client" directive. Add "use client"; as the first line of the file.`,file:u})}}}}return a(ye(r,"app")),a(ye(r,"components")),t}function Io(r){let e=[...ds(r),...cs(r),...ps(r),...hs(r),...gs(r),...ms(r)],t=[...us(r)];return{passed:e.length===0,errors:e,warnings:t}}import{existsSync as ge,readFileSync as Ye,readdirSync as $t,statSync as yt}from"fs";import{join as V}from"path";async function Mo(r,e){let t=[];t.push(fs(r)),t.push(bs(r)),t.push(ys(r)),t.push(xs(r)),t.push(ws(r)),t.push(Cs(r)),e?.plan&&t.push(...vs(r,e)),t.push(ks(r)),t.push(Ss(r)),t.push(...Ps(r)),t.push(Ts(r));let a=t.filter(d=>d.status==="fail"),i=t.filter(d=>d.status==="warn"),n=t.filter(d=>d.status==="pass"),o=a.length>0,s=!o,l;return s&&i.length===0?l=`All ${t.length} checks passed`:s?l=`${n.length}/${t.length} passed, ${i.length} warning(s)`:l=`${a.length} issue(s) found:
12432
- ${a.map(d=>` - ${d.message}`).join(`
12433
- `)}`,{passed:s,blocking:o,checksRun:t.length,checks:t,summary:l}}function fs(r){let e=V(r,".open-next","worker.js");if(!ge(e))return{name:"worker-exists",status:"fail",message:"worker.js not found in build output. The build may have failed silently."};let t=Ye(e,"utf-8");return t.length<1e3?{name:"worker-exists",status:"warn",message:`worker.js is unusually small (${t.length} bytes). The build output may be incomplete.`}:{name:"worker-exists",status:"pass",message:"Worker bundle exists"}}function bs(r){let e=V(r,".open-next","assets");if(!ge(e))return{name:"assets-exist",status:"warn",message:"No static assets directory in build output. CSS and images may not load."};let t=0;function a(i){try{for(let n of $t(i)){let o=V(i,n);try{yt(o).isDirectory()?a(o):t++}catch{}}}catch{}}return a(e),t===0?{name:"assets-exist",status:"warn",message:"Assets directory is empty. Your app may not have styles or images."}:{name:"assets-exist",status:"pass",message:`${t} static assets ready`}}function ys(r){let e=V(r,"app","api","health","route.ts");return ge(e)?{name:"health-route",status:"pass",message:"Health endpoint found"}:{name:"health-route",status:"warn",message:"No health endpoint found. Deployment verification may fail."}}function xs(r){let e=V(r,"app","api","auth","[...all]","route.ts");return ge(e)?{name:"auth-route",status:"pass",message:"Auth routes found"}:{name:"auth-route",status:"warn",message:"Auth catch-all route not found. Login/register may not work."}}function ws(r){let e=V(r,".open-next","worker.js");if(!ge(e))return{name:"worker-size",status:"pass",message:"Worker size check skipped"};let a=yt(e).size/(1024*1024);return a>60?{name:"worker-size",status:"fail",message:`Worker bundle is ${a.toFixed(1)}MB \u2014 exceeds Mistflow Cloud's 64MB worker bundle limit.`}:a>30?{name:"worker-size",status:"warn",message:`Worker bundle is ${a.toFixed(1)}MB \u2014 large but within Mistflow Cloud limits. May be slow to upload.`}:{name:"worker-size",status:"pass",message:`Worker bundle: ${a.toFixed(1)}MB`}}function vs(r,e){let t=[],a=new Set;if(e.plan?.pages)for(let o of e.plan.pages){let s=o.path??o.route??o.name;s&&a.add(s.replace(/^\//,""))}if(a.size===0)return[];let i=0,n=[];for(let o of a){if(o.startsWith("api/")||o==="login"||o==="register"||o==="sign-in"||o==="sign-up"){i++;continue}[V(r,"app","(dashboard)",o,"page.tsx"),V(r,"app","(dashboard)",o,"page.ts"),V(r,"app",o,"page.tsx"),V(r,"app",o,"page.ts"),V(r,"app","(admin)",o,"page.tsx")].some(l=>ge(l))?i++:n.push(`/${o}`)}return n.length===0?t.push({name:"plan-routes",status:"pass",message:`All ${a.size} planned pages found`}):n.length<=2?t.push({name:"plan-routes",status:"warn",message:`${i}/${a.size} planned pages found. Missing: ${n.join(", ")}`}):t.push({name:"plan-routes",status:"warn",message:`Only ${i}/${a.size} planned pages found. ${n.length} pages missing.`}),t}function ks(r){let e=V(r,"app");if(!ge(e))return{name:"empty-pages",status:"pass",message:"App directory check skipped"};let t=[];function a(i){try{for(let n of $t(i)){let o=V(i,n);try{if(yt(o).isDirectory())a(o);else if(n==="page.tsx"||n==="page.ts"){let l=Ye(o,"utf-8").trim();if(l.length<50||l.includes("export default function")&&l.includes("TODO")){let d=o.replace(r+"/","");t.push(d)}}}catch{}}}catch{}}return a(e),t.length>0?{name:"empty-pages",status:"warn",message:`${t.length} page(s) appear to be empty/placeholder: ${t.slice(0,3).join(", ")}`}:{name:"empty-pages",status:"pass",message:"No empty pages detected"}}function Ss(r){let e=V(r,"app","page.tsx");if(!ge(e))return{name:"landing-page",status:"warn",message:"No root page.tsx found"};let t=Ye(e,"utf-8");if(t.includes('redirect("/login")')||t.includes('redirect("/register")')){let a=V(r,"middleware.ts"),i=ge(a)?Ye(a,"utf-8"):"";if(!(i.includes('"/"')||i.includes("'/'")))return{name:"landing-page",status:"fail",message:"Landing page (app/page.tsx) is just a redirect to /login, and middleware does not allow '/' as public. Users will never see a landing page."}}return t.length<200?{name:"landing-page",status:"warn",message:`Landing page is very small (${t.length} chars) \u2014 may be a placeholder.`}:{name:"landing-page",status:"pass",message:"Landing page has content"}}function Cs(r){let e=V(r,"app");if(!ge(e))return{name:"cookies-in-actions",status:"pass",message:"No app directory"};let t=[];function a(i){try{for(let n of $t(i)){if(n==="node_modules"||n===".next"||n===".open-next")continue;let o=V(i,n);try{if(yt(o).isDirectory())a(o);else if(n==="actions.ts"||n==="actions.tsx"){let l=Ye(o,"utf-8");l.includes("use server")&&l.includes("cookies()")&&l.includes(".set(")&&t.push(o.replace(r+"/",""))}}catch{}}}catch{}}return a(e),t.length>0?{name:"cookies-in-actions",status:"fail",message:`${t.length} server action(s) use cookies().set() which crashes on Mistflow Cloud's edge runtime: ${t.join(", ")}. Use a database field or form parameter instead.`}:{name:"cookies-in-actions",status:"pass",message:"No cookies().set() in server actions"}}function Ts(r){let e=V(r,"app");if(!ge(e))return{name:"fake-forms",status:"pass",message:"No app directory"};let t=[];function a(i){try{for(let n of $t(i)){if(n==="node_modules"||n===".next"||n===".open-next")continue;let o=V(i,n);try{if(yt(o).isDirectory())a(o);else if(n.endsWith(".tsx")||n.endsWith(".ts")){let l=Ye(o,"utf-8");if((l.includes("setTimeout")||l.includes("new Promise"))&&l.includes("Simulate")||l.includes("simulate")||l.includes("// TODO")&&l.includes("API")){let d=o.replace(r+"/","");t.push(d)}}}catch{}}}catch{}}return a(e),t.length>0?{name:"fake-forms",status:"fail",message:`${t.length} file(s) have fake/simulated API calls instead of real server actions: ${t.slice(0,3).join(", ")}. Forms must use 'use server' actions that write to the database.`}:{name:"fake-forms",status:"pass",message:"No fake form handlers detected"}}function Ps(r){let t=[V(r,"components","sidebar.tsx"),V(r,"components","topnav.tsx"),V(r,"components","nav.tsx")].find(s=>ge(s));if(!t)return[];let n=(Ye(t,"utf-8").match(/href:\s*"([^"]+)"/g)??[]).map(s=>s.replace(/href:\s*"/,"").replace(/"$/,"")).filter(s=>!s.startsWith("/api")&&!s.includes("["));if(n.length===0)return[];let o=[];for(let s of n){let l=s.replace(/^\//,"");if(!l||l==="")continue;[V(r,"app","(dashboard)",l,"page.tsx"),V(r,"app","(dashboard)",l,"page.ts"),V(r,"app","(app)",l,"page.tsx"),V(r,"app",l,"page.tsx"),V(r,"app",l,"page.ts")].some(u=>ge(u))||o.push(s)}return o.length>0?[{name:"nav-links",status:"fail",message:`Sidebar/nav links to pages that don't exist: ${o.join(", ")}. These will 404.`}]:[{name:"nav-links",status:"pass",message:`All ${n.length} nav links have matching pages`}]}function Re(r,e){return Is("git",r,{cwd:e,encoding:"utf-8",timeout:3e4,stdio:["pipe","pipe","pipe"]}).trim()}function Aa(r){try{return Re(["rev-parse","--is-inside-work-tree"],r),!0}catch{return!1}}function Lo(r){try{return Re(["status","--porcelain"],r).length>0}catch{return!1}}function No(r,e){return Re(["add","-A"],r),Re(["commit","-m",e,"--allow-empty-message"],r),Re(["rev-parse","HEAD"],r)}function Ls(r){return Re(["rev-parse","HEAD"],r)}function Fo(r){try{return Re(["remote","get-url","origin"],r),!0}catch{return!1}}function Ns(r){try{let e=Re(["rev-parse","--abbrev-ref","HEAD"],r);return Re(["push","origin",e],r),{success:!0}}catch(e){return{success:!1,error:e instanceof Error?e.message:"push failed"}}}function Fs(r){let e=qt(Ho(r)),t=qt(e)===e?e:"/";for(;e!==t&&e!==qt(e);){if(ne(J(e,"pnpm-workspace.yaml"))||ne(J(e,"lerna.json")))return e;let a=J(e,"package.json");if(ne(a))try{if(JSON.parse(La(a,"utf-8")).workspaces)return e}catch{}e=qt(e)}return null}function Us(r){let e=J(Rs(),"mistflow-build");Ds(e,{recursive:!0});let t=J(e,`${Bs(r)}-${Date.now()}`);return Ia(r,t,{recursive:!0,filter:a=>{let i=a.slice(r.length);return!(i.startsWith("/.git")||i.startsWith("\\.git")||i==="/node_modules"||i.startsWith("/node_modules/")||i==="/.open-next"||i.startsWith("/.open-next/")||i==="/.next"||i.startsWith("/.next/"))}}),t}function Es(r,e){let t=J(r,".open-next"),a=J(r,".next");if(ne(t)){let i=J(e,".open-next");ne(i)&&Ma(i,{recursive:!0}),Ia(t,i,{recursive:!0})}if(ne(a)){let i=J(e,".next");ne(i)&&Ma(i,{recursive:!0}),Ia(a,i,{recursive:!0})}}function Hs(r){try{Ma(r,{recursive:!0,force:!0})}catch{console.error(`[deploy] Failed to clean up isolated build dir: ${r}`)}}function lt(r,e,t,a,i,n){return new Promise(o=>{let s=Ms(r,e,{cwd:t,stdio:["pipe","pipe","pipe"],timeout:a,...n?{env:n}:{}}),l="",d="";s.stdout?.on("data",u=>{let h=u.toString();if(d+=h,i)for(let c of h.split(`
12434
- `).filter(Boolean))i(c)}),s.stderr?.on("data",u=>{let h=u.toString();if(l+=h,i)for(let c of h.split(`
12435
- `).filter(Boolean))i(c)}),s.on("close",(u,h)=>{o({success:u===0,stdout:d,stderr:l,signal:h})}),s.on("error",u=>{o({success:!1,stdout:d,stderr:l+u.message})})})}var Sp=Vt.object({projectPath:Vt.string().optional().describe("Path to the project directory (default: current working directory)"),message:Vt.string().optional().describe("Deploy message"),environment:Vt.enum(["production","preview"]).optional().default("production").describe("Target environment: 'production' (default) or 'preview' for a shareable URL")});function Os(r){return new Promise(e=>setTimeout(e,r))}function Uo(r){switch(r){case"pending":return"Provisioning database...";case"building":return"Building your app...";case"deploying":return"Deploying to Mistflow Cloud...";case"verifying":return"Verifying deployment...";default:return`Status: ${r}`}}function Ra(r){let e=J(r,"mistflow.json");if(ne(e))try{return JSON.parse(La(e,"utf-8"))}catch{}return{}}function Gs(r,e){let t=J(r,"mistflow.json"),a=Ra(r),i=a.deploy?.count??a.deployCount??0;a.deploy={url:e,count:i+1,lastDeployedAt:new Date().toISOString()};let n=a.plan?.steps;if(Array.isArray(n))for(let o of n)o.status==="in_progress"&&(o.status="completed");delete a.deployUrl,delete a.deployCount,Oo(t,JSON.stringify(a,null,2)+`
12436
- `)}async function Go(r,e){let a=process.platform==="win32"?"npx.cmd":"npx",i=0,n=J(r,"node_modules",".bin","opennextjs-cloudflare"),o=ne(n),s={...process.env,NODE_ENV:"production"},l=await lt(o?n:a,o?["build"]:["@opennextjs/cloudflare","build"],r,3e5,c=>{c.includes("Compiling")?e?.("Compiling your app..."):c.includes("Collecting page data")?e?.("Collecting page data..."):c.includes("Generating static pages")?e?.("Generating static pages..."):c.match(/^[○●◐λƒ]\s/)?i++:c.includes("Creating Cloudflare worker")?e?.("Packaging for Mistflow Cloud..."):c.includes("Build completed")&&e?.("Build completed!")},s);if(l.success)return{success:!0,buildStats:i>0?`${i} routes compiled`:"build complete"};let d=l.stderr+`
12437
- `+l.stdout;if(l.signal==="SIGKILL"||d.includes("SIGKILL")||d.includes("exit code: 137"))return{success:!1,signal:"SIGKILL",error:"Your app ran out of memory while building. This sometimes happens with complex projects. Try deploying again \u2014 if it keeps failing, we'll help you figure it out."};let u=ft(d);if(u.length>0){let c=u.map((g,b)=>{let y=`${b+1}. ${g.humanMessage}`;return g.suggestion&&(y+=`
12438
- Fix: ${g.suggestion}`),y}).join(`
13445
+ Fix these and run mist_build build again.`:"Build failed. Run mist_build with action='debug' for detailed analysis."}),!0)}}}case"qa":return Fo({projectPath:a.projectPath,url:a.url,deploymentId:a.deploymentId});case"mockup":return a.planId?So({planId:a.planId,projectPath:a.projectPath,feedback:a.feedback,approved:a.approved}):p("planId is required for mockup. Pass the planId from mist_plan.",!0);default:return p(`Unknown action: ${a.action}. Use mockup, init, implement, build, debug, or qa.`,!0)}}};import{z as qe}from"zod";import{z as Vt}from"zod";import{resolve as Vo,join as te,dirname as qt,basename as Ws}from"path";import{existsSync as le,readFileSync as qo,writeFileSync as _s,unlinkSync as Go,mkdirSync as js,cpSync as Ra,rmSync as Na,readdirSync as zs}from"fs";import{execFileSync as $s}from"child_process";import{spawn as Vs}from"child_process";import{tmpdir as qs}from"os";function Uo(r,e){if(r instanceof me)switch(r.code){case"auth_missing":return p("No Mistflow credentials found. Run mist_setup to connect your account.",!0);case"auth_revoked":return p("Your Mistflow credentials were revoked. Run mist_setup to reconnect.",!0);case"auth_expired":case"auth_invalid":case"auth_org_not_found":return p("Your Mistflow session needs to be refreshed. Run mist_setup to reconnect.",!0);case"permission_denied":return p(r.message||"You don't have permission for that action.",!0);case"rate_limited":return p("You're hitting rate limits. Wait 30 seconds and try again.",!0);case"quota_exceeded":return p(`${r.message} Upgrade at https://app.mistflow.ai/pricing to lift limits.`,!0);case"validation_error":case"conflict":return p(r.message,!0);case"not_found":{let t=Pt(),i=t?.orgSlug?.replace(/^user_user_/,"")||void 0,s=t?.email||i;return s&&(r.message.toLowerCase().includes("project")||e.toLowerCase().includes("project"))?p(`Project not found. You are signed in as ${s}. This project may belong to a different account. Run mist_setup to sign in with the account that owns this project.`,!0):p(r.message,!0)}case"server_error":case"upstream_error":return p(`${e} failed: the Mistflow backend returned an error. Try again in 30 seconds.`,!0);case"network_error":return p(`${e} failed: cannot reach Mistflow servers. Check your network and try again.`,!0);default:return p(`${e} failed: ${r.message}`,!0)}let a=r instanceof Error?r.message:String(r);return p(`${e} failed: ${a}`,!0)}import{existsSync as He,readFileSync as bt,readdirSync as Da,statSync as Ia}from"fs";import{join as xe,relative as zt}from"path";import{execFileSync as Mp}from"child_process";function Ss(r){let e=xe(r,".env.local"),a=[];return He(e)?(bt(e,"utf-8").match(/^AUTH_SECRET=(.*)$/m)?.[1]?.trim()||a.push({check:"env",message:"AUTH_SECRET is missing from .env.local. Add a random secret for authentication.",file:".env.local"}),a):(a.push({check:"env",message:"Missing .env.local file. Create one with your local development environment variables (AUTH_SECRET, etc.).",file:".env.local"}),a)}function Ts(r){let e=xe(r,"app","api","auth","[...all]","route.ts");return He(e)?[]:[{check:"auth-routes",message:"Missing Better Auth catch-all route at app/api/auth/[...all]/route.ts. Create this file to handle authentication.",file:"app/api/auth/[...all]/route.ts"}]}function Cs(r){let e=xe(r,"app","api","health","route.ts");return He(e)?[]:[{check:"health-endpoint",message:"Missing health check endpoint at app/api/health/route.ts. Create this file so Mistflow can verify your deployment.",file:"app/api/health/route.ts"}]}function Ps(r){let e=xe(r,"mistflow.json");if(!He(e))return[];let a;try{a=JSON.parse(bt(e,"utf-8"))}catch{return[]}let t=a.env;if(!t?.required||typeof t.required!="object")return[];let i=[],s=xe(r,".env"),o="";try{He(s)&&(o=bt(s,"utf-8"))}catch{}for(let[n,l]of Object.entries(t.required)){let d=!!process.env[n],u=o.includes(`${n}=`);if(d||u)continue;let h=l?.description?` for '${l.description}'`:"";i.push({check:"required-env",message:`${n} is required${h}. Run mist_config to set it if you haven't already.`})}return i}function Bs(r){let e=xe(r,"app");if(!He(e))return[];let a=[];function t(o){let n;try{n=Da(o)}catch{return}for(let l of n){if(l==="node_modules"||l===".next"||l===".open-next"||l==="api")continue;let d=xe(o,l),u;try{u=Ia(d)}catch{continue}if(u.isDirectory())t(d);else if(l==="page.tsx"||l==="page.ts"||l==="page.jsx"||l==="page.js"){let c="/"+zt(e,d).replace(/\\/g,"/").replace(/\([^)]+\)\//g,"").replace(/\/page\.(tsx?|jsx?)$/,"").replace(/^page\.(tsx?|jsx?)$/,"");a.push({file:zt(r,d),resolvedRoute:c||"/"})}}}t(e);let i=new Map;for(let o of a){let n=i.get(o.resolvedRoute)??[];n.push(o.file),i.set(o.resolvedRoute,n)}let s=[];for(let[o,n]of i)n.length>1&&s.push({check:"route-collision",message:`Route collision: ${n.join(" and ")} both resolve to "${o}". Next.js will fail with "two parallel pages that resolve to the same path". Move one into a real path segment (e.g. rename (admin)/page.tsx to admin/page.tsx).`,file:n[1]});return s}function As(r){let e=xe(r,"app");if(!He(e))return[];let a=[];function t(i){let s;try{s=Da(i)}catch{return}for(let o of s){if(o==="node_modules"||o===".next"||o===".open-next")continue;let n=xe(i,o),l;try{l=Ia(n)}catch{continue}if(l.isDirectory())t(n);else if(o==="actions.ts"||o==="actions.tsx"){let d=bt(n,"utf-8");d.includes("use server")&&d.includes("cookies()")&&d.includes(".set(")&&a.push(zt(r,n))}}}return t(e),a.length>0?[{check:"cookies-in-actions",message:`${a.length} server action(s) use cookies().set() which crashes on Mistflow Cloud's edge runtime: ${a.join(", ")}. Use a database field or form parameter instead.`,file:a[0]}]:[]}function Ds(r){let e=/\b(useState|useEffect|useRef|useCallback|useMemo|useReducer|useContext|useLayoutEffect|useTransition)\s*\(/,a=[];function t(i){if(!He(i))return;let s;try{s=Da(i)}catch{return}for(let o of s){if(o==="node_modules"||o===".next"||o===".open-next"||o==="ui")continue;let n=xe(i,o),l;try{l=Ia(n)}catch{continue}if(l.isDirectory())t(n);else if(o.endsWith(".tsx")||o.endsWith(".jsx")){if(o==="layout.tsx"||o==="loading.tsx"||o==="error.tsx"||o==="not-found.tsx")continue;let d=bt(n,"utf-8");if(d.trimStart().startsWith('"use client"')||d.trimStart().startsWith("'use client'")||d.trimStart().startsWith('"use server"')||d.trimStart().startsWith("'use server'"))continue;if(e.test(d)){let u=zt(r,n);a.push({check:"missing-use-client",message:`${u} uses React hooks but is missing "use client" directive. Add "use client"; as the first line of the file.`,file:u})}}}}return t(xe(r,"app")),t(xe(r,"components")),a}function Ho(r){let e=[...Ss(r),...Ts(r),...Cs(r),...Bs(r),...As(r),...Ds(r)],a=[...Ps(r)];return{passed:e.length===0,errors:e,warnings:a}}import{existsSync as fe,readFileSync as Ve,readdirSync as $t,statSync as yt}from"fs";import{join as K}from"path";async function Oo(r,e){let a=[];a.push(Is(r)),a.push(Ms(r)),a.push(Rs(r)),a.push(Ns(r)),a.push(Ls(r)),a.push(Hs(r)),e?.plan&&a.push(...Fs(r,e)),a.push(Es(r)),a.push(Us(r)),a.push(...Gs(r)),a.push(Os(r));let t=a.filter(d=>d.status==="fail"),i=a.filter(d=>d.status==="warn"),s=a.filter(d=>d.status==="pass"),o=t.length>0,n=!o,l;return n&&i.length===0?l=`All ${a.length} checks passed`:n?l=`${s.length}/${a.length} passed, ${i.length} warning(s)`:l=`${t.length} issue(s) found:
13446
+ ${t.map(d=>` - ${d.message}`).join(`
13447
+ `)}`,{passed:n,blocking:o,checksRun:a.length,checks:a,summary:l}}function Is(r){let e=K(r,".open-next","worker.js");if(!fe(e))return{name:"worker-exists",status:"fail",message:"worker.js not found in build output. The build may have failed silently."};let a=Ve(e,"utf-8");return a.length<1e3?{name:"worker-exists",status:"warn",message:`worker.js is unusually small (${a.length} bytes). The build output may be incomplete.`}:{name:"worker-exists",status:"pass",message:"Worker bundle exists"}}function Ms(r){let e=K(r,".open-next","assets");if(!fe(e))return{name:"assets-exist",status:"warn",message:"No static assets directory in build output. CSS and images may not load."};let a=0;function t(i){try{for(let s of $t(i)){let o=K(i,s);try{yt(o).isDirectory()?t(o):a++}catch{}}}catch{}}return t(e),a===0?{name:"assets-exist",status:"warn",message:"Assets directory is empty. Your app may not have styles or images."}:{name:"assets-exist",status:"pass",message:`${a} static assets ready`}}function Rs(r){let e=K(r,"app","api","health","route.ts");return fe(e)?{name:"health-route",status:"pass",message:"Health endpoint found"}:{name:"health-route",status:"warn",message:"No health endpoint found. Deployment verification may fail."}}function Ns(r){let e=K(r,"app","api","auth","[...all]","route.ts");return fe(e)?{name:"auth-route",status:"pass",message:"Auth routes found"}:{name:"auth-route",status:"warn",message:"Auth catch-all route not found. Login/register may not work."}}function Ls(r){let e=K(r,".open-next","worker.js");if(!fe(e))return{name:"worker-size",status:"pass",message:"Worker size check skipped"};let t=yt(e).size/(1024*1024);return t>60?{name:"worker-size",status:"fail",message:`Worker bundle is ${t.toFixed(1)}MB \u2014 exceeds Mistflow Cloud's 64MB worker bundle limit.`}:t>30?{name:"worker-size",status:"warn",message:`Worker bundle is ${t.toFixed(1)}MB \u2014 large but within Mistflow Cloud limits. May be slow to upload.`}:{name:"worker-size",status:"pass",message:`Worker bundle: ${t.toFixed(1)}MB`}}function Fs(r,e){let a=[],t=new Set;if(e.plan?.pages)for(let o of e.plan.pages){let n=o.path??o.route??o.name;n&&t.add(n.replace(/^\//,""))}if(t.size===0)return[];let i=0,s=[];for(let o of t){if(o.startsWith("api/")||o==="login"||o==="register"||o==="sign-in"||o==="sign-up"){i++;continue}[K(r,"app","(dashboard)",o,"page.tsx"),K(r,"app","(dashboard)",o,"page.ts"),K(r,"app",o,"page.tsx"),K(r,"app",o,"page.ts"),K(r,"app","(admin)",o,"page.tsx")].some(l=>fe(l))?i++:s.push(`/${o}`)}return s.length===0?a.push({name:"plan-routes",status:"pass",message:`All ${t.size} planned pages found`}):s.length<=2?a.push({name:"plan-routes",status:"warn",message:`${i}/${t.size} planned pages found. Missing: ${s.join(", ")}`}):a.push({name:"plan-routes",status:"warn",message:`Only ${i}/${t.size} planned pages found. ${s.length} pages missing.`}),a}function Es(r){let e=K(r,"app");if(!fe(e))return{name:"empty-pages",status:"pass",message:"App directory check skipped"};let a=[];function t(i){try{for(let s of $t(i)){let o=K(i,s);try{if(yt(o).isDirectory())t(o);else if(s==="page.tsx"||s==="page.ts"){let l=Ve(o,"utf-8").trim();if(l.length<50||l.includes("export default function")&&l.includes("TODO")){let d=o.replace(r+"/","");a.push(d)}}}catch{}}}catch{}}return t(e),a.length>0?{name:"empty-pages",status:"warn",message:`${a.length} page(s) appear to be empty/placeholder: ${a.slice(0,3).join(", ")}`}:{name:"empty-pages",status:"pass",message:"No empty pages detected"}}function Us(r){let e=K(r,"app","page.tsx");if(!fe(e))return{name:"landing-page",status:"warn",message:"No root page.tsx found"};let a=Ve(e,"utf-8");if(a.includes('redirect("/login")')||a.includes('redirect("/register")')){let t=K(r,"middleware.ts"),i=fe(t)?Ve(t,"utf-8"):"";if(!(i.includes('"/"')||i.includes("'/'")))return{name:"landing-page",status:"fail",message:"Landing page (app/page.tsx) is just a redirect to /login, and middleware does not allow '/' as public. Users will never see a landing page."}}return a.length<200?{name:"landing-page",status:"warn",message:`Landing page is very small (${a.length} chars) \u2014 may be a placeholder.`}:{name:"landing-page",status:"pass",message:"Landing page has content"}}function Hs(r){let e=K(r,"app");if(!fe(e))return{name:"cookies-in-actions",status:"pass",message:"No app directory"};let a=[];function t(i){try{for(let s of $t(i)){if(s==="node_modules"||s===".next"||s===".open-next")continue;let o=K(i,s);try{if(yt(o).isDirectory())t(o);else if(s==="actions.ts"||s==="actions.tsx"){let l=Ve(o,"utf-8");l.includes("use server")&&l.includes("cookies()")&&l.includes(".set(")&&a.push(o.replace(r+"/",""))}}catch{}}}catch{}}return t(e),a.length>0?{name:"cookies-in-actions",status:"fail",message:`${a.length} server action(s) use cookies().set() which crashes on Mistflow Cloud's edge runtime: ${a.join(", ")}. Use a database field or form parameter instead.`}:{name:"cookies-in-actions",status:"pass",message:"No cookies().set() in server actions"}}function Os(r){let e=K(r,"app");if(!fe(e))return{name:"fake-forms",status:"pass",message:"No app directory"};let a=[];function t(i){try{for(let s of $t(i)){if(s==="node_modules"||s===".next"||s===".open-next")continue;let o=K(i,s);try{if(yt(o).isDirectory())t(o);else if(s.endsWith(".tsx")||s.endsWith(".ts")){let l=Ve(o,"utf-8");if((l.includes("setTimeout")||l.includes("new Promise"))&&l.includes("Simulate")||l.includes("simulate")||l.includes("// TODO")&&l.includes("API")){let d=o.replace(r+"/","");a.push(d)}}}catch{}}}catch{}}return t(e),a.length>0?{name:"fake-forms",status:"fail",message:`${a.length} file(s) have fake/simulated API calls instead of real server actions: ${a.slice(0,3).join(", ")}. Forms must use 'use server' actions that write to the database.`}:{name:"fake-forms",status:"pass",message:"No fake form handlers detected"}}function Gs(r){let a=[K(r,"components","sidebar.tsx"),K(r,"components","topnav.tsx"),K(r,"components","nav.tsx")].find(n=>fe(n));if(!a)return[];let s=(Ve(a,"utf-8").match(/href:\s*"([^"]+)"/g)??[]).map(n=>n.replace(/href:\s*"/,"").replace(/"$/,"")).filter(n=>!n.startsWith("/api")&&!n.includes("["));if(s.length===0)return[];let o=[];for(let n of s){let l=n.replace(/^\//,"");if(!l||l==="")continue;[K(r,"app","(dashboard)",l,"page.tsx"),K(r,"app","(dashboard)",l,"page.ts"),K(r,"app","(app)",l,"page.tsx"),K(r,"app",l,"page.tsx"),K(r,"app",l,"page.ts")].some(u=>fe(u))||o.push(n)}return o.length>0?[{name:"nav-links",status:"fail",message:`Sidebar/nav links to pages that don't exist: ${o.join(", ")}. These will 404.`}]:[{name:"nav-links",status:"pass",message:`All ${s.length} nav links have matching pages`}]}function Ie(r,e){return $s("git",r,{cwd:e,encoding:"utf-8",timeout:3e4,stdio:["pipe","pipe","pipe"]}).trim()}function Ma(r){try{return Ie(["rev-parse","--is-inside-work-tree"],r),!0}catch{return!1}}function Wo(r){try{return Ie(["status","--porcelain"],r).length>0}catch{return!1}}function _o(r,e){return Ie(["add","-A"],r),Ie(["commit","-m",e,"--allow-empty-message"],r),Ie(["rev-parse","HEAD"],r)}function Ks(r){return Ie(["rev-parse","HEAD"],r)}function jo(r){try{return Ie(["remote","get-url","origin"],r),!0}catch{return!1}}function Ys(r){try{let e=Ie(["rev-parse","--abbrev-ref","HEAD"],r);return Ie(["push","origin",e],r),{success:!0}}catch(e){return{success:!1,error:e instanceof Error?e.message:"push failed"}}}function Js(r){let e=qt(Vo(r)),a=qt(e)===e?e:"/";for(;e!==a&&e!==qt(e);){if(le(te(e,"pnpm-workspace.yaml"))||le(te(e,"lerna.json")))return e;let t=te(e,"package.json");if(le(t))try{if(JSON.parse(qo(t,"utf-8")).workspaces)return e}catch{}e=qt(e)}return null}function Qs(r){let e=te(qs(),"mistflow-build");js(e,{recursive:!0});let a=te(e,`${Ws(r)}-${Date.now()}`);return Ra(r,a,{recursive:!0,filter:t=>{let i=t.slice(r.length);return!(i.startsWith("/.git")||i.startsWith("\\.git")||i==="/node_modules"||i.startsWith("/node_modules/")||i==="/.open-next"||i.startsWith("/.open-next/")||i==="/.next"||i.startsWith("/.next/"))}}),a}function Xs(r,e){let a=te(r,".open-next"),t=te(r,".next");if(le(a)){let i=te(e,".open-next");le(i)&&Na(i,{recursive:!0}),Ra(a,i,{recursive:!0})}if(le(t)){let i=te(e,".next");le(i)&&Na(i,{recursive:!0}),Ra(t,i,{recursive:!0})}}function Zs(r){try{Na(r,{recursive:!0,force:!0})}catch{console.error(`[deploy] Failed to clean up isolated build dir: ${r}`)}}function ot(r,e,a,t,i,s){return new Promise(o=>{let n=Vs(r,e,{cwd:a,stdio:["pipe","pipe","pipe"],timeout:t,...s?{env:s}:{}}),l="",d="";n.stdout?.on("data",u=>{let h=u.toString();if(d+=h,i)for(let c of h.split(`
13448
+ `).filter(Boolean))i(c)}),n.stderr?.on("data",u=>{let h=u.toString();if(l+=h,i)for(let c of h.split(`
13449
+ `).filter(Boolean))i(c)}),n.on("close",(u,h)=>{o({success:u===0,stdout:d,stderr:l,signal:h})}),n.on("error",u=>{o({success:!1,stdout:d,stderr:l+u.message})})})}var Jp=Vt.object({projectPath:Vt.string().optional().describe("Path to the project directory (default: current working directory)"),message:Vt.string().optional().describe("Deploy message"),environment:Vt.enum(["production","preview"]).optional().default("production").describe("Target environment: 'production' (default) or 'preview' for a shareable URL")});function el(r){return new Promise(e=>setTimeout(e,r))}function zo(r){switch(r){case"pending":return"Provisioning database...";case"building":return"Building your app...";case"deploying":return"Deploying to Mistflow Cloud...";case"verifying":return"Verifying deployment...";default:return`Status: ${r}`}}function La(r){let e=te(r,"mistflow.json");if(le(e))try{return JSON.parse(qo(e,"utf-8"))}catch{}return{}}function tl(r,e){let a=te(r,"mistflow.json"),t=La(r),i=t.deploy?.count??t.deployCount??0;t.deploy={url:e,count:i+1,lastDeployedAt:new Date().toISOString()};let s=t.plan?.steps;if(Array.isArray(s))for(let o of s)o.status==="in_progress"&&(o.status="completed");delete t.deployUrl,delete t.deployCount,_s(a,JSON.stringify(t,null,2)+`
13450
+ `)}async function Ko(r,e){let t=process.platform==="win32"?"npx.cmd":"npx",i=0,s=te(r,"node_modules",".bin","opennextjs-cloudflare"),o=le(s),n={...process.env,NODE_ENV:"production"},l=await ot(o?s:t,o?["build"]:["@opennextjs/cloudflare","build"],r,3e5,c=>{c.includes("Compiling")?e?.("Compiling your app..."):c.includes("Collecting page data")?e?.("Collecting page data..."):c.includes("Generating static pages")?e?.("Generating static pages..."):c.match(/^[○●◐λƒ]\s/)?i++:c.includes("Creating Cloudflare worker")?e?.("Packaging for Mistflow Cloud..."):c.includes("Build completed")&&e?.("Build completed!")},n);if(l.success)return{success:!0,buildStats:i>0?`${i} routes compiled`:"build complete"};let d=l.stderr+`
13451
+ `+l.stdout;if(l.signal==="SIGKILL"||d.includes("SIGKILL")||d.includes("exit code: 137"))return{success:!1,signal:"SIGKILL",error:"Your app ran out of memory while building. This sometimes happens with complex projects. Try deploying again \u2014 if it keeps failing, we'll help you figure it out."};let u=ft(d);if(u.length>0){let c=u.map((g,b)=>{let f=`${b+1}. ${g.humanMessage}`;return g.suggestion&&(f+=`
13452
+ Fix: ${g.suggestion}`),f}).join(`
12439
13453
 
12440
13454
  `);return{success:!1,error:`Build failed with ${u.length} error${u.length===1?"":"s"}:
12441
13455
 
@@ -12443,83 +13457,80 @@ ${c}`}}let h=d.split(`
12443
13457
  `).filter(c=>c.includes("error")||c.includes("Error")).slice(0,10);return{success:!1,error:h.length>0?`OpenNext build failed:
12444
13458
  ${h.join(`
12445
13459
  `)}`:`OpenNext build failed:
12446
- ${d.slice(-500)}`}}async function Ws(r,e){let t=Fs(r);if(t)return console.error(`[deploy] Project is inside monorepo at ${t} \u2014 building in isolated temp directory`),e?.("Detected parent project folder \u2014 building in isolated directory..."),Eo(r,e);let a=await Go(r,e);return!a.success&&a.signal==="SIGKILL"?(console.error("[deploy] Build was OOM-killed \u2014 retrying in isolated temp directory"),e?.("Build ran out of memory \u2014 retrying with a fresh setup..."),Eo(r,e)):a}async function Eo(r,e){let t;try{e?.("Copying project to isolated build directory..."),t=Us(r),e?.("Installing dependencies...");let i=process.platform==="win32"?"npm.cmd":"npm",n=await lt(i,["install","--prefer-offline"],t,12e4);if(!n.success)return{success:!1,error:`Failed to install dependencies in isolated build:
12447
- ${n.stderr.slice(-300)}`};await lt(i,["dedupe"],t,6e4);let o=await Go(t,e);return o.success&&(e?.("Copying build artifacts..."),Es(t,r)),{...o,builtInIsolation:t}}finally{t&&Hs(t)}}async function _s(r,e,t){if(!ne(J(r,"db","schema")))return{success:!0,skipped:!0,skipReason:"No database structure found"};let a=J(r,"db","schema");try{if(As(a).filter(g=>g.endsWith(".ts")).length===0)return{success:!0,skipped:!0,skipReason:"No database files found in db/schema/"}}catch{return{success:!0,skipped:!0,skipReason:"Could not read db/schema/ directory"}}if(!ne(J(r,"drizzle.config.ts")))return{success:!0,skipped:!0,skipReason:"No drizzle.config.ts found"};let i;try{i=await Ja(e)}catch{return console.error("[deploy] Could not fetch DB credentials \u2014 skipping local schema push"),{success:!0,skipped:!0,skipReason:"Could not fetch database credentials \u2014 backend will handle the database update"}}let{db_provider:n,credentials:o}=i;if(n==="neon"){if(!o.DATABASE_URL)return console.error("[deploy] No DATABASE_URL found \u2014 skipping local schema push"),{success:!0,skipped:!0,skipReason:"No DATABASE_URL configured \u2014 set it with mist_config"}}else if(!o.TURSO_URL||!o.TURSO_AUTH_TOKEN)return console.error("[deploy] No Turso credentials found \u2014 skipping local schema push"),{success:!0,skipped:!0,skipReason:"No Turso credentials configured"};let s={PATH:process.env.PATH??"",HOME:process.env.HOME??"",NODE_PATH:process.env.NODE_PATH??"",TMPDIR:process.env.TMPDIR??"",...o},d=process.platform==="win32"?"npx.cmd":"npx",h=await lt(d,t?["drizzle-kit","push","--force"]:["drizzle-kit","push"],r,6e4,c=>{let g=c.replace(/(?:TURSO_AUTH_TOKEN|DATABASE_URL|AUTH_SECRET)=[^\s&]*/gi,b=>b.split("=")[0]+"=REDACTED").replace(/postgresql:\/\/[^@]*@/g,"postgresql://REDACTED@").replace(/libsql:\/\/[^\s]*/g,"libsql://REDACTED");console.error(`[drizzle-kit] ${g}`)},s);if(!h.success){let c=(h.stderr+`
13460
+ ${d.slice(-500)}`}}async function al(r,e){let a=Js(r);if(a)return console.error(`[deploy] Project is inside monorepo at ${a} \u2014 building in isolated temp directory`),e?.("Detected parent project folder \u2014 building in isolated directory..."),$o(r,e);let t=await Ko(r,e);return!t.success&&t.signal==="SIGKILL"?(console.error("[deploy] Build was OOM-killed \u2014 retrying in isolated temp directory"),e?.("Build ran out of memory \u2014 retrying with a fresh setup..."),$o(r,e)):t}async function $o(r,e){let a;try{e?.("Copying project to isolated build directory..."),a=Qs(r),e?.("Installing dependencies...");let i=process.platform==="win32"?"npm.cmd":"npm",s=await ot(i,["install","--prefer-offline"],a,12e4);if(!s.success)return{success:!1,error:`Failed to install dependencies in isolated build:
13461
+ ${s.stderr.slice(-300)}`};await ot(i,["dedupe"],a,6e4);let o=await Ko(a,e);return o.success&&(e?.("Copying build artifacts..."),Xs(a,r)),{...o,builtInIsolation:a}}finally{a&&Zs(a)}}async function rl(r,e,a){if(!le(te(r,"db","schema")))return{success:!0,skipped:!0,skipReason:"No database structure found"};let t=te(r,"db","schema");try{if(zs(t).filter(g=>g.endsWith(".ts")).length===0)return{success:!0,skipped:!0,skipReason:"No database files found in db/schema/"}}catch{return{success:!0,skipped:!0,skipReason:"Could not read db/schema/ directory"}}if(!le(te(r,"drizzle.config.ts")))return{success:!0,skipped:!0,skipReason:"No drizzle.config.ts found"};let i;try{i=await Xa(e)}catch{return console.error("[deploy] Could not fetch DB credentials \u2014 skipping local schema push"),{success:!0,skipped:!0,skipReason:"Could not fetch database credentials \u2014 backend will handle the database update"}}let{db_provider:s,credentials:o}=i;if(s==="neon"){if(!o.DATABASE_URL)return console.error("[deploy] No DATABASE_URL found \u2014 skipping local schema push"),{success:!0,skipped:!0,skipReason:"No DATABASE_URL configured \u2014 set it with mist_config"}}else if(!o.TURSO_URL||!o.TURSO_AUTH_TOKEN)return console.error("[deploy] No Turso credentials found \u2014 skipping local schema push"),{success:!0,skipped:!0,skipReason:"No Turso credentials configured"};let n={PATH:process.env.PATH??"",HOME:process.env.HOME??"",NODE_PATH:process.env.NODE_PATH??"",TMPDIR:process.env.TMPDIR??"",...o},d=process.platform==="win32"?"npx.cmd":"npx",h=await ot(d,a?["drizzle-kit","push","--force"]:["drizzle-kit","push"],r,6e4,c=>{let g=c.replace(/(?:TURSO_AUTH_TOKEN|DATABASE_URL|AUTH_SECRET)=[^\s&]*/gi,b=>b.split("=")[0]+"=REDACTED").replace(/postgresql:\/\/[^@]*@/g,"postgresql://REDACTED@").replace(/libsql:\/\/[^\s]*/g,"libsql://REDACTED");console.error(`[drizzle-kit] ${g}`)},n);if(!h.success){let c=(h.stderr+`
12448
13462
  `+h.stdout).trim(),g=c.toLowerCase();if(g.includes("data-loss")||g.includes("cannot be reverted"))return{success:!1,error:"Database update blocked: your database has existing data that would be deleted. The update includes changes that would remove existing data. If you're OK with losing the data, deploy again with forceSchema: true."};let b=c.split(`
12449
- `).filter(f=>f.includes("error")||f.includes("Error")||f.includes("ERR")).slice(0,5);return{success:!1,error:`Database update failed:
13463
+ `).filter(m=>m.includes("error")||m.includes("Error")||m.includes("ERR")).slice(0,5);return{success:!1,error:`Database update failed:
12450
13464
  ${b.length>0?b.join(`
12451
- `):c.slice(-500)}`}}return{success:!0}}async function Wo(r){let{projectPath:e,message:t,environment:a="production",forceSchema:i}=r,n=a,o=Ho(e??process.cwd());if(!ce())return p("No Mistflow credentials found. Run mist_setup to connect your account.",!0);let s=Io(o);if(!s.passed){let T=s.errors.map(R=>{let L=`[${R.check}] ${R.message}`;return R.file&&(L+=` (${R.file}${R.line?`:${R.line}`:""})`),L}).join(`
13465
+ `):c.slice(-500)}`}}return{success:!0}}async function Yo(r,e){let{projectPath:a,message:t,environment:i="production",forceSchema:s}=r,o=i,n=Vo(a??process.cwd());if(!ue())return p("No Mistflow credentials found. Run mist_setup to connect your account.",!0);let l=Ho(n);if(!l.passed){let P=l.errors.map(U=>{let O=`[${U.check}] ${U.message}`;return U.file&&(O+=` (${U.file}${U.line?`:${U.line}`:""})`),O}).join(`
12452
13466
  `);return p(`Deploy blocked \u2014 fix these issues first:
12453
13467
 
12454
- ${T}`,!0)}let l=s.warnings.map(T=>`[${T.check}] ${T.message}`),d;if(Aa(o))if(Lo(o))try{let T=Ra(o).deploy?.count??0,R=t?`Deploy v${T+1}: ${t}`:`Deploy v${T+1}`;d=No(o,R)}catch{console.error("[deploy] Pre-deploy git commit failed, continuing")}else try{d=Ls(o)}catch{}let u=De(o),h=u?.projectId;if(!h){let T=J(o,"mistflow.json");if(!ne(T))return p("No mistflow.json found. Run mist_build (action: 'init') first to set up your project.",!0);try{let R=JSON.parse(La(T,"utf-8")),L=R.name;if(!L)return p("mistflow.json is missing a project name. Run mist_build (action: 'init') to set up your project.",!0);h=(await Bt(L)).id,R.projectId=h,Oo(T,JSON.stringify(R,null,2)+`
12455
- `)}catch(R){let L=R instanceof he?R.message:"Check your internet connection.";return p(`Could not register project with Mistflow: ${L}
13468
+ ${P}`,!0)}let d=l.warnings.map(P=>`[${P.check}] ${P.message}`),u;if(Ma(n))if(Wo(n))try{let P=La(n).deploy?.count??0,U=t?`Deploy v${P+1}: ${t}`:`Deploy v${P+1}`;u=_o(n,U)}catch{console.error("[deploy] Pre-deploy git commit failed, continuing")}else try{u=Ks(n)}catch{}let h=te(n,"mistflow.json");if(!le(h))return p("No mistflow.json found. Run mist_build (action: 'init') first to set up your project.",!0);let c;try{let{ensureBackendRegistered:P}=await import("./self-heal-OGLEOZC4.js");c=await P(n,{forceSync:!0})}catch(P){let U=P instanceof me?P.message:"Check your internet connection.";return p(`Could not register project with Mistflow: ${U}
12456
13469
 
12457
- Try deploying again in a moment.`,!0)}}if(h&&n==="production")try{let T=await za(h),R=T,L=R.deploy_count??T.deployCount??0;(R.deploy_strategy??T.deploy_strategy)==="staging"&&L>0&&(n="preview",console.error("[deploy] Staging mode enabled \u2014 auto-redirecting to preview"))}catch{}let c=[],g=Date.now(),b=(T,R)=>{let L={phase:T,message:R};return c.push(L),L},y=(T,R)=>{T.durationMs=Date.now()-g,R&&(T.message=R)},f=!1;if(h&&n!=="preview"){let T=b("schema","Updating database structure..."),R=await _s(o,h,i);if(R.skipped)y(T,R.skipReason??"Database update skipped");else if(R.success)f=!0,y(T,"Database updated successfully");else return y(T,"Database update failed"),p(`Deploy blocked \u2014 database update failed:
13470
+ Try deploying again in a moment.`,!0)}if(c||(c=Se(n)?.projectId),!c)return p("Could not register project with Mistflow \u2014 you may not be signed in. Run mist_setup to authenticate, then redeploy.",!0);if(c&&o==="production")try{let P=await $a(c),U=P,O=U.deploy_count??P.deployCount??0;(U.deploy_strategy??P.deploy_strategy)==="staging"&&O>0&&(o="preview",console.error("[deploy] Staging mode enabled \u2014 auto-redirecting to preview"))}catch{}let g=[],b=Date.now(),f=(P,U)=>{let O={phase:P,message:U};return g.push(O),O},m=(P,U)=>{P.durationMs=Date.now()-b,U&&(P.message=U)};if(e){let P=pt(e.server,e.progressToken,()=>g[g.length-1]?.message??"Deploying...");e.cleanup=()=>P.stop()}let y=!1;if(c&&o!=="preview"){let P=f("schema","Updating database structure..."),U=await rl(n,c,s);if(U.skipped)m(P,U.skipReason??"Database update skipped");else if(U.success)y=!0,m(P,"Database updated successfully");else return m(P,"Database update failed"),p(`Deploy blocked \u2014 database update failed:
12458
13471
 
12459
- ${R.error}
13472
+ ${U.error}
12460
13473
 
12461
- Try deploying again in a moment. If it keeps failing, check your database files in db/schema/.`,!0)}let v=b("build","Compiling your app for Mistflow Cloud..."),C=Date.now(),P=await Ws(o,T=>{v.message=T});if(!P.success)return p(`Deploy blocked \u2014 OpenNext build failed:
13474
+ Try deploying again in a moment. If it keeps failing, check your database files in db/schema/.`,!0)}let T=f("build","Compiling your app for Mistflow Cloud..."),k=Date.now(),I=await al(n,P=>{T.message=P});if(!I.success)return p(`Deploy blocked \u2014 OpenNext build failed:
12462
13475
 
12463
- ${P.error}`,!0);y(v,`Build complete \u2014 ${P.buildStats??"ready"} (${((Date.now()-C)/1e3).toFixed(0)}s)`);let A=b("qa","Running smoke tests..."),B=await Mo(o,u);if(B.passed)y(A,`Smoke test passed \u2014 ${B.checksRun} checks OK`);else if(y(A,`Smoke test: ${B.summary}`),B.blocking)return p(`Deploy blocked \u2014 smoke test failed:
13476
+ ${I.error}`,!0);m(T,`Build complete \u2014 ${I.buildStats??"ready"} (${((Date.now()-k)/1e3).toFixed(0)}s)`);let B=f("qa","Running smoke tests..."),M=Se(n),C=await Oo(n,M);if(C.passed)m(B,`Smoke test passed \u2014 ${C.checksRun} checks OK`);else if(m(B,`Smoke test: ${C.summary}`),C.blocking)return p(`Deploy blocked \u2014 smoke test failed:
12464
13477
 
12465
- ${B.summary}
13478
+ ${C.summary}
12466
13479
 
12467
- Fix these issues and try again.`,!0);let I=J(o,".open-next");if(!ne(I))return p("Build succeeded but .open-next/ directory not found. Check your OpenNext configuration.",!0);b("package","Packaging build artifacts...");let M=J(o,".open-next-build.tar.gz"),O=[".open-next"];if(ne(J(o,"db"))&&O.push("db"),ne(J(o,"drizzle.config.ts"))&&O.push("drizzle.config.ts"),ne(J(o,"package.json"))&&O.push("package.json"),!(await lt("tar",["-czf",M,"-C",o,...O],o,6e4)).success)return p("Failed to create build archive. Check disk space and permissions.",!0);let H;{let T=J(o,".mistflow-source.tar.gz");(await lt("tar",["-czf",T,"-C",o,"--exclude",".open-next","--exclude","node_modules","--exclude",".git","--exclude",".next","--exclude",".open-next-build.tar.gz","--exclude",".mistflow-source.tar.gz","."],o,6e4)).success?H=T:console.error("[deploy] Source archive creation failed, continuing without it")}b("upload","Uploading to Mistflow...");let re=Pt()?.email,z,K;try{let T=await Va(h,M,n,re,f,H,d);if(z=T.deployment_id??T.id,!z)return p("Upload succeeded but no deployment ID was returned. Check the Mistflow dashboard.",!0);K=T.status}catch(T){return Ao(T,"Deploy")}finally{try{Ro(M)}catch{}if(H)try{Ro(H)}catch{}}b("deploying","Deploying to the edge...");let U=Date.now(),_=24e4,F=3e3,Q=[Uo(K)],le=K;for(;Date.now()-U<_;){await Os(F);let T;try{T=await Ue(z)}catch{continue}if(T.status!==le&&(Q.push(Uo(T.status)),le=T.status),T.status==="live"){let R=((Date.now()-U)/1e3).toFixed(0),L=T.url??"";if(!L)return p("Deployment marked as live but no URL was returned. Check the Mistflow dashboard.",!0);let oe=n==="preview";if(!oe){Gs(o,L);try{let{readLocalState:m,syncRemoteState:x}=await import("./state-manager-XY6GQEFE.js"),S=m(o);S&&h&&(S.deployCount=(S.deployCount??0)+1,x(h,S).catch(()=>{}))}catch{}if(Aa(o)&&Fo(o))try{Lo(o)&&No(o,"Update deploy metadata"),Ns(o)}catch{}}let ie=oe?"Preview":"App",w=Ra(o),N=w.name,q=typeof N=="string"?N:"my app",me=w.features,pe=Array.isArray(me)?me.length:0,Z=w.plan,xe=Z&&typeof Z=="object"&&"steps"in Z?Z.steps:void 0,we=Array.isArray(xe)?xe.length:0,de=c.map(m=>m.message),$={url:L,time:`${R}s`,deploymentId:z,environment:n,statusLog:Q,deployTimeline:de,message:oe?`${ie} is live at ${L} \u2014 preview deployed in ${R}s. This URL expires in 72 hours.`:`Your app is live at ${L} \u2014 deployed in ${R}s.`};if(oe)$.instruction=["STAGING FLOW: The preview is deployed with an isolated database. Verify it before going live.","","1. Call mist_build with action='qa' and url='"+L+"' to test the preview.","2. If QA passes \u2014 tell the user the preview looks good and ask: 'Ready to go live?'","3. If QA finds issues \u2014 fix them, then call mist_deploy action='deploy' environment='preview' to redeploy the preview.","4. When user confirms \u2014 call mist_deploy action='promote' to push to production (~10s).","","Show the preview URL to the user so they can check it themselves while QA runs."].join(`
12468
- `),$.nextAction=`MANDATORY: Call mist_build with action='qa' and url='${L}' to verify the preview. After QA passes, ask user to confirm, then call mist_deploy action='promote'.`,$.qaRequired=!0,$.qaUrl=L,$.previewDeploymentId=z,$.promoteAction=`mist_deploy action='promote' deploymentId='${z}'`;else{let m=encodeURIComponent(`Just built "${q}" with AI and deployed it live in ${R}s.
13480
+ Fix these issues and try again.`,!0);let j=te(n,".open-next");if(!le(j))return p("Build succeeded but .open-next/ directory not found. Check your OpenNext configuration.",!0);f("package","Packaging build artifacts...");let L=te(n,".open-next-build.tar.gz"),G=[".open-next"];if(le(te(n,"db"))&&G.push("db"),le(te(n,"drizzle.config.ts"))&&G.push("drizzle.config.ts"),le(te(n,"package.json"))&&G.push("package.json"),!(await ot("tar",["-czf",L,"-C",n,...G],n,6e4)).success)return p("Failed to create build archive. Check disk space and permissions.",!0);let $;{let P=te(n,".mistflow-source.tar.gz");(await ot("tar",["-czf",P,"-C",n,"--exclude",".open-next","--exclude","node_modules","--exclude",".git","--exclude",".next","--exclude",".open-next-build.tar.gz","--exclude",".mistflow-source.tar.gz","."],n,6e4)).success?$=P:console.error("[deploy] Source archive creation failed, continuing without it")}f("upload","Uploading to Mistflow...");let Q=Pt()?.email,V,F;try{let P=await Ka(c,L,o,Q,y,$,u);if(V=P.deployment_id??P.id,!V)return p("Upload succeeded but no deployment ID was returned. Check the Mistflow dashboard.",!0);F=P.status}catch(P){return Uo(P,"Deploy")}finally{try{Go(L)}catch{}if($)try{Go($)}catch{}}f("deploying","Deploying to the edge...");let H=Date.now(),oe=24e4,he=3e3,Y=[zo(F)],pe=F;for(;Date.now()-H<oe;){await el(he);let P;try{P=await Le(V)}catch{continue}if(P.status!==pe&&(Y.push(zo(P.status)),pe=P.status),P.status==="live"){let U=((Date.now()-H)/1e3).toFixed(0),O=P.url??"";if(!O)return p("Deployment marked as live but no URL was returned. Check the Mistflow dashboard.",!0);let x=o==="preview";if(!x){tl(n,O);try{let{readLocalState:z,syncRemoteState:X}=await import("./state-manager-6NJO66MS.js"),re=z(n);re&&c&&(re.deployCount=(re.deployCount??0)+1,X(c,re).catch(()=>{}))}catch{}if(Ma(n)&&jo(n))try{Wo(n)&&_o(n,"Update deploy metadata"),Ys(n)}catch{}}let E=x?"Preview":"App",q=La(n),ge=q.name,S=typeof ge=="string"?ge:"my app",N=q.features,W=Array.isArray(N)?N.length:0,J=q.plan,se=J&&typeof J=="object"&&"steps"in J?J.steps:void 0,R=Array.isArray(se)?se.length:0,w=g.map(z=>z.message),v={url:O,time:`${U}s`,deploymentId:V,environment:o,statusLog:Y,deployTimeline:w,message:x?`${E} is live at ${O} \u2014 preview deployed in ${U}s. This URL expires in 72 hours.`:`Your app is live at ${O} \u2014 deployed in ${U}s.`};if(x)v.instruction=["STAGING FLOW: The preview is deployed with an isolated database. Verify it before going live.","","1. Call mist_build with action='qa' and url='"+O+"' to test the preview.","2. If QA passes \u2014 tell the user the preview looks good and ask: 'Ready to go live?'","3. If QA finds issues \u2014 fix them, then call mist_deploy action='deploy' environment='preview' to redeploy the preview.","4. When user confirms \u2014 call mist_deploy action='promote' to push to production (~10s).","","Show the preview URL to the user so they can check it themselves while QA runs."].join(`
13481
+ `),v.nextAction=`MANDATORY: Call mist_build with action='qa' and url='${O}' to verify the preview. After QA passes, ask user to confirm, then call mist_deploy action='promote'.`,v.qaRequired=!0,v.qaUrl=O,v.previewDeploymentId=V,v.promoteAction=`mist_deploy action='promote' deploymentId='${V}'`;else{let z=encodeURIComponent(`Just built "${S}" with AI and deployed it live in ${U}s.
12469
13482
 
12470
- ${we} steps, ${pe} features, zero config.
13483
+ ${R} steps, ${W} features, zero config.
12471
13484
 
12472
- ${L}
13485
+ ${O}
12473
13486
 
12474
- Built with @mistflow`),x=encodeURIComponent(`I just described an app idea and got a live, working URL in ${R} seconds.
13487
+ Built with @mistflow`),X=encodeURIComponent(`I just described an app idea and got a live, working URL in ${U} seconds.
12475
13488
 
12476
- "${q}" \u2014 ${we} build steps, ${pe} features, fully deployed.
13489
+ "${S}" \u2014 ${R} build steps, ${W} features, fully deployed.
12477
13490
 
12478
- ${L}
13491
+ ${O}
12479
13492
 
12480
- Built with Mistflow (mistflow.ai)`);$.share={tweetUrl:`https://twitter.com/intent/tweet?text=${m}`,linkedInUrl:`https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(L)}`,celebration:["","================================================",` ${q} is LIVE!`,"================================================","",` ${L}`,"",` Deployed in ${R}s`,` ${pe>0?`${pe} features`:""}${pe>0&&we>0?" \xB7 ":""}${we>0?`${we} build steps`:""}`,""," Share it:",` Twitter \u2192 https://twitter.com/intent/tweet?text=${m}`,` LinkedIn \u2192 https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(L)}`,"","================================================"].join(`
12481
- `)},$.instruction=["IMPORTANT: The app is deployed but NOT verified yet. Before celebrating:","","1. Call mist_build with action='qa' to test the live app.","2. If QA passes \u2014 present the URL prominently and show the celebration block above.","3. If QA finds issues \u2014 fix them, redeploy (mist_deploy action='deploy'), and run qa again.","4. Keep looping until QA passes. Only celebrate when all checks are green.","","Do NOT show the URL to the user until QA passes. A broken app is worse than no app."].join(`
12482
- `),$.nextAction=`MANDATORY: Call mist_build with action='qa' and url='${L}' to verify the app works. Do NOT tell the user the app is ready until QA passes.`,$.qaRequired=!0,$.qaUrl=L}if(T.admin_credentials){let m=!!w.hasAdmin,x=m?`
12483
- Admin panel: ${L}/admin`:"",S=m?"Admin account created":"Test account created";$.adminCredentials={message:`${S}. Save these credentials \u2014 they won't be shown again.
13493
+ Built with Mistflow (mistflow.ai)`);v.share={tweetUrl:`https://twitter.com/intent/tweet?text=${z}`,linkedInUrl:`https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(O)}`,celebration:["","================================================",` ${S} is LIVE!`,"================================================","",` ${O}`,"",` Deployed in ${U}s`,` ${W>0?`${W} features`:""}${W>0&&R>0?" \xB7 ":""}${R>0?`${R} build steps`:""}`,""," Share it:",` Twitter \u2192 https://twitter.com/intent/tweet?text=${z}`,` LinkedIn \u2192 https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(O)}`,"","================================================"].join(`
13494
+ `)},v.instruction=["IMPORTANT: The app is deployed but NOT verified yet. Before celebrating:","","1. Call mist_build with action='qa' to test the live app.","2. If QA passes \u2014 present the URL prominently and show the celebration block above.","3. If QA finds issues \u2014 fix them, redeploy (mist_deploy action='deploy'), and run qa again.","4. Keep looping until QA passes. Only celebrate when all checks are green.","","Do NOT show the URL to the user until QA passes. A broken app is worse than no app."].join(`
13495
+ `),v.nextAction=`MANDATORY: Call mist_build with action='qa' and url='${O}' to verify the app works. Do NOT tell the user the app is ready until QA passes.`,v.qaRequired=!0,v.qaUrl=O}q.hasAdmin&&!x&&Q&&(v.adminSetup={message:`Your app is live. You're the admin \u2014 sign up at ${O}/signup with ${Q} to create your admin account. You'll pick your own password; the /admin panel unlocks as soon as you sign up.`}),d.length>0&&(v.warnings=d);let D=[];if(!x){let z=JSON.stringify(q).toLowerCase();D.push({action:"Add a custom domain",command:"mist_config resource='domain' action='add' domain='yourdomain.com'",reason:"Give your app a professional URL instead of the default .mistflow.app domain.",priority:"medium"}),Ma(n)&&!jo(n)&&D.push({action:"Back up to GitHub",command:"Create a repo at github.com/new, then tell me the URL and I'll push your code there",reason:"Your source code is saved with every deploy, but GitHub gives you an extra backup and lets you collaborate.",priority:"medium"});let X=z.includes("user")||z.includes("account")||z.includes("member"),re=z.includes("stripe")||z.includes("payment")||z.includes("billing");X&&!re&&D.push({action:"Add payments",command:"Tell the AI: 'Add Stripe payments to my app'",reason:"Your app has user accounts \u2014 you could monetize with subscriptions or one-time payments.",priority:"low"}),D.push({action:"Share your app",command:"mist_project action='share'",reason:"Generate a shareable link so others can fork and customize your app.",priority:"low"}),D.push({action:"Make changes",command:"Describe what you want to change, then run mist_deploy action='deploy'",reason:"Edit your code and redeploy in seconds. Your app is yours to evolve.",priority:"low"}),D.push({action:"See analytics",command:`Visit ${_a()} to see pageviews and visitors`,reason:"Track how people are using your app.",priority:"low"})}return D.length>0&&(v.nextSteps=D),Fe(O,JSON.stringify(v))}if(P.status==="failed")return p(P.error??"Deployment failed. Check the Mistflow dashboard for details.",!0)}return p(`Deploy is taking longer than expected. Your deploy ID is ${V} \u2014 check status later.`,!0)}import{z as Kt}from"zod";import{resolve as ol,join as il}from"path";import{execFileSync as nl}from"child_process";var ou=Kt.object({projectPath:Kt.string().optional().describe("Path to the project directory (default: cwd)"),action:Kt.enum(["redeploy","rollback"]).describe("Action to perform: 'redeploy' re-deploys the latest build, 'rollback' reverts to a specific previous deployment"),deploymentId:Kt.string().optional().describe("Deployment ID to rollback to (required for 'rollback')")});function Oe(r,e){return nl("git",r,{cwd:e,encoding:"utf-8",timeout:3e4,stdio:["pipe","pipe","pipe"]}).trim()}function Jo(r){try{return Oe(["rev-parse","--is-inside-work-tree"],r),!0}catch{return!1}}async function sl(r,e,a,t){if(e&&Jo(r))try{Oe(["cat-file","-t",e],r);let i=!1;try{Oe(["status","--porcelain"],r).length>0&&(Oe(["stash","push","-m","mistflow-rollback-auto-stash"],r),i=!0)}catch{}return Oe(["reset","--hard",e],r),Oe(["commit","--allow-empty","-m","Rollback to previous deploy"],r),{method:"git",success:!0,...i?{warning:"Your uncommitted changes were saved to git stash. Run `git stash pop` to restore them."}:{}}}catch{}if(a&&t)try{let{existsSync:i,mkdirSync:s,rmSync:o}=await import("fs"),n=il(r,".mistflow-rollback-source.tar.gz");await dr(t,n);let{spawn:l}=await import("child_process");await new Promise((d,u)=>{let h=l("tar",["-xzf",n,"-C",r,"--exclude","node_modules","--exclude",".git"],{cwd:r,stdio:"pipe"});h.on("close",c=>c===0?d():u(new Error(`tar exit ${c}`))),h.on("error",u)});try{o(n)}catch{}if(Jo(r))try{Oe(["add","-A"],r),Oe(["commit","-m","Rollback to previous deploy (from source snapshot)"],r)}catch{}return{method:"r2",success:!0}}catch(i){return{method:"r2",success:!1,error:i instanceof Error?i.message:"Source download failed"}}return{method:"none",success:!1,error:"No source snapshot available for this deployment"}}function ll(r){return new Promise(e=>setTimeout(e,r))}function dl(r){switch(r){case"pending":return"Provisioning...";case"building":return"Building...";case"deploying":return"Deploying to Mistflow Cloud...";case"verifying":return"Verifying deployment...";default:return`Status: ${r}`}}async function Qo(r,e,a){let t=Date.now(),i=12e4,s=3e3,o=[],n="";for(;Date.now()-t<i;){await ll(s);let l;try{l=await Le(r)}catch{continue}if(l.status!==n&&(o.push(dl(l.status)),n=l.status),l.status==="live"){let d=((Date.now()-t)/1e3).toFixed(0),u=l.url??"";if(a)try{await a()}catch{}return p(JSON.stringify({url:u,time:`${d}s`,deploymentId:r,statusLog:o,message:u?`${e} complete \u2014 live at ${u} in ${d}s.`:`${e} complete in ${d}s.`}))}if(l.status==="failed")return p(l.error??`${e} failed. Check the Mistflow dashboard for details.`,!0)}return p(`${e} is taking longer than expected. Deployment ID: ${r} \u2014 check status later.`,!0)}async function Fa(r){let{projectPath:e,action:a,deploymentId:t}=r,i=ol(e??process.cwd());if(!ue())return p("No Mistflow credentials found. Run mist_setup to connect your account.",!0);let o=Se(i)?.projectId;if(!o)return p("No project ID found. Deploy your project first with mist_deploy, or initialize with mist_plan + mist_build.",!0);try{switch(a){case"redeploy":{let n=await nr(o);return Qo(n.deployment_id,"Redeploy")}case"rollback":{if(!t)return p("Deployment ID is required for rollback. Use mist_project (action: 'deployments') to see previous deployment IDs.",!0);let n=await lr(t);return Qo(n.deployment_id,"Rollback",async()=>{let l=await sl(i,n.git_commit_sha,n.source_artifact_key,t);l.success||console.error(`[rollback] Local source restore failed: ${l.error}`),l.warning&&console.error(`[rollback] ${l.warning}`)})}default:return p(`Unknown action: ${a}. Use redeploy or rollback.`,!0)}}catch(n){if(n instanceof me)return p(n.message,!0);let l=n instanceof Error?n.message:"An unexpected error occurred";return p(l,!0)}}import{resolve as cl}from"path";function pl(r){return new Promise(e=>setTimeout(e,r))}async function Xo(r){let e=cl(r.projectPath??process.cwd());if(!ue())return p("No Mistflow credentials found. Run mist_setup to connect your account.",!0);let t=Se(e)?.projectId;if(!t)return p("No project ID found. Deploy the project first.",!0);let i=r.deploymentId;if(!i)try{let d=(await lt(t)).find(u=>u.environment==="preview"&&u.status==="live");if(!d)return p("No live preview found to promote. Deploy with staging mode first, then call promote after QA passes.",!0);i=d.id}catch{return p("Could not list deployments. Check your connection.",!0)}console.error(`[promote] Promoting preview ${i} to production...`);let s;try{s=(await sr(t,i)).deployment_id}catch(l){let d=l instanceof Error?l.message:"Promote failed";return p(`Promote failed: ${d}`,!0)}let o=Date.now(),n=12e4;for(;Date.now()-o<n;){await pl(3e3);try{let l=await Le(s);if(l.status==="live"){let d=((Date.now()-o)/1e3).toFixed(0),u=l.url??"",h={url:u,time:`${d}s`,deploymentId:s,environment:"production",promoted:!0,message:`Promoted to production at ${u} in ${d}s. Same build that passed QA on preview.`};return h.instruction=["IMPORTANT: The app has been promoted to production. Run a quick QA check to verify.","","1. Call mist_build with action='qa' to test the live production app.","2. If QA passes \u2014 celebrate! Present the URL to the user.","3. If QA finds issues \u2014 these are likely environment differences (auth, DNS). Report them."].join(`
13496
+ `),h.nextAction=`MANDATORY: Call mist_build with action='qa' and url='${u}' to verify production.`,h.qaRequired=!0,h.qaUrl=u,u?Fe(u,JSON.stringify(h)):p(JSON.stringify(h))}if(l.status==="failed")return p(`Promote failed: ${l.error??"Check the Mistflow dashboard for details."}`,!0)}catch{}}return p(`Promote is taking longer than expected. Deployment ID: ${s}`,!0)}import{z as Zo}from"zod";import{spawn as gl,execFileSync as ei}from"child_process";import{existsSync as Jt,readFileSync as ti,writeFileSync as ml,mkdirSync as fl}from"fs";import{join as it,resolve as bl}from"path";import{homedir as ai}from"os";import{createConnection as yl}from"net";import{existsSync as ul,readFileSync as hl}from"fs";function Yt(r){let e=new Set;if(!ul(r))return e;let a=hl(r,"utf-8");for(let t of a.split(`
13497
+ `)){let i=t.trim();if(!i||i.startsWith("#"))continue;let s=i.indexOf("=");if(s>0){let o=i.slice(0,s).trim(),n=i.slice(s+1).trim();n&&n!=='""'&&n!=="''"&&e.add(o)}}return e}var xl=Zo.object({projectPath:Zo.string().optional().describe("Path to the project directory (default: current working directory)")});function ri(){return it(ai(),".mistflow","processes.json")}function oi(){let r=ri();if(!Jt(r))return[];try{return JSON.parse(ti(r,"utf-8"))}catch{return[]}}function ii(r){let e=it(ai(),".mistflow");Jt(e)||fl(e,{recursive:!0}),ml(ri(),JSON.stringify(r,null,2))}function wl(){let r=oi(),e=[];for(let a of r)try{process.kill(a.pid,0),e.push(a)}catch{}ii(e)}function ni(r){return new Promise(e=>{let a=yl({port:r,host:"127.0.0.1"});a.on("connect",()=>{a.destroy(),e(!0)}),a.on("error",()=>{e(!1)})})}async function vl(r,e,a){let t=Date.now();for(;Date.now()-t<e;){if(await ni(r))return!0;await new Promise(i=>setTimeout(i,a))}return!1}function kl(r){return Jt(it(r,"mistflow.json"))}function Sl(r){let e=it(r,"mistflow.json");if(!Jt(e))return[];let a;try{a=JSON.parse(ti(e,"utf-8"))}catch{return[]}let t=a.env?.required;if(!t||typeof t!="object")return[];let i=Object.keys(t);if(i.length===0)return[];let s=Yt(it(r,".env.local"));return i.filter(o=>!s.has(o))}var si={name:"mist_preview",description:"Build the app and start a local production server on localhost:3000 for previewing. Use when the user says 'mist preview'.",inputSchema:xl,handler:async r=>{let a=bl(r.projectPath??process.cwd()),t=3e3;if(!kl(a))return Te(a);let i=Sl(a);if(i.length>0)return p(`Missing required environment variables in .env.local: ${i.join(", ")}. Add them to ${it(a,".env.local")} before previewing.`,!0);wl();let s=await ni(t),o=oi(),n=o.some(h=>h.type==="dev-server"&&h.port===t);if(s&&!n)return p(`Port ${t} is in use. Stop the other process or I will use a different port.`,!0);let l=[...o];if(!s){try{ei("npx",["drizzle-kit","push"],{cwd:a,stdio:["pipe","pipe","pipe"],timeout:3e4})}catch{console.error("[preview] drizzle-kit push failed, continuing...")}try{ei("npm",["run","build"],{cwd:a,stdio:["pipe","pipe","pipe"],timeout:18e4})}catch(b){let f=b instanceof Error&&"stderr"in b?String(b.stderr).slice(-1500):"",m=b instanceof Error&&"stdout"in b?String(b.stdout).slice(-1500):"",y=(f+`
13498
+ `+m).trim();return p(`Build failed. Fix the errors before previewing:
12484
13499
 
12485
- Email: ${T.admin_credentials.email}
12486
- Password: ${T.admin_credentials.password}
12487
- Login: ${L}/login${x}`}}l.length>0&&($.warnings=l);let k=[];if(!oe){let m=JSON.stringify(w).toLowerCase();k.push({action:"Add a custom domain",command:"mist_config resource='domain' action='add' domain='yourdomain.com'",reason:"Give your app a professional URL instead of the default .mistflow.app domain.",priority:"medium"}),Aa(o)&&!Fo(o)&&k.push({action:"Back up to GitHub",command:"Create a repo at github.com/new, then tell me the URL and I'll push your code there",reason:"Your source code is saved with every deploy, but GitHub gives you an extra backup and lets you collaborate.",priority:"medium"});let x=m.includes("user")||m.includes("account")||m.includes("member"),S=m.includes("stripe")||m.includes("payment")||m.includes("billing");x&&!S&&k.push({action:"Add payments",command:"Tell the AI: 'Add Stripe payments to my app'",reason:"Your app has user accounts \u2014 you could monetize with subscriptions or one-time payments.",priority:"low"}),k.push({action:"Share your app",command:"mist_project action='share'",reason:"Generate a shareable link so others can fork and customize your app.",priority:"low"}),k.push({action:"Make changes",command:"Describe what you want to change, then run mist_deploy action='deploy'",reason:"Edit your code and redeploy in seconds. Your app is yours to evolve.",priority:"low"}),k.push({action:"See analytics",command:`Visit ${_a()} to see pageviews and visitors`,reason:"Track how people are using your app.",priority:"low"})}return k.length>0&&($.nextSteps=k),Ee(L,JSON.stringify($))}if(T.status==="failed")return p(T.error??"Deployment failed. Check the Mistflow dashboard for details.",!0)}return p(`Deploy is taking longer than expected. Your deploy ID is ${z} \u2014 check status later.`,!0)}import{z as Kt}from"zod";import{resolve as js,join as zs}from"path";import{execFileSync as $s}from"child_process";var Mp=Kt.object({projectPath:Kt.string().optional().describe("Path to the project directory (default: cwd)"),action:Kt.enum(["redeploy","rollback"]).describe("Action to perform: 'redeploy' re-deploys the latest build, 'rollback' reverts to a specific previous deployment"),deploymentId:Kt.string().optional().describe("Deployment ID to rollback to (required for 'rollback')")});function _e(r,e){return $s("git",r,{cwd:e,encoding:"utf-8",timeout:3e4,stdio:["pipe","pipe","pipe"]}).trim()}function _o(r){try{return _e(["rev-parse","--is-inside-work-tree"],r),!0}catch{return!1}}async function Vs(r,e,t,a){if(e&&_o(r))try{_e(["cat-file","-t",e],r);let i=!1;try{_e(["status","--porcelain"],r).length>0&&(_e(["stash","push","-m","mistflow-rollback-auto-stash"],r),i=!0)}catch{}return _e(["reset","--hard",e],r),_e(["commit","--allow-empty","-m","Rollback to previous deploy"],r),{method:"git",success:!0,...i?{warning:"Your uncommitted changes were saved to git stash. Run `git stash pop` to restore them."}:{}}}catch{}if(t&&a)try{let{existsSync:i,mkdirSync:n,rmSync:o}=await import("fs"),s=zs(r,".mistflow-rollback-source.tar.gz");await sr(a,s);let{spawn:l}=await import("child_process");await new Promise((d,u)=>{let h=l("tar",["-xzf",s,"-C",r,"--exclude","node_modules","--exclude",".git"],{cwd:r,stdio:"pipe"});h.on("close",c=>c===0?d():u(new Error(`tar exit ${c}`))),h.on("error",u)});try{o(s)}catch{}if(_o(r))try{_e(["add","-A"],r),_e(["commit","-m","Rollback to previous deploy (from source snapshot)"],r)}catch{}return{method:"r2",success:!0}}catch(i){return{method:"r2",success:!1,error:i instanceof Error?i.message:"Source download failed"}}return{method:"none",success:!1,error:"No source snapshot available for this deployment"}}function qs(r){return new Promise(e=>setTimeout(e,r))}function Ks(r){switch(r){case"pending":return"Provisioning...";case"building":return"Building...";case"deploying":return"Deploying to Mistflow Cloud...";case"verifying":return"Verifying deployment...";default:return`Status: ${r}`}}async function jo(r,e,t){let a=Date.now(),i=12e4,n=3e3,o=[],s="";for(;Date.now()-a<i;){await qs(n);let l;try{l=await Ue(r)}catch{continue}if(l.status!==s&&(o.push(Ks(l.status)),s=l.status),l.status==="live"){let d=((Date.now()-a)/1e3).toFixed(0),u=l.url??"";if(t)try{await t()}catch{}return p(JSON.stringify({url:u,time:`${d}s`,deploymentId:r,statusLog:o,message:u?`${e} complete \u2014 live at ${u} in ${d}s.`:`${e} complete in ${d}s.`}))}if(l.status==="failed")return p(l.error??`${e} failed. Check the Mistflow dashboard for details.`,!0)}return p(`${e} is taking longer than expected. Deployment ID: ${r} \u2014 check status later.`,!0)}async function Na(r){let{projectPath:e,action:t,deploymentId:a}=r,i=js(e??process.cwd());if(!ce())return p("No Mistflow credentials found. Run mist_setup to connect your account.",!0);let o=De(i)?.projectId;if(!o)return p("No project ID found. Deploy your project first with mist_deploy, or initialize with mist_plan + mist_build.",!0);try{switch(t){case"redeploy":{let s=await or(o);return jo(s.deployment_id,"Redeploy")}case"rollback":{if(!a)return p("Deployment ID is required for rollback. Use mist_project (action: 'deployments') to see previous deployment IDs.",!0);let s=await nr(a);return jo(s.deployment_id,"Rollback",async()=>{let l=await Vs(i,s.git_commit_sha,s.source_artifact_key,a);l.success||console.error(`[rollback] Local source restore failed: ${l.error}`),l.warning&&console.error(`[rollback] ${l.warning}`)})}default:return p(`Unknown action: ${t}. Use redeploy or rollback.`,!0)}}catch(s){if(s instanceof he)return p(s.message,!0);let l=s instanceof Error?s.message:"An unexpected error occurred";return p(l,!0)}}import{resolve as Ys}from"path";function Js(r){return new Promise(e=>setTimeout(e,r))}async function zo(r){let e=Ys(r.projectPath??process.cwd());if(!ce())return p("No Mistflow credentials found. Run mist_setup to connect your account.",!0);let a=De(e)?.projectId;if(!a)return p("No project ID found. Deploy the project first.",!0);let i=r.deploymentId;if(!i)try{let d=(await pt(a)).find(u=>u.environment==="preview"&&u.status==="live");if(!d)return p("No live preview found to promote. Deploy with staging mode first, then call promote after QA passes.",!0);i=d.id}catch{return p("Could not list deployments. Check your connection.",!0)}console.error(`[promote] Promoting preview ${i} to production...`);let n;try{n=(await ir(a,i)).deployment_id}catch(l){let d=l instanceof Error?l.message:"Promote failed";return p(`Promote failed: ${d}`,!0)}let o=Date.now(),s=12e4;for(;Date.now()-o<s;){await Js(3e3);try{let l=await Ue(n);if(l.status==="live"){let d=((Date.now()-o)/1e3).toFixed(0),u=l.url??"",h={url:u,time:`${d}s`,deploymentId:n,environment:"production",promoted:!0,message:`Promoted to production at ${u} in ${d}s. Same build that passed QA on preview.`};return h.instruction=["IMPORTANT: The app has been promoted to production. Run a quick QA check to verify.","","1. Call mist_build with action='qa' to test the live production app.","2. If QA passes \u2014 celebrate! Present the URL to the user.","3. If QA finds issues \u2014 these are likely environment differences (auth, DNS). Report them."].join(`
12488
- `),h.nextAction=`MANDATORY: Call mist_build with action='qa' and url='${u}' to verify production.`,h.qaRequired=!0,h.qaUrl=u,u?Ee(u,JSON.stringify(h)):p(JSON.stringify(h))}if(l.status==="failed")return p(`Promote failed: ${l.error??"Check the Mistflow dashboard for details."}`,!0)}catch{}}return p(`Promote is taking longer than expected. Deployment ID: ${n}`,!0)}import{z as $o}from"zod";import{spawn as Zs,execFileSync as Vo}from"child_process";import{existsSync as Jt,readFileSync as qo,writeFileSync as el,mkdirSync as tl}from"fs";import{join as dt,resolve as al}from"path";import{homedir as Ko}from"os";import{createConnection as rl}from"net";import{existsSync as Qs,readFileSync as Xs}from"fs";function Yt(r){let e=new Set;if(!Qs(r))return e;let t=Xs(r,"utf-8");for(let a of t.split(`
12489
- `)){let i=a.trim();if(!i||i.startsWith("#"))continue;let n=i.indexOf("=");if(n>0){let o=i.slice(0,n).trim(),s=i.slice(n+1).trim();s&&s!=='""'&&s!=="''"&&e.add(o)}}return e}var ol=$o.object({projectPath:$o.string().optional().describe("Path to the project directory (default: current working directory)")});function Yo(){return dt(Ko(),".mistflow","processes.json")}function Jo(){let r=Yo();if(!Jt(r))return[];try{return JSON.parse(qo(r,"utf-8"))}catch{return[]}}function Qo(r){let e=dt(Ko(),".mistflow");Jt(e)||tl(e,{recursive:!0}),el(Yo(),JSON.stringify(r,null,2))}function il(){let r=Jo(),e=[];for(let t of r)try{process.kill(t.pid,0),e.push(t)}catch{}Qo(e)}function Xo(r){return new Promise(e=>{let t=rl({port:r,host:"127.0.0.1"});t.on("connect",()=>{t.destroy(),e(!0)}),t.on("error",()=>{e(!1)})})}async function nl(r,e,t){let a=Date.now();for(;Date.now()-a<e;){if(await Xo(r))return!0;await new Promise(i=>setTimeout(i,t))}return!1}function sl(r){return Jt(dt(r,"mistflow.json"))}function ll(r){let e=dt(r,"mistflow.json");if(!Jt(e))return[];let t;try{t=JSON.parse(qo(e,"utf-8"))}catch{return[]}let a=t.env?.required;if(!a||typeof a!="object")return[];let i=Object.keys(a);if(i.length===0)return[];let n=Yt(dt(r,".env.local"));return i.filter(o=>!n.has(o))}var Zo={name:"mist_preview",description:"Build the app and start a local production server on localhost:3000 for previewing. Use when the user says 'mist preview'.",inputSchema:ol,handler:async r=>{let t=al(r.projectPath??process.cwd()),a=3e3;if(!sl(t))return Te(t);let i=ll(t);if(i.length>0)return p(`Missing required environment variables in .env.local: ${i.join(", ")}. Add them to ${dt(t,".env.local")} before previewing.`,!0);il();let n=await Xo(a),o=Jo(),s=o.some(h=>h.type==="dev-server"&&h.port===a);if(n&&!s)return p(`Port ${a} is in use. Stop the other process or I will use a different port.`,!0);let l=[...o];if(!n){try{Vo("npx",["drizzle-kit","push"],{cwd:t,stdio:["pipe","pipe","pipe"],timeout:3e4})}catch{console.error("[preview] drizzle-kit push failed, continuing...")}try{Vo("npm",["run","build"],{cwd:t,stdio:["pipe","pipe","pipe"],timeout:18e4})}catch(b){let y=b instanceof Error&&"stderr"in b?String(b.stderr).slice(-1500):"",f=b instanceof Error&&"stdout"in b?String(b.stdout).slice(-1500):"",v=(y+`
12490
- `+f).trim();return p(`Build failed. Fix the errors before previewing:
13500
+ ${y}`,!0)}let h=gl("npx",["next","start","-p",String(t)],{cwd:a,detached:!0,stdio:["ignore","pipe","pipe"]}),c="";if(h.stderr?.on("data",b=>{c+=b.toString()}),h.pid&&(h.unref(),l.push({pid:h.pid,type:"dev-server",port:t,startedAt:new Date().toISOString()}),ii(l)),!await vl(t,15e3,500)){let b=c?`Server failed to start:
12491
13501
 
12492
- ${v}`,!0)}let h=Zs("npx",["next","start","-p",String(a)],{cwd:t,detached:!0,stdio:["ignore","pipe","pipe"]}),c="";if(h.stderr?.on("data",b=>{c+=b.toString()}),h.pid&&(h.unref(),l.push({pid:h.pid,type:"dev-server",port:a,startedAt:new Date().toISOString()}),Qo(l)),!await nl(a,15e3,500)){let b=c?`Server failed to start:
13502
+ ${c.slice(-1e3)}`:"Server failed to start within 15s. Check for runtime errors.";return p(b,!0)}}let d=`http://localhost:${t}`,u=JSON.stringify({localUrl:d,message:`Preview is live at ${d}. Run mist_deploy to get your permanent URL on mistflow.app.`});return Fe(d,u)}};import{resolve as Tl,join as Cl}from"path";import{existsSync as Pl,readFileSync as Bl}from"fs";var Ea="test@mistflow.dev",li="MistflowTest123!",Al="Test User";function di(r){let e=Cl(r,"mistflow.json");if(!Pl(e))return null;try{return JSON.parse(Bl(e,"utf-8"))}catch{return null}}function Dl(r){let e=JSON.stringify(r).toLowerCase();return["auth","login","sign-in","sign-up","signin","signup"].some(a=>e.includes(a))}function Il(r){let e=r.plan;if(!e?.steps)return[];let a=new Set;for(let i of e.steps)if(i.pages)for(let s of i.pages)a.add(s);let t=new Set(["login","signup","sign-in","sign-up","signin","signout","sign-out","logout"]);return[...a].filter(i=>!t.has(i.toLowerCase())).slice(0,3)}function Ml(r){let e=r.deploy;return e?.url?e.url:typeof r.deployUrl=="string"?r.deployUrl:null}async function ci(r){let e=Tl(r.projectPath??process.cwd()),a=r.verifyUrl,t=null;if(a)t=di(e);else{if(t=di(e),!t)return p("No mistflow.json found. Run mist_deploy first, or pass verifyUrl explicitly.",!0);if(a=Ml(t)??void 0,!a)return p("No deploy URL found in mistflow.json. Deploy your app first with mist_deploy.",!0)}a.startsWith("http")||(a=`https://${a}`);let i=t?Dl(t):!1,s=t?Il(t):[],o;try{o=await Tt()}catch{return p("Browser not available. Install Playwright to use verify: npx playwright install chromium",!0)}let n={verified:!0,url:a,authTested:!1,testUser:null,pages:[],message:""},l=[];try{await o.goto(a,{waitUntil:"domcontentloaded",timeout:3e4}),await o.waitForLoadState("networkidle").catch(()=>{})}catch{return n.verified=!1,n.message=`Could not load ${a}. The app may not be deployed yet or the URL may be incorrect.`,p(JSON.stringify(n),!0)}let d=await o.title(),u=d.toLowerCase().includes("error")||d.toLowerCase().includes("404")?"error":"ok";n.pages.push({path:"/",status:u,title:d});try{let f=await Ne(o,!1);l.push({data:f.toString("base64"),mimeType:"image/png"})}catch{}if(u==="error"&&(n.verified=!1),i)try{(await o.evaluate(async m=>{let y=await fetch("/api/auth/sign-up/email",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(m)});return{status:y.status,ok:y.ok}},{email:Ea,password:li,name:Al})).ok||await o.evaluate(async m=>{let y=await fetch("/api/auth/sign-in/email",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(m)});return{status:y.status,ok:y.ok}},{email:Ea,password:li}),await o.reload({waitUntil:"domcontentloaded",timeout:15e3}),await o.waitForLoadState("networkidle").catch(()=>{}),n.authTested=!0,n.testUser={email:Ea};try{let m=await Ne(o,!1);l.push({data:m.toString("base64"),mimeType:"image/png"})}catch{}}catch{console.error("[verify] Auth test failed, continuing with public pages")}for(let f of s){let m=f.startsWith("/")?f:`/${f}`;try{await o.goto(`${a}${m}`,{waitUntil:"domcontentloaded",timeout:15e3}),await o.waitForLoadState("networkidle").catch(()=>{});let y=await o.title(),T=y.toLowerCase().includes("404")||y.toLowerCase().includes("not found")?"error":"ok";n.pages.push({path:m,status:T,title:y}),T==="error"&&(n.verified=!1);try{let k=await Ne(o,!1);l.push({data:k.toString("base64"),mimeType:"image/png"})}catch{}}catch{n.pages.push({path:m,status:"error",title:"Timeout"}),n.verified=!1}}let h=n.pages.filter(f=>f.status==="ok").length,c=n.pages.length,g=n.authTested?" Auth login successful.":"";n.message=n.verified?`Verified ${h}/${c} pages.${g} App looks good.`:`Verified ${h}/${c} pages \u2014 some issues found.${g} Check screenshots.`;let b=[{type:"text",text:JSON.stringify(n)}];for(let f of l)b.push({type:"image",data:f.data,mimeType:f.mimeType});return{content:b}}var Rl=qe.object({action:qe.enum(["deploy","promote","preview","redeploy","rollback","verify"]).describe("'deploy' builds and deploys to Mistflow Cloud (auto-redirects to preview if staging mode is enabled). 'promote' promotes a verified preview to production (~10s, reuses the preview build). 'preview' builds the app and starts a local production server on localhost:3000. 'redeploy' re-deploys the latest build without rebuilding. 'rollback' reverts to a specific previous deployment. 'verify' navigates the deployed app, tests auth, and screenshots pages to confirm it works."),projectPath:qe.string().optional().describe("Path to the project directory (default: cwd)"),environment:qe.enum(["production","preview"]).optional().describe("(deploy) Target environment. Defaults to 'production'. Projects with staging mode auto-redirect to preview."),forceSchema:qe.boolean().optional().describe("(deploy) Reset the database structure. Warning: this deletes existing data. Only use when the user confirms this is OK."),deploymentId:qe.string().optional().describe("(rollback/promote) Deployment ID to rollback to, or preview deployment ID to promote"),verifyUrl:qe.string().optional().describe("(verify) URL to verify. Defaults to deploy URL from mistflow.json")}),pi={name:"mist_deploy",description:"STEP 4 (final) of the Mistflow workflow. Deploy a Mistflow project. Call this AFTER all mist_build implement steps are done. If the project has staging mode enabled (configurable in dashboard), the tool automatically deploys to preview first with an isolated database. After QA passes, call action='promote' to go live (~10s, reuses the same build artifact). Actions: 'deploy' (default), 'promote' (promote preview to production), 'preview' (local dev server), 'redeploy', 'rollback', 'verify'. The workflow is: mist_plan \u2192 mist_build init \u2192 implement (repeat) \u2192 mist_build build \u2192 mist_deploy \u2192 mist_build qa.",inputSchema:Rl,handler:async(r,e)=>{let a=r;switch(a.action){case"deploy":return Yo({projectPath:a.projectPath,environment:a.environment??"production",forceSchema:a.forceSchema},e);case"promote":return Xo({projectPath:a.projectPath,deploymentId:a.deploymentId});case"preview":return si.handler({projectPath:a.projectPath});case"redeploy":return Fa({projectPath:a.projectPath,action:"redeploy"});case"rollback":return Fa({projectPath:a.projectPath,action:"rollback",deploymentId:a.deploymentId});case"verify":return ci({projectPath:a.projectPath,verifyUrl:a.verifyUrl});default:return p(`Unknown action: ${a.action}. Use deploy, promote, preview, redeploy, rollback, or verify.`,!0)}}};import{z as ce}from"zod";import{resolve as Qt}from"path";import{existsSync as Xt,readFileSync as Zt}from"fs";import{join as ea}from"path";import{z as Ge}from"zod";import{resolve as Nl,join as ui}from"path";import{existsSync as Ll,readFileSync as hi,writeFileSync as Fl}from"fs";var El=Ge.object({action:Ge.enum(["get","update"]).default("get").describe("'get' reads current project state. 'update' modifies it."),projectPath:Ge.string().optional().describe("Path to the project directory (default: current working directory)"),completedStep:Ge.number().optional().describe("(update only) Mark a plan step as completed by step number"),addEnvVar:Ge.object({key:Ge.string(),description:Ge.string().optional(),setupUrl:Ge.string().optional()}).optional().describe("(update only) Add a required env var to the project manifest")}),gi={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. Use when the user says 'mist status', 'mist state', or 'mist update state'.",inputSchema:El,handler:async r=>{let e=r,a=Nl(e.projectPath??process.cwd()),t=ui(a,"mistflow.json");if(!Ll(t))return Te(a);let i;try{i=JSON.parse(hi(t,"utf-8"))}catch{return p("Failed to parse mistflow.json.",!0)}if(e.action==="get"){if(!i.projectId)try{let{ensureBackendRegistered:m}=await import("./self-heal-OGLEOZC4.js");await m(a)&&(i=JSON.parse(hi(t,"utf-8")))}catch{}let n=i.plan,l=n?.steps?.filter(m=>m.status==="completed").length??0,d=n?.steps?.length??0,u=Yt(ui(a,".env.local")),h=i.env?.required?Object.entries(i.env.required).map(([m,y])=>({name:m,description:y?.description,configured:u.has(m)})):[];i.projectId&&import("./state-manager-6NJO66MS.js").then(({fetchRemoteState:m})=>m(i.projectId)).catch(()=>{});let c=[`Project: ${i.name}`];if(n){c.push(`Plan: ${n.summary??n.name??"unnamed"} \u2014 ${l}/${d} steps complete`);for(let m of n.steps){let y=m.status==="completed"?"\u2713":m.status==="in_progress"?"\u2192":" ";c.push(` [${y}] ${m.number}. ${m.name}`)}}let g=h.filter(m=>!m.configured);g.length>0&&c.push(`Missing env vars: ${g.map(m=>m.name).join(", ")}`),i.deploy?.url?c.push(`Deployed: ${i.deploy.url} (${i.deploy.count??0} deploys)`):c.push("Not deployed yet");let b=[],f=n?.steps?.find(m=>m.status!=="completed");return f?b.push(`NEXT: Call mist_build with action='implement' to work on step ${f.number} (${f.name}).`):n&&l===d&&(i.deploy?.url||b.push("NEXT: All steps complete! Call mist_deploy with action='deploy' to deploy the app now. Do NOT ask the user \u2014 just deploy.")),g.length>0&&b.push(`Missing env vars in .env.local: ${g.map(m=>m.name).join(", ")}`),p(JSON.stringify({name:i.name,projectId:i.projectId,planProgress:n?{name:n.name,summary:n.summary,totalSteps:d,completedSteps:l,steps:n.steps}:null,envStatus:h,deploy:i.deploy??null,contextMessage:c.join(`
13503
+ `),nextSteps:b}))}let s=[];if(e.completedStep!==void 0){let n=i.plan;if(n?.steps){let l=n.steps.findIndex(d=>d.number===e.completedStep);if(l===-1)return p(`Step ${e.completedStep} not found in the plan.`,!0);n.steps[l].status="completed",s.push(`Step ${e.completedStep} marked as completed`)}}e.addEnvVar&&(i.env||(i.env={required:{}}),i.env.required||(i.env.required={}),i.env.required[e.addEnvVar.key]={description:e.addEnvVar.description,setupUrl:e.addEnvVar.setupUrl},s.push(`Added required env var: ${e.addEnvVar.key}`)),Fl(t,JSON.stringify(i,null,2)+`
13504
+ `),i.projectId&&import("./state-manager-6NJO66MS.js").then(async({readLocalState:n,syncRemoteState:l})=>{let d=n(a);d&&await l(i.projectId,d)}).catch(()=>{});let o=[];if(e.completedStep!==void 0){let l=i.plan?.steps?.find(d=>d.status!=="completed");l?o.push(`NEXT: Call mist_build with action='implement' to work on step ${l.number} (${l.name}). Do this now.`):o.push("NEXT: All steps complete! Call mist_deploy with action='deploy' to deploy the app now. Do NOT suggest localhost.")}return e.addEnvVar&&(o.push(`Add ${e.addEnvVar.key} to your .env.local file`),e.addEnvVar.setupUrl&&o.push(`Get the value from: ${e.addEnvVar.setupUrl}`)),p(JSON.stringify({updated:!0,changes:s,message:s.length>0?`Project state saved. ${s.join(". ")}.`:"No changes made.",nextSteps:o.length>0?o:void 0}))}};var Ul=ce.object({action:ce.enum(["get","update","share","landing-designs","app-styles","integrations","errors","logs","deployments","version"]).default("get").describe("'get' reads current project state. 'update' marks steps complete or adds env vars. 'share' makes the project a shareable template. 'landing-designs' lists curated landing page hero designs. 'app-styles' lists 54 full-app styles (Stripe, Linear, Vercel, etc.) for consistent brand-quality UI across all pages. '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. 'version' reports the installed @mistflow-ai/mcp version and whether an upgrade is available."),projectPath:ce.string().optional().describe("Path to the project directory (default: cwd)"),completedStep:ce.number().optional().describe("(update) Mark a plan step as completed by step number"),addEnvVar:ce.object({key:ce.string(),description:ce.string().optional(),setupUrl:ce.string().optional()}).optional().describe("(update) Add a required env var to the project manifest"),templateDescription:ce.string().optional().describe("(share) Short description of what this template builds"),category:ce.string().optional().describe("(landing-designs / app-styles) Filter by category"),presetId:ce.string().optional().describe("(landing-designs) Get full details for a specific landing design by ID"),appStyleId:ce.string().optional().describe("(app-styles) Get full details for a specific app style by ID (e.g. 'stripe', 'linear')"),integrationId:ce.string().optional().describe("(integrations) Get full details for a specific integration preset by ID (e.g. 'stripe-payments', 'resend-email', 'elevenlabs-voice')"),period:ce.string().optional().describe("(errors) Time period for errors: '1h', '24h', '7d' (default: '7d')"),deploymentId:ce.string().optional().describe("(logs) Deployment ID to fetch logs for. If omitted, fetches logs for the latest deployment.")}),mi={name:"mist_project",description:"Read or update Mistflow project state. 'get' loads plan progress, env vars, and deploy info. 'update' marks plan steps complete or adds env vars (note: mist_build implement auto-marks the previous step, so manual updates are rarely needed). 'share' makes the project a forkable template with a shareable URL. 'landing-designs' lists curated landing page hero designs \u2014 pass an ID to mist_plan's landingDesign field to apply it. 'app-styles' lists 54 full-app styles from top companies (Stripe, Linear, Vercel, Notion, etc.) \u2014 pass an ID to mist_plan's appStyle field for consistent brand-quality design across ALL pages. 'integrations' lists third-party service integration blueprints (Stripe, Resend, ElevenLabs, OpenAI, Twilio, etc.) \u2014 these are auto-injected during implementation when the plan includes matching integration steps. 'errors' fetches runtime errors from the live deployed app (same data shown on the dashboard). 'logs' fetches deploy logs for a deployment (phase-by-phase progress with error details). 'deployments' lists deployment history with status and error messages.",inputSchema:Ul,handler:async r=>{let e=r;if(["share","errors","logs","deployments"].includes(e.action)&&!ue())return p("You need to sign in first. Run mist_setup to connect your account.",!0);switch(e.action){case"get":case"update":return gi.handler({action:e.action,projectPath:e.projectPath,completedStep:e.completedStep,addEnvVar:e.addEnvVar});case"share":{let t=Qt(e.projectPath??process.cwd()),i=ea(t,"mistflow.json");if(!Xt(i))return Te(t);let s;try{s=JSON.parse(Zt(i,"utf-8"))}catch{return p("Could not read mistflow.json.",!0)}let o=s.projectId;if(!o)return p("No project ID found. Deploy the project first to register it.",!0);try{let n=await mr(o,{isTemplate:!0,description:e.templateDescription});return p(JSON.stringify({shareUrl:n.share_url,shareToken:n.share_token,message:`Your project is now a shareable template!
12493
13505
 
12494
- ${c.slice(-1e3)}`:"Server failed to start within 15s. Check for runtime errors.";return p(b,!0)}}let d=`http://localhost:${a}`,u=JSON.stringify({localUrl:d,message:`Preview is live at ${d}. Run mist_deploy to get your permanent URL on mistflow.app.`});return Ee(d,u)}};import{resolve as dl,join as cl}from"path";import{existsSync as pl,readFileSync as ul}from"fs";var Fa="test@mistflow.dev",ei="MistflowTest123!",hl="Test User";function ti(r){let e=cl(r,"mistflow.json");if(!pl(e))return null;try{return JSON.parse(ul(e,"utf-8"))}catch{return null}}function gl(r){let e=JSON.stringify(r).toLowerCase();return["auth","login","sign-in","sign-up","signin","signup"].some(t=>e.includes(t))}function ml(r){let e=r.plan;if(!e?.steps)return[];let t=new Set;for(let i of e.steps)if(i.pages)for(let n of i.pages)t.add(n);let a=new Set(["login","signup","sign-in","sign-up","signin","signout","sign-out","logout"]);return[...t].filter(i=>!a.has(i.toLowerCase())).slice(0,3)}function fl(r){let e=r.deploy;return e?.url?e.url:typeof r.deployUrl=="string"?r.deployUrl:null}async function ai(r){let e=dl(r.projectPath??process.cwd()),t=r.verifyUrl,a=null;if(t)a=ti(e);else{if(a=ti(e),!a)return p("No mistflow.json found. Run mist_deploy first, or pass verifyUrl explicitly.",!0);if(t=fl(a)??void 0,!t)return p("No deploy URL found in mistflow.json. Deploy your app first with mist_deploy.",!0)}t.startsWith("http")||(t=`https://${t}`);let i=a?gl(a):!1,n=a?ml(a):[],o;try{o=await Ct()}catch{return p("Browser not available. Install Playwright to use verify: npx playwright install chromium",!0)}let s={verified:!0,url:t,authTested:!1,testUser:null,pages:[],message:""},l=[];try{await o.goto(t,{waitUntil:"domcontentloaded",timeout:3e4}),await o.waitForLoadState("networkidle").catch(()=>{})}catch{return s.verified=!1,s.message=`Could not load ${t}. The app may not be deployed yet or the URL may be incorrect.`,p(JSON.stringify(s),!0)}let d=await o.title(),u=d.toLowerCase().includes("error")||d.toLowerCase().includes("404")?"error":"ok";s.pages.push({path:"/",status:u,title:d});try{let y=await Fe(o,!1);l.push({data:y.toString("base64"),mimeType:"image/png"})}catch{}if(u==="error"&&(s.verified=!1),i)try{(await o.evaluate(async f=>{let v=await fetch("/api/auth/sign-up/email",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(f)});return{status:v.status,ok:v.ok}},{email:Fa,password:ei,name:hl})).ok||await o.evaluate(async f=>{let v=await fetch("/api/auth/sign-in/email",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(f)});return{status:v.status,ok:v.ok}},{email:Fa,password:ei}),await o.reload({waitUntil:"domcontentloaded",timeout:15e3}),await o.waitForLoadState("networkidle").catch(()=>{}),s.authTested=!0,s.testUser={email:Fa};try{let f=await Fe(o,!1);l.push({data:f.toString("base64"),mimeType:"image/png"})}catch{}}catch{console.error("[verify] Auth test failed, continuing with public pages")}for(let y of n){let f=y.startsWith("/")?y:`/${y}`;try{await o.goto(`${t}${f}`,{waitUntil:"domcontentloaded",timeout:15e3}),await o.waitForLoadState("networkidle").catch(()=>{});let v=await o.title(),C=v.toLowerCase().includes("404")||v.toLowerCase().includes("not found")?"error":"ok";s.pages.push({path:f,status:C,title:v}),C==="error"&&(s.verified=!1);try{let P=await Fe(o,!1);l.push({data:P.toString("base64"),mimeType:"image/png"})}catch{}}catch{s.pages.push({path:f,status:"error",title:"Timeout"}),s.verified=!1}}let h=s.pages.filter(y=>y.status==="ok").length,c=s.pages.length,g=s.authTested?" Auth login successful.":"";s.message=s.verified?`Verified ${h}/${c} pages.${g} App looks good.`:`Verified ${h}/${c} pages \u2014 some issues found.${g} Check screenshots.`;let b=[{type:"text",text:JSON.stringify(s)}];for(let y of l)b.push({type:"image",data:y.data,mimeType:y.mimeType});return{content:b}}var bl=Je.object({action:Je.enum(["deploy","promote","preview","redeploy","rollback","verify"]).describe("'deploy' builds and deploys to Mistflow Cloud (auto-redirects to preview if staging mode is enabled). 'promote' promotes a verified preview to production (~10s, reuses the preview build). 'preview' builds the app and starts a local production server on localhost:3000. 'redeploy' re-deploys the latest build without rebuilding. 'rollback' reverts to a specific previous deployment. 'verify' navigates the deployed app, tests auth, and screenshots pages to confirm it works."),projectPath:Je.string().optional().describe("Path to the project directory (default: cwd)"),environment:Je.enum(["production","preview"]).optional().describe("(deploy) Target environment. Defaults to 'production'. Projects with staging mode auto-redirect to preview."),forceSchema:Je.boolean().optional().describe("(deploy) Reset the database structure. Warning: this deletes existing data. Only use when the user confirms this is OK."),deploymentId:Je.string().optional().describe("(rollback/promote) Deployment ID to rollback to, or preview deployment ID to promote"),verifyUrl:Je.string().optional().describe("(verify) URL to verify. Defaults to deploy URL from mistflow.json")}),ri={name:"mist_deploy",description:"STEP 4 (final) of the Mistflow workflow. Deploy a Mistflow project. Call this AFTER all mist_build implement steps are done. If the project has staging mode enabled (configurable in dashboard), the tool automatically deploys to preview first with an isolated database. After QA passes, call action='promote' to go live (~10s, reuses the same build artifact). Actions: 'deploy' (default), 'promote' (promote preview to production), 'preview' (local dev server), 'redeploy', 'rollback', 'verify'. The workflow is: mist_plan \u2192 mist_build init \u2192 implement (repeat) \u2192 mist_build build \u2192 mist_deploy \u2192 mist_build qa.",inputSchema:bl,handler:async r=>{let e=r;switch(e.action){case"deploy":return Wo({projectPath:e.projectPath,environment:e.environment??"production",forceSchema:e.forceSchema});case"promote":return zo({projectPath:e.projectPath,deploymentId:e.deploymentId});case"preview":return Zo.handler({projectPath:e.projectPath});case"redeploy":return Na({projectPath:e.projectPath,action:"redeploy"});case"rollback":return Na({projectPath:e.projectPath,action:"rollback",deploymentId:e.deploymentId});case"verify":return ai({projectPath:e.projectPath,verifyUrl:e.verifyUrl});default:return p(`Unknown action: ${e.action}. Use deploy, promote, preview, redeploy, rollback, or verify.`,!0)}}};import{z as se}from"zod";import{resolve as Qt}from"path";import{existsSync as Xt,readFileSync as Zt}from"fs";import{join as ea}from"path";import{z as je}from"zod";import{resolve as yl,join as oi}from"path";import{existsSync as xl,readFileSync as wl,writeFileSync as vl}from"fs";var kl=je.object({action:je.enum(["get","update"]).default("get").describe("'get' reads current project state. 'update' modifies it."),projectPath:je.string().optional().describe("Path to the project directory (default: current working directory)"),completedStep:je.number().optional().describe("(update only) Mark a plan step as completed by step number"),addEnvVar:je.object({key:je.string(),description:je.string().optional(),setupUrl:je.string().optional()}).optional().describe("(update only) Add a required env var to the project manifest")}),ii={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. Use when the user says 'mist status', 'mist state', or 'mist update state'.",inputSchema:kl,handler:async r=>{let e=r,t=yl(e.projectPath??process.cwd()),a=oi(t,"mistflow.json");if(!xl(a))return Te(t);let i;try{i=JSON.parse(wl(a,"utf-8"))}catch{return p("Failed to parse mistflow.json.",!0)}if(e.action==="get"){let s=i.plan,l=s?.steps?.filter(f=>f.status==="completed").length??0,d=s?.steps?.length??0,u=Yt(oi(t,".env.local")),h=i.env?.required?Object.entries(i.env.required).map(([f,v])=>({name:f,description:v?.description,configured:u.has(f)})):[];i.projectId&&import("./state-manager-XY6GQEFE.js").then(({fetchRemoteState:f})=>f(i.projectId)).catch(()=>{});let c=[`Project: ${i.name}`];if(s){c.push(`Plan: ${s.summary??s.name??"unnamed"} \u2014 ${l}/${d} steps complete`);for(let f of s.steps){let v=f.status==="completed"?"\u2713":f.status==="in_progress"?"\u2192":" ";c.push(` [${v}] ${f.number}. ${f.name}`)}}let g=h.filter(f=>!f.configured);g.length>0&&c.push(`Missing env vars: ${g.map(f=>f.name).join(", ")}`),i.deploy?.url?c.push(`Deployed: ${i.deploy.url} (${i.deploy.count??0} deploys)`):c.push("Not deployed yet");let b=[],y=s?.steps?.find(f=>f.status!=="completed");return y?b.push(`NEXT: Call mist_build with action='implement' to work on step ${y.number} (${y.name}).`):s&&l===d&&(i.deploy?.url||b.push("NEXT: All steps complete! Call mist_deploy with action='deploy' to deploy the app now.")),g.length>0&&b.push(`Missing env vars in .env.local: ${g.map(f=>f.name).join(", ")}`),p(JSON.stringify({name:i.name,projectId:i.projectId,planProgress:s?{name:s.name,summary:s.summary,totalSteps:d,completedSteps:l,steps:s.steps}:null,envStatus:h,deploy:i.deploy??null,contextMessage:c.join(`
12495
- `),nextSteps:b}))}let n=[];if(e.completedStep!==void 0){let s=i.plan;if(s?.steps){let l=s.steps.findIndex(d=>d.number===e.completedStep);if(l===-1)return p(`Step ${e.completedStep} not found in the plan.`,!0);s.steps[l].status="completed",n.push(`Step ${e.completedStep} marked as completed`)}}e.addEnvVar&&(i.env||(i.env={required:{}}),i.env.required||(i.env.required={}),i.env.required[e.addEnvVar.key]={description:e.addEnvVar.description,setupUrl:e.addEnvVar.setupUrl},n.push(`Added required env var: ${e.addEnvVar.key}`)),vl(a,JSON.stringify(i,null,2)+`
12496
- `),i.projectId&&import("./state-manager-XY6GQEFE.js").then(async({readLocalState:s,syncRemoteState:l})=>{let d=s(t);d&&await l(i.projectId,d)}).catch(()=>{});let o=[];if(e.completedStep!==void 0){let l=i.plan?.steps?.find(d=>d.status!=="completed");l?o.push(`NEXT: Call mist_build with action='implement' to work on step ${l.number} (${l.name}). Do this now.`):o.push("NEXT: All steps complete! Call mist_deploy with action='deploy' to deploy the app now. Do NOT suggest localhost.")}return e.addEnvVar&&(o.push(`Add ${e.addEnvVar.key} to your .env.local file`),e.addEnvVar.setupUrl&&o.push(`Get the value from: ${e.addEnvVar.setupUrl}`)),p(JSON.stringify({updated:!0,changes:n,message:n.length>0?`Project state saved. ${n.join(". ")}.`:"No changes made.",nextSteps:o.length>0?o:void 0}))}};var Sl=se.object({action:se.enum(["get","update","share","landing-designs","app-styles","integrations","errors","logs","deployments","version"]).default("get").describe("'get' reads current project state. 'update' marks steps complete or adds env vars. 'share' makes the project a shareable template. 'landing-designs' lists curated landing page hero designs. 'app-styles' lists 54 full-app styles (Stripe, Linear, Vercel, etc.) for consistent brand-quality UI across all pages. '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. 'version' reports the installed @mistflow-ai/mcp version and whether an upgrade is available."),projectPath:se.string().optional().describe("Path to the project directory (default: cwd)"),completedStep:se.number().optional().describe("(update) Mark a plan step as completed by step number"),addEnvVar:se.object({key:se.string(),description:se.string().optional(),setupUrl:se.string().optional()}).optional().describe("(update) Add a required env var to the project manifest"),templateDescription:se.string().optional().describe("(share) Short description of what this template builds"),category:se.string().optional().describe("(landing-designs / app-styles) Filter by category"),presetId:se.string().optional().describe("(landing-designs) Get full details for a specific landing design by ID"),appStyleId:se.string().optional().describe("(app-styles) Get full details for a specific app style by ID (e.g. 'stripe', 'linear')"),integrationId:se.string().optional().describe("(integrations) Get full details for a specific integration preset by ID (e.g. 'stripe-payments', 'resend-email', 'elevenlabs-voice')"),period:se.string().optional().describe("(errors) Time period for errors: '1h', '24h', '7d' (default: '7d')"),deploymentId:se.string().optional().describe("(logs) Deployment ID to fetch logs for. If omitted, fetches logs for the latest deployment.")}),ni={name:"mist_project",description:"Read or update Mistflow project state. 'get' loads plan progress, env vars, and deploy info. 'update' marks plan steps complete or adds env vars (note: mist_build implement auto-marks the previous step, so manual updates are rarely needed). 'share' makes the project a forkable template with a shareable URL. 'landing-designs' lists curated landing page hero designs \u2014 pass an ID to mist_plan's landingDesign field to apply it. 'app-styles' lists 54 full-app styles from top companies (Stripe, Linear, Vercel, Notion, etc.) \u2014 pass an ID to mist_plan's appStyle field for consistent brand-quality design across ALL pages. 'integrations' lists third-party service integration blueprints (Stripe, Resend, ElevenLabs, OpenAI, Twilio, etc.) \u2014 these are auto-injected during implementation when the plan includes matching integration steps. 'errors' fetches runtime errors from the live deployed app (same data shown on the dashboard). 'logs' fetches deploy logs for a deployment (phase-by-phase progress with error details). 'deployments' lists deployment history with status and error messages.",inputSchema:Sl,handler:async r=>{let e=r;if(["share","errors","logs","deployments"].includes(e.action)&&!ce())return p("You need to sign in first. Run mist_setup to connect your account.",!0);switch(e.action){case"get":case"update":return ii.handler({action:e.action,projectPath:e.projectPath,completedStep:e.completedStep,addEnvVar:e.addEnvVar});case"share":{let a=Qt(e.projectPath??process.cwd()),i=ea(a,"mistflow.json");if(!Xt(i))return Te(a);let n;try{n=JSON.parse(Zt(i,"utf-8"))}catch{return p("Could not read mistflow.json.",!0)}let o=n.projectId;if(!o)return p("No project ID found. Deploy the project first to register it.",!0);try{let s=await hr(o,{isTemplate:!0,description:e.templateDescription});return p(JSON.stringify({shareUrl:s.share_url,shareToken:s.share_token,message:`Your project is now a shareable template!
12497
-
12498
- Anyone can fork it: ${s.share_url}
13506
+ Anyone can fork it: ${n.share_url}
12499
13507
 
12500
13508
  Others can use it in their AI editor:
12501
- "build me something like ${s.share_url}"`}))}catch(s){let l=s instanceof Error?s.message:"Failed to share project";return p(l,!0)}}case"landing-designs":{if(e.presetId){let o=et(e.presetId);if(!o)return p(`Preset '${e.presetId}' not found. Use mist_project action='presets' without presetId to list all available presets.`,!0);let s=xr(e.presetId);return p(JSON.stringify({preset:{id:o.id,title:o.title,category:o.category,description:s?.description??"",style:s?.style??"",theme:s?.theme??"dark",colors:s?.colors??[],tags:s?.tags??[],promptLength:o.prompt.length},message:`Landing design "${o.title}" (${o.category}) \u2014 ${s?.description??""}. To use it, pass landingDesign="${o.id}" when calling mist_plan.`}))}let a=vr(e.category??void 0),i=da(e.category),n=wr(i);return p(JSON.stringify({count:a.length,presets:a.map(o=>({id:o.id,title:o.title,category:o.category,description:o.description,style:o.style,theme:o.theme,colors:o.colors})),formatted:n,message:`${a.length} landing designs available.${e.category?` Filtered by: ${e.category}.`:""} To use one, pass landingDesign="<id>" when calling mist_plan. The design blueprint will be injected during the landing page implementation step. Browse them at ${Ae()}/designs?tab=landing-designs.`}))}case"app-styles":{if(e.appStyleId){let o=fe(e.appStyleId);if(!o)return p(`App style '${e.appStyleId}' not found. Use mist_project action='app-styles' without appStyleId to list all available app styles.`,!0);let s=Ie(o.id);return p(JSON.stringify({appStyle:{id:o.id,name:o.name,category:s?.category??o.category,description:s?.description??"",theme:s?.theme??"light",colors:s?.colors??[],fonts:s?.fonts??{heading:"Inter",body:"Inter"},tags:s?.tags??[],sectionCount:o.sections.length,sections:o.sections.map(l=>l.title)},message:`App style "${o.name}" (${o.category}) \u2014 ${s?.description??""}. To use it, pass appStyle="${o.id}" when calling mist_plan. The style's component specs, typography, color palette, and layout rules will be injected during ALL implementation steps for consistent brand-quality design.`}))}let a=ut(e.category??void 0),i=pa(e.category??void 0),n=Tr(i);return p(JSON.stringify({count:a.length,appStyles:a.map(o=>({id:o.id,name:o.name,category:o.category,description:o.description,theme:o.theme,colors:o.colors,fonts:o.fonts})),formatted:n,message:`${a.length} app styles available.${e.category?` Filtered by: ${e.category}.`:""} To use one, pass appStyle="<id>" when calling mist_plan. The style will be applied across ALL pages for consistent design. Browse them at ${Ae()}/designs?tab=app-styles.`}))}case"integrations":{if(e.integrationId){let o=at(e.integrationId);if(!o)return p(`Integration '${e.integrationId}' not found. Use mist_project action='integrations' without integrationId to list all available integrations.`,!0);let s=rt(o.id);return p(JSON.stringify({integration:{id:o.id,name:o.name,category:o.category,description:s?.description??"",packages:s?.packages??[],envVars:s?.envVars??[],docsUrl:s?.docsUrl??"",difficulty:s?.difficulty??"medium"},message:`Integration "${o.name}" (${o.category}) \u2014 ${s?.description??""}. This blueprint is auto-injected during implementation when your plan has a matching integration step. Required env vars: ${s?.envVars?.map(l=>l.key).join(", ")||"none"}. Docs: ${s?.docsUrl??"n/a"}.`}))}let a=Ar(e.category??void 0),i=ha(e.category??void 0),n=Dr(i);return p(JSON.stringify({count:a.length,integrations:a.map(o=>({id:o.id,name:o.name,category:o.category,description:o.description,packages:o.packages,difficulty:o.difficulty,envVars:o.envVars.map(s=>s.key)})),formatted:n,message:`${a.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 a=Qt(e.projectPath??process.cwd()),i=ea(a,"mistflow.json");if(!Xt(i))return Te(a);let n;try{n=JSON.parse(Zt(i,"utf-8"))}catch{return p("Could not read mistflow.json.",!0)}let o=n.projectId;if(!o)return p("No project ID found. Deploy the project first.",!0);try{let s=await rr(o,e.period??"7d");return s.total===0?p(JSON.stringify({total:0,period:s.period,message:`No runtime errors in the last ${s.period}. The app is running clean.`})):p(JSON.stringify({total:s.total,period:s.period,errors:s.errors,message:`${s.total} runtime error(s) in the last ${s.period}. Review the errors above and use mist_build debug to investigate.`}))}catch(s){let l=s instanceof Error?s.message:"Failed to fetch errors";return p(l,!0)}}case"logs":{let a=Qt(e.projectPath??process.cwd()),i=ea(a,"mistflow.json");if(!Xt(i))return Te(a);let n;try{n=JSON.parse(Zt(i,"utf-8"))}catch{return p("Could not read mistflow.json.",!0)}let o=n.projectId;if(!o)return p("No project ID found. Deploy the project first.",!0);let s=e.deploymentId;if(!s)try{let l=await pt(o);if(l.length===0)return p("No deployments found for this project.",!0);s=l[0].id}catch(l){let d=l instanceof Error?l.message:"Failed to fetch deployments";return p(d,!0)}try{let[l,d]=await Promise.all([ar(s),Ue(s)]),u=l.filter(c=>c.level==="error"),h=l.filter(c=>c.level==="warn");return p(JSON.stringify({deploymentId:s,status:d.status,errorMessage:d.error??null,totalLogs:l.length,errorCount:u.length,warnCount:h.length,logs:l.map(c=>({time:c.timestamp,level:c.level,phase:c.phase,message:c.message})),message:d.status==="failed"?`Deployment failed. ${u.length} error(s) found in logs. Review the logs above to diagnose the issue.`:`Deployment status: ${d.status}. ${l.length} log entries (${u.length} errors, ${h.length} warnings).`}))}catch(l){let d=l instanceof Error?l.message:"Failed to fetch deploy logs";return p(d,!0)}}case"deployments":{let a=Qt(e.projectPath??process.cwd()),i=ea(a,"mistflow.json");if(!Xt(i))return Te(a);let n;try{n=JSON.parse(Zt(i,"utf-8"))}catch{return p("Could not read mistflow.json.",!0)}let o=n.projectId;if(!o)return p("No project ID found. Deploy the project first.",!0);try{let s=await pt(o);return p(JSON.stringify({total:s.length,deployments:s.map(l=>({id:l.id,status:l.status,errorMessage:l.error_message,durationSeconds:l.duration_seconds,isRollback:!!l.rollback_from_id,createdAt:l.created_at})),message:`${s.length} deployment(s) found. Use mist_project action='logs' deploymentId='<id>' to see detailed logs for any deployment.`}))}catch(s){let l=s instanceof Error?s.message:"Failed to fetch deployments";return p(l,!0)}}case"version":{let a=St(),i=a.severity==="none",n={none:"up to date",patch:"patch update available",minor:"minor update available",major:"major update available",unsupported:"UNSUPPORTED \u2014 upgrade required"};return p(JSON.stringify({current:a.current,latest:a.latest||"unknown",minSupported:a.minSupported||"unknown",severity:a.severity,upToDate:i,upgradeCmd:a.upgradeCmd,changelogUrl:a.changelogUrl,backendSignalReceived:a.backendSignalReceived,message:a.backendSignalReceived?`Mistflow MCP ${a.current} (${n[a.severity]??a.severity}). Latest: ${a.latest}.${i?"":` Run \`${a.upgradeCmd}\` and restart your editor to upgrade.`}`:`Mistflow MCP ${a.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 p(`Unknown action: ${e.action}. Use get, update, share, landing-designs, app-styles, integrations, errors, logs, deployments, or version.`,!0)}}};import{z as Pe}from"zod";import{z as ze}from"zod";import{resolve as Cl}from"path";var Ru=ze.object({projectPath:ze.string().optional().describe("Path to the project directory (default: cwd)"),action:ze.enum(["set","list","delete"]).describe("Action to perform"),key:ze.string().optional().describe("Environment variable name (required for 'set' and 'delete')"),value:ze.string().optional().describe("Environment variable value (required for 'set')"),category:ze.string().optional().describe("Category for the env var (default: 'custom')"),description:ze.string().optional().describe("Description of what this env var is for"),setupUrl:ze.string().optional().describe("URL where the user can obtain this value (e.g. Stripe dashboard)")});async function si(r){let{projectPath:e,action:t,key:a,value:i,category:n,description:o,setupUrl:s}=r,l=Cl(e??process.cwd());if(!ce())return p("No Mistflow credentials found. Run mist_setup to connect your account.",!0);let u=De(l)?.projectId;if(!u)return p("No project ID found. Deploy your project first with mist_deploy, or initialize with mist_plan + mist_build.",!0);try{switch(t){case"set":return a?i?(await er(u,a,i,{category:n,description:o,setupUrl:s}),p(JSON.stringify({set:!0,key:a,message:`Environment variable '${a}' has been set. It will be available on your next deployment.`}))):p("Value is required. Provide the env var value.",!0):p("Key is required. Provide the env var name like 'STRIPE_SECRET_KEY'.",!0);case"list":{let h=await Za(u);return h.length===0?p(JSON.stringify({envVars:[],message:"No environment variables configured. Use action 'set' to add one."})):p(JSON.stringify({envVars:h.map(c=>({key:c.key,category:c.category,description:c.description,hasValue:c.has_value})),message:`${h.length} environment variable(s) configured.`}))}case"delete":return a?(await tr(u,a),p(JSON.stringify({deleted:!0,key:a,message:`Environment variable '${a}' has been removed.`}))):p("Key is required. Provide the env var name to delete.",!0);default:return p(`Unknown action: ${t}. Use set, list, or delete.`,!0)}}catch(h){if(h instanceof he)return p(h.message,!0);let c=h instanceof Error?h.message:"An unexpected error occurred";return p(c,!0)}}import{z as xt}from"zod";import{resolve as Tl,join as Pl}from"path";import{existsSync as Bl,readFileSync as Dl,writeFileSync as Al}from"fs";var Gu=xt.object({projectPath:xt.string().optional().describe("Path to the project directory (default: cwd)"),action:xt.enum(["add","list","verify","remove"]).describe("Action to perform"),domain:xt.string().optional().describe("Domain name (required for 'add' and 'remove')"),domainId:xt.string().optional().describe("Domain ID (required for 'verify' and 'remove')")});function Ua(r,e){let t=Pl(r,"mistflow.json");if(!Bl(t))return;let a;try{a=JSON.parse(Dl(t,"utf-8"))}catch{return}a.domains=e,Al(t,JSON.stringify(a,null,2)+`
12502
- `)}async function li(r){let{projectPath:e,action:t,domain:a,domainId:i}=r,n=Tl(e??process.cwd());if(!ce())return p("No Mistflow credentials found. Run mist_setup to connect your account.",!0);let s=De(n)?.projectId;if(!s)return p("No project ID found. Deploy your project first with mist_deploy, or initialize with mist_plan + mist_build.",!0);try{switch(t){case"add":{if(!a)return p("Domain name is required. Provide the domain like 'myapp.com' or 'app.mycompany.com'.",!0);let l=await Ka(s,a),d=await qe(s);return Ua(n,d.map(u=>({domain:u.domain,status:u.status}))),p(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 qe(s);return l.length===0?p(JSON.stringify({domains:[],message:"No custom domains configured. Use action 'add' to add one."})):p(JSON.stringify({domains:l.map(d=>({id:d.id,domain:d.domain,status:d.status,ssl:d.ssl_status,error:d.error_message}))}))}case"verify":{if(!i){if(a){let h=(await qe(s)).find(c=>c.domain===a);if(h){let c=await oa(s,h.id);return p(JSON.stringify({domain:c.domain,status:c.status,ssl:c.ssl_status,error:c.error_message,message:c.status==="active"?`Domain '${c.domain}' is active and serving traffic.`:`Domain '${c.domain}' is ${c.status}. Make sure DNS records are configured correctly.`}))}return p(`Domain '${a}' not found. Use action 'list' to see configured domains.`,!0)}return p("Provide either domainId or domain name to verify.",!0)}let l=await oa(s,i),d=await qe(s);return Ua(n,d.map(u=>({domain:u.domain,status:u.status}))),p(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(!i&&!a)return p("Provide either domainId or domain name to remove.",!0);let l=i;if(!l&&a){let h=(await qe(s)).find(c=>c.domain===a);if(!h)return p(`Domain '${a}' not found.`,!0);l=h.id}await Ya(s,l);let d=await qe(s);return Ua(n,d.map(u=>({domain:u.domain,status:u.status}))),p(JSON.stringify({removed:!0,message:"Domain removed. It may take a few minutes for DNS changes to propagate."}))}default:return p(`Unknown action: ${t}. Use add, list, verify, or remove.`,!0)}}catch(l){if(l instanceof he)return p(l.message,!0);let d=l instanceof Error?l.message:"An unexpected error occurred";return p(d,!0)}}var Il=Pe.object({resource:Pe.enum(["env","domain"]).describe("'env' manages app secrets and configuration values. 'domain' manages custom domains."),action:Pe.string().describe("Action to perform. env: 'set', 'list', 'delete'. domain: 'add', 'list', 'verify', 'remove'."),projectPath:Pe.string().optional().describe("Path to the project directory (default: cwd)"),key:Pe.string().optional().describe("(env) Variable name"),value:Pe.string().optional().describe("(env set) Variable value"),category:Pe.string().optional().describe("(env set) Category"),description:Pe.string().optional().describe("(env set) Description"),setupUrl:Pe.string().optional().describe("(env set) URL to obtain the value"),domain:Pe.string().optional().describe("(domain) Domain name"),domainId:Pe.string().optional().describe("(domain) Domain ID")}),di={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:Il,handler:async r=>{let e=r;switch(e.resource){case"env":return si({projectPath:e.projectPath,action:e.action,key:e.key,value:e.value,category:e.category,description:e.description,setupUrl:e.setupUrl});case"domain":return li({projectPath:e.projectPath,action:e.action,domain:e.domain,domainId:e.domainId});default:return p(`Unknown resource: ${e.resource}. Use env or domain.`,!0)}}};import{z as Qe}from"zod";var Ml=Qe.object({action:Qe.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:Qe.string().optional().describe("URL to navigate to. Required for 'navigate'; optional for 'screenshot' (navigates before capturing)."),selector:Qe.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:Qe.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:Qe.boolean().default(!1).describe("For 'screenshot': capture the full scrollable page instead of just the viewport."),includeScreenshot:Qe.boolean().default(!1).describe("For navigate/interact actions: also return a screenshot alongside the accessibility snapshot.")}),ci={name:"mist_browser",description:"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.",inputSchema:Ml,handler:async r=>{let e=r,t=await Ct();if(e.action==="navigate"){if(!e.url)return p("URL is required for 'navigate'.",!0);let n=[],o=d=>{d.type()==="error"&&n.push(d.text())};t.on("console",o),await t.goto(e.url,{waitUntil:"domcontentloaded",timeout:3e4}),await t.waitForLoadState("networkidle").catch(()=>{});let s=[],l=d=>s.push(d.message);if(t.on("pageerror",l),await t.waitForTimeout(500),t.removeListener("console",o),t.removeListener("pageerror",l),n.length>0||s.length>0){let d=await Tt(t),u=[{type:"text",text:JSON.stringify({url:t.url(),title:await t.title(),snapshot:d,consoleErrors:n,pageErrors:s,hasErrors:!0})}];if(e.includeScreenshot){let h=await Fe(t);u.push({type:"image",data:h.toString("base64"),mimeType:"image/png"})}return{content:u}}}else if(e.action==="go_back")await t.goBack({waitUntil:"domcontentloaded",timeout:1e4});else if(e.action==="go_forward")await t.goForward({waitUntil:"domcontentloaded",timeout:1e4});else if(e.action==="click"){if(!e.selector)return p("Selector is required for 'click'.",!0);await t.click(e.selector,{timeout:1e4}),await t.waitForLoadState("domcontentloaded").catch(()=>{}),await t.waitForTimeout(500)}else if(e.action==="type"){if(!e.selector)return p("Selector is required for 'type'.",!0);if(!e.value)return p("Value is required for 'type'.",!0);await t.type(e.selector,e.value,{delay:50})}else if(e.action==="fill"){if(!e.selector)return p("Selector is required for 'fill'.",!0);if(!e.value)return p("Value is required for 'fill'.",!0);await t.fill(e.selector,e.value)}else if(e.action==="select_option"){if(!e.selector)return p("Selector is required for 'select_option'.",!0);if(!e.value)return p("Value is required for 'select_option'.",!0);await t.selectOption(e.selector,e.value)}else if(e.action==="hover"){if(!e.selector)return p("Selector is required for 'hover'.",!0);await t.hover(e.selector,{timeout:1e4})}else if(e.action==="press_key"){if(!e.value)return p("Value is required for 'press_key' (e.g. 'Enter').",!0);await t.keyboard.press(e.value),await t.waitForLoadState("domcontentloaded").catch(()=>{}),await t.waitForTimeout(500)}else if(e.action==="screenshot"){e.url&&(await t.goto(e.url,{waitUntil:"domcontentloaded",timeout:3e4}),await t.waitForLoadState("networkidle").catch(()=>{}));let n;if(e.selector){let o=await t.$(e.selector);if(!o)return p(`Element not found: ${e.selector}`,!0);n=await o.screenshot({type:"png"})}else n=await Fe(t,e.fullPage);return{content:[{type:"text",text:JSON.stringify({url:t.url(),title:await t.title(),message:`Screenshot captured (${e.fullPage?"full page":"viewport"})`})},{type:"image",data:n.toString("base64"),mimeType:"image/png"}]}}else if(e.action==="snapshot"){let n=await Tt(t);return{content:[{type:"text",text:JSON.stringify({url:t.url(),title:await t.title(),snapshot:n})}]}}let a=await Tt(t),i=[{type:"text",text:JSON.stringify({url:t.url(),title:await t.title(),snapshot:a})}];if(e.includeScreenshot){let n=await Fe(t);i.push({type:"image",data:n.toString("base64"),mimeType:"image/png"})}return{content:i}}};import{existsSync as Le,readFileSync as vt,writeFileSync as ct}from"fs";import{join as Be}from"path";import{z as wt}from"zod";function pi(r){let e=Be(r,"mistflow.json");if(!Le(e))return{result:{name:"Project",status:"fail",message:"No mistflow.json found. This is not a Mistflow project.",fix:"Run mist_plan to design your app, then mist_build init to scaffold it."},config:null};let t;try{t=JSON.parse(vt(e,"utf-8"))}catch{return{result:{name:"Project",status:"fail",message:"mistflow.json exists but contains invalid JSON.",fix:"Check mistflow.json for syntax errors. If corrupted beyond repair, delete it and re-run mist_build init."},config:null}}if(!t.name||typeof t.name!="string")return{result:{name:"Project",status:"warn",message:"mistflow.json is missing the 'name' field."},config:t};let a=t.projectId,i=a?`, id: ${a}`:", no projectId";return{result:{name:"Project",status:"pass",message:`${t.name}${i}`},config:t}}async function Rl(){let r=Wa();if(!r.ok)return{result:{name:"Auth",status:"fail",message:r.reason==="missing"?"No credentials found.":"Credentials file is malformed.",fix:"Run mist_setup to log in."},creds:null,authValid:!1};try{let e=ja(),t=await fetch(`${Ve()}/api/org`,{headers:e,signal:AbortSignal.timeout(1e4)});try{Oa(t.headers)}catch{}if(!t.ok)return t.status===401?{result:{name:"Auth",status:"fail",message:"API key is invalid or revoked.",fix:"Run mist_setup to re-authenticate."},creds:r.creds,authValid:!1}:{result:{name:"Auth",status:"warn",message:`Server returned ${t.status}. Credentials may still be valid.`},creds:r.creds,authValid:!1};let a=await t.json(),i=a.slug??r.creds.orgSlug??"unknown",n=a.plan?`, plan: ${a.plan}`:"";return{result:{name:"Auth",status:"pass",message:`org: ${i}${n}`},creds:r.creds,authValid:!0}}catch(e){return e instanceof he?{result:{name:"Auth",status:"fail",message:`Auth check failed: ${e.message}`,fix:"Run mist_setup to re-authenticate."},creds:r.creds,authValid:!1}:{result:{name:"Auth",status:"warn",message:"Could not reach API to validate credentials. Network issue?"},creds:r.creds,authValid:!1}}}async function Ll(){let r=Date.now();try{let e=await Dt("nextjs"),t=Date.now()-r;return{result:{name:"API",status:"pass",message:`${Ve()} reachable (${t}ms)`},scaffold:e}}catch(e){let t=Date.now()-r;return{result:{name:"API",status:"fail",message:e instanceof he?e.message:`Timeout or network error after ${t}ms`,fix:"Check your network connection. If using --api-url, verify the backend is running."},scaffold:null}}}function Nl(){let r=St();if(!r.backendSignalReceived)return{name:"MCP version",status:"warn",message:`v${r.current} installed, but no backend signal received to compare against.`};let e={none:"up to date",patch:"patch update available",minor:"minor update available",major:"major update available",unsupported:"no longer supported"};return r.severity==="none"?{name:"MCP version",status:"pass",message:`v${r.current} (latest)`}:r.severity==="unsupported"?{name:"MCP version",status:"fail",message:`v${r.current} is ${e[r.severity]}. Minimum: v${r.minSupported}.`,fix:`Run \`${r.upgradeCmd}\` then restart your editor.`}:{name:"MCP version",status:"warn",message:`v${r.current} installed, v${r.latest} available (${e[r.severity]}).`,fix:`Run \`${r.upgradeCmd}\` then restart your editor.`}}function Fl(r,e,t,a){let i=Be(r,"AGENTS.md"),n=Be(r,"CLAUDE.md"),o=Le(i),s=e.methodologyVersion??"",l=t?.version??"";if(!o){if(!a||!t?.methodology)return{name:"AGENTS.md",status:"fail",message:"Missing. The host AI has no methodology context for this project.",fix:t?"Run mist_doctor without reportOnly to auto-restore it.":"Cannot restore: API unreachable. Fix connectivity first."};let d=ui(t.methodology,e);return ct(i,d),ct(n,d),{name:"AGENTS.md",status:"fix",message:`Restored from methodology v${l}.`}}if(a&&!Le(n)){let d=vt(i,"utf-8");ct(n,d)}if(l&&s&&l!==s){if(!a||!t?.methodology)return{name:"AGENTS.md",status:"warn",message:`Methodology v${s} installed, v${l} available.`,fix:"Run mist_doctor to update it."};let d=ui(t.methodology,e);ct(i,d),ct(n,d);try{let u=Be(r,"mistflow.json"),h=JSON.parse(vt(u,"utf-8"));h.methodologyVersion=l,ct(u,JSON.stringify(h,null,2)+`
12503
- `)}catch{}return{name:"AGENTS.md",status:"fix",message:`Updated methodology v${s} -> v${l}.`}}return{name:"AGENTS.md",status:"pass",message:s?`v${s}`:"present"}}async function Ul(r,e,t,a){let i=e.projectId;if(!i)return{name:"State sync",status:"skip",message:"No projectId. State sync not applicable without a linked project."};if(!t)return{name:"State sync",status:"skip",message:"Skipped (not authenticated)."};let n=gr(r),o=await mr(i);if(!n&&!o)return{name:"State sync",status:"warn",message:"No local or remote state found."};if(!n&&o){if(a){let{writeLocalState:s}=await import("./state-manager-XY6GQEFE.js");return s(r,o),{name:"State sync",status:"fix",message:"Local state was missing. Restored from remote."}}return{name:"State sync",status:"warn",message:"Local .mistflow/state.json is missing but remote state exists.",fix:"Run mist_doctor to restore it from the server."}}return n&&!o?{name:"State sync",status:"warn",message:"Local state exists but no remote state found. Remote sync may have failed."}:n&&n.projectId!==i?{name:"State sync",status:"warn",message:`state.json projectId (${n.projectId}) doesn't match mistflow.json projectId (${i}).`}:{name:"State sync",status:"pass",message:`deployCount: ${n.deployCount}, features: ${n.features.length}`}}function El(r){let e=[],t=[];if(Le(Be(r,"package.json"))?Le(Be(r,"node_modules"))||e.push("node_modules not installed (run npm install)"):e.push("missing package.json"),!Le(Be(r,".env.local")))e.push("missing .env.local");else try{vt(Be(r,".env.local"),"utf-8").match(/^AUTH_SECRET=(.+)$/m)||e.push("AUTH_SECRET not set in .env.local")}catch{e.push(".env.local exists but is not readable")}let a=Be(r,"app","api","auth","[...all]","route.ts");Le(a)||t.push("missing app/api/auth/[...all]/route.ts");let i=Be(r,"app","api","health","route.ts");return Le(i)||t.push("missing app/api/health/route.ts (needed for deploy verification)"),e.length>0?{name:"Structure",status:"fail",message:e.join("; "),fix:e.map(n=>n.includes("node_modules")?"Run `npm install` in the project directory.":n.includes(".env.local")?"Create .env.local with AUTH_SECRET=<random-secret>.":n.includes("AUTH_SECRET")?"Add AUTH_SECRET=<random-secret> to .env.local.":n.includes("package.json")?"This project is missing package.json. Was it scaffolded correctly?":"").filter(Boolean).join(" ")}:t.length>0?{name:"Structure",status:"warn",message:t.join("; ")}:{name:"Structure",status:"pass",message:"all required files present"}}function Hl(r,e){let t=e.env;if(!t?.required||typeof t.required!="object"||Object.keys(t.required).length===0)return{name:"Env vars",status:"pass",message:"no required env vars declared"};let a=[],i="";for(let n of[".env.local",".env"]){let o=Be(r,n);try{Le(o)&&(i+=`
12504
- `+vt(o,"utf-8"))}catch{}}for(let[n,o]of Object.entries(t.required)){let s=i.includes(`${n}=`),l=!!process.env[n];if(!s&&!l){let d=o?.description?` (${o.description})`:"";a.push(`${n}${d}`)}}return a.length>0?{name:"Env vars",status:"warn",message:`${a.length} required but not set: ${a.join(", ")}`,fix:"Set them via mist_config or add to .env.local."}:{name:"Env vars",status:"pass",message:`${Object.keys(t.required).length} required, all set`}}function Ol(r){let e=r.plan;if(!e?.steps||e.steps.length===0)return{name:"Plan",status:"skip",message:"No plan found in mistflow.json."};let t=e.steps.length,a=e.steps.filter(o=>o.status==="completed").length,i=e.steps.filter(o=>o.status==="in_progress").length,n=t-a-i;if(a===t)return{name:"Plan",status:"pass",message:`${a}/${t} steps completed`};if(i>0){let o=e.steps.find(s=>s.status==="in_progress");return{name:"Plan",status:"warn",message:`${a}/${t} completed, step ${o?.number} "${o?.name}" in progress`,fix:"Run mist_build implement to continue."}}return a===0?{name:"Plan",status:"warn",message:`${t} steps planned, none started.`,fix:"Run mist_build implement to start building."}:{name:"Plan",status:"warn",message:`${a}/${t} completed, ${n} remaining.`,fix:"Run mist_build implement to continue."}}function ui(r,e){let t=e.dbProvider;return t==="neon"||!t?r.replace(/sqliteTable/g,"pgTable").replace(/drizzle-orm\/sqlite-core/g,"drizzle-orm/pg-core").replace(/Use `text` for dates \(SQLite stores dates as text\)/g,"Use `timestamp` for dates and `boolean` for booleans (native Postgres types)").replace(/text\("created_at"\)\.notNull\(\)\.default\(sql`\(CURRENT_TIMESTAMP\)`\)/g,'timestamp("created_at").notNull().defaultNow()').replace(/text\("updated_at"\)\.notNull\(\)\.default\(sql`\(CURRENT_TIMESTAMP\)`\)/g,'timestamp("updated_at").notNull().defaultNow()').replace(/import { sqliteTable, text, integer } from "drizzle-orm\/sqlite-core";\nimport { sql } from "drizzle-orm";/g,'import { pgTable, text, timestamp, boolean } from "drizzle-orm/pg-core";').replace(/`drizzle-kit push` for SQLite\/Turso/g,"`drizzle-kit push` for Postgres"):r}function Gl(r){let e={pass:"PASS",warn:"WARN",fail:"FAIL",fix:"FIXED",skip:"SKIP"},t=Math.max(...r.map(u=>u.name.length)),a=r.map(u=>{let h=".".repeat(t-u.name.length+4),c=e[u.status].padEnd(5);return` ${u.name} ${h} ${c} ${u.message}`}),i=r.filter(u=>u.status==="fix").length,n=r.filter(u=>u.status==="fail").length,o=r.filter(u=>u.status==="warn").length,s=[];i>0&&s.push(`${i} fixed`),n>0&&s.push(`${n} failure${n>1?"s":""}`),o>0&&s.push(`${o} warning${o>1?"s":""}`);let l=`mist_doctor results:
13509
+ "build me something like ${n.share_url}"`}))}catch(n){let l=n instanceof Error?n.message:"Failed to share project";return p(l,!0)}}case"landing-designs":{if(e.presetId){let o=Je(e.presetId);if(!o)return p(`Preset '${e.presetId}' not found. Use mist_project action='presets' without presetId to list all available presets.`,!0);let n=vr(e.presetId);return p(JSON.stringify({preset:{id:o.id,title:o.title,category:o.category,description:n?.description??"",style:n?.style??"",theme:n?.theme??"dark",colors:n?.colors??[],tags:n?.tags??[],promptLength:o.prompt.length},message:`Landing design "${o.title}" (${o.category}) \u2014 ${n?.description??""}. To use it, pass landingDesign="${o.id}" when calling mist_plan.`}))}let t=Sr(e.category??void 0),i=ua(e.category),s=kr(i);return p(JSON.stringify({count:t.length,presets:t.map(o=>({id:o.id,title:o.title,category:o.category,description:o.description,style:o.style,theme:o.theme,colors:o.colors})),formatted:s,message:`${t.length} landing designs available.${e.category?` Filtered by: ${e.category}.`:""} To use one, pass landingDesign="<id>" when calling mist_plan. The design blueprint will be injected during the landing page implementation step. Browse them at ${Ae()}/designs?tab=landing-designs.`}))}case"app-styles":{if(e.appStyleId){let o=be(e.appStyleId);if(!o)return p(`App style '${e.appStyleId}' not found. Use mist_project action='app-styles' without appStyleId to list all available app styles.`,!0);let n=De(o.id);return p(JSON.stringify({appStyle:{id:o.id,name:o.name,category:n?.category??o.category,description:n?.description??"",theme:n?.theme??"light",colors:n?.colors??[],fonts:n?.fonts??{heading:"Inter",body:"Inter"},tags:n?.tags??[],sectionCount:o.sections.length,sections:o.sections.map(l=>l.title)},message:`App style "${o.name}" (${o.category}) \u2014 ${n?.description??""}. To use it, pass appStyle="${o.id}" when calling mist_plan. The style's component specs, typography, color palette, and layout rules will be injected during ALL implementation steps for consistent brand-quality design.`}))}let t=dt(e.category??void 0),i=ga(e.category??void 0),s=Br(i);return p(JSON.stringify({count:t.length,appStyles:t.map(o=>({id:o.id,name:o.name,category:o.category,description:o.description,theme:o.theme,colors:o.colors,fonts:o.fonts})),formatted:s,message:`${t.length} app styles available.${e.category?` Filtered by: ${e.category}.`:""} To use one, pass appStyle="<id>" when calling mist_plan. The style will be applied across ALL pages for consistent design. Browse them at ${Ae()}/designs?tab=app-styles.`}))}case"integrations":{if(e.integrationId){let o=Xe(e.integrationId);if(!o)return p(`Integration '${e.integrationId}' not found. Use mist_project action='integrations' without integrationId to list all available integrations.`,!0);let n=Ze(o.id);return p(JSON.stringify({integration:{id:o.id,name:o.name,category:o.category,description:n?.description??"",packages:n?.packages??[],envVars:n?.envVars??[],docsUrl:n?.docsUrl??"",difficulty:n?.difficulty??"medium"},message:`Integration "${o.name}" (${o.category}) \u2014 ${n?.description??""}. This blueprint is auto-injected during implementation when your plan has a matching integration step. Required env vars: ${n?.envVars?.map(l=>l.key).join(", ")||"none"}. Docs: ${n?.docsUrl??"n/a"}.`}))}let t=Mr(e.category??void 0),i=fa(e.category??void 0),s=Ir(i);return p(JSON.stringify({count:t.length,integrations:t.map(o=>({id:o.id,name:o.name,category:o.category,description:o.description,packages:o.packages,difficulty:o.difficulty,envVars:o.envVars.map(n=>n.key)})),formatted:s,message:`${t.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 t=Qt(e.projectPath??process.cwd()),i=ea(t,"mistflow.json");if(!Xt(i))return Te(t);let s;try{s=JSON.parse(Zt(i,"utf-8"))}catch{return p("Could not read mistflow.json.",!0)}let o=s.projectId;if(!o)return p("No project ID found. Deploy the project first.",!0);try{let n=await ir(o,e.period??"7d");return n.total===0?p(JSON.stringify({total:0,period:n.period,message:`No runtime errors in the last ${n.period}. The app is running clean.`})):p(JSON.stringify({total:n.total,period:n.period,errors:n.errors,message:`${n.total} runtime error(s) in the last ${n.period}. Review the errors above and use mist_build debug to investigate.`}))}catch(n){let l=n instanceof Error?n.message:"Failed to fetch errors";return p(l,!0)}}case"logs":{let t=Qt(e.projectPath??process.cwd()),i=ea(t,"mistflow.json");if(!Xt(i))return Te(t);let s;try{s=JSON.parse(Zt(i,"utf-8"))}catch{return p("Could not read mistflow.json.",!0)}let o=s.projectId;if(!o)return p("No project ID found. Deploy the project first.",!0);let n=e.deploymentId;if(!n)try{let l=await lt(o);if(l.length===0)return p("No deployments found for this project.",!0);n=l[0].id}catch(l){let d=l instanceof Error?l.message:"Failed to fetch deployments";return p(d,!0)}try{let[l,d]=await Promise.all([or(n),Le(n)]),u=l.filter(c=>c.level==="error"),h=l.filter(c=>c.level==="warn");return p(JSON.stringify({deploymentId:n,status:d.status,errorMessage:d.error??null,totalLogs:l.length,errorCount:u.length,warnCount:h.length,logs:l.map(c=>({time:c.timestamp,level:c.level,phase:c.phase,message:c.message})),message:d.status==="failed"?`Deployment failed. ${u.length} error(s) found in logs. Review the logs above to diagnose the issue.`:`Deployment status: ${d.status}. ${l.length} log entries (${u.length} errors, ${h.length} warnings).`}))}catch(l){let d=l instanceof Error?l.message:"Failed to fetch deploy logs";return p(d,!0)}}case"deployments":{let t=Qt(e.projectPath??process.cwd()),i=ea(t,"mistflow.json");if(!Xt(i))return Te(t);let s;try{s=JSON.parse(Zt(i,"utf-8"))}catch{return p("Could not read mistflow.json.",!0)}let o=s.projectId;if(!o)return p("No project ID found. Deploy the project first.",!0);try{let n=await lt(o);return p(JSON.stringify({total:n.length,deployments:n.map(l=>({id:l.id,status:l.status,errorMessage:l.error_message,durationSeconds:l.duration_seconds,isRollback:!!l.rollback_from_id,createdAt:l.created_at})),message:`${n.length} deployment(s) found. Use mist_project action='logs' deploymentId='<id>' to see detailed logs for any deployment.`}))}catch(n){let l=n instanceof Error?n.message:"Failed to fetch deployments";return p(l,!0)}}case"version":{st().backendSignalReceived||await za();let t=st(),i=t.severity==="none",s={none:"up to date",patch:"patch update available",minor:"minor update available",major:"major update available",unsupported:"UNSUPPORTED \u2014 upgrade required"};return p(JSON.stringify({current:t.current,latest:t.latest||"unknown",minSupported:t.minSupported||"unknown",severity:t.severity,upToDate:i,upgradeCmd:t.upgradeCmd,changelogUrl:t.changelogUrl,backendSignalReceived:t.backendSignalReceived,message:t.backendSignalReceived?`Mistflow MCP ${t.current} (${s[t.severity]??t.severity}). Latest: ${t.latest}.${i?"":` Run \`${t.upgradeCmd}\` and restart your editor to upgrade.`}`:`Mistflow MCP ${t.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 p(`Unknown action: ${e.action}. Use get, update, share, landing-designs, app-styles, integrations, errors, logs, deployments, or version.`,!0)}}};import{z as Pe}from"zod";import{z as We}from"zod";import{resolve as Hl}from"path";var ih=We.object({projectPath:We.string().optional().describe("Path to the project directory (default: cwd)"),action:We.enum(["set","list","delete"]).describe("Action to perform"),key:We.string().optional().describe("Environment variable name (required for 'set' and 'delete')"),value:We.string().optional().describe("Environment variable value (required for 'set')"),category:We.string().optional().describe("Category for the env var (default: 'custom')"),description:We.string().optional().describe("Description of what this env var is for"),setupUrl:We.string().optional().describe("URL where the user can obtain this value (e.g. Stripe dashboard)")});async function fi(r){let{projectPath:e,action:a,key:t,value:i,category:s,description:o,setupUrl:n}=r,l=Hl(e??process.cwd());if(!ue())return p("No Mistflow credentials found. Run mist_setup to connect your account.",!0);let u=Se(l)?.projectId;if(!u)return p("No project ID found. Deploy your project first with mist_deploy, or initialize with mist_plan + mist_build.",!0);try{switch(a){case"set":return t?i?(await ar(u,t,i,{category:s,description:o,setupUrl:n}),p(JSON.stringify({set:!0,key:t,message:`Environment variable '${t}' has been set. It will be available on your next deployment.`}))):p("Value is required. Provide the env var value.",!0):p("Key is required. Provide the env var name like 'STRIPE_SECRET_KEY'.",!0);case"list":{let h=await tr(u);return h.length===0?p(JSON.stringify({envVars:[],message:"No environment variables configured. Use action 'set' to add one."})):p(JSON.stringify({envVars:h.map(c=>({key:c.key,category:c.category,description:c.description,hasValue:c.has_value})),message:`${h.length} environment variable(s) configured.`}))}case"delete":return t?(await rr(u,t),p(JSON.stringify({deleted:!0,key:t,message:`Environment variable '${t}' has been removed.`}))):p("Key is required. Provide the env var name to delete.",!0);default:return p(`Unknown action: ${a}. Use set, list, or delete.`,!0)}}catch(h){if(h instanceof me)return p(h.message,!0);let c=h instanceof Error?h.message:"An unexpected error occurred";return p(c,!0)}}import{z as xt}from"zod";import{resolve as Ol,join as Gl}from"path";import{existsSync as Wl,readFileSync as _l,writeFileSync as jl}from"fs";var hh=xt.object({projectPath:xt.string().optional().describe("Path to the project directory (default: cwd)"),action:xt.enum(["add","list","verify","remove"]).describe("Action to perform"),domain:xt.string().optional().describe("Domain name (required for 'add' and 'remove')"),domainId:xt.string().optional().describe("Domain ID (required for 'verify' and 'remove')")});function Ua(r,e){let a=Gl(r,"mistflow.json");if(!Wl(a))return;let t;try{t=JSON.parse(_l(a,"utf-8"))}catch{return}t.domains=e,jl(a,JSON.stringify(t,null,2)+`
13510
+ `)}async function bi(r){let{projectPath:e,action:a,domain:t,domainId:i}=r,s=Ol(e??process.cwd());if(!ue())return p("No Mistflow credentials found. Run mist_setup to connect your account.",!0);let n=Se(s)?.projectId;if(!n)return p("No project ID found. Deploy your project first with mist_deploy, or initialize with mist_plan + mist_build.",!0);try{switch(a){case"add":{if(!t)return p("Domain name is required. Provide the domain like 'myapp.com' or 'app.mycompany.com'.",!0);let l=await Ja(n,t),d=await ze(n);return Ua(s,d.map(u=>({domain:u.domain,status:u.status}))),p(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 ze(n);return l.length===0?p(JSON.stringify({domains:[],message:"No custom domains configured. Use action 'add' to add one."})):p(JSON.stringify({domains:l.map(d=>({id:d.id,domain:d.domain,status:d.status,ssl:d.ssl_status,error:d.error_message}))}))}case"verify":{if(!i){if(t){let h=(await ze(n)).find(c=>c.domain===t);if(h){let c=await sa(n,h.id);return p(JSON.stringify({domain:c.domain,status:c.status,ssl:c.ssl_status,error:c.error_message,message:c.status==="active"?`Domain '${c.domain}' is active and serving traffic.`:`Domain '${c.domain}' is ${c.status}. Make sure DNS records are configured correctly.`}))}return p(`Domain '${t}' not found. Use action 'list' to see configured domains.`,!0)}return p("Provide either domainId or domain name to verify.",!0)}let l=await sa(n,i),d=await ze(n);return Ua(s,d.map(u=>({domain:u.domain,status:u.status}))),p(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(!i&&!t)return p("Provide either domainId or domain name to remove.",!0);let l=i;if(!l&&t){let h=(await ze(n)).find(c=>c.domain===t);if(!h)return p(`Domain '${t}' not found.`,!0);l=h.id}await Qa(n,l);let d=await ze(n);return Ua(s,d.map(u=>({domain:u.domain,status:u.status}))),p(JSON.stringify({removed:!0,message:"Domain removed. It may take a few minutes for DNS changes to propagate."}))}default:return p(`Unknown action: ${a}. Use add, list, verify, or remove.`,!0)}}catch(l){if(l instanceof me)return p(l.message,!0);let d=l instanceof Error?l.message:"An unexpected error occurred";return p(d,!0)}}var zl=Pe.object({resource:Pe.enum(["env","domain"]).describe("'env' manages app secrets and configuration values. 'domain' manages custom domains."),action:Pe.string().describe("Action to perform. env: 'set', 'list', 'delete'. domain: 'add', 'list', 'verify', 'remove'."),projectPath:Pe.string().optional().describe("Path to the project directory (default: cwd)"),key:Pe.string().optional().describe("(env) Variable name"),value:Pe.string().optional().describe("(env set) Variable value"),category:Pe.string().optional().describe("(env set) Category"),description:Pe.string().optional().describe("(env set) Description"),setupUrl:Pe.string().optional().describe("(env set) URL to obtain the value"),domain:Pe.string().optional().describe("(domain) Domain name"),domainId:Pe.string().optional().describe("(domain) Domain ID")}),yi={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:zl,handler:async r=>{let e=r;switch(e.resource){case"env":return fi({projectPath:e.projectPath,action:e.action,key:e.key,value:e.value,category:e.category,description:e.description,setupUrl:e.setupUrl});case"domain":return bi({projectPath:e.projectPath,action:e.action,domain:e.domain,domainId:e.domainId});default:return p(`Unknown resource: ${e.resource}. Use env or domain.`,!0)}}};import{z as Ke}from"zod";var $l=Ke.object({action:Ke.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:Ke.string().optional().describe("URL to navigate to. Required for 'navigate'; optional for 'screenshot' (navigates before capturing)."),selector:Ke.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:Ke.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:Ke.boolean().default(!1).describe("For 'screenshot': capture the full scrollable page instead of just the viewport."),includeScreenshot:Ke.boolean().default(!1).describe("For navigate/interact actions: also return a screenshot alongside the accessibility snapshot.")}),xi={name:"mist_browser",description:"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.",inputSchema:$l,handler:async r=>{let e=r,a=await Tt();if(e.action==="navigate"){if(!e.url)return p("URL is required for 'navigate'.",!0);let s=[],o=d=>{d.type()==="error"&&s.push(d.text())};a.on("console",o),await a.goto(e.url,{waitUntil:"domcontentloaded",timeout:3e4}),await a.waitForLoadState("networkidle").catch(()=>{});let n=[],l=d=>n.push(d.message);if(a.on("pageerror",l),await a.waitForTimeout(500),a.removeListener("console",o),a.removeListener("pageerror",l),s.length>0||n.length>0){let d=await Ct(a),u=[{type:"text",text:JSON.stringify({url:a.url(),title:await a.title(),snapshot:d,consoleErrors:s,pageErrors:n,hasErrors:!0})}];if(e.includeScreenshot){let h=await Ne(a);u.push({type:"image",data:h.toString("base64"),mimeType:"image/png"})}return{content:u}}}else if(e.action==="go_back")await a.goBack({waitUntil:"domcontentloaded",timeout:1e4});else if(e.action==="go_forward")await a.goForward({waitUntil:"domcontentloaded",timeout:1e4});else if(e.action==="click"){if(!e.selector)return p("Selector is required for 'click'.",!0);await a.click(e.selector,{timeout:1e4}),await a.waitForLoadState("domcontentloaded").catch(()=>{}),await a.waitForTimeout(500)}else if(e.action==="type"){if(!e.selector)return p("Selector is required for 'type'.",!0);if(!e.value)return p("Value is required for 'type'.",!0);await a.type(e.selector,e.value,{delay:50})}else if(e.action==="fill"){if(!e.selector)return p("Selector is required for 'fill'.",!0);if(!e.value)return p("Value is required for 'fill'.",!0);await a.fill(e.selector,e.value)}else if(e.action==="select_option"){if(!e.selector)return p("Selector is required for 'select_option'.",!0);if(!e.value)return p("Value is required for 'select_option'.",!0);await a.selectOption(e.selector,e.value)}else if(e.action==="hover"){if(!e.selector)return p("Selector is required for 'hover'.",!0);await a.hover(e.selector,{timeout:1e4})}else if(e.action==="press_key"){if(!e.value)return p("Value is required for 'press_key' (e.g. 'Enter').",!0);await a.keyboard.press(e.value),await a.waitForLoadState("domcontentloaded").catch(()=>{}),await a.waitForTimeout(500)}else if(e.action==="screenshot"){e.url&&(await a.goto(e.url,{waitUntil:"domcontentloaded",timeout:3e4}),await a.waitForLoadState("networkidle").catch(()=>{}));let s;if(e.selector){let o=await a.$(e.selector);if(!o)return p(`Element not found: ${e.selector}`,!0);s=await o.screenshot({type:"png"})}else s=await Ne(a,e.fullPage);return{content:[{type:"text",text:JSON.stringify({url:a.url(),title:await a.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 Ct(a);return{content:[{type:"text",text:JSON.stringify({url:a.url(),title:await a.title(),snapshot:s})}]}}let t=await Ct(a),i=[{type:"text",text:JSON.stringify({url:a.url(),title:await a.title(),snapshot:t})}];if(e.includeScreenshot){let s=await Ne(a);i.push({type:"image",data:s.toString("base64"),mimeType:"image/png"})}return{content:i}}};import{existsSync as Me,readFileSync as vt,writeFileSync as nt}from"fs";import{join as Be}from"path";import{z as wt}from"zod";function wi(r){let e=Be(r,"mistflow.json");if(!Me(e))return{result:{name:"Project",status:"fail",message:"No mistflow.json found. This is not a Mistflow project.",fix:"Run mist_plan to design your app, then mist_build init to scaffold it."},config:null};let a;try{a=JSON.parse(vt(e,"utf-8"))}catch{return{result:{name:"Project",status:"fail",message:"mistflow.json exists but contains invalid JSON.",fix:"Check mistflow.json for syntax errors. If corrupted beyond repair, delete it and re-run mist_build init."},config:null}}if(!a.name||typeof a.name!="string")return{result:{name:"Project",status:"warn",message:"mistflow.json is missing the 'name' field."},config:a};let t=a.projectId,i=t?`, id: ${t}`:", no projectId";return{result:{name:"Project",status:"pass",message:`${a.name}${i}`},config:a}}async function Vl(){let r=Wa();if(!r.ok)return{result:{name:"Auth",status:"fail",message:r.reason==="missing"?"No credentials found.":"Credentials file is malformed.",fix:"Run mist_setup to log in."},creds:null,authValid:!1};try{let e=ja(),a=await fetch(`${je()}/api/org`,{headers:e,signal:AbortSignal.timeout(1e4)});try{Oa(a.headers)}catch{}if(!a.ok)return a.status===401?{result:{name:"Auth",status:"fail",message:"API key is invalid or revoked.",fix:"Run mist_setup to re-authenticate."},creds:r.creds,authValid:!1}:{result:{name:"Auth",status:"warn",message:`Server returned ${a.status}. Credentials may still be valid.`},creds:r.creds,authValid:!1};let t=await a.json(),i=t.slug??r.creds.orgSlug??"unknown",s=t.plan?`, plan: ${t.plan}`:"";return{result:{name:"Auth",status:"pass",message:`org: ${i}${s}`},creds:r.creds,authValid:!0}}catch(e){return e instanceof me?{result:{name:"Auth",status:"fail",message:`Auth check failed: ${e.message}`,fix:"Run mist_setup to re-authenticate."},creds:r.creds,authValid:!1}:{result:{name:"Auth",status:"warn",message:"Could not reach API to validate credentials. Network issue?"},creds:r.creds,authValid:!1}}}async function ql(){let r=Date.now();try{let e=await Bt("nextjs"),a=Date.now()-r;return{result:{name:"API",status:"pass",message:`${je()} reachable (${a}ms)`},scaffold:e}}catch(e){let a=Date.now()-r;return{result:{name:"API",status:"fail",message:e instanceof me?e.message:`Timeout or network error after ${a}ms`,fix:"Check your network connection. If using --api-url, verify the backend is running."},scaffold:null}}}function Kl(){let r=st();if(!r.backendSignalReceived)return{name:"MCP version",status:"warn",message:`v${r.current} installed, but no backend signal received to compare against.`};let e={none:"up to date",patch:"patch update available",minor:"minor update available",major:"major update available",unsupported:"no longer supported"};return r.severity==="none"?{name:"MCP version",status:"pass",message:`v${r.current} (latest)`}:r.severity==="unsupported"?{name:"MCP version",status:"fail",message:`v${r.current} is ${e[r.severity]}. Minimum: v${r.minSupported}.`,fix:`Run \`${r.upgradeCmd}\` then restart your editor.`}:{name:"MCP version",status:"warn",message:`v${r.current} installed, v${r.latest} available (${e[r.severity]}).`,fix:`Run \`${r.upgradeCmd}\` then restart your editor.`}}function Yl(r,e,a,t){let i=Be(r,"AGENTS.md"),s=Be(r,"CLAUDE.md"),o=Me(i),n=e.methodologyVersion??"",l=a?.version??"";if(!o){if(!t||!a?.methodology)return{name:"AGENTS.md",status:"fail",message:"Missing. The host AI has no methodology context for this project.",fix:a?"Run mist_doctor without reportOnly to auto-restore it.":"Cannot restore: API unreachable. Fix connectivity first."};let d=vi(a.methodology,e);return nt(i,d),nt(s,d),{name:"AGENTS.md",status:"fix",message:`Restored from methodology v${l}.`}}if(t&&!Me(s)){let d=vt(i,"utf-8");nt(s,d)}if(l&&n&&l!==n){if(!t||!a?.methodology)return{name:"AGENTS.md",status:"warn",message:`Methodology v${n} installed, v${l} available.`,fix:"Run mist_doctor to update it."};let d=vi(a.methodology,e);nt(i,d),nt(s,d);try{let u=Be(r,"mistflow.json"),h=JSON.parse(vt(u,"utf-8"));h.methodologyVersion=l,nt(u,JSON.stringify(h,null,2)+`
13511
+ `)}catch{}return{name:"AGENTS.md",status:"fix",message:`Updated methodology v${n} -> v${l}.`}}return{name:"AGENTS.md",status:"pass",message:n?`v${n}`:"present"}}async function Jl(r,e,a,t){let i=e.projectId;if(!i)return{name:"State sync",status:"skip",message:"No projectId. State sync not applicable without a linked project."};if(!a)return{name:"State sync",status:"skip",message:"Skipped (not authenticated)."};let s=fr(r),o=await br(i);if(!s&&!o)return{name:"State sync",status:"warn",message:"No local or remote state found."};if(!s&&o){if(t){let{writeLocalState:n}=await import("./state-manager-6NJO66MS.js");return n(r,o),{name:"State sync",status:"fix",message:"Local state was missing. Restored from remote."}}return{name:"State sync",status:"warn",message:"Local .mistflow/state.json is missing but remote state exists.",fix:"Run mist_doctor to restore it from the server."}}return s&&!o?{name:"State sync",status:"warn",message:"Local state exists but no remote state found. Remote sync may have failed."}:s&&s.projectId!==i?{name:"State sync",status:"warn",message:`state.json projectId (${s.projectId}) doesn't match mistflow.json projectId (${i}).`}:{name:"State sync",status:"pass",message:`deployCount: ${s.deployCount}, features: ${s.features.length}`}}function Ql(r){let e=[],a=[];if(Me(Be(r,"package.json"))?Me(Be(r,"node_modules"))||e.push("node_modules not installed (run npm install)"):e.push("missing package.json"),!Me(Be(r,".env.local")))e.push("missing .env.local");else try{vt(Be(r,".env.local"),"utf-8").match(/^AUTH_SECRET=(.+)$/m)||e.push("AUTH_SECRET not set in .env.local")}catch{e.push(".env.local exists but is not readable")}let t=Be(r,"app","api","auth","[...all]","route.ts");Me(t)||a.push("missing app/api/auth/[...all]/route.ts");let i=Be(r,"app","api","health","route.ts");return Me(i)||a.push("missing app/api/health/route.ts (needed for deploy verification)"),e.length>0?{name:"Structure",status:"fail",message:e.join("; "),fix:e.map(s=>s.includes("node_modules")?"Run `npm install` in the project directory.":s.includes(".env.local")?"Create .env.local with AUTH_SECRET=<random-secret>.":s.includes("AUTH_SECRET")?"Add AUTH_SECRET=<random-secret> to .env.local.":s.includes("package.json")?"This project is missing package.json. Was it scaffolded correctly?":"").filter(Boolean).join(" ")}:a.length>0?{name:"Structure",status:"warn",message:a.join("; ")}:{name:"Structure",status:"pass",message:"all required files present"}}function Xl(r,e){let a=e.env;if(!a?.required||typeof a.required!="object"||Object.keys(a.required).length===0)return{name:"Env vars",status:"pass",message:"no required env vars declared"};let t=[],i="";for(let s of[".env.local",".env"]){let o=Be(r,s);try{Me(o)&&(i+=`
13512
+ `+vt(o,"utf-8"))}catch{}}for(let[s,o]of Object.entries(a.required)){let n=i.includes(`${s}=`),l=!!process.env[s];if(!n&&!l){let d=o?.description?` (${o.description})`:"";t.push(`${s}${d}`)}}return t.length>0?{name:"Env vars",status:"warn",message:`${t.length} required but not set: ${t.join(", ")}`,fix:"Set them via mist_config or add to .env.local."}:{name:"Env vars",status:"pass",message:`${Object.keys(a.required).length} required, all set`}}function Zl(r){let e=r.plan;if(!e?.steps||e.steps.length===0)return{name:"Plan",status:"skip",message:"No plan found in mistflow.json."};let a=e.steps.length,t=e.steps.filter(o=>o.status==="completed").length,i=e.steps.filter(o=>o.status==="in_progress").length,s=a-t-i;if(t===a)return{name:"Plan",status:"pass",message:`${t}/${a} steps completed`};if(i>0){let o=e.steps.find(n=>n.status==="in_progress");return{name:"Plan",status:"warn",message:`${t}/${a} completed, step ${o?.number} "${o?.name}" in progress`,fix:"Run mist_build implement to continue."}}return t===0?{name:"Plan",status:"warn",message:`${a} steps planned, none started.`,fix:"Run mist_build implement to start building."}:{name:"Plan",status:"warn",message:`${t}/${a} completed, ${s} remaining.`,fix:"Run mist_build implement to continue."}}function vi(r,e){let a=e.dbProvider;return a==="neon"||!a?r.replace(/sqliteTable/g,"pgTable").replace(/drizzle-orm\/sqlite-core/g,"drizzle-orm/pg-core").replace(/Use `text` for dates \(SQLite stores dates as text\)/g,"Use `timestamp` for dates and `boolean` for booleans (native Postgres types)").replace(/text\("created_at"\)\.notNull\(\)\.default\(sql`\(CURRENT_TIMESTAMP\)`\)/g,'timestamp("created_at").notNull().defaultNow()').replace(/text\("updated_at"\)\.notNull\(\)\.default\(sql`\(CURRENT_TIMESTAMP\)`\)/g,'timestamp("updated_at").notNull().defaultNow()').replace(/import { sqliteTable, text, integer } from "drizzle-orm\/sqlite-core";\nimport { sql } from "drizzle-orm";/g,'import { pgTable, text, timestamp, boolean } from "drizzle-orm/pg-core";').replace(/`drizzle-kit push` for SQLite\/Turso/g,"`drizzle-kit push` for Postgres"):r}function ed(r){let e={pass:"PASS",warn:"WARN",fail:"FAIL",fix:"FIXED",skip:"SKIP"},a=Math.max(...r.map(u=>u.name.length)),t=r.map(u=>{let h=".".repeat(a-u.name.length+4),c=e[u.status].padEnd(5);return` ${u.name} ${h} ${c} ${u.message}`}),i=r.filter(u=>u.status==="fix").length,s=r.filter(u=>u.status==="fail").length,o=r.filter(u=>u.status==="warn").length,n=[];i>0&&n.push(`${i} fixed`),s>0&&n.push(`${s} failure${s>1?"s":""}`),o>0&&n.push(`${o} warning${o>1?"s":""}`);let l=`mist_doctor results:
12505
13513
 
12506
- `+a.join(`
12507
- `);s.length>0?l+=`
13514
+ `+t.join(`
13515
+ `);n.length>0?l+=`
12508
13516
 
12509
- ${s.join(", ")}`:l+=`
13517
+ ${n.join(", ")}`:l+=`
12510
13518
 
12511
13519
  All checks passed.`;let d=r.filter(u=>(u.status==="fail"||u.status==="warn")&&u.fix);if(d.length>0){l+=`
12512
13520
 
12513
13521
  To fix:`;for(let u of d)l+=`
12514
- - ${u.fix}`}return l}var hi=wt.object({projectPath:wt.string().optional().describe("Path to the project directory. Defaults to the current working directory."),checks:wt.array(wt.enum(["project","auth","api","version","methodology","state","structure","env","plan"])).optional().describe("Run only specific checks. Omit to run all."),reportOnly:wt.boolean().optional().describe("If true, report issues without auto-fixing them.")}),gi={name:"mist_doctor",description:"Diagnose project health: checks auth, API connectivity, AGENTS.md methodology, project state, version alignment, and structure. Auto-fixes safe issues (missing AGENTS.md, stale state). Run when something feels off.",inputSchema:hi,handler:async r=>{let e=hi.parse(r),t=e.projectPath||process.cwd(),a=!e.reportOnly,i=!e.checks,n=e.checks??[],o=y=>i||n.includes(y),s=[],l=null;if(o("project")){let y=pi(t);s.push(y.result),l=y.config}else l=pi(t).config;let d=!1;if(o("auth")||o("state")){let y=await Rl();o("auth")&&s.push(y.result),d=y.authValid}let h=null;if(o("api")||o("methodology")){let y=await Ll();o("api")&&s.push(y.result),h=y.scaffold}o("version")&&s.push(Nl()),l?(o("methodology")&&s.push(Fl(t,l,h,a)),o("state")&&s.push(await Ul(t,l,d,a)),o("structure")&&s.push(El(t)),o("env")&&s.push(Hl(t,l)),o("plan")&&s.push(Ol(l))):i&&(s.push({name:"AGENTS.md",status:"skip",message:"Skipped (not a Mistflow project)."}),s.push({name:"State sync",status:"skip",message:"Skipped (not a Mistflow project)."}),s.push({name:"Structure",status:"skip",message:"Skipped (not a Mistflow project)."}),s.push({name:"Env vars",status:"skip",message:"Skipped (not a Mistflow project)."}),s.push({name:"Plan",status:"skip",message:"Skipped (not a Mistflow project)."}));let g=Gl(s),b=s.some(y=>y.status==="fail");return p(g,b)}};var Ea=new Wl({name:"mistflow",version:"0.2.0"},{capabilities:{tools:{}},instructions:`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.
13522
+ - ${u.fix}`}return l}var ki=wt.object({projectPath:wt.string().optional().describe("Path to the project directory. Defaults to the current working directory."),checks:wt.array(wt.enum(["project","auth","api","version","methodology","state","structure","env","plan"])).optional().describe("Run only specific checks. Omit to run all."),reportOnly:wt.boolean().optional().describe("If true, report issues without auto-fixing them.")}),Si={name:"mist_doctor",description:"Diagnose project health: checks auth, API connectivity, AGENTS.md methodology, project state, version alignment, and structure. Auto-fixes safe issues (missing AGENTS.md, stale state). Run when something feels off.",inputSchema:ki,handler:async r=>{let e=ki.parse(r),a=e.projectPath||process.cwd(),t=!e.reportOnly,i=!e.checks,s=e.checks??[],o=f=>i||s.includes(f),n=[],l=null;if(o("project")){let f=wi(a);n.push(f.result),l=f.config}else l=wi(a).config;let d=!1;if(o("auth")||o("state")){let f=await Vl();o("auth")&&n.push(f.result),d=f.authValid}let h=null;if(o("api")||o("methodology")){let f=await ql();o("api")&&n.push(f.result),h=f.scaffold}o("version")&&n.push(Kl()),l?(o("methodology")&&n.push(Yl(a,l,h,t)),o("state")&&n.push(await Jl(a,l,d,t)),o("structure")&&n.push(Ql(a)),o("env")&&n.push(Xl(a,l)),o("plan")&&n.push(Zl(l))):i&&(n.push({name:"AGENTS.md",status:"skip",message:"Skipped (not a Mistflow project)."}),n.push({name:"State sync",status:"skip",message:"Skipped (not a Mistflow project)."}),n.push({name:"Structure",status:"skip",message:"Skipped (not a Mistflow project)."}),n.push({name:"Env vars",status:"skip",message:"Skipped (not a Mistflow project)."}),n.push({name:"Plan",status:"skip",message:"Skipped (not a Mistflow project)."}));let g=ed(n),b=n.some(f=>f.status==="fail");return p(g,b)}};var ta=new td({name:"mistflow",version:"0.3.0"},{capabilities:{tools:{}},instructions:`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.
12515
13523
 
12516
13524
  New app workflow:
12517
13525
  1. mist_plan - pass the user's description EXACTLY as written. Do NOT expand, rephrase, or add features. If the tool returns status "confirm_new_project" (safety gate when inside an existing codebase), you MUST ask the user whether to scaffold a new Mistflow app or edit the existing code. If it returns status "clarify", present the discovery questions to the user, collect answers, and call mist_plan again with the conversationId and answers.
12518
13526
  2. mist_build mockup (optional) - generates a visual HTML wireframe for user preview. Iterative: pass feedback to refine, approved=true to lock in the design.
12519
- 3. mist_build init - scaffolds the project from the plan. Pass the planId from mist_plan.
12520
- 4. mist_build implement - executes plan steps one at a time. Call repeatedly until all steps are done. It auto-marks the previous step as completed on each call.
12521
- 5. mist_deploy - deploys to a live URL at <app-name>.mistflow.app. If staging mode is enabled, auto-deploys to a preview URL first (isolated database, expires in 72h). After QA passes, call mist_deploy promote to go live. Use mist_deploy preview for a local production server on localhost:3000.
12522
- 6. mist_build qa - tests the LIVE deployed app. Call AFTER deploy. Do NOT show the URL to the user until QA passes. If issues found, fix and redeploy, then qa again.
13527
+ 3. mist_build init - scaffolds the project from the plan and initializes git. Pass the planId from mist_plan. Returns in ~10s; does NOT run npm install.
13528
+ 4. mist_build install - runs npm install in the project just created. Pass the projectPath from the init response. Always call this after init before calling implement.
13529
+ 5. mist_build implement - executes plan steps one at a time. Call repeatedly until all steps are done. It auto-marks the previous step as completed on each call.
13530
+ 6. mist_deploy - deploys to a live URL at <app-name>.mistflow.app. If staging mode is enabled, auto-deploys to a preview URL first (isolated database, expires in 72h). After QA passes, call mist_deploy promote to go live. Use mist_deploy preview for a local production server on localhost:3000.
13531
+ 7. mist_build qa - tests the LIVE deployed app. Call AFTER deploy. Do NOT show the URL to the user until QA passes. If issues found, fix and redeploy, then qa again.
13532
+
13533
+ IMPORTANT \u2014 chaining discipline: once the user approves the plan, the init \u2192 install \u2192 implement (repeat) \u2192 build \u2192 deploy \u2192 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." The tool itself will return a status requiring user input when it actually needs one (e.g. confirm_new_project, confirm_dark_theme, awaiting promotion). Otherwise, chain calls continuously. Brief one-line status updates ("Starting step 3 of 7: Google Maps") are fine and encouraged; permission requests are not. Idea is for them to get the mist deployed url without needing to understand or manage the complexity behind it.
12523
13534
 
12524
13535
  Design presets (optional, between steps 1 and 3):
12525
13536
  - mist_project landing-designs: browse 27+ curated landing page hero designs. Pass an ID to mist_plan or mist_build init as landingDesign.
@@ -12542,4 +13553,4 @@ Other tools:
12542
13553
  - mist_project: read/update project state, browse landing designs and app styles, view runtime errors, deploy logs, deployment history, and check MCP version.
12543
13554
  - mist_config: manage encrypted app secrets (env vars) and custom domains.
12544
13555
  - mist_browser: navigate, interact with, and screenshot the app during preview or after deploy.
12545
- - mist_doctor: diagnose project health. Checks auth, API connectivity, AGENTS.md methodology, MCP version, project state, structure, and env vars. Auto-fixes safe issues (missing/outdated AGENTS.md, missing local state). Run when something feels off or tools behave unexpectedly.`}),mi=[yr,Er,Do,ri,ni,di,ci,gi];Ea.setRequestHandler(zl,async()=>({tools:mi.map(r=>({name:r.name,description:r.description,inputSchema:$l(r.inputSchema)}))}));Ea.setRequestHandler(jl,async r=>{let e=mi.find(t=>t.name===r.params.name);if(!e)return p(`Unknown tool: ${r.params.name}`,!0);try{let t=e.inputSchema.safeParse(r.params.arguments);if(!t.success){let a=t.error.issues.map(i=>`${i.path.join(".")}: ${i.message}`).join(", ");return p(`Invalid input: ${a}`,!0)}return await e.handler(t.data)}catch(t){let a=t instanceof Error?t.message:"An unexpected error occurred";return console.error("Tool error:",t),p(a,!0)}});async function Vl(){let r=process.argv.indexOf("--api-url");r!==-1&&process.argv[r+1]&&(process.env.MISTFLOW_API_URL=process.argv[r+1]),process.argv.includes("--local")&&!process.env.MISTFLOW_API_URL&&(process.env.MISTFLOW_API_URL="http://localhost:9100");let e=new _l;await Ea.connect(e),console.error(`Mistflow MCP server running on stdio (API: ${process.env.MISTFLOW_API_URL||"https://api.mistflow.ai"})`)}Vl().catch(r=>{console.error("Fatal error:",r),process.exit(1)});
13556
+ - mist_doctor: diagnose project health. Checks auth, API connectivity, AGENTS.md methodology, MCP version, project state, structure, and env vars. Auto-fixes safe issues (missing/outdated AGENTS.md, missing local state). Run when something feels off or tools behave unexpectedly.`}),Ti=[wr,Or,Eo,pi,mi,yi,xi,Si];ta.setRequestHandler(od,async()=>({tools:Ti.map(r=>({name:r.name,description:r.description,inputSchema:id(r.inputSchema)}))}));ta.setRequestHandler(rd,async r=>{let e=Ti.find(a=>a.name===r.params.name);if(!e)return p(`Unknown tool: ${r.params.name}`,!0);try{let a=e.inputSchema.safeParse(r.params.arguments);if(!a.success){let s=a.error.issues.map(o=>`${o.path.join(".")}: ${o.message}`).join(", ");return p(`Invalid input: ${s}`,!0)}let t=r.params._meta?.progressToken,i={server:ta,progressToken:t};try{return await e.handler(a.data,i)}finally{i.cleanup?.()}}catch(a){let t=a instanceof Error?a.message:"An unexpected error occurred";return console.error("Tool error:",a),p(t,!0)}});async function nd(){let r=process.argv.indexOf("--api-url");r!==-1&&process.argv[r+1]&&(process.env.MISTFLOW_API_URL=process.argv[r+1]),process.argv.includes("--local")&&!process.env.MISTFLOW_API_URL&&(process.env.MISTFLOW_API_URL="http://localhost:9100");let e=new ad;await ta.connect(e),console.error(`Mistflow MCP server running on stdio (API: ${process.env.MISTFLOW_API_URL||"https://api.mistflow.ai"})`)}nd().catch(r=>{console.error("Fatal error:",r),process.exit(1)});