@mevdragon/vidfarm-devcli 0.2.6 → 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 +25 -0
  2. package/README.md +44 -3
  3. package/SKILL.developer.md +147 -11
  4. package/dist/src/account-pages.js +1 -1
  5. package/dist/src/app.js +380 -13
  6. package/dist/src/cli.js +246 -59
  7. package/dist/src/config.js +4 -5
  8. package/dist/src/context.js +16 -0
  9. package/dist/src/db.js +67 -13
  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 +338 -17
  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,15 @@ 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
+ });
93
+ const templateSourceStatusSchema = z.enum(["pending_review", "approved", "rejected", "disabled"]);
94
+ const templateReleaseStatusSchema = z.enum(["imported", "pending_approval", "approved", "rejected", "failed", "active"]);
70
95
  const listJobsQuerySchema = z.object({
71
96
  tracer: z.string().min(1).optional(),
72
97
  start_time: z.string().min(1).optional(),
@@ -288,7 +313,7 @@ function getReleaseTimestamp(release) {
288
313
  function getApprovedHomepageTemplates(c) {
289
314
  const approvedReleaseByTemplateId = new Map();
290
315
  for (const release of database.listTemplateReleases()) {
291
- if (release.status !== "active" && release.status !== "certified") {
316
+ if (release.status !== "active" && release.status !== "approved") {
292
317
  continue;
293
318
  }
294
319
  const current = approvedReleaseByTemplateId.get(release.templateId);
@@ -583,9 +608,7 @@ app.post(`${API_PREFIX}/admin/auth/whitelist`, async (c) => {
583
608
  email: normalizedEmail,
584
609
  name: existing?.name ?? null,
585
610
  defaultWebhookUrl: existing?.defaultWebhookUrl ?? null,
586
- isDeveloper: existing?.isDeveloper
587
- || config.adminEmails.includes(normalizedEmail)
588
- || config.developerEmails.includes(normalizedEmail),
611
+ isDeveloper: existing?.isDeveloper || config.adminEmails.includes(normalizedEmail),
589
612
  isPaidPlan: true
590
613
  });
591
614
  });
@@ -606,6 +629,213 @@ app.post(`${API_PREFIX}/admin/auth/users`, async (c) => {
606
629
  });
607
630
  return c.json({ customer }, 201);
608
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
+ });
609
839
  app.post(`${USER_PREFIX}/request-otp`, async (c) => {
610
840
  const body = otpRequestSchema.parse(await c.req.json());
611
841
  await auth.requestOtp(body.email);
@@ -645,6 +875,12 @@ function requireDeveloper(c) {
645
875
  }
646
876
  return customer;
647
877
  }
878
+ function templatePublishingAdminError() {
879
+ return {
880
+ error: "Admin access required.",
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."
882
+ };
883
+ }
648
884
  function requireSuperagency(c) {
649
885
  const provided = c.req.header("x-superagency-key");
650
886
  if (!config.SUPERAGENCY_KEY || provided !== config.SUPERAGENCY_KEY) {
@@ -791,7 +1027,9 @@ function serializeTemplateAbout(c, template) {
791
1027
  viral_dna: template.about.viral_dna,
792
1028
  visual_dna: template.about.visual_dna,
793
1029
  preview_media: template.about.preview_media.map((entry) => resolveTemplateAboutMediaUrl(c, template.id, entry)),
794
- 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
795
1033
  };
796
1034
  }
797
1035
  function resolveTemplateAboutMediaUrl(c, templateId, entry) {
@@ -1006,6 +1244,36 @@ app.post(`${USER_PREFIX}/me/developer/preview-media/presign`, async (c) => {
1006
1244
  return c.json({ error: error instanceof Error ? error.message : "Unable to create preview upload URL." }, 400);
1007
1245
  }
1008
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
+ });
1009
1277
  app.get(`${USER_PREFIX}/me/jobs`, (c) => {
1010
1278
  const query = listJobsQuerySchema.parse({
1011
1279
  template_id: c.req.query("template_id"),
@@ -1046,7 +1314,9 @@ app.get(`${TEMPLATES_PREFIX}/sources`, (c) => {
1046
1314
  catch (error) {
1047
1315
  return c.json({ error: error instanceof Error ? error.message : "Forbidden" }, 403);
1048
1316
  }
1049
- return c.json({ sources: templateSources.listSources() });
1317
+ const status = c.req.query("status");
1318
+ const parsedStatus = status ? templateSourceStatusSchema.parse(status) : undefined;
1319
+ return c.json({ sources: templateSources.listSources(parsedStatus) });
1050
1320
  });
1051
1321
  app.post(`${TEMPLATES_PREFIX}/sources`, async (c) => {
1052
1322
  try {
@@ -1061,7 +1331,7 @@ app.post(`${TEMPLATES_PREFIX}/sources`, async (c) => {
1061
1331
  slug_id: z.string().min(3).regex(/^[a-z0-9_]+$/i, "slug_id must contain only letters, numbers, and underscores."),
1062
1332
  repo_url: z.string().url(),
1063
1333
  branch: z.string().min(1).default("production"),
1064
- 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."),
1065
1335
  skill_path: z.string().min(1).optional(),
1066
1336
  install_command: z.string().min(1).default("npm install"),
1067
1337
  build_command: z.string().min(1).default("npm run build")
@@ -1079,7 +1349,13 @@ app.post(`${TEMPLATES_PREFIX}/sources`, async (c) => {
1079
1349
  return c.json({
1080
1350
  source: registration.source,
1081
1351
  registration_action: registration.action,
1082
- sync: registration.sync
1352
+ sync: registration.sync,
1353
+ developer_handoff: {
1354
+ completed: true,
1355
+ responsibility_boundary: "third_party_developer_done",
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.",
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."
1358
+ }
1083
1359
  }, registration.action === "created" ? 201 : 200);
1084
1360
  }
1085
1361
  catch (error) {
@@ -1096,6 +1372,40 @@ app.post(`${TEMPLATES_PREFIX}/sources`, async (c) => {
1096
1372
  return c.json({ error: message }, status);
1097
1373
  }
1098
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
+ });
1099
1409
  app.get(`${TEMPLATES_PREFIX}/releases`, (c) => {
1100
1410
  try {
1101
1411
  requireAdmin(c);
@@ -1103,30 +1413,87 @@ app.get(`${TEMPLATES_PREFIX}/releases`, (c) => {
1103
1413
  catch (error) {
1104
1414
  return c.json({ error: error instanceof Error ? error.message : "Forbidden" }, 403);
1105
1415
  }
1106
- return c.json({ releases: templateSources.listReleases(c.req.query("template_id") || undefined) });
1416
+ const status = c.req.query("status");
1417
+ const parsedStatus = status ? templateReleaseStatusSchema.parse(status) : undefined;
1418
+ return c.json({
1419
+ releases: templateSources.listReleases({
1420
+ templateId: c.req.query("template_id") || undefined,
1421
+ status: parsedStatus
1422
+ })
1423
+ });
1107
1424
  });
1108
- app.post(`${TEMPLATES_PREFIX}/sources/:sourceId/import`, async (c) => {
1425
+ app.get(`${TEMPLATES_PREFIX}/review-queue`, (c) => {
1109
1426
  try {
1110
1427
  requireAdmin(c);
1111
1428
  }
1112
1429
  catch (error) {
1113
1430
  return c.json({ error: error instanceof Error ? error.message : "Forbidden" }, 403);
1114
1431
  }
1432
+ return c.json({
1433
+ pending_sources: templateSources.listSources("pending_review"),
1434
+ pending_releases: templateSources.listReleases({ status: "pending_approval" })
1435
+ });
1436
+ });
1437
+ app.post(`${TEMPLATES_PREFIX}/sources/:sourceId/approve`, (c) => {
1438
+ try {
1439
+ requireAdmin(c);
1440
+ }
1441
+ catch (error) {
1442
+ return c.json(templatePublishingAdminError(), 403);
1443
+ }
1444
+ return c.json({ source: templateSources.approveSource({ sourceId: c.req.param("sourceId") }) });
1445
+ });
1446
+ app.post(`${TEMPLATES_PREFIX}/sources/:sourceId/reject`, (c) => {
1447
+ try {
1448
+ requireAdmin(c);
1449
+ }
1450
+ catch (error) {
1451
+ return c.json(templatePublishingAdminError(), 403);
1452
+ }
1453
+ return c.json({ source: templateSources.rejectSource({ sourceId: c.req.param("sourceId") }) });
1454
+ });
1455
+ app.post(`${TEMPLATES_PREFIX}/sources/:sourceId/import`, async (c) => {
1456
+ try {
1457
+ requireAdmin(c);
1458
+ }
1459
+ catch (error) {
1460
+ return c.json(templatePublishingAdminError(), 403);
1461
+ }
1115
1462
  const body = z.object({
1116
- commit_sha: z.string().min(7).optional()
1463
+ commit_sha: z.string().min(7).optional(),
1464
+ artifact_manifest_key: z.string().min(1).optional()
1117
1465
  }).parse(await c.req.json().catch(() => ({})));
1118
1466
  const release = await templateSources.importRelease({
1119
1467
  sourceId: c.req.param("sourceId"),
1120
- commitSha: body.commit_sha ?? null
1468
+ commitSha: body.commit_sha ?? null,
1469
+ artifactManifestKey: body.artifact_manifest_key ?? null
1121
1470
  });
1122
1471
  return c.json({ release }, 201);
1123
1472
  });
1473
+ app.post(`${TEMPLATES_PREFIX}/releases/:releaseId/approve`, (c) => {
1474
+ try {
1475
+ requireAdmin(c);
1476
+ }
1477
+ catch (error) {
1478
+ return c.json(templatePublishingAdminError(), 403);
1479
+ }
1480
+ return c.json({ release: templateSources.approveRelease({ releaseId: c.req.param("releaseId") }) });
1481
+ });
1482
+ app.post(`${TEMPLATES_PREFIX}/releases/:releaseId/reject`, (c) => {
1483
+ try {
1484
+ requireAdmin(c);
1485
+ }
1486
+ catch (error) {
1487
+ return c.json(templatePublishingAdminError(), 403);
1488
+ }
1489
+ return c.json({ release: templateSources.rejectRelease({ releaseId: c.req.param("releaseId") }) });
1490
+ });
1124
1491
  app.post(`${TEMPLATES_PREFIX}/releases/:releaseId/activate`, async (c) => {
1125
1492
  try {
1126
1493
  requireAdmin(c);
1127
1494
  }
1128
1495
  catch (error) {
1129
- return c.json({ error: error instanceof Error ? error.message : "Forbidden" }, 403);
1496
+ return c.json(templatePublishingAdminError(), 403);
1130
1497
  }
1131
1498
  const { release, template } = await templateSources.activateRelease({
1132
1499
  releaseId: c.req.param("releaseId")