create-spatius-app 0.1.3-beta.1 → 0.1.3-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +11 -10
  2. package/dist/cli.js +87 -162
  3. package/dist/templates.js +71 -154
  4. package/package.json +2 -2
  5. package/templates/cloudflare-livekit/AGENTS.md +2 -0
  6. package/templates/cloudflare-livekit/agent/README.md +13 -0
  7. package/templates/cloudflare-livekit/agent/pyproject.toml +1 -1
  8. package/templates/cloudflare-livekit/agent/src/agent.py +2 -1
  9. package/templates/cloudflare-livekit/agent/tests/test_agent_startup.py +49 -0
  10. package/templates/cloudflare-livekit/agent/uv.lock +14 -14
  11. package/templates/cloudflare-livekit/gitignore +1 -0
  12. package/templates/cloudflare-livekit/package-lock.json +177 -16
  13. package/templates/cloudflare-livekit/package.json +8 -7
  14. package/templates/cloudflare-livekit/pnpm-lock.yaml +104 -24
  15. package/templates/cloudflare-livekit/tests/dev-fixtures.mjs +7 -2
  16. package/templates/cloudflare-livekit/tests/dev-supervision.test.mjs +17 -14
  17. package/templates/layers/agora/AGENTS.md +9 -4
  18. package/templates/layers/agora/README.md +14 -8
  19. package/templates/layers/agora/package-lock.json +1344 -424
  20. package/templates/layers/agora/package.json +17 -11
  21. package/templates/layers/agora/pnpm-lock.yaml +1069 -237
  22. package/templates/layers/agora/scripts/check-worker.mjs +98 -0
  23. package/templates/layers/agora/tsconfig.worker.json +23 -0
  24. package/templates/layers/agora/worker/agora.test.ts +2 -0
  25. package/templates/layers/agora/worker/agora.ts +1 -10
  26. package/templates/layers/agora/worker/index.test.ts +66 -0
  27. package/templates/layers/agora/worker/index.ts +4 -0
  28. package/templates/layers/agora/wrangler.jsonc +24 -0
  29. package/templates/cloudflare-livekit/worker-configuration.d.ts +0 -17184
  30. package/templates/layers/agora/tsconfig.json +0 -16
  31. package/templates/layers/node/Dockerfile +0 -12
  32. package/templates/layers/node/dockerignore +0 -10
  33. package/templates/layers/node/package-lock.json +0 -5569
  34. package/templates/layers/node/pnpm-lock.yaml +0 -3470
  35. package/templates/layers/node/railway.json +0 -8
  36. package/templates/layers/node/scripts/dev-service.mjs +0 -14
  37. package/templates/layers/node/scripts/dev-web.mjs +0 -22
  38. package/templates/layers/node/server/index.ts +0 -92
  39. package/templates/layers/node/vite.config.ts +0 -17
  40. package/templates/layers/railway-agent/agent/railway.json +0 -5
  41. /package/templates/layers/agora/{env.example → dev.vars.example} +0 -0
package/README.md CHANGED
@@ -1,18 +1,18 @@
1
1
  # create-spatius-app
2
2
 
3
- Create a voice-avatar app with Spatius and React. Choose LiveKit with Cloudflare or Railway hosting, or Agora Conversational AI on Zeabur.
3
+ Create a voice-avatar app with Spatius and React. Choose LiveKit or Agora Conversational AI. Both templates run the frontend and API on Cloudflare Workers.
4
4
 
5
5
  ## Quick start
6
6
 
