@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.
Files changed (2) hide show
  1. package/dist/index.js +812 -290
  2. 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 Yi}from"commander";import Z from"chalk";import _n from"path";var $t={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"}},ro={cc:"conceptcraft",conceptcraft:"conceptcraft",mf:"mindframes",mindframes:"mindframes"};function Wn(){let t=process.argv[1]||"",o=_n.basename(t).replace(/\.(js|ts)$/,""),e=ro[o];if(e)return $t[e];for(let[n,r]of Object.entries(ro))if(t.includes(`/${n}`)||t.includes(`\\${n}`))return $t[r];return $t.mindframes}var m=Wn();import{Command as sr}from"commander";import Go from"chalk";import _o from"ora";import Wo from"http";import{randomBytes as Jo,createHash as cr}from"crypto";import lr from"open";import Jn from"conf";var io="https://www.mindframes.app",Kn={apiKey:{type:"string"},apiUrl:{type:"string",default:io},defaultTeamId:{type:"string"},accessToken:{type:"string"},refreshToken:{type:"string"},tokenExpiresAt:{type:"number"},clientId:{type:"string"},clientSecret:{type:"string"}},P=new Jn({projectName:m.name,schema:Kn});function ao(){return{apiKey:ie(),apiUrl:M(),defaultTeamId:P.get("defaultTeamId"),accessToken:P.get("accessToken"),refreshToken:P.get("refreshToken"),tokenExpiresAt:P.get("tokenExpiresAt"),clientId:P.get("clientId"),clientSecret:P.get("clientSecret")}}function ie(){let t=process.env[m.apiKeyEnvVar]??process.env.CC_SLIDES_API_KEY;return t||P.get("apiKey")}function Ct(t){P.set("apiKey",t)}function M(){let t=process.env[m.apiUrlEnvVar]??process.env.CC_SLIDES_API_URL;return t||(P.get("apiUrl")??io)}function kt(t){P.set("apiUrl",t)}function Xe(){return P.get("defaultTeamId")}function xe(t){P.set("defaultTeamId",t)}function Qe(){P.clear()}function Ze(){return P.path}function Se(){return!!ie()}function so(){return P.get("accessToken")}function co(){return P.get("refreshToken")}function et(t,o,e){P.set("accessToken",t),P.set("refreshToken",o),P.set("tokenExpiresAt",Date.now()+(e-60)*1e3)}function tt(){P.delete("accessToken"),P.delete("refreshToken"),P.delete("tokenExpiresAt")}function lo(){let t=P.get("tokenExpiresAt");return t?Date.now()>=t:!0}function pe(){return!!P.get("accessToken")&&!!P.get("refreshToken")}function ot(){return P.get("clientId")}function mo(){return P.get("clientSecret")}function uo(t,o){P.set("clientId",t),P.set("clientSecret",o)}import O from"chalk";import Tt from"cli-table3";function po(){return process.stdout.isTTY??!1}function g(t,o="human"){o!=="quiet"&&o!=="json"&&console.log(O.green("\u2713"),t)}function c(t,o="human"){if(o!=="quiet"){if(o==="json"){console.error(JSON.stringify({error:t}));return}console.error(O.red("\u2717"),t)}}function R(t,o="human"){o!=="quiet"&&o!=="json"&&console.warn(O.yellow("\u26A0"),t)}function l(t,o="human"){o!=="quiet"&&o!=="json"&&console.log(O.blue("\u2139"),t)}function $e(t,o="en"){return t?`${M()}/${o}/view/presentations/${t}`:"N/A"}function fo(t,o={}){let{showLinks:e=!1,language:n="en"}=o,r=(s,d)=>s.length>d?s.slice(0,d-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 Tt({head:[O.cyan("Slug"),O.cyan("Title"),O.cyan("Slides"),O.cyan("Created"),O.cyan("URL")]});for(let d of t)s.push([r(d.slug||d.id.slice(0,8),30),r(d.title||"Untitled",22),String(d.numberOfSlides||"-"),i(d.createdAt),$e(d.slug,n)]);return s.toString()}let a=new Tt({head:[O.cyan("Slug"),O.cyan("Title"),O.cyan("Slides"),O.cyan("Mode"),O.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 go(t){return t.map(o=>o.slug||o.id).join(`
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||$}</p>
10
+ <p>${I||x}</p>
11
11
  <p>You can close this window.</p>
