@mevdragon/vidfarm-devcli 0.2.7 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/.env.example +9 -0
  2. package/GETTING_STARTED.developers.md +1 -30
  3. package/README.md +22 -5
  4. package/SKILL.developer.md +464 -12
  5. package/auto-create-templates/AUTO_CREATE_TEMPLATES.md +475 -0
  6. package/auto-create-templates/extractor-system-prompt.md +113 -0
  7. package/auto-create-templates/production-graph.schema.json +276 -0
  8. package/auto-create-templates/runbook.md +140 -0
  9. package/auto-create-templates/template-plan.schema.json +230 -0
  10. package/auto-create-templates/template-planner-prompt.md +75 -0
  11. package/dist/src/account-pages-legacy.js +9064 -0
  12. package/dist/src/account-pages.js +41 -620
  13. package/dist/src/app.js +7173 -494
  14. package/dist/src/cli.js +525 -29
  15. package/dist/src/config.js +14 -7
  16. package/dist/src/context.js +51 -35
  17. package/dist/src/db.js +1049 -55
  18. package/dist/src/dev-app-legacy.js +693 -0
  19. package/dist/src/dev-app.js +4 -904
  20. package/dist/src/domain.js +1 -1
  21. package/dist/src/editor-chat-history.js +72 -0
  22. package/dist/src/editor-chat.js +202 -0
  23. package/dist/src/frontend/flockposter-cache-store.js +116 -0
  24. package/dist/src/frontend/homepage-client.js +114 -0
  25. package/dist/src/frontend/homepage-shared.js +3 -0
  26. package/dist/src/frontend/homepage-store.js +27 -0
  27. package/dist/src/frontend/homepage-view.js +147 -0
  28. package/dist/src/frontend/page-runtime-client.js +100 -0
  29. package/dist/src/frontend/page-runtime-store.js +8 -0
  30. package/dist/src/frontend/template-editor-chat.js +2261 -0
  31. package/dist/src/homepage.js +695 -339
  32. package/dist/src/lib/template-paths.js +10 -4
  33. package/dist/src/lib/template-style-options.js +95 -15
  34. package/dist/src/page-runtime.js +1 -0
  35. package/dist/src/page-shell.js +941 -0
  36. package/dist/src/primitive-context.js +163 -0
  37. package/dist/src/primitive-registry.js +340 -0
  38. package/dist/src/primitive-sdk.js +3 -0
  39. package/dist/src/primitives/remotion/html-image.js +28 -0
  40. package/dist/src/react-page-shell.js +34 -0
  41. package/dist/src/ready-post-schedule-component.js +1514 -0
  42. package/dist/src/registry.js +44 -18
  43. package/dist/src/runtime.js +6 -1
  44. package/dist/src/services/api-call-history.js +245 -0
  45. package/dist/src/services/auth.js +27 -9
  46. package/dist/src/services/billing.js +248 -9
  47. package/dist/src/services/chat-threads.js +88 -0
  48. package/dist/src/services/job-logs.js +10 -3
  49. package/dist/src/services/jobs.js +13 -2
  50. package/dist/src/services/providers.js +944 -55
  51. package/dist/src/services/rate-limits.js +236 -0
  52. package/dist/src/services/remotion.js +143 -16
  53. package/dist/src/services/storage.js +23 -8
  54. package/dist/src/services/template-certification.js +26 -3
  55. package/dist/src/services/template-loader.js +19 -1
  56. package/dist/src/services/template-sources.js +316 -7
  57. package/dist/src/template-editor-pages.js +2309 -0
  58. package/dist/src/template-editor-shell.js +1507 -0
  59. package/dist/src/worker.js +120 -22
  60. package/package.json +30 -6
  61. package/public/assets/homepage-app.js +54 -0
  62. package/public/assets/homepage-client-app.js +54 -0
  63. package/public/assets/page-runtime-client-app.js +68 -0
  64. package/dist/templates/template_0000/src/lib/images.js +0 -202
  65. package/dist/templates/template_0000/src/remotion/Root.js +0 -33
  66. package/dist/templates/template_0000/src/remotion/index.js +0 -3
  67. package/dist/templates/template_0000/src/sdk.js +0 -3
  68. package/dist/templates/template_0000/src/style-options.js +0 -51
  69. package/dist/templates/template_0000/src/template-dna.js +0 -9
  70. package/dist/templates/template_0000/src/template.js +0 -1441
  71. package/templates/template_0000/README.md +0 -77
  72. package/templates/template_0000/SKILL.md +0 -225
  73. package/templates/template_0000/assets/Abel-Regular.ttf +0 -0
  74. package/templates/template_0000/assets/DMSerifDisplay-Regular.ttf +0 -0
  75. package/templates/template_0000/assets/Montserrat[wght].ttf +0 -0
  76. package/templates/template_0000/assets/SourceCodePro[wght].ttf +0 -0
  77. package/templates/template_0000/assets/TikTokSans-SemiBold.ttf +0 -0
  78. package/templates/template_0000/assets/Yesteryear-Regular.ttf +0 -0
  79. package/templates/template_0000/composition.json +0 -11
  80. package/templates/template_0000/package.json +0 -28
  81. package/templates/template_0000/research/preview/.gitkeep +0 -1
  82. package/templates/template_0000/research/source_notes.md +0 -7
  83. package/templates/template_0000/src/lib/images.js +0 -202
  84. package/templates/template_0000/src/lib/images.ts +0 -241
  85. package/templates/template_0000/src/remotion/Root.js +0 -33
  86. package/templates/template_0000/src/remotion/Root.tsx +0 -75
  87. package/templates/template_0000/src/remotion/index.js +0 -3
  88. package/templates/template_0000/src/remotion/index.tsx +0 -4
  89. package/templates/template_0000/src/sdk.js +0 -3
  90. package/templates/template_0000/src/sdk.ts +0 -122
  91. package/templates/template_0000/src/style-options.js +0 -51
  92. package/templates/template_0000/src/style-options.ts +0 -60
  93. package/templates/template_0000/src/template-dna.js +0 -9
  94. package/templates/template_0000/src/template-dna.ts +0 -15
  95. package/templates/template_0000/src/template.js +0 -1441
  96. package/templates/template_0000/src/template.ts +0 -2042
  97. package/templates/template_0000/template.config.json +0 -21
  98. package/templates/template_0000/tsconfig.json +0 -19