7
7
  Requires Node.js 22.12+ and a [Spatius Studio](https://app.spatius.ai) account.
8
- LiveKit stacks also need Python 3.11–3.14 and a LiveKit Cloud project.
9
- The Agora stack needs an Agora account and no Python tooling.
8
+ The LiveKit template also needs Python 3.11–3.14 and a LiveKit Cloud project.
9
+ The Agora template needs an Agora account and no Python tooling.
10
10
 
11
11
  ```sh
12
12
  npx create-spatius-app my-app
13
13
  ```
14
14
 
15
- Choose a tech stack. Follow the remaining prompts to choose package managers, install dependencies, and
15
+ Choose LiveKit or Agora Conversational AI. Follow the remaining prompts to choose package managers, install dependencies, and
16
16
  configure your provider and Spatius credentials.
17
17
  If LiveKit CLI is missing, setup offers to install it using Homebrew on macOS,
18
18
  WinGet on Windows, or LiveKit's Linux installer. You can also enter credentials
@@ -26,7 +26,7 @@ pnpm dev
26
26
  # Or: npm run dev / bun run dev
27
27
  ```
28
28
 
29
- This starts the frontend, API, and, for LiveKit stacks, the Python agent together. Generated DEPLOYMENT.md covers your selected hosting stack.
29
+ This starts the frontend, API, and, for LiveKit, the Python agent together. Generated DEPLOYMENT.md covers deployment to Cloudflare Workers and, for LiveKit, LiveKit Cloud.
30
30
  Open the URL printed in your terminal and start a conversation.
31
31
 
32
32
  ## Use with a coding agent
@@ -47,15 +47,16 @@ npx create-spatius-app setup . --interactive
47
47
 
48
48
  This configures local development only; it does not deploy your app.
49
49
 
50
- ## Choose a stack
50
+ ## Choose a provider
51
51
 
52
52
  ```sh
53
- npx create-spatius-app my-app --stack railway-livekit
53
+ npx create-spatius-app my-app --stack cloudflare-livekit
54
+ # Or:
55
+ npx create-spatius-app my-app --stack cloudflare-agora
54
56
  ```
55
57
 
56
- Cloudflare and Railway web hosting can each be paired with LiveKit Cloud Agents
57
- or a LiveKit agent on Railway. The default is Cloudflare + LiveKit Cloud Agents.
58
- The `zeabur-agora` stack requires no Python tooling.
58
+ LiveKit is the default and deploys its Python agent to LiveKit Cloud.
59
+ Agora Conversational AI hosts the agent itself; its template needs no Python.
59
60
 
60
61
  ## More options
61
62
 
package/dist/cli.js CHANGED
@@ -107,7 +107,11 @@ async function selectCatalog(options) {
107
107
  label: stack.label
108
108
  }));
109
109
  return validateSelection(
110
- options.interactive ? await options.prompts.choose("Which stack?", choices, DEFAULT_STACK) : DEFAULT_STACK
110
+ options.interactive ? await options.prompts.choose(
111
+ "Which voice AI provider?",
112
+ choices,
113
+ DEFAULT_STACK
114
+ ) : DEFAULT_STACK
111
115
  );
112
116
  }
113
117
  var stacks, DEFAULT_STACK;
@@ -117,32 +121,14 @@ var init_catalog = __esm({
117
121
  init_errors();
118
122
  stacks = {
119
123
  "cloudflare-livekit": {
120
- label: "Cloudflare + LiveKit Cloud Agents",
124
+ label: "LiveKit",
121
125
  web: "cloudflare",
122
126
  agent: "livekit",
123
127
  provider: "livekit"
124
128
  },
125
- "cloudflare-livekit-railway": {
126
- label: "Cloudflare + LiveKit Agents on Railway",
129
+ "cloudflare-agora": {
130
+ label: "Agora Conversational AI",
127
131
  web: "cloudflare",
128
- agent: "railway",
129
- provider: "livekit"
130
- },
131
- "railway-livekit-cloud": {
132
- label: "Railway + LiveKit Cloud Agents",
133
- web: "railway",
134
- agent: "livekit",
135
- provider: "livekit"
136
- },
137
- "railway-livekit": {
138
- label: "Railway + LiveKit Agents on Railway",
139
- web: "railway",
140
- agent: "railway",
141
- provider: "livekit"
142
- },
143
- "zeabur-agora": {
144
- label: "Zeabur + Agora Conversational AI",
145
- web: "zeabur",
146
132
  agent: "agora",
147
133
  provider: "agora"
148
134
  }
@@ -661,8 +647,8 @@ async function readProjectConfig(directory) {
661
647
  }
662
648
  }
663
649
  async function webEnvironmentPath(directory) {
664
- const config = await readProjectConfig(directory);
665
- return config && !config.stack.startsWith("cloudflare") ? ".env.local" : ".dev.vars";
650
+ await readProjectConfig(directory);
651
+ return ".dev.vars";
666
652
  }
667
653
  var init_project_config = __esm({
668
654
  "src/project-config.ts"() {
@@ -14817,7 +14803,7 @@ var program = new Command();
14817
14803
 
14818
14804
  // src/cli.ts
14819
14805
  init_errors();
14820
- import { readFile as readFile8 } from "fs/promises";
14806
+ import { readFile as readFile7 } from "fs/promises";
14821
14807
  import process5 from "process";
14822
14808
  import { fileURLToPath as fileURLToPath2 } from "url";
14823
14809
 
@@ -14865,6 +14851,10 @@ import { spawn as spawn3 } from "child_process";
14865
14851
  init_project_config();
14866
14852
  init_catalog();
14867
14853
 
14854
+ // src/templates/composed.ts
14855
+ import { writeFile as writeFile6 } from "fs/promises";
14856
+ import { join as join9 } from "path";
14857
+
14868
14858
  // src/templates/agora.ts
14869
14859
  init_package_managers();
14870
14860
  import { readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
@@ -15146,6 +15136,7 @@ var cloudflareLivekitTemplate = {
15146
15136
  "- agent/ Python LiveKit agent"
15147
15137
  ],
15148
15138
  includeFile(path2, configuration) {
15139
+ if (path2 === "worker-configuration.d.ts") return false;
15149
15140
  const javascript = configuration?.packageManagers.javascript.name ?? "pnpm";
15150
15141
  const python = configuration?.packageManagers.python?.name ?? "uv";
15151
15142
  if (path2 === "package-lock.json") return javascript === "npm";
@@ -15225,17 +15216,13 @@ var runAgoraSetup = async ({
15225
15216
  { exitCode: EXIT_CODES.invalidArgument }
15226
15217
  );
15227
15218
  const config = await readProjectConfig(targetDirectory);
15228
- if (config?.stack !== "zeabur-agora")
15219
+ if (config?.stack !== "cloudflare-agora")
15229
15220
  throw new CliError(
15230
15221
  "INVALID_ARGUMENT",
15231
- "Expected a Zeabur + Agora project.",
15222
+ "Expected a Cloudflare + Agora project.",
15232
15223
  { exitCode: EXIT_CODES.invalidArgument }
15233
15224
  );
15234
- for (const name of [
15235
- "package.json",
15236
- ".env.local.example",
15237
- "worker/agora.ts"
15238
- ]) {
15225
+ for (const name of ["package.json", ".dev.vars.example", "worker/agora.ts"]) {
15239
15226
  const stats = await lstat4(join7(targetDirectory, name));
15240
15227
  if (!stats.isFile() || stats.isSymbolicLink())
15241
15228
  throw new CliError(
@@ -15244,7 +15231,7 @@ var runAgoraSetup = async ({
15244
15231
  { exitCode: EXIT_CODES.invalidArgument }
15245
15232
  );
15246
15233
  }
15247
- const path2 = join7(targetDirectory, ".env.local");
15234
+ const path2 = join7(targetDirectory, ".dev.vars");
15248
15235
  let existing;
15249
15236
  try {
15250
15237
  const stats = await lstat4(path2);
@@ -15253,7 +15240,7 @@ var runAgoraSetup = async ({
15253
15240
  existing = await readFile5(path2, "utf8");
15254
15241
  } catch (error) {
15255
15242
  if (!(error instanceof Error && "code" in error && error.code === "ENOENT"))
15256
- throw new CliError("FILESYSTEM_ERROR", "Cannot safely read .env.local.", {
15243
+ throw new CliError("FILESYSTEM_ERROR", "Cannot safely read .dev.vars.", {
15257
15244
  exitCode: EXIT_CODES.filesystem,
15258
15245
  path: path2
15259
15246
  });
@@ -15305,10 +15292,10 @@ var runAgoraSetup = async ({
15305
15292
  }
15306
15293
  }
15307
15294
  const rendered = mergeDotenv(
15308
- existing ?? await readFile5(join7(targetDirectory, ".env.local.example"), "utf8"),
15295
+ existing ?? await readFile5(join7(targetDirectory, ".dev.vars.example"), "utf8"),
15309
15296
  values
15310
15297
  );
15311
- const temporary = join7(targetDirectory, `.env.local.${randomUUID3()}.tmp`);
15298
+ const temporary = join7(targetDirectory, `.dev.vars.${randomUUID3()}.tmp`);
15312
15299
  try {
15313
15300
  await writeFile4(temporary, rendered, { flag: "wx", mode: 384 });
15314
15301
  await rename2(temporary, path2);
@@ -15316,7 +15303,7 @@ var runAgoraSetup = async ({
15316
15303
  await rm3(temporary, { force: true });
15317
15304
  }
15318
15305
  onStatus(
15319
- "Saved .env.local. Configure an English assistant in the published Agora pipeline, with its TTS sample rate matching AGORA_AVATAR_SAMPLE_RATE. No cloud services were started."
15306
+ "Saved .dev.vars. Configure an English assistant in the published Agora pipeline, with its TTS sample rate matching AGORA_AVATAR_SAMPLE_RATE. No cloud services were started."
15320
15307
  );
15321
15308
  return "configured";
15322
15309
  };
@@ -15330,8 +15317,6 @@ var files = /* @__PURE__ */ new Set([
15330
15317
  "pnpm-workspace.yaml",
15331
15318
  "npmrc",
15332
15319
  "gitignore",
15333
- "dockerignore",
15334
- "Dockerfile",
15335
15320
  ".editorconfig",
15336
15321
  ".prettierrc.json",
15337
15322
  ".prettierignore",
@@ -15341,12 +15326,17 @@ var files = /* @__PURE__ */ new Set([
15341
15326
  "spatius.config.json",
15342
15327
  "index.html",
15343
15328
  "tsconfig.json",
15329
+ "tsconfig.web.json",
15330
+ "tsconfig.worker.json",
15331
+ "tsconfig.node.json",
15344
15332
  "vite.config.ts",
15345
15333
  "vitest.config.ts",
15346
15334
  "eslint.config.mjs",
15347
- "env.example",
15348
- "server/index.ts",
15335
+ "dev.vars.example",
15336
+ "wrangler.jsonc",
15337
+ "scripts/check-worker.mjs",
15349
15338
  "worker/index.ts",
15339
+ "worker/index.test.ts",
15350
15340
  "worker/agora.ts",
15351
15341
  "worker/agora.test.ts",
15352
15342
  "worker/capability.ts",
@@ -15364,8 +15354,8 @@ var files = /* @__PURE__ */ new Set([
15364
15354
  "web/components/agents-ui/PROVENANCE.md"
15365
15355
  ]);
15366
15356
  var agoraTemplate = {
15367
- id: "zeabur-agora",
15368
- stack: "zeabur-agora",
15357
+ id: "cloudflare-agora",
15358
+ stack: "cloudflare-agora",
15369
15359
  requiresPython: false,
15370
15360
  humanSteps: [
15371
15361
  {
@@ -15376,17 +15366,13 @@ var agoraTemplate = {
15376
15366
  }
15377
15367
  ],
15378
15368
  directory: "templates/cloudflare-livekit",
15379
- description: "Zeabur + Agora Conversational AI",
15369
+ description: "Cloudflare Workers + Agora Conversational AI",
15380
15370
  components: [
15381
15371
  "- web/ React + Spatius AvatarKit + Agora",
15382
- "- server/ Node HTTP API; Agora hosts the conversational agent"
15372
+ "- worker/ Cloudflare API; Agora hosts the conversational agent"
15383
15373
  ],
15384
15374
  layers: [
15385
15375
  { directory: "templates/layers/catalog" },
15386
- {
15387
- directory: "templates/layers/node",
15388
- overrides: ["vite.config.ts", "package-lock.json", "pnpm-lock.yaml"]
15389
- },
15390
15376
  {
15391
15377
  directory: "templates/layers/agora",
15392
15378
  overrides: [
@@ -15395,7 +15381,10 @@ var agoraTemplate = {
15395
15381
  "pnpm-lock.yaml",
15396
15382
  "web/App.tsx",
15397
15383
  "worker/index.ts",
15398
- "tsconfig.json",
15384
+ "worker/index.test.ts",
15385
+ "tsconfig.worker.json",
15386
+ ".dev.vars.example",
15387
+ "wrangler.jsonc",
15399
15388
  "vitest.config.ts",
15400
15389
  "README.md",
15401
15390
  "AGENTS.md"
@@ -15407,7 +15396,7 @@ var agoraTemplate = {
15407
15396
  return files.has(path2) || [...files].some((file) => file.startsWith(path2 + "/")) || path2 === "web/assets" || path2.startsWith("web/assets/");
15408
15397
  },
15409
15398
  mapFile(path2) {
15410
- return path2 === "env.example" ? ".env.local.example" : cloudflareLivekitTemplate.mapFile(path2);
15399
+ return cloudflareLivekitTemplate.mapFile(path2);
15411
15400
  },
15412
15401
  async configure(directory, configuration) {
15413
15402
  const manager = configuration.packageManagers.javascript;
@@ -15419,8 +15408,20 @@ var agoraTemplate = {
15419
15408
  );
15420
15409
  if (manager.version && manager.version !== "unknown")
15421
15410
  pkg.packageManager = `${manager.name}@${manager.version}`;
15422
- pkg.scripts.check = ["format:check", "lint", "typecheck", "test", "build"].map(run).join(" && ");
15423
- if (manager.name === "bun") pkg.trustedDependencies = ["esbuild"];
15411
+ else delete pkg.packageManager;
15412
+ pkg.scripts.check = [
15413
+ "format:check",
15414
+ "lint",
15415
+ "typecheck",
15416
+ "test",
15417
+ "build",
15418
+ "test:worker"
15419
+ ].map(run).join(" && ");
15420
+ pkg.scripts.deploy = `${run("build")} && wrangler deploy`;
15421
+ if (manager.name === "bun")
15422
+ pkg.trustedDependencies = ["core-js", "esbuild", "workerd"];
15423
+ else if (manager.name === "npm" && Number.parseInt(manager.version ?? "", 10) >= 12)
15424
+ pkg.allowScripts = { "core-js": true, esbuild: true, workerd: true };
15424
15425
  await writeFile5(path2, JSON.stringify(pkg, null, 2) + "\n");
15425
15426
  if (manager.name === "npm") {
15426
15427
  const lockPath = join8(directory, "package-lock.json");
@@ -15431,24 +15432,7 @@ var agoraTemplate = {
15431
15432
  }
15432
15433
  await writeFile5(
15433
15434
  join8(directory, "spatius.config.json"),
15434
- JSON.stringify({ version: 2, stack: "zeabur-agora" }, null, 2) + "\n"
15435
- );
15436
- const serverPath = join8(directory, "server/index.ts");
15437
- await writeFile5(
15438
- serverPath,
15439
- (await readFile6(serverPath, "utf8")).replace(
15440
- "import { handleRequest }",
15441
- "import type { AgoraEnvironment } from '../worker/agora.js';\nimport { handleRequest }"
15442
- ).replace(
15443
- "const env = process.env;",
15444
- "const env = process.env as unknown as AgoraEnvironment;"
15445
- )
15446
- );
15447
- const dockerPath = join8(directory, "Dockerfile");
15448
- const install = manager.name === "pnpm" ? "npm install --global pnpm@12.3.4 && pnpm install --frozen-lockfile" : manager.name === "bun" ? "npm install --global bun && bun install" : "npm ci";
15449
- await writeFile5(
15450
- dockerPath,
15451
- (await readFile6(dockerPath, "utf8")).replace("__SPATIUS_CONTAINER_INSTALL__", install).replace("__SPATIUS_CONTAINER_BUILD__", run("build"))
15435
+ JSON.stringify({ version: 2, stack: "cloudflare-agora" }, null, 2) + "\n"
15452
15436
  );
15453
15437
  for (const name of ["README.md", "AGENTS.md"]) {
15454
15438
  const path3 = join8(directory, name);
@@ -15459,7 +15443,16 @@ var agoraTemplate = {
15459
15443
  }
15460
15444
  await writeFile5(
15461
15445
  join8(directory, "DEPLOYMENT.md"),
15462
- "# Zeabur + Agora Conversational AI\n\nCreate one Zeabur service from this repository with the repository root as build root. Set ZBPACK_DOCKERFILE_PATH=Dockerfile and PORT=8787. Copy the environment variables from .env.local.example into Zeabur service variables, then generate an HTTPS domain. Use /api/health for the health check. Git redeployments rebuild the same service.\n\nThe Node service serves both frontend and API. There is no Python worker or database. Enable Agora Conversational AI and RTM, publish an English assistant pipeline, and set AGORA_PIPELINE_ID. The pipeline owns its models and voice. Match AGORA_AVATAR_SAMPLE_RATE to its TTS output.\n\nSession creation starts a hosted agent. Disconnect and initialization failures stop it through the backend. Page-close cleanup is best effort, with a 60-second idle timeout fallback. Sessions and client tokens last 30 minutes; start a fresh conversation afterward.\n\nLocal setup writes .env.local only. It never provisions Zeabur or uploads secrets.\n"
15446
+ `# Cloudflare Workers + Agora Conversational AI
15447
+
15448
+ Run \`${run("wrangler -- login")}\`. Set your non-secret configuration from .dev.vars in the vars section of wrangler.jsonc. Upload AGORA_APP_CERTIFICATE and SPATIUS_API_KEY with \`${run("wrangler -- secret put AGORA_APP_CERTIFICATE")}\` (repeat for SPATIUS_API_KEY). Run \`${run("deploy")}\` to deploy the React frontend and API as one Cloudflare Worker.
15449
+
15450
+ Agora hosts the conversational agent. There is no Python worker, Node server, container, or database to deploy. Enable Agora Conversational AI and RTM, publish an English assistant pipeline, and set AGORA_PIPELINE_ID. The pipeline owns its models and voice. Match AGORA_AVATAR_SAMPLE_RATE to its TTS output.
15451
+
15452
+ Session creation starts a hosted agent. Disconnect and initialization failures stop it through the Worker API. Page-close cleanup is best effort, with a 60-second idle timeout fallback. Sessions and client tokens last 30 minutes; start a fresh conversation afterward.
15453
+
15454
+ Local setup writes .dev.vars only. It never provisions Cloudflare services or uploads secrets.
15455
+ `
15463
15456
  );
