create-spatius-app 0.1.2 → 0.1.3-beta.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.
- package/README.md +17 -6
- package/dist/cli.js +721 -143
- package/dist/templates.js +612 -66
- package/package.json +4 -4
- package/schemas/result-v3.schema.json +6 -2
- package/schemas/result-v4.schema.json +170 -0
- package/templates/cloudflare-livekit/agent/env.example +3 -0
- package/templates/cloudflare-livekit/agent/src/agent.py +5 -3
- package/templates/cloudflare-livekit/package-lock.json +70 -142
- package/templates/cloudflare-livekit/package.json +1 -1
- package/templates/cloudflare-livekit/pnpm-lock.yaml +96 -153
- package/templates/cloudflare-livekit/tests/dev-events.test.mjs +23 -0
- package/templates/cloudflare-livekit/tests/dev-fixtures.mjs +7 -3
- package/templates/cloudflare-livekit/tsconfig.web.json +1 -0
- package/templates/cloudflare-livekit/tsconfig.worker.json +1 -0
- package/templates/cloudflare-livekit/web/api.ts +12 -0
- package/templates/cloudflare-livekit/web/components/agents-ui/agent-chat-transcript.tsx +6 -90
- package/templates/cloudflare-livekit/web/components/transcript-view.tsx +90 -0
- package/templates/cloudflare-livekit/web/session-attempt.ts +23 -2
- package/templates/cloudflare-livekit/worker/capability.test.ts +31 -0
- package/templates/cloudflare-livekit/worker/capability.ts +72 -0
- package/templates/cloudflare-livekit/worker/index.test.ts +2 -2
- package/templates/cloudflare-livekit/worker/index.ts +15 -3
- package/templates/cloudflare-livekit/worker/session.ts +34 -0
- package/templates/layers/agora/AGENTS.md +8 -0
- package/templates/layers/agora/README.md +19 -0
- package/templates/layers/agora/env.example +9 -0
- package/templates/layers/agora/package-lock.json +6068 -0
- package/templates/layers/agora/package.json +54 -0
- package/templates/layers/agora/pnpm-lock.yaml +3816 -0
- package/templates/layers/agora/tsconfig.json +16 -0
- package/templates/layers/agora/vitest.config.ts +4 -0
- package/templates/layers/agora/web/App.tsx +174 -0
- package/templates/layers/agora/web/agora-session.test.ts +70 -0
- package/templates/layers/agora/web/agora-session.ts +267 -0
- package/templates/layers/agora/worker/agora.test.ts +60 -0
- package/templates/layers/agora/worker/agora.ts +165 -0
- package/templates/layers/agora/worker/index.ts +50 -0
- package/templates/layers/catalog/DEPLOYMENT.md +3 -0
- package/templates/layers/catalog/spatius.config.json +1 -0
- package/templates/layers/node/Dockerfile +12 -0
- package/templates/layers/node/dockerignore +10 -0
- package/templates/layers/node/package-lock.json +5569 -0
- package/templates/layers/node/pnpm-lock.yaml +3470 -0
- package/templates/layers/node/railway.json +8 -0
- package/templates/layers/node/scripts/dev-service.mjs +14 -0
- package/templates/layers/node/scripts/dev-web.mjs +22 -0
- package/templates/layers/node/server/index.ts +92 -0
- package/templates/layers/node/vite.config.ts +17 -0
- package/templates/layers/railway-agent/agent/railway.json +5 -0
- package/dist/cli.js.map +0 -1
- package/dist/templates.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -40,6 +40,117 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
40
40
|
mod
|
|
41
41
|
));
|
|
42
42
|
|
|
43
|
+
// src/errors.ts
|
|
44
|
+
function normalizeError(error) {
|
|
45
|
+
if (error instanceof CliError) {
|
|
46
|
+
return error;
|
|
47
|
+
}
|
|
48
|
+
if (error instanceof Error && error.message.trim() !== "") {
|
|
49
|
+
return new CliError("UNKNOWN_ERROR", error.message);
|
|
50
|
+
}
|
|
51
|
+
return new CliError(
|
|
52
|
+
"UNKNOWN_ERROR",
|
|
53
|
+
"An unexpected error occurred while creating the project."
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
var EXIT_CODES, CliError, PromptCancelledError;
|
|
57
|
+
var init_errors = __esm({
|
|
58
|
+
"src/errors.ts"() {
|
|
59
|
+
"use strict";
|
|
60
|
+
EXIT_CODES = {
|
|
61
|
+
cancelled: 130,
|
|
62
|
+
filesystem: 4,
|
|
63
|
+
general: 1,
|
|
64
|
+
invalidArgument: 2,
|
|
65
|
+
success: 0,
|
|
66
|
+
targetConflict: 3
|
|
67
|
+
};
|
|
68
|
+
CliError = class extends Error {
|
|
69
|
+
code;
|
|
70
|
+
exitCode;
|
|
71
|
+
path;
|
|
72
|
+
recovery;
|
|
73
|
+
constructor(code, message, { exitCode = EXIT_CODES.general, path: path2, recovery } = {}) {
|
|
74
|
+
super(message);
|
|
75
|
+
this.name = "CliError";
|
|
76
|
+
this.code = code;
|
|
77
|
+
this.exitCode = exitCode;
|
|
78
|
+
this.path = path2;
|
|
79
|
+
this.recovery = recovery;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
PromptCancelledError = class extends CliError {
|
|
83
|
+
constructor(message = "Project creation was cancelled.") {
|
|
84
|
+
super("CANCELLED", message, {
|
|
85
|
+
exitCode: EXIT_CODES.cancelled,
|
|
86
|
+
recovery: "Run the command again when you are ready."
|
|
87
|
+
});
|
|
88
|
+
this.name = "PromptCancelledError";
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// src/catalog.ts
|
|
95
|
+
function validateSelection(stack) {
|
|
96
|
+
if (!Object.hasOwn(stacks, stack)) {
|
|
97
|
+
throw new CliError("INVALID_ARGUMENT", `Unsupported stack: ${stack}`, {
|
|
98
|
+
exitCode: EXIT_CODES.invalidArgument
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return { stack };
|
|
102
|
+
}
|
|
103
|
+
async function selectCatalog(options) {
|
|
104
|
+
if (options.stack !== void 0) return validateSelection(options.stack);
|
|
105
|
+
const choices = Object.entries(stacks).map(([value, stack]) => ({
|
|
106
|
+
value,
|
|
107
|
+
label: stack.label
|
|
108
|
+
}));
|
|
109
|
+
return validateSelection(
|
|
110
|
+
options.interactive ? await options.prompts.choose("Which stack?", choices, DEFAULT_STACK) : DEFAULT_STACK
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
var stacks, DEFAULT_STACK;
|
|
114
|
+
var init_catalog = __esm({
|
|
115
|
+
"src/catalog.ts"() {
|
|
116
|
+
"use strict";
|
|
117
|
+
init_errors();
|
|
118
|
+
stacks = {
|
|
119
|
+
"cloudflare-livekit": {
|
|
120
|
+
label: "Cloudflare + LiveKit Cloud Agents",
|
|
121
|
+
web: "cloudflare",
|
|
122
|
+
agent: "livekit",
|
|
123
|
+
provider: "livekit"
|
|
124
|
+
},
|
|
125
|
+
"cloudflare-livekit-railway": {
|
|
126
|
+
label: "Cloudflare + LiveKit Agents on Railway",
|
|
127
|
+
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
|
+
agent: "agora",
|
|
147
|
+
provider: "agora"
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
DEFAULT_STACK = "cloudflare-livekit";
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
43
154
|
// node_modules/.pnpm/sisteransi@1.0.5/node_modules/sisteransi/src/index.js
|
|
44
155
|
var require_src = __commonJS({
|
|
45
156
|
"node_modules/.pnpm/sisteransi@1.0.5/node_modules/sisteransi/src/index.js"(exports, module) {
|
|
@@ -123,7 +234,7 @@ var require_dist = __commonJS({
|
|
|
123
234
|
determineAgent: () => determineAgent2
|
|
124
235
|
});
|
|
125
236
|
module.exports = __toCommonJS(src_exports);
|
|
126
|
-
var
|
|
237
|
+
var import_promises13 = __require("fs/promises");
|
|
127
238
|
var import_node_fs4 = __require("fs");
|
|
128
239
|
var DEVIN_LOCAL_PATH = "/opt/.devin";
|
|
129
240
|
var CURSOR = "cursor";
|
|
@@ -211,7 +322,7 @@ var require_dist = __commonJS({
|
|
|
211
322
|
return { isAgent: true, agent: { name: GITHUB_COPILOT } };
|
|
212
323
|
}
|
|
213
324
|
try {
|
|
214
|
-
await (0,
|
|
325
|
+
await (0, import_promises13.access)(DEVIN_LOCAL_PATH, import_node_fs4.constants.F_OK);
|
|
215
326
|
return { isAgent: true, agent: { name: DEVIN } };
|
|
216
327
|
} catch (_error) {
|
|
217
328
|
}
|
|
@@ -220,57 +331,6 @@ var require_dist = __commonJS({
|
|
|
220
331
|
}
|
|
221
332
|
});
|
|
222
333
|
|
|
223
|
-
// src/errors.ts
|
|
224
|
-
function normalizeError(error) {
|
|
225
|
-
if (error instanceof CliError) {
|
|
226
|
-
return error;
|
|
227
|
-
}
|
|
228
|
-
if (error instanceof Error && error.message.trim() !== "") {
|
|
229
|
-
return new CliError("UNKNOWN_ERROR", error.message);
|
|
230
|
-
}
|
|
231
|
-
return new CliError(
|
|
232
|
-
"UNKNOWN_ERROR",
|
|
233
|
-
"An unexpected error occurred while creating the project."
|
|
234
|
-
);
|
|
235
|
-
}
|
|
236
|
-
var EXIT_CODES, CliError, PromptCancelledError;
|
|
237
|
-
var init_errors = __esm({
|
|
238
|
-
"src/errors.ts"() {
|
|
239
|
-
"use strict";
|
|
240
|
-
EXIT_CODES = {
|
|
241
|
-
cancelled: 130,
|
|
242
|
-
filesystem: 4,
|
|
243
|
-
general: 1,
|
|
244
|
-
invalidArgument: 2,
|
|
245
|
-
success: 0,
|
|
246
|
-
targetConflict: 3
|
|
247
|
-
};
|
|
248
|
-
CliError = class extends Error {
|
|
249
|
-
code;
|
|
250
|
-
exitCode;
|
|
251
|
-
path;
|
|
252
|
-
recovery;
|
|
253
|
-
constructor(code, message, { exitCode = EXIT_CODES.general, path: path2, recovery } = {}) {
|
|
254
|
-
super(message);
|
|
255
|
-
this.name = "CliError";
|
|
256
|
-
this.code = code;
|
|
257
|
-
this.exitCode = exitCode;
|
|
258
|
-
this.path = path2;
|
|
259
|
-
this.recovery = recovery;
|
|
260
|
-
}
|
|
261
|
-
};
|
|
262
|
-
PromptCancelledError = class extends CliError {
|
|
263
|
-
constructor(message = "Project creation was cancelled.") {
|
|
264
|
-
super("CANCELLED", message, {
|
|
265
|
-
exitCode: EXIT_CODES.cancelled,
|
|
266
|
-
recovery: "Run the command again when you are ready."
|
|
267
|
-
});
|
|
268
|
-
this.name = "PromptCancelledError";
|
|
269
|
-
}
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
});
|
|
273
|
-
|
|
274
334
|
// src/package-managers.ts
|
|
275
335
|
import { execFile, spawn } from "child_process";
|
|
276
336
|
function requiresWindowsCommandShell(command, platform = process.platform) {
|
|
@@ -531,7 +591,7 @@ function selectPythonPackageManager(inventory, explicit, platform = process.plat
|
|
|
531
591
|
}
|
|
532
592
|
function assertPackageManagersAvailable(packageManagers) {
|
|
533
593
|
const unavailable = [packageManagers.javascript, packageManagers.python].find(
|
|
534
|
-
(manager) => !manager.available
|
|
594
|
+
(manager) => manager !== void 0 && !manager.available
|
|
535
595
|
);
|
|
536
596
|
if (unavailable !== void 0) {
|
|
537
597
|
throw new CliError(
|
|
@@ -571,24 +631,74 @@ var init_package_managers = __esm({
|
|
|
571
631
|
}
|
|
572
632
|
});
|
|
573
633
|
|
|
574
|
-
// src/
|
|
575
|
-
import { lstat } from "fs/promises";
|
|
634
|
+
// src/project-config.ts
|
|
635
|
+
import { lstat, readFile } from "fs/promises";
|
|
576
636
|
import { join as join2 } from "path";
|
|
637
|
+
async function readProjectConfig(directory) {
|
|
638
|
+
const path2 = join2(directory, "spatius.config.json");
|
|
639
|
+
try {
|
|
640
|
+
const stats = await lstat(path2);
|
|
641
|
+
if (!stats.isFile() || stats.isSymbolicLink())
|
|
642
|
+
throw new Error("Expected a regular file");
|
|
643
|
+
const value = JSON.parse(await readFile(path2, "utf8"));
|
|
644
|
+
if (value && typeof value === "object" && "version" in value && value.version !== 2)
|
|
645
|
+
throw new CliError(
|
|
646
|
+
"INVALID_ARGUMENT",
|
|
647
|
+
"Unsupported spatius.config.json version. Expected version 2.",
|
|
648
|
+
{ path: path2, exitCode: EXIT_CODES.invalidArgument }
|
|
649
|
+
);
|
|
650
|
+
if (!value || typeof value !== "object" || !("version" in value) || value.version !== 2 || !("stack" in value) || typeof value.stack !== "string")
|
|
651
|
+
throw new Error("Unsupported project configuration");
|
|
652
|
+
return { version: 2, ...validateSelection(value.stack) };
|
|
653
|
+
} catch (error) {
|
|
654
|
+
if (error instanceof CliError) throw error;
|
|
655
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
656
|
+
return void 0;
|
|
657
|
+
throw new CliError("INVALID_ARGUMENT", "Invalid spatius.config.json.", {
|
|
658
|
+
path: path2,
|
|
659
|
+
exitCode: EXIT_CODES.invalidArgument
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
async function webEnvironmentPath(directory) {
|
|
664
|
+
const config = await readProjectConfig(directory);
|
|
665
|
+
return config && !config.stack.startsWith("cloudflare") ? ".env.local" : ".dev.vars";
|
|
666
|
+
}
|
|
667
|
+
var init_project_config = __esm({
|
|
668
|
+
"src/project-config.ts"() {
|
|
669
|
+
"use strict";
|
|
670
|
+
init_catalog();
|
|
671
|
+
init_errors();
|
|
672
|
+
}
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
// src/setup/project.ts
|
|
676
|
+
import { lstat as lstat2 } from "fs/promises";
|
|
677
|
+
import { join as join4 } from "path";
|
|
577
678
|
async function assertSpatiusProject(targetDirectory) {
|
|
578
679
|
try {
|
|
579
|
-
const target = await
|
|
680
|
+
const target = await lstat2(targetDirectory);
|
|
580
681
|
if (!target.isDirectory() || target.isSymbolicLink()) {
|
|
581
682
|
throw new Error("not a regular directory");
|
|
582
683
|
}
|
|
684
|
+
const config = await readProjectConfig(targetDirectory);
|
|
685
|
+
const paths = config ? [
|
|
686
|
+
"package.json",
|
|
687
|
+
"spatius.config.json",
|
|
688
|
+
await webEnvironmentPath(targetDirectory) + ".example",
|
|
689
|
+
"worker/index.ts",
|
|
690
|
+
"agent/src/agent.py",
|
|
691
|
+
"agent/.env.example"
|
|
692
|
+
] : requiredProjectPaths;
|
|
583
693
|
await Promise.all([
|
|
584
694
|
...requiredProjectDirectories.map(async (relativePath) => {
|
|
585
|
-
const stats = await
|
|
695
|
+
const stats = await lstat2(join4(targetDirectory, relativePath));
|
|
586
696
|
if (!stats.isDirectory() || stats.isSymbolicLink()) {
|
|
587
697
|
throw new Error(`${relativePath} is not a regular directory`);
|
|
588
698
|
}
|
|
589
699
|
}),
|
|
590
|
-
...
|
|
591
|
-
const stats = await
|
|
700
|
+
...paths.map(async (relativePath) => {
|
|
701
|
+
const stats = await lstat2(join4(targetDirectory, relativePath));
|
|
592
702
|
if (!stats.isFile() || stats.isSymbolicLink()) {
|
|
593
703
|
throw new Error(`${relativePath} is not a regular file`);
|
|
594
704
|
}
|
|
@@ -610,6 +720,7 @@ var requiredProjectPaths, requiredProjectDirectories;
|
|
|
610
720
|
var init_project = __esm({
|
|
611
721
|
"src/setup/project.ts"() {
|
|
612
722
|
"use strict";
|
|
723
|
+
init_project_config();
|
|
613
724
|
init_errors();
|
|
614
725
|
requiredProjectPaths = [
|
|
615
726
|
"package.json",
|
|
@@ -648,14 +759,14 @@ var init_voices = __esm({
|
|
|
648
759
|
// src/setup/environment.ts
|
|
649
760
|
import {
|
|
650
761
|
chmod,
|
|
651
|
-
lstat as
|
|
652
|
-
readFile as
|
|
762
|
+
lstat as lstat3,
|
|
763
|
+
readFile as readFile3,
|
|
653
764
|
rename,
|
|
654
765
|
rm,
|
|
655
766
|
writeFile as writeFile2
|
|
656
767
|
} from "fs/promises";
|
|
657
768
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
658
|
-
import { join as
|
|
769
|
+
import { join as join5 } from "path";
|
|
659
770
|
function parseQuotedValue(rawValue) {
|
|
660
771
|
const value = rawValue.trim();
|
|
661
772
|
if (value.startsWith('"')) {
|
|
@@ -810,7 +921,7 @@ function isMissingFile(error) {
|
|
|
810
921
|
}
|
|
811
922
|
async function readOptionalCredentialFile(path2) {
|
|
812
923
|
try {
|
|
813
|
-
const stats = await
|
|
924
|
+
const stats = await lstat3(path2);
|
|
814
925
|
if (stats.isSymbolicLink() || !stats.isFile()) {
|
|
815
926
|
throw new CliError(
|
|
816
927
|
"FILESYSTEM_ERROR",
|
|
@@ -822,7 +933,7 @@ async function readOptionalCredentialFile(path2) {
|
|
|
822
933
|
}
|
|
823
934
|
);
|
|
824
935
|
}
|
|
825
|
-
return await
|
|
936
|
+
return await readFile3(path2, "utf8");
|
|
826
937
|
} catch (error) {
|
|
827
938
|
if (isMissingFile(error)) {
|
|
828
939
|
return void 0;
|
|
@@ -831,10 +942,11 @@ async function readOptionalCredentialFile(path2) {
|
|
|
831
942
|
}
|
|
832
943
|
}
|
|
833
944
|
async function readCredentialFileState(targetDirectory) {
|
|
945
|
+
const webPath = await webEnvironmentPath(targetDirectory);
|
|
834
946
|
const [worker, agent, workerExample] = await Promise.all([
|
|
835
|
-
readOptionalCredentialFile(
|
|
836
|
-
readOptionalCredentialFile(
|
|
837
|
-
readOptionalCredentialFile(
|
|
947
|
+
readOptionalCredentialFile(join5(targetDirectory, webPath)),
|
|
948
|
+
readOptionalCredentialFile(join5(targetDirectory, "agent", ".env.local")),
|
|
949
|
+
readOptionalCredentialFile(join5(targetDirectory, webPath + ".example"))
|
|
838
950
|
]);
|
|
839
951
|
return {
|
|
840
952
|
agent,
|
|
@@ -906,23 +1018,24 @@ async function pathExists(path2, fileSystem) {
|
|
|
906
1018
|
}
|
|
907
1019
|
}
|
|
908
1020
|
async function writeCredentialFilesAtomically(targetDirectory, contents, fileSystem = defaultAtomicFileSystem) {
|
|
1021
|
+
const webPath = await webEnvironmentPath(targetDirectory);
|
|
909
1022
|
const nonce = randomUUID2();
|
|
910
1023
|
const targets = [
|
|
911
1024
|
{
|
|
912
1025
|
backupMoved: false,
|
|
913
|
-
backupPath:
|
|
914
|
-
destinationPath:
|
|
1026
|
+
backupPath: join5(targetDirectory, `.dev.vars.${nonce}.backup`),
|
|
1027
|
+
destinationPath: join5(targetDirectory, webPath),
|
|
915
1028
|
hadOriginal: false,
|
|
916
1029
|
installed: false,
|
|
917
|
-
stagedPath:
|
|
1030
|
+
stagedPath: join5(targetDirectory, `.dev.vars.${nonce}.tmp`)
|
|
918
1031
|
},
|
|
919
1032
|
{
|
|
920
1033
|
backupMoved: false,
|
|
921
|
-
backupPath:
|
|
922
|
-
destinationPath:
|
|
1034
|
+
backupPath: join5(targetDirectory, "agent", `.env.local.${nonce}.backup`),
|
|
1035
|
+
destinationPath: join5(targetDirectory, "agent", ".env.local"),
|
|
923
1036
|
hadOriginal: false,
|
|
924
1037
|
installed: false,
|
|
925
|
-
stagedPath:
|
|
1038
|
+
stagedPath: join5(targetDirectory, "agent", `.env.local.${nonce}.tmp`)
|
|
926
1039
|
}
|
|
927
1040
|
];
|
|
928
1041
|
const payloads = [contents.worker, contents.agent];
|
|
@@ -983,9 +1096,10 @@ async function writeCredentialFilesAtomically(targetDirectory, contents, fileSys
|
|
|
983
1096
|
}
|
|
984
1097
|
}
|
|
985
1098
|
async function readCredentialExamples(targetDirectory) {
|
|
1099
|
+
const webPath = await webEnvironmentPath(targetDirectory);
|
|
986
1100
|
const [worker, agent] = await Promise.all([
|
|
987
|
-
|
|
988
|
-
|
|
1101
|
+
readFile3(join5(targetDirectory, webPath + ".example"), "utf8"),
|
|
1102
|
+
readFile3(join5(targetDirectory, "agent", ".env.example"), "utf8")
|
|
989
1103
|
]);
|
|
990
1104
|
return { agent, worker };
|
|
991
1105
|
}
|
|
@@ -993,6 +1107,7 @@ var WORKER_MANAGED_KEYS, AGENT_MANAGED_KEYS, assignmentPattern, defaultAtomicFil
|
|
|
993
1107
|
var init_environment = __esm({
|
|
994
1108
|
"src/setup/environment.ts"() {
|
|
995
1109
|
"use strict";
|
|
1110
|
+
init_project_config();
|
|
996
1111
|
init_errors();
|
|
997
1112
|
WORKER_MANAGED_KEYS = [
|
|
998
1113
|
"LIVEKIT_URL",
|
|
@@ -1012,8 +1127,8 @@ var init_environment = __esm({
|
|
|
1012
1127
|
assignmentPattern = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z\d_]*)\s*=\s*(.*)$/u;
|
|
1013
1128
|
defaultAtomicFileSystem = {
|
|
1014
1129
|
chmod,
|
|
1015
|
-
lstat:
|
|
1016
|
-
readFile: (path2) =>
|
|
1130
|
+
lstat: lstat3,
|
|
1131
|
+
readFile: (path2) => readFile3(path2),
|
|
1017
1132
|
rename,
|
|
1018
1133
|
rm: (path2) => rm(path2, { force: true }),
|
|
1019
1134
|
writeFile: writeFile2
|
|
@@ -8351,12 +8466,12 @@ var require_dist2 = __commonJS({
|
|
|
8351
8466
|
// src/setup/livekit.ts
|
|
8352
8467
|
import { createHmac } from "crypto";
|
|
8353
8468
|
import { spawn as spawn2 } from "child_process";
|
|
8354
|
-
import { chmod as chmod2, mkdtemp, readFile as
|
|
8469
|
+
import { chmod as chmod2, mkdtemp, readFile as readFile4, rm as rm2, writeFile as writeFile3 } from "fs/promises";
|
|
8355
8470
|
import { homedir, tmpdir } from "os";
|
|
8356
|
-
import { join as
|
|
8357
|
-
async function readLiveKitProjects(configPath =
|
|
8471
|
+
import { join as join6 } from "path";
|
|
8472
|
+
async function readLiveKitProjects(configPath = join6(homedir(), ".livekit", "cli-config.yaml")) {
|
|
8358
8473
|
try {
|
|
8359
|
-
const config = (0, import_yaml.parse)(await
|
|
8474
|
+
const config = (0, import_yaml.parse)(await readFile4(configPath, "utf8"));
|
|
8360
8475
|
if (config == null) return [];
|
|
8361
8476
|
if (typeof config !== "object" || !("projects" in config) || !Array.isArray(config.projects))
|
|
8362
8477
|
throw new Error();
|
|
@@ -8519,12 +8634,12 @@ async function loadLiveKitCredentialsWithCli({
|
|
|
8519
8634
|
projectName
|
|
8520
8635
|
} = {}) {
|
|
8521
8636
|
const directory = await fileSystem.mkdtemp(
|
|
8522
|
-
|
|
8637
|
+
join6(temporaryRoot, "create-spatius-app-livekit-")
|
|
8523
8638
|
);
|
|
8524
8639
|
try {
|
|
8525
8640
|
await fileSystem.chmod(directory, 448);
|
|
8526
8641
|
await fileSystem.writeFile(
|
|
8527
|
-
|
|
8642
|
+
join6(directory, ".env.example"),
|
|
8528
8643
|
"LIVEKIT_URL=\nLIVEKIT_API_KEY=\nLIVEKIT_API_SECRET=\n",
|
|
8529
8644
|
384
|
|
8530
8645
|
);
|
|
@@ -8543,7 +8658,7 @@ async function loadLiveKitCredentialsWithCli({
|
|
|
8543
8658
|
}
|
|
8544
8659
|
);
|
|
8545
8660
|
const values = parseDotenv(
|
|
8546
|
-
await fileSystem.readFile(
|
|
8661
|
+
await fileSystem.readFile(join6(directory, ".env.local"))
|
|
8547
8662
|
);
|
|
8548
8663
|
const validated = validateCredentialBundle({
|
|
8549
8664
|
liveKit: {
|
|
@@ -8643,7 +8758,7 @@ var init_livekit = __esm({
|
|
|
8643
8758
|
defaultTemporaryFileSystem = {
|
|
8644
8759
|
chmod: chmod2,
|
|
8645
8760
|
mkdtemp,
|
|
8646
|
-
readFile: (path2) =>
|
|
8761
|
+
readFile: (path2) => readFile4(path2, "utf8"),
|
|
8647
8762
|
remove: (path2) => rm2(path2, {
|
|
8648
8763
|
force: true,
|
|
8649
8764
|
maxRetries: 3,
|
|
@@ -10094,7 +10209,7 @@ async function runCredentialSetup({
|
|
|
10094
10209
|
const examples = await readExamples(targetDirectory);
|
|
10095
10210
|
const rendered = buildCredentialFileContents(state, examples, credentials);
|
|
10096
10211
|
await writeFiles(targetDirectory, rendered);
|
|
10097
|
-
onStatus("Saved
|
|
10212
|
+
onStatus("Saved local web and agent credentials for local development only.");
|
|
10098
10213
|
return "configured";
|
|
10099
10214
|
}
|
|
10100
10215
|
var init_wizard = __esm({
|
|
@@ -10113,6 +10228,9 @@ var init_wizard = __esm({
|
|
|
10113
10228
|
}
|
|
10114
10229
|
});
|
|
10115
10230
|
|
|
10231
|
+
// src/cli.ts
|
|
10232
|
+
init_catalog();
|
|
10233
|
+
|
|
10116
10234
|
// node_modules/.pnpm/@clack+core@1.4.3/node_modules/@clack/core/dist/index.mjs
|
|
10117
10235
|
import { styleText } from "util";
|
|
10118
10236
|
import { stdout, stdin } from "process";
|
|
@@ -14699,7 +14817,7 @@ var program = new Command();
|
|
|
14699
14817
|
|
|
14700
14818
|
// src/cli.ts
|
|
14701
14819
|
init_errors();
|
|
14702
|
-
import { readFile as
|
|
14820
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
14703
14821
|
import process5 from "process";
|
|
14704
14822
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
14705
14823
|
|
|
@@ -14744,18 +14862,18 @@ init_package_managers();
|
|
|
14744
14862
|
import { spawn as spawn3 } from "child_process";
|
|
14745
14863
|
|
|
14746
14864
|
// src/templates.ts
|
|
14747
|
-
|
|
14748
|
-
|
|
14865
|
+
init_project_config();
|
|
14866
|
+
init_catalog();
|
|
14749
14867
|
|
|
14750
|
-
// src/templates/
|
|
14751
|
-
init_errors();
|
|
14868
|
+
// src/templates/agora.ts
|
|
14752
14869
|
init_package_managers();
|
|
14753
|
-
|
|
14870
|
+
import { readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
|
|
14871
|
+
import { basename as basename3, join as join8 } from "path";
|
|
14754
14872
|
|
|
14755
14873
|
// src/templates/cloudflare-livekit/configure.ts
|
|
14756
14874
|
init_package_managers();
|
|
14757
14875
|
import { randomUUID } from "crypto";
|
|
14758
|
-
import { readFile, writeFile } from "fs/promises";
|
|
14876
|
+
import { access, readFile as readFile2, writeFile } from "fs/promises";
|
|
14759
14877
|
import { basename, join as join3 } from "path";
|
|
14760
14878
|
function toValidPackageName(value) {
|
|
14761
14879
|
const normalized = value.trim().toLowerCase().replace(/\s+/gu, "-").replace(/^[._-]+/u, "").replace(/[^a-z\d~._-]+/gu, "-").replace(/-+/gu, "-").slice(0, 214).replace(/-$/u, "");
|
|
@@ -14771,6 +14889,7 @@ function npmRequiresScriptAllowlist(version) {
|
|
|
14771
14889
|
function createReplacements(packageManagers, platform) {
|
|
14772
14890
|
const javascript = packageManagers.javascript;
|
|
14773
14891
|
const python = packageManagers.python;
|
|
14892
|
+
if (!python) throw new Error("LiveKit templates require Python tooling.");
|
|
14774
14893
|
return /* @__PURE__ */ new Map([
|
|
14775
14894
|
[
|
|
14776
14895
|
"{{SPATIUS_JAVASCRIPT_PACKAGE_MANAGER}}",
|
|
@@ -14842,7 +14961,7 @@ function createReplacements(packageManagers, platform) {
|
|
|
14842
14961
|
]);
|
|
14843
14962
|
}
|
|
14844
14963
|
async function renderFile(path2, replacements) {
|
|
14845
|
-
let contents = await
|
|
14964
|
+
let contents = await readFile2(path2, "utf8");
|
|
14846
14965
|
for (const [token, replacement] of replacements) {
|
|
14847
14966
|
contents = contents.replaceAll(token, replacement);
|
|
14848
14967
|
}
|
|
@@ -14854,7 +14973,7 @@ async function configureGeneratedTemplate(targetDirectory, {
|
|
|
14854
14973
|
projectName = basename(targetDirectory)
|
|
14855
14974
|
}) {
|
|
14856
14975
|
const packagePath = join3(targetDirectory, "package.json");
|
|
14857
|
-
const packageContents = await
|
|
14976
|
+
const packageContents = await readFile2(packagePath, "utf8");
|
|
14858
14977
|
const metadata = JSON.parse(packageContents);
|
|
14859
14978
|
const javascript = packageManagers.javascript;
|
|
14860
14979
|
const run = (script) => javascriptRunCommand(javascript.name, script);
|
|
@@ -14893,7 +15012,7 @@ async function configureGeneratedTemplate(targetDirectory, {
|
|
|
14893
15012
|
if (javascript.name === "npm") {
|
|
14894
15013
|
const packageLockPath = join3(targetDirectory, "package-lock.json");
|
|
14895
15014
|
const packageLock = JSON.parse(
|
|
14896
|
-
await
|
|
15015
|
+
await readFile2(packageLockPath, "utf8")
|
|
14897
15016
|
);
|
|
14898
15017
|
packageLock.name = metadata.name;
|
|
14899
15018
|
if (packageLock.packages?.[""] !== void 0) {
|
|
@@ -14912,12 +15031,18 @@ async function configureGeneratedTemplate(targetDirectory, {
|
|
|
14912
15031
|
await Promise.all(
|
|
14913
15032
|
[
|
|
14914
15033
|
".dev.vars.example",
|
|
15034
|
+
".env.local.example",
|
|
14915
15035
|
"wrangler.jsonc",
|
|
14916
15036
|
"agent/src/agent.py",
|
|
14917
15037
|
"agent/README.md",
|
|
14918
15038
|
"worker/index.test.ts"
|
|
14919
15039
|
].map(
|
|
14920
|
-
async (relativePath) =>
|
|
15040
|
+
async (relativePath) => access(join3(targetDirectory, relativePath)).then(
|
|
15041
|
+
() => renderFile(join3(targetDirectory, relativePath), agentReplacements)
|
|
15042
|
+
).catch((error) => {
|
|
15043
|
+
if (!(error instanceof Error && "code" in error && error.code === "ENOENT"))
|
|
15044
|
+
throw error;
|
|
15045
|
+
})
|
|
14921
15046
|
)
|
|
14922
15047
|
);
|
|
14923
15048
|
await Promise.all(
|
|
@@ -14952,6 +15077,7 @@ function createInstallPlan(targetDirectory, packageManagers, platform = process.
|
|
|
14952
15077
|
`Installing JavaScript dependencies with ${javascript.name}`
|
|
14953
15078
|
)
|
|
14954
15079
|
];
|
|
15080
|
+
if (!python) return steps;
|
|
14955
15081
|
if (python.name === "uv") {
|
|
14956
15082
|
steps.push(
|
|
14957
15083
|
commandStep(
|
|
@@ -14990,6 +15116,9 @@ function createInstallPlan(targetDirectory, packageManagers, platform = process.
|
|
|
14990
15116
|
}
|
|
14991
15117
|
|
|
14992
15118
|
// src/templates/cloudflare-livekit/index.ts
|
|
15119
|
+
init_errors();
|
|
15120
|
+
init_package_managers();
|
|
15121
|
+
init_project();
|
|
14993
15122
|
var credentialSetupStep = {
|
|
14994
15123
|
command: "npx create-spatius-app setup . --interactive",
|
|
14995
15124
|
reason: "Connect LiveKit and Spatius accounts and select an avatar and voice in a secure interactive terminal. Never paste secrets into chat.",
|
|
@@ -15018,7 +15147,7 @@ var cloudflareLivekitTemplate = {
|
|
|
15018
15147
|
],
|
|
15019
15148
|
includeFile(path2, configuration) {
|
|
15020
15149
|
const javascript = configuration?.packageManagers.javascript.name ?? "pnpm";
|
|
15021
|
-
const python = configuration?.packageManagers.python
|
|
15150
|
+
const python = configuration?.packageManagers.python?.name ?? "uv";
|
|
15022
15151
|
if (path2 === "package-lock.json") return javascript === "npm";
|
|
15023
15152
|
if (["npmrc", "pnpm-lock.yaml", "pnpm-workspace.yaml"].includes(path2)) {
|
|
15024
15153
|
return javascript === "pnpm";
|
|
@@ -15042,6 +15171,8 @@ var cloudflareLivekitTemplate = {
|
|
|
15042
15171
|
platform,
|
|
15043
15172
|
pythonPackageManager
|
|
15044
15173
|
}) {
|
|
15174
|
+
if (!pythonPackageManager)
|
|
15175
|
+
throw new Error("LiveKit templates require Python tooling.");
|
|
15045
15176
|
return [
|
|
15046
15177
|
...dependenciesInstalled ? [] : [
|
|
15047
15178
|
javascriptInstallCommand(javascriptPackageManager),
|
|
@@ -15074,16 +15205,409 @@ var cloudflareLivekitTemplate = {
|
|
|
15074
15205
|
]
|
|
15075
15206
|
};
|
|
15076
15207
|
|
|
15208
|
+
// src/setup/agora.ts
|
|
15209
|
+
init_environment();
|
|
15210
|
+
init_errors();
|
|
15211
|
+
init_project_config();
|
|
15212
|
+
import { lstat as lstat4, readFile as readFile5, rename as rename2, rm as rm3, writeFile as writeFile4 } from "fs/promises";
|
|
15213
|
+
import { join as join7 } from "path";
|
|
15214
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
15215
|
+
var runAgoraSetup = async ({
|
|
15216
|
+
targetDirectory,
|
|
15217
|
+
prompts,
|
|
15218
|
+
onStatus = () => void 0
|
|
15219
|
+
}) => {
|
|
15220
|
+
const target = await lstat4(targetDirectory);
|
|
15221
|
+
if (!target.isDirectory() || target.isSymbolicLink())
|
|
15222
|
+
throw new CliError(
|
|
15223
|
+
"INVALID_ARGUMENT",
|
|
15224
|
+
"Expected a regular project directory.",
|
|
15225
|
+
{ exitCode: EXIT_CODES.invalidArgument }
|
|
15226
|
+
);
|
|
15227
|
+
const config = await readProjectConfig(targetDirectory);
|
|
15228
|
+
if (config?.stack !== "zeabur-agora")
|
|
15229
|
+
throw new CliError(
|
|
15230
|
+
"INVALID_ARGUMENT",
|
|
15231
|
+
"Expected a Zeabur + Agora project.",
|
|
15232
|
+
{ exitCode: EXIT_CODES.invalidArgument }
|
|
15233
|
+
);
|
|
15234
|
+
for (const name of [
|
|
15235
|
+
"package.json",
|
|
15236
|
+
".env.local.example",
|
|
15237
|
+
"worker/agora.ts"
|
|
15238
|
+
]) {
|
|
15239
|
+
const stats = await lstat4(join7(targetDirectory, name));
|
|
15240
|
+
if (!stats.isFile() || stats.isSymbolicLink())
|
|
15241
|
+
throw new CliError(
|
|
15242
|
+
"INVALID_ARGUMENT",
|
|
15243
|
+
"Expected regular project files.",
|
|
15244
|
+
{ exitCode: EXIT_CODES.invalidArgument }
|
|
15245
|
+
);
|
|
15246
|
+
}
|
|
15247
|
+
const path2 = join7(targetDirectory, ".env.local");
|
|
15248
|
+
let existing;
|
|
15249
|
+
try {
|
|
15250
|
+
const stats = await lstat4(path2);
|
|
15251
|
+
if (!stats.isFile() || stats.isSymbolicLink())
|
|
15252
|
+
throw new Error("Unsafe credential path");
|
|
15253
|
+
existing = await readFile5(path2, "utf8");
|
|
15254
|
+
} catch (error) {
|
|
15255
|
+
if (!(error instanceof Error && "code" in error && error.code === "ENOENT"))
|
|
15256
|
+
throw new CliError("FILESYSTEM_ERROR", "Cannot safely read .env.local.", {
|
|
15257
|
+
exitCode: EXIT_CODES.filesystem,
|
|
15258
|
+
path: path2
|
|
15259
|
+
});
|
|
15260
|
+
}
|
|
15261
|
+
if (existing && !await prompts.confirm(
|
|
15262
|
+
"Replace existing Agora and Spatius credentials? Other variables are preserved.",
|
|
15263
|
+
false
|
|
15264
|
+
))
|
|
15265
|
+
return "unchanged";
|
|
15266
|
+
const values = {};
|
|
15267
|
+
const previous = parseDotenv(existing ?? "");
|
|
15268
|
+
const secretKeys = ["AGORA_APP_CERTIFICATE", "SPATIUS_API_KEY"];
|
|
15269
|
+
for (const key of [
|
|
15270
|
+
"AGORA_APP_ID",
|
|
15271
|
+
"AGORA_APP_CERTIFICATE",
|
|
15272
|
+
"AGORA_PIPELINE_ID",
|
|
15273
|
+
"SPATIUS_APP_ID",
|
|
15274
|
+
"SPATIUS_API_KEY",
|
|
15275
|
+
"SPATIUS_AVATAR_ID",
|
|
15276
|
+
"SPATIUS_REGION",
|
|
15277
|
+
"AGORA_AVATAR_SAMPLE_RATE"
|
|
15278
|
+
]) {
|
|
15279
|
+
const defaults = {
|
|
15280
|
+
SPATIUS_REGION: "cn-beijing",
|
|
15281
|
+
AGORA_AVATAR_SAMPLE_RATE: "24000"
|
|
15282
|
+
};
|
|
15283
|
+
while (true) {
|
|
15284
|
+
const value = secretKeys.includes(key) ? await prompts.password(key) : await prompts.input(key, {
|
|
15285
|
+
initialValue: previous.get(key) ?? defaults[key]
|
|
15286
|
+
});
|
|
15287
|
+
if (isPlaceholderValue(value) || new RegExp("\\p{Cc}", "u").test(value)) {
|
|
15288
|
+
onStatus("Enter a non-empty value without control characters.");
|
|
15289
|
+
continue;
|
|
15290
|
+
}
|
|
15291
|
+
if (["AGORA_APP_ID", "AGORA_APP_CERTIFICATE"].includes(key) && !/^[a-f\d]{32}$/iu.test(value)) {
|
|
15292
|
+
onStatus(
|
|
15293
|
+
"Agora App ID and certificate must contain 32 hexadecimal characters."
|
|
15294
|
+
);
|
|
15295
|
+
continue;
|
|
15296
|
+
}
|
|
15297
|
+
if (key === "AGORA_AVATAR_SAMPLE_RATE" && ![8e3, 16e3, 22050, 24e3, 32e3, 44100, 48e3].includes(
|
|
15298
|
+
Number(value)
|
|
15299
|
+
)) {
|
|
15300
|
+
onStatus("Choose a supported audio sample rate.");
|
|
15301
|
+
continue;
|
|
15302
|
+
}
|
|
15303
|
+
values[key] = value;
|
|
15304
|
+
break;
|
|
15305
|
+
}
|
|
15306
|
+
}
|
|
15307
|
+
const rendered = mergeDotenv(
|
|
15308
|
+
existing ?? await readFile5(join7(targetDirectory, ".env.local.example"), "utf8"),
|
|
15309
|
+
values
|
|
15310
|
+
);
|
|
15311
|
+
const temporary = join7(targetDirectory, `.env.local.${randomUUID3()}.tmp`);
|
|
15312
|
+
try {
|
|
15313
|
+
await writeFile4(temporary, rendered, { flag: "wx", mode: 384 });
|
|
15314
|
+
await rename2(temporary, path2);
|
|
15315
|
+
} finally {
|
|
15316
|
+
await rm3(temporary, { force: true });
|
|
15317
|
+
}
|
|
15318
|
+
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."
|
|
15320
|
+
);
|
|
15321
|
+
return "configured";
|
|
15322
|
+
};
|
|
15323
|
+
|
|
15324
|
+
// src/templates/agora.ts
|
|
15325
|
+
init_project_config();
|
|
15326
|
+
var files = /* @__PURE__ */ new Set([
|
|
15327
|
+
"package.json",
|
|
15328
|
+
"package-lock.json",
|
|
15329
|
+
"pnpm-lock.yaml",
|
|
15330
|
+
"pnpm-workspace.yaml",
|
|
15331
|
+
"npmrc",
|
|
15332
|
+
"gitignore",
|
|
15333
|
+
"dockerignore",
|
|
15334
|
+
"Dockerfile",
|
|
15335
|
+
".editorconfig",
|
|
15336
|
+
".prettierrc.json",
|
|
15337
|
+
".prettierignore",
|
|
15338
|
+
"README.md",
|
|
15339
|
+
"AGENTS.md",
|
|
15340
|
+
"DEPLOYMENT.md",
|
|
15341
|
+
"spatius.config.json",
|
|
15342
|
+
"index.html",
|
|
15343
|
+
"tsconfig.json",
|
|
15344
|
+
"vite.config.ts",
|
|
15345
|
+
"vitest.config.ts",
|
|
15346
|
+
"eslint.config.mjs",
|
|
15347
|
+
"env.example",
|
|
15348
|
+
"server/index.ts",
|
|
15349
|
+
"worker/index.ts",
|
|
15350
|
+
"worker/agora.ts",
|
|
15351
|
+
"worker/agora.test.ts",
|
|
15352
|
+
"worker/capability.ts",
|
|
15353
|
+
"worker/capability.test.ts",
|
|
15354
|
+
"web/App.tsx",
|
|
15355
|
+
"web/main.tsx",
|
|
15356
|
+
"web/styles.css",
|
|
15357
|
+
"web/agora-session.ts",
|
|
15358
|
+
"web/agora-session.test.ts",
|
|
15359
|
+
"web/components/transcript-view.tsx",
|
|
15360
|
+
"web/components/transcript-panel.tsx",
|
|
15361
|
+
"web/components/icons.tsx",
|
|
15362
|
+
"web/components/agents-ui/agent-chat-input.tsx",
|
|
15363
|
+
"web/components/agents-ui/LICENSE",
|
|
15364
|
+
"web/components/agents-ui/PROVENANCE.md"
|
|
15365
|
+
]);
|
|
15366
|
+
var agoraTemplate = {
|
|
15367
|
+
id: "zeabur-agora",
|
|
15368
|
+
stack: "zeabur-agora",
|
|
15369
|
+
requiresPython: false,
|
|
15370
|
+
humanSteps: [
|
|
15371
|
+
{
|
|
15372
|
+
command: "npx create-spatius-app setup . --interactive",
|
|
15373
|
+
reason: "Enter Agora and Spatius credentials in a secure interactive terminal and configure a published English Agora pipeline. Never paste secrets into chat.",
|
|
15374
|
+
requiresHuman: true,
|
|
15375
|
+
requiresTty: true
|
|
15376
|
+
}
|
|
15377
|
+
],
|
|
15378
|
+
directory: "templates/cloudflare-livekit",
|
|
15379
|
+
description: "Zeabur + Agora Conversational AI",
|
|
15380
|
+
components: [
|
|
15381
|
+
"- web/ React + Spatius AvatarKit + Agora",
|
|
15382
|
+
"- server/ Node HTTP API; Agora hosts the conversational agent"
|
|
15383
|
+
],
|
|
15384
|
+
layers: [
|
|
15385
|
+
{ directory: "templates/layers/catalog" },
|
|
15386
|
+
{
|
|
15387
|
+
directory: "templates/layers/node",
|
|
15388
|
+
overrides: ["vite.config.ts", "package-lock.json", "pnpm-lock.yaml"]
|
|
15389
|
+
},
|
|
15390
|
+
{
|
|
15391
|
+
directory: "templates/layers/agora",
|
|
15392
|
+
overrides: [
|
|
15393
|
+
"package.json",
|
|
15394
|
+
"package-lock.json",
|
|
15395
|
+
"pnpm-lock.yaml",
|
|
15396
|
+
"web/App.tsx",
|
|
15397
|
+
"worker/index.ts",
|
|
15398
|
+
"tsconfig.json",
|
|
15399
|
+
"vitest.config.ts",
|
|
15400
|
+
"README.md",
|
|
15401
|
+
"AGENTS.md"
|
|
15402
|
+
]
|
|
15403
|
+
}
|
|
15404
|
+
],
|
|
15405
|
+
includeFile(path2, configuration) {
|
|
15406
|
+
if (!cloudflareLivekitTemplate.includeFile(path2, configuration)) return false;
|
|
15407
|
+
return files.has(path2) || [...files].some((file) => file.startsWith(path2 + "/")) || path2 === "web/assets" || path2.startsWith("web/assets/");
|
|
15408
|
+
},
|
|
15409
|
+
mapFile(path2) {
|
|
15410
|
+
return path2 === "env.example" ? ".env.local.example" : cloudflareLivekitTemplate.mapFile(path2);
|
|
15411
|
+
},
|
|
15412
|
+
async configure(directory, configuration) {
|
|
15413
|
+
const manager = configuration.packageManagers.javascript;
|
|
15414
|
+
const run = (script) => javascriptRunCommand(manager.name, script);
|
|
15415
|
+
const path2 = join8(directory, "package.json");
|
|
15416
|
+
const pkg = JSON.parse(await readFile6(path2, "utf8"));
|
|
15417
|
+
pkg.name = toValidPackageName(
|
|
15418
|
+
configuration.projectName ?? basename3(directory)
|
|
15419
|
+
);
|
|
15420
|
+
if (manager.version && manager.version !== "unknown")
|
|
15421
|
+
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"];
|
|
15424
|
+
await writeFile5(path2, JSON.stringify(pkg, null, 2) + "\n");
|
|
15425
|
+
if (manager.name === "npm") {
|
|
15426
|
+
const lockPath = join8(directory, "package-lock.json");
|
|
15427
|
+
const lock = JSON.parse(await readFile6(lockPath, "utf8"));
|
|
15428
|
+
lock.name = pkg.name;
|
|
15429
|
+
lock.packages[""].name = pkg.name;
|
|
15430
|
+
await writeFile5(lockPath, JSON.stringify(lock, null, 2) + "\n");
|
|
15431
|
+
}
|
|
15432
|
+
await writeFile5(
|
|
15433
|
+
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"))
|
|
15452
|
+
);
|
|
15453
|
+
for (const name of ["README.md", "AGENTS.md"]) {
|
|
15454
|
+
const path3 = join8(directory, name);
|
|
15455
|
+
await writeFile5(
|
|
15456
|
+
path3,
|
|
15457
|
+
(await readFile6(path3, "utf8")).replaceAll("__INSTALL__", javascriptInstallCommand(manager.name)).replaceAll("__DEV__", run("dev")).replaceAll("__CHECK__", run("check"))
|
|
15458
|
+
);
|
|
15459
|
+
}
|
|
15460
|
+
await writeFile5(
|
|
15461
|
+
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"
|
|
15463
|
+
);
|
|
15464
|
+
},
|
|
15465
|
+
createInstallPlan(directory, managers, platform) {
|
|
15466
|
+
return createInstallPlan(
|
|
15467
|
+
directory,
|
|
15468
|
+
{ javascript: managers.javascript },
|
|
15469
|
+
platform
|
|
15470
|
+
);
|
|
15471
|
+
},
|
|
15472
|
+
nextSteps({
|
|
15473
|
+
dependenciesInstalled,
|
|
15474
|
+
credentialsConfigured,
|
|
15475
|
+
javascriptPackageManager
|
|
15476
|
+
}) {
|
|
15477
|
+
return [
|
|
15478
|
+
...dependenciesInstalled ? [] : [javascriptInstallCommand(javascriptPackageManager)],
|
|
15479
|
+
...credentialsConfigured ? [] : ["npx create-spatius-app setup . --interactive"],
|
|
15480
|
+
javascriptRunCommand(javascriptPackageManager, "dev")
|
|
15481
|
+
];
|
|
15482
|
+
},
|
|
15483
|
+
setup: {
|
|
15484
|
+
recognizes: async (directory) => (await readProjectConfig(directory))?.stack === "zeabur-agora",
|
|
15485
|
+
run: runAgoraSetup
|
|
15486
|
+
},
|
|
15487
|
+
verification: [
|
|
15488
|
+
{ javascript: "npm", scripts: ["check"] },
|
|
15489
|
+
{ javascript: "pnpm", scripts: ["check"] }
|
|
15490
|
+
]
|
|
15491
|
+
};
|
|
15492
|
+
|
|
15493
|
+
// 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
|
+
init_package_managers();
|
|
15498
|
+
function composeTemplate(stack) {
|
|
15499
|
+
if (stack === "zeabur-agora") return agoraTemplate;
|
|
15500
|
+
const selected = stacks[stack];
|
|
15501
|
+
const node = selected.web === "railway";
|
|
15502
|
+
return {
|
|
15503
|
+
...cloudflareLivekitTemplate,
|
|
15504
|
+
id: stack,
|
|
15505
|
+
stack,
|
|
15506
|
+
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
|
+
},
|
|
15535
|
+
async configure(directory, configuration) {
|
|
15536
|
+
await cloudflareLivekitTemplate.configure(directory, configuration);
|
|
15537
|
+
await writeFile6(
|
|
15538
|
+
join9(directory, "spatius.config.json"),
|
|
15539
|
+
JSON.stringify({ version: 2, stack }, null, 2) + "\n"
|
|
15540
|
+
);
|
|
15541
|
+
const packagePath = join9(directory, "package.json");
|
|
15542
|
+
const pkg = JSON.parse(await readFile7(packagePath, "utf8"));
|
|
15543
|
+
const manager = configuration.packageManagers.javascript.name;
|
|
15544
|
+
const run = (script) => javascriptRunCommand(manager, script);
|
|
15545
|
+
let deployment = `# ${selected.label}
|
|
15546
|
+
|
|
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")}\`.
|
|
15583
|
+
|
|
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");
|
|
15593
|
+
}
|
|
15594
|
+
};
|
|
15595
|
+
}
|
|
15596
|
+
|
|
15597
|
+
// src/templates.ts
|
|
15598
|
+
init_errors();
|
|
15599
|
+
import { fileURLToPath } from "url";
|
|
15600
|
+
|
|
15077
15601
|
// src/scaffold.ts
|
|
15078
15602
|
init_errors();
|
|
15079
15603
|
import { constants } from "fs";
|
|
15080
|
-
import { copyFile, lstat as
|
|
15081
|
-
import { basename as
|
|
15604
|
+
import { copyFile, lstat as lstat6, mkdir, readdir as readdir2, rm as rm4 } from "fs/promises";
|
|
15605
|
+
import { basename as basename4, dirname as dirname2, isAbsolute, join as join10, win32 as win322 } from "path";
|
|
15082
15606
|
|
|
15083
15607
|
// src/project-directory.ts
|
|
15084
15608
|
init_errors();
|
|
15085
15609
|
import { homedir as homedir2 } from "os";
|
|
15086
|
-
import { lstat as
|
|
15610
|
+
import { lstat as lstat5, readdir } from "fs/promises";
|
|
15087
15611
|
import { parse as parse2, resolve } from "path";
|
|
15088
15612
|
function normalizeTargetDirectory(input, currentWorkingDirectory = process.cwd()) {
|
|
15089
15613
|
return resolve(currentWorkingDirectory, input);
|
|
@@ -15115,7 +15639,7 @@ function assertSafeTargetDirectory(targetDirectory, homeDirectory = homedir2())
|
|
|
15115
15639
|
}
|
|
15116
15640
|
async function inspectTargetDirectory(targetDirectory) {
|
|
15117
15641
|
try {
|
|
15118
|
-
const stats = await
|
|
15642
|
+
const stats = await lstat5(targetDirectory);
|
|
15119
15643
|
if (!stats.isDirectory() || stats.isSymbolicLink()) {
|
|
15120
15644
|
return "not-a-directory";
|
|
15121
15645
|
}
|
|
@@ -15187,10 +15711,10 @@ function assertRelativeDestination(path2) {
|
|
|
15187
15711
|
}
|
|
15188
15712
|
}
|
|
15189
15713
|
async function listTemplateFiles(templateDirectory, template, configuration, relativeDirectory = "") {
|
|
15190
|
-
const entries = await readdir2(
|
|
15714
|
+
const entries = await readdir2(join10(templateDirectory, relativeDirectory), {
|
|
15191
15715
|
withFileTypes: true
|
|
15192
15716
|
});
|
|
15193
|
-
const
|
|
15717
|
+
const files2 = [];
|
|
15194
15718
|
for (const entry of entries) {
|
|
15195
15719
|
if (shouldIgnoreTemplateEntry(entry.name)) continue;
|
|
15196
15720
|
const source = [relativeDirectory, entry.name].filter(Boolean).join("/");
|
|
@@ -15199,7 +15723,7 @@ async function listTemplateFiles(templateDirectory, template, configuration, rel
|
|
|
15199
15723
|
throw new Error("Bundled templates must not contain symbolic links.");
|
|
15200
15724
|
}
|
|
15201
15725
|
if (entry.isDirectory()) {
|
|
15202
|
-
|
|
15726
|
+
files2.push(
|
|
15203
15727
|
...await listTemplateFiles(
|
|
15204
15728
|
templateDirectory,
|
|
15205
15729
|
template,
|
|
@@ -15210,12 +15734,12 @@ async function listTemplateFiles(templateDirectory, template, configuration, rel
|
|
|
15210
15734
|
} else if (entry.isFile()) {
|
|
15211
15735
|
const destination = template.mapFile(source);
|
|
15212
15736
|
assertRelativeDestination(destination);
|
|
15213
|
-
|
|
15737
|
+
files2.push({ source, destination });
|
|
15214
15738
|
} else {
|
|
15215
15739
|
throw new Error("Bundled templates must contain regular files.");
|
|
15216
15740
|
}
|
|
15217
15741
|
}
|
|
15218
|
-
return
|
|
15742
|
+
return files2;
|
|
15219
15743
|
}
|
|
15220
15744
|
async function createScaffoldPlan({
|
|
15221
15745
|
configuration,
|
|
@@ -15227,7 +15751,7 @@ async function createScaffoldPlan({
|
|
|
15227
15751
|
const targetState = await assertTargetDirectoryAvailable(targetDirectory);
|
|
15228
15752
|
let entries;
|
|
15229
15753
|
try {
|
|
15230
|
-
const root = await
|
|
15754
|
+
const root = await lstat6(templateDirectory);
|
|
15231
15755
|
if (!root.isDirectory() || root.isSymbolicLink()) {
|
|
15232
15756
|
throw new Error("The template must be a regular directory.");
|
|
15233
15757
|
}
|
|
@@ -15236,6 +15760,33 @@ async function createScaffoldPlan({
|
|
|
15236
15760
|
template,
|
|
15237
15761
|
configuration
|
|
15238
15762
|
);
|
|
15763
|
+
for (const layer of template.layers ?? []) {
|
|
15764
|
+
const directory = resolveTemplateDirectory({
|
|
15765
|
+
...template,
|
|
15766
|
+
directory: layer.directory
|
|
15767
|
+
});
|
|
15768
|
+
const root2 = await lstat6(directory);
|
|
15769
|
+
if (!root2.isDirectory() || root2.isSymbolicLink())
|
|
15770
|
+
throw new Error("Invalid template layer.");
|
|
15771
|
+
const additions = await listTemplateFiles(
|
|
15772
|
+
directory,
|
|
15773
|
+
template,
|
|
15774
|
+
configuration
|
|
15775
|
+
);
|
|
15776
|
+
for (const addition of additions) {
|
|
15777
|
+
const index = entries.findIndex(
|
|
15778
|
+
(entry) => entry.destination === addition.destination
|
|
15779
|
+
);
|
|
15780
|
+
if (index >= 0) {
|
|
15781
|
+
if (!layer.overrides?.includes(addition.destination))
|
|
15782
|
+
throw new Error(
|
|
15783
|
+
`Undeclared template override: ${addition.destination}`
|
|
15784
|
+
);
|
|
15785
|
+
entries.splice(index, 1);
|
|
15786
|
+
}
|
|
15787
|
+
entries.push({ ...addition, directory });
|
|
15788
|
+
}
|
|
15789
|
+
}
|
|
15239
15790
|
const destinations = /* @__PURE__ */ new Set();
|
|
15240
15791
|
const directories = /* @__PURE__ */ new Map();
|
|
15241
15792
|
for (const { destination } of entries) {
|
|
@@ -15294,11 +15845,11 @@ async function createScaffoldPlan({
|
|
|
15294
15845
|
}
|
|
15295
15846
|
async function rollback(targetDirectory, targetCreated, createdPaths) {
|
|
15296
15847
|
if (targetCreated) {
|
|
15297
|
-
await
|
|
15848
|
+
await rm4(targetDirectory, { force: true, recursive: true });
|
|
15298
15849
|
return;
|
|
15299
15850
|
}
|
|
15300
15851
|
for (const path2 of [...createdPaths].reverse()) {
|
|
15301
|
-
await
|
|
15852
|
+
await rm4(path2, { force: true, recursive: true });
|
|
15302
15853
|
}
|
|
15303
15854
|
}
|
|
15304
15855
|
async function scaffoldProject({
|
|
@@ -15333,9 +15884,9 @@ async function scaffoldProject({
|
|
|
15333
15884
|
targetCreated = true;
|
|
15334
15885
|
}
|
|
15335
15886
|
for (const entry of plan.entries) {
|
|
15336
|
-
const source =
|
|
15337
|
-
const destination =
|
|
15338
|
-
const stats = await
|
|
15887
|
+
const source = join10(entry.directory ?? templateDirectory, entry.source);
|
|
15888
|
+
const destination = join10(targetDirectory, entry.destination);
|
|
15889
|
+
const stats = await lstat6(source);
|
|
15339
15890
|
if (!stats.isFile() || stats.isSymbolicLink()) {
|
|
15340
15891
|
throw new Error("A template source is no longer a regular file.");
|
|
15341
15892
|
}
|
|
@@ -15352,7 +15903,7 @@ async function scaffoldProject({
|
|
|
15352
15903
|
);
|
|
15353
15904
|
throw new CliError(
|
|
15354
15905
|
"FILESYSTEM_ERROR",
|
|
15355
|
-
"Could not create " +
|
|
15906
|
+
"Could not create " + basename4(targetDirectory) + ": " + (error instanceof Error ? error.message : "unknown filesystem error"),
|
|
15356
15907
|
{
|
|
15357
15908
|
exitCode: EXIT_CODES.filesystem,
|
|
15358
15909
|
path: targetDirectory,
|
|
@@ -15366,16 +15917,26 @@ async function scaffoldProject({
|
|
|
15366
15917
|
// src/templates.ts
|
|
15367
15918
|
init_package_managers();
|
|
15368
15919
|
var DEFAULT_TEMPLATE_ID = "cloudflare-livekit";
|
|
15369
|
-
var templateRegistry =
|
|
15370
|
-
[
|
|
15371
|
-
|
|
15920
|
+
var templateRegistry = Object.fromEntries(
|
|
15921
|
+
Object.keys(stacks).map((stack) => [
|
|
15922
|
+
stack,
|
|
15923
|
+
composeTemplate(stack)
|
|
15924
|
+
])
|
|
15925
|
+
);
|
|
15372
15926
|
function getTemplate(id = DEFAULT_TEMPLATE_ID) {
|
|
15373
|
-
|
|
15927
|
+
const template = templateRegistry[id];
|
|
15928
|
+
if (!template)
|
|
15929
|
+
throw new CliError("INVALID_ARGUMENT", `Unknown template: ${id}`, {
|
|
15930
|
+
exitCode: EXIT_CODES.invalidArgument
|
|
15931
|
+
});
|
|
15932
|
+
return template;
|
|
15374
15933
|
}
|
|
15375
15934
|
function resolveTemplateDirectory(template = getTemplate()) {
|
|
15376
15935
|
return fileURLToPath(new URL(`../${template.directory}/`, import.meta.url));
|
|
15377
15936
|
}
|
|
15378
|
-
async function resolveProjectTemplate(targetDirectory, templates =
|
|
15937
|
+
async function resolveProjectTemplate(targetDirectory, templates = [getTemplate()]) {
|
|
15938
|
+
const config = await readProjectConfig(targetDirectory);
|
|
15939
|
+
if (config) return getTemplate(config.stack);
|
|
15379
15940
|
const matches = [];
|
|
15380
15941
|
for (const template of templates) {
|
|
15381
15942
|
if (await template.setup.recognizes(targetDirectory))
|
|
@@ -15568,10 +16129,10 @@ function resolveInteractionMode({
|
|
|
15568
16129
|
init_package_managers();
|
|
15569
16130
|
|
|
15570
16131
|
// src/output.ts
|
|
15571
|
-
var OUTPUT_SCHEMA_VERSION =
|
|
16132
|
+
var OUTPUT_SCHEMA_VERSION = 4;
|
|
15572
16133
|
function createSuccessResult({
|
|
15573
16134
|
dryRun,
|
|
15574
|
-
files,
|
|
16135
|
+
files: files2,
|
|
15575
16136
|
generatorVersion,
|
|
15576
16137
|
dependenciesInstalled,
|
|
15577
16138
|
javascriptPackageManager,
|
|
@@ -15587,7 +16148,7 @@ function createSuccessResult({
|
|
|
15587
16148
|
gitInitialized: false
|
|
15588
16149
|
},
|
|
15589
16150
|
command: "create",
|
|
15590
|
-
created: dryRun ? [] : [...
|
|
16151
|
+
created: dryRun ? [] : [...files2],
|
|
15591
16152
|
dryRun,
|
|
15592
16153
|
generatorVersion,
|
|
15593
16154
|
humanSteps: template.humanSteps.map((step) => ({ ...step })),
|
|
@@ -15600,12 +16161,12 @@ function createSuccessResult({
|
|
|
15600
16161
|
ok: true,
|
|
15601
16162
|
packageManagers: {
|
|
15602
16163
|
javascript: javascriptPackageManager,
|
|
15603
|
-
python: pythonPackageManager.name
|
|
16164
|
+
...pythonPackageManager ? { python: pythonPackageManager.name } : {}
|
|
15604
16165
|
},
|
|
15605
16166
|
projectDirectory,
|
|
15606
16167
|
schemaVersion: OUTPUT_SCHEMA_VERSION,
|
|
15607
|
-
|
|
15608
|
-
wouldCreate: dryRun ? [...
|
|
16168
|
+
stack: template.stack ?? "cloudflare-livekit",
|
|
16169
|
+
wouldCreate: dryRun ? [...files2] : []
|
|
15609
16170
|
};
|
|
15610
16171
|
}
|
|
15611
16172
|
function createFailureResult(error) {
|
|
@@ -16035,7 +16596,10 @@ var PromptSession = class {
|
|
|
16035
16596
|
return this.confirm("Install dependencies now?", true);
|
|
16036
16597
|
}
|
|
16037
16598
|
shouldConfigureCredentials() {
|
|
16038
|
-
return this.confirm(
|
|
16599
|
+
return this.confirm(
|
|
16600
|
+
"Configure provider and Spatius credentials now?",
|
|
16601
|
+
true
|
|
16602
|
+
);
|
|
16039
16603
|
}
|
|
16040
16604
|
};
|
|
16041
16605
|
|
|
@@ -16079,7 +16643,7 @@ async function readPackageVersion() {
|
|
|
16079
16643
|
const packagePath = fileURLToPath2(
|
|
16080
16644
|
new URL("../package.json", import.meta.url)
|
|
16081
16645
|
);
|
|
16082
|
-
const contents = await
|
|
16646
|
+
const contents = await readFile8(packagePath, "utf8");
|
|
16083
16647
|
const metadata = JSON.parse(contents);
|
|
16084
16648
|
return metadata.version;
|
|
16085
16649
|
}
|
|
@@ -16177,7 +16741,7 @@ function managerOptions(managers) {
|
|
|
16177
16741
|
value: manager.name
|
|
16178
16742
|
}));
|
|
16179
16743
|
}
|
|
16180
|
-
async function resolvePackageManagers(options, interactive, prompts) {
|
|
16744
|
+
async function resolvePackageManagers(options, interactive, prompts, requiresPython = true) {
|
|
16181
16745
|
if (!interactive && (options.install !== true || options.dryRun)) {
|
|
16182
16746
|
const inventory2 = { javascript: [], python: [] };
|
|
16183
16747
|
const javascriptDefault2 = options.packageManager ?? inferInvokingPackageManager(process5.env.npm_config_user_agent) ?? "npm";
|
|
@@ -16187,15 +16751,17 @@ async function resolvePackageManagers(options, interactive, prompts) {
|
|
|
16187
16751
|
javascriptDefault2,
|
|
16188
16752
|
process5.env.npm_config_user_agent
|
|
16189
16753
|
),
|
|
16190
|
-
|
|
16191
|
-
|
|
16192
|
-
|
|
16193
|
-
|
|
16754
|
+
...requiresPython ? {
|
|
16755
|
+
python: selectPythonPackageManager(
|
|
16756
|
+
inventory2,
|
|
16757
|
+
options.pythonPackageManager ?? "uv"
|
|
16758
|
+
)
|
|
16759
|
+
} : {}
|
|
16194
16760
|
};
|
|
16195
16761
|
}
|
|
16196
16762
|
const inventory = await detectPackageManagers({
|
|
16197
16763
|
...options.packageManager === void 0 ? {} : { javascript: [options.packageManager] },
|
|
16198
|
-
|
|
16764
|
+
...!requiresPython ? { python: [] } : options.pythonPackageManager === void 0 ? {} : { python: [options.pythonPackageManager] }
|
|
16199
16765
|
});
|
|
16200
16766
|
const javascriptDefault = !interactive && options.packageManager === void 0 && inventory.javascript.length === 0 ? "npm" : options.packageManager;
|
|
16201
16767
|
const pythonDefault = !interactive && options.pythonPackageManager === void 0 && inventory.python.length === 0 ? "uv" : options.pythonPackageManager;
|
|
@@ -16204,7 +16770,7 @@ async function resolvePackageManagers(options, interactive, prompts) {
|
|
|
16204
16770
|
javascriptDefault,
|
|
16205
16771
|
process5.env.npm_config_user_agent
|
|
16206
16772
|
);
|
|
16207
|
-
let python = selectPythonPackageManager(inventory, pythonDefault);
|
|
16773
|
+
let python = requiresPython ? selectPythonPackageManager(inventory, pythonDefault) : void 0;
|
|
16208
16774
|
if (interactive && options.packageManager === void 0) {
|
|
16209
16775
|
const selectedName = await prompts.choose(
|
|
16210
16776
|
"Which JavaScript package manager?",
|
|
@@ -16215,7 +16781,7 @@ async function resolvePackageManagers(options, interactive, prompts) {
|
|
|
16215
16781
|
(manager) => manager.name === selectedName
|
|
16216
16782
|
);
|
|
16217
16783
|
}
|
|
16218
|
-
if (interactive && options.pythonPackageManager === void 0) {
|
|
16784
|
+
if (interactive && python && options.pythonPackageManager === void 0) {
|
|
16219
16785
|
const selectedName = await prompts.choose(
|
|
16220
16786
|
"Which Python package manager?",
|
|
16221
16787
|
managerOptions(inventory.python),
|
|
@@ -16226,7 +16792,7 @@ async function resolvePackageManagers(options, interactive, prompts) {
|
|
|
16226
16792
|
return { javascript, python };
|
|
16227
16793
|
}
|
|
16228
16794
|
function renderManagerSummary(javascript, python) {
|
|
16229
|
-
return `${javascript.name} + ${python.name}
|
|
16795
|
+
return python ? `${javascript.name} + ${python.name}` : javascript.name;
|
|
16230
16796
|
}
|
|
16231
16797
|
function isTestTerminalOverride() {
|
|
16232
16798
|
return process5.env.NODE_ENV === "test" && process5.env.CREATE_SPATIUS_APP_TEST_INTERACTIVE === "1";
|
|
@@ -16256,6 +16822,7 @@ async function detectExecutionAgent() {
|
|
|
16256
16822
|
}
|
|
16257
16823
|
async function runCreateCommand(version, projectDirectoryArgument, rawOptions) {
|
|
16258
16824
|
const options = {
|
|
16825
|
+
stack: rawOptions.stack,
|
|
16259
16826
|
debug: rawOptions.debug === true,
|
|
16260
16827
|
dryRun: rawOptions.dryRun === true,
|
|
16261
16828
|
install: rawOptions.install,
|
|
@@ -16300,6 +16867,14 @@ async function runCreateCommand(version, projectDirectoryArgument, rawOptions) {
|
|
|
16300
16867
|
`${theme.accent("create-spatius-app")} ${theme.highlight(`v${version}`)}`
|
|
16301
16868
|
);
|
|
16302
16869
|
}
|
|
16870
|
+
const selection = await selectCatalog({ ...options, interactive, prompts });
|
|
16871
|
+
const template = getTemplate(selection.stack);
|
|
16872
|
+
if (template.requiresPython === false && options.pythonPackageManager)
|
|
16873
|
+
throw new CliError(
|
|
16874
|
+
"INVALID_ARGUMENT",
|
|
16875
|
+
"This stack does not use Python. Remove --python-package-manager.",
|
|
16876
|
+
{ exitCode: EXIT_CODES.invalidArgument }
|
|
16877
|
+
);
|
|
16303
16878
|
const projectDirectoryInput = await resolveProjectDirectoryInput({
|
|
16304
16879
|
argument: projectDirectoryArgument,
|
|
16305
16880
|
interactive,
|
|
@@ -16309,13 +16884,13 @@ async function runCreateCommand(version, projectDirectoryArgument, rawOptions) {
|
|
|
16309
16884
|
const packageManagers = await resolvePackageManagers(
|
|
16310
16885
|
options,
|
|
16311
16886
|
interactive,
|
|
16312
|
-
prompts
|
|
16887
|
+
prompts,
|
|
16888
|
+
template.requiresPython !== false
|
|
16313
16889
|
);
|
|
16314
16890
|
const shouldInstall = !options.dryRun && (options.install ?? (interactive ? await prompts.shouldInstall() : false));
|
|
16315
16891
|
if (shouldInstall) {
|
|
16316
16892
|
assertPackageManagersAvailable(packageManagers);
|
|
16317
16893
|
}
|
|
16318
|
-
const template = getTemplate();
|
|
16319
16894
|
const configuration = { packageManagers };
|
|
16320
16895
|
const scaffold = options.dryRun ? await createScaffoldPlan({
|
|
16321
16896
|
configuration,
|
|
@@ -16494,7 +17069,11 @@ async function runSetupCommand(version, projectDirectoryArgument, rawOptions) {
|
|
|
16494
17069
|
}
|
|
16495
17070
|
}
|
|
16496
17071
|
function addCreateOptions(command) {
|
|
16497
|
-
return command.
|
|
17072
|
+
return command.addOption(
|
|
17073
|
+
new Option("--stack <stack>", "application and deployment stack").choices(
|
|
17074
|
+
Object.keys(stacks)
|
|
17075
|
+
)
|
|
17076
|
+
).option("-y, --yes", "accept safe defaults without prompting").addOption(new Option("--interactive", "force interactive prompts")).addOption(new Option("--no-interactive", "disable interactive prompts")).addOption(
|
|
16498
17077
|
new Option(
|
|
16499
17078
|
"--package-manager <manager>",
|
|
16500
17079
|
"use pnpm, bun, or npm for JavaScript dependencies"
|
|
@@ -16504,7 +17083,7 @@ function addCreateOptions(command) {
|
|
|
16504
17083
|
"--python-package-manager <manager>",
|
|
16505
17084
|
"use uv or pip for Python dependencies"
|
|
16506
17085
|
).choices(PYTHON_PACKAGE_MANAGERS)
|
|
16507
|
-
).option("--install", "install JavaScript and Python dependencies").option("--no-install", "skip dependency installation").option("--setup", "configure local
|
|
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");
|
|
16508
17087
|
}
|
|
16509
17088
|
async function main() {
|
|
16510
17089
|
const version = await readPackageVersion();
|
|
@@ -16529,7 +17108,7 @@ Examples:
|
|
|
16529
17108
|
async (projectDirectoryArgument, rawOptions) => runCreateCommand(version, projectDirectoryArgument, rawOptions)
|
|
16530
17109
|
);
|
|
16531
17110
|
program2.command("setup").description(
|
|
16532
|
-
"securely configure local
|
|
17111
|
+
"securely configure local provider credentials for an existing generated project"
|
|
16533
17112
|
).argument("[project-directory]", "generated project to configure", ".").addOption(new Option("--interactive", "allow browser and secret prompts")).addOption(new Option("--no-interactive", "disable interactive prompts")).option("--debug", "write safe Spatius request diagnostics to stderr").addHelpText(
|
|
16534
17113
|
"after",
|
|
16535
17114
|
`
|
|
@@ -16567,4 +17146,3 @@ try {
|
|
|
16567
17146
|
}
|
|
16568
17147
|
process5.exitCode = normalizedError.exitCode;
|
|
16569
17148
|
}
|
|
16570
|
-
//# sourceMappingURL=cli.js.map
|