package/dist/src/cli.js CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
3
3
  import { execFile } from "node:child_process";
4
- import { randomUUID } from "node:crypto";
4
+ import { createHash, randomUUID } from "node:crypto";
5
5
  import os from "node:os";
6
6
  import path from "node:path";
7
7
  import { parseArgs } from "node:util";
8
8
  import { promisify } from "node:util";
9
9
  import dotenv from "dotenv";
10
- import { STARTER_TEMPLATE_FONT_OPTIONS, STARTER_TEMPLATE_TEXT_BACKGROUND_COLOR_OPTIONS } from "./lib/template-style-options.js";
10
+ import { CAPTION_STANDARD_VERSION, STARTER_TEMPLATE_FONT_OPTIONS, STARTER_TEMPLATE_TEXT_BACKGROUND_COLOR_OPTIONS, TIKTOK_CAPTION_STYLE_PRESETS } from "./lib/template-style-options.js";
11
11
  import { analyzeTemplateDna, hasTemplatePreviewMedia, stageTemplateDnaInputs, syncTemplateDnaModule } from "./lib/template-dna.js";
12
12
  import { assertTemplateFolderNameHasPrefix, defaultSkillPathForTemplateModule, deriveTemplateRootDirFromModulePath } from "./lib/template-paths.js";
13
13
  const execFileAsync = promisify(execFile);
@@ -41,6 +41,10 @@ async function main() {
41
41
  await runRegisterSourceHostedCommand(process.argv.slice(3));
42
42
  return;
43
43
  }
