@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.
- package/.agents/skills/editor-capabilities/SKILL.md +15 -1
- package/.agents/skills/vidfarm-director/SKILL.md +106 -0
- package/.agents/skills/vidfarm-director/recipes/find-and-fork-template.md +16 -0
- package/.agents/skills/vidfarm-director/recipes/local-edit-render-approve.md +13 -0
- package/.agents/skills/vidfarm-director/recipes/onboard-a-new-director.md +13 -0
- package/.agents/skills/vidfarm-director/recipes/retheme-template.md +17 -0
- package/.agents/skills/vidfarm-director/references/assets-and-sourcing.md +111 -0
- package/.agents/skills/vidfarm-director/references/automation-and-local-dev.md +243 -0
- package/.agents/skills/vidfarm-director/references/core-workflows.md +273 -0
- package/.agents/skills/vidfarm-director/references/editor-workflows.md +365 -0
- package/.agents/skills/vidfarm-director/references/onboarding.md +28 -0
- package/.agents/skills/vidfarm-director/references/primitives.md +265 -0
- package/SKILL.director.md +521 -261
- package/dist/src/cli.js +404 -96
- package/dist/src/devcli/clips.js +15 -10
- package/dist/src/devcli/doctor.js +2 -2
- package/dist/src/devcli/local-backend.js +46 -2
- package/dist/src/services/clip-curation/cost.js +5 -1
- package/dist/src/services/clip-curation/gemini.js +10 -0
- package/package.json +30 -10
- package/SKILL.platform.md +0 -432
- package/demo/README.md +0 -28
- package/demo/dist/app.css +0 -1
- package/demo/dist/app.js +0 -1850
- package/demo/dist/chunks/chunk-DXB73IDG.js +0 -1
- package/demo/dist/chunks/chunk-S7OWAJDS.js +0 -36
- package/demo/dist/chunks/chunk-VTIBZ6AN.js +0 -1
- package/demo/dist/chunks/dist-ADSJKBVE.js +0 -332
- package/demo/dist/chunks/domEditingLayers-VZMLL4AP-SGHWPND4.js +0 -1
- package/demo/dist/chunks/hyperframes-player-XB65TCD6.js +0 -425
- package/demo/dist/chunks/lib-XAQ37YOE.js +0 -1
- package/demo/dist/chunks/src-TJ2QYA4U.js +0 -207
- package/demo/dist/favicon.ico +0 -0
- package/demo/dist/icons/timeline/audio.svg +0 -7
- package/demo/dist/icons/timeline/captions.svg +0 -5
- package/demo/dist/icons/timeline/composition.svg +0 -12
- package/demo/dist/icons/timeline/image.svg +0 -18
- package/demo/dist/icons/timeline/music.svg +0 -10
- package/demo/dist/icons/timeline/text.svg +0 -3
- package/demo/dist/index.html +0 -15
- package/dist/src/account-pages-legacy.js +0 -9396
- package/dist/src/account-pages.js +0 -61
- package/dist/src/app.js +0 -21602
- package/dist/src/composition-runtime.js +0 -1053
- package/dist/src/config.js +0 -217
- package/dist/src/context.js +0 -447
- package/dist/src/dev-app-legacy.js +0 -739
- package/dist/src/dev-app.js +0 -6
- package/dist/src/devcli/migrate-local.js +0 -140
- package/dist/src/devcli/sync.js +0 -368
- package/dist/src/domain.js +0 -5
- package/dist/src/editor-chat-history.js +0 -82
- package/dist/src/editor-chat.js +0 -756
- package/dist/src/editor-dark-theme.js +0 -1128
- package/dist/src/frontend/debug.js +0 -71
- package/dist/src/frontend/discover-client.js +0 -130
- package/dist/src/frontend/discover-store.js +0 -23
- package/dist/src/frontend/file-directory.js +0 -1018
- package/dist/src/frontend/flockposter-cache-store.js +0 -124
- package/dist/src/frontend/homepage-client.js +0 -446
- package/dist/src/frontend/homepage-shared.js +0 -201
- package/dist/src/frontend/homepage-store.js +0 -66
- package/dist/src/frontend/homepage-view.js +0 -705
- package/dist/src/frontend/page-runtime-client.js +0 -132
- package/dist/src/frontend/page-runtime-store.js +0 -9
- package/dist/src/frontend/sentry.js +0 -42
- package/dist/src/frontend/template-editor-chat.js +0 -4181
- package/dist/src/help-page.js +0 -346
- package/dist/src/homepage.js +0 -1458
- package/dist/src/index.js +0 -16
- package/dist/src/instrument.js +0 -30
- package/dist/src/landing-page.js +0 -384
- package/dist/src/page-runtime.js +0 -2
- package/dist/src/page-shell.js +0 -1452
- package/dist/src/primitive-context.js +0 -416
- package/dist/src/primitive-registry.js +0 -3940
- package/dist/src/primitive-sdk.js +0 -4
- package/dist/src/primitives/hyperframes-media.js +0 -108
- package/dist/src/react-page-shell.js +0 -35
- package/dist/src/ready-post-schedule-component.js +0 -1540
- package/dist/src/registry.js +0 -296
- package/dist/src/reskin/agency-page.js +0 -299
- package/dist/src/reskin/calendar-page.js +0 -568
- package/dist/src/reskin/chat-page.js +0 -942
- package/dist/src/reskin/discover-page.js +0 -1788
- package/dist/src/reskin/document.js +0 -1587
- package/dist/src/reskin/help-page.js +0 -357
- package/dist/src/reskin/index-page.js +0 -62
- package/dist/src/reskin/inpaint-clipper-page.js +0 -890
- package/dist/src/reskin/inpaint-page.js +0 -2554
- package/dist/src/reskin/inpaint-video-page.js +0 -1339
- package/dist/src/reskin/job-runs-page.js +0 -477
- package/dist/src/reskin/library-page.js +0 -1634
- package/dist/src/reskin/login-page.js +0 -262
- package/dist/src/reskin/portfolio-page.js +0 -687
- package/dist/src/reskin/pricing-page.js +0 -390
- package/dist/src/reskin/settings-page.js +0 -732
- package/dist/src/reskin/theme.js +0 -711
- package/dist/src/runtime.js +0 -35
- package/dist/src/services/api-call-history.js +0 -249
- package/dist/src/services/auth.js +0 -152
- package/dist/src/services/billing-pricing.js +0 -39
- package/dist/src/services/billing.js +0 -241
- package/dist/src/services/cast.js +0 -127
- package/dist/src/services/chat-threads.js +0 -92
- package/dist/src/services/clip-records.js +0 -250
- package/dist/src/services/clip-search.js +0 -77
- package/dist/src/services/clip-vectors.js +0 -125
- package/dist/src/services/composition-sanitize.js +0 -124
- package/dist/src/services/composition-watch.js +0 -79
- package/dist/src/services/elevenlabs.js +0 -222
- package/dist/src/services/file-directory.js +0 -117
- package/dist/src/services/fork-access.js +0 -93
- package/dist/src/services/fork-manifest.js +0 -42
- package/dist/src/services/ghostcut.js +0 -179
- package/dist/src/services/hyperframes.js +0 -3517
- package/dist/src/services/job-capacity.js +0 -14
- package/dist/src/services/job-logs.js +0 -197
- package/dist/src/services/jobs.js +0 -136
- package/dist/src/services/local-dynamo.js +0 -0
- package/dist/src/services/media-processing.js +0 -766
- package/dist/src/services/primitive-media-lambda.js +0 -280
- package/dist/src/services/providers.js +0 -2748
- package/dist/src/services/rate-limits.js +0 -262
- package/dist/src/services/scene-annotations.js +0 -32
- package/dist/src/services/serverless-auth.js +0 -382
- package/dist/src/services/serverless-jobs.js +0 -1084
- package/dist/src/services/serverless-provider-keys.js +0 -409
- package/dist/src/services/serverless-records.js +0 -1515
- package/dist/src/services/serverless-template-configs.js +0 -75
- package/dist/src/services/storage.js +0 -461
- package/dist/src/services/swipe-customize.js +0 -437
- package/dist/src/services/template-certification.js +0 -413
- package/dist/src/services/template-loader.js +0 -99
- package/dist/src/services/template-runtime-bundles.js +0 -217
- package/dist/src/services/template-sources.js +0 -1017
- package/dist/src/services/upstream.js +0 -248
- package/dist/src/services/video-normalization.js +0 -2
- package/dist/src/services/webhooks.js +0 -62
- package/dist/src/template-editor-pages.js +0 -2576
- package/dist/src/template-editor-shell.js +0 -2893
- package/dist/src/template-sdk.js +0 -4
- package/dist/src/worker.js +0 -17
- package/public/assets/discover-client-app.js +0 -1
- package/public/assets/file-directory-app.js +0 -3
- package/public/assets/homepage-app.js +0 -54
- package/public/assets/homepage-client-app.js +0 -80
- package/public/assets/page-runtime-client-app.js +0 -94
- package/public/assets/placeholders/scene-placeholder.png +0 -0
- package/src/assets/SELLING_AWARENESS_STAGES.md +0 -579
- package/src/assets/SELLING_WITH_HOOKS.md +0 -377
- package/src/assets/SELLING_WITH_VSLS.md +0 -606
- package/src/assets/favicon.ico +0 -0
- package/src/assets/logo-vidfarm.png +0 -0
package/dist/src/runtime.js
DELETED
|
@@ -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
|