@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.
Files changed (56) hide show
  1. package/GETTING_STARTED.developers.md +14 -18
  2. package/README.md +19 -5
  3. package/SKILL.developer.md +140 -11
  4. package/dist/src/account-pages.js +1 -1
  5. package/dist/src/app.js +305 -9
  6. package/dist/src/cli.js +142 -6
  7. package/dist/src/config.js +2 -5
  8. package/dist/src/context.js +16 -0
  9. package/dist/src/db.js +33 -2
  10. package/dist/src/homepage.js +33 -1
  11. package/dist/src/lib/template-paths.js +10 -4
  12. package/dist/src/registry.js +9 -2
  13. package/dist/src/services/auth.js +2 -6
  14. package/dist/src/services/job-logs.js +4 -4
  15. package/dist/src/services/providers.js +470 -0
  16. package/dist/src/services/storage.js +13 -0
  17. package/dist/src/services/template-certification.js +16 -2
  18. package/dist/src/services/template-loader.js +17 -1
  19. package/dist/src/services/template-sources.js +249 -5
  20. package/{templates/template_0000 → dist/templates/vidfarm_template_0000}/src/remotion/Root.js +1 -0
  21. package/dist/templates/{template_0000 → vidfarm_template_0000}/src/template.js +9 -3
  22. package/package.json +4 -3
  23. package/templates/{template_0000 → vidfarm_template_0000}/README.md +23 -0
  24. package/templates/{template_0000 → vidfarm_template_0000}/package.json +0 -1
  25. package/templates/{template_0000 → vidfarm_template_0000}/src/sdk.ts +18 -0
  26. package/templates/{template_0000 → vidfarm_template_0000}/src/template.js +9 -3
  27. package/templates/{template_0000 → vidfarm_template_0000}/src/template.ts +10 -3
  28. package/templates/vidfarm_template_0000/tmp/solobacterium-moorei-slideshow.request.json +31 -0
  29. /package/dist/templates/{template_0000 → vidfarm_template_0000}/src/lib/images.js +0 -0
  30. /package/dist/templates/{template_0000 → vidfarm_template_0000}/src/remotion/index.js +0 -0
  31. /package/dist/templates/{template_0000 → vidfarm_template_0000}/src/sdk.js +0 -0
  32. /package/dist/templates/{template_0000 → vidfarm_template_0000}/src/style-options.js +0 -0
  33. /package/dist/templates/{template_0000 → vidfarm_template_0000}/src/template-dna.js +0 -0
  34. /package/templates/{template_0000 → vidfarm_template_0000}/SKILL.md +0 -0
  35. /package/templates/{template_0000 → vidfarm_template_0000}/assets/Abel-Regular.ttf +0 -0
  36. /package/templates/{template_0000 → vidfarm_template_0000}/assets/DMSerifDisplay-Regular.ttf +0 -0
  37. /package/templates/{template_0000 → vidfarm_template_0000}/assets/Montserrat[wght].ttf +0 -0
  38. /package/templates/{template_0000 → vidfarm_template_0000}/assets/SourceCodePro[wght].ttf +0 -0
  39. /package/templates/{template_0000 → vidfarm_template_0000}/assets/TikTokSans-SemiBold.ttf +0 -0
  40. /package/templates/{template_0000 → vidfarm_template_0000}/assets/Yesteryear-Regular.ttf +0 -0
  41. /package/templates/{template_0000 → vidfarm_template_0000}/composition.json +0 -0
  42. /package/templates/{template_0000 → vidfarm_template_0000}/research/preview/.gitkeep +0 -0
  43. /package/templates/{template_0000 → vidfarm_template_0000}/research/source_notes.md +0 -0
  44. /package/templates/{template_0000 → vidfarm_template_0000}/src/lib/images.js +0 -0
  45. /package/templates/{template_0000 → vidfarm_template_0000}/src/lib/images.ts +0 -0
  46. /package/{dist/templates/template_0000 → templates/vidfarm_template_0000}/src/remotion/Root.js +0 -0
  47. /package/templates/{template_0000 → vidfarm_template_0000}/src/remotion/Root.tsx +0 -0
  48. /package/templates/{template_0000 → vidfarm_template_0000}/src/remotion/index.js +0 -0
  49. /package/templates/{template_0000 → vidfarm_template_0000}/src/remotion/index.tsx +0 -0
  50. /package/templates/{template_0000 → vidfarm_template_0000}/src/sdk.js +0 -0
  51. /package/templates/{template_0000 → vidfarm_template_0000}/src/style-options.js +0 -0
  52. /package/templates/{template_0000 → vidfarm_template_0000}/src/style-options.ts +0 -0
  53. /package/templates/{template_0000 → vidfarm_template_0000}/src/template-dna.js +0 -0
  54. /package/templates/{template_0000 → vidfarm_template_0000}/src/template-dna.ts +0 -0
  55. /package/templates/{template_0000 → vidfarm_template_0000}/template.config.json +0 -0
  56. /package/templates/{template_0000 → vidfarm_template_0000}/tsconfig.json +0 -0
