@mistflow-ai/mcp 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/cli.js +2178 -178
  2. package/dist/index.js +2180 -180
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1,27 +1,27 @@
1
1
  #!/usr/bin/env node
2
- var Si=Object.defineProperty;var _=(t,e)=>()=>(t&&(e=t(t=0)),e);var $t=(t,e)=>{for(var s in e)Si(t,s,{get:e[s],enumerable:!0})};function qs(){let t=[];return t.push("Mistflow is a full-stack app builder that creates and deploys web apps from natural language descriptions. When a user asks to build, create, or make a web app, website, landing page, dashboard, internal tool, marketplace, content site, or browser game, use Mistflow tools. Mistflow creates NEW apps from scratch. It does NOT modify existing non-Mistflow codebases."),t.push(""),t.push('Every capability is an MCP tool. There is no companion CLI. Long-running work (install, build, qa, deploy) uses the fire-and-poll pattern: the first call returns a jobId and `status: "running"`; subsequent calls with that jobId return fresh state. Each call responds in under a second \u2014 no 60s timeout risk.'),t.push(""),t.push(Ei),t.push(""),t.push(Pi),t.push(""),t.push(Ii),t.push(""),t.push(Ti),t.push(""),t.push(_i),t.push(""),t.push(Ni),t.push(""),t.push(Ci),t.push(""),t.push("New app workflow:"),t.push("1. Choose destination: if the user didn't already give a path, ask whether to scaffold in the current folder or a new subfolder here. Resolve the absolute path before mist_init."),t.push('2. Plan: call mist_plan with the user\'s description EXACTLY as written \u2014 do NOT expand or add features. When the response has `status: "clarify"` with `questions[]`, render them via AskUserQuestion, collect the answers, and submit them back in the next mist_plan call with the `conversationId`. When `status: "design_clarify_pending"`, poll until directions are ready, present them to the user, submit the pick.'),t.push("3. Mockup (optional, recommended): call mist_mockup with the planId \u2014 returns a wireframe prompt. Write the HTML to the returned path, open it for the user, wait for approval. Iterate with { feedback }, lock with { approved: true }."),t.push("4. Scaffold: mist_init with { planId, path }. Transactional \u2014 if it fails before the final move, stop."),t.push("5. Install: mist_install { projectPath } starts; poll with { jobId } until complete. Typical 30\u201390s."),t.push("6. Implement: mist_implement runs one plan step at a time and auto-marks the prior step complete. Call repeatedly until all steps are done."),t.push("7. Build: mist_build { projectPath } starts; poll with { jobId }. On failure, the response has `missingModules[]` \u2014 chain mist_install with those modules then mist_build again without asking."),t.push("8. Deploy: mist_deploy { projectPath } starts tar + upload; poll with { action: 'status', deploymentId }. On complete, response has `qaRequired: true` and a `url` \u2014 do NOT surface the URL to the user until mist_qa passes. Subsequent actions: 'promote' (staging \u2192 prod), 'rollback' (revert to a specific deploymentId)."),t.push("9. QA: mist_qa { projectPath } drives Playwright in-process against the live URL \u2014 single synchronous call (~20\u201345s), returns pass/fail and screenshots inline. Only surface the deploy URL to the user after mist_qa returns status: 'pass'."),t.push(""),t.push(Ri),t.push(""),t.push(Ai),t.push(""),t.push("Updating an existing Mistflow app:"),t.push("- Cosmetic or single-file changes: edit files directly, then mist_build \u2192 mist_deploy."),t.push("- New page or feature (no new data model): mist_project action='get' for context, build it, then mist_build \u2192 mist_deploy."),t.push("- Feature needing new data model: ask product questions, call mist_project action='get' for context, call mist_plan with existingPlanId, then mist_implement, then mist_build \u2192 mist_deploy."),t.push("- Integration addition: ask product questions, mist_project action='get', mist_plan with existingPlanId, mist_implement, mist_config resource='env' for keys, mist_build \u2192 mist_deploy \u2192 mist_qa."),t.push("- Bug fix: mist_debug to extract structured errors, fix the code, mist_build \u2192 mist_deploy."),t.push(""),t.push("Tool summary:"),t.push("- mist_setup: authentication. Only call when the user has never signed in or a tool returned 'auth_missing'/'auth_revoked'. Do NOT call for 500s, 404s, or network errors. Also accepts an apiKey parameter for headless auth."),t.push("- mist_project: read/write project state (actions: 'get' / 'update'). Call 'get' before editing any existing Mistflow project to understand its shape."),t.push("- mist_browser: navigate, interact with, and screenshot the app during preview or after deploy. Returns screenshots inline in tool results."),t.push("- mist_help: returns the full tool reference. Call once per session."),t.push("- mist_plan / mist_mockup / mist_init / mist_implement / mist_debug / mist_config: core app-building workflow tools (see step-by-step above)."),t.push("- mist_install / mist_build / mist_deploy: fire-and-poll tools. Always check `status` in the response and keep polling while 'running'."),t.push("- mist_qa: synchronous single call. Drives Playwright in-process against the deployed URL. No jobId / no polling \u2014 one call returns the full QA result with screenshots."),t.join(`
3
- `)}var Pi,Ii,Ti,_i,Ri,Ci,Ai,Ei,Ni,Os,Ms,$s,Us,Ls,Fs,Oe=_(()=>{"use strict";Pi="mist_plan uses the fire-and-poll shape: the first call returns quickly with status 'running' and a conversationId while the backend Sonnet ask_questions call runs in the background (that call alone can take 30-150s). Poll by calling mist_plan with just { conversationId } every ~10-15s until status is 'clarify' (questions arrived) or 'ready' (plan generated). Do NOT re-send description or answers while polling \u2014 the conversation is already tracked server-side.",Ii='Treat the tool\'s returned JSON as authoritative. If `mist_plan` returns `status: "clarify"`, the planning call completed and is waiting on user answers. Do NOT rerun the same call or describe it as "still generating". Only poll again when `status: "design_clarify_pending"`.',Ti="When submitting discovery answers back to `mist_plan` (conversationId + answers), prefer an array payload: `answers: [{question, decisionKey, answer}, ...]`. Do NOT collapse into a `{decisionKey: answer}` map \u2014 multiple discovery questions can share the same decision key.",_i="Before scaffolding a NEW app, resolve the destination path explicitly. If the user asked to create/build a new app and the current directory is not already that Mistflow project, ask whether to scaffold in the current folder or a new subfolder here (unless the user already gave a path). Do NOT inspect sibling directories and silently adopt one as the base just because it looks similar. Reuse an existing Mistflow project only when the user explicitly asks to continue/edit that project.",Ri='Once the user approves the plan, the mist_init \u2192 mist_install \u2192 mist_implement (repeat) \u2192 mist_build \u2192 mist_deploy \u2192 mist_qa chain is expected. Do NOT pause between these calls to ask the user "should I continue?" or offer options like "full build vs step-by-step." A tool returns a status that requires user input when it genuinely needs one (e.g. confirm_new_project, awaiting promotion). Otherwise chain calls continuously. Brief one-line status updates are fine; permission requests are not.',Ci="mist_init is all-or-nothing for normal users: it registers the cloud project first, scaffolds in a temp directory, then moves the finished app into place only after both steps succeed. If auth, quota, or network registration fails, stop before any app appears locally. Do NOT suggest reusing another app unless the user explicitly asks.",Ai="If mist_init fails, stop before any cloud command and explain why. Do NOT default to reusing some other existing app as recovery unless the user explicitly asked to continue that app.",Ei='Long-running tools (mist_plan, mist_install, mist_build, mist_qa, mist_deploy) use the fire-and-poll pattern. The first call returns `status: "running"` with a `jobId` or `conversationId`. Every subsequent call with that same id returns the current state \u2014 re-call IMMEDIATELY whenever you see `status: "running"`. DO NOT run `bash sleep`, `setTimeout`, or any wall-clock wait between polls \u2014 Claude Code\'s harness blocks standalone sleeps, and every MCP server poll endpoint holds the connection up to ~10-15s on its side already, returning as soon as state changes. Just call the tool again. Each response is well inside the 60s MCP ceiling. When `status: "complete"` or `"failed"` arrives, the tool is done. Never assume a job stalled because a single call took 10s \u2014 calls return fast, but the work keeps running in the background. Do not ask the user whether to keep polling; polling is how these tools work.',Ni="When `mist_plan` returns a `questions[]` array (or `directions[]` for design picking), you MUST render every question via your native structured-question UI \u2014 `AskUserQuestion` in Claude Code, quick pick in Cursor, equivalent in other hosts \u2014 and wait for the user to actually answer. Do NOT pick the `recommended` option on their behalf. Do NOT print the questions as a markdown list with a 'say the word if you want to change any' fallback. Do NOT proceed without real answers even if you are inside `/loop`, an autonomous agent run, a scheduled wake-up, or any other non-interactive mode \u2014 if the user isn't available, stop and wait; the flow resumes when they respond. Every question is a product decision the user is paying to make, and `recommended` is a hint for them, not a permission slip for you. Each question has `id`/`decisionKey`/`question`/`options[]` (each option: `label`, `description`) plus an optional `recommended` string. Collect the user's actual selections, then submit them back via `mist_plan` with the `conversationId` \u2014 don't ask the user to do that step.",Os="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).",Ms="Read or inspect Mistflow project state. 'get' (default) loads plan progress, env vars, and deploy info \u2014 call before editing. 'update' marks plan steps complete or adds env vars. 'share' creates a forkable template URL. 'landing-designs' / 'integrations' browse curated catalogs (pass presetId / integrationId for full details). 'errors' fetches runtime errors from the deployed app. 'logs' fetches deploy logs. 'deployments' lists history. 'version' reports the installed MCP version + any upgrade available.",$s="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.",Us="Returns the full Mistflow MCP tool reference. Call ONCE at session start (or whenever unsure which tool to pick) to learn the 14-tool surface, the fire-and-poll pattern, and how to chain end-to-end from mist_plan through mist_qa. Static \u2014 no backend round-trip, safe to call frequently.",Ls="Analyze build errors from a Next.js / TypeScript project. Extracts structured errors with file, line, a human-readable message for the user, and an actionable suggestion. Call with { projectPath } to run `npm run build` and parse the failure; call with { buildOutput } to parse output captured elsewhere (e.g. from a failed mist_build job).",Fs="Generate a grayscale wireframe sketch of the planned app so the user can review layout + information hierarchy before any code is written. The server returns a structured spec + a wireframePrompt \u2014 the host AI writes the HTML file to .mistflow/mockups/mockup-<planId>.html and opens it. Pass { planId } for the first iteration, { planId, feedback: '...' } to iterate, { planId, approved: true } to lock the design before mist_init. Iterative by design \u2014 expect 1-3 rounds of feedback."});import{existsSync as Sn,readFileSync as Ws,writeFileSync as ji,mkdirSync as Di}from"fs";import{join as xn,dirname as kn}from"path";import{homedir as Oi}from"os";import{fileURLToPath as Mi}from"url";function ve(){if(Ut)return Ut;try{let t=Mi(import.meta.url),e=kn(t);for(let s=0;s<6;s++){let n=xn(e,"package.json");if(Sn(n)){let i=JSON.parse(Ws(n,"utf-8"));if(i.name==="@mistflow-ai/mcp"&&typeof i.version=="string")return Ut=i.version,i.version}let o=kn(e);if(o===e)break;e=o}}catch{}return Ut="0.0.0","0.0.0"}function Lt(t){let e=/^(\d+)\.(\d+)\.(\d+)/.exec(t.trim());return e?[parseInt(e[1],10),parseInt(e[2],10),parseInt(e[3],10)]:null}function Bs(t,e){let s=Lt(t),n=Lt(e);if(!s||!n)return 0;for(let o=0;o<3;o++){if(s[o]<n[o])return-1;if(s[o]>n[o])return 1}return 0}function Gs(t,e,s){if(s&&Bs(t,s)<0)return"unsupported";if(!e||Bs(t,e)>=0)return"none";let o=Lt(t),i=Lt(e);return!o||!i?"none":o[0]<i[0]?"major":o[1]<i[1]?"minor":"patch"}function bt(t){let e=t.get("x-mistflow-mcp-latest")??"",s=t.get("x-mistflow-mcp-min-supported")??"",n=t.get("x-mistflow-mcp-changelog-url")??"";!e&&!s||(we={latest:e,minSupported:s,changelogUrl:n})}function Vs(){let t=process.env.MISTFLOW_STATE_DIR||xn(Oi(),".mistflow");return xn(t,"upgrade-state.json")}function $i(){try{let t=Vs();return Sn(t)?JSON.parse(Ws(t,"utf-8")):{}}catch{return{}}}function Ui(t){try{let e=Vs(),s=kn(e);Sn(s)||Di(s,{recursive:!0}),ji(e,JSON.stringify(t,null,2)+`
4
- `,{mode:384})}catch{}}function Li(t){return t==="patch"?7*864e5:t==="minor"?1*864e5:0}function Js(){if(process.env.MISTFLOW_NO_UPGRADE_CHECK==="1"||!we)return null;let t=ve();if(t==="0.0.0")return null;let e=Gs(t,we.latest,we.minSupported);if(e==="none")return null;if(e==="unsupported")return Hs(e,t,we);if(zs)return null;let s=$i(),n=Date.now(),o=Li(e);return s.dismissedForVersion===we.latest&&e!=="major"||s.lastLatestSeen===we.latest&&s.lastShownMs&&n-s.lastShownMs<o?null:(zs=!0,Ui({...s,lastShownMs:n,lastLatestSeen:we.latest}),Hs(e,t,we))}function Hs(t,e,s){let n="npx -y mistflow-ai install",o=s.changelogUrl?`
5
- What's new: ${s.changelogUrl}`:"";return t==="unsupported"?`
2
+ var Vi=Object.defineProperty;var S=(t,e)=>()=>(t&&(e=t(t=0)),e);var Ut=(t,e)=>{for(var r in e)Vi(t,r,{get:e[r],enumerable:!0})};function Br(){let t=[];return t.push("Mistflow is a full-stack app builder that creates and deploys web apps from natural language descriptions. When a user asks to build, create, or make a web app, website, landing page, dashboard, internal tool, marketplace, content site, or browser game, use Mistflow tools. Mistflow creates NEW apps from scratch. It does NOT modify existing non-Mistflow codebases."),t.push(""),t.push('Every capability is an MCP tool. There is no companion CLI. Long-running work (install, build, qa, deploy) uses the fire-and-poll pattern: the first call returns a jobId and `status: "running"`; subsequent calls with that jobId return fresh state. Each call responds in under a second \u2014 no 60s timeout risk.'),t.push(""),t.push(ta),t.push(""),t.push(Ji),t.push(""),t.push(Ki),t.push(""),t.push(Yi),t.push(""),t.push(Qi),t.push(""),t.push(oa),t.push(""),t.push(Zi),t.push(""),t.push("New app workflow:"),t.push("1. Choose destination: if the user didn't already give a path, ask whether to scaffold in the current folder or a new subfolder here. Resolve the absolute path before mist_init."),t.push('2. Plan: call mist_plan with the user\'s description EXACTLY as written \u2014 do NOT expand or add features. When the response has `status: "clarify"` with `questions[]`, render them via your host\'s native question tool (AskUserQuestion in Claude Code / request_user_input in Codex Plan mode / quick pick in Cursor / stop-and-wait for hosts without one \u2014 see RULE_SENTINEL_HANDLING), collect the user\'s actual answers, then submit them back in the next mist_plan call with the `conversationId`. When `status: "design_clarify_pending"`, poll until directions are ready, present them to the user, submit the pick via mist_plan with `designConversationId` + `designDirection` (NOT `conversationId` alone).'),t.push("3. Mockup (optional, recommended): call mist_mockup with the planId \u2014 returns a wireframe prompt. Write the HTML to the returned path, open it for the user, wait for approval. Iterate with { feedback }, lock with { approved: true }."),t.push("4. Scaffold: mist_init with { planId, path }. Transactional \u2014 if it fails before the final move, stop."),t.push("5. Install: mist_install { projectPath } starts; poll with { jobId } until complete. Typical 30\u201390s."),t.push("6. Implement: mist_implement runs one plan step at a time and auto-marks the prior step complete. Call repeatedly until all steps are done."),t.push("7. Build: mist_build { projectPath } starts; poll with { jobId }. On failure, the response has `missingModules[]` \u2014 chain mist_install with those modules then mist_build again without asking."),t.push("8. Deploy: mist_deploy { projectPath } starts tar + upload; poll with { action: 'status', deploymentId }. On complete, response has `qaRequired: true` and a `url` \u2014 do NOT surface the URL to the user until mist_qa passes. Subsequent actions: 'promote' (staging \u2192 prod), 'rollback' (revert to a specific deploymentId)."),t.push("9. QA: mist_qa { projectPath } drives Playwright in-process against the live URL \u2014 single synchronous call (~20\u201345s), returns pass/fail and screenshots inline. Only surface the deploy URL to the user after mist_qa returns status: 'pass'."),t.push(""),t.push(Xi),t.push(""),t.push(ea),t.push(""),t.push("Updating an existing Mistflow app:"),t.push("- Cosmetic or single-file changes: edit files directly, then mist_build \u2192 mist_deploy."),t.push("- New page or feature (no new data model): mist_project action='get' for context, build it, then mist_build \u2192 mist_deploy."),t.push("- Feature needing new data model: ask product questions, call mist_project action='get' for context, call mist_plan with existingPlanId, then mist_implement, then mist_build \u2192 mist_deploy."),t.push("- Integration addition: ask product questions, mist_project action='get', mist_plan with existingPlanId, mist_implement, mist_config resource='env' for keys, mist_build \u2192 mist_deploy \u2192 mist_qa."),t.push("- Bug fix: mist_debug to extract structured errors, fix the code, mist_build \u2192 mist_deploy."),t.push(""),t.push("Tool summary:"),t.push("- mist_setup: authentication. Only call when the user has never signed in or a tool returned 'auth_missing'/'auth_revoked'. Do NOT call for 500s, 404s, or network errors. Also accepts an apiKey parameter for headless auth."),t.push("- mist_project: read/write project state (actions: 'get' / 'update'). Call 'get' before editing any existing Mistflow project to understand its shape."),t.push("- mist_browser: navigate, interact with, and screenshot the app during preview or after deploy. Returns screenshots inline in tool results."),t.push("- mist_help: returns the full tool reference. Call once per session."),t.push("- mist_plan / mist_mockup / mist_init / mist_implement / mist_debug / mist_config: core app-building workflow tools (see step-by-step above)."),t.push("- mist_install / mist_build / mist_deploy: fire-and-poll tools. Always check `status` in the response and keep polling while 'running'."),t.push("- mist_qa: synchronous single call. Drives Playwright in-process against the deployed URL. No jobId / no polling \u2014 one call returns the full QA result with screenshots."),t.join(`
3
+ `)}var Ji,Ki,Yi,Qi,Xi,Zi,ea,ta,oa,Mr,Lr,Ur,$r,Fr,qr,Me=S(()=>{"use strict";Ji="mist_plan uses the fire-and-poll shape: the first call returns quickly with status 'running' and a conversationId while the backend Sonnet ask_questions call runs in the background (that call alone can take 30-150s). Poll by calling mist_plan with just { conversationId } every ~10-15s until status is 'clarify' (questions arrived) or 'ready' (plan generated). Do NOT re-send description or answers while polling \u2014 the conversation is already tracked server-side.",Ki='Treat the tool\'s returned JSON as authoritative. If `mist_plan` returns `status: "clarify"`, the planning call completed and is waiting on user answers. Do NOT rerun the same call or describe it as "still generating". Only poll again when `status: "design_clarify_pending"`.',Yi="When submitting discovery answers back to `mist_plan` (conversationId + answers), prefer an array payload: `answers: [{question, decisionKey, answer}, ...]`. Do NOT collapse into a `{decisionKey: answer}` map \u2014 multiple discovery questions can share the same decision key.",Qi="Before scaffolding a NEW app, resolve the destination path explicitly. If the user asked to create/build a new app and the current directory is not already that Mistflow project, ask whether to scaffold in the current folder or a new subfolder here (unless the user already gave a path). Do NOT inspect sibling directories and silently adopt one as the base just because it looks similar. Reuse an existing Mistflow project only when the user explicitly asks to continue/edit that project.",Xi='Once the user approves the plan, the mist_init \u2192 mist_install \u2192 mist_implement (repeat) \u2192 mist_build \u2192 mist_deploy \u2192 mist_qa chain is expected. Do NOT pause between these calls to ask the user "should I continue?" or offer options like "full build vs step-by-step." A tool returns a status that requires user input when it genuinely needs one (e.g. confirm_new_project, awaiting promotion). Otherwise chain calls continuously. Brief one-line status updates are fine; permission requests are not.',Zi="mist_init is all-or-nothing for normal users: it registers the cloud project first, scaffolds in a temp directory, then moves the finished app into place only after both steps succeed. If auth, quota, or network registration fails, stop before any app appears locally. Do NOT suggest reusing another app unless the user explicitly asks.",ea="If mist_init fails, stop before any cloud command and explain why. Do NOT default to reusing some other existing app as recovery unless the user explicitly asked to continue that app.",ta='Long-running tools (mist_plan, mist_install, mist_build, mist_qa, mist_deploy) use the fire-and-poll pattern. The first call returns `status: "running"` with a `jobId` or `conversationId`. Every subsequent call with that same id returns the current state \u2014 re-call IMMEDIATELY whenever you see `status: "running"`. DO NOT run `bash sleep`, `setTimeout`, or any wall-clock wait between polls \u2014 Claude Code\'s harness blocks standalone sleeps, and every MCP server poll endpoint holds the connection up to ~10-15s on its side already, returning as soon as state changes. Just call the tool again. Each response is well inside the 60s MCP ceiling. When `status: "complete"` or `"failed"` arrives, the tool is done. Never assume a job stalled because a single call took 10s \u2014 calls return fast, but the work keeps running in the background. Do not ask the user whether to keep polling; polling is how these tools work.',oa="When `mist_plan` returns a `questions[]` array (or `directions[]` for design picking), you MUST ask the user and WAIT for their actual answer before submitting anything back. Use your host's native structured-question UI: `AskUserQuestion` in Claude Code, quick pick in Cursor, `request_user_input` in OpenAI Codex (available in Plan mode \u2014 if Codex returns 'request_user_input is unavailable in <mode> mode', you are in default mode; stop your turn and print the questions as a numbered chat message, then wait for the user's next message). For any host that does not expose a structured question tool, stop your turn immediately, print the questions verbatim as a numbered list with all options visible, and wait \u2014 the user's next message is the answer. The rule across every host: you never submit answers to `mist_plan` in the same turn you received the questions. That is always wrong, regardless of how obvious the `recommended` choice looks, regardless of whether you are inside `/loop`, an autonomous agent run, a scheduled wake-up, or any other non-interactive mode. The `recommended` field is a hint FOR the user, not permission FOR you. Anti-example from a real transcript: the host AI received 7 discovery questions, wrote 'I'll lock in the defaults with Azure OpenAI as the only override,' submitted answers the user never saw, and the user ended up with a plan they didn't choose. That behavior is a regression bug. Each question has `id`/`decisionKey`/`question`/`options[]` (each option: `label`, `description`) plus an optional `recommended` string. Collect the user's actual selections, then submit them back via `mist_plan` with the `conversationId` \u2014 don't ask the user to perform that submission step; that is your job.",Mr="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).",Lr="Read or inspect Mistflow project state. 'get' (default) loads plan progress, env vars, and deploy info \u2014 call before editing. 'update' marks plan steps complete or adds env vars. 'share' creates a forkable template URL. 'landing-designs' / 'integrations' browse curated catalogs (pass presetId / integrationId for full details). 'errors' fetches runtime errors from the deployed app. 'logs' fetches deploy logs. 'deployments' lists history. 'version' reports the installed MCP version + any upgrade available.",Ur="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.",$r="Returns the full Mistflow MCP tool reference. Call ONCE at session start (or whenever unsure which tool to pick) to learn the 14-tool surface, the fire-and-poll pattern, and how to chain end-to-end from mist_plan through mist_qa. Static \u2014 no backend round-trip, safe to call frequently.",Fr="Analyze build errors from a Next.js / TypeScript project. Extracts structured errors with file, line, a human-readable message for the user, and an actionable suggestion. Call with { projectPath } to run `npm run build` and parse the failure; call with { buildOutput } to parse output captured elsewhere (e.g. from a failed mist_build job).",qr="Generate a grayscale wireframe sketch of the planned app so the user can review layout + information hierarchy before any code is written. The server returns a structured spec + a wireframePrompt \u2014 the host AI writes the HTML file to .mistflow/mockups/mockup-<planId>.html and opens it. Pass { planId } for the first iteration, { planId, feedback: '...' } to iterate, { planId, approved: true } to lock the design before mist_init. Iterative by design \u2014 expect 1-3 rounds of feedback."});import{existsSync as To,readFileSync as Gr,writeFileSync as ra,mkdirSync as na}from"fs";import{join as ko,dirname as So}from"path";import{homedir as sa}from"os";import{fileURLToPath as ia}from"url";function xe(){if($t)return $t;try{let t=ia(import.meta.url),e=So(t);for(let r=0;r<6;r++){let o=ko(e,"package.json");if(To(o)){let i=JSON.parse(Gr(o,"utf-8"));if(i.name==="@mistflow-ai/mcp"&&typeof i.version=="string")return $t=i.version,i.version}let n=So(e);if(n===e)break;e=n}}catch{}return $t="0.0.0","0.0.0"}function Ft(t){let e=/^(\d+)\.(\d+)\.(\d+)/.exec(t.trim());return e?[parseInt(e[1],10),parseInt(e[2],10),parseInt(e[3],10)]:null}function zr(t,e){let r=Ft(t),o=Ft(e);if(!r||!o)return 0;for(let n=0;n<3;n++){if(r[n]<o[n])return-1;if(r[n]>o[n])return 1}return 0}function Vr(t,e,r){if(r&&zr(t,r)<0)return"unsupported";if(!e||zr(t,e)>=0)return"none";let n=Ft(t),i=Ft(e);return!n||!i?"none":n[0]<i[0]?"major":n[1]<i[1]?"minor":"patch"}function wt(t){let e=t.get("x-mistflow-mcp-latest")??"",r=t.get("x-mistflow-mcp-min-supported")??"",o=t.get("x-mistflow-mcp-changelog-url")??"";!e&&!r||(ve={latest:e,minSupported:r,changelogUrl:o})}function Jr(){let t=process.env.MISTFLOW_STATE_DIR||ko(sa(),".mistflow");return ko(t,"upgrade-state.json")}function aa(){try{let t=Jr();return To(t)?JSON.parse(Gr(t,"utf-8")):{}}catch{return{}}}function la(t){try{let e=Jr(),r=So(e);To(r)||na(r,{recursive:!0}),ra(e,JSON.stringify(t,null,2)+`
4
+ `,{mode:384})}catch{}}function ca(t){return t==="patch"?7*864e5:t==="minor"?1*864e5:0}function Kr(){if(process.env.MISTFLOW_NO_UPGRADE_CHECK==="1"||!ve)return null;let t=xe();if(t==="0.0.0")return null;let e=Vr(t,ve.latest,ve.minSupported);if(e==="none")return null;if(e==="unsupported")return Wr(e,t,ve);if(Hr)return null;let r=aa(),o=Date.now(),n=ca(e);return r.dismissedForVersion===ve.latest&&e!=="major"||r.lastLatestSeen===ve.latest&&r.lastShownMs&&o-r.lastShownMs<n?null:(Hr=!0,la({...r,lastShownMs:o,lastLatestSeen:ve.latest}),Wr(e,t,ve))}function Wr(t,e,r){let o="npx -y mistflow-ai install",n=r.changelogUrl?`
5
+ What's new: ${r.changelogUrl}`:"";return t==="unsupported"?`
6
6
 
7
7
  ---
8
8
  Mistflow ${e} is no longer supported.
9
- You must upgrade to ${s.latest} or newer to continue:
9
+ You must upgrade to ${r.latest} or newer to continue:
10
10
 
11
- ${n}
11
+ ${o}
12
12
 
13
- After upgrading, restart your AI editor.${o}
13
+ After upgrading, restart your AI editor.${n}
14
14
  ---`:t==="major"?`
15
15
 
16
16
  ---
17
- Mistflow ${s.latest} is available (you have ${e}).
18
- This is a major update. Run \`${n}\` and restart your editor.${o}
17
+ Mistflow ${r.latest} is available (you have ${e}).
18
+ This is a major update. Run \`${o}\` and restart your editor.${n}
19
19
  ---`:t==="minor"?`
20
20
 
21
- --- Mistflow update available: ${e} -> ${s.latest} ---
22
- Run \`${n}\` to upgrade, then restart your editor.${o}`:`
21
+ --- Mistflow update available: ${e} -> ${r.latest} ---
22
+ Run \`${o}\` to upgrade, then restart your editor.${n}`:`
23
23
 
24
- (Mistflow ${s.latest} is out, you have ${e}. Run \`${n}\` when convenient.)`}function Pn(){let t=ve(),e=we??{latest:"",minSupported:"",changelogUrl:""},s=Gs(t,e.latest,e.minSupported);return{current:t,latest:e.latest,minSupported:e.minSupported,severity:s,upgradeCmd:"npx -y mistflow-ai install",changelogUrl:e.changelogUrl,backendSignalReceived:we!==null}}var Ut,we,zs,wt=_(()=>{"use strict";Ut=null;we=null;zs=!1});var _n={};$t(_n,{closeBrowser:()=>Tn,getIsolatedContext:()=>qi,getPage:()=>In,getSnapshot:()=>vt,takeScreenshot:()=>xt});async function Ys(){try{return await import("playwright")}catch{throw new Error("Playwright is not installed. Run: cd packages/mcp-server && pnpm add playwright && npx playwright install chromium")}}async function Fi(){let t=await Ys();return(!xe||!xe.isConnected())&&(xe=await t.chromium.launch({headless:!0})),Me&&(await Me.close().catch(()=>{}),Me=null),Me=await xe.newContext({viewport:{width:1280,height:720},deviceScaleFactor:1}),_e=await Me.newPage(),_e}async function In(){return _e&&!_e.isClosed()?_e:(Ft||(Ft=Fi().finally(()=>{Ft=null})),Ft)}async function Tn(){_e&&!_e.isClosed()&&await _e.close().catch(()=>{}),Me&&await Me.close().catch(()=>{}),xe?.isConnected()&&await xe.close().catch(()=>{}),_e=null,Me=null,xe=null}async function qi(){let t=await Ys();(!xe||!xe.isConnected())&&(xe=await t.chromium.launch({headless:!0}));let e=await xe.newContext({viewport:{width:1280,height:720},deviceScaleFactor:1}),s=await e.newPage();return{context:e,page:s}}async function vt(t){return await t.locator("body").ariaSnapshot()}async function xt(t,e=!1){return await t.screenshot({fullPage:e,type:"png"})}var xe,Me,_e,Ft,qt=_(()=>{"use strict";xe=null,Me=null,_e=null,Ft=null;process.once("SIGTERM",()=>{Tn().finally(()=>process.exit(0))});process.once("SIGINT",()=>{Tn().finally(()=>process.exit(0))})});function c(t,e=!1){let s=t;try{let n=Js();n&&(s=t+n)}catch{}return{content:[{type:"text",text:s}],isError:e}}function Re(t){return c(`This is not a Mistflow project (no mistflow.json found at ${t}).
24
+ (Mistflow ${r.latest} is out, you have ${e}. Run \`${o}\` when convenient.)`}function Po(){let t=xe(),e=ve??{latest:"",minSupported:"",changelogUrl:""},r=Vr(t,e.latest,e.minSupported);return{current:t,latest:e.latest,minSupported:e.minSupported,severity:r,upgradeCmd:"npx -y mistflow-ai install",changelogUrl:e.changelogUrl,backendSignalReceived:ve!==null}}var $t,ve,Hr,vt=S(()=>{"use strict";$t=null;ve=null;Hr=!1});var Ao={};Ut(Ao,{closeBrowser:()=>Co,getIsolatedContext:()=>pa,getPage:()=>Io,getSnapshot:()=>xt,takeScreenshot:()=>kt});async function Yr(){try{return await import("playwright")}catch{throw new Error("Playwright is not installed. Run: cd packages/mcp-server && pnpm add playwright && npx playwright install chromium")}}async function da(){let t=await Yr();return(!ke||!ke.isConnected())&&(ke=await t.chromium.launch({headless:!0})),Le&&(await Le.close().catch(()=>{}),Le=null),Le=await ke.newContext({viewport:{width:1280,height:720},deviceScaleFactor:1}),_e=await Le.newPage(),_e}async function Io(){return _e&&!_e.isClosed()?_e:(qt||(qt=da().finally(()=>{qt=null})),qt)}async function Co(){_e&&!_e.isClosed()&&await _e.close().catch(()=>{}),Le&&await Le.close().catch(()=>{}),ke?.isConnected()&&await ke.close().catch(()=>{}),_e=null,Le=null,ke=null}async function pa(){let t=await Yr();(!ke||!ke.isConnected())&&(ke=await t.chromium.launch({headless:!0}));let e=await ke.newContext({viewport:{width:1280,height:720},deviceScaleFactor:1}),r=await e.newPage();return{context:e,page:r}}async function xt(t){return await t.locator("body").ariaSnapshot()}async function kt(t,e=!1){return await t.screenshot({fullPage:e,type:"png"})}var ke,Le,_e,qt,Bt=S(()=>{"use strict";ke=null,Le=null,_e=null,qt=null;process.once("SIGTERM",()=>{Co().finally(()=>process.exit(0))});process.once("SIGINT",()=>{Co().finally(()=>process.exit(0))})});function c(t,e=!1){let r=t;try{let o=Kr();o&&(r=t+o)}catch{}return{content:[{type:"text",text:r}],isError:e}}function Re(t){return c(`This is not a Mistflow project (no mistflow.json found at ${t}).
25
25
 
26
26
  Mistflow creates new projects from scratch \u2014 it doesn't work inside existing codebases.
27
27
 
@@ -30,23 +30,23 @@ To get started:
30
30
  2. Call mist_init({ planId, path: "<absolute-path>" })
31
31
  3. Call mist_install({ projectPath }), then mist_implement({ projectPath })
32
32
 
33
- If you want to deploy an existing project, use your framework's deploy tools directly.`,!0)}async function Ks(t,e){try{let{getPage:s,takeScreenshot:n}=await Promise.resolve().then(()=>(qt(),_n)),o=await s();await o.goto(t,{waitUntil:"domcontentloaded",timeout:15e3}),await o.waitForLoadState("networkidle").catch(()=>{});let i=await n(o,!1);return{content:[{type:"text",text:e},{type:"image",data:i.toString("base64"),mimeType:"image/png"}]}}catch{return c(e)}}var K=_(()=>{"use strict";wt()});import{readFileSync as Qs,existsSync as Rn,writeFileSync as Bi,mkdirSync as zi,renameSync as Hi,unlinkSync as Wi}from"fs";import{join as Cn,dirname as Gi}from"path";import{homedir as Vi}from"os";import{randomBytes as Ji}from"crypto";function Xs(){return Cn(Vi(),".mistflow","credentials.json")}function Bt(){return(process.env.MISTFLOW_API_URL||"https://api.mistflow.ai").replace(/\/+$/,"")}function Zs(){let t=Xs();if(!Rn(t))return null;try{let e=JSON.parse(Qs(t,"utf-8"));return typeof e.apiKey=="string"?{[Yi]:e}:e}catch{return null}}function tt(){let t=process.env.MISTFLOW_API_KEY;if(t)return{ok:!0,creds:{apiKey:t,orgId:"",orgSlug:"env"}};let e=Zs();if(!e)return{ok:!1,reason:"missing"};let s=Bt(),n=e[s];return n&&typeof n.apiKey=="string"&&n.apiKey&&typeof n.orgId=="string"?{ok:!0,creds:n}:{ok:!1,reason:"missing"}}function An(t){let e=Xs(),s=Gi(e);Rn(s)||zi(s,{recursive:!0});let n=Zs()??{},o=Bt();n[o]=t;let i=Cn(s,`.credentials.tmp.${Ji(8).toString("hex")}`);try{Bi(i,JSON.stringify(n,null,2)+`
34
- `,{mode:384}),Hi(i,e)}catch(r){try{Wi(i)}catch{}throw r}}function ee(){return tt().ok}function $e(t){let e=Cn(t,"mistflow.json");if(!Rn(e))return null;try{return JSON.parse(Qs(e,"utf-8"))}catch{return null}}var Yi,nt=_(()=>{"use strict";Yi="https://api.mistflow.ai"});var ro={};$t(ro,{MistflowApiError:()=>L,addDomain:()=>Mn,checkAuth:()=>Zi,checkAuthDetailed:()=>so,checkSubdomain:()=>Nn,createDeployment:()=>ta,createProject:()=>St,deleteEnvVar:()=>Bn,discoverDecisions:()=>na,downloadSource:()=>ra,downloadSourceWithToken:()=>oo,fetchDesignDirections:()=>Dn,fetchModule:()=>Yn,fetchPlanConversation:()=>Ht,fetchScaffold:()=>Vn,fetchStepContext:()=>Jn,forkTemplate:()=>Qn,generatePlan:()=>On,getAuthHeaders:()=>Ke,getBaseUrl:()=>te,getDashboardUrl:()=>Ki,getDbCredentials:()=>sa,getDeployLogs:()=>zn,getDeploymentStatus:()=>Pt,getProject:()=>ea,getProjectErrors:()=>Hn,getSeedInfo:()=>Un,getSiteUrl:()=>st,getTemplate:()=>Kn,hasCredentialsOnDisk:()=>ee,hasLocalCredentials:()=>no,listDeployments:()=>rt,listDomains:()=>Ue,listEnvVars:()=>Fn,markLocalSetupDone:()=>ia,modifyPlan:()=>Wt,pingBackend:()=>En,promotePreview:()=>Wn,redeployProject:()=>oa,removeDomain:()=>$n,rollbackDeployment:()=>Gn,shareProject:()=>Xn,uploadAndDeploy:()=>jn,uploadQAResults:()=>Ln,upsertEnvVar:()=>qn,verifyDomain:()=>Gt});function te(){return Bt()}function st(){let t=process.env.MISTFLOW_API_URL;if(t){if(t.includes("mistflow.localhost"))return t.replace("api.mistflow","mistflow");if(t.includes("localhost:9100"))return t.replace(":9100",":9102")}return"https://mistflow.ai"}function Ki(){let t=process.env.MISTFLOW_API_URL;if(t){if(t.includes("mistflow.localhost"))return t.replace("api.mistflow","app.mistflow");if(t.includes("localhost:9100"))return t.replace(":9100",":9101")}return"https://app.mistflow.ai"}function Ke(){let t=tt();if(!t.ok)throw new L("auth_missing","No Mistflow credentials found.",401);return Qi(t.creds)}function Qi(t){return{Authorization:`ApiKey ${t.apiKey}`,"Content-Type":"application/json","X-Mistflow-MCP-Version":ve()}}function ot(t){try{bt(t.headers)}catch{}}async function En(){try{let t=await fetch(`${te()}/health`,{method:"GET",signal:AbortSignal.timeout(5e3)});ot(t)}catch{}}async function to(t,e,s,n){for(let o=0;o<2;o++)try{return await fetch(t,{...e,signal:AbortSignal.timeout(s)})}catch(i){let r=i instanceof Error&&i.name==="TimeoutError",a=i instanceof TypeError;if(n&&o===0&&(r||a)){console.error(`[api] Retrying ${t} after ${r?"timeout":"network error"}`);continue}throw i}throw new Error("fetchWithRetry: exhausted retries")}async function kt(t){let e=null;try{e=await t.json()}catch{e=null}let s=e&&typeof e.code=="string"?e.code:void 0,n=e&&typeof e.message=="string"?e.message:e&&typeof e.detail=="string"?e.detail:t.statusText||"Request failed";if(s)return new L(s,n,t.status,e?.details);let o=t.status;return o===401?new L("auth_invalid",n,o):o===403?new L("permission_denied",n,o):o===404?new L("not_found",n,o):o===409?new L("conflict",n,o):o===422?new L("validation_error",n,o):o===429?new L("rate_limited",n,o):o>=500?new L("server_error",t.statusText||"Internal server error",o):new L("client_error",n,o)}async function F(t,e={}){let s=Ke(),{timeoutMs:n,idempotent:o,...i}=e,r=n??3e4,a=(i.method??"GET").toUpperCase(),l=o??(a==="GET"||a==="HEAD"),d;try{d=await to(`${te()}${t}`,{...i,headers:{...s,...i.headers}},r,l)}catch(h){throw h instanceof Error&&h.name==="TimeoutError"?new L("network_error","Request timed out. Try again in a moment."):new L("network_error","Cannot reach Mistflow servers. Check your network.")}if(ot(d),!d.ok)throw await kt(d);return d.json()}function no(){return tt().ok}async function Zi(){return(await so()).ok}async function so(){if(zt!==null&&Date.now()<eo)return zt?{ok:!0}:{ok:!1,reason:"no_credentials"};if(!no())return{ok:!1,reason:"no_credentials"};try{return await F("/api/org"),zt=!0,eo=Date.now()+Xi,{ok:!0}}catch(t){if(zt=null,!(t instanceof L))return{ok:!1,reason:"network_error"};switch(t.code){case"auth_missing":case"auth_revoked":return{ok:!1,reason:"no_credentials"};case"auth_expired":case"auth_invalid":case"auth_org_not_found":return{ok:!1,reason:"expired"};case"network_error":return{ok:!1,reason:"network_error"};case"rate_limited":case"server_error":case"upstream_error":return{ok:!1,reason:"server_error"};default:return{ok:!1,reason:"server_error"}}}}async function ea(t){return F(`/api/projects/${encodeURIComponent(t)}`)}async function Nn(t){return F(`/api/projects/check-subdomain?name=${encodeURIComponent(t)}`)}async function St(t,e,s="neon",n){return F("/api/projects",{method:"POST",body:JSON.stringify({name:t,template:e,db_provider:s,requested_subdomain:n})})}async function ta(t,e){return F("/api/deploy",{method:"POST",body:JSON.stringify({project_id:t,build_output_url:e})})}async function jn(t,e,s="production",n,o,i,r){let{readFileSync:a}=await import("fs"),{basename:l}=await import("path"),d=tt();if(!d.ok)throw new L("auth_missing","No Mistflow credentials found.",401);let h=d.creds,m=a(e),p=new Blob([m],{type:"application/gzip"}),y=new FormData;if(y.append("project_id",t),y.append("build",p,l(e)),s!=="production"&&y.append("environment",s),n&&y.append("admin_email",n),o&&y.append("schema_pushed","true"),i){let{existsSync:f}=await import("fs");if(f(i)){let k=a(i),R=new Blob([k],{type:"application/gzip"});y.append("source",R,"source.tar.gz")}}r&&y.append("git_commit_sha",r);let v;try{v=await fetch(`${te()}/api/deploy/upload`,{method:"POST",headers:{Authorization:`ApiKey ${h.apiKey}`,"X-Mistflow-MCP-Version":ve()},body:y,signal:AbortSignal.timeout(3e5)})}catch{throw new L("network_error","Cannot reach Mistflow servers. Check your network.")}if(ot(v),!v.ok)throw await kt(v);let b=await v.json();return{...b,id:b.deployment_id}}async function Pt(t){return F(`/api/deploy/${encodeURIComponent(t)}/status`)}async function Dn(t){return F(`/api/plan/design-directions/${encodeURIComponent(t)}`,{timeoutMs:15e3})}async function Ht(t,e){let s=e?.waitSeconds??20,n=s>0?`?wait=${s}`:"",o=Math.min(6e4,(s+5)*1e3);return F(`/api/plan/conversations/${encodeURIComponent(t)}${n}`,{timeoutMs:o})}async function On(t,e){let s={description:t,conversation_id:e?.conversationId,answers:e?.answers};e?.autonomous&&(s.autonomous=!0),e?.language&&e.language.toLowerCase()!=="english"&&(s.language=e.language),e?.designConversationId&&(s.design_conversation_id=e.designConversationId),e?.designDirection&&(s.design_direction=e.designDirection);let n=e?.conversationId||e?.answers||e?.designConversationId?18e4:6e4;return F("/api/plan",{method:"POST",body:JSON.stringify(s),timeoutMs:n,idempotent:!1})}async function Wt(t,e){return F("/api/plan/modify",{method:"POST",body:JSON.stringify({existing_plan:t,modification:e})})}async function na(t){return F("/api/plan/discover",{method:"POST",body:JSON.stringify({description:t})})}async function Mn(t,e){return F(`/api/projects/${encodeURIComponent(t)}/domains`,{method:"POST",body:JSON.stringify({domain:e})})}async function Ue(t){return F(`/api/projects/${encodeURIComponent(t)}/domains`)}async function Gt(t,e){return F(`/api/projects/${encodeURIComponent(t)}/domains/${encodeURIComponent(e)}/verify`)}async function $n(t,e){return F(`/api/projects/${encodeURIComponent(t)}/domains/${encodeURIComponent(e)}`,{method:"DELETE"})}async function sa(t){return F(`/api/projects/${encodeURIComponent(t)}/db-credentials`)}async function Un(t){try{return await F(`/api/projects/${encodeURIComponent(t)}/test-accounts`)}catch(e){return e instanceof L&&(e.statusCode===404||e.code==="not_found")||console.error("[api] Failed to fetch seed info:",e instanceof Error?e.message:e),null}}async function Ln(t,e){try{return await F(`/api/deploy/${encodeURIComponent(t)}/qa-results`,{method:"POST",body:JSON.stringify(e)})}catch(s){return console.error("[api] Failed to upload QA results:",s instanceof Error?s.message:s),null}}async function Fn(t){return F(`/api/projects/${encodeURIComponent(t)}/env`)}async function qn(t,e,s,n){return F(`/api/projects/${encodeURIComponent(t)}/env`,{method:"PUT",body:JSON.stringify({key:e,value:s,category:n?.category??"custom",description:n?.description,setup_url:n?.setupUrl})})}async function Bn(t,e){return F(`/api/projects/${encodeURIComponent(t)}/env/${encodeURIComponent(e)}`,{method:"DELETE"})}async function zn(t){return F(`/api/deploy/${encodeURIComponent(t)}/logs`)}async function Hn(t,e="7d"){return F(`/api/projects/${encodeURIComponent(t)}/errors?period=${encodeURIComponent(e)}`)}async function rt(t){return F(`/api/projects/${encodeURIComponent(t)}/deployments`)}async function oa(t){return F(`/api/deploy/${encodeURIComponent(t)}/redeploy`,{method:"POST"})}async function Wn(t,e){let s=new URLSearchParams({preview_deployment_id:e});return F(`/api/deploy/${encodeURIComponent(t)}/promote`,{method:"POST",body:s.toString(),headers:{"Content-Type":"application/x-www-form-urlencoded"}})}async function Gn(t){return F(`/api/deploy/${encodeURIComponent(t)}/rollback`,{method:"POST"})}async function ra(t,e){let s=tt();if(!s.ok)throw new L("auth_missing","Not authenticated.",401);let n=s.creds,o=await fetch(`${te()}/api/deploy/${encodeURIComponent(t)}/source`,{headers:{Authorization:`ApiKey ${n.apiKey}`,"X-Mistflow-MCP-Version":ve()},signal:AbortSignal.timeout(12e4)});if(ot(o),!o.ok)throw await kt(o);let{writeFileSync:i}=await import("fs"),r=Buffer.from(await o.arrayBuffer());i(e,r)}async function Vt(t,e){let{timeoutMs:s,idempotent:n,...o}=e??{},i=s??3e4,r=(o.method??"GET").toUpperCase(),a=n??(r==="GET"||r==="HEAD"),l;try{l=await to(`${te()}${t}`,{headers:{"Content-Type":"application/json","X-Mistflow-MCP-Version":ve()},...o},i,a)}catch(d){throw d instanceof Error&&d.name==="TimeoutError"?new L("network_error","Request timed out. Try again in a moment."):new L("network_error","Cannot reach Mistflow servers. Check your network.")}if(ot(l),!l.ok)throw await kt(l);return l.json()}async function Vn(t="nextjs"){return Vt(`/api/scaffold/${encodeURIComponent(t)}`)}async function Jn(t,e){return Vt(`/api/scaffold/${encodeURIComponent(t)}/context?step_type=${encodeURIComponent(e)}`)}async function Yn(t,e,s,n){return Vt(`/api/scaffold/${encodeURIComponent(t)}/module`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({type:"crud",entity:e,fields:s,entity_plural:n})})}async function Kn(t){return Vt(`/api/templates/${encodeURIComponent(t)}`)}async function Qn(t){return F(`/api/templates/${encodeURIComponent(t)}/fork`,{method:"POST"})}async function oo(t,e,s){let n;try{n=await fetch(`${te()}/api/deploy/${encodeURIComponent(t)}/fork-source?fork_token=${encodeURIComponent(e)}`,{headers:{"X-Mistflow-MCP-Version":ve()},signal:AbortSignal.timeout(12e4)})}catch{throw new L("network_error","Cannot reach Mistflow servers. Check your network.")}if(ot(n),!n.ok)throw n.status===401?new L("validation_error","Fork token expired or invalid. Re-open the project in the dashboard to get a fresh token.",n.status):await kt(n);let{writeFileSync:o}=await import("fs"),i=Buffer.from(await n.arrayBuffer());o(s,i)}async function ia(t){await F(`/api/projects/${encodeURIComponent(t)}`,{method:"PATCH",body:JSON.stringify({local_setup_done:!0})})}async function Xn(t,e){return F(`/api/projects/${encodeURIComponent(t)}/share`,{method:"POST",body:JSON.stringify({is_template:e?.isTemplate??!1,template_description:e?.description})})}var L,zt,eo,Xi,ue=_(()=>{"use strict";nt();wt();L=class extends Error{constructor(s,n,o,i){super(n);this.code=s;this.statusCode=o;this.details=i;this.name="MistflowApiError"}get isAuth(){return this.code.startsWith("auth_")}get isTransient(){return this.code==="server_error"||this.code==="upstream_error"||this.code==="network_error"||this.code==="rate_limited"}};zt=null,eo=0,Xi=300*1e3});import{z as Zn}from"zod";import{platform as aa}from"os";import{execFile as io}from"child_process";function ca(t){return"error"in t}function lo(t){return new Promise(e=>setTimeout(e,t))}function da(t){return new Promise(e=>{let s=aa();s==="win32"?io("cmd.exe",["/c","start","",t],n=>{n&&console.error("Could not open browser:",n.message),e(!n)}):io(s==="darwin"?"open":"xdg-open",[t],o=>{o&&console.error("Could not open browser:",o.message),e(!o)}),setTimeout(()=>e(!1),5e3)})}async function ao(t,e,s,n){let o=s,i=n.sleep??lo;for(let r=0;r<e;r++){await i(o);let a;try{let d=await n.fetch(`${te()}/auth/poll`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:t})});if(!d.ok)continue;a=await d.json()}catch{continue}if(ca(a))switch(a.error){case"authorization_pending":continue;case"slow_down":o+=5e3;continue;case"expired_token":return c("The sign-in link expired. Run mist_setup again to get a new code.",!0);case"access_denied":return c("Sign-in was cancelled. Run mist_setup again to try again.",!0);case"already_exchanged":return c("This sign-in link was already used. Run mist_setup again to get a new code.",!0)}let l=a.email||a.org_name||a.org_slug;return An({apiKey:a.api_key,apiKeyId:a.api_key_id,apiKeyName:a.api_key_name,orgId:a.org_id,orgSlug:a.org_slug,email:a.email}),c(`Connected to Mistflow as ${l}. You are ready to build and deploy.`)}return null}async function ua(t,e=pa){let s=t;if(s?.apiKey)try{let r=await e.fetch(`${te()}/api/org`,{headers:{Authorization:`ApiKey ${s.apiKey}`}});if(!r.ok)return c("Invalid API key. Check the key and try again.",!0);let a=await r.json();return An({apiKey:s.apiKey,orgId:a.id,orgSlug:a.slug}),c(`Connected to Mistflow as ${a.slug} via API key. You are ready to build and deploy.`)}catch{return c("Cannot reach Mistflow servers. Check your internet connection.",!0)}if(s?.deviceCode){let r=await ao(s.deviceCode,6,5e3,e);return r||c(JSON.stringify({status:"pending",deviceCode:s.deviceCode,instruction:"The user hasn't approved yet. Wait ~15 seconds and call mist_setup again with the same deviceCode."}))}let n;try{let r=await e.fetch(`${te()}/auth/device`,{method:"POST",headers:{"Content-Type":"application/json"}});if(!r.ok)return c("Cannot reach Mistflow servers. Check your internet connection.",!0);n=await r.json()}catch{return c("Cannot reach Mistflow servers. Check your internet connection.",!0)}let o=`${n.verification_uri}?code=${n.user_code}`;console.error(`
35
- Sign in at: ${o}
36
- Your code: ${n.user_code}
37
- `);try{await e.openBrowser(o)}catch{}let i=await ao(n.device_code,6,5e3,e);return i||c(JSON.stringify({status:"pending",deviceCode:n.device_code,signInUrl:o,userCode:n.user_code,instruction:"The user hasn't approved yet. Wait ~15 seconds, then call mist_setup again with deviceCode='"+n.device_code+"' to check if they approved."}))}var la,pa,co,po=_(()=>{"use strict";Oe();K();ue();nt();la=Zn.object({apiKey:Zn.string().optional().describe("API key (mist_...) for headless auth. Skips the device code flow entirely. Generate one at app.mistflow.ai/mcp-keys."),deviceCode:Zn.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.")});pa={fetch:globalThis.fetch,openBrowser:da,sleep:lo};co={name:"mist_setup",description:Os,inputSchema:la,handler:t=>ua(t)}});import{existsSync as ma,readFileSync as ha}from"fs";function uo(t){let e=new Set;if(!ma(t))return e;let s=ha(t,"utf-8");for(let n of s.split(`
38
- `)){let o=n.trim();if(!o||o.startsWith("#"))continue;let i=o.indexOf("=");if(i>0){let r=o.slice(0,i).trim(),a=o.slice(i+1).trim();a&&a!=='""'&&a!=="''"&&e.add(r)}}return e}var mo=_(()=>{"use strict"});var _t={};$t(_t,{emptyState:()=>Tt,fetchRemoteState:()=>wa,fuzzyMatch:()=>xa,getLocalStatePath:()=>es,readLocalState:()=>ba,syncRemoteState:()=>va,writeLocalState:()=>It});import{existsSync as ho,readFileSync as ga,writeFileSync as fa,mkdirSync as ya}from"fs";import{join as go}from"path";function es(t){return go(t,".mistflow","state.json")}function ba(t){let e=es(t);if(!ho(e))return null;try{return JSON.parse(ga(e,"utf-8"))}catch{return null}}function It(t,e){let s=go(t,".mistflow");ho(s)||ya(s,{recursive:!0}),fa(es(t),JSON.stringify(e,null,2)+`
39
- `)}function Tt(t,e){return{projectId:t,name:e,template:"",features:[],dbSchema:[],deployCount:0,decisions:[],provenance:[]}}async function wa(t){let e;try{e=Ke()}catch{return null}try{let s=await fetch(`${te()}/api/projects/${encodeURIComponent(t)}/state`,{headers:{...e,"Content-Type":"application/json","X-Mistflow-MCP-Version":ve()}});try{bt(s.headers)}catch{}return s.ok?await s.json():null}catch{return null}}async function va(t,e){let s;try{s=Ke()}catch{return!1}try{let n=await fetch(`${te()}/api/projects/${encodeURIComponent(t)}/state`,{method:"PUT",headers:{...s,"Content-Type":"application/json","X-Mistflow-MCP-Version":ve()},body:JSON.stringify(e)});try{bt(n.headers)}catch{}return n.ok}catch{return!1}}function xa(t,e){let s=t.toLowerCase(),n=e.toLowerCase();return n.includes(s)||s.includes(n)?!0:s.split(/\s+/).some(i=>i.length>=3&&n.includes(i))}var Qe=_(()=>{"use strict";ue();wt()});var ts={};$t(ts,{ensureBackendRegistered:()=>Ra,ensureShadcnComponents:()=>Ca});import{existsSync as yo,readFileSync as ka,writeFileSync as Sa}from"fs";import{join as Jt}from"path";import{spawn as Pa}from"child_process";function Ia(t,e,s,n,o){return new Promise(i=>{let r=Pa(t,e,{cwd:s,stdio:["pipe","pipe","pipe"],timeout:n,...o?{env:{...process.env,...o}}:{}}),a="",l="";r.stdout?.on("data",d=>{a+=d.toString()}),r.stderr?.on("data",d=>{l+=d.toString()}),r.on("close",d=>i({success:d===0,stdout:a,stderr:l})),r.on("error",d=>i({success:!1,stdout:a,stderr:l+d.message}))})}function Ta(t){let e=Jt(t,"mistflow.json");if(!yo(e))return null;try{return JSON.parse(ka(e,"utf-8"))}catch{return null}}function _a(t,e){Sa(Jt(t,"mistflow.json"),JSON.stringify(e,null,2))}async function fo(t,e){try{let s=e.features,n=e.steps,o={plan:e};Array.isArray(s)&&s.length>0&&(o.features=s.map(i=>i.name)),Array.isArray(n)&&n.length>0&&(o.provenance=n.map(i=>({feature:i.name??i.title??`Step ${i.number??"?"}`,user_intent:(i.description??"").slice(0,500),decisions:"Seeded from plan",tradeoffs:"",files_affected:[]}))),await fetch(`${te()}/api/projects/${encodeURIComponent(t)}/state`,{method:"PUT",headers:{...Ke(),"Content-Type":"application/json"},body:JSON.stringify(o)})}catch(s){console.error("[self-heal] state sync failed:",s instanceof Error?s.message:String(s))}}async function Ra(t,e={}){let s=Ta(t);if(s){if(!ee())return s.projectId;if(!s.projectId)try{let o=s.plan?.requestedSubdomain,i=await St(s.name,void 0,s.dbProvider??"neon",o);return s.projectId=i.id,_a(t,s),It(t,Tt(i.id,s.name)),s.plan&&await fo(i.id,s.plan),console.error(`[self-heal] registered project ${i.id.slice(0,8)} with backend`),i.id}catch(n){console.error("[self-heal] createProject failed:",n instanceof Error?n.message:String(n));return}return e.forceSync&&s.plan&&s.projectId&&await fo(s.projectId,s.plan),s.projectId}}async function Ca(t,e){let s=["button","card","input","label","form","dialog","table","dropdown-menu","badge","separator","skeleton","sheet","tabs","avatar","select","textarea","checkbox","switch","tooltip","popover","sonner"],n=e&&e.length>0?e:s,o=Jt(t,"components","ui"),i=[],r=[];for(let d of n)yo(Jt(o,`${d}.tsx`))?i.push(d):r.push(d);if(r.length===0)return{installed:[],alreadyPresent:i};let a={NPM_CONFIG_LEGACY_PEER_DEPS:"true"},l=await Ia("npx",["--yes","shadcn@latest","add","-y","-o",...r],t,18e4,a);return l.success?{installed:r,alreadyPresent:i}:{installed:[],alreadyPresent:i,failed:`shadcn add failed for: ${r.join(", ")}. ${l.stderr.slice(-300)}`.trim()}}var ns=_(()=>{"use strict";ue();Qe()});import{z as Le}from"zod";import{resolve as Aa,join as bo}from"path";import{existsSync as Ea,readFileSync as wo,writeFileSync as Na}from"fs";var ja,vo,xo=_(()=>{"use strict";K();mo();ja=Le.object({action:Le.enum(["get","update"]).default("get").describe("'get' reads current project state. 'update' modifies it."),projectPath:Le.string().optional().describe("Path to the project directory (default: current working directory)"),completedStep:Le.number().optional().describe("(update only) Mark a plan step as completed by step number"),addEnvVar:Le.object({key:Le.string(),description:Le.string().optional(),setupUrl:Le.string().optional()}).optional().describe("(update only) Add a required env var to the project manifest")}),vo={name:"mist_state",description:"Read or update project state in mistflow.json. Use action='get' to load plan progress, env var status, and deploy info. Use action='update' to mark plan steps complete or add required env vars. Called internally by mist_project; host AIs should use mist_project directly.",inputSchema:ja,handler:async t=>{let e=t,s=Aa(e.projectPath??process.cwd()),n=bo(s,"mistflow.json");if(!Ea(n))return Re(s);let o;try{o=JSON.parse(wo(n,"utf-8"))}catch{return c("Failed to parse mistflow.json.",!0)}if(e.action==="get"){if(!o.projectId)try{let{ensureBackendRegistered:f}=await Promise.resolve().then(()=>(ns(),ts));await f(s)&&(o=JSON.parse(wo(n,"utf-8")))}catch{}let a=o.plan,l=a?.steps?.filter(f=>f.status==="completed").length??0,d=a?.steps?.length??0,h=uo(bo(s,".env.local")),m=o.env?.required?Object.entries(o.env.required).map(([f,k])=>({name:f,description:k?.description,configured:h.has(f)})):[];o.projectId&&Promise.resolve().then(()=>(Qe(),_t)).then(({fetchRemoteState:f})=>f(o.projectId)).catch(()=>{});let p=[`Project: ${o.name}`];if(a){p.push(`Plan: ${a.summary??a.name??"unnamed"} \u2014 ${l}/${d} steps complete`);for(let f of a.steps){let k=f.status==="completed"?"\u2713":f.status==="in_progress"?"\u2192":" ";p.push(` [${k}] ${f.number}. ${f.name}`)}}let y=m.filter(f=>!f.configured);y.length>0&&p.push(`Missing env vars: ${y.map(f=>f.name).join(", ")}`),o.deploy?.url?p.push(`Deployed: ${o.deploy.url} (${o.deploy.count??0} deploys)`):p.push("Not deployed yet");let v=[],b=a?.steps?.find(f=>f.status!=="completed");return b?v.push(`NEXT: Call mist_implement({ projectPath }) to work on step ${b.number} (${b.name}).`):a&&l===d&&(o.deploy?.url||v.push("NEXT: All steps complete. Call mist_build({ projectPath }) then mist_deploy({ action: 'deploy', projectPath }). Do NOT ask the user \u2014 chain straight through.")),y.length>0&&v.push(`Missing env vars in .env.local: ${y.map(f=>f.name).join(", ")}`),c(JSON.stringify({name:o.name,projectId:o.projectId,planProgress:a?{name:a.name,summary:a.summary,totalSteps:d,completedSteps:l,steps:a.steps}:null,envStatus:m,deploy:o.deploy??null,contextMessage:p.join(`
40
- `),nextSteps:v}))}let i=[];if(e.completedStep!==void 0){let a=o.plan;if(a?.steps){let l=a.steps.findIndex(d=>d.number===e.completedStep);if(l===-1)return c(`Step ${e.completedStep} not found in the plan.`,!0);a.steps[l].status="completed",i.push(`Step ${e.completedStep} marked as completed`)}}e.addEnvVar&&(o.env||(o.env={required:{}}),o.env.required||(o.env.required={}),o.env.required[e.addEnvVar.key]={description:e.addEnvVar.description,setupUrl:e.addEnvVar.setupUrl},i.push(`Added required env var: ${e.addEnvVar.key}`)),Na(n,JSON.stringify(o,null,2)+`
41
- `),o.projectId&&Promise.resolve().then(()=>(Qe(),_t)).then(async({readLocalState:a,syncRemoteState:l})=>{let d=a(s);d&&await l(o.projectId,d)}).catch(()=>{});let r=[];if(e.completedStep!==void 0){let l=o.plan?.steps?.find(d=>d.status!=="completed");l?r.push(`NEXT: Call mist_implement({ projectPath }) to work on step ${l.number} (${l.name}). Do this now.`):r.push("NEXT: All steps complete. Call mist_build({ projectPath }) then mist_deploy({ action: 'deploy', projectPath }) to deploy the app. Do NOT suggest localhost.")}return e.addEnvVar&&(r.push(`Add ${e.addEnvVar.key} to your .env.local file`),e.addEnvVar.setupUrl&&r.push(`Get the value from: ${e.addEnvVar.setupUrl}`)),c(JSON.stringify({updated:!0,changes:i,message:i.length>0?`Project state saved. ${i.join(". ")}.`:"No changes made.",nextSteps:r.length>0?r:void 0}))}}});function Yt(t){let e=it.find(n=>n.id===t);if(e)return e;let s=t.toLowerCase().replace(/[^a-z0-9]/g,"");return it.find(n=>{let o=n.title.toLowerCase().replace(/[^a-z0-9]/g,"");return o===s||o.includes(s)||s.includes(o)})}function ko(t){return ss[t]}function os(t){return t?it.filter(e=>e.category.toLowerCase()===t.toLowerCase()):it}function So(t){let e=t??it;if(e.length===0)return"Landing page presets have been replaced by the tone-based system. The landing page tone is now auto-selected based on your app's description during planning.";let s={};for(let o of e){s[o.category]||(s[o.category]=[]);let i=ss[o.id],r=i?` \u2014 ${i.description}`:"";s[o.category].push(`${o.id} \u2014 "${o.title}"${r}`)}let n=[];for(let[o,i]of Object.entries(s))n.push(`**${o}**:
42
- ${i.map(r=>` \u2022 ${r}`).join(`
43
- `)}`);return n.join(`
33
+ If you want to deploy an existing project, use your framework's deploy tools directly.`,!0)}async function Qr(t,e){try{let{getPage:r,takeScreenshot:o}=await Promise.resolve().then(()=>(Bt(),Ao)),n=await r();await n.goto(t,{waitUntil:"domcontentloaded",timeout:15e3}),await n.waitForLoadState("networkidle").catch(()=>{});let i=await o(n,!1);return{content:[{type:"text",text:e},{type:"image",data:i.toString("base64"),mimeType:"image/png"}]}}catch{return c(e)}}var X=S(()=>{"use strict";vt()});import{readFileSync as Xr,existsSync as _o,writeFileSync as ua,mkdirSync as ma,renameSync as ha,unlinkSync as ga}from"fs";import{join as Ro,dirname as fa}from"path";import{homedir as ya}from"os";import{randomBytes as ba}from"crypto";function Zr(){return Ro(ya(),".mistflow","credentials.json")}function zt(){return(process.env.MISTFLOW_API_URL||"https://api.mistflow.ai").replace(/\/+$/,"")}function en(){let t=Zr();if(!_o(t))return null;try{let e=JSON.parse(Xr(t,"utf-8"));return typeof e.apiKey=="string"?{[wa]:e}:e}catch{return null}}function ot(){let t=process.env.MISTFLOW_API_KEY;if(t)return{ok:!0,creds:{apiKey:t,orgId:"",orgSlug:"env"}};let e=en();if(!e)return{ok:!1,reason:"missing"};let r=zt(),o=e[r];return o&&typeof o.apiKey=="string"&&o.apiKey&&typeof o.orgId=="string"?{ok:!0,creds:o}:{ok:!1,reason:"missing"}}function No(t){let e=Zr(),r=fa(e);_o(r)||ma(r,{recursive:!0});let o=en()??{},n=zt();o[n]=t;let i=Ro(r,`.credentials.tmp.${ba(8).toString("hex")}`);try{ua(i,JSON.stringify(o,null,2)+`
34
+ `,{mode:384}),ha(i,e)}catch(s){try{ga(i)}catch{}throw s}}function oe(){return ot().ok}function Ue(t){let e=Ro(t,"mistflow.json");if(!_o(e))return null;try{return JSON.parse(Xr(e,"utf-8"))}catch{return null}}var wa,rt=S(()=>{"use strict";wa="https://api.mistflow.ai"});var an={};Ut(an,{MistflowApiError:()=>$,addDomain:()=>Lo,checkAuth:()=>Sa,checkAuthDetailed:()=>nn,checkSubdomain:()=>Do,createDeployment:()=>Pa,createProject:()=>Tt,deleteEnvVar:()=>zo,discoverDecisions:()=>Ia,downloadSource:()=>_a,downloadSourceWithToken:()=>sn,fetchDesignDirections:()=>Oo,fetchModule:()=>Yo,fetchPlanConversation:()=>Wt,fetchScaffold:()=>Jo,fetchStepContext:()=>Ko,forkTemplate:()=>Xo,generatePlan:()=>Mo,getAuthHeaders:()=>Ke,getBaseUrl:()=>re,getDashboardUrl:()=>va,getDbCredentials:()=>Ca,getDeployLogs:()=>Ho,getDeploymentStatus:()=>Pt,getProject:()=>Ta,getProjectErrors:()=>Wo,getSeedInfo:()=>$o,getSiteUrl:()=>nt,getTemplate:()=>Qo,hasCredentialsOnDisk:()=>oe,hasLocalCredentials:()=>rn,listDeployments:()=>it,listDomains:()=>$e,listEnvVars:()=>qo,markLocalSetupDone:()=>Ra,modifyPlan:()=>Gt,pingBackend:()=>Eo,promotePreview:()=>Go,redeployProject:()=>Aa,removeDomain:()=>Uo,rollbackDeployment:()=>Vo,shareProject:()=>Zo,uploadAndDeploy:()=>jo,uploadQAResults:()=>Fo,upsertEnvVar:()=>Bo,verifyDomain:()=>Vt});function re(){return zt()}function nt(){let t=process.env.MISTFLOW_API_URL;if(t){if(t.includes("mistflow.localhost"))return t.replace("api.mistflow","mistflow");if(t.includes("localhost:9100"))return t.replace(":9100",":9102")}return"https://mistflow.ai"}function va(){let t=process.env.MISTFLOW_API_URL;if(t){if(t.includes("mistflow.localhost"))return t.replace("api.mistflow","app.mistflow");if(t.includes("localhost:9100"))return t.replace(":9100",":9101")}return"https://app.mistflow.ai"}function Ke(){let t=ot();if(!t.ok)throw new $("auth_missing","No Mistflow credentials found.",401);return xa(t.creds)}function xa(t){return{Authorization:`ApiKey ${t.apiKey}`,"Content-Type":"application/json","X-Mistflow-MCP-Version":xe()}}function st(t){try{wt(t.headers)}catch{}}async function Eo(){try{let t=await fetch(`${re()}/health`,{method:"GET",signal:AbortSignal.timeout(5e3)});st(t)}catch{}}async function on(t,e,r,o){for(let n=0;n<2;n++)try{return await fetch(t,{...e,signal:AbortSignal.timeout(r)})}catch(i){let s=i instanceof Error&&i.name==="TimeoutError",a=i instanceof TypeError;if(o&&n===0&&(s||a)){console.error(`[api] Retrying ${t} after ${s?"timeout":"network error"}`);continue}throw i}throw new Error("fetchWithRetry: exhausted retries")}async function St(t){let e=null;try{e=await t.json()}catch{e=null}let r=e&&typeof e.code=="string"?e.code:void 0,o=e&&typeof e.message=="string"?e.message:e&&typeof e.detail=="string"?e.detail:t.statusText||"Request failed";if(r)return new $(r,o,t.status,e?.details);let n=t.status;return n===401?new $("auth_invalid",o,n):n===403?new $("permission_denied",o,n):n===404?new $("not_found",o,n):n===409?new $("conflict",o,n):n===422?new $("validation_error",o,n):n===429?new $("rate_limited",o,n):n>=500?new $("server_error",t.statusText||"Internal server error",n):new $("client_error",o,n)}async function F(t,e={}){let r=Ke(),{timeoutMs:o,idempotent:n,...i}=e,s=o??3e4,a=(i.method??"GET").toUpperCase(),l=n??(a==="GET"||a==="HEAD"),d;try{d=await on(`${re()}${t}`,{...i,headers:{...r,...i.headers}},s,l)}catch(h){throw h instanceof Error&&h.name==="TimeoutError"?new $("network_error","Request timed out. Try again in a moment."):new $("network_error","Cannot reach Mistflow servers. Check your network.")}if(st(d),!d.ok)throw await St(d);return d.json()}function rn(){return ot().ok}async function Sa(){return(await nn()).ok}async function nn(){if(Ht!==null&&Date.now()<tn)return Ht?{ok:!0}:{ok:!1,reason:"no_credentials"};if(!rn())return{ok:!1,reason:"no_credentials"};try{return await F("/api/org"),Ht=!0,tn=Date.now()+ka,{ok:!0}}catch(t){if(Ht=null,!(t instanceof $))return{ok:!1,reason:"network_error"};switch(t.code){case"auth_missing":case"auth_revoked":return{ok:!1,reason:"no_credentials"};case"auth_expired":case"auth_invalid":case"auth_org_not_found":return{ok:!1,reason:"expired"};case"network_error":return{ok:!1,reason:"network_error"};case"rate_limited":case"server_error":case"upstream_error":return{ok:!1,reason:"server_error"};default:return{ok:!1,reason:"server_error"}}}}async function Ta(t){return F(`/api/projects/${encodeURIComponent(t)}`)}async function Do(t){return F(`/api/projects/check-subdomain?name=${encodeURIComponent(t)}`)}async function Tt(t,e,r="neon",o){return F("/api/projects",{method:"POST",body:JSON.stringify({name:t,template:e,db_provider:r,requested_subdomain:o})})}async function Pa(t,e){return F("/api/deploy",{method:"POST",body:JSON.stringify({project_id:t,build_output_url:e})})}async function jo(t,e,r="production",o,n,i,s){let{readFileSync:a}=await import("fs"),{basename:l}=await import("path"),d=ot();if(!d.ok)throw new $("auth_missing","No Mistflow credentials found.",401);let h=d.creds,u=a(e),p=new Blob([u],{type:"application/gzip"}),f=new FormData;if(f.append("project_id",t),f.append("build",p,l(e)),r!=="production"&&f.append("environment",r),o&&f.append("admin_email",o),n&&f.append("schema_pushed","true"),i){let{existsSync:g}=await import("fs");if(g(i)){let x=a(i),R=new Blob([x],{type:"application/gzip"});f.append("source",R,"source.tar.gz")}}s&&f.append("git_commit_sha",s);let v;try{v=await fetch(`${re()}/api/deploy/upload`,{method:"POST",headers:{Authorization:`ApiKey ${h.apiKey}`,"X-Mistflow-MCP-Version":xe()},body:f,signal:AbortSignal.timeout(3e5)})}catch{throw new $("network_error","Cannot reach Mistflow servers. Check your network.")}if(st(v),!v.ok)throw await St(v);let b=await v.json();return{...b,id:b.deployment_id}}async function Pt(t){return F(`/api/deploy/${encodeURIComponent(t)}/status`)}async function Oo(t){return F(`/api/plan/design-directions/${encodeURIComponent(t)}`,{timeoutMs:15e3})}async function Wt(t,e){let r=e?.waitSeconds??20,o=r>0?`?wait=${r}`:"",n=Math.min(6e4,(r+5)*1e3);return F(`/api/plan/conversations/${encodeURIComponent(t)}${o}`,{timeoutMs:n})}async function Mo(t,e){let r={description:t,conversation_id:e?.conversationId,answers:e?.answers};e?.autonomous&&(r.autonomous=!0),e?.language&&e.language.toLowerCase()!=="english"&&(r.language=e.language),e?.designConversationId&&(r.design_conversation_id=e.designConversationId),e?.designDirection&&(r.design_direction=e.designDirection);let o=e?.conversationId||e?.answers||e?.designConversationId?18e4:6e4;return F("/api/plan",{method:"POST",body:JSON.stringify(r),timeoutMs:o,idempotent:!1})}async function Gt(t,e){return F("/api/plan/modify",{method:"POST",body:JSON.stringify({existing_plan:t,modification:e})})}async function Ia(t){return F("/api/plan/discover",{method:"POST",body:JSON.stringify({description:t})})}async function Lo(t,e){return F(`/api/projects/${encodeURIComponent(t)}/domains`,{method:"POST",body:JSON.stringify({domain:e})})}async function $e(t){return F(`/api/projects/${encodeURIComponent(t)}/domains`)}async function Vt(t,e){return F(`/api/projects/${encodeURIComponent(t)}/domains/${encodeURIComponent(e)}/verify`)}async function Uo(t,e){return F(`/api/projects/${encodeURIComponent(t)}/domains/${encodeURIComponent(e)}`,{method:"DELETE"})}async function Ca(t){return F(`/api/projects/${encodeURIComponent(t)}/db-credentials`)}async function $o(t){try{return await F(`/api/projects/${encodeURIComponent(t)}/test-accounts`)}catch(e){return e instanceof $&&(e.statusCode===404||e.code==="not_found")||console.error("[api] Failed to fetch seed info:",e instanceof Error?e.message:e),null}}async function Fo(t,e){try{return await F(`/api/deploy/${encodeURIComponent(t)}/qa-results`,{method:"POST",body:JSON.stringify(e)})}catch(r){return console.error("[api] Failed to upload QA results:",r instanceof Error?r.message:r),null}}async function qo(t){return F(`/api/projects/${encodeURIComponent(t)}/env`)}async function Bo(t,e,r,o){return F(`/api/projects/${encodeURIComponent(t)}/env`,{method:"PUT",body:JSON.stringify({key:e,value:r,category:o?.category??"custom",description:o?.description,setup_url:o?.setupUrl})})}async function zo(t,e){return F(`/api/projects/${encodeURIComponent(t)}/env/${encodeURIComponent(e)}`,{method:"DELETE"})}async function Ho(t){return F(`/api/deploy/${encodeURIComponent(t)}/logs`)}async function Wo(t,e="7d"){return F(`/api/projects/${encodeURIComponent(t)}/errors?period=${encodeURIComponent(e)}`)}async function it(t){return F(`/api/projects/${encodeURIComponent(t)}/deployments`)}async function Aa(t){return F(`/api/deploy/${encodeURIComponent(t)}/redeploy`,{method:"POST"})}async function Go(t,e){let r=new URLSearchParams({preview_deployment_id:e});return F(`/api/deploy/${encodeURIComponent(t)}/promote`,{method:"POST",body:r.toString(),headers:{"Content-Type":"application/x-www-form-urlencoded"}})}async function Vo(t){return F(`/api/deploy/${encodeURIComponent(t)}/rollback`,{method:"POST"})}async function _a(t,e){let r=ot();if(!r.ok)throw new $("auth_missing","Not authenticated.",401);let o=r.creds,n=await fetch(`${re()}/api/deploy/${encodeURIComponent(t)}/source`,{headers:{Authorization:`ApiKey ${o.apiKey}`,"X-Mistflow-MCP-Version":xe()},signal:AbortSignal.timeout(12e4)});if(st(n),!n.ok)throw await St(n);let{writeFileSync:i}=await import("fs"),s=Buffer.from(await n.arrayBuffer());i(e,s)}async function Jt(t,e){let{timeoutMs:r,idempotent:o,...n}=e??{},i=r??3e4,s=(n.method??"GET").toUpperCase(),a=o??(s==="GET"||s==="HEAD"),l;try{l=await on(`${re()}${t}`,{headers:{"Content-Type":"application/json","X-Mistflow-MCP-Version":xe()},...n},i,a)}catch(d){throw d instanceof Error&&d.name==="TimeoutError"?new $("network_error","Request timed out. Try again in a moment."):new $("network_error","Cannot reach Mistflow servers. Check your network.")}if(st(l),!l.ok)throw await St(l);return l.json()}async function Jo(t="nextjs"){return Jt(`/api/scaffold/${encodeURIComponent(t)}`)}async function Ko(t,e){return Jt(`/api/scaffold/${encodeURIComponent(t)}/context?step_type=${encodeURIComponent(e)}`)}async function Yo(t,e,r,o){return Jt(`/api/scaffold/${encodeURIComponent(t)}/module`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({type:"crud",entity:e,fields:r,entity_plural:o})})}async function Qo(t){return Jt(`/api/templates/${encodeURIComponent(t)}`)}async function Xo(t){return F(`/api/templates/${encodeURIComponent(t)}/fork`,{method:"POST"})}async function sn(t,e,r){let o;try{o=await fetch(`${re()}/api/deploy/${encodeURIComponent(t)}/fork-source?fork_token=${encodeURIComponent(e)}`,{headers:{"X-Mistflow-MCP-Version":xe()},signal:AbortSignal.timeout(12e4)})}catch{throw new $("network_error","Cannot reach Mistflow servers. Check your network.")}if(st(o),!o.ok)throw o.status===401?new $("validation_error","Fork token expired or invalid. Re-open the project in the dashboard to get a fresh token.",o.status):await St(o);let{writeFileSync:n}=await import("fs"),i=Buffer.from(await o.arrayBuffer());n(r,i)}async function Ra(t){await F(`/api/projects/${encodeURIComponent(t)}`,{method:"PATCH",body:JSON.stringify({local_setup_done:!0})})}async function Zo(t,e){return F(`/api/projects/${encodeURIComponent(t)}/share`,{method:"POST",body:JSON.stringify({is_template:e?.isTemplate??!1,template_description:e?.description})})}var $,Ht,tn,ka,ue=S(()=>{"use strict";rt();vt();$=class extends Error{constructor(r,o,n,i){super(o);this.code=r;this.statusCode=n;this.details=i;this.name="MistflowApiError"}get isAuth(){return this.code.startsWith("auth_")}get isTransient(){return this.code==="server_error"||this.code==="upstream_error"||this.code==="network_error"||this.code==="rate_limited"}};Ht=null,tn=0,ka=300*1e3});import{z as er}from"zod";import{platform as Na}from"os";import{execFile as ln}from"child_process";function Da(t){return"error"in t}function dn(t){return new Promise(e=>setTimeout(e,t))}function ja(t){return new Promise(e=>{let r=Na();r==="win32"?ln("cmd.exe",["/c","start","",t],o=>{o&&console.error("Could not open browser:",o.message),e(!o)}):ln(r==="darwin"?"open":"xdg-open",[t],n=>{n&&console.error("Could not open browser:",n.message),e(!n)}),setTimeout(()=>e(!1),5e3)})}async function cn(t,e,r,o){let n=r,i=o.sleep??dn;for(let s=0;s<e;s++){await i(n);let a;try{let d=await o.fetch(`${re()}/auth/poll`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device_code:t})});if(!d.ok)continue;a=await d.json()}catch{continue}if(Da(a))switch(a.error){case"authorization_pending":continue;case"slow_down":n+=5e3;continue;case"expired_token":return c("The sign-in link expired. Run mist_setup again to get a new code.",!0);case"access_denied":return c("Sign-in was cancelled. Run mist_setup again to try again.",!0);case"already_exchanged":return c("This sign-in link was already used. Run mist_setup again to get a new code.",!0)}let l=a.email||a.org_name||a.org_slug;return No({apiKey:a.api_key,apiKeyId:a.api_key_id,apiKeyName:a.api_key_name,orgId:a.org_id,orgSlug:a.org_slug,email:a.email}),c(`Connected to Mistflow as ${l}. You are ready to build and deploy.`)}return null}async function Ma(t,e=Oa){let r=t;if(r?.apiKey)try{let s=await e.fetch(`${re()}/api/org`,{headers:{Authorization:`ApiKey ${r.apiKey}`}});if(!s.ok)return c("Invalid API key. Check the key and try again.",!0);let a=await s.json();return No({apiKey:r.apiKey,orgId:a.id,orgSlug:a.slug}),c(`Connected to Mistflow as ${a.slug} via API key. You are ready to build and deploy.`)}catch{return c("Cannot reach Mistflow servers. Check your internet connection.",!0)}if(r?.deviceCode){let s=await cn(r.deviceCode,6,5e3,e);return s||c(JSON.stringify({status:"pending",deviceCode:r.deviceCode,instruction:"The user hasn't approved yet. Wait ~15 seconds and call mist_setup again with the same deviceCode."}))}let o;try{let s=await e.fetch(`${re()}/auth/device`,{method:"POST",headers:{"Content-Type":"application/json"}});if(!s.ok)return c("Cannot reach Mistflow servers. Check your internet connection.",!0);o=await s.json()}catch{return c("Cannot reach Mistflow servers. Check your internet connection.",!0)}let n=`${o.verification_uri}?code=${o.user_code}`;console.error(`
35
+ Sign in at: ${n}
36
+ Your code: ${o.user_code}
37
+ `);try{await e.openBrowser(n)}catch{}let i=await cn(o.device_code,6,5e3,e);return i||c(JSON.stringify({status:"pending",deviceCode:o.device_code,signInUrl:n,userCode:o.user_code,instruction:"The user hasn't approved yet. Wait ~15 seconds, then call mist_setup again with deviceCode='"+o.device_code+"' to check if they approved."}))}var Ea,Oa,pn,un=S(()=>{"use strict";Me();X();ue();rt();Ea=er.object({apiKey:er.string().optional().describe("API key (mist_...) for headless auth. Skips the device code flow entirely. Generate one at app.mistflow.ai/mcp-keys."),deviceCode:er.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.")});Oa={fetch:globalThis.fetch,openBrowser:ja,sleep:dn};pn={name:"mist_setup",description:Mr,inputSchema:Ea,handler:t=>Ma(t)}});import{existsSync as La,readFileSync as Ua}from"fs";function mn(t){let e=new Set;if(!La(t))return e;let r=Ua(t,"utf-8");for(let o of r.split(`
38
+ `)){let n=o.trim();if(!n||n.startsWith("#"))continue;let i=n.indexOf("=");if(i>0){let s=n.slice(0,i).trim(),a=n.slice(i+1).trim();a&&a!=='""'&&a!=="''"&&e.add(s)}}return e}var hn=S(()=>{"use strict"});var At={};Ut(At,{emptyState:()=>Ct,fetchRemoteState:()=>za,fuzzyMatch:()=>Wa,getLocalStatePath:()=>tr,readLocalState:()=>Ba,syncRemoteState:()=>Ha,writeLocalState:()=>It});import{existsSync as gn,readFileSync as $a,writeFileSync as Fa,mkdirSync as qa}from"fs";import{join as fn}from"path";function tr(t){return fn(t,".mistflow","state.json")}function Ba(t){let e=tr(t);if(!gn(e))return null;try{return JSON.parse($a(e,"utf-8"))}catch{return null}}function It(t,e){let r=fn(t,".mistflow");gn(r)||qa(r,{recursive:!0}),Fa(tr(t),JSON.stringify(e,null,2)+`
39
+ `)}function Ct(t,e){return{projectId:t,name:e,template:"",features:[],dbSchema:[],deployCount:0,decisions:[],provenance:[]}}async function za(t){let e;try{e=Ke()}catch{return null}try{let r=await fetch(`${re()}/api/projects/${encodeURIComponent(t)}/state`,{headers:{...e,"Content-Type":"application/json","X-Mistflow-MCP-Version":xe()}});try{wt(r.headers)}catch{}return r.ok?await r.json():null}catch{return null}}async function Ha(t,e){let r;try{r=Ke()}catch{return!1}try{let o=await fetch(`${re()}/api/projects/${encodeURIComponent(t)}/state`,{method:"PUT",headers:{...r,"Content-Type":"application/json","X-Mistflow-MCP-Version":xe()},body:JSON.stringify(e)});try{wt(o.headers)}catch{}return o.ok}catch{return!1}}function Wa(t,e){let r=t.toLowerCase(),o=e.toLowerCase();return o.includes(r)||r.includes(o)?!0:r.split(/\s+/).some(i=>i.length>=3&&o.includes(i))}var Ye=S(()=>{"use strict";ue();vt()});var or={};Ut(or,{ensureBackendRegistered:()=>Xa,ensureShadcnComponents:()=>Za});import{existsSync as bn,readFileSync as Ga,writeFileSync as Va}from"fs";import{join as Kt}from"path";import{spawn as Ja}from"child_process";function Ka(t,e,r,o,n){return new Promise(i=>{let s=Ja(t,e,{cwd:r,stdio:["pipe","pipe","pipe"],timeout:o,...n?{env:{...process.env,...n}}:{}}),a="",l="";s.stdout?.on("data",d=>{a+=d.toString()}),s.stderr?.on("data",d=>{l+=d.toString()}),s.on("close",d=>i({success:d===0,stdout:a,stderr:l})),s.on("error",d=>i({success:!1,stdout:a,stderr:l+d.message}))})}function Ya(t){let e=Kt(t,"mistflow.json");if(!bn(e))return null;try{return JSON.parse(Ga(e,"utf-8"))}catch{return null}}function Qa(t,e){Va(Kt(t,"mistflow.json"),JSON.stringify(e,null,2))}async function yn(t,e){try{let r=e.features,o=e.steps,n={plan:e};Array.isArray(r)&&r.length>0&&(n.features=r.map(i=>i.name)),Array.isArray(o)&&o.length>0&&(n.provenance=o.map(i=>({feature:i.name??i.title??`Step ${i.number??"?"}`,user_intent:(i.description??"").slice(0,500),decisions:"Seeded from plan",tradeoffs:"",files_affected:[]}))),await fetch(`${re()}/api/projects/${encodeURIComponent(t)}/state`,{method:"PUT",headers:{...Ke(),"Content-Type":"application/json"},body:JSON.stringify(n)})}catch(r){console.error("[self-heal] state sync failed:",r instanceof Error?r.message:String(r))}}async function Xa(t,e={}){let r=Ya(t);if(r){if(!oe())return r.projectId;if(!r.projectId)try{let n=r.plan?.requestedSubdomain,i=await Tt(r.name,void 0,r.dbProvider??"neon",n);return r.projectId=i.id,Qa(t,r),It(t,Ct(i.id,r.name)),r.plan&&await yn(i.id,r.plan),console.error(`[self-heal] registered project ${i.id.slice(0,8)} with backend`),i.id}catch(o){console.error("[self-heal] createProject failed:",o instanceof Error?o.message:String(o));return}return e.forceSync&&r.plan&&r.projectId&&await yn(r.projectId,r.plan),r.projectId}}async function Za(t,e){let r=["button","card","input","label","form","dialog","table","dropdown-menu","badge","separator","skeleton","sheet","tabs","avatar","select","textarea","checkbox","switch","tooltip","popover","sonner"],o=e&&e.length>0?e:r,n=Kt(t,"components","ui"),i=[],s=[];for(let d of o)bn(Kt(n,`${d}.tsx`))?i.push(d):s.push(d);if(s.length===0)return{installed:[],alreadyPresent:i};let a={NPM_CONFIG_LEGACY_PEER_DEPS:"true"},l=await Ka("npx",["--yes","shadcn@latest","add","-y","-o",...s],t,18e4,a);return l.success?{installed:s,alreadyPresent:i}:{installed:[],alreadyPresent:i,failed:`shadcn add failed for: ${s.join(", ")}. ${l.stderr.slice(-300)}`.trim()}}var rr=S(()=>{"use strict";ue();Ye()});import{z as Fe}from"zod";import{resolve as el,join as wn}from"path";import{existsSync as tl,readFileSync as vn,writeFileSync as ol}from"fs";var rl,xn,kn=S(()=>{"use strict";X();hn();rl=Fe.object({action:Fe.enum(["get","update"]).default("get").describe("'get' reads current project state. 'update' modifies it."),projectPath:Fe.string().optional().describe("Path to the project directory (default: current working directory)"),completedStep:Fe.number().optional().describe("(update only) Mark a plan step as completed by step number"),addEnvVar:Fe.object({key:Fe.string(),description:Fe.string().optional(),setupUrl:Fe.string().optional()}).optional().describe("(update only) Add a required env var to the project manifest")}),xn={name:"mist_state",description:"Read or update project state in mistflow.json. Use action='get' to load plan progress, env var status, and deploy info. Use action='update' to mark plan steps complete or add required env vars. Called internally by mist_project; host AIs should use mist_project directly.",inputSchema:rl,handler:async t=>{let e=t,r=el(e.projectPath??process.cwd()),o=wn(r,"mistflow.json");if(!tl(o))return Re(r);let n;try{n=JSON.parse(vn(o,"utf-8"))}catch{return c("Failed to parse mistflow.json.",!0)}if(e.action==="get"){if(!n.projectId)try{let{ensureBackendRegistered:g}=await Promise.resolve().then(()=>(rr(),or));await g(r)&&(n=JSON.parse(vn(o,"utf-8")))}catch{}let a=n.plan,l=a?.steps?.filter(g=>g.status==="completed").length??0,d=a?.steps?.length??0,h=mn(wn(r,".env.local")),u=n.env?.required?Object.entries(n.env.required).map(([g,x])=>({name:g,description:x?.description,configured:h.has(g)})):[];n.projectId&&Promise.resolve().then(()=>(Ye(),At)).then(({fetchRemoteState:g})=>g(n.projectId)).catch(()=>{});let p=[`Project: ${n.name}`];if(a){p.push(`Plan: ${a.summary??a.name??"unnamed"} \u2014 ${l}/${d} steps complete`);for(let g of a.steps){let x=g.status==="completed"?"\u2713":g.status==="in_progress"?"\u2192":" ";p.push(` [${x}] ${g.number}. ${g.name}`)}}let f=u.filter(g=>!g.configured);f.length>0&&p.push(`Missing env vars: ${f.map(g=>g.name).join(", ")}`),n.deploy?.url?p.push(`Deployed: ${n.deploy.url} (${n.deploy.count??0} deploys)`):p.push("Not deployed yet");let v=[],b=a?.steps?.find(g=>g.status!=="completed");return b?v.push(`NEXT: Call mist_implement({ projectPath }) to work on step ${b.number} (${b.name}).`):a&&l===d&&(n.deploy?.url||v.push("NEXT: All steps complete. Call mist_build({ projectPath }) then mist_deploy({ action: 'deploy', projectPath }). Do NOT ask the user \u2014 chain straight through.")),f.length>0&&v.push(`Missing env vars in .env.local: ${f.map(g=>g.name).join(", ")}`),c(JSON.stringify({name:n.name,projectId:n.projectId,planProgress:a?{name:a.name,summary:a.summary,totalSteps:d,completedSteps:l,steps:a.steps}:null,envStatus:u,deploy:n.deploy??null,contextMessage:p.join(`
40
+ `),nextSteps:v}))}let i=[];if(e.completedStep!==void 0){let a=n.plan;if(a?.steps){let l=a.steps.findIndex(d=>d.number===e.completedStep);if(l===-1)return c(`Step ${e.completedStep} not found in the plan.`,!0);a.steps[l].status="completed",i.push(`Step ${e.completedStep} marked as completed`)}}e.addEnvVar&&(n.env||(n.env={required:{}}),n.env.required||(n.env.required={}),n.env.required[e.addEnvVar.key]={description:e.addEnvVar.description,setupUrl:e.addEnvVar.setupUrl},i.push(`Added required env var: ${e.addEnvVar.key}`)),ol(o,JSON.stringify(n,null,2)+`
41
+ `),n.projectId&&Promise.resolve().then(()=>(Ye(),At)).then(async({readLocalState:a,syncRemoteState:l})=>{let d=a(r);d&&await l(n.projectId,d)}).catch(()=>{});let s=[];if(e.completedStep!==void 0){let l=n.plan?.steps?.find(d=>d.status!=="completed");l?s.push(`NEXT: Call mist_implement({ projectPath }) to work on step ${l.number} (${l.name}). Do this now.`):s.push("NEXT: All steps complete. Call mist_build({ projectPath }) then mist_deploy({ action: 'deploy', projectPath }) to deploy the app. Do NOT suggest localhost.")}return e.addEnvVar&&(s.push(`Add ${e.addEnvVar.key} to your .env.local file`),e.addEnvVar.setupUrl&&s.push(`Get the value from: ${e.addEnvVar.setupUrl}`)),c(JSON.stringify({updated:!0,changes:i,message:i.length>0?`Project state saved. ${i.join(". ")}.`:"No changes made.",nextSteps:s.length>0?s:void 0}))}}});function Yt(t){let e=at.find(o=>o.id===t);if(e)return e;let r=t.toLowerCase().replace(/[^a-z0-9]/g,"");return at.find(o=>{let n=o.title.toLowerCase().replace(/[^a-z0-9]/g,"");return n===r||n.includes(r)||r.includes(n)})}function Sn(t){return nr[t]}function sr(t){return t?at.filter(e=>e.category.toLowerCase()===t.toLowerCase()):at}function Tn(t){let e=t??at;if(e.length===0)return"Landing page presets have been replaced by the tone-based system. The landing page tone is now auto-selected based on your app's description during planning.";let r={};for(let n of e){r[n.category]||(r[n.category]=[]);let i=nr[n.id],s=i?` \u2014 ${i.description}`:"";r[n.category].push(`${n.id} \u2014 "${n.title}"${s}`)}let o=[];for(let[n,i]of Object.entries(r))o.push(`**${n}**:
42
+ ${i.map(s=>` \u2022 ${s}`).join(`
43
+ `)}`);return o.join(`
44
44
 
45
- `)}function Da(t){return t.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}function Po(t){return(t?os(t):it).map(s=>{let n=ss[s.id];return{id:s.id,slug:Da(s.title),title:s.title,category:s.category,description:n?.description??"",tags:n?.tags??[],theme:n?.theme??"dark",colors:n?.colors??[],style:n?.style??""}})}function Io(t,e){return[]}var ss,it,rs=_(()=>{"use strict";ss={},it=[]});function Oa(t){return t.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}function lt(t){let e=at.find(n=>n.id===t);if(e)return e;let s=t.toLowerCase().replace(/[^a-z0-9]/g,"");return at.find(n=>{let o=n.name.toLowerCase().replace(/[^a-z0-9]/g,"");return o===s||o.includes(s)||s.includes(o)})}function ct(t){return is[t]}function as(t){return t?at.filter(e=>e.category.toLowerCase()===t.toLowerCase()):at}function To(t){let e=t??at,s={};for(let o of e){s[o.category]||(s[o.category]=[]);let i=is[o.id],r=i?` \u2014 ${i.description}`:"",a=i?.packages.length?` (${i.packages.join(", ")})`:"";s[o.category].push(`${o.id} \u2014 "${o.name}"${r}${a}`)}let n=[];for(let[o,i]of Object.entries(s))n.push(`**${o}**:
46
- ${i.map(r=>` - ${r}`).join(`
47
- `)}`);return n.join(`
45
+ `)}function nl(t){return t.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}function Pn(t){return(t?sr(t):at).map(r=>{let o=nr[r.id];return{id:r.id,slug:nl(r.title),title:r.title,category:r.category,description:o?.description??"",tags:o?.tags??[],theme:o?.theme??"dark",colors:o?.colors??[],style:o?.style??""}})}function In(t,e){return[]}var nr,at,ir=S(()=>{"use strict";nr={},at=[]});function sl(t){return t.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}function ct(t){let e=lt.find(o=>o.id===t);if(e)return e;let r=t.toLowerCase().replace(/[^a-z0-9]/g,"");return lt.find(o=>{let n=o.name.toLowerCase().replace(/[^a-z0-9]/g,"");return n===r||n.includes(r)||r.includes(n)})}function dt(t){return ar[t]}function lr(t){return t?lt.filter(e=>e.category.toLowerCase()===t.toLowerCase()):lt}function Cn(t){let e=t??lt,r={};for(let n of e){r[n.category]||(r[n.category]=[]);let i=ar[n.id],s=i?` \u2014 ${i.description}`:"",a=i?.packages.length?` (${i.packages.join(", ")})`:"";r[n.category].push(`${n.id} \u2014 "${n.name}"${s}${a}`)}let o=[];for(let[n,i]of Object.entries(r))o.push(`**${n}**:
46
+ ${i.map(s=>` - ${s}`).join(`
47
+ `)}`);return o.join(`
48
48
 
49
- `)}function _o(t){return(t?as(t):at).map(s=>{let n=is[s.id];return{id:s.id,slug:Oa(s.name),name:s.name,category:s.category,description:n?.description??"",tags:n?.tags??[],envVars:n?.envVars??[],docsUrl:n?.docsUrl??"",packages:n?.packages??[],difficulty:n?.difficulty??"medium"}})}var is,at,Kt=_(()=>{"use strict";is={"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"}},at=[{id:"resend-email",name:"Resend Email",category:"communication",prompt:`## Resend Email Integration
49
+ `)}function An(t){return(t?lr(t):lt).map(r=>{let o=ar[r.id];return{id:r.id,slug:sl(r.name),name:r.name,category:r.category,description:o?.description??"",tags:o?.tags??[],envVars:o?.envVars??[],docsUrl:o?.docsUrl??"",packages:o?.packages??[],difficulty:o?.difficulty??"medium"}})}var ar,lt,Qt=S(()=>{"use strict";ar={"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"}},lt=[{id:"resend-email",name:"Resend Email",category:"communication",prompt:`## Resend Email Integration
50
50
 
51
51
  ### File Structure
52
52
  \`\`\`
@@ -1466,12 +1466,12 @@ export function ImageGenerator() {
1466
1466
  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.
1467
1467
  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.
1468
1468
  7. **Network I/O does NOT count as CPU time on Workers.** Image generation wait time is all network I/O.
1469
- 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).`}]});import{z as le}from"zod";import{resolve as Qt}from"path";import{existsSync as Xt,readFileSync as Zt}from"fs";import{join as en}from"path";var Ma,Ro,Co=_(()=>{"use strict";K();Oe();xo();ue();rs();wt();Kt();Ma=le.object({action:le.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:le.string().optional().describe("Path to the project directory (default: cwd)"),completedStep:le.number().optional().describe("(update) Mark a plan step as completed by step number"),addEnvVar:le.object({key:le.string(),description:le.string().optional(),setupUrl:le.string().optional()}).optional().describe("(update) Add a required env var to the project manifest"),templateDescription:le.string().optional().describe("(share) Short description of what this template builds"),category:le.string().optional().describe("(landing-designs) Filter by category"),presetId:le.string().optional().describe("(landing-designs) Get full details for a specific landing design by ID"),integrationId:le.string().optional().describe("(integrations) Get full details for a specific integration preset by ID (e.g. 'stripe-payments', 'resend-email', 'elevenlabs-voice')"),period:le.string().optional().describe("(errors) Time period for errors: '1h', '24h', '7d' (default: '7d')"),deploymentId:le.string().optional().describe("(logs) Deployment ID to fetch logs for. If omitted, fetches logs for the latest deployment.")}),Ro={name:"mist_project",description:Ms,inputSchema:Ma,handler:async t=>{let e=t;if(["share","errors","logs","deployments"].includes(e.action)&&!ee())return c("You need to sign in first. Run mist_setup to connect your account.",!0);switch(e.action){case"get":case"update":return vo.handler({action:e.action,projectPath:e.projectPath,completedStep:e.completedStep,addEnvVar:e.addEnvVar});case"share":{let n=Qt(e.projectPath??process.cwd()),o=en(n,"mistflow.json");if(!Xt(o))return Re(n);let i;try{i=JSON.parse(Zt(o,"utf-8"))}catch{return c("Could not read mistflow.json.",!0)}let r=i.projectId;if(!r)return c("No project ID found. Deploy the project first to register it.",!0);try{let a=await Xn(r,{isTemplate:!0,description:e.templateDescription});return c(JSON.stringify({shareUrl:a.share_url,shareToken:a.share_token,message:`Your project is now a shareable template!
1469
+ 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).`}]});import{z as le}from"zod";import{resolve as Xt}from"path";import{existsSync as Zt,readFileSync as eo}from"fs";import{join as to}from"path";var il,_n,Rn=S(()=>{"use strict";X();Me();kn();ue();ir();vt();Qt();il=le.object({action:le.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:le.string().optional().describe("Path to the project directory (default: cwd)"),completedStep:le.number().optional().describe("(update) Mark a plan step as completed by step number"),addEnvVar:le.object({key:le.string(),description:le.string().optional(),setupUrl:le.string().optional()}).optional().describe("(update) Add a required env var to the project manifest"),templateDescription:le.string().optional().describe("(share) Short description of what this template builds"),category:le.string().optional().describe("(landing-designs) Filter by category"),presetId:le.string().optional().describe("(landing-designs) Get full details for a specific landing design by ID"),integrationId:le.string().optional().describe("(integrations) Get full details for a specific integration preset by ID (e.g. 'stripe-payments', 'resend-email', 'elevenlabs-voice')"),period:le.string().optional().describe("(errors) Time period for errors: '1h', '24h', '7d' (default: '7d')"),deploymentId:le.string().optional().describe("(logs) Deployment ID to fetch logs for. If omitted, fetches logs for the latest deployment.")}),_n={name:"mist_project",description:Lr,inputSchema:il,handler:async t=>{let e=t;if(["share","errors","logs","deployments"].includes(e.action)&&!oe())return c("You need to sign in first. Run mist_setup to connect your account.",!0);switch(e.action){case"get":case"update":return xn.handler({action:e.action,projectPath:e.projectPath,completedStep:e.completedStep,addEnvVar:e.addEnvVar});case"share":{let o=Xt(e.projectPath??process.cwd()),n=to(o,"mistflow.json");if(!Zt(n))return Re(o);let i;try{i=JSON.parse(eo(n,"utf-8"))}catch{return c("Could not read mistflow.json.",!0)}let s=i.projectId;if(!s)return c("No project ID found. Deploy the project first to register it.",!0);try{let a=await Zo(s,{isTemplate:!0,description:e.templateDescription});return c(JSON.stringify({shareUrl:a.share_url,shareToken:a.share_token,message:`Your project is now a shareable template!
1470
1470
 
1471
1471
  Anyone can fork it: ${a.share_url}
1472
1472
 
1473
1473
  Others can use it in their AI editor:
1474
- "build me something like ${a.share_url}"`}))}catch(a){let l=a instanceof Error?a.message:"Failed to share project";return c(l,!0)}}case"landing-designs":{if(e.presetId){let r=Yt(e.presetId);if(!r)return c(`Preset '${e.presetId}' not found. Use mist_project action='presets' without presetId to list all available presets.`,!0);let a=ko(e.presetId);return c(JSON.stringify({preset:{id:r.id,title:r.title,category:r.category,description:a?.description??"",style:a?.style??"",theme:a?.theme??"dark",colors:a?.colors??[],tags:a?.tags??[],promptLength:r.prompt.length},message:`Landing design "${r.title}" (${r.category}) \u2014 ${a?.description??""}. To use it, pass landingDesign="${r.id}" when calling mist_plan.`}))}let n=Po(e.category??void 0),o=os(e.category),i=So(o);return c(JSON.stringify({count:n.length,presets:n.map(r=>({id:r.id,title:r.title,category:r.category,description:r.description,style:r.style,theme:r.theme,colors:r.colors})),formatted:i,message:`${n.length} landing designs available.${e.category?` Filtered by: ${e.category}.`:""} To use one, pass landingDesign="<id>" when calling mist_plan. The design blueprint will be injected during the landing page implementation step. Browse them at ${st()}/designs?tab=landing-designs.`}))}case"integrations":{if(e.integrationId){let r=lt(e.integrationId);if(!r)return c(`Integration '${e.integrationId}' not found. Use mist_project action='integrations' without integrationId to list all available integrations.`,!0);let a=ct(r.id);return c(JSON.stringify({integration:{id:r.id,name:r.name,category:r.category,description:a?.description??"",packages:a?.packages??[],envVars:a?.envVars??[],docsUrl:a?.docsUrl??"",difficulty:a?.difficulty??"medium"},message:`Integration "${r.name}" (${r.category}) \u2014 ${a?.description??""}. This blueprint is auto-injected during implementation when your plan has a matching integration step. Required env vars: ${a?.envVars?.map(l=>l.key).join(", ")||"none"}. Docs: ${a?.docsUrl??"n/a"}.`}))}let n=_o(e.category??void 0),o=as(e.category??void 0),i=To(o);return c(JSON.stringify({count:n.length,integrations:n.map(r=>({id:r.id,name:r.name,category:r.category,description:r.description,packages:r.packages,difficulty:r.difficulty,envVars:r.envVars.map(a=>a.key)})),formatted:i,message:`${n.length} integration blueprints available.${e.category?` Filtered by: ${e.category}.`:""} Integration blueprints are auto-injected during implementation when your plan includes a matching integration step. Use integrationId to see full details including env vars and setup URLs.`}))}case"errors":{let n=Qt(e.projectPath??process.cwd()),o=en(n,"mistflow.json");if(!Xt(o))return Re(n);let i;try{i=JSON.parse(Zt(o,"utf-8"))}catch{return c("Could not read mistflow.json.",!0)}let r=i.projectId;if(!r)return c("No project ID found. Deploy the project first.",!0);try{let a=await Hn(r,e.period??"7d");return a.total===0?c(JSON.stringify({total:0,period:a.period,message:`No runtime errors in the last ${a.period}. The app is running clean.`})):c(JSON.stringify({total:a.total,period:a.period,errors:a.errors,message:`${a.total} runtime error(s) in the last ${a.period}. Review the errors above and use mist_debug to investigate.`}))}catch(a){let l=a instanceof Error?a.message:"Failed to fetch errors";return c(l,!0)}}case"logs":{let n=Qt(e.projectPath??process.cwd()),o=en(n,"mistflow.json");if(!Xt(o))return Re(n);let i;try{i=JSON.parse(Zt(o,"utf-8"))}catch{return c("Could not read mistflow.json.",!0)}let r=i.projectId;if(!r)return c("No project ID found. Deploy the project first.",!0);let a=e.deploymentId;if(!a)try{let l=await rt(r);if(l.length===0)return c("No deployments found for this project.",!0);a=l[0].id}catch(l){let d=l instanceof Error?l.message:"Failed to fetch deployments";return c(d,!0)}try{let[l,d]=await Promise.all([zn(a),Pt(a)]),h=l.filter(p=>p.level==="error"),m=l.filter(p=>p.level==="warn");return c(JSON.stringify({deploymentId:a,status:d.status,errorMessage:d.error??null,totalLogs:l.length,errorCount:h.length,warnCount:m.length,logs:l.map(p=>({time:p.timestamp,level:p.level,phase:p.phase,message:p.message})),message:d.status==="failed"?`Deployment failed. ${h.length} error(s) found in logs. Review the logs above to diagnose the issue.`:`Deployment status: ${d.status}. ${l.length} log entries (${h.length} errors, ${m.length} warnings).`}))}catch(l){let d=l instanceof Error?l.message:"Failed to fetch deploy logs";return c(d,!0)}}case"deployments":{let n=Qt(e.projectPath??process.cwd()),o=en(n,"mistflow.json");if(!Xt(o))return Re(n);let i;try{i=JSON.parse(Zt(o,"utf-8"))}catch{return c("Could not read mistflow.json.",!0)}let r=i.projectId;if(!r)return c("No project ID found. Deploy the project first.",!0);try{let a=await rt(r);return c(JSON.stringify({total:a.length,deployments:a.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:`${a.length} deployment(s) found. Use mist_project action='logs' deploymentId='<id>' to see detailed logs for any deployment.`}))}catch(a){let l=a instanceof Error?a.message:"Failed to fetch deployments";return c(l,!0)}}case"version":{Pn().backendSignalReceived||await En();let n=Pn(),o=n.severity==="none",i={none:"up to date",patch:"patch update available",minor:"minor update available",major:"major update available",unsupported:"UNSUPPORTED \u2014 upgrade required"};return c(JSON.stringify({current:n.current,latest:n.latest||"unknown",minSupported:n.minSupported||"unknown",severity:n.severity,upToDate:o,upgradeCmd:n.upgradeCmd,changelogUrl:n.changelogUrl,backendSignalReceived:n.backendSignalReceived,message:n.backendSignalReceived?`Mistflow MCP ${n.current} (${i[n.severity]??n.severity}). Latest: ${n.latest}.${o?"":` Run \`${n.upgradeCmd}\` and restart your editor to upgrade.`}`:`Mistflow MCP ${n.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 c(`Unknown action: ${e.action}. Use get, update, share, landing-designs, integrations, errors, logs, deployments, or version.`,!0)}}}});import{z as Xe}from"zod";var $a,Ao,Eo=_(()=>{"use strict";Oe();K();qt();$a=Xe.object({action:Xe.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:Xe.string().optional().describe("URL to navigate to. Required for 'navigate'; optional for 'screenshot' (navigates before capturing)."),selector:Xe.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:Xe.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:Xe.boolean().default(!1).describe("For 'screenshot': capture the full scrollable page instead of just the viewport."),includeScreenshot:Xe.boolean().default(!1).describe("For navigate/interact actions: also return a screenshot alongside the accessibility snapshot.")}),Ao={name:"mist_browser",description:$s,inputSchema:$a,handler:async t=>{let e=t,s=await In();if(e.action==="navigate"){if(!e.url)return c("URL is required for 'navigate'.",!0);let i=[],r=d=>{d.type()==="error"&&i.push(d.text())};s.on("console",r),await s.goto(e.url,{waitUntil:"domcontentloaded",timeout:3e4}),await s.waitForLoadState("networkidle").catch(()=>{});let a=[],l=d=>a.push(d.message);if(s.on("pageerror",l),await s.waitForTimeout(500),s.removeListener("console",r),s.removeListener("pageerror",l),i.length>0||a.length>0){let d=await vt(s),h=[{type:"text",text:JSON.stringify({url:s.url(),title:await s.title(),snapshot:d,consoleErrors:i,pageErrors:a,hasErrors:!0})}];if(e.includeScreenshot){let m=await xt(s);h.push({type:"image",data:m.toString("base64"),mimeType:"image/png"})}return{content:h}}}else if(e.action==="go_back")await s.goBack({waitUntil:"domcontentloaded",timeout:1e4});else if(e.action==="go_forward")await s.goForward({waitUntil:"domcontentloaded",timeout:1e4});else if(e.action==="click"){if(!e.selector)return c("Selector is required for 'click'.",!0);await s.click(e.selector,{timeout:1e4}),await s.waitForLoadState("domcontentloaded").catch(()=>{}),await s.waitForTimeout(500)}else if(e.action==="type"){if(!e.selector)return c("Selector is required for 'type'.",!0);if(!e.value)return c("Value is required for 'type'.",!0);await s.type(e.selector,e.value,{delay:50})}else if(e.action==="fill"){if(!e.selector)return c("Selector is required for 'fill'.",!0);if(!e.value)return c("Value is required for 'fill'.",!0);await s.fill(e.selector,e.value)}else if(e.action==="select_option"){if(!e.selector)return c("Selector is required for 'select_option'.",!0);if(!e.value)return c("Value is required for 'select_option'.",!0);await s.selectOption(e.selector,e.value)}else if(e.action==="hover"){if(!e.selector)return c("Selector is required for 'hover'.",!0);await s.hover(e.selector,{timeout:1e4})}else if(e.action==="press_key"){if(!e.value)return c("Value is required for 'press_key' (e.g. 'Enter').",!0);await s.keyboard.press(e.value),await s.waitForLoadState("domcontentloaded").catch(()=>{}),await s.waitForTimeout(500)}else if(e.action==="screenshot"){e.url&&(await s.goto(e.url,{waitUntil:"domcontentloaded",timeout:3e4}),await s.waitForLoadState("networkidle").catch(()=>{}));let i;if(e.selector){let r=await s.$(e.selector);if(!r)return c(`Element not found: ${e.selector}`,!0);i=await r.screenshot({type:"png"})}else i=await xt(s,e.fullPage);return{content:[{type:"text",text:JSON.stringify({url:s.url(),title:await s.title(),message:`Screenshot captured (${e.fullPage?"full page":"viewport"})`})},{type:"image",data:i.toString("base64"),mimeType:"image/png"}]}}else if(e.action==="snapshot"){let i=await vt(s);return{content:[{type:"text",text:JSON.stringify({url:s.url(),title:await s.title(),snapshot:i})}]}}let n=await vt(s),o=[{type:"text",text:JSON.stringify({url:s.url(),title:await s.title(),snapshot:n})}];if(e.includeScreenshot){let i=await xt(s);o.push({type:"image",data:i.toString("base64"),mimeType:"image/png"})}return{content:o}}}});import{z as No}from"zod";var jo,Do,Oo=_(()=>{"use strict";Oe();K();jo=`# Mistflow MCP tool reference
1474
+ "build me something like ${a.share_url}"`}))}catch(a){let l=a instanceof Error?a.message:"Failed to share project";return c(l,!0)}}case"landing-designs":{if(e.presetId){let s=Yt(e.presetId);if(!s)return c(`Preset '${e.presetId}' not found. Use mist_project action='presets' without presetId to list all available presets.`,!0);let a=Sn(e.presetId);return c(JSON.stringify({preset:{id:s.id,title:s.title,category:s.category,description:a?.description??"",style:a?.style??"",theme:a?.theme??"dark",colors:a?.colors??[],tags:a?.tags??[],promptLength:s.prompt.length},message:`Landing design "${s.title}" (${s.category}) \u2014 ${a?.description??""}. To use it, pass landingDesign="${s.id}" when calling mist_plan.`}))}let o=Pn(e.category??void 0),n=sr(e.category),i=Tn(n);return c(JSON.stringify({count:o.length,presets:o.map(s=>({id:s.id,title:s.title,category:s.category,description:s.description,style:s.style,theme:s.theme,colors:s.colors})),formatted:i,message:`${o.length} landing designs available.${e.category?` Filtered by: ${e.category}.`:""} To use one, pass landingDesign="<id>" when calling mist_plan. The design blueprint will be injected during the landing page implementation step. Browse them at ${nt()}/designs?tab=landing-designs.`}))}case"integrations":{if(e.integrationId){let s=ct(e.integrationId);if(!s)return c(`Integration '${e.integrationId}' not found. Use mist_project action='integrations' without integrationId to list all available integrations.`,!0);let a=dt(s.id);return c(JSON.stringify({integration:{id:s.id,name:s.name,category:s.category,description:a?.description??"",packages:a?.packages??[],envVars:a?.envVars??[],docsUrl:a?.docsUrl??"",difficulty:a?.difficulty??"medium"},message:`Integration "${s.name}" (${s.category}) \u2014 ${a?.description??""}. This blueprint is auto-injected during implementation when your plan has a matching integration step. Required env vars: ${a?.envVars?.map(l=>l.key).join(", ")||"none"}. Docs: ${a?.docsUrl??"n/a"}.`}))}let o=An(e.category??void 0),n=lr(e.category??void 0),i=Cn(n);return c(JSON.stringify({count:o.length,integrations:o.map(s=>({id:s.id,name:s.name,category:s.category,description:s.description,packages:s.packages,difficulty:s.difficulty,envVars:s.envVars.map(a=>a.key)})),formatted:i,message:`${o.length} integration blueprints available.${e.category?` Filtered by: ${e.category}.`:""} Integration blueprints are auto-injected during implementation when your plan includes a matching integration step. Use integrationId to see full details including env vars and setup URLs.`}))}case"errors":{let o=Xt(e.projectPath??process.cwd()),n=to(o,"mistflow.json");if(!Zt(n))return Re(o);let i;try{i=JSON.parse(eo(n,"utf-8"))}catch{return c("Could not read mistflow.json.",!0)}let s=i.projectId;if(!s)return c("No project ID found. Deploy the project first.",!0);try{let a=await Wo(s,e.period??"7d");return a.total===0?c(JSON.stringify({total:0,period:a.period,message:`No runtime errors in the last ${a.period}. The app is running clean.`})):c(JSON.stringify({total:a.total,period:a.period,errors:a.errors,message:`${a.total} runtime error(s) in the last ${a.period}. Review the errors above and use mist_debug to investigate.`}))}catch(a){let l=a instanceof Error?a.message:"Failed to fetch errors";return c(l,!0)}}case"logs":{let o=Xt(e.projectPath??process.cwd()),n=to(o,"mistflow.json");if(!Zt(n))return Re(o);let i;try{i=JSON.parse(eo(n,"utf-8"))}catch{return c("Could not read mistflow.json.",!0)}let s=i.projectId;if(!s)return c("No project ID found. Deploy the project first.",!0);let a=e.deploymentId;if(!a)try{let l=await it(s);if(l.length===0)return c("No deployments found for this project.",!0);a=l[0].id}catch(l){let d=l instanceof Error?l.message:"Failed to fetch deployments";return c(d,!0)}try{let[l,d]=await Promise.all([Ho(a),Pt(a)]),h=l.filter(p=>p.level==="error"),u=l.filter(p=>p.level==="warn");return c(JSON.stringify({deploymentId:a,status:d.status,errorMessage:d.error??null,totalLogs:l.length,errorCount:h.length,warnCount:u.length,logs:l.map(p=>({time:p.timestamp,level:p.level,phase:p.phase,message:p.message})),message:d.status==="failed"?`Deployment failed. ${h.length} error(s) found in logs. Review the logs above to diagnose the issue.`:`Deployment status: ${d.status}. ${l.length} log entries (${h.length} errors, ${u.length} warnings).`}))}catch(l){let d=l instanceof Error?l.message:"Failed to fetch deploy logs";return c(d,!0)}}case"deployments":{let o=Xt(e.projectPath??process.cwd()),n=to(o,"mistflow.json");if(!Zt(n))return Re(o);let i;try{i=JSON.parse(eo(n,"utf-8"))}catch{return c("Could not read mistflow.json.",!0)}let s=i.projectId;if(!s)return c("No project ID found. Deploy the project first.",!0);try{let a=await it(s);return c(JSON.stringify({total:a.length,deployments:a.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:`${a.length} deployment(s) found. Use mist_project action='logs' deploymentId='<id>' to see detailed logs for any deployment.`}))}catch(a){let l=a instanceof Error?a.message:"Failed to fetch deployments";return c(l,!0)}}case"version":{Po().backendSignalReceived||await Eo();let o=Po(),n=o.severity==="none",i={none:"up to date",patch:"patch update available",minor:"minor update available",major:"major update available",unsupported:"UNSUPPORTED \u2014 upgrade required"};return c(JSON.stringify({current:o.current,latest:o.latest||"unknown",minSupported:o.minSupported||"unknown",severity:o.severity,upToDate:n,upgradeCmd:o.upgradeCmd,changelogUrl:o.changelogUrl,backendSignalReceived:o.backendSignalReceived,message:o.backendSignalReceived?`Mistflow MCP ${o.current} (${i[o.severity]??o.severity}). Latest: ${o.latest}.${n?"":` Run \`${o.upgradeCmd}\` and restart your editor to upgrade.`}`:`Mistflow MCP ${o.current}. The backend hasn't replied yet \u2014 make one other API call (e.g. mist_project action='get') then retry to see the latest version.`}))}default:return c(`Unknown action: ${e.action}. Use get, update, share, landing-designs, integrations, errors, logs, deployments, or version.`,!0)}}}});import{z as Qe}from"zod";var al,Nn,En=S(()=>{"use strict";Me();X();Bt();al=Qe.object({action:Qe.enum(["navigate","go_back","go_forward","click","type","fill","select_option","press_key","hover","screenshot","snapshot"]).describe("Action to perform. Navigation: navigate|go_back|go_forward. Interaction: click|type|fill|select_option|press_key|hover. Visual: screenshot (returns image) | snapshot (returns accessibility tree)."),url:Qe.string().optional().describe("URL to navigate to. Required for 'navigate'; optional for 'screenshot' (navigates before capturing)."),selector:Qe.string().optional().describe("CSS selector of the target element. Required for: click, type, fill, select_option, hover. Optional for screenshot (captures just that element)."),value:Qe.string().optional().describe("Text to type/fill, option to select, or key to press (e.g. 'Enter', 'Tab'). Required for: type, fill, select_option, press_key."),fullPage:Qe.boolean().default(!1).describe("For 'screenshot': capture the full scrollable page instead of just the viewport."),includeScreenshot:Qe.boolean().default(!1).describe("For navigate/interact actions: also return a screenshot alongside the accessibility snapshot.")}),Nn={name:"mist_browser",description:Ur,inputSchema:al,handler:async t=>{let e=t,r=await Io();if(e.action==="navigate"){if(!e.url)return c("URL is required for 'navigate'.",!0);let i=[],s=d=>{d.type()==="error"&&i.push(d.text())};r.on("console",s),await r.goto(e.url,{waitUntil:"domcontentloaded",timeout:3e4}),await r.waitForLoadState("networkidle").catch(()=>{});let a=[],l=d=>a.push(d.message);if(r.on("pageerror",l),await r.waitForTimeout(500),r.removeListener("console",s),r.removeListener("pageerror",l),i.length>0||a.length>0){let d=await xt(r),h=[{type:"text",text:JSON.stringify({url:r.url(),title:await r.title(),snapshot:d,consoleErrors:i,pageErrors:a,hasErrors:!0})}];if(e.includeScreenshot){let u=await kt(r);h.push({type:"image",data:u.toString("base64"),mimeType:"image/png"})}return{content:h}}}else if(e.action==="go_back")await r.goBack({waitUntil:"domcontentloaded",timeout:1e4});else if(e.action==="go_forward")await r.goForward({waitUntil:"domcontentloaded",timeout:1e4});else if(e.action==="click"){if(!e.selector)return c("Selector is required for 'click'.",!0);await r.click(e.selector,{timeout:1e4}),await r.waitForLoadState("domcontentloaded").catch(()=>{}),await r.waitForTimeout(500)}else if(e.action==="type"){if(!e.selector)return c("Selector is required for 'type'.",!0);if(!e.value)return c("Value is required for 'type'.",!0);await r.type(e.selector,e.value,{delay:50})}else if(e.action==="fill"){if(!e.selector)return c("Selector is required for 'fill'.",!0);if(!e.value)return c("Value is required for 'fill'.",!0);await r.fill(e.selector,e.value)}else if(e.action==="select_option"){if(!e.selector)return c("Selector is required for 'select_option'.",!0);if(!e.value)return c("Value is required for 'select_option'.",!0);await r.selectOption(e.selector,e.value)}else if(e.action==="hover"){if(!e.selector)return c("Selector is required for 'hover'.",!0);await r.hover(e.selector,{timeout:1e4})}else if(e.action==="press_key"){if(!e.value)return c("Value is required for 'press_key' (e.g. 'Enter').",!0);await r.keyboard.press(e.value),await r.waitForLoadState("domcontentloaded").catch(()=>{}),await r.waitForTimeout(500)}else if(e.action==="screenshot"){e.url&&(await r.goto(e.url,{waitUntil:"domcontentloaded",timeout:3e4}),await r.waitForLoadState("networkidle").catch(()=>{}));let i;if(e.selector){let s=await r.$(e.selector);if(!s)return c(`Element not found: ${e.selector}`,!0);i=await s.screenshot({type:"png"})}else i=await kt(r,e.fullPage);return{content:[{type:"text",text:JSON.stringify({url:r.url(),title:await r.title(),message:`Screenshot captured (${e.fullPage?"full page":"viewport"})`})},{type:"image",data:i.toString("base64"),mimeType:"image/png"}]}}else if(e.action==="snapshot"){let i=await xt(r);return{content:[{type:"text",text:JSON.stringify({url:r.url(),title:await r.title(),snapshot:i})}]}}let o=await xt(r),n=[{type:"text",text:JSON.stringify({url:r.url(),title:await r.title(),snapshot:o})}];if(e.includeScreenshot){let i=await kt(r);n.push({type:"image",data:i.toString("base64"),mimeType:"image/png"})}return{content:n}}}});import{z as Dn}from"zod";var jn,On,Mn=S(()=>{"use strict";Me();X();jn=`# Mistflow MCP tool reference
1475
1475
 
1476
1476
  Every capability is an MCP tool. There is no companion CLI. Long-running tools
1477
1477
  (install, build, qa, deploy) use the fire-and-poll pattern \u2014 the first call
@@ -1486,15 +1486,25 @@ they want to keep waiting \u2014 polling is how these tools work. Each poll
1486
1486
  responds in under a second; the underlying work continues detached.
1487
1487
 
1488
1488
  When \`mist_plan\` returns \`status: "clarify"\`, the first planning pass is
1489
- complete and waiting on user answers. You MUST render every question via
1490
- AskUserQuestion (Claude Code) or the equivalent structured picker in your
1491
- host \u2014 and wait for the user to answer. DO NOT pick the \`recommended\`
1492
- option on their behalf. DO NOT print the questions as markdown with a
1493
- "say the word if you want to change any" fallback. DO NOT proceed without
1494
- real answers even inside \`/loop\` or other autonomous modes \u2014 stop and
1495
- wait; the flow resumes when the user responds. Every question is a
1496
- product decision the user is paying to make; \`recommended\` is a hint for
1497
- them, not permission for you to decide.
1489
+ complete and waiting on user answers. You MUST ask the user and wait for
1490
+ their actual answer. Use whichever your host supports:
1491
+
1492
+ \u2022 Claude Code: \`AskUserQuestion\` tool
1493
+ \u2022 Cursor: quick-pick UI
1494
+ \u2022 OpenAI Codex (Plan mode): \`request_user_input\` tool
1495
+ \u2022 OpenAI Codex (Default mode): \`request_user_input\` returns
1496
+ "unavailable in <mode> mode" \u2014 when you see that, STOP your turn,
1497
+ print the questions as a numbered chat message with all options
1498
+ visible, and wait for the user's next message. Do NOT resume by
1499
+ picking answers yourself.
1500
+ \u2022 Any host without a native question tool: same stop-and-print fallback.
1501
+
1502
+ DO NOT pick the \`recommended\` option on their behalf. DO NOT print the
1503
+ questions as markdown with a "say the word if you want to change any"
1504
+ fallback. DO NOT proceed without real answers even inside \`/loop\` or
1505
+ other autonomous modes \u2014 stop and wait; the flow resumes when the user
1506
+ responds. Every question is a product decision the user is paying to
1507
+ make; \`recommended\` is a hint for them, not permission for you to decide.
1498
1508
 
1499
1509
  Do NOT re-call mist_plan or describe it as "still generating". Only
1500
1510
  re-call to submit the answers, or to poll when status is
@@ -1530,8 +1540,9 @@ Mistflow project so you understand its shape. Other actions: \`update\`,
1530
1540
  ### \`mist_plan({ description })\`
1531
1541
  Start a new plan. Pass the user's description EXACTLY as written \u2014 do NOT
1532
1542
  expand or add features. Returns \`status: "clarify"\` with \`questions[]\`
1533
- (render via AskUserQuestion) OR \`status: "ready"\` if the description was
1534
- specific enough.
1543
+ (render via your host's question tool \u2014 AskUserQuestion / request_user_input /
1544
+ quick pick / stop-and-wait for hosts without one) OR \`status: "ready"\` if
1545
+ the description was specific enough.
1535
1546
 
1536
1547
  Submit answers: \`mist_plan({ conversationId, answers: [{question, decisionKey, answer}, ...] })\`.
1537
1548
  Use the array form, not a \`{decisionKey: answer}\` map \u2014 multiple questions
@@ -1612,7 +1623,8 @@ This reference. Static \u2014 safe to call frequently.
1612
1623
  mist_plan({ description: "habit tracker" })
1613
1624
  \u2192 { status: "clarify", conversationId: "...", questions: [...] }
1614
1625
 
1615
- # AI renders questions via AskUserQuestion, collects answers:
1626
+ # AI renders questions via its host's tool (AskUserQuestion /
1627
+ # request_user_input / quick pick / stop-and-wait), collects answers:
1616
1628
  mist_plan({ conversationId, answers: [...] })
1617
1629
  \u2192 { status: "design_clarify_pending", conversationId: "..." }
1618
1630
 
@@ -1767,13 +1779,13 @@ is cheaper than building the wrong thing and iterating.
1767
1779
 
1768
1780
  mist_deploy({ action: "rollback", deploymentId: "<last known good>" })
1769
1781
  mist_deploy({ action: "status", deploymentId }) # poll
1770
- `,Do={name:"mist_help",description:Us,inputSchema:No.object({command:No.string().optional().describe("Optional: name of a specific tool (e.g. 'mist_plan') to get focused reference for. Omit to get the full catalog.")}),handler:async t=>{let{command:e}=t;if(!e)return c(jo);let s=e.startsWith("mist_")?e:`mist_${e}`,n=jo.split(`
1771
- `),o=new RegExp(`^### \`${s}`),i=-1,r=n.length;for(let a=0;a<n.length;a++)if(o.test(n[a]))i=a;else if(i>=0&&n[a].startsWith("### ")){r=a;break}else if(i>=0&&n[a].startsWith("## ")&&a>i){r=a;break}return i<0?c(`No tool named '${s}' found. Call mist_help with no args to see the full catalog.`,!0):c(n.slice(i,r).join(`
1772
- `).trim())}}});import{existsSync as tn,mkdirSync as Uo,readFileSync as Lo,readdirSync as Fo,writeFileSync as Ua}from"fs";import{join as Fe,resolve as La}from"path";import{homedir as cs}from"os";import{z as Rt}from"zod";function dt(t){return t.entity??t.name??"Unknown"}function pt(t){return typeof t=="string"?t:t.name}function ls(t){return typeof t=="string"?"text":t.type}function Mo(t){let e=t.sampleRows;if(Array.isArray(e)&&e.length>0)return e.slice(0,3).map(o=>{let i={};for(let[r,a]of Object.entries(o))i[r]=a==null?"":String(a);return i});let s=t.fields||[],n=[];for(let o=0;o<3;o++){let i={};for(let r of s)i[pt(r)]=Fa(pt(r),ls(r),dt(t),o);n.push(i)}return n}function Fa(t,e,s,n){let o=t.toLowerCase(),i=(e||"text").toLowerCase();return o==="name"||o==="title"?[`${s} Alpha`,`${s} Beta`,`${s} Gamma`][n]:o==="email"?["alice@example.com","bob@example.com","carol@example.com"][n]:o==="status"?["Active","Pending","Completed"][n]:o==="priority"?["High","Medium","Low"][n]:o.includes("date")||i==="date"?["Jan 15, 2024","Feb 20, 2024","Mar 10, 2024"][n]:o.includes("price")||o.includes("amount")||o.includes("cost")?["$29","$49","$99"][n]:o.includes("count")||o.includes("quantity")||i==="number"||i==="integer"?["12","34","56"][n]:i==="boolean"||i==="bool"?["Yes","No","Yes"][n]:o.includes("description")||i==="textarea"?["Brief description here","Another example entry","Third sample item"][n]:`Sample ${n+1}`}function qa(t){let e=t.dataModel??[],s=t.pages??[],n=t.design??{},o=s.map(m=>({label:m.name??m.path??"Page",route:m.path??m.route??"/"})),i=[],r=e.slice(0,3).map(m=>({name:dt(m),fields:(m.fields||[]).map(p=>({name:pt(p),type:ls(p)})),sampleData:Mo(m)})),a=[];t.primaryAction&&(a.push(`PRIMARY: ${t.primaryAction.action} \u2014 this is the first thing the user sees and does`),a.push(`SURFACE: ${t.primaryAction.dashboardSurface}`)),a.push(`METRICS: Key counts for ${e.map(m=>dt(m)).join(", ")}`),a.push("RECENT: Latest activity or items"),i.push({name:"Dashboard",type:"dashboard",route:"/dashboard",purpose:t.primaryAction?`Action surface \u2014 user comes here to ${t.primaryAction.action.toLowerCase()}. Not a stats display.`:`Overview of ${e.map(m=>dt(m)).join(", ")} with key metrics.`,informationHierarchy:a,interactionStates:["Empty state: new user, no data yet \u2014 show onboarding prompt","Loading state: skeleton placeholders for metrics and table","Populated state: real data with metrics, recent items, quick actions"],entities:r});let l=e[0];if(l){let m=dt(l),p=m.toLowerCase().endsWith("s")?m:`${m}s`;i.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(y=>pt(y)).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(y=>({name:pt(y),type:ls(y)})),sampleData:Mo(l)}]})}t.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")})&&i.push({name:"Landing Page",type:"landing",route:"/",purpose:`Convince visitors to sign up for ${t.name}. Answer: what is this, who is it for, why should I care.`,informationHierarchy:[`HERO: One sentence about what ${t.name} does \u2014 not "Transform your X", be specific`,"CTA: Sign up / Get started \u2014 one clear action","PROOF: What makes this valuable (features, not buzzwords)","SECONDARY CTA: Repeat the sign up prompt"],interactionStates:["Mobile: hero stacks vertically, nav collapses to hamburger","Desktop: hero side-by-side or centered, full nav"],entities:[]});let h=[];for(let m of e.slice(0,3)){let p=dt(m);(m.fields||[]).find(v=>{let b=pt(v).toLowerCase();return b==="name"||b==="title"})&&h.push(`What if a ${p.toLowerCase()}'s name is 47 characters? Does the layout break?`),h.push(`What if there are 0 ${p.toLowerCase()}s? 1? 500?`)}return t.authModel&&t.authModel!=="none"&&h.push("What does a brand-new user see? (no data, no setup)"),h.push("What if the network is slow? What loads first?"),{appName:t.name,summary:t.summary??"",screens:i,navigation:{style:t.navStyle??"sidebar",items:o},primaryAction:t.primaryAction??null,designDirection:{tone:n.tone??"professional",accentColor:n.accentColor??"blue",navStyle:t.navStyle??"sidebar",fonts:n.fonts??{heading:"Inter",body:"Inter"}},edgeCases:h}}function Ba(t,e,s){let n=[];n.push(`# Wireframe sketch for ${t.appName}`),n.push(""),n.push(`**${t.appName}** \u2014 ${t.summary}`),n.push(""),e&&(n.push("## Feedback to apply"),n.push(e),n.push("")),n.push("## Design principles"),n.push(""),n.push("Apply these when deciding layout and hierarchy:"),n.push("1. **Information hierarchy** \u2014 What does the user see first, second, third? The primary action is first. Metrics are second. Everything else is supporting."),n.push("2. **Interaction states** \u2014 Every screen has at least: empty, loading, populated. Show the populated state but add HTML comments noting the others."),n.push("3. **Edge case paranoia** \u2014 What if there are 0 items? 500 items? A 47-character name? Think about these and comment where they matter."),n.push(`4. **Subtraction** \u2014 "As little design as possible" (Dieter Rams). Every element earns its pixels. If removing something doesn't hurt, remove it.`),n.push("5. **Design for trust** \u2014 Clear labels, predictable layout, obvious actions. No mystery meat navigation."),n.push(""),n.push("## Wireframe rules (strict)"),n.push(""),n.push(`Write a **single self-contained HTML file** saved to \`${s}\`.`),n.push(""),n.push("The wireframe must:"),n.push("- Use **system fonts only** (`-apple-system, system-ui, sans-serif`) \u2014 no Google Fonts, no CDN"),n.push("- Use **inline CSS only** \u2014 no external stylesheets, no Tailwind CDN"),n.push("- Look **intentionally rough** \u2014 thin gray borders (#ddd), light backgrounds (#f8f8f8), no color, no shadows"),n.push("- Use **realistic placeholder content** that matches this specific app (sample data provided below) \u2014 NOT lorem ipsum"),n.push("- Include **HTML comments** explaining design decisions"),n.push("- Show **all screens in a single page** using tabs/sections that the user can click through"),n.push("- Be **responsive** \u2014 test that it looks reasonable at both 1200px and 375px widths"),n.push("- Include a small header bar showing: screen name tabs + the design direction summary"),n.push(""),n.push("The wireframe must NOT:"),n.push("- Use any color except grayscale (#333, #666, #999, #ddd, #f8f8f8, white)"),n.push("- Use any external dependencies \u2014 no CDN, no imports, no build step"),n.push("- Look polished \u2014 it should feel like a sketch on a whiteboard, not a finished product"),n.push("- Include decorative elements \u2014 no icons (use text labels), no illustrations, no gradients"),n.push(""),n.push("## Screens to wireframe"),n.push("");for(let o of t.screens){n.push(`### ${o.name} (\`${o.route}\`)`),n.push(`**Purpose**: ${o.purpose}`),n.push(""),n.push("**Information hierarchy** (render in this order, top to bottom):");for(let i of o.informationHierarchy)n.push(`- ${i}`);n.push(""),n.push("**Interaction states** (add HTML comments for non-visible states):");for(let i of o.interactionStates)n.push(`- ${i}`);if(n.push(""),o.entities.length>0){n.push("**Data model and sample content** (use this real data, not lorem ipsum):");for(let i of o.entities)n.push(`
1773
- **${i.name}** \u2014 fields: ${i.fields.map(r=>`${r.name} (${r.type})`).join(", ")}`),n.push("```json"),n.push(JSON.stringify(i.sampleData,null,2)),n.push("```");n.push("")}}n.push("## Navigation"),n.push(`**Style**: ${t.navigation.style} (use this layout)`),n.push("**Items**:");for(let o of t.navigation.items)n.push(`- ${o.label} \u2192 \`${o.route}\``);if(n.push(""),t.primaryAction&&(n.push("## Primary action (this drives the layout)"),n.push(`- **Action**: ${t.primaryAction.action}`),n.push(`- **Flow**: ${t.primaryAction.flow}`),n.push(`- **Dashboard must show**: ${t.primaryAction.dashboardSurface}`),n.push(""),n.push("The dashboard is an ACTION surface. The primary action should be the most prominent thing on the page \u2014 above the metrics, above the recent items. Users came here to DO something, not to look at numbers."),n.push("")),t.edgeCases.length>0){n.push("## Edge cases to consider"),n.push("Add HTML comments in the wireframe where these matter:");for(let o of t.edgeCases)n.push(`- ${o}`);n.push("")}return n.push("## Design direction (DO NOT apply to wireframe \u2014 this is for reference only)"),n.push(`The final app will use: ${t.designDirection.tone} tone, ${t.designDirection.accentColor} accent, ${t.designDirection.navStyle} nav, ${t.designDirection.fonts.heading} / ${t.designDirection.fonts.body} fonts.`),n.push("The wireframe is grayscale and rough. These tokens will be applied during the actual build."),n.push(""),n.push("## After writing the wireframe"),n.push(`1. Write the file to \`${s}\``),n.push(`2. Open it for the user: \`open "${s}"\``),n.push(`3. Tell the user: "Here's a rough wireframe of your app. Does the layout feel right? Let me know what to change, or I can start building if it's close."`),n.push("4. WAIT for the user's response. Do NOT call mist_init until they approve the layout."),n.join(`
1774
- `)}function qo(t){return Fe(cs(),".mistflow","mockup-state",`${t}.json`)}function za(t){let e=qo(t);if(!tn(e))return null;try{return JSON.parse(Lo(e,"utf-8"))}catch{return null}}function $o(t,e){let s=Fe(cs(),".mistflow","mockup-state");Uo(s,{recursive:!0}),Ua(qo(t),JSON.stringify(e,null,2))}function zo(t){let e=Fe(t,".mistflow","mockups");return tn(e)?Fo(e).filter(s=>s.endsWith(".html")).map(s=>Fe(e,s)):[]}var Ha,Bo,ds=_(()=>{"use strict";Oe();K();Ha=Rt.object({planId:Rt.string().min(1).describe("Plan ID from mist_plan. Required."),projectPath:Rt.string().optional().describe("Project directory (default: cwd). The mockup is written to <projectPath>/.mistflow/mockups/."),feedback:Rt.string().optional().describe("User feedback to apply to the next iteration."),approved:Rt.boolean().optional().describe("Mark the wireframe as approved (terminal \u2014 unlocks scaffolding).")}).refine(t=>!(t.feedback&&t.approved),{message:"Pass either 'feedback' or 'approved' \u2014 not both. Feedback iterates the design; approved locks it in."}),Bo={name:"mist_mockup",description:Fs,inputSchema:Ha,handler:async t=>{let e=t,{planId:s,feedback:n,approved:o}=e,i=La(e.projectPath??process.cwd()),r=Fe(cs(),".mistflow","plans",`${s}.json`);if(!tn(r))return c(`Plan not found for planId '${s}'. Call mist_plan to generate a plan first.`,!0);let a;try{a=JSON.parse(Lo(r,"utf-8"))}catch{return c("Failed to read plan file. Call mist_plan again.",!0)}let l=a.plan;if(!l)return c("Plan data is empty. Call mist_plan again.",!0);let d=za(s);d||(d={planId:s,iterationCount:0,approved:!1,screens:[],feedback:[]});let h=Fe(i,".mistflow","mockups");Uo(h,{recursive:!0});let m=`mockup-${s}.html`,p=Fe(h,m);if(o){d.approved=!0,$o(s,d);let k=tn(h)?Fo(h).filter(R=>R.endsWith(".html")).map(R=>Fe(".mistflow","mockups",R)):[];return c(JSON.stringify({status:"approved",message:`Wireframe approved after ${d.iterationCount} iteration(s). Layout direction is locked in.`,mockupFiles:k,nextAction:`Call mist_init with planId='${s}' and path='<absolute project path>' to scaffold the project. Mockups in .mistflow/mockups/ will be used as layout reference during implementation.`}))}d.iterationCount++,n&&d.feedback.push(n);let y=qa(l);d.screens=y.screens.map(k=>k.name),$o(s,d);let v=Ba(y,n??void 0,p),b=d.iterationCount>=3?"The wireframe is shaping up \u2014 want to keep refining the layout, or start building?":void 0,f=n?`Apply the user's feedback to ${p}. Rewrite the file, open it for review, then ask if they want more changes or are ready to build.`:`Generate the wireframe HTML following the wireframePrompt, write it to ${p}, then open it in the browser. Ask the user if the layout feels right.`;return c(JSON.stringify({status:"wireframe",iterationCount:d.iterationCount,screens:y.screens.map(k=>({name:k.name,type:k.type,route:k.route})),wireframePrompt:v,designDirection:y.designDirection,...b?{nudge:b}:{},mockupFile:m,mockupPath:p,nextAction:f}))}}});function nn(t){let e=[],s=/([^\s(]+)\((\d+),(\d+)\):\s*error\s+(TS\d+):\s*(.+)/g;for(let a of t.matchAll(s)){let[,l,d,h,m,p]=a;e.push({file:l,line:parseInt(d,10),column:parseInt(h,10),message:`${m}: ${p}`,humanMessage:`There is a type error in ${l} on line ${d}: ${p}`,suggestion:`Check line ${d} 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(let a of t.matchAll(n)){let[,l,d,h,m]=a;e.some(p=>p.file===l&&p.line===parseInt(d,10))||e.push({file:l,line:parseInt(d,10),column:parseInt(h,10),message:m,humanMessage:`There is an error in ${l} on line ${d}: ${m.trim()}`,suggestion:`Check line ${d} in ${l} and fix the issue.`})}let o=/Module not found:\s*(?:Error:\s*)?Can't resolve ['"]([^'"]+)['"]\s*(?:in\s*['"]?([^'"]+)['"]?)?/g;for(let a of t.matchAll(o)){let[,l,d]=a;e.push({file:d,message:`Module not found: ${l}`,humanMessage:`The file ${d??"your project"} is trying to import '${l}' which is not installed.`,suggestion:`Run npm install ${l}`})}let i=/Package subpath ['"]([^'"]+)['"] is not defined by "exports" in .*?node_modules\/([^/]+(?:\/[^/]+)?)\//g;for(let a of t.matchAll(i)){let[,l,d]=a;e.push({message:`ERR_PACKAGE_PATH_NOT_EXPORTED: ${d}${l}`,humanMessage:`The package '${d}' does not export the subpath '${l}'. This is usually caused by a version conflict between packages that depend on different major versions of '${d}'.`,suggestion:`Add '${d}' at the version that exports '${l}' as an optionalDependency in package.json (this pins it at root level and lets the other version nest). Then delete node_modules and package-lock.json, and run npm install.`})}let r=/SyntaxError:\s*([^\s:]+):\s*(.+?)\s*\((\d+):(\d+)\)/g;for(let a of t.matchAll(r)){let[,l,d,h,m]=a;e.some(p=>p.file===l&&p.line===parseInt(h,10))||e.push({file:l,line:parseInt(h,10),column:parseInt(m,10),message:`SyntaxError: ${d}`,humanMessage:`There is a syntax error in ${l} on line ${h}.`,suggestion:`Check line ${h} in ${l} for a missing closing bracket or unexpected token.`})}return e}var ps=_(()=>{"use strict"});import{z as us}from"zod";import{resolve as Wa}from"path";import{spawn as Ga}from"child_process";function Ja(t){return new Promise(e=>{let s=Ga("npm",["run","build"],{cwd:t,stdio:["ignore","pipe","pipe"]}),n="";s.stdout?.on("data",o=>{n+=o.toString()}),s.stderr?.on("data",o=>{n+=o.toString()}),s.on("error",o=>{e({exitCode:127,combined:`${n}
1775
- ${o.message}`})}),s.on("close",o=>{e({exitCode:o??1,combined:n})})})}var Va,Ho,Wo=_(()=>{"use strict";Oe();K();ps();Va=us.object({projectPath:us.string().optional().describe("Absolute path to the project directory. Defaults to cwd."),buildOutput:us.string().optional().describe("Build output to parse. If omitted, the tool runs `npm run build` in projectPath.")});Ho={name:"mist_debug",description:Ls,inputSchema:Va,handler:async t=>{let e=t,s=Wa(e.projectPath??process.cwd()),n=e.buildOutput??"";if(!e.buildOutput){let r=await Ja(s);if(r.exitCode===0)return c(JSON.stringify({errors:[],rawOutput:"",message:"Build succeeded with no errors."}));n=r.combined}let o=nn(n),i=n.slice(0,2e3);return o.length===0?c(JSON.stringify({errors:o,rawOutput:i,message:"Build output could not be parsed into structured errors. See rawOutput for the first 2KB."})):c(JSON.stringify({errors:o,rawOutput:i,message:`Found ${o.length} error${o.length===1?"":"s"} in the build output.`}))}}});function sn(t,e,s,n=3e3){if(e===void 0)return{stop:()=>{}};let o=0,i=!1,a=setInterval(()=>{i||(o++,t.notification({method:"notifications/progress",params:{progressToken:e,progress:o,message:s()}}).catch(()=>{}))},n);return{stop:()=>{i||(i=!0,clearInterval(a))}}}var ms=_(()=>{"use strict"});function Go(t,e,s){let n=s?.suggestedName||Ka(t),o=e.primaryActor==="both"?"Staff + Customers":e.primaryActor==="staff"?"Staff / Admin":e.primaryActor==="customers"?"End Users":"Users",i=e.audienceType??(e.surfaceType==="internal-tool"?"internal":(e.primaryActor==="customers"||e.primaryActor==="both","b2c")),r=e.surfaceType==="internal-tool"?"Internal tool":e.surfaceType==="marketplace"?"Marketplace":e.surfaceType==="content-site"?"Content site":e.surfaceType==="game"?"Game":"App",a;if(s?.suggestedFeatures&&s.suggestedFeatures.length>0)a=s.suggestedFeatures.map(l=>({name:l.name,description:l.description,checked:l.recommended,source:l.recommended?"explicit":"suggested"}));else{let l=`${t} ${e.primaryAction||""}`;a=[];for(let d of Ya){let h=d.keywords.test(l),m=d.condition(e);(h||m)&&a.push({name:d.name,description:d.description,checked:h,source:h?"explicit":"suggested"})}}return{name:n,audience:o,audienceType:i,surfaceType:r,primaryAction:e.primaryAction||"manage items",features:a,publicLanding:e.publicLanding??!0,authModel:e.authModel??"email",dbProvider:e.dbProvider??"neon",integrations:e.integrations??[],language:s?.language||"English"}}function Vo(t){let e=t.features.filter(a=>a.checked),s=t.features.filter(a=>!a.checked),n={email:"Email sign-up",none:"No login (public)",social:"Social login","invite-only":"Invite-only"},o={neon:"Postgres",turso:"SQLite (legacy)"},i={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=[`**${t.name}** \u2014 ${t.surfaceType} for ${t.audience}`,`Audience: ${i[t.audienceType]??t.audienceType}`,`Primary action: ${t.primaryAction}`,`Access: ${n[t.authModel]??t.authModel} | Database: ${o[t.dbProvider]??t.dbProvider}${t.publicLanding?" | Landing page: Yes":""}${t.language&&t.language!=="English"?` | Language: ${t.language}`:""}`,""];if(e.length>0){r.push("**Included:**");for(let a of e)r.push(` \u2713 ${a.name} \u2014 ${a.description}`)}if(t.integrations.length>0&&(r.push(""),r.push(`**Integrations:** ${t.integrations.join(", ")}`)),s.length>0){r.push(""),r.push("**Available to add:**");for(let a of s)r.push(` \u25CB ${a.name} \u2014 ${a.description}`)}return r.join(`
1776
- `)}function Ka(t){let e=t.match(/\b(?:called|named)\s+["']?([A-Za-z][A-Za-z0-9 ]{1,30})["']?/i);if(e)return hs(e[1]);let s=new Set(["build","create","make","a","an","the","for","me","my","app","application","website","web","tool","system","platform","using","with","and","that","this","want","need","please","can","you","i","mist","mistflow"]),o=t.toLowerCase().replace(/[^a-z0-9\s]/g,"").split(/\s+/).filter(i=>i.length>2&&!s.has(i)).slice(0,3);return o.length===0?"my-app":o.join("-")}function hs(t){return t.toLowerCase().replace(/[^a-z0-9\s]/g,"").trim().replace(/\s+/g,"-")}var Ya,Jo=_(()=>{"use strict";Ya=[{name:"Dashboard",description:"Overview with key stats and today's activity",condition:t=>t.surfaceType==="internal-tool"||t.surfaceType==="customer-app",keywords:/\b(dashboard|overview|home.?page|stats)\b/i},{name:"Landing Page",description:"Public page explaining what this does",condition:t=>t.publicLanding===!0,keywords:/\b(landing|marketing|hero|homepage)\b/i},{name:"Scheduling / Booking",description:"Calendar, time slots, reservations",condition:t=>t.scheduling===!0,keywords:/\b(schedul|book|reserv|appointment|calendar|slot)\b/i},{name:"Payments / Billing",description:"Charge users, invoices, subscriptions (experimental \u2014 Stripe integration is early-stage)",condition:()=>!1,keywords:/\b(payment|billing|invoice|subscription|checkout|stripe)\b/i},{name:"Admin Panel",description:"Manage users, roles, and content",condition:t=>t.multiRole===!0||t.primaryActor==="both",keywords:/\b(admin|panel|manage.?user|moderat)\b/i},{name:"User Profiles",description:"Account pages, settings, preferences",condition:t=>t.primaryActor==="customers"||t.primaryActor==="both",keywords:/\b(profile|account|settings|preferences)\b/i},{name:"Search / Browse",description:"Find and filter content or listings",condition:t=>t.surfaceType==="marketplace",keywords:/\b(search|browse|filter|discover|explore)\b/i},{name:"Email Notifications",description:"Welcome emails, alerts, reminders",condition:t=>t.integrations?.includes("email")===!0,keywords:/\b(notification|alert|reminder|email.?notif|sms|welcome.?email)\b/i},{name:"Analytics / Reports",description:"Usage stats, trends, data exports",condition:()=>!1,keywords:/\b(analytics|report|chart|trend|insight|metric)\b/i},{name:"File Uploads",description:"Images, documents, attachments",condition:t=>t.integrations?.includes("file-uploads")===!0,keywords:/\b(upload|image|photo|attachment|document|gallery|file)\b/i},{name:"AI Features",description:"Chatbot, content generation, AI assistant",condition:t=>t.integrations?.includes("ai")===!0,keywords:/\b(ai|chatbot|gpt|llm|generat|assistant)\b/i},{name:"Maps / Location",description:"Google Maps, location search, geolocation",condition:t=>t.integrations?.includes("maps")===!0,keywords:/\b(map|location|address|geo|places)\b/i},{name:"Voice / TTS",description:"Text-to-speech, voice notes, audio generation",condition:()=>!1,keywords:/\b(voice|tts|text.?to.?speech|audio|speak|narrat|podcast|elevenlabs)\b/i},{name:"SMS / Text Messages",description:"Send SMS notifications, OTP verification",condition:t=>t.integrations?.includes("sms")===!0,keywords:/\b(sms|text.?message|twilio|otp|verification.?code|phone.?verif)\b/i},{name:"Web Scraping",description:"Scrape URLs, crawl websites, extract structured data",condition:()=>!1,keywords:/\b(scrape|crawl|web.?scrap|extract.?from.?url|read.?url|ingest.?web|firecrawl)\b/i},{name:"Chat / Messaging",description:"Real-time messaging between users",condition:()=>!1,keywords:/\b(chat|messag|inbox|conversation|dm)\b/i},{name:"Events / Tournaments",description:"Create and manage events, registrations",condition:()=>!1,keywords:/\b(event|tournament|competition|league|registration)\b/i},{name:"High Scores",description:"Track and display top scores with a leaderboard",condition:t=>t.surfaceType==="game",keywords:/\b(high.?score|leaderboard|top.?score|ranking|scoreboard)\b/i},{name:"Save Progress",description:"Save and resume game state between sessions",condition:t=>t.surfaceType==="game",keywords:/\b(save|progress|resume|checkpoint|continue)\b/i},{name:"Guest Play",description:"Play immediately without signing up, optionally link account later",condition:t=>t.surfaceType==="game",keywords:/\b(guest|anonymous|no.?login|play.?now|instant.?play)\b/i},{name:"Levels / Stages",description:"Progressive difficulty with unlockable stages",condition:()=>!1,keywords:/\b(level|stage|unlock|difficult|progress|world)\b/i},{name:"Achievements",description:"Badges, trophies, and milestones for player accomplishments",condition:()=>!1,keywords:/\b(achieve|badge|trophy|milestone|reward|unlock)\b/i},{name:"Daily Challenge",description:"New puzzle or challenge every day to keep players coming back",condition:()=>!1,keywords:/\b(daily|challenge|streak|word.?of.?the.?day|puzzle.?of)\b/i}]});function gs(t,e,s){return e.includes(t)?t:s}function ce(t){return String(t??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}function ut(t,e){return typeof t!="string"?e:/^#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$/.test(t.trim())?t.trim():e}function el(t){let e=new Set;for(let n of t)n.fonts?.display&&e.add(n.fonts.display),n.fonts?.body&&e.add(n.fonts.body);return e.size===0?"":`https://fonts.googleapis.com/css2?${[...e].map(n=>`family=${n.trim().replace(/\s+/g,"+").replace(/[^A-Za-z0-9+]/g,"")}:wght@400;700`).join("&")}&display=swap`}function Yo(t){let e=(t||"").trim(),s=e.toLowerCase(),n=/mono|courier|code/.test(s)?"ui-monospace, monospace":/serif|garamond|bodoni|fraunces|playfair|lora|sentient|migra|sectra|cormorant|abril|crimson/.test(s)?"Georgia, serif":"system-ui, sans-serif";return e?`"${e.replace(/"/g,"")}", ${n}`:n}function tl(t){switch(t){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 nl(){let t=`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`
1782
+ `,On={name:"mist_help",description:$r,inputSchema:Dn.object({command:Dn.string().optional().describe("Optional: name of a specific tool (e.g. 'mist_plan') to get focused reference for. Omit to get the full catalog.")}),handler:async t=>{let{command:e}=t;if(!e)return c(jn);let r=e.startsWith("mist_")?e:`mist_${e}`,o=jn.split(`
1783
+ `),n=new RegExp(`^### \`${r}`),i=-1,s=o.length;for(let a=0;a<o.length;a++)if(n.test(o[a]))i=a;else if(i>=0&&o[a].startsWith("### ")){s=a;break}else if(i>=0&&o[a].startsWith("## ")&&a>i){s=a;break}return i<0?c(`No tool named '${r}' found. Call mist_help with no args to see the full catalog.`,!0):c(o.slice(i,s).join(`
1784
+ `).trim())}}});import{existsSync as oo,mkdirSync as $n,readFileSync as Fn,readdirSync as qn,writeFileSync as ll}from"fs";import{join as qe,resolve as cl}from"path";import{homedir as dr}from"os";import{z as _t}from"zod";function pt(t){return t.entity??t.name??"Unknown"}function ut(t){return typeof t=="string"?t:t.name}function cr(t){return typeof t=="string"?"text":t.type}function Ln(t){let e=t.sampleRows;if(Array.isArray(e)&&e.length>0)return e.slice(0,3).map(n=>{let i={};for(let[s,a]of Object.entries(n))i[s]=a==null?"":String(a);return i});let r=t.fields||[],o=[];for(let n=0;n<3;n++){let i={};for(let s of r)i[ut(s)]=dl(ut(s),cr(s),pt(t),n);o.push(i)}return o}function dl(t,e,r,o){let n=t.toLowerCase(),i=(e||"text").toLowerCase();return n==="name"||n==="title"?[`${r} Alpha`,`${r} Beta`,`${r} Gamma`][o]:n==="email"?["alice@example.com","bob@example.com","carol@example.com"][o]:n==="status"?["Active","Pending","Completed"][o]:n==="priority"?["High","Medium","Low"][o]:n.includes("date")||i==="date"?["Jan 15, 2024","Feb 20, 2024","Mar 10, 2024"][o]:n.includes("price")||n.includes("amount")||n.includes("cost")?["$29","$49","$99"][o]:n.includes("count")||n.includes("quantity")||i==="number"||i==="integer"?["12","34","56"][o]:i==="boolean"||i==="bool"?["Yes","No","Yes"][o]:n.includes("description")||i==="textarea"?["Brief description here","Another example entry","Third sample item"][o]:`Sample ${o+1}`}function pl(t){let e=t.dataModel??[],r=t.pages??[],o=t.design??{},n=r.map(u=>({label:u.name??u.path??"Page",route:u.path??u.route??"/"})),i=[],s=e.slice(0,3).map(u=>({name:pt(u),fields:(u.fields||[]).map(p=>({name:ut(p),type:cr(p)})),sampleData:Ln(u)})),a=[];t.primaryAction&&(a.push(`PRIMARY: ${t.primaryAction.action} \u2014 this is the first thing the user sees and does`),a.push(`SURFACE: ${t.primaryAction.dashboardSurface}`)),a.push(`METRICS: Key counts for ${e.map(u=>pt(u)).join(", ")}`),a.push("RECENT: Latest activity or items"),i.push({name:"Dashboard",type:"dashboard",route:"/dashboard",purpose:t.primaryAction?`Action surface \u2014 user comes here to ${t.primaryAction.action.toLowerCase()}. Not a stats display.`:`Overview of ${e.map(u=>pt(u)).join(", ")} with key metrics.`,informationHierarchy:a,interactionStates:["Empty state: new user, no data yet \u2014 show onboarding prompt","Loading state: skeleton placeholders for metrics and table","Populated state: real data with metrics, recent items, quick actions"],entities:s});let l=e[0];if(l){let u=pt(l),p=u.toLowerCase().endsWith("s")?u:`${u}s`;i.push({name:`${u} List`,type:"detail",route:`/${p.toLowerCase()}`,purpose:`Browse, search, and manage ${p.toLowerCase()}. Create new ${u.toLowerCase()}s.`,informationHierarchy:[`HEADER: "${p}" title + "Add ${u}" button`,"SEARCH: Filter/search bar \u2014 users will have many items",`TABLE: ${(l.fields||[]).slice(0,5).map(f=>ut(f)).join(", ")} columns`,"ROW ACTIONS: Edit, delete on each row"],interactionStates:[`Empty state: "No ${p.toLowerCase()} yet" with create CTA`,"Loading state: skeleton table rows",`Search with no results: "No ${p.toLowerCase()} matching..." with clear filter`],entities:[{name:u,fields:(l.fields||[]).map(f=>({name:ut(f),type:cr(f)})),sampleData:Ln(l)}]})}t.steps.some(u=>{let p=`${u.name??u.title??""} ${u.description??""}`.toLowerCase();return p.includes("landing")||p.includes("hero")||p.includes("marketing")||p.includes("homepage")})&&i.push({name:"Landing Page",type:"landing",route:"/",purpose:`Convince visitors to sign up for ${t.name}. Answer: what is this, who is it for, why should I care.`,informationHierarchy:[`HERO: One sentence about what ${t.name} does \u2014 not "Transform your X", be specific`,"CTA: Sign up / Get started \u2014 one clear action","PROOF: What makes this valuable (features, not buzzwords)","SECONDARY CTA: Repeat the sign up prompt"],interactionStates:["Mobile: hero stacks vertically, nav collapses to hamburger","Desktop: hero side-by-side or centered, full nav"],entities:[]});let h=[];for(let u of e.slice(0,3)){let p=pt(u);(u.fields||[]).find(v=>{let b=ut(v).toLowerCase();return b==="name"||b==="title"})&&h.push(`What if a ${p.toLowerCase()}'s name is 47 characters? Does the layout break?`),h.push(`What if there are 0 ${p.toLowerCase()}s? 1? 500?`)}return t.authModel&&t.authModel!=="none"&&h.push("What does a brand-new user see? (no data, no setup)"),h.push("What if the network is slow? What loads first?"),{appName:t.name,summary:t.summary??"",screens:i,navigation:{style:t.navStyle??"sidebar",items:n},primaryAction:t.primaryAction??null,designDirection:{tone:o.tone??"professional",accentColor:o.accentColor??"blue",navStyle:t.navStyle??"sidebar",fonts:o.fonts??{heading:"Inter",body:"Inter"}},edgeCases:h}}function ul(t,e,r){let o=[];o.push(`# Wireframe sketch for ${t.appName}`),o.push(""),o.push(`**${t.appName}** \u2014 ${t.summary}`),o.push(""),e&&(o.push("## Feedback to apply"),o.push(e),o.push("")),o.push("## Design principles"),o.push(""),o.push("Apply these when deciding layout and hierarchy:"),o.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."),o.push("2. **Interaction states** \u2014 Every screen has at least: empty, loading, populated. Show the populated state but add HTML comments noting the others."),o.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."),o.push(`4. **Subtraction** \u2014 "As little design as possible" (Dieter Rams). Every element earns its pixels. If removing something doesn't hurt, remove it.`),o.push("5. **Design for trust** \u2014 Clear labels, predictable layout, obvious actions. No mystery meat navigation."),o.push(""),o.push("## Wireframe rules (strict)"),o.push(""),o.push(`Write a **single self-contained HTML file** saved to \`${r}\`.`),o.push(""),o.push("The wireframe must:"),o.push("- Use **system fonts only** (`-apple-system, system-ui, sans-serif`) \u2014 no Google Fonts, no CDN"),o.push("- Use **inline CSS only** \u2014 no external stylesheets, no Tailwind CDN"),o.push("- Look **intentionally rough** \u2014 thin gray borders (#ddd), light backgrounds (#f8f8f8), no color, no shadows"),o.push("- Use **realistic placeholder content** that matches this specific app (sample data provided below) \u2014 NOT lorem ipsum"),o.push("- Include **HTML comments** explaining design decisions"),o.push("- Show **all screens in a single page** using tabs/sections that the user can click through"),o.push("- Be **responsive** \u2014 test that it looks reasonable at both 1200px and 375px widths"),o.push("- Include a small header bar showing: screen name tabs + the design direction summary"),o.push(""),o.push("The wireframe must NOT:"),o.push("- Use any color except grayscale (#333, #666, #999, #ddd, #f8f8f8, white)"),o.push("- Use any external dependencies \u2014 no CDN, no imports, no build step"),o.push("- Look polished \u2014 it should feel like a sketch on a whiteboard, not a finished product"),o.push("- Include decorative elements \u2014 no icons (use text labels), no illustrations, no gradients"),o.push(""),o.push("## Screens to wireframe"),o.push("");for(let n of t.screens){o.push(`### ${n.name} (\`${n.route}\`)`),o.push(`**Purpose**: ${n.purpose}`),o.push(""),o.push("**Information hierarchy** (render in this order, top to bottom):");for(let i of n.informationHierarchy)o.push(`- ${i}`);o.push(""),o.push("**Interaction states** (add HTML comments for non-visible states):");for(let i of n.interactionStates)o.push(`- ${i}`);if(o.push(""),n.entities.length>0){o.push("**Data model and sample content** (use this real data, not lorem ipsum):");for(let i of n.entities)o.push(`
1785
+ **${i.name}** \u2014 fields: ${i.fields.map(s=>`${s.name} (${s.type})`).join(", ")}`),o.push("```json"),o.push(JSON.stringify(i.sampleData,null,2)),o.push("```");o.push("")}}o.push("## Navigation"),o.push(`**Style**: ${t.navigation.style} (use this layout)`),o.push("**Items**:");for(let n of t.navigation.items)o.push(`- ${n.label} \u2192 \`${n.route}\``);if(o.push(""),t.primaryAction&&(o.push("## Primary action (this drives the layout)"),o.push(`- **Action**: ${t.primaryAction.action}`),o.push(`- **Flow**: ${t.primaryAction.flow}`),o.push(`- **Dashboard must show**: ${t.primaryAction.dashboardSurface}`),o.push(""),o.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."),o.push("")),t.edgeCases.length>0){o.push("## Edge cases to consider"),o.push("Add HTML comments in the wireframe where these matter:");for(let n of t.edgeCases)o.push(`- ${n}`);o.push("")}return o.push("## Design direction (DO NOT apply to wireframe \u2014 this is for reference only)"),o.push(`The final app will use: ${t.designDirection.tone} tone, ${t.designDirection.accentColor} accent, ${t.designDirection.navStyle} nav, ${t.designDirection.fonts.heading} / ${t.designDirection.fonts.body} fonts.`),o.push("The wireframe is grayscale and rough. These tokens will be applied during the actual build."),o.push(""),o.push("## After writing the wireframe"),o.push(`1. Write the file to \`${r}\``),o.push(`2. Open it for the user: \`open "${r}"\``),o.push(`3. Tell the user: "Here's a rough wireframe of your app. Does the layout feel right? Let me know what to change, or I can start building if it's close."`),o.push("4. WAIT for the user's response. Do NOT call mist_init until they approve the layout."),o.join(`
1786
+ `)}function Bn(t){return qe(dr(),".mistflow","mockup-state",`${t}.json`)}function ml(t){let e=Bn(t);if(!oo(e))return null;try{return JSON.parse(Fn(e,"utf-8"))}catch{return null}}function Un(t,e){let r=qe(dr(),".mistflow","mockup-state");$n(r,{recursive:!0}),ll(Bn(t),JSON.stringify(e,null,2))}function Hn(t){let e=qe(t,".mistflow","mockups");return oo(e)?qn(e).filter(r=>r.endsWith(".html")).map(r=>qe(e,r)):[]}var hl,zn,pr=S(()=>{"use strict";Me();X();hl=_t.object({planId:_t.string().min(1).describe("Plan ID from mist_plan. Required."),projectPath:_t.string().optional().describe("Project directory (default: cwd). The mockup is written to <projectPath>/.mistflow/mockups/."),feedback:_t.string().optional().describe("User feedback to apply to the next iteration."),approved:_t.boolean().optional().describe("Mark the wireframe as approved (terminal \u2014 unlocks scaffolding).")}).refine(t=>!(t.feedback&&t.approved),{message:"Pass either 'feedback' or 'approved' \u2014 not both. Feedback iterates the design; approved locks it in."}),zn={name:"mist_mockup",description:qr,inputSchema:hl,handler:async t=>{let e=t,{planId:r,feedback:o,approved:n}=e,i=cl(e.projectPath??process.cwd()),s=qe(dr(),".mistflow","plans",`${r}.json`);if(!oo(s))return c(`Plan not found for planId '${r}'. Call mist_plan to generate a plan first.`,!0);let a;try{a=JSON.parse(Fn(s,"utf-8"))}catch{return c("Failed to read plan file. Call mist_plan again.",!0)}let l=a.plan;if(!l)return c("Plan data is empty. Call mist_plan again.",!0);let d=ml(r);d||(d={planId:r,iterationCount:0,approved:!1,screens:[],feedback:[]});let h=qe(i,".mistflow","mockups");$n(h,{recursive:!0});let u=`mockup-${r}.html`,p=qe(h,u);if(n){d.approved=!0,Un(r,d);let x=oo(h)?qn(h).filter(R=>R.endsWith(".html")).map(R=>qe(".mistflow","mockups",R)):[];return c(JSON.stringify({status:"approved",message:`Wireframe approved after ${d.iterationCount} iteration(s). Layout direction is locked in.`,mockupFiles:x,nextAction:`Call mist_init with planId='${r}' and path='<absolute project path>' to scaffold the project. Mockups in .mistflow/mockups/ will be used as layout reference during implementation.`}))}d.iterationCount++,o&&d.feedback.push(o);let f=pl(l);d.screens=f.screens.map(x=>x.name),Un(r,d);let v=ul(f,o??void 0,p),b=d.iterationCount>=3?"The wireframe is shaping up \u2014 want to keep refining the layout, or start building?":void 0,g=o?`Apply the user's feedback to ${p}. Rewrite the file, open it for review, then ask if they want more changes or are ready to build.`:`Generate the wireframe HTML following the wireframePrompt, write it to ${p}, then open it in the browser. Ask the user if the layout feels right.`;return c(JSON.stringify({status:"wireframe",iterationCount:d.iterationCount,screens:f.screens.map(x=>({name:x.name,type:x.type,route:x.route})),wireframePrompt:v,designDirection:f.designDirection,...b?{nudge:b}:{},mockupFile:u,mockupPath:p,nextAction:g}))}}});function ro(t){let e=[],r=/([^\s(]+)\((\d+),(\d+)\):\s*error\s+(TS\d+):\s*(.+)/g;for(let a of t.matchAll(r)){let[,l,d,h,u,p]=a;e.push({file:l,line:parseInt(d,10),column:parseInt(h,10),message:`${u}: ${p}`,humanMessage:`There is a type error in ${l} on line ${d}: ${p}`,suggestion:`Check line ${d} in ${l}. ${u==="TS2345"?"The types of the arguments do not match.":`Fix the ${u} error.`}`})}let o=/(?:Error:\s*)?\.\/([^\s:]+):(\d+):(\d+)\s*\n\s*(.+)/g;for(let a of t.matchAll(o)){let[,l,d,h,u]=a;e.some(p=>p.file===l&&p.line===parseInt(d,10))||e.push({file:l,line:parseInt(d,10),column:parseInt(h,10),message:u,humanMessage:`There is an error in ${l} on line ${d}: ${u.trim()}`,suggestion:`Check line ${d} in ${l} and fix the issue.`})}let n=/Module not found:\s*(?:Error:\s*)?Can't resolve ['"]([^'"]+)['"]\s*(?:in\s*['"]?([^'"]+)['"]?)?/g;for(let a of t.matchAll(n)){let[,l,d]=a;e.push({file:d,message:`Module not found: ${l}`,humanMessage:`The file ${d??"your project"} is trying to import '${l}' which is not installed.`,suggestion:`Run npm install ${l}`})}let i=/Package subpath ['"]([^'"]+)['"] is not defined by "exports" in .*?node_modules\/([^/]+(?:\/[^/]+)?)\//g;for(let a of t.matchAll(i)){let[,l,d]=a;e.push({message:`ERR_PACKAGE_PATH_NOT_EXPORTED: ${d}${l}`,humanMessage:`The package '${d}' does not export the subpath '${l}'. This is usually caused by a version conflict between packages that depend on different major versions of '${d}'.`,suggestion:`Add '${d}' at the version that exports '${l}' as an optionalDependency in package.json (this pins it at root level and lets the other version nest). Then delete node_modules and package-lock.json, and run npm install.`})}let s=/SyntaxError:\s*([^\s:]+):\s*(.+?)\s*\((\d+):(\d+)\)/g;for(let a of t.matchAll(s)){let[,l,d,h,u]=a;e.some(p=>p.file===l&&p.line===parseInt(h,10))||e.push({file:l,line:parseInt(h,10),column:parseInt(u,10),message:`SyntaxError: ${d}`,humanMessage:`There is a syntax error in ${l} on line ${h}.`,suggestion:`Check line ${h} in ${l} for a missing closing bracket or unexpected token.`})}return e}var ur=S(()=>{"use strict"});import{z as mr}from"zod";import{resolve as gl}from"path";import{spawn as fl}from"child_process";function bl(t){return new Promise(e=>{let r=fl("npm",["run","build"],{cwd:t,stdio:["ignore","pipe","pipe"]}),o="";r.stdout?.on("data",n=>{o+=n.toString()}),r.stderr?.on("data",n=>{o+=n.toString()}),r.on("error",n=>{e({exitCode:127,combined:`${o}
1787
+ ${n.message}`})}),r.on("close",n=>{e({exitCode:n??1,combined:o})})})}var yl,Wn,Gn=S(()=>{"use strict";Me();X();ur();yl=mr.object({projectPath:mr.string().optional().describe("Absolute path to the project directory. Defaults to cwd."),buildOutput:mr.string().optional().describe("Build output to parse. If omitted, the tool runs `npm run build` in projectPath.")});Wn={name:"mist_debug",description:Fr,inputSchema:yl,handler:async t=>{let e=t,r=gl(e.projectPath??process.cwd()),o=e.buildOutput??"";if(!e.buildOutput){let s=await bl(r);if(s.exitCode===0)return c(JSON.stringify({errors:[],rawOutput:"",message:"Build succeeded with no errors."}));o=s.combined}let n=ro(o),i=o.slice(0,2e3);return n.length===0?c(JSON.stringify({errors:n,rawOutput:i,message:"Build output could not be parsed into structured errors. See rawOutput for the first 2KB."})):c(JSON.stringify({errors:n,rawOutput:i,message:`Found ${n.length} error${n.length===1?"":"s"} in the build output.`}))}}});function no(t,e,r,o=3e3){if(e===void 0)return{stop:()=>{}};let n=0,i=!1,a=setInterval(()=>{i||(n++,t.notification({method:"notifications/progress",params:{progressToken:e,progress:n,message:r()}}).catch(()=>{}))},o);return{stop:()=>{i||(i=!0,clearInterval(a))}}}var hr=S(()=>{"use strict"});function Vn(t,e,r){let o=r?.suggestedName||vl(t),n=e.primaryActor==="both"?"Staff + Customers":e.primaryActor==="staff"?"Staff / Admin":e.primaryActor==="customers"?"End Users":"Users",i=e.audienceType??(e.surfaceType==="internal-tool"?"internal":(e.primaryActor==="customers"||e.primaryActor==="both","b2c")),s=e.surfaceType==="internal-tool"?"Internal tool":e.surfaceType==="marketplace"?"Marketplace":e.surfaceType==="content-site"?"Content site":e.surfaceType==="game"?"Game":"App",a;if(r?.suggestedFeatures&&r.suggestedFeatures.length>0)a=r.suggestedFeatures.map(l=>({name:l.name,description:l.description,checked:l.recommended,source:l.recommended?"explicit":"suggested"}));else{let l=`${t} ${e.primaryAction||""}`;a=[];for(let d of wl){let h=d.keywords.test(l),u=d.condition(e);(h||u)&&a.push({name:d.name,description:d.description,checked:h,source:h?"explicit":"suggested"})}}return{name:o,audience:n,audienceType:i,surfaceType:s,primaryAction:e.primaryAction||"manage items",features:a,publicLanding:e.publicLanding??!0,authModel:e.authModel??"email",dbProvider:e.dbProvider??"neon",integrations:e.integrations??[],language:r?.language||"English"}}function Jn(t){let e=t.features.filter(a=>a.checked),r=t.features.filter(a=>!a.checked),o={email:"Email sign-up",none:"No login (public)",social:"Social login","invite-only":"Invite-only"},n={neon:"Postgres",turso:"SQLite (legacy)"},i={b2c:"Your customers use this app (business-to-customer)",b2b:"Other businesses sign up for this (SaaS platform)",internal:"Internal team tool (staff only)"},s=[`**${t.name}** \u2014 ${t.surfaceType} for ${t.audience}`,`Audience: ${i[t.audienceType]??t.audienceType}`,`Primary action: ${t.primaryAction}`,`Access: ${o[t.authModel]??t.authModel} | Database: ${n[t.dbProvider]??t.dbProvider}${t.publicLanding?" | Landing page: Yes":""}${t.language&&t.language!=="English"?` | Language: ${t.language}`:""}`,""];if(e.length>0){s.push("**Included:**");for(let a of e)s.push(` \u2713 ${a.name} \u2014 ${a.description}`)}if(t.integrations.length>0&&(s.push(""),s.push(`**Integrations:** ${t.integrations.join(", ")}`)),r.length>0){s.push(""),s.push("**Available to add:**");for(let a of r)s.push(` \u25CB ${a.name} \u2014 ${a.description}`)}return s.join(`
1788
+ `)}function vl(t){let e=t.match(/\b(?:called|named)\s+["']?([A-Za-z][A-Za-z0-9 ]{1,30})["']?/i);if(e)return gr(e[1]);let r=new Set(["build","create","make","a","an","the","for","me","my","app","application","website","web","tool","system","platform","using","with","and","that","this","want","need","please","can","you","i","mist","mistflow"]),n=t.toLowerCase().replace(/[^a-z0-9\s]/g,"").split(/\s+/).filter(i=>i.length>2&&!r.has(i)).slice(0,3);return n.length===0?"my-app":n.join("-")}function gr(t){return t.toLowerCase().replace(/[^a-z0-9\s]/g,"").trim().replace(/\s+/g,"-")}var wl,Kn=S(()=>{"use strict";wl=[{name:"Dashboard",description:"Overview with key stats and today's activity",condition:t=>t.surfaceType==="internal-tool"||t.surfaceType==="customer-app",keywords:/\b(dashboard|overview|home.?page|stats)\b/i},{name:"Landing Page",description:"Public page explaining what this does",condition:t=>t.publicLanding===!0,keywords:/\b(landing|marketing|hero|homepage)\b/i},{name:"Scheduling / Booking",description:"Calendar, time slots, reservations",condition:t=>t.scheduling===!0,keywords:/\b(schedul|book|reserv|appointment|calendar|slot)\b/i},{name:"Payments / Billing",description:"Charge users, invoices, subscriptions (experimental \u2014 Stripe integration is early-stage)",condition:()=>!1,keywords:/\b(payment|billing|invoice|subscription|checkout|stripe)\b/i},{name:"Admin Panel",description:"Manage users, roles, and content",condition:t=>t.multiRole===!0||t.primaryActor==="both",keywords:/\b(admin|panel|manage.?user|moderat)\b/i},{name:"User Profiles",description:"Account pages, settings, preferences",condition:t=>t.primaryActor==="customers"||t.primaryActor==="both",keywords:/\b(profile|account|settings|preferences)\b/i},{name:"Search / Browse",description:"Find and filter content or listings",condition:t=>t.surfaceType==="marketplace",keywords:/\b(search|browse|filter|discover|explore)\b/i},{name:"Email Notifications",description:"Welcome emails, alerts, reminders",condition:t=>t.integrations?.includes("email")===!0,keywords:/\b(notification|alert|reminder|email.?notif|sms|welcome.?email)\b/i},{name:"Analytics / Reports",description:"Usage stats, trends, data exports",condition:()=>!1,keywords:/\b(analytics|report|chart|trend|insight|metric)\b/i},{name:"File Uploads",description:"Images, documents, attachments",condition:t=>t.integrations?.includes("file-uploads")===!0,keywords:/\b(upload|image|photo|attachment|document|gallery|file)\b/i},{name:"AI Features",description:"Chatbot, content generation, AI assistant",condition:t=>t.integrations?.includes("ai")===!0,keywords:/\b(ai|chatbot|gpt|llm|generat|assistant)\b/i},{name:"Maps / Location",description:"Google Maps, location search, geolocation",condition:t=>t.integrations?.includes("maps")===!0,keywords:/\b(map|location|address|geo|places)\b/i},{name:"Voice / TTS",description:"Text-to-speech, voice notes, audio generation",condition:()=>!1,keywords:/\b(voice|tts|text.?to.?speech|audio|speak|narrat|podcast|elevenlabs)\b/i},{name:"SMS / Text Messages",description:"Send SMS notifications, OTP verification",condition:t=>t.integrations?.includes("sms")===!0,keywords:/\b(sms|text.?message|twilio|otp|verification.?code|phone.?verif)\b/i},{name:"Web Scraping",description:"Scrape URLs, crawl websites, extract structured data",condition:()=>!1,keywords:/\b(scrape|crawl|web.?scrap|extract.?from.?url|read.?url|ingest.?web|firecrawl)\b/i},{name:"Chat / Messaging",description:"Real-time messaging between users",condition:()=>!1,keywords:/\b(chat|messag|inbox|conversation|dm)\b/i},{name:"Events / Tournaments",description:"Create and manage events, registrations",condition:()=>!1,keywords:/\b(event|tournament|competition|league|registration)\b/i},{name:"High Scores",description:"Track and display top scores with a leaderboard",condition:t=>t.surfaceType==="game",keywords:/\b(high.?score|leaderboard|top.?score|ranking|scoreboard)\b/i},{name:"Save Progress",description:"Save and resume game state between sessions",condition:t=>t.surfaceType==="game",keywords:/\b(save|progress|resume|checkpoint|continue)\b/i},{name:"Guest Play",description:"Play immediately without signing up, optionally link account later",condition:t=>t.surfaceType==="game",keywords:/\b(guest|anonymous|no.?login|play.?now|instant.?play)\b/i},{name:"Levels / Stages",description:"Progressive difficulty with unlockable stages",condition:()=>!1,keywords:/\b(level|stage|unlock|difficult|progress|world)\b/i},{name:"Achievements",description:"Badges, trophies, and milestones for player accomplishments",condition:()=>!1,keywords:/\b(achieve|badge|trophy|milestone|reward|unlock)\b/i},{name:"Daily Challenge",description:"New puzzle or challenge every day to keep players coming back",condition:()=>!1,keywords:/\b(daily|challenge|streak|word.?of.?the.?day|puzzle.?of)\b/i}]});function fr(t,e,r){return e.includes(t)?t:r}function ce(t){return String(t??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}function mt(t,e){return typeof t!="string"?e:/^#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$/.test(t.trim())?t.trim():e}function Tl(t){let e=new Set;for(let o of t)o.fonts?.display&&e.add(o.fonts.display),o.fonts?.body&&e.add(o.fonts.body);return e.size===0?"":`https://fonts.googleapis.com/css2?${[...e].map(o=>`family=${o.trim().replace(/\s+/g,"+").replace(/[^A-Za-z0-9+]/g,"")}:wght@400;700`).join("&")}&display=swap`}function Yn(t){let e=(t||"").trim(),r=e.toLowerCase(),o=/mono|courier|code/.test(r)?"ui-monospace, monospace":/serif|garamond|bodoni|fraunces|playfair|lora|sentient|migra|sectra|cormorant|abril|crimson/.test(r)?"Georgia, serif":"system-ui, sans-serif";return e?`"${e.replace(/"/g,"")}", ${o}`:o}function Pl(t){switch(t){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 Il(){let t=`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`
1777
1789
  .card-hero { position: relative; overflow: hidden; isolation: isolate; }
1778
1790
  .card-hero::before {
1779
1791
  content: "";
@@ -1831,67 +1843,67 @@ ${o.message}`})}),s.on("close",o=>{e({exitCode:o??1,combined:n})})})}var Va,Ho,W
1831
1843
  backdrop-filter: blur(14px);
1832
1844
  mix-blend-mode: normal;
1833
1845
  }
1834
- `}function sl(t,e,s,n){let o=ut(t.colors?.bg??"","#0f0f0f"),i=ut(t.colors?.fg??"","#f5f5f5"),r=ut(t.colors?.accent??"","#7c9cff"),a=ce(t.name),l=ce(t.hero_headline||t.name),d=ce(t.body_sample||t.summary||""),h=ce(t.cta_text||"Continue"),m=`<div class="hero-eyebrow" style="font-family:${s}">${a.toUpperCase()}</div>`,p=`<h2 class="hero-headline" style="font-family:${e}">${l}</h2>`,y=`<p class="hero-body" style="font-family:${s}">${d}</p>`,v=`<button class="hero-cta" style="background:${r};color:${o};font-family:${s};border-radius:${n.button}">${h}</button>`,b=gs(t.hero_treatment,Qa,"typographic");if(b==="terminal"){let f=`"${(t.fonts?.display??"JetBrains Mono").replace(/"/g,"")}", ui-monospace, monospace`;return`
1835
- ${m}
1836
- <div class="hero-terminal-lines" style="font-family:${f};color:${i}">
1846
+ `}function Cl(t,e,r,o){let n=mt(t.colors?.bg??"","#0f0f0f"),i=mt(t.colors?.fg??"","#f5f5f5"),s=mt(t.colors?.accent??"","#7c9cff"),a=ce(t.name),l=ce(t.hero_headline||t.name),d=ce(t.body_sample||t.summary||""),h=ce(t.cta_text||"Continue"),u=`<div class="hero-eyebrow" style="font-family:${r}">${a.toUpperCase()}</div>`,p=`<h2 class="hero-headline" style="font-family:${e}">${l}</h2>`,f=`<p class="hero-body" style="font-family:${r}">${d}</p>`,v=`<button class="hero-cta" style="background:${s};color:${n};font-family:${r};border-radius:${o.button}">${h}</button>`,b=fr(t.hero_treatment,xl,"typographic");if(b==="terminal"){let g=`"${(t.fonts?.display??"JetBrains Mono").replace(/"/g,"")}", ui-monospace, monospace`;return`
1847
+ ${u}
1848
+ <div class="hero-terminal-lines" style="font-family:${g};color:${i}">
1837
1849
  <div>$ status --all</div>
1838
- <div>api.service.......<span style="color:${r}">OK</span></div>
1839
- <div>worker.queue......<span style="color:${r}">OK</span></div>
1840
- <div>db.primary.......<span style="color:${r}">OK</span></div>
1850
+ <div>api.service.......<span style="color:${s}">OK</span></div>
1851
+ <div>worker.queue......<span style="color:${s}">OK</span></div>
1852
+ <div>db.primary.......<span style="color:${s}">OK</span></div>
1841
1853
  </div>
1842
1854
  ${p}
1843
- ${y}
1855
+ ${f}
1844
1856
  <div class="hero-cta-row">${v}</div>
1845
1857
  `}return b==="split-panel"?`
1846
1858
  <div class="hero-split">
1847
- <div class="hero-split-left" style="background:${r};color:${o};font-family:${e}">
1848
- <div class="hero-split-mark" style="font-family:${s};color:${o}">${a[0]??"\xB7"}</div>
1859
+ <div class="hero-split-left" style="background:${s};color:${n};font-family:${e}">
1860
+ <div class="hero-split-mark" style="font-family:${r};color:${n}">${a[0]??"\xB7"}</div>
1849
1861
  </div>
1850
1862
  <div class="hero-split-right">
1851
- ${m}
1863
+ ${u}
1852
1864
  ${p}
1853
- ${y}
1865
+ ${f}
1854
1866
  <div class="hero-cta-row">${v}</div>
1855
1867
  </div>
1856
1868
  </div>
1857
1869
  `:b==="full-bleed-photo"?`
1858
- <div class="hero-photo" style="background: linear-gradient(160deg, ${r}30 0%, ${i}08 35%, ${o} 100%), ${o}">
1859
- <span class="hero-photo-label" style="font-family:${s};color:${i}">photo slot</span>
1870
+ <div class="hero-photo" style="background: linear-gradient(160deg, ${s}30 0%, ${i}08 35%, ${n} 100%), ${n}">
1871
+ <span class="hero-photo-label" style="font-family:${r};color:${i}">photo slot</span>
1860
1872
  </div>
1861
- ${m}
1873
+ ${u}
1862
1874
  ${p}
1863
- ${y}
1875
+ ${f}
1864
1876
  <div class="hero-cta-row">${v}</div>
1865
1877
  `:b==="magazine-hero"?`
1866
- ${m}
1878
+ ${u}
1867
1879
  <h2 class="hero-headline hero-headline-mag" style="font-family:${e}">${l}</h2>
1868
1880
  <div class="hero-rule" style="background:${i}"></div>
1869
- <p class="hero-body hero-body-mag" style="font-family:${s}">${d}</p>
1870
- <div class="hero-byline" style="font-family:${s};color:${i}">\u2014 Volume 01 \xB7 Issue 01</div>
1881
+ <p class="hero-body hero-body-mag" style="font-family:${r}">${d}</p>
1882
+ <div class="hero-byline" style="font-family:${r};color:${i}">\u2014 Volume 01 \xB7 Issue 01</div>
1871
1883
  <div class="hero-cta-row">${v}</div>
1872
1884
  `:`
1873
- ${m}
1885
+ ${u}
1874
1886
  ${p}
1875
- ${y}
1887
+ ${f}
1876
1888
  <div class="hero-cta-row">${v}</div>
1877
- `}function Ko(t,e){let s=el(e),n=e.map(o=>{let i=ut(o.colors?.bg??"","#0f0f0f"),r=ut(o.colors?.fg??"","#f5f5f5"),a=ut(o.colors?.accent??"","#7c9cff"),l=Yo(o.fonts?.display??""),d=Yo(o.fonts?.body??""),h=gs(o.shape_lang,Xa,"soft"),m=gs(o.texture,Za,"flat"),p=tl(h),y=sl(o,l,d,p),v=ce(o.name),b=ce(o.id),f=ce(o.fonts?.display??""),k=ce(o.fonts?.body??""),R=ce(o.summary||""),j=ce(o.decoration_hint||""),S=(O,U)=>`<div class="card-meta-row"><span class="card-meta-label">${O}</span><span class="card-meta-value">${U}</span></div>`;return` <article class="card" data-direction-id="${b}" style="border-radius:${p.card}">
1878
- <div class="card-hero texture-${m}" style="background:${i};color:${r}">${y}
1889
+ `}function Qn(t,e){let r=Tl(e),o=e.map(n=>{let i=mt(n.colors?.bg??"","#0f0f0f"),s=mt(n.colors?.fg??"","#f5f5f5"),a=mt(n.colors?.accent??"","#7c9cff"),l=Yn(n.fonts?.display??""),d=Yn(n.fonts?.body??""),h=fr(n.shape_lang,kl,"soft"),u=fr(n.texture,Sl,"flat"),p=Pl(h),f=Cl(n,l,d,p),v=ce(n.name),b=ce(n.id),g=ce(n.fonts?.display??""),x=ce(n.fonts?.body??""),R=ce(n.summary||""),D=ce(n.decoration_hint||""),P=(j,U)=>`<div class="card-meta-row"><span class="card-meta-label">${j}</span><span class="card-meta-value">${U}</span></div>`;return` <article class="card" data-direction-id="${b}" style="border-radius:${p.card}">
1890
+ <div class="card-hero texture-${u}" style="background:${i};color:${s}">${f}
1879
1891
  </div>
1880
- <div class="card-meta" style="border-top-color:${r}14">
1892
+ <div class="card-meta" style="border-top-color:${s}14">
1881
1893
  <div class="card-meta-title">${v}</div>
1882
1894
  <p class="card-meta-summary">${R}</p>
1883
- ${S("Fonts",`${f||"\u2014"} <span class="sep">\xB7</span> ${k||"\u2014"}`)}
1884
- ${S("Palette",`
1895
+ ${P("Fonts",`${g||"\u2014"} <span class="sep">\xB7</span> ${x||"\u2014"}`)}
1896
+ ${P("Palette",`
1885
1897
  <span class="card-meta-swatches">
1886
1898
  <span class="card-meta-swatch" title="${i}" style="background:${i}"></span>
1887
- <span class="card-meta-swatch" title="${r}" style="background:${r}"></span>
1899
+ <span class="card-meta-swatch" title="${s}" style="background:${s}"></span>
1888
1900
  <span class="card-meta-swatch" title="${a}" style="background:${a}"></span>
1889
1901
  </span>
1890
1902
  `)}
1891
- ${S("Shape",`<span class="chip" style="border-radius:${p.chip}">${h}</span>`)}
1892
- ${S("Texture",`<span class="chip" style="border-radius:${p.chip}">${ce(m)}</span>`)}
1893
- ${j?S("Decoration",ce(j)):""}
1894
- ${S("Pick with",`<code>${v}</code>`)}
1903
+ ${P("Shape",`<span class="chip" style="border-radius:${p.chip}">${h}</span>`)}
1904
+ ${P("Texture",`<span class="chip" style="border-radius:${p.chip}">${ce(u)}</span>`)}
1905
+ ${D?P("Decoration",ce(D)):""}
1906
+ ${P("Pick with",`<code>${v}</code>`)}
1895
1907
  </div>
1896
1908
  </article>`}).join(`
1897
1909
  `);return`<!DOCTYPE html>
@@ -1902,7 +1914,7 @@ ${o.message}`})}),s.on("close",o=>{e({exitCode:o??1,combined:n})})})}var Va,Ho,W
1902
1914
  <title>Design directions \u2014 ${ce(t)}</title>
1903
1915
  <link rel="preconnect" href="https://fonts.googleapis.com" />
1904
1916
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
1905
- ${s?`<link rel="stylesheet" href="${s}" />`:""}
1917
+ ${r?`<link rel="stylesheet" href="${r}" />`:""}
1906
1918
  <style>
1907
1919
  :root {
1908
1920
  --chrome-bg: #f4f3ef;
@@ -2086,7 +2098,7 @@ ${o.message}`})}),s.on("close",o=>{e({exitCode:o??1,combined:n})})})}var Va,Ho,W
2086
2098
  margin-top: 4px;
2087
2099
  }
2088
2100
 
2089
- ${nl()}
2101
+ ${Il()}
2090
2102
 
2091
2103
  /* \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 */
2092
2104
  .card-meta {
@@ -2200,7 +2212,7 @@ ${o.message}`})}),s.on("close",o=>{e({exitCode:o??1,combined:n})})})}var Va,Ho,W
2200
2212
  </header>
2201
2213
 
2202
2214
  <div class="grid">
2203
- ${n}
2215
+ ${o}
2204
2216
  </div>
2205
2217
 
2206
2218
  <footer class="page-footer">
@@ -2215,30 +2227,31 @@ ${n}
2215
2227
  </div>
2216
2228
  </body>
2217
2229
  </html>
2218
- `}var Qa,Xa,Za,Qo=_(()=>{"use strict";Qa=["typographic","split-panel","terminal","full-bleed-photo","magazine-hero"],Xa=["sharp","soft","pill","organic"],Za=["flat","paper-grain","film-grain","scanlines","gradient-mesh","noise","glassmorphic"]});import{z as N}from"zod";import{existsSync as Ct,mkdirSync as rn,readFileSync as Zo,readdirSync as ol,statSync as rl,unlinkSync as il,writeFileSync as an}from"fs";import{dirname as al,isAbsolute as ll,join as de}from"path";import{homedir as qe}from"os";import{createHash as cl,createHmac as er,randomBytes as dl,randomUUID as Xo,timingSafeEqual as pl}from"crypto";function tr(){let t=de(qe(),".mistflow","confirm-secret");if(Ct(t))try{return Buffer.from(Zo(t,"utf-8").trim(),"hex")}catch{}let e=dl(32);return rn(de(qe(),".mistflow"),{recursive:!0}),an(t,e.toString("hex"),{mode:384}),e}function nr(t){return cl("sha256").update(t.trim().toLowerCase()).digest("hex").slice(0,16)}function ml(t,e){let s={cwd:t,d:nr(e),exp:Date.now()+ul},n=Buffer.from(JSON.stringify(s)).toString("base64url"),o=er("sha256",tr()).update(n).digest("base64url");return`${n}.${o}`}function hl(t,e,s){let n=t.split(".");if(n.length!==2)return!1;let[o,i]=n,r=er("sha256",tr()).update(o).digest("base64url"),a=Buffer.from(i),l=Buffer.from(r);if(a.length!==l.length||!pl(a,l))return!1;try{let d=JSON.parse(Buffer.from(o,"base64url").toString("utf-8"));return!(typeof d.exp!="number"||Date.now()>d.exp||d.cwd!==e||d.d!==nr(s))}catch{return!1}}function gl(t){let e=t,s=qe(),n=!1;for(let o=0;o<64;o++){if(Ct(de(e,"mistflow.json")))return"mistflow";if(!n&&Ct(de(e,"package.json"))&&(n=!0),e===s)break;let i=al(e);if(i===e)break;e=i}return n?"foreign":"none"}function fl(t){let e=qe(),s=t.replace(/\/+$/,"");if(s===e||s==="/"||s===""||s==="/tmp"||s==="/private/tmp")return!0;let n=["Desktop","Documents","Downloads"];for(let o of n)if(s===de(e,o))return!0;return!1}function bl(t){let e=[[/payment/i,"Payments"],[/database/i,"Database"],[/auth|sign.?up|login|access/i,"Access"],[/landing.?page/i,"Landing page"],[/who.*using|user|role/i,"Users"],[/design|theme|style/i,"Design"],[/deploy/i,"Deploy"],[/domain/i,"Domain"],[/notification/i,"Notify"],[/email/i,"Email"],[/mobile|responsive/i,"Mobile"],[/integrat/i,"Integration"],[/field|info|propert|detail|contain/i,"Item shape"],[/view|layout|board|grid|list|timeline/i,"View"],[/scope|how many|one.*or.*many|multi/i,"Scope"],[/share|read.?only|viewer|stakeholder/i,"Sharing"],[/workflow|status|state|move|stage|pipeline/i,"Workflow"],[/avoid|bloat|simple|complex|minimal/i,"Constraints"],[/time.*period|quarter|month|sprint/i,"Time periods"],[/swimlane|column|group|categor/i,"Structure"]];for(let[n,o]of e)if(n.test(t))return o;return t.replace(/[?.,!]/g,"").split(/\s+/).filter(n=>!["what","how","do","does","is","are","the","a","an","would","should","you","your","for","this","that","to","of","or","and","want","like","prefer"].includes(n.toLowerCase())).slice(0,2).join(" ").slice(0,12)||"Option"}function wl(t){let e=de(qe(),".mistflow","plans",`${t}.json`);if(!Ct(e))return null;try{return JSON.parse(Zo(e,"utf-8")).plan??null}catch{return null}}async function vl(t){for(let n=0;n<60;n++){try{let o=await Dn(t.design_conversation_id);if(o.status==="ready")return{status:"design_clarify",design_conversation_id:t.design_conversation_id,directions:o.directions,plan:o.plan,methodology:o.methodology};if(o.status==="failed")return{status:"ready",plan:o.plan,methodology:o.methodology}}catch(o){let i=o instanceof Error?o.message:String(o);if(i.toLowerCase().includes("not found"))return{status:"ready",plan:t.plan,methodology:t.methodology};console.error(`[plan] directions poll attempt ${n+1} failed: ${i}`)}await new Promise(o=>setTimeout(o,2e3))}return console.error("[plan] directions poll exhausted, falling back to ready"),{status:"ready",plan:t.plan,methodology:t.methodology}}async function xl(t,e){let{description:s,projectPath:n,conversationId:o,answers:i,existingPlan:r,existingPlanId:a,templateToken:l,remixDescription:d,autonomous:h,language:m,landingDesign:p,appStyle:y,brandMentioned:v,confirmToken:b,urlChoice:f,designConversationId:k,designDirection:R}=t;if(o&&!s&&!i&&!k&&!R&&!r&&!a&&!l)try{let u=await Ht(o);return u.status==="clarify_pending"?c(JSON.stringify({status:"running",conversationId:o,phase:"generating_questions",nextAction:`Still generating. Call mist_plan with { projectPath, conversationId: "${o}" } IMMEDIATELY \u2014 do NOT run bash sleep. The server holds each poll open up to ~10s and returns as soon as questions are ready.`})):u.status==="plan_pending"?c(JSON.stringify({status:"running",conversationId:o,phase:"generating_plan",nextAction:`Plan is being generated (build_plan + image enrichment, 30-60s typical). Call mist_plan with { projectPath, conversationId: "${o}" } IMMEDIATELY \u2014 do NOT run bash sleep. The server holds each poll open up to ~10s and returns as soon as the plan is ready.`})):c(JSON.stringify(u))}catch(u){let g=u instanceof Error?u.message:String(u);return c(`Could not poll plan conversation '${o}': ${g}`,!0)}let j=s??"";if(!j.trim()&&!o&&!r&&!a&&!l)return c("mist_plan requires a `description` for the first call (the user's app idea). Pass { conversationId } alone to poll an in-flight call, or along with { answers } to submit responses.",!0);let S=r;if(!S&&a&&(S=wl(a)??void 0,!S))return c("Your previous plan is no longer available. Please describe your app again to generate a new plan.",!0);let O=o;if(!ee())return c("No Mistflow credentials found. Run mist_setup to connect your account.",!0);let U;if(!O&&!S&&!l){if(!ll(n))return c(`projectPath must be an absolute path \u2014 received '${n}'. Pass the full absolute path to the user's project directory (e.g. /Users/alice/projects/my-app).`,!0);let u=gl(n);if(u!=="mistflow"&&fl(n))return c(JSON.stringify({status:"unsafe_cwd",projectPath:n,instruction:[`The projectPath you passed (${n}) is a system-level directory (home root, Desktop, Documents, Downloads, or /tmp).`,"Scaffolding here would drop node_modules and a git repo directly at that location, which is messy and hard to clean up.","MANDATORY: Before calling mist_plan again:"," 1. Pick a subfolder name based on the app description (e.g. 'nutrition-tracker', 'habit-app').",` 2. Create the subfolder at ${n}/<subfolder-name>.`," 3. Call mist_plan again with the SAME description and projectPath set to the new subfolder.","Do NOT ask the user where to put the project \u2014 just pick a sensible subfolder name from their description."].join(`
2219
- `)}),!0);if(u==="foreign"&&!v){if(!(b?hl(b,n,j):!1)){let P=ml(n,j);return c(JSON.stringify({status:"confirm_new_project",projectPath:n,description:j,confirmToken:P,askUserQuestion:{question:"You're inside an existing project directory. Do you want to scaffold a new Mistflow app here, or edit the existing codebase directly?",header:"Scope",options:[{label:"Scaffold a new Mistflow app in a subdirectory",description:"Creates a fresh project in this folder without touching the existing code."},{label:"Edit this existing codebase directly",description:"Cancel Mistflow. Handle the request by editing the current project's files."}],multiSelect:!1},instruction:["The user is inside an existing project (package.json found up the directory tree, no mistflow.json) and did NOT explicitly invoke Mistflow by name.","MANDATORY: Use the AskUserQuestion tool with the provided askUserQuestion to confirm their intent before calling mist_plan again.","If they pick 'Scaffold a new Mistflow app in a subdirectory', call mist_plan again with the SAME description and confirmToken set to the token returned above.","If they pick 'Edit this existing codebase directly', DO NOT call mist_plan again. Fulfill their request by editing files directly in the current project.",b?"The previous confirmToken was invalid, expired, or did not match the current directory/description. Use the fresh token above.":""].filter(Boolean).join(`
2220
- `)}))}U="Note: You're inside an existing project. Mistflow will create the new app in a subdirectory. It won't modify this codebase."}else u==="foreign"&&v&&(U="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 Kn(l)).plan)return c("This template has no plan to fork. Try a different template.",!0);let g=await Qn(l),P=g.plan,T="";if(d&&g.has_source)try{let ae=await Wt(g.plan,d),W=ae.plan??ae,Mt=ae.diff,Y=W?.steps??[],vn=new Set([...(Mt?.added??[]).map(be=>be.number),...(Mt?.modified??[]).map(be=>be.number)]),De=Y.map(be=>{let ki=be.number;return vn.has(ki)?{...be,status:"pending"}:{...be,status:"completed",source:"forked"}});W.steps=De,P=W;let Ye=De.filter(be=>be.status==="pending").length;T=` Remixed: ${De.filter(be=>be.status==="completed").length} steps unchanged, ${Ye} steps need re-implementation.`}catch(ae){console.error("[plan] Remix failed, using original plan:",ae),T=" (Remix failed \u2014 using original plan. You can modify it later.)"}let X=Xo(),J=de(qe(),".mistflow","plans");rn(J,{recursive:!0}),an(de(J,`${X}.json`),JSON.stringify({plan:P,projectId:g.id,sourceDeploymentId:g.source_deployment_id,forkToken:g.fork_token,requiredEnvVars:g.required_env_vars,dbProvider:g.db_provider}));let se=P?.name??"forked-app",ie=g.has_source,yt=ie?"Source code will be restored during init. Run init promptly \u2014 the download token expires in 1 hour.":"",Je=g.deploy_url?` Instant deploy started \u2014 your app will be live at ${g.deploy_url} in under a minute.`:"";return c(JSON.stringify({planId:X,forkedFrom:g.forked_from,projectId:g.id,hasSource:ie,deployUrl:g.deploy_url,message:`Forked "${g.forked_from}" into your workspace.${T}${Je} ${yt} NEXT: Call mist_init, name='${se}', and planId='${X}' to create the project now.`}))}catch(u){let g=u instanceof Error?u.message:"Failed to fork template";return c(g,!0)}if(S){let u;try{u=await Wt(S,j)}catch(J){let se=J instanceof Error?J.message:"Failed to modify plan";return c(se,!0)}let g=u.plan,P=u.diff,T=[];if(P?.added?.length){let J=P.added.map(se=>se.title);T.push(`Added ${J.length} step(s): ${J.join(", ")}`)}if(P?.removed?.length){let J=P.removed.map(se=>se.title);T.push(`Removed ${J.length} step(s): ${J.join(", ")}`)}if(P?.modified?.length){let J=P.modified.map(se=>se.title);T.push(`Modified ${J.length} step(s): ${J.join(", ")}`)}let X=T.length>0?T.join(". "):"No changes detected.";return c(JSON.stringify({plan:g,diff:P,message:`Plan modified. ${X}. Update mistflow.json with the new plan, then continue with mist_implement.`}))}let x=f?.trim()||void 0,$=i;if(!x&&i&&on in i&&(x=i[on]),i&&on in i){let{[on]:u,...g}=i;$=Object.keys(g).length>0?g:void 0}if(x&&(x=x.replace(/^Keep\s+/i,"").replace(/\s*\(Recommended\)\s*$/i,"").replace(/\.mistflow\.app.*$/i,"").trim()||void 0),x){let u=x.toLowerCase().replace(/\s+/g,"-");/^[a-z0-9][a-z0-9-]{1,30}[a-z0-9]$/.test(u)?x=u:(console.error(`[mist_plan] Discarding urlChoice '${x}' \u2014 does not look like a subdomain. Backend will auto-generate.`),x=void 0)}let H;if(R){H={...R};let u={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[g,P]of Object.entries(u))R[g]!==void 0&&H[P]===void 0&&(H[P]=R[g])}let G=i?"Generating plan with your answers (LLM call)":k?"Finalizing design direction":"Thinking through discovery questions",oe=e?sn(e.server,e.progressToken,()=>G):{stop:()=>{}};e&&(e.cleanup=()=>oe.stop());let E;try{O&&!$&&!k&&!S&&!a?E=await Ht(O):E=await On(j,{conversationId:O,answers:$,autonomous:h,language:m,designConversationId:k,designDirection:H})}catch(u){oe.stop();let g=u instanceof Error?u.message:"Failed to generate plan";return c(g,!0)}if(E.status==="clarify_pending"){oe.stop();let u=E;return c(JSON.stringify({status:"running",conversationId:u.conversation_id,phase:"generating_questions",nextAction:`Discovery questions are generating. Call mist_plan with { projectPath, conversationId: "${u.conversation_id}" } IMMEDIATELY \u2014 do NOT run bash sleep between polls. The server holds each poll open up to ~10s and returns as soon as questions land. Do NOT re-send description or answers.`}))}if(E.status==="plan_pending"){oe.stop();let u=E;return c(JSON.stringify({status:"running",conversationId:u.conversation_id,phase:"generating_plan",nextAction:`Plan is being generated (build_plan + image enrichment, 30-60s typical). Call mist_plan with { projectPath, conversationId: "${u.conversation_id}" } IMMEDIATELY \u2014 do NOT run bash sleep between polls. The server holds each poll open up to ~10s and returns as soon as the plan lands. Do NOT re-send answers.`}))}if(E.status==="design_clarify_pending"&&(G="Generating creative design directions",E=await vl(E)),oe.stop(),E.status==="clarify"){let u=E.reflection||"",g=E.suggestedName||"",P=E.suggestedFeatures??[],T=E.questions??[],X=T.some(Y=>Array.isArray(Y.options)&&typeof Y.options[0]=="object"&&Y.options[0]?.label),J={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"},se=T.map(Y=>{let vn=Y.decisionKey&&J[Y.decisionKey]||bl(Y.question),De;return X&&Array.isArray(Y.options)?De=Y.options.map(Ye=>({label:Ye.label,description:Ye.description??""})):Array.isArray(Y.options)?De=Y.options.map((Ye,Ds)=>({label:Ds===0?`${Ye} (Recommended)`:String(Ye),description:Y.why??""})):De=[{label:"Yes (Recommended)",description:Y.why??""},{label:"No",description:""}],{question:Y.question,header:vn,options:De,multiSelect:!1}}),yt=E.decisions?.audienceType??null,Je=P.length>0?Go(j,{primaryActor:null,primaryAction:null,surfaceType:null,audienceType:yt,multiRole:null,publicLanding:null,realMoney:null,scheduling:null,authModel:null,dbProvider:null,integrations:null},{suggestedName:g,suggestedFeatures:P,language:m}):null,ae=Je?Vo(Je):"",W=hs(g||"my-app").slice(0,32);try{let Y=await Nn(W);!Y.available&&Y.suggestion&&(W=Y.suggestion)}catch{}ae&&(ae+=`
2230
+ `}var xl,kl,Sl,Xn=S(()=>{"use strict";xl=["typographic","split-panel","terminal","full-bleed-photo","magazine-hero"],kl=["sharp","soft","pill","organic"],Sl=["flat","paper-grain","film-grain","scanlines","gradient-mesh","noise","glassmorphic"]});import{z as C}from"zod";import{existsSync as Rt,mkdirSync as io,readFileSync as es,readdirSync as Al,statSync as _l,unlinkSync as Rl,writeFileSync as ao}from"fs";import{dirname as Nl,isAbsolute as El,join as de}from"path";import{homedir as Be}from"os";import{createHash as Dl,createHmac as ts,randomBytes as jl,randomUUID as Zn,timingSafeEqual as Ol}from"crypto";function os(){let t=de(Be(),".mistflow","confirm-secret");if(Rt(t))try{return Buffer.from(es(t,"utf-8").trim(),"hex")}catch{}let e=jl(32);return io(de(Be(),".mistflow"),{recursive:!0}),ao(t,e.toString("hex"),{mode:384}),e}function rs(t){return Dl("sha256").update(t.trim().toLowerCase()).digest("hex").slice(0,16)}function Ll(t,e){let r={cwd:t,d:rs(e),exp:Date.now()+Ml},o=Buffer.from(JSON.stringify(r)).toString("base64url"),n=ts("sha256",os()).update(o).digest("base64url");return`${o}.${n}`}function Ul(t,e,r){let o=t.split(".");if(o.length!==2)return!1;let[n,i]=o,s=ts("sha256",os()).update(n).digest("base64url"),a=Buffer.from(i),l=Buffer.from(s);if(a.length!==l.length||!Ol(a,l))return!1;try{let d=JSON.parse(Buffer.from(n,"base64url").toString("utf-8"));return!(typeof d.exp!="number"||Date.now()>d.exp||d.cwd!==e||d.d!==rs(r))}catch{return!1}}function $l(t){let e=t,r=Be(),o=!1;for(let n=0;n<64;n++){if(Rt(de(e,"mistflow.json")))return"mistflow";if(!o&&Rt(de(e,"package.json"))&&(o=!0),e===r)break;let i=Nl(e);if(i===e)break;e=i}return o?"foreign":"none"}function Fl(t){let e=Be(),r=t.replace(/\/+$/,"");if(r===e||r==="/"||r===""||r==="/tmp"||r==="/private/tmp")return!0;let o=["Desktop","Documents","Downloads"];for(let n of o)if(r===de(e,n))return!0;return!1}function Bl(t){let e=[[/payment/i,"Payments"],[/database/i,"Database"],[/auth|sign.?up|login|access/i,"Access"],[/landing.?page/i,"Landing page"],[/who.*using|user|role/i,"Users"],[/design|theme|style/i,"Design"],[/deploy/i,"Deploy"],[/domain/i,"Domain"],[/notification/i,"Notify"],[/email/i,"Email"],[/mobile|responsive/i,"Mobile"],[/integrat/i,"Integration"],[/field|info|propert|detail|contain/i,"Item shape"],[/view|layout|board|grid|list|timeline/i,"View"],[/scope|how many|one.*or.*many|multi/i,"Scope"],[/share|read.?only|viewer|stakeholder/i,"Sharing"],[/workflow|status|state|move|stage|pipeline/i,"Workflow"],[/avoid|bloat|simple|complex|minimal/i,"Constraints"],[/time.*period|quarter|month|sprint/i,"Time periods"],[/swimlane|column|group|categor/i,"Structure"]];for(let[o,n]of e)if(o.test(t))return n;return t.replace(/[?.,!]/g,"").split(/\s+/).filter(o=>!["what","how","do","does","is","are","the","a","an","would","should","you","your","for","this","that","to","of","or","and","want","like","prefer"].includes(o.toLowerCase())).slice(0,2).join(" ").slice(0,12)||"Option"}function zl(t){let e=de(Be(),".mistflow","plans",`${t}.json`);if(!Rt(e))return null;try{return JSON.parse(es(e,"utf-8")).plan??null}catch{return null}}async function Hl(t){for(let o=0;o<60;o++){try{let n=await Oo(t.design_conversation_id);if(n.status==="ready")return{status:"design_clarify",design_conversation_id:t.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 i=n instanceof Error?n.message:String(n);if(i.toLowerCase().includes("not found"))return{status:"ready",plan:t.plan,methodology:t.methodology};console.error(`[plan] directions poll attempt ${o+1} failed: ${i}`)}await new Promise(n=>setTimeout(n,2e3))}return console.error("[plan] directions poll exhausted, falling back to ready"),{status:"ready",plan:t.plan,methodology:t.methodology}}async function Wl(t,e){let{description:r,projectPath:o,conversationId:n,answers:i,existingPlan:s,existingPlanId:a,templateToken:l,remixDescription:d,autonomous:h,language:u,landingDesign:p,appStyle:f,brandMentioned:v,confirmToken:b,urlChoice:g,designConversationId:x,designDirection:R}=t;if(n&&!r&&!i&&!x&&!R&&!s&&!a&&!l)try{let m=await Wt(n);return m.status==="clarify_pending"?c(JSON.stringify({status:"running",conversationId:n,phase:"generating_questions",nextAction:`Still generating. Call mist_plan with { projectPath, conversationId: "${n}" } IMMEDIATELY \u2014 do NOT run bash sleep. The server holds each poll open up to ~10s and returns as soon as questions are ready.`})):m.status==="plan_pending"?c(JSON.stringify({status:"running",conversationId:n,phase:"generating_plan",nextAction:`Plan is being generated (build_plan + image enrichment, 30-60s typical). Call mist_plan with { projectPath, conversationId: "${n}" } IMMEDIATELY \u2014 do NOT run bash sleep. The server holds each poll open up to ~10s and returns as soon as the plan is ready.`})):c(JSON.stringify(m))}catch(m){let w=m instanceof Error?m.message:String(m);return c(`Could not poll plan conversation '${n}': ${w}`,!0)}let D=r??"";if(!D.trim()&&!n&&!x&&!s&&!a&&!l)return c("mist_plan requires one of:\n \u2022 `description` \u2014 first call with a new app idea\n \u2022 `conversationId` alone \u2014 poll an in-flight discovery / plan-gen call\n \u2022 `conversationId` + `answers` \u2014 submit answers after `status: 'clarify'`\n \u2022 `designConversationId` + `designDirection` \u2014 submit a design pick after `status: 'design_clarify_pending'`\n \u2022 `existingPlanId` + a modification `description` \u2014 edit an existing plan\n \u2022 `templateToken` \u2014 fork a published template\n\nThe most common confusion: after `design_clarify_pending`, the next call needs `designConversationId` + `designDirection`, NOT `conversationId` alone.",!0);if(x&&!R&&!D.trim()&&!i)return c(`You passed designConversationId='${x}' but no designDirection. After the user picks one of the directions from the preview, pass it back as: mist_plan({ designConversationId: '${x}', designDirection: { id: '<their-pick-id>' } }). If the user asked for something custom, pass designDirection: { custom: '<their description>' }.`,!0);let P=s;if(!P&&a&&(P=zl(a)??void 0,!P))return c("Your previous plan is no longer available. Please describe your app again to generate a new plan.",!0);let j=n;if(!oe())return c("No Mistflow credentials found. Run mist_setup to connect your account.",!0);let U;if(!j&&!P&&!l){if(!El(o))return c(`projectPath must be an absolute path \u2014 received '${o}'. Pass the full absolute path to the user's project directory (e.g. /Users/alice/projects/my-app).`,!0);let m=$l(o);if(m!=="mistflow"&&Fl(o))return c(JSON.stringify({status:"unsafe_cwd",projectPath:o,instruction:[`The projectPath you passed (${o}) 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 ${o}/<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(`
2231
+ `)}),!0);if(m==="foreign"&&!v){if(!(b?Ul(b,o,D):!1)){let T=Ll(o,D);return c(JSON.stringify({status:"confirm_new_project",projectPath:o,description:D,confirmToken:T,askUserQuestion:{question:"You're inside an existing project directory. Do you want to scaffold a new Mistflow app here, or edit the existing codebase directly?",header:"Scope",options:[{label:"Scaffold a new Mistflow app in a subdirectory",description:"Creates a fresh project in this folder without touching the existing code."},{label:"Edit this existing codebase directly",description:"Cancel Mistflow. Handle the request by editing the current project's files."}],multiSelect:!1},instruction:["The user is inside an existing project (package.json found up the directory tree, no mistflow.json) and did NOT explicitly invoke Mistflow by name.","MANDATORY: Use the AskUserQuestion tool with the provided askUserQuestion to confirm their intent before calling mist_plan again.","If they pick 'Scaffold a new Mistflow app in a subdirectory', call mist_plan again with the SAME description and confirmToken set to the token returned above.","If they pick 'Edit this existing codebase directly', DO NOT call mist_plan again. Fulfill their request by editing files directly in the current project.",b?"The previous confirmToken was invalid, expired, or did not match the current directory/description. Use the fresh token above.":""].filter(Boolean).join(`
2232
+ `)}))}U="Note: You're inside an existing project. Mistflow will create the new app in a subdirectory. It won't modify this codebase."}else m==="foreign"&&v&&(U="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 Qo(l)).plan)return c("This template has no plan to fork. Try a different template.",!0);let w=await Xo(l),T=w.plan,E="";if(d&&w.has_source)try{let ae=await Gt(w.plan,d),W=ae.plan??ae,Lt=ae.diff,Q=W?.steps??[],xo=new Set([...(Lt?.added??[]).map(we=>we.number),...(Lt?.modified??[]).map(we=>we.number)]),Oe=Q.map(we=>{let Gi=we.number;return xo.has(Gi)?{...we,status:"pending"}:{...we,status:"completed",source:"forked"}});W.steps=Oe,T=W;let Je=Oe.filter(we=>we.status==="pending").length;E=` Remixed: ${Oe.filter(we=>we.status==="completed").length} steps unchanged, ${Je} steps need re-implementation.`}catch(ae){console.error("[plan] Remix failed, using original plan:",ae),E=" (Remix failed \u2014 using original plan. You can modify it later.)"}let te=Zn(),Y=de(Be(),".mistflow","plans");io(Y,{recursive:!0}),ao(de(Y,`${te}.json`),JSON.stringify({plan:T,projectId:w.id,sourceDeploymentId:w.source_deployment_id,forkToken:w.fork_token,requiredEnvVars:w.required_env_vars,dbProvider:w.db_provider}));let ne=T?.name??"forked-app",ie=w.has_source,bt=ie?"Source code will be restored during init. Run init promptly \u2014 the download token expires in 1 hour.":"",Ve=w.deploy_url?` Instant deploy started \u2014 your app will be live at ${w.deploy_url} in under a minute.`:"";return c(JSON.stringify({planId:te,forkedFrom:w.forked_from,projectId:w.id,hasSource:ie,deployUrl:w.deploy_url,message:`Forked "${w.forked_from}" into your workspace.${E}${Ve} ${bt} NEXT: Call mist_init, name='${ne}', and planId='${te}' to create the project now.`}))}catch(m){let w=m instanceof Error?m.message:"Failed to fork template";return c(w,!0)}if(P){let m;try{m=await Gt(P,D)}catch(Y){let ne=Y instanceof Error?Y.message:"Failed to modify plan";return c(ne,!0)}let w=m.plan,T=m.diff,E=[];if(T?.added?.length){let Y=T.added.map(ne=>ne.title);E.push(`Added ${Y.length} step(s): ${Y.join(", ")}`)}if(T?.removed?.length){let Y=T.removed.map(ne=>ne.title);E.push(`Removed ${Y.length} step(s): ${Y.join(", ")}`)}if(T?.modified?.length){let Y=T.modified.map(ne=>ne.title);E.push(`Modified ${Y.length} step(s): ${Y.join(", ")}`)}let te=E.length>0?E.join(". "):"No changes detected.";return c(JSON.stringify({plan:w,diff:T,message:`Plan modified. ${te}. Update mistflow.json with the new plan, then continue with mist_implement.`}))}let k=g?.trim()||void 0,L=i;if(i!=null&&!Array.isArray(i)){let m=i;if(!k&&so in m&&(k=m[so]),so in m){let{[so]:w,...T}=m;L=Object.keys(T).length>0?T:void 0}}if(k&&(k=k.replace(/^Keep\s+/i,"").replace(/\s*\(Recommended\)\s*$/i,"").replace(/\.mistflow\.app.*$/i,"").trim()||void 0),k){let m=k.toLowerCase().replace(/\s+/g,"-");/^[a-z0-9][a-z0-9-]{1,30}[a-z0-9]$/.test(m)?k=m:(console.error(`[mist_plan] Discarding urlChoice '${k}' \u2014 does not look like a subdomain. Backend will auto-generate.`),k=void 0)}let H;if(R){H={...R};let m={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[w,T]of Object.entries(m))R[w]!==void 0&&H[T]===void 0&&(H[T]=R[w])}let ye=i?"Generating plan with your answers (LLM call)":x?"Finalizing design direction":"Thinking through discovery questions",J=e?no(e.server,e.progressToken,()=>ye):{stop:()=>{}};e&&(e.cleanup=()=>J.stop());let N;try{j&&!L&&!x&&!P&&!a?N=await Wt(j):N=await Mo(D,{conversationId:j,answers:L,autonomous:h,language:u,designConversationId:x,designDirection:H})}catch(m){J.stop();let w=m instanceof Error?m.message:"Failed to generate plan";return c(w,!0)}if(N.status==="clarify_pending"){J.stop();let m=N;return c(JSON.stringify({status:"running",conversationId:m.conversation_id,phase:"generating_questions",nextAction:`Discovery questions are generating. Call mist_plan with { projectPath, conversationId: "${m.conversation_id}" } IMMEDIATELY \u2014 do NOT run bash sleep between polls. The server holds each poll open up to ~10s and returns as soon as questions land. Do NOT re-send description or answers.`}))}if(N.status==="plan_pending"){J.stop();let m=N;return c(JSON.stringify({status:"running",conversationId:m.conversation_id,phase:"generating_plan",nextAction:`Plan is being generated (build_plan + image enrichment, 30-60s typical). Call mist_plan with { projectPath, conversationId: "${m.conversation_id}" } IMMEDIATELY \u2014 do NOT run bash sleep between polls. The server holds each poll open up to ~10s and returns as soon as the plan lands. Do NOT re-send answers.`}))}if(N.status==="design_clarify_pending"&&(ye="Generating creative design directions",N=await Hl(N)),J.stop(),N.status==="clarify"){let m=N.reflection||"",w=N.suggestedName||"",T=N.suggestedFeatures??[],E=N.questions??[],te=E.some(Q=>Array.isArray(Q.options)&&typeof Q.options[0]=="object"&&Q.options[0]?.label),Y={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"},ne=E.map(Q=>{let xo=Q.decisionKey&&Y[Q.decisionKey]||Bl(Q.question),Oe;return te&&Array.isArray(Q.options)?Oe=Q.options.map(Je=>({label:Je.label,description:Je.description??""})):Array.isArray(Q.options)?Oe=Q.options.map((Je,Or)=>({label:Or===0?`${Je} (Recommended)`:String(Je),description:Q.why??""})):Oe=[{label:"Yes (Recommended)",description:Q.why??""},{label:"No",description:""}],{question:Q.question,header:xo,options:Oe,multiSelect:!1}}),bt=N.decisions?.audienceType??null,Ve=T.length>0?Vn(D,{primaryActor:null,primaryAction:null,surfaceType:null,audienceType:bt,multiRole:null,publicLanding:null,realMoney:null,scheduling:null,authModel:null,dbProvider:null,integrations:null},{suggestedName:w,suggestedFeatures:T,language:u}):null,ae=Ve?Jn(Ve):"",W=gr(w||"my-app").slice(0,32);try{let Q=await Do(W);!Q.available&&Q.suggestion&&(W=Q.suggestion)}catch{}ae&&(ae+=`
2221
2233
 
2222
- **Your app URL:** https://${W}.mistflow.app`);let Mt={question:`Your app will be at ${W}.mistflow.app \u2014 want to customize the URL?`,header:"URL",options:[{label:`Keep ${W}.mistflow.app (Recommended)`,description:"This URL is available"},{label:"Choose a different URL",description:"Type your preferred subdomain"}],multiSelect:!1};return se.push(Mt),c(JSON.stringify({status:"clarify",conversation_id:E.conversation_id,questions:T,questionCount:T.length,suggestedFeatures:P,suggestedName:g,suggestedSubdomain:W,reflection:u,briefText:ae,askUserQuestions:se,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:[...U?[U,""]:[],u?`${u}
2234
+ **Your app URL:** https://${W}.mistflow.app`);let Lt={question:`Your app will be at ${W}.mistflow.app \u2014 want to customize the URL?`,header:"URL",options:[{label:`Keep ${W}.mistflow.app (Recommended)`,description:"This URL is available"},{label:"Choose a different URL",description:"Type your preferred subdomain"}],multiSelect:!1};return ne.push(Lt),c(JSON.stringify({status:"clarify",conversation_id:N.conversation_id,questions:E,questionCount:E.length,suggestedFeatures:T,suggestedName:w,suggestedSubdomain:W,reflection:m,briefText:ae,askUserQuestions:ne,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:[...U?[U,""]:[],m?`${m}
2223
2235
  `:"",ae?`Here's what I'd build:
2224
2236
 
2225
2237
  ${ae}
2226
- `:"",`I have ${T.length} quick question${T.length===1?"":"s"} to pin down the details.`,"","\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550","STOP. DO NOT CONTINUE UNTIL THE USER ACTUALLY ANSWERS THESE QUESTIONS.","\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550","","You MUST call the AskUserQuestion tool (not a text prompt, not a","bash echo, not a chat message) to present these questions. The","user has to click through them. You do NOT get to pick the","'recommended' answer on their behalf \u2014 even if it seems obvious,","even if they said 'just build it', even if you are inside /loop","or any autonomous mode. The recommended label is a hint for the","user, not a permission slip for you to decide. Every question is","a real product decision the user is paying to make.","","What NOT to do (these have all happened in production transcripts","and are unacceptable):"," \u2717 'I'll go with the recommended defaults \u2014 say the word if you"," want to change any before I continue.' (auto-accepting)"," \u2717 Printing the questions + options as markdown and inferring"," answers from silence."," \u2717 Calling mist_plan with answers you picked yourself."," \u2717 Skipping AskUserQuestion because /loop is active \u2014 in loop,"," stop the loop and wait for the user. The loop will resume.","","How to call it: pass each object in the `askUserQuestions` array","below directly to AskUserQuestion. Each has `question`, `header`,","`options[]` (each option has `label` + `description`), and","`multiSelect`. The tool returns the user's selected labels.","","If your host does NOT expose AskUserQuestion (Cursor, Codex, some","other MCP clients), present each question one at a time as a","clearly-numbered chat message with all options visible, and WAIT","for a user reply before moving on. Still do NOT infer answers.","","Before calling mist_plan again with the answers, tell the user:"," 'Generating your plan now. This takes 30\u201360 seconds \u2014 I'm"," writing the data model, page layout, and build steps.'","Then call mist_plan with:",` conversationId: "${E.conversation_id}"`,` answers: { "<question text>": "<the user's selected label>", ... }`,' urlChoice: "<the URL subdomain the user picked>" \u2190 top-level param, NOT inside answers'," (description is no longer needed \u2014 the server has it from the first call)","","Follow-up clarify rounds are normal \u2014 if the user's answers reveal new","ambiguity, you'll get another `clarify` response. Relay those too,","same way, never inferring. Keep going until the response status is","'ready' or 'design_clarify_pending'.","","IMPORTANT: For the URL question, pass the answer as the top-level 'urlChoice' parameter (not inside answers).",`If the user keeps the default, set urlChoice: "${W}".`,'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(`
2227
- `)}))}if(E.status==="design_clarify"){let u=E.directions??[],g=E.plan.name??"your app",P;try{let ie=u.map(W=>({id:W.id,name:W.name,summary:W.summary,hero_headline:W.hero_headline,cta_text:W.cta_text,body_sample:W.body_sample,fonts:W.fonts,colors:W.colors,hero_treatment:W.hero_treatment,shape_lang:W.shape_lang,texture:W.texture,decoration_hint:W.decoration_hint})),yt=Ko(g,ie),Je=de(n,".mistflow");rn(Je,{recursive:!0});let ae=de(Je,"design-directions.html");an(ae,yt,"utf-8"),P=ae}catch(ie){console.error(`[mist_plan] design-directions preview render failed: ${ie instanceof Error?ie.message:String(ie)}`)}let T=u.map(ie=>({label:ie.name,description:`${ie.summary} \u2014 ${ie.fonts?.display??""} + ${ie.fonts?.body??""}`}));T.push({label:"Describe your own direction",description:"Skip the proposed options and give me a short description of how the app should feel."});let X={question:`${g} is planned. Now pick the creative direction \u2014 this shapes fonts, colors, and overall feel.`,header:"Design",options:T,multiSelect:!1},J=P?`A visual preview of all ${u.length} directions has been written to ${P}. 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.",se=P?`open "${P}"`:"";return c(JSON.stringify({status:"design_clarify",designConversationId:E.design_conversation_id,directions:u,previewPath:P,askUserQuestion:X,instruction:[`The plan for "${g}" is ready. I've proposed ${u.length} creative directions \u2014 each commits to a specific aesthetic (fonts, colors, voice).`,"",J,se?`Run this command for the user: ${se}`:"","","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: "${E.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(`
2228
- `)}))}let C=E.plan,re=C.name??"Untitled App",pe=E.methodology,D=C.steps;if(!Array.isArray(D)||D.length===0)return c("Plan generation incomplete \u2014 the plan is missing implementation steps. Please call mist_plan again with the same description to retry.",!0);let Z=C.publicPages;if(!Z||Array.isArray(Z)&&Z.length===0){let u=C.pages,g=D.some(T=>typeof T.name=="string"&&T.name.toLowerCase().includes("landing")||typeof T.title=="string"&&T.title.toLowerCase().includes("landing")),P=Array.isArray(u)&&u.some(T=>T.path==="/"||T.route==="/");g||P?Z=["/","/pricing"]:Z=["/"]}let Ie=C.primaryAction;if(!Ie){let u=C.features;if(Array.isArray(u)&&u.length>0){let P=u.find(X=>typeof X.priority=="string"&&X.priority.toLowerCase()==="must-have")??u[0];Ie={entity:P.name??P.title??"item",action:"create",fromPage:"/dashboard"}}}let me=C.nonNegotiables;(!me||Array.isArray(me)&&me.length===0)&&(me=["Landing page renders correctly at / with content (not a redirect)","Core user action works end-to-end (create entity, see it in list)"]);let Te=Xo(),He=de(qe(),".mistflow","plans");rn(He,{recursive:!0});try{let g=Date.now();for(let P of[He,de(qe(),".mistflow","mockup-state")])if(Ct(P))for(let T of ol(P))try{let X=de(P,T),J=rl(X).mtimeMs;g-J>6048e5&&il(X)}catch{}}catch{}let q;if(p){let u=Yt(p);u?q=u.id:console.error(`Landing design '${p}' not found \u2014 ignoring. Use mist_project action='landing-designs' to browse available landing designs.`)}let A=y||void 0,z=!!q,fe=D.some(u=>{let g=`${u.name??u.title} ${u.description??""}`.toLowerCase();return g.includes("landing")||g.includes("hero")||g.includes("marketing")||g.includes("homepage")}),V=!z&&fe?Io(j,{maxResults:2}):[];V.length>0&&(q=V[0].id,console.error(`Auto-assigned landing layout preset (default): ${V[0].title} (${q})`));let Pe={name:C.name,summary:C.summary,dataModel:C.dataModel,pages:C.pages,features:C.features,steps:D.map(u=>({...u,name:u.name??u.title})),design:C.design,landingDesign:q,appStyle:A,dbProvider:C.dbProvider??"neon",authModel:C.authModel,audienceType:C.audienceType??"b2c",roles:C.roles,defaultRole:C.defaultRole,publicPages:Z,navStyle:C.navStyle,multiTenant:C.multiTenant,primaryAction:Ie,nonNegotiables:me,requestedSubdomain:x,...m&&m.toLowerCase()!=="english"?{language:m}:{}};an(de(He,`${Te}.json`),JSON.stringify({plan:Pe,methodology:pe}));let We=D.map(u=>`${u.number}. ${u.name??u.title}`),Ge="",Ve=[],he;!z&&V.length>0&&(Ve=V.map(g=>({id:g.id,slug:g.slug,title:g.title,description:g.description,url:`${st()}/designs?tab=landing-designs`})),he={question:"What landing page style fits this app?",header:"Landing Design",options:[...V.map((g,P)=>({label:P===0?`${g.title} (Recommended)`:g.title,description:g.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 ${st()}/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_init. Do NOT assume the recommended option \u2014 the user may want a different style than we inferred. Recommended: ${V.map(g=>g.title).join(" or ")}. If they pick "Design from scratch", pass landingDesign='freeform' to mist_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='${V[0].id}' explicitly.`);let Ee="",Ne=[],je=void 0,I=(C.audienceType??"b2c")==="b2c",B={question:"Include a lifestyle photo in your landing page hero? Photos add warmth and human context; pure CSS stays cleaner.",header:"Hero",options:[{label:I?"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:I?"No, CSS only":"No, CSS only (Recommended)",description:"Animated gradients + glassmorphism, no photo \u2014 cleaner and more technical (like Stripe, Linear, Vercel)."}],multiSelect:!1},Q=" Also ask the user about the 'heroPhotoQuestion' provided. Once they pick, pass heroPhoto=true (photo) or heroPhoto=false (CSS only) to the mist_init call.",ye="",ne=[];for(let u of D){let g=u.name??u.title,P=u.integrationId;if(P){let T=lt(P);if(T){let X=ct(T.id);ne.push({step:g,presetId:T.id,presetName:T.name,envVars:X?.envVars??[]})}}}if(ne.length>0){let u=ne.flatMap(T=>T.envVars),g=[...new Set(u.map(T=>T.key))];ye=` This plan uses integrations (${ne.map(T=>T.presetName).join(", ")}). Detailed blueprints will be auto-injected during each integration step.${g.length>0?` The user will need these API keys: ${g.join(", ")}.`:""}`}return c(JSON.stringify({planId:Te,name:C.name,summary:C.summary,stepCount:D.length,steps:We,design:C.design,...q?{landingDesign:q}:{},...A?{appStyle:A}:{},...Ne.length>0?{recommendedAppStyles:Ne}:{},...je?{appStyleQuestion:je}:{},...Ve.length>0?{recommendedLandingDesigns:Ve}:{},...he?{landingDesignQuestion:he}:{},heroPhotoQuestion:B,...ne.length>0?{integrations:ne.map(u=>({step:u.step,preset:u.presetId,name:u.presetName,envVars:u.envVars}))}:{},message:`Plan generated for "${re}" (${D.length} steps).${q?` Landing layout "${q}" set as default.`:""}${A?` App style "${A}" will be applied across all pages.`:""}${ye}${Ee}${Ge}${Q}`,timingContext:`Planning took ~90 seconds. Building will take roughly ${Math.max(15,D.length*3)}\u2013${D.length*5} minutes total across ${D.length} steps (varies by complexity). Mention this to the user before starting the build so they know what to expect.`,mockupPrompt:`Before building, ask the user: "Would you like to preview a mockup of your app before we start building? You can iterate on the design, or skip straight to building." If the user wants a mockup, call mist_mockup({ planId: '${Te}' }). If the user says skip or "just build it", call mist_init({ planId: '${Te}', path: '<absolute path>' }) immediately.`,...U?{warning:U}:{}}))}var on,ul,yl,sr,or=_(()=>{"use strict";K();ue();ms();rs();Kt();Jo();Qo();on="__mistflow_url_choice__",ul=600*1e3;yl=N.object({description:N.string().optional().describe("App description or modification request. Required for the first call; omit on follow-up polls where only conversationId is passed. "),projectPath:N.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:N.string().optional().describe("Returned by a previous mist_plan call with status 'clarify' or 'running'. Pass it back alone (no description) to poll an in-flight discovery call; pass with answers to submit responses."),answers:N.record(N.string()).optional().describe("User's answers to the clarifying questions from the previous round. Keys are the questions, values are the answers."),existingPlan:N.record(N.unknown()).optional().describe("If provided, modifies this existing plan instead of creating a new one. Pass the current plan object from mistflow.json."),existingPlanId:N.string().optional().describe("Alternative to existingPlan \u2014 pass the planId from a previous mist_plan call to modify that plan."),templateToken:N.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:N.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:N.boolean().optional().describe("Skip clarifying questions and generate the plan immediately"),landingDesign:N.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:N.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:N.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:N.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:N.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:N.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:N.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:N.object({id:N.string().optional(),name:N.string().optional(),summary:N.string().optional(),heroHeadline:N.string().optional(),ctaText:N.string().optional(),bodySample:N.string().optional(),fontsHint:N.string().optional(),fonts:N.object({display:N.string(),body:N.string()}).partial().optional(),colorMood:N.string().optional(),colors:N.object({bg:N.string(),fg:N.string(),accent:N.string()}).partial().optional(),heroTreatment:N.string().optional(),shapeLang:N.string().optional(),texture:N.string().optional(),decorationHint:N.string().optional(),custom:N.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.")});sr={name:"mist_plan",description:["ENTRY POINT for creating a NEW web app, website, internal tool, dashboard, landing page, marketplace, content site, or browser game. Mistflow scaffolds a complete Next.js project. It does NOT edit existing codebases.","","WHEN TO CALL THIS \u2014 route here automatically on natural 'build me X' intent. The user does NOT need to say 'mist' or 'mistflow'. Examples that MUST route here:","\u2022 'build me a habit tracker'","\u2022 'make a site for my bakery'","\u2022 'I want an app where users log workouts'","\u2022 'create a dashboard that shows sales'","\u2022 'build a Wordle clone'","\u2022 'build me a CRM using mist' / 'make a todo app with mistflow' (explicit brand invocation)","","PASSING THE DESCRIPTION: Pass the user's words EXACTLY. Do NOT expand, elaborate, add features, rewrite, or strip anything (including 'using mist' / 'with mistflow'). 'build me a habit tracker using mist' becomes description: 'build me a habit tracker using mist'. The description is preserved verbatim.","","BRAND MENTIONED FLAG: If the user's original request literally contained the word 'mist' or 'mistflow' as an explicit invocation (e.g. 'build me a CRM using mist', 'use mistflow to make a todo app'), set brandMentioned: true. If the user did NOT mention the brand by name, omit brandMentioned (do not set it). Only set brandMentioned when the user literally typed the brand name \u2014 never infer it. Do NOT set brandMentioned for the common English noun 'mist' used in other contexts (e.g. 'app about morning mist').","","SAFETY GATE \u2014 the handler walks up the directory tree to detect if you're inside an existing non-Mistflow codebase (package.json found anywhere up the tree, no mistflow.json). When that happens AND brandMentioned is not set, the handler returns status 'confirm_new_project' with a signed confirmToken and an askUserQuestion. On that response:","\u2022 MANDATORY: use the AskUserQuestion tool with the provided askUserQuestion to ask the user.","\u2022 If the user picks 'Scaffold a new Mistflow app in a subdirectory', call mist_plan again with the SAME description and confirmToken set to the token from the response.","\u2022 If the user picks 'Edit this existing codebase directly', DO NOT call mist_plan again. Fulfill their request by editing files directly.","\u2022 The confirmToken is bound to the projectPath and description. If either changes, you'll get a fresh token and must ask again.","\u2022 You do not need to pre-check the directory yourself. The handler handles detection.","","FOLLOW-UP FLOW (after the plan is being generated):","\u2022 status 'clarify' \u2192 use AskUserQuestion with the provided askUserQuestions, then call mist_plan again with conversationId + answers + the same description.","\u2022 You may receive MULTIPLE 'clarify' rounds \u2014 if the user's answers reveal new ambiguity, the planner will ask follow-up questions. Keep relaying questions until you get status 'ready'. This is normal and produces better plans.","\u2022 status 'ready' \u2192 IMMEDIATELY call mist_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(`
2229
- `),inputSchema:yl,handler:xl}});function rr(t){if(!t)return"Item";let e=t.replace(/([a-z0-9])([A-Z])/g,"$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean);return e.length===0?"Item":e.map(s=>s.charAt(0).toUpperCase()+s.slice(1).toLowerCase()).join("")}function kl(t){return t&&t.replace(/([a-z0-9])([A-Z])/g,"$1-$2").replace(/[^A-Za-z0-9]+/g,"-").toLowerCase().replace(/^-+|-+$/g,"")||"item"}function Sl(t){let e=rr(t);return e.charAt(0).toLowerCase()+e.slice(1)}function ys(){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(`
2230
- `)}function ir(){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(`
2231
- `)}function bs(t){let e=ir();if(t.includes(fs)){let n=t.indexOf(fs),o=ar,i=t.indexOf(o,n);if(i===-1)return t.slice(0,n)+e;let r=t.slice(i+o.length);return t.slice(0,n)+e+r.replace(/^\n+/,"")}return t.replace(/\s+$/,"")+`
2232
-
2233
- `+e}function ws(t,e){let s=rr(t),n=e??Sl(t);return['import { createSelectSchema, createInsertSchema } from "drizzle-zod";','import { z } from "zod";',`import { ${n} } from "@/db/schema";`,"","// Select schema \u2014 shape of a row read from the DB. Use this on both","// sides of the wire: backend validates responses, frontend gets types.",`export const ${s}Schema = createSelectSchema(${n});`,`export type ${s} = z.infer<typeof ${s}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${s}Input = createInsertSchema(${n}).omit({`," id: true,"," createdAt: true,","});",`export type Create${s}Input = z.infer<typeof Create${s}Input>;`,""].join(`
2234
- `)}function vs(t){return`contracts/${kl(t)}.ts`}var fs,ar,xs=_(()=>{"use strict";fs="<!-- mist:contracts:start -->",ar="<!-- mist:contracts:end -->"});import{z as At}from"zod";import{existsSync as Ze,mkdirSync as ks,writeFileSync as Ss,readFileSync as cn,readdirSync as dr,copyFileSync as Pl}from"fs";import{join as ke,resolve as pr,dirname as Et,isAbsolute as Il}from"path";import{homedir as Tl}from"os";import{spawn as Tu}from"child_process";import{randomBytes as _l}from"crypto";import{simpleGit as Cl}from"simple-git";function Rl(t){let e=ke(Tl(),".mistflow","plans",`${t}.json`);if(!Ze(e))return null;try{let s=JSON.parse(cn(e,"utf-8"));return s.plan?{plan:s.plan}:null}catch{return null}}function Al(t){let e=Et(pr(t)),s=10,n=0;for(;n<s&&e!==Et(e);){if(Ze(ke(e,"pnpm-workspace.yaml"))||Ze(ke(e,"lerna.json")))return e;let o=ke(e,"package.json");if(Ze(o))try{if(JSON.parse(cn(o,"utf-8")).workspaces)return e}catch{}e=Et(e),n++}return null}function M(t,e,s){let n=ke(t,e);ks(Et(n),{recursive:!0}),Ss(n,s)}function Nl(t){if(!Ze(t))return!0;let e;try{e=dr(t)}catch{return!1}return e.filter(n=>n!==".mistflow").length===0}function Ol(t){let e=t.match(/^---\n([\s\S]*?)\n---/);if(!e)return null;let s=e[1],n={theme:"light",colors:{},typography:{},rounded:{},spacing:{}},o=s.split(`
2235
- `),i=null,r=null,a=l=>l.replace(/^["']|["']$/g,"").trim();for(let l of o){let d=l.replace(/\r$/,"");if(!d.trim()||d.trim().startsWith("#"))continue;if(d.startsWith("theme:")){let p=a(d.slice(6).trim());(p==="dark"||p==="light")&&(n.theme=p);continue}let h=d.match(/^(colors|typography|rounded|spacing):\s*$/);if(h){i=h[1],r=null;continue}if(i==="typography"){let p=d.match(/^ {2}([a-zA-Z0-9_-]+):\s*$/);if(p){r=p[1].replace(/-/g,"_"),n.typography[r]={};continue}let y=d.match(/^ {4}([a-zA-Z0-9_-]+):\s*(.+)$/);if(y&&r){n.typography[r][y[1]]=a(y[2]);continue}}let m=d.match(/^ {2}([a-zA-Z0-9_-]+):\s*(.+)$/);if(m&&i){let p=m[1].replace(/-/g,"_"),y=a(m[2]);i==="colors"?n.colors[p]=y:i==="rounded"?n.rounded[p]=y:i==="spacing"&&(n.spacing[p]=y)}}return n}function Ml(t,e){let s=t.colors,o=[["--color-background",s.background],["--color-foreground",s.on_background??s.on_surface],["--color-card",s.surface??s.background],["--color-card-foreground",s.on_surface??s.on_background],["--color-popover",s.surface??s.background],["--color-popover-foreground",s.on_surface??s.on_background],["--color-muted",s.surface_variant??s.outline_variant??s.outline],["--color-muted-foreground",s.on_surface_variant??s.outline],["--color-border",s.outline_variant??s.outline],["--color-input",s.outline_variant??s.outline],["--color-primary",s.primary],["--color-primary-foreground",s.on_primary],["--color-ring",s.primary],["--color-secondary",s.secondary],["--color-secondary-foreground",s.on_secondary],["--color-accent",s.secondary],["--color-accent-foreground",s.on_secondary],["--color-destructive",s.error],["--color-destructive-foreground",s.on_error],["--color-success",s.success],["--color-success-foreground",s.on_success],["--color-warning",s.warning],["--color-warning-foreground",s.on_warning],["--color-info",s.info??s.primary],["--color-info-foreground",s.on_info??s.on_primary]].filter(a=>typeof a[1]=="string").map(([a,l])=>` ${a}: ${l};`).join(`
2238
+ `:"",`I have ${E.length} quick question${E.length===1?"":"s"} to pin down the details.`,"","\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550","STOP. DO NOT CONTINUE UNTIL THE USER ACTUALLY ANSWERS THESE QUESTIONS.","\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550","","You MUST ask the user via your host's native structured-question","tool (not a text prompt, not a bash echo, not a chat message).","The user has to actually choose. You do NOT get to pick the","'recommended' answer on their behalf \u2014 even if it seems obvious,","even if they said 'just build it', even if you are inside /loop","or any autonomous mode. The recommended label is a hint for the","user, not a permission slip for you to decide. Every question is","a real product decision the user is paying to make.","","Per host (use whichever applies):"," \u2022 Claude Code \u2192 AskUserQuestion tool"," \u2022 Cursor \u2192 quick-pick UI"," \u2022 OpenAI Codex (Plan mode) \u2192 request_user_input tool"," \u2022 OpenAI Codex (Default mode) \u2192 request_user_input returns"," 'unavailable in <mode> mode'. When you see that error, STOP"," your turn, print the questions as a numbered chat message"," with all options visible, and wait for the user's next"," message. Do NOT resume by picking answers yourself."," \u2022 Any host without a native question tool \u2192 same as above:"," stop your turn, print the questions, wait.","","What NOT to do (these have all happened in production transcripts","and are unacceptable):"," \u2717 'I'll go with the recommended defaults \u2014 say the word if you"," want to change any before I continue.' (auto-accepting)"," \u2717 'Locking in the defaults with X as the only override.'"," (inferring answers from the original spec)"," \u2717 Printing the questions + options as markdown and inferring"," answers from silence."," \u2717 Calling mist_plan with answers you picked yourself."," \u2717 Skipping the question UI because /loop is active \u2014 in loop,"," stop the loop and wait for the user. The loop will resume.","","How to call the tool (when available): pass each object in the","`askUserQuestions` array below. Each has `question`, `header`,","`options[]` (each option has `label` + `description`), and","`multiSelect`. The tool returns the user's selected labels.","","Before calling mist_plan again with the answers, tell the user:"," 'Generating your plan now. This takes 30\u201360 seconds \u2014 I'm"," writing the data model, page layout, and build steps.'","Then call mist_plan with:",` conversationId: "${N.conversation_id}"`,` answers: { "<question text>": "<the user's selected label>", ... }`,' urlChoice: "<the URL subdomain the user picked>" \u2190 top-level param, NOT inside answers'," (description is no longer needed \u2014 the server has it from the first call)","","Follow-up clarify rounds are normal \u2014 if the user's answers reveal new","ambiguity, you'll get another `clarify` response. Relay those too,","same way, never inferring. Keep going until the response status is","'ready' or 'design_clarify_pending'.","","IMPORTANT: For the URL question, pass the answer as the top-level 'urlChoice' parameter (not inside answers).",`If the user keeps the default, set urlChoice: "${W}".`,'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(`
2239
+ `)}))}if(N.status==="design_clarify"){let m=N.directions??[],w=N.plan.name??"your app",T;try{let ie=m.map(W=>({id:W.id,name:W.name,summary:W.summary,hero_headline:W.hero_headline,cta_text:W.cta_text,body_sample:W.body_sample,fonts:W.fonts,colors:W.colors,hero_treatment:W.hero_treatment,shape_lang:W.shape_lang,texture:W.texture,decoration_hint:W.decoration_hint})),bt=Qn(w,ie),Ve=de(o,".mistflow");io(Ve,{recursive:!0});let ae=de(Ve,"design-directions.html");ao(ae,bt,"utf-8"),T=ae}catch(ie){console.error(`[mist_plan] design-directions preview render failed: ${ie instanceof Error?ie.message:String(ie)}`)}let E=m.map(ie=>({label:ie.name,description:`${ie.summary} \u2014 ${ie.fonts?.display??""} + ${ie.fonts?.body??""}`}));E.push({label:"Describe your own direction",description:"Skip the proposed options and give me a short description of how the app should feel."});let te={question:`${w} is planned. Now pick the creative direction \u2014 this shapes fonts, colors, and overall feel.`,header:"Design",options:E,multiSelect:!1},Y=T?[`A visual preview of all ${m.length} directions has been written to:`,` ${T}`,"Each card is rendered in its direction's own fonts + palette so the user can see what they're picking \u2014 the picker is meaningless without it.","","OPEN THE PREVIEW NOW. Pick whichever works in your host:",` \u2022 macOS: run open "${T}"`,` \u2022 Linux: run xdg-open "${T}"`,` \u2022 Windows: run start "" "${T}"`," \u2022 No shell access: tell the user the exact path and ask them to open it in their browser before they answer.",'Do NOT skip the preview. "I described the options in text" is NOT a replacement \u2014 the user needs to SEE the directions.'].join(`
2240
+ `):"No visual preview rendered (see server logs). The question options below carry fonts + mood so the user can still pick, but warn them the HTML preview didn't land.",ne=T?`open "${T}"`:"";return c(JSON.stringify({status:"design_clarify",designConversationId:N.design_conversation_id,directions:m,previewPath:T,askUserQuestion:te,instruction:[`The plan for "${w}" is ready. I've proposed ${m.length} creative directions \u2014 each commits to a specific aesthetic (fonts, colors, voice).`,"","\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550","STOP. DO NOT PICK A DIRECTION YOURSELF. THE USER PICKS.","\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550","",Y,ne?`Run this command to open the preview for the user: ${ne}`:"","","Then ASK THE USER which direction they want. Use whichever your host supports:"," \u2022 Claude Code \u2192 AskUserQuestion tool with the directionQuestion payload above"," \u2022 Cursor \u2192 quick-pick UI"," \u2022 OpenAI Codex (Plan mode) \u2192 request_user_input tool"," \u2022 OpenAI Codex (Default mode) OR any host without a native question tool:"," STOP your turn. Print the direction names + one-line summaries as a"," numbered chat message and wait for the user's reply. Do NOT resume.","","What NOT to do (these have all happened in production transcripts and are unacceptable):"," \u2717 'I'll go with a custom ops-focused brief that fits better than the default.'"," (auto-picking a direction the user never chose)"," \u2717 'Submitting a custom design brief now so we can keep moving.'"," \u2717 Calling mist_plan with designDirection: { custom: '<your own description>' }"," because the user didn't respond fast enough."," \u2717 Skipping this picker because you already decided on the design yourself."," \u2717 Opening the HTML preview but not actually asking the user anything.","","Once the user picks a direction, call mist_plan with:",` designConversationId: "${N.design_conversation_id}"`," designDirection: <the full direction object from the 'directions' array that the user picked>","(No description needed \u2014 server has it from the first call.)","","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?'),"," wait for a real answer, then call mist_plan with"," designDirection: { custom: '<their exact words>' } + 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(`
2241
+ `)}))}let O=N.plan,pe=O.name??"Untitled App",z=N.methodology,G=O.steps;if(!Array.isArray(G)||G.length===0)return c("Plan generation incomplete \u2014 the plan is missing implementation steps. Please call mist_plan again with the same description to retry.",!0);let me=O.publicPages;if(!me||Array.isArray(me)&&me.length===0){let m=O.pages,w=G.some(E=>typeof E.name=="string"&&E.name.toLowerCase().includes("landing")||typeof E.title=="string"&&E.title.toLowerCase().includes("landing")),T=Array.isArray(m)&&m.some(E=>E.path==="/"||E.route==="/");w||T?me=["/","/pricing"]:me=["/"]}let Pe=O.primaryAction;if(!Pe){let m=O.features;if(Array.isArray(m)&&m.length>0){let T=m.find(te=>typeof te.priority=="string"&&te.priority.toLowerCase()==="must-have")??m[0];Pe={entity:T.name??T.title??"item",action:"create",fromPage:"/dashboard"}}}let Ie=O.nonNegotiables;(!Ie||Array.isArray(Ie)&&Ie.length===0)&&(Ie=["Landing page renders correctly at / with content (not a redirect)","Core user action works end-to-end (create entity, see it in list)"]);let De=Zn(),V=de(Be(),".mistflow","plans");io(V,{recursive:!0});try{let w=Date.now();for(let T of[V,de(Be(),".mistflow","mockup-state")])if(Rt(T))for(let E of Al(T))try{let te=de(T,E),Y=_l(te).mtimeMs;w-Y>6048e5&&Rl(te)}catch{}}catch{}let A;if(p){let m=Yt(p);m?A=m.id:console.error(`Landing design '${p}' not found \u2014 ignoring. Use mist_project action='landing-designs' to browse available landing designs.`)}let q=f||void 0,he=!!A,se=G.some(m=>{let w=`${m.name??m.title} ${m.description??""}`.toLowerCase();return w.includes("landing")||w.includes("hero")||w.includes("marketing")||w.includes("homepage")}),ee=!he&&se?In(D,{maxResults:2}):[];ee.length>0&&(A=ee[0].id,console.error(`Auto-assigned landing layout preset (default): ${ee[0].title} (${A})`));let We={name:O.name,summary:O.summary,dataModel:O.dataModel,pages:O.pages,features:O.features,steps:G.map(m=>({...m,name:m.name??m.title})),design:O.design,landingDesign:A,appStyle:q,dbProvider:O.dbProvider??"neon",authModel:O.authModel,audienceType:O.audienceType??"b2c",roles:O.roles,defaultRole:O.defaultRole,publicPages:me,navStyle:O.navStyle,multiTenant:O.multiTenant,primaryAction:Pe,nonNegotiables:Ie,requestedSubdomain:k,...u&&u.toLowerCase()!=="english"?{language:u}:{}};ao(de(V,`${De}.json`),JSON.stringify({plan:We,methodology:z}));let et=G.map(m=>`${m.number}. ${m.name??m.title}`),tt="",ge=[],Ce;!he&&ee.length>0&&(ge=ee.map(w=>({id:w.id,slug:w.slug,title:w.title,description:w.description,url:`${nt()}/designs?tab=landing-designs`})),Ce={question:"What landing page style fits this app?",header:"Landing Design",options:[...ee.map((w,T)=>({label:T===0?`${w.title} (Recommended)`:w.title,description:w.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 ${nt()}/designs?tab=landing-designs and pass the ID back.`}],multiSelect:!1},tt=` REQUIRED: ask the user which landing design they want using the AskUserQuestion tool with the 'landingDesignQuestion' object before calling mist_init. Do NOT assume the recommended option \u2014 the user may want a different style than we inferred. Recommended: ${ee.map(w=>w.title).join(" or ")}. If they pick "Design from scratch", pass landingDesign='freeform' to mist_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='${ee[0].id}' explicitly.`);let Ge="",je=[],y=void 0,B=(O.audienceType??"b2c")==="b2c",Z={question:"Include a lifestyle photo in your landing page hero? Photos add warmth and human context; pure CSS stays cleaner.",header:"Hero",options:[{label:B?"Yes, add a photo (Recommended)":"Yes, add a photo",description:"Lifestyle photography background with your product preview overlaid \u2014 feels warm and human (like HabitFlow, Airbnb)."},{label:B?"No, CSS only":"No, CSS only (Recommended)",description:"Animated gradients + glassmorphism, no photo \u2014 cleaner and more technical (like Stripe, Linear, Vercel)."}],multiSelect:!1},Ae=" Also ask the user about the 'heroPhotoQuestion' provided. Once they pick, pass heroPhoto=true (photo) or heroPhoto=false (CSS only) to the mist_init call.",be="",_=[];for(let m of G){let w=m.name??m.title,T=m.integrationId;if(T){let E=ct(T);if(E){let te=dt(E.id);_.push({step:w,presetId:E.id,presetName:E.name,envVars:te?.envVars??[]})}}}if(_.length>0){let m=_.flatMap(E=>E.envVars),w=[...new Set(m.map(E=>E.key))];be=` This plan uses integrations (${_.map(E=>E.presetName).join(", ")}). Detailed blueprints will be auto-injected during each integration step.${w.length>0?` The user will need these API keys: ${w.join(", ")}.`:""}`}return c(JSON.stringify({planId:De,name:O.name,summary:O.summary,stepCount:G.length,steps:et,design:O.design,...A?{landingDesign:A}:{},...q?{appStyle:q}:{},...je.length>0?{recommendedAppStyles:je}:{},...y?{appStyleQuestion:y}:{},...ge.length>0?{recommendedLandingDesigns:ge}:{},...Ce?{landingDesignQuestion:Ce}:{},heroPhotoQuestion:Z,..._.length>0?{integrations:_.map(m=>({step:m.step,preset:m.presetId,name:m.presetName,envVars:m.envVars}))}:{},message:`Plan generated for "${pe}" (${G.length} steps).${A?` Landing layout "${A}" set as default.`:""}${q?` App style "${q}" will be applied across all pages.`:""}${be}${Ge}${tt}${Ae}`,timingContext:`Planning took ~90 seconds. Building will take roughly ${Math.max(15,G.length*3)}\u2013${G.length*5} minutes total across ${G.length} steps (varies by complexity). Mention this to the user before starting the build so they know what to expect.`,mockupPrompt:`Before building, ask the user: "Would you like to preview a mockup of your app before we start building? You can iterate on the design, or skip straight to building." If the user wants a mockup, call mist_mockup({ planId: '${De}' }). If the user says skip or "just build it", call mist_init({ planId: '${De}', path: '<absolute path>' }) immediately.`,...U?{warning:U}:{}}))}var so,Ml,ql,ns,ss=S(()=>{"use strict";X();ue();hr();ir();Qt();Kn();Xn();so="__mistflow_url_choice__",Ml=600*1e3;ql=C.object({description:C.string().optional().describe("App description or modification request. Required for the first call; omit on follow-up polls where only conversationId is passed. "),projectPath:C.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:C.string().optional().describe("Returned by a previous mist_plan call with status 'clarify' or 'running'. Pass it back alone (no description) to poll an in-flight discovery call; pass with answers to submit responses."),answers:C.union([C.record(C.string()),C.array(C.object({question:C.string().optional(),decisionKey:C.string().optional(),answer:C.string()}))]).optional().describe("User's answers to the clarifying questions. Preferred shape: array of { question, decisionKey, answer } objects (supports duplicate decisionKeys). Legacy shape: { '<question text>': '<answer label>' } object map. Both are accepted; the server normalizes either."),existingPlan:C.record(C.unknown()).optional().describe("If provided, modifies this existing plan instead of creating a new one. Pass the current plan object from mistflow.json."),existingPlanId:C.string().optional().describe("Alternative to existingPlan \u2014 pass the planId from a previous mist_plan call to modify that plan."),templateToken:C.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:C.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:C.boolean().optional().describe("Skip clarifying questions and generate the plan immediately"),landingDesign:C.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:C.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:C.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:C.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:C.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:C.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:C.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:C.object({id:C.string().optional(),name:C.string().optional(),summary:C.string().optional(),heroHeadline:C.string().optional(),ctaText:C.string().optional(),bodySample:C.string().optional(),fontsHint:C.string().optional(),fonts:C.object({display:C.string(),body:C.string()}).partial().optional(),colorMood:C.string().optional(),colors:C.object({bg:C.string(),fg:C.string(),accent:C.string()}).partial().optional(),heroTreatment:C.string().optional(),shapeLang:C.string().optional(),texture:C.string().optional(),decorationHint:C.string().optional(),custom:C.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.")});ns={name:"mist_plan",description:["ENTRY POINT for creating a NEW web app, website, internal tool, dashboard, landing page, marketplace, content site, or browser game. Mistflow scaffolds a complete Next.js project. It does NOT edit existing codebases.","","WHEN TO CALL THIS \u2014 route here automatically on natural 'build me X' intent. The user does NOT need to say 'mist' or 'mistflow'. Examples that MUST route here:","\u2022 'build me a habit tracker'","\u2022 'make a site for my bakery'","\u2022 'I want an app where users log workouts'","\u2022 'create a dashboard that shows sales'","\u2022 'build a Wordle clone'","\u2022 'build me a CRM using mist' / 'make a todo app with mistflow' (explicit brand invocation)","","PASSING THE DESCRIPTION: Pass the user's words EXACTLY. Do NOT expand, elaborate, add features, rewrite, or strip anything (including 'using mist' / 'with mistflow'). 'build me a habit tracker using mist' becomes description: 'build me a habit tracker using mist'. The description is preserved verbatim.","","BRAND MENTIONED FLAG: If the user's original request literally contained the word 'mist' or 'mistflow' as an explicit invocation (e.g. 'build me a CRM using mist', 'use mistflow to make a todo app'), set brandMentioned: true. If the user did NOT mention the brand by name, omit brandMentioned (do not set it). Only set brandMentioned when the user literally typed the brand name \u2014 never infer it. Do NOT set brandMentioned for the common English noun 'mist' used in other contexts (e.g. 'app about morning mist').","","SAFETY GATE \u2014 the handler walks up the directory tree to detect if you're inside an existing non-Mistflow codebase (package.json found anywhere up the tree, no mistflow.json). When that happens AND brandMentioned is not set, the handler returns status 'confirm_new_project' with a signed confirmToken and an askUserQuestion. On that response:","\u2022 MANDATORY: use the AskUserQuestion tool with the provided askUserQuestion to ask the user.","\u2022 If the user picks 'Scaffold a new Mistflow app in a subdirectory', call mist_plan again with the SAME description and confirmToken set to the token from the response.","\u2022 If the user picks 'Edit this existing codebase directly', DO NOT call mist_plan again. Fulfill their request by editing files directly.","\u2022 The confirmToken is bound to the projectPath and description. If either changes, you'll get a fresh token and must ask again.","\u2022 You do not need to pre-check the directory yourself. The handler handles detection.","","FOLLOW-UP FLOW (after the plan is being generated):","\u2022 status 'clarify' \u2192 use AskUserQuestion with the provided askUserQuestions, then call mist_plan again with conversationId + answers + the same description.","\u2022 You may receive MULTIPLE 'clarify' rounds \u2014 if the user's answers reveal new ambiguity, the planner will ask follow-up questions. Keep relaying questions until you get status 'ready'. This is normal and produces better plans.","\u2022 status 'ready' \u2192 IMMEDIATELY call mist_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(`
2242
+ `),inputSchema:ql,handler:Wl}});function is(t){if(!t)return"Item";let e=t.replace(/([a-z0-9])([A-Z])/g,"$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean);return e.length===0?"Item":e.map(r=>r.charAt(0).toUpperCase()+r.slice(1).toLowerCase()).join("")}function Gl(t){return t&&t.replace(/([a-z0-9])([A-Z])/g,"$1-$2").replace(/[^A-Za-z0-9]+/g,"-").toLowerCase().replace(/^-+|-+$/g,"")||"item"}function Vl(t){let e=is(t);return e.charAt(0).toLowerCase()+e.slice(1)}function br(){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(`
2243
+ `)}function as(){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(`
2244
+ `)}function wr(t){let e=as();if(t.includes(yr)){let o=t.indexOf(yr),n=ls,i=t.indexOf(n,o);if(i===-1)return t.slice(0,o)+e;let s=t.slice(i+n.length);return t.slice(0,o)+e+s.replace(/^\n+/,"")}return t.replace(/\s+$/,"")+`
2245
+
2246
+ `+e}function vr(t,e){let r=is(t),o=e??Vl(t);return['import { createSelectSchema, createInsertSchema } from "drizzle-zod";','import { z } from "zod";',`import { ${o} } from "@/db/schema";`,"","// Select schema \u2014 shape of a row read from the DB. Use this on both","// sides of the wire: backend validates responses, frontend gets types.",`export const ${r}Schema = createSelectSchema(${o});`,`export type ${r} = z.infer<typeof ${r}Schema>;`,"","// Insert schema \u2014 shape accepted when creating a new row. id +","// createdAt are generated server-side, so we strip them from the","// input contract. Adjust if your schema uses different column names.",`export const Create${r}Input = createInsertSchema(${o}).omit({`," id: true,"," createdAt: true,","});",`export type Create${r}Input = z.infer<typeof Create${r}Input>;`,""].join(`
2247
+ `)}function xr(t){return`contracts/${Gl(t)}.ts`}var yr,ls,kr=S(()=>{"use strict";yr="<!-- mist:contracts:start -->",ls="<!-- mist:contracts:end -->"});import{z as Nt}from"zod";import{existsSync as Xe,mkdirSync as Sr,writeFileSync as Tr,readFileSync as co,readdirSync as ps,copyFileSync as Jl}from"fs";import{join as Se,resolve as us,dirname as Et,isAbsolute as Kl}from"path";import{homedir as Yl}from"os";import{spawn as im}from"child_process";import{randomBytes as Ql}from"crypto";import{simpleGit as Zl}from"simple-git";function Xl(t){let e=Se(Yl(),".mistflow","plans",`${t}.json`);if(!Xe(e))return null;try{let r=JSON.parse(co(e,"utf-8"));return r.plan?{plan:r.plan}:null}catch{return null}}function ec(t){let e=Et(us(t)),r=10,o=0;for(;o<r&&e!==Et(e);){if(Xe(Se(e,"pnpm-workspace.yaml"))||Xe(Se(e,"lerna.json")))return e;let n=Se(e,"package.json");if(Xe(n))try{if(JSON.parse(co(n,"utf-8")).workspaces)return e}catch{}e=Et(e),o++}return null}function M(t,e,r){let o=Se(t,e);Sr(Et(o),{recursive:!0}),Tr(o,r)}function oc(t){if(!Xe(t))return!0;let e;try{e=ps(t)}catch{return!1}return e.filter(o=>o!==".mistflow").length===0}function sc(t){let e=t.match(/^---\n([\s\S]*?)\n---/);if(!e)return null;let r=e[1],o={theme:"light",colors:{},typography:{},rounded:{},spacing:{}},n=r.split(`
2248
+ `),i=null,s=null,a=l=>l.replace(/^["']|["']$/g,"").trim();for(let l of n){let d=l.replace(/\r$/,"");if(!d.trim()||d.trim().startsWith("#"))continue;if(d.startsWith("theme:")){let p=a(d.slice(6).trim());(p==="dark"||p==="light")&&(o.theme=p);continue}let h=d.match(/^(colors|typography|rounded|spacing):\s*$/);if(h){i=h[1],s=null;continue}if(i==="typography"){let p=d.match(/^ {2}([a-zA-Z0-9_-]+):\s*$/);if(p){s=p[1].replace(/-/g,"_"),o.typography[s]={};continue}let f=d.match(/^ {4}([a-zA-Z0-9_-]+):\s*(.+)$/);if(f&&s){o.typography[s][f[1]]=a(f[2]);continue}}let u=d.match(/^ {2}([a-zA-Z0-9_-]+):\s*(.+)$/);if(u&&i){let p=u[1].replace(/-/g,"_"),f=a(u[2]);i==="colors"?o.colors[p]=f:i==="rounded"?o.rounded[p]=f:i==="spacing"&&(o.spacing[p]=f)}}return o}function ic(t,e){let r=t.colors,n=[["--color-background",r.background],["--color-foreground",r.on_background??r.on_surface],["--color-card",r.surface??r.background],["--color-card-foreground",r.on_surface??r.on_background],["--color-popover",r.surface??r.background],["--color-popover-foreground",r.on_surface??r.on_background],["--color-muted",r.surface_variant??r.outline_variant??r.outline],["--color-muted-foreground",r.on_surface_variant??r.outline],["--color-border",r.outline_variant??r.outline],["--color-input",r.outline_variant??r.outline],["--color-primary",r.primary],["--color-primary-foreground",r.on_primary],["--color-ring",r.primary],["--color-secondary",r.secondary],["--color-secondary-foreground",r.on_secondary],["--color-accent",r.secondary],["--color-accent-foreground",r.on_secondary],["--color-destructive",r.error],["--color-destructive-foreground",r.on_error],["--color-success",r.success],["--color-success-foreground",r.on_success],["--color-warning",r.warning],["--color-warning-foreground",r.on_warning],["--color-info",r.info??r.primary],["--color-info-foreground",r.on_info??r.on_primary]].filter(a=>typeof a[1]=="string").map(([a,l])=>` ${a}: ${l};`).join(`
2236
2249
  `),i=[` --radius-sm: ${t.rounded.sm??"0.25rem"};`,` --radius-md: ${t.rounded.md??e};`,` --radius-lg: ${t.rounded.lg??"0.5rem"};`,` --radius-xl: ${t.rounded.xl??"0.75rem"};`].join(`
2237
2250
  `);return`@import "tailwindcss";
2238
2251
  @import "tw-animate-css";
2239
2252
 
2240
2253
  @theme {
2241
- ${o}
2254
+ ${n}
2242
2255
  ${i}
2243
2256
  }
2244
2257
 
@@ -2289,11 +2302,11 @@ ${i}
2289
2302
 
2290
2303
  button, [role="button"] { transition: transform 100ms var(--ease-quart-out); }
2291
2304
  button:active:not(:disabled), [role="button"]:active:not(:disabled) { transform: scale(0.97); }
2292
- `}function $l(t,e,s){let n=t?.borderRadius??"subtle",o=jl[n]??"0.375rem";if(s){let r=Ol(s);if(r)return Ml(r,o)}return`@import "tailwindcss";
2305
+ `}function ac(t,e,r){let o=t?.borderRadius??"subtle",n=rc[o]??"0.375rem";if(r){let s=sc(r);if(s)return ic(s,n)}return`@import "tailwindcss";
2293
2306
  @import "tw-animate-css";
2294
2307
 
2295
2308
  @theme {
2296
- ${[...Dl.map(([r,a])=>` ${r}: ${a};`)," --radius-sm: 0.25rem;",` --radius-md: ${o};`," --radius-lg: 0.5rem;"," --radius-xl: 0.75rem;"].join(`
2309
+ ${[...nc.map(([s,a])=>` ${s}: ${a};`)," --radius-sm: 0.25rem;",` --radius-md: ${n};`," --radius-lg: 0.5rem;"," --radius-xl: 0.75rem;"].join(`
2297
2310
  `)}
2298
2311
  }
2299
2312
 
@@ -2343,34 +2356,34 @@ ${[...Dl.map(([r,a])=>` ${r}: ${a};`)," --radius-sm: 0.25rem;",` --radius-md:
2343
2356
 
2344
2357
  button, [role="button"] { transition: transform 100ms var(--ease-quart-out); }
2345
2358
  button:active:not(:disabled), [role="button"]:active:not(:disabled) { transform: scale(0.97); }
2346
- `}function cr(t){let e=t.replace(/[^A-Za-z0-9_ -]/g,"");return lr[e]?lr[e]:e.replace(/\s+/g,"_")}function Ul(t){return t?{english:"en",spanish:"es",french:"fr",german:"de",italian:"it",portuguese:"pt",dutch:"nl",russian:"ru",japanese:"ja",chinese:"zh",korean:"ko",arabic:"ar",hebrew:"he",hindi:"hi",turkish:"tr",polish:"pl",swedish:"sv",norwegian:"no",danish:"da",finnish:"fi",thai:"th",vietnamese:"vi",indonesian:"id",malay:"ms",farsi:"fa",persian:"fa",czech:"cs",greek:"el",romanian:"ro",hungarian:"hu",ukrainian:"uk",bengali:"bn",tamil:"ta",telugu:"te",urdu:"ur"}[t.toLowerCase()]??"en":"en"}function Fl(t,e,s){let n=t.replace(/[\\"`$]/g,""),o=Ul(s),r=Ll.has(o)?`lang="${o}" dir="rtl"`:`lang="${o}"`,a=e?.fonts?.heading,l=e?.fonts?.body;if(!a&&!l)return`import type { Metadata } from "next";
2359
+ `}function ds(t){let e=t.replace(/[^A-Za-z0-9_ -]/g,"");return cs[e]?cs[e]:e.replace(/\s+/g,"_")}function lc(t){return t?{english:"en",spanish:"es",french:"fr",german:"de",italian:"it",portuguese:"pt",dutch:"nl",russian:"ru",japanese:"ja",chinese:"zh",korean:"ko",arabic:"ar",hebrew:"he",hindi:"hi",turkish:"tr",polish:"pl",swedish:"sv",norwegian:"no",danish:"da",finnish:"fi",thai:"th",vietnamese:"vi",indonesian:"id",malay:"ms",farsi:"fa",persian:"fa",czech:"cs",greek:"el",romanian:"ro",hungarian:"hu",ukrainian:"uk",bengali:"bn",tamil:"ta",telugu:"te",urdu:"ur"}[t.toLowerCase()]??"en":"en"}function dc(t,e,r){let o=t.replace(/[\\"`$]/g,""),n=lc(r),s=cc.has(n)?`lang="${n}" dir="rtl"`:`lang="${n}"`,a=e?.fonts?.heading,l=e?.fonts?.body;if(!a&&!l)return`import type { Metadata } from "next";
2347
2360
  import { DM_Sans } from "next/font/google";
2348
2361
  import { Toaster } from "sonner";
2349
2362
  import "./globals.css";
2350
2363
 
2351
2364
  const body = DM_Sans({ subsets: ["latin"], variable: "--font-body" });
2352
2365
 
2353
- export const metadata: Metadata = { title: "${n}", description: "Built with Mistflow" };
2366
+ export const metadata: Metadata = { title: "${o}", description: "Built with Mistflow" };
2354
2367
 
2355
2368
  export default function RootLayout({ children }: { children: React.ReactNode }) {
2356
2369
  return (
2357
- <html ${r}>
2370
+ <html ${s}>
2358
2371
  <body className={body.variable}>{children}<Toaster richColors /></body>
2359
2372
  </html>
2360
2373
  );
2361
2374
  }
2362
- `;let d=cr(a??l),h=cr(l??a);return d===h?`import type { Metadata } from "next";
2375
+ `;let d=ds(a??l),h=ds(l??a);return d===h?`import type { Metadata } from "next";
2363
2376
  import { ${d} } from "next/font/google";
2364
2377
  import { Toaster } from "sonner";
2365
2378
  import "./globals.css";
2366
2379
 
2367
2380
  const font = ${d}({ subsets: ["latin"], variable: "--font-body" });
2368
2381
 
2369
- export const metadata: Metadata = { title: "${n}", description: "Built with Mistflow" };
2382
+ export const metadata: Metadata = { title: "${o}", description: "Built with Mistflow" };
2370
2383
 
2371
2384
  export default function RootLayout({ children }: { children: React.ReactNode }) {
2372
2385
  return (
2373
- <html ${r}>
2386
+ <html ${s}>
2374
2387
  <body className={font.variable}>{children}<Toaster richColors /></body>
2375
2388
  </html>
2376
2389
  );
@@ -2383,72 +2396,2059 @@ import "./globals.css";
2383
2396
  const heading = ${d}({ subsets: ["latin"], variable: "--font-heading" });
2384
2397
  const body = ${h}({ subsets: ["latin"], variable: "--font-body" });
2385
2398
 
2386
- export const metadata: Metadata = { title: "${n}", description: "Built with Mistflow" };
2399
+ export const metadata: Metadata = { title: "${o}", description: "Built with Mistflow" };
2387
2400
 
2388
2401
  export default function RootLayout({ children }: { children: React.ReactNode }) {
2389
2402
  return (
2390
- <html ${r}>
2403
+ <html ${s}>
2391
2404
  <body className={\`\${heading.variable} \${body.variable}\`}>{children}<Toaster richColors /></body>
2392
2405
  </html>
2393
2406
  );
2394
2407
  }
2395
- `}function ln(t,...e){let s=JSON.stringify(t).toLowerCase();return e.some(n=>s.includes(n.toLowerCase()))}function Ps(t){let e=t.toLowerCase().replace(/[^a-z]/g,"");for(let[s,n]of Object.entries(ql))if(e.includes(s))return n;return"Circle"}function Nt(t){return t.split("-").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" ")}function Bl(t){if(t.authModel==="none")return null;let e=["/login","/register","/forgot-password","/reset-password","/api/auth","/api/health","/api/webhooks","/api/admin/seed"];if(t.publicPages&&Array.isArray(t.publicPages))for(let i of t.publicPages){if(typeof i!="string"||i.length<1)continue;let r=i.replace(/[\u201C\u201D\u201E\u201F\u2018\u2019\u2033\u2036]/g,"").trim();if(!r)continue;let a=r.startsWith("/")?r:"/"+r;e.includes(a)||e.push(a)}let s=e.filter(i=>i==="/"),n=e.filter(i=>i!=="/"),o=[];o.push('import { NextRequest, NextResponse } from "next/server";'),o.push(""),o.push("const PUBLIC_PREFIXES = [");for(let i of n)o.push(' "'+i+'",');return o.push("];"),o.push(""),s.length>0&&(o.push('const PUBLIC_EXACT = ["'+s.join('", "')+'"];'),o.push("")),o.push("export function middleware(req: NextRequest) {"),o.push(" const { pathname, search } = req.nextUrl;"),o.push(""),s.length>0&&o.push(" if (PUBLIC_EXACT.includes(pathname)) return NextResponse.next();"),o.push(" if (PUBLIC_PREFIXES.some((p) => pathname.startsWith(p))) return NextResponse.next();"),o.push(""),o.push(' const token = req.cookies.get("better-auth.session_token")?.value || req.cookies.get("__Secure-better-auth.session_token")?.value;'),o.push(" if (!token) {"),o.push(' const loginUrl = new URL("/login", req.url);'),o.push(" const params = new URLSearchParams(search);"),o.push(' for (const key of ["verified", "error"]) {'),o.push(" const v = params.get(key);"),o.push(" if (v) loginUrl.searchParams.set(key, v);"),o.push(" }"),o.push(" return NextResponse.redirect(loginUrl);"),o.push(" }"),o.push(""),o.push(" return NextResponse.next();"),o.push("}"),o.push(""),o.push("export const config = {"),o.push(' matcher: ["/((?!_next|static|favicon\\\\.ico).*)"],'),o.push("};"),o.push(""),o.join(`
2396
- `)}function zl(t){if(t.navStyle==="none")return null;let e=["/api","/login","/register","/sign-in","/sign-up","/admin","/pricing","/about","/contact","/terms","/privacy","/onboarding","/join","/forgot-password","/reset-password"],n=(t.pages??[]).filter(l=>{let d=l.path??l.route??"";return d==="/"||d===""||d.includes("[")||d.replace(/^\//,"").split("/").length>1?!1:!e.some(m=>d.startsWith(m))}).map(l=>{let d=l.path??l.route??"",h=d.startsWith("/")?d:"/"+d,m=l.name??Nt(d.replace(/^\//,"")),p=Ps(m);return{label:m,href:h,icon:p}});n.some(l=>l.href==="/dashboard")||n.unshift({label:"Dashboard",href:"/dashboard",icon:"Home"});let o=t.authModel==="none",i=[...new Set(n.map(l=>l.icon))];o||i.push("LogOut");let r=Nt(t.name);if(t.navStyle==="topbar"){let l=[];l.push('"use client";'),l.push(""),l.push('import Link from "next/link";'),l.push('import { usePathname } from "next/navigation";'),o||l.push('import { authClient } from "@/lib/auth-client";'),l.push('import { Button } from "@/components/ui/button";'),l.push('import { cn } from "@/lib/utils";'),l.push("import { "+i.join(", ")+' } from "lucide-react";'),l.push(""),l.push("interface TopNavProps {"),l.push(" user: { name: string | null; email: string; role?: string | undefined };"),l.push("}"),l.push(""),l.push("const NAV_ITEMS = [");for(let d of n)l.push(' { label: "'+d.label+'", href: "'+d.href+'", icon: '+d.icon+" },");return l.push("];"),l.push(""),l.push("export default function TopNav({ user }: TopNavProps) {"),l.push(" const pathname = usePathname();"),l.push(""),l.push(" return ("),l.push(' <nav className="border-b bg-card">'),l.push(' <div className="mx-auto flex h-14 max-w-7xl items-center justify-between px-4">'),l.push(' <div className="flex items-center gap-6">'),l.push(' <span className="text-lg font-semibold">'+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>"),o?l.push(' <span className="text-sm text-muted-foreground">{user.name}</span>'):(l.push(' <div className="flex items-center gap-2">'),l.push(' <span className="text-sm text-muted-foreground">{user.email}</span>'),l.push(" <Button"),l.push(' variant="ghost"'),l.push(' size="sm"'),l.push(' onClick={() => authClient.signOut({ fetchOptions: { onSuccess: () => { window.location.href = "/login"; } } })}'),l.push(" >"),l.push(' <LogOut className="h-4 w-4" />'),l.push(" </Button>"),l.push(" </div>")),l.push(" </div>"),l.push(" </nav>"),l.push(" );"),l.push("}"),l.push(""),{path:"components/topnav.tsx",content:l.join(`
2397
- `)}}let a=[];a.push('"use client";'),a.push(""),a.push('import { useState } from "react";'),a.push('import Link from "next/link";'),a.push('import { usePathname } from "next/navigation";'),o||a.push('import { authClient } from "@/lib/auth-client";'),a.push('import { Button } from "@/components/ui/button";'),a.push('import { Sheet, SheetContent, SheetTrigger, SheetTitle } from "@/components/ui/sheet";'),a.push('import { cn } from "@/lib/utils";'),a.push("import { Menu, "+i.join(", ")+' } from "lucide-react";'),a.push(""),a.push("interface SidebarProps {"),a.push(" user: { name: string | null; email: string; role?: string | undefined };"),a.push("}"),a.push(""),a.push("const NAV_ITEMS = [");for(let l of n)a.push(' { label: "'+l.label+'", href: "'+l.href+'", icon: '+l.icon+" },");return a.push("];"),a.push(""),a.push('function NavContent({ pathname, user, onNavigate }: { pathname: string; user: SidebarProps["user"]; onNavigate?: () => void }) {'),a.push(" return ("),a.push(" <>"),a.push(' <div className="flex h-14 items-center border-b px-4">'),a.push(' <span className="text-lg font-semibold">'+r+"</span>"),a.push(" </div>"),a.push(' <nav className="flex-1 space-y-1 p-2">'),a.push(" {NAV_ITEMS.map((item) => ("),a.push(" <Link"),a.push(" key={item.href}"),a.push(" href={item.href}"),a.push(" onClick={onNavigate}"),a.push(' aria-current={pathname === item.href ? "page" : undefined}'),a.push(" className={cn("),a.push(' "flex items-center gap-3 rounded-md px-3 py-2.5 text-sm font-medium transition-colors",'),a.push(' pathname === item.href ? "bg-primary/10 text-primary" : "text-muted-foreground hover:bg-muted hover:text-foreground"'),a.push(" )}"),a.push(" >"),a.push(' <item.icon className="h-4 w-4" />'),a.push(" {item.label}"),a.push(" </Link>"),a.push(" ))}"),a.push(" </nav>"),a.push(' <div className="border-t p-4">'),a.push(' <div className="flex items-center gap-3">'),a.push(' <div className="flex-1 truncate">'),a.push(' <p className="truncate text-sm font-medium">{user.name ?? "User"}</p>'),a.push(' <p className="truncate text-xs text-muted-foreground">{user.email}</p>'),a.push(" </div>"),o||(a.push(" <Button"),a.push(' variant="ghost"'),a.push(' size="icon"'),a.push(' onClick={() => authClient.signOut({ fetchOptions: { onSuccess: () => { window.location.href = "/login"; } } })}'),a.push(" >"),a.push(' <LogOut className="h-4 w-4" />'),a.push(" </Button>")),a.push(" </div>"),a.push(" </div>"),a.push(" </>"),a.push(" );"),a.push("}"),a.push(""),a.push("export default function Sidebar({ user }: SidebarProps) {"),a.push(" const pathname = usePathname();"),a.push(" const [open, setOpen] = useState(false);"),a.push(""),a.push(" return ("),a.push(" <>"),a.push(' <aside className="hidden md:flex h-screen w-64 flex-col border-r bg-card">'),a.push(" <NavContent pathname={pathname} user={user} />"),a.push(" </aside>"),a.push(' <div className="sticky top-0 z-[var(--z-sticky)] flex h-14 items-center gap-3 border-b bg-card px-4 md:hidden">'),a.push(" <Sheet open={open} onOpenChange={setOpen}>"),a.push(" <SheetTrigger asChild>"),a.push(' <Button variant="ghost" size="icon" className="-ml-2">'),a.push(' <Menu className="h-5 w-5" />'),a.push(" </Button>"),a.push(" </SheetTrigger>"),a.push(' <SheetContent side="left" className="w-64 p-0">'),a.push(' <SheetTitle className="sr-only">Navigation</SheetTitle>'),a.push(' <div className="flex h-full flex-col">'),a.push(" <NavContent pathname={pathname} user={user} onNavigate={() => setOpen(false)} />"),a.push(" </div>"),a.push(" </SheetContent>"),a.push(" </Sheet>"),a.push(' <span className="text-lg font-semibold">'+r+"</span>"),a.push(" </div>"),a.push(" </>"),a.push(" );"),a.push("}"),a.push(""),{path:"components/sidebar.tsx",content:a.join(`
2398
- `)}}function Hl(t){if(!t.roles||t.roles.length===0)return null;let e=t.roles,s=t.defaultRole??e[0],n=[];n.push("export type Role = "+e.map(o=>'"'+o+'"').join(" | ")+";"),n.push(""),n.push("export const ROLES = ["+e.map(o=>'"'+o+'"').join(", ")+"] as const;"),n.push(""),n.push('export const DEFAULT_ROLE: Role = "'+s+'";'),n.push(""),n.push("export const ROLE_LABELS: Record<Role, string> = {");for(let o of e){let i=o.charAt(0).toUpperCase()+o.slice(1);n.push(' "'+o+'": "'+i+'",')}return n.push("};"),n.push(""),n.push("export function getUserRole(user: Record<string, unknown>): Role {"),n.push(" const role = (user.role as string) ?? DEFAULT_ROLE;"),n.push(" if (ROLES.includes(role as Role)) return role as Role;"),n.push(" return DEFAULT_ROLE;"),n.push("}"),n.push(""),n.push("export function hasRole(userRole: string | undefined, required: Role | Role[]): boolean {"),n.push(" if (!userRole) return false;"),n.push(" const allowed = Array.isArray(required) ? required : [required];"),n.push(" return allowed.includes(userRole as Role);"),n.push("}"),n.push(""),n.join(`
2399
- `)}function Wl(t){let e=Nt(t.name);if(t.authModel==="none"){let i=[];return i.push("export default function HomePage() {"),i.push(" return ("),i.push(' <main className="flex min-h-screen flex-col items-center justify-center p-8">'),i.push(' <h1 className="text-4xl font-bold">'+e+"</h1>"),t.summary&&i.push(' <p className="mt-4 text-lg text-muted-foreground">'+t.summary+"</p>"),i.push(" </main>"),i.push(" );"),i.push("}"),i.push(""),i.join(`
2400
- `)}let s=t.publicPages?.includes("/"),n=t.design?.landingTone;if(s&&n){let i=[];return i.push('import Link from "next/link";'),i.push(""),i.push("export default function HomePage() {"),i.push(" return ("),i.push(' <main className="flex min-h-screen flex-col">'),i.push(' <section className="flex flex-1 flex-col items-center justify-center gap-6 px-4 py-24 text-center">'),i.push(' <h1 className="text-5xl font-bold tracking-tight">'+e+"</h1>"),t.summary&&i.push(' <p className="max-w-2xl text-xl text-muted-foreground">'+t.summary+"</p>"),i.push(' <div className="flex gap-4">'),i.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">'),i.push(" Get Started"),i.push(" </Link>"),i.push(' <Link href="/login" className="inline-flex h-11 items-center rounded-md border px-8 text-sm font-medium hover:bg-muted">'),i.push(" Sign In"),i.push(" </Link>"),i.push(" </div>"),i.push(" </section>"),i.push(" </main>"),i.push(" );"),i.push("}"),i.push(""),i.join(`
2401
- `)}if(s){let i=[];return i.push('import Link from "next/link";'),i.push(""),i.push("export default function HomePage() {"),i.push(" return ("),i.push(' <main className="flex min-h-screen flex-col items-center justify-center gap-6 p-8 text-center">'),i.push(' <h1 className="text-4xl font-bold">'+e+"</h1>"),t.summary&&i.push(' <p className="text-lg text-muted-foreground">'+t.summary+"</p>"),i.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">'),i.push(" Sign In"),i.push(" </Link>"),i.push(" </main>"),i.push(" );"),i.push("}"),i.push(""),i.join(`
2402
- `)}let o=[];return o.push('import { headers } from "next/headers";'),o.push('import { redirect } from "next/navigation";'),o.push('import { auth } from "@/lib/auth";'),o.push(""),o.push("export default async function HomePage() {"),o.push(" const session = await auth.api.getSession({ headers: await headers() });"),o.push(' if (session) redirect("/dashboard");'),o.push(' redirect("/login");'),o.push("}"),o.push(""),o.join(`
2403
- `)}function Gl(t,e){let s=t.authModel==="none",n=[];s||(n.push('import { Suspense } from "react";'),n.push('import { headers } from "next/headers";'),n.push('import { redirect } from "next/navigation";'),n.push('import { auth } from "@/lib/auth";'),n.push('import { VerifiedBanner } from "@/components/auth/verified-banner";')),t.navStyle==="topbar"?n.push('import TopNav from "@/components/topnav";'):t.navStyle!=="none"&&n.push('import Sidebar from "@/components/sidebar";'),!s&&t.roles&&t.roles.length>0&&n.push('import { getUserRole } from "@/lib/roles";'),n.push(""),n.push("export default async function DashboardLayout({ children }: { children: React.ReactNode }) {"),s?n.push(' const user = { name: "Guest", email: "" };'):(n.push(" const session = await auth.api.getSession({ headers: await headers() });"),n.push(' if (!session) redirect("/login");'),n.push(""),t.roles&&t.roles.length>0?(n.push(" const role = getUserRole(session.user as Record<string, unknown>);"),n.push(" const user = { name: session.user.name, email: session.user.email, role };")):(n.push(" const user = {"),n.push(" name: session.user.name,"),n.push(" email: session.user.email,"),n.push(" role: (session.user as Record<string, unknown>).role as string | undefined,"),n.push(" };"))),n.push("");let o=s?"":"<Suspense fallback={null}><VerifiedBanner /></Suspense>";return t.navStyle==="topbar"?(n.push(" return ("),n.push(' <div className="min-h-screen">'),n.push(" <TopNav user={user} />"),n.push(' <main className="mx-auto max-w-7xl p-6">'+o+"{children}</main>"),n.push(" </div>"),n.push(" );")):t.navStyle==="none"?(n.push(" return ("),n.push(' <div className="min-h-screen">'),n.push(' <main className="mx-auto max-w-5xl p-6">'+o+"{children}</main>"),n.push(" </div>"),n.push(" );")):(n.push(" return ("),n.push(' <div className="flex flex-col md:flex-row min-h-screen">'),n.push(" <Sidebar user={user} />"),n.push(' <main className="flex-1 overflow-x-hidden p-4 md:p-6">'+o+"{children}</main>"),n.push(" </div>"),n.push(" );")),n.push("}"),n.push(""),n.join(`
2404
- `)}function Vl(t){let e=Nt(t.name),s=t.dataModel??[],n=[];if(s.length>0){let o=s.map(r=>Ps(r.entity??r.name??"item")),i=[...new Set(o)];n.push('import { Card, CardContent } from "@/components/ui/card";'),n.push("import { "+i.join(", ")+' } from "lucide-react";'),n.push("")}if(n.push("export default function DashboardPage() {"),n.push(" return ("),n.push(' <div className="space-y-6">'),n.push(" <div>"),n.push(' <h1 className="text-3xl font-bold">'+e+"</h1>"),t.summary&&n.push(' <p className="mt-1 text-muted-foreground">'+t.summary+"</p>"),n.push(" </div>"),s.length>0){n.push(' <div className="rounded-lg border p-8 text-center">'),n.push(' <h2 className="text-lg font-semibold">Get started</h2>'),n.push(` <p className="mt-1 text-sm text-muted-foreground">Here's what you can do</p>`),n.push(' <div className="mt-6 grid gap-3 sm:grid-cols-2 text-left">');for(let o of s){let i=o.entity??o.name??"Item",r=Ps(i),a=Nt(i.replace(/_/g,"-"));n.push(' <div className="flex items-center gap-3 rounded-md border p-3">'),n.push(" <"+r+' className="h-5 w-5 text-muted-foreground" />'),n.push(' <span className="text-sm font-medium">Add your first '+a+"</span>"),n.push(" </div>")}n.push(" </div>"),n.push(" </div>")}return n.push(" </div>"),n.push(" );"),n.push("}"),n.push(""),n.join(`
2405
- `)}function Jl(t,e=!1){if(!t.multiTenant)return null;let s=[];return e?(s.push('import { pgTable, text, timestamp, index } from "drizzle-orm/pg-core";'),s.push('import { user } from "./auth";'),s.push(""),s.push('export const organization = pgTable("organization", {'),s.push(' id: text("id").primaryKey(),'),s.push(' name: text("name").notNull(),'),s.push(' slug: text("slug").unique().notNull(),'),s.push(' createdAt: timestamp("created_at").defaultNow().notNull(),'),s.push(' updatedAt: timestamp("updated_at").defaultNow().notNull(),'),s.push("});"),s.push(""),s.push("export const orgMember = pgTable(")):(s.push('import { sqliteTable, text, index } from "drizzle-orm/sqlite-core";'),s.push('import { sql } from "drizzle-orm";'),s.push('import { user } from "./auth";'),s.push(""),s.push('export const organization = sqliteTable("organization", {'),s.push(' id: text("id").primaryKey(),'),s.push(' name: text("name").notNull(),'),s.push(' slug: text("slug").unique().notNull(),'),s.push(' createdAt: text("created_at").default(sql`CURRENT_TIMESTAMP`).notNull(),'),s.push(' updatedAt: text("updated_at").default(sql`CURRENT_TIMESTAMP`).notNull(),'),s.push("});"),s.push(""),s.push("export const orgMember = sqliteTable(")),s.push(' "org_member",'),s.push(" {"),s.push(' id: text("id").primaryKey(),'),s.push(' orgId: text("org_id").notNull().references(() => organization.id),'),s.push(' userId: text("user_id").notNull().references(() => user.id),'),s.push(' role: text("role").notNull(),'),e?s.push(' joinedAt: timestamp("joined_at").defaultNow().notNull(),'):s.push(' joinedAt: text("joined_at").default(sql`CURRENT_TIMESTAMP`).notNull(),'),s.push(" },"),s.push(" (table) => ({"),s.push(' orgIdx: index("org_member_org_idx").on(table.orgId),'),s.push(' userIdx: index("org_member_user_idx").on(table.userId),'),s.push(" }),"),s.push(");"),s.push(""),s.join(`
2406
- `)}function Yl(t){if(!t.multiTenant)return null;let e=[];return e.push('import { db } from "./db";'),e.push('import { organization, orgMember } from "@/db/schema/organization";'),e.push('import { eq } from "drizzle-orm";'),e.push(""),e.push("export async function getCurrentOrg(userId: string) {"),e.push(" const membership = await db"),e.push(" .select()"),e.push(" .from(orgMember)"),e.push(" .where(eq(orgMember.userId, userId))"),e.push(" .limit(1);"),e.push(" if (membership.length === 0) return null;"),e.push(" const org = await db"),e.push(" .select()"),e.push(" .from(organization)"),e.push(" .where(eq(organization.id, membership[0].orgId))"),e.push(" .limit(1);"),e.push(" return org[0] ?? null;"),e.push("}"),e.push(""),e.push("export async function getOrgMembers(orgId: string) {"),e.push(" return db"),e.push(" .select()"),e.push(" .from(orgMember)"),e.push(" .where(eq(orgMember.orgId, orgId));"),e.push("}"),e.push(""),e.push("export async function inviteToOrg(orgId: string, email: string, role: string) {"),e.push(" const id = crypto.randomUUID();"),e.push(" await db.insert(orgMember).values({"),e.push(" id,"),e.push(" orgId,"),e.push(" userId: email,"),e.push(" role,"),e.push(" });"),e.push(" return { id, orgId, email, role };"),e.push("}"),e.push(""),e.join(`
2407
- `)}function Kl(t){if(!t.multiTenant)return null;let e=[];return e.push('"use client";'),e.push(""),e.push("import {"),e.push(" DropdownMenu,"),e.push(" DropdownMenuContent,"),e.push(" DropdownMenuItem,"),e.push(" DropdownMenuTrigger,"),e.push('} from "@/components/ui/dropdown-menu";'),e.push('import { Button } from "@/components/ui/button";'),e.push('import { ChevronsUpDown } from "lucide-react";'),e.push(""),e.push("interface OrgSwitcherProps {"),e.push(" orgs: Array<{ id: string; name: string }>;"),e.push(" currentOrgId: string;"),e.push("}"),e.push(""),e.push("export default function OrgSwitcher({ orgs, currentOrgId }: OrgSwitcherProps) {"),e.push(" const currentOrg = orgs.find((o) => o.id === currentOrgId);"),e.push(""),e.push(" return ("),e.push(" <DropdownMenu>"),e.push(" <DropdownMenuTrigger asChild>"),e.push(' <Button variant="outline" className="w-full justify-between">'),e.push(' <span className="truncate">{currentOrg?.name ?? "Select org"}</span>'),e.push(' <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />'),e.push(" </Button>"),e.push(" </DropdownMenuTrigger>"),e.push(' <DropdownMenuContent className="w-56">'),e.push(" {orgs.map((org) => ("),e.push(" <DropdownMenuItem key={org.id}>"),e.push(" {org.name}"),e.push(" </DropdownMenuItem>"),e.push(" ))}"),e.push(" </DropdownMenuContent>"),e.push(" </DropdownMenu>"),e.push(" );"),e.push("}"),e.push(""),e.join(`
2408
- `)}function Ql(t,e,s){let n=[],o=t.split("-").map(l=>l.charAt(0).toUpperCase()+l.slice(1)).join(" ");n.push(`# ${o}`),n.push(""),e?.summary&&(n.push(e.summary),n.push(""));let i=e?.features??[];if(i.length>0){n.push("## Features"),n.push("");for(let l of i){let d=l.description?` \u2014 ${l.description}`:"";n.push(`- **${l.name}**${d}`)}n.push("")}n.push("## Tech Stack"),n.push(""),n.push("| Layer | Technology |"),n.push("|-------|------------|"),n.push("| Framework | Next.js 15 (App Router) |"),n.push("| Database | Mistflow Cloud (Postgres) + Drizzle ORM |"),n.push("| Auth | Better Auth (email/password, social login) |"),n.push("| Styling | Tailwind CSS + shadcn/ui |"),n.push("| Deployment | Mistflow Cloud |"),s.hasStripe&&n.push("| Payments | Stripe |"),s.hasResend&&n.push("| Email | Resend + React Email |"),s.hasStorage&&n.push("| File Storage | Mistflow Cloud (managed blob storage) |"),s.hasAdmin&&n.push("| Admin | Better Auth admin plugin |"),s.hasAI&&n.push("| AI | Vercel AI SDK + OpenAI |"),n.push("");let r=e?.pages??[];if(r.length>0){n.push("## Pages"),n.push(""),n.push("| Route | Description |"),n.push("|-------|-------------|");for(let l of r){let d=l.path??l.route??l.name??"",h=l.description??"";n.push(`| \`${d.startsWith("/")?d:"/"+d}\` | ${h} |`)}n.push("")}let a=e?.dataModel??[];if(a.length>0){n.push("## Data Model"),n.push("");for(let l of a){let d=l.entity??l.name??"Unknown";if(n.push(`### ${d}`),n.push(""),l.fields.length>0){if(typeof l.fields[0]=="string")n.push(`Fields: ${l.fields.join(", ")}`);else{n.push("| Field | Type |"),n.push("|-------|------|");for(let h of l.fields)n.push(`| ${h.name} | ${h.type} |`)}n.push("")}}}return n.push("## Getting Started"),n.push(""),n.push("### Prerequisites"),n.push(""),n.push("- Node.js 20+"),n.push("- npm"),n.push(""),n.push("### Install"),n.push(""),n.push("The host AI calls mist_install (fire-and-poll) which handles this for you. Run manually with plain npm if needed:"),n.push(""),n.push("```bash"),n.push("npm install"),n.push("```"),n.push(""),n.push("### Set up environment"),n.push(""),n.push("Copy `.env.example` to `.env.local` and fill in the values:"),n.push(""),n.push("```bash"),n.push("cp .env.example .env.local"),n.push("```"),n.push(""),n.push("| Variable | Description | Required |"),n.push("|----------|-------------|----------|"),s.isNeon?n.push("| `DATABASE_URL` | Postgres connection URL | Yes |"):(n.push("| `TURSO_URL` | Database connection URL | Yes |"),n.push("| `TURSO_AUTH_TOKEN` | Database auth token | Yes |")),n.push("| `AUTH_SECRET` | Auth encryption secret (auto-generated) | Yes |"),s.hasStripe&&(n.push("| `STRIPE_SECRET_KEY` | Stripe secret key | Yes |"),n.push("| `STRIPE_WEBHOOK_SECRET` | Stripe webhook signing secret | Yes |"),n.push("| `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` | Stripe publishable key | Yes |")),s.hasResend&&(n.push("| `RESEND_API_KEY` | Resend API key | Yes |"),n.push("| `EMAIL_FROM` | Sender email address | Yes (production) |")),s.hasStorage&&(n.push("| `MISTFLOW_API_KEY` | Mistflow API key for file storage | Yes |"),n.push("| `MISTFLOW_PROJECT_ID` | Mistflow project ID | Yes |")),s.hasAI&&n.push("| `OPENAI_API_KEY` | OpenAI API key | Yes |"),n.push(""),n.push("### Local database"),n.push(""),s.isNeon?(n.push("For local development, start a local Postgres server:"),n.push(""),n.push("```bash"),n.push("# Using Docker:"),n.push("docker run -d --name postgres -e POSTGRES_PASSWORD=postgres -p 5432:5432 postgres:17"),n.push("# Or install via Homebrew: brew install postgresql@17 && brew services start postgresql@17"),n.push("```")):(n.push("For local development, start a local Turso server:"),n.push(""),n.push("```bash"),n.push("npx turso dev"),n.push("```")),n.push(""),n.push("Then set up the database:"),n.push(""),n.push("```bash"),n.push("npm run db:push"),n.push("```"),n.push(""),n.push("### Run"),n.push(""),n.push("```bash"),n.push("npm run dev"),n.push("```"),n.push(""),n.push("Open [http://localhost:3000](http://localhost:3000)."),n.push(""),n.push("## Project Structure"),n.push(""),n.push("```"),n.push("app/"),n.push(" (auth)/ Login and registration pages"),n.push(" (dashboard)/ Authenticated app pages"),s.hasAdmin&&n.push(" (admin)/ Admin panel pages"),n.push(" api/ API routes (auth, health, webhooks)"),n.push(" layout.tsx Root layout with fonts and providers"),n.push(" globals.css Design tokens and Tailwind config"),n.push("components/ Reusable UI components"),n.push("db/"),n.push(" schema/ Database table definitions"),n.push(" index.ts Schema exports"),n.push("lib/"),n.push(" auth.ts Better Auth server config"),n.push(" auth-client.ts Better Auth client config"),n.push(` db.ts ${s.isNeon?"Postgres":"SQLite"} database connection`),s.hasStripe&&n.push(" stripe.ts Stripe client"),s.hasResend&&(n.push(" resend.ts Resend client"),n.push(" email.ts Email send helpers")),s.hasStorage&&n.push(" storage.ts File upload/download helpers"),s.hasAI&&n.push(" ai.ts AI client (Vercel AI SDK + OpenAI)"),s.hasResend&&n.push("emails/ React Email templates"),n.push("```"),n.push(""),n.push("## Deploy"),n.push(""),n.push("Deploy to production with Mistflow:"),n.push(""),n.push("```"),n.push("# In your AI editor (Claude Code, Cursor, etc.):"),n.push("mist_deploy action='deploy'"),n.push("```"),n.push(""),n.push("Your app will be live at `https://<app-name>.mistflow.app`."),n.push(""),e?.design&&(n.push("## Design"),n.push(""),e.design.tone&&n.push(`- **Tone**: ${e.design.tone}`),e.design.fonts&&(n.push(`- **Heading font**: ${e.design.fonts.heading}`),n.push(`- **Body font**: ${e.design.fonts.body}`)),e.design.accentColor&&n.push(`- **Accent color**: ${e.design.accentColor}`),e.design.borderRadius&&n.push(`- **Border radius**: ${e.design.borderRadius}`),n.push("")),n.push("---"),n.push(""),n.push("Built with [Mistflow](https://mistflow.ai)"),n.push(""),n.join(`
2409
- `)}async function Xl(t,e){let{name:s,plan:n,path:o,planId:i}=t;if(!o)return c("mist_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(!Il(o))return c(`mist_init 'path' must be an absolute path \u2014 received '${o}'. Pass the full absolute path to the target directory.`,!0);let r=pr(o),a=n;if(!a&&i){let w=Rl(i);if(!w)return c(`No plan found for planId '${i}'. Call mist_plan first, or pass the plan object inline.`,!0);a=w.plan}let l=a?.design,d=a?.appStyle,h=a?ln(a,"stripe","payment","billing","subscription","checkout","pricing"):!1,m=!0,p=a?ln(a,"upload","file storage","image upload","profile picture","attachment","gallery","media","blob"):!1,y=a?ln(a,"admin panel","admin dashboard","admin management"):!1,v=a?ln(a,"ai integration","openai","llm","ai chat","chatbot","gpt"):!1,b=a,f=!0;if(!Nl(r))return c(`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);ks(r,{recursive:!0});try{let w=ke(Et(r),".mistflow","mockups");if(Ze(w)){let I=dr(w).filter(B=>B.endsWith(".html"));if(I.length>0){let B=ke(r,".mistflow","mockups");ks(B,{recursive:!0});for(let Q of I)Pl(ke(w,Q),ke(B,Q));console.error(`Copied ${I.length} mockup file(s) into project`)}}}catch(w){console.error("Could not copy mockup files:",w instanceof Error?w.message:w)}let k=null;try{k=await Vn("nextjs")}catch(w){console.error("Could not fetch scaffold from API, using minimal scaffold:",w instanceof Error?w.message:w)}if(k){let w=s.toLowerCase().replace(/[^a-z0-9-]/g,"-");for(let u of k.files){if(u.path==="package.json"||u.path==="middleware.ts"||u.path==="components/sidebar.tsx"||u.path==="components/topnav.tsx"||u.path==="app/(dashboard)/layout.tsx"||u.path==="app/(dashboard)/page.tsx"||u.path==="app/(dashboard)/dashboard/page.tsx"||!h&&(u.path.includes("stripe")||u.path.includes("webhook/stripe"))||!m&&(u.path.includes("resend")||u.path.includes("emails/"))||!y&&(u.path.includes("(admin)")||u.path.includes("admin-sidebar"))||f&&(u.path==="lib/db.ts"||u.path==="lib/auth.ts"||u.path==="drizzle.config.ts"||u.path==="db/schema/auth.ts"))continue;let g=u.content.replace(/\{\{APP_NAME\}\}/g,s).replace(/\{\{WORKER_NAME\}\}/g,w);if(f&&u.path==="next.config.ts"&&(g=g.replace(/serverExternalPackages:\s*\[[^\]]*\],?/g,'serverExternalPackages: ["@electric-sql/pglite"],')),u.path==="next.config.ts"){let P=Al(r);P&&(console.error(`[init] Project is inside monorepo at ${P} \u2014 adding outputFileTracingRoot`),g.includes("outputFileTracingRoot")||(g=g.replace('import type { NextConfig } from "next";',`import type { NextConfig } from "next";
2408
+ `}function lo(t,...e){let r=JSON.stringify(t).toLowerCase();return e.some(o=>r.includes(o.toLowerCase()))}function Pr(t){let e=t.toLowerCase().replace(/[^a-z]/g,"");for(let[r,o]of Object.entries(pc))if(e.includes(r))return o;return"Circle"}function Dt(t){return t.split("-").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" ")}function uc(t){if(t.authModel==="none")return null;let e=["/login","/register","/forgot-password","/reset-password","/api/auth","/api/health","/api/webhooks","/api/admin/seed"];if(t.publicPages&&Array.isArray(t.publicPages))for(let i of t.publicPages){if(typeof i!="string"||i.length<1)continue;let s=i.replace(/[\u201C\u201D\u201E\u201F\u2018\u2019\u2033\u2036]/g,"").trim();if(!s)continue;let a=s.startsWith("/")?s:"/"+s;e.includes(a)||e.push(a)}let r=e.filter(i=>i==="/"),o=e.filter(i=>i!=="/"),n=[];n.push('import { NextRequest, NextResponse } from "next/server";'),n.push(""),n.push("const PUBLIC_PREFIXES = [");for(let i of o)n.push(' "'+i+'",');return n.push("];"),n.push(""),r.length>0&&(n.push('const PUBLIC_EXACT = ["'+r.join('", "')+'"];'),n.push("")),n.push("export function middleware(req: NextRequest) {"),n.push(" const { pathname, search } = req.nextUrl;"),n.push(""),r.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(`
2409
+ `)}function mc(t){if(t.navStyle==="none")return null;let e=["/api","/login","/register","/sign-in","/sign-up","/admin","/pricing","/about","/contact","/terms","/privacy","/onboarding","/join","/forgot-password","/reset-password"],o=(t.pages??[]).filter(l=>{let d=l.path??l.route??"";return d==="/"||d===""||d.includes("[")||d.replace(/^\//,"").split("/").length>1?!1:!e.some(u=>d.startsWith(u))}).map(l=>{let d=l.path??l.route??"",h=d.startsWith("/")?d:"/"+d,u=l.name??Dt(d.replace(/^\//,"")),p=Pr(u);return{label:u,href:h,icon:p}});o.some(l=>l.href==="/dashboard")||o.unshift({label:"Dashboard",href:"/dashboard",icon:"Home"});let n=t.authModel==="none",i=[...new Set(o.map(l=>l.icon))];n||i.push("LogOut");let s=Dt(t.name);if(t.navStyle==="topbar"){let l=[];l.push('"use client";'),l.push(""),l.push('import Link from "next/link";'),l.push('import { usePathname } from "next/navigation";'),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 { "+i.join(", ")+' } from "lucide-react";'),l.push(""),l.push("interface TopNavProps {"),l.push(" user: { name: string | null; email: string; role?: string | undefined };"),l.push("}"),l.push(""),l.push("const NAV_ITEMS = [");for(let d of o)l.push(' { label: "'+d.label+'", href: "'+d.href+'", icon: '+d.icon+" },");return l.push("];"),l.push(""),l.push("export default function TopNav({ user }: TopNavProps) {"),l.push(" const pathname = usePathname();"),l.push(""),l.push(" return ("),l.push(' <nav className="border-b bg-card">'),l.push(' <div className="mx-auto flex h-14 max-w-7xl items-center justify-between px-4">'),l.push(' <div className="flex items-center gap-6">'),l.push(' <span className="text-lg font-semibold">'+s+"</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(`
2410
+ `)}}let a=[];a.push('"use client";'),a.push(""),a.push('import { useState } from "react";'),a.push('import Link from "next/link";'),a.push('import { usePathname } from "next/navigation";'),n||a.push('import { authClient } from "@/lib/auth-client";'),a.push('import { Button } from "@/components/ui/button";'),a.push('import { Sheet, SheetContent, SheetTrigger, SheetTitle } from "@/components/ui/sheet";'),a.push('import { cn } from "@/lib/utils";'),a.push("import { Menu, "+i.join(", ")+' } from "lucide-react";'),a.push(""),a.push("interface SidebarProps {"),a.push(" user: { name: string | null; email: string; role?: string | undefined };"),a.push("}"),a.push(""),a.push("const NAV_ITEMS = [");for(let l of o)a.push(' { label: "'+l.label+'", href: "'+l.href+'", icon: '+l.icon+" },");return a.push("];"),a.push(""),a.push('function NavContent({ pathname, user, onNavigate }: { pathname: string; user: SidebarProps["user"]; onNavigate?: () => void }) {'),a.push(" return ("),a.push(" <>"),a.push(' <div className="flex h-14 items-center border-b px-4">'),a.push(' <span className="text-lg font-semibold">'+s+"</span>"),a.push(" </div>"),a.push(' <nav className="flex-1 space-y-1 p-2">'),a.push(" {NAV_ITEMS.map((item) => ("),a.push(" <Link"),a.push(" key={item.href}"),a.push(" href={item.href}"),a.push(" onClick={onNavigate}"),a.push(' aria-current={pathname === item.href ? "page" : undefined}'),a.push(" className={cn("),a.push(' "flex items-center gap-3 rounded-md px-3 py-2.5 text-sm font-medium transition-colors",'),a.push(' pathname === item.href ? "bg-primary/10 text-primary" : "text-muted-foreground hover:bg-muted hover:text-foreground"'),a.push(" )}"),a.push(" >"),a.push(' <item.icon className="h-4 w-4" />'),a.push(" {item.label}"),a.push(" </Link>"),a.push(" ))}"),a.push(" </nav>"),a.push(' <div className="border-t p-4">'),a.push(' <div className="flex items-center gap-3">'),a.push(' <div className="flex-1 truncate">'),a.push(' <p className="truncate text-sm font-medium">{user.name ?? "User"}</p>'),a.push(' <p className="truncate text-xs text-muted-foreground">{user.email}</p>'),a.push(" </div>"),n||(a.push(" <Button"),a.push(' variant="ghost"'),a.push(' size="icon"'),a.push(' onClick={() => authClient.signOut({ fetchOptions: { onSuccess: () => { window.location.href = "/login"; } } })}'),a.push(" >"),a.push(' <LogOut className="h-4 w-4" />'),a.push(" </Button>")),a.push(" </div>"),a.push(" </div>"),a.push(" </>"),a.push(" );"),a.push("}"),a.push(""),a.push("export default function Sidebar({ user }: SidebarProps) {"),a.push(" const pathname = usePathname();"),a.push(" const [open, setOpen] = useState(false);"),a.push(""),a.push(" return ("),a.push(" <>"),a.push(' <aside className="hidden md:flex h-screen w-64 flex-col border-r bg-card">'),a.push(" <NavContent pathname={pathname} user={user} />"),a.push(" </aside>"),a.push(' <div className="sticky top-0 z-[var(--z-sticky)] flex h-14 items-center gap-3 border-b bg-card px-4 md:hidden">'),a.push(" <Sheet open={open} onOpenChange={setOpen}>"),a.push(" <SheetTrigger asChild>"),a.push(' <Button variant="ghost" size="icon" className="-ml-2">'),a.push(' <Menu className="h-5 w-5" />'),a.push(" </Button>"),a.push(" </SheetTrigger>"),a.push(' <SheetContent side="left" className="w-64 p-0">'),a.push(' <SheetTitle className="sr-only">Navigation</SheetTitle>'),a.push(' <div className="flex h-full flex-col">'),a.push(" <NavContent pathname={pathname} user={user} onNavigate={() => setOpen(false)} />"),a.push(" </div>"),a.push(" </SheetContent>"),a.push(" </Sheet>"),a.push(' <span className="text-lg font-semibold">'+s+"</span>"),a.push(" </div>"),a.push(" </>"),a.push(" );"),a.push("}"),a.push(""),{path:"components/sidebar.tsx",content:a.join(`
2411
+ `)}}function hc(t){if(!t.roles||t.roles.length===0)return null;let e=t.roles,r=t.defaultRole??e[0],o=[];o.push("export type Role = "+e.map(n=>'"'+n+'"').join(" | ")+";"),o.push(""),o.push("export const ROLES = ["+e.map(n=>'"'+n+'"').join(", ")+"] as const;"),o.push(""),o.push('export const DEFAULT_ROLE: Role = "'+r+'";'),o.push(""),o.push("export const ROLE_LABELS: Record<Role, string> = {");for(let n of e){let i=n.charAt(0).toUpperCase()+n.slice(1);o.push(' "'+n+'": "'+i+'",')}return o.push("};"),o.push(""),o.push("export function getUserRole(user: Record<string, unknown>): Role {"),o.push(" const role = (user.role as string) ?? DEFAULT_ROLE;"),o.push(" if (ROLES.includes(role as Role)) return role as Role;"),o.push(" return DEFAULT_ROLE;"),o.push("}"),o.push(""),o.push("export function hasRole(userRole: string | undefined, required: Role | Role[]): boolean {"),o.push(" if (!userRole) return false;"),o.push(" const allowed = Array.isArray(required) ? required : [required];"),o.push(" return allowed.includes(userRole as Role);"),o.push("}"),o.push(""),o.join(`
2412
+ `)}function gc(t){let e=Dt(t.name);if(t.authModel==="none"){let i=[];return i.push("export default function HomePage() {"),i.push(" return ("),i.push(' <main className="flex min-h-screen flex-col items-center justify-center p-8">'),i.push(' <h1 className="text-4xl font-bold">'+e+"</h1>"),t.summary&&i.push(' <p className="mt-4 text-lg text-muted-foreground">'+t.summary+"</p>"),i.push(" </main>"),i.push(" );"),i.push("}"),i.push(""),i.join(`
2413
+ `)}let r=t.publicPages?.includes("/"),o=t.design?.landingTone;if(r&&o){let i=[];return i.push('import Link from "next/link";'),i.push(""),i.push("export default function HomePage() {"),i.push(" return ("),i.push(' <main className="flex min-h-screen flex-col">'),i.push(' <section className="flex flex-1 flex-col items-center justify-center gap-6 px-4 py-24 text-center">'),i.push(' <h1 className="text-5xl font-bold tracking-tight">'+e+"</h1>"),t.summary&&i.push(' <p className="max-w-2xl text-xl text-muted-foreground">'+t.summary+"</p>"),i.push(' <div className="flex gap-4">'),i.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">'),i.push(" Get Started"),i.push(" </Link>"),i.push(' <Link href="/login" className="inline-flex h-11 items-center rounded-md border px-8 text-sm font-medium hover:bg-muted">'),i.push(" Sign In"),i.push(" </Link>"),i.push(" </div>"),i.push(" </section>"),i.push(" </main>"),i.push(" );"),i.push("}"),i.push(""),i.join(`
2414
+ `)}if(r){let i=[];return i.push('import Link from "next/link";'),i.push(""),i.push("export default function HomePage() {"),i.push(" return ("),i.push(' <main className="flex min-h-screen flex-col items-center justify-center gap-6 p-8 text-center">'),i.push(' <h1 className="text-4xl font-bold">'+e+"</h1>"),t.summary&&i.push(' <p className="text-lg text-muted-foreground">'+t.summary+"</p>"),i.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">'),i.push(" Sign In"),i.push(" </Link>"),i.push(" </main>"),i.push(" );"),i.push("}"),i.push(""),i.join(`
2415
+ `)}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(`
2416
+ `)}function fc(t,e){let r=t.authModel==="none",o=[];r||(o.push('import { Suspense } from "react";'),o.push('import { headers } from "next/headers";'),o.push('import { redirect } from "next/navigation";'),o.push('import { auth } from "@/lib/auth";'),o.push('import { VerifiedBanner } from "@/components/auth/verified-banner";')),t.navStyle==="topbar"?o.push('import TopNav from "@/components/topnav";'):t.navStyle!=="none"&&o.push('import Sidebar from "@/components/sidebar";'),!r&&t.roles&&t.roles.length>0&&o.push('import { getUserRole } from "@/lib/roles";'),o.push(""),o.push("export default async function DashboardLayout({ children }: { children: React.ReactNode }) {"),r?o.push(' const user = { name: "Guest", email: "" };'):(o.push(" const session = await auth.api.getSession({ headers: await headers() });"),o.push(' if (!session) redirect("/login");'),o.push(""),t.roles&&t.roles.length>0?(o.push(" const role = getUserRole(session.user as Record<string, unknown>);"),o.push(" const user = { name: session.user.name, email: session.user.email, role };")):(o.push(" const user = {"),o.push(" name: session.user.name,"),o.push(" email: session.user.email,"),o.push(" role: (session.user as Record<string, unknown>).role as string | undefined,"),o.push(" };"))),o.push("");let n=r?"":"<Suspense fallback={null}><VerifiedBanner /></Suspense>";return t.navStyle==="topbar"?(o.push(" return ("),o.push(' <div className="min-h-screen">'),o.push(" <TopNav user={user} />"),o.push(' <main className="mx-auto max-w-7xl p-6">'+n+"{children}</main>"),o.push(" </div>"),o.push(" );")):t.navStyle==="none"?(o.push(" return ("),o.push(' <div className="min-h-screen">'),o.push(' <main className="mx-auto max-w-5xl p-6">'+n+"{children}</main>"),o.push(" </div>"),o.push(" );")):(o.push(" return ("),o.push(' <div className="flex flex-col md:flex-row min-h-screen">'),o.push(" <Sidebar user={user} />"),o.push(' <main className="flex-1 overflow-x-hidden p-4 md:p-6">'+n+"{children}</main>"),o.push(" </div>"),o.push(" );")),o.push("}"),o.push(""),o.join(`
2417
+ `)}function yc(t){let e=Dt(t.name),r=t.dataModel??[],o=[];if(r.length>0){let n=r.map(s=>Pr(s.entity??s.name??"item")),i=[...new Set(n)];o.push('import { Card, CardContent } from "@/components/ui/card";'),o.push("import { "+i.join(", ")+' } from "lucide-react";'),o.push("")}if(o.push("export default function DashboardPage() {"),o.push(" return ("),o.push(' <div className="space-y-6">'),o.push(" <div>"),o.push(' <h1 className="text-3xl font-bold">'+e+"</h1>"),t.summary&&o.push(' <p className="mt-1 text-muted-foreground">'+t.summary+"</p>"),o.push(" </div>"),r.length>0){o.push(' <div className="rounded-lg border p-8 text-center">'),o.push(' <h2 className="text-lg font-semibold">Get started</h2>'),o.push(` <p className="mt-1 text-sm text-muted-foreground">Here's what you can do</p>`),o.push(' <div className="mt-6 grid gap-3 sm:grid-cols-2 text-left">');for(let n of r){let i=n.entity??n.name??"Item",s=Pr(i),a=Dt(i.replace(/_/g,"-"));o.push(' <div className="flex items-center gap-3 rounded-md border p-3">'),o.push(" <"+s+' className="h-5 w-5 text-muted-foreground" />'),o.push(' <span className="text-sm font-medium">Add your first '+a+"</span>"),o.push(" </div>")}o.push(" </div>"),o.push(" </div>")}return o.push(" </div>"),o.push(" );"),o.push("}"),o.push(""),o.join(`
2418
+ `)}function bc(t,e=!1){if(!t.multiTenant)return null;let r=[];return e?(r.push('import { pgTable, text, timestamp, index } from "drizzle-orm/pg-core";'),r.push('import { user } from "./auth";'),r.push(""),r.push('export const organization = pgTable("organization", {'),r.push(' id: text("id").primaryKey(),'),r.push(' name: text("name").notNull(),'),r.push(' slug: text("slug").unique().notNull(),'),r.push(' createdAt: timestamp("created_at").defaultNow().notNull(),'),r.push(' updatedAt: timestamp("updated_at").defaultNow().notNull(),'),r.push("});"),r.push(""),r.push("export const orgMember = pgTable(")):(r.push('import { sqliteTable, text, index } from "drizzle-orm/sqlite-core";'),r.push('import { sql } from "drizzle-orm";'),r.push('import { user } from "./auth";'),r.push(""),r.push('export const organization = sqliteTable("organization", {'),r.push(' id: text("id").primaryKey(),'),r.push(' name: text("name").notNull(),'),r.push(' slug: text("slug").unique().notNull(),'),r.push(' createdAt: text("created_at").default(sql`CURRENT_TIMESTAMP`).notNull(),'),r.push(' updatedAt: text("updated_at").default(sql`CURRENT_TIMESTAMP`).notNull(),'),r.push("});"),r.push(""),r.push("export const orgMember = sqliteTable(")),r.push(' "org_member",'),r.push(" {"),r.push(' id: text("id").primaryKey(),'),r.push(' orgId: text("org_id").notNull().references(() => organization.id),'),r.push(' userId: text("user_id").notNull().references(() => user.id),'),r.push(' role: text("role").notNull(),'),e?r.push(' joinedAt: timestamp("joined_at").defaultNow().notNull(),'):r.push(' joinedAt: text("joined_at").default(sql`CURRENT_TIMESTAMP`).notNull(),'),r.push(" },"),r.push(" (table) => ({"),r.push(' orgIdx: index("org_member_org_idx").on(table.orgId),'),r.push(' userIdx: index("org_member_user_idx").on(table.userId),'),r.push(" }),"),r.push(");"),r.push(""),r.join(`
2419
+ `)}function wc(t){if(!t.multiTenant)return null;let e=[];return e.push('import { db } from "./db";'),e.push('import { organization, orgMember } from "@/db/schema/organization";'),e.push('import { eq } from "drizzle-orm";'),e.push(""),e.push("export async function getCurrentOrg(userId: string) {"),e.push(" const membership = await db"),e.push(" .select()"),e.push(" .from(orgMember)"),e.push(" .where(eq(orgMember.userId, userId))"),e.push(" .limit(1);"),e.push(" if (membership.length === 0) return null;"),e.push(" const org = await db"),e.push(" .select()"),e.push(" .from(organization)"),e.push(" .where(eq(organization.id, membership[0].orgId))"),e.push(" .limit(1);"),e.push(" return org[0] ?? null;"),e.push("}"),e.push(""),e.push("export async function getOrgMembers(orgId: string) {"),e.push(" return db"),e.push(" .select()"),e.push(" .from(orgMember)"),e.push(" .where(eq(orgMember.orgId, orgId));"),e.push("}"),e.push(""),e.push("export async function inviteToOrg(orgId: string, email: string, role: string) {"),e.push(" const id = crypto.randomUUID();"),e.push(" await db.insert(orgMember).values({"),e.push(" id,"),e.push(" orgId,"),e.push(" userId: email,"),e.push(" role,"),e.push(" });"),e.push(" return { id, orgId, email, role };"),e.push("}"),e.push(""),e.join(`
2420
+ `)}function vc(t){if(!t.multiTenant)return null;let e=[];return e.push('"use client";'),e.push(""),e.push("import {"),e.push(" DropdownMenu,"),e.push(" DropdownMenuContent,"),e.push(" DropdownMenuItem,"),e.push(" DropdownMenuTrigger,"),e.push('} from "@/components/ui/dropdown-menu";'),e.push('import { Button } from "@/components/ui/button";'),e.push('import { ChevronsUpDown } from "lucide-react";'),e.push(""),e.push("interface OrgSwitcherProps {"),e.push(" orgs: Array<{ id: string; name: string }>;"),e.push(" currentOrgId: string;"),e.push("}"),e.push(""),e.push("export default function OrgSwitcher({ orgs, currentOrgId }: OrgSwitcherProps) {"),e.push(" const currentOrg = orgs.find((o) => o.id === currentOrgId);"),e.push(""),e.push(" return ("),e.push(" <DropdownMenu>"),e.push(" <DropdownMenuTrigger asChild>"),e.push(' <Button variant="outline" className="w-full justify-between">'),e.push(' <span className="truncate">{currentOrg?.name ?? "Select org"}</span>'),e.push(' <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />'),e.push(" </Button>"),e.push(" </DropdownMenuTrigger>"),e.push(' <DropdownMenuContent className="w-56">'),e.push(" {orgs.map((org) => ("),e.push(" <DropdownMenuItem key={org.id}>"),e.push(" {org.name}"),e.push(" </DropdownMenuItem>"),e.push(" ))}"),e.push(" </DropdownMenuContent>"),e.push(" </DropdownMenu>"),e.push(" );"),e.push("}"),e.push(""),e.join(`
2421
+ `)}function xc(t,e,r){let o=[],n=t.split("-").map(l=>l.charAt(0).toUpperCase()+l.slice(1)).join(" ");o.push(`# ${n}`),o.push(""),e?.summary&&(o.push(e.summary),o.push(""));let i=e?.features??[];if(i.length>0){o.push("## Features"),o.push("");for(let l of i){let d=l.description?` \u2014 ${l.description}`:"";o.push(`- **${l.name}**${d}`)}o.push("")}o.push("## Tech Stack"),o.push(""),o.push("| Layer | Technology |"),o.push("|-------|------------|"),o.push("| Framework | Next.js 15 (App Router) |"),o.push("| Database | Mistflow Cloud (Postgres) + Drizzle ORM |"),o.push("| Auth | Better Auth (email/password, social login) |"),o.push("| Styling | Tailwind CSS + shadcn/ui |"),o.push("| Deployment | Mistflow Cloud |"),r.hasStripe&&o.push("| Payments | Stripe |"),r.hasResend&&o.push("| Email | Resend + React Email |"),r.hasStorage&&o.push("| File Storage | Mistflow Cloud (managed blob storage) |"),r.hasAdmin&&o.push("| Admin | Better Auth admin plugin |"),r.hasAI&&o.push("| AI | Vercel AI SDK + OpenAI |"),o.push("");let s=e?.pages??[];if(s.length>0){o.push("## Pages"),o.push(""),o.push("| Route | Description |"),o.push("|-------|-------------|");for(let l of s){let d=l.path??l.route??l.name??"",h=l.description??"";o.push(`| \`${d.startsWith("/")?d:"/"+d}\` | ${h} |`)}o.push("")}let a=e?.dataModel??[];if(a.length>0){o.push("## Data Model"),o.push("");for(let l of a){let d=l.entity??l.name??"Unknown";if(o.push(`### ${d}`),o.push(""),l.fields.length>0){if(typeof l.fields[0]=="string")o.push(`Fields: ${l.fields.join(", ")}`);else{o.push("| Field | Type |"),o.push("|-------|------|");for(let h of l.fields)o.push(`| ${h.name} | ${h.type} |`)}o.push("")}}}return o.push("## Getting Started"),o.push(""),o.push("### Prerequisites"),o.push(""),o.push("- Node.js 20+"),o.push("- npm"),o.push(""),o.push("### Install"),o.push(""),o.push("The host AI calls mist_install (fire-and-poll) which handles this for you. Run manually with plain npm if needed:"),o.push(""),o.push("```bash"),o.push("npm install"),o.push("```"),o.push(""),o.push("### Set up environment"),o.push(""),o.push("Copy `.env.example` to `.env.local` and fill in the values:"),o.push(""),o.push("```bash"),o.push("cp .env.example .env.local"),o.push("```"),o.push(""),o.push("| Variable | Description | Required |"),o.push("|----------|-------------|----------|"),r.isNeon?o.push("| `DATABASE_URL` | Postgres connection URL | Yes |"):(o.push("| `TURSO_URL` | Database connection URL | Yes |"),o.push("| `TURSO_AUTH_TOKEN` | Database auth token | Yes |")),o.push("| `AUTH_SECRET` | Auth encryption secret (auto-generated) | Yes |"),r.hasStripe&&(o.push("| `STRIPE_SECRET_KEY` | Stripe secret key | Yes |"),o.push("| `STRIPE_WEBHOOK_SECRET` | Stripe webhook signing secret | Yes |"),o.push("| `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` | Stripe publishable key | Yes |")),r.hasResend&&(o.push("| `RESEND_API_KEY` | Resend API key | Yes |"),o.push("| `EMAIL_FROM` | Sender email address | Yes (production) |")),r.hasStorage&&(o.push("| `MISTFLOW_API_KEY` | Mistflow API key for file storage | Yes |"),o.push("| `MISTFLOW_PROJECT_ID` | Mistflow project ID | Yes |")),r.hasAI&&o.push("| `OPENAI_API_KEY` | OpenAI API key | Yes |"),o.push(""),o.push("### Local database"),o.push(""),r.isNeon?(o.push("For local development, start a local Postgres server:"),o.push(""),o.push("```bash"),o.push("# Using Docker:"),o.push("docker run -d --name postgres -e POSTGRES_PASSWORD=postgres -p 5432:5432 postgres:17"),o.push("# Or install via Homebrew: brew install postgresql@17 && brew services start postgresql@17"),o.push("```")):(o.push("For local development, start a local Turso server:"),o.push(""),o.push("```bash"),o.push("npx turso dev"),o.push("```")),o.push(""),o.push("Then set up the database:"),o.push(""),o.push("```bash"),o.push("npm run db:push"),o.push("```"),o.push(""),o.push("### Run"),o.push(""),o.push("```bash"),o.push("npm run dev"),o.push("```"),o.push(""),o.push("Open [http://localhost:3000](http://localhost:3000)."),o.push(""),o.push("## Project Structure"),o.push(""),o.push("```"),o.push("app/"),o.push(" (auth)/ Login and registration pages"),o.push(" (dashboard)/ Authenticated app pages"),r.hasAdmin&&o.push(" (admin)/ Admin panel pages"),o.push(" api/ API routes (auth, health, webhooks)"),o.push(" layout.tsx Root layout with fonts and providers"),o.push(" globals.css Design tokens and Tailwind config"),o.push("components/ Reusable UI components"),o.push("db/"),o.push(" schema/ Database table definitions"),o.push(" index.ts Schema exports"),o.push("lib/"),o.push(" auth.ts Better Auth server config"),o.push(" auth-client.ts Better Auth client config"),o.push(` db.ts ${r.isNeon?"Postgres":"SQLite"} database connection`),r.hasStripe&&o.push(" stripe.ts Stripe client"),r.hasResend&&(o.push(" resend.ts Resend client"),o.push(" email.ts Email send helpers")),r.hasStorage&&o.push(" storage.ts File upload/download helpers"),r.hasAI&&o.push(" ai.ts AI client (Vercel AI SDK + OpenAI)"),r.hasResend&&o.push("emails/ React Email templates"),o.push("```"),o.push(""),o.push("## Deploy"),o.push(""),o.push("Deploy to production with Mistflow:"),o.push(""),o.push("```"),o.push("# In your AI editor (Claude Code, Cursor, etc.):"),o.push("mist_deploy action='deploy'"),o.push("```"),o.push(""),o.push("Your app will be live at `https://<app-name>.mistflow.app`."),o.push(""),e?.design&&(o.push("## Design"),o.push(""),e.design.tone&&o.push(`- **Tone**: ${e.design.tone}`),e.design.fonts&&(o.push(`- **Heading font**: ${e.design.fonts.heading}`),o.push(`- **Body font**: ${e.design.fonts.body}`)),e.design.accentColor&&o.push(`- **Accent color**: ${e.design.accentColor}`),e.design.borderRadius&&o.push(`- **Border radius**: ${e.design.borderRadius}`),o.push("")),o.push("---"),o.push(""),o.push("Built with [Mistflow](https://mistflow.ai)"),o.push(""),o.join(`
2422
+ `)}async function kc(t,e){let{name:r,plan:o,path:n,planId:i}=t;if(!n)return c("mist_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(!Kl(n))return c(`mist_init 'path' must be an absolute path \u2014 received '${n}'. Pass the full absolute path to the target directory.`,!0);let s=us(n),a=o;if(!a&&i){let y=Xl(i);if(!y)return c(`No plan found for planId '${i}'. Call mist_plan first, or pass the plan object inline.`,!0);a=y.plan}let l=a?.design,d=a?.appStyle,h=a?lo(a,"stripe","payment","billing","subscription","checkout","pricing"):!1,u=!0,p=a?lo(a,"upload","file storage","image upload","profile picture","attachment","gallery","media","blob"):!1,f=a?lo(a,"admin panel","admin dashboard","admin management"):!1,v=a?lo(a,"ai integration","openai","llm","ai chat","chatbot","gpt"):!1,b=a,g=!0;if(!oc(s))return c(`A project already exists at this location (${s}). 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);Sr(s,{recursive:!0});try{let y=Se(Et(s),".mistflow","mockups");if(Xe(y)){let I=ps(y).filter(B=>B.endsWith(".html"));if(I.length>0){let B=Se(s,".mistflow","mockups");Sr(B,{recursive:!0});for(let Z of I)Jl(Se(y,Z),Se(B,Z));console.error(`Copied ${I.length} mockup file(s) into project`)}}}catch(y){console.error("Could not copy mockup files:",y instanceof Error?y.message:y)}let x=null;try{x=await Jo("nextjs")}catch(y){console.error("Could not fetch scaffold from API, using minimal scaffold:",y instanceof Error?y.message:y)}if(x){let y=r.toLowerCase().replace(/[^a-z0-9-]/g,"-");for(let _ of x.files){if(_.path==="package.json"||_.path==="middleware.ts"||_.path==="components/sidebar.tsx"||_.path==="components/topnav.tsx"||_.path==="app/(dashboard)/layout.tsx"||_.path==="app/(dashboard)/page.tsx"||_.path==="app/(dashboard)/dashboard/page.tsx"||!h&&(_.path.includes("stripe")||_.path.includes("webhook/stripe"))||!u&&(_.path.includes("resend")||_.path.includes("emails/"))||!f&&(_.path.includes("(admin)")||_.path.includes("admin-sidebar"))||g&&(_.path==="lib/db.ts"||_.path==="lib/auth.ts"||_.path==="drizzle.config.ts"||_.path==="db/schema/auth.ts"))continue;let m=_.content.replace(/\{\{APP_NAME\}\}/g,r).replace(/\{\{WORKER_NAME\}\}/g,y);if(g&&_.path==="next.config.ts"&&(m=m.replace(/serverExternalPackages:\s*\[[^\]]*\],?/g,'serverExternalPackages: ["@electric-sql/pglite"],')),_.path==="next.config.ts"){let w=ec(s);w&&(console.error(`[init] Project is inside monorepo at ${w} \u2014 adding outputFileTracingRoot`),m.includes("outputFileTracingRoot")||(m=m.replace('import type { NextConfig } from "next";',`import type { NextConfig } from "next";
2410
2423
  import { dirname } from "path";
2411
2424
  import { fileURLToPath } from "url";
2412
2425
 
2413
- const __dirname = dirname(fileURLToPath(import.meta.url));`),g=g.replace("images: {",`outputFileTracingRoot: __dirname,
2414
- images: {`)))}!y&&u.path.includes("sidebar")&&(g=g.replace(/\{user\.role === "admin"[\s\S]*?<\/Link>\s*\)\}/m,""),g=g.replace(/, Shield/g,"")),M(r,u.path,g)}let I={...k.dependencies},B={...k.devDependencies};if(I["drizzle-zod"]||(I["drizzle-zod"]="^0.5.1"),f&&(delete I["@libsql/client"],I["@neondatabase/serverless"]="^0.10.0",B["@electric-sql/pglite"]="^0.2.0"),h&&(I.stripe="^17.0.0"),m&&(I.resend="^4.0.0",I["@react-email/components"]="^0.0.31"),v&&(I.ai="^4.0.0",I["@ai-sdk/openai"]="^1.0.0",I.openai="^4.0.0"),M(r,"package.json",JSON.stringify({name:s,version:"0.1.0",private:!0,scripts:{dev:"next dev",build:"next build","build:cf":"opennextjs-cloudflare build",start:"next start",lint:"next lint","db:push":"drizzle-kit push","db:studio":"drizzle-kit studio"},dependencies:I,devDependencies:B,optionalDependencies:{"@noble/ciphers":"^1.3.0"},overrides:{react:"19.1.0","react-dom":"19.1.0",punycode:"^2.3.1","zod-to-json-schema":"3.24.6"}},null,2)),k.methodology){let u=k.methodology;f&&(u=u.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")),u=bs(u),M(r,"AGENTS.md",u),M(r,"CLAUDE.md",u)}let ne=a?.designMd;ne&&M(r,"DESIGN.md",ne),f&&(M(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(`
2415
- `)),M(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(`
2416
- `)),M(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(`
2417
- `)),M(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(`
2418
- `)),M(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(`
2419
- `)))}else M(r,"package.json",JSON.stringify({name:s,version:"0.1.0",private:!0},null,2));let R=a?.designMd;M(r,"app/globals.css",$l(l,d,R)),M(r,"app/layout.tsx",Fl(s,l,b?.language)),M(r,"README.md",Ql(s,a,{hasStripe:h,hasResend:m,hasStorage:p,hasAdmin:y,hasAI:v,isNeon:f})),M(r,"contracts/README.md",ys());let j=a?.dataModel??[],S=new Set,O=0;for(let w of j){let I=w.entity??w.name;if(!I||typeof I!="string")continue;let B=vs(I);S.has(B)||(S.add(B),M(r,B,ws(I)),O++)}O===0&&M(r,"contracts/.gitkeep","");let U=[],x=a?.publicPages;if(Array.isArray(x))U=x;else if(typeof x=="string"){try{U=JSON.parse(x)}catch{U=[]}Array.isArray(U)||(U=[])}if(!U.includes("/")){let w=a?.steps?.some(B=>{let Q=((B.name??"")+" "+(B.description??"")).toLowerCase();return Q.includes("landing")||Q.includes("marketing")||Q.includes("homepage")}),I=a?.pages?.some(B=>B.path==="/");(w||I)&&(U=["/",...U])}let $={name:s,summary:a?.summary,authModel:a?.authModel,roles:a?.roles,defaultRole:a?.defaultRole,publicPages:U,navStyle:a?.navStyle,multiTenant:a?.multiTenant,pages:a?.pages,dataModel:a?.dataModel,design:a?.design},H=Bl($);H&&M(r,"middleware.ts",H);let G=zl($);G&&M(r,G.path,G.content);let oe=Hl($);if(oe&&M(r,"lib/roles.ts",oe),M(r,"app/page.tsx",Wl($)),M(r,"app/(dashboard)/layout.tsx",Gl($,y)),M(r,"app/(dashboard)/dashboard/page.tsx",Vl($)),$.multiTenant){let w=Jl($,f);w&&M(r,"db/schema/organization.ts",w);let I=Yl($);I&&M(r,"lib/org.ts",I);let B=Kl($);B&&M(r,"components/org-switcher.tsx",B)}M(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)),h&&M(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(`
2420
- `)),m&&(M(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(`
2421
- `)),M(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(`
2422
- `))),p&&(M(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(`
2423
- `)),M(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(`
2424
- `))),v&&(M(r,"lib/ai.ts",['import { createOpenAI } from "@ai-sdk/openai";',"","export const openai = createOpenAI({"," apiKey: process.env.OPENAI_API_KEY,","});",""].join(`
2425
- `)),M(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(`
2426
- `)));let E=Array.isArray(a?.integrations)?a.integrations.map(w=>({name:w.name,preset:w.preset,envVars:w.envVars??[]})):[],C={};v&&!E.some(w=>w.envVars?.some(I=>I.key==="OPENAI_API_KEY"))&&(C.OPENAI_API_KEY={description:"OpenAI API key",setupUrl:"https://platform.openai.com/api-keys"});for(let w of E)for(let I of w.envVars??[])C[I.key]||(C[I.key]={description:I.description,setupUrl:I.setupUrl,...w.name?{integration:w.name}:{}});let re={name:s,methodologyVersion:k?.version??"1.0",createdAt:new Date().toISOString(),...i?{planId:i}:{},plan:Array.isArray(a?.steps)?{...a,steps:a.steps.map(w=>({number:w.number,name:w.name??w.title,description:w.description,entities:w.entities,pages:w.pages,features:w.features,status:"pending"}))}:a,dbProvider:"neon",env:{managed:{DATABASE_URL:{description:"Postgres connection URL",scope:"production"},AUTH_SECRET:{description:"Auth encryption secret",scope:"production"},...h?{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"}}:{},...m?{RESEND_API_KEY:{description:"Resend API key \u2014 managed by Mistflow by default, override with your own key from resend.com",scope:"production"},EMAIL_FROM:{description:"Sender email address \u2014 managed by Mistflow by default",scope:"production"}}:{},...p?{MISTFLOW_API_KEY:{description:"Mistflow API key for file storage",scope:"production"},MISTFLOW_PROJECT_ID:{description:"Mistflow project ID",scope:"production"}}:{}},...Object.keys(C).length>0?{required:C}:{}},authModel:a?.authModel??"email",roles:a?.roles??null,navStyle:a?.navStyle??"sidebar",multiTenant:a?.multiTenant??!1,hasAdmin:y,hasResend:m,hasStorage:p,hasAI:v,deploy:null};M(r,"mistflow.json",JSON.stringify(re,null,2));let pe=_l(32).toString("hex"),D=h?`
2426
+ const __dirname = dirname(fileURLToPath(import.meta.url));`),m=m.replace("images: {",`outputFileTracingRoot: __dirname,
2427
+ images: {`)))}!f&&_.path.includes("sidebar")&&(m=m.replace(/\{user\.role === "admin"[\s\S]*?<\/Link>\s*\)\}/m,""),m=m.replace(/, Shield/g,"")),M(s,_.path,m)}let I={...x.dependencies},B={...x.devDependencies};if(I["drizzle-zod"]||(I["drizzle-zod"]="^0.5.1"),g&&(delete I["@libsql/client"],I["@neondatabase/serverless"]="^0.10.0",B["@electric-sql/pglite"]="^0.2.0"),h&&(I.stripe="^17.0.0"),u&&(I.resend="^4.0.0",I["@react-email/components"]="^0.0.31"),v&&(I.ai="^4.0.0",I["@ai-sdk/openai"]="^1.0.0",I.openai="^4.0.0"),M(s,"package.json",JSON.stringify({name:r,version:"0.1.0",private:!0,scripts:{dev:"next dev",build:"next build","build:cf":"opennextjs-cloudflare build",start:"next start",lint:"next lint","db:push":"drizzle-kit push","db:studio":"drizzle-kit studio"},dependencies:I,devDependencies:B,optionalDependencies:{"@noble/ciphers":"^1.3.0"},overrides:{react:"19.1.0","react-dom":"19.1.0",punycode:"^2.3.1","zod-to-json-schema":"3.24.6"}},null,2)),x.methodology){let _=x.methodology;g&&(_=_.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")),_=wr(_),M(s,"AGENTS.md",_),M(s,"CLAUDE.md",_)}let be=a?.designMd;be&&M(s,"DESIGN.md",be),g&&(M(s,"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(`
2428
+ `)),M(s,"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(`
2429
+ `)),M(s,"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(`
2430
+ `)),M(s,"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(`
2431
+ `)),M(s,"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(`
2432
+ `)))}else M(s,"package.json",JSON.stringify({name:r,version:"0.1.0",private:!0},null,2));let R=a?.designMd;M(s,"app/globals.css",ac(l,d,R)),M(s,"app/layout.tsx",dc(r,l,b?.language)),M(s,"README.md",xc(r,a,{hasStripe:h,hasResend:u,hasStorage:p,hasAdmin:f,hasAI:v,isNeon:g})),M(s,"contracts/README.md",br());let D=a?.dataModel??[],P=new Set,j=0;for(let y of D){let I=y.entity??y.name;if(!I||typeof I!="string")continue;let B=xr(I);P.has(B)||(P.add(B),M(s,B,vr(I)),j++)}j===0&&M(s,"contracts/.gitkeep","");let U=[],k=a?.publicPages;if(Array.isArray(k))U=k;else if(typeof k=="string"){try{U=JSON.parse(k)}catch{U=[]}Array.isArray(U)||(U=[])}if(!U.includes("/")){let y=a?.steps?.some(B=>{let Z=((B.name??"")+" "+(B.description??"")).toLowerCase();return Z.includes("landing")||Z.includes("marketing")||Z.includes("homepage")}),I=a?.pages?.some(B=>B.path==="/");(y||I)&&(U=["/",...U])}let L={name:r,summary:a?.summary,authModel:a?.authModel,roles:a?.roles,defaultRole:a?.defaultRole,publicPages:U,navStyle:a?.navStyle,multiTenant:a?.multiTenant,pages:a?.pages,dataModel:a?.dataModel,design:a?.design},K=uc(L);K&&M(s,"middleware.ts",K);let H=mc(L);H&&M(s,H.path,H.content);let ye=hc(L);if(ye&&M(s,"lib/roles.ts",ye),M(s,"app/page.tsx",gc(L)),M(s,"app/(dashboard)/layout.tsx",fc(L,f)),M(s,"app/(dashboard)/dashboard/page.tsx",yc(L)),L.multiTenant){let y=bc(L,g);y&&M(s,"db/schema/organization.ts",y);let I=wc(L);I&&M(s,"lib/org.ts",I);let B=vc(L);B&&M(s,"components/org-switcher.tsx",B)}M(s,"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)),h&&M(s,"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(`
2433
+ `)),u&&(M(s,"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(`
2434
+ `)),M(s,"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(`
2435
+ `))),p&&(M(s,"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(`
2436
+ `)),M(s,"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(`
2437
+ `))),v&&(M(s,"lib/ai.ts",['import { createOpenAI } from "@ai-sdk/openai";',"","export const openai = createOpenAI({"," apiKey: process.env.OPENAI_API_KEY,","});",""].join(`
2438
+ `)),M(s,"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(`
2439
+ `)));let J=Array.isArray(a?.integrations)?a.integrations.map(y=>({name:y.name,preset:y.preset,envVars:y.envVars??[]})):[],N={};v&&!J.some(y=>y.envVars?.some(I=>I.key==="OPENAI_API_KEY"))&&(N.OPENAI_API_KEY={description:"OpenAI API key",setupUrl:"https://platform.openai.com/api-keys"});for(let y of J)for(let I of y.envVars??[])N[I.key]||(N[I.key]={description:I.description,setupUrl:I.setupUrl,...y.name?{integration:y.name}:{}});let O={name:r,methodologyVersion:x?.version??"1.0",createdAt:new Date().toISOString(),...i?{planId:i}:{},plan:Array.isArray(a?.steps)?{...a,steps:a.steps.map(y=>({number:y.number,name:y.name??y.title,description:y.description,entities:y.entities,pages:y.pages,features:y.features,status:"pending"}))}:a,dbProvider:"neon",env:{managed:{DATABASE_URL:{description:"Postgres connection URL",scope:"production"},AUTH_SECRET:{description:"Auth encryption secret",scope:"production"},...h?{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"}}:{},...p?{MISTFLOW_API_KEY:{description:"Mistflow API key for file storage",scope:"production"},MISTFLOW_PROJECT_ID:{description:"Mistflow project ID",scope:"production"}}:{}},...Object.keys(N).length>0?{required:N}:{}},authModel:a?.authModel??"email",roles:a?.roles??null,navStyle:a?.navStyle??"sidebar",multiTenant:a?.multiTenant??!1,hasAdmin:f,hasResend:u,hasStorage:p,hasAI:v,deploy:null};M(s,"mistflow.json",JSON.stringify(O,null,2));let pe=Ql(32).toString("hex"),z=h?`
2427
2440
  # Stripe
2428
2441
  STRIPE_SECRET_KEY=
2429
2442
  STRIPE_WEBHOOK_SECRET=
2430
2443
  NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=
2431
- `:"",Z=m?`
2444
+ `:"",G=u?`
2432
2445
  # Email (Resend)
2433
2446
  RESEND_API_KEY=
2434
2447
  EMAIL_FROM=onboarding@resend.dev
2435
- `:"",Ie=p?`
2448
+ `:"",me=p?`
2436
2449
  # File Storage (Mistflow managed)
2437
2450
  MISTFLOW_API_KEY=
2438
2451
  MISTFLOW_PROJECT_ID=
2439
- `:"",me=v?`
2452
+ `:"",Pe=v?`
2440
2453
  # AI (get your key at https://platform.openai.com/api-keys)
2441
2454
  OPENAI_API_KEY=
2442
- `:"",Te=`# Local dev: PGlite is used automatically (zero-install embedded Postgres)
2455
+ `:"",Ie=`# Local dev: PGlite is used automatically (zero-install embedded Postgres)
2443
2456
  # Set DATABASE_URL only for production or to use a remote Postgres
2444
- # DATABASE_URL=postgresql://postgres:postgres@localhost:5432/devdb`,He=`# Local dev: PGlite is used automatically (zero-install embedded Postgres)
2457
+ # DATABASE_URL=postgresql://postgres:postgres@localhost:5432/devdb`,De=`# Local dev: PGlite is used automatically (zero-install embedded Postgres)
2445
2458
  # Set DATABASE_URL only for production or to use a remote Postgres
2446
- # DATABASE_URL=postgresql://postgres:postgres@localhost:5432/devdb`;M(r,".env.local",`${Te}
2459
+ # DATABASE_URL=postgresql://postgres:postgres@localhost:5432/devdb`;M(s,".env.local",`${Ie}
2447
2460
  AUTH_SECRET=${pe}
2448
- ${D}${Z}${Ie}${me}`),M(r,".env.example",`${He}
2461
+ ${z}${G}${me}${Pe}`),M(s,".env.example",`${De}
2449
2462
  AUTH_SECRET=your-secret-here
2450
- ${D}${Z}${Ie}${me}`);let q=[],A=(w,I)=>{q.push({phase:w,message:I})},z=(w,I)=>{let B=q.find(Q=>Q.phase===w&&!Q.durationMs);B&&(B.durationMs=I)};if(e){let w=sn(e.server,e.progressToken,()=>q[q.length-1]?.message??"Setting up project...");e.cleanup=()=>w.stop()}let fe=b?.requestedSubdomain||void 0,V,Pe;A("register","Registering project on Mistflow...");let We=Date.now();try{let w=await St(s,void 0,"neon",fe);V=w.id;let I=ke(r,"mistflow.json"),B=JSON.parse(cn(I,"utf-8"));if(B.projectId=V,Ss(I,JSON.stringify(B,null,2)),It(r,Tt(V,s)),w.managed_env&&Object.keys(w.managed_env).length>0){let Q=ke(r,".env.local"),ye=Ze(Q)?cn(Q,"utf-8"):"";for(let[ne,u]of Object.entries(w.managed_env)){let g=new RegExp(`^${ne}=.*$`,"m");g.test(ye)?ye=ye.replace(g,`${ne}=${u}`):ye+=`
2451
- ${ne}=${u}`}Ss(Q,ye)}try{let{getBaseUrl:Q,getAuthHeaders:ye}=await Promise.resolve().then(()=>(ue(),ro)),ne=ye(),u=a?.features,g=a?.steps,P={};Array.isArray(u)&&u.length>0&&(P.features=u.map(T=>T.name)),a&&(P.plan=a),Array.isArray(g)&&g.length>0&&(P.provenance=g.map(T=>({feature:T.name??T.title??`Step ${T.number??"?"}`,user_intent:(T.description??"").slice(0,500),decisions:"Seeded from plan at init",tradeoffs:"",files_affected:[]}))),Object.keys(P).length>0&&await fetch(`${Q()}/api/projects/${encodeURIComponent(V)}/state`,{method:"PUT",headers:{...ne,"Content-Type":"application/json"},body:JSON.stringify(P)})}catch{}q[q.length-1].message=`Registered as ${V.slice(0,8)}`}catch(w){let I=w instanceof Error?w.message:String(w);console.error("Could not register project on backend:",I),Pe=`Project created locally but NOT registered on Mistflow servers (${I}). Deploy will auto-register it.`,q[q.length-1].message="Registration skipped (offline \u2014 deploy will retry)"}z("register",Date.now()-We),A("git","Initializing git repository...");let Ge=Date.now();try{let w=Cl(r);await w.init(),await w.add("."),await w.commit("Initial Mistflow project setup"),q[q.length-1].message="Git repository initialized"}catch{console.error("Git initialization failed, continuing without git."),q[q.length-1].message="Git init skipped"}z("git",Date.now()-Ge);let Ve=q.reduce((w,I)=>w+(I.durationMs??0),0),he={projectPath:r,projectId:V,status:"awaiting_install"},Ee=q.map(w=>{let I=w.durationMs?` (${(w.durationMs/1e3).toFixed(1)}s)`:"";return`${w.message}${I}`});he.progress=Ee,he.totalSetupTime=`${(Ve/1e3).toFixed(1)}s`;let Ne=[];V||Ne.push("Project was not registered with Mistflow (not signed in). Run mist_setup to sign in BEFORE deploying \u2014 deploy will fail without it."),Pe&&(he.registrationWarning=Pe),Ne.length>0&&(he.warnings=Ne);let je=`NEXT: Call mist_install({ projectPath: "${r}" }). This is fire-and-poll \u2014 the first call returns a jobId and status: "running"; re-call with the same jobId every ~15-30s until status is "complete". Typical duration 30-90s. Do NOT ask the user for permission \u2014 install is a required follow-up to init, not a decision point. After install completes, call mist_implement({ projectPath: "${r}" }) to build the first plan step.`;return he.nextAction=V?je:`${je} IMPORTANT: You MUST also run mist_setup to sign in before deploying \u2014 the project could not be registered because auth is missing.`,c(JSON.stringify(he))}var El,jl,Dl,lr,Ll,ql,ur,mr=_(()=>{"use strict";K();ms();ue();Qe();xs();xs();El=At.object({name:At.string().min(1).describe("Human-readable app name (e.g. 'Task Flow')."),plan:At.any().optional().describe("Full plan object. Optional when planId is provided \u2014 the tool loads the cached plan from ~/.mistflow/plans/<planId>.json."),path:At.string().optional().describe("Absolute path where the project should be scaffolded."),planId:At.string().optional().describe("Plan ID from a prior mist_plan call. When present, the full plan is loaded from disk if 'plan' is omitted.")});jl={sharp:"0.125rem",subtle:"0.375rem",rounded:"0.75rem",pill:"9999px"},Dl=[["--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"]];lr={"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"};Ll=new Set(["ar","he","fa","ur"]);ql={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"};ur={name:"mist_init",description:"Initialize a new Next.js project using the Mistflow stack. Downloads templates from the Mistflow registry, installs dependencies, sets up auth, and initializes git. Use this after mist_plan to create the project.",inputSchema:El,handler:Xl}});var gr,hr=_(()=>{gr="\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 yr,fr=_(()=>{yr=`# Landing Page Rules
2463
+ ${z}${G}${me}${Pe}`);let V=[],A=(y,I)=>{V.push({phase:y,message:I})},q=(y,I)=>{let B=V.find(Z=>Z.phase===y&&!Z.durationMs);B&&(B.durationMs=I)};if(e){let y=no(e.server,e.progressToken,()=>V[V.length-1]?.message??"Setting up project...");e.cleanup=()=>y.stop()}let he=b?.requestedSubdomain||void 0,se,ee;A("register","Registering project on Mistflow...");let We=Date.now();try{let y=await Tt(r,void 0,"neon",he);se=y.id;let I=Se(s,"mistflow.json"),B=JSON.parse(co(I,"utf-8"));if(B.projectId=se,Tr(I,JSON.stringify(B,null,2)),It(s,Ct(se,r)),y.managed_env&&Object.keys(y.managed_env).length>0){let Z=Se(s,".env.local"),Ae=Xe(Z)?co(Z,"utf-8"):"";for(let[be,_]of Object.entries(y.managed_env)){let m=new RegExp(`^${be}=.*$`,"m");m.test(Ae)?Ae=Ae.replace(m,`${be}=${_}`):Ae+=`
2464
+ ${be}=${_}`}Tr(Z,Ae)}try{let{getBaseUrl:Z,getAuthHeaders:Ae}=await Promise.resolve().then(()=>(ue(),an)),be=Ae(),_=a?.features,m=a?.steps,w={};Array.isArray(_)&&_.length>0&&(w.features=_.map(T=>T.name)),a&&(w.plan=a),Array.isArray(m)&&m.length>0&&(w.provenance=m.map(T=>({feature:T.name??T.title??`Step ${T.number??"?"}`,user_intent:(T.description??"").slice(0,500),decisions:"Seeded from plan at init",tradeoffs:"",files_affected:[]}))),Object.keys(w).length>0&&await fetch(`${Z()}/api/projects/${encodeURIComponent(se)}/state`,{method:"PUT",headers:{...be,"Content-Type":"application/json"},body:JSON.stringify(w)})}catch{}V[V.length-1].message=`Registered as ${se.slice(0,8)}`}catch(y){let I=y instanceof Error?y.message:String(y);console.error("Could not register project on backend:",I),ee=`Project created locally but NOT registered on Mistflow servers (${I}). Deploy will auto-register it.`,V[V.length-1].message="Registration skipped (offline \u2014 deploy will retry)"}q("register",Date.now()-We),A("git","Initializing git repository...");let et=Date.now();try{let y=Zl(s);await y.init(),await y.add("."),await y.commit("Initial Mistflow project setup"),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"}q("git",Date.now()-et);let tt=V.reduce((y,I)=>y+(I.durationMs??0),0),ge={projectPath:s,projectId:se,status:"awaiting_install"},Ce=V.map(y=>{let I=y.durationMs?` (${(y.durationMs/1e3).toFixed(1)}s)`:"";return`${y.message}${I}`});ge.progress=Ce,ge.totalSetupTime=`${(tt/1e3).toFixed(1)}s`;let Ge=[];se||Ge.push("Project was not registered with Mistflow (not signed in). Run mist_setup to sign in BEFORE deploying \u2014 deploy will fail without it."),ee&&(ge.registrationWarning=ee),Ge.length>0&&(ge.warnings=Ge);let je=`NEXT: Call mist_install({ projectPath: "${s}" }). This is fire-and-poll \u2014 the first call returns a jobId and status: "running"; re-call with the same jobId every ~15-30s until status is "complete". Typical duration 30-90s. Do NOT ask the user for permission \u2014 install is a required follow-up to init, not a decision point. After install completes, call mist_implement({ projectPath: "${s}" }) to build the first plan step.`;return ge.nextAction=se?je:`${je} IMPORTANT: You MUST also run mist_setup to sign in before deploying \u2014 the project could not be registered because auth is missing.`,c(JSON.stringify(ge))}var tc,rc,nc,cs,cc,pc,ms,hs=S(()=>{"use strict";X();hr();ue();Ye();kr();kr();tc=Nt.object({name:Nt.string().min(1).describe("Human-readable app name (e.g. 'Task Flow')."),plan:Nt.any().optional().describe("Full plan object. Optional when planId is provided \u2014 the tool loads the cached plan from ~/.mistflow/plans/<planId>.json."),path:Nt.string().optional().describe("Absolute path where the project should be scaffolded."),planId:Nt.string().optional().describe("Plan ID from a prior mist_plan call. When present, the full plan is loaded from disk if 'plan' is omitted.")});rc={sharp:"0.125rem",subtle:"0.375rem",rounded:"0.75rem",pill:"9999px"},nc=[["--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"]];cs={"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"};cc=new Set(["ar","he","fa","ur"]);pc={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"};ms={name:"mist_init",description:"Initialize a new Next.js project using the Mistflow stack. Downloads templates from the Mistflow registry, installs dependencies, sets up auth, and initializes git. Use this after mist_plan to create the project.",inputSchema:tc,handler:kc}});var fs,gs=S(()=>{fs="\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 bs,ys=S(()=>{bs=`# Consumer Warm Archetype
2465
+
2466
+ Component-level design guidance for personal, lifestyle, and wellness apps. Habits, journals, recipes, mood trackers, meditation, daily routines, personal finance.
2467
+
2468
+ These are apps people use for THEMSELVES. The UI should feel like a personal space, not a workplace.
2469
+
2470
+ ---
2471
+
2472
+ ## Page Composition
2473
+
2474
+ ### Dashboard / Home
2475
+
2476
+ The dashboard is the daily action surface. Users open the app, do their thing, close it. Optimize for speed and satisfaction.
2477
+
2478
+ - **Primary action above the fold.** A habit tracker's checkboxes, a journal's "Write" button, a recipe app's "What's for dinner?" The user should be able to complete the core action without scrolling.
2479
+ - **Today-focused.** Show today's data prominently. Yesterday is secondary. Last week is in a separate view.
2480
+ - **Progress visibility.** Streak counters, completion percentages, progress rings. These create the dopamine loop that brings users back.
2481
+ - **Warm empty states.** "No habits tracked yet" with an encouraging illustration or emoji and a clear CTA. Never show an empty table.
2482
+
2483
+ Layout:
2484
+ \`\`\`
2485
+ \u250C\u2500 greeting + date \u2500\u2500\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
2486
+ \u2502 Good morning, Sarah Mon, April 17 \u2502
2487
+ \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\u2524
2488
+ \u2502 \u250C\u2500 primary action card \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
2489
+ \u2502 \u2502 Today's [habits/entries/meals] \u2502 \u2502
2490
+ \u2502 \u2502 [interactive items with checkboxes] \u2502 \u2502
2491
+ \u2502 \u2502 [inline add button at bottom] \u2502 \u2502
2492
+ \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
2493
+ \u2502 \u2502
2494
+ \u2502 \u250C\u2500 stats row \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
2495
+ \u2502 \u2502 \u{1F525} 12 day streak \u2502 \u2713 85% today \u2502 \u2502
2496
+ \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
2497
+ \u2502 \u2502
2498
+ \u2502 \u250C\u2500 secondary content \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
2499
+ \u2502 \u2502 Recent history / weekly overview \u2502 \u2502
2500
+ \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
2501
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
2502
+ \`\`\`
2503
+
2504
+ ### List / Collection Views
2505
+
2506
+ - Cards over tables. Personal data feels wrong in spreadsheet rows. Use cards with rounded corners, warm shadows, and generous padding.
2507
+ - Grid layout for visual items (recipes, photos). List layout for text items (journal entries, habits).
2508
+ - Subtle category indicators: colored dots, small tags, or border-left accents. Not full-width color bars.
2509
+ - Soft filtering: pill toggles ("All / Active / Completed") not complex filter dropdowns.
2510
+
2511
+ ### Detail Pages
2512
+
2513
+ - Large hero area with the item's key visual (recipe photo, habit streak chart, journal entry text).
2514
+ - Warm card containers for metadata (date, category, tags).
2515
+ - Related items below: "Similar recipes," "Other entries this week."
2516
+ - Edit is inline or a sheet/modal, not a separate page. Reduces friction for personal data.
2517
+
2518
+ ### Forms / Input
2519
+
2520
+ - Conversational flow for creation. "What habit do you want to track?" not "Habit Name (required)."
2521
+ - Large touch targets. These apps are used on phones more than desktops.
2522
+ - Positive feedback on submit: a brief animation, a checkmark, a streak update. Not just "Saved."
2523
+ - Pre-filled smart defaults. A meal tracker defaults to the current meal time. A habit defaults to "Daily."
2524
+
2525
+ ## Component Patterns
2526
+
2527
+ ### Cards
2528
+
2529
+ Personal data cards should feel warm and contained:
2530
+
2531
+ \`\`\`
2532
+ bg-card rounded-xl p-5 shadow-sm border border-border/40
2533
+ hover: shadow-md transition-shadow (for clickable cards)
2534
+ \`\`\`
2535
+
2536
+ - Rounded corners: \`rounded-xl\` minimum. Sharp corners feel corporate.
2537
+ - Soft shadows: \`shadow-sm\` at rest, \`shadow-md\` on hover. Never \`shadow-lg\` (too aggressive).
2538
+ - Border: very subtle, \`border-border/40\` or \`border-border/30\`. The shadow does the separation work.
2539
+ - Internal spacing: generous. \`p-5\` or \`p-6\`. Personal data needs breathing room.
2540
+
2541
+ ### Progress and Streaks
2542
+
2543
+ Streaks and progress are the core engagement mechanic. Make them feel rewarding:
2544
+
2545
+ - **Streak counter**: large number + fire/star icon + "day streak" label. Use the primary color for the number.
2546
+ - **Progress ring**: circular SVG with animated fill. \`stroke-primary\` for progress, \`stroke-muted/20\` for background track.
2547
+ - **Completion badges**: small filled circles or checkmarks. Today's completed items use \`bg-primary\`, upcoming use \`bg-muted/30\`.
2548
+ - **Weekly grid**: 7 small squares (Mon-Sun), filled with primary color intensity proportional to completion. Like GitHub's contribution graph but warmer.
2549
+
2550
+ ### Stat Cards
2551
+
2552
+ Stats should feel encouraging, not analytical:
2553
+
2554
+ \`\`\`
2555
+ bg-primary/5 rounded-xl p-4
2556
+ large number: text-3xl font-bold text-primary
2557
+ label: text-sm text-muted-foreground
2558
+ trend: small arrow + percentage in green/red
2559
+ \`\`\`
2560
+
2561
+ - Tinted background using the primary color at 5% opacity. Warmer than neutral gray cards.
2562
+ - The number is the hero. Make it large and in the primary color.
2563
+ - Trend indicators are small and secondary. This isn't a trading dashboard.
2564
+
2565
+ ### Navigation
2566
+
2567
+ Sidebar for desktop, bottom tab bar for mobile:
2568
+
2569
+ - **Sidebar**: warm tinted background (\`bg-muted/30\`). Active item has \`bg-primary/10 text-primary\` with a subtle left border.
2570
+ - **Bottom tabs (mobile)**: max 4-5 items. Active tab icon uses primary color. Labels below icons.
2571
+ - **Navigation items**: icons from lucide-react. Use filled icons for active state, outline for inactive.
2572
+
2573
+ ### Buttons
2574
+
2575
+ - Primary: \`bg-primary text-primary-foreground rounded-xl px-6 py-3\`. Generous padding. Rounded.
2576
+ - Secondary: \`bg-muted text-foreground rounded-xl\`. Never outline on warm apps (too cold).
2577
+ - Destructive: \`bg-destructive/10 text-destructive rounded-xl\`. Tinted, not solid red.
2578
+ - FAB (floating action button): for primary creation action. \`rounded-full bg-primary shadow-lg\` fixed at bottom-right on mobile.
2579
+
2580
+ ### Badges and Tags
2581
+
2582
+ - Soft tinted pills: \`bg-primary/10 text-primary rounded-full px-3 py-1 text-xs\`
2583
+ - Category tags: use 3-4 muted colors from the palette, not the primary. \`bg-amber/10 text-amber-700\`, \`bg-emerald/10 text-emerald-700\`.
2584
+ - Status: completed = primary, pending = muted, skipped = \`text-muted-foreground line-through\`.
2585
+
2586
+ ## Spacing and Rhythm
2587
+
2588
+ - Page padding: \`p-5 md:p-8\`. Slightly more than business apps.
2589
+ - Card gaps: \`gap-4\`. Consistent throughout.
2590
+ - Section gaps: \`gap-8\` between major sections.
2591
+ - Internal card padding: \`p-5\` or \`p-6\`.
2592
+ - Touch targets: minimum \`h-12\` on all interactive elements. These apps are phone-first.
2593
+
2594
+ ## Typography
2595
+
2596
+ - Headings: the project's heading font at \`font-semibold\`, not \`font-bold\`. Softer weight for personal apps.
2597
+ - Body: \`text-sm\` or \`text-base\` with \`leading-relaxed\`. Readable, not dense.
2598
+ - Greeting: \`text-2xl font-semibold\` with the user's name. Personal.
2599
+ - Stats numbers: \`text-3xl font-bold tabular-nums\`. Large and proud.
2600
+ - Timestamps: \`text-xs text-muted-foreground\`. Small and unobtrusive.
2601
+
2602
+ ## Color Usage
2603
+
2604
+ All colors from project CSS custom properties. Key guidance for consumer-warm:
2605
+
2606
+ - Primary color for positive actions, progress, streaks, active states.
2607
+ - \`bg-primary/5\` for tinted card backgrounds (stat cards, active items).
2608
+ - \`bg-muted/30\` for secondary surfaces (sidebar, section separators).
2609
+ - Neutral backgrounds: warm white (\`bg-background\`), not cool gray.
2610
+ - Avoid using red/destructive for anything except actual delete actions.
2611
+ - Use the primary color sparingly. It should feel like an accent, not a flood.
2612
+
2613
+ ## Motion
2614
+
2615
+ - **Check-off animation**: scale down slightly then back (scale 0.95 -> 1) + opacity to 0.5 on the completed item. 200ms. Satisfying.
2616
+ - **Streak increment**: the number bumps up with a spring scale (1 -> 1.15 -> 1). 300ms.
2617
+ - **Card entrance**: \`PageTransition\` with gentle fade-up (y: 8px, 250ms). Not dramatic.
2618
+ - **Tab switch**: crossfade (opacity only, 200ms). No slide.
2619
+ - **Add item**: new card slides in from top or fades in at its position. 200ms.
2620
+
2621
+ ## Anti-Patterns
2622
+
2623
+ - Dense data tables. This is not a CRM. Use cards.
2624
+ - Gray/neutral-only palette. Consumer apps need color warmth.
2625
+ - "Dashboard" that's just stats. The dashboard IS the action surface.
2626
+ - Sharp corners (\`rounded-sm\` or \`rounded-none\`). Too corporate.
2627
+ - Heavy shadows (\`shadow-lg\`, \`shadow-xl\`). Too aggressive.
2628
+ - Small touch targets. Phone-first means \`h-12\` minimum.
2629
+ - Empty states that just say "No data." Be encouraging.
2630
+ - Dark theme as default. Consumer warm apps default to light.
2631
+ `});var vs,ws=S(()=>{vs=`# Consumer Bold Archetype
2632
+
2633
+ Component-level design guidance for energetic, achievement-driven consumer apps. Fitness, workouts, sports, gaming, social platforms, competitive tracking.
2634
+
2635
+ These apps are about DOING and WINNING. The UI should feel energetic, motivating, and visually impactful.
2636
+
2637
+ ---
2638
+
2639
+ ## Page Composition
2640
+
2641
+ ### Dashboard / Home
2642
+
2643
+ The dashboard is a scoreboard. Users want to see their numbers, their rank, their progress at a glance.
2644
+
2645
+ - **Large hero metric.** The ONE number that matters most: calories burned, workouts completed, current level, active streak. Display it huge (\`text-5xl font-bold\`).
2646
+ - **Action cards below.** Today's workout, next challenge, current activity. Each card is a CTA.
2647
+ - **Achievement shelf.** Recent badges, milestones, or personal records in a horizontal scroll.
2648
+ - **High contrast.** Bold colors, strong type hierarchy. Nothing is subtle.
2649
+
2650
+ Layout:
2651
+ \`\`\`
2652
+ \u250C\u2500 hero metric \u2500\u2500\u2500\u2500\u2500\u2500\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
2653
+ \u2502 \u{1F525} 2,847 cal \u2502
2654
+ \u2502 burned this week \u2502
2655
+ \u2502 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2591\u2591 85% of goal \u2502
2656
+ \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\u2524
2657
+ \u2502 \u250C\u2500 action grid (2 cols) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
2658
+ \u2502 \u2502 Today's Workout \u2502 Weekly Challenge \u2502 \u2502
2659
+ \u2502 \u2502 [Start] \u2502 [View] \u2502 \u2502
2660
+ \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
2661
+ \u2502 \u2502
2662
+ \u2502 \u250C\u2500 achievements (horizontal scroll) \u2500\u2500\u2510 \u2502
2663
+ \u2502 \u2502 \u{1F3C6} First 5K \u2502 \u2B50 7-Day Streak \u2502 \u2502
2664
+ \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
2665
+ \u2502 \u2502
2666
+ \u2502 \u250C\u2500 activity feed \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
2667
+ \u2502 \u2502 Recent activity / leaderboard \u2502 \u2502
2668
+ \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
2669
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
2670
+ \`\`\`
2671
+
2672
+ ### List / Activity Views
2673
+
2674
+ - Mixed card sizes: hero card for the featured item, smaller cards for the rest. Bento-style.
2675
+ - Large imagery when available (workout photos, game screenshots). Cards are visual-first, text-second.
2676
+ - Bold status indicators: completion percentages in large text, not small badges.
2677
+ - Leaderboard patterns: rank number large, user avatar, score right-aligned. Alternating row tints.
2678
+
2679
+ ### Detail Pages
2680
+
2681
+ - Full-bleed hero image or gradient at the top.
2682
+ - Stats row immediately below: duration, calories, reps, distance. Large numbers, small labels.
2683
+ - Timeline or activity log for the session.
2684
+ - Share button prominent. Social/competitive apps want sharing.
2685
+
2686
+ ### Forms / Input
2687
+
2688
+ - Quick-entry patterns. Workout logging should be fast: big number inputs, increment/decrement buttons, pre-set options.
2689
+ - Timer interfaces for active workouts: large centered timer, minimal distractions.
2690
+ - Rating systems: star ratings, difficulty selectors, mood sliders. Visual, not text inputs.
2691
+
2692
+ ## Component Patterns
2693
+
2694
+ ### Cards
2695
+
2696
+ Bold cards with strong presence:
2697
+
2698
+ \`\`\`
2699
+ bg-card rounded-2xl p-5 shadow-md border border-border/30
2700
+ \`\`\`
2701
+
2702
+ - Rounded: \`rounded-2xl\`. Friendly but substantial.
2703
+ - Shadow: \`shadow-md\` at rest. These cards have presence.
2704
+ - Optional gradient accent: a subtle gradient on the left border or top edge using primary color.
2705
+ - Hero cards: \`bg-gradient-to-br from-primary/10 to-primary/5\` for featured items.
2706
+
2707
+ ### Metrics and Stats
2708
+
2709
+ The core engagement element. Numbers should feel powerful:
2710
+
2711
+ - **Hero metric**: \`text-5xl md:text-6xl font-extrabold text-foreground\`. The biggest thing on the page.
2712
+ - **Progress bar**: thick (\`h-3\` or \`h-4\`), rounded-full, \`bg-primary\` fill over \`bg-muted/20\` track. Animated on mount.
2713
+ - **Circular progress**: large (120-160px) SVG ring with bold \`stroke-primary\`, animated fill. Number centered inside.
2714
+ - **Comparison stats**: before/after or goal/actual side by side. Use green for above target, primary for on track.
2715
+
2716
+ ### Achievement Badges
2717
+
2718
+ Unlockable achievements create engagement:
2719
+
2720
+ \`\`\`
2721
+ // Locked
2722
+ bg-muted/20 rounded-xl p-4 opacity-50
2723
+ icon: grayscale, 32px
2724
+ label: text-xs text-muted-foreground
2725
+
2726
+ // Unlocked
2727
+ bg-primary/10 rounded-xl p-4
2728
+ icon: primary color, 32px
2729
+ label: text-xs font-medium text-primary
2730
+ date: text-xs text-muted-foreground
2731
+ \`\`\`
2732
+
2733
+ - Horizontal scroll row or 3-column grid.
2734
+ - Locked badges are visible but dimmed. Users see what they're working toward.
2735
+ - Unlocked badges have a subtle \`ring-2 ring-primary/20\` glow.
2736
+
2737
+ ### Activity Feed
2738
+
2739
+ Social/competitive apps need an activity feed:
2740
+
2741
+ - Avatar (32px, rounded-full) + name + action + timestamp per row.
2742
+ - Action verb in \`font-medium\`: "completed," "achieved," "beat their record."
2743
+ - Compact spacing: \`gap-3\` between items. Dense is OK for feeds.
2744
+ - Pull-to-refresh indicator at top.
2745
+
2746
+ ### Navigation
2747
+
2748
+ - **Bottom tab bar** is primary (mobile-first). 4-5 tabs: Home, Activity, Log/Add, Profile, and one app-specific.
2749
+ - **Center FAB** for the primary action (Start Workout, Log Meal, etc.). Larger than other tabs, \`bg-primary rounded-full\`.
2750
+ - **Sidebar on desktop**: dark or primary-tinted sidebar. Active item is bold with a strong left accent.
2751
+
2752
+ ### Buttons
2753
+
2754
+ - Primary: \`bg-primary text-primary-foreground rounded-xl px-8 py-4 text-lg font-bold\`. BIG. These are action triggers.
2755
+ - Start/Go buttons: even larger. \`h-16 text-xl\`. The user should feel the button's importance.
2756
+ - Secondary: \`bg-muted text-foreground rounded-xl font-medium\`.
2757
+ - Icon buttons: \`rounded-full bg-primary/10 text-primary h-12 w-12\`. For quick actions (add, share, bookmark).
2758
+
2759
+ ## Spacing and Rhythm
2760
+
2761
+ - Page padding: \`p-4 md:p-6\`. Slightly tighter than warm apps to fit more content.
2762
+ - Card gaps: \`gap-3 md:gap-4\`.
2763
+ - Section gaps: \`gap-6\` between major sections.
2764
+ - Hero section: generous. \`py-8 md:py-12\`. The hero metric needs space.
2765
+ - Touch targets: minimum \`h-14\` for primary actions. Larger than standard.
2766
+
2767
+ ## Typography
2768
+
2769
+ - Headings: \`font-extrabold\` or \`font-black\`. Bold apps use bold type.
2770
+ - Hero metric: \`text-5xl md:text-6xl font-extrabold tracking-tight\`.
2771
+ - Body: \`text-sm\` with standard leading. Dense is OK.
2772
+ - Labels: \`text-xs uppercase tracking-wide text-muted-foreground\`. Athletic/competitive feel.
2773
+ - Action verbs: \`font-semibold text-primary\`. "Start Workout," "Log Meal."
2774
+
2775
+ ## Color Usage
2776
+
2777
+ - Primary color used generously: progress bars, hero metrics, active states, CTA buttons. This is not a restrained palette.
2778
+ - \`bg-primary/10\` for tinted surfaces (achievement cards, active items, highlighted rows).
2779
+ - Success green for "above target" or "personal record" indicators.
2780
+ - \`bg-gradient-to-br from-primary to-primary/80\` for hero sections and featured cards.
2781
+ - Dark sections OK for contrast: a dark hero metric section with light text, then back to light.
2782
+ - Red/destructive only for actual data loss. Use amber for warnings.
2783
+
2784
+ ## Motion
2785
+
2786
+ - **Progress fill**: animated width from 0 to target over 600ms, ease-out. Satisfying.
2787
+ - **Achievement unlock**: scale 0.8 -> 1.1 -> 1 with spring bounce + subtle confetti particles. 500ms.
2788
+ - **Metric counter**: NumberFlow counting up from 0 to the value. 800ms.
2789
+ - **Card entrance**: staggered fade-up with spring physics. Snappy, not gentle.
2790
+ - **Tab switch**: slide left/right (not just fade). 200ms.
2791
+ - **Pull-to-refresh**: elastic overscroll with bounce-back.
2792
+
2793
+ ## Anti-Patterns
2794
+
2795
+ - Muted, desaturated palette. Bold apps need bold color.
2796
+ - Small metrics. If the number matters, make it huge.
2797
+ - Gentle, slow animations. These apps are energetic.
2798
+ - Serif fonts. Too editorial for athletic/gaming context.
2799
+ - Card-only layouts with no hierarchy. Use hero cards + supporting cards.
2800
+ - Missing achievement/progress systems. The gamification IS the product.
2801
+ - Light-touch buttons. CTAs should be unmissable.
2802
+ `});var ks,xs=S(()=>{ks=`# Professional Clean Archetype
2803
+
2804
+ Component-level design guidance for service and appointment-based apps. Booking systems, clinics, salons, real estate, consulting, restaurants, event management.
2805
+
2806
+ These apps serve CLIENTS. The UI must feel trustworthy, organized, and efficient. Clients judge the business by the app's polish.
2807
+
2808
+ ---
2809
+
2810
+ ## Page Composition
2811
+
2812
+ ### Dashboard / Home
2813
+
2814
+ The dashboard is a control center. The operator needs to see today's schedule, pending items, and key metrics at a glance.
2815
+
2816
+ - **Today's schedule** as the primary view. Upcoming appointments/bookings in a time-ordered list or mini calendar.
2817
+ - **Action items** count: pending confirmations, unread messages, incomplete tasks. Numbered badges.
2818
+ - **Quick actions**: "New Booking," "Walk-in," "Block Time." Accessible without scrolling.
2819
+ - **Key metrics row**: today's bookings, revenue, utilization percentage. Small, factual, not flashy.
2820
+
2821
+ Layout:
2822
+ \`\`\`
2823
+ \u250C\u2500 header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
2824
+ \u2502 Today, April 17 [+ New Booking] \u2502
2825
+ \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\u2524
2826
+ \u2502 \u250C\u2500 metrics row (3-4 cols) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
2827
+ \u2502 \u2502 8 Bookings \u2502 $1,240 \u2502 85% Utilized \u2502 \u2502
2828
+ \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
2829
+ \u2502 \u2502
2830
+ \u2502 \u250C\u2500 schedule (time-ordered) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
2831
+ \u2502 \u2502 9:00 Sarah M. \u2014 Haircut (45m) \u2502 \u2502
2832
+ \u2502 \u2502 10:00 David K. \u2014 Consultation \u2502 \u2502
2833
+ \u2502 \u2502 11:30 [Available] \u2502 \u2502
2834
+ \u2502 \u2502 13:00 Emma L. \u2014 Follow-up \u2502 \u2502
2835
+ \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
2836
+ \u2502 \u2502
2837
+ \u2502 \u250C\u2500 pending actions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
2838
+ \u2502 \u2502 2 unconfirmed \u2502 1 message \u2502 1 task \u2502 \u2502
2839
+ \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
2840
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
2841
+ \`\`\`
2842
+
2843
+ ### Calendar View
2844
+
2845
+ Central to booking apps. Must be well-built:
2846
+
2847
+ - Week view as default (day view on mobile). Month view for overview.
2848
+ - Time slots as rows, resources/staff as columns (if multi-staff).
2849
+ - Booked slots: filled cards with client name, service, duration. Color-coded by service type or status.
2850
+ - Available slots: subtle background, clickable to book.
2851
+ - Current time indicator: horizontal line at current time, \`border-primary\`.
2852
+
2853
+ ### Client / Contact List
2854
+
2855
+ - DataTable with proper columns: name, phone/email, last visit, next appointment, total visits.
2856
+ - Avatar with initials (deterministic color from name) in the first column.
2857
+ - Search bar above: instant filter by name, phone, or email.
2858
+ - Click row to open client detail (sheet or page).
2859
+ - Sort by last visit date by default (most recent first).
2860
+
2861
+ ### Booking / Appointment Detail
2862
+
2863
+ - Client info card at top: name, phone, email, visit history count.
2864
+ - Appointment details: date, time, duration, service, assigned staff, notes.
2865
+ - Status workflow: Pending -> Confirmed -> Completed (or Cancelled/No-show). Status as a prominent badge.
2866
+ - Action buttons: Confirm, Reschedule, Cancel. Grouped in a row.
2867
+ - Notes section: editable textarea for appointment notes.
2868
+
2869
+ ### Forms
2870
+
2871
+ - Structured, clear, professional. Labels above inputs.
2872
+ - Date/time pickers for appointment scheduling. Use shadcn Calendar + time select.
2873
+ - Service selection: radio cards or a dropdown with service name + duration + price.
2874
+ - Client search: autocomplete that searches existing clients, with "New Client" option.
2875
+ - Validation messages are specific: "Phone number must be 10 digits" not "Invalid input."
2876
+
2877
+ ## Component Patterns
2878
+
2879
+ ### Cards
2880
+
2881
+ Clean, professional cards with clear boundaries:
2882
+
2883
+ \`\`\`
2884
+ bg-card rounded-lg p-4 border border-border shadow-sm
2885
+ hover: shadow-md transition-shadow (for clickable)
2886
+ \`\`\`
2887
+
2888
+ - Rounded: \`rounded-lg\` (not too rounded, not sharp). Professional balance.
2889
+ - Border: visible \`border-border\`. Clear structure.
2890
+ - Shadow: \`shadow-sm\` at rest. Professional, not dramatic.
2891
+ - No gradient backgrounds on cards. Solid fills only.
2892
+
2893
+ ### Schedule / Time Slots
2894
+
2895
+ The core UI pattern for booking apps:
2896
+
2897
+ \`\`\`
2898
+ // Booked slot
2899
+ bg-primary/5 border-l-4 border-l-primary rounded-lg p-3
2900
+ client name: font-medium
2901
+ service: text-sm text-muted-foreground
2902
+ time: text-sm tabular-nums
2903
+
2904
+ // Available slot
2905
+ bg-muted/10 border border-dashed border-border/50 rounded-lg p-3
2906
+ "Available" in text-muted-foreground
2907
+ click to book
2908
+
2909
+ // Blocked/break
2910
+ bg-muted/30 rounded-lg p-3
2911
+ strikethrough or "Blocked" label
2912
+ \`\`\`
2913
+
2914
+ - Left border color indicates service category or status.
2915
+ - Compact spacing within slots: \`p-3\`. Dense but readable.
2916
+ - Time always in \`tabular-nums\` for alignment.
2917
+
2918
+ ### Status Badges
2919
+
2920
+ Appointment/booking status needs clear visual language:
2921
+
2922
+ \`\`\`
2923
+ Pending: bg-amber/10 text-amber-700 border border-amber/20
2924
+ Confirmed: bg-primary/10 text-primary border border-primary/20
2925
+ Completed: bg-emerald/10 text-emerald-700 border border-emerald/20
2926
+ Cancelled: bg-muted text-muted-foreground border border-border
2927
+ No-show: bg-destructive/10 text-destructive border border-destructive/20
2928
+ \`\`\`
2929
+
2930
+ - Always \`rounded-full px-3 py-1 text-xs font-medium\`.
2931
+ - Border adds definition. Not just background tint.
2932
+
2933
+ ### Data Tables
2934
+
2935
+ Professional apps need real tables (unlike consumer-warm which uses cards):
2936
+
2937
+ - shadcn DataTable with sticky header.
2938
+ - Alternating row tints: \`even:bg-muted/20\`.
2939
+ - Row hover: \`hover:bg-muted/30\`.
2940
+ - Compact rows: \`py-2.5 px-4\`. Professional density.
2941
+ - Actions column: three-dot dropdown menu per row.
2942
+ - Sortable columns with sort indicator icons.
2943
+ - Pagination at bottom with page size selector.
2944
+
2945
+ ### Client Avatars
2946
+
2947
+ Deterministic color avatars for clients without photos:
2948
+
2949
+ \`\`\`
2950
+ h-10 w-10 rounded-full bg-[deterministic-color] flex items-center justify-center
2951
+ text-white text-sm font-medium
2952
+ initials from first + last name
2953
+ \`\`\`
2954
+
2955
+ - Color derived from name hash. Consistent across views.
2956
+ - 6-8 muted colors in the rotation (not primary-only).
2957
+
2958
+ ### Navigation
2959
+
2960
+ - Sidebar with clear sections: Schedule, Clients, Services, Settings.
2961
+ - Active item: \`bg-primary/10 text-primary font-medium\` with rounded corners.
2962
+ - Collapse to icon-only on tablet. Full sidebar on desktop.
2963
+ - No bottom tab bar (this is a desktop-first management app).
2964
+
2965
+ ### Buttons
2966
+
2967
+ - Primary: \`bg-primary text-primary-foreground rounded-lg px-5 py-2.5\`. Clean, professional.
2968
+ - Secondary: \`border border-border bg-background text-foreground rounded-lg\`. Outline style.
2969
+ - Destructive (cancel, delete): \`text-destructive hover:bg-destructive/10 rounded-lg\`. Text-only until hover.
2970
+ - Button groups: related actions (Confirm / Reschedule / Cancel) in a flex row with \`gap-2\`.
2971
+
2972
+ ## Spacing and Rhythm
2973
+
2974
+ - Page padding: \`p-4 md:p-6\`. Efficient.
2975
+ - Card gaps: \`gap-3 md:gap-4\`.
2976
+ - Table cell padding: \`px-4 py-2.5\`. Compact but readable.
2977
+ - Form field gaps: \`space-y-4\`.
2978
+ - Section gaps: \`gap-6\`.
2979
+
2980
+ ## Typography
2981
+
2982
+ - Headings: \`font-semibold\`. Professional weight, not bold.
2983
+ - Body: \`text-sm leading-normal\`. Efficient for data-dense views.
2984
+ - Client names: \`font-medium text-base\`. Slightly emphasized.
2985
+ - Metadata (dates, times, IDs): \`text-xs text-muted-foreground tabular-nums\`.
2986
+ - Prices/amounts: \`font-medium tabular-nums\`. Right-aligned in tables.
2987
+
2988
+ ## Color Usage
2989
+
2990
+ - Primary color used sparingly: active navigation, booked slots, confirm buttons, selected dates.
2991
+ - Status colors (amber, emerald, destructive) are semantic. Use them consistently.
2992
+ - \`bg-muted/10\` to \`bg-muted/30\` for surface variation. Professional apps use neutral tints.
2993
+ - No gradients. Solid colors only. Gradients are too casual for professional apps.
2994
+ - White/light default theme. Dark mode is optional but not default.
2995
+
2996
+ ## Motion
2997
+
2998
+ - **Page transitions**: \`PageTransition\` with subtle fade-up (y: 6px, 200ms). Quick, not showy.
2999
+ - **Schedule slot creation**: new slot fades in at its time position. 200ms.
3000
+ - **Status change**: badge color crossfades. 150ms. Instant-feeling.
3001
+ - **Sidebar collapse**: width transition 200ms. Clean resize.
3002
+ - **Modal/sheet open**: slide up from bottom. 250ms.
3003
+ - NO staggered card grids. NO bounce. NO spring physics. Professional = precise.
3004
+
3005
+ ## Anti-Patterns
3006
+
3007
+ - Rounded pill buttons or playful shapes. Too casual for client-facing.
3008
+ - Achievement badges or gamification. This is a work tool.
3009
+ - Large hero metrics. Professionals want data density, not scoreboard displays.
3010
+ - Card-only layouts for tabular data. Use DataTable for client lists, transaction logs.
3011
+ - Missing status workflows. Every booking/appointment needs a clear state machine.
3012
+ - Dark theme as default. Clients associate light themes with professionalism.
3013
+ - Emoji in the UI. Professional context.
3014
+ `});var Ts,Ss=S(()=>{Ts=`# Education Structured Archetype
3015
+
3016
+ Component-level design guidance for learning, education, and knowledge apps. Course platforms, quiz apps, flashcards, LMS, student portals, tutorial sites, documentation tools.
3017
+
3018
+ These apps are about UNDERSTANDING. The UI must reduce cognitive load, make progress visible, and keep learners oriented.
3019
+
3020
+ ---
3021
+
3022
+ ## Page Composition
3023
+
3024
+ ### Dashboard / Home
3025
+
3026
+ The dashboard shows learning progress and surfaces the next thing to do.
3027
+
3028
+ - **Continue where you left off.** The in-progress course/lesson with a progress bar and "Continue" button. This is the #1 action.
3029
+ - **Progress overview.** Courses completed, current streak, time spent this week. Small stats, not hero metrics.
3030
+ - **Recommended next.** 2-3 suggested courses or lessons based on what the user has completed.
3031
+ - **Achievement summary.** Completed courses, certificates earned. Subtle, not gamified.
3032
+
3033
+ Layout:
3034
+ \`\`\`
3035
+ \u250C\u2500 greeting \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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
3036
+ \u2502 Welcome back, Alex \u2502
3037
+ \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\u2524
3038
+ \u2502 \u250C\u2500 continue learning (hero card) \u2500\u2500\u2500\u2500\u2500\u2510 \u2502
3039
+ \u2502 \u2502 Course: Intro to Design \u2502 \u2502
3040
+ \u2502 \u2502 Lesson 4 of 12 \u2014 "Color Theory" \u2502 \u2502
3041
+ \u2502 \u2502 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2591\u2591\u2591\u2591 33% [Continue \u2192] \u2502 \u2502
3042
+ \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
3043
+ \u2502 \u2502
3044
+ \u2502 \u250C\u2500 stats row \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
3045
+ \u2502 \u2502 3 completed \u2502 12h learned \u2502 5 streak\u2502 \u2502
3046
+ \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
3047
+ \u2502 \u2502
3048
+ \u2502 \u250C\u2500 recommended (card grid) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
3049
+ \u2502 \u2502 [Course Card] [Course Card] \u2502 \u2502
3050
+ \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
3051
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
3052
+ \`\`\`
3053
+
3054
+ ### Course / Module View
3055
+
3056
+ - Clear hierarchy: Course > Module > Lesson. Breadcrumb navigation at the top.
3057
+ - Lesson list as a vertical checklist: completed lessons have checkmarks, current lesson is highlighted, future lessons are available but not emphasized.
3058
+ - Module progress bar at the top of each module section.
3059
+ - Estimated time per lesson: "~5 min read" or "~10 min video."
3060
+
3061
+ ### Lesson / Content Page
3062
+
3063
+ The most important page. This is where learning happens.
3064
+
3065
+ - **Content area**: max-width \`max-w-3xl\`, centered. Optimized for reading.
3066
+ - **Typography**: large body text (\`text-base leading-relaxed\` or \`text-lg leading-relaxed\`). Reading is the product.
3067
+ - **Code blocks**: if relevant, use syntax-highlighted code with copy button.
3068
+ - **Images/diagrams**: full content-width, rounded corners, subtle border.
3069
+ - **Callout boxes**: for tips, warnings, key concepts. Tinted backgrounds with icon.
3070
+ - **Navigation footer**: Previous Lesson / Next Lesson buttons. Always visible.
3071
+ - **Progress indicator**: thin bar at the very top of the page showing scroll progress.
3072
+
3073
+ ### Quiz / Assessment
3074
+
3075
+ - One question per view (not all questions on one page). Reduces overwhelm.
3076
+ - Large, tappable answer options as cards (not radio buttons).
3077
+ - Immediate feedback: green flash for correct, red for incorrect, with explanation.
3078
+ - Score summary at the end with breakdown per question.
3079
+
3080
+ ## Component Patterns
3081
+
3082
+ ### Cards
3083
+
3084
+ Education cards are informational, not decorative:
3085
+
3086
+ \`\`\`
3087
+ bg-card rounded-lg p-5 border border-border/50 shadow-sm
3088
+ \`\`\`
3089
+
3090
+ - Clean borders over shadows. Structure over depth.
3091
+ - Course cards: thumbnail image (top), title, description, progress bar (bottom).
3092
+ - Lesson items: list style, not cards. \`border-b border-border/30 py-3\`.
3093
+
3094
+ ### Progress Indicators
3095
+
3096
+ Central to education apps. Multiple levels of progress:
3097
+
3098
+ \`\`\`
3099
+ // Course progress
3100
+ h-2 rounded-full bg-muted/20
3101
+ fill: bg-primary rounded-full (animated width)
3102
+ label above: "4 of 12 lessons" text-sm text-muted-foreground
3103
+
3104
+ // Lesson checklist
3105
+ checkmark circle (bg-primary text-white) for completed
3106
+ numbered circle (border-primary text-primary) for current
3107
+ gray circle (bg-muted/30 text-muted-foreground) for future
3108
+ vertical line connecting circles (border-l-2 border-border/30)
3109
+ \`\`\`
3110
+
3111
+ - Checklist style for lesson sequences. Visual progress through a vertical timeline.
3112
+ - Progress bars for overall course completion. Thin, subtle.
3113
+ - Completion percentage next to the progress bar in \`text-sm text-muted-foreground\`.
3114
+
3115
+ ### Callout Boxes
3116
+
3117
+ For tips, key concepts, and warnings within content:
3118
+
3119
+ \`\`\`
3120
+ // Tip
3121
+ bg-primary/5 border-l-4 border-l-primary rounded-r-lg p-4
3122
+ icon: Lightbulb (text-primary)
3123
+ label: "Tip" font-medium text-primary text-sm
3124
+ content: text-sm
3125
+
3126
+ // Warning
3127
+ bg-amber/5 border-l-4 border-l-amber-500 rounded-r-lg p-4
3128
+ icon: AlertTriangle (text-amber-500)
3129
+
3130
+ // Key concept
3131
+ bg-muted/30 border-l-4 border-l-foreground/30 rounded-r-lg p-4
3132
+ icon: BookOpen (text-foreground/60)
3133
+ \`\`\`
3134
+
3135
+ ### Quiz Answer Cards
3136
+
3137
+ Large, tappable, clear:
3138
+
3139
+ \`\`\`
3140
+ // Default
3141
+ border border-border rounded-xl p-4 cursor-pointer
3142
+ hover: bg-muted/20
3143
+ letter: A/B/C/D in a circle (bg-muted/20 text-foreground h-8 w-8 rounded-full)
3144
+ answer text: text-base
3145
+
3146
+ // Selected
3147
+ border-2 border-primary bg-primary/5 rounded-xl p-4
3148
+
3149
+ // Correct
3150
+ border-2 border-emerald-500 bg-emerald-50 rounded-xl p-4
3151
+
3152
+ // Incorrect
3153
+ border-2 border-destructive bg-destructive/5 rounded-xl p-4
3154
+ \`\`\`
3155
+
3156
+ ### Navigation
3157
+
3158
+ - Sidebar with course outline (collapsible modules with lesson lists).
3159
+ - Breadcrumbs at the top of every page: Course > Module > Lesson.
3160
+ - Next/Previous navigation at the bottom of lesson content.
3161
+ - Sidebar active lesson: \`bg-primary/10 text-primary font-medium\`.
3162
+
3163
+ ### Buttons
3164
+
3165
+ - Primary: \`bg-primary text-primary-foreground rounded-lg px-5 py-2.5\`. Standard, clean.
3166
+ - "Continue" button: primary, slightly larger. \`px-8 py-3\`. It's the main CTA.
3167
+ - "Next Lesson" / "Previous Lesson": secondary, with arrow icons. \`gap-2\`.
3168
+ - Quiz submit: primary, full-width on mobile. \`w-full md:w-auto\`.
3169
+
3170
+ ## Spacing and Rhythm
3171
+
3172
+ - Content width: \`max-w-3xl mx-auto\` for lesson content. Readable line length.
3173
+ - Page padding: \`p-5 md:p-8\`.
3174
+ - Content paragraph spacing: \`space-y-4\` between paragraphs.
3175
+ - Heading spacing: \`mt-8 mb-4\` above headings within content.
3176
+ - Card grid gaps: \`gap-4 md:gap-6\`.
3177
+ - Quiz option gaps: \`gap-3\`.
3178
+
3179
+ ## Typography
3180
+
3181
+ - Lesson content: \`text-base md:text-lg leading-relaxed\`. Optimized for extended reading.
3182
+ - Headings within content: \`text-xl md:text-2xl font-semibold mt-8 mb-4\`. Clear hierarchy.
3183
+ - Course titles: \`text-lg font-semibold\`.
3184
+ - Metadata (duration, lesson count): \`text-sm text-muted-foreground\`.
3185
+ - Code: monospace, \`text-sm\`, syntax-highlighted. Copy button in top-right corner.
3186
+
3187
+ ## Color Usage
3188
+
3189
+ - Primary for progress indicators, active lesson, CTAs.
3190
+ - \`bg-primary/5\` for current lesson highlight and selected quiz answers.
3191
+ - Semantic callout colors: primary for tips, amber for warnings, muted for key concepts.
3192
+ - Emerald for correct answers. Destructive for incorrect.
3193
+ - Neutral, calm backgrounds. Education apps should not be visually stimulating. The content is the stimulation.
3194
+ - No gradients in the app UI. Clean, flat surfaces.
3195
+
3196
+ ## Motion
3197
+
3198
+ - **Progress bar fill**: animated width, 400ms, ease-out. Satisfying on lesson complete.
3199
+ - **Quiz feedback**: correct/incorrect card border + background transition, 200ms. Immediate.
3200
+ - **Lesson complete checkmark**: scale 0 -> 1 with slight overshoot, 300ms.
3201
+ - **Page content**: fade-in only (opacity, 200ms). No vertical movement. Content should feel stable.
3202
+ - **Sidebar expand/collapse**: height transition, 200ms.
3203
+ - Minimal motion overall. Reduce distraction. Focus is on the content.
3204
+
3205
+ ## Anti-Patterns
3206
+
3207
+ - Gamification overload (badges, XP, leaderboards). Subtle progress is better for learning.
3208
+ - Dense data tables. Education is not enterprise.
3209
+ - Dark theme as default. Light theme reduces eye strain for reading.
3210
+ - Small body text. Reading is the product. \`text-base\` minimum.
3211
+ - Hero metrics. "2,847 XP" is gaming, not learning. Use "4 of 12 lessons" instead.
3212
+ - Long lesson pages with no progress indicator. Users need to know where they are.
3213
+ - Multiple competing elements per view. One thing at a time. One question at a time.
3214
+ `});var Is,Ps=S(()=>{Is=`# Marketplace Browse Archetype
3215
+
3216
+ Component-level design guidance for browsing, listing, and shopping apps. Marketplaces, directories, shops, classifieds, rental platforms, food delivery, product catalogs.
3217
+
3218
+ These apps are about FINDING and CHOOSING. The UI must make browsing fast, comparison easy, and trust obvious.
3219
+
3220
+ ---
3221
+
3222
+ ## Page Composition
3223
+
3224
+ ### Home / Browse
3225
+
3226
+ The home page is a discovery surface. Users are looking, not managing.
3227
+
3228
+ - **Search bar prominent.** Top of the page, large, with placeholder text specific to the domain ("Search 2,000+ recipes" not "Search"). Optional filter pills below.
3229
+ - **Featured/hero section.** 1-3 highlighted items in a carousel or large card. Editor's picks, trending, seasonal.
3230
+ - **Category navigation.** Horizontal scroll pills or icon grid for quick category filtering.
3231
+ - **Item grid.** The main content. Product/listing cards in a responsive grid.
3232
+
3233
+ Layout:
3234
+ \`\`\`
3235
+ \u250C\u2500 search bar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\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
3236
+ \u2502 \u{1F50D} Search restaurants near you... \u2502
3237
+ \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\u2524
3238
+ \u2502 [All] [Pizza] [Sushi] [Mexican] [Thai] \u2192 \u2502
3239
+ \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\u2524
3240
+ \u2502 \u250C\u2500 featured (large card / carousel) \u2500\u2500\u2510 \u2502
3241
+ \u2502 \u2502 \u{1F31F} Featured: Joe's Pizza \u2502 \u2502
3242
+ \u2502 \u2502 "Best pizza in downtown" \u2502 \u2502
3243
+ \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
3244
+ \u2502 \u2502
3245
+ \u2502 \u250C\u2500 grid (2-3 cols) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
3246
+ \u2502 \u2502 [Card] [Card] [Card] \u2502 \u2502
3247
+ \u2502 \u2502 [Card] [Card] [Card] \u2502 \u2502
3248
+ \u2502 \u2502 [Card] [Card] [Card] \u2502 \u2502
3249
+ \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
3250
+ \u2502 \u2502
3251
+ \u2502 [Load more] or infinite scroll \u2502
3252
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
3253
+ \`\`\`
3254
+
3255
+ ### Search Results / Filtered View
3256
+
3257
+ - Same grid layout as home but with active filters shown.
3258
+ - Result count: "24 results for 'pizza'" at the top.
3259
+ - Sort options: "Recommended / Price: Low-High / Newest / Rating" in a select or pill group.
3260
+ - Filter sidebar on desktop (collapsible on mobile): price range, rating, category, location.
3261
+ - Grid/list view toggle if both make sense for the content type.
3262
+
3263
+ ### Item Detail Page
3264
+
3265
+ - Large image gallery (swipeable on mobile, thumbnails on desktop).
3266
+ - Title + price/rate prominently placed.
3267
+ - Rating + review count: \`\u2605 4.8 (234 reviews)\`.
3268
+ - Key attributes in a structured list or tag row (size, color, condition, location).
3269
+ - Description section with expandable "Read more."
3270
+ - Seller/provider info card: name, avatar, rating, "View profile."
3271
+ - CTA button: "Add to Cart" / "Book Now" / "Contact Seller." Sticky on mobile.
3272
+ - Reviews section below: recent reviews with rating, name, date, text.
3273
+
3274
+ ### Cart / Checkout (if applicable)
3275
+
3276
+ - Item list with quantity controls (- / count / +).
3277
+ - Running total visible at all times.
3278
+ - Minimal form for checkout: address, payment. Fewest fields possible.
3279
+ - Order summary sidebar on desktop, sticky bottom bar on mobile.
3280
+
3281
+ ## Component Patterns
3282
+
3283
+ ### Product / Listing Cards
3284
+
3285
+ The primary UI element. Must be fast to scan:
3286
+
3287
+ \`\`\`
3288
+ bg-card rounded-xl overflow-hidden border border-border/30 shadow-sm
3289
+ hover: shadow-md -translate-y-0.5 transition-all
3290
+
3291
+ image: aspect-[4/3] object-cover (top)
3292
+ body: p-4
3293
+ title: font-medium text-base line-clamp-2
3294
+ subtitle: text-sm text-muted-foreground (location, category)
3295
+ price: font-semibold text-lg (or text-primary for emphasis)
3296
+ rating: \u2605 text-amber-500 text-sm + review count
3297
+ tags: small pills for attributes
3298
+ \`\`\`
3299
+
3300
+ - Image always on top. \`aspect-[4/3]\` for consistency in grids.
3301
+ - \`line-clamp-2\` on titles. Prevents layout breaking with long names.
3302
+ - Price is prominent: \`font-semibold\` or \`text-lg\`. If price varies, show range.
3303
+ - Rating inline with stars: amber/gold star icon + numeric rating + "(N reviews)."
3304
+
3305
+ ### Search Bar
3306
+
3307
+ Large, inviting, domain-specific:
3308
+
3309
+ \`\`\`
3310
+ bg-card border border-border rounded-xl px-4 py-3 shadow-sm
3311
+ icon: Search (text-muted-foreground) left
3312
+ input: text-base placeholder-muted-foreground
3313
+ clear button: X icon, appears when text is entered
3314
+ submit: optional, or search-on-type with debounce
3315
+ \`\`\`
3316
+
3317
+ - Full width on mobile. \`max-w-2xl\` on desktop.
3318
+ - Placeholder text is specific: "Search 500+ listings" not "Search..."
3319
+ - Optional: recent searches dropdown on focus.
3320
+
3321
+ ### Filter Pills
3322
+
3323
+ Horizontal scrollable category/filter selectors:
3324
+
3325
+ \`\`\`
3326
+ // Inactive
3327
+ bg-muted/20 text-foreground rounded-full px-4 py-2 text-sm
3328
+ hover: bg-muted/40
3329
+
3330
+ // Active
3331
+ bg-primary text-primary-foreground rounded-full px-4 py-2 text-sm font-medium
3332
+ \`\`\`
3333
+
3334
+ - Horizontal scroll with \`overflow-x-auto\` and hidden scrollbar.
3335
+ - Single-select for categories. Multi-select for attributes (with checkmarks).
3336
+
3337
+ ### Rating Display
3338
+
3339
+ Consistent rating pattern throughout:
3340
+
3341
+ \`\`\`
3342
+ inline-flex items-center gap-1
3343
+ star icon: h-4 w-4 text-amber-500 fill-amber-500
3344
+ rating: font-medium text-sm "4.8"
3345
+ count: text-muted-foreground text-sm "(234)"
3346
+ \`\`\`
3347
+
3348
+ - Filled stars for the integer part, half-star if applicable, empty for remainder.
3349
+ - Or simplified: one star icon + numeric rating + count. Cleaner for cards.
3350
+
3351
+ ### Price Display
3352
+
3353
+ \`\`\`
3354
+ // Standard
3355
+ text-lg font-semibold
3356
+
3357
+ // Sale/discount
3358
+ original: text-sm text-muted-foreground line-through
3359
+ sale: text-lg font-semibold text-primary
3360
+ badge: bg-primary/10 text-primary rounded-full px-2 py-0.5 text-xs "20% off"
3361
+
3362
+ // Range
3363
+ text-base font-medium "$50 \u2014 $120"
3364
+
3365
+ // Per-unit
3366
+ text-lg font-semibold + text-sm text-muted-foreground "/night" or "/kg"
3367
+ \`\`\`
3368
+
3369
+ ### Review Cards
3370
+
3371
+ \`\`\`
3372
+ border-b border-border/30 py-4
3373
+ header: avatar (h-8 w-8 rounded-full) + name (font-medium text-sm) + date (text-xs text-muted-foreground)
3374
+ rating: star row below name
3375
+ text: text-sm leading-relaxed mt-2
3376
+ helpful: "Was this helpful? \u{1F44D} 12" text-xs text-muted-foreground mt-2
3377
+ \`\`\`
3378
+
3379
+ ### Navigation
3380
+
3381
+ - **Top bar**: logo left, search center (or search icon that expands), cart/profile right.
3382
+ - **Category tabs**: horizontal scroll below the top bar. Persistent while browsing.
3383
+ - **Bottom tab bar (mobile)**: Home, Search, Cart/Favorites, Profile. 4 tabs max.
3384
+ - **Sidebar filters (desktop)**: left side, collapsible, with checkboxes and range sliders.
3385
+
3386
+ ### Buttons
3387
+
3388
+ - Primary CTA: \`bg-primary text-primary-foreground rounded-xl px-6 py-3 font-medium\`. "Add to Cart," "Book Now."
3389
+ - Secondary: \`border border-border rounded-xl px-4 py-2.5\`. "Save," "Share."
3390
+ - Quantity controls: \`h-10 w-10 rounded-lg border border-border\` for +/- buttons, number between.
3391
+ - Sticky CTA on mobile: fixed bottom bar with price + primary button. \`bg-background border-t border-border p-4\`.
3392
+
3393
+ ## Spacing and Rhythm
3394
+
3395
+ - Page padding: \`px-4 md:px-6 lg:px-8\`.
3396
+ - Card grid: \`grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 md:gap-5\`.
3397
+ - Card internal: \`p-4\`. Compact but scannable.
3398
+ - Section gaps: \`gap-6 md:gap-8\`.
3399
+ - Search bar height: \`h-12 md:h-14\`. Prominent.
3400
+
3401
+ ## Typography
3402
+
3403
+ - Product titles: \`font-medium text-base line-clamp-2\`. Scannable.
3404
+ - Prices: \`font-semibold text-lg\`. The most important number.
3405
+ - Body/descriptions: \`text-sm leading-relaxed text-muted-foreground\`.
3406
+ - Category labels: \`text-sm font-medium\`.
3407
+ - Review text: \`text-sm leading-relaxed\`.
3408
+ - Metadata: \`text-xs text-muted-foreground\`. Dates, counts, locations.
3409
+
3410
+ ## Color Usage
3411
+
3412
+ - Primary for CTAs (Add to Cart, Book), active filters, selected states.
3413
+ - Amber/gold for ratings (star icons). Universal association.
3414
+ - \`bg-primary/10\` for sale badges and promotional highlights.
3415
+ - Neutral card backgrounds. Let the product images provide the color.
3416
+ - Red/destructive for price drops or sale indicators (contextual, not danger).
3417
+ - Avoid tinting cards with the primary color. Product images should be the visual focus.
3418
+
3419
+ ## Motion
3420
+
3421
+ - **Card hover**: lift \`-translate-y-0.5\` + \`shadow-md\`. 150ms. Subtle but responsive.
3422
+ - **Grid load**: staggered fade-in for card grid, 0.03s per card. Feels like content flowing in.
3423
+ - **Image gallery**: swipe/slide transition, 200ms. Snappy.
3424
+ - **Filter toggle**: pill scales slightly on select (1 -> 1.05 -> 1), 150ms.
3425
+ - **Add to cart**: button pulses briefly + cart icon counter increments with bounce.
3426
+ - **Infinite scroll**: new cards fade-in at the bottom. No jarring layout shift.
3427
+
3428
+ ## Anti-Patterns
3429
+
3430
+ - Missing search. Browse apps need search. Always.
3431
+ - Inconsistent card heights. Use \`aspect-ratio\` on images and \`line-clamp\` on titles.
3432
+ - No rating display. Trust signals are mandatory for marketplace apps.
3433
+ - Filters that require a page reload. Instant filtering or nothing.
3434
+ - Missing price or burying it below the fold. Price is always visible on cards.
3435
+ - Small product images. The image is the primary decision-making element.
3436
+ - No empty state for zero search results. "No results for 'xyz'. Try broader terms."
3437
+ - Desktop-only filter sidebar without mobile equivalent. Use a slide-out sheet on mobile.
3438
+ `});var As,Cs=S(()=>{As=`# SaaS Analytical Archetype
3439
+
3440
+ 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.
3441
+
3442
+ These apps are where WORK HAPPENS. The user has the product open 4+ hours a day. The UI must feel efficient, dense-but-scannable, and professional. Linear, Notion, Vercel, Stripe, Posthog are the reference bar.
3443
+
3444
+ ---
3445
+
3446
+ ## Theme
3447
+
3448
+ Default: **either** \u2014 follows the app's \`design.tone\`. \`clean-saas\` and \`dense-professional\` lean light (Notion, Stripe), \`dark-first\` leans dark (Linear, Vercel, Posthog). All CSS in this archetype uses semantic tokens (\`bg-background\`, \`text-foreground\`, \`border-border\`) so the visual flips cleanly when the theme flips. Never hardcode hex values.
3449
+
3450
+ ---
3451
+
3452
+ ## Page Composition
3453
+
3454
+ ### Dashboard / Overview
3455
+
3456
+ The dashboard is a scoreboard AND an action surface. Users want to know the state of their world and complete the next action without navigating.
3457
+
3458
+ - **KPI strip at the top.** 3\u20134 key metrics in a horizontal row. Each metric: big number + delta vs last period + sparkline. \`tabular-nums\` for all numbers.
3459
+ - **Primary workspace panel.** The main thing the user does: pipeline, task list, recent activity feed, chart. Takes 60-70% of the grid.
3460
+ - **Secondary rail on the right.** Recent activity, notifications, or quick-add form. 30-40% width, sticky on scroll.
3461
+ - **Density over decoration.** Small padding (\`p-4\` on cards), compact typography (\`text-sm\` body, \`text-xs\` metadata), generous information per pixel.
3462
+
3463
+ Layout:
3464
+ \`\`\`
3465
+ \u250C\u2500 header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
3466
+ \u2502 Overview [+ Quick Action] \u2502
3467
+ \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
3468
+ \u2502 \u250C\u2500 KPI strip (4 cols) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
3469
+ \u2502 \u2502 $47.2k 1,847 92% 23 min \u2502 \u2502
3470
+ \u2502 \u2502 +12% +8% +2% -15% \u2502 \u2502
3471
+ \u2502 \u2502 MRR Users Uptime Response \u2502 \u2502
3472
+ \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
3473
+ \u2502 \u250C\u2500 main panel (60%) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500 right rail \u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
3474
+ \u2502 \u2502 Revenue chart / pipeline \u2502 Recent activity \u2502 \u2502
3475
+ \u2502 \u2502 [line chart w/ axis] \u2502 \xB7 Deal closed \u2502 \u2502
3476
+ \u2502 \u2502 \u2502 \xB7 New signup \u2502 \u2502
3477
+ \u2502 \u2502 \u2502 \xB7 Invoice paid \u2502 \u2502
3478
+ \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
3479
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
3480
+ \`\`\`
3481
+
3482
+ ### Data Tables
3483
+
3484
+ The core UI pattern. Most B2B apps are "table + filters + detail pane" under the hood.
3485
+
3486
+ - **shadcn DataTable with sorting, filtering, pagination.** Not a plain \`<table>\`.
3487
+ - **Checkbox selection column on the left** for bulk actions.
3488
+ - **Status badges** in their own column: \`variant="secondary"\` for neutral, custom Tailwind for brand states.
3489
+ - **Row hover: \`bg-muted/40\`.** Click-through opens a detail sheet (desktop) or navigates (mobile).
3490
+ - **Filter bar above:** segmented control for common filters + "More filters" dropdown for power users.
3491
+ - **Empty state:** illustration or dashed border box with clear CTA ("No deals yet. Create your first deal.")
3492
+
3493
+ ### Detail / Drill-down Pages
3494
+
3495
+ - **Header row** with breadcrumbs, title, primary action button, secondary actions menu.
3496
+ - **Tabs** for sub-sections (Overview / Activity / Notes / Files). Tabs in a horizontal row below the header.
3497
+ - **Two-column layout on the Overview tab:** main content 70%, metadata sidebar 30%.
3498
+ - **Metadata sidebar** uses definition-list style: small label, value below. Grouped into cards.
3499
+ - **Activity feed** on the Activity tab: vertical timeline with event icons, actor, timestamp.
3500
+
3501
+ ### Forms
3502
+
3503
+ - **Multi-section forms** separated by \`<Separator />\` or card boundaries, NOT one long scroll.
3504
+ - **Inputs stacked vertically**, labels above, helper text below. 2-column grid only for short paired fields (first name / last name, city / zip).
3505
+ - **Inline validation**: red border + icon + short message beneath the input.
3506
+ - **Primary action sticky at the bottom** on long forms. "Save changes" or "Create X."
3507
+
3508
+ ## Component Patterns
3509
+
3510
+ ### Cards
3511
+
3512
+ Clean, functional, borderless-leaning:
3513
+
3514
+ \`\`\`
3515
+ bg-card border border-border rounded-lg p-5
3516
+ \`\`\`
3517
+
3518
+ - \`rounded-lg\` \u2014 professional, not overly friendly.
3519
+ - \`border\` over shadow for light mode. \`border-border/50 bg-card/60\` with backdrop-blur for dark mode (glass aesthetic).
3520
+ - Header row: title (\`text-sm font-medium\`) + action button or menu trigger aligned right.
3521
+ - Body: data, chart, or list. Minimal decoration.
3522
+
3523
+ ### Metrics and Stats
3524
+
3525
+ The bread and butter of this archetype:
3526
+
3527
+ - **Hero metric:** \`text-3xl md:text-4xl font-semibold tabular-nums\`. NOT \`font-extrabold\` (too loud for B2B).
3528
+ - **Delta indicator:** inline \`text-xs\` with color (green for positive, red for negative) and arrow icon (\`\u2191\` \`\u2193\`). Always include the baseline period: "+12% vs last month."
3529
+ - **Sparkline:** thin line (\`strokeWidth={1.5}\`), no fill, muted color. 40-60px height.
3530
+ - **Unit labels:** below the number, \`text-sm text-muted-foreground\`.
3531
+
3532
+ ### Navigation
3533
+
3534
+ - **Sidebar** is standard for B2B: 240-280px wide, collapsible, with sections.
3535
+ - **Section headers** in the sidebar: \`text-xs uppercase tracking-wider text-muted-foreground\`.
3536
+ - **Active link:** \`bg-muted\` + \`text-foreground\`, inactive: \`text-muted-foreground hover:bg-muted/50\`.
3537
+ - **Top bar** is thin (48-56px): breadcrumbs left, search middle (\`cmdk\` command palette), user menu right.
3538
+ - **Command palette** (\`\u2318K\`) is a must. Power users live in it. Actions, navigation, recent items.
3539
+
3540
+ ### Typography
3541
+
3542
+ - **Sans-serif heading font** \u2014 Inter is overused; prefer Geist, S\xF6hne (or IBM Plex Sans free alternative), or Manrope.
3543
+ - **Body:** same font or a humanist sans (Inter is acceptable for body, not heading).
3544
+ - **Numeric data:** always \`font-variant-numeric: tabular-nums\` or \`tabular-nums\` class.
3545
+ - **Scale:** \`text-xs\` metadata \u2192 \`text-sm\` body \u2192 \`text-base\` subheads \u2192 \`text-lg/xl\` section titles \u2192 \`text-2xl+\` page titles. Tight hierarchy.
3546
+
3547
+ ### Motion
3548
+
3549
+ - Scroll reveals: **fade-up, 200ms, staggered 40ms per card.** Fast, efficient \u2014 don't linger.
3550
+ - Hover: \`transition-colors\` 150ms on rows, cards, nav items.
3551
+ - Numbers on dashboard load: \`@number-flow/react\` counts up. Satisfying but not slow.
3552
+ - Skip entrance animations entirely on return visits if possible (the user opens this app 20\xD7/day).
3553
+ - Avoid: bouncy spring physics, floating decorative shapes, loading screens with branding.
3554
+
3555
+ ---
3556
+
3557
+ ## Landing Page Signature
3558
+
3559
+ For marketing pages, this archetype uses **product-first** patterns. The UI IS the marketing. Restore these from the old prism-dashboard / obsidian-tech / daylight-saas / crystal-clear presets.
3560
+
3561
+ ### Signature 1: Dashboard preview with parallax
3562
+
3563
+ The most recognizable B2B SaaS landing pattern.
3564
+
3565
+ - **Full-viewport hero** with headline left, dashboard screenshot right (or full-width below).
3566
+ - Dashboard image in a **browser frame** or bare with subtle border + shadow.
3567
+ - **Parallax scroll effect**: as user scrolls past the hero, dashboard moves slower than the text. \`useTransform(scrollY, [0, viewport.height], [0, -120])\`.
3568
+ - Optional: dashboard image renders with \`mixBlendMode: "luminosity"\` to tint it with the page background, then fades back to full color on scroll.
3569
+ - Fill the dashboard with **real-looking domain-specific data** \u2014 never generic "Sample Chart." If it's a CRM, show deal names; analytics, show metric names specific to the product.
3570
+
3571
+ \`\`\`tsx
3572
+ <motion.img
3573
+ src="/dashboard-preview.png"
3574
+ style={{ y: useTransform(scrollY, [0, 600], [0, -120]) }}
3575
+ className="rounded-2xl border border-border/60 shadow-2xl"
3576
+ />
3577
+ \`\`\`
3578
+
3579
+ ### Signature 2: Glass navbar + logo marquee
3580
+
3581
+ - **Fixed glass navbar** at top: \`fixed top-0 w-full backdrop-blur-md bg-background/80 border-b border-border/40 z-50\`.
3582
+ - Logo left, nav links center (3-5 items max), sign-in + primary CTA right.
3583
+ - **Logo marquee** below the hero: "Trusted by 2,847 teams" + infinite horizontal scroll of customer logos via \`motion\` with \`x: -100%\` loop. Logos in grayscale: \`opacity-50 brightness-0 [filter:invert(var(--invert-for-theme))]\`.
3584
+
3585
+ ### Signature 3: Alternating feature rows with product screenshots
3586
+
3587
+ - 3-4 feature sections below the hero, alternating image/text sides.
3588
+ - Each row: product screenshot (real UI, not a marketing mockup) in \`rounded-xl border shadow-xl\` on one side, feature name + 2-sentence benefit + bullet list on the other.
3589
+ - Scroll reveal: text fades up first, image slides in from its side (\`x: \xB130, opacity: 0 \u2192 1\`).
3590
+
3591
+ ### Signature 4: Pricing calculator section (ember-pricing pattern)
3592
+
3593
+ - Full-width section with 3 pricing tiers. The "Pro" tier has \`ring-2 ring-primary\` and a "Recommended" badge.
3594
+ - Interactive slider above the tiers that adjusts the price based on usage (seats, API calls, GB). Numbers animate with \`NumberFlow\`.
3595
+ - Feature list per tier: checkmarks in primary color, cross in muted for absent features.
3596
+
3597
+ ---
3598
+
3599
+ ## Copy Voice
3600
+
3601
+ - **Confident, specific, outcome-focused.** "Close 40% more deals" not "Boost sales productivity."
3602
+ - Numbers everywhere. Real ones if you have them; plausible-sounding if you don't.
3603
+ - No "revolutionize," "seamless," "leverage," "empower" \u2014 see landing-rules Tier 1 bans.
3604
+ - Feature names sound like UI elements: "Pipeline view," "Timeline export," "Saved filters." Not marketing phrases.
3605
+
3606
+ ---
3607
+
3608
+ ## Anti-patterns
3609
+
3610
+ - **Gradient-heavy hero backgrounds.** B2B looks cheap with aggressive purple/blue gradients. Use subtle mesh or none.
3611
+ - **Testimonials without specifics.** "Great product!" is worse than no testimonial. Every quote needs a name + company + concrete outcome.
3612
+ - **Floating abstract 3D shapes.** Signal of AI-generated landing. Skip.
3613
+ - **"Welcome to [Product]"** as hero headline. Lead with a specific value prop.
3614
+ - **Icon cards with generic icons** (rocket, lightning bolt, checkmark) as the feature section. Use real product screenshots or specific domain icons.
3615
+ - **Padding everywhere.** B2B users want density. Give them density.
3616
+ - **Inter as the only font.** Pair it with a distinctive heading font or swap it entirely.
3617
+ - **Centered text + centered image below.** The most overused AI hero. Use split or offset layouts.
3618
+ - **Animation on every element.** Efficient motion only: hero entrance, card reveals on scroll, number counters. Nothing else.
3619
+ `});var Rs,_s=S(()=>{Rs=`# Content Editorial Archetype
3620
+
3621
+ 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.
3622
+
3623
+ These apps are about READING and WRITING words. The design must serve the text \u2014 never compete with it. Typography is the hero. Color is restrained. Everything else exists to make a 2,000-word piece a pleasure to read on a phone in bed.
3624
+
3625
+ ---
3626
+
3627
+ ## Theme
3628
+
3629
+ Default: **light-leaning.** Long-form reading on dark backgrounds causes eye strain for most readers, and the editorial convention (NYT, New Yorker, Stripe docs) is light. However, dark mode is a required toggle for any serious reading tool \u2014 the user decides, not you. Use semantic tokens so both themes render correctly.
3630
+
3631
+ Override to dark if the app's \`design.tone\` is \`dark-first\` (rare for content) or the brand explicitly wants cinematic/moody vibe (dark-mode-only magazine, music-industry publication).
3632
+
3633
+ ---
3634
+
3635
+ ## Page Composition
3636
+
3637
+ ### Home / Index Page
3638
+
3639
+ The front page is a **curated reading list**, not a dashboard.
3640
+
3641
+ - **Featured article** at the top: large thumbnail, display-size headline, 1-line summary, author + date + reading time. One hero piece.
3642
+ - **Secondary list** below: 3-column grid on desktop, single column on mobile. Smaller thumbnails, standard headline + summary.
3643
+ - **Categories or tags** as a horizontal pill row, not a sidebar. Sticky on scroll if long.
3644
+ - **Newsletter signup** embedded above the fold OR at the end of each article. Not a modal popup.
3645
+ - **No dashboard.** No KPIs. No activity feed. This isn't a SaaS app.
3646
+
3647
+ Layout:
3648
+ \`\`\`
3649
+ \u250C\u2500 header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
3650
+ \u2502 Publication Name [Subscribe] [Sign in] \u2502
3651
+ \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
3652
+ \u2502 [Design] [Engineering] [Culture] [Longform] \u2502
3653
+ \u2502 \u2502
3654
+ \u2502 \u250C\u2500 featured article \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
3655
+ \u2502 \u2502 [wide image, 16:9] \u2502 \u2502
3656
+ \u2502 \u2502 On Making Things That Last \u2502 \u2502
3657
+ \u2502 \u2502 by Jamie Chen \xB7 8 min read \xB7 Apr 12 \u2502 \u2502
3658
+ \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
3659
+ \u2502 \u2502
3660
+ \u2502 \u250C\u2500\u2500 3-up grid of recent articles \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
3661
+ \u2502 \u2502 [img] Title 1 [img] Title 2 [img] T3 \u2502 \u2502
3662
+ \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
3663
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
3664
+ \`\`\`
3665
+
3666
+ ### Article / Post Page
3667
+
3668
+ The main event. This is where the archetype lives or dies.
3669
+
3670
+ - **Wide reading column:** \`max-w-prose\` (~65ch) for body text. No wider.
3671
+ - **Generous line-height:** \`leading-relaxed\` or \`leading-loose\` on body paragraphs.
3672
+ - **Sidebar TOC on desktop only** (hide below \`lg\`): sticky on scroll, active section highlighted via IntersectionObserver.
3673
+ - **Metadata header:** author avatar + name, date, reading time. Small, muted, above the title.
3674
+ - **Drop cap** on the first paragraph of longform pieces (\`::first-letter\` pseudo). Optional \u2014 use for signature pieces.
3675
+ - **Inline images** break out of the reading column: \`-mx-8 md:-mx-16\` with caption below in small italic.
3676
+ - **Pull quotes** in a larger serif face: \`text-2xl font-serif italic border-l-4 border-primary pl-6 my-12\`.
3677
+
3678
+ ### List / Archive Views
3679
+
3680
+ - **Chronological reverse-chron list** for archive.
3681
+ - Each row: thumbnail (square, 120-160px), headline, excerpt (1-2 lines, truncated), metadata row.
3682
+ - Category filter as a horizontal pill row above.
3683
+ - Pagination at the bottom (page numbers, not infinite scroll \u2014 readers want a sense of size).
3684
+
3685
+ ### Search
3686
+
3687
+ - \`/search\` page with a prominent search box at the top.
3688
+ - Results list: highlight matched terms in the excerpt with \`<mark className="bg-primary/10">\`.
3689
+ - Facets on the left: category, author, date range.
3690
+ - Empty state is warm: "No matches. Try another keyword, or browse by category."
3691
+
3692
+ ### Newsletter / Subscribe Pages
3693
+
3694
+ - Dedicated \`/subscribe\` page that pitches the newsletter \u2014 NOT a modal popup.
3695
+ - Value proposition: "What you'll get." Frequency: "Twice a month." Preview: link to an example issue.
3696
+ - Email input + submit button. That's it. No checkboxes for "marketing consent" unless legally required.
3697
+
3698
+ ## Component Patterns
3699
+
3700
+ ### Typography
3701
+
3702
+ This is the whole archetype. Get this right, everything else follows.
3703
+
3704
+ - **Heading font: a serif or refined sans.** Good pairings:
3705
+ - \`Fraunces\` (heading) + \`Inter\` (body) \u2014 modern editorial
3706
+ - \`Playfair Display\` (heading) + \`Source Serif 4\` (body) \u2014 classical
3707
+ - \`Instrument Serif\` (heading) + \`Geist Sans\` (body) \u2014 contemporary
3708
+ - \`Bricolage Grotesque\` (heading) + \`Source Serif 4\` (body) \u2014 expressive
3709
+ - Avoid: Inter as both heading and body. Editorial publications need typographic personality.
3710
+ - **Scale:** \`text-5xl md:text-6xl lg:text-7xl\` for article titles, \`text-lg md:text-xl\` for body, \`text-2xl\` for subheads in the article.
3711
+ - **Line height:** body is \`leading-relaxed\` (1.625) to \`leading-loose\` (2). Headlines are \`leading-tight\`.
3712
+ - **Tracking:** headlines \`tracking-tight\`, body default, small caps section markers \`tracking-widest uppercase\`.
3713
+ - **Serif italic for accent** on a single key word in the title (stolen from navy-serif preset): \`<span className="font-serif italic">that</span>\`.
3714
+
3715
+ ### Cards
3716
+
3717
+ Minimal, borderless, text-led:
3718
+
3719
+ \`\`\`
3720
+ no background, no border \u2014 just typography + spacing + optional thumbnail
3721
+ \`\`\`
3722
+
3723
+ - Thumbnail if present: \`aspect-[16/9] rounded-md\`, subtle hover scale.
3724
+ - Title: heading font, \`text-xl md:text-2xl tracking-tight\`.
3725
+ - Excerpt: \`text-muted-foreground leading-relaxed\`.
3726
+ - Metadata: \`text-xs uppercase tracking-wider text-muted-foreground mt-2\`.
3727
+ - No shadow, no border on cards. Spacing is the separator.
3728
+
3729
+ ### Navigation
3730
+
3731
+ - **Simple top nav.** Logo (publication name, often wordmark) left. 4-6 category links right. Search icon + subscribe CTA.
3732
+ - **No sidebar** except for the article TOC.
3733
+ - On mobile: hamburger for categories, search icon, subscribe CTA stays visible.
3734
+ - Breadcrumbs in archive pages: "Home \u203A Design \u203A Article Title."
3735
+
3736
+ ### Author bylines
3737
+
3738
+ - Small avatar + name + date + reading time.
3739
+ - On article page, appears both above the title and in a larger author-bio card at the end (with photo, bio, "more from this author" links).
3740
+
3741
+ ### Motion
3742
+
3743
+ - **Scroll reveals on section content,** NOT on every paragraph. Just the article header (fade-up 500ms) and any section breaks.
3744
+ - **Reading progress bar** at the top: thin line (\`h-0.5 bg-primary\`) that fills as user scrolls through the article.
3745
+ - **Image fade-in** on lazy-load with 200ms duration.
3746
+ - **Hover on article cards:** \`text-foreground\` on the title (link color transition). No lift, no scale.
3747
+ - Avoid: flashy animations during reading. Respect the reader.
3748
+
3749
+ ---
3750
+
3751
+ ## Landing Page Signature
3752
+
3753
+ For publications/newsletters with a dedicated marketing page (rare \u2014 the home page IS the marketing for most content sites).
3754
+
3755
+ ### Signature 1: Editorial hero (ivory-cinema / inkwell pattern)
3756
+
3757
+ - **Large display headline,** 2-3 lines, serif or refined sans. \`text-6xl md:text-8xl lg:text-9xl\` \u2014 unusually huge. Generous line-height.
3758
+ - One word of the headline is in **serif italic** accent (navy-serif pattern): \`<span className="font-serif italic text-primary">words</span>\`.
3759
+ - Subheadline in the body font, 1 sentence, muted.
3760
+ - Single CTA: "Subscribe" or "Read the archive." No secondary button.
3761
+ - Optional: large full-bleed photograph below, in grayscale with primary tint applied via \`mix-blend-color\`.
3762
+
3763
+ ### Signature 2: Monochrome editorial (inkwell pattern)
3764
+
3765
+ Strict grayscale palette with ONE accent color used sparingly. The whole landing is literally one color family.
3766
+
3767
+ - Background: pure white or cream (\`bg-background\`).
3768
+ - Text: all in \`foreground\` with \`/80\`, \`/60\`, \`/40\` opacity variations for hierarchy. No colored text except on hover states.
3769
+ - Dividers: \`border-border/60\` hairlines between sections.
3770
+ - Accent color only on links, the Subscribe button, and the occasional pull-quote border.
3771
+ - This looks sophisticated BECAUSE it's restrained. Don't add more colors.
3772
+
3773
+ ### Signature 3: Liquid-glass buttons (navy-serif pattern)
3774
+
3775
+ - CTAs use liquid-glass styling on a light background:
3776
+
3777
+ \`\`\`css
3778
+ .editorial-glass-btn {
3779
+ background: rgba(var(--foreground), 0.03);
3780
+ backdrop-filter: blur(12px);
3781
+ border: 1px solid rgba(var(--foreground), 0.08);
3782
+ border-radius: 9999px;
3783
+ padding: 0.75rem 1.5rem;
3784
+ }
3785
+ .editorial-glass-btn::before {
3786
+ content: "";
3787
+ position: absolute;
3788
+ inset: 0;
3789
+ border-radius: inherit;
3790
+ padding: 1px;
3791
+ background: linear-gradient(135deg, rgba(var(--foreground), 0.15), transparent);
3792
+ -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
3793
+ -webkit-mask-composite: xor;
3794
+ mask-composite: exclude;
3795
+ }
3796
+ \`\`\`
3797
+
3798
+ - Refined, premium, doesn't shout. Great for longform publication CTAs.
3799
+
3800
+ ### Signature 4: Featured-articles strip with oversized type
3801
+
3802
+ Below the hero: 3 featured pieces laid out as a horizontal strip with **oversized serial numbers** (01, 02, 03) next to each title. Rest of the card is minimal \u2014 just headline + byline + date.
3803
+
3804
+ ---
3805
+
3806
+ ## Copy Voice
3807
+
3808
+ - **Conversational, confident, literary.** Write like a human who cares about the topic.
3809
+ - Headlines can be long and declarative: "The Case for Making Things That Break" is better than "Quality Over Quantity."
3810
+ - Byline matters. "by Jamie Chen" always present, even if fictional for the landing.
3811
+ - No "revolutionize," "leverage," corporate filler. This is the one archetype where you actively AVOID SaaS copy patterns.
3812
+
3813
+ ---
3814
+
3815
+ ## Anti-patterns
3816
+
3817
+ - **Sans-serif everything.** The whole point of editorial is typographic character. Use a serif somewhere \u2014 headline, pull quote, drop cap, byline.
3818
+ - **Narrow reading column wider than \`max-w-prose\`.** Eye tracking suffers over ~75 characters per line.
3819
+ - **Cards with borders AND shadows AND backgrounds.** Editorial cards are minimal \u2014 text + spacing, nothing more.
3820
+ - **Newsletter popup on load.** Readers haven't read anything yet. Put the subscribe block after they finish an article (end-of-post) or at the end of the home page.
3821
+ - **Auto-playing video or audio** in article cards. Respect the reader's focus.
3822
+ - **Ads/sponsored content** indistinguishable from real articles. If sponsored, label clearly.
3823
+ - **Dark mode as default for a reading tool.** Offer it as a toggle, not the default.
3824
+ - **Flashy motion while reading.** Scroll hijacking, parallax on body text, animated backgrounds behind paragraphs \u2014 all break reading flow.
3825
+ - **More than 2 fonts.** Heading + body is enough. Adding a third font for captions or metadata dilutes the identity.
3826
+ `});var Es,Ns=S(()=>{Es=`# Devtool Technical Archetype
3827
+
3828
+ 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.
3829
+
3830
+ These apps serve DEVELOPERS. The user is technical, skeptical, allergic to marketing fluff, and wants to see the API/CLI/code before signing up. The UI must look like it was built by someone who ships code every day.
3831
+
3832
+ ---
3833
+
3834
+ ## Theme
3835
+
3836
+ Default: **dark-leaning.** Developer tools overwhelmingly default to dark \u2014 it's the terminal convention, reduces eye strain during long sessions, and signals "we're for people who hack at night." However, a light mode toggle is essential (not every dev runs dark) and must be well-tuned, not an afterthought.
3837
+
3838
+ Override to light if the app's \`design.tone\` is explicitly \`warm-minimal\` or \`clean-saas\` (rare for dev tools).
3839
+
3840
+ All CSS uses semantic tokens. Glow, neon, and heavy shadow effects should ONLY appear when the resolved theme is dark \u2014 they look tacky on light.
3841
+
3842
+ ---
3843
+
3844
+ ## Page Composition
3845
+
3846
+ ### Dashboard / Console
3847
+
3848
+ The dashboard mimics a terminal's functional density. No decoration, maximum information.
3849
+
3850
+ - **Project / resource list at the top.** Compact cards or a table. Filter by status (running, errored, deployed).
3851
+ - **Logs / events stream as the primary panel.** Terminal-styled: monospace font, line numbers, syntax-highlighted JSON, timestamps in ISO format.
3852
+ - **Metrics strip** (optional): request count, error rate, p95 latency. \`tabular-nums\`, \`text-sm\`, no fancy charts.
3853
+ - **Keyboard shortcut hints** visible everywhere: \`g d\` to go to deployments, \`\u2318K\` for palette, \`?\` for help.
3854
+
3855
+ Layout:
3856
+ \`\`\`
3857
+ \u250C\u2500 header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
3858
+ \u2502 my-project \u25B8 production [Deploy] [\u2318K] \u2502
3859
+ \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
3860
+ \u2502 \u25CF Deployment \xB7 3m ago \xB7 api.example.com \u2502
3861
+ \u2502 \u250C\u2500 logs stream \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
3862
+ \u2502 \u2502 12:34:01.122 GET /api/users 200 42ms\u2502 \u2502
3863
+ \u2502 \u2502 12:34:01.156 POST /api/auth/login 401 12ms\u2502 \u2502
3864
+ \u2502 \u2502 12:34:02.003 GET /api/health 200 3ms\u2502 \u2502
3865
+ \u2502 \u2502 12:34:02.541 ERROR: connection timeout \u2502 \u2502
3866
+ \u2502 \u2502 \u2514\u2500 stack trace (click to expand) \u2502 \u2502
3867
+ \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\u2502
3868
+ \u2502 [filter: errors only] [pause stream] \u2502
3869
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
3870
+ \`\`\`
3871
+
3872
+ ### Code / API Reference View
3873
+
3874
+ If the app ships an SDK or API, the docs/reference page IS a first-class product surface.
3875
+
3876
+ - **Two-column layout:** prose left (description, params table), code right (sticky on scroll).
3877
+ - **Code blocks** with syntax highlighting (\`shiki\` or \`prism-react-renderer\`), line numbers, copy button, language tabs (JavaScript / Python / curl / Rust).
3878
+ - **Inline code** (\`<code>\` spans) in \`font-mono text-sm bg-muted/50 px-1 py-0.5 rounded\`.
3879
+ - **API endpoint signatures:** \`GET /api/users/:id\` in monospace with colored method (green for GET, blue for POST, orange for PATCH, red for DELETE).
3880
+
3881
+ ### Detail / Drill-down (logs, traces, errors)
3882
+
3883
+ - **Header** with breadcrumbs, title, filter controls.
3884
+ - **Details in a key-value grid:** \`grid-cols-[max-content_1fr] gap-x-4\` for aligned labels and values.
3885
+ - **Raw JSON / payload** in a collapsible code block at the bottom.
3886
+ - **Timeline view** for traces: horizontal bars per span, labels, duration.
3887
+ - **Copy commands** everywhere (CLI snippets, curl commands, IDs).
3888
+
3889
+ ### Forms
3890
+
3891
+ Dev-tool forms are functional, not decorated:
3892
+
3893
+ - **Monospace for technical inputs**: environment variables, API keys, commands, URLs.
3894
+ - **Placeholder inputs show real examples**: \`KEY=value\`, \`https://api.example.com\`, \`const x = 42\`.
3895
+ - **Keyboard shortcuts** in labels: "Deploy (\u2318\u23CE)."
3896
+ - **Validation**: inline, specific, technical. "Key must match /^[A-Z_][A-Z0-9_]*$/" not "Invalid format."
3897
+
3898
+ ## Component Patterns
3899
+
3900
+ ### Cards
3901
+
3902
+ Dark-mode glass, light-mode flat bordered:
3903
+
3904
+ \`\`\`
3905
+ dark: bg-card/40 backdrop-blur-sm border border-border/50 rounded-lg p-5
3906
+ light: bg-card border border-border rounded-lg p-5
3907
+ \`\`\`
3908
+
3909
+ - Status indicator as a colored dot (\`\u25CF\`) on the left \u2014 not a badge. \`bg-green-500\` running, \`bg-yellow-500\` building, \`bg-red-500\` errored, \`bg-muted\` idle.
3910
+ - Header: technical name in monospace (project slug, resource ID), human-friendly name below in regular font.
3911
+ - Metadata row: created date in relative time ("3m ago"), owner, region.
3912
+
3913
+ ### Typography
3914
+
3915
+ - **Heading font:** geometric sans (Geist, Inter, S\xF6hne, Departure Mono for novelty). Never a serif.
3916
+ - **Body font:** same as heading or a humanist sans.
3917
+ - **Monospace font:** JetBrains Mono, Geist Mono, IBM Plex Mono, or Berkeley Mono. Used for: code, API endpoints, IDs, keyboard shortcuts, tabular data.
3918
+ - **Scale:** smaller than SaaS-analytical. \`text-xs\` for metadata, \`text-sm\` for body, tight hierarchy.
3919
+ - **Weight:** \`font-medium\` default for headings, \`font-normal\` for body. Avoid \`font-bold\` except for critical emphasis \u2014 developers read a lot of type, heavy weights fatigue.
3920
+
3921
+ ### Metrics and Data
3922
+
3923
+ - **Numbers in monospace** (\`tabular-nums\` minimum, full monospace font for raw data).
3924
+ - **Unit suffixes in muted color:** "42" in foreground, "ms" or "MB" after in \`text-muted-foreground\`.
3925
+ - **Sparklines with event markers:** dots on the line where deploys happened, errors occurred.
3926
+ - **Relative timestamps with absolute on hover:** "3m ago" \u2192 tooltip shows "2026-04-19 12:34:01 UTC."
3927
+
3928
+ ### Navigation
3929
+
3930
+ - **Sidebar** with sections: Overview, Deployments, Logs, Metrics, Settings.
3931
+ - **Environment switcher** at the top of the sidebar: "production \u25BE" dropdown.
3932
+ - **Keyboard shortcuts** next to each sidebar link (small, muted, right-aligned): "Deploy \u2318\u23CE."
3933
+ - **Command palette** (\`\u2318K\`) is MANDATORY. All navigation and actions accessible.
3934
+ - **Top bar:** breadcrumbs left (click to navigate up), deploy status indicator center, user menu right.
3935
+
3936
+ ### Motion
3937
+
3938
+ - **Minimal, functional.** Fade-ins under 150ms. No bounces, no spring physics, no floating.
3939
+ - **Log streams:** new rows append with a brief background flash (\`bg-primary/5\` fading out over 600ms) \u2014 highlights recency without being distracting.
3940
+ - **Status dot pulse:** \`animate-pulse\` on "running" or "live" states. Nothing else pulses.
3941
+ - **Command palette:** opens with a subtle scale-in (\`scale-95 \u2192 1\`, 120ms).
3942
+ - Avoid: any animation that slows down workflow. Developers are impatient.
3943
+
3944
+ ---
3945
+
3946
+ ## Landing Page Signature
3947
+
3948
+ For dev tool marketing pages. Restore patterns from obsidian-tech / spectrum-ai / neural-edge / chain-zero / cobalt-builder presets.
3949
+
3950
+ ### Signature 1: Terminal / code block hero (obsidian-tech pattern)
3951
+
3952
+ The single most effective dev-tool landing pattern: show the code.
3953
+
3954
+ - **Split-panel hero:** headline + subheadline left (~45%), animated terminal or code block right (~55%).
3955
+ - Code block styled as a terminal: \`bg-black/90 rounded-xl border border-border/40 p-6 font-mono text-sm\`. macOS-style window chrome on top (3 dots: red / yellow / green).
3956
+ - Inside: type out the install command or key API call character-by-character (typewriter effect from typewriter-intro). Then output appears below.
3957
+ - Code is REAL and copy-pastable. Not fake syntax.
3958
+
3959
+ \`\`\`tsx
3960
+ <pre className="bg-black/90 rounded-xl border border-border/40 p-6 font-mono text-sm">
3961
+ <div className="flex gap-2 mb-4">
3962
+ <span className="w-3 h-3 rounded-full bg-red-500/80" />
3963
+ <span className="w-3 h-3 rounded-full bg-yellow-500/80" />
3964
+ <span className="w-3 h-3 rounded-full bg-green-500/80" />
3965
+ </div>
3966
+ <code>
3967
+ <span className="text-green-400">$</span>{" "}
3968
+ <TypewriterText text="npx @your-sdk/cli init my-app" />
3969
+ </code>
3970
+ </pre>
3971
+ \`\`\`
3972
+
3973
+ ### Signature 2: Glass navbar with badge row (obsidian-tech pattern)
3974
+
3975
+ - **Fixed glass navbar:** \`fixed backdrop-blur-md bg-background/80 border-b border-border/40\`.
3976
+ - **Below the hero:** row of 3 "Integrated with..." badges in liquid-glass style, each with a partner logo and short label. Horizontal, centered.
3977
+
3978
+ ### Signature 3: Gradient headline + logo marquee (gradient-titan / spectrum-ai pattern)
3979
+
3980
+ - **Massive headline** (\`text-6xl md:text-8xl lg:text-9xl font-medium tracking-[-2px]\`) with a gradient on ONE keyword: \`bg-gradient-to-r from-primary to-primary/60 bg-clip-text text-transparent\`.
3981
+ - **Logo marquee** just below the CTA: "Trusted by teams at..." with infinite horizontal scroll of customer logos. Grayscale, \`opacity-50 hover:opacity-100\`.
3982
+ - Background: subtle mesh gradient with primary color at 5-10% opacity, positioned asymmetrically.
3983
+
3984
+ ### Signature 4: Code example section with language tabs
3985
+
3986
+ Below the hero: a "how it works" section featuring a large code example with tabs for different languages/frameworks. Real code, syntax highlighted.
3987
+
3988
+ ### Signature 5: Stats section with terminal output
3989
+
3990
+ Numbers styled as terminal output: \`> 47,293 deploys this week\`, \`> 99.98% uptime last 30d\`, \`> 12ms p50 latency\`. All in monospace, \`text-muted-foreground\` prefix, \`text-foreground\` number.
3991
+
3992
+ ---
3993
+
3994
+ ## Copy Voice
3995
+
3996
+ - **Technical, confident, specific.** "Ship in 30 seconds. No config." not "Accelerate your development workflow."
3997
+ - **Lead with the command or API call.** \`npx create-my-app\` as the hero, with prose wrapped around it.
3998
+ - **Numbers are p50/p95/p99, request counts, bundle sizes, MB/s.** Real technical metrics, not "ROI" or "productivity."
3999
+ - **Name-drop specific technologies** your tool works with: "Ships with TypeScript. Works with Next.js, Remix, Astro."
4000
+ - Avoid: "empower," "enterprise-grade" (except for enterprise sales), "revolutionize," anything sales-flavored.
4001
+
4002
+ ---
4003
+
4004
+ ## Anti-patterns
4005
+
4006
+ - **Stock photos of diverse happy teams.** Developer tools don't use lifestyle photography. Use code, terminal output, architecture diagrams.
4007
+ - **Gradient blobs with no code shown.** The landing page must show code. Hiding what the product actually is makes developers bounce.
4008
+ - **Light mode with heavy neon effects.** Neon/glow looks cheap on light backgrounds. If you want glow, ship dark mode as default.
4009
+ - **Purple/blue gradients as the primary visual.** Devtool clich\xE9. If you use a gradient, make it unusual (green, amber, teal) or skip it.
4010
+ - **Hamburger menus on desktop.** Developer tools show the full nav \u2014 we're not hiding complexity from power users.
4011
+ - **Onboarding gated behind a sign-up.** Offer a "try without account" or "read the docs" path. Developers want to evaluate before committing.
4012
+ - **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).
4013
+ - **Feature cards with rocket / lightning / puzzle icons.** Use technical icons (terminal, code, database, network) or skip icons entirely.
4014
+ - **Motion on code blocks** while the user is trying to read them. Type out ONCE on load, then leave them alone.
4015
+ `});var js,Ds=S(()=>{js=`# Creative Showcase Archetype
4016
+
4017
+ 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.
4018
+
4019
+ These apps are about VISUAL CRAFT. The design is itself the portfolio. Every pixel signals taste. The user is usually a prospective client evaluating whether you can do great work for them \u2014 the site answers that question.
4020
+
4021
+ ---
4022
+
4023
+ ## Theme
4024
+
4025
+ Default: **either** \u2014 creative work splits evenly between dark (cinematic, moody, premium) and light (editorial, minimal, refined). Pick based on the work being showcased:
4026
+ - **Photography, architecture, filmmaking, luxury brands:** default dark for drama.
4027
+ - **Product design, illustration, type design, agencies:** default light for clarity.
4028
+
4029
+ Both render well. Override to follow the app's \`design.tone\`. This archetype is the one where pushing the design boundary is most encouraged \u2014 break your own rules if the craft calls for it.
4030
+
4031
+ ---
4032
+
4033
+ ## Page Composition
4034
+
4035
+ ### Home / Index Page
4036
+
4037
+ The home is a **work reel**. Visitors see the caliber of work in the first 5 seconds or they leave.
4038
+
4039
+ - **Immediate visual impact.** Hero is a single striking image, video loop, or animated composition \u2014 NOT a headline-first layout. The visual IS the value prop.
4040
+ - **Work grid** fills most of the page: mixed aspect ratios (bento style), large imagery, minimal text overlays.
4041
+ - **Short intro paragraph** (1-2 sentences, oversized body type) positioned unusually \u2014 offset, rotated, or bleeding over the work grid.
4042
+ - **Contact / Inquire CTA** is the primary action. Everything else is secondary.
4043
+ - **Minimal navigation** \u2014 Work / About / Contact. That's often enough.
4044
+
4045
+ Layout:
4046
+ \`\`\`
4047
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
4048
+ \u2502 STUDIO NAME [Work] [Say hi]\u2502
4049
+ \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
4050
+ \u2502 \u2502
4051
+ \u2502 \u250C\u2500 fullscreen hero image/video \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
4052
+ \u2502 \u2502 \u2502 \u2502
4053
+ \u2502 \u2502 [cinematic image or looped video] \u2502 \u2502
4054
+ \u2502 \u2502 \u2502 \u2502
4055
+ \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
4056
+ \u2502 \u2502
4057
+ \u2502 A studio that makes things \u2502
4058
+ \u2502 that feel inevitable. \u2502
4059
+ \u2502 \u2502
4060
+ \u2502 \u250C\u2500 bento work grid (mixed sizes) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
4061
+ \u2502 \u2502 [wide] \u2502 [sqr] \u2502 \u2502
4062
+ \u2502 \u2502 Project One \u2502 Project Two \u2502 \u2502
4063
+ \u2502 \u2502 \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524 \u2502
4064
+ \u2502 \u2502 \u2502 [tall] \u2502 \u2502
4065
+ \u2502 \u2502 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524 Project Three \u2502 \u2502
4066
+ \u2502 \u2502 [sqr] [sqr] \u2502 \u2502 \u2502
4067
+ \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
4068
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
4069
+ \`\`\`
4070
+
4071
+ ### Project / Case Study Page
4072
+
4073
+ When the visitor clicks a project, they expect depth.
4074
+
4075
+ - **Hero image** full-bleed, often with the project title overlaid at the bottom.
4076
+ - **Metadata strip:** year, client, role (Design + Creative Direction), team (if relevant).
4077
+ - **Long scroll** with alternating wide images, 2-up grids, text blocks, and occasional video embeds.
4078
+ - **Pull quotes** from the creative brief or client in oversized serif italic.
4079
+ - **Generous whitespace** \u2014 120-200px between sections. The content breathes.
4080
+ - **Navigation between projects** at the bottom: "Previous" / "Next" with project thumbnails.
4081
+
4082
+ ### About / Studio Page
4083
+
4084
+ - **Portrait photo or team image** \u2014 large, cinematic, not a LinkedIn corporate shot.
4085
+ - **Manifesto-style writing:** confident voice, specific beliefs, short paragraphs.
4086
+ - **Capabilities list:** what the studio does. Often in small caps or oversized numbered list.
4087
+ - **Client list:** logos in grayscale, or wordmarks in a single-weight text list. No starred ratings, no testimonial cards.
4088
+
4089
+ ### Contact / Inquire Page
4090
+
4091
+ - **Simple form** with 4-5 fields: name, email, project type (select), budget range, message.
4092
+ - OR: a large email address with an arrow icon, "Send a note \u2192." No form at all \u2014 just an email link.
4093
+ - **Availability status:** "Currently booking Q2 2026 projects." Small, matter-of-fact.
4094
+
4095
+ ## Component Patterns
4096
+
4097
+ ### Cards (Work Grid)
4098
+
4099
+ The work grid is the CORE component. It must be great.
4100
+
4101
+ \`\`\`
4102
+ bento grid: 3-column, mixed sizes, minimal chrome
4103
+ \`\`\`
4104
+
4105
+ - **No card borders or shadows.** The image is the card. Rounded corners (\`rounded-xl\` or \`rounded-2xl\`) on the image itself.
4106
+ - **Metadata overlays** appear on hover: project title + client slides up from the bottom with a subtle gradient overlay. Otherwise, image only.
4107
+ - **Aspect ratios vary:** 16:9, 1:1, 4:5, 3:2. Grid uses CSS grid with \`grid-template-rows: masonry\` (or fallback to explicit row spans).
4108
+ - **Hover effect:** subtle scale (\`hover:scale-[1.02]\`) + shadow appearance + overlay. NOT rotation or skew.
4109
+
4110
+ ### Typography
4111
+
4112
+ This is where creative-showcase distinguishes itself from SaaS-analytical.
4113
+
4114
+ - **Heading font: distinctive and dramatic.** Options:
4115
+ - \`Cabinet Grotesque\` / \`Clash Display\` / \`Satoshi\` \u2014 contemporary geometric
4116
+ - \`Editorial New\` / \`Fraunces\` / \`Playfair Display\` \u2014 editorial serif
4117
+ - \`Neue Haas Grotesk\` / \`S\xF6hne\` \u2014 Swiss-inspired classic
4118
+ - \`Monument Grotesk\` / \`S\xF6hne Breit\` \u2014 extended display
4119
+ - **Body font:** humanist sans (S\xF6hne, Inter, DM Sans) OR a readable serif (Source Serif 4).
4120
+ - **Scale is dramatic:** \`text-8xl md:text-9xl\` for hero titles, sometimes even larger with \`clamp()\`. Tight leading.
4121
+ - **Oversized numerals** as graphic elements: project numbers, year markers, section counts. \`text-[clamp(4rem,15vw,12rem)] font-medium tabular-nums\`.
4122
+ - **Serif italic accent** on one or two words in display text.
4123
+
4124
+ ### Navigation
4125
+
4126
+ - **Minimal top nav.** Logo (wordmark or monogram) left. 2-3 links right. Often no hamburger \u2014 just the links.
4127
+ - **On scroll:** nav remains visible but sometimes shrinks or changes background (from transparent to glass).
4128
+ - **Page transitions** are common for agencies: fade or slide to next page via a page-transition library (e.g., framer-motion AnimatePresence or similar).
4129
+
4130
+ ### Motion
4131
+
4132
+ Motion is a STORYTELLING tool for this archetype. More allowed than elsewhere, but must be purposeful.
4133
+
4134
+ - **Scroll-driven reveals:** images fade + scale up as they enter viewport. Use \`useScroll\` + \`useTransform\` for the effect.
4135
+ - **Text reveals:** words or lines slide up with stagger (\`BlurText\` pattern from onyx-studio \u2014 word-by-word blur-to-clear).
4136
+ - **Page transitions:** fade or slide between routes.
4137
+ - **Hover on work tiles:** scale + overlay appearance, 300-400ms ease-out.
4138
+ - **Scroll-pinned sections:** GSAP ScrollTrigger for hero sections that stay fixed while content scrolls over them (common in agency sites).
4139
+ - **Cursor effects** (optional): custom cursor on work grid that shows "View project" or project name when hovering tiles.
4140
+
4141
+ ---
4142
+
4143
+ ## Landing Page Signature
4144
+
4145
+ The home page IS the landing page for this archetype. But if there's a dedicated \`/services\` or \`/about\` marketing page, here's the signature. Restore patterns from onyx-studio / aurora-glass / stellar-folio / studio-blanc / volt-agency presets.
4146
+
4147
+ ### Signature 1: Liquid-glass agency (onyx-studio pattern)
4148
+
4149
+ Premium Apple-inspired aesthetic with \`.liquid-glass\` CSS classes applied to major elements.
4150
+
4151
+ \`\`\`css
4152
+ .liquid-glass {
4153
+ background: rgba(var(--foreground-rgb), 0.03);
4154
+ backdrop-filter: blur(20px);
4155
+ -webkit-backdrop-filter: blur(20px);
4156
+ border-radius: 1.5rem;
4157
+ position: relative;
4158
+ }
4159
+ .liquid-glass::before {
4160
+ content: "";
4161
+ position: absolute;
4162
+ inset: 0;
4163
+ border-radius: inherit;
4164
+ padding: 1px;
4165
+ background: linear-gradient(135deg,
4166
+ rgba(var(--foreground-rgb), 0.2),
4167
+ rgba(var(--foreground-rgb), 0.02));
4168
+ -webkit-mask:
4169
+ linear-gradient(#fff 0 0) content-box,
4170
+ linear-gradient(#fff 0 0);
4171
+ -webkit-mask-composite: xor;
4172
+ mask-composite: exclude;
4173
+ pointer-events: none;
4174
+ }
4175
+ .liquid-glass-strong { /* same but with blur(30px) and stronger border */ }
4176
+ \`\`\`
4177
+
4178
+ Apply \`.liquid-glass\` to: navbar, nav pill menus, CTA buttons, stat containers, feature cards.
4179
+
4180
+ ### Signature 2: Split-panel liquid glass (frost-bloom pattern)
4181
+
4182
+ Two-panel hero with the right side being a liquid-glass-strong overlay on a dark mesh gradient background.
4183
+
4184
+ - Left panel (52%): headline + subtext + CTA + optional feature pills below.
4185
+ - Right panel (48%): liquid glass container with community card, feature cards, or social icons in glass pills. Hidden on mobile.
4186
+ - Absolute inset-4 rounded-3xl on the glass panels.
4187
+
4188
+ ### Signature 3: GSAP bento portfolio (stellar-folio pattern)
4189
+
4190
+ - **Hero:** cinematic full-bleed with massive display type. Maybe a scroll-hijacked intro where text assembles.
4191
+ - **Work section:** bento grid with mixed aspect ratios. Each tile reveals on scroll with a clip-path reveal or BlurText effect.
4192
+ - **Scroll-pinned testimonial:** quote scales up + fades in as it crosses viewport center. Author + role appear with a delay.
4193
+ - **Stats section:** large serif italic numbers (\`text-8xl md:text-9xl font-serif italic\`), small labels below.
4194
+
4195
+ ### Signature 4: Typewriter intro / loading screen (typewriter-intro pattern)
4196
+
4197
+ Premium agency touch: on first load, a full-screen loading component shows the studio name being typed character-by-character, then a brief pause, then fades out to reveal the site.
4198
+
4199
+ - Only on first load \u2014 use \`sessionStorage\` to skip on subsequent navigations.
4200
+ - 1.5-2 seconds total duration. Not longer.
4201
+ - Falls back to no-intro on \`prefers-reduced-motion\`.
4202
+
4203
+ ### Signature 5: Cinematic serif headline (navy-serif / ivory-cinema pattern)
4204
+
4205
+ - Full-viewport hero with display serif type, one word in italic.
4206
+ - Subtle gradient glow behind the text (if dark) or desaturated photo background (if light).
4207
+ - Single CTA in liquid-glass style.
4208
+
4209
+ ---
4210
+
4211
+ ## Copy Voice
4212
+
4213
+ - **Confident, understated, specific.** "We help brands feel inevitable" not "We build cutting-edge creative solutions."
4214
+ - **First person plural** ("We design...") or third person ("The studio makes..."). Either works.
4215
+ - Client names and deliverables over abstractions: "Identity and packaging for Oatly. Motion design for Apple." Specifics > adjectives.
4216
+ - Manifesto beliefs are welcome: "We believe the web can feel handmade." A point of view is the product.
4217
+
4218
+ ---
4219
+
4220
+ ## Anti-patterns
4221
+
4222
+ - **Stock photos of abstract people in coworking spaces.** Real work, real photography only.
4223
+ - **"Award-winning" as a tagline without specific awards.** Name the awards or delete the word.
4224
+ - **Long paragraphs of marketing copy on the home page.** Show the work. Talk about it only if asked (About page).
4225
+ - **Carousel of client logos that auto-rotates.** Static grid is better \u2014 visitor can read at their own pace.
4226
+ - **Every project identical in the grid.** Vary aspect ratios and size. Make the grid compositional, not a simple 3-up.
4227
+ - **Starred testimonials or review widgets.** Too B2B. Use pull quotes from specific clients instead.
4228
+ - **Purple-to-blue gradient anywhere.** Worst possible signal of "I used a default."
4229
+ - **Scroll hijacking through the whole site.** Use sparingly \u2014 one or two scroll-pinned sections maximum.
4230
+ - **Mystery-meat navigation.** Clever is bad here. "Work / About / Contact" is fine. Don't make visitors guess.
4231
+ - **No contact info.** The primary purpose of this site is to generate inquiries. Make it stupid-easy to contact you.
4232
+ `});var Ms,Os=S(()=>{Ms=`# Finance Clarity Archetype
4233
+
4234
+ 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.
4235
+
4236
+ These apps handle the user's MONEY. The UI must feel trustworthy, precise, calm. A single misaligned number or sloppy alignment undermines the whole product. Numbers are the hero \u2014 design around them.
4237
+
4238
+ ---
4239
+
4240
+ ## Theme
4241
+
4242
+ Default: **light-leaning.** Money apps overwhelmingly default to light (Stripe, Wise, QuickBooks) because clarity and trust are the job. Light mode signals transparency. Dark mode is offered as a toggle but rarely default.
4243
+
4244
+ Exceptions: crypto / trading platforms often default dark (Binance, Coinbase Pro) because the audience expects terminal aesthetics. If the app is crypto-specific and \`design.tone\` is \`dark-first\`, go dark.
4245
+
4246
+ All numeric display uses \`tabular-nums\` in BOTH themes \u2014 misaligned digits ruin credibility.
4247
+
4248
+ ---
4249
+
4250
+ ## Page Composition
4251
+
4252
+ ### Dashboard / Overview
4253
+
4254
+ The dashboard is a **financial statement at a glance**. Calm, organized, trustworthy.
4255
+
4256
+ - **Primary balance / total at the top.** Huge, centered or left-aligned. "Your balance: $12,847.53" in \`text-5xl md:text-6xl font-semibold tabular-nums\`.
4257
+ - **Delta row below:** "+$842 this month" with sparkline showing the trend. Small, matter-of-fact.
4258
+ - **Account / category list:** vertical list of sub-totals that roll up to the primary number. Each row: name + amount + optional progress bar.
4259
+ - **Recent transactions panel:** chronological list. Icon + merchant + amount + category. Negative in red, positive in green (or the app's semantic tokens).
4260
+ - **Primary actions:** "Send," "Request," "Pay bill," "Add transaction" \u2014 floating buttons or a horizontal row.
4261
+
4262
+ Layout:
4263
+ \`\`\`
4264
+ \u250C\u2500 header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
4265
+ \u2502 Dashboard Apr 19 \xB7 [Add entry] \u2502
4266
+ \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
4267
+ \u2502 \u2502
4268
+ \u2502 Balance \u2502
4269
+ \u2502 $12,847.53 \u2502
4270
+ \u2502 +$842.12 this month \u2581\u2582\u2583\u2582\u2584\u2585\u2586\u2585\u2586 \u2502
4271
+ \u2502 \u2502
4272
+ \u2502 \u250C\u2500 accounts (list) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
4273
+ \u2502 \u2502 \u25FC Checking $8,402.18 \u2502 \u2502
4274
+ \u2502 \u2502 \u25FC Savings $4,242.10 \u2502 \u2502
4275
+ \u2502 \u2502 \u25FC Credit card -$203.25 [Pay] \u2502 \u2502
4276
+ \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
4277
+ \u2502 \u2502
4278
+ \u2502 \u250C\u2500 recent transactions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
4279
+ \u2502 \u2502 Today \u2502 \u2502
4280
+ \u2502 \u2502 \u25CB Blue Bottle Coffee -$4.75 \u2502 \u2502
4281
+ \u2502 \u2502 \u25CB Spotify Premium -$10.99 \u2502 \u2502
4282
+ \u2502 \u2502 Yesterday \u2502 \u2502
4283
+ \u2502 \u2502 \u25CB Salary - Acme Corp +$4,250.00 \u2502 \u2502
4284
+ \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
4285
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
4286
+ \`\`\`
4287
+
4288
+ ### Transaction / Detail View
4289
+
4290
+ - **Amount hero:** the transaction amount in huge type at the top. Signed (+ or \u2212) with color.
4291
+ - **Metadata grid:** Date, Time, Category, Account, Reference. Two columns, key-value layout.
4292
+ - **Receipt / description:** if available, shown below. Merchant logo (or fallback initials) as a 48px circle.
4293
+ - **Related transactions:** "From this merchant" list or "Same category this month" sum.
4294
+ - **Action buttons:** "Dispute," "Categorize," "Export," "Split." Grouped at the bottom.
4295
+
4296
+ ### Lists / Tables (Transactions, Invoices, Expenses)
4297
+
4298
+ - **shadcn DataTable with sticky totals row at the bottom.** Running total always visible.
4299
+ - **Columns:** Date, Description, Category (badge), Amount (right-aligned, \`tabular-nums\`).
4300
+ - **Row hover:** subtle \`bg-muted/40\`. Click opens the detail sheet.
4301
+ - **Filter bar:** date range (preset chips: "This month", "Last 30 days", "YTD"), category multi-select, amount range slider.
4302
+ - **Bulk actions** when rows are selected: categorize, export, delete. Toolbar appears above the table.
4303
+ - **Empty state:** "No transactions in this period." With CTA to add one or change the filter.
4304
+
4305
+ ### Forms (Transaction Entry, Invoice Creation)
4306
+
4307
+ - **Amount input is the protagonist.** Big, centered, formatted as currency with live input mask. \`text-4xl font-semibold tabular-nums\`.
4308
+ - **Date / time pickers** use shadcn Calendar. Default to today.
4309
+ - **Category picker:** icon grid or searchable dropdown with common categories listed first.
4310
+ - **Description / notes:** textarea, smaller.
4311
+ - **Attachments:** receipt upload with drag-drop zone. Preview thumbnail on upload.
4312
+ - **Submit button at the bottom, full-width on mobile:** "Add transaction" or "Save invoice."
4313
+
4314
+ ### Reports / Analytics
4315
+
4316
+ - **Charts are informative, not decorative.** Line charts for trends, bar charts for comparisons, pie charts for category breakdowns (though bar is usually better).
4317
+ - **Recharts or visx** with semantic token colors \u2014 never hardcoded.
4318
+ - **Always include the source data** as a table below the chart. Users audit charts against raw numbers.
4319
+ - **Export options:** CSV, PDF, Excel. Button group above the chart.
4320
+
4321
+ ## Component Patterns
4322
+
4323
+ ### Numeric Display (THE core pattern)
4324
+
4325
+ Every number in this archetype follows strict rules:
4326
+
4327
+ - **\`tabular-nums\` everywhere.** Either the Tailwind class or \`font-variant-numeric: tabular-nums\` in CSS. Non-negotiable \u2014 without it, columns of numbers wobble.
4328
+ - **Currency formatting via \`Intl.NumberFormat\`:**
4329
+ \`\`\`ts
4330
+ const formatCurrency = (cents: number, currency = "USD") =>
4331
+ new Intl.NumberFormat("en-US", {
4332
+ style: "currency",
4333
+ currency,
4334
+ minimumFractionDigits: 2,
4335
+ }).format(cents / 100);
4336
+ \`\`\`
4337
+ - **Signed amounts:** always show \`+\` for positive, \`\u2212\` (minus, not hyphen) for negative. Color-coded.
4338
+ - **Currency symbol alignment:** \`$1,234.56\` with symbol attached, not spaced. Right-align amounts in tables.
4339
+ - **Decimals always shown** (2 places for most currencies). Never "$12" when it's actually "$12.00" \u2014 loses trust.
4340
+ - **Pending / estimate indicators:** "~" prefix or a subtle clock icon for pending amounts.
4341
+
4342
+ ### Cards
4343
+
4344
+ Clean, bordered, minimal shadow:
4345
+
4346
+ \`\`\`
4347
+ bg-card border border-border rounded-lg p-6
4348
+ \`\`\`
4349
+
4350
+ - \`rounded-lg\` \u2014 professional, not friendly.
4351
+ - Light mode: thin border, no shadow. Dark mode: subtle \`bg-card/60\` with slight backdrop-blur.
4352
+ - Header: label (\`text-sm text-muted-foreground uppercase tracking-wide\`).
4353
+ - Primary content: large number + delta.
4354
+ - Minimal visual noise. Money apps look cheap when over-designed.
4355
+
4356
+ ### Typography
4357
+
4358
+ - **Heading font:** geometric sans for modernity (Geist, S\xF6hne, Inter, IBM Plex Sans) or a refined serif for premium/wealth management feel (Source Serif 4, Fraunces).
4359
+ - **Body font:** same family or a humanist sans.
4360
+ - **Numeric font:** same as body IF it has great tabular figures. Otherwise, switch to a font with proper tabular support (e.g., \`font-variant-numeric: tabular-nums\` on Geist, Inter Tight, IBM Plex Mono for data-heavy).
4361
+ - **Scale:** headlines are restrained \u2014 this archetype doesn't shout. \`text-xl\` section headers, \`text-2xl/3xl\` page titles. The HUGE size goes on the money numbers, not the copy.
4362
+
4363
+ ### Status & Categories
4364
+
4365
+ - **Category badges:** pill-shaped, subtle background, category-specific icon. \`bg-primary/10 text-primary rounded-full px-2 py-1 text-xs\`.
4366
+ - **Transaction status:** small dot indicator + label. "\u25CF Pending" (yellow), "\u25CF Cleared" (green), "\u25CF Failed" (red). Plain, functional.
4367
+ - **Icons for transaction types:** lucide icons \u2014 \`ArrowUpRight\` for outgoing, \`ArrowDownLeft\` for incoming, \`Repeat\` for recurring, \`CreditCard\` for card payments.
4368
+
4369
+ ### Navigation
4370
+
4371
+ - **Sidebar** for desktop, bottom tab bar for mobile. Never hamburger-only \u2014 money apps need primary sections always visible.
4372
+ - **Sidebar items:** Dashboard, Transactions, Budgets, Goals, Reports, Settings. 5-7 items max.
4373
+ - **Top bar:** account switcher (if multi-account) + search + notifications bell + user menu.
4374
+ - **Global add button** (floating action button or prominent header button): "Add transaction / Create invoice."
4375
+
4376
+ ### Motion
4377
+
4378
+ - **Restrained.** Money apps are not the place for bouncy springs. Use \`ease-out\` with durations under 250ms.
4379
+ - **Number transitions use \`@number-flow/react\`** for satisfying count-up on dashboard load. Also on filter changes ("5 transactions" \u2192 "12 transactions").
4380
+ - **Sparkline draw animation** on mount: line draws from left to right over 600ms, \`strokeDasharray\` trick.
4381
+ - **Avoid:** large parallax, rotation, scale-on-hover for cards. This archetype values stability.
4382
+
4383
+ ---
4384
+
4385
+ ## Landing Page Signature
4386
+
4387
+ For fintech marketing pages. Restore patterns from ember-pricing / daylight-saas / crystal-clear and draw from Stripe / Wise / Mercury landing pages.
4388
+
4389
+ ### Signature 1: Hero with massive number (trust-building pattern)
4390
+
4391
+ - **Headline + subheadline left (~45%):** benefit-focused copy, restrained sans, one word emphasized (e.g., "Your money, clearly.").
4392
+ - **Big stat / chart preview right (~55%):** a dashboard snippet showing a real-looking balance + sparkline. OR a large isolated number: "$2.4B processed" with a primary-colored gradient backdrop.
4393
+ - Product UI preview in a bordered frame (\`rounded-xl border shadow-2xl\`), showing fake but realistic data (actual dollar amounts, merchant names, categories).
4394
+
4395
+ ### Signature 2: Pricing calculator (ember-pricing pattern)
4396
+
4397
+ For fintech with usage-based pricing:
4398
+
4399
+ - Full-width section with an **interactive pricing calculator** \u2014 slider or input for transaction volume, account count, or monthly charges.
4400
+ - Live-updating price display next to it, animated with \`NumberFlow\`.
4401
+ - 3 tier columns below the calculator, with the "Recommended" tier highlighted by \`ring-2 ring-primary\` + badge.
4402
+ - Each tier: price, "What's included" checkmark list, CTA button.
4403
+
4404
+ ### Signature 3: Trust markers strip
4405
+
4406
+ Below the hero, a horizontal strip of trust signals:
4407
+
4408
+ - Regulatory badges (FDIC insured, SOC 2, PCI DSS, GDPR) \u2014 small logos with labels.
4409
+ - Customer logos (small, grayscale) with a line like "Trusted by 5,000+ small businesses."
4410
+ - Security anchor: "Bank-level encryption. Never stored unencrypted."
4411
+ - Position BELOW the fold, not in the hero (it's supporting evidence, not a headline).
4412
+
4413
+ ### Signature 4: Comparison / before-after table
4414
+
4415
+ Finance products often replace something worse (a clunky bank, a spreadsheet, an old tool). Make the comparison explicit:
4416
+
4417
+ - Two-column "Before / After" layout OR a comparison table.
4418
+ - Before column: "Hours spent reconciling," "Stacks of receipts," "Paper invoices," etc.
4419
+ - After column: specific time / money savings with real numbers.
4420
+
4421
+ ### Signature 5: Mesh gradient background (subtle)
4422
+
4423
+ - ONE primary-color mesh gradient as background ambiance, at low opacity. 2-3 radial gradients positioned asymmetrically.
4424
+ - NOT aggressive purple/blue \u2014 usually a brand color (Stripe's indigo, Wise's bright green, Mercury's purple used subtly).
4425
+ - Stays behind everything; doesn't compete with content.
4426
+
4427
+ ---
4428
+
4429
+ ## Copy Voice
4430
+
4431
+ - **Clear, trustworthy, specific.** "Get paid in 3 days, not 30" not "Accelerate your cashflow solutions."
4432
+ - **Real dollar amounts and percentages.** "Save $1,200/year on fees" > "Save significantly."
4433
+ - **Regulatory context when relevant:** "FDIC insured up to $250k." "Funds held by a US-regulated bank partner." Transparency = trust.
4434
+ - **Plain-English financial terms:** "Interest earned" not "Yield accumulation." Users of money apps are not finance experts by default.
4435
+ - Avoid: "revolutionize," "fintech disruption," "next-generation banking." Dull but trustworthy beats hip.
4436
+
4437
+ ---
4438
+
4439
+ ## Anti-patterns
4440
+
4441
+ - **Non-tabular numbers in data tables.** Digits wobble \u2192 instant credibility loss.
4442
+ - **Charts without axis labels / source data.** Trust requires auditability.
4443
+ - **Aggressive animation around money displays.** Springs, bounces, scale pulses \u2014 all make the app feel tacky.
4444
+ - **Gradient text on dollar amounts.** Money should be crisp and readable, not decorative.
4445
+ - **Hidden decimals ("$12" instead of "$12.00").** Users notice. Feels imprecise.
4446
+ - **Red for ALL negative numbers everywhere.** In transaction lists, keep all amounts in \`foreground\` \u2014 use red only for losses on a P&L or refunds on a receipt. Overusing red creates alarm fatigue.
4447
+ - **Stock photos of business people shaking hands.** Dead fintech clich\xE9. Use product screenshots or geometric illustrations.
4448
+ - **Purple-to-blue gradients in the hero.** Fintech SaaS default. Use a brand color instead \u2014 or no gradient.
4449
+ - **Dark mode as default for a consumer money app.** Too crypto-coded. Default light, offer dark.
4450
+ - **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.
4451
+ `});function Us(t){let e=t?.archetype;if(!(!e||typeof e!="string"))return Ls[e]}var Ls,Lm,$s=S(()=>{"use strict";ys();ws();xs();Ss();Ps();Cs();_s();Ns();Ds();Os();Ls={"consumer-warm":{id:"consumer-warm",name:"Consumer Warm",description:"Personal, lifestyle, and wellness apps (habits, journals, recipes, mood, meditation)",content:bs},"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:ks},"education-structured":{id:"education-structured",name:"Education Structured",description:"Learning and knowledge apps (courses, quizzes, flashcards, LMS, tutorials)",content:Ts},"marketplace-browse":{id:"marketplace-browse",name:"Marketplace Browse",description:"Browsing and shopping apps (marketplaces, directories, shops, catalogs, food delivery)",content:Is},"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:As},"content-editorial":{id:"content-editorial",name:"Content Editorial",description:"Blogs, newsletters, magazines, publications, documentation, knowledge bases",content:Rs},"devtool-technical":{id:"devtool-technical",name:"Devtool Technical",description:"Developer tools, APIs, CLIs, SDKs, infrastructure, monitoring, AI/ML platforms",content:Es},"creative-showcase":{id:"creative-showcase",name:"Creative Showcase",description:"Portfolios, design agencies, creative studios, photographers, artists",content:js},"finance-clarity":{id:"finance-clarity",name:"Finance Clarity",description:"Fintech, invoicing, expense tracking, budgeting, payroll, accounting, banking",content:Ms}},Lm=Object.keys(Ls)});var qs,Fs=S(()=>{qs=`# Landing Page Rules
2452
4452
 
2453
4453
  These rules apply to every landing page. They are non-negotiable.
2454
4454
 
@@ -2845,7 +4845,7 @@ app/
2845
4845
  \`\`\`
2846
4846
 
2847
4847
  Each section is a separate component file. The page.tsx simply imports and stacks them. Do NOT build one monolithic page component.
2848
- `});var wr,br=_(()=>{wr=`# Design Doctrine
4848
+ `});var zs,Bs=S(()=>{zs=`# Design Doctrine
2849
4849
 
2850
4850
  This is the standard for every UI you generate. It is not aspirational. It is the floor.
2851
4851
 
@@ -2911,7 +4911,7 @@ Before submitting any UI file, read it with this checklist and regenerate if any
2911
4911
  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.**
2912
4912
 
2913
4913
  If any answer fails, the UI is not ready. Redesign \u2014 not tweak \u2014 the failing piece.
2914
- `});var xr,vr=_(()=>{xr=`# Typography
4914
+ `});var Ws,Hs=S(()=>{Ws=`# Typography
2915
4915
 
2916
4916
  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.
2917
4917
 
@@ -2999,7 +4999,7 @@ Pick exactly one:
2999
4999
  - **Mixed case display** \u2014 Title Case for proper nouns, SmallCaps for the verb. Editorial feel.
3000
5000
 
3001
5001
  Do NOT use: gradient text (\`bg-clip-text\`) on one word of the headline. That effect is the most overused generic-AI marker.
3002
- `});var Sr,kr=_(()=>{Sr="# 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 Ir,Pr=_(()=>{Ir=`# Motion
5002
+ `});var Vs,Gs=S(()=>{Vs="# 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 Ks,Js=S(()=>{Ks=`# Motion
3003
5003
 
3004
5004
  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.
3005
5005
 
@@ -3077,7 +5077,7 @@ Primary CTA has a 2px vertical translate on \`:active\` with 80ms \`--ease-quart
3077
5077
  ## One-Line Motion Rule
3078
5078
 
3079
5079
  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.
3080
- `});var _r,Tr=_(()=>{_r=`# Spatial Composition
5080
+ `});var Qs,Ys=S(()=>{Qs=`# Spatial Composition
3081
5081
 
3082
5082
  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.
3083
5083
 
@@ -3147,7 +5147,7 @@ To signal "designed, not templated":
3147
5147
  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.
3148
5148
 
3149
5149
  Pick: generous breathing (luxury / refined / editorial) OR controlled density (dashboards / industrial / brutalist). Never a mushy middle.
3150
- `});var Cr,Rr=_(()=>{Cr=`# Interaction
5150
+ `});var Zs,Xs=S(()=>{Zs=`# Interaction
3151
5151
 
3152
5152
  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.
3153
5153
 
@@ -3232,7 +5232,7 @@ Small moments that add up to "designed":
3232
5232
  - **Empty states** offer a specific next action, not "No data yet".
3233
5233
 
3234
5234
  These micro-interactions are the texture of a designed product. Ship them.
3235
- `});var Er,Ar=_(()=>{Er=`# UX Writing
5235
+ `});var ti,ei=S(()=>{ti=`# UX Writing
3236
5236
 
3237
5237
  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.
3238
5238
 
@@ -3308,14 +5308,14 @@ If there's a pricing page:
3308
5308
  ## The Footer
3309
5309
 
3310
5310
  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."
3311
- `});import{z as Is}from"zod";import{existsSync as jt,readFileSync as _s,writeFileSync as Ts,mkdirSync as lc}from"fs";import{join as mt,resolve as cc,dirname as dc}from"path";import{createConnection as pc}from"net";function uc(t){return new Promise(e=>{let s=pc({port:t,host:"127.0.0.1"});s.on("connect",()=>{s.destroy(),e(!0)}),s.on("error",()=>{e(!1)})})}function hc(t){let e=mt(t,"mistflow.json");if(!jt(e))return null;try{return JSON.parse(_s(e,"utf-8"))}catch{return null}}function Nr(t,e){let s=mt(t,"mistflow.json");Ts(s,JSON.stringify(e,null,2)+`
3312
- `)}function dn(t){return t.entity??t.name??"Unknown"}function gc(t){return t.length===0?"":typeof t[0]=="string"?t.join(", "):t.map(e=>`${e.name} (${e.type})`).join(", ")}function jr(t){return t.path??t.route??t.name??""}function fc(t){let e=(t||"text").toLowerCase();return e==="string"||e==="varchar"||e==="char"?"text":e==="integer"||e==="int"||e==="number"||e==="float"||e==="decimal"||e==="double"?"number":e==="boolean"||e==="bool"?"boolean":e==="date"||e==="datetime"||e==="timestamp"?"date":e==="email"?"email":e==="url"||e==="uri"?"url":e==="enum"||e==="select"?"select":e==="text"||e==="longtext"||e==="textarea"?"textarea":"text"}function Dr(t,e){if(!t.entities||t.entities.length===0)return e;let s=t.entities.map(n=>n.toLowerCase());return e.filter(n=>{let o=dn(n).toLowerCase();return s.some(i=>o.includes(i)||i.includes(o))})}function yc(t,e){if(!t.pages||t.pages.length===0)return[];let s=t.pages.map(n=>n.toLowerCase());return e.filter(n=>{let o=(n.name??"").toLowerCase(),i=jr(n).toLowerCase();return s.some(r=>o.includes(r)||r.includes(o)||i.includes(r))})}function wc(t){let e=t.stepType;if(e&&bc.has(e))return e;if(t.integrationId)return"integration";let s=`${t.name} ${t.description}`.toLowerCase();return s.includes("crud")||s.includes("list")&&s.includes("create")?"crud":s.includes("auth")||s.includes("login")||s.includes("register")?"auth":s.includes("admin")&&(s.includes("panel")||s.includes("dashboard")||s.includes("manage")||s.includes("users"))?"admin":s.includes("dashboard")||s.includes("overview")||s.includes("analytics")?"dashboard":s.includes("schema")||s.includes("database")||s.includes("model")?"schema":s.includes("layout")||s.includes("sidebar")||s.includes("navigation")?"layout":s.includes("deploy")||s.includes("cloudflare")?"deploy":s.includes("organization")||s.includes("team")||s.includes("workspace")||s.includes("multi-tenant")||s.includes("invite member")?"multi-tenant":s.includes("landing")||s.includes("hero")||s.includes("marketing")||s.includes("homepage")?"landing":s.includes("design")||s.includes("theme")||s.includes("styling")||s.includes("ui polish")||s.includes("visual")?"design":"general"}function vc(t){switch(t){case"landing":case"design":return{min:6,max:10};case"integration":return{min:8,max:12};case"dashboard":case"admin":return{min:5,max:7};case"crud":case"multi-tenant":return{min:4,max:6};case"schema":return{min:3,max:4};case"layout":case"auth":return{min:3,max:5};case"deploy":return{min:1,max:2};default:return{min:4,max:6}}}function xc(t,e){let s=[];if(s.push("### Design choices (decided at plan time \u2014 follow these exactly):"),t.tone&&s.push(`- **App tone**: ${t.tone}`),t.fonts&&(s.push(`- **Heading font**: ${t.fonts.heading} (load from Google Fonts)`),s.push(`- **Body font**: ${t.fonts.body} (load from Google Fonts)`)),s.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."),s.push("- **Outbound URLs (invite links, email CTAs, absolute hrefs in server code):** use `process.env.BETTER_AUTH_URL` as the base. Do NOT use `process.env.NEXT_PUBLIC_APP_URL` \u2014 Next.js bakes every `NEXT_PUBLIC_*` literal into the production bundle at build time, so the scaffolded localhost default ships to prod and invite emails land on `http://localhost:3000`. Client components should use relative URLs (they resolve to the current origin automatically)."),t.borderRadius){let n={sharp:"2px",subtle:"6px",rounded:"12px",pill:"9999px"};s.push(`- **Border radius**: ${t.borderRadius} (${n[t.borderRadius]??t.borderRadius}) \u2014 set as --radius in globals.css`)}if(t.shadowStyle){let n={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"};s.push(`- **Shadow style**: ${t.shadowStyle} \u2014 ${n[t.shadowStyle]??t.shadowStyle}`)}if(t.cardStyle){let n={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"};s.push(`- **Card style**: ${t.cardStyle} \u2014 ${n[t.cardStyle]??t.cardStyle}`)}if(t.landingTone&&s.push(`- **Landing page tone**: ${t.landingTone}`),t.visualStrategy){let n=t.visualStrategy,o=t.heroPhoto!==!1,i=o&&!!n.heroImages?.length;if(s.push(""),s.push("### Visual strategy:"),i&&n.heroImages&&n.heroImages.length>0){s.push("**Hero image** \u2014 use this Unsplash photo as the landing page hero BACKGROUND:");let r=n.heroImages[0];s.push(`- URL: ${r.url}`),s.push(`- Alt text for img tag: "${r.alt||"Hero image"} \u2014 Photo by ${r.photographer} on Unsplash"`),s.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 o&&!n.heroImages?.length?s.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.'"):o||s.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(n.sectionImages?.length){s.push("**Section images available** \u2014 use these for feature sections, about sections, or testimonial backgrounds (NOT the hero):");for(let r of n.sectionImages)s.push(`- ${r.url} \u2014 alt: "${r.alt||"section image"} \u2014 Photo by ${r.photographer} on Unsplash"`)}(i||n.sectionImages?.length)&&s.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."),s.push(""),s.push("**Hero layout \u2014 split hero with product UI mockup (follow this exactly):**"),s.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."),s.push(""),s.push("```"),s.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"),s.push("\u2502 [Logo] [Sign In] [CTA \u2192] \u2502 \u2190 transparent nav, NOT sticky"),s.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"),s.push("\u2502 \u2502"),s.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"),s.push("\u2502 \u2502 Glassmorphic \u2502 \u2502"),s.push("\u2502 Big bold headline, \u2502 product mockup \u2502 \u2502"),s.push("\u2502 accent color on key word \u2502 card showing \u2502 \u2502"),s.push("\u2502 \u2502 real app data \u2502 \u2502"),s.push("\u2502 Description paragraph \u2502 (stats, table, \u2502 \u2502"),s.push("\u2502 \u2502 chart, etc.) \u2502 \u2502"),s.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"),s.push("\u2502 \u2502"),s.push("\u2502 500+ 25K+ 99% \u2502"),s.push("\u2502 Label Label Label \u2502"),s.push("\u2502 \u2502"),i?s.push("\u2502 \u2190 full-bleed photo bg + dark overlay behind all \u2192 \u2502"):s.push("\u2502 \u2190 preset gradient / glass background behind all \u2192 \u2502"),s.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"),s.push("```"),s.push(""),s.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"),s.push("**Right side (~45%)**: A glassmorphic floating card that previews what the app looks like inside:"),s.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)"),s.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"),s.push("- Must be DOMAIN-SPECIFIC: a library app shows book catalog rows, a gym app shows check-in stats, a CRM shows pipeline cards"),s.push("- Add subtle inner elements with `bg-white/5` or `bg-white/10` for depth"),s.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 s.push(""),s.push(gr),s.join(`
3313
- `)}async function kc(t){try{let e=await Jn("nextjs",t);return{reminders:e.reminders,skill:e.skill}}catch{return{reminders:`### ${t} step
5311
+ `});import{z as Ir}from"zod";import{existsSync as jt,readFileSync as Ar,writeFileSync as Cr,mkdirSync as Bc}from"fs";import{join as ht,resolve as zc,dirname as Hc}from"path";import{createConnection as Wc}from"net";function Gc(t){return new Promise(e=>{let r=Wc({port:t,host:"127.0.0.1"});r.on("connect",()=>{r.destroy(),e(!0)}),r.on("error",()=>{e(!1)})})}function Jc(t){let e=ht(t,"mistflow.json");if(!jt(e))return null;try{return JSON.parse(Ar(e,"utf-8"))}catch{return null}}function oi(t,e){let r=ht(t,"mistflow.json");Cr(r,JSON.stringify(e,null,2)+`
5312
+ `)}function po(t){return t.entity??t.name??"Unknown"}function Kc(t){return t.length===0?"":typeof t[0]=="string"?t.join(", "):t.map(e=>`${e.name} (${e.type})`).join(", ")}function ri(t){return t.path??t.route??t.name??""}function Yc(t){let e=(t||"text").toLowerCase();return e==="string"||e==="varchar"||e==="char"?"text":e==="integer"||e==="int"||e==="number"||e==="float"||e==="decimal"||e==="double"?"number":e==="boolean"||e==="bool"?"boolean":e==="date"||e==="datetime"||e==="timestamp"?"date":e==="email"?"email":e==="url"||e==="uri"?"url":e==="enum"||e==="select"?"select":e==="text"||e==="longtext"||e==="textarea"?"textarea":"text"}function ni(t,e){if(!t.entities||t.entities.length===0)return e;let r=t.entities.map(o=>o.toLowerCase());return e.filter(o=>{let n=po(o).toLowerCase();return r.some(i=>n.includes(i)||i.includes(n))})}function Qc(t,e){if(!t.pages||t.pages.length===0)return[];let r=t.pages.map(o=>o.toLowerCase());return e.filter(o=>{let n=(o.name??"").toLowerCase(),i=ri(o).toLowerCase();return r.some(s=>n.includes(s)||s.includes(n)||i.includes(s))})}function Zc(t){let e=t.stepType;if(e&&Xc.has(e))return e;if(t.integrationId)return"integration";let r=`${t.name} ${t.description}`.toLowerCase();return r.includes("crud")||r.includes("list")&&r.includes("create")?"crud":r.includes("auth")||r.includes("login")||r.includes("register")?"auth":r.includes("admin")&&(r.includes("panel")||r.includes("dashboard")||r.includes("manage")||r.includes("users"))?"admin":r.includes("dashboard")||r.includes("overview")||r.includes("analytics")?"dashboard":r.includes("schema")||r.includes("database")||r.includes("model")?"schema":r.includes("layout")||r.includes("sidebar")||r.includes("navigation")?"layout":r.includes("deploy")||r.includes("cloudflare")?"deploy":r.includes("organization")||r.includes("team")||r.includes("workspace")||r.includes("multi-tenant")||r.includes("invite member")?"multi-tenant":r.includes("landing")||r.includes("hero")||r.includes("marketing")||r.includes("homepage")?"landing":r.includes("design")||r.includes("theme")||r.includes("styling")||r.includes("ui polish")||r.includes("visual")?"design":"general"}function ed(t){switch(t){case"landing":case"design":return{min:6,max:10};case"integration":return{min:8,max:12};case"dashboard":case"admin":return{min:5,max:7};case"crud":case"multi-tenant":return{min:4,max:6};case"schema":return{min:3,max:4};case"layout":case"auth":return{min:3,max:5};case"deploy":return{min:1,max:2};default:return{min:4,max:6}}}function td(t,e){let r=[];if(r.push("### Design choices (decided at plan time \u2014 follow these exactly):"),t.tone&&r.push(`- **App tone**: ${t.tone}`),t.fonts&&(r.push(`- **Heading font**: ${t.fonts.heading} (load from Google Fonts)`),r.push(`- **Body font**: ${t.fonts.body} (load from Google Fonts)`)),r.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."),r.push("- **Outbound URLs (invite links, email CTAs, absolute hrefs in server code):** use `process.env.BETTER_AUTH_URL` as the base. Do NOT use `process.env.NEXT_PUBLIC_APP_URL` \u2014 Next.js bakes every `NEXT_PUBLIC_*` literal into the production bundle at build time, so the scaffolded localhost default ships to prod and invite emails land on `http://localhost:3000`. Client components should use relative URLs (they resolve to the current origin automatically)."),t.borderRadius){let o={sharp:"2px",subtle:"6px",rounded:"12px",pill:"9999px"};r.push(`- **Border radius**: ${t.borderRadius} (${o[t.borderRadius]??t.borderRadius}) \u2014 set as --radius in globals.css`)}if(t.shadowStyle){let o={flat:"No shadows \u2014 use borders for separation",subtle:"shadow-sm on cards, shadow on hover",elevated:"shadow-md on cards, shadow-lg on modals, layered depth",dramatic:"shadow-lg with colored tinting, bold depth"};r.push(`- **Shadow style**: ${t.shadowStyle} \u2014 ${o[t.shadowStyle]??t.shadowStyle}`)}if(t.cardStyle){let o={filled:"bg-card with subtle background fill, no visible border",bordered:"border border-border on white/transparent background",elevated:"bg-card shadow-md, no border, lifted appearance",glass:"bg-white/60 backdrop-blur-sm border border-white/20, translucent"};r.push(`- **Card style**: ${t.cardStyle} \u2014 ${o[t.cardStyle]??t.cardStyle}`)}if(t.landingTone&&r.push(`- **Landing page tone**: ${t.landingTone}`),t.visualStrategy){let o=t.visualStrategy,n=t.heroPhoto!==!1,i=n&&!!o.heroImages?.length;if(r.push(""),r.push("### Visual strategy:"),i&&o.heroImages&&o.heroImages.length>0){r.push("**Hero image** \u2014 use this Unsplash photo as the landing page hero BACKGROUND:");let a=o.heroImages[0];r.push(`- URL: ${a.url}`),r.push(`- Alt text for img tag: "${a.alt||"Hero image"} \u2014 Photo by ${a.photographer} on Unsplash"`),r.push("- Use as full-bleed background behind the entire hero section with a dark overlay (bg-black/60 or similar for readability). The photo is atmosphere and context, NOT the main visual \u2014 the glassmorphic product card sits on top.")}else n&&!o.heroImages?.length?r.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||r.push("**Hero background** \u2014 user opted for CSS-only (no photo). Use the design preset's specified gradient, animated background, or glassmorphism. No Unsplash.");if(o.sectionImages?.length){r.push("**Section images available** \u2014 use these for feature sections, about sections, or testimonial backgrounds (NOT the hero):");for(let a of o.sectionImages)r.push(`- ${a.url} \u2014 alt: "${a.alt||"section image"} \u2014 Photo by ${a.photographer} on Unsplash"`)}(i||o.sectionImages?.length)&&r.push("For image attribution: put photographer credit in the img alt text (already provided above) and add an HTML comment <!-- Images from Unsplash --> near the images. Do NOT add visible attribution text on the page.");let s=Us(t);s?(r.push(""),r.push(`### Page composition \u2014 ${s.name} archetype`),r.push(`This plan was classified as **${s.id}** \u2014 ${s.description}. The layouts below (landing, dashboard, lists, detail, forms) are PRESCRIPTIVE for this category of app. Follow them. Do NOT reach for the generic split-hero + glassmorphic-mockup pattern \u2014 that template was retired precisely because it produces the same cold AI-slop hero regardless of the app's intent.`),r.push(""),r.push(s.content)):(r.push(""),r.push("**No archetype was selected for this plan.** Fall back to a split-hero layout, but if this app clearly fits a category (personal/wellness, booking, B2B SaaS, marketplace, etc.), flag it to the user \u2014 the plan should have picked one of the ten archetypes in archetypes.ts."),r.push(""),r.push("**Hero layout \u2014 split hero with product UI mockup (follow this exactly):**"),r.push("The hero uses a split layout with text on the left and a product preview on the right."),r.push(""),r.push("```"),r.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"),r.push("\u2502 [Logo] [Sign In] [CTA \u2192] \u2502 \u2190 transparent nav, NOT sticky"),r.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"),r.push("\u2502 \u2502"),r.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"),r.push("\u2502 \u2502 Glassmorphic \u2502 \u2502"),r.push("\u2502 Big bold headline, \u2502 product mockup \u2502 \u2502"),r.push("\u2502 accent color on key word \u2502 card showing \u2502 \u2502"),r.push("\u2502 \u2502 real app data \u2502 \u2502"),r.push("\u2502 Description paragraph \u2502 (stats, table, \u2502 \u2502"),r.push("\u2502 \u2502 chart, etc.) \u2502 \u2502"),r.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"),r.push("\u2502 \u2502"),r.push("\u2502 500+ 25K+ 99% \u2502"),r.push("\u2502 Label Label Label \u2502"),r.push("\u2502 \u2502"),i?r.push("\u2502 \u2190 full-bleed photo bg + dark overlay behind all \u2192 \u2502"):r.push("\u2502 \u2190 preset gradient / glass background behind all \u2192 \u2502"),r.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"),r.push("```"),r.push(""),r.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"),r.push("**Right side (~45%)**: A floating card that previews what the app looks like inside. Build realistic fake app data using styled divs \u2014 stat cards, mini table rows, schedule blocks, or charts that match what this specific app's dashboard shows. Must be DOMAIN-SPECIFIC."),r.push("**Stats row**: 3 proof-point numbers at the bottom of the left side."))}return r.push(""),r.push(fs),r.join(`
5313
+ `)}async function od(t){try{let e=await Ko("nextjs",t);return{reminders:e.reminders,skill:e.skill}}catch{return{reminders:`### ${t} step
3314
5314
  - Follow existing patterns in the codebase
3315
- - Server Components by default, "use client" only when interactivity is needed`,skill:""}}}async function Sc(t,e,s,n,o,i){let r=[];r.push(`## Step ${t.number}: ${t.name}`),r.push(""),r.push("### What to build:"),r.push(t.description),r.push(""),e.primaryAction&&(r.push("### Primary user action (non-negotiable):"),r.push(`- **Core action**: ${e.primaryAction.action}`),r.push(`- **User flow**: ${e.primaryAction.flow}`),r.push(`- **Dashboard must show**: ${e.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(o);if(e.design&&l?(r.push(xc(e.design,{hasDesignPreset:!!e.landingDesign&&(o==="landing"||o==="design")})),r.push("")):e.design&&!l&&(r.push("### Design tokens (for reference only \u2014 this step is not UI-focused):"),e.design.fonts&&r.push(`- Fonts: ${e.design.fonts.heading} / ${e.design.fonts.body}`),r.push("- Colors come from CSS variables (`--color-primary`, `--color-background`, etc.) \u2014 not Tailwind palette names."),r.push("")),i){let b=zo(i);if(b.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 f of b){let k=f.replace(i,"").replace(/^\//,"");r.push(`- \`${k}\``)}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("")}}e.roles&&Array.isArray(e.roles)&&e.roles.length>0&&(r.push("### Role system (from plan):"),r.push(`- Roles: ${e.roles.join(", ")}`),r.push(`- Default role for new signups: ${e.defaultRole??e.roles[0]}`),r.push("- Role helpers are in `lib/roles.ts` \u2014 use `getUserRole()` and `hasRole()` for access checks"),r.push("")),e.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("")),e.language&&(r.push(`### Language: ${e.language}`),r.push(`ALL user-facing text must be written in ${e.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 ${e.language}.`),r.push(""));let d=["landing","design","auth","general","crud","dashboard"];e.audienceType&&d.includes(o)&&(e.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("")):e.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("")):e.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(""))),s.length>0&&(r.push("### Already completed:"),s.forEach(b=>r.push(`- ${b}`)),r.push(""));let h=e.dataModel?Dr(t,e.dataModel):[];h.length>0&&(r.push("### Data model (from plan):"),h.forEach(b=>{let f=dn(b),k=gc(b.fields);r.push(`- **${f}**: ${k}`),r.push(` Schema file: \`db/schema/${f.toLowerCase().replace(/\s+/g,"-")}.ts\``)}),r.push(""));let m=e.pages?yc(t,e.pages):[];if(m.length>0&&(r.push("### Pages to create/update:"),m.forEach(b=>{let f=b.description?` \u2014 ${b.description}`:"";r.push(`- \`${jr(b)}\`${f}`)}),r.push("")),o==="crud"&&h.length>0&&h.forEach(b=>{let f=dn(b),k=f.toLowerCase().replace(/\s+/g,"-"),R=k.endsWith("s")?k:`${k}s`;r.push(`### Files for ${f} 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/${k}-table-columns.tsx\``),r.push(`- Form: \`components/${k}-form.tsx\``),r.push("")}),l){r.push("## Design Doctrine (the standard for every UI step)"),r.push(""),r.push(wr),r.push(""),r.push("## Design Reference Library"),r.push(""),r.push("### Typography"),r.push(xr),r.push(""),r.push("### Color"),r.push(Sr),r.push(""),r.push("### Motion"),r.push(Ir),r.push(""),r.push("### Spatial Composition"),r.push(_r),r.push(""),r.push("### Interaction"),r.push(Cr),r.push(""),r.push("### UX Writing"),r.push(Er),r.push("");let b=i?mt(i,"DESIGN.md"):void 0,f=b&&jt(b)?(()=>{try{return _s(b,"utf-8")}catch{return null}})():null;f&&(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(f),r.push(""))}(o==="landing"||o==="design")&&(r.push(yr),r.push(""));let p=t.integrationId?lt(t.integrationId):void 0;if(p){let b=ct(p.id);if(r.push("### Integration blueprint (follow this closely):"),r.push(""),r.push(`Using integration: **${p.name}** (${p.category})`),b?.docsUrl&&r.push(`Official docs: ${b.docsUrl}`),b?.envVars?.length){r.push(""),r.push("**Required environment variables:**");for(let f of b.envVars)r.push(`- \`${f.key}\`: ${f.description} \u2014 Get it at ${f.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.")}b?.packages?.length&&(r.push(""),r.push(`**Packages to install:** \`npm install ${b.packages.join(" ")}\``)),r.push(""),r.push("---"),r.push(p.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:y,skill:v}=await kc(o);return r.push(y),r.push(""),v&&!(o==="landing"&&e.landingDesign==="freeform")&&(r.push(`### ${o} reference:`),r.push(v),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(`
3316
- `)}async function Pc(t){let{projectPath:e,step:s}=t,n=cc(e??process.cwd()),o=hc(n);if(!o)return Re(n);if(!jt(mt(n,"node_modules")))return c(`Dependencies are not installed at ${n}. Call mist_install and projectPath='${n}' before running implement. This is a one-time setup step after init.`,!0);try{let{ensureBackendRegistered:x,ensureShadcnComponents:$}=await Promise.resolve().then(()=>(ns(),ts));await x(n);let H=await $(n);H.failed?console.error(`[implement] ${H.failed}`):H.installed.length>0&&console.error(`[implement] installed ${H.installed.length} shadcn components`)}catch(x){console.error("[implement] self-heal skipped:",x instanceof Error?x.message:String(x))}let i=o.plan;if(!i||!i.steps||i.steps.length===0)return c("No project plan found. Start by describing your app idea first \u2014 the AI will create a plan for you.",!0);let r,a=i.steps.find(x=>x.status==="in_progress");if(a){let x=i.steps.findIndex($=>$.number===a.number);x!==-1&&(i.steps[x].status="completed",r=`Auto-completed step ${a.number} (${a.name})`,Nr(n,o))}let l;if(s!==void 0){if(l=i.steps.find(x=>x.number===s),!l)return c(`Step ${s} not found. The plan has ${i.steps.length} steps (numbered ${i.steps[0].number} to ${i.steps[i.steps.length-1].number}).`,!0)}else if(l=i.steps.find(x=>x.status!=="completed"),!l)return c(JSON.stringify({message:"All plan steps are completed!",completedSteps:i.steps.map(x=>x.name),nextAction:"NEXT: Deploy the app now. Call mist_deploy action='deploy'. Do NOT suggest localhost or ask the user \u2014 just deploy."}));let d=i.steps.filter(x=>x.status==="completed").map(x=>`Step ${x.number}: ${x.name}`),{readLocalState:h}=await Promise.resolve().then(()=>(Qe(),_t)),m=h(n),p=wc(l),y=[];if((p==="crud"||p==="schema")&&i.dataModel&&l.entities&&l.entities.length>0){let x=Dr(l,i.dataModel);for(let $ of x){let H=dn($);try{let G=($.fields||[]).map(D=>typeof D=="string"?{name:D,type:"text"}:{name:D.name,type:fc(D.type),required:D.required!==!1});if(G.length===0)continue;let oe=o.dbProvider==="neon"?"nextjs-neon":"nextjs",E=await Yn(oe,H,G),C=0,re=0;for(let D of E.files){let Z=mt(n,D.path);if(jt(Z)){re++;continue}lc(dc(Z),{recursive:!0}),Ts(Z,D.content),C++}let pe=mt(n,"db","index.ts");if(jt(pe)){let D=_s(pe,"utf-8");D.includes(E.dbExport)||Ts(pe,D.trimEnd()+`
3317
- `+E.dbExport+`
3318
- `)}C>0?y.push(`${E.entityPascal} CRUD (${C} new files${re>0?`, ${re} existing skipped`:""})`):re>0&&y.push(`${E.entityPascal} CRUD (all ${re} files already exist \u2014 skipped)`)}catch(G){console.error(`Module generation failed for ${H} (non-fatal):`,G instanceof Error?G.message:G)}}}let v=await Sc(l,i,d,null,p,n),b=i.steps.findIndex(x=>x.number===l.number);if(b!==-1&&(o.plan.steps[b].status="in_progress",Nr(n,o)),m&&o.projectId){let{syncRemoteState:x}=await Promise.resolve().then(()=>(Qe(),_t));x(o.projectId,m).catch(()=>{})}let f=i.steps.every(x=>x.status==="completed"||x.number===l.number),k;f?k=`THIS IS THE LAST STEP. Rules for speed:
5315
+ - Server Components by default, "use client" only when interactivity is needed`,skill:""}}}async function rd(t,e,r,o,n,i){let s=[];s.push(`## Step ${t.number}: ${t.name}`),s.push(""),s.push("### What to build:"),s.push(t.description),s.push(""),e.primaryAction&&(s.push("### Primary user action (non-negotiable):"),s.push(`- **Core action**: ${e.primaryAction.action}`),s.push(`- **User flow**: ${e.primaryAction.flow}`),s.push(`- **Dashboard must show**: ${e.primaryAction.dashboardSurface}`),s.push(""),s.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."),s.push(""));let l=["landing","design","dashboard","crud","layout","admin","general","auth"].includes(n);if(e.design&&l?(s.push(td(e.design,{hasDesignPreset:!!e.landingDesign&&(n==="landing"||n==="design")})),s.push("")):e.design&&!l&&(s.push("### Design tokens (for reference only \u2014 this step is not UI-focused):"),e.design.fonts&&s.push(`- Fonts: ${e.design.fonts.heading} / ${e.design.fonts.body}`),s.push("- Colors come from CSS variables (`--color-primary`, `--color-background`, etc.) \u2014 not Tailwind palette names."),s.push("")),i){let b=Hn(i);if(b.length>0){s.push("### Approved wireframe (MUST READ before writing any files):"),s.push("The user approved a wireframe sketch before building. **Read these files NOW before writing any code for this step:**");for(let g of b){let x=g.replace(i,"").replace(/^\//,"");s.push(`- \`${x}\``)}s.push(""),s.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."),s.push(""),s.push("The wireframe is intentionally rough (grayscale, system fonts). Your job is to:"),s.push("1. **Keep the same layout structure** \u2014 same information hierarchy, same element placement, same sections in the same order"),s.push("2. **Apply the design tokens** \u2014 colors, fonts, shadows, radius from the plan design choices above"),s.push("3. **Elevate the visual quality** \u2014 make it feel designed for THIS specific app, not generic"),s.push("4. **Respect the HTML comments** \u2014 they explain WHY things are placed where they are"),s.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"),s.push("")}}e.roles&&Array.isArray(e.roles)&&e.roles.length>0&&(s.push("### Role system (from plan):"),s.push(`- Roles: ${e.roles.join(", ")}`),s.push(`- Default role for new signups: ${e.defaultRole??e.roles[0]}`),s.push("- Role helpers are in `lib/roles.ts` \u2014 use `getUserRole()` and `hasRole()` for access checks"),s.push("")),e.multiTenant&&(s.push("### Multi-tenant (from plan):"),s.push("- Organization tables are in `db/schema/organization.ts`"),s.push("- Org helpers are in `lib/org.ts` \u2014 use `getCurrentOrg()` to scope queries"),s.push("- All data queries MUST be scoped to the current org (filter by orgId)"),s.push("- Org switcher component is at `components/org-switcher.tsx`"),s.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."),s.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."),s.push("")),e.language&&(s.push(`### Language: ${e.language}`),s.push(`ALL user-facing text must be written in ${e.language}:`),s.push("- Page titles, headings, labels, button text, placeholder text"),s.push("- Navigation items, menu labels, footer text"),s.push("- Error messages, success messages, empty states"),s.push("- Landing page copy, marketing text, CTAs"),s.push("- Form labels and validation messages"),s.push("Code (variable names, comments, file names) stays in English."),s.push(`Set the HTML lang attribute to the appropriate locale code for ${e.language}.`),s.push(""));let d=["landing","design","auth","general","crud","dashboard"];e.audienceType&&d.includes(n)&&(e.audienceType==="b2c"?(s.push("### Audience: this app belongs to ONE business. The landing page talks TO their customers."),s.push("- Hero: what the customer gets ('Exceptional catering for your next event'), NOT what the tool does"),s.push("- CTAs: customer action ('Order Catering', 'Book Now'), NOT business action ('Get Started Free')"),s.push("- Testimonials: from customers ('They catered our wedding'), NOT from business owners"),s.push("- Features: customer benefits ('Specify your dietary needs'), NOT business benefits ('Track preferences')"),s.push("- Stats: social proof for customers ('2,400+ events served'), NOT internal metrics ('$48k revenue')"),s.push("- The business name IS the brand. Say it like a business homepage, not a SaaS onboarding."),s.push("")):e.audienceType==="b2b"?(s.push("### Audience: this is a SaaS platform. The landing page pitches TO business owners."),s.push("- Hero: the business pain + solution ('Catering orders managed in one place')"),s.push("- CTAs: business owner action ('Start Free Trial', 'Get Started')"),s.push("- Testimonials: from business owners who use the platform"),s.push("- Features: business benefits ('Track dietary preferences across all orders')"),s.push("- Stats: platform metrics ('500+ businesses', '50K+ orders processed')"),s.push("")):e.audienceType==="internal"&&(s.push("### Audience: internal staff tool. No marketing copy needed."),s.push("- No landing page. Auth page copy is functional: 'Sign in to continue'."),s.push("- Dashboard focuses on operational efficiency, not onboarding or sales."),s.push(""))),r.length>0&&(s.push("### Already completed:"),r.forEach(b=>s.push(`- ${b}`)),s.push(""));let h=e.dataModel?ni(t,e.dataModel):[];h.length>0&&(s.push("### Data model (from plan):"),h.forEach(b=>{let g=po(b),x=Kc(b.fields);s.push(`- **${g}**: ${x}`),s.push(` Schema file: \`db/schema/${g.toLowerCase().replace(/\s+/g,"-")}.ts\``)}),s.push(""));let u=e.pages?Qc(t,e.pages):[];if(u.length>0&&(s.push("### Pages to create/update:"),u.forEach(b=>{let g=b.description?` \u2014 ${b.description}`:"";s.push(`- \`${ri(b)}\`${g}`)}),s.push("")),n==="crud"&&h.length>0&&h.forEach(b=>{let g=po(b),x=g.toLowerCase().replace(/\s+/g,"-"),R=x.endsWith("s")?x:`${x}s`;s.push(`### Files for ${g} CRUD:`),s.push(`- List page: \`app/(dashboard)/${R}/page.tsx\` (Server Component)`),s.push(`- Detail page: \`app/(dashboard)/${R}/[id]/page.tsx\``),s.push(`- Create page: \`app/(dashboard)/${R}/new/page.tsx\``),s.push(`- Server Actions: \`app/(dashboard)/${R}/actions.ts\``),s.push(`- DataTable columns: \`components/${x}-table-columns.tsx\``),s.push(`- Form: \`components/${x}-form.tsx\``),s.push("")}),l){s.push("## Design Doctrine (the standard for every UI step)"),s.push(""),s.push(zs),s.push(""),s.push("## Design Reference Library"),s.push(""),s.push("### Typography"),s.push(Ws),s.push(""),s.push("### Color"),s.push(Vs),s.push(""),s.push("### Motion"),s.push(Ks),s.push(""),s.push("### Spatial Composition"),s.push(Qs),s.push(""),s.push("### Interaction"),s.push(Zs),s.push(""),s.push("### UX Writing"),s.push(ti),s.push("");let b=i?ht(i,"DESIGN.md"):void 0,g=b&&jt(b)?(()=>{try{return Ar(b,"utf-8")}catch{return null}})():null;g&&(s.push("### Design system (source of truth: DESIGN.md):"),s.push(""),s.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."),s.push(""),s.push(g),s.push(""))}(n==="landing"||n==="design")&&(s.push(qs),s.push(""));let p=t.integrationId?ct(t.integrationId):void 0;if(p){let b=dt(p.id);if(s.push("### Integration blueprint (follow this closely):"),s.push(""),s.push(`Using integration: **${p.name}** (${p.category})`),b?.docsUrl&&s.push(`Official docs: ${b.docsUrl}`),b?.envVars?.length){s.push(""),s.push("**Required environment variables:**");for(let g of b.envVars)s.push(`- \`${g.key}\`: ${g.description} \u2014 Get it at ${g.setupUrl}`);s.push(""),s.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.")}b?.packages?.length&&(s.push(""),s.push(`**Packages to install:** \`npm install ${b.packages.join(" ")}\``)),s.push(""),s.push("---"),s.push(p.prompt),s.push("---"),s.push(""),s.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."),s.push("")}let{reminders:f,skill:v}=await od(n);return s.push(f),s.push(""),v&&!(n==="landing"&&e.landingDesign==="freeform")&&(s.push(`### ${n} reference:`),s.push(v),s.push("")),l&&(s.push("## Self-Audit \u2014 run before submitting this file"),s.push(""),s.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.`),s.push(""),s.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.'),s.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."),s.push("3. **Centered single-column hero?** Pill \u2192 headline \u2192 subhead \u2192 two CTAs stacked vertically? \u2192 redesign asymmetrically (see spatial.md)."),s.push("4. **Three checkmark benefit bullets** below the hero CTAs? \u2192 remove the triplet pattern."),s.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."),s.push("6. **Inter, Geist, Roboto, Arial, or system-ui** as the primary font? \u2192 pick something distinctive (see typography.md)."),s.push("7. **Purple gradient on white background** anywhere? \u2192 that's the single most overused AI-design pattern. Replace."),s.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.'),s.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).'),s.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)."),s.push(""),s.push(`If every answer is "no, I'm good" \u2014 then submit.`),s.push("")),s.join(`
5316
+ `)}async function nd(t){let{projectPath:e,step:r}=t,o=zc(e??process.cwd()),n=Jc(o);if(!n)return Re(o);if(!jt(ht(o,"node_modules")))return c(`Dependencies are not installed at ${o}. Call mist_install and projectPath='${o}' before running implement. This is a one-time setup step after init.`,!0);try{let{ensureBackendRegistered:k,ensureShadcnComponents:L}=await Promise.resolve().then(()=>(rr(),or));await k(o);let K=await L(o);K.failed?console.error(`[implement] ${K.failed}`):K.installed.length>0&&console.error(`[implement] installed ${K.installed.length} shadcn components`)}catch(k){console.error("[implement] self-heal skipped:",k instanceof Error?k.message:String(k))}let i=n.plan;if(!i||!i.steps||i.steps.length===0)return c("No project plan found. Start by describing your app idea first \u2014 the AI will create a plan for you.",!0);let s,a=i.steps.find(k=>k.status==="in_progress");if(a){let k=i.steps.findIndex(L=>L.number===a.number);k!==-1&&(i.steps[k].status="completed",s=`Auto-completed step ${a.number} (${a.name})`,oi(o,n))}let l;if(r!==void 0){if(l=i.steps.find(k=>k.number===r),!l)return c(`Step ${r} not found. The plan has ${i.steps.length} steps (numbered ${i.steps[0].number} to ${i.steps[i.steps.length-1].number}).`,!0)}else if(l=i.steps.find(k=>k.status!=="completed"),!l)return c(JSON.stringify({message:"All plan steps are completed!",completedSteps:i.steps.map(k=>k.name),nextAction:"NEXT: Deploy the app now. Call mist_deploy action='deploy'. Do NOT suggest localhost or ask the user \u2014 just deploy."}));let d=i.steps.filter(k=>k.status==="completed").map(k=>`Step ${k.number}: ${k.name}`),{readLocalState:h}=await Promise.resolve().then(()=>(Ye(),At)),u=h(o),p=Zc(l),f=[];if((p==="crud"||p==="schema")&&i.dataModel&&l.entities&&l.entities.length>0){let k=ni(l,i.dataModel);for(let L of k){let K=po(L);try{let H=(L.fields||[]).map(z=>typeof z=="string"?{name:z,type:"text"}:{name:z.name,type:Yc(z.type),required:z.required!==!1});if(H.length===0)continue;let ye=n.dbProvider==="neon"?"nextjs-neon":"nextjs",J=await Yo(ye,K,H),N=0,O=0;for(let z of J.files){let G=ht(o,z.path);if(jt(G)){O++;continue}Bc(Hc(G),{recursive:!0}),Cr(G,z.content),N++}let pe=ht(o,"db","index.ts");if(jt(pe)){let z=Ar(pe,"utf-8");z.includes(J.dbExport)||Cr(pe,z.trimEnd()+`
5317
+ `+J.dbExport+`
5318
+ `)}N>0?f.push(`${J.entityPascal} CRUD (${N} new files${O>0?`, ${O} existing skipped`:""})`):O>0&&f.push(`${J.entityPascal} CRUD (all ${O} files already exist \u2014 skipped)`)}catch(H){console.error(`Module generation failed for ${K} (non-fatal):`,H instanceof Error?H.message:H)}}}let v=await rd(l,i,d,null,p,o),b=i.steps.findIndex(k=>k.number===l.number);if(b!==-1&&(n.plan.steps[b].status="in_progress",oi(o,n)),u&&n.projectId){let{syncRemoteState:k}=await Promise.resolve().then(()=>(Ye(),At));k(n.projectId,u).catch(()=>{})}let g=i.steps.every(k=>k.status==="completed"||k.number===l.number),x;g?x=`THIS IS THE LAST STEP. Rules for speed:
3319
5319
 
3320
5320
  1. Write ALL files using PARALLEL tool calls \u2014 batch multiple Write/Edit calls in a single message.
3321
5321
  2. Do NOT read files you already know (AGENTS.md, CLAUDE.md, mistflow.json, middleware.ts, lib/auth.ts, lib/db.ts).
@@ -3324,18 +5324,18 @@ A footer with the same five links ("Product / Pricing / Docs / Blog / Terms") is
3324
5324
  - app/page.tsx must be a real landing page, NOT a redirect to /login
3325
5325
  - middleware.ts must have "/" in PUBLIC_EXACT or PUBLIC_PREFIXES
3326
5326
  - Forms must use server actions (actions.ts with 'use server'), NOT setTimeout/simulate
3327
- 5. Call mist_build({ projectPath }) (fire-and-poll \u2014 re-call with the returned jobId until status 'complete'), then mist_deploy({ action: 'deploy', projectPath }). Do NOT pause between these two calls to ask the user \u2014 the build is expected, the deploy is expected, just chain them.`:k=`IMPLEMENT THIS STEP NOW. Rules for speed:
5327
+ 5. Call mist_build({ projectPath }) (fire-and-poll \u2014 re-call with the returned jobId until status 'complete'), then mist_deploy({ action: 'deploy', projectPath }). Do NOT pause between these two calls to ask the user \u2014 the build is expected, the deploy is expected, just chain them.`:x=`IMPLEMENT THIS STEP NOW. Rules for speed:
3328
5328
 
3329
5329
  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.
3330
5330
  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.
3331
5331
  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.
3332
5332
  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).
3333
- 5. After writing ALL files, call mist_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 R=vc(p),j={stepNumber:l.number,totalSteps:i.steps.length,estimatedMinutes:R,announcement:`Starting step ${l.number} of ${i.steps.length}: ${l.name}. This step usually takes ${R.min}\u2013${R.max} minutes.`},O=JSON.stringify({instruction:v,step:{number:l.number,name:l.name,description:l.description,status:"in_progress"},stepTiming:j,compactionGuidance:"If your context gets compacted mid-step (common on long builds), call mist_implement again immediately after the compaction finishes. The tool finds the in-progress step and returns fresh context \u2014 you don't need to re-read files or re-derive state.",...r?{autoCompleted:r}:{},...y.length>0?{generatedModules:y,generatedNote:"These CRUD modules were pre-generated. Review and customize them instead of writing from scratch. Focus on: business logic in actions.ts, UI polish, and wiring navigation."}:{},progress:`${d.length}/${i.steps.length} steps done`,nextAction:k});return await uc(3e3)?Ks("http://localhost:3000",O):c(O)}var mc,bc,Or,Mr=_(()=>{"use strict";K();ue();Kt();ds();hr();fr();br();vr();kr();Pr();Tr();Rr();Ar();mc=Is.object({projectPath:Is.string().optional().describe("Path to the project directory (default: cwd)"),step:Is.number().optional().describe("Specific step number to implement (default: next incomplete step)")});bc=new Set(["landing","design","dashboard","crud","layout","admin","auth","schema","integration","multi-tenant","deploy","general"]);Or={name:"mist_implement",description:"Execute the next step (or a specific step) from the app plan. Reads mistflow.json, finds the target step, and returns rich context and detailed implementation instructions for the host AI. Auto-commits a checkpoint before changes for safe undo. Use when the user says 'mist implement' or 'mist next step'.",inputSchema:mc,handler:Pc}});import{z as Be}from"zod";import{resolve as Ic}from"path";async function $r(t){let{projectPath:e,action:s,key:n,value:o,category:i,description:r,setupUrl:a}=t,l=Ic(e??process.cwd());if(!ee())return c("No Mistflow credentials found. Run mist_setup to connect your account.",!0);let h=$e(l)?.projectId;if(!h)return c("No project ID found. Deploy your project first with mist_deploy, or initialize with mist_plan + mist_init + mist_install.",!0);try{switch(s){case"set":return n?o?(await qn(h,n,o,{category:i,description:r,setupUrl:a}),c(JSON.stringify({set:!0,key:n,message:`Environment variable '${n}' has been set. It will be available on your next deployment.`}))):c("Value is required. Provide the env var value.",!0):c("Key is required. Provide the env var name like 'STRIPE_SECRET_KEY'.",!0);case"list":{let m=await Fn(h);return m.length===0?c(JSON.stringify({envVars:[],message:"No environment variables configured. Use action 'set' to add one."})):c(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 n?(await Bn(h,n),c(JSON.stringify({deleted:!0,key:n,message:`Environment variable '${n}' has been removed.`}))):c("Key is required. Provide the env var name to delete.",!0);default:return c(`Unknown action: ${s}. Use set, list, or delete.`,!0)}}catch(m){if(m instanceof L)return c(m.message,!0);let p=m instanceof Error?m.message:"An unexpected error occurred";return c(p,!0)}}var hm,Ur=_(()=>{"use strict";K();ue();nt();hm=Be.object({projectPath:Be.string().optional().describe("Path to the project directory (default: cwd)"),action:Be.enum(["set","list","delete"]).describe("Action to perform"),key:Be.string().optional().describe("Environment variable name (required for 'set' and 'delete')"),value:Be.string().optional().describe("Environment variable value (required for 'set')"),category:Be.string().optional().describe("Category for the env var (default: 'custom')"),description:Be.string().optional().describe("Description of what this env var is for"),setupUrl:Be.string().optional().describe("URL where the user can obtain this value (e.g. Stripe dashboard)")})});import{z as Dt}from"zod";import{resolve as Tc,join as _c}from"path";import{existsSync as Rc,readFileSync as Cc,writeFileSync as Ac}from"fs";function Rs(t,e){let s=_c(t,"mistflow.json");if(!Rc(s))return;let n;try{n=JSON.parse(Cc(s,"utf-8"))}catch{return}n.domains=e,Ac(s,JSON.stringify(n,null,2)+`
3334
- `)}async function Lr(t){let{projectPath:e,action:s,domain:n,domainId:o}=t,i=Tc(e??process.cwd());if(!ee())return c("No Mistflow credentials found. Run mist_setup to connect your account.",!0);let a=$e(i)?.projectId;if(!a)return c("No project ID found. Deploy your project first with mist_deploy, or initialize with mist_plan + mist_init + mist_install.",!0);try{switch(s){case"add":{if(!n)return c("Domain name is required. Provide the domain like 'myapp.com' or 'app.mycompany.com'.",!0);let l=await Mn(a,n),d=await Ue(a);return Rs(i,d.map(h=>({domain:h.domain,status:h.status}))),c(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 Ue(a);return l.length===0?c(JSON.stringify({domains:[],message:"No custom domains configured. Use action 'add' to add one."})):c(JSON.stringify({domains:l.map(d=>({id:d.id,domain:d.domain,status:d.status,ssl:d.ssl_status,error:d.error_message}))}))}case"verify":{if(!o){if(n){let m=(await Ue(a)).find(p=>p.domain===n);if(m){let p=await Gt(a,m.id);return c(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 c(`Domain '${n}' not found. Use action 'list' to see configured domains.`,!0)}return c("Provide either domainId or domain name to verify.",!0)}let l=await Gt(a,o),d=await Ue(a);return Rs(i,d.map(h=>({domain:h.domain,status:h.status}))),c(JSON.stringify({domain:l.domain,status:l.status,ssl:l.ssl_status,error:l.error_message,message:l.status==="active"?`Domain '${l.domain}' is active and serving traffic.`:`Domain '${l.domain}' is ${l.status}. Make sure DNS records are configured correctly.`}))}case"remove":{if(!o&&!n)return c("Provide either domainId or domain name to remove.",!0);let l=o;if(!l&&n){let m=(await Ue(a)).find(p=>p.domain===n);if(!m)return c(`Domain '${n}' not found.`,!0);l=m.id}await $n(a,l);let d=await Ue(a);return Rs(i,d.map(h=>({domain:h.domain,status:h.status}))),c(JSON.stringify({removed:!0,message:"Domain removed. It may take a few minutes for DNS changes to propagate."}))}default:return c(`Unknown action: ${s}. Use add, list, verify, or remove.`,!0)}}catch(l){if(l instanceof L)return c(l.message,!0);let d=l instanceof Error?l.message:"An unexpected error occurred";return c(d,!0)}}var km,Fr=_(()=>{"use strict";K();ue();nt();km=Dt.object({projectPath:Dt.string().optional().describe("Path to the project directory (default: cwd)"),action:Dt.enum(["add","list","verify","remove"]).describe("Action to perform"),domain:Dt.string().optional().describe("Domain name (required for 'add' and 'remove')"),domainId:Dt.string().optional().describe("Domain ID (required for 'verify' and 'remove')")})});import{z as Se}from"zod";var Ec,qr,Br=_(()=>{"use strict";K();Ur();Fr();Ec=Se.object({resource:Se.enum(["env","domain"]).describe("'env' manages app secrets and configuration values. 'domain' manages custom domains."),action:Se.string().describe("Action to perform. env: 'set', 'list', 'delete'. domain: 'add', 'list', 'verify', 'remove'."),projectPath:Se.string().optional().describe("Path to the project directory (default: cwd)"),key:Se.string().optional().describe("(env) Variable name"),value:Se.string().optional().describe("(env set) Variable value"),category:Se.string().optional().describe("(env set) Category"),description:Se.string().optional().describe("(env set) Description"),setupUrl:Se.string().optional().describe("(env set) URL to obtain the value"),domain:Se.string().optional().describe("(domain) Domain name"),domainId:Se.string().optional().describe("(domain) Domain ID")}),qr={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:Ec,handler:async t=>{let e=t;switch(e.resource){case"env":return $r({projectPath:e.projectPath,action:e.action,key:e.key,value:e.value,category:e.category,description:e.description,setupUrl:e.setupUrl});case"domain":return Lr({projectPath:e.projectPath,action:e.action,domain:e.domain,domainId:e.domainId});default:return c(`Unknown resource: ${e.resource}. Use env or domain.`,!0)}}}});import{spawn as Nc,execFileSync as jc}from"child_process";import{existsSync as pn,mkdirSync as Wr,openSync as Cs,closeSync as As,readSync as Dc,readFileSync as Gr,writeFileSync as Vr,renameSync as Jr,unlinkSync as Oc,readdirSync as Em,statSync as Mc}from"fs";import{homedir as Yr}from"os";import{join as ge,dirname as Kr}from"path";import{randomBytes as Qr,randomUUID as $c}from"crypto";function un(){return ge(Yr(),".mistflow","jobs")}function Lc(){return ge(Yr(),".mistflow","job-wrapper.cjs")}function Fc(){let t=Lc(),e=Kr(t);pn(e)||Wr(e,{recursive:!0});let s=`v${Xr}`;if(pn(t))try{if(Gr(t,"utf-8").includes(s))return t}catch{}let n=ge(e,`.job-wrapper.tmp.${Qr(6).toString("hex")}`);return Vr(n,Uc),Jr(n,t),t}function ht(t){return ge(un(),t)}function Zr(t){return ge(ht(t),"status.json")}function zr(t,e){let s=Zr(t),n=ge(Kr(s),`.status.tmp.${Qr(6).toString("hex")}`);try{Vr(n,JSON.stringify(e,null,2)+`
3335
- `),Jr(n,s)}catch(o){try{Oc(n)}catch{}throw o}}function qc(t){let e=Zr(t);if(!pn(e))return null;try{return JSON.parse(Gr(e,"utf-8"))}catch{return null}}async function mn(t){let e=`job_${$c().replace(/-/g,"").slice(0,12)}`,s=ht(e);Wr(s,{recursive:!0}),As(Cs(ge(s,"stdout.log"),"a")),As(Cs(ge(s,"stderr.log"),"a"));let n=new Date().toISOString(),o={id:e,type:t.type,status:"starting",pid:0,wrapperPid:0,startedAt:n,cmd:t.cmd,args:t.args,cwd:t.cwd};zr(e,o);let i=Fc(),r={...process.env,...t.env??{}},a=Nc("node",[i,s,t.cwd,t.cmd,...t.args],{detached:!0,stdio:"ignore",env:r});a.unref();let l={...o,wrapperPid:a.pid??0};return zr(e,l),l}function Bc(t){let e=t.trim();if(!e)return NaN;let s=Date.parse(e);return Number.isFinite(s)?s:NaN}function zc(t){let e=t.pid||t.wrapperPid;if(!e)return!1;try{process.kill(e,0)}catch{return!1}try{let s=jc("ps",["-o","lstart=","-p",String(e)],{encoding:"utf-8",stdio:["ignore","pipe","ignore"]}),n=Bc(s),o=Date.parse(t.startedAt);if(Number.isFinite(n)&&Number.isFinite(o)&&Math.abs(n-o)>2e3)return!1}catch{}return!0}function Hc(t,e){let s=Date.parse(t),n=e?Date.parse(e):Date.now();if(!Number.isFinite(s)||!Number.isFinite(n))return"0s";let o=Math.max(0,n-s),i=Math.floor(o/1e3);if(i<60)return`${i}s`;let r=Math.floor(i/60),a=i%60;return r<60?`${r}m ${a}s`:`${Math.floor(r/60)}h ${r%60}m`}function Hr(t,e){if(!pn(t))return"";let s=Mc(t);if(s.size===0)return"";let n=s.size>e?s.size-e:0,o=s.size-n,i=Cs(t,"r");try{let r=Buffer.alloc(o);return Dc(i,r,0,o,n),r.toString("utf-8")}finally{As(i)}}function Wc(t,e=200){let s=Hr(ge(ht(t),"stdout.log"),65536),n=Hr(ge(ht(t),"stderr.log"),64*1024),o=[];return s.trim()&&o.push(...s.split(`
3336
- `).slice(-e).map(i=>`[out] ${i}`)),n.trim()&&o.push(...n.split(`
3337
- `).slice(-e).map(i=>`[err] ${i}`)),o.filter(i=>i.trim().length>0).join(`
3338
- `)}function Ot(t){return{stdout:ge(ht(t),"stdout.log"),stderr:ge(ht(t),"stderr.log")}}async function hn(t){let e=qc(t);if(!e)return null;let s=e;return(e.status==="running"||e.status==="starting")&&!zc(e)&&(s={...e,status:"unknown_exit",endedAt:e.endedAt??new Date().toISOString()}),{...s,elapsed:Hc(s.startedAt,s.endedAt),logTail:Wc(t)}}var Xr,Uc,Es=_(()=>{"use strict";Xr=1,Uc=`// Generated by @mistflow-ai/mcp local-jobs (v${Xr}). Do not edit.
5333
+ 5. After writing ALL files, call mist_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 R=ed(p),D={stepNumber:l.number,totalSteps:i.steps.length,estimatedMinutes:R,announcement:`Starting step ${l.number} of ${i.steps.length}: ${l.name}. This step usually takes ${R.min}\u2013${R.max} minutes.`},j=JSON.stringify({instruction:v,step:{number:l.number,name:l.name,description:l.description,status:"in_progress"},stepTiming:D,compactionGuidance:"If your context gets compacted mid-step (common on long builds), call mist_implement again immediately after the compaction finishes. The tool finds the in-progress step and returns fresh context \u2014 you don't need to re-read files or re-derive state.",...s?{autoCompleted:s}:{},...f.length>0?{generatedModules:f,generatedNote:"These CRUD modules were pre-generated. Review and customize them instead of writing from scratch. Focus on: business logic in actions.ts, UI polish, and wiring navigation."}:{},progress:`${d.length}/${i.steps.length} steps done`,nextAction:x});return await Gc(3e3)?Qr("http://localhost:3000",j):c(j)}var Vc,Xc,si,ii=S(()=>{"use strict";X();ue();Qt();pr();gs();$s();Fs();Bs();Hs();Gs();Js();Ys();Xs();ei();Vc=Ir.object({projectPath:Ir.string().optional().describe("Path to the project directory (default: cwd)"),step:Ir.number().optional().describe("Specific step number to implement (default: next incomplete step)")});Xc=new Set(["landing","design","dashboard","crud","layout","admin","auth","schema","integration","multi-tenant","deploy","general"]);si={name:"mist_implement",description:"Execute the next step (or a specific step) from the app plan. Reads mistflow.json, finds the target step, and returns rich context and detailed implementation instructions for the host AI. Auto-commits a checkpoint before changes for safe undo. Use when the user says 'mist implement' or 'mist next step'.",inputSchema:Vc,handler:nd}});import{z as ze}from"zod";import{resolve as sd}from"path";async function ai(t){let{projectPath:e,action:r,key:o,value:n,category:i,description:s,setupUrl:a}=t,l=sd(e??process.cwd());if(!oe())return c("No Mistflow credentials found. Run mist_setup to connect your account.",!0);let h=Ue(l)?.projectId;if(!h)return c("No project ID found. Deploy your project first with mist_deploy, or initialize with mist_plan + mist_init + mist_install.",!0);try{switch(r){case"set":return o?n?(await Bo(h,o,n,{category:i,description:s,setupUrl:a}),c(JSON.stringify({set:!0,key:o,message:`Environment variable '${o}' has been set. It will be available on your next deployment.`}))):c("Value is required. Provide the env var value.",!0):c("Key is required. Provide the env var name like 'STRIPE_SECRET_KEY'.",!0);case"list":{let u=await qo(h);return u.length===0?c(JSON.stringify({envVars:[],message:"No environment variables configured. Use action 'set' to add one."})):c(JSON.stringify({envVars:u.map(p=>({key:p.key,category:p.category,description:p.description,hasValue:p.has_value})),message:`${u.length} environment variable(s) configured.`}))}case"delete":return o?(await zo(h,o),c(JSON.stringify({deleted:!0,key:o,message:`Environment variable '${o}' has been removed.`}))):c("Key is required. Provide the env var name to delete.",!0);default:return c(`Unknown action: ${r}. Use set, list, or delete.`,!0)}}catch(u){if(u instanceof $)return c(u.message,!0);let p=u instanceof Error?u.message:"An unexpected error occurred";return c(p,!0)}}var yh,li=S(()=>{"use strict";X();ue();rt();yh=ze.object({projectPath:ze.string().optional().describe("Path to the project directory (default: cwd)"),action:ze.enum(["set","list","delete"]).describe("Action to perform"),key:ze.string().optional().describe("Environment variable name (required for 'set' and 'delete')"),value:ze.string().optional().describe("Environment variable value (required for 'set')"),category:ze.string().optional().describe("Category for the env var (default: 'custom')"),description:ze.string().optional().describe("Description of what this env var is for"),setupUrl:ze.string().optional().describe("URL where the user can obtain this value (e.g. Stripe dashboard)")})});import{z as Ot}from"zod";import{resolve as id,join as ad}from"path";import{existsSync as ld,readFileSync as cd,writeFileSync as dd}from"fs";function _r(t,e){let r=ad(t,"mistflow.json");if(!ld(r))return;let o;try{o=JSON.parse(cd(r,"utf-8"))}catch{return}o.domains=e,dd(r,JSON.stringify(o,null,2)+`
5334
+ `)}async function ci(t){let{projectPath:e,action:r,domain:o,domainId:n}=t,i=id(e??process.cwd());if(!oe())return c("No Mistflow credentials found. Run mist_setup to connect your account.",!0);let a=Ue(i)?.projectId;if(!a)return c("No project ID found. Deploy your project first with mist_deploy, or initialize with mist_plan + mist_init + mist_install.",!0);try{switch(r){case"add":{if(!o)return c("Domain name is required. Provide the domain like 'myapp.com' or 'app.mycompany.com'.",!0);let l=await Lo(a,o),d=await $e(a);return _r(i,d.map(h=>({domain:h.domain,status:h.status}))),c(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 $e(a);return l.length===0?c(JSON.stringify({domains:[],message:"No custom domains configured. Use action 'add' to add one."})):c(JSON.stringify({domains:l.map(d=>({id:d.id,domain:d.domain,status:d.status,ssl:d.ssl_status,error:d.error_message}))}))}case"verify":{if(!n){if(o){let u=(await $e(a)).find(p=>p.domain===o);if(u){let p=await Vt(a,u.id);return c(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 c(`Domain '${o}' not found. Use action 'list' to see configured domains.`,!0)}return c("Provide either domainId or domain name to verify.",!0)}let l=await Vt(a,n),d=await $e(a);return _r(i,d.map(h=>({domain:h.domain,status:h.status}))),c(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&&!o)return c("Provide either domainId or domain name to remove.",!0);let l=n;if(!l&&o){let u=(await $e(a)).find(p=>p.domain===o);if(!u)return c(`Domain '${o}' not found.`,!0);l=u.id}await Uo(a,l);let d=await $e(a);return _r(i,d.map(h=>({domain:h.domain,status:h.status}))),c(JSON.stringify({removed:!0,message:"Domain removed. It may take a few minutes for DNS changes to propagate."}))}default:return c(`Unknown action: ${r}. Use add, list, verify, or remove.`,!0)}}catch(l){if(l instanceof $)return c(l.message,!0);let d=l instanceof Error?l.message:"An unexpected error occurred";return c(d,!0)}}var Ph,di=S(()=>{"use strict";X();ue();rt();Ph=Ot.object({projectPath:Ot.string().optional().describe("Path to the project directory (default: cwd)"),action:Ot.enum(["add","list","verify","remove"]).describe("Action to perform"),domain:Ot.string().optional().describe("Domain name (required for 'add' and 'remove')"),domainId:Ot.string().optional().describe("Domain ID (required for 'verify' and 'remove')")})});import{z as Te}from"zod";var pd,pi,ui=S(()=>{"use strict";X();li();di();pd=Te.object({resource:Te.enum(["env","domain"]).describe("'env' manages app secrets and configuration values. 'domain' manages custom domains."),action:Te.string().describe("Action to perform. env: 'set', 'list', 'delete'. domain: 'add', 'list', 'verify', 'remove'."),projectPath:Te.string().optional().describe("Path to the project directory (default: cwd)"),key:Te.string().optional().describe("(env) Variable name"),value:Te.string().optional().describe("(env set) Variable value"),category:Te.string().optional().describe("(env set) Category"),description:Te.string().optional().describe("(env set) Description"),setupUrl:Te.string().optional().describe("(env set) URL to obtain the value"),domain:Te.string().optional().describe("(domain) Domain name"),domainId:Te.string().optional().describe("(domain) Domain ID")}),pi={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:pd,handler:async t=>{let e=t;switch(e.resource){case"env":return ai({projectPath:e.projectPath,action:e.action,key:e.key,value:e.value,category:e.category,description:e.description,setupUrl:e.setupUrl});case"domain":return ci({projectPath:e.projectPath,action:e.action,domain:e.domain,domainId:e.domainId});default:return c(`Unknown resource: ${e.resource}. Use env or domain.`,!0)}}}});import{spawn as ud,execFileSync as md}from"child_process";import{existsSync as uo,mkdirSync as gi,openSync as Rr,closeSync as Nr,readSync as hd,readFileSync as fi,writeFileSync as yi,renameSync as bi,unlinkSync as gd,readdirSync as jh,statSync as fd}from"fs";import{homedir as wi}from"os";import{join as fe,dirname as vi}from"path";import{randomBytes as xi,randomUUID as yd}from"crypto";function mo(){return fe(wi(),".mistflow","jobs")}function wd(){return fe(wi(),".mistflow","job-wrapper.cjs")}function vd(){let t=wd(),e=vi(t);uo(e)||gi(e,{recursive:!0});let r=`v${ki}`;if(uo(t))try{if(fi(t,"utf-8").includes(r))return t}catch{}let o=fe(e,`.job-wrapper.tmp.${xi(6).toString("hex")}`);return yi(o,bd),bi(o,t),t}function gt(t){return fe(mo(),t)}function Si(t){return fe(gt(t),"status.json")}function mi(t,e){let r=Si(t),o=fe(vi(r),`.status.tmp.${xi(6).toString("hex")}`);try{yi(o,JSON.stringify(e,null,2)+`
5335
+ `),bi(o,r)}catch(n){try{gd(o)}catch{}throw n}}function xd(t){let e=Si(t);if(!uo(e))return null;try{return JSON.parse(fi(e,"utf-8"))}catch{return null}}async function ho(t){let e=`job_${yd().replace(/-/g,"").slice(0,12)}`,r=gt(e);gi(r,{recursive:!0}),Nr(Rr(fe(r,"stdout.log"),"a")),Nr(Rr(fe(r,"stderr.log"),"a"));let o=new Date().toISOString(),n={id:e,type:t.type,status:"starting",pid:0,wrapperPid:0,startedAt:o,cmd:t.cmd,args:t.args,cwd:t.cwd};mi(e,n);let i=vd(),s={...process.env,...t.env??{}},a=ud("node",[i,r,t.cwd,t.cmd,...t.args],{detached:!0,stdio:"ignore",env:s});a.unref();let l={...n,wrapperPid:a.pid??0};return mi(e,l),l}function kd(t){let e=t.trim();if(!e)return NaN;let r=Date.parse(e);return Number.isFinite(r)?r:NaN}function Sd(t){let e=t.pid||t.wrapperPid;if(!e)return!1;try{process.kill(e,0)}catch{return!1}try{let r=md("ps",["-o","lstart=","-p",String(e)],{encoding:"utf-8",stdio:["ignore","pipe","ignore"]}),o=kd(r),n=Date.parse(t.startedAt);if(Number.isFinite(o)&&Number.isFinite(n)&&Math.abs(o-n)>2e3)return!1}catch{}return!0}function Td(t,e){let r=Date.parse(t),o=e?Date.parse(e):Date.now();if(!Number.isFinite(r)||!Number.isFinite(o))return"0s";let n=Math.max(0,o-r),i=Math.floor(n/1e3);if(i<60)return`${i}s`;let s=Math.floor(i/60),a=i%60;return s<60?`${s}m ${a}s`:`${Math.floor(s/60)}h ${s%60}m`}function hi(t,e){if(!uo(t))return"";let r=fd(t);if(r.size===0)return"";let o=r.size>e?r.size-e:0,n=r.size-o,i=Rr(t,"r");try{let s=Buffer.alloc(n);return hd(i,s,0,n,o),s.toString("utf-8")}finally{Nr(i)}}function Pd(t,e=200){let r=hi(fe(gt(t),"stdout.log"),65536),o=hi(fe(gt(t),"stderr.log"),64*1024),n=[];return r.trim()&&n.push(...r.split(`
5336
+ `).slice(-e).map(i=>`[out] ${i}`)),o.trim()&&n.push(...o.split(`
5337
+ `).slice(-e).map(i=>`[err] ${i}`)),n.filter(i=>i.trim().length>0).join(`
5338
+ `)}function Mt(t){return{stdout:fe(gt(t),"stdout.log"),stderr:fe(gt(t),"stderr.log")}}async function go(t){let e=xd(t);if(!e)return null;let r=e;return(e.status==="running"||e.status==="starting")&&!Sd(e)&&(r={...e,status:"unknown_exit",endedAt:e.endedAt??new Date().toISOString()}),{...r,elapsed:Td(r.startedAt,r.endedAt),logTail:Pd(t)}}var ki,bd,Er=S(()=>{"use strict";ki=1,bd=`// Generated by @mistflow-ai/mcp local-jobs (v${ki}). Do not edit.
3339
5339
  const { spawn } = require('node:child_process');
3340
5340
  const { readFileSync, writeFileSync, openSync, renameSync } = require('node:fs');
3341
5341
  const { join, dirname } = require('node:path');
@@ -3399,21 +5399,21 @@ child.on('error', (err) => {
3399
5399
  });
3400
5400
  process.exit(1);
3401
5401
  });
3402
- `});import{z as Ns}from"zod";import{resolve as Gc}from"path";import{existsSync as Vc}from"fs";import{join as Jc}from"path";var Yc,ei,ti=_(()=>{"use strict";K();Es();Yc=Ns.object({projectPath:Ns.string().optional().describe("Absolute path to the project. Required for the first call (to start the install)."),jobId:Ns.string().optional().describe("Job ID returned from a previous call. Present means poll; absent means start.")}).refine(t=>!!t.projectPath||!!t.jobId,{message:"Pass projectPath to start an install, or jobId to poll an existing one."}),ei={name:"mist_install",description:"Install a Mistflow project's npm dependencies with the fire-and-poll pattern. First call with { projectPath } starts the install and returns a jobId. Subsequent calls with { jobId } poll for progress (status: 'running' | 'complete' | 'failed'). Re-call while status is 'running' \u2014 each response is <1s so there is no 60s timeout risk.",inputSchema:Yc,handler:async t=>{let e=t;if(e.jobId){let a=await hn(e.jobId);if(!a)return c(JSON.stringify({status:"not_found",jobId:e.jobId,message:`No install job found for jobId '${e.jobId}'. Start a new one with { projectPath }.`}),!0);if(a.status==="running"||a.status==="starting")return c(JSON.stringify({status:"running",jobId:a.id,elapsed:a.elapsed,logTail:a.logTail,nextAction:"Still running. Call mist_install with the same jobId in ~15-30s to check progress."}));if(a.status==="complete")return c(JSON.stringify({status:"complete",jobId:a.id,elapsed:a.elapsed,exitCode:a.exitCode,nextAction:"Dependencies installed. Run mist_implement next to start executing plan steps."}));let l=Ot(a.id);return c(JSON.stringify({status:a.status,jobId:a.id,elapsed:a.elapsed,exitCode:a.exitCode,logTail:a.logTail,logPaths:l,nextAction:`npm install failed. Read the logTail for the error and fix it \u2014 usually a missing native dep or a version conflict. If the 200-line tail is truncated, read the full log from logPaths.stderr (${l.stderr}) with your file-read tool. After fixing, start a new install with { projectPath }.`}),!0)}let s=Gc(e.projectPath);if(!Vc(Jc(s,"package.json")))return c(`No package.json at ${s}. Confirm the path and that mist_init has scaffolded the project.`,!0);let i=`npm install && (${`npx --yes shadcn@latest add -y -o ${["button","card","input","label","form","dialog","table","dropdown-menu","badge","separator","skeleton","sheet","tabs","avatar","select","textarea","checkbox","switch","tooltip","popover","sonner"].join(" ")}`} || echo 'shadcn add failed \u2014 implement will retry lazily')`,r=await mn({type:"install",cmd:"sh",args:["-c",i],cwd:s,env:{NPM_CONFIG_LEGACY_PEER_DEPS:"true"}});return c(JSON.stringify({status:"running",jobId:r.id,startedAt:r.startedAt,cwd:s,nextAction:"Install started in the background (npm install + shadcn components). Call mist_install again with { jobId: '"+r.id+"' } in ~15-30s to check progress. Typical duration: 30-90s."}))}}});import{z as gn}from"zod";import{resolve as Kc,join as Ce}from"path";import{existsSync as Ae,readFileSync as ni,statSync as Qc}from"fs";function Xc(t){if(!(Ae(Ce(t,"open-next.config.ts"))||Ae(Ce(t,"open-next.config.js"))))return null;let s=Ce(t,".open-next","worker.js");if(!Ae(s))return`Build exited 0 but .open-next/worker.js is missing at ${s}. The Cloudflare adapter did not produce a worker bundle. Common causes: (1) open-next.config.ts is misconfigured, (2) a client component imports a Node-only API (fs, path, crypto node:* imports in 'use client' files), (3) the adapter crashed silently mid-build. Check the logTail above for "error"/"failed" messages, fix the offending file, and re-run mist_build.`;try{let n=Qc(s).size;if(n<si)return`Build exited 0 but .open-next/worker.js is only ${n} bytes (expected at least ${si}). The worker bundle is too small to be a working app \u2014 the adapter likely failed to bundle most of the code. Check open-next.config.ts and the logTail above for errors, fix them, and re-run mist_build.`}catch(n){return`Could not stat .open-next/worker.js: ${n instanceof Error?n.message:String(n)}. Re-run mist_build.`}return null}function ed(t){let e=Ce(un(),t,"stdout.log"),s=Ce(un(),t,"stderr.log"),n="";try{Ae(e)&&(n+=ni(e,"utf-8"))}catch{}try{Ae(s)&&(n+=`
3403
- `+ni(s,"utf-8"))}catch{}return n}function td(t){let e=/Module not found:\s*(?:Error:\s*)?Can't resolve ['"]([^'"]+)['"]/g,s=new Set;for(let n of t.matchAll(e)){let o=n[1];if(o.startsWith(".")||o.startsWith("@/")||o.startsWith("~/"))continue;let i=o.startsWith("@")?o.split("/").slice(0,2).join("/"):o.split("/")[0];i&&s.add(i)}return[...s]}var si,Zc,oi,ri=_(()=>{"use strict";K();Es();ps();si=10*1024;Zc=gn.object({projectPath:gn.string().optional().describe("Absolute path to the project. Required for the first call."),jobId:gn.string().optional().describe("Job ID from a previous call. Present means poll; absent means start."),script:gn.string().optional().describe("Optional override: run `npm run <script>` instead of the Cloudflare adapter. Default behavior is `npx @opennextjs/cloudflare build` when open-next.config.ts exists, else `npm run build`. Ignored on poll calls.")}).refine(t=>!!t.projectPath||!!t.jobId,{message:"Pass projectPath to start a build, or jobId to poll an existing one."});oi={name:"mist_build",description:"Run a Mistflow project's production build with the fire-and-poll pattern. First call with { projectPath } starts `npm run build` and returns a jobId. Subsequent calls with { jobId } poll status. On failure, the response surfaces structured errors (from parseBuildErrors) and any missingModules[] \u2014 chain into mist_install with those modules, then mist_build again, without asking the user.",inputSchema:Zc,handler:async t=>{let e=t;if(e.jobId){let l=await hn(e.jobId);if(!l)return c(JSON.stringify({status:"not_found",jobId:e.jobId,message:`No build job found for jobId '${e.jobId}'. Start a new one with { projectPath }.`}),!0);if(l.status==="running"||l.status==="starting")return c(JSON.stringify({status:"running",jobId:l.id,elapsed:l.elapsed,logTail:l.logTail,nextAction:"Build still running. Call mist_build again with the same jobId in ~20-40s."}));if(l.status==="complete"){let b=Xc(l.cwd);if(b){let f=Ot(l.id);return c(JSON.stringify({status:"failed",jobId:l.id,elapsed:l.elapsed,exitCode:l.exitCode,logTail:l.logTail,logPaths:f,error:b,nextAction:b+` Full build log: ${f.stdout} and ${f.stderr}.`}),!0)}return c(JSON.stringify({status:"complete",jobId:l.id,elapsed:l.elapsed,exitCode:l.exitCode,nextAction:"Build passed. Run mist_deploy next to ship \u2014 do NOT ask the user to confirm, the build is the approval gate."}))}let d=ed(l.id),h=nn(d),m=td(d),p=Ot(l.id),y=` Full log: ${p.stdout} and ${p.stderr}.`,v=m.length>0?`Build failed with missing modules: ${m.join(", ")}. Call mist_install with these packages (or chain mist_install { projectPath } to reinstall all), then call mist_build again. Do NOT ask the user.${y}`:h.length>0?`Build failed with structured errors (see errors[]). Fix the code, then call mist_build again.${y}`:`Build failed. Read the logTail for the error, or call mist_debug with the failed jobId to extract structured errors.${y}`;return c(JSON.stringify({status:l.status,jobId:l.id,elapsed:l.elapsed,exitCode:l.exitCode,errors:h,missingModules:m,logTail:l.logTail,logPaths:p,nextAction:v}),!0)}let s=Kc(e.projectPath);if(!Ae(Ce(s,"package.json")))return c(`No package.json at ${s}. Run mist_init first.`,!0);if(!Ae(Ce(s,"node_modules")))return c(`node_modules not installed. Run mist_install { projectPath: '${s}' } first.`,!0);let n,o,i,r=Ae(Ce(s,"open-next.config.ts"))||Ae(Ce(s,"open-next.config.js"));e.script?(n="npm",o=["run",e.script],i=e.script):r?(n="npx",o=["-y","@opennextjs/cloudflare","build"],i="@opennextjs/cloudflare build"):(n="npm",o=["run","build"],i="build");let a=await mn({type:"build",cmd:n,args:o,cwd:s});return c(JSON.stringify({status:"running",jobId:a.id,startedAt:a.startedAt,cwd:s,script:i,nextAction:`Build started. Call mist_build again with { jobId: '${a.id}' } in ~30s to check progress. Typical duration: 30-90s on a fresh scaffold (Cloudflare adapter), 10-30s on subsequent builds.`}))}}});import{existsSync as nd,readFileSync as sd}from"fs";import{join as od}from"path";import{z as fn}from"zod";function id(t){let e=od(t,"mistflow.json");if(!nd(e))return null;try{return JSON.parse(sd(e,"utf-8"))}catch{return null}}async function ii(t){try{let e=await fetch(t,{redirect:"follow",signal:AbortSignal.timeout(15e3)}),s=await e.text();return{status:e.status,body:s}}catch(e){return{status:0,body:String(e)}}}async function ad(t,e,s){try{let n=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json",...s??{}},body:JSON.stringify(e),redirect:"follow",signal:AbortSignal.timeout(15e3)}),o=await n.text(),i;try{i=JSON.parse(o)}catch{}return{status:n.status,json:i}}catch{return{status:0}}}async function ai(t){try{let e=await t.screenshot({type:"png"});return Buffer.from(e).toString("base64")}catch{return}}async function gt(t,e,s){let n=[],o=i=>{i.type()==="error"&&n.push(i.text())};t.on("console",o);try{let i=await s(),r=await ai(t);return{name:e,status:i.pass?"pass":"fail",detail:i.detail,fix:i.fix,screenshot:r,consoleErrors:n.length>0?n:void 0}}catch(i){let r=await ai(t);return{name:e,status:"fail",detail:`Unexpected error: ${i instanceof Error?i.message:String(i)}`,screenshot:r,consoleErrors:n.length>0?n:void 0}}finally{t.removeListener("console",o)}}async function ld(t){let e=t.projectPath??process.cwd(),s=id(e),n=t.url;if(n||(n=s?.deploy?.url),!n)return c("No deploy URL found. Deploy the app first with mist_deploy, then call mist_qa.",!0);n.startsWith("http")||(n=`https://${n}`);let o=s?.projectId,i=[],r=await ii(`${n}/api/health`);if(r.status!==200)return i.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."}),yn(n,i);i.push({name:"Health endpoint",status:"pass",detail:"Returns 200"});let a=await ii(`${n}/api/auth/ok`);if(a.status!==200)return i.push({name:"Auth system",status:"fail",detail:`Auth endpoint returns ${a.status}`,fix:"Better Auth is not working. Check lib/auth.ts, lib/db.ts, and that your database env vars are set."}),yn(n,i);i.push({name:"Auth system",status:"pass",detail:"Better Auth running"});let l,d,h;if(o){let y=await Un(o);if(y){console.error("[qa] Calling seed endpoint for session token");let v=await ad(`${n}/api/admin/seed`,{token:y.seedToken,email:y.email,password:"QaTemp1!"});v.status===200&&v.json?(l=v.json.sessionToken,d=v.json.email,v.json.seeded?(h=v.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 ${v.status}`)}}if(!l)return i.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."}),yn(n,i);let m,p;try{let{getIsolatedContext:y}=await Promise.resolve().then(()=>(qt(),_n)),v=await y();m=v.context,p=v.page}catch(y){let v=y instanceof Error?y.message:String(y);return c(JSON.stringify({status:"cannot_verify",url:n,deployed:!0,reason:"App deployed successfully, but QA could not verify it via automated browser testing because Playwright is not installed locally.",detail:v,httpChecks:i.map(({screenshot:b,...f})=>f),fix:"Run: npx playwright install chromium",instruction:[`The app deployed and is live at ${n}. That part succeeded.`,"What DID NOT succeed: automated QA verification. Playwright is not installed locally, so we could not open the app in a browser and check that the landing renders, signup works, and the dashboard loads.","Tell the user BOTH facts clearly \u2014 don't conflate 'deployed' with 'verified':",` "Your app is live at ${n}. I couldn't run automated QA because Playwright isn't installed locally. Run \`npx playwright install chromium\` (one-time, ~150MB) and I'll verify it for you \u2014 or just open the URL and try it yourself."`,"HTTP checks (health/auth endpoints) passed \u2014 those prove the server is responding. They do not prove the UI renders or user flows work.","After Playwright is installed, call mist_qa again for full verification."].join(`
3404
- `)}),!1)}try{let y=await gt(p,"Landing page",async()=>{await p.goto(n,{waitUntil:"domcontentloaded",timeout:3e4}),await p.waitForLoadState("networkidle").catch(()=>{});let b=await p.evaluate(()=>{let k=document.body;if(!k)return"";let R=document.createTreeWalker(k,NodeFilter.SHOW_TEXT),j="",S;for(;S=R.nextNode();){let O=S.parentElement;if(O){let U=window.getComputedStyle(O);U.display!=="none"&&U.visibility!=="hidden"&&parseFloat(U.opacity)>0&&(j+=S.textContent?.trim()+" ")}}return j.trim()});if(b.length<50)return{pass:!1,detail:`Landing page appears blank (${b.length} chars visible). Likely a CSS/JS rendering issue.`,fix:"Common cause: motion/react animations with opacity:0 and whileInView that never trigger on Mistflow Cloud's edge runtime (no Intersection Observer). Replace with CSS animations or set initial={{ opacity: 1 }}."};let f=p.url();return f.includes("/login")||f.includes("/sign-in")?{pass:!1,detail:"Root URL redirects to login instead of showing a landing page",fix:"Check middleware.ts: '/' must be in PUBLIC_EXACT. Check app/page.tsx: must be a landing page, not a redirect."}:{pass:!0,detail:`Renders visible content (${b.length} chars)`}});i.push(y);let v=!1;if(h){let b=await gt(p,"Login",async()=>{await p.goto(`${n}/login`,{waitUntil:"domcontentloaded",timeout:15e3}),await p.waitForLoadState("networkidle").catch(()=>{});let f=p.locator('input[type="email"], input[name="email"], input[placeholder*="email" i]'),k=p.locator('input[type="password"], input[name="password"]');try{await f.first().waitFor({state:"visible",timeout:1e4})}catch{return{pass:!1,detail:"Login page has no visible email input field",fix:"Check app/(auth)/login/page.tsx renders a login form with email and password inputs."}}await f.first().fill(d),await k.first().fill(h),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(j=>!j.pathname.includes("/login"),{timeout:1e4})}catch{let j=await p.locator('[role="alert"], .text-red-500, .text-destructive, [data-error]').first().textContent().catch(()=>null);return{pass:!1,detail:j?`Login failed: ${j}`:"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 v=!0,{pass:!0,detail:`Logged in, redirected to ${p.url()}`}});i.push(b)}else i.push({name:"Login",status:"pass",detail:"Skipped form login (redeploy, password unavailable). Using session injection."});if(!v&&l){let b=new URL(n).hostname,f=n.startsWith("https");await m.addCookies([{name:f?"__Secure-better-auth.session_token":"better-auth.session_token",value:l,domain:b,path:"/",httpOnly:!0,secure:f,sameSite:"Lax"}]),console.error("[qa] Injected session cookie for dashboard checks")}{let b=await gt(p,"Dashboard",async()=>{p.url().includes("/dashboard")||(await p.goto(`${n}/dashboard`,{waitUntil:"domcontentloaded",timeout:15e3}),await p.waitForLoadState("networkidle").catch(()=>{}));let j=await p.content();return j.length<1e3?{pass:!1,detail:`Dashboard page is very small (${j.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 (${j.length} bytes)`}});i.push(b);let f=await p.evaluate(()=>{let R=[];return document.querySelectorAll("nav a[href], aside a[href]").forEach(S=>{let O=S.getAttribute("href");O&&O.startsWith("/")&&!O.startsWith("/api")&&!O.includes("[")&&O!=="/dashboard"&&O!=="/"&&!O.includes("/login")&&!O.includes("/sign")&&R.push(O)}),[...new Set(R)]});if(f.length>0){let R=0,j=[];for(let S of f.slice(0,8)){let O=await gt(p,`Page: ${S}`,async()=>{await p.goto(`${n}${S}`,{waitUntil:"domcontentloaded",timeout:15e3}),await p.waitForLoadState("networkidle").catch(()=>{});let U=await p.title(),x=await p.content();return U.toLowerCase().includes("500")||U.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."}:U.toLowerCase().includes("404")||U.toLowerCase().includes("not found")?{pass:!1,detail:"Page returns 404",fix:`Page ${S} 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."}:x.length<500?{pass:!1,detail:`Page is very small (${x.length} bytes)`,fix:"Page may not have rendered. Check the page component."}:{pass:!0,detail:"Loads without errors"}});O.status==="fail"&&(R++,j.push(S)),i.push(O)}}let k=await gt(p,"Design quality",async()=>{let R=p.url().includes("/dashboard")?p.url():`${n}/dashboard`;p.url().includes("/dashboard")||(await p.goto(R,{waitUntil:"domcontentloaded",timeout:15e3}),await p.waitForLoadState("networkidle").catch(()=>{}));let j=await p.evaluate(()=>{let S=[],O=document.querySelectorAll("h1, h2, h3, h4, h5, h6"),U=new Set;O.forEach(A=>{U.add(window.getComputedStyle(A).fontSize)}),O.length>=3&&U.size<2&&S.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 x=Array.from(O).map(A=>parseInt(A.tagName.charAt(1),10));for(let A=1;A<x.length;A++)if(x[A]-x[A-1]>1){S.push(`TYPOGRAPHY: Heading level skipped (h${x[A-1]} -> h${x[A]}). Use sequential heading levels for accessibility.`);break}let $=document.querySelectorAll("*"),H=!1,G=!1;$.forEach(A=>{let z=window.getComputedStyle(A);z.backgroundColor==="rgb(0, 0, 0)"&&A.clientHeight>100&&A.clientWidth>200&&(H=!0);let fe=z.backgroundColor,V=z.color;if(fe&&!fe.includes("0, 0, 0")&&!fe.includes("255, 255, 255")&&fe!=="rgba(0, 0, 0, 0)"&&fe!=="transparent"&&V.match(/rgb\((\d+), (\d+), (\d+)\)/)){let Pe=V.match(/rgb\((\d+), (\d+), (\d+)\)/);if(Pe){let[We,Ge,Ve]=[parseInt(Pe[1]),parseInt(Pe[2]),parseInt(Pe[3])],he=Math.abs(We-Ge)<10&&Math.abs(Ge-Ve)<10&&We>80&&We<180,Ee=fe.match(/rgb\((\d+), (\d+), (\d+)\)/);if(he&&Ee){let[Ne,je,w]=[parseInt(Ee[1]),parseInt(Ee[2]),parseInt(Ee[3])];!(Math.abs(Ne-je)<15&&Math.abs(je-w)<15)&&A.textContent&&A.textContent.trim().length>0&&(G=!0)}}}}),H&&S.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&&S.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 oe=document.querySelectorAll('[class*="card"], [class*="Card"], [role="group"]'),E=!1;oe.forEach(A=>{A.querySelectorAll('[class*="card"], [class*="Card"]').length>0&&(E=!0)}),E&&S.push("LAYOUT: Nested cards detected (card inside card). Flatten the hierarchy. Use spacing and background color to create separation instead.");let C=document.querySelectorAll("p, li, span, div"),re=0,pe=0;C.forEach(A=>{A.textContent&&A.textContent.trim().length>20&&A.clientHeight>0&&(pe++,window.getComputedStyle(A).textAlign==="center"&&re++)}),pe>5&&re/pe>.7&&S.push("LAYOUT: Most text is center-aligned. Use left-alignment for body content and lists. Reserve center-alignment for heroes and CTAs.");let D=new Set;$.forEach(A=>{let z=window.getComputedStyle(A);z.gap&&z.gap!=="normal"&&z.gap!=="0px"&&D.add(z.gap)}),D.size===1&&$.length>20&&S.push("LAYOUT: Same gap value used everywhere. Vary spacing to create hierarchy: tight within groups (8-12px), generous between sections (32-64px).");let Z=document.querySelectorAll("button, a, input, select, textarea");document.querySelectorAll("table, [role='table']").forEach(A=>{A.querySelectorAll("tbody tr").length===0&&(A.parentElement?.querySelector('[class*="empty"], [class*="Empty"], [class*="no-data"], [class*="placeholder"]')||S.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"),Te=!1;me.forEach(A=>{let z=A.textContent||"";(z.includes("bounce")||z.includes("elastic")||z.match(/cubic-bezier\([^)]*[2-9]\.[0-9]/))&&(Te=!0)}),Te&&S.push("MOTION: Bounce or elastic easing detected. These feel dated. Use smooth deceleration curves (Quart out, Expo out) instead.");let He=document.querySelectorAll("button, a, input, select, [role='button']"),q=0;return He.forEach(A=>{let z=A.getBoundingClientRect();z.width>0&&z.height>0&&(z.width<32||z.height<32)&&q++}),q>3&&S.push(`ACCESSIBILITY: ${q} interactive elements are smaller than 32x32px. Minimum recommended touch target is 44x44px. Add padding to increase tap area.`),S});return j.length===0?{pass:!0,detail:"No design quality issues detected. Typography hierarchy, color usage, layout patterns, and accessibility basics look good."}:{pass:!1,detail:`${j.length} design quality issue(s) found:
3405
- ${j.map((S,O)=>`${O+1}. ${S}`).join(`
3406
- `)}`,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(i.push(k),i.find(R=>R.name==="Landing page"&&R.status==="pass")){let R=await gt(p,"Landing design quality",async()=>{await p.goto(n,{waitUntil:"domcontentloaded",timeout:15e3}),await p.waitForLoadState("networkidle").catch(()=>{});let j=await p.evaluate(()=>{let S=[];document.querySelectorAll("*").forEach($=>{let H=window.getComputedStyle($);(H.getPropertyValue("-webkit-background-clip")||H.getPropertyValue("background-clip"))==="text"&&$.textContent&&$.textContent.trim().length>0&&S.push("SLOP: Gradient text detected. This is a common AI design pattern. Use solid colors for text.")});let U=document.querySelector("section, [class*='hero'], [class*='Hero'], header + div, main > div:first-child");if(U){let $=(U.textContent||"").toLowerCase(),H=["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 H)if($.match(new RegExp(G))){S.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($=>{let G=window.getComputedStyle($).gridTemplateColumns;if(G){let oe=G.split(" ").filter(C=>C!=="").length,E=$.children;if(oe===3&&E.length===3){let C=Array.from(E).map(D=>D.offsetHeight),re=C.every(D=>Math.abs(D-C[0])<5),pe=Array.from(E).every(D=>{let Z=D.querySelectorAll("svg"),Ie=D.querySelectorAll("h2, h3, h4"),me=D.querySelectorAll("p");return Z.length>=1&&Ie.length>=1&&me.length>=1});re&&pe&&S.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.")}}}),S});return j.length===0?{pass:!0,detail:"Landing page design looks intentional. No generic AI patterns detected."}:{pass:!1,detail:`${j.length} landing design issue(s):
3407
- ${j.map((S,O)=>`${O+1}. ${S}`).join(`
3408
- `)}`,fix:"These patterns make the landing page look AI-generated. Fix them to create a more distinctive, professional design."}});i.push(R)}}}finally{m&&await m.close().catch(()=>{})}if(t.deploymentId){let y=i.filter(f=>f.status==="fail"),v=i.filter(f=>f.status==="pass"),b=Date.now();await Ln(t.deploymentId,{checks:i.map(({screenshot:f,...k})=>k),overall:y.length===0?"pass":"fail",passed:v.length,failed:y.length,duration_ms:Date.now()-b}).catch(()=>{})}return yn(n,i)}function yn(t,e){let s=e.filter(i=>i.status==="fail"),n=e.filter(i=>i.status==="pass"),o=[];if(s.length===0)o.push({type:"text",text:JSON.stringify({status:"pass",message:`QA passed. All ${e.length} checks OK. The app is working correctly.`,url:t,checks:e.map(({screenshot:i,...r})=>r)})});else{let i=s.map((r,a)=>`${a+1}. **${r.name}**: ${r.detail}
3409
- Fix: ${r.fix}`).join(`
5402
+ `});import{z as Dr}from"zod";import{resolve as Id}from"path";import{existsSync as Cd}from"fs";import{join as Ad}from"path";var _d,Ti,Pi=S(()=>{"use strict";X();Er();_d=Dr.object({projectPath:Dr.string().optional().describe("Absolute path to the project. Required for the first call (to start the install)."),jobId:Dr.string().optional().describe("Job ID returned from a previous call. Present means poll; absent means start.")}).refine(t=>!!t.projectPath||!!t.jobId,{message:"Pass projectPath to start an install, or jobId to poll an existing one."}),Ti={name:"mist_install",description:"Install a Mistflow project's npm dependencies with the fire-and-poll pattern. First call with { projectPath } starts the install and returns a jobId. Subsequent calls with { jobId } poll for progress (status: 'running' | 'complete' | 'failed'). Re-call while status is 'running' \u2014 each response is <1s so there is no 60s timeout risk.",inputSchema:_d,handler:async t=>{let e=t;if(e.jobId){let a=await go(e.jobId);if(!a)return c(JSON.stringify({status:"not_found",jobId:e.jobId,message:`No install job found for jobId '${e.jobId}'. Start a new one with { projectPath }.`}),!0);if(a.status==="running"||a.status==="starting")return c(JSON.stringify({status:"running",jobId:a.id,elapsed:a.elapsed,logTail:a.logTail,nextAction:"Still running. Call mist_install with the same jobId in ~15-30s to check progress."}));if(a.status==="complete")return c(JSON.stringify({status:"complete",jobId:a.id,elapsed:a.elapsed,exitCode:a.exitCode,nextAction:"Dependencies installed. Run mist_implement next to start executing plan steps."}));let l=Mt(a.id);return c(JSON.stringify({status:a.status,jobId:a.id,elapsed:a.elapsed,exitCode:a.exitCode,logTail:a.logTail,logPaths:l,nextAction:`npm install failed. Read the logTail for the error and fix it \u2014 usually a missing native dep or a version conflict. If the 200-line tail is truncated, read the full log from logPaths.stderr (${l.stderr}) with your file-read tool. After fixing, start a new install with { projectPath }.`}),!0)}let r=Id(e.projectPath);if(!Cd(Ad(r,"package.json")))return c(`No package.json at ${r}. Confirm the path and that mist_init has scaffolded the project.`,!0);let i=`npm install && (${`npx --yes shadcn@latest add -y -o ${["button","card","input","label","form","dialog","table","dropdown-menu","badge","separator","skeleton","sheet","tabs","avatar","select","textarea","checkbox","switch","tooltip","popover","sonner"].join(" ")}`} || echo 'shadcn add failed \u2014 implement will retry lazily')`,s=await ho({type:"install",cmd:"sh",args:["-c",i],cwd:r,env:{NPM_CONFIG_LEGACY_PEER_DEPS:"true"}});return c(JSON.stringify({status:"running",jobId:s.id,startedAt:s.startedAt,cwd:r,nextAction:"Install started in the background (npm install + shadcn components). Call mist_install again with { jobId: '"+s.id+"' } in ~15-30s to check progress. Typical duration: 30-90s."}))}}});import{z as fo}from"zod";import{resolve as Rd,join as Ne}from"path";import{existsSync as Ee,readFileSync as Ii,statSync as Nd}from"fs";function Ed(t){if(!(Ee(Ne(t,"open-next.config.ts"))||Ee(Ne(t,"open-next.config.js"))))return null;let r=Ne(t,".open-next","worker.js");if(!Ee(r))return`Build exited 0 but .open-next/worker.js is missing at ${r}. The Cloudflare adapter did not produce a worker bundle. Common causes: (1) open-next.config.ts is misconfigured, (2) a client component imports a Node-only API (fs, path, crypto node:* imports in 'use client' files), (3) the adapter crashed silently mid-build. Check the logTail above for "error"/"failed" messages, fix the offending file, and re-run mist_build.`;try{let o=Nd(r).size;if(o<Ci)return`Build exited 0 but .open-next/worker.js is only ${o} bytes (expected at least ${Ci}). The worker bundle is too small to be a working app \u2014 the adapter likely failed to bundle most of the code. Check open-next.config.ts and the logTail above for errors, fix them, and re-run mist_build.`}catch(o){return`Could not stat .open-next/worker.js: ${o instanceof Error?o.message:String(o)}. Re-run mist_build.`}return null}function jd(t){let e=Ne(mo(),t,"stdout.log"),r=Ne(mo(),t,"stderr.log"),o="";try{Ee(e)&&(o+=Ii(e,"utf-8"))}catch{}try{Ee(r)&&(o+=`
5403
+ `+Ii(r,"utf-8"))}catch{}return o}function Od(t){let e=/Module not found:\s*(?:Error:\s*)?Can't resolve ['"]([^'"]+)['"]/g,r=new Set;for(let o of t.matchAll(e)){let n=o[1];if(n.startsWith(".")||n.startsWith("@/")||n.startsWith("~/"))continue;let i=n.startsWith("@")?n.split("/").slice(0,2).join("/"):n.split("/")[0];i&&r.add(i)}return[...r]}var Ci,Dd,Ai,_i=S(()=>{"use strict";X();Er();ur();Ci=10*1024;Dd=fo.object({projectPath:fo.string().optional().describe("Absolute path to the project. Required for the first call."),jobId:fo.string().optional().describe("Job ID from a previous call. Present means poll; absent means start."),script:fo.string().optional().describe("Optional override: run `npm run <script>` instead of the Cloudflare adapter. Default behavior is `npx @opennextjs/cloudflare build` when open-next.config.ts exists, else `npm run build`. Ignored on poll calls.")}).refine(t=>!!t.projectPath||!!t.jobId,{message:"Pass projectPath to start a build, or jobId to poll an existing one."});Ai={name:"mist_build",description:"Run a Mistflow project's production build with the fire-and-poll pattern. First call with { projectPath } starts `npm run build` and returns a jobId. Subsequent calls with { jobId } poll status. On failure, the response surfaces structured errors (from parseBuildErrors) and any missingModules[] \u2014 chain into mist_install with those modules, then mist_build again, without asking the user.",inputSchema:Dd,handler:async t=>{let e=t;if(e.jobId){let l=await go(e.jobId);if(!l)return c(JSON.stringify({status:"not_found",jobId:e.jobId,message:`No build job found for jobId '${e.jobId}'. Start a new one with { projectPath }.`}),!0);if(l.status==="running"||l.status==="starting")return c(JSON.stringify({status:"running",jobId:l.id,elapsed:l.elapsed,logTail:l.logTail,nextAction:"Build still running. Call mist_build again with the same jobId in ~20-40s."}));if(l.status==="complete"){let b=Ed(l.cwd);if(b){let g=Mt(l.id);return c(JSON.stringify({status:"failed",jobId:l.id,elapsed:l.elapsed,exitCode:l.exitCode,logTail:l.logTail,logPaths:g,error:b,nextAction:b+` Full build log: ${g.stdout} and ${g.stderr}.`}),!0)}return c(JSON.stringify({status:"complete",jobId:l.id,elapsed:l.elapsed,exitCode:l.exitCode,nextAction:"Build passed. Run mist_deploy next to ship \u2014 do NOT ask the user to confirm, the build is the approval gate."}))}let d=jd(l.id),h=ro(d),u=Od(d),p=Mt(l.id),f=` Full log: ${p.stdout} and ${p.stderr}.`,v=u.length>0?`Build failed with missing modules: ${u.join(", ")}. Call mist_install with these packages (or chain mist_install { projectPath } to reinstall all), then call mist_build again. Do NOT ask the user.${f}`:h.length>0?`Build failed with structured errors (see errors[]). Fix the code, then call mist_build again.${f}`:`Build failed. Read the logTail for the error, or call mist_debug with the failed jobId to extract structured errors.${f}`;return c(JSON.stringify({status:l.status,jobId:l.id,elapsed:l.elapsed,exitCode:l.exitCode,errors:h,missingModules:u,logTail:l.logTail,logPaths:p,nextAction:v}),!0)}let r=Rd(e.projectPath);if(!Ee(Ne(r,"package.json")))return c(`No package.json at ${r}. Run mist_init first.`,!0);if(!Ee(Ne(r,"node_modules")))return c(`node_modules not installed. Run mist_install { projectPath: '${r}' } first.`,!0);let o,n,i,s=Ee(Ne(r,"open-next.config.ts"))||Ee(Ne(r,"open-next.config.js"));e.script?(o="npm",n=["run",e.script],i=e.script):s?(o="npx",n=["-y","@opennextjs/cloudflare","build"],i="@opennextjs/cloudflare build"):(o="npm",n=["run","build"],i="build");let a=await ho({type:"build",cmd:o,args:n,cwd:r});return c(JSON.stringify({status:"running",jobId:a.id,startedAt:a.startedAt,cwd:r,script:i,nextAction:`Build started. Call mist_build again with { jobId: '${a.id}' } in ~30s to check progress. Typical duration: 30-90s on a fresh scaffold (Cloudflare adapter), 10-30s on subsequent builds.`}))}}});import{existsSync as Md,readFileSync as Ld}from"fs";import{join as Ud}from"path";import{z as yo}from"zod";function Fd(t){let e=Ud(t,"mistflow.json");if(!Md(e))return null;try{return JSON.parse(Ld(e,"utf-8"))}catch{return null}}async function Ri(t){try{let e=await fetch(t,{redirect:"follow",signal:AbortSignal.timeout(15e3)}),r=await e.text();return{status:e.status,body:r}}catch(e){return{status:0,body:String(e)}}}async function qd(t,e,r){try{let o=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json",...r??{}},body:JSON.stringify(e),redirect:"follow",signal:AbortSignal.timeout(15e3)}),n=await o.text(),i;try{i=JSON.parse(n)}catch{}return{status:o.status,json:i}}catch{return{status:0}}}async function Ni(t){try{let e=await t.screenshot({type:"png"});return Buffer.from(e).toString("base64")}catch{return}}async function ft(t,e,r){let o=[],n=i=>{i.type()==="error"&&o.push(i.text())};t.on("console",n);try{let i=await r(),s=await Ni(t);return{name:e,status:i.pass?"pass":"fail",detail:i.detail,fix:i.fix,screenshot:s,consoleErrors:o.length>0?o:void 0}}catch(i){let s=await Ni(t);return{name:e,status:"fail",detail:`Unexpected error: ${i instanceof Error?i.message:String(i)}`,screenshot:s,consoleErrors:o.length>0?o:void 0}}finally{t.removeListener("console",n)}}async function Bd(t){let e=t.projectPath??process.cwd(),r=Fd(e),o=t.url;if(o||(o=r?.deploy?.url),!o)return c("No deploy URL found. Deploy the app first with mist_deploy, then call mist_qa.",!0);o.startsWith("http")||(o=`https://${o}`);let n=r?.projectId,i=[],s=await Ri(`${o}/api/health`);if(s.status!==200)return i.push({name:"Health endpoint",status:"fail",detail:`Returns ${s.status}`,fix:"The worker is not running or crashed on startup. Check app/api/health/route.ts exists and the build succeeded."}),bo(o,i);i.push({name:"Health endpoint",status:"pass",detail:"Returns 200"});let a=await Ri(`${o}/api/auth/ok`);if(a.status!==200)return i.push({name:"Auth system",status:"fail",detail:`Auth endpoint returns ${a.status}`,fix:"Better Auth is not working. Check lib/auth.ts, lib/db.ts, and that your database env vars are set."}),bo(o,i);i.push({name:"Auth system",status:"pass",detail:"Better Auth running"});let l,d,h;if(n){let f=await $o(n);if(f){console.error("[qa] Calling seed endpoint for session token");let v=await qd(`${o}/api/admin/seed`,{token:f.seedToken,email:f.email,password:"QaTemp1!"});v.status===200&&v.json?(l=v.json.sessionToken,d=v.json.email,v.json.seeded?(h=v.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 ${v.status}`)}}if(!l)return i.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."}),bo(o,i);let u,p;try{let{getIsolatedContext:f}=await Promise.resolve().then(()=>(Bt(),Ao)),v=await f();u=v.context,p=v.page}catch(f){let v=f instanceof Error?f.message:String(f);return c(JSON.stringify({status:"cannot_verify",url:o,deployed:!0,reason:"App deployed successfully, but QA could not verify it via automated browser testing because Playwright is not installed locally.",detail:v,httpChecks:i.map(({screenshot:b,...g})=>g),fix:"Run: npx playwright install chromium",instruction:[`The app deployed and is live at ${o}. 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 ${o}. I couldn't run automated QA because Playwright isn't installed locally. Run \`npx playwright install chromium\` (one-time, ~150MB) and I'll verify it for you \u2014 or just open the URL and try it yourself."`,"HTTP checks (health/auth endpoints) passed \u2014 those prove the server is responding. They do not prove the UI renders or user flows work.","After Playwright is installed, call mist_qa again for full verification."].join(`
5404
+ `)}),!1)}try{let f=await ft(p,"Landing page",async()=>{await p.goto(o,{waitUntil:"domcontentloaded",timeout:3e4}),await p.waitForLoadState("networkidle").catch(()=>{});let b=await p.evaluate(()=>{let x=document.body;if(!x)return"";let R=document.createTreeWalker(x,NodeFilter.SHOW_TEXT),D="",P;for(;P=R.nextNode();){let j=P.parentElement;if(j){let U=window.getComputedStyle(j);U.display!=="none"&&U.visibility!=="hidden"&&parseFloat(U.opacity)>0&&(D+=P.textContent?.trim()+" ")}}return D.trim()});if(b.length<50)return{pass:!1,detail:`Landing page appears blank (${b.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 g=p.url();return g.includes("/login")||g.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 (${b.length} chars)`}});i.push(f);let v=!1;if(h){let b=await ft(p,"Login",async()=>{await p.goto(`${o}/login`,{waitUntil:"domcontentloaded",timeout:15e3}),await p.waitForLoadState("networkidle").catch(()=>{});let g=p.locator('input[type="email"], input[name="email"], input[placeholder*="email" i]'),x=p.locator('input[type="password"], input[name="password"]');try{await g.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 g.first().fill(d),await x.first().fill(h),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(D=>!D.pathname.includes("/login"),{timeout:1e4})}catch{let D=await p.locator('[role="alert"], .text-red-500, .text-destructive, [data-error]').first().textContent().catch(()=>null);return{pass:!1,detail:D?`Login failed: ${D}`:"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 v=!0,{pass:!0,detail:`Logged in, redirected to ${p.url()}`}});i.push(b)}else i.push({name:"Login",status:"pass",detail:"Skipped form login (redeploy, password unavailable). Using session injection."});if(!v&&l){let b=new URL(o).hostname,g=o.startsWith("https");await u.addCookies([{name:g?"__Secure-better-auth.session_token":"better-auth.session_token",value:l,domain:b,path:"/",httpOnly:!0,secure:g,sameSite:"Lax"}]),console.error("[qa] Injected session cookie for dashboard checks")}{let b=await ft(p,"Dashboard",async()=>{p.url().includes("/dashboard")||(await p.goto(`${o}/dashboard`,{waitUntil:"domcontentloaded",timeout:15e3}),await p.waitForLoadState("networkidle").catch(()=>{}));let D=await p.content();return D.length<1e3?{pass:!1,detail:`Dashboard page is very small (${D.length} bytes)`,fix:"Check app/(dashboard)/dashboard/page.tsx exists and the dashboard layout doesn't crash."}:await 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 (${D.length} bytes)`}});i.push(b);let g=await p.evaluate(()=>{let R=[];return document.querySelectorAll("nav a[href], aside a[href]").forEach(P=>{let j=P.getAttribute("href");j&&j.startsWith("/")&&!j.startsWith("/api")&&!j.includes("[")&&j!=="/dashboard"&&j!=="/"&&!j.includes("/login")&&!j.includes("/sign")&&R.push(j)}),[...new Set(R)]});if(g.length>0){let R=0,D=[];for(let P of g.slice(0,8)){let j=await ft(p,`Page: ${P}`,async()=>{await p.goto(`${o}${P}`,{waitUntil:"domcontentloaded",timeout:15e3}),await p.waitForLoadState("networkidle").catch(()=>{});let U=await p.title(),k=await p.content();return U.toLowerCase().includes("500")||U.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."}:U.toLowerCase().includes("404")||U.toLowerCase().includes("not found")?{pass:!1,detail:"Page returns 404",fix:`Page ${P} 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."}:k.length<500?{pass:!1,detail:`Page is very small (${k.length} bytes)`,fix:"Page may not have rendered. Check the page component."}:{pass:!0,detail:"Loads without errors"}});j.status==="fail"&&(R++,D.push(P)),i.push(j)}}let x=await ft(p,"Design quality",async()=>{let R=p.url().includes("/dashboard")?p.url():`${o}/dashboard`;p.url().includes("/dashboard")||(await p.goto(R,{waitUntil:"domcontentloaded",timeout:15e3}),await p.waitForLoadState("networkidle").catch(()=>{}));let D=await p.evaluate(()=>{let P=[],j=document.querySelectorAll("h1, h2, h3, h4, h5, h6"),U=new Set;j.forEach(A=>{U.add(window.getComputedStyle(A).fontSize)}),j.length>=3&&U.size<2&&P.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 k=Array.from(j).map(A=>parseInt(A.tagName.charAt(1),10));for(let A=1;A<k.length;A++)if(k[A]-k[A-1]>1){P.push(`TYPOGRAPHY: Heading level skipped (h${k[A-1]} -> h${k[A]}). Use sequential heading levels for accessibility.`);break}let L=document.querySelectorAll("*"),K=!1,H=!1;L.forEach(A=>{let q=window.getComputedStyle(A);q.backgroundColor==="rgb(0, 0, 0)"&&A.clientHeight>100&&A.clientWidth>200&&(K=!0);let he=q.backgroundColor,se=q.color;if(he&&!he.includes("0, 0, 0")&&!he.includes("255, 255, 255")&&he!=="rgba(0, 0, 0, 0)"&&he!=="transparent"&&se.match(/rgb\((\d+), (\d+), (\d+)\)/)){let ee=se.match(/rgb\((\d+), (\d+), (\d+)\)/);if(ee){let[We,et,tt]=[parseInt(ee[1]),parseInt(ee[2]),parseInt(ee[3])],ge=Math.abs(We-et)<10&&Math.abs(et-tt)<10&&We>80&&We<180,Ce=he.match(/rgb\((\d+), (\d+), (\d+)\)/);if(ge&&Ce){let[Ge,je,y]=[parseInt(Ce[1]),parseInt(Ce[2]),parseInt(Ce[3])];!(Math.abs(Ge-je)<15&&Math.abs(je-y)<15)&&A.textContent&&A.textContent.trim().length>0&&(H=!0)}}}}),K&&P.push("COLOR: Pure black (#000) background detected on a large element. Use a tinted dark color instead (e.g. oklch(15% 0.01 hue) or a deep navy/charcoal)."),H&&P.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 ye=document.querySelectorAll('[class*="card"], [class*="Card"], [role="group"]'),J=!1;ye.forEach(A=>{A.querySelectorAll('[class*="card"], [class*="Card"]').length>0&&(J=!0)}),J&&P.push("LAYOUT: Nested cards detected (card inside card). Flatten the hierarchy. Use spacing and background color to create separation instead.");let N=document.querySelectorAll("p, li, span, div"),O=0,pe=0;N.forEach(A=>{A.textContent&&A.textContent.trim().length>20&&A.clientHeight>0&&(pe++,window.getComputedStyle(A).textAlign==="center"&&O++)}),pe>5&&O/pe>.7&&P.push("LAYOUT: Most text is center-aligned. Use left-alignment for body content and lists. Reserve center-alignment for heroes and CTAs.");let z=new Set;L.forEach(A=>{let q=window.getComputedStyle(A);q.gap&&q.gap!=="normal"&&q.gap!=="0px"&&z.add(q.gap)}),z.size===1&&L.length>20&&P.push("LAYOUT: Same gap value used everywhere. Vary spacing to create hierarchy: tight within groups (8-12px), generous between sections (32-64px).");let G=document.querySelectorAll("button, a, input, select, textarea");document.querySelectorAll("table, [role='table']").forEach(A=>{A.querySelectorAll("tbody tr").length===0&&(A.parentElement?.querySelector('[class*="empty"], [class*="Empty"], [class*="no-data"], [class*="placeholder"]')||P.push("UX: Empty table with no empty state. Add a helpful message explaining what will appear here and a CTA to create the first item."))});let Pe=document.querySelectorAll("style"),Ie=!1;Pe.forEach(A=>{let q=A.textContent||"";(q.includes("bounce")||q.includes("elastic")||q.match(/cubic-bezier\([^)]*[2-9]\.[0-9]/))&&(Ie=!0)}),Ie&&P.push("MOTION: Bounce or elastic easing detected. These feel dated. Use smooth deceleration curves (Quart out, Expo out) instead.");let De=document.querySelectorAll("button, a, input, select, [role='button']"),V=0;return De.forEach(A=>{let q=A.getBoundingClientRect();q.width>0&&q.height>0&&(q.width<32||q.height<32)&&V++}),V>3&&P.push(`ACCESSIBILITY: ${V} interactive elements are smaller than 32x32px. Minimum recommended touch target is 44x44px. Add padding to increase tap area.`),P});return D.length===0?{pass:!0,detail:"No design quality issues detected. Typography hierarchy, color usage, layout patterns, and accessibility basics look good."}:{pass:!1,detail:`${D.length} design quality issue(s) found:
5405
+ ${D.map((P,j)=>`${j+1}. ${P}`).join(`
5406
+ `)}`,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(i.push(x),i.find(R=>R.name==="Landing page"&&R.status==="pass")){let R=await ft(p,"Landing design quality",async()=>{await p.goto(o,{waitUntil:"domcontentloaded",timeout:15e3}),await p.waitForLoadState("networkidle").catch(()=>{});let D=await p.evaluate(()=>{let P=[];document.querySelectorAll("*").forEach(L=>{let K=window.getComputedStyle(L);(K.getPropertyValue("-webkit-background-clip")||K.getPropertyValue("background-clip"))==="text"&&L.textContent&&L.textContent.trim().length>0&&P.push("SLOP: Gradient text detected. This is a common AI design pattern. Use solid colors for text.")});let U=document.querySelector("section, [class*='hero'], [class*='Hero'], header + div, main > div:first-child");if(U){let L=(U.textContent||"").toLowerCase(),K=["transform your","unlock the power","revolutionize your","take your .* to the next level","the future of","welcome to","get started today","join thousands","powerful analytics","seamless integration","lightning fast"];for(let H of K)if(L.match(new RegExp(H))){P.push(`COPY: Generic hero text detected ('${H}'). Write specific copy about what THIS app does for its users.`);break}}return document.querySelectorAll('[class*="grid"]').forEach(L=>{let H=window.getComputedStyle(L).gridTemplateColumns;if(H){let ye=H.split(" ").filter(N=>N!=="").length,J=L.children;if(ye===3&&J.length===3){let N=Array.from(J).map(z=>z.offsetHeight),O=N.every(z=>Math.abs(z-N[0])<5),pe=Array.from(J).every(z=>{let G=z.querySelectorAll("svg"),me=z.querySelectorAll("h2, h3, h4"),Pe=z.querySelectorAll("p");return G.length>=1&&me.length>=1&&Pe.length>=1});O&&pe&&P.push("SLOP: 3-column icon + title + paragraph feature grid detected. This is the most common AI layout pattern. Use asymmetric layouts, bento grids, or varied card sizes instead.")}}}),P});return D.length===0?{pass:!0,detail:"Landing page design looks intentional. No generic AI patterns detected."}:{pass:!1,detail:`${D.length} landing design issue(s):
5407
+ ${D.map((P,j)=>`${j+1}. ${P}`).join(`
5408
+ `)}`,fix:"These patterns make the landing page look AI-generated. Fix them to create a more distinctive, professional design."}});i.push(R)}}}finally{u&&await u.close().catch(()=>{})}if(t.deploymentId){let f=i.filter(g=>g.status==="fail"),v=i.filter(g=>g.status==="pass"),b=Date.now();await Fo(t.deploymentId,{checks:i.map(({screenshot:g,...x})=>x),overall:f.length===0?"pass":"fail",passed:v.length,failed:f.length,duration_ms:Date.now()-b}).catch(()=>{})}return bo(o,i)}function bo(t,e){let r=e.filter(i=>i.status==="fail"),o=e.filter(i=>i.status==="pass"),n=[];if(r.length===0)n.push({type:"text",text:JSON.stringify({status:"pass",message:`QA passed. All ${e.length} checks OK. The app is working correctly.`,url:t,checks:e.map(({screenshot:i,...s})=>s)})});else{let i=r.map((s,a)=>`${a+1}. **${s.name}**: ${s.detail}
5409
+ Fix: ${s.fix}`).join(`
3410
5410
 
3411
- `);o.push({type:"text",text:JSON.stringify({status:"fail",message:`QA found ${s.length} issue(s) on the live app. Fix them and redeploy.`,url:t,passed:n.length,failed:s.length,checks:e.map(({screenshot:r,...a})=>a),fixInstructions:`The deployed app at ${t} has ${s.length} issue(s):
5411
+ `);n.push({type:"text",text:JSON.stringify({status:"fail",message:`QA found ${r.length} issue(s) on the live app. Fix them and redeploy.`,url:t,passed:o.length,failed:r.length,checks:e.map(({screenshot:s,...a})=>a),fixInstructions:`The deployed app at ${t} has ${r.length} issue(s):
3412
5412
 
3413
5413
  ${i}
3414
5414
 
3415
- Fix these issues in the source code, then call mist_deploy with action='deploy'${t.includes("-pv-")?" environment='preview'":""} to redeploy. After redeploying, call mist_qa again to verify the fixes.`})})}for(let i of e)i.screenshot&&o.push({type:"image",data:i.screenshot,mimeType:"image/png"});return{content:o}}var rd,li,ci=_(()=>{"use strict";K();ue();rd=fn.object({projectPath:fn.string().optional().describe("Absolute path to the Mistflow project. Defaults to the current working directory. Used to read mistflow.json for the deploy URL + projectId."),url:fn.string().optional().describe("Override the deploy URL to QA. Useful for previews or when mistflow.json doesn't yet have the URL recorded. Defaults to mistflow.json's deploy.url."),deploymentId:fn.string().optional().describe("Deployment ID to associate QA results with. When set, results are uploaded to the backend and surfaced on the dashboard deployment card.")}),li={name:"mist_qa",description:"Post-deploy QA: drives Playwright in-process against the live deploy URL. Checks health + auth endpoints, logs in with a seeded admin account, walks the dashboard and sidebar pages, and runs design-quality audits (typography, color, layout, slop patterns). Returns pass/fail + screenshots inline. Call ONCE after mist_deploy completes. Do NOT surface the deploy URL to the user until mist_qa returns status: 'pass'.",inputSchema:rd,handler:async t=>ld(t)}});import{existsSync as bn,readdirSync as cd,statSync as di,unlinkSync as pi}from"fs";import{join as et}from"path";import{execFile as dd}from"child_process";function pd(t,e){let s=0;for(let n of e){let o=et(t,n);if(bn(o))try{let i=di(o);if(i.isFile())s++;else if(i.isDirectory()){let r=[o];for(;r.length;){let a=r.pop(),l=cd(a,{withFileTypes:!0});for(let d of l){let h=et(a,d.name);d.isDirectory()?r.push(h):s++}}}}catch{}}return s}function ui(t,e,s){return new Promise(n=>{dd("tar",t,{cwd:e,timeout:s,maxBuffer:10*1024*1024,env:{...process.env,COPYFILE_DISABLE:"1"}},(o,i,r)=>{n({success:!o,stderr:r?.toString()??""})})})}async function mi(t){let e=et(t,".open-next-build.tar.gz"),s=[".open-next"];bn(et(t,"db"))&&s.push("db"),bn(et(t,"drizzle.config.ts"))&&s.push("drizzle.config.ts"),bn(et(t,"package.json"))&&s.push("package.json");let n=pd(t,s),o=await ui(["-czf",e,"-C",t,...s],t,12e4);if(!o.success){try{pi(e)}catch{}throw new Error("Failed to create build archive. Check disk space and permissions."+(o.stderr?`
3416
- ${o.stderr.slice(-500)}`:""))}let i=0;try{i=di(e).size}catch{}return{path:e,sizeBytes:i,fileCount:n}}async function hi(t){let e=et(t,".mistflow-source.tar.gz");return(await ui(["-czf",e,"-C",t,"--exclude",".open-next","--exclude","node_modules","--exclude",".git","--exclude",".next","--exclude",".open-next-build.tar.gz","--exclude",".mistflow-source.tar.gz","."],t,12e4)).success?e:null}function js(t){if(t)try{pi(t)}catch{}}function gi(t){return t<1024?`${t}B`:t<1024*1024?`${(t/1024).toFixed(1)}KB`:`${(t/(1024*1024)).toFixed(1)}MB`}function fi(t){switch(t){case"pending":return"Provisioning...";case"building":return"Building your app...";case"deploying":return"Deploying to Mistflow Cloud...";case"verifying":return"Verifying deployment...";case"live":return"Live!";case"failed":return"Failed";default:return`Status: ${t}`}}var yi=_(()=>{"use strict"});import{z as ft}from"zod";import{resolve as ud,join as md}from"path";import{existsSync as hd}from"fs";function fd(t){return hd(md(t,".open-next"))}function yd(t){return $e(t)?.deploy?.strategy==="staging"?"preview":null}async function bd(t){let e=ud(t.projectPath);if(!ee())return c("Not signed in. Call mist_setup first.",!0);let n=$e(e)?.projectId;if(!n)return c(`No projectId in mistflow.json at ${e}. Run mist_init first.`,!0);if(!fd(e))return c(`No .open-next build output at ${e}. Run mist_build first \u2014 the deploy tool ships the build artifact, not the raw source.`,!0);let o=t.environment??yd(e)??"production",i=null,r=null;try{i=await mi(e),r=await hi(e);let a=await jn(n,i.path,o,t.adminEmail,void 0,r??void 0,void 0);return c(JSON.stringify({status:"running",jobId:a.deployment_id,deploymentId:a.deployment_id,environment:a.environment??o,buildSize:gi(i.sizeBytes),buildFileCount:i.fileCount,nextAction:`Upload complete. Call mist_deploy { action: 'status', deploymentId: '${a.deployment_id}' } in ~5-10s to poll deployment progress. Do NOT surface the URL to the user until mist_qa passes.`}))}catch(a){let l=a instanceof L||a instanceof Error?a.message:String(a);return c(`Deploy upload failed: ${l}`,!0)}finally{js(i?.path),js(r)}}async function wd(t){try{let e=await Pt(t),s=fi(e.status);return e.status==="live"?c(JSON.stringify({status:"complete",jobId:e.id,deploymentId:e.id,url:e.url,completedAt:e.completedAt,qaRequired:!0,nextAction:`Deployment is live. Call mist_qa { projectPath, url: '${e.url??""}', deploymentId: '${e.id??""}' } next. Do NOT show the URL to the user until mist_qa returns status: 'pass'.`})):e.status==="failed"?c(JSON.stringify({status:"failed",jobId:e.id,deploymentId:e.id,error:e.error,phase:s,nextAction:"Deployment failed. Read the error message, fix the code, then call mist_deploy { action: 'deploy', projectPath } again."}),!0):c(JSON.stringify({status:"running",jobId:e.id,deploymentId:e.id,phase:s,phaseCode:e.status,nextAction:`Still ${e.status}. Call mist_deploy { action: 'status', deploymentId: '${e.id}' } again in ~5-10s.`}))}catch(e){let s=e instanceof L||e instanceof Error?e.message:String(e);return c(`Could not fetch deploy status: ${s}`,!0)}}async function vd(t,e){if(!ee())return c("Not signed in. Call mist_setup first.",!0);let n=$e(t)?.projectId;if(!n)return c(`No projectId in mistflow.json at ${t}. Run mist_init first.`,!0);let o=e;if(!o)try{let r=(await rt(n)).find(a=>a.environment==="preview"&&a.status==="live");if(!r)return c("No live preview deployment to promote. Deploy to preview first with { action: 'deploy', environment: 'preview' }.",!0);o=r.id}catch(i){let r=i instanceof L?i.message:String(i);return c(`Could not list deployments: ${r}`,!0)}try{let i=await Wn(n,o);return c(JSON.stringify({status:"running",jobId:i.deployment_id,deploymentId:i.deployment_id,promotedFrom:o,nextAction:`Promote started. Call mist_deploy { action: 'status', deploymentId: '${i.deployment_id}' } to poll. Promotion re-uses the preview artifact so it typically completes in ~10s.`}))}catch(i){let r=i instanceof L||i instanceof Error?i.message:String(i);return c(`Promote failed: ${r}`,!0)}}async function xd(t){if(!ee())return c("Not signed in. Call mist_setup first.",!0);try{let e=await Gn(t);return c(JSON.stringify({status:"running",jobId:e.deployment_id,deploymentId:e.deployment_id,rollbackFrom:e.rollback_from,nextAction:`Rollback started. Call mist_deploy { action: 'status', deploymentId: '${e.deployment_id}' } to poll.`}))}catch(e){let s=e instanceof L||e instanceof Error?e.message:String(e);return c(`Rollback failed: ${s}`,!0)}}var gd,bi,wi=_(()=>{"use strict";K();ue();nt();yi();gd=ft.object({action:ft.enum(["deploy","promote","rollback","status"]).default("deploy").describe("'deploy' (default) tars + uploads + starts a deployment. 'promote' promotes a staging preview to production. 'rollback' rolls back to a specific deployment. 'status' polls an in-flight deployment by id."),projectPath:ft.string().optional().describe("Absolute path to the project. Required for 'deploy' and 'promote'."),deploymentId:ft.string().optional().describe("Deployment id \u2014 required for 'rollback' and 'status'; the preview id for 'promote'."),environment:ft.enum(["production","preview"]).optional().describe("Deploy target. Defaults to 'production'; auto-redirects to 'preview' when the project uses staging mode."),adminEmail:ft.string().optional().describe("Admin email injected as BETTER_AUTH_ADMIN_EMAIL on first deploy of an auth-enabled project.")});bi={name:"mist_deploy",description:"Deploy a Mistflow project. action='deploy' (default) tars + uploads + starts backend orchestration; action='status' polls an in-flight deployment; action='promote' ships a staging preview to prod; action='rollback' reverts to a previous deployment. Fire-and-poll: deploy/promote/rollback return a jobId immediately; poll with { action: 'status', deploymentId }. Do NOT show the deploy URL to the user until mist_qa passes \u2014 qaRequired=true is returned on completion.",inputSchema:gd,handler:async t=>{let e=t;switch(e.action??"deploy"){case"deploy":return e.projectPath?bd({projectPath:e.projectPath,environment:e.environment,adminEmail:e.adminEmail}):c("projectPath is required for action='deploy'.",!0);case"status":return e.deploymentId?wd(e.deploymentId):c("deploymentId is required for action='status'.",!0);case"promote":return e.projectPath?vd(e.projectPath,e.deploymentId):c("projectPath is required for action='promote'.",!0);case"rollback":return e.deploymentId?xd(e.deploymentId):c("deploymentId is required for action='rollback'.",!0)}}}});var Rd={};import{Server as kd}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as Sd}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as Pd,ListToolsRequestSchema as Id}from"@modelcontextprotocol/sdk/types.js";import{zodToJsonSchema as Td}from"zod-to-json-schema";async function _d(){let t=process.argv.indexOf("--api-url");t!==-1&&process.argv[t+1]&&(process.env.MISTFLOW_API_URL=process.argv[t+1]),process.argv.includes("--local")&&!process.env.MISTFLOW_API_URL&&(process.env.MISTFLOW_API_URL="http://localhost:9100");let e=new Sd;await wn.connect(e),console.error(`Mistflow MCP server running on stdio (API: ${process.env.MISTFLOW_API_URL||"https://api.mistflow.ai"})`)}var wn,vi,xi=_(()=>{"use strict";Oe();K();po();Co();Eo();Oo();ds();Wo();or();mr();Mr();Br();ti();ri();ci();wi();wn=new kd({name:"mistflow",version:"1.0.0"},{capabilities:{tools:{}},instructions:qs()}),vi=[co,Ro,Ao,Do,sr,ur,Bo,Or,Ho,qr,ei,oi,li,bi];wn.setRequestHandler(Id,async()=>({tools:vi.map(t=>({name:t.name,description:t.description,inputSchema:Td(t.inputSchema)}))}));wn.setRequestHandler(Pd,async t=>{let e=vi.find(s=>s.name===t.params.name);if(!e)return c(`Unknown tool: ${t.params.name}`,!0);try{let s=e.inputSchema.safeParse(t.params.arguments);if(!s.success){let i=s.error.issues.map(r=>`${r.path.join(".")}: ${r.message}`).join(", ");return c(`Invalid input: ${i}`,!0)}let n=t.params._meta?.progressToken,o={server:wn,progressToken:n};try{return await e.handler(s.data,o)}finally{o.cleanup?.()}}catch(s){let n=s instanceof Error?s.message:"An unexpected error occurred";return console.error("Tool error:",s),c(n,!0)}});_d().catch(t=>{console.error("Fatal error:",t),process.exit(1)})});import{readFileSync as Cd}from"fs";import{dirname as Ad,join as Ed}from"path";import{fileURLToPath as Nd}from"url";var ze=process.argv[2];if(ze==="--version"||ze==="-v"){try{let t=Ad(Nd(import.meta.url)),e=Ed(t,"..","package.json"),s=JSON.parse(Cd(e,"utf-8"));console.log(s.version)}catch{console.log("unknown")}process.exit(0)}(ze==="--help"||ze==="-h")&&(console.log(`@mistflow-ai/mcp \u2014 Mistflow MCP server
5415
+ Fix these issues in the source code, then call mist_deploy with action='deploy'${t.includes("-pv-")?" environment='preview'":""} to redeploy. After redeploying, call mist_qa again to verify the fixes.`})})}for(let i of e)i.screenshot&&n.push({type:"image",data:i.screenshot,mimeType:"image/png"});return{content:n}}var $d,Ei,Di=S(()=>{"use strict";X();ue();$d=yo.object({projectPath:yo.string().optional().describe("Absolute path to the Mistflow project. Defaults to the current working directory. Used to read mistflow.json for the deploy URL + projectId."),url:yo.string().optional().describe("Override the deploy URL to QA. Useful for previews or when mistflow.json doesn't yet have the URL recorded. Defaults to mistflow.json's deploy.url."),deploymentId:yo.string().optional().describe("Deployment ID to associate QA results with. When set, results are uploaded to the backend and surfaced on the dashboard deployment card.")}),Ei={name:"mist_qa",description:"Post-deploy QA: drives Playwright in-process against the live deploy URL. Checks health + auth endpoints, logs in with a seeded admin account, walks the dashboard and sidebar pages, and runs design-quality audits (typography, color, layout, slop patterns). Returns pass/fail + screenshots inline. Call ONCE after mist_deploy completes. Do NOT surface the deploy URL to the user until mist_qa returns status: 'pass'.",inputSchema:$d,handler:async t=>Bd(t)}});import{existsSync as wo,readdirSync as zd,statSync as ji,unlinkSync as Oi}from"fs";import{join as Ze}from"path";import{execFile as Hd}from"child_process";function Wd(t,e){let r=0;for(let o of e){let n=Ze(t,o);if(wo(n))try{let i=ji(n);if(i.isFile())r++;else if(i.isDirectory()){let s=[n];for(;s.length;){let a=s.pop(),l=zd(a,{withFileTypes:!0});for(let d of l){let h=Ze(a,d.name);d.isDirectory()?s.push(h):r++}}}}catch{}}return r}function Mi(t,e,r){return new Promise(o=>{Hd("tar",t,{cwd:e,timeout:r,maxBuffer:10*1024*1024,env:{...process.env,COPYFILE_DISABLE:"1"}},(n,i,s)=>{o({success:!n,stderr:s?.toString()??""})})})}async function Li(t){let e=Ze(t,".open-next-build.tar.gz"),r=[".open-next"];wo(Ze(t,"db"))&&r.push("db"),wo(Ze(t,"drizzle.config.ts"))&&r.push("drizzle.config.ts"),wo(Ze(t,"package.json"))&&r.push("package.json");let o=Wd(t,r),n=await Mi(["-czf",e,"-C",t,...r],t,12e4);if(!n.success){try{Oi(e)}catch{}throw new Error("Failed to create build archive. Check disk space and permissions."+(n.stderr?`
5416
+ ${n.stderr.slice(-500)}`:""))}let i=0;try{i=ji(e).size}catch{}return{path:e,sizeBytes:i,fileCount:o}}async function Ui(t){let e=Ze(t,".mistflow-source.tar.gz");return(await Mi(["-czf",e,"-C",t,"--exclude",".open-next","--exclude","node_modules","--exclude",".git","--exclude",".next","--exclude",".open-next-build.tar.gz","--exclude",".mistflow-source.tar.gz","."],t,12e4)).success?e:null}function jr(t){if(t)try{Oi(t)}catch{}}function $i(t){return t<1024?`${t}B`:t<1024*1024?`${(t/1024).toFixed(1)}KB`:`${(t/(1024*1024)).toFixed(1)}MB`}function Fi(t){switch(t){case"pending":return"Provisioning...";case"building":return"Building your app...";case"deploying":return"Deploying to Mistflow Cloud...";case"verifying":return"Verifying deployment...";case"live":return"Live!";case"failed":return"Failed";default:return`Status: ${t}`}}var qi=S(()=>{"use strict"});import{z as yt}from"zod";import{resolve as Gd,join as Vd}from"path";import{existsSync as Jd}from"fs";function Yd(t){return Jd(Vd(t,".open-next"))}function Qd(t){return Ue(t)?.deploy?.strategy==="staging"?"preview":null}async function Xd(t){let e=Gd(t.projectPath);if(!oe())return c("Not signed in. Call mist_setup first.",!0);let o=Ue(e)?.projectId;if(!o)return c(`No projectId in mistflow.json at ${e}. Run mist_init first.`,!0);if(!Yd(e))return c(`No .open-next build output at ${e}. Run mist_build first \u2014 the deploy tool ships the build artifact, not the raw source.`,!0);let n=t.environment??Qd(e)??"production",i=null,s=null;try{i=await Li(e),s=await Ui(e);let a=await jo(o,i.path,n,t.adminEmail,void 0,s??void 0,void 0);return c(JSON.stringify({status:"running",jobId:a.deployment_id,deploymentId:a.deployment_id,environment:a.environment??n,buildSize:$i(i.sizeBytes),buildFileCount:i.fileCount,nextAction:`Upload complete. Call mist_deploy { action: 'status', deploymentId: '${a.deployment_id}' } in ~5-10s to poll deployment progress. Do NOT surface the URL to the user until mist_qa passes.`}))}catch(a){let l=a instanceof $||a instanceof Error?a.message:String(a);return c(`Deploy upload failed: ${l}`,!0)}finally{jr(i?.path),jr(s)}}async function Zd(t){try{let e=await Pt(t),r=Fi(e.status);return e.status==="live"?c(JSON.stringify({status:"complete",jobId:e.id,deploymentId:e.id,url:e.url,completedAt:e.completedAt,qaRequired:!0,nextAction:`Deployment is live. Call mist_qa { projectPath, url: '${e.url??""}', deploymentId: '${e.id??""}' } next. Do NOT show the URL to the user until mist_qa returns status: 'pass'.`})):e.status==="failed"?c(JSON.stringify({status:"failed",jobId:e.id,deploymentId:e.id,error:e.error,phase:r,nextAction:"Deployment failed. Read the error message, fix the code, then call mist_deploy { action: 'deploy', projectPath } again."}),!0):c(JSON.stringify({status:"running",jobId:e.id,deploymentId:e.id,phase:r,phaseCode:e.status,nextAction:`Still ${e.status}. Call mist_deploy { action: 'status', deploymentId: '${e.id}' } again in ~5-10s.`}))}catch(e){let r=e instanceof $||e instanceof Error?e.message:String(e);return c(`Could not fetch deploy status: ${r}`,!0)}}async function ep(t,e){if(!oe())return c("Not signed in. Call mist_setup first.",!0);let o=Ue(t)?.projectId;if(!o)return c(`No projectId in mistflow.json at ${t}. Run mist_init first.`,!0);let n=e;if(!n)try{let s=(await it(o)).find(a=>a.environment==="preview"&&a.status==="live");if(!s)return c("No live preview deployment to promote. Deploy to preview first with { action: 'deploy', environment: 'preview' }.",!0);n=s.id}catch(i){let s=i instanceof $?i.message:String(i);return c(`Could not list deployments: ${s}`,!0)}try{let i=await Go(o,n);return c(JSON.stringify({status:"running",jobId:i.deployment_id,deploymentId:i.deployment_id,promotedFrom:n,nextAction:`Promote started. Call mist_deploy { action: 'status', deploymentId: '${i.deployment_id}' } to poll. Promotion re-uses the preview artifact so it typically completes in ~10s.`}))}catch(i){let s=i instanceof $||i instanceof Error?i.message:String(i);return c(`Promote failed: ${s}`,!0)}}async function tp(t){if(!oe())return c("Not signed in. Call mist_setup first.",!0);try{let e=await Vo(t);return c(JSON.stringify({status:"running",jobId:e.deployment_id,deploymentId:e.deployment_id,rollbackFrom:e.rollback_from,nextAction:`Rollback started. Call mist_deploy { action: 'status', deploymentId: '${e.deployment_id}' } to poll.`}))}catch(e){let r=e instanceof $||e instanceof Error?e.message:String(e);return c(`Rollback failed: ${r}`,!0)}}var Kd,Bi,zi=S(()=>{"use strict";X();ue();rt();qi();Kd=yt.object({action:yt.enum(["deploy","promote","rollback","status"]).default("deploy").describe("'deploy' (default) tars + uploads + starts a deployment. 'promote' promotes a staging preview to production. 'rollback' rolls back to a specific deployment. 'status' polls an in-flight deployment by id."),projectPath:yt.string().optional().describe("Absolute path to the project. Required for 'deploy' and 'promote'."),deploymentId:yt.string().optional().describe("Deployment id \u2014 required for 'rollback' and 'status'; the preview id for 'promote'."),environment:yt.enum(["production","preview"]).optional().describe("Deploy target. Defaults to 'production'; auto-redirects to 'preview' when the project uses staging mode."),adminEmail:yt.string().optional().describe("Admin email injected as BETTER_AUTH_ADMIN_EMAIL on first deploy of an auth-enabled project.")});Bi={name:"mist_deploy",description:"Deploy a Mistflow project. action='deploy' (default) tars + uploads + starts backend orchestration; action='status' polls an in-flight deployment; action='promote' ships a staging preview to prod; action='rollback' reverts to a previous deployment. Fire-and-poll: deploy/promote/rollback return a jobId immediately; poll with { action: 'status', deploymentId }. Do NOT show the deploy URL to the user until mist_qa passes \u2014 qaRequired=true is returned on completion.",inputSchema:Kd,handler:async t=>{let e=t;switch(e.action??"deploy"){case"deploy":return e.projectPath?Xd({projectPath:e.projectPath,environment:e.environment,adminEmail:e.adminEmail}):c("projectPath is required for action='deploy'.",!0);case"status":return e.deploymentId?Zd(e.deploymentId):c("deploymentId is required for action='status'.",!0);case"promote":return e.projectPath?ep(e.projectPath,e.deploymentId):c("projectPath is required for action='promote'.",!0);case"rollback":return e.deploymentId?tp(e.deploymentId):c("deploymentId is required for action='rollback'.",!0)}}}});var lp={};import{Server as op}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as rp}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as np,ListToolsRequestSchema as sp}from"@modelcontextprotocol/sdk/types.js";import{zodToJsonSchema as ip}from"zod-to-json-schema";async function ap(){let t=process.argv.indexOf("--api-url");t!==-1&&process.argv[t+1]&&(process.env.MISTFLOW_API_URL=process.argv[t+1]),process.argv.includes("--local")&&!process.env.MISTFLOW_API_URL&&(process.env.MISTFLOW_API_URL="http://localhost:9100");let e=new rp;await vo.connect(e),console.error(`Mistflow MCP server running on stdio (API: ${process.env.MISTFLOW_API_URL||"https://api.mistflow.ai"})`)}var vo,Hi,Wi=S(()=>{"use strict";Me();X();un();Rn();En();Mn();pr();Gn();ss();hs();ii();ui();Pi();_i();Di();zi();vo=new op({name:"mistflow",version:"1.0.0"},{capabilities:{tools:{}},instructions:Br()}),Hi=[pn,_n,Nn,On,ns,ms,zn,si,Wn,pi,Ti,Ai,Ei,Bi];vo.setRequestHandler(sp,async()=>({tools:Hi.map(t=>({name:t.name,description:t.description,inputSchema:ip(t.inputSchema)}))}));vo.setRequestHandler(np,async t=>{let e=Hi.find(r=>r.name===t.params.name);if(!e)return c(`Unknown tool: ${t.params.name}`,!0);try{let r=e.inputSchema.safeParse(t.params.arguments);if(!r.success){let i=r.error.issues.map(s=>`${s.path.join(".")}: ${s.message}`).join(", ");return c(`Invalid input: ${i}`,!0)}let o=t.params._meta?.progressToken,n={server:vo,progressToken:o};try{return await e.handler(r.data,n)}finally{n.cleanup?.()}}catch(r){let o=r instanceof Error?r.message:"An unexpected error occurred";return console.error("Tool error:",r),c(o,!0)}});ap().catch(t=>{console.error("Fatal error:",t),process.exit(1)})});import{readFileSync as cp}from"fs";import{dirname as dp,join as pp}from"path";import{fileURLToPath as up}from"url";var He=process.argv[2];if(He==="--version"||He==="-v"){try{let t=dp(up(import.meta.url)),e=pp(t,"..","package.json"),r=JSON.parse(cp(e,"utf-8"));console.log(r.version)}catch{console.log("unknown")}process.exit(0)}(He==="--help"||He==="-h")&&(console.log(`@mistflow-ai/mcp \u2014 Mistflow MCP server
3417
5417
 
3418
5418
  Usage:
3419
5419
  npx @mistflow-ai/mcp Start the MCP server on stdio (default; invoked by editors)
@@ -3421,8 +5421,8 @@ Usage:
3421
5421
 
3422
5422
  To install the server into your editor config, use the installer:
3423
5423
  npx -y mistflow-ai install
3424
- `),process.exit(0));(ze==="install"||ze==="uninstall")&&(console.error(`'npx @mistflow-ai/mcp ${ze}' is no longer supported.
5424
+ `),process.exit(0));(He==="install"||He==="uninstall")&&(console.error(`'npx @mistflow-ai/mcp ${He}' is no longer supported.
3425
5425
  Use the installer package instead:
3426
5426
 
3427
- npx -y mistflow-ai ${ze}
3428
- `),process.exit(1));await Promise.resolve().then(()=>(xi(),Rd));
5427
+ npx -y mistflow-ai ${He}
5428
+ `),process.exit(1));await Promise.resolve().then(()=>(Wi(),lp));