@mistflow-ai/mcp 0.4.5 → 0.4.6
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 +195 -190
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{a as Et,d as Nt,e as Le}from"./chunk-UNFTM4B3.js";import{b as
|
|
1
|
+
import{a as Et,d as Nt,e as Le}from"./chunk-UNFTM4B3.js";import{b as Ss,c as co,d as po,e as Ts}from"./chunk-2X3ZTRP7.js";import{A as ao,C as ss,D as Ke,E as lo,F as rs,G as ns,H as is,I as as,J as ls,K as cs,L as ds,M as ps,N as us,O as yt,P as ms,Q as hs,R as gs,S as fs,T as Dt,U as ys,V as bs,W as ws,X as vs,Y as ks,_ as xs,b as Go,c as Wo,d as gt,e as Vo,f as io,g as _t,h as ye,i as Re,j as Ye,k as ft,l as Yo,m as xe,n as Ko,o as Jo,s as Qo,t as Xo,u as Zo,w as es,x as Oe,y as ts,z as os}from"./chunk-OIW7LXTF.js";import{Server as gc}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as fc}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as yc,ListToolsRequestSchema as bc}from"@modelcontextprotocol/sdk/types.js";import{zodToJsonSchema as wc}from"zod-to-json-schema";function d(s,o=!1){let e=s;try{let t=Wo();t&&(e=s+t)}catch{}return{content:[{type:"text",text:e}],isError:o}}function Ee(s){return d(`This is not a Mistflow project (no mistflow.json found at ${s}).
|
|
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 Ue(s,o){try{let{getPage:
|
|
10
|
+
If you want to deploy an existing project, use your framework's deploy tools directly.`,!0)}async function Ue(s,o){try{let{getPage:e,takeScreenshot:t}=await import("./browser-manager-QW3OYPFB.js"),n=await e();await n.goto(s,{waitUntil:"domcontentloaded",timeout:15e3}),await n.waitForLoadState("networkidle").catch(()=>{});let a=await t(n,!1);return{content:[{type:"text",text:o},{type:"image",data:a.toString("base64"),mimeType:"image/png"}]}}catch{return d(o)}}import{z as uo}from"zod";import{platform as _n}from"os";import{execFile as Cs}from"child_process";var Dn=uo.object({apiKey:uo.string().optional().describe("API key (mist_...) for headless auth. Skips the device code flow entirely. Generate one at app.mistflow.ai/mcp-keys."),deviceCode:uo.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 Mn(s){return"error"in s}function $n(s){return new Promise(o=>setTimeout(o,s))}function jn(s){return new Promise(o=>{let e=_n();e==="win32"?Cs("cmd.exe",["/c","start","",s],t=>{t&&console.error("Could not open browser:",t.message),o(!t)}):Cs(e==="darwin"?"open":"xdg-open",[s],n=>{n&&console.error("Could not open browser:",n.message),o(!n)}),setTimeout(()=>o(!1),5e3)})}var Ln={fetch:globalThis.fetch,openBrowser:jn};async function Ps(s,o,e,t){let n=e;for(let a=0;a<o;a++){await $n(n);let r;try{let l=await t.fetch(`${Ye()}/auth/poll`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:s})});if(!l.ok)continue;r=await l.json()}catch{continue}if(Mn(r))switch(r.error){case"authorization_pending":continue;case"slow_down":n+=5e3;continue;case"expired_token":return d("The sign-in link expired. Run mist_setup again to get a new code.",!0);case"access_denied":return d("Sign-in was cancelled. Run mist_setup again to try again.",!0);case"already_exchanged":return d("This sign-in link was already used. Run mist_setup again to get a new code.",!0)}let i=r.email||r.org_name||r.org_slug;return io({apiKey:r.api_key,apiKeyId:r.api_key_id,apiKeyName:r.api_key_name,orgId:r.org_id,orgSlug:r.org_slug,email:r.email}),d(`Connected to Mistflow as ${i}. You are ready to build and deploy.`)}return null}async function On(s,o=Ln){let e=s;if(e?.apiKey)try{let r=await o.fetch(`${Ye()}/api/org`,{headers:{Authorization:`ApiKey ${e.apiKey}`}});if(!r.ok)return d("Invalid API key. Check the key and try again.",!0);let i=await r.json();return io({apiKey:e.apiKey,orgId:i.id,orgSlug:i.slug}),d(`Connected to Mistflow as ${i.slug} via API key. You are ready to build and deploy.`)}catch{return d("Cannot reach Mistflow servers. Check your internet connection.",!0)}if(e?.deviceCode){let r=await Ps(e.deviceCode,6,5e3,o);return r||d(JSON.stringify({status:"pending",deviceCode:e.deviceCode,instruction:"The user hasn't approved yet. Wait ~15 seconds and call mist_setup again with the same deviceCode."}))}let t;try{let r=await o.fetch(`${Ye()}/auth/device`,{method:"POST",headers:{"Content-Type":"application/json"}});if(!r.ok)return d("Cannot reach Mistflow servers. Check your internet connection.",!0);t=await r.json()}catch{return d("Cannot reach Mistflow servers. Check your internet connection.",!0)}let n=`${t.verification_uri}?code=${t.user_code}`;console.error(`
|
|
11
11
|
Sign in at: ${n}
|
|
12
|
-
Your code: ${
|
|
13
|
-
`);try{await o.openBrowser(n)}catch{}let a=await
|
|
12
|
+
Your code: ${t.user_code}
|
|
13
|
+
`);try{await o.openBrowser(n)}catch{}let a=await Ps(t.device_code,6,5e3,o);return a||d(JSON.stringify({status:"pending",deviceCode:t.device_code,signInUrl:n,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 As={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:Dn,handler:s=>On(s)};import{z as O}from"zod";import{existsSync as bt,mkdirSync as $t,readFileSync as Us,readdirSync as Jn,statSync as Qn,unlinkSync as Xn,writeFileSync as jt}from"fs";import{dirname as Zn,isAbsolute as ei,join as we}from"path";import{homedir as Fe}from"os";import{createHash as ti,createHmac as Fs,randomBytes as oi,randomUUID as Os,timingSafeEqual as si}from"crypto";function Je(s,o,e,t=3e3){if(o===void 0)return{stop:()=>{}};let n=0,a=!1,i=setInterval(()=>{a||(n++,s.notification({method:"notifications/progress",params:{progressToken:o,progress:n,message:e()}}).catch(()=>{}))},t);return{stop:()=>{a||(a=!0,clearInterval(i))}}}var mo={},tt=[];function ot(s){let o=tt.find(t=>t.id===s);if(o)return o;let e=s.toLowerCase().replace(/[^a-z0-9]/g,"");return tt.find(t=>{let n=t.title.toLowerCase().replace(/[^a-z0-9]/g,"");return n===e||n.includes(e)||e.includes(n)})}function Is(s){return mo[s]}function ho(s){return s?tt.filter(o=>o.category.toLowerCase()===s.toLowerCase()):tt}function Rs(s){let o=s??tt;if(o.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 e={};for(let n of o){e[n.category]||(e[n.category]=[]);let a=mo[n.id],r=a?` \u2014 ${a.description}`:"";e[n.category].push(`${n.id} \u2014 "${n.title}"${r}`)}let t=[];for(let[n,a]of Object.entries(e))t.push(`**${n}**:
|
|
14
14
|
${a.map(r=>` \u2022 ${r}`).join(`
|
|
15
|
-
`)}`);return
|
|
15
|
+
`)}`);return t.join(`
|
|
16
16
|
|
|
17
|
-
`)}function
|
|
17
|
+
`)}function Un(s){return s.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}function Es(s){return(s?ho(s):tt).map(e=>{let t=mo[e.id];return{id:e.id,slug:Un(e.title),title:e.title,category:e.category,description:t?.description??"",tags:t?.tags??[],theme:t?.theme??"dark",colors:t?.colors??[],style:t?.style??""}})}function Ns(s,o){return[]}var go={"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"}},st=[{id:"resend-email",name:"Resend Email",category:"communication",prompt:`## Resend Email Integration
|
|
18
18
|
|
|
19
19
|
### File Structure
|
|
20
20
|
\`\`\`
|
|
@@ -1434,12 +1434,12 @@ export function ImageGenerator() {
|
|
|
1434
1434
|
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.
|
|
1435
1435
|
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.
|
|
1436
1436
|
7. **Network I/O does NOT count as CPU time on Workers.** Image generation wait time is all network I/O.
|
|
1437
|
-
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
|
|
1437
|
+
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 Fn(s){return s.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}function rt(s){let o=st.find(t=>t.id===s);if(o)return o;let e=s.toLowerCase().replace(/[^a-z0-9]/g,"");return st.find(t=>{let n=t.name.toLowerCase().replace(/[^a-z0-9]/g,"");return n===e||n.includes(e)||e.includes(n)})}function nt(s){return go[s]}function fo(s){return s?st.filter(o=>o.category.toLowerCase()===s.toLowerCase()):st}function _s(s){let o=s??st,e={};for(let n of o){e[n.category]||(e[n.category]=[]);let a=go[n.id],r=a?` \u2014 ${a.description}`:"",i=a?.packages.length?` (${a.packages.join(", ")})`:"";e[n.category].push(`${n.id} \u2014 "${n.name}"${r}${i}`)}let t=[];for(let[n,a]of Object.entries(e))t.push(`**${n}**:
|
|
1438
1438
|
${a.map(r=>` - ${r}`).join(`
|
|
1439
|
-
`)}`);return
|
|
1439
|
+
`)}`);return t.join(`
|
|
1440
1440
|
|
|
1441
|
-
`)}function
|
|
1442
|
-
`)}function
|
|
1441
|
+
`)}function Ds(s){return(s?fo(s):st).map(e=>{let t=go[e.id];return{id:e.id,slug:Fn(e.name),name:e.name,category:e.category,description:t?.description??"",tags:t?.tags??[],envVars:t?.envVars??[],docsUrl:t?.docsUrl??"",packages:t?.packages??[],difficulty:t?.difficulty??"medium"}})}var Bn=[{name:"Dashboard",description:"Overview with key stats and today's activity",condition:s=>s.surfaceType==="internal-tool"||s.surfaceType==="customer-app",keywords:/\b(dashboard|overview|home.?page|stats)\b/i},{name:"Landing Page",description:"Public page explaining what this does",condition:s=>s.publicLanding===!0,keywords:/\b(landing|marketing|hero|homepage)\b/i},{name:"Scheduling / Booking",description:"Calendar, time slots, reservations",condition:s=>s.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:s=>s.multiRole===!0||s.primaryActor==="both",keywords:/\b(admin|panel|manage.?user|moderat)\b/i},{name:"User Profiles",description:"Account pages, settings, preferences",condition:s=>s.primaryActor==="customers"||s.primaryActor==="both",keywords:/\b(profile|account|settings|preferences)\b/i},{name:"Search / Browse",description:"Find and filter content or listings",condition:s=>s.surfaceType==="marketplace",keywords:/\b(search|browse|filter|discover|explore)\b/i},{name:"Email Notifications",description:"Welcome emails, alerts, reminders",condition:s=>s.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:s=>s.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:s=>s.integrations?.includes("ai")===!0,keywords:/\b(ai|chatbot|gpt|llm|generat|assistant)\b/i},{name:"Maps / Location",description:"Google Maps, location search, geolocation",condition:s=>s.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:s=>s.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:s=>s.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:s=>s.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:s=>s.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 Ms(s,o,e){let t=e?.suggestedName||qn(s),n=o.primaryActor==="both"?"Staff + Customers":o.primaryActor==="staff"?"Staff / Admin":o.primaryActor==="customers"?"End Users":"Users",a=o.audienceType??(o.surfaceType==="internal-tool"?"internal":(o.primaryActor==="customers"||o.primaryActor==="both","b2c")),r=o.surfaceType==="internal-tool"?"Internal tool":o.surfaceType==="marketplace"?"Marketplace":o.surfaceType==="content-site"?"Content site":o.surfaceType==="game"?"Game":"App",i;if(e?.suggestedFeatures&&e.suggestedFeatures.length>0)i=e.suggestedFeatures.map(l=>({name:l.name,description:l.description,checked:l.recommended,source:l.recommended?"explicit":"suggested"}));else{let l=`${s} ${o.primaryAction||""}`;i=[];for(let c of Bn){let u=c.keywords.test(l),m=c.condition(o);(u||m)&&i.push({name:c.name,description:c.description,checked:u,source:u?"explicit":"suggested"})}}return{name:t,audience:n,audienceType:a,surfaceType:r,primaryAction:o.primaryAction||"manage items",features:i,publicLanding:o.publicLanding??!0,authModel:o.authModel??"email",dbProvider:o.dbProvider??"neon",integrations:o.integrations??[],language:e?.language||"English"}}function $s(s){let o=s.features.filter(i=>i.checked),e=s.features.filter(i=>!i.checked),t={email:"Email sign-up",none:"No login (public)",social:"Social login","invite-only":"Invite-only"},n={neon:"Postgres",turso:"SQLite (legacy)"},a={b2c:"Your customers use this app (business-to-customer)",b2b:"Other businesses sign up for this (SaaS platform)",internal:"Internal team tool (staff only)"},r=[`**${s.name}** \u2014 ${s.surfaceType} for ${s.audience}`,`Audience: ${a[s.audienceType]??s.audienceType}`,`Primary action: ${s.primaryAction}`,`Access: ${t[s.authModel]??s.authModel} | Database: ${n[s.dbProvider]??s.dbProvider}${s.publicLanding?" | Landing page: Yes":""}${s.language&&s.language!=="English"?` | Language: ${s.language}`:""}`,""];if(o.length>0){r.push("**Included:**");for(let i of o)r.push(` \u2713 ${i.name} \u2014 ${i.description}`)}if(s.integrations.length>0&&(r.push(""),r.push(`**Integrations:** ${s.integrations.join(", ")}`)),e.length>0){r.push(""),r.push("**Available to add:**");for(let i of e)r.push(` \u25CB ${i.name} \u2014 ${i.description}`)}return r.join(`
|
|
1442
|
+
`)}function qn(s){let o=s.match(/\b(?:called|named)\s+["']?([A-Za-z][A-Za-z0-9 ]{1,30})["']?/i);if(o)return yo(o[1]);let e=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"]),n=s.toLowerCase().replace(/[^a-z0-9\s]/g,"").split(/\s+/).filter(a=>a.length>2&&!e.has(a)).slice(0,3);return n.length===0?"my-app":n.join("-")}function yo(s){return s.toLowerCase().replace(/[^a-z0-9\s]/g,"").trim().replace(/\s+/g,"-")}var zn=["typographic","split-panel","terminal","full-bleed-photo","magazine-hero"],Hn=["sharp","soft","pill","organic"],Gn=["flat","paper-grain","film-grain","scanlines","gradient-mesh","noise","glassmorphic"];function bo(s,o,e){return o.includes(s)?s:e}function be(s){return String(s??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}function it(s,o){return typeof s!="string"?o:/^#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$/.test(s.trim())?s.trim():o}function Wn(s){let o=new Set;for(let t of s)t.fonts?.display&&o.add(t.fonts.display),t.fonts?.body&&o.add(t.fonts.body);return o.size===0?"":`https://fonts.googleapis.com/css2?${[...o].map(t=>`family=${t.trim().replace(/\s+/g,"+").replace(/[^A-Za-z0-9+]/g,"")}:wght@400;700`).join("&")}&display=swap`}function js(s){let o=(s||"").trim(),e=o.toLowerCase(),t=/mono|courier|code/.test(e)?"ui-monospace, monospace":/serif|garamond|bodoni|fraunces|playfair|lora|sentient|migra|sectra|cormorant|abril|crimson/.test(e)?"Georgia, serif":"system-ui, sans-serif";return o?`"${o.replace(/"/g,"")}", ${t}`:t}function Vn(s){switch(s){case"sharp":return{card:"0px",button:"0px",chip:"0px"};case"pill":return{card:"20px",button:"999px",chip:"999px"};case"organic":return{card:"4px 24px 4px 24px",button:"24px 4px 24px 4px",chip:"12px 2px 12px 2px"};default:return{card:"10px",button:"8px",chip:"6px"}}}function Yn(){let s=`url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='180' height='180'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.3 0 0 0 0 0.3 0 0 0 0 0.3 0 0 0 0.55 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>")`;return`
|
|
1443
1443
|
.card-hero { position: relative; overflow: hidden; isolation: isolate; }
|
|
1444
1444
|
.card-hero::before {
|
|
1445
1445
|
content: "";
|
|
@@ -1497,7 +1497,7 @@ ${a.map(r=>` - ${r}`).join(`
|
|
|
1497
1497
|
backdrop-filter: blur(14px);
|
|
1498
1498
|
mix-blend-mode: normal;
|
|
1499
1499
|
}
|
|
1500
|
-
`}function
|
|
1500
|
+
`}function Kn(s,o,e,t){let n=it(s.colors?.bg??"","#0f0f0f"),a=it(s.colors?.fg??"","#f5f5f5"),r=it(s.colors?.accent??"","#7c9cff"),i=be(s.name),l=be(s.hero_headline||s.name),c=be(s.body_sample||s.summary||""),u=be(s.cta_text||"Continue"),m=`<div class="hero-eyebrow" style="font-family:${e}">${i.toUpperCase()}</div>`,p=`<h2 class="hero-headline" style="font-family:${o}">${l}</h2>`,g=`<p class="hero-body" style="font-family:${e}">${c}</p>`,w=`<button class="hero-cta" style="background:${r};color:${n};font-family:${e};border-radius:${t.button}">${u}</button>`,f=bo(s.hero_treatment,zn,"typographic");if(f==="terminal"){let h=`"${(s.fonts?.display??"JetBrains Mono").replace(/"/g,"")}", ui-monospace, monospace`;return`
|
|
1501
1501
|
${m}
|
|
1502
1502
|
<div class="hero-terminal-lines" style="font-family:${h};color:${a}">
|
|
1503
1503
|
<div>$ status --all</div>
|
|
@@ -1505,59 +1505,59 @@ ${a.map(r=>` - ${r}`).join(`
|
|
|
1505
1505
|
<div>worker.queue......<span style="color:${r}">OK</span></div>
|
|
1506
1506
|
<div>db.primary.......<span style="color:${r}">OK</span></div>
|
|
1507
1507
|
</div>
|
|
1508
|
-
${
|
|
1508
|
+
${p}
|
|
1509
1509
|
${g}
|
|
1510
|
-
<div class="hero-cta-row">${
|
|
1510
|
+
<div class="hero-cta-row">${w}</div>
|
|
1511
1511
|
`}return f==="split-panel"?`
|
|
1512
1512
|
<div class="hero-split">
|
|
1513
1513
|
<div class="hero-split-left" style="background:${r};color:${n};font-family:${o}">
|
|
1514
|
-
<div class="hero-split-mark" style="font-family:${
|
|
1514
|
+
<div class="hero-split-mark" style="font-family:${e};color:${n}">${i[0]??"\xB7"}</div>
|
|
1515
1515
|
</div>
|
|
1516
1516
|
<div class="hero-split-right">
|
|
1517
1517
|
${m}
|
|
1518
|
-
${
|
|
1518
|
+
${p}
|
|
1519
1519
|
${g}
|
|
1520
|
-
<div class="hero-cta-row">${
|
|
1520
|
+
<div class="hero-cta-row">${w}</div>
|
|
1521
1521
|
</div>
|
|
1522
1522
|
</div>
|
|
1523
1523
|
`:f==="full-bleed-photo"?`
|
|
1524
1524
|
<div class="hero-photo" style="background: linear-gradient(160deg, ${r}30 0%, ${a}08 35%, ${n} 100%), ${n}">
|
|
1525
|
-
<span class="hero-photo-label" style="font-family:${
|
|
1525
|
+
<span class="hero-photo-label" style="font-family:${e};color:${a}">photo slot</span>
|
|
1526
1526
|
</div>
|
|
1527
1527
|
${m}
|
|
1528
|
-
${
|
|
1528
|
+
${p}
|
|
1529
1529
|
${g}
|
|
1530
|
-
<div class="hero-cta-row">${
|
|
1530
|
+
<div class="hero-cta-row">${w}</div>
|
|
1531
1531
|
`:f==="magazine-hero"?`
|
|
1532
1532
|
${m}
|
|
1533
1533
|
<h2 class="hero-headline hero-headline-mag" style="font-family:${o}">${l}</h2>
|
|
1534
1534
|
<div class="hero-rule" style="background:${a}"></div>
|
|
1535
|
-
<p class="hero-body hero-body-mag" style="font-family:${
|
|
1536
|
-
<div class="hero-byline" style="font-family:${
|
|
1537
|
-
<div class="hero-cta-row">${
|
|
1535
|
+
<p class="hero-body hero-body-mag" style="font-family:${e}">${c}</p>
|
|
1536
|
+
<div class="hero-byline" style="font-family:${e};color:${a}">\u2014 Volume 01 \xB7 Issue 01</div>
|
|
1537
|
+
<div class="hero-cta-row">${w}</div>
|
|
1538
1538
|
`:`
|
|
1539
1539
|
${m}
|
|
1540
|
-
${
|
|
1540
|
+
${p}
|
|
1541
1541
|
${g}
|
|
1542
|
-
<div class="hero-cta-row">${
|
|
1543
|
-
`}function
|
|
1542
|
+
<div class="hero-cta-row">${w}</div>
|
|
1543
|
+
`}function Ls(s,o){let e=Wn(o),t=o.map(n=>{let a=it(n.colors?.bg??"","#0f0f0f"),r=it(n.colors?.fg??"","#f5f5f5"),i=it(n.colors?.accent??"","#7c9cff"),l=js(n.fonts?.display??""),c=js(n.fonts?.body??""),u=bo(n.shape_lang,Hn,"soft"),m=bo(n.texture,Gn,"flat"),p=Vn(u),g=Kn(n,l,c,p),w=be(n.name),f=be(n.id),h=be(n.fonts?.display??""),v=be(n.fonts?.body??""),E=be(n.summary||""),R=be(n.decoration_hint||""),C=(P,j)=>`<div class="card-meta-row"><span class="card-meta-label">${P}</span><span class="card-meta-value">${j}</span></div>`;return` <article class="card" data-direction-id="${f}" style="border-radius:${p.card}">
|
|
1544
1544
|
<div class="card-hero texture-${m}" style="background:${a};color:${r}">${g}
|
|
1545
1545
|
</div>
|
|
1546
1546
|
<div class="card-meta" style="border-top-color:${r}14">
|
|
1547
|
-
<div class="card-meta-title">${
|
|
1548
|
-
<p class="card-meta-summary">${
|
|
1549
|
-
${
|
|
1550
|
-
${
|
|
1547
|
+
<div class="card-meta-title">${w}</div>
|
|
1548
|
+
<p class="card-meta-summary">${E}</p>
|
|
1549
|
+
${C("Fonts",`${h||"\u2014"} <span class="sep">\xB7</span> ${v||"\u2014"}`)}
|
|
1550
|
+
${C("Palette",`
|
|
1551
1551
|
<span class="card-meta-swatches">
|
|
1552
1552
|
<span class="card-meta-swatch" title="${a}" style="background:${a}"></span>
|
|
1553
1553
|
<span class="card-meta-swatch" title="${r}" style="background:${r}"></span>
|
|
1554
1554
|
<span class="card-meta-swatch" title="${i}" style="background:${i}"></span>
|
|
1555
1555
|
</span>
|
|
1556
1556
|
`)}
|
|
1557
|
-
${
|
|
1558
|
-
${
|
|
1559
|
-
${
|
|
1560
|
-
${
|
|
1557
|
+
${C("Shape",`<span class="chip" style="border-radius:${p.chip}">${u}</span>`)}
|
|
1558
|
+
${C("Texture",`<span class="chip" style="border-radius:${p.chip}">${be(m)}</span>`)}
|
|
1559
|
+
${R?C("Decoration",be(R)):""}
|
|
1560
|
+
${C("Pick with",`<code>${w}</code>`)}
|
|
1561
1561
|
</div>
|
|
1562
1562
|
</article>`}).join(`
|
|
1563
1563
|
`);return`<!DOCTYPE html>
|
|
@@ -1565,10 +1565,10 @@ ${a.map(r=>` - ${r}`).join(`
|
|
|
1565
1565
|
<head>
|
|
1566
1566
|
<meta charset="UTF-8" />
|
|
1567
1567
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
1568
|
-
<title>Design directions \u2014 ${
|
|
1568
|
+
<title>Design directions \u2014 ${be(s)}</title>
|
|
1569
1569
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
1570
1570
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
1571
|
-
${
|
|
1571
|
+
${e?`<link rel="stylesheet" href="${e}" />`:""}
|
|
1572
1572
|
<style>
|
|
1573
1573
|
:root {
|
|
1574
1574
|
--chrome-bg: #f4f3ef;
|
|
@@ -1752,7 +1752,7 @@ ${a.map(r=>` - ${r}`).join(`
|
|
|
1752
1752
|
margin-top: 4px;
|
|
1753
1753
|
}
|
|
1754
1754
|
|
|
1755
|
-
${
|
|
1755
|
+
${Yn()}
|
|
1756
1756
|
|
|
1757
1757
|
/* \u2500\u2500 Card meta footer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
1758
1758
|
.card-meta {
|
|
@@ -1861,12 +1861,12 @@ ${a.map(r=>` - ${r}`).join(`
|
|
|
1861
1861
|
<div class="page">
|
|
1862
1862
|
<header class="page-header">
|
|
1863
1863
|
<div class="eyebrow"><span class="eyebrow-dot"></span>Mistflow \xB7 design direction</div>
|
|
1864
|
-
<h1 class="page-title">How should <strong>${
|
|
1864
|
+
<h1 class="page-title">How should <strong>${be(s)}</strong> feel?</h1>
|
|
1865
1865
|
<p class="page-sub">Each card commits to a different extreme \u2014 different fonts, colors, hero layout, corner radius, texture, and voice. Pick one by telling your coding assistant its name, or describe your own.</p>
|
|
1866
1866
|
</header>
|
|
1867
1867
|
|
|
1868
1868
|
<div class="grid">
|
|
1869
|
-
${
|
|
1869
|
+
${t}
|
|
1870
1870
|
</div>
|
|
1871
1871
|
|
|
1872
1872
|
<footer class="page-footer">
|
|
@@ -1881,22 +1881,27 @@ ${e}
|
|
|
1881
1881
|
</div>
|
|
1882
1882
|
</body>
|
|
1883
1883
|
</html>
|
|
1884
|
-
`}var Mt="__mistflow_url_choice__",
|
|
1885
|
-
`)}),!0);if(
|
|
1886
|
-
`)}))}
|
|
1887
|
-
|
|
1888
|
-
**Your app URL:** https://${
|
|
1889
|
-
`:"",
|
|
1890
|
-
|
|
1891
|
-
${
|
|
1892
|
-
`:"",`I have ${
|
|
1893
|
-
`)}))}if(
|
|
1894
|
-
`)}))}let _=z.plan,ee=_.name??"Untitled App",Y=z.methodology,B=_.steps;if(!Array.isArray(B)||B.length===0)return d("Plan generation incomplete \u2014 the plan is missing implementation steps. Please call mist_plan again with the same description to retry.",!0);let N=_.publicPages;if(!N||Array.isArray(N)&&N.length===0){let v=_.pages,T=B.some(j=>typeof j.name=="string"&&j.name.toLowerCase().includes("landing")||typeof j.title=="string"&&j.title.toLowerCase().includes("landing")),$=Array.isArray(v)&&v.some(j=>j.path==="/"||j.route==="/");T||$?N=["/","/pricing"]:N=["/"]}let le=_.primaryAction;if(!le){let v=_.features;if(Array.isArray(v)&&v.length>0){let $=v.find(de=>typeof de.priority=="string"&&de.priority.toLowerCase()==="must-have")??v[0];le={entity:$.name??$.title??"item",action:"create",fromPage:"/dashboard"}}}let ce=_.nonNegotiables;(!ce||Array.isArray(ce)&&ce.length===0)&&(ce=["Landing page renders correctly at / with content (not a redirect)","Core user action works end-to-end (create entity, see it in list)"]);let me=_s(),re=ve(Fe(),".mistflow","plans");$t(re,{recursive:!0});try{let T=Date.now();for(let $ of[re,ve(Fe(),".mistflow","mockup-state")])if(bt($))for(let j of Bn($))try{let de=ve($,j),ie=qn(de).mtimeMs;T-ie>6048e5&&zn(de)}catch{}}catch{}let k;if(u){let v=ot(u);v?k=v.id:console.error(`Landing design '${u}' not found \u2014 ignoring. Use mist_project action='landing-designs' to browse available landing designs.`)}let M=g||void 0,U=!!k,y=B.some(v=>{let T=`${v.name??v.title} ${v.description??""}`.toLowerCase();return T.includes("landing")||T.includes("hero")||T.includes("marketing")||T.includes("homepage")}),R=!U&&y?Ps(t,{maxResults:2}):[];R.length>0&&(k=R[0].id,console.error(`Auto-assigned landing layout preset (default): ${R[0].title} (${k})`));let te={name:_.name,summary:_.summary,dataModel:_.dataModel,pages:_.pages,features:_.features,steps:B.map(v=>({...v,name:v.name??v.title})),design:_.design,landingDesign:k,appStyle:M,dbProvider:_.dbProvider??"neon",authModel:_.authModel,audienceType:_.audienceType??"b2c",roles:_.roles,defaultRole:_.defaultRole,publicPages:N,navStyle:_.navStyle,multiTenant:_.multiTenant,primaryAction:le,nonNegotiables:ce,requestedSubdomain:L,...m&&m.toLowerCase()!=="english"?{language:m}:{}};jt(ve(re,`${me}.json`),JSON.stringify({plan:te,methodology:Y}));let Pe=B.map(v=>`${v.number}. ${v.name??v.title}`),ge="",S=[],D;!U&&R.length>0&&(S=R.map(T=>({id:T.id,slug:T.slug,title:T.title,description:T.description,url:`${ft()}/designs?tab=landing-designs`})),D={question:"What landing page style fits this app?",header:"Landing Design",options:[...R.map((T,$)=>({label:$===0?`${T.title} (Recommended)`:T.title,description:T.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 ${ft()}/designs?tab=landing-designs and pass the ID back.`}],multiSelect:!1},ge=` 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: ${R.map(T=>T.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='${R[0].id}' explicitly.`);let G="",J=[],oe=void 0,A=(_.audienceType??"b2c")==="b2c",K={question:"Include a lifestyle photo in your landing page hero? Photos add warmth and human context; pure CSS stays cleaner.",header:"Hero",options:[{label:A?"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:A?"No, CSS only":"No, CSS only (Recommended)",description:"Animated gradients + glassmorphism, no photo \u2014 cleaner and more technical (like Stripe, Linear, Vercel)."}],multiSelect:!1},ne=" 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.",X="",Ae=[];for(let v of B){let T=v.name??v.title,$=v.integrationId;if($){let j=rt($);if(j){let de=nt(j.id);Ae.push({step:T,presetId:j.id,presetName:j.name,envVars:de?.envVars??[]})}}}if(Ae.length>0){let v=Ae.flatMap(j=>j.envVars),T=[...new Set(v.map(j=>j.key))];X=` This plan uses integrations (${Ae.map(j=>j.presetName).join(", ")}). Detailed blueprints will be auto-injected during each integration step.${T.length>0?` The user will need these API keys: ${T.join(", ")}.`:""}`}return d(JSON.stringify({planId:me,name:_.name,summary:_.summary,stepCount:B.length,steps:Pe,design:_.design,...k?{landingDesign:k}:{},...M?{appStyle:M}:{},...J.length>0?{recommendedAppStyles:J}:{},...oe?{appStyleQuestion:oe}:{},...S.length>0?{recommendedLandingDesigns:S}:{},...D?{landingDesignQuestion:D}:{},heroPhotoQuestion:K,...Ae.length>0?{integrations:Ae.map(v=>({step:v.step,preset:v.presetId,name:v.presetName,envVars:v.envVars}))}:{},message:`Plan generated for "${ee}" (${B.length} steps).${k?` Landing layout "${k}" set as default.`:""}${M?` App style "${M}" will be applied across all pages.`:""}${X}${G}${ge}${ne}`,timingContext:`Planning took ~90 seconds. Building will take roughly ${Math.max(15,B.length*3)}\u2013${B.length*5} minutes total across ${B.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='${me}'. If the user says skip or "just build it", call mist_build with action='init', name='${ee}', and planId='${me}' immediately.`,...E?{warning:E}:{}}))}var Ls={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.","","PREFER the `mist plan` CLI (`npx -y @mistflow-ai/cli plan --describe '<desc>' --json`) via your shell/bash tool \u2014 the backend Sonnet call can take 30-150s, which reliably hits the MCP 60s timeout ceiling even though the backend finishes. The CLI has no ceiling and streams progress. This MCP tool remains as a fallback for environments without shell access and retains identical backend behaviour.","","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(`
|
|
1895
|
-
`),inputSchema:
|
|
1896
|
-
`).
|
|
1897
|
-
`).
|
|
1898
|
-
|
|
1899
|
-
|
|
1884
|
+
`}var Mt="__mistflow_url_choice__",ri=600*1e3;function Bs(){let s=we(Fe(),".mistflow","confirm-secret");if(bt(s))try{return Buffer.from(Us(s,"utf-8").trim(),"hex")}catch{}let o=oi(32);return $t(we(Fe(),".mistflow"),{recursive:!0}),jt(s,o.toString("hex"),{mode:384}),o}function qs(s){return ti("sha256").update(s.trim().toLowerCase()).digest("hex").slice(0,16)}function ni(s,o){let e={cwd:s,d:qs(o),exp:Date.now()+ri},t=Buffer.from(JSON.stringify(e)).toString("base64url"),n=Fs("sha256",Bs()).update(t).digest("base64url");return`${t}.${n}`}function ii(s,o,e){let t=s.split(".");if(t.length!==2)return!1;let[n,a]=t,r=Fs("sha256",Bs()).update(n).digest("base64url"),i=Buffer.from(a),l=Buffer.from(r);if(i.length!==l.length||!si(i,l))return!1;try{let c=JSON.parse(Buffer.from(n,"base64url").toString("utf-8"));return!(typeof c.exp!="number"||Date.now()>c.exp||c.cwd!==o||c.d!==qs(e))}catch{return!1}}function ai(s){let o=s,e=Fe(),t=!1;for(let n=0;n<64;n++){if(bt(we(o,"mistflow.json")))return"mistflow";if(!t&&bt(we(o,"package.json"))&&(t=!0),o===e)break;let a=Zn(o);if(a===o)break;o=a}return t?"foreign":"none"}function li(s){let o=Fe(),e=s.replace(/\/+$/,"");if(e===o||e==="/"||e===""||e==="/tmp"||e==="/private/tmp")return!0;let t=["Desktop","Documents","Downloads"];for(let n of t)if(e===we(o,n))return!0;return!1}var ci=O.object({description:O.string().min(1,"App description or modification request"),projectPath:O.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:O.string().optional().describe("Returned by a previous mist_plan call with status 'clarify'. Pass it back to continue the conversation."),answers:O.record(O.string()).optional().describe("User's answers to the clarifying questions from the previous round. Keys are the questions, values are the answers."),existingPlan:O.record(O.unknown()).optional().describe("If provided, modifies this existing plan instead of creating a new one. Pass the current plan object from mistflow.json."),existingPlanId:O.string().optional().describe("Alternative to existingPlan \u2014 pass the planId from a previous mist_plan call to modify that plan."),templateToken:O.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:O.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:O.boolean().optional().describe("Skip clarifying questions and generate the plan immediately"),landingDesign:O.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:O.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:O.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:O.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:O.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:O.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."),designConversationId:O.string().optional().describe("Returned by a previous mist_plan call with status 'design_clarify'. Pass it back on the follow-up call together with designDirection to finalize the plan's DESIGN.md."),designDirection:O.object({id:O.string().optional(),name:O.string().optional(),summary:O.string().optional(),heroHeadline:O.string().optional(),ctaText:O.string().optional(),bodySample:O.string().optional(),fontsHint:O.string().optional(),fonts:O.object({display:O.string(),body:O.string()}).partial().optional(),colorMood:O.string().optional(),colors:O.object({bg:O.string(),fg:O.string(),accent:O.string()}).partial().optional(),heroTreatment:O.string().optional(),shapeLang:O.string().optional(),texture:O.string().optional(),decorationHint:O.string().optional(),custom:O.string().optional()}).passthrough().optional().describe("The creative direction the user picked from a previous 'design_clarify' response. Pass the FULL direction object the user chose (all fields from the 'directions' array). If the user wrote their own description instead of picking one, pass { custom: '<their description>' } and omit the other fields.")});function di(s){let o=[[/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,n]of o)if(t.test(s))return n;return s.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 pi(s){let o=we(Fe(),".mistflow","plans",`${s}.json`);if(!bt(o))return null;try{return JSON.parse(Us(o,"utf-8")).plan??null}catch{return null}}async function ui(s){for(let t=0;t<60;t++){try{let n=await ts(s.design_conversation_id);if(n.status==="ready")return{status:"design_clarify",design_conversation_id:s.design_conversation_id,directions:n.directions,plan:n.plan,methodology:n.methodology};if(n.status==="failed")return{status:"ready",plan:n.plan,methodology:n.methodology}}catch(n){let a=n instanceof Error?n.message:String(n);if(a.toLowerCase().includes("not found"))return{status:"ready",plan:s.plan,methodology:s.methodology};console.error(`[plan] directions poll attempt ${t+1} failed: ${a}`)}await new Promise(n=>setTimeout(n,2e3))}return console.error("[plan] directions poll exhausted, falling back to ready"),{status:"ready",plan:s.plan,methodology:s.methodology}}async function mi(s,o){let{description:e,projectPath:t,conversationId:n,answers:a,existingPlan:r,existingPlanId:i,templateToken:l,remixDescription:c,autonomous:u,language:m,landingDesign:p,appStyle:g,brandMentioned:w,confirmToken:f,urlChoice:h,designConversationId:v,designDirection:E}=s,R=r;if(!R&&i&&(R=pi(i)??void 0,!R))return d("Your previous plan is no longer available. Please describe your app again to generate a new plan.",!0);let C=n;if(!ye())return d("No Mistflow credentials found. Run mist_setup to connect your account.",!0);let P;if(!C&&!R&&!l){if(!ei(t))return d(`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 b=ai(t);if(b!=="mistflow"&&li(t))return d(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(`
|
|
1885
|
+
`)}),!0);if(b==="foreign"&&!w){if(!(f?ii(f,t,e):!1)){let $=ni(t,e);return d(JSON.stringify({status:"confirm_new_project",projectPath:t,description:e,confirmToken:$,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.",f?"The previous confirmToken was invalid, expired, or did not match the current directory/description. Use the fresh token above.":""].filter(Boolean).join(`
|
|
1886
|
+
`)}))}P="Note: You're inside an existing project. Mistflow will create the new app in a subdirectory. It won't modify this codebase."}else b==="foreign"&&w&&(P="Note: You're inside an existing project. Mistflow will create the new app in a subdirectory. It won't modify this codebase.")}if(l)try{if(!(await ws(l)).plan)return d("This template has no plan to fork. Try a different template.",!0);let k=await vs(l),$=k.plan,L="";if(c&&k.has_source)try{let fe=await ao(k.plan,c),X=fe.plan??fe,Rt=fe.diff,ne=X?.steps??[],no=new Set([...(Rt?.added??[]).map(Ie=>Ie.number),...(Rt?.modified??[]).map(Ie=>Ie.number)]),je=ne.map(Ie=>{let Nn=Ie.number;return no.has(Nn)?{...Ie,status:"pending"}:{...Ie,status:"completed",source:"forked"}});X.steps=je,$=X;let Ve=je.filter(Ie=>Ie.status==="pending").length;L=` Remixed: ${je.filter(Ie=>Ie.status==="completed").length} steps unchanged, ${Ve} steps need re-implementation.`}catch(fe){console.error("[plan] Remix failed, using original plan:",fe),L=" (Remix failed \u2014 using original plan. You can modify it later.)"}let ce=Os(),re=we(Fe(),".mistflow","plans");$t(re,{recursive:!0}),jt(we(re,`${ce}.json`),JSON.stringify({plan:$,projectId:k.id,sourceDeploymentId:k.source_deployment_id,forkToken:k.fork_token,requiredEnvVars:k.required_env_vars,dbProvider:k.db_provider}));let he=$?.name??"forked-app",ge=k.has_source,ht=ge?"Source code will be restored during init. Run init promptly \u2014 the download token expires in 1 hour.":"",We=k.deploy_url?` Instant deploy started \u2014 your app will be live at ${k.deploy_url} in under a minute.`:"";return d(JSON.stringify({planId:ce,forkedFrom:k.forked_from,projectId:k.id,hasSource:ge,deployUrl:k.deploy_url,message:`Forked "${k.forked_from}" into your workspace.${L}${We} ${ht} NEXT: Call mist_build with action='init', name='${he}', and planId='${ce}' to create the project now.`}))}catch(b){let k=b instanceof Error?b.message:"Failed to fork template";return d(k,!0)}if(R){let b;try{b=await ao(R,e)}catch(re){let he=re instanceof Error?re.message:"Failed to modify plan";return d(he,!0)}let k=b.plan,$=b.diff,L=[];if($?.added?.length){let re=$.added.map(he=>he.title);L.push(`Added ${re.length} step(s): ${re.join(", ")}`)}if($?.removed?.length){let re=$.removed.map(he=>he.title);L.push(`Removed ${re.length} step(s): ${re.join(", ")}`)}if($?.modified?.length){let re=$.modified.map(he=>he.title);L.push(`Modified ${re.length} step(s): ${re.join(", ")}`)}let ce=L.length>0?L.join(". "):"No changes detected.";return d(JSON.stringify({plan:k,diff:$,message:`Plan modified. ${ce}. Update mistflow.json with the new plan, then continue with mist_build (action: 'implement').`}))}let j=h?.trim()||void 0,S=a;if(!j&&a&&Mt in a&&(j=a[Mt]),a&&Mt in a){let{[Mt]:b,...k}=a;S=Object.keys(k).length>0?k:void 0}if(j&&(j=j.replace(/^Keep\s+/i,"").replace(/\s*\(Recommended\)\s*$/i,"").replace(/\.mistflow\.app.*$/i,"").trim()||void 0),j){let b=j.toLowerCase().replace(/\s+/g,"-");/^[a-z0-9][a-z0-9-]{1,30}[a-z0-9]$/.test(b)?j=b:(console.error(`[mist_plan] Discarding urlChoice '${j}' \u2014 does not look like a subdomain. Backend will auto-generate.`),j=void 0)}let z;if(E){z={...E};let b={fontsHint:"fonts_hint",colorMood:"color_mood",heroHeadline:"hero_headline",ctaText:"cta_text",bodySample:"body_sample",heroTreatment:"hero_treatment",shapeLang:"shape_lang",decorationHint:"decoration_hint"};for(let[k,$]of Object.entries(b))E[k]!==void 0&&z[$]===void 0&&(z[$]=E[k])}let Y=a?"Generating plan with your answers (LLM call)":v?"Finalizing design direction":"Thinking through discovery questions",G=o?Je(o.server,o.progressToken,()=>Y):{stop:()=>{}};o&&(o.cleanup=()=>G.stop());let W;try{W=await os(e,{conversationId:C,answers:S,autonomous:u,language:m,designConversationId:v,designDirection:z})}catch(b){G.stop();let k=b instanceof Error?b.message:"Failed to generate plan";return d(k,!0)}if(W.status==="design_clarify_pending"&&(Y="Generating creative design directions",W=await ui(W)),G.stop(),W.status==="clarify"){let b=W.reflection||"",k=W.suggestedName||"",$=W.suggestedFeatures??[],L=W.questions??[],ce=L.some(ne=>Array.isArray(ne.options)&&typeof ne.options[0]=="object"&&ne.options[0]?.label),re={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"},he=L.map(ne=>{let no=ne.decisionKey&&re[ne.decisionKey]||di(ne.question),je;return ce&&Array.isArray(ne.options)?je=ne.options.map(Ve=>({label:Ve.label,description:Ve.description??""})):Array.isArray(ne.options)?je=ne.options.map((Ve,Ho)=>({label:Ho===0?`${Ve} (Recommended)`:String(Ve),description:ne.why??""})):je=[{label:"Yes (Recommended)",description:ne.why??""},{label:"No",description:""}],{question:ne.question,header:no,options:je,multiSelect:!1}}),ht=W.decisions?.audienceType??null,We=$.length>0?Ms(e,{primaryActor:null,primaryAction:null,surfaceType:null,audienceType:ht,multiRole:null,publicLanding:null,realMoney:null,scheduling:null,authModel:null,dbProvider:null,integrations:null},{suggestedName:k,suggestedFeatures:$,language:m}):null,fe=We?$s(We):"",X=yo(k||"my-app").slice(0,32);try{let ne=await Xo(X);!ne.available&&ne.suggestion&&(X=ne.suggestion)}catch{}fe&&(fe+=`
|
|
1887
|
+
|
|
1888
|
+
**Your app URL:** https://${X}.mistflow.app`);let Rt={question:`Your app will be at ${X}.mistflow.app \u2014 want to customize the URL?`,header:"URL",options:[{label:`Keep ${X}.mistflow.app (Recommended)`,description:"This URL is available"},{label:"Choose a different URL",description:"Type your preferred subdomain"}],multiSelect:!1};return he.push(Rt),d(JSON.stringify({status:"clarify",conversation_id:W.conversation_id,questions:L,questionCount:L.length,suggestedFeatures:$,suggestedName:k,suggestedSubdomain:X,reflection:b,briefText:fe,askUserQuestions:he,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:[...P?[P,""]:[],b?`${b}
|
|
1889
|
+
`:"",fe?`Here's what I'd build:
|
|
1890
|
+
|
|
1891
|
+
${fe}
|
|
1892
|
+
`:"",`I have ${L.length} quick question${L.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: "${W.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: "${X}".`,'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(`
|
|
1893
|
+
`)}))}if(W.status==="design_clarify"){let b=W.directions??[],k=W.plan.name??"your app",$;try{let ge=b.map(X=>({id:X.id,name:X.name,summary:X.summary,hero_headline:X.hero_headline,cta_text:X.cta_text,body_sample:X.body_sample,fonts:X.fonts,colors:X.colors,hero_treatment:X.hero_treatment,shape_lang:X.shape_lang,texture:X.texture,decoration_hint:X.decoration_hint})),ht=Ls(k,ge),We=we(t,".mistflow");$t(We,{recursive:!0});let fe=we(We,"design-directions.html");jt(fe,ht,"utf-8"),$=fe}catch(ge){console.error(`[mist_plan] design-directions preview render failed: ${ge instanceof Error?ge.message:String(ge)}`)}let L=b.map(ge=>({label:ge.name,description:`${ge.summary} \u2014 ${ge.fonts?.display??""} + ${ge.fonts?.body??""}`}));L.push({label:"Describe your own direction",description:"Skip the proposed options and give me a short description of how the app should feel."});let ce={question:`${k} is planned. Now pick the creative direction \u2014 this shapes fonts, colors, and overall feel.`,header:"Design",options:L,multiSelect:!1},re=$?`A visual preview of all ${b.length} directions has been written to ${$}. Open it in the browser \u2014 each card is rendered in its direction's own fonts + palette so the user can see what they're picking.`:"No visual preview rendered (see server logs). The AskUserQuestion options below carry fonts + mood so the user can still pick.",he=$?`open "${$}"`:"";return d(JSON.stringify({status:"design_clarify",designConversationId:W.design_conversation_id,directions:b,previewPath:$,askUserQuestion:ce,instruction:[`The plan for "${k}" is ready. I've proposed ${b.length} creative directions \u2014 each commits to a specific aesthetic (fonts, colors, voice).`,"",re,he?`Run this command for the user: ${he}`:"","","MANDATORY: After opening the preview, use the AskUserQuestion tool to present the directionQuestion above. Do NOT present it as text.","","Once the user picks a direction, call mist_plan again with:"," description: (same description as before)",` designConversationId: "${W.design_conversation_id}"`," designDirection: <the full direction object from the 'directions' array that the user picked>","","IF the user picks 'Describe your own direction':"," Ask them a short open question ('How should the app feel? Any fonts or colors in mind?')."," Then call mist_plan with designDirection: { custom: '<their description>' } and the same designConversationId.","","The next mist_plan call takes ~10-20s \u2014 that's the LLM generating the final DESIGN.md with the picked direction. Tell the user 'Locking in the direction now \u2014 this takes about 15 seconds.' before calling."].filter(Boolean).join(`
|
|
1894
|
+
`)}))}let _=W.plan,ee=_.name??"Untitled App",J=W.methodology,q=_.steps;if(!Array.isArray(q)||q.length===0)return d("Plan generation incomplete \u2014 the plan is missing implementation steps. Please call mist_plan again with the same description to retry.",!0);let F=_.publicPages;if(!F||Array.isArray(F)&&F.length===0){let b=_.pages,k=q.some(L=>typeof L.name=="string"&&L.name.toLowerCase().includes("landing")||typeof L.title=="string"&&L.title.toLowerCase().includes("landing")),$=Array.isArray(b)&&b.some(L=>L.path==="/"||L.route==="/");k||$?F=["/","/pricing"]:F=["/"]}let ie=_.primaryAction;if(!ie){let b=_.features;if(Array.isArray(b)&&b.length>0){let $=b.find(ce=>typeof ce.priority=="string"&&ce.priority.toLowerCase()==="must-have")??b[0];ie={entity:$.name??$.title??"item",action:"create",fromPage:"/dashboard"}}}let le=_.nonNegotiables;(!le||Array.isArray(le)&&le.length===0)&&(le=["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=Os(),me=we(Fe(),".mistflow","plans");$t(me,{recursive:!0});try{let k=Date.now();for(let $ of[me,we(Fe(),".mistflow","mockup-state")])if(bt($))for(let L of Jn($))try{let ce=we($,L),re=Qn(ce).mtimeMs;k-re>6048e5&&Xn(ce)}catch{}}catch{}let x;if(p){let b=ot(p);b?x=b.id:console.error(`Landing design '${p}' not found \u2014 ignoring. Use mist_project action='landing-designs' to browse available landing designs.`)}let D=g||void 0,M=!!x,y=q.some(b=>{let k=`${b.name??b.title} ${b.description??""}`.toLowerCase();return k.includes("landing")||k.includes("hero")||k.includes("marketing")||k.includes("homepage")}),N=!M&&y?Ns(e,{maxResults:2}):[];N.length>0&&(x=N[0].id,console.error(`Auto-assigned landing layout preset (default): ${N[0].title} (${x})`));let te={name:_.name,summary:_.summary,dataModel:_.dataModel,pages:_.pages,features:_.features,steps:q.map(b=>({...b,name:b.name??b.title})),design:_.design,landingDesign:x,appStyle:D,dbProvider:_.dbProvider??"neon",authModel:_.authModel,audienceType:_.audienceType??"b2c",roles:_.roles,defaultRole:_.defaultRole,publicPages:F,navStyle:_.navStyle,multiTenant:_.multiTenant,primaryAction:ie,nonNegotiables:le,requestedSubdomain:j,...m&&m.toLowerCase()!=="english"?{language:m}:{}};jt(we(me,`${K}.json`),JSON.stringify({plan:te,methodology:J}));let De=q.map(b=>`${b.number}. ${b.name??b.title}`),ae="",Ce=[],pe;!M&&N.length>0&&(Ce=N.map(k=>({id:k.id,slug:k.slug,title:k.title,description:k.description,url:`${ft()}/designs?tab=landing-designs`})),pe={question:"What landing page style fits this app?",header:"Landing Design",options:[...N.map((k,$)=>({label:$===0?`${k.title} (Recommended)`:k.title,description:k.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 ${ft()}/designs?tab=landing-designs and pass the ID back.`}],multiSelect:!1},ae=` 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: ${N.map(k=>k.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='${N[0].id}' explicitly.`);let Ae="",T=[],A=void 0,U=(_.audienceType??"b2c")==="b2c",ke={question:"Include a lifestyle photo in your landing page hero? Photos add warmth and human context; pure CSS stays cleaner.",header:"Hero",options:[{label:U?"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:U?"No, CSS only":"No, CSS only (Recommended)",description:"Animated gradients + glassmorphism, no photo \u2014 cleaner and more technical (like Stripe, Linear, Vercel)."}],multiSelect:!1},se=" 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.",I="",H=[];for(let b of q){let k=b.name??b.title,$=b.integrationId;if($){let L=rt($);if(L){let ce=nt(L.id);H.push({step:k,presetId:L.id,presetName:L.name,envVars:ce?.envVars??[]})}}}if(H.length>0){let b=H.flatMap(L=>L.envVars),k=[...new Set(b.map(L=>L.key))];I=` This plan uses integrations (${H.map(L=>L.presetName).join(", ")}). Detailed blueprints will be auto-injected during each integration step.${k.length>0?` The user will need these API keys: ${k.join(", ")}.`:""}`}return d(JSON.stringify({planId:K,name:_.name,summary:_.summary,stepCount:q.length,steps:De,design:_.design,...x?{landingDesign:x}:{},...D?{appStyle:D}:{},...T.length>0?{recommendedAppStyles:T}:{},...A?{appStyleQuestion:A}:{},...Ce.length>0?{recommendedLandingDesigns:Ce}:{},...pe?{landingDesignQuestion:pe}:{},heroPhotoQuestion:ke,...H.length>0?{integrations:H.map(b=>({step:b.step,preset:b.presetId,name:b.presetName,envVars:b.envVars}))}:{},message:`Plan generated for "${ee}" (${q.length} steps).${x?` Landing layout "${x}" set as default.`:""}${D?` App style "${D}" will be applied across all pages.`:""}${I}${Ae}${ae}${se}`,timingContext:`Planning took ~90 seconds. Building will take roughly ${Math.max(15,q.length*3)}\u2013${q.length*5} minutes total across ${q.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='${ee}', and planId='${K}' immediately.`,...P?{warning:P}:{}}))}var zs={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.","","PREFER the `mist plan` CLI (`npx -y @mistflow-ai/cli plan --describe '<desc>' --json`) via your shell/bash tool \u2014 the backend Sonnet call can take 30-150s, which reliably hits the MCP 60s timeout ceiling even though the backend finishes. The CLI has no ceiling and streams progress. This MCP tool remains as a fallback for environments without shell access and retains identical backend behaviour.","","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(`
|
|
1895
|
+
`),inputSchema:ci,handler:mi};import{z as de}from"zod";import{existsSync as Gt,readFileSync as Na}from"fs";import{join as Wt,resolve as _a}from"path";import{homedir as Da}from"os";import{execFileSync as Mo}from"child_process";import{z as Lt}from"zod";import{existsSync as Me,mkdirSync as Ut,writeFileSync as Be,readFileSync as Ft,readdirSync as Ks,copyFileSync as gi}from"fs";import{join as Z,resolve as Bt,dirname as wt,isAbsolute as Po}from"path";import{spawn as fi}from"child_process";import{randomBytes as yi}from"crypto";import{simpleGit as Js}from"simple-git";function Hs(s){if(!s)return"Item";let o=s.replace(/([a-z0-9])([A-Z])/g,"$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean);return o.length===0?"Item":o.map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join("")}function vo(s){return s&&s.replace(/([a-z0-9])([A-Z])/g,"$1-$2").replace(/[^A-Za-z0-9]+/g,"-").toLowerCase().replace(/^-+|-+$/g,"")||"item"}function hi(s){let o=Hs(s);return o.charAt(0).toLowerCase()+o.slice(1)}function ko(){return["# Integration contracts","","This directory holds the single source of truth for every API shape in","this app. Frontend code and backend routes both import from here, so","drift becomes a compile error instead of a silent runtime bug.","","## The convention","","Every entity defined in `db/schema.ts` MUST have a matching contract","file in this directory. Each contract file exports three things:","","- `<Entity>Schema` \u2014 the Zod schema for reading the entity (derived from"," the Drizzle table via `createSelectSchema`).","- `Create<Entity>Input` \u2014 the Zod schema for creating a new row"," (derived via `createInsertSchema`, typically with `id` and"," `createdAt` omitted).","- `<Entity>` \u2014 the inferred TypeScript type from `<Entity>Schema`.","","File name is the kebab-case singular of the entity, e.g. `habit.ts`,","`blog-post.ts`. One entity per file.","","## Example","","```ts","// contracts/habit.ts",'import { createSelectSchema, createInsertSchema } from "drizzle-zod";','import { z } from "zod";','import { habit } from "@/db/schema";',"","export const HabitSchema = createSelectSchema(habit);","export type Habit = z.infer<typeof HabitSchema>;","","export const CreateHabitInput = createInsertSchema(habit).omit({"," id: true,"," createdAt: true,","});","export type CreateHabitInput = z.infer<typeof CreateHabitInput>;","```","","## Usage","","Frontend fetch and backend route should BOTH import from `contracts/`:","","```ts","// app/api/habits/route.ts",'import { CreateHabitInput, HabitSchema } from "@/contracts/habit";',"","export async function POST(request: Request) {"," const body = CreateHabitInput.parse(await request.json());"," const row = await db.insert(habit).values(body).returning();"," return Response.json(HabitSchema.parse(row[0]));","}","```","","```ts","// app/habits/page.tsx",'import type { Habit } from "@/contracts/habit";',"",'const res = await fetch("/api/habits");',"const habits: Habit[] = await res.json();","```","","## Adding a new entity","","1. Add the Drizzle table to `db/schema.ts`.","2. Create the matching `contracts/<entity>.ts` file using the template above.","3. Import from `contracts/` in every route, server action, and client"," component that touches the entity. Never inline the type.","","Run `mist_doctor` to check for entities that are missing a contract.",""].join(`
|
|
1896
|
+
`)}function Gs(){return["<!-- mist:contracts:start -->","## Integration contracts","","Every API shape in this app lives in `contracts/`. The directory holds","Zod schemas derived from `db/schema.ts` via `drizzle-zod`. Frontend and","backend both import from `contracts/`, so type drift becomes a compile","error instead of a runtime bug.","","### Rules for AI agents","","1. Every API route (`app/api/**/route.ts`) and server action MUST"," import its request + response types from `contracts/<entity>.ts`."," Never inline a Zod schema or a TypeScript type for an entity that"," already has a contract. Never `z.object({ ... })` inside a route"," handler for a known entity.","2. When you add a new entity to `db/schema.ts`, you MUST create the"," matching contract file BEFORE writing any route that uses it. Order"," matters: schema -> contract -> route.","3. Validate every request body with the contract's `.parse()` method."," If validation fails, return a 400 with the Zod error message \u2014 the"," contract is the boundary, not a decoration.","4. Validate every response body before returning it. Use the select"," schema's `.parse()` on the row(s) you read from the DB. This catches"," the case where the DB shape has drifted from the contract.","5. Client components that fetch an entity MUST import the inferred",' TypeScript type (`import type { Habit } from "@/contracts/habit"`)'," \u2014 never hand-write a duplicate type.","","### Contract file shape","","```ts","// contracts/<entity>.ts",'import { createSelectSchema, createInsertSchema } from "drizzle-zod";','import { z } from "zod";','import { entityTable } from "@/db/schema";',"","export const EntitySchema = createSelectSchema(entityTable);","export type Entity = z.infer<typeof EntitySchema>;","","export const CreateEntityInput = createInsertSchema(entityTable).omit({"," id: true,"," createdAt: true,","});","export type CreateEntityInput = z.infer<typeof CreateEntityInput>;","```","","### Example route using a contract","","```ts","// app/api/entities/route.ts",'import { db } from "@/lib/db";','import { entityTable } from "@/db/schema";','import { CreateEntityInput, EntitySchema } from "@/contracts/entity";',"","export async function POST(request: Request) {"," const body = CreateEntityInput.parse(await request.json());"," const [row] = await db.insert(entityTable).values(body).returning();"," return Response.json(EntitySchema.parse(row));","}","```","","Run `mist_doctor` to check every entity in `db/schema.ts` has a","contract file. Missing contracts are reported as warnings.","<!-- mist:contracts:end -->",""].join(`
|
|
1897
|
+
`)}var wo="<!-- mist:contracts:start -->",Ws="<!-- mist:contracts:end -->";function xo(s){let o=Gs();if(s.includes(wo)){let t=s.indexOf(wo),n=Ws,a=s.indexOf(n,t);if(a===-1)return s.slice(0,t)+o;let r=s.slice(a+n.length);return s.slice(0,t)+o+r.replace(/^\n+/,"")}return s.replace(/\s+$/,"")+`
|
|
1898
|
+
|
|
1899
|
+
`+o}function So(s,o){let e=Hs(s),t=o??hi(s);return['import { createSelectSchema, createInsertSchema } from "drizzle-zod";','import { z } from "zod";',`import { ${t} } from "@/db/schema";`,"","// Select schema \u2014 shape of a row read from the DB. Use this on both","// sides of the wire: backend validates responses, frontend gets types.",`export const ${e}Schema = createSelectSchema(${t});`,`export type ${e} = z.infer<typeof ${e}Schema>;`,"","// Insert schema \u2014 shape accepted when creating a new row. id +","// createdAt are generated server-side, so we strip them from the","// input contract. Adjust if your schema uses different column names.",`export const Create${e}Input = createInsertSchema(${t}).omit({`," id: true,"," createdAt: true,","});",`export type Create${e}Input = z.infer<typeof Create${e}Input>;`,""].join(`
|
|
1900
|
+
`)}function To(s){return`contracts/${vo(s)}.ts`}function bi(s){let o=wt(Bt(s)),e=10,t=0;for(;t<e&&o!==wt(o);){if(Me(Z(o,"pnpm-workspace.yaml"))||Me(Z(o,"lerna.json")))return o;let n=Z(o,"package.json");if(Me(n))try{if(JSON.parse(Ft(n,"utf-8")).workspaces)return o}catch{}o=wt(o),t++}return null}function vt(s,o,e,t,n,a){return new Promise(r=>{let i=fi(s,o,{cwd:e,stdio:["pipe","pipe","pipe"],timeout:t,env:a?{...process.env,...a}:process.env}),l="",c="";i.stdout?.on("data",u=>{let m=u.toString();if(c+=m,n)for(let p of m.split(`
|
|
1901
|
+
`).filter(Boolean))n(p)}),i.stderr?.on("data",u=>{let m=u.toString();if(l+=m,n)for(let p of m.split(`
|
|
1902
|
+
`).filter(Boolean))n(p)}),i.on("close",u=>{if(u===0)r({success:!0});else{let m=l.split(`
|
|
1903
|
+
`).find(p=>p.startsWith("npm error"))??l.slice(0,300);r({success:!1,error:m})}}),i.on("error",u=>{r({success:!1,error:u.message})})})}var id=Lt.object({name:Lt.string().min(1),plan:Lt.any(),path:Lt.string().optional()});function B(s,o,e){let t=Z(s,o);Ut(wt(t),{recursive:!0}),Be(t,e)}function Qs(s){if(!Me(s))return!0;let o;try{o=Ks(s)}catch{return!1}return o.filter(t=>t!==".mistflow").length===0}var wi={sharp:"0.125rem",subtle:"0.375rem",rounded:"0.75rem",pill:"9999px"},vi=[["--color-background","#ffffff"],["--color-foreground","#0a0a0a"],["--color-card","#ffffff"],["--color-card-foreground","#0a0a0a"],["--color-popover","#ffffff"],["--color-popover-foreground","#0a0a0a"],["--color-muted","#f5f5f5"],["--color-muted-foreground","#525252"],["--color-border","#e5e5e5"],["--color-input","#e5e5e5"],["--color-primary","#0a0a0a"],["--color-primary-foreground","#ffffff"],["--color-ring","#0a0a0a"],["--color-secondary","#f5f5f5"],["--color-secondary-foreground","#0a0a0a"],["--color-accent","#f5f5f5"],["--color-accent-foreground","#0a0a0a"],["--color-destructive","#b91c1c"],["--color-destructive-foreground","#ffffff"],["--color-success","#15803d"],["--color-success-foreground","#ffffff"],["--color-warning","#a16207"],["--color-warning-foreground","#ffffff"],["--color-info","#0369a1"],["--color-info-foreground","#ffffff"]];function ki(s){let o=s.match(/^---\n([\s\S]*?)\n---/);if(!o)return null;let e=o[1],t={theme:"light",colors:{},typography:{},rounded:{},spacing:{}},n=e.split(`
|
|
1904
|
+
`),a=null,r=null,i=l=>l.replace(/^["']|["']$/g,"").trim();for(let l of n){let c=l.replace(/\r$/,"");if(!c.trim()||c.trim().startsWith("#"))continue;if(c.startsWith("theme:")){let p=i(c.slice(6).trim());(p==="dark"||p==="light")&&(t.theme=p);continue}let u=c.match(/^(colors|typography|rounded|spacing):\s*$/);if(u){a=u[1],r=null;continue}if(a==="typography"){let p=c.match(/^ {2}([a-zA-Z0-9_-]+):\s*$/);if(p){r=p[1].replace(/-/g,"_"),t.typography[r]={};continue}let g=c.match(/^ {4}([a-zA-Z0-9_-]+):\s*(.+)$/);if(g&&r){t.typography[r][g[1]]=i(g[2]);continue}}let m=c.match(/^ {2}([a-zA-Z0-9_-]+):\s*(.+)$/);if(m&&a){let p=m[1].replace(/-/g,"_"),g=i(m[2]);a==="colors"?t.colors[p]=g:a==="rounded"?t.rounded[p]=g:a==="spacing"&&(t.spacing[p]=g)}}return t}function xi(s,o){let e=s.colors,n=[["--color-background",e.background],["--color-foreground",e.on_background??e.on_surface],["--color-card",e.surface??e.background],["--color-card-foreground",e.on_surface??e.on_background],["--color-popover",e.surface??e.background],["--color-popover-foreground",e.on_surface??e.on_background],["--color-muted",e.surface_variant??e.outline_variant??e.outline],["--color-muted-foreground",e.on_surface_variant??e.outline],["--color-border",e.outline_variant??e.outline],["--color-input",e.outline_variant??e.outline],["--color-primary",e.primary],["--color-primary-foreground",e.on_primary],["--color-ring",e.primary],["--color-secondary",e.secondary],["--color-secondary-foreground",e.on_secondary],["--color-accent",e.secondary],["--color-accent-foreground",e.on_secondary],["--color-destructive",e.error],["--color-destructive-foreground",e.on_error],["--color-success",e.success],["--color-success-foreground",e.on_success],["--color-warning",e.warning],["--color-warning-foreground",e.on_warning],["--color-info",e.info??e.primary],["--color-info-foreground",e.on_info??e.on_primary]].filter(i=>typeof i[1]=="string").map(([i,l])=>` ${i}: ${l};`).join(`
|
|
1900
1905
|
`),a=[` --radius-sm: ${s.rounded.sm??"0.25rem"};`,` --radius-md: ${s.rounded.md??o};`,` --radius-lg: ${s.rounded.lg??"0.5rem"};`,` --radius-xl: ${s.rounded.xl??"0.75rem"};`].join(`
|
|
1901
1906
|
`);return`@import "tailwindcss";
|
|
1902
1907
|
@import "tw-animate-css";
|
|
@@ -1953,11 +1958,11 @@ ${a}
|
|
|
1953
1958
|
|
|
1954
1959
|
button, [role="button"] { transition: transform 100ms var(--ease-quart-out); }
|
|
1955
1960
|
button:active:not(:disabled), [role="button"]:active:not(:disabled) { transform: scale(0.97); }
|
|
1956
|
-
`}function
|
|
1961
|
+
`}function Si(s,o,e){let t=s?.borderRadius??"subtle",n=wi[t]??"0.375rem";if(e){let r=ki(e);if(r)return xi(r,n)}return`@import "tailwindcss";
|
|
1957
1962
|
@import "tw-animate-css";
|
|
1958
1963
|
|
|
1959
1964
|
@theme {
|
|
1960
|
-
${[...
|
|
1965
|
+
${[...vi.map(([r,i])=>` ${r}: ${i};`)," --radius-sm: 0.25rem;",` --radius-md: ${n};`," --radius-lg: 0.5rem;"," --radius-xl: 0.75rem;"].join(`
|
|
1961
1966
|
`)}
|
|
1962
1967
|
}
|
|
1963
1968
|
|
|
@@ -2007,14 +2012,14 @@ ${[...di.map(([r,i])=>` ${r}: ${i};`)," --radius-sm: 0.25rem;",` --radius-md:
|
|
|
2007
2012
|
|
|
2008
2013
|
button, [role="button"] { transition: transform 100ms var(--ease-quart-out); }
|
|
2009
2014
|
button:active:not(:disabled), [role="button"]:active:not(:disabled) { transform: scale(0.97); }
|
|
2010
|
-
`}var
|
|
2015
|
+
`}var Vs={"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 Ys(s){let o=s.replace(/[^A-Za-z0-9_ -]/g,"");return Vs[o]?Vs[o]:o.replace(/\s+/g,"_")}function Ti(s){return s?{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"}[s.toLowerCase()]??"en":"en"}var Ci=new Set(["ar","he","fa","ur"]);function Pi(s,o,e){let t=s.replace(/[\\"`$]/g,""),n=Ti(e),r=Ci.has(n)?`lang="${n}" dir="rtl"`:`lang="${n}"`,i=o?.fonts?.heading,l=o?.fonts?.body;if(!i&&!l)return`import type { Metadata } from "next";
|
|
2011
2016
|
import { DM_Sans } from "next/font/google";
|
|
2012
2017
|
import { Toaster } from "sonner";
|
|
2013
2018
|
import "./globals.css";
|
|
2014
2019
|
|
|
2015
2020
|
const body = DM_Sans({ subsets: ["latin"], variable: "--font-body" });
|
|
2016
2021
|
|
|
2017
|
-
export const metadata: Metadata = { title: "${
|
|
2022
|
+
export const metadata: Metadata = { title: "${t}", description: "Built with Mistflow" };
|
|
2018
2023
|
|
|
2019
2024
|
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
2020
2025
|
return (
|
|
@@ -2023,14 +2028,14 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|
|
2023
2028
|
</html>
|
|
2024
2029
|
);
|
|
2025
2030
|
}
|
|
2026
|
-
`;let c=
|
|
2031
|
+
`;let c=Ys(i??l),u=Ys(l??i);return c===u?`import type { Metadata } from "next";
|
|
2027
2032
|
import { ${c} } from "next/font/google";
|
|
2028
2033
|
import { Toaster } from "sonner";
|
|
2029
2034
|
import "./globals.css";
|
|
2030
2035
|
|
|
2031
2036
|
const font = ${c}({ subsets: ["latin"], variable: "--font-body" });
|
|
2032
2037
|
|
|
2033
|
-
export const metadata: Metadata = { title: "${
|
|
2038
|
+
export const metadata: Metadata = { title: "${t}", description: "Built with Mistflow" };
|
|
2034
2039
|
|
|
2035
2040
|
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
2036
2041
|
return (
|
|
@@ -2040,14 +2045,14 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|
|
2040
2045
|
);
|
|
2041
2046
|
}
|
|
2042
2047
|
`:`import type { Metadata } from "next";
|
|
2043
|
-
import { ${c}, ${
|
|
2048
|
+
import { ${c}, ${u} } from "next/font/google";
|
|
2044
2049
|
import { Toaster } from "sonner";
|
|
2045
2050
|
import "./globals.css";
|
|
2046
2051
|
|
|
2047
2052
|
const heading = ${c}({ subsets: ["latin"], variable: "--font-heading" });
|
|
2048
|
-
const body = ${
|
|
2053
|
+
const body = ${u}({ subsets: ["latin"], variable: "--font-body" });
|
|
2049
2054
|
|
|
2050
|
-
export const metadata: Metadata = { title: "${
|
|
2055
|
+
export const metadata: Metadata = { title: "${t}", description: "Built with Mistflow" };
|
|
2051
2056
|
|
|
2052
2057
|
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
2053
2058
|
return (
|
|
@@ -2056,67 +2061,67 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|
|
2056
2061
|
</html>
|
|
2057
2062
|
);
|
|
2058
2063
|
}
|
|
2059
|
-
`}function Ot(s,...o){let
|
|
2060
|
-
`)}function
|
|
2061
|
-
`)}}let i=[];i.push('"use client";'),i.push(""),i.push('import { useState } from "react";'),i.push('import Link from "next/link";'),i.push('import { usePathname } from "next/navigation";'),n||i.push('import { authClient } from "@/lib/auth-client";'),i.push('import { Button } from "@/components/ui/button";'),i.push('import { Sheet, SheetContent, SheetTrigger, SheetTitle } from "@/components/ui/sheet";'),i.push('import { cn } from "@/lib/utils";'),i.push("import { Menu, "+a.join(", ")+' } from "lucide-react";'),i.push(""),i.push("interface SidebarProps {"),i.push(" user: { name: string | null; email: string; role?: string | undefined };"),i.push("}"),i.push(""),i.push("const NAV_ITEMS = [");for(let l of
|
|
2062
|
-
`)}}function
|
|
2063
|
-
`)}function
|
|
2064
|
-
`)}let
|
|
2065
|
-
`)}if(
|
|
2064
|
+
`}function Ot(s,...o){let e=JSON.stringify(s).toLowerCase();return o.some(t=>e.includes(t.toLowerCase()))}var Ai={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 Co(s){let o=s.toLowerCase().replace(/[^a-z]/g,"");for(let[e,t]of Object.entries(Ai))if(o.includes(e))return t;return"Circle"}function kt(s){return s.split("-").map(o=>o.charAt(0).toUpperCase()+o.slice(1)).join(" ")}function Ii(s){if(s.authModel==="none")return null;let o=["/login","/register","/forgot-password","/reset-password","/api/auth","/api/health","/api/webhooks","/api/admin/seed"];if(s.publicPages&&Array.isArray(s.publicPages))for(let a of s.publicPages){if(typeof a!="string"||a.length<1)continue;let r=a.replace(/[\u201C\u201D\u201E\u201F\u2018\u2019\u2033\u2036]/g,"").trim();if(!r)continue;let i=r.startsWith("/")?r:"/"+r;o.includes(i)||o.push(i)}let e=o.filter(a=>a==="/"),t=o.filter(a=>a!=="/"),n=[];n.push('import { NextRequest, NextResponse } from "next/server";'),n.push(""),n.push("const PUBLIC_PREFIXES = [");for(let a of t)n.push(' "'+a+'",');return n.push("];"),n.push(""),e.length>0&&(n.push('const PUBLIC_EXACT = ["'+e.join('", "')+'"];'),n.push("")),n.push("export function middleware(req: NextRequest) {"),n.push(" const { pathname, search } = req.nextUrl;"),n.push(""),e.length>0&&n.push(" if (PUBLIC_EXACT.includes(pathname)) return NextResponse.next();"),n.push(" if (PUBLIC_PREFIXES.some((p) => pathname.startsWith(p))) return NextResponse.next();"),n.push(""),n.push(' const token = req.cookies.get("better-auth.session_token")?.value || req.cookies.get("__Secure-better-auth.session_token")?.value;'),n.push(" if (!token) {"),n.push(' const loginUrl = new URL("/login", req.url);'),n.push(" const params = new URLSearchParams(search);"),n.push(' for (const key of ["verified", "error"]) {'),n.push(" const v = params.get(key);"),n.push(" if (v) loginUrl.searchParams.set(key, v);"),n.push(" }"),n.push(" return NextResponse.redirect(loginUrl);"),n.push(" }"),n.push(""),n.push(" return NextResponse.next();"),n.push("}"),n.push(""),n.push("export const config = {"),n.push(' matcher: ["/((?!_next|static|favicon\\\\.ico).*)"],'),n.push("};"),n.push(""),n.join(`
|
|
2065
|
+
`)}function Ri(s){if(s.navStyle==="none")return null;let o=["/api","/login","/register","/sign-in","/sign-up","/admin","/pricing","/about","/contact","/terms","/privacy","/onboarding","/join","/forgot-password","/reset-password"],t=(s.pages??[]).filter(l=>{let c=l.path??l.route??"";return c==="/"||c===""||c.includes("[")||c.replace(/^\//,"").split("/").length>1?!1:!o.some(m=>c.startsWith(m))}).map(l=>{let c=l.path??l.route??"",u=c.startsWith("/")?c:"/"+c,m=l.name??kt(c.replace(/^\//,"")),p=Co(m);return{label:m,href:u,icon:p}});t.some(l=>l.href==="/dashboard")||t.unshift({label:"Dashboard",href:"/dashboard",icon:"Home"});let n=s.authModel==="none",a=[...new Set(t.map(l=>l.icon))];n||a.push("LogOut");let r=kt(s.name);if(s.navStyle==="topbar"){let l=[];l.push('"use client";'),l.push(""),l.push('import Link from "next/link";'),l.push('import { usePathname } from "next/navigation";'),n||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 { "+a.join(", ")+' } from "lucide-react";'),l.push(""),l.push("interface TopNavProps {"),l.push(" user: { name: string | null; email: string; role?: string | undefined };"),l.push("}"),l.push(""),l.push("const NAV_ITEMS = [");for(let c of t)l.push(' { label: "'+c.label+'", href: "'+c.href+'", icon: '+c.icon+" },");return l.push("];"),l.push(""),l.push("export default function TopNav({ user }: TopNavProps) {"),l.push(" const pathname = usePathname();"),l.push(""),l.push(" return ("),l.push(' <nav className="border-b bg-card">'),l.push(' <div className="mx-auto flex h-14 max-w-7xl items-center justify-between px-4">'),l.push(' <div className="flex items-center gap-6">'),l.push(' <span className="text-lg font-semibold">'+r+"</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>"),n?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(`
|
|
2066
|
+
`)}}let i=[];i.push('"use client";'),i.push(""),i.push('import { useState } from "react";'),i.push('import Link from "next/link";'),i.push('import { usePathname } from "next/navigation";'),n||i.push('import { authClient } from "@/lib/auth-client";'),i.push('import { Button } from "@/components/ui/button";'),i.push('import { Sheet, SheetContent, SheetTrigger, SheetTitle } from "@/components/ui/sheet";'),i.push('import { cn } from "@/lib/utils";'),i.push("import { Menu, "+a.join(", ")+' } from "lucide-react";'),i.push(""),i.push("interface SidebarProps {"),i.push(" user: { name: string | null; email: string; role?: string | undefined };"),i.push("}"),i.push(""),i.push("const NAV_ITEMS = [");for(let l of t)i.push(' { label: "'+l.label+'", href: "'+l.href+'", icon: '+l.icon+" },");return i.push("];"),i.push(""),i.push('function NavContent({ pathname, user, onNavigate }: { pathname: string; user: SidebarProps["user"]; onNavigate?: () => void }) {'),i.push(" return ("),i.push(" <>"),i.push(' <div className="flex h-14 items-center border-b px-4">'),i.push(' <span className="text-lg font-semibold">'+r+"</span>"),i.push(" </div>"),i.push(' <nav className="flex-1 space-y-1 p-2">'),i.push(" {NAV_ITEMS.map((item) => ("),i.push(" <Link"),i.push(" key={item.href}"),i.push(" href={item.href}"),i.push(" onClick={onNavigate}"),i.push(' aria-current={pathname === item.href ? "page" : undefined}'),i.push(" className={cn("),i.push(' "flex items-center gap-3 rounded-md px-3 py-2.5 text-sm font-medium transition-colors",'),i.push(' pathname === item.href ? "bg-primary/10 text-primary" : "text-muted-foreground hover:bg-muted hover:text-foreground"'),i.push(" )}"),i.push(" >"),i.push(' <item.icon className="h-4 w-4" />'),i.push(" {item.label}"),i.push(" </Link>"),i.push(" ))}"),i.push(" </nav>"),i.push(' <div className="border-t p-4">'),i.push(' <div className="flex items-center gap-3">'),i.push(' <div className="flex-1 truncate">'),i.push(' <p className="truncate text-sm font-medium">{user.name ?? "User"}</p>'),i.push(' <p className="truncate text-xs text-muted-foreground">{user.email}</p>'),i.push(" </div>"),n||(i.push(" <Button"),i.push(' variant="ghost"'),i.push(' size="icon"'),i.push(' onClick={() => authClient.signOut({ fetchOptions: { onSuccess: () => { window.location.href = "/login"; } } })}'),i.push(" >"),i.push(' <LogOut className="h-4 w-4" />'),i.push(" </Button>")),i.push(" </div>"),i.push(" </div>"),i.push(" </>"),i.push(" );"),i.push("}"),i.push(""),i.push("export default function Sidebar({ user }: SidebarProps) {"),i.push(" const pathname = usePathname();"),i.push(" const [open, setOpen] = useState(false);"),i.push(""),i.push(" return ("),i.push(" <>"),i.push(' <aside className="hidden md:flex h-screen w-64 flex-col border-r bg-card">'),i.push(" <NavContent pathname={pathname} user={user} />"),i.push(" </aside>"),i.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">'),i.push(" <Sheet open={open} onOpenChange={setOpen}>"),i.push(" <SheetTrigger asChild>"),i.push(' <Button variant="ghost" size="icon" className="-ml-2">'),i.push(' <Menu className="h-5 w-5" />'),i.push(" </Button>"),i.push(" </SheetTrigger>"),i.push(' <SheetContent side="left" className="w-64 p-0">'),i.push(' <SheetTitle className="sr-only">Navigation</SheetTitle>'),i.push(' <div className="flex h-full flex-col">'),i.push(" <NavContent pathname={pathname} user={user} onNavigate={() => setOpen(false)} />"),i.push(" </div>"),i.push(" </SheetContent>"),i.push(" </Sheet>"),i.push(' <span className="text-lg font-semibold">'+r+"</span>"),i.push(" </div>"),i.push(" </>"),i.push(" );"),i.push("}"),i.push(""),{path:"components/sidebar.tsx",content:i.join(`
|
|
2067
|
+
`)}}function Ei(s){if(!s.roles||s.roles.length===0)return null;let o=s.roles,e=s.defaultRole??o[0],t=[];t.push("export type Role = "+o.map(n=>'"'+n+'"').join(" | ")+";"),t.push(""),t.push("export const ROLES = ["+o.map(n=>'"'+n+'"').join(", ")+"] as const;"),t.push(""),t.push('export const DEFAULT_ROLE: Role = "'+e+'";'),t.push(""),t.push("export const ROLE_LABELS: Record<Role, string> = {");for(let n of o){let a=n.charAt(0).toUpperCase()+n.slice(1);t.push(' "'+n+'": "'+a+'",')}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(`
|
|
2068
|
+
`)}function Ni(s){let o=kt(s.name);if(s.authModel==="none"){let a=[];return a.push("export default function HomePage() {"),a.push(" return ("),a.push(' <main className="flex min-h-screen flex-col items-center justify-center p-8">'),a.push(' <h1 className="text-4xl font-bold">'+o+"</h1>"),s.summary&&a.push(' <p className="mt-4 text-lg text-muted-foreground">'+s.summary+"</p>"),a.push(" </main>"),a.push(" );"),a.push("}"),a.push(""),a.join(`
|
|
2069
|
+
`)}let e=s.publicPages?.includes("/"),t=s.design?.landingTone;if(e&&t){let a=[];return a.push('import Link from "next/link";'),a.push(""),a.push("export default function HomePage() {"),a.push(" return ("),a.push(' <main className="flex min-h-screen flex-col">'),a.push(' <section className="flex flex-1 flex-col items-center justify-center gap-6 px-4 py-24 text-center">'),a.push(' <h1 className="text-5xl font-bold tracking-tight">'+o+"</h1>"),s.summary&&a.push(' <p className="max-w-2xl text-xl text-muted-foreground">'+s.summary+"</p>"),a.push(' <div className="flex gap-4">'),a.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">'),a.push(" Get Started"),a.push(" </Link>"),a.push(' <Link href="/login" className="inline-flex h-11 items-center rounded-md border px-8 text-sm font-medium hover:bg-muted">'),a.push(" Sign In"),a.push(" </Link>"),a.push(" </div>"),a.push(" </section>"),a.push(" </main>"),a.push(" );"),a.push("}"),a.push(""),a.join(`
|
|
2070
|
+
`)}if(e){let a=[];return a.push('import Link from "next/link";'),a.push(""),a.push("export default function HomePage() {"),a.push(" return ("),a.push(' <main className="flex min-h-screen flex-col items-center justify-center gap-6 p-8 text-center">'),a.push(' <h1 className="text-4xl font-bold">'+o+"</h1>"),s.summary&&a.push(' <p className="text-lg text-muted-foreground">'+s.summary+"</p>"),a.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">'),a.push(" Sign In"),a.push(" </Link>"),a.push(" </main>"),a.push(" );"),a.push("}"),a.push(""),a.join(`
|
|
2066
2071
|
`)}let n=[];return n.push('import { headers } from "next/headers";'),n.push('import { redirect } from "next/navigation";'),n.push('import { auth } from "@/lib/auth";'),n.push(""),n.push("export default async function HomePage() {"),n.push(" const session = await auth.api.getSession({ headers: await headers() });"),n.push(' if (session) redirect("/dashboard");'),n.push(' redirect("/login");'),n.push("}"),n.push(""),n.join(`
|
|
2067
|
-
`)}function
|
|
2068
|
-
`)}function
|
|
2069
|
-
`)}function
|
|
2070
|
-
`)}function
|
|
2071
|
-
`)}function
|
|
2072
|
-
`)}function
|
|
2073
|
-
`)}async function
|
|
2072
|
+
`)}function _i(s,o){let e=s.authModel==="none",t=[];e||(t.push('import { Suspense } from "react";'),t.push('import { headers } from "next/headers";'),t.push('import { redirect } from "next/navigation";'),t.push('import { auth } from "@/lib/auth";'),t.push('import { VerifiedBanner } from "@/components/auth/verified-banner";')),s.navStyle==="topbar"?t.push('import TopNav from "@/components/topnav";'):s.navStyle!=="none"&&t.push('import Sidebar from "@/components/sidebar";'),!e&&s.roles&&s.roles.length>0&&t.push('import { getUserRole } from "@/lib/roles";'),t.push(""),t.push("export default async function DashboardLayout({ children }: { children: React.ReactNode }) {"),e?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(""),s.roles&&s.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("");let n=e?"":"<Suspense fallback={null}><VerifiedBanner /></Suspense>";return s.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">'+n+"{children}</main>"),t.push(" </div>"),t.push(" );")):s.navStyle==="none"?(t.push(" return ("),t.push(' <div className="min-h-screen">'),t.push(' <main className="mx-auto max-w-5xl p-6">'+n+"{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">'+n+"{children}</main>"),t.push(" </div>"),t.push(" );")),t.push("}"),t.push(""),t.join(`
|
|
2073
|
+
`)}function Di(s){let o=kt(s.name),e=s.dataModel??[],t=[];if(e.length>0){let n=e.map(r=>Co(r.entity??r.name??"item")),a=[...new Set(n)];t.push('import { Card, CardContent } from "@/components/ui/card";'),t.push("import { "+a.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">'+o+"</h1>"),s.summary&&t.push(' <p className="mt-1 text-muted-foreground">'+s.summary+"</p>"),t.push(" </div>"),e.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 n of e){let a=n.entity??n.name??"Item",r=Co(a),i=kt(a.replace(/_/g,"-"));t.push(' <div className="flex items-center gap-3 rounded-md border p-3">'),t.push(" <"+r+' className="h-5 w-5 text-muted-foreground" />'),t.push(' <span className="text-sm font-medium">Add your first '+i+"</span>"),t.push(" </div>")}t.push(" </div>"),t.push(" </div>")}return t.push(" </div>"),t.push(" );"),t.push("}"),t.push(""),t.join(`
|
|
2074
|
+
`)}function Mi(s,o=!1){if(!s.multiTenant)return null;let e=[];return o?(e.push('import { pgTable, text, timestamp, index } from "drizzle-orm/pg-core";'),e.push('import { user } from "./auth";'),e.push(""),e.push('export const organization = pgTable("organization", {'),e.push(' id: text("id").primaryKey(),'),e.push(' name: text("name").notNull(),'),e.push(' slug: text("slug").unique().notNull(),'),e.push(' createdAt: timestamp("created_at").defaultNow().notNull(),'),e.push(' updatedAt: timestamp("updated_at").defaultNow().notNull(),'),e.push("});"),e.push(""),e.push("export const orgMember = pgTable(")):(e.push('import { sqliteTable, text, index } from "drizzle-orm/sqlite-core";'),e.push('import { sql } from "drizzle-orm";'),e.push('import { user } from "./auth";'),e.push(""),e.push('export const organization = sqliteTable("organization", {'),e.push(' id: text("id").primaryKey(),'),e.push(' name: text("name").notNull(),'),e.push(' slug: text("slug").unique().notNull(),'),e.push(' createdAt: text("created_at").default(sql`CURRENT_TIMESTAMP`).notNull(),'),e.push(' updatedAt: text("updated_at").default(sql`CURRENT_TIMESTAMP`).notNull(),'),e.push("});"),e.push(""),e.push("export const orgMember = sqliteTable(")),e.push(' "org_member",'),e.push(" {"),e.push(' id: text("id").primaryKey(),'),e.push(' orgId: text("org_id").notNull().references(() => organization.id),'),e.push(' userId: text("user_id").notNull().references(() => user.id),'),e.push(' role: text("role").notNull(),'),o?e.push(' joinedAt: timestamp("joined_at").defaultNow().notNull(),'):e.push(' joinedAt: text("joined_at").default(sql`CURRENT_TIMESTAMP`).notNull(),'),e.push(" },"),e.push(" (table) => ({"),e.push(' orgIdx: index("org_member_org_idx").on(table.orgId),'),e.push(' userIdx: index("org_member_user_idx").on(table.userId),'),e.push(" }),"),e.push(");"),e.push(""),e.join(`
|
|
2075
|
+
`)}function $i(s){if(!s.multiTenant)return null;let o=[];return o.push('import { db } from "./db";'),o.push('import { organization, orgMember } from "@/db/schema/organization";'),o.push('import { eq } from "drizzle-orm";'),o.push(""),o.push("export async function getCurrentOrg(userId: string) {"),o.push(" const membership = await db"),o.push(" .select()"),o.push(" .from(orgMember)"),o.push(" .where(eq(orgMember.userId, userId))"),o.push(" .limit(1);"),o.push(" if (membership.length === 0) return null;"),o.push(" const org = await db"),o.push(" .select()"),o.push(" .from(organization)"),o.push(" .where(eq(organization.id, membership[0].orgId))"),o.push(" .limit(1);"),o.push(" return org[0] ?? null;"),o.push("}"),o.push(""),o.push("export async function getOrgMembers(orgId: string) {"),o.push(" return db"),o.push(" .select()"),o.push(" .from(orgMember)"),o.push(" .where(eq(orgMember.orgId, orgId));"),o.push("}"),o.push(""),o.push("export async function inviteToOrg(orgId: string, email: string, role: string) {"),o.push(" const id = crypto.randomUUID();"),o.push(" await db.insert(orgMember).values({"),o.push(" id,"),o.push(" orgId,"),o.push(" userId: email,"),o.push(" role,"),o.push(" });"),o.push(" return { id, orgId, email, role };"),o.push("}"),o.push(""),o.join(`
|
|
2076
|
+
`)}function ji(s){if(!s.multiTenant)return null;let o=[];return o.push('"use client";'),o.push(""),o.push("import {"),o.push(" DropdownMenu,"),o.push(" DropdownMenuContent,"),o.push(" DropdownMenuItem,"),o.push(" DropdownMenuTrigger,"),o.push('} from "@/components/ui/dropdown-menu";'),o.push('import { Button } from "@/components/ui/button";'),o.push('import { ChevronsUpDown } from "lucide-react";'),o.push(""),o.push("interface OrgSwitcherProps {"),o.push(" orgs: Array<{ id: string; name: string }>;"),o.push(" currentOrgId: string;"),o.push("}"),o.push(""),o.push("export default function OrgSwitcher({ orgs, currentOrgId }: OrgSwitcherProps) {"),o.push(" const currentOrg = orgs.find((o) => o.id === currentOrgId);"),o.push(""),o.push(" return ("),o.push(" <DropdownMenu>"),o.push(" <DropdownMenuTrigger asChild>"),o.push(' <Button variant="outline" className="w-full justify-between">'),o.push(' <span className="truncate">{currentOrg?.name ?? "Select org"}</span>'),o.push(' <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />'),o.push(" </Button>"),o.push(" </DropdownMenuTrigger>"),o.push(' <DropdownMenuContent className="w-56">'),o.push(" {orgs.map((org) => ("),o.push(" <DropdownMenuItem key={org.id}>"),o.push(" {org.name}"),o.push(" </DropdownMenuItem>"),o.push(" ))}"),o.push(" </DropdownMenuContent>"),o.push(" </DropdownMenu>"),o.push(" );"),o.push("}"),o.push(""),o.join(`
|
|
2077
|
+
`)}function Li(s,o,e){let t=[],n=s.split("-").map(l=>l.charAt(0).toUpperCase()+l.slice(1)).join(" ");t.push(`# ${n}`),t.push(""),o?.summary&&(t.push(o.summary),t.push(""));let a=o?.features??[];if(a.length>0){t.push("## Features"),t.push("");for(let l of a){let c=l.description?` \u2014 ${l.description}`:"";t.push(`- **${l.name}**${c}`)}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 |"),e.hasStripe&&t.push("| Payments | Stripe |"),e.hasResend&&t.push("| Email | Resend + React Email |"),e.hasStorage&&t.push("| File Storage | Mistflow Cloud (managed blob storage) |"),e.hasAdmin&&t.push("| Admin | Better Auth admin plugin |"),e.hasAI&&t.push("| AI | Vercel AI SDK + OpenAI |"),t.push("");let r=o?.pages??[];if(r.length>0){t.push("## Pages"),t.push(""),t.push("| Route | Description |"),t.push("|-------|-------------|");for(let l of r){let c=l.path??l.route??l.name??"",u=l.description??"";t.push(`| \`${c.startsWith("/")?c:"/"+c}\` | ${u} |`)}t.push("")}let i=o?.dataModel??[];if(i.length>0){t.push("## Data Model"),t.push("");for(let l of i){let c=l.entity??l.name??"Unknown";if(t.push(`### ${c}`),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("Use the Mistflow CLI (recommended \u2014 streams output live and has no 60s tool-call timeout):"),t.push(""),t.push("```bash"),t.push("npx -y @mistflow-ai/cli install"),t.push("```"),t.push(""),t.push("Or plain npm if you prefer:"),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("|----------|-------------|----------|"),e.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 |"),e.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 |")),e.hasResend&&(t.push("| `RESEND_API_KEY` | Resend API key | Yes |"),t.push("| `EMAIL_FROM` | Sender email address | Yes (production) |")),e.hasStorage&&(t.push("| `MISTFLOW_API_KEY` | Mistflow API key for file storage | Yes |"),t.push("| `MISTFLOW_PROJECT_ID` | Mistflow project ID | Yes |")),e.hasAI&&t.push("| `OPENAI_API_KEY` | OpenAI API key | Yes |"),t.push(""),t.push("### Local database"),t.push(""),e.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"),e.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 ${e.isNeon?"Postgres":"SQLite"} database connection`),e.hasStripe&&t.push(" stripe.ts Stripe client"),e.hasResend&&(t.push(" resend.ts Resend client"),t.push(" email.ts Email send helpers")),e.hasStorage&&t.push(" storage.ts File upload/download helpers"),e.hasAI&&t.push(" ai.ts AI client (Vercel AI SDK + OpenAI)"),e.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(""),o?.design&&(t.push("## Design"),t.push(""),o.design.tone&&t.push(`- **Tone**: ${o.design.tone}`),o.design.fonts&&(t.push(`- **Heading font**: ${o.design.fonts.heading}`),t.push(`- **Body font**: ${o.design.fonts.body}`)),o.design.accentColor&&t.push(`- **Accent color**: ${o.design.accentColor}`),o.design.borderRadius&&t.push(`- **Border radius**: ${o.design.borderRadius}`),t.push("")),t.push("---"),t.push(""),t.push("Built with [Mistflow](https://mistflow.ai)"),t.push(""),t.join(`
|
|
2078
|
+
`)}async function Xs(s,o){let{name:e,plan:t,path:n,planId:a}=s;if(!n)return d("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(!Po(n))return d(`mist_build init 'path' must be an absolute path \u2014 received '${n}'. Pass the full absolute path to the target directory.`,!0);let r=Bt(n),i=t?.design,l=t?.appStyle,c=t?Ot(t,"stripe","payment","billing","subscription","checkout","pricing"):!1,u=!0,m=t?Ot(t,"upload","file storage","image upload","profile picture","attachment","gallery","media","blob"):!1,p=t?Ot(t,"admin panel","admin dashboard","admin management"):!1,g=t?Ot(t,"ai integration","openai","llm","ai chat","chatbot","gpt"):!1,w=t,f=!0;if(!Qs(r))return d(`A project already exists at this location (${r}). Choose a different name, or delete the existing folder first. (A .mistflow/ folder left over from planning is fine \u2014 init will preserve it.)`,!0);Ut(r,{recursive:!0});try{let T=Z(wt(r),".mistflow","mockups");if(Me(T)){let A=Ks(T).filter(V=>V.endsWith(".html"));if(A.length>0){let V=Z(r,".mistflow","mockups");Ut(V,{recursive:!0});for(let U of A)gi(Z(T,U),Z(V,U));console.error(`Copied ${A.length} mockup file(s) into project`)}}}catch(T){console.error("Could not copy mockup files:",T instanceof Error?T.message:T)}let h=null;try{h=await Dt("nextjs")}catch(T){console.error("Could not fetch scaffold from API, using minimal scaffold:",T instanceof Error?T.message:T)}if(h){let T=e.toLowerCase().replace(/[^a-z0-9-]/g,"-");for(let I of h.files){if(I.path==="package.json"||I.path==="middleware.ts"||I.path==="components/sidebar.tsx"||I.path==="components/topnav.tsx"||I.path==="app/(dashboard)/layout.tsx"||I.path==="app/(dashboard)/page.tsx"||I.path==="app/(dashboard)/dashboard/page.tsx"||!c&&(I.path.includes("stripe")||I.path.includes("webhook/stripe"))||!u&&(I.path.includes("resend")||I.path.includes("emails/"))||!p&&(I.path.includes("(admin)")||I.path.includes("admin-sidebar"))||f&&(I.path==="lib/db.ts"||I.path==="lib/auth.ts"||I.path==="drizzle.config.ts"||I.path==="db/schema/auth.ts"))continue;let H=I.content.replace(/\{\{APP_NAME\}\}/g,e).replace(/\{\{WORKER_NAME\}\}/g,T);if(f&&I.path==="next.config.ts"&&(H=H.replace(/serverExternalPackages:\s*\[[^\]]*\],?/g,'serverExternalPackages: ["@electric-sql/pglite"],')),I.path==="next.config.ts"){let b=bi(r);b&&(console.error(`[init] Project is inside monorepo at ${b} \u2014 adding outputFileTracingRoot`),H.includes("outputFileTracingRoot")||(H=H.replace('import type { NextConfig } from "next";',`import type { NextConfig } from "next";
|
|
2074
2079
|
import { dirname } from "path";
|
|
2075
2080
|
import { fileURLToPath } from "url";
|
|
2076
2081
|
|
|
2077
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));`),
|
|
2078
|
-
images: {`)))}!
|
|
2079
|
-
`)),
|
|
2080
|
-
`)),
|
|
2081
|
-
`)),
|
|
2082
|
-
`)),
|
|
2083
|
-
`)))}else
|
|
2084
|
-
`)),
|
|
2085
|
-
`)),
|
|
2086
|
-
`))),m&&(
|
|
2087
|
-
`)),
|
|
2088
|
-
`))),g&&(
|
|
2089
|
-
`)),
|
|
2090
|
-
`)));let
|
|
2082
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));`),H=H.replace("images: {",`outputFileTracingRoot: __dirname,
|
|
2083
|
+
images: {`)))}!p&&I.path.includes("sidebar")&&(H=H.replace(/\{user\.role === "admin"[\s\S]*?<\/Link>\s*\)\}/m,""),H=H.replace(/, Shield/g,"")),B(r,I.path,H)}let A={...h.dependencies},V={...h.devDependencies};if(A["drizzle-zod"]||(A["drizzle-zod"]="^0.5.1"),f&&(delete A["@libsql/client"],A["@neondatabase/serverless"]="^0.10.0",V["@electric-sql/pglite"]="^0.2.0"),c&&(A.stripe="^17.0.0"),u&&(A.resend="^4.0.0",A["@react-email/components"]="^0.0.31"),g&&(A.ai="^4.0.0",A["@ai-sdk/openai"]="^1.0.0",A.openai="^4.0.0"),B(r,"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:A,devDependencies:V,optionalDependencies:{"@noble/ciphers":"^1.3.0"},overrides:{react:"19.1.0","react-dom":"19.1.0",punycode:"^2.3.1"}},null,2)),h.methodology){let I=h.methodology;f&&(I=I.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")),I=xo(I),B(r,"AGENTS.md",I),B(r,"CLAUDE.md",I)}let se=t?.designMd;se&&B(r,"DESIGN.md",se),f&&(B(r,"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(`
|
|
2084
|
+
`)),B(r,"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(`
|
|
2085
|
+
`)),B(r,"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(`
|
|
2086
|
+
`)),B(r,"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(`
|
|
2087
|
+
`)),B(r,"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, fallbackUrl }: { to: string; subject: string; html: string; fallbackUrl?: string }) {"," const apiKey = process.env.RESEND_API_KEY;"," if (!apiKey) {"," if (fallbackUrl) {"," console.error(`\\n[auth] No RESEND_API_KEY set. Email to ${to} was not sent.`);"," console.error(`[auth] Dev fallback \u2014 use this link directly:\\n ${fallbackUrl}\\n`);"," } else {"," console.error(`[auth] RESEND_API_KEY not set \u2014 skipping email send to ${to}`);"," }"," return;"," }",' const from = process.env.EMAIL_FROM || "noreply@mail.mistflow.app";',' const res = await fetch("https://api.resend.com/emails", {',' method: "POST",',' headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },'," body: JSON.stringify({ from, to, subject, html }),"," });"," if (!res.ok) {",' const body = await res.text().catch(() => "unknown");'," console.error(`[auth] Email send failed (${res.status}): ${body}`);"," throw new Error(`Email send failed: ${res.status}`);"," }","}","","function createAuth() {",' const baseURL = process.env.BETTER_AUTH_URL || process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000";',' const isLocal = baseURL.includes("localhost") || baseURL.includes("127.0.0.1");'," const canSendEmail = Boolean(process.env.RESEND_API_KEY);"," // Refuse to boot a production app with email auth but no mail sender. Mistflow's"," // deploy pipeline injects a managed Resend key automatically; if it's missing,"," // that's a real misconfig and silent fallbacks let unverified users sign up."," if (!isLocal && !canSendEmail) {",` throw new Error("[auth] RESEND_API_KEY is required in production. The Mistflow deploy pipeline injects one automatically \u2014 if you're seeing this, check the project's env vars in the dashboard, or set your own RESEND_API_KEY.");`," }"," return betterAuth({"," baseURL,"," trustedOrigins: [baseURL],",' database: drizzleAdapter(db, { provider: "pg", schema }),'," emailAndPassword: {"," enabled: true,"," requireEmailVerification: !isLocal && canSendEmail,"," sendResetPassword: async ({ user, token }: { user: { email: string; name: string }; url: string; token: string }) => {"," // Better Auth's default reset URL points at /reset-password/:token (path),"," // which our frontend doesn't route. We build our own pointing at our"," // /reset-password?token=xxx page which reads the token from the query."," const resetUrl = `${baseURL}/reset-password?token=${token}`;"," await sendEmail({"," to: user.email,",' subject: "Reset your password",',' html: `<p>Hi ${user.name},</p><p>Click the link below to reset your password:</p><p><a href="${resetUrl}">${resetUrl}</a></p>`,'," fallbackUrl: isLocal ? resetUrl : undefined,"," });"," },"," },"," emailVerification: {"," sendOnSignUp: canSendEmail,"," autoSignInAfterVerification: true,"," sendVerificationEmail: async ({ user, url }: { user: { email: string; name: string }; url: string }) => {"," await sendEmail({"," to: user.email,",' subject: "Verify your email address",',' html: `<p>Hi ${user.name},</p><p>Click the link below to verify your email:</p><p><a href="${url}">${url}</a></p>`,'," fallbackUrl: isLocal ? url : undefined,"," });"," },"," },"," secret: process.env.AUTH_SECRET,",' plugins: [admin({ defaultRole: "user" }), nextCookies()],'," databaseHooks: {"," user: {"," create: {"," // Auto-promote the app owner to admin on first signup. ADMIN_EMAIL"," // is injected by the Mistflow deploy pipeline (the email of the"," // account that ran mist_deploy). Email verification still gates"," // login when Resend is configured, so a collision attempt can't"," // actually sign in without clicking a link delivered to the"," // owner's inbox."," before: async (user: { email?: string; [k: string]: unknown }) => {"," const adminEmail = process.env.ADMIN_EMAIL;"," if (adminEmail && user.email?.toLowerCase() === adminEmail.toLowerCase()) {",' return { data: { ...user, role: "admin" } };'," }"," return { data: user };"," },"," },"," },"," },"," socialProviders: {"," ...(process.env.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(`
|
|
2088
|
+
`)))}else B(r,"package.json",JSON.stringify({name:e,version:"0.1.0",private:!0},null,2));let v=t?.designMd;B(r,"app/globals.css",Si(i,l,v)),B(r,"app/layout.tsx",Pi(e,i,w?.language)),B(r,"README.md",Li(e,t,{hasStripe:c,hasResend:u,hasStorage:m,hasAdmin:p,hasAI:g,isNeon:f})),B(r,"contracts/README.md",ko());let E=t?.dataModel??[],R=new Set,C=0;for(let T of E){let A=T.entity??T.name;if(!A||typeof A!="string")continue;let V=To(A);R.has(V)||(R.add(V),B(r,V,So(A)),C++)}C===0&&B(r,"contracts/.gitkeep","");let P=[],j=t?.publicPages;if(Array.isArray(j))P=j;else if(typeof j=="string"){try{P=JSON.parse(j)}catch{P=[]}Array.isArray(P)||(P=[])}if(!P.includes("/")){let T=t?.steps?.some(V=>{let U=((V.name??"")+" "+(V.description??"")).toLowerCase();return U.includes("landing")||U.includes("marketing")||U.includes("homepage")}),A=t?.pages?.some(V=>V.path==="/");(T||A)&&(P=["/",...P])}let S={name:e,summary:t?.summary,authModel:t?.authModel,roles:t?.roles,defaultRole:t?.defaultRole,publicPages:P,navStyle:t?.navStyle,multiTenant:t?.multiTenant,pages:t?.pages,dataModel:t?.dataModel,design:t?.design},z=Ii(S);z&&B(r,"middleware.ts",z);let Y=Ri(S);Y&&B(r,Y.path,Y.content);let G=Ei(S);if(G&&B(r,"lib/roles.ts",G),B(r,"app/page.tsx",Ni(S)),B(r,"app/(dashboard)/layout.tsx",_i(S,p)),B(r,"app/(dashboard)/dashboard/page.tsx",Di(S)),S.multiTenant){let T=Mi(S,f);T&&B(r,"db/schema/organization.ts",T);let A=$i(S);A&&B(r,"lib/org.ts",A);let V=ji(S);V&&B(r,"components/org-switcher.tsx",V)}B(r,"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)),c&&B(r,"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(`
|
|
2089
|
+
`)),u&&(B(r,"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(`
|
|
2090
|
+
`)),B(r,"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(`
|
|
2091
|
+
`))),m&&(B(r,"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(`
|
|
2092
|
+
`)),B(r,"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(`
|
|
2093
|
+
`))),g&&(B(r,"lib/ai.ts",['import { createOpenAI } from "@ai-sdk/openai";',"","export const openai = createOpenAI({"," apiKey: process.env.OPENAI_API_KEY,","});",""].join(`
|
|
2094
|
+
`)),B(r,"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(`
|
|
2095
|
+
`)));let W={name:e,methodologyVersion:h?.version??"1.0",createdAt:new Date().toISOString(),...a?{planId:a}:{},plan:Array.isArray(t?.steps)?{...t,steps:t.steps.map(T=>({number:T.number,name:T.name??T.title,description:T.description,entities:T.entities,pages:T.pages,features:T.features,status:"pending"}))}:t,dbProvider:"neon",env:{managed:{DATABASE_URL:{description:"Postgres connection URL",scope:"production"},AUTH_SECRET:{description:"Auth encryption secret",scope:"production"},...c?{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"}}:{},...m?{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:p,hasResend:u,hasStorage:m,hasAI:g,deploy:null};B(r,"mistflow.json",JSON.stringify(W,null,2));let _=yi(32).toString("hex"),ee=c?`
|
|
2091
2096
|
# Stripe
|
|
2092
2097
|
STRIPE_SECRET_KEY=
|
|
2093
2098
|
STRIPE_WEBHOOK_SECRET=
|
|
2094
2099
|
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=
|
|
2095
|
-
`:"",
|
|
2100
|
+
`:"",J=u?`
|
|
2096
2101
|
# Email (Resend)
|
|
2097
2102
|
RESEND_API_KEY=
|
|
2098
2103
|
EMAIL_FROM=onboarding@resend.dev
|
|
2099
|
-
`:"",
|
|
2104
|
+
`:"",q=m?`
|
|
2100
2105
|
# File Storage (Mistflow managed)
|
|
2101
2106
|
MISTFLOW_API_KEY=
|
|
2102
2107
|
MISTFLOW_PROJECT_ID=
|
|
2103
|
-
`:"",
|
|
2108
|
+
`:"",F=g?`
|
|
2104
2109
|
# AI (get your key at https://platform.openai.com/api-keys)
|
|
2105
2110
|
OPENAI_API_KEY=
|
|
2106
|
-
`:"",
|
|
2111
|
+
`:"",ie=`# Local dev: PGlite is used automatically (zero-install embedded Postgres)
|
|
2107
2112
|
# Set DATABASE_URL only for production or to use a remote Postgres
|
|
2108
|
-
# DATABASE_URL=postgresql://postgres:postgres@localhost:5432/devdb`,
|
|
2113
|
+
# DATABASE_URL=postgresql://postgres:postgres@localhost:5432/devdb`,le=`# Local dev: PGlite is used automatically (zero-install embedded Postgres)
|
|
2109
2114
|
# Set DATABASE_URL only for production or to use a remote Postgres
|
|
2110
|
-
# DATABASE_URL=postgresql://postgres:postgres@localhost:5432/devdb`;
|
|
2111
|
-
AUTH_SECRET=${
|
|
2112
|
-
${
|
|
2115
|
+
# DATABASE_URL=postgresql://postgres:postgres@localhost:5432/devdb`;B(r,".env.local",`${ie}
|
|
2116
|
+
AUTH_SECRET=${_}
|
|
2117
|
+
${ee}${J}${q}${F}`),B(r,".env.example",`${le}
|
|
2113
2118
|
AUTH_SECRET=your-secret-here
|
|
2114
|
-
${
|
|
2115
|
-
${
|
|
2116
|
-
${y.key}=`});Be(Z(h,".env.local"),
|
|
2119
|
+
${ee}${J}${q}${F}`);let K=[],me=(T,A)=>{K.push({phase:T,message:A})},x=(T,A)=>{let V=K.find(U=>U.phase===T&&!U.durationMs);V&&(V.durationMs=A)};if(o){let T=Je(o.server,o.progressToken,()=>K[K.length-1]?.message??"Setting up project...");o.cleanup=()=>T.stop()}let D=w?.requestedSubdomain||void 0,M,y;me("register","Registering project on Mistflow...");let N=Date.now();try{let T=await Zo(e,void 0,"neon",D);M=T.id;let A=Z(r,"mistflow.json"),V=JSON.parse(Ft(A,"utf-8"));if(V.projectId=M,Be(A,JSON.stringify(V,null,2)),co(r,po(M,e)),T.managed_env&&Object.keys(T.managed_env).length>0){let U=Z(r,".env.local"),ke=Me(U)?Ft(U,"utf-8"):"";for(let[se,I]of Object.entries(T.managed_env)){let H=new RegExp(`^${se}=.*$`,"m");H.test(ke)?ke=ke.replace(H,`${se}=${I}`):ke+=`
|
|
2120
|
+
${se}=${I}`}Be(U,ke)}try{let{getBaseUrl:U,getAuthHeaders:ke}=await import("./api-client-RCNDMCPS.js"),se=ke(),I=t?.features,H=t?.steps,b={};Array.isArray(I)&&I.length>0&&(b.features=I.map(k=>k.name)),t&&(b.plan=t),Array.isArray(H)&&H.length>0&&(b.provenance=H.map(k=>({feature:k.name??k.title??`Step ${k.number??"?"}`,user_intent:(k.description??"").slice(0,500),decisions:"Seeded from plan at init",tradeoffs:"",files_affected:[]}))),Object.keys(b).length>0&&await fetch(`${U()}/api/projects/${encodeURIComponent(M)}/state`,{method:"PUT",headers:{...se,"Content-Type":"application/json"},body:JSON.stringify(b)})}catch{}K[K.length-1].message=`Registered as ${M.slice(0,8)}`}catch(T){let A=T instanceof Error?T.message:String(T);console.error("Could not register project on backend:",A),y=`Project created locally but NOT registered on Mistflow servers (${A}). Deploy will auto-register it.`,K[K.length-1].message="Registration skipped (offline \u2014 deploy will retry)"}x("register",Date.now()-N),me("git","Initializing git repository...");let te=Date.now();try{let T=Js(r);await T.init(),await T.add("."),await T.commit("Initial Mistflow project setup"),K[K.length-1].message="Git repository initialized"}catch{console.error("Git initialization failed, continuing without git."),K[K.length-1].message="Git init skipped"}x("git",Date.now()-te);let De=K.reduce((T,A)=>T+(A.durationMs??0),0),ae={projectPath:r,projectId:M,status:"awaiting_install"},Ce=K.map(T=>{let A=T.durationMs?` (${(T.durationMs/1e3).toFixed(1)}s)`:"";return`${T.message}${A}`});ae.progress=Ce,ae.totalSetupTime=`${(De/1e3).toFixed(1)}s`;let pe=[];M||pe.push("Project was not registered with Mistflow (not signed in). Run mist_setup to sign in BEFORE deploying \u2014 deploy will fail without it."),y&&(ae.registrationWarning=y),pe.length>0&&(ae.warnings=pe);let Ae=`NEXT: Run \`npx -y @mistflow-ai/cli install ${r}\` via your shell/bash tool \u2014 NOT via the mist_build MCP tool. The CLI streams npm output live and has no MCP 60s timeout; the MCP install action can hit -32001 timeout on slow installs. Do NOT ask the user for permission \u2014 install is a required follow-up to init, not a decision point. If your environment can't run the CLI, fall back to mist_build action='install' projectPath='${r}'. After install finishes, call mist_build action='implement' to build the first plan step \u2014 also without asking.`;return ae.nextAction=M?Ae:`${Ae} IMPORTANT: You MUST also run mist_setup to sign in before deploying \u2014 the project could not be registered because auth is missing.`,d(JSON.stringify(ae))}async function Zs(s,o){let{projectPath:e}=s;if(!e)return d("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(!Po(e))return d(`mist_build install 'projectPath' must be an absolute path \u2014 received '${e}'.`,!0);let t=Bt(e);if(!Me(Z(t,"package.json")))return d(`No package.json found at ${t}. Run mist_build init first to scaffold the project.`,!0);if(Me(Z(t,"node_modules")))return d(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 n="Installing packages...";if(o){let u=Je(o.server,o.progressToken,()=>n);o.cleanup=()=>u.stop()}let a={NPM_CONFIG_LEGACY_PEER_DEPS:"true"},r=Date.now(),i=0,l=await vt("npm",["install"],t,12e4,u=>{let m=u.match(/added (\d+) packages/);m&&(i=parseInt(m[1],10))},a);if(l.success||(console.error("[install] npm install failed, retrying..."),n="Install failed, retrying...",l=await vt("npm",["install"],t,12e4,u=>{let m=u.match(/added (\d+) packages/);m&&(i=parseInt(m[1],10))},a)),!l.success)return d(`npm install failed after 2 attempts at ${t}: ${l.error}. Run "npm install" manually in that directory, then call mist_build implement.`,!0);let c=((Date.now()-r)/1e3).toFixed(1);return d(JSON.stringify({status:"installed",projectPath:t,packagesInstalled:i||null,durationSec:c,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 er(s){let{name:o,plan:e,projectId:t,sourceDeploymentId:n,forkToken:a,requiredEnvVars:r,dbProvider:i,planId:l}=s;if(!s.path)return d("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(!Po(s.path))return d(`mist_build init 'path' must be an absolute path \u2014 received '${s.path}'. Pass the full absolute path to the target directory.`,!0);let c=Bt(s.path);if(!Qs(c))return d(`A project already exists at this location (${c}). Choose a different name, or delete the existing folder first. (A .mistflow/ folder left over from planning is fine \u2014 init will preserve it.)`,!0);let{mkdtempSync:u,renameSync:m,rmSync:p,cpSync:g}=await import("fs"),{tmpdir:w}=await import("os"),f=u(Z(w(),"mistflow-fork-")),h=Z(f,"project");Ut(h,{recursive:!0});let v=[],E=(C,P)=>v.push({phase:C,message:P}),R=(C,P)=>{let j=v.find(S=>S.phase===C);j&&(j.durationMs=P)};try{E("download","Downloading source code from template...");let C=Date.now(),P=Z(f,"source.tar.gz");try{await ks(n,a,P)}catch(y){p(f,{recursive:!0,force:!0});let N=y instanceof Error?y.message:"Source download failed";return d(`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)}R("download",Date.now()-C),v[v.length-1].message="Source code downloaded",E("extract","Extracting source code...");let j=Date.now(),S=await vt("tar",["-xzf",P,"-C",h,"--exclude","node_modules","--exclude",".git"],h,6e4);if(!S.success)return p(f,{recursive:!0,force:!0}),d(`Failed to extract source archive: ${S.error}`,!0);if(R("extract",Date.now()-j),!Me(Z(h,"package.json")))return p(f,{recursive:!0,force:!0}),d("Source archive does not contain a package.json. The template may be corrupted.",!0);let z=[".mistflow",".env.local",".env.example","local.db","local.pg"];for(let y of z){let N=Z(h,y);Me(N)&&p(N,{recursive:!0,force:!0})}let Y={name:o,projectId:t,template:"nextjs",createdAt:new Date().toISOString(),planId:l??void 0,plan:e,dbProvider:i};Be(Z(h,"mistflow.json"),JSON.stringify(Y,null,2));let G=r.map(y=>{let N=y.description?`# ${y.description}`:`# ${y.key}`,te=y.setup_url?` (${y.setup_url})`:"";return`${N}${te}
|
|
2121
|
+
${y.key}=`});Be(Z(h,".env.local"),G.join(`
|
|
2117
2122
|
|
|
2118
2123
|
`)+`
|
|
2119
|
-
`);let
|
|
2124
|
+
`);let W=r.map(y=>`${y.key}=`);Be(Z(h,".env.example"),W.join(`
|
|
2120
2125
|
`)+`
|
|
2121
2126
|
`),Be(Z(h,"README.md"),`# ${o}
|
|
2122
2127
|
|
|
@@ -2127,11 +2132,11 @@ Forked from a Mistflow template. Built with Next.js, Drizzle ORM, and Better Aut
|
|
|
2127
2132
|
1. Copy \`.env.example\` to \`.env.local\` and fill in your values
|
|
2128
2133
|
2. Run \`npm run dev\`
|
|
2129
2134
|
3. Deploy with \`mist_deploy\`
|
|
2130
|
-
`),
|
|
2135
|
+
`),E("setup","Setting up project directory...");let _=Date.now();try{m(h,c)}catch(y){if(y.code==="EXDEV")g(h,c,{recursive:!0}),p(h,{recursive:!0,force:!0});else throw y}R("setup",Date.now()-_),E("install","Installing packages...");let ee=Date.now(),J=0,q=await vt("npm",["install"],c,12e4,y=>{let N=y.match(/added (\d+) packages/);N&&(J=parseInt(N[1],10))});if(q.success||(console.error("[initFromSource] npm install failed, retrying..."),q=await vt("npm",["install"],c,12e4,y=>{let N=y.match(/added (\d+) packages/);N&&(J=parseInt(N[1],10))})),!q.success)return d(`Source code was restored at ${c} but package installation failed: ${q.error}. Run "npm install" manually.`,!0);R("install",Date.now()-ee),v[v.length-1].message=`Installed ${J||"all"} packages`,E("git","Initializing git repository...");let F=Date.now();try{let y=Js(c);await y.init(),await y.add("."),await y.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"}R("git",Date.now()-F);try{let{markLocalSetupDone:y}=await import("./api-client-RCNDMCPS.js");await y(t)}catch{console.error("[initFromSource] Could not mark local_setup_done on backend, continuing.")}let ie=JSON.parse(Ft(Z(c,"mistflow.json"),"utf-8"));ie.projectId=t,Be(Z(c,"mistflow.json"),JSON.stringify(ie,null,2)),co(c,po(t,o));let le=e.steps,K=le?.filter(y=>y.status==="completed").length??0,me=le?.filter(y=>y.status==="pending").length??0,x=le?.length??0,D=r.length>0?`
|
|
2131
2136
|
|
|
2132
2137
|
Environment variables needed:
|
|
2133
2138
|
`+r.map(y=>` \u2022 ${y.key}${y.description?` \u2014 ${y.description}`:""}`).join(`
|
|
2134
|
-
`):"",
|
|
2139
|
+
`):"",M=me>0?`Source code restored with ${K}/${x} steps complete. ${me} steps need implementation \u2014 call mist_build with action='implement' to apply your changes.`:`Source code fully restored (${x} steps complete). Configure your .env.local, then deploy with mist_deploy.`;return d(JSON.stringify({status:"success",projectPath:c,projectId:t,planStepsCompleted:K,planStepsTotal:x,pendingSteps:me,progress:v,nextAction:M+D}))}finally{try{p(f,{recursive:!0,force:!0})}catch{}}}import{z as Eo}from"zod";import{existsSync as xt,readFileSync as _o,writeFileSync as No,mkdirSync as ca}from"fs";import{join as ct,resolve as da,dirname as pa}from"path";import{createConnection as ua}from"net";var tr=`# Consumer Warm Archetype
|
|
2135
2140
|
|
|
2136
2141
|
Component-level design guidance for personal, lifestyle, and wellness apps. Habits, journals, recipes, mood trackers, meditation, daily routines, personal finance.
|
|
2137
2142
|
|
|
@@ -2298,7 +2303,7 @@ All colors from project CSS custom properties. Key guidance for consumer-warm:
|
|
|
2298
2303
|
- Small touch targets. Phone-first means \`h-12\` minimum.
|
|
2299
2304
|
- Empty states that just say "No data." Be encouraging.
|
|
2300
2305
|
- Dark theme as default. Consumer warm apps default to light.
|
|
2301
|
-
`;var
|
|
2306
|
+
`;var or=`# Consumer Bold Archetype
|
|
2302
2307
|
|
|
2303
2308
|
Component-level design guidance for energetic, achievement-driven consumer apps. Fitness, workouts, sports, gaming, social platforms, competitive tracking.
|
|
2304
2309
|
|
|
@@ -2469,7 +2474,7 @@ Social/competitive apps need an activity feed:
|
|
|
2469
2474
|
- Card-only layouts with no hierarchy. Use hero cards + supporting cards.
|
|
2470
2475
|
- Missing achievement/progress systems. The gamification IS the product.
|
|
2471
2476
|
- Light-touch buttons. CTAs should be unmissable.
|
|
2472
|
-
`;var
|
|
2477
|
+
`;var sr=`# Professional Clean Archetype
|
|
2473
2478
|
|
|
2474
2479
|
Component-level design guidance for service and appointment-based apps. Booking systems, clinics, salons, real estate, consulting, restaurants, event management.
|
|
2475
2480
|
|
|
@@ -2681,7 +2686,7 @@ h-10 w-10 rounded-full bg-[deterministic-color] flex items-center justify-center
|
|
|
2681
2686
|
- Missing status workflows. Every booking/appointment needs a clear state machine.
|
|
2682
2687
|
- Dark theme as default. Clients associate light themes with professionalism.
|
|
2683
2688
|
- Emoji in the UI. Professional context.
|
|
2684
|
-
`;var
|
|
2689
|
+
`;var rr=`# Education Structured Archetype
|
|
2685
2690
|
|
|
2686
2691
|
Component-level design guidance for learning, education, and knowledge apps. Course platforms, quiz apps, flashcards, LMS, student portals, tutorial sites, documentation tools.
|
|
2687
2692
|
|
|
@@ -2881,7 +2886,7 @@ border-2 border-destructive bg-destructive/5 rounded-xl p-4
|
|
|
2881
2886
|
- Hero metrics. "2,847 XP" is gaming, not learning. Use "4 of 12 lessons" instead.
|
|
2882
2887
|
- Long lesson pages with no progress indicator. Users need to know where they are.
|
|
2883
2888
|
- Multiple competing elements per view. One thing at a time. One question at a time.
|
|
2884
|
-
`;var
|
|
2889
|
+
`;var nr=`# Marketplace Browse Archetype
|
|
2885
2890
|
|
|
2886
2891
|
Component-level design guidance for browsing, listing, and shopping apps. Marketplaces, directories, shops, classifieds, rental platforms, food delivery, product catalogs.
|
|
2887
2892
|
|
|
@@ -3105,7 +3110,7 @@ border-b border-border/30 py-4
|
|
|
3105
3110
|
- Small product images. The image is the primary decision-making element.
|
|
3106
3111
|
- No empty state for zero search results. "No results for 'xyz'. Try broader terms."
|
|
3107
3112
|
- Desktop-only filter sidebar without mobile equivalent. Use a slide-out sheet on mobile.
|
|
3108
|
-
`;var
|
|
3113
|
+
`;var ir=`# SaaS Analytical Archetype
|
|
3109
3114
|
|
|
3110
3115
|
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.
|
|
3111
3116
|
|
|
@@ -3286,7 +3291,7 @@ The most recognizable B2B SaaS landing pattern.
|
|
|
3286
3291
|
- **Inter as the only font.** Pair it with a distinctive heading font or swap it entirely.
|
|
3287
3292
|
- **Centered text + centered image below.** The most overused AI hero. Use split or offset layouts.
|
|
3288
3293
|
- **Animation on every element.** Efficient motion only: hero entrance, card reveals on scroll, number counters. Nothing else.
|
|
3289
|
-
`;var
|
|
3294
|
+
`;var ar=`# Content Editorial Archetype
|
|
3290
3295
|
|
|
3291
3296
|
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.
|
|
3292
3297
|
|
|
@@ -3493,7 +3498,7 @@ Below the hero: 3 featured pieces laid out as a horizontal strip with **oversize
|
|
|
3493
3498
|
- **Dark mode as default for a reading tool.** Offer it as a toggle, not the default.
|
|
3494
3499
|
- **Flashy motion while reading.** Scroll hijacking, parallax on body text, animated backgrounds behind paragraphs \u2014 all break reading flow.
|
|
3495
3500
|
- **More than 2 fonts.** Heading + body is enough. Adding a third font for captions or metadata dilutes the identity.
|
|
3496
|
-
`;var
|
|
3501
|
+
`;var lr=`# Devtool Technical Archetype
|
|
3497
3502
|
|
|
3498
3503
|
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.
|
|
3499
3504
|
|
|
@@ -3682,7 +3687,7 @@ Numbers styled as terminal output: \`> 47,293 deploys this week\`, \`> 99.98% up
|
|
|
3682
3687
|
- **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).
|
|
3683
3688
|
- **Feature cards with rocket / lightning / puzzle icons.** Use technical icons (terminal, code, database, network) or skip icons entirely.
|
|
3684
3689
|
- **Motion on code blocks** while the user is trying to read them. Type out ONCE on load, then leave them alone.
|
|
3685
|
-
`;var
|
|
3690
|
+
`;var cr=`# Creative Showcase Archetype
|
|
3686
3691
|
|
|
3687
3692
|
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.
|
|
3688
3693
|
|
|
@@ -3899,7 +3904,7 @@ Premium agency touch: on first load, a full-screen loading component shows the s
|
|
|
3899
3904
|
- **Scroll hijacking through the whole site.** Use sparingly \u2014 one or two scroll-pinned sections maximum.
|
|
3900
3905
|
- **Mystery-meat navigation.** Clever is bad here. "Work / About / Contact" is fine. Don't make visitors guess.
|
|
3901
3906
|
- **No contact info.** The primary purpose of this site is to generate inquiries. Make it stupid-easy to contact you.
|
|
3902
|
-
`;var
|
|
3907
|
+
`;var dr=`# Finance Clarity Archetype
|
|
3903
3908
|
|
|
3904
3909
|
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.
|
|
3905
3910
|
|
|
@@ -4118,9 +4123,9 @@ Finance products often replace something worse (a clunky bank, a spreadsheet, an
|
|
|
4118
4123
|
- **Purple-to-blue gradients in the hero.** Fintech SaaS default. Use a brand color instead \u2014 or no gradient.
|
|
4119
4124
|
- **Dark mode as default for a consumer money app.** Too crypto-coded. Default light, offer dark.
|
|
4120
4125
|
- **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.
|
|
4121
|
-
`;var or={"consumer-warm":{id:"consumer-warm",name:"Consumer Warm",description:"Personal, lifestyle, and wellness apps (habits, journals, recipes, mood, meditation)",content:Ws},"consumer-bold":{id:"consumer-bold",name:"Consumer Bold",description:"Energetic, achievement-driven apps (fitness, workouts, sports, gaming, social)",content:Vs},"professional-clean":{id:"professional-clean",name:"Professional Clean",description:"Service and appointment-based apps (booking, clinics, salons, real estate, restaurants)",content:Ys},"education-structured":{id:"education-structured",name:"Education Structured",description:"Learning and knowledge apps (courses, quizzes, flashcards, LMS, tutorials)",content:Js},"marketplace-browse":{id:"marketplace-browse",name:"Marketplace Browse",description:"Browsing and shopping apps (marketplaces, directories, shops, catalogs, food delivery)",content:Ks},"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:Qs},"content-editorial":{id:"content-editorial",name:"Content Editorial",description:"Blogs, newsletters, magazines, publications, documentation, knowledge bases",content:Xs},"devtool-technical":{id:"devtool-technical",name:"Devtool Technical",description:"Developer tools, APIs, CLIs, SDKs, infrastructure, monitoring, AI/ML platforms",content:Zs},"creative-showcase":{id:"creative-showcase",name:"Creative Showcase",description:"Portfolios, design agencies, creative studios, photographers, artists",content:er},"finance-clarity":{id:"finance-clarity",name:"Finance Clarity",description:"Fintech, invoicing, expense tracking, budgeting, payroll, accounting, banking",content:tr}};function sr(s){return or[s]?.content}function rr(s,o){let t=o?.archetype;if(t&&t in or)return t;if(t===null)return;let e=s.toLowerCase(),n=o?.tone??"",a=o?.audienceType??"";return/\bbooking|appointment|reserv|salon|clinic|dental|barber|spa|hotel|restaurant|event|venue|schedule|court|racquet|tee.?time\b/i.test(e)?"professional-clean":/\binvoic|expense|budget|payroll|accounting|bookkeep|payment|banking|wallet|fintech|cashflow|reimbursement|tax.?filing\b/i.test(e)?"finance-clarity":/\bblog|newsletter|publication|magazine|docs?\b|documentation|knowledge.?base|wiki|\barticle|editorial|cms|longform|writing.?platform\b/i.test(e)?"content-editorial":/\bportfolio|\bagency\b|\bstudio\b|freelance|photographer|illustrator|gallery|architect|filmmaker\b/i.test(e)?"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(e)?"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(e)?"saas-analytical":/\bhabit|journal|diary|mood|meditation|yoga|mindful|gratitude|daily.?routine|self.?care|wellness\b/i.test(e)?"consumer-warm":/\bfitness|workout|exercise|gym|training|sport|running|cycling|weight|muscle|cardio|athletic|leaderboard|\bgame\b/i.test(e)?"consumer-bold":/\bcourse|quiz|flashcard|lesson|tutorial|learning|education|\blms\b|student|teach|curriculum|exam|classroom\b/i.test(e)?"education-structured":/\bmarketplace|\bshop\b|\bstore\b|catalog|listing|directory|classified|rental|e.?commerce|food.?delivery|menu\b/i.test(e)?"marketplace-browse":n==="warm-minimal"||n==="playful"?/\brecipe|cook|meal|food|pet|plant|garden|travel|photo|music|social|community|family\b/i.test(e)?"consumer-warm":/\bcompet|score|rank|challenge|achieve|badge|level|quest\b/i.test(e)?"consumer-bold":"consumer-warm":a==="b2c"?"consumer-warm":"saas-analytical"}import{existsSync as qt,mkdirSync as ir,readFileSync as ar,readdirSync as lr,writeFileSync as Oi}from"fs";import{join as lt,resolve as Ui}from"path";import{homedir as So}from"os";function at(s){return s.entity??s.name??"Unknown"}function Qe(s){return typeof s=="string"?s:s.name}function xo(s){return typeof s=="string"?"text":s.type}function nr(s){let o=s.sampleRows;if(Array.isArray(o)&&o.length>0)return o.slice(0,3).map(n=>{let a={};for(let[r,i]of Object.entries(n))a[r]=i==null?"":String(i);return a});let t=s.fields||[],e=[];for(let n=0;n<3;n++){let a={};for(let r of t)a[Qe(r)]=Fi(Qe(r),xo(r),at(s),n);e.push(a)}return e}function Fi(s,o,t,e){let n=s.toLowerCase(),a=(o||"text").toLowerCase();return n==="name"||n==="title"?[`${t} Alpha`,`${t} Beta`,`${t} Gamma`][e]:n==="email"?["alice@example.com","bob@example.com","carol@example.com"][e]:n==="status"?["Active","Pending","Completed"][e]:n==="priority"?["High","Medium","Low"][e]:n.includes("date")||a==="date"?["Jan 15, 2024","Feb 20, 2024","Mar 10, 2024"][e]:n.includes("price")||n.includes("amount")||n.includes("cost")?["$29","$49","$99"][e]:n.includes("count")||n.includes("quantity")||a==="number"||a==="integer"?["12","34","56"][e]:a==="boolean"||a==="bool"?["Yes","No","Yes"][e]:n.includes("description")||a==="textarea"?["Brief description here","Another example entry","Third sample item"][e]:[`Sample ${e+1}`][0]}function Bi(s){let o=s.dataModel??[],t=s.pages??[],e=s.design??{},n=t.map(m=>({label:m.name??m.path??"Page",route:m.path??m.route??"/"})),a=[],r=o.slice(0,3).map(m=>({name:at(m),fields:(m.fields||[]).map(u=>({name:Qe(u),type:xo(u)})),sampleData:nr(m)})),i=[];s.primaryAction&&(i.push(`PRIMARY: ${s.primaryAction.action} \u2014 this is the first thing the user sees and does`),i.push(`SURFACE: ${s.primaryAction.dashboardSurface}`)),i.push(`METRICS: Key counts for ${o.map(m=>at(m)).join(", ")}`),i.push("RECENT: Latest activity or items"),a.push({name:"Dashboard",type:"dashboard",route:"/dashboard",purpose:s.primaryAction?`Action surface \u2014 user comes here to ${s.primaryAction.action.toLowerCase()}. Not a stats display.`:`Overview of ${o.map(m=>at(m)).join(", ")} with key metrics.`,informationHierarchy:i,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:r});let l=o[0];if(l){let m=at(l),u=m.toLowerCase().endsWith("s")?m:`${m}s`;a.push({name:`${m} List`,type:"detail",route:`/${u.toLowerCase()}`,purpose:`Browse, search, and manage ${u.toLowerCase()}. Create new ${m.toLowerCase()}s.`,informationHierarchy:[`HEADER: "${u}" title + "Add ${m}" button`,"SEARCH: Filter/search bar \u2014 users will have many items",`TABLE: ${(l.fields||[]).slice(0,5).map(g=>Qe(g)).join(", ")} columns`,"ROW ACTIONS: Edit, delete on each row"],interactionStates:[`Empty state: "No ${u.toLowerCase()} yet" with create CTA`,"Loading state: skeleton table rows",`Search with no results: "No ${u.toLowerCase()} matching..." with clear filter`],entities:[{name:m,fields:(l.fields||[]).map(g=>({name:Qe(g),type:xo(g)})),sampleData:nr(l)}]})}s.steps.some(m=>{let u=`${m.name??m.title??""} ${m.description??""}`.toLowerCase();return u.includes("landing")||u.includes("hero")||u.includes("marketing")||u.includes("homepage")})&&a.push({name:"Landing Page",type:"landing",route:"/",purpose:`Convince visitors to sign up for ${s.name}. Answer: what is this, who is it for, why should I care.`,informationHierarchy:[`HERO: One sentence about what ${s.name} does \u2014 not "Transform your X", be specific`,"CTA: Sign up / Get started \u2014 one clear action","PROOF: What makes this valuable (features, not buzzwords)","SECONDARY CTA: Repeat the sign up prompt"],interactionStates:["Mobile: hero stacks vertically, nav collapses to hamburger","Desktop: hero side-by-side or centered, full nav"],entities:[]});let p=[];for(let m of o.slice(0,3)){let u=at(m);(m.fields||[]).find(b=>Qe(b).toLowerCase()==="name"||Qe(b).toLowerCase()==="title")&&p.push(`What if a ${u.toLowerCase()}'s name is 47 characters? Does the layout break?`),p.push(`What if there are 0 ${u.toLowerCase()}s? 1? 500?`)}return s.authModel&&s.authModel!=="none"&&p.push("What does a brand-new user see? (no data, no setup)"),p.push("What if the network is slow? What loads first?"),{appName:s.name,summary:s.summary??"",screens:a,navigation:{style:s.navStyle??"sidebar",items:n},primaryAction:s.primaryAction??null,designDirection:{tone:e.tone??"professional",accentColor:e.accentColor??"blue",navStyle:s.navStyle??"sidebar",fonts:e.fonts??{heading:"Inter",body:"Inter"}},edgeCases:p}}function qi(s,o){let t=[];t.push(`# Wireframe sketch for ${s.appName}`),t.push(""),t.push(`**${s.appName}** \u2014 ${s.summary}`),t.push(""),o&&(t.push("## Feedback to apply"),t.push(o),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 e of s.screens){t.push(`### ${e.name} (\`${e.route}\`)`),t.push(`**Purpose**: ${e.purpose}`),t.push(""),t.push("**Information hierarchy** (render in this order, top to bottom):");for(let n of e.informationHierarchy)t.push(`- ${n}`);t.push(""),t.push("**Interaction states** (add HTML comments for non-visible states):");for(let n of e.interactionStates)t.push(`- ${n}`);if(t.push(""),e.entities.length>0){t.push("**Data model and sample content** (use this real data, not lorem ipsum):");for(let n of e.entities)t.push(`
|
|
4122
|
-
**${n.name}** \u2014 fields: ${n.fields.map(a=>`${a.name} (${a.type})`).join(", ")}`),
|
|
4123
|
-
`)}function cr(s){return lt(So(),".mistflow","mockup-state",`${s}.json`)}function zi(s){let o=cr(s);if(!qt(o))return null;try{return JSON.parse(ar(o,"utf-8"))}catch{return null}}function ko(s,o){let t=lt(So(),".mistflow","mockup-state");ir(t,{recursive:!0}),Oi(cr(s),JSON.stringify(o,null,2))}async function dr(s){let{planId:o,feedback:t,approved:e}=s,n=Ui(s.projectPath??process.cwd()),a=lt(So(),".mistflow","plans",`${o}.json`);if(!qt(a))return d(`Plan not found for planId '${o}'. Call mist_plan to generate a plan first.`,!0);let r;try{r=JSON.parse(ar(a,"utf-8"))}catch{return d("Failed to read plan file. Call mist_plan again.",!0)}let i=r.plan;if(!i)return d("Plan data is empty. Call mist_plan again.",!0);let l=zi(o);l||(l={planId:o,iterationCount:0,approved:!1,screens:[],feedback:[]});let c=lt(n,".mistflow","mockups");if(ir(c,{recursive:!0}),e){l.approved=!0,ko(o,l);let g=qt(c)?lr(c).filter(b=>b.endsWith(".html")):[];return d(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='${i.name}', and planId='${o}' 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++,ko(o,l);let p=Bi(i);l.screens=p.screens.map(g=>g.name),ko(o,l);let m=qi(p,t??void 0),u;return l.iterationCount>=3&&(u="The wireframe is shaping up \u2014 want to keep refining the layout, or start building?"),d(JSON.stringify({status:"wireframe",iterationCount:l.iterationCount,screens:p.screens.map(g=>({name:g.name,type:g.type,route:g.route})),wireframePrompt:m,designDirection:p.designDirection,...u?{nudge:u}:{},mockupFile:`mockup-${o}.html`,nextAction:t?`Apply the user's feedback to the wireframe. Rewrite .mistflow/mockups/mockup-${o}.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-${o}.html, then open it in the browser. Ask the user if the layout feels right.`}))}function ur(s){let o=lt(s,".mistflow","mockups");return qt(o)?lr(o).filter(t=>t.endsWith(".html")).map(t=>lt(o,t)):[]}var pr="\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 mr=`# Landing Page Rules
|
|
4126
|
+
`;var pr={"consumer-warm":{id:"consumer-warm",name:"Consumer Warm",description:"Personal, lifestyle, and wellness apps (habits, journals, recipes, mood, meditation)",content:tr},"consumer-bold":{id:"consumer-bold",name:"Consumer Bold",description:"Energetic, achievement-driven apps (fitness, workouts, sports, gaming, social)",content:or},"professional-clean":{id:"professional-clean",name:"Professional Clean",description:"Service and appointment-based apps (booking, clinics, salons, real estate, restaurants)",content:sr},"education-structured":{id:"education-structured",name:"Education Structured",description:"Learning and knowledge apps (courses, quizzes, flashcards, LMS, tutorials)",content:rr},"marketplace-browse":{id:"marketplace-browse",name:"Marketplace Browse",description:"Browsing and shopping apps (marketplaces, directories, shops, catalogs, food delivery)",content:nr},"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:ir},"content-editorial":{id:"content-editorial",name:"Content Editorial",description:"Blogs, newsletters, magazines, publications, documentation, knowledge bases",content:ar},"devtool-technical":{id:"devtool-technical",name:"Devtool Technical",description:"Developer tools, APIs, CLIs, SDKs, infrastructure, monitoring, AI/ML platforms",content:lr},"creative-showcase":{id:"creative-showcase",name:"Creative Showcase",description:"Portfolios, design agencies, creative studios, photographers, artists",content:cr},"finance-clarity":{id:"finance-clarity",name:"Finance Clarity",description:"Fintech, invoicing, expense tracking, budgeting, payroll, accounting, banking",content:dr}};function ur(s){return pr[s]?.content}function mr(s,o){let e=o?.archetype;if(e&&e in pr)return e;if(e===null)return;let t=s.toLowerCase(),n=o?.tone??"",a=o?.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":n==="warm-minimal"||n==="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":a==="b2c"?"consumer-warm":"saas-analytical"}import{existsSync as qt,mkdirSync as gr,readFileSync as fr,readdirSync as yr,writeFileSync as Yi}from"fs";import{join as lt,resolve as Ki}from"path";import{homedir as Ro}from"os";function at(s){return s.entity??s.name??"Unknown"}function Qe(s){return typeof s=="string"?s:s.name}function Io(s){return typeof s=="string"?"text":s.type}function hr(s){let o=s.sampleRows;if(Array.isArray(o)&&o.length>0)return o.slice(0,3).map(n=>{let a={};for(let[r,i]of Object.entries(n))a[r]=i==null?"":String(i);return a});let e=s.fields||[],t=[];for(let n=0;n<3;n++){let a={};for(let r of e)a[Qe(r)]=Ji(Qe(r),Io(r),at(s),n);t.push(a)}return t}function Ji(s,o,e,t){let n=s.toLowerCase(),a=(o||"text").toLowerCase();return n==="name"||n==="title"?[`${e} Alpha`,`${e} Beta`,`${e} Gamma`][t]:n==="email"?["alice@example.com","bob@example.com","carol@example.com"][t]:n==="status"?["Active","Pending","Completed"][t]:n==="priority"?["High","Medium","Low"][t]:n.includes("date")||a==="date"?["Jan 15, 2024","Feb 20, 2024","Mar 10, 2024"][t]:n.includes("price")||n.includes("amount")||n.includes("cost")?["$29","$49","$99"][t]:n.includes("count")||n.includes("quantity")||a==="number"||a==="integer"?["12","34","56"][t]:a==="boolean"||a==="bool"?["Yes","No","Yes"][t]:n.includes("description")||a==="textarea"?["Brief description here","Another example entry","Third sample item"][t]:[`Sample ${t+1}`][0]}function Qi(s){let o=s.dataModel??[],e=s.pages??[],t=s.design??{},n=e.map(m=>({label:m.name??m.path??"Page",route:m.path??m.route??"/"})),a=[],r=o.slice(0,3).map(m=>({name:at(m),fields:(m.fields||[]).map(p=>({name:Qe(p),type:Io(p)})),sampleData:hr(m)})),i=[];s.primaryAction&&(i.push(`PRIMARY: ${s.primaryAction.action} \u2014 this is the first thing the user sees and does`),i.push(`SURFACE: ${s.primaryAction.dashboardSurface}`)),i.push(`METRICS: Key counts for ${o.map(m=>at(m)).join(", ")}`),i.push("RECENT: Latest activity or items"),a.push({name:"Dashboard",type:"dashboard",route:"/dashboard",purpose:s.primaryAction?`Action surface \u2014 user comes here to ${s.primaryAction.action.toLowerCase()}. Not a stats display.`:`Overview of ${o.map(m=>at(m)).join(", ")} with key metrics.`,informationHierarchy:i,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:r});let l=o[0];if(l){let m=at(l),p=m.toLowerCase().endsWith("s")?m:`${m}s`;a.push({name:`${m} List`,type:"detail",route:`/${p.toLowerCase()}`,purpose:`Browse, search, and manage ${p.toLowerCase()}. Create new ${m.toLowerCase()}s.`,informationHierarchy:[`HEADER: "${p}" title + "Add ${m}" button`,"SEARCH: Filter/search bar \u2014 users will have many items",`TABLE: ${(l.fields||[]).slice(0,5).map(g=>Qe(g)).join(", ")} columns`,"ROW ACTIONS: Edit, delete on each row"],interactionStates:[`Empty state: "No ${p.toLowerCase()} yet" with create CTA`,"Loading state: skeleton table rows",`Search with no results: "No ${p.toLowerCase()} matching..." with clear filter`],entities:[{name:m,fields:(l.fields||[]).map(g=>({name:Qe(g),type:Io(g)})),sampleData:hr(l)}]})}s.steps.some(m=>{let p=`${m.name??m.title??""} ${m.description??""}`.toLowerCase();return p.includes("landing")||p.includes("hero")||p.includes("marketing")||p.includes("homepage")})&&a.push({name:"Landing Page",type:"landing",route:"/",purpose:`Convince visitors to sign up for ${s.name}. Answer: what is this, who is it for, why should I care.`,informationHierarchy:[`HERO: One sentence about what ${s.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 m of o.slice(0,3)){let p=at(m);(m.fields||[]).find(w=>Qe(w).toLowerCase()==="name"||Qe(w).toLowerCase()==="title")&&u.push(`What if a ${p.toLowerCase()}'s name is 47 characters? Does the layout break?`),u.push(`What if there are 0 ${p.toLowerCase()}s? 1? 500?`)}return s.authModel&&s.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:s.name,summary:s.summary??"",screens:a,navigation:{style:s.navStyle??"sidebar",items:n},primaryAction:s.primaryAction??null,designDirection:{tone:t.tone??"professional",accentColor:t.accentColor??"blue",navStyle:s.navStyle??"sidebar",fonts:t.fonts??{heading:"Inter",body:"Inter"}},edgeCases:u}}function Xi(s,o){let e=[];e.push(`# Wireframe sketch for ${s.appName}`),e.push(""),e.push(`**${s.appName}** \u2014 ${s.summary}`),e.push(""),o&&(e.push("## Feedback to apply"),e.push(o),e.push("")),e.push("## Design principles"),e.push(""),e.push("Apply these when deciding layout and hierarchy:"),e.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."),e.push("2. **Interaction states** \u2014 Every screen has at least: empty, loading, populated. Show the populated state but add HTML comments noting the others."),e.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."),e.push(`4. **Subtraction** \u2014 "As little design as possible" (Dieter Rams). Every element earns its pixels. If removing something doesn't hurt, remove it.`),e.push("5. **Design for trust** \u2014 Clear labels, predictable layout, obvious actions. No mystery meat navigation."),e.push(""),e.push("## Wireframe rules (strict)"),e.push(""),e.push("Write a **single self-contained HTML file** saved to `.mistflow/mockups/mockup-{planId}.html` (replace `{planId}` with the actual plan ID)."),e.push(""),e.push("The wireframe must:"),e.push("- Use **system fonts only** (`-apple-system, system-ui, sans-serif`) \u2014 no Google Fonts, no CDN"),e.push("- Use **inline CSS only** \u2014 no external stylesheets, no Tailwind CDN"),e.push("- Look **intentionally rough** \u2014 thin gray borders (#ddd), light backgrounds (#f8f8f8), no color, no shadows"),e.push("- Use **realistic placeholder content** that matches this specific app (sample data provided below) \u2014 NOT lorem ipsum"),e.push("- Include **HTML comments** explaining design decisions (e.g., `<!-- Primary action is prominent because users come here to check in members -->`)"),e.push("- Show **all screens in a single page** using tabs/sections that the user can click through"),e.push("- Be **responsive** \u2014 test that it looks reasonable at both 1200px and 375px widths"),e.push("- Include a small header bar showing: screen name tabs + the design direction summary"),e.push(""),e.push("The wireframe must NOT:"),e.push("- Use any color except grayscale (#333, #666, #999, #ddd, #f8f8f8, white)"),e.push("- Use any external dependencies \u2014 no CDN, no imports, no build step"),e.push("- Look polished \u2014 it should feel like a sketch on a whiteboard, not a finished product"),e.push("- Include decorative elements \u2014 no icons (use text labels), no illustrations, no gradients"),e.push(""),e.push("## Screens to wireframe"),e.push("");for(let t of s.screens){e.push(`### ${t.name} (\`${t.route}\`)`),e.push(`**Purpose**: ${t.purpose}`),e.push(""),e.push("**Information hierarchy** (render in this order, top to bottom):");for(let n of t.informationHierarchy)e.push(`- ${n}`);e.push(""),e.push("**Interaction states** (add HTML comments for non-visible states):");for(let n of t.interactionStates)e.push(`- ${n}`);if(e.push(""),t.entities.length>0){e.push("**Data model and sample content** (use this real data, not lorem ipsum):");for(let n of t.entities)e.push(`
|
|
4127
|
+
**${n.name}** \u2014 fields: ${n.fields.map(a=>`${a.name} (${a.type})`).join(", ")}`),e.push("```json"),e.push(JSON.stringify(n.sampleData,null,2)),e.push("```");e.push("")}}e.push("## Navigation"),e.push(`**Style**: ${s.navigation.style} (use this layout)`),e.push("**Items**:");for(let t of s.navigation.items)e.push(`- ${t.label} \u2192 \`${t.route}\``);if(e.push(""),s.primaryAction&&(e.push("## Primary action (this drives the layout)"),e.push(`- **Action**: ${s.primaryAction.action}`),e.push(`- **Flow**: ${s.primaryAction.flow}`),e.push(`- **Dashboard must show**: ${s.primaryAction.dashboardSurface}`),e.push(""),e.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."),e.push("")),s.edgeCases.length>0){e.push("## Edge cases to consider"),e.push("Add HTML comments in the wireframe where these matter:");for(let t of s.edgeCases)e.push(`- ${t}`);e.push("")}return e.push("## Design direction (DO NOT apply to wireframe \u2014 this is for reference only)"),e.push(`The final app will use: ${s.designDirection.tone} tone, ${s.designDirection.accentColor} accent, ${s.designDirection.navStyle} nav, ${s.designDirection.fonts.heading} / ${s.designDirection.fonts.body} fonts.`),e.push("The wireframe is grayscale and rough. These tokens will be applied during the actual build."),e.push(""),e.push("## After writing the wireframe"),e.push(""),e.push("1. Write the file to `.mistflow/mockups/mockup-{planId}.html` (replace `{planId}` with the actual plan ID)"),e.push("2. Open it: `open .mistflow/mockups/mockup-{planId}.html`"),e.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?"`),e.join(`
|
|
4128
|
+
`)}function br(s){return lt(Ro(),".mistflow","mockup-state",`${s}.json`)}function Zi(s){let o=br(s);if(!qt(o))return null;try{return JSON.parse(fr(o,"utf-8"))}catch{return null}}function Ao(s,o){let e=lt(Ro(),".mistflow","mockup-state");gr(e,{recursive:!0}),Yi(br(s),JSON.stringify(o,null,2))}async function wr(s){let{planId:o,feedback:e,approved:t}=s,n=Ki(s.projectPath??process.cwd()),a=lt(Ro(),".mistflow","plans",`${o}.json`);if(!qt(a))return d(`Plan not found for planId '${o}'. Call mist_plan to generate a plan first.`,!0);let r;try{r=JSON.parse(fr(a,"utf-8"))}catch{return d("Failed to read plan file. Call mist_plan again.",!0)}let i=r.plan;if(!i)return d("Plan data is empty. Call mist_plan again.",!0);let l=Zi(o);l||(l={planId:o,iterationCount:0,approved:!1,screens:[],feedback:[]});let c=lt(n,".mistflow","mockups");if(gr(c,{recursive:!0}),t){l.approved=!0,Ao(o,l);let g=qt(c)?yr(c).filter(w=>w.endsWith(".html")):[];return d(JSON.stringify({status:"approved",message:`Wireframe approved after ${l.iterationCount} iteration(s). Layout direction is locked in.`,mockupFiles:g.map(w=>`.mistflow/mockups/${w}`),nextAction:`NEXT: Call mist_build with action='init', name='${i.name}', and planId='${o}' to create the project. The wireframe in .mistflow/mockups/ will be used as layout reference during implementation.`}))}e?(l.iterationCount++,l.feedback.push(e)):l.iterationCount++,Ao(o,l);let u=Qi(i);l.screens=u.screens.map(g=>g.name),Ao(o,l);let m=Xi(u,e??void 0),p;return l.iterationCount>=3&&(p="The wireframe is shaping up \u2014 want to keep refining the layout, or start building?"),d(JSON.stringify({status:"wireframe",iterationCount:l.iterationCount,screens:u.screens.map(g=>({name:g.name,type:g.type,route:g.route})),wireframePrompt:m,designDirection:u.designDirection,...p?{nudge:p}:{},mockupFile:`mockup-${o}.html`,nextAction:e?`Apply the user's feedback to the wireframe. Rewrite .mistflow/mockups/mockup-${o}.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-${o}.html, then open it in the browser. Ask the user if the layout feels right.`}))}function vr(s){let o=lt(s,".mistflow","mockups");return qt(o)?yr(o).filter(e=>e.endsWith(".html")).map(e=>lt(o,e)):[]}var kr="\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 xr=`# Landing Page Rules
|
|
4124
4129
|
|
|
4125
4130
|
These rules apply to every landing page. They are non-negotiable.
|
|
4126
4131
|
|
|
@@ -4517,7 +4522,7 @@ app/
|
|
|
4517
4522
|
\`\`\`
|
|
4518
4523
|
|
|
4519
4524
|
Each section is a separate component file. The page.tsx simply imports and stacks them. Do NOT build one monolithic page component.
|
|
4520
|
-
`;var
|
|
4525
|
+
`;var Sr=`# Design Doctrine
|
|
4521
4526
|
|
|
4522
4527
|
This is the standard for every UI you generate. It is not aspirational. It is the floor.
|
|
4523
4528
|
|
|
@@ -4583,7 +4588,7 @@ Before submitting any UI file, read it with this checklist and regenerate if any
|
|
|
4583
4588
|
8. Is there ONE memorable moment on the page (signature typography / orchestrated motion / unexpected layout)? **If zero, add one. If three, keep the strongest one.**
|
|
4584
4589
|
|
|
4585
4590
|
If any answer fails, the UI is not ready. Redesign \u2014 not tweak \u2014 the failing piece.
|
|
4586
|
-
`;var
|
|
4591
|
+
`;var Tr=`# Typography
|
|
4587
4592
|
|
|
4588
4593
|
Typography is the cheapest way to escape AI slop and the most visible place it shows up. Every generic landing uses Inter. Every remarkable landing picks something you'd notice.
|
|
4589
4594
|
|
|
@@ -4671,7 +4676,7 @@ Pick exactly one:
|
|
|
4671
4676
|
- **Mixed case display** \u2014 Title Case for proper nouns, SmallCaps for the verb. Editorial feel.
|
|
4672
4677
|
|
|
4673
4678
|
Do NOT use: gradient text (\`bg-clip-text\`) on one word of the headline. That effect is the most overused generic-AI marker.
|
|
4674
|
-
`;var
|
|
4679
|
+
`;var Cr="# Color\n\nColor is a commitment, not a palette swatch. The #1 failure mode in AI-generated landings is an evenly-distributed palette: a little emerald here, a little amber there, a gradient, a badge. That's visual hedging \u2014 it signals \"I couldn't pick a point of view.\"\n\nPick one color. Use it with intent.\n\n## The Token System\n\nEvery color on every screen goes through a CSS variable defined in `globals.css`. You do not hand-pick hex values in JSX. You do not use Tailwind palette utilities (`bg-emerald-500`, `text-blue-600`, `border-slate-200`). If a color isn't in the token scheme, it doesn't exist for this app.\n\n### Core tokens\n\n- `--color-background` \u2014 page background\n- `--color-foreground` \u2014 default body text\n- `--color-card` \u2014 elevated surface (cards, panels)\n- `--color-card-foreground` \u2014 text on cards\n- `--color-muted` \u2014 low-emphasis surface fill\n- `--color-muted-foreground` \u2014 secondary text (\u2265 WCAG AA contrast vs background)\n- `--color-border` \u2014 subtle dividers\n- `--color-input` \u2014 form input borders\n- `--color-popover` / `--color-popover-foreground` \u2014 dropdowns, tooltips, menus\n\n### Interactive tokens\n\n- `--color-primary` \u2014 primary CTAs, links, active tab, focus ring\n- `--color-primary-foreground` \u2014 text on primary (derived for WCAG AA contrast)\n- `--color-ring` \u2014 focus outline (same hue as primary)\n- `--color-secondary` / `--color-secondary-foreground` \u2014 quieter than primary, more than muted\n- `--color-accent` / `--color-accent-foreground` \u2014 hover states on neutrals\n\n### Semantic tokens\n\n- `--color-success` \u2014 confirmation, \"verified\" banners, positive states\n- `--color-warning` \u2014 pending states, attention, non-destructive caution\n- `--color-destructive` / `--color-destructive-foreground` \u2014 errors, irreversible actions\n- `--color-info` \u2014 neutral system messages, tips\n\n**You use these semantic tokens for all status UI.** When you need a \"green checkmark\" you reach for `text-success`, not `text-emerald-500`. When you need an amber alert you reach for `bg-warning/10 text-warning`, not `bg-amber-50 text-amber-700`.\n\n## Usage Rules\n\n**Primary color:**\n- Primary CTAs, links, the active navigation tab, focus rings.\n- NOT for headings. Black or foreground-colored headings are stronger.\n- NOT for large surface fills (it's a splash of color, not a wash).\n- NOT for borders except focus rings.\n- One bold use of primary beats twenty sprinkled uses.\n\n**Neutrals do the structural work:**\n- Background, cards, borders \u2014 all come from the neutral scale.\n- Text hierarchy via opacity on foreground (`text-foreground`, `text-foreground/80`, `text-muted-foreground`) \u2014 not different colors.\n\n**Semantic colors:**\n- Use `bg-success/10 text-success border border-success/30` for success banners (reproducibility: a light-tinted version of the success color for surface, the solid version for text and border).\n- Same shape for warning, info, destructive.\n\n## Dark Mode\n\nCommit to one theme. If DESIGN.md says the app is dark-themed, make it unambiguously dark \u2014 `#0c0c0c` / near-black base, not neutral-900. If light-themed, commit to light.\n\nDo NOT auto-swap based on `prefers-color-scheme` when the design system declared a theme direction. The appStyle chose a theme for a reason; respect it.\n\n## Forbidden\n\n- Palette utilities: `bg-emerald-500`, `text-blue-600`, `border-slate-200`, `from-purple-500`, `via-pink-300`, `to-amber-400`. Every one of these in your output is a slop indicator.\n- Hex literals inside `className` or inline `style` props.\n- Named colors from CSS (`red`, `lightgrey`, `rebeccapurple`) \u2014 never used in production UI; always a sign of rushed work.\n- Purple gradients on white. The single most overused AI-design pattern.\n- Three-color gradient pills. Rainbow badges. \"Vibrant\" anything.\n\n## Contrast\n\nEvery text/surface pair in DESIGN.md is already WCAG AA-verified by Mistflow's contrast test. You do not need to re-check \u2014 the tokens are safe by construction. You DO need to:\n\n- Not override token combinations with hardcoded classes that might fail contrast.\n- Maintain contrast for overlays (text on images, modals on scrims). When stacking text over a gradient or image, include a scrim (`bg-background/80 backdrop-blur-sm`) so body text stays \u2265 4.5:1.\n\n## Getting Color Wrong\n\nThe specific failure mode to avoid: a page where primary is used in five places, all small and incidental \u2014 a checkmark, a hover state, a tiny badge. That's AI hedging. Either use primary confidently on a hero CTA and mean it, OR don't use primary on the page at all and let neutrals carry the identity.\n";var Pr=`# Motion
|
|
4675
4680
|
|
|
4676
4681
|
Motion is the most-ignored dimension of AI-generated UI. Generic landings either have no motion or sprinkle tiny fades on everything. Remarkable landings pick ONE orchestrated moment and execute it precisely.
|
|
4677
4682
|
|
|
@@ -4749,7 +4754,7 @@ Primary CTA has a 2px vertical translate on \`:active\` with 80ms \`--ease-quart
|
|
|
4749
4754
|
## One-Line Motion Rule
|
|
4750
4755
|
|
|
4751
4756
|
If the motion you're about to add could be described as "subtle animation on scroll", delete it. Design motion is specific, named, and described in one sentence: "the headline lines drop in from above with a 50ms stagger at page load." If you can't describe it that specifically, you don't have a motion design \u2014 you have motion noise.
|
|
4752
|
-
`;var
|
|
4757
|
+
`;var Ar=`# Spatial Composition
|
|
4753
4758
|
|
|
4754
4759
|
Layout is where AI-slop is most visible. A centered single-column hero with a pill, headline, subhead, and two CTAs is the most-recognizable generic-SaaS pattern on the internet. It is the layout you must not use.
|
|
4755
4760
|
|
|
@@ -4819,7 +4824,7 @@ To signal "designed, not templated":
|
|
|
4819
4824
|
Negative space is a design element. A hero with \`py-32\` and a tight \`max-w-2xl\` text block in a wide viewport reads as confident. A hero that fills every pixel with checkmarks, logos, and counters reads as scared.
|
|
4820
4825
|
|
|
4821
4826
|
Pick: generous breathing (luxury / refined / editorial) OR controlled density (dashboards / industrial / brutalist). Never a mushy middle.
|
|
4822
|
-
`;var
|
|
4827
|
+
`;var Ir=`# Interaction
|
|
4823
4828
|
|
|
4824
4829
|
Every interactive element has a designed state for every phase: rest, hover, focus, active, disabled. The difference between remarkable and adequate lives in these states.
|
|
4825
4830
|
|
|
@@ -4904,7 +4909,7 @@ Small moments that add up to "designed":
|
|
|
4904
4909
|
- **Empty states** offer a specific next action, not "No data yet".
|
|
4905
4910
|
|
|
4906
4911
|
These micro-interactions are the texture of a designed product. Ship them.
|
|
4907
|
-
`;var
|
|
4912
|
+
`;var Rr=`# UX Writing
|
|
4908
4913
|
|
|
4909
4914
|
Copy is design. A remarkable page has copy that could only be about this specific product. A generic page has copy that could describe a hundred.
|
|
4910
4915
|
|
|
@@ -4980,14 +4985,14 @@ If there's a pricing page:
|
|
|
4980
4985
|
## The Footer
|
|
4981
4986
|
|
|
4982
4987
|
A footer with the same five links ("Product / Pricing / Docs / Blog / Terms") is fine, but the company name / tagline should be specific. "StandupSync \u2014 async updates, human-sized" is better than "\xA9 2026 StandupSync Inc."
|
|
4983
|
-
`;function
|
|
4984
|
-
`)}function zt(s){return s.entity??s.name??"Unknown"}function na(s){return s.length===0?"":typeof s[0]=="string"?s.join(", "):s.map(o=>`${o.name} (${o.type})`).join(", ")}function xr(s){return s.path??s.route??s.name??""}function ia(s){let o=(s||"text").toLowerCase();return o==="string"||o==="varchar"||o==="char"?"text":o==="integer"||o==="int"||o==="number"||o==="float"||o==="decimal"||o==="double"?"number":o==="boolean"||o==="bool"?"boolean":o==="date"||o==="datetime"||o==="timestamp"?"date":o==="email"?"email":o==="url"||o==="uri"?"url":o==="enum"||o==="select"?"select":o==="text"||o==="longtext"||o==="textarea"?"textarea":"text"}function Sr(s,o){if(!s.entities||s.entities.length===0)return o;let t=s.entities.map(e=>e.toLowerCase());return o.filter(e=>{let n=zt(e).toLowerCase();return t.some(a=>n.includes(a)||a.includes(n))})}function aa(s,o){if(!s.pages||s.pages.length===0)return[];let t=s.pages.map(e=>e.toLowerCase());return o.filter(e=>{let n=(e.name??"").toLowerCase(),a=xr(e).toLowerCase();return t.some(r=>n.includes(r)||r.includes(n)||a.includes(r))})}var la=new Set(["landing","design","dashboard","crud","layout","admin","auth","schema","integration","multi-tenant","deploy","general"]);function ca(s){let o=s.stepType;if(o&&la.has(o))return o;if(s.integrationId)return"integration";let t=`${s.name} ${s.description}`.toLowerCase();return t.includes("crud")||t.includes("list")&&t.includes("create")?"crud":t.includes("auth")||t.includes("login")||t.includes("register")?"auth":t.includes("admin")&&(t.includes("panel")||t.includes("dashboard")||t.includes("manage")||t.includes("users"))?"admin":t.includes("dashboard")||t.includes("overview")||t.includes("analytics")?"dashboard":t.includes("schema")||t.includes("database")||t.includes("model")?"schema":t.includes("layout")||t.includes("sidebar")||t.includes("navigation")?"layout":t.includes("deploy")||t.includes("cloudflare")?"deploy":t.includes("organization")||t.includes("team")||t.includes("workspace")||t.includes("multi-tenant")||t.includes("invite member")?"multi-tenant":t.includes("landing")||t.includes("hero")||t.includes("marketing")||t.includes("homepage")?"landing":t.includes("design")||t.includes("theme")||t.includes("styling")||t.includes("ui polish")||t.includes("visual")?"design":"general"}function da(s){switch(s){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 ua(s,o){let t=[];if(t.push("### Design choices (decided at plan time \u2014 follow these exactly):"),s.tone&&t.push(`- **App tone**: ${s.tone}`),s.fonts&&(t.push(`- **Heading font**: ${s.fonts.heading} (load from Google Fonts)`),t.push(`- **Body font**: ${s.fonts.body} (load from Google Fonts)`)),t.push("- **All color comes from CSS variables** \u2014 never use Tailwind palette utilities like `bg-emerald-500`, `text-blue-600`, `border-slate-200`. Use `bg-primary`, `text-primary-foreground`, `bg-muted`, `text-muted-foreground`, `border-border`, `bg-card`, `text-foreground`, `bg-destructive`, etc. The primary/accent is already wired in globals.css from the chosen design system."),t.push("- **Outbound URLs (invite links, email CTAs, absolute hrefs in server code):** use `process.env.BETTER_AUTH_URL` as the base. Do NOT use `process.env.NEXT_PUBLIC_APP_URL` \u2014 Next.js bakes every `NEXT_PUBLIC_*` literal into the production bundle at build time, so the scaffolded localhost default ships to prod and invite emails land on `http://localhost:3000`. Client components should use relative URLs (they resolve to the current origin automatically)."),s.borderRadius){let e={sharp:"2px",subtle:"6px",rounded:"12px",pill:"9999px"};t.push(`- **Border radius**: ${s.borderRadius} (${e[s.borderRadius]??s.borderRadius}) \u2014 set as --radius in globals.css`)}if(s.shadowStyle){let e={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**: ${s.shadowStyle} \u2014 ${e[s.shadowStyle]??s.shadowStyle}`)}if(s.cardStyle){let e={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**: ${s.cardStyle} \u2014 ${e[s.cardStyle]??s.cardStyle}`)}if(s.landingTone&&t.push(`- **Landing page tone**: ${s.landingTone}`),s.visualStrategy){let e=s.visualStrategy,n=s.heroPhoto!==!1,a=n&&!!e.heroImages?.length;if(t.push(""),t.push("### Visual strategy:"),a&&e.heroImages&&e.heroImages.length>0){t.push("**Hero image** \u2014 use this Unsplash photo as the landing page hero BACKGROUND:");let r=e.heroImages[0];t.push(`- URL: ${r.url}`),t.push(`- Alt text for img tag: "${r.alt||"Hero image"} \u2014 Photo by ${r.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 n&&!e.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.'"):n||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(e.sectionImages?.length){t.push("**Section images available** \u2014 use these for feature sections, about sections, or testimonial backgrounds (NOT the hero):");for(let r of e.sectionImages)t.push(`- ${r.url} \u2014 alt: "${r.alt||"section image"} \u2014 Photo by ${r.photographer} on Unsplash"`)}(a||e.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"),a?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(pr),t.join(`
|
|
4985
|
-
`)}async function
|
|
4988
|
+
`;function ma(s){return new Promise(o=>{let e=ua({port:s,host:"127.0.0.1"});e.on("connect",()=>{e.destroy(),o(!0)}),e.on("error",()=>{o(!1)})})}var dp=Eo.object({projectPath:Eo.string().optional().describe("Path to the project directory (default: cwd)"),step:Eo.number().optional().describe("Specific step number to implement (default: next incomplete step)")});function ha(s){let o=ct(s,"mistflow.json");if(!xt(o))return null;try{return JSON.parse(_o(o,"utf-8"))}catch{return null}}function Er(s,o){let e=ct(s,"mistflow.json");No(e,JSON.stringify(o,null,2)+`
|
|
4989
|
+
`)}function zt(s){return s.entity??s.name??"Unknown"}function ga(s){return s.length===0?"":typeof s[0]=="string"?s.join(", "):s.map(o=>`${o.name} (${o.type})`).join(", ")}function Nr(s){return s.path??s.route??s.name??""}function fa(s){let o=(s||"text").toLowerCase();return o==="string"||o==="varchar"||o==="char"?"text":o==="integer"||o==="int"||o==="number"||o==="float"||o==="decimal"||o==="double"?"number":o==="boolean"||o==="bool"?"boolean":o==="date"||o==="datetime"||o==="timestamp"?"date":o==="email"?"email":o==="url"||o==="uri"?"url":o==="enum"||o==="select"?"select":o==="text"||o==="longtext"||o==="textarea"?"textarea":"text"}function _r(s,o){if(!s.entities||s.entities.length===0)return o;let e=s.entities.map(t=>t.toLowerCase());return o.filter(t=>{let n=zt(t).toLowerCase();return e.some(a=>n.includes(a)||a.includes(n))})}function ya(s,o){if(!s.pages||s.pages.length===0)return[];let e=s.pages.map(t=>t.toLowerCase());return o.filter(t=>{let n=(t.name??"").toLowerCase(),a=Nr(t).toLowerCase();return e.some(r=>n.includes(r)||r.includes(n)||a.includes(r))})}var ba=new Set(["landing","design","dashboard","crud","layout","admin","auth","schema","integration","multi-tenant","deploy","general"]);function wa(s){let o=s.stepType;if(o&&ba.has(o))return o;if(s.integrationId)return"integration";let e=`${s.name} ${s.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 va(s){switch(s){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 ka(s,o){let e=[];if(e.push("### Design choices (decided at plan time \u2014 follow these exactly):"),s.tone&&e.push(`- **App tone**: ${s.tone}`),s.fonts&&(e.push(`- **Heading font**: ${s.fonts.heading} (load from Google Fonts)`),e.push(`- **Body font**: ${s.fonts.body} (load from Google Fonts)`)),e.push("- **All color comes from CSS variables** \u2014 never use Tailwind palette utilities like `bg-emerald-500`, `text-blue-600`, `border-slate-200`. Use `bg-primary`, `text-primary-foreground`, `bg-muted`, `text-muted-foreground`, `border-border`, `bg-card`, `text-foreground`, `bg-destructive`, etc. The primary/accent is already wired in globals.css from the chosen design system."),e.push("- **Outbound URLs (invite links, email CTAs, absolute hrefs in server code):** use `process.env.BETTER_AUTH_URL` as the base. Do NOT use `process.env.NEXT_PUBLIC_APP_URL` \u2014 Next.js bakes every `NEXT_PUBLIC_*` literal into the production bundle at build time, so the scaffolded localhost default ships to prod and invite emails land on `http://localhost:3000`. Client components should use relative URLs (they resolve to the current origin automatically)."),s.borderRadius){let t={sharp:"2px",subtle:"6px",rounded:"12px",pill:"9999px"};e.push(`- **Border radius**: ${s.borderRadius} (${t[s.borderRadius]??s.borderRadius}) \u2014 set as --radius in globals.css`)}if(s.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"};e.push(`- **Shadow style**: ${s.shadowStyle} \u2014 ${t[s.shadowStyle]??s.shadowStyle}`)}if(s.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"};e.push(`- **Card style**: ${s.cardStyle} \u2014 ${t[s.cardStyle]??s.cardStyle}`)}if(s.landingTone&&e.push(`- **Landing page tone**: ${s.landingTone}`),s.visualStrategy){let t=s.visualStrategy,n=s.heroPhoto!==!1,a=n&&!!t.heroImages?.length;if(e.push(""),e.push("### Visual strategy:"),a&&t.heroImages&&t.heroImages.length>0){e.push("**Hero image** \u2014 use this Unsplash photo as the landing page hero BACKGROUND:");let r=t.heroImages[0];e.push(`- URL: ${r.url}`),e.push(`- Alt text for img tag: "${r.alt||"Hero image"} \u2014 Photo by ${r.photographer} on Unsplash"`),e.push("- Use as full-bleed background behind the entire hero section with a dark overlay (bg-black/60 or similar for readability). The photo is atmosphere and context, NOT the main visual \u2014 the glassmorphic product card sits on top.")}else n&&!t.heroImages?.length?e.push("**Hero background** \u2014 the user requested a lifestyle photo, but no Unsplash image was fetched (likely a transient API issue). Fall back gracefully: use the design preset's gradient + glassmorphism, AND tell the user in your reply: 'I couldn't fetch a stock photo for the hero this time \u2014 using a CSS gradient instead. You can add a photo later by editing the hero section in app/page.tsx.'"):n||e.push("**Hero background** \u2014 user opted for CSS-only (no photo). Use the design preset's specified gradient, animated background, or glassmorphism. No Unsplash.");if(t.sectionImages?.length){e.push("**Section images available** \u2014 use these for feature sections, about sections, or testimonial backgrounds (NOT the hero):");for(let r of t.sectionImages)e.push(`- ${r.url} \u2014 alt: "${r.alt||"section image"} \u2014 Photo by ${r.photographer} on Unsplash"`)}(a||t.sectionImages?.length)&&e.push("For image attribution: put photographer credit in the img alt text (already provided above) and add an HTML comment <!-- Images from Unsplash --> near the images. Do NOT add visible attribution text on the page."),e.push(""),e.push("**Hero layout \u2014 split hero with product UI mockup (follow this exactly):**"),e.push("The hero uses a split layout with text on the left and a product preview on the right. This is non-negotiable for consumer apps and SaaS/tool apps alike."),e.push(""),e.push("```"),e.push("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"),e.push("\u2502 [Logo] [Sign In] [CTA \u2192] \u2502 \u2190 transparent nav, NOT sticky"),e.push("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"),e.push("\u2502 \u2502"),e.push("\u2502 \u25CF Built for [audience] \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502"),e.push("\u2502 \u2502 Glassmorphic \u2502 \u2502"),e.push("\u2502 Big bold headline, \u2502 product mockup \u2502 \u2502"),e.push("\u2502 accent color on key word \u2502 card showing \u2502 \u2502"),e.push("\u2502 \u2502 real app data \u2502 \u2502"),e.push("\u2502 Description paragraph \u2502 (stats, table, \u2502 \u2502"),e.push("\u2502 \u2502 chart, etc.) \u2502 \u2502"),e.push("\u2502 [Primary CTA \u2192] [Secondary] \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502"),e.push("\u2502 \u2502"),e.push("\u2502 500+ 25K+ 99% \u2502"),e.push("\u2502 Label Label Label \u2502"),e.push("\u2502 \u2502"),a?e.push("\u2502 \u2190 full-bleed photo bg + dark overlay behind all \u2192 \u2502"):e.push("\u2502 \u2190 preset gradient / glass background behind all \u2192 \u2502"),e.push("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"),e.push("```"),e.push(""),e.push("**Left side (~55%)**: badge pill \u2192 bold headline (use accent color on ONE key word or phrase) \u2192 description \u2192 two CTA buttons (primary filled + secondary outline/ghost) \u2192 stats row with 3 proof points"),e.push("**Right side (~45%)**: A glassmorphic floating card that previews what the app looks like inside:"),e.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)"),e.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"),e.push("- Must be DOMAIN-SPECIFIC: a library app shows book catalog rows, a gym app shows check-in stats, a CRM shows pipeline cards"),e.push("- Add subtle inner elements with `bg-white/5` or `bg-white/10` for depth"),e.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 e.push(""),e.push(kr),e.join(`
|
|
4990
|
+
`)}async function xa(s){try{let o=await ys("nextjs",s);return{reminders:o.reminders,skill:o.skill}}catch{return{reminders:`### ${s} step
|
|
4986
4991
|
- Follow existing patterns in the codebase
|
|
4987
|
-
- Server Components by default, "use client" only when interactivity is needed`,skill:""}}}async function ma(s,o,t,e,n,a){let r=[];r.push(`## Step ${s.number}: ${s.name}`),r.push(""),r.push("### What to build:"),r.push(s.description),r.push(""),o.primaryAction&&(r.push("### Primary user action (non-negotiable):"),r.push(`- **Core action**: ${o.primaryAction.action}`),r.push(`- **User flow**: ${o.primaryAction.flow}`),r.push(`- **Dashboard must show**: ${o.primaryAction.dashboardSurface}`),r.push(""),r.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."),r.push(""));let l=["landing","design","dashboard","crud","layout","admin","general","auth"].includes(n);if(o.design&&l?(r.push(ua(o.design,{hasDesignPreset:!!o.landingDesign&&(n==="landing"||n==="design")})),r.push("")):o.design&&!l&&(r.push("### Design tokens (for reference only \u2014 this step is not UI-focused):"),o.design.fonts&&r.push(`- Fonts: ${o.design.fonts.heading} / ${o.design.fonts.body}`),r.push("- Colors come from CSS variables (`--color-primary`, `--color-background`, etc.) \u2014 not Tailwind palette names."),r.push("")),a){let h=ur(a);if(h.length>0){r.push("### Approved wireframe (MUST READ before writing any files):"),r.push("The user approved a wireframe sketch before building. **Read these files NOW before writing any code for this step:**");for(let w of h){let P=w.replace(a,"").replace(/^\//,"");r.push(`- \`${P}\``)}r.push(""),r.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."),r.push(""),r.push("The wireframe is intentionally rough (grayscale, system fonts). Your job is to:"),r.push("1. **Keep the same layout structure** \u2014 same information hierarchy, same element placement, same sections in the same order"),r.push("2. **Apply the design tokens** \u2014 colors, fonts, shadows, radius from the plan design choices above"),r.push("3. **Elevate the visual quality** \u2014 make it feel designed for THIS specific app, not generic"),r.push("4. **Respect the HTML comments** \u2014 they explain WHY things are placed where they are"),r.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"),r.push("")}}o.roles&&Array.isArray(o.roles)&&o.roles.length>0&&(r.push("### Role system (from plan):"),r.push(`- Roles: ${o.roles.join(", ")}`),r.push(`- Default role for new signups: ${o.defaultRole??o.roles[0]}`),r.push("- Role helpers are in `lib/roles.ts` \u2014 use `getUserRole()` and `hasRole()` for access checks"),r.push("")),o.multiTenant&&(r.push("### Multi-tenant (from plan):"),r.push("- Organization tables are in `db/schema/organization.ts`"),r.push("- Org helpers are in `lib/org.ts` \u2014 use `getCurrentOrg()` to scope queries"),r.push("- All data queries MUST be scoped to the current org (filter by orgId)"),r.push("- Org switcher component is at `components/org-switcher.tsx`"),r.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."),r.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."),r.push("")),o.language&&(r.push(`### Language: ${o.language}`),r.push(`ALL user-facing text must be written in ${o.language}:`),r.push("- Page titles, headings, labels, button text, placeholder text"),r.push("- Navigation items, menu labels, footer text"),r.push("- Error messages, success messages, empty states"),r.push("- Landing page copy, marketing text, CTAs"),r.push("- Form labels and validation messages"),r.push("Code (variable names, comments, file names) stays in English."),r.push(`Set the HTML lang attribute to the appropriate locale code for ${o.language}.`),r.push(""));let c=["landing","design","auth","general","crud","dashboard"];o.audienceType&&c.includes(n)&&(o.audienceType==="b2c"?(r.push("### Audience: this app belongs to ONE business. The landing page talks TO their customers."),r.push("- Hero: what the customer gets ('Exceptional catering for your next event'), NOT what the tool does"),r.push("- CTAs: customer action ('Order Catering', 'Book Now'), NOT business action ('Get Started Free')"),r.push("- Testimonials: from customers ('They catered our wedding'), NOT from business owners"),r.push("- Features: customer benefits ('Specify your dietary needs'), NOT business benefits ('Track preferences')"),r.push("- Stats: social proof for customers ('2,400+ events served'), NOT internal metrics ('$48k revenue')"),r.push("- The business name IS the brand. Say it like a business homepage, not a SaaS onboarding."),r.push("")):o.audienceType==="b2b"?(r.push("### Audience: this is a SaaS platform. The landing page pitches TO business owners."),r.push("- Hero: the business pain + solution ('Catering orders managed in one place')"),r.push("- CTAs: business owner action ('Start Free Trial', 'Get Started')"),r.push("- Testimonials: from business owners who use the platform"),r.push("- Features: business benefits ('Track dietary preferences across all orders')"),r.push("- Stats: platform metrics ('500+ businesses', '50K+ orders processed')"),r.push("")):o.audienceType==="internal"&&(r.push("### Audience: internal staff tool. No marketing copy needed."),r.push("- No landing page. Auth page copy is functional: 'Sign in to continue'."),r.push("- Dashboard focuses on operational efficiency, not onboarding or sales."),r.push(""))),t.length>0&&(r.push("### Already completed:"),t.forEach(h=>r.push(`- ${h}`)),r.push(""));let p=o.dataModel?Sr(s,o.dataModel):[];p.length>0&&(r.push("### Data model (from plan):"),p.forEach(h=>{let w=zt(h),P=na(h.fields);r.push(`- **${w}**: ${P}`),r.push(` Schema file: \`db/schema/${w.toLowerCase().replace(/\s+/g,"-")}.ts\``)}),r.push(""));let m=o.pages?aa(s,o.pages):[];m.length>0&&(r.push("### Pages to create/update:"),m.forEach(h=>{let w=h.description?` \u2014 ${h.description}`:"";r.push(`- \`${xr(h)}\`${w}`)}),r.push("")),n==="crud"&&p.length>0&&p.forEach(h=>{let w=zt(h),P=w.toLowerCase().replace(/\s+/g,"-"),I=P.endsWith("s")?P:`${P}s`;r.push(`### Files for ${w} CRUD:`),r.push(`- List page: \`app/(dashboard)/${I}/page.tsx\` (Server Component)`),r.push(`- Detail page: \`app/(dashboard)/${I}/[id]/page.tsx\``),r.push(`- Create page: \`app/(dashboard)/${I}/new/page.tsx\``),r.push(`- Server Actions: \`app/(dashboard)/${I}/actions.ts\``),r.push(`- DataTable columns: \`components/${P}-table-columns.tsx\``),r.push(`- Form: \`components/${P}-form.tsx\``),r.push("")});let u=o.appStyle;if(l){r.push("## Design Doctrine (the standard for every UI step)"),r.push(""),r.push(hr),r.push(""),r.push("## Design Reference Library"),r.push(""),r.push("### Typography"),r.push(gr),r.push(""),r.push("### Color"),r.push(fr),r.push(""),r.push("### Motion"),r.push(yr),r.push(""),r.push("### Spatial Composition"),r.push(br),r.push(""),r.push("### Interaction"),r.push(wr),r.push(""),r.push("### UX Writing"),r.push(vr),r.push("");let h=a?ct(a,"DESIGN.md"):void 0,w=h&&xt(h)?(()=>{try{return Co(h,"utf-8")}catch{return null}})():null;w&&(r.push("### Design system (source of truth: DESIGN.md):"),r.push(""),r.push("The project's DESIGN.md defines the visual identity. Follow it exactly. It's emitted in google-labs-code/design.md format (YAML front matter with colors/typography/rounded/spacing tokens, plus markdown rationale). All UI elements must use the project's CSS custom properties (--color-primary, --color-background, etc. \u2014 these are generated from the YAML tokens) and the fonts configured in layout.tsx. If DESIGN.md and this plan disagree, DESIGN.md wins. The user may have edited it."),r.push(""),r.push(w),r.push(""))}if(l){let h=rr(o.summary??o.name,{tone:o.design?.tone,audienceType:o.audienceType,archetype:o.design?.archetype});if(h){let w=sr(h);w&&(r.push(`### App archetype: ${h}`),r.push(""),r.push(`This app matches the **${h}** archetype. Follow these component-level patterns for page composition, cards, navigation, spacing, and motion. `+(u?`The app style (${u}) 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.")),r.push(""),r.push(w),r.push(""))}}(n==="landing"||n==="design")&&(r.push(mr),r.push(""));let g=s.integrationId?rt(s.integrationId):void 0;if(g){let h=nt(g.id);if(r.push("### Integration blueprint (follow this closely):"),r.push(""),r.push(`Using integration: **${g.name}** (${g.category})`),h?.docsUrl&&r.push(`Official docs: ${h.docsUrl}`),h?.envVars?.length){r.push(""),r.push("**Required environment variables:**");for(let w of h.envVars)r.push(`- \`${w.key}\`: ${w.description} \u2014 Get it at ${w.setupUrl}`);r.push(""),r.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.")}h?.packages?.length&&(r.push(""),r.push(`**Packages to install:** \`npm install ${h.packages.join(" ")}\``)),r.push(""),r.push("---"),r.push(g.prompt),r.push("---"),r.push(""),r.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."),r.push("")}let{reminders:b,skill:f}=await pa(n);return r.push(b),r.push(""),f&&!(n==="landing"&&o.landingDesign==="freeform")&&(r.push(`### ${n} reference:`),r.push(f),r.push("")),l&&(r.push("## Self-Audit \u2014 run before submitting this file"),r.push(""),r.push(`Read the file you just wrote and answer each of these. If any answer is "yes", REDESIGN the failing piece \u2014 don't tweak classes on the same template. A different composition is required.`),r.push(""),r.push('1. **Pill badge at the top?** A small rounded label saying "Built for" / "New:" / "Made for" with a colored dot? \u2192 redesign the hero without it.'),r.push("2. **Fake browser chrome?** A rounded box with red/yellow/green dots and a fake URL bar? \u2192 delete it. Show real components instead, or no preview."),r.push("3. **Centered single-column hero?** Pill \u2192 headline \u2192 subhead \u2192 two CTAs stacked vertically? \u2192 redesign asymmetrically (see spatial.md)."),r.push("4. **Three checkmark benefit bullets** below the hero CTAs? \u2192 remove the triplet pattern."),r.push("5. **Tailwind palette utilities?** Any `bg-emerald-*`, `bg-amber-*`, `text-violet-*`, `border-slate-*`, `from-purple-*`, etc.? \u2192 replace with token classes (`bg-primary`, `bg-success`, `bg-muted`, `border-border`). No exceptions."),r.push("6. **Inter, Geist, Roboto, Arial, or system-ui** as the primary font? \u2192 pick something distinctive (see typography.md)."),r.push("7. **Purple gradient on white background** anywhere? \u2192 that's the single most overused AI-design pattern. Replace."),r.push('8. **Hardcoded hex values** (e.g. `style={{ color: "#10b981" }}`) or named CSS colors (`red`, `lightgrey`) in className or inline styles? \u2192 replace with CSS variables.'),r.push('9. **Generic copy** ("Boost your productivity", "The platform teams love", "Everything you need to")? \u2192 rewrite with specific nouns, numbers, or mechanisms from this product (see writing.md).'),r.push("10. **Zero memorable moments?** Is there ONE signature element \u2014 typographic hero, orchestrated reveal, asymmetric break, product-specific illustration? If zero, the page is generic. Add one (see motion.md, typography.md)."),r.push(""),r.push(`If every answer is "no, I'm good" \u2014 then submit.`),r.push("")),r.join(`
|
|
4988
|
-
`)}async function
|
|
4992
|
+
- Server Components by default, "use client" only when interactivity is needed`,skill:""}}}async function Sa(s,o,e,t,n,a){let r=[];r.push(`## Step ${s.number}: ${s.name}`),r.push(""),r.push("### What to build:"),r.push(s.description),r.push(""),o.primaryAction&&(r.push("### Primary user action (non-negotiable):"),r.push(`- **Core action**: ${o.primaryAction.action}`),r.push(`- **User flow**: ${o.primaryAction.flow}`),r.push(`- **Dashboard must show**: ${o.primaryAction.dashboardSurface}`),r.push(""),r.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."),r.push(""));let l=["landing","design","dashboard","crud","layout","admin","general","auth"].includes(n);if(o.design&&l?(r.push(ka(o.design,{hasDesignPreset:!!o.landingDesign&&(n==="landing"||n==="design")})),r.push("")):o.design&&!l&&(r.push("### Design tokens (for reference only \u2014 this step is not UI-focused):"),o.design.fonts&&r.push(`- Fonts: ${o.design.fonts.heading} / ${o.design.fonts.body}`),r.push("- Colors come from CSS variables (`--color-primary`, `--color-background`, etc.) \u2014 not Tailwind palette names."),r.push("")),a){let h=vr(a);if(h.length>0){r.push("### Approved wireframe (MUST READ before writing any files):"),r.push("The user approved a wireframe sketch before building. **Read these files NOW before writing any code for this step:**");for(let v of h){let E=v.replace(a,"").replace(/^\//,"");r.push(`- \`${E}\``)}r.push(""),r.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."),r.push(""),r.push("The wireframe is intentionally rough (grayscale, system fonts). Your job is to:"),r.push("1. **Keep the same layout structure** \u2014 same information hierarchy, same element placement, same sections in the same order"),r.push("2. **Apply the design tokens** \u2014 colors, fonts, shadows, radius from the plan design choices above"),r.push("3. **Elevate the visual quality** \u2014 make it feel designed for THIS specific app, not generic"),r.push("4. **Respect the HTML comments** \u2014 they explain WHY things are placed where they are"),r.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"),r.push("")}}o.roles&&Array.isArray(o.roles)&&o.roles.length>0&&(r.push("### Role system (from plan):"),r.push(`- Roles: ${o.roles.join(", ")}`),r.push(`- Default role for new signups: ${o.defaultRole??o.roles[0]}`),r.push("- Role helpers are in `lib/roles.ts` \u2014 use `getUserRole()` and `hasRole()` for access checks"),r.push("")),o.multiTenant&&(r.push("### Multi-tenant (from plan):"),r.push("- Organization tables are in `db/schema/organization.ts`"),r.push("- Org helpers are in `lib/org.ts` \u2014 use `getCurrentOrg()` to scope queries"),r.push("- All data queries MUST be scoped to the current org (filter by orgId)"),r.push("- Org switcher component is at `components/org-switcher.tsx`"),r.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."),r.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."),r.push("")),o.language&&(r.push(`### Language: ${o.language}`),r.push(`ALL user-facing text must be written in ${o.language}:`),r.push("- Page titles, headings, labels, button text, placeholder text"),r.push("- Navigation items, menu labels, footer text"),r.push("- Error messages, success messages, empty states"),r.push("- Landing page copy, marketing text, CTAs"),r.push("- Form labels and validation messages"),r.push("Code (variable names, comments, file names) stays in English."),r.push(`Set the HTML lang attribute to the appropriate locale code for ${o.language}.`),r.push(""));let c=["landing","design","auth","general","crud","dashboard"];o.audienceType&&c.includes(n)&&(o.audienceType==="b2c"?(r.push("### Audience: this app belongs to ONE business. The landing page talks TO their customers."),r.push("- Hero: what the customer gets ('Exceptional catering for your next event'), NOT what the tool does"),r.push("- CTAs: customer action ('Order Catering', 'Book Now'), NOT business action ('Get Started Free')"),r.push("- Testimonials: from customers ('They catered our wedding'), NOT from business owners"),r.push("- Features: customer benefits ('Specify your dietary needs'), NOT business benefits ('Track preferences')"),r.push("- Stats: social proof for customers ('2,400+ events served'), NOT internal metrics ('$48k revenue')"),r.push("- The business name IS the brand. Say it like a business homepage, not a SaaS onboarding."),r.push("")):o.audienceType==="b2b"?(r.push("### Audience: this is a SaaS platform. The landing page pitches TO business owners."),r.push("- Hero: the business pain + solution ('Catering orders managed in one place')"),r.push("- CTAs: business owner action ('Start Free Trial', 'Get Started')"),r.push("- Testimonials: from business owners who use the platform"),r.push("- Features: business benefits ('Track dietary preferences across all orders')"),r.push("- Stats: platform metrics ('500+ businesses', '50K+ orders processed')"),r.push("")):o.audienceType==="internal"&&(r.push("### Audience: internal staff tool. No marketing copy needed."),r.push("- No landing page. Auth page copy is functional: 'Sign in to continue'."),r.push("- Dashboard focuses on operational efficiency, not onboarding or sales."),r.push(""))),e.length>0&&(r.push("### Already completed:"),e.forEach(h=>r.push(`- ${h}`)),r.push(""));let u=o.dataModel?_r(s,o.dataModel):[];u.length>0&&(r.push("### Data model (from plan):"),u.forEach(h=>{let v=zt(h),E=ga(h.fields);r.push(`- **${v}**: ${E}`),r.push(` Schema file: \`db/schema/${v.toLowerCase().replace(/\s+/g,"-")}.ts\``)}),r.push(""));let m=o.pages?ya(s,o.pages):[];m.length>0&&(r.push("### Pages to create/update:"),m.forEach(h=>{let v=h.description?` \u2014 ${h.description}`:"";r.push(`- \`${Nr(h)}\`${v}`)}),r.push("")),n==="crud"&&u.length>0&&u.forEach(h=>{let v=zt(h),E=v.toLowerCase().replace(/\s+/g,"-"),R=E.endsWith("s")?E:`${E}s`;r.push(`### Files for ${v} CRUD:`),r.push(`- List page: \`app/(dashboard)/${R}/page.tsx\` (Server Component)`),r.push(`- Detail page: \`app/(dashboard)/${R}/[id]/page.tsx\``),r.push(`- Create page: \`app/(dashboard)/${R}/new/page.tsx\``),r.push(`- Server Actions: \`app/(dashboard)/${R}/actions.ts\``),r.push(`- DataTable columns: \`components/${E}-table-columns.tsx\``),r.push(`- Form: \`components/${E}-form.tsx\``),r.push("")});let p=o.appStyle;if(l){r.push("## Design Doctrine (the standard for every UI step)"),r.push(""),r.push(Sr),r.push(""),r.push("## Design Reference Library"),r.push(""),r.push("### Typography"),r.push(Tr),r.push(""),r.push("### Color"),r.push(Cr),r.push(""),r.push("### Motion"),r.push(Pr),r.push(""),r.push("### Spatial Composition"),r.push(Ar),r.push(""),r.push("### Interaction"),r.push(Ir),r.push(""),r.push("### UX Writing"),r.push(Rr),r.push("");let h=a?ct(a,"DESIGN.md"):void 0,v=h&&xt(h)?(()=>{try{return _o(h,"utf-8")}catch{return null}})():null;v&&(r.push("### Design system (source of truth: DESIGN.md):"),r.push(""),r.push("The project's DESIGN.md defines the visual identity. Follow it exactly. It's emitted in google-labs-code/design.md format (YAML front matter with colors/typography/rounded/spacing tokens, plus markdown rationale). All UI elements must use the project's CSS custom properties (--color-primary, --color-background, etc. \u2014 these are generated from the YAML tokens) and the fonts configured in layout.tsx. If DESIGN.md and this plan disagree, DESIGN.md wins. The user may have edited it."),r.push(""),r.push(v),r.push(""))}if(l){let h=mr(o.summary??o.name,{tone:o.design?.tone,audienceType:o.audienceType,archetype:o.design?.archetype});if(h){let v=ur(h);v&&(r.push(`### App archetype: ${h}`),r.push(""),r.push(`This app matches the **${h}** archetype. Follow these component-level patterns for page composition, cards, navigation, spacing, and motion. `+(p?`The app style (${p}) 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.")),r.push(""),r.push(v),r.push(""))}}(n==="landing"||n==="design")&&(r.push(xr),r.push(""));let g=s.integrationId?rt(s.integrationId):void 0;if(g){let h=nt(g.id);if(r.push("### Integration blueprint (follow this closely):"),r.push(""),r.push(`Using integration: **${g.name}** (${g.category})`),h?.docsUrl&&r.push(`Official docs: ${h.docsUrl}`),h?.envVars?.length){r.push(""),r.push("**Required environment variables:**");for(let v of h.envVars)r.push(`- \`${v.key}\`: ${v.description} \u2014 Get it at ${v.setupUrl}`);r.push(""),r.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.")}h?.packages?.length&&(r.push(""),r.push(`**Packages to install:** \`npm install ${h.packages.join(" ")}\``)),r.push(""),r.push("---"),r.push(g.prompt),r.push("---"),r.push(""),r.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."),r.push("")}let{reminders:w,skill:f}=await xa(n);return r.push(w),r.push(""),f&&!(n==="landing"&&o.landingDesign==="freeform")&&(r.push(`### ${n} reference:`),r.push(f),r.push("")),l&&(r.push("## Self-Audit \u2014 run before submitting this file"),r.push(""),r.push(`Read the file you just wrote and answer each of these. If any answer is "yes", REDESIGN the failing piece \u2014 don't tweak classes on the same template. A different composition is required.`),r.push(""),r.push('1. **Pill badge at the top?** A small rounded label saying "Built for" / "New:" / "Made for" with a colored dot? \u2192 redesign the hero without it.'),r.push("2. **Fake browser chrome?** A rounded box with red/yellow/green dots and a fake URL bar? \u2192 delete it. Show real components instead, or no preview."),r.push("3. **Centered single-column hero?** Pill \u2192 headline \u2192 subhead \u2192 two CTAs stacked vertically? \u2192 redesign asymmetrically (see spatial.md)."),r.push("4. **Three checkmark benefit bullets** below the hero CTAs? \u2192 remove the triplet pattern."),r.push("5. **Tailwind palette utilities?** Any `bg-emerald-*`, `bg-amber-*`, `text-violet-*`, `border-slate-*`, `from-purple-*`, etc.? \u2192 replace with token classes (`bg-primary`, `bg-success`, `bg-muted`, `border-border`). No exceptions."),r.push("6. **Inter, Geist, Roboto, Arial, or system-ui** as the primary font? \u2192 pick something distinctive (see typography.md)."),r.push("7. **Purple gradient on white background** anywhere? \u2192 that's the single most overused AI-design pattern. Replace."),r.push('8. **Hardcoded hex values** (e.g. `style={{ color: "#10b981" }}`) or named CSS colors (`red`, `lightgrey`) in className or inline styles? \u2192 replace with CSS variables.'),r.push('9. **Generic copy** ("Boost your productivity", "The platform teams love", "Everything you need to")? \u2192 rewrite with specific nouns, numbers, or mechanisms from this product (see writing.md).'),r.push("10. **Zero memorable moments?** Is there ONE signature element \u2014 typographic hero, orchestrated reveal, asymmetric break, product-specific illustration? If zero, the page is generic. Add one (see motion.md, typography.md)."),r.push(""),r.push(`If every answer is "no, I'm good" \u2014 then submit.`),r.push("")),r.join(`
|
|
4993
|
+
`)}async function Dr(s){let{projectPath:o,step:e}=s,t=da(o??process.cwd()),n=ha(t);if(!n)return Ee(t);if(!xt(ct(t,"node_modules")))return d(`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:S,ensureShadcnComponents:z}=await import("./self-heal-BXJXUFUF.js");await S(t);let Y=await z(t);Y.failed?console.error(`[implement] ${Y.failed}`):Y.installed.length>0&&console.error(`[implement] installed ${Y.installed.length} shadcn components`)}catch(S){console.error("[implement] self-heal skipped:",S instanceof Error?S.message:String(S))}let a=n.plan;if(!a||!a.steps||a.steps.length===0)return d("No project plan found. Start by describing your app idea first \u2014 the AI will create a plan for you.",!0);let r,i=a.steps.find(S=>S.status==="in_progress");if(i){let S=a.steps.findIndex(z=>z.number===i.number);S!==-1&&(a.steps[S].status="completed",r=`Auto-completed step ${i.number} (${i.name})`,Er(t,n))}let l;if(e!==void 0){if(l=a.steps.find(S=>S.number===e),!l)return d(`Step ${e} not found. The plan has ${a.steps.length} steps (numbered ${a.steps[0].number} to ${a.steps[a.steps.length-1].number}).`,!0)}else if(l=a.steps.find(S=>S.status!=="completed"),!l)return d(JSON.stringify({message:"All plan steps are completed!",completedSteps:a.steps.map(S=>S.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 c=a.steps.filter(S=>S.status==="completed").map(S=>`Step ${S.number}: ${S.name}`),{readLocalState:u}=await import("./state-manager-GBP2NSYC.js"),m=u(t),p=wa(l),g=[];if((p==="crud"||p==="schema")&&a.dataModel&&l.entities&&l.entities.length>0){let S=_r(l,a.dataModel);for(let z of S){let Y=zt(z);try{let G=(z.fields||[]).map(F=>typeof F=="string"?{name:F,type:"text"}:{name:F.name,type:fa(F.type),required:F.required!==!1});if(G.length===0)continue;let W=n.dbProvider==="neon"?"nextjs-neon":"nextjs",_=await bs(W,Y,G),ee=0,J=0;for(let F of _.files){let ie=ct(t,F.path);if(xt(ie)){J++;continue}ca(pa(ie),{recursive:!0}),No(ie,F.content),ee++}let q=ct(t,"db","index.ts");if(xt(q)){let F=_o(q,"utf-8");F.includes(_.dbExport)||No(q,F.trimEnd()+`
|
|
4989
4994
|
`+_.dbExport+`
|
|
4990
|
-
`)}ee>0?g.push(`${_.entityPascal} CRUD (${ee} new files${
|
|
4995
|
+
`)}ee>0?g.push(`${_.entityPascal} CRUD (${ee} new files${J>0?`, ${J} existing skipped`:""})`):J>0&&g.push(`${_.entityPascal} CRUD (all ${J} files already exist \u2014 skipped)`)}catch(G){console.error(`Module generation failed for ${Y} (non-fatal):`,G instanceof Error?G.message:G)}}}let w=await Sa(l,a,c,null,p,t),f=a.steps.findIndex(S=>S.number===l.number);if(f!==-1&&(n.plan.steps[f].status="in_progress",Er(t,n)),m&&n.projectId){let{syncRemoteState:S}=await import("./state-manager-GBP2NSYC.js");S(n.projectId,m).catch(()=>{})}let h=a.steps.every(S=>S.status==="completed"||S.number===l.number),v;if(h){let S=a.primaryAction?.action??"the core action",z=a.primaryAction?.flow??"sign up \u2192 reach dashboard \u2192 complete primary action";v=`THIS IS THE LAST STEP. Rules for speed:
|
|
4991
4996
|
|
|
4992
4997
|
1. Write ALL files using PARALLEL tool calls \u2014 batch multiple Write/Edit calls in a single message.
|
|
4993
4998
|
2. Do NOT read files you already know (AGENTS.md, CLAUDE.md, mistflow.json, middleware.ts, lib/auth.ts, lib/db.ts).
|
|
@@ -4996,111 +5001,111 @@ A footer with the same five links ("Product / Pricing / Docs / Blog / Terms") is
|
|
|
4996
5001
|
- app/page.tsx must be a real landing page, NOT a redirect to /login
|
|
4997
5002
|
- middleware.ts must have "/" in PUBLIC_EXACT or PUBLIC_PREFIXES
|
|
4998
5003
|
- Forms must use server actions (actions.ts with 'use server'), NOT setTimeout/simulate
|
|
4999
|
-
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
|
|
5004
|
+
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 v=`IMPLEMENT THIS STEP NOW. Rules for speed:
|
|
5000
5005
|
|
|
5001
5006
|
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.
|
|
5002
5007
|
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.
|
|
5003
5008
|
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.
|
|
5004
5009
|
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).
|
|
5005
|
-
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
|
|
5006
|
-
`+l}let a=St(n);return a.length===0?d(JSON.stringify({errors:[],rawOutput:n.slice(0,2e3),message:"Build failed but I could not extract specific errors. Here is the raw output."})):d(JSON.stringify({errors:a,rawOutput:n.slice(0,2e3),message:`Found ${a.length} error${a.length===1?"":"s"} in the build output.`}))}import{existsSync as
|
|
5007
|
-
`)}),!1)}try{let g=await dt(
|
|
5008
|
-
${
|
|
5009
|
-
`)}`,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(a.push(
|
|
5010
|
-
${
|
|
5011
|
-
`)}`,fix:"These patterns make the landing page look AI-generated. Fix them to create a more distinctive, professional design."}});a.push(
|
|
5010
|
+
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 E=va(p),R={stepNumber:l.number,totalSteps:a.steps.length,estimatedMinutes:E,announcement:`Starting step ${l.number} of ${a.steps.length}: ${l.name}. This step usually takes ${E.min}\u2013${E.max} minutes.`},P=JSON.stringify({instruction:w,step:{number:l.number,name:l.name,description:l.description,status:"in_progress"},stepTiming:R,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.",...r?{autoCompleted:r}:{},...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:`${c.length}/${a.steps.length} steps done`,nextAction:v});return await ma(3e3)?Ue("http://localhost:3000",P):d(P)}import{z as Do}from"zod";import{resolve as Ta}from"path";import{execFileSync as Ca}from"child_process";var fp=Do.object({projectPath:Do.string().optional().describe("Path to the project directory (default: current working directory)"),buildOutput:Do.string().optional().describe("Build output to parse (if not provided, runs npm run build)")});function St(s){let o=[],e=/([^\s(]+)\((\d+),(\d+)\):\s*error\s+(TS\d+):\s*(.+)/g,t;for(;(t=e.exec(s))!==null;){let[,l,c,u,m,p]=t;o.push({file:l,line:parseInt(c,10),column:parseInt(u,10),message:`${m}: ${p}`,humanMessage:`There is a type error in ${l} on line ${c}: ${p}`,suggestion:`Check line ${c} in ${l}. ${m==="TS2345"?"The types of the arguments do not match.":`Fix the ${m} error.`}`})}let n=/(?:Error:\s*)?\.\/([^\s:]+):(\d+):(\d+)\s*\n\s*(.+)/g;for(;(t=n.exec(s))!==null;){let[,l,c,u,m]=t;o.some(p=>p.file===l&&p.line===parseInt(c,10))||o.push({file:l,line:parseInt(c,10),column:parseInt(u,10),message:m,humanMessage:`There is an error in ${l} on line ${c}: ${m.trim()}`,suggestion:`Check line ${c} in ${l} and fix the issue.`})}let a=/Module not found:\s*(?:Error:\s*)?Can't resolve ['"]([^'"]+)['"]\s*(?:in\s*['"]?([^'"]+)['"]?)?/g;for(;(t=a.exec(s))!==null;){let[,l,c]=t;o.push({file:c,message:`Module not found: ${l}`,humanMessage:`The file ${c??"your project"} is trying to import '${l}' which is not installed.`,suggestion:`Run npm install ${l}`})}let r=/Package subpath ['"]([^'"]+)['"] is not defined by "exports" in .*?node_modules\/([^/]+(?:\/[^/]+)?)\//g;for(;(t=r.exec(s))!==null;){let[,l,c]=t;o.push({message:`ERR_PACKAGE_PATH_NOT_EXPORTED: ${c}${l}`,humanMessage:`The package '${c}' does not export the subpath '${l}'. This is usually caused by a version conflict between packages that depend on different major versions of '${c}'.`,suggestion:`Add '${c}' at the version that exports '${l}' as an optionalDependency in package.json (this pins it at root level and lets the other version nest). Then delete node_modules and package-lock.json, and run npm install.`})}let i=/SyntaxError:\s*([^\s:]+):\s*(.+?)\s*\((\d+):(\d+)\)/g;for(;(t=i.exec(s))!==null;){let[,l,c,u,m]=t;o.some(p=>p.file===l&&p.line===parseInt(u,10))||o.push({file:l,line:parseInt(u,10),column:parseInt(m,10),message:`SyntaxError: ${c}`,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 o}async function Mr(s){let{projectPath:o,buildOutput:e}=s,t=Ta(o??process.cwd()),n=e??"";if(!e)try{return Ca("npm",["run","build"],{cwd:t,stdio:["pipe","pipe","pipe"],timeout:12e4}),d(JSON.stringify({errors:[],rawOutput:"",message:"Build succeeded with no errors."}))}catch(r){let i=r instanceof Error&&"stderr"in r?String(r.stderr):"",l=r instanceof Error&&"stdout"in r?String(r.stdout):"";n=i+`
|
|
5011
|
+
`+l}let a=St(n);return a.length===0?d(JSON.stringify({errors:[],rawOutput:n.slice(0,2e3),message:"Build failed but I could not extract specific errors. Here is the raw output."})):d(JSON.stringify({errors:a,rawOutput:n.slice(0,2e3),message:`Found ${a.length} error${a.length===1?"":"s"} in the build output.`}))}import{existsSync as Pa,readFileSync as Aa}from"fs";import{join as Ia}from"path";function Ra(s){let o=Ia(s,"mistflow.json");if(!Pa(o))return null;try{return JSON.parse(Aa(o,"utf-8"))}catch{return null}}async function $r(s){try{let o=await fetch(s,{redirect:"follow",signal:AbortSignal.timeout(15e3)}),e=await o.text();return{status:o.status,body:e}}catch(o){return{status:0,body:String(o)}}}async function Ea(s,o,e){try{let t=await fetch(s,{method:"POST",headers:{"Content-Type":"application/json",...e??{}},body:JSON.stringify(o),redirect:"follow",signal:AbortSignal.timeout(15e3)}),n=await t.text(),a;try{a=JSON.parse(n)}catch{}return{status:t.status,json:a}}catch{return{status:0}}}async function jr(s){try{let o=await s.screenshot({type:"png"});return Buffer.from(o).toString("base64")}catch{return}}async function dt(s,o,e){let t=[],n=a=>{a.type()==="error"&&t.push(a.text())};s.on("console",n);try{let a=await e(),r=await jr(s);return{name:o,status:a.pass?"pass":"fail",detail:a.detail,fix:a.fix,screenshot:r,consoleErrors:t.length>0?t:void 0}}catch(a){let r=await jr(s);return{name:o,status:"fail",detail:`Unexpected error: ${a instanceof Error?a.message:String(a)}`,screenshot:r,consoleErrors:t.length>0?t:void 0}}finally{s.removeListener("console",n)}}async function Lr(s){let o=s.projectPath??process.cwd(),e=Ra(o),t=s.url;if(t||(t=e?.deploy?.url),!t)return d("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 n=e?.projectId,a=[],r=await $r(`${t}/api/health`);if(r.status!==200)return a.push({name:"Health endpoint",status:"fail",detail:`Returns ${r.status}`,fix:"The worker is not running or crashed on startup. Check app/api/health/route.ts exists and the build succeeded."}),Ht(t,a);a.push({name:"Health endpoint",status:"pass",detail:"Returns 200"});let i=await $r(`${t}/api/auth/ok`);if(i.status!==200)return a.push({name:"Auth system",status:"fail",detail:`Auth endpoint returns ${i.status}`,fix:"Better Auth is not working. Check lib/auth.ts, lib/db.ts, and that your database env vars are set."}),Ht(t,a);a.push({name:"Auth system",status:"pass",detail:"Better Auth running"});let l,c,u;if(n){let g=await is(n);if(g){console.error("[qa] Calling seed endpoint for session token");let w=await Ea(`${t}/api/admin/seed`,{token:g.seedToken,email:g.email,password:"QaTemp1!"});w.status===200&&w.json?(l=w.json.sessionToken,c=w.json.email,w.json.seeded?(u=w.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 ${w.status}`)}}if(!l)return a.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."}),Ht(t,a);let m,p;try{let{getIsolatedContext:g,takeScreenshot:w}=await import("./browser-manager-QW3OYPFB.js"),f=await g();m=f.context,p=f.page}catch(g){let w=g instanceof Error?g.message:String(g);return d(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:w,httpChecks:a.map(({screenshot:f,...h})=>h),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(`
|
|
5012
|
+
`)}),!1)}try{let g=await dt(p,"Landing page",async()=>{await p.goto(t,{waitUntil:"domcontentloaded",timeout:3e4}),await p.waitForLoadState("networkidle").catch(()=>{});let f=await p.evaluate(()=>{let v=document.body;if(!v)return"";let E=document.createTreeWalker(v,NodeFilter.SHOW_TEXT),R="",C;for(;C=E.nextNode();){let P=C.parentElement;if(P){let j=window.getComputedStyle(P);j.display!=="none"&&j.visibility!=="hidden"&&parseFloat(j.opacity)>0&&(R+=C.textContent?.trim()+" ")}}return R.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 h=p.url();return h.includes("/login")||h.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)`}});a.push(g);let w=!1;if(u){let f=await dt(p,"Login",async()=>{await p.goto(`${t}/login`,{waitUntil:"domcontentloaded",timeout:15e3}),await p.waitForLoadState("networkidle").catch(()=>{});let h=p.locator('input[type="email"], input[name="email"], input[placeholder*="email" i]'),v=p.locator('input[type="password"], input[name="password"]');try{await h.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 h.first().fill(c),await v.first().fill(u),await p.locator('button[type="submit"], button:has-text("Sign in"), button:has-text("Log in"), button:has-text("Login")').first().click();try{await p.waitForURL(R=>!R.pathname.includes("/login"),{timeout:1e4})}catch{let R=await p.locator('[role="alert"], .text-red-500, .text-destructive, [data-error]').first().textContent().catch(()=>null);return{pass:!1,detail:R?`Login failed: ${R}`:"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 w=!0,{pass:!0,detail:`Logged in, redirected to ${p.url()}`}});a.push(f)}else a.push({name:"Login",status:"pass",detail:"Skipped form login (redeploy, password unavailable). Using session injection."});if(!w&&l){let f=new URL(t).hostname,h=t.startsWith("https");await m.addCookies([{name:h?"__Secure-better-auth.session_token":"better-auth.session_token",value:l,domain:f,path:"/",httpOnly:!0,secure:h,sameSite:"Lax"}]),console.error("[qa] Injected session cookie for dashboard checks")}{let f=await dt(p,"Dashboard",async()=>{p.url().includes("/dashboard")||(await p.goto(`${t}/dashboard`,{waitUntil:"domcontentloaded",timeout:15e3}),await p.waitForLoadState("networkidle").catch(()=>{}));let R=await p.content();return R.length<1e3?{pass:!1,detail:`Dashboard page is very small (${R.length} bytes)`,fix:"Check app/(dashboard)/dashboard/page.tsx exists and the dashboard layout doesn't crash."}:await p.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 (${R.length} bytes)`}});a.push(f);let h=await p.evaluate(()=>{let E=[];return document.querySelectorAll("nav a[href], aside a[href]").forEach(C=>{let P=C.getAttribute("href");P&&P.startsWith("/")&&!P.startsWith("/api")&&!P.includes("[")&&P!=="/dashboard"&&P!=="/"&&!P.includes("/login")&&!P.includes("/sign")&&E.push(P)}),[...new Set(E)]});if(h.length>0){let E=0,R=[];for(let C of h.slice(0,8)){let P=await dt(p,`Page: ${C}`,async()=>{await p.goto(`${t}${C}`,{waitUntil:"domcontentloaded",timeout:15e3}),await p.waitForLoadState("networkidle").catch(()=>{});let j=await p.title(),S=await p.content();return j.toLowerCase().includes("500")||j.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."}:j.toLowerCase().includes("404")||j.toLowerCase().includes("not found")?{pass:!1,detail:"Page returns 404",fix:`Page ${C} not found. Create the page or remove the nav link.`}:await p.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."}:S.length<500?{pass:!1,detail:`Page is very small (${S.length} bytes)`,fix:"Page may not have rendered. Check the page component."}:{pass:!0,detail:"Loads without errors"}});P.status==="fail"&&(E++,R.push(C)),a.push(P)}}let v=await dt(p,"Design quality",async()=>{let E=p.url().includes("/dashboard")?p.url():`${t}/dashboard`;p.url().includes("/dashboard")||(await p.goto(E,{waitUntil:"domcontentloaded",timeout:15e3}),await p.waitForLoadState("networkidle").catch(()=>{}));let R=await p.evaluate(()=>{let C=[],P=document.querySelectorAll("h1, h2, h3, h4, h5, h6"),j=new Set;P.forEach(y=>{j.add(window.getComputedStyle(y).fontSize)}),P.length>=3&&j.size<2&&C.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 S=Array.from(P).map(y=>parseInt(y.tagName.charAt(1),10));for(let y=1;y<S.length;y++)if(S[y]-S[y-1]>1){C.push(`TYPOGRAPHY: Heading level skipped (h${S[y-1]} -> h${S[y]}). Use sequential heading levels for accessibility.`);break}let z=document.querySelectorAll("*"),Y=!1,G=!1;z.forEach(y=>{let N=window.getComputedStyle(y);N.backgroundColor==="rgb(0, 0, 0)"&&y.clientHeight>100&&y.clientWidth>200&&(Y=!0);let te=N.backgroundColor,De=N.color;if(te&&!te.includes("0, 0, 0")&&!te.includes("255, 255, 255")&&te!=="rgba(0, 0, 0, 0)"&&te!=="transparent"&&De.match(/rgb\((\d+), (\d+), (\d+)\)/)){let ae=De.match(/rgb\((\d+), (\d+), (\d+)\)/);if(ae){let[Ce,pe,Ae]=[parseInt(ae[1]),parseInt(ae[2]),parseInt(ae[3])],T=Math.abs(Ce-pe)<10&&Math.abs(pe-Ae)<10&&Ce>80&&Ce<180,A=te.match(/rgb\((\d+), (\d+), (\d+)\)/);if(T&&A){let[V,U,ke]=[parseInt(A[1]),parseInt(A[2]),parseInt(A[3])];!(Math.abs(V-U)<15&&Math.abs(U-ke)<15)&&y.textContent&&y.textContent.trim().length>0&&(G=!0)}}}}),Y&&C.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&&C.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 W=document.querySelectorAll('[class*="card"], [class*="Card"], [role="group"]'),_=!1;W.forEach(y=>{y.querySelectorAll('[class*="card"], [class*="Card"]').length>0&&(_=!0)}),_&&C.push("LAYOUT: Nested cards detected (card inside card). Flatten the hierarchy. Use spacing and background color to create separation instead.");let ee=document.querySelectorAll("p, li, span, div"),J=0,q=0;ee.forEach(y=>{y.textContent&&y.textContent.trim().length>20&&y.clientHeight>0&&(q++,window.getComputedStyle(y).textAlign==="center"&&J++)}),q>5&&J/q>.7&&C.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;z.forEach(y=>{let N=window.getComputedStyle(y);N.gap&&N.gap!=="normal"&&N.gap!=="0px"&&F.add(N.gap)}),F.size===1&&z.length>20&&C.push("LAYOUT: Same gap value used everywhere. Vary spacing to create hierarchy: tight within groups (8-12px), generous between sections (32-64px).");let ie=document.querySelectorAll("button, a, input, select, textarea"),le=!1;ie.forEach(y=>{let N=window.getComputedStyle(y);if(N.outline==="none"||N.outline==="0px none rgb(0, 0, 0)"){let te=y;(te.style.outline==="none"||te.style.outline==="0")&&(le=!0)}}),document.querySelectorAll("table, [role='table']").forEach(y=>{y.querySelectorAll("tbody tr").length===0&&(y.parentElement?.querySelector('[class*="empty"], [class*="Empty"], [class*="no-data"], [class*="placeholder"]')||C.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 me=document.querySelectorAll("style"),x=!1;me.forEach(y=>{let N=y.textContent||"";(N.includes("bounce")||N.includes("elastic")||N.match(/cubic-bezier\([^)]*[2-9]\.[0-9]/))&&(x=!0)}),x&&C.push("MOTION: Bounce or elastic easing detected. These feel dated. Use smooth deceleration curves (Quart out, Expo out) instead.");let D=document.querySelectorAll("button, a, input, select, [role='button']"),M=0;return D.forEach(y=>{let N=y.getBoundingClientRect();N.width>0&&N.height>0&&(N.width<32||N.height<32)&&M++}),M>3&&C.push(`ACCESSIBILITY: ${M} interactive elements are smaller than 32x32px. Minimum recommended touch target is 44x44px. Add padding to increase tap area.`),C});return R.length===0?{pass:!0,detail:"No design quality issues detected. Typography hierarchy, color usage, layout patterns, and accessibility basics look good."}:{pass:!1,detail:`${R.length} design quality issue(s) found:
|
|
5013
|
+
${R.map((C,P)=>`${P+1}. ${C}`).join(`
|
|
5014
|
+
`)}`,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(a.push(v),a.find(E=>E.name==="Landing page"&&E.status==="pass")){let E=await dt(p,"Landing design quality",async()=>{await p.goto(t,{waitUntil:"domcontentloaded",timeout:15e3}),await p.waitForLoadState("networkidle").catch(()=>{});let R=await p.evaluate(()=>{let C=[];document.querySelectorAll("*").forEach(z=>{let Y=window.getComputedStyle(z);(Y.getPropertyValue("-webkit-background-clip")||Y.getPropertyValue("background-clip"))==="text"&&z.textContent&&z.textContent.trim().length>0&&C.push("SLOP: Gradient text detected. This is a common AI design pattern. Use solid colors for text.")});let j=document.querySelector("section, [class*='hero'], [class*='Hero'], header + div, main > div:first-child");if(j){let z=(j.textContent||"").toLowerCase(),Y=["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 Y)if(z.match(new RegExp(G))){C.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(z=>{let G=window.getComputedStyle(z).gridTemplateColumns;if(G){let W=G.split(" ").filter(ee=>ee!=="").length,_=z.children;if(W===3&&_.length===3){let ee=Array.from(_).map(F=>F.offsetHeight),J=ee.every(F=>Math.abs(F-ee[0])<5),q=Array.from(_).every(F=>{let ie=F.querySelectorAll("svg"),le=F.querySelectorAll("h2, h3, h4"),K=F.querySelectorAll("p");return ie.length>=1&&le.length>=1&&K.length>=1});J&&q&&C.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.")}}}),C});return R.length===0?{pass:!0,detail:"Landing page design looks intentional. No generic AI patterns detected."}:{pass:!1,detail:`${R.length} landing design issue(s):
|
|
5015
|
+
${R.map((C,P)=>`${P+1}. ${C}`).join(`
|
|
5016
|
+
`)}`,fix:"These patterns make the landing page look AI-generated. Fix them to create a more distinctive, professional design."}});a.push(E)}}}finally{m&&await m.close().catch(()=>{})}if(s.deploymentId){let g=a.filter(h=>h.status==="fail"),w=a.filter(h=>h.status==="pass"),f=Date.now();await as(s.deploymentId,{checks:a.map(({screenshot:h,...v})=>v),overall:g.length===0?"pass":"fail",passed:w.length,failed:g.length,duration_ms:Date.now()-f}).catch(()=>{})}return Ht(t,a)}function Ht(s,o){let e=o.filter(a=>a.status==="fail"),t=o.filter(a=>a.status==="pass"),n=[];if(e.length===0)n.push({type:"text",text:JSON.stringify({status:"pass",message:`QA passed. All ${o.length} checks OK. The app is working correctly.`,url:s,checks:o.map(({screenshot:a,...r})=>r)})});else{let a=e.map((r,i)=>`${i+1}. **${r.name}**: ${r.detail}
|
|
5012
5017
|
Fix: ${r.fix}`).join(`
|
|
5013
5018
|
|
|
5014
|
-
`);n.push({type:"text",text:JSON.stringify({status:"fail",message:`QA found ${
|
|
5019
|
+
`);n.push({type:"text",text:JSON.stringify({status:"fail",message:`QA found ${e.length} issue(s) on the live app. Fix them and redeploy.`,url:s,passed:t.length,failed:e.length,checks:o.map(({screenshot:r,...i})=>i),fixInstructions:`The deployed app at ${s} has ${e.length} issue(s):
|
|
5015
5020
|
|
|
5016
5021
|
${a}
|
|
5017
5022
|
|
|
5018
|
-
Fix these issues in the source code, then call mist_deploy with action='deploy'${s.includes("-pv-")?" environment='preview'":""} to redeploy. After redeploying, call mist_build action='qa' again to verify the fixes.`})})}for(let a of o)a.screenshot&&n.push({type:"image",data:a.screenshot,mimeType:"image/png"});return{content:n}}function
|
|
5019
|
-
`+
|
|
5023
|
+
Fix these issues in the source code, then call mist_deploy with action='deploy'${s.includes("-pv-")?" environment='preview'":""} to redeploy. After redeploying, call mist_build action='qa' again to verify the fixes.`})})}for(let a of o)a.screenshot&&n.push({type:"image",data:a.screenshot,mimeType:"image/png"});return{content:n}}function Ma(s){let o=Wt(Da(),".mistflow","plans",`${s}.json`);if(!Gt(o))return null;try{let e=JSON.parse(Na(o,"utf-8"));return e.plan?e:null}catch{return null}}var $a=de.object({action:de.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. PREFER `npx -y @mistflow-ai/cli install <path>` via your bash/shell tool \u2014 it streams output and has no MCP timeout. Only fall back to this MCP action if your environment can't shell out. 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:de.string().optional().describe("(init) Project name"),planId:de.string().optional().describe("(init/mockup) Plan ID from mist_plan"),plan:de.any().optional().describe("(init) Full plan object \u2014 use planId instead when available"),path:de.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:de.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:de.string().optional().describe("(init) Optional aesthetic-direction hint passed to the backend LLM. DESIGN.md is generated per-product; this string is a hint, not a catalog ID."),confirmDarkTheme:de.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:de.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:de.string().optional().describe("Path to the project directory (default: cwd)"),step:de.number().optional().describe("(implement) Specific step number to implement"),buildOutput:de.string().optional().describe("(debug) Build output to parse instead of running a build"),feedback:de.string().optional().describe("(mockup) User feedback on the current mockup \u2014 describe what to change."),approved:de.boolean().optional().describe("(mockup) Set to true when the user approves the mockup. Locks in the design direction."),url:de.string().optional().describe("(qa) URL to test. Defaults to deploy URL from mistflow.json"),deploymentId:de.string().optional().describe("(qa) Deployment ID to associate QA results with. Passed from mist_deploy output.")}),Or={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:$a,handler:async(s,o)=>{let e=s;switch(e.action){case"init":{if(!e.name)return d("Project name is required for init.",!0);let t=e.plan,n=null;if(e.planId){if(n=Ma(e.planId),!n)return d(`Plan not found for planId '${e.planId}'. The plan may have expired. Call mist_plan again to generate a new plan.`,!0);t=n.plan}if(!t)return d("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 d("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(n?.sourceDeploymentId&&n?.forkToken&&n?.projectId)return er({name:e.name,plan:t,path:e.path,projectId:n.projectId,sourceDeploymentId:n.sourceDeploymentId,forkToken:n.forkToken,requiredEnvVars:n.requiredEnvVars??[],dbProvider:n.dbProvider??"neon",planId:e.planId});if(e.landingDesign){let c=ot(e.landingDesign);c?t.landingDesign=c.id:console.error(`Landing design '${e.landingDesign}' not found \u2014 ignoring.`)}e.appStyle&&(t.appStyle=e.appStyle);let a=t.design,i=t.audienceType==="b2c",l=e.heroPhoto??i;return t.design={...a??{},heroPhoto:l},Xs({name:e.name,plan:t,path:e.path,planId:e.planId},o)}case"install":return Zs({projectPath:e.projectPath},o);case"implement":return Dr({projectPath:e.projectPath,step:e.step});case"debug":return Mr({projectPath:e.projectPath,buildOutput:e.buildOutput});case"build":{let t=_a(e.projectPath??process.cwd());if(!Gt(Wt(t,"mistflow.json")))return d("Not a Mistflow project \u2014 mistflow.json not found. Run mist_build init first.",!0);if(!Gt(Wt(t,"node_modules")))try{Mo("npm",["install"],{cwd:t,stdio:["pipe","pipe","pipe"],timeout:12e4})}catch{return d("npm install failed. Check package.json for issues.",!0)}let a=process.platform==="win32"?"npx.cmd":"npx",r=0,i=2;for(;;){r++;try{Mo(a,["@opennextjs/cloudflare","build"],{cwd:t,stdio:["pipe","pipe","pipe"],timeout:18e4});let l=Gt(Wt(t,".open-next"));return d(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 c=l instanceof Error&&"stderr"in l?String(l.stderr):"",u=l instanceof Error&&"stdout"in l?String(l.stdout):"",m=c+`
|
|
5024
|
+
`+u;if(r<i){let g=[],w=/Module not found:\s*(?:Error:\s*)?Can't resolve ['"]([^'"]+)['"]/g,f;for(;(f=w.exec(m))!==null;){let h=f[1];if(!h.startsWith(".")&&!h.startsWith("@/")&&!h.startsWith("~/")){let v=h.startsWith("@")?h.split("/").slice(0,2).join("/"):h.split("/")[0];g.includes(v)||g.push(v)}}if(g.length>0){console.error(`[build] Auto-installing missing packages: ${g.join(", ")}`);try{Mo("npm",["install",...g],{cwd:t,stdio:["pipe","pipe","pipe"],timeout:6e4});continue}catch{console.error("[build] Auto-install failed, reporting build errors")}}}let p=St(m);return d(JSON.stringify({success:!1,errors:p,rawOutput:m.slice(0,3e3),message:p.length>0?`Build failed with ${p.length} error${p.length===1?"":"s"}:
|
|
5020
5025
|
|
|
5021
|
-
`+
|
|
5026
|
+
`+p.map((g,w)=>{let f=g.file?`${g.file}${g.line?`:${g.line}`:""}`:"unknown";return`${w+1}. [${f}] ${g.humanMessage}
|
|
5022
5027
|
Fix: ${g.suggestion}`}).join(`
|
|
5023
5028
|
|
|
5024
5029
|
`)+`
|
|
5025
5030
|
|
|
5026
|
-
Fix these and run mist_build build again.`:"Build failed. Run mist_build with action='debug' for detailed analysis."}),!0)}}}case"qa":return
|
|
5027
|
-
${
|
|
5028
|
-
`)}`,{passed:i,blocking:r,checksRun:
|
|
5029
|
-
`).filter(Boolean))n(
|
|
5030
|
-
`).filter(Boolean))n(
|
|
5031
|
-
`)}async function
|
|
5032
|
-
`+l.stdout;if(l.signal==="SIGKILL"||c.includes("SIGKILL")||c.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
|
|
5031
|
+
Fix these and run mist_build build again.`:"Build failed. Run mist_build with action='debug' for detailed analysis."}),!0)}}}case"qa":return Lr({projectPath:e.projectPath,url:e.url,deploymentId:e.deploymentId});case"mockup":return e.planId?wr({planId:e.planId,projectPath:e.projectPath,feedback:e.feedback,approved:e.approved}):d("planId is required for mockup. Pass the planId from mist_plan.",!0);default:return d(`Unknown action: ${e.action}. Use mockup, init, implement, build, debug, or qa.`,!0)}}};import{z as Ze}from"zod";import{z as Kt}from"zod";import{resolve as Yr,join as oe,dirname as Jt,basename as el}from"path";import{existsSync as ue,readFileSync as Kr,writeFileSync as tl,unlinkSync as qr,mkdirSync as ol,cpSync as Oo,rmSync as Uo,readdirSync as sl}from"fs";import{execFileSync as rl}from"child_process";import{spawn as nl}from"child_process";import{tmpdir as il}from"os";function Ur(s,o){if(s instanceof xe)switch(s.code){case"auth_missing":return d("No Mistflow credentials found. Run mist_setup to connect your account.",!0);case"auth_revoked":return d("Your Mistflow credentials were revoked. Run mist_setup to reconnect.",!0);case"auth_expired":case"auth_invalid":case"auth_org_not_found":return d("Your Mistflow session needs to be refreshed. Run mist_setup to reconnect.",!0);case"permission_denied":return d(s.message||"You don't have permission for that action.",!0);case"rate_limited":return d("You're hitting rate limits. Wait 30 seconds and try again.",!0);case"quota_exceeded":return d(`${s.message} Upgrade at https://app.mistflow.ai/pricing to lift limits.`,!0);case"validation_error":case"conflict":return d(s.message,!0);case"not_found":{let t=_t(),n=t?.orgSlug?.replace(/^user_user_/,"")||void 0,a=t?.email||n;return a&&(s.message.toLowerCase().includes("project")||o.toLowerCase().includes("project"))?d(`Project not found. You are signed in as ${a}. This project may belong to a different account. Run mist_setup to sign in with the account that owns this project.`,!0):d(s.message,!0)}case"server_error":case"upstream_error":return d(`${o} failed: the Mistflow backend returned an error. Try again in 30 seconds.`,!0);case"network_error":return d(`${o} failed: cannot reach Mistflow servers. Check your network and try again.`,!0);default:return d(`${o} failed: ${s.message}`,!0)}let e=s instanceof Error?s.message:String(s);return d(`${o} failed: ${e}`,!0)}import{existsSync as qe,readFileSync as Tt,readdirSync as $o,statSync as jo}from"fs";import{join as Pe,relative as Vt}from"path";import{execFileSync as Hp}from"child_process";function ja(s){let o=Pe(s,".env.local"),e=[];return qe(o)?(Tt(o,"utf-8").match(/^AUTH_SECRET=(.*)$/m)?.[1]?.trim()||e.push({check:"env",message:"AUTH_SECRET is missing from .env.local. Add a random secret for authentication.",file:".env.local"}),e):(e.push({check:"env",message:"Missing .env.local file. Create one with your local development environment variables (AUTH_SECRET, etc.).",file:".env.local"}),e)}function La(s){let o=Pe(s,"app","api","auth","[...all]","route.ts");return qe(o)?[]:[{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 Oa(s){let o=Pe(s,"app","api","health","route.ts");return qe(o)?[]:[{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 Ua(s){let o=Pe(s,"mistflow.json");if(!qe(o))return[];let e;try{e=JSON.parse(Tt(o,"utf-8"))}catch{return[]}let t=e.env;if(!t?.required||typeof t.required!="object")return[];let n=[],a=Pe(s,".env"),r="";try{qe(a)&&(r=Tt(a,"utf-8"))}catch{}for(let[i,l]of Object.entries(t.required)){let c=!!process.env[i],u=r.includes(`${i}=`);if(c||u)continue;let m=l?.description?` for '${l.description}'`:"";n.push({check:"required-env",message:`${i} is required${m}. Run mist_config to set it if you haven't already.`})}return n}function Fa(s){let o=Pe(s,"app");if(!qe(o))return[];let e=[];function t(r){let i;try{i=$o(r)}catch{return}for(let l of i){if(l==="node_modules"||l===".next"||l===".open-next"||l==="api")continue;let c=Pe(r,l),u;try{u=jo(c)}catch{continue}if(u.isDirectory())t(c);else if(l==="page.tsx"||l==="page.ts"||l==="page.jsx"||l==="page.js"){let p="/"+Vt(o,c).replace(/\\/g,"/").replace(/\([^)]+\)\//g,"").replace(/\/page\.(tsx?|jsx?)$/,"").replace(/^page\.(tsx?|jsx?)$/,"");e.push({file:Vt(s,c),resolvedRoute:p||"/"})}}}t(o);let n=new Map;for(let r of e){let i=n.get(r.resolvedRoute)??[];i.push(r.file),n.set(r.resolvedRoute,i)}let a=[];for(let[r,i]of n)i.length>1&&a.push({check:"route-collision",message:`Route collision: ${i.join(" and ")} both resolve to "${r}". 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:i[1]});return a}function Ba(s){let o=Pe(s,"app");if(!qe(o))return[];let e=[];function t(n){let a;try{a=$o(n)}catch{return}for(let r of a){if(r==="node_modules"||r===".next"||r===".open-next")continue;let i=Pe(n,r),l;try{l=jo(i)}catch{continue}if(l.isDirectory())t(i);else if(r==="actions.ts"||r==="actions.tsx"){let c=Tt(i,"utf-8");c.includes("use server")&&c.includes("cookies()")&&c.includes(".set(")&&e.push(Vt(s,i))}}}return t(o),e.length>0?[{check:"cookies-in-actions",message:`${e.length} server action(s) use cookies().set() which crashes on Mistflow Cloud's edge runtime: ${e.join(", ")}. Use a database field or form parameter instead.`,file:e[0]}]:[]}function qa(s){let o=/\b(useState|useEffect|useRef|useCallback|useMemo|useReducer|useContext|useLayoutEffect|useTransition)\s*\(/,e=[];function t(n){if(!qe(n))return;let a;try{a=$o(n)}catch{return}for(let r of a){if(r==="node_modules"||r===".next"||r===".open-next"||r==="ui")continue;let i=Pe(n,r),l;try{l=jo(i)}catch{continue}if(l.isDirectory())t(i);else if(r.endsWith(".tsx")||r.endsWith(".jsx")){if(r==="layout.tsx"||r==="loading.tsx"||r==="error.tsx"||r==="not-found.tsx")continue;let c=Tt(i,"utf-8");if(c.trimStart().startsWith('"use client"')||c.trimStart().startsWith("'use client'")||c.trimStart().startsWith('"use server"')||c.trimStart().startsWith("'use server'"))continue;if(o.test(c)){let u=Vt(s,i);e.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(Pe(s,"app")),t(Pe(s,"components")),e}function Fr(s){let o=[...ja(s),...La(s),...Oa(s),...Fa(s),...Ba(s),...qa(s)],e=[...Ua(s)];return{passed:o.length===0,errors:o,warnings:e}}import{existsSync as Se,readFileSync as Xe,readdirSync as Yt,statSync as Ct}from"fs";import{join as Q}from"path";async function Br(s,o){let e=[];e.push(za(s)),e.push(Ha(s)),e.push(Ga(s)),e.push(Wa(s)),e.push(Va(s)),e.push(Qa(s)),o?.plan&&e.push(...Ya(s,o)),e.push(Ka(s)),e.push(Ja(s)),e.push(...Za(s)),e.push(Xa(s));let t=e.filter(c=>c.status==="fail"),n=e.filter(c=>c.status==="warn"),a=e.filter(c=>c.status==="pass"),r=t.length>0,i=!r,l;return i&&n.length===0?l=`All ${e.length} checks passed`:i?l=`${a.length}/${e.length} passed, ${n.length} warning(s)`:l=`${t.length} issue(s) found:
|
|
5032
|
+
${t.map(c=>` - ${c.message}`).join(`
|
|
5033
|
+
`)}`,{passed:i,blocking:r,checksRun:e.length,checks:e,summary:l}}function za(s){let o=Q(s,".open-next","worker.js");if(!Se(o))return{name:"worker-exists",status:"fail",message:"worker.js not found in build output. The build may have failed silently."};let e=Xe(o,"utf-8");return e.length<1e3?{name:"worker-exists",status:"warn",message:`worker.js is unusually small (${e.length} bytes). The build output may be incomplete.`}:{name:"worker-exists",status:"pass",message:"Worker bundle exists"}}function Ha(s){let o=Q(s,".open-next","assets");if(!Se(o))return{name:"assets-exist",status:"warn",message:"No static assets directory in build output. CSS and images may not load."};let e=0;function t(n){try{for(let a of Yt(n)){let r=Q(n,a);try{Ct(r).isDirectory()?t(r):e++}catch{}}}catch{}}return t(o),e===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:`${e} static assets ready`}}function Ga(s){let o=Q(s,"app","api","health","route.ts");return Se(o)?{name:"health-route",status:"pass",message:"Health endpoint found"}:{name:"health-route",status:"warn",message:"No health endpoint found. Deployment verification may fail."}}function Wa(s){let o=Q(s,"app","api","auth","[...all]","route.ts");return Se(o)?{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 Va(s){let o=Q(s,".open-next","worker.js");if(!Se(o))return{name:"worker-size",status:"pass",message:"Worker size check skipped"};let t=Ct(o).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 Ya(s,o){let e=[],t=new Set;if(o.plan?.pages)for(let r of o.plan.pages){let i=r.path??r.route??r.name;i&&t.add(i.replace(/^\//,""))}if(t.size===0)return[];let n=0,a=[];for(let r of t){if(r.startsWith("api/")||r==="login"||r==="register"||r==="sign-in"||r==="sign-up"){n++;continue}[Q(s,"app","(dashboard)",r,"page.tsx"),Q(s,"app","(dashboard)",r,"page.ts"),Q(s,"app",r,"page.tsx"),Q(s,"app",r,"page.ts"),Q(s,"app","(admin)",r,"page.tsx")].some(l=>Se(l))?n++:a.push(`/${r}`)}return a.length===0?e.push({name:"plan-routes",status:"pass",message:`All ${t.size} planned pages found`}):a.length<=2?e.push({name:"plan-routes",status:"warn",message:`${n}/${t.size} planned pages found. Missing: ${a.join(", ")}`}):e.push({name:"plan-routes",status:"warn",message:`Only ${n}/${t.size} planned pages found. ${a.length} pages missing.`}),e}function Ka(s){let o=Q(s,"app");if(!Se(o))return{name:"empty-pages",status:"pass",message:"App directory check skipped"};let e=[];function t(n){try{for(let a of Yt(n)){let r=Q(n,a);try{if(Ct(r).isDirectory())t(r);else if(a==="page.tsx"||a==="page.ts"){let l=Xe(r,"utf-8").trim();if(l.length<50||l.includes("export default function")&&l.includes("TODO")){let c=r.replace(s+"/","");e.push(c)}}}catch{}}}catch{}}return t(o),e.length>0?{name:"empty-pages",status:"warn",message:`${e.length} page(s) appear to be empty/placeholder: ${e.slice(0,3).join(", ")}`}:{name:"empty-pages",status:"pass",message:"No empty pages detected"}}function Ja(s){let o=Q(s,"app","page.tsx");if(!Se(o))return{name:"landing-page",status:"warn",message:"No root page.tsx found"};let e=Xe(o,"utf-8");if(e.includes('redirect("/login")')||e.includes('redirect("/register")')){let t=Q(s,"middleware.ts"),n=Se(t)?Xe(t,"utf-8"):"";if(!(n.includes('"/"')||n.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 e.length<200?{name:"landing-page",status:"warn",message:`Landing page is very small (${e.length} chars) \u2014 may be a placeholder.`}:{name:"landing-page",status:"pass",message:"Landing page has content"}}function Qa(s){let o=Q(s,"app");if(!Se(o))return{name:"cookies-in-actions",status:"pass",message:"No app directory"};let e=[];function t(n){try{for(let a of Yt(n)){if(a==="node_modules"||a===".next"||a===".open-next")continue;let r=Q(n,a);try{if(Ct(r).isDirectory())t(r);else if(a==="actions.ts"||a==="actions.tsx"){let l=Xe(r,"utf-8");l.includes("use server")&&l.includes("cookies()")&&l.includes(".set(")&&e.push(r.replace(s+"/",""))}}catch{}}}catch{}}return t(o),e.length>0?{name:"cookies-in-actions",status:"fail",message:`${e.length} server action(s) use cookies().set() which crashes on Mistflow Cloud's edge runtime: ${e.join(", ")}. Use a database field or form parameter instead.`}:{name:"cookies-in-actions",status:"pass",message:"No cookies().set() in server actions"}}function Xa(s){let o=Q(s,"app");if(!Se(o))return{name:"fake-forms",status:"pass",message:"No app directory"};let e=[];function t(n){try{for(let a of Yt(n)){if(a==="node_modules"||a===".next"||a===".open-next")continue;let r=Q(n,a);try{if(Ct(r).isDirectory())t(r);else if(a.endsWith(".tsx")||a.endsWith(".ts")){let l=Xe(r,"utf-8");if((l.includes("setTimeout")||l.includes("new Promise"))&&l.includes("Simulate")||l.includes("simulate")||l.includes("// TODO")&&l.includes("API")){let c=r.replace(s+"/","");e.push(c)}}}catch{}}}catch{}}return t(o),e.length>0?{name:"fake-forms",status:"fail",message:`${e.length} file(s) have fake/simulated API calls instead of real server actions: ${e.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 Za(s){let e=[Q(s,"components","sidebar.tsx"),Q(s,"components","topnav.tsx"),Q(s,"components","nav.tsx")].find(i=>Se(i));if(!e)return[];let a=(Xe(e,"utf-8").match(/href:\s*"([^"]+)"/g)??[]).map(i=>i.replace(/href:\s*"/,"").replace(/"$/,"")).filter(i=>!i.startsWith("/api")&&!i.includes("["));if(a.length===0)return[];let r=[];for(let i of a){let l=i.replace(/^\//,"");if(!l||l==="")continue;[Q(s,"app","(dashboard)",l,"page.tsx"),Q(s,"app","(dashboard)",l,"page.ts"),Q(s,"app","(app)",l,"page.tsx"),Q(s,"app",l,"page.tsx"),Q(s,"app",l,"page.ts")].some(u=>Se(u))||r.push(i)}return r.length>0?[{name:"nav-links",status:"fail",message:`Sidebar/nav links to pages that don't exist: ${r.join(", ")}. These will 404.`}]:[{name:"nav-links",status:"pass",message:`All ${a.length} nav links have matching pages`}]}function $e(s,o){return rl("git",s,{cwd:o,encoding:"utf-8",timeout:3e4,stdio:["pipe","pipe","pipe"]}).trim()}function Lo(s){try{return $e(["rev-parse","--is-inside-work-tree"],s),!0}catch{return!1}}function zr(s){try{return $e(["status","--porcelain"],s).length>0}catch{return!1}}function Hr(s,o){return $e(["add","-A"],s),$e(["commit","-m",o,"--allow-empty-message"],s),$e(["rev-parse","HEAD"],s)}function al(s){return $e(["rev-parse","HEAD"],s)}function Gr(s){try{return $e(["remote","get-url","origin"],s),!0}catch{return!1}}function ll(s){try{let o=$e(["rev-parse","--abbrev-ref","HEAD"],s);return $e(["push","origin",o],s),{success:!0}}catch(o){return{success:!1,error:o instanceof Error?o.message:"push failed"}}}function cl(s){let o=Jt(Yr(s)),e=Jt(o)===o?o:"/";for(;o!==e&&o!==Jt(o);){if(ue(oe(o,"pnpm-workspace.yaml"))||ue(oe(o,"lerna.json")))return o;let t=oe(o,"package.json");if(ue(t))try{if(JSON.parse(Kr(t,"utf-8")).workspaces)return o}catch{}o=Jt(o)}return null}function dl(s){let o=oe(il(),"mistflow-build");ol(o,{recursive:!0});let e=oe(o,`${el(s)}-${Date.now()}`);return Oo(s,e,{recursive:!0,filter:t=>{let n=t.slice(s.length);return!(n.startsWith("/.git")||n.startsWith("\\.git")||n==="/node_modules"||n.startsWith("/node_modules/")||n==="/.open-next"||n.startsWith("/.open-next/")||n==="/.next"||n.startsWith("/.next/"))}}),e}function pl(s,o){let e=oe(s,".open-next"),t=oe(s,".next");if(ue(e)){let n=oe(o,".open-next");ue(n)&&Uo(n,{recursive:!0}),Oo(e,n,{recursive:!0})}if(ue(t)){let n=oe(o,".next");ue(n)&&Uo(n,{recursive:!0}),Oo(t,n,{recursive:!0})}}function ul(s){try{Uo(s,{recursive:!0,force:!0})}catch{console.error(`[deploy] Failed to clean up isolated build dir: ${s}`)}}function pt(s,o,e,t,n,a){return new Promise(r=>{let i=nl(s,o,{cwd:e,stdio:["pipe","pipe","pipe"],timeout:t,...a?{env:a}:{}}),l="",c="";i.stdout?.on("data",u=>{let m=u.toString();if(c+=m,n)for(let p of m.split(`
|
|
5034
|
+
`).filter(Boolean))n(p)}),i.stderr?.on("data",u=>{let m=u.toString();if(l+=m,n)for(let p of m.split(`
|
|
5035
|
+
`).filter(Boolean))n(p)}),i.on("close",(u,m)=>{r({success:u===0,stdout:c,stderr:l,signal:m})}),i.on("error",u=>{r({success:!1,stdout:c,stderr:l+u.message})})})}var cu=Kt.object({projectPath:Kt.string().optional().describe("Path to the project directory (default: current working directory)"),message:Kt.string().optional().describe("Deploy message"),environment:Kt.enum(["production","preview"]).optional().default("production").describe("Target environment: 'production' (default) or 'preview' for a shareable URL")});function ml(s){return new Promise(o=>setTimeout(o,s))}function Wr(s){switch(s){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: ${s}`}}function Fo(s){let o=oe(s,"mistflow.json");if(ue(o))try{return JSON.parse(Kr(o,"utf-8"))}catch{}return{}}function hl(s,o){let e=oe(s,"mistflow.json"),t=Fo(s),n=t.deploy?.count??t.deployCount??0;t.deploy={url:o,count:n+1,lastDeployedAt:new Date().toISOString()};let a=t.plan?.steps;if(Array.isArray(a))for(let r of a)r.status==="in_progress"&&(r.status="completed");delete t.deployUrl,delete t.deployCount,tl(e,JSON.stringify(t,null,2)+`
|
|
5036
|
+
`)}async function Jr(s,o){let t=process.platform==="win32"?"npx.cmd":"npx",n=0,a=oe(s,"node_modules",".bin","opennextjs-cloudflare"),r=ue(a),i={...process.env,NODE_ENV:"production"},l=await pt(r?a:t,r?["build"]:["@opennextjs/cloudflare","build"],s,3e5,p=>{p.includes("Compiling")?o?.("Compiling your app..."):p.includes("Collecting page data")?o?.("Collecting page data..."):p.includes("Generating static pages")?o?.("Generating static pages..."):p.match(/^[○●◐λƒ]\s/)?n++:p.includes("Creating Cloudflare worker")?o?.("Packaging for Mistflow Cloud..."):p.includes("Build completed")&&o?.("Build completed!")},i);if(l.success)return{success:!0,buildStats:n>0?`${n} routes compiled`:"build complete"};let c=l.stderr+`
|
|
5037
|
+
`+l.stdout;if(l.signal==="SIGKILL"||c.includes("SIGKILL")||c.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=St(c);if(u.length>0){let p=u.map((g,w)=>{let f=`${w+1}. ${g.humanMessage}`;return g.suggestion&&(f+=`
|
|
5033
5038
|
Fix: ${g.suggestion}`),f}).join(`
|
|
5034
5039
|
|
|
5035
|
-
`);return{success:!1,error:`Build failed with ${
|
|
5040
|
+
`);return{success:!1,error:`Build failed with ${u.length} error${u.length===1?"":"s"}:
|
|
5036
5041
|
|
|
5037
|
-
${
|
|
5038
|
-
`).filter(
|
|
5042
|
+
${p}`}}let m=c.split(`
|
|
5043
|
+
`).filter(p=>p.includes("error")||p.includes("Error")).slice(0,10);return{success:!1,error:m.length>0?`OpenNext build failed:
|
|
5039
5044
|
${m.join(`
|
|
5040
5045
|
`)}`:`OpenNext build failed:
|
|
5041
|
-
${c.slice(-500)}`}}async function
|
|
5042
|
-
${a.stderr.slice(-300)}`};await
|
|
5043
|
-
`+m.stdout).trim(),g=
|
|
5046
|
+
${c.slice(-500)}`}}async function gl(s,o){let e=cl(s);if(e)return console.error(`[deploy] Project is inside monorepo at ${e} \u2014 building in isolated temp directory`),o?.("Detected parent project folder \u2014 building in isolated directory..."),Vr(s,o);let t=await Jr(s,o);return!t.success&&t.signal==="SIGKILL"?(console.error("[deploy] Build was OOM-killed \u2014 retrying in isolated temp directory"),o?.("Build ran out of memory \u2014 retrying with a fresh setup..."),Vr(s,o)):t}async function Vr(s,o){let e;try{o?.("Copying project to isolated build directory..."),e=dl(s),o?.("Installing dependencies...");let n=process.platform==="win32"?"npm.cmd":"npm",a=await pt(n,["install","--prefer-offline"],e,12e4);if(!a.success)return{success:!1,error:`Failed to install dependencies in isolated build:
|
|
5047
|
+
${a.stderr.slice(-300)}`};await pt(n,["dedupe"],e,6e4);let r=await Jr(e,o);return r.success&&(o?.("Copying build artifacts..."),pl(e,s)),{...r,builtInIsolation:e}}finally{e&&ul(e)}}async function fl(s,o,e){if(!ue(oe(s,"db","schema")))return{success:!0,skipped:!0,skipReason:"No database structure found"};let t=oe(s,"db","schema");try{if(sl(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(!ue(oe(s,"drizzle.config.ts")))return{success:!0,skipped:!0,skipReason:"No drizzle.config.ts found"};let n;try{n=await ns(o)}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:a,credentials:r}=n;if(a==="neon"){if(!r.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(!r.TURSO_URL||!r.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 i={PATH:process.env.PATH??"",HOME:process.env.HOME??"",NODE_PATH:process.env.NODE_PATH??"",TMPDIR:process.env.TMPDIR??"",...r},c=process.platform==="win32"?"npx.cmd":"npx",m=await pt(c,e?["drizzle-kit","push","--force"]:["drizzle-kit","push"],s,6e4,p=>{let g=p.replace(/(?:TURSO_AUTH_TOKEN|DATABASE_URL|AUTH_SECRET)=[^\s&]*/gi,w=>w.split("=")[0]+"=REDACTED").replace(/postgresql:\/\/[^@]*@/g,"postgresql://REDACTED@").replace(/libsql:\/\/[^\s]*/g,"libsql://REDACTED");console.error(`[drizzle-kit] ${g}`)},i);if(!m.success){let p=(m.stderr+`
|
|
5048
|
+
`+m.stdout).trim(),g=p.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 w=p.split(`
|
|
5044
5049
|
`).filter(h=>h.includes("error")||h.includes("Error")||h.includes("ERR")).slice(0,5);return{success:!1,error:`Database update failed:
|
|
5045
|
-
${
|
|
5046
|
-
`):
|
|
5050
|
+
${w.length>0?w.join(`
|
|
5051
|
+
`):p.slice(-500)}`}}return{success:!0}}async function Qr(s,o){let{projectPath:e,message:t,environment:n="production",forceSchema:a}=s,r=n,i=Yr(e??process.cwd());if(!ye())return d("No Mistflow credentials found. Run mist_setup to connect your account.",!0);let l=Fr(i);if(!l.passed){let x=l.errors.map(D=>{let M=`[${D.check}] ${D.message}`;return D.file&&(M+=` (${D.file}${D.line?`:${D.line}`:""})`),M}).join(`
|
|
5047
5052
|
`);return d(`Deploy blocked \u2014 fix these issues first:
|
|
5048
5053
|
|
|
5049
|
-
${
|
|
5054
|
+
${x}`,!0)}let c=l.warnings.map(x=>`[${x.check}] ${x.message}`),u;if(Lo(i))if(zr(i))try{let x=Fo(i).deploy?.count??0,D=t?`Deploy v${x+1}: ${t}`:`Deploy v${x+1}`;u=Hr(i,D)}catch{console.error("[deploy] Pre-deploy git commit failed, continuing")}else try{u=al(i)}catch{}let m=oe(i,"mistflow.json");if(!ue(m))return d("No mistflow.json found. Run mist_build (action: 'init') first to set up your project.",!0);let p;try{let{ensureBackendRegistered:x}=await import("./self-heal-BXJXUFUF.js");p=await x(i,{forceSync:!0})}catch(x){let D=x instanceof xe?x.message:"Check your internet connection.";return d(`Could not register project with Mistflow: ${D}
|
|
5050
5055
|
|
|
5051
|
-
Try deploying again in a moment.`,!0)}if(
|
|
5056
|
+
Try deploying again in a moment.`,!0)}if(p||(p=Re(i)?.projectId),!p)return d("Could not register project with Mistflow \u2014 you may not be signed in. Run mist_setup to authenticate, then redeploy.",!0);if(p&&r==="production")try{let x=await Qo(p),D=x,M=D.deploy_count??x.deployCount??0;(D.deploy_strategy??x.deploy_strategy)==="staging"&&M>0&&(r="preview",console.error("[deploy] Staging mode enabled \u2014 auto-redirecting to preview"))}catch{}let g=[],w=Date.now(),f=(x,D)=>{let M={phase:x,message:D};return g.push(M),M},h=(x,D)=>{x.durationMs=Date.now()-w,D&&(x.message=D)};if(o){let x=Je(o.server,o.progressToken,()=>g[g.length-1]?.message??"Deploying...");o.cleanup=()=>x.stop()}let v=!1;if(p&&r!=="preview"){let x=f("schema","Updating database structure..."),D=await fl(i,p,a);if(D.skipped)h(x,D.skipReason??"Database update skipped");else if(D.success)v=!0,h(x,"Database updated successfully");else return h(x,"Database update failed"),d(`Deploy blocked \u2014 database update failed:
|
|
5052
5057
|
|
|
5053
|
-
${
|
|
5058
|
+
${D.error}
|
|
5054
5059
|
|
|
5055
|
-
Try deploying again in a moment. If it keeps failing, check your database files in db/schema/.`,!0)}let
|
|
5060
|
+
Try deploying again in a moment. If it keeps failing, check your database files in db/schema/.`,!0)}let E=f("build","Compiling your app for Mistflow Cloud..."),R=Date.now(),C=await gl(i,x=>{E.message=x});if(!C.success)return d(`Deploy blocked \u2014 OpenNext build failed:
|
|
5056
5061
|
|
|
5057
|
-
${
|
|
5062
|
+
${C.error}`,!0);h(E,`Build complete \u2014 ${C.buildStats??"ready"} (${((Date.now()-R)/1e3).toFixed(0)}s)`);let P=f("qa","Running smoke tests..."),j=Re(i),S=await Br(i,j);if(S.passed)h(P,`Smoke test passed \u2014 ${S.checksRun} checks OK`);else if(h(P,`Smoke test: ${S.summary}`),S.blocking)return d(`Deploy blocked \u2014 smoke test failed:
|
|
5058
5063
|
|
|
5059
|
-
${
|
|
5064
|
+
${S.summary}
|
|
5060
5065
|
|
|
5061
|
-
Fix these issues and try again.`,!0);let
|
|
5062
|
-
`),
|
|
5066
|
+
Fix these issues and try again.`,!0);let z=oe(i,".open-next");if(!ue(z))return d("Build succeeded but .open-next/ directory not found. Check your OpenNext configuration.",!0);f("package","Packaging build artifacts...");let Y=oe(i,".open-next-build.tar.gz"),G=[".open-next"];if(ue(oe(i,"db"))&&G.push("db"),ue(oe(i,"drizzle.config.ts"))&&G.push("drizzle.config.ts"),ue(oe(i,"package.json"))&&G.push("package.json"),!(await pt("tar",["-czf",Y,"-C",i,...G],i,6e4)).success)return d("Failed to create build archive. Check disk space and permissions.",!0);let _;{let x=oe(i,".mistflow-source.tar.gz");(await pt("tar",["-czf",x,"-C",i,"--exclude",".open-next","--exclude","node_modules","--exclude",".git","--exclude",".next","--exclude",".open-next-build.tar.gz","--exclude",".mistflow-source.tar.gz","."],i,6e4)).success?_=x:console.error("[deploy] Source archive creation failed, continuing without it")}f("upload","Uploading to Mistflow...");let ee=_t()?.email,J,q;try{let x=await es(p,Y,r,ee,v,_,u);if(J=x.deployment_id??x.id,!J)return d("Upload succeeded but no deployment ID was returned. Check the Mistflow dashboard.",!0);q=x.status}catch(x){return Ur(x,"Deploy")}finally{try{qr(Y)}catch{}if(_)try{qr(_)}catch{}}f("deploying","Deploying to the edge...");let F=Date.now(),ie=24e4,le=3e3,K=[Wr(q)],me=q;for(;Date.now()-F<ie;){await ml(le);let x;try{x=await Oe(J)}catch{continue}if(x.status!==me&&(K.push(Wr(x.status)),me=x.status),x.status==="live"){let D=((Date.now()-F)/1e3).toFixed(0),M=x.url??"";if(!M)return d("Deployment marked as live but no URL was returned. Check the Mistflow dashboard.",!0);let y=r==="preview";if(!y){hl(i,M);try{let{readLocalState:I,syncRemoteState:H}=await import("./state-manager-GBP2NSYC.js"),b=I(i);b&&p&&(b.deployCount=(b.deployCount??0)+1,H(p,b).catch(()=>{}))}catch{}if(Lo(i)&&Gr(i))try{zr(i)&&Hr(i,"Update deploy metadata"),ll(i)}catch{}}let N=y?"Preview":"App",te=Fo(i),De=te.name,ae=typeof De=="string"?De:"my app",Ce=te.features,pe=Array.isArray(Ce)?Ce.length:0,Ae=te.plan,T=Ae&&typeof Ae=="object"&&"steps"in Ae?Ae.steps:void 0,A=Array.isArray(T)?T.length:0,V=g.map(I=>I.message),U={url:M,time:`${D}s`,deploymentId:J,environment:r,statusLog:K,deployTimeline:V,message:y?`${N} is live at ${M} \u2014 preview deployed in ${D}s. This URL expires in 72 hours.`:`Your app is live at ${M} \u2014 deployed in ${D}s.`};if(y)U.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='"+M+"' 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(`
|
|
5067
|
+
`),U.nextAction=`MANDATORY: Call mist_build with action='qa' and url='${M}' to verify the preview. After QA passes, ask user to confirm, then call mist_deploy action='promote'.`,U.qaRequired=!0,U.qaUrl=M,U.previewDeploymentId=J,U.promoteAction=`mist_deploy action='promote' deploymentId='${J}'`;else{let I=encodeURIComponent(`Just built "${ae}" with AI and deployed it live in ${D}s.
|
|
5063
5068
|
|
|
5064
|
-
${
|
|
5069
|
+
${A} steps, ${pe} features, zero config.
|
|
5065
5070
|
|
|
5066
|
-
${
|
|
5071
|
+
${M}
|
|
5067
5072
|
|
|
5068
|
-
Built with @mistflow`),
|
|
5073
|
+
Built with @mistflow`),H=encodeURIComponent(`I just described an app idea and got a live, working URL in ${D} seconds.
|
|
5069
5074
|
|
|
5070
|
-
"${
|
|
5075
|
+
"${ae}" \u2014 ${A} build steps, ${pe} features, fully deployed.
|
|
5071
5076
|
|
|
5072
|
-
${
|
|
5077
|
+
${M}
|
|
5073
5078
|
|
|
5074
|
-
Built with Mistflow (mistflow.ai)`);
|
|
5075
|
-
`)},
|
|
5076
|
-
`),
|
|
5077
|
-
`),m.nextAction=`MANDATORY: Call mist_build with action='qa' and url='${
|
|
5078
|
-
`)){let n=
|
|
5079
|
+
Built with Mistflow (mistflow.ai)`);U.share={tweetUrl:`https://twitter.com/intent/tweet?text=${I}`,linkedInUrl:`https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(M)}`,celebration:["","================================================",` ${ae} is LIVE!`,"================================================","",` ${M}`,"",` Deployed in ${D}s`,` ${pe>0?`${pe} features`:""}${pe>0&&A>0?" \xB7 ":""}${A>0?`${A} build steps`:""}`,""," Share it:",` Twitter \u2192 https://twitter.com/intent/tweet?text=${I}`,` LinkedIn \u2192 https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(M)}`,"","================================================"].join(`
|
|
5080
|
+
`)},U.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(`
|
|
5081
|
+
`),U.nextAction=`MANDATORY: Call mist_build with action='qa' and url='${M}' to verify the app works. Do NOT tell the user the app is ready until QA passes.`,U.qaRequired=!0,U.qaUrl=M}te.hasAdmin&&!y&&ee&&(U.adminSetup={message:`Your app is live. You're the admin \u2014 sign up at ${M}/signup with ${ee} to create your admin account. You'll pick your own password; the /admin panel unlocks as soon as you sign up.`}),c.length>0&&(U.warnings=c);let se=[];if(!y){let I=JSON.stringify(te).toLowerCase();se.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"}),Lo(i)&&!Gr(i)&&se.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 H=I.includes("user")||I.includes("account")||I.includes("member"),b=I.includes("stripe")||I.includes("payment")||I.includes("billing");H&&!b&&se.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"}),se.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"}),se.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"}),se.push({action:"See analytics",command:`Visit ${Yo()} to see pageviews and visitors`,reason:"Track how people are using your app.",priority:"low"})}return se.length>0&&(U.nextSteps=se),Ue(M,JSON.stringify(U))}if(x.status==="failed")return d(x.error??"Deployment failed. Check the Mistflow dashboard for details.",!0)}return d(`Deploy is taking longer than expected. Your deploy ID is ${J} \u2014 check status later.`,!0)}import{z as Qt}from"zod";import{resolve as yl,join as bl}from"path";import{execFileSync as wl}from"child_process";var yu=Qt.object({projectPath:Qt.string().optional().describe("Path to the project directory (default: cwd)"),action:Qt.enum(["redeploy","rollback"]).describe("Action to perform: 'redeploy' re-deploys the latest build, 'rollback' reverts to a specific previous deployment"),deploymentId:Qt.string().optional().describe("Deployment ID to rollback to (required for 'rollback')")});function ze(s,o){return wl("git",s,{cwd:o,encoding:"utf-8",timeout:3e4,stdio:["pipe","pipe","pipe"]}).trim()}function Xr(s){try{return ze(["rev-parse","--is-inside-work-tree"],s),!0}catch{return!1}}async function vl(s,o,e,t){if(o&&Xr(s))try{ze(["cat-file","-t",o],s);let n=!1;try{ze(["status","--porcelain"],s).length>0&&(ze(["stash","push","-m","mistflow-rollback-auto-stash"],s),n=!0)}catch{}return ze(["reset","--hard",o],s),ze(["commit","--allow-empty","-m","Rollback to previous deploy"],s),{method:"git",success:!0,...n?{warning:"Your uncommitted changes were saved to git stash. Run `git stash pop` to restore them."}:{}}}catch{}if(e&&t)try{let{existsSync:n,mkdirSync:a,rmSync:r}=await import("fs"),i=bl(s,".mistflow-rollback-source.tar.gz");await fs(t,i);let{spawn:l}=await import("child_process");await new Promise((c,u)=>{let m=l("tar",["-xzf",i,"-C",s,"--exclude","node_modules","--exclude",".git"],{cwd:s,stdio:"pipe"});m.on("close",p=>p===0?c():u(new Error(`tar exit ${p}`))),m.on("error",u)});try{r(i)}catch{}if(Xr(s))try{ze(["add","-A"],s),ze(["commit","-m","Rollback to previous deploy (from source snapshot)"],s)}catch{}return{method:"r2",success:!0}}catch(n){return{method:"r2",success:!1,error:n instanceof Error?n.message:"Source download failed"}}return{method:"none",success:!1,error:"No source snapshot available for this deployment"}}function kl(s){return new Promise(o=>setTimeout(o,s))}function xl(s){switch(s){case"pending":return"Provisioning...";case"building":return"Building...";case"deploying":return"Deploying to Mistflow Cloud...";case"verifying":return"Verifying deployment...";default:return`Status: ${s}`}}async function Zr(s,o,e){let t=Date.now(),n=12e4,a=3e3,r=[],i="";for(;Date.now()-t<n;){await kl(a);let l;try{l=await Oe(s)}catch{continue}if(l.status!==i&&(r.push(xl(l.status)),i=l.status),l.status==="live"){let c=((Date.now()-t)/1e3).toFixed(0),u=l.url??"";if(e)try{await e()}catch{}return d(JSON.stringify({url:u,time:`${c}s`,deploymentId:s,statusLog:r,message:u?`${o} complete \u2014 live at ${u} in ${c}s.`:`${o} complete in ${c}s.`}))}if(l.status==="failed")return d(l.error??`${o} failed. Check the Mistflow dashboard for details.`,!0)}return d(`${o} is taking longer than expected. Deployment ID: ${s} \u2014 check status later.`,!0)}async function Bo(s){let{projectPath:o,action:e,deploymentId:t}=s,n=yl(o??process.cwd());if(!ye())return d("No Mistflow credentials found. Run mist_setup to connect your account.",!0);let r=Re(n)?.projectId;if(!r)return d("No project ID found. Deploy your project first with mist_deploy, or initialize with mist_plan + mist_build.",!0);try{switch(e){case"redeploy":{let i=await ms(r);return Zr(i.deployment_id,"Redeploy")}case"rollback":{if(!t)return d("Deployment ID is required for rollback. Use mist_project (action: 'deployments') to see previous deployment IDs.",!0);let i=await gs(t);return Zr(i.deployment_id,"Rollback",async()=>{let l=await vl(n,i.git_commit_sha,i.source_artifact_key,t);l.success||console.error(`[rollback] Local source restore failed: ${l.error}`),l.warning&&console.error(`[rollback] ${l.warning}`)})}default:return d(`Unknown action: ${e}. Use redeploy or rollback.`,!0)}}catch(i){if(i instanceof xe)return d(i.message,!0);let l=i instanceof Error?i.message:"An unexpected error occurred";return d(l,!0)}}import{resolve as Sl}from"path";function Tl(s){return new Promise(o=>setTimeout(o,s))}async function en(s){let o=Sl(s.projectPath??process.cwd());if(!ye())return d("No Mistflow credentials found. Run mist_setup to connect your account.",!0);let t=Re(o)?.projectId;if(!t)return d("No project ID found. Deploy the project first.",!0);let n=s.deploymentId;if(!n)try{let c=(await yt(t)).find(u=>u.environment==="preview"&&u.status==="live");if(!c)return d("No live preview found to promote. Deploy with staging mode first, then call promote after QA passes.",!0);n=c.id}catch{return d("Could not list deployments. Check your connection.",!0)}console.error(`[promote] Promoting preview ${n} to production...`);let a;try{a=(await hs(t,n)).deployment_id}catch(l){let c=l instanceof Error?l.message:"Promote failed";return d(`Promote failed: ${c}`,!0)}let r=Date.now(),i=12e4;for(;Date.now()-r<i;){await Tl(3e3);try{let l=await Oe(a);if(l.status==="live"){let c=((Date.now()-r)/1e3).toFixed(0),u=l.url??"",m={url:u,time:`${c}s`,deploymentId:a,environment:"production",promoted:!0,message:`Promoted to production at ${u} in ${c}s. Same build that passed QA on preview.`};return m.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(`
|
|
5082
|
+
`),m.nextAction=`MANDATORY: Call mist_build with action='qa' and url='${u}' to verify production.`,m.qaRequired=!0,m.qaUrl=u,u?Ue(u,JSON.stringify(m)):d(JSON.stringify(m))}if(l.status==="failed")return d(`Promote failed: ${l.error??"Check the Mistflow dashboard for details."}`,!0)}catch{}}return d(`Promote is taking longer than expected. Deployment ID: ${a}`,!0)}import{z as tn}from"zod";import{spawn as Al,execFileSync as on}from"child_process";import{existsSync as Zt,readFileSync as sn,writeFileSync as Il,mkdirSync as Rl}from"fs";import{join as ut,resolve as El}from"path";import{homedir as rn}from"os";import{createConnection as Nl}from"net";import{existsSync as Cl,readFileSync as Pl}from"fs";function Xt(s){let o=new Set;if(!Cl(s))return o;let e=Pl(s,"utf-8");for(let t of e.split(`
|
|
5083
|
+
`)){let n=t.trim();if(!n||n.startsWith("#"))continue;let a=n.indexOf("=");if(a>0){let r=n.slice(0,a).trim(),i=n.slice(a+1).trim();i&&i!=='""'&&i!=="''"&&o.add(r)}}return o}var _l=tn.object({projectPath:tn.string().optional().describe("Path to the project directory (default: current working directory)")});function nn(){return ut(rn(),".mistflow","processes.json")}function an(){let s=nn();if(!Zt(s))return[];try{return JSON.parse(sn(s,"utf-8"))}catch{return[]}}function ln(s){let o=ut(rn(),".mistflow");Zt(o)||Rl(o,{recursive:!0}),Il(nn(),JSON.stringify(s,null,2))}function Dl(){let s=an(),o=[];for(let e of s)try{process.kill(e.pid,0),o.push(e)}catch{}ln(o)}function cn(s){return new Promise(o=>{let e=Nl({port:s,host:"127.0.0.1"});e.on("connect",()=>{e.destroy(),o(!0)}),e.on("error",()=>{o(!1)})})}async function Ml(s,o,e){let t=Date.now();for(;Date.now()-t<o;){if(await cn(s))return!0;await new Promise(n=>setTimeout(n,e))}return!1}function $l(s){return Zt(ut(s,"mistflow.json"))}function jl(s){let o=ut(s,"mistflow.json");if(!Zt(o))return[];let e;try{e=JSON.parse(sn(o,"utf-8"))}catch{return[]}let t=e.env?.required;if(!t||typeof t!="object")return[];let n=Object.keys(t);if(n.length===0)return[];let a=Xt(ut(s,".env.local"));return n.filter(r=>!a.has(r))}var dn={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:_l,handler:async s=>{let e=El(s.projectPath??process.cwd()),t=3e3;if(!$l(e))return Ee(e);let n=jl(e);if(n.length>0)return d(`Missing required environment variables in .env.local: ${n.join(", ")}. Add them to ${ut(e,".env.local")} before previewing.`,!0);Dl();let a=await cn(t),r=an(),i=r.some(m=>m.type==="dev-server"&&m.port===t);if(a&&!i)return d(`Port ${t} is in use. Stop the other process or I will use a different port.`,!0);let l=[...r];if(!a){try{on("npx",["drizzle-kit","push"],{cwd:e,stdio:["pipe","pipe","pipe"],timeout:3e4})}catch{console.error("[preview] drizzle-kit push failed, continuing...")}try{on("npm",["run","build"],{cwd:e,stdio:["pipe","pipe","pipe"],timeout:18e4})}catch(w){let f=w instanceof Error&&"stderr"in w?String(w.stderr).slice(-1500):"",h=w instanceof Error&&"stdout"in w?String(w.stdout).slice(-1500):"",v=(f+`
|
|
5079
5084
|
`+h).trim();return d(`Build failed. Fix the errors before previewing:
|
|
5080
5085
|
|
|
5081
|
-
${
|
|
5086
|
+
${v}`,!0)}let m=Al("npx",["next","start","-p",String(t)],{cwd:e,detached:!0,stdio:["ignore","pipe","pipe"]}),p="";if(m.stderr?.on("data",w=>{p+=w.toString()}),m.pid&&(m.unref(),l.push({pid:m.pid,type:"dev-server",port:t,startedAt:new Date().toISOString()}),ln(l)),!await Ml(t,15e3,500)){let w=p?`Server failed to start:
|
|
5082
5087
|
|
|
5083
|
-
${
|
|
5084
|
-
`),nextSteps:
|
|
5085
|
-
`),n.projectId&&import("./state-manager-GBP2NSYC.js").then(async({readLocalState:i,syncRemoteState:l})=>{let c=i(
|
|
5088
|
+
${p.slice(-1e3)}`:"Server failed to start within 15s. Check for runtime errors.";return d(w,!0)}}let c=`http://localhost:${t}`,u=JSON.stringify({localUrl:c,message:`Preview is live at ${c}. Run mist_deploy to get your permanent URL on mistflow.app.`});return Ue(c,u)}};import{resolve as Ll,join as Ol}from"path";import{existsSync as Ul,readFileSync as Fl}from"fs";var qo="test@mistflow.dev",pn="MistflowTest123!",Bl="Test User";function un(s){let o=Ol(s,"mistflow.json");if(!Ul(o))return null;try{return JSON.parse(Fl(o,"utf-8"))}catch{return null}}function ql(s){let o=JSON.stringify(s).toLowerCase();return["auth","login","sign-in","sign-up","signin","signup"].some(e=>o.includes(e))}function zl(s){let o=s.plan;if(!o?.steps)return[];let e=new Set;for(let n of o.steps)if(n.pages)for(let a of n.pages)e.add(a);let t=new Set(["login","signup","sign-in","sign-up","signin","signout","sign-out","logout"]);return[...e].filter(n=>!t.has(n.toLowerCase())).slice(0,3)}function Hl(s){let o=s.deploy;return o?.url?o.url:typeof s.deployUrl=="string"?s.deployUrl:null}async function mn(s){let o=Ll(s.projectPath??process.cwd()),e=s.verifyUrl,t=null;if(e)t=un(o);else{if(t=un(o),!t)return d("No mistflow.json found. Run mist_deploy first, or pass verifyUrl explicitly.",!0);if(e=Hl(t)??void 0,!e)return d("No deploy URL found in mistflow.json. Deploy your app first with mist_deploy.",!0)}e.startsWith("http")||(e=`https://${e}`);let n=t?ql(t):!1,a=t?zl(t):[],r;try{r=await Et()}catch{return d("Browser not available. Install Playwright to use verify: npx playwright install chromium",!0)}let i={verified:!0,url:e,authTested:!1,testUser:null,pages:[],message:""},l=[];try{await r.goto(e,{waitUntil:"domcontentloaded",timeout:3e4}),await r.waitForLoadState("networkidle").catch(()=>{})}catch{return i.verified=!1,i.message=`Could not load ${e}. The app may not be deployed yet or the URL may be incorrect.`,d(JSON.stringify(i),!0)}let c=await r.title(),u=c.toLowerCase().includes("error")||c.toLowerCase().includes("404")?"error":"ok";i.pages.push({path:"/",status:u,title:c});try{let f=await Le(r,!1);l.push({data:f.toString("base64"),mimeType:"image/png"})}catch{}if(u==="error"&&(i.verified=!1),n)try{(await r.evaluate(async h=>{let v=await fetch("/api/auth/sign-up/email",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(h)});return{status:v.status,ok:v.ok}},{email:qo,password:pn,name:Bl})).ok||await r.evaluate(async h=>{let v=await fetch("/api/auth/sign-in/email",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(h)});return{status:v.status,ok:v.ok}},{email:qo,password:pn}),await r.reload({waitUntil:"domcontentloaded",timeout:15e3}),await r.waitForLoadState("networkidle").catch(()=>{}),i.authTested=!0,i.testUser={email:qo};try{let h=await Le(r,!1);l.push({data:h.toString("base64"),mimeType:"image/png"})}catch{}}catch{console.error("[verify] Auth test failed, continuing with public pages")}for(let f of a){let h=f.startsWith("/")?f:`/${f}`;try{await r.goto(`${e}${h}`,{waitUntil:"domcontentloaded",timeout:15e3}),await r.waitForLoadState("networkidle").catch(()=>{});let v=await r.title(),E=v.toLowerCase().includes("404")||v.toLowerCase().includes("not found")?"error":"ok";i.pages.push({path:h,status:E,title:v}),E==="error"&&(i.verified=!1);try{let R=await Le(r,!1);l.push({data:R.toString("base64"),mimeType:"image/png"})}catch{}}catch{i.pages.push({path:h,status:"error",title:"Timeout"}),i.verified=!1}}let m=i.pages.filter(f=>f.status==="ok").length,p=i.pages.length,g=i.authTested?" Auth login successful.":"";i.message=i.verified?`Verified ${m}/${p} pages.${g} App looks good.`:`Verified ${m}/${p} pages \u2014 some issues found.${g} Check screenshots.`;let w=[{type:"text",text:JSON.stringify(i)}];for(let f of l)w.push({type:"image",data:f.data,mimeType:f.mimeType});return{content:w}}var Gl=Ze.object({action:Ze.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:Ze.string().optional().describe("Path to the project directory (default: cwd)"),environment:Ze.enum(["production","preview"]).optional().describe("(deploy) Target environment. Defaults to 'production'. Projects with staging mode auto-redirect to preview."),forceSchema:Ze.boolean().optional().describe("(deploy) Reset the database structure. Warning: this deletes existing data. Only use when the user confirms this is OK."),deploymentId:Ze.string().optional().describe("(rollback/promote) Deployment ID to rollback to, or preview deployment ID to promote"),verifyUrl:Ze.string().optional().describe("(verify) URL to verify. Defaults to deploy URL from mistflow.json")}),hn={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:Gl,handler:async(s,o)=>{let e=s;switch(e.action){case"deploy":return Qr({projectPath:e.projectPath,environment:e.environment??"production",forceSchema:e.forceSchema},o);case"promote":return en({projectPath:e.projectPath,deploymentId:e.deploymentId});case"preview":return dn.handler({projectPath:e.projectPath});case"redeploy":return Bo({projectPath:e.projectPath,action:"redeploy"});case"rollback":return Bo({projectPath:e.projectPath,action:"rollback",deploymentId:e.deploymentId});case"verify":return mn({projectPath:e.projectPath,verifyUrl:e.verifyUrl});default:return d(`Unknown action: ${e.action}. Use deploy, promote, preview, redeploy, rollback, or verify.`,!0)}}};import{z as ve}from"zod";import{resolve as eo}from"path";import{existsSync as to,readFileSync as oo}from"fs";import{join as so}from"path";import{z as He}from"zod";import{resolve as Wl,join as gn}from"path";import{existsSync as Vl,readFileSync as fn,writeFileSync as Yl}from"fs";var Kl=He.object({action:He.enum(["get","update"]).default("get").describe("'get' reads current project state. 'update' modifies it."),projectPath:He.string().optional().describe("Path to the project directory (default: current working directory)"),completedStep:He.number().optional().describe("(update only) Mark a plan step as completed by step number"),addEnvVar:He.object({key:He.string(),description:He.string().optional(),setupUrl:He.string().optional()}).optional().describe("(update only) Add a required env var to the project manifest")}),yn={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 s=>{let o=s,e=Wl(o.projectPath??process.cwd()),t=gn(e,"mistflow.json");if(!Vl(t))return Ee(e);let n;try{n=JSON.parse(fn(t,"utf-8"))}catch{return d("Failed to parse mistflow.json.",!0)}if(o.action==="get"){if(!n.projectId)try{let{ensureBackendRegistered:h}=await import("./self-heal-BXJXUFUF.js");await h(e)&&(n=JSON.parse(fn(t,"utf-8")))}catch{}let i=n.plan,l=i?.steps?.filter(h=>h.status==="completed").length??0,c=i?.steps?.length??0,u=Xt(gn(e,".env.local")),m=n.env?.required?Object.entries(n.env.required).map(([h,v])=>({name:h,description:v?.description,configured:u.has(h)})):[];n.projectId&&import("./state-manager-GBP2NSYC.js").then(({fetchRemoteState:h})=>h(n.projectId)).catch(()=>{});let p=[`Project: ${n.name}`];if(i){p.push(`Plan: ${i.summary??i.name??"unnamed"} \u2014 ${l}/${c} steps complete`);for(let h of i.steps){let v=h.status==="completed"?"\u2713":h.status==="in_progress"?"\u2192":" ";p.push(` [${v}] ${h.number}. ${h.name}`)}}let g=m.filter(h=>!h.configured);g.length>0&&p.push(`Missing env vars: ${g.map(h=>h.name).join(", ")}`),n.deploy?.url?p.push(`Deployed: ${n.deploy.url} (${n.deploy.count??0} deploys)`):p.push("Not deployed yet");let w=[],f=i?.steps?.find(h=>h.status!=="completed");return f?w.push(`NEXT: Call mist_build with action='implement' to work on step ${f.number} (${f.name}).`):i&&l===c&&(n.deploy?.url||w.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&&w.push(`Missing env vars in .env.local: ${g.map(h=>h.name).join(", ")}`),d(JSON.stringify({name:n.name,projectId:n.projectId,planProgress:i?{name:i.name,summary:i.summary,totalSteps:c,completedSteps:l,steps:i.steps}:null,envStatus:m,deploy:n.deploy??null,contextMessage:p.join(`
|
|
5089
|
+
`),nextSteps:w}))}let a=[];if(o.completedStep!==void 0){let i=n.plan;if(i?.steps){let l=i.steps.findIndex(c=>c.number===o.completedStep);if(l===-1)return d(`Step ${o.completedStep} not found in the plan.`,!0);i.steps[l].status="completed",a.push(`Step ${o.completedStep} marked as completed`)}}o.addEnvVar&&(n.env||(n.env={required:{}}),n.env.required||(n.env.required={}),n.env.required[o.addEnvVar.key]={description:o.addEnvVar.description,setupUrl:o.addEnvVar.setupUrl},a.push(`Added required env var: ${o.addEnvVar.key}`)),Yl(t,JSON.stringify(n,null,2)+`
|
|
5090
|
+
`),n.projectId&&import("./state-manager-GBP2NSYC.js").then(async({readLocalState:i,syncRemoteState:l})=>{let c=i(e);c&&await l(n.projectId,c)}).catch(()=>{});let r=[];if(o.completedStep!==void 0){let l=n.plan?.steps?.find(c=>c.status!=="completed");l?r.push(`NEXT: Call mist_build with action='implement' to work on step ${l.number} (${l.name}). Do this now.`):r.push("NEXT: All steps complete! Call mist_deploy with action='deploy' to deploy the app now. Do NOT suggest localhost.")}return o.addEnvVar&&(r.push(`Add ${o.addEnvVar.key} to your .env.local file`),o.addEnvVar.setupUrl&&r.push(`Get the value from: ${o.addEnvVar.setupUrl}`)),d(JSON.stringify({updated:!0,changes:a,message:a.length>0?`Project state saved. ${a.join(". ")}.`:"No changes made.",nextSteps:r.length>0?r:void 0}))}};var Jl=ve.object({action:ve.enum(["get","update","share","landing-designs","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. '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:ve.string().optional().describe("Path to the project directory (default: cwd)"),completedStep:ve.number().optional().describe("(update) Mark a plan step as completed by step number"),addEnvVar:ve.object({key:ve.string(),description:ve.string().optional(),setupUrl:ve.string().optional()}).optional().describe("(update) Add a required env var to the project manifest"),templateDescription:ve.string().optional().describe("(share) Short description of what this template builds"),category:ve.string().optional().describe("(landing-designs) Filter by category"),presetId:ve.string().optional().describe("(landing-designs) Get full details for a specific landing design by ID"),integrationId:ve.string().optional().describe("(integrations) Get full details for a specific integration preset by ID (e.g. 'stripe-payments', 'resend-email', 'elevenlabs-voice')"),period:ve.string().optional().describe("(errors) Time period for errors: '1h', '24h', '7d' (default: '7d')"),deploymentId:ve.string().optional().describe("(logs) Deployment ID to fetch logs for. If omitted, fetches logs for the latest deployment.")}),bn={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. '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:Jl,handler:async s=>{let o=s;if(["share","errors","logs","deployments"].includes(o.action)&&!ye())return d("You need to sign in first. Run mist_setup to connect your account.",!0);switch(o.action){case"get":case"update":return yn.handler({action:o.action,projectPath:o.projectPath,completedStep:o.completedStep,addEnvVar:o.addEnvVar});case"share":{let t=eo(o.projectPath??process.cwd()),n=so(t,"mistflow.json");if(!to(n))return Ee(t);let a;try{a=JSON.parse(oo(n,"utf-8"))}catch{return d("Could not read mistflow.json.",!0)}let r=a.projectId;if(!r)return d("No project ID found. Deploy the project first to register it.",!0);try{let i=await xs(r,{isTemplate:!0,description:o.templateDescription});return d(JSON.stringify({shareUrl:i.share_url,shareToken:i.share_token,message:`Your project is now a shareable template!
|
|
5086
5091
|
|
|
5087
5092
|
Anyone can fork it: ${i.share_url}
|
|
5088
5093
|
|
|
5089
5094
|
Others can use it in their AI editor:
|
|
5090
|
-
"build me something like ${i.share_url}"`}))}catch(i){let l=i instanceof Error?i.message:"Failed to share project";return d(l,!0)}}case"landing-designs":{if(o.presetId){let r=ot(o.presetId);if(!r)return d(`Preset '${o.presetId}' not found. Use mist_project action='presets' without presetId to list all available presets.`,!0);let i=
|
|
5091
|
-
`)}async function un(s){let{projectPath:o,action:t,domain:e,domainId:n}=s,a=ql(o??process.cwd());if(!be())return d("No Mistflow credentials found. Run mist_setup to connect your account.",!0);let i=Re(a)?.projectId;if(!i)return d("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(!e)return d("Domain name is required. Provide the domain like 'myapp.com' or 'app.mycompany.com'.",!0);let l=await Qo(i,e),c=await Je(i);return Lo(a,c.map(p=>({domain:p.domain,status:p.status}))),d(JSON.stringify({added:!0,domain:l.domain,status:l.status,instructions:l.instructions,message:`Domain '${l.domain}' added. Set up DNS records as described, then use action 'verify' to check status.`}))}case"list":{let l=await Je(i);return l.length===0?d(JSON.stringify({domains:[],message:"No custom domains configured. Use action 'add' to add one."})):d(JSON.stringify({domains:l.map(c=>({id:c.id,domain:c.domain,status:c.status,ssl:c.ssl_status,error:c.error_message}))}))}case"verify":{if(!n){if(e){let m=(await Je(i)).find(u=>u.domain===e);if(m){let u=await lo(i,m.id);return d(JSON.stringify({domain:u.domain,status:u.status,ssl:u.ssl_status,error:u.error_message,message:u.status==="active"?`Domain '${u.domain}' is active and serving traffic.`:`Domain '${u.domain}' is ${u.status}. Make sure DNS records are configured correctly.`}))}return d(`Domain '${e}' not found. Use action 'list' to see configured domains.`,!0)}return d("Provide either domainId or domain name to verify.",!0)}let l=await lo(i,n),c=await Je(i);return Lo(a,c.map(p=>({domain:p.domain,status:p.status}))),d(JSON.stringify({domain:l.domain,status:l.status,ssl:l.ssl_status,error:l.error_message,message:l.status==="active"?`Domain '${l.domain}' is active and serving traffic.`:`Domain '${l.domain}' is ${l.status}. Make sure DNS records are configured correctly.`}))}case"remove":{if(!n&&!e)return d("Provide either domainId or domain name to remove.",!0);let l=n;if(!l&&e){let m=(await Je(i)).find(u=>u.domain===e);if(!m)return d(`Domain '${e}' not found.`,!0);l=m.id}await Xo(i,l);let c=await Je(i);return Lo(a,c.map(p=>({domain:p.domain,status:p.status}))),d(JSON.stringify({removed:!0,message:"Domain removed. It may take a few minutes for DNS changes to propagate."}))}default:return d(`Unknown action: ${t}. Use add, list, verify, or remove.`,!0)}}catch(l){if(l instanceof Se)return d(l.message,!0);let c=l instanceof Error?l.message:"An unexpected error occurred";return d(c,!0)}}var Vl=Ne.object({resource:Ne.enum(["env","domain"]).describe("'env' manages app secrets and configuration values. 'domain' manages custom domains."),action:Ne.string().describe("Action to perform. env: 'set', 'list', 'delete'. domain: 'add', 'list', 'verify', 'remove'."),projectPath:Ne.string().optional().describe("Path to the project directory (default: cwd)"),key:Ne.string().optional().describe("(env) Variable name"),value:Ne.string().optional().describe("(env set) Variable value"),category:Ne.string().optional().describe("(env set) Category"),description:Ne.string().optional().describe("(env set) Description"),setupUrl:Ne.string().optional().describe("(env set) URL to obtain the value"),domain:Ne.string().optional().describe("(domain) Domain name"),domainId:Ne.string().optional().describe("(domain) Domain ID")}),pn={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:Vl,handler:async s=>{let o=s;switch(o.resource){case"env":return dn({projectPath:o.projectPath,action:o.action,key:o.key,value:o.value,category:o.category,description:o.description,setupUrl:o.setupUrl});case"domain":return un({projectPath:o.projectPath,action:o.action,domain:o.domain,domainId:o.domainId});default:return d(`Unknown resource: ${o.resource}. Use env or domain.`,!0)}}};import{z as et}from"zod";var Yl=et.object({action:et.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:et.string().optional().describe("URL to navigate to. Required for 'navigate'; optional for 'screenshot' (navigates before capturing)."),selector:et.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:et.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:et.boolean().default(!1).describe("For 'screenshot': capture the full scrollable page instead of just the viewport."),includeScreenshot:et.boolean().default(!1).describe("For navigate/interact actions: also return a screenshot alongside the accessibility snapshot.")}),mn={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:Yl,handler:async s=>{let o=s,t=await Et();if(o.action==="navigate"){if(!o.url)return d("URL is required for 'navigate'.",!0);let a=[],r=c=>{c.type()==="error"&&a.push(c.text())};t.on("console",r),await t.goto(o.url,{waitUntil:"domcontentloaded",timeout:3e4}),await t.waitForLoadState("networkidle").catch(()=>{});let i=[],l=c=>i.push(c.message);if(t.on("pageerror",l),await t.waitForTimeout(500),t.removeListener("console",r),t.removeListener("pageerror",l),a.length>0||i.length>0){let c=await Nt(t),p=[{type:"text",text:JSON.stringify({url:t.url(),title:await t.title(),snapshot:c,consoleErrors:a,pageErrors:i,hasErrors:!0})}];if(o.includeScreenshot){let m=await Le(t);p.push({type:"image",data:m.toString("base64"),mimeType:"image/png"})}return{content:p}}}else if(o.action==="go_back")await t.goBack({waitUntil:"domcontentloaded",timeout:1e4});else if(o.action==="go_forward")await t.goForward({waitUntil:"domcontentloaded",timeout:1e4});else if(o.action==="click"){if(!o.selector)return d("Selector is required for 'click'.",!0);await t.click(o.selector,{timeout:1e4}),await t.waitForLoadState("domcontentloaded").catch(()=>{}),await t.waitForTimeout(500)}else if(o.action==="type"){if(!o.selector)return d("Selector is required for 'type'.",!0);if(!o.value)return d("Value is required for 'type'.",!0);await t.type(o.selector,o.value,{delay:50})}else if(o.action==="fill"){if(!o.selector)return d("Selector is required for 'fill'.",!0);if(!o.value)return d("Value is required for 'fill'.",!0);await t.fill(o.selector,o.value)}else if(o.action==="select_option"){if(!o.selector)return d("Selector is required for 'select_option'.",!0);if(!o.value)return d("Value is required for 'select_option'.",!0);await t.selectOption(o.selector,o.value)}else if(o.action==="hover"){if(!o.selector)return d("Selector is required for 'hover'.",!0);await t.hover(o.selector,{timeout:1e4})}else if(o.action==="press_key"){if(!o.value)return d("Value is required for 'press_key' (e.g. 'Enter').",!0);await t.keyboard.press(o.value),await t.waitForLoadState("domcontentloaded").catch(()=>{}),await t.waitForTimeout(500)}else if(o.action==="screenshot"){o.url&&(await t.goto(o.url,{waitUntil:"domcontentloaded",timeout:3e4}),await t.waitForLoadState("networkidle").catch(()=>{}));let a;if(o.selector){let r=await t.$(o.selector);if(!r)return d(`Element not found: ${o.selector}`,!0);a=await r.screenshot({type:"png"})}else a=await Le(t,o.fullPage);return{content:[{type:"text",text:JSON.stringify({url:t.url(),title:await t.title(),message:`Screenshot captured (${o.fullPage?"full page":"viewport"})`})},{type:"image",data:a.toString("base64"),mimeType:"image/png"}]}}else if(o.action==="snapshot"){let a=await Nt(t);return{content:[{type:"text",text:JSON.stringify({url:t.url(),title:await t.title(),snapshot:a})}]}}let e=await Nt(t),n=[{type:"text",text:JSON.stringify({url:t.url(),title:await t.title(),snapshot:e})}];if(o.includeScreenshot){let a=await Le(t);n.push({type:"image",data:a.toString("base64"),mimeType:"image/png"})}return{content:n}}};import{existsSync as $e,readFileSync as It,writeFileSync as mt}from"fs";import{join as _e}from"path";import{z as At}from"zod";function hn(s){let o=_e(s,"mistflow.json");if(!$e(o))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(It(o,"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 e=t.projectId,n=e?`, id: ${e}`:", no projectId";return{result:{name:"Project",status:"pass",message:`${t.name}${n}`},config:t}}async function Jl(){let s=Bo();if(!s.ok)return{result:{name:"Auth",status:"fail",message:s.reason==="missing"?"No credentials found.":"Credentials file is malformed.",fix:"Run mist_setup to log in."},creds:null,authValid:!1};try{let o=zo(),t=await fetch(`${Ye()}/api/org`,{headers:o,signal:AbortSignal.timeout(1e4)});try{Uo(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:s.creds,authValid:!1}:{result:{name:"Auth",status:"warn",message:`Server returned ${t.status}. Credentials may still be valid.`},creds:s.creds,authValid:!1};let e=await t.json(),n=e.slug??s.creds.orgSlug??"unknown",a=e.plan?`, plan: ${e.plan}`:"";return{result:{name:"Auth",status:"pass",message:`org: ${n}${a}`},creds:s.creds,authValid:!0}}catch(o){return o instanceof Se?{result:{name:"Auth",status:"fail",message:`Auth check failed: ${o.message}`,fix:"Run mist_setup to re-authenticate."},creds:s.creds,authValid:!1}:{result:{name:"Auth",status:"warn",message:"Could not reach API to validate credentials. Network issue?"},creds:s.creds,authValid:!1}}}async function Kl(){let s=Date.now();try{let o=await Dt("nextjs"),t=Date.now()-s;return{result:{name:"API",status:"pass",message:`${Ye()} reachable (${t}ms)`},scaffold:o}}catch(o){let t=Date.now()-s;return{result:{name:"API",status:"fail",message:o instanceof Se?o.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 Ql(){let s=gt();if(!s.backendSignalReceived)return{name:"MCP version",status:"warn",message:`v${s.current} installed, but no backend signal received to compare against.`};let o={none:"up to date",patch:"patch update available",minor:"minor update available",major:"major update available",unsupported:"no longer supported"};return s.severity==="none"?{name:"MCP version",status:"pass",message:`v${s.current} (latest)`}:s.severity==="unsupported"?{name:"MCP version",status:"fail",message:`v${s.current} is ${o[s.severity]}. Minimum: v${s.minSupported}.`,fix:`Run \`${s.upgradeCmd}\` then restart your editor.`}:{name:"MCP version",status:"warn",message:`v${s.current} installed, v${s.latest} available (${o[s.severity]}).`,fix:`Run \`${s.upgradeCmd}\` then restart your editor.`}}function Xl(s,o,t,e){let n=_e(s,"AGENTS.md"),a=_e(s,"CLAUDE.md"),r=$e(n),i=o.methodologyVersion??"",l=t?.version??"";if(!r){if(!e||!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 c=gn(t.methodology,o);return mt(n,c),mt(a,c),{name:"AGENTS.md",status:"fix",message:`Restored from methodology v${l}.`}}if(e&&!$e(a)){let c=It(n,"utf-8");mt(a,c)}if(l&&i&&l!==i){if(!e||!t?.methodology)return{name:"AGENTS.md",status:"warn",message:`Methodology v${i} installed, v${l} available.`,fix:"Run mist_doctor to update it."};let c=gn(t.methodology,o);mt(n,c),mt(a,c);try{let p=_e(s,"mistflow.json"),m=JSON.parse(It(p,"utf-8"));m.methodologyVersion=l,mt(p,JSON.stringify(m,null,2)+`
|
|
5092
|
-
`)}catch{}return{name:"AGENTS.md",status:"fix",message:`Updated methodology v${i} -> v${l}.`}}return{name:"AGENTS.md",status:"pass",message:i?`v${i}`:"present"}}async function
|
|
5093
|
-
`+It(r,"utf-8"))}catch{}}for(let[a,r]of Object.entries(
|
|
5095
|
+
"build me something like ${i.share_url}"`}))}catch(i){let l=i instanceof Error?i.message:"Failed to share project";return d(l,!0)}}case"landing-designs":{if(o.presetId){let r=ot(o.presetId);if(!r)return d(`Preset '${o.presetId}' not found. Use mist_project action='presets' without presetId to list all available presets.`,!0);let i=Is(o.presetId);return d(JSON.stringify({preset:{id:r.id,title:r.title,category:r.category,description:i?.description??"",style:i?.style??"",theme:i?.theme??"dark",colors:i?.colors??[],tags:i?.tags??[],promptLength:r.prompt.length},message:`Landing design "${r.title}" (${r.category}) \u2014 ${i?.description??""}. To use it, pass landingDesign="${r.id}" when calling mist_plan.`}))}let t=Es(o.category??void 0),n=ho(o.category),a=Rs(n);return d(JSON.stringify({count:t.length,presets:t.map(r=>({id:r.id,title:r.title,category:r.category,description:r.description,style:r.style,theme:r.theme,colors:r.colors})),formatted:a,message:`${t.length} landing designs available.${o.category?` Filtered by: ${o.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 ${ft()}/designs?tab=landing-designs.`}))}case"integrations":{if(o.integrationId){let r=rt(o.integrationId);if(!r)return d(`Integration '${o.integrationId}' not found. Use mist_project action='integrations' without integrationId to list all available integrations.`,!0);let i=nt(r.id);return d(JSON.stringify({integration:{id:r.id,name:r.name,category:r.category,description:i?.description??"",packages:i?.packages??[],envVars:i?.envVars??[],docsUrl:i?.docsUrl??"",difficulty:i?.difficulty??"medium"},message:`Integration "${r.name}" (${r.category}) \u2014 ${i?.description??""}. This blueprint is auto-injected during implementation when your plan has a matching integration step. Required env vars: ${i?.envVars?.map(l=>l.key).join(", ")||"none"}. Docs: ${i?.docsUrl??"n/a"}.`}))}let t=Ds(o.category??void 0),n=fo(o.category??void 0),a=_s(n);return d(JSON.stringify({count:t.length,integrations:t.map(r=>({id:r.id,name:r.name,category:r.category,description:r.description,packages:r.packages,difficulty:r.difficulty,envVars:r.envVars.map(i=>i.key)})),formatted:a,message:`${t.length} integration blueprints available.${o.category?` Filtered by: ${o.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=eo(o.projectPath??process.cwd()),n=so(t,"mistflow.json");if(!to(n))return Ee(t);let a;try{a=JSON.parse(oo(n,"utf-8"))}catch{return d("Could not read mistflow.json.",!0)}let r=a.projectId;if(!r)return d("No project ID found. Deploy the project first.",!0);try{let i=await us(r,o.period??"7d");return i.total===0?d(JSON.stringify({total:0,period:i.period,message:`No runtime errors in the last ${i.period}. The app is running clean.`})):d(JSON.stringify({total:i.total,period:i.period,errors:i.errors,message:`${i.total} runtime error(s) in the last ${i.period}. Review the errors above and use mist_build debug to investigate.`}))}catch(i){let l=i instanceof Error?i.message:"Failed to fetch errors";return d(l,!0)}}case"logs":{let t=eo(o.projectPath??process.cwd()),n=so(t,"mistflow.json");if(!to(n))return Ee(t);let a;try{a=JSON.parse(oo(n,"utf-8"))}catch{return d("Could not read mistflow.json.",!0)}let r=a.projectId;if(!r)return d("No project ID found. Deploy the project first.",!0);let i=o.deploymentId;if(!i)try{let l=await yt(r);if(l.length===0)return d("No deployments found for this project.",!0);i=l[0].id}catch(l){let c=l instanceof Error?l.message:"Failed to fetch deployments";return d(c,!0)}try{let[l,c]=await Promise.all([ps(i),Oe(i)]),u=l.filter(p=>p.level==="error"),m=l.filter(p=>p.level==="warn");return d(JSON.stringify({deploymentId:i,status:c.status,errorMessage:c.error??null,totalLogs:l.length,errorCount:u.length,warnCount:m.length,logs:l.map(p=>({time:p.timestamp,level:p.level,phase:p.phase,message:p.message})),message:c.status==="failed"?`Deployment failed. ${u.length} error(s) found in logs. Review the logs above to diagnose the issue.`:`Deployment status: ${c.status}. ${l.length} log entries (${u.length} errors, ${m.length} warnings).`}))}catch(l){let c=l instanceof Error?l.message:"Failed to fetch deploy logs";return d(c,!0)}}case"deployments":{let t=eo(o.projectPath??process.cwd()),n=so(t,"mistflow.json");if(!to(n))return Ee(t);let a;try{a=JSON.parse(oo(n,"utf-8"))}catch{return d("Could not read mistflow.json.",!0)}let r=a.projectId;if(!r)return d("No project ID found. Deploy the project first.",!0);try{let i=await yt(r);return d(JSON.stringify({total:i.length,deployments:i.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:`${i.length} deployment(s) found. Use mist_project action='logs' deploymentId='<id>' to see detailed logs for any deployment.`}))}catch(i){let l=i instanceof Error?i.message:"Failed to fetch deployments";return d(l,!0)}}case"version":{gt().backendSignalReceived||await Jo();let t=gt(),n=t.severity==="none",a={none:"up to date",patch:"patch update available",minor:"minor update available",major:"major update available",unsupported:"UNSUPPORTED \u2014 upgrade required"};return d(JSON.stringify({current:t.current,latest:t.latest||"unknown",minSupported:t.minSupported||"unknown",severity:t.severity,upToDate:n,upgradeCmd:t.upgradeCmd,changelogUrl:t.changelogUrl,backendSignalReceived:t.backendSignalReceived,message:t.backendSignalReceived?`Mistflow MCP ${t.current} (${a[t.severity]??t.severity}). Latest: ${t.latest}.${n?"":` 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 d(`Unknown action: ${o.action}. Use get, update, share, landing-designs, integrations, errors, logs, deployments, or version.`,!0)}}};import{z as Ne}from"zod";import{z as Ge}from"zod";import{resolve as Ql}from"path";var ym=Ge.object({projectPath:Ge.string().optional().describe("Path to the project directory (default: cwd)"),action:Ge.enum(["set","list","delete"]).describe("Action to perform"),key:Ge.string().optional().describe("Environment variable name (required for 'set' and 'delete')"),value:Ge.string().optional().describe("Environment variable value (required for 'set')"),category:Ge.string().optional().describe("Category for the env var (default: 'custom')"),description:Ge.string().optional().describe("Description of what this env var is for"),setupUrl:Ge.string().optional().describe("URL where the user can obtain this value (e.g. Stripe dashboard)")});async function wn(s){let{projectPath:o,action:e,key:t,value:n,category:a,description:r,setupUrl:i}=s,l=Ql(o??process.cwd());if(!ye())return d("No Mistflow credentials found. Run mist_setup to connect your account.",!0);let u=Re(l)?.projectId;if(!u)return d("No project ID found. Deploy your project first with mist_deploy, or initialize with mist_plan + mist_build.",!0);try{switch(e){case"set":return t?n?(await cs(u,t,n,{category:a,description:r,setupUrl:i}),d(JSON.stringify({set:!0,key:t,message:`Environment variable '${t}' has been set. It will be available on your next deployment.`}))):d("Value is required. Provide the env var value.",!0):d("Key is required. Provide the env var name like 'STRIPE_SECRET_KEY'.",!0);case"list":{let m=await ls(u);return m.length===0?d(JSON.stringify({envVars:[],message:"No environment variables configured. Use action 'set' to add one."})):d(JSON.stringify({envVars:m.map(p=>({key:p.key,category:p.category,description:p.description,hasValue:p.has_value})),message:`${m.length} environment variable(s) configured.`}))}case"delete":return t?(await ds(u,t),d(JSON.stringify({deleted:!0,key:t,message:`Environment variable '${t}' has been removed.`}))):d("Key is required. Provide the env var name to delete.",!0);default:return d(`Unknown action: ${e}. Use set, list, or delete.`,!0)}}catch(m){if(m instanceof xe)return d(m.message,!0);let p=m instanceof Error?m.message:"An unexpected error occurred";return d(p,!0)}}import{z as Pt}from"zod";import{resolve as Xl,join as Zl}from"path";import{existsSync as ec,readFileSync as tc,writeFileSync as oc}from"fs";var Cm=Pt.object({projectPath:Pt.string().optional().describe("Path to the project directory (default: cwd)"),action:Pt.enum(["add","list","verify","remove"]).describe("Action to perform"),domain:Pt.string().optional().describe("Domain name (required for 'add' and 'remove')"),domainId:Pt.string().optional().describe("Domain ID (required for 'verify' and 'remove')")});function zo(s,o){let e=Zl(s,"mistflow.json");if(!ec(e))return;let t;try{t=JSON.parse(tc(e,"utf-8"))}catch{return}t.domains=o,oc(e,JSON.stringify(t,null,2)+`
|
|
5096
|
+
`)}async function vn(s){let{projectPath:o,action:e,domain:t,domainId:n}=s,a=Xl(o??process.cwd());if(!ye())return d("No Mistflow credentials found. Run mist_setup to connect your account.",!0);let i=Re(a)?.projectId;if(!i)return d("No project ID found. Deploy your project first with mist_deploy, or initialize with mist_plan + mist_build.",!0);try{switch(e){case"add":{if(!t)return d("Domain name is required. Provide the domain like 'myapp.com' or 'app.mycompany.com'.",!0);let l=await ss(i,t),c=await Ke(i);return zo(a,c.map(u=>({domain:u.domain,status:u.status}))),d(JSON.stringify({added:!0,domain:l.domain,status:l.status,instructions:l.instructions,message:`Domain '${l.domain}' added. Set up DNS records as described, then use action 'verify' to check status.`}))}case"list":{let l=await Ke(i);return l.length===0?d(JSON.stringify({domains:[],message:"No custom domains configured. Use action 'add' to add one."})):d(JSON.stringify({domains:l.map(c=>({id:c.id,domain:c.domain,status:c.status,ssl:c.ssl_status,error:c.error_message}))}))}case"verify":{if(!n){if(t){let m=(await Ke(i)).find(p=>p.domain===t);if(m){let p=await lo(i,m.id);return d(JSON.stringify({domain:p.domain,status:p.status,ssl:p.ssl_status,error:p.error_message,message:p.status==="active"?`Domain '${p.domain}' is active and serving traffic.`:`Domain '${p.domain}' is ${p.status}. Make sure DNS records are configured correctly.`}))}return d(`Domain '${t}' not found. Use action 'list' to see configured domains.`,!0)}return d("Provide either domainId or domain name to verify.",!0)}let l=await lo(i,n),c=await Ke(i);return zo(a,c.map(u=>({domain:u.domain,status:u.status}))),d(JSON.stringify({domain:l.domain,status:l.status,ssl:l.ssl_status,error:l.error_message,message:l.status==="active"?`Domain '${l.domain}' is active and serving traffic.`:`Domain '${l.domain}' is ${l.status}. Make sure DNS records are configured correctly.`}))}case"remove":{if(!n&&!t)return d("Provide either domainId or domain name to remove.",!0);let l=n;if(!l&&t){let m=(await Ke(i)).find(p=>p.domain===t);if(!m)return d(`Domain '${t}' not found.`,!0);l=m.id}await rs(i,l);let c=await Ke(i);return zo(a,c.map(u=>({domain:u.domain,status:u.status}))),d(JSON.stringify({removed:!0,message:"Domain removed. It may take a few minutes for DNS changes to propagate."}))}default:return d(`Unknown action: ${e}. Use add, list, verify, or remove.`,!0)}}catch(l){if(l instanceof xe)return d(l.message,!0);let c=l instanceof Error?l.message:"An unexpected error occurred";return d(c,!0)}}var sc=Ne.object({resource:Ne.enum(["env","domain"]).describe("'env' manages app secrets and configuration values. 'domain' manages custom domains."),action:Ne.string().describe("Action to perform. env: 'set', 'list', 'delete'. domain: 'add', 'list', 'verify', 'remove'."),projectPath:Ne.string().optional().describe("Path to the project directory (default: cwd)"),key:Ne.string().optional().describe("(env) Variable name"),value:Ne.string().optional().describe("(env set) Variable value"),category:Ne.string().optional().describe("(env set) Category"),description:Ne.string().optional().describe("(env set) Description"),setupUrl:Ne.string().optional().describe("(env set) URL to obtain the value"),domain:Ne.string().optional().describe("(domain) Domain name"),domainId:Ne.string().optional().describe("(domain) Domain ID")}),kn={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:sc,handler:async s=>{let o=s;switch(o.resource){case"env":return wn({projectPath:o.projectPath,action:o.action,key:o.key,value:o.value,category:o.category,description:o.description,setupUrl:o.setupUrl});case"domain":return vn({projectPath:o.projectPath,action:o.action,domain:o.domain,domainId:o.domainId});default:return d(`Unknown resource: ${o.resource}. Use env or domain.`,!0)}}};import{z as et}from"zod";var rc=et.object({action:et.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:et.string().optional().describe("URL to navigate to. Required for 'navigate'; optional for 'screenshot' (navigates before capturing)."),selector:et.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:et.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:et.boolean().default(!1).describe("For 'screenshot': capture the full scrollable page instead of just the viewport."),includeScreenshot:et.boolean().default(!1).describe("For navigate/interact actions: also return a screenshot alongside the accessibility snapshot.")}),xn={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:rc,handler:async s=>{let o=s,e=await Et();if(o.action==="navigate"){if(!o.url)return d("URL is required for 'navigate'.",!0);let a=[],r=c=>{c.type()==="error"&&a.push(c.text())};e.on("console",r),await e.goto(o.url,{waitUntil:"domcontentloaded",timeout:3e4}),await e.waitForLoadState("networkidle").catch(()=>{});let i=[],l=c=>i.push(c.message);if(e.on("pageerror",l),await e.waitForTimeout(500),e.removeListener("console",r),e.removeListener("pageerror",l),a.length>0||i.length>0){let c=await Nt(e),u=[{type:"text",text:JSON.stringify({url:e.url(),title:await e.title(),snapshot:c,consoleErrors:a,pageErrors:i,hasErrors:!0})}];if(o.includeScreenshot){let m=await Le(e);u.push({type:"image",data:m.toString("base64"),mimeType:"image/png"})}return{content:u}}}else if(o.action==="go_back")await e.goBack({waitUntil:"domcontentloaded",timeout:1e4});else if(o.action==="go_forward")await e.goForward({waitUntil:"domcontentloaded",timeout:1e4});else if(o.action==="click"){if(!o.selector)return d("Selector is required for 'click'.",!0);await e.click(o.selector,{timeout:1e4}),await e.waitForLoadState("domcontentloaded").catch(()=>{}),await e.waitForTimeout(500)}else if(o.action==="type"){if(!o.selector)return d("Selector is required for 'type'.",!0);if(!o.value)return d("Value is required for 'type'.",!0);await e.type(o.selector,o.value,{delay:50})}else if(o.action==="fill"){if(!o.selector)return d("Selector is required for 'fill'.",!0);if(!o.value)return d("Value is required for 'fill'.",!0);await e.fill(o.selector,o.value)}else if(o.action==="select_option"){if(!o.selector)return d("Selector is required for 'select_option'.",!0);if(!o.value)return d("Value is required for 'select_option'.",!0);await e.selectOption(o.selector,o.value)}else if(o.action==="hover"){if(!o.selector)return d("Selector is required for 'hover'.",!0);await e.hover(o.selector,{timeout:1e4})}else if(o.action==="press_key"){if(!o.value)return d("Value is required for 'press_key' (e.g. 'Enter').",!0);await e.keyboard.press(o.value),await e.waitForLoadState("domcontentloaded").catch(()=>{}),await e.waitForTimeout(500)}else if(o.action==="screenshot"){o.url&&(await e.goto(o.url,{waitUntil:"domcontentloaded",timeout:3e4}),await e.waitForLoadState("networkidle").catch(()=>{}));let a;if(o.selector){let r=await e.$(o.selector);if(!r)return d(`Element not found: ${o.selector}`,!0);a=await r.screenshot({type:"png"})}else a=await Le(e,o.fullPage);return{content:[{type:"text",text:JSON.stringify({url:e.url(),title:await e.title(),message:`Screenshot captured (${o.fullPage?"full page":"viewport"})`})},{type:"image",data:a.toString("base64"),mimeType:"image/png"}]}}else if(o.action==="snapshot"){let a=await Nt(e);return{content:[{type:"text",text:JSON.stringify({url:e.url(),title:await e.title(),snapshot:a})}]}}let t=await Nt(e),n=[{type:"text",text:JSON.stringify({url:e.url(),title:await e.title(),snapshot:t})}];if(o.includeScreenshot){let a=await Le(e);n.push({type:"image",data:a.toString("base64"),mimeType:"image/png"})}return{content:n}}};import{existsSync as _e,readFileSync as It,writeFileSync as mt}from"fs";import{join as Te}from"path";import{z as At}from"zod";function Sn(s){let o=Te(s,"mistflow.json");if(!_e(o))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 e;try{e=JSON.parse(It(o,"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(!e.name||typeof e.name!="string")return{result:{name:"Project",status:"warn",message:"mistflow.json is missing the 'name' field."},config:e};let t=e.projectId,n=t?`, id: ${t}`:", no projectId";return{result:{name:"Project",status:"pass",message:`${e.name}${n}`},config:e}}async function nc(){let s=Vo();if(!s.ok)return{result:{name:"Auth",status:"fail",message:s.reason==="missing"?"No credentials found.":"Credentials file is malformed.",fix:"Run mist_setup to log in."},creds:null,authValid:!1};try{let o=Ko(),e=await fetch(`${Ye()}/api/org`,{headers:o,signal:AbortSignal.timeout(1e4)});try{Go(e.headers)}catch{}if(!e.ok)return e.status===401?{result:{name:"Auth",status:"fail",message:"API key is invalid or revoked.",fix:"Run mist_setup to re-authenticate."},creds:s.creds,authValid:!1}:{result:{name:"Auth",status:"warn",message:`Server returned ${e.status}. Credentials may still be valid.`},creds:s.creds,authValid:!1};let t=await e.json(),n=t.slug??s.creds.orgSlug??"unknown",a=t.plan?`, plan: ${t.plan}`:"";return{result:{name:"Auth",status:"pass",message:`org: ${n}${a}`},creds:s.creds,authValid:!0}}catch(o){return o instanceof xe?{result:{name:"Auth",status:"fail",message:`Auth check failed: ${o.message}`,fix:"Run mist_setup to re-authenticate."},creds:s.creds,authValid:!1}:{result:{name:"Auth",status:"warn",message:"Could not reach API to validate credentials. Network issue?"},creds:s.creds,authValid:!1}}}async function ic(){let s=Date.now();try{let o=await Dt("nextjs"),e=Date.now()-s;return{result:{name:"API",status:"pass",message:`${Ye()} reachable (${e}ms)`},scaffold:o}}catch(o){let e=Date.now()-s;return{result:{name:"API",status:"fail",message:o instanceof xe?o.message:`Timeout or network error after ${e}ms`,fix:"Check your network connection. If using --api-url, verify the backend is running."},scaffold:null}}}function ac(){let s=gt();if(!s.backendSignalReceived)return{name:"MCP version",status:"warn",message:`v${s.current} installed, but no backend signal received to compare against.`};let o={none:"up to date",patch:"patch update available",minor:"minor update available",major:"major update available",unsupported:"no longer supported"};return s.severity==="none"?{name:"MCP version",status:"pass",message:`v${s.current} (latest)`}:s.severity==="unsupported"?{name:"MCP version",status:"fail",message:`v${s.current} is ${o[s.severity]}. Minimum: v${s.minSupported}.`,fix:`Run \`${s.upgradeCmd}\` then restart your editor.`}:{name:"MCP version",status:"warn",message:`v${s.current} installed, v${s.latest} available (${o[s.severity]}).`,fix:`Run \`${s.upgradeCmd}\` then restart your editor.`}}function lc(s,o,e,t){let n=Te(s,"AGENTS.md"),a=Te(s,"CLAUDE.md"),r=_e(n),i=o.methodologyVersion??"",l=e?.version??"";if(!r){if(!t||!e?.methodology)return{name:"AGENTS.md",status:"fail",message:"Missing. The host AI has no methodology context for this project.",fix:e?"Run mist_doctor without reportOnly to auto-restore it.":"Cannot restore: API unreachable. Fix connectivity first."};let c=Tn(e.methodology,o);return mt(n,c),mt(a,c),{name:"AGENTS.md",status:"fix",message:`Restored from methodology v${l}.`}}if(t&&!_e(a)){let c=It(n,"utf-8");mt(a,c)}if(l&&i&&l!==i){if(!t||!e?.methodology)return{name:"AGENTS.md",status:"warn",message:`Methodology v${i} installed, v${l} available.`,fix:"Run mist_doctor to update it."};let c=Tn(e.methodology,o);mt(n,c),mt(a,c);try{let u=Te(s,"mistflow.json"),m=JSON.parse(It(u,"utf-8"));m.methodologyVersion=l,mt(u,JSON.stringify(m,null,2)+`
|
|
5097
|
+
`)}catch{}return{name:"AGENTS.md",status:"fix",message:`Updated methodology v${i} -> v${l}.`}}return{name:"AGENTS.md",status:"pass",message:i?`v${i}`:"present"}}async function cc(s,o,e,t){let n=o.projectId;if(!n)return{name:"State sync",status:"skip",message:"No projectId. State sync not applicable without a linked project."};if(!e)return{name:"State sync",status:"skip",message:"Skipped (not authenticated)."};let a=Ss(s),r=await Ts(n);if(!a&&!r)return{name:"State sync",status:"warn",message:"No local or remote state found."};if(!a&&r){if(t){let{writeLocalState:i}=await import("./state-manager-GBP2NSYC.js");return i(s,r),{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 a&&!r?{name:"State sync",status:"warn",message:"Local state exists but no remote state found. Remote sync may have failed."}:a&&a.projectId!==n?{name:"State sync",status:"warn",message:`state.json projectId (${a.projectId}) doesn't match mistflow.json projectId (${n}).`}:{name:"State sync",status:"pass",message:`deployCount: ${a.deployCount}, features: ${a.features.length}`}}function dc(s){let o=[],e=[];if(_e(Te(s,"package.json"))?_e(Te(s,"node_modules"))||o.push("node_modules not installed (run `npx -y @mistflow-ai/cli install`)"):o.push("missing package.json"),!_e(Te(s,".env.local")))o.push("missing .env.local");else try{It(Te(s,".env.local"),"utf-8").match(/^AUTH_SECRET=(.+)$/m)||o.push("AUTH_SECRET not set in .env.local")}catch{o.push(".env.local exists but is not readable")}let t=Te(s,"app","api","auth","[...all]","route.ts");_e(t)||e.push("missing app/api/auth/[...all]/route.ts");let n=Te(s,"app","api","health","route.ts");return _e(n)||e.push("missing app/api/health/route.ts (needed for deploy verification)"),o.length>0?{name:"Structure",status:"fail",message:o.join("; "),fix:o.map(a=>a.includes("node_modules")?"Run `npx -y @mistflow-ai/cli install` in the project directory (streams output, no MCP 60s timeout). Plain `npm install` also works.":a.includes(".env.local")?"Create .env.local with AUTH_SECRET=<random-secret>.":a.includes("AUTH_SECRET")?"Add AUTH_SECRET=<random-secret> to .env.local.":a.includes("package.json")?"This project is missing package.json. Was it scaffolded correctly?":"").filter(Boolean).join(" ")}:e.length>0?{name:"Structure",status:"warn",message:e.join("; ")}:{name:"Structure",status:"pass",message:"all required files present"}}function pc(s,o){let e=o.env;if(!e?.required||typeof e.required!="object"||Object.keys(e.required).length===0)return{name:"Env vars",status:"pass",message:"no required env vars declared"};let t=[],n="";for(let a of[".env.local",".env"]){let r=Te(s,a);try{_e(r)&&(n+=`
|
|
5098
|
+
`+It(r,"utf-8"))}catch{}}for(let[a,r]of Object.entries(e.required)){let i=n.includes(`${a}=`),l=!!process.env[a];if(!i&&!l){let c=r?.description?` (${r.description})`:"";t.push(`${a}${c}`)}}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(e.required).length} required, all set`}}function uc(s){let o=s.plan;if(!o?.steps||o.steps.length===0)return{name:"Plan",status:"skip",message:"No plan found in mistflow.json."};let e=o.steps.length,t=o.steps.filter(r=>r.status==="completed").length,n=o.steps.filter(r=>r.status==="in_progress").length,a=e-t-n;if(t===e)return{name:"Plan",status:"pass",message:`${t}/${e} steps completed`};if(n>0){let r=o.steps.find(i=>i.status==="in_progress");return{name:"Plan",status:"warn",message:`${t}/${e} completed, step ${r?.number} "${r?.name}" in progress`,fix:"Run mist_build implement to continue."}}return t===0?{name:"Plan",status:"warn",message:`${e} steps planned, none started.`,fix:"Run mist_build implement to start building."}:{name:"Plan",status:"warn",message:`${t}/${e} completed, ${a} remaining.`,fix:"Run mist_build implement to continue."}}function mc(s,o){let e=Te(s,"contracts");if(!_e(e))return{name:"Contracts",status:"skip",message:"No contracts/ directory. Scaffolded before integration contracts landed, or template app."};let n=o.plan?.dataModel??[];if(n.length===0)return{name:"Contracts",status:"pass",message:"contracts/ present, no entities in plan"};let a=[];for(let r of n){let i=r.entity??r.name;if(!i||typeof i!="string")continue;let l=Te(e,`${vo(i)}.ts`);_e(l)||a.push(i)}return a.length>0?{name:"Contracts",status:"warn",message:`${a.length} entit${a.length===1?"y":"ies"} missing a contract file: ${a.join(", ")}`,fix:"Create contracts/<entity>.ts for each missing entity. See contracts/README.md for the template. Every route and server action should import its types from contracts/, never inline them."}:{name:"Contracts",status:"pass",message:`${n.length} entit${n.length===1?"y has":"ies have"} contracts`}}function Tn(s,o){let e=o.dbProvider;return e==="neon"||!e?s.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"):s}function hc(s){let o={pass:"PASS",warn:"WARN",fail:"FAIL",fix:"FIXED",skip:"SKIP"},e=Math.max(...s.map(u=>u.name.length)),t=s.map(u=>{let m=".".repeat(e-u.name.length+4),p=o[u.status].padEnd(5);return` ${u.name} ${m} ${p} ${u.message}`}),n=s.filter(u=>u.status==="fix").length,a=s.filter(u=>u.status==="fail").length,r=s.filter(u=>u.status==="warn").length,i=[];n>0&&i.push(`${n} fixed`),a>0&&i.push(`${a} failure${a>1?"s":""}`),r>0&&i.push(`${r} warning${r>1?"s":""}`);let l=`mist_doctor results:
|
|
5094
5099
|
|
|
5095
|
-
`+
|
|
5100
|
+
`+t.join(`
|
|
5096
5101
|
`);i.length>0?l+=`
|
|
5097
5102
|
|
|
5098
5103
|
${i.join(", ")}`:l+=`
|
|
5099
5104
|
|
|
5100
|
-
All checks passed.`;let c=s.filter(
|
|
5105
|
+
All checks passed.`;let c=s.filter(u=>(u.status==="fail"||u.status==="warn")&&u.fix);if(c.length>0){l+=`
|
|
5101
5106
|
|
|
5102
|
-
To fix:`;for(let
|
|
5103
|
-
- ${
|
|
5107
|
+
To fix:`;for(let u of c)l+=`
|
|
5108
|
+
- ${u.fix}`}return l}var Cn=At.object({projectPath:At.string().optional().describe("Path to the project directory. Defaults to the current working directory."),checks:At.array(At.enum(["project","auth","api","version","methodology","state","structure","env","plan","contracts"])).optional().describe("Run only specific checks. Omit to run all."),reportOnly:At.boolean().optional().describe("If true, report issues without auto-fixing them.")}),Pn={name:"mist_doctor",description:"Diagnose project health: checks auth, API connectivity, AGENTS.md methodology, project state, version alignment, structure, and integration contracts. Auto-fixes safe issues (missing AGENTS.md, stale state). Run when something feels off.",inputSchema:Cn,handler:async s=>{let o=Cn.parse(s),e=o.projectPath||process.cwd(),t=!o.reportOnly,n=!o.checks,a=o.checks??[],r=f=>n||a.includes(f),i=[],l=null;if(r("project")){let f=Sn(e);i.push(f.result),l=f.config}else l=Sn(e).config;let c=!1;if(r("auth")||r("state")){let f=await nc();r("auth")&&i.push(f.result),c=f.authValid}let m=null;if(r("api")||r("methodology")){let f=await ic();r("api")&&i.push(f.result),m=f.scaffold}r("version")&&i.push(ac()),l?(r("methodology")&&i.push(lc(e,l,m,t)),r("state")&&i.push(await cc(e,l,c,t)),r("structure")&&i.push(dc(e)),r("env")&&i.push(pc(e,l)),r("plan")&&i.push(uc(l)),r("contracts")&&i.push(mc(e,l))):n&&(i.push({name:"AGENTS.md",status:"skip",message:"Skipped (not a Mistflow project)."}),i.push({name:"State sync",status:"skip",message:"Skipped (not a Mistflow project)."}),i.push({name:"Structure",status:"skip",message:"Skipped (not a Mistflow project)."}),i.push({name:"Env vars",status:"skip",message:"Skipped (not a Mistflow project)."}),i.push({name:"Plan",status:"skip",message:"Skipped (not a Mistflow project)."}),i.push({name:"Contracts",status:"skip",message:"Skipped (not a Mistflow project)."}));let g=hc(i),w=i.some(f=>f.status==="fail");return d(g,w)}};import{z as An}from"zod";var In=`# Mistflow CLI reference
|
|
5104
5109
|
|
|
5105
5110
|
The Mistflow CLI handles local execution and long-running operations that
|
|
5106
5111
|
would hit the MCP 60s tool-call ceiling. Every command below is invokable
|
|
@@ -5182,9 +5187,9 @@ and decides the next command. Example end-to-end chain:
|
|
|
5182
5187
|
echo '{...}' | mist plan --cid ... --pick-stdin --json
|
|
5183
5188
|
# \u2192 {"status":"ready","plan":{...}}
|
|
5184
5189
|
# Then: mist_build init (MCP), mist install (CLI), mist_build implement (MCP), etc.
|
|
5185
|
-
`,
|
|
5186
|
-
`),
|
|
5187
|
-
`).trim())}};var ro=new
|
|
5190
|
+
`,Rn={name:"mist_help",description:"Returns the Mistflow CLI command reference. Call this ONCE at session start (or whenever you're unsure which tool to use) to learn every `mist` CLI command, when to prefer it over an MCP tool, and how to chain calls. Results are static \u2014 no backend round-trip, safe to call frequently.",inputSchema:An.object({command:An.string().optional().describe("Optional: name of a specific command to get focused reference for. Omit to get the full catalog.")}),handler:async s=>{let{command:o}=s;if(!o)return d(In);let e=In.split(`
|
|
5191
|
+
`),t=new RegExp(`^### \`mist ${o}`),n=-1,a=e.length;for(let r=0;r<e.length;r++)if(t.test(e[r]))n=r;else if(n>=0&&e[r].startsWith("### ")){a=r;break}else if(n>=0&&e[r].startsWith("## ")&&r>n){a=r;break}return n<0?d(`No command named '${o}' found. Call mist_help with no args to see the full catalog.`,!0):d(e.slice(n,a).join(`
|
|
5192
|
+
`).trim())}};var ro=new gc({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.
|
|
5188
5193
|
|
|
5189
5194
|
Call \`mist_help\` at any point for the full CLI command reference. Prefer the CLI for long-running local or LLM-backed operations; MCP tools work for interactive, short, structured flows.
|
|
5190
5195
|
|
|
@@ -5226,4 +5231,4 @@ Other tools:
|
|
|
5226
5231
|
- mist_project: read/update project state, browse landing designs and app styles, view runtime errors, deploy logs, deployment history, and check MCP version.
|
|
5227
5232
|
- mist_config: manage encrypted app secrets (env vars) and custom domains.
|
|
5228
5233
|
- mist_browser: navigate, interact with, and screenshot the app during preview or after deploy.
|
|
5229
|
-
- 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.`}),
|
|
5234
|
+
- 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.`}),En=[As,zs,Or,hn,bn,kn,xn,Pn,Rn];ro.setRequestHandler(bc,async()=>({tools:En.map(s=>({name:s.name,description:s.description,inputSchema:wc(s.inputSchema)}))}));ro.setRequestHandler(yc,async s=>{let o=En.find(e=>e.name===s.params.name);if(!o)return d(`Unknown tool: ${s.params.name}`,!0);try{let e=o.inputSchema.safeParse(s.params.arguments);if(!e.success){let a=e.error.issues.map(r=>`${r.path.join(".")}: ${r.message}`).join(", ");return d(`Invalid input: ${a}`,!0)}let t=s.params._meta?.progressToken,n={server:ro,progressToken:t};try{return await o.handler(e.data,n)}finally{n.cleanup?.()}}catch(e){let t=e instanceof Error?e.message:"An unexpected error occurred";return console.error("Tool error:",e),d(t,!0)}});async function vc(){let s=process.argv.indexOf("--api-url");s!==-1&&process.argv[s+1]&&(process.env.MISTFLOW_API_URL=process.argv[s+1]),process.argv.includes("--local")&&!process.env.MISTFLOW_API_URL&&(process.env.MISTFLOW_API_URL="http://localhost:9100");let o=new fc;await ro.connect(o),console.error(`Mistflow MCP server running on stdio (API: ${process.env.MISTFLOW_API_URL||"https://api.mistflow.ai"})`)}vc().catch(s=>{console.error("Fatal error:",s),process.exit(1)});
|