package/dist/src/app.js CHANGED
@@ -23,6 +23,8 @@ const templateSources = new TemplateSourceService();
23
23
  const API_PREFIX = "/api/v1";
24
24
  const USER_PREFIX = `${API_PREFIX}/user`;
25
25
  const TEMPLATES_PREFIX = `${API_PREFIX}/templates`;
26
+ const ADMIN_TEMPLATES_PREFIX = `${API_PREFIX}/admin/templates`;
27
+ const ADMIN_CUSTOMERS_PREFIX = `${API_PREFIX}/admin/customers`;
26
28
  const SESSION_COOKIE = "vidfarm_session";
27
29
  const app = new Hono();
28
30
  const otpRequestSchema = z.object({
@@ -45,6 +47,20 @@ const adminCreateUserSchema = z.object({
45
47
  password: z.string().min(8),
46
48
  name: z.string().optional()
47
49
  });
50
+ const adminProvisionUserSchema = z.object({
51
+ email: z.string().email().optional(),
52
+ username: z.string().email().optional(),
53
+ password: z.string().min(8),
54
+ name: z.string().optional()
55
+ }).refine((value) => Boolean(value.email || value.username), {
56
+ message: "Provide email or username."
57
+ });
58
+ const adminCustomerAccessSchema = z.object({
59
+ email: z.string().email(),
60
+ is_developer: z.boolean().optional(),
61
+ is_paid_plan: z.boolean().optional(),
62
+ name: z.string().optional()
63
+ }).refine((value) => value.is_developer !== undefined || value.is_paid_plan !== undefined || value.name !== undefined, { message: "Provide at least one of is_developer, is_paid_plan, or name." });
48
64
  const providerKeySchema = z.object({
49
65
  provider: z.enum(["openai", "gemini", "openrouter", "perplexity"]),
50
66
  label: z.string().optional(),
@@ -67,6 +83,13 @@ const developerPreviewPresignSchema = z.object({
67
83
  content_type: z.string().trim().min(1).max(255).optional(),
68
84
  directory: z.string().trim().max(500).optional()
69
85
  });
86
+ const developerTemplateArtifactPresignSchema = z.object({
87
+ commit_sha: z.string().trim().min(7).max(255)
88
+ });
89
+ const developerTemplateArtifactPublishSchema = z.object({
90
+ commit_sha: z.string().trim().min(7).max(255),
91
+ artifact_sha256: z.string().trim().regex(/^[0-9a-f]{64}$/i, "artifact_sha256 must be a SHA-256 hex digest.")
92
+ });
70
93
  const templateSourceStatusSchema = z.enum(["pending_review", "approved", "rejected", "disabled"]);
71
94
  const templateReleaseStatusSchema = z.enum(["imported", "pending_approval", "approved", "rejected", "failed", "active"]);
72
95
  const listJobsQuerySchema = z.object({
@@ -585,9 +608,7 @@ app.post(`${API_PREFIX}/admin/auth/whitelist`, async (c) => {
585
608
  email: normalizedEmail,
586
609
  name: existing?.name ?? null,
587
610
  defaultWebhookUrl: existing?.defaultWebhookUrl ?? null,
588
- isDeveloper: existing?.isDeveloper
589
- || config.adminEmails.includes(normalizedEmail)
590
- || config.developerEmails.includes(normalizedEmail),
611
+ isDeveloper: existing?.isDeveloper || config.adminEmails.includes(normalizedEmail),
591
612
  isPaidPlan: true
592
613
  });
593
614
  });
@@ -608,6 +629,213 @@ app.post(`${API_PREFIX}/admin/auth/users`, async (c) => {
608
629
  });
609
630
  return c.json({ customer }, 201);
610
631
  });
632
+ app.post(`${API_PREFIX}/admin/auth/provision-user-account`, async (c) => {
633
+ try {
634
+ requireSuperagency(c);
635
+ }
636
+ catch (error) {
637
+ return c.json({ error: error instanceof Error ? error.message : "Forbidden" }, 403);
638
+ }
639
+ const parsed = adminProvisionUserSchema.parse(await c.req.json());
640
+ const email = (parsed.email ?? parsed.username ?? "").trim().toLowerCase();
641
+ const existing = database.getCustomerByEmail(email);
642
+ const customer = auth.createPasswordUser({
643
+ email,
644
+ password: parsed.password,
645
+ name: parsed.name ?? null
646
+ });
647
+ const apiKey = getVisibleApiKey(customer.id);
648
+ return c.json({
649
+ customer,
650
+ credentials: {
651
+ email,
652
+ password: parsed.password,
653
+ api_key: apiKey
654
+ },
655
+ action: existing ? "updated" : "created"
656
+ }, existing ? 200 : 201);
657
+ });
658
+ app.post(`${ADMIN_CUSTOMERS_PREFIX}/access`, async (c) => {
659
+ try {
660
+ requireSuperagency(c);
661
+ }
662
+ catch (error) {
663
+ return c.json({ error: error instanceof Error ? error.message : "Forbidden" }, 403);
664
+ }
665
+ const parsed = adminCustomerAccessSchema.parse(await c.req.json());
666
+ const existing = database.getCustomerByEmail(parsed.email);
667
+ const customer = database.setCustomerAccessByEmail({
668
+ email: parsed.email,
669
+ isDeveloper: parsed.is_developer,
670
+ isPaidPlan: parsed.is_paid_plan,
671
+ name: parsed.name
672
+ });
673
+ return c.json({
674
+ customer,
675
+ action: existing ? "updated" : "created"
676
+ }, existing ? 200 : 201);
677
+ });
678
+ app.get(`${ADMIN_TEMPLATES_PREFIX}/sources`, (c) => {
679
+ try {
680
+ requireSuperagency(c);
681
+ }
682
+ catch (error) {
683
+ return c.json({ error: error instanceof Error ? error.message : "Forbidden" }, 403);
684
+ }
685
+ const status = c.req.query("status");
686
+ const parsedStatus = status ? templateSourceStatusSchema.parse(status) : undefined;
687
+ return c.json({ sources: templateSources.listSources(parsedStatus) });
688
+ });
689
+ app.get(`${ADMIN_TEMPLATES_PREFIX}/releases`, (c) => {
690
+ try {
691
+ requireSuperagency(c);
692
+ }
693
+ catch (error) {
694
+ return c.json({ error: error instanceof Error ? error.message : "Forbidden" }, 403);
695
+ }
696
+ const status = c.req.query("status");
697
+ const parsedStatus = status ? templateReleaseStatusSchema.parse(status) : undefined;
698
+ return c.json({
699
+ releases: templateSources.listReleases({
700
+ templateId: c.req.query("template_id") || undefined,
701
+ status: parsedStatus
702
+ })
703
+ });
704
+ });
705
+ app.get(`${ADMIN_TEMPLATES_PREFIX}/review-queue`, (c) => {
706
+ try {
707
+ requireSuperagency(c);
708
+ }
709
+ catch (error) {
710
+ return c.json({ error: error instanceof Error ? error.message : "Forbidden" }, 403);
711
+ }
712
+ return c.json({
713
+ pending_sources: templateSources.listSources("pending_review"),
714
+ pending_releases: templateSources.listReleases({ status: "pending_approval" })
715
+ });
716
+ });
717
+ app.post(`${ADMIN_TEMPLATES_PREFIX}/sources/register`, async (c) => {
718
+ try {
719
+ requireSuperagency(c);
720
+ }
721
+ catch (error) {
722
+ return c.json({ error: error instanceof Error ? error.message : "Forbidden" }, 403);
723
+ }
724
+ try {
725
+ const body = z.object({
726
+ template_id: z.string().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i, "template_id must be a UUIDv4."),
727
+ slug_id: z.string().min(3).regex(/^[a-z0-9_]+$/i, "slug_id must contain only letters, numbers, and underscores."),
728
+ repo_url: z.string().url(),
729
+ branch: z.string().min(1).default("production"),
730
+ template_module_path: z.string().regex(/(^src\/template\.ts$|(^|\/)vidfarm_template_[^/]+\/src\/template\.ts$)/i, "template_module_path must point at src/template.ts for a standalone template repo, or at a template entrypoint inside a folder that starts with vidfarm_template_, for example templates/vidfarm_template_example/src/template.ts."),
731
+ skill_path: z.string().min(1).optional(),
732
+ install_command: z.string().min(1).default("npm install"),
733
+ build_command: z.string().min(1).default("npm run build")
734
+ }).parse(await c.req.json());
735
+ const registration = await templateSources.registerSource({
736
+ templateId: body.template_id,
737
+ slugId: body.slug_id,
738
+ repoUrl: body.repo_url,
739
+ branch: body.branch,
740
+ templateModulePath: body.template_module_path,
741
+ skillPath: body.skill_path,
742
+ installCommand: body.install_command,
743
+ buildCommand: body.build_command
744
+ });
745
+ return c.json({
746
+ source: registration.source,
747
+ registration_action: registration.action,
748
+ sync: registration.sync
749
+ }, registration.action === "created" ? 201 : 200);
750
+ }
751
+ catch (error) {
752
+ if (error instanceof TemplateSourceAlreadyRegisteredError) {
753
+ return c.json({
754
+ error: error.message,
755
+ registration_status: error.existingSource.status,
756
+ existing_source: error.existingSource,
757
+ conflict_field: error.conflictField
758
+ }, 409);
759
+ }
760
+ const message = error instanceof Error ? error.message : "Unable to register template source.";
761
+ const status = /already exists/i.test(message) ? 409 : 400;
762
+ return c.json({ error: message }, status);
763
+ }
764
+ });
765
+ app.post(`${ADMIN_TEMPLATES_PREFIX}/sources/:sourceId/approve`, (c) => {
766
+ try {
767
+ requireSuperagency(c);
768
+ }
769
+ catch (error) {
770
+ return c.json({ error: error instanceof Error ? error.message : "Forbidden" }, 403);
771
+ }
772
+ return c.json({ source: templateSources.approveSource({ sourceId: c.req.param("sourceId") }) });
773
+ });
774
+ app.post(`${ADMIN_TEMPLATES_PREFIX}/sources/:sourceId/reject`, (c) => {
775
+ try {
776
+ requireSuperagency(c);
777
+ }
778
+ catch (error) {
779
+ return c.json({ error: error instanceof Error ? error.message : "Forbidden" }, 403);
780
+ }
781
+ return c.json({ source: templateSources.rejectSource({ sourceId: c.req.param("sourceId") }) });
782
+ });
783
+ app.post(`${ADMIN_TEMPLATES_PREFIX}/sources/:sourceId/import`, async (c) => {
784
+ try {
785
+ requireSuperagency(c);
786
+ }
787
+ catch (error) {
788
+ return c.json({ error: error instanceof Error ? error.message : "Forbidden" }, 403);
789
+ }
790
+ const body = z.object({
791
+ commit_sha: z.string().min(7).optional(),
792
+ artifact_manifest_key: z.string().min(1).optional()
793
+ }).parse(await c.req.json().catch(() => ({})));
794
+ const release = await templateSources.importRelease({
795
+ sourceId: c.req.param("sourceId"),
796
+ commitSha: body.commit_sha ?? null,
797
+ artifactManifestKey: body.artifact_manifest_key ?? null
798
+ });
799
+ return c.json({ release }, 201);
800
+ });
801
+ app.post(`${ADMIN_TEMPLATES_PREFIX}/releases/:releaseId/approve`, (c) => {
802
+ try {
803
+ requireSuperagency(c);
804
+ }
805
+ catch (error) {
806
+ return c.json({ error: error instanceof Error ? error.message : "Forbidden" }, 403);
807
+ }
808
+ return c.json({ release: templateSources.approveRelease({ releaseId: c.req.param("releaseId") }) });
809
+ });
810
+ app.post(`${ADMIN_TEMPLATES_PREFIX}/releases/:releaseId/reject`, (c) => {
811
+ try {
812
+ requireSuperagency(c);
813
+ }
814
+ catch (error) {
815
+ return c.json({ error: error instanceof Error ? error.message : "Forbidden" }, 403);
816
+ }
817
+ return c.json({ release: templateSources.rejectRelease({ releaseId: c.req.param("releaseId") }) });
818
+ });
819
+ app.post(`${ADMIN_TEMPLATES_PREFIX}/releases/:releaseId/activate`, async (c) => {
820
+ try {
821
+ requireSuperagency(c);
822
+ }
823
+ catch (error) {
824
+ return c.json({ error: error instanceof Error ? error.message : "Forbidden" }, 403);
825
+ }
826
+ const { release, template } = await templateSources.activateRelease({
827
+ releaseId: c.req.param("releaseId")
828
+ });
829
+ templateRegistry.registerRuntimeTemplate(template);
830
+ return c.json({
831
+ release,
832
+ template: {
833
+ id: template.id,
834
+ version: template.version,
835
+ description: template.about.description
836
+ }
837
+ });
838
+ });
611
839
  app.post(`${USER_PREFIX}/request-otp`, async (c) => {
612
840
  const body = otpRequestSchema.parse(await c.req.json());
613
841
  await auth.requestOtp(body.email);
@@ -650,7 +878,7 @@ function requireDeveloper(c) {
650
878
  function templatePublishingAdminError() {
651
879
  return {
652
880
  error: "Admin access required.",
653
- detail: "Reviewing, importing, approving, or activating a registered template source is platform-managed. Third-party developers only push to GitHub and register/update the source metadata."
881
+ detail: "Reviewing, importing, approving, or activating a registered template source is platform-managed. Third-party developers can register/update source metadata and publish an exact GitHub commit for review, but only platform admins can approve or activate releases."
654
882
  };
655
883
  }
656
884
  function requireSuperagency(c) {
@@ -799,7 +1027,9 @@ function serializeTemplateAbout(c, template) {
799
1027
  viral_dna: template.about.viral_dna,
800
1028
  visual_dna: template.about.visual_dna,
801
1029
  preview_media: template.about.preview_media.map((entry) => resolveTemplateAboutMediaUrl(c, template.id, entry)),
802
- link_to_original: template.about.link_to_original
1030
+ link_to_original: template.about.link_to_original,
1031
+ proposal_generator: template.about.proposal_generator,
1032
+ sample_prompts: template.about.sample_prompts
803
1033
  };
804
1034
  }
805
1035
  function resolveTemplateAboutMediaUrl(c, templateId, entry) {
@@ -1014,6 +1244,36 @@ app.post(`${USER_PREFIX}/me/developer/preview-media/presign`, async (c) => {
1014
1244
  return c.json({ error: error instanceof Error ? error.message : "Unable to create preview upload URL." }, 400);
1015
1245
  }
1016
1246
  });
1247
+ app.post(`${TEMPLATES_PREFIX}/sources/:sourceId/artifacts/presign`, async (c) => {
1248
+ try {
1249
+ requireDeveloper(c);
1250
+ }
1251
+ catch (error) {
1252
+ return c.json({ error: error instanceof Error ? error.message : "Forbidden" }, 403);
1253
+ }
1254
+ try {
1255
+ const body = developerTemplateArtifactPresignSchema.parse(await c.req.json());
1256
+ const artifact = await templateSources.createDeveloperArtifactUpload({
1257
+ sourceId: c.req.param("sourceId"),
1258
+ commitSha: body.commit_sha
1259
+ });
1260
+ return c.json({
1261
+ artifact_key: artifact.artifactKey,
1262
+ artifact_manifest_key: artifact.manifestKey,
1263
+ upload: {
1264
+ method: artifact.upload.method,
1265
+ url: artifact.upload.url,
1266
+ headers: artifact.upload.headers,
1267
+ expires_in_seconds: artifact.upload.expiresIn
1268
+ }
1269
+ });
1270
+ }
1271
+ catch (error) {
1272
+ const message = error instanceof Error ? error.message : "Unable to prepare template artifact upload.";
1273
+ const status = /not found/i.test(message) ? 404 : 400;
1274
+ return c.json({ error: message }, status);
1275
+ }
1276
+ });
1017
1277
  app.get(`${USER_PREFIX}/me/jobs`, (c) => {
1018
1278
  const query = listJobsQuerySchema.parse({
1019
1279
  template_id: c.req.query("template_id"),
@@ -1071,7 +1331,7 @@ app.post(`${TEMPLATES_PREFIX}/sources`, async (c) => {
1071
1331
  slug_id: z.string().min(3).regex(/^[a-z0-9_]+$/i, "slug_id must contain only letters, numbers, and underscores."),
1072
1332
  repo_url: z.string().url(),
1073
1333
  branch: z.string().min(1).default("production"),
1074
- template_module_path: z.string().regex(/(^|\/)vidfarm_template_[^/]+\/src\/template\.ts$/i, "template_module_path must point at a template entrypoint inside a folder that starts with vidfarm_template_, for example templates/vidfarm_template_example/src/template.ts."),
1334
+ template_module_path: z.string().regex(/(^src\/template\.ts$|(^|\/)vidfarm_template_[^/]+\/src\/template\.ts$)/i, "template_module_path must point at src/template.ts for a standalone template repo, or at a template entrypoint inside a folder that starts with vidfarm_template_, for example templates/vidfarm_template_example/src/template.ts."),
1075
1335
  skill_path: z.string().min(1).optional(),
1076
1336
  install_command: z.string().min(1).default("npm install"),
1077
1337
  build_command: z.string().min(1).default("npm run build")
@@ -1094,7 +1354,7 @@ app.post(`${TEMPLATES_PREFIX}/sources`, async (c) => {
1094
1354
  completed: true,
1095
1355
  responsibility_boundary: "third_party_developer_done",
1096
1356
  message: "Template source registration is complete. Your responsibility stops at GitHub push plus source registration metadata. Importing, activating, and making the template live/public are platform-admin steps.",
1097
- next_step_for_platform: "A Vidfarm admin can now import this source from the registered branch head or a chosen commit SHA."
1357
+ next_step_for_platform: "A Vidfarm admin can now import this source from the registered branch head or a chosen commit SHA, or the developer can publish an exact commit SHA for review."
1098
1358
  }
1099
1359
  }, registration.action === "created" ? 201 : 200);
1100
1360
  }
@@ -1112,6 +1372,40 @@ app.post(`${TEMPLATES_PREFIX}/sources`, async (c) => {
1112
1372
  return c.json({ error: message }, status);
1113
1373
  }
1114
1374
  });
1375
+ app.post(`${TEMPLATES_PREFIX}/sources/:sourceId/publish`, async (c) => {
1376
+ let customer;
1377
+ try {
1378
+ customer = requireDeveloper(c);
1379
+ }
1380
+ catch (error) {
1381
+ return c.json({ error: error instanceof Error ? error.message : "Forbidden" }, 403);
1382
+ }
1383
+ try {
1384
+ const body = developerTemplateArtifactPublishSchema.parse(await c.req.json());
1385
+ const result = await templateSources.publishArtifactRelease({
1386
+ sourceId: c.req.param("sourceId"),
1387
+ commitSha: body.commit_sha,
1388
+ artifactSha256: body.artifact_sha256,
1389
+ signedBy: customer.email
1390
+ });
1391
+ return c.json({
1392
+ source: result.source,
1393
+ release: result.release,
1394
+ publish_action: result.publishAction,
1395
+ developer_handoff: {
1396
+ completed: true,
1397
+ responsibility_boundary: "third_party_developer_done",
1398
+ message: "The exact GitHub commit has been submitted for platform review.",
1399
+ next_step_for_platform: "A Vidfarm admin reviews the certification result, approves the release, and activates it."
1400
+ }
1401
+ }, result.publishAction === "created" ? 201 : 200);
1402
+ }
1403
+ catch (error) {
1404
+ const message = error instanceof Error ? error.message : "Unable to publish template update.";
1405
+ const status = /not found/i.test(message) ? 404 : 400;
1406
+ return c.json({ error: message }, status);
1407
+ }
1408
+ });
1115
1409
  app.get(`${TEMPLATES_PREFIX}/releases`, (c) => {
1116
1410
  try {
1117
1411
  requireAdmin(c);
@@ -1166,11 +1460,13 @@ app.post(`${TEMPLATES_PREFIX}/sources/:sourceId/import`, async (c) => {
1166
1460
  return c.json(templatePublishingAdminError(), 403);
1167
1461
  }
1168
1462
  const body = z.object({
1169
- commit_sha: z.string().min(7).optional()
1463
+ commit_sha: z.string().min(7).optional(),
1464
+ artifact_manifest_key: z.string().min(1).optional()
1170
1465
  }).parse(await c.req.json().catch(() => ({})));
1171
1466
  const release = await templateSources.importRelease({
1172
1467
  sourceId: c.req.param("sourceId"),
1173
- commitSha: body.commit_sha ?? null
1468
+ commitSha: body.commit_sha ?? null,
1469
+ artifactManifestKey: body.artifact_manifest_key ?? null
1174
1470
  });
1175
1471
  return c.json({ release }, 201);
1176
1472
  });
package/dist/src/cli.js CHANGED
@@ -1,7 +1,7 @@
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";
@@ -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;
@@ -368,7 +372,39 @@ async function runGenerateTemplateCommand(argv) {
368
372
  site_name: siteName,
369
373
  research: stagedInputs,
370
374
  template_dna_module: moduleSync,
371
- dna_analysis_runs: dnaRuns
375
+ dna_analysis_runs: dnaRuns,
376
+ required_template_contract: {
377
+ template_fields: [
378
+ "id",
379
+ "slugId",
380
+ "version",
381
+ "about.title",
382
+ "about.description",
383
+ "about.viral_dna",
384
+ "about.visual_dna",
385
+ "about.preview_media",
386
+ "about.link_to_original",
387
+ "about.proposal_generator",
388
+ "about.sample_prompts",
389
+ "configSchema",
390
+ ],
391
+ operation_fields: [
392
+ "operations.<name>.workflow",
393
+ "operations.<name>.smokeTestPayload",
394
+ "jobs.<workflow>",
395
+ ],
396
+ files: [
397
+ "src/template.ts",
398
+ "SKILL.md",
399
+ ],
400
+ },
401
+ next_steps: [
402
+ "Review src/template.ts and replace the starter proposal_generator with template-specific proposal instructions.",
403
+ "Review src/template.ts and replace the starter sample_prompts with prompts that fit the new format.",
404
+ "If DNA analysis was skipped or the source research changed, run analyze-viral-dna and analyze-visual-dna.",
405
+ "Update SKILL.md so the usage examples match the template's real operations and payloads.",
406
+ `Run validate-template against ${slugId} and launch at least one real local job before treating the template as ready.`,
407
+ ],
372
408
  }, null, 2));
373
409
  }
374
410
  async function runRegisterSourceProdCommand(argv) {
@@ -402,6 +438,45 @@ async function runRegisterSourceHostedCommand(argv) {
402
438
  next_step: "Platform admin reviews the source, imports a release, approves it, and activates it."
403
439
  }, null, 2));
404
440
  }
441
+ async function runPublishUpdateHostedCommand(argv) {
442
+ const input = parseProdTemplateCommandArgs(argv, {
443
+ envFile: ".env",
444
+ baseUrl: process.env.VIDFARM_BASE_URL ?? "https://vidfarm.cloud.zoomgtm.com",
445
+ activate: false
446
+ });
447
+ if (!input.commitSha) {
448
+ throw new Error("publish-update-hosted requires --commit-sha.");
449
+ }
450
+ const registration = await registerSourceViaApi(input);
451
+ const artifact = await packageTemplateArtifact(input.repoDir, input.commitSha);
452
+ const presign = await postProdTemplateApi(input, `/api/v1/templates/sources/${registration.source.id}/artifacts/presign`, {
453
+ method: "POST",
454
+ body: JSON.stringify({
455
+ commit_sha: input.commitSha
456
+ })
457
+ });
458
+ await uploadArtifactArchive(presign, artifact.buffer);
459
+ const payload = await postProdTemplateApi(input, `/api/v1/templates/sources/${registration.source.id}/publish`, {
460
+ method: "POST",
461
+ body: JSON.stringify({
462
+ commit_sha: input.commitSha,
463
+ artifact_sha256: artifact.sha256
464
+ })
465
+ });
466
+ console.log(JSON.stringify({
467
+ mode: "hosted-publish-update",
468
+ base_url: input.baseUrl,
469
+ source: payload.source,
470
+ release: payload.release,
471
+ artifact: {
472
+ artifact_key: presign.artifact_key,
473
+ artifact_manifest_key: presign.artifact_manifest_key,
474
+ sha256: artifact.sha256
475
+ },
476
+ publish_action: payload.publish_action,
477
+ developer_handoff: payload.developer_handoff ?? null
478
+ }, null, 2));
479
+ }
405
480
  async function runImportSourceProdCommand(argv) {
406
481
  const input = parseProdTemplateCommandArgs(argv, {
407
482
  envFile: ".env.production",
@@ -486,7 +561,37 @@ async function runCopyReferenceTemplateCommand(argv) {
486
561
  template_dir: destinationDir,
487
562
  template_id: "4c7a7e1a-7f35-4f30-9f86-9c8a63c7f2db",
488
563
  slug_id: "template_0000",
489
- rewritten: false
564
+ rewritten: false,
565
+ required_template_contract: {
566
+ template_fields: [
567
+ "id",
568
+ "slugId",
569
+ "version",
570
+ "about.title",
571
+ "about.description",
572
+ "about.viral_dna",
573
+ "about.visual_dna",
574
+ "about.preview_media",
575
+ "about.link_to_original",
576
+ "about.proposal_generator",
577
+ "about.sample_prompts",
578
+ "configSchema",
579
+ ],
580
+ operation_fields: [
581
+ "operations.<name>.workflow",
582
+ "operations.<name>.smokeTestPayload",
583
+ "jobs.<workflow>",
584
+ ],
585
+ files: [
586
+ "src/template.ts",
587
+ "SKILL.md",
588
+ ],
589
+ },
590
+ next_steps: [
591
+ "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.",
593
+ "Keep the template-local SKILL.md aligned with the operations and payloads in code.",
594
+ ],
490
595
  }, null, 2));
491
596
  }
492
597
  async function runAnalyzeTemplateDnaCommand(mode, argv) {
@@ -613,7 +718,7 @@ async function runSessionCommand(argv) {
613
718
  starter_style_options: {
614
719
  fonts: STARTER_TEMPLATE_FONT_OPTIONS,
615
720
  text_background_colors: STARTER_TEMPLATE_TEXT_BACKGROUND_COLOR_OPTIONS,
616
- starter_template_file: "templates/template_0000/src/style-options.ts"
721
+ starter_template_file: "templates/vidfarm_template_0000/src/style-options.ts"
617
722
  },
618
723
  headers: {
619
724
  "vidfarm-api-key": session.apiKey,
@@ -700,8 +805,8 @@ function toProjectName(folderName) {
700
805
  }
701
806
  function resolveStarterTemplateDir() {
702
807
  const candidates = [
703
- path.resolve(import.meta.dirname, "..", "templates", "template_0000"),
704
- path.resolve(import.meta.dirname, "..", "..", "templates", "template_0000")
808
+ path.resolve(import.meta.dirname, "..", "templates", "vidfarm_template_0000"),
809
+ path.resolve(import.meta.dirname, "..", "..", "templates", "vidfarm_template_0000")
705
810
  ];
706
811
  for (const candidate of candidates) {
707
812
  if (existsSync(candidate)) {
@@ -723,6 +828,7 @@ function parseProdTemplateCommandArgs(argv, defaults) {
723
828
  "install-command": { type: "string", default: "npm install" },
724
829
  "build-command": { type: "string", default: "npm run build" },
725
830
  "commit-sha": { type: "string" },
831
+ "repo-dir": { type: "string", default: "." },
726
832
  "env-file": { type: "string", default: defaults.envFile },
727
833
  "base-url": { type: "string", default: defaults.baseUrl },
728
834
  "api-key": { type: "string" },
@@ -752,6 +858,7 @@ function parseProdTemplateCommandArgs(argv, defaults) {
752
858
  installCommand: parsed.values["install-command"],
753
859
  buildCommand: parsed.values["build-command"],
754
860
  commitSha: parsed.values["commit-sha"] ?? undefined,
861
+ repoDir: path.resolve(process.cwd(), parsed.values["repo-dir"]),
755
862
  envFile: parsed.values["env-file"],
756
863
  baseUrl: normalizeBaseUrl(parsed.values["base-url"]),
757
864
  apiKey,
@@ -810,6 +917,35 @@ async function postProdTemplateApi(input, pathname, init) {
810
917
  }
811
918
  return payload;
812
919
  }
920
+ async function packageTemplateArtifact(repoDir, commitSha) {
921
+ const tempDir = mkdtempSync(path.join(os.tmpdir(), "vidfarm-template-artifact-"));
922
+ const repoRoot = (await execFileAsync("git", ["-C", repoDir, "rev-parse", "--show-toplevel"])).stdout.trim();
923
+ const artifactPath = path.join(tempDir, "template-source.tar.gz");
924
+ try {
925
+ await execFileAsync("git", ["-C", repoRoot, "archive", "--format=tar.gz", `--output=${artifactPath}`, commitSha], {
926
+ cwd: repoRoot
927
+ });
928
+ const buffer = readFileSync(artifactPath);
929
+ return {
930
+ buffer,
931
+ sha256: createHash("sha256").update(buffer).digest("hex")
932
+ };
933
+ }
934
+ finally {
935
+ rmSync(tempDir, { recursive: true, force: true });
936
+ }
937
+ }
938
+ async function uploadArtifactArchive(presign, buffer) {
939
+ const response = await fetch(presign.upload.url, {
940
+ method: presign.upload.method,
941
+ headers: presign.upload.headers,
942
+ body: new Uint8Array(buffer)
943
+ });
944
+ if (!response.ok) {
945
+ const body = await response.text().catch(() => "");
946
+ throw new Error(body || `Artifact upload failed with ${response.status}.`);
947
+ }
948
+ }
813
949
  function loadEnvFile(envFile) {
814
950
  const resolved = path.resolve(process.cwd(), envFile);
815
951
  if (!existsSync(resolved)) {
@@ -32,6 +32,7 @@ const schema = z.object({
32
32
  GEMINI_API_KEY: z.string().optional(),
33
33
  PERPLEXITY_API_KEY: z.string().optional(),
34
34
  SUPERAGENCY_KEY: z.string().optional(),
35
+ ARTIFACT_SIGNING_SECRET: z.string().optional(),
35
36
  REMOTION_REGION: z.string().default("us-east-1"),
36
37
  REMOTION_BUCKET_NAME: z.string().optional(),
37
38
  REMOTION_SITE_NAME: z.string().optional(),
@@ -43,7 +44,6 @@ const schema = z.object({
43
44
  REMOTION_MODE: z.enum(["auto", "mock", "local", "lambda"]).default("auto"),
44
45
  MOCK_PROVIDER_RESPONSES: z.string().optional(),
45
46
  VIDFARM_ADMIN_EMAILS: z.string().default(""),
46
- VIDFARM_DEVELOPER_EMAILS: z.string().default(""),
47
47
  VIDFARM_GITHUB_TOKEN: z.string().optional(),
48
48
  GITHUB_TOKEN: z.string().optional(),
49
49
  TEMPLATE_SOURCE_ROOT: z.string().default("./data/template-sources")
@@ -60,6 +60,7 @@ export const config = {
60
60
  VIDFARM_DATA_DIR: dataDir,
61
61
  TEMPLATE_SOURCE_ROOT: templateSourceRoot,
62
62
  PUBLIC_BASE_URL: publicBaseUrl,
63
+ ARTIFACT_SIGNING_SECRET: parsed.ARTIFACT_SIGNING_SECRET || parsed.SUPERAGENCY_KEY || "",
63
64
  isProduction: parsed.NODE_ENV === "production",
64
65
  mockProviders: parsed.MOCK_PROVIDER_RESPONSES === "true" ||
65
66
  (parsed.MOCK_PROVIDER_RESPONSES == null && parsed.NODE_ENV !== "production"),
@@ -68,8 +69,4 @@ export const config = {
68
69
  .split(",")
69
70
  .map((value) => value.trim().toLowerCase())
70
71
  .filter(Boolean),
71
- developerEmails: parsed.VIDFARM_DEVELOPER_EMAILS
72
- .split(",")
73
- .map((value) => value.trim().toLowerCase())
74
- .filter(Boolean)
75
72
  };
@@ -176,6 +176,14 @@ export function createTemplateJobContext(input) {
176
176
  ...request
177
177
  });
178
178
  },
179
+ async generateSpeech(request) {
180
+ return input.providers.generateSpeech({
181
+ customerId: input.customer.id,
182
+ jobId: input.job.id,
183
+ workerId: input.workerId,
184
+ ...request
185
+ });
186
+ },
179
187
  async generateImage(request) {
180
188
  return input.providers.generateImage({
181
189
  customerId: input.customer.id,
@@ -191,6 +199,14 @@ export function createTemplateJobContext(input) {
191
199
  workerId: input.workerId,
192
200
  ...request
193
201
  });
202
+ },
203
+ async transcribeSpeech(request) {
204
+ return input.providers.transcribeSpeech({
205
+ customerId: input.customer.id,
206
+ jobId: input.job.id,
207
+ workerId: input.workerId,
208
+ ...request
209
+ });
194
210
  }
195
211
  },
196
212
  remotion: {