@mevdragon/vidfarm-devcli 0.2.8 → 0.2.10

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 -26
  3. package/README.md +3 -0
  4. package/SKILL.developer.md +419 -8
  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 +6627 -244
  14. package/dist/src/cli.js +386 -26
  15. package/dist/src/config.js +12 -2
  16. package/dist/src/context.js +35 -35
  17. package/dist/src/db.js +1016 -53
  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 -371
  32. package/dist/src/lib/template-style-options.js +95 -15
  33. package/dist/src/page-runtime.js +1 -0
  34. package/dist/src/page-shell.js +941 -0
  35. package/dist/src/primitive-context.js +163 -0
  36. package/dist/src/primitive-registry.js +340 -0
  37. package/dist/src/primitive-sdk.js +3 -0
  38. package/dist/src/primitives/remotion/html-image.js +28 -0
  39. package/dist/src/react-page-shell.js +34 -0
  40. package/dist/src/ready-post-schedule-component.js +1514 -0
  41. package/dist/src/registry.js +36 -17
  42. package/dist/src/runtime.js +6 -1
  43. package/dist/src/services/api-call-history.js +245 -0
  44. package/dist/src/services/auth.js +25 -3
  45. package/dist/src/services/billing.js +248 -9
  46. package/dist/src/services/chat-threads.js +88 -0
  47. package/dist/src/services/job-logs.js +14 -7
  48. package/dist/src/services/jobs.js +13 -2
  49. package/dist/src/services/providers.js +552 -133
  50. package/dist/src/services/rate-limits.js +236 -0
  51. package/dist/src/services/remotion.js +143 -16
  52. package/dist/src/services/storage.js +10 -8
  53. package/dist/src/services/template-certification.js +11 -2
  54. package/dist/src/services/template-loader.js +3 -1
  55. package/dist/src/services/template-sources.js +68 -3
  56. package/dist/src/template-editor-pages.js +2309 -0
  57. package/dist/src/template-editor-shell.js +1507 -0
  58. package/dist/src/worker.js +120 -22
  59. package/package.json +27 -4
  60. package/public/assets/homepage-app.js +54 -0
  61. package/public/assets/homepage-client-app.js +54 -0
  62. package/public/assets/page-runtime-client-app.js +68 -0
  63. package/dist/templates/vidfarm_template_0000/src/lib/images.js +0 -202
  64. package/dist/templates/vidfarm_template_0000/src/remotion/Root.js +0 -34
  65. package/dist/templates/vidfarm_template_0000/src/remotion/index.js +0 -3
  66. package/dist/templates/vidfarm_template_0000/src/sdk.js +0 -3
  67. package/dist/templates/vidfarm_template_0000/src/style-options.js +0 -51
  68. package/dist/templates/vidfarm_template_0000/src/template-dna.js +0 -9
  69. package/dist/templates/vidfarm_template_0000/src/template.js +0 -1447
  70. package/templates/vidfarm_template_0000/README.md +0 -100
  71. package/templates/vidfarm_template_0000/SKILL.md +0 -225
  72. package/templates/vidfarm_template_0000/assets/Abel-Regular.ttf +0 -0
  73. package/templates/vidfarm_template_0000/assets/DMSerifDisplay-Regular.ttf +0 -0
  74. package/templates/vidfarm_template_0000/assets/Montserrat[wght].ttf +0 -0
  75. package/templates/vidfarm_template_0000/assets/SourceCodePro[wght].ttf +0 -0
  76. package/templates/vidfarm_template_0000/assets/TikTokSans-SemiBold.ttf +0 -0
  77. package/templates/vidfarm_template_0000/assets/Yesteryear-Regular.ttf +0 -0
  78. package/templates/vidfarm_template_0000/composition.json +0 -11
  79. package/templates/vidfarm_template_0000/package.json +0 -27
  80. package/templates/vidfarm_template_0000/research/preview/.gitkeep +0 -1
  81. package/templates/vidfarm_template_0000/research/source_notes.md +0 -7
  82. package/templates/vidfarm_template_0000/src/lib/images.js +0 -202
  83. package/templates/vidfarm_template_0000/src/lib/images.ts +0 -241
  84. package/templates/vidfarm_template_0000/src/remotion/Root.js +0 -33
  85. package/templates/vidfarm_template_0000/src/remotion/Root.tsx +0 -75
  86. package/templates/vidfarm_template_0000/src/remotion/index.js +0 -3
  87. package/templates/vidfarm_template_0000/src/remotion/index.tsx +0 -4
  88. package/templates/vidfarm_template_0000/src/sdk.js +0 -3
  89. package/templates/vidfarm_template_0000/src/sdk.ts +0 -140
  90. package/templates/vidfarm_template_0000/src/style-options.js +0 -51
  91. package/templates/vidfarm_template_0000/src/style-options.ts +0 -60
  92. package/templates/vidfarm_template_0000/src/template-dna.js +0 -9
  93. package/templates/vidfarm_template_0000/src/template-dna.ts +0 -15
  94. package/templates/vidfarm_template_0000/src/template.js +0 -1447
  95. package/templates/vidfarm_template_0000/src/template.ts +0 -2049
  96. package/templates/vidfarm_template_0000/template.config.json +0 -21
  97. package/templates/vidfarm_template_0000/tmp/solobacterium-moorei-slideshow.request.json +0 -31
  98. package/templates/vidfarm_template_0000/tsconfig.json +0 -19
