@mevdragon/vidfarm-devcli 0.1.0 → 0.2.0

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 (63) hide show
  1. package/.env.example +11 -4
  2. package/PLATFORM_SPEC.md +142 -2
  3. package/README.md +165 -16
  4. package/SKILL.developer.md +577 -0
  5. package/dist/infra/cdk/bin/vidfarm-prod.js +59 -0
  6. package/dist/infra/cdk/lib/vidfarm-prod-stack.js +212 -0
  7. package/dist/src/account-pages.js +578 -0
  8. package/dist/src/app.js +887 -66
  9. package/dist/src/cli.js +284 -5
  10. package/dist/src/config.js +24 -4
  11. package/dist/src/db.js +427 -18
  12. package/dist/src/dev-app.js +59 -12
  13. package/dist/src/homepage.js +441 -0
  14. package/dist/src/index.js +12 -7
  15. package/dist/src/lib/crypto.js +14 -0
  16. package/dist/src/lib/template-dna.js +542 -0
  17. package/dist/src/lib/template-style-options.js +49 -0
  18. package/dist/src/registry.js +54 -7
  19. package/dist/src/runtime.js +3 -1
  20. package/dist/src/services/auth.js +69 -5
  21. package/dist/src/services/jobs.js +23 -4
  22. package/dist/src/services/providers.js +74 -12
  23. package/dist/src/services/storage.js +52 -18
  24. package/dist/src/services/template-certification.js +160 -0
  25. package/dist/src/services/template-loader.js +37 -0
  26. package/dist/src/services/template-sources.js +135 -0
  27. package/dist/src/worker.js +19 -7
  28. package/dist/templates/template_0000/src/lib/images.js +242 -0
  29. package/dist/templates/template_0000/src/remotion/Root.js +33 -0
  30. package/dist/templates/template_0000/src/sdk.js +3 -0
  31. package/dist/templates/template_0000/src/style-options.js +51 -0
  32. package/dist/templates/template_0000/src/template-dna.js +9 -0
  33. package/dist/templates/template_0000/src/template.js +1217 -0
  34. package/package.json +9 -1
  35. package/templates/template_0000/README.md +121 -0
  36. package/templates/template_0000/SKILL.md +193 -0
  37. package/templates/template_0000/assets/Abel-Regular.ttf +0 -0
  38. package/templates/template_0000/assets/DMSerifDisplay-Regular.ttf +0 -0
  39. package/templates/template_0000/assets/Montserrat[wght].ttf +0 -0
  40. package/templates/template_0000/assets/SourceCodePro[wght].ttf +0 -0
  41. package/templates/template_0000/assets/TikTokSans-SemiBold.ttf +0 -0
  42. package/templates/template_0000/assets/Yesteryear-Regular.ttf +0 -0
  43. package/templates/template_0000/composition.json +11 -0
  44. package/templates/template_0000/package-lock.json +5137 -0
  45. package/templates/template_0000/package.json +30 -0
  46. package/templates/template_0000/research/preview/.gitkeep +1 -0
  47. package/templates/template_0000/research/source_notes.md +7 -0
  48. package/templates/template_0000/scripts/create-site.mjs +27 -0
  49. package/templates/template_0000/scripts/render-cloud.mjs +72 -0
  50. package/templates/template_0000/src/lib/images.ts +284 -0
  51. package/templates/template_0000/src/remotion/Root.js +33 -0
  52. package/templates/template_0000/src/remotion/Root.tsx +75 -0
  53. package/templates/template_0000/src/remotion/index.tsx +4 -0
  54. package/templates/template_0000/src/sdk.ts +122 -0
  55. package/templates/template_0000/src/style-options.js +51 -0
  56. package/templates/template_0000/src/style-options.ts +60 -0
  57. package/templates/template_0000/src/template-dna.ts +15 -0
  58. package/templates/template_0000/src/template.ts +1747 -0
  59. package/templates/template_0000/template.config.json +26 -0
  60. package/templates/template_0000/tsconfig.json +19 -0
  61. package/dist/templates/template_0000/demo-template.js +0 -196
  62. package/dist/templates/template_0000/remotion/Root.js +0 -66
  63. /package/dist/templates/template_0000/{remotion → src/remotion}/index.js +0 -0