12
12
  </body>
13
13
  </html>
14
- `),a(),n(new Error(I||$));return}if(!y||!w){p.end(`
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(),n(new Error("Missing authorization code or state"));return}if(w!==o){p.end(`
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(),n(new Error("State mismatch"));return}p.end(`
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: ${o}
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
- # ${o} CLI
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 @${o}/cli\` or \`pnpm add -g @${o}/cli\`
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 ${n}_API_KEY="your-key-here"
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 \`${o}-presentation\` skill
85
+ \`${e} create "Topic"\` \u2192 Load \`${n}-presentation\` skill
86
86
 
87
87
  ### Video
88
- \`${e} video generate new my-video\` \u2192 Load \`${o}-video\` skill
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 dt(t){let{name:o,cmd:e}=t;return`---
118
- name: ${o}-video
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
- Read \`video-plan.md\` and generate all audio assets using standalone commands. Extract text and prompts directly from the plan \u2014 do NOT invent content.
319
+ Spawn the \`video-audio-director\` agent to handle all audio generation and timing reconciliation in one focused pass.
319
320
 
320
- **Where to find what in the plan:**
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
- **For ElevenLabs only:** Use batch mode for voice continuity (consistent seed + context chaining). Pipe a minimal JSON:
337
- \`\`\`bash
338
- echo '{"scenes":[{"name":"s1","script":"..."},{"name":"s2","script":"..."}],"provider":"elevenlabs","voice":"Rachel"}' | ${e} video generate assets --output ./public
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
- **Voices:** \`${e} tts voices -p <provider>\`
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
- **Skip** unless the plan explicitly references photos/footage. CSS-built visuals do NOT need stock media.
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
- **CRITICAL - Audio Mixing in Remotion:**
375
- - Voiceover: 100% volume
376
- - Background music: 25-30% volume max \u2192 \`<Audio src={music} volume={0.25} />\`
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
- You MUST build a Component Inventory before writing a single line of scene code. This is not optional.
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
- Icons: lucide-react is installed \u2014 use named imports (Shield, Cloud, Brain, etc.)
404
- === END INVENTORY ===
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
- **WHY:** If you skip this, you will default to what you know from training data (\`slide()\`, \`wipe()\`, plain CSS). The plan references specific components \u2014 you must know their exact import paths and props BEFORE coding.
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 skeleton:**
417
+ **3. Create Main.tsx using the template's audio components:**
449
418
 
450
- Build \`src/Root.tsx\` / \`src/Main.tsx\` with the composition structure using scene durations from the plan. Use \`<TransitionSeries>\` with the transitions specified between scenes. Import each scene component (the agents will create the files).
419
+ The template provides \`VoiceoverSequence\`, \`DuckedMusic\`, and timing helpers in \`src/audio/\`.
451
420
 
452
- \`\`\`tsx
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
- Problem: Transitions create visual overlap between scenes, but audio continues from Scene 1 while Scene 2 is already visible \u2192 desync.
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
- // Scene 1: 90 frames total
472
- <Sequence durationInFrames={90}>
473
- <SlideTransition direction="right" durationInFrames={20}>
474
- <SceneOne />
475
- </SlideTransition>
476
- {/* Audio for Scene 1: only 70 frames, ends before transition */}
477
- <Audio src={scene1Audio} endAt={70} />
478
- </Sequence>
479
-
480
- // OR use audio fade during transition
481
- <Audio
482
- src={scene1Audio}
483
- volume={(f) => f > 70 ? interpolate(f, [70, 90], [1, 0]) : 1}
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
- // For multi-scene: use crossfade pattern (fadeIn * fadeOut)
487
- // Fade out before transition, fade in after transition starts
488
- // Skip fadeIn for first scene, skip fadeOut for last scene
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
- **Duration math:** Transitions OVERLAP scenes, so total = sum of durations MINUS (transitionFrames * numTransitions).
492
-
493
- **When generating assets:** Keep voiceover scripts brief so audio naturally ends before transition (leave ~0.5-1s gap).
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
- **CRITICAL: Audio Mixing volumes:**
496
- - Voiceover: 100% volume
497
- - Background music: 25-30% volume max \u2192 \`<Audio src={music} volume={0.25} />\`
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. Verify and Render
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 mt(t){let{name:o,cmd:e}=t;return`---
618
- name: ${o}-presentation
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
- # ${o} Presentation CLI
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 Te(t){let{name:o,cmd:e}=t;return`---
798
- name: ${o}-tts
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
- # ${o} TTS (Text-to-Speech)
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 Ie(t){let{name:o,cmd:e}=t;return`---
997
- name: ${o}-music
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
- # ${o} Music Generation
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 Re(t){let{name:o,cmd:e}=t;return`---
1137
- name: ${o}-sfx
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
- # ${o} Sound Effects (SFX)
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 Ae(t){let{name:o,cmd:e}=t;return`---
1346
- name: ${o}-mix
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
- # ${o} Audio Mixing
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 Pe(t){let{name:o,cmd:e}=t;return`---
1496
- name: ${o}-image
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
- # ${o} Image Commands
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 Oe(t){let{name:o,cmd:e}=t;return`---
1680
- name: ${o}-video-search
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
- # ${o} Video Search
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 Ee(t){let{name:o,cmd:e}=t;return`---
1855
- name: ${o}-scrape
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
- # ${o} URL Scraping
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 Fe(t){let{name:o,cmd:e}=t;return`---
2045
- name: ${o}-branding
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
- # ${o} Branding Management
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 Ue(t){let{name:o,cmd:e}=t,n=o.toUpperCase().replace(/[^A-Z0-9]/g,"_");return`---
2164
- name: ${o}-config
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
- # ${o} Configuration
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 ${n}_API_KEY="your-api-key"
2243
- export ${n}_API_URL="https://custom-api.example.com"
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 ${n}_API_KEY="your-key"
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 Nt={name:"video-ref-website-assets",description:"Extract maximum assets from websites for video production.",render:t=>`---
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 jt={name:"video-ref-brief-guide",description:"How to write video brief for server-side plan generation.",render:t=>`---
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 Mt={name:"video-ref-source-scraping",description:"Extract content from various sources (YouTube, websites, Twitter).",render:t=>`---
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 qt,writeFileSync as Vt,existsSync as Ke,rmSync as hn}from"fs";import{join as Q,resolve as ut,relative as ni}from"path";import{homedir as pt}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 ri(t,o){let e=ut(t),n=ut(t,o);if(ni(e,n).startsWith("..")||ut(n)!==n.replace(/\.\./g,""))throw new Error(`Invalid path: "${o}" would escape base directory`);return n}function gn(t,o,e){let n=Q(t,"SKILL.md");if(qt(t,{recursive:!0}),Vt(n,o,"utf-8"),e&&e.length>0){let r=Q(t,"references");qt(r,{recursive:!0});for(let i of e){let a=Q(r,`${i.name}.md`);Vt(a,i.content,"utf-8")}}}function ft(t,o,e={},n){let r={installed:[],skipped:[],errors:[]},i=e.local?process.cwd():pt();if(e.dir)try{let a=ut(e.dir),s=ri(a,Q("skills",t));gn(s,o,n),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),d=Q(s,"skills",t),u=Q(d,"SKILL.md");if(Ke(s)){if(Ke(u)&&!e.force){r.skipped.push(a.name);continue}try{gn(d,o,n),r.installed.push(a.name)}catch(p){r.errors.push(`${a.name}: ${p instanceof Error?p.message:String(p)}`)}}}return r}function gt(t,o={}){let e={removed:[],errors:[]},n=o.local?process.cwd():pt();for(let r of me){let i=Q(n,r.dir,"skills",t);if(Ke(i))try{hn(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 yn(){return me.map(t=>t.name)}function bn(t,o,e={}){let n={installed:[],skipped:[],errors:[]},r=pt(),i=me.filter(a=>a.dir===".claude");for(let a of i){let s=Q(r,a.dir,"agents"),d=Q(s,`${t}.md`);if(Ke(d)&&!e.force){n.skipped.push(a.name);continue}try{qt(s,{recursive:!0}),Vt(d,o,"utf-8"),n.installed.push(a.name)}catch(u){n.errors.push(`${a.name}: ${u instanceof Error?u.message:String(u)}`)}}return n}function vn(t){let o={removed:[],errors:[]},e=pt(),n=me.filter(r=>r.dir===".claude");for(let r of n){let i=Q(e,r.dir,"agents",`${t}.md`);if(Ke(i))try{hn(i),o.removed.push(r.name)}catch(a){o.errors.push(`${r.name}: ${a instanceof Error?a.message:String(a)}`)}}return o}function wn(){return`---
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 video scenes. Compares every scene against the video plan and flags deviations. Run after building scenes, before rendering.
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: 3/9 \u2014 NEEDS REWORK
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 xn(){return`---
2633
+ `}function $o(){return`---
2588
2634
  name: video-scene-builder
2589
- description: Builds a single Remotion video scene from a plan section. Loads design guidelines, writes the scene .tsx file, spawns a reviewer, and fixes issues until 85%+ quality score. One agent per scene \u2014 focused context, better output.
2590
- tools: Read, Write, Edit, Glob, Grep, Skill, Task
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. **Output file path** \u2014 e.g. \`src/scenes/Scene02UINav.tsx\`
2602
- 4. **Adjacent transitions** \u2014 previous scene's transition out, next scene's transition in (for continuity)
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
- ### Step 1: Load Design Guidelines
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
- Invoke the \\\`frontend-design\\\` skill to load premium design quality guidelines:
2663
+ If the plan references specific template components (transitions, effects), read them to understand their props:
2609
2664
 
2610
2665
  \\\`\\\`\\\`
2611
- Skill('frontend-design')
2666
+ Read('src/components/transitions/WhipPan.tsx')
2667
+ Read('src/components/effects/ColorGrading.tsx')
2612
2668
  \\\`\\\`\\\`
2613
2669
 
2614
- This gives you the creative vocabulary and quality bar for building the scene.
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: Read Template Components (if needed)
2672
+ ### Step 2: Build the Scene
2617
2673
 
2618
- If the plan references specific template components (transitions, effects), read them to understand their props:
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
- Read('src/components/transitions/WhipPan.tsx')
2622
- Read('src/components/effects/ColorGrading.tsx')
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
- 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.
2701
+ ### Step 4: Fix Issues
2626
2702
 
2627
- ### Step 3: Build the Scene
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
- Write the .tsx file to the output path. Follow these rules precisely:
2708
+ ### Step 5: Re-review if Needed
2630
2709
 
2631
- **CODE STRUCTURE \u2014 Phase-Based:**
2710
+ If the reviewer score was below 85%, re-run the reviewer after fixes. Repeat until passing.
2632
2711
 
2633
- Every scene MUST have explicit time phases:
2712
+ ### Step 6: Done
2634
2713
 
2635
- \\\`\\\`\\\`tsx
2636
- // === PHASE 1: ENTRANCE (frames 0-30) ===
2637
- // Elements enter with staggered spring animations
2714
+ When the reviewer passes (85%+), output:
2638
2715
 
2639
- // === PHASE 2: CONTENT REVEAL (frames 30-90) ===
2640
- // Data fills, numbers count up, labels appear, charts draw
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
- // === PHASE 3: EMPHASIS (frames 90-150) ===
2643
- // Highlight key element, camera push, color shift, pulse
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
- // === PHASE 4: TRANSITION PREP (frames 150-180) ===
2646
- // Begin exit animation, elements start moving/fading
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
- Adjust frame ranges to match the scene's actual duration. A 10s/300-frame scene uses wider ranges. Each phase MUST contain different code \u2014 empty phases = slideshow.
2781
+ Add 5s buffer \u2014 easier to trim than re-generate.
2650
2782
 
2651
- **QUALITY RULES:**
2783
+ ### Step 4: Generate SFX (if plan describes them)
2652
2784
 
2653
- 1. **Minimum 100 lines** \u2014 If your scene is under 80 lines, it's missing animations, texture, or visual complexity
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
- **IMPORT PATTERN:**
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
- \\\`\\\`\\\`tsx
2667
- import { AbsoluteFill, useCurrentFrame, interpolate, spring, useVideoConfig, Sequence, Audio, Img } from 'remotion';
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 Scene01Name: React.FC = () => {
3084
+ export const SceneNNName: React.FC = () => {
2676
3085
  const frame = useCurrentFrame();
2677
3086
  const { fps } = useVideoConfig();
2678
3087
 
2679
- // === PHASE 1: ENTRANCE ===
2680
- // ...
2681
- // === PHASE 2: CONTENT REVEAL ===
2682
- // ...
2683
- // === PHASE 3: EMPHASIS ===
2684
- // ...
2685
- // === PHASE 4: TRANSITION PREP ===
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 layer */}
2691
- {/* Content layer */}
2692
- {/* Texture layer (ColorGrading, GrainOverlay, Vignette) */}
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
- **CRAFTSMANSHIP:**
3121
+ Each phase MUST contain different code. Empty phases = slideshow = reviewer FAIL.
2699
3122
 
2700
- Build real visual components, not divs with text:
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
- All technologies available: CSS/HTML, Canvas 2D, Three.js/@react-three/fiber, WebGL shaders, SVG animations.
3125
+ The technique that separates premium from amateur:
2708
3126
 
2709
- ### Step 4: Review
3127
+ ### Stagger N items across VO duration
2710
3128
 
2711
- After writing the scene, spawn the \\\`video-scene-reviewer\\\` agent to review THIS scene only:
3129
+ \`\`\`tsx
3130
+ const items = ['Security', 'Speed', 'Scale', 'Support'];
3131
+ const staggerFrames = Math.floor(VO_DURATION / items.length);
2712
3132
 
2713
- \\\`\\\`\\\`
2714
- Task('video-scene-reviewer') with prompt:
2715
- "Review ONLY the scene at [output-path]. The plan section for this scene is: [scene section]. branding.json may exist in the project root."
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
- ### Step 5: Fix Issues
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
- Read the reviewer's report. Fix every issue flagged:
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
- ### Step 6: Re-review if Needed
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
- If the reviewer score was below 85%, re-run the reviewer after fixes. Repeat until passing.
3157
+ ### Scenes without VO
2728
3158
 
2729
- ### Step 7: Done
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
- When the reviewer passes (85%+), you're done. Output a brief summary:
3165
+ ## 5. Animation Recipes
2732
3166
 
2733
- \\\`\\\`\\\`
2734
- Scene [name] built: [X] lines, score [Y]%, [Z] visual events
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
- ## What You Do NOT Do
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
- - Don't create Main.tsx or other scenes \u2014 only YOUR assigned scene
2740
- - Don't modify component templates \u2014 use them as-is
2741
- - Don't download assets \u2014 the main AI already did that
2742
- - Don't run render commands \u2014 the main AI handles that
2743
- - Don't modify video-plan.md \u2014 it's read-only to you
2744
- `}var He=["main","video","presentation","tts","music","sfx","mix","image","video-search","scrape","branding","config"],C={cmd:m.commands[0],pkg:m.packageName,url:m.apiUrl,name:m.name},De=new ii("skill").description(`Manage ${m.displayName} skills for AI coding assistants`).addHelpText("after",`
2745
- ${L.bold("Skill Types:")}
2746
- ${L.cyan("main")} Main CLI skill with all capabilities overview
2747
- ${L.cyan("video")} Detailed video creation workflow with Remotion/R3F patterns
2748
- ${L.cyan("presentation")} Detailed presentation creation workflow
2749
-
2750
- ${L.bold("Reference Skills:")}
2751
- ${L.cyan("tts")} Text-to-speech generation (voices, providers, options)
2752
- ${L.cyan("music")} AI music generation (prompts, styles, duration)
2753
- ${L.cyan("sfx")} Sound effects generation (categories, looping, formats)
2754
- ${L.cyan("mix")} Audio mixing (video + voice + music)
2755
- ${L.cyan("image")} Image search and AI generation
2756
- ${L.cyan("video-search")} Stock video search
2757
- ${L.cyan("scrape")} URL content extraction
2758
- ${L.cyan("branding")} Brand profile management
2759
- ${L.cyan("config")} CLI configuration management
2760
-
2761
- ${L.bold("Examples:")}
2762
- ${L.gray("# Install main CLI skill (comprehensive overview)")}
2763
- $ ${m.commands[0]} skill install main
2764
-
2765
- ${L.gray("# Install video skill")}
2766
- $ ${m.commands[0]} skill install video
2767
-
2768
- ${L.gray("# Install presentation skill")}
2769
- $ ${m.commands[0]} skill install presentation
2770
-
2771
- ${L.gray("# Install all skills")}
2772
- $ ${m.commands[0]} skill install
2773
-
2774
- ${L.gray("# Install to specific directory")}
2775
- $ ${m.commands[0]} skill install main --dir ~/.claude
2776
-
2777
- ${L.gray("# Show skill content")}
2778
- $ ${m.commands[0]} skill show main
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 o)console.log(` ${e}`),console.log(` ${ht(e)}`);console.log()}),Vi=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 o=t.format,e=o==="human"?ze("Injecting thumbnail...").start():null;try{let n=Ye(t.video),r=Ye(t.thumbnail),i=t.output?Ye(t.output):n.replace(/\.mp4$/,"-with-thumb.mp4");await zt(n),await zt(r);let a=$i;if(a)try{await zt(a)}catch{a=null}if(!a)try{a=je("which ffmpeg",{encoding:"utf8"}).trim()}catch{throw new Error("ffmpeg not found. Install ffmpeg or ffmpeg-static.")}let s=Si(a,["-i",n],{encoding:"utf8"});if(s.error)throw new Error(`Failed to probe video: ${s.error.message}`);let d=(s.stderr??"").match(/(\d{2,5})x(\d{2,5})/);if(!d)throw new Error("Could not detect video dimensions");let[,u,p]=d;e&&(e.text="Creating thumbnail frame...");let k="/tmp/cc-thumb-frame.mp4";je(`${a} -y -loop 1 -i "${r}" -vf "scale=${u}:${p}:force_original_aspect_ratio=decrease,pad=${u}:${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 y="/tmp/cc-concat-video.mp4";if(je(`${a} -y -i "${k}" -i "${n}" -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 "${y}"`,{stdio:"pipe"}),e&&(e.text="Adding file icon thumbnail..."),je(`${a} -y -i "${y}" -i "${r}" -map 0 -map 1 -c copy -c:v:1 png -disposition:v:1 attached_pic "${i}"`,{stdio:"pipe"}),await Xt(k,{force:!0}),await Xt(y,{force:!0}),e?.stop(),o==="json"){V({success:!0,output:i});return}if(o==="quiet"){console.log(i);return}g(`Thumbnail injected: ${i}`)}catch(n){e?.stop(),c(n instanceof Error?n.message:"Unknown error"),process.exit(v.GENERAL_ERROR)}}),Bi=new se("thumbnail").description("Thumbnail operations").addCommand(Vi),Dn=new se("video").description("Video generation commands").addCommand(Mi).addCommand(Li).addCommand(qi).addCommand(Bi);import{Command as Gi}from"commander";import _i from"ora";import{writeFile as Wi}from"fs/promises";var Ln=new Gi("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,o)=>{let e=o.format,n=e==="human"?_i("Scraping URL...").start():null;try{let r=await No(t);n?.stop(),r.success||(c(r.error||"Failed to scrape URL"),process.exit(v.GENERAL_ERROR));let i=r.data;if(o.output&&(await Wi(o.output,i.content,"utf-8"),e==="human"&&g(`Content saved to: ${o.output}`)),e==="json"){V(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),o.output||(console.log(),console.log("--- Content ---"),console.log(i.content)),i.cost&&i.cost>0&&l(`Cost: $${i.cost.toFixed(6)}`)}catch(r){n?.stop(),c(r instanceof Error?r.message:"Unknown error"),process.exit(v.GENERAL_ERROR)}});import{Command as eo}from"commander";import Qt from"ora";import{writeFile as Nn}from"fs/promises";function Zt(t,o){if(o==="json"){V(t);return}if(o==="quiet"){t.audioUrl?console.log(t.audioUrl):t.requestId?console.log(t.requestId):console.log(t.id);return}l(`Request ID: ${t.requestId||t.id}`),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 Ji(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 Nn(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 Nn(o,Buffer.from(n))}var Ki=new eo("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 o;t.duration&&(o=parseFloat(t.duration),(isNaN(o)||o<.5||o>30)&&(c("Duration must be between 0.5 and 30 seconds"),process.exit(v.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(v.INVALID_INPUT)));let n=t.outputFormat,r=n==="human"?Qt("Generating sound effects...").start():null;try{let i=await jo({text:t.text,duration:o,options:{provider:t.provider,promptInfluence:e,loop:t.loop,format:t.format}});if(!t.wait){r?.stop(),Zt(i,n);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(()=>Et(s),60,2e3)}if(r?.stop(),a.status==="failed"&&(c(a.error||"Sound effects generation failed"),process.exit(v.GENERAL_ERROR)),Zt(a,n),t.output&&a.audioUrl){let s=n==="human"?Qt("Downloading...").start():null;try{await Ji(a.audioUrl,t.output),s?.stop(),n==="human"&&g(`Saved to: ${t.output}`)}catch(d){s?.stop(),R(`Failed to download: ${d instanceof Error?d.message:"Unknown error"}`)}}}catch(i){r?.stop(),c(i instanceof Error?i.message:"Unknown error"),process.exit(v.GENERAL_ERROR)}}),Hi=new eo("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,o)=>{let e=o.format==="human"?Qt("Checking status...").start():null;try{let n=await Et(t);e?.stop(),Zt(n,o.format)}catch(n){e?.stop(),c(n instanceof Error?n.message:"Unknown error"),process.exit(v.GENERAL_ERROR)}}),jn=new eo("sfx").description("Sound effects generation commands").addCommand(Ki).addCommand(Hi);var zi="0.1.28",U=new Yi,ce=m.commands[0];U.name(ce).description(m.description).version(zi,"-v, --version","Show version number").option("--debug","Enable debug logging").option("--no-color","Disable colored output").configureOutput({outputError:(t,o)=>{o(Z.red(t))}});U.addCommand(Ko);U.addCommand(Ho);U.addCommand(zo);U.addCommand(en);U.addCommand(tn);U.addCommand(on);U.addCommand(nn);U.addCommand(an);U.addCommand(cn);U.addCommand(dn);U.addCommand(pn);U.addCommand(fn);U.addCommand(De);U.addCommand(kn);U.addCommand(In);U.addCommand(Rn);U.addCommand(Pn);U.addCommand(Dn);U.addCommand(Ln);U.addCommand(jn);var Mn=un();Mn.commands.length>0&&U.addCommand(Mn);U.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)});U.addHelpText("after",`
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(m.docsUrl)}
2820
- `);U.parse();
3341
+ ${Z.blue(u.docsUrl)}
3342
+ `);D.parse();