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.
- package/README.md +11 -10
- package/dist/cli.js +87 -162
- package/dist/templates.js +71 -154
- package/package.json +2 -2
- package/templates/cloudflare-livekit/AGENTS.md +2 -0
- package/templates/cloudflare-livekit/agent/README.md +13 -0
- package/templates/cloudflare-livekit/agent/pyproject.toml +1 -1
- package/templates/cloudflare-livekit/agent/src/agent.py +2 -1
- package/templates/cloudflare-livekit/agent/tests/test_agent_startup.py +49 -0
- package/templates/cloudflare-livekit/agent/uv.lock +14 -14
- package/templates/cloudflare-livekit/gitignore +1 -0
- package/templates/cloudflare-livekit/package-lock.json +177 -16
- package/templates/cloudflare-livekit/package.json +8 -7
- package/templates/cloudflare-livekit/pnpm-lock.yaml +104 -24
- package/templates/cloudflare-livekit/tests/dev-fixtures.mjs +7 -2
- package/templates/cloudflare-livekit/tests/dev-supervision.test.mjs +17 -14
- package/templates/layers/agora/AGENTS.md +9 -4
- package/templates/layers/agora/README.md +14 -8
- package/templates/layers/agora/package-lock.json +1344 -424
- package/templates/layers/agora/package.json +17 -11
- package/templates/layers/agora/pnpm-lock.yaml +1069 -237
- package/templates/layers/agora/scripts/check-worker.mjs +98 -0
- package/templates/layers/agora/tsconfig.worker.json +23 -0
- package/templates/layers/agora/worker/agora.test.ts +2 -0
- package/templates/layers/agora/worker/agora.ts +1 -10
- package/templates/layers/agora/worker/index.test.ts +66 -0
- package/templates/layers/agora/worker/index.ts +4 -0
- package/templates/layers/agora/wrangler.jsonc +24 -0
- package/templates/cloudflare-livekit/worker-configuration.d.ts +0 -17184
- package/templates/layers/agora/tsconfig.json +0 -16
- package/templates/layers/node/Dockerfile +0 -12
- package/templates/layers/node/dockerignore +0 -10
- package/templates/layers/node/package-lock.json +0 -5569
- package/templates/layers/node/pnpm-lock.yaml +0 -3470
- package/templates/layers/node/railway.json +0 -8
- package/templates/layers/node/scripts/dev-service.mjs +0 -14
- package/templates/layers/node/scripts/dev-web.mjs +0 -22
- package/templates/layers/node/server/index.ts +0 -92
- package/templates/layers/node/vite.config.ts +0 -17
- package/templates/layers/railway-agent/agent/railway.json +0 -5
- /package/templates/layers/agora/{env.example → dev.vars.example} +0 -0
package/dist/templates.js
CHANGED
|
@@ -95,32 +95,14 @@ var init_catalog = __esm({
|
|
|
95
95
|
init_errors();
|
|
96
96
|
stacks = {
|
|
97
97
|
"cloudflare-livekit": {
|
|
98
|
-
label: "
|
|
98
|
+
label: "LiveKit",
|
|
99
99
|
web: "cloudflare",
|
|
100
100
|
agent: "livekit",
|
|
101
101
|
provider: "livekit"
|
|
102
102
|
},
|
|
103
|
-
"cloudflare-
|
|
104
|
-
label: "
|
|
103
|
+
"cloudflare-agora": {
|
|
104
|
+
label: "Agora Conversational AI",
|
|
105
105
|
web: "cloudflare",
|
|
106
|
-
agent: "railway",
|
|
107
|
-
provider: "livekit"
|
|
108
|
-
},
|
|
109
|
-
"railway-livekit-cloud": {
|
|
110
|
-
label: "Railway + LiveKit Cloud Agents",
|
|
111
|
-
web: "railway",
|
|
112
|
-
agent: "livekit",
|
|
113
|
-
provider: "livekit"
|
|
114
|
-
},
|
|
115
|
-
"railway-livekit": {
|
|
116
|
-
label: "Railway + LiveKit Agents on Railway",
|
|
117
|
-
web: "railway",
|
|
118
|
-
agent: "railway",
|
|
119
|
-
provider: "livekit"
|
|
120
|
-
},
|
|
121
|
-
"zeabur-agora": {
|
|
122
|
-
label: "Zeabur + Agora Conversational AI",
|
|
123
|
-
web: "zeabur",
|
|
124
106
|
agent: "agora",
|
|
125
107
|
provider: "agora"
|
|
126
108
|
}
|
|
@@ -158,8 +140,8 @@ async function readProjectConfig(directory) {
|
|
|
158
140
|
}
|
|
159
141
|
}
|
|
160
142
|
async function webEnvironmentPath(directory) {
|
|
161
|
-
|
|
162
|
-
return
|
|
143
|
+
await readProjectConfig(directory);
|
|
144
|
+
return ".dev.vars";
|
|
163
145
|
}
|
|
164
146
|
var init_project_config = __esm({
|
|
165
147
|
"src/project-config.ts"() {
|
|
@@ -9850,6 +9832,10 @@ var init_wizard = __esm({
|
|
|
9850
9832
|
init_project_config();
|
|
9851
9833
|
init_catalog();
|
|
9852
9834
|
|
|
9835
|
+
// src/templates/composed.ts
|
|
9836
|
+
import { writeFile as writeFile6 } from "fs/promises";
|
|
9837
|
+
import { join as join8 } from "path";
|
|
9838
|
+
|
|
9853
9839
|
// src/templates/agora.ts
|
|
9854
9840
|
init_package_managers();
|
|
9855
9841
|
import { readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
|
|
@@ -10131,6 +10117,7 @@ var cloudflareLivekitTemplate = {
|
|
|
10131
10117
|
"- agent/ Python LiveKit agent"
|
|
10132
10118
|
],
|
|
10133
10119
|
includeFile(path, configuration) {
|
|
10120
|
+
if (path === "worker-configuration.d.ts") return false;
|
|
10134
10121
|
const javascript = configuration?.packageManagers.javascript.name ?? "pnpm";
|
|
10135
10122
|
const python = configuration?.packageManagers.python?.name ?? "uv";
|
|
10136
10123
|
if (path === "package-lock.json") return javascript === "npm";
|
|
@@ -10210,17 +10197,13 @@ var runAgoraSetup = async ({
|
|
|
10210
10197
|
{ exitCode: EXIT_CODES.invalidArgument }
|
|
10211
10198
|
);
|
|
10212
10199
|
const config = await readProjectConfig(targetDirectory);
|
|
10213
|
-
if (config?.stack !== "
|
|
10200
|
+
if (config?.stack !== "cloudflare-agora")
|
|
10214
10201
|
throw new CliError(
|
|
10215
10202
|
"INVALID_ARGUMENT",
|
|
10216
|
-
"Expected a
|
|
10203
|
+
"Expected a Cloudflare + Agora project.",
|
|
10217
10204
|
{ exitCode: EXIT_CODES.invalidArgument }
|
|
10218
10205
|
);
|
|
10219
|
-
for (const name of [
|
|
10220
|
-
"package.json",
|
|
10221
|
-
".env.local.example",
|
|
10222
|
-
"worker/agora.ts"
|
|
10223
|
-
]) {
|
|
10206
|
+
for (const name of ["package.json", ".dev.vars.example", "worker/agora.ts"]) {
|
|
10224
10207
|
const stats = await lstat4(join6(targetDirectory, name));
|
|
10225
10208
|
if (!stats.isFile() || stats.isSymbolicLink())
|
|
10226
10209
|
throw new CliError(
|
|
@@ -10229,7 +10212,7 @@ var runAgoraSetup = async ({
|
|
|
10229
10212
|
{ exitCode: EXIT_CODES.invalidArgument }
|
|
10230
10213
|
);
|
|
10231
10214
|
}
|
|
10232
|
-
const path = join6(targetDirectory, ".
|
|
10215
|
+
const path = join6(targetDirectory, ".dev.vars");
|
|
10233
10216
|
let existing;
|
|
10234
10217
|
try {
|
|
10235
10218
|
const stats = await lstat4(path);
|
|
@@ -10238,7 +10221,7 @@ var runAgoraSetup = async ({
|
|
|
10238
10221
|
existing = await readFile5(path, "utf8");
|
|
10239
10222
|
} catch (error) {
|
|
10240
10223
|
if (!(error instanceof Error && "code" in error && error.code === "ENOENT"))
|
|
10241
|
-
throw new CliError("FILESYSTEM_ERROR", "Cannot safely read .
|
|
10224
|
+
throw new CliError("FILESYSTEM_ERROR", "Cannot safely read .dev.vars.", {
|
|
10242
10225
|
exitCode: EXIT_CODES.filesystem,
|
|
10243
10226
|
path
|
|
10244
10227
|
});
|
|
@@ -10290,10 +10273,10 @@ var runAgoraSetup = async ({
|
|
|
10290
10273
|
}
|
|
10291
10274
|
}
|
|
10292
10275
|
const rendered = mergeDotenv(
|
|
10293
|
-
existing ?? await readFile5(join6(targetDirectory, ".
|
|
10276
|
+
existing ?? await readFile5(join6(targetDirectory, ".dev.vars.example"), "utf8"),
|
|
10294
10277
|
values
|
|
10295
10278
|
);
|
|
10296
|
-
const temporary = join6(targetDirectory, `.
|
|
10279
|
+
const temporary = join6(targetDirectory, `.dev.vars.${randomUUID3()}.tmp`);
|
|
10297
10280
|
try {
|
|
10298
10281
|
await writeFile4(temporary, rendered, { flag: "wx", mode: 384 });
|
|
10299
10282
|
await rename2(temporary, path);
|
|
@@ -10301,7 +10284,7 @@ var runAgoraSetup = async ({
|
|
|
10301
10284
|
await rm3(temporary, { force: true });
|
|
10302
10285
|
}
|
|
10303
10286
|
onStatus(
|
|
10304
|
-
"Saved .
|
|
10287
|
+
"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."
|
|
10305
10288
|
);
|
|
10306
10289
|
return "configured";
|
|
10307
10290
|
};
|
|
@@ -10315,8 +10298,6 @@ var files = /* @__PURE__ */ new Set([
|
|
|
10315
10298
|
"pnpm-workspace.yaml",
|
|
10316
10299
|
"npmrc",
|
|
10317
10300
|
"gitignore",
|
|
10318
|
-
"dockerignore",
|
|
10319
|
-
"Dockerfile",
|
|
10320
10301
|
".editorconfig",
|
|
10321
10302
|
".prettierrc.json",
|
|
10322
10303
|
".prettierignore",
|
|
@@ -10326,12 +10307,17 @@ var files = /* @__PURE__ */ new Set([
|
|
|
10326
10307
|
"spatius.config.json",
|
|
10327
10308
|
"index.html",
|
|
10328
10309
|
"tsconfig.json",
|
|
10310
|
+
"tsconfig.web.json",
|
|
10311
|
+
"tsconfig.worker.json",
|
|
10312
|
+
"tsconfig.node.json",
|
|
10329
10313
|
"vite.config.ts",
|
|
10330
10314
|
"vitest.config.ts",
|
|
10331
10315
|
"eslint.config.mjs",
|
|
10332
|
-
"
|
|
10333
|
-
"
|
|
10316
|
+
"dev.vars.example",
|
|
10317
|
+
"wrangler.jsonc",
|
|
10318
|
+
"scripts/check-worker.mjs",
|
|
10334
10319
|
"worker/index.ts",
|
|
10320
|
+
"worker/index.test.ts",
|
|
10335
10321
|
"worker/agora.ts",
|
|
10336
10322
|
"worker/agora.test.ts",
|
|
10337
10323
|
"worker/capability.ts",
|
|
@@ -10349,8 +10335,8 @@ var files = /* @__PURE__ */ new Set([
|
|
|
10349
10335
|
"web/components/agents-ui/PROVENANCE.md"
|
|
10350
10336
|
]);
|
|
10351
10337
|
var agoraTemplate = {
|
|
10352
|
-
id: "
|
|
10353
|
-
stack: "
|
|
10338
|
+
id: "cloudflare-agora",
|
|
10339
|
+
stack: "cloudflare-agora",
|
|
10354
10340
|
requiresPython: false,
|
|
10355
10341
|
humanSteps: [
|
|
10356
10342
|
{
|
|
@@ -10361,17 +10347,13 @@ var agoraTemplate = {
|
|
|
10361
10347
|
}
|
|
10362
10348
|
],
|
|
10363
10349
|
directory: "templates/cloudflare-livekit",
|
|
10364
|
-
description: "
|
|
10350
|
+
description: "Cloudflare Workers + Agora Conversational AI",
|
|
10365
10351
|
components: [
|
|
10366
10352
|
"- web/ React + Spatius AvatarKit + Agora",
|
|
10367
|
-
"-
|
|
10353
|
+
"- worker/ Cloudflare API; Agora hosts the conversational agent"
|
|
10368
10354
|
],
|
|
10369
10355
|
layers: [
|
|
10370
10356
|
{ directory: "templates/layers/catalog" },
|
|
10371
|
-
{
|
|
10372
|
-
directory: "templates/layers/node",
|
|
10373
|
-
overrides: ["vite.config.ts", "package-lock.json", "pnpm-lock.yaml"]
|
|
10374
|
-
},
|
|
10375
10357
|
{
|
|
10376
10358
|
directory: "templates/layers/agora",
|
|
10377
10359
|
overrides: [
|
|
@@ -10380,7 +10362,10 @@ var agoraTemplate = {
|
|
|
10380
10362
|
"pnpm-lock.yaml",
|
|
10381
10363
|
"web/App.tsx",
|
|
10382
10364
|
"worker/index.ts",
|
|
10383
|
-
"
|
|
10365
|
+
"worker/index.test.ts",
|
|
10366
|
+
"tsconfig.worker.json",
|
|
10367
|
+
".dev.vars.example",
|
|
10368
|
+
"wrangler.jsonc",
|
|
10384
10369
|
"vitest.config.ts",
|
|
10385
10370
|
"README.md",
|
|
10386
10371
|
"AGENTS.md"
|
|
@@ -10392,7 +10377,7 @@ var agoraTemplate = {
|
|
|
10392
10377
|
return files.has(path) || [...files].some((file) => file.startsWith(path + "/")) || path === "web/assets" || path.startsWith("web/assets/");
|
|
10393
10378
|
},
|
|
10394
10379
|
mapFile(path) {
|
|
10395
|
-
return
|
|
10380
|
+
return cloudflareLivekitTemplate.mapFile(path);
|
|
10396
10381
|
},
|
|
10397
10382
|
async configure(directory, configuration) {
|
|
10398
10383
|
const manager = configuration.packageManagers.javascript;
|
|
@@ -10404,8 +10389,20 @@ var agoraTemplate = {
|
|
|
10404
10389
|
);
|
|
10405
10390
|
if (manager.version && manager.version !== "unknown")
|
|
10406
10391
|
pkg.packageManager = `${manager.name}@${manager.version}`;
|
|
10407
|
-
|
|
10408
|
-
|
|
10392
|
+
else delete pkg.packageManager;
|
|
10393
|
+
pkg.scripts.check = [
|
|
10394
|
+
"format:check",
|
|
10395
|
+
"lint",
|
|
10396
|
+
"typecheck",
|
|
10397
|
+
"test",
|
|
10398
|
+
"build",
|
|
10399
|
+
"test:worker"
|
|
10400
|
+
].map(run).join(" && ");
|
|
10401
|
+
pkg.scripts.deploy = `${run("build")} && wrangler deploy`;
|
|
10402
|
+
if (manager.name === "bun")
|
|
10403
|
+
pkg.trustedDependencies = ["core-js", "esbuild", "workerd"];
|
|
10404
|
+
else if (manager.name === "npm" && Number.parseInt(manager.version ?? "", 10) >= 12)
|
|
10405
|
+
pkg.allowScripts = { "core-js": true, esbuild: true, workerd: true };
|
|
10409
10406
|
await writeFile5(path, JSON.stringify(pkg, null, 2) + "\n");
|
|
10410
10407
|
if (manager.name === "npm") {
|
|
10411
10408
|
const lockPath = join7(directory, "package-lock.json");
|
|
@@ -10416,24 +10413,7 @@ var agoraTemplate = {
|
|
|
10416
10413
|
}
|
|
10417
10414
|
await writeFile5(
|
|
10418
10415
|
join7(directory, "spatius.config.json"),
|
|
10419
|
-
JSON.stringify({ version: 2, stack: "
|
|
10420
|
-
);
|
|
10421
|
-
const serverPath = join7(directory, "server/index.ts");
|
|
10422
|
-
await writeFile5(
|
|
10423
|
-
serverPath,
|
|
10424
|
-
(await readFile6(serverPath, "utf8")).replace(
|
|
10425
|
-
"import { handleRequest }",
|
|
10426
|
-
"import type { AgoraEnvironment } from '../worker/agora.js';\nimport { handleRequest }"
|
|
10427
|
-
).replace(
|
|
10428
|
-
"const env = process.env;",
|
|
10429
|
-
"const env = process.env as unknown as AgoraEnvironment;"
|
|
10430
|
-
)
|
|
10431
|
-
);
|
|
10432
|
-
const dockerPath = join7(directory, "Dockerfile");
|
|
10433
|
-
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";
|
|
10434
|
-
await writeFile5(
|
|
10435
|
-
dockerPath,
|
|
10436
|
-
(await readFile6(dockerPath, "utf8")).replace("__SPATIUS_CONTAINER_INSTALL__", install).replace("__SPATIUS_CONTAINER_BUILD__", run("build"))
|
|
10416
|
+
JSON.stringify({ version: 2, stack: "cloudflare-agora" }, null, 2) + "\n"
|
|
10437
10417
|
);
|
|
10438
10418
|
for (const name of ["README.md", "AGENTS.md"]) {
|
|
10439
10419
|
const path2 = join7(directory, name);
|
|
@@ -10444,7 +10424,16 @@ var agoraTemplate = {
|
|
|
10444
10424
|
}
|
|
10445
10425
|
await writeFile5(
|
|
10446
10426
|
join7(directory, "DEPLOYMENT.md"),
|
|
10447
|
-
|
|
10427
|
+
`# Cloudflare Workers + Agora Conversational AI
|
|
10428
|
+
|
|
10429
|
+
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.
|
|
10430
|
+
|
|
10431
|
+
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.
|
|
10432
|
+
|
|
10433
|
+
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.
|
|
10434
|
+
|
|
10435
|
+
Local setup writes .dev.vars only. It never provisions Cloudflare services or uploads secrets.
|
|
10436
|
+
`
|
|
10448
10437
|
);
|
|
10449
10438
|
},
|
|
10450
10439
|
createInstallPlan(directory, managers, platform) {
|
|
@@ -10466,7 +10455,7 @@ var agoraTemplate = {
|
|
|
10466
10455
|
];
|
|
10467
10456
|
},
|
|
10468
10457
|
setup: {
|
|
10469
|
-
recognizes: async (directory) => (await readProjectConfig(directory))?.stack === "
|
|
10458
|
+
recognizes: async (directory) => (await readProjectConfig(directory))?.stack === "cloudflare-agora",
|
|
10470
10459
|
run: runAgoraSetup
|
|
10471
10460
|
},
|
|
10472
10461
|
verification: [
|
|
@@ -10476,105 +10465,33 @@ var agoraTemplate = {
|
|
|
10476
10465
|
};
|
|
10477
10466
|
|
|
10478
10467
|
// src/templates/composed.ts
|
|
10479
|
-
init_catalog();
|
|
10480
|
-
import { readFile as readFile7, writeFile as writeFile6 } from "fs/promises";
|
|
10481
|
-
import { join as join8 } from "path";
|
|
10482
10468
|
init_package_managers();
|
|
10483
10469
|
function composeTemplate(stack) {
|
|
10484
|
-
if (stack === "
|
|
10485
|
-
const selected = stacks[stack];
|
|
10486
|
-
const node = selected.web === "railway";
|
|
10470
|
+
if (stack === "cloudflare-agora") return agoraTemplate;
|
|
10487
10471
|
return {
|
|
10488
10472
|
...cloudflareLivekitTemplate,
|
|
10489
|
-
id: stack,
|
|
10490
10473
|
stack,
|
|
10491
10474
|
requiresPython: true,
|
|
10492
|
-
|
|
10493
|
-
components: [
|
|
10494
|
-
"- web/ React + Spatius AvatarKit",
|
|
10495
|
-
`- ${node ? "server/" : "worker/"} ${selected.web} API`,
|
|
10496
|
-
"- agent/ Python LiveKit agent"
|
|
10497
|
-
],
|
|
10498
|
-
layers: [
|
|
10499
|
-
{ directory: "templates/layers/catalog" },
|
|
10500
|
-
...selected.agent === "railway" ? [{ directory: "templates/layers/railway-agent" }] : [],
|
|
10501
|
-
...node ? [
|
|
10502
|
-
{
|
|
10503
|
-
directory: "templates/layers/node",
|
|
10504
|
-
overrides: [
|
|
10505
|
-
"vite.config.ts",
|
|
10506
|
-
"scripts/dev-web.mjs",
|
|
10507
|
-
"package-lock.json",
|
|
10508
|
-
"pnpm-lock.yaml"
|
|
10509
|
-
]
|
|
10510
|
-
}
|
|
10511
|
-
] : []
|
|
10512
|
-
],
|
|
10513
|
-
includeFile(path, configuration) {
|
|
10514
|
-
if (node && ["wrangler.jsonc"].includes(path)) return false;
|
|
10515
|
-
return cloudflareLivekitTemplate.includeFile(path, configuration);
|
|
10516
|
-
},
|
|
10517
|
-
mapFile(path) {
|
|
10518
|
-
return node && path === "dev.vars.example" ? ".env.local.example" : cloudflareLivekitTemplate.mapFile(path);
|
|
10519
|
-
},
|
|
10475
|
+
layers: [{ directory: "templates/layers/catalog" }],
|
|
10520
10476
|
async configure(directory, configuration) {
|
|
10521
10477
|
await cloudflareLivekitTemplate.configure(directory, configuration);
|
|
10522
10478
|
await writeFile6(
|
|
10523
10479
|
join8(directory, "spatius.config.json"),
|
|
10524
10480
|
JSON.stringify({ version: 2, stack }, null, 2) + "\n"
|
|
10525
10481
|
);
|
|
10526
|
-
const packagePath = join8(directory, "package.json");
|
|
10527
|
-
const pkg = JSON.parse(await readFile7(packagePath, "utf8"));
|
|
10528
10482
|
const manager = configuration.packageManagers.javascript.name;
|
|
10529
10483
|
const run = (script) => javascriptRunCommand(manager, script);
|
|
10530
|
-
|
|
10484
|
+
await writeFile6(
|
|
10485
|
+
join8(directory, "DEPLOYMENT.md"),
|
|
10486
|
+
`# Cloudflare Workers + LiveKit Cloud
|
|
10531
10487
|
|
|
10532
|
-
|
|
10533
|
-
if (node) {
|
|
10534
|
-
for (const name of ["README.md", "AGENTS.md", "agent/README.md"]) {
|
|
10535
|
-
const path = join8(directory, name);
|
|
10536
|
-
await writeFile6(
|
|
10537
|
-
path,
|
|
10538
|
-
(await readFile7(path, "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"
|
|
10539
|
-
);
|
|
10540
|
-
}
|
|
10541
|
-
const workerConfigPath = join8(directory, "tsconfig.worker.json");
|
|
10542
|
-
await writeFile6(
|
|
10543
|
-
workerConfigPath,
|
|
10544
|
-
(await readFile7(workerConfigPath, "utf8")).replace(
|
|
10545
|
-
'"types": ["./worker-configuration.d.ts"]',
|
|
10546
|
-
'"types": ["./worker-configuration.d.ts", "node"]'
|
|
10547
|
-
).replace(
|
|
10548
|
-
'"include": ["worker", "worker-configuration.d.ts"]',
|
|
10549
|
-
'"include": ["worker", "worker-configuration.d.ts", "server"]'
|
|
10550
|
-
)
|
|
10551
|
-
);
|
|
10552
|
-
delete pkg.devDependencies["@cloudflare/vite-plugin"];
|
|
10553
|
-
delete pkg.devDependencies.wrangler;
|
|
10554
|
-
pkg.dependencies.tsx = "4.23.13";
|
|
10555
|
-
delete pkg.scripts["cf-typegen"];
|
|
10556
|
-
delete pkg.scripts.wrangler;
|
|
10557
|
-
pkg.scripts.start = "node --env-file-if-exists=.env.local node_modules/tsx/dist/cli.mjs server/index.ts";
|
|
10558
|
-
pkg.scripts.deploy = "railway up";
|
|
10559
|
-
const dockerPath = join8(directory, "Dockerfile");
|
|
10560
|
-
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";
|
|
10561
|
-
await writeFile6(
|
|
10562
|
-
dockerPath,
|
|
10563
|
-
(await readFile7(dockerPath, "utf8")).replace("__SPATIUS_CONTAINER_INSTALL__", install).replace("__SPATIUS_CONTAINER_BUILD__", run("build"))
|
|
10564
|
-
);
|
|
10565
|
-
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";
|
|
10566
|
-
} else {
|
|
10567
|
-
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")}\`.
|
|
10488
|
+
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.
|
|
10568
10489
|
|
|
10569
|
-
|
|
10570
|
-
|
|
10571
|
-
|
|
10572
|
-
|
|
10573
|
-
|
|
10574
|
-
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";
|
|
10575
|
-
deployment += "\nDeploy the agent before opening conversations. Use separate LiveKit projects for development and production. Local setup never uploads secrets.\n";
|
|
10576
|
-
await writeFile6(join8(directory, "DEPLOYMENT.md"), deployment);
|
|
10577
|
-
await writeFile6(packagePath, JSON.stringify(pkg, null, 2) + "\n");
|
|
10490
|
+
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.
|
|
10491
|
+
|
|
10492
|
+
Deploy the agent before opening conversations. Use separate LiveKit projects for development and production. Local setup never uploads secrets.
|
|
10493
|
+
`
|
|
10494
|
+
);
|
|
10578
10495
|
}
|
|
10579
10496
|
};
|
|
10580
10497
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-spatius-app",
|
|
3
|
-
"version": "0.1.3-beta.
|
|
4
|
-
"description": "Create a Spatius voice-avatar app with LiveKit or Agora
|
|
3
|
+
"version": "0.1.3-beta.2",
|
|
4
|
+
"description": "Create a Spatius voice-avatar app with LiveKit or Agora Conversational AI on Cloudflare Workers.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"create-spatius-app": "./dist/cli.js"
|
|
@@ -22,6 +22,8 @@ Run from the project root:
|
|
|
22
22
|
|
|
23
23
|
All checks can run after dependency installation, before credential setup.
|
|
24
24
|
Browser tests require Chromium; see README.md for the installation command.
|
|
25
|
+
Wrangler generates the ignored `worker-configuration.d.ts` during installation
|
|
26
|
+
and before lint, type checking, and builds. Update `wrangler.jsonc` to change bindings.
|
|
25
27
|
|
|
26
28
|
## Working guidelines
|
|
27
29
|
|
|
@@ -6,6 +6,19 @@ and turn detection. The Spatius plugin sends the generated speech through the
|
|
|
6
6
|
Spatius motion service and publishes synchronized avatar data into the same
|
|
7
7
|
LiveKit room as the browser.
|
|
8
8
|
|
|
9
|
+
## Session startup
|
|
10
|
+
|
|
11
|
+
The worker runs `spatius.prewarm` through `AgentServer`'s `setup_fnc` when each
|
|
12
|
+
job process initializes. It prepares automatic region selection, TLS connections,
|
|
13
|
+
and a session token using the same Spatius environment variables as the avatar
|
|
14
|
+
session, so a warm process can skip that setup when a room is dispatched.
|
|
15
|
+
The avatar ID still comes from the job's dispatch metadata.
|
|
16
|
+
|
|
17
|
+
Warm-up is best effort: missing configuration or a failed warm-up leaves normal
|
|
18
|
+
session startup responsible for resolving the region and fetching a token.
|
|
19
|
+
LiveKit keeps idle processes ready in production; development mode initializes
|
|
20
|
+
processes on demand, so its first session still pays the warm-up cost.
|
|
21
|
+
|
|
9
22
|
## Voice configuration
|
|
10
23
|
|
|
11
24
|
- TTS uses `cartesia/sonic-3.6` through LiveKit Inference. Setup offers
|
|
@@ -5,7 +5,7 @@ description = "A LiveKit voice agent with a Spatius avatar"
|
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11,<3.15"
|
|
7
7
|
dependencies = [
|
|
8
|
-
"livekit-agents[spatius]~=1.8.
|
|
8
|
+
"livekit-agents[spatius]~=1.8.1",
|
|
9
9
|
"livekit-plugins-noise-cancellation~=0.3.0",
|
|
10
10
|
"python-dotenv>=1.2.1,<2",
|
|
11
11
|
]
|
|
@@ -11,6 +11,55 @@ with patch("dotenv.load_dotenv"):
|
|
|
11
11
|
from src import agent as agent_module
|
|
12
12
|
|
|
13
13
|
|
|
14
|
+
class ProcessPrewarmTests(unittest.TestCase):
|
|
15
|
+
def setUp(self) -> None:
|
|
16
|
+
self.enterContext(
|
|
17
|
+
patch.dict(
|
|
18
|
+
"os.environ",
|
|
19
|
+
{"SPATIUS_APP_ID": "app-test", "SPATIUS_API_KEY": "key-test"},
|
|
20
|
+
clear=True,
|
|
21
|
+
)
|
|
22
|
+
)
|
|
23
|
+
# Exercise the registered hook and installed plugin without provider calls.
|
|
24
|
+
self.sdk_prewarm = self.enterContext(
|
|
25
|
+
patch(
|
|
26
|
+
"spatius.prewarm",
|
|
27
|
+
new_callable=AsyncMock,
|
|
28
|
+
return_value=SimpleNamespace(
|
|
29
|
+
region="test-region",
|
|
30
|
+
tls_warmed=True,
|
|
31
|
+
session_token_prefetched=True,
|
|
32
|
+
),
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def test_process_setup_warms_connection_state_without_an_avatar(self) -> None:
|
|
37
|
+
with patch.object(agent_module.spatius, "AvatarSession") as avatar:
|
|
38
|
+
agent_module.server.setup_fnc(SimpleNamespace())
|
|
39
|
+
|
|
40
|
+
self.sdk_prewarm.assert_awaited_once()
|
|
41
|
+
options = self.sdk_prewarm.call_args.kwargs
|
|
42
|
+
self.assertEqual(options["app_id"], "app-test")
|
|
43
|
+
self.assertEqual(options["api_key"], "key-test")
|
|
44
|
+
self.assertEqual(options["region"], "auto")
|
|
45
|
+
self.assertTrue(options["prefetch_session_token"])
|
|
46
|
+
avatar.assert_not_called()
|
|
47
|
+
|
|
48
|
+
def test_warmup_failure_does_not_fail_process_setup(self) -> None:
|
|
49
|
+
self.sdk_prewarm.side_effect = ConnectionError("warm-up unavailable")
|
|
50
|
+
|
|
51
|
+
with self.assertLogs("livekit.plugins.spatius", level="WARNING"):
|
|
52
|
+
agent_module.server.setup_fnc(SimpleNamespace())
|
|
53
|
+
|
|
54
|
+
self.sdk_prewarm.assert_awaited_once()
|
|
55
|
+
|
|
56
|
+
def test_process_setup_without_configuration_skips_warmup(self) -> None:
|
|
57
|
+
with patch.dict("os.environ", {}, clear=True):
|
|
58
|
+
agent_module.server.setup_fnc(SimpleNamespace())
|
|
59
|
+
|
|
60
|
+
self.sdk_prewarm.assert_not_awaited()
|
|
61
|
+
|
|
62
|
+
|
|
14
63
|
class AgentStartupTests(unittest.IsolatedAsyncioTestCase):
|
|
15
64
|
def setUp(self) -> None:
|
|
16
65
|
self.events: list[str] = []
|
|
@@ -777,7 +777,7 @@ wheels = [
|
|
|
777
777
|
|
|
778
778
|
[[package]]
|
|
779
779
|
name = "livekit"
|
|
780
|
-
version = "1.1.
|
|
780
|
+
version = "1.1.18"
|
|
781
781
|
source = { registry = "https://pypi.org/simple" }
|
|
782
782
|
dependencies = [
|
|
783
783
|
{ name = "aiofiles" },
|
|
@@ -786,18 +786,18 @@ dependencies = [
|
|
|
786
786
|
{ name = "protobuf" },
|
|
787
787
|
{ name = "types-protobuf" },
|
|
788
788
|
]
|
|
789
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
789
|
+
sdist = { url = "https://files.pythonhosted.org/packages/43/06/ed2cda99ce0c7f10f6599588b296d2bde08ec1235a97416e9986629ce131/livekit-1.1.18.tar.gz", hash = "sha256:1097b8613fa4625e3fb0bd4fe5b09a3b04b39151c08b78498831dfc57235d1d1", size = 386573, upload-time = "2026-09-08T01:50:55.05Z" }
|
|
790
790
|
wheels = [
|
|
791
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
792
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
793
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
794
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
795
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
791
|
+
{ url = "https://files.pythonhosted.org/packages/d3/b1/be04d760136480aa2d27f8247adc6d0a1e25395b146f04267fb8e716fe6a/livekit-1.1.18-py3-none-macosx_10_15_x86_64.whl", hash = "sha256:8337e956e9816814572c964e7553d66e4845bc3299cb5aeab8a0d671e293ed32", size = 10471856, upload-time = "2026-09-08T01:50:41.85Z" },
|
|
792
|
+
{ url = "https://files.pythonhosted.org/packages/d3/60/a0bf23774eecfc012b60bae84f329537053a0e0952d6f43dc6154d01ac5b/livekit-1.1.18-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8557d6438da4b8968263b44920e7ab606a9ea34bc49fb8fd89054eb3e5cc162d", size = 9292804, upload-time = "2026-09-08T01:50:44.968Z" },
|
|
793
|
+
{ url = "https://files.pythonhosted.org/packages/f1/68/4b5e996660185f2db937a356c9e72981eab42c846f24ba992b997fa9f973/livekit-1.1.18-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:e9305aa097fe75f533db3205ab15868730e467130ffdda7d4adc3840f30b282a", size = 10584633, upload-time = "2026-09-08T01:50:47.735Z" },
|
|
794
|
+
{ url = "https://files.pythonhosted.org/packages/e0/5e/a0220ddf5e53bb3ee4f4969e9ca5e3a3e9567c584b66cd71355a45c069a8/livekit-1.1.18-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:dc2461b668ffa66ca504d05c88af7ea640372f53672ae367145412e374d6aff0", size = 11809468, upload-time = "2026-09-08T01:50:50.332Z" },
|
|
795
|
+
{ url = "https://files.pythonhosted.org/packages/f6/47/22455e01c76b271f7d9399546c2becabe4bf0f58a43e79859bb0ca4fc426/livekit-1.1.18-py3-none-win_amd64.whl", hash = "sha256:a61d4fd39392e7d28a7dbbe3b68be7b600115ed32501f5b016579846de945913", size = 10972019, upload-time = "2026-09-08T01:50:52.912Z" },
|
|
796
796
|
]
|
|
797
797
|
|
|
798
798
|
[[package]]
|
|
799
799
|
name = "livekit-agents"
|
|
800
|
-
version = "1.8.
|
|
800
|
+
version = "1.8.1"
|
|
801
801
|
source = { registry = "https://pypi.org/simple" }
|
|
802
802
|
dependencies = [
|
|
803
803
|
{ name = "aiofiles" },
|
|
@@ -833,9 +833,9 @@ dependencies = [
|
|
|
833
833
|
{ name = "typing-extensions" },
|
|
834
834
|
{ name = "watchfiles" },
|
|
835
835
|
]
|
|
836
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
836
|
+
sdist = { url = "https://files.pythonhosted.org/packages/5c/f1/cc1fcbc24da6f90cf38809c5ceafff1a1d1c1527a32431ce9b00e64deb95/livekit_agents-1.8.1.tar.gz", hash = "sha256:9319b4a7cf3891db4c1de9edd98fad4918919beadb2ceff5b3fbd34ae67384b5", size = 2712687, upload-time = "2026-09-10T18:59:30.729Z" }
|
|
837
837
|
wheels = [
|
|
838
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
838
|
+
{ url = "https://files.pythonhosted.org/packages/0e/2c/29419a913c57b863eba93cbb41df2f3e58b57c3d8f790c6f97be81203ef7/livekit_agents-1.8.1-py3-none-any.whl", hash = "sha256:3e9e4e732ecebcf0512d5eec25621a365c3572289d1f136396d8a1c5ea78e2ac", size = 2832498, upload-time = "2026-09-10T18:59:28.67Z" },
|
|
839
839
|
]
|
|
840
840
|
|
|
841
841
|
[package.optional-dependencies]
|
|
@@ -935,15 +935,15 @@ wheels = [
|
|
|
935
935
|
|
|
936
936
|
[[package]]
|
|
937
937
|
name = "livekit-plugins-spatius"
|
|
938
|
-
version = "1.8.
|
|
938
|
+
version = "1.8.1"
|
|
939
939
|
source = { registry = "https://pypi.org/simple" }
|
|
940
940
|
dependencies = [
|
|
941
941
|
{ name = "livekit-agents" },
|
|
942
942
|
{ name = "spatius", extra = ["opus"] },
|
|
943
943
|
]
|
|
944
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
944
|
+
sdist = { url = "https://files.pythonhosted.org/packages/3f/a7/96d690fa9177fb29ae162fdd3957eee2c87ff3c0b9c4e85956532f94a779/livekit_plugins_spatius-1.8.1.tar.gz", hash = "sha256:d71b74eea79a58861c5893d67d4196d4a3deb2836c23cf4316759aae368b4b9d", size = 10093, upload-time = "2026-09-10T19:01:10.334Z" }
|
|
945
945
|
wheels = [
|
|
946
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
946
|
+
{ url = "https://files.pythonhosted.org/packages/f7/93/b562e8e3ff22b19f69575302b41649bcde379497b73b2fc003f76553a8c4/livekit_plugins_spatius-1.8.1-py3-none-any.whl", hash = "sha256:234d2d0817e43a0aa736c2bfc698b364f6ad64c2ba55c469bab35169099f928e", size = 11330, upload-time = "2026-09-10T19:01:09.016Z" },
|
|
947
947
|
]
|
|
948
948
|
|
|
949
949
|
[[package]]
|
|
@@ -1891,7 +1891,7 @@ dev = [
|
|
|
1891
1891
|
|
|
1892
1892
|
[package.metadata]
|
|
1893
1893
|
requires-dist = [
|
|
1894
|
-
{ name = "livekit-agents", extras = ["spatius"], specifier = "~=1.8.
|
|
1894
|
+
{ name = "livekit-agents", extras = ["spatius"], specifier = "~=1.8.1" },
|
|
1895
1895
|
{ name = "livekit-plugins-noise-cancellation", specifier = "~=0.3.0" },
|
|
1896
1896
|
{ name = "python-dotenv", specifier = ">=1.2.1,<2" },
|
|
1897
1897
|
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.15,<1" },
|