@@ -0,0 +1,26 @@
1
+ {
2
+ "template_id": "4c7a7e1a-7f35-4f30-9f86-9c8a63c7f2db",
3
+ "slug_id": "template_0000",
4
+ "project_name": "vidfarm_template_0000",
5
+ "github_repo": "mevdragon/vidfarm_template_0000",
6
+ "source_branch": "production",
7
+ "skill_path": "SKILL.md",
8
+ "template_module_path": "src/template.js",
9
+ "release_controls": {
10
+ "shared_remotion_publish": "admin_only",
11
+ "platform_activation": "admin_only",
12
+ "production_docker_promotion": "admin_only"
13
+ },
14
+ "remotion": {
15
+ "region": "us-east-1",
16
+ "function_name": "remotion-render-4-0-355-mem2048mb-disk2048mb-180sec",
17
+ "bucket_name": "remotionlambda-useast1-ujg7c0h43q",
18
+ "site_name": "vidfarm-template-0000",
19
+ "serve_url": "https://remotionlambda-useast1-ujg7c0h43q.s3.us-east-1.amazonaws.com/sites/vidfarm-template-0000/index.html",
20
+ "composition_id": "template-0000",
21
+ "entry_point": "src/remotion/index.tsx",
22
+ "props_file": "composition.json",
23
+ "timeout_ms": 120000,
24
+ "frames_per_lambda": 100
25
+ }
26
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "jsx": "react-jsx",
7
+ "strict": true,
8
+ "skipLibCheck": true,
9
+ "esModuleInterop": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "types": [
12
+ "node"
13
+ ]
14
+ },
15
+ "include": [
16
+ "src/**/*.ts",
17
+ "src/**/*.tsx"
18
+ ]
19
+ }
@@ -1,196 +0,0 @@
1
- import { z } from "zod";
2
- import { existsSync } from "node:fs";
3
- import { fileURLToPath } from "node:url";
4
- import { normalizeToPortraitFrame } from "../../src/lib/images.js";
5
- import { defineTemplate } from "../../src/template-sdk.js";
6
- const slideInputSchema = z.tuple([z.string().min(3), z.string().min(1)]);
7
- const generateInputSchema = z.object({
8
- slides: z.array(slideInputSchema).min(1).max(20),
9
- secondsPerSlide: z.number().min(2).max(10).default(4)
10
- });
11
- const remotionEntryPoint = resolveRemotionEntryPoint();
12
- export const demoTemplate = defineTemplate({
13
- id: "demo-template",
14
- version: "1.0.0",
15
- description: "Opinionated slideshow generator that makes tall images, picks text placement, and renders a TikTok-ready Remotion video.",
16
- configSchema: z.object({
17
- defaultProvider: z.enum(["openai", "gemini"]).default("gemini"),
18
- textModel: z.string().default("gemini-3.1-flash-lite"),
19
- imageModel: z.string().default("gemini-2.5-flash-image"),
20
- renderCompositionId: z.string().default("demo-template")
21
- }),
22
- operations: {
23
- generate: {
24
- description: "Generate a slideshow from [[imagePrompt, exactOverlayText], ...] and return public artifact URLs.",
25
- inputSchema: generateInputSchema,
26
- workflow: "generateWorkflow",
27
- webhookSupport: true
28
- }
29
- },
30
- jobs: {
31
- async generateWorkflow(ctx, input) {
32
- const payload = generateInputSchema.parse(input);
33
- ctx.logger.progress(0.04, "Starting demo slideshow pipeline");
34
- const provider = String(ctx.templateConfig.defaultProvider ?? "gemini");
35
- const textModel = String(ctx.templateConfig.textModel ?? "gemini-3.1-flash-lite");
36
- const imageModel = String(ctx.templateConfig.imageModel ?? "gemini-2.5-flash-image");
37
- const compositionId = String(ctx.templateConfig.renderCompositionId ?? "demo-template");
38
- const slides = [];
39
- for (const [index, [imagePrompt, overlayText]] of payload.slides.entries()) {
40
- const prompt = buildImagePrompt(imagePrompt, overlayText);
41
- ctx.logger.progress(0.08 + (index / payload.slides.length) * 0.42, `Generating slide ${index + 1} image`);
42
- const image = await ctx.providers.generateImage({
43
- provider,
44
- model: imageModel,
45
- prompt,
46
- size: sourceImageSizeForProvider(provider)
47
- });
48
- await ctx.billing.record({
49
- type: "ai_generation",
50
- costUsd: 0.04,
51
- metadata: { stage: "image_generation", slideIndex: index, model: imageModel }
52
- });
53
- ctx.logger.progress(0.13 + (index / payload.slides.length) * 0.42, `Normalizing slide ${index + 1} to strict 9:16 portrait`);
54
- const normalizedImage = await normalizeToPortraitFrame(image.bytes, { width: 1080, height: 1920 });
55
- const imageArtifact = await ctx.storage.putBuffer(`slides/slide-${pad2(index + 1)}.png`, normalizedImage.bytes, {
56
- contentType: normalizedImage.contentType,
57
- kind: "image",
58
- metadata: {
59
- slideIndex: index,
60
- prompt,
61
- revisedPrompt: image.revisedPrompt,
62
- width: normalizedImage.width,
63
- height: normalizedImage.height,
64
- aspectRatio: "9:16"
65
- }
66
- });
67
- const layout = await safeAnalyzeLayout(ctx, {
68
- imageUrl: imageArtifact.url,
69
- overlayText,
70
- textModel
71
- });
72
- await ctx.billing.record({
73
- type: "ai_generation",
74
- costUsd: 0.005,
75
- metadata: { stage: "layout_analysis", slideIndex: index, model: textModel }
76
- });
77
- slides.push({
78
- index,
79
- imagePrompt,
80
- overlayText,
81
- imageUrl: imageArtifact.url,
82
- prompt,
83
- revisedPrompt: image.revisedPrompt,
84
- layout
85
- });
86
- }
87
- ctx.logger.progress(0.58, "Saving slideshow manifest");
88
- const manifest = {
89
- templateId: "demo-template",
90
- size: { width: 1080, height: 1920, aspectRatio: "9:16" },
91
- secondsPerSlide: payload.secondsPerSlide,
92
- font: "Times New Roman",
93
- slides
94
- };
95
- const manifestArtifact = await ctx.storage.putJson("manifests/demo-template.json", manifest);
96
- ctx.logger.progress(0.72, "Submitting Remotion render");
97
- const render = await ctx.remotion.render({
98
- compositionId,
99
- entryPoint: remotionEntryPoint,
100
- outputKey: "renders/final.mp4",
101
- inputProps: {
102
- slides: slides.map((slide) => ({
103
- imageUrl: slide.imageUrl,
104
- text: slide.overlayText,
105
- layout: slide.layout
106
- })),
107
- fps: 30,
108
- secondsPerSlide: payload.secondsPerSlide
109
- }
110
- });
111
- await ctx.billing.record({
112
- type: "render",
113
- costUsd: 0.35,
114
- metadata: render.metadata
115
- });
116
- const files = slides.map((slide) => slide.imageUrl).filter((value) => Boolean(value));
117
- if (manifestArtifact.url) {
118
- files.push(manifestArtifact.url);
119
- }
120
- if (render.outputUrl) {
121
- files.push(render.outputUrl);
122
- }
123
- ctx.logger.progress(1, "Demo slideshow complete", {
124
- fileCount: files.length,
125
- renderId: render.renderId
126
- });
127
- return {
128
- progress: 1,
129
- output: {
130
- files,
131
- render,
132
- manifest: manifestArtifact,
133
- slides
134
- }
135
- };
136
- }
137
- }
138
- });
139
- function buildImagePrompt(imagePrompt, overlayText) {
140
- return [
141
- "Create an exact 9:16 portrait slideshow image for a TikTok-style vertical video.",
142
- "The composition must be designed for strict full-frame mobile portrait output at 1080x1920.",
143
- "Do not produce square, landscape, or loose portrait framing.",
144
- "Do not letterbox, pillarbox, add borders, or leave empty margins.",
145
- "Keep the subject composition strong but leave one large clean zone with low visual detail for text overlay.",
146
- "Do not place important faces, hands, or product details in the top or bottom caption-safe regions.",
147
- "Do not render any words, letters, captions, subtitles, titles, logos, signage, labels, watermarks, or typography inside the image.",
148
- "The final image must contain zero visible text.",
149
- "Use cinematic lighting, crisp detail, and framing that already fits an exact 9:16 portrait frame.",
150
- `User visual prompt: ${imagePrompt}`,
151
- `Reserve room for a short editorial overlay approximately ${overlayText.length} characters long, but do not render the overlay text itself.`
152
- ].join("\n");
153
- }
154
- async function safeAnalyzeLayout(ctx, input) {
155
- if (!input.imageUrl) {
156
- return defaultLayout();
157
- }
158
- try {
159
- return await ctx.providers.analyzeImageLayout({
160
- provider: String(ctx.templateConfig.defaultProvider ?? "gemini"),
161
- model: input.textModel,
162
- imageUrl: input.imageUrl,
163
- overlayText: input.overlayText
164
- });
165
- }
166
- catch (error) {
167
- ctx.logger.warn("Layout analysis failed, using default placement", {
168
- error: error instanceof Error ? error.message : "Unknown error"
169
- });
170
- return defaultLayout();
171
- }
172
- }
173
- function defaultLayout() {
174
- return {
175
- zone: "bottom",
176
- align: "center",
177
- maxWidthPercent: 82,
178
- justification: "Fallback placement keeps the overlay in a readable lower safe zone."
179
- };
180
- }
181
- function sourceImageSizeForProvider(provider) {
182
- if (provider === "openai") {
183
- return "1024x1792";
184
- }
185
- return "1080x1920";
186
- }
187
- function pad2(value) {
188
- return String(value).padStart(2, "0");
189
- }
190
- function resolveRemotionEntryPoint() {
191
- const builtPath = fileURLToPath(new URL("./remotion/index.js", import.meta.url));
192
- if (existsSync(builtPath)) {
193
- return builtPath;
194
- }
195
- return fileURLToPath(new URL("./remotion/index.tsx", import.meta.url));
196
- }
@@ -1,66 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { AbsoluteFill, Composition, Img, Sequence, interpolate, spring, useCurrentFrame, useVideoConfig } from "remotion";
3
- export const RemotionRoot = () => {
4
- return (_jsx(Composition, { id: "demo-template", component: DemoTemplateVideo, width: 1080, height: 1920, fps: 30, durationInFrames: 120, defaultProps: { slides: [], secondsPerSlide: 4 }, calculateMetadata: ({ props }) => ({
5
- width: 1080,
6
- height: 1920,
7
- durationInFrames: Math.max(1, props.slides.length) * Math.max(1, Math.round((props.secondsPerSlide ?? 4) * 30))
8
- }) }));
9
- };
10
- const DemoTemplateVideo = ({ slides, secondsPerSlide = 4 }) => {
11
- const { fps } = useVideoConfig();
12
- const framesPerSlide = Math.max(1, Math.round(secondsPerSlide * fps));
13
- return (_jsx(AbsoluteFill, { style: { backgroundColor: "#120f0b" }, children: slides.map((slide, index) => (_jsx(Sequence, { from: index * framesPerSlide, durationInFrames: framesPerSlide, children: _jsx(SlideFrame, { slide: slide }) }, `${slide.imageUrl}-${index}`))) }));
14
- };
15
- const SlideFrame = ({ slide }) => {
16
- const frame = useCurrentFrame();
17
- const { fps } = useVideoConfig();
18
- const entrance = spring({ fps, frame, config: { damping: 200, stiffness: 140 } });
19
- const scale = interpolate(entrance, [0, 1], [1.06, 1]);
20
- const opacity = interpolate(entrance, [0, 1], [0.2, 1]);
21
- return (_jsxs(AbsoluteFill, { children: [_jsx(AbsoluteFill, { style: { opacity, transform: `scale(${scale})` }, children: _jsx(Img, { src: slide.imageUrl, style: { width: "100%", height: "100%", objectFit: "cover" } }) }), _jsx(AbsoluteFill, { style: { background: "linear-gradient(180deg, rgba(0,0,0,0.18) 0%, rgba(0,0,0,0.06) 36%, rgba(0,0,0,0.34) 100%)" } }), _jsx(TextOverlay, { slide: slide })] }));
22
- };
23
- const TextOverlay = ({ slide }) => {
24
- const zoneStyle = slide.layout.zone === "top"
25
- ? { top: "8%", bottom: "auto" }
26
- : slide.layout.zone === "center"
27
- ? { top: "50%", bottom: "auto", transform: "translateY(-50%)" }
28
- : { top: "auto", bottom: "9%" };
29
- return (_jsx("div", { style: {
30
- position: "absolute",
31
- left: "9%",
32
- right: "9%",
33
- display: "flex",
34
- justifyContent: alignmentToJustify(slide.layout.align),
35
- pointerEvents: "none",
36
- ...zoneStyle
37
- }, children: _jsx("div", { style: {
38
- maxWidth: `${slide.layout.maxWidthPercent}%`,
39
- width: `${slide.layout.maxWidthPercent}%`,
40
- boxSizing: "border-box",
41
- padding: "28px 32px",
42
- color: "#fffaf2",
43
- fontFamily: '"Times New Roman", Times, serif',
44
- fontSize: 72,
45
- lineHeight: 1.08,
46
- letterSpacing: "-0.03em",
47
- textAlign: slide.layout.align,
48
- overflowWrap: "anywhere",
49
- wordBreak: "break-word",
50
- whiteSpace: "pre-wrap",
51
- background: "rgba(18, 14, 10, 0.28)",
52
- boxShadow: "0 18px 44px rgba(0, 0, 0, 0.22)",
53
- textShadow: "0 2px 6px rgba(0, 0, 0, 0.48)",
54
- backdropFilter: "blur(8px)"
55
- }, children: slide.text }) }));
56
- };
57
- function alignmentToJustify(align) {
58
- switch (align) {
59
- case "left":
60
- return "flex-start";
61
- case "right":
62
- return "flex-end";
63
- default:
64
- return "center";
65
- }
66
- }