package/dist/src/cli.js CHANGED
@@ -7,7 +7,7 @@ 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);
@@ -57,6 +57,10 @@ async function main() {
57
57
  await runGenerateTemplateCommand(process.argv.slice(3));
58
58
  return;
59
59
  }
60
+ if (command === "autocreate-template" || command === "autocreate") {
61
+ await runAutocreateTemplateCommand(process.argv.slice(3));
62
+ return;
63
+ }
60
64
  if (command === "copy-reference-template") {
61
65
  await runCopyReferenceTemplateCommand(process.argv.slice(3));
62
66
  return;
@@ -128,7 +132,8 @@ async function runDevCommand(argv) {
128
132
  email: parsed.values.email,
129
133
  name: parsed.values.name,
130
134
  isDeveloper: true,
131
- isPaidPlan: true
135
+ isPaidPlan: true,
136
+ planTier: "standard"
132
137
  });
133
138
  const rawApiKey = parsed.values["api-key"] ?? `vf_local_${Math.random().toString(36).slice(2, 12)}`;
134
139
  database.insertApiKey({
@@ -298,9 +303,28 @@ async function runGenerateTemplateCommand(argv) {
298
303
  force: { type: "boolean", default: false }
299
304
  }
300
305
  });
301
- const templateId = parsed.values["template-id"] ?? randomUUID();
302
- const slugId = parsed.values["slug-id"];
303
- 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;
304
328
  if (!slugId || !templateDir) {
305
329
  throw new Error("generate-template requires --slug-id and --template-dir. --template-id is optional and defaults to a new UUIDv4.");
306
330
  }
@@ -313,7 +337,7 @@ async function runGenerateTemplateCommand(argv) {
313
337
  throw new Error(`Starter template not found: ${starterDir}`);
314
338
  }
315
339
  if (existsSync(destinationDir)) {
316
- if (!parsed.values.force) {
340
+ if (!input.force) {
317
341
  throw new Error(`Destination already exists: ${destinationDir}`);
318
342
  }
319
343
  rmSync(destinationDir, { recursive: true, force: true });
@@ -325,9 +349,9 @@ async function runGenerateTemplateCommand(argv) {
325
349
  return ![".git", "node_modules", "dist", "package-lock.json"].includes(name);
326
350
  }
327
351
  });
