@mevdragon/vidfarm-devcli 0.5.3 → 0.7.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 (116) hide show
  1. package/README.md +3 -3
  2. package/SKILL.director.md +28 -7
  3. package/SKILL.platform.md +9 -5
  4. package/demo/README.md +28 -0
  5. package/demo/dist/app.css +1 -0
  6. package/demo/dist/app.js +1184 -0
  7. package/demo/dist/chunks/chunk-DXB73IDG.js +1 -0
  8. package/demo/dist/chunks/chunk-S7OWAJDS.js +36 -0
  9. package/demo/dist/chunks/chunk-VTIBZ6AN.js +1 -0
  10. package/demo/dist/chunks/dist-ADSJKBVE.js +332 -0
  11. package/demo/dist/chunks/domEditingLayers-VZMLL4AP-SGHWPND4.js +1 -0
  12. package/demo/dist/chunks/hyperframes-player-XB65TCD6.js +425 -0
  13. package/demo/dist/chunks/lib-XAQ37YOE.js +1 -0
  14. package/demo/dist/chunks/src-TJ2QYA4U.js +207 -0
  15. package/demo/dist/favicon.ico +0 -0
  16. package/demo/dist/icons/timeline/audio.svg +7 -0
  17. package/demo/dist/icons/timeline/captions.svg +5 -0
  18. package/demo/dist/icons/timeline/composition.svg +12 -0
  19. package/demo/dist/icons/timeline/image.svg +18 -0
  20. package/demo/dist/icons/timeline/music.svg +10 -0
  21. package/demo/dist/icons/timeline/text.svg +3 -0
  22. package/demo/dist/index.html +15 -0
  23. package/dist/src/account-pages-legacy.js +9396 -0
  24. package/dist/src/account-pages.js +61 -0
  25. package/dist/src/app.js +14663 -0
  26. package/dist/src/cli.js +60 -97
  27. package/dist/src/composition-runtime.js +613 -0
  28. package/dist/src/config.js +173 -0
  29. package/dist/src/context.js +447 -0
  30. package/dist/src/dev-app-legacy.js +739 -0
  31. package/dist/src/dev-app.js +6 -0
  32. package/dist/src/domain.js +2 -0
  33. package/dist/src/editor-chat-history.js +82 -0
  34. package/dist/src/editor-chat.js +449 -0
  35. package/dist/src/editor-dark-theme.js +1128 -0
  36. package/dist/src/frontend/debug.js +71 -0
  37. package/dist/src/frontend/flockposter-cache-store.js +124 -0
  38. package/dist/src/frontend/homepage-client.js +182 -0
  39. package/dist/src/frontend/homepage-shared.js +4 -0
  40. package/dist/src/frontend/homepage-store.js +28 -0
  41. package/dist/src/frontend/homepage-view.js +547 -0
  42. package/dist/src/frontend/page-runtime-client.js +132 -0
  43. package/dist/src/frontend/page-runtime-store.js +9 -0
  44. package/dist/src/frontend/sentry.js +42 -0
  45. package/dist/src/frontend/template-editor-chat.js +3960 -0
  46. package/dist/src/help-page.js +346 -0
  47. package/dist/src/homepage.js +1235 -0
  48. package/dist/src/hyperframes/composition.js +180 -0
  49. package/dist/src/index.js +16 -0
  50. package/dist/src/instrument.js +30 -0
  51. package/dist/src/lib/crypto.js +45 -0
  52. package/dist/src/lib/dev-log.js +54 -0
  53. package/dist/src/lib/display-name.js +11 -0
  54. package/dist/src/lib/ids.js +24 -0
  55. package/dist/src/lib/images.js +19 -0
  56. package/dist/src/lib/json.js +15 -0
  57. package/dist/src/lib/package-root.js +47 -0
  58. package/dist/src/lib/template-paths.js +28 -0
  59. package/dist/src/lib/time.js +7 -0
  60. package/dist/src/lib/url-clean.js +85 -0
  61. package/dist/src/page-runtime.js +2 -0
  62. package/dist/src/page-shell.js +1381 -0
  63. package/dist/src/primitive-context.js +357 -0
  64. package/dist/src/primitive-registry.js +2561 -0
  65. package/dist/src/primitive-sdk.js +4 -0
  66. package/dist/src/primitives/hyperframes-media.js +108 -0
  67. package/dist/src/react-page-shell.js +35 -0
  68. package/dist/src/ready-post-schedule-component.js +1540 -0
  69. package/dist/src/registry.js +296 -0
  70. package/dist/src/runtime.js +35 -0
  71. package/dist/src/services/api-call-history.js +249 -0
  72. package/dist/src/services/auth.js +152 -0
  73. package/dist/src/services/billing-pricing.js +39 -0
  74. package/dist/src/services/billing.js +228 -0
  75. package/dist/src/services/cast.js +127 -0
  76. package/dist/src/services/chat-threads.js +92 -0
  77. package/dist/src/services/composition-sanitize.js +124 -0
  78. package/dist/src/services/composition-watch.js +79 -0
  79. package/dist/src/services/fork-access.js +93 -0
  80. package/dist/src/services/fork-manifest.js +42 -0
  81. package/dist/src/services/ghostcut.js +179 -0
  82. package/dist/src/services/hyperframes.js +2307 -0
  83. package/dist/src/services/job-capacity.js +14 -0
  84. package/dist/src/services/job-logs.js +197 -0
  85. package/dist/src/services/jobs.js +136 -0
  86. package/dist/src/services/local-dynamo.js +0 -0
  87. package/dist/src/services/media-processing.js +766 -0
  88. package/dist/src/services/primitive-media-lambda.js +280 -0
  89. package/dist/src/services/providers.js +2926 -0
  90. package/dist/src/services/rate-limits.js +262 -0
  91. package/dist/src/services/serverless-auth.js +382 -0
  92. package/dist/src/services/serverless-jobs.js +1082 -0
  93. package/dist/src/services/serverless-provider-keys.js +409 -0
  94. package/dist/src/services/serverless-records.js +1385 -0
  95. package/dist/src/services/serverless-template-configs.js +75 -0
  96. package/dist/src/services/storage.js +383 -0
  97. package/dist/src/services/template-certification.js +413 -0
  98. package/dist/src/services/template-loader.js +99 -0
  99. package/dist/src/services/template-runtime-bundles.js +217 -0
  100. package/dist/src/services/template-sources.js +1017 -0
  101. package/dist/src/services/upstream.js +248 -0
  102. package/dist/src/services/video-normalization.js +2 -0
  103. package/dist/src/services/webhooks.js +62 -0
  104. package/dist/src/template-editor-pages.js +2576 -0
  105. package/dist/src/template-editor-shell.js +2840 -0
  106. package/dist/src/template-sdk.js +4 -0
  107. package/dist/src/worker.js +17 -0
  108. package/package.json +6 -4
  109. package/public/assets/homepage-app.js +54 -0
  110. package/public/assets/homepage-client-app.js +80 -0
  111. package/public/assets/page-runtime-client-app.js +94 -0
  112. package/src/assets/SELLING_AWARENESS_STAGES.md +579 -0
  113. package/src/assets/SELLING_WITH_HOOKS.md +377 -0
  114. package/src/assets/SELLING_WITH_VSLS.md +606 -0
  115. package/src/assets/favicon.ico +0 -0
  116. package/src/assets/logo-vidfarm.png +0 -0