15464
15457
  },
15465
15458
  createInstallPlan(directory, managers, platform) {
@@ -15481,7 +15474,7 @@ var agoraTemplate = {
15481
15474
  ];
15482
15475
  },
15483
15476
  setup: {
15484
- recognizes: async (directory) => (await readProjectConfig(directory))?.stack === "zeabur-agora",
15477
+ recognizes: async (directory) => (await readProjectConfig(directory))?.stack === "cloudflare-agora",
15485
15478
  run: runAgoraSetup
15486
15479
  },
15487
15480
  verification: [
@@ -15491,105 +15484,33 @@ var agoraTemplate = {
15491
15484
  };
15492
15485
 
15493
15486
  // src/templates/composed.ts
15494
- init_catalog();
15495
- import { readFile as readFile7, writeFile as writeFile6 } from "fs/promises";
15496
- import { join as join9 } from "path";
15497
15487
  init_package_managers();
15498
15488
  function composeTemplate(stack) {
15499
- if (stack === "zeabur-agora") return agoraTemplate;
15500
- const selected = stacks[stack];
15501
- const node = selected.web === "railway";
15489
+ if (stack === "cloudflare-agora") return agoraTemplate;
15502
15490
  return {
15503
15491
  ...cloudflareLivekitTemplate,
15504
- id: stack,
15505
15492
  stack,
15506
15493
  requiresPython: true,
15507
- description: selected.label,
15508
- components: [
15509
- "- web/ React + Spatius AvatarKit",
15510
- `- ${node ? "server/" : "worker/"} ${selected.web} API`,
15511
- "- agent/ Python LiveKit agent"
15512
- ],
15513
- layers: [
15514
- { directory: "templates/layers/catalog" },
15515
- ...selected.agent === "railway" ? [{ directory: "templates/layers/railway-agent" }] : [],
15516
- ...node ? [
15517
- {
15518
- directory: "templates/layers/node",
15519
- overrides: [
15520
- "vite.config.ts",
15521
- "scripts/dev-web.mjs",
15522
- "package-lock.json",
15523
- "pnpm-lock.yaml"
15524
- ]
15525
- }
15526
- ] : []
15527
- ],
15528
- includeFile(path2, configuration) {
15529
- if (node && ["wrangler.jsonc"].includes(path2)) return false;
15530
- return cloudflareLivekitTemplate.includeFile(path2, configuration);
15531
- },
15532
- mapFile(path2) {
15533
- return node && path2 === "dev.vars.example" ? ".env.local.example" : cloudflareLivekitTemplate.mapFile(path2);
15534
- },
15494
+ layers: [{ directory: "templates/layers/catalog" }],
15535
15495
  async configure(directory, configuration) {
15536
15496
  await cloudflareLivekitTemplate.configure(directory, configuration);
15537
15497
  await writeFile6(
15538
15498
  join9(directory, "spatius.config.json"),
15539
15499
  JSON.stringify({ version: 2, stack }, null, 2) + "\n"
15540
15500
  );
15541
- const packagePath = join9(directory, "package.json");
15542
- const pkg = JSON.parse(await readFile7(packagePath, "utf8"));
15543
15501
  const manager = configuration.packageManagers.javascript.name;
15544
15502
  const run = (script) => javascriptRunCommand(manager, script);
15545
- let deployment = `# ${selected.label}
15503
+ await writeFile6(
15504
+ join9(directory, "DEPLOYMENT.md"),
15505
+ `# Cloudflare Workers + LiveKit Cloud
15546
15506
 
15547
- `;
15548
- if (node) {
15549
- for (const name of ["README.md", "AGENTS.md", "agent/README.md"]) {
15550
- const path2 = join9(directory, name);
15551
- await writeFile6(
15552
- path2,
15553
- (await readFile7(path2, "utf8")).replaceAll("Cloudflare Workers", "Railway").replaceAll("Cloudflare Worker", "Node API").replaceAll(".dev.vars", ".env.local") + "\nSee DEPLOYMENT.md for this stack\u2019s deployment steps.\n"
15554
- );
15555
- }
15556
- const workerConfigPath = join9(directory, "tsconfig.worker.json");
15557
- await writeFile6(
15558
- workerConfigPath,
15559
- (await readFile7(workerConfigPath, "utf8")).replace(
15560
- '"types": ["./worker-configuration.d.ts"]',
15561
- '"types": ["./worker-configuration.d.ts", "node"]'
15562
- ).replace(
15563
- '"include": ["worker", "worker-configuration.d.ts"]',
15564
- '"include": ["worker", "worker-configuration.d.ts", "server"]'
15565
- )
15566
- );
15567
- delete pkg.devDependencies["@cloudflare/vite-plugin"];
15568
- delete pkg.devDependencies.wrangler;
15569
- pkg.dependencies.tsx = "4.23.13";
15570
- delete pkg.scripts["cf-typegen"];
15571
- delete pkg.scripts.wrangler;
15572
- pkg.scripts.start = "node --env-file-if-exists=.env.local node_modules/tsx/dist/cli.mjs server/index.ts";
15573
- pkg.scripts.deploy = "railway up";
15574
- const dockerPath = join9(directory, "Dockerfile");
15575
- const install = manager === "pnpm" ? "npm install --global pnpm@12.3.4 && pnpm install --frozen-lockfile" : manager === "bun" ? "npm install --global bun && bun install" : "npm ci";
15576
- await writeFile6(
15577
- dockerPath,
15578
- (await readFile7(dockerPath, "utf8")).replace("__SPATIUS_CONTAINER_INSTALL__", install).replace("__SPATIUS_CONTAINER_BUILD__", run("build"))
15579
- );
15580
- deployment += "Create a Railway service from this repository with the repository root as its build root and railway.json as its configuration. Add the variables from .env.local.example in Railway. Generate a public domain. The web service serves both the frontend and /api. Deploy with `railway up`; subsequent Git deployments rebuild the service.\n\n";
15581
- } else {
15582
- deployment += `Run \`${run("wrangler -- login")}\`, set the non-secret configuration from .dev.vars.example in wrangler.jsonc, and upload LIVEKIT_API_KEY and LIVEKIT_API_SECRET using \`${run("wrangler -- secret put LIVEKIT_API_SECRET")}\` (repeat for the key). Run \`${run("deploy")}\`.
15507
+ Run \`${run("wrangler -- login")}\`, set the non-secret configuration from .dev.vars.example in wrangler.jsonc, and upload LIVEKIT_API_KEY and LIVEKIT_API_SECRET using \`${run("wrangler -- secret put LIVEKIT_API_SECRET")}\` (repeat for the key). Run \`${run("deploy")}\` to deploy the frontend and API together.
15583
15508
 
15584
- `;
15585
- }
15586
- if (selected.agent === "railway") {
15587
- deployment += "Create a separate Railway agent service from this repository, with root directory /agent and configuration /agent/railway.json. Add the variables from agent/.env.example. Keep it running continuously; do not enable sleeping. No public domain is needed. Set PORT=8081 for the agent health check. Deploy this service independently of the web service.\n";
15588
- } else
15589
- deployment += "From agent/, run `lk cloud auth` and `lk agent create` for the first deployment. Configure the agent secrets from agent/.env.example. Use `lk agent deploy` for updates.\n";
15590
- deployment += "\nDeploy the agent before opening conversations. Use separate LiveKit projects for development and production. Local setup never uploads secrets.\n";
15591
- await writeFile6(join9(directory, "DEPLOYMENT.md"), deployment);
15592
- await writeFile6(packagePath, JSON.stringify(pkg, null, 2) + "\n");
15509
+ From agent/, run \`lk cloud auth\` and \`lk agent create\` for the first deployment. Configure the agent secrets from agent/.env.example. Use \`lk agent deploy\` for updates.
15510
+
15511
+ Deploy the agent before opening conversations. Use separate LiveKit projects for development and production. Local setup never uploads secrets.
15512
+ `
15513
+ );
15593
15514
  }
15594
15515
  };
