@conceptcraft/mindframes 0.1.28 → 0.1.30
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/dist/index.js +812 -290
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'module'; const require = createRequire(import.meta.url);
|
|
3
|
-
import{Command as
|
|
4
|
-
`)}function ho(t){let o=[...t].sort((r,i)=>{if(r.isDefault&&!i.isDefault)return-1;if(!r.isDefault&&i.isDefault)return 1;let a=r.createdAt?new Date(r.createdAt).getTime():0;return(i.createdAt?new Date(i.createdAt).getTime():0)-a}),e=r=>{if(!r)return"-";try{return new URL(r).href}catch{return r.startsWith("http")?r:`https://${r}`}},n=new Tt({head:[O.cyan("Name"),O.cyan("Source"),O.cyan("Color"),O.cyan("Logo"),O.cyan("Default"),O.cyan("ID")]});for(let r of o){let i=r.primaryColor?`${O.bgHex(r.primaryColor)(" ")} ${r.primaryColor}`:"-";n.push([r.name,e(r.sourceUrl),i,r.logoUrl?O.green("\u2713"):O.gray("\u2717"),r.isDefault?O.green("\u2605"):"",r.id])}return n.toString()}function It(t){try{return new Date(t).toLocaleString()}catch{return t}}function te(t){console.log(),console.log(O.bold(t)),console.log(O.gray("\u2500".repeat(t.length)))}function h(t,o){console.log(` ${O.gray(t+":")} ${o??"-"}`)}function yo(t,o,e=30,n=!0){let r=Math.min(100,Math.round(t/o*100)),i=Math.round(e*t/o),a=e-i,s=O.green("\u2588".repeat(i))+O.gray("\u2591".repeat(a));return n?`[${s}] ${r}%`:`[${s}]`}function Hn(t){return JSON.stringify(t,null,2)}function V(t){console.log(Hn(t))}import rr from"conf";import nt from"chalk";var v={SUCCESS:0,GENERAL_ERROR:1,AUTH_ERROR:2,NOT_FOUND:3,RATE_LIMIT:4,NETWORK_ERROR:5,INVALID_INPUT:6};async function Yn(){let t=co(),o=ot(),e=M();if(!t||!o)return null;try{let n=await fetch(`${e}/.well-known/oauth-authorization-server`);if(!n.ok)return null;let r=await n.json(),i=new URLSearchParams({grant_type:"refresh_token",refresh_token:t,client_id:o}),a=await fetch(r.token_endpoint,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:i.toString()});if(!a.ok)return tt(),null;let s=await a.json();return et(s.access_token,s.refresh_token,s.expires_in),s.access_token}catch{return null}}async function bo(){return pe()?lo()?Yn():so()||null:null}function oe(){return pe()||Se()}async function F(){if(!oe()){let t=m.commands[0],o=m.apiKeyEnvVar;console.error(nt.red("\u2717 Not authenticated.")),console.error(),console.error(nt.bold("To authenticate, run:")),console.error(nt.cyan(` ${t} login`)),console.error(),console.error(nt.gray(`Or set ${o} environment variable.`)),process.exit(v.AUTH_ERROR)}}function zn(t){return t.length<=14?"****":`${t.slice(0,10)}...${t.slice(-4)}`}function vo(){let t=ie();if(t)return zn(t)}function Rt(t){return["cc_slides_","mcp_","cc_sk_","sk_"].some(e=>t.startsWith(e))}import{readFileSync as Xn,statSync as Qn}from"fs";import{basename as wo}from"path";import{randomUUID as Zn}from"crypto";async function le(){let t=await bo();if(t)return{Authorization:`Bearer ${t}`};let o=ie();return o?{"x-api-key":o}:{}}var E=class extends Error{constructor(e,n,r=1){super(e);this.statusCode=n;this.exitCode=r;this.name="ApiError"}};async function N(t,o={}){let e=M();if(!oe())throw new E(`Not authenticated. Run '${m.commands[0]} login' or set ${m.apiKeyEnvVar} environment variable.`,401,2);let n=await le(),r=`${e}${t}`,i={...n,...o.headers};o.body&&!o.stream&&(i["Content-Type"]="application/json");let a={method:o.method??"GET",headers:i};o.body&&(a.body=JSON.stringify(o.body));let s;try{s=await fetch(r,a)}catch(u){throw new E(`Network error: ${u instanceof Error?u.message:"Unknown error"}`,0,5)}if(!s.ok){let u=await s.text().catch(()=>""),p=1;switch(s.status){case 401:p=2;break;case 403:p=2;break;case 404:p=3;break;case 429:p=4;break;case 500:p=1;break}let k=u.trim()||`HTTP ${s.status}: ${s.statusText||"Server error"}`;throw new E(k,s.status,p)}return o.stream?s:s.headers.get("content-type")?.includes("application/json")?s.json():s.text()}async function er(t,o){let e=M();if(!oe())throw new E(`Not authenticated. Run '${m.commands[0]} login' or set ${m.apiKeyEnvVar} environment variable.`,401,2);let n=await le(),r=`${e}${t}`,i;try{i=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json",...n},body:JSON.stringify(o)})}catch(a){throw new E(`Network error: ${a instanceof Error?a.message:"Unknown error"}`,0,5)}if(!i.ok){let a=await i.text().catch(()=>"Unknown error"),s=1;switch(i.status){case 401:s=2;break;case 403:s=2;break;case 404:s=3;break;case 429:s=4;break}throw new E(a,i.status,s)}return i}function tr(t){let o=t.toLowerCase().split(".").pop();return{pdf:"application/pdf",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",txt:"text/plain",md:"text/markdown",csv:"text/csv",json:"application/json",html:"text/html",css:"text/css",js:"application/javascript",ts:"text/x-typescript",py:"text/x-python"}[o||""]||"application/octet-stream"}async function or(t){let o=M();if(!oe())throw new E(`Not authenticated. Run '${m.commands[0]} login' or set ${m.apiKeyEnvVar} environment variable.`,401,2);let e=await le(),n=Qn(t),r=wo(t),i=tr(t),a=await fetch(`${o}/api/cli/files/upload`,{method:"POST",headers:{"Content-Type":"application/json",...e},body:JSON.stringify({fileMetadata:{name:r,type:i,size:n.size}})});if(!a.ok){let y=await a.text();throw new E(y||"Failed to get upload URL",a.status,1)}let s=await a.json(),d=Xn(t),u=new FormData;for(let[y,w]of Object.entries(s.fields||{}))u.append(y,w);let p=new Blob([d],{type:i});u.append("file",p,r);let k=await fetch(s.url,{method:"POST",body:u});if(!k.ok)throw new E("Failed to upload file to storage",k.status,1);return{id:Zn(),name:r,url:s.fileUrl,size:n.size,type:i,selected:!0}}async function xo(t,o){let e=[];for(let n=0;n<t.length;n++){let r=t[n],i=wo(r);o?.(n,t.length,i);let a=await or(r);e.push(a)}return o?.(t.length,t.length,""),e}async function So(t){let o={};for(let[e,n]of Object.entries(t))n!==void 0&&(o[e]=n);return er("/api/slides/cli/create-presentation",o)}async function $o(t,o=20){let e=new URLSearchParams;return e.set("limit",String(o)),t&&e.set("teamId",t),(await N(`/api/cli/presentations?${e}`)).presentations}async function Ce(t){return N(`/api/cli/presentation/${t}`)}async function Co(t){await N(`/api/cli/presentation/${t}`,{method:"DELETE"})}async function ko(t,o={}){let e=M();if(!oe())throw new E(`Not authenticated. Run '${m.commands[0]} login' or set ${m.apiKeyEnvVar} environment variable.`,401,2);let n=await le(),r=await fetch(`${e}/api/presentations/export`,{method:"POST",headers:{"Content-Type":"application/json",...n},body:JSON.stringify({presentationId:t,options:{includeImages:o.includeImages??!0,includeBranding:o.includeBranding??!0,downloadExternalImages:o.includeExternal??!0}})});if(!r.ok)throw new E(await r.text(),r.status,r.status===404?3:1);return r.arrayBuffer()}async function To(t,o,e={}){let n=M();if(!oe())throw new E(`Not authenticated. Run '${m.commands[0]} login' or set ${m.apiKeyEnvVar} environment variable.`,401,2);let r=await le(),i=new FormData,a=new Blob([t],{type:"application/zip"});i.append("file",a,o),i.append("options",JSON.stringify({dryRun:e.dryRun??!1,remapIds:!0}));let s=await fetch(`${n}/api/presentations/import`,{method:"POST",headers:r,body:i}),d=await s.json();if(!s.ok)throw new E(d.errors?.[0]?.message??"Import failed",s.status,1);return d}async function Io(){return N("/api/cli/branding")}async function At(t){return N(`/api/branding/${t}`)}async function Ro(t,o){return N("/api/cli/branding/extract",{method:"POST",body:{url:t,teamId:o}})}async function fe(){return N("/api/cli/whoami")}async function Ao(){return N("/api/cli/features")}async function Po(t,o,e={}){let n=M();if(!oe())throw new E(`Not authenticated. Run '${m.commands[0]} login' or set ${m.apiKeyEnvVar} environment variable.`,401,2);let r=await le(),i=await fetch(`${n}/api/presentations/${t}/generate-blog`,{method:"POST",headers:{"Content-Type":"application/json",...r},body:JSON.stringify({documentCreatedAt:o,targetWordCount:e.targetWordCount??300,tone:e.tone??"professional",language:e.language??"en",targetAudience:e.targetAudience??"General Audience",blogFormat:e.blogFormat??"narrative",customInstructions:e.customInstructions??"",includeImages:e.includeImages??!0,includeTableOfContents:e.includeTableOfContents??!1})});if(!i.ok)throw new E(await i.text(),i.status,i.status===404?3:1);return i}async function nr(t){let o=new URLSearchParams;t&&o.set("teamId",t);let e=o.toString();return N(`/api/cli/limits${e?`?${e}`:""}`)}async function Oo(t,o,e){let n=await nr(e);if(!n.canGenerate[t]){let r=n.remaining[t]??0,i=n.limits[t]??0;throw i===0?new E(`The "${t}" mode is not available on your ${n.planName} plan. Available modes: ${n.availableModes.join(", ")||"none"}`,403,2):new E(`You have reached your ${t} generation limit for this billing cycle (${n.usage[t]??0}/${i} used). Try a different mode: ${n.availableModes.join(", ")||"none available"}`,403,4)}if(o>n.maxSlidesPerPresentation)throw new E(`Maximum ${n.maxSlidesPerPresentation} slides allowed on your ${n.planName} plan (requested: ${o})`,403,6);return n}function Eo(t){return t.replace(/[\u2014\u2013]/g,"-").replace(/[\u201C\u201D]/g,'"').replace(/[\u2018\u2019]/g,"'").replace(/\u2026/g,"...").replace(/\u00A0/g," ").replace(/[\u2010-\u2015]/g,"-").replace(/[\u2032\u2033]/g,"'")}async function rt(t){let o=M();if(!oe())throw new E(`Not authenticated. Run '${m.commands[0]} login' or set ${m.apiKeyEnvVar} environment variable.`,401,2);let e=await le(),n={...t,text:Eo(t.text)},r;try{r=await fetch(`${o}/api/cli/tts`,{method:"POST",headers:{"Content-Type":"application/json",...e},body:JSON.stringify(n)})}catch(y){throw new E(`Network error: ${y instanceof Error?y.message:"Unknown error"}`,0,5)}if(!r.ok){let y=await r.text().catch(()=>"Unknown error"),w;try{let $=JSON.parse(y);w=$.error||$.message||y}catch{w=y}throw new E(w,r.status,r.status===401?2:1)}let i=Buffer.from(await r.arrayBuffer()),a=parseFloat(r.headers.get("X-Duration-Seconds")||"0"),s=parseFloat(r.headers.get("X-Cost-USD")||"0"),d=r.headers.get("X-Provider")||"unknown",u=r.headers.get("X-Audio-Format")||"mp3",p,k=r.headers.get("X-Timestamps");if(k)try{p=JSON.parse(k)}catch{}return{audioData:i,duration:a,cost:s,provider:d,format:u,timestamps:p}}async function Fo(t){let o=M();if(!oe())throw new E(`Not authenticated. Run '${m.commands[0]} login' or set ${m.apiKeyEnvVar} environment variable.`,401,2);let e=await le(),n={...t,texts:t.texts.map(a=>({...a,text:Eo(a.text)}))},r;try{r=await fetch(`${o}/api/cli/tts/batch`,{method:"POST",headers:{"Content-Type":"application/json",...e},body:JSON.stringify(n)})}catch(a){throw new E(`Network error: ${a instanceof Error?a.message:"Unknown error"}`,0,5)}if(!r.ok){let a=await r.text().catch(()=>"Unknown error"),s;try{let d=JSON.parse(a);s=d.error||d.message||a}catch{s=a}throw new E(s,r.status,r.status===401?2:1)}let i=await r.json();return i.results=i.results.map(a=>({...a,audioData:Buffer.from(a.audioData,"base64")})),i}async function Uo(t){let o=t?`/api/cli/tts?provider=${t}`:"/api/cli/tts";return N(o)}async function it(t){let o=await N("/api/cli/music",{method:"POST",body:t});if(!o.data)throw new E(`Invalid API response: ${JSON.stringify(o)}`,500,1);return{requestId:o.data.id,status:o.data.status,audioUrl:o.data.audioUrl,duration:o.data.duration,cost:o.data.cost,error:o.data.error}}async function _e(t){let o=await N(`/api/cli/music?requestId=${encodeURIComponent(t)}`);return{requestId:o.data.id,status:o.data.status,audioUrl:o.data.audioUrl,duration:o.data.duration,cost:o.data.cost,error:o.data.error}}async function Do(t){return N("/api/cli/mix",{method:"POST",body:t})}async function Pt(t){return N(`/api/cli/mix?requestId=${encodeURIComponent(t)}`)}async function at(t){return N("/api/cli/images/search",{method:"POST",body:t})}async function Lo(t){return N("/api/cli/images/generate",{method:"POST",body:t})}async function Ot(t){return N("/api/cli/videos/search",{method:"POST",body:t})}async function de(t,o=60,e=2e3){for(let n=0;n<o;n++){let r=await t();if(r.status==="completed"||r.status==="failed")return r;await new Promise(i=>setTimeout(i,e))}throw new E("Operation timed out",408,1)}async function No(t){return N("/api/cli/scrape",{method:"POST",body:{url:t}})}async function jo(t){let o=await N("/api/cli/sfx",{method:"POST",body:t});if(!o.data)throw new E(`Invalid API response: ${JSON.stringify(o)}`,500,1);return{id:o.data.id,status:o.data.status,audioUrl:o.data.audioUrl,duration:o.data.duration,format:o.data.format,cost:o.data.cost,error:o.data.error,requestId:o.data.requestId}}async function Et(t){let o=await N(`/api/cli/sfx?requestId=${encodeURIComponent(t)}`);return{id:o.data.id,status:o.data.status,audioUrl:o.data.audioUrl,duration:o.data.duration,format:o.data.format,cost:o.data.cost,error:o.data.error,requestId:o.data.requestId}}async function Mo(t){return N("/api/cli/video/plan",{method:"POST",body:t})}var st=new rr({projectName:"conceptcraft",configName:"feature-cache"}),ir=3600*1e3,ar=1440*60*1e3;function qo(t){let o=0;for(let e=0;e<t.length;e++){let n=t.charCodeAt(e);o=(o<<5)-o+n,o=o&o}return o.toString(16)}function Vo(){if(!Se())return null;let t=st.get("featureFlags");if(!t)return null;let o=qo(ie());if(t.apiKeyHash&&t.apiKeyHash!==o)return he(),null;let e=Date.now()-t.fetchedAt;return e>ar?null:(e>ir&&Ft(),t.flags)}function Ft(){ge().catch(()=>{})}async function ge(){let t=await Ao(),o=ie();return st.set("featureFlags",{flags:t,fetchedAt:Date.now(),apiKeyHash:o?qo(o):void 0}),t}function he(){st.delete("featureFlags")}function Bo(){return st.path}var dr=`${m.displayName} CLI`,mr=8765,ur=8775;function pr(){return Jo(32).toString("base64url")}function fr(t){return cr("sha256").update(t).digest("base64url")}function gr(){return Jo(16).toString("hex")}async function hr(t,o){for(let e=t;e<=o;e++)try{return await new Promise((n,r)=>{let i=Wo.createServer();i.listen(e,()=>{i.close(()=>n())}),i.on("error",r)}),e}catch{continue}throw new Error(`No available port found between ${t} and ${o}`)}async function yr(t){let o=await fetch(`${t}/.well-known/oauth-authorization-server`);if(!o.ok)throw new Error(`Failed to fetch OAuth metadata: ${o.status}`);return o.json()}async function br(t,o){let e=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({client_name:dr,redirect_uris:[o],grant_types:["authorization_code","refresh_token"],response_types:["code"],token_endpoint_auth_method:"none",scope:"presentations:read presentations:write"})});if(!e.ok){let n=await e.text();throw new Error(`Client registration failed: ${n}`)}return e.json()}async function vr(t,o,e,n,r){let i=new URLSearchParams({grant_type:"authorization_code",code:o,redirect_uri:n,client_id:r,code_verifier:e}),a=await fetch(t,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:i.toString()});if(!a.ok){let s=await a.text();throw new Error(`Token exchange failed: ${s}`)}return a.json()}function wr(t,o){return new Promise((e,n)=>{let r,i=!1,a=()=>{i||(i=!0,clearTimeout(r),process.off("SIGINT",s),process.off("SIGTERM",s),d.close())},s=()=>{a(),n(new Error("Login cancelled"))},d=Wo.createServer((u,p)=>{let k=new URL(u.url||"",`http://localhost:${t}`);if(k.pathname!=="/callback"){p.writeHead(404),p.end("Not found");return}let y=k.searchParams.get("code"),w=k.searchParams.get("state"),$=k.searchParams.get("error"),I=k.searchParams.get("error_description");if(p.writeHead(200,{"Content-Type":"text/html"}),$){p.end(`
|
|
3
|
+
import{Command as ya}from"commander";import Z from"chalk";import nr from"path";var At={conceptcraft:{id:"conceptcraft",name:"conceptcraft",displayName:"ConceptCraft",description:"CLI tool for ConceptCraft presentation generation",commands:["ccr","conceptcraft"],apiUrl:"https://conceptcraft.ai",docsUrl:"https://docs.conceptcraft.ai",packageName:"@conceptcraft/cli",configDir:".conceptcraft",apiKeyEnvVar:"CONCEPTCRAFT_API_KEY",apiUrlEnvVar:"CONCEPTCRAFT_API_URL"},mindframes:{id:"mindframes",name:"mindframes",displayName:"Mindframes",description:"CLI tool for Mindframes presentation generation",commands:["mf","mindframes"],apiUrl:"https://mindframes.app",docsUrl:"https://docs.mindframes.app",packageName:"@conceptcraft/mindframes",configDir:".mindframes",apiKeyEnvVar:"MINDFRAMES_API_KEY",apiUrlEnvVar:"MINDFRAMES_API_URL"}},mn={cc:"conceptcraft",conceptcraft:"conceptcraft",mf:"mindframes",mindframes:"mindframes"};function or(){let t=process.argv[1]||"",n=nr.basename(t).replace(/\.(js|ts)$/,""),e=mn[n];if(e)return At[e];for(let[o,r]of Object.entries(mn))if(t.includes(`/${o}`)||t.includes(`\\${o}`))return At[r];return At.mindframes}var u=or();import{Command as vr}from"commander";import Hn from"chalk";import Kn from"ora";import zn from"http";import{randomBytes as Xn,createHash as xr}from"crypto";import wr from"open";import rr from"conf";var un="https://www.mindframes.app",ir={apiKey:{type:"string"},apiUrl:{type:"string",default:un},defaultTeamId:{type:"string"},accessToken:{type:"string"},refreshToken:{type:"string"},tokenExpiresAt:{type:"number"},clientId:{type:"string"},clientSecret:{type:"string"}},E=new rr({projectName:u.name,schema:ir});function pn(){return{apiKey:ie(),apiUrl:j(),defaultTeamId:E.get("defaultTeamId"),accessToken:E.get("accessToken"),refreshToken:E.get("refreshToken"),tokenExpiresAt:E.get("tokenExpiresAt"),clientId:E.get("clientId"),clientSecret:E.get("clientSecret")}}function ie(){let t=process.env[u.apiKeyEnvVar]??process.env.CC_SLIDES_API_KEY;return t||E.get("apiKey")}function Ot(t){E.set("apiKey",t)}function j(){let t=process.env[u.apiUrlEnvVar]??process.env.CC_SLIDES_API_URL;return t||(E.get("apiUrl")??un)}function Et(t){E.set("apiUrl",t)}function Ze(){return E.get("defaultTeamId")}function Se(t){E.set("defaultTeamId",t)}function et(){E.clear()}function tt(){return E.path}function Te(){return!!ie()}function fn(){return E.get("accessToken")}function gn(){return E.get("refreshToken")}function nt(t,n,e){E.set("accessToken",t),E.set("refreshToken",n),E.set("tokenExpiresAt",Date.now()+(e-60)*1e3)}function ot(){E.delete("accessToken"),E.delete("refreshToken"),E.delete("tokenExpiresAt")}function hn(){let t=E.get("tokenExpiresAt");return t?Date.now()>=t:!0}function pe(){return!!E.get("accessToken")&&!!E.get("refreshToken")}function rt(){return E.get("clientId")}function it(){return E.get("clientSecret")}function yn(t,n){E.set("clientId",t),E.set("clientSecret",n)}import P from"chalk";import Pt from"cli-table3";function bn(){return process.stdout.isTTY??!1}function b(t,n="human"){n!=="quiet"&&n!=="json"&&console.log(P.green("\u2713"),t)}function c(t,n="human"){if(n!=="quiet"){if(n==="json"){console.error(JSON.stringify({error:t}));return}console.error(P.red("\u2717"),t)}}function R(t,n="human"){n!=="quiet"&&n!=="json"&&console.warn(P.yellow("\u26A0"),t)}function d(t,n="human"){n!=="quiet"&&n!=="json"&&console.log(P.blue("\u2139"),t)}function Ce(t,n="en"){return t?`${j()}/${n}/view/presentations/${t}`:"N/A"}function vn(t,n={}){let{showLinks:e=!1,language:o="en"}=n,r=(s,l)=>s.length>l?s.slice(0,l-1)+"\u2026":s,i=s=>{try{return new Date(s).toLocaleString("en-US",{month:"short",day:"numeric",hour:"numeric",minute:"2-digit",hour12:!0}).replace(" at ",", ")}catch{return"-"}};if(e){let s=new Pt({head:[P.cyan("Slug"),P.cyan("Title"),P.cyan("Slides"),P.cyan("Created"),P.cyan("URL")]});for(let l of t)s.push([r(l.slug||l.id.slice(0,8),30),r(l.title||"Untitled",22),String(l.numberOfSlides||"-"),i(l.createdAt),Ce(l.slug,o)]);return s.toString()}let a=new Pt({head:[P.cyan("Slug"),P.cyan("Title"),P.cyan("Slides"),P.cyan("Mode"),P.cyan("Created")],colWidths:[32,28,8,11,18]});for(let s of t)a.push([s.slug||s.id.slice(0,8),s.title||"Untitled",String(s.numberOfSlides||"-"),s.mode||"-",i(s.createdAt)]);return a.toString()}function xn(t){return t.map(n=>n.slug||n.id).join(`
|
|
4
|
+
`)}function wn(t){let n=[...t].sort((r,i)=>{if(r.isDefault&&!i.isDefault)return-1;if(!r.isDefault&&i.isDefault)return 1;let a=r.createdAt?new Date(r.createdAt).getTime():0;return(i.createdAt?new Date(i.createdAt).getTime():0)-a}),e=r=>{if(!r)return"-";try{return new URL(r).href}catch{return r.startsWith("http")?r:`https://${r}`}},o=new Pt({head:[P.cyan("Name"),P.cyan("Source"),P.cyan("Color"),P.cyan("Logo"),P.cyan("Default"),P.cyan("ID")]});for(let r of n){let i=r.primaryColor?`${P.bgHex(r.primaryColor)(" ")} ${r.primaryColor}`:"-";o.push([r.name,e(r.sourceUrl),i,r.logoUrl?P.green("\u2713"):P.gray("\u2717"),r.isDefault?P.green("\u2605"):"",r.id])}return o.toString()}function Ft(t){try{return new Date(t).toLocaleString()}catch{return t}}function te(t){console.log(),console.log(P.bold(t)),console.log(P.gray("\u2500".repeat(t.length)))}function v(t,n){console.log(` ${P.gray(t+":")} ${n??"-"}`)}function Sn(t,n,e=30,o=!0){let r=Math.min(100,Math.round(t/n*100)),i=Math.round(e*t/n),a=e-i,s=P.green("\u2588".repeat(i))+P.gray("\u2591".repeat(a));return o?`[${s}] ${r}%`:`[${s}]`}function ar(t){return JSON.stringify(t,null,2)}function _(t){console.log(ar(t))}import hr from"conf";import at from"chalk";var S={SUCCESS:0,GENERAL_ERROR:1,AUTH_ERROR:2,NOT_FOUND:3,RATE_LIMIT:4,NETWORK_ERROR:5,INVALID_INPUT:6};async function sr(){let t=gn(),n=rt(),e=j();if(!t||!n)return null;try{let o=await fetch(`${e}/.well-known/oauth-authorization-server`);if(!o.ok)return null;let r=await o.json(),i=new URLSearchParams({grant_type:"refresh_token",refresh_token:t,client_id:n}),a=it();a&&i.set("client_secret",a);let s=await fetch(r.token_endpoint,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:i.toString()});if(!s.ok)return ot(),null;let l=await s.json();return nt(l.access_token,l.refresh_token,l.expires_in),l.access_token}catch{return null}}async function Tn(){return pe()?hn()?sr():fn()||null:null}function ne(){return pe()||Te()}async function N(){if(!ne()){let t=u.commands[0],n=u.apiKeyEnvVar;console.error(at.red("\u2717 Not authenticated.")),console.error(),console.error(at.bold("To authenticate, run:")),console.error(at.cyan(` ${t} login`)),console.error(),console.error(at.gray(`Or set ${n} environment variable.`)),process.exit(S.AUTH_ERROR)}}function cr(t){return t.length<=14?"****":`${t.slice(0,10)}...${t.slice(-4)}`}function Cn(){let t=ie();if(t)return cr(t)}function Nt(t){return["cc_slides_","mcp_","cc_sk_","sk_"].some(e=>t.startsWith(e))}import{readFileSync as lr,statSync as dr}from"fs";import{basename as kn}from"path";import{randomUUID as mr}from"crypto";async function le(){let t=await Tn();if(t)return{Authorization:`Bearer ${t}`};let n=ie();return n?{"x-api-key":n}:{}}var F=class extends Error{constructor(e,o,r=1){super(e);this.statusCode=o;this.exitCode=r;this.name="ApiError"}};async function L(t,n={}){let e=j();if(!ne())throw new F(`Not authenticated. Run '${u.commands[0]} login' or set ${u.apiKeyEnvVar} environment variable.`,401,2);let o=await le(),r=`${e}${t}`,i={...o,...n.headers};n.body&&!n.stream&&(i["Content-Type"]="application/json");let a={method:n.method??"GET",headers:i};n.body&&(a.body=JSON.stringify(n.body));let s;try{s=await fetch(r,a)}catch(m){throw new F(`Network error: ${m instanceof Error?m.message:"Unknown error"}`,0,5)}if(!s.ok){let m=await s.text().catch(()=>""),p=1;switch(s.status){case 401:p=2;break;case 403:p=2;break;case 404:p=3;break;case 429:p=4;break;case 500:p=1;break}let k=m.trim()||`HTTP ${s.status}: ${s.statusText||"Server error"}`;throw new F(k,s.status,p)}return n.stream?s:s.headers.get("content-type")?.includes("application/json")?s.json():s.text()}async function ur(t,n){let e=j();if(!ne())throw new F(`Not authenticated. Run '${u.commands[0]} login' or set ${u.apiKeyEnvVar} environment variable.`,401,2);let o=await le(),r=`${e}${t}`,i;try{i=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json",...o},body:JSON.stringify(n)})}catch(a){throw new F(`Network error: ${a instanceof Error?a.message:"Unknown error"}`,0,5)}if(!i.ok){let a=await i.text().catch(()=>"Unknown error"),s=1;switch(i.status){case 401:s=2;break;case 403:s=2;break;case 404:s=3;break;case 429:s=4;break}throw new F(a,i.status,s)}return i}function pr(t){let n=t.toLowerCase().split(".").pop();return{pdf:"application/pdf",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",txt:"text/plain",md:"text/markdown",csv:"text/csv",json:"application/json",html:"text/html",css:"text/css",js:"application/javascript",ts:"text/x-typescript",py:"text/x-python"}[n||""]||"application/octet-stream"}async function fr(t){let n=j();if(!ne())throw new F(`Not authenticated. Run '${u.commands[0]} login' or set ${u.apiKeyEnvVar} environment variable.`,401,2);let e=await le(),o=dr(t),r=kn(t),i=pr(t),a=await fetch(`${n}/api/cli/files/upload`,{method:"POST",headers:{"Content-Type":"application/json",...e},body:JSON.stringify({fileMetadata:{name:r,type:i,size:o.size}})});if(!a.ok){let h=await a.text();throw new F(h||"Failed to get upload URL",a.status,1)}let s=await a.json(),l=lr(t),m=new FormData;for(let[h,f]of Object.entries(s.fields||{}))m.append(h,f);let p=new Blob([l],{type:i});m.append("file",p,r);let k=await fetch(s.url,{method:"POST",body:m});if(!k.ok)throw new F("Failed to upload file to storage",k.status,1);return{id:mr(),name:r,url:s.fileUrl,size:o.size,type:i,selected:!0}}async function $n(t,n){let e=[];for(let o=0;o<t.length;o++){let r=t[o],i=kn(r);n?.(o,t.length,i);let a=await fr(r);e.push(a)}return n?.(t.length,t.length,""),e}async function In(t){let n={};for(let[e,o]of Object.entries(t))o!==void 0&&(n[e]=o);return ur("/api/slides/cli/create-presentation",n)}async function Rn(t,n=20){let e=new URLSearchParams;return e.set("limit",String(n)),t&&e.set("teamId",t),(await L(`/api/cli/presentations?${e}`)).presentations}async function ke(t){return L(`/api/cli/presentation/${t}`)}async function An(t){await L(`/api/cli/presentation/${t}`,{method:"DELETE"})}async function On(t,n={}){let e=j();if(!ne())throw new F(`Not authenticated. Run '${u.commands[0]} login' or set ${u.apiKeyEnvVar} environment variable.`,401,2);let o=await le(),r=await fetch(`${e}/api/presentations/export`,{method:"POST",headers:{"Content-Type":"application/json",...o},body:JSON.stringify({presentationId:t,options:{includeImages:n.includeImages??!0,includeBranding:n.includeBranding??!0,downloadExternalImages:n.includeExternal??!0}})});if(!r.ok)throw new F(await r.text(),r.status,r.status===404?3:1);return r.arrayBuffer()}async function En(t,n,e={}){let o=j();if(!ne())throw new F(`Not authenticated. Run '${u.commands[0]} login' or set ${u.apiKeyEnvVar} environment variable.`,401,2);let r=await le(),i=new FormData,a=new Blob([t],{type:"application/zip"});i.append("file",a,n),i.append("options",JSON.stringify({dryRun:e.dryRun??!1,remapIds:!0}));let s=await fetch(`${o}/api/presentations/import`,{method:"POST",headers:r,body:i}),l=await s.json();if(!s.ok)throw new F(l.errors?.[0]?.message??"Import failed",s.status,1);return l}async function Pn(){return L("/api/cli/branding")}async function Dt(t){return L(`/api/branding/${t}`)}async function Fn(t,n){return L("/api/cli/branding/extract",{method:"POST",body:{url:t,teamId:n}})}async function fe(){return L("/api/cli/whoami")}async function Nn(){return L("/api/cli/features")}async function Dn(t,n,e={}){let o=j();if(!ne())throw new F(`Not authenticated. Run '${u.commands[0]} login' or set ${u.apiKeyEnvVar} environment variable.`,401,2);let r=await le(),i=await fetch(`${o}/api/presentations/${t}/generate-blog`,{method:"POST",headers:{"Content-Type":"application/json",...r},body:JSON.stringify({documentCreatedAt:n,targetWordCount:e.targetWordCount??300,tone:e.tone??"professional",language:e.language??"en",targetAudience:e.targetAudience??"General Audience",blogFormat:e.blogFormat??"narrative",customInstructions:e.customInstructions??"",includeImages:e.includeImages??!0,includeTableOfContents:e.includeTableOfContents??!1})});if(!i.ok)throw new F(await i.text(),i.status,i.status===404?3:1);return i}async function gr(t){let n=new URLSearchParams;t&&n.set("teamId",t);let e=n.toString();return L(`/api/cli/limits${e?`?${e}`:""}`)}async function Un(t,n,e){let o=await gr(e);if(!o.canGenerate[t]){let r=o.remaining[t]??0,i=o.limits[t]??0;throw i===0?new F(`The "${t}" mode is not available on your ${o.planName} plan. Available modes: ${o.availableModes.join(", ")||"none"}`,403,2):new F(`You have reached your ${t} generation limit for this billing cycle (${o.usage[t]??0}/${i} used). Try a different mode: ${o.availableModes.join(", ")||"none available"}`,403,4)}if(n>o.maxSlidesPerPresentation)throw new F(`Maximum ${o.maxSlidesPerPresentation} slides allowed on your ${o.planName} plan (requested: ${n})`,403,6);return o}function Ln(t){return t.replace(/[\u2014\u2013]/g,"-").replace(/[\u201C\u201D]/g,'"').replace(/[\u2018\u2019]/g,"'").replace(/\u2026/g,"...").replace(/\u00A0/g," ").replace(/[\u2010-\u2015]/g,"-").replace(/[\u2032\u2033]/g,"'")}async function st(t){let n=j();if(!ne())throw new F(`Not authenticated. Run '${u.commands[0]} login' or set ${u.apiKeyEnvVar} environment variable.`,401,2);let e=await le(),o={...t,text:Ln(t.text)},r;try{r=await fetch(`${n}/api/cli/tts`,{method:"POST",headers:{"Content-Type":"application/json",...e},body:JSON.stringify(o)})}catch(h){throw new F(`Network error: ${h instanceof Error?h.message:"Unknown error"}`,0,5)}if(!r.ok){let h=await r.text().catch(()=>"Unknown error"),f;try{let x=JSON.parse(h);f=x.error||x.message||h}catch{f=h}throw new F(f,r.status,r.status===401?2:1)}let i=Buffer.from(await r.arrayBuffer()),a=parseFloat(r.headers.get("X-Duration-Seconds")||"0"),s=parseFloat(r.headers.get("X-Cost-USD")||"0"),l=r.headers.get("X-Provider")||"unknown",m=r.headers.get("X-Audio-Format")||"mp3",p,k=r.headers.get("X-Timestamps");if(k)try{p=JSON.parse(k)}catch{}return{audioData:i,duration:a,cost:s,provider:l,format:m,timestamps:p}}async function Mn(t){let n=j();if(!ne())throw new F(`Not authenticated. Run '${u.commands[0]} login' or set ${u.apiKeyEnvVar} environment variable.`,401,2);let e=await le(),o={...t,texts:t.texts.map(a=>({...a,text:Ln(a.text)}))},r;try{r=await fetch(`${n}/api/cli/tts/batch`,{method:"POST",headers:{"Content-Type":"application/json",...e},body:JSON.stringify(o)})}catch(a){throw new F(`Network error: ${a instanceof Error?a.message:"Unknown error"}`,0,5)}if(!r.ok){let a=await r.text().catch(()=>"Unknown error"),s;try{let l=JSON.parse(a);s=l.error||l.message||a}catch{s=a}throw new F(s,r.status,r.status===401?2:1)}let i=await r.json();return i.results=i.results.map(a=>({...a,audioData:Buffer.from(a.audioData,"base64")})),i}async function jn(t){let n=t?`/api/cli/tts?provider=${t}`:"/api/cli/tts";return L(n)}async function ct(t){let n=await L("/api/cli/music",{method:"POST",body:t});if(!n.data)throw new F(`Invalid API response: ${JSON.stringify(n)}`,500,1);return{requestId:n.data.id,status:n.data.status,audioUrl:n.data.audioUrl,duration:n.data.duration,cost:n.data.cost,error:n.data.error}}async function Je(t){let n=await L(`/api/cli/music?requestId=${encodeURIComponent(t)}`);return{requestId:n.data.id,status:n.data.status,audioUrl:n.data.audioUrl,duration:n.data.duration,cost:n.data.cost,error:n.data.error}}async function Vn(t){return L("/api/cli/mix",{method:"POST",body:t})}async function Ut(t){return L(`/api/cli/mix?requestId=${encodeURIComponent(t)}`)}async function lt(t){return L("/api/cli/images/search",{method:"POST",body:t})}async function _n(t){return L("/api/cli/images/generate",{method:"POST",body:t})}async function Lt(t){return L("/api/cli/videos/search",{method:"POST",body:t})}async function de(t,n=60,e=2e3){for(let o=0;o<n;o++){let r=await t();if(r.status==="completed"||r.status==="failed")return r;await new Promise(i=>setTimeout(i,e))}throw new F("Operation timed out",408,1)}async function qn(t){return L("/api/cli/scrape",{method:"POST",body:{url:t}})}async function Bn(t){let n=await L("/api/cli/sfx",{method:"POST",body:t});if(!n.data)throw new F(`Invalid API response: ${JSON.stringify(n)}`,500,1);return{id:n.data.id,status:n.data.status,audioUrl:n.data.audioUrl,duration:n.data.duration,format:n.data.format,cost:n.data.cost,error:n.data.error,requestId:n.data.requestId}}async function Mt(t){let n=await L(`/api/cli/sfx?requestId=${encodeURIComponent(t)}`);return{id:n.data.id,status:n.data.status,audioUrl:n.data.audioUrl,duration:n.data.duration,format:n.data.format,cost:n.data.cost,error:n.data.error,requestId:n.data.requestId}}async function Gn(t){return L("/api/cli/video/plan",{method:"POST",body:t})}var dt=new hr({projectName:"conceptcraft",configName:"feature-cache"}),yr=3600*1e3,br=1440*60*1e3;function Wn(t){let n=0;for(let e=0;e<t.length;e++){let o=t.charCodeAt(e);n=(n<<5)-n+o,n=n&n}return n.toString(16)}function Jn(){if(!Te())return null;let t=dt.get("featureFlags");if(!t)return null;let n=Wn(ie());if(t.apiKeyHash&&t.apiKeyHash!==n)return he(),null;let e=Date.now()-t.fetchedAt;return e>br?null:(e>yr&&jt(),t.flags)}function jt(){ge().catch(()=>{})}async function ge(){let t=await Nn(),n=ie();return dt.set("featureFlags",{flags:t,fetchedAt:Date.now(),apiKeyHash:n?Wn(n):void 0}),t}function he(){dt.delete("featureFlags")}function Yn(){return dt.path}var Sr=`${u.displayName} CLI`,Tr=8765,Cr=8775;function kr(){return Xn(32).toString("base64url")}function $r(t){return xr("sha256").update(t).digest("base64url")}function Ir(){return Xn(16).toString("hex")}async function Rr(t,n){for(let e=t;e<=n;e++)try{return await new Promise((o,r)=>{let i=zn.createServer();i.listen(e,()=>{i.close(()=>o())}),i.on("error",r)}),e}catch{continue}throw new Error(`No available port found between ${t} and ${n}`)}async function Ar(t){let n=await fetch(`${t}/.well-known/oauth-authorization-server`);if(!n.ok)throw new Error(`Failed to fetch OAuth metadata: ${n.status}`);return n.json()}async function Or(t,n){let e=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({client_name:Sr,redirect_uris:[n],grant_types:["authorization_code","refresh_token"],response_types:["code"],token_endpoint_auth_method:"none",scope:"presentations:read presentations:write"})});if(!e.ok){let o=await e.text();throw new Error(`Client registration failed: ${o}`)}return e.json()}async function Er(t,n,e,o,r){let i=new URLSearchParams({grant_type:"authorization_code",code:n,redirect_uri:o,client_id:r,code_verifier:e}),a=await fetch(t,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:i.toString()});if(!a.ok){let s=await a.text();throw new Error(`Token exchange failed: ${s}`)}return a.json()}function Pr(t,n){return new Promise((e,o)=>{let r,i=!1,a=()=>{i||(i=!0,clearTimeout(r),process.off("SIGINT",s),process.off("SIGTERM",s),l.close())},s=()=>{a(),o(new Error("Login cancelled"))},l=zn.createServer((m,p)=>{let k=new URL(m.url||"",`http://localhost:${t}`);if(k.pathname!=="/callback"){p.writeHead(404),p.end("Not found");return}let h=k.searchParams.get("code"),f=k.searchParams.get("state"),x=k.searchParams.get("error"),I=k.searchParams.get("error_description");if(p.writeHead(200,{"Content-Type":"text/html"}),x){p.end(`
|
|
5
5
|
<!DOCTYPE html>
|
|
6
6
|
<html>
|
|
7
7
|
<head><title>Login Failed</title></head>
|
|
8
8
|
<body style="font-family: system-ui; text-align: center; padding: 50px;">
|
|
9
9
|
<h1 style="color: #dc2626;">Login Failed</h1>
|
|
10
|
-
<p>${I
|
|
10
|
+
<p>${I||x}</p>
|
|
11
11
|
<p>You can close this window.</p>
|
|
12
12
|
</body>
|
|
13
13
|
</html>
|
|
14
|
-
`),a(),
|
|
14
|
+
`),a(),o(new Error(I||x));return}if(!h||!f){p.end(`
|
|
15
15
|
<!DOCTYPE html>
|
|
16
16
|
<html>
|
|
17
17
|
<head><title>Login Failed</title></head>
|
|
@@ -21,7 +21,7 @@ import{Command as Yi}from"commander";import Z from"chalk";import _n from"path";v
|
|
|
21
21
|
<p>You can close this window.</p>
|
|
22
22
|
</body>
|
|
23
23
|
</html>
|
|
24
|
-
`),a(),
|
|
24
|
+
`),a(),o(new Error("Missing authorization code or state"));return}if(f!==n){p.end(`
|
|
25
25
|
<!DOCTYPE html>
|
|
26
26
|
<html>
|
|
27
27
|
<head><title>Login Failed</title></head>
|
|
@@ -31,7 +31,7 @@ import{Command as Yi}from"commander";import Z from"chalk";import _n from"path";v
|
|
|
31
31
|
<p>You can close this window.</p>
|
|
32
32
|
</body>
|
|
33
33
|
</html>
|
|
34
|
-
`),a(),
|
|
34
|
+
`),a(),o(new Error("State mismatch"));return}p.end(`
|
|
35
35
|
<!DOCTYPE html>
|
|
36
36
|
<html>
|
|
37
37
|
<head><title>Login Successful</title></head>
|
|
@@ -40,17 +40,17 @@ import{Command as Yi}from"commander";import Z from"chalk";import _n from"path";v
|
|
|
40
40
|
<p>You can close this window and return to the terminal.</p>
|
|
41
41
|
</body>
|
|
42
42
|
</html>
|
|
43
|
-
`),a(),e({code:y,state:w})});d.listen(t),process.once("SIGINT",s),process.once("SIGTERM",s),r=setTimeout(()=>{a(),n(new Error("Login timed out - no callback received within 5 minutes"))},300*1e3)})}async function xr(t,o){let e=await fetch(`${t}/api/cli/whoami`,{headers:{Authorization:`Bearer ${o}`}});if(!e.ok)throw new Error(`Failed to fetch user info: ${e.status}`);return e.json()}async function Sr(t){let o=M(),e=_o("Discovering OAuth server...").start();try{let n=await yr(o);e.succeed("Connecting to "+o);let r=await hr(mr,ur),i=`http://localhost:${r}/callback`;console.log("Redirect URI: "+i),console.log("Authorization endpoint: "+n.authorization_endpoint),console.log("Token endpoint: "+n.token_endpoint),console.log("Registration endpoint: "+n.registration_endpoint);let a=ot(),s=mo();if(!a){let x=await br(n.registration_endpoint,i);a=x.client_id,s=x.client_secret,uo(a,s)}let d=pr(),u=fr(d),p=gr(),k=new URLSearchParams({client_id:a,redirect_uri:i,response_type:"code",scope:"presentations:read presentations:write",state:p,code_challenge:u,code_challenge_method:"S256"}),y=`${n.authorization_endpoint}?${k}`;console.log("Authorization URL: "+y),t.browser?(l("Opening browser..."),await lr(y)):(console.log(Go.bold("Open this URL in your browser:")),console.log(Go.cyan(y)));let w=wr(r,p),{code:$}=await w;e=_o("Completing login...").start();let I=await vr(n.token_endpoint,$,d,i,a);et(I.access_token,I.refresh_token,I.expires_in);let T=await xr(o,I.access_token);e.succeed("Logged in!"),console.log(),h("Logged in as",T.user.email),T.currentTeam&&(xe(T.currentTeam.id),h("Team",`${T.currentTeam.name} (${T.currentTeam.planName})`));try{await ge()}catch{}console.log(),g("You're all set!"),console.log()}catch(n){throw e.fail("Login failed"),c(n instanceof Error?n.message:String(n)),n}}var Ko=new sr("login").description(`Authenticate with ${m.displayName} (opens browser)`).option("--no-browser","Print URL instead of opening browser").action(async t=>{console.log(),pe()&&(R("You are already logged in."),l(`Run '${m.commands[0]} logout' to log out first, or continue to re-authenticate.`),console.log());try{await Sr(t),process.exit(0)}catch{process.exit(1)}});import{Command as $r}from"commander";import{confirm as Cr}from"@inquirer/prompts";var Ho=new $r("logout").description("Log out and clear authentication").option("--all","Clear all config including API key").action(async t=>{console.log();let o=pe(),e=Se();if(!o&&!e){R("You are not logged in."),console.log();return}if(t.all)try{await Cr({message:"Clear all configuration including API key?",default:!1})?(Qe(),he(),g("All configuration cleared.")):l("Cancelled.")}catch{l("Cancelled.")}else tt(),he(),g("Logged out successfully."),e&&l("Note: Your API key is still configured. Use --all to clear everything.");console.log()});import{Command as ye}from"commander";import We from"chalk";import{input as kr,password as Tr,confirm as Yo,select as Ir}from"@inquirer/prompts";import Ut from"ora";var zo=new ye("config").description("Manage CLI configuration").addCommand(new ye("init").description("Initialize configuration interactively").action(async()=>{console.log(),console.log(We.bold(`${m.displayName} CLI Configuration`)),console.log(We.gray("\u2500".repeat(35))),console.log();try{let t=await Tr({message:"Enter your API key:",mask:"*",validate:n=>!n||n.trim().length===0?"API key is required":Rt(n.trim())?!0:"Invalid API key format. Keys should start with 'cc_slides_' or 'mcp_'"});if(Ct(t.trim()),await Yo({message:`Use a custom API URL? (default: ${m.apiUrl.replace("https://","")})`,default:!1})){let n=await kr({message:"Enter API URL:",default:M(),validate:r=>{try{return new URL(r),!0}catch{return"Invalid URL format"}}});kt(n)}console.log();let e=Ut("Verifying API key...").start();try{let n=await fe();if(e.succeed("API key verified!"),console.log(),l(`Logged in as: ${n.user.email}`),n.teams.length===0)R("No teams found for this user.");else if(n.teams.length===1){let r=n.teams[0];xe(r.id),l(`Team: ${r.name} (${r.planName})`)}else{console.log(),l(`You have access to ${n.teams.length} teams.`);let r=await Ir({message:"Select your default team:",choices:n.teams.map(a=>({name:`${a.name} (${a.planName}) - ${a.role}${a.isCurrent?" [current]":""}`,value:a.id})),default:n.currentTeam?.id});xe(r);let i=n.teams.find(a=>a.id===r);g(`Selected team: ${i?.name}`)}try{await ge()}catch{}}catch(n){e.fail("Failed to verify API key"),R(`Could not verify API key: ${n instanceof Error?n.message:String(n)}`),R(`You may need to set the team ID manually: ${m.commands[0]} config set team-id <id>`)}console.log(),g("Configuration saved!"),l(`Config file: ${Ze()}`),console.log()}catch(t){if(t.name==="ExitPromptError"){console.log(),l("Configuration cancelled.");return}throw t}})).addCommand(new ye("show").description("Show current configuration").option("--verify","Verify API key and show team details").action(async t=>{let o=ao();if(te("Current Configuration"),console.log(),h("API Key",vo()??We.red("Not set")),h("API URL",o.apiUrl),h("Default Team ID",o.defaultTeamId??We.gray("Not set")),console.log(),h("Config file",Ze()),t.verify&&o.apiKey){console.log();let e=Ut("Verifying...").start();try{let n=await fe();e.succeed("Verified"),console.log(),h("User",n.user.email),n.currentTeam&&h("Current Team",`${n.currentTeam.name} (${n.currentTeam.planName})`),n.teams.length>1&&h("Total Teams",String(n.teams.length))}catch(n){e.fail("Verification failed"),R(n instanceof Error?n.message:String(n))}}console.log()})).addCommand(new ye("set").description("Set a configuration value").argument("<key>","Configuration key (api-key, api-url, team-id)").argument("<value>","Value to set").action(async(t,o)=>{switch(t){case"api-key":Rt(o)||(c("Invalid API key format. Keys should start with 'cc_slides_' or 'mcp_'"),process.exit(6)),Ct(o),he(),g("API key updated"),ge().catch(()=>{});break;case"api-url":try{new URL(o),kt(o),g(`API URL set to: ${o}`)}catch{c("Invalid URL format"),process.exit(6)}break;case"team-id":xe(o),g(`Default team ID set to: ${o}`);break;default:c(`Unknown config key: ${t}`),console.log(We.gray("Valid keys: api-key, api-url, team-id")),process.exit(6)}})).addCommand(new ye("clear").description("Clear all configuration").action(async()=>{try{await Yo({message:"Are you sure you want to clear all configuration?",default:!1})?(Qe(),he(),g("Configuration cleared")):l("Cancelled")}catch{l("Cancelled")}})).addCommand(new ye("refresh").description("Refresh cached feature flags").action(async()=>{let t=Ut("Refreshing feature flags...").start();try{await ge(),t.succeed("Feature flags refreshed"),l(`Run '${m.commands[0]} --help' to see updated commands`)}catch(o){t.fail("Failed to refresh"),c(o instanceof Error?o.message:String(o)),process.exit(1)}})).addCommand(new ye("path").description("Show configuration file path").option("--cache","Show feature cache file path").action(t=>{t.cache?console.log(Bo()):console.log(Ze())}));import{Command as Or}from"commander";import K from"chalk";import{createParser as Rr}from"eventsource-parser";import J from"chalk";import Ar from"ora";async function Pr(t,o,e={}){let n=t.body?.getReader();if(!n)throw new Error("Response body is not readable");let r=new TextDecoder,i,a,s,d=0,u=0,p=0,k={},y=Rr({onEvent:$=>{if($.data!=="[DONE]")try{let I=JSON.parse($.data),{type:T,data:x}=I;switch(e.debug&&console.log(J.gray(`[SSE] ${T}:`),x),o.onData?.(T,x),T){case"data-id":i=x;break;case"data-slug":a=x;break;case"data-title":s=x;break;case"data-slides-number-of-slides":d=x;break;case"data-slide-progress":{let D=x;u=D.current,d=D.total,o.onProgress?.(u,d),o.onSlide?.(u,d);break}case"data-overall-progress":{let D=x;o.onPhase?.(D.phase,D.percentage),D.details?.slides&&(u=D.details.slides.complete,d=D.details.slides.total,o.onProgress?.(u,d),o.onSlide?.(u,d));break}case"data-structure-tokens":{p=x,o.onTokens?.(p);break}case"data-slide-meta-tokens":{let[D,S]=x.split(":");if(D&&S){k[D]=parseInt(S,10);let f=Object.values(k).reduce((A,B)=>A+B,0);p=Math.max(p,f),o.onTokens?.(p+f)}break}case"data-generation-meta-estimated-time":{o.onEstimatedTime?.(x);break}case"data-finish":o.onComplete?.({id:i,slug:a,title:s,slidesCount:d});break;case"error":x&&o.onError?.(x);break}}catch{e.debug&&console.log(J.gray("[SSE Raw]"),$.data)}}}),w=!1;for(;!w;){let $=await n.read();if(w=$.done,$.value){let I=r.decode($.value,{stream:!0});y.feed(I)}}return{id:i,slug:a,title:s,slidesCount:d}}async function Xo(t,o,e){let n=po()&&!e.noStream&&!e.quiet&&!e.json;e.json||(console.log(),console.log(J.bold(`Creating presentation: "${o}"`)),console.log(J.gray(`Quality: ${e.mode} | Tone: ${e.tone??"professional"} | Slides: ${e.slideCount} | Language: ${e.language}`)),console.log());let r,i="",a="Preparing",s=0,d={done:0,total:e.slideCount},u=0,p=0,k=Date.now(),y,w=f=>{let A=Math.floor(f/60),B=f%60;return`${A}:${B.toString().padStart(2,"0")}`},$=()=>Math.floor((Date.now()-k)/1e3),I=()=>{if(!p)return" \u2014:\u2014\u2014";let f=Math.max(0,p-$());return f===0&&s<100?"soon!":w(f)},T=f=>f>=1e3?`${(f/1e3).toFixed(1)}k`.padStart(5," "):`${f}tk`.padStart(5," "),x=f=>({"structure-generation":"Planning ","slide-generation":"Writing ","image-generation":"Images ",assembling:"Finishing",completed:"Done "})[f]||"Working ",D=()=>{let f=yo(s,100,20,!1),A=String(s).padStart(3," "),B=x(a),H=`${String(d.done).padStart(2," ")}/${String(d.total).padEnd(2," ")}`,ne=T(u),be=w($()).padStart(5," "),qe=I().padStart(5," ");return`${f} ${J.bold(A)}${J.gray("%")} ${J.cyan(B)} ${J.gray(H)} ${J.yellow(ne)} ${J.gray(be)} ${J.green(qe+" left")}`};return n&&(r=Ar({text:D(),spinner:"dots"}).start(),y=setInterval(()=>{r&&r.isSpinning&&(r.text=D())},100)),{...await Pr(t,{onProgress:(f,A)=>{if(d={done:f,total:A},!n&&!e.quiet&&!e.json){let B=`${a.trim()}: slide ${f}/${A}`;B!==i&&(console.log(B),i=B)}},onPhase:(f,A)=>{a=x(f),s=A},onTokens:f=>{u=f},onEstimatedTime:f=>{p=f},onError:f=>{y&&clearInterval(y),r?r.fail(J.red(`Error: ${f}`)):e.json||console.error(J.red(`Error: ${f}`))},onComplete:f=>{if(y&&clearInterval(y),r)s=100,a="Done",r.succeed(D());else if(!e.json){let A=u>0?` | ${u.toLocaleString()} tokens`:"";console.log(`Done! [${w($())}${A}]`)}}},{debug:e.debug}),elapsedSeconds:$(),totalTokens:u}}async function Qo(t,o={}){let e=t.body?.getReader();if(!e)throw new Error("Response body is not readable");let n=new TextDecoder,r="",i=!1;for(;!i;){let a=await e.read();if(i=a.done,a.value){let s=n.decode(a.value,{stream:!0});r+=s,o.quiet||process.stdout.write(s)}}return o.quiet||console.log(),r}import{readFileSync as Er,existsSync as Fr}from"fs";import{resolve as Zo}from"path";var en=new Or("create").description("Create a new presentation").argument("<topic>","The topic or title for the presentation").option("-n, --slides <count>","Number of slides (1-20)").option("-m, --mode <mode>","Generation quality (best, balanced, fast, ultrafast, instant)").option("-t, --tone <tone>","Tone (creative, professional, educational, formal, casual)").option("--amount <amount>","Content density (minimal, concise, detailed, extensive)").option("--audience <text>","Target audience description").option("-l, --language <lang>","Output language").option("--branding-id <id>","Branding ID (from `ccr branding list` or `ccr branding extract`)").option("-c, --context <text>","Inline text context (research notes, key facts)").option("--context-file <path>","Path to a context file (text, markdown, JSON)").option("--stdin","Read context from stdin").option("-f, --file <paths...>","Files to upload (PDF, PPTX, DOCX, images)").option("-g, --goal <type>","Goal (inform, persuade, train, learn, entertain, report)").option("--styling <mode>","Styling mode (freeform, brand-only, brand-plus-style, style-only, no-styling)").option("--reference-url <url>","Image URL to use as visual style reference").option("--thinking-depth <depth>","AI thinking depth (quick, moderate, deep, profound)").option("--theme <preset>","[instant mode only] Color theme preset (blue, violet, rose, orange, green)").option("--primary-color <hex>","[instant mode only] Primary color (e.g., #0066CC)").option("--secondary-color <hex>","[instant mode only] Secondary color").option("--accent-color <hex>","[instant mode only] Accent color").option("--background-color <hex>","[instant mode only] Background color").option("--foreground-color <hex>","[instant mode only] Foreground/text color").option("--decorations <style>","[instant mode only] Background decoration (none, waves-bottom-left, waves-top-right, blob-corners, minimal)").option("--team-id <id>","Team ID (switch teams)").option("-o, --output <format>","Output format (human, json, quiet)").option("--no-stream","Wait for completion without streaming progress").option("--debug","Enable debug logging").option("--open","Open in browser after creation").action(async(t,o)=>{await F();let e;o.slides!==void 0&&(e=parseInt(o.slides,10),(isNaN(e)||e<1||e>20)&&(c("Slide count must be between 1 and 20"),process.exit(6)));let n=["best","balanced","fast","ultrafast","instant"];o.mode&&!n.includes(o.mode)&&(c(`Invalid mode: ${o.mode}. Valid: ${n.join(", ")}`),process.exit(6));let r=["creative","professional","educational","formal","casual"];o.tone&&!r.includes(o.tone)&&(c(`Invalid tone: ${o.tone}. Valid: ${r.join(", ")}`),process.exit(6));let i=["minimal","concise","detailed","extensive"];o.amount&&!i.includes(o.amount)&&(c(`Invalid amount: ${o.amount}. Valid: ${i.join(", ")}`),process.exit(6));let a=["freeform","brand-only","brand-plus-style","style-only","no-styling"];o.styling&&!a.includes(o.styling)&&(c(`Invalid styling: ${o.styling}. Valid: ${a.join(", ")}`),process.exit(6));let s=["quick","moderate","deep","profound"];o.thinkingDepth&&!s.includes(o.thinkingDepth)&&(c(`Invalid thinking-depth: ${o.thinkingDepth}. Valid: ${s.join(", ")}`),process.exit(6));let d=["inform","persuade","train","learn","entertain","report"];o.goal&&!d.includes(o.goal)&&(c(`Invalid goal: ${o.goal}. Valid: ${d.join(", ")}`),process.exit(6));let u=["blue","violet","rose","orange","green"];o.theme&&!u.includes(o.theme)&&(c(`Invalid theme: ${o.theme}. Valid: ${u.join(", ")}`),process.exit(6));let p=["none","waves-bottom-left","waves-top-right","blob-corners","minimal"];o.decorations&&!p.includes(o.decorations)&&(c(`Invalid decorations: ${o.decorations}. Valid: ${p.join(", ")}`),process.exit(6)),(o.theme||o.primaryColor||o.secondaryColor||o.accentColor||o.backgroundColor||o.foregroundColor||o.decorations)&&o.mode&&o.mode!=="instant"&&(c("--theme, --primary-color, --decorations and other color options are only supported with --mode instant"),process.exit(6));let y=/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;for(let[S,f]of[["primary-color",o.primaryColor],["secondary-color",o.secondaryColor],["accent-color",o.accentColor],["background-color",o.backgroundColor],["foreground-color",o.foregroundColor]])f&&!y.test(f)&&(c(`Invalid ${S}: ${f}. Must be hex (e.g., #0066CC)`),process.exit(6));try{o.output!=="json"&&o.output!=="quiet"&&process.stdout.write(K.gray("Checking generation limits... "));let S=await Oo(o.mode??"fast",e??10,o.teamId);o.output!=="json"&&o.output!=="quiet"&&(console.log(K.green("\u2713")),console.log(K.gray(` Plan: ${S.planName} | ${o.mode??"fast"}: ${S.remaining[o.mode??"fast"]}/${S.limits[o.mode??"fast"]} remaining`)))}catch(S){o.output==="json"?console.log(JSON.stringify({success:!1,error:S instanceof Error?S.message:String(S)})):(console.log(K.red("\u2717")),c(S instanceof Error?S.message:String(S))),process.exit(S.exitCode??1)}let w=[];if(o.file&&o.file.length>0){for(let S of o.file)Fr(Zo(S))||(c(`File not found: ${S}`),process.exit(6));o.output!=="json"&&o.output!=="quiet"&&console.log(K.gray(`
|
|
44
|
-
Uploading ${o.file.length} file(s)...`));try{w=await xo(o.file.map(S=>Zo(S)),(S,f,A)=>{o.output!=="json"&&o.output!=="quiet"&&A&&process.stdout.write(`\r ${K.cyan("\u2B06")} Uploading: ${A} (${S+1}/${f})`)}),o.output!=="json"&&o.output!=="quiet"&&console.log(`\r ${K.green("\u2713")} Uploaded ${w.length} file(s) `)}catch(S){c(`Failed to upload files: ${S instanceof Error?S.message:String(S)}`),process.exit(1)}}let $;(o.stdin||!process.stdin.isTTY)&&($=await Ur(),o.stdin&&!$&&(c("No content received from stdin"),process.exit(6)));let I=o.context;if(o.contextFile)try{I=Er(o.contextFile,"utf-8"),I.trim()||(c(`Context file is empty: ${o.contextFile}`),process.exit(6))}catch{c(`Failed to read context file: ${o.contextFile}`),process.exit(6)}!$&&!I&&w.length===0&&(c("Context is required to create a presentation."),console.log(),console.log(K.gray(" -f, --file <paths...> Upload files (PDF, PPTX, images)")),console.log(K.gray(" -c, --context <text> Inline text context")),console.log(K.gray(" --context-file <path> Read from a file")),console.log(K.gray(` cat file | ${m.commands[0]} Pipe content`)),process.exit(6));let T=[...w];$&&T.push({id:`stdin-${Date.now()}`,content:$,title:"Piped Content",mime:"text/plain",selected:!0}),I&&T.push({id:`context-${Date.now()}`,content:I,title:"Context",mime:"text/plain",selected:!0});let x,D=o.primaryColor||o.secondaryColor||o.accentColor||o.backgroundColor||o.foregroundColor;(o.theme||D||o.decorations)&&(x={},o.theme&&(x.preset=o.theme),D&&(x.custom={},o.primaryColor&&(x.custom.primary=o.primaryColor),o.secondaryColor&&(x.custom.secondary=o.secondaryColor),o.accentColor&&(x.custom.accent=o.accentColor),o.backgroundColor&&(x.custom.background=o.backgroundColor),o.foregroundColor&&(x.custom.foreground=o.foregroundColor)),o.decorations&&(x.decorations=o.decorations));try{let S=await So({topic:t,...e!==void 0&&{slideCount:e},...o.mode&&{mode:o.mode},...o.tone&&{tone:o.tone},...o.amount&&{amount:o.amount},...o.audience&&{audience:o.audience},...o.language&&{language:o.language},...o.brandingId&&{brandingId:o.brandingId},...o.styling&&{stylingMode:o.styling},...o.referenceUrl&&{referenceUrl:o.referenceUrl},...o.thinkingDepth&&{thinkingDepth:o.thinkingDepth},...o.goal&&{goal:o.goal},...o.teamId&&{teamId:o.teamId},...x&&{theme:x},...T.length>0&&{contentSources:{uploadedFiles:T,contextFiles:[]}}}),f=await Xo(S,t,{slideCount:e,mode:o.mode,tone:o.tone,language:o.language,noStream:o.noStream,debug:o.debug,quiet:o.output==="quiet",json:o.output==="json"});if(o.output==="json")console.log(JSON.stringify({success:!0,presentation:{slug:f.slug,title:f.title??t,slidesCount:f.slidesCount,elapsedSeconds:f.elapsedSeconds,totalTokens:f.totalTokens},viewUrl:$e(f.slug,o.language??"en")},null,2));else{console.log(),g("Presentation created successfully"),console.log(),h("Title",f.title??t),h("Slides",String(f.slidesCount??e??"\u2014"));let A=[];if(f.elapsedSeconds){let H=Math.floor(f.elapsedSeconds/60),ne=f.elapsedSeconds%60;A.push(H>0?`${H}m ${ne}s`:`${ne}s`)}f.totalTokens&&A.push(`${f.totalTokens.toLocaleString()} tokens`),A.length>0&&h("Generated in",A.join(" \xB7 ")),console.log();let B=$e(f.slug,o.language??"en");B!=="N/A"&&(console.log(K.bold(" Open: ")+K.cyan.underline(B)),o.open&&await(await import("open")).default(B)),console.log()}}catch(S){o.output==="json"?console.log(JSON.stringify({success:!1,error:S instanceof Error?S.message:String(S)})):c(S instanceof Error?S.message:String(S)),process.exit(S.exitCode??1)}});async function Ur(){return new Promise(t=>{let o="";if(process.stdin.setEncoding("utf8"),process.stdin.isTTY){t("");return}process.stdin.on("data",e=>{o+=e}),process.stdin.on("end",()=>{t(o.trim())}),setTimeout(()=>{t(o.trim())},100)})}import{Command as Dr}from"commander";var tn=new Dr("list").description("List presentations").option("-n, --limit <count>","Number of results to return","20").option("-f, --format <format>","Output format (table, json, ids)","table").option("--sort <field>","Sort by field (created, updated, title)").option("--team-id <id>","Team ID (uses default if not specified)").option("-d, --detail","Show detailed output including URLs").option("-l, --language <lang>","Language for URLs","en").action(async t=>{await F();let o=parseInt(t.limit,10);(isNaN(o)||o<1)&&(c("Invalid limit value"),process.exit(6));let e=t.teamId??Xe();e||(c("`Team ID required. Set a default with '${brand.commands[0]} config set team-id <id>' or use --team-id`"),process.exit(6));try{let n=await $o(e,o);if(n.length===0){t.format==="json"?console.log(JSON.stringify([])):t.format!=="ids"&&l("No presentations found");return}switch(t.sort&&n.sort((r,i)=>{switch(t.sort){case"created":return new Date(i.createdAt).getTime()-new Date(r.createdAt).getTime();case"title":return(r.title||"").localeCompare(i.title||"");default:return 0}}),t.format){case"json":console.log(JSON.stringify(n,null,2));break;case"ids":console.log(go(n));break;default:console.log(fo(n,{showLinks:t.detail,language:t.language}));break}}catch(n){c(n instanceof Error?n.message:String(n)),process.exit(n.exitCode??1)}});import{Command as Lr}from"commander";import ct from"chalk";var on=new Lr("get").description("Get presentation details").argument("<slug>","Presentation slug (e.g., my-presentation-v1-abc123)").option("-f, --format <format>","Output format (summary, full, json)","summary").option("--include <fields>","Fields to include (comma-separated: slides,styling)").option("-l, --language <lang>","Language for view URL","en").action(async(t,o)=>{await F();try{let e=await Ce(t);if(o.format==="json"){console.log(JSON.stringify(e,null,2));return}let n=$e(e.slug,o.language);te("Presentation Details"),console.log(),h("Slug",e.slug),h("Title",e.title||"Untitled"),h("Description",e.description||ct.gray("None")),h("Slides",String(e.numberOfSlides||"-")),h("Mode",e.mode||"-"),h("Created",It(e.createdAt)),e.updatedAt&&h("Updated",It(e.updatedAt)),console.log(),n!=="N/A"&&console.log(ct.bold(" Open: ")+ct.cyan.underline(n)),console.log(),o.format==="full"&&o.include?.includes("slides")&&(console.log(ct.gray("(Full slide content available in JSON format)")),console.log())}catch(e){c(e instanceof Error?e.message:String(e)),process.exit(e.exitCode??1)}});import{Command as Nr}from"commander";import{confirm as jr}from"@inquirer/prompts";var nn=new Nr("delete").description("Delete a presentation").argument("<slug>","Presentation slug (e.g., my-presentation-v1-abc123)").option("-f, --force","Skip confirmation prompt").option("-q, --quiet","Suppress output").action(async(t,o)=>{if(await F(),!o.force)try{if(!await jr({message:`Are you sure you want to delete presentation "${t}"?`,default:!1})){o.quiet||R("Deletion cancelled");return}}catch{o.quiet||R("Deletion cancelled");return}try{await Co(t),o.quiet||g(`Presentation "${t}" deleted`)}catch(e){c(e instanceof Error?e.message:String(e)),process.exit(e.exitCode??1)}});import{Command as Mr}from"commander";import{writeFile as qr}from"fs/promises";import{resolve as rn}from"path";import Vr from"ora";var an=new Mr("export").description("Export a presentation to ZIP").argument("<slug>","Presentation slug (e.g., my-presentation-v1-abc123)").option("-o, --output <path>","Output file path").option("--include-images","Include images (default: true)",!0).option("--include-branding","Include branding (default: true)",!0).option("--no-external","Skip external image downloads").action(async(t,o)=>{await F();let e=Vr("Fetching presentation...").start();try{let n=await Ce(t),r=n.title||t,i=n.id,a=t.replace(/[^a-z0-9-]/gi,"_").toLowerCase().substring(0,50),s=new Date().toISOString().split("T")[0],d=`${a}_${s}.zip`,u=o.output?rn(o.output):rn(process.cwd(),d);e.text=`Exporting "${r}"...`;let p=await ko(i,{includeImages:o.includeImages,includeBranding:o.includeBranding,includeExternal:!o.noExternal});e.text="Saving file...",await qr(u,Buffer.from(p)),e.succeed("Export complete!"),console.log(),l(`File saved: ${u}`),l(`Size: ${Br(p.byteLength)}`),console.log()}catch(n){e.fail("Export failed"),c(n instanceof Error?n.message:String(n)),process.exit(n.exitCode??1)}});function Br(t){return t<1024?`${t} B`:t<1024*1024?`${(t/1024).toFixed(1)} KB`:`${(t/(1024*1024)).toFixed(1)} MB`}import{Command as Gr}from"commander";import{readFile as _r}from"fs/promises";import{resolve as Wr,basename as Jr}from"path";import Kr from"ora";var sn=new Gr("import").description("Import a presentation from ZIP (admin only)").argument("<file>","Path to ZIP file");sn._hidden=!0;var cn=sn.option("--dry-run","Validate without importing").option("--overwrite","Overwrite existing presentations").option("--remap-ids","Generate new IDs (default: true)",!0).action(async(t,o)=>{await F();try{(await fe()).user.role!=="admin"&&(c("This command is only available to administrators"),process.exit(2))}catch{c("Failed to verify permissions"),process.exit(2)}let e=Wr(t),n=Jr(e);n.endsWith(".zip")||(c("File must be a ZIP archive"),process.exit(6));let r=Kr("Reading file...").start();try{let i=await _r(e);o.dryRun?r.text="Validating...":r.text="Importing presentation...";let a=await To(i,n,{dryRun:o.dryRun});if(a.success){if(o.dryRun)r.succeed("Validation passed!"),console.log(),l("The file is valid and can be imported."),a.statistics&&(console.log(),h("Slides",String(a.statistics.slidesImported)),h("Images",String(a.statistics.imagesUploaded)));else if(r.succeed("Import complete!"),console.log(),h("Presentation ID",a.presentationId??"N/A"),h("Document ID",a.documentId??"N/A"),a.statistics&&(h("Slides imported",String(a.statistics.slidesImported)),h("Images uploaded",String(a.statistics.imagesUploaded)),h("Time",`${a.statistics.totalTimeMs}ms`)),a.presentationId){let s=M();h("View URL",`${s}/p/${a.presentationId}`)}if(a.warnings&&a.warnings.length>0){console.log();for(let s of a.warnings)R(s)}console.log()}else{if(r.fail("Import failed"),console.log(),a.errors)for(let s of a.errors)c(`${s.type}: ${s.message}`);process.exit(1)}}catch(i){r.fail("Import failed"),i.code==="ENOENT"&&(c(`File not found: ${e}`),process.exit(3)),c(i instanceof Error?i.message:String(i)),process.exit(i.exitCode??1)}});import{Command as Je}from"commander";import X from"chalk";import Hr from"ora";import{writeFile as ln}from"fs/promises";var dn=new Je("branding").description("Manage brand profiles").addCommand(new Je("list").description("List brand profiles").option("-f, --format <format>","Output format (table, json)","table").action(async t=>{await F();try{let o=await Io();if(o.brandings.length===0){t.format==="json"?console.log(JSON.stringify([])):(l("No brand profiles found"),console.log(),console.log(X.gray("`Create one with: ${brand.commands[0]} branding extract <url>`")));return}t.format==="json"?console.log(JSON.stringify(o.brandings,null,2)):console.log(ho(o.brandings))}catch(o){c(o instanceof Error?o.message:String(o)),process.exit(o.exitCode??1)}})).addCommand(new Je("get").description("Get brand profile details").argument("<id>","Brand profile ID").option("-f, --format <format>","Output format (summary, json)","summary").option("-o, --output <path>","Save JSON to file").action(async(t,o)=>{await F();try{let e=await At(t);if(o.output){await ln(o.output,JSON.stringify(e,null,2)),g(`Brand profile saved to ${o.output}`);return}if(o.format==="json")console.log(JSON.stringify(e,null,2));else{if(te("Brand Profile"),console.log(),h("ID",e.id),h("Name",e.name),h("Source URL",e.sourceUrl??X.gray("None")),h("Default",e.isDefault?X.green("Yes"):"No"),e.createdAt&&h("Created",new Date(e.createdAt).toLocaleString()),console.log(),e.primaryColor||e.colors.length>0){if(console.log(X.bold(" Colors")),e.primaryColor){let r=X.bgHex(e.primaryColor)(" ");console.log(` Primary: ${r} ${e.primaryColor}`)}if(e.colors.length>0){console.log(" Palette:");for(let r of e.colors.slice(0,10)){let i=X.bgHex(r.hex)(" "),a=r.role?X.gray(` (${r.role})`):"";console.log(` ${i} ${r.hex}${a}`)}}console.log()}e.logoUrl&&(console.log(X.bold(" Logo")),console.log(` ${e.logoUrl}`),console.log());let n=e.typography;n&&Object.keys(n).length>0&&(console.log(X.bold(" Typography")),(n.primary||n.headings)&&console.log(` Headings: ${n.primary||n.headings||"-"}`),(n.secondary||n.body)&&console.log(` Body: ${n.secondary||n.body||"-"}`),console.log()),(e.confidence||e.extractionMethod)&&(console.log(X.bold(" Extraction")),e.confidence&&console.log(` Confidence: ${Math.round(e.confidence*100)}%`),e.extractionMethod&&console.log(` Method: ${e.extractionMethod}`),console.log()),console.log(X.gray(" Use --format json for full details")),console.log()}}catch(e){c(e instanceof Error?e.message:String(e)),process.exit(e.exitCode??1)}})).addCommand(new Je("extract").description("Extract brand profile from a website").argument("<url>","Website URL to extract branding from").option("--team-id <id>","Team ID").option("--no-save","Extract without saving").option("-o, --output <path>","Save JSON to file").action(async(t,o)=>{await F();try{new URL(t.startsWith("http")?t:`https://${t}`)}catch{c("Invalid URL format"),process.exit(6)}let e=t.startsWith("http")?t:`https://${t}`,n=o.teamId??Xe(),r=Hr({text:`Extracting branding from ${e}...`,stream:process.stdout}).start();try{let i=await Ro(e,n),a=await At(i.id);r.succeed("Branding extracted!");let s=JSON.stringify(a,null,2);o.output?(await ln(o.output,s),g(`Brand profile saved to ${o.output}`)):console.log(s),console.log()}catch(i){r.fail("Extraction failed"),c(i instanceof Error?i.message:String(i)),process.exit(i.exitCode??1)}})).addCommand(new Je("set-default").description("Set a brand profile as default").argument("<id>","Brand profile ID").action(async t=>{await F(),l(`To set brand ${t} as default, use the web dashboard.`),console.log(),console.log(X.gray("API support for setting default brand is coming soon."))}));import{Command as ke}from"commander";import mn from"chalk";import Yr from"ora";function zr(){return new ke("blog").description("Generate a blog post from a presentation").argument("<slug>","Presentation slug").option("--words <count>","Target word count (50-2000)","300").option("--tone <tone>","Writing tone (professional, casual, educational)","professional").option("--audience <text>","Target audience description").option("-f, --format <format>","Output format (human, json, markdown)","human").option("--instructions <text>","Custom instructions for the blog").option("--no-images","Exclude image references").option("--toc","Include table of contents").action(async(t,o)=>{await F();let e=parseInt(o.words,10);(isNaN(e)||e<50||e>2e3)&&(c("Word count must be between 50 and 2000"),process.exit(6));let n=["professional","casual","educational"];n.includes(o.tone)||(c(`Invalid tone. Valid options: ${n.join(", ")}`),process.exit(6));let r=Yr("Fetching presentation...").start();try{let i=await Ce(t);r.text="Generating blog post...";let a=await Po(i.id,i.documentCreatedAt||i.createdAt,{targetWordCount:e,tone:o.tone,targetAudience:o.audience??"General Audience",customInstructions:o.instructions??"",includeImages:!o.noImages,includeTableOfContents:o.toc??!1}),s=await Qo(a,{quiet:!0});r.succeed("Blog generated"),o.format==="json"?console.log(JSON.stringify({presentationId:t,title:i.title,wordCount:e,tone:o.tone,content:s},null,2)):o.format==="markdown"?console.log(s):(console.log(),console.log(mn.bold(`Blog: ${i.title}`)),console.log(mn.gray("\u2500".repeat(40))),console.log(),console.log(s),console.log())}catch(i){r.fail("Generation failed"),c(i instanceof Error?i.message:String(i)),process.exit(i.exitCode??1)}})}function Xr(){return new ke("tweets").description("Generate tweets from a presentation").argument("<slug>","Presentation slug").option("--count <n>","Number of tweets to generate","5").option("-f, --format <format>","Output format (human, json)","human").action(async(t,o)=>{await F(),l("Tweet generation coming soon.")})}function Qr(){return new ke("linkedin").description("Generate a LinkedIn carousel from a presentation").argument("<slug>","Presentation slug").option("-f, --format <format>","Output format (human, json)","human").action(async t=>{await F(),l("LinkedIn carousel generation coming soon.")})}function Zr(){return new ke("questions").description("Generate questions for a presentation").argument("<slug>","Presentation slug").option("--count <n>","Number of questions","10").option("-f, --format <format>","Output format (human, json)","human").action(async(t,o)=>{await F(),l("Question generation coming soon.")})}function ei(){return new ke("cheatsheet").description("Generate a presenter cheat sheet").argument("<slug>","Presentation slug").option("--mode <mode>","Cheat sheet mode (qa-prep, talking-points)","qa-prep").option("-f, --format <format>","Output format (human, json, markdown)","human").action(async(t,o)=>{await F(),l("Cheat sheet generation coming soon.")})}function un(){let t=new ke("derive").description("Generate derivative content from a presentation"),o=Vo();return o&&(o.deckToBlog&&t.addCommand(zr()),o.deckToTweets&&t.addCommand(Xr()),o.linkedInCarousel&&t.addCommand(Qr()),o.redTeamQuestions&&t.addCommand(Zr()),o.presenterCheatSheet&&t.addCommand(ei())),t}import{Command as ti}from"commander";import Dt from"chalk";var pn=new ti("ideas").description("Generate presentation topic ideas").option("--context <text>","Custom context for idea generation").option("--count <n>","Number of ideas to generate","5").option("-f, --format <format>","Output format (human, json)","human").action(async t=>{await F(),l("Idea generation is available in the web dashboard."),console.log(),console.log(Dt.gray("The CLI will support idea generation in a future release.")),console.log(),console.log("For now, try these approaches:"),console.log(Dt.gray("` 1. Visit the ${brand.displayName} dashboard and use the idea generator`")),console.log(Dt.gray(" 2. Create a presentation with a broad topic and refine it")),console.log()});import{Command as oi}from"commander";import Lt from"chalk";var fn=new oi("whoami").description("Show current user and team information").option("-f, --format <format>","Output format (human, json)","human").action(async t=>{await F();try{let o=await fe();if(Ft(),t.format==="json"){console.log(JSON.stringify(o,null,2));return}if(te("User"),console.log(),h("Email",o.user.email),o.user.username&&h("Username",o.user.username),(o.user.firstName||o.user.lastName)&&h("Name",[o.user.firstName,o.user.lastName].filter(Boolean).join(" ")),h("Auth Type",o.authType==="apiKey"?"API Key":"Session"),o.currentTeam&&(console.log(),te("Current Team"),console.log(),h("ID",o.currentTeam.id),h("Name",o.currentTeam.name),h("Plan",o.currentTeam.planName),h("Role",o.currentTeam.isOwner?"Owner":"Member")),o.teams.length>1){console.log(),te("All Teams"),console.log();for(let e of o.teams){let n=e.isCurrent?Lt.green(" (current)"):"";console.log(` ${Lt.bold(e.name)}${n}`),console.log(Lt.gray(` ID: ${e.id} | Plan: ${e.planName} | Role: ${e.role}`))}}console.log()}catch(o){c(o instanceof Error?o.message:String(o)),process.exit(o.exitCode??1)}});import{Command as ii}from"commander";import L from"chalk";function lt(t){let{name:o,cmd:e}=t,n=o.toUpperCase().replace(/[^A-Z0-9]/g,"_");return`---
|
|
45
|
-
name: ${
|
|
43
|
+
`),a(),e({code:h,state:f})});l.listen(t),process.once("SIGINT",s),process.once("SIGTERM",s),r=setTimeout(()=>{a(),o(new Error("Login timed out - no callback received within 5 minutes"))},300*1e3)})}async function Fr(t,n){let e=await fetch(`${t}/api/cli/whoami`,{headers:{Authorization:`Bearer ${n}`}});if(!e.ok)throw new Error(`Failed to fetch user info: ${e.status}`);return e.json()}async function Nr(t){let n=j(),e=Kn("Discovering OAuth server...").start();try{let o=await Ar(n);e.succeed("Connecting to "+n);let r=await Rr(Tr,Cr),i=`http://localhost:${r}/callback`;console.log("Redirect URI: "+i),console.log("Authorization endpoint: "+o.authorization_endpoint),console.log("Token endpoint: "+o.token_endpoint),console.log("Registration endpoint: "+o.registration_endpoint);let a=rt(),s=it();if(!a){let y=await Or(o.registration_endpoint,i);a=y.client_id,s=y.client_secret,yn(a,s)}let l=kr(),m=$r(l),p=Ir(),k=new URLSearchParams({client_id:a,redirect_uri:i,response_type:"code",scope:"presentations:read presentations:write",state:p,code_challenge:m,code_challenge_method:"S256"}),h=`${o.authorization_endpoint}?${k}`;console.log("Authorization URL: "+h),t.browser?(d("Opening browser..."),await wr(h)):(console.log(Hn.bold("Open this URL in your browser:")),console.log(Hn.cyan(h)));let f=Pr(r,p),{code:x}=await f;e=Kn("Completing login...").start();let I=await Er(o.token_endpoint,x,l,i,a);nt(I.access_token,I.refresh_token,I.expires_in);let T=await Fr(n,I.access_token);e.succeed("Logged in!"),console.log(),v("Logged in as",T.user.email),T.currentTeam&&(Se(T.currentTeam.id),v("Team",`${T.currentTeam.name} (${T.currentTeam.planName})`));try{await ge()}catch{}console.log(),b("You're all set!"),console.log()}catch(o){throw e.fail("Login failed"),c(o instanceof Error?o.message:String(o)),o}}var Qn=new vr("login").description(`Authenticate with ${u.displayName} (opens browser)`).option("--no-browser","Print URL instead of opening browser").action(async t=>{console.log(),pe()&&(R("You are already logged in."),d(`Run '${u.commands[0]} logout' to log out first, or continue to re-authenticate.`),console.log());try{await Nr(t),process.exit(0)}catch{process.exit(1)}});import{Command as Dr}from"commander";import{confirm as Ur}from"@inquirer/prompts";var Zn=new Dr("logout").description("Log out and clear authentication").option("--all","Clear all config including API key").action(async t=>{console.log();let n=pe(),e=Te();if(!n&&!e){R("You are not logged in."),console.log();return}if(t.all)try{await Ur({message:"Clear all configuration including API key?",default:!1})?(et(),he(),b("All configuration cleared.")):d("Cancelled.")}catch{d("Cancelled.")}else ot(),he(),b("Logged out successfully."),e&&d("Note: Your API key is still configured. Use --all to clear everything.");console.log()});import{Command as ye}from"commander";import Ye from"chalk";import{input as Lr,password as Mr,confirm as eo,select as jr}from"@inquirer/prompts";import Vt from"ora";var to=new ye("config").description("Manage CLI configuration").addCommand(new ye("init").description("Initialize configuration interactively").action(async()=>{console.log(),console.log(Ye.bold(`${u.displayName} CLI Configuration`)),console.log(Ye.gray("\u2500".repeat(35))),console.log();try{let t=await Mr({message:"Enter your API key:",mask:"*",validate:o=>!o||o.trim().length===0?"API key is required":Nt(o.trim())?!0:"Invalid API key format. Keys should start with 'cc_slides_' or 'mcp_'"});if(Ot(t.trim()),await eo({message:`Use a custom API URL? (default: ${u.apiUrl.replace("https://","")})`,default:!1})){let o=await Lr({message:"Enter API URL:",default:j(),validate:r=>{try{return new URL(r),!0}catch{return"Invalid URL format"}}});Et(o)}console.log();let e=Vt("Verifying API key...").start();try{let o=await fe();if(e.succeed("API key verified!"),console.log(),d(`Logged in as: ${o.user.email}`),o.teams.length===0)R("No teams found for this user.");else if(o.teams.length===1){let r=o.teams[0];Se(r.id),d(`Team: ${r.name} (${r.planName})`)}else{console.log(),d(`You have access to ${o.teams.length} teams.`);let r=await jr({message:"Select your default team:",choices:o.teams.map(a=>({name:`${a.name} (${a.planName}) - ${a.role}${a.isCurrent?" [current]":""}`,value:a.id})),default:o.currentTeam?.id});Se(r);let i=o.teams.find(a=>a.id===r);b(`Selected team: ${i?.name}`)}try{await ge()}catch{}}catch(o){e.fail("Failed to verify API key"),R(`Could not verify API key: ${o instanceof Error?o.message:String(o)}`),R(`You may need to set the team ID manually: ${u.commands[0]} config set team-id <id>`)}console.log(),b("Configuration saved!"),d(`Config file: ${tt()}`),console.log()}catch(t){if(t.name==="ExitPromptError"){console.log(),d("Configuration cancelled.");return}throw t}})).addCommand(new ye("show").description("Show current configuration").option("--verify","Verify API key and show team details").action(async t=>{let n=pn();if(te("Current Configuration"),console.log(),v("API Key",Cn()??Ye.red("Not set")),v("API URL",n.apiUrl),v("Default Team ID",n.defaultTeamId??Ye.gray("Not set")),console.log(),v("Config file",tt()),t.verify&&n.apiKey){console.log();let e=Vt("Verifying...").start();try{let o=await fe();e.succeed("Verified"),console.log(),v("User",o.user.email),o.currentTeam&&v("Current Team",`${o.currentTeam.name} (${o.currentTeam.planName})`),o.teams.length>1&&v("Total Teams",String(o.teams.length))}catch(o){e.fail("Verification failed"),R(o instanceof Error?o.message:String(o))}}console.log()})).addCommand(new ye("set").description("Set a configuration value").argument("<key>","Configuration key (api-key, api-url, team-id)").argument("<value>","Value to set").action(async(t,n)=>{switch(t){case"api-key":Nt(n)||(c("Invalid API key format. Keys should start with 'cc_slides_' or 'mcp_'"),process.exit(6)),Ot(n),he(),b("API key updated"),ge().catch(()=>{});break;case"api-url":try{new URL(n),Et(n),b(`API URL set to: ${n}`)}catch{c("Invalid URL format"),process.exit(6)}break;case"team-id":Se(n),b(`Default team ID set to: ${n}`);break;default:c(`Unknown config key: ${t}`),console.log(Ye.gray("Valid keys: api-key, api-url, team-id")),process.exit(6)}})).addCommand(new ye("clear").description("Clear all configuration").action(async()=>{try{await eo({message:"Are you sure you want to clear all configuration?",default:!1})?(et(),he(),b("Configuration cleared")):d("Cancelled")}catch{d("Cancelled")}})).addCommand(new ye("refresh").description("Refresh cached feature flags").action(async()=>{let t=Vt("Refreshing feature flags...").start();try{await ge(),t.succeed("Feature flags refreshed"),d(`Run '${u.commands[0]} --help' to see updated commands`)}catch(n){t.fail("Failed to refresh"),c(n instanceof Error?n.message:String(n)),process.exit(1)}})).addCommand(new ye("path").description("Show configuration file path").option("--cache","Show feature cache file path").action(t=>{t.cache?console.log(Yn()):console.log(tt())}));import{Command as Br}from"commander";import Y from"chalk";import{createParser as Vr}from"eventsource-parser";import J from"chalk";import _r from"ora";async function qr(t,n,e={}){let o=t.body?.getReader();if(!o)throw new Error("Response body is not readable");let r=new TextDecoder,i,a,s,l=0,m=0,p=0,k={},h=Vr({onEvent:x=>{if(x.data!=="[DONE]")try{let I=JSON.parse(x.data),{type:T,data:y}=I;switch(e.debug&&console.log(J.gray(`[SSE] ${T}:`),y),n.onData?.(T,y),T){case"data-id":i=y;break;case"data-slug":a=y;break;case"data-title":s=y;break;case"data-slides-number-of-slides":l=y;break;case"data-slide-progress":{let A=y;m=A.current,l=A.total,n.onProgress?.(m,l),n.onSlide?.(m,l);break}case"data-overall-progress":{let A=y;n.onPhase?.(A.phase,A.percentage),A.details?.slides&&(m=A.details.slides.complete,l=A.details.slides.total,n.onProgress?.(m,l),n.onSlide?.(m,l));break}case"data-structure-tokens":{p=y,n.onTokens?.(p);break}case"data-slide-meta-tokens":{let[A,C]=y.split(":");if(A&&C){k[A]=parseInt(C,10);let g=Object.values(k).reduce((O,q)=>O+q,0);p=Math.max(p,g),n.onTokens?.(p+g)}break}case"data-generation-meta-estimated-time":{n.onEstimatedTime?.(y);break}case"data-finish":n.onComplete?.({id:i,slug:a,title:s,slidesCount:l});break;case"error":y&&n.onError?.(y);break}}catch{e.debug&&console.log(J.gray("[SSE Raw]"),x.data)}}}),f=!1;for(;!f;){let x=await o.read();if(f=x.done,x.value){let I=r.decode(x.value,{stream:!0});h.feed(I)}}return{id:i,slug:a,title:s,slidesCount:l}}async function no(t,n,e){let o=bn()&&!e.noStream&&!e.quiet&&!e.json;e.json||(console.log(),console.log(J.bold(`Creating presentation: "${n}"`)),console.log(J.gray(`Quality: ${e.mode} | Tone: ${e.tone??"professional"} | Slides: ${e.slideCount} | Language: ${e.language}`)),console.log());let r,i="",a="Preparing",s=0,l={done:0,total:e.slideCount},m=0,p=0,k=Date.now(),h,f=g=>{let O=Math.floor(g/60),q=g%60;return`${O}:${q.toString().padStart(2,"0")}`},x=()=>Math.floor((Date.now()-k)/1e3),I=()=>{if(!p)return" \u2014:\u2014\u2014";let g=Math.max(0,p-x());return g===0&&s<100?"soon!":f(g)},T=g=>g>=1e3?`${(g/1e3).toFixed(1)}k`.padStart(5," "):`${g}tk`.padStart(5," "),y=g=>({"structure-generation":"Planning ","slide-generation":"Writing ","image-generation":"Images ",assembling:"Finishing",completed:"Done "})[g]||"Working ",A=()=>{let g=Sn(s,100,20,!1),O=String(s).padStart(3," "),q=y(a),H=`${String(l.done).padStart(2," ")}/${String(l.total).padEnd(2," ")}`,oe=T(m),ve=f(x()).padStart(5," "),qe=I().padStart(5," ");return`${g} ${J.bold(O)}${J.gray("%")} ${J.cyan(q)} ${J.gray(H)} ${J.yellow(oe)} ${J.gray(ve)} ${J.green(qe+" left")}`};return o&&(r=_r({text:A(),spinner:"dots"}).start(),h=setInterval(()=>{r&&r.isSpinning&&(r.text=A())},100)),{...await qr(t,{onProgress:(g,O)=>{if(l={done:g,total:O},!o&&!e.quiet&&!e.json){let q=`${a.trim()}: slide ${g}/${O}`;q!==i&&(console.log(q),i=q)}},onPhase:(g,O)=>{a=y(g),s=O},onTokens:g=>{m=g},onEstimatedTime:g=>{p=g},onError:g=>{h&&clearInterval(h),r?r.fail(J.red(`Error: ${g}`)):e.json||console.error(J.red(`Error: ${g}`))},onComplete:g=>{if(h&&clearInterval(h),r)s=100,a="Done",r.succeed(A());else if(!e.json){let O=m>0?` | ${m.toLocaleString()} tokens`:"";console.log(`Done! [${f(x())}${O}]`)}}},{debug:e.debug}),elapsedSeconds:x(),totalTokens:m}}async function oo(t,n={}){let e=t.body?.getReader();if(!e)throw new Error("Response body is not readable");let o=new TextDecoder,r="",i=!1;for(;!i;){let a=await e.read();if(i=a.done,a.value){let s=o.decode(a.value,{stream:!0});r+=s,n.quiet||process.stdout.write(s)}}return n.quiet||console.log(),r}import{readFileSync as Gr,existsSync as Wr}from"fs";import{resolve as ro}from"path";var io=new Br("create").description("Create a new presentation").argument("<topic>","The topic or title for the presentation").option("-n, --slides <count>","Number of slides (1-20)").option("-m, --mode <mode>","Generation quality (best, balanced, fast, ultrafast, instant)").option("-t, --tone <tone>","Tone (creative, professional, educational, formal, casual)").option("--amount <amount>","Content density (minimal, concise, detailed, extensive)").option("--audience <text>","Target audience description").option("-l, --language <lang>","Output language").option("--branding-id <id>","Branding ID (from `ccr branding list` or `ccr branding extract`)").option("-c, --context <text>","Inline text context (research notes, key facts)").option("--context-file <path>","Path to a context file (text, markdown, JSON)").option("--stdin","Read context from stdin").option("-f, --file <paths...>","Files to upload (PDF, PPTX, DOCX, images)").option("-g, --goal <type>","Goal (inform, persuade, train, learn, entertain, report)").option("--styling <mode>","Styling mode (freeform, brand-only, brand-plus-style, style-only, no-styling)").option("--reference-url <url>","Image URL to use as visual style reference").option("--thinking-depth <depth>","AI thinking depth (quick, moderate, deep, profound)").option("--theme <preset>","[instant mode only] Color theme preset (blue, violet, rose, orange, green)").option("--primary-color <hex>","[instant mode only] Primary color (e.g., #0066CC)").option("--secondary-color <hex>","[instant mode only] Secondary color").option("--accent-color <hex>","[instant mode only] Accent color").option("--background-color <hex>","[instant mode only] Background color").option("--foreground-color <hex>","[instant mode only] Foreground/text color").option("--decorations <style>","[instant mode only] Background decoration (none, waves-bottom-left, waves-top-right, blob-corners, minimal)").option("--team-id <id>","Team ID (switch teams)").option("-o, --output <format>","Output format (human, json, quiet)").option("--no-stream","Wait for completion without streaming progress").option("--debug","Enable debug logging").option("--open","Open in browser after creation").action(async(t,n)=>{await N();let e;n.slides!==void 0&&(e=parseInt(n.slides,10),(isNaN(e)||e<1||e>20)&&(c("Slide count must be between 1 and 20"),process.exit(6)));let o=["best","balanced","fast","ultrafast","instant"];n.mode&&!o.includes(n.mode)&&(c(`Invalid mode: ${n.mode}. Valid: ${o.join(", ")}`),process.exit(6));let r=["creative","professional","educational","formal","casual"];n.tone&&!r.includes(n.tone)&&(c(`Invalid tone: ${n.tone}. Valid: ${r.join(", ")}`),process.exit(6));let i=["minimal","concise","detailed","extensive"];n.amount&&!i.includes(n.amount)&&(c(`Invalid amount: ${n.amount}. Valid: ${i.join(", ")}`),process.exit(6));let a=["freeform","brand-only","brand-plus-style","style-only","no-styling"];n.styling&&!a.includes(n.styling)&&(c(`Invalid styling: ${n.styling}. Valid: ${a.join(", ")}`),process.exit(6));let s=["quick","moderate","deep","profound"];n.thinkingDepth&&!s.includes(n.thinkingDepth)&&(c(`Invalid thinking-depth: ${n.thinkingDepth}. Valid: ${s.join(", ")}`),process.exit(6));let l=["inform","persuade","train","learn","entertain","report"];n.goal&&!l.includes(n.goal)&&(c(`Invalid goal: ${n.goal}. Valid: ${l.join(", ")}`),process.exit(6));let m=["blue","violet","rose","orange","green"];n.theme&&!m.includes(n.theme)&&(c(`Invalid theme: ${n.theme}. Valid: ${m.join(", ")}`),process.exit(6));let p=["none","waves-bottom-left","waves-top-right","blob-corners","minimal"];n.decorations&&!p.includes(n.decorations)&&(c(`Invalid decorations: ${n.decorations}. Valid: ${p.join(", ")}`),process.exit(6)),(n.theme||n.primaryColor||n.secondaryColor||n.accentColor||n.backgroundColor||n.foregroundColor||n.decorations)&&n.mode&&n.mode!=="instant"&&(c("--theme, --primary-color, --decorations and other color options are only supported with --mode instant"),process.exit(6));let h=/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;for(let[C,g]of[["primary-color",n.primaryColor],["secondary-color",n.secondaryColor],["accent-color",n.accentColor],["background-color",n.backgroundColor],["foreground-color",n.foregroundColor]])g&&!h.test(g)&&(c(`Invalid ${C}: ${g}. Must be hex (e.g., #0066CC)`),process.exit(6));try{n.output!=="json"&&n.output!=="quiet"&&process.stdout.write(Y.gray("Checking generation limits... "));let C=await Un(n.mode??"fast",e??10,n.teamId);n.output!=="json"&&n.output!=="quiet"&&(console.log(Y.green("\u2713")),console.log(Y.gray(` Plan: ${C.planName} | ${n.mode??"fast"}: ${C.remaining[n.mode??"fast"]}/${C.limits[n.mode??"fast"]} remaining`)))}catch(C){n.output==="json"?console.log(JSON.stringify({success:!1,error:C instanceof Error?C.message:String(C)})):(console.log(Y.red("\u2717")),c(C instanceof Error?C.message:String(C))),process.exit(C.exitCode??1)}let f=[];if(n.file&&n.file.length>0){for(let C of n.file)Wr(ro(C))||(c(`File not found: ${C}`),process.exit(6));n.output!=="json"&&n.output!=="quiet"&&console.log(Y.gray(`
|
|
44
|
+
Uploading ${n.file.length} file(s)...`));try{f=await $n(n.file.map(C=>ro(C)),(C,g,O)=>{n.output!=="json"&&n.output!=="quiet"&&O&&process.stdout.write(`\r ${Y.cyan("\u2B06")} Uploading: ${O} (${C+1}/${g})`)}),n.output!=="json"&&n.output!=="quiet"&&console.log(`\r ${Y.green("\u2713")} Uploaded ${f.length} file(s) `)}catch(C){c(`Failed to upload files: ${C instanceof Error?C.message:String(C)}`),process.exit(1)}}let x;(n.stdin||!process.stdin.isTTY)&&(x=await Jr(),n.stdin&&!x&&(c("No content received from stdin"),process.exit(6)));let I=n.context;if(n.contextFile)try{I=Gr(n.contextFile,"utf-8"),I.trim()||(c(`Context file is empty: ${n.contextFile}`),process.exit(6))}catch{c(`Failed to read context file: ${n.contextFile}`),process.exit(6)}!x&&!I&&f.length===0&&(c("Context is required to create a presentation."),console.log(),console.log(Y.gray(" -f, --file <paths...> Upload files (PDF, PPTX, images)")),console.log(Y.gray(" -c, --context <text> Inline text context")),console.log(Y.gray(" --context-file <path> Read from a file")),console.log(Y.gray(` cat file | ${u.commands[0]} Pipe content`)),process.exit(6));let T=[...f];x&&T.push({id:`stdin-${Date.now()}`,content:x,title:"Piped Content",mime:"text/plain",selected:!0}),I&&T.push({id:`context-${Date.now()}`,content:I,title:"Context",mime:"text/plain",selected:!0});let y,A=n.primaryColor||n.secondaryColor||n.accentColor||n.backgroundColor||n.foregroundColor;(n.theme||A||n.decorations)&&(y={},n.theme&&(y.preset=n.theme),A&&(y.custom={},n.primaryColor&&(y.custom.primary=n.primaryColor),n.secondaryColor&&(y.custom.secondary=n.secondaryColor),n.accentColor&&(y.custom.accent=n.accentColor),n.backgroundColor&&(y.custom.background=n.backgroundColor),n.foregroundColor&&(y.custom.foreground=n.foregroundColor)),n.decorations&&(y.decorations=n.decorations));try{let C=await In({topic:t,...e!==void 0&&{slideCount:e},...n.mode&&{mode:n.mode},...n.tone&&{tone:n.tone},...n.amount&&{amount:n.amount},...n.audience&&{audience:n.audience},...n.language&&{language:n.language},...n.brandingId&&{brandingId:n.brandingId},...n.styling&&{stylingMode:n.styling},...n.referenceUrl&&{referenceUrl:n.referenceUrl},...n.thinkingDepth&&{thinkingDepth:n.thinkingDepth},...n.goal&&{goal:n.goal},...n.teamId&&{teamId:n.teamId},...y&&{theme:y},...T.length>0&&{contentSources:{uploadedFiles:T,contextFiles:[]}}}),g=await no(C,t,{slideCount:e,mode:n.mode,tone:n.tone,language:n.language,noStream:n.noStream,debug:n.debug,quiet:n.output==="quiet",json:n.output==="json"});if(n.output==="json")console.log(JSON.stringify({success:!0,presentation:{slug:g.slug,title:g.title??t,slidesCount:g.slidesCount,elapsedSeconds:g.elapsedSeconds,totalTokens:g.totalTokens},viewUrl:Ce(g.slug,n.language??"en")},null,2));else{console.log(),b("Presentation created successfully"),console.log(),v("Title",g.title??t),v("Slides",String(g.slidesCount??e??"\u2014"));let O=[];if(g.elapsedSeconds){let H=Math.floor(g.elapsedSeconds/60),oe=g.elapsedSeconds%60;O.push(H>0?`${H}m ${oe}s`:`${oe}s`)}g.totalTokens&&O.push(`${g.totalTokens.toLocaleString()} tokens`),O.length>0&&v("Generated in",O.join(" \xB7 ")),console.log();let q=Ce(g.slug,n.language??"en");q!=="N/A"&&(console.log(Y.bold(" Open: ")+Y.cyan.underline(q)),n.open&&await(await import("open")).default(q)),console.log()}}catch(C){n.output==="json"?console.log(JSON.stringify({success:!1,error:C instanceof Error?C.message:String(C)})):c(C instanceof Error?C.message:String(C)),process.exit(C.exitCode??1)}});async function Jr(){return new Promise(t=>{let n="";if(process.stdin.setEncoding("utf8"),process.stdin.isTTY){t("");return}process.stdin.on("data",e=>{n+=e}),process.stdin.on("end",()=>{t(n.trim())}),setTimeout(()=>{t(n.trim())},100)})}import{Command as Yr}from"commander";var ao=new Yr("list").description("List presentations").option("-n, --limit <count>","Number of results to return","20").option("-f, --format <format>","Output format (table, json, ids)","table").option("--sort <field>","Sort by field (created, updated, title)").option("--team-id <id>","Team ID (uses default if not specified)").option("-d, --detail","Show detailed output including URLs").option("-l, --language <lang>","Language for URLs","en").action(async t=>{await N();let n=parseInt(t.limit,10);(isNaN(n)||n<1)&&(c("Invalid limit value"),process.exit(6));let e=t.teamId??Ze();e||(c("`Team ID required. Set a default with '${brand.commands[0]} config set team-id <id>' or use --team-id`"),process.exit(6));try{let o=await Rn(e,n);if(o.length===0){t.format==="json"?console.log(JSON.stringify([])):t.format!=="ids"&&d("No presentations found");return}switch(t.sort&&o.sort((r,i)=>{switch(t.sort){case"created":return new Date(i.createdAt).getTime()-new Date(r.createdAt).getTime();case"title":return(r.title||"").localeCompare(i.title||"");default:return 0}}),t.format){case"json":console.log(JSON.stringify(o,null,2));break;case"ids":console.log(xn(o));break;default:console.log(vn(o,{showLinks:t.detail,language:t.language}));break}}catch(o){c(o instanceof Error?o.message:String(o)),process.exit(o.exitCode??1)}});import{Command as Hr}from"commander";import mt from"chalk";var so=new Hr("get").description("Get presentation details").argument("<slug>","Presentation slug (e.g., my-presentation-v1-abc123)").option("-f, --format <format>","Output format (summary, full, json)","summary").option("--include <fields>","Fields to include (comma-separated: slides,styling)").option("-l, --language <lang>","Language for view URL","en").action(async(t,n)=>{await N();try{let e=await ke(t);if(n.format==="json"){console.log(JSON.stringify(e,null,2));return}let o=Ce(e.slug,n.language);te("Presentation Details"),console.log(),v("Slug",e.slug),v("Title",e.title||"Untitled"),v("Description",e.description||mt.gray("None")),v("Slides",String(e.numberOfSlides||"-")),v("Mode",e.mode||"-"),v("Created",Ft(e.createdAt)),e.updatedAt&&v("Updated",Ft(e.updatedAt)),console.log(),o!=="N/A"&&console.log(mt.bold(" Open: ")+mt.cyan.underline(o)),console.log(),n.format==="full"&&n.include?.includes("slides")&&(console.log(mt.gray("(Full slide content available in JSON format)")),console.log())}catch(e){c(e instanceof Error?e.message:String(e)),process.exit(e.exitCode??1)}});import{Command as Kr}from"commander";import{confirm as zr}from"@inquirer/prompts";var co=new Kr("delete").description("Delete a presentation").argument("<slug>","Presentation slug (e.g., my-presentation-v1-abc123)").option("-f, --force","Skip confirmation prompt").option("-q, --quiet","Suppress output").action(async(t,n)=>{if(await N(),!n.force)try{if(!await zr({message:`Are you sure you want to delete presentation "${t}"?`,default:!1})){n.quiet||R("Deletion cancelled");return}}catch{n.quiet||R("Deletion cancelled");return}try{await An(t),n.quiet||b(`Presentation "${t}" deleted`)}catch(e){c(e instanceof Error?e.message:String(e)),process.exit(e.exitCode??1)}});import{Command as Xr}from"commander";import{writeFile as Qr}from"fs/promises";import{resolve as lo}from"path";import Zr from"ora";var mo=new Xr("export").description("Export a presentation to ZIP").argument("<slug>","Presentation slug (e.g., my-presentation-v1-abc123)").option("-o, --output <path>","Output file path").option("--include-images","Include images (default: true)",!0).option("--include-branding","Include branding (default: true)",!0).option("--no-external","Skip external image downloads").action(async(t,n)=>{await N();let e=Zr("Fetching presentation...").start();try{let o=await ke(t),r=o.title||t,i=o.id,a=t.replace(/[^a-z0-9-]/gi,"_").toLowerCase().substring(0,50),s=new Date().toISOString().split("T")[0],l=`${a}_${s}.zip`,m=n.output?lo(n.output):lo(process.cwd(),l);e.text=`Exporting "${r}"...`;let p=await On(i,{includeImages:n.includeImages,includeBranding:n.includeBranding,includeExternal:!n.noExternal});e.text="Saving file...",await Qr(m,Buffer.from(p)),e.succeed("Export complete!"),console.log(),d(`File saved: ${m}`),d(`Size: ${ei(p.byteLength)}`),console.log()}catch(o){e.fail("Export failed"),c(o instanceof Error?o.message:String(o)),process.exit(o.exitCode??1)}});function ei(t){return t<1024?`${t} B`:t<1024*1024?`${(t/1024).toFixed(1)} KB`:`${(t/(1024*1024)).toFixed(1)} MB`}import{Command as ti}from"commander";import{readFile as ni}from"fs/promises";import{resolve as oi,basename as ri}from"path";import ii from"ora";var uo=new ti("import").description("Import a presentation from ZIP (admin only)").argument("<file>","Path to ZIP file");uo._hidden=!0;var po=uo.option("--dry-run","Validate without importing").option("--overwrite","Overwrite existing presentations").option("--remap-ids","Generate new IDs (default: true)",!0).action(async(t,n)=>{await N();try{(await fe()).user.role!=="admin"&&(c("This command is only available to administrators"),process.exit(2))}catch{c("Failed to verify permissions"),process.exit(2)}let e=oi(t),o=ri(e);o.endsWith(".zip")||(c("File must be a ZIP archive"),process.exit(6));let r=ii("Reading file...").start();try{let i=await ni(e);n.dryRun?r.text="Validating...":r.text="Importing presentation...";let a=await En(i,o,{dryRun:n.dryRun});if(a.success){if(n.dryRun)r.succeed("Validation passed!"),console.log(),d("The file is valid and can be imported."),a.statistics&&(console.log(),v("Slides",String(a.statistics.slidesImported)),v("Images",String(a.statistics.imagesUploaded)));else if(r.succeed("Import complete!"),console.log(),v("Presentation ID",a.presentationId??"N/A"),v("Document ID",a.documentId??"N/A"),a.statistics&&(v("Slides imported",String(a.statistics.slidesImported)),v("Images uploaded",String(a.statistics.imagesUploaded)),v("Time",`${a.statistics.totalTimeMs}ms`)),a.presentationId){let s=j();v("View URL",`${s}/p/${a.presentationId}`)}if(a.warnings&&a.warnings.length>0){console.log();for(let s of a.warnings)R(s)}console.log()}else{if(r.fail("Import failed"),console.log(),a.errors)for(let s of a.errors)c(`${s.type}: ${s.message}`);process.exit(1)}}catch(i){r.fail("Import failed"),i.code==="ENOENT"&&(c(`File not found: ${e}`),process.exit(3)),c(i instanceof Error?i.message:String(i)),process.exit(i.exitCode??1)}});import{Command as He}from"commander";import X from"chalk";import ai from"ora";import{writeFile as fo}from"fs/promises";var go=new He("branding").description("Manage brand profiles").addCommand(new He("list").description("List brand profiles").option("-f, --format <format>","Output format (table, json)","table").action(async t=>{await N();try{let n=await Pn();if(n.brandings.length===0){t.format==="json"?console.log(JSON.stringify([])):(d("No brand profiles found"),console.log(),console.log(X.gray("`Create one with: ${brand.commands[0]} branding extract <url>`")));return}t.format==="json"?console.log(JSON.stringify(n.brandings,null,2)):console.log(wn(n.brandings))}catch(n){c(n instanceof Error?n.message:String(n)),process.exit(n.exitCode??1)}})).addCommand(new He("get").description("Get brand profile details").argument("<id>","Brand profile ID").option("-f, --format <format>","Output format (summary, json)","summary").option("-o, --output <path>","Save JSON to file").action(async(t,n)=>{await N();try{let e=await Dt(t);if(n.output){await fo(n.output,JSON.stringify(e,null,2)),b(`Brand profile saved to ${n.output}`);return}if(n.format==="json")console.log(JSON.stringify(e,null,2));else{if(te("Brand Profile"),console.log(),v("ID",e.id),v("Name",e.name),v("Source URL",e.sourceUrl??X.gray("None")),v("Default",e.isDefault?X.green("Yes"):"No"),e.createdAt&&v("Created",new Date(e.createdAt).toLocaleString()),console.log(),e.primaryColor||e.colors.length>0){if(console.log(X.bold(" Colors")),e.primaryColor){let r=X.bgHex(e.primaryColor)(" ");console.log(` Primary: ${r} ${e.primaryColor}`)}if(e.colors.length>0){console.log(" Palette:");for(let r of e.colors.slice(0,10)){let i=X.bgHex(r.hex)(" "),a=r.role?X.gray(` (${r.role})`):"";console.log(` ${i} ${r.hex}${a}`)}}console.log()}e.logoUrl&&(console.log(X.bold(" Logo")),console.log(` ${e.logoUrl}`),console.log());let o=e.typography;o&&Object.keys(o).length>0&&(console.log(X.bold(" Typography")),(o.primary||o.headings)&&console.log(` Headings: ${o.primary||o.headings||"-"}`),(o.secondary||o.body)&&console.log(` Body: ${o.secondary||o.body||"-"}`),console.log()),(e.confidence||e.extractionMethod)&&(console.log(X.bold(" Extraction")),e.confidence&&console.log(` Confidence: ${Math.round(e.confidence*100)}%`),e.extractionMethod&&console.log(` Method: ${e.extractionMethod}`),console.log()),console.log(X.gray(" Use --format json for full details")),console.log()}}catch(e){c(e instanceof Error?e.message:String(e)),process.exit(e.exitCode??1)}})).addCommand(new He("extract").description("Extract brand profile from a website").argument("<url>","Website URL to extract branding from").option("--team-id <id>","Team ID").option("--no-save","Extract without saving").option("-o, --output <path>","Save JSON to file").action(async(t,n)=>{await N();try{new URL(t.startsWith("http")?t:`https://${t}`)}catch{c("Invalid URL format"),process.exit(6)}let e=t.startsWith("http")?t:`https://${t}`,o=n.teamId??Ze(),r=ai({text:`Extracting branding from ${e}...`,stream:process.stdout}).start();try{let i=await Fn(e,o),a=await Dt(i.id);r.succeed("Branding extracted!");let s=JSON.stringify(a,null,2);n.output?(await fo(n.output,s),b(`Brand profile saved to ${n.output}`)):console.log(s),console.log()}catch(i){r.fail("Extraction failed"),c(i instanceof Error?i.message:String(i)),process.exit(i.exitCode??1)}})).addCommand(new He("set-default").description("Set a brand profile as default").argument("<id>","Brand profile ID").action(async t=>{await N(),d(`To set brand ${t} as default, use the web dashboard.`),console.log(),console.log(X.gray("API support for setting default brand is coming soon."))}));import{Command as $e}from"commander";import ho from"chalk";import si from"ora";function ci(){return new $e("blog").description("Generate a blog post from a presentation").argument("<slug>","Presentation slug").option("--words <count>","Target word count (50-2000)","300").option("--tone <tone>","Writing tone (professional, casual, educational)","professional").option("--audience <text>","Target audience description").option("-f, --format <format>","Output format (human, json, markdown)","human").option("--instructions <text>","Custom instructions for the blog").option("--no-images","Exclude image references").option("--toc","Include table of contents").action(async(t,n)=>{await N();let e=parseInt(n.words,10);(isNaN(e)||e<50||e>2e3)&&(c("Word count must be between 50 and 2000"),process.exit(6));let o=["professional","casual","educational"];o.includes(n.tone)||(c(`Invalid tone. Valid options: ${o.join(", ")}`),process.exit(6));let r=si("Fetching presentation...").start();try{let i=await ke(t);r.text="Generating blog post...";let a=await Dn(i.id,i.documentCreatedAt||i.createdAt,{targetWordCount:e,tone:n.tone,targetAudience:n.audience??"General Audience",customInstructions:n.instructions??"",includeImages:!n.noImages,includeTableOfContents:n.toc??!1}),s=await oo(a,{quiet:!0});r.succeed("Blog generated"),n.format==="json"?console.log(JSON.stringify({presentationId:t,title:i.title,wordCount:e,tone:n.tone,content:s},null,2)):n.format==="markdown"?console.log(s):(console.log(),console.log(ho.bold(`Blog: ${i.title}`)),console.log(ho.gray("\u2500".repeat(40))),console.log(),console.log(s),console.log())}catch(i){r.fail("Generation failed"),c(i instanceof Error?i.message:String(i)),process.exit(i.exitCode??1)}})}function li(){return new $e("tweets").description("Generate tweets from a presentation").argument("<slug>","Presentation slug").option("--count <n>","Number of tweets to generate","5").option("-f, --format <format>","Output format (human, json)","human").action(async(t,n)=>{await N(),d("Tweet generation coming soon.")})}function di(){return new $e("linkedin").description("Generate a LinkedIn carousel from a presentation").argument("<slug>","Presentation slug").option("-f, --format <format>","Output format (human, json)","human").action(async t=>{await N(),d("LinkedIn carousel generation coming soon.")})}function mi(){return new $e("questions").description("Generate questions for a presentation").argument("<slug>","Presentation slug").option("--count <n>","Number of questions","10").option("-f, --format <format>","Output format (human, json)","human").action(async(t,n)=>{await N(),d("Question generation coming soon.")})}function ui(){return new $e("cheatsheet").description("Generate a presenter cheat sheet").argument("<slug>","Presentation slug").option("--mode <mode>","Cheat sheet mode (qa-prep, talking-points)","qa-prep").option("-f, --format <format>","Output format (human, json, markdown)","human").action(async(t,n)=>{await N(),d("Cheat sheet generation coming soon.")})}function yo(){let t=new $e("derive").description("Generate derivative content from a presentation"),n=Jn();return n&&(n.deckToBlog&&t.addCommand(ci()),n.deckToTweets&&t.addCommand(li()),n.linkedInCarousel&&t.addCommand(di()),n.redTeamQuestions&&t.addCommand(mi()),n.presenterCheatSheet&&t.addCommand(ui())),t}import{Command as pi}from"commander";import _t from"chalk";var bo=new pi("ideas").description("Generate presentation topic ideas").option("--context <text>","Custom context for idea generation").option("--count <n>","Number of ideas to generate","5").option("-f, --format <format>","Output format (human, json)","human").action(async t=>{await N(),d("Idea generation is available in the web dashboard."),console.log(),console.log(_t.gray("The CLI will support idea generation in a future release.")),console.log(),console.log("For now, try these approaches:"),console.log(_t.gray("` 1. Visit the ${brand.displayName} dashboard and use the idea generator`")),console.log(_t.gray(" 2. Create a presentation with a broad topic and refine it")),console.log()});import{Command as fi}from"commander";import qt from"chalk";var vo=new fi("whoami").description("Show current user and team information").option("-f, --format <format>","Output format (human, json)","human").action(async t=>{await N();try{let n=await fe();if(jt(),t.format==="json"){console.log(JSON.stringify(n,null,2));return}if(te("User"),console.log(),v("Email",n.user.email),n.user.username&&v("Username",n.user.username),(n.user.firstName||n.user.lastName)&&v("Name",[n.user.firstName,n.user.lastName].filter(Boolean).join(" ")),v("Auth Type",n.authType==="apiKey"?"API Key":"Session"),n.currentTeam&&(console.log(),te("Current Team"),console.log(),v("ID",n.currentTeam.id),v("Name",n.currentTeam.name),v("Plan",n.currentTeam.planName),v("Role",n.currentTeam.isOwner?"Owner":"Member")),n.teams.length>1){console.log(),te("All Teams"),console.log();for(let e of n.teams){let o=e.isCurrent?qt.green(" (current)"):"";console.log(` ${qt.bold(e.name)}${o}`),console.log(qt.gray(` ID: ${e.id} | Plan: ${e.planName} | Role: ${e.role}`))}}console.log()}catch(n){c(n instanceof Error?n.message:String(n)),process.exit(n.exitCode??1)}});import{Command as yi}from"commander";import U from"chalk";function ut(t){let{name:n,cmd:e}=t,o=n.toUpperCase().replace(/[^A-Z0-9]/g,"_");return`---
|
|
45
|
+
name: ${n}
|
|
46
46
|
description: CLI with TTS (text-to-speech), AI music generation, sound effects, stock image/video search, audio mixing, URL scraping, and branding. Use when user asks for voiceover, narration, background music, sound effects, stock footage, or needs to scrape a website.
|
|
47
47
|
---
|
|
48
48
|
|
|
49
|
-
# ${
|
|
49
|
+
# ${n} CLI
|
|
50
50
|
|
|
51
51
|
A comprehensive CLI for AI-powered content creation. Generate presentations, video assets, voiceovers, music, and search stock media - all from your terminal.
|
|
52
52
|
|
|
53
|
-
**Install:** \`npm install -g @${
|
|
53
|
+
**Install:** \`npm install -g @${n}/cli\` or \`pnpm add -g @${n}/cli\`
|
|
54
54
|
|
|
55
55
|
---
|
|
56
56
|
|
|
@@ -65,7 +65,7 @@ ${e} login
|
|
|
65
65
|
This opens a browser for OAuth login. Alternatively, set an API key:
|
|
66
66
|
|
|
67
67
|
\`\`\`bash
|
|
68
|
-
export ${
|
|
68
|
+
export ${o}_API_KEY="your-key-here"
|
|
69
69
|
\`\`\`
|
|
70
70
|
|
|
71
71
|
**For AI Assistants (Claude Code, Cursor, etc.):**
|
|
@@ -82,10 +82,10 @@ ${e} whoami
|
|
|
82
82
|
## Commands
|
|
83
83
|
|
|
84
84
|
### Presentations
|
|
85
|
-
\`${e} create "Topic"\` \u2192 Load \`${
|
|
85
|
+
\`${e} create "Topic"\` \u2192 Load \`${n}-presentation\` skill
|
|
86
86
|
|
|
87
87
|
### Video
|
|
88
|
-
\`${e} video generate new my-video\` \u2192 Load \`${
|
|
88
|
+
\`${e} video generate new my-video\` \u2192 Load \`${n}-video\` skill
|
|
89
89
|
|
|
90
90
|
### TTS
|
|
91
91
|
\`${e} tts generate -t "Text" -o out.mp3\` \u2192 [references/tts.md](references/tts.md)
|
|
@@ -114,8 +114,8 @@ ${e} whoami
|
|
|
114
114
|
### Config
|
|
115
115
|
\`${e} config show\` \u2192 [references/config.md](references/config.md)
|
|
116
116
|
|
|
117
|
-
`}function
|
|
118
|
-
name: ${
|
|
117
|
+
`}function pt(t){let{name:n,cmd:e}=t;return`---
|
|
118
|
+
name: ${n}-video
|
|
119
119
|
description: Orchestrates video asset assembly for marketing videos, product demos, explainers, and promo content. Analyzes projects to extract branding (logos, colors, fonts), identifies reusable UI components, generates voiceovers and music via CLI, searches stock media, and produces a video-manifest.json. Use when user says "create a video", "make a promo", "product demo", "explainer video", "marketing video", "gather video assets", "video for my project", or "TikTok/Reels content".
|
|
120
120
|
---
|
|
121
121
|
|
|
@@ -156,6 +156,7 @@ ${e} whoami
|
|
|
156
156
|
| \`${e} music generate\` | Generate background music |
|
|
157
157
|
| \`${e} image search\` | Search stock images |
|
|
158
158
|
| \`${e} video find\` | Search stock video clips |
|
|
159
|
+
| \`${e} video analyze <file>\` | Analyze video: scene detection, transcription, frame descriptions |
|
|
159
160
|
|
|
160
161
|
## Step 1: Initialize Project
|
|
161
162
|
|
|
@@ -313,67 +314,47 @@ The returned \`video-plan.md\` is your source of truth for everything that follo
|
|
|
313
314
|
|
|
314
315
|
Follow the plan precisely for all subsequent steps.
|
|
315
316
|
|
|
316
|
-
## Step 8: Generate Audio Assets
|
|
317
|
+
## Step 8: Generate Audio Assets & Timing
|
|
317
318
|
|
|
318
|
-
|
|
319
|
+
Spawn the \`video-audio-director\` agent to handle all audio generation and timing reconciliation in one focused pass.
|
|
319
320
|
|
|
320
|
-
**
|
|
321
|
-
- **Voiceover scripts** \u2192 each scene's Animation & Audio section (VO lines)
|
|
322
|
-
- **Music prompt** \u2192 Video Overview \u2192 Music Direction (copy verbatim)
|
|
323
|
-
- **SFX prompts** \u2192 Animation & Audio timestamps (each distinct sound described)
|
|
324
|
-
- **Stock media** \u2192 ONLY if plan explicitly references photos/footage from Available Assets. If plan says "Build (CSS)", there is nothing to search for
|
|
321
|
+
**Construct the Task prompt:**
|
|
325
322
|
|
|
326
|
-
### Voiceover
|
|
327
|
-
|
|
328
|
-
Generate one TTS file per scene:
|
|
329
|
-
|
|
330
|
-
\`\`\`bash
|
|
331
|
-
${e} tts generate --text "Voiceover text from scene 1" --voice Puck --output ./public/audio/scene-1.wav
|
|
332
|
-
${e} tts generate --text "Voiceover text from scene 2" --voice Puck --output ./public/audio/scene-2.wav
|
|
333
|
-
# ... one per scene, run in parallel
|
|
334
323
|
\`\`\`
|
|
324
|
+
## Audio Director Task
|
|
335
325
|
|
|
336
|
-
**
|
|
337
|
-
|
|
338
|
-
|
|
326
|
+
**CLI command:** ${e}
|
|
327
|
+
**Video plan:** video-plan.md
|
|
328
|
+
**Output directory:** ./public/audio/
|
|
329
|
+
**Voice:** [chosen voice, default: Puck]
|
|
330
|
+
**Provider:** [chosen provider, default: gemini]
|
|
331
|
+
|
|
332
|
+
Generate all audio assets and produce timing-manifest.md.
|
|
339
333
|
\`\`\`
|
|
340
334
|
|
|
341
|
-
**
|
|
335
|
+
**Available voices:** \`${e} tts voices -p <provider>\`
|
|
342
336
|
- **Gemini (default):** Puck, Kore, Charon, Zephyr, Fenrir, Aoede, Enceladus...
|
|
343
337
|
- **OpenAI:** alloy, coral, nova, onyx, sage, shimmer, ash, ballad...
|
|
344
338
|
- **ElevenLabs:** Use \`--voice-id\` with ID from \`${e} tts voices -p elevenlabs\`
|
|
345
339
|
|
|
346
|
-
### Music
|
|
347
|
-
|
|
348
|
-
Use Music Direction from the plan's Video Overview:
|
|
349
|
-
|
|
350
|
-
\`\`\`bash
|
|
351
|
-
${e} music generate --prompt "copied verbatim from Music Direction" --duration 50 --output ./public/audio/music.mp3
|
|
352
|
-
\`\`\`
|
|
353
|
-
|
|
354
|
-
### Sound Effects (if plan describes them)
|
|
355
|
-
|
|
356
|
-
Extract distinct SFX from Animation & Audio timestamps:
|
|
357
|
-
|
|
358
|
-
\`\`\`bash
|
|
359
|
-
${e} sfx generate --prompt "digital snap, harsh electronic break" --duration 0.5 --output ./public/audio/snap.mp3
|
|
360
|
-
${e} sfx generate --prompt "massive airy whoosh, sweeping" --duration 1.0 --output ./public/audio/whoosh.mp3
|
|
361
|
-
${e} sfx generate --prompt "metallic shield lock, heavy ka-chunk" --duration 0.5 --output ./public/audio/shield-lock.mp3
|
|
362
|
-
\`\`\`
|
|
363
|
-
|
|
364
340
|
### Stock Media (only if plan requires it)
|
|
365
341
|
|
|
366
|
-
|
|
342
|
+
While the audio director works, search for stock media if the plan references real photos/footage. CSS-built visuals do NOT need stock media.
|
|
367
343
|
|
|
368
344
|
\`\`\`bash
|
|
369
|
-
# Only if plan references actual photos/footage
|
|
370
345
|
${e} image search -q "exact description from plan" --max-results 3
|
|
371
346
|
${e} video find "exact description from plan" --max-results 3
|
|
372
347
|
\`\`\`
|
|
373
348
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
349
|
+
### After Audio Director Completes
|
|
350
|
+
|
|
351
|
+
Verify the outputs:
|
|
352
|
+
- \`timing-manifest.md\` exists with valid timing data
|
|
353
|
+
- All VO files exist in \`./public/audio/\`
|
|
354
|
+
- Music file exists
|
|
355
|
+
- Read \`timing-manifest.md\` \u2014 you'll need it for Step 9b (Main.tsx) and Step 9c (scene builders)
|
|
356
|
+
|
|
357
|
+
**Audio mixing is handled automatically** by the template's \`DuckedMusic\` component (see Step 9b).
|
|
377
358
|
|
|
378
359
|
## Step 9: Build Scenes and Render Video
|
|
379
360
|
|
|
@@ -381,30 +362,18 @@ ${e} video find "exact description from plan" --max-results 3
|
|
|
381
362
|
|
|
382
363
|
### 9a. Component Discovery (BEFORE writing ANY code)
|
|
383
364
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
1. Run: \`find src/components -name '*.tsx' | head -50\` to list all template components
|
|
387
|
-
2. Read \`src/components/transitions/index.ts\` (or the transitions folder) to see every available transition with its props
|
|
388
|
-
3. Read \`src/components/effects/index.ts\` (or the effects folder) to see every available effect with its props
|
|
389
|
-
4. Output a **Component Inventory** block listing every discovered component:
|
|
365
|
+
List available template components. Do NOT read every file \u2014 just get the directory listing:
|
|
390
366
|
|
|
367
|
+
\`\`\`bash
|
|
368
|
+
ls src/components/transitions/ src/components/effects/
|
|
391
369
|
\`\`\`
|
|
392
|
-
=== COMPONENT INVENTORY ===
|
|
393
|
-
Transitions:
|
|
394
|
-
- WhipPan: import from '../components/transitions/WhipPan' | props: direction, blur, speed
|
|
395
|
-
- ZoomBlur: import from '../components/transitions/ZoomBlur' | props: intensity, duration
|
|
396
|
-
- [... every transition found]
|
|
397
|
-
|
|
398
|
-
Effects:
|
|
399
|
-
- ColorGrading: import from '../components/effects/ColorGrading' | props: preset (cinematic|vintage|noir|warm|cool|matrix|...)
|
|
400
|
-
- GrainOverlay: import from '../components/effects/GrainOverlay' | props: intensity
|
|
401
|
-
- [... every effect found]
|
|
402
370
|
|
|
403
|
-
|
|
404
|
-
|
|
371
|
+
Then read ONLY the barrel exports to get component names and props:
|
|
372
|
+
\`\`\`bash
|
|
373
|
+
cat src/components/transitions/index.ts src/components/effects/index.ts 2>/dev/null
|
|
405
374
|
\`\`\`
|
|
406
375
|
|
|
407
|
-
|
|
376
|
+
Output a brief **Component Inventory** listing component names and import paths. Scene builders have the \`remotion-scene-craft\` skill preloaded and will read individual component files themselves if they need prop details.
|
|
408
377
|
|
|
409
378
|
### 9b. Shared Setup
|
|
410
379
|
|
|
@@ -445,56 +414,84 @@ Read \`video-plan.md\` and split it into sections:
|
|
|
445
414
|
- **Per-scene sections** \u2014 split by \`## Scene\` headers. Each section = everything from one \`## Scene\` header to the next (or EOF)
|
|
446
415
|
- Count the scenes and note each scene's title, duration, transition in, and transition out
|
|
447
416
|
|
|
448
|
-
**3. Create Main.tsx
|
|
417
|
+
**3. Create Main.tsx using the template's audio components:**
|
|
449
418
|
|
|
450
|
-
|
|
419
|
+
The template provides \`VoiceoverSequence\`, \`DuckedMusic\`, and timing helpers in \`src/audio/\`.
|
|
451
420
|
|
|
452
|
-
|
|
453
|
-
import { TransitionSeries } from '@remotion/transitions';
|
|
454
|
-
// Import transitions from template components
|
|
455
|
-
import { WhipPan } from './components/transitions/WhipPan';
|
|
456
|
-
import { Scene01Intro } from './scenes/Scene01Intro';
|
|
457
|
-
import { Scene02Feature } from './scenes/Scene02Feature';
|
|
458
|
-
// ... etc, one import per scene from the plan
|
|
459
|
-
\`\`\`
|
|
460
|
-
|
|
461
|
-
**CRITICAL: Transition Overlap & Audio Sync**
|
|
421
|
+
**Key principle: visual Sequences and audio components are SIBLINGS, not nested.** Voiceover lives in its own layer \u2014 immune to scene transition overlap. Music auto-ducks during VO.
|
|
462
422
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
**Solution - Plan voiceover timing:**
|
|
466
|
-
1. Scene duration = Voiceover duration + Transition duration
|
|
467
|
-
2. Voiceover should END before transition starts
|
|
468
|
-
3. Example: 90 frame scene with 20 frame transition \u2192 Voiceover max 70 frames
|
|
423
|
+
Build \`src/Main.tsx\` following this exact pattern. Use **timing-manifest.md** from Step 8 for all durations and frame positions:
|
|
469
424
|
|
|
470
425
|
\`\`\`tsx
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
426
|
+
import { AbsoluteFill, Sequence, staticFile } from 'remotion';
|
|
427
|
+
import { VoiceoverSequence, DuckedMusic, T_TRANSITION } from './audio';
|
|
428
|
+
import type { VoSegment } from './audio';
|
|
429
|
+
// Import scenes (agents will create these files)
|
|
430
|
+
import { Scene01Hook } from './scenes/Scene01Hook';
|
|
431
|
+
import { Scene02Feature } from './scenes/Scene02Feature';
|
|
432
|
+
// ... one import per scene
|
|
433
|
+
|
|
434
|
+
// \u2500\u2500 Scene timing (from Timing Manifest \u2014 use ADJUSTED durations) \u2500\u2500
|
|
435
|
+
const SCENE_DURATIONS = [120, 210, 180, 150]; // frames per scene
|
|
436
|
+
|
|
437
|
+
// TOTAL_FRAMES is CALCULATED, never hardcoded
|
|
438
|
+
const TOTAL_FRAMES =
|
|
439
|
+
SCENE_DURATIONS.reduce((a, b) => a + b, 0) -
|
|
440
|
+
(SCENE_DURATIONS.length - 1) * T_TRANSITION;
|
|
441
|
+
|
|
442
|
+
// \u2500\u2500 VO segments (from Timing Manifest \u2014 absolute frame positions) \u2500\u2500
|
|
443
|
+
const voSegments: VoSegment[] = [
|
|
444
|
+
{ src: staticFile('audio/scene-1.wav'), from: 10, durationInFrames: 69 },
|
|
445
|
+
{ src: staticFile('audio/scene-2.wav'), from: 115, durationInFrames: 156 },
|
|
446
|
+
// ... one per scene with VO
|
|
447
|
+
];
|
|
448
|
+
|
|
449
|
+
// \u2500\u2500 Helper: scene start frame accounting for transition overlaps \u2500\u2500
|
|
450
|
+
function sceneStart(index: number): number {
|
|
451
|
+
let start = 0;
|
|
452
|
+
for (let i = 0; i < index; i++) {
|
|
453
|
+
start += SCENE_DURATIONS[i] - T_TRANSITION;
|
|
454
|
+
}
|
|
455
|
+
return start;
|
|
456
|
+
}
|
|
485
457
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
458
|
+
export const Main: React.FC = () => {
|
|
459
|
+
return (
|
|
460
|
+
<AbsoluteFill style={{ background: '#000' }}>
|
|
461
|
+
{/* \u2500\u2500 VISUAL LAYER \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */}
|
|
462
|
+
<Sequence from={sceneStart(0)} durationInFrames={SCENE_DURATIONS[0]}>
|
|
463
|
+
<Scene01Hook />
|
|
464
|
+
</Sequence>
|
|
465
|
+
<Sequence from={sceneStart(1)} durationInFrames={SCENE_DURATIONS[1]}>
|
|
466
|
+
<Scene02Feature />
|
|
467
|
+
</Sequence>
|
|
468
|
+
{/* ... one Sequence per scene */}
|
|
469
|
+
|
|
470
|
+
{/* \u2500\u2500 AUDIO LAYER (siblings, NOT nested in scenes) \u2500\u2500 */}
|
|
471
|
+
<VoiceoverSequence segments={voSegments} />
|
|
472
|
+
<DuckedMusic
|
|
473
|
+
src={staticFile('audio/music.mp3')}
|
|
474
|
+
durationInFrames={TOTAL_FRAMES}
|
|
475
|
+
voSegments={voSegments}
|
|
476
|
+
/>
|
|
477
|
+
</AbsoluteFill>
|
|
478
|
+
);
|
|
479
|
+
};
|
|
489
480
|
\`\`\`
|
|
490
481
|
|
|
491
|
-
**
|
|
492
|
-
|
|
493
|
-
|
|
482
|
+
**Also update \`src/Root.tsx\`** to use the adjusted TOTAL_FRAMES:
|
|
483
|
+
\`\`\`tsx
|
|
484
|
+
import { DEFAULT_FPS } from './audio/constants';
|
|
485
|
+
// ... in Composition: fps={DEFAULT_FPS}, durationInFrames={TOTAL_FRAMES}
|
|
486
|
+
\`\`\`
|
|
494
487
|
|
|
495
|
-
**
|
|
496
|
-
-
|
|
497
|
-
-
|
|
488
|
+
**Key rules:**
|
|
489
|
+
- \`SCENE_DURATIONS\` comes from \`timing-manifest.md\`, NOT from the plan's original estimates
|
|
490
|
+
- \`TOTAL_FRAMES\` is CALCULATED: sum minus (N-1) \xD7 T_TRANSITION
|
|
491
|
+
- VO \`from\` values are ABSOLUTE composition frame positions from the manifest
|
|
492
|
+
- Audio is NEVER nested inside scene Sequences \u2014 \`VoiceoverSequence\` and \`DuckedMusic\` are siblings
|
|
493
|
+
- \`DuckedMusic\` auto-ducks during VO \u2014 no manual volume math needed
|
|
494
|
+
- Scenes are VISUAL ONLY \u2014 they do not contain \`<Audio>\` for VO/music (SFX is the exception)
|
|
498
495
|
|
|
499
496
|
### 9c. Spawn Per-Scene Builder Agents
|
|
500
497
|
|
|
@@ -526,6 +523,28 @@ Problem: Transitions create visual overlap between scenes, but audio continues f
|
|
|
526
523
|
|
|
527
524
|
[Paste the VERBATIM scene section from video-plan.md \u2014 everything between two ## Scene headers]
|
|
528
525
|
|
|
526
|
+
## Audio Timing (from Timing Manifest)
|
|
527
|
+
|
|
528
|
+
**Scene duration:** [Y.Ys] ([M] frames at 30fps)
|
|
529
|
+
**Actual VO duration:** [X.Xs] ([N] frames at 30fps)
|
|
530
|
+
**VO text:** "[exact text from Voiceover Script section]"
|
|
531
|
+
**Phase breakdown (use these exact frame numbers):**
|
|
532
|
+
- Phase 1 ENTRANCE: frames 0\u201310 (before VO starts)
|
|
533
|
+
- Phase 2 CONTENT+VO: frames 10\u2013[10+N] (progressive reveals synced to VO)
|
|
534
|
+
- Phase 3 EMPHASIS: frames [10+N]\u2013[M-15] (post-VO highlight, camera push)
|
|
535
|
+
- Phase 4 TRANSITION PREP: frames [M-15]\u2013[M] (exit animation)
|
|
536
|
+
|
|
537
|
+
## Rhythm & Energy (from Timing Manifest)
|
|
538
|
+
|
|
539
|
+
**BPM:** [e.g. 115]
|
|
540
|
+
**This scene's rhythm:** [e.g. staccato / legato / accelerando / decelerando]
|
|
541
|
+
**Energy level:** [e.g. low / building / peak / resolving]
|
|
542
|
+
**Music cue:** [e.g. "Accelerando @00:11.0" or "none"]
|
|
543
|
+
|
|
544
|
+
Use rhythm to set spring configs and stagger delays \u2014 see Step 2b vocabulary table.
|
|
545
|
+
|
|
546
|
+
**NOTE:** Audio (VO + music) is handled in Main.tsx. Your scene is VISUAL ONLY. Do NOT add \`<Audio>\` for voiceover or music. SFX \`<Audio>\` for short sound effects is OK.
|
|
547
|
+
|
|
529
548
|
## Output
|
|
530
549
|
|
|
531
550
|
Write the scene to: src/scenes/Scene[NN][Name].tsx
|
|
@@ -540,7 +559,23 @@ Write the scene to: src/scenes/Scene[NN][Name].tsx
|
|
|
540
559
|
|
|
541
560
|
**Wait for all agents to complete** before proceeding.
|
|
542
561
|
|
|
543
|
-
### 9d.
|
|
562
|
+
### 9d. Continuity Check
|
|
563
|
+
|
|
564
|
+
After all scene builders complete, spawn the \`video-continuity-editor\` agent to catch cross-scene issues:
|
|
565
|
+
|
|
566
|
+
\`\`\`
|
|
567
|
+
Task('video-continuity-editor') with prompt:
|
|
568
|
+
"Check cross-scene consistency.
|
|
569
|
+
Plan: video-plan.md
|
|
570
|
+
Manifest: timing-manifest.md
|
|
571
|
+
Branding: branding.json"
|
|
572
|
+
\`\`\`
|
|
573
|
+
|
|
574
|
+
This checks transition flow (Scene N out = Scene N+1 in), timing alignment (Main.tsx matches manifest), color grade progression, import consistency, and brand coherence.
|
|
575
|
+
|
|
576
|
+
If score is below 85%, fix the flagged issues before rendering.
|
|
577
|
+
|
|
578
|
+
### 9e. Verify and Render
|
|
544
579
|
|
|
545
580
|
After all agents finish:
|
|
546
581
|
|
|
@@ -614,13 +649,13 @@ Add \`--concurrency=2\` to reduce memory usage, or render scenes separately.
|
|
|
614
649
|
- Preview: \`npx remotion studio\` or \`npm run dev\` (if configured)
|
|
615
650
|
- Type check: \`npx tsc --noEmit\`
|
|
616
651
|
- Render: \`npx remotion render Main out/video.mp4\`
|
|
617
|
-
`}function
|
|
618
|
-
name: ${
|
|
652
|
+
`}function ft(t){let{name:n,cmd:e}=t;return`---
|
|
653
|
+
name: ${n}-presentation
|
|
619
654
|
description: Use when user asks to create presentations, slides, decks, pitch decks.
|
|
620
655
|
Researches the topic, gathers context, and generates AI-powered presentations via CLI.
|
|
621
656
|
---
|
|
622
657
|
|
|
623
|
-
# ${
|
|
658
|
+
# ${n} Presentation CLI
|
|
624
659
|
|
|
625
660
|
---
|
|
626
661
|
|
|
@@ -794,12 +829,12 @@ ${e} import ./presentation.zip
|
|
|
794
829
|
- **Auth fails** \u2192 \`${e} logout && ${e} login\` (browser required)
|
|
795
830
|
- **"Context required"** \u2192 add \`--context\`, \`--file\`, or \`--context-file\`
|
|
796
831
|
- **Rate limit (429)** \u2192 check plan with \`${e} whoami\`
|
|
797
|
-
`}function
|
|
798
|
-
name: ${
|
|
832
|
+
`}function Ie(t){let{name:n,cmd:e}=t;return`---
|
|
833
|
+
name: ${n}-tts
|
|
799
834
|
description: Text-to-speech generation. Convert text to natural speech with multiple providers (Gemini, ElevenLabs, OpenAI) and voices. Use for voiceovers, narration, accessibility.
|
|
800
835
|
---
|
|
801
836
|
|
|
802
|
-
# ${
|
|
837
|
+
# ${n} TTS (Text-to-Speech)
|
|
803
838
|
|
|
804
839
|
Convert text to natural speech with multiple providers and voices.
|
|
805
840
|
|
|
@@ -993,12 +1028,12 @@ ${e} tts generate \\
|
|
|
993
1028
|
2. **Voice matching** - Match voice style to content: Puck for upbeat, Charon for serious
|
|
994
1029
|
3. **Speed adjustment** - Use 0.9-0.95 for narration, 1.0-1.1 for casual
|
|
995
1030
|
4. **Batch processing** - Use \`video generate assets\` for multiple voiceovers
|
|
996
|
-
`}function
|
|
997
|
-
name: ${
|
|
1031
|
+
`}function Re(t){let{name:n,cmd:e}=t;return`---
|
|
1032
|
+
name: ${n}-music
|
|
998
1033
|
description: AI music generation from text prompts. Create background music, soundtracks, jingles for videos, presentations, podcasts. Supports ElevenLabs and Suno providers.
|
|
999
1034
|
---
|
|
1000
1035
|
|
|
1001
|
-
# ${
|
|
1036
|
+
# ${n} Music Generation
|
|
1002
1037
|
|
|
1003
1038
|
Generate AI music from text descriptions.
|
|
1004
1039
|
|
|
@@ -1133,12 +1168,12 @@ ${e} music generate -p "calm piano" -d 30 -f json
|
|
|
1133
1168
|
2. **Looping** - For seamless loops, mention "loopable" in prompt.
|
|
1134
1169
|
3. **Video sync** - Use \`video generate assets\` to auto-generate music for video projects.
|
|
1135
1170
|
4. **Volume mixing** - Background music should be 20-30% volume when mixed with voiceover.
|
|
1136
|
-
`}function
|
|
1137
|
-
name: ${
|
|
1171
|
+
`}function Ae(t){let{name:n,cmd:e}=t;return`---
|
|
1172
|
+
name: ${n}-sfx
|
|
1138
1173
|
description: AI sound effects generation from text descriptions. Create foley, ambient, UI sounds, impacts, whooshes for videos, games, apps. Uses ElevenLabs SFX.
|
|
1139
1174
|
---
|
|
1140
1175
|
|
|
1141
|
-
# ${
|
|
1176
|
+
# ${n} Sound Effects (SFX)
|
|
1142
1177
|
|
|
1143
1178
|
Generate AI sound effects from text descriptions.
|
|
1144
1179
|
|
|
@@ -1342,12 +1377,12 @@ ${e} sfx generate -t "click" -d 0.5 -f json
|
|
|
1342
1377
|
3. **Prompt influence** - Higher (0.7-1.0) = more literal, lower (0.1-0.3) = more creative.
|
|
1343
1378
|
4. **Descriptive prompts** - Add context: "door slam, heavy wooden door" > "door slam".
|
|
1344
1379
|
5. **Format** - Use WAV for editing, MP3 for final output.
|
|
1345
|
-
`}function
|
|
1346
|
-
name: ${
|
|
1380
|
+
`}function Oe(t){let{name:n,cmd:e}=t;return`---
|
|
1381
|
+
name: ${n}-mix
|
|
1347
1382
|
description: Audio mixing for video production. Combine video with voiceover and background music. Supports volume control, fade effects, and multiple audio tracks.
|
|
1348
1383
|
---
|
|
1349
1384
|
|
|
1350
|
-
# ${
|
|
1385
|
+
# ${n} Audio Mixing
|
|
1351
1386
|
|
|
1352
1387
|
Mix video with voiceover and background music.
|
|
1353
1388
|
|
|
@@ -1492,12 +1527,12 @@ ${e} mix status <request-id>
|
|
|
1492
1527
|
2. **Music selection** - Choose music that doesn't compete with voice frequency.
|
|
1493
1528
|
3. **Consistent levels** - Keep voice at 100%, adjust music only.
|
|
1494
1529
|
4. **File sizes** - Use \`--no-wait\` for large files to avoid timeout.
|
|
1495
|
-
`}function
|
|
1496
|
-
name: ${
|
|
1530
|
+
`}function Ee(t){let{name:n,cmd:e}=t;return`---
|
|
1531
|
+
name: ${n}-image
|
|
1497
1532
|
description: Stock image search and AI image generation. Find royalty-free images from multiple providers or generate custom images with AI. For videos, presentations, marketing.
|
|
1498
1533
|
---
|
|
1499
1534
|
|
|
1500
|
-
# ${
|
|
1535
|
+
# ${n} Image Commands
|
|
1501
1536
|
|
|
1502
1537
|
Search stock images and generate AI images.
|
|
1503
1538
|
|
|
@@ -1676,12 +1711,12 @@ Found 10 images for "mountain landscape"
|
|
|
1676
1711
|
3. **Size matters** - Use \`--size large\` for hero images
|
|
1677
1712
|
4. **JSON for automation** - Parse results programmatically
|
|
1678
1713
|
5. **Generation prompts** - Be detailed, describe style, lighting, composition
|
|
1679
|
-
`}function
|
|
1680
|
-
name: ${
|
|
1714
|
+
`}function Pe(t){let{name:n,cmd:e}=t;return`---
|
|
1715
|
+
name: ${n}-video-search
|
|
1681
1716
|
description: Stock video search. Find royalty-free video clips from multiple providers (Pexels, Pixabay). For b-roll, backgrounds, transitions in video production.
|
|
1682
1717
|
---
|
|
1683
1718
|
|
|
1684
|
-
# ${
|
|
1719
|
+
# ${n} Video Search
|
|
1685
1720
|
|
|
1686
1721
|
Search for stock video clips.
|
|
1687
1722
|
|
|
@@ -1851,12 +1886,12 @@ ${e} video find "city timelapse" --orientation landscape -n 10
|
|
|
1851
1886
|
3. **Preview first** - Use preview URLs to check quality
|
|
1852
1887
|
4. **License** - Verify license for commercial use
|
|
1853
1888
|
5. **JSON export** - Save searches for batch processing
|
|
1854
|
-
`}function
|
|
1855
|
-
name: ${
|
|
1889
|
+
`}function Fe(t){let{name:n,cmd:e}=t;return`---
|
|
1890
|
+
name: ${n}-scrape
|
|
1856
1891
|
description: Scrape any URL the user provides. Use when user says "check this site", "look at this URL", "use this website", "here's the link", or shares any URL for context. Supports websites, YouTube, Twitter/X.
|
|
1857
1892
|
---
|
|
1858
1893
|
|
|
1859
|
-
# ${
|
|
1894
|
+
# ${n} URL Scraping
|
|
1860
1895
|
|
|
1861
1896
|
Extract content from URLs for analysis and context.
|
|
1862
1897
|
|
|
@@ -2041,12 +2076,12 @@ ${e} scrape https://blog.example.com -f quiet | ${e} create "Blog Summary"
|
|
|
2041
2076
|
3. **Use JSON** - For programmatic processing of results
|
|
2042
2077
|
4. **Combine sources** - Scrape multiple URLs for comprehensive context
|
|
2043
2078
|
5. **Token count** - Check token usage for large documents
|
|
2044
|
-
`}function
|
|
2045
|
-
name: ${
|
|
2079
|
+
`}function Ne(t){let{name:n,cmd:e}=t;return`---
|
|
2080
|
+
name: ${n}-branding
|
|
2046
2081
|
description: Brand profile management. Extract branding from websites, manage saved brands, apply consistent styling to presentations.
|
|
2047
2082
|
---
|
|
2048
2083
|
|
|
2049
|
-
# ${
|
|
2084
|
+
# ${n} Branding Management
|
|
2050
2085
|
|
|
2051
2086
|
Manage brand profiles for consistent styling across presentations.
|
|
2052
2087
|
|
|
@@ -2160,12 +2195,12 @@ Saved Brands:
|
|
|
2160
2195
|
2. **Set default** - If you mostly work with one brand, set it as default
|
|
2161
2196
|
3. **Override** - Use \`--brand\` flag to override default for specific presentations
|
|
2162
2197
|
4. **Update** - Re-extract from URL to update brand colors/logos
|
|
2163
|
-
`}function
|
|
2164
|
-
name: ${
|
|
2198
|
+
`}function De(t){let{name:n,cmd:e}=t,o=n.toUpperCase().replace(/[^A-Z0-9]/g,"_");return`---
|
|
2199
|
+
name: ${n}-config
|
|
2165
2200
|
description: CLI configuration management. Set API keys, team IDs, custom API URLs. Verify authentication status.
|
|
2166
2201
|
---
|
|
2167
2202
|
|
|
2168
|
-
# ${
|
|
2203
|
+
# ${n} Configuration
|
|
2169
2204
|
|
|
2170
2205
|
Manage CLI settings and authentication.
|
|
2171
2206
|
|
|
@@ -2239,8 +2274,8 @@ ${e} config path
|
|
|
2239
2274
|
Environment variables override config file settings.
|
|
2240
2275
|
|
|
2241
2276
|
\`\`\`bash
|
|
2242
|
-
export ${
|
|
2243
|
-
export ${
|
|
2277
|
+
export ${o}_API_KEY="your-api-key"
|
|
2278
|
+
export ${o}_API_URL="https://custom-api.example.com"
|
|
2244
2279
|
\`\`\`
|
|
2245
2280
|
|
|
2246
2281
|
**Priority:** Environment variables > Config file
|
|
@@ -2297,7 +2332,7 @@ Opens browser for secure OAuth flow. Tokens are stored securely.
|
|
|
2297
2332
|
\`\`\`bash
|
|
2298
2333
|
${e} config set api-key <your-key>
|
|
2299
2334
|
# or
|
|
2300
|
-
export ${
|
|
2335
|
+
export ${o}_API_KEY="your-key"
|
|
2301
2336
|
\`\`\`
|
|
2302
2337
|
|
|
2303
2338
|
Get API key from your account settings.
|
|
@@ -2321,7 +2356,7 @@ ${e} config show --verify # Test API key
|
|
|
2321
2356
|
\`\`\`bash
|
|
2322
2357
|
${e} config set team-id <correct-team-id>
|
|
2323
2358
|
\`\`\`
|
|
2324
|
-
`}var
|
|
2359
|
+
`}var Bt={name:"video-ref-website-assets",description:"Extract maximum assets from websites for video production.",render:t=>`---
|
|
2325
2360
|
name: ${t.name}-video-ref-website-assets
|
|
2326
2361
|
description: Extract maximum assets from websites for video production.
|
|
2327
2362
|
---
|
|
@@ -2399,7 +2434,7 @@ agent-browser screenshot --path ./screenshots/hero.png
|
|
|
2399
2434
|
**Images 403/404** - may need referrer header: \`curl -e <site-url> -O <image-url>\`
|
|
2400
2435
|
|
|
2401
2436
|
**Lazy-loaded images** - use agent-browser to scroll and capture, or inspect network tab for actual URLs.
|
|
2402
|
-
`};var
|
|
2437
|
+
`};var Gt={name:"video-ref-brief-guide",description:"How to write video brief for server-side plan generation.",render:t=>`---
|
|
2403
2438
|
name: ${t.name}-video-ref-brief-guide
|
|
2404
2439
|
description: How to write video brief for server-side plan generation.
|
|
2405
2440
|
---
|
|
@@ -2462,7 +2497,7 @@ These will be rebuilt as lightweight animated versions for Remotion. Focus on vi
|
|
|
2462
2497
|
---
|
|
2463
2498
|
|
|
2464
2499
|
**IMPORTANT:** The brief contains ONLY the 6 sections above. Do NOT add "Suggested Scenes", "Scene Breakdown", or any other sections \u2014 scene planning happens server-side via \`video generate plan\`.
|
|
2465
|
-
`};var
|
|
2500
|
+
`};var Wt={name:"video-ref-source-scraping",description:"Extract content from various sources (YouTube, websites, Twitter).",render:t=>`---
|
|
2466
2501
|
name: ${t.name}-video-ref-source-scraping
|
|
2467
2502
|
description: Extract content from various sources (YouTube, websites, Twitter).
|
|
2468
2503
|
---
|
|
@@ -2498,11 +2533,12 @@ Extracts: tweet text, author info, media URLs
|
|
|
2498
2533
|
---
|
|
2499
2534
|
|
|
2500
2535
|
All scraped content is returned in markdown format.
|
|
2501
|
-
`};import{mkdirSync as
|
|
2536
|
+
`};import{mkdirSync as Jt,writeFileSync as Yt,existsSync as Ke,rmSync as wo}from"fs";import{join as Q,resolve as gt,relative as gi}from"path";import{homedir as ht}from"os";var me=[{name:"Claude Code",dir:".claude"},{name:"Cursor",dir:".cursor"},{name:"Codex",dir:".codex"},{name:"OpenCode",dir:".opencode"},{name:"Windsurf",dir:".windsurf"},{name:"Agent",dir:".agent"}];function hi(t,n){let e=gt(t),o=gt(t,n);if(gi(e,o).startsWith("..")||gt(o)!==o.replace(/\.\./g,""))throw new Error(`Invalid path: "${n}" would escape base directory`);return o}function xo(t,n,e){let o=Q(t,"SKILL.md");if(Jt(t,{recursive:!0}),Yt(o,n,"utf-8"),e&&e.length>0){let r=Q(t,"references");Jt(r,{recursive:!0});for(let i of e){let a=Q(r,`${i.name}.md`);Yt(a,i.content,"utf-8")}}}function yt(t,n,e={},o){let r={installed:[],skipped:[],errors:[]},i=e.local?process.cwd():ht();if(e.dir)try{let a=gt(e.dir),s=hi(a,Q("skills",t));xo(s,n,o),r.installed.push(e.dir)}catch(a){r.errors.push(`${e.dir}: ${a instanceof Error?a.message:String(a)}`)}else for(let a of me){let s=Q(i,a.dir),l=Q(s,"skills",t),m=Q(l,"SKILL.md");if(Ke(s)){if(Ke(m)&&!e.force){r.skipped.push(a.name);continue}try{xo(l,n,o),r.installed.push(a.name)}catch(p){r.errors.push(`${a.name}: ${p instanceof Error?p.message:String(p)}`)}}}return r}function bt(t,n={}){let e={removed:[],errors:[]},o=n.local?process.cwd():ht();for(let r of me){let i=Q(o,r.dir,"skills",t);if(Ke(i))try{wo(i,{recursive:!0}),e.removed.push(r.name)}catch(a){e.errors.push(`${r.name}: ${a instanceof Error?a.message:String(a)}`)}}return e}function So(){return me.map(t=>t.name)}function To(t,n,e={}){let o={installed:[],skipped:[],errors:[]},r=ht(),i=me.filter(a=>a.dir===".claude");for(let a of i){let s=Q(r,a.dir,"agents"),l=Q(s,`${t}.md`);if(Ke(l)&&!e.force){o.skipped.push(a.name);continue}try{Jt(s,{recursive:!0}),Yt(l,n,"utf-8"),o.installed.push(a.name)}catch(m){o.errors.push(`${a.name}: ${m instanceof Error?m.message:String(m)}`)}}return o}function Co(t){let n={removed:[],errors:[]},e=ht(),o=me.filter(r=>r.dir===".claude");for(let r of o){let i=Q(e,r.dir,"agents",`${t}.md`);if(Ke(i))try{wo(i),n.removed.push(r.name)}catch(a){n.errors.push(`${r.name}: ${a instanceof Error?a.message:String(a)}`)}}return n}function ko(){return`---
|
|
2502
2537
|
name: video-scene-reviewer
|
|
2503
|
-
description: Strict QA reviewer for Remotion
|
|
2538
|
+
description: Strict QA reviewer for a single Remotion scene. Compares code against the video plan checking ColorGrading, transitions, icons, logos, spring physics, idle states, VO-sync, and phase structure. Returns a pass/fail checklist.
|
|
2504
2539
|
tools: Read, Glob
|
|
2505
2540
|
model: haiku
|
|
2541
|
+
permissionMode: plan
|
|
2506
2542
|
---
|
|
2507
2543
|
|
|
2508
2544
|
You are a strict QA reviewer for Remotion video projects. Compare every scene against the video plan. Find everything that was simplified, skipped, or doesn't match.
|
|
@@ -2533,6 +2569,11 @@ For each scene, check whether the CODE matches what the PLAN specified:
|
|
|
2533
2569
|
- **Animation quality** \u2014 are there 4 phases (ENTRANCE, CONTENT REVEAL, EMPHASIS, TRANSITION PREP) with real code in each? Empty phases = FAIL. Single static composition = CRITICAL FAIL
|
|
2534
2570
|
- **Visual complexity** \u2014 is this a SCENE or a SLIDE? Count distinct visual events (entrance, reveal, emphasis, morph, color shift). Minimum 3 for a 5s scene
|
|
2535
2571
|
- **Voiceover-sync stagger** \u2014 if a scene has multiple content elements (cards, bullets, features), do they enter with staggered timing or all at once? All elements appearing on the same frame while voiceover narrates them one by one = FAIL. Look for stagger delays (e.g. \`frame - index * N\`) or sequential \`<Sequence>\` offsets. Elements that enter together in a batch = "dump" = bad
|
|
2572
|
+
- **Audio placement** \u2014 scenes must NOT contain \`<Audio>\` tags for voiceover or background music files. VO and music are handled in Main.tsx via VoiceoverSequence and DuckedMusic. Only exception: short SFX \`<Audio>\` tags tied to visual events (whoosh, click, etc.) are acceptable. If a scene imports and uses \`<Audio>\` with a VO file (e.g. \`scene-1.wav\`) or music file \u2192 CRITICAL FAIL
|
|
2573
|
+
- **Phase timing alignment** \u2014 if the scene has timing constants (VO_START, VO_DURATION, SCENE_FRAMES), check that the 4 phase boundaries roughly match. Phase 2 (content reveal) should span approximately the VO duration. If phase constants are missing entirely or frame numbers are clearly wrong (e.g. VO_DURATION = 90 but code uses phase boundary at frame 40) \u2192 FAIL
|
|
2574
|
+
- **Spring physics** \u2014 plan writes \\\`Spring(stiffness, damping)\\\`. Code must use matching values in \\\`spring({ config: { stiffness, damping } })\\\`. If plan says Spring(100, 15) but code uses \\\`{ damping: 20 }\\\` with default stiffness = FAIL. Close values (off by 1-2) = WEAK, not FAIL
|
|
2575
|
+
- **Idle state fidelity** \u2014 if the plan's Idle State says "float \xB14px over 4s", the code must implement that exact range and period. Missing idle animation = FAIL. Generic float without matching the plan's numbers = WEAK
|
|
2576
|
+
- **Hook type** (Scene 1 only) \u2014 if plan implies a hook type (cold open, stat hook, pattern interrupt, question hook), code structure must match. A "stat hook" without an animated counter = FAIL. A "cold open" that starts with blank screen + title = FAIL
|
|
2536
2577
|
|
|
2537
2578
|
## Output Format
|
|
2538
2579
|
|
|
@@ -2553,8 +2594,13 @@ Duration: 8s | Lines: 275
|
|
|
2553
2594
|
Animation | 4 phases required | 2 phases with code | \u2717 FAIL
|
|
2554
2595
|
Complexity | 5+ events for 8s | 3 found | ~ WEAK
|
|
2555
2596
|
VO-sync stagger | 4 cards staggered | all enter frame 0 | \u2717 FAIL
|
|
2597
|
+
Audio placement | VO in Main.tsx only | no <Audio> for VO/music | \u2713 PASS
|
|
2598
|
+
Phase timing | VO=156f, Scene=210f | constants match | \u2713 PASS
|
|
2599
|
+
Spring physics | Spring(100, 15) | { stiffness: 100, d: 15 }| \u2713 PASS
|
|
2600
|
+
Idle state | float \xB14px/4s | sin() \xB14px/4s | \u2713 PASS
|
|
2601
|
+
Hook type | cold open | hero visual at frame 0 | \u2713 PASS
|
|
2556
2602
|
|
|
2557
|
-
Score:
|
|
2603
|
+
Score: 7/14 \u2014 NEEDS REWORK
|
|
2558
2604
|
|
|
2559
2605
|
Fixes:
|
|
2560
2606
|
1. [CRITICAL] Wrong icons \u2014 use Bot, BrainCircuit not GitBranch
|
|
@@ -2584,11 +2630,16 @@ Priority fixes:
|
|
|
2584
2630
|
- Scene under 80 lines with 5+ seconds duration = flag as too simple
|
|
2585
2631
|
- Be specific in fixes: exact icon name, exact component, exact file
|
|
2586
2632
|
- 85% pass rate minimum. Below = not ready for render
|
|
2587
|
-
`}function
|
|
2633
|
+
`}function $o(){return`---
|
|
2588
2634
|
name: video-scene-builder
|
|
2589
|
-
description: Builds a single Remotion video scene from a plan section.
|
|
2590
|
-
tools: Read, Write, Edit, Glob, Grep,
|
|
2635
|
+
description: Builds a single Remotion video scene (.tsx) from a video plan section. Receives global context, scene spec, audio timing, and rhythm cues. Writes the scene file, spawns a reviewer, and iterates until 85%+ quality. One agent per scene.
|
|
2636
|
+
tools: Read, Write, Edit, Glob, Grep, Task
|
|
2591
2637
|
model: sonnet
|
|
2638
|
+
skills:
|
|
2639
|
+
- frontend-design
|
|
2640
|
+
- remotion-best-practices
|
|
2641
|
+
- remotion-scene-craft
|
|
2642
|
+
permissionMode: acceptEdits
|
|
2592
2643
|
---
|
|
2593
2644
|
|
|
2594
2645
|
You are a **scene builder** for a Remotion video project. You receive the plan for ONE scene + global context, and your job is to build it as a high-quality .tsx file.
|
|
@@ -2598,193 +2649,664 @@ You are a **scene builder** for a Remotion video project. You receive the plan f
|
|
|
2598
2649
|
The main AI passes you:
|
|
2599
2650
|
1. **Global context** \u2014 video overview, brand colors, font setup, logo path, component inventory
|
|
2600
2651
|
2. **Scene section** \u2014 verbatim from video-plan.md (everything for this scene)
|
|
2601
|
-
3. **
|
|
2602
|
-
4. **
|
|
2652
|
+
3. **Audio timing** \u2014 actual VO duration (from TTS), scene total duration (adjusted), frame ranges for each animation phase, and the exact VO text
|
|
2653
|
+
4. **Rhythm & energy** \u2014 BPM, this scene's rhythm pattern (staccato/legato/etc.), energy level, music cues
|
|
2654
|
+
5. **Output file path** \u2014 e.g. \`src/scenes/Scene02UINav.tsx\`
|
|
2655
|
+
6. **Adjacent transitions** \u2014 previous scene's transition out, next scene's transition in (for continuity)
|
|
2603
2656
|
|
|
2604
2657
|
## Process
|
|
2605
2658
|
|
|
2606
|
-
|
|
2659
|
+
Skills (frontend-design, remotion-best-practices, remotion-scene-craft) are preloaded into your context. Use them as reference for all coding decisions.
|
|
2660
|
+
|
|
2661
|
+
### Step 1: Read Template Components (if needed)
|
|
2607
2662
|
|
|
2608
|
-
|
|
2663
|
+
If the plan references specific template components (transitions, effects), read them to understand their props:
|
|
2609
2664
|
|
|
2610
2665
|
\\\`\\\`\\\`
|
|
2611
|
-
|
|
2666
|
+
Read('src/components/transitions/WhipPan.tsx')
|
|
2667
|
+
Read('src/components/effects/ColorGrading.tsx')
|
|
2612
2668
|
\\\`\\\`\\\`
|
|
2613
2669
|
|
|
2614
|
-
|
|
2670
|
+
Only read components that your scene actually uses. The component inventory from global context tells you import paths and props \u2014 read the source only if you need more detail.
|
|
2615
2671
|
|
|
2616
|
-
### Step 2:
|
|
2672
|
+
### Step 2: Build the Scene
|
|
2617
2673
|
|
|
2618
|
-
|
|
2674
|
+
Write the .tsx file to the output path.
|
|
2675
|
+
|
|
2676
|
+
Follow the **remotion-scene-craft** skill precisely:
|
|
2677
|
+
- Use the **4-phase architecture** (Section 3) with constants from Audio Timing
|
|
2678
|
+
- Map plan vocabulary using the **decoder** (Section 2) \u2014 spring values, rhythm, hook types, idle states
|
|
2679
|
+
- Sync content reveals to VO using **VO-sync patterns** (Section 4)
|
|
2680
|
+
- Use **animation recipes** (Section 5) for cards, counters, particles, glass, gradients
|
|
2681
|
+
- Meet the **quality floor** (Section 6) \u2014 100+ lines, real icons, real logos, template transitions
|
|
2682
|
+
|
|
2683
|
+
Build real visual components, not divs with text:
|
|
2684
|
+
- Data viz: animated counters, progress bars, percentage rings
|
|
2685
|
+
- UI mockups: windows, toolbars, buttons, animated cursor
|
|
2686
|
+
- Diagrams: nodes with animated connection lines
|
|
2687
|
+
- Typography: FadeInWords, scale slams, gradient text fills
|
|
2688
|
+
- Backgrounds: animated gradient meshes, particle fields
|
|
2689
|
+
|
|
2690
|
+
All technologies available: CSS/HTML, Canvas 2D, Three.js/@react-three/fiber, WebGL shaders, SVG animations.
|
|
2691
|
+
|
|
2692
|
+
### Step 3: Review
|
|
2693
|
+
|
|
2694
|
+
After writing the scene, spawn the \\\`video-scene-reviewer\\\` agent:
|
|
2619
2695
|
|
|
2620
2696
|
\\\`\\\`\\\`
|
|
2621
|
-
|
|
2622
|
-
|
|
2697
|
+
Task('video-scene-reviewer') with prompt:
|
|
2698
|
+
"Review ONLY the scene at [output-path]. The plan section for this scene is: [scene section]. branding.json may exist in the project root."
|
|
2623
2699
|
\\\`\\\`\\\`
|
|
2624
2700
|
|
|
2625
|
-
|
|
2701
|
+
### Step 4: Fix Issues
|
|
2626
2702
|
|
|
2627
|
-
|
|
2703
|
+
Read the reviewer's report. Fix every issue:
|
|
2704
|
+
- CRITICAL first (wrong icons, missing logos, text-as-icons)
|
|
2705
|
+
- Then FAIL (missing transitions, empty phases, wrong colors)
|
|
2706
|
+
- Then WEAK (low complexity, missing idle animations)
|
|
2628
2707
|
|
|
2629
|
-
|
|
2708
|
+
### Step 5: Re-review if Needed
|
|
2630
2709
|
|
|
2631
|
-
|
|
2710
|
+
If the reviewer score was below 85%, re-run the reviewer after fixes. Repeat until passing.
|
|
2632
2711
|
|
|
2633
|
-
|
|
2712
|
+
### Step 6: Done
|
|
2634
2713
|
|
|
2635
|
-
|
|
2636
|
-
// === PHASE 1: ENTRANCE (frames 0-30) ===
|
|
2637
|
-
// Elements enter with staggered spring animations
|
|
2714
|
+
When the reviewer passes (85%+), output:
|
|
2638
2715
|
|
|
2639
|
-
|
|
2640
|
-
|
|
2716
|
+
\\\`\\\`\\\`
|
|
2717
|
+
Scene [name] built: [X] lines, score [Y]%, [Z] visual events
|
|
2718
|
+
\\\`\\\`\\\`
|
|
2719
|
+
|
|
2720
|
+
## What You Do NOT Do
|
|
2721
|
+
|
|
2722
|
+
- Don't create Main.tsx or other scenes \u2014 only YOUR assigned scene
|
|
2723
|
+
- Don't modify component templates \u2014 use them as-is
|
|
2724
|
+
- Don't download assets \u2014 the main AI already did that
|
|
2725
|
+
- Don't run render commands \u2014 the main AI handles that
|
|
2726
|
+
- Don't modify video-plan.md \u2014 it's read-only to you
|
|
2727
|
+
`}function Io(){return`---
|
|
2728
|
+
name: video-audio-director
|
|
2729
|
+
description: Generates all audio assets (voiceover TTS, background music, sound effects) and builds the timing manifest that reconciles planned vs actual durations. One agent per video, runs before scene building.
|
|
2730
|
+
tools: Read, Bash, Write
|
|
2731
|
+
model: sonnet
|
|
2732
|
+
permissionMode: acceptEdits
|
|
2733
|
+
---
|
|
2734
|
+
|
|
2735
|
+
You are the **Audio Director** for a video production. You receive the video plan and CLI command, and your job is to generate all audio assets and produce a Timing Manifest.
|
|
2736
|
+
|
|
2737
|
+
## Your Input (provided in the Task prompt)
|
|
2641
2738
|
|
|
2642
|
-
|
|
2643
|
-
|
|
2739
|
+
1. **CLI command** \u2014 the command name (e.g., \\\`mf\\\` or \\\`conceptcraft\\\`)
|
|
2740
|
+
2. **Video plan path** \u2014 path to video-plan.md
|
|
2741
|
+
3. **Output directory** \u2014 where to write audio files (e.g., ./public/audio/)
|
|
2742
|
+
4. **Voice** \u2014 TTS voice (default: Puck)
|
|
2743
|
+
5. **Provider** \u2014 TTS provider (default: gemini)
|
|
2644
2744
|
|
|
2645
|
-
|
|
2646
|
-
|
|
2745
|
+
## Process
|
|
2746
|
+
|
|
2747
|
+
### Step 1: Parse the Plan
|
|
2748
|
+
|
|
2749
|
+
Read video-plan.md and extract:
|
|
2750
|
+
- **Per-scene VO scripts** from each \\\`### Voiceover Script\\\` section (block quote text only \u2014 no timestamps, no SFX descriptions)
|
|
2751
|
+
- **Music Direction** from Video Overview
|
|
2752
|
+
- **SFX descriptions** from Animation & Audio timestamps (distinct sounds)
|
|
2753
|
+
- **Scene durations** from each scene's \\\`**Duration:**\\\` field
|
|
2754
|
+
|
|
2755
|
+
### Step 2: Generate Voiceover
|
|
2756
|
+
|
|
2757
|
+
For each scene with VO text, generate TTS. **Run all in parallel:**
|
|
2758
|
+
|
|
2759
|
+
\\\`\\\`\\\`bash
|
|
2760
|
+
<cmd> tts generate --text "Exact text from VO section" --voice <voice> --output ./public/audio/scene-1.wav
|
|
2761
|
+
<cmd> tts generate --text "Exact text from scene 2 VO" --voice <voice> --output ./public/audio/scene-2.wav
|
|
2762
|
+
\\\`\\\`\\\`
|
|
2763
|
+
|
|
2764
|
+
**CRITICAL:** Capture the actual duration from each command's output (\\\`Duration: X.XXs\\\`). These actual durations override plan estimates.
|
|
2765
|
+
|
|
2766
|
+
For scenes with "No voiceover" \u2014 skip, no file needed.
|
|
2767
|
+
|
|
2768
|
+
**For ElevenLabs only:** Use batch mode for voice continuity:
|
|
2769
|
+
\\\`\\\`\\\`bash
|
|
2770
|
+
echo '{"scenes":[{"name":"s1","script":"..."},{"name":"s2","script":"..."}],"provider":"elevenlabs","voice":"Rachel"}' | <cmd> video generate assets --output ./public
|
|
2771
|
+
\\\`\\\`\\\`
|
|
2772
|
+
|
|
2773
|
+
### Step 3: Generate Music
|
|
2774
|
+
|
|
2775
|
+
Use Music Direction from the Video Overview (copy verbatim as prompt):
|
|
2776
|
+
|
|
2777
|
+
\\\`\\\`\\\`bash
|
|
2778
|
+
<cmd> music generate --prompt "copied verbatim from Music Direction" --duration <total_video_seconds + 5> --output ./public/audio/music.mp3
|
|
2647
2779
|
\\\`\\\`\\\`
|
|
2648
2780
|
|
|
2649
|
-
|
|
2781
|
+
Add 5s buffer \u2014 easier to trim than re-generate.
|
|
2650
2782
|
|
|
2651
|
-
|
|
2783
|
+
### Step 4: Generate SFX (if plan describes them)
|
|
2652
2784
|
|
|
2653
|
-
|
|
2654
|
-
2. **Real icons only** \u2014 Use Lucide React imports (\`import { Shield, Cloud, Brain } from 'lucide-react'\`). NEVER use text characters as icons ("\u2713", "O", "AI")
|
|
2655
|
-
3. **Real logos only** \u2014 Third-party brands use real logo files from \`public/\`. A Lucide icon is NOT a brand logo
|
|
2656
|
-
4. **Brand fonts** \u2014 Use the font setup from global context (either \`loadFont()\` or \`@font-face\`). Never substitute with "closest match"
|
|
2657
|
-
5. **ColorGrading** \u2014 If the plan specifies a preset, wrap content with \`<ColorGrading preset="..." />\`
|
|
2658
|
-
6. **Template transitions** \u2014 Use the exact components from the plan. Don't substitute WhipPan with \`slide()\`
|
|
2659
|
-
7. **Background motion** \u2014 Never a flat solid color. Use gradients, subtle drift, particles, noise
|
|
2660
|
-
8. **Idle animations** \u2014 After elements enter, they must move: float, breathe, pulse, drift
|
|
2661
|
-
9. **SFX** \u2014 If SFX files exist in \`public/audio/\`, use \`<Audio src={staticFile('audio/...')} />\` at correct frames
|
|
2662
|
-
10. **REVEAL WITH VOICEOVER \u2014 NEVER dump all elements at once.** If a scene has 4 cards, 3 bullet points, or a list of features \u2014 they MUST appear progressively, timed to the voiceover. The viewer hears "First, security" \u2192 the security card appears. They hear "Then, speed" \u2192 the speed card appears. Dumping all cards on frame 1 while the voiceover slowly explains each one looks broken and amateur. **Rule: each distinct content element enters when the voiceover mentions it, not before.** Use the Animation & Audio timestamps from the plan to determine stagger timing. If the plan says 4 cards in an 8-second scene, space entrances ~1.5-2s apart across the voiceover duration. Calculate: \`const cardDelay = (index: number) => spring({ frame: frame - (voStartFrame + index * staggerFrames), fps, config: { damping: 12 } })\`
|
|
2785
|
+
Extract distinct sound effects from Animation & Audio timestamps. Skip generic sounds that transitions handle natively.
|
|
2663
2786
|
|
|
2664
|
-
|
|
2787
|
+
\\\`\\\`\\\`bash
|
|
2788
|
+
<cmd> sfx generate --text "digital snap, harsh electronic break" --duration 1 --output ./public/audio/snap.mp3
|
|
2789
|
+
<cmd> sfx generate --text "massive airy whoosh, sweeping" --duration 1 --output ./public/audio/whoosh.mp3
|
|
2790
|
+
\\\`\\\`\\\`
|
|
2791
|
+
|
|
2792
|
+
### Step 5: Build Timing Manifest
|
|
2793
|
+
|
|
2794
|
+
Reconcile planned vs actual VO durations. This manifest is the **source of truth** for Main.tsx and scene builders.
|
|
2795
|
+
|
|
2796
|
+
**For each scene:**
|
|
2797
|
+
1. Planned VO duration from plan's \\\`Estimated VO duration\\\`
|
|
2798
|
+
2. Actual TTS duration from Step 2
|
|
2799
|
+
3. If actual differs by >0.5s \u2192 adjust: \\\`scene_duration = MAX(planned, actual_VO + 1.5s padding)\\\`
|
|
2800
|
+
4. Never shrink a scene below 3s
|
|
2801
|
+
|
|
2802
|
+
**Calculate absolute frame positions:**
|
|
2803
|
+
- FPS = 30, T_TRANSITION = 15 frames (0.5s overlap)
|
|
2804
|
+
- Scene N start frame = sum(prev durations) - N \xD7 T_TRANSITION
|
|
2805
|
+
- VO starts ~10 frames (0.33s) into each scene (after entrance)
|
|
2806
|
+
- VO duration in frames = Math.round(actual_seconds \xD7 30)
|
|
2807
|
+
|
|
2808
|
+
**Write \\\`timing-manifest.md\\\`:**
|
|
2809
|
+
|
|
2810
|
+
\\\`\\\`\\\`
|
|
2811
|
+
=== TIMING MANIFEST ===
|
|
2812
|
+
FPS: 30 | T_TRANSITION: 15 frames (0.5s)
|
|
2813
|
+
|
|
2814
|
+
Scene 1: "Hook Title"
|
|
2815
|
+
Duration: 4.0s \u2192 4.0s (120 frames) [no change]
|
|
2816
|
+
VO: planned 2.0s | actual 2.3s | delta +0.3s
|
|
2817
|
+
VO absolute: from=10, durationInFrames=69
|
|
2818
|
+
Audio: ./public/audio/scene-1.wav
|
|
2819
|
+
|
|
2820
|
+
Scene 2: "Feature Overview"
|
|
2821
|
+
Duration: 6.0s \u2192 7.0s (210 frames) [EXTENDED +1.0s]
|
|
2822
|
+
VO: planned 4.0s | actual 5.2s | delta +1.2s
|
|
2823
|
+
VO absolute: from=115, durationInFrames=156
|
|
2824
|
+
Audio: ./public/audio/scene-2.wav
|
|
2825
|
+
|
|
2826
|
+
[... all scenes ...]
|
|
2827
|
+
|
|
2828
|
+
TOTAL FRAMES: [sum of adjusted durations - (N-1) \xD7 15]
|
|
2829
|
+
TOTAL DURATION: [TOTAL_FRAMES / 30]s
|
|
2830
|
+
Music: ./public/audio/music.mp3 (needs [TOTAL_DURATION]s)
|
|
2831
|
+
SFX: [list of SFX files generated]
|
|
2832
|
+
=== END MANIFEST ===
|
|
2833
|
+
\\\`\\\`\\\`
|
|
2834
|
+
|
|
2835
|
+
**Extract rhythm & energy cues from the plan:**
|
|
2836
|
+
|
|
2837
|
+
Read Animation & Audio timestamps for rhythm markers and Music Direction for BPM. Append to the manifest:
|
|
2838
|
+
|
|
2839
|
+
\\\`\\\`\\\`
|
|
2840
|
+
=== RHYTHM & ENERGY CUES ===
|
|
2841
|
+
BPM: [from Music Direction, e.g. 115]
|
|
2842
|
+
Energy curve: [e.g. "ambient \u2192 driving at S3 \u2192 resolve S5"]
|
|
2843
|
+
|
|
2844
|
+
Scene 1: rhythm=legato | energy=low | music=ambient pads
|
|
2845
|
+
Scene 2: rhythm=legato | energy=building | cue="Accelerando @00:11.0"
|
|
2846
|
+
Scene 3: rhythm=staccato | energy=peak | cue="bass DROP @00:13.0"
|
|
2847
|
+
Scene 4: rhythm=decelerando | energy=resolving | cue="Decelerando @00:24.0"
|
|
2848
|
+
Scene 5: rhythm=legato | energy=low | music=piano sustain
|
|
2849
|
+
=== END CUES ===
|
|
2850
|
+
\\\`\\\`\\\`
|
|
2665
2851
|
|
|
2666
|
-
|
|
2667
|
-
|
|
2852
|
+
Scene builders use these: "staccato" \u2192 fast springs + tight stagger. "Accelerando" cue at a timestamp \u2192 animations speed up at that moment. BPM \u2192 \\\`useBeatSync\\\` alignment.
|
|
2853
|
+
|
|
2854
|
+
**If music is shorter than total duration**, re-generate with correct \\\`--duration\\\`.
|
|
2855
|
+
|
|
2856
|
+
### Step 6: Done
|
|
2857
|
+
|
|
2858
|
+
Output a summary:
|
|
2859
|
+
|
|
2860
|
+
\\\`\\\`\\\`
|
|
2861
|
+
=== AUDIO DIRECTOR COMPLETE ===
|
|
2862
|
+
VO: [count] files ([total VO seconds]s)
|
|
2863
|
+
Music: [path] ([duration]s)
|
|
2864
|
+
SFX: [count] files
|
|
2865
|
+
Timing adjustments: [count] scenes adjusted
|
|
2866
|
+
Total video: [X]s ([Y] frames)
|
|
2867
|
+
Manifest: timing-manifest.md
|
|
2868
|
+
=== END ===
|
|
2869
|
+
\\\`\\\`\\\`
|
|
2870
|
+
|
|
2871
|
+
## What You Do NOT Do
|
|
2872
|
+
|
|
2873
|
+
- Don't build Remotion scenes or write .tsx files
|
|
2874
|
+
- Don't modify video-plan.md \u2014 it's read-only
|
|
2875
|
+
- Don't make creative decisions about visuals
|
|
2876
|
+
- Don't choose voices \u2014 use the voice from your input
|
|
2877
|
+
- Don't skip the timing manifest \u2014 it's the critical handoff to scene builders
|
|
2878
|
+
`}function Ro(){return`---
|
|
2879
|
+
name: video-continuity-editor
|
|
2880
|
+
description: Post-build cross-scene QA. Checks transition continuity, color grade flow, timing alignment between Main.tsx and manifest, import consistency, brand coherence, rhythm flow, and music cue alignment. Runs after all scenes are built.
|
|
2881
|
+
tools: Read, Glob, Grep
|
|
2882
|
+
model: haiku
|
|
2883
|
+
permissionMode: plan
|
|
2884
|
+
---
|
|
2885
|
+
|
|
2886
|
+
You are a **Continuity Editor** for a Remotion video project. Individual scene reviewers check per-scene quality. YOUR job is **cross-scene consistency** \u2014 things only visible when you look at all scenes together.
|
|
2887
|
+
|
|
2888
|
+
## Your Input (provided in the Task prompt)
|
|
2889
|
+
|
|
2890
|
+
1. **Video plan path** \u2014 video-plan.md
|
|
2891
|
+
2. **Timing manifest path** \u2014 timing-manifest.md
|
|
2892
|
+
3. **Branding path** \u2014 branding.json (may not exist)
|
|
2893
|
+
|
|
2894
|
+
## Process
|
|
2895
|
+
|
|
2896
|
+
1. Read video-plan.md, timing-manifest.md, and branding.json (if exists)
|
|
2897
|
+
2. Read src/Main.tsx and src/Root.tsx
|
|
2898
|
+
3. Glob \\\`src/scenes/Scene*.tsx\\\` and read ALL scene files
|
|
2899
|
+
4. Run the checks below
|
|
2900
|
+
5. Output report
|
|
2901
|
+
|
|
2902
|
+
## Checks
|
|
2903
|
+
|
|
2904
|
+
### 1. Transition Continuity
|
|
2905
|
+
Scene N's **Transition Out** must be compatible with Scene N+1's **Transition In**:
|
|
2906
|
+
- Plan says Scene 2 out = "WhipPan right" \u2192 Scene 3 must handle WhipPan entrance
|
|
2907
|
+
- Mismatch between plan's transition and code's actual component \u2192 FAIL
|
|
2908
|
+
|
|
2909
|
+
### 2. Color Grade Flow
|
|
2910
|
+
Check ColorGrading presets across all scenes:
|
|
2911
|
+
- All scenes identical grade when plan specified variation \u2192 WARN
|
|
2912
|
+
- Jarring adjacent grades without narrative justification \u2192 WARN
|
|
2913
|
+
|
|
2914
|
+
### 3. Timing Alignment
|
|
2915
|
+
Compare Main.tsx against timing-manifest.md:
|
|
2916
|
+
- \\\`SCENE_DURATIONS\\\` array must match manifest frame counts
|
|
2917
|
+
- \\\`TOTAL_FRAMES\\\` calculation must be correct: sum - (N-1) \xD7 T_TRANSITION
|
|
2918
|
+
- \\\`voSegments\\\` from/durationInFrames must match manifest
|
|
2919
|
+
- Scene count in Main.tsx must match manifest
|
|
2920
|
+
|
|
2921
|
+
### 4. Import Consistency
|
|
2922
|
+
Check all scenes use consistent imports:
|
|
2923
|
+
- Same transition component \u2192 same import path everywhere
|
|
2924
|
+
- Brand fonts \u2192 same import method (loadFont OR @font-face, not mixed)
|
|
2925
|
+
- No conflicting component names
|
|
2926
|
+
|
|
2927
|
+
### 5. Brand Consistency (if branding.json exists)
|
|
2928
|
+
- Brand primary color used across scenes (check hex values)
|
|
2929
|
+
- Logo appears where plan specifies
|
|
2930
|
+
- Brand font family consistent (not approximated differently per scene)
|
|
2931
|
+
|
|
2932
|
+
### 6. Completeness
|
|
2933
|
+
- Scene file count matches plan's scene count
|
|
2934
|
+
- All scenes exported with names matching Main.tsx imports
|
|
2935
|
+
- No missing or extra scene files
|
|
2936
|
+
|
|
2937
|
+
### 7. Rhythm Flow
|
|
2938
|
+
Check rhythm progression across scenes matches the plan's arc:
|
|
2939
|
+
- Extract each scene's rhythm (staccato/legato/accelerando/decelerando) from plan
|
|
2940
|
+
- Verify narrative coherence: legato \u2192 staccato \u2192 peak \u2192 decelerando is good; random alternation = WARN
|
|
2941
|
+
- Scene durations should match rhythm: staccato = 2-4s, legato = 5-8s. A "staccato" at 8s = WARN
|
|
2942
|
+
|
|
2943
|
+
### 8. Music Cue Alignment
|
|
2944
|
+
Check music energy shifts align with timing manifest:
|
|
2945
|
+
- Plan says "Accelerando at Scene 2" \u2192 Scene 2 must not be the shortest scene
|
|
2946
|
+
- Plan says "music drops at 00:13.0" \u2192 manifest Scene 3 start frame must be near that time
|
|
2947
|
+
- Plan says "Decelerando" \u2192 that scene should be longer, not shorter
|
|
2948
|
+
|
|
2949
|
+
## Output Format
|
|
2950
|
+
|
|
2951
|
+
\\\`\\\`\\\`
|
|
2952
|
+
=== CONTINUITY REPORT ===
|
|
2953
|
+
|
|
2954
|
+
TRANSITIONS:
|
|
2955
|
+
S1\u2192S2: WhipPan \u2192 WhipPan \u2713
|
|
2956
|
+
S2\u2192S3: ZoomBlur \u2192 ZoomBlur \u2713
|
|
2957
|
+
S3\u2192S4: Fade \u2192 (missing) \u2717
|
|
2958
|
+
|
|
2959
|
+
COLOR FLOW:
|
|
2960
|
+
S1:noir \u2192 S2:cinematic \u2192 S3:matrix \u2192 S4:cinematic \u2192 S5:none
|
|
2961
|
+
\u2713 coherent
|
|
2962
|
+
|
|
2963
|
+
TIMING:
|
|
2964
|
+
Main.tsx: [120, 210, 180, 150, 120]
|
|
2965
|
+
Manifest: [120, 210, 180, 150, 120]
|
|
2966
|
+
\u2713 match | TOTAL: 705 frames
|
|
2967
|
+
|
|
2968
|
+
IMPORTS: \u2713 consistent
|
|
2969
|
+
BRAND: \u2713 #0055FF in 4/5 scenes, logo in S1+S5
|
|
2970
|
+
COMPLETE: 5/5 scenes \u2713
|
|
2971
|
+
|
|
2972
|
+
RHYTHM: S1:legato \u2192 S2:legato \u2192 S3:staccato \u2192 S4:decelerando \u2192 S5:legato
|
|
2973
|
+
\u2713 follows Brand Film arc
|
|
2974
|
+
MUSIC CUES: "Accelerando@S2" \u2713 | "DROP@S3:13.0" \u2192 S3 starts@12.5s \u2713
|
|
2975
|
+
|
|
2976
|
+
=== SCORE: 13/14 (93%) \u2713 PASS ===
|
|
2977
|
+
|
|
2978
|
+
FIXES:
|
|
2979
|
+
1. S3\u2192S4: Add FadeTransition entrance to Scene04
|
|
2980
|
+
=== END ===
|
|
2981
|
+
\\\`\\\`\\\`
|
|
2982
|
+
|
|
2983
|
+
## Rules
|
|
2984
|
+
|
|
2985
|
+
- **Read only.** Never modify any files.
|
|
2986
|
+
- Focus on CROSS-SCENE issues. Per-scene quality is the reviewer's job.
|
|
2987
|
+
- Be specific in fixes: exact file, exact line, exact component.
|
|
2988
|
+
- 85% pass rate minimum.
|
|
2989
|
+
`}function Ao(t){return`---
|
|
2990
|
+
name: remotion-scene-craft
|
|
2991
|
+
description: Remotion scene coding reference for scene builder agents. DO NOT load this directly \u2014 it is preloaded into video-scene-builder agents via frontmatter. Contains plan vocabulary decoder, 4-phase architecture, VO-sync, animation recipes.
|
|
2992
|
+
---
|
|
2993
|
+
|
|
2994
|
+
## 1. Remotion Core API
|
|
2995
|
+
|
|
2996
|
+
\`\`\`
|
|
2997
|
+
useCurrentFrame() -> number
|
|
2998
|
+
useVideoConfig() -> { fps, width, height, durationInFrames }
|
|
2999
|
+
spring({ frame, fps, config: { stiffness, damping } }) -> 0..1
|
|
3000
|
+
interpolate(value, [in0, in1], [out0, out1], { extrapolateRight: 'clamp' }) -> number
|
|
3001
|
+
<Sequence from={N} durationInFrames={M}> -- time-based grouping
|
|
3002
|
+
<AbsoluteFill> -- root container, layers via z-index
|
|
3003
|
+
staticFile('path') -- reference public/ assets
|
|
3004
|
+
<Img src={staticFile('image.png')} /> -- images
|
|
3005
|
+
<Audio src={staticFile('sfx.mp3')} startFrom={N} /> -- SFX ONLY (not VO/music)
|
|
3006
|
+
\`\`\`
|
|
3007
|
+
|
|
3008
|
+
## 2. Plan Vocabulary Decoder
|
|
3009
|
+
|
|
3010
|
+
The video plan uses production vocabulary. Map to Remotion code exactly.
|
|
3011
|
+
|
|
3012
|
+
### Spring Values
|
|
3013
|
+
|
|
3014
|
+
Plan writes \`Spring(100, 15)\` -> first = stiffness, second = damping:
|
|
3015
|
+
\`\`\`tsx
|
|
3016
|
+
spring({ frame, fps, config: { stiffness: 100, damping: 15 } })
|
|
3017
|
+
\`\`\`
|
|
3018
|
+
Use the plan's exact numbers. Do not invent values.
|
|
3019
|
+
|
|
3020
|
+
### Rhythm Patterns
|
|
3021
|
+
|
|
3022
|
+
| Pattern | Spring config | Stagger delay | Use when |
|
|
3023
|
+
|---------|--------------|---------------|----------|
|
|
3024
|
+
| Staccato | stiffness 120+, damping 15+ | 0.05-0.1s (2-3 frames) | Energy, urgency, feature montage |
|
|
3025
|
+
| Legato | stiffness 60-80, damping 8-10 | 0.3-0.5s (9-15 frames) | Emotional weight, breathing room |
|
|
3026
|
+
| Accelerando | Each interval shorter than previous | 0.4s down to 0.1s | Building toward climax |
|
|
3027
|
+
| Decelerando | Each interval longer than previous | 0.1s up to 0.4s | Resolution, outro |
|
|
3028
|
+
|
|
3029
|
+
### Hook Types (Scene 1 only)
|
|
3030
|
+
|
|
3031
|
+
- **Cold open**: Hero visual fills frame at frame 0. Context/text enters after VO_START.
|
|
3032
|
+
- **Pattern interrupt**: Scale 200%->100% slam or color flash in first 10 frames.
|
|
3033
|
+
- **Stat hook**: Large counter (\`interpolate() + Math.floor()\`) dominates frame. Context enters after.
|
|
3034
|
+
- **Question hook**: Text question via FadeInWords, 1s pause, then answer elements enter.
|
|
3035
|
+
|
|
3036
|
+
### ColorGrading Modifications
|
|
3037
|
+
|
|
3038
|
+
Plan says "matrix modified to red":
|
|
3039
|
+
\`\`\`tsx
|
|
3040
|
+
<ColorGrading preset="matrix">
|
|
3041
|
+
<div style={{ background: 'rgba(220,0,0,0.1)', mixBlendMode: 'multiply', position: 'absolute', inset: 0 }} />
|
|
3042
|
+
{/* scene content */}
|
|
3043
|
+
</ColorGrading>
|
|
3044
|
+
\`\`\`
|
|
3045
|
+
|
|
3046
|
+
### Idle State Formulas
|
|
3047
|
+
|
|
3048
|
+
Copy the plan's exact values. Common patterns:
|
|
3049
|
+
|
|
3050
|
+
"breathes +-4px over 4s":
|
|
3051
|
+
\`\`\`tsx
|
|
3052
|
+
const breathe = Math.sin(frame / fps * Math.PI * 2 / 4) * 4;
|
|
3053
|
+
// style: { transform: \`translateY(\${breathe}px)\` }
|
|
3054
|
+
\`\`\`
|
|
3055
|
+
|
|
3056
|
+
"pulses opacity 0.7 to 1.0":
|
|
3057
|
+
\`\`\`tsx
|
|
3058
|
+
const pulse = interpolate(Math.sin(frame / fps * Math.PI * 2 / 3), [-1, 1], [0.7, 1.0]);
|
|
3059
|
+
// style: { opacity: pulse }
|
|
3060
|
+
\`\`\`
|
|
3061
|
+
|
|
3062
|
+
"drifts right 20px over 6s":
|
|
3063
|
+
\`\`\`tsx
|
|
3064
|
+
const drift = interpolate(frame, [0, fps * 6], [0, 20], { extrapolateRight: 'clamp' });
|
|
3065
|
+
// style: { transform: \`translateX(\${drift}px)\` }
|
|
3066
|
+
\`\`\`
|
|
3067
|
+
|
|
3068
|
+
### Transition Emotional Weight
|
|
3069
|
+
|
|
3070
|
+
Match the plan's described energy to spring intensity:
|
|
3071
|
+
- "violent snap" / "smash" -> stiffness 150+, damping 18+, fast exit
|
|
3072
|
+
- "let that sink in" / "closure" -> slow fade 1s+ (30+ frames)
|
|
3073
|
+
- "entering a world" -> blur clears gradually over 20+ frames
|
|
3074
|
+
- "and then!" / "surprise" -> instant WhipPan, stiffness 200+
|
|
3075
|
+
|
|
3076
|
+
## 3. 4-Phase Scene Architecture
|
|
3077
|
+
|
|
3078
|
+
Every scene MUST follow this structure. Constants come from Audio Timing in the Task prompt.
|
|
3079
|
+
|
|
3080
|
+
\`\`\`tsx
|
|
3081
|
+
import { AbsoluteFill, useCurrentFrame, useVideoConfig, interpolate, spring, Img, Audio } from 'remotion';
|
|
2668
3082
|
import { staticFile } from 'remotion';
|
|
2669
|
-
// Template components (from inventory)
|
|
2670
|
-
import { ColorGrading } from '../components/effects/ColorGrading';
|
|
2671
|
-
import { GrainOverlay } from '../components/effects/GrainOverlay';
|
|
2672
|
-
// Lucide icons (as specified in plan)
|
|
2673
|
-
import { Shield, Lock, Eye } from 'lucide-react';
|
|
2674
3083
|
|
|
2675
|
-
export const
|
|
3084
|
+
export const SceneNNName: React.FC = () => {
|
|
2676
3085
|
const frame = useCurrentFrame();
|
|
2677
3086
|
const { fps } = useVideoConfig();
|
|
2678
3087
|
|
|
2679
|
-
// ===
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
// === PHASE
|
|
2686
|
-
//
|
|
3088
|
+
// === CONSTANTS (from Audio Timing) ===
|
|
3089
|
+
const VO_START = 10;
|
|
3090
|
+
const VO_DURATION = 156;
|
|
3091
|
+
const VO_END = VO_START + VO_DURATION;
|
|
3092
|
+
const SCENE_FRAMES = 210;
|
|
3093
|
+
|
|
3094
|
+
// === PHASE 1: ENTRANCE (frames 0 - VO_START) ===
|
|
3095
|
+
// Background fades in, hero elements begin entrance.
|
|
3096
|
+
// VO has NOT started -- purely visual setup.
|
|
3097
|
+
const bgOpacity = interpolate(frame, [0, 10], [0, 1], { extrapolateRight: 'clamp' });
|
|
3098
|
+
|
|
3099
|
+
// === PHASE 2: CONTENT REVEAL (frames VO_START - VO_END) ===
|
|
3100
|
+
// Progressive reveals SYNCED to voiceover.
|
|
3101
|
+
// Elements enter when the narrator mentions them.
|
|
3102
|
+
// If 4 items over VO_DURATION: item[i] at VO_START + i * (VO_DURATION / 4)
|
|
3103
|
+
|
|
3104
|
+
// === PHASE 3: EMPHASIS (frames VO_END - SCENE_FRAMES - 15) ===
|
|
3105
|
+
// VO finished. Highlight key element, camera push, color shift.
|
|
3106
|
+
|
|
3107
|
+
// === PHASE 4: TRANSITION PREP (last 15 frames) ===
|
|
3108
|
+
// Exit animation toward transition direction.
|
|
3109
|
+
const exitProgress = interpolate(frame, [SCENE_FRAMES - 15, SCENE_FRAMES], [0, 1], { extrapolateRight: 'clamp' });
|
|
2687
3110
|
|
|
2688
3111
|
return (
|
|
2689
3112
|
<AbsoluteFill>
|
|
2690
|
-
{/* Background
|
|
2691
|
-
{/* Content
|
|
2692
|
-
{/* Texture
|
|
3113
|
+
{/* Layer A: Background (gradient, image, motion) */}
|
|
3114
|
+
{/* Layer B: Hero Content (cards, text, data viz) */}
|
|
3115
|
+
{/* Layer C: Texture (ColorGrading, GrainOverlay, Vignette) */}
|
|
2693
3116
|
</AbsoluteFill>
|
|
2694
3117
|
);
|
|
2695
3118
|
};
|
|
2696
|
-
|
|
3119
|
+
\`\`\`
|
|
2697
3120
|
|
|
2698
|
-
|
|
3121
|
+
Each phase MUST contain different code. Empty phases = slideshow = reviewer FAIL.
|
|
2699
3122
|
|
|
2700
|
-
|
|
2701
|
-
- **Data viz:** Animated counters (\`interpolate() + Math.floor()\`), progress bars, percentage rings
|
|
2702
|
-
- **UI mockups:** Windows, toolbars, buttons, animated cursor \u2014 not screenshots
|
|
2703
|
-
- **Diagrams:** Nodes with animated connection lines, pulsing network graphs
|
|
2704
|
-
- **Typography:** FadeInWords for reveals, scale slams for impact, gradient text fills
|
|
2705
|
-
- **Backgrounds:** Animated gradient meshes, particle fields, noise flows \u2014 never flat
|
|
3123
|
+
## 4. VO-Sync Timing Patterns
|
|
2706
3124
|
|
|
2707
|
-
|
|
3125
|
+
The technique that separates premium from amateur:
|
|
2708
3126
|
|
|
2709
|
-
###
|
|
3127
|
+
### Stagger N items across VO duration
|
|
2710
3128
|
|
|
2711
|
-
|
|
3129
|
+
\`\`\`tsx
|
|
3130
|
+
const items = ['Security', 'Speed', 'Scale', 'Support'];
|
|
3131
|
+
const staggerFrames = Math.floor(VO_DURATION / items.length);
|
|
2712
3132
|
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
3133
|
+
const itemProgress = (index: number) => spring({
|
|
3134
|
+
frame: frame - (VO_START + index * staggerFrames),
|
|
3135
|
+
fps,
|
|
3136
|
+
config: { stiffness: 100, damping: 15 },
|
|
3137
|
+
});
|
|
2717
3138
|
|
|
2718
|
-
|
|
3139
|
+
// In JSX:
|
|
3140
|
+
{items.map((item, i) => (
|
|
3141
|
+
<div key={i} style={{
|
|
3142
|
+
opacity: itemProgress(i),
|
|
3143
|
+
transform: \`translateY(\${interpolate(itemProgress(i), [0, 1], [30, 0])}px)\`,
|
|
3144
|
+
}}>
|
|
3145
|
+
{item}
|
|
3146
|
+
</div>
|
|
3147
|
+
))}
|
|
3148
|
+
\`\`\`
|
|
2719
3149
|
|
|
2720
|
-
|
|
2721
|
-
- CRITICAL items first (wrong icons, missing logos, text-as-icons)
|
|
2722
|
-
- Then FAIL items (missing transitions, empty phases, wrong colors)
|
|
2723
|
-
- Then WEAK items (low complexity, missing idle animations)
|
|
3150
|
+
### Rhythm cues from timing manifest
|
|
2724
3151
|
|
|
2725
|
-
|
|
3152
|
+
- **BPM provided**: Align stagger to beat intervals (\`const beatFrames = Math.round(fps * 60 / bpm)\`)
|
|
3153
|
+
- **Energy = low**: Gentle springs (stiffness 60-80)
|
|
3154
|
+
- **Energy = peak**: Aggressive springs (stiffness 120+), tight staggers
|
|
3155
|
+
- **Music cue "Accelerando @00:11.0"**: Decrease stagger intervals after that frame
|
|
2726
3156
|
|
|
2727
|
-
|
|
3157
|
+
### Scenes without VO
|
|
2728
3158
|
|
|
2729
|
-
|
|
3159
|
+
No VO_START/VO_DURATION. Use SCENE_FRAMES directly:
|
|
3160
|
+
- Entrance: frames 0-15
|
|
3161
|
+
- Content: frames 15 to SCENE_FRAMES - 30
|
|
3162
|
+
- Emphasis: last 30-15 frames
|
|
3163
|
+
- Exit: last 15 frames
|
|
2730
3164
|
|
|
2731
|
-
|
|
3165
|
+
## 5. Animation Recipes
|
|
2732
3166
|
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
3167
|
+
### Staggered card grid
|
|
3168
|
+
\`\`\`tsx
|
|
3169
|
+
const cardProgress = (i: number) => spring({
|
|
3170
|
+
frame: frame - (VO_START + i * Math.floor(VO_DURATION / cardCount)),
|
|
3171
|
+
fps,
|
|
3172
|
+
config: { stiffness: 120, damping: 12 },
|
|
3173
|
+
});
|
|
3174
|
+
// opacity: cardProgress(i)
|
|
3175
|
+
// translateY: interpolate(cardProgress(i), [0, 1], [30, 0])
|
|
3176
|
+
\`\`\`
|
|
2736
3177
|
|
|
2737
|
-
|
|
3178
|
+
### Counter animation
|
|
3179
|
+
\`\`\`tsx
|
|
3180
|
+
const count = Math.floor(interpolate(frame, [startFrame, endFrame], [0, target], {
|
|
3181
|
+
extrapolateRight: 'clamp',
|
|
3182
|
+
}));
|
|
3183
|
+
// Display: {count.toLocaleString()}
|
|
3184
|
+
\`\`\`
|
|
2738
3185
|
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
`);De.command("install").description(`Install ${m.displayName} skills for AI coding assistants`).argument("[type]","Skill type: main, video, presentation, or omit for all").option("-d, --dir <path>","Install to specific directory").option("-g, --global","Install globally (to home directory)",!0).option("-l, --local","Install locally (to current directory)").option("-f, --force","Overwrite existing skill files").action(async(t,o)=>{let e=[];t&&!He.includes(t)&&(c(`Invalid skill type: ${t}. Must be one of: ${He.join(", ")}`),process.exit(1));let n=[{name:"tts",content:Te(C)},{name:"music",content:Ie(C)},{name:"sfx",content:Re(C)},{name:"mix",content:Ae(C)},{name:"image",content:Pe(C)},{name:"video-search",content:Oe(C)},{name:"scrape",content:Ee(C)},{name:"branding",content:Fe(C)},{name:"config",content:Ue(C)}];(!t||t==="main")&&e.push({name:m.name,content:lt(C),references:n});let r=[{name:"brief-guide",content:jt.render(C)},{name:"website-assets",content:Nt.render(C)},{name:"source-scraping",content:Mt.render(C)}];(!t||t==="video")&&e.push({name:`${m.name}-video`,content:dt(C),references:r}),(!t||t==="presentation")&&e.push({name:`${m.name}-presentation`,content:mt(C)}),t==="tts"&&e.push({name:`${m.name}-tts`,content:Te(C)}),t==="music"&&e.push({name:`${m.name}-music`,content:Ie(C)}),t==="sfx"&&e.push({name:`${m.name}-sfx`,content:Re(C)}),t==="mix"&&e.push({name:`${m.name}-mix`,content:Ae(C)}),t==="image"&&e.push({name:`${m.name}-image`,content:Pe(C)}),t==="video-search"&&e.push({name:`${m.name}-video-search`,content:Oe(C)}),t==="scrape"&&e.push({name:`${m.name}-scrape`,content:Ee(C)}),t==="branding"&&e.push({name:`${m.name}-branding`,content:Fe(C)}),t==="config"&&e.push({name:`${m.name}-config`,content:Ue(C)}),console.log();for(let i of e){l(`Installing ${i.name}...`);let a=ft(i.name,i.content,{dir:o.dir,local:o.local,force:o.force},i.references);if(a.installed.length>0&&(g(`${i.name} installed successfully`),h(" Installed to",a.installed.join(", "))),a.skipped.length>0&&(l(` Skipped (already exists): ${a.skipped.join(", ")}`),console.log(L.gray(" Use --force to overwrite"))),a.errors.length>0)for(let s of a.errors)c(` ${s}`);a.installed.length===0&&a.skipped.length===0&&a.errors.length===0&&(l(" No supported AI coding assistants detected"),console.log(L.gray(" Supported editors: "+yn().join(", "))),console.log(L.gray(" Use --dir <path> to install to a specific directory"))),console.log()}if(!t||t==="video"){for(let i of[{name:"video-scene-builder",generate:xn},{name:"video-scene-reviewer",generate:wn}]){l(`Installing ${i.name} agent...`);let a=bn(i.name,i.generate(),{force:o.force});a.installed.length>0&&(g(`${i.name} agent installed`),h(" Installed to",a.installed.map(s=>`${s} (~/.claude/agents/)`).join(", "))),a.skipped.length>0&&l(` Agent skipped (already exists): ${a.skipped.join(", ")}`)}console.log()}});De.command("show").description("Display skill content").argument("[type]","Skill type (default: main)").action((t="main")=>{let e={main:()=>lt(C),video:()=>dt(C),presentation:()=>mt(C),tts:()=>Te(C),music:()=>Ie(C),sfx:()=>Re(C),mix:()=>Ae(C),image:()=>Pe(C),"video-search":()=>Oe(C),scrape:()=>Ee(C),branding:()=>Fe(C),config:()=>Ue(C)}[t];e?console.log(e()):(c(`Invalid skill type: ${t}. Must be one of: ${He.join(", ")}`),process.exit(1))});De.command("uninstall").description(`Remove ${m.displayName} skills from AI coding assistants`).argument("[type]","Skill type: main, video, presentation, or omit for all").option("-g, --global","Uninstall globally (from home directory)",!0).option("-l, --local","Uninstall locally (from current directory)").action(async(t,o)=>{let e=[];t&&!He.includes(t)&&(c(`Invalid skill type: ${t}. Must be one of: ${He.join(", ")}`),process.exit(1)),(!t||t==="main")&&e.push(m.name),(!t||t==="video")&&e.push(`${m.name}-video`),(!t||t==="presentation")&&e.push(`${m.name}-presentation`),(!t||t==="tts")&&e.push(`${m.name}-tts`),(!t||t==="music")&&e.push(`${m.name}-music`),(!t||t==="sfx")&&e.push(`${m.name}-sfx`),(!t||t==="mix")&&e.push(`${m.name}-mix`),(!t||t==="image")&&e.push(`${m.name}-image`),(!t||t==="video-search")&&e.push(`${m.name}-video-search`),(!t||t==="scrape")&&e.push(`${m.name}-scrape`),(!t||t==="branding")&&e.push(`${m.name}-branding`),(!t||t==="config")&&e.push(`${m.name}-config`),console.log();for(let n of e){let r=gt(n,{local:o.local});if(r.removed.length>0?(g(`${n} uninstalled`),h(" Removed from",r.removed.join(", "))):l(` ${n} not found`),r.errors.length>0)for(let i of r.errors)R(` Failed to remove: ${i}`)}if(!t||t==="video")for(let n of["video-scene-builder","video-scene-reviewer"])vn(n).removed.length>0&&g(`${n} agent uninstalled`);console.log()});import{Command as Bt}from"commander";import Cn from"ora";import{writeFile as ai}from"fs/promises";var Sn="en-US-Neural2-C",$n="gemini",si=new Bt("generate").description("Generate speech from text").requiredOption("-t, --text <text>","Text to convert to speech").requiredOption("-o, --output <path>","Output file path").option("--voice-id <voiceId>","Voice ID (e.g., ElevenLabs: 21m00Tcm4TlvDq8ikWAM)").option("-v, --voice <voice>",`Voice name (default: ${Sn})`).option("-p, --provider <provider>",`Provider: gemini, elevenlabs, openai (default: ${$n})`).option("-m, --model <model>","Model (provider-specific)").option("-s, --speed <speed>","Speech speed 0.25-4.0 (default: 1.0)").option("--speakers <json>",`Multi-speaker (Gemini, max 2): '[{"name":"Joe","voice":"Kore"},{"name":"Jane","voice":"Puck"}]'`).option("-i, --instructions <text>","Style prompt (OpenAI gpt-4o-mini-tts): tone, accent, speed, emotion").option("-f, --format <format>","Output format: human, json, quiet","human").action(async t=>{let o=t.format,e=o==="human"?Cn("Generating speech...").start():null,n;t.speed&&(n=parseFloat(t.speed),(isNaN(n)||n<.25||n>4)&&(e?.stop(),c("Speed must be between 0.25 and 4.0"),process.exit(v.INVALID_INPUT)));let r;if(t.speakers)try{r=JSON.parse(t.speakers),(!Array.isArray(r)||r.length>2)&&(e?.stop(),c("Speakers must be an array with max 2 items"),process.exit(v.INVALID_INPUT))}catch{e?.stop(),c(`Invalid --speakers JSON. Example: '[{"name":"Joe","voice":"Kore"}]'`),process.exit(v.INVALID_INPUT)}try{let i=t.voiceId,a=t.voice||Sn,s=t.provider||$n,d={};r&&(d.gemini={speakers:r}),t.instructions&&(d.openai={instructions:t.instructions});let u=await rt({text:t.text,options:{provider:s,voiceId:i,voice:a,model:t.model,speed:n,providerOptions:Object.keys(d).length>0?d:void 0}});e?.stop();let p=t.output.endsWith(`.${u.format}`)?t.output:`${t.output}.${u.format}`;if(await ai(p,u.audioData),o==="json"){V({status:"completed",output:p,duration:u.duration,cost:u.cost,provider:u.provider,format:u.format});return}if(o==="quiet"){console.log(p);return}g(`Saved to: ${p}`),l(`Duration: ${u.duration.toFixed(2)}s`),l(`Provider: ${u.provider}`),l(`Cost: $${u.cost.toFixed(6)}`)}catch(i){e?.stop(),c(i instanceof Error?i.message:"Unknown error"),process.exit(v.GENERAL_ERROR)}}),ci=new Bt("voices").description("List available voices").option("-p, --provider <provider>","Filter by provider: gemini, elevenlabs, openai").option("-f, --format <format>","Output format: human, json","human").action(async t=>{let o=t.format==="human"?Cn("Fetching voices...").start():null;try{let e=await Uo(t.provider);if(o?.stop(),t.format==="json"){V(e.voices);return}if(Array.isArray(e.voices)){let n=t.provider?.toUpperCase()||"VOICES";console.log(),console.log(`${n} Voices:`),console.log("-".repeat(50));for(let r of e.voices)console.log(` ${r.name} (${r.id})`),console.log(` ${r.description}`)}else for(let[n,r]of Object.entries(e.voices))if(!(!r||r.length===0)){console.log(),console.log(`${n.toUpperCase()} Voices:`),console.log("-".repeat(50));for(let i of r)console.log(` ${i.name} (${i.id})`),console.log(` ${i.description}`)}}catch(e){o?.stop(),c(e instanceof Error?e.message:"Unknown error"),process.exit(v.GENERAL_ERROR)}}),kn=new Bt("tts").description("Text-to-speech commands").addCommand(si).addCommand(ci);import{Command as Wt}from"commander";import Gt from"ora";import{writeFile as Tn}from"fs/promises";function _t(t,o){if(o==="json"){V(t);return}if(o==="quiet"){t.audioUrl?console.log(t.audioUrl):console.log(t.requestId);return}l(`Request ID: ${t.requestId}`),l(`Status: ${t.status}`),t.duration&&l(`Duration: ${t.duration}s`),t.audioUrl&&g(`Audio URL: ${t.audioUrl}`),t.cost!==void 0&&l(`Cost: $${t.cost.toFixed(4)}`),t.error&&c(`Error: ${t.error}`)}async function li(t,o){if(t.startsWith("data:")){let r=t.match(/^data:[^;]+;base64,(.+)$/);if(!r)throw new Error("Invalid data URL format");let i=Buffer.from(r[1],"base64");await Tn(o,i);return}let e=await fetch(t);if(!e.ok)throw new Error(`Failed to download: ${e.status}`);let n=await e.arrayBuffer();await Tn(o,Buffer.from(n))}var di=new Wt("generate").description("Generate music from a text prompt").requiredOption("-p, --prompt <text>","Music description").option("-d, --duration <seconds>","Duration in seconds (3-600)","30").option("-s, --style <style>","Style preset").option("--provider <provider>","Provider (elevenlabs, suno)").option("-o, --output <path>","Output file path").option("--no-wait","Do not wait for completion").option("-f, --format <format>","Output format: human, json, quiet","human").action(async t=>{let o=parseInt(t.duration,10);(isNaN(o)||o<3||o>600)&&(c("Duration must be between 3 and 600 seconds (10 minutes)"),process.exit(v.INVALID_INPUT));let e=t.format,n=e==="human"?Gt("Generating music...").start():null;try{let r=await it({prompt:t.prompt,duration:o,options:{provider:t.provider,style:t.style}});if(!t.wait){n?.stop(),_t(r,e);return}let i=r;if(r.status!=="completed"&&r.status!=="failed"&&(n&&(n.text=`Processing (ID: ${r.requestId})...`),i=await de(()=>_e(r.requestId),60,2e3)),n?.stop(),i.status==="failed"&&(c(i.error||"Music generation failed"),process.exit(v.GENERAL_ERROR)),_t(i,e),t.output&&i.audioUrl){let a=e==="human"?Gt("Downloading...").start():null;try{await li(i.audioUrl,t.output),a?.stop(),e==="human"&&g(`Saved to: ${t.output}`)}catch(s){a?.stop(),R(`Failed to download: ${s instanceof Error?s.message:"Unknown error"}`)}}}catch(r){n?.stop(),c(r instanceof Error?r.message:"Unknown error"),process.exit(v.GENERAL_ERROR)}}),mi=new Wt("status").description("Check status of a music generation request").argument("<id>","Request ID").option("-f, --format <format>","Output format: human, json, quiet","human").action(async(t,o)=>{let e=o.format==="human"?Gt("Checking status...").start():null;try{let n=await _e(t);e?.stop(),_t(n,o.format)}catch(n){e?.stop(),c(n instanceof Error?n.message:"Unknown error"),process.exit(v.GENERAL_ERROR)}}),In=new Wt("music").description("Music generation commands").addCommand(di).addCommand(mi);import{Command as Ht}from"commander";import Jt from"ora";import{writeFile as ui}from"fs/promises";function Kt(t,o){if(o==="json"){V(t);return}if(o==="quiet"){t.outputUrl?console.log(t.outputUrl):console.log(t.requestId);return}l(`Request ID: ${t.requestId}`),l(`Status: ${t.status}`),t.duration&&l(`Duration: ${t.duration}s`),t.outputUrl&&g(`Output URL: ${t.outputUrl}`),t.cost!==void 0&&l(`Cost: $${t.cost.toFixed(4)}`),t.error&&c(`Error: ${t.error}`)}async function pi(t,o){let e=await fetch(t);if(!e.ok)throw new Error(`Failed to download: ${e.status}`);let n=await e.arrayBuffer();await ui(o,Buffer.from(n))}var fi=new Ht("create").description("Mix audio tracks into a video (music will loop to match video duration)").requiredOption("--video <url>","Input video file/URL").option("--music <url>","Background music file/URL (will loop if shorter than video)").option("--voice <url>","Voiceover file/URL").option("--music-volume <percent>","Music volume 0-100 (default: 30, recommended for mix with voice)","30").option("--voice-volume <percent>","Voice volume 0-100","100").option("-o, --output <path>","Output file path").option("--no-wait","Do not wait for completion").option("-f, --format <format>","Output format: human, json, quiet","human").action(async t=>{!t.music&&!t.voice&&(c("At least one of --music or --voice must be provided"),process.exit(v.INVALID_INPUT));let o=parseInt(t.musicVolume,10)/100,e=parseInt(t.voiceVolume,10)/100;(isNaN(o)||o<0||o>1)&&(c("Music volume must be between 0 and 100"),process.exit(v.INVALID_INPUT)),(isNaN(e)||e<0||e>2)&&(c("Voice volume must be between 0 and 200"),process.exit(v.INVALID_INPUT));let n=t.format,r=n==="human"?Jt("Mixing audio...").start():null,i=[{url:t.video,role:"video"}];t.music&&i.push({url:t.music,role:"background",volume:o*5}),t.voice&&i.push({url:t.voice,role:"voice",volume:e*2});try{let a=await Do({operation:"add-to-video",inputs:i,options:{musicVolume:o,voiceVolume:e}});if(!t.wait){r?.stop(),Kt(a,n);return}r&&(r.text=`Processing (ID: ${a.requestId})...`);let s=await de(()=>Pt(a.requestId),120,3e3);if(r?.stop(),s.status==="failed"&&(c(s.error||"Audio mixing failed"),process.exit(v.GENERAL_ERROR)),Kt(s,n),t.output&&s.outputUrl){let d=n==="human"?Jt("Downloading...").start():null;try{await pi(s.outputUrl,t.output),d?.stop(),n==="human"&&g(`Saved to: ${t.output}`)}catch(u){d?.stop(),R(`Failed to download: ${u instanceof Error?u.message:"Unknown error"}`)}}}catch(a){r?.stop(),c(a instanceof Error?a.message:"Unknown error"),process.exit(v.GENERAL_ERROR)}}),gi=new Ht("status").description("Check status of an audio mix request").argument("<id>","Request ID").option("-f, --format <format>","Output format: human, json, quiet","human").action(async(t,o)=>{let e=o.format==="human"?Jt("Checking status...").start():null;try{let n=await Pt(t);e?.stop(),Kt(n,o.format)}catch(n){e?.stop(),c(n instanceof Error?n.message:"Unknown error"),process.exit(v.GENERAL_ERROR)}}),Rn=new Ht("mix").description("Audio mixing commands").addCommand(fi).addCommand(gi);import{Command as An}from"commander";import hi from"ora";var yi=new An("search").description("Search for images").requiredOption("-q, --query <query>","Search query").option("-n, --max-results <number>","Maximum number of results (default: 10)").option("-s, --size <size>","Image size: small, medium, large, any","large").option("--safe-search","Enable safe search (default: true)",!0).option("--no-safe-search","Disable safe search").option("-f, --format <format>","Output format: human, json, quiet","human").action(async t=>{let o=t.format,e=o==="human"?hi("Searching for images...").start():null,n;t.maxResults&&(n=parseInt(t.maxResults,10),(isNaN(n)||n<1)&&(e?.stop(),c("Max results must be a positive number"),process.exit(v.INVALID_INPUT)));try{let r=await at({query:t.query,options:{maxResults:n||10,size:t.size,safeSearch:t.safeSearch}});e?.stop(),r.success||(c("Search failed"),process.exit(v.GENERAL_ERROR));let i=r.data.results.flatMap(a=>a.results.map(s=>({...s,provider:a.providerName})));if(o==="json"){V({success:!0,query:t.query,totalResults:i.length,totalCost:r.data.totalCost,images:i});return}if(o==="quiet"){for(let a of i)console.log(a.url);return}if(i.length===0){l("No images found");return}g(`Found ${i.length} images for "${t.query}"`),console.log();for(let a=0;a<i.length;a++){let s=i[a];console.log(`[${a+1}] ${s.title||"Untitled"}`),console.log(` URL: ${s.url}`),console.log(` Size: ${s.width}x${s.height}`),s.author&&console.log(` Author: ${s.author}`),console.log(` Provider: ${s.provider}`),console.log()}l(`Total cost: $${r.data.totalCost.toFixed(4)}`)}catch(r){e?.stop(),c(r instanceof Error?r.message:"Unknown error"),process.exit(v.GENERAL_ERROR)}}),Pn=new An("image").description("Image search commands").addCommand(yi);import{Command as se}from"commander";import ze from"ora";import{mkdir as Me,writeFile as ae,readFile as bt,access as zt,rm as Xt}from"fs/promises";import{join as q,resolve as Ye}from"path";import{execSync as je,spawn as xi,spawnSync as Si}from"child_process";import $i from"ffmpeg-static";import{homedir as bi}from"os";import{join as On}from"path";import{readdir as vi}from"fs/promises";var wi="conceptcraft";function Le(){return On(bi(),`.${wi}`,"projects")}function ht(t){return On(Le(),t)}async function Yt(){try{let t=Le();return(await vi(t,{withFileTypes:!0})).filter(e=>e.isDirectory()).map(e=>e.name)}catch{return[]}}function En(t,o){let e=2,n=`${t}-${e}`;for(;o.includes(n);)e++,n=`${t}-${e}`;return n}var Ci="inizio-inc/remotion-video-template#main",Ne=30;async function ki(t){let o=await bt(t,"utf-8");return JSON.parse(o)}async function Ti(t){let o=q(t,"template.manifest.json");try{return await ki(o)}catch{return{entry:"src/Root.tsx",compositions:[{id:"Main",type:"youtube",aspect:"16:9",default:!0}]}}}function Fn(){try{return je("bun --version",{stdio:"ignore"}),!0}catch{return!1}}function Ii(t){if(t.includes("---")||t.includes("[Section")){let r=t.split(/---|\[Section \d+\]/i).filter(i=>i.trim());if(r.length>1)return r.map(i=>i.trim())}let o=t.split(/(?<=[.!?])\s+/).map(r=>r.trim()).filter(r=>r.length>0),e=[],n="";for(let r of o){let i=r.split(/\s+/).length;n?(e.push(`${n} ${r}`),n=""):i<5&&e.length<o.length-1?n=r:e.push(r)}return n&&(e.length>0?e[e.length-1]+=` ${n}`:e.push(n)),e}function Ri(t,o,e=Ne,n){if(n&&n.characters.length>0)return Ai(t,n,e);let r=t.reduce((a,s)=>a+s.split(/\s+/).length,0),i=0;return t.map((a,s)=>{let d=a.split(/\s+/).length,u=d/r,p=o*u,k=Math.round(p*e),y=a.split(""),w=p/y.length,$={characters:y,characterStartTimesSeconds:y.map((T,x)=>x*w),characterEndTimesSeconds:y.map((T,x)=>(x+1)*w)},I={id:s+1,text:a,wordCount:d,startTime:i,endTime:i+p,durationInSeconds:p,durationInFrames:k,timestamps:$};return i+=p,I})}function Ai(t,o,e){let{characters:n,characterStartTimesSeconds:r,characterEndTimesSeconds:i}=o,a=n.join(""),s=[],d=0;for(let u=0;u<t.length;u++){let p=t[u],k=p.length;for(;d<n.length&&n[d].match(/^\s*$/);)d++;let y=d,w=r[y]||0;d+=k;let $=d-1;for(;$>y&&n[$]?.match(/^\s*$/);)$--;let I=i[Math.min($,i.length-1)]||w+1,T=I-w,x=Math.round(T*e),D={characters:n.slice(y,$+1),characterStartTimesSeconds:r.slice(y,$+1).map(S=>S-w),characterEndTimesSeconds:i.slice(y,$+1).map(S=>S-w)};s.push({id:u+1,text:p,wordCount:p.split(/\s+/).length,startTime:w,endTime:I,durationInSeconds:T,durationInFrames:x,timestamps:D})}return s}function Pi(t){let o=t==="tiktok"?"vertical":t==="audiogram"?"square":"widescreen";return{scenes:[{name:"Hook",script:"Replace this with a bold opening line that grabs attention.",imageQuery:`cinematic ${o} intro visual, dramatic lighting`,videoQuery:`abstract ${o} motion background`},{name:"Proof",script:"Share the core insight, benefit, or product moment you want viewers to remember.",imageQuery:`people collaborating ${o} office, warm lighting`,videoQuery:`close-up hands using technology ${o}`},{name:"Close",script:"End with a crisp call to action and a confident closing line.",imageQuery:`bold typography ${o} background, high contrast`,videoQuery:`sunrise skyline ${o} timelapse`}],voice:"en-US-Neural2-C",voiceSettings:{speed:1,stability:.4,style:.6},musicPrompt:"uplifting cinematic background, modern and confident"}}async function Oi(t,o,e){let n=q(t,"public"),r=q(t,"scripts");await Me(n,{recursive:!0}),await Me(r,{recursive:!0});let i=q(n,"scenes.json");await ae(i,JSON.stringify(Pi(o),null,2),"utf-8");let a=`import { execSync } from "node:child_process";
|
|
3186
|
+
### Typing effect
|
|
3187
|
+
\`\`\`tsx
|
|
3188
|
+
const chars = Math.floor(Math.max(0, (frame - startFrame) * charsPerFrame));
|
|
3189
|
+
const text = fullText.slice(0, chars);
|
|
3190
|
+
// Cursor: opacity via Math.floor(frame / 20) % 2
|
|
3191
|
+
\`\`\`
|
|
3192
|
+
|
|
3193
|
+
### Floating particles
|
|
3194
|
+
\`\`\`tsx
|
|
3195
|
+
{Array.from({ length: 12 }).map((_, i) => {
|
|
3196
|
+
const delay = i * 8;
|
|
3197
|
+
const x = 10 + (i * 7) % 80;
|
|
3198
|
+
const y = interpolate(Math.sin((frame + delay) / 40), [-1, 1], [20, 80]);
|
|
3199
|
+
const opacity = interpolate(frame - delay, [0, 30], [0, 0.3], { extrapolateRight: 'clamp' });
|
|
3200
|
+
return <div key={i} style={{ position: 'absolute', left: \`\${x}%\`, top: \`\${y}%\`, opacity,
|
|
3201
|
+
width: 4, height: 4, borderRadius: '50%', background: 'rgba(255,255,255,0.5)' }} />;
|
|
3202
|
+
})}
|
|
3203
|
+
\`\`\`
|
|
3204
|
+
|
|
3205
|
+
### Glass card
|
|
3206
|
+
\`\`\`tsx
|
|
3207
|
+
style={{
|
|
3208
|
+
background: 'rgba(255,255,255,0.05)',
|
|
3209
|
+
backdropFilter: 'blur(20px)',
|
|
3210
|
+
border: '1px solid rgba(255,255,255,0.1)',
|
|
3211
|
+
boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.15), 0 8px 32px rgba(0,0,0,0.3)',
|
|
3212
|
+
borderRadius: 24,
|
|
3213
|
+
padding: 32,
|
|
3214
|
+
}}
|
|
3215
|
+
\`\`\`
|
|
3216
|
+
|
|
3217
|
+
### Camera push (Ken Burns)
|
|
3218
|
+
\`\`\`tsx
|
|
3219
|
+
const scale = interpolate(frame, [pushStart, pushEnd], [1.0, 1.05], { extrapolateRight: 'clamp' });
|
|
3220
|
+
// Apply to background layer: transform: \`scale(\${scale})\`
|
|
3221
|
+
\`\`\`
|
|
3222
|
+
|
|
3223
|
+
### Gradient text
|
|
3224
|
+
\`\`\`tsx
|
|
3225
|
+
style={{
|
|
3226
|
+
backgroundImage: 'linear-gradient(135deg, #FFFFFF 0%, #9CA3AF 100%)',
|
|
3227
|
+
WebkitBackgroundClip: 'text',
|
|
3228
|
+
WebkitTextFillColor: 'transparent',
|
|
3229
|
+
}}
|
|
3230
|
+
\`\`\`
|
|
3231
|
+
|
|
3232
|
+
### Pulsing glow
|
|
3233
|
+
\`\`\`tsx
|
|
3234
|
+
const glow = interpolate(Math.sin(frame / fps * Math.PI * 2 / 2), [-1, 1], [0.1, 0.3]);
|
|
3235
|
+
// boxShadow: \`0 0 \${glow * 60}px rgba(59, 130, 246, \${glow})\`
|
|
3236
|
+
\`\`\`
|
|
3237
|
+
|
|
3238
|
+
## 6. Quality Floor and Anti-patterns
|
|
3239
|
+
|
|
3240
|
+
### Minimum requirements (reviewer checks at 85%+ pass rate):
|
|
3241
|
+
|
|
3242
|
+
1. 100+ lines of code per scene
|
|
3243
|
+
2. Real Lucide React icons only (\`import { Shield, Lock } from 'lucide-react'\`). Never text/emoji substitutes.
|
|
3244
|
+
3. Real logo files from public/. Never Lucide icons as brand logos.
|
|
3245
|
+
4. Brand fonts via loadFont() or @font-face. Never "closest Google Font match".
|
|
3246
|
+
5. ColorGrading wrapper when plan specifies a preset.
|
|
3247
|
+
6. Template transitions: use exact components from plan. Never generic substitutes.
|
|
3248
|
+
7. Background motion: gradients, drift, particles. Never flat solid color.
|
|
3249
|
+
8. Idle animations after entrance: float, breathe, pulse, drift.
|
|
3250
|
+
9. SFX via \`<Audio>\` at correct frames when files available.
|
|
3251
|
+
10. VO-sync stagger: elements enter when narrator mentions them. Never all at once.
|
|
3252
|
+
11. Audio timing constants (VO_START, VO_DURATION, VO_END, SCENE_FRAMES) at top of component.
|
|
3253
|
+
|
|
3254
|
+
### Anti-patterns (will cause reviewer FAIL):
|
|
3255
|
+
|
|
3256
|
+
- setTimeout / useEffect -> Remotion is declarative, frame-based. Use \`frame\` directly.
|
|
3257
|
+
- Hardcoded frame numbers -> Use VO_START / VO_END / SCENE_FRAMES constants.
|
|
3258
|
+
- \`<Audio>\` for VO or music -> Main.tsx handles via VoiceoverSequence + DuckedMusic. Scenes are visual only.
|
|
3259
|
+
- All elements entering on frame 0 while VO narrates them one by one -> Stagger to VO timing.
|
|
3260
|
+
- Flat solid backgrounds -> Always gradient, noise, or moving texture.
|
|
3261
|
+
- Static scenes with no visual change for 2+ seconds -> Every 2s needs a visual event.
|
|
3262
|
+
- Generic component substitution -> Plan says WhipPan, use WhipPan. Not slide() or wipe().
|
|
3263
|
+
`}var ze=["main","video","presentation","tts","music","sfx","mix","image","video-search","scrape","branding","config"],$={cmd:u.commands[0],pkg:u.packageName,url:u.apiUrl,name:u.name},Ue=new yi("skill").description(`Manage ${u.displayName} skills for AI coding assistants`).addHelpText("after",`
|
|
3264
|
+
${U.bold("Skill Types:")}
|
|
3265
|
+
${U.cyan("main")} Main CLI skill with all capabilities overview
|
|
3266
|
+
${U.cyan("video")} Detailed video creation workflow with Remotion/R3F patterns
|
|
3267
|
+
${U.cyan("presentation")} Detailed presentation creation workflow
|
|
3268
|
+
|
|
3269
|
+
${U.bold("Reference Skills:")}
|
|
3270
|
+
${U.cyan("tts")} Text-to-speech generation (voices, providers, options)
|
|
3271
|
+
${U.cyan("music")} AI music generation (prompts, styles, duration)
|
|
3272
|
+
${U.cyan("sfx")} Sound effects generation (categories, looping, formats)
|
|
3273
|
+
${U.cyan("mix")} Audio mixing (video + voice + music)
|
|
3274
|
+
${U.cyan("image")} Image search and AI generation
|
|
3275
|
+
${U.cyan("video-search")} Stock video search
|
|
3276
|
+
${U.cyan("scrape")} URL content extraction
|
|
3277
|
+
${U.cyan("branding")} Brand profile management
|
|
3278
|
+
${U.cyan("config")} CLI configuration management
|
|
3279
|
+
|
|
3280
|
+
${U.bold("Examples:")}
|
|
3281
|
+
${U.gray("# Install main CLI skill (comprehensive overview)")}
|
|
3282
|
+
$ ${u.commands[0]} skill install main
|
|
3283
|
+
|
|
3284
|
+
${U.gray("# Install video skill")}
|
|
3285
|
+
$ ${u.commands[0]} skill install video
|
|
3286
|
+
|
|
3287
|
+
${U.gray("# Install presentation skill")}
|
|
3288
|
+
$ ${u.commands[0]} skill install presentation
|
|
3289
|
+
|
|
3290
|
+
${U.gray("# Install all skills")}
|
|
3291
|
+
$ ${u.commands[0]} skill install
|
|
3292
|
+
|
|
3293
|
+
${U.gray("# Install to specific directory")}
|
|
3294
|
+
$ ${u.commands[0]} skill install main --dir ~/.claude
|
|
3295
|
+
|
|
3296
|
+
${U.gray("# Show skill content")}
|
|
3297
|
+
$ ${u.commands[0]} skill show main
|
|
3298
|
+
`);Ue.command("install").description(`Install ${u.displayName} skills for AI coding assistants`).argument("[type]","Skill type: main, video, presentation, or omit for all").option("-d, --dir <path>","Install to specific directory").option("-g, --global","Install globally (to home directory)",!0).option("-l, --local","Install locally (to current directory)").option("-f, --force","Overwrite existing skill files").action(async(t,n)=>{let e=[];t&&!ze.includes(t)&&(c(`Invalid skill type: ${t}. Must be one of: ${ze.join(", ")}`),process.exit(1));let o=[{name:"tts",content:Ie($)},{name:"music",content:Re($)},{name:"sfx",content:Ae($)},{name:"mix",content:Oe($)},{name:"image",content:Ee($)},{name:"video-search",content:Pe($)},{name:"scrape",content:Fe($)},{name:"branding",content:Ne($)},{name:"config",content:De($)}];(!t||t==="main")&&e.push({name:u.name,content:ut($),references:o});let r=[{name:"brief-guide",content:Gt.render($)},{name:"website-assets",content:Bt.render($)},{name:"source-scraping",content:Wt.render($)}];(!t||t==="video")&&(e.push({name:`${u.name}-video`,content:pt($),references:r}),e.push({name:"remotion-scene-craft",content:Ao($)})),(!t||t==="presentation")&&e.push({name:`${u.name}-presentation`,content:ft($)}),t==="tts"&&e.push({name:`${u.name}-tts`,content:Ie($)}),t==="music"&&e.push({name:`${u.name}-music`,content:Re($)}),t==="sfx"&&e.push({name:`${u.name}-sfx`,content:Ae($)}),t==="mix"&&e.push({name:`${u.name}-mix`,content:Oe($)}),t==="image"&&e.push({name:`${u.name}-image`,content:Ee($)}),t==="video-search"&&e.push({name:`${u.name}-video-search`,content:Pe($)}),t==="scrape"&&e.push({name:`${u.name}-scrape`,content:Fe($)}),t==="branding"&&e.push({name:`${u.name}-branding`,content:Ne($)}),t==="config"&&e.push({name:`${u.name}-config`,content:De($)}),console.log();for(let i of e){d(`Installing ${i.name}...`);let a=yt(i.name,i.content,{dir:n.dir,local:n.local,force:n.force},i.references);if(a.installed.length>0&&(b(`${i.name} installed successfully`),v(" Installed to",a.installed.join(", "))),a.skipped.length>0&&(d(` Skipped (already exists): ${a.skipped.join(", ")}`),console.log(U.gray(" Use --force to overwrite"))),a.errors.length>0)for(let s of a.errors)c(` ${s}`);a.installed.length===0&&a.skipped.length===0&&a.errors.length===0&&(d(" No supported AI coding assistants detected"),console.log(U.gray(" Supported editors: "+So().join(", "))),console.log(U.gray(" Use --dir <path> to install to a specific directory"))),console.log()}if(!t||t==="video"){for(let i of[{name:"video-scene-builder",generate:$o},{name:"video-scene-reviewer",generate:ko},{name:"video-audio-director",generate:Io},{name:"video-continuity-editor",generate:Ro}]){d(`Installing ${i.name} agent...`);let a=To(i.name,i.generate(),{force:n.force});a.installed.length>0&&(b(`${i.name} agent installed`),v(" Installed to",a.installed.map(s=>`${s} (~/.claude/agents/)`).join(", "))),a.skipped.length>0&&d(` Agent skipped (already exists): ${a.skipped.join(", ")}`)}console.log()}});Ue.command("show").description("Display skill content").argument("[type]","Skill type (default: main)").action((t="main")=>{let e={main:()=>ut($),video:()=>pt($),presentation:()=>ft($),tts:()=>Ie($),music:()=>Re($),sfx:()=>Ae($),mix:()=>Oe($),image:()=>Ee($),"video-search":()=>Pe($),scrape:()=>Fe($),branding:()=>Ne($),config:()=>De($)}[t];e?console.log(e()):(c(`Invalid skill type: ${t}. Must be one of: ${ze.join(", ")}`),process.exit(1))});Ue.command("uninstall").description(`Remove ${u.displayName} skills from AI coding assistants`).argument("[type]","Skill type: main, video, presentation, or omit for all").option("-g, --global","Uninstall globally (from home directory)",!0).option("-l, --local","Uninstall locally (from current directory)").action(async(t,n)=>{let e=[];t&&!ze.includes(t)&&(c(`Invalid skill type: ${t}. Must be one of: ${ze.join(", ")}`),process.exit(1)),(!t||t==="main")&&e.push(u.name),(!t||t==="video")&&(e.push(`${u.name}-video`),e.push("remotion-scene-craft")),(!t||t==="presentation")&&e.push(`${u.name}-presentation`),(!t||t==="tts")&&e.push(`${u.name}-tts`),(!t||t==="music")&&e.push(`${u.name}-music`),(!t||t==="sfx")&&e.push(`${u.name}-sfx`),(!t||t==="mix")&&e.push(`${u.name}-mix`),(!t||t==="image")&&e.push(`${u.name}-image`),(!t||t==="video-search")&&e.push(`${u.name}-video-search`),(!t||t==="scrape")&&e.push(`${u.name}-scrape`),(!t||t==="branding")&&e.push(`${u.name}-branding`),(!t||t==="config")&&e.push(`${u.name}-config`),console.log();for(let o of e){let r=bt(o,{local:n.local});if(r.removed.length>0?(b(`${o} uninstalled`),v(" Removed from",r.removed.join(", "))):d(` ${o} not found`),r.errors.length>0)for(let i of r.errors)R(` Failed to remove: ${i}`)}if(!t||t==="video")for(let o of["video-scene-builder","video-scene-reviewer","video-audio-director","video-continuity-editor"])Co(o).removed.length>0&&b(`${o} agent uninstalled`);console.log()});import{Command as Ht}from"commander";import Po from"ora";import{writeFile as bi}from"fs/promises";var Oo="en-US-Neural2-C",Eo="gemini",vi=new Ht("generate").description("Generate speech from text").requiredOption("-t, --text <text>","Text to convert to speech").requiredOption("-o, --output <path>","Output file path").option("--voice-id <voiceId>","Voice ID (e.g., ElevenLabs: 21m00Tcm4TlvDq8ikWAM)").option("-v, --voice <voice>",`Voice name (default: ${Oo})`).option("-p, --provider <provider>",`Provider: gemini, elevenlabs, openai (default: ${Eo})`).option("-m, --model <model>","Model (provider-specific)").option("-s, --speed <speed>","Speech speed 0.25-4.0 (default: 1.0)").option("--speakers <json>",`Multi-speaker (Gemini, max 2): '[{"name":"Joe","voice":"Kore"},{"name":"Jane","voice":"Puck"}]'`).option("-i, --instructions <text>","Style prompt (OpenAI gpt-4o-mini-tts): tone, accent, speed, emotion").option("-f, --format <format>","Output format: human, json, quiet","human").action(async t=>{let n=t.format,e=n==="human"?Po("Generating speech...").start():null,o;t.speed&&(o=parseFloat(t.speed),(isNaN(o)||o<.25||o>4)&&(e?.stop(),c("Speed must be between 0.25 and 4.0"),process.exit(S.INVALID_INPUT)));let r;if(t.speakers)try{r=JSON.parse(t.speakers),(!Array.isArray(r)||r.length>2)&&(e?.stop(),c("Speakers must be an array with max 2 items"),process.exit(S.INVALID_INPUT))}catch{e?.stop(),c(`Invalid --speakers JSON. Example: '[{"name":"Joe","voice":"Kore"}]'`),process.exit(S.INVALID_INPUT)}try{let i=t.voiceId,a=t.voice||Oo,s=t.provider||Eo,l={};r&&(l.gemini={speakers:r}),t.instructions&&(l.openai={instructions:t.instructions});let m=await st({text:t.text,options:{provider:s,voiceId:i,voice:a,model:t.model,speed:o,providerOptions:Object.keys(l).length>0?l:void 0}});e?.stop();let p=t.output.endsWith(`.${m.format}`)?t.output:`${t.output}.${m.format}`;if(await bi(p,m.audioData),n==="json"){_({status:"completed",output:p,duration:m.duration,cost:m.cost,provider:m.provider,format:m.format});return}if(n==="quiet"){console.log(p);return}b(`Saved to: ${p}`),d(`Duration: ${m.duration.toFixed(2)}s`),d(`Provider: ${m.provider}`),d(`Cost: $${m.cost.toFixed(6)}`)}catch(i){e?.stop(),c(i instanceof Error?i.message:"Unknown error"),process.exit(S.GENERAL_ERROR)}}),xi=new Ht("voices").description("List available voices").option("-p, --provider <provider>","Filter by provider: gemini, elevenlabs, openai").option("-f, --format <format>","Output format: human, json","human").action(async t=>{let n=t.format==="human"?Po("Fetching voices...").start():null;try{let e=await jn(t.provider);if(n?.stop(),t.format==="json"){_(e.voices);return}if(Array.isArray(e.voices)){let o=t.provider?.toUpperCase()||"VOICES";console.log(),console.log(`${o} Voices:`),console.log("-".repeat(50));for(let r of e.voices)console.log(` ${r.name} (${r.id})`),console.log(` ${r.description}`)}else for(let[o,r]of Object.entries(e.voices))if(!(!r||r.length===0)){console.log(),console.log(`${o.toUpperCase()} Voices:`),console.log("-".repeat(50));for(let i of r)console.log(` ${i.name} (${i.id})`),console.log(` ${i.description}`)}}catch(e){n?.stop(),c(e instanceof Error?e.message:"Unknown error"),process.exit(S.GENERAL_ERROR)}}),Fo=new Ht("tts").description("Text-to-speech commands").addCommand(vi).addCommand(xi);import{Command as Xt}from"commander";import Kt from"ora";import{writeFile as No}from"fs/promises";function zt(t,n){if(n==="json"){_(t);return}if(n==="quiet"){t.audioUrl?console.log(t.audioUrl):console.log(t.requestId);return}d(`Request ID: ${t.requestId}`),d(`Status: ${t.status}`),t.duration&&d(`Duration: ${t.duration}s`),t.audioUrl&&b(`Audio URL: ${t.audioUrl}`),t.cost!==void 0&&d(`Cost: $${t.cost.toFixed(4)}`),t.error&&c(`Error: ${t.error}`)}async function wi(t,n){if(t.startsWith("data:")){let r=t.match(/^data:[^;]+;base64,(.+)$/);if(!r)throw new Error("Invalid data URL format");let i=Buffer.from(r[1],"base64");await No(n,i);return}let e=await fetch(t);if(!e.ok)throw new Error(`Failed to download: ${e.status}`);let o=await e.arrayBuffer();await No(n,Buffer.from(o))}var Si=new Xt("generate").description("Generate music from a text prompt").requiredOption("-p, --prompt <text>","Music description").option("-d, --duration <seconds>","Duration in seconds (3-600)","30").option("-s, --style <style>","Style preset").option("--provider <provider>","Provider (elevenlabs, suno)").option("-o, --output <path>","Output file path").option("--no-wait","Do not wait for completion").option("-f, --format <format>","Output format: human, json, quiet","human").action(async t=>{let n=parseInt(t.duration,10);(isNaN(n)||n<3||n>600)&&(c("Duration must be between 3 and 600 seconds (10 minutes)"),process.exit(S.INVALID_INPUT));let e=t.format,o=e==="human"?Kt("Generating music...").start():null;try{let r=await ct({prompt:t.prompt,duration:n,options:{provider:t.provider,style:t.style}});if(!t.wait){o?.stop(),zt(r,e);return}let i=r;if(r.status!=="completed"&&r.status!=="failed"&&(o&&(o.text=`Processing (ID: ${r.requestId})...`),i=await de(()=>Je(r.requestId),60,2e3)),o?.stop(),i.status==="failed"&&(c(i.error||"Music generation failed"),process.exit(S.GENERAL_ERROR)),zt(i,e),t.output&&i.audioUrl){let a=e==="human"?Kt("Downloading...").start():null;try{await wi(i.audioUrl,t.output),a?.stop(),e==="human"&&b(`Saved to: ${t.output}`)}catch(s){a?.stop(),R(`Failed to download: ${s instanceof Error?s.message:"Unknown error"}`)}}}catch(r){o?.stop(),c(r instanceof Error?r.message:"Unknown error"),process.exit(S.GENERAL_ERROR)}}),Ti=new Xt("status").description("Check status of a music generation request").argument("<id>","Request ID").option("-f, --format <format>","Output format: human, json, quiet","human").action(async(t,n)=>{let e=n.format==="human"?Kt("Checking status...").start():null;try{let o=await Je(t);e?.stop(),zt(o,n.format)}catch(o){e?.stop(),c(o instanceof Error?o.message:"Unknown error"),process.exit(S.GENERAL_ERROR)}}),Do=new Xt("music").description("Music generation commands").addCommand(Si).addCommand(Ti);import{Command as en}from"commander";import Qt from"ora";import{writeFile as Ci}from"fs/promises";function Zt(t,n){if(n==="json"){_(t);return}if(n==="quiet"){t.outputUrl?console.log(t.outputUrl):console.log(t.requestId);return}d(`Request ID: ${t.requestId}`),d(`Status: ${t.status}`),t.duration&&d(`Duration: ${t.duration}s`),t.outputUrl&&b(`Output URL: ${t.outputUrl}`),t.cost!==void 0&&d(`Cost: $${t.cost.toFixed(4)}`),t.error&&c(`Error: ${t.error}`)}async function ki(t,n){let e=await fetch(t);if(!e.ok)throw new Error(`Failed to download: ${e.status}`);let o=await e.arrayBuffer();await Ci(n,Buffer.from(o))}var $i=new en("create").description("Mix audio tracks into a video (music will loop to match video duration)").requiredOption("--video <url>","Input video file/URL").option("--music <url>","Background music file/URL (will loop if shorter than video)").option("--voice <url>","Voiceover file/URL").option("--music-volume <percent>","Music volume 0-100 (default: 30, recommended for mix with voice)","30").option("--voice-volume <percent>","Voice volume 0-100","100").option("-o, --output <path>","Output file path").option("--no-wait","Do not wait for completion").option("-f, --format <format>","Output format: human, json, quiet","human").action(async t=>{!t.music&&!t.voice&&(c("At least one of --music or --voice must be provided"),process.exit(S.INVALID_INPUT));let n=parseInt(t.musicVolume,10)/100,e=parseInt(t.voiceVolume,10)/100;(isNaN(n)||n<0||n>1)&&(c("Music volume must be between 0 and 100"),process.exit(S.INVALID_INPUT)),(isNaN(e)||e<0||e>2)&&(c("Voice volume must be between 0 and 200"),process.exit(S.INVALID_INPUT));let o=t.format,r=o==="human"?Qt("Mixing audio...").start():null,i=[{url:t.video,role:"video"}];t.music&&i.push({url:t.music,role:"background",volume:n*5}),t.voice&&i.push({url:t.voice,role:"voice",volume:e*2});try{let a=await Vn({operation:"add-to-video",inputs:i,options:{musicVolume:n,voiceVolume:e}});if(!t.wait){r?.stop(),Zt(a,o);return}r&&(r.text=`Processing (ID: ${a.requestId})...`);let s=await de(()=>Ut(a.requestId),120,3e3);if(r?.stop(),s.status==="failed"&&(c(s.error||"Audio mixing failed"),process.exit(S.GENERAL_ERROR)),Zt(s,o),t.output&&s.outputUrl){let l=o==="human"?Qt("Downloading...").start():null;try{await ki(s.outputUrl,t.output),l?.stop(),o==="human"&&b(`Saved to: ${t.output}`)}catch(m){l?.stop(),R(`Failed to download: ${m instanceof Error?m.message:"Unknown error"}`)}}}catch(a){r?.stop(),c(a instanceof Error?a.message:"Unknown error"),process.exit(S.GENERAL_ERROR)}}),Ii=new en("status").description("Check status of an audio mix request").argument("<id>","Request ID").option("-f, --format <format>","Output format: human, json, quiet","human").action(async(t,n)=>{let e=n.format==="human"?Qt("Checking status...").start():null;try{let o=await Ut(t);e?.stop(),Zt(o,n.format)}catch(o){e?.stop(),c(o instanceof Error?o.message:"Unknown error"),process.exit(S.GENERAL_ERROR)}}),Uo=new en("mix").description("Audio mixing commands").addCommand($i).addCommand(Ii);import{Command as Lo}from"commander";import Ri from"ora";var Ai=new Lo("search").description("Search for images").requiredOption("-q, --query <query>","Search query").option("-n, --max-results <number>","Maximum number of results (default: 10)").option("-s, --size <size>","Image size: small, medium, large, any","large").option("--safe-search","Enable safe search (default: true)",!0).option("--no-safe-search","Disable safe search").option("-f, --format <format>","Output format: human, json, quiet","human").action(async t=>{let n=t.format,e=n==="human"?Ri("Searching for images...").start():null,o;t.maxResults&&(o=parseInt(t.maxResults,10),(isNaN(o)||o<1)&&(e?.stop(),c("Max results must be a positive number"),process.exit(S.INVALID_INPUT)));try{let r=await lt({query:t.query,options:{maxResults:o||10,size:t.size,safeSearch:t.safeSearch}});e?.stop(),r.success||(c("Search failed"),process.exit(S.GENERAL_ERROR));let i=r.data.results.flatMap(a=>a.results.map(s=>({...s,provider:a.providerName})));if(n==="json"){_({success:!0,query:t.query,totalResults:i.length,totalCost:r.data.totalCost,images:i});return}if(n==="quiet"){for(let a of i)console.log(a.url);return}if(i.length===0){d("No images found");return}b(`Found ${i.length} images for "${t.query}"`),console.log();for(let a=0;a<i.length;a++){let s=i[a];console.log(`[${a+1}] ${s.title||"Untitled"}`),console.log(` URL: ${s.url}`),console.log(` Size: ${s.width}x${s.height}`),s.author&&console.log(` Author: ${s.author}`),console.log(` Provider: ${s.provider}`),console.log()}d(`Total cost: $${r.data.totalCost.toFixed(4)}`)}catch(r){e?.stop(),c(r instanceof Error?r.message:"Unknown error"),process.exit(S.GENERAL_ERROR)}}),Mo=new Lo("image").description("Image search commands").addCommand(Ai);import{Command as se}from"commander";import Qe from"ora";import{mkdir as _e,writeFile as ae,readFile as Ct,access as nn,rm as on}from"fs/promises";import{join as V,resolve as Xe}from"path";import{execSync as Ve,spawn as Bi,spawnSync as Gi}from"child_process";import Wi from"ffmpeg-static";import{homedir as Oi}from"os";import{join as jo}from"path";import{readdir as Ei}from"fs/promises";var Pi="conceptcraft";function Le(){return jo(Oi(),`.${Pi}`,"projects")}function vt(t){return jo(Le(),t)}async function tn(){try{let t=Le();return(await Ei(t,{withFileTypes:!0})).filter(e=>e.isDirectory()).map(e=>e.name)}catch{return[]}}function Vo(t,n){let e=2,o=`${t}-${e}`;for(;n.includes(o);)e++,o=`${t}-${e}`;return o}import{Command as Fi}from"commander";import Ni from"ora";import{execSync as Di,spawnSync as Me}from"child_process";import{mkdirSync as _o,existsSync as wt,readFileSync as Bo,writeFileSync as qo}from"fs";import{join as be,basename as Ui}from"path";import{tmpdir as Li}from"os";function St(t,n){try{return Di(n,{stdio:"ignore"}),!0}catch{return!1}}function xt(t){let n=Math.floor(t/60),e=Math.floor(t%60),o=Math.round(t%1*10);return`${n}:${String(e).padStart(2,"0")}.${o}`}function Mi(t){let n=Me("ffprobe",["-v","error","-show_entries","format=duration","-of","default=noprint_wrappers=1:nokey=1",t],{encoding:"utf-8"});return parseFloat(n.stdout.trim())}function ji(t,n){let e=Me("ffmpeg",["-i",t,"-vf",`select='gt(scene,${n})',showinfo`,"-vsync","vfr","-f","null","-"],{encoding:"utf-8",timeout:12e4}),o=[0],r=(e.stderr||"").split(`
|
|
3299
|
+
`);for(let i of r){let a=i.match(/pts_time:([\d.]+)/);if(a){let s=parseFloat(a[1]);s-o[o.length-1]>=1.5&&o.push(s)}}return o}function Vi(t,n,e){let o=[];for(let r=0;r<n.length;r++){let i=n[r]+.5,a=be(e,`scene_${String(r+1).padStart(2,"0")}.jpg`);Me("ffmpeg",["-ss",String(i),"-i",t,"-vframes","1","-q:v","2","-y",a],{stdio:"ignore",timeout:3e4}),o.push(a)}return o}function _i(t,n){let e=be(n,"audio.wav");if(Me("ffmpeg",["-i",t,"-vn","-acodec","pcm_s16le","-ar","16000","-ac","1","-y",e],{stdio:"ignore",timeout:6e4}),!wt(e)||!St("whisper","which whisper")&&!St("whisper-cpp","which whisper-cpp"))return null;let r=Me("whisper",[e,"--model","base","--output_format","json","--output_dir",n,"--language","en"],{encoding:"utf-8",timeout:3e5}),i=be(n,"audio.json");if(!wt(i))return null;try{let a=JSON.parse(Bo(i,"utf-8"));return{text:a.text||"",segments:(a.segments||[]).map(s=>({start:s.start,end:s.end,text:s.text?.trim()||""}))}}catch{return null}}function qi(t,n){let e=Bo(t).toString("base64"),o=JSON.stringify({model:n,prompt:"Describe this image in one short sentence (max 15 words). Focus on: what is shown, the setting, key objects or people.",images:[e],stream:!1}),r=Me("curl",["-s","-X","POST","http://localhost:11434/api/generate","-H","Content-Type: application/json","-d",o],{encoding:"utf-8",timeout:6e4});try{return(JSON.parse(r.stdout).response||"").replace(/!!!IMAGE!!!/gi,"").replace(/\[image[^\]]*\]/gi,"").trim()||"Unable to describe frame"}catch{return"Unable to describe frame"}}var Go=new Fi("analyze").description("Analyze a video file: detect scenes, transcribe audio, describe frames").argument("<file>","Path to video file").option("-o, --output <path>","Output directory for analysis results").option("-t, --threshold <number>","Scene detection threshold (0-1, lower = more scenes)","0.3").option("-m, --model <name>","Ollama vision model","moondream").option("--no-vision","Skip frame description (faster)").option("--no-transcribe","Skip audio transcription").option("-f, --output-format <format>","Output format: human, json","human").action(async(t,n)=>{let e=Ni();wt(t)||(console.error(`File not found: ${t}`),process.exit(1)),St("ffmpeg","which ffmpeg")||(console.error("ffmpeg is required. Install: brew install ffmpeg"),process.exit(1)),n.vision!==!1&&!St("ollama","which ollama")&&(console.error("ollama is required for vision. Install: brew install ollama"),process.exit(1));let o=n.output||be(Li(),`video-analyze-${Date.now()}`);_o(o,{recursive:!0});let r=be(o,"frames");_o(r,{recursive:!0});let i=parseFloat(n.threshold),a=n.model;e.start("Reading video metadata...");let s=Mi(t);e.succeed(`Video: ${xt(s)} (${s.toFixed(1)}s)`),e.start("Detecting scenes...");let l=ji(t,i);e.succeed(`Detected ${l.length} scenes`),e.start("Extracting key frames...");let m=Vi(t,l,r);e.succeed(`Extracted ${m.length} key frames`);let p=null;n.transcribe!==!1&&(e.start("Transcribing audio..."),p=_i(t,o),p?e.succeed(`Transcribed: ${p.segments.length} segments`):e.warn("Transcription skipped (whisper not installed: pip install openai-whisper)"));let k=[];for(let f=0;f<l.length;f++){let x=l[f],I=f<l.length-1?l[f+1]:s,T="";n.vision!==!1&&wt(m[f])&&(e.start(`Describing scene ${f+1}/${l.length}...`),T=qi(m[f],a),e.succeed(`Scene ${f+1}: ${T}`)),k.push({id:f+1,startTime:x,endTime:I,duration:I-x,framePath:m[f],description:T})}let h={source:t,duration:s,scenes:k,transcript:p};if(n.outputFormat==="json"){let f=JSON.stringify(h,null,2),x=be(o,"analysis.json");qo(x,f),console.log(f)}else{let f=["# Video Analysis","",`**Source:** ${Ui(t)}`,`**Duration:** ${xt(s)} (${s.toFixed(1)}s)`,`**Scenes:** ${k.length}`,"","## Scenes",""];for(let T of k){if(f.push(`### Scene ${T.id} (${xt(T.startTime)} - ${xt(T.endTime)}) [${T.duration.toFixed(1)}s]`),T.description&&f.push(`**Visual:** ${T.description}`),p?.segments){let y=p.segments.filter(A=>A.end>T.startTime&&A.start<T.endTime);y.length>0&&f.push(`**Audio:** ${y.map(A=>A.text).join(" ").trim()}`)}f.push(`**Frame:** ${T.framePath}`),f.push("")}p?.text&&(f.push("## Full Transcript"),f.push(""),f.push(p.text.trim()),f.push(""));let x=f.join(`
|
|
3300
|
+
`),I=be(o,"analysis.md");qo(I,x),console.log(x),console.log(`
|
|
3301
|
+
Results saved to: ${o}`)}});var Ji="inizio-inc/remotion-video-template#main",je=30;async function Yi(t){let n=await Ct(t,"utf-8");return JSON.parse(n)}async function Hi(t){let n=V(t,"template.manifest.json");try{return await Yi(n)}catch{return{entry:"src/Root.tsx",compositions:[{id:"Main",type:"youtube",aspect:"16:9",default:!0}]}}}function Wo(){try{return Ve("bun --version",{stdio:"ignore"}),!0}catch{return!1}}function Ki(t){if(t.includes("---")||t.includes("[Section")){let r=t.split(/---|\[Section \d+\]/i).filter(i=>i.trim());if(r.length>1)return r.map(i=>i.trim())}let n=t.split(/(?<=[.!?])\s+/).map(r=>r.trim()).filter(r=>r.length>0),e=[],o="";for(let r of n){let i=r.split(/\s+/).length;o?(e.push(`${o} ${r}`),o=""):i<5&&e.length<n.length-1?o=r:e.push(r)}return o&&(e.length>0?e[e.length-1]+=` ${o}`:e.push(o)),e}function zi(t,n,e=je,o){if(o&&o.characters.length>0)return Xi(t,o,e);let r=t.reduce((a,s)=>a+s.split(/\s+/).length,0),i=0;return t.map((a,s)=>{let l=a.split(/\s+/).length,m=l/r,p=n*m,k=Math.round(p*e),h=a.split(""),f=p/h.length,x={characters:h,characterStartTimesSeconds:h.map((T,y)=>y*f),characterEndTimesSeconds:h.map((T,y)=>(y+1)*f)},I={id:s+1,text:a,wordCount:l,startTime:i,endTime:i+p,durationInSeconds:p,durationInFrames:k,timestamps:x};return i+=p,I})}function Xi(t,n,e){let{characters:o,characterStartTimesSeconds:r,characterEndTimesSeconds:i}=n,a=o.join(""),s=[],l=0;for(let m=0;m<t.length;m++){let p=t[m],k=p.length;for(;l<o.length&&o[l].match(/^\s*$/);)l++;let h=l,f=r[h]||0;l+=k;let x=l-1;for(;x>h&&o[x]?.match(/^\s*$/);)x--;let I=i[Math.min(x,i.length-1)]||f+1,T=I-f,y=Math.round(T*e),A={characters:o.slice(h,x+1),characterStartTimesSeconds:r.slice(h,x+1).map(C=>C-f),characterEndTimesSeconds:i.slice(h,x+1).map(C=>C-f)};s.push({id:m+1,text:p,wordCount:p.split(/\s+/).length,startTime:f,endTime:I,durationInSeconds:T,durationInFrames:y,timestamps:A})}return s}function Qi(t){let n=t==="tiktok"?"vertical":t==="audiogram"?"square":"widescreen";return{scenes:[{name:"Hook",script:"Replace this with a bold opening line that grabs attention.",imageQuery:`cinematic ${n} intro visual, dramatic lighting`,videoQuery:`abstract ${n} motion background`},{name:"Proof",script:"Share the core insight, benefit, or product moment you want viewers to remember.",imageQuery:`people collaborating ${n} office, warm lighting`,videoQuery:`close-up hands using technology ${n}`},{name:"Close",script:"End with a crisp call to action and a confident closing line.",imageQuery:`bold typography ${n} background, high contrast`,videoQuery:`sunrise skyline ${n} timelapse`}],voice:"en-US-Neural2-C",voiceSettings:{speed:1,stability:.4,style:.6},musicPrompt:"uplifting cinematic background, modern and confident"}}async function Zi(t,n,e){let o=V(t,"public"),r=V(t,"scripts");await _e(o,{recursive:!0}),await _e(r,{recursive:!0});let i=V(o,"scenes.json");await ae(i,JSON.stringify(Qi(n),null,2),"utf-8");let a=`import { execSync } from "node:child_process";
|
|
2780
3302
|
|
|
2781
3303
|
execSync("${e} video generate plan --project .", { stdio: "inherit" });
|
|
2782
3304
|
`,s=`import { execSync } from "node:child_process";
|
|
2783
3305
|
|
|
2784
3306
|
const args = process.argv.slice(2).join(" ");
|
|
2785
3307
|
execSync("${e} video export --project . " + args, { stdio: "inherit" });
|
|
2786
|
-
`;await ae(q(r,"plan.ts"),a,"utf-8"),await ae(q(r,"export.ts"),s,"utf-8")}async function Un(){return process.stdin.isTTY?null:new Promise(t=>{let o="",e=!1;process.stdin.setEncoding("utf-8"),process.stdin.on("data",n=>{o+=n}),process.stdin.on("end",()=>{e||(e=!0,t(o.trim()||null))}),process.stdin.on("error",()=>{e||(e=!0,t(null))}),setTimeout(()=>{!e&&!o&&(e=!0,t(null))},500)})}function Ei(t){return t.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}function Fi(t){let o={shortTitle:"Video",elements:[],audio:[],text:[],scenes:[]},e=!0;return t.forEach(n=>{let r=n.startTime*1e3,i=n.endTime*1e3,a=n.durationInSeconds*1e3;o.scenes.push({name:n.name||`Scene ${n.id}`,text:n.text,startMs:r,endMs:i}),n.imagePath?(o.elements.push({startMs:r,endMs:i,imageUrl:n.imagePath,enterTransition:"blur",exitTransition:"blur",animations:[{type:"scale",from:e?1.3:1,to:e?1:1.3,startMs:0,endMs:a}]}),e=!e):n.videoPath&&o.elements.push({startMs:r,endMs:i,videoUrl:n.videoPath,enterTransition:"blur",exitTransition:"blur",animations:[]}),n.audioPath&&o.audio.push({startMs:r,endMs:i,audioUrl:n.audioPath}),n.timestamps&&o.text.push({startMs:r,endMs:i,text:n.text,position:"bottom",animations:[],timestamps:n.timestamps})}),o}async function yt(t,o){if(t.startsWith("data:")){let r=t.match(/^data:[^;]+;base64,(.+)$/);if(!r)throw new Error("Invalid data URL format");let i=Buffer.from(r[1],"base64");await ae(o,i);return}let e=await fetch(t);if(!e.ok)throw new Error(`Failed to download: ${e.status}`);let n=await e.arrayBuffer();await ae(o,Buffer.from(n))}function Ui(t){try{let n=new URL(t).pathname.split(".").pop()?.toLowerCase();if(n&&["jpg","jpeg","png","gif","webp"].includes(n))return n}catch{}return"jpg"}var Di=new se("assets").description("Generate assets (voiceover per scene, music, images, video)").option("-s, --script <text>","Narration script (legacy single-script mode)").option("--script-file <path>","Path to script file (legacy) or scenes JSON").option("--scenes <path>","Path to scenes.json (defaults to <project>/public/scenes.json)").option("-p, --project <dir>","Project directory (defaults to current directory; uses <dir>/public for output)").option("-t, --topic <text>","Topic for image search").option("-v, --voice <name>","TTS voice ID (ElevenLabs: Rachel, Josh, Adam; OpenAI: alloy, nova; Gemini: Kore, Puck)").option("-m, --music-prompt <text>","Music description").option("-n, --num-images <number>","Number of images to search/download","5").option("-o, --output <dir>","Output directory","./public").option("-f, --format <format>","Output format: human, json, quiet","human").action(async t=>{let o=t.format,e=o==="human"?ze("Initializing...").start():null,n=t.project?Ye(process.cwd(),t.project):null;try{let r=await Un(),i=null;if(t.scenes&&!t.scriptFile&&(t.scriptFile=t.scenes),n&&(t.output=q(n,"public"),t.scriptFile?t.scriptFile=Ye(n,t.scriptFile):!t.script&&!r&&(t.scriptFile=q(n,"public","scenes.json"))),r)try{let b=JSON.parse(r);b.scenes&&Array.isArray(b.scenes)&&(i=b)}catch{}if(!i&&t.scriptFile)try{let b=await bt(t.scriptFile,"utf-8"),G=JSON.parse(b);G.scenes&&Array.isArray(G.scenes)&&(i=G)}catch{}let a=i?.voice||t.voice,s=i?.voiceId,d=i?.provider,u=i?.model,p=i?.voiceInstructions,k=i?.musicPrompt||t.musicPrompt||"uplifting background music, positive energy",y=i?.musicDuration,w=q(t.output,"audio"),$=q(t.output,"images"),I=q(t.output,"videos");e&&(e.text="Creating directories..."),await Me(w,{recursive:!0}),await Me($,{recursive:!0}),await Me(I,{recursive:!0});let T=0,x=[],D=0,S=[],f=[];if(i&&i.scenes.length>0){o==="human"&&(e?.stop(),l(`Processing ${i.scenes.length} scenes...`),e?.start());let b=i.scenes.map((z,j)=>{let re=z.script;return z.voiceStyle&&(re=`${z.voiceStyle} ${re}`),{text:re,id:`scene-${j}`}});e&&(e.text="Generating speech for all scenes...");let G=await Fo({texts:b,options:{provider:d,voice:a,voiceId:s,model:u,voiceSettings:i.voiceSettings,instructions:p}});T+=G.totalCost;let Y=0;for(let z=0;z<i.scenes.length;z++){let j=i.scenes[z],re=Ei(j.name),ee=G.results[z];e&&(e.text=`[${j.name}] Saving audio...`);let xt=q(w,`${re}.${ee.format}`);await ae(xt,ee.audioData);let we=ee.duration,Gn=Math.round(we*Ne),Ve={id:z+1,name:j.name,text:j.script,wordCount:j.script.split(/\s+/).length,startTime:Y,endTime:Y+we,durationInSeconds:we,durationInFrames:Gn,audioPath:`audio/${re}.${ee.format}`,timestamps:ee.timestamps};if(j.imageQuery){e&&(e.text=`[${j.name}] Searching image...`);try{let W=await at({query:j.imageQuery,options:{maxResults:1,size:"large",safeSearch:!0}}),Be=W.data.results.flatMap(_=>_.results);if(T+=W.data.totalCost,Be.length>0){let _=Be[0],Ge=Ui(_.url),ue=`${re}.${Ge}`,St=q($,ue);await yt(_.url,St),Ve.imagePath=`images/${ue}`,S.push({path:`images/${ue}`,url:_.url,width:_.width,height:_.height,query:j.imageQuery})}}catch(W){o==="human"&&(e?.stop(),R(`[${j.name}] Image search failed: ${W instanceof Error?W.message:"Unknown"}`),e?.start())}}if(j.videoQuery){e&&(e.text=`[${j.name}] Searching video...`);try{let W=await Ot({query:j.videoQuery,options:{maxResults:1,license:"free"}}),Be=W.data.results.flatMap(_=>_.results);if(T+=W.data.totalCost,Be.length>0){let _=Be[0],Ge=_.previewUrl||_.downloadUrl;if(Ge){let ue=`${re}.mp4`,St=q(I,ue);await yt(Ge,St),Ve.videoPath=`videos/${ue}`,f.push({path:`videos/${ue}`,url:Ge,width:_.width,height:_.height,duration:_.duration,query:j.videoQuery})}}}catch(W){o==="human"&&(e?.stop(),R(`[${j.name}] Video search failed: ${W instanceof Error?W.message:"Unknown"}`),e?.start())}}if(x.push(Ve),Y+=we,D+=we,o==="human"){e?.stop();let W=[`audio: ${we.toFixed(1)}s`,Ve.imagePath?"image":null,Ve.videoPath?"video":null].filter(Boolean).join(", ");g(` ${j.name}: ${W}`),e?.start()}}}else{let b=t.script;if(t.scriptFile)try{b=await bt(t.scriptFile,"utf-8")}catch(ee){e?.stop(),c(`Failed to read script file: ${ee instanceof Error?ee.message:"Unknown error"}`),process.exit(v.INVALID_INPUT)}(!b||b.trim().length===0)&&(e?.stop(),c("Provide scenes via stdin JSON, --scenes/--script-file with scenes JSON, or --script for legacy mode"),process.exit(v.INVALID_INPUT)),b=b.trim();let G=t.topic||b.split(".")[0].slice(0,50);e&&(e.text="Generating voiceover...");let Y=await rt({text:b,options:{voice:a}}),z=q(w,`voiceover.${Y.format}`);await ae(z,Y.audioData),T+=Y.cost,D=Y.duration;let j=Ii(b);x=Ri(j,Y.duration,Ne,Y.timestamps).map((ee,xt)=>({...ee,name:`Section${xt+1}`,audioPath:`audio/voiceover.${Y.format}`})),o==="human"&&(e?.stop(),g(`Voiceover: ${z} (${Y.duration.toFixed(1)}s)`),e?.start())}e&&(e.text="Creating timeline...");let A=Fi(x),B=Math.max(A.audio.length>0?Math.max(...A.audio.map(b=>b.endMs)):0,A.text.length>0?Math.max(...A.text.map(b=>b.endMs)):0,A.elements.length>0?Math.max(...A.elements.map(b=>b.endMs)):0),H=B/1e3,ne=y?Math.min(300,Math.max(3,y)):Math.min(300,Math.ceil(H));console.log("[Music Generation] Requesting music:",{prompt:k,requestedDuration:ne,totalAudioDuration:D,actualVideoDuration:H,timelineDurationMs:B});let be,qe;e&&(e.text="Generating music and thumbnail...");let qn=`YouTube thumbnail style: BIG BOLD short headline text (like "$50K/MONTH"), dramatic engaging scene, arrows pointing to key elements, high contrast, vibrant colors, clean composition, 16:9. Video context: ${x.map(b=>b.text).join(" ")}`,vt=[],Vn=(async()=>{if(ne<3)return o==="human"&&(e?.stop(),R(`Video duration (${H.toFixed(1)}s) is too short for music generation (minimum 3s).`),e?.start()),null;try{let b=await it({prompt:k,duration:ne});if(b.status!=="completed"&&b.status!=="failed"&&(b=await de(()=>_e(b.requestId),60,2e3)),b.status==="failed")throw new Error(b.error||"Unknown error");let G=q(w,"music.mp3");return b.audioUrl&&await yt(b.audioUrl,G),{path:"audio/music.mp3",duration:b.duration||ne,prompt:k,cost:b.cost||0}}catch(b){return o==="human"&&(e?.stop(),R(`Music generation failed: ${b.message}`),e?.start()),null}})();vt.push(Vn);let Bn=(async()=>{try{let b=await Lo({prompt:qn,options:{width:1280,height:720}});if(b.success&&b.data.url){let G=q(t.output,"thumbnail.jpg");return await yt(b.data.url,G),T+=b.data.cost||0,G}return null}catch(b){return o==="human"&&(e?.stop(),R(`Thumbnail generation failed: ${b.message}`),e?.start()),null}})();vt.push(Bn);let[ve,to]=await Promise.all(vt);ve&&(be=ve,T+=ve.cost||0,o==="human"&&(e?.stop(),g(`Music: ${q(w,"music.mp3")} (${ve.duration}s)`),ve.duration<H&&R(`Music duration (${ve.duration.toFixed(1)}s) is shorter than video duration (${H.toFixed(1)}s).`),e?.start())),to&&(qe=to,o==="human"&&(e?.stop(),g(`Thumbnail: ${qe}`),e?.start())),e&&(e.text="Writing manifest...");let oo=Math.round(H*Ne),no={music:be,thumbnail:qe?"thumbnail.jpg":void 0,images:S,videos:f,scenes:x,timeline:A,totalDurationInFrames:oo,fps:Ne,totalCost:T,createdAt:new Date().toISOString()},wt=q(t.output,"video-manifest.json");if(await ae(wt,JSON.stringify(no,null,2)),e?.stop(),o==="json"){V(no);return}if(o==="quiet"){console.log(wt);return}console.log(),g("Video assets created successfully!"),console.log(),l(`Scenes: ${x.length} (${oo} frames at ${Ne}fps)`);for(let b of x){let G=[b.audioPath?"audio":null,b.imagePath?"image":null,b.videoPath?"video":null].filter(Boolean).join(", ");l(` - ${b.name}: ${b.durationInSeconds.toFixed(1)}s [${G}]`)}l(`Music: ${be?.path} (${be?.duration}s)`),l(`Manifest: ${wt}`),console.log(),l(`Total cost: $${T.toFixed(4)}`)}catch(r){e?.stop(),c(r instanceof Error?r.message:"Unknown error"),process.exit(v.GENERAL_ERROR)}}),Li=new se("find").description("Find stock video clips (supporting)").argument("<query>","Search query").option("-n, --max-results <count>","Maximum number of results","10").option("-o, --orientation <type>","Video orientation: landscape, portrait, square, any","any").option("-l, --license <type>","License type: free, premium, any","free").option("-f, --format <format>","Output format: human, json, quiet","human").action(async(t,o)=>{let{maxResults:e,orientation:n,license:r,format:i}=o,a=i==="human"?ze("Finding videos...").start():null;try{let s=await Ot({query:t,options:{maxResults:parseInt(e,10),orientation:n,license:r}});a?.stop();let d=s.data.results.flatMap(u=>u.results);if(i==="json"){V(s);return}if(i==="quiet"){d.forEach(u=>{console.log(u.previewUrl||u.thumbnailUrl)});return}if(d.length===0){l("No videos found");return}g(`Found ${d.length} videos for "${t}"`),console.log(),d.forEach((u,p)=>{console.log(`[${p+1}] ${u.title}`),console.log(` URL: ${u.previewUrl||u.thumbnailUrl}`),console.log(` Duration: ${u.duration}s | Size: ${u.width}x${u.height}`),console.log(` Provider: ${u.provider}`),console.log()}),l(`Total cost: $${s.data.totalCost.toFixed(4)}`)}catch(s){a?.stop(),c(s instanceof Error?s.message:"Unknown error"),process.exit(v.GENERAL_ERROR)}}),Ni=new se("new").description("Create a new video project from a template").argument("<name>","Project directory name").option("-t, --template <repo>","GitHub repo (user/repo#commit)",Ci).option("--type <type>","Video type: youtube (16:9), tiktok (9:16), or audiogram (1:1)","youtube").option("--no-install","Skip dependency install").option("-f, --format <format>","Output format: human, json, quiet","human").action(async(t,o)=>{let e=o.format,n=e==="human"?ze("Initializing video project...").start():null;try{let r=Le();await Me(r,{recursive:!0});let i=ht(t),a=o.type,s=await Yt();if(s.includes(t)){let w=En(t,s);n?.stop(),c(`Project "${t}" already exists at: ${i}`),l(`Try: ${m.commands[0]} video generate new ${w}`),process.exit(v.INVALID_INPUT)}/^[a-zA-Z0-9_-]+\/[a-zA-Z0-9_.-]+(#[a-zA-Z0-9_.\/-]+)?$/.test(o.template)||(n?.stop(),c(`Invalid template format: "${o.template}". Expected format: owner/repo or owner/repo#branch`),process.exit(v.INVALID_INPUT)),["youtube","tiktok","audiogram"].includes(a)||(n?.stop(),c(`Invalid type "${a}". Use youtube, tiktok, or audiogram.`),process.exit(v.INVALID_INPUT)),n&&(n.text=`Cloning template from ${o.template}...`);let[u,p]=o.template.split("#"),k=p?`-b ${p}`:"";try{je(`git clone ${k} --depth 1 https://github.com/${u}.git "${i}"`,{stdio:"pipe"}),await Xt(q(i,".git"),{recursive:!0,force:!0})}catch(w){n?.stop(),c(`Failed to clone template: ${w instanceof Error?w.message:"Unknown error"}`),process.exit(v.GENERAL_ERROR)}e==="human"&&(n?.stop(),g(`Template downloaded to ${t}/`),n?.start());let y;try{y=await Ti(i)}catch{n?.stop(),c("template.manifest.json not found or invalid in template repo"),process.exit(v.INVALID_INPUT)}if(y.propsSchema&&y.propsSchema!=="video-plan.v1"&&(n?.stop(),c(`Template propsSchema must be video-plan.v1 (found ${y.propsSchema})`),process.exit(v.INVALID_INPUT)),n&&(n.text="Writing scaffold files..."),await Oi(i,a,m.commands[0]),o.install){let w=Fn()?"bun":"npm";n&&(n.text=`Installing dependencies with ${w}...`),await new Promise(($,I)=>{let T=xi(w,["install"],{cwd:i,stdio:"pipe",shell:!0});T.on("close",x=>{x===0?$():I(new Error(`${w} install failed with code ${x}`))}),T.on("error",I)}),e==="human"&&(n?.stop(),g("Dependencies installed"),n?.start())}if(n?.stop(),e==="json"){V({name:t,path:i,template:o.template,type:a,installed:o.install});return}if(e==="quiet"){console.log(i);return}console.log(),g(`Video project "${t}" created successfully!`),l(`Location: ${i}`),a==="tiktok"?l("Format: TikTok/Reels/Shorts (1080x1920 @ 30fps)"):a==="audiogram"?l("Format: Audiogram (1080x1080 @ 30fps)"):l("Format: YouTube (1920x1080 @ 30fps)"),console.log(),l("Next steps:"),l(` cd ${i}`),o.install||l(` ${Fn()?"bun":"npm"} install`),l(" bun run dev # Preview in Remotion Studio"),l(` ${m.commands[0]} video generate assets --scenes public/scenes.json`),l(` ${m.commands[0]} video generate plan`)}catch(r){n?.stop(),c(r instanceof Error?r.message:"Unknown error"),process.exit(v.GENERAL_ERROR)}}),ji=new se("plan").description("Generate video plan from context (via stdin or --context)").option("-c, --context <text>","Context for plan generation").option("-o, --output <path>","Save plan to file").option("-b, --branding <path>","Branding JSON file to include").action(async t=>{let o=ze("Generating video plan...").start();try{let e=t.context;if(!e){let a=await Un();a&&(e=a)}e||(o.stop(),c("Provide context via stdin or --context flag"),process.exit(v.INVALID_INPUT));let n;if(t.branding)try{n=await bt(t.branding,"utf-8")}catch{o.stop(),c(`Could not read branding file: ${t.branding}`),process.exit(v.INVALID_INPUT)}let r=await Mo({context:e,branding:n});o.stop(),r.success||(c(r.error||"Failed to generate plan"),process.exit(v.GENERAL_ERROR));let i=r.data;t.output?(await ae(t.output,i),g(`Plan saved to ${t.output}`)):console.log(i)}catch(e){o.stop(),c(e instanceof Error?e.message:"Unknown error"),process.exit(v.GENERAL_ERROR)}}),Mi=new se("generate").description("Generate parts of a video project").addCommand(Ni).addCommand(Di).addCommand(ji),qi=new se("projects").description("List all video projects").option("-f, --format <format>","Output format: human, json","human").action(async t=>{let o=await Yt();if(t.format==="json"){V({projectsDir:Le(),projects:o.map(e=>({name:e,path:ht(e)}))});return}if(o.length===0){l("No video projects found"),l(`Projects are stored in: ${Le()}`);return}console.log(`
|
|
2787
|
-
Video Projects:`),console.log("-".repeat(50));for(let e of
|
|
3308
|
+
`;await ae(V(r,"plan.ts"),a,"utf-8"),await ae(V(r,"export.ts"),s,"utf-8")}async function Jo(){return process.stdin.isTTY?null:new Promise(t=>{let n="",e=!1;process.stdin.setEncoding("utf-8"),process.stdin.on("data",o=>{n+=o}),process.stdin.on("end",()=>{e||(e=!0,t(n.trim()||null))}),process.stdin.on("error",()=>{e||(e=!0,t(null))}),setTimeout(()=>{!e&&!n&&(e=!0,t(null))},500)})}function ea(t){return t.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}function ta(t){let n={shortTitle:"Video",elements:[],audio:[],text:[],scenes:[]},e=!0;return t.forEach(o=>{let r=o.startTime*1e3,i=o.endTime*1e3,a=o.durationInSeconds*1e3;n.scenes.push({name:o.name||`Scene ${o.id}`,text:o.text,startMs:r,endMs:i}),o.imagePath?(n.elements.push({startMs:r,endMs:i,imageUrl:o.imagePath,enterTransition:"blur",exitTransition:"blur",animations:[{type:"scale",from:e?1.3:1,to:e?1:1.3,startMs:0,endMs:a}]}),e=!e):o.videoPath&&n.elements.push({startMs:r,endMs:i,videoUrl:o.videoPath,enterTransition:"blur",exitTransition:"blur",animations:[]}),o.audioPath&&n.audio.push({startMs:r,endMs:i,audioUrl:o.audioPath}),o.timestamps&&n.text.push({startMs:r,endMs:i,text:o.text,position:"bottom",animations:[],timestamps:o.timestamps})}),n}async function Tt(t,n){if(t.startsWith("data:")){let r=t.match(/^data:[^;]+;base64,(.+)$/);if(!r)throw new Error("Invalid data URL format");let i=Buffer.from(r[1],"base64");await ae(n,i);return}let e=await fetch(t);if(!e.ok)throw new Error(`Failed to download: ${e.status}`);let o=await e.arrayBuffer();await ae(n,Buffer.from(o))}function na(t){try{let o=new URL(t).pathname.split(".").pop()?.toLowerCase();if(o&&["jpg","jpeg","png","gif","webp"].includes(o))return o}catch{}return"jpg"}var oa=new se("assets").description("Generate assets (voiceover per scene, music, images, video)").option("-s, --script <text>","Narration script (legacy single-script mode)").option("--script-file <path>","Path to script file (legacy) or scenes JSON").option("--scenes <path>","Path to scenes.json (defaults to <project>/public/scenes.json)").option("-p, --project <dir>","Project directory (defaults to current directory; uses <dir>/public for output)").option("-t, --topic <text>","Topic for image search").option("-v, --voice <name>","TTS voice ID (ElevenLabs: Rachel, Josh, Adam; OpenAI: alloy, nova; Gemini: Kore, Puck)").option("-m, --music-prompt <text>","Music description").option("-n, --num-images <number>","Number of images to search/download","5").option("-o, --output <dir>","Output directory","./public").option("-f, --format <format>","Output format: human, json, quiet","human").action(async t=>{let n=t.format,e=n==="human"?Qe("Initializing...").start():null,o=t.project?Xe(process.cwd(),t.project):null;try{let r=await Jo(),i=null;if(t.scenes&&!t.scriptFile&&(t.scriptFile=t.scenes),o&&(t.output=V(o,"public"),t.scriptFile?t.scriptFile=Xe(o,t.scriptFile):!t.script&&!r&&(t.scriptFile=V(o,"public","scenes.json"))),r)try{let w=JSON.parse(r);w.scenes&&Array.isArray(w.scenes)&&(i=w)}catch{}if(!i&&t.scriptFile)try{let w=await Ct(t.scriptFile,"utf-8"),B=JSON.parse(w);B.scenes&&Array.isArray(B.scenes)&&(i=B)}catch{}let a=i?.voice||t.voice,s=i?.voiceId,l=i?.provider,m=i?.model,p=i?.voiceInstructions,k=i?.musicPrompt||t.musicPrompt||"uplifting background music, positive energy",h=i?.musicDuration,f=V(t.output,"audio"),x=V(t.output,"images"),I=V(t.output,"videos");e&&(e.text="Creating directories..."),await _e(f,{recursive:!0}),await _e(x,{recursive:!0}),await _e(I,{recursive:!0});let T=0,y=[],A=0,C=[],g=[];if(i&&i.scenes.length>0){n==="human"&&(e?.stop(),d(`Processing ${i.scenes.length} scenes...`),e?.start());let w=i.scenes.map((z,M)=>{let re=z.script;return z.voiceStyle&&(re=`${z.voiceStyle} ${re}`),{text:re,id:`scene-${M}`}});e&&(e.text="Generating speech for all scenes...");let B=await Mn({texts:w,options:{provider:l,voice:a,voiceId:s,model:m,voiceSettings:i.voiceSettings,instructions:p}});T+=B.totalCost;let K=0;for(let z=0;z<i.scenes.length;z++){let M=i.scenes[z],re=ea(M.name),ee=B.results[z];e&&(e.text=`[${M.name}] Saving audio...`);let It=V(f,`${re}.${ee.format}`);await ae(It,ee.audioData);let we=ee.duration,tr=Math.round(we*je),Be={id:z+1,name:M.name,text:M.script,wordCount:M.script.split(/\s+/).length,startTime:K,endTime:K+we,durationInSeconds:we,durationInFrames:tr,audioPath:`audio/${re}.${ee.format}`,timestamps:ee.timestamps};if(M.imageQuery){e&&(e.text=`[${M.name}] Searching image...`);try{let W=await lt({query:M.imageQuery,options:{maxResults:1,size:"large",safeSearch:!0}}),Ge=W.data.results.flatMap(G=>G.results);if(T+=W.data.totalCost,Ge.length>0){let G=Ge[0],We=na(G.url),ue=`${re}.${We}`,Rt=V(x,ue);await Tt(G.url,Rt),Be.imagePath=`images/${ue}`,C.push({path:`images/${ue}`,url:G.url,width:G.width,height:G.height,query:M.imageQuery})}}catch(W){n==="human"&&(e?.stop(),R(`[${M.name}] Image search failed: ${W instanceof Error?W.message:"Unknown"}`),e?.start())}}if(M.videoQuery){e&&(e.text=`[${M.name}] Searching video...`);try{let W=await Lt({query:M.videoQuery,options:{maxResults:1,license:"free"}}),Ge=W.data.results.flatMap(G=>G.results);if(T+=W.data.totalCost,Ge.length>0){let G=Ge[0],We=G.previewUrl||G.downloadUrl;if(We){let ue=`${re}.mp4`,Rt=V(I,ue);await Tt(We,Rt),Be.videoPath=`videos/${ue}`,g.push({path:`videos/${ue}`,url:We,width:G.width,height:G.height,duration:G.duration,query:M.videoQuery})}}}catch(W){n==="human"&&(e?.stop(),R(`[${M.name}] Video search failed: ${W instanceof Error?W.message:"Unknown"}`),e?.start())}}if(y.push(Be),K+=we,A+=we,n==="human"){e?.stop();let W=[`audio: ${we.toFixed(1)}s`,Be.imagePath?"image":null,Be.videoPath?"video":null].filter(Boolean).join(", ");b(` ${M.name}: ${W}`),e?.start()}}}else{let w=t.script;if(t.scriptFile)try{w=await Ct(t.scriptFile,"utf-8")}catch(ee){e?.stop(),c(`Failed to read script file: ${ee instanceof Error?ee.message:"Unknown error"}`),process.exit(S.INVALID_INPUT)}(!w||w.trim().length===0)&&(e?.stop(),c("Provide scenes via stdin JSON, --scenes/--script-file with scenes JSON, or --script for legacy mode"),process.exit(S.INVALID_INPUT)),w=w.trim();let B=t.topic||w.split(".")[0].slice(0,50);e&&(e.text="Generating voiceover...");let K=await st({text:w,options:{voice:a}}),z=V(f,`voiceover.${K.format}`);await ae(z,K.audioData),T+=K.cost,A=K.duration;let M=Ki(w);y=zi(M,K.duration,je,K.timestamps).map((ee,It)=>({...ee,name:`Section${It+1}`,audioPath:`audio/voiceover.${K.format}`})),n==="human"&&(e?.stop(),b(`Voiceover: ${z} (${K.duration.toFixed(1)}s)`),e?.start())}e&&(e.text="Creating timeline...");let O=ta(y),q=Math.max(O.audio.length>0?Math.max(...O.audio.map(w=>w.endMs)):0,O.text.length>0?Math.max(...O.text.map(w=>w.endMs)):0,O.elements.length>0?Math.max(...O.elements.map(w=>w.endMs)):0),H=q/1e3,oe=h?Math.min(300,Math.max(3,h)):Math.min(300,Math.ceil(H));console.log("[Music Generation] Requesting music:",{prompt:k,requestedDuration:oe,totalAudioDuration:A,actualVideoDuration:H,timelineDurationMs:q});let ve,qe;e&&(e.text="Generating music and thumbnail...");let Qo=`YouTube thumbnail style: BIG BOLD short headline text (like "$50K/MONTH"), dramatic engaging scene, arrows pointing to key elements, high contrast, vibrant colors, clean composition, 16:9. Video context: ${y.map(w=>w.text).join(" ")}`,kt=[],Zo=(async()=>{if(oe<3)return n==="human"&&(e?.stop(),R(`Video duration (${H.toFixed(1)}s) is too short for music generation (minimum 3s).`),e?.start()),null;try{let w=await ct({prompt:k,duration:oe});if(w.status!=="completed"&&w.status!=="failed"&&(w=await de(()=>Je(w.requestId),60,2e3)),w.status==="failed")throw new Error(w.error||"Unknown error");let B=V(f,"music.mp3");return w.audioUrl&&await Tt(w.audioUrl,B),{path:"audio/music.mp3",duration:w.duration||oe,prompt:k,cost:w.cost||0}}catch(w){return n==="human"&&(e?.stop(),R(`Music generation failed: ${w.message}`),e?.start()),null}})();kt.push(Zo);let er=(async()=>{try{let w=await _n({prompt:Qo,options:{width:1280,height:720}});if(w.success&&w.data.url){let B=V(t.output,"thumbnail.jpg");return await Tt(w.data.url,B),T+=w.data.cost||0,B}return null}catch(w){return n==="human"&&(e?.stop(),R(`Thumbnail generation failed: ${w.message}`),e?.start()),null}})();kt.push(er);let[xe,cn]=await Promise.all(kt);xe&&(ve=xe,T+=xe.cost||0,n==="human"&&(e?.stop(),b(`Music: ${V(f,"music.mp3")} (${xe.duration}s)`),xe.duration<H&&R(`Music duration (${xe.duration.toFixed(1)}s) is shorter than video duration (${H.toFixed(1)}s).`),e?.start())),cn&&(qe=cn,n==="human"&&(e?.stop(),b(`Thumbnail: ${qe}`),e?.start())),e&&(e.text="Writing manifest...");let ln=Math.round(H*je),dn={music:ve,thumbnail:qe?"thumbnail.jpg":void 0,images:C,videos:g,scenes:y,timeline:O,totalDurationInFrames:ln,fps:je,totalCost:T,createdAt:new Date().toISOString()},$t=V(t.output,"video-manifest.json");if(await ae($t,JSON.stringify(dn,null,2)),e?.stop(),n==="json"){_(dn);return}if(n==="quiet"){console.log($t);return}console.log(),b("Video assets created successfully!"),console.log(),d(`Scenes: ${y.length} (${ln} frames at ${je}fps)`);for(let w of y){let B=[w.audioPath?"audio":null,w.imagePath?"image":null,w.videoPath?"video":null].filter(Boolean).join(", ");d(` - ${w.name}: ${w.durationInSeconds.toFixed(1)}s [${B}]`)}d(`Music: ${ve?.path} (${ve?.duration}s)`),d(`Manifest: ${$t}`),console.log(),d(`Total cost: $${T.toFixed(4)}`)}catch(r){e?.stop(),c(r instanceof Error?r.message:"Unknown error"),process.exit(S.GENERAL_ERROR)}}),ra=new se("find").description("Find stock video clips (supporting)").argument("<query>","Search query").option("-n, --max-results <count>","Maximum number of results","10").option("-o, --orientation <type>","Video orientation: landscape, portrait, square, any","any").option("-l, --license <type>","License type: free, premium, any","free").option("-f, --format <format>","Output format: human, json, quiet","human").action(async(t,n)=>{let{maxResults:e,orientation:o,license:r,format:i}=n,a=i==="human"?Qe("Finding videos...").start():null;try{let s=await Lt({query:t,options:{maxResults:parseInt(e,10),orientation:o,license:r}});a?.stop();let l=s.data.results.flatMap(m=>m.results);if(i==="json"){_(s);return}if(i==="quiet"){l.forEach(m=>{console.log(m.previewUrl||m.thumbnailUrl)});return}if(l.length===0){d("No videos found");return}b(`Found ${l.length} videos for "${t}"`),console.log(),l.forEach((m,p)=>{console.log(`[${p+1}] ${m.title}`),console.log(` URL: ${m.previewUrl||m.thumbnailUrl}`),console.log(` Duration: ${m.duration}s | Size: ${m.width}x${m.height}`),console.log(` Provider: ${m.provider}`),console.log()}),d(`Total cost: $${s.data.totalCost.toFixed(4)}`)}catch(s){a?.stop(),c(s instanceof Error?s.message:"Unknown error"),process.exit(S.GENERAL_ERROR)}}),ia=new se("new").description("Create a new video project from a template").argument("<name>","Project directory name").option("-t, --template <repo>","GitHub repo (user/repo#commit)",Ji).option("--type <type>","Video type: youtube (16:9), tiktok (9:16), or audiogram (1:1)","youtube").option("--no-install","Skip dependency install").option("-f, --format <format>","Output format: human, json, quiet","human").action(async(t,n)=>{let e=n.format,o=e==="human"?Qe("Initializing video project...").start():null;try{let r=Le();await _e(r,{recursive:!0});let i=vt(t),a=n.type,s=await tn();if(s.includes(t)){let f=Vo(t,s);o?.stop(),c(`Project "${t}" already exists at: ${i}`),d(`Try: ${u.commands[0]} video generate new ${f}`),process.exit(S.INVALID_INPUT)}/^[a-zA-Z0-9_-]+\/[a-zA-Z0-9_.-]+(#[a-zA-Z0-9_.\/-]+)?$/.test(n.template)||(o?.stop(),c(`Invalid template format: "${n.template}". Expected format: owner/repo or owner/repo#branch`),process.exit(S.INVALID_INPUT)),["youtube","tiktok","audiogram"].includes(a)||(o?.stop(),c(`Invalid type "${a}". Use youtube, tiktok, or audiogram.`),process.exit(S.INVALID_INPUT)),o&&(o.text=`Cloning template from ${n.template}...`);let[m,p]=n.template.split("#"),k=p?`-b ${p}`:"";try{Ve(`git clone ${k} --depth 1 https://github.com/${m}.git "${i}"`,{stdio:"pipe"}),await on(V(i,".git"),{recursive:!0,force:!0})}catch(f){o?.stop(),c(`Failed to clone template: ${f instanceof Error?f.message:"Unknown error"}`),process.exit(S.GENERAL_ERROR)}e==="human"&&(o?.stop(),b(`Template downloaded to ${t}/`),o?.start());let h;try{h=await Hi(i)}catch{o?.stop(),c("template.manifest.json not found or invalid in template repo"),process.exit(S.INVALID_INPUT)}if(h.propsSchema&&h.propsSchema!=="video-plan.v1"&&(o?.stop(),c(`Template propsSchema must be video-plan.v1 (found ${h.propsSchema})`),process.exit(S.INVALID_INPUT)),o&&(o.text="Writing scaffold files..."),await Zi(i,a,u.commands[0]),n.install){let f=Wo()?"bun":"npm";o&&(o.text=`Installing dependencies with ${f}...`),await new Promise((x,I)=>{let T=Bi(f,["install"],{cwd:i,stdio:"pipe",shell:!0});T.on("close",y=>{y===0?x():I(new Error(`${f} install failed with code ${y}`))}),T.on("error",I)}),e==="human"&&(o?.stop(),b("Dependencies installed"),o?.start())}if(o?.stop(),e==="json"){_({name:t,path:i,template:n.template,type:a,installed:n.install});return}if(e==="quiet"){console.log(i);return}console.log(),b(`Video project "${t}" created successfully!`),d(`Location: ${i}`),a==="tiktok"?d("Format: TikTok/Reels/Shorts (1080x1920 @ 30fps)"):a==="audiogram"?d("Format: Audiogram (1080x1080 @ 30fps)"):d("Format: YouTube (1920x1080 @ 30fps)"),console.log(),d("Next steps:"),d(` cd ${i}`),n.install||d(` ${Wo()?"bun":"npm"} install`),d(" bun run dev # Preview in Remotion Studio"),d(` ${u.commands[0]} video generate assets --scenes public/scenes.json`),d(` ${u.commands[0]} video generate plan`)}catch(r){o?.stop(),c(r instanceof Error?r.message:"Unknown error"),process.exit(S.GENERAL_ERROR)}}),aa=new se("plan").description("Generate video plan from context (via stdin or --context)").option("-c, --context <text>","Context for plan generation").option("-o, --output <path>","Save plan to file").option("-b, --branding <path>","Branding JSON file to include").action(async t=>{let n=Qe("Generating video plan...").start();try{let e=t.context;if(!e){let a=await Jo();a&&(e=a)}e||(n.stop(),c("Provide context via stdin or --context flag"),process.exit(S.INVALID_INPUT));let o;if(t.branding)try{o=await Ct(t.branding,"utf-8")}catch{n.stop(),c(`Could not read branding file: ${t.branding}`),process.exit(S.INVALID_INPUT)}let r=await Gn({context:e,branding:o});n.stop(),r.success||(c(r.error||"Failed to generate plan"),process.exit(S.GENERAL_ERROR));let i=r.data;t.output?(await ae(t.output,i),b(`Plan saved to ${t.output}`)):console.log(i)}catch(e){n.stop(),c(e instanceof Error?e.message:"Unknown error"),process.exit(S.GENERAL_ERROR)}}),sa=new se("generate").description("Generate parts of a video project").addCommand(ia).addCommand(oa).addCommand(aa),ca=new se("projects").description("List all video projects").option("-f, --format <format>","Output format: human, json","human").action(async t=>{let n=await tn();if(t.format==="json"){_({projectsDir:Le(),projects:n.map(e=>({name:e,path:vt(e)}))});return}if(n.length===0){d("No video projects found"),d(`Projects are stored in: ${Le()}`);return}console.log(`
|
|
3309
|
+
Video Projects:`),console.log("-".repeat(50));for(let e of n)console.log(` ${e}`),console.log(` ${vt(e)}`);console.log()}),la=new se("inject").description("Inject thumbnail into video (first frame + file icon)").requiredOption("-i, --video <path>","Input video file").requiredOption("-t, --thumbnail <path>","Thumbnail image").option("-o, --output <path>","Output video (default: video-with-thumb.mp4)").option("-f, --format <format>","Output format: human, json, quiet","human").action(async t=>{let n=t.format,e=n==="human"?Qe("Injecting thumbnail...").start():null;try{let o=Xe(t.video),r=Xe(t.thumbnail),i=t.output?Xe(t.output):o.replace(/\.mp4$/,"-with-thumb.mp4");await nn(o),await nn(r);let a=Wi;if(a)try{await nn(a)}catch{a=null}if(!a)try{a=Ve("which ffmpeg",{encoding:"utf8"}).trim()}catch{throw new Error("ffmpeg not found. Install ffmpeg or ffmpeg-static.")}let s=Gi(a,["-i",o],{encoding:"utf8"});if(s.error)throw new Error(`Failed to probe video: ${s.error.message}`);let l=(s.stderr??"").match(/(\d{2,5})x(\d{2,5})/);if(!l)throw new Error("Could not detect video dimensions");let[,m,p]=l;e&&(e.text="Creating thumbnail frame...");let k="/tmp/cc-thumb-frame.mp4";Ve(`${a} -y -loop 1 -i "${r}" -vf "scale=${m}:${p}:force_original_aspect_ratio=decrease,pad=${m}:${p}:(ow-iw)/2:(oh-ih)/2,setsar=1" -t 0.033 -r 30 -c:v libx264 -pix_fmt yuv420p "${k}"`,{stdio:"pipe"}),e&&(e.text="Concatenating with video...");let h="/tmp/cc-concat-video.mp4";if(Ve(`${a} -y -i "${k}" -i "${o}" -filter_complex "[0:v][1:v]concat=n=2:v=1:a=0[v]" -map "[v]" -map 1:a -c:v libx264 -preset ultrafast -crf 18 -c:a copy "${h}"`,{stdio:"pipe"}),e&&(e.text="Adding file icon thumbnail..."),Ve(`${a} -y -i "${h}" -i "${r}" -map 0 -map 1 -c copy -c:v:1 png -disposition:v:1 attached_pic "${i}"`,{stdio:"pipe"}),await on(k,{force:!0}),await on(h,{force:!0}),e?.stop(),n==="json"){_({success:!0,output:i});return}if(n==="quiet"){console.log(i);return}b(`Thumbnail injected: ${i}`)}catch(o){e?.stop(),c(o instanceof Error?o.message:"Unknown error"),process.exit(S.GENERAL_ERROR)}}),da=new se("thumbnail").description("Thumbnail operations").addCommand(la),Yo=new se("video").description("Video generation commands").addCommand(sa).addCommand(ra).addCommand(ca).addCommand(da).addCommand(Go);import{Command as ma}from"commander";import ua from"ora";import{writeFile as pa}from"fs/promises";var Ho=new ma("scrape").description("Extract content from a URL").argument("<url>","URL to scrape").option("-o, --output <path>","Save content to file").option("-f, --format <format>","Output format: human, json, quiet","human").action(async(t,n)=>{let e=n.format,o=e==="human"?ua("Scraping URL...").start():null;try{let r=await qn(t);o?.stop(),r.success||(c(r.error||"Failed to scrape URL"),process.exit(S.GENERAL_ERROR));let i=r.data;if(n.output&&(await pa(n.output,i.content,"utf-8"),e==="human"&&b(`Content saved to: ${n.output}`)),e==="json"){_(i);return}if(e==="quiet"){console.log(i.content);return}i.title&&(console.log(),console.log(`Title: ${i.title}`)),i.metadata?.description&&console.log(`Description: ${i.metadata.description}`),console.log(`URL: ${i.url}`),console.log(`Tokens: ~${i.metadata?.tokenUsage?.toLocaleString()||"unknown"}`),i.warning&&R(i.warning),n.output||(console.log(),console.log("--- Content ---"),console.log(i.content)),i.cost&&i.cost>0&&d(`Cost: $${i.cost.toFixed(6)}`)}catch(r){o?.stop(),c(r instanceof Error?r.message:"Unknown error"),process.exit(S.GENERAL_ERROR)}});import{Command as sn}from"commander";import rn from"ora";import{writeFile as Ko}from"fs/promises";function an(t,n){if(n==="json"){_(t);return}if(n==="quiet"){t.audioUrl?console.log(t.audioUrl):t.requestId?console.log(t.requestId):console.log(t.id);return}d(`Request ID: ${t.requestId||t.id}`),d(`Status: ${t.status}`),t.duration&&d(`Duration: ${t.duration}s`),t.audioUrl&&b(`Audio URL: ${t.audioUrl}`),t.cost!==void 0&&d(`Cost: $${t.cost.toFixed(4)}`),t.error&&c(`Error: ${t.error}`)}async function fa(t,n){if(t.startsWith("data:")){let r=t.match(/^data:[^;]+;base64,(.+)$/);if(!r)throw new Error("Invalid data URL format");let i=Buffer.from(r[1],"base64");await Ko(n,i);return}let e=await fetch(t);if(!e.ok)throw new Error(`Failed to download: ${e.status}`);let o=await e.arrayBuffer();await Ko(n,Buffer.from(o))}var ga=new sn("generate").description("Generate sound effects from a text prompt").requiredOption("-t, --text <text>","Sound effect description (e.g., 'thunder rumbling, rain on window')").option("-d, --duration <seconds>","Duration in seconds (0.5-30)").option("-p, --provider <provider>","Provider (elevenlabs)","elevenlabs").option("--prompt-influence <value>","How closely to follow prompt (0-1)","0.3").option("-l, --loop","Generate seamless looping audio",!1).option("--format <format>","Audio format: mp3, wav, ogg","mp3").option("-o, --output <path>","Output file path").option("--no-wait","Do not wait for completion").option("-f, --output-format <format>","Output format: human, json, quiet","human").action(async t=>{let n;t.duration&&(n=parseFloat(t.duration),(isNaN(n)||n<.5||n>30)&&(c("Duration must be between 0.5 and 30 seconds"),process.exit(S.INVALID_INPUT)));let e;t.promptInfluence&&(e=parseFloat(t.promptInfluence),(isNaN(e)||e<0||e>1)&&(c("Prompt influence must be between 0 and 1"),process.exit(S.INVALID_INPUT)));let o=t.outputFormat,r=o==="human"?rn("Generating sound effects...").start():null;try{let i=await Bn({text:t.text,duration:n,options:{provider:t.provider,promptInfluence:e,loop:t.loop,format:t.format}});if(!t.wait){r?.stop(),an(i,o);return}let a=i;if(i.status!=="completed"&&i.status!=="failed"){let s=i.requestId||i.id;r&&(r.text=`Processing (ID: ${s})...`),a=await de(()=>Mt(s),60,2e3)}if(r?.stop(),a.status==="failed"&&(c(a.error||"Sound effects generation failed"),process.exit(S.GENERAL_ERROR)),an(a,o),t.output&&a.audioUrl){let s=o==="human"?rn("Downloading...").start():null;try{await fa(a.audioUrl,t.output),s?.stop(),o==="human"&&b(`Saved to: ${t.output}`)}catch(l){s?.stop(),R(`Failed to download: ${l instanceof Error?l.message:"Unknown error"}`)}}}catch(i){r?.stop(),c(i instanceof Error?i.message:"Unknown error"),process.exit(S.GENERAL_ERROR)}}),ha=new sn("status").description("Check status of a sound effects generation request").argument("<id>","Request ID").option("-f, --format <format>","Output format: human, json, quiet","human").action(async(t,n)=>{let e=n.format==="human"?rn("Checking status...").start():null;try{let o=await Mt(t);e?.stop(),an(o,n.format)}catch(o){e?.stop(),c(o instanceof Error?o.message:"Unknown error"),process.exit(S.GENERAL_ERROR)}}),zo=new sn("sfx").description("Sound effects generation commands").addCommand(ga).addCommand(ha);var ba="0.1.30",D=new ya,ce=u.commands[0];D.name(ce).description(u.description).version(ba,"-v, --version","Show version number").option("--debug","Enable debug logging").option("--no-color","Disable colored output").configureOutput({outputError:(t,n)=>{n(Z.red(t))}});D.addCommand(Qn);D.addCommand(Zn);D.addCommand(to);D.addCommand(io);D.addCommand(ao);D.addCommand(so);D.addCommand(co);D.addCommand(mo);D.addCommand(po);D.addCommand(go);D.addCommand(bo);D.addCommand(vo);D.addCommand(Ue);D.addCommand(Fo);D.addCommand(Do);D.addCommand(Uo);D.addCommand(Mo);D.addCommand(Yo);D.addCommand(Ho);D.addCommand(zo);var Xo=yo();Xo.commands.length>0&&D.addCommand(Xo);D.on("command:*",t=>{console.error(Z.red(`Error: Unknown command '${t[0]}'`)),console.error(),console.error(`Run '${ce} --help' to see available commands.`),process.exit(1)});D.addHelpText("after",`
|
|
2788
3310
|
${Z.bold("Examples:")}
|
|
2789
3311
|
${Z.gray("# Log in (opens browser)")}
|
|
2790
3312
|
$ ${ce} login
|
|
@@ -2816,5 +3338,5 @@ ${Z.bold("Authentication:")}
|
|
|
2816
3338
|
Or set CC_MINDFRAMES_API_KEY environment variable for API key auth.
|
|
2817
3339
|
|
|
2818
3340
|
${Z.bold("More Info:")}
|
|
2819
|
-
${Z.blue(
|
|
2820
|
-
`);
|
|
3341
|
+
${Z.blue(u.docsUrl)}
|
|
3342
|
+
`);D.parse();
|