@@ -0,0 +1,173 @@
1
+ import { mkdirSync } from "node:fs";
2
+ import path from "node:path";
3
+ import dotenv from "dotenv";
4
+ import { z } from "zod";
5
+ dotenv.config();
6
+ function normalizeEnvValue(value) {
7
+ if (typeof value !== "string") {
8
+ return value;
9
+ }
10
+ const trimmed = value.trim();
11
+ const unquoted = ((trimmed.startsWith("\"") && trimmed.endsWith("\""))
12
+ || (trimmed.startsWith("'") && trimmed.endsWith("'")))
13
+ ? trimmed.slice(1, -1).trim()
14
+ : trimmed;
15
+ return unquoted === "" ? undefined : unquoted;
16
+ }
17
+ const optionalSampleRate = z.preprocess((value) => value === "" || value == null ? undefined : value, z.coerce.number().min(0).max(1).optional());
18
+ const schema = z.object({
19
+ NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
20
+ VIDFARM_DEPLOYMENT_TARGET: z.string().optional(),
21
+ VIDFARM_RUNTIME_BACKEND: z.literal("serverless").default("serverless"),
22
+ VIDFARM_DEBUG_LOGS: z.string().default("false"),
23
+ VIDFARM_LOAD_LOCAL_TEMPLATES: z.enum(["auto", "true", "false"]).default("auto"),
24
+ PORT: z.coerce.number().default(3000),
25
+ VIDFARM_DATA_DIR: z.string().default("./data"),
26
+ ENCRYPTION_SECRET: z.string().default("development-encryption-secret-change-me"),
27
+ API_KEY_SALT: z.string().default("development-api-key-salt"),
28
+ WEBHOOK_SECRET: z.string().default("development-webhook-secret"),
29
+ DEFAULT_JOB_DELAY_SECONDS: z.coerce.number().default(20),
30
+ MAX_JOB_WALL_CLOCK_SECONDS: z.coerce.number().default(6 * 60 * 60),
31
+ STALE_RUNNING_JOB_SECONDS: z.coerce.number().default(60 * 60),
32
+ PROVIDER_LEASE_WAIT_TIMEOUT_SECONDS: z.coerce.number().default(5 * 60),
33
+ PROVIDER_LEASE_INLINE_WAIT_SECONDS: z.coerce.number().default(5),
34
+ PROVIDER_LEASE_STEP_WAIT_SECONDS: z.coerce.number().int().min(1).default(60),
35
+ PROVIDER_LEASE_MAX_STEP_WAIT_SECONDS: z.coerce.number().int().min(1).default(12 * 60 * 60),
36
+ MAX_ACTIVE_JOBS_PER_CUSTOMER: z.coerce.number().int().min(1).default(30),
37
+ STORAGE_DRIVER: z.enum(["local", "s3"]).default("local"),
38
+ // Records/auth backend. Defaults to `dynamo` so deployed environments are
39
+ // unaffected; `vidfarm serve` sets this to `local` to run fully on disk.
40
+ RECORDS_DRIVER: z.enum(["local", "dynamo"]).default("dynamo"),
41
+ AWS_REGION: z.string().default("us-east-1"),
42
+ AWS_S3_BUCKET: z.string().default(""),
43
+ AWS_S3_ENDPOINT: z.string().optional(),
44
+ AWS_ACCESS_KEY_ID: z.string().optional(),
45
+ AWS_SECRET_ACCESS_KEY: z.string().optional(),
46
+ PUBLIC_BASE_URL: z.string().optional(),
47
+ PUBLIC_EDITOR_CHAT_URL: z.string().url().optional(),
48
+ VIDFARM_USER_ID: z.string().optional(),
49
+ VIDFARM_API_KEY: z.string().optional(),
50
+ VIDFARM_STAGING_BOOTSTRAP_USER_ID: z.string().optional(),
51
+ VIDFARM_STAGING_BOOTSTRAP_EMAIL: z.string().optional(),
52
+ VIDFARM_STAGING_BOOTSTRAP_API_KEY: z.string().optional(),
53
+ EDITOR_CHAT_URL: z.string().url().optional(),
54
+ RESEND_API_KEY: z.string().optional(),
55
+ RESEND_FROM_EMAIL: z.string().default("vidfarm@fwd.zoomgtm.com"),
56
+ FLOCKPOSTER_API_URL: z.string().url().optional(),
57
+ VIDFARM_ZIP_LAMBDA_URL: z.string().url().optional(),
58
+ VIDFARM_ZIP_LAMBDA_SECRET: z.string().optional(),
59
+ VIDFARM_PRIMITIVE_MEDIA_LAMBDA_URL: z.string().url().optional(),
60
+ VIDFARM_PRIMITIVE_MEDIA_LAMBDA_SECRET: z.string().optional(),
61
+ VIDFARM_BILLING_URL: z.string().url().optional(),
62
+ VIDFARM_BILLING_SECRET: z.string().optional(),
63
+ VIDFARM_MIN_RENDER_WALLET_BALANCE_USD: z.coerce.number().default(1),
64
+ OPENAI_API_KEY: z.string().optional(),
65
+ OPENROUTER_API_KEY: z.string().optional(),
66
+ GEMINI_API_KEY: z.string().optional(),
67
+ PERPLEXITY_API_KEY: z.string().optional(),
68
+ GHOSTCUT_KEY: z.string().optional(),
69
+ GHOSTCUT_SECRET: z.string().optional(),
70
+ GHOSTCUT_USD_PER_30S: z.coerce.number().min(0).default(0.10),
71
+ // Cloud passthrough for `vidfarm serve`: the local box keeps editing/render
72
+ // fully local but lists the upstream (prod) catalog in /discover and
73
+ // /library, seeds forks on demand, and can hand a render off to the cloud.
74
+ // Set by the serve command from --host + the ambient cloud VIDFARM_API_KEY;
75
+ // never set on deployed environments.
76
+ VIDFARM_UPSTREAM_HOST: z.string().optional(),
77
+ VIDFARM_UPSTREAM_API_KEY: z.string().optional(),
78
+ RAPIDAPI_KEY: z.string().optional(),
79
+ RAPIDAPI_VIDEO_DOWNLOAD_URL: z.string().url().default("https://snap-video3.p.rapidapi.com/download"),
80
+ RAPIDAPI_VIDEO_DOWNLOAD_HOST: z.string().default("snap-video3.p.rapidapi.com"),
81
+ RAPIDAPI_VIDEO_DOWNLOAD_BASE_COST_USD: z.coerce.number().min(0).default(0.005),
82
+ RAPIDAPI_VIDEO_DOWNLOAD_LAMBDA_COST_USD: z.coerce.number().min(0).default(0.0002),
83
+ RAPIDAPI_REMOVE_BACKGROUND_URL: z.string().url().default("https://remove-background18.p.rapidapi.com/public/remove-background/url"),
84
+ RAPIDAPI_REMOVE_BACKGROUND_HOST: z.string().default("remove-background18.p.rapidapi.com"),
85
+ RAPIDAPI_REMOVE_BACKGROUND_COST_USD: z.coerce.number().min(0).default(0.005),
86
+ SUPERAGENCY_KEY: z.string().optional(),
87
+ ARTIFACT_SIGNING_SECRET: z.string().optional(),
88
+ HYPERFRAMES_LAMBDA_STACK_NAME: z.string().default("hyperframes-vidfarm-prod"),
89
+ HYPERFRAMES_RENDER_REGION: z.string().optional(),
90
+ HYPERFRAMES_RENDER_BUCKET_NAME: z.string().optional(),
91
+ HYPERFRAMES_LAMBDA_BUCKET_NAME: z.string().optional(),
92
+ HYPERFRAMES_RENDER_STATE_MACHINE_ARN: z.string().optional(),
93
+ HYPERFRAMES_STATE_MACHINE_ARN: z.string().optional(),
94
+ HYPERFRAMES_RENDER_MEMORY_MB: z.coerce.number().int().min(512).default(3072),
95
+ // Force the in-process (single-machine, headless-Chromium) HyperFrames
96
+ // renderer even when a cloud render stack is configured. When unset, the
97
+ // renderer auto-selects local whenever no cloud render stack is wired (the
98
+ // `vidfarm serve` case). Accepts "1"/"true"/"yes".
99
+ VIDFARM_LOCAL_RENDER: z
100
+ .string()
101
+ .optional()
102
+ .transform((value) => /^(1|true|yes)$/i.test(String(value ?? ""))),
103
+ MOCK_PROVIDER_RESPONSES: z.string().optional(),
104
+ VIDFARM_ADMIN_EMAILS: z.string().default(""),
105
+ VIDFARM_GITHUB_TOKEN: z.string().optional(),
106
+ GITHUB_TOKEN: z.string().optional(),
107
+ TEMPLATE_SOURCE_ROOT: z.string().default("./data/template-sources"),
108
+ VIDFARM_RATE_LIMITS_TABLE_NAME: z.string().optional(),
109
+ VIDFARM_SERVERLESS_MAIN_TABLE_NAME: z.string().optional(),
110
+ VIDFARM_JOB_STATE_MACHINE_ARN: z.string().optional(),
111
+ SENTRY_DSN: z.string().optional(),
112
+ SENTRY_ENVIRONMENT: z.string().optional(),
113
+ SENTRY_RELEASE: z.string().optional(),
114
+ SENTRY_TRACES_SAMPLE_RATE: optionalSampleRate,
115
+ SENTRY_ENABLE_LOGS: z.string().default("true"),
116
+ SENTRY_SEND_DEFAULT_PII: z.string().default("false"),
117
+ SENTRY_DEBUG_ROUTE: z.string().default("false"),
118
+ PUBLIC_SENTRY_DSN: z.string().optional(),
119
+ PUBLIC_SENTRY_TRACES_SAMPLE_RATE: optionalSampleRate,
120
+ PUBLIC_SENTRY_REPLAYS_SESSION_SAMPLE_RATE: optionalSampleRate,
121
+ PUBLIC_SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE: optionalSampleRate,
122
+ PUBLIC_SENTRY_ENABLE_LOGS: z.string().default("true"),
123
+ PUBLIC_SENTRY_SEND_DEFAULT_PII: z.string().default("true"),
124
+ PUBLIC_SENTRY_TRACE_PROPAGATION_TARGETS: z.string().default("")
125
+ }).superRefine((data, ctx) => {
126
+ if (data.NODE_ENV === "production" && data.VIDFARM_RUNTIME_BACKEND === "serverless") {
127
+ if (!data.VIDFARM_SERVERLESS_MAIN_TABLE_NAME) {
128
+ ctx.addIssue({
129
+ code: z.ZodIssueCode.custom,
130
+ path: ["VIDFARM_SERVERLESS_MAIN_TABLE_NAME"],
131
+ message: "VIDFARM_SERVERLESS_MAIN_TABLE_NAME is required when VIDFARM_RUNTIME_BACKEND=serverless in production"
132
+ });
133
+ }
134
+ if (!data.VIDFARM_RATE_LIMITS_TABLE_NAME) {
135
+ ctx.addIssue({
136
+ code: z.ZodIssueCode.custom,
137
+ path: ["VIDFARM_RATE_LIMITS_TABLE_NAME"],
138
+ message: "VIDFARM_RATE_LIMITS_TABLE_NAME is required when VIDFARM_RUNTIME_BACKEND=serverless in production"
139
+ });
140
+ }
141
+ if (!data.VIDFARM_JOB_STATE_MACHINE_ARN) {
142
+ ctx.addIssue({
143
+ code: z.ZodIssueCode.custom,
144
+ path: ["VIDFARM_JOB_STATE_MACHINE_ARN"],
145
+ message: "VIDFARM_JOB_STATE_MACHINE_ARN is required when VIDFARM_RUNTIME_BACKEND=serverless in production"
146
+ });
147
+ }
148
+ }
149
+ });
150
+ const normalizedEnv = Object.fromEntries(Object.entries(process.env).map(([key, value]) => [key, normalizeEnvValue(value)]));
151
+ const parsed = schema.parse(normalizedEnv);
152
+ const dataDir = path.resolve(parsed.VIDFARM_DATA_DIR);
153
+ mkdirSync(dataDir, { recursive: true });
154
+ const templateSourceRoot = path.resolve(parsed.TEMPLATE_SOURCE_ROOT);
155
+ mkdirSync(templateSourceRoot, { recursive: true });
156
+ const publicBaseUrl = parsed.PUBLIC_BASE_URL || `http://localhost:${parsed.PORT}`;
157
+ export const config = {
158
+ ...parsed,
159
+ VIDFARM_DATA_DIR: dataDir,
160
+ TEMPLATE_SOURCE_ROOT: templateSourceRoot,
161
+ PUBLIC_BASE_URL: publicBaseUrl,
162
+ ARTIFACT_SIGNING_SECRET: parsed.ARTIFACT_SIGNING_SECRET || parsed.SUPERAGENCY_KEY || "",
163
+ isProduction: parsed.NODE_ENV === "production",
164
+ mockProviders: parsed.MOCK_PROVIDER_RESPONSES === "true" ||
165
+ (parsed.MOCK_PROVIDER_RESPONSES == null && parsed.NODE_ENV !== "production"),
166
+ minRenderWalletBalanceUsd: parsed.VIDFARM_MIN_RENDER_WALLET_BALANCE_USD,
167
+ debugLogs: parsed.VIDFARM_DEBUG_LOGS === "true" || parsed.VIDFARM_DEPLOYMENT_TARGET === "serverless-staging",
168
+ adminEmails: parsed.VIDFARM_ADMIN_EMAILS
169
+ .split(",")
170
+ .map((value) => value.trim().toLowerCase())
171
+ .filter(Boolean),
172
+ };
173
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1,447 @@
1
+ import { existsSync, statSync } from "node:fs";
2
+ import path from "node:path";
3
+ import * as Sentry from "@sentry/node";
4
+ import { config } from "./config.js";
5
+ import { devErrorFields, devLog } from "./lib/dev-log.js";
6
+ import { createId } from "./lib/ids.js";
7
+ import { estimateNormalizationCostUsd, normalizeVideoAsset } from "./services/video-normalization.js";
8
+ export function createTemplateJobContext(input) {
9
+ const templateConfig = {};
10
+ const prefix = input.storage.templateJobPrefix(input.template.id, input.customer.id, input.job.id);
11
+ const buildArtifactUrl = (storageKey, fallbackUrl) => fallbackUrl ?? input.storage.getPublicUrl(storageKey) ?? buildTemplateMediaUrl(storageKey);
12
+ const templatePreviewMediaUrls = resolveTemplatePreviewMediaUrls(input.template, input.storage);
13
+ const jobPreviewMediaPolicy = parseTemplatePreviewMediaPolicy(input.job.payload._templatePreviewMediaPolicy);
14
+ // Billing records are written after the billable work (render, normalize,
15
+ // etc.) has already succeeded. A transient billing failure must never fail
16
+ // the job — the customer would retry and pay for the expensive work twice.
17
+ const recordBillingNonFatal = async (record) => {
18
+ try {
19
+ await input.billing.record(record);
20
+ }
21
+ catch (error) {
22
+ Sentry.captureException(error);
23
+ devLog("billing.record_failed_nonfatal", {
24
+ job_id: input.job.id,
25
+ customer_id: input.customer.id,
26
+ template_id: input.template.id,
27
+ cost_center_slug: record.costCenterSlug ?? null,
28
+ idempotency_key: record.idempotencyKey ?? null,
29
+ ...devErrorFields(error)
30
+ }, "warn");
31
+ }
32
+ };
33
+ return {
34
+ env: config.isProduction ? "production" : "development",
35
+ customer: input.customer,
36
+ template: {
37
+ id: input.template.id,
38
+ slugId: input.template.slugId,
39
+ previewMediaUrls: templatePreviewMediaUrls,
40
+ previewMediaPolicy: jobPreviewMediaPolicy
41
+ },
42
+ templateConfig,
43
+ logger: {
44
+ debug(message, metadata = {}) {
45
+ void input.jobs.addLogEvent({
46
+ id: createId("evt"),
47
+ jobId: input.job.id,
48
+ level: "debug",
49
+ message,
50
+ metadata,
51
+ progress: null,
52
+ artifactKey: null,
53
+ createdAt: new Date().toISOString()
54
+ });
55
+ },
56
+ info(message, metadata = {}) {
57
+ void input.jobs.addLogEvent({
58
+ id: createId("evt"),
59
+ jobId: input.job.id,
60
+ level: "info",
61
+ message,
62
+ metadata,
63
+ progress: null,
64
+ artifactKey: null,
65
+ createdAt: new Date().toISOString()
66
+ });
67
+ },
68
+ warn(message, metadata = {}) {
69
+ void input.jobs.addLogEvent({
70
+ id: createId("evt"),
71
+ jobId: input.job.id,
72
+ level: "warn",
73
+ message,
74
+ metadata,
75
+ progress: null,
76
+ artifactKey: null,
77
+ createdAt: new Date().toISOString()
78
+ });
79
+ },
80
+ error(message, metadata = {}) {
81
+ void input.jobs.addLogEvent({
82
+ id: createId("evt"),
83
+ jobId: input.job.id,
84
+ level: "error",
85
+ message,
86
+ metadata,
87
+ progress: null,
88
+ artifactKey: null,
89
+ createdAt: new Date().toISOString()
90
+ });
91
+ },
92
+ progress(progress, message, metadata = {}) {
93
+ void input.jobs.addLogEvent({
94
+ id: createId("evt"),
95
+ jobId: input.job.id,
96
+ level: "info",
97
+ message,
98
+ metadata,
99
+ progress,
100
+ artifactKey: typeof metadata.artifactKey === "string" ? metadata.artifactKey : null,
101
+ createdAt: new Date().toISOString()
102
+ });
103
+ void input.jobs.updateJobStatus({
104
+ id: input.job.id,
105
+ status: "running",
106
+ progress
107
+ });
108
+ }
109
+ },
110
+ jobs: {
111
+ async enqueueChild({ operationName, workflowName, payload, providerHint }) {
112
+ const child = await input.jobs.enqueueChildJob({
113
+ templateId: input.template.id,
114
+ operationName,
115
+ workflowName,
116
+ tracer: input.job.tracer,
117
+ payload,
118
+ customerId: input.customer.id,
119
+ parentJobId: input.job.id,
120
+ providerHint
121
+ });
122
+ return { jobId: child.id };
123
+ }
124
+ },
125
+ storage: {
126
+ async putJson(key, value) {
127
+ const stored = await input.storage.putJson(`${prefix}/${key}`, value);
128
+ const artifactUrl = buildArtifactUrl(stored.key, stored.url);
129
+ await insertArtifact("json", stored.key, artifactUrl, {});
130
+ return {
131
+ ...stored,
132
+ url: artifactUrl
133
+ };
134
+ },
135
+ async putText(key, value, contentType = "text/plain; charset=utf-8") {
136
+ const stored = await input.storage.putText(`${prefix}/${key}`, value, contentType);
137
+ const artifactUrl = buildArtifactUrl(stored.key, stored.url);
138
+ await insertArtifact("text", stored.key, artifactUrl, { contentType });
139
+ return {
140
+ ...stored,
141
+ url: artifactUrl
142
+ };
143
+ },
144
+ async putBuffer(key, value, options = {}) {
145
+ const stored = await input.storage.putBuffer(`${prefix}/${key}`, value, options.contentType);
146
+ const artifactUrl = buildArtifactUrl(stored.key, stored.url);
147
+ await insertArtifact(options.kind ?? "binary", stored.key, artifactUrl, {
148
+ ...(options.metadata ?? {}),
149
+ contentType: options.contentType ?? null
150
+ });
151
+ return {
152
+ ...stored,
153
+ url: artifactUrl
154
+ };
155
+ },
156
+ getPublicUrl(key) {
157
+ const storageKey = `${prefix}/${key}`;
158
+ return input.storage.getPublicUrl(storageKey) ?? buildTemplateMediaUrl(storageKey);
159
+ }
160
+ },
161
+ billing: {
162
+ async record(record) {
163
+ await recordBillingNonFatal({
164
+ customerId: input.customer.id,
165
+ jobId: input.job.id,
166
+ tracer: input.job.tracer,
167
+ templateId: input.template.id,
168
+ type: record.type,
169
+ costUsd: record.costUsd,
170
+ chargeUsd: record.chargeUsd ?? Number((record.costUsd * 1.2).toFixed(4)),
171
+ costCenterSlug: record.costCenterSlug,
172
+ idempotencyKey: record.idempotencyKey,
173
+ occurredAtMs: record.occurredAtMs,
174
+ metadata: record.metadata
175
+ });
176
+ }
177
+ },
178
+ providers: {
179
+ listAvailableProviders() {
180
+ return input.providers.listAvailableProviders(input.customer.id);
181
+ },
182
+ async generateText(request) {
183
+ return input.providers.generateText({
184
+ customerId: input.customer.id,
185
+ jobId: input.job.id,
186
+ workerId: input.workerId,
187
+ ...request
188
+ });
189
+ },
190
+ async generateSpeech(request) {
191
+ return input.providers.generateSpeech({
192
+ customerId: input.customer.id,
193
+ jobId: input.job.id,
194
+ workerId: input.workerId,
195
+ ...request
196
+ });
197
+ },
198
+ async generateImage(request) {
199
+ return input.providers.generateImage({
200
+ customerId: input.customer.id,
201
+ jobId: input.job.id,
202
+ workerId: input.workerId,
203
+ ...request
204
+ });
205
+ },
206
+ async analyzeImageLayout(request) {
207
+ return input.providers.analyzeImageLayout({
208
+ customerId: input.customer.id,
209
+ jobId: input.job.id,
210
+ workerId: input.workerId,
211
+ ...request
212
+ });
213
+ },
214
+ async transcribeSpeech(request) {
215
+ return input.providers.transcribeSpeech({
216
+ customerId: input.customer.id,
217
+ jobId: input.job.id,
218
+ workerId: input.workerId,
219
+ ...request
220
+ });
221
+ }
222
+ },
223
+ hyperframes: {
224
+ async render(request) {
225
+ const hyperframesOutputKey = `renders/vidfarm/templates/${input.template.id}/${input.customer.id}/${input.job.id}/output.mp4`;
226
+ const render = await input.hyperframes.render({
227
+ ...request,
228
+ outputKey: hyperframesOutputKey,
229
+ wait: true
230
+ });
231
+ let outputUrl = render.outputUrl;
232
+ let platformOutputKey = null;
233
+ if (render.outputS3Uri) {
234
+ const output = await input.hyperframes.readRenderOutput(render.outputS3Uri);
235
+ platformOutputKey = `${prefix}/${request.outputKey || "renders/output.mp4"}`;
236
+ const stored = await input.storage.putBuffer(platformOutputKey, output.bytes, output.contentType);
237
+ outputUrl = buildArtifactUrl(stored.key, stored.url);
238
+ await insertArtifact("video", stored.key, outputUrl, {
239
+ operation: "hyperframes-render",
240
+ contentType: output.contentType,
241
+ renderId: render.renderId,
242
+ outputS3Uri: render.outputS3Uri
243
+ });
244
+ }
245
+ const estimatedCostUsd = typeof render.metadata.estimatedCostUsd === "number" ? render.metadata.estimatedCostUsd : null;
246
+ const chargeUsd = typeof render.metadata.chargeUsd === "number" ? render.metadata.chargeUsd : null;
247
+ if (estimatedCostUsd !== null && estimatedCostUsd > 0) {
248
+ await recordBillingNonFatal({
249
+ customerId: input.customer.id,
250
+ jobId: input.job.id,
251
+ tracer: input.job.tracer,
252
+ templateId: input.template.id,
253
+ type: "render",
254
+ costUsd: estimatedCostUsd,
255
+ chargeUsd: typeof chargeUsd === "number" ? chargeUsd : undefined,
256
+ costCenterSlug: "hyperframes_lambda",
257
+ idempotencyKey: `hyperframes_lambda:${render.renderId}`,
258
+ occurredAtMs: Date.now(),
259
+ metadata: {
260
+ ...render.metadata,
261
+ render_id: render.renderId,
262
+ output_url: outputUrl,
263
+ signed_output_url: render.outputUrl,
264
+ output_s3_uri: render.outputS3Uri,
265
+ platform_output_key: platformOutputKey
266
+ }
267
+ });
268
+ }
269
+ return {
270
+ renderId: render.renderId,
271
+ outputUrl,
272
+ outputS3Uri: render.outputS3Uri,
273
+ metadata: {
274
+ ...render.metadata,
275
+ outputUrl,
276
+ signedOutputUrl: render.outputUrl,
277
+ platformOutputKey
278
+ }
279
+ };
280
+ }
281
+ },
282
+ media: {
283
+ async normalizeVideo(request) {
284
+ const normalized = await normalizeVideoAsset({
285
+ sourceUrl: request.sourceUrl,
286
+ maxWidth: request.maxWidth,
287
+ maxHeight: request.maxHeight,
288
+ crf: request.crf,
289
+ preset: request.preset,
290
+ maxVideoBitrateKbps: request.maxVideoBitrateKbps,
291
+ audioBitrateKbps: request.audioBitrateKbps
292
+ });
293
+ const stored = await input.storage.putBuffer(`${prefix}/${request.outputKey}`, normalized.bytes, normalized.contentType);
294
+ const artifactUrl = buildArtifactUrl(stored.key, stored.url);
295
+ await insertArtifact("video", stored.key, artifactUrl, {
296
+ ...(request.metadata ?? {}),
297
+ operation: "normalize-video",
298
+ contentType: normalized.contentType,
299
+ normalization: normalized.metadata
300
+ });
301
+ await recordBillingNonFatal({
302
+ customerId: input.customer.id,
303
+ jobId: input.job.id,
304
+ tracer: input.job.tracer,
305
+ templateId: input.template.id,
306
+ type: "cpu_estimate",
307
+ costUsd: estimateNormalizationCostUsd(normalized.metadata),
308
+ costCenterSlug: "video_normalization",
309
+ idempotencyKey: `video_normalization:${input.job.id}:${request.outputKey}`,
310
+ occurredAtMs: Date.now(),
311
+ metadata: {
312
+ ...(request.metadata ?? {}),
313
+ operation: "normalize-video",
314
+ source_url: request.sourceUrl,
315
+ output_key: `${prefix}/${request.outputKey}`,
316
+ normalization: normalized.metadata
317
+ }
318
+ });
319
+ return {
320
+ ...stored,
321
+ url: artifactUrl,
322
+ contentType: normalized.contentType,
323
+ normalization: normalized.metadata
324
+ };
325
+ }
326
+ }
327
+ };
328
+ async function insertArtifact(kind, storageKey, publicUrl, metadata) {
329
+ await input.jobs.insertArtifact({
330
+ id: createId("art"),
331
+ jobId: input.job.id,
332
+ customerId: input.customer.id,
333
+ templateId: input.template.id,
334
+ kind,
335
+ storageKey,
336
+ publicUrl,
337
+ metadata
338
+ });
339
+ }
340
+ }
341
+ function parseTemplatePreviewMediaPolicy(value) {
342
+ if (value === "include" || value === "exclude" || value === "auto") {
343
+ return value;
344
+ }
345
+ if (value === true) {
346
+ return "include";
347
+ }
348
+ if (value === false) {
349
+ return "exclude";
350
+ }
351
+ return "auto";
352
+ }
353
+ function resolveTemplatePreviewMediaUrls(template, storage) {
354
+ return uniqueStrings(template.about.preview_media
355
+ .filter(isImagePreviewReference)
356
+ .map((entry) => resolveTemplatePreviewMediaUrl(template, storage, entry))
357
+ .filter((entry) => Boolean(entry)));
358
+ }
359
+ function resolveTemplatePreviewMediaUrl(template, storage, entry) {
360
+ if (/^https?:\/\//i.test(entry)) {
361
+ return entry;
362
+ }
363
+ const normalizedEntry = entry.replace(/^\/+/, "");
364
+ if (normalizedEntry.startsWith("templates/")) {
365
+ return resolveBundledTemplateAssetFromStorageKey(template, normalizedEntry)
366
+ ? buildTemplateMediaUrl(normalizedEntry)
367
+ : storage.getPublicUrl(normalizedEntry) ?? buildTemplateMediaUrl(normalizedEntry);
368
+ }
369
+ const templateAboutPrefix = storage.templateAboutKey(template.id, "");
370
+ const storageAssetPath = normalizedEntry.startsWith(templateAboutPrefix)
371
+ ? normalizedEntry.slice(templateAboutPrefix.length)
372
+ : normalizedEntry.startsWith("about/")
373
+ ? normalizedEntry.slice("about/".length)
374
+ : normalizedEntry;
375
+ const storageKey = storage.templateAboutKey(template.id, storageAssetPath);
376
+ return resolveBundledTemplateAsset(template, storageAssetPath)
377
+ ? buildTemplateMediaUrl(storageKey)
378
+ : storage.getPublicUrl(storageKey) ?? buildTemplateMediaUrl(storageKey);
379
+ }
380
+ function isImagePreviewReference(entry) {
381
+ return /\.(png|jpe?g|webp)(?:$|[?#])/i.test(entry);
382
+ }
383
+ function uniqueStrings(values) {
384
+ return Array.from(new Set(values.filter((value) => value.trim().length > 0)));
385
+ }
386
+ function buildTemplateMediaUrl(storageKey) {
387
+ return `${config.PUBLIC_BASE_URL.replace(/\/$/, "")}/template-media?key=${encodeURIComponent(storageKey)}`;
388
+ }
389
+ function resolveBundledTemplateAssetFromStorageKey(template, storageKey) {
390
+ const match = /^templates\/([^/]+)\/about\/(.+)$/.exec(storageKey.replace(/^\/+/, ""));
391
+ if (!match || match[1] !== template.id) {
392
+ return null;
393
+ }
394
+ return resolveBundledTemplateAsset(template, match[2]);
395
+ }
396
+ function resolveBundledTemplateAsset(template, assetPath) {
397
+ if (!template.skillPath || !assetPath) {
398
+ return null;
399
+ }
400
+ const normalizedAssetPath = assetPath.replace(/^\/+/, "");
401
+ if (!normalizedAssetPath || normalizedAssetPath.includes("\\") || normalizedAssetPath.split("/").some((segment) => !segment || segment === "." || segment === "..")) {
402
+ return null;
403
+ }
404
+ let templateRoot = path.resolve(template.skillPath);
405
+ try {
406
+ if (existsSync(templateRoot) && !statSync(templateRoot).isDirectory()) {
407
+ templateRoot = path.dirname(templateRoot);
408
+ }
409
+ }
410
+ catch {
411
+ return null;
412
+ }
413
+ const candidates = normalizedAssetPath.startsWith("about/")
414
+ ? [normalizedAssetPath]
415
+ : [`about/${normalizedAssetPath}`, normalizedAssetPath];
416
+ for (const candidate of candidates) {
417
+ const resolved = path.resolve(templateRoot, candidate);
418
+ if ((resolved === templateRoot || resolved.startsWith(`${templateRoot}${path.sep}`)) && existsSync(resolved)) {
419
+ return resolved;
420
+ }
421
+ }
422
+ return null;
423
+ }
424
+ function findTemplateRoot(modulePath) {
425
+ let current = path.dirname(modulePath);
426
+ for (let index = 0; index < 10; index += 1) {
427
+ if (existsSync(path.join(current, "template.config.json"))) {
428
+ return current;
429
+ }
430
+ const parent = path.dirname(current);
431
+ if (parent === current) {
432
+ break;
433
+ }
434
+ current = parent;
435
+ }
436
+ return null;
437
+ }
438
+ function nonBlank(value) {
439
+ const trimmed = value?.trim();
440
+ return trimmed ? trimmed : undefined;
441
+ }
442
+ function normalizePositiveInteger(value) {
443
+ return typeof value === "number" && Number.isFinite(value) && value > 0
444
+ ? Math.floor(value)
445
+ : undefined;
446
+ }
447
+ //# sourceMappingURL=context.js.map