328
- const projectName = parsed.values["project-name"] ?? toProjectName(destinationFolderName);
329
- const siteName = parsed.values["site-name"] ?? projectName.replace(/_/g, "-");
330
- 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}`;
331
355
  rewriteTemplateStarter(destinationDir, [
332
356
  ["mevdragon/vidfarm_template_0000", githubRepo],
333
357
  ["vidfarm_template_0000", projectName],
@@ -335,35 +359,35 @@ async function runGenerateTemplateCommand(argv) {
335
359
  ["template_0000", slugId],
336
360
  ["template-0000", slugId.replace(/_/g, "-")],
337
361
  ["4c7a7e1a-7f35-4f30-9f86-9c8a63c7f2db", templateId],
338
- ['"production"', JSON.stringify(parsed.values.branch)]
362
+ ['"production"', JSON.stringify(input.branch ?? "production")]
339
363
  ]);
340
364
  const stagedInputs = stageTemplateDnaInputs({
341
365
  templateDir: destinationDir,
342
- sourceNotesPath: parsed.values["source-notes-path"],
343
- sourcePreviewDir: parsed.values["source-preview-dir"]
366
+ sourceNotesPath: input.sourceNotesPath,
367
+ sourcePreviewDir: input.sourcePreviewDir
344
368
  });
345
369
  const moduleSync = syncTemplateDnaModule({
346
370
  templateDir: destinationDir,
347
- linkToOriginal: parsed.values["link-to-original"]
371
+ linkToOriginal: input.linkToOriginal
348
372
  });
349
373
  const dnaRuns = [];
350
- if (!parsed.values["skip-dna-analysis"] && hasTemplatePreviewMedia(destinationDir)) {
374
+ if (!input.skipDnaAnalysis && hasTemplatePreviewMedia(destinationDir)) {
351
375
  dnaRuns.push(await analyzeTemplateDna({
352
376
  mode: "viral",
353
377
  templateDir: destinationDir,
354
- envFile: parsed.values["dna-env-file"],
355
- model: parsed.values["dna-model"],
356
- linkToOriginal: parsed.values["link-to-original"]
378
+ envFile: input.dnaEnvFile ?? ".env",
379
+ model: input.dnaModel,
380
+ linkToOriginal: input.linkToOriginal
357
381
  }));
358
382
  dnaRuns.push(await analyzeTemplateDna({
359
383
  mode: "visual",
360
384
  templateDir: destinationDir,
361
- envFile: parsed.values["dna-env-file"],
362
- model: parsed.values["dna-model"],
363
- linkToOriginal: parsed.values["link-to-original"]
385
+ envFile: input.dnaEnvFile ?? ".env",
386
+ model: input.dnaModel,
387
+ linkToOriginal: input.linkToOriginal
364
388
  }));
365
389
  }
366
- console.log(JSON.stringify({
390
+ return {
367
391
  template_id: templateId,
368
392
  slug_id: slugId,
369
393
  template_dir: destinationDir,
@@ -385,6 +409,7 @@ async function runGenerateTemplateCommand(argv) {
385
409
  "about.preview_media",
386
410
  "about.link_to_original",
387
411
  "about.proposal_generator",
412
+ "about.skeleton_prompt",
388
413
  "about.sample_prompts",
389
414
  "configSchema",
390
415
  ],
@@ -400,11 +425,123 @@ async function runGenerateTemplateCommand(argv) {
400
425
  },
401
426
  next_steps: [
402
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.",
403
429
  "Review src/template.ts and replace the starter sample_prompts with prompts that fit the new format.",
404
430
  "If DNA analysis was skipped or the source research changed, run analyze-viral-dna and analyze-visual-dna.",
405
431
  "Update SKILL.md so the usage examples match the template's real operations and payloads.",
406
432
  `Run validate-template against ${slugId} and launch at least one real local job before treating the template as ready.`,
407
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}.`
408
545
  }, null, 2));
409
546
  }
410
547
  async function runRegisterSourceProdCommand(argv) {
@@ -574,6 +711,7 @@ async function runCopyReferenceTemplateCommand(argv) {
574
711
  "about.preview_media",
575
712
  "about.link_to_original",
576
713
  "about.proposal_generator",
714
+ "about.skeleton_prompt",
577
715
  "about.sample_prompts",
578
716
  "configSchema",
579
717
  ],
@@ -589,7 +727,7 @@ async function runCopyReferenceTemplateCommand(argv) {
589
727
  },
590
728
  next_steps: [
591
729
  "Treat src/template.ts as the canonical entrypoint and review the required about metadata before copying patterns into a new template.",
592
- "Use the starter proposal_generator and sample_prompts as placeholders only; rewrite them for the real format you are building.",
730
+ "Use the starter proposal_generator, skeleton_prompt, and sample_prompts as placeholders only; rewrite them for the real format you are building.",
593
731
  "Keep the template-local SKILL.md aligned with the operations and payloads in code.",
594
732
  ],
595
733
  }, null, 2));
@@ -716,9 +854,18 @@ async function runSessionCommand(argv) {
716
854
  base_url: session.baseUrl,
717
855
  email: session.email,
718
856
  starter_style_options: {
857
+ caption_standard_version: CAPTION_STANDARD_VERSION,
719
858
  fonts: STARTER_TEMPLATE_FONT_OPTIONS,
720
859
  text_background_colors: STARTER_TEMPLATE_TEXT_BACKGROUND_COLOR_OPTIONS,
721
- starter_template_file: "templates/vidfarm_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"
722
869
  },
723
870
  headers: {
724
871
  "vidfarm-api-key": session.apiKey,
@@ -754,8 +901,7 @@ function seedEnvProviderKeys(input) {
754
901
  customerId: input.customerId,
755
902
  provider: entry.provider,
756
903
  label: "Local CLI env key",
757
- encryptedSecret: input.encryptString(secret, input.encryptionSecret),
758
- weight: 1
904
+ encryptedSecret: input.encryptString(secret, input.encryptionSecret)
759
905
  });
760
906
  }
