@conceptcraft/mindframes 0.1.29 → 0.1.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +348 -242
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'module'; const require = createRequire(import.meta.url);
|
|
3
|
-
import{Command as
|
|
4
|
-
`)}function 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(`
|
|
3
|
+
import{Command as Ca}from"commander";import Q from"chalk";import rr from"path";var Et={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"}},yn={cc:"conceptcraft",conceptcraft:"conceptcraft",mf:"mindframes",mindframes:"mindframes"};function ir(){let n=process.argv[1]||"",t=rr.basename(n).replace(/\.(js|ts)$/,""),e=yn[t];if(e)return Et[e];for(let[o,r]of Object.entries(yn))if(n.includes(`/${o}`)||n.includes(`\\${o}`))return Et[r];return Et.mindframes}var u=ir();import{Command as wr}from"commander";import Zn from"chalk";import eo from"ora";import to from"http";import{randomBytes as no,createHash as Sr}from"crypto";import Tr from"open";import ar from"conf";var bn="https://www.mindframes.app",sr={apiKey:{type:"string"},apiUrl:{type:"string",default:bn},defaultTeamId:{type:"string"},accessToken:{type:"string"},refreshToken:{type:"string"},tokenExpiresAt:{type:"number"},clientId:{type:"string"},clientSecret:{type:"string"}},O=new ar({projectName:u.name,schema:sr});function vn(){return{apiKey:re(),apiUrl:M(),defaultTeamId:O.get("defaultTeamId"),accessToken:O.get("accessToken"),refreshToken:O.get("refreshToken"),tokenExpiresAt:O.get("tokenExpiresAt"),clientId:O.get("clientId"),clientSecret:O.get("clientSecret")}}function re(){let n=process.env[u.apiKeyEnvVar]??process.env.CC_SLIDES_API_KEY;return n||O.get("apiKey")}function Pt(n){O.set("apiKey",n)}function M(){let n=process.env[u.apiUrlEnvVar]??process.env.CC_SLIDES_API_URL;return n||(O.get("apiUrl")??bn)}function Ft(n){O.set("apiUrl",n)}function Ze(){return O.get("defaultTeamId")}function we(n){O.set("defaultTeamId",n)}function et(){O.clear()}function tt(){return O.path}function Se(){return!!re()}function xn(){return O.get("accessToken")}function wn(){return O.get("refreshToken")}function nt(n,t,e){O.set("accessToken",n),O.set("refreshToken",t),O.set("tokenExpiresAt",Date.now()+(e-60)*1e3)}function ot(){O.delete("accessToken"),O.delete("refreshToken"),O.delete("tokenExpiresAt")}function Sn(){let n=O.get("tokenExpiresAt");return n?Date.now()>=n:!0}function ue(){return!!O.get("accessToken")&&!!O.get("refreshToken")}function rt(){return O.get("clientId")}function it(){return O.get("clientSecret")}function Tn(n,t){O.set("clientId",n),O.set("clientSecret",t)}import E from"chalk";import Dt from"cli-table3";function kn(){return process.stdout.isTTY??!1}function b(n,t="human"){t!=="quiet"&&t!=="json"&&console.log(E.green("\u2713"),n)}function c(n,t="human"){if(t!=="quiet"){if(t==="json"){console.error(JSON.stringify({error:n}));return}console.error(E.red("\u2717"),n)}}function I(n,t="human"){t!=="quiet"&&t!=="json"&&console.warn(E.yellow("\u26A0"),n)}function d(n,t="human"){t!=="quiet"&&t!=="json"&&console.log(E.blue("\u2139"),n)}function Te(n,t="en"){return n?`${M()}/${t}/view/presentations/${n}`:"N/A"}function Cn(n,t={}){let{showLinks:e=!1,language:o="en"}=t,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 Dt({head:[E.cyan("Slug"),E.cyan("Title"),E.cyan("Slides"),E.cyan("Created"),E.cyan("URL")]});for(let l of n)s.push([r(l.slug||l.id.slice(0,8),30),r(l.title||"Untitled",22),String(l.numberOfSlides||"-"),i(l.createdAt),Te(l.slug,o)]);return s.toString()}let a=new Dt({head:[E.cyan("Slug"),E.cyan("Title"),E.cyan("Slides"),E.cyan("Mode"),E.cyan("Created")],colWidths:[32,28,8,11,18]});for(let s of n)a.push([s.slug||s.id.slice(0,8),s.title||"Untitled",String(s.numberOfSlides||"-"),s.mode||"-",i(s.createdAt)]);return a.toString()}function $n(n){return n.map(t=>t.slug||t.id).join(`
|
|
4
|
+
`)}function In(n){let t=[...n].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 Dt({head:[E.cyan("Name"),E.cyan("Source"),E.cyan("Color"),E.cyan("Logo"),E.cyan("Default"),E.cyan("ID")]});for(let r of t){let i=r.primaryColor?`${E.bgHex(r.primaryColor)(" ")} ${r.primaryColor}`:"-";o.push([r.name,e(r.sourceUrl),i,r.logoUrl?E.green("\u2713"):E.gray("\u2717"),r.isDefault?E.green("\u2605"):"",r.id])}return o.toString()}function Nt(n){try{return new Date(n).toLocaleString()}catch{return n}}function ee(n){console.log(),console.log(E.bold(n)),console.log(E.gray("\u2500".repeat(n.length)))}function v(n,t){console.log(` ${E.gray(n+":")} ${t??"-"}`)}function Rn(n,t,e=30,o=!0){let r=Math.min(100,Math.round(n/t*100)),i=Math.round(e*n/t),a=e-i,s=E.green("\u2588".repeat(i))+E.gray("\u2591".repeat(a));return o?`[${s}] ${r}%`:`[${s}]`}function cr(n){return JSON.stringify(n,null,2)}function V(n){console.log(cr(n))}import br 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 lr(){let n=wn(),t=rt(),e=M();if(!n||!t)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:n,client_id:t}),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 An(){return ue()?Sn()?lr():xn()||null:null}function te(){return ue()||Se()}async function F(){if(!te()){let n=u.commands[0],t=u.apiKeyEnvVar;console.error(at.red("\u2717 Not authenticated.")),console.error(),console.error(at.bold("To authenticate, run:")),console.error(at.cyan(` ${n} login`)),console.error(),console.error(at.gray(`Or set ${t} environment variable.`)),process.exit(S.AUTH_ERROR)}}function dr(n){return n.length<=14?"****":`${n.slice(0,10)}...${n.slice(-4)}`}function On(){let n=re();if(n)return dr(n)}function Ut(n){return["cc_slides_","mcp_","cc_sk_","sk_"].some(e=>n.startsWith(e))}import{readFileSync as mr,statSync as ur}from"fs";import{basename as En}from"path";import{randomUUID as pr}from"crypto";async function ce(){let n=await An();if(n)return{Authorization:`Bearer ${n}`};let t=re();return t?{"x-api-key":t}:{}}var P=class extends Error{constructor(e,o,r=1){super(e);this.statusCode=o;this.exitCode=r;this.name="ApiError"}};async function U(n,t={}){let e=M();if(!te())throw new P(`Not authenticated. Run '${u.commands[0]} login' or set ${u.apiKeyEnvVar} environment variable.`,401,2);let o=await ce(),r=`${e}${n}`,i={...o,...t.headers};t.body&&!t.stream&&(i["Content-Type"]="application/json");let a={method:t.method??"GET",headers:i};t.body&&(a.body=JSON.stringify(t.body));let s;try{s=await fetch(r,a)}catch(m){throw new P(`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 C=m.trim()||`HTTP ${s.status}: ${s.statusText||"Server error"}`;throw new P(C,s.status,p)}return t.stream?s:s.headers.get("content-type")?.includes("application/json")?s.json():s.text()}async function fr(n,t){let e=M();if(!te())throw new P(`Not authenticated. Run '${u.commands[0]} login' or set ${u.apiKeyEnvVar} environment variable.`,401,2);let o=await ce(),r=`${e}${n}`,i;try{i=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json",...o},body:JSON.stringify(t)})}catch(a){throw new P(`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 P(a,i.status,s)}return i}function gr(n){let t=n.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"}[t||""]||"application/octet-stream"}async function hr(n){let t=M();if(!te())throw new P(`Not authenticated. Run '${u.commands[0]} login' or set ${u.apiKeyEnvVar} environment variable.`,401,2);let e=await ce(),o=ur(n),r=En(n),i=gr(n),a=await fetch(`${t}/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 P(h||"Failed to get upload URL",a.status,1)}let s=await a.json(),l=mr(n),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 C=await fetch(s.url,{method:"POST",body:m});if(!C.ok)throw new P("Failed to upload file to storage",C.status,1);return{id:pr(),name:r,url:s.fileUrl,size:o.size,type:i,selected:!0}}async function Pn(n,t){let e=[];for(let o=0;o<n.length;o++){let r=n[o],i=En(r);t?.(o,n.length,i);let a=await hr(r);e.push(a)}return t?.(n.length,n.length,""),e}async function Fn(n){let t={};for(let[e,o]of Object.entries(n))o!==void 0&&(t[e]=o);return fr("/api/slides/cli/create-presentation",t)}async function Dn(n,t=20){let e=new URLSearchParams;return e.set("limit",String(t)),n&&e.set("teamId",n),(await U(`/api/cli/presentations?${e}`)).presentations}async function ke(n){return U(`/api/cli/presentation/${n}`)}async function Nn(n){await U(`/api/cli/presentation/${n}`,{method:"DELETE"})}async function Un(n,t={}){let e=M();if(!te())throw new P(`Not authenticated. Run '${u.commands[0]} login' or set ${u.apiKeyEnvVar} environment variable.`,401,2);let o=await ce(),r=await fetch(`${e}/api/presentations/export`,{method:"POST",headers:{"Content-Type":"application/json",...o},body:JSON.stringify({presentationId:n,options:{includeImages:t.includeImages??!0,includeBranding:t.includeBranding??!0,downloadExternalImages:t.includeExternal??!0}})});if(!r.ok)throw new P(await r.text(),r.status,r.status===404?3:1);return r.arrayBuffer()}async function Ln(n,t,e={}){let o=M();if(!te())throw new P(`Not authenticated. Run '${u.commands[0]} login' or set ${u.apiKeyEnvVar} environment variable.`,401,2);let r=await ce(),i=new FormData,a=new Blob([n],{type:"application/zip"});i.append("file",a,t),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 P(l.errors?.[0]?.message??"Import failed",s.status,1);return l}async function Lt(){return U("/api/cli/branding")}async function Mt(n){return U(`/api/branding/${n}`)}async function Mn(n,t){return U("/api/cli/branding/extract",{method:"POST",body:{url:n,teamId:t}})}async function pe(){return U("/api/cli/whoami")}async function jn(){return U("/api/cli/features")}async function Vn(n,t,e={}){let o=M();if(!te())throw new P(`Not authenticated. Run '${u.commands[0]} login' or set ${u.apiKeyEnvVar} environment variable.`,401,2);let r=await ce(),i=await fetch(`${o}/api/presentations/${n}/generate-blog`,{method:"POST",headers:{"Content-Type":"application/json",...r},body:JSON.stringify({documentCreatedAt:t,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 P(await i.text(),i.status,i.status===404?3:1);return i}async function yr(n){let t=new URLSearchParams;n&&t.set("teamId",n);let e=t.toString();return U(`/api/cli/limits${e?`?${e}`:""}`)}async function _n(n,t,e){let o=await yr(e);if(!o.canGenerate[n]){let r=o.remaining[n]??0,i=o.limits[n]??0;throw i===0?new P(`The "${n}" mode is not available on your ${o.planName} plan. Available modes: ${o.availableModes.join(", ")||"none"}`,403,2):new P(`You have reached your ${n} generation limit for this billing cycle (${o.usage[n]??0}/${i} used). Try a different mode: ${o.availableModes.join(", ")||"none available"}`,403,4)}if(t>o.maxSlidesPerPresentation)throw new P(`Maximum ${o.maxSlidesPerPresentation} slides allowed on your ${o.planName} plan (requested: ${t})`,403,6);return o}function qn(n){return n.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(n){let t=M();if(!te())throw new P(`Not authenticated. Run '${u.commands[0]} login' or set ${u.apiKeyEnvVar} environment variable.`,401,2);let e=await ce(),o={...n,text:qn(n.text)},r;try{r=await fetch(`${t}/api/cli/tts`,{method:"POST",headers:{"Content-Type":"application/json",...e},body:JSON.stringify(o)})}catch(h){throw new P(`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 P(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,C=r.headers.get("X-Timestamps");if(C)try{p=JSON.parse(C)}catch{}return{audioData:i,duration:a,cost:s,provider:l,format:m,timestamps:p}}async function Bn(n){let t=M();if(!te())throw new P(`Not authenticated. Run '${u.commands[0]} login' or set ${u.apiKeyEnvVar} environment variable.`,401,2);let e=await ce(),o={...n,texts:n.texts.map(a=>({...a,text:qn(a.text)}))},r;try{r=await fetch(`${t}/api/cli/tts/batch`,{method:"POST",headers:{"Content-Type":"application/json",...e},body:JSON.stringify(o)})}catch(a){throw new P(`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 P(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 Gn(n){let t=n?`/api/cli/tts?provider=${n}`:"/api/cli/tts";return U(t)}async function ct(n){let t=await U("/api/cli/music",{method:"POST",body:n});if(!t.data)throw new P(`Invalid API response: ${JSON.stringify(t)}`,500,1);return{requestId:t.data.id,status:t.data.status,audioUrl:t.data.audioUrl,duration:t.data.duration,cost:t.data.cost,error:t.data.error}}async function Je(n){let t=await U(`/api/cli/music?requestId=${encodeURIComponent(n)}`);return{requestId:t.data.id,status:t.data.status,audioUrl:t.data.audioUrl,duration:t.data.duration,cost:t.data.cost,error:t.data.error}}async function Wn(n){return U("/api/cli/mix",{method:"POST",body:n})}async function jt(n){return U(`/api/cli/mix?requestId=${encodeURIComponent(n)}`)}async function lt(n){return U("/api/cli/images/search",{method:"POST",body:n})}async function Yn(n){return U("/api/cli/images/generate",{method:"POST",body:n})}async function Vt(n){return U("/api/cli/videos/search",{method:"POST",body:n})}async function le(n,t=60,e=2e3){for(let o=0;o<t;o++){let r=await n();if(r.status==="completed"||r.status==="failed")return r;await new Promise(i=>setTimeout(i,e))}throw new P("Operation timed out",408,1)}async function Jn(n){return U("/api/cli/scrape",{method:"POST",body:{url:n}})}async function Hn(n){let t=await U("/api/cli/sfx",{method:"POST",body:n});if(!t.data)throw new P(`Invalid API response: ${JSON.stringify(t)}`,500,1);return{id:t.data.id,status:t.data.status,audioUrl:t.data.audioUrl,duration:t.data.duration,format:t.data.format,cost:t.data.cost,error:t.data.error,requestId:t.data.requestId}}async function _t(n){let t=await U(`/api/cli/sfx?requestId=${encodeURIComponent(n)}`);return{id:t.data.id,status:t.data.status,audioUrl:t.data.audioUrl,duration:t.data.duration,format:t.data.format,cost:t.data.cost,error:t.data.error,requestId:t.data.requestId}}async function Kn(n){return U("/api/cli/video/plan",{method:"POST",body:n})}var dt=new br({projectName:"conceptcraft",configName:"feature-cache"}),vr=3600*1e3,xr=1440*60*1e3;function zn(n){let t=0;for(let e=0;e<n.length;e++){let o=n.charCodeAt(e);t=(t<<5)-t+o,t=t&t}return t.toString(16)}function Xn(){if(!Se())return null;let n=dt.get("featureFlags");if(!n)return null;let t=zn(re());if(n.apiKeyHash&&n.apiKeyHash!==t)return ge(),null;let e=Date.now()-n.fetchedAt;return e>xr?null:(e>vr&&qt(),n.flags)}function qt(){fe().catch(()=>{})}async function fe(){let n=await jn(),t=re();return dt.set("featureFlags",{flags:n,fetchedAt:Date.now(),apiKeyHash:t?zn(t):void 0}),n}function ge(){dt.delete("featureFlags")}function Qn(){return dt.path}var kr=`${u.displayName} CLI`,Cr=8765,$r=8775;function Ir(){return no(32).toString("base64url")}function Rr(n){return Sr("sha256").update(n).digest("base64url")}function Ar(){return no(16).toString("hex")}async function Or(n,t){for(let e=n;e<=t;e++)try{return await new Promise((o,r)=>{let i=to.createServer();i.listen(e,()=>{i.close(()=>o())}),i.on("error",r)}),e}catch{continue}throw new Error(`No available port found between ${n} and ${t}`)}async function Er(n){let t=await fetch(`${n}/.well-known/oauth-authorization-server`);if(!t.ok)throw new Error(`Failed to fetch OAuth metadata: ${t.status}`);return t.json()}async function Pr(n,t){let e=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({client_name:kr,redirect_uris:[t],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 Fr(n,t,e,o,r){let i=new URLSearchParams({grant_type:"authorization_code",code:t,redirect_uri:o,client_id:r,code_verifier:e}),a=await fetch(n,{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 Dr(n,t){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=to.createServer((m,p)=>{let C=new URL(m.url||"",`http://localhost:${n}`);if(C.pathname!=="/callback"){p.writeHead(404),p.end("Not found");return}let h=C.searchParams.get("code"),f=C.searchParams.get("state"),x=C.searchParams.get("error"),$=C.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>${
|
|
10
|
+
<p>${$||x}</p>
|
|
11
11
|
<p>You can close this window.</p>
|
|
12
12
|
</body>
|
|
13
13
|
</html>
|
|
14
|
-
`),a(),o(new Error(
|
|
14
|
+
`),a(),o(new Error($||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 ya}from"commander";import Z from"chalk";import nr from"path";v
|
|
|
21
21
|
<p>You can close this window.</p>
|
|
22
22
|
</body>
|
|
23
23
|
</html>
|
|
24
|
-
`),a(),o(new Error("Missing authorization code or state"));return}if(f!==
|
|
24
|
+
`),a(),o(new Error("Missing authorization code or state"));return}if(f!==t){p.end(`
|
|
25
25
|
<!DOCTYPE html>
|
|
26
26
|
<html>
|
|
27
27
|
<head><title>Login Failed</title></head>
|
|
@@ -40,17 +40,18 @@ import{Command as ya}from"commander";import Z from"chalk";import nr 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: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}
|
|
43
|
+
`),a(),e({code:h,state:f})});l.listen(n),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 Nr(n,t){let e=await fetch(`${n}/api/cli/whoami`,{headers:{Authorization:`Bearer ${t}`}});if(!e.ok)throw new Error(`Failed to fetch user info: ${e.status}`);return e.json()}async function Ur(n){let t=M(),e=eo("Discovering OAuth server...").start();try{let o=await Er(t);e.succeed("Connecting to "+t);let r=await Or(Cr,$r),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 Pr(o.registration_endpoint,i);a=y.client_id,s=y.client_secret,Tn(a,s)}let l=Ir(),m=Rr(l),p=Ar(),C=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}?${C}`;console.log("Authorization URL: "+h),n.browser?(d("Opening browser..."),await Tr(h)):(console.log(Zn.bold("Open this URL in your browser:")),console.log(Zn.cyan(h)));let f=Dr(r,p),{code:x}=await f;e=eo("Completing login...").start();let $=await Fr(o.token_endpoint,x,l,i,a);nt($.access_token,$.refresh_token,$.expires_in);let T=await Nr(t,$.access_token);e.succeed("Logged in!"),console.log(),v("Logged in as",T.user.email),T.currentTeam&&(we(T.currentTeam.id),v("Team",`${T.currentTeam.name} (${T.currentTeam.planName})`));try{await fe()}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 oo=new wr("login").description(`Authenticate with ${u.displayName} (opens browser)`).option("--no-browser","Print URL instead of opening browser").action(async n=>{console.log(),ue()&&(I("You are already logged in."),d(`Run '${u.commands[0]} logout' to log out first, or continue to re-authenticate.`),console.log());try{await Ur(n),process.exit(0)}catch{process.exit(1)}});import{Command as Lr}from"commander";import{confirm as Mr}from"@inquirer/prompts";var ro=new Lr("logout").description("Log out and clear authentication").option("--all","Clear all config including API key").action(async n=>{console.log();let t=ue(),e=Se();if(!t&&!e){I("You are not logged in."),console.log();return}if(n.all)try{await Mr({message:"Clear all configuration including API key?",default:!1})?(et(),ge(),b("All configuration cleared.")):d("Cancelled.")}catch{d("Cancelled.")}else ot(),ge(),b("Logged out successfully."),e&&d("Note: Your API key is still configured. Use --all to clear everything.");console.log()});import{Command as he}from"commander";import He from"chalk";import{input as jr,password as Vr,confirm as io,select as _r}from"@inquirer/prompts";import Bt from"ora";var ao=new he("config").description("Manage CLI configuration").addCommand(new he("init").description("Initialize configuration interactively").action(async()=>{console.log(),console.log(He.bold(`${u.displayName} CLI Configuration`)),console.log(He.gray("\u2500".repeat(35))),console.log();try{let n=await Vr({message:"Enter your API key:",mask:"*",validate:o=>!o||o.trim().length===0?"API key is required":Ut(o.trim())?!0:"Invalid API key format. Keys should start with 'cc_slides_' or 'mcp_'"});if(Pt(n.trim()),await io({message:`Use a custom API URL? (default: ${u.apiUrl.replace("https://","")})`,default:!1})){let o=await jr({message:"Enter API URL:",default:M(),validate:r=>{try{return new URL(r),!0}catch{return"Invalid URL format"}}});Ft(o)}console.log();let e=Bt("Verifying API key...").start();try{let o=await pe();if(e.succeed("API key verified!"),console.log(),d(`Logged in as: ${o.user.email}`),o.teams.length===0)I("No teams found for this user.");else if(o.teams.length===1){let r=o.teams[0];we(r.id),d(`Team: ${r.name} (${r.planName})`)}else{console.log(),d(`You have access to ${o.teams.length} teams.`);let r=await _r({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});we(r);let i=o.teams.find(a=>a.id===r);b(`Selected team: ${i?.name}`)}try{await fe()}catch{}}catch(o){e.fail("Failed to verify API key"),I(`Could not verify API key: ${o instanceof Error?o.message:String(o)}`),I(`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(n){if(n.name==="ExitPromptError"){console.log(),d("Configuration cancelled.");return}throw n}})).addCommand(new he("show").description("Show current configuration").option("--verify","Verify API key and show team details").action(async n=>{let t=vn();if(ee("Current Configuration"),console.log(),v("API Key",On()??He.red("Not set")),v("API URL",t.apiUrl),v("Default Team ID",t.defaultTeamId??He.gray("Not set")),console.log(),v("Config file",tt()),n.verify&&t.apiKey){console.log();let e=Bt("Verifying...").start();try{let o=await pe();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"),I(o instanceof Error?o.message:String(o))}}console.log()})).addCommand(new he("set").description("Set a configuration value").argument("<key>","Configuration key (api-key, api-url, team-id)").argument("<value>","Value to set").action(async(n,t)=>{switch(n){case"api-key":Ut(t)||(c("Invalid API key format. Keys should start with 'cc_slides_' or 'mcp_'"),process.exit(6)),Pt(t),ge(),b("API key updated"),fe().catch(()=>{});break;case"api-url":try{new URL(t),Ft(t),b(`API URL set to: ${t}`)}catch{c("Invalid URL format"),process.exit(6)}break;case"team-id":we(t),b(`Default team ID set to: ${t}`);break;default:c(`Unknown config key: ${n}`),console.log(He.gray("Valid keys: api-key, api-url, team-id")),process.exit(6)}})).addCommand(new he("clear").description("Clear all configuration").action(async()=>{try{await io({message:"Are you sure you want to clear all configuration?",default:!1})?(et(),ge(),b("Configuration cleared")):d("Cancelled")}catch{d("Cancelled")}})).addCommand(new he("refresh").description("Refresh cached feature flags").action(async()=>{let n=Bt("Refreshing feature flags...").start();try{await fe(),n.succeed("Feature flags refreshed"),d(`Run '${u.commands[0]} --help' to see updated commands`)}catch(t){n.fail("Failed to refresh"),c(t instanceof Error?t.message:String(t)),process.exit(1)}})).addCommand(new he("path").description("Show configuration file path").option("--cache","Show feature cache file path").action(n=>{n.cache?console.log(Qn()):console.log(tt())}));import{Command as Wr}from"commander";import J from"chalk";import{createParser as qr}from"eventsource-parser";import Y from"chalk";import Br from"ora";async function Gr(n,t,e={}){let o=n.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,C={},h=qr({onEvent:x=>{if(x.data!=="[DONE]")try{let $=JSON.parse(x.data),{type:T,data:y}=$;switch(e.debug&&console.log(Y.gray(`[SSE] ${T}:`),y),t.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 R=y;m=R.current,l=R.total,t.onProgress?.(m,l),t.onSlide?.(m,l);break}case"data-overall-progress":{let R=y;t.onPhase?.(R.phase,R.percentage),R.details?.slides&&(m=R.details.slides.complete,l=R.details.slides.total,t.onProgress?.(m,l),t.onSlide?.(m,l));break}case"data-structure-tokens":{p=y,t.onTokens?.(p);break}case"data-slide-meta-tokens":{let[R,k]=y.split(":");if(R&&k){C[R]=parseInt(k,10);let g=Object.values(C).reduce((A,q)=>A+q,0);p=Math.max(p,g),t.onTokens?.(p+g)}break}case"data-generation-meta-estimated-time":{t.onEstimatedTime?.(y);break}case"data-finish":t.onComplete?.({id:i,slug:a,title:s,slidesCount:l});break;case"error":y&&t.onError?.(y);break}}catch{e.debug&&console.log(Y.gray("[SSE Raw]"),x.data)}}}),f=!1;for(;!f;){let x=await o.read();if(f=x.done,x.value){let $=r.decode(x.value,{stream:!0});h.feed($)}}return{id:i,slug:a,title:s,slidesCount:l}}async function so(n,t,e){let o=kn()&&!e.noStream&&!e.quiet&&!e.json;e.json||(console.log(),console.log(Y.bold(`Creating presentation: "${t}"`)),console.log(Y.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,C=Date.now(),h,f=g=>{let A=Math.floor(g/60),q=g%60;return`${A}:${q.toString().padStart(2,"0")}`},x=()=>Math.floor((Date.now()-C)/1e3),$=()=>{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 ",R=()=>{let g=Rn(s,100,20,!1),A=String(s).padStart(3," "),q=y(a),K=`${String(l.done).padStart(2," ")}/${String(l.total).padEnd(2," ")}`,ne=T(m),be=f(x()).padStart(5," "),Be=$().padStart(5," ");return`${g} ${Y.bold(A)}${Y.gray("%")} ${Y.cyan(q)} ${Y.gray(K)} ${Y.yellow(ne)} ${Y.gray(be)} ${Y.green(Be+" left")}`};return o&&(r=Br({text:R(),spinner:"dots"}).start(),h=setInterval(()=>{r&&r.isSpinning&&(r.text=R())},100)),{...await Gr(n,{onProgress:(g,A)=>{if(l={done:g,total:A},!o&&!e.quiet&&!e.json){let q=`${a.trim()}: slide ${g}/${A}`;q!==i&&(console.log(q),i=q)}},onPhase:(g,A)=>{a=y(g),s=A},onTokens:g=>{m=g},onEstimatedTime:g=>{p=g},onError:g=>{h&&clearInterval(h),r?r.fail(Y.red(`Error: ${g}`)):e.json||console.error(Y.red(`Error: ${g}`))},onComplete:g=>{if(h&&clearInterval(h),r)s=100,a="Done",r.succeed(R());else if(!e.json){let A=m>0?` | ${m.toLocaleString()} tokens`:"";console.log(`Done! [${f(x())}${A}]`)}}},{debug:e.debug}),elapsedSeconds:x(),totalTokens:m}}async function co(n,t={}){let e=n.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,t.quiet||process.stdout.write(s)}}return t.quiet||console.log(),r}import{readFileSync as Yr,existsSync as Jr}from"fs";import{resolve as lo}from"path";var mo=new Wr("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(n,t)=>{await F();let e;t.slides!==void 0&&(e=parseInt(t.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"];t.mode&&!o.includes(t.mode)&&(c(`Invalid mode: ${t.mode}. Valid: ${o.join(", ")}`),process.exit(6));let r=["creative","professional","educational","formal","casual"];t.tone&&!r.includes(t.tone)&&(c(`Invalid tone: ${t.tone}. Valid: ${r.join(", ")}`),process.exit(6));let i=["minimal","concise","detailed","extensive"];t.amount&&!i.includes(t.amount)&&(c(`Invalid amount: ${t.amount}. Valid: ${i.join(", ")}`),process.exit(6));let a=["freeform","brand-only","brand-plus-style","style-only","no-styling"];t.styling&&!a.includes(t.styling)&&(c(`Invalid styling: ${t.styling}. Valid: ${a.join(", ")}`),process.exit(6));let s=["quick","moderate","deep","profound"];t.thinkingDepth&&!s.includes(t.thinkingDepth)&&(c(`Invalid thinking-depth: ${t.thinkingDepth}. Valid: ${s.join(", ")}`),process.exit(6));let l=["inform","persuade","train","learn","entertain","report"];t.goal&&!l.includes(t.goal)&&(c(`Invalid goal: ${t.goal}. Valid: ${l.join(", ")}`),process.exit(6));let m=["blue","violet","rose","orange","green"];t.theme&&!m.includes(t.theme)&&(c(`Invalid theme: ${t.theme}. Valid: ${m.join(", ")}`),process.exit(6));let p=["none","waves-bottom-left","waves-top-right","blob-corners","minimal"];t.decorations&&!p.includes(t.decorations)&&(c(`Invalid decorations: ${t.decorations}. Valid: ${p.join(", ")}`),process.exit(6)),(t.theme||t.primaryColor||t.secondaryColor||t.accentColor||t.backgroundColor||t.foregroundColor||t.decorations)&&t.mode&&t.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[k,g]of[["primary-color",t.primaryColor],["secondary-color",t.secondaryColor],["accent-color",t.accentColor],["background-color",t.backgroundColor],["foreground-color",t.foregroundColor]])g&&!h.test(g)&&(c(`Invalid ${k}: ${g}. Must be hex (e.g., #0066CC)`),process.exit(6));try{t.output!=="json"&&t.output!=="quiet"&&process.stdout.write(J.gray("Checking generation limits... "));let k=await _n(t.mode??"fast",e??10,t.teamId);t.output!=="json"&&t.output!=="quiet"&&(console.log(J.green("\u2713")),console.log(J.gray(` Plan: ${k.planName} | ${t.mode??"fast"}: ${k.remaining[t.mode??"fast"]}/${k.limits[t.mode??"fast"]} remaining`)))}catch(k){t.output==="json"?console.log(JSON.stringify({success:!1,error:k instanceof Error?k.message:String(k)})):(console.log(J.red("\u2717")),c(k instanceof Error?k.message:String(k))),process.exit(k.exitCode??1)}let f=[];if(t.file&&t.file.length>0){for(let k of t.file)Jr(lo(k))||(c(`File not found: ${k}`),process.exit(6));t.output!=="json"&&t.output!=="quiet"&&console.log(J.gray(`
|
|
44
|
+
Uploading ${t.file.length} file(s)...`));try{f=await Pn(t.file.map(k=>lo(k)),(k,g,A)=>{t.output!=="json"&&t.output!=="quiet"&&A&&process.stdout.write(`\r ${J.cyan("\u2B06")} Uploading: ${A} (${k+1}/${g})`)}),t.output!=="json"&&t.output!=="quiet"&&console.log(`\r ${J.green("\u2713")} Uploaded ${f.length} file(s) `)}catch(k){c(`Failed to upload files: ${k instanceof Error?k.message:String(k)}`),process.exit(1)}}let x;(t.stdin||!process.stdin.isTTY)&&(x=await Hr(),t.stdin&&!x&&(c("No content received from stdin"),process.exit(6)));let $=t.context;if(t.contextFile)try{$=Yr(t.contextFile,"utf-8"),$.trim()||(c(`Context file is empty: ${t.contextFile}`),process.exit(6))}catch{c(`Failed to read context file: ${t.contextFile}`),process.exit(6)}!x&&!$&&f.length===0&&(c("Context is required to create a presentation."),console.log(),console.log(J.gray(" -f, --file <paths...> Upload files (PDF, PPTX, images)")),console.log(J.gray(" -c, --context <text> Inline text context")),console.log(J.gray(" --context-file <path> Read from a file")),console.log(J.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}),$&&T.push({id:`context-${Date.now()}`,content:$,title:"Context",mime:"text/plain",selected:!0});let y,R=t.primaryColor||t.secondaryColor||t.accentColor||t.backgroundColor||t.foregroundColor;(t.theme||R||t.decorations)&&(y={},t.theme&&(y.preset=t.theme),R&&(y.custom={},t.primaryColor&&(y.custom.primary=t.primaryColor),t.secondaryColor&&(y.custom.secondary=t.secondaryColor),t.accentColor&&(y.custom.accent=t.accentColor),t.backgroundColor&&(y.custom.background=t.backgroundColor),t.foregroundColor&&(y.custom.foreground=t.foregroundColor)),t.decorations&&(y.decorations=t.decorations));try{let k=await Fn({topic:n,...e!==void 0&&{slideCount:e},...t.mode&&{mode:t.mode},...t.tone&&{tone:t.tone},...t.amount&&{amount:t.amount},...t.audience&&{audience:t.audience},...t.language&&{language:t.language},...t.brandingId&&{brandingId:t.brandingId},...t.styling&&{stylingMode:t.styling},...t.referenceUrl&&{referenceUrl:t.referenceUrl},...t.thinkingDepth&&{thinkingDepth:t.thinkingDepth},...t.goal&&{goal:t.goal},...t.teamId&&{teamId:t.teamId},...y&&{theme:y},...T.length>0&&{contentSources:{uploadedFiles:T,contextFiles:[]}}}),g=await so(k,n,{slideCount:e,mode:t.mode,tone:t.tone,language:t.language,noStream:t.noStream,debug:t.debug,quiet:t.output==="quiet",json:t.output==="json"});if(t.output==="json")console.log(JSON.stringify({success:!0,presentation:{slug:g.slug,title:g.title??n,slidesCount:g.slidesCount,elapsedSeconds:g.elapsedSeconds,totalTokens:g.totalTokens},viewUrl:Te(g.slug,t.language??"en")},null,2));else{console.log(),b("Presentation created successfully"),console.log(),v("Title",g.title??n),v("Slides",String(g.slidesCount??e??"\u2014"));let A=[];if(g.elapsedSeconds){let K=Math.floor(g.elapsedSeconds/60),ne=g.elapsedSeconds%60;A.push(K>0?`${K}m ${ne}s`:`${ne}s`)}g.totalTokens&&A.push(`${g.totalTokens.toLocaleString()} tokens`),A.length>0&&v("Generated in",A.join(" \xB7 ")),console.log();let q=Te(g.slug,t.language??"en");q!=="N/A"&&(console.log(J.bold(" Open: ")+J.cyan.underline(q)),t.open&&await(await import("open")).default(q)),console.log()}}catch(k){t.output==="json"?console.log(JSON.stringify({success:!1,error:k instanceof Error?k.message:String(k)})):c(k instanceof Error?k.message:String(k)),process.exit(k.exitCode??1)}});async function Hr(){return new Promise(n=>{let t="";if(process.stdin.setEncoding("utf8"),process.stdin.isTTY){n("");return}process.stdin.on("data",e=>{t+=e}),process.stdin.on("end",()=>{n(t.trim())}),setTimeout(()=>{n(t.trim())},100)})}import{Command as Kr}from"commander";var uo=new Kr("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 n=>{await F();let t=parseInt(n.limit,10);(isNaN(t)||t<1)&&(c("Invalid limit value"),process.exit(6));let e=n.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 Dn(e,t);if(o.length===0){n.format==="json"?console.log(JSON.stringify([])):n.format!=="ids"&&d("No presentations found");return}switch(n.sort&&o.sort((r,i)=>{switch(n.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}}),n.format){case"json":console.log(JSON.stringify(o,null,2));break;case"ids":console.log($n(o));break;default:console.log(Cn(o,{showLinks:n.detail,language:n.language}));break}}catch(o){c(o instanceof Error?o.message:String(o)),process.exit(o.exitCode??1)}});import{Command as zr}from"commander";import mt from"chalk";var po=new zr("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(n,t)=>{await F();try{let e=await ke(n);if(t.format==="json"){console.log(JSON.stringify(e,null,2));return}let o=Te(e.slug,t.language);ee("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",Nt(e.createdAt)),e.updatedAt&&v("Updated",Nt(e.updatedAt)),console.log(),o!=="N/A"&&console.log(mt.bold(" Open: ")+mt.cyan.underline(o)),console.log(),t.format==="full"&&t.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 Xr}from"commander";import{confirm as Qr}from"@inquirer/prompts";var fo=new Xr("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(n,t)=>{if(await F(),!t.force)try{if(!await Qr({message:`Are you sure you want to delete presentation "${n}"?`,default:!1})){t.quiet||I("Deletion cancelled");return}}catch{t.quiet||I("Deletion cancelled");return}try{await Nn(n),t.quiet||b(`Presentation "${n}" deleted`)}catch(e){c(e instanceof Error?e.message:String(e)),process.exit(e.exitCode??1)}});import{Command as Zr}from"commander";import{writeFile as ei}from"fs/promises";import{resolve as go}from"path";import ti from"ora";var ho=new Zr("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(n,t)=>{await F();let e=ti("Fetching presentation...").start();try{let o=await ke(n),r=o.title||n,i=o.id,a=n.replace(/[^a-z0-9-]/gi,"_").toLowerCase().substring(0,50),s=new Date().toISOString().split("T")[0],l=`${a}_${s}.zip`,m=t.output?go(t.output):go(process.cwd(),l);e.text=`Exporting "${r}"...`;let p=await Un(i,{includeImages:t.includeImages,includeBranding:t.includeBranding,includeExternal:!t.noExternal});e.text="Saving file...",await ei(m,Buffer.from(p)),e.succeed("Export complete!"),console.log(),d(`File saved: ${m}`),d(`Size: ${ni(p.byteLength)}`),console.log()}catch(o){e.fail("Export failed"),c(o instanceof Error?o.message:String(o)),process.exit(o.exitCode??1)}});function ni(n){return n<1024?`${n} B`:n<1024*1024?`${(n/1024).toFixed(1)} KB`:`${(n/(1024*1024)).toFixed(1)} MB`}import{Command as oi}from"commander";import{readFile as ri}from"fs/promises";import{resolve as ii,basename as ai}from"path";import si from"ora";var yo=new oi("import").description("Import a presentation from ZIP (admin only)").argument("<file>","Path to ZIP file");yo._hidden=!0;var bo=yo.option("--dry-run","Validate without importing").option("--overwrite","Overwrite existing presentations").option("--remap-ids","Generate new IDs (default: true)",!0).action(async(n,t)=>{await F();try{(await pe()).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=ii(n),o=ai(e);o.endsWith(".zip")||(c("File must be a ZIP archive"),process.exit(6));let r=si("Reading file...").start();try{let i=await ri(e);t.dryRun?r.text="Validating...":r.text="Importing presentation...";let a=await Ln(i,o,{dryRun:t.dryRun});if(a.success){if(t.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=M();v("View URL",`${s}/p/${a.presentationId}`)}if(a.warnings&&a.warnings.length>0){console.log();for(let s of a.warnings)I(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 Ce}from"commander";import _ from"chalk";import ci from"ora";import{writeFile as vo}from"fs/promises";var xo=new Ce("branding").description("Manage brand profiles").addCommand(new Ce("list").description("List brand profiles").option("-f, --format <format>","Output format (table, json)","table").action(async n=>{await F();try{let t=await Lt();if(t.brandings.length===0){n.format==="json"?console.log(JSON.stringify([])):(d("No brand profiles found"),console.log(),console.log(_.gray("`Create one with: ${brand.commands[0]} branding extract <url>`")));return}n.format==="json"?console.log(JSON.stringify(t.brandings,null,2)):console.log(In(t.brandings))}catch(t){c(t instanceof Error?t.message:String(t)),process.exit(t.exitCode??1)}})).addCommand(new Ce("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(n,t)=>{await F();try{let e=await Mt(n);if(t.output){await vo(t.output,JSON.stringify(e,null,2)),b(`Brand profile saved to ${t.output}`);return}if(t.format==="json")console.log(JSON.stringify(e,null,2));else{if(ee("Brand Profile"),console.log(),v("ID",e.id),v("Name",e.name),v("Source URL",e.sourceUrl??_.gray("None")),v("Default",e.isDefault?_.green("Yes"):"No"),e.createdAt&&v("Created",new Date(e.createdAt).toLocaleString()),console.log(),e.primaryColor||e.colors.length>0){if(console.log(_.bold(" Colors")),e.primaryColor){let r=_.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=_.bgHex(r.hex)(" "),a=r.role?_.gray(` (${r.role})`):"";console.log(` ${i} ${r.hex}${a}`)}}console.log()}e.logoUrl&&(console.log(_.bold(" Logo")),console.log(` ${e.logoUrl}`),console.log());let o=e.typography;o&&Object.keys(o).length>0&&(console.log(_.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(_.bold(" Extraction")),e.confidence&&console.log(` Confidence: ${Math.round(e.confidence*100)}%`),e.extractionMethod&&console.log(` Method: ${e.extractionMethod}`),console.log()),console.log(_.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 Ce("find").description("Find existing brand profile by name or URL").argument("<query>","Brand name or website URL to search for").action(async n=>{await F();try{let t=await Lt(),e=n.toLowerCase().replace(/^https?:\/\//,"").replace(/\/$/,""),o=t.brandings.filter(r=>{let i=(r.name||"").toLowerCase(),a=(r.sourceUrl||"").toLowerCase().replace(/^https?:\/\//,"").replace(/\/$/,"");return i.includes(e)||a.includes(e)||i.length>0&&e.includes(i)||a.length>0&&e.includes(a)});if(o.length===0){d(`No brand profiles matching "${n}"`),console.log(_.gray(` Extract one with: ${u.commands[0]} branding extract <url>`));return}console.log(`Found ${o.length} brand${o.length>1?"s":""}:
|
|
45
|
+
`);for(let r of o){let i=r.primaryColor?_.bgHex(r.primaryColor)(" ")+" "+r.primaryColor:"";console.log(` ${_.bold(r.name)} ${_.gray(r.id)}`),r.sourceUrl&&console.log(` ${_.gray(r.sourceUrl)}`),i&&console.log(` ${i}`),console.log()}console.log(_.gray(` Export with: ${u.commands[0]} branding get <id> -o branding.json`))}catch(t){c(t instanceof Error?t.message:String(t)),process.exit(t.exitCode??1)}})).addCommand(new Ce("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(n,t)=>{await F();try{new URL(n.startsWith("http")?n:`https://${n}`)}catch{c("Invalid URL format"),process.exit(6)}let e=n.startsWith("http")?n:`https://${n}`,o=t.teamId??Ze(),r=ci({text:`Extracting branding from ${e}...`,stream:process.stdout}).start();try{let i=await Mn(e,o),a=await Mt(i.id);r.succeed("Branding extracted!");let s=JSON.stringify(a,null,2);t.output?(await vo(t.output,s),b(`Brand profile saved to ${t.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 Ce("set-default").description("Set a brand profile as default").argument("<id>","Brand profile ID").action(async n=>{await F(),d(`To set brand ${n} as default, use the web dashboard.`),console.log(),console.log(_.gray("API support for setting default brand is coming soon."))}));import{Command as $e}from"commander";import wo from"chalk";import li from"ora";function di(){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(n,t)=>{await F();let e=parseInt(t.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(t.tone)||(c(`Invalid tone. Valid options: ${o.join(", ")}`),process.exit(6));let r=li("Fetching presentation...").start();try{let i=await ke(n);r.text="Generating blog post...";let a=await Vn(i.id,i.documentCreatedAt||i.createdAt,{targetWordCount:e,tone:t.tone,targetAudience:t.audience??"General Audience",customInstructions:t.instructions??"",includeImages:!t.noImages,includeTableOfContents:t.toc??!1}),s=await co(a,{quiet:!0});r.succeed("Blog generated"),t.format==="json"?console.log(JSON.stringify({presentationId:n,title:i.title,wordCount:e,tone:t.tone,content:s},null,2)):t.format==="markdown"?console.log(s):(console.log(),console.log(wo.bold(`Blog: ${i.title}`)),console.log(wo.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 mi(){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(n,t)=>{await F(),d("Tweet generation coming soon.")})}function ui(){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 n=>{await F(),d("LinkedIn carousel generation coming soon.")})}function pi(){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(n,t)=>{await F(),d("Question generation coming soon.")})}function fi(){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(n,t)=>{await F(),d("Cheat sheet generation coming soon.")})}function So(){let n=new $e("derive").description("Generate derivative content from a presentation"),t=Xn();return t&&(t.deckToBlog&&n.addCommand(di()),t.deckToTweets&&n.addCommand(mi()),t.linkedInCarousel&&n.addCommand(ui()),t.redTeamQuestions&&n.addCommand(pi()),t.presenterCheatSheet&&n.addCommand(fi())),n}import{Command as gi}from"commander";import Gt from"chalk";var To=new gi("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 n=>{await F(),d("Idea generation is available in the web dashboard."),console.log(),console.log(Gt.gray("The CLI will support idea generation in a future release.")),console.log(),console.log("For now, try these approaches:"),console.log(Gt.gray("` 1. Visit the ${brand.displayName} dashboard and use the idea generator`")),console.log(Gt.gray(" 2. Create a presentation with a broad topic and refine it")),console.log()});import{Command as hi}from"commander";import Wt from"chalk";var ko=new hi("whoami").description("Show current user and team information").option("-f, --format <format>","Output format (human, json)","human").action(async n=>{await F();try{let t=await pe();if(qt(),n.format==="json"){console.log(JSON.stringify(t,null,2));return}if(ee("User"),console.log(),v("Email",t.user.email),t.user.username&&v("Username",t.user.username),(t.user.firstName||t.user.lastName)&&v("Name",[t.user.firstName,t.user.lastName].filter(Boolean).join(" ")),v("Auth Type",t.authType==="apiKey"?"API Key":"Session"),t.currentTeam&&(console.log(),ee("Current Team"),console.log(),v("ID",t.currentTeam.id),v("Name",t.currentTeam.name),v("Plan",t.currentTeam.planName),v("Role",t.currentTeam.isOwner?"Owner":"Member")),t.teams.length>1){console.log(),ee("All Teams"),console.log();for(let e of t.teams){let o=e.isCurrent?Wt.green(" (current)"):"";console.log(` ${Wt.bold(e.name)}${o}`),console.log(Wt.gray(` ID: ${e.id} | Plan: ${e.planName} | Role: ${e.role}`))}}console.log()}catch(t){c(t instanceof Error?t.message:String(t)),process.exit(t.exitCode??1)}});import{Command as vi}from"commander";import N from"chalk";function ut(n){let{name:t,cmd:e}=n,o=t.toUpperCase().replace(/[^A-Z0-9]/g,"_");return`---
|
|
46
|
+
name: ${t}
|
|
46
47
|
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
48
|
---
|
|
48
49
|
|
|
49
|
-
# ${
|
|
50
|
+
# ${t} CLI
|
|
50
51
|
|
|
51
52
|
A comprehensive CLI for AI-powered content creation. Generate presentations, video assets, voiceovers, music, and search stock media - all from your terminal.
|
|
52
53
|
|
|
53
|
-
**Install:** \`npm install -g @${
|
|
54
|
+
**Install:** \`npm install -g @${t}/cli\` or \`pnpm add -g @${t}/cli\`
|
|
54
55
|
|
|
55
56
|
---
|
|
56
57
|
|
|
@@ -82,10 +83,10 @@ ${e} whoami
|
|
|
82
83
|
## Commands
|
|
83
84
|
|
|
84
85
|
### Presentations
|
|
85
|
-
\`${e} create "Topic"\` \u2192 Load \`${
|
|
86
|
+
\`${e} create "Topic"\` \u2192 Load \`${t}-presentation\` skill
|
|
86
87
|
|
|
87
88
|
### Video
|
|
88
|
-
\`${e} video generate new my-video\` \u2192 Load \`${
|
|
89
|
+
\`${e} video generate new my-video\` \u2192 Load \`${t}-video\` skill
|
|
89
90
|
|
|
90
91
|
### TTS
|
|
91
92
|
\`${e} tts generate -t "Text" -o out.mp3\` \u2192 [references/tts.md](references/tts.md)
|
|
@@ -114,12 +115,12 @@ ${e} whoami
|
|
|
114
115
|
### Config
|
|
115
116
|
\`${e} config show\` \u2192 [references/config.md](references/config.md)
|
|
116
117
|
|
|
117
|
-
`}function pt(
|
|
118
|
-
name: ${
|
|
119
|
-
description: Orchestrates video
|
|
118
|
+
`}function pt(n){let{name:t,cmd:e,mode:o="standalone"}=n,r=o==="studio";return`---
|
|
119
|
+
name: ${t}-video
|
|
120
|
+
description: Orchestrates video creation by coordinating specialized agents for branding, audio, and scene building. Gathers context, writes briefs, generates plans, and assembles Remotion videos. 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
121
|
---
|
|
121
122
|
|
|
122
|
-
|
|
123
|
+
Follow the steps in order. Some steps delegate work to specialized agents (branding, audio, scenes) \u2014 spawn them and wait for results before continuing. In YOLO mode, make decisions autonomously instead of asking the user. In other modes, use AskUserQuestion when a step offers it.
|
|
123
124
|
|
|
124
125
|
## CRITICAL: Authentication Required
|
|
125
126
|
|
|
@@ -148,16 +149,13 @@ ${e} whoami
|
|
|
148
149
|
| Command | Purpose |
|
|
149
150
|
|---------|---------|
|
|
150
151
|
| \`${e} scrape <url>\` | Extract content from URLs (websites, YouTube, Twitter) |
|
|
151
|
-
| \`${e} branding extract <url> -o <file>\` | Extract brand identity and save to JSON file |
|
|
152
|
-
| \`${e} branding get <id> -o <file>\` | Save existing brand profile to JSON file |
|
|
153
|
-
| \`${e} tts generate\` | Text-to-speech with Gemini/OpenAI/ElevenLabs |
|
|
154
|
-
| \`${e} tts voices\` | List available TTS voices by provider |
|
|
155
|
-
| \`${e} sfx generate\` | Generate sound effects from text prompts |
|
|
156
|
-
| \`${e} music generate\` | Generate background music |
|
|
157
152
|
| \`${e} image search\` | Search stock images |
|
|
158
153
|
| \`${e} video find\` | Search stock video clips |
|
|
159
154
|
| \`${e} video analyze <file>\` | Analyze video: scene detection, transcription, frame descriptions |
|
|
160
155
|
|
|
156
|
+
Branding commands (\`branding find/extract/get\`) are used by the \`video-branding-director\` \u2014 see Step 3.
|
|
157
|
+
Audio commands (\`tts generate\`, \`music generate\`, \`sfx generate\`) are used by the \`video-audio-director\` \u2014 see Step 8.
|
|
158
|
+
|
|
161
159
|
## Step 1: Initialize Project
|
|
162
160
|
|
|
163
161
|
\`\`\`bash
|
|
@@ -174,65 +172,20 @@ Projects are stored in \`~/.conceptcraft/projects/\`. Use \`${e} video projects\
|
|
|
174
172
|
|
|
175
173
|
Understand user's request and identify source type. Gather all relevant information for the video.
|
|
176
174
|
|
|
177
|
-
**For URLs/websites:** \`${e} scrape <url>\` - extracts content
|
|
175
|
+
**For URLs/websites:** \`${e} scrape <url>\` - extracts content and page structure.
|
|
178
176
|
**For codebases:** Explore repo, find UI components, README, docs.
|
|
179
177
|
**For topics:** Research via web search, gather key points.
|
|
180
178
|
|
|
181
|
-
|
|
179
|
+
If the scraped content contains useful images (hero shots, product photos, screenshots, diagrams), download them to \`./public/\` \u2014 real assets from the source look better than stock photos:
|
|
182
180
|
\`\`\`bash
|
|
183
181
|
curl -L -o ./public/hero-image.jpg "https://example.com/blog/wp-content/uploads/hero.jpg"
|
|
184
182
|
curl -L -o ./public/screenshot-1.png "https://example.com/images/feature-screenshot.png"
|
|
185
183
|
\`\`\`
|
|
186
|
-
|
|
184
|
+
If branding is needed (logos, colors, fonts), there's a dedicated \`video-branding-director\` for that in Step 3. It uses the CLI's branding extraction pipeline which handles logo resolution, color palette extraction, font file downloads, and S3-hosted assets \u2014 things that are tricky to do manually from a scraped page.
|
|
187
185
|
|
|
188
186
|
Check \`references/\` folder for detailed guidance.
|
|
189
187
|
|
|
190
|
-
## Step 3:
|
|
191
|
-
|
|
192
|
-
Before choosing a creation mode, determine if branding should be applied. If branding is extracted, save it to \`branding.json\` \u2014 this file is reused in Step 5 (brief) and Step 7 (plan generation, where the server uses it for color/logo/typography decisions).
|
|
193
|
-
|
|
194
|
-
**When brand/company is detected in the user's request** (e.g. "video for Stripe", "Nike promo", "demo of acme.com"):
|
|
195
|
-
- Infer the website URL from the company name (e.g. "Nike" -> nike.com, "Figma" -> figma.com)
|
|
196
|
-
- Use AskUserQuestion to confirm before extracting:
|
|
197
|
-
- "I detected [Brand]. Extract branding from [inferred-url]?"
|
|
198
|
-
- Options: "Yes, extract from [url]" / "Use a different URL" / "Pick from existing brands" / "Skip branding"
|
|
199
|
-
|
|
200
|
-
**When no brand/company is detected:**
|
|
201
|
-
- Use AskUserQuestion: "Do you want to apply branding to this video?"
|
|
202
|
-
- Options: "Yes, I'll provide a URL" / "Yes, pick from existing brands" / "No branding"
|
|
203
|
-
|
|
204
|
-
**After user confirms:**
|
|
205
|
-
- Extract from URL: \`${e} branding extract <url> --output branding.json\`
|
|
206
|
-
- Pick existing: \`${e} branding list --format json\` \u2014 let user choose, then \`${e} branding get <id> --output branding.json\`
|
|
207
|
-
|
|
208
|
-
**Download brand assets locally** (so Remotion renders without network dependencies):
|
|
209
|
-
- Read \`branding.json\` and download any remote URLs (logoUrl, logos.faviconUrl, logos.additionalImages) to \`./public/\`
|
|
210
|
-
- Update the paths in \`branding.json\` to point to local files (e.g. \`logoUrl\`: \`"./public/logo.png"\`)
|
|
211
|
-
|
|
212
|
-
**Extract brand typography** \u2014 \`branding.json\` contains font files already hosted on S3. The actual font files (.woff2) are in \`brandData.enhancedTypography.fontStack\`:
|
|
213
|
-
|
|
214
|
-
\`\`\`
|
|
215
|
-
fontStack.primary.family = "Segoe UI"
|
|
216
|
-
fontStack.primary.fontFiles = [
|
|
217
|
-
{ url: "https://.../fonts/abc123.woff2", format: "woff2", weight: 700, style: "normal" },
|
|
218
|
-
{ url: "https://.../fonts/def456.woff2", format: "woff2", weight: 400, style: "normal" }
|
|
219
|
-
]
|
|
220
|
-
\`\`\`
|
|
221
|
-
|
|
222
|
-
**If the brand has more than 2 font families** (e.g. primary, secondary, tertiary, display) \u2014 ask the user which font to use as the primary (headings/hero text) via AskUserQuestion. List the font family names as options. Use the user's choice as the main font for the video.
|
|
223
|
-
|
|
224
|
-
You MUST:
|
|
225
|
-
- Download ALL \`.woff2\` files from \`fontStack.primary.fontFiles[].url\` and \`fontStack.secondary.fontFiles[].url\` to \`./public/fonts/\`
|
|
226
|
-
- Note: URLs may point to localhost (dev) or S3 (prod) \u2014 the S3 hash filenames (e.g. \`4c38c2a78502af8dfbfe0f71cc49a1ae.woff2\`) are the actual font files, download them
|
|
227
|
-
- If localhost URLs fail, extract the hash filename and try the S3 URL pattern: \`https://cc-cb-slides.s3.us-east-2.amazonaws.com/fonts/{hash}.woff2\`
|
|
228
|
-
- Note the exact font family name and weights for use in Step 9
|
|
229
|
-
- **DO NOT use \`@remotion/google-fonts\`** \u2014 brand fonts are real fonts extracted from the brand's website, not Google Fonts substitutes
|
|
230
|
-
|
|
231
|
-
The saved \`branding.json\` is reused in Step 5 (brief) and Step 7 (plan generation via \`--branding\` flag).
|
|
232
|
-
|
|
233
|
-
**Skip when:** Source is a plain topic with no company association and user declines branding.
|
|
234
|
-
|
|
235
|
-
## Step 4: Choose Creation Mode
|
|
188
|
+
## Step 3: Choose Creation Mode
|
|
236
189
|
|
|
237
190
|
After gathering context, ask user to choose a creation mode using AskUserQuestion:
|
|
238
191
|
|
|
@@ -265,6 +218,27 @@ Conduct detailed interview using AskUserQuestion. Based on gathered context, ask
|
|
|
265
218
|
|
|
266
219
|
---
|
|
267
220
|
|
|
221
|
+
## Step 4: Branding
|
|
222
|
+
|
|
223
|
+
Spawn the \`video-branding-director\` agent when the user's request involves a brand or company. If no brand is involved: in YOLO mode skip this step, otherwise ask the user via AskUserQuestion.
|
|
224
|
+
|
|
225
|
+
**Construct the Task prompt:**
|
|
226
|
+
|
|
227
|
+
\`\`\`
|
|
228
|
+
## Branding Director Task
|
|
229
|
+
|
|
230
|
+
**Brand query:** [company name or URL from user's request, e.g. "alfaromeousa.com" or "Nike"]
|
|
231
|
+
**Project directory:** [absolute path to current project]
|
|
232
|
+
|
|
233
|
+
Search for existing brand profiles, extract if needed, download all assets locally, and write branding.json.
|
|
234
|
+
\`\`\`
|
|
235
|
+
|
|
236
|
+
**Wait for the agent to complete before proceeding to Step 5.**
|
|
237
|
+
|
|
238
|
+
If the agent succeeds \u2014 \`branding.json\` is ready. Use it in Step 5 (brief) and Step 7 (plan generation via \`--branding\` flag).
|
|
239
|
+
|
|
240
|
+
If the agent fails \u2014 stop and ask the user via AskUserQuestion with options: "Continue without branding" / "Try a different URL". This applies in every mode, including YOLO. If the user provides a different URL, spawn the agent again with the new URL. You do not have enough information to make branding decisions on your own \u2014 colors, fonts, and logos from a scraped page are not the same as a proper brand profile.
|
|
241
|
+
|
|
268
242
|
## Step 5: Prepare Brief
|
|
269
243
|
|
|
270
244
|
Following [references/brief-guide.md](references/brief-guide.md), prepare a comprehensive brief covering:
|
|
@@ -405,7 +379,7 @@ export const brandFontFace = \`
|
|
|
405
379
|
export const brandFontFamily = "'BrandFont', sans-serif";
|
|
406
380
|
\`\`\`
|
|
407
381
|
|
|
408
|
-
Use the EXACT font family name from \`fontStack.primary.family\`.
|
|
382
|
+
Use the EXACT font family name from \`fontStack.primary.family\`. Font files should already be in \`./public/fonts/\` (downloaded by the branding agent in Step 3).
|
|
409
383
|
|
|
410
384
|
**2. Parse video-plan.md into per-scene sections:**
|
|
411
385
|
|
|
@@ -573,16 +547,16 @@ Task('video-continuity-editor') with prompt:
|
|
|
573
547
|
|
|
574
548
|
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
549
|
|
|
576
|
-
If score is below 85%, fix the flagged issues before rendering.
|
|
550
|
+
If score is below 85%, fix the flagged issues before ${r?"previewing":"rendering"}.
|
|
577
551
|
|
|
578
|
-
### 9e. Verify and Render
|
|
552
|
+
### 9e. Verify and ${r?"Preview":"Render"}
|
|
579
553
|
|
|
580
554
|
After all agents finish:
|
|
581
555
|
|
|
582
556
|
1. **Verify all scene files exist:** \`ls src/scenes/Scene*.tsx\` \u2014 count must match number of scenes in plan
|
|
583
557
|
2. **If any scene is missing**, re-spawn its builder agent
|
|
584
558
|
|
|
585
|
-
**Checklist before render:**
|
|
559
|
+
**Checklist before ${r?"preview":"render"}:**
|
|
586
560
|
- [ ] All scene files exist and match the plan's scene count
|
|
587
561
|
- [ ] Main.tsx/Root.tsx imports all scenes correctly
|
|
588
562
|
- [ ] Fonts loaded via \`loadFont()\` or \`@font-face\` \u2014 no raw fontFamily
|
|
@@ -591,7 +565,7 @@ After all agents finish:
|
|
|
591
565
|
- [ ] Transitions use template components as specified in plan \u2014 no generic substitutions
|
|
592
566
|
- [ ] SFX files from Step 8 are used at correct timestamps
|
|
593
567
|
- [ ] Layout fills frame \u2014 not clustered in corners
|
|
594
|
-
|
|
568
|
+
${r?"\n**Studio Preview auto-reloads** when scene files are saved. After verifying the checklist, tell the user to check the Studio Preview in the app.\n\n**Do NOT run `npx remotion render` unless the user explicitly asks to render the final video.**\nIf the user requests a render, use:\n\n```bash\nnpx remotion render Main out/video.mp4\n```\n\nFor WebGL/Three.js scenes, use `--gl=angle`:\n```bash\nnpx remotion render Main out/video.mp4 --gl=angle\n```\n":`
|
|
595
569
|
**Then render:**
|
|
596
570
|
|
|
597
571
|
\`\`\`bash
|
|
@@ -613,7 +587,7 @@ ${e} video thumbnail inject -i out/video.mp4 -t ./public/thumbnail.jpg
|
|
|
613
587
|
\`\`\`
|
|
614
588
|
|
|
615
589
|
Injects thumbnail as first frame (QuickLook preview) + attached_pic (file icons).
|
|
616
|
-
|
|
590
|
+
`}
|
|
617
591
|
---
|
|
618
592
|
|
|
619
593
|
## Troubleshooting
|
|
@@ -628,34 +602,40 @@ Injects thumbnail as first frame (QuickLook preview) + attached_pic (file icons)
|
|
|
628
602
|
|
|
629
603
|
**Why:** Audio assets cannot be generated without authentication. Building Remotion scenes without audio is wasted work that cannot be completed later.
|
|
630
604
|
|
|
631
|
-
### TTS
|
|
632
|
-
|
|
633
|
-
Retry with explicit options: \`${e} tts generate --text "..." --voice Puck --output ./public/audio/scene-name.wav\`
|
|
634
|
-
|
|
635
|
-
### Render fails with WebGL components
|
|
636
|
-
Use \`--gl=angle\` flag: \`npx remotion render Main out/video.mp4 --gl=angle\`
|
|
637
|
-
|
|
638
|
-
### Music generation fails
|
|
639
|
-
Retry with standalone: \`${e} music generate --prompt "..." --duration 30 --output ./public/audio/music.mp3\`
|
|
640
|
-
|
|
641
|
-
### TTS fails for one scene
|
|
642
|
-
Retry single scene: \`${e} tts generate --text "..." --output ./public/audio/scene-name.wav\`
|
|
605
|
+
### Audio generation fails (TTS, music, SFX)
|
|
643
606
|
|
|
607
|
+
Audio is handled by the \`video-audio-director\` agent. If it fails, re-spawn it \u2014 it has retry logic built in.
|
|
608
|
+
${r?"":"\n### Render fails with WebGL components\nUse `--gl=angle` flag: `npx remotion render Main out/video.mp4 --gl=angle`\n"}
|
|
609
|
+
${r?"":`
|
|
644
610
|
### Video too long to render
|
|
645
611
|
Add \`--concurrency=2\` to reduce memory usage, or render scenes separately.
|
|
646
|
-
|
|
612
|
+
`}
|
|
647
613
|
### Preview/build confusion
|
|
648
614
|
\`npm run build\` may run Next.js instead of Remotion. Use Remotion-specific commands:
|
|
649
615
|
- Preview: \`npx remotion studio\` or \`npm run dev\` (if configured)
|
|
650
|
-
- Type check: \`npx tsc --noEmit
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
616
|
+
- Type check: \`npx tsc --noEmit\`${r?"":"\n- Render: `npx remotion render Main out/video.mp4`"}
|
|
617
|
+
|
|
618
|
+
## CLI Quick Reference
|
|
619
|
+
|
|
620
|
+
The \`${e}\` CLI has tools for working with individual assets directly. This is useful for quick fixes \u2014 re-generating a single voiceover, trying a different music style, or searching for stock footage. Run \`${e} --help\` for the full list.
|
|
621
|
+
|
|
622
|
+
- **Voiceover:** \`${e} tts generate --text "..." --voice Puck --output file.wav\` \u2014 generate speech from text. \`${e} tts voices\` lists available voices.
|
|
623
|
+
- **Music:** \`${e} music generate --prompt "..." --duration 30 --output file.mp3\` \u2014 generate background music from a text prompt.
|
|
624
|
+
- **Sound effects:** \`${e} sfx generate --prompt "..." --output file.wav\` \u2014 generate SFX from a description.
|
|
625
|
+
- **Stock images:** \`${e} image search "query"\` \u2014 search stock photo libraries.
|
|
626
|
+
- **Stock video:** \`${e} video find "query"\` \u2014 search stock video clips.
|
|
627
|
+
- **Scraping:** \`${e} scrape <url>\` \u2014 extract text content from a URL.
|
|
628
|
+
- **Branding:** \`${e} branding find <name>\`, \`${e} branding extract <url>\`, \`${e} branding get <id>\` \u2014 search, extract, or export brand profiles.
|
|
629
|
+
- **Audio mixing:** \`${e} mix\` \u2014 combine voice, music, and video tracks.
|
|
630
|
+
|
|
631
|
+
During the main workflow, branding and audio are handled by their dedicated agents. But for one-off fixes or user requests outside the workflow, use these commands directly.
|
|
632
|
+
`}function ft(n){let{name:t,cmd:e}=n;return`---
|
|
633
|
+
name: ${t}-presentation
|
|
654
634
|
description: Use when user asks to create presentations, slides, decks, pitch decks.
|
|
655
635
|
Researches the topic, gathers context, and generates AI-powered presentations via CLI.
|
|
656
636
|
---
|
|
657
637
|
|
|
658
|
-
# ${
|
|
638
|
+
# ${t} Presentation CLI
|
|
659
639
|
|
|
660
640
|
---
|
|
661
641
|
|
|
@@ -680,7 +660,8 @@ If this fails \u2192 **STOP**. Tell the user: \`${e} login\`. Do NOT proceed wit
|
|
|
680
660
|
| \`${e} list\` | List presentations |
|
|
681
661
|
| \`${e} get <id>\` | Get details |
|
|
682
662
|
| \`${e} export <id> -o file.zip\` | Export |
|
|
683
|
-
| \`${e} branding
|
|
663
|
+
| \`${e} branding find <name-or-url>\` | Search existing brands (returns IDs) |
|
|
664
|
+
| \`${e} branding extract <url>\` | Extract NEW brand identity |
|
|
684
665
|
| \`${e} branding list\` | List saved brands |
|
|
685
666
|
| \`${e} images search "query"\` | Stock images |
|
|
686
667
|
|
|
@@ -721,20 +702,26 @@ ${e} create "Q4 Review" --file ./report.pdf --file ./data.xlsx -n 10
|
|
|
721
702
|
|
|
722
703
|
**If a brand/company is detected in the request:**
|
|
723
704
|
|
|
724
|
-
1.
|
|
705
|
+
1. **Search existing brands first** (always before extracting):
|
|
725
706
|
\`\`\`bash
|
|
726
|
-
${e} branding
|
|
707
|
+
${e} branding find "Company Name"
|
|
708
|
+
${e} branding find company.com
|
|
727
709
|
\`\`\`
|
|
728
710
|
|
|
729
|
-
2. **
|
|
730
|
-
-
|
|
731
|
-
- If
|
|
732
|
-
|
|
711
|
+
2. **If found** \u2014 use AskUserQuestion: "Found existing brand for [name]. Apply it?"
|
|
712
|
+
- Options: "Yes, apply" / "Skip branding"
|
|
713
|
+
- If yes: get the brand ID from the find results
|
|
714
|
+
|
|
715
|
+
3. **If not found** \u2014 extract immediately (no need to ask):
|
|
716
|
+
\`${e} branding extract https://company.com\`
|
|
717
|
+
|
|
718
|
+
4. **If extraction FAILS** (rate limit, plan limit, server error):
|
|
719
|
+
- **STOP immediately** \u2014 do NOT retry or manually create brand assets
|
|
720
|
+
- Use AskUserQuestion: "Brand extraction failed: [error]. How to proceed?"
|
|
721
|
+
- Options: "Continue without branding" / "Try a different URL" / "Pick from existing brands"
|
|
733
722
|
|
|
734
|
-
|
|
723
|
+
5. **If user confirms** \u2192 use brand ID in create:
|
|
735
724
|
\`\`\`bash
|
|
736
|
-
${e} branding extract https://company.com
|
|
737
|
-
# Returns brand ID \u2192 use in create
|
|
738
725
|
${e} create "Topic" --branding-id <id> --styling brand-plus-style ...
|
|
739
726
|
\`\`\`
|
|
740
727
|
|
|
@@ -829,12 +816,12 @@ ${e} import ./presentation.zip
|
|
|
829
816
|
- **Auth fails** \u2192 \`${e} logout && ${e} login\` (browser required)
|
|
830
817
|
- **"Context required"** \u2192 add \`--context\`, \`--file\`, or \`--context-file\`
|
|
831
818
|
- **Rate limit (429)** \u2192 check plan with \`${e} whoami\`
|
|
832
|
-
`}function Ie(
|
|
833
|
-
name: ${
|
|
819
|
+
`}function Ie(n){let{name:t,cmd:e}=n;return`---
|
|
820
|
+
name: ${t}-tts
|
|
834
821
|
description: Text-to-speech generation. Convert text to natural speech with multiple providers (Gemini, ElevenLabs, OpenAI) and voices. Use for voiceovers, narration, accessibility.
|
|
835
822
|
---
|
|
836
823
|
|
|
837
|
-
# ${
|
|
824
|
+
# ${t} TTS (Text-to-Speech)
|
|
838
825
|
|
|
839
826
|
Convert text to natural speech with multiple providers and voices.
|
|
840
827
|
|
|
@@ -1028,12 +1015,12 @@ ${e} tts generate \\
|
|
|
1028
1015
|
2. **Voice matching** - Match voice style to content: Puck for upbeat, Charon for serious
|
|
1029
1016
|
3. **Speed adjustment** - Use 0.9-0.95 for narration, 1.0-1.1 for casual
|
|
1030
1017
|
4. **Batch processing** - Use \`video generate assets\` for multiple voiceovers
|
|
1031
|
-
`}function Re(
|
|
1032
|
-
name: ${
|
|
1018
|
+
`}function Re(n){let{name:t,cmd:e}=n;return`---
|
|
1019
|
+
name: ${t}-music
|
|
1033
1020
|
description: AI music generation from text prompts. Create background music, soundtracks, jingles for videos, presentations, podcasts. Supports ElevenLabs and Suno providers.
|
|
1034
1021
|
---
|
|
1035
1022
|
|
|
1036
|
-
# ${
|
|
1023
|
+
# ${t} Music Generation
|
|
1037
1024
|
|
|
1038
1025
|
Generate AI music from text descriptions.
|
|
1039
1026
|
|
|
@@ -1168,12 +1155,12 @@ ${e} music generate -p "calm piano" -d 30 -f json
|
|
|
1168
1155
|
2. **Looping** - For seamless loops, mention "loopable" in prompt.
|
|
1169
1156
|
3. **Video sync** - Use \`video generate assets\` to auto-generate music for video projects.
|
|
1170
1157
|
4. **Volume mixing** - Background music should be 20-30% volume when mixed with voiceover.
|
|
1171
|
-
`}function Ae(
|
|
1172
|
-
name: ${
|
|
1158
|
+
`}function Ae(n){let{name:t,cmd:e}=n;return`---
|
|
1159
|
+
name: ${t}-sfx
|
|
1173
1160
|
description: AI sound effects generation from text descriptions. Create foley, ambient, UI sounds, impacts, whooshes for videos, games, apps. Uses ElevenLabs SFX.
|
|
1174
1161
|
---
|
|
1175
1162
|
|
|
1176
|
-
# ${
|
|
1163
|
+
# ${t} Sound Effects (SFX)
|
|
1177
1164
|
|
|
1178
1165
|
Generate AI sound effects from text descriptions.
|
|
1179
1166
|
|
|
@@ -1377,12 +1364,12 @@ ${e} sfx generate -t "click" -d 0.5 -f json
|
|
|
1377
1364
|
3. **Prompt influence** - Higher (0.7-1.0) = more literal, lower (0.1-0.3) = more creative.
|
|
1378
1365
|
4. **Descriptive prompts** - Add context: "door slam, heavy wooden door" > "door slam".
|
|
1379
1366
|
5. **Format** - Use WAV for editing, MP3 for final output.
|
|
1380
|
-
`}function Oe(
|
|
1381
|
-
name: ${
|
|
1367
|
+
`}function Oe(n){let{name:t,cmd:e}=n;return`---
|
|
1368
|
+
name: ${t}-mix
|
|
1382
1369
|
description: Audio mixing for video production. Combine video with voiceover and background music. Supports volume control, fade effects, and multiple audio tracks.
|
|
1383
1370
|
---
|
|
1384
1371
|
|
|
1385
|
-
# ${
|
|
1372
|
+
# ${t} Audio Mixing
|
|
1386
1373
|
|
|
1387
1374
|
Mix video with voiceover and background music.
|
|
1388
1375
|
|
|
@@ -1527,12 +1514,12 @@ ${e} mix status <request-id>
|
|
|
1527
1514
|
2. **Music selection** - Choose music that doesn't compete with voice frequency.
|
|
1528
1515
|
3. **Consistent levels** - Keep voice at 100%, adjust music only.
|
|
1529
1516
|
4. **File sizes** - Use \`--no-wait\` for large files to avoid timeout.
|
|
1530
|
-
`}function Ee(
|
|
1531
|
-
name: ${
|
|
1517
|
+
`}function Ee(n){let{name:t,cmd:e}=n;return`---
|
|
1518
|
+
name: ${t}-image
|
|
1532
1519
|
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.
|
|
1533
1520
|
---
|
|
1534
1521
|
|
|
1535
|
-
# ${
|
|
1522
|
+
# ${t} Image Commands
|
|
1536
1523
|
|
|
1537
1524
|
Search stock images and generate AI images.
|
|
1538
1525
|
|
|
@@ -1711,12 +1698,12 @@ Found 10 images for "mountain landscape"
|
|
|
1711
1698
|
3. **Size matters** - Use \`--size large\` for hero images
|
|
1712
1699
|
4. **JSON for automation** - Parse results programmatically
|
|
1713
1700
|
5. **Generation prompts** - Be detailed, describe style, lighting, composition
|
|
1714
|
-
`}function Pe(
|
|
1715
|
-
name: ${
|
|
1701
|
+
`}function Pe(n){let{name:t,cmd:e}=n;return`---
|
|
1702
|
+
name: ${t}-video-search
|
|
1716
1703
|
description: Stock video search. Find royalty-free video clips from multiple providers (Pexels, Pixabay). For b-roll, backgrounds, transitions in video production.
|
|
1717
1704
|
---
|
|
1718
1705
|
|
|
1719
|
-
# ${
|
|
1706
|
+
# ${t} Video Search
|
|
1720
1707
|
|
|
1721
1708
|
Search for stock video clips.
|
|
1722
1709
|
|
|
@@ -1886,12 +1873,12 @@ ${e} video find "city timelapse" --orientation landscape -n 10
|
|
|
1886
1873
|
3. **Preview first** - Use preview URLs to check quality
|
|
1887
1874
|
4. **License** - Verify license for commercial use
|
|
1888
1875
|
5. **JSON export** - Save searches for batch processing
|
|
1889
|
-
`}function Fe(
|
|
1890
|
-
name: ${
|
|
1876
|
+
`}function Fe(n){let{name:t,cmd:e}=n;return`---
|
|
1877
|
+
name: ${t}-scrape
|
|
1891
1878
|
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.
|
|
1892
1879
|
---
|
|
1893
1880
|
|
|
1894
|
-
# ${
|
|
1881
|
+
# ${t} URL Scraping
|
|
1895
1882
|
|
|
1896
1883
|
Extract content from URLs for analysis and context.
|
|
1897
1884
|
|
|
@@ -2076,12 +2063,12 @@ ${e} scrape https://blog.example.com -f quiet | ${e} create "Blog Summary"
|
|
|
2076
2063
|
3. **Use JSON** - For programmatic processing of results
|
|
2077
2064
|
4. **Combine sources** - Scrape multiple URLs for comprehensive context
|
|
2078
2065
|
5. **Token count** - Check token usage for large documents
|
|
2079
|
-
`}function
|
|
2080
|
-
name: ${
|
|
2066
|
+
`}function De(n){let{name:t,cmd:e}=n;return`---
|
|
2067
|
+
name: ${t}-branding
|
|
2081
2068
|
description: Brand profile management. Extract branding from websites, manage saved brands, apply consistent styling to presentations.
|
|
2082
2069
|
---
|
|
2083
2070
|
|
|
2084
|
-
# ${
|
|
2071
|
+
# ${t} Branding Management
|
|
2085
2072
|
|
|
2086
2073
|
Manage brand profiles for consistent styling across presentations.
|
|
2087
2074
|
|
|
@@ -2106,9 +2093,19 @@ ${e} branding list
|
|
|
2106
2093
|
${e} branding list -f json
|
|
2107
2094
|
\`\`\`
|
|
2108
2095
|
|
|
2096
|
+
### \`branding find\`
|
|
2097
|
+
|
|
2098
|
+
Search existing brand profiles by name or website URL. **Always try this before extracting.**
|
|
2099
|
+
|
|
2100
|
+
\`\`\`bash
|
|
2101
|
+
${e} branding find <query>
|
|
2102
|
+
\`\`\`
|
|
2103
|
+
|
|
2104
|
+
Prints matching brands with IDs, colors, and source URLs. Use \`${e} branding get <id> -o branding.json\` to export a match.
|
|
2105
|
+
|
|
2109
2106
|
### \`branding extract\`
|
|
2110
2107
|
|
|
2111
|
-
Extract branding (colors, logos, fonts) from a website.
|
|
2108
|
+
Extract branding (colors, logos, fonts) from a website. **Only use if \`branding find\` returns no results.**
|
|
2112
2109
|
|
|
2113
2110
|
\`\`\`bash
|
|
2114
2111
|
${e} branding extract <url>
|
|
@@ -2120,6 +2117,8 @@ ${e} branding extract <url>
|
|
|
2120
2117
|
- Typography styles
|
|
2121
2118
|
- Brand name
|
|
2122
2119
|
|
|
2120
|
+
**If extraction fails** (rate limit, plan limit): proceed without branding. Do not attempt manual workarounds.
|
|
2121
|
+
|
|
2123
2122
|
### \`branding get\`
|
|
2124
2123
|
|
|
2125
2124
|
Get details of a specific brand.
|
|
@@ -2142,7 +2141,14 @@ ${e} branding set-default <brand-id>
|
|
|
2142
2141
|
## Examples
|
|
2143
2142
|
|
|
2144
2143
|
\`\`\`bash
|
|
2145
|
-
#
|
|
2144
|
+
# Search for existing brand first (ALWAYS do this before extracting)
|
|
2145
|
+
${e} branding find Acme
|
|
2146
|
+
${e} branding find acme.com
|
|
2147
|
+
|
|
2148
|
+
# Export a found brand to file by ID
|
|
2149
|
+
${e} branding get <id> -o branding.json
|
|
2150
|
+
|
|
2151
|
+
# Only extract if find returns nothing
|
|
2146
2152
|
${e} branding extract https://company.com
|
|
2147
2153
|
# Returns brand ID
|
|
2148
2154
|
|
|
@@ -2157,50 +2163,22 @@ ${e} create "Quarterly Report" --brand abc123
|
|
|
2157
2163
|
|
|
2158
2164
|
# Set as default
|
|
2159
2165
|
${e} branding set-default abc123
|
|
2160
|
-
|
|
2161
|
-
# Now all presentations use this brand by default
|
|
2162
|
-
${e} create "New Presentation"
|
|
2163
|
-
\`\`\`
|
|
2164
|
-
|
|
2165
|
-
---
|
|
2166
|
-
|
|
2167
|
-
## Output
|
|
2168
|
-
|
|
2169
|
-
### List Output
|
|
2170
|
-
\`\`\`
|
|
2171
|
-
Saved Brands:
|
|
2172
|
-
1. Acme Corp (abc123) [default]
|
|
2173
|
-
Primary: #0066CC
|
|
2174
|
-
Logo: https://...
|
|
2175
|
-
|
|
2176
|
-
2. Startup Inc (def456)
|
|
2177
|
-
Primary: #FF6B00
|
|
2178
|
-
Logo: https://...
|
|
2179
|
-
\`\`\`
|
|
2180
|
-
|
|
2181
|
-
### Extract Output
|
|
2182
|
-
\`\`\`
|
|
2183
|
-
\u2713 Brand extracted successfully
|
|
2184
|
-
ID: abc123
|
|
2185
|
-
Name: Acme Corp
|
|
2186
|
-
Primary Color: #0066CC
|
|
2187
|
-
Logo: https://...
|
|
2188
2166
|
\`\`\`
|
|
2189
2167
|
|
|
2190
2168
|
---
|
|
2191
2169
|
|
|
2192
2170
|
## Tips
|
|
2193
2171
|
|
|
2194
|
-
1. **
|
|
2195
|
-
2. **
|
|
2196
|
-
3. **
|
|
2197
|
-
4. **
|
|
2198
|
-
`}function
|
|
2199
|
-
name: ${
|
|
2172
|
+
1. **Find first, extract second** \u2014 Always search existing brands before extracting. Extraction uses API quota.
|
|
2173
|
+
2. **If extraction fails** \u2014 Proceed without branding. Do not manually create brand assets.
|
|
2174
|
+
3. **Set default** \u2014 If you mostly work with one brand, set it as default.
|
|
2175
|
+
4. **Override** \u2014 Use \`--brand\` flag to override default for specific presentations.
|
|
2176
|
+
`}function Ne(n){let{name:t,cmd:e}=n,o=t.toUpperCase().replace(/[^A-Z0-9]/g,"_");return`---
|
|
2177
|
+
name: ${t}-config
|
|
2200
2178
|
description: CLI configuration management. Set API keys, team IDs, custom API URLs. Verify authentication status.
|
|
2201
2179
|
---
|
|
2202
2180
|
|
|
2203
|
-
# ${
|
|
2181
|
+
# ${t} Configuration
|
|
2204
2182
|
|
|
2205
2183
|
Manage CLI settings and authentication.
|
|
2206
2184
|
|
|
@@ -2356,8 +2334,8 @@ ${e} config show --verify # Test API key
|
|
|
2356
2334
|
\`\`\`bash
|
|
2357
2335
|
${e} config set team-id <correct-team-id>
|
|
2358
2336
|
\`\`\`
|
|
2359
|
-
`}var
|
|
2360
|
-
name: ${
|
|
2337
|
+
`}var Yt={name:"video-ref-website-assets",description:"Extract maximum assets from websites for video production.",render:n=>`---
|
|
2338
|
+
name: ${n.name}-video-ref-website-assets
|
|
2361
2339
|
description: Extract maximum assets from websites for video production.
|
|
2362
2340
|
---
|
|
2363
2341
|
|
|
@@ -2367,17 +2345,12 @@ Goal: extract everything usable from the website. Use real assets, not stock.
|
|
|
2367
2345
|
|
|
2368
2346
|
## Branding
|
|
2369
2347
|
|
|
2370
|
-
|
|
2371
|
-
${t.cmd} branding extract <url>
|
|
2372
|
-
${t.cmd} branding get <id> --format json
|
|
2373
|
-
\`\`\`
|
|
2374
|
-
|
|
2375
|
-
Get: logo, colors, typography.
|
|
2348
|
+
Branding is handled by the \`video-branding-director\` agent (Step 4). Do not extract branding manually here.
|
|
2376
2349
|
|
|
2377
2350
|
## Content
|
|
2378
2351
|
|
|
2379
2352
|
\`\`\`bash
|
|
2380
|
-
${
|
|
2353
|
+
${n.cmd} scrape <url> -o page.md
|
|
2381
2354
|
\`\`\`
|
|
2382
2355
|
|
|
2383
2356
|
Scrape multiple pages:
|
|
@@ -2429,13 +2402,11 @@ agent-browser screenshot --path ./screenshots/hero.png
|
|
|
2429
2402
|
|
|
2430
2403
|
## Troubleshooting
|
|
2431
2404
|
|
|
2432
|
-
**branding extract fails** - try scrape first, some sites need JS rendering.
|
|
2433
|
-
|
|
2434
2405
|
**Images 403/404** - may need referrer header: \`curl -e <site-url> -O <image-url>\`
|
|
2435
2406
|
|
|
2436
2407
|
**Lazy-loaded images** - use agent-browser to scroll and capture, or inspect network tab for actual URLs.
|
|
2437
|
-
`};var
|
|
2438
|
-
name: ${
|
|
2408
|
+
`};var Jt={name:"video-ref-brief-guide",description:"How to write video brief for server-side plan generation.",render:n=>`---
|
|
2409
|
+
name: ${n.name}-video-ref-brief-guide
|
|
2439
2410
|
description: How to write video brief for server-side plan generation.
|
|
2440
2411
|
---
|
|
2441
2412
|
|
|
@@ -2497,19 +2468,19 @@ These will be rebuilt as lightweight animated versions for Remotion. Focus on vi
|
|
|
2497
2468
|
---
|
|
2498
2469
|
|
|
2499
2470
|
**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\`.
|
|
2500
|
-
`};var
|
|
2501
|
-
name: ${
|
|
2471
|
+
`};var Ht={name:"video-ref-source-scraping",description:"Extract content from various sources (YouTube, websites, Twitter).",render:n=>`---
|
|
2472
|
+
name: ${n.name}-video-ref-source-scraping
|
|
2502
2473
|
description: Extract content from various sources (YouTube, websites, Twitter).
|
|
2503
2474
|
---
|
|
2504
2475
|
|
|
2505
2476
|
# Source Scraping
|
|
2506
2477
|
|
|
2507
|
-
The \`${
|
|
2478
|
+
The \`${n.cmd} scrape\` command automatically detects source type and extracts content.
|
|
2508
2479
|
|
|
2509
2480
|
## YouTube Videos
|
|
2510
2481
|
|
|
2511
2482
|
\`\`\`bash
|
|
2512
|
-
${
|
|
2483
|
+
${n.cmd} scrape <youtube-url>
|
|
2513
2484
|
\`\`\`
|
|
2514
2485
|
|
|
2515
2486
|
Extracts: transcript, title, description, channel info
|
|
@@ -2517,7 +2488,7 @@ Extracts: transcript, title, description, channel info
|
|
|
2517
2488
|
## Websites
|
|
2518
2489
|
|
|
2519
2490
|
\`\`\`bash
|
|
2520
|
-
${
|
|
2491
|
+
${n.cmd} scrape <website-url>
|
|
2521
2492
|
\`\`\`
|
|
2522
2493
|
|
|
2523
2494
|
Extracts: content, headings, metadata
|
|
@@ -2525,7 +2496,7 @@ Extracts: content, headings, metadata
|
|
|
2525
2496
|
## Twitter/X Posts
|
|
2526
2497
|
|
|
2527
2498
|
\`\`\`bash
|
|
2528
|
-
${
|
|
2499
|
+
${n.cmd} scrape <twitter-url>
|
|
2529
2500
|
\`\`\`
|
|
2530
2501
|
|
|
2531
2502
|
Extracts: tweet text, author info, media URLs
|
|
@@ -2533,7 +2504,7 @@ Extracts: tweet text, author info, media URLs
|
|
|
2533
2504
|
---
|
|
2534
2505
|
|
|
2535
2506
|
All scraped content is returned in markdown format.
|
|
2536
|
-
`};import{mkdirSync as
|
|
2507
|
+
`};import{mkdirSync as gt,writeFileSync as ht,existsSync as Ue,rmSync as $o}from"fs";import{join as H,resolve as Ke,relative as yi}from"path";import{homedir as yt}from"os";var de=[{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 bi(n,t){let e=Ke(n),o=Ke(n,t);if(yi(e,o).startsWith("..")||Ke(o)!==o.replace(/\.\./g,""))throw new Error(`Invalid path: "${t}" would escape base directory`);return o}function Co(n,t,e){let o=H(n,"SKILL.md");if(gt(n,{recursive:!0}),ht(o,t,"utf-8"),e&&e.length>0){let r=H(n,"references");gt(r,{recursive:!0});for(let i of e){let a=H(r,`${i.name}.md`);ht(a,i.content,"utf-8")}}}function bt(n,t,e={},o){let r={installed:[],skipped:[],errors:[]},i=e.local?process.cwd():yt();if(e.dir)try{let a=Ke(e.dir),s=bi(a,H("skills",n));Co(s,t,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 de){let s=H(i,a.dir),l=H(s,"skills",n),m=H(l,"SKILL.md");if(Ue(s)){if(Ue(m)&&!e.force){r.skipped.push(a.name);continue}try{Co(l,t,o),r.installed.push(a.name)}catch(p){r.errors.push(`${a.name}: ${p instanceof Error?p.message:String(p)}`)}}}return r}function vt(n,t={}){let e={removed:[],errors:[]},o=t.local?process.cwd():yt();for(let r of de){let i=H(o,r.dir,"skills",n);if(Ue(i))try{$o(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 Io(){return de.map(n=>n.name)}function Ro(n,t,e={}){let o={installed:[],skipped:[],errors:[]};if(e.dir){let a=H(Ke(e.dir),"agents"),s=H(a,`${n}.md`);if(Ue(s)&&!e.force)o.skipped.push(e.dir);else try{gt(a,{recursive:!0}),ht(s,t,"utf-8"),o.installed.push(e.dir)}catch(l){o.errors.push(`${e.dir}: ${l instanceof Error?l.message:String(l)}`)}return o}let r=yt(),i=de.filter(a=>a.dir===".claude");for(let a of i){let s=H(r,a.dir,"agents"),l=H(s,`${n}.md`);if(Ue(l)&&!e.force){o.skipped.push(a.name);continue}try{gt(s,{recursive:!0}),ht(l,t,"utf-8"),o.installed.push(a.name)}catch(m){o.errors.push(`${a.name}: ${m instanceof Error?m.message:String(m)}`)}}return o}function Ao(n){let t={removed:[],errors:[]},e=yt(),o=de.filter(r=>r.dir===".claude");for(let r of o){let i=H(e,r.dir,"agents",`${n}.md`);if(Ue(i))try{$o(i),t.removed.push(r.name)}catch(a){t.errors.push(`${r.name}: ${a instanceof Error?a.message:String(a)}`)}}return t}function Kt(){return`---
|
|
2537
2508
|
name: video-scene-reviewer
|
|
2538
2509
|
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.
|
|
2539
2510
|
tools: Read, Glob
|
|
@@ -2630,10 +2601,10 @@ Priority fixes:
|
|
|
2630
2601
|
- Scene under 80 lines with 5+ seconds duration = flag as too simple
|
|
2631
2602
|
- Be specific in fixes: exact icon name, exact component, exact file
|
|
2632
2603
|
- 85% pass rate minimum. Below = not ready for render
|
|
2633
|
-
`}function
|
|
2604
|
+
`}function zt(){return`---
|
|
2634
2605
|
name: video-scene-builder
|
|
2635
2606
|
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
|
|
2607
|
+
tools: Read, Write, Edit, Glob, Grep, Task, Bash
|
|
2637
2608
|
model: sonnet
|
|
2638
2609
|
skills:
|
|
2639
2610
|
- frontend-design
|
|
@@ -2709,12 +2680,34 @@ Read the reviewer's report. Fix every issue:
|
|
|
2709
2680
|
|
|
2710
2681
|
If the reviewer score was below 85%, re-run the reviewer after fixes. Repeat until passing.
|
|
2711
2682
|
|
|
2712
|
-
### Step 6:
|
|
2683
|
+
### Step 6: TypeScript Verification
|
|
2713
2684
|
|
|
2714
|
-
|
|
2685
|
+
**MANDATORY** \u2014 a scene is NOT done until it has zero TypeScript errors.
|
|
2715
2686
|
|
|
2687
|
+
After the reviewer passes, run TypeScript check on your scene file:
|
|
2688
|
+
|
|
2689
|
+
\\\`\\\`\\\`bash
|
|
2690
|
+
npx tsc --noEmit --pretty 2>&1 | grep -A 2 "[output-file-name]"
|
|
2716
2691
|
\\\`\\\`\\\`
|
|
2717
|
-
|
|
2692
|
+
|
|
2693
|
+
If there are ANY errors or warnings:
|
|
2694
|
+
1. **Fix every issue** \u2014 unused variables, missing imports, type mismatches, unused imports
|
|
2695
|
+
2. **Remove unused variables entirely** \u2014 do not prefix with \\\`_\\\`, just delete them or use them
|
|
2696
|
+
3. **Re-run** \\\`npx tsc --noEmit\\\` after fixes to confirm zero errors
|
|
2697
|
+
4. **Repeat** until the output is clean for your scene file
|
|
2698
|
+
|
|
2699
|
+
Common issues to watch for:
|
|
2700
|
+
- Declared variables/constants you never reference in JSX \u2192 remove them
|
|
2701
|
+
- Imported components or icons you don't use \u2192 remove the import
|
|
2702
|
+
- \\\`interpolate()\\\` or \\\`spring()\\\` return values assigned but not used in any style \u2192 either use them or remove them
|
|
2703
|
+
- Type errors from wrong props on Remotion or Lucide components
|
|
2704
|
+
|
|
2705
|
+
### Step 7: Done
|
|
2706
|
+
|
|
2707
|
+
When the reviewer passes (85%+) AND TypeScript reports zero errors for your scene:
|
|
2708
|
+
|
|
2709
|
+
\\\`\\\`\\\`
|
|
2710
|
+
Scene [name] built: [X] lines, score [Y]%, [Z] visual events, 0 TS errors
|
|
2718
2711
|
\\\`\\\`\\\`
|
|
2719
2712
|
|
|
2720
2713
|
## What You Do NOT Do
|
|
@@ -2724,7 +2717,7 @@ Scene [name] built: [X] lines, score [Y]%, [Z] visual events
|
|
|
2724
2717
|
- Don't download assets \u2014 the main AI already did that
|
|
2725
2718
|
- Don't run render commands \u2014 the main AI handles that
|
|
2726
2719
|
- Don't modify video-plan.md \u2014 it's read-only to you
|
|
2727
|
-
`}function
|
|
2720
|
+
`}function Xt(n){return`---
|
|
2728
2721
|
name: video-audio-director
|
|
2729
2722
|
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
2723
|
tools: Read, Bash, Write
|
|
@@ -2736,7 +2729,7 @@ You are the **Audio Director** for a video production. You receive the video pla
|
|
|
2736
2729
|
|
|
2737
2730
|
## Your Input (provided in the Task prompt)
|
|
2738
2731
|
|
|
2739
|
-
1. **CLI command** \u2014
|
|
2732
|
+
1. **CLI command** \u2014 \\\`${n?.cmd??"mf"}\\\`
|
|
2740
2733
|
2. **Video plan path** \u2014 path to video-plan.md
|
|
2741
2734
|
3. **Output directory** \u2014 where to write audio files (e.g., ./public/audio/)
|
|
2742
2735
|
4. **Voice** \u2014 TTS voice (default: Puck)
|
|
@@ -2868,6 +2861,14 @@ Manifest: timing-manifest.md
|
|
|
2868
2861
|
=== END ===
|
|
2869
2862
|
\\\`\\\`\\\`
|
|
2870
2863
|
|
|
2864
|
+
## Error Handling
|
|
2865
|
+
|
|
2866
|
+
If a command output shows a successful generation (Status: completed, Audio URL: ...) but the file download failed, do NOT re-run the generate command. Instead:
|
|
2867
|
+
1. Create the missing directory: \\\`mkdir -p ./public/audio\\\`
|
|
2868
|
+
2. Download from the Audio URL: \\\`curl -o <output_path> "<Audio URL from the output>"\\\`
|
|
2869
|
+
|
|
2870
|
+
This saves money and time \u2014 generation is expensive, downloading is free.
|
|
2871
|
+
|
|
2871
2872
|
## What You Do NOT Do
|
|
2872
2873
|
|
|
2873
2874
|
- Don't build Remotion scenes or write .tsx files
|
|
@@ -2875,7 +2876,8 @@ Manifest: timing-manifest.md
|
|
|
2875
2876
|
- Don't make creative decisions about visuals
|
|
2876
2877
|
- Don't choose voices \u2014 use the voice from your input
|
|
2877
2878
|
- Don't skip the timing manifest \u2014 it's the critical handoff to scene builders
|
|
2878
|
-
|
|
2879
|
+
- Don't re-generate audio if it was already generated successfully (see Error Handling above)
|
|
2880
|
+
`}function Qt(){return`---
|
|
2879
2881
|
name: video-continuity-editor
|
|
2880
2882
|
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
2883
|
tools: Read, Glob, Grep
|
|
@@ -2986,7 +2988,110 @@ FIXES:
|
|
|
2986
2988
|
- Focus on CROSS-SCENE issues. Per-scene quality is the reviewer's job.
|
|
2987
2989
|
- Be specific in fixes: exact file, exact line, exact component.
|
|
2988
2990
|
- 85% pass rate minimum.
|
|
2989
|
-
`}function
|
|
2991
|
+
`}function Zt(n){let t=n?.cmd??"mf";return`---
|
|
2992
|
+
name: video-branding-director
|
|
2993
|
+
description: Extracts brand identity (logos, colors, fonts) from a URL or existing profiles. Searches saved brands, extracts new ones via CLI, downloads assets locally, and writes branding.json. Returns success or failure. One agent per video, runs before brief/plan generation.
|
|
2994
|
+
tools: Read, Bash, Write
|
|
2995
|
+
model: sonnet
|
|
2996
|
+
permissionMode: acceptEdits
|
|
2997
|
+
---
|
|
2998
|
+
|
|
2999
|
+
You are the **Branding Agent** for a video production pipeline. Your job is to obtain brand identity data and write \\\`branding.json\\\` to the project directory.
|
|
3000
|
+
|
|
3001
|
+
## Input
|
|
3002
|
+
|
|
3003
|
+
You receive these values in the Task prompt:
|
|
3004
|
+
|
|
3005
|
+
1. **Brand query** \u2014 a company name, URL, or brand ID
|
|
3006
|
+
2. **Project directory** \u2014 where to write branding.json and download assets
|
|
3007
|
+
|
|
3008
|
+
The CLI command is \\\`${t}\\\`.
|
|
3009
|
+
|
|
3010
|
+
## Rules
|
|
3011
|
+
|
|
3012
|
+
- All branding data comes from the CLI. You have no direct access to brand assets.
|
|
3013
|
+
- If the CLI cannot provide branding, return failure. Do not work around it.
|
|
3014
|
+
- Never download logos from websites directly.
|
|
3015
|
+
- Never guess brand colors or create branding.json by hand.
|
|
3016
|
+
- Never retry a failed extraction on the same URL.
|
|
3017
|
+
- Never ask the user questions \u2014 return failure and let the orchestrator handle user interaction.
|
|
3018
|
+
|
|
3019
|
+
## Process
|
|
3020
|
+
|
|
3021
|
+
### 1. Search existing brands
|
|
3022
|
+
|
|
3023
|
+
\\\`\\\`\\\`bash
|
|
3024
|
+
${t} branding find <name-or-url>
|
|
3025
|
+
\\\`\\\`\\\`
|
|
3026
|
+
|
|
3027
|
+
If a match is found, export by ID and skip to step 3:
|
|
3028
|
+
|
|
3029
|
+
\\\`\\\`\\\`bash
|
|
3030
|
+
${t} branding get <id> -o branding.json
|
|
3031
|
+
\\\`\\\`\\\`
|
|
3032
|
+
|
|
3033
|
+
### 2. Extract brand (only if step 1 found nothing)
|
|
3034
|
+
|
|
3035
|
+
\\\`\\\`\\\`bash
|
|
3036
|
+
${t} branding extract <url> --output branding.json
|
|
3037
|
+
\\\`\\\`\\\`
|
|
3038
|
+
|
|
3039
|
+
If the query is a company name, infer the URL (e.g. "Nike" \u2192 nike.com).
|
|
3040
|
+
|
|
3041
|
+
If extraction fails for any reason \u2014 rate limit, server error, plan limit, auth error \u2014 stop immediately and return failure with the error message.
|
|
3042
|
+
|
|
3043
|
+
### 3. Download assets locally
|
|
3044
|
+
|
|
3045
|
+
Only if branding.json exists and was written successfully.
|
|
3046
|
+
|
|
3047
|
+
**Logos:** Download \\\`logoUrl\\\`, \\\`logos.faviconUrl\\\`, and any \\\`logos.additionalImages\\\` URLs to \\\`./public/\\\`. Update paths in branding.json to local files.
|
|
3048
|
+
|
|
3049
|
+
**Fonts:** Download \\\`.woff2\\\` files from \\\`brandData.enhancedTypography.fontStack\\\`:
|
|
3050
|
+
|
|
3051
|
+
\\\`\\\`\\\`
|
|
3052
|
+
fontStack.primary.fontFiles = [
|
|
3053
|
+
{ url: "https://.../abc123.woff2", format: "woff2", weight: 700 }
|
|
3054
|
+
]
|
|
3055
|
+
\\\`\\\`\\\`
|
|
3056
|
+
|
|
3057
|
+
Download primary and secondary font files to \\\`./public/fonts/\\\`.
|
|
3058
|
+
|
|
3059
|
+
If localhost URLs fail, try the S3 pattern: \\\`https://cc-cb-slides.s3.us-east-2.amazonaws.com/fonts/{hash}.woff2\\\`
|
|
3060
|
+
|
|
3061
|
+
Do not use \\\`@remotion/google-fonts\\\` \u2014 these are real brand fonts, not substitutes.
|
|
3062
|
+
|
|
3063
|
+
### 4. Return result
|
|
3064
|
+
|
|
3065
|
+
Write a short summary as your final message.
|
|
3066
|
+
|
|
3067
|
+
**On success:**
|
|
3068
|
+
|
|
3069
|
+
\\\`\\\`\\\`
|
|
3070
|
+
BRANDING_SUCCESS
|
|
3071
|
+
File: branding.json
|
|
3072
|
+
Brand: <brand name>
|
|
3073
|
+
Colors: <primary>, <secondary>
|
|
3074
|
+
Logo: <local path>
|
|
3075
|
+
Fonts: <primary family> (<weights>), <secondary family> (<weights>)
|
|
3076
|
+
\\\`\\\`\\\`
|
|
3077
|
+
|
|
3078
|
+
**On failure:**
|
|
3079
|
+
|
|
3080
|
+
\\\`\\\`\\\`
|
|
3081
|
+
BRANDING_FAILED
|
|
3082
|
+
Error: <error message from the CLI>
|
|
3083
|
+
Query: <what was searched/extracted>
|
|
3084
|
+
\\\`\\\`\\\`
|
|
3085
|
+
|
|
3086
|
+
## What you do NOT do
|
|
3087
|
+
|
|
3088
|
+
- Build Remotion scenes or write .tsx files
|
|
3089
|
+
- Make creative decisions about video content
|
|
3090
|
+
- Retry failed extractions on the same URL
|
|
3091
|
+
- Download logos or images from websites directly (only from branding.json URLs after successful extraction)
|
|
3092
|
+
- Create or edit branding.json by hand
|
|
3093
|
+
- Ask the user questions \u2014 return failure to the orchestrator instead
|
|
3094
|
+
`}function Oo(n){return`---
|
|
2990
3095
|
name: remotion-scene-craft
|
|
2991
3096
|
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
3097
|
---
|
|
@@ -3260,83 +3365,84 @@ const glow = interpolate(Math.sin(frame / fps * Math.PI * 2 / 2), [-1, 1], [0.1,
|
|
|
3260
3365
|
- Flat solid backgrounds -> Always gradient, noise, or moving texture.
|
|
3261
3366
|
- Static scenes with no visual change for 2+ seconds -> Every 2s needs a visual event.
|
|
3262
3367
|
- Generic component substitution -> Plan says WhipPan, use WhipPan. Not slide() or wipe().
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
${
|
|
3267
|
-
${
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
${
|
|
3272
|
-
${
|
|
3273
|
-
${
|
|
3274
|
-
${
|
|
3275
|
-
${
|
|
3276
|
-
${
|
|
3277
|
-
${
|
|
3278
|
-
${
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3368
|
+
- Unused variables or imports -> Every \`const\`, \`import\`, and \`interpolate()\` call must be used in JSX/return. Remove what you don't use. Do NOT prefix with \`_\` \u2014 delete entirely.
|
|
3369
|
+
`}var ze=["main","video","presentation","tts","music","sfx","mix","image","video-search","scrape","branding","config"],xt=["standalone","studio"],Eo={cmd:u.commands[0],pkg:u.packageName,url:u.apiUrl,name:u.name},Le=new vi("skill").description(`Manage ${u.displayName} skills for AI coding assistants`).addHelpText("after",`
|
|
3370
|
+
${N.bold("Skill Types:")}
|
|
3371
|
+
${N.cyan("main")} Main CLI skill with all capabilities overview
|
|
3372
|
+
${N.cyan("video")} Detailed video creation workflow with Remotion/R3F patterns
|
|
3373
|
+
${N.cyan("presentation")} Detailed presentation creation workflow
|
|
3374
|
+
|
|
3375
|
+
${N.bold("Reference Skills:")}
|
|
3376
|
+
${N.cyan("tts")} Text-to-speech generation (voices, providers, options)
|
|
3377
|
+
${N.cyan("music")} AI music generation (prompts, styles, duration)
|
|
3378
|
+
${N.cyan("sfx")} Sound effects generation (categories, looping, formats)
|
|
3379
|
+
${N.cyan("mix")} Audio mixing (video + voice + music)
|
|
3380
|
+
${N.cyan("image")} Image search and AI generation
|
|
3381
|
+
${N.cyan("video-search")} Stock video search
|
|
3382
|
+
${N.cyan("scrape")} URL content extraction
|
|
3383
|
+
${N.cyan("branding")} Brand profile management
|
|
3384
|
+
${N.cyan("config")} CLI configuration management
|
|
3385
|
+
|
|
3386
|
+
${N.bold("Examples:")}
|
|
3387
|
+
${N.gray("# Install main CLI skill (comprehensive overview)")}
|
|
3282
3388
|
$ ${u.commands[0]} skill install main
|
|
3283
3389
|
|
|
3284
|
-
${
|
|
3390
|
+
${N.gray("# Install video skill")}
|
|
3285
3391
|
$ ${u.commands[0]} skill install video
|
|
3286
3392
|
|
|
3287
|
-
${
|
|
3393
|
+
${N.gray("# Install presentation skill")}
|
|
3288
3394
|
$ ${u.commands[0]} skill install presentation
|
|
3289
3395
|
|
|
3290
|
-
${
|
|
3396
|
+
${N.gray("# Install all skills")}
|
|
3291
3397
|
$ ${u.commands[0]} skill install
|
|
3292
3398
|
|
|
3293
|
-
${
|
|
3399
|
+
${N.gray("# Install to specific directory")}
|
|
3294
3400
|
$ ${u.commands[0]} skill install main --dir ~/.claude
|
|
3295
3401
|
|
|
3296
|
-
${
|
|
3402
|
+
${N.gray("# Show skill content")}
|
|
3297
3403
|
$ ${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
|
|
3300
|
-
`)
|
|
3301
|
-
Results saved to: ${o}`)}});var
|
|
3404
|
+
`);Le.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").option("-m, --mode <mode>","Skill mode: standalone (render to file) or studio (Studio Preview)","standalone").action(async(n,t)=>{t.mode&&!xt.includes(t.mode)&&(c(`Invalid mode: ${t.mode}. Must be one of: ${xt.join(", ")}`),process.exit(1));let e={...Eo,mode:t.mode},o=[];n&&!ze.includes(n)&&(c(`Invalid skill type: ${n}. Must be one of: ${ze.join(", ")}`),process.exit(1));let r=[{name:"tts",content:Ie(e)},{name:"music",content:Re(e)},{name:"sfx",content:Ae(e)},{name:"mix",content:Oe(e)},{name:"image",content:Ee(e)},{name:"video-search",content:Pe(e)},{name:"scrape",content:Fe(e)},{name:"branding",content:De(e)},{name:"config",content:Ne(e)}];(!n||n==="main")&&o.push({name:u.name,content:ut(e),references:r});let i=[{name:"brief-guide",content:Jt.render(e)},{name:"website-assets",content:Yt.render(e)},{name:"source-scraping",content:Ht.render(e)}];(!n||n==="video")&&(o.push({name:`${u.name}-video`,content:pt(e),references:i}),o.push({name:"remotion-scene-craft",content:Oo(e)})),(!n||n==="presentation")&&o.push({name:`${u.name}-presentation`,content:ft(e)}),n==="tts"&&o.push({name:`${u.name}-tts`,content:Ie(e)}),n==="music"&&o.push({name:`${u.name}-music`,content:Re(e)}),n==="sfx"&&o.push({name:`${u.name}-sfx`,content:Ae(e)}),n==="mix"&&o.push({name:`${u.name}-mix`,content:Oe(e)}),n==="image"&&o.push({name:`${u.name}-image`,content:Ee(e)}),n==="video-search"&&o.push({name:`${u.name}-video-search`,content:Pe(e)}),n==="scrape"&&o.push({name:`${u.name}-scrape`,content:Fe(e)}),n==="branding"&&o.push({name:`${u.name}-branding`,content:De(e)}),n==="config"&&o.push({name:`${u.name}-config`,content:Ne(e)}),console.log();for(let a of o){d(`Installing ${a.name}...`);let s=bt(a.name,a.content,{dir:t.dir,local:t.local,force:t.force},a.references);if(s.installed.length>0&&(b(`${a.name} installed successfully`),v(" Installed to",s.installed.join(", "))),s.skipped.length>0&&(d(` Skipped (already exists): ${s.skipped.join(", ")}`),console.log(N.gray(" Use --force to overwrite"))),s.errors.length>0)for(let l of s.errors)c(` ${l}`);s.installed.length===0&&s.skipped.length===0&&s.errors.length===0&&(d(" No supported AI coding assistants detected"),console.log(N.gray(" Supported editors: "+Io().join(", "))),console.log(N.gray(" Use --dir <path> to install to a specific directory"))),console.log()}if(!n||n==="video"){for(let a of[{name:"video-branding-director",generate:()=>Zt(e)},{name:"video-scene-builder",generate:zt},{name:"video-scene-reviewer",generate:Kt},{name:"video-audio-director",generate:()=>Xt(e)},{name:"video-continuity-editor",generate:Qt}]){d(`Installing ${a.name} agent...`);let s=Ro(a.name,a.generate(),{force:t.force,dir:t.dir});s.installed.length>0&&(b(`${a.name} agent installed`),v(" Installed to",s.installed.map(l=>t.dir?l:`${l} (~/.claude/agents/)`).join(", "))),s.skipped.length>0&&d(` Agent skipped (already exists): ${s.skipped.join(", ")}`)}console.log()}});Le.command("show").description("Display skill content").argument("[type]","Skill type (default: main)").option("-m, --mode <mode>","Skill mode: standalone or studio","standalone").action((n="main",t)=>{t.mode&&!xt.includes(t.mode)&&(c(`Invalid mode: ${t.mode}. Must be one of: ${xt.join(", ")}`),process.exit(1));let e={...Eo,mode:t.mode},r={main:()=>ut(e),video:()=>pt(e),presentation:()=>ft(e),tts:()=>Ie(e),music:()=>Re(e),sfx:()=>Ae(e),mix:()=>Oe(e),image:()=>Ee(e),"video-search":()=>Pe(e),scrape:()=>Fe(e),branding:()=>De(e),config:()=>Ne(e),"video-branding-director":()=>Zt(e),"video-scene-builder":()=>zt(),"video-scene-reviewer":()=>Kt(),"video-audio-director":()=>Xt(e),"video-continuity-editor":()=>Qt()}[n];if(r)console.log(r());else{let i=[...ze,"video-branding-director","video-scene-builder","video-scene-reviewer","video-audio-director","video-continuity-editor"];c(`Invalid skill type: ${n}. Must be one of: ${i.join(", ")}`),process.exit(1)}});Le.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(n,t)=>{let e=[];n&&!ze.includes(n)&&(c(`Invalid skill type: ${n}. Must be one of: ${ze.join(", ")}`),process.exit(1)),(!n||n==="main")&&e.push(u.name),(!n||n==="video")&&(e.push(`${u.name}-video`),e.push("remotion-scene-craft")),(!n||n==="presentation")&&e.push(`${u.name}-presentation`),(!n||n==="tts")&&e.push(`${u.name}-tts`),(!n||n==="music")&&e.push(`${u.name}-music`),(!n||n==="sfx")&&e.push(`${u.name}-sfx`),(!n||n==="mix")&&e.push(`${u.name}-mix`),(!n||n==="image")&&e.push(`${u.name}-image`),(!n||n==="video-search")&&e.push(`${u.name}-video-search`),(!n||n==="scrape")&&e.push(`${u.name}-scrape`),(!n||n==="branding")&&e.push(`${u.name}-branding`),(!n||n==="config")&&e.push(`${u.name}-config`),console.log();for(let o of e){let r=vt(o,{local:t.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)I(` Failed to remove: ${i}`)}if(!n||n==="video")for(let o of["video-branding-director","video-scene-builder","video-scene-reviewer","video-audio-director","video-continuity-editor"])Ao(o).removed.length>0&&b(`${o} agent uninstalled`);console.log()});import{Command as en}from"commander";import Do from"ora";import{writeFile as xi,mkdir as wi}from"fs/promises";import{dirname as Si}from"path";var Po="en-US-Neural2-C",Fo="gemini",Ti=new en("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: ${Po})`).option("-p, --provider <provider>",`Provider: gemini, elevenlabs, openai (default: ${Fo})`).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 n=>{let t=n.format,e=t==="human"?Do("Generating speech...").start():null,o;n.speed&&(o=parseFloat(n.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(n.speakers)try{r=JSON.parse(n.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=n.voiceId,a=n.voice||Po,s=n.provider||Fo,l={};r&&(l.gemini={speakers:r}),n.instructions&&(l.openai={instructions:n.instructions});let m=await st({text:n.text,options:{provider:s,voiceId:i,voice:a,model:n.model,speed:o,providerOptions:Object.keys(l).length>0?l:void 0}});e?.stop();let p=n.output.endsWith(`.${m.format}`)?n.output:`${n.output}.${m.format}`;if(await wi(Si(p),{recursive:!0}),await xi(p,m.audioData),t==="json"){V({status:"completed",output:p,duration:m.duration,cost:m.cost,provider:m.provider,format:m.format});return}if(t==="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)}}),ki=new en("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 n=>{let t=n.format==="human"?Do("Fetching voices...").start():null;try{let e=await Gn(n.provider);if(t?.stop(),n.format==="json"){V(e.voices);return}if(Array.isArray(e.voices)){let o=n.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){t?.stop(),c(e instanceof Error?e.message:"Unknown error"),process.exit(S.GENERAL_ERROR)}}),No=new en("tts").description("Text-to-speech commands").addCommand(Ti).addCommand(ki);import{Command as on}from"commander";import tn from"ora";import{writeFile as Uo,mkdir as Ci}from"fs/promises";import{dirname as $i}from"path";function nn(n,t){if(t==="json"){V(n);return}if(t==="quiet"){n.audioUrl?console.log(n.audioUrl):console.log(n.requestId);return}d(`Request ID: ${n.requestId}`),d(`Status: ${n.status}`),n.duration&&d(`Duration: ${n.duration}s`),n.audioUrl&&b(`Audio URL: ${n.audioUrl}`),n.cost!==void 0&&d(`Cost: $${n.cost.toFixed(4)}`),n.error&&c(`Error: ${n.error}`)}async function Ii(n,t){if(await Ci($i(t),{recursive:!0}),n.startsWith("data:")){let r=n.match(/^data:[^;]+;base64,(.+)$/);if(!r)throw new Error("Invalid data URL format");let i=Buffer.from(r[1],"base64");await Uo(t,i);return}let e=await fetch(n);if(!e.ok)throw new Error(`Failed to download: ${e.status}`);let o=await e.arrayBuffer();await Uo(t,Buffer.from(o))}var Ri=new on("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 n=>{let t=parseInt(n.duration,10);(isNaN(t)||t<3||t>600)&&(c("Duration must be between 3 and 600 seconds (10 minutes)"),process.exit(S.INVALID_INPUT));let e=n.format,o=e==="human"?tn("Generating music...").start():null;try{let r=await ct({prompt:n.prompt,duration:t,options:{provider:n.provider,style:n.style}});if(!n.wait){o?.stop(),nn(r,e);return}let i=r;if(r.status!=="completed"&&r.status!=="failed"&&(o&&(o.text=`Processing (ID: ${r.requestId})...`),i=await le(()=>Je(r.requestId),60,2e3)),o?.stop(),i.status==="failed"&&(c(i.error||"Music generation failed"),process.exit(S.GENERAL_ERROR)),nn(i,e),n.output&&i.audioUrl){let a=e==="human"?tn("Downloading...").start():null;try{await Ii(i.audioUrl,n.output),a?.stop(),e==="human"&&b(`Saved to: ${n.output}`)}catch(s){a?.stop(),I(`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)}}),Ai=new on("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(n,t)=>{let e=t.format==="human"?tn("Checking status...").start():null;try{let o=await Je(n);e?.stop(),nn(o,t.format)}catch(o){e?.stop(),c(o instanceof Error?o.message:"Unknown error"),process.exit(S.GENERAL_ERROR)}}),Lo=new on("music").description("Music generation commands").addCommand(Ri).addCommand(Ai);import{Command as sn}from"commander";import rn from"ora";import{writeFile as Oi}from"fs/promises";function an(n,t){if(t==="json"){V(n);return}if(t==="quiet"){n.outputUrl?console.log(n.outputUrl):console.log(n.requestId);return}d(`Request ID: ${n.requestId}`),d(`Status: ${n.status}`),n.duration&&d(`Duration: ${n.duration}s`),n.outputUrl&&b(`Output URL: ${n.outputUrl}`),n.cost!==void 0&&d(`Cost: $${n.cost.toFixed(4)}`),n.error&&c(`Error: ${n.error}`)}async function Ei(n,t){let e=await fetch(n);if(!e.ok)throw new Error(`Failed to download: ${e.status}`);let o=await e.arrayBuffer();await Oi(t,Buffer.from(o))}var Pi=new sn("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 n=>{!n.music&&!n.voice&&(c("At least one of --music or --voice must be provided"),process.exit(S.INVALID_INPUT));let t=parseInt(n.musicVolume,10)/100,e=parseInt(n.voiceVolume,10)/100;(isNaN(t)||t<0||t>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=n.format,r=o==="human"?rn("Mixing audio...").start():null,i=[{url:n.video,role:"video"}];n.music&&i.push({url:n.music,role:"background",volume:t*5}),n.voice&&i.push({url:n.voice,role:"voice",volume:e*2});try{let a=await Wn({operation:"add-to-video",inputs:i,options:{musicVolume:t,voiceVolume:e}});if(!n.wait){r?.stop(),an(a,o);return}r&&(r.text=`Processing (ID: ${a.requestId})...`);let s=await le(()=>jt(a.requestId),120,3e3);if(r?.stop(),s.status==="failed"&&(c(s.error||"Audio mixing failed"),process.exit(S.GENERAL_ERROR)),an(s,o),n.output&&s.outputUrl){let l=o==="human"?rn("Downloading...").start():null;try{await Ei(s.outputUrl,n.output),l?.stop(),o==="human"&&b(`Saved to: ${n.output}`)}catch(m){l?.stop(),I(`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)}}),Fi=new sn("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(n,t)=>{let e=t.format==="human"?rn("Checking status...").start():null;try{let o=await jt(n);e?.stop(),an(o,t.format)}catch(o){e?.stop(),c(o instanceof Error?o.message:"Unknown error"),process.exit(S.GENERAL_ERROR)}}),Mo=new sn("mix").description("Audio mixing commands").addCommand(Pi).addCommand(Fi);import{Command as jo}from"commander";import Di from"ora";var Ni=new jo("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 n=>{let t=n.format,e=t==="human"?Di("Searching for images...").start():null,o;n.maxResults&&(o=parseInt(n.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:n.query,options:{maxResults:o||10,size:n.size,safeSearch:n.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(t==="json"){V({success:!0,query:n.query,totalResults:i.length,totalCost:r.data.totalCost,images:i});return}if(t==="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 "${n.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)}}),Vo=new jo("image").description("Image search commands").addCommand(Ni);import{Command as ae}from"commander";import Qe from"ora";import{mkdir as qe,writeFile as ie,readFile as $t,access as ln,rm as dn}from"fs/promises";import{join as j,resolve as Xe}from"path";import{execSync as _e,spawn as Ki,spawnSync as zi}from"child_process";import Xi from"ffmpeg-static";import{homedir as Ui}from"os";import{join as _o}from"path";import{readdir as Li}from"fs/promises";var Mi="conceptcraft";function Me(){return _o(Ui(),`.${Mi}`,"projects")}function wt(n){return _o(Me(),n)}async function cn(){try{let n=Me();return(await Li(n,{withFileTypes:!0})).filter(e=>e.isDirectory()).map(e=>e.name)}catch{return[]}}function qo(n,t){let e=2,o=`${n}-${e}`;for(;t.includes(o);)e++,o=`${n}-${e}`;return o}import{Command as ji}from"commander";import Vi from"ora";import{execSync as _i,spawnSync as je}from"child_process";import{mkdirSync as Bo,existsSync as Tt,readFileSync as Wo,writeFileSync as Go}from"fs";import{join as ye,basename as qi}from"path";import{tmpdir as Bi}from"os";function kt(n,t){try{return _i(t,{stdio:"ignore"}),!0}catch{return!1}}function St(n){let t=Math.floor(n/60),e=Math.floor(n%60),o=Math.round(n%1*10);return`${t}:${String(e).padStart(2,"0")}.${o}`}function Gi(n){let t=je("ffprobe",["-v","error","-show_entries","format=duration","-of","default=noprint_wrappers=1:nokey=1",n],{encoding:"utf-8"});return parseFloat(t.stdout.trim())}function Wi(n,t){let e=je("ffmpeg",["-i",n,"-vf",`select='gt(scene,${t})',showinfo`,"-vsync","vfr","-f","null","-"],{encoding:"utf-8",timeout:12e4}),o=[0],r=(e.stderr||"").split(`
|
|
3405
|
+
`);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 Yi(n,t,e){let o=[];for(let r=0;r<t.length;r++){let i=t[r]+.5,a=ye(e,`scene_${String(r+1).padStart(2,"0")}.jpg`);je("ffmpeg",["-ss",String(i),"-i",n,"-vframes","1","-q:v","2","-y",a],{stdio:"ignore",timeout:3e4}),o.push(a)}return o}function Ji(n,t){let e=ye(t,"audio.wav");if(je("ffmpeg",["-i",n,"-vn","-acodec","pcm_s16le","-ar","16000","-ac","1","-y",e],{stdio:"ignore",timeout:6e4}),!Tt(e)||!kt("whisper","which whisper")&&!kt("whisper-cpp","which whisper-cpp"))return null;let r=je("whisper",[e,"--model","base","--output_format","json","--output_dir",t,"--language","en"],{encoding:"utf-8",timeout:3e5}),i=ye(t,"audio.json");if(!Tt(i))return null;try{let a=JSON.parse(Wo(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 Hi(n,t){let e=Wo(n).toString("base64"),o=JSON.stringify({model:t,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=je("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 Yo=new ji("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(n,t)=>{let e=Vi();Tt(n)||(console.error(`File not found: ${n}`),process.exit(1)),kt("ffmpeg","which ffmpeg")||(console.error("ffmpeg is required. Install: brew install ffmpeg"),process.exit(1)),t.vision!==!1&&!kt("ollama","which ollama")&&(console.error("ollama is required for vision. Install: brew install ollama"),process.exit(1));let o=t.output||ye(Bi(),`video-analyze-${Date.now()}`);Bo(o,{recursive:!0});let r=ye(o,"frames");Bo(r,{recursive:!0});let i=parseFloat(t.threshold),a=t.model;e.start("Reading video metadata...");let s=Gi(n);e.succeed(`Video: ${St(s)} (${s.toFixed(1)}s)`),e.start("Detecting scenes...");let l=Wi(n,i);e.succeed(`Detected ${l.length} scenes`),e.start("Extracting key frames...");let m=Yi(n,l,r);e.succeed(`Extracted ${m.length} key frames`);let p=null;t.transcribe!==!1&&(e.start("Transcribing audio..."),p=Ji(n,o),p?e.succeed(`Transcribed: ${p.segments.length} segments`):e.warn("Transcription skipped (whisper not installed: pip install openai-whisper)"));let C=[];for(let f=0;f<l.length;f++){let x=l[f],$=f<l.length-1?l[f+1]:s,T="";t.vision!==!1&&Tt(m[f])&&(e.start(`Describing scene ${f+1}/${l.length}...`),T=Hi(m[f],a),e.succeed(`Scene ${f+1}: ${T}`)),C.push({id:f+1,startTime:x,endTime:$,duration:$-x,framePath:m[f],description:T})}let h={source:n,duration:s,scenes:C,transcript:p};if(t.outputFormat==="json"){let f=JSON.stringify(h,null,2),x=ye(o,"analysis.json");Go(x,f),console.log(f)}else{let f=["# Video Analysis","",`**Source:** ${qi(n)}`,`**Duration:** ${St(s)} (${s.toFixed(1)}s)`,`**Scenes:** ${C.length}`,"","## Scenes",""];for(let T of C){if(f.push(`### Scene ${T.id} (${St(T.startTime)} - ${St(T.endTime)}) [${T.duration.toFixed(1)}s]`),T.description&&f.push(`**Visual:** ${T.description}`),p?.segments){let y=p.segments.filter(R=>R.end>T.startTime&&R.start<T.endTime);y.length>0&&f.push(`**Audio:** ${y.map(R=>R.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(`
|
|
3406
|
+
`),$=ye(o,"analysis.md");Go($,x),console.log(x),console.log(`
|
|
3407
|
+
Results saved to: ${o}`)}});var Qi="inizio-inc/remotion-video-template#main",Ve=30;async function Zi(n){let t=await $t(n,"utf-8");return JSON.parse(t)}async function ea(n){let t=j(n,"template.manifest.json");try{return await Zi(t)}catch{return{entry:"src/Root.tsx",compositions:[{id:"Main",type:"youtube",aspect:"16:9",default:!0}]}}}function Jo(){try{return _e("bun --version",{stdio:"ignore"}),!0}catch{return!1}}function ta(n){if(n.includes("---")||n.includes("[Section")){let r=n.split(/---|\[Section \d+\]/i).filter(i=>i.trim());if(r.length>1)return r.map(i=>i.trim())}let t=n.split(/(?<=[.!?])\s+/).map(r=>r.trim()).filter(r=>r.length>0),e=[],o="";for(let r of t){let i=r.split(/\s+/).length;o?(e.push(`${o} ${r}`),o=""):i<5&&e.length<t.length-1?o=r:e.push(r)}return o&&(e.length>0?e[e.length-1]+=` ${o}`:e.push(o)),e}function na(n,t,e=Ve,o){if(o&&o.characters.length>0)return oa(n,o,e);let r=n.reduce((a,s)=>a+s.split(/\s+/).length,0),i=0;return n.map((a,s)=>{let l=a.split(/\s+/).length,m=l/r,p=t*m,C=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)},$={id:s+1,text:a,wordCount:l,startTime:i,endTime:i+p,durationInSeconds:p,durationInFrames:C,timestamps:x};return i+=p,$})}function oa(n,t,e){let{characters:o,characterStartTimesSeconds:r,characterEndTimesSeconds:i}=t,a=o.join(""),s=[],l=0;for(let m=0;m<n.length;m++){let p=n[m],C=p.length;for(;l<o.length&&o[l].match(/^\s*$/);)l++;let h=l,f=r[h]||0;l+=C;let x=l-1;for(;x>h&&o[x]?.match(/^\s*$/);)x--;let $=i[Math.min(x,i.length-1)]||f+1,T=$-f,y=Math.round(T*e),R={characters:o.slice(h,x+1),characterStartTimesSeconds:r.slice(h,x+1).map(k=>k-f),characterEndTimesSeconds:i.slice(h,x+1).map(k=>k-f)};s.push({id:m+1,text:p,wordCount:p.split(/\s+/).length,startTime:f,endTime:$,durationInSeconds:T,durationInFrames:y,timestamps:R})}return s}function ra(n){let t=n==="tiktok"?"vertical":n==="audiogram"?"square":"widescreen";return{scenes:[{name:"Hook",script:"Replace this with a bold opening line that grabs attention.",imageQuery:`cinematic ${t} intro visual, dramatic lighting`,videoQuery:`abstract ${t} motion background`},{name:"Proof",script:"Share the core insight, benefit, or product moment you want viewers to remember.",imageQuery:`people collaborating ${t} office, warm lighting`,videoQuery:`close-up hands using technology ${t}`},{name:"Close",script:"End with a crisp call to action and a confident closing line.",imageQuery:`bold typography ${t} background, high contrast`,videoQuery:`sunrise skyline ${t} timelapse`}],voice:"en-US-Neural2-C",voiceSettings:{speed:1,stability:.4,style:.6},musicPrompt:"uplifting cinematic background, modern and confident"}}async function ia(n,t,e){let o=j(n,"public"),r=j(n,"scripts");await qe(o,{recursive:!0}),await qe(r,{recursive:!0});let i=j(o,"scenes.json");await ie(i,JSON.stringify(ra(t),null,2),"utf-8");let a=`import { execSync } from "node:child_process";
|
|
3302
3408
|
|
|
3303
3409
|
execSync("${e} video generate plan --project .", { stdio: "inherit" });
|
|
3304
3410
|
`,s=`import { execSync } from "node:child_process";
|
|
3305
3411
|
|
|
3306
3412
|
const args = process.argv.slice(2).join(" ");
|
|
3307
3413
|
execSync("${e} video export --project . " + args, { stdio: "inherit" });
|
|
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
|
|
3310
|
-
${
|
|
3311
|
-
${
|
|
3312
|
-
$ ${
|
|
3313
|
-
|
|
3314
|
-
${
|
|
3315
|
-
$ ${
|
|
3414
|
+
`;await ie(j(r,"plan.ts"),a,"utf-8"),await ie(j(r,"export.ts"),s,"utf-8")}async function Ho(){return process.stdin.isTTY?null:new Promise(n=>{let t="",e=!1;process.stdin.setEncoding("utf-8"),process.stdin.on("data",o=>{t+=o}),process.stdin.on("end",()=>{e||(e=!0,n(t.trim()||null))}),process.stdin.on("error",()=>{e||(e=!0,n(null))}),setTimeout(()=>{!e&&!t&&(e=!0,n(null))},500)})}function aa(n){return n.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}function sa(n){let t={shortTitle:"Video",elements:[],audio:[],text:[],scenes:[]},e=!0;return n.forEach(o=>{let r=o.startTime*1e3,i=o.endTime*1e3,a=o.durationInSeconds*1e3;t.scenes.push({name:o.name||`Scene ${o.id}`,text:o.text,startMs:r,endMs:i}),o.imagePath?(t.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&&t.elements.push({startMs:r,endMs:i,videoUrl:o.videoPath,enterTransition:"blur",exitTransition:"blur",animations:[]}),o.audioPath&&t.audio.push({startMs:r,endMs:i,audioUrl:o.audioPath}),o.timestamps&&t.text.push({startMs:r,endMs:i,text:o.text,position:"bottom",animations:[],timestamps:o.timestamps})}),t}async function Ct(n,t){if(n.startsWith("data:")){let r=n.match(/^data:[^;]+;base64,(.+)$/);if(!r)throw new Error("Invalid data URL format");let i=Buffer.from(r[1],"base64");await ie(t,i);return}let e=await fetch(n);if(!e.ok)throw new Error(`Failed to download: ${e.status}`);let o=await e.arrayBuffer();await ie(t,Buffer.from(o))}function ca(n){try{let o=new URL(n).pathname.split(".").pop()?.toLowerCase();if(o&&["jpg","jpeg","png","gif","webp"].includes(o))return o}catch{}return"jpg"}var la=new ae("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 n=>{let t=n.format,e=t==="human"?Qe("Initializing...").start():null,o=n.project?Xe(process.cwd(),n.project):null;try{let r=await Ho(),i=null;if(n.scenes&&!n.scriptFile&&(n.scriptFile=n.scenes),o&&(n.output=j(o,"public"),n.scriptFile?n.scriptFile=Xe(o,n.scriptFile):!n.script&&!r&&(n.scriptFile=j(o,"public","scenes.json"))),r)try{let w=JSON.parse(r);w.scenes&&Array.isArray(w.scenes)&&(i=w)}catch{}if(!i&&n.scriptFile)try{let w=await $t(n.scriptFile,"utf-8"),B=JSON.parse(w);B.scenes&&Array.isArray(B.scenes)&&(i=B)}catch{}let a=i?.voice||n.voice,s=i?.voiceId,l=i?.provider,m=i?.model,p=i?.voiceInstructions,C=i?.musicPrompt||n.musicPrompt||"uplifting background music, positive energy",h=i?.musicDuration,f=j(n.output,"audio"),x=j(n.output,"images"),$=j(n.output,"videos");e&&(e.text="Creating directories..."),await qe(f,{recursive:!0}),await qe(x,{recursive:!0}),await qe($,{recursive:!0});let T=0,y=[],R=0,k=[],g=[];if(i&&i.scenes.length>0){t==="human"&&(e?.stop(),d(`Processing ${i.scenes.length} scenes...`),e?.start());let w=i.scenes.map((X,L)=>{let oe=X.script;return X.voiceStyle&&(oe=`${X.voiceStyle} ${oe}`),{text:oe,id:`scene-${L}`}});e&&(e.text="Generating speech for all scenes...");let B=await Bn({texts:w,options:{provider:l,voice:a,voiceId:s,model:m,voiceSettings:i.voiceSettings,instructions:p}});T+=B.totalCost;let z=0;for(let X=0;X<i.scenes.length;X++){let L=i.scenes[X],oe=aa(L.name),Z=B.results[X];e&&(e.text=`[${L.name}] Saving audio...`);let At=j(f,`${oe}.${Z.format}`);await ie(At,Z.audioData);let xe=Z.duration,or=Math.round(xe*Ve),Ge={id:X+1,name:L.name,text:L.script,wordCount:L.script.split(/\s+/).length,startTime:z,endTime:z+xe,durationInSeconds:xe,durationInFrames:or,audioPath:`audio/${oe}.${Z.format}`,timestamps:Z.timestamps};if(L.imageQuery){e&&(e.text=`[${L.name}] Searching image...`);try{let W=await lt({query:L.imageQuery,options:{maxResults:1,size:"large",safeSearch:!0}}),We=W.data.results.flatMap(G=>G.results);if(T+=W.data.totalCost,We.length>0){let G=We[0],Ye=ca(G.url),me=`${oe}.${Ye}`,Ot=j(x,me);await Ct(G.url,Ot),Ge.imagePath=`images/${me}`,k.push({path:`images/${me}`,url:G.url,width:G.width,height:G.height,query:L.imageQuery})}}catch(W){t==="human"&&(e?.stop(),I(`[${L.name}] Image search failed: ${W instanceof Error?W.message:"Unknown"}`),e?.start())}}if(L.videoQuery){e&&(e.text=`[${L.name}] Searching video...`);try{let W=await Vt({query:L.videoQuery,options:{maxResults:1,license:"free"}}),We=W.data.results.flatMap(G=>G.results);if(T+=W.data.totalCost,We.length>0){let G=We[0],Ye=G.previewUrl||G.downloadUrl;if(Ye){let me=`${oe}.mp4`,Ot=j($,me);await Ct(Ye,Ot),Ge.videoPath=`videos/${me}`,g.push({path:`videos/${me}`,url:Ye,width:G.width,height:G.height,duration:G.duration,query:L.videoQuery})}}}catch(W){t==="human"&&(e?.stop(),I(`[${L.name}] Video search failed: ${W instanceof Error?W.message:"Unknown"}`),e?.start())}}if(y.push(Ge),z+=xe,R+=xe,t==="human"){e?.stop();let W=[`audio: ${xe.toFixed(1)}s`,Ge.imagePath?"image":null,Ge.videoPath?"video":null].filter(Boolean).join(", ");b(` ${L.name}: ${W}`),e?.start()}}}else{let w=n.script;if(n.scriptFile)try{w=await $t(n.scriptFile,"utf-8")}catch(Z){e?.stop(),c(`Failed to read script file: ${Z instanceof Error?Z.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=n.topic||w.split(".")[0].slice(0,50);e&&(e.text="Generating voiceover...");let z=await st({text:w,options:{voice:a}}),X=j(f,`voiceover.${z.format}`);await ie(X,z.audioData),T+=z.cost,R=z.duration;let L=ta(w);y=na(L,z.duration,Ve,z.timestamps).map((Z,At)=>({...Z,name:`Section${At+1}`,audioPath:`audio/voiceover.${z.format}`})),t==="human"&&(e?.stop(),b(`Voiceover: ${X} (${z.duration.toFixed(1)}s)`),e?.start())}e&&(e.text="Creating timeline...");let A=sa(y),q=Math.max(A.audio.length>0?Math.max(...A.audio.map(w=>w.endMs)):0,A.text.length>0?Math.max(...A.text.map(w=>w.endMs)):0,A.elements.length>0?Math.max(...A.elements.map(w=>w.endMs)):0),K=q/1e3,ne=h?Math.min(300,Math.max(3,h)):Math.min(300,Math.ceil(K));console.log("[Music Generation] Requesting music:",{prompt:C,requestedDuration:ne,totalAudioDuration:R,actualVideoDuration:K,timelineDurationMs:q});let be,Be;e&&(e.text="Generating music and thumbnail...");let er=`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(" ")}`,It=[],tr=(async()=>{if(ne<3)return t==="human"&&(e?.stop(),I(`Video duration (${K.toFixed(1)}s) is too short for music generation (minimum 3s).`),e?.start()),null;try{let w=await ct({prompt:C,duration:ne});if(w.status!=="completed"&&w.status!=="failed"&&(w=await le(()=>Je(w.requestId),60,2e3)),w.status==="failed")throw new Error(w.error||"Unknown error");let B=j(f,"music.mp3");return w.audioUrl&&await Ct(w.audioUrl,B),{path:"audio/music.mp3",duration:w.duration||ne,prompt:C,cost:w.cost||0}}catch(w){return t==="human"&&(e?.stop(),I(`Music generation failed: ${w.message}`),e?.start()),null}})();It.push(tr);let nr=(async()=>{try{let w=await Yn({prompt:er,options:{width:1280,height:720}});if(w.success&&w.data.url){let B=j(n.output,"thumbnail.jpg");return await Ct(w.data.url,B),T+=w.data.cost||0,B}return null}catch(w){return t==="human"&&(e?.stop(),I(`Thumbnail generation failed: ${w.message}`),e?.start()),null}})();It.push(nr);let[ve,fn]=await Promise.all(It);ve&&(be=ve,T+=ve.cost||0,t==="human"&&(e?.stop(),b(`Music: ${j(f,"music.mp3")} (${ve.duration}s)`),ve.duration<K&&I(`Music duration (${ve.duration.toFixed(1)}s) is shorter than video duration (${K.toFixed(1)}s).`),e?.start())),fn&&(Be=fn,t==="human"&&(e?.stop(),b(`Thumbnail: ${Be}`),e?.start())),e&&(e.text="Writing manifest...");let gn=Math.round(K*Ve),hn={music:be,thumbnail:Be?"thumbnail.jpg":void 0,images:k,videos:g,scenes:y,timeline:A,totalDurationInFrames:gn,fps:Ve,totalCost:T,createdAt:new Date().toISOString()},Rt=j(n.output,"video-manifest.json");if(await ie(Rt,JSON.stringify(hn,null,2)),e?.stop(),t==="json"){V(hn);return}if(t==="quiet"){console.log(Rt);return}console.log(),b("Video assets created successfully!"),console.log(),d(`Scenes: ${y.length} (${gn} frames at ${Ve}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: ${be?.path} (${be?.duration}s)`),d(`Manifest: ${Rt}`),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)}}),da=new ae("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(n,t)=>{let{maxResults:e,orientation:o,license:r,format:i}=t,a=i==="human"?Qe("Finding videos...").start():null;try{let s=await Vt({query:n,options:{maxResults:parseInt(e,10),orientation:o,license:r}});a?.stop();let l=s.data.results.flatMap(m=>m.results);if(i==="json"){V(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 "${n}"`),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)}}),ma=new ae("new").description("Create a new video project from a template").argument("<name>","Project directory name").option("-t, --template <repo>","GitHub repo (user/repo#commit)",Qi).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(n,t)=>{let e=t.format,o=e==="human"?Qe("Initializing video project...").start():null;try{let r=Me();await qe(r,{recursive:!0});let i=wt(n),a=t.type,s=await cn();if(s.includes(n)){let f=qo(n,s);o?.stop(),c(`Project "${n}" 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(t.template)||(o?.stop(),c(`Invalid template format: "${t.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 ${t.template}...`);let[m,p]=t.template.split("#"),C=p?`-b ${p}`:"";try{_e(`git clone ${C} --depth 1 https://github.com/${m}.git "${i}"`,{stdio:"pipe"}),await dn(j(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 ${n}/`),o?.start());let h;try{h=await ea(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 ia(i,a,u.commands[0]),t.install){let f=Jo()?"bun":"npm";o&&(o.text=`Installing dependencies with ${f}...`),await new Promise((x,$)=>{let T=Ki(f,["install"],{cwd:i,stdio:"pipe",shell:!0});T.on("close",y=>{y===0?x():$(new Error(`${f} install failed with code ${y}`))}),T.on("error",$)}),e==="human"&&(o?.stop(),b("Dependencies installed"),o?.start())}if(o?.stop(),e==="json"){V({name:n,path:i,template:t.template,type:a,installed:t.install});return}if(e==="quiet"){console.log(i);return}console.log(),b(`Video project "${n}" 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}`),t.install||d(` ${Jo()?"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)}}),ua=new ae("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 n=>{let t=Qe("Generating video plan...").start();try{let e=n.context;if(!e){let a=await Ho();a&&(e=a)}e||(t.stop(),c("Provide context via stdin or --context flag"),process.exit(S.INVALID_INPUT));let o;if(n.branding)try{o=await $t(n.branding,"utf-8")}catch{t.stop(),c(`Could not read branding file: ${n.branding}`),process.exit(S.INVALID_INPUT)}let r=await Kn({context:e,branding:o});t.stop(),r.success||(c(r.error||"Failed to generate plan"),process.exit(S.GENERAL_ERROR));let i=r.data;n.output?(await ie(n.output,i),b(`Plan saved to ${n.output}`)):console.log(i)}catch(e){t.stop(),c(e instanceof Error?e.message:"Unknown error"),process.exit(S.GENERAL_ERROR)}}),pa=new ae("generate").description("Generate parts of a video project").addCommand(ma).addCommand(la).addCommand(ua),fa=new ae("projects").description("List all video projects").option("-f, --format <format>","Output format: human, json","human").action(async n=>{let t=await cn();if(n.format==="json"){V({projectsDir:Me(),projects:t.map(e=>({name:e,path:wt(e)}))});return}if(t.length===0){d("No video projects found"),d(`Projects are stored in: ${Me()}`);return}console.log(`
|
|
3415
|
+
Video Projects:`),console.log("-".repeat(50));for(let e of t)console.log(` ${e}`),console.log(` ${wt(e)}`);console.log()}),ga=new ae("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 n=>{let t=n.format,e=t==="human"?Qe("Injecting thumbnail...").start():null;try{let o=Xe(n.video),r=Xe(n.thumbnail),i=n.output?Xe(n.output):o.replace(/\.mp4$/,"-with-thumb.mp4");await ln(o),await ln(r);let a=Xi;if(a)try{await ln(a)}catch{a=null}if(!a)try{a=_e("which ffmpeg",{encoding:"utf8"}).trim()}catch{throw new Error("ffmpeg not found. Install ffmpeg or ffmpeg-static.")}let s=zi(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 C="/tmp/cc-thumb-frame.mp4";_e(`${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 "${C}"`,{stdio:"pipe"}),e&&(e.text="Concatenating with video...");let h="/tmp/cc-concat-video.mp4";if(_e(`${a} -y -i "${C}" -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..."),_e(`${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 dn(C,{force:!0}),await dn(h,{force:!0}),e?.stop(),t==="json"){V({success:!0,output:i});return}if(t==="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)}}),ha=new ae("thumbnail").description("Thumbnail operations").addCommand(ga),Ko=new ae("video").description("Video generation commands").addCommand(pa).addCommand(da).addCommand(fa).addCommand(ha).addCommand(Yo);import{Command as ya}from"commander";import ba from"ora";import{writeFile as va}from"fs/promises";var zo=new ya("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(n,t)=>{let e=t.format,o=e==="human"?ba("Scraping URL...").start():null;try{let r=await Jn(n);o?.stop(),r.success||(c(r.error||"Failed to scrape URL"),process.exit(S.GENERAL_ERROR));let i=r.data;if(t.output&&(await va(t.output,i.content,"utf-8"),e==="human"&&b(`Content saved to: ${t.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&&I(i.warning),t.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 pn}from"commander";import mn from"ora";import{writeFile as Xo,mkdir as xa}from"fs/promises";import{dirname as wa}from"path";function un(n,t){if(t==="json"){V(n);return}if(t==="quiet"){n.audioUrl?console.log(n.audioUrl):n.requestId?console.log(n.requestId):console.log(n.id);return}d(`Request ID: ${n.requestId||n.id}`),d(`Status: ${n.status}`),n.duration&&d(`Duration: ${n.duration}s`),n.audioUrl&&b(`Audio URL: ${n.audioUrl}`),n.cost!==void 0&&d(`Cost: $${n.cost.toFixed(4)}`),n.error&&c(`Error: ${n.error}`)}async function Sa(n,t){if(await xa(wa(t),{recursive:!0}),n.startsWith("data:")){let r=n.match(/^data:[^;]+;base64,(.+)$/);if(!r)throw new Error("Invalid data URL format");let i=Buffer.from(r[1],"base64");await Xo(t,i);return}let e=await fetch(n);if(!e.ok)throw new Error(`Failed to download: ${e.status}`);let o=await e.arrayBuffer();await Xo(t,Buffer.from(o))}var Ta=new pn("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 n=>{let t;n.duration&&(t=parseFloat(n.duration),(isNaN(t)||t<.5||t>30)&&(c("Duration must be between 0.5 and 30 seconds"),process.exit(S.INVALID_INPUT)));let e;n.promptInfluence&&(e=parseFloat(n.promptInfluence),(isNaN(e)||e<0||e>1)&&(c("Prompt influence must be between 0 and 1"),process.exit(S.INVALID_INPUT)));let o=n.outputFormat,r=o==="human"?mn("Generating sound effects...").start():null;try{let i=await Hn({text:n.text,duration:t,options:{provider:n.provider,promptInfluence:e,loop:n.loop,format:n.format}});if(!n.wait){r?.stop(),un(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 le(()=>_t(s),60,2e3)}if(r?.stop(),a.status==="failed"&&(c(a.error||"Sound effects generation failed"),process.exit(S.GENERAL_ERROR)),un(a,o),n.output&&a.audioUrl){let s=o==="human"?mn("Downloading...").start():null;try{await Sa(a.audioUrl,n.output),s?.stop(),o==="human"&&b(`Saved to: ${n.output}`)}catch(l){s?.stop(),I(`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)}}),ka=new pn("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(n,t)=>{let e=t.format==="human"?mn("Checking status...").start():null;try{let o=await _t(n);e?.stop(),un(o,t.format)}catch(o){e?.stop(),c(o instanceof Error?o.message:"Unknown error"),process.exit(S.GENERAL_ERROR)}}),Qo=new pn("sfx").description("Sound effects generation commands").addCommand(Ta).addCommand(ka);var $a="0.1.31",D=new Ca,se=u.commands[0];D.name(se).description(u.description).version($a,"-v, --version","Show version number").option("--debug","Enable debug logging").option("--no-color","Disable colored output").configureOutput({outputError:(n,t)=>{t(Q.red(n))}});D.addCommand(oo);D.addCommand(ro);D.addCommand(ao);D.addCommand(mo);D.addCommand(uo);D.addCommand(po);D.addCommand(fo);D.addCommand(ho);D.addCommand(bo);D.addCommand(xo);D.addCommand(To);D.addCommand(ko);D.addCommand(Le);D.addCommand(No);D.addCommand(Lo);D.addCommand(Mo);D.addCommand(Vo);D.addCommand(Ko);D.addCommand(zo);D.addCommand(Qo);var Zo=So();Zo.commands.length>0&&D.addCommand(Zo);D.on("command:*",n=>{console.error(Q.red(`Error: Unknown command '${n[0]}'`)),console.error(),console.error(`Run '${se} --help' to see available commands.`),process.exit(1)});D.addHelpText("after",`
|
|
3416
|
+
${Q.bold("Examples:")}
|
|
3417
|
+
${Q.gray("# Log in (opens browser)")}
|
|
3418
|
+
$ ${se} login
|
|
3419
|
+
|
|
3420
|
+
${Q.gray("# Create a presentation (recommended pattern)")}
|
|
3421
|
+
$ ${se} create "Q4 Business Review" \\
|
|
3316
3422
|
-n 12 -m best \\
|
|
3317
3423
|
--audience "Executive leadership team" \\
|
|
3318
3424
|
--context "Revenue: $50M (+25% YoY), New customers: 150"
|
|
3319
3425
|
|
|
3320
|
-
${
|
|
3321
|
-
$ ${
|
|
3426
|
+
${Q.gray("# Create from a file")}
|
|
3427
|
+
$ ${se} create "Product Launch" \\
|
|
3322
3428
|
-n 10 -m balanced \\
|
|
3323
3429
|
--audience "Sales team and partners" \\
|
|
3324
3430
|
--context-file ./launch-brief.md
|
|
3325
3431
|
|
|
3326
|
-
${
|
|
3327
|
-
$ ${
|
|
3432
|
+
${Q.gray("# Create from URLs")}
|
|
3433
|
+
$ ${se} create "Market Analysis" \\
|
|
3328
3434
|
-n 15 -m best \\
|
|
3329
3435
|
--audience "Strategy team" \\
|
|
3330
3436
|
--sources https://example.com/report.pdf
|
|
3331
3437
|
|
|
3332
|
-
${
|
|
3333
|
-
$ ${
|
|
3334
|
-
$ ${
|
|
3438
|
+
${Q.gray("# List and export")}
|
|
3439
|
+
$ ${se} list --format table
|
|
3440
|
+
$ ${se} export <slug> -o backup.zip
|
|
3335
3441
|
|
|
3336
|
-
${
|
|
3337
|
-
Run '${
|
|
3442
|
+
${Q.bold("Authentication:")}
|
|
3443
|
+
Run '${se} login' to authenticate (recommended).
|
|
3338
3444
|
Or set CC_MINDFRAMES_API_KEY environment variable for API key auth.
|
|
3339
3445
|
|
|
3340
|
-
${
|
|
3341
|
-
${
|
|
3446
|
+
${Q.bold("More Info:")}
|
|
3447
|
+
${Q.blue(u.docsUrl)}
|
|
3342
3448
|
`);D.parse();
|