@mevdragon/vidfarm-devcli 0.2.7 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/GETTING_STARTED.developers.md +14 -18
- package/README.md +19 -5
- package/SKILL.developer.md +140 -11
- package/dist/src/account-pages.js +1 -1
- package/dist/src/app.js +305 -9
- package/dist/src/cli.js +142 -6
- package/dist/src/config.js +2 -5
- package/dist/src/context.js +16 -0
- package/dist/src/db.js +33 -2
- package/dist/src/homepage.js +33 -1
- package/dist/src/lib/template-paths.js +10 -4
- package/dist/src/registry.js +9 -2
- package/dist/src/services/auth.js +2 -6
- package/dist/src/services/job-logs.js +4 -4
- package/dist/src/services/providers.js +470 -0
- package/dist/src/services/storage.js +13 -0
- package/dist/src/services/template-certification.js +16 -2
- package/dist/src/services/template-loader.js +17 -1
- package/dist/src/services/template-sources.js +249 -5
- package/{templates/template_0000 → dist/templates/vidfarm_template_0000}/src/remotion/Root.js +1 -0
- package/dist/templates/{template_0000 → vidfarm_template_0000}/src/template.js +9 -3
- package/package.json +4 -3
- package/templates/{template_0000 → vidfarm_template_0000}/README.md +23 -0
- package/templates/{template_0000 → vidfarm_template_0000}/package.json +0 -1
- package/templates/{template_0000 → vidfarm_template_0000}/src/sdk.ts +18 -0
- package/templates/{template_0000 → vidfarm_template_0000}/src/template.js +9 -3
- package/templates/{template_0000 → vidfarm_template_0000}/src/template.ts +10 -3
- package/templates/vidfarm_template_0000/tmp/solobacterium-moorei-slideshow.request.json +31 -0
- /package/dist/templates/{template_0000 → vidfarm_template_0000}/src/lib/images.js +0 -0
- /package/dist/templates/{template_0000 → vidfarm_template_0000}/src/remotion/index.js +0 -0
- /package/dist/templates/{template_0000 → vidfarm_template_0000}/src/sdk.js +0 -0
- /package/dist/templates/{template_0000 → vidfarm_template_0000}/src/style-options.js +0 -0
- /package/dist/templates/{template_0000 → vidfarm_template_0000}/src/template-dna.js +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/SKILL.md +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/assets/Abel-Regular.ttf +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/assets/DMSerifDisplay-Regular.ttf +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/assets/Montserrat[wght].ttf +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/assets/SourceCodePro[wght].ttf +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/assets/TikTokSans-SemiBold.ttf +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/assets/Yesteryear-Regular.ttf +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/composition.json +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/research/preview/.gitkeep +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/research/source_notes.md +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/src/lib/images.js +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/src/lib/images.ts +0 -0
- /package/{dist/templates/template_0000 → templates/vidfarm_template_0000}/src/remotion/Root.js +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/src/remotion/Root.tsx +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/src/remotion/index.js +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/src/remotion/index.tsx +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/src/sdk.js +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/src/style-options.js +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/src/style-options.ts +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/src/template-dna.js +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/src/template-dna.ts +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/template.config.json +0 -0
- /package/templates/{template_0000 → vidfarm_template_0000}/tsconfig.json +0 -0
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
|
|
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
|
|
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/
|
|
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", "
|
|
704
|
-
path.resolve(import.meta.dirname, "..", "..", "templates", "
|
|
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)) {
|
package/dist/src/config.js
CHANGED
|
@@ -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
|
};
|
package/dist/src/context.js
CHANGED
|
@@ -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: {
|