@mevdragon/vidfarm-devcli 0.20.5 → 0.20.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/.agents/skills/editor-capabilities/SKILL.md +15 -1
  2. package/.agents/skills/vidfarm-director/SKILL.md +106 -0
  3. package/.agents/skills/vidfarm-director/recipes/find-and-fork-template.md +16 -0
  4. package/.agents/skills/vidfarm-director/recipes/local-edit-render-approve.md +13 -0
  5. package/.agents/skills/vidfarm-director/recipes/onboard-a-new-director.md +13 -0
  6. package/.agents/skills/vidfarm-director/recipes/retheme-template.md +17 -0
  7. package/.agents/skills/vidfarm-director/references/assets-and-sourcing.md +111 -0
  8. package/.agents/skills/vidfarm-director/references/automation-and-local-dev.md +243 -0
  9. package/.agents/skills/vidfarm-director/references/core-workflows.md +273 -0
  10. package/.agents/skills/vidfarm-director/references/editor-workflows.md +365 -0
  11. package/.agents/skills/vidfarm-director/references/onboarding.md +28 -0
  12. package/.agents/skills/vidfarm-director/references/primitives.md +265 -0
  13. package/SKILL.director.md +521 -261
  14. package/dist/src/cli.js +404 -96
  15. package/dist/src/devcli/clips.js +15 -10
  16. package/dist/src/devcli/doctor.js +2 -2
  17. package/dist/src/devcli/local-backend.js +46 -2
  18. package/dist/src/services/clip-curation/cost.js +5 -1
  19. package/dist/src/services/clip-curation/gemini.js +10 -0
  20. package/package.json +30 -10
  21. package/SKILL.platform.md +0 -432
  22. package/demo/README.md +0 -28
  23. package/demo/dist/app.css +0 -1
  24. package/demo/dist/app.js +0 -1850
  25. package/demo/dist/chunks/chunk-DXB73IDG.js +0 -1
  26. package/demo/dist/chunks/chunk-S7OWAJDS.js +0 -36
  27. package/demo/dist/chunks/chunk-VTIBZ6AN.js +0 -1
  28. package/demo/dist/chunks/dist-ADSJKBVE.js +0 -332
  29. package/demo/dist/chunks/domEditingLayers-VZMLL4AP-SGHWPND4.js +0 -1
  30. package/demo/dist/chunks/hyperframes-player-XB65TCD6.js +0 -425
  31. package/demo/dist/chunks/lib-XAQ37YOE.js +0 -1
  32. package/demo/dist/chunks/src-TJ2QYA4U.js +0 -207
  33. package/demo/dist/favicon.ico +0 -0
  34. package/demo/dist/icons/timeline/audio.svg +0 -7
  35. package/demo/dist/icons/timeline/captions.svg +0 -5
  36. package/demo/dist/icons/timeline/composition.svg +0 -12
  37. package/demo/dist/icons/timeline/image.svg +0 -18
  38. package/demo/dist/icons/timeline/music.svg +0 -10
  39. package/demo/dist/icons/timeline/text.svg +0 -3
  40. package/demo/dist/index.html +0 -15
  41. package/dist/src/account-pages-legacy.js +0 -9396
  42. package/dist/src/account-pages.js +0 -61
  43. package/dist/src/app.js +0 -21602
  44. package/dist/src/composition-runtime.js +0 -1053
  45. package/dist/src/config.js +0 -217
  46. package/dist/src/context.js +0 -447
  47. package/dist/src/dev-app-legacy.js +0 -739
  48. package/dist/src/dev-app.js +0 -6
  49. package/dist/src/devcli/migrate-local.js +0 -140
  50. package/dist/src/devcli/sync.js +0 -368
  51. package/dist/src/domain.js +0 -5
  52. package/dist/src/editor-chat-history.js +0 -82
  53. package/dist/src/editor-chat.js +0 -756
  54. package/dist/src/editor-dark-theme.js +0 -1128
  55. package/dist/src/frontend/debug.js +0 -71
  56. package/dist/src/frontend/discover-client.js +0 -130
  57. package/dist/src/frontend/discover-store.js +0 -23
  58. package/dist/src/frontend/file-directory.js +0 -1018
  59. package/dist/src/frontend/flockposter-cache-store.js +0 -124
  60. package/dist/src/frontend/homepage-client.js +0 -446
  61. package/dist/src/frontend/homepage-shared.js +0 -201
  62. package/dist/src/frontend/homepage-store.js +0 -66
  63. package/dist/src/frontend/homepage-view.js +0 -705
  64. package/dist/src/frontend/page-runtime-client.js +0 -132
  65. package/dist/src/frontend/page-runtime-store.js +0 -9
  66. package/dist/src/frontend/sentry.js +0 -42
  67. package/dist/src/frontend/template-editor-chat.js +0 -4181
  68. package/dist/src/help-page.js +0 -346
  69. package/dist/src/homepage.js +0 -1458
  70. package/dist/src/index.js +0 -16
  71. package/dist/src/instrument.js +0 -30
  72. package/dist/src/landing-page.js +0 -384
  73. package/dist/src/page-runtime.js +0 -2
  74. package/dist/src/page-shell.js +0 -1452
  75. package/dist/src/primitive-context.js +0 -416
  76. package/dist/src/primitive-registry.js +0 -3940
  77. package/dist/src/primitive-sdk.js +0 -4
  78. package/dist/src/primitives/hyperframes-media.js +0 -108
  79. package/dist/src/react-page-shell.js +0 -35
  80. package/dist/src/ready-post-schedule-component.js +0 -1540
  81. package/dist/src/registry.js +0 -296
  82. package/dist/src/reskin/agency-page.js +0 -299
  83. package/dist/src/reskin/calendar-page.js +0 -568
  84. package/dist/src/reskin/chat-page.js +0 -942
  85. package/dist/src/reskin/discover-page.js +0 -1788
  86. package/dist/src/reskin/document.js +0 -1587
  87. package/dist/src/reskin/help-page.js +0 -357
  88. package/dist/src/reskin/index-page.js +0 -62
  89. package/dist/src/reskin/inpaint-clipper-page.js +0 -890
  90. package/dist/src/reskin/inpaint-page.js +0 -2554
  91. package/dist/src/reskin/inpaint-video-page.js +0 -1339
  92. package/dist/src/reskin/job-runs-page.js +0 -477
  93. package/dist/src/reskin/library-page.js +0 -1634
  94. package/dist/src/reskin/login-page.js +0 -262
  95. package/dist/src/reskin/portfolio-page.js +0 -687
  96. package/dist/src/reskin/pricing-page.js +0 -390
  97. package/dist/src/reskin/settings-page.js +0 -732
  98. package/dist/src/reskin/theme.js +0 -711
  99. package/dist/src/runtime.js +0 -35
  100. package/dist/src/services/api-call-history.js +0 -249
  101. package/dist/src/services/auth.js +0 -152
  102. package/dist/src/services/billing-pricing.js +0 -39
  103. package/dist/src/services/billing.js +0 -241
  104. package/dist/src/services/cast.js +0 -127
  105. package/dist/src/services/chat-threads.js +0 -92
  106. package/dist/src/services/clip-records.js +0 -250
  107. package/dist/src/services/clip-search.js +0 -77
  108. package/dist/src/services/clip-vectors.js +0 -125
  109. package/dist/src/services/composition-sanitize.js +0 -124
  110. package/dist/src/services/composition-watch.js +0 -79
  111. package/dist/src/services/elevenlabs.js +0 -222
  112. package/dist/src/services/file-directory.js +0 -117
  113. package/dist/src/services/fork-access.js +0 -93
  114. package/dist/src/services/fork-manifest.js +0 -42
  115. package/dist/src/services/ghostcut.js +0 -179
  116. package/dist/src/services/hyperframes.js +0 -3517
  117. package/dist/src/services/job-capacity.js +0 -14
  118. package/dist/src/services/job-logs.js +0 -197
  119. package/dist/src/services/jobs.js +0 -136
  120. package/dist/src/services/local-dynamo.js +0 -0
  121. package/dist/src/services/media-processing.js +0 -766
  122. package/dist/src/services/primitive-media-lambda.js +0 -280
  123. package/dist/src/services/providers.js +0 -2748
  124. package/dist/src/services/rate-limits.js +0 -262
  125. package/dist/src/services/scene-annotations.js +0 -32
  126. package/dist/src/services/serverless-auth.js +0 -382
  127. package/dist/src/services/serverless-jobs.js +0 -1084
  128. package/dist/src/services/serverless-provider-keys.js +0 -409
  129. package/dist/src/services/serverless-records.js +0 -1515
  130. package/dist/src/services/serverless-template-configs.js +0 -75
  131. package/dist/src/services/storage.js +0 -461
  132. package/dist/src/services/swipe-customize.js +0 -437
  133. package/dist/src/services/template-certification.js +0 -413
  134. package/dist/src/services/template-loader.js +0 -99
  135. package/dist/src/services/template-runtime-bundles.js +0 -217
  136. package/dist/src/services/template-sources.js +0 -1017
  137. package/dist/src/services/upstream.js +0 -248
  138. package/dist/src/services/video-normalization.js +0 -2
  139. package/dist/src/services/webhooks.js +0 -62
  140. package/dist/src/template-editor-pages.js +0 -2576
  141. package/dist/src/template-editor-shell.js +0 -2893
  142. package/dist/src/template-sdk.js +0 -4
  143. package/dist/src/worker.js +0 -17
  144. package/public/assets/discover-client-app.js +0 -1
  145. package/public/assets/file-directory-app.js +0 -3
  146. package/public/assets/homepage-app.js +0 -54
  147. package/public/assets/homepage-client-app.js +0 -80
  148. package/public/assets/page-runtime-client-app.js +0 -94
  149. package/public/assets/placeholders/scene-placeholder.png +0 -0
  150. package/src/assets/SELLING_AWARENESS_STAGES.md +0 -579
  151. package/src/assets/SELLING_WITH_HOOKS.md +0 -377
  152. package/src/assets/SELLING_WITH_VSLS.md +0 -606
  153. package/src/assets/favicon.ico +0 -0
  154. package/src/assets/logo-vidfarm.png +0 -0