15595
15516
  }
@@ -16643,7 +16564,7 @@ async function readPackageVersion() {
16643
16564
  const packagePath = fileURLToPath2(
16644
16565
  new URL("../package.json", import.meta.url)
16645
16566
  );
16646
- const contents = await readFile8(packagePath, "utf8");
16567
+ const contents = await readFile7(packagePath, "utf8");
16647
16568
  const metadata = JSON.parse(contents);
16648
16569
  return metadata.version;
16649
16570
  }
@@ -17070,9 +16991,10 @@ async function runSetupCommand(version, projectDirectoryArgument, rawOptions) {
17070
16991
  }
17071
16992
  function addCreateOptions(command) {
17072
16993
  return command.addOption(
17073
- new Option("--stack <stack>", "application and deployment stack").choices(
17074
- Object.keys(stacks)
17075
- )
16994
+ new Option(
16995
+ "--stack <stack>",
16996
+ "voice provider on Cloudflare Workers"
16997
+ ).choices(Object.keys(stacks))
17076
16998
  ).option("-y, --yes", "accept safe defaults without prompting").addOption(new Option("--interactive", "force interactive prompts")).addOption(new Option("--no-interactive", "disable interactive prompts")).addOption(
17077
16999
  new Option(
17078
17000
  "--package-manager <manager>",
@@ -17083,12 +17005,14 @@ function addCreateOptions(command) {
17083
17005
  "--python-package-manager <manager>",
17084
17006
  "use uv or pip for Python dependencies"
17085
17007
  ).choices(PYTHON_PACKAGE_MANAGERS)
17086
- ).option("--install", "install JavaScript and Python dependencies").option("--no-install", "skip dependency installation").option("--setup", "configure local provider and Spatius credentials").option("--no-setup", "skip credential setup").option("--debug", "write safe Spatius request diagnostics to stderr").option("--json", "emit one machine-readable JSON result").option("--dry-run", "validate and show planned files without writing");
17008
+ ).option("--install", "install dependencies for the selected provider").option("--no-install", "skip dependency installation").option("--setup", "configure local provider and Spatius credentials").option("--no-setup", "skip credential setup").option("--debug", "write safe Spatius request diagnostics to stderr").option("--json", "emit one machine-readable JSON result").option("--dry-run", "validate and show planned files without writing");
17087
17009
  }
17088
17010
  async function main() {
17089
17011
  const version = await readPackageVersion();
17090
17012
  const program2 = addCreateOptions(
17091
- new Command().name("create-spatius-app").description(getTemplate().description).version(version).argument(
17013
+ new Command().name("create-spatius-app").description(
17014
+ "Create a Spatius voice-avatar app with LiveKit or Agora Conversational AI on Cloudflare Workers."
17015
+ ).version(version).argument(
17092
17016
  "[project-directory]",
17093
17017
  "directory in which to create the project"
17094
17018
  )
@@ -17098,6 +17022,7 @@ async function main() {
17098
17022
  `
17099
17023
  Examples:
17100
17024
  npx create-spatius-app my-app
17025
+ npx create-spatius-app my-app --stack cloudflare-agora
17101
17026
  npx create-spatius-app my-app --package-manager pnpm --python-package-manager uv
17102
17027
  npx create-spatius-app my-app --no-setup
17103
17028
  npx create-spatius-app my-app --setup --interactive