@mevdragon/vidfarm-devcli 0.2.7 → 0.2.8
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/GETTING_STARTED.developers.md +14 -18
- package/README.md +19 -5
- package/SKILL.developer.md +140 -11
- package/dist/src/account-pages.js +1 -1
- package/dist/src/app.js +305 -9
- package/dist/src/cli.js +142 -6
- package/dist/src/config.js +2 -5
- package/dist/src/context.js +16 -0
- package/dist/src/db.js +33 -2
- package/dist/src/homepage.js +33 -1
- package/dist/src/lib/template-paths.js +10 -4
- package/dist/src/registry.js +9 -2
- package/dist/src/services/auth.js +2 -6
- package/dist/src/services/job-logs.js +4 -4
- package/dist/src/services/providers.js +470 -0
- package/dist/src/services/storage.js +13 -0
- package/dist/src/services/template-certification.js +16 -2
- package/dist/src/services/template-loader.js +17 -1
- package/dist/src/services/template-sources.js +249 -5
- package/{templates/template_0000 → dist/templates/vidfarm_template_0000}/src/remotion/Root.js +1 -0
- package/dist/templates/{template_0000 → vidfarm_template_0000}/src/template.js +9 -3
- package/package.json +4 -3
- package/templates/{template_0000 → vidfarm_template_0000}/README.md +23 -0
- package/templates/{template_0000 → vidfarm_template_0000}/package.json +0 -1
- package/templates/{template_0000 → vidfarm_template_0000}/src/sdk.ts +18 -0
- package/templates/{template_0000 → vidfarm_template_0000}/src/template.js +9 -3
- package/templates/{template_0000 → vidfarm_template_0000}/src/template.ts +10 -3
- package/templates/vidfarm_template_0000/tmp/solobacterium-moorei-slideshow.request.json +31 -0
- /package/dist/templates/{template_0000 → vidfarm_template_0000}/src/lib/images.js +0 -0
- /package/dist/templates/{template_0000 → vidfarm_template_0000}/src/remotion/index.js +0 -0
- /package/dist/templates/{template_0000 → vidfarm_template_0000}/src/sdk.js +0 -0
- /package/dist/templates/{template_0000 → vidfarm_template_0000}/src/style-options.js +0 -0
- /package/dist/templates/{template_0000 → vidfarm_template_0000}/src/template-dna.js +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/SKILL.md +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/assets/Abel-Regular.ttf +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/assets/DMSerifDisplay-Regular.ttf +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/assets/Montserrat[wght].ttf +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/assets/SourceCodePro[wght].ttf +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/assets/TikTokSans-SemiBold.ttf +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/assets/Yesteryear-Regular.ttf +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/composition.json +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/research/preview/.gitkeep +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/research/source_notes.md +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/src/lib/images.js +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/src/lib/images.ts +0 -0
- /package/{dist/templates/template_0000 → templates/vidfarm_template_0000}/src/remotion/Root.js +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/src/remotion/Root.tsx +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/src/remotion/index.js +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/src/remotion/index.tsx +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/src/sdk.js +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/src/style-options.js +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/src/style-options.ts +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/src/template-dna.js +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/src/template-dna.ts +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/template.config.json +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/tsconfig.json +0 -0
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHash, createHmac } from "node:crypto";
|
|
2
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
2
4
|
import path from "node:path";
|
|
3
5
|
import { execFile } from "node:child_process";
|
|
4
6
|
import { promisify } from "node:util";
|
|
@@ -9,7 +11,9 @@ import { defaultSkillPathForTemplateModule, deriveTemplateRootDirFromModulePath
|
|
|
9
11
|
import { nowIso } from "../lib/time.js";
|
|
10
12
|
import { loadTemplateFromModule } from "./template-loader.js";
|
|
11
13
|
import { TemplateCertificationService } from "./template-certification.js";
|
|
14
|
+
import { StorageService } from "./storage.js";
|
|
12
15
|
const execFileAsync = promisify(execFile);
|
|
16
|
+
const storage = new StorageService();
|
|
13
17
|
export class TemplateSourceAlreadyRegisteredError extends Error {
|
|
14
18
|
existingSource;
|
|
15
19
|
conflictField;
|
|
@@ -127,14 +131,26 @@ export class TemplateSourceService {
|
|
|
127
131
|
if (!source) {
|
|
128
132
|
throw new Error("Template source not found.");
|
|
129
133
|
}
|
|
130
|
-
const
|
|
134
|
+
const artifactManifest = input.artifactManifestKey
|
|
135
|
+
? await this.loadAndVerifyArtifactManifest(input.artifactManifestKey, source)
|
|
136
|
+
: null;
|
|
137
|
+
const commitSha = artifactManifest?.commit_sha ?? input.commitSha ?? await this.resolveBranchHead(source.repoUrl, source.branch);
|
|
131
138
|
const checkoutPath = path.join(config.TEMPLATE_SOURCE_ROOT, source.templateId, commitSha);
|
|
132
|
-
const skillPath = path.join(checkoutPath, source.skillPath);
|
|
139
|
+
const skillPath = path.join(checkoutPath, artifactManifest?.skill_path ?? source.skillPath);
|
|
133
140
|
const authRepoUrl = this.resolveGitRemoteUrl(source.repoUrl);
|
|
134
141
|
if (!existsSync(checkoutPath)) {
|
|
135
142
|
mkdirSync(path.dirname(checkoutPath), { recursive: true });
|
|
136
|
-
|
|
137
|
-
|
|
143
|
+
if (artifactManifest) {
|
|
144
|
+
await this.extractArtifactToCheckout({
|
|
145
|
+
checkoutPath,
|
|
146
|
+
artifactKey: artifactManifest.artifact_key,
|
|
147
|
+
expectedSha256: artifactManifest.artifact_sha256
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
await this.runShell(["git", "clone", "--branch", source.branch, authRepoUrl, checkoutPath], process.cwd());
|
|
152
|
+
await this.runShell(["git", "checkout", commitSha], checkoutPath);
|
|
153
|
+
}
|
|
138
154
|
if (source.installCommand.trim()) {
|
|
139
155
|
await this.runCommandString(source.installCommand, checkoutPath);
|
|
140
156
|
}
|
|
@@ -161,11 +177,112 @@ export class TemplateSourceService {
|
|
|
161
177
|
checkoutPath,
|
|
162
178
|
skillPath,
|
|
163
179
|
modulePath,
|
|
180
|
+
artifactManifestKey: input.artifactManifestKey ?? null,
|
|
181
|
+
artifactKey: artifactManifest?.artifact_key ?? null,
|
|
164
182
|
status,
|
|
165
183
|
certificationReport,
|
|
166
184
|
activatedAt: null
|
|
167
185
|
});
|
|
168
186
|
}
|
|
187
|
+
async publishRelease(input) {
|
|
188
|
+
const source = database.getTemplateSource(input.sourceId);
|
|
189
|
+
if (!source) {
|
|
190
|
+
throw new Error("Template source not found.");
|
|
191
|
+
}
|
|
192
|
+
if (source.status !== "approved") {
|
|
193
|
+
throw new Error("Template source must be approved before a developer can publish an update.");
|
|
194
|
+
}
|
|
195
|
+
const trimmedCommitSha = input.commitSha.trim();
|
|
196
|
+
if (!trimmedCommitSha) {
|
|
197
|
+
throw new Error("commit_sha is required.");
|
|
198
|
+
}
|
|
199
|
+
const existingRelease = database.getTemplateReleaseBySourceAndCommit(source.id, trimmedCommitSha);
|
|
200
|
+
if (existingRelease &&
|
|
201
|
+
(existingRelease.status === "pending_approval"
|
|
202
|
+
|| existingRelease.status === "approved"
|
|
203
|
+
|| existingRelease.status === "active")) {
|
|
204
|
+
return {
|
|
205
|
+
source,
|
|
206
|
+
release: existingRelease,
|
|
207
|
+
publishAction: "updated"
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
const release = await this.importRelease({
|
|
211
|
+
sourceId: source.id,
|
|
212
|
+
commitSha: trimmedCommitSha
|
|
213
|
+
});
|
|
214
|
+
return {
|
|
215
|
+
source,
|
|
216
|
+
release,
|
|
217
|
+
publishAction: existingRelease ? "updated" : "created"
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
async publishArtifactRelease(input) {
|
|
221
|
+
const source = database.getTemplateSource(input.sourceId);
|
|
222
|
+
if (!source) {
|
|
223
|
+
throw new Error("Template source not found.");
|
|
224
|
+
}
|
|
225
|
+
if (source.status !== "approved") {
|
|
226
|
+
throw new Error("Template source must be approved before a developer can publish an update.");
|
|
227
|
+
}
|
|
228
|
+
const trimmedCommitSha = input.commitSha.trim();
|
|
229
|
+
const trimmedArtifactSha256 = input.artifactSha256.trim().toLowerCase();
|
|
230
|
+
if (!trimmedCommitSha) {
|
|
231
|
+
throw new Error("commit_sha is required.");
|
|
232
|
+
}
|
|
233
|
+
if (!/^[0-9a-f]{64}$/i.test(trimmedArtifactSha256)) {
|
|
234
|
+
throw new Error("artifact_sha256 must be a 64-character hexadecimal SHA-256 hash.");
|
|
235
|
+
}
|
|
236
|
+
const existingRelease = database.getTemplateReleaseBySourceAndCommit(source.id, trimmedCommitSha);
|
|
237
|
+
if (existingRelease &&
|
|
238
|
+
(existingRelease.status === "pending_approval"
|
|
239
|
+
|| existingRelease.status === "approved"
|
|
240
|
+
|| existingRelease.status === "active")) {
|
|
241
|
+
return {
|
|
242
|
+
source,
|
|
243
|
+
release: existingRelease,
|
|
244
|
+
publishAction: "updated"
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
const { manifestKey } = await this.createSignedArtifactManifest({
|
|
248
|
+
source,
|
|
249
|
+
commitSha: trimmedCommitSha,
|
|
250
|
+
artifactSha256: trimmedArtifactSha256,
|
|
251
|
+
signedBy: input.signedBy
|
|
252
|
+
});
|
|
253
|
+
const release = await this.importRelease({
|
|
254
|
+
sourceId: source.id,
|
|
255
|
+
artifactManifestKey: manifestKey
|
|
256
|
+
});
|
|
257
|
+
return {
|
|
258
|
+
source,
|
|
259
|
+
release,
|
|
260
|
+
publishAction: existingRelease ? "updated" : "created"
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
async createDeveloperArtifactUpload(input) {
|
|
264
|
+
const source = database.getTemplateSource(input.sourceId);
|
|
265
|
+
if (!source) {
|
|
266
|
+
throw new Error("Template source not found.");
|
|
267
|
+
}
|
|
268
|
+
if (source.status !== "approved") {
|
|
269
|
+
throw new Error("Template source must be approved before a developer can publish an update.");
|
|
270
|
+
}
|
|
271
|
+
const trimmedCommitSha = input.commitSha.trim();
|
|
272
|
+
if (!trimmedCommitSha) {
|
|
273
|
+
throw new Error("commit_sha is required.");
|
|
274
|
+
}
|
|
275
|
+
const artifact = this.buildArtifactKeys(source, trimmedCommitSha);
|
|
276
|
+
const upload = await storage.createWriteUrl(artifact.artifactKey, {
|
|
277
|
+
contentType: "application/gzip",
|
|
278
|
+
publicRead: false,
|
|
279
|
+
expiresIn: 3600
|
|
280
|
+
});
|
|
281
|
+
return {
|
|
282
|
+
...artifact,
|
|
283
|
+
upload
|
|
284
|
+
};
|
|
285
|
+
}
|
|
169
286
|
approveSource(input) {
|
|
170
287
|
const source = database.getTemplateSource(input.sourceId);
|
|
171
288
|
if (!source) {
|
|
@@ -315,6 +432,133 @@ export class TemplateSourceService {
|
|
|
315
432
|
}
|
|
316
433
|
});
|
|
317
434
|
}
|
|
435
|
+
async loadAndVerifyArtifactManifest(manifestKey, source) {
|
|
436
|
+
const raw = await storage.readText(manifestKey);
|
|
437
|
+
if (!raw) {
|
|
438
|
+
throw new Error(`Artifact manifest not found: ${manifestKey}`);
|
|
439
|
+
}
|
|
440
|
+
const manifest = JSON.parse(raw);
|
|
441
|
+
if (manifest.version !== 1 || manifest.algorithm !== "hmac-sha256") {
|
|
442
|
+
throw new Error("Unsupported template artifact manifest version or algorithm.");
|
|
443
|
+
}
|
|
444
|
+
if (!config.ARTIFACT_SIGNING_SECRET) {
|
|
445
|
+
throw new Error("ARTIFACT_SIGNING_SECRET or SUPERAGENCY_KEY is required to verify artifact manifests.");
|
|
446
|
+
}
|
|
447
|
+
const expectedSignature = createHmac("sha256", config.ARTIFACT_SIGNING_SECRET)
|
|
448
|
+
.update(this.serializeArtifactManifestPayload({
|
|
449
|
+
version: 1,
|
|
450
|
+
algorithm: "hmac-sha256",
|
|
451
|
+
template_id: String(manifest.template_id ?? ""),
|
|
452
|
+
slug_id: String(manifest.slug_id ?? ""),
|
|
453
|
+
repo_url: String(manifest.repo_url ?? ""),
|
|
454
|
+
branch: String(manifest.branch ?? ""),
|
|
455
|
+
commit_sha: String(manifest.commit_sha ?? ""),
|
|
456
|
+
template_module_path: String(manifest.template_module_path ?? ""),
|
|
457
|
+
skill_path: String(manifest.skill_path ?? ""),
|
|
458
|
+
install_command: String(manifest.install_command ?? ""),
|
|
459
|
+
build_command: String(manifest.build_command ?? ""),
|
|
460
|
+
artifact_key: String(manifest.artifact_key ?? ""),
|
|
461
|
+
artifact_sha256: String(manifest.artifact_sha256 ?? ""),
|
|
462
|
+
created_at: String(manifest.created_at ?? ""),
|
|
463
|
+
signed_by: String(manifest.signed_by ?? "")
|
|
464
|
+
}))
|
|
465
|
+
.digest("hex");
|
|
466
|
+
if (manifest.signature !== expectedSignature) {
|
|
467
|
+
throw new Error("Template artifact manifest signature verification failed.");
|
|
468
|
+
}
|
|
469
|
+
if (manifest.template_id !== source.templateId || manifest.slug_id !== source.slugId) {
|
|
470
|
+
throw new Error("Template artifact manifest does not match the registered template identity.");
|
|
471
|
+
}
|
|
472
|
+
if (manifest.repo_url !== source.repoUrl || manifest.branch !== source.branch) {
|
|
473
|
+
throw new Error("Template artifact manifest does not match the registered repo URL or branch.");
|
|
474
|
+
}
|
|
475
|
+
if (manifest.template_module_path !== source.templateModulePath) {
|
|
476
|
+
throw new Error("Template artifact manifest does not match the registered template module path.");
|
|
477
|
+
}
|
|
478
|
+
if (manifest.skill_path !== source.skillPath) {
|
|
479
|
+
throw new Error("Template artifact manifest does not match the registered skill path.");
|
|
480
|
+
}
|
|
481
|
+
if (manifest.install_command !== source.installCommand || manifest.build_command !== source.buildCommand) {
|
|
482
|
+
throw new Error("Template artifact manifest does not match the registered install or build command.");
|
|
483
|
+
}
|
|
484
|
+
return manifest;
|
|
485
|
+
}
|
|
486
|
+
async extractArtifactToCheckout(input) {
|
|
487
|
+
const archive = await storage.readBuffer(input.artifactKey);
|
|
488
|
+
if (!archive) {
|
|
489
|
+
throw new Error(`Template artifact not found: ${input.artifactKey}`);
|
|
490
|
+
}
|
|
491
|
+
const actualSha256 = createHash("sha256").update(archive).digest("hex");
|
|
492
|
+
if (actualSha256 !== input.expectedSha256) {
|
|
493
|
+
throw new Error(`Template artifact hash mismatch for ${input.artifactKey}.`);
|
|
494
|
+
}
|
|
495
|
+
mkdirSync(input.checkoutPath, { recursive: true });
|
|
496
|
+
const tempArchivePath = path.join(os.tmpdir(), `vidfarm-template-${createId("tmp")}.tar.gz`);
|
|
497
|
+
writeFileSync(tempArchivePath, archive);
|
|
498
|
+
try {
|
|
499
|
+
await execFileAsync("tar", ["-xzf", tempArchivePath, "-C", input.checkoutPath], { cwd: process.cwd() });
|
|
500
|
+
}
|
|
501
|
+
finally {
|
|
502
|
+
rmSync(tempArchivePath, { force: true });
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
serializeArtifactManifestPayload(input) {
|
|
506
|
+
return JSON.stringify({
|
|
507
|
+
version: input.version,
|
|
508
|
+
algorithm: input.algorithm,
|
|
509
|
+
template_id: input.template_id,
|
|
510
|
+
slug_id: input.slug_id,
|
|
511
|
+
repo_url: input.repo_url,
|
|
512
|
+
branch: input.branch,
|
|
513
|
+
commit_sha: input.commit_sha,
|
|
514
|
+
template_module_path: input.template_module_path,
|
|
515
|
+
skill_path: input.skill_path,
|
|
516
|
+
install_command: input.install_command,
|
|
517
|
+
build_command: input.build_command,
|
|
518
|
+
artifact_key: input.artifact_key,
|
|
519
|
+
artifact_sha256: input.artifact_sha256,
|
|
520
|
+
created_at: input.created_at,
|
|
521
|
+
signed_by: input.signed_by
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
buildArtifactKeys(source, commitSha) {
|
|
525
|
+
return {
|
|
526
|
+
artifactKey: `template-artifacts/${source.templateId}/${commitSha}/source.tar.gz`,
|
|
527
|
+
manifestKey: `template-artifacts/${source.templateId}/${commitSha}/manifest.json`
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
async createSignedArtifactManifest(input) {
|
|
531
|
+
const artifact = this.buildArtifactKeys(input.source, input.commitSha);
|
|
532
|
+
const createdAt = nowIso();
|
|
533
|
+
const payload = {
|
|
534
|
+
version: 1,
|
|
535
|
+
algorithm: "hmac-sha256",
|
|
536
|
+
template_id: input.source.templateId,
|
|
537
|
+
slug_id: input.source.slugId,
|
|
538
|
+
repo_url: input.source.repoUrl,
|
|
539
|
+
branch: input.source.branch,
|
|
540
|
+
commit_sha: input.commitSha,
|
|
541
|
+
template_module_path: input.source.templateModulePath,
|
|
542
|
+
skill_path: input.source.skillPath,
|
|
543
|
+
install_command: input.source.installCommand,
|
|
544
|
+
build_command: input.source.buildCommand,
|
|
545
|
+
artifact_key: artifact.artifactKey,
|
|
546
|
+
artifact_sha256: input.artifactSha256,
|
|
547
|
+
created_at: createdAt,
|
|
548
|
+
signed_by: input.signedBy
|
|
549
|
+
};
|
|
550
|
+
const signature = createHmac("sha256", config.ARTIFACT_SIGNING_SECRET)
|
|
551
|
+
.update(this.serializeArtifactManifestPayload(payload))
|
|
552
|
+
.digest("hex");
|
|
553
|
+
await storage.putText(artifact.manifestKey, JSON.stringify({
|
|
554
|
+
...payload,
|
|
555
|
+
signature
|
|
556
|
+
}, null, 2), "application/json");
|
|
557
|
+
return {
|
|
558
|
+
...artifact,
|
|
559
|
+
signature
|
|
560
|
+
};
|
|
561
|
+
}
|
|
318
562
|
resolveGitRemoteUrl(repoUrl) {
|
|
319
563
|
const githubToken = config.VIDFARM_GITHUB_TOKEN || config.GITHUB_TOKEN;
|
|
320
564
|
if (!githubToken) {
|
package/{templates/template_0000 → dist/templates/vidfarm_template_0000}/src/remotion/Root.js
RENAMED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
2
3
|
import { AbsoluteFill, Composition, Img, Sequence, interpolate, spring, useCurrentFrame, useVideoConfig } from "remotion";
|
|
3
4
|
export const RemotionRoot = () => {
|
|
4
5
|
return (_jsx(Composition, { id: "template-0000", component: TemplateVideo, width: 1080, height: 1920, fps: 30, durationInFrames: 120, defaultProps: { slides: [] }, calculateMetadata: ({ props }) => ({
|
|
@@ -100,6 +100,12 @@ export const template0000Definition = defineTemplate({
|
|
|
100
100
|
visual_dna: templateVisualDna,
|
|
101
101
|
preview_media: [...TEMPLATE_PREVIEW_MEDIA],
|
|
102
102
|
link_to_original: templateLinkToOriginal,
|
|
103
|
+
proposal_generator: "Given the director's product, customer, and angle, propose multiple slideshow concepts that fit this template's creator-native TikTok format. Return concrete post ideas that can either be used directly as single prompts or expanded into 10+ related prompts. Favor specific hooks, clear educational angles, founder stories, product proof, objections, comparisons, and repeatable series ideas.",
|
|
104
|
+
sample_prompts: [
|
|
105
|
+
"Make a 3-slide TikTok slideshow explaining why cold plunges feel so hard at first but get easier after a week. Use exact overlay text for each slide and generate matching cinematic background prompts.",
|
|
106
|
+
"Create a founder-story slideshow about launching a niche coffee brand, with one hook slide, one proof slide, and one CTA slide. Keep the tone creator-native and direct.",
|
|
107
|
+
"Build a product explainer slideshow for a posture-correcting desk setup. I want concise overlays, clean visual prompts, and output ready for optional Remotion rendering."
|
|
108
|
+
],
|
|
103
109
|
},
|
|
104
110
|
skillPath,
|
|
105
111
|
configSchema: z.object({
|
|
@@ -1407,7 +1413,7 @@ function resolveRemotionEntryPoint() {
|
|
|
1407
1413
|
function resolveTemplateRuntimeConfig() {
|
|
1408
1414
|
const candidates = [
|
|
1409
1415
|
fileURLToPath(new URL("../template.config.json", import.meta.url)),
|
|
1410
|
-
path.resolve(process.cwd(), "templates/
|
|
1416
|
+
path.resolve(process.cwd(), "templates/vidfarm_template_0000/template.config.json"),
|
|
1411
1417
|
];
|
|
1412
1418
|
for (const candidate of candidates) {
|
|
1413
1419
|
if (!existsSync(candidate)) {
|
|
@@ -1422,13 +1428,13 @@ function resolveSkillPath() {
|
|
|
1422
1428
|
if (existsSync(builtPath)) {
|
|
1423
1429
|
return builtPath;
|
|
1424
1430
|
}
|
|
1425
|
-
return path.resolve(process.cwd(), "templates/
|
|
1431
|
+
return path.resolve(process.cwd(), "templates/vidfarm_template_0000/SKILL.md");
|
|
1426
1432
|
}
|
|
1427
1433
|
function resolveOverlayFonts() {
|
|
1428
1434
|
const resolved = Object.fromEntries(TEMPLATE_FONT_OPTIONS.map((option) => {
|
|
1429
1435
|
const candidates = [
|
|
1430
1436
|
fileURLToPath(new URL(`../assets/${option.assetFile}`, import.meta.url)),
|
|
1431
|
-
path.resolve(process.cwd(), `templates/
|
|
1437
|
+
path.resolve(process.cwd(), `templates/vidfarm_template_0000/assets/${option.assetFile}`),
|
|
1432
1438
|
];
|
|
1433
1439
|
for (const candidate of candidates) {
|
|
1434
1440
|
if (existsSync(candidate)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mevdragon/vidfarm-devcli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"description": "Developer CLI for running the Vidfarm local template platform.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"dist/src",
|
|
12
|
-
"dist/templates/
|
|
13
|
-
"templates/
|
|
12
|
+
"dist/templates/vidfarm_template_0000",
|
|
13
|
+
"templates/vidfarm_template_0000",
|
|
14
14
|
"README.md",
|
|
15
15
|
"SKILL.developer.md",
|
|
16
16
|
"GETTING_STARTED.developers.md",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"scripts": {
|
|
30
30
|
"dev": "tsx watch src/index.ts",
|
|
31
31
|
"dev:cli": "tsx src/cli.ts dev",
|
|
32
|
+
"operator:template-artifact": "node scripts/platform-operator-template-artifact.mjs",
|
|
32
33
|
"deploy:prod-inplace": "bash scripts/deploy-prod-inplace.sh",
|
|
33
34
|
"rollback:prod-inplace": "bash scripts/rollback-prod-inplace.sh",
|
|
34
35
|
"build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.json",
|
|
@@ -23,6 +23,22 @@ Production integration settings are checked into `template.config.json`. AI agen
|
|
|
23
23
|
- the template module path, which should be the TypeScript source entrypoint `src/template.ts`
|
|
24
24
|
- the local Remotion composition settings used by the starter template
|
|
25
25
|
|
|
26
|
+
## Required template contract
|
|
27
|
+
|
|
28
|
+
Every new template copied from `template_0000` should keep the current required `defineTemplate(...)` contract intact.
|
|
29
|
+
|
|
30
|
+
- `id`, `slugId`, and `version`
|
|
31
|
+
- `about.title` and `about.description`
|
|
32
|
+
- `about.viral_dna`, `about.visual_dna`, `about.preview_media`, and `about.link_to_original`
|
|
33
|
+
- `about.proposal_generator`, which teaches a director or agent how to turn business context into template-native post proposals
|
|
34
|
+
- `about.sample_prompts`, which shows multiple concrete high-signal prompts for the template
|
|
35
|
+
- `configSchema`
|
|
36
|
+
- at least one operation with a `smokeTestPayload`
|
|
37
|
+
- a matching workflow implementation in `jobs`
|
|
38
|
+
- a template-local `SKILL.md`
|
|
39
|
+
|
|
40
|
+
The starter already ships with example `proposal_generator` and `sample_prompts` values in `src/template.ts`. When you adapt this template, replace them with instructions and examples that fit the new format instead of leaving the starter language in place.
|
|
41
|
+
|
|
26
42
|
## Included
|
|
27
43
|
|
|
28
44
|
- `SKILL.md` for customer AI-agent usage
|
|
@@ -75,3 +91,10 @@ vidfarm analyze-visual-dna --template-dir .
|
|
|
75
91
|
```
|
|
76
92
|
|
|
77
93
|
If you scaffold a template with `vidfarm generate-template` and provide `--source-preview-dir`, the CLI will run both analyzers automatically unless `--skip-dna-analysis` is set.
|
|
94
|
+
|
|
95
|
+
After DNA analysis, review and customize the remaining human-authored metadata before calling the template ready:
|
|
96
|
+
|
|
97
|
+
- refresh `about.proposal_generator`
|
|
98
|
+
- refresh `about.sample_prompts`
|
|
99
|
+
- update `SKILL.md` to match the actual operations and payload shapes
|
|
100
|
+
- run `vidfarm validate-template --template-id <slug-or-id>` and at least one real local job
|
|
@@ -18,6 +18,8 @@ export interface TemplateAboutMetadata {
|
|
|
18
18
|
visual_dna: string;
|
|
19
19
|
preview_media: string[];
|
|
20
20
|
link_to_original: string;
|
|
21
|
+
proposal_generator: string;
|
|
22
|
+
sample_prompts: string[];
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
export interface JobExecutionResult {
|
|
@@ -85,6 +87,14 @@ export interface TemplateJobContext {
|
|
|
85
87
|
prompt: string;
|
|
86
88
|
temperature?: number;
|
|
87
89
|
}): Promise<{ text: string; usage: { inputTokens: number; outputTokens: number; costUsd: number } }>;
|
|
90
|
+
generateSpeech(input: {
|
|
91
|
+
provider: ProviderType;
|
|
92
|
+
model: string;
|
|
93
|
+
text: string;
|
|
94
|
+
voice?: string;
|
|
95
|
+
instructions?: string;
|
|
96
|
+
responseFormat?: "mp3" | "wav";
|
|
97
|
+
}): Promise<{ bytes: Uint8Array; contentType: string; usage: { inputTokens: number; outputTokens: number; costUsd: number } }>;
|
|
88
98
|
generateImage(input: {
|
|
89
99
|
provider: ProviderType;
|
|
90
100
|
model: string;
|
|
@@ -105,6 +115,14 @@ export interface TemplateJobContext {
|
|
|
105
115
|
maxWidthPercent: number;
|
|
106
116
|
justification: string;
|
|
107
117
|
}>;
|
|
118
|
+
transcribeSpeech(input: {
|
|
119
|
+
provider: ProviderType;
|
|
120
|
+
model: string;
|
|
121
|
+
audio: Uint8Array;
|
|
122
|
+
contentType?: string;
|
|
123
|
+
prompt?: string;
|
|
124
|
+
language?: string;
|
|
125
|
+
}): Promise<{ text: string; usage: { inputTokens: number; outputTokens: number; costUsd: number } }>;
|
|
108
126
|
};
|
|
109
127
|
remotion: {
|
|
110
128
|
render(input: {
|
|
@@ -100,6 +100,12 @@ export const template0000Definition = defineTemplate({
|
|
|
100
100
|
visual_dna: templateVisualDna,
|
|
101
101
|
preview_media: [...TEMPLATE_PREVIEW_MEDIA],
|
|
102
102
|
link_to_original: templateLinkToOriginal,
|
|
103
|
+
proposal_generator: "Given the director's product, customer, and angle, propose multiple slideshow concepts that fit this template's creator-native TikTok format. Return concrete post ideas that can either be used directly as single prompts or expanded into 10+ related prompts. Favor specific hooks, clear educational angles, founder stories, product proof, objections, comparisons, and repeatable series ideas.",
|
|
104
|
+
sample_prompts: [
|
|
105
|
+
"Make a 3-slide TikTok slideshow explaining why cold plunges feel so hard at first but get easier after a week. Use exact overlay text for each slide and generate matching cinematic background prompts.",
|
|
106
|
+
"Create a founder-story slideshow about launching a niche coffee brand, with one hook slide, one proof slide, and one CTA slide. Keep the tone creator-native and direct.",
|
|
107
|
+
"Build a product explainer slideshow for a posture-correcting desk setup. I want concise overlays, clean visual prompts, and output ready for optional Remotion rendering."
|
|
108
|
+
],
|
|
103
109
|
},
|
|
104
110
|
skillPath,
|
|
105
111
|
configSchema: z.object({
|
|
@@ -1407,7 +1413,7 @@ function resolveRemotionEntryPoint() {
|
|
|
1407
1413
|
function resolveTemplateRuntimeConfig() {
|
|
1408
1414
|
const candidates = [
|
|
1409
1415
|
fileURLToPath(new URL("../template.config.json", import.meta.url)),
|
|
1410
|
-
path.resolve(process.cwd(), "templates/
|
|
1416
|
+
path.resolve(process.cwd(), "templates/vidfarm_template_0000/template.config.json"),
|
|
1411
1417
|
];
|
|
1412
1418
|
for (const candidate of candidates) {
|
|
1413
1419
|
if (!existsSync(candidate)) {
|
|
@@ -1422,13 +1428,13 @@ function resolveSkillPath() {
|
|
|
1422
1428
|
if (existsSync(builtPath)) {
|
|
1423
1429
|
return builtPath;
|
|
1424
1430
|
}
|
|
1425
|
-
return path.resolve(process.cwd(), "templates/
|
|
1431
|
+
return path.resolve(process.cwd(), "templates/vidfarm_template_0000/SKILL.md");
|
|
1426
1432
|
}
|
|
1427
1433
|
function resolveOverlayFonts() {
|
|
1428
1434
|
const resolved = Object.fromEntries(TEMPLATE_FONT_OPTIONS.map((option) => {
|
|
1429
1435
|
const candidates = [
|
|
1430
1436
|
fileURLToPath(new URL(`../assets/${option.assetFile}`, import.meta.url)),
|
|
1431
|
-
path.resolve(process.cwd(), `templates/
|
|
1437
|
+
path.resolve(process.cwd(), `templates/vidfarm_template_0000/assets/${option.assetFile}`),
|
|
1432
1438
|
];
|
|
1433
1439
|
for (const candidate of candidates) {
|
|
1434
1440
|
if (existsSync(candidate)) {
|
|
@@ -190,6 +190,13 @@ export const template0000Definition = defineTemplate({
|
|
|
190
190
|
visual_dna: templateVisualDna,
|
|
191
191
|
preview_media: [...TEMPLATE_PREVIEW_MEDIA],
|
|
192
192
|
link_to_original: templateLinkToOriginal,
|
|
193
|
+
proposal_generator:
|
|
194
|
+
"Given the director's product, customer, and angle, propose multiple slideshow concepts that fit this template's creator-native TikTok format. Return concrete post ideas that can either be used directly as single prompts or expanded into 10+ related prompts. Favor specific hooks, clear educational angles, founder stories, product proof, objections, comparisons, and repeatable series ideas.",
|
|
195
|
+
sample_prompts: [
|
|
196
|
+
"Make a 3-slide TikTok slideshow explaining why cold plunges feel so hard at first but get easier after a week. Use exact overlay text for each slide and generate matching cinematic background prompts.",
|
|
197
|
+
"Create a founder-story slideshow about launching a niche coffee brand, with one hook slide, one proof slide, and one CTA slide. Keep the tone creator-native and direct.",
|
|
198
|
+
"Build a product explainer slideshow for a posture-correcting desk setup. I want concise overlays, clean visual prompts, and output ready for optional Remotion rendering."
|
|
199
|
+
],
|
|
193
200
|
},
|
|
194
201
|
skillPath,
|
|
195
202
|
configSchema: z.object({
|
|
@@ -1988,7 +1995,7 @@ function resolveRemotionEntryPoint() {
|
|
|
1988
1995
|
function resolveTemplateRuntimeConfig() {
|
|
1989
1996
|
const candidates = [
|
|
1990
1997
|
fileURLToPath(new URL("../template.config.json", import.meta.url)),
|
|
1991
|
-
path.resolve(process.cwd(), "templates/
|
|
1998
|
+
path.resolve(process.cwd(), "templates/vidfarm_template_0000/template.config.json"),
|
|
1992
1999
|
];
|
|
1993
2000
|
|
|
1994
2001
|
for (const candidate of candidates) {
|
|
@@ -2010,7 +2017,7 @@ function resolveSkillPath() {
|
|
|
2010
2017
|
if (existsSync(builtPath)) {
|
|
2011
2018
|
return builtPath;
|
|
2012
2019
|
}
|
|
2013
|
-
return path.resolve(process.cwd(), "templates/
|
|
2020
|
+
return path.resolve(process.cwd(), "templates/vidfarm_template_0000/SKILL.md");
|
|
2014
2021
|
}
|
|
2015
2022
|
|
|
2016
2023
|
function resolveOverlayFonts() {
|
|
@@ -2022,7 +2029,7 @@ function resolveOverlayFonts() {
|
|
|
2022
2029
|
),
|
|
2023
2030
|
path.resolve(
|
|
2024
2031
|
process.cwd(),
|
|
2025
|
-
`templates/
|
|
2032
|
+
`templates/vidfarm_template_0000/assets/${option.assetFile}`,
|
|
2026
2033
|
),
|
|
2027
2034
|
];
|
|
2028
2035
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tracer": "solobacterium-moorei-slideshow-001",
|
|
3
|
+
"payload": {
|
|
4
|
+
"slides": [
|
|
5
|
+
{
|
|
6
|
+
"image_prompt": "Cinematic medical illustration of Solobacterium moorei associated with bad breath, dramatic close-up of the back of a human tongue with invisible odor vapor represented as subtle green sulfur haze, magnified bacteria overlay, clean educational health graphic, realistic but not gory, premium editorial lighting, vertical 9:16 composition, no extra text in image",
|
|
7
|
+
"caption": "Solobacterium moorei:\nthe bad-breath bacteria",
|
|
8
|
+
"duration_ms": 3600
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"image_prompt": "Educational biology diagram showing Solobacterium moorei as an anaerobic oral bacterium living in tongue coating biofilm, labeled-style scientific infographic composition without actual readable text, cross-section of tongue surface, low-oxygen pockets, food debris, sulfur gas release, subtle gum irritation cues, clean modern medical illustration, vertical 9:16",
|
|
12
|
+
"caption": "Grows in low-oxygen tongue coating\nFeeds on debris\nReleases sulfur gases that cause odor",
|
|
13
|
+
"duration_ms": 4200
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"image_prompt": "Healthy dental hygiene scene, child brushing teeth carefully while also brushing the tongue, foamy toothpaste, parent-supervised bathroom routine, clear action shot, fresh clean morning light, visible plaque-removal motion, optimistic health advertising style, vertical 9:16",
|
|
17
|
+
"caption": "Fight it with thorough brushing:\nteeth + tongue, twice a day",
|
|
18
|
+
"duration_ms": 3800
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"image_prompt": "Mobile app promotion scene for Ticko: Kids Toothbrush Timer, cheerful bathroom setting, child using a smartphone timer while brushing teeth, playful clean dental-care branding, app badge style layout areas for App Store and Google Play callouts, feature the provided Ticko logo prominently on screen and in the environment, bright family-friendly colors, polished ad creative, vertical 9:16, no extra text beyond the preserved overlay caption",
|
|
22
|
+
"image_prompt_attachments": [
|
|
23
|
+
"https://play-lh.googleusercontent.com/DLb-Dx0aKT7iJSZ8F1jy2pQzZWeODE4L7pOkl1E6RXoSZ2FkQ1YLsFXMtCK1ddnxUvl5sXegkvEkiTxJlND_3g=s96-rw"
|
|
24
|
+
],
|
|
25
|
+
"caption": "Keep brushing fun with Ticko:\nKids Toothbrush Timer\nDownload on iOS + Android",
|
|
26
|
+
"duration_ms": 4200
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"meta_details_prompt": "Create native TikTok metadata for a US parent and family-health audience. Keep it educational, clear, and non-alarmist. Mention that Solobacterium moorei is linked to bad breath, that brushing the tongue helps, and end with a soft CTA for Ticko: Kids Toothbrush Timer. Avoid medical overclaiming."
|
|
30
|
+
}
|
|
31
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/templates/{template_0000 → vidfarm_template_0000}/assets/DMSerifDisplay-Regular.ttf
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/{dist/templates/template_0000 → templates/vidfarm_template_0000}/src/remotion/Root.js
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|