@mevdragon/vidfarm-devcli 0.2.14 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -4
- package/SKILL.director.md +100 -0
- package/SKILL.platform.md +66 -6
- package/dist/src/cli.js +135 -1527
- package/package.json +5 -24
- package/.env.example +0 -60
- package/auto-create-hyperframe-templates/AUTO_CREATE_HYPERFRAME_TEMPLATES.md +0 -275
- package/auto-create-hyperframe-templates/extractor-system-prompt.md +0 -128
- package/auto-create-hyperframe-templates/input-manifest.schema.json +0 -56
- package/auto-create-hyperframe-templates/lambda-memory-estimator.md +0 -118
- package/auto-create-hyperframe-templates/production-graph.schema.json +0 -314
- package/auto-create-hyperframe-templates/runbook.md +0 -198
- package/auto-create-hyperframe-templates/scripts/create-hyperframe-template.mjs +0 -368
- package/auto-create-hyperframe-templates/template-plan.schema.json +0 -311
- package/auto-create-hyperframe-templates/template-planner-prompt.md +0 -144
- package/auto-create-templates/AUTO_CREATE_TEMPLATES.md +0 -599
- package/auto-create-templates/extractor-system-prompt.md +0 -119
- package/auto-create-templates/production-graph.schema.json +0 -276
- package/auto-create-templates/runbook.md +0 -140
- package/auto-create-templates/template-plan.schema.json +0 -301
- package/auto-create-templates/template-planner-prompt.md +0 -137
- package/dist/src/account-pages-legacy.js +0 -9338
- package/dist/src/account-pages-legacy.js.map +0 -1
- package/dist/src/account-pages.js +0 -52
- package/dist/src/account-pages.js.map +0 -1
- package/dist/src/app.js +0 -13711
- package/dist/src/app.js.map +0 -1
- package/dist/src/cli.js.map +0 -1
- package/dist/src/composition-runtime.js +0 -581
- package/dist/src/composition-runtime.js.map +0 -1
- package/dist/src/config.js +0 -140
- package/dist/src/config.js.map +0 -1
- package/dist/src/context.js +0 -492
- package/dist/src/context.js.map +0 -1
- package/dist/src/dev-app-legacy.js +0 -694
- package/dist/src/dev-app-legacy.js.map +0 -1
- package/dist/src/dev-app.js +0 -6
- package/dist/src/dev-app.js.map +0 -1
- package/dist/src/dev-serve.js.map +0 -1
- package/dist/src/domain.js +0 -2
- package/dist/src/domain.js.map +0 -1
- package/dist/src/editor-chat-history.js +0 -82
- package/dist/src/editor-chat-history.js.map +0 -1
- package/dist/src/editor-chat.js +0 -312
- package/dist/src/editor-chat.js.map +0 -1
- package/dist/src/editor-dark-theme.js +0 -1128
- package/dist/src/editor-dark-theme.js.map +0 -1
- package/dist/src/frontend/flockposter-cache-store.js +0 -124
- package/dist/src/frontend/flockposter-cache-store.js.map +0 -1
- package/dist/src/frontend/homepage-client.js +0 -117
- package/dist/src/frontend/homepage-client.js.map +0 -1
- package/dist/src/frontend/homepage-shared.js +0 -4
- package/dist/src/frontend/homepage-shared.js.map +0 -1
- package/dist/src/frontend/homepage-store.js +0 -28
- package/dist/src/frontend/homepage-store.js.map +0 -1
- package/dist/src/frontend/homepage-view.js +0 -260
- package/dist/src/frontend/homepage-view.js.map +0 -1
- package/dist/src/frontend/page-runtime-client.js +0 -124
- package/dist/src/frontend/page-runtime-client.js.map +0 -1
- package/dist/src/frontend/page-runtime-store.js +0 -9
- package/dist/src/frontend/page-runtime-store.js.map +0 -1
- package/dist/src/frontend/sentry.js +0 -42
- package/dist/src/frontend/sentry.js.map +0 -1
- package/dist/src/frontend/template-editor-chat.js +0 -3760
- package/dist/src/frontend/template-editor-chat.js.map +0 -1
- package/dist/src/help-page.js +0 -333
- package/dist/src/help-page.js.map +0 -1
- package/dist/src/homepage.js +0 -919
- package/dist/src/homepage.js.map +0 -1
- package/dist/src/hyperframes/composition.js +0 -180
- package/dist/src/hyperframes/composition.js.map +0 -1
- package/dist/src/index.js +0 -16
- package/dist/src/index.js.map +0 -1
- package/dist/src/instrument.js +0 -30
- package/dist/src/instrument.js.map +0 -1
- package/dist/src/lib/crypto.js +0 -45
- package/dist/src/lib/crypto.js.map +0 -1
- package/dist/src/lib/dev-log.js +0 -54
- package/dist/src/lib/dev-log.js.map +0 -1
- package/dist/src/lib/display-name.js +0 -11
- package/dist/src/lib/display-name.js.map +0 -1
- package/dist/src/lib/ids.js +0 -24
- package/dist/src/lib/ids.js.map +0 -1
- package/dist/src/lib/images.js +0 -19
- package/dist/src/lib/images.js.map +0 -1
- package/dist/src/lib/json.js +0 -15
- package/dist/src/lib/json.js.map +0 -1
- package/dist/src/lib/template-dna.js +0 -543
- package/dist/src/lib/template-dna.js.map +0 -1
- package/dist/src/lib/template-paths.js +0 -28
- package/dist/src/lib/template-paths.js.map +0 -1
- package/dist/src/lib/template-style-options.js +0 -203
- package/dist/src/lib/template-style-options.js.map +0 -1
- package/dist/src/lib/time.js +0 -7
- package/dist/src/lib/time.js.map +0 -1
- package/dist/src/lib/video-quality-harness.js +0 -60
- package/dist/src/lib/video-quality-harness.js.map +0 -1
- package/dist/src/page-runtime.js +0 -2
- package/dist/src/page-runtime.js.map +0 -1
- package/dist/src/page-shell.js +0 -1315
- package/dist/src/page-shell.js.map +0 -1
- package/dist/src/primitive-context.js +0 -299
- package/dist/src/primitive-context.js.map +0 -1
- package/dist/src/primitive-registry.js +0 -2181
- package/dist/src/primitive-registry.js.map +0 -1
- package/dist/src/primitive-sdk.js +0 -4
- package/dist/src/primitive-sdk.js.map +0 -1
- package/dist/src/primitives/remotion/html-image.js +0 -30
- package/dist/src/primitives/remotion/html-image.js.map +0 -1
- package/dist/src/primitives/remotion/media-slideshow.js +0 -60
- package/dist/src/primitives/remotion/media-slideshow.js.map +0 -1
- package/dist/src/react-page-shell.js +0 -35
- package/dist/src/react-page-shell.js.map +0 -1
- package/dist/src/ready-post-schedule-component.js +0 -1515
- package/dist/src/ready-post-schedule-component.js.map +0 -1
- package/dist/src/registry.js +0 -296
- package/dist/src/registry.js.map +0 -1
- package/dist/src/runtime.js +0 -31
- package/dist/src/runtime.js.map +0 -1
- package/dist/src/services/api-call-history.js +0 -249
- package/dist/src/services/api-call-history.js.map +0 -1
- package/dist/src/services/auth.js +0 -152
- package/dist/src/services/auth.js.map +0 -1
- package/dist/src/services/billing.js +0 -231
- package/dist/src/services/billing.js.map +0 -1
- package/dist/src/services/chat-threads.js +0 -92
- package/dist/src/services/chat-threads.js.map +0 -1
- package/dist/src/services/fork-access.js +0 -93
- package/dist/src/services/fork-access.js.map +0 -1
- package/dist/src/services/fork-manifest.js +0 -43
- package/dist/src/services/fork-manifest.js.map +0 -1
- package/dist/src/services/ghostcut.js +0 -132
- package/dist/src/services/ghostcut.js.map +0 -1
- package/dist/src/services/hyperframes.js +0 -1014
- package/dist/src/services/hyperframes.js.map +0 -1
- package/dist/src/services/job-capacity.js +0 -14
- package/dist/src/services/job-capacity.js.map +0 -1
- package/dist/src/services/job-logs.js +0 -197
- package/dist/src/services/job-logs.js.map +0 -1
- package/dist/src/services/jobs.js +0 -136
- package/dist/src/services/jobs.js.map +0 -1
- package/dist/src/services/media-processing.js +0 -743
- package/dist/src/services/media-processing.js.map +0 -1
- package/dist/src/services/primitive-media-lambda.js +0 -280
- package/dist/src/services/primitive-media-lambda.js.map +0 -1
- package/dist/src/services/providers.js +0 -2870
- package/dist/src/services/providers.js.map +0 -1
- package/dist/src/services/rate-limits.js +0 -237
- package/dist/src/services/rate-limits.js.map +0 -1
- package/dist/src/services/remotion.js +0 -688
- package/dist/src/services/remotion.js.map +0 -1
- package/dist/src/services/serverless-auth.js +0 -374
- package/dist/src/services/serverless-auth.js.map +0 -1
- package/dist/src/services/serverless-jobs.js +0 -1074
- package/dist/src/services/serverless-jobs.js.map +0 -1
- package/dist/src/services/serverless-provider-keys.js +0 -401
- package/dist/src/services/serverless-provider-keys.js.map +0 -1
- package/dist/src/services/serverless-records.js +0 -1088
- package/dist/src/services/serverless-records.js.map +0 -1
- package/dist/src/services/serverless-template-configs.js +0 -67
- package/dist/src/services/serverless-template-configs.js.map +0 -1
- package/dist/src/services/storage.js +0 -383
- package/dist/src/services/storage.js.map +0 -1
- package/dist/src/services/template-certification.js +0 -475
- package/dist/src/services/template-certification.js.map +0 -1
- package/dist/src/services/template-loader.js +0 -99
- package/dist/src/services/template-loader.js.map +0 -1
- package/dist/src/services/template-runtime-bundles.js +0 -217
- package/dist/src/services/template-runtime-bundles.js.map +0 -1
- package/dist/src/services/template-sources.js +0 -1017
- package/dist/src/services/template-sources.js.map +0 -1
- package/dist/src/services/video-normalization.js +0 -2
- package/dist/src/services/video-normalization.js.map +0 -1
- package/dist/src/services/webhooks.js +0 -62
- package/dist/src/services/webhooks.js.map +0 -1
- package/dist/src/template-editor-pages.js +0 -2491
- package/dist/src/template-editor-pages.js.map +0 -1
- package/dist/src/template-editor-shell.js +0 -2702
- package/dist/src/template-editor-shell.js.map +0 -1
- package/dist/src/template-sdk.js +0 -4
- package/dist/src/template-sdk.js.map +0 -1
- package/dist/src/worker.js +0 -17
- package/dist/src/worker.js.map +0 -1
- package/public/assets/homepage-app.js +0 -54
- package/public/assets/homepage-client-app.js +0 -80
- package/public/assets/page-runtime-client-app.js +0 -94
- package/readme.secret.md +0 -89
- package/templates/vidfarm_template_0000/README.md +0 -106
- package/templates/vidfarm_template_0000/SKILL.md +0 -266
- package/templates/vidfarm_template_0000/assets/Abel-Regular.ttf +0 -0
- package/templates/vidfarm_template_0000/assets/DMSerifDisplay-Regular.ttf +0 -0
- package/templates/vidfarm_template_0000/assets/Montserrat[wght].ttf +0 -0
- package/templates/vidfarm_template_0000/assets/SourceCodePro[wght].ttf +0 -0
- package/templates/vidfarm_template_0000/assets/TikTokSans-SemiBold.ttf +0 -0
- package/templates/vidfarm_template_0000/assets/Yesteryear-Regular.ttf +0 -0
- package/templates/vidfarm_template_0000/composition.json +0 -11
- package/templates/vidfarm_template_0000/src/lib/images.js +0 -221
- package/templates/vidfarm_template_0000/src/remotion/Root.js +0 -33
- package/templates/vidfarm_template_0000/src/remotion/index.js +0 -3
- package/templates/vidfarm_template_0000/src/sdk.js +0 -3
- package/templates/vidfarm_template_0000/src/style-options.js +0 -200
- package/templates/vidfarm_template_0000/src/template-dna.js +0 -9
- package/templates/vidfarm_template_0000/src/template.js +0 -1566
- package/templates/vidfarm_template_0000/template.config.json +0 -21
package/dist/src/cli.js
CHANGED
|
@@ -1,1572 +1,180 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { execFile } from "node:child_process";
|
|
4
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
5
|
-
import os from "node:os";
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
6
3
|
import path from "node:path";
|
|
7
4
|
import { parseArgs } from "node:util";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
console.warn(`${yellow} Commands may partially work but are not supported. No fixes will land.${reset}`);
|
|
35
|
-
console.warn(line);
|
|
36
|
-
console.warn("");
|
|
37
|
-
}
|
|
5
|
+
// vidfarm-devcli — thin CLI that pairs a local composition directory with the
|
|
6
|
+
// hosted Trackpad Editor at https://vidfarm.cc/editor/. Point it at a template
|
|
7
|
+
// id, and it: resolves the user's fork, downloads the current composition,
|
|
8
|
+
// starts a local dev-serve, and prints the editor URL to open in the browser.
|
|
9
|
+
const DEFAULT_HOST = "https://vidfarm.cc";
|
|
10
|
+
const DEFAULT_PORT = 4321;
|
|
11
|
+
const HELP = `vidfarm-devcli — local editor bridge for the Vidfarm Trackpad Editor
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
vidfarm-devcli <template_id> [options] Start local editor session
|
|
15
|
+
vidfarm-devcli dev-serve [options] Serve an existing local dir only
|
|
16
|
+
vidfarm-devcli --help Show this help
|
|
17
|
+
|
|
18
|
+
Options (edit mode):
|
|
19
|
+
--port <n> Local dev-serve port (default: ${DEFAULT_PORT})
|
|
20
|
+
--dir <path> Local composition directory (default: .vidfarm/<template_id>)
|
|
21
|
+
--host <url> Vidfarm host to fetch from (default: ${DEFAULT_HOST})
|
|
22
|
+
--fork <id> Fork id to fetch (default: auto-resolve for the current user)
|
|
23
|
+
--api-key <key> Vidfarm API key (or set VIDFARM_API_KEY)
|
|
24
|
+
--share <token> Share token for a shared fork (public read)
|
|
25
|
+
--refetch Overwrite local composition files with the latest remote copy
|
|
26
|
+
|
|
27
|
+
Options (dev-serve mode):
|
|
28
|
+
--dir <path> Directory to serve (default: current dir)
|
|
29
|
+
--port <n> Port to listen on (default: ${DEFAULT_PORT})
|
|
30
|
+
`;
|
|
38
31
|
void main().catch((error) => {
|
|
39
32
|
console.error(error instanceof Error ? error.stack ?? error.message : String(error));
|
|
40
33
|
process.exit(1);
|
|
41
34
|
});
|
|
42
35
|
async function main() {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
await runSessionCommand(process.argv.slice(3));
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
if (command === "dev") {
|
|
50
|
-
await runDevCommand(process.argv.slice(3));
|
|
36
|
+
const argv = process.argv.slice(2);
|
|
37
|
+
if (argv.length === 0 || argv[0] === "--help" || argv[0] === "-h") {
|
|
38
|
+
process.stdout.write(HELP);
|
|
51
39
|
return;
|
|
52
40
|
}
|
|
41
|
+
const command = argv[0];
|
|
53
42
|
if (command === "dev-serve") {
|
|
54
43
|
const { runDevServeCommand } = await import("./dev-serve.js");
|
|
55
|
-
await runDevServeCommand(
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
if (command === "validate-template") {
|
|
59
|
-
await runValidateTemplateCommand(process.argv.slice(3));
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
if (command === "import-source") {
|
|
63
|
-
await runImportSourceCommand(process.argv.slice(3));
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
if (command === "import-source-prod") {
|
|
67
|
-
await runImportSourceProdCommand(process.argv.slice(3));
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
if (command === "register-source-hosted") {
|
|
71
|
-
await runRegisterSourceHostedCommand(process.argv.slice(3));
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
if (command === "publish-update-hosted") {
|
|
75
|
-
await runPublishUpdateHostedCommand(process.argv.slice(3));
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
if (command === "register-source-prod") {
|
|
79
|
-
await runRegisterSourceProdCommand(process.argv.slice(3));
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
if (command === "deploy-template-cycle") {
|
|
83
|
-
await runDeployTemplateCycleCommand(process.argv.slice(3));
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
if (command === "generate-template") {
|
|
87
|
-
await runGenerateTemplateCommand(process.argv.slice(3));
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
if (command === "autocreate-template" || command === "autocreate") {
|
|
91
|
-
await runAutocreateTemplateCommand(process.argv.slice(3));
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
if (command === "copy-reference-template") {
|
|
95
|
-
await runCopyReferenceTemplateCommand(process.argv.slice(3));
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
if (command === "analyze-viral-dna") {
|
|
99
|
-
await runAnalyzeTemplateDnaCommand("viral", process.argv.slice(3));
|
|
44
|
+
await runDevServeCommand(argv.slice(1));
|
|
100
45
|
return;
|
|
101
46
|
}
|
|
102
|
-
if (command === "
|
|
103
|
-
await
|
|
47
|
+
if (command === "edit") {
|
|
48
|
+
await runEditCommand(argv.slice(1));
|
|
104
49
|
return;
|
|
105
50
|
}
|
|
106
|
-
if (command
|
|
107
|
-
|
|
51
|
+
if (!command.startsWith("-")) {
|
|
52
|
+
// Positional template id → default `edit` command.
|
|
53
|
+
await runEditCommand(argv);
|
|
108
54
|
return;
|
|
109
55
|
}
|
|
110
|
-
throw new Error(`Unknown command: ${command}`);
|
|
111
|
-
}
|
|
112
|
-
async function runDevCommand(argv) {
|
|
113
|
-
const parsed = parseArgs({
|
|
114
|
-
args: argv,
|
|
115
|
-
options: {
|
|
116
|
-
port: { type: "string", default: "3000" },
|
|
117
|
-
"data-dir": { type: "string", default: ".vidfarm/local" },
|
|
118
|
-
"env-file": { type: "string", default: ".env" },
|
|
119
|
-
email: { type: "string", default: "dev@vidfarm.local" },
|
|
120
|
-
name: { type: "string", default: "Vidfarm Local Dev" },
|
|
121
|
-
"api-key": { type: "string" },
|
|
122
|
-
reset: { type: "boolean", default: false },
|
|
123
|
-
"mock-ai": { type: "boolean" }
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
const port = Number(parsed.values.port);
|
|
127
|
-
const root = process.cwd();
|
|
128
|
-
const dataDir = path.resolve(root, parsed.values["data-dir"]);
|
|
129
|
-
const sessionPath = path.join(dataDir, "session.json");
|
|
130
|
-
dotenv.config({
|
|
131
|
-
path: path.resolve(root, parsed.values["env-file"]),
|
|
132
|
-
override: false
|
|
133
|
-
});
|
|
134
|
-
if (parsed.values.reset) {
|
|
135
|
-
rmSync(dataDir, { recursive: true, force: true });
|
|
136
|
-
}
|
|
137
|
-
mkdirSync(dataDir, { recursive: true });
|
|
138
|
-
const hasProviderEnvKey = Boolean(process.env.OPENAI_API_KEY ||
|
|
139
|
-
process.env.GEMINI_API_KEY ||
|
|
140
|
-
process.env.OPENROUTER_API_KEY ||
|
|
141
|
-
process.env.PERPLEXITY_API_KEY);
|
|
142
|
-
process.env.NODE_ENV = process.env.NODE_ENV || "development";
|
|
143
|
-
process.env.PORT = String(port);
|
|
144
|
-
process.env.VIDFARM_DATA_DIR = dataDir;
|
|
145
|
-
process.env.VIDFARM_RUNTIME_BACKEND = "serverless";
|
|
146
|
-
process.env.STORAGE_DRIVER = "local";
|
|
147
|
-
process.env.PUBLIC_BASE_URL = `http://127.0.0.1:${port}`;
|
|
148
|
-
process.env.REMOTION_MODE = "local";
|
|
149
|
-
process.env.VIDFARM_ADMIN_EMAILS = process.env.VIDFARM_ADMIN_EMAILS || parsed.values.email;
|
|
150
|
-
process.env.MOCK_PROVIDER_RESPONSES =
|
|
151
|
-
parsed.values["mock-ai"] ? "true" : process.env.MOCK_PROVIDER_RESPONSES ?? (hasProviderEnvKey ? "false" : "true");
|
|
152
|
-
const [{ startRuntime }, { ServerlessAuthService }, { ServerlessProviderKeyService }, { config }, { createId }, crypto] = await Promise.all([
|
|
153
|
-
import("./runtime.js"),
|
|
154
|
-
import("./services/serverless-auth.js"),
|
|
155
|
-
import("./services/serverless-provider-keys.js"),
|
|
156
|
-
import("./config.js"),
|
|
157
|
-
import("./lib/ids.js"),
|
|
158
|
-
import("./lib/crypto.js")
|
|
159
|
-
]);
|
|
160
|
-
const auth = new ServerlessAuthService();
|
|
161
|
-
const providerKeys = new ServerlessProviderKeyService();
|
|
162
|
-
const customer = await auth.getCustomerByEmailPublic(parsed.values.email) ?? await auth.upsertCustomer({
|
|
163
|
-
id: createId("cus"),
|
|
164
|
-
email: parsed.values.email,
|
|
165
|
-
name: parsed.values.name,
|
|
166
|
-
isDeveloper: true,
|
|
167
|
-
isPaidPlan: true,
|
|
168
|
-
planTier: "standalone"
|
|
169
|
-
});
|
|
170
|
-
const rawApiKey = parsed.values["api-key"] ?? `vf_local_${Math.random().toString(36).slice(2, 12)}`;
|
|
171
|
-
await auth.insertApiKey({
|
|
172
|
-
id: createId("api"),
|
|
173
|
-
customerId: customer.id,
|
|
174
|
-
keyHash: crypto.hashSecret(rawApiKey + config.API_KEY_SALT),
|
|
175
|
-
rawValue: rawApiKey,
|
|
176
|
-
label: "Local CLI session"
|
|
177
|
-
});
|
|
178
|
-
await seedEnvProviderKeys({
|
|
179
|
-
customerId: customer.id,
|
|
180
|
-
providerKeys,
|
|
181
|
-
createId,
|
|
182
|
-
encryptString: crypto.encryptString,
|
|
183
|
-
encryptionSecret: config.ENCRYPTION_SECRET,
|
|
184
|
-
allowMockPlaceholders: config.mockProviders
|
|
185
|
-
});
|
|
186
|
-
writeFileSync(sessionPath, JSON.stringify({
|
|
187
|
-
baseUrl: process.env.PUBLIC_BASE_URL,
|
|
188
|
-
customerId: customer.id,
|
|
189
|
-
apiKey: rawApiKey,
|
|
190
|
-
email: customer.email,
|
|
191
|
-
dataDir
|
|
192
|
-
}, null, 2));
|
|
193
|
-
printStartupBanner({
|
|
194
|
-
baseUrl: process.env.PUBLIC_BASE_URL,
|
|
195
|
-
customerId: customer.id,
|
|
196
|
-
apiKey: rawApiKey,
|
|
197
|
-
email: customer.email,
|
|
198
|
-
dataDir,
|
|
199
|
-
sessionPath,
|
|
200
|
-
mockProviders: config.mockProviders
|
|
201
|
-
});
|
|
202
|
-
const runtime = await startRuntime();
|
|
203
|
-
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
204
|
-
process.on(signal, () => {
|
|
205
|
-
runtime.shutdown();
|
|
206
|
-
process.exit(0);
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
async function runValidateTemplateCommand(argv) {
|
|
211
|
-
const parsed = parseArgs({
|
|
212
|
-
args: argv,
|
|
213
|
-
options: {
|
|
214
|
-
"template-id": { type: "string" },
|
|
215
|
-
"module-path": { type: "string" },
|
|
216
|
-
"skill-path": { type: "string" }
|
|
217
|
-
}
|
|
218
|
-
});
|
|
219
|
-
const [{ templateRegistry }, { TemplateCertificationService }, { loadTemplateFromModule }] = await Promise.all([
|
|
220
|
-
import("./registry.js"),
|
|
221
|
-
import("./services/template-certification.js"),
|
|
222
|
-
import("./services/template-loader.js")
|
|
223
|
-
]);
|
|
224
|
-
const certification = new TemplateCertificationService();
|
|
225
|
-
let template;
|
|
226
|
-
let skillPath;
|
|
227
|
-
if (parsed.values["module-path"]) {
|
|
228
|
-
const modulePath = path.resolve(process.cwd(), parsed.values["module-path"]);
|
|
229
|
-
template = await loadTemplateFromModule(modulePath);
|
|
230
|
-
skillPath = path.resolve(process.cwd(), parsed.values["skill-path"] ?? "SKILL.md");
|
|
231
|
-
const report = await certification.certify({ template, skillPath, modulePath });
|
|
232
|
-
console.log(JSON.stringify(report, null, 2));
|
|
233
|
-
if (!report.passed) {
|
|
234
|
-
process.exitCode = 1;
|
|
235
|
-
}
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
else {
|
|
239
|
-
await templateRegistry.ensureInitialized();
|
|
240
|
-
const templateId = parsed.values["template-id"];
|
|
241
|
-
if (!templateId) {
|
|
242
|
-
throw new Error("Provide --template-id or --module-path.");
|
|
243
|
-
}
|
|
244
|
-
template = templateRegistry.get(templateId);
|
|
245
|
-
if (!template) {
|
|
246
|
-
throw new Error(`Unknown template: ${templateId}`);
|
|
247
|
-
}
|
|
248
|
-
skillPath = template.skillPath;
|
|
249
|
-
}
|
|
250
|
-
if (!template || !skillPath) {
|
|
251
|
-
throw new Error("Template or skill path not found for certification.");
|
|
252
|
-
}
|
|
253
|
-
const report = await certification.certify({ template, skillPath });
|
|
254
|
-
console.log(JSON.stringify(report, null, 2));
|
|
255
|
-
if (!report.passed) {
|
|
256
|
-
process.exitCode = 1;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
async function runImportSourceCommand(argv) {
|
|
260
|
-
const parsed = parseArgs({
|
|
261
|
-
args: argv,
|
|
262
|
-
options: {
|
|
263
|
-
"template-id": { type: "string" },
|
|
264
|
-
"slug-id": { type: "string" },
|
|
265
|
-
"repo-url": { type: "string" },
|
|
266
|
-
branch: { type: "string", default: "production" },
|
|
267
|
-
"template-module-path": { type: "string" },
|
|
268
|
-
"skill-path": { type: "string" },
|
|
269
|
-
"install-command": { type: "string", default: "npm install" },
|
|
270
|
-
"build-command": { type: "string", default: "npm run build" },
|
|
271
|
-
"commit-sha": { type: "string" },
|
|
272
|
-
activate: { type: "boolean", default: true }
|
|
273
|
-
}
|
|
274
|
-
});
|
|
275
|
-
const templateId = parsed.values["template-id"];
|
|
276
|
-
const slugId = parsed.values["slug-id"];
|
|
277
|
-
const repoUrl = parsed.values["repo-url"];
|
|
278
|
-
const templateModulePath = parsed.values["template-module-path"];
|
|
279
|
-
if (!templateId || !slugId || !repoUrl || !templateModulePath) {
|
|
280
|
-
throw new Error("import-source requires --template-id, --slug-id, --repo-url, and --template-module-path.");
|
|
281
|
-
}
|
|
282
|
-
if (!/(^|\/)template\.ts$/i.test(templateModulePath)) {
|
|
283
|
-
throw new Error("import-source requires --template-module-path to point at the TypeScript entrypoint, usually src/template.ts.");
|
|
284
|
-
}
|
|
285
|
-
const skillPath = parsed.values["skill-path"] ?? defaultSkillPathForTemplateModule(templateModulePath);
|
|
286
|
-
const [{ TemplateSourceService }, { templateRegistry }] = await Promise.all([
|
|
287
|
-
import("./services/template-sources.js"),
|
|
288
|
-
import("./registry.js")
|
|
289
|
-
]);
|
|
290
|
-
const sources = new TemplateSourceService();
|
|
291
|
-
const registration = await sources.registerSource({
|
|
292
|
-
templateId,
|
|
293
|
-
slugId,
|
|
294
|
-
repoUrl,
|
|
295
|
-
branch: parsed.values.branch,
|
|
296
|
-
templateModulePath,
|
|
297
|
-
skillPath,
|
|
298
|
-
installCommand: parsed.values["install-command"],
|
|
299
|
-
buildCommand: parsed.values["build-command"]
|
|
300
|
-
});
|
|
301
|
-
const release = await sources.importRelease({
|
|
302
|
-
sourceId: registration.source.id,
|
|
303
|
-
commitSha: parsed.values["commit-sha"] ?? null
|
|
304
|
-
});
|
|
305
|
-
let sourceRecord = registration.source;
|
|
306
|
-
let activated = null;
|
|
307
|
-
if (parsed.values.activate) {
|
|
308
|
-
sourceRecord = sources.approveSource({ sourceId: registration.source.id });
|
|
309
|
-
sources.approveRelease({ releaseId: release.id });
|
|
310
|
-
const result = await sources.activateRelease({ releaseId: release.id });
|
|
311
|
-
templateRegistry.registerRuntimeTemplate(result.template);
|
|
312
|
-
activated = result.release;
|
|
313
|
-
}
|
|
314
|
-
console.log(JSON.stringify({
|
|
315
|
-
source: sourceRecord,
|
|
316
|
-
registration_action: registration.action,
|
|
317
|
-
sync: registration.sync,
|
|
318
|
-
release,
|
|
319
|
-
activated_release: activated
|
|
320
|
-
}, null, 2));
|
|
321
|
-
}
|
|
322
|
-
async function runGenerateTemplateCommand(argv) {
|
|
323
|
-
const parsed = parseArgs({
|
|
324
|
-
args: argv,
|
|
325
|
-
options: {
|
|
326
|
-
"template-id": { type: "string" },
|
|
327
|
-
"slug-id": { type: "string" },
|
|
328
|
-
"template-dir": { type: "string" },
|
|
329
|
-
"project-name": { type: "string" },
|
|
330
|
-
"github-repo": { type: "string" },
|
|
331
|
-
"site-name": { type: "string" },
|
|
332
|
-
"link-to-original": { type: "string" },
|
|
333
|
-
"source-notes-path": { type: "string" },
|
|
334
|
-
"source-preview-dir": { type: "string" },
|
|
335
|
-
"dna-env-file": { type: "string", default: ".env" },
|
|
336
|
-
"dna-model": { type: "string" },
|
|
337
|
-
"skip-dna-analysis": { type: "boolean", default: false },
|
|
338
|
-
branch: { type: "string", default: "production" },
|
|
339
|
-
force: { type: "boolean", default: false }
|
|
340
|
-
}
|
|
341
|
-
});
|
|
342
|
-
const result = await generateTemplateFromStarter({
|
|
343
|
-
templateId: parsed.values["template-id"],
|
|
344
|
-
slugId: parsed.values["slug-id"],
|
|
345
|
-
templateDir: parsed.values["template-dir"],
|
|
346
|
-
projectName: parsed.values["project-name"],
|
|
347
|
-
githubRepo: parsed.values["github-repo"],
|
|
348
|
-
siteName: parsed.values["site-name"],
|
|
349
|
-
linkToOriginal: parsed.values["link-to-original"],
|
|
350
|
-
sourceNotesPath: parsed.values["source-notes-path"],
|
|
351
|
-
sourcePreviewDir: parsed.values["source-preview-dir"],
|
|
352
|
-
dnaEnvFile: parsed.values["dna-env-file"],
|
|
353
|
-
dnaModel: parsed.values["dna-model"],
|
|
354
|
-
skipDnaAnalysis: parsed.values["skip-dna-analysis"],
|
|
355
|
-
branch: parsed.values.branch,
|
|
356
|
-
force: parsed.values.force
|
|
357
|
-
});
|
|
358
|
-
console.log(JSON.stringify(result, null, 2));
|
|
359
|
-
}
|
|
360
|
-
async function generateTemplateFromStarter(input) {
|
|
361
|
-
const templateId = input.templateId ?? randomUUID();
|
|
362
|
-
const slugId = input.slugId;
|
|
363
|
-
const templateDir = input.templateDir;
|
|
364
|
-
if (!slugId || !templateDir) {
|
|
365
|
-
throw new Error("generate-template requires --slug-id and --template-dir. --template-id is optional and defaults to a new UUIDv4.");
|
|
366
|
-
}
|
|
367
|
-
const root = process.cwd();
|
|
368
|
-
const starterDir = resolveStarterTemplateDir();
|
|
369
|
-
const destinationDir = path.resolve(root, templateDir);
|
|
370
|
-
const destinationFolderName = path.basename(destinationDir);
|
|
371
|
-
assertTemplateFolderNameHasPrefix(destinationFolderName);
|
|
372
|
-
if (!existsSync(starterDir)) {
|
|
373
|
-
throw new Error(`Starter template not found: ${starterDir}`);
|
|
374
|
-
}
|
|
375
|
-
if (existsSync(destinationDir)) {
|
|
376
|
-
if (!input.force) {
|
|
377
|
-
throw new Error(`Destination already exists: ${destinationDir}`);
|
|
378
|
-
}
|
|
379
|
-
rmSync(destinationDir, { recursive: true, force: true });
|
|
380
|
-
}
|
|
381
|
-
cpSync(starterDir, destinationDir, {
|
|
382
|
-
recursive: true,
|
|
383
|
-
filter: (entry) => {
|
|
384
|
-
const name = path.basename(entry);
|
|
385
|
-
return ![".git", "node_modules", "dist", "package-lock.json"].includes(name);
|
|
386
|
-
}
|
|
387
|
-
});
|
|
388
|
-
const projectName = input.projectName ?? toProjectName(destinationFolderName);
|
|
389
|
-
const compositionId = slugId.replace(/_/g, "-");
|
|
390
|
-
const siteName = input.siteName ?? compositionId;
|
|
391
|
-
const githubRepo = input.githubRepo ?? `mevdragon/${projectName}`;
|
|
392
|
-
rewriteTemplateStarter(destinationDir, [
|
|
393
|
-
["mevdragon/vidfarm_template_0000", githubRepo],
|
|
394
|
-
["vidfarm_template_0000", projectName],
|
|
395
|
-
["vidfarm-template-0000", siteName],
|
|
396
|
-
["template_0000", slugId],
|
|
397
|
-
["template-0000", compositionId],
|
|
398
|
-
["4c7a7e1a-7f35-4f30-9f86-9c8a63c7f2db", templateId],
|
|
399
|
-
['"production"', JSON.stringify(input.branch ?? "production")]
|
|
400
|
-
]);
|
|
401
|
-
const stagedInputs = stageTemplateDnaInputs({
|
|
402
|
-
templateDir: destinationDir,
|
|
403
|
-
sourceNotesPath: input.sourceNotesPath,
|
|
404
|
-
sourcePreviewDir: input.sourcePreviewDir
|
|
405
|
-
});
|
|
406
|
-
const moduleSync = syncTemplateDnaModule({
|
|
407
|
-
templateDir: destinationDir,
|
|
408
|
-
linkToOriginal: input.linkToOriginal
|
|
409
|
-
});
|
|
410
|
-
const dnaRuns = [];
|
|
411
|
-
if (!input.skipDnaAnalysis && hasTemplatePreviewMedia(destinationDir)) {
|
|
412
|
-
dnaRuns.push(await analyzeTemplateDna({
|
|
413
|
-
mode: "viral",
|
|
414
|
-
templateDir: destinationDir,
|
|
415
|
-
envFile: input.dnaEnvFile ?? ".env",
|
|
416
|
-
model: input.dnaModel,
|
|
417
|
-
linkToOriginal: input.linkToOriginal
|
|
418
|
-
}));
|
|
419
|
-
dnaRuns.push(await analyzeTemplateDna({
|
|
420
|
-
mode: "visual",
|
|
421
|
-
templateDir: destinationDir,
|
|
422
|
-
envFile: input.dnaEnvFile ?? ".env",
|
|
423
|
-
model: input.dnaModel,
|
|
424
|
-
linkToOriginal: input.linkToOriginal
|
|
425
|
-
}));
|
|
426
|
-
}
|
|
427
|
-
return {
|
|
428
|
-
template_id: templateId,
|
|
429
|
-
slug_id: slugId,
|
|
430
|
-
template_dir: destinationDir,
|
|
431
|
-
project_name: projectName,
|
|
432
|
-
github_repo: githubRepo,
|
|
433
|
-
site_name: siteName,
|
|
434
|
-
research: stagedInputs,
|
|
435
|
-
template_dna_module: moduleSync,
|
|
436
|
-
dna_analysis_runs: dnaRuns,
|
|
437
|
-
required_template_contract: {
|
|
438
|
-
template_fields: [
|
|
439
|
-
"id",
|
|
440
|
-
"slugId",
|
|
441
|
-
"version",
|
|
442
|
-
"about.title",
|
|
443
|
-
"about.description",
|
|
444
|
-
"about.difficulty",
|
|
445
|
-
"about.viral_dna",
|
|
446
|
-
"about.visual_dna",
|
|
447
|
-
"about.preview_media",
|
|
448
|
-
"about.link_to_original",
|
|
449
|
-
"about.links_to_inspiration",
|
|
450
|
-
"about.sound",
|
|
451
|
-
"about.sound.sound_title",
|
|
452
|
-
"about.sound.sound_url",
|
|
453
|
-
"about.proposal_generator",
|
|
454
|
-
"about.skeleton_prompt",
|
|
455
|
-
"about.sample_prompts",
|
|
456
|
-
"examples",
|
|
457
|
-
"configSchema",
|
|
458
|
-
],
|
|
459
|
-
operation_fields: [
|
|
460
|
-
"operations.<name>.workflow",
|
|
461
|
-
"operations.<name>.smokeTestPayload",
|
|
462
|
-
"jobs.<workflow>",
|
|
463
|
-
],
|
|
464
|
-
files: [
|
|
465
|
-
"src/template.ts",
|
|
466
|
-
"SKILL.md",
|
|
467
|
-
],
|
|
468
|
-
},
|
|
469
|
-
next_steps: [
|
|
470
|
-
"Review src/template.ts and replace the starter proposal_generator with template-specific proposal instructions.",
|
|
471
|
-
"Review src/template.ts and replace the starter skeleton_prompt with a fill-in-the-blanks prompt that fits the real format.",
|
|
472
|
-
"Review src/template.ts and replace the starter sample_prompts with prompts that fit the new format.",
|
|
473
|
-
"Verify AI calls use ctx.providers.* so the platform resolves an active customer provider key before generation. Operation payload provider fields must be optional explicit overrides, not z.enum(...).default(...), and parser helpers must not silently fall back to a provider the customer may not have.",
|
|
474
|
-
"If DNA analysis was skipped or the source research changed, run analyze-viral-dna and analyze-visual-dna.",
|
|
475
|
-
"Update SKILL.md so the usage examples match the template's real operations and payloads.",
|
|
476
|
-
`Run validate-template against ${slugId} and launch at least one real local job before treating the template as ready.`,
|
|
477
|
-
],
|
|
478
|
-
};
|
|
56
|
+
throw new Error(`Unknown option before command: ${command}\n\n${HELP}`);
|
|
479
57
|
}
|
|
480
|
-
async function
|
|
58
|
+
async function runEditCommand(argv) {
|
|
481
59
|
const parsed = parseArgs({
|
|
482
60
|
args: argv,
|
|
483
61
|
allowPositionals: true,
|
|
484
62
|
options: {
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
"harness-dir": { type: "string" },
|
|
490
|
-
"slug-id": { type: "string" },
|
|
491
|
-
"template-id": { type: "string" },
|
|
492
|
-
"project-name": { type: "string" },
|
|
493
|
-
"github-repo": { type: "string" },
|
|
494
|
-
"site-name": { type: "string" },
|
|
495
|
-
"link-to-original": { type: "string" },
|
|
496
|
-
"dna-env-file": { type: "string", default: ".env" },
|
|
497
|
-
"dna-model": { type: "string" },
|
|
498
|
-
"skip-dna-analysis": { type: "boolean", default: false },
|
|
499
|
-
"skip-scaffold": { type: "boolean", default: false },
|
|
500
|
-
"skip-media-probes": { type: "boolean", default: false },
|
|
501
|
-
"skip-harness-snapshot": { type: "boolean", default: false },
|
|
502
|
-
branch: { type: "string", default: "production" },
|
|
503
|
-
force: { type: "boolean", default: false }
|
|
504
|
-
}
|
|
505
|
-
});
|
|
506
|
-
const positionalInput = parsed.positionals[0];
|
|
507
|
-
const positionalOutput = parsed.positionals[1];
|
|
508
|
-
const inputPathRaw = parsed.values["input-path"] ?? positionalInput;
|
|
509
|
-
const outputPathRaw = parsed.values["output-path"] ?? positionalOutput;
|
|
510
|
-
if (!inputPathRaw || !outputPathRaw) {
|
|
511
|
-
throw new Error("autocreate-template requires --input-path and --output-path, or positional args: autocreate-template <input_path> <output_path>.");
|
|
512
|
-
}
|
|
513
|
-
const root = process.cwd();
|
|
514
|
-
const inputPath = path.resolve(root, inputPathRaw);
|
|
515
|
-
const outputPath = path.resolve(root, outputPathRaw);
|
|
516
|
-
if (!existsSync(inputPath)) {
|
|
517
|
-
throw new Error(`Input path not found: ${inputPath}`);
|
|
518
|
-
}
|
|
519
|
-
const outputFolderName = path.basename(outputPath);
|
|
520
|
-
assertTemplateFolderNameHasPrefix(outputFolderName);
|
|
521
|
-
const slugId = parsed.values["slug-id"] ?? toSlugId(outputFolderName);
|
|
522
|
-
const runId = parsed.values["run-id"] ?? `${toProjectName(slugId)}-${formatUtcTimestamp(new Date())}`;
|
|
523
|
-
const runsDir = path.resolve(root, parsed.values["runs-dir"]);
|
|
524
|
-
const runDir = path.join(runsDir, runId);
|
|
525
|
-
const sourceDir = path.join(runDir, "source");
|
|
526
|
-
const previewDir = path.join(sourceDir, "preview");
|
|
527
|
-
const harnessDir = parsed.values["harness-dir"]
|
|
528
|
-
? path.resolve(root, parsed.values["harness-dir"])
|
|
529
|
-
: resolveAutoCreateHarnessDir();
|
|
530
|
-
createAutoCreateRunFolders(runDir);
|
|
531
|
-
stageAutocreateSource({
|
|
532
|
-
inputPath,
|
|
533
|
-
sourceDir,
|
|
534
|
-
previewDir,
|
|
535
|
-
linkToOriginal: parsed.values["link-to-original"]
|
|
536
|
-
});
|
|
537
|
-
const mediaProbe = parsed.values["skip-media-probes"]
|
|
538
|
-
? { skipped: true, reason: "skip-media-probes was set" }
|
|
539
|
-
: await prepareAutocreateMediaArtifacts({ runDir, previewDir });
|
|
540
|
-
const harnessSnapshot = parsed.values["skip-harness-snapshot"]
|
|
541
|
-
? null
|
|
542
|
-
: snapshotAutoCreateHarness({ harnessDir, runDir });
|
|
543
|
-
const sourceNotesPath = path.join(sourceDir, "source.md");
|
|
544
|
-
const scaffold = parsed.values["skip-scaffold"]
|
|
545
|
-
? null
|
|
546
|
-
: await generateTemplateFromStarter({
|
|
547
|
-
templateId: parsed.values["template-id"],
|
|
548
|
-
slugId,
|
|
549
|
-
templateDir: path.relative(root, outputPath),
|
|
550
|
-
projectName: parsed.values["project-name"],
|
|
551
|
-
githubRepo: parsed.values["github-repo"],
|
|
552
|
-
siteName: parsed.values["site-name"],
|
|
553
|
-
linkToOriginal: parsed.values["link-to-original"],
|
|
554
|
-
sourceNotesPath,
|
|
555
|
-
sourcePreviewDir: previewDir,
|
|
556
|
-
dnaEnvFile: parsed.values["dna-env-file"],
|
|
557
|
-
dnaModel: parsed.values["dna-model"],
|
|
558
|
-
skipDnaAnalysis: parsed.values["skip-dna-analysis"],
|
|
559
|
-
branch: parsed.values.branch,
|
|
560
|
-
force: parsed.values.force
|
|
561
|
-
});
|
|
562
|
-
const agentPromptPath = path.join(runDir, "agent_prompt.md");
|
|
563
|
-
const manifest = {
|
|
564
|
-
mode: "autocreate-template",
|
|
565
|
-
harness_entry: path.join(harnessDir, "AUTO_CREATE_TEMPLATES.md"),
|
|
566
|
-
harness_snapshot: harnessSnapshot,
|
|
567
|
-
developer_skill_context: harnessSnapshot?.developer_skill_path ?? resolveDeveloperSkillPath(),
|
|
568
|
-
input_path: inputPath,
|
|
569
|
-
output_path: outputPath,
|
|
570
|
-
run_id: runId,
|
|
571
|
-
run_dir: runDir,
|
|
572
|
-
source_dir: sourceDir,
|
|
573
|
-
source_notes_path: sourceNotesPath,
|
|
574
|
-
source_preview_dir: previewDir,
|
|
575
|
-
slug_id: slugId,
|
|
576
|
-
scaffold,
|
|
577
|
-
media_probe: mediaProbe,
|
|
578
|
-
required_artifacts: {
|
|
579
|
-
viral_steering_and_visual_preservation: path.join(runDir, "dna", "preservation_decision.json"),
|
|
580
|
-
production_graph: path.join(runDir, "extraction", "production_graph.json"),
|
|
581
|
-
template_plan: path.join(runDir, "plan", "template_plan.json")
|
|
582
|
-
}
|
|
583
|
-
};
|
|
584
|
-
writeFileSync(path.join(runDir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n", "utf8");
|
|
585
|
-
writeFileSync(agentPromptPath, renderAutocreateAgentPrompt(manifest), "utf8");
|
|
586
|
-
console.log(JSON.stringify({
|
|
587
|
-
...manifest,
|
|
588
|
-
agent_prompt_path: agentPromptPath,
|
|
589
|
-
next_command_for_agent: `Use ${manifest.harness_entry} as the harness. Analyze ${sourceDir} as the source format, use run artifacts in ${runDir}, generate the production graph and template plan, then scaffold and implement a runnable Vidfarm template to ${outputPath}.`
|
|
590
|
-
}, null, 2));
|
|
591
|
-
}
|
|
592
|
-
async function runRegisterSourceProdCommand(argv) {
|
|
593
|
-
const input = parseProdTemplateCommandArgs(argv, {
|
|
594
|
-
envFile: ".env.production",
|
|
595
|
-
baseUrl: "https://vidfarm.cc",
|
|
596
|
-
activate: false
|
|
597
|
-
});
|
|
598
|
-
const registration = await registerSourceViaApi(input);
|
|
599
|
-
console.log(JSON.stringify({
|
|
600
|
-
mode: "prod-register-source",
|
|
601
|
-
base_url: input.baseUrl,
|
|
602
|
-
source: registration.source,
|
|
603
|
-
registration_action: registration.registration_action,
|
|
604
|
-
sync: registration.sync ?? null
|
|
605
|
-
}, null, 2));
|
|
606
|
-
}
|
|
607
|
-
async function runRegisterSourceHostedCommand(argv) {
|
|
608
|
-
const input = parseProdTemplateCommandArgs(argv, {
|
|
609
|
-
envFile: ".env",
|
|
610
|
-
baseUrl: process.env.VIDFARM_BASE_URL ?? "https://vidfarm.cc",
|
|
611
|
-
activate: false
|
|
612
|
-
});
|
|
613
|
-
const registration = await registerSourceViaApi(input);
|
|
614
|
-
console.log(JSON.stringify({
|
|
615
|
-
mode: "hosted-register-source",
|
|
616
|
-
base_url: input.baseUrl,
|
|
617
|
-
source: registration.source,
|
|
618
|
-
registration_action: registration.registration_action,
|
|
619
|
-
sync: registration.sync ?? null,
|
|
620
|
-
next_step: "Platform admin reviews the source, imports a release, approves it, and activates it."
|
|
621
|
-
}, null, 2));
|
|
622
|
-
}
|
|
623
|
-
async function runPublishUpdateHostedCommand(argv) {
|
|
624
|
-
const input = parseProdTemplateCommandArgs(argv, {
|
|
625
|
-
envFile: ".env",
|
|
626
|
-
baseUrl: process.env.VIDFARM_BASE_URL ?? "https://vidfarm.cc",
|
|
627
|
-
activate: false
|
|
628
|
-
});
|
|
629
|
-
if (!input.commitSha) {
|
|
630
|
-
throw new Error("publish-update-hosted requires --commit-sha.");
|
|
631
|
-
}
|
|
632
|
-
const registration = await registerSourceViaApi(input);
|
|
633
|
-
const artifact = await packageTemplateArtifact(input.repoDir, input.commitSha);
|
|
634
|
-
const presign = await postProdTemplateApi(input, `/api/v1/templates/sources/${registration.source.id}/artifacts/presign`, {
|
|
635
|
-
method: "POST",
|
|
636
|
-
body: JSON.stringify({
|
|
637
|
-
commit_sha: input.commitSha
|
|
638
|
-
})
|
|
639
|
-
});
|
|
640
|
-
await uploadArtifactArchive(presign, artifact.buffer);
|
|
641
|
-
const payload = await postProdTemplateApi(input, `/api/v1/templates/sources/${registration.source.id}/publish`, {
|
|
642
|
-
method: "POST",
|
|
643
|
-
body: JSON.stringify({
|
|
644
|
-
commit_sha: input.commitSha,
|
|
645
|
-
artifact_sha256: artifact.sha256
|
|
646
|
-
})
|
|
647
|
-
});
|
|
648
|
-
console.log(JSON.stringify({
|
|
649
|
-
mode: "hosted-publish-update",
|
|
650
|
-
base_url: input.baseUrl,
|
|
651
|
-
source: payload.source,
|
|
652
|
-
release: payload.release,
|
|
653
|
-
artifact: {
|
|
654
|
-
artifact_key: presign.artifact_key,
|
|
655
|
-
artifact_manifest_key: presign.artifact_manifest_key,
|
|
656
|
-
sha256: artifact.sha256
|
|
657
|
-
},
|
|
658
|
-
publish_action: payload.publish_action,
|
|
659
|
-
developer_handoff: payload.developer_handoff ?? null
|
|
660
|
-
}, null, 2));
|
|
661
|
-
}
|
|
662
|
-
async function runImportSourceProdCommand(argv) {
|
|
663
|
-
const input = parseProdTemplateCommandArgs(argv, {
|
|
664
|
-
envFile: ".env.production",
|
|
665
|
-
baseUrl: "https://vidfarm.cc",
|
|
666
|
-
activate: true
|
|
667
|
-
});
|
|
668
|
-
const result = await importSourceIntoProd(input);
|
|
669
|
-
console.log(JSON.stringify({
|
|
670
|
-
mode: "prod-import-source",
|
|
671
|
-
base_url: input.baseUrl,
|
|
672
|
-
source: result.payload.source,
|
|
673
|
-
release: result.payload.release,
|
|
674
|
-
activated_release: result.payload.activated_release ?? null
|
|
675
|
-
}, null, 2));
|
|
676
|
-
}
|
|
677
|
-
async function runDeployTemplateCycleCommand(argv) {
|
|
678
|
-
const input = parseProdTemplateCommandArgs(argv, {
|
|
679
|
-
envFile: ".env.production",
|
|
680
|
-
baseUrl: "https://vidfarm.cc",
|
|
681
|
-
stackName: "VidfarmProdStack",
|
|
682
|
-
activate: true
|
|
683
|
-
});
|
|
684
|
-
if (!input.stackName) {
|
|
685
|
-
throw new Error("deploy-template-cycle requires --stack-name.");
|
|
686
|
-
}
|
|
687
|
-
const importResult = await importSourceIntoProd(input);
|
|
688
|
-
const currentImage = await getProdCurrentImage({
|
|
689
|
-
stackName: input.stackName,
|
|
690
|
-
envFile: input.envFile
|
|
691
|
-
});
|
|
692
|
-
await runLocalCommand("bash", [
|
|
693
|
-
"scripts/deploy-prod-inplace.sh",
|
|
694
|
-
"--env-file", input.envFile,
|
|
695
|
-
"--stack-name", input.stackName,
|
|
696
|
-
"--image-uri", currentImage.image,
|
|
697
|
-
"--skip-check",
|
|
698
|
-
"--skip-build",
|
|
699
|
-
"--tag-suffix", `template-${input.slugId}`
|
|
700
|
-
]);
|
|
701
|
-
console.log(JSON.stringify({
|
|
702
|
-
mode: "deploy-template-cycle",
|
|
703
|
-
stack_name: input.stackName,
|
|
704
|
-
release: importResult.payload.activated_release ?? importResult.payload.release,
|
|
705
|
-
restart_image: currentImage.image
|
|
706
|
-
}, null, 2));
|
|
707
|
-
}
|
|
708
|
-
async function runCopyReferenceTemplateCommand(argv) {
|
|
709
|
-
const parsed = parseArgs({
|
|
710
|
-
args: argv,
|
|
711
|
-
options: {
|
|
712
|
-
"template-dir": { type: "string" },
|
|
713
|
-
force: { type: "boolean", default: false }
|
|
714
|
-
}
|
|
715
|
-
});
|
|
716
|
-
const templateDir = parsed.values["template-dir"];
|
|
717
|
-
if (!templateDir) {
|
|
718
|
-
throw new Error("copy-reference-template requires --template-dir.");
|
|
719
|
-
}
|
|
720
|
-
const root = process.cwd();
|
|
721
|
-
const starterDir = resolveStarterTemplateDir();
|
|
722
|
-
const destinationDir = path.resolve(root, templateDir);
|
|
723
|
-
const destinationFolderName = path.basename(destinationDir);
|
|
724
|
-
assertTemplateFolderNameHasPrefix(destinationFolderName);
|
|
725
|
-
if (!existsSync(starterDir)) {
|
|
726
|
-
throw new Error(`Starter template not found: ${starterDir}`);
|
|
727
|
-
}
|
|
728
|
-
if (existsSync(destinationDir)) {
|
|
729
|
-
if (!parsed.values.force) {
|
|
730
|
-
throw new Error(`Destination already exists: ${destinationDir}`);
|
|
731
|
-
}
|
|
732
|
-
rmSync(destinationDir, { recursive: true, force: true });
|
|
733
|
-
}
|
|
734
|
-
cpSync(starterDir, destinationDir, {
|
|
735
|
-
recursive: true,
|
|
736
|
-
filter: (entry) => {
|
|
737
|
-
const name = path.basename(entry);
|
|
738
|
-
return ![".git", "node_modules", "dist"].includes(name);
|
|
739
|
-
}
|
|
740
|
-
});
|
|
741
|
-
console.log(JSON.stringify({
|
|
742
|
-
copied_from: starterDir,
|
|
743
|
-
template_dir: destinationDir,
|
|
744
|
-
template_id: "4c7a7e1a-7f35-4f30-9f86-9c8a63c7f2db",
|
|
745
|
-
slug_id: "template_0000",
|
|
746
|
-
rewritten: false,
|
|
747
|
-
required_template_contract: {
|
|
748
|
-
template_fields: [
|
|
749
|
-
"id",
|
|
750
|
-
"slugId",
|
|
751
|
-
"version",
|
|
752
|
-
"about.title",
|
|
753
|
-
"about.description",
|
|
754
|
-
"about.difficulty",
|
|
755
|
-
"about.viral_dna",
|
|
756
|
-
"about.visual_dna",
|
|
757
|
-
"about.preview_media",
|
|
758
|
-
"about.link_to_original",
|
|
759
|
-
"about.links_to_inspiration",
|
|
760
|
-
"about.sound",
|
|
761
|
-
"about.sound.sound_title",
|
|
762
|
-
"about.sound.sound_url",
|
|
763
|
-
"about.proposal_generator",
|
|
764
|
-
"about.skeleton_prompt",
|
|
765
|
-
"about.sample_prompts",
|
|
766
|
-
"examples",
|
|
767
|
-
"configSchema",
|
|
768
|
-
],
|
|
769
|
-
operation_fields: [
|
|
770
|
-
"operations.<name>.workflow",
|
|
771
|
-
"operations.<name>.smokeTestPayload",
|
|
772
|
-
"jobs.<workflow>",
|
|
773
|
-
],
|
|
774
|
-
files: [
|
|
775
|
-
"src/template.ts",
|
|
776
|
-
"SKILL.md",
|
|
777
|
-
],
|
|
778
|
-
},
|
|
779
|
-
next_steps: [
|
|
780
|
-
"Treat src/template.ts as the canonical entrypoint and review the required about metadata before copying patterns into a new template.",
|
|
781
|
-
"Use the starter proposal_generator, skeleton_prompt, and sample_prompts as placeholders only; rewrite them for the real format you are building.",
|
|
782
|
-
"Keep the template-local SKILL.md aligned with the operations and payloads in code.",
|
|
783
|
-
],
|
|
784
|
-
}, null, 2));
|
|
785
|
-
}
|
|
786
|
-
async function runAnalyzeTemplateDnaCommand(mode, argv) {
|
|
787
|
-
const parsed = parseArgs({
|
|
788
|
-
args: argv,
|
|
789
|
-
options: {
|
|
790
|
-
"template-dir": { type: "string", default: "." },
|
|
791
|
-
"env-file": { type: "string", default: ".env" },
|
|
792
|
-
model: { type: "string" },
|
|
793
|
-
"notes-path": { type: "string" },
|
|
794
|
-
"preview-dir": { type: "string" },
|
|
795
|
-
"output-path": { type: "string" },
|
|
796
|
-
"link-to-original": { type: "string" },
|
|
797
|
-
"sync-template-module": { type: "boolean", default: true }
|
|
798
|
-
}
|
|
799
|
-
});
|
|
800
|
-
const result = await analyzeTemplateDna({
|
|
801
|
-
mode,
|
|
802
|
-
templateDir: parsed.values["template-dir"],
|
|
803
|
-
envFile: parsed.values["env-file"],
|
|
804
|
-
model: parsed.values.model,
|
|
805
|
-
notesPath: parsed.values["notes-path"],
|
|
806
|
-
previewDir: parsed.values["preview-dir"],
|
|
807
|
-
outputPath: parsed.values["output-path"],
|
|
808
|
-
linkToOriginal: parsed.values["link-to-original"],
|
|
809
|
-
syncTemplateModule: parsed.values["sync-template-module"]
|
|
810
|
-
});
|
|
811
|
-
console.log(JSON.stringify(result, null, 2));
|
|
812
|
-
}
|
|
813
|
-
async function runPresignPreviewMediaCommand(argv) {
|
|
814
|
-
const parsed = parseArgs({
|
|
815
|
-
args: argv,
|
|
816
|
-
options: {
|
|
817
|
-
file: { type: "string" },
|
|
818
|
-
"file-name": { type: "string" },
|
|
819
|
-
"content-type": { type: "string" },
|
|
820
|
-
directory: { type: "string" },
|
|
821
|
-
"base-url": { type: "string" },
|
|
63
|
+
port: { type: "string", default: String(DEFAULT_PORT) },
|
|
64
|
+
dir: { type: "string" },
|
|
65
|
+
host: { type: "string", default: DEFAULT_HOST },
|
|
66
|
+
fork: { type: "string" },
|
|
822
67
|
"api-key": { type: "string" },
|
|
823
|
-
|
|
68
|
+
share: { type: "string" },
|
|
69
|
+
refetch: { type: "boolean", default: false }
|
|
824
70
|
}
|
|
825
71
|
});
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
throw new Error(`Preview media file not found: ${filePath}`);
|
|
72
|
+
const templateId = parsed.positionals[0];
|
|
73
|
+
if (!templateId) {
|
|
74
|
+
throw new Error(`edit requires a template id.\n\n${HELP}`);
|
|
830
75
|
}
|
|
831
|
-
const
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
throw new Error("presign-preview-media requires --file or --file-name.");
|
|
76
|
+
const port = Number(parsed.values.port);
|
|
77
|
+
if (!Number.isFinite(port) || port <= 0) {
|
|
78
|
+
throw new Error(`Invalid --port: ${parsed.values.port}`);
|
|
835
79
|
}
|
|
836
|
-
const
|
|
837
|
-
?? inferUploadContentType(fileName);
|
|
838
|
-
const baseUrl = normalizeBaseUrl(parsed.values["base-url"] ?? process.env.VIDFARM_BASE_URL ?? "https://vidfarm.cc");
|
|
80
|
+
const host = trimTrailingSlash(parsed.values.host);
|
|
839
81
|
const apiKey = parsed.values["api-key"] ?? process.env.VIDFARM_API_KEY;
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
}
|
|
843
|
-
const
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
});
|
|
855
|
-
const
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
82
|
+
const shareToken = parsed.values.share;
|
|
83
|
+
const rootDir = path.resolve(process.cwd(), parsed.values.dir ?? path.join(".vidfarm", templateId));
|
|
84
|
+
mkdirSync(rootDir, { recursive: true });
|
|
85
|
+
const forkId = parsed.values.fork ?? await resolveForkId({ host, templateId, apiKey, shareToken });
|
|
86
|
+
if (!forkId) {
|
|
87
|
+
throw new Error(`Could not resolve a fork id for template ${templateId}. Pass --fork <id>, or open ${host}/editor/${templateId} once so a fork is created.`);
|
|
88
|
+
}
|
|
89
|
+
await fetchCompositionFiles({
|
|
90
|
+
host,
|
|
91
|
+
forkId,
|
|
92
|
+
dir: rootDir,
|
|
93
|
+
apiKey,
|
|
94
|
+
shareToken,
|
|
95
|
+
refetch: parsed.values.refetch
|
|
96
|
+
});
|
|
97
|
+
const devSource = `http://localhost:${port}`;
|
|
98
|
+
const editorUrl = `${host}/editor/${encodeURIComponent(templateId)}?fork=${encodeURIComponent(forkId)}&dev=${encodeURIComponent(devSource)}`;
|
|
99
|
+
printEditorBanner({ templateId, forkId, dir: rootDir, port, editorUrl });
|
|
100
|
+
const { runDevServeCommand } = await import("./dev-serve.js");
|
|
101
|
+
await runDevServeCommand(["--dir", rootDir, "--port", String(port)]);
|
|
102
|
+
}
|
|
103
|
+
async function resolveForkId(input) {
|
|
104
|
+
// /editor/<template_id> issues a 302 → /editor/<template_id>?fork=<id>
|
|
105
|
+
// whenever the caller (or the template's default fork) has one. Follow the
|
|
106
|
+
// redirect once by hand so we can read the fork id off the query string.
|
|
107
|
+
const url = `${input.host}/editor/${encodeURIComponent(input.templateId)}`;
|
|
108
|
+
const res = await fetch(url, {
|
|
109
|
+
method: "GET",
|
|
110
|
+
redirect: "manual",
|
|
111
|
+
headers: buildAuthHeaders(input)
|
|
112
|
+
});
|
|
113
|
+
if (res.status >= 300 && res.status < 400) {
|
|
114
|
+
const location = res.headers.get("location");
|
|
115
|
+
if (location) {
|
|
116
|
+
try {
|
|
117
|
+
const target = new URL(location, input.host);
|
|
118
|
+
const fork = target.searchParams.get("fork");
|
|
119
|
+
if (fork)
|
|
120
|
+
return fork;
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// fall through
|
|
124
|
+
}
|
|
870
125
|
}
|
|
871
|
-
uploadResult = {
|
|
872
|
-
uploaded: true,
|
|
873
|
-
size_bytes: statSync(filePath).size,
|
|
874
|
-
method: payload.upload?.method ?? "PUT",
|
|
875
|
-
status: uploadResponse.status
|
|
876
|
-
};
|
|
877
126
|
}
|
|
878
|
-
|
|
879
|
-
base_url: baseUrl,
|
|
880
|
-
file_path: filePath,
|
|
881
|
-
file_name: payload.file_name,
|
|
882
|
-
content_type: payload.content_type,
|
|
883
|
-
storage_key: payload.storage_key,
|
|
884
|
-
preview_media_url: payload.preview_media_url,
|
|
885
|
-
public_url: payload.public_url ?? payload.preview_media_url,
|
|
886
|
-
upload_result: uploadResult,
|
|
887
|
-
upload: payload.upload,
|
|
888
|
-
curl_upload_example: [
|
|
889
|
-
`curl -X ${payload.upload.method} '${payload.upload.url}' \\`,
|
|
890
|
-
` -H 'content-type: ${payload.content_type}' \\`,
|
|
891
|
-
" --upload-file <local-preview-file>"
|
|
892
|
-
].join("\n")
|
|
893
|
-
}, null, 2));
|
|
894
|
-
}
|
|
895
|
-
async function runSessionCommand(argv) {
|
|
896
|
-
const parsed = parseArgs({
|
|
897
|
-
args: argv,
|
|
898
|
-
options: {
|
|
899
|
-
"data-dir": { type: "string", default: ".vidfarm/local" }
|
|
900
|
-
}
|
|
901
|
-
});
|
|
902
|
-
const sessionPath = path.resolve(process.cwd(), parsed.values["data-dir"], "session.json");
|
|
903
|
-
const session = JSON.parse(readFileSync(sessionPath, "utf8"));
|
|
904
|
-
console.log(JSON.stringify({
|
|
905
|
-
base_url: session.baseUrl,
|
|
906
|
-
email: session.email,
|
|
907
|
-
starter_style_options: {
|
|
908
|
-
caption_standard_version: CAPTION_STANDARD_VERSION,
|
|
909
|
-
fonts: STARTER_TEMPLATE_FONT_OPTIONS,
|
|
910
|
-
text_background_colors: STARTER_TEMPLATE_TEXT_BACKGROUND_COLOR_OPTIONS,
|
|
911
|
-
text_background_rendering: TIKTOK_TEXT_BACKGROUND_RENDERING,
|
|
912
|
-
caption_shadows: TIKTOK_CAPTION_SHADOWS,
|
|
913
|
-
caption_rendering_rules: TIKTOK_CAPTION_RENDERING_RULES,
|
|
914
|
-
presets: TIKTOK_CAPTION_STYLE_PRESETS,
|
|
915
|
-
package_dependencies: [
|
|
916
|
-
"@fontsource/tiktok-sans",
|
|
917
|
-
"@fontsource/montserrat",
|
|
918
|
-
"@fontsource/source-code-pro",
|
|
919
|
-
"@fontsource/libre-baskerville"
|
|
920
|
-
],
|
|
921
|
-
standard_file: "src/lib/template-style-options.ts",
|
|
922
|
-
starter_template_file: "demos/vidfarm_template_0000/src/style-options.ts"
|
|
923
|
-
},
|
|
924
|
-
headers: {
|
|
925
|
-
"vidfarm-api-key": session.apiKey,
|
|
926
|
-
"content-type": "application/json"
|
|
927
|
-
},
|
|
928
|
-
curl_example: [
|
|
929
|
-
`curl -X POST ${session.baseUrl}/api/v1/primitives/image/operations/run \\`,
|
|
930
|
-
` -H 'vidfarm-api-key: ${session.apiKey}' \\`,
|
|
931
|
-
" -H 'content-type: application/json' \\",
|
|
932
|
-
" -d '{\"tracer\":\"local-test\",\"payload\":{\"prompt\":\"a cinematic founder at a desk\",\"width\":1080,\"height\":1920}}'"
|
|
933
|
-
].join("\n")
|
|
934
|
-
}, null, 2));
|
|
127
|
+
return null;
|
|
935
128
|
}
|
|
936
|
-
async function
|
|
937
|
-
const
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
];
|
|
943
|
-
const existing = await input.providerKeys.listProviderKeys(input.customerId);
|
|
944
|
-
for (const entry of entries) {
|
|
945
|
-
const secret = entry.secret || (input.allowMockPlaceholders ? entry.placeholder : "");
|
|
946
|
-
if (!secret) {
|
|
129
|
+
async function fetchCompositionFiles(input) {
|
|
130
|
+
const files = ["composition.html", "composition.json", "manifest.json"];
|
|
131
|
+
for (const filename of files) {
|
|
132
|
+
const target = path.join(input.dir, filename);
|
|
133
|
+
if (existsSync(target) && !input.refetch) {
|
|
134
|
+
console.log(`[vidfarm] keep local ${filename} (use --refetch to overwrite)`);
|
|
947
135
|
continue;
|
|
948
136
|
}
|
|
949
|
-
const
|
|
950
|
-
|
|
137
|
+
const url = `${input.host}/api/v1/compositions/${encodeURIComponent(input.forkId)}/${filename}`;
|
|
138
|
+
const res = await fetch(url, { headers: buildAuthHeaders(input) });
|
|
139
|
+
if (!res.ok) {
|
|
140
|
+
console.warn(`[vidfarm] skip ${filename}: ${res.status} ${res.statusText} (${url})`);
|
|
951
141
|
continue;
|
|
952
142
|
}
|
|
953
|
-
await
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
provider: entry.provider,
|
|
957
|
-
label: "Local CLI env key",
|
|
958
|
-
encryptedSecret: input.encryptString(secret, input.encryptionSecret)
|
|
959
|
-
});
|
|
143
|
+
const body = Buffer.from(await res.arrayBuffer());
|
|
144
|
+
writeFileSync(target, body);
|
|
145
|
+
console.log(`[vidfarm] fetched ${filename} (${body.length} bytes)`);
|
|
960
146
|
}
|
|
961
147
|
}
|
|
962
|
-
function
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
148
|
+
function buildAuthHeaders(input) {
|
|
149
|
+
const headers = { accept: "text/html,application/json" };
|
|
150
|
+
if (input.apiKey)
|
|
151
|
+
headers.authorization = `Bearer ${input.apiKey}`;
|
|
152
|
+
if (input.shareToken)
|
|
153
|
+
headers["x-vidfarm-share-token"] = input.shareToken;
|
|
154
|
+
return headers;
|
|
155
|
+
}
|
|
156
|
+
function printEditorBanner(input) {
|
|
157
|
+
const bold = "\x1b[1m";
|
|
158
|
+
const green = "\x1b[32m";
|
|
159
|
+
const cyan = "\x1b[36m";
|
|
160
|
+
const dim = "\x1b[2m";
|
|
161
|
+
const reset = "\x1b[0m";
|
|
162
|
+
const line = `${dim}${"─".repeat(74)}${reset}`;
|
|
973
163
|
console.log("");
|
|
974
|
-
console.log(
|
|
975
|
-
console.log(
|
|
976
|
-
console.log(
|
|
164
|
+
console.log(line);
|
|
165
|
+
console.log(`${bold}${green} Vidfarm local editor${reset}`);
|
|
166
|
+
console.log(line);
|
|
167
|
+
console.log(` template ${input.templateId}`);
|
|
168
|
+
console.log(` fork ${input.forkId}`);
|
|
169
|
+
console.log(` local dir ${input.dir}`);
|
|
170
|
+
console.log(` dev-serve http://localhost:${input.port}`);
|
|
977
171
|
console.log("");
|
|
978
|
-
console.log(
|
|
979
|
-
console.log(
|
|
172
|
+
console.log(` ${bold}Open in browser:${reset}`);
|
|
173
|
+
console.log(` ${cyan}${input.editorUrl}${reset}`);
|
|
174
|
+
console.log(line);
|
|
980
175
|
console.log("");
|
|
981
176
|
}
|
|
982
|
-
function
|
|
983
|
-
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
984
|
-
const target = path.join(root, entry.name);
|
|
985
|
-
if (entry.name === ".git" || entry.name === "node_modules" || entry.name === "dist") {
|
|
986
|
-
continue;
|
|
987
|
-
}
|
|
988
|
-
if (entry.isDirectory()) {
|
|
989
|
-
rewriteTemplateStarter(target, replacements);
|
|
990
|
-
continue;
|
|
991
|
-
}
|
|
992
|
-
const original = readFileSync(target, "utf8");
|
|
993
|
-
const next = replacements.reduce((value, [from, to]) => value.split(from).join(to), original);
|
|
994
|
-
if (next !== original) {
|
|
995
|
-
writeFileSync(target, next);
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
function toProjectName(folderName) {
|
|
1000
|
-
return folderName
|
|
1001
|
-
.replace(/[^a-zA-Z0-9_-]+/g, "_")
|
|
1002
|
-
.replace(/-+/g, "_")
|
|
1003
|
-
.replace(/^_+|_+$/g, "")
|
|
1004
|
-
.toLowerCase();
|
|
1005
|
-
}
|
|
1006
|
-
function resolveStarterTemplateDir() {
|
|
1007
|
-
const candidates = [
|
|
1008
|
-
path.resolve(import.meta.dirname, "..", "templates", "vidfarm_template_0000"),
|
|
1009
|
-
path.resolve(import.meta.dirname, "..", "..", "templates", "vidfarm_template_0000"),
|
|
1010
|
-
path.resolve(import.meta.dirname, "..", "demos", "vidfarm_template_0000"),
|
|
1011
|
-
path.resolve(import.meta.dirname, "..", "..", "demos", "vidfarm_template_0000")
|
|
1012
|
-
];
|
|
1013
|
-
for (const candidate of candidates) {
|
|
1014
|
-
if (existsSync(candidate)) {
|
|
1015
|
-
return candidate;
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
return candidates[0];
|
|
1019
|
-
}
|
|
1020
|
-
function resolveAutoCreateHarnessDir() {
|
|
1021
|
-
const candidates = [
|
|
1022
|
-
path.resolve(process.cwd(), "auto-create-templates"),
|
|
1023
|
-
path.resolve(import.meta.dirname, "..", "auto-create-templates"),
|
|
1024
|
-
path.resolve(import.meta.dirname, "..", "..", "auto-create-templates")
|
|
1025
|
-
];
|
|
1026
|
-
for (const candidate of candidates) {
|
|
1027
|
-
if (existsSync(path.join(candidate, "AUTO_CREATE_TEMPLATES.md"))) {
|
|
1028
|
-
return candidate;
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
return candidates[0];
|
|
1032
|
-
}
|
|
1033
|
-
function resolveDeveloperSkillPath() {
|
|
1034
|
-
const candidates = [
|
|
1035
|
-
path.resolve(process.cwd(), "SKILL.platform.md"),
|
|
1036
|
-
path.resolve(import.meta.dirname, "..", "SKILL.platform.md"),
|
|
1037
|
-
path.resolve(import.meta.dirname, "..", "..", "SKILL.platform.md")
|
|
1038
|
-
];
|
|
1039
|
-
for (const candidate of candidates) {
|
|
1040
|
-
if (existsSync(candidate)) {
|
|
1041
|
-
return candidate;
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
1044
|
-
return candidates[0];
|
|
1045
|
-
}
|
|
1046
|
-
function createAutoCreateRunFolders(runDir) {
|
|
1047
|
-
for (const name of ["source", "frames", "audio", "transcripts", "ocr", "motion", "dna", "extraction", "plan"]) {
|
|
1048
|
-
mkdirSync(path.join(runDir, name), { recursive: true });
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1051
|
-
function stageAutocreateSource(input) {
|
|
1052
|
-
mkdirSync(input.sourceDir, { recursive: true });
|
|
1053
|
-
mkdirSync(input.previewDir, { recursive: true });
|
|
1054
|
-
const stats = statSync(input.inputPath);
|
|
1055
|
-
if (stats.isDirectory()) {
|
|
1056
|
-
const inputHadPreviewDir = existsSync(path.join(input.inputPath, "preview"));
|
|
1057
|
-
copyDirectoryContentsShallow(input.inputPath, input.sourceDir);
|
|
1058
|
-
const existingPreviewDir = path.join(input.sourceDir, "preview");
|
|
1059
|
-
if (!inputHadPreviewDir) {
|
|
1060
|
-
mkdirSync(existingPreviewDir, { recursive: true });
|
|
1061
|
-
for (const mediaFile of listMediaFiles(input.sourceDir)) {
|
|
1062
|
-
cpSync(mediaFile, path.join(existingPreviewDir, path.basename(mediaFile)));
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
else {
|
|
1067
|
-
cpSync(input.inputPath, path.join(input.previewDir, path.basename(input.inputPath)));
|
|
1068
|
-
}
|
|
1069
|
-
const notesPath = path.join(input.sourceDir, "source.md");
|
|
1070
|
-
if (!existsSync(notesPath)) {
|
|
1071
|
-
writeFileSync(notesPath, [
|
|
1072
|
-
"# Source Notes",
|
|
1073
|
-
"",
|
|
1074
|
-
`- input path: ${input.inputPath}`,
|
|
1075
|
-
`- original format URL: ${input.linkToOriginal ?? ""}`,
|
|
1076
|
-
"- creator/account:",
|
|
1077
|
-
"- product/category being adapted:",
|
|
1078
|
-
"- what appears repeatable:",
|
|
1079
|
-
"- constraints:",
|
|
1080
|
-
""
|
|
1081
|
-
].join("\n"), "utf8");
|
|
1082
|
-
}
|
|
1083
|
-
}
|
|
1084
|
-
async function prepareAutocreateMediaArtifacts(input) {
|
|
1085
|
-
const videoFile = listMediaFiles(input.previewDir).find((filePath) => isVideoFile(filePath));
|
|
1086
|
-
if (!videoFile) {
|
|
1087
|
-
return {
|
|
1088
|
-
skipped: true,
|
|
1089
|
-
reason: "No video file found in source preview media.",
|
|
1090
|
-
preview_files: listMediaFiles(input.previewDir)
|
|
1091
|
-
};
|
|
1092
|
-
}
|
|
1093
|
-
const ffprobePath = path.join(input.runDir, "source", "ffprobe.json");
|
|
1094
|
-
const contactSheetPath = path.join(input.runDir, "frames", "contact-sheet.jpg");
|
|
1095
|
-
const audioPath = path.join(input.runDir, "audio", "source.wav");
|
|
1096
|
-
const steps = [];
|
|
1097
|
-
steps.push(await runOptionalLocalCommand("ffprobe", [
|
|
1098
|
-
"-v", "error",
|
|
1099
|
-
"-show_entries", "format=duration:stream=codec_type,width,height,avg_frame_rate",
|
|
1100
|
-
"-of", "json",
|
|
1101
|
-
videoFile
|
|
1102
|
-
], ffprobePath));
|
|
1103
|
-
steps.push(await runOptionalLocalCommand("ffmpeg", [
|
|
1104
|
-
"-y",
|
|
1105
|
-
"-i", videoFile,
|
|
1106
|
-
"-vf", "fps=1/2,scale=240:-1,tile=3x4",
|
|
1107
|
-
"-frames:v", "1",
|
|
1108
|
-
contactSheetPath
|
|
1109
|
-
]));
|
|
1110
|
-
steps.push(await runOptionalLocalCommand("ffmpeg", [
|
|
1111
|
-
"-y",
|
|
1112
|
-
"-i", videoFile,
|
|
1113
|
-
"-vn",
|
|
1114
|
-
"-ac", "1",
|
|
1115
|
-
"-ar", "16000",
|
|
1116
|
-
audioPath
|
|
1117
|
-
]));
|
|
1118
|
-
const result = {
|
|
1119
|
-
skipped: false,
|
|
1120
|
-
video_file: videoFile,
|
|
1121
|
-
ffprobe_path: existsSync(ffprobePath) ? ffprobePath : null,
|
|
1122
|
-
contact_sheet_path: existsSync(contactSheetPath) ? contactSheetPath : null,
|
|
1123
|
-
audio_path: existsSync(audioPath) ? audioPath : null,
|
|
1124
|
-
steps
|
|
1125
|
-
};
|
|
1126
|
-
writeFileSync(path.join(input.runDir, "source", "media_artifacts.json"), JSON.stringify(result, null, 2) + "\n", "utf8");
|
|
1127
|
-
return result;
|
|
1128
|
-
}
|
|
1129
|
-
function snapshotAutoCreateHarness(input) {
|
|
1130
|
-
if (!existsSync(path.join(input.harnessDir, "AUTO_CREATE_TEMPLATES.md"))) {
|
|
1131
|
-
throw new Error(`Auto-create harness not found: ${input.harnessDir}`);
|
|
1132
|
-
}
|
|
1133
|
-
const destinationDir = path.join(input.runDir, "harness");
|
|
1134
|
-
mkdirSync(destinationDir, { recursive: true });
|
|
1135
|
-
for (const fileName of [
|
|
1136
|
-
"AUTO_CREATE_TEMPLATES.md",
|
|
1137
|
-
"runbook.md",
|
|
1138
|
-
"extractor-system-prompt.md",
|
|
1139
|
-
"production-graph.schema.json",
|
|
1140
|
-
"template-planner-prompt.md",
|
|
1141
|
-
"template-plan.schema.json"
|
|
1142
|
-
]) {
|
|
1143
|
-
const source = path.join(input.harnessDir, fileName);
|
|
1144
|
-
if (existsSync(source)) {
|
|
1145
|
-
cpSync(source, path.join(destinationDir, fileName));
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
const developerSkillPath = resolveDeveloperSkillPath();
|
|
1149
|
-
const developerSkillDestination = path.join(destinationDir, "SKILL.platform.md");
|
|
1150
|
-
if (existsSync(developerSkillPath)) {
|
|
1151
|
-
cpSync(developerSkillPath, developerSkillDestination);
|
|
1152
|
-
}
|
|
1153
|
-
return {
|
|
1154
|
-
harness_dir: destinationDir,
|
|
1155
|
-
entry: path.join(destinationDir, "AUTO_CREATE_TEMPLATES.md"),
|
|
1156
|
-
developer_skill_path: existsSync(developerSkillDestination)
|
|
1157
|
-
? developerSkillDestination
|
|
1158
|
-
: developerSkillPath
|
|
1159
|
-
};
|
|
1160
|
-
}
|
|
1161
|
-
function renderAutocreateAgentPrompt(manifest) {
|
|
1162
|
-
return [
|
|
1163
|
-
"# Vidfarm Autocreate Template Handoff",
|
|
1164
|
-
"",
|
|
1165
|
-
"Cacheable context, read first and keep before run-specific artifacts in your working context:",
|
|
1166
|
-
"",
|
|
1167
|
-
`1. Developer standard: ${manifest.developer_skill_context}`,
|
|
1168
|
-
`2. Auto-create harness: ${manifest.harness_snapshot?.entry ?? manifest.harness_entry}`,
|
|
1169
|
-
manifest.harness_snapshot?.harness_dir
|
|
1170
|
-
? `3. Harness snapshot directory: ${manifest.harness_snapshot.harness_dir}`
|
|
1171
|
-
: "3. Harness snapshot directory: not created; use the live harness path above.",
|
|
1172
|
-
"",
|
|
1173
|
-
"These files are stable across runs and are intended to be prompt-cache friendly. Read them before loading source media, analyzer outputs, or run-specific JSON.",
|
|
1174
|
-
"",
|
|
1175
|
-
`Use ${manifest.harness_snapshot?.entry ?? manifest.harness_entry} as the harness.`,
|
|
1176
|
-
`Use ${manifest.developer_skill_context} as the developer template standard context.`,
|
|
1177
|
-
`Analyze ${manifest.source_dir} as the source format.`,
|
|
1178
|
-
`Use the run artifacts in ${manifest.run_dir}.`,
|
|
1179
|
-
`Output the runnable Vidfarm template to ${manifest.output_path}.`,
|
|
1180
|
-
"",
|
|
1181
|
-
"Required work:",
|
|
1182
|
-
"",
|
|
1183
|
-
"1. Inspect source notes and preview media.",
|
|
1184
|
-
"2. Use or create analyzer artifacts in the run folder.",
|
|
1185
|
-
"3. Run or consume viral and visual DNA analysis.",
|
|
1186
|
-
"4. Create `dna/preservation_decision.json`.",
|
|
1187
|
-
"5. Create `extraction/production_graph.json` matching the production graph schema.",
|
|
1188
|
-
"6. Create `plan/template_plan.json` matching the template plan schema.",
|
|
1189
|
-
"7. Implement the template operations, jobs, metadata, prompts, storage outputs, and template-local `SKILL.md`.",
|
|
1190
|
-
"8. Validate storage boundaries: use only `ctx.storage` with job-relative keys; do not generate direct AWS/S3 clients, generic `AWS_*` credential handling, `REMOTION_AWS_*` handling, or absolute keys like `templates/${templateId}/users/${ctx.customer.id}/...`.",
|
|
1191
|
-
"9. Run `vidfarm-devcli validate-template` and at least one real local job before calling it done.",
|
|
1192
|
-
"",
|
|
1193
|
-
"Manifest:",
|
|
1194
|
-
"",
|
|
1195
|
-
"```json",
|
|
1196
|
-
JSON.stringify(manifest, null, 2),
|
|
1197
|
-
"```",
|
|
1198
|
-
""
|
|
1199
|
-
].join("\n");
|
|
1200
|
-
}
|
|
1201
|
-
async function runOptionalLocalCommand(command, args, outputPath) {
|
|
1202
|
-
try {
|
|
1203
|
-
const result = await execFileAsync(command, args, {
|
|
1204
|
-
maxBuffer: 20 * 1024 * 1024
|
|
1205
|
-
});
|
|
1206
|
-
if (outputPath) {
|
|
1207
|
-
writeFileSync(outputPath, result.stdout, "utf8");
|
|
1208
|
-
}
|
|
1209
|
-
return {
|
|
1210
|
-
command,
|
|
1211
|
-
ok: true,
|
|
1212
|
-
output_path: outputPath ?? null,
|
|
1213
|
-
stderr: result.stderr.trim() || null
|
|
1214
|
-
};
|
|
1215
|
-
}
|
|
1216
|
-
catch (error) {
|
|
1217
|
-
return {
|
|
1218
|
-
command,
|
|
1219
|
-
ok: false,
|
|
1220
|
-
output_path: outputPath ?? null,
|
|
1221
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1222
|
-
};
|
|
1223
|
-
}
|
|
1224
|
-
}
|
|
1225
|
-
function copyDirectoryContentsShallow(sourceDir, destinationDir) {
|
|
1226
|
-
mkdirSync(destinationDir, { recursive: true });
|
|
1227
|
-
for (const entry of readdirSync(sourceDir, { withFileTypes: true })) {
|
|
1228
|
-
if (!entry.name.startsWith(".")) {
|
|
1229
|
-
cpSync(path.join(sourceDir, entry.name), path.join(destinationDir, entry.name), { recursive: true });
|
|
1230
|
-
}
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
function listMediaFiles(dir) {
|
|
1234
|
-
if (!existsSync(dir)) {
|
|
1235
|
-
return [];
|
|
1236
|
-
}
|
|
1237
|
-
const files = [];
|
|
1238
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
1239
|
-
const filePath = path.join(dir, entry.name);
|
|
1240
|
-
if (entry.name.startsWith(".")) {
|
|
1241
|
-
continue;
|
|
1242
|
-
}
|
|
1243
|
-
if (entry.isDirectory()) {
|
|
1244
|
-
files.push(...listMediaFiles(filePath));
|
|
1245
|
-
continue;
|
|
1246
|
-
}
|
|
1247
|
-
if (entry.isFile() && isMediaFile(filePath)) {
|
|
1248
|
-
files.push(filePath);
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
|
-
return files.sort((a, b) => a.localeCompare(b));
|
|
1252
|
-
}
|
|
1253
|
-
function isMediaFile(filePath) {
|
|
1254
|
-
return [".png", ".jpg", ".jpeg", ".webp", ".mp4", ".mov", ".webm", ".m4v"].includes(path.extname(filePath).toLowerCase());
|
|
1255
|
-
}
|
|
1256
|
-
function isVideoFile(filePath) {
|
|
1257
|
-
return [".mp4", ".mov", ".webm", ".m4v"].includes(path.extname(filePath).toLowerCase());
|
|
1258
|
-
}
|
|
1259
|
-
function toSlugId(folderName) {
|
|
1260
|
-
return toProjectName(folderName).replace(/^vidfarm_template_/, "template_");
|
|
1261
|
-
}
|
|
1262
|
-
function formatUtcTimestamp(date) {
|
|
1263
|
-
return date.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
|
|
1264
|
-
}
|
|
1265
|
-
function parseProdTemplateCommandArgs(argv, defaults) {
|
|
1266
|
-
const parsed = parseArgs({
|
|
1267
|
-
args: argv,
|
|
1268
|
-
options: {
|
|
1269
|
-
"template-id": { type: "string" },
|
|
1270
|
-
"slug-id": { type: "string" },
|
|
1271
|
-
"repo-url": { type: "string" },
|
|
1272
|
-
branch: { type: "string", default: "production" },
|
|
1273
|
-
"template-module-path": { type: "string" },
|
|
1274
|
-
"skill-path": { type: "string" },
|
|
1275
|
-
"install-command": { type: "string", default: "npm install" },
|
|
1276
|
-
"build-command": { type: "string", default: "npm run build" },
|
|
1277
|
-
"commit-sha": { type: "string" },
|
|
1278
|
-
"repo-dir": { type: "string", default: "." },
|
|
1279
|
-
"env-file": { type: "string", default: defaults.envFile },
|
|
1280
|
-
"base-url": { type: "string", default: defaults.baseUrl },
|
|
1281
|
-
"api-key": { type: "string" },
|
|
1282
|
-
"stack-name": { type: "string", default: defaults.stackName }
|
|
1283
|
-
}
|
|
1284
|
-
});
|
|
1285
|
-
const templateId = parsed.values["template-id"];
|
|
1286
|
-
const slugId = parsed.values["slug-id"];
|
|
1287
|
-
const repoUrl = parsed.values["repo-url"];
|
|
1288
|
-
const templateModulePath = parsed.values["template-module-path"];
|
|
1289
|
-
if (!templateId || !slugId || !repoUrl || !templateModulePath) {
|
|
1290
|
-
throw new Error("Command requires --template-id, --slug-id, --repo-url, and --template-module-path.");
|
|
1291
|
-
}
|
|
1292
|
-
deriveTemplateRootDirFromModulePath(templateModulePath);
|
|
1293
|
-
loadEnvFile(parsed.values["env-file"]);
|
|
1294
|
-
const apiKey = parsed.values["api-key"] ?? process.env.VIDFARM_API_KEY;
|
|
1295
|
-
if (!apiKey) {
|
|
1296
|
-
throw new Error("Missing Vidfarm API key. Pass --api-key or set VIDFARM_API_KEY in the selected env file.");
|
|
1297
|
-
}
|
|
1298
|
-
return {
|
|
1299
|
-
templateId,
|
|
1300
|
-
slugId,
|
|
1301
|
-
repoUrl,
|
|
1302
|
-
branch: parsed.values.branch,
|
|
1303
|
-
templateModulePath,
|
|
1304
|
-
skillPath: parsed.values["skill-path"] ?? defaultSkillPathForTemplateModule(templateModulePath),
|
|
1305
|
-
installCommand: parsed.values["install-command"],
|
|
1306
|
-
buildCommand: parsed.values["build-command"],
|
|
1307
|
-
commitSha: parsed.values["commit-sha"] ?? undefined,
|
|
1308
|
-
repoDir: path.resolve(process.cwd(), parsed.values["repo-dir"]),
|
|
1309
|
-
envFile: parsed.values["env-file"],
|
|
1310
|
-
baseUrl: normalizeBaseUrl(parsed.values["base-url"]),
|
|
1311
|
-
apiKey,
|
|
1312
|
-
stackName: parsed.values["stack-name"] || undefined,
|
|
1313
|
-
activate: defaults.activate
|
|
1314
|
-
};
|
|
1315
|
-
}
|
|
1316
|
-
async function importSourceIntoProd(input) {
|
|
1317
|
-
const registration = await registerSourceViaApi(input);
|
|
1318
|
-
const source = await postProdTemplateApi(input, `/api/v1/templates/sources/${registration.source.id}/approve`, { method: "POST" });
|
|
1319
|
-
const releaseResponse = await postProdTemplateApi(input, `/api/v1/templates/sources/${registration.source.id}/import`, {
|
|
1320
|
-
method: "POST",
|
|
1321
|
-
body: JSON.stringify({
|
|
1322
|
-
commit_sha: input.commitSha ?? undefined
|
|
1323
|
-
})
|
|
1324
|
-
});
|
|
1325
|
-
const approvedRelease = await postProdTemplateApi(input, `/api/v1/templates/releases/${releaseResponse.release.id}/approve`, { method: "POST" });
|
|
1326
|
-
const activatedRelease = input.activate
|
|
1327
|
-
? await postProdTemplateApi(input, `/api/v1/templates/releases/${releaseResponse.release.id}/activate`, { method: "POST" })
|
|
1328
|
-
: null;
|
|
1329
|
-
return {
|
|
1330
|
-
payload: {
|
|
1331
|
-
source: source.source,
|
|
1332
|
-
release: approvedRelease.release,
|
|
1333
|
-
activated_release: activatedRelease?.release ?? null
|
|
1334
|
-
}
|
|
1335
|
-
};
|
|
1336
|
-
}
|
|
1337
|
-
async function registerSourceViaApi(input) {
|
|
1338
|
-
return postProdTemplateApi(input, "/api/v1/templates/sources", {
|
|
1339
|
-
method: "POST",
|
|
1340
|
-
body: JSON.stringify({
|
|
1341
|
-
template_id: input.templateId,
|
|
1342
|
-
slug_id: input.slugId,
|
|
1343
|
-
repo_url: input.repoUrl,
|
|
1344
|
-
branch: input.branch,
|
|
1345
|
-
template_module_path: input.templateModulePath,
|
|
1346
|
-
skill_path: input.skillPath,
|
|
1347
|
-
install_command: input.installCommand,
|
|
1348
|
-
build_command: input.buildCommand
|
|
1349
|
-
})
|
|
1350
|
-
});
|
|
1351
|
-
}
|
|
1352
|
-
async function postProdTemplateApi(input, pathname, init) {
|
|
1353
|
-
const response = await fetch(`${input.baseUrl}${pathname}`, {
|
|
1354
|
-
...init,
|
|
1355
|
-
headers: {
|
|
1356
|
-
"content-type": "application/json",
|
|
1357
|
-
"vidfarm-api-key": input.apiKey,
|
|
1358
|
-
...(init.headers ?? {})
|
|
1359
|
-
}
|
|
1360
|
-
});
|
|
1361
|
-
const payload = await response.json().catch(() => ({}));
|
|
1362
|
-
if (!response.ok) {
|
|
1363
|
-
throw new Error(typeof payload.error === "string" ? payload.error : `Request failed with ${response.status}.`);
|
|
1364
|
-
}
|
|
1365
|
-
return payload;
|
|
1366
|
-
}
|
|
1367
|
-
async function packageTemplateArtifact(repoDir, commitSha) {
|
|
1368
|
-
const tempDir = mkdtempSync(path.join(os.tmpdir(), "vidfarm-template-artifact-"));
|
|
1369
|
-
const repoRoot = (await execFileAsync("git", ["-C", repoDir, "rev-parse", "--show-toplevel"])).stdout.trim();
|
|
1370
|
-
const artifactPath = path.join(tempDir, "template-source.tar.gz");
|
|
1371
|
-
try {
|
|
1372
|
-
await execFileAsync("git", ["-C", repoRoot, "archive", "--format=tar.gz", `--output=${artifactPath}`, commitSha], {
|
|
1373
|
-
cwd: repoRoot
|
|
1374
|
-
});
|
|
1375
|
-
const buffer = readFileSync(artifactPath);
|
|
1376
|
-
return {
|
|
1377
|
-
buffer,
|
|
1378
|
-
sha256: createHash("sha256").update(buffer).digest("hex")
|
|
1379
|
-
};
|
|
1380
|
-
}
|
|
1381
|
-
finally {
|
|
1382
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
1383
|
-
}
|
|
1384
|
-
}
|
|
1385
|
-
async function uploadArtifactArchive(presign, buffer) {
|
|
1386
|
-
const response = await fetch(presign.upload.url, {
|
|
1387
|
-
method: presign.upload.method,
|
|
1388
|
-
headers: presign.upload.headers,
|
|
1389
|
-
body: new Uint8Array(buffer)
|
|
1390
|
-
});
|
|
1391
|
-
if (!response.ok) {
|
|
1392
|
-
const body = await response.text().catch(() => "");
|
|
1393
|
-
throw new Error(body || `Artifact upload failed with ${response.status}.`);
|
|
1394
|
-
}
|
|
1395
|
-
}
|
|
1396
|
-
function loadEnvFile(envFile) {
|
|
1397
|
-
const resolved = path.resolve(process.cwd(), envFile);
|
|
1398
|
-
if (!existsSync(resolved)) {
|
|
1399
|
-
throw new Error(`Missing env file: ${resolved}`);
|
|
1400
|
-
}
|
|
1401
|
-
dotenv.config({
|
|
1402
|
-
path: resolved,
|
|
1403
|
-
override: true
|
|
1404
|
-
});
|
|
1405
|
-
}
|
|
1406
|
-
function loadOptionalEnvFile(envFile) {
|
|
1407
|
-
const resolved = path.resolve(process.cwd(), envFile);
|
|
1408
|
-
if (!existsSync(resolved)) {
|
|
1409
|
-
return;
|
|
1410
|
-
}
|
|
1411
|
-
dotenv.config({
|
|
1412
|
-
path: resolved,
|
|
1413
|
-
override: false
|
|
1414
|
-
});
|
|
1415
|
-
}
|
|
1416
|
-
function sanitizeCliUploadFileName(value) {
|
|
1417
|
-
if (!value) {
|
|
1418
|
-
return "";
|
|
1419
|
-
}
|
|
1420
|
-
const normalized = path.basename(value).replace(/[^\w.-]+/g, "_");
|
|
1421
|
-
return normalized.length ? normalized : "upload.bin";
|
|
1422
|
-
}
|
|
1423
|
-
function normalizeBaseUrl(value) {
|
|
177
|
+
function trimTrailingSlash(value) {
|
|
1424
178
|
return value.replace(/\/+$/, "");
|
|
1425
179
|
}
|
|
1426
|
-
function inferUploadContentType(fileName) {
|
|
1427
|
-
switch (path.extname(fileName).toLowerCase()) {
|
|
1428
|
-
case ".png":
|
|
1429
|
-
return "image/png";
|
|
1430
|
-
case ".gif":
|
|
1431
|
-
return "image/gif";
|
|
1432
|
-
case ".jpg":
|
|
1433
|
-
case ".jpeg":
|
|
1434
|
-
return "image/jpeg";
|
|
1435
|
-
case ".webp":
|
|
1436
|
-
return "image/webp";
|
|
1437
|
-
case ".svg":
|
|
1438
|
-
return "image/svg+xml";
|
|
1439
|
-
case ".mp4":
|
|
1440
|
-
case ".m4v":
|
|
1441
|
-
return "video/mp4";
|
|
1442
|
-
case ".mov":
|
|
1443
|
-
return "video/quicktime";
|
|
1444
|
-
case ".webm":
|
|
1445
|
-
return "video/webm";
|
|
1446
|
-
case ".mp3":
|
|
1447
|
-
return "audio/mpeg";
|
|
1448
|
-
case ".wav":
|
|
1449
|
-
return "audio/wav";
|
|
1450
|
-
case ".m4a":
|
|
1451
|
-
return "audio/mp4";
|
|
1452
|
-
case ".aac":
|
|
1453
|
-
return "audio/aac";
|
|
1454
|
-
case ".ogg":
|
|
1455
|
-
return "audio/ogg";
|
|
1456
|
-
case ".pdf":
|
|
1457
|
-
return "application/pdf";
|
|
1458
|
-
case ".md":
|
|
1459
|
-
return "text/markdown; charset=utf-8";
|
|
1460
|
-
case ".txt":
|
|
1461
|
-
return "text/plain; charset=utf-8";
|
|
1462
|
-
default:
|
|
1463
|
-
return "application/octet-stream";
|
|
1464
|
-
}
|
|
1465
|
-
}
|
|
1466
|
-
async function resolveStackOutput(stackName, outputKey) {
|
|
1467
|
-
const { stdout } = await runLocalCommand("aws", [
|
|
1468
|
-
"cloudformation",
|
|
1469
|
-
"describe-stacks",
|
|
1470
|
-
"--stack-name", stackName,
|
|
1471
|
-
"--query", `Stacks[0].Outputs[?OutputKey=='${outputKey}'].OutputValue`,
|
|
1472
|
-
"--output", "text"
|
|
1473
|
-
]);
|
|
1474
|
-
const value = stdout.trim();
|
|
1475
|
-
if (!value || value === "None") {
|
|
1476
|
-
throw new Error(`Could not resolve ${outputKey} from stack ${stackName}.`);
|
|
1477
|
-
}
|
|
1478
|
-
return value;
|
|
1479
|
-
}
|
|
1480
|
-
async function getProdCurrentImage(input) {
|
|
1481
|
-
loadEnvFile(input.envFile);
|
|
1482
|
-
const instanceId = await resolveStackOutput(input.stackName, "VidfarmInstanceId");
|
|
1483
|
-
const stdout = await runSsmScript({
|
|
1484
|
-
instanceId,
|
|
1485
|
-
comment: "Read current Vidfarm image",
|
|
1486
|
-
script: [
|
|
1487
|
-
"set -euo pipefail",
|
|
1488
|
-
"sudo docker ps -a --filter name=^/vidfarm$ --format '{{.Image}}' | head -n 1",
|
|
1489
|
-
"sudo systemctl is-active vidfarm || true"
|
|
1490
|
-
].join("\n")
|
|
1491
|
-
});
|
|
1492
|
-
const [image, status] = stdout.trim().split(/\n+/);
|
|
1493
|
-
if (!image) {
|
|
1494
|
-
throw new Error("Could not determine the current live Docker image.");
|
|
1495
|
-
}
|
|
1496
|
-
return {
|
|
1497
|
-
instanceId,
|
|
1498
|
-
image,
|
|
1499
|
-
status: status ?? ""
|
|
1500
|
-
};
|
|
1501
|
-
}
|
|
1502
|
-
async function runSsmScript(input) {
|
|
1503
|
-
const tempDir = mkTempDir();
|
|
1504
|
-
try {
|
|
1505
|
-
const commandPath = path.join(tempDir, "commands.json");
|
|
1506
|
-
const scriptB64 = Buffer.from(input.script, "utf8").toString("base64");
|
|
1507
|
-
writeFileSync(commandPath, JSON.stringify({
|
|
1508
|
-
commands: [
|
|
1509
|
-
`echo ${shellQuote(scriptB64)} | base64 --decode > /tmp/vidfarm-operator.sh`,
|
|
1510
|
-
"bash /tmp/vidfarm-operator.sh"
|
|
1511
|
-
]
|
|
1512
|
-
}));
|
|
1513
|
-
const { stdout: sendStdout } = await runLocalCommand("aws", [
|
|
1514
|
-
"ssm",
|
|
1515
|
-
"send-command",
|
|
1516
|
-
"--instance-ids", input.instanceId,
|
|
1517
|
-
"--document-name", "AWS-RunShellScript",
|
|
1518
|
-
"--comment", input.comment,
|
|
1519
|
-
"--parameters", `file://${commandPath}`,
|
|
1520
|
-
"--query", "Command.CommandId",
|
|
1521
|
-
"--output", "text"
|
|
1522
|
-
]);
|
|
1523
|
-
const commandId = sendStdout.trim();
|
|
1524
|
-
if (!commandId) {
|
|
1525
|
-
throw new Error(`Unable to start SSM command for ${input.comment}.`);
|
|
1526
|
-
}
|
|
1527
|
-
while (true) {
|
|
1528
|
-
const { stdout } = await runLocalCommand("aws", [
|
|
1529
|
-
"ssm",
|
|
1530
|
-
"get-command-invocation",
|
|
1531
|
-
"--command-id", commandId,
|
|
1532
|
-
"--instance-id", input.instanceId
|
|
1533
|
-
]);
|
|
1534
|
-
const response = JSON.parse(stdout);
|
|
1535
|
-
if (response.Status === "Success") {
|
|
1536
|
-
return response.StandardOutputContent ?? "";
|
|
1537
|
-
}
|
|
1538
|
-
if (["Failed", "Cancelled", "TimedOut", "Cancelling"].includes(response.Status)) {
|
|
1539
|
-
throw new Error((response.StandardErrorContent ?? "").trim()
|
|
1540
|
-
|| (response.StandardOutputContent ?? "").trim()
|
|
1541
|
-
|| `SSM command failed with status ${response.Status}.`);
|
|
1542
|
-
}
|
|
1543
|
-
await sleep(2000);
|
|
1544
|
-
}
|
|
1545
|
-
}
|
|
1546
|
-
finally {
|
|
1547
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
1548
|
-
}
|
|
1549
|
-
}
|
|
1550
|
-
async function runLocalCommand(command, args) {
|
|
1551
|
-
return execFileAsync(command, args, {
|
|
1552
|
-
cwd: process.cwd(),
|
|
1553
|
-
env: process.env,
|
|
1554
|
-
maxBuffer: 10 * 1024 * 1024
|
|
1555
|
-
});
|
|
1556
|
-
}
|
|
1557
|
-
function shellQuote(value) {
|
|
1558
|
-
return `'${value.replace(/'/g, `'\"'\"'`)}'`;
|
|
1559
|
-
}
|
|
1560
|
-
function mkTempDir() {
|
|
1561
|
-
return mkdtempSync(path.join(os.tmpdir(), "vidfarm-operator-"));
|
|
1562
|
-
}
|
|
1563
|
-
function extractLastJson(stdout) {
|
|
1564
|
-
const trimmed = stdout.trim();
|
|
1565
|
-
const start = trimmed.lastIndexOf("\n{");
|
|
1566
|
-
const jsonText = start >= 0 ? trimmed.slice(start + 1) : trimmed;
|
|
1567
|
-
return JSON.parse(jsonText);
|
|
1568
|
-
}
|
|
1569
|
-
function sleep(ms) {
|
|
1570
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1571
|
-
}
|
|
1572
180
|
//# sourceMappingURL=cli.js.map
|