761
907
  }
@@ -806,7 +952,9 @@ function toProjectName(folderName) {
806
952
  function resolveStarterTemplateDir() {
807
953
  const candidates = [
808
954
  path.resolve(import.meta.dirname, "..", "templates", "vidfarm_template_0000"),
809
- 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")
810
958
  ];
811
959
  for (const candidate of candidates) {
812
960
  if (existsSync(candidate)) {
@@ -815,6 +963,218 @@ function resolveStarterTemplateDir() {
815
963
  }
816
964
  return candidates[0];
817
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
+ }
818
1178
  function parseProdTemplateCommandArgs(argv, defaults) {
819
1179
  const parsed = parseArgs({
820
1180
  args: argv,
@@ -12,7 +12,7 @@ const schema = z.object({
12
12
  API_KEY_SALT: z.string().default("development-api-key-salt"),
13
13
  WORKER_POLL_MS: z.coerce.number().default(1500),
14
14
  WORKER_BATCH_SIZE: z.coerce.number().default(2),
15
- WORKER_MAX_CONCURRENT_JOBS: z.coerce.number().default(1),
15
+ WORKER_MAX_CONCURRENT_JOBS: z.coerce.number().default(2),
16
16
  WEBHOOK_SECRET: z.string().default("development-webhook-secret"),
17
17
  DEFAULT_JOB_DELAY_SECONDS: z.coerce.number().default(20),
18
18
  MAX_PENDING_JOBS_GLOBAL: z.coerce.number().default(0),
@@ -25,8 +25,16 @@ const schema = z.object({
25
25
  AWS_ACCESS_KEY_ID: z.string().optional(),
26
26
  AWS_SECRET_ACCESS_KEY: z.string().optional(),
27
27
  PUBLIC_BASE_URL: z.string().optional(),
28
+ PUBLIC_EDITOR_CHAT_URL: z.string().url().optional(),
29
+ EDITOR_CHAT_URL: z.string().url().optional(),
28
30
  RESEND_API_KEY: z.string().optional(),
29
31
  RESEND_FROM_EMAIL: z.string().default("vidfarm@fwd.zoomgtm.com"),
32
+ FLOCKPOSTER_API_URL: z.string().url().optional(),
33
+ VIDFARM_ZIP_LAMBDA_URL: z.string().url().optional(),
34
+ VIDFARM_ZIP_LAMBDA_SECRET: z.string().optional(),
35
+ VIDFARM_BILLING_URL: z.string().url().optional(),
36
+ VIDFARM_BILLING_SECRET: z.string().optional(),
37
+ VIDFARM_MIN_RENDER_WALLET_BALANCE_USD: z.coerce.number().default(1),
30
38
  OPENAI_API_KEY: z.string().optional(),
31
39
  OPENROUTER_API_KEY: z.string().optional(),
32
40
  GEMINI_API_KEY: z.string().optional(),
@@ -46,7 +54,8 @@ const schema = z.object({
46
54
  VIDFARM_ADMIN_EMAILS: z.string().default(""),
47
55
  VIDFARM_GITHUB_TOKEN: z.string().optional(),
48
56
  GITHUB_TOKEN: z.string().optional(),
49
- TEMPLATE_SOURCE_ROOT: z.string().default("./data/template-sources")
57
+ TEMPLATE_SOURCE_ROOT: z.string().default("./data/template-sources"),
58
+ VIDFARM_RATE_LIMITS_TABLE_NAME: z.string().optional()
50
59
  });
51
60
  const parsed = schema.parse(process.env);
52
61
  const dataDir = path.resolve(parsed.VIDFARM_DATA_DIR);
@@ -65,6 +74,7 @@ export const config = {
65
74
  mockProviders: parsed.MOCK_PROVIDER_RESPONSES === "true" ||
66
75
  (parsed.MOCK_PROVIDER_RESPONSES == null && parsed.NODE_ENV !== "production"),
67
76
  s3PublicRead: parsed.AWS_S3_PUBLIC_READ === "true",
77
+ minRenderWalletBalanceUsd: parsed.VIDFARM_MIN_RENDER_WALLET_BALANCE_USD,
68
78
  adminEmails: parsed.VIDFARM_ADMIN_EMAILS
69
79
  .split(",")
70
80
  .map((value) => value.trim().toLowerCase())