generatesaas 1.19.0 → 1.19.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +125 -115
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import{Command as Zs}from"commander";import*as ro from"@clack/prompts";import{existsSync as hs,readdirSync as ys,rmSync as vs}from"fs";import{join as Ss,resolve as Es}from"path";import{Option as Y}from"commander";import*as k from"@clack/prompts";import*as at from"@clack/prompts";import Dt from"picocolors";function ar(e){let t=e?` GenerateSaaS v${e} `:" GenerateSaaS ";at.intro(Dt.bgYellow(Dt.black(t)))}function cr(){at.outro(Dt.yellow("Happy building!"))}import*as g from"@clack/prompts";import h from"picocolors";var qe={nextjs:{label:"Next.js",hint:"React 19 + Next.js 16"},nuxt:{label:"Nuxt",hint:"Vue 3 + Nuxt 4"}},Je={fullstack:{label:"Fullstack",hint:"Frontend hosts the API - works on serverless or long-running"},separate:{label:"Separate",hint:"Standalone Hono backend - long-running runtimes only (Docker, Render, Fly.io, Railway, Coolify, Dokploy)"}},ct={stripe:{label:"Stripe"},polar:{label:"Polar"},none:{label:"None",hint:"disable payments"}},lt={smtp:{label:"SMTP",hint:"Mailpit for local dev"},ses:{label:"Amazon SES"},resend:{label:"Resend"}},pt={user:{label:"Per user",hint:"each user has their own subscription"},organization:{label:"Per organization",hint:"org subscription shared by members"}},Te={postgres:{label:"PostgreSQL",hint:"port 5432",port:5432},redis:{label:"Redis",hint:"port 6379",port:6379},inngest:{label:"Inngest",hint:"port 8288",port:8288},mailpit:{label:"Mailpit",hint:"port 1025",port:1025}},We={"claude-code":{label:"Claude Code"},cursor:{label:"Cursor"},codex:{label:"Codex"},"gemini-cli":{label:"Gemini CLI"},windsurf:{label:"Windsurf"}};var he={USD:{symbol:"$",name:"US Dollar",place:"left",space:!1},EUR:{symbol:"\u20AC",name:"Euro",place:"right",space:!1},GBP:{symbol:"\xA3",name:"British Pound",place:"left",space:!1},CAD:{symbol:"CA$",name:"Canadian Dollar",place:"left",space:!1},AUD:{symbol:"A$",name:"Australian Dollar",place:"left",space:!1},BRL:{symbol:"R$",name:"Brazilian Real",place:"left",space:!1},JPY:{symbol:"\xA5",name:"Japanese Yen",place:"left",space:!1}};var Q={node:{label:"Node.js / Docker",hint:"long-running runtime - Render, Fly.io, Railway, Coolify, Dokploy, VPS",edgeRuntime:!1},vercel:{label:"Vercel",hint:"serverless functions",edgeRuntime:!0}},V={postgres:{label:"PostgreSQL (self-hosted)",hint:"local Docker, drizzle-orm/node-postgres",managed:!1,envVars:[{key:"DATABASE_URL",defaultValue:"postgres://postgres:postgres@localhost:5432/saas"}]},neon:{label:"Neon",hint:"serverless Postgres",managed:!0,envVars:[{key:"DATABASE_URL",comment:"# TODO: Add your Neon connection string"}]},supabase:{label:"Supabase",hint:"managed Postgres",managed:!0,envVars:[{key:"DATABASE_URL",comment:"# TODO: Add your Supabase connection string"}]}},ee={redis:{label:"Redis (self-hosted)",hint:"local Docker, ioredis",managed:!1,envVars:[{key:"REDIS_URL",defaultValue:"redis://localhost:6379"}]},upstash:{label:"Upstash",hint:"serverless Redis",managed:!0,envVars:[{key:"UPSTASH_REDIS_REST_URL",comment:"# TODO: Add your Upstash REST URL"},{key:"UPSTASH_REDIS_REST_TOKEN",comment:"# TODO: Add your Upstash REST token"}]}},Xe=[{target:"vercel",provider:"redis",reason:"Vercel serverless cannot maintain persistent Redis connections. Consider Upstash."}],Ze=[{architecture:"separate",target:"vercel",reason:"Standalone backends need a long-running runtime. Vercel's per-function TypeScript check conflicts with pnpm's isolated install. Use --architecture fullstack for serverless, or deploy the standalone backend to a long-running runtime (Render, Fly.io, Railway, Coolify, Dokploy, or your own VPS via Docker)."}],lr=["Local file storage (sharp, geoip-lite)","SMTP email (use Resend or SES instead)","Content API git integration"];function dt(e){let t=V[e.databaseProvider].managed,r=ee[e.cacheProvider].managed;return e.dockerServices.some(n=>!(n==="postgres"&&t||n==="redis"&&r))}var ne={google:{label:"Google",envVars:[{name:"GOOGLE_CLIENT_ID",secret:!1},{name:"GOOGLE_CLIENT_SECRET",secret:!0}]},github:{label:"GitHub",envVars:[{name:"GITHUB_CLIENT_ID",secret:!1},{name:"GITHUB_CLIENT_SECRET",secret:!0}]},facebook:{label:"Facebook",envVars:[{name:"FACEBOOK_CLIENT_ID",secret:!1},{name:"FACEBOOK_CLIENT_SECRET",secret:!0}]},discord:{label:"Discord",envVars:[{name:"DISCORD_CLIENT_ID",secret:!1},{name:"DISCORD_CLIENT_SECRET",secret:!0}]},x:{label:"X",envVars:[{name:"TWITTER_CLIENT_ID",secret:!1},{name:"TWITTER_CLIENT_SECRET",secret:!0}]}};var ye=["nextjs","nuxt"],Re=["fullstack","separate"],_e=["stripe","polar","none"],Oe=["smtp","ses","resend"],xe=["postgres","redis","inngest","mailpit"],De=["claude-code","cursor","codex","gemini-cli","windsurf"],Ce=["user","organization"],oe=["USD","EUR","GBP","CAD","AUD","BRL","JPY"],ie=["node","vercel"],se=["postgres","neon","supabase"],ae=["redis","upstash"],Ne=["google","github","facebook","discord","x"];function ut(e){return e.split("-").map(t=>t.charAt(0).toUpperCase()+t.slice(1)).join(" ")}function mt(e){return/^[a-z][a-z0-9-]*$/.test(e)}function O(e){return e instanceof Error?e.message:String(e)}function I(e){g.isCancel(e)&&(g.cancel("Setup cancelled."),process.exit(0))}function Ct(e){let t=[];e.databaseProvider==="neon"&&t.push({key:"DATABASE_URL",message:"Neon connection string (optional):",placeholder:"postgres://...",secret:!0}),e.databaseProvider==="supabase"&&t.push({key:"DATABASE_URL",message:"Supabase connection string (optional):",placeholder:"postgres://...",secret:!0}),e.cacheProvider==="upstash"&&(t.push({key:"UPSTASH_REDIS_REST_URL",message:"Upstash REST URL (optional):",placeholder:"https://...",secret:!1}),t.push({key:"UPSTASH_REDIS_REST_TOKEN",message:"Upstash REST token (optional):",secret:!0})),e.paymentProvider==="stripe"&&(t.push({key:"STRIPE_SECRET_KEY",message:"Stripe secret key (optional):",placeholder:"sk_test_...",secret:!0}),t.push({key:"STRIPE_WEBHOOK_SECRET",message:"Stripe webhook secret (optional):",placeholder:"whsec_...",secret:!0})),e.paymentProvider==="polar"&&(t.push({key:"POLAR_ACCESS_TOKEN",message:"Polar access token (optional):",secret:!0}),t.push({key:"POLAR_WEBHOOK_SECRET",message:"Polar webhook secret (optional):",secret:!0})),e.emailProvider==="resend"&&t.push({key:"RESEND_API_KEY",message:"Resend API key (optional):",placeholder:"re_...",secret:!0}),e.emailProvider==="ses"&&(t.push({key:"AMAZON_SES_REGION",message:"Amazon SES region (optional):",placeholder:"us-east-1",secret:!1}),t.push({key:"AMAZON_SES_KEY",message:"Amazon SES access key (optional):",secret:!0}),t.push({key:"AMAZON_SES_SECRET",message:"Amazon SES secret (optional):",secret:!0}));for(let r of e.socialProviders){let n=ne[r];for(let o of n.envVars)t.push({key:o.name,message:`${o.name} (${n.label}, optional):`,secret:o.secret})}return e.demo&&t.push({key:"TURNSTILE_SECRET_KEY",message:"Cloudflare Turnstile secret key (optional):",secret:!0}),t}async function pr(e,t){let r=!1;g.log.info(h.bold("Project"));let n=e?.projectName??await(async()=>{r=!0;let c=await g.text({message:"Project name:",placeholder:"my-saas",validate:p=>{if(!p?.trim())return"Project name is required.";if(!mt(p))return"Use lowercase letters, numbers, and hyphens only. Must start with a letter."}});return I(c),c})(),o=e?.appName??await(async()=>{r=!0;let c=await g.text({message:"App name:",initialValue:ut(n),validate:p=>{if(!p?.trim())return"App name is required."}});return I(c),c})(),i=e?.projectDir??await(async()=>{r=!0;let c=await g.text({message:"Project location:",initialValue:`./${n}`});return I(c),c==="."?process.cwd():c})(),s=e?.frontend??await(async()=>{r=!0;let c=Object.keys(qe),p=await g.select({message:"Frontend framework:",options:c.map(E=>({value:E,label:qe[E].label,hint:qe[E].hint}))});return I(p),p})();g.log.info(h.bold("Infrastructure"));let a=e?.deploymentTarget??"node";if(e?.deploymentTarget===void 0){r=!0;let c=await g.select({message:"Deployment target:",options:ie.map(p=>({value:p,label:Q[p].label,hint:Q[p].hint}))});I(c),a=c}let d=e?.architecture?Ze.find(c=>c.architecture===e.architecture&&c.target===a):void 0;if(d)throw new Error(`Incompatible: --architecture ${d.architecture} + --deploy ${d.target}. ${d.reason}`);let u=new Set(Ze.filter(c=>c.target===a).map(c=>c.architecture)),m=Re.filter(c=>!u.has(c)),f=e?.architecture??await(async()=>{if(m.length===1){let p=m[0];return g.log.info(`Auto-selected ${Je[p].label} architecture (only compatible option for ${Q[a].label}).`),p}r=!0;let c=await g.select({message:"Architecture:",options:m.map(p=>({value:p,label:Je[p].label,hint:Je[p].hint}))});return I(c),c})(),v=e?.databaseProvider??await(async()=>{r=!0;let c=se.filter(E=>!Xe.some(U=>U.target===a&&U.provider===E));if(c.length===1){let E=c[0];return g.log.info(`Auto-selected ${V[E].label} (only compatible option for ${Q[a].label}).`),E}let p=await g.select({message:"Database provider:",options:c.map(E=>({value:E,label:V[E].label,hint:V[E].hint}))});return I(p),p})(),b=e?.cacheProvider??await(async()=>{r=!0;let c=ae.filter(E=>!Xe.some(U=>U.target===a&&U.provider===E));if(c.length===1){let E=c[0];return g.log.info(`Auto-selected ${ee[E].label} (only compatible option for ${Q[a].label}).`),E}let p=await g.select({message:"Cache provider:",options:c.map(E=>({value:E,label:ee[E].label,hint:ee[E].hint}))});return I(p),p})();if(Q[a]?.edgeRuntime){let c=lr.map(p=>` - ${p}`).join(`
3
- `);g.note(c,"Unavailable on edge runtime")}g.log.info(h.bold("Features"));let P=e?.paymentProvider??await(async()=>{r=!0;let c=await g.select({message:"Payment provider:",options:_e.map(p=>({value:p,label:ct[p].label,hint:ct[p].hint}))});return I(c),c})(),B=e?.defaultCurrency??await(async()=>{if(P==="none")return"USD";r=!0;let c=await g.select({message:"Default currency:",options:oe.map(p=>({value:p,label:p,hint:he[p].name}))});return I(c),c})(),C=e?.emailProvider??await(async()=>{r=!0;let c=await g.select({message:"Email provider:",options:Oe.map(p=>({value:p,label:lt[p].label,hint:lt[p].hint}))});return I(c),c})(),W=e?.multiTenancy??await(async()=>{r=!0;let c=await g.confirm({message:"Enable multi-tenancy (organizations)?",initialValue:!1});return I(c),c})(),X=e?.billingScope??"user";if(W&&e?.billingScope===void 0){r=!0;let c=await g.select({message:"Billing scope:",options:Ce.map(p=>({value:p,label:pt[p].label,hint:pt[p].hint}))});I(c),X=c}let Pe=e?.blog??await(async()=>{r=!0;let c=await g.confirm({message:"Enable blog?",initialValue:!0});return I(c),c})(),S=e?.docs??await(async()=>{r=!0;let c=await g.confirm({message:"Include docs app? (self-hosted Fumadocs documentation site)",initialValue:!1});return I(c),c})(),T=t?.desktopAllowed!==!1,N=e?.desktop??(T?await(async()=>{r=!0;let c=await g.confirm({message:"Include the Electron desktop app? (apps/desktop - cross-platform, device-auth)",initialValue:!1});return I(c),c})():(g.log.info(h.dim("Desktop app: available on the Pro plan and up - skipped.")),!1)),G=N?e?.desktopAutoRelease??await(async()=>{r=!0;let c=await g.select({message:"Desktop release trigger:",options:[{value:!1,label:"Manual only",hint:"run from the Actions tab (saves CI minutes)"},{value:!0,label:"Automatic",hint:"release on every CI success on main"}],initialValue:!1});return I(c),c})():!1,A=e?.revenueSharing??await(async()=>{r=!0;let c=await g.confirm({message:"Enable revenue sharing? (opt-in MRR leaderboard with dofollow backlinks)",initialValue:!1});return I(c),c})(),Z=P==="none"?!1:e?.credits??await(async()=>{r=!0;let c=await g.confirm({message:"Enable credits? (metered usage on top of subscription plans)",initialValue:!0});return I(c),c})(),it=e?.socialProviders??await(async()=>{r=!0;let c=Ne.map(E=>({value:E,label:ne[E].label,hint:`requires ${ne[E].envVars.map(U=>U.name).join(" / ")}`})),p=await g.multiselect({message:"Which social login providers should the sign-in screen show?",options:c,initialValues:[],required:!1});return I(p),p})();g.log.info(h.bold("Tooling"));let _t=e?.dockerServices??await(async()=>{r=!0;let c=[...xe].filter(M=>M!=="mailpit");C==="smtp"&&c.push("mailpit");let p=c.map(M=>({value:M,label:Te[M].label,hint:Te[M].hint})),E=p.map(M=>M.value).filter(M=>!(M==="postgres"&&(v==="neon"||v==="supabase")||M==="redis"&&b==="upstash")),U=await g.multiselect({message:"Which services should we set up in Docker for you?",options:p,initialValues:E,required:!1});return I(U),U})(),Ot=e?.aiTools??await(async()=>{r=!0;let c=De.map(E=>({value:E,label:We[E].label})),p=await g.multiselect({message:"Which AI coding tools do you use?",options:c,initialValues:[],required:!1});return I(p),p})(),xt=e?.demo,st=Ct({databaseProvider:v,cacheProvider:b,paymentProvider:P,emailProvider:C,socialProviders:it,demo:xt}),Ye={};if(st.length>0&&r){g.log.info(h.bold("Credentials")+h.dim(" all optional - press Enter to skip, fill in .env later"));for(let c of st)if(r=!0,c.secret){let p=await g.password({message:c.message,mask:"*"});I(p),typeof p=="string"&&p.trim()&&(Ye[c.key]=p.trim())}else{let p=await g.text({message:c.message,placeholder:c.placeholder});I(p),typeof p=="string"&&p.trim()&&(Ye[c.key]=p.trim())}}if(r){let c=[` Name: ${h.cyan(n)}`,` App name: ${h.cyan(o)}`,` Location: ${h.cyan(i)}`,` Frontend: ${h.cyan(qe[s].label)}`,` Architecture: ${h.cyan(Je[f].label)}`].join(`
4
- `),p=[` Deploy target: ${h.cyan(Q[a]?.label??"Node.js / Docker")}`,` Database: ${h.cyan(V[v].label)}`,` Cache: ${h.cyan(ee[b].label)}`,_t.length>0?` Docker: ${h.cyan(_t.map(ge=>Te[ge].label).join(", "))}`:` Docker: ${h.dim("none")}`].filter(Boolean).join(`
5
- `),E=[P!=="none"?` Payment: ${h.cyan(ct[P].label)} (${B})`:` Payment: ${h.dim("none")}`,` Credits: ${Z?h.cyan("Yes"):h.dim("No")}`,` Email: ${h.cyan(lt[C].label)}`,` Multi-tenancy: ${W?h.cyan("Yes")+` (billing: ${pt[X].label})`:h.dim("No")}`,` Blog: ${Pe?h.cyan("Yes"):h.dim("No")}`,` Docs app: ${S?h.cyan("Yes"):h.dim("No")}`,` Desktop app: ${N?h.cyan("Yes"):h.dim("No")}`,...N?[` Releases: ${h.cyan(G?"Automatic on CI":"Manual only")}`]:[],` Rev. sharing: ${A?h.cyan("Yes"):h.dim("No")}`,it.length>0?` Social login: ${h.cyan(it.map(ge=>ne[ge].label).join(", "))}`:` Social login: ${h.dim("none")}`,Ot.length>0?` AI tools: ${h.cyan(Ot.map(ge=>We[ge].label).join(", "))}`:` AI tools: ${h.dim("none")}`].join(`
6
- `),U=[h.bold("Project"),c,"",h.bold("Infrastructure"),p,"",h.bold("Features"),E];if(st.length>0){let ge=st.map(sr=>{let no=Ye[sr.key]?h.green("provided"):h.dim("skipped");return` ${sr.key}: ${no}`}).join(`
7
- `);U.push("",h.bold("Credentials"),ge)}g.note(U.join(`
8
- `),"Summary");let M=await g.confirm({message:"Proceed with these settings?"});(g.isCancel(M)||!M)&&(g.cancel("Setup cancelled."),process.exit(0))}return{projectName:n,appName:o,projectDir:i,frontend:s,architecture:f,deploymentTarget:a,databaseProvider:v,cacheProvider:b,paymentProvider:P,emailProvider:C,multiTenancy:W,billingScope:X,blog:Pe,docs:S,desktop:N,desktopAutoRelease:G,desktopAi:N?e?.desktopAi??!1:!1,revenueSharing:A,credits:Z,dockerServices:_t,aiTools:Ot,socialProviders:it,defaultCurrency:B,...Object.keys(Ye).length>0?{credentials:Ye}:{},...e?.baseUrl!==void 0?{baseUrl:e.baseUrl}:{},...xt!==void 0?{demo:xt}:{}}}import{createReadStream as mo}from"fs";import{mkdir as fo}from"fs/promises";import{Readable as go}from"stream";import{pipeline as vr}from"stream/promises";import{extract as ho}from"tar";import{join as ve}from"path";import{homedir as oo}from"os";var Qe=process.env.GENERATESAAS_API_URL??"https://cli.generatesaas.com",K=".generatesaas",pe=ve(K,"manifest.json"),dr=ve(K,"hashes.json"),ft=ve(K,"template-hashes.json"),gt=ve(K,"template"),ur=ve(K,"staging"),mr=ve(K,"staging.json"),ce=ve(oo(),".generatesaas");var L=class extends Error{constructor(r,n,o){super(n);this.status=r;this.body=o}status;body;name="ApiError"};function le(e){return{apiKey:e,baseUrl:Qe}}async function de(e,t,r){let n=`${e.baseUrl}${t}`,o=await fetch(n,{...r,headers:{...r?.headers,Authorization:`Bearer ${e.apiKey}`,"User-Agent":"generatesaas-cli"}});if(!o.ok){let i,s;try{s=await o.json(),i=s.error??`API ${o.status}: ${t}`}catch{i=`API ${o.status}: ${t}`}throw new L(o.status,i,s)}return o}import{existsSync as io,readFileSync as so,writeFileSync as ao,mkdirSync as co}from"fs";import{dirname as lo}from"path";import*as ue from"@clack/prompts";function et(){if(!io(ce))return null;try{let e=JSON.parse(so(ce,"utf-8"));return e.apiKey?e.apiKey:(e.token&&!e.apiKey&&ue.log.warning(`Found old GitHub token in ${ce}. Run 'generatesaas init' to set up your API key.`),null)}catch{return null}}function Se(e){co(lo(ce),{recursive:!0}),ao(ce,JSON.stringify({apiKey:e},null," ")+`
9
- `,{mode:384})}async function Le(e){if(e?.apiKey)return e.apiKey;let t=process.env.GENERATESAAS_API_KEY;if(t)return t;let r=et();if(r)return r;if(!e?.prompt)throw new Error("API key not found. Set GENERATESAAS_API_KEY or run 'generatesaas init' to configure.");return tt()}async function tt(){let e=await ue.text({message:"Enter your GenerateSaaS API key:",placeholder:"gs_live_...",validate:t=>{if(!t?.trim())return"API key is required."}});return ue.isCancel(e)&&(ue.cancel("Setup cancelled."),process.exit(0)),e.trim()}async function me(e){return process.env.GENERATESAAS_OFFLINE_LICENSE==="1"?{latest:"0.0.0-ci",versions:[{version:"0.0.0-ci",date:new Date().toISOString(),breaking:!1}],entitlements:null}:await(await de(e,"/versions")).json()}async function Nt(e,t){try{return await(await de(e,`/changelog/${encodeURIComponent(t)}`)).text()}catch(r){if(r instanceof L&&r.status===404)return null;throw r}}async function fr(e,t){return await(await de(e,`/skill/${encodeURIComponent(t)}`)).json()}function ht(e){if(!(e instanceof L)||e.status!==403)return null;let t=e.body;return!t||t.code!=="update_window_expired"?null:{message:e.message,lastAllowedVersion:t.lastAllowedVersion??null}}function gr(e){return{frontend:e.frontend,architecture:e.architecture,deployTarget:e.deploymentTarget,database:e.databaseProvider,cache:e.cacheProvider,payment:e.paymentProvider,email:e.emailProvider,multiTenancy:e.multiTenancy,billingScope:e.billingScope,blog:e.blog,docs:e.docs,desktop:e.desktop,credits:e.credits,revenueSharing:e.revenueSharing,socialProviders:e.socialProviders,aiTools:e.aiTools,currency:e.defaultCurrency}}async function Lt(e,t){return process.env.GENERATESAAS_OFFLINE_LICENSE==="1"?{token:"offline-test-token",licenseId:"offline-test-license-id",installId:t.installId}:await(await de(e,"/license/sign",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).json()}async function hr(e,t){return await(await de(e,"/license/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).json()}async function $t(e,t){let r=await fetch(`${e}/license/verify`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)});if(!r.ok)throw new Error(`Verification service returned ${r.status}`);return await r.json()}async function yr(e,t,r){let n=await fetch(`${e}/license/inspect`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t}`},body:JSON.stringify(r)});if(!n.ok){let o=await n.json().catch(()=>null);throw new Error(o?.error??`Inspect endpoint returned ${n.status}`)}return await n.json()}var jt=new Set([".git","node_modules",".pnpm-store",".env",".env.test",".turbo",".nuxt",".output",".data","dist",".next",".svelte-kit",".wrangler",".devcontainer","playwright-report","test-results"]),Ut=new Set(["pnpm-lock.yaml"]);function $e(e){if(Mt(e))return!0;for(let t of e.split("/"))if(Ut.has(t))return!0;return!1}var po=new Set(["data","mksaas","references","scripts",".cursor",".agents",".codex",".generatesaas",".vscode",".mcp.json","README.md","TODO.md","OVERVIEW.md"]),uo=["docs/superpowers","packages/cli","packages/cli-api","infra/docker-compose.yml",".claude/commands",".claude/skills/web-next-port",".claude/skills/web-next-port-workspace",".claude/settings.local.json",".claude/worktrees"];function Mt(e){let t=e.split("/");for(let r of t)if(jt.has(r))return!0;if(po.has(t[0]))return!0;for(let r of uo)if(e===r||e.startsWith(r+"/"))return!0;return!1}async function yt(e,t,r){await fo(r,{recursive:!0});let n=process.env.GENERATESAAS_TEMPLATE_TARBALL;if(n){await vr(mo(n),Sr(r));return}let o=await de(e,`/template/${encodeURIComponent(t)}`);if(!o.body)throw new Error("Empty response body");let i=go.fromWeb(o.body);await vr(i,Sr(r))}function Sr(e){return ho({cwd:e,strip:1,filter:t=>{let r=t.replace(/^[^/]+\//,"");return r?!Mt(r):!0},sync:!1})}import{readdir as yo,readFile as Vt,rm as kr,writeFile as Ft}from"fs/promises";import{join as je}from"path";var vo=["apps/web-nuxt/public/images/blog","apps/web-next/public/images/blog","packages/content/en/blog","packages/content/ro/blog"];async function br(e){await Promise.all(vo.map(t=>kr(je(e,t),{recursive:!0,force:!0})))}var So="packages/config/src/blog.ts";async function wr(e){let t=je(e,So),r=await Vt(t,"utf-8"),n=Er(Er(r,"blogCategories",t),"blogAuthors",t);await Ft(t,n)}function Er(e,t,r){let n=new RegExp(`(export const ${t}\\b[^=]*=\\s*)\\[[\\s\\S]*?\\];`);if(!n.test(e))throw new Error(`emptyBlogConfig: could not find the \`export const ${t} = [...]\` declaration in ${r}. The boilerplate blog config may have been renamed or restructured; update the CLI strip in cleanup.ts.`);return e.replace(n,"$1[];")}async function Ar(e){let t=je(e,"packages/i18n/translations"),r;try{r=await yo(t)}catch{return}for(let n of r){let o=je(t,n,"web.json"),i;try{i=await Vt(o,"utf-8")}catch{continue}let s=JSON.parse(i);!s.blog||s.blog.categories===void 0||(s.blog.categories={},await Ft(o,JSON.stringify(s,null," ")+`
10
- `))}}async function Ir(e,t){t.includes("claude-code")||await kr(je(e,".claude"),{recursive:!0,force:!0})}async function Pr(e,t){let r=je(e,".claude","settings.json"),n;try{n=await Vt(r,"utf8")}catch{return}let o=JSON.parse(n);delete o.alwaysThinkingEnabled,delete o.enableAllProjectMcpServers,o.env&&(delete o.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS,Object.keys(o.env).length===0&&delete o.env),o.permissions?.allow&&(o.permissions.allow=o.permissions.allow.filter(i=>Eo(i,t))),await Ft(r,JSON.stringify(o,null," ")+`
11
- `)}function Eo(e,t){return!(e.startsWith("mcp__")||t!=="nuxt"&&e.includes("nuxt"))}import{join as Tr}from"path";import{mkdir as ko,readdir as bo,rm as wo,rmdir as Ao,writeFile as Io}from"fs/promises";import{dirname as vt,join as Po,relative as To,sep as Ro}from"path";function Ee(e){return e.split(Ro).join("/")}async function St(e){await ko(e,{recursive:!0})}async function l(e,t){await St(vt(e)),await Io(e,t,"utf-8")}async function Bt(e,t){await wo(e,{force:!0});let r=vt(e);for(;r!==t&&r!==vt(r);){try{await Ao(r)}catch{return}r=vt(r)}}async function ke(e,t,r){let n=[],o=await bo(e,{withFileTypes:!0});for(let i of o){let s=Po(e,i.name),a=Ee(To(t,s));r(a)||(i.isDirectory()?n.push(...await ke(s,t,r)):i.isFile()&&n.push(s))}return n}var _o={postgres:"Postgres",neon:"Neon (managed Postgres)",supabase:"Supabase (managed Postgres)"},Oo={resend:"Resend",ses:"Amazon SES",smtp:"SMTP"},xo={redis:"Redis",upstash:"Upstash Redis"},Do={node:"Node.js / Docker",vercel:"Vercel"},Co={stripe:"Stripe",polar:"Polar"};function No(e){let t=e.frontend==="nuxt",r=t?"Nuxt 4":"Next.js 16",n=t?"apps/web-nuxt":"apps/web-next",o=t?"`app/` pages + components + composables":"`app/` routes, `components/`, `lib/`",i=e.architecture==="fullstack",s=i?"(fullstack - Hono API mounted inside the app)":"(separate - standalone Hono backend)",a=i?`Mounted inside \`${n}\`. A standalone \`apps/backend\` is also kept (inert) so you can switch to separate later.`:"Runs from `apps/backend`; the frontend reaches it over HTTP.",d=xo[e.cacheProvider],u=Do[e.deploymentTarget],m=e.paymentProvider==="none"?"":`
12
- - **Payments:** ${Co[e.paymentProvider]}`,f=t?"`$t('key')` in templates (global helper); `useI18n()` from `vue-i18n` in `<script setup>` for `locale`/`setLocale`/`t()`.":"`useTranslations()` from `next-intl` in components; messages are loaded in `apps/web-next/i18n/request.ts`.",v=t?"**Navigation:** always use `localePath()` for paths (hardcoded paths break non-default locales); `await navigateTo()` in SSR.":"**Navigation:** `next/link` for links; `redirect()` from `next/navigation` for programmatic redirects in Server Components.";return`# AGENTS.md
2
+ import{Command as ea}from"commander";import*as no from"@clack/prompts";import{existsSync as vs,readdirSync as bs,rmSync as ks}from"fs";import{join as Ss,resolve as ws}from"path";import{Option as Y}from"commander";import*as E from"@clack/prompts";import*as at from"@clack/prompts";import Dt from"picocolors";function ar(e){let t=e?` GenerateSaaS v${e} `:" GenerateSaaS ";at.intro(Dt.bgYellow(Dt.black(t)))}function cr(){at.outro(Dt.yellow("Happy building!"))}import*as h from"@clack/prompts";import y from"picocolors";var qe={nextjs:{label:"Next.js",hint:"React 19 + Next.js 16"},nuxt:{label:"Nuxt",hint:"Vue 3 + Nuxt 4"}},Je={fullstack:{label:"Fullstack",hint:"Frontend hosts the API - works on serverless or long-running"},separate:{label:"Separate",hint:"Standalone Hono backend - long-running runtimes only (Docker, Render, Fly.io, Railway, Coolify, Dokploy)"}},ct={stripe:{label:"Stripe"},polar:{label:"Polar"},none:{label:"None",hint:"disable payments"}},lt={smtp:{label:"SMTP",hint:"Mailpit for local dev"},ses:{label:"Amazon SES"},resend:{label:"Resend"}},pt={user:{label:"Per user",hint:"each user has their own subscription"},organization:{label:"Per organization",hint:"org subscription shared by members"}},Te={postgres:{label:"PostgreSQL",hint:"port 5432",port:5432},redis:{label:"Redis",hint:"port 6379",port:6379},inngest:{label:"Inngest",hint:"port 8288",port:8288},mailpit:{label:"Mailpit",hint:"port 1025",port:1025}},We={"claude-code":{label:"Claude Code"},cursor:{label:"Cursor"},codex:{label:"Codex"},"gemini-cli":{label:"Gemini CLI"},windsurf:{label:"Windsurf"}};var ye={USD:{symbol:"$",name:"US Dollar",place:"left",space:!1},EUR:{symbol:"\u20AC",name:"Euro",place:"right",space:!1},GBP:{symbol:"\xA3",name:"British Pound",place:"left",space:!1},CAD:{symbol:"CA$",name:"Canadian Dollar",place:"left",space:!1},AUD:{symbol:"A$",name:"Australian Dollar",place:"left",space:!1},BRL:{symbol:"R$",name:"Brazilian Real",place:"left",space:!1},JPY:{symbol:"\xA5",name:"Japanese Yen",place:"left",space:!1}};var Q={node:{label:"Node.js / Docker",hint:"long-running runtime - Render, Fly.io, Railway, Coolify, Dokploy, VPS",edgeRuntime:!1},vercel:{label:"Vercel",hint:"serverless functions",edgeRuntime:!0}},F={postgres:{label:"PostgreSQL (self-hosted)",hint:"local Docker, drizzle-orm/node-postgres",managed:!1,envVars:[{key:"DATABASE_URL",defaultValue:"postgres://postgres:postgres@localhost:5432/saas"}]},neon:{label:"Neon",hint:"serverless Postgres",managed:!0,envVars:[{key:"DATABASE_URL",comment:"# TODO: Add your Neon connection string"}]},supabase:{label:"Supabase",hint:"managed Postgres",managed:!0,envVars:[{key:"DATABASE_URL",comment:"# TODO: Add your Supabase connection string"}]}},ee={redis:{label:"Redis (self-hosted)",hint:"local Docker, ioredis",managed:!1,envVars:[{key:"REDIS_URL",defaultValue:"redis://localhost:6379"}]},upstash:{label:"Upstash",hint:"serverless Redis",managed:!0,envVars:[{key:"UPSTASH_REDIS_REST_URL",comment:"# TODO: Add your Upstash REST URL"},{key:"UPSTASH_REDIS_REST_TOKEN",comment:"# TODO: Add your Upstash REST token"}]}},Xe=[{target:"vercel",provider:"redis",reason:"Vercel serverless cannot maintain persistent Redis connections. Consider Upstash."}],Ze=[{architecture:"separate",target:"vercel",reason:"Standalone backends need a long-running runtime. Vercel's per-function TypeScript check conflicts with pnpm's isolated install. Use --architecture fullstack for serverless, or deploy the standalone backend to a long-running runtime (Render, Fly.io, Railway, Coolify, Dokploy, or your own VPS via Docker)."}],lr=["Local file storage (sharp, geoip-lite)","SMTP email (use Resend or SES instead)","Content API git integration"];function dt(e){let t=F[e.databaseProvider].managed,r=ee[e.cacheProvider].managed;return e.dockerServices.some(n=>!(n==="postgres"&&t||n==="redis"&&r))}var oe={google:{label:"Google",envVars:[{name:"GOOGLE_CLIENT_ID",secret:!1},{name:"GOOGLE_CLIENT_SECRET",secret:!0}]},github:{label:"GitHub",envVars:[{name:"GITHUB_CLIENT_ID",secret:!1},{name:"GITHUB_CLIENT_SECRET",secret:!0}]},facebook:{label:"Facebook",envVars:[{name:"FACEBOOK_CLIENT_ID",secret:!1},{name:"FACEBOOK_CLIENT_SECRET",secret:!0}]},discord:{label:"Discord",envVars:[{name:"DISCORD_CLIENT_ID",secret:!1},{name:"DISCORD_CLIENT_SECRET",secret:!0}]},x:{label:"X",envVars:[{name:"TWITTER_CLIENT_ID",secret:!1},{name:"TWITTER_CLIENT_SECRET",secret:!0}]}};var ve=["nextjs","nuxt"],Re=["fullstack","separate"],_e=["stripe","polar","none"],Oe=["smtp","ses","resend"],xe=["postgres","redis","inngest","mailpit"],De=["claude-code","cursor","codex","gemini-cli","windsurf"],Ce=["user","organization"],ie=["USD","EUR","GBP","CAD","AUD","BRL","JPY"],se=["node","vercel"],ae=["postgres","neon","supabase"],ce=["redis","upstash"],Ne=["google","github","facebook","discord","x"];function ut(e){return e.split("-").map(t=>t.charAt(0).toUpperCase()+t.slice(1)).join(" ")}function mt(e){return/^[a-z][a-z0-9-]*$/.test(e)}function x(e){return e instanceof Error?e.message:String(e)}function P(e){h.isCancel(e)&&(h.cancel("Setup cancelled."),process.exit(0))}function Ct(e){let t=[];e.databaseProvider==="neon"&&t.push({key:"DATABASE_URL",message:"Neon connection string (optional):",placeholder:"postgres://...",secret:!0}),e.databaseProvider==="supabase"&&t.push({key:"DATABASE_URL",message:"Supabase connection string (optional):",placeholder:"postgres://...",secret:!0}),e.cacheProvider==="upstash"&&(t.push({key:"UPSTASH_REDIS_REST_URL",message:"Upstash REST URL (optional):",placeholder:"https://...",secret:!1}),t.push({key:"UPSTASH_REDIS_REST_TOKEN",message:"Upstash REST token (optional):",secret:!0})),e.paymentProvider==="stripe"&&(t.push({key:"STRIPE_SECRET_KEY",message:"Stripe secret key (optional):",placeholder:"sk_test_...",secret:!0}),t.push({key:"STRIPE_WEBHOOK_SECRET",message:"Stripe webhook secret (optional):",placeholder:"whsec_...",secret:!0})),e.paymentProvider==="polar"&&(t.push({key:"POLAR_ACCESS_TOKEN",message:"Polar access token (optional):",secret:!0}),t.push({key:"POLAR_WEBHOOK_SECRET",message:"Polar webhook secret (optional):",secret:!0})),e.emailProvider==="resend"&&t.push({key:"RESEND_API_KEY",message:"Resend API key (optional):",placeholder:"re_...",secret:!0}),e.emailProvider==="ses"&&(t.push({key:"AMAZON_SES_REGION",message:"Amazon SES region (optional):",placeholder:"us-east-1",secret:!1}),t.push({key:"AMAZON_SES_KEY",message:"Amazon SES access key (optional):",secret:!0}),t.push({key:"AMAZON_SES_SECRET",message:"Amazon SES secret (optional):",secret:!0}));for(let r of e.socialProviders){let n=oe[r];for(let o of n.envVars)t.push({key:o.name,message:`${o.name} (${n.label}, optional):`,secret:o.secret})}return e.demo&&t.push({key:"TURNSTILE_SECRET_KEY",message:"Cloudflare Turnstile secret key (optional):",secret:!0}),t}async function pr(e,t){let r=!1;h.log.info(y.bold("Project"));let n=e?.projectName??await(async()=>{r=!0;let l=await h.text({message:"Project name:",placeholder:"my-saas",validate:d=>{if(!d?.trim())return"Project name is required.";if(!mt(d))return"Use lowercase letters, numbers, and hyphens only. Must start with a letter."}});return P(l),l})(),o=e?.appName??await(async()=>{r=!0;let l=await h.text({message:"App name:",initialValue:ut(n),validate:d=>{if(!d?.trim())return"App name is required."}});return P(l),l})(),i=e?.projectDir??await(async()=>{r=!0;let l=await h.text({message:"Project location:",initialValue:`./${n}`});return P(l),l==="."?process.cwd():l})(),s=e?.frontend??await(async()=>{r=!0;let l=Object.keys(qe),d=await h.select({message:"Frontend framework:",options:l.map(k=>({value:k,label:qe[k].label,hint:qe[k].hint}))});return P(d),d})();h.log.info(y.bold("Infrastructure"));let a=e?.deploymentTarget??"node";if(e?.deploymentTarget===void 0){r=!0;let l=await h.select({message:"Deployment target:",options:se.map(d=>({value:d,label:Q[d].label,hint:Q[d].hint}))});P(l),a=l}let p=e?.architecture?Ze.find(l=>l.architecture===e.architecture&&l.target===a):void 0;if(p)throw new Error(`Incompatible: --architecture ${p.architecture} + --deploy ${p.target}. ${p.reason}`);let f=new Set(Ze.filter(l=>l.target===a).map(l=>l.architecture)),m=Re.filter(l=>!f.has(l)),u=e?.architecture??await(async()=>{if(m.length===1){let d=m[0];return h.log.info(`Auto-selected ${Je[d].label} architecture (only compatible option for ${Q[a].label}).`),d}r=!0;let l=await h.select({message:"Architecture:",options:m.map(d=>({value:d,label:Je[d].label,hint:Je[d].hint}))});return P(l),l})(),v=e?.databaseProvider??await(async()=>{r=!0;let l=ae.filter(k=>!Xe.some(M=>M.target===a&&M.provider===k));if(l.length===1){let k=l[0];return h.log.info(`Auto-selected ${F[k].label} (only compatible option for ${Q[a].label}).`),k}let d=await h.select({message:"Database provider:",options:l.map(k=>({value:k,label:F[k].label,hint:F[k].hint}))});return P(d),d})(),w=e?.cacheProvider??await(async()=>{r=!0;let l=ce.filter(k=>!Xe.some(M=>M.target===a&&M.provider===k));if(l.length===1){let k=l[0];return h.log.info(`Auto-selected ${ee[k].label} (only compatible option for ${Q[a].label}).`),k}let d=await h.select({message:"Cache provider:",options:l.map(k=>({value:k,label:ee[k].label,hint:ee[k].hint}))});return P(d),d})();if(Q[a]?.edgeRuntime){let l=lr.map(d=>` - ${d}`).join(`
3
+ `);h.note(l,"Unavailable on edge runtime")}h.log.info(y.bold("Features"));let T=e?.paymentProvider??await(async()=>{r=!0;let l=await h.select({message:"Payment provider:",options:_e.map(d=>({value:d,label:ct[d].label,hint:ct[d].hint}))});return P(l),l})(),G=e?.defaultCurrency??await(async()=>{if(T==="none")return"USD";r=!0;let l=await h.select({message:"Default currency:",options:ie.map(d=>({value:d,label:d,hint:ye[d].name}))});return P(l),l})(),L=e?.emailProvider??await(async()=>{r=!0;let l=await h.select({message:"Email provider:",options:Oe.map(d=>({value:d,label:lt[d].label,hint:lt[d].hint}))});return P(l),l})(),W=e?.multiTenancy??await(async()=>{r=!0;let l=await h.confirm({message:"Enable multi-tenancy (organizations)?",initialValue:!1});return P(l),l})(),X=e?.billingScope??"user";if(W&&e?.billingScope===void 0){r=!0;let l=await h.select({message:"Billing scope:",options:Ce.map(d=>({value:d,label:pt[d].label,hint:pt[d].hint}))});P(l),X=l}let ne=e?.blog??await(async()=>{r=!0;let l=await h.confirm({message:"Enable blog?",initialValue:!0});return P(l),l})(),b=e?.docs??await(async()=>{r=!0;let l=await h.confirm({message:"Include docs app? (self-hosted Fumadocs documentation site)",initialValue:!1});return P(l),l})(),I=t?.desktopAllowed!==!1,R=e?.desktop??(I?await(async()=>{r=!0;let l=await h.confirm({message:"Include the Electron desktop app? (apps/desktop - cross-platform, device-auth)",initialValue:!1});return P(l),l})():(h.log.info(y.dim("Desktop app: available on the Pro plan and up - skipped.")),!1)),K=R?e?.desktopAutoRelease??await(async()=>{r=!0;let l=await h.select({message:"Desktop release trigger:",options:[{value:!1,label:"Manual only",hint:"run from the Actions tab (saves CI minutes)"},{value:!0,label:"Automatic",hint:"release on every CI success on main"}],initialValue:!1});return P(l),l})():!1,A=e?.revenueSharing??await(async()=>{r=!0;let l=await h.confirm({message:"Enable revenue sharing? (opt-in MRR leaderboard with dofollow backlinks)",initialValue:!1});return P(l),l})(),Z=T==="none"?!1:e?.credits??await(async()=>{r=!0;let l=await h.confirm({message:"Enable credits? (metered usage on top of subscription plans)",initialValue:!0});return P(l),l})(),it=e?.socialProviders??await(async()=>{r=!0;let l=Ne.map(k=>({value:k,label:oe[k].label,hint:`requires ${oe[k].envVars.map(M=>M.name).join(" / ")}`})),d=await h.multiselect({message:"Which social login providers should the sign-in screen show?",options:l,initialValues:[],required:!1});return P(d),d})();h.log.info(y.bold("Tooling"));let _t=e?.dockerServices??await(async()=>{r=!0;let l=[...xe].filter(V=>V!=="mailpit");L==="smtp"&&l.push("mailpit");let d=l.map(V=>({value:V,label:Te[V].label,hint:Te[V].hint})),k=d.map(V=>V.value).filter(V=>!(V==="postgres"&&(v==="neon"||v==="supabase")||V==="redis"&&w==="upstash")),M=await h.multiselect({message:"Which services should we set up in Docker for you?",options:d,initialValues:k,required:!1});return P(M),M})(),Ot=e?.aiTools??await(async()=>{r=!0;let l=De.map(k=>({value:k,label:We[k].label})),d=await h.multiselect({message:"Which AI coding tools do you use?",options:l,initialValues:[],required:!1});return P(d),d})(),xt=e?.demo,st=Ct({databaseProvider:v,cacheProvider:w,paymentProvider:T,emailProvider:L,socialProviders:it,demo:xt}),Ye={};if(st.length>0&&r){h.log.info(y.bold("Credentials")+y.dim(" all optional - press Enter to skip, fill in .env later"));for(let l of st)if(r=!0,l.secret){let d=await h.password({message:l.message,mask:"*"});P(d),typeof d=="string"&&d.trim()&&(Ye[l.key]=d.trim())}else{let d=await h.text({message:l.message,placeholder:l.placeholder});P(d),typeof d=="string"&&d.trim()&&(Ye[l.key]=d.trim())}}if(r){let l=[` Name: ${y.cyan(n)}`,` App name: ${y.cyan(o)}`,` Location: ${y.cyan(i)}`,` Frontend: ${y.cyan(qe[s].label)}`,` Architecture: ${y.cyan(Je[u].label)}`].join(`
4
+ `),d=[` Deploy target: ${y.cyan(Q[a]?.label??"Node.js / Docker")}`,` Database: ${y.cyan(F[v].label)}`,` Cache: ${y.cyan(ee[w].label)}`,_t.length>0?` Docker: ${y.cyan(_t.map(he=>Te[he].label).join(", "))}`:` Docker: ${y.dim("none")}`].filter(Boolean).join(`
5
+ `),k=[T!=="none"?` Payment: ${y.cyan(ct[T].label)} (${G})`:` Payment: ${y.dim("none")}`,` Credits: ${Z?y.cyan("Yes"):y.dim("No")}`,` Email: ${y.cyan(lt[L].label)}`,` Multi-tenancy: ${W?y.cyan("Yes")+` (billing: ${pt[X].label})`:y.dim("No")}`,` Blog: ${ne?y.cyan("Yes"):y.dim("No")}`,` Docs app: ${b?y.cyan("Yes"):y.dim("No")}`,` Desktop app: ${R?y.cyan("Yes"):y.dim("No")}`,...R?[` Releases: ${y.cyan(K?"Automatic on CI":"Manual only")}`]:[],` Rev. sharing: ${A?y.cyan("Yes"):y.dim("No")}`,it.length>0?` Social login: ${y.cyan(it.map(he=>oe[he].label).join(", "))}`:` Social login: ${y.dim("none")}`,Ot.length>0?` AI tools: ${y.cyan(Ot.map(he=>We[he].label).join(", "))}`:` AI tools: ${y.dim("none")}`].join(`
6
+ `),M=[y.bold("Project"),l,"",y.bold("Infrastructure"),d,"",y.bold("Features"),k];if(st.length>0){let he=st.map(sr=>{let oo=Ye[sr.key]?y.green("provided"):y.dim("skipped");return` ${sr.key}: ${oo}`}).join(`
7
+ `);M.push("",y.bold("Credentials"),he)}h.note(M.join(`
8
+ `),"Summary");let V=await h.confirm({message:"Proceed with these settings?"});(h.isCancel(V)||!V)&&(h.cancel("Setup cancelled."),process.exit(0))}return{projectName:n,appName:o,projectDir:i,frontend:s,architecture:u,deploymentTarget:a,databaseProvider:v,cacheProvider:w,paymentProvider:T,emailProvider:L,multiTenancy:W,billingScope:X,blog:ne,docs:b,desktop:R,desktopAutoRelease:K,desktopAi:R?e?.desktopAi??!1:!1,revenueSharing:A,credits:Z,dockerServices:_t,aiTools:Ot,socialProviders:it,defaultCurrency:G,...Object.keys(Ye).length>0?{credentials:Ye}:{},...e?.baseUrl!==void 0?{baseUrl:e.baseUrl}:{},...xt!==void 0?{demo:xt}:{}}}import{createReadStream as fo}from"fs";import{mkdir as go}from"fs/promises";import{Readable as ho}from"stream";import{pipeline as vr}from"stream/promises";import{extract as yo}from"tar";import{join as be}from"path";import{homedir as io}from"os";var Qe=process.env.GENERATESAAS_API_URL??"https://cli.generatesaas.com",z=".generatesaas",de=be(z,"manifest.json"),dr=be(z,"hashes.json"),ft=be(z,"template-hashes.json"),gt=be(z,"template"),ur=be(z,"staging"),mr=be(z,"staging.json"),le=be(io(),".generatesaas");var $=class extends Error{constructor(r,n,o){super(n);this.status=r;this.body=o}status;body;name="ApiError"};function pe(e){return{apiKey:e,baseUrl:Qe}}async function ue(e,t,r){let n=`${e.baseUrl}${t}`,o=await fetch(n,{...r,headers:{...r?.headers,Authorization:`Bearer ${e.apiKey}`,"User-Agent":"generatesaas-cli"}});if(!o.ok){let i,s;try{s=await o.json(),i=s.error??`API ${o.status}: ${t}`}catch{i=`API ${o.status}: ${t}`}throw new $(o.status,i,s)}return o}import{existsSync as so,readFileSync as ao,writeFileSync as co,mkdirSync as lo}from"fs";import{dirname as po}from"path";import*as me from"@clack/prompts";function et(){if(!so(le))return null;try{let e=JSON.parse(ao(le,"utf-8"));return e.apiKey?e.apiKey:(e.token&&!e.apiKey&&me.log.warning(`Found old GitHub token in ${le}. Run 'generatesaas init' to set up your API key.`),null)}catch{return null}}function ke(e){lo(po(le),{recursive:!0}),co(le,JSON.stringify({apiKey:e},null," ")+`
9
+ `,{mode:384})}async function Le(e){if(e?.apiKey)return e.apiKey;let t=process.env.GENERATESAAS_API_KEY;if(t)return t;let r=et();if(r)return r;if(!e?.prompt)throw new Error("API key not found. Set GENERATESAAS_API_KEY or run 'generatesaas init' to configure.");return tt()}async function tt(){let e=await me.text({message:"Enter your GenerateSaaS API key:",placeholder:"gs_live_...",validate:t=>{if(!t?.trim())return"API key is required."}});return me.isCancel(e)&&(me.cancel("Setup cancelled."),process.exit(0)),e.trim()}async function fe(e){return process.env.GENERATESAAS_OFFLINE_LICENSE==="1"?{latest:"0.0.0-ci",versions:[{version:"0.0.0-ci",date:new Date().toISOString(),breaking:!1}],entitlements:null}:await(await ue(e,"/versions")).json()}async function Nt(e,t){try{return await(await ue(e,`/changelog/${encodeURIComponent(t)}`)).text()}catch(r){if(r instanceof $&&r.status===404)return null;throw r}}async function fr(e,t){return await(await ue(e,`/skill/${encodeURIComponent(t)}`)).json()}function ht(e){if(!(e instanceof $)||e.status!==403)return null;let t=e.body;return!t||t.code!=="update_window_expired"?null:{message:e.message,lastAllowedVersion:t.lastAllowedVersion??null}}function gr(e){return{frontend:e.frontend,architecture:e.architecture,deployTarget:e.deploymentTarget,database:e.databaseProvider,cache:e.cacheProvider,payment:e.paymentProvider,email:e.emailProvider,multiTenancy:e.multiTenancy,billingScope:e.billingScope,blog:e.blog,docs:e.docs,desktop:e.desktop,credits:e.credits,revenueSharing:e.revenueSharing,socialProviders:e.socialProviders,aiTools:e.aiTools,currency:e.defaultCurrency}}async function Lt(e,t){return process.env.GENERATESAAS_OFFLINE_LICENSE==="1"?{token:"offline-test-token",licenseId:"offline-test-license-id",installId:t.installId}:await(await ue(e,"/license/sign",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).json()}async function hr(e,t){return await(await ue(e,"/license/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).json()}async function $t(e,t){let r=await fetch(`${e}/license/verify`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)});if(!r.ok)throw new Error(`Verification service returned ${r.status}`);return await r.json()}async function yr(e,t,r){let n=await fetch(`${e}/license/inspect`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t}`},body:JSON.stringify(r)});if(!n.ok){let o=await n.json().catch(()=>null);throw new Error(o?.error??`Inspect endpoint returned ${n.status}`)}return await n.json()}var jt=new Set([".git","node_modules",".pnpm-store",".env",".env.test",".turbo",".nuxt",".output",".data","dist",".next",".svelte-kit",".wrangler",".devcontainer","playwright-report","test-results"]),Ut=new Set(["pnpm-lock.yaml"]);function $e(e){if(Mt(e))return!0;for(let t of e.split("/"))if(Ut.has(t))return!0;return!1}var uo=new Set(["data","mksaas","references","scripts",".cursor",".agents",".codex",".generatesaas",".vscode",".mcp.json","README.md","TODO.md","OVERVIEW.md"]),mo=["docs/superpowers","packages/cli","packages/cli-api","infra/docker-compose.yml",".claude/commands",".claude/skills/web-next-port",".claude/skills/web-next-port-workspace",".claude/settings.local.json",".claude/worktrees"];function Mt(e){let t=e.split("/");for(let r of t)if(jt.has(r))return!0;if(uo.has(t[0]))return!0;for(let r of mo)if(e===r||e.startsWith(r+"/"))return!0;return!1}async function yt(e,t,r){await go(r,{recursive:!0});let n=process.env.GENERATESAAS_TEMPLATE_TARBALL;if(n){await vr(fo(n),br(r));return}let o=await ue(e,`/template/${encodeURIComponent(t)}`);if(!o.body)throw new Error("Empty response body");let i=ho.fromWeb(o.body);await vr(i,br(r))}function br(e){return yo({cwd:e,strip:1,filter:t=>{let r=t.replace(/^[^/]+\//,"");return r?!Mt(r):!0},sync:!1})}import{readdir as vo,readFile as Vt,rm as Sr,writeFile as Ft}from"fs/promises";import{join as je}from"path";var bo=["apps/web-nuxt/public/images/blog","apps/web-next/public/images/blog","packages/content/en/blog","packages/content/ro/blog"];async function wr(e){await Promise.all(bo.map(t=>Sr(je(e,t),{recursive:!0,force:!0})))}var ko="packages/config/src/blog.ts";async function Er(e){let t=je(e,ko),r=await Vt(t,"utf-8"),n=kr(kr(r,"blogCategories",t),"blogAuthors",t);await Ft(t,n)}function kr(e,t,r){let n=new RegExp(`(export const ${t}\\b[^=]*=\\s*)\\[[\\s\\S]*?\\];`);if(!n.test(e))throw new Error(`emptyBlogConfig: could not find the \`export const ${t} = [...]\` declaration in ${r}. The boilerplate blog config may have been renamed or restructured; update the CLI strip in cleanup.ts.`);return e.replace(n,"$1[];")}async function Ar(e){let t=je(e,"packages/i18n/translations"),r;try{r=await vo(t)}catch{return}for(let n of r){let o=je(t,n,"web.json"),i;try{i=await Vt(o,"utf-8")}catch{continue}let s=JSON.parse(i);!s.blog||s.blog.categories===void 0||(s.blog.categories={},await Ft(o,JSON.stringify(s,null," ")+`
10
+ `))}}async function Ir(e,t){t.includes("claude-code")||await Sr(je(e,".claude"),{recursive:!0,force:!0})}async function Pr(e,t){let r=je(e,".claude","settings.json"),n;try{n=await Vt(r,"utf8")}catch{return}let o=JSON.parse(n);delete o.alwaysThinkingEnabled,delete o.enableAllProjectMcpServers,o.env&&(delete o.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS,Object.keys(o.env).length===0&&delete o.env),o.permissions?.allow&&(o.permissions.allow=o.permissions.allow.filter(i=>So(i,t))),await Ft(r,JSON.stringify(o,null," ")+`
11
+ `)}function So(e,t){return!(e.startsWith("mcp__")||t!=="nuxt"&&e.includes("nuxt"))}import{join as Tr}from"path";import{mkdir as wo,readdir as Eo,rm as Ao,rmdir as Io,writeFile as Po}from"fs/promises";import{dirname as vt,join as To,relative as Ro,sep as _o}from"path";function Se(e){return e.split(_o).join("/")}async function bt(e){await wo(e,{recursive:!0})}async function c(e,t){await bt(vt(e)),await Po(e,t,"utf-8")}async function Bt(e,t){await Ao(e,{force:!0});let r=vt(e);for(;r!==t&&r!==vt(r);){try{await Io(r)}catch{return}r=vt(r)}}async function we(e,t,r){let n=[],o=await Eo(e,{withFileTypes:!0});for(let i of o){let s=To(e,i.name),a=Se(Ro(t,s));r(a)||(i.isDirectory()?n.push(...await we(s,t,r)):i.isFile()&&n.push(s))}return n}var Oo={postgres:"Postgres",neon:"Neon (managed Postgres)",supabase:"Supabase (managed Postgres)"},xo={resend:"Resend",ses:"Amazon SES",smtp:"SMTP"},Do={redis:"Redis",upstash:"Upstash Redis"},Co={node:"Node.js / Docker",vercel:"Vercel"},No={stripe:"Stripe",polar:"Polar"};function Lo(e){let t=e.frontend==="nuxt",r=t?"Nuxt 4":"Next.js 16",n=t?"apps/web-nuxt":"apps/web-next",o=t?"`app/` pages + components + composables":"`app/` routes, `components/`, `lib/`",i=e.architecture==="fullstack",s=i?"(fullstack - Hono API mounted inside the app)":"(separate - standalone Hono backend)",a=i?`Mounted inside \`${n}\`. A standalone \`apps/backend\` is also kept (inert) so you can switch to separate later.`:"Runs from `apps/backend`; the frontend reaches it over HTTP.",p=Do[e.cacheProvider],f=Co[e.deploymentTarget],m=e.paymentProvider==="none"?"":`
12
+ - **Payments:** ${No[e.paymentProvider]}`,u=e.desktop?", and the Electron desktop app under `docs/desktop/`":"",v=t?"`$t('key')` in templates (global helper); `useI18n()` from `vue-i18n` in `<script setup>` for `locale`/`setLocale`/`t()`.":"`useTranslations()` from `next-intl` in components; messages are loaded in `apps/web-next/i18n/request.ts`.",w=t?"**Navigation:** always use `localePath()` for paths (hardcoded paths break non-default locales); `await navigateTo()` in SSR.":"**Navigation:** `next/link` for links; `redirect()` from `next/navigation` for programmatic redirects in Server Components.";return`# AGENTS.md
13
13
 
14
14
  Guidelines for AI coding agents (Claude Code, Codex, Cursor, \u2026) in this project.
15
15
  Keep this file tight: add a rule only when it prevents a recurring mistake - too
@@ -37,11 +37,11 @@ flag, route, or translation is the most common and most costly mistake in this r
37
37
 
38
38
  - **Frontend:** ${r} ${s}
39
39
  - **API:** Hono, RPC-typed. ${a}
40
- - **Database:** Drizzle ORM + ${_o[e.databaseProvider]}
41
- - **Cache + jobs:** ${d} + Inngest
40
+ - **Database:** Drizzle ORM + ${Oo[e.databaseProvider]}
41
+ - **Cache + jobs:** ${p} + Inngest
42
42
  - **Auth:** Better Auth${m}
43
- - **Email:** ${Oo[e.emailProvider]}
44
- - **Deploy:** ${u}
43
+ - **Email:** ${xo[e.emailProvider]}
44
+ - **Deploy:** ${f}
45
45
 
46
46
  ## Where things live (extend these - don't reinvent)
47
47
 
@@ -52,7 +52,7 @@ flag, route, or translation is the most common and most costly mistake in this r
52
52
  - \`packages/runtime/src/env.ts\` - the validated (Zod) env schema. New env var \u2192 add it here **and** to \`.env.example\` (the committed reference); set the local value in \`.env\`.
53
53
  - \`packages/{payments,mail,sms,storage,notifications}\` - config-gated integrations. Every provider's files stay even when its feature is off, so flipping a flag is enough to enable it.
54
54
  - \`${n}\` - the ${r} app (${o}).
55
- - \`docs/${t?"nuxt":"next"}/\` - feature & architecture documentation for this stack (Markdown). Consult it before searching from scratch; it cites real config keys, package names, and file paths you can act on.
55
+ - \`docs/${t?"nuxt":"next"}/\` - feature & architecture documentation for this stack (Markdown). Consult it before searching from scratch; it cites real config keys, package names, and file paths you can act on. The GenerateSaaS CLI lives under \`docs/cli/\`${u}.
56
56
 
57
57
  ## Code style
58
58
 
@@ -69,8 +69,8 @@ flag, route, or translation is the most common and most costly mistake in this r
69
69
 
70
70
  - **Hono routes:** keep the method chain (\`.get().post()\`) - RPC type inference depends on it. Validate with \`sValidator\` from \`@hono/standard-validator\`.
71
71
  - **Feature flags:** respect \`config.*\` - hide/skip a feature when its flag is off (the files stay so you can flip it later).
72
- - **i18n:** strings live in \`packages/i18n/translations/{locale}/{scope}.json\`; edit \`en/\` only, keep keys generic. ${f} A pre-commit hook runs \`pnpm translate\` (needs \`OPENROUTER_API_KEY\`) to sync other locales from \`en\`; without the key it skips.
73
- - ${v}
72
+ - **i18n:** strings live in \`packages/i18n/translations/{locale}/{scope}.json\`; edit \`en/\` only, keep keys generic. ${v} A pre-commit hook runs \`pnpm translate\` (needs \`OPENROUTER_API_KEY\`) to sync other locales from \`en\`; without the key it skips.
73
+ - ${w}
74
74
  - **Routes:** use \`config.routes.*\`, never hardcoded path strings.
75
75
  - **Secrets:** never send them to an external service; generate tokens/QR codes client-side.
76
76
 
@@ -81,14 +81,14 @@ In \`@repo/*\` backend packages prefer web-standard APIs: \`crypto.randomUUID()\
81
81
  ## This project
82
82
 
83
83
  Scaffolded from the GenerateSaaS boilerplate. To pull upstream boilerplate updates, ask your agent to **"update my GenerateSaaS project"**. To remove the license heartbeat + manifest: \`pnpm dlx generatesaas eject\`.
84
- `}async function Rr(e){await l(Tr(e.projectDir,"AGENTS.md"),No(e)),await l(Tr(e.projectDir,"CLAUDE.md"),`@AGENTS.md
85
- `)}import{join as Lo}from"path";var $o={postgres:"Postgres (self-hosted)",neon:"Neon (managed Postgres)",supabase:"Supabase (managed Postgres)"};function jo(e){let t=e.appName.trim()||e.projectName,r=e.frontend==="nuxt",n=r?"Nuxt 4":"Next.js 16",o=r?"apps/web-nuxt":"apps/web-next",i=$o[e.databaseProvider],s=dt(e),a=e.architecture==="fullstack"?`${n} app at \`${o}/\` with the Hono API mounted inside it. A standalone \`apps/backend/\` is also included (inert in this fullstack setup) so you can split the API into a separate service later without re-scaffolding.`:`${n} app at \`${o}/\` and a separate Hono backend at \`apps/backend/\`.`,d=e.architecture==="fullstack"?`- App + API: http://localhost:3000
84
+ `}async function Rr(e){await c(Tr(e.projectDir,"AGENTS.md"),Lo(e)),await c(Tr(e.projectDir,"CLAUDE.md"),`@AGENTS.md
85
+ `)}import{join as $o}from"path";var jo={postgres:"Postgres (self-hosted)",neon:"Neon (managed Postgres)",supabase:"Supabase (managed Postgres)"};function Uo(e){let t=e.appName.trim()||e.projectName,r=e.frontend==="nuxt",n=r?"Nuxt 4":"Next.js 16",o=r?"apps/web-nuxt":"apps/web-next",i=jo[e.databaseProvider],s=dt(e),a=e.architecture==="fullstack"?`${n} app at \`${o}/\` with the Hono API mounted inside it. A standalone \`apps/backend/\` is also included (inert in this fullstack setup) so you can split the API into a separate service later without re-scaffolding.`:`${n} app at \`${o}/\` and a separate Hono backend at \`apps/backend/\`.`,p=e.architecture==="fullstack"?`- App + API: http://localhost:3000
86
86
  - Inngest dev server: http://127.0.0.1:8288`:`- App: http://localhost:3000
87
87
  - API: http://localhost:3010
88
- - Inngest dev server: http://127.0.0.1:8288`,u=s?`pnpm infra # optional: starts local Docker services (Postgres / Redis / Inngest / Mailpit)
88
+ - Inngest dev server: http://127.0.0.1:8288`,f=s?`pnpm infra # optional: starts local Docker services (Postgres / Redis / Inngest / Mailpit)
89
89
  `:"",m=e.deploymentTarget==="vercel"?"### Deployment\n\nDeploy to Vercel: `vercel deploy` (or connect the repo in the Vercel dashboard). Required environment variables are listed in `.env`.":`### Deployment
90
90
 
91
- This project ships with Dockerfiles for each app. Build images with \`docker build\` and deploy to your runtime of choice (Render / Fly.io / Railway / Coolify / Dokploy / your own VPS).${s?"\n\nThe `infra/` directory ships a Docker Compose file for the local-only services (Postgres / Redis / Inngest / Mailpit, filtered by your provider choices).":""}`,f=e.aiTools.length>0?"To pull the latest boilerplate changes into this project, open it in your AI coding agent and ask: `update my GenerateSaaS project`.":"To pull the latest boilerplate changes into this project, install an AI coding agent (Claude Code / Cursor / Codex / Gemini CLI / Windsurf) and ask: `update my GenerateSaaS project`. The skill bundle that drives the update lives under each tool's skill root.";return`# ${t}
91
+ This project ships with Dockerfiles for each app. Build images with \`docker build\` and deploy to your runtime of choice (Render / Fly.io / Railway / Coolify / Dokploy / your own VPS).${s?"\n\nThe `infra/` directory ships a Docker Compose file for the local-only services (Postgres / Redis / Inngest / Mailpit, filtered by your provider choices).":""}`,u=e.aiTools.length>0?"To pull the latest boilerplate changes into this project, open it in your AI coding agent and ask: `update my GenerateSaaS project`.":"To pull the latest boilerplate changes into this project, install an AI coding agent (Claude Code / Cursor / Codex / Gemini CLI / Windsurf) and ask: `update my GenerateSaaS project`. The skill bundle that drives the update lives under each tool's skill root.";return`# ${t}
92
92
 
93
93
  ${a}
94
94
 
@@ -108,10 +108,10 @@ pnpm install
108
108
  # A ready-to-run \`.env\` was generated for you - open it and fill in the
109
109
  # values marked \`# TODO\` (provider API keys). \`.env.example\` is the committed
110
110
  # reference. All apps load the single root \`.env\`.
111
- ${u}pnpm dev
111
+ ${f}pnpm dev
112
112
  \`\`\`
113
113
 
114
- ${d}
114
+ ${p}
115
115
 
116
116
  ## Build your SaaS
117
117
 
@@ -142,12 +142,12 @@ pnpm dlx generatesaas eject
142
142
 
143
143
  ## Updates
144
144
 
145
- ${f}
146
- `}async function _r(e){await l(Lo(e.projectDir,"README.md"),jo(e))}import{join as Et}from"path";function Uo(e){let t=e.split(".");return t.length>=3?t.slice(1).join("."):e}function Mo(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"app"}function Vo(e,t){let r=Mo(e);return{appId:`${t.split(".").reverse().join(".")}.${r.replace(/-/g,"")}`,productName:e,protocol:r}}async function Or(e){let t=e.appName.replace(/\\/g,"\\\\").replace(/"/g,'\\"'),r=e.paymentProvider!=="none",n=e.baseUrl??"http://localhost:3000",o=e.baseUrl?new URL(e.baseUrl).hostname:"example.com",i=Uo(o),s=e.demo?"false":"true",a=e.demo?`
145
+ ${u}
146
+ `}async function _r(e){await c($o(e.projectDir,"README.md"),Uo(e))}import{join as kt}from"path";function Mo(e){let t=e.split(".");return t.length>=3?t.slice(1).join("."):e}function Vo(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"app"}function Fo(e,t){let r=Vo(e);return{appId:`${t.split(".").reverse().join(".")}.${r.replace(/-/g,"")}`,productName:e,protocol:r}}async function Or(e){let t=e.appName.replace(/\\/g,"\\\\").replace(/"/g,'\\"'),r=e.paymentProvider!=="none",n=e.baseUrl??"http://localhost:3000",o=e.baseUrl?new URL(e.baseUrl).hostname:"example.com",i=Mo(o),s=e.demo?"false":"true",a=e.demo?`
147
147
  support: {
148
148
  enableInDev: true,
149
149
  crisp: { websiteId: "7e221cec-ed61-46b7-b1b4-8cbc16557cca" }
150
- },`:"",d=e.frontend==="nextjs"&&e.architecture==="fullstack"?"false":"true",u=Vo(t,i),m=`import type { AppConfig } from "@repo/config/types";
150
+ },`:"",p=e.frontend==="nextjs"&&e.architecture==="fullstack"?"false":"true",f=Fo(t,i),m=`import type { AppConfig } from "@repo/config/types";
151
151
  import { desktopConfig } from "./desktop.mjs";
152
152
  import { tenancyConfig } from "./tenancy-flags.mjs";
153
153
 
@@ -216,7 +216,7 @@ export const config: AppConfig = {
216
216
  prefix: "key_"
217
217
  },
218
218
  apiDocs: true,
219
- performanceMonitor: { enabled: ${d} },
219
+ performanceMonitor: { enabled: ${p} },
220
220
  notifications: {
221
221
  enabled: true,
222
222
  maxPerUser: 100,
@@ -254,7 +254,7 @@ export const config: AppConfig = {
254
254
  }`:`{
255
255
  base: "${e.defaultCurrency}",
256
256
  list: [
257
- { symbol: "${he[e.defaultCurrency].symbol}", name: "${he[e.defaultCurrency].name}", code: "${e.defaultCurrency}", place: "${he[e.defaultCurrency].place}", space: ${he[e.defaultCurrency].space} }
257
+ { symbol: "${ye[e.defaultCurrency].symbol}", name: "${ye[e.defaultCurrency].name}", code: "${e.defaultCurrency}", place: "${ye[e.defaultCurrency].place}", space: ${ye[e.defaultCurrency].space} }
258
258
  ],
259
259
  countryMap: {
260
260
  default: "${e.defaultCurrency}"
@@ -324,7 +324,7 @@ export * from "./pricing";
324
324
  export * from "./roles";
325
325
  export * from "./section-tabs";
326
326
  export * from "./tenancy";
327
- `,f=Et(e.projectDir,"packages/config/src/index.ts");await l(f,m),await l(Et(e.projectDir,"packages/config/src/desktop.mjs"),`// Plain-JS desktop app identity. NO env reads, so tooling that cannot import the
327
+ `,u=kt(e.projectDir,"packages/config/src/index.ts");await c(u,m),await c(kt(e.projectDir,"packages/config/src/desktop.mjs"),`// Plain-JS desktop app identity. NO env reads, so tooling that cannot import the
328
328
  // TypeScript config index reads it directly: the Electron renderer + main process
329
329
  // (electron-vite bundles @repo/config/desktop) and electron-builder (Node ESM).
330
330
  // Edit these values once to rebrand - they are the same in dev and production, so
@@ -333,9 +333,9 @@ export * from "./tenancy";
333
333
 
334
334
  export const desktopConfig = {
335
335
  enabled: ${e.desktop},
336
- appId: "${u.appId}",
337
- productName: "${u.productName}",
338
- protocol: "${u.protocol}",
336
+ appId: "${f.appId}",
337
+ productName: "${f.productName}",
338
+ protocol: "${f.protocol}",
339
339
  baseUrl: "${n}",
340
340
  // Name of the app's data folder (created under the OS app-data dir). Where the app
341
341
  // and its AI agents persist files - logs, gathered data, generated artifacts. Rename
@@ -361,7 +361,7 @@ export const desktopConfig = {
361
361
  userSchedules: true
362
362
  }
363
363
  };
364
- `),await l(Et(e.projectDir,"packages/config/src/tenancy-flags.mjs"),`// Plain-JS tenancy flags. NO env reads, so tooling that cannot import the
364
+ `),await c(kt(e.projectDir,"packages/config/src/tenancy-flags.mjs"),`// Plain-JS tenancy flags. NO env reads, so tooling that cannot import the
365
365
  // TypeScript config index reads it directly (the Electron renderer). Mirrors the
366
366
  // desktop.mjs pattern. The config index re-exports this as config.tenancy.
367
367
 
@@ -370,11 +370,11 @@ export const tenancyConfig = ${e.multiTenancy?`{
370
370
  organizationLimit: 5,
371
371
  billingScope: "${e.billingScope}"
372
372
  }`:"{ multiTenant: false }"};
373
- `),e.desktop&&await l(Et(e.projectDir,"apps/desktop/dev-app-update.yml"),`provider: generic
373
+ `),e.desktop&&await c(kt(e.projectDir,"apps/desktop/dev-app-update.yml"),`provider: generic
374
374
  url: https://cdn.${i}/desktop
375
- updaterCacheDirName: ${u.protocol}-updater
376
- `)}import{join as Fo}from"path";function Bo(e){return e==="stripe"?' stripePriceId: "",':' polarProductId: "",'}function Gt(e){let t=[];return e.withCredits&&(t.push(` credits: ${e.credits},`),t.push(" creditInterval: 30,")),t.push(` apiRateLimit: { maxRequests: ${e.rateLimit} }`),t.join(`
377
- `)}async function xr(e){let t=Fo(e.projectDir,"packages/config/src/pricing.ts"),r=e.defaultCurrency;if(e.paymentProvider==="none"){let f=`import type { PricingConfig } from "@repo/config/types";
375
+ updaterCacheDirName: ${f.protocol}-updater
376
+ `)}import{join as Bo}from"path";function Go(e){return e==="stripe"?' stripePriceId: "",':' polarProductId: "",'}function Gt(e){let t=[];return e.withCredits&&(t.push(` credits: ${e.credits},`),t.push(" creditInterval: 30,")),t.push(` apiRateLimit: { maxRequests: ${e.rateLimit} }`),t.join(`
377
+ `)}async function xr(e){let t=Bo(e.projectDir,"packages/config/src/pricing.ts"),r=e.defaultCurrency;if(e.paymentProvider==="none"){let u=`import type { PricingConfig } from "@repo/config/types";
378
378
 
379
379
  export const pricingConfig: PricingConfig = {
380
380
  defaultPlan: "free",
@@ -403,7 +403,7 @@ export const pricingConfig: PricingConfig = {
403
403
  credits: { enabled: false },
404
404
  products: { enabled: false, items: [] }
405
405
  };
406
- `;await l(t,f);return}let n=e.paymentProvider,o=Bo(n),i=e.credits,s=Gt({credits:5,rateLimit:100,withCredits:i}),a=Gt({credits:10,rateLimit:1e3,withCredits:i}),d=Gt({credits:50,rateLimit:5e3,withCredits:i}),m=`import type { PricingConfig } from "@repo/config/types";
406
+ `;await c(t,u);return}let n=e.paymentProvider,o=Go(n),i=e.credits,s=Gt({credits:5,rateLimit:100,withCredits:i}),a=Gt({credits:10,rateLimit:1e3,withCredits:i}),p=Gt({credits:50,rateLimit:5e3,withCredits:i}),m=`import type { PricingConfig } from "@repo/config/types";
407
407
 
408
408
  export const pricingConfig: PricingConfig = {
409
409
  defaultPlan: "free",
@@ -484,7 +484,7 @@ ${o}
484
484
  anchorAmounts: { ${r}: 349 }
485
485
  }
486
486
  ],
487
- ${d}
487
+ ${p}
488
488
  }
489
489
  ],
490
490
  ${i?" credits: { enabled: true }":" credits: { enabled: false }"},
@@ -493,11 +493,11 @@ ${i?" credits: { enabled: true }":" credits: { enabled: false }"},
493
493
  items: []
494
494
  }
495
495
  };
496
- `;await l(t,m)}var Go={smtp:[{key:"SMTP_HOST",defaultValue:"localhost"},{key:"SMTP_PORT",defaultValue:"1025"}],ses:[{key:"AMAZON_SES_REGION",comment:"# TODO: Configure Amazon SES credentials (e.g. us-east-1)"},{key:"AMAZON_SES_KEY"},{key:"AMAZON_SES_SECRET"}],resend:[{key:"RESEND_API_KEY",comment:"# TODO: Add your Resend API key"}]};function Ko(e){let t=ne[e];return t.envVars.map((r,n)=>({key:r.name,...n===0?{comment:`# TODO: Add your ${t.label} OAuth credentials`}:{}}))}var zo={stripe:[{key:"STRIPE_SECRET_KEY",comment:"# TODO: Add your Stripe keys"},{key:"STRIPE_WEBHOOK_SECRET"}],polar:[{key:"POLAR_ACCESS_TOKEN",comment:"# TODO: Add your Polar keys"},{key:"POLAR_WEBHOOK_SECRET"}]};function Ue(e,t){return t?e.map(r=>{let n=t[r.key];return n?{...r,defaultValue:n,comment:void 0}:r}):e}function Me(e,t){for(let r of e)r.comment&&t.push(r.comment),r.defaultValue!==void 0?t.push(`${r.key}=${r.defaultValue}`):t.push(`#${r.key}=`)}function Kt(e){return Array.from(crypto.getRandomValues(new Uint8Array(e))).map(t=>t.toString(16).padStart(2,"0")).join("")}function Cr(e){return e.architecture==="fullstack"?{apiUrl:"http://localhost:3000/api",baseUrl:"http://localhost:3000"}:{apiUrl:"http://localhost:3010",baseUrl:"http://localhost:3000"}}function Ho(e){let{architecture:t,deploymentTarget:r}=e;return t==="fullstack"?r==="vercel"?{frontend:"https://your-app.vercel.app",backend:"https://your-app.vercel.app"}:r==="node"?{frontend:"https://your-app.example.com",backend:"https://your-app.example.com"}:null:{frontend:"https://your-app.example.com",backend:"https://your-app.example.com/api"}}function kt(e,t,r,n){e.push(n==="example"?`${t}=`:`${t}=${r}`)}function Dr(e,t){let r=t==="example"?void 0:e.credentials,{apiUrl:n,baseUrl:o}=Cr(e),i=[];e.architecture==="separate"?i.push("# API Configuration","# Standalone backend's own URL (the frontend reaches it via the public var above).",`API_URL=${n}`,`BASE_URL=${o}`):i.push("# App","# (API_URL is derived from the frontend's *_PUBLIC_API_URL by the runtime env schema.)",`BASE_URL=${o}`),i.push("","# Database"),Me(Ue(V[e.databaseProvider].envVars,r),i),i.push("","# Cache"),Me(Ue(ee[e.cacheProvider].envVars,r),i),i.push("","# Authentication"),t==="example"&&i.push("# Generate a strong secret, e.g. `openssl rand -hex 32`"),kt(i,"BETTER_AUTH_SECRET",crypto.randomUUID(),t),i.push("","# Content API (random secret; required when contentApi feature is enabled in @repo/config)"),kt(i,"CONTENT_API_KEY",Kt(32),t),i.push("","# Job Queue - Inngest","INNGEST_APP_ID=api"),kt(i,"INNGEST_EVENT_KEY",Kt(32),t),kt(i,"INNGEST_SIGNING_KEY",Kt(32),t),i.push("INNGEST_BASE_URL=http://127.0.0.1:8288"),e.architecture==="separate"&&(i.push("","# API Port (standalone backend)","API_PORT=3010"),i.push("","# CORS + cross-subdomain cookies (production only - leave unset for local dev)","# TRUSTED_ORIGINS=https://your-app.example.com","# AUTH_COOKIE_DOMAIN=.example.com"));let s=Go[e.emailProvider];if(s&&(i.push("","# Email"),Me(Ue(s,r),i)),e.paymentProvider!=="none"){let a=zo[e.paymentProvider];a&&(i.push("","# Payment"),Me(Ue(a,r),i))}if(e.socialProviders.length>0){i.push("","# Social auth");for(let a of e.socialProviders)Me(Ue(Ko(a),r),i)}return e.demo&&(i.push("","# Captcha (Cloudflare Turnstile)"),Me(Ue([{key:"TURNSTILE_SECRET_KEY",comment:"# TODO: Add your Cloudflare Turnstile secret key"}],r),i)),i.push("","# Translations - OpenRouter (optional)","# Set to auto-translate en/* into your other locales with `pnpm translate`.","# The pre-commit hook runs it on commit; without a key it skips and untranslated","# locales fall back to the default locale. Get a key: https://openrouter.ai/keys","#OPENROUTER_API_KEY="),i.push("","# AI base - provider API keys (optional)","# Enable config.ai in packages/config and set the key for your chosen provider.","# OPENROUTER_API_KEY above also works as an AI provider key.","#ANTHROPIC_API_KEY=","#OPENAI_API_KEY="),i.push(""),i.join(`
497
- `)}function Yo(e){let{apiUrl:t}=Cr(e),r=e.frontend==="nextjs"?"NEXT_PUBLIC_API_URL":"NUXT_PUBLIC_API_URL",n=["# API Configuration",`${r}=${t}`],o=Ho(e);return o&&e.architecture==="separate"&&n.push("","# Production (uncomment and replace with your deployed hostnames):",`# ${r}=${o.backend}`),n.push(""),n.join(`
498
- `)}async function Nr(e){let t=Yo(e);await l(`${e.projectDir}/.env`,t+`
499
- `+Dr(e,"env")),await l(`${e.projectDir}/.env.example`,t+`
500
- `+Dr(e,"example"))}import{join as qo}from"path";async function Lr(e){let t=[...e.dockerServices];if(V[e.databaseProvider].managed&&(t=t.filter(i=>i!=="postgres")),ee[e.cacheProvider].managed&&(t=t.filter(i=>i!=="redis")),t.length===0)return!1;let r=[],n=[];t.includes("postgres")&&(r.push(` postgres:
496
+ `;await c(t,m)}var Ko={smtp:[{key:"SMTP_HOST",defaultValue:"localhost"},{key:"SMTP_PORT",defaultValue:"1025"}],ses:[{key:"AMAZON_SES_REGION",comment:"# TODO: Configure Amazon SES credentials (e.g. us-east-1)"},{key:"AMAZON_SES_KEY"},{key:"AMAZON_SES_SECRET"}],resend:[{key:"RESEND_API_KEY",comment:"# TODO: Add your Resend API key"}]};function zo(e){let t=oe[e];return t.envVars.map((r,n)=>({key:r.name,...n===0?{comment:`# TODO: Add your ${t.label} OAuth credentials`}:{}}))}var Ho={stripe:[{key:"STRIPE_SECRET_KEY",comment:"# TODO: Add your Stripe keys"},{key:"STRIPE_WEBHOOK_SECRET"}],polar:[{key:"POLAR_ACCESS_TOKEN",comment:"# TODO: Add your Polar keys"},{key:"POLAR_WEBHOOK_SECRET"}]};function Ue(e,t){return t?e.map(r=>{let n=t[r.key];return n?{...r,defaultValue:n,comment:void 0}:r}):e}function Me(e,t){for(let r of e)r.comment&&t.push(r.comment),r.defaultValue!==void 0?t.push(`${r.key}=${r.defaultValue}`):t.push(`#${r.key}=`)}function Kt(e){return Array.from(crypto.getRandomValues(new Uint8Array(e))).map(t=>t.toString(16).padStart(2,"0")).join("")}function Cr(e){return e.architecture==="fullstack"?{apiUrl:"http://localhost:3000/api",baseUrl:"http://localhost:3000"}:{apiUrl:"http://localhost:3010",baseUrl:"http://localhost:3000"}}function Yo(e){let{architecture:t,deploymentTarget:r}=e;return t==="fullstack"?r==="vercel"?{frontend:"https://your-app.vercel.app",backend:"https://your-app.vercel.app"}:r==="node"?{frontend:"https://your-app.example.com",backend:"https://your-app.example.com"}:null:{frontend:"https://your-app.example.com",backend:"https://your-app.example.com/api"}}function St(e,t,r,n){e.push(n==="example"?`${t}=`:`${t}=${r}`)}function Dr(e,t){let r=t==="example"?void 0:e.credentials,{apiUrl:n,baseUrl:o}=Cr(e),i=[];e.architecture==="separate"?i.push("# API Configuration","# Standalone backend's own URL (the frontend reaches it via the public var above).",`API_URL=${n}`,`BASE_URL=${o}`):i.push("# App","# (API_URL is derived from the frontend's *_PUBLIC_API_URL by the runtime env schema.)",`BASE_URL=${o}`),i.push("","# Database"),Me(Ue(F[e.databaseProvider].envVars,r),i),i.push("","# Cache"),Me(Ue(ee[e.cacheProvider].envVars,r),i),i.push("","# Authentication"),t==="example"&&i.push("# Generate a strong secret, e.g. `openssl rand -hex 32`"),St(i,"BETTER_AUTH_SECRET",crypto.randomUUID(),t),i.push("","# Content API (random secret; required when contentApi feature is enabled in @repo/config)"),St(i,"CONTENT_API_KEY",Kt(32),t),i.push("","# Job Queue - Inngest","INNGEST_APP_ID=api"),St(i,"INNGEST_EVENT_KEY",Kt(32),t),St(i,"INNGEST_SIGNING_KEY",Kt(32),t),i.push("INNGEST_BASE_URL=http://127.0.0.1:8288"),e.architecture==="separate"&&(i.push("","# API Port (standalone backend)","API_PORT=3010"),i.push("","# CORS + cross-subdomain cookies (production only - leave unset for local dev)","# TRUSTED_ORIGINS=https://your-app.example.com","# AUTH_COOKIE_DOMAIN=.example.com"));let s=Ko[e.emailProvider];if(s&&(i.push("","# Email"),Me(Ue(s,r),i)),e.paymentProvider!=="none"){let a=Ho[e.paymentProvider];a&&(i.push("","# Payment"),Me(Ue(a,r),i))}if(e.socialProviders.length>0){i.push("","# Social auth");for(let a of e.socialProviders)Me(Ue(zo(a),r),i)}return e.demo&&(i.push("","# Captcha (Cloudflare Turnstile)"),Me(Ue([{key:"TURNSTILE_SECRET_KEY",comment:"# TODO: Add your Cloudflare Turnstile secret key"}],r),i)),i.push("","# Translations - OpenRouter (optional)","# Set to auto-translate en/* into your other locales with `pnpm translate`.","# The pre-commit hook runs it on commit; without a key it skips and untranslated","# locales fall back to the default locale. Get a key: https://openrouter.ai/keys","#OPENROUTER_API_KEY="),i.push("","# AI base - provider API keys (optional)","# Enable config.ai in packages/config and set the key for your chosen provider.","# OPENROUTER_API_KEY above also works as an AI provider key.","#ANTHROPIC_API_KEY=","#OPENAI_API_KEY="),i.push(""),i.join(`
497
+ `)}function qo(e){let{apiUrl:t}=Cr(e),r=e.frontend==="nextjs"?"NEXT_PUBLIC_API_URL":"NUXT_PUBLIC_API_URL",n=["# API Configuration",`${r}=${t}`],o=Yo(e);return o&&e.architecture==="separate"&&n.push("","# Production (uncomment and replace with your deployed hostnames):",`# ${r}=${o.backend}`),n.push(""),n.join(`
498
+ `)}async function Nr(e){let t=qo(e);await c(`${e.projectDir}/.env`,t+`
499
+ `+Dr(e,"env")),await c(`${e.projectDir}/.env.example`,t+`
500
+ `+Dr(e,"example"))}import{join as Jo}from"path";async function Lr(e){let t=[...e.dockerServices];if(F[e.databaseProvider].managed&&(t=t.filter(i=>i!=="postgres")),ee[e.cacheProvider].managed&&(t=t.filter(i=>i!=="redis")),t.length===0)return!1;let r=[],n=[];t.includes("postgres")&&(r.push(` postgres:
501
501
  image: postgres:18-alpine
502
502
  ports:
503
503
  - "\${POSTGRES_PORT:-5432}:5432"
@@ -541,8 +541,8 @@ ${r.join(`
541
541
  volumes:
542
542
  ${n.join(`
543
543
  `)}
544
- `),await l(qo(e.projectDir,"infra/docker-compose.yml"),o),!0}import{join as Jo}from"path";function Wo(e){let t=[];e.architecture==="separate"&&t.push({type:"node-terminal",request:"launch",name:"Backend",command:"pnpm dev",cwd:"${workspaceFolder}/apps/backend",skipFiles:["<node_internals>/**"],env:{NODE_ENV:"development"}});let r=e.frontend==="nextjs"?"Next.js":"Nuxt";if(t.push({type:"node-terminal",request:"launch",name:r,command:"pnpm dev",cwd:`\${workspaceFolder}/apps/web-${e.frontend==="nextjs"?"next":"nuxt"}`,skipFiles:["<node_internals>/**"],env:{NODE_ENV:"development"}}),e.docs&&t.push({type:"node-terminal",request:"launch",name:"Docs",command:"pnpm dev",cwd:"${workspaceFolder}/apps/docs",skipFiles:["<node_internals>/**"],env:{NODE_ENV:"development"}}),e.desktop){let n=e.architecture==="separate"?"http://localhost:3010":"http://localhost:3000/api";t.push({type:"node-terminal",request:"launch",name:"Desktop",command:"pnpm dev",cwd:"${workspaceFolder}/apps/desktop",skipFiles:["<node_internals>/**"],env:{NODE_ENV:"development",BASE_URL:"http://localhost:3000",PUBLIC_API_URL:n}})}if(t.push({type:"node-terminal",request:"launch",name:"Inngest",command:"pnpm dev:inngest",cwd:"${workspaceFolder}",skipFiles:["<node_internals>/**"],env:{NODE_ENV:"development"}}),t.push({type:"node-terminal",request:"launch",name:"Mail",command:"pnpm dev:mail",cwd:"${workspaceFolder}",skipFiles:["<node_internals>/**"],env:{NODE_ENV:"development"}}),e.paymentProvider==="stripe"){let n=e.architecture==="separate"?"localhost:3010/auth/stripe/webhook":"localhost:3000/api/auth/stripe/webhook";t.push({type:"node-terminal",request:"launch",name:"Stripe",command:`stripe listen --forward-to ${n}`,cwd:"${workspaceFolder}",skipFiles:["<node_internals>/**"]})}return t}function Xo(e){let t=e.frontend==="nextjs"?"Next.js":"Nuxt",r=[];return e.architecture==="separate"?r.push({name:`Dev (${t} + Backend + Inngest)`,configurations:["Backend",t,"Inngest"]}):r.push({name:`Dev (${t} + Inngest)`,configurations:[t,"Inngest"]}),r}async function $r(e){let t={version:"0.2.0",configurations:Wo(e),compounds:Xo(e)};await l(Jo(e.projectDir,".vscode/launch.json"),JSON.stringify(t,null," ")+`
545
- `)}import{join as Zo}from"path";async function jr(e){if(e.architecture!=="separate")return;await l(Zo(e.projectDir,"apps/backend/src/index.ts"),`import { serve } from "@hono/node-server";
544
+ `),await c(Jo(e.projectDir,"infra/docker-compose.yml"),o),!0}import{join as Wo}from"path";function Xo(e){let t=[];e.architecture==="separate"&&t.push({type:"node-terminal",request:"launch",name:"Backend",command:"pnpm dev",cwd:"${workspaceFolder}/apps/backend",skipFiles:["<node_internals>/**"],env:{NODE_ENV:"development"}});let r=e.frontend==="nextjs"?"Next.js":"Nuxt";if(t.push({type:"node-terminal",request:"launch",name:r,command:"pnpm dev",cwd:`\${workspaceFolder}/apps/web-${e.frontend==="nextjs"?"next":"nuxt"}`,skipFiles:["<node_internals>/**"],env:{NODE_ENV:"development"}}),e.docs&&t.push({type:"node-terminal",request:"launch",name:"Docs",command:"pnpm dev",cwd:"${workspaceFolder}/apps/docs",skipFiles:["<node_internals>/**"],env:{NODE_ENV:"development"}}),e.desktop){let n=e.architecture==="separate"?"http://localhost:3010":"http://localhost:3000/api";t.push({type:"node-terminal",request:"launch",name:"Desktop",command:"pnpm dev",cwd:"${workspaceFolder}/apps/desktop",skipFiles:["<node_internals>/**"],env:{NODE_ENV:"development",BASE_URL:"http://localhost:3000",PUBLIC_API_URL:n}})}if(t.push({type:"node-terminal",request:"launch",name:"Inngest",command:"pnpm dev:inngest",cwd:"${workspaceFolder}",skipFiles:["<node_internals>/**"],env:{NODE_ENV:"development"}}),t.push({type:"node-terminal",request:"launch",name:"Mail",command:"pnpm dev:mail",cwd:"${workspaceFolder}",skipFiles:["<node_internals>/**"],env:{NODE_ENV:"development"}}),e.paymentProvider==="stripe"){let n=e.architecture==="separate"?"localhost:3010/auth/stripe/webhook":"localhost:3000/api/auth/stripe/webhook";t.push({type:"node-terminal",request:"launch",name:"Stripe",command:`stripe listen --forward-to ${n}`,cwd:"${workspaceFolder}",skipFiles:["<node_internals>/**"]})}return t}function Zo(e){let t=e.frontend==="nextjs"?"Next.js":"Nuxt",r=[];return e.architecture==="separate"?r.push({name:`Dev (${t} + Backend + Inngest)`,configurations:["Backend",t,"Inngest"]}):r.push({name:`Dev (${t} + Inngest)`,configurations:[t,"Inngest"]}),r}async function $r(e){let t={version:"0.2.0",configurations:Xo(e),compounds:Zo(e)};await c(Wo(e.projectDir,".vscode/launch.json"),JSON.stringify(t,null," ")+`
545
+ `)}import{join as Qo}from"path";async function jr(e){if(e.architecture!=="separate")return;await c(Qo(e.projectDir,"apps/backend/src/index.ts"),`import { serve } from "@hono/node-server";
546
546
  import app from "@repo/api";
547
547
  import { closeRedis, env, logger } from "@repo/runtime";
548
548
 
@@ -573,9 +573,9 @@ bootstrap().catch((error) => {
573
573
  logger.error("[Backend] Fatal error", error);
574
574
  process.exit(1);
575
575
  });
576
- `)}import{readFile as Qo}from"fs/promises";import{join as ei}from"path";var ti=`export * from "./db/auth";
576
+ `)}import{readFile as ei}from"fs/promises";import{join as ti}from"path";var ri=`export * from "./db/auth";
577
577
  export * from "./db/schema";
578
- export type { User, Account, Organization, Member } from "./db/auth";`;async function Ur(e){let t=ei(e.projectDir,"packages/database/src/index.ts"),r=ti;try{let o=await Qo(t,"utf-8"),i=ri(o);i&&(r=i)}catch{}let n;switch(e.databaseProvider){case"postgres":n=ni(r);break;case"neon":n=oi(r);break;case"supabase":n=ii(r);break}await l(t,n)}function ri(e){return e.split(`
578
+ export type { User, Account, Organization, Member } from "./db/auth";`;async function Ur(e){let t=ti(e.projectDir,"packages/database/src/index.ts"),r=ri;try{let o=await ei(t,"utf-8"),i=ni(o);i&&(r=i)}catch{}let n;switch(e.databaseProvider){case"postgres":n=oi(r);break;case"neon":n=ii(r);break;case"supabase":n=si(r);break}await c(t,n)}function ni(e){return e.split(`
579
579
  `).filter(n=>n.startsWith("export type ")||n.startsWith("export * from")).join(`
580
580
  `)}var zt=`
581
581
  /** Extract affected-row count from a delete/update result (works for pg + postgres-js). */
@@ -584,7 +584,7 @@ export function affectedRowCount(result: unknown): number {
584
584
  const r = result as { rowCount?: number; count?: number };
585
585
  return r.rowCount ?? r.count ?? 0;
586
586
  }
587
- `;function ni(e){return`import { drizzle } from "drizzle-orm/node-postgres";
587
+ `;function oi(e){return`import { drizzle } from "drizzle-orm/node-postgres";
588
588
  import { z } from "zod";
589
589
  import * as authSchema from "./db/auth";
590
590
  import * as appSchema from "./db/schema";
@@ -599,7 +599,7 @@ export const db = drizzle(parsed.data.DATABASE_URL, { schema });
599
599
  export const pool = db.$client;
600
600
 
601
601
  ${e}
602
- ${zt}`}function oi(e){return`import { neon } from "@neondatabase/serverless";
602
+ ${zt}`}function ii(e){return`import { neon } from "@neondatabase/serverless";
603
603
  import { drizzle } from "drizzle-orm/neon-http";
604
604
  import { z } from "zod";
605
605
  import * as authSchema from "./db/auth";
@@ -615,7 +615,7 @@ const sql = neon(parsed.data.DATABASE_URL);
615
615
  export const db = drizzle(sql, { schema });
616
616
 
617
617
  ${e}
618
- ${zt}`}function ii(e){return`import { drizzle } from "drizzle-orm/postgres-js";
618
+ ${zt}`}function si(e){return`import { drizzle } from "drizzle-orm/postgres-js";
619
619
  import postgres from "postgres";
620
620
  import { z } from "zod";
621
621
  import * as authSchema from "./db/auth";
@@ -631,7 +631,7 @@ const client = postgres(parsed.data.DATABASE_URL);
631
631
  export const db = drizzle(client, { schema });
632
632
 
633
633
  ${e}
634
- ${zt}`}import{join as bt}from"path";async function Mr(e){switch(e.cacheProvider){case"redis":await si(e),await ai(e);break;case"upstash":await ci(e),await li(e);break}}async function si(e){await l(bt(e.projectDir,"packages/runtime/src/redis.ts"),`import type { Store } from "hono-rate-limiter";
634
+ ${zt}`}import{join as wt}from"path";async function Mr(e){switch(e.cacheProvider){case"redis":await ai(e),await ci(e);break;case"upstash":await li(e),await pi(e);break}}async function ai(e){await c(wt(e.projectDir,"packages/runtime/src/redis.ts"),`import type { Store } from "hono-rate-limiter";
635
635
  import { Redis } from "ioredis";
636
636
  import { RedisStore, type RedisReply } from "rate-limit-redis";
637
637
  import { env } from "./env";
@@ -755,7 +755,7 @@ export async function closeRedis() {
755
755
  closed = true;
756
756
  }
757
757
  }
758
- `)}async function ai(e){await l(bt(e.projectDir,"packages/runtime/src/mutex.ts"),`import { Mutex } from "redis-semaphore";
758
+ `)}async function ci(e){await c(wt(e.projectDir,"packages/runtime/src/mutex.ts"),`import { Mutex } from "redis-semaphore";
759
759
  import { redis } from "./redis";
760
760
 
761
761
  export class MutexTimeoutError extends Error {
@@ -792,7 +792,7 @@ export async function withMutex<T>(
792
792
  await mutex.release();
793
793
  }
794
794
  }
795
- `)}async function ci(e){await l(bt(e.projectDir,"packages/runtime/src/redis.ts"),`import { Redis } from "@upstash/redis";
795
+ `)}async function li(e){await c(wt(e.projectDir,"packages/runtime/src/redis.ts"),`import { Redis } from "@upstash/redis";
796
796
  import type { Store } from "hono-rate-limiter";
797
797
  import { env } from "./env";
798
798
 
@@ -905,7 +905,7 @@ export const limiterStore: Store = createLimiterStore();
905
905
 
906
906
  /** No persistent connection to close with Upstash REST. */
907
907
  export async function closeRedis(): Promise<void> {}
908
- `)}async function li(e){await l(bt(e.projectDir,"packages/runtime/src/mutex.ts"),`import { Lock } from "@upstash/lock";
908
+ `)}async function pi(e){await c(wt(e.projectDir,"packages/runtime/src/mutex.ts"),`import { Lock } from "@upstash/lock";
909
909
  import { redis } from "./redis";
910
910
 
911
911
  export class MutexTimeoutError extends Error {
@@ -950,7 +950,7 @@ export async function withMutex<T>(
950
950
  await lock.release();
951
951
  }
952
952
  }
953
- `)}async function Vr(e){await l(`${e.projectDir}/packages/runtime/src/env.ts`,pi(e))}function pi(e){return`import { z } from "zod";
953
+ `)}async function Vr(e){await c(`${e.projectDir}/packages/runtime/src/env.ts`,di(e))}function di(e){return`import { z } from "zod";
954
954
 
955
955
  const EnvSchema = z.object({
956
956
  NODE_ENV: z.enum(["development", "production"]).default("development"),
@@ -1069,13 +1069,13 @@ export const env = (() => {
1069
1069
  const parsedApiUrl = new URL(env.API_URL);
1070
1070
  export const apiBasePath = parsedApiUrl.pathname === "/" ? "" : parsedApiUrl.pathname;
1071
1071
  export const apiOrigin = parsedApiUrl.origin;
1072
- `}import{readFile as di}from"fs/promises";import{join as te}from"path";var wt={"@upstash/redis":"^1.37.0","@upstash/lock":"^0.2.1","@neondatabase/serverless":"^1.0.1",postgres:"^3.4.7"};async function Fe(e){let t=await di(e,"utf-8");return JSON.parse(t)}async function Be(e,t){await l(e,JSON.stringify(t,null," ")+`
1073
- `)}function rt(e,t){for(let r of t)delete e.dependencies?.[r],delete e.devDependencies?.[r]}function Ve(e,t,r,n=!1){let o=n?"devDependencies":"dependencies";e[o]||(e[o]={}),e[o][t]=r}async function Fr(e){await ui(e),await mi(e),await fi(e),await gi(e),e.frontend==="nextjs"?await yi(e):await hi(e)}async function ui(e){let t=te(e.projectDir,"packages/api/package.json"),r=await Fe(t);rt(r,["sharp","@types/sharp"]),await Be(t,r)}async function mi(e){let t=te(e.projectDir,"packages/runtime/package.json"),r=await Fe(t);e.cacheProvider==="upstash"&&(rt(r,["ioredis","rate-limit-redis","redis-semaphore"]),Ve(r,"@upstash/redis",wt["@upstash/redis"]),Ve(r,"@upstash/lock",wt["@upstash/lock"])),await Be(t,r)}async function fi(e){let t=te(e.projectDir,"packages/database/package.json"),r=await Fe(t);e.databaseProvider==="neon"?(rt(r,["pg","@types/pg"]),Ve(r,"@neondatabase/serverless",wt["@neondatabase/serverless"])):e.databaseProvider==="supabase"&&(rt(r,["pg","@types/pg"]),Ve(r,"postgres",wt.postgres)),await Be(t,r)}async function gi(e){if(e.architecture!=="separate")return;let t=te(e.projectDir,"apps/backend/package.json"),r=await Fe(t);e.deploymentTarget!=="node"&&rt(r,["@hono/node-server"]),await Be(t,r)}async function hi(e){if(e.architecture!=="separate")return;let t=te(e.projectDir,"apps/web-nuxt");await Bt(te(t,"server/api/[...paths].ts"),t);let r=te(t,"package.json"),n=await Fe(r),o=n.dependencies?.["@repo/api"];o&&(delete n.dependencies?.["@repo/api"],Ve(n,"@repo/api",o,!0)),await Be(r,n)}async function yi(e){if(e.architecture!=="separate")return;let t=te(e.projectDir,"apps/web-next");await Bt(te(t,"app/api/[[...rest]]/route.ts"),t);let r=te(t,"package.json"),n=await Fe(r),o=n.dependencies?.["@repo/api"];o&&(delete n.dependencies?.["@repo/api"],Ve(n,"@repo/api",o,!0)),await Be(r,n)}import{readFile as Gr}from"fs/promises";import{join as Kr}from"path";async function zr(e){let t=Kr(e.projectDir,"turbo.json"),r;try{r=await Gr(t,"utf-8")}catch{return}let n=JSON.parse(r),o=n.tasks?.build;if(!o)return;let i=e.frontend==="nextjs"?"NUXT_PUBLIC_*":"NEXT_PUBLIC_*",s=e.frontend==="nextjs"?new Set([".nuxt/**",".output/**"]):new Set([".next/**","!.next/cache/**"]),a=!1;if(Array.isArray(o.env)){let d=o.env.filter(u=>u!==i);d.length!==o.env.length&&(o.env=d,a=!0)}if(Array.isArray(o.outputs)){let d=o.outputs.filter(u=>!s.has(u));d.length!==o.outputs.length&&(o.outputs=d,a=!0)}a&&await l(t,JSON.stringify(n,null," ")+`
1074
- `)}var vi=["base.json","node.json","next.json"],Br="GenerateSaaS ";async function Hr(e){if(!e.demo)for(let t of vi){let r=Kr(e.projectDir,"tooling/typescript",t),n;try{n=await Gr(r,"utf-8")}catch{continue}let o=JSON.parse(n);typeof o.display!="string"||!o.display.startsWith(Br)||(o.display=o.display.slice(Br.length),await l(r,JSON.stringify(o,null," ")+`
1075
- `))}}import{readFile as Si,rm as Ei}from"fs/promises";import{existsSync as Yr}from"fs";import{join as qr}from"path";async function Jr(e){if(e.frontend==="nuxt")return;let t=qr(e.projectDir,"packages/i18n/package.json");if(!Yr(t))throw new Error(`pruneI18nNuxt: expected ${t} to exist - did the i18n package move?`);let r=JSON.parse(await Si(t,"utf-8")),n=!!(r.exports?.["./module"]??r.exports?.["./nuxt"]),o=!1;if(r.exports)for(let s of["./module","./nuxt"])s in r.exports&&(delete r.exports[s],o=!0);r.devDependencies&&"@nuxt/kit"in r.devDependencies&&(delete r.devDependencies["@nuxt/kit"],o=!0),o&&await l(t,JSON.stringify(r,null," ")+`
1076
- `);let i=qr(e.projectDir,"packages/i18n/nuxt");if(n&&!Yr(i))throw new Error(`pruneI18nNuxt: packages/i18n declares a Nuxt export surface but ${i} is missing - did the i18n Nuxt module move?`);await Ei(i,{recursive:!0,force:!0})}import{readFile as Wr,rm as ki}from"fs/promises";import{join as Ht}from"path";async function Xr(e){e.cacheProvider==="upstash"&&await Promise.all([bi(e.projectDir),wi(e.projectDir),ki(Ht(e.projectDir,"packages/runtime/tests/redis.test.ts"),{force:!0})])}async function bi(e){let t=Ht(e,"packages/runtime/tests/setup.ts"),r;try{r=await Wr(t,"utf-8")}catch{return}let n=r.replace(/\tREDIS_URL:\s*"[^"]*",?\n/,` UPSTASH_REDIS_REST_URL: "https://test.upstash.io",
1072
+ `}import{readFile as ui}from"fs/promises";import{join as te}from"path";var Et={"@upstash/redis":"^1.37.0","@upstash/lock":"^0.2.1","@neondatabase/serverless":"^1.0.1",postgres:"^3.4.7"};async function Fe(e){let t=await ui(e,"utf-8");return JSON.parse(t)}async function Be(e,t){await c(e,JSON.stringify(t,null," ")+`
1073
+ `)}function rt(e,t){for(let r of t)delete e.dependencies?.[r],delete e.devDependencies?.[r]}function Ve(e,t,r,n=!1){let o=n?"devDependencies":"dependencies";e[o]||(e[o]={}),e[o][t]=r}async function Fr(e){await mi(e),await fi(e),await gi(e),await hi(e),e.frontend==="nextjs"?await vi(e):await yi(e)}async function mi(e){let t=te(e.projectDir,"packages/api/package.json"),r=await Fe(t);rt(r,["sharp","@types/sharp"]),await Be(t,r)}async function fi(e){let t=te(e.projectDir,"packages/runtime/package.json"),r=await Fe(t);e.cacheProvider==="upstash"&&(rt(r,["ioredis","rate-limit-redis","redis-semaphore"]),Ve(r,"@upstash/redis",Et["@upstash/redis"]),Ve(r,"@upstash/lock",Et["@upstash/lock"])),await Be(t,r)}async function gi(e){let t=te(e.projectDir,"packages/database/package.json"),r=await Fe(t);e.databaseProvider==="neon"?(rt(r,["pg","@types/pg"]),Ve(r,"@neondatabase/serverless",Et["@neondatabase/serverless"])):e.databaseProvider==="supabase"&&(rt(r,["pg","@types/pg"]),Ve(r,"postgres",Et.postgres)),await Be(t,r)}async function hi(e){if(e.architecture!=="separate")return;let t=te(e.projectDir,"apps/backend/package.json"),r=await Fe(t);e.deploymentTarget!=="node"&&rt(r,["@hono/node-server"]),await Be(t,r)}async function yi(e){if(e.architecture!=="separate")return;let t=te(e.projectDir,"apps/web-nuxt");await Bt(te(t,"server/api/[...paths].ts"),t);let r=te(t,"package.json"),n=await Fe(r),o=n.dependencies?.["@repo/api"];o&&(delete n.dependencies?.["@repo/api"],Ve(n,"@repo/api",o,!0)),await Be(r,n)}async function vi(e){if(e.architecture!=="separate")return;let t=te(e.projectDir,"apps/web-next");await Bt(te(t,"app/api/[[...rest]]/route.ts"),t);let r=te(t,"package.json"),n=await Fe(r),o=n.dependencies?.["@repo/api"];o&&(delete n.dependencies?.["@repo/api"],Ve(n,"@repo/api",o,!0)),await Be(r,n)}import{readFile as Gr}from"fs/promises";import{join as Kr}from"path";async function zr(e){let t=Kr(e.projectDir,"turbo.json"),r;try{r=await Gr(t,"utf-8")}catch{return}let n=JSON.parse(r),o=n.tasks?.build;if(!o)return;let i=e.frontend==="nextjs"?"NUXT_PUBLIC_*":"NEXT_PUBLIC_*",s=e.frontend==="nextjs"?new Set([".nuxt/**",".output/**"]):new Set([".next/**","!.next/cache/**"]),a=!1;if(Array.isArray(o.env)){let p=o.env.filter(f=>f!==i);p.length!==o.env.length&&(o.env=p,a=!0)}if(Array.isArray(o.outputs)){let p=o.outputs.filter(f=>!s.has(f));p.length!==o.outputs.length&&(o.outputs=p,a=!0)}a&&await c(t,JSON.stringify(n,null," ")+`
1074
+ `)}var bi=["base.json","node.json","next.json"],Br="GenerateSaaS ";async function Hr(e){if(!e.demo)for(let t of bi){let r=Kr(e.projectDir,"tooling/typescript",t),n;try{n=await Gr(r,"utf-8")}catch{continue}let o=JSON.parse(n);typeof o.display!="string"||!o.display.startsWith(Br)||(o.display=o.display.slice(Br.length),await c(r,JSON.stringify(o,null," ")+`
1075
+ `))}}import{readFile as ki,rm as Si}from"fs/promises";import{existsSync as Yr}from"fs";import{join as qr}from"path";async function Jr(e){if(e.frontend==="nuxt")return;let t=qr(e.projectDir,"packages/i18n/package.json");if(!Yr(t))throw new Error(`pruneI18nNuxt: expected ${t} to exist - did the i18n package move?`);let r=JSON.parse(await ki(t,"utf-8")),n=!!(r.exports?.["./module"]??r.exports?.["./nuxt"]),o=!1;if(r.exports)for(let s of["./module","./nuxt"])s in r.exports&&(delete r.exports[s],o=!0);r.devDependencies&&"@nuxt/kit"in r.devDependencies&&(delete r.devDependencies["@nuxt/kit"],o=!0),o&&await c(t,JSON.stringify(r,null," ")+`
1076
+ `);let i=qr(e.projectDir,"packages/i18n/nuxt");if(n&&!Yr(i))throw new Error(`pruneI18nNuxt: packages/i18n declares a Nuxt export surface but ${i} is missing - did the i18n Nuxt module move?`);await Si(i,{recursive:!0,force:!0})}import{readFile as Wr,rm as wi}from"fs/promises";import{join as Ht}from"path";async function Xr(e){e.cacheProvider==="upstash"&&await Promise.all([Ei(e.projectDir),Ai(e.projectDir),wi(Ht(e.projectDir,"packages/runtime/tests/redis.test.ts"),{force:!0})])}async function Ei(e){let t=Ht(e,"packages/runtime/tests/setup.ts"),r;try{r=await Wr(t,"utf-8")}catch{return}let n=r.replace(/\tREDIS_URL:\s*"[^"]*",?\n/,` UPSTASH_REDIS_REST_URL: "https://test.upstash.io",
1077
1077
  UPSTASH_REDIS_REST_TOKEN: "test-token",
1078
- `);n!==r&&await l(t,n)}async function wi(e){let t=Ht(e,"packages/api/tests/setup.ts"),r;try{r=await Wr(t,"utf-8")}catch{return}let n=r;n=n.replace(/\tREDIS_URL:\s*"[^"]*",?\n/g,` UPSTASH_REDIS_REST_URL: "https://test.upstash.io",
1078
+ `);n!==r&&await c(t,n)}async function Ai(e){let t=Ht(e,"packages/api/tests/setup.ts"),r;try{r=await Wr(t,"utf-8")}catch{return}let n=r;n=n.replace(/\tREDIS_URL:\s*"[^"]*",?\n/g,` UPSTASH_REDIS_REST_URL: "https://test.upstash.io",
1079
1079
  UPSTASH_REDIS_REST_TOKEN: "test-token",
1080
1080
  `),n=n.replace(/vi\.mock\("ioredis"[\s\S]*?\n\}\);\n\n?/,""),n=n.replace(/vi\.mock\("rate-limit-redis"[\s\S]*?\n\}\);\n\n?/,""),n=n.replace(/\t\t\tREDIS_URL:\s*"[^"]*",?\n/,` UPSTASH_REDIS_REST_URL: "https://test.upstash.io",
1081
1081
  UPSTASH_REDIS_REST_TOKEN: "test-token",
@@ -1096,14 +1096,20 @@ vi.mock("@upstash/lock", () => {
1096
1096
  return { Lock };
1097
1097
  });
1098
1098
 
1099
- $1`),n!==r&&await l(t,n)}import{readdir as Yt,readFile as z,rm as w}from"fs/promises";import{join as y}from"path";var Ai=new Set(["ci.yml","desktop-release.yml"]),Ii=["cli","cli:clean","demo:bench","playground:regen","playground:test","playground:test:units","playground:test:build"];async function Qr(e){let t=y(e.projectDir,".github/workflows"),r=await Yt(t).catch(()=>[]);for(let n of r)Ai.has(n)||await w(y(t,n),{recursive:!0,force:!0})}async function en(e){let t=y(e.projectDir,"package.json"),r=await z(t,"utf-8"),n=JSON.parse(r),o=!1;if(n.scripts){for(let i of Ii)i in n.scripts&&(delete n.scripts[i],o=!0);if(!dt(e))for(let i of["infra","infra:stop"])i in n.scripts&&(delete n.scripts[i],o=!0)}o&&await l(t,JSON.stringify(n,null," ")+`
1100
- `)}async function tn(e){let t=e.frontend==="nextjs"?"apps/web-nuxt":"apps/web-next";await w(y(e.projectDir,t),{recursive:!0,force:!0})}async function rn(e){e.docs||await w(y(e.projectDir,"apps/docs"),{recursive:!0})}async function nn(e){let t=e.frontend==="nextjs"?"docs/nuxt":"docs/next";await w(y(e.projectDir,t),{recursive:!0,force:!0}),await w(y(e.projectDir,"docs/index.mdx"))}async function on(e){if(e.desktop)return;await w(y(e.projectDir,"apps/desktop"),{recursive:!0});let t=y(e.projectDir,"packages/i18n/translations");for(let r of await Yt(t,{withFileTypes:!0}))r.isDirectory()&&await w(y(t,r.name,"desktop.json"),{force:!0});await w(y(e.projectDir,".github/workflows/desktop-release.yml"))}function $(e,t,r,n){let o=e.indexOf(t);if(o===-1)throw new Error(`stripDesktopAi: expected to find the ${n} seam, but it was missing (boilerplate drift).`);if(e.indexOf(t,o+t.length)!==-1)throw new Error(`stripDesktopAi: the ${n} seam appears more than once (boilerplate drift).`);return e.slice(0,o)+r+e.slice(o+t.length)}var Zr=/^[ \t]*(?:\/\/|\{\/\*) gsaas:ai-start(?: \*\/\})?[ \t]*\r?\n[\s\S]*?^[ \t]*(?:\/\/|\{\/\*) gsaas:ai-end(?: \*\/\})?[ \t]*\r?\n/gm;function Ge(e,t){if(e.match(Zr)===null)throw new Error(`stripDesktopAi: expected gsaas AI markers in ${t}, but found none (boilerplate drift).`);let r=e.replace(Zr,"");if(r.includes("gsaas:ai-start")||r.includes("gsaas:ai-end"))throw new Error(`stripDesktopAi: an unbalanced gsaas AI marker is left in ${t} (boilerplate drift).`);return r.replace(/\n{3,}/g,`
1101
-
1102
- `)}async function sn(e){if(!e.desktop||e.desktopAi)return;let t=e.projectDir;await w(y(t,"apps/desktop/src/main/ai"),{recursive:!0}),await w(y(t,"apps/desktop/resources/ai-skills"),{recursive:!0}),await w(y(t,"apps/desktop/resources/knowledge"),{recursive:!0}),await w(y(t,"apps/desktop/src/renderer/src/screens/agents"),{recursive:!0}),await w(y(t,"apps/desktop/src/renderer/src/screens/integrations"),{recursive:!0}),await w(y(t,"apps/desktop/src/renderer/src/lib/ai.ts")),await w(y(t,"apps/desktop/src/renderer/src/hooks/use-ai.ts")),await w(y(t,"apps/desktop/src/renderer/src/hooks/use-ai-scope.ts")),await w(y(t,"apps/desktop/src/renderer/src/screens/schedules.tsx")),await w(y(t,"apps/desktop/src/renderer/src/components/side-panel-dock.tsx")),await w(y(t,"apps/desktop/src/renderer/src/components/side-panel.tsx")),await w(y(t,"apps/desktop/src/renderer/src/config/side-panels.tsx")),await w(y(t,"apps/desktop/src/renderer/src/lib/side-panel-state.ts")),await w(y(t,"apps/desktop/tests/renderer/lib/side-panel-state.test.ts")),await w(y(t,"apps/desktop/src/renderer/src/lib/chat-prefs.ts")),await w(y(t,"apps/desktop/tests/renderer/lib/chat-prefs.test.ts")),await w(y(t,"apps/desktop/tests/main/ai"),{recursive:!0}),await w(y(t,"apps/desktop/tests/main/ai-import-boundary.test.ts")),await w(y(t,"apps/desktop/tests/renderer/lib/ai.test.ts"));let r=y(t,"apps/desktop/package.json"),n=JSON.parse(await z(r,"utf-8")),o=["@repo/ai","@anthropic-ai/claude-agent-sdk","@openai/codex-sdk","@modelcontextprotocol/sdk","cross-spawn"];for(let s of o){if(!n.dependencies||!(s in n.dependencies))throw new Error(`stripDesktopAi: expected ${s} in apps/desktop dependencies (boilerplate drift).`);delete n.dependencies[s]}await l(r,JSON.stringify(n,null," ")+`
1103
- `),await Pi(t);let i=y(t,"packages/i18n/translations");for(let s of await Yt(i,{withFileTypes:!0})){if(!s.isDirectory())continue;let a=y(i,s.name,"desktop.json"),d=await z(a,"utf-8").catch(()=>null);if(d===null)continue;let u=JSON.parse(d),m=!1;"agents"in u&&(delete u.agents,m=!0),"schedules"in u&&(delete u.schedules,m=!0),"integrations"in u&&(delete u.integrations,m=!0),m&&await l(a,JSON.stringify(u,null," ")+`
1104
- `)}}async function Pi(e){let t=y(e,"apps/desktop/src/main/ipc.ts"),r=Ge(await z(t,"utf-8"),"main/ipc.ts");await l(t,r);let n=y(e,"apps/desktop/src/main/config.ts"),o=await z(n,"utf-8");o=$(o,`,
1099
+ $1`),n!==r&&await c(t,n)}import{readdir as Yt,readFile as j,rm as S}from"fs/promises";import{join as g}from"path";var Ii=new Set(["ci.yml","desktop-release.yml"]),Pi=["cli","cli:clean","demo:bench","playground:regen","playground:test","playground:test:units","playground:test:build"];async function en(e){let t=g(e.projectDir,".github/workflows"),r=await Yt(t).catch(()=>[]);for(let n of r)Ii.has(n)||await S(g(t,n),{recursive:!0,force:!0})}async function tn(e){let t=g(e.projectDir,"package.json"),r=await j(t,"utf-8"),n=JSON.parse(r),o=!1;if(n.scripts){for(let i of Pi)i in n.scripts&&(delete n.scripts[i],o=!0);if(!dt(e))for(let i of["infra","infra:stop"])i in n.scripts&&(delete n.scripts[i],o=!0)}o&&await c(t,JSON.stringify(n,null," ")+`
1100
+ `)}async function rn(e){let t=e.frontend==="nextjs"?"apps/web-nuxt":"apps/web-next";await S(g(e.projectDir,t),{recursive:!0,force:!0})}async function nn(e){e.docs||await S(g(e.projectDir,"apps/docs"),{recursive:!0})}async function on(e){let t=e.frontend==="nextjs"?"docs/nuxt":"docs/next";if(await S(g(e.projectDir,t),{recursive:!0,force:!0}),await S(g(e.projectDir,"docs/index.mdx")),!e.desktop)await S(g(e.projectDir,"docs/desktop"),{recursive:!0,force:!0});else if(!e.desktopAi){await S(g(e.projectDir,"docs/desktop/ai"),{recursive:!0,force:!0});let r=g(e.projectDir,"docs/desktop/meta.json"),n=JSON.parse(await j(r,"utf-8"));n.pages=n.pages.filter(o=>o!=="ai"),await c(r,`${JSON.stringify(n,null,2)}
1101
+ `)}}async function sn(e){if(e.desktop)return;await S(g(e.projectDir,"apps/desktop"),{recursive:!0});let t=g(e.projectDir,"packages/i18n/translations");for(let r of await Yt(t,{withFileTypes:!0}))r.isDirectory()&&await S(g(t,r.name,"desktop.json"),{force:!0});await S(g(e.projectDir,".github/workflows/desktop-release.yml"))}var Zr=`
1102
+ # Local-embedding model binaries are fetched on install/build (see
1103
+ # scripts/fetch-embedding-model.mjs), never committed.
1104
+ resources/models/
1105
+ `;function Ti(e){if(!e.includes(Zr))throw new Error("stripDesktopAi: expected the resources/models .gitignore block in apps/desktop/.gitignore, but it was missing (boilerplate drift).");return e.replace(Zr,`
1106
+ `)}function D(e,t,r,n){let o=e.indexOf(t);if(o===-1)throw new Error(`stripDesktopAi: expected to find the ${n} seam, but it was missing (boilerplate drift).`);if(e.indexOf(t,o+t.length)!==-1)throw new Error(`stripDesktopAi: the ${n} seam appears more than once (boilerplate drift).`);return e.slice(0,o)+r+e.slice(o+t.length)}var Qr=/^[ \t]*(?:\/\/|\{\/\*) gsaas:ai-start(?: \*\/\})?[ \t]*\r?\n[\s\S]*?^[ \t]*(?:\/\/|\{\/\*) gsaas:ai-end(?: \*\/\})?[ \t]*\r?\n/gm;function Ge(e,t){if(e.match(Qr)===null)throw new Error(`stripDesktopAi: expected gsaas AI markers in ${t}, but found none (boilerplate drift).`);let r=e.replace(Qr,"");if(r.includes("gsaas:ai-start")||r.includes("gsaas:ai-end"))throw new Error(`stripDesktopAi: an unbalanced gsaas AI marker is left in ${t} (boilerplate drift).`);return r.replace(/\n{3,}/g,`
1107
+
1108
+ `)}async function an(e){if(!e.desktop||e.desktopAi)return;let t=e.projectDir;await S(g(t,"apps/desktop/src/main/ai"),{recursive:!0}),await S(g(t,"apps/desktop/resources/ai-skills"),{recursive:!0}),await S(g(t,"apps/desktop/resources/knowledge"),{recursive:!0}),await S(g(t,"apps/desktop/scripts/fetch-embedding-model.mjs")),await S(g(t,"apps/desktop/resources/models"),{recursive:!0,force:!0}),await S(g(t,"apps/desktop/src/renderer/src/screens/agents"),{recursive:!0}),await S(g(t,"apps/desktop/src/renderer/src/screens/integrations"),{recursive:!0}),await S(g(t,"apps/desktop/src/renderer/src/lib/ai.ts")),await S(g(t,"apps/desktop/src/renderer/src/hooks/use-ai.ts")),await S(g(t,"apps/desktop/src/renderer/src/hooks/use-ai-scope.ts")),await S(g(t,"apps/desktop/src/renderer/src/screens/schedules.tsx")),await S(g(t,"apps/desktop/src/renderer/src/components/side-panel-dock.tsx")),await S(g(t,"apps/desktop/src/renderer/src/components/side-panel.tsx")),await S(g(t,"apps/desktop/src/renderer/src/config/side-panels.tsx")),await S(g(t,"apps/desktop/src/renderer/src/lib/side-panel-state.ts")),await S(g(t,"apps/desktop/tests/renderer/lib/side-panel-state.test.ts")),await S(g(t,"apps/desktop/src/renderer/src/lib/chat-prefs.ts")),await S(g(t,"apps/desktop/tests/renderer/lib/chat-prefs.test.ts")),await S(g(t,"apps/desktop/tests/main/ai"),{recursive:!0}),await S(g(t,"apps/desktop/tests/main/ai-import-boundary.test.ts")),await S(g(t,"apps/desktop/tests/renderer/lib/ai.test.ts"));let r=g(t,"apps/desktop/package.json"),n=JSON.parse(await j(r,"utf-8")),o=["@repo/ai","@anthropic-ai/claude-agent-sdk","@openai/codex-sdk","@modelcontextprotocol/sdk","cross-spawn","@huggingface/transformers","@orama/orama"];for(let p of o){if(!n.dependencies||!(p in n.dependencies))throw new Error(`stripDesktopAi: expected ${p} in apps/desktop dependencies (boilerplate drift).`);delete n.dependencies[p]}for(let p of["fetch:model","predev","prebuild"]){if(!n.scripts||!(p in n.scripts))throw new Error(`stripDesktopAi: expected the ${p} script in apps/desktop package.json (boilerplate drift).`);delete n.scripts[p]}await c(r,JSON.stringify(n,null," ")+`
1109
+ `);let i=g(t,"apps/desktop/.gitignore"),s=await j(i,"utf-8");await c(i,Ti(s)),await Ri(t);let a=g(t,"packages/i18n/translations");for(let p of await Yt(a,{withFileTypes:!0})){if(!p.isDirectory())continue;let f=g(a,p.name,"desktop.json"),m=await j(f,"utf-8").catch(()=>null);if(m===null)continue;let u=JSON.parse(m),v=!1;"agents"in u&&(delete u.agents,v=!0),"schedules"in u&&(delete u.schedules,v=!0),"integrations"in u&&(delete u.integrations,v=!0),v&&await c(f,JSON.stringify(u,null," ")+`
1110
+ `)}}async function Ri(e){let t=g(e,"apps/desktop/src/main/ipc.ts"),r=Ge(await j(t,"utf-8"),"main/ipc.ts");await c(t,r);let n=g(e,"apps/desktop/src/main/config.ts"),o=await j(n,"utf-8");o=D(o,`,
1105
1111
  /** AI tool orchestration ("agents") feature flags. */
1106
- agents: desktopConfig.agents`,"","main/config agents field"),await l(n,o);let i=y(e,"apps/desktop/src/preload/index.ts"),s=await z(i,"utf-8");s=$(s,`import type {
1112
+ agents: desktopConfig.agents`,"","main/config agents field"),await c(n,o);let i=g(e,"apps/desktop/src/preload/index.ts"),s=await j(i,"utf-8");s=D(s,`import type {
1107
1113
  AdapterCapabilities,
1108
1114
  AuthStatus,
1109
1115
  ConnectionRef,
@@ -1115,19 +1121,19 @@ $1`),n!==r&&await l(t,n)}import{readdir as Yt,readFile as z,rm as w}from"fs/prom
1115
1121
  RunEvent,
1116
1122
  RunRequest
1117
1123
  } from '@repo/ai/backends'
1118
- `,"","preload @repo/ai type import"),s=$(s,`import type { ChatSession, ScheduleRun, ScheduleTrigger } from '@repo/ai/agents'
1119
- `,"","preload @repo/ai/agents type import"),s=$(s,`import type { ReasoningEffort } from '@repo/ai/backends'
1120
- `,"","preload ReasoningEffort type import"),s=$(s,`import type { DesktopTaskSpec } from '@repo/config/types'
1121
- `,"","preload DesktopTaskSpec import"),s=$(s,`import type { ScheduleView } from '../main/ai/ipc'
1122
- `,"","preload ScheduleView import");let a=/,\n[ \t]*\/\/ gsaas:ai-start\n[\s\S]*?\n[ \t]*\/\/ gsaas:ai-end\n/g,d=s.match(a);if(d===null)throw new Error("stripDesktopAi: expected the preload `ai` bridge marker region, but it was missing (boilerplate drift).");if(d.length>1)throw new Error("stripDesktopAi: the preload `ai` bridge marker region appears more than once (boilerplate drift).");s=s.replace(a,`
1123
- `),s=Ge(s,"preload/index.ts"),await l(i,s);let u=y(e,"apps/desktop/src/renderer/src/components/shell.tsx"),m=Ge(await z(u,"utf-8"),"components/shell.tsx");await l(u,m);let f=y(e,"apps/desktop/src/renderer/src/router.tsx"),v=await z(f,"utf-8");v=$(v,`import { clientConfig } from '@/lib/config'
1124
- `,"","router clientConfig import"),v=$(v,`import { AgentsScreen } from '@/screens/agents'
1125
- `,"","router AgentsScreen import"),v=Ge(v,"router.tsx"),v=$(v,` ...(clientConfig.agentsEnabled ? [agentsRoute] : []),
1126
- `,"","router agents route spread"),v=$(v,`import { SchedulesScreen } from '@/screens/schedules'
1127
- `,"","router SchedulesScreen import"),v=$(v,` ...(clientConfig.agentsEnabled ? [schedulesRoute] : []),
1128
- `,"","router schedules route spread"),v=$(v,`import { IntegrationsScreen } from '@/screens/integrations'
1129
- `,"","router IntegrationsScreen import"),v=$(v,` ...(clientConfig.agentsEnabled ? [integrationsRoute] : []),
1130
- `,"","router integrations route spread"),await l(f,v);let b=y(e,"apps/desktop/src/renderer/src/config/sidebar.ts"),P=await z(b,"utf-8");P=$(P,`import {
1124
+ `,"","preload @repo/ai type import"),s=D(s,`import type { ChatSession, ScheduleRun, ScheduleTrigger } from '@repo/ai/agents'
1125
+ `,"","preload @repo/ai/agents type import"),s=D(s,`import type { ReasoningEffort } from '@repo/ai/backends'
1126
+ `,"","preload ReasoningEffort type import"),s=D(s,`import type { DesktopTaskSpec } from '@repo/config/types'
1127
+ `,"","preload DesktopTaskSpec import"),s=D(s,`import type { ScheduleView } from '../main/ai/ipc'
1128
+ `,"","preload ScheduleView import");let a=/,\n[ \t]*\/\/ gsaas:ai-start\n[\s\S]*?\n[ \t]*\/\/ gsaas:ai-end\n/g,p=s.match(a);if(p===null)throw new Error("stripDesktopAi: expected the preload `ai` bridge marker region, but it was missing (boilerplate drift).");if(p.length>1)throw new Error("stripDesktopAi: the preload `ai` bridge marker region appears more than once (boilerplate drift).");s=s.replace(a,`
1129
+ `),s=Ge(s,"preload/index.ts"),await c(i,s);let f=g(e,"apps/desktop/src/renderer/src/components/shell.tsx"),m=Ge(await j(f,"utf-8"),"components/shell.tsx");await c(f,m);let u=g(e,"apps/desktop/src/renderer/src/router.tsx"),v=await j(u,"utf-8");v=D(v,`import { clientConfig } from '@/lib/config'
1130
+ `,"","router clientConfig import"),v=D(v,`import { AgentsScreen } from '@/screens/agents'
1131
+ `,"","router AgentsScreen import"),v=Ge(v,"router.tsx"),v=D(v,` ...(clientConfig.agentsEnabled ? [agentsRoute] : []),
1132
+ `,"","router agents route spread"),v=D(v,`import { SchedulesScreen } from '@/screens/schedules'
1133
+ `,"","router SchedulesScreen import"),v=D(v,` ...(clientConfig.agentsEnabled ? [schedulesRoute] : []),
1134
+ `,"","router schedules route spread"),v=D(v,`import { IntegrationsScreen } from '@/screens/integrations'
1135
+ `,"","router IntegrationsScreen import"),v=D(v,` ...(clientConfig.agentsEnabled ? [integrationsRoute] : []),
1136
+ `,"","router integrations route spread"),await c(u,v);let w=g(e,"apps/desktop/src/renderer/src/config/sidebar.ts"),T=await j(w,"utf-8");T=D(T,`import {
1131
1137
  BuildingsIcon,
1132
1138
  ClockCountdownIcon,
1133
1139
  GearIcon,
@@ -1135,23 +1141,27 @@ $1`),n!==r&&await l(t,n)}import{readdir as Yt,readFile as z,rm as w}from"fs/prom
1135
1141
  PlugsConnectedIcon,
1136
1142
  RobotIcon,
1137
1143
  UserCircleIcon
1138
- } from '@phosphor-icons/react'`,"import { BuildingsIcon, GearIcon, HouseIcon, UserCircleIcon } from '@phosphor-icons/react'","sidebar icon imports"),P=Ge(P,"config/sidebar.ts"),await l(b,P);let B=y(e,"apps/desktop/src/renderer/src/lib/config.ts"),C=await z(B,"utf-8");C=$(C,`,
1144
+ } from '@phosphor-icons/react'`,"import { BuildingsIcon, GearIcon, HouseIcon, UserCircleIcon } from '@phosphor-icons/react'","sidebar icon imports"),T=Ge(T,"config/sidebar.ts"),await c(w,T);let G=g(e,"apps/desktop/src/renderer/src/lib/config.ts"),L=await j(G,"utf-8");L=D(L,`,
1139
1145
  /** Whether the AI tool orchestration ("agents") feature is enabled. */
1140
1146
  agentsEnabled: desktopConfig.agents.enabled,
1141
1147
  /** Whether end users may author their own background schedules (default true). */
1142
- userSchedulesEnabled: desktopConfig.agents.userSchedules !== false`,"","lib/config agent flags"),await l(B,C);let W=y(e,"apps/desktop/src/renderer/src/lib/sidebar-flags.ts"),X=Ge(await z(W,"utf-8"),"lib/sidebar-flags.ts");await l(W,X)}async function an(e){e.frontend==="nextjs"||e.desktop||await w(y(e.projectDir,"packages/ui-next"),{recursive:!0})}import{readFile as Ti}from"fs/promises";import{join as Ri}from"path";var cn=`on:
1148
+ userSchedulesEnabled: desktopConfig.agents.userSchedules !== false`,"","lib/config agent flags"),await c(G,L);let W=g(e,"apps/desktop/src/renderer/src/lib/sidebar-flags.ts"),X=Ge(await j(W,"utf-8"),"lib/sidebar-flags.ts");await c(W,X);let ne=g(e,"apps/desktop/electron.vite.config.ts"),b=await j(ne,"utf-8");b=D(b,",\n // The local-embeddings worker (`embeddings.ts`) spawns `embeddings-worker.js`,\n // which must sit next to the main `index.js`. Declaring both as named rollup\n // inputs emits `out/main/index.js` (the app entry) and\n // `out/main/embeddings-worker.js` (the worker) as `[name].js`, so the host's\n // `join(__dirname, 'embeddings-worker.js')` resolves in dev and packaged builds.\n rollupOptions: {\n // `@huggingface/transformers` loads its model runtime from `onnxruntime-node`,\n // a native addon that resolves its `.node` binding with a dynamic\n // `require('../bin/napi-v6/<platform>/onnxruntime_binding.node')`. A native\n // addon cannot be Rollup-bundled, so both packages stay external and load from\n // `node_modules` at runtime; electron-builder ships their trees (see\n // `electron-builder.mjs` `files`/`asarUnpack`). The CLI strips this whole\n // rollup block for an AI-off desktop build (the worker is removed with it).\n external: ['@huggingface/transformers', 'onnxruntime-node'],\n input: {\n index: resolve('src/main/index.ts'),\n 'embeddings-worker': resolve('src/main/ai/embeddings-worker.ts')\n }\n }","","electron.vite.config rollup worker block"),await c(ne,b);let I=g(e,"apps/desktop/electron-builder.mjs"),R=await j(I,"utf-8");R=D(R," // The main, preload, and renderer are self-contained bundles (electron-vite with\n // build.externalizeDeps:false), so the packaged app needs almost ZERO runtime\n // node_modules: exclude them all by default to keep the thin client small (the\n // backend dependency trees `@repo/api` drags in - express, pg, aws-sdk - are never\n // `require`d at runtime once bundled). Then RE-INCLUDE only the trees the local\n // embeddings worker keeps external (the native ONNX runtime can't be Rollup-bundled).\n // The worker `require`s `@huggingface/transformers`, whose Node build eagerly, at\n // import, pulls in `onnxruntime-node` (its prebuilt `.node` binding) + the shared\n // `onnxruntime-common`, AND `sharp` (a top-level `require(\"sharp\")` in its image\n // util that runs even for text-only embedding); `sharp` in turn needs its `@img/*`\n // platform natives plus `detect-libc` and `semver` at load. All of these must ship\n // or the worker import throws and semantic search silently degrades to BM25. Each\n // glob matches against electron-builder's production-dependency collection, which\n // flattens pnpm's `.pnpm` virtual store into the packaged `node_modules/<name>`\n // (following symlinks) - so transitive names resolve even though, under pnpm's\n // isolated linker, only `@huggingface/transformers` is linked at the app's own\n // node_modules and the rest live in `.pnpm`. The CLI strips every re-include for an\n // AI-off desktop build (there is no external runtime to collect). The\n // `KB_SMOKE`-gated packaged-embedding test guards against this closure drifting.\n files: [\n 'out/**',\n 'resources/**',\n 'package.json',\n '!node_modules/**/*',\n 'node_modules/@huggingface/transformers/**/*',\n 'node_modules/onnxruntime-node/**/*',\n 'node_modules/onnxruntime-common/**/*',\n 'node_modules/sharp/**/*',\n 'node_modules/@img/**/*',\n 'node_modules/detect-libc/**/*',\n 'node_modules/semver/**/*'\n ],",` // The main, preload, and renderer are self-contained bundles (electron-vite with
1149
+ // build.externalizeDeps:false), so the packaged app ships ZERO runtime node_modules
1150
+ // (every dependency is Rollup-bundled): keep only the built output, the resources,
1151
+ // and the package.json, and exclude node_modules entirely.
1152
+ files: ['out/**', 'resources/**', 'package.json', '!node_modules/**/*'],`,"electron-builder files re-includes"),R=D(R,"'resources/**', '**/*.node'","'resources/**'","electron-builder asarUnpack entry"),await c(I,R)}async function cn(e){e.frontend==="nextjs"||e.desktop||await S(g(e.projectDir,"packages/ui-next"),{recursive:!0})}import{readFile as _i}from"fs/promises";import{join as Oi}from"path";var ln=`on:
1143
1153
  workflow_run:
1144
1154
  workflows: ["CI"]
1145
1155
  branches: [main]
1146
1156
  types: [completed]
1147
- workflow_dispatch:`,_i=`on:
1157
+ workflow_dispatch:`,xi=`on:
1148
1158
  # Automatic release on CI success is disabled for this project to conserve
1149
1159
  # GitHub Actions minutes. Re-enable by uncommenting the workflow_run trigger.
1150
1160
  # workflow_run:
1151
1161
  # workflows: ["CI"]
1152
1162
  # branches: [main]
1153
1163
  # types: [completed]
1154
- workflow_dispatch:`;async function ln(e){if(!e.desktop||e.desktopAutoRelease)return;let t=Ri(e.projectDir,".github/workflows/desktop-release.yml"),r=await Ti(t,"utf8");if(!r.includes(cn))throw new Error(`Cannot make desktop releases manual: the expected workflow_run trigger block was not found in ${t}. The boilerplate workflow may have drifted.`);await l(t,r.replace(cn,_i))}import{join as Oi}from"path";async function pn(e){let t=xi(e);await l(Oi(e.projectDir,".github/workflows/ci.yml"),t)}function xi(e){let t=V[e.databaseProvider].managed,r=e.cacheProvider==="upstash",n=e.frontend==="nuxt",o=t?"":` services:
1164
+ workflow_dispatch:`;async function pn(e){if(!e.desktop||e.desktopAutoRelease)return;let t=Oi(e.projectDir,".github/workflows/desktop-release.yml"),r=await _i(t,"utf8");if(!r.includes(ln))throw new Error(`Cannot make desktop releases manual: the expected workflow_run trigger block was not found in ${t}. The boilerplate workflow may have drifted.`);await c(t,r.replace(ln,xi))}import{join as Di}from"path";async function dn(e){let t=Ci(e);await c(Di(e.projectDir,".github/workflows/ci.yml"),t)}function Ci(e){let t=F[e.databaseProvider].managed,r=e.cacheProvider==="upstash",n=e.frontend==="nuxt",o=t?"":` services:
1155
1165
  postgres:
1156
1166
  image: postgres:18-alpine
1157
1167
  env:
@@ -1171,7 +1181,7 @@ $1`),n!==r&&await l(t,n)}import{readdir as Yt,readFile as z,rm as w}from"fs/prom
1171
1181
  STRIPE_SECRET_KEY: test
1172
1182
  STRIPE_WEBHOOK_SECRET: test`;case"polar":return`
1173
1183
  POLAR_ACCESS_TOKEN: test
1174
- POLAR_WEBHOOK_SECRET: test`;case"none":return"";default:{let f=e.paymentProvider;throw new Error(`buildCiYaml: unhandled payment provider "${String(f)}"`)}}})(),d=e.socialProviders.map(f=>ne[f].envVars.map(v=>`
1184
+ POLAR_WEBHOOK_SECRET: test`;case"none":return"";default:{let u=e.paymentProvider;throw new Error(`buildCiYaml: unhandled payment provider "${String(u)}"`)}}})(),p=e.socialProviders.map(u=>oe[u].envVars.map(v=>`
1175
1185
  ${v.name}: test`).join("")).join("");return`# Deployment is handled by the hosting platform (Vercel, Coolify, etc.)
1176
1186
  # which auto-deploys on push. CI runs in parallel as a quality gate.
1177
1187
  # For PR-based workflows, enable GitHub branch protection to require CI before merging.
@@ -1197,7 +1207,7 @@ jobs:
1197
1207
  runs-on: ubuntu-latest
1198
1208
  timeout-minutes: 20
1199
1209
  ${o} env:
1200
- CONTENT_API_KEY: test-contentapi-key-16chars${s}${a}${d}
1210
+ CONTENT_API_KEY: test-contentapi-key-16chars${s}${a}${p}
1201
1211
  STORAGE_REGION: test
1202
1212
  STORAGE_ENDPOINT: http://test
1203
1213
  STORAGE_ACCESS_KEY_ID: test
@@ -1221,46 +1231,46 @@ ${n?` # vue-tsc on web-nuxt OOMs on the GitHub runner's default heap once
1221
1231
  run: echo "DATABASE_URL=${i}" > .env
1222
1232
 
1223
1233
  - run: pnpm test
1224
- `}import{readFile as dn}from"fs/promises";import{existsSync as Di}from"fs";import{join as qt}from"path";var un="@repo/database";function Ci(e){return e?"pnpm -F @repo/database reset && pnpm -F @repo/database push --force":"pnpm -F @repo/database migrate"}function Ni(e,t){switch(e){case"fullstack":return t==="nextjs"?"web-next":"web-nuxt";case"separate":return"backend";default:{let r=e;throw new Error(`schemaOwnerApp: unhandled architecture "${String(r)}"`)}}}async function Li(e,t,r){let n=await dn(e,"utf-8"),o=JSON.parse(n),i=o.scripts?.[t];if(!i)throw new Error(`Cannot prepend to missing script "${t}" in ${e}`);i.includes(un)||(o.scripts={...o.scripts,[t]:`${r} && ${i}`},await l(e,JSON.stringify(o,null," ")+`
1225
- `))}async function $i(e,t){let r=Di(e)?JSON.parse(await dn(e,"utf-8")):{$schema:"https://openapi.vercel.sh/vercel.json"},n=r.buildCommand?.trim()||"pnpm build";n.includes(un)||(r.buildCommand=`${t} && ${n}`,await l(e,JSON.stringify(r,null," ")+`
1226
- `))}async function mn(e){let t=Ci(e.demo===!0),r=Ni(e.architecture,e.frontend),n=qt(e.projectDir,"apps",r);switch(e.deploymentTarget){case"node":await Li(qt(n,"package.json"),"start",t);return;case"vercel":await $i(qt(n,"vercel.json"),t);return;default:{let o=e.deploymentTarget;throw new Error(`generateDeployScripts: unhandled deployment target "${String(o)}"`)}}}import{readFile as ji}from"fs/promises";import{existsSync as Ui}from"fs";import{join as At}from"path";var Mi=["stripe","polar"];async function fn(e){let t=At(e.projectDir,"packages/payments/src"),r=e.paymentProvider,n=`// Active payment provider. To switch, change the path below to
1234
+ `}import{readFile as un}from"fs/promises";import{existsSync as Ni}from"fs";import{join as qt}from"path";var mn="@repo/database";function Li(e){return e?"pnpm -F @repo/database reset && pnpm -F @repo/database push --force":"pnpm -F @repo/database migrate"}function $i(e,t){switch(e){case"fullstack":return t==="nextjs"?"web-next":"web-nuxt";case"separate":return"backend";default:{let r=e;throw new Error(`schemaOwnerApp: unhandled architecture "${String(r)}"`)}}}async function ji(e,t,r){let n=await un(e,"utf-8"),o=JSON.parse(n),i=o.scripts?.[t];if(!i)throw new Error(`Cannot prepend to missing script "${t}" in ${e}`);i.includes(mn)||(o.scripts={...o.scripts,[t]:`${r} && ${i}`},await c(e,JSON.stringify(o,null," ")+`
1235
+ `))}async function Ui(e,t){let r=Ni(e)?JSON.parse(await un(e,"utf-8")):{$schema:"https://openapi.vercel.sh/vercel.json"},n=r.buildCommand?.trim()||"pnpm build";n.includes(mn)||(r.buildCommand=`${t} && ${n}`,await c(e,JSON.stringify(r,null," ")+`
1236
+ `))}async function fn(e){let t=Li(e.demo===!0),r=$i(e.architecture,e.frontend),n=qt(e.projectDir,"apps",r);switch(e.deploymentTarget){case"node":await ji(qt(n,"package.json"),"start",t);return;case"vercel":await Ui(qt(n,"vercel.json"),t);return;default:{let o=e.deploymentTarget;throw new Error(`generateDeployScripts: unhandled deployment target "${String(o)}"`)}}}import{readFile as Mi}from"fs/promises";import{existsSync as Vi}from"fs";import{join as At}from"path";var Fi=["stripe","polar"];async function gn(e){let t=At(e.projectDir,"packages/payments/src"),r=e.paymentProvider,n=`// Active payment provider. To switch, change the path below to
1227
1237
  // "./polar/index" or "./none/index" (other folders are kept in place).
1228
1238
  export { ops } from "./${r}/index";
1229
- `;await l(At(t,"providers/index.ts"),n),await l(At(t,"index.ts"),await Vi(t,r))}async function Vi(e,t){let r=At(e,"index.ts");if(!Ui(r))throw new Error(`generatePaymentBarrel: expected ${r} to exist - did packages/payments move?`);let n=await ji(r,"utf-8"),o=Mi.filter(s=>s!==t);return n.split(`
1239
+ `;await c(At(t,"providers/index.ts"),n),await c(At(t,"index.ts"),await Bi(t,r))}async function Bi(e,t){let r=At(e,"index.ts");if(!Vi(r))throw new Error(`generatePaymentBarrel: expected ${r} to exist - did packages/payments move?`);let n=await Mi(r,"utf-8"),o=Fi.filter(s=>s!==t);return n.split(`
1230
1240
  `).filter(s=>!o.some(a=>s.includes(`./providers/${a}/`))).join(`
1231
- `)}import{readdir as Fi,readFile as Bi}from"fs/promises";import{join as gn}from"path";async function hn(e){if(e.demo)return;let t=e.appName.trim()||e.projectName,r=JSON.stringify(t).slice(1,-1),n=gn(e.projectDir,"packages/i18n/translations"),o;try{o=await Fi(n)}catch{return}for(let i of o){let s=gn(n,i,"web.json"),a;try{a=await Bi(s,"utf-8")}catch{continue}let d=a.replaceAll("GenerateSaaS",r);d!==a&&await l(s,d)}}import{readFile as Gi}from"fs/promises";import{join as yn}from"path";var Ki=[".nuxt/",".nuxt",".nitro/",".nitro",".output/",".output","_locales/"],zi=[".next/",".next",".svelte-kit/",".svelte-kit",".wrangler/",".wrangler",".dev.vars"];async function Sn(e){let r=e.frontend==="nuxt"?zi:Ki;await vn(yn(e.projectDir,".gitignore"),r),await vn(yn(e.projectDir,".dockerignore"),r)}async function vn(e,t){let r;try{r=await Gi(e,"utf-8")}catch{return}let n=new Set(t),o=r.split(`
1241
+ `)}import{readdir as Gi,readFile as Ki}from"fs/promises";import{join as hn}from"path";async function yn(e){if(e.demo)return;let t=e.appName.trim()||e.projectName,r=JSON.stringify(t).slice(1,-1),n=hn(e.projectDir,"packages/i18n/translations"),o;try{o=await Gi(n)}catch{return}for(let i of o){let s=hn(n,i,"web.json"),a;try{a=await Ki(s,"utf-8")}catch{continue}let p=a.replaceAll("GenerateSaaS",r);p!==a&&await c(s,p)}}import{readFile as zi}from"fs/promises";import{join as vn}from"path";var Hi=[".nuxt/",".nuxt",".nitro/",".nitro",".output/",".output","_locales/"],Yi=[".next/",".next",".svelte-kit/",".svelte-kit",".wrangler/",".wrangler",".dev.vars"];async function kn(e){let r=e.frontend==="nuxt"?Yi:Hi;await bn(vn(e.projectDir,".gitignore"),r),await bn(vn(e.projectDir,".dockerignore"),r)}async function bn(e,t){let r;try{r=await zi(e,"utf-8")}catch{return}let n=new Set(t),o=r.split(`
1232
1242
  `).filter(i=>!n.has(i.trim()));o.length!==r.split(`
1233
- `).length&&await l(e,o.join(`
1234
- `))}async function It(e){let t=e.projectDir;e.demo||(await br(t),await wr(t),await Ar(t)),await Ir(t,e.aiTools),await Pr(t,e.frontend),await Rr(e),await _r(e),await Or(e),e.demo||await xr(e),await Nr(e);let r=await Lr(e);return await $r(e),await jr(e),await Ur(e),await Mr(e),await Vr(e),await Fr(e),await zr(e),await Hr(e),await Jr(e),await Xr(e),await Qr(e),await pn(e),await en(e),await tn(e),await rn(e),await nn(e),await on(e),await sn(e),await ln(e),await an(e),await mn(e),await fn(e),await hn(e),await Sn(e),{dockerComposeGenerated:r}}import{basename as kn,join as bn,relative as Yi}from"path";import{createHash as En}from"crypto";import{readFile as Hi}from"fs/promises";async function Pt(e){let t=await Hi(e);return En("sha256").update(t).digest("hex")}function Jt(e){return En("sha256").update(e).digest("hex")}var qi=new Set(["data",K]);function Ji(e){let t=e.split("/");for(let r of t)if(jt.has(r)||Ut.has(r)||qi.has(r)||r.startsWith(".env")&&!r.includes("example"))return!0;return!1}function wn(e,t){return{projectName:e.projectName??kn(t),appName:e.appName??e.projectName??kn(t),projectDir:t,frontend:e.frontend==="nextjs"?"nextjs":"nuxt",architecture:e.architecture??"fullstack",paymentProvider:e.paymentProvider??"none",emailProvider:e.emailProvider??"smtp",multiTenancy:e.multiTenancy??!1,billingScope:e.billingScope??"user",blog:e.blog??!1,docs:e.docs??!1,desktop:e.desktop??!1,desktopAutoRelease:e.desktopAutoRelease??!0,desktopAi:e.desktopAi??!1,revenueSharing:e.revenueSharing??!1,credits:e.credits??!1,dockerServices:e.dockerServices??[],aiTools:e.aiTools??[],socialProviders:e.socialProviders??[],defaultCurrency:e.defaultCurrency??"USD",deploymentTarget:e.deploymentTarget??"node",databaseProvider:e.databaseProvider??"postgres",cacheProvider:e.cacheProvider??"redis",version:e.version,baseUrl:e.baseUrl,credentials:{},demo:!1}}function Wi(e,t){return{version:e.version,initialVersion:e.version,repo:"Duzbee/GenerateSaaS",appName:e.appName,projectName:e.projectName,frontend:e.frontend,architecture:e.architecture,paymentProvider:e.paymentProvider,emailProvider:e.emailProvider,multiTenancy:e.multiTenancy,billingScope:e.billingScope,blog:e.blog,docs:e.docs,desktop:e.desktop,desktopAutoRelease:e.desktopAutoRelease,desktopAi:e.desktopAi,credits:e.credits,revenueSharing:e.revenueSharing,defaultCurrency:e.defaultCurrency,dockerServices:e.dockerServices,socialProviders:e.socialProviders,deploymentTarget:e.deploymentTarget,databaseProvider:e.databaseProvider,cacheProvider:e.cacheProvider,aiTools:e.aiTools,...e.baseUrl?{baseUrl:e.baseUrl}:{},...t&&{licenseToken:t.token,licenseKeyHash:t.keyHash,installId:t.installId}}}async function An(e,t){let n=(await ke(e.projectDir,e.projectDir,Ji)).sort(),o=await Promise.all(n.map(async a=>[Ee(Yi(e.projectDir,a)),await Pt(a)])),i=Object.fromEntries(o),s=Wi(e,t);await l(bn(e.projectDir,pe),JSON.stringify(s,null," ")+`
1235
- `),await l(bn(e.projectDir,dr),JSON.stringify(i,null," ")+`
1236
- `)}import{relative as Xi}from"path";async function Ke(e){let r=(await ke(e,e,$e)).sort(),n=await Promise.all(r.map(async o=>[Ee(Xi(e,o)),await Pt(o)]));return Object.fromEntries(n)}import{copyFile as Zi,mkdir as Qi,rm as es}from"fs/promises";import{dirname as ts,join as In,relative as rs}from"path";import{existsSync as ns}from"fs";async function Wt(e,t){ns(t)&&await es(t,{recursive:!0,force:!0});let r=await ke(e,e,$e);for(let n of r){let o=Ee(rs(e,n)),i=In(t,o);await Qi(ts(i),{recursive:!0}),await Zi(n,i)}}async function Pn(e,t){await Wt(e,In(t,gt))}import{existsSync as os}from"fs";import{readFile as Tn,readdir as is}from"fs/promises";import{join as re,dirname as ss,resolve as as,sep as cs}from"path";import{fileURLToPath as ls}from"url";var nt={"claude-code":".claude/skills",cursor:".cursor/skills",codex:".agents/skills","gemini-cli":".gemini/skills",windsurf:".windsurf/skills"},Sp=Object.values(nt),Xt="generatesaas-update",Rn=ss(ls(import.meta.url));function ps(){let e=re(Rn,"skill","content");return os(e)?e:re(Rn,"content")}function Zt(e){return!e||e.length===0?[]:e.map(t=>nt[t])}async function Qt(e,t,r,n){let o=Zt(n);for(let i of o){let s=re(e,i,Xt),a=re(s,"scripts"),d=re(s,"references");await St(a),await St(d),await l(re(s,"SKILL.md"),t.replaceAll("__SKILL_ROOT__",i)),await l(re(d,".gitkeep"),"");for(let[u,m]of Object.entries(r)){let f=as(a,u);f.startsWith(a+cs)&&await l(f,m)}}}async function _n(e,t){let r=ps(),n=await Tn(re(r,"SKILL.md"),"utf-8"),o=re(r,"scripts"),i=await is(o),s={};for(let a of i)a!==".gitkeep"&&(s[a]=await Tn(re(o,a),"utf-8"));await Qt(e,n,s,t)}import{execFile as ds,execFileSync as us}from"child_process";import{access as On,readFile as ms}from"fs/promises";import{join as er}from"path";import*as x from"@clack/prompts";function we(e){try{let t=process.platform==="win32"?"where":"which";return us(t,[e],{stdio:"ignore"}),!0}catch{return!1}}function be(e,t,r,n=3e5){return new Promise((o,i)=>{ds(e,t,{cwd:r,timeout:n},(s,a,d)=>{if(s){let u=String(a||"").trim(),f=[String(d||"").trim(),u].filter(Boolean).join(`
1237
- `);i(new Error(f?`${s.message}
1238
- ${f}`:s.message))}else o()})})}async function xn(e){if(!we("pnpm"))return x.log.warn("pnpm not found. Skipping lockfile regeneration."),!1;try{return await be("pnpm",["install","--lockfile-only","--no-frozen-lockfile","--config.minimumReleaseAge=0"],e),!0}catch(t){let r=t instanceof Error?t.message:String(t);return x.log.warn(`Lockfile regeneration failed: ${r}`),x.log.warn("Deploys using --frozen-lockfile may fail."),!1}}async function Dn(e){if(!we("pnpm"))return x.log.warn("pnpm not found. Skipping dependency installation."),x.log.info("Install pnpm: https://pnpm.io/installation"),!1;let t=x.spinner();t.start("Installing dependencies (this may take a minute)...");try{return await be("pnpm",["install","--config.minimumReleaseAge=0"],e),t.stop("Dependencies installed."),!0}catch(r){t.stop("Dependency installation failed.");let n=r instanceof Error?r.message:String(r);return x.log.warn(`pnpm install failed: ${n}`),x.log.warn("You can run it manually later."),!1}}async function Cn(e){if(!we("pnpm"))return!1;let t=x.spinner();t.start("Generating baseline database migration...");try{return await be("pnpm",["-F","@repo/database","generate"],e),t.stop("Baseline migration generated."),!0}catch(r){t.stop("Baseline migration generation failed.");let n=r instanceof Error?r.message:String(r);return x.log.warn(`Could not generate baseline migration: ${n}`),x.log.warn("Run 'pnpm -F @repo/database generate' before your first deploy."),!1}}async function Nn(e){try{return await On(er(e,".git")),x.log.info("Git repository already exists, skipping init."),!0}catch{}if(!we("git"))return x.log.warn("git not found. Skipping repository initialization."),!1;let t=x.spinner();t.start("Initializing git repository...");try{return await be("git",["init"],e),await be("git",["add","-A"],e),await be("git",["commit","--no-verify","-m","Initial commit from GenerateSaaS"],e),t.stop("Git repository initialized."),!0}catch{return t.stop("Git initialization failed."),x.log.warn("You can run git init manually later."),!1}}async function Ln(e){if(!we("pnpm"))return!1;try{await On(er(e,".git"))}catch{return!1}try{let t=JSON.parse(await ms(er(e,"package.json"),"utf-8")),r=!!t.devDependencies?.["simple-git-hooks"],n=!!t["simple-git-hooks"];if(!r||!n)return!1}catch{return!1}try{return await be("pnpm",["exec","simple-git-hooks"],e),!0}catch{return x.log.warn("Could not install git hooks. Run 'pnpm exec simple-git-hooks' manually."),!1}}import*as ze from"@clack/prompts";import H from"picocolors";function $n(e,t){t.dockerComposeGenerated&&!t.dockerAvailable&&ze.log.warn("Docker not found. Install Docker to run local services: https://docs.docker.com/get-docker/");let r=[];if(r.push(`cd ${e.projectDir}`),t.pnpmInstalled||r.push("pnpm install"),t.dockerComposeGenerated){let i=e.dockerServices.map(s=>Te[s].label).join(", ");r.push(`pnpm infra ${H.dim(`# ${i}`)}`)}if(r.push(`pnpm dev ${H.dim("# http://localhost:3000")}`),t.skippedCredentials.length>0&&(r.push(""),r.push(H.dim("Fill in remaining TODO values in .env"))),ze.note(r.join(`
1243
+ `).length&&await c(e,o.join(`
1244
+ `))}async function It(e){let t=e.projectDir;e.demo||(await wr(t),await Er(t),await Ar(t)),await Ir(t,e.aiTools),await Pr(t,e.frontend),await Rr(e),await _r(e),await Or(e),e.demo||await xr(e),await Nr(e);let r=await Lr(e);return await $r(e),await jr(e),await Ur(e),await Mr(e),await Vr(e),await Fr(e),await zr(e),await Hr(e),await Jr(e),await Xr(e),await en(e),await dn(e),await tn(e),await rn(e),await nn(e),await on(e),await sn(e),await an(e),await pn(e),await cn(e),await fn(e),await gn(e),await yn(e),await kn(e),{dockerComposeGenerated:r}}import{basename as wn,join as En,relative as Ji}from"path";import{createHash as Sn}from"crypto";import{readFile as qi}from"fs/promises";async function Pt(e){let t=await qi(e);return Sn("sha256").update(t).digest("hex")}function Jt(e){return Sn("sha256").update(e).digest("hex")}var Wi=new Set(["data",z]);function Xi(e){let t=e.split("/");for(let r of t)if(jt.has(r)||Ut.has(r)||Wi.has(r)||r.startsWith(".env")&&!r.includes("example"))return!0;return!1}function An(e,t){return{projectName:e.projectName??wn(t),appName:e.appName??e.projectName??wn(t),projectDir:t,frontend:e.frontend==="nextjs"?"nextjs":"nuxt",architecture:e.architecture??"fullstack",paymentProvider:e.paymentProvider??"none",emailProvider:e.emailProvider??"smtp",multiTenancy:e.multiTenancy??!1,billingScope:e.billingScope??"user",blog:e.blog??!1,docs:e.docs??!1,desktop:e.desktop??!1,desktopAutoRelease:e.desktopAutoRelease??!0,desktopAi:e.desktopAi??!1,revenueSharing:e.revenueSharing??!1,credits:e.credits??!1,dockerServices:e.dockerServices??[],aiTools:e.aiTools??[],socialProviders:e.socialProviders??[],defaultCurrency:e.defaultCurrency??"USD",deploymentTarget:e.deploymentTarget??"node",databaseProvider:e.databaseProvider??"postgres",cacheProvider:e.cacheProvider??"redis",version:e.version,baseUrl:e.baseUrl,credentials:{},demo:!1}}function Zi(e,t){return{version:e.version,initialVersion:e.version,repo:"Duzbee/GenerateSaaS",appName:e.appName,projectName:e.projectName,frontend:e.frontend,architecture:e.architecture,paymentProvider:e.paymentProvider,emailProvider:e.emailProvider,multiTenancy:e.multiTenancy,billingScope:e.billingScope,blog:e.blog,docs:e.docs,desktop:e.desktop,desktopAutoRelease:e.desktopAutoRelease,desktopAi:e.desktopAi,credits:e.credits,revenueSharing:e.revenueSharing,defaultCurrency:e.defaultCurrency,dockerServices:e.dockerServices,socialProviders:e.socialProviders,deploymentTarget:e.deploymentTarget,databaseProvider:e.databaseProvider,cacheProvider:e.cacheProvider,aiTools:e.aiTools,...e.baseUrl?{baseUrl:e.baseUrl}:{},...t&&{licenseToken:t.token,licenseKeyHash:t.keyHash,installId:t.installId}}}async function In(e,t){let n=(await we(e.projectDir,e.projectDir,Xi)).sort(),o=await Promise.all(n.map(async a=>[Se(Ji(e.projectDir,a)),await Pt(a)])),i=Object.fromEntries(o),s=Zi(e,t);await c(En(e.projectDir,de),JSON.stringify(s,null," ")+`
1245
+ `),await c(En(e.projectDir,dr),JSON.stringify(i,null," ")+`
1246
+ `)}import{relative as Qi}from"path";async function Ke(e){let r=(await we(e,e,$e)).sort(),n=await Promise.all(r.map(async o=>[Se(Qi(e,o)),await Pt(o)]));return Object.fromEntries(n)}import{copyFile as es,mkdir as ts,rm as rs}from"fs/promises";import{dirname as ns,join as Pn,relative as os}from"path";import{existsSync as is}from"fs";async function Wt(e,t){is(t)&&await rs(t,{recursive:!0,force:!0});let r=await we(e,e,$e);for(let n of r){let o=Se(os(e,n)),i=Pn(t,o);await ts(ns(i),{recursive:!0}),await es(n,i)}}async function Tn(e,t){await Wt(e,Pn(t,gt))}import{existsSync as ss}from"fs";import{readFile as Rn,readdir as as}from"fs/promises";import{join as re,dirname as cs,resolve as ls,sep as ps}from"path";import{fileURLToPath as ds}from"url";var nt={"claude-code":".claude/skills",cursor:".cursor/skills",codex:".agents/skills","gemini-cli":".gemini/skills",windsurf:".windsurf/skills"},Sp=Object.values(nt),Xt="generatesaas-update",_n=cs(ds(import.meta.url));function us(){let e=re(_n,"skill","content");return ss(e)?e:re(_n,"content")}function Zt(e){return!e||e.length===0?[]:e.map(t=>nt[t])}async function Qt(e,t,r,n){let o=Zt(n);for(let i of o){let s=re(e,i,Xt),a=re(s,"scripts"),p=re(s,"references");await bt(a),await bt(p),await c(re(s,"SKILL.md"),t.replaceAll("__SKILL_ROOT__",i)),await c(re(p,".gitkeep"),"");for(let[f,m]of Object.entries(r)){let u=ls(a,f);u.startsWith(a+ps)&&await c(u,m)}}}async function On(e,t){let r=us(),n=await Rn(re(r,"SKILL.md"),"utf-8"),o=re(r,"scripts"),i=await as(o),s={};for(let a of i)a!==".gitkeep"&&(s[a]=await Rn(re(o,a),"utf-8"));await Qt(e,n,s,t)}import{execFile as ms,execFileSync as fs}from"child_process";import{access as xn,readFile as gs}from"fs/promises";import{join as er}from"path";import*as C from"@clack/prompts";function Ae(e){try{let t=process.platform==="win32"?"where":"which";return fs(t,[e],{stdio:"ignore"}),!0}catch{return!1}}function Ee(e,t,r,n=3e5){return new Promise((o,i)=>{ms(e,t,{cwd:r,timeout:n},(s,a,p)=>{if(s){let f=String(a||"").trim(),u=[String(p||"").trim(),f].filter(Boolean).join(`
1247
+ `);i(new Error(u?`${s.message}
1248
+ ${u}`:s.message))}else o()})})}async function Dn(e){if(!Ae("pnpm"))return C.log.warn("pnpm not found. Skipping lockfile regeneration."),!1;try{return await Ee("pnpm",["install","--lockfile-only","--no-frozen-lockfile","--config.minimumReleaseAge=0"],e),!0}catch(t){let r=t instanceof Error?t.message:String(t);return C.log.warn(`Lockfile regeneration failed: ${r}`),C.log.warn("Deploys using --frozen-lockfile may fail."),!1}}async function Cn(e){if(!Ae("pnpm"))return C.log.warn("pnpm not found. Skipping dependency installation."),C.log.info("Install pnpm: https://pnpm.io/installation"),!1;let t=C.spinner();t.start("Installing dependencies (this may take a minute)...");try{return await Ee("pnpm",["install","--config.minimumReleaseAge=0"],e),t.stop("Dependencies installed."),!0}catch(r){t.stop("Dependency installation failed.");let n=r instanceof Error?r.message:String(r);return C.log.warn(`pnpm install failed: ${n}`),C.log.warn("You can run it manually later."),!1}}async function Nn(e){if(!Ae("pnpm"))return!1;let t=C.spinner();t.start("Generating baseline database migration...");try{return await Ee("pnpm",["-F","@repo/database","generate"],e),t.stop("Baseline migration generated."),!0}catch(r){t.stop("Baseline migration generation failed.");let n=r instanceof Error?r.message:String(r);return C.log.warn(`Could not generate baseline migration: ${n}`),C.log.warn("Run 'pnpm -F @repo/database generate' before your first deploy."),!1}}async function Ln(e){try{return await xn(er(e,".git")),C.log.info("Git repository already exists, skipping init."),!0}catch{}if(!Ae("git"))return C.log.warn("git not found. Skipping repository initialization."),!1;let t=C.spinner();t.start("Initializing git repository...");try{return await Ee("git",["init"],e),await Ee("git",["add","-A"],e),await Ee("git",["commit","--no-verify","-m","Initial commit from GenerateSaaS"],e),t.stop("Git repository initialized."),!0}catch{return t.stop("Git initialization failed."),C.log.warn("You can run git init manually later."),!1}}async function $n(e){if(!Ae("pnpm"))return!1;try{await xn(er(e,".git"))}catch{return!1}try{let t=JSON.parse(await gs(er(e,"package.json"),"utf-8")),r=!!t.devDependencies?.["simple-git-hooks"],n=!!t["simple-git-hooks"];if(!r||!n)return!1}catch{return!1}try{return await Ee("pnpm",["exec","simple-git-hooks"],e),!0}catch{return C.log.warn("Could not install git hooks. Run 'pnpm exec simple-git-hooks' manually."),!1}}import*as ze from"@clack/prompts";import H from"picocolors";function jn(e,t){t.dockerComposeGenerated&&!t.dockerAvailable&&ze.log.warn("Docker not found. Install Docker to run local services: https://docs.docker.com/get-docker/");let r=[];if(r.push(`cd ${e.projectDir}`),t.pnpmInstalled||r.push("pnpm install"),t.dockerComposeGenerated){let i=e.dockerServices.map(s=>Te[s].label).join(", ");r.push(`pnpm infra ${H.dim(`# ${i}`)}`)}if(r.push(`pnpm dev ${H.dim("# http://localhost:3000")}`),t.skippedCredentials.length>0&&(r.push(""),r.push(H.dim("Fill in remaining TODO values in .env"))),ze.note(r.join(`
1239
1249
  `),H.yellow("Start Development")),t.dockerComposeGenerated){let i=[];i.push(`App ${H.cyan("http://localhost:3000")}`),e.architecture==="separate"&&i.push(`API ${H.cyan("http://localhost:3010")}`),e.dockerServices.includes("mailpit")&&i.push(`Mailpit ${H.cyan("http://localhost:8025")}`),e.dockerServices.includes("inngest")&&i.push(`Inngest ${H.cyan("http://localhost:8288")}`),ze.note(i.join(`
1240
- `),H.yellow("Dev Tools"))}let n=[],o=fs(e);o.length>0&&n.push(`Set in production: ${H.dim(o.join(", "))}`),n.push("pnpm db:push # Run database migrations"),n.push(gs(e)),ze.note(n.join(`
1241
- `),H.yellow("Deployment"))}function fs(e){let t=["DATABASE_URL","BETTER_AUTH_SECRET"];return e.cacheProvider==="upstash"?t.push("UPSTASH_REDIS_REST_URL","UPSTASH_REDIS_REST_TOKEN"):t.push("REDIS_URL"),e.paymentProvider==="stripe"?t.push("STRIPE_SECRET_KEY","STRIPE_WEBHOOK_SECRET"):e.paymentProvider==="polar"&&t.push("POLAR_ACCESS_TOKEN","POLAR_WEBHOOK_SECRET"),e.emailProvider==="ses"?t.push("AMAZON_SES_REGION","AMAZON_SES_KEY","AMAZON_SES_SECRET"):e.emailProvider==="resend"?t.push("RESEND_API_KEY"):t.push("SMTP_HOST","SMTP_PORT"),t}function gs(e){switch(e.deploymentTarget){case"node":return"Deploy with Docker or your preferred Node.js host";case"vercel":return"vercel deploy # Deploy to Vercel"}}function jn(e){let t={};if(e.name!==void 0){if(!mt(e.name))throw new Error(`Invalid project name "${e.name}". Use lowercase letters, numbers, and hyphens only. Must start with a letter.`);t.projectName=e.name}if(e.appName!==void 0){if(!e.appName.trim())throw new Error("App name cannot be empty.");t.appName=e.appName}if(e.location!==void 0?t.projectDir=e.location==="."?process.cwd():e.location:t.projectName!==void 0&&(t.projectDir=`./${t.projectName}`),e.frontend!==void 0){if(!ye.includes(e.frontend))throw new Error(`Invalid frontend "${e.frontend}". Valid values: ${ye.join(", ")}`);t.frontend=e.frontend}if(e.architecture!==void 0&&(t.architecture=e.architecture),e.payment!==void 0&&(t.paymentProvider=e.payment),e.email!==void 0&&(t.emailProvider=e.email),e.org!==void 0&&(t.multiTenancy=e.org),e.billingScope!==void 0){if(e.org===!1)throw new Error("--billing-scope requires --org to be enabled.");t.billingScope=e.billingScope}if(e.blog!==void 0&&(t.blog=e.blog),e.docs!==void 0&&(t.docs=e.docs),e.desktop!==void 0&&(t.desktop=e.desktop),e.desktopAuto!==void 0&&(t.desktopAutoRelease=e.desktopAuto),e.desktopAi!==void 0){if(e.desktopAi===!0&&e.desktop!==!0)throw new Error("--desktop-ai requires --desktop to be enabled.");t.desktopAi=e.desktopAi}if(e.revenueSharing!==void 0&&(t.revenueSharing=e.revenueSharing),e.credits!==void 0){if(e.credits===!0&&e.payment==="none")throw new Error("--credits requires a payment provider (got --payment none).");t.credits=e.credits}if(e.docker!==void 0&&(t.dockerServices=tr(e.docker,xe,"docker service")),e.aiTools!==void 0&&(t.aiTools=tr(e.aiTools,De,"AI tool")),e.socialProviders!==void 0&&(t.socialProviders=tr(e.socialProviders,Ne,"social provider")),e.currency!==void 0){if(!oe.includes(e.currency))throw new Error(`Invalid currency "${e.currency}". Valid values: ${oe.join(", ")}`);t.defaultCurrency=e.currency}if(e.deploy!==void 0){if(!ie.includes(e.deploy))throw new Error(`Invalid deployment target "${e.deploy}". Valid values: ${ie.join(", ")}`);t.deploymentTarget=e.deploy}if(e.database!==void 0){if(!se.includes(e.database))throw new Error(`Invalid database provider "${e.database}". Valid values: ${se.join(", ")}`);t.databaseProvider=e.database}if(e.cache!==void 0){if(!ae.includes(e.cache))throw new Error(`Invalid cache provider "${e.cache}". Valid values: ${ae.join(", ")}`);t.cacheProvider=e.cache}if(e.demo===!0&&(t.demo=!0),e.baseUrl!==void 0){let r=e.baseUrl.trim();if(r==="")throw new Error("--base-url cannot be empty. Provide an absolute URL like https://example.com.");let n;try{n=new URL(r)}catch{throw new Error(`Invalid --base-url "${e.baseUrl}". Must be an absolute URL like https://example.com.`)}if(n.protocol!=="http:"&&n.protocol!=="https:")throw new Error(`Invalid --base-url "${e.baseUrl}". Must use http or https.`);t.baseUrl=`${n.protocol}//${n.host}`}return t}var fe={projectName:"my-saas",frontend:"nextjs",architecture:"fullstack",paymentProvider:"stripe",emailProvider:"smtp",multiTenancy:!1,billingScope:"user",blog:!0,docs:!1,desktop:!1,desktopAutoRelease:!1,desktopAi:!1,revenueSharing:!1,credits:!0,dockerServices:["postgres","redis","inngest"],aiTools:[],socialProviders:[],defaultCurrency:"USD",deploymentTarget:"node",databaseProvider:"postgres",cacheProvider:"redis"};function Un(e){let t=e.projectName??fe.projectName,r=e.projectDir??`./${t}`,n=e.appName??ut(t),o=e.deploymentTarget??fe.deploymentTarget,i=Q[o]?.edgeRuntime??!1,s=e.databaseProvider??(i?"neon":fe.databaseProvider),a=e.cacheProvider??(i?"upstash":fe.cacheProvider),d=e.emailProvider??(i?"resend":fe.emailProvider),u=e.dockerServices??(i?fe.dockerServices.filter(f=>f!=="postgres"&&f!=="redis"):fe.dockerServices),m={...fe,...e,projectName:t,appName:n,projectDir:r,deploymentTarget:o,databaseProvider:s,cacheProvider:a,emailProvider:d,dockerServices:u};m.paymentProvider==="none"&&(m.credits=!1);for(let f of Xe){if(m.deploymentTarget!==f.target)continue;let v=m.databaseProvider===f.provider?"database":"cache";if(m.databaseProvider===f.provider||m.cacheProvider===f.provider)throw new Error(`Incompatible: --deploy ${f.target} + --${v} ${f.provider}. ${f.reason}`)}for(let f of Ze)if(m.architecture===f.architecture&&m.deploymentTarget===f.target)throw new Error(`Incompatible: --architecture ${f.architecture} + --deploy ${f.target}. ${f.reason}`);return m}function tr(e,t,r){if(e.trim()==="")return[];let n=e.split(",").map(i=>i.trim()).filter(Boolean),o=n.filter(i=>!t.includes(i));if(o.length>0)throw new Error(`Invalid ${r}(s): ${o.join(", ")}. Valid values: ${t.join(", ")}`);return n}import ks from"picocolors";var bs="a10a6fb9d7cadde32e37dad52059d17b5d2b916b08c76d8fbcc99982e9a3d87f";async function ws(e){let t=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(`generatesaas-demo:${e}`)),r=[...new Uint8Array(t).subarray(0,16)].map(n=>n.toString(16).padStart(2,"0")).join("");return`${r.slice(0,8)}-${r.slice(8,12)}-${r.slice(12,16)}-${r.slice(16,20)}-${r.slice(20,32)}`}function As(e){if(e===void 0)return;let t=e.trim().replace(/^v/,"");if(!/^\d+\.\d+\.\d+$/.test(t))throw new Error(`Invalid template version "${e}". Use semver like 1.2.3.`);return t}function Mn(e){e.command("init").description("Scaffold a new GenerateSaaS project").argument("[apiKey]","license key (same as --api-key)").option("-n, --name <name>","project name (lowercase, hyphens, starts with letter)").option("--app-name <name>","display name for the app").option("-l, --location <path>","project directory (default: ./{name})").addOption(new Y("--frontend <type>","frontend framework").choices([...ye])).addOption(new Y("--architecture <type>","fullstack or separate").choices([...Re])).addOption(new Y("--payment <provider>","payment provider").choices([..._e])).addOption(new Y("--email <provider>","email provider").choices([...Oe])).option("--org","enable multi-tenancy (organizations)").option("--no-org","disable multi-tenancy").addOption(new Y("--billing-scope <scope>","billing scope (requires --org)").choices([...Ce])).option("--blog","enable blog").option("--no-blog","disable blog").option("--docs","include the docs app (apps/docs, Fumadocs)").option("--no-docs","exclude the docs app").option("--desktop","include the Electron desktop app (apps/desktop)").option("--no-desktop","exclude the Electron desktop app").option("--desktop-auto","auto-trigger the desktop release workflow on CI success (default: manual-only)").option("--no-desktop-auto","manual-only desktop releases (workflow_dispatch)").option("--desktop-ai","include the desktop AI orchestration layer (requires --desktop)").option("--no-desktop-ai","exclude the desktop AI orchestration layer").option("--revenue-sharing","enable revenue sharing").option("--no-revenue-sharing","disable revenue sharing").option("--credits","enable credits system").option("--no-credits","disable credits system (subscription-only)").option("--docker <services>","comma-separated: postgres,redis,inngest,mailpit").option("--ai-tools <tools>","comma-separated: claude-code,cursor,codex,gemini-cli,windsurf").option("--social-providers <providers>","comma-separated: google,github,facebook,discord,x").addOption(new Y("--currency <code>","default currency for billing").choices([...oe])).addOption(new Y("--deploy <target>","deployment target").choices([...ie])).addOption(new Y("--database <provider>","database provider").choices([...se])).addOption(new Y("--cache <provider>","cache provider").choices([...ae])).option("--template-version <version>","specific template version to scaffold").option("--api-key <key>","API key (skips interactive prompt)").option("--base-url <url>","public base URL (e.g. https://example.com) - bakes into canonical/og/sitemap").option("-y, --yes","accept defaults for unspecified options (non-interactive)").addOption(new Y("--demo","first-party demo build: keep sample content, mark site non-indexable - requires CI API key").hideHelp()).addOption(new Y("--no-db-migration","skip generating the baseline DB migration (internal: demos/CI/playground)").hideHelp()).action(async(t,r)=>{await Is(t?{...r,apiKey:t}:r)})}async function Is(e){let t=performance.now();ar("1.19.0");let r,n;try{r=jn(e),n=As(e.templateVersion)}catch(S){k.cancel(O(S)),process.exit(1)}let o=k.spinner(),i;try{i=await Le({apiKey:e.apiKey,prompt:!e.yes})}catch(S){k.cancel(O(S)),process.exit(1)}e.demo&&Jt(i)!==bs&&(k.cancel("--demo is restricted to first-party demo deployments."),process.exit(1));let s=le(i),a=async()=>{let S=await me(s),T=S.latest,N=n??T;if(n&&!S.versions.some(G=>G.version===N))throw new Error(`Template version "${n}" is not available.`);return{latestVersion:T,selectedVersion:N,desktopAllowed:S.entitlements?.desktopAllowed??!0}};o.start("Verifying access...");let d,u,m=!0;try{({latestVersion:d,selectedVersion:u,desktopAllowed:m}=await a()),o.stop("Access verified."),Se(i)}catch(S){if(o.stop("Access verification failed."),S instanceof L&&S.status===401){e.yes&&(k.cancel("Invalid API key. Cannot prompt in non-interactive mode."),process.exit(1)),k.log.warning("Invalid API key."),i=await tt(),s=le(i),o.start("Verifying access...");try{({latestVersion:d,selectedVersion:u,desktopAllowed:m}=await a()),o.stop("Access verified."),Se(i)}catch(T){o.stop("Access verification failed."),k.cancel(T instanceof L&&T.status===401?"Invalid API key.":O(T)),process.exit(1)}}else k.cancel(O(S)),process.exit(1)}k.log.success(`Latest version: ${d}`),u!==d&&k.log.success(`Using template version: ${u}`),r.desktop===!0&&!m&&(k.cancel("The desktop app is available on the Pro plan and up. Upgrade your plan at generatesaas.com."),process.exit(1));let f;e.yes?f=Un(m?r:{...r,desktop:!1}):f=await pr(r,{desktopAllowed:m});let v;o.start("Activating license...");try{let S=e.demo&&f.baseUrl?await ws(f.baseUrl):crypto.randomUUID(),T=()=>({frontend:f.frontend,version:u,installId:S,projectName:f.projectName,options:gr(f)}),N;try{N=await Lt(s,T())}catch(G){let A=ht(G);if(!A?.lastAllowedVersion)throw G;o.stop("License activation failed."),e.yes&&(k.cancel(`${A.message} Re-run with --template-version ${A.lastAllowedVersion}.`),process.exit(1));let Z=await k.confirm({message:`Your update window has ended. Continue with v${A.lastAllowedVersion} (the last version your license covers)?`});(k.isCancel(Z)||!Z)&&(k.cancel("Setup cancelled."),process.exit(0)),u=A.lastAllowedVersion,o.start(`Activating license for v${u}...`),N=await Lt(s,T())}v={token:N.token,keyHash:Jt(i),installId:N.installId??S},o.stop("License activated.")}catch(S){o.stop("License activation failed."),k.cancel(O(S)),process.exit(1)}let b=Es(f.projectDir);if(hs(b)&&ys(b).length>0)if(e.yes)k.log.info(`Directory ${b} is not empty. Merging (keeping existing files, overwriting conflicts).`);else{let T=await k.select({message:`Directory ${b} is not empty.`,options:[{value:"merge",label:"Merge",hint:"keep existing files, overwrite conflicts"},{value:"overwrite",label:"Overwrite",hint:"delete everything and start fresh"},{value:"cancel",label:"Cancel"}]});(k.isCancel(T)||T==="cancel")&&(k.cancel("Setup cancelled."),process.exit(0)),T==="overwrite"&&vs(b,{recursive:!0,force:!0})}let P={...f,projectDir:b,version:u,...e.demo?{docs:!1}:{}};o.start("Downloading template...");try{await yt(s,u,b),o.stop("Template downloaded.")}catch(S){o.stop("Download failed."),k.cancel(O(S)),process.exit(1)}let B;o.start("Generating project files...");try{if({dockerComposeGenerated:B}=await It(P),!e.demo){let S=await Ke(b);await l(Ss(b,ft),JSON.stringify(S,null," ")+`
1242
- `),await Pn(b,b)}await _n(b,P.aiTools),await An(P,v),o.stop("Project files generated.")}catch(S){o.stop("Generation failed."),k.cancel(O(S)),process.exit(1)}await xn(b);let C=await Dn(b);C&&P.demo!==!0&&e.dbMigration!==!1&&await Cn(b),await Nn(b),C&&await Ln(b);let W=we("docker"),Pe=Ct(P).map(S=>S.key).filter(S=>!P.credentials?.[S]);$n(P,{pnpmInstalled:C,dockerComposeGenerated:B,dockerAvailable:W,skippedCredentials:Pe}),cr(),k.log.info(ks.dim(`Done in ${((performance.now()-t)/1e3).toFixed(1)}s`))}import{existsSync as Kn}from"fs";import{readFile as zn}from"fs/promises";import{join as ot,resolve as xs}from"path";import*as D from"@clack/prompts";import He from"picocolors";import{mkdtemp as Ps,rm as Ts}from"fs/promises";import{tmpdir as Rs}from"os";import{join as _s}from"path";async function rr(e,t,r,n){let o=await Ps(_s(Rs(),"generatesaas-stage-"));try{await yt(e,t,o),await It({...r,projectDir:o}),await Wt(o,n)}finally{await Ts(o,{recursive:!0,force:!0})}}var Vn=[{key:"frontend",label:"Frontend framework",hint:"Nuxt (Vue) or Next.js (React).",category:"Project",kind:"enum",default:"nuxt",choices:ye,adoptable:!1,impact:"structural"},{key:"architecture",label:"Architecture",hint:"Fullstack (frontend hosts the API) or a standalone Hono backend.",category:"Infrastructure",kind:"enum",default:"fullstack",choices:Re,adoptable:!1,impact:"structural"},{key:"deploymentTarget",label:"Deployment target",hint:"Node.js / Docker or Vercel serverless.",category:"Infrastructure",kind:"enum",default:"node",choices:ie,adoptable:!1,impact:"config"},{key:"databaseProvider",label:"Database provider",hint:"Self-hosted PostgreSQL, Neon, or Supabase.",category:"Infrastructure",kind:"enum",default:"postgres",choices:se,adoptable:!1,impact:"config"},{key:"cacheProvider",label:"Cache provider",hint:"Self-hosted Redis or Upstash.",category:"Infrastructure",kind:"enum",default:"redis",choices:ae,adoptable:!1,impact:"config"},{key:"paymentProvider",label:"Payment provider",hint:"Stripe, Polar, or none.",category:"Features",kind:"enum",default:"none",choices:_e,adoptable:!1,impact:"structural"},{key:"defaultCurrency",label:"Default currency",hint:"Billing and pricing currency.",category:"Features",kind:"enum",default:"USD",choices:oe,adoptable:!1,impact:"config"},{key:"emailProvider",label:"Email provider",hint:"SMTP, Amazon SES, or Resend.",category:"Features",kind:"enum",default:"smtp",choices:Oe,adoptable:!1,impact:"config"},{key:"multiTenancy",label:"Multi-tenancy (organizations)",hint:"Adds organizations - teams, members, and shared resources.",category:"Features",kind:"boolean",default:!1,adoptable:!0,impact:"structural"},{key:"billingScope",label:"Billing scope",hint:"Whether subscriptions belong to a user or an organization.",category:"Features",kind:"enum",default:"user",choices:Ce,adoptable:!1,impact:"config",requires:e=>e.multiTenancy===!0,requiresLabel:"requires multi-tenancy"},{key:"blog",label:"Blog",hint:"Adds the marketing blog (content collection, list and post pages).",category:"Features",kind:"boolean",default:!1,adoptable:!0,impact:"structural"},{key:"docs",label:"Docs app",hint:"Adds the self-hosted Fumadocs documentation site (apps/docs).",category:"Features",kind:"boolean",default:!1,adoptable:!0,impact:"structural"},{key:"desktop",label:"Desktop app",hint:"Adds the cross-platform Electron desktop app (apps/desktop).",category:"Features",kind:"boolean",default:!1,adoptable:!0,impact:"structural"},{key:"desktopAutoRelease",label:"Desktop auto-release",hint:"Release the desktop app on every CI success (vs manual-only).",category:"Features",kind:"boolean",default:!0,adoptable:!1,impact:"config",requires:e=>e.desktop===!0,requiresLabel:"requires the desktop app"},{key:"desktopAi",label:"Desktop AI orchestration",hint:"Adds the desktop AI agents layer (requires the desktop app).",category:"Features",kind:"boolean",default:!1,adoptable:!0,impact:"structural",requires:e=>e.desktop===!0,requiresLabel:"requires the desktop app"},{key:"credits",label:"Metered credits",hint:"Adds metered usage credits on top of subscription plans.",category:"Features",kind:"boolean",default:!1,adoptable:!0,impact:"config",requires:e=>e.paymentProvider!==void 0&&e.paymentProvider!=="none",requiresLabel:"requires a payment provider"},{key:"revenueSharing",label:"Revenue sharing",hint:"Opt-in MRR leaderboard with dofollow backlinks.",category:"Features",kind:"boolean",default:!1,adoptable:!0,impact:"config"},{key:"socialProviders",label:"Social login providers",hint:"Which social sign-in buttons the auth screen shows.",category:"Features",kind:"multiselect",default:[],choices:Ne,adoptable:!1,impact:"config"},{key:"dockerServices",label:"Docker services",hint:"Local services scaffolded in docker-compose.",category:"Tooling",kind:"multiselect",default:[],choices:xe,adoptable:!1,impact:"config"},{key:"aiTools",label:"AI coding tools",hint:"Which AI assistants receive the bundled skill files.",category:"Tooling",kind:"multiselect",default:[],choices:De,adoptable:!1,impact:"config"}];function Os(e,t){return e[t]!==void 0}function Fn(e){return Vn.filter(t=>t.adoptable&&!Os(e,t.key)&&(t.requires?t.requires(e):!0)).map(t=>({key:t.key,label:t.label,hint:t.hint,kind:t.kind,default:t.default,...t.choices?{choices:t.choices}:{},impact:t.impact,...t.requiresLabel?{requiresLabel:t.requiresLabel}:{}}))}function Bn(e){let t={};for(let r of Vn)t[r.key]=Array.isArray(r.default)?[...r.default]:r.default;return{...t,...e}}function Gn(e){let r=(e.startsWith("v")?e.slice(1):e).match(/^(\d+)\.(\d+)\.(\d+)$/);return r?[Number(r[1]),Number(r[2]),Number(r[3])]:null}function Tt(e,t){let r=Gn(e),n=Gn(t);if(!r||!n)return 0;for(let o=0;o<3;o++)if(r[o]!==n[o])return r[o]-n[o];return 0}function Hn(e){e.command("update").description("Update AI skill files and stage template updates").argument("[mode]","pass 'auto' to mark the staged update for unattended apply by your AI assistant").option("--cwd <path>","project directory (default: current directory)").action(async(t,r)=>{let n=t==="auto"?"auto":void 0,o=xs(r.cwd??process.cwd()),i=ot(o,pe),s;try{s=JSON.parse(await zn(i,"utf-8"))}catch{D.cancel(".generatesaas/manifest.json not found. Run this from a GenerateSaaS project."),process.exit(1)}let a;try{a=await Le()}catch(m){D.cancel(O(m)),process.exit(1)}let d=le(a),u=D.spinner();try{u.start("Verifying access...");let m;try{m=await me(d)}catch(A){throw A instanceof L&&A.status===401?new Error("Your saved API key was rejected. Run `generatesaas auth` to update it, or set GENERATESAAS_API_KEY."):A}u.stop("Access verified."),Se(a),u.start("Fetching latest skill files...");let f=await fr(d,m.latest);await Qt(o,f.skillMd,f.scripts,s.aiTools);let v=Zt(s.aiTools);if(u.stop("Skills updated."),D.log.success(`Skill files installed to ${He.cyan(v.length.toString())} locations.`),s.version===m.latest){D.log.info(`Already on the latest version (${s.version}).`);return}let b=Fn(s),P=Bn(s),B=JSON.stringify(P)!==JSON.stringify(s);if(s=P,s.licenseToken)try{let A=await hr(d,{currentToken:s.licenseToken,newVersion:m.latest});s.licenseToken=A.token,A.licenseKeyHash&&(s.licenseKeyHash=A.licenseKeyHash),await l(i,JSON.stringify(s,null," ")+`
1243
- `),B=!1,D.log.success("License refreshed.")}catch(A){let Z=ht(A);Z&&(D.cancel(Z.message),process.exit(1)),D.log.warn("License refresh skipped.")}B&&await l(i,JSON.stringify(s,null," ")+`
1244
- `);let C=wn(s,o),W=ot(o,ur);u.start(`Staging v${m.latest} (shaped for your config)...`),await rr(d,m.latest,C,W),u.stop("Template staged.");let{text:X,title:Pe}=await Ds(d,m,s.version);X&&D.note(X,Pe);let S=ot(o,ft),T=ot(o,gt),N=!Kn(T),G=!Kn(S);if(N){if(u.start("Building baseline template (one-time migration)..."),await rr(d,s.version,C,T),G){let A=await Ke(T);await l(S,JSON.stringify(A,null," ")+`
1245
- `)}if(u.stop("Baseline template stored."),!G){let A=await Cs(S,T);A>0&&D.log.warn(`Rebuilt baseline differs from the original for ${A} file(s) (the CLI's shaping evolved since this project was scaffolded). Classification still follows the committed template-hashes.json; upstream diffs for those files may include unrelated noise.`)}}else if(G){u.start("Computing baseline template hashes...");let A=await Ke(T);await l(S,JSON.stringify(A,null," ")+`
1246
- `),u.stop("Baseline hashes computed.")}if(await l(ot(o,mr),JSON.stringify({currentVersion:s.version,targetVersion:m.latest,changelog:X,stagedAt:new Date().toISOString(),...b.length>0?{newOptions:b}:{},...n?{mode:n}:{}},null," ")+`
1247
- `),D.log.info(`Update staged: ${He.cyan(s.version)} \u2192 ${He.cyan(m.latest)}`),s.aiTools&&s.aiTools.length>0){let A=s.aiTools[0],Z=We[A].label;D.log.info(`Open your project in ${He.cyan(Z)} and ask: ${He.cyan("'update my GenerateSaaS project'")}`)}else D.log.info(`Ask your AI coding assistant to ${He.cyan("'update my GenerateSaaS project'")}.`)}catch(m){u.stop("Failed."),D.cancel(`Update failed: ${O(m)}`),process.exit(1)}})}async function Ds(e,t,r){let n=t.latest,o=t.versions.filter(s=>Tt(s.version,r)>0&&Tt(s.version,n)<=0).sort((s,a)=>Tt(s.version,a.version));if(o.length<=1)return{text:await Nt(e,n),title:`Changelog v${n}`};let i=[];for(let s of o){let a=null;try{a=await Nt(e,s.version)}catch{a=null}let d=s.date?` (${s.date.slice(0,10)})`:"",u=s.breaking?" [BREAKING]":"";i.push(`# v${s.version}${d}${u}
1250
+ `),H.yellow("Dev Tools"))}let n=[],o=hs(e);o.length>0&&n.push(`Set in production: ${H.dim(o.join(", "))}`),n.push("pnpm db:push # Run database migrations"),n.push(ys(e)),ze.note(n.join(`
1251
+ `),H.yellow("Deployment"))}function hs(e){let t=["DATABASE_URL","BETTER_AUTH_SECRET"];return e.cacheProvider==="upstash"?t.push("UPSTASH_REDIS_REST_URL","UPSTASH_REDIS_REST_TOKEN"):t.push("REDIS_URL"),e.paymentProvider==="stripe"?t.push("STRIPE_SECRET_KEY","STRIPE_WEBHOOK_SECRET"):e.paymentProvider==="polar"&&t.push("POLAR_ACCESS_TOKEN","POLAR_WEBHOOK_SECRET"),e.emailProvider==="ses"?t.push("AMAZON_SES_REGION","AMAZON_SES_KEY","AMAZON_SES_SECRET"):e.emailProvider==="resend"?t.push("RESEND_API_KEY"):t.push("SMTP_HOST","SMTP_PORT"),t}function ys(e){switch(e.deploymentTarget){case"node":return"Deploy with Docker or your preferred Node.js host";case"vercel":return"vercel deploy # Deploy to Vercel"}}function Un(e){let t={};if(e.name!==void 0){if(!mt(e.name))throw new Error(`Invalid project name "${e.name}". Use lowercase letters, numbers, and hyphens only. Must start with a letter.`);t.projectName=e.name}if(e.appName!==void 0){if(!e.appName.trim())throw new Error("App name cannot be empty.");t.appName=e.appName}if(e.location!==void 0?t.projectDir=e.location==="."?process.cwd():e.location:t.projectName!==void 0&&(t.projectDir=`./${t.projectName}`),e.frontend!==void 0){if(!ve.includes(e.frontend))throw new Error(`Invalid frontend "${e.frontend}". Valid values: ${ve.join(", ")}`);t.frontend=e.frontend}if(e.architecture!==void 0&&(t.architecture=e.architecture),e.payment!==void 0&&(t.paymentProvider=e.payment),e.email!==void 0&&(t.emailProvider=e.email),e.org!==void 0&&(t.multiTenancy=e.org),e.billingScope!==void 0){if(e.org===!1)throw new Error("--billing-scope requires --org to be enabled.");t.billingScope=e.billingScope}if(e.blog!==void 0&&(t.blog=e.blog),e.docs!==void 0&&(t.docs=e.docs),e.desktop!==void 0&&(t.desktop=e.desktop),e.desktopAuto!==void 0&&(t.desktopAutoRelease=e.desktopAuto),e.desktopAi!==void 0){if(e.desktopAi===!0&&e.desktop!==!0)throw new Error("--desktop-ai requires --desktop to be enabled.");t.desktopAi=e.desktopAi}if(e.revenueSharing!==void 0&&(t.revenueSharing=e.revenueSharing),e.credits!==void 0){if(e.credits===!0&&e.payment==="none")throw new Error("--credits requires a payment provider (got --payment none).");t.credits=e.credits}if(e.docker!==void 0&&(t.dockerServices=tr(e.docker,xe,"docker service")),e.aiTools!==void 0&&(t.aiTools=tr(e.aiTools,De,"AI tool")),e.socialProviders!==void 0&&(t.socialProviders=tr(e.socialProviders,Ne,"social provider")),e.currency!==void 0){if(!ie.includes(e.currency))throw new Error(`Invalid currency "${e.currency}". Valid values: ${ie.join(", ")}`);t.defaultCurrency=e.currency}if(e.deploy!==void 0){if(!se.includes(e.deploy))throw new Error(`Invalid deployment target "${e.deploy}". Valid values: ${se.join(", ")}`);t.deploymentTarget=e.deploy}if(e.database!==void 0){if(!ae.includes(e.database))throw new Error(`Invalid database provider "${e.database}". Valid values: ${ae.join(", ")}`);t.databaseProvider=e.database}if(e.cache!==void 0){if(!ce.includes(e.cache))throw new Error(`Invalid cache provider "${e.cache}". Valid values: ${ce.join(", ")}`);t.cacheProvider=e.cache}if(e.demo===!0&&(t.demo=!0),e.baseUrl!==void 0){let r=e.baseUrl.trim();if(r==="")throw new Error("--base-url cannot be empty. Provide an absolute URL like https://example.com.");let n;try{n=new URL(r)}catch{throw new Error(`Invalid --base-url "${e.baseUrl}". Must be an absolute URL like https://example.com.`)}if(n.protocol!=="http:"&&n.protocol!=="https:")throw new Error(`Invalid --base-url "${e.baseUrl}". Must use http or https.`);t.baseUrl=`${n.protocol}//${n.host}`}return t}var ge={projectName:"my-saas",frontend:"nextjs",architecture:"fullstack",paymentProvider:"stripe",emailProvider:"smtp",multiTenancy:!1,billingScope:"user",blog:!0,docs:!1,desktop:!1,desktopAutoRelease:!1,desktopAi:!1,revenueSharing:!1,credits:!0,dockerServices:["postgres","redis","inngest"],aiTools:[],socialProviders:[],defaultCurrency:"USD",deploymentTarget:"node",databaseProvider:"postgres",cacheProvider:"redis"};function Mn(e){let t=e.projectName??ge.projectName,r=e.projectDir??`./${t}`,n=e.appName??ut(t),o=e.deploymentTarget??ge.deploymentTarget,i=Q[o]?.edgeRuntime??!1,s=e.databaseProvider??(i?"neon":ge.databaseProvider),a=e.cacheProvider??(i?"upstash":ge.cacheProvider),p=e.emailProvider??(i?"resend":ge.emailProvider),f=e.dockerServices??(i?ge.dockerServices.filter(u=>u!=="postgres"&&u!=="redis"):ge.dockerServices),m={...ge,...e,projectName:t,appName:n,projectDir:r,deploymentTarget:o,databaseProvider:s,cacheProvider:a,emailProvider:p,dockerServices:f};m.paymentProvider==="none"&&(m.credits=!1);for(let u of Xe){if(m.deploymentTarget!==u.target)continue;let v=m.databaseProvider===u.provider?"database":"cache";if(m.databaseProvider===u.provider||m.cacheProvider===u.provider)throw new Error(`Incompatible: --deploy ${u.target} + --${v} ${u.provider}. ${u.reason}`)}for(let u of Ze)if(m.architecture===u.architecture&&m.deploymentTarget===u.target)throw new Error(`Incompatible: --architecture ${u.architecture} + --deploy ${u.target}. ${u.reason}`);return m}function tr(e,t,r){if(e.trim()==="")return[];let n=e.split(",").map(i=>i.trim()).filter(Boolean),o=n.filter(i=>!t.includes(i));if(o.length>0)throw new Error(`Invalid ${r}(s): ${o.join(", ")}. Valid values: ${t.join(", ")}`);return n}import Es from"picocolors";var As="a10a6fb9d7cadde32e37dad52059d17b5d2b916b08c76d8fbcc99982e9a3d87f";async function Is(e){let t=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(`generatesaas-demo:${e}`)),r=[...new Uint8Array(t).subarray(0,16)].map(n=>n.toString(16).padStart(2,"0")).join("");return`${r.slice(0,8)}-${r.slice(8,12)}-${r.slice(12,16)}-${r.slice(16,20)}-${r.slice(20,32)}`}function Ps(e){if(e===void 0)return;let t=e.trim().replace(/^v/,"");if(!/^\d+\.\d+\.\d+$/.test(t))throw new Error(`Invalid template version "${e}". Use semver like 1.2.3.`);return t}function Vn(e){e.command("init").description("Scaffold a new GenerateSaaS project").argument("[apiKey]","license key (same as --api-key)").option("-n, --name <name>","project name (lowercase, hyphens, starts with letter)").option("--app-name <name>","display name for the app").option("-l, --location <path>","project directory (default: ./{name})").addOption(new Y("--frontend <type>","frontend framework").choices([...ve])).addOption(new Y("--architecture <type>","fullstack or separate").choices([...Re])).addOption(new Y("--payment <provider>","payment provider").choices([..._e])).addOption(new Y("--email <provider>","email provider").choices([...Oe])).option("--org","enable multi-tenancy (organizations)").option("--no-org","disable multi-tenancy").addOption(new Y("--billing-scope <scope>","billing scope (requires --org)").choices([...Ce])).option("--blog","enable blog").option("--no-blog","disable blog").option("--docs","include the docs app (apps/docs, Fumadocs)").option("--no-docs","exclude the docs app").option("--desktop","include the Electron desktop app (apps/desktop)").option("--no-desktop","exclude the Electron desktop app").option("--desktop-auto","auto-trigger the desktop release workflow on CI success (default: manual-only)").option("--no-desktop-auto","manual-only desktop releases (workflow_dispatch)").option("--desktop-ai","include the desktop AI orchestration layer (requires --desktop)").option("--no-desktop-ai","exclude the desktop AI orchestration layer").option("--revenue-sharing","enable revenue sharing").option("--no-revenue-sharing","disable revenue sharing").option("--credits","enable credits system").option("--no-credits","disable credits system (subscription-only)").option("--docker <services>","comma-separated: postgres,redis,inngest,mailpit").option("--ai-tools <tools>","comma-separated: claude-code,cursor,codex,gemini-cli,windsurf").option("--social-providers <providers>","comma-separated: google,github,facebook,discord,x").addOption(new Y("--currency <code>","default currency for billing").choices([...ie])).addOption(new Y("--deploy <target>","deployment target").choices([...se])).addOption(new Y("--database <provider>","database provider").choices([...ae])).addOption(new Y("--cache <provider>","cache provider").choices([...ce])).option("--template-version <version>","specific template version to scaffold").option("--api-key <key>","API key (skips interactive prompt)").option("--base-url <url>","public base URL (e.g. https://example.com) - bakes into canonical/og/sitemap").option("-y, --yes","accept defaults for unspecified options (non-interactive)").addOption(new Y("--demo","first-party demo build: keep sample content, mark site non-indexable - requires CI API key").hideHelp()).addOption(new Y("--no-db-migration","skip generating the baseline DB migration (internal: demos/CI/playground)").hideHelp()).action(async(t,r)=>{await Ts(t?{...r,apiKey:t}:r)})}async function Ts(e){let t=performance.now();ar("1.19.2");let r,n;try{r=Un(e),n=Ps(e.templateVersion)}catch(b){E.cancel(x(b)),process.exit(1)}let o=E.spinner(),i;try{i=await Le({apiKey:e.apiKey,prompt:!e.yes})}catch(b){E.cancel(x(b)),process.exit(1)}e.demo&&Jt(i)!==As&&(E.cancel("--demo is restricted to first-party demo deployments."),process.exit(1));let s=pe(i),a=async()=>{let b=await fe(s),I=b.latest,R=n??I;if(n&&!b.versions.some(K=>K.version===R))throw new Error(`Template version "${n}" is not available.`);return{latestVersion:I,selectedVersion:R,desktopAllowed:b.entitlements?.desktopAllowed??!0}};o.start("Verifying access...");let p,f,m=!0;try{({latestVersion:p,selectedVersion:f,desktopAllowed:m}=await a()),o.stop("Access verified."),ke(i)}catch(b){if(o.stop("Access verification failed."),b instanceof $&&b.status===401){e.yes&&(E.cancel("Invalid API key. Cannot prompt in non-interactive mode."),process.exit(1)),E.log.warning("Invalid API key."),i=await tt(),s=pe(i),o.start("Verifying access...");try{({latestVersion:p,selectedVersion:f,desktopAllowed:m}=await a()),o.stop("Access verified."),ke(i)}catch(I){o.stop("Access verification failed."),E.cancel(I instanceof $&&I.status===401?"Invalid API key.":x(I)),process.exit(1)}}else E.cancel(x(b)),process.exit(1)}E.log.success(`Latest version: ${p}`),f!==p&&E.log.success(`Using template version: ${f}`),r.desktop===!0&&!m&&(E.cancel("The desktop app is available on the Pro plan and up. Upgrade your plan at generatesaas.com."),process.exit(1));let u;e.yes?u=Mn(m?r:{...r,desktop:!1}):u=await pr(r,{desktopAllowed:m});let v;o.start("Activating license...");try{let b=e.demo&&u.baseUrl?await Is(u.baseUrl):crypto.randomUUID(),I=()=>({frontend:u.frontend,version:f,installId:b,projectName:u.projectName,options:gr(u)}),R;try{R=await Lt(s,I())}catch(K){let A=ht(K);if(!A?.lastAllowedVersion)throw K;o.stop("License activation failed."),e.yes&&(E.cancel(`${A.message} Re-run with --template-version ${A.lastAllowedVersion}.`),process.exit(1));let Z=await E.confirm({message:`Your update window has ended. Continue with v${A.lastAllowedVersion} (the last version your license covers)?`});(E.isCancel(Z)||!Z)&&(E.cancel("Setup cancelled."),process.exit(0)),f=A.lastAllowedVersion,o.start(`Activating license for v${f}...`),R=await Lt(s,I())}v={token:R.token,keyHash:Jt(i),installId:R.installId??b},o.stop("License activated.")}catch(b){o.stop("License activation failed."),E.cancel(x(b)),process.exit(1)}let w=ws(u.projectDir);if(vs(w)&&bs(w).length>0)if(e.yes)E.log.info(`Directory ${w} is not empty. Merging (keeping existing files, overwriting conflicts).`);else{let I=await E.select({message:`Directory ${w} is not empty.`,options:[{value:"merge",label:"Merge",hint:"keep existing files, overwrite conflicts"},{value:"overwrite",label:"Overwrite",hint:"delete everything and start fresh"},{value:"cancel",label:"Cancel"}]});(E.isCancel(I)||I==="cancel")&&(E.cancel("Setup cancelled."),process.exit(0)),I==="overwrite"&&ks(w,{recursive:!0,force:!0})}let T={...u,projectDir:w,version:f,...e.demo?{docs:!1}:{}};o.start("Downloading template...");try{await yt(s,f,w),o.stop("Template downloaded.")}catch(b){o.stop("Download failed."),E.cancel(x(b)),process.exit(1)}let G;o.start("Generating project files...");try{if({dockerComposeGenerated:G}=await It(T),!e.demo){let b=await Ke(w);await c(Ss(w,ft),JSON.stringify(b,null," ")+`
1252
+ `),await Tn(w,w)}await On(w,T.aiTools),await In(T,v),o.stop("Project files generated.")}catch(b){o.stop("Generation failed."),E.cancel(x(b)),process.exit(1)}await Dn(w);let L=await Cn(w);L&&T.demo!==!0&&e.dbMigration!==!1&&await Nn(w),await Ln(w),L&&await $n(w);let W=Ae("docker"),ne=Ct(T).map(b=>b.key).filter(b=>!T.credentials?.[b]);jn(T,{pnpmInstalled:L,dockerComposeGenerated:G,dockerAvailable:W,skippedCredentials:ne}),cr(),E.log.info(Es.dim(`Done in ${((performance.now()-t)/1e3).toFixed(1)}s`))}import{existsSync as zn}from"fs";import{readFile as Hn}from"fs/promises";import{join as ot,resolve as Cs}from"path";import*as N from"@clack/prompts";import He from"picocolors";import{mkdtemp as Rs,rm as _s}from"fs/promises";import{tmpdir as Os}from"os";import{join as xs}from"path";async function rr(e,t,r,n){let o=await Rs(xs(Os(),"generatesaas-stage-"));try{await yt(e,t,o),await It({...r,projectDir:o}),await Wt(o,n)}finally{await _s(o,{recursive:!0,force:!0})}}var Fn=[{key:"frontend",label:"Frontend framework",hint:"Nuxt (Vue) or Next.js (React).",category:"Project",kind:"enum",default:"nuxt",choices:ve,adoptable:!1,impact:"structural"},{key:"architecture",label:"Architecture",hint:"Fullstack (frontend hosts the API) or a standalone Hono backend.",category:"Infrastructure",kind:"enum",default:"fullstack",choices:Re,adoptable:!1,impact:"structural"},{key:"deploymentTarget",label:"Deployment target",hint:"Node.js / Docker or Vercel serverless.",category:"Infrastructure",kind:"enum",default:"node",choices:se,adoptable:!1,impact:"config"},{key:"databaseProvider",label:"Database provider",hint:"Self-hosted PostgreSQL, Neon, or Supabase.",category:"Infrastructure",kind:"enum",default:"postgres",choices:ae,adoptable:!1,impact:"config"},{key:"cacheProvider",label:"Cache provider",hint:"Self-hosted Redis or Upstash.",category:"Infrastructure",kind:"enum",default:"redis",choices:ce,adoptable:!1,impact:"config"},{key:"paymentProvider",label:"Payment provider",hint:"Stripe, Polar, or none.",category:"Features",kind:"enum",default:"none",choices:_e,adoptable:!1,impact:"structural"},{key:"defaultCurrency",label:"Default currency",hint:"Billing and pricing currency.",category:"Features",kind:"enum",default:"USD",choices:ie,adoptable:!1,impact:"config"},{key:"emailProvider",label:"Email provider",hint:"SMTP, Amazon SES, or Resend.",category:"Features",kind:"enum",default:"smtp",choices:Oe,adoptable:!1,impact:"config"},{key:"multiTenancy",label:"Multi-tenancy (organizations)",hint:"Adds organizations - teams, members, and shared resources.",category:"Features",kind:"boolean",default:!1,adoptable:!0,impact:"structural"},{key:"billingScope",label:"Billing scope",hint:"Whether subscriptions belong to a user or an organization.",category:"Features",kind:"enum",default:"user",choices:Ce,adoptable:!1,impact:"config",requires:e=>e.multiTenancy===!0,requiresLabel:"requires multi-tenancy"},{key:"blog",label:"Blog",hint:"Adds the marketing blog (content collection, list and post pages).",category:"Features",kind:"boolean",default:!1,adoptable:!0,impact:"structural"},{key:"docs",label:"Docs app",hint:"Adds the self-hosted Fumadocs documentation site (apps/docs).",category:"Features",kind:"boolean",default:!1,adoptable:!0,impact:"structural"},{key:"desktop",label:"Desktop app",hint:"Adds the cross-platform Electron desktop app (apps/desktop).",category:"Features",kind:"boolean",default:!1,adoptable:!0,impact:"structural"},{key:"desktopAutoRelease",label:"Desktop auto-release",hint:"Release the desktop app on every CI success (vs manual-only).",category:"Features",kind:"boolean",default:!0,adoptable:!1,impact:"config",requires:e=>e.desktop===!0,requiresLabel:"requires the desktop app"},{key:"desktopAi",label:"Desktop AI orchestration",hint:"Adds the desktop AI agents layer (requires the desktop app).",category:"Features",kind:"boolean",default:!1,adoptable:!0,impact:"structural",requires:e=>e.desktop===!0,requiresLabel:"requires the desktop app"},{key:"credits",label:"Metered credits",hint:"Adds metered usage credits on top of subscription plans.",category:"Features",kind:"boolean",default:!1,adoptable:!0,impact:"config",requires:e=>e.paymentProvider!==void 0&&e.paymentProvider!=="none",requiresLabel:"requires a payment provider"},{key:"revenueSharing",label:"Revenue sharing",hint:"Opt-in MRR leaderboard with dofollow backlinks.",category:"Features",kind:"boolean",default:!1,adoptable:!0,impact:"config"},{key:"socialProviders",label:"Social login providers",hint:"Which social sign-in buttons the auth screen shows.",category:"Features",kind:"multiselect",default:[],choices:Ne,adoptable:!1,impact:"config"},{key:"dockerServices",label:"Docker services",hint:"Local services scaffolded in docker-compose.",category:"Tooling",kind:"multiselect",default:[],choices:xe,adoptable:!1,impact:"config"},{key:"aiTools",label:"AI coding tools",hint:"Which AI assistants receive the bundled skill files.",category:"Tooling",kind:"multiselect",default:[],choices:De,adoptable:!1,impact:"config"}];function Ds(e,t){return e[t]!==void 0}function Bn(e){return Fn.filter(t=>t.adoptable&&!Ds(e,t.key)&&(t.requires?t.requires(e):!0)).map(t=>({key:t.key,label:t.label,hint:t.hint,kind:t.kind,default:t.default,...t.choices?{choices:t.choices}:{},impact:t.impact,...t.requiresLabel?{requiresLabel:t.requiresLabel}:{}}))}function Gn(e){let t={};for(let r of Fn)t[r.key]=Array.isArray(r.default)?[...r.default]:r.default;return{...t,...e}}function Kn(e){let r=(e.startsWith("v")?e.slice(1):e).match(/^(\d+)\.(\d+)\.(\d+)$/);return r?[Number(r[1]),Number(r[2]),Number(r[3])]:null}function Tt(e,t){let r=Kn(e),n=Kn(t);if(!r||!n)return 0;for(let o=0;o<3;o++)if(r[o]!==n[o])return r[o]-n[o];return 0}function Yn(e){e.command("update").description("Update AI skill files and stage template updates").argument("[mode]","pass 'auto' to mark the staged update for unattended apply by your AI assistant").option("--cwd <path>","project directory (default: current directory)").action(async(t,r)=>{let n=t==="auto"?"auto":void 0,o=Cs(r.cwd??process.cwd()),i=ot(o,de),s;try{s=JSON.parse(await Hn(i,"utf-8"))}catch{N.cancel(".generatesaas/manifest.json not found. Run this from a GenerateSaaS project."),process.exit(1)}let a;try{a=await Le()}catch(m){N.cancel(x(m)),process.exit(1)}let p=pe(a),f=N.spinner();try{f.start("Verifying access...");let m;try{m=await fe(p)}catch(A){throw A instanceof $&&A.status===401?new Error("Your saved API key was rejected. Run `generatesaas auth` to update it, or set GENERATESAAS_API_KEY."):A}f.stop("Access verified."),ke(a),f.start("Fetching latest skill files...");let u=await fr(p,m.latest);await Qt(o,u.skillMd,u.scripts,s.aiTools);let v=Zt(s.aiTools);if(f.stop("Skills updated."),N.log.success(`Skill files installed to ${He.cyan(v.length.toString())} locations.`),s.version===m.latest){N.log.info(`Already on the latest version (${s.version}).`);return}let w=Bn(s),T=Gn(s),G=JSON.stringify(T)!==JSON.stringify(s);if(s=T,s.licenseToken)try{let A=await hr(p,{currentToken:s.licenseToken,newVersion:m.latest});s.licenseToken=A.token,A.licenseKeyHash&&(s.licenseKeyHash=A.licenseKeyHash),await c(i,JSON.stringify(s,null," ")+`
1253
+ `),G=!1,N.log.success("License refreshed.")}catch(A){let Z=ht(A);Z&&(N.cancel(Z.message),process.exit(1)),N.log.warn("License refresh skipped.")}G&&await c(i,JSON.stringify(s,null," ")+`
1254
+ `);let L=An(s,o),W=ot(o,ur);f.start(`Staging v${m.latest} (shaped for your config)...`),await rr(p,m.latest,L,W),f.stop("Template staged.");let{text:X,title:ne}=await Ns(p,m,s.version);X&&N.note(X,ne);let b=ot(o,ft),I=ot(o,gt),R=!zn(I),K=!zn(b);if(R){if(f.start("Building baseline template (one-time migration)..."),await rr(p,s.version,L,I),K){let A=await Ke(I);await c(b,JSON.stringify(A,null," ")+`
1255
+ `)}if(f.stop("Baseline template stored."),!K){let A=await Ls(b,I);A>0&&N.log.warn(`Rebuilt baseline differs from the original for ${A} file(s) (the CLI's shaping evolved since this project was scaffolded). Classification still follows the committed template-hashes.json; upstream diffs for those files may include unrelated noise.`)}}else if(K){f.start("Computing baseline template hashes...");let A=await Ke(I);await c(b,JSON.stringify(A,null," ")+`
1256
+ `),f.stop("Baseline hashes computed.")}if(await c(ot(o,mr),JSON.stringify({currentVersion:s.version,targetVersion:m.latest,changelog:X,stagedAt:new Date().toISOString(),...w.length>0?{newOptions:w}:{},...n?{mode:n}:{}},null," ")+`
1257
+ `),N.log.info(`Update staged: ${He.cyan(s.version)} \u2192 ${He.cyan(m.latest)}`),s.aiTools&&s.aiTools.length>0){let A=s.aiTools[0],Z=We[A].label;N.log.info(`Open your project in ${He.cyan(Z)} and ask: ${He.cyan("'update my GenerateSaaS project'")}`)}else N.log.info(`Ask your AI coding assistant to ${He.cyan("'update my GenerateSaaS project'")}.`)}catch(m){f.stop("Failed."),N.cancel(`Update failed: ${x(m)}`),process.exit(1)}})}async function Ns(e,t,r){let n=t.latest,o=t.versions.filter(s=>Tt(s.version,r)>0&&Tt(s.version,n)<=0).sort((s,a)=>Tt(s.version,a.version));if(o.length<=1)return{text:await Nt(e,n),title:`Changelog v${n}`};let i=[];for(let s of o){let a=null;try{a=await Nt(e,s.version)}catch{a=null}let p=s.date?` (${s.date.slice(0,10)})`:"",f=s.breaking?" [BREAKING]":"";i.push(`# v${s.version}${p}${f}
1248
1258
 
1249
1259
  ${a??"_No changelog available for this release._"}`)}return{text:i.join(`
1250
1260
 
1251
- `),title:`Changelog v${r} \u2192 v${n}`}}async function Cs(e,t){let r=JSON.parse(await zn(e,"utf-8")),n=await Ke(t),o=0;for(let[i,s]of Object.entries(r))$e(i)||n[i]!==s&&o++;for(let i of Object.keys(n))i in r||o++;return o}import*as F from"@clack/prompts";import q from"picocolors";import{readFile as Ns}from"fs/promises";import{join as Ls,resolve as $s}from"path";function Yn(e){e.command("status").description("Show project status and check for updates").option("--cwd <path>","project directory (default: current directory)").action(async t=>{let r=$s(t.cwd??process.cwd()),n=Ls(r,pe),o;try{o=JSON.parse(await Ns(n,"utf-8"))}catch{F.cancel(".generatesaas/manifest.json not found. Run this from a GenerateSaaS project."),process.exit(1)}let i=[`Version: ${q.cyan(o.version)}`,`Frontend: ${q.cyan(o.frontend)}`,o.deploymentTarget?`Deploy target: ${q.cyan(o.deploymentTarget)}`:null,o.databaseProvider?`Database: ${q.cyan(o.databaseProvider)}`:null,o.cacheProvider?`Cache: ${q.cyan(o.cacheProvider)}`:null,o.aiTools&&o.aiTools.length>0?`AI tools: ${q.cyan(o.aiTools.join(", "))}`:null].filter(Boolean).join(`
1252
- `);F.note(i,q.bold("Project Status"));let s=F.spinner();s.start("Checking for updates...");try{let a=await Le(),d=le(a),m=(await me(d)).latest;o.version===m?(s.stop("Up to date."),F.log.success(`Already on the latest version (${q.green(m)})`)):(s.stop("Update available."),F.log.warning(`Update available: ${q.yellow(o.version)} \u2192 ${q.green(m)}`),F.log.info(`Open this project in your AI coding agent and ask it to ${q.cyan("update my GenerateSaaS project")} - it fetches and applies the update for you.`))}catch(a){s.stop("Check failed."),a instanceof L&&a.status===401?F.log.warning("Invalid API key. Run `generatesaas auth` to update it, or set GENERATESAAS_API_KEY."):F.log.warning(`Could not check for updates: ${O(a)}`)}})}import{readFile as js}from"fs/promises";import*as _ from"@clack/prompts";import R from"picocolors";function Us(){return process.env.GENERATESAAS_API_KEY??et()}function Ms(e){return{verdict:e.verdict,plan:e.license?.plan??e.domainInstalls.find(t=>t.ownerPlan)?.ownerPlan??null,mismatchDomain:e.install?.domain??null,ejectedAt:e.install?.ejectedAt??e.domainInstalls.find(t=>t.ejectedAt)?.ejectedAt??null}}function Vs(e){return{verdict:e.verdict??"unknown",ejectedAt:e.ejectedAt??null}}function Fs(e){switch(e.verdict){case"licensed":return _.log.success(`${R.green("LICENSED")} - resolves to an account with an active${e.plan?` ${e.plan}`:""} license.`),!0;case"ejected":return _.log.success(`${R.green("EJECTED")} - a licensed buyer opted this install out of telemetry${e.ejectedAt?` on ${e.ejectedAt.slice(0,10)}`:""}. The site is legitimate.`),!0;case"revoked":return _.log.error(`${R.red("REVOKED")} - the owning account no longer holds a plan (refund or chargeback). This deployment is no longer licensed.`),!1;case"token_domain_mismatch":return _.log.error(`${R.red("LEAKED TOKEN")} - this license belongs to a different deployment${e.mismatchDomain?` (${R.cyan(e.mismatchDomain)})`:""}, not this site. The token was copied from a licensed project.`),!1;case"no_license_history":return _.log.error(`${R.red("NO LICENSE HISTORY")} - no license has ever been associated with this site. If it runs GenerateSaaS, treat it as unlicensed.`),!1;default:return _.log.warn(`${R.yellow("UNKNOWN")} - could not cross-reference the records right now. Try again shortly.`),!1}}async function qn(e,t){let r=process.env.GENERATESAAS_API_URL??Qe,n=Us();e.start("Cross-referencing license records...");try{let o=n?Ms(await yr(r,n,{lkh:t.lkh,nid:t.nid,domain:t.domain})):Vs(await $t(r,{token:t.token,domain:t.domain}));return e.stop(`${R.green("Checked")} - records cross-referenced`),Fs(o)}catch(o){return e.stop(`${R.yellow("Skipped")} - ${O(o)}`),null}}function Bs(e){let t=e.split(".");if(t.length!==3||!t[1])throw new Error("Invalid JWT format");let r=Buffer.from(t[1],"base64url").toString("utf-8");return JSON.parse(r)}function nr(e){return typeof e!="number"?"unknown":new Date(e*1e3).toISOString().split("T")[0]}function Jn(e){_.note([`License ID: ${R.cyan(String(e.lid??"unknown"))}`,`Version: ${R.cyan(String(e.ver??"unknown"))}`,`Init version: ${String(e.iver??"unknown")}`,`Frontend: ${String(e.fe??"unknown")}`,`Created: ${nr(e.pat)}`,`Last updated: ${nr(e.uat)}`,`Expires: ${nr(e.exp)}`,`Install ID: ${String(e.nid??"unknown")}`].join(`
1253
- `),R.yellow("License Details"))}function Gs(e){let r=(/^https?:\/\//i.test(e)?e:`https://${e}`).replace(/\/+$/,"");if(r.endsWith("/api"))return[`${r}/license`];try{if(new URL(r).pathname!=="/")return[`${r}/license`]}catch{return[`${r}/license`]}return[`${r}/api/license`,`${r}/license`]}async function Wn(e){let t=_.spinner(),r=null,n="no candidates";for(let s of Gs(e)){t.start(`Checking ${s}...`);try{let a=await fetch(s);if(!a.ok){n=`${s} returned ${a.status}`,t.stop(`${R.yellow("Not here")} - ${n}`);continue}let d=(await a.text()).trim();if(!d||d.split(".").length!==3){n=`${s} did not return a JWT`,t.stop(`${R.yellow("Not here")} - ${n}`);continue}r=d,t.stop(`${R.green("Found")} - license endpoint responded`);break}catch(a){n=`${s}: ${O(a)}`,t.stop(`${R.yellow("Unreachable")} - ${n}`)}}if(r===null){_.log.warn(`No license endpoint found (last: ${n}). The site may be ejected, not a GenerateSaaS app, or serving its API elsewhere.`);let s=Xn(e);return s?await qn(t,{domain:s})??!1:!1}let o;try{o=Bs(r)}catch{return _.log.error("Could not decode JWT payload."),!1}t.start("Verifying signature...");try{let s=process.env.GENERATESAAS_API_URL??Qe,a=await $t(s,{token:r});if(a.valid)t.stop(`${R.green("Valid")} - signature verified`);else return t.stop(`${R.red("Invalid")} - ${a.reason}`),!1}catch{return t.stop(`${R.yellow("Skipped")} - could not reach verification service`),_.log.warn("Signature not verified. Displaying unverified claims:"),Jn(o),!1}return Jn(o),await qn(t,{token:r,lkh:typeof o.lkh=="string"?o.lkh:void 0,nid:typeof o.nid=="string"?o.nid:void 0,domain:Xn(e)})??!0}function Xn(e){try{return new URL(/^https?:\/\//i.test(e)?e:`https://${e}`).hostname}catch{return}}function Zn(e){e.command("verify").description("Verify a GenerateSaaS license on a deployed site").argument("[url]","URL of the site to verify (e.g. https://example.com or https://example.com/api)").option("--file <path>","file with URLs to check, one per line").action(async(t,r)=>{if(!t&&!r.file&&(_.cancel("Provide a URL or --file <path>."),process.exit(1)),r.file){let o=(await js(r.file,"utf-8")).split(`
1254
- `).map(s=>s.trim()).filter(s=>s&&!s.startsWith("#"));o.length===0&&(_.cancel("No URLs found in file."),process.exit(1));let i=0;for(let s of o)await Wn(s)&&i++,_.log.info("");_.log.success(`${i}/${o.length} sites verified.`)}else await Wn(t)||process.exit(1)})}import{existsSync as Ks,rmSync as zs}from"fs";import*as J from"@clack/prompts";function Qn(e){e.command("auth").description("Set or update your GenerateSaaS API key").option("--clear","remove saved API key").action(async t=>{if(t.clear){Ks(ce)?(zs(ce),J.log.success("API key removed.")):J.log.info("No API key configured.");return}let r=et();r?J.log.info(`Current API key: ****${r.slice(-4)}`):J.log.info("No API key configured.");let n=await tt(),o=le(n),i=J.spinner();i.start("Verifying API key...");try{await me(o),i.stop("API key verified."),Se(n),J.log.success("API key saved.")}catch(s){i.stop("Verification failed."),s instanceof L&&s.status===401?J.cancel("Invalid API key."):J.cancel(O(s)),process.exit(1)}})}import{existsSync as Rt,rmSync as Hs,readFileSync as ir,writeFileSync as eo}from"fs";import{join as Ae}from"path";import*as j from"@clack/prompts";var Ys=["packages/api/src/functions/maintenance/license-heartbeat.ts","packages/api/src/lib/cron-spread.ts","packages/api/src/lib/manifest.ts","packages/api/src/routes/internal/license.ts"],qs=[{file:"packages/api/src/routes/inngest.ts",removals:[`import { licenseHeartbeatFunction } from "../functions/maintenance/license-heartbeat";
1261
+ `),title:`Changelog v${r} \u2192 v${n}`}}async function Ls(e,t){let r=JSON.parse(await Hn(e,"utf-8")),n=await Ke(t),o=0;for(let[i,s]of Object.entries(r))$e(i)||n[i]!==s&&o++;for(let i of Object.keys(n))i in r||o++;return o}import*as B from"@clack/prompts";import q from"picocolors";import{readFile as $s}from"fs/promises";import{join as js,resolve as Us}from"path";function qn(e){e.command("status").description("Show project status and check for updates").option("--cwd <path>","project directory (default: current directory)").action(async t=>{let r=Us(t.cwd??process.cwd()),n=js(r,de),o;try{o=JSON.parse(await $s(n,"utf-8"))}catch{B.cancel(".generatesaas/manifest.json not found. Run this from a GenerateSaaS project."),process.exit(1)}let i=[`Version: ${q.cyan(o.version)}`,`Frontend: ${q.cyan(o.frontend)}`,o.deploymentTarget?`Deploy target: ${q.cyan(o.deploymentTarget)}`:null,o.databaseProvider?`Database: ${q.cyan(o.databaseProvider)}`:null,o.cacheProvider?`Cache: ${q.cyan(o.cacheProvider)}`:null,o.aiTools&&o.aiTools.length>0?`AI tools: ${q.cyan(o.aiTools.join(", "))}`:null].filter(Boolean).join(`
1262
+ `);B.note(i,q.bold("Project Status"));let s=B.spinner();s.start("Checking for updates...");try{let a=await Le(),p=pe(a),m=(await fe(p)).latest;o.version===m?(s.stop("Up to date."),B.log.success(`Already on the latest version (${q.green(m)})`)):(s.stop("Update available."),B.log.warning(`Update available: ${q.yellow(o.version)} \u2192 ${q.green(m)}`),B.log.info(`Open this project in your AI coding agent and ask it to ${q.cyan("update my GenerateSaaS project")} - it fetches and applies the update for you.`))}catch(a){s.stop("Check failed."),a instanceof $&&a.status===401?B.log.warning("Invalid API key. Run `generatesaas auth` to update it, or set GENERATESAAS_API_KEY."):B.log.warning(`Could not check for updates: ${x(a)}`)}})}import{readFile as Ms}from"fs/promises";import*as O from"@clack/prompts";import _ from"picocolors";function Vs(){return process.env.GENERATESAAS_API_KEY??et()}function Fs(e){return{verdict:e.verdict,plan:e.license?.plan??e.domainInstalls.find(t=>t.ownerPlan)?.ownerPlan??null,mismatchDomain:e.install?.domain??null,ejectedAt:e.install?.ejectedAt??e.domainInstalls.find(t=>t.ejectedAt)?.ejectedAt??null}}function Bs(e){return{verdict:e.verdict??"unknown",ejectedAt:e.ejectedAt??null}}function Gs(e){switch(e.verdict){case"licensed":return O.log.success(`${_.green("LICENSED")} - resolves to an account with an active${e.plan?` ${e.plan}`:""} license.`),!0;case"ejected":return O.log.success(`${_.green("EJECTED")} - a licensed buyer opted this install out of telemetry${e.ejectedAt?` on ${e.ejectedAt.slice(0,10)}`:""}. The site is legitimate.`),!0;case"revoked":return O.log.error(`${_.red("REVOKED")} - the owning account no longer holds a plan (refund or chargeback). This deployment is no longer licensed.`),!1;case"token_domain_mismatch":return O.log.error(`${_.red("LEAKED TOKEN")} - this license belongs to a different deployment${e.mismatchDomain?` (${_.cyan(e.mismatchDomain)})`:""}, not this site. The token was copied from a licensed project.`),!1;case"no_license_history":return O.log.error(`${_.red("NO LICENSE HISTORY")} - no license has ever been associated with this site. If it runs GenerateSaaS, treat it as unlicensed.`),!1;default:return O.log.warn(`${_.yellow("UNKNOWN")} - could not cross-reference the records right now. Try again shortly.`),!1}}async function Jn(e,t){let r=process.env.GENERATESAAS_API_URL??Qe,n=Vs();e.start("Cross-referencing license records...");try{let o=n?Fs(await yr(r,n,{lkh:t.lkh,nid:t.nid,domain:t.domain})):Bs(await $t(r,{token:t.token,domain:t.domain}));return e.stop(`${_.green("Checked")} - records cross-referenced`),Gs(o)}catch(o){return e.stop(`${_.yellow("Skipped")} - ${x(o)}`),null}}function Ks(e){let t=e.split(".");if(t.length!==3||!t[1])throw new Error("Invalid JWT format");let r=Buffer.from(t[1],"base64url").toString("utf-8");return JSON.parse(r)}function nr(e){return typeof e!="number"?"unknown":new Date(e*1e3).toISOString().split("T")[0]}function Wn(e){O.note([`License ID: ${_.cyan(String(e.lid??"unknown"))}`,`Version: ${_.cyan(String(e.ver??"unknown"))}`,`Init version: ${String(e.iver??"unknown")}`,`Frontend: ${String(e.fe??"unknown")}`,`Created: ${nr(e.pat)}`,`Last updated: ${nr(e.uat)}`,`Expires: ${nr(e.exp)}`,`Install ID: ${String(e.nid??"unknown")}`].join(`
1263
+ `),_.yellow("License Details"))}function zs(e){let r=(/^https?:\/\//i.test(e)?e:`https://${e}`).replace(/\/+$/,"");if(r.endsWith("/api"))return[`${r}/license`];try{if(new URL(r).pathname!=="/")return[`${r}/license`]}catch{return[`${r}/license`]}return[`${r}/api/license`,`${r}/license`]}async function Xn(e){let t=O.spinner(),r=null,n="no candidates";for(let s of zs(e)){t.start(`Checking ${s}...`);try{let a=await fetch(s);if(!a.ok){n=`${s} returned ${a.status}`,t.stop(`${_.yellow("Not here")} - ${n}`);continue}let p=(await a.text()).trim();if(!p||p.split(".").length!==3){n=`${s} did not return a JWT`,t.stop(`${_.yellow("Not here")} - ${n}`);continue}r=p,t.stop(`${_.green("Found")} - license endpoint responded`);break}catch(a){n=`${s}: ${x(a)}`,t.stop(`${_.yellow("Unreachable")} - ${n}`)}}if(r===null){O.log.warn(`No license endpoint found (last: ${n}). The site may be ejected, not a GenerateSaaS app, or serving its API elsewhere.`);let s=Zn(e);return s?await Jn(t,{domain:s})??!1:!1}let o;try{o=Ks(r)}catch{return O.log.error("Could not decode JWT payload."),!1}t.start("Verifying signature...");try{let s=process.env.GENERATESAAS_API_URL??Qe,a=await $t(s,{token:r});if(a.valid)t.stop(`${_.green("Valid")} - signature verified`);else return t.stop(`${_.red("Invalid")} - ${a.reason}`),!1}catch{return t.stop(`${_.yellow("Skipped")} - could not reach verification service`),O.log.warn("Signature not verified. Displaying unverified claims:"),Wn(o),!1}return Wn(o),await Jn(t,{token:r,lkh:typeof o.lkh=="string"?o.lkh:void 0,nid:typeof o.nid=="string"?o.nid:void 0,domain:Zn(e)})??!0}function Zn(e){try{return new URL(/^https?:\/\//i.test(e)?e:`https://${e}`).hostname}catch{return}}function Qn(e){e.command("verify").description("Verify a GenerateSaaS license on a deployed site").argument("[url]","URL of the site to verify (e.g. https://example.com or https://example.com/api)").option("--file <path>","file with URLs to check, one per line").action(async(t,r)=>{if(!t&&!r.file&&(O.cancel("Provide a URL or --file <path>."),process.exit(1)),r.file){let o=(await Ms(r.file,"utf-8")).split(`
1264
+ `).map(s=>s.trim()).filter(s=>s&&!s.startsWith("#"));o.length===0&&(O.cancel("No URLs found in file."),process.exit(1));let i=0;for(let s of o)await Xn(s)&&i++,O.log.info("");O.log.success(`${i}/${o.length} sites verified.`)}else await Xn(t)||process.exit(1)})}import{existsSync as Hs,rmSync as Ys}from"fs";import*as J from"@clack/prompts";function eo(e){e.command("auth").description("Set or update your GenerateSaaS API key").option("--clear","remove saved API key").action(async t=>{if(t.clear){Hs(le)?(Ys(le),J.log.success("API key removed.")):J.log.info("No API key configured.");return}let r=et();r?J.log.info(`Current API key: ****${r.slice(-4)}`):J.log.info("No API key configured.");let n=await tt(),o=pe(n),i=J.spinner();i.start("Verifying API key...");try{await fe(o),i.stop("API key verified."),ke(n),J.log.success("API key saved.")}catch(s){i.stop("Verification failed."),s instanceof $&&s.status===401?J.cancel("Invalid API key."):J.cancel(x(s)),process.exit(1)}})}import{existsSync as Rt,rmSync as qs,readFileSync as ir,writeFileSync as to}from"fs";import{join as Ie}from"path";import*as U from"@clack/prompts";var Js=["packages/api/src/functions/maintenance/license-heartbeat.ts","packages/api/src/lib/cron-spread.ts","packages/api/src/lib/manifest.ts","packages/api/src/routes/internal/license.ts"],Ws=[{file:"packages/api/src/routes/inngest.ts",removals:[`import { licenseHeartbeatFunction } from "../functions/maintenance/license-heartbeat";
1255
1265
  `,` licenseHeartbeatFunction,
1256
1266
  `]},{file:"packages/api/src/routes/internal/index.ts",removals:[`import licenseRoutes from "./license";
1257
1267
  `,` .route("/license", licenseRoutes)
1258
- `]}];function Js(e){return(e&&e.length>0?e.map(r=>nt[r]):Object.values(nt)).map(r=>Ae(r,Xt))}function or(e){return Rt(e)?(Hs(e,{recursive:!0}),!0):!1}function Ws(e,t){if(!Rt(e))return!1;let r=ir(e,"utf-8"),n=r;for(let o of t)n=n.replace(o,"");return n===r?!1:(eo(e,n,"utf-8"),!0)}function Xs(e){let t=Ae(e,".gitignore");if(!Rt(t))return!1;let r=ir(t,"utf-8"),n=r.split(`
1268
+ `]}];function Xs(e){return(e&&e.length>0?e.map(r=>nt[r]):Object.values(nt)).map(r=>Ie(r,Xt))}function or(e){return Rt(e)?(qs(e,{recursive:!0}),!0):!1}function Zs(e,t){if(!Rt(e))return!1;let r=ir(e,"utf-8"),n=r;for(let o of t)n=n.replace(o,"");return n===r?!1:(to(e,n,"utf-8"),!0)}function Qs(e){let t=Ie(e,".gitignore");if(!Rt(t))return!1;let r=ir(t,"utf-8"),n=r.split(`
1259
1269
  `).filter(o=>!o.includes(".generatesaas")).join(`
1260
- `);return n===r?!1:(eo(t,n,"utf-8"),!0)}function to(e){e.command("eject").description("Remove all GenerateSaaS ties - manifest, license, heartbeat, skills").action(async()=>{let t=process.cwd(),r=Ae(t,pe),n;try{n=JSON.parse(ir(r,"utf-8"))}catch{j.cancel("No GenerateSaaS project found in this directory."),process.exit(1)}let o=await j.text({message:'Type "eject" to confirm (this cannot be undone):',validate:a=>{if(a!=="eject")return'Type "eject" to confirm, or press Ctrl+C to cancel.'}});if(j.isCancel(o)&&(j.cancel("Eject cancelled."),process.exit(0)),n.licenseToken)try{await fetch("https://generatesaas.com/api/v1/heartbeat",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${n.licenseToken}`},body:JSON.stringify({event:"eject",version:n.version,frontend:n.frontend}),signal:AbortSignal.timeout(5e3)}),j.log.info("Recorded the opt-out with generatesaas.com (final event - nothing is sent after this).")}catch{j.log.warn("Could not reach generatesaas.com to record the opt-out. Ejecting anyway.")}let i=[],s=[];for(let a of Js(n.aiTools))or(Ae(t,a))&&i.push(a);for(let a of Ys)or(Ae(t,a))&&i.push(a);or(Ae(t,K))&&i.push(K+"/");for(let a of qs){let d=Ae(t,a.file);Ws(d,a.removals)?s.push(a.file):Rt(d)&&j.log.warn(`Could not auto-modify ${a.file} - manually remove license/heartbeat references.`)}Xs(t)&&s.push(".gitignore");for(let a of i)j.log.info(`Deleted ${a}`);for(let a of s)j.log.info(`Modified ${a}`);j.log.success("Ejected successfully. This project is now fully standalone.")})}var Ie=new Zs().name("generatesaas").description("CLI for scaffolding and managing GenerateSaaS projects").version("1.19.0").addHelpText("after",`
1270
+ `);return n===r?!1:(to(t,n,"utf-8"),!0)}function ro(e){e.command("eject").description("Remove all GenerateSaaS ties - manifest, license, heartbeat, skills").action(async()=>{let t=process.cwd(),r=Ie(t,de),n;try{n=JSON.parse(ir(r,"utf-8"))}catch{U.cancel("No GenerateSaaS project found in this directory."),process.exit(1)}let o=await U.text({message:'Type "eject" to confirm (this cannot be undone):',validate:a=>{if(a!=="eject")return'Type "eject" to confirm, or press Ctrl+C to cancel.'}});if(U.isCancel(o)&&(U.cancel("Eject cancelled."),process.exit(0)),n.licenseToken)try{await fetch("https://generatesaas.com/api/v1/heartbeat",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${n.licenseToken}`},body:JSON.stringify({event:"eject",version:n.version,frontend:n.frontend}),signal:AbortSignal.timeout(5e3)}),U.log.info("Recorded the opt-out with generatesaas.com (final event - nothing is sent after this).")}catch{U.log.warn("Could not reach generatesaas.com to record the opt-out. Ejecting anyway.")}let i=[],s=[];for(let a of Xs(n.aiTools))or(Ie(t,a))&&i.push(a);for(let a of Js)or(Ie(t,a))&&i.push(a);or(Ie(t,z))&&i.push(z+"/");for(let a of Ws){let p=Ie(t,a.file);Zs(p,a.removals)?s.push(a.file):Rt(p)&&U.log.warn(`Could not auto-modify ${a.file} - manually remove license/heartbeat references.`)}Qs(t)&&s.push(".gitignore");for(let a of i)U.log.info(`Deleted ${a}`);for(let a of s)U.log.info(`Modified ${a}`);U.log.success("Ejected successfully. This project is now fully standalone.")})}var Pe=new ea().name("generatesaas").description("CLI for scaffolding and managing GenerateSaaS projects").version("1.19.2").addHelpText("after",`
1261
1271
  Examples:
1262
1272
  $ generatesaas init Interactive setup
1263
1273
  $ generatesaas init -n my-app -y Quick setup with defaults
1264
1274
  $ generatesaas status Check for updates
1265
1275
  $ generatesaas auth Set or update API key
1266
- `);Mn(Ie);Hn(Ie);Yn(Ie);Zn(Ie);Qn(Ie);to(Ie);Ie.parseAsync().catch(e=>{ro.cancel("An unexpected error occurred."),console.error(e),process.exit(1)});
1276
+ `);Vn(Pe);Yn(Pe);qn(Pe);Qn(Pe);eo(Pe);ro(Pe);Pe.parseAsync().catch(e=>{no.cancel("An unexpected error occurred."),console.error(e),process.exit(1)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "generatesaas",
3
- "version": "1.19.0",
3
+ "version": "1.19.2",
4
4
  "type": "module",
5
5
  "description": "CLI for scaffolding and managing GenerateSaaS projects",
6
6
  "license": "UNLICENSED",