@mevdragon/vidfarm-devcli 0.5.2 → 0.6.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.
- package/README.md +3 -3
- package/demo/README.md +28 -0
- package/demo/dist/app.css +1 -0
- package/demo/dist/app.js +1184 -0
- package/demo/dist/chunks/chunk-DXB73IDG.js +1 -0
- package/demo/dist/chunks/chunk-S7OWAJDS.js +36 -0
- package/demo/dist/chunks/chunk-VTIBZ6AN.js +1 -0
- package/demo/dist/chunks/dist-ADSJKBVE.js +332 -0
- package/demo/dist/chunks/domEditingLayers-VZMLL4AP-SGHWPND4.js +1 -0
- package/demo/dist/chunks/hyperframes-player-XB65TCD6.js +425 -0
- package/demo/dist/chunks/lib-XAQ37YOE.js +1 -0
- package/demo/dist/chunks/src-TJ2QYA4U.js +207 -0
- package/demo/dist/favicon.ico +0 -0
- package/demo/dist/icons/timeline/audio.svg +7 -0
- package/demo/dist/icons/timeline/captions.svg +5 -0
- package/demo/dist/icons/timeline/composition.svg +12 -0
- package/demo/dist/icons/timeline/image.svg +18 -0
- package/demo/dist/icons/timeline/music.svg +10 -0
- package/demo/dist/icons/timeline/text.svg +3 -0
- package/demo/dist/index.html +15 -0
- package/dist/src/account-pages-legacy.js +9396 -0
- package/dist/src/account-pages.js +61 -0
- package/dist/src/app.js +14378 -0
- package/dist/src/cli.js +1 -1
- package/dist/src/composition-runtime.js +613 -0
- package/dist/src/config.js +166 -0
- package/dist/src/context.js +447 -0
- package/dist/src/dev-app-legacy.js +739 -0
- package/dist/src/dev-app.js +6 -0
- package/dist/src/domain.js +2 -0
- package/dist/src/editor-chat-history.js +82 -0
- package/dist/src/editor-chat.js +449 -0
- package/dist/src/editor-dark-theme.js +1128 -0
- package/dist/src/frontend/debug.js +71 -0
- package/dist/src/frontend/flockposter-cache-store.js +124 -0
- package/dist/src/frontend/homepage-client.js +182 -0
- package/dist/src/frontend/homepage-shared.js +4 -0
- package/dist/src/frontend/homepage-store.js +28 -0
- package/dist/src/frontend/homepage-view.js +547 -0
- package/dist/src/frontend/page-runtime-client.js +132 -0
- package/dist/src/frontend/page-runtime-store.js +9 -0
- package/dist/src/frontend/sentry.js +42 -0
- package/dist/src/frontend/template-editor-chat.js +3960 -0
- package/dist/src/help-page.js +346 -0
- package/dist/src/homepage.js +1235 -0
- package/dist/src/hyperframes/composition.js +180 -0
- package/dist/src/index.js +16 -0
- package/dist/src/instrument.js +30 -0
- package/dist/src/lib/crypto.js +45 -0
- package/dist/src/lib/dev-log.js +54 -0
- package/dist/src/lib/display-name.js +11 -0
- package/dist/src/lib/ids.js +24 -0
- package/dist/src/lib/images.js +19 -0
- package/dist/src/lib/json.js +15 -0
- package/dist/src/lib/package-root.js +47 -0
- package/dist/src/lib/template-paths.js +28 -0
- package/dist/src/lib/time.js +7 -0
- package/dist/src/lib/url-clean.js +85 -0
- package/dist/src/page-runtime.js +2 -0
- package/dist/src/page-shell.js +1381 -0
- package/dist/src/primitive-context.js +357 -0
- package/dist/src/primitive-registry.js +2436 -0
- package/dist/src/primitive-sdk.js +4 -0
- package/dist/src/primitives/hyperframes-media.js +108 -0
- package/dist/src/react-page-shell.js +35 -0
- package/dist/src/ready-post-schedule-component.js +1540 -0
- package/dist/src/registry.js +296 -0
- package/dist/src/runtime.js +35 -0
- package/dist/src/services/api-call-history.js +249 -0
- package/dist/src/services/auth.js +152 -0
- package/dist/src/services/billing-pricing.js +39 -0
- package/dist/src/services/billing.js +228 -0
- package/dist/src/services/cast.js +127 -0
- package/dist/src/services/chat-threads.js +92 -0
- package/dist/src/services/composition-sanitize.js +124 -0
- package/dist/src/services/composition-watch.js +79 -0
- package/dist/src/services/fork-access.js +93 -0
- package/dist/src/services/fork-manifest.js +42 -0
- package/dist/src/services/ghostcut.js +179 -0
- package/dist/src/services/hyperframes.js +2307 -0
- package/dist/src/services/job-capacity.js +14 -0
- package/dist/src/services/job-logs.js +197 -0
- package/dist/src/services/jobs.js +136 -0
- package/dist/src/services/local-dynamo.js +0 -0
- package/dist/src/services/media-processing.js +766 -0
- package/dist/src/services/primitive-media-lambda.js +280 -0
- package/dist/src/services/providers.js +2926 -0
- package/dist/src/services/rate-limits.js +262 -0
- package/dist/src/services/serverless-auth.js +382 -0
- package/dist/src/services/serverless-jobs.js +1082 -0
- package/dist/src/services/serverless-provider-keys.js +409 -0
- package/dist/src/services/serverless-records.js +1385 -0
- package/dist/src/services/serverless-template-configs.js +75 -0
- package/dist/src/services/storage.js +383 -0
- package/dist/src/services/template-certification.js +413 -0
- package/dist/src/services/template-loader.js +99 -0
- package/dist/src/services/template-runtime-bundles.js +217 -0
- package/dist/src/services/template-sources.js +1017 -0
- package/dist/src/services/video-normalization.js +2 -0
- package/dist/src/services/webhooks.js +62 -0
- package/dist/src/template-editor-pages.js +2576 -0
- package/dist/src/template-editor-shell.js +2840 -0
- package/dist/src/template-sdk.js +4 -0
- package/dist/src/worker.js +17 -0
- package/package.json +6 -4
- package/public/assets/homepage-app.js +54 -0
- package/public/assets/homepage-client-app.js +80 -0
- package/public/assets/page-runtime-client-app.js +94 -0
- package/src/assets/SELLING_AWARENESS_STAGES.md +579 -0
- package/src/assets/SELLING_WITH_HOOKS.md +377 -0
- package/src/assets/SELLING_WITH_VSLS.md +606 -0
- package/src/assets/favicon.ico +0 -0
- package/src/assets/logo-vidfarm.png +0 -0
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const APPROVED_TEMPLATE_MODEL_IDS = new Set([
|
|
4
|
+
"gpt-5.4",
|
|
5
|
+
"gemini-3.1-flash-lite",
|
|
6
|
+
"gemini-3.1-flash-lite-preview",
|
|
7
|
+
"gemini-2.5-flash-lite",
|
|
8
|
+
"qwen/qwen3.6-plus",
|
|
9
|
+
"sonar",
|
|
10
|
+
"gpt-image-1",
|
|
11
|
+
"gpt-image-2",
|
|
12
|
+
"gemini-3.1-flash-image-preview",
|
|
13
|
+
"gemini-2.5-flash-image",
|
|
14
|
+
"gemini-3-pro-image-preview",
|
|
15
|
+
"google/gemini-3.1-flash-image-preview",
|
|
16
|
+
"google/gemini-2.5-flash-image-preview",
|
|
17
|
+
"bytedance/seedance-2.0",
|
|
18
|
+
"bytedance-seed/seedream-4.5",
|
|
19
|
+
"google/gemini-3.1-flash-tts-preview",
|
|
20
|
+
"gemini-3.1-flash-tts-preview",
|
|
21
|
+
"gemini-2.5-flash-preview-tts",
|
|
22
|
+
"gpt-4o-mini-tts-2025-12-15",
|
|
23
|
+
"openai/gpt-4o-mini-transcribe",
|
|
24
|
+
"gpt-4o-mini-transcribe-2025-12-15",
|
|
25
|
+
"sora-2",
|
|
26
|
+
"veo-3.0-generate-001"
|
|
27
|
+
]);
|
|
28
|
+
const TEMPLATE_DIRECT_PROVIDER_ACCESS_PATTERNS = [
|
|
29
|
+
/process\.env\.(OPENAI_API_KEY|GEMINI_API_KEY|OPENROUTER_API_KEY|PERPLEXITY_API_KEY)/,
|
|
30
|
+
/https?:\/\/api\.openai\.com/,
|
|
31
|
+
/https?:\/\/generativelanguage\.googleapis\.com/,
|
|
32
|
+
/https?:\/\/openrouter\.ai/,
|
|
33
|
+
/https?:\/\/api\.perplexity\.ai/,
|
|
34
|
+
/\bAuthorization\s*:/,
|
|
35
|
+
/\bapiKey\s*:/,
|
|
36
|
+
/\bapi_key\s*:/
|
|
37
|
+
];
|
|
38
|
+
const TEMPLATE_OPERATION_PROVIDER_DEFAULT_PATTERN = /(?:^|[\s,{])(?:provider|text_provider|image_provider|tts_provider|speech_provider|transcription_provider)\s*:\s*z\.enum\([^;\n]+\.default\(/;
|
|
39
|
+
const TEMPLATE_GEMINI_PROVIDER_FALLBACK_PATTERN = /(?:default(?:Text|Image)?Provider|textProvider|imageProvider|ttsProvider|voiceProvider|transcriptionProvider)\s*:\s*z\.enum\([^;\n]+\.default\(["']gemini["']\)|return\s+["']gemini["'];/g;
|
|
40
|
+
const TEMPLATE_MODEL_LITERAL_PATTERN = /["'`](dall-e-3|dall-e|gpt-[^"'`]+|gemini-[^"'`]+|google\/gemini-[^"'`]+|openai\/gpt-[^"'`]+|qwen\/qwen[^"'`]+|bytedance\/[^"'`]+|bytedance-seed\/[^"'`]+|sora-[^"'`]+|veo-[^"'`]+|sonar(?:-[^"'`]+)?)["'`]/g;
|
|
41
|
+
const TEMPLATE_UNSAFE_MODEL_PASSTHROUGH_PATTERN = /String\(ctx\.templateConfig\.[A-Za-z_]*Model|ctx\.templateConfig\.[A-Za-z_]*Model\s*\?\?|payload\.[A-Za-z_]*model\s*\?\?|sanitizeConfiguredModel\([^;\n]*(?:textModel|imageModel|layoutModel|ttsModel|transcriptionModel|speechModel|payload\.model|payload\.tts_model|payload\.transcription_model|payload\.speech_model)/g;
|
|
42
|
+
export class TemplateCertificationService {
|
|
43
|
+
async certify(input) {
|
|
44
|
+
const checks = [];
|
|
45
|
+
const { template, skillPath } = input;
|
|
46
|
+
checks.push(this.check("template.metadata", Boolean(template.id &&
|
|
47
|
+
template.slugId &&
|
|
48
|
+
template.version &&
|
|
49
|
+
template.about &&
|
|
50
|
+
typeof template.about.title === "string" &&
|
|
51
|
+
typeof template.about.description === "string" &&
|
|
52
|
+
["easy", "medium", "hard", "expert"].includes(template.about.difficulty) &&
|
|
53
|
+
typeof template.about.viral_dna === "string" &&
|
|
54
|
+
typeof template.about.visual_dna === "string" &&
|
|
55
|
+
Array.isArray(template.about.preview_media) &&
|
|
56
|
+
template.about.preview_media.every((entry) => typeof entry === "string") &&
|
|
57
|
+
typeof template.about.link_to_original === "string" &&
|
|
58
|
+
Array.isArray(template.about.links_to_inspiration) &&
|
|
59
|
+
template.about.links_to_inspiration.every((entry) => typeof entry === "string") &&
|
|
60
|
+
template.about.sound &&
|
|
61
|
+
typeof template.about.sound.sound_title === "string" &&
|
|
62
|
+
typeof template.about.sound.sound_url === "string" &&
|
|
63
|
+
typeof template.about.proposal_generator === "string" &&
|
|
64
|
+
typeof template.about.skeleton_prompt === "string" &&
|
|
65
|
+
template.about.skeleton_prompt.length > 0 &&
|
|
66
|
+
Array.isArray(template.about.sample_prompts) &&
|
|
67
|
+
template.about.sample_prompts.every((entry) => typeof entry === "string") &&
|
|
68
|
+
template.about.sample_prompts.length > 0 &&
|
|
69
|
+
Array.isArray(template.examples) &&
|
|
70
|
+
template.examples.every((example) => Array.isArray(example) && example.every((entry) => (entry &&
|
|
71
|
+
typeof entry.media_url === "string" &&
|
|
72
|
+
typeof entry.note === "string")))), "Template must define id, slugId, version, examples, and complete about metadata, including difficulty, links_to_inspiration, sound, proposal_generator, skeleton_prompt, and sample prompts.", {
|
|
73
|
+
templateId: template.id,
|
|
74
|
+
slugId: template.slugId,
|
|
75
|
+
version: template.version,
|
|
76
|
+
difficulty: template.about?.difficulty ?? null,
|
|
77
|
+
inspirationLinkCount: template.about?.links_to_inspiration?.length ?? 0,
|
|
78
|
+
hasSound: Boolean(template.about?.sound),
|
|
79
|
+
exampleCount: template.examples?.length ?? 0,
|
|
80
|
+
previewMediaCount: template.about?.preview_media?.length ?? 0,
|
|
81
|
+
hasProposalGenerator: typeof template.about?.proposal_generator === "string" && template.about.proposal_generator.length > 0,
|
|
82
|
+
hasSkeletonPrompt: typeof template.about?.skeleton_prompt === "string" && template.about.skeleton_prompt.length > 0,
|
|
83
|
+
samplePromptCount: template.about?.sample_prompts?.length ?? 0
|
|
84
|
+
}));
|
|
85
|
+
checks.push(this.check("template.operations", Object.keys(template.operations).length > 0, "Template must define at least one operation."));
|
|
86
|
+
checks.push(this.checkSkill(skillPath));
|
|
87
|
+
checks.push(...this.checkProviderPolicy({ modulePath: input.modulePath }));
|
|
88
|
+
for (const [operationName, operation] of Object.entries(template.operations)) {
|
|
89
|
+
checks.push(this.check(`operation.${operationName}.workflow`, typeof template.jobs[operation.workflow] === "function", `Operation ${operationName} must reference an existing workflow.`, { workflow: operation.workflow }));
|
|
90
|
+
checks.push(this.check(`operation.${operationName}.smoke_payload`, Boolean(operation.smokeTestPayload), `Operation ${operationName} must define smokeTestPayload for certification.`));
|
|
91
|
+
if (!operation.smokeTestPayload) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
const payload = operation.inputSchema.parse(operation.smokeTestPayload);
|
|
96
|
+
const workflow = template.jobs[operation.workflow];
|
|
97
|
+
const smoke = createSmokeContext(template.id);
|
|
98
|
+
const result = await workflow(smoke.ctx, payload);
|
|
99
|
+
checks.push(this.check(`operation.${operationName}.smoke_run`, Boolean(result && (result.output || result.progress !== undefined)), `Operation ${operationName} smoke run completed.`, {
|
|
100
|
+
logCount: smoke.logCount,
|
|
101
|
+
storageWrites: smoke.storageWrites,
|
|
102
|
+
billingCalls: smoke.billingCalls,
|
|
103
|
+
providerCalls: smoke.providerCalls,
|
|
104
|
+
remotionCalls: smoke.remotionCalls
|
|
105
|
+
}));
|
|
106
|
+
checks.push(this.check(`operation.${operationName}.logs`, smoke.logCount > 0, `Operation ${operationName} must emit at least one log/progress event during smoke run.`));
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
checks.push(this.check(`operation.${operationName}.smoke_run`, false, error instanceof Error ? error.message : "Smoke run failed."));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
passed: checks.every((check) => check.ok),
|
|
114
|
+
checks
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
check(code, ok, message, details) {
|
|
118
|
+
return { code, ok, message, details };
|
|
119
|
+
}
|
|
120
|
+
checkSkill(skillPath) {
|
|
121
|
+
try {
|
|
122
|
+
const contents = readFileSync(skillPath, "utf8").trim();
|
|
123
|
+
return this.check("template.skill", contents.length >= 40, "Template must include a non-trivial SKILL.md for customer agents.", { skillPath });
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
return this.check("template.skill", false, error instanceof Error ? error.message : "Unable to read SKILL.md.", { skillPath });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
checkProviderPolicy(input) {
|
|
130
|
+
if (!input.modulePath) {
|
|
131
|
+
return [
|
|
132
|
+
this.check("template.provider_policy.source", true, "Template module path was not provided; skipped provider policy source validation.")
|
|
133
|
+
];
|
|
134
|
+
}
|
|
135
|
+
const moduleContents = readFileSync(input.modulePath, "utf8");
|
|
136
|
+
const directProviderAccess = TEMPLATE_DIRECT_PROVIDER_ACCESS_PATTERNS
|
|
137
|
+
.flatMap((pattern) => collectPatternMatches(moduleContents, pattern))
|
|
138
|
+
.map((match) => ({
|
|
139
|
+
line: lineNumberForOffset(moduleContents, match.index),
|
|
140
|
+
text: match.text.slice(0, 140)
|
|
141
|
+
}));
|
|
142
|
+
const providerDefaults = collectPatternMatches(moduleContents, TEMPLATE_OPERATION_PROVIDER_DEFAULT_PATTERN)
|
|
143
|
+
.map((match) => ({
|
|
144
|
+
line: lineNumberForOffset(moduleContents, match.index),
|
|
145
|
+
text: match.text.slice(0, 140)
|
|
146
|
+
}));
|
|
147
|
+
const geminiFallbacks = collectPatternMatches(moduleContents, TEMPLATE_GEMINI_PROVIDER_FALLBACK_PATTERN)
|
|
148
|
+
.map((match) => ({
|
|
149
|
+
line: lineNumberForOffset(moduleContents, match.index),
|
|
150
|
+
text: match.text.slice(0, 140)
|
|
151
|
+
}));
|
|
152
|
+
const unsupportedModels = collectUnsupportedModelLiterals(moduleContents);
|
|
153
|
+
const unsafeModelPassthrough = collectPatternMatches(moduleContents, TEMPLATE_UNSAFE_MODEL_PASSTHROUGH_PATTERN)
|
|
154
|
+
.filter((match) => !isInsideSafeProviderModelResolver(moduleContents, match.index))
|
|
155
|
+
.map((match) => ({
|
|
156
|
+
line: lineNumberForOffset(moduleContents, match.index),
|
|
157
|
+
text: match.text.slice(0, 140)
|
|
158
|
+
}));
|
|
159
|
+
return [
|
|
160
|
+
this.check("template.provider_policy.keys", directProviderAccess.length === 0, "Templates must use ctx.providers so AI calls lease the customer's saved provider keys; direct provider env/API usage is not allowed.", directProviderAccess.length > 0 ? { modulePath: input.modulePath, matches: directProviderAccess.slice(0, 10) } : { modulePath: input.modulePath }),
|
|
161
|
+
this.check("template.provider_policy.operation_defaults", providerDefaults.length === 0, "Operation payload provider fields must be optional explicit overrides; provider defaults belong in saved template config.", providerDefaults.length > 0 ? { modulePath: input.modulePath, matches: providerDefaults.slice(0, 10) } : { modulePath: input.modulePath }),
|
|
162
|
+
this.check("template.provider_policy.provider_fallbacks", geminiFallbacks.length === 0, "Templates must not hide Gemini provider defaults or parser fallbacks. AI calls must use ctx.providers so platform resolution can choose from the customer's saved active keys first.", geminiFallbacks.length > 0 ? { modulePath: input.modulePath, matches: geminiFallbacks.slice(0, 10) } : { modulePath: input.modulePath }),
|
|
163
|
+
this.check("template.provider_policy.models", unsupportedModels.length === 0, "Templates may only reference model IDs from the approved Vidfarm provider allowlist.", unsupportedModels.length > 0 ? { modulePath: input.modulePath, models: unsupportedModels.slice(0, 20) } : { modulePath: input.modulePath }),
|
|
164
|
+
this.check("template.provider_policy.model_resolution", unsafeModelPassthrough.length === 0, "Template model config and payload overrides must be validated against the selected provider/model allowlist before calling ctx.providers.", unsafeModelPassthrough.length > 0 ? { modulePath: input.modulePath, matches: unsafeModelPassthrough.slice(0, 10) } : { modulePath: input.modulePath })
|
|
165
|
+
];
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const MOCK_PNG_BYTES = Uint8Array.from(Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACXBIWXMAAAPoAAAD6AG1e1JrAAAADUlEQVR4nGP4////fwAJ+wP9KobjigAAAABJRU5ErkJggg==", "base64"));
|
|
169
|
+
function createSmokeContext(templateId) {
|
|
170
|
+
let logCount = 0;
|
|
171
|
+
let storageWrites = 0;
|
|
172
|
+
let billingCalls = 0;
|
|
173
|
+
let providerCalls = 0;
|
|
174
|
+
let remotionCalls = 0;
|
|
175
|
+
const prefix = `templates/${templateId}/users/certification-user/jobs/certification-job`;
|
|
176
|
+
const ctx = {
|
|
177
|
+
env: "development",
|
|
178
|
+
customer: {
|
|
179
|
+
id: "certification-user",
|
|
180
|
+
email: "certification@vidfarm.local",
|
|
181
|
+
name: "Certification",
|
|
182
|
+
defaultWebhookUrl: null,
|
|
183
|
+
isDeveloper: true,
|
|
184
|
+
isAgency: false,
|
|
185
|
+
isPaidPlan: true,
|
|
186
|
+
planTier: "standalone",
|
|
187
|
+
about: null,
|
|
188
|
+
groupchatUrl: null,
|
|
189
|
+
flockposterApiKey: null,
|
|
190
|
+
createdAt: new Date(0).toISOString(),
|
|
191
|
+
updatedAt: new Date(0).toISOString()
|
|
192
|
+
},
|
|
193
|
+
template: {
|
|
194
|
+
id: templateId,
|
|
195
|
+
slugId: "certification-template",
|
|
196
|
+
previewMediaUrls: [],
|
|
197
|
+
previewMediaPolicy: "auto"
|
|
198
|
+
},
|
|
199
|
+
templateConfig: {},
|
|
200
|
+
logger: {
|
|
201
|
+
debug() { logCount += 1; },
|
|
202
|
+
info() { logCount += 1; },
|
|
203
|
+
warn() { logCount += 1; },
|
|
204
|
+
error() { logCount += 1; },
|
|
205
|
+
progress() { logCount += 1; }
|
|
206
|
+
},
|
|
207
|
+
jobs: {
|
|
208
|
+
async enqueueChild() {
|
|
209
|
+
return { jobId: "child-certification-job" };
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
storage: {
|
|
213
|
+
async putJson(key) {
|
|
214
|
+
storageWrites += 1;
|
|
215
|
+
return { key: `${prefix}/${key}`, url: `https://certification.local/${encodeURIComponent(key)}` };
|
|
216
|
+
},
|
|
217
|
+
async putText(key) {
|
|
218
|
+
storageWrites += 1;
|
|
219
|
+
return { key: `${prefix}/${key}`, url: `https://certification.local/${encodeURIComponent(key)}` };
|
|
220
|
+
},
|
|
221
|
+
async putBuffer(key) {
|
|
222
|
+
storageWrites += 1;
|
|
223
|
+
return { key: `${prefix}/${key}`, url: `https://certification.local/${encodeURIComponent(key)}` };
|
|
224
|
+
},
|
|
225
|
+
getPublicUrl(key) {
|
|
226
|
+
return `https://certification.local/${encodeURIComponent(`${prefix}/${key}`)}`;
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
billing: {
|
|
230
|
+
async record() {
|
|
231
|
+
billingCalls += 1;
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
providers: {
|
|
235
|
+
listAvailableProviders() {
|
|
236
|
+
return ["openai", "gemini", "openrouter", "perplexity"];
|
|
237
|
+
},
|
|
238
|
+
async generateText() {
|
|
239
|
+
providerCalls += 1;
|
|
240
|
+
return { text: "certification text", usage: { inputTokens: 1, outputTokens: 1, costUsd: 0 } };
|
|
241
|
+
},
|
|
242
|
+
async generateSpeech() {
|
|
243
|
+
providerCalls += 1;
|
|
244
|
+
return { bytes: Uint8Array.from([0, 1, 2, 3]), contentType: "audio/mpeg", usage: { inputTokens: 1, outputTokens: 1, costUsd: 0 } };
|
|
245
|
+
},
|
|
246
|
+
async generateImage() {
|
|
247
|
+
providerCalls += 1;
|
|
248
|
+
return { bytes: MOCK_PNG_BYTES, contentType: "image/png", revisedPrompt: null };
|
|
249
|
+
},
|
|
250
|
+
async analyzeImageLayout() {
|
|
251
|
+
providerCalls += 1;
|
|
252
|
+
return {
|
|
253
|
+
zone: "bottom",
|
|
254
|
+
align: "center",
|
|
255
|
+
maxWidthPercent: 80,
|
|
256
|
+
justification: "Certification mock layout."
|
|
257
|
+
};
|
|
258
|
+
},
|
|
259
|
+
async transcribeSpeech() {
|
|
260
|
+
providerCalls += 1;
|
|
261
|
+
return { text: "certification transcript", usage: { inputTokens: 1, outputTokens: 1, costUsd: 0 } };
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
hyperframes: {
|
|
265
|
+
async render() {
|
|
266
|
+
remotionCalls += 1;
|
|
267
|
+
return {
|
|
268
|
+
renderId: "hyperframes-render-certification",
|
|
269
|
+
outputUrl: "https://certification.local/hyperframes-render.mp4",
|
|
270
|
+
outputS3Uri: null,
|
|
271
|
+
metadata: { mode: "certification-mock", architecture: "timeline_layers" }
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
media: {
|
|
276
|
+
async normalizeVideo(input) {
|
|
277
|
+
storageWrites += 1;
|
|
278
|
+
return {
|
|
279
|
+
key: `${prefix}/${input.outputKey}`,
|
|
280
|
+
url: `https://certification.local/${encodeURIComponent(input.outputKey)}`,
|
|
281
|
+
contentType: "video/mp4",
|
|
282
|
+
normalization: {
|
|
283
|
+
source: {
|
|
284
|
+
durationSeconds: 5,
|
|
285
|
+
width: 1080,
|
|
286
|
+
height: 1920,
|
|
287
|
+
videoCodec: "h264",
|
|
288
|
+
audioCodec: "aac",
|
|
289
|
+
videoBitrateKbps: 2000,
|
|
290
|
+
audioBitrateKbps: 128,
|
|
291
|
+
formatBitrateKbps: 2200,
|
|
292
|
+
hasAudio: true,
|
|
293
|
+
hasVideo: true,
|
|
294
|
+
sampleRateHz: 48_000,
|
|
295
|
+
channels: 2,
|
|
296
|
+
frameRate: 30,
|
|
297
|
+
formatName: "mp4",
|
|
298
|
+
bytes: 1_000_000,
|
|
299
|
+
url: input.sourceUrl
|
|
300
|
+
},
|
|
301
|
+
normalized: {
|
|
302
|
+
durationSeconds: 5,
|
|
303
|
+
width: 1080,
|
|
304
|
+
height: 1920,
|
|
305
|
+
videoCodec: "h264",
|
|
306
|
+
audioCodec: "aac",
|
|
307
|
+
videoBitrateKbps: 1500,
|
|
308
|
+
audioBitrateKbps: 128,
|
|
309
|
+
formatBitrateKbps: 1650,
|
|
310
|
+
hasAudio: true,
|
|
311
|
+
hasVideo: true,
|
|
312
|
+
sampleRateHz: 48_000,
|
|
313
|
+
channels: 2,
|
|
314
|
+
frameRate: 30,
|
|
315
|
+
formatName: "mp4",
|
|
316
|
+
bytes: 750_000
|
|
317
|
+
},
|
|
318
|
+
compressionRatio: 0.75
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
return {
|
|
325
|
+
ctx,
|
|
326
|
+
get logCount() { return logCount; },
|
|
327
|
+
get storageWrites() { return storageWrites; },
|
|
328
|
+
get billingCalls() { return billingCalls; },
|
|
329
|
+
get providerCalls() { return providerCalls; },
|
|
330
|
+
get remotionCalls() { return remotionCalls; }
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
function findTemplateRoot(modulePath) {
|
|
334
|
+
let current = path.dirname(modulePath);
|
|
335
|
+
for (let index = 0; index < 10; index += 1) {
|
|
336
|
+
if (existsSync(path.join(current, "template.config.json"))) {
|
|
337
|
+
return current;
|
|
338
|
+
}
|
|
339
|
+
const parent = path.dirname(current);
|
|
340
|
+
if (parent === current) {
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
current = parent;
|
|
344
|
+
}
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
function nonBlankString(value) {
|
|
348
|
+
if (typeof value !== "string") {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
const trimmed = value.trim();
|
|
352
|
+
return trimmed ? trimmed : null;
|
|
353
|
+
}
|
|
354
|
+
function collectTemplateSourceFiles(templateRoot) {
|
|
355
|
+
const files = [];
|
|
356
|
+
const stack = [templateRoot];
|
|
357
|
+
while (stack.length > 0) {
|
|
358
|
+
const current = stack.pop();
|
|
359
|
+
if (!current) {
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
363
|
+
if (entry.name === "node_modules" || entry.name === "build" || entry.name === "dist" || entry.name.startsWith(".")) {
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
const fullPath = path.join(current, entry.name);
|
|
367
|
+
if (entry.isDirectory()) {
|
|
368
|
+
stack.push(fullPath);
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
if (/\.(?:[cm]?[jt]sx?)$/i.test(entry.name)) {
|
|
372
|
+
files.push(fullPath);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return files;
|
|
377
|
+
}
|
|
378
|
+
function collectPatternMatches(contents, pattern) {
|
|
379
|
+
const source = pattern.global ? pattern : new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`);
|
|
380
|
+
const matches = [];
|
|
381
|
+
for (const match of contents.matchAll(source)) {
|
|
382
|
+
matches.push({ index: match.index ?? 0, text: match[0] });
|
|
383
|
+
}
|
|
384
|
+
return matches;
|
|
385
|
+
}
|
|
386
|
+
function collectUnsupportedModelLiterals(contents) {
|
|
387
|
+
const seen = new Map();
|
|
388
|
+
for (const match of contents.matchAll(TEMPLATE_MODEL_LITERAL_PATTERN)) {
|
|
389
|
+
const model = match[1]?.trim();
|
|
390
|
+
if (!model || APPROVED_TEMPLATE_MODEL_IDS.has(model) || seen.has(model)) {
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
seen.set(model, {
|
|
394
|
+
model,
|
|
395
|
+
line: lineNumberForOffset(contents, match.index ?? 0)
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
return [...seen.values()];
|
|
399
|
+
}
|
|
400
|
+
function lineNumberForOffset(contents, offset) {
|
|
401
|
+
let line = 1;
|
|
402
|
+
for (let index = 0; index < offset; index += 1) {
|
|
403
|
+
if (contents.charCodeAt(index) === 10) {
|
|
404
|
+
line += 1;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return line;
|
|
408
|
+
}
|
|
409
|
+
function isInsideSafeProviderModelResolver(contents, offset) {
|
|
410
|
+
const prefix = contents.slice(Math.max(0, offset - 80), offset);
|
|
411
|
+
return prefix.includes("sanitizeProviderModel(");
|
|
412
|
+
}
|
|
413
|
+
//# sourceMappingURL=template-certification.js.map
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { pathToFileURL } from "node:url";
|
|
2
|
+
export async function loadTemplateFromModule(modulePath) {
|
|
3
|
+
const loaded = await import(pathToFileURL(modulePath).href);
|
|
4
|
+
const candidates = Object.values(loaded);
|
|
5
|
+
for (const candidate of candidates) {
|
|
6
|
+
if (isTemplateDefinition(candidate)) {
|
|
7
|
+
return normalizeTemplateDefinition(candidate);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
throw new Error(`No template definition export found in ${modulePath}`);
|
|
11
|
+
}
|
|
12
|
+
function normalizeTemplateDefinition(template) {
|
|
13
|
+
return {
|
|
14
|
+
...template,
|
|
15
|
+
about: {
|
|
16
|
+
...template.about,
|
|
17
|
+
// Older runtime template releases may predate prompt metadata fields.
|
|
18
|
+
difficulty: template.about.difficulty ?? "easy",
|
|
19
|
+
links_to_inspiration: normalizeTemplateInspirationLinks(template.about.links_to_inspiration, template.about.link_to_original),
|
|
20
|
+
sound: normalizeTemplateSound(template.about.sound),
|
|
21
|
+
proposal_generator: template.about.proposal_generator ?? "",
|
|
22
|
+
skeleton_prompt: template.about.skeleton_prompt ?? "",
|
|
23
|
+
sample_prompts: template.about.sample_prompts ?? []
|
|
24
|
+
},
|
|
25
|
+
examples: template.examples ?? []
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function isTemplateDefinition(value) {
|
|
29
|
+
if (!value || typeof value !== "object") {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
const candidate = value;
|
|
33
|
+
const about = typeof candidate.about === "object" && candidate.about !== null
|
|
34
|
+
? candidate.about
|
|
35
|
+
: null;
|
|
36
|
+
const previewMedia = about && Array.isArray(about.preview_media)
|
|
37
|
+
? about.preview_media
|
|
38
|
+
: null;
|
|
39
|
+
const samplePrompts = about && Array.isArray(about.sample_prompts)
|
|
40
|
+
? about.sample_prompts
|
|
41
|
+
: null;
|
|
42
|
+
const sound = about && typeof about.sound === "object" && about.sound !== null
|
|
43
|
+
? about.sound
|
|
44
|
+
: null;
|
|
45
|
+
const linksToInspiration = about && Array.isArray(about.links_to_inspiration)
|
|
46
|
+
? about.links_to_inspiration
|
|
47
|
+
: null;
|
|
48
|
+
const examples = Array.isArray(candidate.examples)
|
|
49
|
+
? candidate.examples
|
|
50
|
+
: null;
|
|
51
|
+
return (typeof candidate.id === "string" &&
|
|
52
|
+
typeof candidate.slugId === "string" &&
|
|
53
|
+
typeof candidate.version === "string" &&
|
|
54
|
+
about !== null &&
|
|
55
|
+
typeof about.title === "string" &&
|
|
56
|
+
typeof about.description === "string" &&
|
|
57
|
+
(["easy", "medium", "hard", "expert"].includes(String(about.difficulty)) || typeof about.difficulty === "undefined") &&
|
|
58
|
+
typeof about.viral_dna === "string" &&
|
|
59
|
+
typeof about.visual_dna === "string" &&
|
|
60
|
+
Array.isArray(previewMedia) &&
|
|
61
|
+
previewMedia.every((entry) => typeof entry === "string") &&
|
|
62
|
+
typeof about.link_to_original === "string" &&
|
|
63
|
+
(linksToInspiration === null || linksToInspiration.every((entry) => typeof entry === "string")) &&
|
|
64
|
+
(sound === null || (typeof sound.sound_title === "string" &&
|
|
65
|
+
typeof sound.sound_url === "string")) &&
|
|
66
|
+
(typeof about.proposal_generator === "string" || typeof about.proposal_generator === "undefined") &&
|
|
67
|
+
(typeof about.skeleton_prompt === "string" || typeof about.skeleton_prompt === "undefined") &&
|
|
68
|
+
(samplePrompts === null || samplePrompts.every((entry) => typeof entry === "string")) &&
|
|
69
|
+
(examples === null || examples.every((example) => Array.isArray(example) && example.every((entry) => {
|
|
70
|
+
if (!entry || typeof entry !== "object") {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
const media = entry;
|
|
74
|
+
return typeof media.media_url === "string" && typeof media.note === "string";
|
|
75
|
+
}))) &&
|
|
76
|
+
typeof candidate.configSchema === "object" &&
|
|
77
|
+
typeof candidate.operations === "object" &&
|
|
78
|
+
typeof candidate.jobs === "object");
|
|
79
|
+
}
|
|
80
|
+
function normalizeTemplateSound(value) {
|
|
81
|
+
if (!value || typeof value !== "object") {
|
|
82
|
+
return {
|
|
83
|
+
sound_title: "",
|
|
84
|
+
sound_url: ""
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const sound = value;
|
|
88
|
+
return {
|
|
89
|
+
sound_title: typeof sound.sound_title === "string" ? sound.sound_title : "",
|
|
90
|
+
sound_url: typeof sound.sound_url === "string" ? sound.sound_url : ""
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function normalizeTemplateInspirationLinks(value, linkToOriginal) {
|
|
94
|
+
if (Array.isArray(value)) {
|
|
95
|
+
return value.filter((entry) => typeof entry === "string");
|
|
96
|
+
}
|
|
97
|
+
return linkToOriginal ? [linkToOriginal] : [];
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=template-loader.js.map
|