@mistflow-ai/mcp 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +37 -29
- package/dist/index.js +17 -9
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -20,8 +20,8 @@ This is a major update. Run \`${n}\` and restart your editor.${s}
|
|
|
20
20
|
Run \`${n}\` to upgrade, then restart your editor.${s}`:`
|
|
21
21
|
|
|
22
22
|
(Mistflow ${o.latest} is out, you have ${e}. Run \`${n}\` when convenient.)`}function fn(){let t=pe(),e=ce??{latest:"",minSupported:"",changelogUrl:""},o=jo(t,e.latest,e.minSupported);return{current:t,latest:e.latest,minSupported:e.minSupported,severity:o,upgradeCmd:"npx -y mistflow-ai install",changelogUrl:e.changelogUrl,backendSignalReceived:ce!==null}}var jt,ce,Co,mt=ze(()=>{"use strict";jt=null;ce=null;Co=!1});var Uo={};Nt(Uo,{closeBrowser:()=>bn,getIsolatedContext:()=>Kr,getPage:()=>yn,getSnapshot:()=>ht,takeScreenshot:()=>gt});async function Mo(){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 Jr(){let t=await Mo();return(!de||!de.isConnected())&&(de=await t.chromium.launch({headless:!0})),Ie&&(await Ie.close().catch(()=>{}),Ie=null),Ie=await de.newContext({viewport:{width:1280,height:720},deviceScaleFactor:1}),xe=await Ie.newPage(),xe}async function yn(){return xe&&!xe.isClosed()?xe:(Ot||(Ot=Jr().finally(()=>{Ot=null})),Ot)}async function bn(){xe&&!xe.isClosed()&&await xe.close().catch(()=>{}),Ie&&await Ie.close().catch(()=>{}),de?.isConnected()&&await de.close().catch(()=>{}),xe=null,Ie=null,de=null}async function Kr(){let t=await Mo();(!de||!de.isConnected())&&(de=await t.chromium.launch({headless:!0}));let e=await de.newContext({viewport:{width:1280,height:720},deviceScaleFactor:1}),o=await e.newPage();return{context:e,page:o}}async function ht(t){return await t.locator("body").ariaSnapshot()}async function gt(t,e=!1){return await t.screenshot({fullPage:e,type:"png"})}var de,Ie,xe,Ot,wn=ze(()=>{"use strict";de=null,Ie=null,xe=null,Ot=null;process.once("SIGTERM",()=>{bn().finally(()=>process.exit(0))});process.once("SIGINT",()=>{bn().finally(()=>process.exit(0))})});import{readFileSync as Lo,existsSync as vn,writeFileSync as Yr,mkdirSync as Qr,renameSync as Xr,unlinkSync as Zr}from"fs";import{join as xn,dirname as ei}from"path";import{homedir as ti}from"os";import{randomBytes as ni}from"crypto";function Fo(){return xn(ti(),".mistflow","credentials.json")}function Mt(){return(process.env.MISTFLOW_API_URL||"https://api.mistflow.ai").replace(/\/+$/,"")}function qo(){let t=Fo();if(!vn(t))return null;try{let e=JSON.parse(Lo(t,"utf-8"));return typeof e.apiKey=="string"?{[oi]:e}:e}catch{return null}}function He(){let t=process.env.MISTFLOW_API_KEY;if(t)return{ok:!0,creds:{apiKey:t,orgId:"",orgSlug:"env"}};let e=qo();if(!e)return{ok:!1,reason:"missing"};let o=Mt(),n=e[o];return n&&typeof n.apiKey=="string"&&n.apiKey&&typeof n.orgId=="string"?{ok:!0,creds:n}:{ok:!1,reason:"missing"}}function kn(t){let e=Fo(),o=ei(e);vn(o)||Qr(o,{recursive:!0});let n=qo()??{},s=Mt();n[s]=t;let a=xn(o,`.credentials.tmp.${ni(8).toString("hex")}`);try{Yr(a,JSON.stringify(n,null,2)+`
|
|
23
|
-
`,{mode:384}),Xr(a,e)}catch(r){try{Zr(a)}catch{}throw r}}function G(){return He().ok}function _e(t){let e=xn(t,"mistflow.json");if(!vn(e))return null;try{return JSON.parse(Lo(e,"utf-8"))}catch{return null}}var oi,We=ze(()=>{"use strict";oi="https://api.mistflow.ai"});var Vo={};Nt(Vo,{MistflowApiError:()=>A,addDomain:()=>Rn,checkAuth:()=>ai,checkAuthDetailed:()=>Wo,checkSubdomain:()=>Pn,createDeployment:()=>ci,createProject:()=>yt,deleteEnvVar:()=>Nn,discoverDecisions:()=>pi,downloadSource:()=>gi,downloadSourceWithToken:()=>Go,fetchDesignDirections:()=>_n,fetchModule:()=>Ln,fetchPlanConversation:()=>$t,fetchScaffold:()=>Un,fetchStepContext:()=>$n,forkTemplate:()=>qn,generatePlan:()=>Tn,getAuthHeaders:()=>Ue,getBaseUrl:()=>V,getDashboardUrl:()=>si,getDbCredentials:()=>di,getDeployLogs:()=>jn,getDeploymentStatus:()=>bt,getProject:()=>li,getProjectErrors:()=>Dn,getSeedInfo:()=>ui,getSiteUrl:()=>Ge,getTemplate:()=>Fn,hasCredentialsOnDisk:()=>G,hasLocalCredentials:()=>Ho,listDeployments:()=>Je,listDomains:()=>Te,listEnvVars:()=>Cn,markLocalSetupDone:()=>fi,modifyPlan:()=>Lt,pingBackend:()=>Sn,promotePreview:()=>On,redeployProject:()=>hi,removeDomain:()=>An,rollbackDeployment:()=>Mn,shareProject:()=>Bn,uploadAndDeploy:()=>In,uploadQAResults:()=>mi,upsertEnvVar:()=>En,verifyDomain:()=>Ft});function V(){return Mt()}function Ge(){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 si(){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 Ue(){let t=He();if(!t.ok)throw new A("auth_missing","No Mistflow credentials found.",401);return ri(t.creds)}function ri(t){return{Authorization:`ApiKey ${t.apiKey}`,"Content-Type":"application/json","X-Mistflow-MCP-Version":pe()}}function Ve(t){try{ut(t.headers)}catch{}}async function Sn(){try{let t=await fetch(`${V()}/health`,{method:"GET",signal:AbortSignal.timeout(5e3)});Ve(t)}catch{}}async function zo(t,e,o,n){for(let s=0;s<2;s++)try{return await fetch(t,{...e,signal:AbortSignal.timeout(o)})}catch(a){let r=a instanceof Error&&a.name==="TimeoutError",i=a instanceof TypeError;if(n&&s===0&&(r||i)){console.error(`[api] Retrying ${t} after ${r?"timeout":"network error"}`);continue}throw a}throw new Error("fetchWithRetry: exhausted retries")}async function ft(t){let e=null;try{e=await t.json()}catch{e=null}let o=e&&typeof e.code=="string"?e.code:void 0,n=e&&typeof e.message=="string"?e.message:e&&typeof e.detail=="string"?e.detail:t.statusText||"Request failed";if(o)return new A(o,n,t.status,e?.details);let s=t.status;return s===401?new A("auth_invalid",n,s):s===403?new A("permission_denied",n,s):s===404?new A("not_found",n,s):s===409?new A("conflict",n,s):s===422?new A("validation_error",n,s):s===429?new A("rate_limited",n,s):s>=500?new A("server_error",t.statusText||"Internal server error",s):new A("client_error",n,s)}async function N(t,e={}){let o=Ue(),{timeoutMs:n,idempotent:s,...a}=e,r=n??3e4,i=(a.method??"GET").toUpperCase(),l=s??(i==="GET"||i==="HEAD"),p;try{p=await zo(`${V()}${t}`,{...a,headers:{...o,...a.headers}},r,l)}catch(m){throw m instanceof Error&&m.name==="TimeoutError"?new A("network_error","Request timed out. Try again in a moment."):new A("network_error","Cannot reach Mistflow servers. Check your network.")}if(Ve(p),!p.ok)throw await ft(p);return p.json()}function Ho(){return He().ok}async function ai(){return(await Wo()).ok}async function Wo(){if(Ut!==null&&Date.now()<Bo)return Ut?{ok:!0}:{ok:!1,reason:"no_credentials"};if(!Ho())return{ok:!1,reason:"no_credentials"};try{return await N("/api/org"),Ut=!0,Bo=Date.now()+ii,{ok:!0}}catch(t){if(Ut=null,!(t instanceof A))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 li(t){return N(`/api/projects/${encodeURIComponent(t)}`)}async function Pn(t){return N(`/api/projects/check-subdomain?name=${encodeURIComponent(t)}`)}async function yt(t,e,o="neon",n){return N("/api/projects",{method:"POST",body:JSON.stringify({name:t,template:e,db_provider:o,requested_subdomain:n})})}async function ci(t,e){return N("/api/deploy",{method:"POST",body:JSON.stringify({project_id:t,build_output_url:e})})}async function In(t,e,o="production",n,s,a,r){let{readFileSync:i}=await import("fs"),{basename:l}=await import("path"),p=He();if(!p.ok)throw new A("auth_missing","No Mistflow credentials found.",401);let m=p.creds,u=i(e),d=new Blob([u],{type:"application/gzip"}),b=new FormData;if(b.append("project_id",t),b.append("build",d,l(e)),o!=="production"&&b.append("environment",o),n&&b.append("admin_email",n),s&&b.append("schema_pushed","true"),a){let{existsSync:y}=await import("fs");if(y(a)){let x=i(a),M=new Blob([x],{type:"application/gzip"});b.append("source",M,"source.tar.gz")}}r&&b.append("git_commit_sha",r);let T;try{T=await fetch(`${V()}/api/deploy/upload`,{method:"POST",headers:{Authorization:`ApiKey ${m.apiKey}`,"X-Mistflow-MCP-Version":pe()},body:b,signal:AbortSignal.timeout(3e5)})}catch{throw new A("network_error","Cannot reach Mistflow servers. Check your network.")}if(Ve(T),!T.ok)throw await ft(T);let w=await T.json();return{...w,id:w.deployment_id}}async function bt(t){return N(`/api/deploy/${encodeURIComponent(t)}/status`)}async function _n(t){return N(`/api/plan/design-directions/${encodeURIComponent(t)}`,{timeoutMs:15e3})}async function $t(t){return N(`/api/plan/conversations/${encodeURIComponent(t)}`,{timeoutMs:15e3})}async function Tn(t,e){let o={description:t,conversation_id:e?.conversationId,answers:e?.answers};e?.autonomous&&(o.autonomous=!0),e?.language&&e.language.toLowerCase()!=="english"&&(o.language=e.language),e?.designConversationId&&(o.design_conversation_id=e.designConversationId),e?.designDirection&&(o.design_direction=e.designDirection);let n=e?.conversationId||e?.answers||e?.designConversationId?18e4:6e4;return N("/api/plan",{method:"POST",body:JSON.stringify(o),timeoutMs:n,idempotent:!1})}async function Lt(t,e){return N("/api/plan/modify",{method:"POST",body:JSON.stringify({existing_plan:t,modification:e})})}async function pi(t){return N("/api/plan/discover",{method:"POST",body:JSON.stringify({description:t})})}async function Rn(t,e){return N(`/api/projects/${encodeURIComponent(t)}/domains`,{method:"POST",body:JSON.stringify({domain:e})})}async function Te(t){return N(`/api/projects/${encodeURIComponent(t)}/domains`)}async function Ft(t,e){return N(`/api/projects/${encodeURIComponent(t)}/domains/${encodeURIComponent(e)}/verify`)}async function An(t,e){return N(`/api/projects/${encodeURIComponent(t)}/domains/${encodeURIComponent(e)}`,{method:"DELETE"})}async function di(t){return N(`/api/projects/${encodeURIComponent(t)}/db-credentials`)}async function ui(t){try{return await N(`/api/projects/${encodeURIComponent(t)}/test-accounts`)}catch(e){return e instanceof A&&(e.statusCode===404||e.code==="not_found")||console.error("[api] Failed to fetch seed info:",e instanceof Error?e.message:e),null}}async function mi(t,e){try{return await N(`/api/deploy/${encodeURIComponent(t)}/qa-results`,{method:"POST",body:JSON.stringify(e)})}catch(o){return console.error("[api] Failed to upload QA results:",o instanceof Error?o.message:o),null}}async function Cn(t){return N(`/api/projects/${encodeURIComponent(t)}/env`)}async function En(t,e,o,n){return N(`/api/projects/${encodeURIComponent(t)}/env`,{method:"PUT",body:JSON.stringify({key:e,value:o,category:n?.category??"custom",description:n?.description,setup_url:n?.setupUrl})})}async function Nn(t,e){return N(`/api/projects/${encodeURIComponent(t)}/env/${encodeURIComponent(e)}`,{method:"DELETE"})}async function jn(t){return N(`/api/deploy/${encodeURIComponent(t)}/logs`)}async function Dn(t,e="7d"){return N(`/api/projects/${encodeURIComponent(t)}/errors?period=${encodeURIComponent(e)}`)}async function Je(t){return N(`/api/projects/${encodeURIComponent(t)}/deployments`)}async function hi(t){return N(`/api/deploy/${encodeURIComponent(t)}/redeploy`,{method:"POST"})}async function On(t,e){let o=new URLSearchParams({preview_deployment_id:e});return N(`/api/deploy/${encodeURIComponent(t)}/promote`,{method:"POST",body:o.toString(),headers:{"Content-Type":"application/x-www-form-urlencoded"}})}async function Mn(t){return N(`/api/deploy/${encodeURIComponent(t)}/rollback`,{method:"POST"})}async function gi(t,e){let o=He();if(!o.ok)throw new A("auth_missing","Not authenticated.",401);let n=o.creds,s=await fetch(`${V()}/api/deploy/${encodeURIComponent(t)}/source`,{headers:{Authorization:`ApiKey ${n.apiKey}`,"X-Mistflow-MCP-Version":pe()},signal:AbortSignal.timeout(12e4)});if(Ve(s),!s.ok)throw await ft(s);let{writeFileSync:a}=await import("fs"),r=Buffer.from(await s.arrayBuffer());a(e,r)}async function qt(t,e){let{timeoutMs:o,idempotent:n,...s}=e??{},a=o??3e4,r=(s.method??"GET").toUpperCase(),i=n??(r==="GET"||r==="HEAD"),l;try{l=await zo(`${V()}${t}`,{headers:{"Content-Type":"application/json","X-Mistflow-MCP-Version":pe()},...s},a,i)}catch(p){throw p instanceof Error&&p.name==="TimeoutError"?new A("network_error","Request timed out. Try again in a moment."):new A("network_error","Cannot reach Mistflow servers. Check your network.")}if(Ve(l),!l.ok)throw await ft(l);return l.json()}async function Un(t="nextjs"){return qt(`/api/scaffold/${encodeURIComponent(t)}`)}async function $n(t,e){return qt(`/api/scaffold/${encodeURIComponent(t)}/context?step_type=${encodeURIComponent(e)}`)}async function Ln(t,e,o,n){return qt(`/api/scaffold/${encodeURIComponent(t)}/module`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({type:"crud",entity:e,fields:o,entity_plural:n})})}async function Fn(t){return qt(`/api/templates/${encodeURIComponent(t)}`)}async function qn(t){return N(`/api/templates/${encodeURIComponent(t)}/fork`,{method:"POST"})}async function Go(t,e,o){let n;try{n=await fetch(`${V()}/api/deploy/${encodeURIComponent(t)}/fork-source?fork_token=${encodeURIComponent(e)}`,{headers:{"X-Mistflow-MCP-Version":pe()},signal:AbortSignal.timeout(12e4)})}catch{throw new A("network_error","Cannot reach Mistflow servers. Check your network.")}if(Ve(n),!n.ok)throw n.status===401?new A("validation_error","Fork token expired or invalid. Re-open the project in the dashboard to get a fresh token.",n.status):await ft(n);let{writeFileSync:s}=await import("fs"),a=Buffer.from(await n.arrayBuffer());s(o,a)}async function fi(t){await N(`/api/projects/${encodeURIComponent(t)}`,{method:"PATCH",body:JSON.stringify({local_setup_done:!0})})}async function Bn(t,e){return N(`/api/projects/${encodeURIComponent(t)}/share`,{method:"POST",body:JSON.stringify({is_template:e?.isTemplate??!1,template_description:e?.description})})}var A,Ut,Bo,ii,ie=ze(()=>{"use strict";We();mt();A=class extends Error{constructor(o,n,s,a){super(n);this.code=o;this.statusCode=s;this.details=a;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"}};Ut=null,Bo=0,ii=300*1e3});var xt={};Nt(xt,{emptyState:()=>vt,fetchRemoteState:()=>Ai,fuzzyMatch:()=>Ei,getLocalStatePath:()=>Hn,readLocalState:()=>Ri,syncRemoteState:()=>Ci,writeLocalState:()=>wt});import{existsSync as Zo,readFileSync as Ii,writeFileSync as _i,mkdirSync as Ti}from"fs";import{join as es}from"path";function Hn(t){return es(t,".mistflow","state.json")}function Ri(t){let e=Hn(t);if(!Zo(e))return null;try{return JSON.parse(Ii(e,"utf-8"))}catch{return null}}function wt(t,e){let o=es(t,".mistflow");Zo(o)||Ti(o,{recursive:!0}),_i(Hn(t),JSON.stringify(e,null,2)+`
|
|
24
|
-
`)}function vt(t,e){return{projectId:t,name:e,template:"",features:[],dbSchema:[],deployCount:0,decisions:[],provenance:[]}}async function Ai(t){let e;try{e=Ue()}catch{return null}try{let o=await fetch(`${V()}/api/projects/${encodeURIComponent(t)}/state`,{headers:{...e,"Content-Type":"application/json","X-Mistflow-MCP-Version":pe()}});try{ut(o.headers)}catch{}return o.ok?await o.json():null}catch{return null}}async function Ci(t,e){let o;try{o=Ue()}catch{return!1}try{let n=await fetch(`${V()}/api/projects/${encodeURIComponent(t)}/state`,{method:"PUT",headers:{...o,"Content-Type":"application/json","X-Mistflow-MCP-Version":pe()},body:JSON.stringify(e)});try{ut(n.headers)}catch{}return n.ok}catch{return!1}}function Ei(t,e){let o=t.toLowerCase(),n=e.toLowerCase();return n.includes(o)||o.includes(n)?!0:o.split(/\s+/).some(a=>a.length>=3&&n.includes(a))}var $e=ze(()=>{"use strict";ie();mt()});var Wn={};Nt(Wn,{ensureBackendRegistered:()=>$i,ensureShadcnComponents:()=>Li});import{existsSync as ns,readFileSync as Ni,writeFileSync as ji}from"fs";import{join as Bt}from"path";import{spawn as Di}from"child_process";function Oi(t,e,o,n,s){return new Promise(a=>{let r=Di(t,e,{cwd:o,stdio:["pipe","pipe","pipe"],timeout:n,...s?{env:{...process.env,...s}}:{}}),i="",l="";r.stdout?.on("data",p=>{i+=p.toString()}),r.stderr?.on("data",p=>{l+=p.toString()}),r.on("close",p=>a({success:p===0,stdout:i,stderr:l})),r.on("error",p=>a({success:!1,stdout:i,stderr:l+p.message}))})}function Mi(t){let e=Bt(t,"mistflow.json");if(!ns(e))return null;try{return JSON.parse(Ni(e,"utf-8"))}catch{return null}}function Ui(t,e){ji(Bt(t,"mistflow.json"),JSON.stringify(e,null,2))}async function ts(t,e){try{let o=e.features,n=e.steps,s={plan:e};Array.isArray(o)&&o.length>0&&(s.features=o.map(a=>a.name)),Array.isArray(n)&&n.length>0&&(s.provenance=n.map(a=>({feature:a.name??a.title??`Step ${a.number??"?"}`,user_intent:(a.description??"").slice(0,500),decisions:"Seeded from plan",tradeoffs:"",files_affected:[]}))),await fetch(`${V()}/api/projects/${encodeURIComponent(t)}/state`,{method:"PUT",headers:{...Ue(),"Content-Type":"application/json"},body:JSON.stringify(s)})}catch(o){console.error("[self-heal] state sync failed:",o instanceof Error?o.message:String(o))}}async function $i(t,e={}){let o=Mi(t);if(o){if(!G())return o.projectId;if(!o.projectId)try{let s=o.plan?.requestedSubdomain,a=await yt(o.name,void 0,o.dbProvider??"neon",s);return o.projectId=a.id,Ui(t,o),wt(t,vt(a.id,o.name)),o.plan&&await ts(a.id,o.plan),console.error(`[self-heal] registered project ${a.id.slice(0,8)} with backend`),a.id}catch(n){console.error("[self-heal] createProject failed:",n instanceof Error?n.message:String(n));return}return e.forceSync&&o.plan&&o.projectId&&await ts(o.projectId,o.plan),o.projectId}}async function Li(t,e){let o=["button","card","input","label","form","dialog","table","dropdown-menu","badge","separator","skeleton","sheet","tabs","avatar","select","textarea","checkbox","switch","tooltip","popover","sonner"],n=e&&e.length>0?e:o,s=Bt(t,"components","ui"),a=[],r=[];for(let p of n)ns(Bt(s,`${p}.tsx`))?a.push(p):r.push(p);if(r.length===0)return{installed:[],alreadyPresent:a};let i={NPM_CONFIG_LEGACY_PEER_DEPS:"true"},l=await Oi("npx",["--yes","shadcn@latest","add","-y","-o",...r],t,18e4,i);return l.success?{installed:r,alreadyPresent:a}:{installed:[],alreadyPresent:a,failed:`shadcn add failed for: ${r.join(", ")}. ${l.stderr.slice(-300)}`.trim()}}var Gn=ze(()=>{"use strict";ie();$e()});import{Server as Ic}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as _c}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as Tc,ListToolsRequestSchema as Rc}from"@modelcontextprotocol/sdk/types.js";import{zodToJsonSchema as Ac}from"zod-to-json-schema";var Nr="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.",jr='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"`.',Dr="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.",Or="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.",Mr='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.',Ur="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.",$r="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.",Lr='Long-running tools (mist_install, mist_build, mist_qa, mist_deploy) use the fire-and-poll pattern. The first call returns `status: "running"` with a `jobId`. Every subsequent call with that `jobId` returns the current state \u2014 re-call whenever you see `status: "running"`. Each response is <1s so there is no 60s timeout risk. When `status: "complete"` or `"failed"` arrives, the tool is done. Never assume a job stalled because a single call took 20s \u2014 calls return fast, but the job itself keeps running in the background. Do not ask the user whether to keep polling; polling is how these tools work.',Fr="When `mist_plan` returns a `questions[]` array (or `directions[]` for design picking), render each one via your native structured-question UI \u2014 `AskUserQuestion` in Claude Code, quick pick in Cursor, equivalents in other hosts. Do NOT just print them as plain numbered text; proper radio-button-style choices dramatically improve response quality. Each question has `id`/`decisionKey`/`question`/`options[]` (each option: `label`, `description`) plus an optional `recommended` hint. Collect answers, submit them back via `mist_plan` with the `conversationId` \u2014 don't ask the user to do that step.",ko="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).",So="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.",Po="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.",Io="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.",_o="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).",To="Generate a grayscale wireframe sketch of the planned app so the user can review layout + information hierarchy before any code is written. The server returns a structured spec + a wireframePrompt \u2014 the host AI writes the HTML file to .mistflow/mockups/mockup-<planId>.html and opens it. Pass { planId } for the first iteration, { planId, feedback: '...' } to iterate, { planId, approved: true } to lock the design before mist_init. Iterative by design \u2014 expect 1-3 rounds of feedback.";function Ro(){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(Lr),t.push(""),t.push(Nr),t.push(""),t.push(jr),t.push(""),t.push(Dr),t.push(""),t.push(Or),t.push(""),t.push(Fr),t.push(""),t.push(Ur),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 } starts Playwright; poll with { jobId }. Only surface the deploy URL to the user after QA exits 0."),t.push(""),t.push(Mr),t.push(""),t.push($r),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_qa / mist_deploy: fire-and-poll tools. Always check `status` in the response and keep polling while 'running'."),t.join(`
|
|
23
|
+
`,{mode:384}),Xr(a,e)}catch(r){try{Zr(a)}catch{}throw r}}function G(){return He().ok}function _e(t){let e=xn(t,"mistflow.json");if(!vn(e))return null;try{return JSON.parse(Lo(e,"utf-8"))}catch{return null}}var oi,We=ze(()=>{"use strict";oi="https://api.mistflow.ai"});var Vo={};Nt(Vo,{MistflowApiError:()=>A,addDomain:()=>Rn,checkAuth:()=>ai,checkAuthDetailed:()=>Wo,checkSubdomain:()=>Pn,createDeployment:()=>ci,createProject:()=>yt,deleteEnvVar:()=>Nn,discoverDecisions:()=>pi,downloadSource:()=>gi,downloadSourceWithToken:()=>Go,fetchDesignDirections:()=>_n,fetchModule:()=>Ln,fetchPlanConversation:()=>$t,fetchScaffold:()=>Un,fetchStepContext:()=>$n,forkTemplate:()=>qn,generatePlan:()=>Tn,getAuthHeaders:()=>Ue,getBaseUrl:()=>V,getDashboardUrl:()=>si,getDbCredentials:()=>di,getDeployLogs:()=>jn,getDeploymentStatus:()=>bt,getProject:()=>li,getProjectErrors:()=>Dn,getSeedInfo:()=>ui,getSiteUrl:()=>Ge,getTemplate:()=>Fn,hasCredentialsOnDisk:()=>G,hasLocalCredentials:()=>Ho,listDeployments:()=>Je,listDomains:()=>Te,listEnvVars:()=>Cn,markLocalSetupDone:()=>fi,modifyPlan:()=>Lt,pingBackend:()=>Sn,promotePreview:()=>On,redeployProject:()=>hi,removeDomain:()=>An,rollbackDeployment:()=>Mn,shareProject:()=>Bn,uploadAndDeploy:()=>In,uploadQAResults:()=>mi,upsertEnvVar:()=>En,verifyDomain:()=>Ft});function V(){return Mt()}function Ge(){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 si(){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 Ue(){let t=He();if(!t.ok)throw new A("auth_missing","No Mistflow credentials found.",401);return ri(t.creds)}function ri(t){return{Authorization:`ApiKey ${t.apiKey}`,"Content-Type":"application/json","X-Mistflow-MCP-Version":pe()}}function Ve(t){try{ut(t.headers)}catch{}}async function Sn(){try{let t=await fetch(`${V()}/health`,{method:"GET",signal:AbortSignal.timeout(5e3)});Ve(t)}catch{}}async function zo(t,e,o,n){for(let s=0;s<2;s++)try{return await fetch(t,{...e,signal:AbortSignal.timeout(o)})}catch(a){let r=a instanceof Error&&a.name==="TimeoutError",i=a instanceof TypeError;if(n&&s===0&&(r||i)){console.error(`[api] Retrying ${t} after ${r?"timeout":"network error"}`);continue}throw a}throw new Error("fetchWithRetry: exhausted retries")}async function ft(t){let e=null;try{e=await t.json()}catch{e=null}let o=e&&typeof e.code=="string"?e.code:void 0,n=e&&typeof e.message=="string"?e.message:e&&typeof e.detail=="string"?e.detail:t.statusText||"Request failed";if(o)return new A(o,n,t.status,e?.details);let s=t.status;return s===401?new A("auth_invalid",n,s):s===403?new A("permission_denied",n,s):s===404?new A("not_found",n,s):s===409?new A("conflict",n,s):s===422?new A("validation_error",n,s):s===429?new A("rate_limited",n,s):s>=500?new A("server_error",t.statusText||"Internal server error",s):new A("client_error",n,s)}async function N(t,e={}){let o=Ue(),{timeoutMs:n,idempotent:s,...a}=e,r=n??3e4,i=(a.method??"GET").toUpperCase(),l=s??(i==="GET"||i==="HEAD"),p;try{p=await zo(`${V()}${t}`,{...a,headers:{...o,...a.headers}},r,l)}catch(m){throw m instanceof Error&&m.name==="TimeoutError"?new A("network_error","Request timed out. Try again in a moment."):new A("network_error","Cannot reach Mistflow servers. Check your network.")}if(Ve(p),!p.ok)throw await ft(p);return p.json()}function Ho(){return He().ok}async function ai(){return(await Wo()).ok}async function Wo(){if(Ut!==null&&Date.now()<Bo)return Ut?{ok:!0}:{ok:!1,reason:"no_credentials"};if(!Ho())return{ok:!1,reason:"no_credentials"};try{return await N("/api/org"),Ut=!0,Bo=Date.now()+ii,{ok:!0}}catch(t){if(Ut=null,!(t instanceof A))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 li(t){return N(`/api/projects/${encodeURIComponent(t)}`)}async function Pn(t){return N(`/api/projects/check-subdomain?name=${encodeURIComponent(t)}`)}async function yt(t,e,o="neon",n){return N("/api/projects",{method:"POST",body:JSON.stringify({name:t,template:e,db_provider:o,requested_subdomain:n})})}async function ci(t,e){return N("/api/deploy",{method:"POST",body:JSON.stringify({project_id:t,build_output_url:e})})}async function In(t,e,o="production",n,s,a,r){let{readFileSync:i}=await import("fs"),{basename:l}=await import("path"),p=He();if(!p.ok)throw new A("auth_missing","No Mistflow credentials found.",401);let m=p.creds,u=i(e),d=new Blob([u],{type:"application/gzip"}),b=new FormData;if(b.append("project_id",t),b.append("build",d,l(e)),o!=="production"&&b.append("environment",o),n&&b.append("admin_email",n),s&&b.append("schema_pushed","true"),a){let{existsSync:y}=await import("fs");if(y(a)){let x=i(a),M=new Blob([x],{type:"application/gzip"});b.append("source",M,"source.tar.gz")}}r&&b.append("git_commit_sha",r);let T;try{T=await fetch(`${V()}/api/deploy/upload`,{method:"POST",headers:{Authorization:`ApiKey ${m.apiKey}`,"X-Mistflow-MCP-Version":pe()},body:b,signal:AbortSignal.timeout(3e5)})}catch{throw new A("network_error","Cannot reach Mistflow servers. Check your network.")}if(Ve(T),!T.ok)throw await ft(T);let w=await T.json();return{...w,id:w.deployment_id}}async function bt(t){return N(`/api/deploy/${encodeURIComponent(t)}/status`)}async function _n(t){return N(`/api/plan/design-directions/${encodeURIComponent(t)}`,{timeoutMs:15e3})}async function $t(t,e){let o=e?.waitSeconds??10,n=o>0?`?wait=${o}`:"",s=Math.min(6e4,(o+5)*1e3);return N(`/api/plan/conversations/${encodeURIComponent(t)}${n}`,{timeoutMs:s})}async function Tn(t,e){let o={description:t,conversation_id:e?.conversationId,answers:e?.answers};e?.autonomous&&(o.autonomous=!0),e?.language&&e.language.toLowerCase()!=="english"&&(o.language=e.language),e?.designConversationId&&(o.design_conversation_id=e.designConversationId),e?.designDirection&&(o.design_direction=e.designDirection);let n=e?.conversationId||e?.answers||e?.designConversationId?18e4:6e4;return N("/api/plan",{method:"POST",body:JSON.stringify(o),timeoutMs:n,idempotent:!1})}async function Lt(t,e){return N("/api/plan/modify",{method:"POST",body:JSON.stringify({existing_plan:t,modification:e})})}async function pi(t){return N("/api/plan/discover",{method:"POST",body:JSON.stringify({description:t})})}async function Rn(t,e){return N(`/api/projects/${encodeURIComponent(t)}/domains`,{method:"POST",body:JSON.stringify({domain:e})})}async function Te(t){return N(`/api/projects/${encodeURIComponent(t)}/domains`)}async function Ft(t,e){return N(`/api/projects/${encodeURIComponent(t)}/domains/${encodeURIComponent(e)}/verify`)}async function An(t,e){return N(`/api/projects/${encodeURIComponent(t)}/domains/${encodeURIComponent(e)}`,{method:"DELETE"})}async function di(t){return N(`/api/projects/${encodeURIComponent(t)}/db-credentials`)}async function ui(t){try{return await N(`/api/projects/${encodeURIComponent(t)}/test-accounts`)}catch(e){return e instanceof A&&(e.statusCode===404||e.code==="not_found")||console.error("[api] Failed to fetch seed info:",e instanceof Error?e.message:e),null}}async function mi(t,e){try{return await N(`/api/deploy/${encodeURIComponent(t)}/qa-results`,{method:"POST",body:JSON.stringify(e)})}catch(o){return console.error("[api] Failed to upload QA results:",o instanceof Error?o.message:o),null}}async function Cn(t){return N(`/api/projects/${encodeURIComponent(t)}/env`)}async function En(t,e,o,n){return N(`/api/projects/${encodeURIComponent(t)}/env`,{method:"PUT",body:JSON.stringify({key:e,value:o,category:n?.category??"custom",description:n?.description,setup_url:n?.setupUrl})})}async function Nn(t,e){return N(`/api/projects/${encodeURIComponent(t)}/env/${encodeURIComponent(e)}`,{method:"DELETE"})}async function jn(t){return N(`/api/deploy/${encodeURIComponent(t)}/logs`)}async function Dn(t,e="7d"){return N(`/api/projects/${encodeURIComponent(t)}/errors?period=${encodeURIComponent(e)}`)}async function Je(t){return N(`/api/projects/${encodeURIComponent(t)}/deployments`)}async function hi(t){return N(`/api/deploy/${encodeURIComponent(t)}/redeploy`,{method:"POST"})}async function On(t,e){let o=new URLSearchParams({preview_deployment_id:e});return N(`/api/deploy/${encodeURIComponent(t)}/promote`,{method:"POST",body:o.toString(),headers:{"Content-Type":"application/x-www-form-urlencoded"}})}async function Mn(t){return N(`/api/deploy/${encodeURIComponent(t)}/rollback`,{method:"POST"})}async function gi(t,e){let o=He();if(!o.ok)throw new A("auth_missing","Not authenticated.",401);let n=o.creds,s=await fetch(`${V()}/api/deploy/${encodeURIComponent(t)}/source`,{headers:{Authorization:`ApiKey ${n.apiKey}`,"X-Mistflow-MCP-Version":pe()},signal:AbortSignal.timeout(12e4)});if(Ve(s),!s.ok)throw await ft(s);let{writeFileSync:a}=await import("fs"),r=Buffer.from(await s.arrayBuffer());a(e,r)}async function qt(t,e){let{timeoutMs:o,idempotent:n,...s}=e??{},a=o??3e4,r=(s.method??"GET").toUpperCase(),i=n??(r==="GET"||r==="HEAD"),l;try{l=await zo(`${V()}${t}`,{headers:{"Content-Type":"application/json","X-Mistflow-MCP-Version":pe()},...s},a,i)}catch(p){throw p instanceof Error&&p.name==="TimeoutError"?new A("network_error","Request timed out. Try again in a moment."):new A("network_error","Cannot reach Mistflow servers. Check your network.")}if(Ve(l),!l.ok)throw await ft(l);return l.json()}async function Un(t="nextjs"){return qt(`/api/scaffold/${encodeURIComponent(t)}`)}async function $n(t,e){return qt(`/api/scaffold/${encodeURIComponent(t)}/context?step_type=${encodeURIComponent(e)}`)}async function Ln(t,e,o,n){return qt(`/api/scaffold/${encodeURIComponent(t)}/module`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({type:"crud",entity:e,fields:o,entity_plural:n})})}async function Fn(t){return qt(`/api/templates/${encodeURIComponent(t)}`)}async function qn(t){return N(`/api/templates/${encodeURIComponent(t)}/fork`,{method:"POST"})}async function Go(t,e,o){let n;try{n=await fetch(`${V()}/api/deploy/${encodeURIComponent(t)}/fork-source?fork_token=${encodeURIComponent(e)}`,{headers:{"X-Mistflow-MCP-Version":pe()},signal:AbortSignal.timeout(12e4)})}catch{throw new A("network_error","Cannot reach Mistflow servers. Check your network.")}if(Ve(n),!n.ok)throw n.status===401?new A("validation_error","Fork token expired or invalid. Re-open the project in the dashboard to get a fresh token.",n.status):await ft(n);let{writeFileSync:s}=await import("fs"),a=Buffer.from(await n.arrayBuffer());s(o,a)}async function fi(t){await N(`/api/projects/${encodeURIComponent(t)}`,{method:"PATCH",body:JSON.stringify({local_setup_done:!0})})}async function Bn(t,e){return N(`/api/projects/${encodeURIComponent(t)}/share`,{method:"POST",body:JSON.stringify({is_template:e?.isTemplate??!1,template_description:e?.description})})}var A,Ut,Bo,ii,ie=ze(()=>{"use strict";We();mt();A=class extends Error{constructor(o,n,s,a){super(n);this.code=o;this.statusCode=s;this.details=a;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"}};Ut=null,Bo=0,ii=300*1e3});var xt={};Nt(xt,{emptyState:()=>vt,fetchRemoteState:()=>Ai,fuzzyMatch:()=>Ei,getLocalStatePath:()=>Hn,readLocalState:()=>Ri,syncRemoteState:()=>Ci,writeLocalState:()=>wt});import{existsSync as Zo,readFileSync as Ii,writeFileSync as _i,mkdirSync as Ti}from"fs";import{join as es}from"path";function Hn(t){return es(t,".mistflow","state.json")}function Ri(t){let e=Hn(t);if(!Zo(e))return null;try{return JSON.parse(Ii(e,"utf-8"))}catch{return null}}function wt(t,e){let o=es(t,".mistflow");Zo(o)||Ti(o,{recursive:!0}),_i(Hn(t),JSON.stringify(e,null,2)+`
|
|
24
|
+
`)}function vt(t,e){return{projectId:t,name:e,template:"",features:[],dbSchema:[],deployCount:0,decisions:[],provenance:[]}}async function Ai(t){let e;try{e=Ue()}catch{return null}try{let o=await fetch(`${V()}/api/projects/${encodeURIComponent(t)}/state`,{headers:{...e,"Content-Type":"application/json","X-Mistflow-MCP-Version":pe()}});try{ut(o.headers)}catch{}return o.ok?await o.json():null}catch{return null}}async function Ci(t,e){let o;try{o=Ue()}catch{return!1}try{let n=await fetch(`${V()}/api/projects/${encodeURIComponent(t)}/state`,{method:"PUT",headers:{...o,"Content-Type":"application/json","X-Mistflow-MCP-Version":pe()},body:JSON.stringify(e)});try{ut(n.headers)}catch{}return n.ok}catch{return!1}}function Ei(t,e){let o=t.toLowerCase(),n=e.toLowerCase();return n.includes(o)||o.includes(n)?!0:o.split(/\s+/).some(a=>a.length>=3&&n.includes(a))}var $e=ze(()=>{"use strict";ie();mt()});var Wn={};Nt(Wn,{ensureBackendRegistered:()=>$i,ensureShadcnComponents:()=>Li});import{existsSync as ns,readFileSync as Ni,writeFileSync as ji}from"fs";import{join as Bt}from"path";import{spawn as Di}from"child_process";function Oi(t,e,o,n,s){return new Promise(a=>{let r=Di(t,e,{cwd:o,stdio:["pipe","pipe","pipe"],timeout:n,...s?{env:{...process.env,...s}}:{}}),i="",l="";r.stdout?.on("data",p=>{i+=p.toString()}),r.stderr?.on("data",p=>{l+=p.toString()}),r.on("close",p=>a({success:p===0,stdout:i,stderr:l})),r.on("error",p=>a({success:!1,stdout:i,stderr:l+p.message}))})}function Mi(t){let e=Bt(t,"mistflow.json");if(!ns(e))return null;try{return JSON.parse(Ni(e,"utf-8"))}catch{return null}}function Ui(t,e){ji(Bt(t,"mistflow.json"),JSON.stringify(e,null,2))}async function ts(t,e){try{let o=e.features,n=e.steps,s={plan:e};Array.isArray(o)&&o.length>0&&(s.features=o.map(a=>a.name)),Array.isArray(n)&&n.length>0&&(s.provenance=n.map(a=>({feature:a.name??a.title??`Step ${a.number??"?"}`,user_intent:(a.description??"").slice(0,500),decisions:"Seeded from plan",tradeoffs:"",files_affected:[]}))),await fetch(`${V()}/api/projects/${encodeURIComponent(t)}/state`,{method:"PUT",headers:{...Ue(),"Content-Type":"application/json"},body:JSON.stringify(s)})}catch(o){console.error("[self-heal] state sync failed:",o instanceof Error?o.message:String(o))}}async function $i(t,e={}){let o=Mi(t);if(o){if(!G())return o.projectId;if(!o.projectId)try{let s=o.plan?.requestedSubdomain,a=await yt(o.name,void 0,o.dbProvider??"neon",s);return o.projectId=a.id,Ui(t,o),wt(t,vt(a.id,o.name)),o.plan&&await ts(a.id,o.plan),console.error(`[self-heal] registered project ${a.id.slice(0,8)} with backend`),a.id}catch(n){console.error("[self-heal] createProject failed:",n instanceof Error?n.message:String(n));return}return e.forceSync&&o.plan&&o.projectId&&await ts(o.projectId,o.plan),o.projectId}}async function Li(t,e){let o=["button","card","input","label","form","dialog","table","dropdown-menu","badge","separator","skeleton","sheet","tabs","avatar","select","textarea","checkbox","switch","tooltip","popover","sonner"],n=e&&e.length>0?e:o,s=Bt(t,"components","ui"),a=[],r=[];for(let p of n)ns(Bt(s,`${p}.tsx`))?a.push(p):r.push(p);if(r.length===0)return{installed:[],alreadyPresent:a};let i={NPM_CONFIG_LEGACY_PEER_DEPS:"true"},l=await Oi("npx",["--yes","shadcn@latest","add","-y","-o",...r],t,18e4,i);return l.success?{installed:r,alreadyPresent:a}:{installed:[],alreadyPresent:a,failed:`shadcn add failed for: ${r.join(", ")}. ${l.stderr.slice(-300)}`.trim()}}var Gn=ze(()=>{"use strict";ie();$e()});import{Server as Ic}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as _c}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as Tc,ListToolsRequestSchema as Rc}from"@modelcontextprotocol/sdk/types.js";import{zodToJsonSchema as Ac}from"zod-to-json-schema";var Nr="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.",jr='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"`.',Dr="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.",Or="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.",Mr='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.',Ur="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.",$r="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.",Lr='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.',Fr="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.",ko="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).",So="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.",Po="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.",Io="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.",_o="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).",To="Generate a grayscale wireframe sketch of the planned app so the user can review layout + information hierarchy before any code is written. The server returns a structured spec + a wireframePrompt \u2014 the host AI writes the HTML file to .mistflow/mockups/mockup-<planId>.html and opens it. Pass { planId } for the first iteration, { planId, feedback: '...' } to iterate, { planId, approved: true } to lock the design before mist_init. Iterative by design \u2014 expect 1-3 rounds of feedback.";function Ro(){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(Lr),t.push(""),t.push(Nr),t.push(""),t.push(jr),t.push(""),t.push(Dr),t.push(""),t.push(Or),t.push(""),t.push(Fr),t.push(""),t.push(Ur),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 } starts Playwright; poll with { jobId }. Only surface the deploy URL to the user after QA exits 0."),t.push(""),t.push(Mr),t.push(""),t.push($r),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_qa / mist_deploy: fire-and-poll tools. Always check `status` in the response and keep polling while 'running'."),t.join(`
|
|
25
25
|
`)}mt();function c(t,e=!1){let o=t;try{let n=Oo();n&&(o=t+n)}catch{}return{content:[{type:"text",text:o}],isError:e}}function ke(t){return c(`This is not a Mistflow project (no mistflow.json found at ${t}).
|
|
26
26
|
|
|
27
27
|
Mistflow creates new projects from scratch \u2014 it doesn't work inside existing codebases.
|
|
@@ -1485,10 +1485,18 @@ they want to keep waiting \u2014 polling is how these tools work. Each poll
|
|
|
1485
1485
|
responds in under a second; the underlying work continues detached.
|
|
1486
1486
|
|
|
1487
1487
|
When \`mist_plan\` returns \`status: "clarify"\`, the first planning pass is
|
|
1488
|
-
complete and waiting on user answers.
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1488
|
+
complete and waiting on user answers. You MUST render every question via
|
|
1489
|
+
AskUserQuestion (Claude Code) or the equivalent structured picker in your
|
|
1490
|
+
host \u2014 and wait for the user to answer. DO NOT pick the \`recommended\`
|
|
1491
|
+
option on their behalf. DO NOT print the questions as markdown with a
|
|
1492
|
+
"say the word if you want to change any" fallback. DO NOT proceed without
|
|
1493
|
+
real answers even inside \`/loop\` or other autonomous modes \u2014 stop and
|
|
1494
|
+
wait; the flow resumes when the user responds. Every question is a
|
|
1495
|
+
product decision the user is paying to make; \`recommended\` is a hint for
|
|
1496
|
+
them, not permission for you to decide.
|
|
1497
|
+
|
|
1498
|
+
Do NOT re-call mist_plan or describe it as "still generating". Only
|
|
1499
|
+
re-call to submit the answers, or to poll when status is
|
|
1492
1500
|
\`"design_clarify_pending"\`.
|
|
1493
1501
|
|
|
1494
1502
|
Before scaffolding a NEW app, resolve the destination path explicitly. If
|
|
@@ -2203,15 +2211,15 @@ ${n}
|
|
|
2203
2211
|
</div>
|
|
2204
2212
|
</body>
|
|
2205
2213
|
</html>
|
|
2206
|
-
`}var Qt="__mistflow_url_choice__",ka=600*1e3;function js(){let t=te(Ce(),".mistflow","confirm-secret");if(St(t))try{return Buffer.from(Es(t,"utf-8").trim(),"hex")}catch{}let e=va(32);return Xt(te(Ce(),".mistflow"),{recursive:!0}),Zt(t,e.toString("hex"),{mode:384}),e}function Ds(t){return wa("sha256").update(t.trim().toLowerCase()).digest("hex").slice(0,16)}function Sa(t,e){let o={cwd:t,d:Ds(e),exp:Date.now()+ka},n=Buffer.from(JSON.stringify(o)).toString("base64url"),s=Ns("sha256",js()).update(n).digest("base64url");return`${n}.${s}`}function Pa(t,e,o){let n=t.split(".");if(n.length!==2)return!1;let[s,a]=n,r=Ns("sha256",js()).update(s).digest("base64url"),i=Buffer.from(a),l=Buffer.from(r);if(i.length!==l.length||!xa(i,l))return!1;try{let p=JSON.parse(Buffer.from(s,"base64url").toString("utf-8"));return!(typeof p.exp!="number"||Date.now()>p.exp||p.cwd!==e||p.d!==Ds(o))}catch{return!1}}function Ia(t){let e=t,o=Ce(),n=!1;for(let s=0;s<64;s++){if(St(te(e,"mistflow.json")))return"mistflow";if(!n&&St(te(e,"package.json"))&&(n=!0),e===o)break;let a=ya(e);if(a===e)break;e=a}return n?"foreign":"none"}function _a(t){let e=Ce(),o=t.replace(/\/+$/,"");if(o===e||o==="/"||o===""||o==="/tmp"||o==="/private/tmp")return!0;let n=["Desktop","Documents","Downloads"];for(let s of n)if(o===te(e,s))return!0;return!1}var Ta=I.object({description:I.string().optional().describe("App description or modification request. Required for the first call; omit on follow-up polls where only conversationId is passed. "),projectPath:I.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:I.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:I.record(I.string()).optional().describe("User's answers to the clarifying questions from the previous round. Keys are the questions, values are the answers."),existingPlan:I.record(I.unknown()).optional().describe("If provided, modifies this existing plan instead of creating a new one. Pass the current plan object from mistflow.json."),existingPlanId:I.string().optional().describe("Alternative to existingPlan \u2014 pass the planId from a previous mist_plan call to modify that plan."),templateToken:I.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:I.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:I.boolean().optional().describe("Skip clarifying questions and generate the plan immediately"),landingDesign:I.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:I.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:I.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:I.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:I.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:I.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:I.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:I.object({id:I.string().optional(),name:I.string().optional(),summary:I.string().optional(),heroHeadline:I.string().optional(),ctaText:I.string().optional(),bodySample:I.string().optional(),fontsHint:I.string().optional(),fonts:I.object({display:I.string(),body:I.string()}).partial().optional(),colorMood:I.string().optional(),colors:I.object({bg:I.string(),fg:I.string(),accent:I.string()}).partial().optional(),heroTreatment:I.string().optional(),shapeLang:I.string().optional(),texture:I.string().optional(),decorationHint:I.string().optional(),custom:I.string().optional()}).passthrough().optional().describe("The creative direction the user picked from a previous 'design_clarify' response. Pass the FULL direction object the user chose (all fields from the 'directions' array). If the user wrote their own description instead of picking one, pass { custom: '<their description>' } and omit the other fields.")});function Ra(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,s]of e)if(n.test(t))return s;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 Aa(t){let e=te(Ce(),".mistflow","plans",`${t}.json`);if(!St(e))return null;try{return JSON.parse(Es(e,"utf-8")).plan??null}catch{return null}}async function Ca(t){for(let n=0;n<60;n++){try{let s=await _n(t.design_conversation_id);if(s.status==="ready")return{status:"design_clarify",design_conversation_id:t.design_conversation_id,directions:s.directions,plan:s.plan,methodology:s.methodology};if(s.status==="failed")return{status:"ready",plan:s.plan,methodology:s.methodology}}catch(s){let a=s instanceof Error?s.message:String(s);if(a.toLowerCase().includes("not found"))return{status:"ready",plan:t.plan,methodology:t.methodology};console.error(`[plan] directions poll attempt ${n+1} failed: ${a}`)}await new Promise(s=>setTimeout(s,2e3))}return console.error("[plan] directions poll exhausted, falling back to ready"),{status:"ready",plan:t.plan,methodology:t.methodology}}async function Ea(t,e){let{description:o,projectPath:n,conversationId:s,answers:a,existingPlan:r,existingPlanId:i,templateToken:l,remixDescription:p,autonomous:m,language:u,landingDesign:d,appStyle:b,brandMentioned:T,confirmToken:w,urlChoice:y,designConversationId:x,designDirection:M}=t;if(s&&!o&&!a&&!x&&!M&&!r&&!i&&!l)try{let h=await $t(s);return h.status==="clarify_pending"?c(JSON.stringify({status:"running",conversationId:s,phase:"generating_questions",nextAction:`Still generating. Call mist_plan with { projectPath, conversationId: "${s}" } again in ~10-15s.`})):h.status==="plan_pending"?c(JSON.stringify({status:"running",conversationId:s,phase:"generating_plan",nextAction:`Plan is being generated (build_plan + image enrichment, 30-60s typical). Call mist_plan with { projectPath, conversationId: "${s}" } again in ~15-20s.`})):c(JSON.stringify(h))}catch(h){let g=h instanceof Error?h.message:String(h);return c(`Could not poll plan conversation '${s}': ${g}`,!0)}let J=o??"";if(!J.trim()&&!s&&!r&&!i&&!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 H=r;if(!H&&i&&(H=Aa(i)??void 0,!H))return c("Your previous plan is no longer available. Please describe your app again to generate a new plan.",!0);let se=s;if(!G())return c("No Mistflow credentials found. Run mist_setup to connect your account.",!0);let q;if(!se&&!H&&!l){if(!ba(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 h=Ia(n);if(h!=="mistflow"&&_a(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(`
|
|
2214
|
+
`}var Qt="__mistflow_url_choice__",ka=600*1e3;function js(){let t=te(Ce(),".mistflow","confirm-secret");if(St(t))try{return Buffer.from(Es(t,"utf-8").trim(),"hex")}catch{}let e=va(32);return Xt(te(Ce(),".mistflow"),{recursive:!0}),Zt(t,e.toString("hex"),{mode:384}),e}function Ds(t){return wa("sha256").update(t.trim().toLowerCase()).digest("hex").slice(0,16)}function Sa(t,e){let o={cwd:t,d:Ds(e),exp:Date.now()+ka},n=Buffer.from(JSON.stringify(o)).toString("base64url"),s=Ns("sha256",js()).update(n).digest("base64url");return`${n}.${s}`}function Pa(t,e,o){let n=t.split(".");if(n.length!==2)return!1;let[s,a]=n,r=Ns("sha256",js()).update(s).digest("base64url"),i=Buffer.from(a),l=Buffer.from(r);if(i.length!==l.length||!xa(i,l))return!1;try{let p=JSON.parse(Buffer.from(s,"base64url").toString("utf-8"));return!(typeof p.exp!="number"||Date.now()>p.exp||p.cwd!==e||p.d!==Ds(o))}catch{return!1}}function Ia(t){let e=t,o=Ce(),n=!1;for(let s=0;s<64;s++){if(St(te(e,"mistflow.json")))return"mistflow";if(!n&&St(te(e,"package.json"))&&(n=!0),e===o)break;let a=ya(e);if(a===e)break;e=a}return n?"foreign":"none"}function _a(t){let e=Ce(),o=t.replace(/\/+$/,"");if(o===e||o==="/"||o===""||o==="/tmp"||o==="/private/tmp")return!0;let n=["Desktop","Documents","Downloads"];for(let s of n)if(o===te(e,s))return!0;return!1}var Ta=I.object({description:I.string().optional().describe("App description or modification request. Required for the first call; omit on follow-up polls where only conversationId is passed. "),projectPath:I.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:I.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:I.record(I.string()).optional().describe("User's answers to the clarifying questions from the previous round. Keys are the questions, values are the answers."),existingPlan:I.record(I.unknown()).optional().describe("If provided, modifies this existing plan instead of creating a new one. Pass the current plan object from mistflow.json."),existingPlanId:I.string().optional().describe("Alternative to existingPlan \u2014 pass the planId from a previous mist_plan call to modify that plan."),templateToken:I.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:I.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:I.boolean().optional().describe("Skip clarifying questions and generate the plan immediately"),landingDesign:I.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:I.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:I.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:I.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:I.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:I.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:I.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:I.object({id:I.string().optional(),name:I.string().optional(),summary:I.string().optional(),heroHeadline:I.string().optional(),ctaText:I.string().optional(),bodySample:I.string().optional(),fontsHint:I.string().optional(),fonts:I.object({display:I.string(),body:I.string()}).partial().optional(),colorMood:I.string().optional(),colors:I.object({bg:I.string(),fg:I.string(),accent:I.string()}).partial().optional(),heroTreatment:I.string().optional(),shapeLang:I.string().optional(),texture:I.string().optional(),decorationHint:I.string().optional(),custom:I.string().optional()}).passthrough().optional().describe("The creative direction the user picked from a previous 'design_clarify' response. Pass the FULL direction object the user chose (all fields from the 'directions' array). If the user wrote their own description instead of picking one, pass { custom: '<their description>' } and omit the other fields.")});function Ra(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,s]of e)if(n.test(t))return s;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 Aa(t){let e=te(Ce(),".mistflow","plans",`${t}.json`);if(!St(e))return null;try{return JSON.parse(Es(e,"utf-8")).plan??null}catch{return null}}async function Ca(t){for(let n=0;n<60;n++){try{let s=await _n(t.design_conversation_id);if(s.status==="ready")return{status:"design_clarify",design_conversation_id:t.design_conversation_id,directions:s.directions,plan:s.plan,methodology:s.methodology};if(s.status==="failed")return{status:"ready",plan:s.plan,methodology:s.methodology}}catch(s){let a=s instanceof Error?s.message:String(s);if(a.toLowerCase().includes("not found"))return{status:"ready",plan:t.plan,methodology:t.methodology};console.error(`[plan] directions poll attempt ${n+1} failed: ${a}`)}await new Promise(s=>setTimeout(s,2e3))}return console.error("[plan] directions poll exhausted, falling back to ready"),{status:"ready",plan:t.plan,methodology:t.methodology}}async function Ea(t,e){let{description:o,projectPath:n,conversationId:s,answers:a,existingPlan:r,existingPlanId:i,templateToken:l,remixDescription:p,autonomous:m,language:u,landingDesign:d,appStyle:b,brandMentioned:T,confirmToken:w,urlChoice:y,designConversationId:x,designDirection:M}=t;if(s&&!o&&!a&&!x&&!M&&!r&&!i&&!l)try{let h=await $t(s);return h.status==="clarify_pending"?c(JSON.stringify({status:"running",conversationId:s,phase:"generating_questions",nextAction:`Still generating. Call mist_plan with { projectPath, conversationId: "${s}" } IMMEDIATELY \u2014 do NOT run bash sleep. The server holds each poll open up to ~10s and returns as soon as questions are ready.`})):h.status==="plan_pending"?c(JSON.stringify({status:"running",conversationId:s,phase:"generating_plan",nextAction:`Plan is being generated (build_plan + image enrichment, 30-60s typical). Call mist_plan with { projectPath, conversationId: "${s}" } IMMEDIATELY \u2014 do NOT run bash sleep. The server holds each poll open up to ~10s and returns as soon as the plan is ready.`})):c(JSON.stringify(h))}catch(h){let g=h instanceof Error?h.message:String(h);return c(`Could not poll plan conversation '${s}': ${g}`,!0)}let J=o??"";if(!J.trim()&&!s&&!r&&!i&&!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 H=r;if(!H&&i&&(H=Aa(i)??void 0,!H))return c("Your previous plan is no longer available. Please describe your app again to generate a new plan.",!0);let se=s;if(!G())return c("No Mistflow credentials found. Run mist_setup to connect your account.",!0);let q;if(!se&&!H&&!l){if(!ba(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 h=Ia(n);if(h!=="mistflow"&&_a(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(`
|
|
2207
2215
|
`)}),!0);if(h==="foreign"&&!T){if(!(w?Pa(w,n,J):!1)){let S=Sa(n,J);return c(JSON.stringify({status:"confirm_new_project",projectPath:n,description:J,confirmToken:S,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.",w?"The previous confirmToken was invalid, expired, or did not match the current directory/description. Use the fresh token above.":""].filter(Boolean).join(`
|
|
2208
|
-
`)}))}q="Note: You're inside an existing project. Mistflow will create the new app in a subdirectory. It won't modify this codebase."}else h==="foreign"&&T&&(q="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 Fn(l)).plan)return c("This template has no plan to fork. Try a different template.",!0);let g=await qn(l),S=g.plan,P="";if(p&&g.has_source)try{let X=await Lt(g.plan,p),U=X.plan??X,Et=X.diff,F=U?.steps??[],un=new Set([...(Et?.added??[]).map(le=>le.number),...(Et?.modified??[]).map(le=>le.number)]),Pe=F.map(le=>{let Cr=le.number;return un.has(Cr)?{...le,status:"pending"}:{...le,status:"completed",source:"forked"}});U.steps=Pe,S=U;let Me=Pe.filter(le=>le.status==="pending").length;P=` Remixed: ${Pe.filter(le=>le.status==="completed").length} steps unchanged, ${Me} steps need re-implementation.`}catch(X){console.error("[plan] Remix failed, using original plan:",X),P=" (Remix failed \u2014 using original plan. You can modify it later.)"}let W=Cs(),L=te(Ce(),".mistflow","plans");Xt(L,{recursive:!0}),Zt(te(L,`${W}.json`),JSON.stringify({plan:S,projectId:g.id,sourceDeploymentId:g.source_deployment_id,forkToken:g.fork_token,requiredEnvVars:g.required_env_vars,dbProvider:g.db_provider}));let Y=S?.name??"forked-app",Q=g.has_source,dt=Q?"Source code will be restored during init. Run init promptly \u2014 the download token expires in 1 hour.":"",Oe=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:W,forkedFrom:g.forked_from,projectId:g.id,hasSource:Q,deployUrl:g.deploy_url,message:`Forked "${g.forked_from}" into your workspace.${P}${Oe} ${dt} NEXT: Call mist_init, name='${Y}', and planId='${W}' to create the project now.`}))}catch(h){let g=h instanceof Error?h.message:"Failed to fork template";return c(g,!0)}if(H){let h;try{h=await Lt(H,J)}catch(L){let Y=L instanceof Error?L.message:"Failed to modify plan";return c(Y,!0)}let g=h.plan,S=h.diff,P=[];if(S?.added?.length){let L=S.added.map(Y=>Y.title);P.push(`Added ${L.length} step(s): ${L.join(", ")}`)}if(S?.removed?.length){let L=S.removed.map(Y=>Y.title);P.push(`Removed ${L.length} step(s): ${L.join(", ")}`)}if(S?.modified?.length){let L=S.modified.map(Y=>Y.title);P.push(`Modified ${L.length} step(s): ${L.join(", ")}`)}let W=P.length>0?P.join(". "):"No changes detected.";return c(JSON.stringify({plan:g,diff:S,message:`Plan modified. ${W}. Update mistflow.json with the new plan, then continue with mist_implement.`}))}let v=y?.trim()||void 0,$=a;if(!v&&a&&Qt in a&&(v=a[Qt]),a&&Qt in a){let{[Qt]:h,...g}=a;$=Object.keys(g).length>0?g:void 0}if(v&&(v=v.replace(/^Keep\s+/i,"").replace(/\s*\(Recommended\)\s*$/i,"").replace(/\.mistflow\.app.*$/i,"").trim()||void 0),v){let h=v.toLowerCase().replace(/\s+/g,"-");/^[a-z0-9][a-z0-9-]{1,30}[a-z0-9]$/.test(h)?v=h:(console.error(`[mist_plan] Discarding urlChoice '${v}' \u2014 does not look like a subdomain. Backend will auto-generate.`),v=void 0)}let K;if(M){K={...M};let h={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,S]of Object.entries(h))M[g]!==void 0&&K[S]===void 0&&(K[S]=M[g])}let ne=a?"Generating plan with your answers (LLM call)":x?"Finalizing design direction":"Thinking through discovery questions",he=e?Yt(e.server,e.progressToken,()=>ne):{stop:()=>{}};e&&(e.cleanup=()=>he.stop());let C;try{se&&!$&&!x&&!H&&!i?C=await $t(se):C=await Tn(J,{conversationId:se,answers:$,autonomous:m,language:u,designConversationId:x,designDirection:K})}catch(h){he.stop();let g=h instanceof Error?h.message:"Failed to generate plan";return c(g,!0)}if(C.status==="clarify_pending"){he.stop();let h=C;return c(JSON.stringify({status:"running",conversationId:h.conversation_id,phase:"generating_questions",nextAction:`Discovery questions are generating
|
|
2216
|
+
`)}))}q="Note: You're inside an existing project. Mistflow will create the new app in a subdirectory. It won't modify this codebase."}else h==="foreign"&&T&&(q="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 Fn(l)).plan)return c("This template has no plan to fork. Try a different template.",!0);let g=await qn(l),S=g.plan,P="";if(p&&g.has_source)try{let X=await Lt(g.plan,p),U=X.plan??X,Et=X.diff,F=U?.steps??[],un=new Set([...(Et?.added??[]).map(le=>le.number),...(Et?.modified??[]).map(le=>le.number)]),Pe=F.map(le=>{let Cr=le.number;return un.has(Cr)?{...le,status:"pending"}:{...le,status:"completed",source:"forked"}});U.steps=Pe,S=U;let Me=Pe.filter(le=>le.status==="pending").length;P=` Remixed: ${Pe.filter(le=>le.status==="completed").length} steps unchanged, ${Me} steps need re-implementation.`}catch(X){console.error("[plan] Remix failed, using original plan:",X),P=" (Remix failed \u2014 using original plan. You can modify it later.)"}let W=Cs(),L=te(Ce(),".mistflow","plans");Xt(L,{recursive:!0}),Zt(te(L,`${W}.json`),JSON.stringify({plan:S,projectId:g.id,sourceDeploymentId:g.source_deployment_id,forkToken:g.fork_token,requiredEnvVars:g.required_env_vars,dbProvider:g.db_provider}));let Y=S?.name??"forked-app",Q=g.has_source,dt=Q?"Source code will be restored during init. Run init promptly \u2014 the download token expires in 1 hour.":"",Oe=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:W,forkedFrom:g.forked_from,projectId:g.id,hasSource:Q,deployUrl:g.deploy_url,message:`Forked "${g.forked_from}" into your workspace.${P}${Oe} ${dt} NEXT: Call mist_init, name='${Y}', and planId='${W}' to create the project now.`}))}catch(h){let g=h instanceof Error?h.message:"Failed to fork template";return c(g,!0)}if(H){let h;try{h=await Lt(H,J)}catch(L){let Y=L instanceof Error?L.message:"Failed to modify plan";return c(Y,!0)}let g=h.plan,S=h.diff,P=[];if(S?.added?.length){let L=S.added.map(Y=>Y.title);P.push(`Added ${L.length} step(s): ${L.join(", ")}`)}if(S?.removed?.length){let L=S.removed.map(Y=>Y.title);P.push(`Removed ${L.length} step(s): ${L.join(", ")}`)}if(S?.modified?.length){let L=S.modified.map(Y=>Y.title);P.push(`Modified ${L.length} step(s): ${L.join(", ")}`)}let W=P.length>0?P.join(". "):"No changes detected.";return c(JSON.stringify({plan:g,diff:S,message:`Plan modified. ${W}. Update mistflow.json with the new plan, then continue with mist_implement.`}))}let v=y?.trim()||void 0,$=a;if(!v&&a&&Qt in a&&(v=a[Qt]),a&&Qt in a){let{[Qt]:h,...g}=a;$=Object.keys(g).length>0?g:void 0}if(v&&(v=v.replace(/^Keep\s+/i,"").replace(/\s*\(Recommended\)\s*$/i,"").replace(/\.mistflow\.app.*$/i,"").trim()||void 0),v){let h=v.toLowerCase().replace(/\s+/g,"-");/^[a-z0-9][a-z0-9-]{1,30}[a-z0-9]$/.test(h)?v=h:(console.error(`[mist_plan] Discarding urlChoice '${v}' \u2014 does not look like a subdomain. Backend will auto-generate.`),v=void 0)}let K;if(M){K={...M};let h={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,S]of Object.entries(h))M[g]!==void 0&&K[S]===void 0&&(K[S]=M[g])}let ne=a?"Generating plan with your answers (LLM call)":x?"Finalizing design direction":"Thinking through discovery questions",he=e?Yt(e.server,e.progressToken,()=>ne):{stop:()=>{}};e&&(e.cleanup=()=>he.stop());let C;try{se&&!$&&!x&&!H&&!i?C=await $t(se):C=await Tn(J,{conversationId:se,answers:$,autonomous:m,language:u,designConversationId:x,designDirection:K})}catch(h){he.stop();let g=h instanceof Error?h.message:"Failed to generate plan";return c(g,!0)}if(C.status==="clarify_pending"){he.stop();let h=C;return c(JSON.stringify({status:"running",conversationId:h.conversation_id,phase:"generating_questions",nextAction:`Discovery questions are generating. Call mist_plan with { projectPath, conversationId: "${h.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(C.status==="plan_pending"){he.stop();let h=C;return c(JSON.stringify({status:"running",conversationId:h.conversation_id,phase:"generating_plan",nextAction:`Plan is being generated (build_plan + image enrichment, 30-60s typical). Call mist_plan with { projectPath, conversationId: "${h.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(C.status==="design_clarify_pending"&&(ne="Generating creative design directions",C=await Ca(C)),he.stop(),C.status==="clarify"){let h=C.reflection||"",g=C.suggestedName||"",S=C.suggestedFeatures??[],P=C.questions??[],W=P.some(F=>Array.isArray(F.options)&&typeof F.options[0]=="object"&&F.options[0]?.label),L={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"},Y=P.map(F=>{let un=F.decisionKey&&L[F.decisionKey]||Ra(F.question),Pe;return W&&Array.isArray(F.options)?Pe=F.options.map(Me=>({label:Me.label,description:Me.description??""})):Array.isArray(F.options)?Pe=F.options.map((Me,xo)=>({label:xo===0?`${Me} (Recommended)`:String(Me),description:F.why??""})):Pe=[{label:"Yes (Recommended)",description:F.why??""},{label:"No",description:""}],{question:F.question,header:un,options:Pe,multiSelect:!1}}),dt=C.decisions?.audienceType??null,Oe=S.length>0?_s(J,{primaryActor:null,primaryAction:null,surfaceType:null,audienceType:dt,multiRole:null,publicLanding:null,realMoney:null,scheduling:null,authModel:null,dbProvider:null,integrations:null},{suggestedName:g,suggestedFeatures:S,language:u}):null,X=Oe?Ts(Oe):"",U=eo(g||"my-app").slice(0,32);try{let F=await Pn(U);!F.available&&F.suggestion&&(U=F.suggestion)}catch{}X&&(X+=`
|
|
2209
2217
|
|
|
2210
2218
|
**Your app URL:** https://${U}.mistflow.app`);let Et={question:`Your app will be at ${U}.mistflow.app \u2014 want to customize the URL?`,header:"URL",options:[{label:`Keep ${U}.mistflow.app (Recommended)`,description:"This URL is available"},{label:"Choose a different URL",description:"Type your preferred subdomain"}],multiSelect:!1};return Y.push(Et),c(JSON.stringify({status:"clarify",conversation_id:C.conversation_id,questions:P,questionCount:P.length,suggestedFeatures:S,suggestedName:g,suggestedSubdomain:U,reflection:h,briefText:X,askUserQuestions:Y,planTimingHint:"After the user answers all questions, generating the actual plan takes about 60-90 seconds (backend LLM). Narrate this explicitly before the next mist_plan call.",instruction:[...q?[q,""]:[],h?`${h}
|
|
2211
2219
|
`:"",X?`Here's what I'd build:
|
|
2212
2220
|
|
|
2213
2221
|
${X}
|
|
2214
|
-
`:"",`I have ${P.length} quick question${P.length===1?"":"s"} to pin down the details.`,"","
|
|
2222
|
+
`:"",`I have ${P.length} quick question${P.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: "${C.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: "${U}".`,'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(`
|
|
2215
2223
|
`)}))}if(C.status==="design_clarify"){let h=C.directions??[],g=C.plan.name??"your app",S;try{let Q=h.map(U=>({id:U.id,name:U.name,summary:U.summary,hero_headline:U.hero_headline,cta_text:U.cta_text,body_sample:U.body_sample,fonts:U.fonts,colors:U.colors,hero_treatment:U.hero_treatment,shape_lang:U.shape_lang,texture:U.texture,decoration_hint:U.decoration_hint})),dt=As(g,Q),Oe=te(n,".mistflow");Xt(Oe,{recursive:!0});let X=te(Oe,"design-directions.html");Zt(X,dt,"utf-8"),S=X}catch(Q){console.error(`[mist_plan] design-directions preview render failed: ${Q instanceof Error?Q.message:String(Q)}`)}let P=h.map(Q=>({label:Q.name,description:`${Q.summary} \u2014 ${Q.fonts?.display??""} + ${Q.fonts?.body??""}`}));P.push({label:"Describe your own direction",description:"Skip the proposed options and give me a short description of how the app should feel."});let W={question:`${g} is planned. Now pick the creative direction \u2014 this shapes fonts, colors, and overall feel.`,header:"Design",options:P,multiSelect:!1},L=S?`A visual preview of all ${h.length} directions has been written to ${S}. 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.",Y=S?`open "${S}"`:"";return c(JSON.stringify({status:"design_clarify",designConversationId:C.design_conversation_id,directions:h,previewPath:S,askUserQuestion:W,instruction:[`The plan for "${g}" is ready. I've proposed ${h.length} creative directions \u2014 each commits to a specific aesthetic (fonts, colors, voice).`,"",L,Y?`Run this command for the user: ${Y}`:"","","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: "${C.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(`
|
|
2216
2224
|
`)}))}let E=C.plan,ge=E.name??"Untitled App",Se=C.methodology,j=E.steps;if(!Array.isArray(j)||j.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 oe=E.publicPages;if(!oe||Array.isArray(oe)&&oe.length===0){let h=E.pages,g=j.some(P=>typeof P.name=="string"&&P.name.toLowerCase().includes("landing")||typeof P.title=="string"&&P.title.toLowerCase().includes("landing")),S=Array.isArray(h)&&h.some(P=>P.path==="/"||P.route==="/");g||S?oe=["/","/pricing"]:oe=["/"]}let lt=E.primaryAction;if(!lt){let h=E.features;if(Array.isArray(h)&&h.length>0){let S=h.find(W=>typeof W.priority=="string"&&W.priority.toLowerCase()==="must-have")??h[0];lt={entity:S.name??S.title??"item",action:"create",fromPage:"/dashboard"}}}let Ne=E.nonNegotiables;(!Ne||Array.isArray(Ne)&&Ne.length===0)&&(Ne=["Landing page renders correctly at / with content (not a redirect)","Core user action works end-to-end (create entity, see it in list)"]);let B=Cs(),Be=te(Ce(),".mistflow","plans");Xt(Be,{recursive:!0});try{let g=Date.now();for(let S of[Be,te(Ce(),".mistflow","mockup-state")])if(St(S))for(let P of ha(S))try{let W=te(S,P),L=ga(W).mtimeMs;g-L>6048e5&&fa(W)}catch{}}catch{}let re;if(d){let h=zt(d);h?re=h.id:console.error(`Landing design '${d}' not found \u2014 ignoring. Use mist_project action='landing-designs' to browse available landing designs.`)}let je=b||void 0,ae=!!re,ct=j.some(h=>{let g=`${h.name??h.title} ${h.description??""}`.toLowerCase();return g.includes("landing")||g.includes("hero")||g.includes("marketing")||g.includes("homepage")}),fe=!ae&&ct?cs(J,{maxResults:2}):[];fe.length>0&&(re=fe[0].id,console.error(`Auto-assigned landing layout preset (default): ${fe[0].title} (${re})`));let pn={name:E.name,summary:E.summary,dataModel:E.dataModel,pages:E.pages,features:E.features,steps:j.map(h=>({...h,name:h.name??h.title})),design:E.design,landingDesign:re,appStyle:je,dbProvider:E.dbProvider??"neon",authModel:E.authModel,audienceType:E.audienceType??"b2c",roles:E.roles,defaultRole:E.defaultRole,publicPages:oe,navStyle:E.navStyle,multiTenant:E.multiTenant,primaryAction:lt,nonNegotiables:Ne,requestedSubdomain:v,...u&&u.toLowerCase()!=="english"?{language:u}:{}};Zt(te(Be,`${B}.json`),JSON.stringify({plan:pn,methodology:Se}));let dn=j.map(h=>`${h.number}. ${h.name??h.title}`),ve="",pt=[],De;!ae&&fe.length>0&&(pt=fe.map(g=>({id:g.id,slug:g.slug,title:g.title,description:g.description,url:`${Ge()}/designs?tab=landing-designs`})),De={question:"What landing page style fits this app?",header:"Landing Design",options:[...fe.map((g,S)=>({label:S===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 ${Ge()}/designs?tab=landing-designs and pass the ID back.`}],multiSelect:!1},ve=` 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: ${fe.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='${fe[0].id}' explicitly.`);let Ct="",f=[],k=void 0,z=(E.audienceType??"b2c")==="b2c",ye={question:"Include a lifestyle photo in your landing page hero? Photos add warmth and human context; pure CSS stays cleaner.",header:"Hero",options:[{label:z?"Yes, add a photo (Recommended)":"Yes, add a photo",description:"Lifestyle photography background with your product preview overlaid \u2014 feels warm and human (like HabitFlow, Airbnb)."},{label:z?"No, CSS only":"No, CSS only (Recommended)",description:"Animated gradients + glassmorphism, no photo \u2014 cleaner and more technical (like Stripe, Linear, Vercel)."}],multiSelect:!1},be=" 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.",_="",O=[];for(let h of j){let g=h.name??h.title,S=h.integrationId;if(S){let P=Qe(S);if(P){let W=Xe(P.id);O.push({step:g,presetId:P.id,presetName:P.name,envVars:W?.envVars??[]})}}}if(O.length>0){let h=O.flatMap(P=>P.envVars),g=[...new Set(h.map(P=>P.key))];_=` This plan uses integrations (${O.map(P=>P.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:B,name:E.name,summary:E.summary,stepCount:j.length,steps:dn,design:E.design,...re?{landingDesign:re}:{},...je?{appStyle:je}:{},...f.length>0?{recommendedAppStyles:f}:{},...k?{appStyleQuestion:k}:{},...pt.length>0?{recommendedLandingDesigns:pt}:{},...De?{landingDesignQuestion:De}:{},heroPhotoQuestion:ye,...O.length>0?{integrations:O.map(h=>({step:h.step,preset:h.presetId,name:h.presetName,envVars:h.envVars}))}:{},message:`Plan generated for "${ge}" (${j.length} steps).${re?` Landing layout "${re}" set as default.`:""}${je?` App style "${je}" will be applied across all pages.`:""}${_}${Ct}${ve}${be}`,timingContext:`Planning took ~90 seconds. Building will take roughly ${Math.max(15,j.length*3)}\u2013${j.length*5} minutes total across ${j.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: '${B}' }). If the user says skip or "just build it", call mist_init({ planId: '${B}', path: '<absolute path>' }) immediately.`,...q?{warning:q}:{}}))}var Os={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(`
|
|
2217
2225
|
`),inputSchema:Ta,handler:Ea};import{z as Pt}from"zod";import{existsSync as Fe,mkdirSync as ao,writeFileSync as lo,readFileSync as tn,readdirSync as qs,copyFileSync as Da}from"fs";import{join as ue,resolve as Bs,dirname as It,isAbsolute as Oa}from"path";import{homedir as Ma}from"os";import{spawn as Pd}from"child_process";import{randomBytes as Ua}from"crypto";import{simpleGit as La}from"simple-git";ie();$e();function Ms(t){if(!t)return"Item";let e=t.replace(/([a-z0-9])([A-Z])/g,"$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean);return e.length===0?"Item":e.map(o=>o.charAt(0).toUpperCase()+o.slice(1).toLowerCase()).join("")}function Na(t){return t&&t.replace(/([a-z0-9])([A-Z])/g,"$1-$2").replace(/[^A-Za-z0-9]+/g,"-").toLowerCase().replace(/^-+|-+$/g,"")||"item"}function ja(t){let e=Ms(t);return e.charAt(0).toLowerCase()+e.slice(1)}function oo(){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(`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mistflow-ai/mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Mistflow MCP server for AI coding editors. Installed into Claude Code, Cursor, Codex CLI, and VS Code Copilot by the mistflow-ai installer.",
|
|
5
5
|
"license": "Elastic-2.0",
|
|
6
6
|
"type": "module",
|