create-sailor 1.2.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +96 -23
  2. package/dist/index.js +2785 -75
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,70 +1,1829 @@
1
1
  #!/usr/bin/env node
2
- import{execSync as ce}from"child_process";import Yt from"fs";import ye from"path";import*as v from"@clack/prompts";import{Command as Kt}from"commander";import A from"picocolors";import Mt from"update-notifier";import pt from"cfonts";import lt from"picocolors";import ge from"fs";import le from"path";import{fileURLToPath as at}from"url";function ct(){try{let t=le.dirname(at(import.meta.url));for(let n=0;n<4;n++){let s=le.join(t,"package.json");if(ge.existsSync(s)){let r=ge.readFileSync(s,"utf-8"),a=JSON.parse(r);if(a.name==="create-sailor"&&typeof a.version=="string")return a.version}t=le.dirname(t)}return"0.0.0-dev"}catch{return"0.0.0-dev"}}var U=ct();function ve(){if(process.env.NO_COLOR||!process.stdout.isTTY){process.stdout.write([`Sailor v${U}`,"AI-Native SaaS Unicorn Template","by Nebutra \xB7 https://nebutra.com","",""].join(`
3
- `));return}pt.say("Sailor",{font:"block",align:"left",colors:["#0033FE","#0BF1C3"],gradient:["#0033FE","#0BF1C3"],transitionGradient:!0,space:!1}),process.stdout.write(lt.dim(` AI-Native SaaS Template \xB7 v${U} \xB7 nebutra.com
2
+ import{execSync as At}from"child_process";import go from"fs";import Yt from"path";import*as P from"@clack/prompts";import{Command as pi}from"commander";import x from"picocolors";import li from"update-notifier";import ko from"cfonts";import Oo from"picocolors";import $t from"fs";import Rt from"path";import{fileURLToPath as xo}from"url";function To(){try{let t=Rt.dirname(xo(import.meta.url));for(let o=0;o<4;o++){let n=Rt.join(t,"package.json");if($t.existsSync(n)){let r=$t.readFileSync(n,"utf-8"),s=JSON.parse(r);if(s.name==="create-sailor"&&typeof s.version=="string")return s.version}t=Rt.dirname(t)}return"0.0.0-dev"}catch{return"0.0.0-dev"}}var se=To();function qt(){if(process.env.NO_COLOR||!process.stdout.isTTY){process.stdout.write([`Sailor v${se}`,"AI-Native SaaS Unicorn Template","by Nebutra \xB7 https://nebutra.com","",""].join(`
3
+ `));return}ko.say("Sailor",{font:"block",align:"left",colors:["#0033FE","#0BF1C3"],gradient:["#0033FE","#0BF1C3"],transitionGradient:!0,space:!1}),process.stdout.write(Oo.dim(` AI-Native SaaS Template \xB7 v${se} \xB7 nebutra.com
4
4
 
5
- `))}import V from"picocolors";function ut(){return!process.env.NO_COLOR&&!!process.stdout.isTTY}function he(e){let t=ut(),n=t?"\u2693":"-",s=t?"\u25B8":"->",r=t?"\u2605":"*",a=t?"\u{1F4E7}":"*",i=["",` ${t?V.bold(`${n} Done in ${e.elapsedSec}s \xB7 ${e.targetDir}/`):`${n} Done in ${e.elapsedSec}s \xB7 ${e.targetDir}/`}`,"",` ${t?V.bold("Next:"):"Next:"}`,` ${s} cd ${e.targetDir}`];e.skippedInstall&&i.push(` ${s} pnpm install (if --no-install was set)`),i.push(` ${s} pnpm dev ${t?V.dim("\u2192 http://localhost:3000"):"-> http://localhost:3000"}`,"",` ${t?V.bold("Customize:"):"Customize:"}`,` ${s} pnpm sailor brand init ${t?V.dim("\u2192 your colors, logo, domain"):"-> your colors, logo, domain"}`,` ${s} pnpm sailor preset apply ${t?V.dim("\u2192 toggle features"):"-> toggle features"}`,` ${s} pnpm sailor add <feat> ${t?V.dim("\u2192 add queue/search/cache/..."):"-> add queue/search/cache/..."}`,"",` ${t?V.bold("Ship faster:"):"Ship faster:"}`,` ${r} Star us https://github.com/nebutra/nebutra-sailor`,` ${a} Free license https://nebutra.com/get-license`,""),process.stdout.write(i.join(`
5
+ `))}import j from"picocolors";function Uo(){return!process.env.NO_COLOR&&!!process.stdout.isTTY}function Gt(e){let t=Uo(),o=t?"\u2693":"-",n=t?"\u25B8":"->",r=t?"\u2605":"*",s=t?"\u{1F4E7}":"*",c=["",` ${t?j.bold(`${o} Done in ${e.elapsedSec}s \xB7 ${e.targetDir}/`):`${o} Done in ${e.elapsedSec}s \xB7 ${e.targetDir}/`}`,"",` ${t?j.bold("Next:"):"Next:"}`,` ${n} cd ${e.targetDir}`];e.skippedInstall&&c.push(` ${n} pnpm install (if --no-install was set)`),c.push(` ${n} pnpm dev ${t?j.dim("\u2192 http://localhost:3000"):"-> http://localhost:3000"}`,"",` ${t?j.bold("Customize:"):"Customize:"}`,` ${n} pnpm sailor brand init ${t?j.dim("\u2192 your colors, logo, domain"):"-> your colors, logo, domain"}`,` ${n} pnpm sailor preset apply ${t?j.dim("\u2192 toggle features"):"-> toggle features"}`,` ${n} pnpm sailor add <feat> ${t?j.dim("\u2192 add queue/search/cache/..."):"-> add queue/search/cache/..."}`,"",` ${t?j.bold("Add more features later:"):"Add more features later:"}`,` ${n} sailor add payment --provider=wechat`,` ${n} sailor add email --provider=postmark`,` ${n} sailor add storage --provider=supabase`,` ${t?j.dim("(more providers: `sailor add --list`)"):"(more providers: `sailor add --list`)"}`,"",` ${t?j.bold("Ship faster:"):"Ship faster:"}`,` ${r} Star us https://github.com/nebutra/nebutra-sailor`,` ${s} Free license https://nebutra.com/get-license`,""),process.stdout.write(c.join(`
6
6
  `)+`
7
- `)}var dt=`Usage: create-sailor [name] [options]
7
+ `)}var Lo=`Usage: create-sailor [name] [options]
8
8
 
9
9
  Arguments:
10
- name project directory (default: ./my-saas-app)
11
-
12
- Options:
13
- -p, --pm <id> npm | pnpm | yarn | bun (auto-detected)
14
- --orm <id> prisma | drizzle | none
15
- --db <id> postgres | mysql | sqlite | none
16
- --auth <id> clerk | betterauth | none
17
- --payment <id> stripe | lemon | wechat | alipay | none
18
- --ai <ids> comma-separated provider ids (e.g. openai,anthropic)
19
- --deploy <target> vercel | railway | cloudflare | selfhost
20
- --docs <id> fumadocs | mintlify | docusaurus | nextra | vitepress | none
21
- --i18n enable i18n (default: true)
22
- --no-i18n disable
23
- --no-install skip package install
24
- --no-git skip git init
25
- -y, --yes accept all defaults (non-interactive)
26
- --dry-run preview actions without writing files
27
- --json machine-readable output
28
- --no-color disable color output
29
- -h, --help show this help
30
- -v, --version show version
10
+ name project directory (default: ./my-saas-app)
11
+
12
+ Core options:
13
+ -p, --pm <id> npm | pnpm | yarn | bun (auto-detected)
14
+ --region <id> global | cn | hybrid (default: global)
15
+ --auth <id> clerk | betterauth | none
16
+ --ai <ids> comma-separated provider ids (e.g. openai,anthropic)
17
+
18
+ Stack flags (region-based smart defaults):
19
+ --orm <id> prisma | drizzle | none (default: prisma)
20
+ --db <id> postgres | mysql | sqlite | none (default: postgres)
21
+ --payment <id> stripe | lemon | wechat | alipay | none (default: region-based)
22
+ --deploy <target> vercel | railway | cloudflare | selfhost (default: vercel)
23
+ --docs <id> fumadocs | mintlify | docusaurus | nextra | vitepress | none
24
+
25
+ Feature flags (region-based smart defaults):
26
+ --email <id> resend | postmark | ses | aliyun-dm | tencent-ses | netease | none
27
+ --storage <id> r2 | s3 | supabase | aliyun-oss | tencent-cos | qiniu | none
28
+ --monitoring <id> sentry | datadog | aliyun-arms | tingyun | none
29
+ --analytics <id> posthog | plausible | umami | baidu | sensors | none
30
+ --sms <id> twilio | aliyun-sms | tencent-sms | yunpian | none
31
+ --queue <id> qstash | bullmq | upstash | sqs | none
32
+ --search <id> meilisearch | typesense | algolia | pgvector | none
33
+ --cache <id> upstash-redis | vercel-kv | redis | dragonfly | none
34
+ --notifications <id> novu | knock | custom | none
35
+ --webhooks <id> svix | custom | none
36
+ --cms <id> sanity | contentful | strapi | none
37
+ --feature-flags <id> vercel-flags | growthbook | configcat | none
38
+ --captcha <id> turnstile | hcaptcha | aliyun-slide | none
39
+
40
+ Sailor features:
41
+ --mcp <mode> on | off (default: on \u2014 Agent-friendly MCP server)
42
+ --metering <mode> auto | on | off (default: auto)
43
+
44
+ Toggles:
45
+ --i18n enable i18n (default: true)
46
+ --no-i18n disable
47
+ --no-install skip package install
48
+ --no-git skip git init
49
+ -y, --yes accept all defaults (non-interactive)
50
+ --dry-run preview actions without writing files
51
+ --json machine-readable output
52
+ --no-color disable color output
53
+ -h, --help show this help
54
+ -v, --version show version
31
55
 
32
56
  Examples:
33
57
  $ npm create sailor@latest
34
58
  $ npm create sailor@latest my-app -y
35
- $ npm create sailor@latest my-app --auth=clerk --ai=openai,deepseek
36
- $ npm create sailor@latest --dry-run
37
- `;function Ee(){process.stdout.write(dt)}import oe from"picocolors";function Pe(){return!process.env.NO_COLOR&&!!process.stdout.isTTY}function mt(e){let t=Pe()?"\u25B8":"->",n=`[${e.index}/${e.total}]`,s=e.label.padEnd(18," ");return Pe()?`${oe.dim(n)} ${oe.bold(s)} ${oe.dim(t)} ${oe.cyan(e.value)}`:`${n} ${s} ${t} ${e.value}`}function be(e){process.stdout.write(mt(e)+`
38
- `)}var ie=[{id:"anthropic",name:"Anthropic",category:"\u76F4\u63A5\u5B9E\u9A8C\u5BA4",status:"opencode",docs:"https://docs.anthropic.com",envVarPrefix:"ANTHROPIC",requiredEnvVars:["ANTHROPIC_API_KEY"]},{id:"openai",name:"OpenAI",category:"\u76F4\u63A5\u5B9E\u9A8C\u5BA4",status:"opencode",docs:"https://platform.openai.com/docs",envVarPrefix:"OPENAI",requiredEnvVars:["OPENAI_API_KEY"]},{id:"deepseek",name:"DeepSeek",category:"\u76F4\u63A5\u5B9E\u9A8C\u5BA4",status:"opencode",docs:"https://platform.deepseek.com/api-docs",envVarPrefix:"DEEPSEEK",requiredEnvVars:["DEEPSEEK_API_KEY"]},{id:"xai",name:"xAI (Grok)",category:"\u76F4\u63A5\u5B9E\u9A8C\u5BA4",status:"opencode",docs:"https://docs.x.ai/api",envVarPrefix:"XAI",requiredEnvVars:["XAI_API_KEY"]},{id:"moonshot",name:"Moonshot (Kimi)",category:"\u76F4\u63A5\u5B9E\u9A8C\u5BA4",status:"opencode",docs:"https://platform.moonshot.cn/docs",envVarPrefix:"MOONSHOT",requiredEnvVars:["MOONSHOT_API_KEY","MOONSHOT_BASE_URL"]},{id:"google",name:"Google Gemini",category:"\u76F4\u63A5\u5B9E\u9A8C\u5BA4",status:"ai-sdk",docs:"https://ai.google.dev/gemini-api/docs",envVarPrefix:"GOOGLE_GENERATIVE_AI",requiredEnvVars:["GOOGLE_GENERATIVE_AI_API_KEY"]},{id:"mistral",name:"Mistral AI",category:"\u76F4\u63A5\u5B9E\u9A8C\u5BA4",status:"ai-sdk",docs:"https://docs.mistral.ai",envVarPrefix:"MISTRAL",requiredEnvVars:["MISTRAL_API_KEY"]},{id:"cohere",name:"Cohere",category:"\u76F4\u63A5\u5B9E\u9A8C\u5BA4",status:"ai-sdk",docs:"https://docs.cohere.com",envVarPrefix:"COHERE",requiredEnvVars:["COHERE_API_KEY"]},{id:"perplexity",name:"Perplexity",category:"\u76F4\u63A5\u5B9E\u9A8C\u5BA4",status:"cn-compatible",docs:"https://docs.perplexity.ai",baseURL:"https://api.perplexity.ai",envVarPrefix:"PERPLEXITY",requiredEnvVars:["PERPLEXITY_API_KEY"]},{id:"ai21",name:"AI21 Labs",category:"\u76F4\u63A5\u5B9E\u9A8C\u5BA4",status:"cn-compatible",docs:"https://docs.ai21.com",baseURL:"https://api.ai21.com/studio/v1",envVarPrefix:"AI21",requiredEnvVars:["AI21_API_KEY"]},{id:"upstage",name:"Upstage (Solar)",category:"\u76F4\u63A5\u5B9E\u9A8C\u5BA4",status:"cn-compatible",docs:"https://developers.upstage.ai/docs",baseURL:"https://api.upstage.ai/v1/solar",envVarPrefix:"UPSTAGE",requiredEnvVars:["UPSTAGE_API_KEY"]},{id:"siliconflow",name:"\u7845\u57FA\u6D41\u52A8 SiliconFlow",category:"\u56FD\u5185\u5E73\u53F0",status:"cn-compatible",docs:"https://docs.siliconflow.cn",baseURL:"https://api.siliconflow.cn/v1",envVarPrefix:"SILICONFLOW",requiredEnvVars:["SILICONFLOW_API_KEY"]},{id:"volcengine-ark",name:"\u706B\u5C71\u5F15\u64CE ARK (\u8C46\u5305)",category:"\u56FD\u5185\u5E73\u53F0",status:"cn-compatible",docs:"https://www.volcengine.com/docs/82379",baseURL:"https://ark.cn-beijing.volces.com/api/v3",envVarPrefix:"ARK",requiredEnvVars:["ARK_API_KEY"]},{id:"bailian",name:"\u767E\u70BC Bailian (\u901A\u4E49)",category:"\u56FD\u5185\u5E73\u53F0",status:"cn-compatible",docs:"https://help.aliyun.com/zh/model-studio",baseURL:"https://dashscope.aliyuncs.com/compatible-mode/v1",envVarPrefix:"DASHSCOPE",requiredEnvVars:["DASHSCOPE_API_KEY"]},{id:"zhipu",name:"\u667A\u8C31 AI Zhipu",category:"\u56FD\u5185\u5E73\u53F0",status:"ai-sdk",docs:"https://docs.bigmodel.cn",baseURL:"https://open.bigmodel.cn/api/paas/v4",envVarPrefix:"ZHIPU",requiredEnvVars:["ZHIPU_API_KEY"]},{id:"baichuan",name:"\u767E\u5DDD\u667A\u80FD Baichuan",category:"\u56FD\u5185\u5E73\u53F0",status:"cn-compatible",docs:"https://platform.baichuan-ai.com/docs",baseURL:"https://api.baichuan-ai.com/v1",envVarPrefix:"BAICHUAN",requiredEnvVars:["BAICHUAN_API_KEY"]},{id:"minimax",name:"MiniMax (ABAB)",category:"\u56FD\u5185\u5E73\u53F0",status:"cn-compatible",docs:"https://platform.minimaxi.com/document/guides",baseURL:"https://api.minimax.chat/v1",envVarPrefix:"MINIMAX",requiredEnvVars:["MINIMAX_API_KEY","MINIMAX_GROUP_ID"]},{id:"stepfun",name:"\u9636\u8DC3\u661F\u8FB0 StepFun",category:"\u56FD\u5185\u5E73\u53F0",status:"cn-compatible",docs:"https://platform.stepfun.com/docs",baseURL:"https://api.stepfun.com/v1",envVarPrefix:"STEPFUN",requiredEnvVars:["STEPFUN_API_KEY"]},{id:"sensetime",name:"\u5546\u6C64 SenseNova",category:"\u56FD\u5185\u5E73\u53F0",status:"cn-compatible",docs:"https://platform.sensetime.com",baseURL:"https://api.sensenova.cn/v1",envVarPrefix:"SENSENOVA",requiredEnvVars:["SENSENOVA_API_KEY"]},{id:"tencent",name:"\u817E\u8BAF\u6DF7\u5143 Hunyuan",category:"\u56FD\u5185\u5E73\u53F0",status:"cn-compatible",docs:"https://cloud.tencent.com/document/product/1729",baseURL:"https://api.hunyuan.cloud.tencent.com/v1",envVarPrefix:"HUNYUAN",requiredEnvVars:["HUNYUAN_API_KEY"]},{id:"lingyi",name:"\u96F6\u4E00\u4E07\u7269 Yi",category:"\u56FD\u5185\u5E73\u53F0",status:"cn-compatible",docs:"https://platform.lingyiwanwu.com/docs",baseURL:"https://api.lingyiwanwu.com/v1",envVarPrefix:"YI",requiredEnvVars:["YI_API_KEY"]},{id:"openrouter",name:"OpenRouter",category:"\u7EDF\u4E00\u7F51\u5173",status:"opencode",docs:"https://openrouter.ai/docs",envVarPrefix:"OPENROUTER",requiredEnvVars:["OPENROUTER_API_KEY"]},{id:"vercel-gateway",name:"Vercel AI Gateway",category:"\u7EDF\u4E00\u7F51\u5173",status:"opencode",docs:"https://vercel.com/docs/ai-gateway",envVarPrefix:"AI_GATEWAY",requiredEnvVars:["AI_GATEWAY_API_KEY"]},{id:"litellm",name:"LiteLLM Proxy",category:"\u7EDF\u4E00\u7F51\u5173",status:"cn-compatible",docs:"https://docs.litellm.ai",baseURL:"http://localhost:4000/v1",envVarPrefix:"LITELLM",requiredEnvVars:["LITELLM_API_KEY"]},{id:"portkey",name:"Portkey",category:"\u7EDF\u4E00\u7F51\u5173",status:"cn-compatible",docs:"https://portkey.ai/docs",baseURL:"https://api.portkey.ai/v1",envVarPrefix:"PORTKEY",requiredEnvVars:["PORTKEY_API_KEY"]},{id:"aws-bedrock",name:"AWS Bedrock",category:"\u4E91\u5E73\u53F0",status:"ai-sdk",docs:"https://aws.amazon.com/bedrock/",envVarPrefix:"AWS_BEDROCK",requiredEnvVars:["AWS_ACCESS_KEY_ID","AWS_SECRET_ACCESS_KEY","AWS_REGION"]},{id:"azure-openai",name:"Azure OpenAI",category:"\u4E91\u5E73\u53F0",status:"ai-sdk",docs:"https://azure.microsoft.com/en-us/products/cognitive-services/openai-service/",envVarPrefix:"AZURE_OPENAI",requiredEnvVars:["AZURE_OPENAI_API_KEY","AZURE_OPENAI_ENDPOINT"]},{id:"gcp-vertex",name:"GCP Vertex AI",category:"\u4E91\u5E73\u53F0",status:"ai-sdk",docs:"https://cloud.google.com/vertex-ai",envVarPrefix:"GOOGLE_VERTEX",requiredEnvVars:["GOOGLE_APPLICATION_CREDENTIALS"]},{id:"groq",name:"Groq",category:"\u63A8\u7406\u52A0\u901F",status:"opencode",docs:"https://console.groq.com/docs",envVarPrefix:"GROQ",requiredEnvVars:["GROQ_API_KEY"]},{id:"fireworks",name:"Fireworks AI",category:"\u63A8\u7406\u52A0\u901F",status:"opencode",docs:"https://docs.fireworks.ai",envVarPrefix:"FIREWORKS",requiredEnvVars:["FIREWORKS_API_KEY"]},{id:"together",name:"Together AI",category:"\u63A8\u7406\u52A0\u901F",status:"opencode",docs:"https://docs.together.ai",envVarPrefix:"TOGETHER",requiredEnvVars:["TOGETHER_API_KEY"]},{id:"huggingface",name:"Hugging Face",category:"\u63A8\u7406\u52A0\u901F",status:"opencode",docs:"https://huggingface.co/docs/inference-providers",envVarPrefix:"HF",requiredEnvVars:["HF_API_KEY"]},{id:"replicate",name:"Replicate",category:"\u63A8\u7406\u52A0\u901F",status:"cn-compatible",docs:"https://replicate.com/docs",envVarPrefix:"REPLICATE",requiredEnvVars:["REPLICATE_API_TOKEN"]},{id:"lepton",name:"Lepton AI",category:"\u63A8\u7406\u52A0\u901F",status:"cn-compatible",docs:"https://www.lepton.ai/docs",baseURL:"https://api.lepton.ai/v1",envVarPrefix:"LEPTON",requiredEnvVars:["LEPTON_API_KEY"]},{id:"anyscale",name:"Anyscale Endpoints",category:"\u63A8\u7406\u52A0\u901F",status:"cn-compatible",docs:"https://docs.endpoints.anyscale.com/",baseURL:"https://api.endpoints.anyscale.com/v1",envVarPrefix:"ANYSCALE",requiredEnvVars:["ANYSCALE_API_KEY"]},{id:"octoai",name:"OctoAI",category:"\u63A8\u7406\u52A0\u901F",status:"cn-compatible",docs:"https://octoai.cloud/docs",baseURL:"https://text.octoai.run/v1",envVarPrefix:"OCTOAI",requiredEnvVars:["OCTOAI_TOKEN"]},{id:"deepinfra",name:"DeepInfra",category:"\u63A8\u7406\u52A0\u901F",status:"cn-compatible",docs:"https://deepinfra.com/docs",baseURL:"https://api.deepinfra.com/v1/openai",envVarPrefix:"DEEPINFRA",requiredEnvVars:["DEEPINFRA_API_KEY"]},{id:"novita",name:"Novita AI",category:"\u63A8\u7406\u52A0\u901F",status:"cn-compatible",docs:"https://novita.ai/docs",baseURL:"https://api.novita.ai/v3/openai",envVarPrefix:"NOVITA",requiredEnvVars:["NOVITA_API_KEY"]},{id:"black-forest-labs",name:"Black Forest Labs (FLUX)",category:"\u591A\u6A21\u6001",status:"ai-sdk",docs:"https://docs.bfl.ml",envVarPrefix:"BFL",requiredEnvVars:["BFL_API_KEY"]},{id:"elevenlabs",name:"ElevenLabs (TTS)",category:"\u591A\u6A21\u6001",status:"ai-sdk",docs:"https://elevenlabs.io/docs",envVarPrefix:"ELEVENLABS",requiredEnvVars:["ELEVENLABS_API_KEY"]},{id:"runway",name:"Runway (Video)",category:"\u591A\u6A21\u6001",status:"pending",docs:"https://runwayml.com",envVarPrefix:"RUNWAY",requiredEnvVars:["RUNWAY_API_KEY"]},{id:"luma",name:"Luma (Video)",category:"\u591A\u6A21\u6001",status:"pending",docs:"https://lumalabs.ai",envVarPrefix:"LUMA",requiredEnvVars:["LUMA_API_KEY"]},{id:"midjourney",name:"Midjourney",category:"\u591A\u6A21\u6001",status:"pending",docs:"https://docs.midjourney.com",envVarPrefix:"MIDJOURNEY",requiredEnvVars:[]},{id:"ollama",name:"Ollama (\u672C\u5730)",category:"\u672C\u5730\u90E8\u7F72",status:"opencode",docs:"https://github.com/ollama/ollama",baseURL:"http://localhost:11434/v1",envVarPrefix:"OLLAMA",requiredEnvVars:["OLLAMA_BASE_URL"]},{id:"lmstudio",name:"LM Studio",category:"\u672C\u5730\u90E8\u7F72",status:"cn-compatible",docs:"https://lmstudio.ai/docs",baseURL:"http://localhost:1234/v1",envVarPrefix:"LMSTUDIO",requiredEnvVars:[]},{id:"vllm",name:"vLLM",category:"\u672C\u5730\u90E8\u7F72",status:"cn-compatible",docs:"https://docs.vllm.ai",baseURL:"http://localhost:8000/v1",envVarPrefix:"VLLM",requiredEnvVars:[]},{id:"tgi",name:"HF TGI",category:"\u672C\u5730\u90E8\u7F72",status:"cn-compatible",docs:"https://huggingface.co/docs/text-generation-inference",baseURL:"http://localhost:8080/v1",envVarPrefix:"TGI",requiredEnvVars:[]}],an=ie.reduce((e,t)=>(e[t.category]||(e[t.category]=[]),e[t.category].push(t),e),{});import x from"fs";import $ from"path";function ue(e){x.existsSync(e)&&x.rmSync(e,{recursive:!0,force:!0})}function xe(e,t){let n=$.join(e,".env.example");x.existsSync(n)?x.appendFileSync(n,`
39
- `+t):x.writeFileSync(n,t)}function Se(e,t){if(!x.existsSync(e))return;let n=x.readFileSync(e,"utf8"),s=n.replace(/export type AuthProviderId\s*=\s*"[^"]+"\s*\|\s*"[^"]+";/,`export type AuthProviderId = "${t}";`);s!==n&&x.writeFileSync(e,s)}function ft(e,t){let n=$.join(e,"README.md");if(!x.existsSync(n)){x.writeFileSync(n,t);return}x.appendFileSync(n,`
40
- `+t)}async function Ae(e,t){let n=$.join(e,"packages","auth");if(!x.existsSync(n))return;if(t==="none"){ue(n),ft(e,`
59
+ $ npm create sailor@latest my-app --region=cn --auth=clerk
60
+ $ npm create sailor@latest my-app --region=hybrid --ai=openai,deepseek
61
+ $ npm create sailor@latest --dry-run --region=cn
62
+ `;function Ht(){process.stdout.write(Lo)}import dt from"picocolors";function Wt(){return!process.env.NO_COLOR&&!!process.stdout.isTTY}function Mo(e){let t=Wt()?"\u25B8":"->",o=`[${e.index}/${e.total}]`,n=e.label.padEnd(18," ");return Wt()?`${dt.dim(o)} ${dt.bold(n)} ${dt.dim(t)} ${dt.cyan(e.value)}`:`${o} ${n} ${t} ${e.value}`}function Xt(e){process.stdout.write(Mo(e)+`
63
+ `)}var ut=[{id:"anthropic",name:"Anthropic",category:"\u76F4\u63A5\u5B9E\u9A8C\u5BA4",status:"opencode",docs:"https://docs.anthropic.com",envVarPrefix:"ANTHROPIC",requiredEnvVars:["ANTHROPIC_API_KEY"]},{id:"openai",name:"OpenAI",category:"\u76F4\u63A5\u5B9E\u9A8C\u5BA4",status:"opencode",docs:"https://platform.openai.com/docs",envVarPrefix:"OPENAI",requiredEnvVars:["OPENAI_API_KEY"]},{id:"deepseek",name:"DeepSeek",category:"\u76F4\u63A5\u5B9E\u9A8C\u5BA4",status:"opencode",docs:"https://platform.deepseek.com/api-docs",envVarPrefix:"DEEPSEEK",requiredEnvVars:["DEEPSEEK_API_KEY"]},{id:"xai",name:"xAI (Grok)",category:"\u76F4\u63A5\u5B9E\u9A8C\u5BA4",status:"opencode",docs:"https://docs.x.ai/api",envVarPrefix:"XAI",requiredEnvVars:["XAI_API_KEY"]},{id:"moonshot",name:"Moonshot (Kimi)",category:"\u76F4\u63A5\u5B9E\u9A8C\u5BA4",status:"opencode",docs:"https://platform.moonshot.cn/docs",envVarPrefix:"MOONSHOT",requiredEnvVars:["MOONSHOT_API_KEY","MOONSHOT_BASE_URL"]},{id:"google",name:"Google Gemini",category:"\u76F4\u63A5\u5B9E\u9A8C\u5BA4",status:"ai-sdk",docs:"https://ai.google.dev/gemini-api/docs",envVarPrefix:"GOOGLE_GENERATIVE_AI",requiredEnvVars:["GOOGLE_GENERATIVE_AI_API_KEY"]},{id:"mistral",name:"Mistral AI",category:"\u76F4\u63A5\u5B9E\u9A8C\u5BA4",status:"ai-sdk",docs:"https://docs.mistral.ai",envVarPrefix:"MISTRAL",requiredEnvVars:["MISTRAL_API_KEY"]},{id:"cohere",name:"Cohere",category:"\u76F4\u63A5\u5B9E\u9A8C\u5BA4",status:"ai-sdk",docs:"https://docs.cohere.com",envVarPrefix:"COHERE",requiredEnvVars:["COHERE_API_KEY"]},{id:"perplexity",name:"Perplexity",category:"\u76F4\u63A5\u5B9E\u9A8C\u5BA4",status:"cn-compatible",docs:"https://docs.perplexity.ai",baseURL:"https://api.perplexity.ai",envVarPrefix:"PERPLEXITY",requiredEnvVars:["PERPLEXITY_API_KEY"]},{id:"ai21",name:"AI21 Labs",category:"\u76F4\u63A5\u5B9E\u9A8C\u5BA4",status:"cn-compatible",docs:"https://docs.ai21.com",baseURL:"https://api.ai21.com/studio/v1",envVarPrefix:"AI21",requiredEnvVars:["AI21_API_KEY"]},{id:"upstage",name:"Upstage (Solar)",category:"\u76F4\u63A5\u5B9E\u9A8C\u5BA4",status:"cn-compatible",docs:"https://developers.upstage.ai/docs",baseURL:"https://api.upstage.ai/v1/solar",envVarPrefix:"UPSTAGE",requiredEnvVars:["UPSTAGE_API_KEY"]},{id:"siliconflow",name:"\u7845\u57FA\u6D41\u52A8 SiliconFlow",category:"\u56FD\u5185\u5E73\u53F0",status:"cn-compatible",docs:"https://docs.siliconflow.cn",baseURL:"https://api.siliconflow.cn/v1",envVarPrefix:"SILICONFLOW",requiredEnvVars:["SILICONFLOW_API_KEY"]},{id:"volcengine-ark",name:"\u706B\u5C71\u5F15\u64CE ARK (\u8C46\u5305)",category:"\u56FD\u5185\u5E73\u53F0",status:"cn-compatible",docs:"https://www.volcengine.com/docs/82379",baseURL:"https://ark.cn-beijing.volces.com/api/v3",envVarPrefix:"ARK",requiredEnvVars:["ARK_API_KEY"]},{id:"bailian",name:"\u767E\u70BC Bailian (\u901A\u4E49)",category:"\u56FD\u5185\u5E73\u53F0",status:"cn-compatible",docs:"https://help.aliyun.com/zh/model-studio",baseURL:"https://dashscope.aliyuncs.com/compatible-mode/v1",envVarPrefix:"DASHSCOPE",requiredEnvVars:["DASHSCOPE_API_KEY"]},{id:"zhipu",name:"\u667A\u8C31 AI Zhipu",category:"\u56FD\u5185\u5E73\u53F0",status:"ai-sdk",docs:"https://docs.bigmodel.cn",baseURL:"https://open.bigmodel.cn/api/paas/v4",envVarPrefix:"ZHIPU",requiredEnvVars:["ZHIPU_API_KEY"]},{id:"baichuan",name:"\u767E\u5DDD\u667A\u80FD Baichuan",category:"\u56FD\u5185\u5E73\u53F0",status:"cn-compatible",docs:"https://platform.baichuan-ai.com/docs",baseURL:"https://api.baichuan-ai.com/v1",envVarPrefix:"BAICHUAN",requiredEnvVars:["BAICHUAN_API_KEY"]},{id:"minimax",name:"MiniMax (ABAB)",category:"\u56FD\u5185\u5E73\u53F0",status:"cn-compatible",docs:"https://platform.minimaxi.com/document/guides",baseURL:"https://api.minimax.chat/v1",envVarPrefix:"MINIMAX",requiredEnvVars:["MINIMAX_API_KEY","MINIMAX_GROUP_ID"]},{id:"stepfun",name:"\u9636\u8DC3\u661F\u8FB0 StepFun",category:"\u56FD\u5185\u5E73\u53F0",status:"cn-compatible",docs:"https://platform.stepfun.com/docs",baseURL:"https://api.stepfun.com/v1",envVarPrefix:"STEPFUN",requiredEnvVars:["STEPFUN_API_KEY"]},{id:"sensetime",name:"\u5546\u6C64 SenseNova",category:"\u56FD\u5185\u5E73\u53F0",status:"cn-compatible",docs:"https://platform.sensetime.com",baseURL:"https://api.sensenova.cn/v1",envVarPrefix:"SENSENOVA",requiredEnvVars:["SENSENOVA_API_KEY"]},{id:"tencent",name:"\u817E\u8BAF\u6DF7\u5143 Hunyuan",category:"\u56FD\u5185\u5E73\u53F0",status:"cn-compatible",docs:"https://cloud.tencent.com/document/product/1729",baseURL:"https://api.hunyuan.cloud.tencent.com/v1",envVarPrefix:"HUNYUAN",requiredEnvVars:["HUNYUAN_API_KEY"]},{id:"lingyi",name:"\u96F6\u4E00\u4E07\u7269 Yi",category:"\u56FD\u5185\u5E73\u53F0",status:"cn-compatible",docs:"https://platform.lingyiwanwu.com/docs",baseURL:"https://api.lingyiwanwu.com/v1",envVarPrefix:"YI",requiredEnvVars:["YI_API_KEY"]},{id:"openrouter",name:"OpenRouter",category:"\u7EDF\u4E00\u7F51\u5173",status:"opencode",docs:"https://openrouter.ai/docs",envVarPrefix:"OPENROUTER",requiredEnvVars:["OPENROUTER_API_KEY"]},{id:"vercel-gateway",name:"Vercel AI Gateway",category:"\u7EDF\u4E00\u7F51\u5173",status:"opencode",docs:"https://vercel.com/docs/ai-gateway",envVarPrefix:"AI_GATEWAY",requiredEnvVars:["AI_GATEWAY_API_KEY"]},{id:"litellm",name:"LiteLLM Proxy",category:"\u7EDF\u4E00\u7F51\u5173",status:"cn-compatible",docs:"https://docs.litellm.ai",baseURL:"http://localhost:4000/v1",envVarPrefix:"LITELLM",requiredEnvVars:["LITELLM_API_KEY"]},{id:"portkey",name:"Portkey",category:"\u7EDF\u4E00\u7F51\u5173",status:"cn-compatible",docs:"https://portkey.ai/docs",baseURL:"https://api.portkey.ai/v1",envVarPrefix:"PORTKEY",requiredEnvVars:["PORTKEY_API_KEY"]},{id:"aws-bedrock",name:"AWS Bedrock",category:"\u4E91\u5E73\u53F0",status:"ai-sdk",docs:"https://aws.amazon.com/bedrock/",envVarPrefix:"AWS_BEDROCK",requiredEnvVars:["AWS_ACCESS_KEY_ID","AWS_SECRET_ACCESS_KEY","AWS_REGION"]},{id:"azure-openai",name:"Azure OpenAI",category:"\u4E91\u5E73\u53F0",status:"ai-sdk",docs:"https://azure.microsoft.com/en-us/products/cognitive-services/openai-service/",envVarPrefix:"AZURE_OPENAI",requiredEnvVars:["AZURE_OPENAI_API_KEY","AZURE_OPENAI_ENDPOINT"]},{id:"gcp-vertex",name:"GCP Vertex AI",category:"\u4E91\u5E73\u53F0",status:"ai-sdk",docs:"https://cloud.google.com/vertex-ai",envVarPrefix:"GOOGLE_VERTEX",requiredEnvVars:["GOOGLE_APPLICATION_CREDENTIALS"]},{id:"groq",name:"Groq",category:"\u63A8\u7406\u52A0\u901F",status:"opencode",docs:"https://console.groq.com/docs",envVarPrefix:"GROQ",requiredEnvVars:["GROQ_API_KEY"]},{id:"fireworks",name:"Fireworks AI",category:"\u63A8\u7406\u52A0\u901F",status:"opencode",docs:"https://docs.fireworks.ai",envVarPrefix:"FIREWORKS",requiredEnvVars:["FIREWORKS_API_KEY"]},{id:"together",name:"Together AI",category:"\u63A8\u7406\u52A0\u901F",status:"opencode",docs:"https://docs.together.ai",envVarPrefix:"TOGETHER",requiredEnvVars:["TOGETHER_API_KEY"]},{id:"huggingface",name:"Hugging Face",category:"\u63A8\u7406\u52A0\u901F",status:"opencode",docs:"https://huggingface.co/docs/inference-providers",envVarPrefix:"HF",requiredEnvVars:["HF_API_KEY"]},{id:"replicate",name:"Replicate",category:"\u63A8\u7406\u52A0\u901F",status:"cn-compatible",docs:"https://replicate.com/docs",envVarPrefix:"REPLICATE",requiredEnvVars:["REPLICATE_API_TOKEN"]},{id:"lepton",name:"Lepton AI",category:"\u63A8\u7406\u52A0\u901F",status:"cn-compatible",docs:"https://www.lepton.ai/docs",baseURL:"https://api.lepton.ai/v1",envVarPrefix:"LEPTON",requiredEnvVars:["LEPTON_API_KEY"]},{id:"anyscale",name:"Anyscale Endpoints",category:"\u63A8\u7406\u52A0\u901F",status:"cn-compatible",docs:"https://docs.endpoints.anyscale.com/",baseURL:"https://api.endpoints.anyscale.com/v1",envVarPrefix:"ANYSCALE",requiredEnvVars:["ANYSCALE_API_KEY"]},{id:"octoai",name:"OctoAI",category:"\u63A8\u7406\u52A0\u901F",status:"cn-compatible",docs:"https://octoai.cloud/docs",baseURL:"https://text.octoai.run/v1",envVarPrefix:"OCTOAI",requiredEnvVars:["OCTOAI_TOKEN"]},{id:"deepinfra",name:"DeepInfra",category:"\u63A8\u7406\u52A0\u901F",status:"cn-compatible",docs:"https://deepinfra.com/docs",baseURL:"https://api.deepinfra.com/v1/openai",envVarPrefix:"DEEPINFRA",requiredEnvVars:["DEEPINFRA_API_KEY"]},{id:"novita",name:"Novita AI",category:"\u63A8\u7406\u52A0\u901F",status:"cn-compatible",docs:"https://novita.ai/docs",baseURL:"https://api.novita.ai/v3/openai",envVarPrefix:"NOVITA",requiredEnvVars:["NOVITA_API_KEY"]},{id:"black-forest-labs",name:"Black Forest Labs (FLUX)",category:"\u591A\u6A21\u6001",status:"ai-sdk",docs:"https://docs.bfl.ml",envVarPrefix:"BFL",requiredEnvVars:["BFL_API_KEY"]},{id:"elevenlabs",name:"ElevenLabs (TTS)",category:"\u591A\u6A21\u6001",status:"ai-sdk",docs:"https://elevenlabs.io/docs",envVarPrefix:"ELEVENLABS",requiredEnvVars:["ELEVENLABS_API_KEY"]},{id:"runway",name:"Runway (Video)",category:"\u591A\u6A21\u6001",status:"pending",docs:"https://runwayml.com",envVarPrefix:"RUNWAY",requiredEnvVars:["RUNWAY_API_KEY"]},{id:"luma",name:"Luma (Video)",category:"\u591A\u6A21\u6001",status:"pending",docs:"https://lumalabs.ai",envVarPrefix:"LUMA",requiredEnvVars:["LUMA_API_KEY"]},{id:"midjourney",name:"Midjourney",category:"\u591A\u6A21\u6001",status:"pending",docs:"https://docs.midjourney.com",envVarPrefix:"MIDJOURNEY",requiredEnvVars:[]},{id:"ollama",name:"Ollama (\u672C\u5730)",category:"\u672C\u5730\u90E8\u7F72",status:"opencode",docs:"https://github.com/ollama/ollama",baseURL:"http://localhost:11434/v1",envVarPrefix:"OLLAMA",requiredEnvVars:["OLLAMA_BASE_URL"]},{id:"lmstudio",name:"LM Studio",category:"\u672C\u5730\u90E8\u7F72",status:"cn-compatible",docs:"https://lmstudio.ai/docs",baseURL:"http://localhost:1234/v1",envVarPrefix:"LMSTUDIO",requiredEnvVars:[]},{id:"vllm",name:"vLLM",category:"\u672C\u5730\u90E8\u7F72",status:"cn-compatible",docs:"https://docs.vllm.ai",baseURL:"http://localhost:8000/v1",envVarPrefix:"VLLM",requiredEnvVars:[]},{id:"tgi",name:"HF TGI",category:"\u672C\u5730\u90E8\u7F72",status:"cn-compatible",docs:"https://huggingface.co/docs/text-generation-inference",baseURL:"http://localhost:8080/v1",envVarPrefix:"TGI",requiredEnvVars:[]}],Ri=ut.reduce((e,t)=>(e[t.category]||(e[t.category]=[]),e[t.category].push(t),e),{});import Te from"fs";import A from"path";var zt=[{id:"posthog",name:"PostHog",region:"both",envVars:["POSTHOG_KEY","NEXT_PUBLIC_POSTHOG_KEY","NEXT_PUBLIC_POSTHOG_HOST"],docs:"https://posthog.com/docs",pkgs:["posthog-js","posthog-node"],providerFile:"apps/web/src/lib/analytics/posthog.client.ts"},{id:"plausible",name:"Plausible",region:"global",envVars:["NEXT_PUBLIC_PLAUSIBLE_DOMAIN","NEXT_PUBLIC_PLAUSIBLE_SRC"],docs:"https://plausible.io/docs",providerFile:"apps/web/src/components/analytics/PlausibleScript.tsx"},{id:"umami",name:"Umami",region:"both",envVars:["NEXT_PUBLIC_UMAMI_WEBSITE_ID","NEXT_PUBLIC_UMAMI_SRC"],docs:"https://umami.is/docs",providerFile:"apps/web/src/components/analytics/UmamiScript.tsx"},{id:"mixpanel",name:"Mixpanel",region:"global",envVars:["MIXPANEL_TOKEN","NEXT_PUBLIC_MIXPANEL_TOKEN"],docs:"https://docs.mixpanel.com",pkgs:["mixpanel-browser","mixpanel"],providerFile:"apps/web/src/lib/analytics/mixpanel.client.ts"},{id:"baidu",name:"\u767E\u5EA6\u7EDF\u8BA1 Baidu Tongji",region:"cn",envVars:["NEXT_PUBLIC_BAIDU_STATS_ID"],docs:"https://tongji.baidu.com",providerFile:"apps/web/src/components/analytics/BaiduStatsScript.tsx"},{id:"sensors",name:"\u795E\u7B56 Sensors Analytics",region:"cn",envVars:["NEXT_PUBLIC_SENSORS_SERVER_URL","NEXT_PUBLIC_SENSORS_APP_JS_URL"],docs:"https://manual.sensorsdata.cn",pkgs:["sa-sdk-javascript"],providerFile:"apps/web/src/lib/analytics/sensors.client.ts"},{id:"growingio",name:"GrowingIO",region:"cn",envVars:["NEXT_PUBLIC_GROWINGIO_ACCOUNT_ID"],docs:"https://growingio.com/docs",providerFile:"apps/web/src/components/analytics/GrowingIoScript.tsx"},{id:"none",name:"None",region:"both",envVars:[],docs:""}],Ni=zt.reduce((e,t)=>(e[t.region]||(e[t.region]=[]),e[t.region].push(t),e),{});function Qt(e){return zt.find(t=>t.id===e)}function Do(e){Te.mkdirSync(e,{recursive:!0})}function F(e,t){Do(A.dirname(e)),Te.writeFileSync(e,t)}function ie(e,t){let o=A.join(e,".env.example");Te.existsSync(o)?Te.appendFileSync(o,`
64
+ `+t):Te.writeFileSync(o,t)}function jo(e){let t=`"use client";
65
+ /**
66
+ * PostHog browser SDK \u2014 lazy-initialized on first access.
67
+ */
68
+ import posthog from "posthog-js";
69
+
70
+ let initialized = false;
71
+
72
+ export function initPostHog(): void {
73
+ if (initialized || typeof window === "undefined") return;
74
+ const key = process.env.NEXT_PUBLIC_POSTHOG_KEY;
75
+ if (!key) return;
76
+
77
+ posthog.init(key, {
78
+ api_host:
79
+ process.env.NEXT_PUBLIC_POSTHOG_HOST ?? "https://us.i.posthog.com",
80
+ person_profiles: "identified_only",
81
+ capture_pageview: true,
82
+ capture_pageleave: true,
83
+ autocapture: true,
84
+ });
85
+ initialized = true;
86
+ }
87
+
88
+ export { posthog };
89
+ `,o=`import { PostHog } from "posthog-node";
90
+
91
+ /**
92
+ * PostHog server-side client. Remember to await client.shutdown() at the end
93
+ * of serverless requests so events are flushed before the invocation dies.
94
+ */
95
+ let client: PostHog | null = null;
96
+
97
+ export function getPostHogServerClient(): PostHog | null {
98
+ if (client) return client;
99
+ const key = process.env.POSTHOG_KEY ?? process.env.NEXT_PUBLIC_POSTHOG_KEY;
100
+ if (!key) return null;
101
+
102
+ client = new PostHog(key, {
103
+ host: process.env.NEXT_PUBLIC_POSTHOG_HOST ?? "https://us.i.posthog.com",
104
+ flushAt: 1,
105
+ flushInterval: 0,
106
+ });
107
+ return client;
108
+ }
109
+
110
+ export async function captureServerEvent(
111
+ distinctId: string,
112
+ event: string,
113
+ properties?: Record<string, unknown>,
114
+ ): Promise<void> {
115
+ const ph = getPostHogServerClient();
116
+ if (!ph) return;
117
+ ph.capture({ distinctId, event, properties });
118
+ await ph.shutdown();
119
+ }
120
+ `,n=`"use client";
121
+ /**
122
+ * <AnalyticsProvider /> \u2014 wrap the app root to enable PostHog + auto page views.
123
+ */
124
+ import { useEffect } from "react";
125
+ import { usePathname, useSearchParams } from "next/navigation";
126
+ import { initPostHog, posthog } from "../../lib/analytics/posthog.client";
127
+
128
+ export function AnalyticsProvider({ children }: { children: React.ReactNode }) {
129
+ const pathname = usePathname();
130
+ const searchParams = useSearchParams();
131
+
132
+ useEffect(() => {
133
+ initPostHog();
134
+ }, []);
135
+
136
+ useEffect(() => {
137
+ if (!pathname) return;
138
+ const url =
139
+ window.location.origin +
140
+ pathname +
141
+ (searchParams?.toString() ? \`?\${searchParams.toString()}\` : "");
142
+ posthog.capture("$pageview", { $current_url: url });
143
+ }, [pathname, searchParams]);
144
+
145
+ return <>{children}</>;
146
+ }
147
+ `;F(A.join(e,"src","lib","analytics","posthog.client.ts"),t),F(A.join(e,"src","lib","analytics","posthog.server.ts"),o),F(A.join(e,"src","components","analytics","AnalyticsProvider.tsx"),n)}function Fo(){return`# PostHog \u2014 product analytics
148
+ POSTHOG_KEY=
149
+ NEXT_PUBLIC_POSTHOG_KEY=
150
+ NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
151
+ `}function Vo(e){F(A.join(e,"src","components","analytics","PlausibleScript.tsx"),`"use client";
152
+ /**
153
+ * Plausible script tag \u2014 lightweight, privacy-friendly analytics.
154
+ */
155
+ import Script from "next/script";
156
+
157
+ export function PlausibleScript() {
158
+ const domain = process.env.NEXT_PUBLIC_PLAUSIBLE_DOMAIN;
159
+ const src =
160
+ process.env.NEXT_PUBLIC_PLAUSIBLE_SRC ?? "https://plausible.io/js/script.js";
161
+ if (!domain) return null;
162
+ return (
163
+ <Script
164
+ id="plausible"
165
+ strategy="afterInteractive"
166
+ data-domain={domain}
167
+ src={src}
168
+ />
169
+ );
170
+ }
171
+ `)}function Ko(){return`# Plausible \u2014 privacy-friendly analytics
172
+ NEXT_PUBLIC_PLAUSIBLE_DOMAIN=
173
+ NEXT_PUBLIC_PLAUSIBLE_SRC=https://plausible.io/js/script.js
174
+ `}function Yo(e){F(A.join(e,"src","components","analytics","UmamiScript.tsx"),`"use client";
175
+ /**
176
+ * Umami analytics \u2014 self-hosted OR umami.is cloud.
177
+ */
178
+ import Script from "next/script";
179
+
180
+ export function UmamiScript() {
181
+ const websiteId = process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID;
182
+ const src =
183
+ process.env.NEXT_PUBLIC_UMAMI_SRC ?? "https://cloud.umami.is/script.js";
184
+ if (!websiteId) return null;
185
+ return (
186
+ <Script
187
+ id="umami"
188
+ strategy="afterInteractive"
189
+ src={src}
190
+ data-website-id={websiteId}
191
+ />
192
+ );
193
+ }
194
+ `)}function Bo(){return`# Umami \u2014 self-hostable analytics
195
+ NEXT_PUBLIC_UMAMI_WEBSITE_ID=
196
+ NEXT_PUBLIC_UMAMI_SRC=https://cloud.umami.is/script.js
197
+ `}function $o(e){let t=`"use client";
198
+ import mixpanel from "mixpanel-browser";
199
+
200
+ let initialized = false;
201
+
202
+ export function initMixpanel(): void {
203
+ if (initialized || typeof window === "undefined") return;
204
+ const token = process.env.NEXT_PUBLIC_MIXPANEL_TOKEN;
205
+ if (!token) return;
206
+ mixpanel.init(token, {
207
+ debug: process.env.NODE_ENV !== "production",
208
+ track_pageview: true,
209
+ persistence: "localStorage",
210
+ });
211
+ initialized = true;
212
+ }
213
+
214
+ export { mixpanel };
215
+ `,o=`import Mixpanel from "mixpanel";
216
+
217
+ let client: Mixpanel.Mixpanel | null = null;
218
+
219
+ export function getMixpanelServerClient(): Mixpanel.Mixpanel | null {
220
+ if (client) return client;
221
+ const token = process.env.MIXPANEL_TOKEN ?? process.env.NEXT_PUBLIC_MIXPANEL_TOKEN;
222
+ if (!token) return null;
223
+ client = Mixpanel.init(token);
224
+ return client;
225
+ }
226
+
227
+ export function trackServer(
228
+ distinctId: string,
229
+ event: string,
230
+ properties?: Record<string, unknown>,
231
+ ): void {
232
+ const c = getMixpanelServerClient();
233
+ if (!c) return;
234
+ c.track(event, { distinct_id: distinctId, ...properties });
235
+ }
236
+ `;F(A.join(e,"src","lib","analytics","mixpanel.client.ts"),t),F(A.join(e,"src","lib","analytics","mixpanel.server.ts"),o)}function qo(){return`# Mixpanel \u2014 product analytics
237
+ MIXPANEL_TOKEN=
238
+ NEXT_PUBLIC_MIXPANEL_TOKEN=
239
+ `}function Go(e){F(A.join(e,"src","components","analytics","BaiduStatsScript.tsx"),`"use client";
240
+ /**
241
+ * \u767E\u5EA6\u7EDF\u8BA1 Baidu Tongji \u2014 hm.js \u57CB\u70B9\u811A\u672C.
242
+ */
243
+ import Script from "next/script";
244
+
245
+ export function BaiduStatsScript() {
246
+ const id = process.env.NEXT_PUBLIC_BAIDU_STATS_ID;
247
+ if (!id) return null;
248
+ const inline = \`
249
+ var _hmt = _hmt || [];
250
+ (function() {
251
+ var hm = document.createElement("script");
252
+ hm.src = "https://hm.baidu.com/hm.js?\${id}";
253
+ var s = document.getElementsByTagName("script")[0];
254
+ s.parentNode.insertBefore(hm, s);
255
+ })();
256
+ \`;
257
+ return (
258
+ <Script
259
+ id="baidu-stats"
260
+ strategy="afterInteractive"
261
+ dangerouslySetInnerHTML={{ __html: inline }}
262
+ />
263
+ );
264
+ }
265
+ `)}function Ho(){return`# \u767E\u5EA6\u7EDF\u8BA1 \u2014 \u4E2D\u56FD\u533A\u7F51\u7AD9\u5206\u6790
266
+ NEXT_PUBLIC_BAIDU_STATS_ID=
267
+ `}function Wo(e){F(A.join(e,"src","lib","analytics","sensors.client.ts"),`"use client";
268
+ /**
269
+ * \u795E\u7B56\u5206\u6790 (Sensors Analytics) \u2014 sa-sdk-javascript \u5C01\u88C5.
270
+ */
271
+ import sensors from "sa-sdk-javascript";
272
+
273
+ let initialized = false;
274
+
275
+ export function initSensors(): void {
276
+ if (initialized || typeof window === "undefined") return;
277
+ const serverUrl = process.env.NEXT_PUBLIC_SENSORS_SERVER_URL;
278
+ if (!serverUrl) return;
279
+
280
+ sensors.init({
281
+ server_url: serverUrl,
282
+ is_track_single_page: true,
283
+ use_client_time: true,
284
+ send_type: "beacon",
285
+ heatmap: {
286
+ clickmap: "default",
287
+ scroll_notice_map: "default",
288
+ },
289
+ });
290
+ sensors.quick("autoTrack");
291
+ initialized = true;
292
+ }
293
+
294
+ export { sensors };
295
+ `)}function Xo(){return`# \u795E\u7B56\u5206\u6790 Sensors Analytics
296
+ NEXT_PUBLIC_SENSORS_SERVER_URL=
297
+ NEXT_PUBLIC_SENSORS_APP_JS_URL=
298
+ `}function zo(e){F(A.join(e,"src","components","analytics","GrowingIoScript.tsx"),`"use client";
299
+ /**
300
+ * GrowingIO CDP \u2014 inline snippet installer.
301
+ */
302
+ import Script from "next/script";
303
+
304
+ export function GrowingIoScript() {
305
+ const accountId = process.env.NEXT_PUBLIC_GROWINGIO_ACCOUNT_ID;
306
+ if (!accountId) return null;
307
+ const inline = \`
308
+ !(function(e,t,n,g,i){e[i]=e[i]||function(){(e[i].q=e[i].q||[]).push(arguments)};
309
+ var a=t.createElement(n);a.async=!0;a.src=g;
310
+ var s=t.getElementsByTagName(n)[0];s.parentNode.insertBefore(a,s);
311
+ })(window,document,"script","https://assets.giocdn.com/vds.js","gdp");
312
+ gdp("init", "\${accountId}", {});
313
+ gdp("send");
314
+ \`;
315
+ return (
316
+ <Script
317
+ id="growingio"
318
+ strategy="afterInteractive"
319
+ dangerouslySetInnerHTML={{ __html: inline }}
320
+ />
321
+ );
322
+ }
323
+ `)}function Qo(){return`# GrowingIO \u2014 \u5168\u57CB\u70B9 CDP (\u4E2D\u56FD\u533A)
324
+ NEXT_PUBLIC_GROWINGIO_ACCOUNT_ID=
325
+ `}async function Jt(e,t,o="global"){let n=Qt(t);if(!n||n.id==="none")return;let r=A.join(e,"apps","web");if(!Te.existsSync(r))return;let s=n.id;try{switch(s){case"posthog":jo(r),ie(e,Fo());return;case"plausible":Vo(r),ie(e,Ko());return;case"umami":Yo(r),ie(e,Bo());return;case"mixpanel":$o(r),ie(e,qo());return;case"baidu":Go(r),ie(e,Ho());return;case"sensors":Wo(r),ie(e,Xo());return;case"growingio":zo(r),ie(e,Qo());return;default:return}}catch(a){throw console.error(`Failed to apply analytics selection "${t}":`,a),new Error(`Analytics scaffold for "${t}" failed \u2014 see stderr for details.`)}}import w from"fs";import ae from"path";function wt(e){w.existsSync(e)&&w.rmSync(e,{recursive:!0,force:!0})}function Zt(e,t){let o=ae.join(e,".env.example");w.existsSync(o)?w.appendFileSync(o,`
326
+ `+t):w.writeFileSync(o,t)}function en(e,t){if(!w.existsSync(e))return;let o=w.readFileSync(e,"utf8"),n=o.replace(/export type AuthProviderId\s*=\s*"[^"]+"\s*\|\s*"[^"]+";/,`export type AuthProviderId = "${t}";`);n!==o&&w.writeFileSync(e,n)}function Jo(e,t){let o=ae.join(e,"README.md");if(!w.existsSync(o)){w.writeFileSync(o,t);return}w.appendFileSync(o,`
327
+ `+t)}async function tn(e,t){let o=ae.join(e,"packages","auth");if(!w.existsSync(o))return;if(t==="none"){wt(o),Jo(e,`
41
328
  ## Auth
42
329
 
43
330
  Auth was skipped at scaffold time (\`--auth=none\`). Add your preferred auth provider manually (e.g. Clerk, Better Auth, NextAuth).
44
- `);return}let s=$.join(n,"src","providers"),r=$.join(n,"src","types.ts");if(t==="clerk"){ue($.join(s,"better-auth.ts")),Se(r,"clerk"),xe(e,`# Clerk auth
331
+ `);return}let n=ae.join(o,"src","providers"),r=ae.join(o,"src","types.ts");if(t==="clerk"){wt(ae.join(n,"better-auth.ts")),en(r,"clerk"),Zt(e,`# Clerk auth
45
332
  CLERK_PUBLISHABLE_KEY=
46
333
  CLERK_SECRET_KEY=
47
- `);return}if(t==="betterauth"){ue($.join(s,"clerk.ts")),Se(r,"better-auth"),xe(e,`# Better Auth
334
+ `);return}if(t==="betterauth"){wt(ae.join(n,"clerk.ts")),en(r,"better-auth"),Zt(e,`# Better Auth
48
335
  BETTER_AUTH_SECRET=
49
336
  BETTER_AUTH_URL=http://localhost:3000
50
- `);return}}import yt from"fs";import gt from"path";async function we(e,t){let n=gt.join(e,"nebutra.config.json");yt.writeFileSync(n,JSON.stringify(t,null,2)+`
51
- `)}import j from"fs";import de from"path";function vt(e){j.existsSync(e)&&j.rmSync(e,{recursive:!0,force:!0})}function ke(e,t){return e.replace(/provider\s*=\s*"postgresql"/,`provider = "${t}"`)}function ht(e){return e.replace(/^\s*directUrl\s*=\s*env\([^)]*\)\s*\n/gm,"")}function Ie(e){let t=e.replace(/^\s*extensions\s*=\s*\[[^\]]*\]\s*\n/gm,"");return t=t.replace(/previewFeatures\s*=\s*\[\s*"postgresqlExtensions"\s*\]\s*\n/g,""),t}function Et(e,t){let n=de.join(e,".env.example");j.existsSync(n)?j.appendFileSync(n,`
52
- `+t):j.writeFileSync(n,t)}async function _e(e,t,n){let s=de.join(e,"packages","db");if(t==="none"){vt(s);return}let r=de.join(s,"prisma","schema.prisma"),a;if(t==="postgresql"?a=`postgresql://postgres:postgres@localhost:5432/${n}`:t==="mysql"?a=`mysql://root:root@localhost:3306/${n}`:a="file:./dev.db",Et(e,`# Database (${t})
53
- DATABASE_URL="${a}"
54
- `),!j.existsSync(r))return;let p=j.readFileSync(r,"utf8"),i=p;t==="mysql"?(i=ke(i,"mysql"),i=Ie(i)):t==="sqlite"&&(i=ke(i,"sqlite"),i=ht(i),i=Ie(i)),i!==p&&j.writeFileSync(r,i)}import Z from"fs";import S from"path";import{fileURLToPath as Pt}from"url";var bt=Pt(import.meta.url),xt=S.dirname(bt);async function Re(e,t){if(t==="none")return;let n=S.join(xt,"../../templates/deploy");t==="vercel"?await Z.promises.copyFile(S.join(n,"vercel.json"),S.join(e,"vercel.json")):t==="railway"?await Z.promises.copyFile(S.join(n,"railway.toml"),S.join(e,"railway.toml")):t==="cloudflare"?await Z.promises.copyFile(S.join(n,"wrangler.toml"),S.join(e,"wrangler.toml")):t==="selfhost"&&(await Z.promises.copyFile(S.join(n,"docker-compose.yml"),S.join(e,"docker-compose.yml")),await Z.promises.copyFile(S.join(n,"Dockerfile.web"),S.join(e,"Dockerfile.web")))}import q from"fs";import Y from"path";import{fileURLToPath as St}from"url";var At=St(import.meta.url),Ce=Y.dirname(At),wt=/\.(md|mdx|json|ts|tsx|js|mjs|cjs|yaml|yml)$/;function kt(e){let t=[Y.join(Ce,"..","templates","docs",e),Y.join(Ce,"..","..","templates","docs",e)];for(let n of t)if(q.existsSync(n))return n;return t[0]}async function Le(e,t){if(t.framework==="none")return;let n=(t.framework==="fumadocs","fumadocs"),s=kt(n),r=Y.join(e,"apps","docs");if(!q.existsSync(s))throw new Error(`Docs template not found: ${s}`);Te(s,r),Ne(r,{"{PRODUCT_NAME}":t.projectName})}function Te(e,t){q.mkdirSync(t,{recursive:!0});for(let n of q.readdirSync(e,{withFileTypes:!0})){let s=Y.join(e,n.name),r=Y.join(t,n.name);n.isDirectory()?Te(s,r):q.copyFileSync(s,r)}}function Ne(e,t){for(let n of q.readdirSync(e,{withFileTypes:!0})){let s=Y.join(e,n.name);if(n.isDirectory())Ne(s,t);else if(wt.test(n.name)){let r=q.readFileSync(s,"utf-8");for(let[a,p]of Object.entries(t))r=r.split(a).join(p);q.writeFileSync(s,r)}}}import ae from"fs";import Oe from"path";async function Ve(e,t){let n=`# Automatically injected by create-sailor
337
+ `);return}}var Xe=[{id:"wechat",name:"\u5FAE\u4FE1\u5F00\u653E\u5E73\u53F0",region:"cn",envVars:["WECHAT_APP_ID","WECHAT_APP_SECRET"],docs:"https://open.weixin.qq.com/cgi-bin/showdocument",callbackUrl:"/api/auth/callback/wechat",tokenEndpoint:"https://api.weixin.qq.com/sns/oauth2/access_token"},{id:"qq",name:"QQ \u767B\u5F55",region:"cn",envVars:["QQ_APP_ID","QQ_APP_KEY"],docs:"https://wiki.connect.qq.com",callbackUrl:"/api/auth/callback/qq",tokenEndpoint:"https://graph.qq.com/oauth2.0/token"},{id:"dingtalk",name:"\u9489\u9489",region:"cn",envVars:["DINGTALK_APP_ID","DINGTALK_APP_SECRET"],docs:"https://open.dingtalk.com/document/",callbackUrl:"/api/auth/callback/dingtalk",tokenEndpoint:"https://api.dingtalk.com/v1.0/oauth2/userAccessToken"},{id:"workweixin",name:"\u4F01\u4E1A\u5FAE\u4FE1",region:"cn",envVars:["WORKWEIXIN_CORP_ID","WORKWEIXIN_AGENT_ID","WORKWEIXIN_SECRET"],docs:"https://developer.work.weixin.qq.com",callbackUrl:"/api/auth/callback/workweixin",tokenEndpoint:"https://qyapi.weixin.qq.com/cgi-bin/gettoken"},{id:"feishu",name:"\u98DE\u4E66 / Lark",region:"cn",envVars:["FEISHU_APP_ID","FEISHU_APP_SECRET"],docs:"https://open.feishu.cn/document/",callbackUrl:"/api/auth/callback/feishu",tokenEndpoint:"https://open.feishu.cn/open-apis/authen/v1/access_token"},{id:"weibo",name:"\u5FAE\u535A",region:"cn",envVars:["WEIBO_APP_KEY","WEIBO_APP_SECRET"],docs:"https://open.weibo.com/wiki/",callbackUrl:"/api/auth/callback/weibo",tokenEndpoint:"https://api.weibo.com/oauth2/access_token"}],Zo=new Set(Xe.map(e=>e.id));function nn(e){return Xe.find(t=>t.id===e)}function on(e){if(!e)return[];let t=e.split(",").map(r=>r.trim().toLowerCase()).filter(Boolean);if(t.includes("none"))return[];let o=[],n=new Set;for(let r of t)Zo.has(r)&&!n.has(r)&&(o.push(r),n.add(r));return o}import pe from"fs";import ce from"path";function er(e,t){let o=`// apps/web/src/app/api/auth/callback/${e.id}/route.ts
338
+ // Generated by create-sailor \u2014 OAuth callback for ${e.name}
339
+ // Primary auth provider: ${t}
340
+ // Docs: ${e.docs}`,n={wechat:` const tokenRes = await fetch(
341
+ "${e.tokenEndpoint}?" +
342
+ new URLSearchParams({
343
+ appid: process.env.WECHAT_APP_ID ?? "",
344
+ secret: process.env.WECHAT_APP_SECRET ?? "",
345
+ code,
346
+ grant_type: "authorization_code",
347
+ }),
348
+ );
349
+ const token = (await tokenRes.json()) as {
350
+ access_token?: string;
351
+ openid?: string;
352
+ errcode?: number;
353
+ errmsg?: string;
354
+ };
355
+ if (!token.access_token || !token.openid) {
356
+ return NextResponse.redirect(new URL("/login?error=wechat_token", req.url));
357
+ }`,qq:` const tokenRes = await fetch(
358
+ "${e.tokenEndpoint}?" +
359
+ new URLSearchParams({
360
+ client_id: process.env.QQ_APP_ID ?? "",
361
+ client_secret: process.env.QQ_APP_KEY ?? "",
362
+ code,
363
+ grant_type: "authorization_code",
364
+ redirect_uri: new URL("${e.callbackUrl}", req.url).toString(),
365
+ fmt: "json",
366
+ }),
367
+ );
368
+ const token = (await tokenRes.json()) as { access_token?: string; openid?: string };
369
+ if (!token.access_token) {
370
+ return NextResponse.redirect(new URL("/login?error=qq_token", req.url));
371
+ }`,dingtalk:` const tokenRes = await fetch("${e.tokenEndpoint}", {
372
+ method: "POST",
373
+ headers: { "Content-Type": "application/json" },
374
+ body: JSON.stringify({
375
+ clientId: process.env.DINGTALK_APP_ID ?? "",
376
+ clientSecret: process.env.DINGTALK_APP_SECRET ?? "",
377
+ code,
378
+ grantType: "authorization_code",
379
+ }),
380
+ });
381
+ const token = (await tokenRes.json()) as { accessToken?: string };
382
+ if (!token.accessToken) {
383
+ return NextResponse.redirect(new URL("/login?error=dingtalk_token", req.url));
384
+ }`,workweixin:` // Enterprise WeChat returns an access_token by pairing corpId + secret
385
+ const tokenRes = await fetch(
386
+ "${e.tokenEndpoint}?" +
387
+ new URLSearchParams({
388
+ corpid: process.env.WORKWEIXIN_CORP_ID ?? "",
389
+ corpsecret: process.env.WORKWEIXIN_SECRET ?? "",
390
+ }),
391
+ );
392
+ const token = (await tokenRes.json()) as {
393
+ access_token?: string;
394
+ errcode?: number;
395
+ errmsg?: string;
396
+ };
397
+ if (!token.access_token) {
398
+ return NextResponse.redirect(new URL("/login?error=workweixin_token", req.url));
399
+ }`,feishu:` // Step 1: app_access_token
400
+ const appTokenRes = await fetch(
401
+ "https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal",
402
+ {
403
+ method: "POST",
404
+ headers: { "Content-Type": "application/json" },
405
+ body: JSON.stringify({
406
+ app_id: process.env.FEISHU_APP_ID ?? "",
407
+ app_secret: process.env.FEISHU_APP_SECRET ?? "",
408
+ }),
409
+ },
410
+ );
411
+ const appToken = (await appTokenRes.json()) as { app_access_token?: string };
412
+ if (!appToken.app_access_token) {
413
+ return NextResponse.redirect(new URL("/login?error=feishu_app_token", req.url));
414
+ }
415
+ // Step 2: user access_token
416
+ const tokenRes = await fetch("${e.tokenEndpoint}", {
417
+ method: "POST",
418
+ headers: {
419
+ "Content-Type": "application/json",
420
+ Authorization: \`Bearer \${appToken.app_access_token}\`,
421
+ },
422
+ body: JSON.stringify({ grant_type: "authorization_code", code }),
423
+ });
424
+ const token = (await tokenRes.json()) as { data?: { access_token?: string } };
425
+ if (!token.data?.access_token) {
426
+ return NextResponse.redirect(new URL("/login?error=feishu_token", req.url));
427
+ }`,weibo:` const tokenRes = await fetch("${e.tokenEndpoint}", {
428
+ method: "POST",
429
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
430
+ body: new URLSearchParams({
431
+ client_id: process.env.WEIBO_APP_KEY ?? "",
432
+ client_secret: process.env.WEIBO_APP_SECRET ?? "",
433
+ grant_type: "authorization_code",
434
+ code,
435
+ redirect_uri: new URL("${e.callbackUrl}", req.url).toString(),
436
+ }),
437
+ });
438
+ const token = (await tokenRes.json()) as { access_token?: string; uid?: string };
439
+ if (!token.access_token) {
440
+ return NextResponse.redirect(new URL("/login?error=weibo_token", req.url));
441
+ }`};return`${o}
442
+ import { type NextRequest, NextResponse } from "next/server";
443
+
444
+ export async function GET(req: NextRequest) {
445
+ const code = req.nextUrl.searchParams.get("code");
446
+ const state = req.nextUrl.searchParams.get("state");
447
+
448
+ if (!code) {
449
+ return NextResponse.redirect(new URL("/login?error=no_code", req.url));
450
+ }
451
+
452
+ ${n[e.id]}
453
+
454
+ // TODO: fetch user profile from ${e.name} using the access token.
455
+ // TODO: upsert the user into your primary auth provider (${t}).
456
+ // e.g. clerk.users.createUser(...) or betterAuth.signIn.oauth(...).
457
+ // Persist a session cookie before redirecting.
458
+
459
+ void state;
460
+ return NextResponse.redirect(new URL("/dashboard", req.url));
461
+ }
462
+ `}function tr(e){return`"use client";
463
+
464
+ /**
465
+ * SocialLoginButtons \u2014 generated by create-sailor.
466
+ *
467
+ * Renders a button per selected China-region OAuth provider. Clicking a
468
+ * button navigates to the provider's authorization URL (\`/api/auth/oauth/<id>\`),
469
+ * which the app is expected to implement and redirect to the upstream consent
470
+ * page. The OAuth callback lives at \`/api/auth/callback/<id>\`.
471
+ */
472
+
473
+ import { Button } from "@nebutra/ui/components";
474
+
475
+ interface SocialProvider {
476
+ id: string;
477
+ name: string;
478
+ href: string;
479
+ }
480
+
481
+ const PROVIDERS: SocialProvider[] = [
482
+ ${e.map(o=>` { id: ${JSON.stringify(o.id)}, name: ${JSON.stringify(o.name)}, href: ${JSON.stringify(`/api/auth/oauth/${o.id}`)} }`).join(`,
483
+ `)},
484
+ ];
485
+
486
+ export function SocialLoginButtons() {
487
+ return (
488
+ <div className="space-y-2">
489
+ {PROVIDERS.map((p) => (
490
+ <Button
491
+ key={p.id}
492
+ type="button"
493
+ variant="outline"
494
+ className="w-full"
495
+ onClick={() => {
496
+ window.location.href = p.href;
497
+ }}
498
+ >
499
+ Sign in with {p.name}
500
+ </Button>
501
+ ))}
502
+ </div>
503
+ );
504
+ }
505
+ `}function rn(e){pe.mkdirSync(e,{recursive:!0})}function nr(e,t){let o=ce.join(e,".env.example");pe.existsSync(o)?pe.appendFileSync(o,`
506
+ `+t):pe.writeFileSync(o,t)}function or(e){let t=`# ${e.name} (${e.id}) \u2014 ${e.docs}`,o=e.envVars.map(n=>`${n}=`).join(`
507
+ `);return`${t}
508
+ ${o}
509
+ `}async function sn(e,t,o){if(!t||t.length===0)return;let n=ce.join(e,"apps","web");if(!pe.existsSync(n))return;let r=new Set,s=[];for(let d of t){if(r.has(d))continue;r.add(d);let m=nn(d);m&&s.push(m)}if(s.length===0)return;for(let d of s)nr(e,or(d));let a=ce.join(n,"src","app","api","auth","callback");for(let d of s){let m=ce.join(a,d.id);rn(m);let E=ce.join(m,"route.ts");pe.writeFileSync(E,er(d,o))}let c=ce.join(n,"src","components","auth");rn(c);let i=ce.join(c,"SocialLoginButtons.tsx");pe.writeFileSync(i,tr(s))}import I from"fs";import _ from"path";var rr=_.join("apps","web"),Nt=_.join("src","components","compliance"),ze=_.join("src","app","[locale]");function V(e){return _.join(e,rr)}function sr(e){I.mkdirSync(e,{recursive:!0})}function O(e,t){sr(_.dirname(e)),I.writeFileSync(e,t,"utf8")}function xt(e,t){let o=_.join(e,".env.example");if(I.existsSync(o)){if(I.readFileSync(o,"utf8").includes(t.split(`
510
+ `)[0]??""))return;I.appendFileSync(o,`
511
+ `+t+`
512
+ `)}else I.writeFileSync(o,t+`
513
+ `)}var Qe=`> **TEMPLATE \u2014 NOT LEGAL ADVICE.** This document is a boilerplate scaffold only.
514
+ > Consult a qualified lawyer in every jurisdiction you operate in before publishing.
515
+ > \u672C\u6587\u6863\u4E3A\u6A21\u677F\u811A\u624B\u67B6\uFF0C\u4E0D\u6784\u6210\u6CD5\u5F8B\u610F\u89C1\u3002\u53D1\u5E03\u524D\u8BF7\u54A8\u8BE2\u76F8\u5173\u53F8\u6CD5\u8F96\u533A\u7684\u5408\u683C\u5F8B\u5E08\u3002`,ir=`import Link from "next/link";
516
+ import { IcpFooter } from "@/components/compliance/IcpFooter";
517
+ import { PublicSecurityFooter } from "@/components/compliance/PublicSecurityFooter";
518
+
519
+ interface FooterProps {
520
+ region?: "global" | "cn" | "hybrid";
521
+ }
522
+
523
+ export function Footer({ region = "global" }: FooterProps) {
524
+ const showCn = region === "cn" || region === "hybrid";
525
+ return (
526
+ <footer className="border-t border-[var(--neutral-7)] bg-[var(--neutral-2)] py-8">
527
+ <div className="mx-auto flex max-w-[1400px] flex-col gap-4 px-4 text-sm text-[var(--neutral-11)] md:flex-row md:items-center md:justify-between md:px-6">
528
+ <div className="flex flex-wrap items-center gap-4">
529
+ <Link href="/privacy" className="hover:underline">
530
+ Privacy Policy / \u9690\u79C1\u653F\u7B56
531
+ </Link>
532
+ <Link href="/terms" className="hover:underline">
533
+ Terms of Service / \u670D\u52A1\u6761\u6B3E
534
+ </Link>
535
+ <button
536
+ type="button"
537
+ onClick={() => window.dispatchEvent(new CustomEvent("cookie-consent-open"))}
538
+ className="hover:underline"
539
+ >
540
+ Cookie Settings
541
+ </button>
542
+ {showCn ? (
543
+ <>
544
+ <Link href="/aigc-disclosure" className="hover:underline">
545
+ AIGC \u8BF4\u660E
546
+ </Link>
547
+ <Link href="/cross-border-data" className="hover:underline">
548
+ \u6570\u636E\u51FA\u5883
549
+ </Link>
550
+ <Link href="/pii-collection" className="hover:underline">
551
+ \u4E2A\u4EBA\u4FE1\u606F\u6E05\u5355
552
+ </Link>
553
+ </>
554
+ ) : null}
555
+ </div>
556
+ {showCn ? (
557
+ <div className="flex flex-wrap items-center gap-4">
558
+ <IcpFooter />
559
+ <PublicSecurityFooter />
560
+ </div>
561
+ ) : null}
562
+ </div>
563
+ </footer>
564
+ );
565
+ }
566
+ `,ar=`export function IcpFooter() {
567
+ const icp = process.env.NEXT_PUBLIC_ICP_BEIAN;
568
+ if (!icp) return null;
569
+ return (
570
+ <a
571
+ href="https://beian.miit.gov.cn/"
572
+ target="_blank"
573
+ rel="noopener noreferrer"
574
+ className="text-xs text-[var(--neutral-11)] hover:underline"
575
+ >
576
+ {icp}
577
+ </a>
578
+ );
579
+ }
580
+ `,cr=`export function PublicSecurityFooter() {
581
+ const psb = process.env.NEXT_PUBLIC_PSB_BEIAN;
582
+ const psbCode = process.env.NEXT_PUBLIC_PSB_CODE;
583
+ if (!psb) return null;
584
+ return (
585
+ <a
586
+ href={\`http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=\${psbCode ?? ""}\`}
587
+ target="_blank"
588
+ rel="noopener noreferrer"
589
+ className="inline-flex items-center gap-1 text-xs text-[var(--neutral-11)] hover:underline"
590
+ >
591
+ {/* Place /public/compliance/psb-icon.png (\u5B98\u65B9\u516C\u5B89\u5907\u6848\u5C0F\u5FBD\u6807) */}
592
+ <img src="/compliance/psb-icon.png" alt="\u516C\u5B89\u5907\u6848" className="h-3 w-3" />
593
+ {psb}
594
+ </a>
595
+ );
596
+ }
597
+ `,pr=`"use client";
598
+
599
+ import { useCallback, useEffect, useState } from "react";
600
+
601
+ type ConsentMode = "accepted" | "rejected" | "custom";
602
+
603
+ interface CustomConsent {
604
+ necessary: true; // always true per PIPL/GDPR
605
+ analytics: boolean;
606
+ marketing: boolean;
607
+ preferences: boolean;
608
+ }
609
+
610
+ interface StoredConsent {
611
+ mode: ConsentMode;
612
+ categories: CustomConsent;
613
+ timestamp: number;
614
+ version: 1;
615
+ }
616
+
617
+ const STORAGE_KEY = "cookie-consent";
618
+ const CONSENT_EVENT = "cookie-consent-changed";
619
+ const OPEN_EVENT = "cookie-consent-open";
620
+
621
+ const DEFAULT_CATEGORIES: CustomConsent = {
622
+ necessary: true,
623
+ analytics: false,
624
+ marketing: false,
625
+ preferences: false,
626
+ };
627
+
628
+ function emitConsent(consent: StoredConsent): void {
629
+ if (typeof window === "undefined") return;
630
+ window.dispatchEvent(new CustomEvent(CONSENT_EVENT, { detail: consent }));
631
+ }
632
+
633
+ export function CookieBanner() {
634
+ const [isOpen, setIsOpen] = useState(false);
635
+ const [showCustomize, setShowCustomize] = useState(false);
636
+ const [categories, setCategories] = useState<CustomConsent>(DEFAULT_CATEGORIES);
637
+
638
+ useEffect(() => {
639
+ try {
640
+ const stored = localStorage.getItem(STORAGE_KEY);
641
+ if (!stored) {
642
+ setIsOpen(true);
643
+ return;
644
+ }
645
+ const parsed = JSON.parse(stored) as StoredConsent;
646
+ // Re-emit so analytics/marketing scripts mounted after consent can read state
647
+ emitConsent(parsed);
648
+ } catch {
649
+ setIsOpen(true);
650
+ }
651
+ }, []);
652
+
653
+ useEffect(() => {
654
+ const handler = () => {
655
+ setShowCustomize(true);
656
+ setIsOpen(true);
657
+ };
658
+ window.addEventListener(OPEN_EVENT, handler);
659
+ return () => window.removeEventListener(OPEN_EVENT, handler);
660
+ }, []);
661
+
662
+ const persist = useCallback((mode: ConsentMode, cats: CustomConsent) => {
663
+ const consent: StoredConsent = {
664
+ mode,
665
+ categories: cats,
666
+ timestamp: Date.now(),
667
+ version: 1,
668
+ };
669
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(consent));
670
+ emitConsent(consent);
671
+ setIsOpen(false);
672
+ setShowCustomize(false);
673
+ }, []);
674
+
675
+ const acceptAll = useCallback(() => {
676
+ persist("accepted", {
677
+ necessary: true,
678
+ analytics: true,
679
+ marketing: true,
680
+ preferences: true,
681
+ });
682
+ }, [persist]);
683
+
684
+ const rejectNonEssential = useCallback(() => {
685
+ persist("rejected", DEFAULT_CATEGORIES);
686
+ }, [persist]);
687
+
688
+ const saveCustom = useCallback(() => {
689
+ persist("custom", categories);
690
+ }, [persist, categories]);
691
+
692
+ if (!isOpen) return null;
693
+
694
+ return (
695
+ <div
696
+ role="dialog"
697
+ aria-modal="false"
698
+ aria-labelledby="cookie-banner-title"
699
+ className="fixed inset-x-0 bottom-0 z-50 border-t border-[var(--neutral-7)] bg-[var(--neutral-1)] shadow-lg"
700
+ >
701
+ <div className="mx-auto flex max-w-[1400px] flex-col gap-4 p-4 md:p-6">
702
+ <div>
703
+ <h2 id="cookie-banner-title" className="text-sm font-semibold text-[var(--neutral-12)]">
704
+ Cookie Preferences \xB7 Cookie \u504F\u597D\u8BBE\u7F6E
705
+ </h2>
706
+ <p className="mt-1 text-xs text-[var(--neutral-11)]">
707
+ We use cookies to provide essential functionality, analyze usage, and personalize content.
708
+ You can accept all, reject non-essential, or customize your choice. See our{" "}
709
+ <a href="/privacy" className="underline">
710
+ Privacy Policy
711
+ </a>
712
+ .
713
+ <br />
714
+ \u6211\u4EEC\u4F7F\u7528 Cookie \u63D0\u4F9B\u5FC5\u8981\u529F\u80FD\u3001\u5206\u6790\u4F7F\u7528\u60C5\u51B5\u5E76\u4E2A\u6027\u5316\u5185\u5BB9\u3002\u60A8\u53EF\u4EE5\u63A5\u53D7\u5168\u90E8\u3001\u62D2\u7EDD\u975E\u5FC5\u8981\uFF0C\u6216\u81EA\u5B9A\u4E49\u9009\u62E9\u3002\u8BE6\u89C1
715
+ <a href="/privacy" className="underline">
716
+ \u9690\u79C1\u653F\u7B56
717
+ </a>
718
+ \u3002
719
+ </p>
720
+ </div>
721
+
722
+ {showCustomize ? (
723
+ <fieldset className="grid gap-3 md:grid-cols-2">
724
+ <CategoryRow
725
+ id="necessary"
726
+ title="Necessary / \u5FC5\u8981"
727
+ description="Required for the site to function. Cannot be disabled. / \u7AD9\u70B9\u8FD0\u884C\u6240\u5FC5\u9700\uFF0C\u65E0\u6CD5\u5173\u95ED\u3002"
728
+ checked={true}
729
+ disabled
730
+ onChange={() => undefined}
731
+ />
732
+ <CategoryRow
733
+ id="analytics"
734
+ title="Analytics / \u5206\u6790"
735
+ description="Helps us understand usage patterns. / \u5E2E\u52A9\u6211\u4EEC\u4E86\u89E3\u4F7F\u7528\u6A21\u5F0F\u3002"
736
+ checked={categories.analytics}
737
+ onChange={(v) => setCategories((c) => ({ ...c, analytics: v }))}
738
+ />
739
+ <CategoryRow
740
+ id="marketing"
741
+ title="Marketing / \u8425\u9500"
742
+ description="Used for targeted advertising. / \u7528\u4E8E\u5B9A\u5411\u5E7F\u544A\u3002"
743
+ checked={categories.marketing}
744
+ onChange={(v) => setCategories((c) => ({ ...c, marketing: v }))}
745
+ />
746
+ <CategoryRow
747
+ id="preferences"
748
+ title="Preferences / \u504F\u597D"
749
+ description="Remember your preferences (language, theme). / \u8BB0\u4F4F\u60A8\u7684\u504F\u597D\uFF08\u8BED\u8A00\u3001\u4E3B\u9898\uFF09\u3002"
750
+ checked={categories.preferences}
751
+ onChange={(v) => setCategories((c) => ({ ...c, preferences: v }))}
752
+ />
753
+ </fieldset>
754
+ ) : null}
755
+
756
+ <div className="flex flex-wrap gap-2 md:justify-end">
757
+ {showCustomize ? (
758
+ <button
759
+ type="button"
760
+ onClick={saveCustom}
761
+ className="rounded-md bg-[var(--blue-9)] px-4 py-2 text-sm font-medium text-white hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-[var(--blue-9)] focus:ring-offset-1"
762
+ >
763
+ Save Preferences / \u4FDD\u5B58\u504F\u597D
764
+ </button>
765
+ ) : (
766
+ <button
767
+ type="button"
768
+ onClick={() => setShowCustomize(true)}
769
+ className="rounded-md border border-[var(--neutral-7)] px-4 py-2 text-sm font-medium text-[var(--neutral-12)] hover:bg-[var(--neutral-3)] focus:outline-none focus:ring-2 focus:ring-[var(--blue-9)] focus:ring-offset-1"
770
+ >
771
+ Customize / \u81EA\u5B9A\u4E49
772
+ </button>
773
+ )}
774
+ <button
775
+ type="button"
776
+ onClick={rejectNonEssential}
777
+ className="rounded-md border border-[var(--neutral-7)] px-4 py-2 text-sm font-medium text-[var(--neutral-12)] hover:bg-[var(--neutral-3)] focus:outline-none focus:ring-2 focus:ring-[var(--blue-9)] focus:ring-offset-1"
778
+ >
779
+ Reject Non-Essential / \u62D2\u7EDD\u975E\u5FC5\u8981
780
+ </button>
781
+ <button
782
+ type="button"
783
+ onClick={acceptAll}
784
+ className="rounded-md bg-[var(--blue-9)] px-4 py-2 text-sm font-medium text-white hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-[var(--blue-9)] focus:ring-offset-1"
785
+ >
786
+ Accept All / \u5168\u90E8\u63A5\u53D7
787
+ </button>
788
+ </div>
789
+ </div>
790
+ </div>
791
+ );
792
+ }
793
+
794
+ interface CategoryRowProps {
795
+ id: string;
796
+ title: string;
797
+ description: string;
798
+ checked: boolean;
799
+ disabled?: boolean;
800
+ onChange: (value: boolean) => void;
801
+ }
802
+
803
+ function CategoryRow({ id, title, description, checked, disabled, onChange }: CategoryRowProps) {
804
+ return (
805
+ <label
806
+ htmlFor={id}
807
+ className="flex items-start gap-3 rounded-md border border-[var(--neutral-7)] p-3"
808
+ >
809
+ <input
810
+ id={id}
811
+ type="checkbox"
812
+ checked={checked}
813
+ disabled={disabled}
814
+ onChange={(e) => onChange(e.target.checked)}
815
+ className="mt-0.5 h-4 w-4"
816
+ />
817
+ <span className="flex flex-col">
818
+ <span className="text-sm font-medium text-[var(--neutral-12)]">{title}</span>
819
+ <span className="text-xs text-[var(--neutral-11)]">{description}</span>
820
+ </span>
821
+ </label>
822
+ );
823
+ }
824
+ `,lr=`interface AigcDisclaimerProps {
825
+ className?: string;
826
+ }
827
+
828
+ /**
829
+ * Inline disclaimer for AI-generated content (required by \u7F51\u4FE1\u529E\u300A\u751F\u6210\u5F0F\u4EBA\u5DE5\u667A\u80FD
830
+ * \u670D\u52A1\u7BA1\u7406\u6682\u884C\u529E\u6CD5\u300B2023/2024). Attach to any block/page that contains AI output.
831
+ */
832
+ export function AigcDisclaimer({ className }: AigcDisclaimerProps) {
833
+ return (
834
+ <div className={className}>
835
+ <p className="text-xs text-[var(--neutral-11)]">
836
+ \u672C\u5185\u5BB9\u7531 AI \u751F\u6210 \xB7 AI-Generated Content \xB7{" "}
837
+ <a href="/aigc-disclosure" className="underline">
838
+ \u4E86\u89E3\u66F4\u591A / Learn more
839
+ </a>
840
+ </p>
841
+ </div>
842
+ );
843
+ }
844
+ `,dr=`---
845
+ title: Privacy Policy / \u9690\u79C1\u653F\u7B56
846
+ description: How {{COMPANY_NAME}} collects, uses, shares, and protects personal information.
847
+ lastUpdated: 2026-01-01
848
+ ---
849
+
850
+ ${Qe}
851
+
852
+ # Privacy Policy / \u9690\u79C1\u653F\u7B56
853
+
854
+ > **Effective Date:** 2026-01-01 \xB7 **Last Updated:** 2026-01-01
855
+ > **\u751F\u6548\u65E5\u671F\uFF1A** 2026-01-01 \xB7 **\u6700\u540E\u66F4\u65B0\uFF1A** 2026-01-01
856
+
857
+ This Privacy Policy describes how **{{COMPANY_NAME}}** ("we", "us", "our") collects, uses, shares,
858
+ and protects your personal information in connection with our services.
859
+
860
+ \u672C\u9690\u79C1\u653F\u7B56\u8BF4\u660E **{{COMPANY_NAME}}**\uFF08"\u6211\u4EEC"\uFF09\u5728\u63D0\u4F9B\u670D\u52A1\u8FC7\u7A0B\u4E2D\u5982\u4F55\u6536\u96C6\u3001\u4F7F\u7528\u3001\u5171\u4EAB\u548C\u4FDD\u62A4\u60A8\u7684\u4E2A\u4EBA\u4FE1\u606F\u3002
861
+
862
+ ---
863
+
864
+ ## 1. Data Collection Scope / \u6570\u636E\u6536\u96C6\u8303\u56F4
865
+
866
+ **EN:** We collect the following categories of personal information:
867
+ - **Account data:** name, email address, phone number, password (hashed).
868
+ - **Usage data:** IP address, browser, device, operating system, page views, clickstream, timestamps.
869
+ - **Billing data:** payment method tokens (handled by our payment processor), billing address.
870
+ - **Content data:** files, messages, and other content you upload or create.
871
+ - **Cookies & similar technologies:** see our Cookie Preferences banner for details.
872
+
873
+ **ZH:** \u6211\u4EEC\u6536\u96C6\u4EE5\u4E0B\u7C7B\u522B\u7684\u4E2A\u4EBA\u4FE1\u606F\uFF1A
874
+ - **\u8D26\u6237\u4FE1\u606F\uFF1A** \u59D3\u540D\u3001\u90AE\u7BB1\u3001\u624B\u673A\u53F7\u3001\u5BC6\u7801\uFF08\u52A0\u5BC6\u5B58\u50A8\uFF09\u3002
875
+ - **\u4F7F\u7528\u4FE1\u606F\uFF1A** IP \u5730\u5740\u3001\u6D4F\u89C8\u5668\u3001\u8BBE\u5907\u3001\u64CD\u4F5C\u7CFB\u7EDF\u3001\u9875\u9762\u6D4F\u89C8\u3001\u70B9\u51FB\u6D41\u3001\u65F6\u95F4\u6233\u3002
876
+ - **\u652F\u4ED8\u4FE1\u606F\uFF1A** \u652F\u4ED8\u4EE4\u724C\uFF08\u7531\u7B2C\u4E09\u65B9\u652F\u4ED8\u670D\u52A1\u5546\u5904\u7406\uFF09\u3001\u8D26\u5355\u5730\u5740\u3002
877
+ - **\u5185\u5BB9\u4FE1\u606F\uFF1A** \u60A8\u4E0A\u4F20\u6216\u521B\u5EFA\u7684\u6587\u4EF6\u3001\u6D88\u606F\u7B49\u5185\u5BB9\u3002
878
+ - **Cookie \u53CA\u7C7B\u4F3C\u6280\u672F\uFF1A** \u8BE6\u89C1 Cookie \u504F\u597D\u8BBE\u7F6E\u3002
879
+
880
+ ---
881
+
882
+ ## 2. Purpose / \u4F7F\u7528\u76EE\u7684
883
+
884
+ **EN:** We use personal information to (a) provide and maintain the service; (b) authenticate users;
885
+ (c) process payments; (d) detect and prevent fraud/abuse; (e) improve product quality; (f) comply with
886
+ legal obligations; (g) communicate about the service (with consent where required).
887
+
888
+ **ZH:** \u6211\u4EEC\u5C06\u4E2A\u4EBA\u4FE1\u606F\u7528\u4E8E\uFF1A(a) \u63D0\u4F9B\u548C\u7EF4\u62A4\u670D\u52A1\uFF1B(b) \u7528\u6237\u8EAB\u4EFD\u9A8C\u8BC1\uFF1B(c) \u5904\u7406\u652F\u4ED8\uFF1B(d) \u68C0\u6D4B\u548C\u9632\u8303
889
+ \u6B3A\u8BC8/\u6EE5\u7528\uFF1B(e) \u6539\u8FDB\u4EA7\u54C1\u8D28\u91CF\uFF1B(f) \u5C65\u884C\u6CD5\u5F8B\u4E49\u52A1\uFF1B(g) \u5C31\u670D\u52A1\u4E0E\u60A8\u6C9F\u901A\uFF08\u9700\u8981\u65F6\u53D6\u5F97\u60A8\u7684\u540C\u610F\uFF09\u3002
890
+
891
+ ---
892
+
893
+ ## 3. Sharing & Cross-Border Transfer / \u6570\u636E\u5171\u4EAB\u53CA\u8DE8\u5883\u4F20\u8F93
894
+
895
+ **EN:** We share personal information only with:
896
+ - **Processors** (cloud hosting, analytics, email, payment) bound by data processing agreements.
897
+ - **Legal authorities** when required by law.
898
+ - **Business transferees** in the event of merger, acquisition, or asset sale.
899
+
900
+ Some processors operate **outside your country**. For users in China, see our separate
901
+ [Cross-Border Data Notice](/cross-border-data). For users in the EEA/UK, transfers rely on
902
+ Standard Contractual Clauses or equivalent safeguards.
903
+
904
+ **ZH:** \u6211\u4EEC\u4EC5\u4E0E\u4EE5\u4E0B\u4E3B\u4F53\u5171\u4EAB\u4E2A\u4EBA\u4FE1\u606F\uFF1A
905
+ - **\u5904\u7406\u8005**\uFF08\u4E91\u670D\u52A1\u3001\u5206\u6790\u3001\u90AE\u4EF6\u3001\u652F\u4ED8\uFF09\u2014\u2014 \u53D7\u6570\u636E\u5904\u7406\u534F\u8BAE\u7EA6\u675F\u3002
906
+ - **\u53F8\u6CD5/\u76D1\u7BA1\u673A\u5173** \u2014\u2014 \u4F9D\u6CD5\u5F8B\u8981\u6C42\u3002
907
+ - **\u4E1A\u52A1\u7EE7\u53D7\u8005** \u2014\u2014 \u53D1\u751F\u5408\u5E76\u3001\u6536\u8D2D\u6216\u8D44\u4EA7\u51FA\u552E\u65F6\u3002
908
+
909
+ \u90E8\u5206\u5904\u7406\u8005\u4F4D\u4E8E**\u5883\u5916**\u3002\u4E2D\u56FD\u5883\u5185\u7528\u6237\u8BF7\u53C2\u9605\u300A[\u6570\u636E\u51FA\u5883\u544A\u77E5](/cross-border-data)\u300B\u3002
910
+ \u6B27\u76DF/\u82F1\u56FD\u7528\u6237\u7684\u8DE8\u5883\u4F20\u8F93\u4F9D\u636E\u300A\u6807\u51C6\u5408\u540C\u6761\u6B3E\u300B(SCCs) \u6216\u7B49\u6548\u4FDD\u62A4\u63AA\u65BD\u3002
911
+
912
+ ---
913
+
914
+ ## 4. Your Rights / \u7528\u6237\u6743\u5229
915
+
916
+ **EN:** Depending on your jurisdiction (GDPR/CCPA/PIPL/\u7B49), you may have the right to:
917
+ - **Access** your personal information.
918
+ - **Rectify** inaccurate data.
919
+ - **Delete** your data ("right to be forgotten").
920
+ - **Export** your data in a portable format.
921
+ - **Restrict or object** to certain processing.
922
+ - **Withdraw consent** at any time without affecting the lawfulness of prior processing.
923
+ - **Lodge a complaint** with your local supervisory authority.
924
+
925
+ To exercise these rights, contact **{{COMPANY_CONTACT_EMAIL}}**.
926
+
927
+ **ZH:** \u6839\u636E\u60A8\u6240\u5728\u53F8\u6CD5\u8F96\u533A\uFF08\u300A\u4E2A\u4EBA\u4FE1\u606F\u4FDD\u62A4\u6CD5\u300B/GDPR/CCPA \u7B49\uFF09\uFF0C\u60A8\u53EF\u80FD\u4EAB\u6709\u4EE5\u4E0B\u6743\u5229\uFF1A
928
+ - **\u8BBF\u95EE**\u60A8\u7684\u4E2A\u4EBA\u4FE1\u606F\u3002
929
+ - **\u66F4\u6B63**\u4E0D\u51C6\u786E\u4FE1\u606F\u3002
930
+ - **\u5220\u9664**\u60A8\u7684\u6570\u636E\uFF08\u88AB\u9057\u5FD8\u6743\uFF09\u3002
931
+ - **\u5BFC\u51FA**\u60A8\u7684\u6570\u636E\uFF08\u53EF\u643A\u5E26\u683C\u5F0F\uFF09\u3002
932
+ - **\u9650\u5236\u6216\u53CD\u5BF9**\u7279\u5B9A\u5904\u7406\u3002
933
+ - **\u968F\u65F6\u64A4\u56DE\u540C\u610F**\uFF08\u4E0D\u5F71\u54CD\u64A4\u56DE\u524D\u7684\u5904\u7406\u5408\u6CD5\u6027\uFF09\u3002
934
+ - \u5411\u5F53\u5730\u76D1\u7BA1\u673A\u6784**\u6295\u8BC9**\u3002
935
+
936
+ \u5982\u9700\u884C\u4F7F\u4E0A\u8FF0\u6743\u5229\uFF0C\u8BF7\u8054\u7CFB **{{COMPANY_CONTACT_EMAIL}}**\u3002
937
+
938
+ ---
939
+
940
+ ## 5. Cookies / Cookie \u4F7F\u7528
941
+
942
+ See our Cookie Preferences banner (footer \u2192 Cookie Settings). We categorize cookies as:
943
+ Necessary (cannot be disabled), Analytics, Marketing, Preferences.
944
+
945
+ \u8BF7\u53C2\u9605 Cookie \u504F\u597D\u8BBE\u7F6E\uFF08\u9875\u811A \u2192 Cookie Settings\uFF09\u3002Cookie \u7C7B\u522B\u5305\u62EC\uFF1A\u5FC5\u8981\uFF08\u4E0D\u53EF\u5173\u95ED\uFF09\u3001
946
+ \u5206\u6790\u3001\u8425\u9500\u3001\u504F\u597D\u3002
947
+
948
+ ---
949
+
950
+ ## 6. Data Security / \u6570\u636E\u5B89\u5168\u63AA\u65BD
951
+
952
+ **EN:** Administrative, technical, and physical safeguards include TLS 1.2+ in transit, AES-256 at rest
953
+ (envelope encryption for secrets via \`@nebutra/vault\`), role-based access control, audit logging,
954
+ periodic vulnerability scans, and least-privilege principles. No system is 100% secure; we notify
955
+ affected users and regulators of material breaches within statutory timelines.
956
+
957
+ **ZH:** \u6211\u4EEC\u91C7\u53D6\u7BA1\u7406\u3001\u6280\u672F\u548C\u7269\u7406\u5C42\u9762\u7684\u4FDD\u969C\u63AA\u65BD\uFF0C\u5305\u62EC\u4F20\u8F93\u4E2D TLS 1.2+\u3001\u9759\u6001 AES-256 \u52A0\u5BC6
958
+ \uFF08\u5BC6\u94A5\u901A\u8FC7 \`@nebutra/vault\` \u4FE1\u5C01\u52A0\u5BC6\u7BA1\u7406\uFF09\u3001\u57FA\u4E8E\u89D2\u8272\u7684\u8BBF\u95EE\u63A7\u5236\u3001\u5BA1\u8BA1\u65E5\u5FD7\u3001\u5B9A\u671F\u6F0F\u6D1E\u626B\u63CF
959
+ \u548C\u6700\u5C0F\u6743\u9650\u539F\u5219\u3002\u4EFB\u4F55\u7CFB\u7EDF\u90FD\u65E0\u6CD5\u505A\u5230\u7EDD\u5BF9\u5B89\u5168\uFF1B\u53D1\u751F\u91CD\u5927\u6570\u636E\u6CC4\u9732\u65F6\uFF0C\u6211\u4EEC\u5C06\u5728\u6CD5\u5B9A\u671F\u9650\u5185
960
+ \u901A\u77E5\u53D7\u5F71\u54CD\u7528\u6237\u548C\u76D1\u7BA1\u673A\u6784\u3002
961
+
962
+ ---
963
+
964
+ ## 7. Retention / \u6570\u636E\u4FDD\u7559
965
+
966
+ We retain personal information only as long as necessary to fulfill the purposes described, or as
967
+ required by law. Inactive accounts are deleted after 24 months unless retention is legally required.
968
+
969
+ \u6211\u4EEC\u4EC5\u5728\u5B9E\u73B0\u4E0A\u8FF0\u76EE\u7684\u6240\u9700\u7684\u671F\u95F4\u6216\u6CD5\u5F8B\u8981\u6C42\u7684\u671F\u95F4\u5185\u4FDD\u7559\u4E2A\u4EBA\u4FE1\u606F\u3002\u975E\u6D3B\u8DC3\u8D26\u6237\u5C06\u5728 24 \u4E2A\u6708\u540E
970
+ \u5220\u9664\uFF0C\u6CD5\u5F8B\u53E6\u6709\u8981\u6C42\u7684\u9664\u5916\u3002
971
+
972
+ ---
973
+
974
+ ## 8. Changes / \u53D8\u66F4\u901A\u77E5
975
+
976
+ We will notify you of material changes by posting the updated policy and, where required, by email
977
+ or in-app notification before the change takes effect.
978
+
979
+ \u6211\u4EEC\u5C06\u901A\u8FC7\u53D1\u5E03\u66F4\u65B0\u540E\u7684\u653F\u7B56\u901A\u77E5\u60A8\u91CD\u5927\u53D8\u66F4\uFF1B\u5982\u6CD5\u5F8B\u8981\u6C42\uFF0C\u8FD8\u5C06\u5728\u53D8\u66F4\u751F\u6548\u524D\u901A\u8FC7\u90AE\u4EF6\u6216\u5E94\u7528\u5185
980
+ \u901A\u77E5\u7684\u65B9\u5F0F\u544A\u77E5\u3002
981
+
982
+ ---
983
+
984
+ ## 9. Contact / \u8054\u7CFB\u65B9\u5F0F
985
+
986
+ **Data Controller / \u6570\u636E\u63A7\u5236\u8005:** {{COMPANY_NAME}}
987
+ **Address / \u5730\u5740:** {{COMPANY_ADDRESS}}
988
+ **Email / \u90AE\u7BB1:** {{COMPANY_CONTACT_EMAIL}}
989
+ `,ur=`---
990
+ title: Terms of Service / \u670D\u52A1\u6761\u6B3E
991
+ description: Terms governing your use of {{COMPANY_NAME}}.
992
+ lastUpdated: 2026-01-01
993
+ ---
994
+
995
+ ${Qe}
996
+
997
+ # Terms of Service / \u670D\u52A1\u6761\u6B3E
998
+
999
+ > **Effective Date:** 2026-01-01
1000
+ > **\u751F\u6548\u65E5\u671F\uFF1A** 2026-01-01
1001
+
1002
+ ## 1. Acceptance / \u63A5\u53D7\u6761\u6B3E
1003
+
1004
+ **EN:** By accessing or using the services provided by {{COMPANY_NAME}} ("Service"), you agree to be
1005
+ bound by these Terms. If you do not agree, do not use the Service.
1006
+
1007
+ **ZH:** \u60A8\u8BBF\u95EE\u6216\u4F7F\u7528 {{COMPANY_NAME}} \u63D0\u4F9B\u7684\u670D\u52A1\uFF08"\u670D\u52A1"\uFF09\u5373\u8868\u793A\u540C\u610F\u53D7\u672C\u6761\u6B3E\u7EA6\u675F\u3002
1008
+ \u5982\u4E0D\u540C\u610F\uFF0C\u8BF7\u52FF\u4F7F\u7528\u672C\u670D\u52A1\u3002
1009
+
1010
+ ---
1011
+
1012
+ ## 2. Eligibility / \u7528\u6237\u8D44\u683C
1013
+
1014
+ You must be at least 13 years old (or the minimum age in your jurisdiction, whichever is higher) and
1015
+ have the legal capacity to enter into a contract.
1016
+
1017
+ \u7528\u6237\u987B\u5E74\u6EE1 13 \u5468\u5C81\uFF08\u6216\u5176\u6240\u5728\u53F8\u6CD5\u8F96\u533A\u89C4\u5B9A\u7684\u6700\u4F4E\u5E74\u9F84\uFF0C\u53D6\u8F83\u9AD8\u8005\uFF09\u5E76\u5177\u5907\u7B7E\u8BA2\u5408\u540C\u7684\u6CD5\u5F8B\u884C\u4E3A\u80FD\u529B\u3002
1018
+
1019
+ ---
1020
+
1021
+ ## 3. Accounts / \u8D26\u6237
1022
+
1023
+ You are responsible for maintaining the confidentiality of your credentials and for all activity
1024
+ under your account. Notify us immediately of any unauthorized use at {{COMPANY_CONTACT_EMAIL}}.
1025
+
1026
+ \u60A8\u8D1F\u8D23\u4FDD\u7BA1\u8D26\u6237\u51ED\u636E\u7684\u673A\u5BC6\u6027\uFF0C\u5E76\u5BF9\u8D26\u6237\u4E0B\u7684\u4E00\u5207\u6D3B\u52A8\u8D1F\u8D23\u3002\u53D1\u73B0\u672A\u7ECF\u6388\u6743\u7684\u4F7F\u7528\uFF0C\u8BF7\u7ACB\u5373\u901A\u8FC7
1027
+ {{COMPANY_CONTACT_EMAIL}} \u901A\u77E5\u6211\u4EEC\u3002
1028
+
1029
+ ---
1030
+
1031
+ ## 4. Acceptable Use / \u5408\u89C4\u4F7F\u7528
1032
+
1033
+ You agree not to: (a) violate any law; (b) infringe intellectual property rights; (c) transmit
1034
+ malware; (d) reverse engineer the Service except as permitted by law; (e) use the Service for
1035
+ activities prohibited in your jurisdiction (including content review regulations in the PRC).
1036
+
1037
+ \u60A8\u540C\u610F\u4E0D\u5F97\uFF1A(a) \u8FDD\u53CD\u4EFB\u4F55\u6CD5\u5F8B\uFF1B(b) \u4FB5\u72AF\u77E5\u8BC6\u4EA7\u6743\uFF1B(c) \u4F20\u64AD\u6076\u610F\u8F6F\u4EF6\uFF1B(d) \u8D85\u51FA\u6CD5\u5F8B\u5141\u8BB8\u8303\u56F4\u5BF9
1038
+ \u670D\u52A1\u8FDB\u884C\u9006\u5411\u5DE5\u7A0B\uFF1B(e) \u5C06\u670D\u52A1\u7528\u4E8E\u60A8\u6240\u5728\u53F8\u6CD5\u8F96\u533A\uFF08\u5305\u62EC\u4E2D\u56FD\u5883\u5185\u5185\u5BB9\u5BA1\u6838\u6CD5\u89C4\uFF09\u7981\u6B62\u7684\u6D3B\u52A8\u3002
1039
+
1040
+ ---
1041
+
1042
+ ## 5. Subscription & Billing / \u8BA2\u9605\u4E0E\u8BA1\u8D39
1043
+
1044
+ Paid plans are billed in advance on a recurring basis. You authorize us to charge your payment
1045
+ method until you cancel. Refunds are handled in accordance with applicable law and our refund policy.
1046
+
1047
+ \u4ED8\u8D39\u8BA2\u9605\u6309\u5468\u671F\u9884\u5148\u8BA1\u8D39\u3002\u60A8\u6388\u6743\u6211\u4EEC\u5411\u60A8\u7684\u652F\u4ED8\u65B9\u5F0F\u6301\u7EED\u6263\u6B3E\uFF0C\u76F4\u81F3\u60A8\u53D6\u6D88\u8BA2\u9605\u3002\u9000\u6B3E\u4F9D\u9002\u7528\u6CD5\u5F8B
1048
+ \u53CA\u672C\u670D\u52A1\u9000\u6B3E\u653F\u7B56\u5904\u7406\u3002
1049
+
1050
+ ---
1051
+
1052
+ ## 6. Intellectual Property / \u77E5\u8BC6\u4EA7\u6743
1053
+
1054
+ **EN:** The Service, including all software, design, and content provided by us, is owned by
1055
+ {{COMPANY_NAME}} and protected by copyright, trademark, and other laws. You retain ownership of
1056
+ content you upload; you grant us a worldwide, non-exclusive, royalty-free license to host, process,
1057
+ and display your content solely to provide the Service.
1058
+
1059
+ **ZH:** \u670D\u52A1\uFF08\u5305\u62EC\u6211\u4EEC\u63D0\u4F9B\u7684\u6240\u6709\u8F6F\u4EF6\u3001\u8BBE\u8BA1\u548C\u5185\u5BB9\uFF09\u5F52 {{COMPANY_NAME}} \u6240\u6709\uFF0C\u53D7\u8457\u4F5C\u6743\u3001
1060
+ \u5546\u6807\u53CA\u5176\u4ED6\u6CD5\u5F8B\u4FDD\u62A4\u3002\u60A8\u4FDD\u7559\u5BF9\u60A8\u4E0A\u4F20\u5185\u5BB9\u7684\u6240\u6709\u6743\uFF1B\u60A8\u6388\u4E88\u6211\u4EEC\u5168\u7403\u8303\u56F4\u5185\u3001\u975E\u72EC\u5360\u3001\u514D\u7248\u7A0E\u7684
1061
+ \u8BB8\u53EF\uFF0C\u4EC5\u7528\u4E8E\u6258\u7BA1\u3001\u5904\u7406\u548C\u5C55\u793A\u60A8\u7684\u5185\u5BB9\u4EE5\u63D0\u4F9B\u670D\u52A1\u3002
1062
+
1063
+ ---
1064
+
1065
+ ## 7. AI-Generated Content / \u751F\u6210\u5F0F AI \u5185\u5BB9
1066
+
1067
+ Outputs generated by AI models are provided "as-is". You are responsible for verifying accuracy
1068
+ and ensuring compliance with applicable laws (including Chinese AIGC regulations) before relying
1069
+ on or publishing such outputs. See the [AIGC Disclosure](/aigc-disclosure) for details.
1070
+
1071
+ AI \u6A21\u578B\u751F\u6210\u7684\u8F93\u51FA\u6309"\u73B0\u72B6"\u63D0\u4F9B\u3002\u60A8\u5E94\u8D1F\u8D23\u6838\u5B9E\u5176\u51C6\u786E\u6027\uFF0C\u5E76\u5728\u4F9D\u8D56\u6216\u53D1\u5E03\u524D\u786E\u4FDD\u7B26\u5408\u9002\u7528\u6CD5\u5F8B
1072
+ \uFF08\u5305\u62EC\u4E2D\u56FD AIGC \u65B0\u89C4\uFF09\u3002\u8BE6\u89C1\u300A[AIGC \u8BF4\u660E](/aigc-disclosure)\u300B\u3002
1073
+
1074
+ ---
1075
+
1076
+ ## 8. Disclaimer & Limitation of Liability / \u514D\u8D23\u58F0\u660E\u53CA\u8D23\u4EFB\u9650\u5236
1077
+
1078
+ **EN:** TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SERVICE IS PROVIDED "AS IS" WITHOUT WARRANTIES.
1079
+ IN NO EVENT SHALL {{COMPANY_NAME}} BE LIABLE FOR INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES.
1080
+
1081
+ **ZH:** \u5728\u6CD5\u5F8B\u5141\u8BB8\u7684\u6700\u5927\u8303\u56F4\u5185\uFF0C\u670D\u52A1\u6309"\u73B0\u72B6"\u63D0\u4F9B\uFF0C\u4E0D\u4F5C\u4EFB\u4F55\u660E\u793A\u6216\u9ED8\u793A\u4FDD\u8BC1\u3002{{COMPANY_NAME}}
1082
+ \u6982\u4E0D\u5BF9\u95F4\u63A5\u3001\u5076\u7136\u6216\u540E\u679C\u6027\u635F\u5931\u627F\u62C5\u8D23\u4EFB\u3002
1083
+
1084
+ ---
1085
+
1086
+ ## 9. Termination / \u7EC8\u6B62
1087
+
1088
+ We may suspend or terminate your account for material breach. You may cancel at any time from
1089
+ your account settings.
1090
+
1091
+ \u56E0\u91CD\u5927\u8FDD\u7EA6\uFF0C\u6211\u4EEC\u53EF\u6682\u505C\u6216\u7EC8\u6B62\u60A8\u7684\u8D26\u6237\u3002\u60A8\u53EF\u968F\u65F6\u5728\u8D26\u6237\u8BBE\u7F6E\u4E2D\u53D6\u6D88\u8BA2\u9605\u3002
1092
+
1093
+ ---
1094
+
1095
+ ## 10. Governing Law / \u9002\u7528\u6CD5\u5F8B
1096
+
1097
+ These Terms are governed by the laws of **{{GOVERNING_JURISDICTION}}**, without regard to
1098
+ conflict-of-laws principles. Disputes shall be resolved in the competent courts of that jurisdiction.
1099
+
1100
+ \u672C\u6761\u6B3E\u53D7 **{{GOVERNING_JURISDICTION}}** \u6CD5\u5F8B\u7BA1\u8F96\uFF0C\u4E0D\u8003\u8651\u51B2\u7A81\u6CD5\u539F\u5219\u3002\u4E89\u8BAE\u5E94\u63D0\u4EA4\u81F3\u8BE5
1101
+ \u53F8\u6CD5\u8F96\u533A\u7684\u6709\u7BA1\u8F96\u6743\u6CD5\u9662\u89E3\u51B3\u3002
1102
+
1103
+ ---
1104
+
1105
+ ## 11. Contact / \u8054\u7CFB\u65B9\u5F0F
1106
+
1107
+ {{COMPANY_NAME}} \xB7 {{COMPANY_ADDRESS}} \xB7 {{COMPANY_CONTACT_EMAIL}}
1108
+ `,mr=`---
1109
+ title: AIGC Disclosure / \u751F\u6210\u5F0F AI \u670D\u52A1\u8BF4\u660E
1110
+ description: Disclosure required by \u7F51\u4FE1\u529E\u300A\u751F\u6210\u5F0F\u4EBA\u5DE5\u667A\u80FD\u670D\u52A1\u7BA1\u7406\u6682\u884C\u529E\u6CD5\u300B
1111
+ lastUpdated: 2026-01-01
1112
+ ---
1113
+
1114
+ ${Qe}
1115
+
1116
+ # AIGC Disclosure / \u751F\u6210\u5F0F AI \u670D\u52A1\u8BF4\u660E
1117
+
1118
+ This disclosure is provided in accordance with the **Interim Measures for the Management of
1119
+ Generative AI Services** (\u751F\u6210\u5F0F\u4EBA\u5DE5\u667A\u80FD\u670D\u52A1\u7BA1\u7406\u6682\u884C\u529E\u6CD5) promulgated by the Cyberspace
1120
+ Administration of China.
1121
+
1122
+ \u672C\u8BF4\u660E\u4F9D\u636E\u56FD\u5BB6\u4E92\u8054\u7F51\u4FE1\u606F\u529E\u516C\u5BA4\u300A\u751F\u6210\u5F0F\u4EBA\u5DE5\u667A\u80FD\u670D\u52A1\u7BA1\u7406\u6682\u884C\u529E\u6CD5\u300B\u7684\u8981\u6C42\u63D0\u4F9B\u3002
1123
+
1124
+ ---
1125
+
1126
+ ## 1. AI Models Used / \u4F7F\u7528\u7684 AI \u6A21\u578B
1127
+
1128
+ > TODO (fill in before launch / \u4E0A\u7EBF\u524D\u586B\u5199):
1129
+
1130
+ | Model / \u6A21\u578B | Provider / \u63D0\u4F9B\u65B9 | Use Case / \u7528\u9014 |
1131
+ | --- | --- | --- |
1132
+ | _e.g. GPT-4o_ | _OpenAI_ | _Chat, content drafting_ |
1133
+ | _e.g. Claude 3.5 Sonnet_ | _Anthropic_ | _Reasoning, code generation_ |
1134
+ | _e.g. \u901A\u4E49\u5343\u95EE_ | _\u963F\u91CC\u4E91_ | _\u4E2D\u6587\u5185\u5BB9\u751F\u6210_ |
1135
+
1136
+ ---
1137
+
1138
+ ## 2. Algorithm Filing / \u7B97\u6CD5\u5907\u6848\u53F7
1139
+
1140
+ **Filing Number / \u5907\u6848\u53F7:** \`{{AIGC_ALGORITHM_FILING_NUMBER}}\`
1141
+ (Apply at <https://beian.cac.gov.cn/> \xB7 \u4E8E\u4E2D\u56FD\u7F51\u4FE1\u529E\u5B8C\u6210\u7B97\u6CD5\u5907\u6848)
1142
+
1143
+ ---
1144
+
1145
+ ## 3. AI-Generated Content Labeling / \u751F\u6210\u5185\u5BB9\u6807\u8BC6\u673A\u5236
1146
+
1147
+ **EN:** All content generated by AI models in this product is labeled with:
1148
+ - An inline disclaimer: "\u672C\u5185\u5BB9\u7531 AI \u751F\u6210 \xB7 AI-Generated Content".
1149
+ - Machine-readable metadata (where the output format supports it): \`X-AI-Generated: true\`.
1150
+ - For images/videos: visible watermark and/or embedded metadata per GB/T 45438-2025.
1151
+
1152
+ **ZH:** \u672C\u4EA7\u54C1\u4E2D\u7531 AI \u6A21\u578B\u751F\u6210\u7684\u6240\u6709\u5185\u5BB9\u5747\u5E26\u6709\uFF1A
1153
+ - \u663E\u5F0F\u6587\u5B57\u6807\u8BC6\uFF1A"\u672C\u5185\u5BB9\u7531 AI \u751F\u6210 \xB7 AI-Generated Content"\u3002
1154
+ - \u673A\u5668\u53EF\u8BFB\u5143\u6570\u636E\uFF08\u89C6\u8F93\u51FA\u683C\u5F0F\u652F\u6301\uFF09\uFF1A\`X-AI-Generated: true\`\u3002
1155
+ - \u56FE\u7247/\u89C6\u9891\uFF1A\u53EF\u89C1\u6C34\u5370\u548C/\u6216\u4F9D GB/T 45438-2025 \u5D4C\u5165\u5143\u6570\u636E\u3002
1156
+
1157
+ ---
1158
+
1159
+ ## 4. Training Data / \u8BAD\u7EC3\u6570\u636E\u8BF4\u660E
1160
+
1161
+ We do **not** train foundation models in-house. We use third-party model APIs whose training data
1162
+ practices are described in their respective privacy notices. We do not use customer data to train
1163
+ third-party models by default (opt-out where offered).
1164
+
1165
+ \u6211\u4EEC**\u4E0D**\u81EA\u884C\u8BAD\u7EC3\u57FA\u7840\u6A21\u578B\u3002\u6211\u4EEC\u4F7F\u7528\u7B2C\u4E09\u65B9\u6A21\u578B API\uFF0C\u5176\u8BAD\u7EC3\u6570\u636E\u5B9E\u8DF5\u8BE6\u89C1\u5404\u81EA\u7684\u9690\u79C1\u8BF4\u660E\u3002
1166
+ \u9ED8\u8BA4\u60C5\u51B5\u4E0B\uFF0C\u6211\u4EEC\u4E0D\u4F1A\u4F7F\u7528\u5BA2\u6237\u6570\u636E\u8BAD\u7EC3\u7B2C\u4E09\u65B9\u6A21\u578B\uFF08\u5728\u53EF\u9009\u9000\u51FA\u7684\u60C5\u51B5\u4E0B\u5DF2\u9009\u62E9\u9000\u51FA\uFF09\u3002
1167
+
1168
+ ---
1169
+
1170
+ ## 5. Reporting Channel / \u4E3E\u62A5\u6E20\u9053
1171
+
1172
+ If you encounter illegal content, hallucinated output causing harm, or suspected misuse, report to:
1173
+
1174
+ \u5982\u60A8\u53D1\u73B0\u8FDD\u6CD5\u5185\u5BB9\u3001\u5BFC\u81F4\u635F\u5BB3\u7684\u5E7B\u89C9\u8F93\u51FA\u6216\u7591\u4F3C\u6EE5\u7528\uFF0C\u8BF7\u901A\u8FC7\u4EE5\u4E0B\u6E20\u9053\u4E3E\u62A5\uFF1A
1175
+
1176
+ - **Email / \u90AE\u7BB1:** {{COMPANY_CONTACT_EMAIL}}
1177
+ - **In-app:** Settings \u2192 Feedback \u2192 Report AI Content / \u5E94\u7528\u5185\uFF1A\u8BBE\u7F6E \u2192 \u53CD\u9988 \u2192 \u4E3E\u62A5 AI \u5185\u5BB9
1178
+ - **Regulator / \u76D1\u7BA1\u90E8\u95E8:** [\u56FD\u5BB6\u4E92\u8054\u7F51\u4FE1\u606F\u529E\u516C\u5BA4\u4E3E\u62A5\u4E2D\u5FC3](https://www.12377.cn/)
1179
+
1180
+ ---
1181
+
1182
+ ## 6. Limitations / \u5C40\u9650\u6027
1183
+
1184
+ AI outputs may be inaccurate, biased, or out of date. Do **not** rely on AI outputs for medical,
1185
+ legal, financial, or safety-critical decisions without independent verification by a qualified
1186
+ human professional.
1187
+
1188
+ AI \u8F93\u51FA\u53EF\u80FD\u4E0D\u51C6\u786E\u3001\u5B58\u5728\u504F\u89C1\u6216\u8FC7\u65F6\u3002**\u8BF7\u52FF**\u5728\u672A\u7ECF\u5408\u683C\u4E13\u4E1A\u4EBA\u58EB\u72EC\u7ACB\u6838\u5B9E\u7684\u60C5\u51B5\u4E0B\uFF0C\u5C06 AI \u8F93\u51FA
1189
+ \u7528\u4E8E\u533B\u7597\u3001\u6CD5\u5F8B\u3001\u91D1\u878D\u6216\u5B89\u5168\u6538\u5173\u7684\u51B3\u7B56\u3002
1190
+ `,gr=`---
1191
+ title: Cross-Border Data Notice / \u6570\u636E\u51FA\u5883\u544A\u77E5
1192
+ description: Notice of cross-border transfer of personal information, per PIPL.
1193
+ lastUpdated: 2026-01-01
1194
+ ---
1195
+
1196
+ ${Qe}
1197
+
1198
+ # Cross-Border Data Notice / \u6570\u636E\u51FA\u5883\u544A\u77E5
1199
+
1200
+ Pursuant to Articles 38\u201341 of the **Personal Information Protection Law of the PRC** (\u300A\u4E2A\u4EBA\u4FE1\u606F
1201
+ \u4FDD\u62A4\u6CD5\u300B, "PIPL"), we notify users that the following personal information may be transferred
1202
+ outside mainland China to provide the Service.
1203
+
1204
+ \u4F9D\u636E\u300A\u4E2D\u534E\u4EBA\u6C11\u5171\u548C\u56FD\u4E2A\u4EBA\u4FE1\u606F\u4FDD\u62A4\u6CD5\u300B\u7B2C\u4E09\u5341\u516B\u6761\u81F3\u7B2C\u56DB\u5341\u4E00\u6761\uFF0C\u6211\u4EEC\u544A\u77E5\u7528\u6237\uFF0C\u4EE5\u4E0B\u4E2A\u4EBA\u4FE1\u606F
1205
+ \u53EF\u80FD\u4E3A\u63D0\u4F9B\u670D\u52A1\u800C\u4F20\u8F93\u81F3\u4E2D\u56FD\u5927\u9646\u5883\u5916\u3002
1206
+
1207
+ ---
1208
+
1209
+ ## 1. Overseas Recipients / \u5883\u5916\u63A5\u6536\u65B9
1210
+
1211
+ > TODO (fill in before launch / \u4E0A\u7EBF\u524D\u586B\u5199):
1212
+
1213
+ | Recipient / \u63A5\u6536\u65B9 | Country / \u56FD\u5BB6 | Purpose / \u76EE\u7684 | Data Categories / \u6570\u636E\u7C7B\u522B |
1214
+ | --- | --- | --- | --- |
1215
+ | _AWS (example)_ | _US_ | _Hosting_ | _Account, usage logs, content_ |
1216
+ | _OpenAI_ | _US_ | _LLM inference_ | _Prompts, generated content_ |
1217
+ | _Resend_ | _US_ | _Transactional email_ | _Email address, message body_ |
1218
+ | _Cloudflare_ | _US / Global_ | _CDN, DDoS protection_ | _IP address, request metadata_ |
1219
+
1220
+ ---
1221
+
1222
+ ## 2. Legal Basis / \u5408\u6CD5\u6027\u57FA\u7840
1223
+
1224
+ Cross-border transfers rely on one of:
1225
+ - **User's separate consent** under PIPL Article 39.
1226
+ - **Standard Contract** with the overseas recipient per CAC Measures (CAC \u6807\u51C6\u5408\u540C).
1227
+ - **Security Assessment** by the CAC (for transfers meeting statutory thresholds).
1228
+
1229
+ \u8DE8\u5883\u4F20\u8F93\u4F9D\u636E\u4EE5\u4E0B\u4E4B\u4E00\uFF1A
1230
+ - \u300A\u4E2A\u4EBA\u4FE1\u606F\u4FDD\u62A4\u6CD5\u300B\u7B2C\u4E09\u5341\u4E5D\u6761\u4E0B\u7684**\u5355\u72EC\u540C\u610F**\u3002
1231
+ - \u4E0E\u5883\u5916\u63A5\u6536\u65B9\u7B7E\u8BA2\u7684 **CAC \u6807\u51C6\u5408\u540C**\u3002
1232
+ - \u8FBE\u5230\u6CD5\u5B9A\u95E8\u69DB\u65F6\u7684**\u7F51\u4FE1\u529E\u5B89\u5168\u8BC4\u4F30**\u3002
1233
+
1234
+ ---
1235
+
1236
+ ## 3. Your Rights / \u60A8\u7684\u6743\u5229
1237
+
1238
+ You may **refuse** cross-border transfer at any time by contacting {{COMPANY_CONTACT_EMAIL}}.
1239
+ Refusal may limit your ability to use features that rely on overseas services.
1240
+
1241
+ \u60A8\u53EF\u968F\u65F6\u8054\u7CFB {{COMPANY_CONTACT_EMAIL}} \u62D2\u7EDD\u8DE8\u5883\u4F20\u8F93\u3002\u62D2\u7EDD\u53EF\u80FD\u4F1A\u9650\u5236\u60A8\u4F7F\u7528\u4F9D\u8D56\u5883\u5916\u670D\u52A1\u7684
1242
+ \u529F\u80FD\u3002
1243
+
1244
+ ---
1245
+
1246
+ ## 4. Safeguards / \u4FDD\u969C\u63AA\u65BD
1247
+
1248
+ - Encryption in transit (TLS 1.2+) and at rest (AES-256).
1249
+ - Contractual clauses obligating overseas recipients to provide PIPL-equivalent protection.
1250
+ - Access controls, audit logging, and breach notification procedures.
1251
+
1252
+ - \u4F20\u8F93\u4E2D\u52A0\u5BC6 (TLS 1.2+) \u53CA\u9759\u6001\u52A0\u5BC6 (AES-256)\u3002
1253
+ - \u5408\u540C\u6761\u6B3E\u8981\u6C42\u5883\u5916\u63A5\u6536\u65B9\u63D0\u4F9B\u7B49\u540C\u4E8E PIPL \u7684\u4FDD\u62A4\u3002
1254
+ - \u8BBF\u95EE\u63A7\u5236\u3001\u5BA1\u8BA1\u65E5\u5FD7\u548C\u8FDD\u89C4\u901A\u77E5\u7A0B\u5E8F\u3002
1255
+ `,fr=`---
1256
+ title: Personal Information Collection List / \u4E2A\u4EBA\u4FE1\u606F\u6536\u96C6\u6E05\u5355
1257
+ description: Itemized list of personal information collected, per PIPL Article 17.
1258
+ lastUpdated: 2026-01-01
1259
+ ---
1260
+
1261
+ ${Qe}
1262
+
1263
+ # Personal Information Collection List / \u4E2A\u4EBA\u4FE1\u606F\u6536\u96C6\u6E05\u5355
1264
+
1265
+ Pursuant to Article 17 of the **PIPL**, we publish an itemized list of personal information
1266
+ collected, the purpose of processing, and retention period.
1267
+
1268
+ \u4F9D\u636E\u300A\u4E2A\u4EBA\u4FE1\u606F\u4FDD\u62A4\u6CD5\u300B\u7B2C\u5341\u4E03\u6761\uFF0C\u6211\u4EEC\u516C\u5F00\u4E2A\u4EBA\u4FE1\u606F\u6536\u96C6\u6E05\u5355\uFF0C\u5305\u62EC\u6536\u96C6\u9879\u3001\u5904\u7406\u76EE\u7684\u548C\u4FDD\u7559\u671F\u9650\u3002
1269
+
1270
+ ---
1271
+
1272
+ | Category / \u7C7B\u522B | Item / \u9879 | Purpose / \u76EE\u7684 | Source / \u6765\u6E90 | Retention / \u4FDD\u7559\u671F | Required? / \u5FC5\u8981? |
1273
+ | --- | --- | --- | --- | --- | --- |
1274
+ | Account / \u8D26\u6237 | Email / \u90AE\u7BB1 | Registration, auth, notifications / \u6CE8\u518C\u3001\u767B\u5F55\u3001\u901A\u77E5 | User-provided | Until account deletion + 30d / \u8D26\u6237\u5220\u9664\u540E 30 \u5929 | Yes / \u662F |
1275
+ | Account / \u8D26\u6237 | Password (hashed) / \u5BC6\u7801\uFF08\u52A0\u5BC6\uFF09 | Authentication / \u8EAB\u4EFD\u9A8C\u8BC1 | User-provided | Until account deletion / \u8D26\u6237\u5220\u9664 | Yes / \u662F |
1276
+ | Account / \u8D26\u6237 | Phone / \u624B\u673A\u53F7 | 2FA, account recovery / \u53CC\u56E0\u7D20\u3001\u8D26\u6237\u6062\u590D | User-provided | Until account deletion / \u8D26\u6237\u5220\u9664 | Optional / \u53EF\u9009 |
1277
+ | Profile / \u8D44\u6599 | Display name, avatar / \u6635\u79F0\u3001\u5934\u50CF | Service personalization / \u670D\u52A1\u4E2A\u6027\u5316 | User-provided | Until updated / \u76F4\u81F3\u66F4\u65B0 | Optional / \u53EF\u9009 |
1278
+ | Usage / \u4F7F\u7528 | IP address / IP \u5730\u5740 | Security, abuse prevention / \u5B89\u5168\u3001\u9632\u6EE5\u7528 | Automatic / \u81EA\u52A8 | 90 days / 90 \u5929 | Yes / \u662F |
1279
+ | Usage / \u4F7F\u7528 | User agent / \u6D4F\u89C8\u5668\u6807\u8BC6 | Compatibility, security / \u517C\u5BB9\u6027\u3001\u5B89\u5168 | Automatic / \u81EA\u52A8 | 90 days / 90 \u5929 | Yes / \u662F |
1280
+ | Usage / \u4F7F\u7528 | Page views, clickstream / \u9875\u9762\u6D4F\u89C8\u3001\u70B9\u51FB\u6D41 | Analytics, product improvement / \u5206\u6790\u3001\u4EA7\u54C1\u6539\u8FDB | Automatic / \u81EA\u52A8 | 13 months / 13 \u4E2A\u6708 | Consent / \u9700\u540C\u610F |
1281
+ | Usage / \u4F7F\u7528 | Cookies / Cookie | See Cookie Preferences / \u8BE6\u89C1 Cookie \u504F\u597D | Automatic / \u81EA\u52A8 | Varies / \u4E0D\u4E00 | Consent / \u9700\u540C\u610F |
1282
+ | Billing / \u652F\u4ED8 | Billing address / \u8D26\u5355\u5730\u5740 | Payment processing / \u652F\u4ED8\u5904\u7406 | User-provided | 7 years (tax) / 7 \u5E74\uFF08\u7A0E\u52A1\uFF09 | If paid plan / \u4ED8\u8D39\u65F6 |
1283
+ | Billing / \u652F\u4ED8 | Payment method token / \u652F\u4ED8\u4EE4\u724C | Recurring billing / \u8BA2\u9605\u7EED\u8D39 | Stripe / payment processor | Until deletion / \u76F4\u81F3\u5220\u9664 | If paid plan / \u4ED8\u8D39\u65F6 |
1284
+ | Content / \u5185\u5BB9 | Uploaded files / \u4E0A\u4F20\u6587\u4EF6 | Service delivery / \u670D\u52A1\u4EA4\u4ED8 | User-provided | Until deletion by user / \u76F4\u81F3\u7528\u6237\u5220\u9664 | Optional / \u53EF\u9009 |
1285
+ | Content / \u5185\u5BB9 | Messages, prompts / \u6D88\u606F\u3001\u63D0\u793A\u8BCD | Service delivery, AI inference / \u670D\u52A1\u4EA4\u4ED8\u3001AI \u63A8\u7406 | User-provided | 30 days then purged unless saved / 30 \u5929\u540E\u6E05\u9664\uFF08\u9664\u975E\u4FDD\u5B58\uFF09 | Optional / \u53EF\u9009 |
1286
+ | Behavior / \u884C\u4E3A | Feature flags, experiments / \u529F\u80FD\u5F00\u5173\u3001\u5B9E\u9A8C | Product personalization / \u4EA7\u54C1\u4E2A\u6027\u5316 | Automatic / \u81EA\u52A8 | 13 months / 13 \u4E2A\u6708 | Consent / \u9700\u540C\u610F |
1287
+ | Device / \u8BBE\u5907 | Device ID, OS version / \u8BBE\u5907 ID\u3001\u7CFB\u7EDF\u7248\u672C | Security, compatibility / \u5B89\u5168\u3001\u517C\u5BB9\u6027 | Automatic / \u81EA\u52A8 | 90 days / 90 \u5929 | Yes / \u662F |
1288
+
1289
+ ---
1290
+
1291
+ ## Contact / \u8054\u7CFB\u65B9\u5F0F
1292
+
1293
+ To exercise your rights (access, rectify, delete, export, withdraw consent), contact
1294
+ **{{COMPANY_CONTACT_EMAIL}}**.
1295
+
1296
+ \u5982\u9700\u884C\u4F7F\u60A8\u7684\u6743\u5229\uFF08\u8BBF\u95EE\u3001\u66F4\u6B63\u3001\u5220\u9664\u3001\u5BFC\u51FA\u3001\u64A4\u56DE\u540C\u610F\uFF09\uFF0C\u8BF7\u8054\u7CFB **{{COMPANY_CONTACT_EMAIL}}**\u3002
1297
+ `;async function yr(e,t){let o=V(e);if(!I.existsSync(o))return;let n=_.join(o,"src","components","compliance","Footer.tsx");O(n,ir);let r=_.join(o,"src","components","compliance","README.md");O(r,["# Compliance Components","",`Auto-generated by \`create-sailor\` for region: \`${t}\`.`,"","## Mounting the Footer","","```tsx",'import { Footer } from "@/components/compliance/Footer";','import { CookieBanner } from "@/components/compliance/CookieBanner";',"","export default function RootLayout({ children }) {"," return ("," <html>"," <body>"," {children}",` <Footer region="${t}" />`," <CookieBanner />"," </body>"," </html>"," );","}","```","","## Placeholders","","Policy MDX files contain `{{COMPANY_NAME}}`, `{{COMPANY_ADDRESS}}`,","`{{COMPANY_CONTACT_EMAIL}}`, `{{GOVERNING_JURISDICTION}}`, and","`{{AIGC_ALGORITHM_FILING_NUMBER}}`. These are filled by the `brand-apply` step.",""].join(`
1298
+ `))}async function Sr(e,t){let o=V(e);if(!I.existsSync(o))return;let n=_.join(o,"src","components","compliance","CookieBanner.tsx");O(n,pr)}async function Er(e,t){let o=V(e);if(!I.existsSync(o))return;let n=_.join(o,ze,"privacy","page.mdx");O(n,dr)}async function hr(e,t){let o=V(e);if(!I.existsSync(o))return;let n=_.join(o,ze,"terms","page.mdx");O(n,ur)}async function vr(e){let t=V(e);if(!I.existsSync(t))return;let o=_.join(t,Nt,"IcpFooter.tsx");O(o,ar),xt(e,["# Chinese ICP filing (\u5DE5\u4FE1\u90E8 ICP \u5907\u6848) \u2014 required to legally host a","# public-facing site from mainland China. Apply at https://beian.miit.gov.cn/",'NEXT_PUBLIC_ICP_BEIAN="" # \u5982 \u82CFICP\u59072024XXXXXX\u53F7'].join(`
1299
+ `))}async function _r(e){let t=V(e);if(!I.existsSync(t))return;let o=_.join(t,Nt,"PublicSecurityFooter.tsx");O(o,cr),xt(e,["# Chinese Public Security Bureau filing (\u516C\u5B89\u8054\u7F51\u5907\u6848)","# Required in addition to ICP. Apply at http://www.beian.gov.cn/",'NEXT_PUBLIC_PSB_BEIAN="" # \u5982 \u82CF\u516C\u7F51\u5B89\u5907 32011402XXXXXX\u53F7','NEXT_PUBLIC_PSB_CODE="" # 14-digit record code'].join(`
1300
+ `))}async function Pr(e){let t=V(e);if(!I.existsSync(t))return;let o=_.join(t,Nt,"AigcDisclaimer.tsx");O(o,lr),xt(e,["# Generative AI algorithm filing (\u7B97\u6CD5\u5907\u6848\u53F7) \u2014 required by","# \u7F51\u4FE1\u529E\u300A\u751F\u6210\u5F0F\u4EBA\u5DE5\u667A\u80FD\u670D\u52A1\u7BA1\u7406\u6682\u884C\u529E\u6CD5\u300Bfor services offered in China.",'NEXT_PUBLIC_AIGC_FILING_NUMBER="" # e.g. \u7F51\u4FE1\u7B97\u5907XXXXXXXX\u53F7'].join(`
1301
+ `))}async function Ir(e){let t=V(e);if(!I.existsSync(t))return;let o=_.join(t,ze,"aigc-disclosure","page.mdx");O(o,mr)}async function Cr(e){let t=V(e);if(!I.existsSync(t))return;let o=_.join(t,ze,"cross-border-data","page.mdx");O(o,gr)}async function br(e){let t=V(e);if(!I.existsSync(t))return;let o=_.join(t,ze,"pii-collection","page.mdx");O(o,fr)}async function an(e,t){try{await yr(e,t),await Sr(e,t),await Er(e,t),await hr(e,t),(t==="cn"||t==="hybrid")&&(await vr(e),await _r(e),await Pr(e),await Ir(e),await Cr(e),await br(e))}catch(o){throw console.error("applyComplianceTemplates failed:",o),new Error(`Failed to apply compliance templates for region "${t}". Check that ${e} is writable.`)}}import Ar from"fs";import Rr from"path";async function cn(e,t){let o=Rr.join(e,"nebutra.config.json");Ar.writeFileSync(o,JSON.stringify(t,null,2)+`
1302
+ `)}import z from"fs";import Tt from"path";function wr(e){z.existsSync(e)&&z.rmSync(e,{recursive:!0,force:!0})}function pn(e,t){return e.replace(/provider\s*=\s*"postgresql"/,`provider = "${t}"`)}function Nr(e){return e.replace(/^\s*directUrl\s*=\s*env\([^)]*\)\s*\n/gm,"")}function ln(e){let t=e.replace(/^\s*extensions\s*=\s*\[[^\]]*\]\s*\n/gm,"");return t=t.replace(/previewFeatures\s*=\s*\[\s*"postgresqlExtensions"\s*\]\s*\n/g,""),t}function xr(e,t){let o=Tt.join(e,".env.example");z.existsSync(o)?z.appendFileSync(o,`
1303
+ `+t):z.writeFileSync(o,t)}async function dn(e,t,o){let n=Tt.join(e,"packages","db");if(t==="none"){wr(n);return}let r=Tt.join(n,"prisma","schema.prisma"),s;if(t==="postgresql"?s=`postgresql://postgres:postgres@localhost:5432/${o}`:t==="mysql"?s=`mysql://root:root@localhost:3306/${o}`:s="file:./dev.db",xr(e,`# Database (${t})
1304
+ DATABASE_URL="${s}"
1305
+ `),!z.existsSync(r))return;let a=z.readFileSync(r,"utf8"),c=a;t==="mysql"?(c=pn(c,"mysql"),c=ln(c)):t==="sqlite"&&(c=pn(c,"sqlite"),c=Nr(c),c=ln(c)),c!==a&&z.writeFileSync(r,c)}import Je from"fs";import N from"path";import{fileURLToPath as Tr}from"url";var kr=Tr(import.meta.url),Or=N.dirname(kr);async function un(e,t){if(t==="none")return;let o=N.join(Or,"../../templates/deploy");t==="vercel"?await Je.promises.copyFile(N.join(o,"vercel.json"),N.join(e,"vercel.json")):t==="railway"?await Je.promises.copyFile(N.join(o,"railway.toml"),N.join(e,"railway.toml")):t==="cloudflare"?await Je.promises.copyFile(N.join(o,"wrangler.toml"),N.join(e,"wrangler.toml")):t==="selfhost"&&(await Je.promises.copyFile(N.join(o,"docker-compose.yml"),N.join(e,"docker-compose.yml")),await Je.promises.copyFile(N.join(o,"Dockerfile.web"),N.join(e,"Dockerfile.web")))}import Q from"fs";import le from"path";import{fileURLToPath as Ur}from"url";var Lr=Ur(import.meta.url),mn=le.dirname(Lr),Mr=/\.(md|mdx|json|ts|tsx|js|mjs|cjs|yaml|yml)$/;function Dr(e){let t=[le.join(mn,"..","templates","docs",e),le.join(mn,"..","..","templates","docs",e)];for(let o of t)if(Q.existsSync(o))return o;return t[0]}async function gn(e,t){if(t.framework==="none")return;let o=(t.framework==="fumadocs","fumadocs"),n=Dr(o),r=le.join(e,"apps","docs");if(!Q.existsSync(n))throw new Error(`Docs template not found: ${n}`);fn(n,r),yn(r,{"{PRODUCT_NAME}":t.projectName})}function fn(e,t){Q.mkdirSync(t,{recursive:!0});for(let o of Q.readdirSync(e,{withFileTypes:!0})){let n=le.join(e,o.name),r=le.join(t,o.name);o.isDirectory()?fn(n,r):Q.copyFileSync(n,r)}}function yn(e,t){for(let o of Q.readdirSync(e,{withFileTypes:!0})){let n=le.join(e,o.name);if(o.isDirectory())yn(n,t);else if(Mr.test(o.name)){let r=Q.readFileSync(n,"utf-8");for(let[s,a]of Object.entries(t))r=r.split(s).join(a);Q.writeFileSync(n,r)}}}import ue from"fs";import de from"path";var mt=[{id:"resend",name:"Resend",region:"global",envVars:["RESEND_API_KEY","RESEND_FROM"],docs:"https://resend.com/docs",pkg:"resend",pkgVersion:"^4.0.1"},{id:"postmark",name:"Postmark",region:"global",envVars:["POSTMARK_API_KEY","POSTMARK_FROM"],docs:"https://postmarkapp.com/developer",pkg:"postmark",pkgVersion:"^4.0.5"},{id:"ses",name:"AWS SES",region:"global",envVars:["AWS_ACCESS_KEY_ID","AWS_SECRET_ACCESS_KEY","AWS_SES_REGION","SES_FROM"],docs:"https://docs.aws.amazon.com/ses",pkg:"@aws-sdk/client-sesv2",pkgVersion:"^3.658.0"},{id:"aliyun-dm",name:"\u963F\u91CC\u4E91\u90AE\u4EF6\u63A8\u9001",region:"cn",envVars:["ALIYUN_DM_ACCESS_KEY","ALIYUN_DM_ACCESS_SECRET","ALIYUN_DM_ACCOUNT_NAME","ALIYUN_DM_FROM_ALIAS"],docs:"https://help.aliyun.com/product/29412.html",pkg:"@alicloud/dm20151123",pkgVersion:"^3.0.0"},{id:"tencent-ses",name:"\u817E\u8BAF\u4E91 SES",region:"cn",envVars:["TENCENT_SECRET_ID","TENCENT_SECRET_KEY","TENCENT_SES_FROM_EMAIL"],docs:"https://cloud.tencent.com/document/product/1288",pkg:"tencentcloud-sdk-nodejs-ses",pkgVersion:"^4.0.900"},{id:"netease",name:"\u7F51\u6613\u4F01\u4E1A\u90AE\u7BB1",region:"cn",envVars:["NETEASE_USER","NETEASE_PASS","NETEASE_FROM"],docs:"https://qiye.163.com/help",pkg:"nodemailer",pkgVersion:"^6.9.15"},{id:"none",name:"None",region:"global",envVars:[],docs:""}];function Sn(e){return mt.find(t=>t.id===e)}var ia=mt.reduce((e,t)=>(e[t.region]||(e[t.region]=[]),e[t.region].push(t),e),{});var me="@nebutra/email";function kt(e,t){ue.mkdirSync(de.dirname(e),{recursive:!0}),ue.writeFileSync(e,t)}function jr(e,t){let o=de.join(e,".env.example");ue.existsSync(o)?ue.appendFileSync(o,`
1306
+ `+t):ue.writeFileSync(o,t)}function Fr(e){if(e.envVars.length===0)return"";let t=`# Email provider: ${e.name}`,o=e.envVars.map(n=>`${n}=`).join(`
1307
+ `);return`${t}
1308
+ ${o}
1309
+ `}var Vr=`/**
1310
+ * Public inputs accepted by the \`sendEmail\` wrapper.
1311
+ * Keep the surface minimal \u2014 individual providers may accept more, but
1312
+ * consumers should code against this shape for portability.
1313
+ */
1314
+ export interface SendEmailInput {
1315
+ to: string | string[];
1316
+ subject: string;
1317
+ html?: string;
1318
+ text?: string;
1319
+ from?: string;
1320
+ replyTo?: string;
1321
+ }
1322
+
1323
+ export interface SendEmailResult {
1324
+ /** Provider-specific message identifier, when available. */
1325
+ id?: string;
1326
+ /** Raw provider response \u2014 useful for debugging, not a stable contract. */
1327
+ raw?: unknown;
1328
+ }
1329
+ `;function Kr(){return`/**
1330
+ * @${me.slice(1)} \u2014 Resend transactional email wrapper.
1331
+ *
1332
+ * Docs: https://resend.com/docs
1333
+ */
1334
+ import { Resend } from "resend";
1335
+
1336
+ import type { SendEmailInput, SendEmailResult } from "./types.js";
1337
+
1338
+ const apiKey = process.env.RESEND_API_KEY;
1339
+ if (!apiKey) {
1340
+ throw new Error("RESEND_API_KEY not configured");
1341
+ }
1342
+
1343
+ const resend = new Resend(apiKey);
1344
+
1345
+ export async function sendEmail(input: SendEmailInput): Promise<SendEmailResult> {
1346
+ const from = input.from ?? process.env.RESEND_FROM;
1347
+ if (!from) {
1348
+ throw new Error("RESEND_FROM (or input.from) must be provided");
1349
+ }
1350
+
1351
+ const { data, error } = await resend.emails.send({
1352
+ from,
1353
+ to: Array.isArray(input.to) ? input.to : [input.to],
1354
+ subject: input.subject,
1355
+ html: input.html,
1356
+ text: input.text,
1357
+ ...(input.replyTo ? { replyTo: input.replyTo } : {}),
1358
+ });
1359
+
1360
+ if (error) {
1361
+ throw new Error(\`Resend send failed: \${error.message}\`);
1362
+ }
1363
+
1364
+ return { id: data?.id, raw: data };
1365
+ }
1366
+
1367
+ export type { SendEmailInput, SendEmailResult } from "./types.js";
1368
+ `}function Yr(){return`/**
1369
+ * @${me.slice(1)} \u2014 Postmark transactional email wrapper.
1370
+ *
1371
+ * Docs: https://postmarkapp.com/developer
1372
+ */
1373
+ import { ServerClient } from "postmark";
1374
+
1375
+ import type { SendEmailInput, SendEmailResult } from "./types.js";
1376
+
1377
+ const apiKey = process.env.POSTMARK_API_KEY;
1378
+ if (!apiKey) {
1379
+ throw new Error("POSTMARK_API_KEY not configured");
1380
+ }
1381
+
1382
+ const client = new ServerClient(apiKey);
1383
+
1384
+ export async function sendEmail(input: SendEmailInput): Promise<SendEmailResult> {
1385
+ const from = input.from ?? process.env.POSTMARK_FROM;
1386
+ if (!from) {
1387
+ throw new Error("POSTMARK_FROM (or input.from) must be provided");
1388
+ }
1389
+
1390
+ const to = Array.isArray(input.to) ? input.to.join(", ") : input.to;
1391
+
1392
+ const response = await client.sendEmail({
1393
+ From: from,
1394
+ To: to,
1395
+ Subject: input.subject,
1396
+ HtmlBody: input.html,
1397
+ TextBody: input.text,
1398
+ ReplyTo: input.replyTo,
1399
+ MessageStream: "outbound",
1400
+ });
1401
+
1402
+ return { id: response.MessageID, raw: response };
1403
+ }
1404
+
1405
+ export type { SendEmailInput, SendEmailResult } from "./types.js";
1406
+ `}function Br(){return`/**
1407
+ * @${me.slice(1)} \u2014 AWS SES v2 transactional email wrapper.
1408
+ *
1409
+ * Docs: https://docs.aws.amazon.com/ses
1410
+ */
1411
+ import {
1412
+ SendEmailCommand,
1413
+ SESv2Client,
1414
+ } from "@aws-sdk/client-sesv2";
1415
+
1416
+ import type { SendEmailInput, SendEmailResult } from "./types.js";
1417
+
1418
+ const region = process.env.AWS_SES_REGION;
1419
+ if (!region) {
1420
+ throw new Error("AWS_SES_REGION not configured");
1421
+ }
1422
+
1423
+ const client = new SESv2Client({
1424
+ region,
1425
+ credentials:
1426
+ process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY
1427
+ ? {
1428
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID,
1429
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
1430
+ }
1431
+ : undefined,
1432
+ });
1433
+
1434
+ export async function sendEmail(input: SendEmailInput): Promise<SendEmailResult> {
1435
+ const from = input.from ?? process.env.SES_FROM;
1436
+ if (!from) {
1437
+ throw new Error("SES_FROM (or input.from) must be provided");
1438
+ }
1439
+
1440
+ const toAddresses = Array.isArray(input.to) ? input.to : [input.to];
1441
+
1442
+ const command = new SendEmailCommand({
1443
+ FromEmailAddress: from,
1444
+ Destination: { ToAddresses: toAddresses },
1445
+ ReplyToAddresses: input.replyTo ? [input.replyTo] : undefined,
1446
+ Content: {
1447
+ Simple: {
1448
+ Subject: { Data: input.subject, Charset: "UTF-8" },
1449
+ Body: {
1450
+ ...(input.html ? { Html: { Data: input.html, Charset: "UTF-8" } } : {}),
1451
+ ...(input.text ? { Text: { Data: input.text, Charset: "UTF-8" } } : {}),
1452
+ },
1453
+ },
1454
+ },
1455
+ });
1456
+
1457
+ const response = await client.send(command);
1458
+ return { id: response.MessageId, raw: response };
1459
+ }
1460
+
1461
+ export type { SendEmailInput, SendEmailResult } from "./types.js";
1462
+ `}function $r(){return`/**
1463
+ * @${me.slice(1)} \u2014 \u963F\u91CC\u4E91\u90AE\u4EF6\u63A8\u9001 (Aliyun DirectMail) wrapper.
1464
+ *
1465
+ * Docs: https://help.aliyun.com/product/29412.html
1466
+ */
1467
+ import Dm20151123, * as $Dm20151123 from "@alicloud/dm20151123";
1468
+ import * as $OpenApi from "@alicloud/openapi-client";
1469
+
1470
+ import type { SendEmailInput, SendEmailResult } from "./types.js";
1471
+
1472
+ const accessKeyId = process.env.ALIYUN_DM_ACCESS_KEY;
1473
+ const accessKeySecret = process.env.ALIYUN_DM_ACCESS_SECRET;
1474
+ if (!accessKeyId || !accessKeySecret) {
1475
+ throw new Error(
1476
+ "ALIYUN_DM_ACCESS_KEY and ALIYUN_DM_ACCESS_SECRET must be configured",
1477
+ );
1478
+ }
1479
+
1480
+ const config = new $OpenApi.Config({
1481
+ accessKeyId,
1482
+ accessKeySecret,
1483
+ endpoint: "dm.aliyuncs.com",
1484
+ });
1485
+
1486
+ const client = new Dm20151123.default(config);
1487
+
1488
+ export async function sendEmail(input: SendEmailInput): Promise<SendEmailResult> {
1489
+ const accountName = process.env.ALIYUN_DM_ACCOUNT_NAME;
1490
+ if (!accountName) {
1491
+ throw new Error("ALIYUN_DM_ACCOUNT_NAME must be configured");
1492
+ }
1493
+
1494
+ const toAddress = Array.isArray(input.to) ? input.to.join(",") : input.to;
1495
+
1496
+ const req = new $Dm20151123.SingleSendMailRequest({
1497
+ accountName,
1498
+ addressType: 1,
1499
+ replyToAddress: false,
1500
+ toAddress,
1501
+ subject: input.subject,
1502
+ htmlBody: input.html,
1503
+ textBody: input.text,
1504
+ fromAlias: process.env.ALIYUN_DM_FROM_ALIAS,
1505
+ });
1506
+
1507
+ const response = await client.singleSendMail(req);
1508
+ return { id: response.body?.requestId, raw: response };
1509
+ }
1510
+
1511
+ export type { SendEmailInput, SendEmailResult } from "./types.js";
1512
+ `}function qr(){return`/**
1513
+ * @${me.slice(1)} \u2014 \u817E\u8BAF\u4E91 SES wrapper.
1514
+ *
1515
+ * Docs: https://cloud.tencent.com/document/product/1288
1516
+ */
1517
+ import tencentcloud from "tencentcloud-sdk-nodejs-ses";
1518
+
1519
+ import type { SendEmailInput, SendEmailResult } from "./types.js";
1520
+
1521
+ const secretId = process.env.TENCENT_SECRET_ID;
1522
+ const secretKey = process.env.TENCENT_SECRET_KEY;
1523
+ if (!secretId || !secretKey) {
1524
+ throw new Error("TENCENT_SECRET_ID and TENCENT_SECRET_KEY must be configured");
1525
+ }
1526
+
1527
+ const SesClient = tencentcloud.ses.v20201002.Client;
1528
+
1529
+ const client = new SesClient({
1530
+ credential: { secretId, secretKey },
1531
+ region: "ap-hongkong",
1532
+ profile: {
1533
+ httpProfile: { endpoint: "ses.tencentcloudapi.com" },
1534
+ },
1535
+ });
1536
+
1537
+ export async function sendEmail(input: SendEmailInput): Promise<SendEmailResult> {
1538
+ const from = input.from ?? process.env.TENCENT_SES_FROM_EMAIL;
1539
+ if (!from) {
1540
+ throw new Error("TENCENT_SES_FROM_EMAIL (or input.from) must be provided");
1541
+ }
1542
+
1543
+ const destination = Array.isArray(input.to) ? input.to : [input.to];
1544
+
1545
+ const response = await client.SendEmail({
1546
+ FromEmailAddress: from,
1547
+ Destination: destination,
1548
+ Subject: input.subject,
1549
+ ReplyToAddresses: input.replyTo,
1550
+ Simple: {
1551
+ Html: input.html,
1552
+ Text: input.text,
1553
+ },
1554
+ });
1555
+
1556
+ return { id: response.MessageId, raw: response };
1557
+ }
1558
+
1559
+ export type { SendEmailInput, SendEmailResult } from "./types.js";
1560
+ `}function Gr(){return`/**
1561
+ * @${me.slice(1)} \u2014 \u7F51\u6613\u4F01\u4E1A\u90AE\u7BB1 via SMTP (nodemailer).
1562
+ *
1563
+ * Docs: https://qiye.163.com/help
1564
+ */
1565
+ import nodemailer from "nodemailer";
1566
+
1567
+ import type { SendEmailInput, SendEmailResult } from "./types.js";
1568
+
1569
+ const user = process.env.NETEASE_USER;
1570
+ const pass = process.env.NETEASE_PASS;
1571
+ if (!user || !pass) {
1572
+ throw new Error("NETEASE_USER and NETEASE_PASS must be configured");
1573
+ }
1574
+
1575
+ const transporter = nodemailer.createTransport({
1576
+ host: "smtp.qiye.163.com",
1577
+ port: 465,
1578
+ secure: true,
1579
+ auth: { user, pass },
1580
+ });
1581
+
1582
+ export async function sendEmail(input: SendEmailInput): Promise<SendEmailResult> {
1583
+ const from = input.from ?? process.env.NETEASE_FROM ?? user;
1584
+
1585
+ const response = await transporter.sendMail({
1586
+ from,
1587
+ to: Array.isArray(input.to) ? input.to.join(", ") : input.to,
1588
+ subject: input.subject,
1589
+ html: input.html,
1590
+ text: input.text,
1591
+ replyTo: input.replyTo,
1592
+ });
1593
+
1594
+ return { id: response.messageId, raw: response };
1595
+ }
1596
+
1597
+ export type { SendEmailInput, SendEmailResult } from "./types.js";
1598
+ `}function Hr(e){switch(e){case"resend":return Kr();case"postmark":return Yr();case"ses":return Br();case"aliyun-dm":return $r();case"tencent-ses":return qr();case"netease":return Gr();case"none":return`export {};
1599
+ `}}function Wr(e){let t={};e.pkg&&e.pkgVersion&&(t[e.pkg]=e.pkgVersion),e.id==="aliyun-dm"&&(t["@alicloud/openapi-client"]="^0.4.12");let o={name:me,version:"0.1.0",private:!0,description:`Transactional email via ${e.name}`,type:"module",main:"./dist/index.js",exports:{".":{import:"./dist/index.js",types:"./dist/index.d.ts"}},scripts:{build:"tsup",typecheck:"tsc --noEmit",clean:"rm -rf dist .turbo"},dependencies:t,devDependencies:{tsup:"^8.5.0",typescript:"catalog:"}};return JSON.stringify(o,null,2)+`
1600
+ `}async function En(e,t,o){if(t==="none")return;let n=Sn(t);if(!n)throw new Error(`Unknown email provider "${t}". Valid ids: ${mt.map(a=>a.id).join(", ")}`);let r=de.join(e,"packages","email"),s=de.join(r,"src");ue.existsSync(s)&&ue.rmSync(s,{recursive:!0,force:!0}),kt(de.join(r,"package.json"),Wr(n)),kt(de.join(s,"types.ts"),Vr),kt(de.join(s,"index.ts"),Hr(n.id)),jr(e,Fr(n))}import vn from"crypto";import Ze from"fs";import hn from"path";function ge(e){return vn.randomBytes(e).toString("base64").replace(/=+$/,"")}function Xr(e){return vn.randomBytes(e).toString("hex")}function zr(){return{AUTH_SECRET:ge(32),NEXTAUTH_SECRET:ge(32),BETTER_AUTH_SECRET:ge(32),JWT_SECRET:ge(32),WEBHOOK_SECRET:Xr(16),ENCRYPTION_KEY:ge(32),API_SECRET_KEY:ge(32),SESSION_SECRET:ge(32)}}function Qr(e){return e.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}function Jr(e,t,o){let n=new RegExp(`^(?!#)\\s*${t}=.*$`,"gm"),r=Qr(o);return e.replace(n,`${t}="${r}"`)}async function _n(e){let t=hn.join(e,".env.example"),o=hn.join(e,".env.local");if(!Ze.existsSync(t))return null;try{let n=Ze.readFileSync(t,"utf8"),r=zr(),a=Ze.existsSync(o)?Ze.readFileSync(o,"utf8"):n;for(let c of Object.keys(r))a=Jr(a,c,r[c]);return Ze.writeFileSync(o,a),r}catch(n){let r=n instanceof Error?n.message:String(n);throw console.error(`[create-sailor] Failed to generate env secrets: ${r}`),new Error(`Unable to write .env.local: ${r}`)}}import gt from"fs";import Pn from"path";async function In(e,t){let o=`# Automatically injected by create-sailor
55
1601
  DATABASE_URL="${String(t.databaseUrl)}"
56
1602
 
57
1603
  NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="${String(t.clerkPublishable)}"
58
1604
  CLERK_SECRET_KEY="${String(t.clerkSecret)}"
59
1605
 
60
1606
  NEXT_PUBLIC_SITE_URL="http://localhost:3000"
61
- `,s=Oe.join(e,".env"),r=Oe.join(e,".env.local");!ae.existsSync(s)&&!ae.existsSync(r)?ae.writeFileSync(r,n):ae.appendFileSync(r,`
62
- `+n)}import{execSync as It}from"child_process";import _ from"fs";import z from"path";import _t from"ignore";var Rt={mirror:{repo:process.env.SAILOR_TEMPLATE_REPO||"Nebutra/Sailor-Template",ref:process.env.SAILOR_TEMPLATE_REF||"main",applyIgnore:!1},main:{repo:"Nebutra/Nebutra-Sailor",ref:"main",applyIgnore:!0}};function Ct(e,t){return`https://github.com/${e}/archive/refs/heads/${t}.tar.gz`}function je(e,t,n){let s=_.readdirSync(t,{withFileTypes:!0});for(let r of s){if(r.name==="node_modules"||r.name===".git")continue;let a=z.join(t,r.name),p=z.relative(e,a).split(z.sep).join("/");r.isDirectory()?(n.push(p+"/"),je(e,a,n)):n.push(p)}}function Lt(e){let t=z.join(e,".templateignore");if(!_.existsSync(t))return;let n=_.readFileSync(t,"utf8"),s=_t().add(n),r=[];je(e,e,r);let a=r.map(o=>o.endsWith("/")?o.slice(0,-1):o),p=new Set(s.filter(a)),i=a.filter(o=>!p.has(o)).sort((o,m)=>m.split("/").length-o.split("/").length);for(let o of i){let m=z.join(e,o);try{_.existsSync(m)&&_.rmSync(m,{recursive:!0,force:!0})}catch{}}try{_.existsSync(t)&&_.rmSync(t,{force:!0})}catch{}}function Tt(e,t){It(`set -o pipefail; curl -sSfL ${e} | tar -xz -C "${t}" --strip-components=1`,{stdio:"ignore",shell:"bash"})}async function qe(e){_.mkdirSync(e,{recursive:!0});let n=process.env.SAILOR_TEMPLATE_SOURCE==="main"?["main"]:["mirror","main"],s;for(let r of n){let a=Rt[r],p=Ct(a.repo,a.ref);try{if(Tt(p,e),a.applyIgnore)try{Lt(e)}catch(i){throw new Error(`Template cloned, but .templateignore processing failed: ${i instanceof Error?i.message:String(i)}`)}return}catch(i){s=i instanceof Error?i:new Error(String(i));try{let o=_.readdirSync(e);for(let m of o)_.rmSync(z.join(e,m),{recursive:!0,force:!0})}catch{}}}throw new Error(`Failed to download the template from any source. Last error: ${s?.message??"unknown"}
1607
+ `,n=Pn.join(e,".env"),r=Pn.join(e,".env.local");!gt.existsSync(n)&&!gt.existsSync(r)?gt.writeFileSync(r,o):gt.appendFileSync(r,`
1608
+ `+o)}import{execSync as Zr}from"child_process";import K from"fs";import ke from"path";import es from"ignore";var ts={mirror:{repo:process.env.SAILOR_TEMPLATE_REPO||"Nebutra/Sailor-Template",ref:process.env.SAILOR_TEMPLATE_REF||"main",applyIgnore:!1},main:{repo:"Nebutra/Nebutra-Sailor",ref:"main",applyIgnore:!0}};function ns(e,t){return`https://github.com/${e}/archive/refs/heads/${t}.tar.gz`}function Cn(e,t,o){let n=K.readdirSync(t,{withFileTypes:!0});for(let r of n){if(r.name==="node_modules"||r.name===".git")continue;let s=ke.join(t,r.name),a=ke.relative(e,s).split(ke.sep).join("/");r.isDirectory()?(o.push(a+"/"),Cn(e,s,o)):o.push(a)}}function os(e){let t=ke.join(e,".templateignore");if(!K.existsSync(t))return;let o=K.readFileSync(t,"utf8"),n=es().add(o),r=[];Cn(e,e,r);let s=r.map(i=>i.endsWith("/")?i.slice(0,-1):i),a=new Set(n.filter(s)),c=s.filter(i=>!a.has(i)).sort((i,d)=>d.split("/").length-i.split("/").length);for(let i of c){let d=ke.join(e,i);try{K.existsSync(d)&&K.rmSync(d,{recursive:!0,force:!0})}catch{}}try{K.existsSync(t)&&K.rmSync(t,{force:!0})}catch{}}function rs(e,t){Zr(`set -o pipefail; curl -sSfL ${e} | tar -xz -C "${t}" --strip-components=1`,{stdio:"ignore",shell:"bash"})}async function bn(e){K.mkdirSync(e,{recursive:!0});let o=process.env.SAILOR_TEMPLATE_SOURCE==="main"?["main"]:["mirror","main"],n;for(let r of o){let s=ts[r],a=ns(s.repo,s.ref);try{if(rs(a,e),s.applyIgnore)try{os(e)}catch(c){throw new Error(`Template cloned, but .templateignore processing failed: ${c instanceof Error?c.message:String(c)}`)}return}catch(c){n=c instanceof Error?c:new Error(String(c));try{let i=K.readdirSync(e);for(let d of i)K.rmSync(ke.join(e,d),{recursive:!0,force:!0})}catch{}}}throw new Error(`Failed to download the template from any source. Last error: ${n?.message??"unknown"}
63
1609
 
64
1610
  Ensure you have internet access and curl/tar installed.
65
- You can override the source with SAILOR_TEMPLATE_REPO / SAILOR_TEMPLATE_REF env vars.`)}import me from"fs";import Nt from"path";async function Fe(e,t){let n=Nt.join(e,"package.json");if(me.existsSync(n)){let s=me.readFileSync(n,"utf8"),r=JSON.parse(s);r.name=t,r.version="0.1.0",me.writeFileSync(n,JSON.stringify(r,null,2)+`
66
- `)}}import C from"fs";import Q from"path";var Ot=["stripe","lemonsqueezy","chinapay","polar"];function De(e){C.existsSync(e)&&C.rmSync(e,{recursive:!0,force:!0})}function Vt(e){switch(e){case"stripe":return["stripe"];case"lemon":return["lemonsqueezy"];case"wechat":case"alipay":return["chinapay"]}}function jt(e,t){let n=Q.join(e,".env.example");C.existsSync(n)?C.appendFileSync(n,`
67
- `+t):C.writeFileSync(n,t)}function qt(e){switch(e){case"stripe":return`# Stripe
1611
+ You can override the source with SAILOR_TEMPLATE_REPO / SAILOR_TEMPLATE_REF env vars.`)}import Oe from"fs";import R from"path";var An=[{id:"sentry",name:"Sentry",region:"both",envVars:["SENTRY_DSN","NEXT_PUBLIC_SENTRY_DSN"],docs:"https://docs.sentry.io",pkg:"@sentry/nextjs",instrumentationFile:"apps/web/instrumentation.ts"},{id:"datadog",name:"Datadog",region:"global",envVars:["DD_API_KEY","DD_APP_KEY","DD_SITE"],docs:"https://docs.datadoghq.com",pkg:"dd-trace",instrumentationFile:"apps/web/instrumentation.ts"},{id:"bugsnag",name:"Bugsnag",region:"global",envVars:["BUGSNAG_API_KEY","NEXT_PUBLIC_BUGSNAG_API_KEY"],docs:"https://docs.bugsnag.com",pkg:"@bugsnag/js",instrumentationFile:"apps/web/src/lib/monitoring/bugsnag.ts"},{id:"aliyun-arms",name:"\u963F\u91CC\u4E91 ARMS",region:"cn",envVars:["ALIYUN_ARMS_LICENSE_KEY","ALIYUN_ARMS_APP_NAME","NEXT_PUBLIC_ARMS_PID"],docs:"https://help.aliyun.com/product/34364.html",instrumentationFile:"apps/web/src/components/monitoring/ArmsScript.tsx"},{id:"tingyun",name:"\u542C\u4E91 TingYun",region:"cn",envVars:["TINGYUN_LICENSE_KEY","NEXT_PUBLIC_TINGYUN_APP_KEY"],docs:"https://www.tingyun.com/docs",instrumentationFile:"apps/web/src/components/monitoring/TingYunScript.tsx"},{id:"none",name:"None",region:"both",envVars:[],docs:""}],Ca=An.reduce((e,t)=>(e[t.region]||(e[t.region]=[]),e[t.region].push(t),e),{});function Rn(e){return An.find(t=>t.id===e)}function ss(e){Oe.mkdirSync(e,{recursive:!0})}function Y(e,t){ss(R.dirname(e)),Oe.writeFileSync(e,t)}function et(e,t){let o=R.join(e,".env.example");Oe.existsSync(o)?Oe.appendFileSync(o,`
1612
+ `+t):Oe.writeFileSync(o,t)}function is(e){let t=`import * as Sentry from "@sentry/nextjs";
1613
+
1614
+ Sentry.init({
1615
+ dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
1616
+ tracesSampleRate: 1.0,
1617
+ replaysOnErrorSampleRate: 1.0,
1618
+ replaysSessionSampleRate: 0.1,
1619
+ integrations: [
1620
+ Sentry.replayIntegration({
1621
+ maskAllText: true,
1622
+ blockAllMedia: true,
1623
+ }),
1624
+ ],
1625
+ environment: process.env.NODE_ENV,
1626
+ });
1627
+ `,o=`import * as Sentry from "@sentry/nextjs";
1628
+
1629
+ Sentry.init({
1630
+ dsn: process.env.SENTRY_DSN ?? process.env.NEXT_PUBLIC_SENTRY_DSN,
1631
+ tracesSampleRate: 1.0,
1632
+ environment: process.env.NODE_ENV,
1633
+ });
1634
+ `,n=`import * as Sentry from "@sentry/nextjs";
1635
+
1636
+ Sentry.init({
1637
+ dsn: process.env.SENTRY_DSN ?? process.env.NEXT_PUBLIC_SENTRY_DSN,
1638
+ tracesSampleRate: 1.0,
1639
+ environment: process.env.NODE_ENV,
1640
+ });
1641
+ `,r=`/**
1642
+ * Next.js instrumentation hook \u2014 wires Sentry into Node and Edge runtimes.
1643
+ * See https://nextjs.org/docs/app/building-your-application/optimizing/instrumentation
1644
+ */
1645
+ export async function register() {
1646
+ if (process.env.NEXT_RUNTIME === "nodejs") {
1647
+ await import("./sentry.server.config");
1648
+ }
1649
+ if (process.env.NEXT_RUNTIME === "edge") {
1650
+ await import("./sentry.edge.config");
1651
+ }
1652
+ }
1653
+
1654
+ export { captureRequestError as onRequestError } from "@sentry/nextjs";
1655
+ `;Y(R.join(e,"sentry.client.config.ts"),t),Y(R.join(e,"sentry.server.config.ts"),o),Y(R.join(e,"sentry.edge.config.ts"),n),Y(R.join(e,"instrumentation.ts"),r)}function as(){return`# Sentry \u2014 error tracking + performance monitoring
1656
+ # Get your DSN from https://sentry.io/settings/projects/<project>/keys/
1657
+ SENTRY_DSN=
1658
+ NEXT_PUBLIC_SENTRY_DSN=
1659
+ SENTRY_ORG=
1660
+ SENTRY_PROJECT=
1661
+ SENTRY_AUTH_TOKEN=
1662
+ `}function cs(e){let t=`/**
1663
+ * Next.js instrumentation hook \u2014 wires Datadog APM (dd-trace) into Node runtime.
1664
+ * Edge runtime is not supported by dd-trace; only nodejs is initialized.
1665
+ */
1666
+ export async function register() {
1667
+ if (process.env.NEXT_RUNTIME === "nodejs") {
1668
+ const tracer = (await import("dd-trace")).default;
1669
+ tracer.init({
1670
+ service: process.env.DD_SERVICE ?? "sailor-web",
1671
+ env: process.env.DD_ENV ?? process.env.NODE_ENV,
1672
+ version: process.env.DD_VERSION,
1673
+ logInjection: true,
1674
+ runtimeMetrics: true,
1675
+ profiling: true,
1676
+ });
1677
+ }
1678
+ }
1679
+ `,o=`"use client";
1680
+ /**
1681
+ * Datadog RUM (Real User Monitoring) \u2014 browser-side tracking.
1682
+ * Initialize once in your root layout via <DatadogRumProvider />.
1683
+ */
1684
+ import { datadogRum } from "@datadog/browser-rum";
1685
+
1686
+ let initialized = false;
1687
+
1688
+ export function initDatadogRum(): void {
1689
+ if (initialized || typeof window === "undefined") return;
1690
+ const applicationId = process.env.NEXT_PUBLIC_DD_APPLICATION_ID;
1691
+ const clientToken = process.env.NEXT_PUBLIC_DD_CLIENT_TOKEN;
1692
+ if (!applicationId || !clientToken) return;
1693
+
1694
+ datadogRum.init({
1695
+ applicationId,
1696
+ clientToken,
1697
+ site: process.env.NEXT_PUBLIC_DD_SITE ?? "datadoghq.com",
1698
+ service: process.env.NEXT_PUBLIC_DD_SERVICE ?? "sailor-web",
1699
+ env: process.env.NEXT_PUBLIC_DD_ENV ?? "production",
1700
+ version: process.env.NEXT_PUBLIC_DD_VERSION,
1701
+ sessionSampleRate: 100,
1702
+ sessionReplaySampleRate: 20,
1703
+ trackUserInteractions: true,
1704
+ trackResources: true,
1705
+ trackLongTasks: true,
1706
+ defaultPrivacyLevel: "mask-user-input",
1707
+ });
1708
+ initialized = true;
1709
+ }
1710
+ `;Y(R.join(e,"instrumentation.ts"),t),Y(R.join(e,"src","lib","monitoring","datadog-rum.ts"),o)}function ps(){return`# Datadog \u2014 APM + RUM
1711
+ # Server APM (dd-trace)
1712
+ DD_API_KEY=
1713
+ DD_APP_KEY=
1714
+ DD_SITE=datadoghq.com
1715
+ DD_SERVICE=sailor-web
1716
+ DD_ENV=production
1717
+ DD_VERSION=
1718
+ # Browser RUM
1719
+ NEXT_PUBLIC_DD_APPLICATION_ID=
1720
+ NEXT_PUBLIC_DD_CLIENT_TOKEN=
1721
+ NEXT_PUBLIC_DD_SITE=datadoghq.com
1722
+ NEXT_PUBLIC_DD_SERVICE=sailor-web
1723
+ NEXT_PUBLIC_DD_ENV=production
1724
+ NEXT_PUBLIC_DD_VERSION=
1725
+ `}function ls(e){let t=`/**
1726
+ * Bugsnag server-side initialization.
1727
+ * Import this once at the top of your server entrypoint or a shared init module.
1728
+ */
1729
+ import Bugsnag from "@bugsnag/js";
1730
+
1731
+ let started = false;
1732
+
1733
+ export function startBugsnagServer(): void {
1734
+ if (started) return;
1735
+ const apiKey = process.env.BUGSNAG_API_KEY;
1736
+ if (!apiKey) return;
1737
+
1738
+ Bugsnag.start({
1739
+ apiKey,
1740
+ releaseStage: process.env.NODE_ENV,
1741
+ appVersion: process.env.APP_VERSION,
1742
+ });
1743
+ started = true;
1744
+ }
1745
+ `,o=`"use client";
1746
+ /**
1747
+ * Bugsnag browser initialization \u2014 wrap your app root with the returned provider.
1748
+ */
1749
+ import Bugsnag from "@bugsnag/js";
1750
+ import BugsnagPluginReact from "@bugsnag/plugin-react";
1751
+ import React from "react";
1752
+
1753
+ let errorBoundary: React.ComponentType<{
1754
+ children: React.ReactNode;
1755
+ FallbackComponent?: React.ComponentType<{ error: Error }>;
1756
+ }> | null = null;
1757
+
1758
+ export function getBugsnagErrorBoundary() {
1759
+ if (errorBoundary) return errorBoundary;
1760
+ const apiKey = process.env.NEXT_PUBLIC_BUGSNAG_API_KEY;
1761
+ if (!apiKey || typeof window === "undefined") return null;
1762
+
1763
+ Bugsnag.start({
1764
+ apiKey,
1765
+ plugins: [new BugsnagPluginReact()],
1766
+ releaseStage: process.env.NODE_ENV,
1767
+ });
1768
+
1769
+ const plugin = Bugsnag.getPlugin("react");
1770
+ if (!plugin) return null;
1771
+ errorBoundary = plugin.createErrorBoundary(React) as typeof errorBoundary;
1772
+ return errorBoundary;
1773
+ }
1774
+ `;Y(R.join(e,"src","lib","monitoring","bugsnag.server.ts"),t),Y(R.join(e,"src","lib","monitoring","bugsnag.client.tsx"),o)}function ds(){return`# Bugsnag \u2014 error tracking
1775
+ BUGSNAG_API_KEY=
1776
+ NEXT_PUBLIC_BUGSNAG_API_KEY=
1777
+ APP_VERSION=
1778
+ `}function us(e){Y(R.join(e,"src","components","monitoring","ArmsScript.tsx"),`"use client";
1779
+ /**
1780
+ * \u963F\u91CC\u4E91 ARMS (Application Real-time Monitoring Service) browser agent.
1781
+ * Injects the official JS agent via next/script.
1782
+ * Docs: https://help.aliyun.com/document_detail/58655.html
1783
+ */
1784
+ import Script from "next/script";
1785
+
1786
+ export function ArmsScript() {
1787
+ const pid = process.env.NEXT_PUBLIC_ARMS_PID;
1788
+ if (!pid) return null;
1789
+ return (
1790
+ <Script
1791
+ id="aliyun-arms"
1792
+ strategy="afterInteractive"
1793
+ dangerouslySetInnerHTML={{
1794
+ __html: \`!(function(c,b,d,a){c[a]||(c[a]={});c[a].config={pid:"\${pid}",imgUrl:"https://arms-retcode.aliyuncs.com/r.png?",sendResource:true,enableLinkTrace:true,behavior:true};with(b)with(body)with(insertBefore(createElement("script"),firstChild))setAttribute("crossorigin","",src=d)})(window,document,"https://retcode.alicdn.com/retcode/bl.js","__bl");\`,
1795
+ }}
1796
+ />
1797
+ );
1798
+ }
1799
+ `)}function ms(){return`# \u963F\u91CC\u4E91 ARMS \u2014 APM (\u4E2D\u56FD\u533A)
1800
+ ALIYUN_ARMS_LICENSE_KEY=
1801
+ ALIYUN_ARMS_APP_NAME=
1802
+ NEXT_PUBLIC_ARMS_PID=
1803
+ `}function gs(e){Y(R.join(e,"src","components","monitoring","TingYunScript.tsx"),`"use client";
1804
+ /**
1805
+ * \u542C\u4E91 Browser Agent (\u524D\u7AEF\u6027\u80FD\u76D1\u63A7).
1806
+ * Docs: https://www.tingyun.com/docs
1807
+ */
1808
+ import Script from "next/script";
1809
+
1810
+ export function TingYunScript() {
1811
+ const appKey = process.env.NEXT_PUBLIC_TINGYUN_APP_KEY;
1812
+ if (!appKey) return null;
1813
+ return (
1814
+ <Script
1815
+ id="tingyun-browser"
1816
+ strategy="afterInteractive"
1817
+ src={\`https://rs.tingyun.com/rum/\${appKey}.js\`}
1818
+ />
1819
+ );
1820
+ }
1821
+ `)}function fs(){return`# \u542C\u4E91 TingYun \u2014 APM (\u4E2D\u56FD\u533A)
1822
+ TINGYUN_LICENSE_KEY=
1823
+ NEXT_PUBLIC_TINGYUN_APP_KEY=
1824
+ `}async function wn(e,t,o="global"){let n=Rn(t);if(!n||n.id==="none")return;let r=R.join(e,"apps","web");if(!Oe.existsSync(r))return;let s=n.id;try{switch(s){case"sentry":is(r),et(e,as());return;case"datadog":cs(r),et(e,ps());return;case"bugsnag":ls(r),et(e,ds());return;case"aliyun-arms":us(r),et(e,ms());return;case"tingyun":gs(r),et(e,fs());return;default:return}}catch(a){throw console.error(`Failed to apply monitoring selection "${t}":`,a),new Error(`Monitoring scaffold for "${t}" failed \u2014 see stderr for details.`)}}import Ot from"fs";import ys from"path";async function Nn(e,t){let o=ys.join(e,"package.json");if(Ot.existsSync(o)){let n=Ot.readFileSync(o,"utf8"),r=JSON.parse(n);r.name=t,r.version="0.1.0",Ot.writeFileSync(o,JSON.stringify(r,null,2)+`
1825
+ `)}}import G from"fs";import tt from"path";var Ss=["stripe","lemonsqueezy","chinapay","polar"];function xn(e){G.existsSync(e)&&G.rmSync(e,{recursive:!0,force:!0})}function Es(e){switch(e){case"stripe":return["stripe"];case"lemon":return["lemonsqueezy"];case"wechat":case"alipay":return["chinapay"]}}function hs(e,t){let o=tt.join(e,".env.example");G.existsSync(o)?G.appendFileSync(o,`
1826
+ `+t):G.writeFileSync(o,t)}function vs(e){switch(e){case"stripe":return`# Stripe
68
1827
  STRIPE_SECRET_KEY=
69
1828
  STRIPE_WEBHOOK_SECRET=
70
1829
  `;case"lemon":return`# Lemon Squeezy
@@ -79,14 +1838,14 @@ CHINAPAY_WECHAT_ENABLED=true
79
1838
  CHINAPAY_APP_ID=
80
1839
  CHINAPAY_APP_SECRET=
81
1840
  CHINAPAY_ALIPAY_ENABLED=true
82
- `}}async function Ue(e,t){let n=Q.join(e,"packages","billing");if(!C.existsSync(n))return;if(t==="none"){De(n);return}let s=Q.join(n,"src"),r=new Set(Vt(t));for(let p of Ot)r.has(p)||De(Q.join(s,p));let a=Q.join(s,"checkout","types.ts");if(C.existsSync(a)){let p=t==="stripe"?"stripe":t==="lemon"?"lemonsqueezy":"chinapay",i=C.readFileSync(a,"utf8"),o=i.replace(/export type CheckoutProviderType\s*=\s*[^;]+;/,`export type CheckoutProviderType = "${p}";`);o!==i&&C.writeFileSync(a,o)}jt(e,qt(t))}import F from"fs";import G from"path";var Ke=new Set(["siliconflow","volcengine-ark","bailian","moonshot"]),fe=/@sailor:([^\s*]+)/;function Ft(e,t){let{providerIds:n,customEndpoint:s}=t;if(e.startsWith("provider:")){let r=e.slice(9);return{marker:e,keep:n.includes(r)}}if(e==="cn-compatible"){let r=n.some(a=>Ke.has(a));return{marker:e,keep:r||!!s}}if(e.startsWith("cn-instance:")){let r=e.slice(12);return{marker:e,keep:n.includes(r)}}return e==="custom-endpoint"?{marker:e,keep:!!s}:e==="registry-entries"?{marker:e,keep:!0}:{marker:e,keep:!0}}function $e(e){return e.replace(/\s*\/\/\s*@sailor:[^\n]*$/,"")}function Ye(e){return e.replace(/\s*#\s*@sailor:[^\n]*$/,"")}function Me(e,t){let n=e.split(`
83
- `),s=[],r=e.includes("#"),a=0;for(;a<n.length;){let p=n[a],i=p.match(fe);if(!i){s.push(p),a++;continue}let{marker:o,keep:m}=Ft(i[1],t);if(o==="registry-entries"){s.push(r?Ye(p):$e(p)),a++;continue}if(m){let f=r?Ye(p):$e(p);f.trim().length>0&&s.push(f);let E=a+1;for(;E<n.length&&n[E].trim()!==""&&!fe.test(n[E]);)s.push(n[E]),E++;E<n.length&&n[E].trim()===""&&(s.push(n[E]),E++),a=E}else{let f=a+1;for(;f<n.length&&n[f].trim()!==""&&!fe.test(n[f]);)f++;f<n.length&&n[f].trim()===""&&f++,a=f}}return Dt(s.join(`
84
- `),t)}function Dt(e,t){let{providerIds:n,customEndpoint:s}=t,r=new Set;for(let m of n)["openai","anthropic","google","mistral","xai"].includes(m)&&r.add(m),r.add(m.replace(/-/g,"_")),r.add(m);s&&r.add("custom");let a=/createProviderRegistry\(\{([\s\S]*?)\}\)/,p=e.match(a);if(!p)return e;let o=p[1].split(`
85
- `).filter(m=>{let f=m.trim();if(f===""||f.startsWith("//"))return!0;let E=f.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*,?$/);return E?r.has(E[1]):!0}).join(`
86
- `);return e.replace(a,`createProviderRegistry({${o}})`)}function Ut(e,t){let n=G.join(t,"registry.ts.template"),s=F.readFileSync(n,"utf-8");return Me(s,e)}function $t(e,t){let n=G.join(t,".env.example.template"),s=F.readFileSync(n,"utf-8");return Me(s,e)}async function Be(e,t,n){let s=Ut(t,n),r=$t(t,n),a=G.join(e,"packages/ai-sdk/src");await F.promises.mkdir(a,{recursive:!0}),await F.promises.writeFile(G.join(a,"registry.ts"),s),await F.promises.appendFile(G.join(e,".env.example"),`
87
- `+r);let p={ai:"latest"},{providerIds:i,customEndpoint:o}=t;(i.includes("openai")||o||i.some(f=>Ke.has(f)))&&(p["@ai-sdk/openai"]="latest",p["@ai-sdk/openai-compatible"]="latest"),i.includes("anthropic")&&(p["@ai-sdk/anthropic"]="latest"),i.includes("google")&&(p["@ai-sdk/google"]="latest"),i.includes("mistral")&&(p["@ai-sdk/mistral"]="latest"),i.includes("xai")&&(p["@ai-sdk/openai"]="latest");let m=G.join(e,"package.json");if(F.existsSync(m)){let f=JSON.parse(await F.promises.readFile(m,"utf-8"));f.dependencies||(f.dependencies={});for(let[E,K]of Object.entries(p))f.dependencies[E]=K;await F.promises.writeFile(m,JSON.stringify(f,null,2)+`
88
- `)}}import l from"fs";import h from"path";function We(e,t){if(!l.existsSync(e))return;let n=l.readdirSync(e);for(let s of n){let r=h.join(e,s);l.statSync(r).isDirectory()?We(r,t):t(r)}}async function He(e,t){let n=h.join(e,"package.json");if(!l.existsSync(n))return;let s=JSON.parse(l.readFileSync(n,"utf8"));if(t.orm!=="prisma"){let r=h.join(e,"packages","db");if(l.existsSync(r)){let a=h.join(r,"prisma");l.existsSync(a)&&l.rmSync(a,{recursive:!0,force:!0});let p=h.join(r,"package.json");if(l.existsSync(p)){let o=JSON.parse(l.readFileSync(p,"utf8"));o.dependencies&&delete o.dependencies["@prisma/client"],o.devDependencies&&delete o.devDependencies.prisma,l.writeFileSync(p,JSON.stringify(o,null,2)+`
89
- `)}let i=h.join(r,"src","index.ts");l.existsSync(i)&&l.writeFileSync(i,`
1841
+ `}}async function Tn(e,t){let o=tt.join(e,"packages","billing");if(!G.existsSync(o))return;if(t==="none"){xn(o);return}let n=tt.join(o,"src"),r=new Set(Es(t));for(let a of Ss)r.has(a)||xn(tt.join(n,a));let s=tt.join(n,"checkout","types.ts");if(G.existsSync(s)){let a=t==="stripe"?"stripe":t==="lemon"?"lemonsqueezy":"chinapay",c=G.readFileSync(s,"utf8"),i=c.replace(/export type CheckoutProviderType\s*=\s*[^;]+;/,`export type CheckoutProviderType = "${a}";`);i!==c&&G.writeFileSync(s,i)}hs(e,vs(t))}import J from"fs";import Ue from"path";var Un=new Set(["siliconflow","volcengine-ark","bailian","moonshot"]),Ut=/@sailor:([^\s*]+)/;function _s(e,t){let{providerIds:o,customEndpoint:n}=t;if(e.startsWith("provider:")){let r=e.slice(9);return{marker:e,keep:o.includes(r)}}if(e==="cn-compatible"){let r=o.some(s=>Un.has(s));return{marker:e,keep:r||!!n}}if(e.startsWith("cn-instance:")){let r=e.slice(12);return{marker:e,keep:o.includes(r)}}return e==="custom-endpoint"?{marker:e,keep:!!n}:e==="registry-entries"?{marker:e,keep:!0}:{marker:e,keep:!0}}function kn(e){return e.replace(/\s*\/\/\s*@sailor:[^\n]*$/,"")}function On(e){return e.replace(/\s*#\s*@sailor:[^\n]*$/,"")}function Ln(e,t){let o=e.split(`
1842
+ `),n=[],r=e.includes("#"),s=0;for(;s<o.length;){let a=o[s],c=a.match(Ut);if(!c){n.push(a),s++;continue}let{marker:i,keep:d}=_s(c[1],t);if(i==="registry-entries"){n.push(r?On(a):kn(a)),s++;continue}if(d){let m=r?On(a):kn(a);m.trim().length>0&&n.push(m);let E=s+1;for(;E<o.length&&o[E].trim()!==""&&!Ut.test(o[E]);)n.push(o[E]),E++;E<o.length&&o[E].trim()===""&&(n.push(o[E]),E++),s=E}else{let m=s+1;for(;m<o.length&&o[m].trim()!==""&&!Ut.test(o[m]);)m++;m<o.length&&o[m].trim()===""&&m++,s=m}}return Ps(n.join(`
1843
+ `),t)}function Ps(e,t){let{providerIds:o,customEndpoint:n}=t,r=new Set;for(let d of o)["openai","anthropic","google","mistral","xai"].includes(d)&&r.add(d),r.add(d.replace(/-/g,"_")),r.add(d);n&&r.add("custom");let s=/createProviderRegistry\(\{([\s\S]*?)\}\)/,a=e.match(s);if(!a)return e;let i=a[1].split(`
1844
+ `).filter(d=>{let m=d.trim();if(m===""||m.startsWith("//"))return!0;let E=m.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*,?$/);return E?r.has(E[1]):!0}).join(`
1845
+ `);return e.replace(s,`createProviderRegistry({${i}})`)}function Is(e,t){let o=Ue.join(t,"registry.ts.template"),n=J.readFileSync(o,"utf-8");return Ln(n,e)}function Cs(e,t){let o=Ue.join(t,".env.example.template"),n=J.readFileSync(o,"utf-8");return Ln(n,e)}async function Mn(e,t,o){let n=Is(t,o),r=Cs(t,o),s=Ue.join(e,"packages/ai-sdk/src");await J.promises.mkdir(s,{recursive:!0}),await J.promises.writeFile(Ue.join(s,"registry.ts"),n),await J.promises.appendFile(Ue.join(e,".env.example"),`
1846
+ `+r);let a={ai:"latest"},{providerIds:c,customEndpoint:i}=t;(c.includes("openai")||i||c.some(m=>Un.has(m)))&&(a["@ai-sdk/openai"]="latest",a["@ai-sdk/openai-compatible"]="latest"),c.includes("anthropic")&&(a["@ai-sdk/anthropic"]="latest"),c.includes("google")&&(a["@ai-sdk/google"]="latest"),c.includes("mistral")&&(a["@ai-sdk/mistral"]="latest"),c.includes("xai")&&(a["@ai-sdk/openai"]="latest");let d=Ue.join(e,"package.json");if(J.existsSync(d)){let m=JSON.parse(await J.promises.readFile(d,"utf-8"));m.dependencies||(m.dependencies={});for(let[E,Pe]of Object.entries(a))m.dependencies[E]=Pe;await J.promises.writeFile(d,JSON.stringify(m,null,2)+`
1847
+ `)}}import u from"fs";import h from"path";function Dn(e,t){if(!u.existsSync(e))return;let o=u.readdirSync(e);for(let n of o){let r=h.join(e,n);u.statSync(r).isDirectory()?Dn(r,t):t(r)}}async function jn(e,t){let o=h.join(e,"package.json");if(!u.existsSync(o))return;let n=JSON.parse(u.readFileSync(o,"utf8"));if(t.orm!=="prisma"){let r=h.join(e,"packages","db");if(u.existsSync(r)){let s=h.join(r,"prisma");u.existsSync(s)&&u.rmSync(s,{recursive:!0,force:!0});let a=h.join(r,"package.json");if(u.existsSync(a)){let i=JSON.parse(u.readFileSync(a,"utf8"));i.dependencies&&delete i.dependencies["@prisma/client"],i.devDependencies&&delete i.devDependencies.prisma,u.writeFileSync(a,JSON.stringify(i,null,2)+`
1848
+ `)}let c=h.join(r,"src","index.ts");u.existsSync(c)&&u.writeFileSync(c,`
90
1849
  export const prisma = {} as any;
91
1850
  export const db = {} as any;
92
1851
  export type PrismaClient = any;
@@ -132,7 +1891,7 @@ export type UsageAggregate = any;
132
1891
  export type UsageLedgerEntry = any;
133
1892
  export type UsageType = any;
134
1893
  export type UsageLedgerSource = any;
135
- `)}s.scripts?.postinstall&&(s.scripts.postinstall=s.scripts.postinstall.replace(/prisma generate/g,'echo "ORM Disabled"'))}if(!t.i18n){let r=h.join(e,"apps");if(l.existsSync(r)){let a=l.readdirSync(r);for(let p of a){let i=h.join(r,p);if(!l.statSync(i).isDirectory())continue;let o=h.join(i,"src","app");if(!l.existsSync(o))continue;["[lang]","[locale]"].forEach(g=>{let c=h.join(o,g);if(l.existsSync(c)){let b=l.readdirSync(c);for(let L of b)l.renameSync(h.join(c,L),h.join(o,L));l.rmdirSync(c)}}),["src/middleware.ts","middleware.ts"].forEach(g=>{let c=h.join(i,g);l.existsSync(c)&&l.rmSync(c)});let m=h.join(i,"src","lib");l.existsSync(m)||l.mkdirSync(m,{recursive:!0});let f=h.join(m,"mock-i18n.ts");l.writeFileSync(f,`
1894
+ `)}n.scripts?.postinstall&&(n.scripts.postinstall=n.scripts.postinstall.replace(/prisma generate/g,'echo "ORM Disabled"'))}if(!t.i18n){let r=h.join(e,"apps");if(u.existsSync(r)){let s=u.readdirSync(r);for(let a of s){let c=h.join(r,a);if(!u.statSync(c).isDirectory())continue;let i=h.join(c,"src","app");if(!u.existsSync(i))continue;["[lang]","[locale]"].forEach(y=>{let l=h.join(i,y);if(u.existsSync(l)){let b=u.readdirSync(l);for(let rt of b)u.renameSync(h.join(l,rt),h.join(i,rt));u.rmdirSync(l)}}),["src/middleware.ts","middleware.ts"].forEach(y=>{let l=h.join(c,y);u.existsSync(l)&&u.rmSync(l)});let d=h.join(c,"src","lib");u.existsSync(d)||u.mkdirSync(d,{recursive:!0});let m=h.join(d,"mock-i18n.ts");u.writeFileSync(m,`
136
1895
  import React from 'react';
137
1896
  export function useTranslations(namespace?: string): any { return (key: string) => key; }
138
1897
  export function useLocale() { return 'en'; }
@@ -150,7 +1909,7 @@ export const useRouter = () => ({ push: (...args: any[]) => {}, replace: (...arg
150
1909
  export default function createMiddleware(...args: any[]) { return (req: any) => req; }
151
1910
  export function createNavigation(...args: any[]) { return { Link, redirect, usePathname, useRouter, getPathname: () => '/' }; }
152
1911
  export function defineRouting<T>(config: T): T { return config; }
153
- `);let K=h.join(m,"mock-i18n.d.ts");l.writeFileSync(K,`declare module '@/lib/mock-i18n' {
1912
+ `);let Pe=h.join(d,"mock-i18n.d.ts");u.writeFileSync(Pe,`declare module '@/lib/mock-i18n' {
154
1913
  export function useTranslations(namespace?: string): any;
155
1914
  export function useLocale(): string;
156
1915
  export const NextIntlClientProvider: React.FC<any>;
@@ -168,30 +1927,981 @@ export function defineRouting<T>(config: T): T { return config; }
168
1927
  export function createNavigation(...args: any[]): any;
169
1928
  export function defineRouting<T>(config: T): T;
170
1929
  }
171
- `),l.writeFileSync(h.join(m,"mock-i18n-index.ts"),`
1930
+ `),u.writeFileSync(h.join(d,"mock-i18n-index.ts"),`
172
1931
  export * from './mock-i18n';
173
1932
  export { default } from './mock-i18n';
174
- `);let M=h.join(i,"tsconfig.json");if(l.existsSync(M))try{let g=l.readFileSync(M,"utf8");if(g.includes('"paths": {')){let c=g.replace(/"paths":\s*\{/,`"paths": {
1933
+ `);let Ie=h.join(c,"tsconfig.json");if(u.existsSync(Ie))try{let y=u.readFileSync(Ie,"utf8");if(y.includes('"paths": {')){let l=y.replace(/"paths":\s*\{/,`"paths": {
175
1934
  "next-intl": ["./src/lib/mock-i18n"],
176
- "next-intl/*": ["./src/lib/mock-i18n"],`);l.writeFileSync(M,c)}}catch{}let J=h.join(i,"src");l.existsSync(J)&&We(J,g=>{if(!g.endsWith(".ts")&&!g.endsWith(".tsx"))return;let c=l.readFileSync(g,"utf8"),b=!1;g.endsWith("src/app/layout.tsx")&&c.includes("../providers")&&(c=c.replace(/from\s+['"]\.\.\/providers['"]/g,"from './providers'"),b=!0),c=c.replace(/params:\s*Promise<\{[^}]*(lang|locale)[^}]*\}>/g,"params: any"),c=c.replace(/\(\{\s*params\s*\}\s*:\s*\{\s*params:\s*any\s*\}\)/g,"({ params }: any)"),c.match(/locale\s*:\s*any/)&&(c=c.replace(/locale\s*:\s*any/g,"locale: string"),b=!0),c.match(/lang\s*:\s*any/)&&(c=c.replace(/lang\s*:\s*any/g,"lang: string"),b=!0),c.match(/requestLocale\s*:\s*any/)&&(c=c.replace(/requestLocale\s*:\s*any/g,"requestLocale: string"),b=!0),c.match(/\(locale\s*=\s*(.*)\)/)&&(c=c.replace(/\(locale\s*=\s*(.*)\)/g,"() "),b=!0),c.match(/export\s+default\s+async\s+function\s+LangLayout/)&&(c=c.replace(/export\s+default\s+async\s+function\s+LangLayout\s*\(\s*\{\s*children\s*,\s*params\s*\}\s*:\s*LangLayoutProps\s*\)/g,"export default async function LangLayout({ children }: { children: React.ReactNode })"),b=!0),(g.endsWith("src/app/layout.tsx")||g.endsWith("src/app/sitemap.ts")||g.endsWith("src/app/robots.ts")||g.endsWith("src/components/ui/locale-switcher.tsx")||g.endsWith("src/app/(marketing)/blog/[slug]/page.tsx"))&&(c=c.replace(/const\s+\{\s*lang\s*\}\s*=\s*(await\s+)?params;?/g,'const lang = "en";'),c=c.replace(/const\s+\{\s*locale\s*\}\s*=\s*(await\s+)?params;?/g,'const locale = "en";'),c=c.replace(/const\s+locale\s*=\s*lang\s+as\s+Locale;?/g,'const locale = "en";'),c=c.replace(/\(l\)\s*=>/g,"(l: any) =>"),b=!0),g.endsWith("src/i18n/request.ts")&&(c=c.replace(/async\s*\(\{\s*requestLocale\s*\}\)/g,"async ({ requestLocale }: any)"),b=!0),(c.match(/locale\s*:/)||c.match(/lang\s*:/))&&(b=!0),b&&l.writeFileSync(g,c)});let B=h.join(i,"package.json");if(l.existsSync(B)){let g=JSON.parse(l.readFileSync(B,"utf8"));g.dependencies&&delete g.dependencies["next-intl"],l.writeFileSync(B,JSON.stringify(g,null,2)+`
177
- `)}}}}l.writeFileSync(n,JSON.stringify(s,null,2)+`
178
- `)}var ze="create-sailor";function u(e,t){e&&process.stdout.write(JSON.stringify(t)+`
179
- `)}function Bt(){let e=process.env.npm_config_user_agent??"";return e.startsWith("pnpm")?"pnpm":e.startsWith("yarn")?"yarn":e.startsWith("bun")?"bun":"npm"}function Ge(e){switch(e){case"postgres":case"postgresql":return"postgresql";case"mysql":return"mysql";case"sqlite":return"sqlite";case"none":return"none";default:return"postgresql"}}function Je(e){return e==="drizzle"?"drizzle":e==="none"?"none":"prisma"}function Xe(e){return e==="lemon"||e==="lemonsqueezy"?"lemonsqueezy":e==="none"?"none":"stripe"}function Ze(e){if(!e)return"stripe";let t=e.toLowerCase();return t==="lemon"||t==="lemonsqueezy"?"lemon":t==="wechat"?"wechat":t==="alipay"?"alipay":t==="none"?"none":"stripe"}function Qe(e){if(!e)return"clerk";let t=e.toLowerCase();return t==="betterauth"||t==="better-auth"?"betterauth":t==="none"?"none":"clerk"}function et(e){if(!e)return["openai"];let t=e.split(",").map(n=>n.trim().toLowerCase());return t.includes("none")?[]:t}var tt={mintlify:"Mintlify",docusaurus:"Docusaurus",nextra:"Nextra",vitepress:"VitePress"};function nt(e,t){if(!e)return"fumadocs";let n=e.toLowerCase();if(n==="fumadocs"||n==="none")return n;if(n in tt){let s=tt[n];return t?u(!0,{event:"notice",kind:"docs-fallback",requested:n,effective:"fumadocs"}):process.stdout.write(A.yellow(`\u26A0 ${s} support is coming in v1.2. Falling back to fumadocs.
180
- `)+A.dim(` Track progress: https://github.com/Nebutra/Nebutra-Sailor/issues
181
- `)),"fumadocs"}return"fumadocs"}function st(e){switch(e){case"vercel":return"vercel";case"railway":return"railway";case"cloudflare":return"cloudflare";case"selfhost":return"selfhost";case"none":return"none";default:return"vercel"}}async function Wt(){let e=new Kt;e.name(ze).description("Nebutra-Sailor \u2014 AI-Native SaaS template").version(U,"-v, --version").helpOption(!1).argument("[name]","project directory",void 0).option("-p, --pm <id>","npm | pnpm | yarn | bun").option("--orm <id>","prisma | drizzle | none").option("--db <id>","postgres | mysql | sqlite | none").option("--auth <id>","clerk | betterauth | none").option("--payment <id>","stripe | lemon | wechat | alipay | none").option("--ai <ids>","comma-separated provider ids").option("--deploy <target>","vercel | railway | cloudflare | selfhost").option("--docs <id>","fumadocs | mintlify | docusaurus | nextra | vitepress | none").option("--i18n","enable i18n").option("--no-i18n","disable i18n").option("--no-install","skip package install").option("--no-git","skip git init").option("-y, --yes","accept all defaults (non-interactive)").option("--dry-run","preview actions without writing files").option("--json","machine-readable output").option("--no-color","disable color output").option("-h, --help","show help"),e.parse(process.argv);let t=e.opts(),[n]=e.args;t.help&&(Ee(),process.exit(0));let s=!!t.json,r=!!t.dryRun,a=!!t.yes,p=a||!process.stdin.isTTY;s||ve(),u(s,{event:"start",version:U});let i=n??(a?"./my-saas-app":void 0),o;if(i)o=i;else if(p)o="./my-saas-app";else{let d=await v.group({name:()=>v.text({message:"Where should we create your project?",placeholder:"./my-saas-app",defaultValue:"./my-saas-app",validate:y=>{if(y.length===0)return"Please enter a path."}})},{onCancel:()=>{process.stdout.write(A.red(`\u2718 Cancelled
182
- `)),process.exit(130)}});o=String(d.name)}let m=ye.basename(ye.resolve(o)),f=t.pm??Bt(),E=!!t.orm,K=!!t.db,M=!!t.auth,J=!!t.payment,B=!!t.ai,g=!!t.deploy,c=!!t.docs,b=t.i18n!==void 0,L,R,pe,T,N,D,W,O,w,ee;if(p)L=Je(t.orm),R=Ge(t.db),pe=Xe(t.payment),T=Ze(t.payment),N=Qe(t.auth),D=et(t.ai),O=st(t.deploy),w=nt(t.docs,s),ee=b?!!t.i18n:!0;else{let d={};if(E||(d.orm=()=>v.select({message:"Which ORM?",options:[{value:"prisma",label:"Prisma"},{value:"drizzle",label:"Drizzle"},{value:"none",label:"None"}],initialValue:"prisma"})),K||(d.database=()=>v.select({message:"Database?",options:[{value:"postgresql",label:"PostgreSQL"},{value:"mysql",label:"MySQL"},{value:"sqlite",label:"SQLite"},{value:"none",label:"None"}],initialValue:"postgresql"})),M||(d.auth=()=>v.select({message:"Auth provider?",options:[{value:"clerk",label:"Clerk"},{value:"betterauth",label:"Better Auth"},{value:"none",label:"None"}],initialValue:"clerk"})),J||(d.payment=()=>v.select({message:"Payment?",options:[{value:"stripe",label:"Stripe"},{value:"lemon",label:"Lemon Squeezy"},{value:"wechat",label:"WeChat Pay"},{value:"alipay",label:"Alipay"},{value:"none",label:"None"}],initialValue:"stripe"})),!B){let se=Array.from(new Set(ie.map(P=>P.category)));d.aiCategories=()=>v.multiselect({message:"Which AI Provider categories do you want to explore? (Select multiple)",options:se.map(P=>({value:P,label:P})),initialValues:["\u76F4\u63A5\u5B9E\u9A8C\u5BA4","\u7EDF\u4E00\u7F51\u5173"]}),d.aiProviders=({results:P})=>{let I=P.aiCategories||[],re=ie.filter(X=>I.includes(X.category)).map(X=>({value:X.id,label:`[${X.category}] ${X.name}`}));return re.push({value:"custom",label:"[\u81EA\u5B9A\u4E49] Custom OpenAI-compatible endpoint"}),v.multiselect({message:"Select AI Providers (Space to select, Enter to submit)",options:re,initialValues:["openai","anthropic"],required:!1})},d.enableCustom=({results:P})=>P.aiProviders?.includes("custom")?v.confirm({message:"Configure custom OpenAI-compatible endpoint?",initialValue:!0}):Promise.resolve(!1),d.customAiName=({results:P})=>P.enableCustom?v.text({message:"Custom endpoint name (e.g. proxy, local):",defaultValue:"custom",placeholder:"custom"}):Promise.resolve(void 0),d.customAiBaseUrl=({results:P})=>P.enableCustom?v.text({message:"Custom endpoint base URL (e.g. https://api.proxy.com/v1):",validate:I=>{if(I.length===0)return"Base URL is required."}}):Promise.resolve(void 0),d.customAiApiKeyEnv=({results:P})=>P.enableCustom?v.text({message:"Environment variable name for the API Key:",defaultValue:"CUSTOM_AI_API_KEY",placeholder:"CUSTOM_AI_API_KEY"}):Promise.resolve(void 0)}g||(d.deployTarget=()=>v.select({message:"Where will you deploy?",options:[{value:"vercel",label:"Vercel"},{value:"railway",label:"Railway"},{value:"cloudflare",label:"Cloudflare Workers"},{value:"selfhost",label:"Self-host / Docker"},{value:"none",label:"None for now"}],initialValue:"vercel"})),c||(d.docs=()=>v.select({message:"What documentation framework? (affects `apps/docs`)",options:[{value:"fumadocs",label:"Fumadocs \u2014 Next.js monorepo native (Recommended)"},{value:"mintlify",label:"Mintlify \u2014 managed docs SaaS (Coming in v1.2)"},{value:"docusaurus",label:"Docusaurus \u2014 Meta's framework (Coming in v1.2)"},{value:"nextra",label:"Nextra \u2014 Next.js minimal (Coming in v1.2)"},{value:"vitepress",label:"VitePress \u2014 Vue ecosystem, separate repo (Experimental)"},{value:"none",label:"None \u2014 skip docs"}],initialValue:"fumadocs"})),b||(d.i18n=()=>v.confirm({message:"Enable i18n?",initialValue:!0}));let y=Object.keys(d).length>0?await v.group(d,{onCancel:()=>{process.stdout.write(A.red(`\u2718 Cancelled
183
- `)),process.exit(130)}}):{};L=E?Je(t.orm):y.orm,R=K?Ge(t.db):y.database;let k=J?t.payment:y.payment;pe=Xe(k),T=Ze(k),N=Qe(M?t.auth:y.auth),D=B?et(t.ai):y.aiProviders?.filter(se=>se!=="custom")??[],y.enableCustom&&(W={name:y.customAiName,baseURL:y.customAiBaseUrl,apiKeyEnvName:y.customAiApiKeyEnv}),O=g?st(t.deploy):y.deployTarget,w=nt(c?t.docs:y.docs,s),ee=b?!!t.i18n:!!y.i18n}let te={orm:L,database:R,payment:pe,aiProviders:D,customAiEndpoint:W,deployTarget:O,docs:w,i18n:ee},ne=[["Project name",m],["Auth",N],["ORM",L],["Database",R],["Payment",T],["AI",Array.isArray(D)?D.join(", ")+(W?", custom":""):String(D)],["Deploy Target",O],["Docs Framework",w]];s?ne.forEach(([d,y],k)=>{u(!0,{event:"step",step:d,value:y,index:k+1,total:ne.length})}):ne.forEach(([d,y],k)=>{be({index:k+1,total:ne.length,label:d,value:y})});let rt={databaseUrl:"postgresql://postgres:postgres@localhost:5432/nebutra",clerkPublishable:"",clerkSecret:""},ot=Date.now();if(r){let d=D.length,y=[`clone template \u2192 ${o}`,"write nebutra.config.json",`prune template (orm=${L}, i18n=${ee})`,`auth \u2192 ${N==="none"?"skip (remove packages/auth)":`configure ${N}`}`,`db \u2192 ${R==="none"?"skip (remove packages/db)":`configure Prisma for ${R}`}`,`payment \u2192 ${T==="none"?"skip (remove packages/billing)":`configure ${T}`}`,...w!=="none"?[`docs \u2192 scaffold apps/docs (${w==="fumadocs"?"fumadocs":`${w} \u2192 fumadocs fallback`})`]:[],...d>0||W?[`ai-providers \u2192 generate registry.ts + env (${d}${W?" + custom":""} provider${d===1&&!W?"":"s"})`]:[],...O!=="none"?[`deploy \u2192 inject ${O} config`]:[],"inject .env.local",t.install===!1?"skip install":`run ${f} install`,t.git===!1?"skip git init":"run git init"];if(s)y.forEach(k=>u(!0,{event:"plan",action:k})),u(!0,{event:"done",dryRun:!0});else{process.stdout.write(`
184
- `+A.bold(`Dry run \u2014 planned actions:
185
- `));for(let k of y)process.stdout.write(` \u2022 ${k}
186
- `);process.stdout.write(A.dim(`
1935
+ "next-intl/*": ["./src/lib/mock-i18n"],`);u.writeFileSync(Ie,l)}}catch{}let Le=h.join(c,"src");u.existsSync(Le)&&Dn(Le,y=>{if(!y.endsWith(".ts")&&!y.endsWith(".tsx"))return;let l=u.readFileSync(y,"utf8"),b=!1;y.endsWith("src/app/layout.tsx")&&l.includes("../providers")&&(l=l.replace(/from\s+['"]\.\.\/providers['"]/g,"from './providers'"),b=!0),l=l.replace(/params:\s*Promise<\{[^}]*(lang|locale)[^}]*\}>/g,"params: any"),l=l.replace(/\(\{\s*params\s*\}\s*:\s*\{\s*params:\s*any\s*\}\)/g,"({ params }: any)"),l.match(/locale\s*:\s*any/)&&(l=l.replace(/locale\s*:\s*any/g,"locale: string"),b=!0),l.match(/lang\s*:\s*any/)&&(l=l.replace(/lang\s*:\s*any/g,"lang: string"),b=!0),l.match(/requestLocale\s*:\s*any/)&&(l=l.replace(/requestLocale\s*:\s*any/g,"requestLocale: string"),b=!0),l.match(/\(locale\s*=\s*(.*)\)/)&&(l=l.replace(/\(locale\s*=\s*(.*)\)/g,"() "),b=!0),l.match(/export\s+default\s+async\s+function\s+LangLayout/)&&(l=l.replace(/export\s+default\s+async\s+function\s+LangLayout\s*\(\s*\{\s*children\s*,\s*params\s*\}\s*:\s*LangLayoutProps\s*\)/g,"export default async function LangLayout({ children }: { children: React.ReactNode })"),b=!0),(y.endsWith("src/app/layout.tsx")||y.endsWith("src/app/sitemap.ts")||y.endsWith("src/app/robots.ts")||y.endsWith("src/components/ui/locale-switcher.tsx")||y.endsWith("src/app/(marketing)/blog/[slug]/page.tsx"))&&(l=l.replace(/const\s+\{\s*lang\s*\}\s*=\s*(await\s+)?params;?/g,'const lang = "en";'),l=l.replace(/const\s+\{\s*locale\s*\}\s*=\s*(await\s+)?params;?/g,'const locale = "en";'),l=l.replace(/const\s+locale\s*=\s*lang\s+as\s+Locale;?/g,'const locale = "en";'),l=l.replace(/\(l\)\s*=>/g,"(l: any) =>"),b=!0),y.endsWith("src/i18n/request.ts")&&(l=l.replace(/async\s*\(\{\s*requestLocale\s*\}\)/g,"async ({ requestLocale }: any)"),b=!0),(l.match(/locale\s*:/)||l.match(/lang\s*:/))&&(b=!0),b&&u.writeFileSync(y,l)});let Me=h.join(c,"package.json");if(u.existsSync(Me)){let y=JSON.parse(u.readFileSync(Me,"utf8"));y.dependencies&&delete y.dependencies["next-intl"],u.writeFileSync(Me,JSON.stringify(y,null,2)+`
1936
+ `)}}}}u.writeFileSync(o,JSON.stringify(n,null,2)+`
1937
+ `)}import Z from"fs";import fe from"path";function bs(e){let t=[fe.join(e,"packages","db"),fe.join(e,"apps","web")];for(let o of t){let n=fe.join(o,"prisma","schema.prisma");if(Z.existsSync(n))return{packageDir:o,seedPath:fe.join(o,"prisma","seed.ts"),packageJsonPath:fe.join(o,"package.json")}}return null}function As(e){try{let t=Z.readFileSync(e,"utf8"),o=new Set,n=/^\s*model\s+(\w+)\s*\{/gm,r=n.exec(t);for(;r;)o.add(r[1]),r=n.exec(t);return o}catch{return new Set}}function Rs(e,t){return!t||e==="none"?` // Skipping admin user \u2014 auth=${JSON.stringify(e)} (or no User model).`:e==="clerk"?` try {
1938
+ const admin = await prisma.user.create({
1939
+ data: {
1940
+ clerkId: "user_mock_admin",
1941
+ email: "admin@example.com",
1942
+ name: "Admin",
1943
+ },
1944
+ });
1945
+ console.log("\\u2713 Created admin user:", admin.email);
1946
+ } catch (err) {
1947
+ console.warn("\\u26A0 Admin user creation skipped:", (err as Error).message);
1948
+ }`:e==="betterauth"?` try {
1949
+ // NOTE: in production hash passwords with the project's password hasher
1950
+ // (e.g. argon2id via @nebutra/auth). This is a scaffold placeholder.
1951
+ const admin = await prisma.user.create({
1952
+ data: {
1953
+ email: "admin@example.com",
1954
+ name: "Admin",
1955
+ hashedPassword: "CHANGE_ME_scaffold_placeholder",
1956
+ },
1957
+ });
1958
+ console.log("\\u2713 Created admin user:", admin.email);
1959
+ } catch (err) {
1960
+ console.warn("\\u26A0 Admin user creation skipped:", (err as Error).message);
1961
+ }`:` try {
1962
+ const admin = await prisma.user.create({
1963
+ data: { email: "admin@example.com", name: "Admin" },
1964
+ });
1965
+ console.log("\\u2713 Created admin user:", admin.email);
1966
+ } catch (err) {
1967
+ console.warn("\\u26A0 Admin user creation skipped:", (err as Error).message);
1968
+ }`}function ws(e,t){let o=t.has("Tenant"),n=t.has("User"),r=t.has("Project"),s=Rs(e,n),a=o?` let tenants: Array<{ id: string; name: string }> = [];
1969
+ try {
1970
+ tenants = await Promise.all([
1971
+ prisma.tenant.create({
1972
+ data: { name: "Acme Corp", slug: "acme", plan: "pro" },
1973
+ }),
1974
+ prisma.tenant.create({
1975
+ data: { name: "Widget Co", slug: "widget", plan: "free" },
1976
+ }),
1977
+ prisma.tenant.create({
1978
+ data: { name: "Startup Inc", slug: "startup", plan: "startup" },
1979
+ }),
1980
+ ]);
1981
+ console.log(\`\\u2713 Created \${tenants.length} tenants\`);
1982
+ } catch (err) {
1983
+ console.warn("\\u26A0 Tenant creation skipped:", (err as Error).message);
1984
+ }`:` const tenants: Array<{ id: string; name: string }> = [];
1985
+ console.log("\\u2139 No Tenant model in schema \u2014 skipping tenant seed.");`,c=r&&o?` for (const tenant of tenants) {
1986
+ try {
1987
+ await prisma.project.createMany({
1988
+ data: [
1989
+ { name: \`\${tenant.name} - Sample Project 1\`, tenantId: tenant.id },
1990
+ { name: \`\${tenant.name} - Sample Project 2\`, tenantId: tenant.id },
1991
+ ],
1992
+ });
1993
+ } catch (err) {
1994
+ console.warn(
1995
+ \`\\u26A0 Project seed skipped for \${tenant.name}:\`,
1996
+ (err as Error).message,
1997
+ );
1998
+ }
1999
+ }`:' console.log("\\u2139 No Project model (or no tenants) \u2014 skipping project seed.");';return`/**
2000
+ * Database seed \u2014 generated by create-sailor.
2001
+ *
2002
+ * Run with: pnpm db:seed
2003
+ *
2004
+ * Auth provider: ${e}
2005
+ * Detected models at scaffold time: ${[...t].sort().join(", ")||"(none)"}
2006
+ *
2007
+ * Each block is wrapped in try/catch so evolving the schema does not
2008
+ * break the seed \u2014 missing models log a warning and move on.
2009
+ */
2010
+ import { PrismaClient } from "@prisma/client";
2011
+
2012
+ const prisma = new PrismaClient();
2013
+
2014
+ async function main(): Promise<void> {
2015
+ console.log("\\uD83C\\uDF31 Seeding database...");
2016
+
2017
+ // --- Tenants ---------------------------------------------------------
2018
+ ${a}
2019
+
2020
+ // --- Admin user ------------------------------------------------------
2021
+ ${s}
2022
+
2023
+ // --- Sample projects per tenant --------------------------------------
2024
+ ${c}
2025
+
2026
+ console.log("\\u2713 Seed complete");
2027
+ }
2028
+
2029
+ main()
2030
+ .catch((err) => {
2031
+ console.error("Seed failed:", err);
2032
+ process.exitCode = 1;
2033
+ })
2034
+ .finally(async () => {
2035
+ await prisma.$disconnect();
2036
+ });
2037
+ `}function Ns(e){if(Z.existsSync(e))try{let t=Z.readFileSync(e,"utf8"),o=JSON.parse(t),n=o.scripts??{},r=!1;n["db:seed"]||(n["db:seed"]="prisma db seed",r=!0);let s=o.prisma??{};if(s.seed||(s.seed="tsx prisma/seed.ts",r=!0),!r)return;let a={...o,scripts:n,prisma:s};Z.writeFileSync(e,JSON.stringify(a,null,2)+`
2038
+ `)}catch(t){let o=t instanceof Error?t.message:String(t);console.warn(`[create-sailor] Failed to patch seed scripts in ${e}: ${o}`)}}async function Fn(e,t){let o=bs(e);if(o)try{let n=fe.join(o.packageDir,"prisma","schema.prisma"),r=As(n),s=ws(t,r),a=fe.dirname(o.seedPath);Z.existsSync(a)||Z.mkdirSync(a,{recursive:!0}),Z.writeFileSync(o.seedPath,s),Ns(o.packageJsonPath)}catch(n){let r=n instanceof Error?n.message:String(n);throw console.error(`[create-sailor] Failed to generate seed data: ${r}`),new Error(`Unable to write prisma/seed.ts: ${r}`)}}import ee from"fs";import ye from"path";var ft=[{id:"twilio",name:"Twilio",region:"global",envVars:["TWILIO_ACCOUNT_SID","TWILIO_AUTH_TOKEN","TWILIO_FROM_NUMBER"],docs:"https://www.twilio.com/docs/sms",pkgs:["twilio"]},{id:"messagebird",name:"MessageBird (Bird)",region:"global",envVars:["MESSAGEBIRD_ACCESS_KEY","MESSAGEBIRD_ORIGINATOR"],docs:"https://developers.messagebird.com/api/sms-messaging/",pkgs:["messagebird"]},{id:"plivo",name:"Plivo",region:"global",envVars:["PLIVO_AUTH_ID","PLIVO_AUTH_TOKEN","PLIVO_FROM_NUMBER"],docs:"https://www.plivo.com/docs/sms/",pkgs:["plivo"]},{id:"aliyun-sms",name:"\u963F\u91CC\u4E91\u77ED\u4FE1 Aliyun SMS",region:"cn",envVars:["ALIYUN_SMS_ACCESS_KEY","ALIYUN_SMS_ACCESS_SECRET","ALIYUN_SMS_SIGN_NAME","ALIYUN_SMS_TEMPLATE_CODE","ALIYUN_SMS_ENDPOINT"],docs:"https://help.aliyun.com/product/44282.html",pkgs:["@alicloud/dysmsapi20170525","@alicloud/openapi-client"]},{id:"tencent-sms",name:"\u817E\u8BAF\u4E91\u77ED\u4FE1 Tencent SMS",region:"cn",envVars:["TENCENT_SMS_SECRET_ID","TENCENT_SMS_SECRET_KEY","TENCENT_SMS_SDK_APP_ID","TENCENT_SMS_SIGN_NAME","TENCENT_SMS_TEMPLATE_ID","TENCENT_SMS_REGION"],docs:"https://cloud.tencent.com/document/product/382",pkgs:["tencentcloud-sdk-nodejs-sms"]},{id:"yunpian",name:"\u4E91\u7247 Yunpian",region:"cn",envVars:["YUNPIAN_API_KEY","YUNPIAN_SIGN"],docs:"https://www.yunpian.com/official/document/sms/zh_CN/introduction_product_intro",pkgs:["yunpian-nodejs-sdk"]},{id:"none",name:"None",region:"both",envVars:[],docs:""}],qa=ft.reduce((e,t)=>(e[t.region]||(e[t.region]=[]),e[t.region].push(t),e),{});function Vn(e){return ft.find(t=>t.id===e)}var xs="@nebutra/sms",Ts={twilio:"^5.3.6",messagebird:"^4.0.1",plivo:"^4.73.0","@alicloud/dysmsapi20170525":"^2.0.24","@alicloud/openapi-client":"^0.4.12","tencentcloud-sdk-nodejs-sms":"^4.0.970","yunpian-nodejs-sdk":"^0.0.2"};function Lt(e,t){ee.mkdirSync(ye.dirname(e),{recursive:!0}),ee.writeFileSync(e,t)}function ks(e,t){let o=ye.join(e,".env.example");ee.existsSync(o)?ee.appendFileSync(o,`
2039
+ `+t):ee.writeFileSync(o,t)}function Os(e){if(e.envVars.length===0)return"";let t=`# SMS provider: ${e.name}`,o=e.envVars.map(n=>`${n}=`).join(`
2040
+ `);return`${t}
2041
+ ${o}
2042
+ `}var Us=`/**
2043
+ * Inputs for the \`sendSms\` wrapper. Keep this surface portable across
2044
+ * providers \u2014 provider-specific options belong in the wrapper itself.
2045
+ */
2046
+ export interface SendSmsInput {
2047
+ /** E.164 (+1415...) for global providers, or 11-digit string for CN providers. */
2048
+ to: string;
2049
+ /** Template code / ID registered with the provider. */
2050
+ templateCode?: string;
2051
+ /** Template variables \u2014 usually { code: "123456" } or { name, orderId, ... }. */
2052
+ params?: Record<string, string | number>;
2053
+ /** Free-form text body (only honored by providers that allow ad-hoc content). */
2054
+ body?: string;
2055
+ }
2056
+
2057
+ export interface SendSmsResult {
2058
+ messageId?: string;
2059
+ provider: string;
2060
+ raw?: unknown;
2061
+ }
2062
+ `;function Ls(){return`import twilio from "twilio";
2063
+ import type { SendSmsInput, SendSmsResult } from "./types.js";
2064
+
2065
+ let client: ReturnType<typeof twilio> | null = null;
2066
+
2067
+ function getClient() {
2068
+ if (client) return client;
2069
+ const sid = process.env.TWILIO_ACCOUNT_SID;
2070
+ const token = process.env.TWILIO_AUTH_TOKEN;
2071
+ if (!sid || !token) {
2072
+ throw new Error("TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN must be set");
2073
+ }
2074
+ client = twilio(sid, token);
2075
+ return client;
2076
+ }
2077
+
2078
+ export async function sendSms(input: SendSmsInput): Promise<SendSmsResult> {
2079
+ const from = process.env.TWILIO_FROM_NUMBER;
2080
+ if (!from) throw new Error("TWILIO_FROM_NUMBER must be set");
2081
+
2082
+ const body =
2083
+ input.body ??
2084
+ Object.values(input.params ?? {}).join(" ") ??
2085
+ "";
2086
+
2087
+ const message = await getClient().messages.create({
2088
+ to: input.to,
2089
+ from,
2090
+ body,
2091
+ });
2092
+
2093
+ return { messageId: message.sid, provider: "twilio", raw: message };
2094
+ }
2095
+
2096
+ export type { SendSmsInput, SendSmsResult } from "./types.js";
2097
+ `}function Ms(){return`import messagebird from "messagebird";
2098
+ import type { SendSmsInput, SendSmsResult } from "./types.js";
2099
+
2100
+ let client: ReturnType<typeof messagebird> | null = null;
2101
+
2102
+ function getClient() {
2103
+ if (client) return client;
2104
+ const key = process.env.MESSAGEBIRD_ACCESS_KEY;
2105
+ if (!key) throw new Error("MESSAGEBIRD_ACCESS_KEY must be set");
2106
+ client = messagebird(key);
2107
+ return client;
2108
+ }
2109
+
2110
+ export async function sendSms(input: SendSmsInput): Promise<SendSmsResult> {
2111
+ const originator = process.env.MESSAGEBIRD_ORIGINATOR;
2112
+ if (!originator) throw new Error("MESSAGEBIRD_ORIGINATOR must be set");
2113
+
2114
+ const body =
2115
+ input.body ??
2116
+ Object.values(input.params ?? {}).join(" ") ??
2117
+ "";
2118
+
2119
+ return await new Promise<SendSmsResult>((resolve, reject) => {
2120
+ getClient().messages.create(
2121
+ { originator, recipients: [input.to], body },
2122
+ (err, response) => {
2123
+ if (err) return reject(err);
2124
+ resolve({
2125
+ messageId: response?.id,
2126
+ provider: "messagebird",
2127
+ raw: response,
2128
+ });
2129
+ },
2130
+ );
2131
+ });
2132
+ }
2133
+
2134
+ export type { SendSmsInput, SendSmsResult } from "./types.js";
2135
+ `}function Ds(){return`import plivo from "plivo";
2136
+ import type { SendSmsInput, SendSmsResult } from "./types.js";
2137
+
2138
+ let client: InstanceType<typeof plivo.Client> | null = null;
2139
+
2140
+ function getClient() {
2141
+ if (client) return client;
2142
+ const authId = process.env.PLIVO_AUTH_ID;
2143
+ const authToken = process.env.PLIVO_AUTH_TOKEN;
2144
+ if (!authId || !authToken) {
2145
+ throw new Error("PLIVO_AUTH_ID and PLIVO_AUTH_TOKEN must be set");
2146
+ }
2147
+ client = new plivo.Client(authId, authToken);
2148
+ return client;
2149
+ }
2150
+
2151
+ export async function sendSms(input: SendSmsInput): Promise<SendSmsResult> {
2152
+ const from = process.env.PLIVO_FROM_NUMBER;
2153
+ if (!from) throw new Error("PLIVO_FROM_NUMBER must be set");
2154
+
2155
+ const body =
2156
+ input.body ??
2157
+ Object.values(input.params ?? {}).join(" ") ??
2158
+ "";
2159
+
2160
+ const response = await getClient().messages.create({
2161
+ src: from,
2162
+ dst: input.to,
2163
+ text: body,
2164
+ });
2165
+
2166
+ return {
2167
+ messageId: response.messageUuid?.[0],
2168
+ provider: "plivo",
2169
+ raw: response,
2170
+ };
2171
+ }
2172
+
2173
+ export type { SendSmsInput, SendSmsResult } from "./types.js";
2174
+ `}function js(){return`import Dysmsapi, { SendSmsRequest } from "@alicloud/dysmsapi20170525";
2175
+ import * as $OpenApi from "@alicloud/openapi-client";
2176
+ import type { SendSmsInput, SendSmsResult } from "./types.js";
2177
+
2178
+ let client: Dysmsapi | null = null;
2179
+
2180
+ function getClient(): Dysmsapi {
2181
+ if (client) return client;
2182
+ const accessKeyId = process.env.ALIYUN_SMS_ACCESS_KEY;
2183
+ const accessKeySecret = process.env.ALIYUN_SMS_ACCESS_SECRET;
2184
+ if (!accessKeyId || !accessKeySecret) {
2185
+ throw new Error(
2186
+ "ALIYUN_SMS_ACCESS_KEY and ALIYUN_SMS_ACCESS_SECRET must be set",
2187
+ );
2188
+ }
2189
+ const config = new $OpenApi.Config({ accessKeyId, accessKeySecret });
2190
+ config.endpoint = process.env.ALIYUN_SMS_ENDPOINT ?? "dysmsapi.aliyuncs.com";
2191
+ client = new Dysmsapi(config);
2192
+ return client;
2193
+ }
2194
+
2195
+ export async function sendSms(input: SendSmsInput): Promise<SendSmsResult> {
2196
+ const signName = process.env.ALIYUN_SMS_SIGN_NAME;
2197
+ const templateCode = input.templateCode ?? process.env.ALIYUN_SMS_TEMPLATE_CODE;
2198
+ if (!signName) throw new Error("ALIYUN_SMS_SIGN_NAME must be set");
2199
+ if (!templateCode) throw new Error("templateCode or ALIYUN_SMS_TEMPLATE_CODE must be provided");
2200
+
2201
+ const req = new SendSmsRequest({
2202
+ phoneNumbers: input.to,
2203
+ signName,
2204
+ templateCode,
2205
+ templateParam: JSON.stringify(input.params ?? {}),
2206
+ });
2207
+
2208
+ const response = await getClient().sendSms(req);
2209
+ const body = response.body;
2210
+
2211
+ if (body?.code !== "OK") {
2212
+ throw new Error(\`Aliyun SMS failed: \${body?.code} \${body?.message}\`);
2213
+ }
2214
+
2215
+ return {
2216
+ messageId: body?.bizId,
2217
+ provider: "aliyun-sms",
2218
+ raw: body,
2219
+ };
2220
+ }
2221
+
2222
+ export type { SendSmsInput, SendSmsResult } from "./types.js";
2223
+ `}function Fs(){return`import * as tencentcloud from "tencentcloud-sdk-nodejs-sms";
2224
+ import type { SendSmsInput, SendSmsResult } from "./types.js";
2225
+
2226
+ const SmsClient = tencentcloud.sms.v20210111.Client;
2227
+ type SmsClientInstance = InstanceType<typeof SmsClient>;
2228
+
2229
+ let client: SmsClientInstance | null = null;
2230
+
2231
+ function getClient(): SmsClientInstance {
2232
+ if (client) return client;
2233
+ const secretId = process.env.TENCENT_SMS_SECRET_ID;
2234
+ const secretKey = process.env.TENCENT_SMS_SECRET_KEY;
2235
+ if (!secretId || !secretKey) {
2236
+ throw new Error(
2237
+ "TENCENT_SMS_SECRET_ID and TENCENT_SMS_SECRET_KEY must be set",
2238
+ );
2239
+ }
2240
+
2241
+ client = new SmsClient({
2242
+ credential: { secretId, secretKey },
2243
+ region: process.env.TENCENT_SMS_REGION ?? "ap-guangzhou",
2244
+ profile: {
2245
+ httpProfile: { endpoint: "sms.tencentcloudapi.com" },
2246
+ },
2247
+ });
2248
+ return client;
2249
+ }
2250
+
2251
+ export async function sendSms(input: SendSmsInput): Promise<SendSmsResult> {
2252
+ const sdkAppId = process.env.TENCENT_SMS_SDK_APP_ID;
2253
+ const signName = process.env.TENCENT_SMS_SIGN_NAME;
2254
+ const templateId = input.templateCode ?? process.env.TENCENT_SMS_TEMPLATE_ID;
2255
+
2256
+ if (!sdkAppId) throw new Error("TENCENT_SMS_SDK_APP_ID must be set");
2257
+ if (!signName) throw new Error("TENCENT_SMS_SIGN_NAME must be set");
2258
+ if (!templateId) {
2259
+ throw new Error("templateCode or TENCENT_SMS_TEMPLATE_ID must be provided");
2260
+ }
2261
+
2262
+ // Tencent expects phone numbers with + prefix (E.164). Also expects ordered
2263
+ // template params as a string array matching the template placeholders.
2264
+ const to = input.to.startsWith("+") ? input.to : \`+\${input.to}\`;
2265
+ const templateParamSet = Object.values(input.params ?? {}).map(String);
2266
+
2267
+ const response = await getClient().SendSms({
2268
+ SmsSdkAppId: sdkAppId,
2269
+ SignName: signName,
2270
+ TemplateId: templateId,
2271
+ PhoneNumberSet: [to],
2272
+ TemplateParamSet: templateParamSet,
2273
+ });
2274
+
2275
+ const first = response.SendStatusSet?.[0];
2276
+ if (first && first.Code !== "Ok") {
2277
+ throw new Error(\`Tencent SMS failed: \${first.Code} \${first.Message}\`);
2278
+ }
2279
+
2280
+ return {
2281
+ messageId: first?.SerialNo,
2282
+ provider: "tencent-sms",
2283
+ raw: response,
2284
+ };
2285
+ }
2286
+
2287
+ export type { SendSmsInput, SendSmsResult } from "./types.js";
2288
+ `}function Vs(){return`// @ts-expect-error \u2014 yunpian-nodejs-sdk ships no type declarations
2289
+ import Yunpian from "yunpian-nodejs-sdk";
2290
+ import type { SendSmsInput, SendSmsResult } from "./types.js";
2291
+
2292
+ let client: { sms: { single_send(opts: unknown): Promise<unknown>; tpl_single_send(opts: unknown): Promise<unknown> } } | null =
2293
+ null;
2294
+
2295
+ function getClient() {
2296
+ if (client) return client;
2297
+ const apiKey = process.env.YUNPIAN_API_KEY;
2298
+ if (!apiKey) throw new Error("YUNPIAN_API_KEY must be set");
2299
+ client = new Yunpian(apiKey);
2300
+ return client!;
2301
+ }
2302
+
2303
+ /**
2304
+ * Yunpian (\u4E91\u7247) supports two modes:
2305
+ * - tpl_single_send: registered template + tpl_value
2306
+ * - single_send: ad-hoc text (must include registered \u7B7E\u540D)
2307
+ */
2308
+ export async function sendSms(input: SendSmsInput): Promise<SendSmsResult> {
2309
+ const sign = process.env.YUNPIAN_SIGN ?? "";
2310
+
2311
+ if (input.templateCode) {
2312
+ const tplValue = Object.entries(input.params ?? {})
2313
+ .map(([k, v]) => \`#\${k}#=\${encodeURIComponent(String(v))}\`)
2314
+ .join("&");
2315
+
2316
+ const raw = await getClient().sms.tpl_single_send({
2317
+ mobile: input.to,
2318
+ tpl_id: input.templateCode,
2319
+ tpl_value: tplValue,
2320
+ });
2321
+
2322
+ return { provider: "yunpian", raw };
2323
+ }
2324
+
2325
+ const text = input.body
2326
+ ? input.body.startsWith("\u3010")
2327
+ ? input.body
2328
+ : \`\${sign}\${input.body}\`
2329
+ : \`\${sign}\${Object.values(input.params ?? {}).join(" ")}\`;
2330
+
2331
+ const raw = await getClient().sms.single_send({
2332
+ mobile: input.to,
2333
+ text,
2334
+ });
2335
+
2336
+ return { provider: "yunpian", raw };
2337
+ }
2338
+
2339
+ export type { SendSmsInput, SendSmsResult } from "./types.js";
2340
+ `}function Ks(e){switch(e){case"twilio":return Ls();case"messagebird":return Ms();case"plivo":return Ds();case"aliyun-sms":return js();case"tencent-sms":return Fs();case"yunpian":return Vs();case"none":return`export {};
2341
+ `}}function Ys(e){let t={};for(let n of e.pkgs??[]){let r=Ts[n];r&&(t[n]=r)}let o={name:xs,version:"0.1.0",private:!0,description:`Transactional SMS via ${e.name}`,type:"module",main:"./dist/index.js",exports:{".":{import:"./dist/index.js",types:"./dist/index.d.ts"}},scripts:{build:"tsup",typecheck:"tsc --noEmit",clean:"rm -rf dist .turbo"},dependencies:t,devDependencies:{tsup:"^8.5.0",typescript:"catalog:"}};return JSON.stringify(o,null,2)+`
2342
+ `}async function Kn(e,t,o="global"){if(t==="none")return;let n=Vn(t);if(!n)throw new Error(`Unknown SMS provider "${t}". Valid ids: ${ft.map(a=>a.id).join(", ")}`);let r=ye.join(e,"packages","sms");if(!ee.existsSync(r))return;let s=ye.join(r,"src");ee.existsSync(s)&&ee.rmSync(s,{recursive:!0,force:!0});try{Lt(ye.join(r,"package.json"),Ys(n)),Lt(ye.join(s,"types.ts"),Us),Lt(ye.join(s,"index.ts"),Ks(n.id)),ks(e,Os(n))}catch(a){throw console.error(`Failed to apply SMS selection "${t}":`,a),new Error(`SMS scaffold for "${t}" failed \u2014 see stderr for details.`)}}import Ee from"fs";import Se from"path";var yt=[{id:"r2",name:"Cloudflare R2",region:"global",envVars:["R2_ACCOUNT_ID","R2_ACCESS_KEY_ID","R2_SECRET_ACCESS_KEY","R2_BUCKET"],docs:"https://developers.cloudflare.com/r2/api/s3/api/",pkg:"@aws-sdk/client-s3",pkgVersion:"^3.658.0"},{id:"s3",name:"AWS S3",region:"global",envVars:["AWS_ACCESS_KEY_ID","AWS_SECRET_ACCESS_KEY","AWS_S3_REGION","S3_BUCKET"],docs:"https://docs.aws.amazon.com/s3/",pkg:"@aws-sdk/client-s3",pkgVersion:"^3.658.0"},{id:"supabase-storage",name:"Supabase Storage",region:"global",envVars:["SUPABASE_URL","SUPABASE_SERVICE_ROLE_KEY","SUPABASE_BUCKET"],docs:"https://supabase.com/docs/guides/storage",pkg:"@supabase/supabase-js",pkgVersion:"^2.45.4"},{id:"aliyun-oss",name:"\u963F\u91CC\u4E91 OSS",region:"cn",envVars:["ALIYUN_OSS_ACCESS_KEY","ALIYUN_OSS_ACCESS_SECRET","ALIYUN_OSS_REGION","ALIYUN_OSS_BUCKET"],docs:"https://help.aliyun.com/product/31815.html",pkg:"ali-oss",pkgVersion:"^6.21.0"},{id:"tencent-cos",name:"\u817E\u8BAF\u4E91 COS",region:"cn",envVars:["TENCENT_COS_SECRET_ID","TENCENT_COS_SECRET_KEY","TENCENT_COS_BUCKET","TENCENT_COS_REGION"],docs:"https://cloud.tencent.com/document/product/436",pkg:"cos-nodejs-sdk-v5",pkgVersion:"^2.13.7"},{id:"qiniu",name:"\u4E03\u725B\u4E91 Kodo",region:"cn",envVars:["QINIU_ACCESS_KEY","QINIU_SECRET_KEY","QINIU_BUCKET","QINIU_DOMAIN"],docs:"https://developer.qiniu.com/kodo",pkg:"qiniu",pkgVersion:"^7.14.0"},{id:"none",name:"None",region:"global",envVars:[],docs:""}];function Yn(e){return yt.find(t=>t.id===e)}var Qa=yt.reduce((e,t)=>(e[t.region]||(e[t.region]=[]),e[t.region].push(t),e),{});var he="@nebutra/storage";function Mt(e,t){Ee.mkdirSync(Se.dirname(e),{recursive:!0}),Ee.writeFileSync(e,t)}function Bs(e,t){let o=Se.join(e,".env.example");Ee.existsSync(o)?Ee.appendFileSync(o,`
2343
+ `+t):Ee.writeFileSync(o,t)}function $s(e){if(e.envVars.length===0)return"";let t=`# Storage provider: ${e.name}`,o=e.envVars.map(n=>`${n}=`).join(`
2344
+ `);return`${t}
2345
+ ${o}
2346
+ `}var qs=`/**
2347
+ * Inputs for the \`uploadFile\` wrapper. Keep this surface portable across
2348
+ * providers \u2014 provider-specific options belong in the wrapper itself.
2349
+ */
2350
+ export interface UploadFileInput {
2351
+ key: string;
2352
+ body: Buffer | Uint8Array | string;
2353
+ contentType?: string;
2354
+ /** Optional access control. Most providers default to private. */
2355
+ acl?: "private" | "public-read";
2356
+ }
2357
+
2358
+ export interface UploadFileResult {
2359
+ key: string;
2360
+ /** Public or signed URL, whichever the provider returns by default. */
2361
+ url?: string;
2362
+ raw?: unknown;
2363
+ }
2364
+
2365
+ export interface SignedUrlOptions {
2366
+ /** Time-to-live in seconds. Defaults to 3600 (1h). */
2367
+ expiresIn?: number;
2368
+ /** Operation \u2014 "get" for download, "put" for direct upload. */
2369
+ operation?: "get" | "put";
2370
+ contentType?: string;
2371
+ }
2372
+ `;function Gs(){return`/**
2373
+ * @${he.slice(1)} \u2014 Cloudflare R2 via the S3-compatible API.
2374
+ *
2375
+ * Docs: https://developers.cloudflare.com/r2/api/s3/api/
2376
+ */
2377
+ import {
2378
+ GetObjectCommand,
2379
+ PutObjectCommand,
2380
+ S3Client,
2381
+ } from "@aws-sdk/client-s3";
2382
+ import { getSignedUrl as awsGetSignedUrl } from "@aws-sdk/s3-request-presigner";
2383
+
2384
+ import type {
2385
+ SignedUrlOptions,
2386
+ UploadFileInput,
2387
+ UploadFileResult,
2388
+ } from "./types.js";
2389
+
2390
+ const accountId = process.env.R2_ACCOUNT_ID;
2391
+ const accessKeyId = process.env.R2_ACCESS_KEY_ID;
2392
+ const secretAccessKey = process.env.R2_SECRET_ACCESS_KEY;
2393
+ const bucket = process.env.R2_BUCKET;
2394
+
2395
+ if (!accountId || !accessKeyId || !secretAccessKey || !bucket) {
2396
+ throw new Error(
2397
+ "R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET must be configured",
2398
+ );
2399
+ }
2400
+
2401
+ const client = new S3Client({
2402
+ region: "auto",
2403
+ endpoint: \`https://\${accountId}.r2.cloudflarestorage.com\`,
2404
+ credentials: { accessKeyId, secretAccessKey },
2405
+ });
2406
+
2407
+ export async function uploadFile(input: UploadFileInput): Promise<UploadFileResult> {
2408
+ const response = await client.send(
2409
+ new PutObjectCommand({
2410
+ Bucket: bucket,
2411
+ Key: input.key,
2412
+ Body: input.body,
2413
+ ContentType: input.contentType,
2414
+ }),
2415
+ );
2416
+ return { key: input.key, raw: response };
2417
+ }
2418
+
2419
+ export async function getSignedUrl(
2420
+ key: string,
2421
+ opts: SignedUrlOptions = {},
2422
+ ): Promise<string> {
2423
+ const expiresIn = opts.expiresIn ?? 3600;
2424
+ const command =
2425
+ opts.operation === "put"
2426
+ ? new PutObjectCommand({
2427
+ Bucket: bucket,
2428
+ Key: key,
2429
+ ContentType: opts.contentType,
2430
+ })
2431
+ : new GetObjectCommand({ Bucket: bucket, Key: key });
2432
+ return awsGetSignedUrl(client, command, { expiresIn });
2433
+ }
2434
+
2435
+ export type { UploadFileInput, UploadFileResult, SignedUrlOptions } from "./types.js";
2436
+ `}function Hs(){return`/**
2437
+ * @${he.slice(1)} \u2014 AWS S3 wrapper.
2438
+ *
2439
+ * Docs: https://docs.aws.amazon.com/s3/
2440
+ */
2441
+ import {
2442
+ GetObjectCommand,
2443
+ PutObjectCommand,
2444
+ S3Client,
2445
+ } from "@aws-sdk/client-s3";
2446
+ import { getSignedUrl as awsGetSignedUrl } from "@aws-sdk/s3-request-presigner";
2447
+
2448
+ import type {
2449
+ SignedUrlOptions,
2450
+ UploadFileInput,
2451
+ UploadFileResult,
2452
+ } from "./types.js";
2453
+
2454
+ const region = process.env.AWS_S3_REGION;
2455
+ const bucket = process.env.S3_BUCKET;
2456
+ if (!region || !bucket) {
2457
+ throw new Error("AWS_S3_REGION and S3_BUCKET must be configured");
2458
+ }
2459
+
2460
+ const client = new S3Client({
2461
+ region,
2462
+ credentials:
2463
+ process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY
2464
+ ? {
2465
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID,
2466
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
2467
+ }
2468
+ : undefined,
2469
+ });
2470
+
2471
+ export async function uploadFile(input: UploadFileInput): Promise<UploadFileResult> {
2472
+ const response = await client.send(
2473
+ new PutObjectCommand({
2474
+ Bucket: bucket,
2475
+ Key: input.key,
2476
+ Body: input.body,
2477
+ ContentType: input.contentType,
2478
+ ACL: input.acl,
2479
+ }),
2480
+ );
2481
+ return { key: input.key, raw: response };
2482
+ }
2483
+
2484
+ export async function getSignedUrl(
2485
+ key: string,
2486
+ opts: SignedUrlOptions = {},
2487
+ ): Promise<string> {
2488
+ const expiresIn = opts.expiresIn ?? 3600;
2489
+ const command =
2490
+ opts.operation === "put"
2491
+ ? new PutObjectCommand({
2492
+ Bucket: bucket,
2493
+ Key: key,
2494
+ ContentType: opts.contentType,
2495
+ })
2496
+ : new GetObjectCommand({ Bucket: bucket, Key: key });
2497
+ return awsGetSignedUrl(client, command, { expiresIn });
2498
+ }
2499
+
2500
+ export type { UploadFileInput, UploadFileResult, SignedUrlOptions } from "./types.js";
2501
+ `}function Ws(){return`/**
2502
+ * @${he.slice(1)} \u2014 Supabase Storage wrapper.
2503
+ *
2504
+ * Docs: https://supabase.com/docs/guides/storage
2505
+ */
2506
+ import { createClient } from "@supabase/supabase-js";
2507
+
2508
+ import type {
2509
+ SignedUrlOptions,
2510
+ UploadFileInput,
2511
+ UploadFileResult,
2512
+ } from "./types.js";
2513
+
2514
+ const url = process.env.SUPABASE_URL;
2515
+ const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
2516
+ const bucket = process.env.SUPABASE_BUCKET;
2517
+ if (!url || !serviceRoleKey || !bucket) {
2518
+ throw new Error(
2519
+ "SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, SUPABASE_BUCKET must be configured",
2520
+ );
2521
+ }
2522
+
2523
+ const client = createClient(url, serviceRoleKey, {
2524
+ auth: { persistSession: false },
2525
+ });
2526
+
2527
+ export async function uploadFile(input: UploadFileInput): Promise<UploadFileResult> {
2528
+ const { data, error } = await client.storage
2529
+ .from(bucket)
2530
+ .upload(input.key, input.body, {
2531
+ contentType: input.contentType,
2532
+ upsert: true,
2533
+ });
2534
+ if (error) {
2535
+ throw new Error(\`Supabase upload failed: \${error.message}\`);
2536
+ }
2537
+ return { key: data?.path ?? input.key, raw: data };
2538
+ }
2539
+
2540
+ export async function getSignedUrl(
2541
+ key: string,
2542
+ opts: SignedUrlOptions = {},
2543
+ ): Promise<string> {
2544
+ const expiresIn = opts.expiresIn ?? 3600;
2545
+ if (opts.operation === "put") {
2546
+ const { data, error } = await client.storage
2547
+ .from(bucket)
2548
+ .createSignedUploadUrl(key);
2549
+ if (error || !data) {
2550
+ throw new Error(\`Supabase signed upload url failed: \${error?.message ?? "unknown"}\`);
2551
+ }
2552
+ return data.signedUrl;
2553
+ }
2554
+ const { data, error } = await client.storage
2555
+ .from(bucket)
2556
+ .createSignedUrl(key, expiresIn);
2557
+ if (error || !data) {
2558
+ throw new Error(\`Supabase signed url failed: \${error?.message ?? "unknown"}\`);
2559
+ }
2560
+ return data.signedUrl;
2561
+ }
2562
+
2563
+ export type { UploadFileInput, UploadFileResult, SignedUrlOptions } from "./types.js";
2564
+ `}function Xs(){return`/**
2565
+ * @${he.slice(1)} \u2014 \u963F\u91CC\u4E91 OSS wrapper via ali-oss.
2566
+ *
2567
+ * Docs: https://help.aliyun.com/product/31815.html
2568
+ */
2569
+ import OSS from "ali-oss";
2570
+
2571
+ import type {
2572
+ SignedUrlOptions,
2573
+ UploadFileInput,
2574
+ UploadFileResult,
2575
+ } from "./types.js";
2576
+
2577
+ const accessKeyId = process.env.ALIYUN_OSS_ACCESS_KEY;
2578
+ const accessKeySecret = process.env.ALIYUN_OSS_ACCESS_SECRET;
2579
+ const region = process.env.ALIYUN_OSS_REGION;
2580
+ const bucket = process.env.ALIYUN_OSS_BUCKET;
2581
+ if (!accessKeyId || !accessKeySecret || !region || !bucket) {
2582
+ throw new Error(
2583
+ "ALIYUN_OSS_ACCESS_KEY, ALIYUN_OSS_ACCESS_SECRET, ALIYUN_OSS_REGION, ALIYUN_OSS_BUCKET must be configured",
2584
+ );
2585
+ }
2586
+
2587
+ const client = new OSS({ accessKeyId, accessKeySecret, region, bucket });
2588
+
2589
+ export async function uploadFile(input: UploadFileInput): Promise<UploadFileResult> {
2590
+ const body =
2591
+ typeof input.body === "string" ? Buffer.from(input.body) : Buffer.from(input.body);
2592
+ const response = await client.put(input.key, body, {
2593
+ mime: input.contentType,
2594
+ });
2595
+ return { key: response.name, url: response.url, raw: response };
2596
+ }
2597
+
2598
+ export async function getSignedUrl(
2599
+ key: string,
2600
+ opts: SignedUrlOptions = {},
2601
+ ): Promise<string> {
2602
+ const expires = opts.expiresIn ?? 3600;
2603
+ return client.signatureUrl(key, {
2604
+ expires,
2605
+ method: opts.operation === "put" ? "PUT" : "GET",
2606
+ "Content-Type": opts.contentType,
2607
+ });
2608
+ }
2609
+
2610
+ export type { UploadFileInput, UploadFileResult, SignedUrlOptions } from "./types.js";
2611
+ `}function zs(){return`/**
2612
+ * @${he.slice(1)} \u2014 \u817E\u8BAF\u4E91 COS wrapper via cos-nodejs-sdk-v5.
2613
+ *
2614
+ * Docs: https://cloud.tencent.com/document/product/436
2615
+ */
2616
+ import COS from "cos-nodejs-sdk-v5";
2617
+
2618
+ import type {
2619
+ SignedUrlOptions,
2620
+ UploadFileInput,
2621
+ UploadFileResult,
2622
+ } from "./types.js";
2623
+
2624
+ const secretId = process.env.TENCENT_COS_SECRET_ID;
2625
+ const secretKey = process.env.TENCENT_COS_SECRET_KEY;
2626
+ const bucket = process.env.TENCENT_COS_BUCKET;
2627
+ const region = process.env.TENCENT_COS_REGION;
2628
+ if (!secretId || !secretKey || !bucket || !region) {
2629
+ throw new Error(
2630
+ "TENCENT_COS_SECRET_ID, TENCENT_COS_SECRET_KEY, TENCENT_COS_BUCKET, TENCENT_COS_REGION must be configured",
2631
+ );
2632
+ }
2633
+
2634
+ const cos = new COS({ SecretId: secretId, SecretKey: secretKey });
2635
+
2636
+ export async function uploadFile(input: UploadFileInput): Promise<UploadFileResult> {
2637
+ const body =
2638
+ typeof input.body === "string" ? Buffer.from(input.body) : Buffer.from(input.body);
2639
+ const response = await cos.putObject({
2640
+ Bucket: bucket,
2641
+ Region: region,
2642
+ Key: input.key,
2643
+ Body: body,
2644
+ ContentType: input.contentType,
2645
+ });
2646
+ return { key: input.key, url: response.Location, raw: response };
2647
+ }
2648
+
2649
+ export async function getSignedUrl(
2650
+ key: string,
2651
+ opts: SignedUrlOptions = {},
2652
+ ): Promise<string> {
2653
+ const expires = opts.expiresIn ?? 3600;
2654
+ const url = cos.getObjectUrl({
2655
+ Bucket: bucket,
2656
+ Region: region,
2657
+ Key: key,
2658
+ Sign: true,
2659
+ Method: opts.operation === "put" ? "PUT" : "GET",
2660
+ Expires: expires,
2661
+ } as unknown as COS.GetObjectUrlParams);
2662
+ return url as unknown as string;
2663
+ }
2664
+
2665
+ export type { UploadFileInput, UploadFileResult, SignedUrlOptions } from "./types.js";
2666
+ `}function Qs(){return`/**
2667
+ * @${he.slice(1)} \u2014 \u4E03\u725B\u4E91 Kodo wrapper.
2668
+ *
2669
+ * Docs: https://developer.qiniu.com/kodo
2670
+ */
2671
+ import qiniu from "qiniu";
2672
+
2673
+ import type {
2674
+ SignedUrlOptions,
2675
+ UploadFileInput,
2676
+ UploadFileResult,
2677
+ } from "./types.js";
2678
+
2679
+ const accessKey = process.env.QINIU_ACCESS_KEY;
2680
+ const secretKey = process.env.QINIU_SECRET_KEY;
2681
+ const bucket = process.env.QINIU_BUCKET;
2682
+ const domain = process.env.QINIU_DOMAIN;
2683
+ if (!accessKey || !secretKey || !bucket || !domain) {
2684
+ throw new Error(
2685
+ "QINIU_ACCESS_KEY, QINIU_SECRET_KEY, QINIU_BUCKET, QINIU_DOMAIN must be configured",
2686
+ );
2687
+ }
2688
+
2689
+ const mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
2690
+ const config = new qiniu.conf.Config();
2691
+ const formUploader = new qiniu.form_up.FormUploader(config);
2692
+ const bucketManager = new qiniu.rs.BucketManager(mac, config);
2693
+
2694
+ function uploadToken(key: string): string {
2695
+ const putPolicy = new qiniu.rs.PutPolicy({ scope: \`\${bucket}:\${key}\` });
2696
+ return putPolicy.uploadToken(mac);
2697
+ }
2698
+
2699
+ export async function uploadFile(input: UploadFileInput): Promise<UploadFileResult> {
2700
+ const body =
2701
+ typeof input.body === "string" ? Buffer.from(input.body) : Buffer.from(input.body);
2702
+
2703
+ return new Promise<UploadFileResult>((resolve, reject) => {
2704
+ formUploader.put(
2705
+ uploadToken(input.key),
2706
+ input.key,
2707
+ body,
2708
+ new qiniu.form_up.PutExtra(undefined, undefined, input.contentType),
2709
+ (err, body, info) => {
2710
+ if (err) return reject(err);
2711
+ if (info.statusCode !== 200) {
2712
+ return reject(new Error(\`Qiniu upload failed: \${info.statusCode}\`));
2713
+ }
2714
+ resolve({ key: input.key, raw: body });
2715
+ },
2716
+ );
2717
+ });
2718
+ }
2719
+
2720
+ export async function getSignedUrl(
2721
+ key: string,
2722
+ opts: SignedUrlOptions = {},
2723
+ ): Promise<string> {
2724
+ const expires = opts.expiresIn ?? 3600;
2725
+ if (opts.operation === "put") {
2726
+ // Qiniu issues upload tokens, not signed PUT URLs.
2727
+ return uploadToken(key);
2728
+ }
2729
+ const deadline = Math.floor(Date.now() / 1000) + expires;
2730
+ return bucketManager.privateDownloadUrl(domain, key, deadline);
2731
+ }
2732
+
2733
+ export type { UploadFileInput, UploadFileResult, SignedUrlOptions } from "./types.js";
2734
+ `}function Js(e){switch(e){case"r2":return Gs();case"s3":return Hs();case"supabase-storage":return Ws();case"aliyun-oss":return Xs();case"tencent-cos":return zs();case"qiniu":return Qs();case"none":return`export {};
2735
+ `}}function Zs(e){let t={};e.pkg&&e.pkgVersion&&(t[e.pkg]=e.pkgVersion),(e.id==="r2"||e.id==="s3")&&(t["@aws-sdk/s3-request-presigner"]="^3.658.0");let o={name:he,version:"0.1.0",private:!0,description:`Object storage via ${e.name}`,type:"module",main:"./dist/index.js",exports:{".":{import:"./dist/index.js",types:"./dist/index.d.ts"}},scripts:{build:"tsup",typecheck:"tsc --noEmit",clean:"rm -rf dist .turbo"},dependencies:t,devDependencies:{tsup:"^8.5.0",typescript:"catalog:"}};return JSON.stringify(o,null,2)+`
2736
+ `}async function Bn(e,t,o){if(t==="none")return;let n=Yn(t);if(!n)throw new Error(`Unknown storage provider "${t}". Valid ids: ${yt.map(a=>a.id).join(", ")}`);let r=Se.join(e,"packages","storage"),s=Se.join(r,"src");Ee.existsSync(s)&&Ee.rmSync(s,{recursive:!0,force:!0}),Mt(Se.join(r,"package.json"),Zs(n)),Mt(Se.join(s,"types.ts"),qs),Mt(Se.join(s,"index.ts"),Js(n.id)),Bs(e,$s(n))}import te from"fs";import Dt from"path";var St=[{id:"qstash",name:"Upstash QStash",region:"both",envVars:["QSTASH_TOKEN","QSTASH_CURRENT_SIGNING_KEY","QSTASH_NEXT_SIGNING_KEY"],docs:"https://upstash.com/docs/qstash",description:"Serverless HTTP-based queue"},{id:"bullmq",name:"BullMQ",region:"both",envVars:["REDIS_URL"],docs:"https://docs.bullmq.io",description:"Redis-backed job queue"},{id:"upstash",name:"Upstash Kafka",region:"both",envVars:["UPSTASH_KAFKA_REST_URL","UPSTASH_KAFKA_REST_USERNAME","UPSTASH_KAFKA_REST_PASSWORD"],docs:"https://upstash.com/docs/kafka",description:"Serverless Kafka"},{id:"sqs",name:"AWS SQS",region:"global",envVars:["AWS_SQS_QUEUE_URL","AWS_ACCESS_KEY_ID","AWS_SECRET_ACCESS_KEY","AWS_REGION"],docs:"https://docs.aws.amazon.com/AWSSimpleQueueService/",description:"AWS managed queue"},{id:"none",name:"None",region:"both",envVars:[],docs:"",description:"Skip queue package"}];function $n(e){return St.find(t=>t.id===e)}var oc=St.reduce((e,t)=>(e[t.region]||(e[t.region]=[]),e[t.region].push(t),e),{});function qn(e){return te.existsSync(e)?te.readFileSync(e,"utf-8"):null}function ei(e,t){let o=Dt.join(e,".env.example"),n=qn(o);if(n===null)return;let r=`# Queue: ${t.name}`;if(n.includes(r))return;let s=["","# =============================================",r,`# Docs: ${t.docs}`,"# =============================================",...t.envVars.map(a=>`${a}=""`),""].join(`
2737
+ `);te.appendFileSync(o,s)}function ti(e,t,o){let n=Dt.join(e,".env.example"),r=qn(n);if(r===null)return;let s=new RegExp(`^${t}=.*$`,"m");s.test(r)?te.writeFileSync(n,r.replace(s,`${t}="${o}"`)):te.appendFileSync(n,`${t}="${o}"
2738
+ `)}async function Gn(e,t,o){let n=Dt.join(e,"packages","queue");if(t==="none"){te.existsSync(n)&&te.rmSync(n,{recursive:!0,force:!0});return}let r=$n(t);if(!r)throw new Error(`Unknown queue provider "${t}". Valid ids: ${St.map(s=>s.id).join(", ")}`);te.existsSync(n)&&(ei(e,r),ti(e,"QUEUE_PROVIDER",t))}import ne from"fs";import jt from"path";var Et=[{id:"meilisearch",name:"Meilisearch",region:"both",envVars:["MEILISEARCH_HOST","MEILISEARCH_MASTER_KEY"],docs:"https://docs.meilisearch.com",description:"Self-hostable search"},{id:"typesense",name:"Typesense",region:"both",envVars:["TYPESENSE_HOST","TYPESENSE_PORT","TYPESENSE_PROTOCOL","TYPESENSE_API_KEY"],docs:"https://typesense.org/docs",description:"Open-source search"},{id:"algolia",name:"Algolia",region:"global",envVars:["ALGOLIA_APP_ID","ALGOLIA_ADMIN_API_KEY","ALGOLIA_SEARCH_API_KEY"],docs:"https://www.algolia.com/doc/",description:"Managed search as a service"},{id:"pgvector",name:"pgvector (Postgres)",region:"both",envVars:["DATABASE_URL"],docs:"https://github.com/pgvector/pgvector",description:"Postgres vector + full-text"},{id:"none",name:"None",region:"both",envVars:[],docs:"",description:"Skip search package"}];function Hn(e){return Et.find(t=>t.id===e)}var pc=Et.reduce((e,t)=>(e[t.region]||(e[t.region]=[]),e[t.region].push(t),e),{});function Wn(e){return ne.existsSync(e)?ne.readFileSync(e,"utf-8"):null}function ni(e,t){let o=jt.join(e,".env.example"),n=Wn(o);if(n===null)return;let r=`# Search: ${t.name}`;if(n.includes(r))return;let s=["","# =============================================",r,`# Docs: ${t.docs}`,"# =============================================",...t.envVars.map(a=>`${a}=""`),""].join(`
2739
+ `);ne.appendFileSync(o,s)}function oi(e,t,o){let n=jt.join(e,".env.example"),r=Wn(n);if(r===null)return;let s=new RegExp(`^${t}=.*$`,"m");s.test(r)?ne.writeFileSync(n,r.replace(s,`${t}="${o}"`)):ne.appendFileSync(n,`${t}="${o}"
2740
+ `)}async function Xn(e,t,o){let n=jt.join(e,"packages","search");if(t==="none"){ne.existsSync(n)&&ne.rmSync(n,{recursive:!0,force:!0});return}let r=Hn(t);if(!r)throw new Error(`Unknown search provider "${t}". Valid ids: ${Et.map(s=>s.id).join(", ")}`);ne.existsSync(n)&&(ni(e,r),oi(e,"SEARCH_PROVIDER",t))}import oe from"fs";import Ft from"path";var ht=[{id:"upstash-redis",name:"Upstash Redis",region:"both",envVars:["UPSTASH_REDIS_REST_URL","UPSTASH_REDIS_REST_TOKEN"],docs:"https://upstash.com/docs/redis",description:"Serverless Redis"},{id:"vercel-kv",name:"Vercel KV",region:"global",envVars:["KV_URL","KV_REST_API_URL","KV_REST_API_TOKEN","KV_REST_API_READ_ONLY_TOKEN"],docs:"https://vercel.com/docs/storage/vercel-kv",description:"Vercel native KV"},{id:"redis",name:"Redis (self-hosted)",region:"both",envVars:["REDIS_URL"],docs:"https://redis.io/docs/",description:"Self-hosted Redis"},{id:"dragonfly",name:"Dragonfly",region:"global",envVars:["DRAGONFLY_URL"],docs:"https://www.dragonflydb.io/docs",description:"Drop-in Redis replacement, faster"},{id:"none",name:"None",region:"both",envVars:[],docs:"",description:"Skip cache package"}];function zn(e){return ht.find(t=>t.id===e)}var fc=ht.reduce((e,t)=>(e[t.region]||(e[t.region]=[]),e[t.region].push(t),e),{});function Qn(e){return oe.existsSync(e)?oe.readFileSync(e,"utf-8"):null}function ri(e,t){let o=Ft.join(e,".env.example"),n=Qn(o);if(n===null)return;let r=`# Cache: ${t.name}`;if(n.includes(r))return;let s=["","# =============================================",r,`# Docs: ${t.docs}`,"# =============================================",...t.envVars.map(a=>`${a}=""`),""].join(`
2741
+ `);oe.appendFileSync(o,s)}function si(e,t,o){let n=Ft.join(e,".env.example"),r=Qn(n);if(r===null)return;let s=new RegExp(`^${t}=.*$`,"m");s.test(r)?oe.writeFileSync(n,r.replace(s,`${t}="${o}"`)):oe.appendFileSync(n,`${t}="${o}"
2742
+ `)}async function Jn(e,t,o){let n=Ft.join(e,"packages","cache");if(t==="none"){oe.existsSync(n)&&oe.rmSync(n,{recursive:!0,force:!0});return}let r=zn(t);if(!r)throw new Error(`Unknown cache provider "${t}". Valid ids: ${ht.map(s=>s.id).join(", ")}`);oe.existsSync(n)&&(ri(e,r),si(e,"CACHE_PROVIDER",t))}import H from"fs";import Vt from"path";function B(e,t){let o=Vt.join(e,".env.example");if(!H.existsSync(o)||t.envVars.length===0)return;let r=`# ${t.category??t.name}: ${t.name}`;if(H.readFileSync(o,"utf-8").includes(r))return;let c=["","# =============================================",r,t.docs?`# Docs: ${t.docs}`:null,"# =============================================",...t.envVars.map(i=>`${i}=""`),""].filter(i=>i!==null).join(`
2743
+ `);H.appendFileSync(o,c)}function $(e,t,o){let n=Vt.join(e,".env.example");if(!H.existsSync(n))return;let r=H.readFileSync(n,"utf-8"),s=`${t}="${o}"`,a=new RegExp(`^${t}=.*$`,"m");if(a.test(r)){H.writeFileSync(n,r.replace(a,s));return}let c=r.endsWith(`
2744
+ `)?"":`
2745
+ `;H.appendFileSync(n,`${c}${s}
2746
+ `)}function U(e,t){let o=Vt.join(e,"packages",t);H.existsSync(o)&&H.rmSync(o,{recursive:!0,force:!0})}var vt=[{id:"novu",name:"Novu",region:"both",envVars:["NOVU_API_KEY","NOVU_APPLICATION_IDENTIFIER"],docs:"https://docs.novu.co",description:"Multi-channel notification infrastructure"},{id:"knock",name:"Knock",region:"global",envVars:["KNOCK_API_KEY","KNOCK_SIGNING_KEY","KNOCK_PUBLIC_API_KEY"],docs:"https://docs.knock.app",description:"Product notifications platform"},{id:"custom",name:"Custom (direct dispatchers)",region:"both",envVars:[],docs:"",description:"Use email + sms + push directly"},{id:"none",name:"None",region:"both",envVars:[],docs:"",description:""}];function Zn(e){return vt.find(t=>t.id===e)}var Cc=vt.reduce((e,t)=>(e[t.region]||(e[t.region]=[]),e[t.region].push(t),e),{});async function eo(e,t,o="global"){let n=Zn(t);if(!n)throw new Error(`Unknown notifications provider "${t}". Valid ids: ${vt.map(r=>r.id).join(", ")}`);if(n.id==="none"){U(e,"notifications");return}n.envVars.length>0&&B(e,{category:"Notifications",name:n.name,envVars:n.envVars,docs:n.docs}),$(e,"NOTIFICATIONS_PROVIDER",n.id)}var _t=[{id:"svix",name:"Svix",region:"global",envVars:["SVIX_API_TOKEN"],docs:"https://docs.svix.com",description:"Managed webhooks platform"},{id:"custom",name:"Custom (self-hosted)",region:"both",envVars:["WEBHOOK_SIGNING_SECRET"],docs:"",description:"Custom implementation with built-in package"},{id:"none",name:"None",region:"both",envVars:[],docs:"",description:""}];function to(e){return _t.find(t=>t.id===e)}var Nc=_t.reduce((e,t)=>(e[t.region]||(e[t.region]=[]),e[t.region].push(t),e),{});async function no(e,t,o="global"){let n=to(t);if(!n)throw new Error(`Unknown webhooks provider "${t}". Valid ids: ${_t.map(r=>r.id).join(", ")}`);if(n.id==="none"){U(e,"webhooks");return}n.envVars.length>0&&B(e,{category:"Webhooks",name:n.name,envVars:n.envVars,docs:n.docs}),$(e,"WEBHOOKS_PROVIDER",n.id)}import It from"fs";import ii from"path";var Pt=[{id:"sanity",name:"Sanity",region:"global",envVars:["SANITY_PROJECT_ID","SANITY_DATASET","SANITY_API_TOKEN","SANITY_API_VERSION"],docs:"https://www.sanity.io/docs",description:"Structured content platform with Studio"},{id:"contentful",name:"Contentful",region:"global",envVars:["CONTENTFUL_SPACE_ID","CONTENTFUL_ACCESS_TOKEN","CONTENTFUL_PREVIEW_ACCESS_TOKEN"],docs:"https://www.contentful.com/developers/docs/",description:"Headless CMS"},{id:"strapi",name:"Strapi",region:"both",envVars:["STRAPI_URL","STRAPI_API_TOKEN"],docs:"https://docs.strapi.io",description:"Open-source self-hosted CMS"},{id:"none",name:"None",region:"both",envVars:[],docs:"",description:""}];function oo(e){return Pt.find(t=>t.id===e)}var Uc=Pt.reduce((e,t)=>(e[t.region]||(e[t.region]=[]),e[t.region].push(t),e),{});async function ro(e,t,o="global"){let n=oo(t);if(!n)throw new Error(`Unknown CMS provider "${t}". Valid ids: ${Pt.map(s=>s.id).join(", ")}`);let r=ii.join(e,"apps","studio");if(n.id==="none"){U(e,"sanity"),It.existsSync(r)&&It.rmSync(r,{recursive:!0,force:!0});return}n.id!=="sanity"&&(U(e,"sanity"),It.existsSync(r)&&It.rmSync(r,{recursive:!0,force:!0})),n.envVars.length>0&&B(e,{category:"CMS",name:n.name,envVars:n.envVars,docs:n.docs}),$(e,"CMS_PROVIDER",n.id)}var Ct=[{id:"vercel-flags",name:"Vercel Flags SDK",region:"global",envVars:["FLAGS_SECRET"],docs:"https://vercel.com/docs/workflow-collaboration/feature-flags",description:"Native Vercel feature flags"},{id:"growthbook",name:"GrowthBook",region:"both",envVars:["GROWTHBOOK_API_HOST","GROWTHBOOK_CLIENT_KEY","GROWTHBOOK_DECRYPTION_KEY"],docs:"https://docs.growthbook.io",description:"Open-source A/B testing + flags"},{id:"configcat",name:"ConfigCat",region:"global",envVars:["CONFIGCAT_SDK_KEY"],docs:"https://configcat.com/docs/",description:"Feature flag & config management"},{id:"none",name:"None",region:"both",envVars:[],docs:"",description:""}];function so(e){return Ct.find(t=>t.id===e)}var Kc=Ct.reduce((e,t)=>(e[t.region]||(e[t.region]=[]),e[t.region].push(t),e),{});async function io(e,t,o="global"){let n=so(t);if(!n)throw new Error(`Unknown feature-flags provider "${t}". Valid ids: ${Ct.map(r=>r.id).join(", ")}`);if(n.id==="none"){U(e,"feature-flags");return}n.envVars.length>0&&B(e,{category:"Feature Flags",name:n.name,envVars:n.envVars,docs:n.docs}),$(e,"FEATURE_FLAGS_PROVIDER",n.id)}var bt=[{id:"turnstile",name:"Cloudflare Turnstile",region:"global",envVars:["NEXT_PUBLIC_TURNSTILE_SITE_KEY","TURNSTILE_SECRET_KEY"],docs:"https://developers.cloudflare.com/turnstile/",description:"Free, privacy-focused captcha alternative"},{id:"hcaptcha",name:"hCaptcha",region:"global",envVars:["NEXT_PUBLIC_HCAPTCHA_SITE_KEY","HCAPTCHA_SECRET_KEY"],docs:"https://docs.hcaptcha.com/",description:"Privacy-respecting captcha"},{id:"aliyun-slide",name:"\u963F\u91CC\u4E91\u9A8C\u8BC1\u7801 2.0",region:"cn",envVars:["ALIYUN_CAPTCHA_ACCESS_KEY_ID","ALIYUN_CAPTCHA_ACCESS_KEY_SECRET","ALIYUN_CAPTCHA_APP_KEY","NEXT_PUBLIC_ALIYUN_CAPTCHA_APP_KEY"],docs:"https://help.aliyun.com/product/28308.html",description:"\u963F\u91CC\u4E91\u667A\u80FD\u9A8C\u8BC1 (\u6ED1\u5757/\u65E0\u75D5)"},{id:"none",name:"None",region:"both",envVars:[],docs:"",description:""}];function ao(e){return bt.find(t=>t.id===e)}var Gc=bt.reduce((e,t)=>(e[t.region]||(e[t.region]=[]),e[t.region].push(t),e),{});async function co(e,t,o="global"){let n=ao(t);if(!n)throw new Error(`Unknown captcha provider "${t}". Valid ids: ${bt.map(r=>r.id).join(", ")}`);if(n.id==="none"){U(e,"captcha");return}n.envVars.length>0&&B(e,{category:"Captcha",name:n.name,envVars:n.envVars,docs:n.docs}),$(e,"CAPTCHA_PROVIDER",n.id)}import ve from"fs";import Kt from"path";async function po(e,t){let o=Kt.join(e,"packages","mcp");if(t==="off"){ve.existsSync(o)&&ve.rmSync(o,{recursive:!0,force:!0});let r=Kt.join(e,"packages","cli","src","commands","mcp-server.ts");ve.existsSync(r)&&ve.rmSync(r,{force:!0});return}let n=Kt.join(e,".env.example");if(ve.existsSync(n)){let r=ve.readFileSync(n,"utf-8"),s="# MCP Server (Agent-friendly API)";if(!r.includes(s)){let a=["","# =============================================",s,"# Docs: https://modelcontextprotocol.io","# =============================================","# MCP server exposes your SaaS capabilities to AI agents via MCP protocol.","# Run: pnpm mcp-server (starts stdio server)","# No config required for stdio transport. HTTP transport needs:",'MCP_HTTP_PORT="3100"','MCP_API_KEY=""',""].join(`
2747
+ `);ve.appendFileSync(n,a)}}}import nt from"fs";import lo from"path";async function uo(e,t,o){let n=lo.join(e,"packages","metering"),r;if(t==="auto"?r=o!=="none"?"on":"off":t==="on"||t==="off"?r=t:r="off",r==="off"){nt.existsSync(n)&&nt.rmSync(n,{recursive:!0,force:!0});return}let s=lo.join(e,".env.example");if(!nt.existsSync(s))return;let a=nt.readFileSync(s,"utf-8"),c="# Metering (usage-based billing)";if(!a.includes(c)){let i=["","# =============================================",c,"# Docs: see packages/metering/README.md","# =============================================","# ClickHouse-based metering pipeline for consumption billing.",'CLICKHOUSE_URL=""','CLICKHOUSE_USER="default"','CLICKHOUSE_PASSWORD=""','CLICKHOUSE_DATABASE="metering"',""].join(`
2748
+ `);nt.appendFileSync(s,i)}}import _e from"fs";import ot from"path";function ai(e){return`import Link from "next/link";
2749
+
2750
+ export default function WelcomePage() {
2751
+ const projectName = ${JSON.stringify(e)};
2752
+ return (
2753
+ <div className="mx-auto max-w-3xl px-6 py-16">
2754
+ <div className="space-y-8">
2755
+ <header className="space-y-2">
2756
+ <p className="text-sm text-muted-foreground">Powered by Sailor \u2693</p>
2757
+ <h1 className="text-4xl font-bold tracking-tight">
2758
+ Welcome to {projectName}
2759
+ </h1>
2760
+ <p className="text-lg text-muted-foreground">
2761
+ Your AI-native SaaS template is ready. Here&apos;s what to do next:
2762
+ </p>
2763
+ </header>
2764
+
2765
+ <section className="space-y-4">
2766
+ <h2 className="text-2xl font-semibold">Getting started</h2>
2767
+ <ol className="space-y-3">
2768
+ <Step
2769
+ number={1}
2770
+ title="Configure environment"
2771
+ description="Fill in your .env.local \u2014 we've generated random secrets for you"
2772
+ command=".env.local"
2773
+ />
2774
+ <Step
2775
+ number={2}
2776
+ title="Run database migrations"
2777
+ description="Sync your Prisma schema with your database"
2778
+ command="pnpm db:migrate"
2779
+ />
2780
+ <Step
2781
+ number={3}
2782
+ title="Seed example data"
2783
+ description="1 admin user + 3 tenants + sample records"
2784
+ command="pnpm db:seed"
2785
+ />
2786
+ <Step
2787
+ number={4}
2788
+ title="Customize your brand"
2789
+ description="Colors, domain, logo, SEO \u2014 all in one file"
2790
+ command="pnpm sailor brand init"
2791
+ />
2792
+ </ol>
2793
+ </section>
2794
+
2795
+ <section className="space-y-4">
2796
+ <h2 className="text-2xl font-semibold">Resources</h2>
2797
+ <ul className="list-inside list-disc space-y-2 text-sm">
2798
+ <li>
2799
+ <Link href="/docs" className="underline">
2800
+ Documentation
2801
+ </Link>
2802
+ </li>
2803
+ <li>
2804
+ <a
2805
+ href="https://github.com/nebutra/nebutra-sailor"
2806
+ className="underline"
2807
+ >
2808
+ GitHub
2809
+ </a>
2810
+ </li>
2811
+ <li>
2812
+ <a
2813
+ href="https://nebutra.com/get-license"
2814
+ className="underline"
2815
+ >
2816
+ Get free license (Individual/OPC)
2817
+ </a>
2818
+ </li>
2819
+ </ul>
2820
+ </section>
2821
+
2822
+ <footer className="border-t pt-6 text-xs text-muted-foreground">
2823
+ Delete this page after setup:{" "}
2824
+ <code>apps/web/src/app/[locale]/welcome/</code>
2825
+ </footer>
2826
+ </div>
2827
+ </div>
2828
+ );
2829
+ }
2830
+
2831
+ function Step({
2832
+ number,
2833
+ title,
2834
+ description,
2835
+ command,
2836
+ }: {
2837
+ number: number;
2838
+ title: string;
2839
+ description: string;
2840
+ command: string;
2841
+ }) {
2842
+ return (
2843
+ <li className="flex gap-4">
2844
+ <span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-primary text-sm font-medium text-primary-foreground">
2845
+ {number}
2846
+ </span>
2847
+ <div className="space-y-1">
2848
+ <h3 className="font-medium">{title}</h3>
2849
+ <p className="text-sm text-muted-foreground">{description}</p>
2850
+ <code className="inline-block rounded bg-muted px-2 py-0.5 text-xs">
2851
+ {command}
2852
+ </code>
2853
+ </div>
2854
+ </li>
2855
+ );
2856
+ }
2857
+ `}function ci(e,t){return`# Next steps \u2014 ${e}
2858
+
2859
+ Region: ${t}
2860
+
2861
+ Your AI-native SaaS scaffold is ready. Complete these four steps to go from template to running dev server:
2862
+
2863
+ 1. **Configure environment** \u2014 fill in \`.env.local\`
2864
+ > Random cryptographic secrets have been generated for you. Add your provider keys (DB, auth, payments, AI).
2865
+
2866
+ 2. **Run database migrations** \u2014 sync your Prisma schema with your database
2867
+ \`\`\`bash
2868
+ pnpm db:migrate
2869
+ \`\`\`
2870
+
2871
+ 3. **Seed example data** \u2014 1 admin user + 3 tenants + sample records
2872
+ \`\`\`bash
2873
+ pnpm db:seed
2874
+ \`\`\`
2875
+
2876
+ 4. **Customize your brand** \u2014 colors, domain, logo, SEO \u2014 all in one file
2877
+ \`\`\`bash
2878
+ pnpm sailor brand init
2879
+ \`\`\`
2880
+
2881
+ After setup, delete the welcome route at \`apps/web/src/app/[locale]/welcome/\`.
2882
+
2883
+ ## Resources
2884
+
2885
+ - [Documentation](/docs)
2886
+ - [GitHub](https://github.com/nebutra/nebutra-sailor)
2887
+ - [Get free license (Individual/OPC)](https://nebutra.com/get-license)
2888
+ `}async function mo(e,t){let{projectName:o,region:n}=t;try{let r=ot.join(e,".sailor");_e.existsSync(r)||_e.mkdirSync(r,{recursive:!0}),_e.writeFileSync(ot.join(r,"next-steps.md"),ci(o,n))}catch(r){let s=r instanceof Error?r.message:String(r);console.warn(`[create-sailor] Failed to write .sailor/next-steps.md: ${s}`)}try{let r=ot.join(e,"apps","web");if(!_e.existsSync(r))return;let s=ot.join(r,"src","app","[locale]","welcome");_e.existsSync(s)||_e.mkdirSync(s,{recursive:!0}),_e.writeFileSync(ot.join(s,"page.tsx"),ai(o))}catch(r){let s=r instanceof Error?r.message:String(r);console.warn(`[create-sailor] Failed to write welcome page: ${s}`)}}var fo="create-sailor";function p(e,t){e&&process.stdout.write(JSON.stringify(t)+`
2889
+ `)}function di(){let e=process.env.npm_config_user_agent??"";return e.startsWith("pnpm")?"pnpm":e.startsWith("yarn")?"yarn":e.startsWith("bun")?"bun":"npm"}function yo(e){switch(e){case"postgres":case"postgresql":return"postgresql";case"mysql":return"mysql";case"sqlite":return"sqlite";case"none":return"none";default:return"postgresql"}}function So(e){return e==="drizzle"?"drizzle":e==="none"?"none":"prisma"}function Eo(e){return e==="lemon"||e==="lemonsqueezy"?"lemonsqueezy":e==="none"?"none":"stripe"}function ho(e){if(!e)return"stripe";let t=e.toLowerCase();return t==="lemon"||t==="lemonsqueezy"?"lemon":t==="wechat"?"wechat":t==="alipay"?"alipay":t==="none"?"none":"stripe"}function vo(e){if(!e)return"clerk";let t=e.toLowerCase();return t==="betterauth"||t==="better-auth"?"betterauth":t==="none"?"none":"clerk"}function _o(e){if(!e)return["openai"];let t=e.split(",").map(o=>o.trim().toLowerCase());return t.includes("none")?[]:t}var Po={mintlify:"Mintlify",docusaurus:"Docusaurus",nextra:"Nextra",vitepress:"VitePress"};function Io(e,t){if(!e)return"fumadocs";let o=e.toLowerCase();if(o==="fumadocs"||o==="none")return o;if(o in Po){let n=Po[o];return t?p(!0,{event:"notice",kind:"docs-fallback",requested:o,effective:"fumadocs"}):process.stdout.write(x.yellow(`\u26A0 ${n} support is coming in v1.2. Falling back to fumadocs.
2890
+ `)+x.dim(` Track progress: https://github.com/Nebutra/Nebutra-Sailor/issues
2891
+ `)),"fumadocs"}return"fumadocs"}function Co(e){switch(e){case"vercel":return"vercel";case"railway":return"railway";case"cloudflare":return"cloudflare";case"selfhost":return"selfhost";case"none":return"none";default:return"vercel"}}function bo(e){if(!e)return"global";let t=e.toLowerCase();return t==="cn"?"cn":t==="hybrid"?"hybrid":"global"}function ui(e){return{...e==="cn"?{email:"aliyun-dm",storage:"aliyun-oss",monitoring:"sentry",analytics:"baidu",sms:"aliyun-sms"}:e==="hybrid"?{email:"resend",storage:"aliyun-oss",monitoring:"sentry",analytics:"posthog",sms:"aliyun-sms"}:{email:"resend",storage:"r2",monitoring:"sentry",analytics:"posthog",sms:"twilio"},queue:"none",search:"none",cache:e==="global"?"upstash-redis":e==="cn"?"redis":"upstash-redis",notifications:"none",webhooks:"none",cms:"none",featureFlags:"none",captcha:e==="cn"?"aliyun-slide":"turnstile",mcp:"on",metering:"auto"}}function Ao(e){return e==="cn"?"wechat":"stripe"}async function mi(){let e=new pi;e.name(fo).description("Nebutra-Sailor \u2014 AI-Native SaaS template").version(se,"-v, --version").helpOption(!1).argument("[name]","project directory",void 0).option("-p, --pm <id>","npm | pnpm | yarn | bun").option("--region <id>","global | cn | hybrid").option("--orm <id>","prisma | drizzle | none").option("--db <id>","postgres | mysql | sqlite | none").option("--auth <id>","clerk | betterauth | none").option("--social-login <ids>","CN social login providers \u2014 wechat | qq | dingtalk | workweixin | feishu | weibo (comma-separated)").option("--payment <id>","stripe | lemon | wechat | alipay | none").option("--ai <ids>","comma-separated provider ids").option("--deploy <target>","vercel | railway | cloudflare | selfhost").option("--docs <id>","fumadocs | mintlify | docusaurus | nextra | vitepress | none").option("--email <id>","resend | postmark | ses | aliyun-dm | tencent-ses | netease | none").option("--storage <id>","r2 | s3 | supabase | aliyun-oss | tencent-cos | qiniu | none").option("--monitoring <id>","sentry | datadog | aliyun-arms | tingyun | none").option("--analytics <id>","posthog | plausible | umami | baidu | sensors | none").option("--sms <id>","twilio | aliyun-sms | tencent-sms | yunpian | none").option("--queue <id>","qstash | bullmq | upstash | sqs | none").option("--search <id>","meilisearch | typesense | algolia | pgvector | none").option("--cache <id>","upstash-redis | vercel-kv | redis | dragonfly | none").option("--notifications <id>","novu | knock | custom | none").option("--webhooks <id>","svix | custom | none").option("--cms <id>","sanity | contentful | strapi | none").option("--feature-flags <id>","vercel-flags | growthbook | configcat | none").option("--captcha <id>","turnstile | hcaptcha | aliyun-slide | none").option("--mcp <mode>","on | off (default: on)").option("--metering <mode>","auto | on | off (default: auto \u2014 auto-on when payment is set)").option("--i18n","enable i18n").option("--no-i18n","disable i18n").option("--no-install","skip package install").option("--no-git","skip git init").option("-y, --yes","accept all defaults (non-interactive)").option("--dry-run","preview actions without writing files").option("--json","machine-readable output").option("--no-color","disable color output").option("-h, --help","show help"),e.parse(process.argv);let t=e.opts(),[o]=e.args;t.help&&(Ht(),process.exit(0));let n=!!t.json,r=!!t.dryRun,s=!!t.yes,a=s||!process.stdin.isTTY;n||qt(),p(n,{event:"start",version:se});let c=o??(s?"./my-saas-app":void 0),i;if(c)i=c;else if(a)i="./my-saas-app";else{let g=await P.group({name:()=>P.text({message:"Where should we create your project?",placeholder:"./my-saas-app",defaultValue:"./my-saas-app",validate:S=>{if(S.length===0)return"Please enter a path."}})},{onCancel:()=>{process.stdout.write(x.red(`\u2718 Cancelled
2892
+ `)),process.exit(130)}});i=String(g.name)}let d=Yt.basename(Yt.resolve(i)),m=t.pm??di(),E=!!t.region,Pe=!!t.auth,Ie=!!t.payment,Le=!!t.ai,Me=!!t.email,y=!!t.storage,l=!!t.monitoring,b=!!t.analytics,rt=!!t.sms,Bt=t.i18n!==void 0,f,De,q,je,W,L,M=on(t.socialLogin),re,Ce,X,T,st;if(a){f=bo(t.region),De=So(t.orm),q=yo(t.db);let g=Ie?t.payment:Ao(f);je=Eo(g),W=ho(g),L=vo(t.auth),re=_o(t.ai),X=Co(t.deploy),T=Io(t.docs,n),st=Bt?!!t.i18n:!0}else{let g={};if(E||(g.region=()=>P.select({message:"Target region?",options:[{value:"global",label:"global \u2014 \u6D77\u5916\u4F18\u5148"},{value:"cn",label:"cn \u2014 \u56FD\u5185\u4F18\u5148"},{value:"hybrid",label:"hybrid \u2014 \u53CC\u8F68\uFF08\u56FD\u5185+\u51FA\u6D77\uFF09"}],initialValue:"global"})),Pe||(g.auth=()=>P.select({message:"Auth provider?",options:[{value:"clerk",label:"Clerk"},{value:"betterauth",label:"Better Auth"},{value:"none",label:"None"}],initialValue:"clerk"})),!Le){let pt=Array.from(new Set(ut.map(v=>v.category)));g.aiCategories=()=>P.multiselect({message:"Which AI Provider categories do you want to explore? (Select multiple)",options:pt.map(v=>({value:v,label:v})),initialValues:["\u76F4\u63A5\u5B9E\u9A8C\u5BA4","\u7EDF\u4E00\u7F51\u5173"]}),g.aiProviders=({results:v})=>{let D=v.aiCategories||[],lt=ut.filter(We=>D.includes(We.category)).map(We=>({value:We.id,label:`[${We.category}] ${We.name}`}));return lt.push({value:"custom",label:"[\u81EA\u5B9A\u4E49] Custom OpenAI-compatible endpoint"}),P.multiselect({message:"Select AI Providers (Space to select, Enter to submit)",options:lt,initialValues:["openai","anthropic"],required:!1})},g.enableCustom=({results:v})=>v.aiProviders?.includes("custom")?P.confirm({message:"Configure custom OpenAI-compatible endpoint?",initialValue:!0}):Promise.resolve(!1),g.customAiName=({results:v})=>v.enableCustom?P.text({message:"Custom endpoint name (e.g. proxy, local):",defaultValue:"custom",placeholder:"custom"}):Promise.resolve(void 0),g.customAiBaseUrl=({results:v})=>v.enableCustom?P.text({message:"Custom endpoint base URL (e.g. https://api.proxy.com/v1):",validate:D=>{if(D.length===0)return"Base URL is required."}}):Promise.resolve(void 0),g.customAiApiKeyEnv=({results:v})=>v.enableCustom?P.text({message:"Environment variable name for the API Key:",defaultValue:"CUSTOM_AI_API_KEY",placeholder:"CUSTOM_AI_API_KEY"}):Promise.resolve(void 0)}let S=Object.keys(g).length>0?await P.group(g,{onCancel:()=>{process.stdout.write(x.red(`\u2718 Cancelled
2893
+ `)),process.exit(130)}}):{};f=bo(E?t.region:S.region),De=So(t.orm),q=yo(t.db);let k=Ie?t.payment:Ao(f);je=Eo(k),W=ho(k),L=vo(Pe?t.auth:S.auth),re=Le?_o(t.ai):S.aiProviders?.filter(pt=>pt!=="custom")??[],S.enableCustom&&(Ce={name:S.customAiName,baseURL:S.customAiBaseUrl,apiKeyEnvName:S.customAiApiKeyEnv}),X=Co(t.deploy),T=Io(t.docs,n),st=Bt?!!t.i18n:!0}let C=ui(f),be=Me?t.email:C.email,Ae=y?t.storage:C.storage,Re=l?t.monitoring:C.monitoring,we=b?t.analytics:C.analytics,Ne=rt?t.sms:C.sms,Fe=t.queue??C.queue,Ve=t.search??C.search,Ke=t.cache??C.cache,Ye=t.notifications??C.notifications,Be=t.webhooks??C.webhooks,$e=t.cms??C.cms,qe=t.featureFlags??C.featureFlags,Ge=t.captcha??C.captcha,it=t.mcp??C.mcp,He=t.metering??C.metering,at={region:f,orm:De,database:q,payment:je,aiProviders:re,customAiEndpoint:Ce,deployTarget:X,docs:T,i18n:st,email:be,storage:Ae,monitoring:Re,analytics:we,sms:Ne,queue:Fe,search:Ve,cache:Ke,notifications:Ye,webhooks:Be,cms:$e,featureFlags:qe,captcha:Ge,mcp:it,metering:He},ct=[["Project name",d],["Region",f],["Auth",L],["Social login",M.length>0?M.map(g=>Xe.find(S=>S.id===g)?.name??g).join(", "):"none"],["ORM",De],["Database",q],["Payment",W],["AI",Array.isArray(re)?re.join(", ")+(Ce?", custom":""):String(re)],["Email",be],["Storage",Ae],["Monitoring",Re],["Analytics",we],["SMS",Ne],["Deploy Target",X],["Docs Framework",T]];n?ct.forEach(([g,S],k)=>{p(!0,{event:"step",step:g,value:S,index:k+1,total:ct.length})}):ct.forEach(([g,S],k)=>{Xt({index:k+1,total:ct.length,label:g,value:S})});let Ro={databaseUrl:"postgresql://postgres:postgres@localhost:5432/nebutra",clerkPublishable:"",clerkSecret:""},wo=Date.now();if(r){let g=re.length,S=[`clone template \u2192 ${i}`,"write nebutra.config.json",`prune template (orm=${De}, i18n=${st})`,`region \u2192 ${f}`,`auth \u2192 ${L==="none"?"skip (remove packages/auth)":`configure ${L}`}`,...M.length>0?[`social-login \u2192 generate ${M.length} callback route${M.length===1?"":"s"} + SocialLoginButtons.tsx (${M.join(", ")})`]:[],`db \u2192 ${q==="none"?"skip (remove packages/db)":`configure Prisma for ${q}`}`,`payment \u2192 ${W==="none"?"skip (remove packages/billing)":`configure ${W}`}`,...T!=="none"?[`docs \u2192 scaffold apps/docs (${T==="fumadocs"?"fumadocs":`${T} \u2192 fumadocs fallback`})`]:[],...g>0||Ce?[`ai-providers \u2192 generate registry.ts + env (${g}${Ce?" + custom":""} provider${g===1&&!Ce?"":"s"})`]:[],...be!=="none"?[`email \u2192 configure ${be}`]:[],...Ae!=="none"?[`storage \u2192 configure ${Ae}`]:[],...Re!=="none"?[`monitoring \u2192 configure ${Re}`]:[],...we!=="none"?[`analytics \u2192 configure ${we}`]:[],...Ne!=="none"?[`sms \u2192 configure ${Ne}`]:[],...Fe!=="none"?[`queue \u2192 configure ${Fe}`]:[],...Ve!=="none"?[`search \u2192 configure ${Ve}`]:[],...Ke!=="none"?[`cache \u2192 configure ${Ke}`]:[],...Ye!=="none"?[`notifications \u2192 configure ${Ye}`]:[],...Be!=="none"?[`webhooks \u2192 configure ${Be}`]:[],...$e!=="none"?[`cms \u2192 configure ${$e}`]:[],...qe!=="none"?[`feature-flags \u2192 configure ${qe}`]:[],...Ge!=="none"?[`captcha \u2192 configure ${Ge}`]:[],`mcp \u2192 ${it==="on"?"enable MCP server":"remove packages/mcp"}`,`metering \u2192 ${He==="off"?"disabled":He==="auto"?je!=="none"?"enabled (auto: payment set)":"disabled (auto: no payment)":"enabled"}`,`compliance \u2192 inject ${f} boilerplate (ICP/Cookie/AIGC/Privacy)`,"welcome \u2192 generate dev welcome page","env \u2192 generate random secrets (AUTH_SECRET, JWT_SECRET)","seed \u2192 generate prisma/seed.ts (1 admin + 3 tenants)",...X!=="none"?[`deploy \u2192 inject ${X} config`]:[],"inject .env.local",t.install===!1?"skip install":`run ${m} install`,t.git===!1?"skip git init":"run git init"];if(n)S.forEach(k=>p(!0,{event:"plan",action:k})),p(!0,{event:"done",dryRun:!0});else{process.stdout.write(`
2894
+ `+x.bold(`Dry run \u2014 planned actions:
2895
+ `));for(let k of S)process.stdout.write(` \u2022 ${k}
2896
+ `);process.stdout.write(x.dim(`
187
2897
  No files were written.
188
- `))}process.exit(0)}let it=()=>{process.stdout.write(`
189
- `+A.red(`\u2718 Cancelled
190
- `));try{Yt.existsSync(o)&&process.stdout.write(A.dim(` Partial directory left at ${o} \u2014 remove manually if needed.
191
- `))}catch{}process.exit(130)};process.on("SIGINT",it);try{u(s,{event:"step",step:"clone",status:"start"}),await qe(o),u(s,{event:"step",step:"clone",status:"ok"}),u(s,{event:"step",step:"package",status:"start"}),await Fe(o,m),u(s,{event:"step",step:"package",status:"ok"}),u(s,{event:"step",step:"config",status:"start"}),await we(o,te),u(s,{event:"step",step:"config",status:"ok"}),u(s,{event:"step",step:"prune",status:"start"}),await He(o,te),u(s,{event:"step",step:"prune",status:"ok"}),u(s,{event:"step",step:"auth",choice:N,status:"start"}),await Ae(o,N),u(s,{event:"step",step:"auth",choice:N,status:"ok"}),u(s,{event:"step",step:"db",choice:R,status:"start"}),await _e(o,R,m),u(s,{event:"step",step:"db",choice:R,status:"ok"}),u(s,{event:"step",step:"payment",choice:T,status:"start"}),await Ue(o,T),u(s,{event:"step",step:"payment",choice:T,status:"ok"}),u(s,{event:"step",step:"ai-providers",status:"start"});let d={providerIds:te.aiProviders,customEndpoint:te.customAiEndpoint},y=ye.join(o,"packages/ai-providers/templates");if(await Be(o,d,y),u(s,{event:"step",step:"ai-providers",status:"ok"}),u(s,{event:"step",step:"docs",status:"start"}),w!=="none"?(await Le(o,{framework:w,projectName:m}),u(s,{event:"step",step:"docs",framework:"fumadocs",requested:w,status:"ok"})):u(s,{event:"step",step:"docs",status:"skip"}),u(s,{event:"step",step:"deploy-target",status:"start"}),O!=="none"&&await Re(o,O),u(s,{event:"step",step:"deploy-target",status:"ok"}),u(s,{event:"step",step:"env",status:"start"}),await Ve(o,rt),u(s,{event:"step",step:"env",status:"ok"}),t.install!==!1){let I=f==="bun"?"bun install":`${f} install`;s?u(!0,{event:"step",step:"install",pm:f,status:"start"}):process.stdout.write(A.dim(` Installing dependencies with ${f}\u2026
192
- `));try{ce(I,{cwd:o,stdio:s?"ignore":"inherit"}),u(s,{event:"step",step:"install",status:"ok"})}catch(H){let re=H instanceof Error?H.message:String(H);s?u(!0,{event:"step",step:"install",status:"error",error:re}):process.stdout.write(A.yellow(` \u26A0 install failed \u2014 run '${I}' manually.
193
- `))}}else u(s,{event:"step",step:"install",status:"skip"});if(t.git!==!1){s&&u(!0,{event:"step",step:"git-init",status:"start"});try{ce("git init -q",{cwd:o,stdio:"ignore"}),ce("git add -A",{cwd:o,stdio:"ignore"}),ce('git -c user.email=you@example.com -c user.name="You" commit -q -m "chore: initial scaffold from create-sailor"',{cwd:o,stdio:"ignore"}),u(s,{event:"step",step:"git-init",status:"ok"})}catch(I){let H=I instanceof Error?I.message:String(I);s?u(!0,{event:"step",step:"git-init",status:"error",error:H}):process.stdout.write(A.yellow(` \u26A0 git init skipped \u2014 not fatal.
194
- `))}}else u(s,{event:"step",step:"git-init",status:"skip"});let P=Math.max(1,Math.round((Date.now()-ot)/1e3));s?u(!0,{event:"done",status:"ok",elapsedSec:P,targetDir:o}):he({elapsedSec:P,targetDir:o,skippedInstall:t.install===!1});try{Mt({pkg:{name:ze,version:U},updateCheckInterval:1e3*60*60*24}).notify({defer:!1,isGlobal:!0})}catch{}process.exit(0)}catch(d){let y=d instanceof Error?d.message:String(d);s?u(!0,{event:"error",message:y}):process.stdout.write(A.red(`
195
- \u2718 Failed: ${y}
196
- `)),process.exit(1)}}Wt().catch(e=>{process.stderr.write(String(e)+`
2898
+ `))}process.exit(0)}let No=async()=>{process.stdout.write(`
2899
+ `+x.red(`\u2718 Cancelled
2900
+ `)),go.existsSync(i)&&await P.confirm({message:`Cleanup partial install at ${i}?`,initialValue:!0})===!0&&(go.rmSync(i,{recursive:!0,force:!0}),process.stdout.write(x.dim(` \u2713 Removed ${i}
2901
+ `))),process.exit(130)};process.on("SIGINT",No);try{p(n,{event:"step",step:"clone",status:"start"}),await bn(i),p(n,{event:"step",step:"clone",status:"ok"}),p(n,{event:"step",step:"package",status:"start"}),await Nn(i,d),p(n,{event:"step",step:"package",status:"ok"}),p(n,{event:"step",step:"config",status:"start"}),await cn(i,at),p(n,{event:"step",step:"config",status:"ok"}),p(n,{event:"step",step:"prune",status:"start"}),await jn(i,at),p(n,{event:"step",step:"prune",status:"ok"}),p(n,{event:"step",step:"auth",choice:L,status:"start"}),await tn(i,L),p(n,{event:"step",step:"auth",choice:L,status:"ok"}),M.length>0?(p(n,{event:"step",step:"social-login",providers:M,status:"start"}),await sn(i,M,L),p(n,{event:"step",step:"social-login",providers:M,status:"ok"})):p(n,{event:"step",step:"social-login",status:"skip"}),p(n,{event:"step",step:"db",choice:q,status:"start"}),await dn(i,q,d),p(n,{event:"step",step:"db",choice:q,status:"ok"}),p(n,{event:"step",step:"payment",choice:W,status:"start"}),await Tn(i,W),p(n,{event:"step",step:"payment",choice:W,status:"ok"}),await En(i,be,f),n&&p(!0,{event:"step",step:"email",choice:be,status:"ok"}),await Bn(i,Ae,f),n&&p(!0,{event:"step",step:"storage",choice:Ae,status:"ok"}),await wn(i,Re,f),n&&p(!0,{event:"step",step:"monitoring",choice:Re,status:"ok"}),await Jt(i,we,f),n&&p(!0,{event:"step",step:"analytics",choice:we,status:"ok"}),await Kn(i,Ne,f),n&&p(!0,{event:"step",step:"sms",choice:Ne,status:"ok"}),await Gn(i,Fe,f),n&&p(!0,{event:"step",step:"queue",choice:Fe,status:"ok"}),await Xn(i,Ve,f),n&&p(!0,{event:"step",step:"search",choice:Ve,status:"ok"}),await Jn(i,Ke,f),n&&p(!0,{event:"step",step:"cache",choice:Ke,status:"ok"}),await eo(i,Ye,f),n&&p(!0,{event:"step",step:"notifications",choice:Ye,status:"ok"}),await no(i,Be,f),n&&p(!0,{event:"step",step:"webhooks",choice:Be,status:"ok"}),await ro(i,$e,f),n&&p(!0,{event:"step",step:"cms",choice:$e,status:"ok"}),await io(i,qe,f),n&&p(!0,{event:"step",step:"feature-flags",choice:qe,status:"ok"}),await co(i,Ge,f),n&&p(!0,{event:"step",step:"captcha",choice:Ge,status:"ok"}),await po(i,it),n&&p(!0,{event:"step",step:"mcp",mode:it,status:"ok"}),await uo(i,He,je),n&&p(!0,{event:"step",step:"metering",mode:He,status:"ok"}),await an(i,f),n&&p(!0,{event:"step",step:"compliance",region:f,status:"ok"}),await _n(i),await Fn(i,L),await mo(i,{projectName:d,region:f}),p(n,{event:"step",step:"ai-providers",status:"start"});let g={providerIds:at.aiProviders,customEndpoint:at.customAiEndpoint},S=Yt.join(i,"packages/ai-providers/templates");if(await Mn(i,g,S),p(n,{event:"step",step:"ai-providers",status:"ok"}),p(n,{event:"step",step:"docs",status:"start"}),T!=="none"?(await gn(i,{framework:T,projectName:d}),p(n,{event:"step",step:"docs",framework:"fumadocs",requested:T,status:"ok"})):p(n,{event:"step",step:"docs",status:"skip"}),p(n,{event:"step",step:"deploy-target",status:"start"}),X!=="none"&&await un(i,X),p(n,{event:"step",step:"deploy-target",status:"ok"}),p(n,{event:"step",step:"env",status:"start"}),await In(i,Ro),p(n,{event:"step",step:"env",status:"ok"}),t.install!==!1){let D=m==="bun"?"bun install":`${m} install`;n?p(!0,{event:"step",step:"install",pm:m,status:"start"}):process.stdout.write(x.dim(` Installing dependencies with ${m}\u2026
2902
+ `));try{At(D,{cwd:i,stdio:n?"ignore":"inherit"}),p(n,{event:"step",step:"install",status:"ok"})}catch(xe){let lt=xe instanceof Error?xe.message:String(xe);n?p(!0,{event:"step",step:"install",status:"error",error:lt}):process.stdout.write(x.yellow(` \u26A0 install failed \u2014 run '${D}' manually.
2903
+ `))}}else p(n,{event:"step",step:"install",status:"skip"});if(t.git!==!1){n&&p(!0,{event:"step",step:"git-init",status:"start"});try{At("git init -q",{cwd:i,stdio:"ignore"}),At("git add -A",{cwd:i,stdio:"ignore"}),At('git -c user.email=you@example.com -c user.name="You" commit -q -m "chore: initial scaffold from create-sailor"',{cwd:i,stdio:"ignore"}),p(n,{event:"step",step:"git-init",status:"ok"})}catch(D){let xe=D instanceof Error?D.message:String(D);n?p(!0,{event:"step",step:"git-init",status:"error",error:xe}):process.stdout.write(x.yellow(` \u26A0 git init skipped \u2014 not fatal.
2904
+ `))}}else p(n,{event:"step",step:"git-init",status:"skip"});let v=Math.max(1,Math.round((Date.now()-wo)/1e3));n?p(!0,{event:"done",status:"ok",elapsedSec:v,targetDir:i}):Gt({elapsedSec:v,targetDir:i,skippedInstall:t.install===!1});try{li({pkg:{name:fo,version:se},updateCheckInterval:1e3*60*60*24}).notify({defer:!1,isGlobal:!0})}catch{}process.exit(0)}catch(g){let S=g instanceof Error?g.message:String(g);n?p(!0,{event:"error",message:S}):process.stdout.write(x.red(`
2905
+ \u2718 Failed: ${S}
2906
+ `)),process.exit(1)}}mi().catch(e=>{process.stderr.write(String(e)+`
197
2907
  `),process.exit(1)});