@@ -1,35 +0,0 @@
1
- import { serve } from "@hono/node-server";
2
- import * as Sentry from "@sentry/node";
3
- import app from "./app.js";
4
- import { config } from "./config.js";
5
- import { templateRegistry } from "./registry.js";
6
- import { apiCallHistoryStore } from "./services/api-call-history.js";
7
- import { jobLogStore } from "./services/job-logs.js";
8
- import { compositionWatch } from "./services/composition-watch.js";
9
- import { Worker } from "./worker.js";
10
- export async function startRuntime() {
11
- await templateRegistry.ensureInitialized();
12
- const worker = new Worker();
13
- worker.start();
14
- // No-op unless STORAGE_DRIVER=local (i.e. `vidfarm serve`): fans on-disk
15
- // composition edits out to open editor tabs over same-origin SSE.
16
- compositionWatch.start();
17
- const server = serve({
18
- fetch: app.fetch,
19
- port: config.PORT,
20
- serverOptions: {
21
- maxHeaderSize: 64 * 1024
22
- }
23
- }, (info) => {
24
- console.log(`[vidfarm] listening on http://localhost:${info.port}`);
25
- });
26
- const shutdown = async () => {
27
- worker.stop();
28
- await apiCallHistoryStore.shutdown();
29
- await jobLogStore.shutdown();
30
- await Sentry.flush(2_000).catch(() => undefined);
31
- server.close();
32
- };
33
- return { server, worker, shutdown };
34
- }
35
- //# sourceMappingURL=runtime.js.map
@@ -1,249 +0,0 @@
1
- import { StorageService } from "./storage.js";
2
- const API_CALL_HISTORY_PREFIX = "api-call-history";
3
- const CHUNK_INTERVAL_MS = 60_000;
4
- const CHUNK_MAX_BYTES = 1024 * 1024;
5
- const FLUSH_INTERVAL_MS = 5_000;
6
- export class ApiCallHistoryStore {
7
- storage = new StorageService();
8
- activeChunks = new Map();
9
- flushTimer = setInterval(() => {
10
- void this.flushExpiredChunks();
11
- }, FLUSH_INTERVAL_MS);
12
- constructor() {
13
- this.flushTimer.unref();
14
- }
15
- addEntry(entry) {
16
- const normalized = {
17
- ...entry,
18
- createdAt: entry.createdAt ?? new Date().toISOString()
19
- };
20
- const minuteStartMs = floorToMinute(Date.parse(normalized.createdAt));
21
- const activePrefix = this.activeChunkPrefix(normalized.customerId, minuteStartMs);
22
- const current = this.findLatestChunk(activePrefix);
23
- const entryBytes = serializedEntrySize(normalized);
24
- const chunk = current && current.sizeBytes + entryBytes <= CHUNK_MAX_BYTES
25
- ? current
26
- : this.createChunk(normalized.customerId, minuteStartMs, current ? current.sequence + 1 : 0);
27
- chunk.entries.push(normalized);
28
- chunk.sizeBytes += entryBytes;
29
- chunk.dirty = true;
30
- if (chunk.sizeBytes >= CHUNK_MAX_BYTES) {
31
- void this.persistChunk(chunk, true);
32
- }
33
- }
34
- async listEntries(input) {
35
- const persisted = await this.readPersistedEntries(input);
36
- const buffered = this.readBufferedEntries(input);
37
- const merged = dedupeAndSort([...persisted, ...buffered]).reverse();
38
- const filtered = input.cursor
39
- ? merged.filter((entry) => (Date.parse(entry.createdAt) < Date.parse(input.cursor.createdAt)
40
- || (entry.createdAt === input.cursor.createdAt && entry.id < input.cursor.id)))
41
- : merged;
42
- if (input.limit === undefined) {
43
- return filtered;
44
- }
45
- const limit = Math.max(1, Math.min(input.limit, 501));
46
- return filtered.slice(0, limit);
47
- }
48
- async listStorageKeys(input) {
49
- return this.listRelevantKeys(input);
50
- }
51
- async renameTracer(input) {
52
- const fromTracer = input.fromTracer.trim();
53
- const toTracer = input.toTracer.trim();
54
- if (!fromTracer || !toTracer || fromTracer === toTracer) {
55
- return { updated: 0 };
56
- }
57
- let updated = 0;
58
- for (const chunk of this.activeChunks.values()) {
59
- if (chunk.customerId !== input.customerId) {
60
- continue;
61
- }
62
- let chunkChanged = false;
63
- for (const entry of chunk.entries) {
64
- if (entry.tracer === fromTracer && withinRange(entry.createdAt, input.startTime, input.endTime)) {
65
- entry.tracer = toTracer;
66
- updated += 1;
67
- chunkChanged = true;
68
- }
69
- }
70
- if (chunkChanged) {
71
- chunk.dirty = true;
72
- chunk.sizeBytes = chunk.entries.reduce((sum, entry) => sum + serializedEntrySize(entry), 0);
73
- await this.persistChunk(chunk, false);
74
- }
75
- }
76
- const keys = await this.listRelevantKeys(input);
77
- await Promise.all(keys.map(async (key) => {
78
- const content = await this.storage.readText(key);
79
- const entries = parseChunk(content);
80
- let changed = false;
81
- for (const entry of entries) {
82
- if (entry.customerId === input.customerId
83
- && entry.tracer === fromTracer
84
- && withinRange(entry.createdAt, input.startTime, input.endTime)) {
85
- entry.tracer = toTracer;
86
- updated += 1;
87
- changed = true;
88
- }
89
- }
90
- if (changed) {
91
- const body = `${entries.map((entry) => JSON.stringify(entry)).join("\n")}\n`;
92
- await this.storage.putText(key, body, "application/x-ndjson; charset=utf-8");
93
- }
94
- }));
95
- return { updated };
96
- }
97
- async shutdown() {
98
- clearInterval(this.flushTimer);
99
- await Promise.all([...this.activeChunks.values()].map((chunk) => this.persistChunk(chunk, true)));
100
- }
101
- async readPersistedEntries(input) {
102
- const keys = await this.listRelevantKeys(input);
103
- const entries = await Promise.all(keys.map(async (key) => parseChunk(await this.storage.readText(key))));
104
- return entries
105
- .flat()
106
- .filter((entry) => entry.customerId === input.customerId)
107
- .filter((entry) => withinRange(entry.createdAt, input.startTime, input.endTime));
108
- }
109
- readBufferedEntries(input) {
110
- const buffered = [];
111
- for (const chunk of this.activeChunks.values()) {
112
- if (chunk.customerId !== input.customerId) {
113
- continue;
114
- }
115
- for (const entry of chunk.entries.slice(chunk.lastPersistedEntryCount)) {
116
- if (withinRange(entry.createdAt, input.startTime, input.endTime)) {
117
- buffered.push(entry);
118
- }
119
- }
120
- }
121
- return buffered;
122
- }
123
- async listRelevantKeys(input) {
124
- const minuteStarts = this.minuteStartsForQuery(input.startTime, input.endTime);
125
- if (minuteStarts === null) {
126
- return this.storage.listKeys(`${API_CALL_HISTORY_PREFIX}/customer_id=${input.customerId}/`);
127
- }
128
- const nested = await Promise.all(minuteStarts.map((minuteStartMs) => (this.storage.listKeys(this.minutePrefix(input.customerId, minuteStartMs)))));
129
- return nested.flat();
130
- }
131
- minuteStartsForQuery(startTime, endTime) {
132
- if (!startTime && !endTime) {
133
- return null;
134
- }
135
- const startMs = floorToMinute(Date.parse(startTime ?? endTime ?? new Date().toISOString()));
136
- const endMs = floorToMinute(Date.parse(endTime ?? startTime ?? new Date().toISOString()));
137
- const minMs = Math.min(startMs, endMs);
138
- const maxMs = Math.max(startMs, endMs);
139
- const minuteStarts = [];
140
- for (let cursor = minMs; cursor <= maxMs; cursor += 60_000) {
141
- minuteStarts.push(cursor);
142
- }
143
- return minuteStarts;
144
- }
145
- async flushExpiredChunks() {
146
- const now = Date.now();
147
- await Promise.all([...this.activeChunks.values()].map(async (chunk) => {
148
- const intervalElapsed = now >= chunk.minuteStartMs + CHUNK_INTERVAL_MS;
149
- if (intervalElapsed || chunk.sizeBytes >= CHUNK_MAX_BYTES) {
150
- await this.persistChunk(chunk, true);
151
- return;
152
- }
153
- if (chunk.dirty) {
154
- await this.persistChunk(chunk, false);
155
- }
156
- }));
157
- }
158
- createChunk(customerId, minuteStartMs, sequence) {
159
- const chunk = {
160
- customerId,
161
- minuteStartMs,
162
- sequence,
163
- entries: [],
164
- sizeBytes: 0,
165
- lastPersistedEntryCount: 0,
166
- dirty: false
167
- };
168
- this.activeChunks.set(this.chunkKeyForMap(customerId, minuteStartMs, sequence), chunk);
169
- return chunk;
170
- }
171
- findLatestChunk(activePrefix) {
172
- const matching = [...this.activeChunks.entries()]
173
- .filter(([key]) => key.startsWith(activePrefix))
174
- .map(([, chunk]) => chunk)
175
- .sort((a, b) => a.sequence - b.sequence);
176
- return matching.at(-1) ?? null;
177
- }
178
- activeChunkPrefix(customerId, minuteStartMs) {
179
- return `${customerId}:${minuteStartMs}:`;
180
- }
181
- chunkKeyForMap(customerId, minuteStartMs, sequence) {
182
- return `${this.activeChunkPrefix(customerId, minuteStartMs)}${sequence}`;
183
- }
184
- async persistChunk(chunk, finalize) {
185
- if (!chunk.entries.length) {
186
- if (finalize) {
187
- this.activeChunks.delete(this.chunkKeyForMap(chunk.customerId, chunk.minuteStartMs, chunk.sequence));
188
- }
189
- return;
190
- }
191
- const key = this.objectKeyForChunk(chunk);
192
- const body = `${chunk.entries.map((entry) => JSON.stringify(entry)).join("\n")}\n`;
193
- await this.storage.putText(key, body, "application/x-ndjson; charset=utf-8");
194
- chunk.lastPersistedEntryCount = chunk.entries.length;
195
- chunk.dirty = false;
196
- if (finalize) {
197
- this.activeChunks.delete(this.chunkKeyForMap(chunk.customerId, chunk.minuteStartMs, chunk.sequence));
198
- }
199
- }
200
- objectKeyForChunk(chunk) {
201
- const startTs = Math.floor(chunk.entries[0] ? Date.parse(chunk.entries[0].createdAt) / 1000 : chunk.minuteStartMs / 1000);
202
- const endTs = Math.floor(chunk.entries.at(-1) ? Date.parse(chunk.entries.at(-1).createdAt) / 1000 : chunk.minuteStartMs / 1000);
203
- return `${this.minutePrefix(chunk.customerId, chunk.minuteStartMs)}chunk-${startTs}-${endTs}-${String(chunk.sequence).padStart(4, "0")}.ndjson`;
204
- }
205
- minutePrefix(customerId, minuteStartMs) {
206
- return `${API_CALL_HISTORY_PREFIX}/customer_id=${customerId}/minute=${Math.floor(minuteStartMs / 1000)}/`;
207
- }
208
- }
209
- export const apiCallHistoryStore = new ApiCallHistoryStore();
210
- function serializedEntrySize(entry) {
211
- return Buffer.byteLength(`${JSON.stringify(entry)}\n`, "utf8");
212
- }
213
- function floorToMinute(timestampMs) {
214
- return Math.floor(timestampMs / 60_000) * 60_000;
215
- }
216
- function parseChunk(content) {
217
- if (!content) {
218
- return [];
219
- }
220
- return content
221
- .split("\n")
222
- .map((line) => line.trim())
223
- .filter(Boolean)
224
- .map((line) => {
225
- const parsed = JSON.parse(line);
226
- return {
227
- ...parsed,
228
- jobId: typeof parsed.jobId === "string" && parsed.jobId.trim() ? parsed.jobId.trim() : null
229
- };
230
- });
231
- }
232
- function dedupeAndSort(entries) {
233
- const deduped = new Map();
234
- for (const entry of entries) {
235
- deduped.set(entry.id, entry);
236
- }
237
- return [...deduped.values()].sort((a, b) => Date.parse(a.createdAt) - Date.parse(b.createdAt));
238
- }
239
- function withinRange(createdAt, startTime, endTime) {
240
- const value = Date.parse(createdAt);
241
- if (startTime && value < Date.parse(startTime)) {
242
- return false;
243
- }
244
- if (endTime && value > Date.parse(endTime)) {
245
- return false;
246
- }
247
- return true;
248
- }
249
- //# sourceMappingURL=api-call-history.js.map
@@ -1,152 +0,0 @@
1
- import { randomInt } from "node:crypto";
2
- import { config } from "../config.js";
3
- import { encryptString, hashPassword, hashSecret, safeEqualHash, verifyPassword } from "../lib/crypto.js";
4
- import { displayNameFromEmail } from "../lib/display-name.js";
5
- import { createId } from "../lib/ids.js";
6
- import { addSeconds, nowIso } from "../lib/time.js";
7
- import { ServerlessAuthService } from "./serverless-auth.js";
8
- export class AuthService {
9
- serverless = new ServerlessAuthService();
10
- async requestOtp(email) {
11
- const code = `${randomInt(100000, 999999)}`;
12
- await this.serverless.insertOtpChallenge({
13
- id: createId("otp"),
14
- email,
15
- codeHash: hashSecret(code),
16
- expiresAt: addSeconds(new Date(), 600)
17
- });
18
- if (config.RESEND_API_KEY) {
19
- const response = await fetch("https://api.resend.com/emails", {
20
- method: "POST",
21
- headers: {
22
- "Content-Type": "application/json",
23
- Authorization: `Bearer ${config.RESEND_API_KEY}`
24
- },
25
- body: JSON.stringify({
26
- from: config.RESEND_FROM_EMAIL,
27
- to: email,
28
- subject: "Your Vidfarm login code",
29
- text: `Your login code is ${code}. It expires in 10 minutes.`
30
- })
31
- });
32
- if (!response.ok) {
33
- const body = await response.text();
34
- throw new Error(`Unable to send OTP email (${response.status}): ${body || "empty response body"}`);
35
- }
36
- return {
37
- delivery: "email"
38
- };
39
- }
40
- else {
41
- console.log(`[vidfarm] OTP for ${email}: ${code}`);
42
- return {
43
- delivery: "console",
44
- code: config.isProduction ? undefined : code
45
- };
46
- }
47
- }
48
- async verifyOtp(email, code, name) {
49
- const challenge = await this.serverless.findLatestOtpChallenge(email);
50
- if (!challenge) {
51
- throw new Error("No OTP challenge found for that email.");
52
- }
53
- if (challenge.consumed_at) {
54
- throw new Error("OTP challenge already used.");
55
- }
56
- if (new Date(String(challenge.expires_at)).getTime() < Date.now()) {
57
- throw new Error("OTP challenge expired.");
58
- }
59
- if (!safeEqualHash(code, String(challenge.code_hash))) {
60
- throw new Error("Invalid OTP code.");
61
- }
62
- await this.serverless.consumeOtpChallenge(email, String(challenge.id));
63
- const existing = await this.serverless.getCustomerByEmailPublic(email);
64
- const normalizedEmail = email.trim().toLowerCase();
65
- const customer = await this.serverless.upsertCustomer({
66
- id: existing?.id ?? createId("cus"),
67
- email: normalizedEmail,
68
- name: name?.trim() || existing?.name || displayNameFromEmail(normalizedEmail),
69
- defaultWebhookUrl: existing?.defaultWebhookUrl ?? null,
70
- isDeveloper: existing?.isDeveloper || config.adminEmails.includes(normalizedEmail),
71
- isPaidPlan: existing?.isPaidPlan ?? false,
72
- planTier: existing?.planTier ?? "client"
73
- });
74
- const rawApiKey = `vf_${createId("key")}`;
75
- await this.serverless.insertApiKey({
76
- id: createId("api"),
77
- customerId: customer.id,
78
- keyHash: hashSecret(rawApiKey + config.API_KEY_SALT),
79
- rawValue: rawApiKey,
80
- label: `Issued ${nowIso()}`
81
- });
82
- return { customer, apiKey: rawApiKey };
83
- }
84
- authenticate(apiKey) {
85
- throw new Error("Use authenticateAsync; DynamoDB authentication is asynchronous.");
86
- }
87
- async authenticateAsync(apiKey) {
88
- return this.serverless.authenticate(apiKey);
89
- }
90
- async verifyOtpForBrowserLogin(email, code, name) {
91
- const result = await this.verifyOtp(email, code, name);
92
- if (!result.customer.isPaidPlan) {
93
- return {
94
- status: "pricing",
95
- customer: result.customer
96
- };
97
- }
98
- return {
99
- status: "authenticated",
100
- customer: result.customer
101
- };
102
- }
103
- async authenticateWithPassword(email, password) {
104
- const row = await this.serverless.getCustomerAuthByEmail(email);
105
- if (!row || !row.password_hash || !verifyPassword(password, String(row.password_hash))) {
106
- throw new Error("Invalid email or password.");
107
- }
108
- const customer = await this.serverless.getCustomerById(String(row.customer_id ?? row.id));
109
- if (!customer) {
110
- throw new Error("Customer not found.");
111
- }
112
- await this.serverless.updateCustomerPassword(customer.id, String(row.password_hash), encryptString(password, config.ENCRYPTION_SECRET));
113
- if (!customer.isPaidPlan) {
114
- return {
115
- status: "pricing",
116
- customer
117
- };
118
- }
119
- return {
120
- status: "authenticated",
121
- customer
122
- };
123
- }
124
- async createPasswordUser(input) {
125
- const normalizedEmail = input.email.trim().toLowerCase();
126
- const existing = await this.serverless.getCustomerByEmailPublic(normalizedEmail);
127
- return this.serverless.upsertCustomer({
128
- id: existing?.id ?? createId("cus"),
129
- email: normalizedEmail,
130
- name: input.name?.trim() || existing?.name || displayNameFromEmail(normalizedEmail),
131
- defaultWebhookUrl: existing?.defaultWebhookUrl ?? null,
132
- isDeveloper: existing?.isDeveloper || config.adminEmails.includes(normalizedEmail),
133
- isPaidPlan: true,
134
- planTier: input.planTier ?? existing?.planTier ?? "client",
135
- passwordHash: hashPassword(input.password),
136
- encryptedPassword: encryptString(input.password, config.ENCRYPTION_SECRET)
137
- });
138
- }
139
- }
140
- function normalizePlanTier(value) {
141
- if (value === "client" || value === "standalone" || value === "agency") {
142
- return value;
143
- }
144
- if (value === "invisible" || value === "starter") {
145
- return "client";
146
- }
147
- if (value === "standard") {
148
- return "standalone";
149
- }
150
- return "client";
151
- }
152
- //# sourceMappingURL=auth.js.map
@@ -1,39 +0,0 @@
1
- // Single source of truth for the render/compute billing markup.
2
- //
3
- // The billing lambda debits the wallet by `base_provider_cost * markup` (see
4
- // infra/lambda/billing.ts). Every surface that *displays* a render's cost — and
5
- // every place that pre-computes the marked-up `chargeUsd` stashed in render
6
- // metadata — must apply the SAME markup, otherwise the number shown to the user
7
- // diverges from what their wallet is actually charged.
8
- //
9
- // The markup is expressed in basis points so it matches the billing lambda's
10
- // integer micros math exactly. It is overridable per-environment via
11
- // `VIDFARM_BILLING_MARKUP_BPS`; keep that env var in sync across the billing,
12
- // job-runner, and api lambdas so display and charge never drift.
13
- export const DEFAULT_MARKUP_BPS = 12_000; // 1.2x
14
- const BPS_DENOMINATOR = 10_000;
15
- // Resolve the active markup (basis points). Falls back to the default when the
16
- // env var is unset, empty, or not a positive integer.
17
- export function resolveMarkupBps() {
18
- const raw = process.env.VIDFARM_BILLING_MARKUP_BPS;
19
- if (raw === undefined || raw === null || raw.trim() === "") {
20
- return DEFAULT_MARKUP_BPS;
21
- }
22
- const parsed = Number.parseInt(raw, 10);
23
- if (!Number.isFinite(parsed) || parsed <= 0) {
24
- return DEFAULT_MARKUP_BPS;
25
- }
26
- return parsed;
27
- }
28
- export function roundUsd(value) {
29
- return Number(value.toFixed(4));
30
- }
31
- // Convert a raw provider cost (USD) into the amount the wallet is actually
32
- // charged. Non-positive or non-finite inputs yield 0 (nothing to bill).
33
- export function applyMarkupUsd(baseCostUsd, markupBps = resolveMarkupBps()) {
34
- if (!Number.isFinite(baseCostUsd) || baseCostUsd <= 0) {
35
- return 0;
36
- }
37
- return roundUsd((baseCostUsd * markupBps) / BPS_DENOMINATOR);
38
- }
39
- //# sourceMappingURL=billing-pricing.js.map