Repository Public
1import "./_env";2import { sql, j } from "../src/lib/db";3import { CreativeBrief } from "../src/lib/contracts";4import { env } from "../src/lib/env";5import { STAGES, STAGE_FN, getProject, type Stage } from "../src/pipeline/stages";6import { approveProject, publishProject } from "../src/pipeline/publish";78/**9 * Operator CLI: run the pipeline inline (no queue) for one brief or an existing project.10 * tsx scripts/produce.ts --theme "…" [--series slug] [--lang en|fr|bilingual] [--shots 5] [--quality draft|master] [--until qa]11 * tsx scripts/produce.ts --project <id> [--from video] [--until qa] [--publish --approver <email>]12 * Budgets, idempotency and the kill switch apply exactly as in the worker.13 */14const arg = (name: string) => { const i = process.argv.indexOf(`--${name}`); return i < 0 ? undefined : process.argv[i + 1] ?? ""; };15const flag = (name: string) => process.argv.includes(`--${name}`);1617async function main() {18 let id = arg("project");19 if (!id) {20 const brief = CreativeBrief.parse({ theme: arg("theme"), seriesSlug: arg("series") ?? "", language: arg("lang") ?? "en", shots: arg("shots") ?? 5, quality: arg("quality") ?? "master", mood: arg("mood") ?? "", objective: arg("objective") ?? "", constraints: arg("constraints") ?? "", mode: "manual" });21 const [series] = brief.seriesSlug ? await sql<{ id: string }[]>`SELECT id FROM series WHERE slug = ${brief.seriesSlug}` : [];22 const [p] = await sql<{ id: string }[]>`INSERT INTO projects (title, series_id, brief, language, mode, origin, video_model)23 VALUES (${brief.theme.slice(0, 80)}, ${series?.id ?? null}, ${j(brief)}, ${brief.language}, 'manual', 'api', ${brief.quality === "draft" ? env.videoDraftModel : env.videoModel}) RETURNING id`;24 id = p.id;25 console.log(`project ${id}`);26 }27 const from = (arg("from") as Stage | undefined) ?? undefined, until = (arg("until") as Stage | undefined) ?? "qa";28 const order: Record<string, number> = { draft: 0, pitched: 1, scripted: 2, storyboarded: 3, keyframes: 4, voiced: 5, filmed: 6, assembled: 7, qa_passed: 8, qa_failed: 8, approved: 8, published: 8 };29 const p0 = await getProject(id);30 const start = from ? STAGES.indexOf(from) : order[p0.status] ?? 0;31 for (const stage of STAGES.slice(start, STAGES.indexOf(until) + 1)) {32 const t = Date.now();33 await STAGE_FN[stage](id);34 const [{ usd }] = await sql<{ usd: number }[]>`SELECT COALESCE(SUM(usd), 0)::float8 AS usd FROM cost_entries WHERE project_id = ${id}`;35 console.log(`${stage.padEnd(10)} ok ${((Date.now() - t) / 1000).toFixed(1)} s total $${usd.toFixed(3)}`);36 }37 const p = await getProject(id);38 console.log(`status: ${p.status}`);39 if (flag("publish")) {40 const [u] = await sql<{ id: string }[]>`SELECT id FROM users WHERE email = ${arg("approver") ?? ""}`;41 if (!u) throw new Error("--publish needs --approver <email of a studio user>: publication is a human decision");42 await approveProject(id, u.id, { override: flag("override"), note: arg("note") });43 console.log(`published film ${await publishProject(id, u.id, { featured: flag("featured") })}`);44 }45 await sql.end();46}47main().catch(async (e) => { console.error(`FAILED: ${e.name}: ${e.message}`); await sql.end(); process.exit(1); });48