44
+ if (command === "publish-update-hosted") {
45
+ await runPublishUpdateHostedCommand(process.argv.slice(3));
46
+ return;
47
+ }
44
48
  if (command === "register-source-prod") {
45
49
  await runRegisterSourceProdCommand(process.argv.slice(3));
46
50
  return;
@@ -53,6 +57,10 @@ async function main() {
53
57
  await runGenerateTemplateCommand(process.argv.slice(3));
54
58
  return;
55
59
  }
60
+ if (command === "autocreate-template" || command === "autocreate") {
61
+ await runAutocreateTemplateCommand(process.argv.slice(3));
62
+ return;
63
+ }
56
64
  if (command === "copy-reference-template") {
57
65
  await runCopyReferenceTemplateCommand(process.argv.slice(3));
58
66
  return;
@@ -124,7 +132,8 @@ async function runDevCommand(argv) {
124
132
  email: parsed.values.email,
125
133
  name: parsed.values.name,
126
134
  isDeveloper: true,
127
- isPaidPlan: true
135
+ isPaidPlan: true,
136
+ planTier: "standard"
128
137
  });
129
138
  const rawApiKey = parsed.values["api-key"] ?? `vf_local_${Math.random().toString(36).slice(2, 12)}`;
130
139
  database.insertApiKey({
@@ -294,9 +303,28 @@ async function runGenerateTemplateCommand(argv) {
294
303
  force: { type: "boolean", default: false }
295
304
  }
296
305
  });
297
- const templateId = parsed.values["template-id"] ?? randomUUID();
298
- const slugId = parsed.values["slug-id"];
299
- const templateDir = parsed.values["template-dir"];
306
+ const result = await generateTemplateFromStarter({
307
+ templateId: parsed.values["template-id"],
308
+ slugId: parsed.values["slug-id"],
309
+ templateDir: parsed.values["template-dir"],
310
+ projectName: parsed.values["project-name"],
311
+ githubRepo: parsed.values["github-repo"],
312
+ siteName: parsed.values["site-name"],
313
+ linkToOriginal: parsed.values["link-to-original"],
314
+ sourceNotesPath: parsed.values["source-notes-path"],
315
+ sourcePreviewDir: parsed.values["source-preview-dir"],
316
+ dnaEnvFile: parsed.values["dna-env-file"],
317
+ dnaModel: parsed.values["dna-model"],
318
+ skipDnaAnalysis: parsed.values["skip-dna-analysis"],
319
+ branch: parsed.values.branch,
320
+ force: parsed.values.force
321
+ });
322
+ console.log(JSON.stringify(result, null, 2));
323
+ }
324
+ async function generateTemplateFromStarter(input) {
325
+ const templateId = input.templateId ?? randomUUID();
326
+ const slugId = input.slugId;
327
+ const templateDir = input.templateDir;
300
328
  if (!slugId || !templateDir) {
301
329
  throw new Error("generate-template requires --slug-id and --template-dir. --template-id is optional and defaults to a new UUIDv4.");
302
330
  }
@@ -309,7 +337,7 @@ async function runGenerateTemplateCommand(argv) {
309
337
  throw new Error(`Starter template not found: ${starterDir}`);
310
338
  }
311
339
  if (existsSync(destinationDir)) {
312
- if (!parsed.values.force) {
340
+ if (!input.force) {
313
341
  throw new Error(`Destination already exists: ${destinationDir}`);
314
342
  }
315
343
  rmSync(destinationDir, { recursive: true, force: true });
@@ -321,9 +349,9 @@ async function runGenerateTemplateCommand(argv) {
321
349
  return ![".git", "node_modules", "dist", "package-lock.json"].includes(name);
322
350
  }
323
351
  });
324
- const projectName = parsed.values["project-name"] ?? toProjectName(destinationFolderName);
325
- const siteName = parsed.values["site-name"] ?? projectName.replace(/_/g, "-");
326
- const githubRepo = parsed.values["github-repo"] ?? `mevdragon/${projectName}`;
352
+ const projectName = input.projectName ?? toProjectName(destinationFolderName);
353
+ const siteName = input.siteName ?? projectName.replace(/_/g, "-");
354
+ const githubRepo = input.githubRepo ?? `mevdragon/${projectName}`;
327
355
  rewriteTemplateStarter(destinationDir, [
328
356
  ["mevdragon/vidfarm_template_0000", githubRepo],
329
357
  ["vidfarm_template_0000", projectName],
@@ -331,35 +359,35 @@ async function runGenerateTemplateCommand(argv) {
331
359
  ["template_0000", slugId],
332
360
  ["template-0000", slugId.replace(/_/g, "-")],
333
361
  ["4c7a7e1a-7f35-4f30-9f86-9c8a63c7f2db", templateId],
334
- ['"production"', JSON.stringify(parsed.values.branch)]
362
+ ['"production"', JSON.stringify(input.branch ?? "production")]
335
363
  ]);
336
364
  const stagedInputs = stageTemplateDnaInputs({
337
365
  templateDir: destinationDir,
338
- sourceNotesPath: parsed.values["source-notes-path"],
339
- sourcePreviewDir: parsed.values["source-preview-dir"]
366
+ sourceNotesPath: input.sourceNotesPath,
367
+ sourcePreviewDir: input.sourcePreviewDir
340
368
  });
341
369
  const moduleSync = syncTemplateDnaModule({
342
370
  templateDir: destinationDir,
343
- linkToOriginal: parsed.values["link-to-original"]
371
+ linkToOriginal: input.linkToOriginal
344
372
  });
345
373
  const dnaRuns = [];
346
- if (!parsed.values["skip-dna-analysis"] && hasTemplatePreviewMedia(destinationDir)) {
374
+ if (!input.skipDnaAnalysis && hasTemplatePreviewMedia(destinationDir)) {
347
375
  dnaRuns.push(await analyzeTemplateDna({
348
376
  mode: "viral",
349
377
  templateDir: destinationDir,
350
- envFile: parsed.values["dna-env-file"],
351
- model: parsed.values["dna-model"],
352
- linkToOriginal: parsed.values["link-to-original"]
378
+ envFile: input.dnaEnvFile ?? ".env",
379
+ model: input.dnaModel,
380
+ linkToOriginal: input.linkToOriginal
353
381
  }));
354
382
  dnaRuns.push(await analyzeTemplateDna({
355
383
  mode: "visual",
356
384
  templateDir: destinationDir,
357
- envFile: parsed.values["dna-env-file"],
358
- model: parsed.values["dna-model"],
359
- linkToOriginal: parsed.values["link-to-original"]
385
+ envFile: input.dnaEnvFile ?? ".env",
386
+ model: input.dnaModel,
387
+ linkToOriginal: input.linkToOriginal
360
388
  }));
361
389
  }
362
- console.log(JSON.stringify({
390
+ return {
363
391
  template_id: templateId,
364
392
  slug_id: slugId,
365
393
  template_dir: destinationDir,
@@ -368,7 +396,152 @@ async function runGenerateTemplateCommand(argv) {
368
396
  site_name: siteName,
369
397
  research: stagedInputs,
370
398
  template_dna_module: moduleSync,
371
- dna_analysis_runs: dnaRuns
399
+ dna_analysis_runs: dnaRuns,
400
+ required_template_contract: {
401
+ template_fields: [
402
+ "id",
403
+ "slugId",
404
+ "version",
405
+ "about.title",
406
+ "about.description",
407
+ "about.viral_dna",
408
+ "about.visual_dna",
409
+ "about.preview_media",
410
+ "about.link_to_original",
411
+ "about.proposal_generator",
412
+ "about.skeleton_prompt",
413
+ "about.sample_prompts",
414
+ "configSchema",
415
+ ],
416
+ operation_fields: [
417
+ "operations.<name>.workflow",
418
+ "operations.<name>.smokeTestPayload",
419
+ "jobs.<workflow>",
420
+ ],
421
+ files: [
422
+ "src/template.ts",
423
+ "SKILL.md",
424
+ ],
425
+ },
426
+ next_steps: [
427
+ "Review src/template.ts and replace the starter proposal_generator with template-specific proposal instructions.",
428
+ "Review src/template.ts and replace the starter skeleton_prompt with a fill-in-the-blanks prompt that fits the real format.",
429
+ "Review src/template.ts and replace the starter sample_prompts with prompts that fit the new format.",
430
+ "If DNA analysis was skipped or the source research changed, run analyze-viral-dna and analyze-visual-dna.",
431
+ "Update SKILL.md so the usage examples match the template's real operations and payloads.",
432
+ `Run validate-template against ${slugId} and launch at least one real local job before treating the template as ready.`,
433
+ ],
434
+ };
435
+ }
436
+ async function runAutocreateTemplateCommand(argv) {
437
+ const parsed = parseArgs({
438
+ args: argv,
439
+ allowPositionals: true,
440
+ options: {
441
+ "input-path": { type: "string" },
442
+ "output-path": { type: "string" },
443
+ "run-id": { type: "string" },
444
+ "runs-dir": { type: "string", default: "auto-create-templates/runs" },
445
+ "harness-dir": { type: "string" },
446
+ "slug-id": { type: "string" },
447
+ "template-id": { type: "string" },
448
+ "project-name": { type: "string" },
449
+ "github-repo": { type: "string" },
450
+ "site-name": { type: "string" },
451
+ "link-to-original": { type: "string" },
452
+ "dna-env-file": { type: "string", default: ".env" },
453
+ "dna-model": { type: "string" },
454
+ "skip-dna-analysis": { type: "boolean", default: false },
455
+ "skip-scaffold": { type: "boolean", default: false },
456
+ "skip-media-probes": { type: "boolean", default: false },
457
+ "skip-harness-snapshot": { type: "boolean", default: false },
458
+ branch: { type: "string", default: "production" },
459
+ force: { type: "boolean", default: false }
460
+ }
461
+ });
462
+ const positionalInput = parsed.positionals[0];
463
+ const positionalOutput = parsed.positionals[1];
464
+ const inputPathRaw = parsed.values["input-path"] ?? positionalInput;
465
+ const outputPathRaw = parsed.values["output-path"] ?? positionalOutput;
466
+ if (!inputPathRaw || !outputPathRaw) {
467
+ throw new Error("autocreate-template requires --input-path and --output-path, or positional args: autocreate-template <input_path> <output_path>.");
468
+ }
469
+ const root = process.cwd();
470
+ const inputPath = path.resolve(root, inputPathRaw);
471
+ const outputPath = path.resolve(root, outputPathRaw);
472
+ if (!existsSync(inputPath)) {
473
+ throw new Error(`Input path not found: ${inputPath}`);
474
+ }
475
+ const outputFolderName = path.basename(outputPath);
476
+ assertTemplateFolderNameHasPrefix(outputFolderName);
477
+ const slugId = parsed.values["slug-id"] ?? toSlugId(outputFolderName);
478
+ const runId = parsed.values["run-id"] ?? `${toProjectName(slugId)}-${formatUtcTimestamp(new Date())}`;
479
+ const runsDir = path.resolve(root, parsed.values["runs-dir"]);
480
+ const runDir = path.join(runsDir, runId);
481
+ const sourceDir = path.join(runDir, "source");
482
+ const previewDir = path.join(sourceDir, "preview");
483
+ const harnessDir = parsed.values["harness-dir"]
484
+ ? path.resolve(root, parsed.values["harness-dir"])
485
+ : resolveAutoCreateHarnessDir();
486
+ createAutoCreateRunFolders(runDir);
487
+ stageAutocreateSource({
488
+ inputPath,
489
+ sourceDir,
490
+ previewDir,
491
+ linkToOriginal: parsed.values["link-to-original"]
492
+ });
493
+ const mediaProbe = parsed.values["skip-media-probes"]
494
+ ? { skipped: true, reason: "skip-media-probes was set" }
495
+ : await prepareAutocreateMediaArtifacts({ runDir, previewDir });
496
+ const harnessSnapshot = parsed.values["skip-harness-snapshot"]
497
+ ? null
498
+ : snapshotAutoCreateHarness({ harnessDir, runDir });
499
+ const sourceNotesPath = path.join(sourceDir, "source.md");
500
+ const scaffold = parsed.values["skip-scaffold"]
501
+ ? null
502
+ : await generateTemplateFromStarter({
503
+ templateId: parsed.values["template-id"],
504
+ slugId,
505
+ templateDir: path.relative(root, outputPath),
506
+ projectName: parsed.values["project-name"],
507
+ githubRepo: parsed.values["github-repo"],
508
+ siteName: parsed.values["site-name"],
509
+ linkToOriginal: parsed.values["link-to-original"],
510
+ sourceNotesPath,
511
+ sourcePreviewDir: previewDir,
512
+ dnaEnvFile: parsed.values["dna-env-file"],
513
+ dnaModel: parsed.values["dna-model"],
514
+ skipDnaAnalysis: parsed.values["skip-dna-analysis"],
515
+ branch: parsed.values.branch,
516
+ force: parsed.values.force
517
+ });
518
+ const agentPromptPath = path.join(runDir, "agent_prompt.md");
519
+ const manifest = {
520
+ mode: "autocreate-template",
521
+ harness_entry: path.join(harnessDir, "AUTO_CREATE_TEMPLATES.md"),
522
+ harness_snapshot: harnessSnapshot,
523
+ input_path: inputPath,
524
+ output_path: outputPath,
525
+ run_id: runId,
526
+ run_dir: runDir,
527
+ source_dir: sourceDir,
528
+ source_notes_path: sourceNotesPath,
529
+ source_preview_dir: previewDir,
530
+ slug_id: slugId,
531
+ scaffold,
532
+ media_probe: mediaProbe,
533
+ required_artifacts: {
534
+ viral_steering_and_visual_preservation: path.join(runDir, "dna", "preservation_decision.json"),
535
+ production_graph: path.join(runDir, "extraction", "production_graph.json"),
536
+ template_plan: path.join(runDir, "plan", "template_plan.json")
537
+ }
538
+ };
539
+ writeFileSync(path.join(runDir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n", "utf8");
540
+ writeFileSync(agentPromptPath, renderAutocreateAgentPrompt(manifest), "utf8");
541
+ console.log(JSON.stringify({
542
+ ...manifest,
543
+ agent_prompt_path: agentPromptPath,
544
+ 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}.`
372
545
  }, null, 2));
373
546
  }
374
547
  async function runRegisterSourceProdCommand(argv) {
@@ -402,6 +575,45 @@ async function runRegisterSourceHostedCommand(argv) {
402
575
  next_step: "Platform admin reviews the source, imports a release, approves it, and activates it."
403
576
  }, null, 2));
404
577
  }
578
+ async function runPublishUpdateHostedCommand(argv) {
579
+ const input = parseProdTemplateCommandArgs(argv, {
580
+ envFile: ".env",
581
+ baseUrl: process.env.VIDFARM_BASE_URL ?? "https://vidfarm.cloud.zoomgtm.com",
582
+ activate: false
583
+ });
584
+ if (!input.commitSha) {
585
+ throw new Error("publish-update-hosted requires --commit-sha.");
586
+ }
587
+ const registration = await registerSourceViaApi(input);
588
+ const artifact = await packageTemplateArtifact(input.repoDir, input.commitSha);
589
+ const presign = await postProdTemplateApi(input, `/api/v1/templates/sources/${registration.source.id}/artifacts/presign`, {
590
+ method: "POST",
591
+ body: JSON.stringify({
592
+ commit_sha: input.commitSha
593
+ })
594
+ });
595
+ await uploadArtifactArchive(presign, artifact.buffer);
596
+ const payload = await postProdTemplateApi(input, `/api/v1/templates/sources/${registration.source.id}/publish`, {
597
+ method: "POST",
598
+ body: JSON.stringify({
599
+ commit_sha: input.commitSha,
600
+ artifact_sha256: artifact.sha256
601
+ })
602
+ });
603
+ console.log(JSON.stringify({
604
+ mode: "hosted-publish-update",
605
+ base_url: input.baseUrl,
606
+ source: payload.source,
607
+ release: payload.release,
608
+ artifact: {
609
+ artifact_key: presign.artifact_key,
610
+ artifact_manifest_key: presign.artifact_manifest_key,
611
+ sha256: artifact.sha256
612
+ },
613
+ publish_action: payload.publish_action,
614
+ developer_handoff: payload.developer_handoff ?? null
615
+ }, null, 2));
616
+ }
405
617
  async function runImportSourceProdCommand(argv) {
406
618
  const input = parseProdTemplateCommandArgs(argv, {
407
619
  envFile: ".env.production",
@@ -486,7 +698,38 @@ async function runCopyReferenceTemplateCommand(argv) {
486
698
  template_dir: destinationDir,
487
699
  template_id: "4c7a7e1a-7f35-4f30-9f86-9c8a63c7f2db",
488
700
  slug_id: "template_0000",
489
- rewritten: false
701
+ rewritten: false,
702
+ required_template_contract: {
703
+ template_fields: [
704
+ "id",
705
+ "slugId",
706
+ "version",
707
+ "about.title",
708
+ "about.description",
709
+ "about.viral_dna",
710
+ "about.visual_dna",
711
+ "about.preview_media",
712
+ "about.link_to_original",
713
+ "about.proposal_generator",
714
+ "about.skeleton_prompt",
715
+ "about.sample_prompts",
716
+ "configSchema",
717
+ ],
718
+ operation_fields: [
719
+ "operations.<name>.workflow",
720
+ "operations.<name>.smokeTestPayload",
721
+ "jobs.<workflow>",
722
+ ],
723
+ files: [
724
+ "src/template.ts",
725
+ "SKILL.md",
726
+ ],
727
+ },
728
+ next_steps: [
729
+ "Treat src/template.ts as the canonical entrypoint and review the required about metadata before copying patterns into a new template.",
730
+ "Use the starter proposal_generator, skeleton_prompt, and sample_prompts as placeholders only; rewrite them for the real format you are building.",
731
+ "Keep the template-local SKILL.md aligned with the operations and payloads in code.",
732
+ ],
490
733
  }, null, 2));
491
734
  }
492
735
  async function runAnalyzeTemplateDnaCommand(mode, argv) {
@@ -611,9 +854,18 @@ async function runSessionCommand(argv) {
611
854
  base_url: session.baseUrl,
612
855
  email: session.email,
613
856
  starter_style_options: {
857
+ caption_standard_version: CAPTION_STANDARD_VERSION,
614
858
  fonts: STARTER_TEMPLATE_FONT_OPTIONS,
615
859
  text_background_colors: STARTER_TEMPLATE_TEXT_BACKGROUND_COLOR_OPTIONS,
616
- starter_template_file: "templates/template_0000/src/style-options.ts"
860
+ presets: TIKTOK_CAPTION_STYLE_PRESETS,
861
+ package_dependencies: [
862
+ "@fontsource/tiktok-sans",
863
+ "@fontsource/montserrat",
864
+ "@fontsource/source-code-pro",
865
+ "@fontsource/libre-baskerville"
866
+ ],
867
+ standard_file: "src/lib/template-style-options.ts",
868
+ starter_template_file: "demos/vidfarm_template_0000/src/style-options.ts"
617
869
  },
618
870
  headers: {
619
871
  "vidfarm-api-key": session.apiKey,
@@ -649,8 +901,7 @@ function seedEnvProviderKeys(input) {
649
901
  customerId: input.customerId,
650
902
  provider: entry.provider,
651
903
  label: "Local CLI env key",
652
- encryptedSecret: input.encryptString(secret, input.encryptionSecret),
653
- weight: 1
904
+ encryptedSecret: input.encryptString(secret, input.encryptionSecret)
654
905
  });
655
906
  }
656
907
  }
@@ -700,8 +951,10 @@ function toProjectName(folderName) {
700
951
  }
701
952
  function resolveStarterTemplateDir() {
702
953
  const candidates = [
703
- path.resolve(import.meta.dirname, "..", "templates", "template_0000"),
704
- path.resolve(import.meta.dirname, "..", "..", "templates", "template_0000")
954
+ path.resolve(import.meta.dirname, "..", "templates", "vidfarm_template_0000"),
955
+ path.resolve(import.meta.dirname, "..", "..", "templates", "vidfarm_template_0000"),
956
+ path.resolve(import.meta.dirname, "..", "demos", "vidfarm_template_0000"),
957
+ path.resolve(import.meta.dirname, "..", "..", "demos", "vidfarm_template_0000")
705
958
  ];
706
959
  for (const candidate of candidates) {
707
960
  if (existsSync(candidate)) {
@@ -710,6 +963,218 @@ function resolveStarterTemplateDir() {
710
963
  }
711
964
  return candidates[0];
712
965
  }
966
+ function resolveAutoCreateHarnessDir() {
967
+ const candidates = [
968
+ path.resolve(process.cwd(), "auto-create-templates"),
969
+ path.resolve(import.meta.dirname, "..", "auto-create-templates"),
970
+ path.resolve(import.meta.dirname, "..", "..", "auto-create-templates")
971
+ ];
972
+ for (const candidate of candidates) {
973
+ if (existsSync(path.join(candidate, "AUTO_CREATE_TEMPLATES.md"))) {
974
+ return candidate;
975
+ }
976
+ }
977
+ return candidates[0];
978
+ }
979
+ function createAutoCreateRunFolders(runDir) {
980
+ for (const name of ["source", "frames", "audio", "transcripts", "ocr", "motion", "dna", "extraction", "plan"]) {
981
+ mkdirSync(path.join(runDir, name), { recursive: true });
982
+ }
983
+ }
984
+ function stageAutocreateSource(input) {
985
+ mkdirSync(input.sourceDir, { recursive: true });
986
+ mkdirSync(input.previewDir, { recursive: true });
987
+ const stats = statSync(input.inputPath);
988
+ if (stats.isDirectory()) {
989
+ const inputHadPreviewDir = existsSync(path.join(input.inputPath, "preview"));
990
+ copyDirectoryContentsShallow(input.inputPath, input.sourceDir);
991
+ const existingPreviewDir = path.join(input.sourceDir, "preview");
992
+ if (!inputHadPreviewDir) {
993
+ mkdirSync(existingPreviewDir, { recursive: true });
994
+ for (const mediaFile of listMediaFiles(input.sourceDir)) {
995
+ cpSync(mediaFile, path.join(existingPreviewDir, path.basename(mediaFile)));
996
+ }
997
+ }
998
+ }
999
+ else {
1000
+ cpSync(input.inputPath, path.join(input.previewDir, path.basename(input.inputPath)));
1001
+ }
1002
+ const notesPath = path.join(input.sourceDir, "source.md");
1003
+ if (!existsSync(notesPath)) {
1004
+ writeFileSync(notesPath, [
1005
+ "# Source Notes",
1006
+ "",
1007
+ `- input path: ${input.inputPath}`,
1008
+ `- original format URL: ${input.linkToOriginal ?? ""}`,
1009
+ "- creator/account:",
1010
+ "- product/category being adapted:",
1011
+ "- what appears repeatable:",
1012
+ "- constraints:",
1013
+ ""
1014
+ ].join("\n"), "utf8");
1015
+ }
1016
+ }
1017
+ async function prepareAutocreateMediaArtifacts(input) {
1018
+ const videoFile = listMediaFiles(input.previewDir).find((filePath) => isVideoFile(filePath));
1019
+ if (!videoFile) {
1020
+ return {
1021
+ skipped: true,
1022
+ reason: "No video file found in source preview media.",
1023
+ preview_files: listMediaFiles(input.previewDir)
1024
+ };
1025
+ }
1026
+ const ffprobePath = path.join(input.runDir, "source", "ffprobe.json");
1027
+ const contactSheetPath = path.join(input.runDir, "frames", "contact-sheet.jpg");
1028
+ const audioPath = path.join(input.runDir, "audio", "source.wav");
1029
+ const steps = [];
1030
+ steps.push(await runOptionalLocalCommand("ffprobe", [
1031
+ "-v", "error",
1032
+ "-show_entries", "format=duration:stream=codec_type,width,height,avg_frame_rate",
1033
+ "-of", "json",
1034
+ videoFile
1035
+ ], ffprobePath));
1036
+ steps.push(await runOptionalLocalCommand("ffmpeg", [
1037
+ "-y",
1038
+ "-i", videoFile,
1039
+ "-vf", "fps=1/2,scale=240:-1,tile=3x4",
1040
+ "-frames:v", "1",
1041
+ contactSheetPath
1042
+ ]));
1043
+ steps.push(await runOptionalLocalCommand("ffmpeg", [
1044
+ "-y",
1045
+ "-i", videoFile,
1046
+ "-vn",
1047
+ "-ac", "1",
1048
+ "-ar", "16000",
1049
+ audioPath
1050
+ ]));
1051
+ const result = {
1052
+ skipped: false,
1053
+ video_file: videoFile,
1054
+ ffprobe_path: existsSync(ffprobePath) ? ffprobePath : null,
1055
+ contact_sheet_path: existsSync(contactSheetPath) ? contactSheetPath : null,
1056
+ audio_path: existsSync(audioPath) ? audioPath : null,
1057
+ steps
1058
+ };
1059
+ writeFileSync(path.join(input.runDir, "source", "media_artifacts.json"), JSON.stringify(result, null, 2) + "\n", "utf8");
1060
+ return result;
1061
+ }
1062
+ function snapshotAutoCreateHarness(input) {
1063
+ if (!existsSync(path.join(input.harnessDir, "AUTO_CREATE_TEMPLATES.md"))) {
1064
+ throw new Error(`Auto-create harness not found: ${input.harnessDir}`);
1065
+ }
1066
+ const destinationDir = path.join(input.runDir, "harness");
1067
+ mkdirSync(destinationDir, { recursive: true });
1068
+ for (const fileName of [
1069
+ "AUTO_CREATE_TEMPLATES.md",
1070
+ "runbook.md",
1071
+ "extractor-system-prompt.md",
1072
+ "production-graph.schema.json",
1073
+ "template-planner-prompt.md",
1074
+ "template-plan.schema.json"
1075
+ ]) {
1076
+ const source = path.join(input.harnessDir, fileName);
1077
+ if (existsSync(source)) {
1078
+ cpSync(source, path.join(destinationDir, fileName));
1079
+ }
1080
+ }
1081
+ return {
1082
+ harness_dir: destinationDir,
1083
+ entry: path.join(destinationDir, "AUTO_CREATE_TEMPLATES.md")
1084
+ };
1085
+ }
1086
+ function renderAutocreateAgentPrompt(manifest) {
1087
+ return [
1088
+ "# Vidfarm Autocreate Template Handoff",
1089
+ "",
1090
+ `Use ${manifest.harness_entry} as the harness.`,
1091
+ `Analyze ${manifest.source_dir} as the source format.`,
1092
+ `Use the run artifacts in ${manifest.run_dir}.`,
1093
+ `Output the runnable Vidfarm template to ${manifest.output_path}.`,
1094
+ "",
1095
+ "Required work:",
1096
+ "",
1097
+ "1. Inspect source notes and preview media.",
1098
+ "2. Use or create analyzer artifacts in the run folder.",
1099
+ "3. Run or consume viral and visual DNA analysis.",
1100
+ "4. Create `dna/preservation_decision.json`.",
1101
+ "5. Create `extraction/production_graph.json` matching the production graph schema.",
1102
+ "6. Create `plan/template_plan.json` matching the template plan schema.",
1103
+ "7. Implement the template operations, jobs, metadata, prompts, storage outputs, and template-local `SKILL.md`.",
1104
+ "8. Run `vidfarm-devcli validate-template` and at least one real local job before calling it done.",
1105
+ "",
1106
+ "Manifest:",
1107
+ "",
1108
+ "```json",
1109
+ JSON.stringify(manifest, null, 2),
1110
+ "```",
1111
+ ""
1112
+ ].join("\n");
1113
+ }
1114
+ async function runOptionalLocalCommand(command, args, outputPath) {
1115
+ try {
1116
+ const result = await execFileAsync(command, args, {
1117
+ maxBuffer: 20 * 1024 * 1024
1118
+ });
1119
+ if (outputPath) {
1120
+ writeFileSync(outputPath, result.stdout, "utf8");
1121
+ }
1122
+ return {
1123
+ command,
1124
+ ok: true,
1125
+ output_path: outputPath ?? null,
1126
+ stderr: result.stderr.trim() || null
1127
+ };
1128
+ }
1129
+ catch (error) {
1130
+ return {
1131
+ command,
1132
+ ok: false,
1133
+ output_path: outputPath ?? null,
1134
+ error: error instanceof Error ? error.message : String(error)
1135
+ };
1136
+ }
1137
+ }
1138
+ function copyDirectoryContentsShallow(sourceDir, destinationDir) {
1139
+ mkdirSync(destinationDir, { recursive: true });
1140
+ for (const entry of readdirSync(sourceDir, { withFileTypes: true })) {
1141
+ if (!entry.name.startsWith(".")) {
1142
+ cpSync(path.join(sourceDir, entry.name), path.join(destinationDir, entry.name), { recursive: true });
1143
+ }
1144
+ }
1145
+ }
1146
+ function listMediaFiles(dir) {
1147
+ if (!existsSync(dir)) {
1148
+ return [];
1149
+ }
1150
+ const files = [];
1151
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
1152
+ const filePath = path.join(dir, entry.name);
1153
+ if (entry.name.startsWith(".")) {
1154
+ continue;
1155
+ }
1156
+ if (entry.isDirectory()) {
1157
+ files.push(...listMediaFiles(filePath));
1158
+ continue;
1159
+ }
1160
+ if (entry.isFile() && isMediaFile(filePath)) {
1161
+ files.push(filePath);
1162
+ }
1163
+ }
1164
+ return files.sort((a, b) => a.localeCompare(b));
1165
+ }
1166
+ function isMediaFile(filePath) {
1167
+ return [".png", ".jpg", ".jpeg", ".webp", ".mp4", ".mov", ".webm", ".m4v"].includes(path.extname(filePath).toLowerCase());
1168
+ }
1169
+ function isVideoFile(filePath) {
1170
+ return [".mp4", ".mov", ".webm", ".m4v"].includes(path.extname(filePath).toLowerCase());
1171
+ }
1172
+ function toSlugId(folderName) {
1173
+ return toProjectName(folderName).replace(/^vidfarm_template_/, "template_");
1174
+ }
1175
+ function formatUtcTimestamp(date) {
1176
+ return date.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
1177
+ }
713
1178
  function parseProdTemplateCommandArgs(argv, defaults) {
714
1179
  const parsed = parseArgs({
715
1180
  args: argv,
@@ -723,6 +1188,7 @@ function parseProdTemplateCommandArgs(argv, defaults) {
723
1188
  "install-command": { type: "string", default: "npm install" },
724
1189
  "build-command": { type: "string", default: "npm run build" },
725
1190
  "commit-sha": { type: "string" },
1191
+ "repo-dir": { type: "string", default: "." },
726
1192
  "env-file": { type: "string", default: defaults.envFile },
727
1193
  "base-url": { type: "string", default: defaults.baseUrl },
728
1194
  "api-key": { type: "string" },
@@ -752,6 +1218,7 @@ function parseProdTemplateCommandArgs(argv, defaults) {
752
1218
  installCommand: parsed.values["install-command"],
753
1219
  buildCommand: parsed.values["build-command"],
754
1220
  commitSha: parsed.values["commit-sha"] ?? undefined,
1221
+ repoDir: path.resolve(process.cwd(), parsed.values["repo-dir"]),
755
1222
  envFile: parsed.values["env-file"],
756
1223
  baseUrl: normalizeBaseUrl(parsed.values["base-url"]),
757
1224
  apiKey,
@@ -810,6 +1277,35 @@ async function postProdTemplateApi(input, pathname, init) {
810
1277
  }
811
1278
  return payload;
812
1279
  }
1280
+ async function packageTemplateArtifact(repoDir, commitSha) {
1281
+ const tempDir = mkdtempSync(path.join(os.tmpdir(), "vidfarm-template-artifact-"));
1282
+ const repoRoot = (await execFileAsync("git", ["-C", repoDir, "rev-parse", "--show-toplevel"])).stdout.trim();
1283
+ const artifactPath = path.join(tempDir, "template-source.tar.gz");
1284
+ try {
1285
+ await execFileAsync("git", ["-C", repoRoot, "archive", "--format=tar.gz", `--output=${artifactPath}`, commitSha], {
1286
+ cwd: repoRoot
1287
+ });
1288
+ const buffer = readFileSync(artifactPath);
1289
+ return {
1290
+ buffer,
1291
+ sha256: createHash("sha256").update(buffer).digest("hex")
1292
+ };
1293
+ }
1294
+ finally {
1295
+ rmSync(tempDir, { recursive: true, force: true });
1296
+ }
1297
+ }
1298
+ async function uploadArtifactArchive(presign, buffer) {
1299
+ const response = await fetch(presign.upload.url, {
1300
+ method: presign.upload.method,
1301
+ headers: presign.upload.headers,
1302
+ body: new Uint8Array(buffer)
1303
+ });
1304
+ if (!response.ok) {
1305
+ const body = await response.text().catch(() => "");
1306
+ throw new Error(body || `Artifact upload failed with ${response.status}.`);
1307
+ }
1308
+ }
813
1309
  function loadEnvFile(envFile) {
814
1310
  const resolved = path.resolve(process.cwd(), envFile);
815
1311
  if (!existsSync(resolved)) {