@barbapapazes/content-creation 0.19.0 → 0.20.0
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/README.md +6 -0
- package/dist/cli.mjs +1 -8
- package/dist/index.d.mts +190 -2
- package/dist/index.mjs +1 -1
- package/dist/x-dKYi_aTZ.mjs +9 -0
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -23,6 +23,7 @@ content-creation <command> --help
|
|
|
23
23
|
- `content upcoming` — list upcoming publications
|
|
24
24
|
- `content rebuild-indexes` — regenerate content indexes
|
|
25
25
|
- `series new` — create a new series directory
|
|
26
|
+
- `series video process` — process a series video into subtitles and optional thumbnails
|
|
26
27
|
- `series article new` — insert a new article in a series
|
|
27
28
|
- `series article ready` — mark a series article as ready and refresh reading time
|
|
28
29
|
- `series article estimate-time` — compute reading time for a series article
|
|
@@ -46,6 +47,11 @@ Environment variables used by advanced workflows:
|
|
|
46
47
|
- `AUTOMATION_ENDPOINT`
|
|
47
48
|
- `CF_ACCESS_CLIENT_ID`
|
|
48
49
|
- `CF_ACCESS_CLIENT_SECRET`
|
|
50
|
+
- `OPENAI_API_KEY`
|
|
51
|
+
- `OPENAI_TRANSCRIPTION_LANGUAGE`
|
|
52
|
+
- `OPENAI_TRANSCRIPTION_MODEL`
|
|
53
|
+
|
|
54
|
+
For series video processing, the command scans the selected series directory recursively for `.mp4` files, generates an `.srt` transcription next to the chosen video, and can optionally render thumbnails into a `thumbnails/` subdirectory.
|
|
49
55
|
|
|
50
56
|
## License
|
|
51
57
|
|
package/dist/cli.mjs
CHANGED
|
@@ -1,9 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import e
|
|
3
|
-
`);for(let t of i){let n=v(t.date,`EEEE, MMMM d, yyyy`),r=v(t.date,`yyyy-MM-dd`);e.stdout.write(`${n} (${r}) · ${x[t.contentType]} · ${t.title}\n`),e.stdout.write(`${t.path}\n\n`)}}function ke(e,t){let n=O(e,{contentTypes:[t],sort:`date-desc`});if(n.length===0){r.info(`No ${t} posts found`);return}let{fileName:i,heading:a}=he[t],o=[`# ${a}`,``,`_Generated on ${new Date().toLocaleDateString()}_`,``,`Total posts: ${n.length}`,``];for(let e of n){let t=e.date.toLocaleDateString(`en-US`,{year:`numeric`,month:`long`,day:`numeric`});o.push(`- [${e.title}](${e.relativePath}) - _${t}_`)}m(h(e,i),`${o.join(`
|
|
4
|
-
`)}\n`,`utf-8`),r.success(`Created ${i} with ${n.length} posts`)}function Ae(e){for(let t of b)ke(e,t)}function je(t=`Operation cancelled.`){r.error(t),e.exit(0)}function k(t){r.error(t),e.exit(1)}function A(e,t){return n(e)&&je(t),e}function j(e){let{content:t,data:n}=y(d(e,`utf-8`));return{content:t,frontmatter:n}}function M(e,t){m(e,y.stringify(t.content,t.frontmatter),`utf-8`)}async function N(e){return A(await s({message:e.message,placeholder:e.placeholder,validate:t=>{if(!t||t.trim().length===0)return e.requiredMessage||`Value is required`}})).trim()}const Me=/<!--([\s\S]*?)-->/g;function Ne(e,t){let n=e.replace(Me,``).replace(/\s+/g,``);if(n.length===0)return 0;let r=n.length/5;return Math.ceil(r/t)}function Pe(e){return e.charAt(0).toUpperCase()+e.slice(1)}function P(e){return e.split(`-`).filter(Boolean).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(` `)}function F(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function I(e,t){try{let{content:n,frontmatter:i}=j(e.path);if(i.ready===!0){r.warn(`This ${t} is already marked as ready.`);return}let a={...i,ready:!0};M(e.path,{content:n,frontmatter:a}),r.success(`${Pe(t)} marked as ready: ${e.relativePath}`)}catch(e){k(`Failed to mark ${t} as ready: ${e instanceof Error?e.message:String(e)}`)}}async function Fe(){let e=new Date,t=7;for(;;){let n=A(await o({message:`When do you want to create content?`,options:Ie(e,t)}));if(n===C){t+=7;continue}return typeof n!=`string`&&k(`Unable to resolve selected date: ${String(n)}`),ie(n)}}function Ie(e,t){let n=[];for(let r=0;r<t;r++){let t=_(e,r),i=v(t,`yyyy-MM-dd`),a=v(t,`EEEE`),o=i,s=`Create content for ${a}, ${i}`;r===0?(o=`Today - ${a} (${i})`,s=`Create content for today (${a})`):r===1?(o=`Tomorrow - ${a} (${i})`,s=`Create content for tomorrow (${a})`):(o=`In ${r} days - ${a} (${i})`,s=`Create content for ${a}, ${i}`),n.push({label:o,value:i,hint:s})}return n.push({label:`Show 7 more dates (${t+1}-${t+7} days ahead)`,value:C,hint:`Extend the list up to ${v(_(e,t+7-1),`yyyy-MM-dd`)}`}),n}async function Le(){return N({message:`What is the title of your content?`,placeholder:`Enter content title`,requiredMessage:`Title is required`})}async function Re(){let e=A(await o({message:`Is a LinkedIn video planned?`,options:[{label:`Yes`,value:!0,hint:`Add video: true to frontmatter`},{label:`No`,value:!1,hint:`No video metadata`}]}));return typeof e!=`boolean`&&k(`Unable to resolve LinkedIn video choice: ${String(e)}`),e}async function ze(e){if(e.length===0)return;let t=A(await o({message:`What is the content theme? (optional)`,options:[{label:`No theme`,value:``,hint:`Leave theme empty`},...e.map(e=>({label:e,value:e,hint:`Use theme: ${e}`}))]}));typeof t!=`string`&&k(`Unable to resolve selected theme: ${String(t)}`);let n=t;return n.length>0?n:void 0}async function Be(){let e=A(await o({message:`What type of resource is this?`,options:[{label:`Article`,value:`article`,hint:`Create article.md`},{label:`Video`,value:`video`,hint:`Create video.md`},{label:`Audio`,value:`audio`,hint:`Create audio.md`},{label:`Tweet`,value:`tweet`,hint:`Create tweet.md`}]}));return[`article`,`video`,`audio`,`tweet`].includes(String(e))||k(`Unable to resolve resource type: ${String(e)}`),e}async function Ve(){let e=A(await i({message:`Which content types do you want to create?`,options:[{label:`LinkedIn`,value:`linkedin`,hint:`Create LinkedIn post and optional script`},{label:`X (Twitter)`,value:`x`,hint:`Create X post`},{label:`YouTube`,value:`youtube`,hint:`Create YouTube script and description`},{label:`Instagram`,value:`instagram`,hint:`Create Instagram script and description`}],required:!0}));return(!Array.isArray(e)||e.some(e=>!b.includes(e)))&&k(`Unable to resolve selected content types.`),e}async function He(e){return L({basePath:e,contentTypes:[`x`],message:`Which X publication do you want to schedule a reminder for?`,emptyMessage:`No X publications found in dated folders`,sort:`date-desc`})}async function Ue(e){return L({basePath:e,contentTypes:[`linkedin`],message:`Which LinkedIn publication do you want to mark as ready?`,emptyMessage:`No LinkedIn publications found in dated folders`,sort:`date-desc`})}async function L(e){let t=O(e.basePath,{contentTypes:e.contentTypes,sort:e.sort});t.length===0&&k(e.emptyMessage);let n=20;for(;;){let r=A(await o({message:e.message,options:We(t,n)}));if(r===C){n+=20;continue}typeof r!=`string`&&k(`Unable to resolve selected publication: ${String(r)}`);let i=t.find(e=>e.path===r);return i||k(`Unable to resolve selected publication: ${r}`),i}}function We(e,t){let n=e.slice(0,t).map(e=>Ge(e));if(t>=e.length)return n;let r=Math.min(t+20,e.length);return[...n,{label:`Show ${r-t} more publications (${t+1}-${r} of ${e.length})`,value:C,hint:`Extend the list to ${e[r-1]?.relativePath}`}]}function Ge(e){return{label:`${v(e.date,`yyyy-MM-dd`)} · ${x[e.contentType]} · ${e.title}`,value:e.path,hint:e.relativePath}}async function Ke(e){t(`Content Creation - Create dated content directory`);let n=await E(),r=await Le(),i=await Ve(),o={title:r};i.includes(`linkedin`)&&(o.hasVideo=await Re(),o.hasImages=!o.hasVideo,n.thematic.length>0&&(o.theme=await ze(n.thematic)));let s=await Fe();xe(s,i,o,n,e);let c=i.join(`, `);a(`✓ Content directory created: ${v(s,`yyyy/MM/dd`)} (${c})`)}function qe(e){t(`Content Creation - List Upcoming Publications`),Oe(e),a(`✓ Upcoming publications listed`)}function Je(e){t(`Content Creation - Rebuild Content Indexes`),Ae(e),a(`✓ Content indexes rebuilt successfully`)}const R=de(new URL(`../../../../`,import.meta.url)),z=`content-creation.ics`;function B(e){return e.replace(/\\/g,`\\\\`).replace(/\r\n|\r|\n/g,`\\n`).replace(/;/g,`\\;`).replace(/,/g,`\\,`)}function Ye(e){if(e.length<=75)return e;let t=[],n=e;for(;n.length>75;)t.push(n.slice(0,75)),n=` ${n.slice(75)}`;return t.push(n),t.join(`\r
|
|
5
|
-
`)}function Xe(e){return e.toISOString().replace(/[-:]/g,``).replace(/\.\d{3}Z$/,`Z`)}function Ze(e){return v(e,`yyyyMMdd`)}function Qe(e){return e.split(`/`).map(e=>encodeURIComponent(e)).join(`/`)}function $e(e){return`https://github.com/barbapapazes/content-creation/blob/main/${Qe(e)}`}function et(e){let t=Qe(e);return`vscode://file${t.startsWith(`/`)?``:`/`}${t}`}function tt(e){let t=[`GitHub: ${$e(e.relativePath)}`,`VS Code: ${et(e.path)}`,`Status: ${e.ready?`ready`:`not ready`}`];return e.theme&&t.push(`Theme: ${e.theme}`),typeof e.video==`boolean`&&t.push(`Video planned: ${e.video?`yes`:`no`}`),e.imageCount>0&&t.push(`Images planned: ${e.imageCount}`),t.join(`
|
|
6
|
-
`)}function nt(e){return`${e.ready?`Ready`:`Not ready`} · ${e.title} · ${x.linkedin}`}function rt(e){return`linkedin-${ue(`sha1`).update(e).digest(`hex`)}@barbapapazes`}function it(e){let t=[`BEGIN:VCALENDAR`,`VERSION:2.0`,`PRODID:-//Barbapapazes//Content Creation LinkedIn Calendar//EN`,`CALSCALE:GREGORIAN`,`METHOD:PUBLISH`,`X-WR-CALNAME:${B(`content-creation.ics`)}`];for(let n of e){let e=Xe(new Date(Date.UTC(n.date.getFullYear(),n.date.getMonth(),n.date.getDate()))),r=_(n.date,1);t.push(`BEGIN:VEVENT`,`UID:${rt(n.relativePath)}`,`DTSTAMP:${e}`,`SUMMARY:${B(nt(n))}`,`DTSTART;VALUE=DATE:${Ze(n.date)}`,`DTEND;VALUE=DATE:${Ze(r)}`,`TRANSP:TRANSPARENT`,`DESCRIPTION:${B(tt(n))}`,`END:VEVENT`)}return t.push(`END:VCALENDAR`),`${t.map(Ye).join(`\r
|
|
7
|
-
`)}\r\n`}function at(){let t=h(R,`node_modules`,`.bin`,e.platform===`win32`?`wrangler.cmd`:`wrangler`);return l(t)?t:`wrangler`}function ot(t){(!t.calendar.publicUrl||!t.calendar.token)&&(r.error(`Calendar publishing configuration is missing. Please set CALENDAR_PUBLIC_URL and CALENDAR_TOKEN in your .env file or config.`),e.exit(1));let n=t.calendar.publicUrl.endsWith(`/`)?t.calendar.publicUrl:`${t.calendar.publicUrl}/`,i=new URL(z,n);return i.searchParams.set(`token`,t.calendar.token),i.toString()}function st(e){return O(e,{contentTypes:[`linkedin`],sort:`date-asc`}).map(e=>{let t=e.frontmatter;return{...e,title:t.title||e.title,ready:t.ready===!0,theme:t.theme,video:t.video,imageCount:t.images?.filter(Boolean).length??0}})}function ct(t){let n=at();try{le(n,[`r2`,`object`,`put`,`content-creation/${z}`,`--pipe`,`--content-type`,`text/calendar; charset=utf-8`,`--remote`],{cwd:R,stdio:[`pipe`,`inherit`,`inherit`],env:e.env,input:t})}catch(t){r.error(`Failed to upload LinkedIn calendar: ${t instanceof Error?t.message:String(t)}`),e.exit(1)}}async function lt(e,t){let n=st(e);if(n.length===0)return r.info(`No LinkedIn publications found. Skipping calendar upload.`),null;let i=ot(t);return ct(it(n)),r.success(`✓ LinkedIn calendar uploaded to R2 as ${z}`),r.info(`Subscription URL: ${i}`),i}function ut(e){let t=[];try{let n=f(e);for(let r of n){if(!p(h(e,r)).isFile())continue;let n=r.toLowerCase().substring(r.lastIndexOf(`.`));ge.includes(n)&&t.push(r)}}catch(e){k(`Error reading directory: ${e instanceof Error?e.message:String(e)}`)}return t.sort()}function dt(e,t){try{let{content:n,frontmatter:i}=j(e.path);M(e.path,{content:n,frontmatter:{...i,images:t.length>0?t:[null]}}),r.success(`LinkedIn publication updated: ${e.relativePath} (${t.length} image(s))`)}catch(e){k(`Failed to update LinkedIn publication: ${e instanceof Error?e.message:String(e)}`)}}function ft(e){let t=ut(e.folderPath);t.length===0?r.info(`No images found for LinkedIn publication: ${e.relativePath}`):r.info(`Found ${t.length} image(s) for ${e.relativePath}: ${t.join(`, `)}`),dt(e,t),I({path:e.path,relativePath:e.relativePath},`linkedin publication`);let n=h(e.folderPath,`linkedin-script.md`),i=e.relativePath.slice(0,e.relativePath.lastIndexOf(`/`)),a=l(n);return a&&I({path:n,relativePath:`${i}/linkedin-script.md`},`linkedin script`),{imageCount:t.length,scriptMarked:a}}async function pt(e){if(t(`Content Creation - Publish LinkedIn Calendar`),await lt(e,await E())){a(`✓ LinkedIn calendar published successfully`);return}a(`✓ No LinkedIn publications found, calendar was not updated`)}async function mt(e){t(`Content Creation - Mark LinkedIn Publication Ready`);let n=await Ue(e),r=ft(n),i=r.scriptMarked?`script marked ready`:`no script found`;a(`✓ LinkedIn publication ready: ${n.relativePath} (${r.imageCount} image(s), ${i})`)}function V(e){return e.toLowerCase().trim().normalize(`NFD`).replace(/[\u0300-\u036F]/g,``).replace(/[^a-z0-9\s-]/g,``).replace(/\s+/g,`-`).replace(/-+/g,`-`).replace(/^-+|-+$/g,``)||`untitled`}function ht(e,t,n){let i=V(e),a=h(g(h(n,`resources`)),i),o=`${t}.md`,s=h(a,o);if(l(s))return r.info(`${o} already exists at: ${s}`),a;u(a,{recursive:!0}),r.success(`Created directory: ${a}`);let c={title:e,url:``,date:``};return m(s,y.stringify(``,c),`utf-8`),r.success(`Created ${o} at: ${s}`),a}async function gt(e){t(`Content Creation - Create Resource`);let n=await Le(),r=await Be();ht(n,r,e),a(`✓ Resource created: resources/${V(n)}/${r}.md`)}const H=`## Index`,U=`<!-- content-creation:series-index:start -->`,W=`<!-- content-creation:series-index:end -->`,_t=/^(\d+)\.([^.]+(?:\.[^.]+)*)\.md$/;function G(e){return g(h(e,`series`))}function vt(e){let t=G(e);return l(t)?f(t).filter(e=>St(h(t,e))).map(e=>Ct(t,e)).sort((e,t)=>e.title.localeCompare(t.title,`fr`)):[]}function K(e){return f(e).map(Dt).filter(e=>e!==null).map(t=>wt(e,t)).sort((e,t)=>e.index-t.index)}function q(e){let t=K(e),n=h(e,`README.md`);m(n,yt(l(n)?d(n,`utf-8`):``,t,e),`utf-8`),r.success(`Generated series index: ${n}`)}function yt(e,t,n){let r=bt(t);if(e.includes(U)&&e.includes(W))return e.replace(xt(),r);let i=e.trimEnd();return i.length>0?`${i}\n\n${r}\n`:`# ${P(n.split(`/`).pop()||`series`)}\n\n${r}\n`}function bt(e){let t=[H,``,U];if(e.length===0)t.push(``,`_No articles yet._`);else{t.push(``);for(let n of e)t.push(`${n.index}. [${n.title}](./${n.fileName})`)}return t.push(``,W),t.join(`
|
|
8
|
-
`)}function xt(){return RegExp(`${F(H)}\n\n${F(U)}[\\s\\S]*?${F(W)}`)}function St(e){return p(e).isDirectory()}function Ct(e,t){let n=h(e,t);return{name:t,path:n,title:Tt(n,t)}}function wt(e,t){let n=h(e,t.fileName);return{...t,path:n,title:Et(n,t.slug)}}function Tt(e,t){let n=h(e,`README.md`);if(l(n)){let e=d(n,`utf-8`).split(`
|
|
9
|
-
`).map(e=>e.trim()).find(e=>e.startsWith(`# `));if(e)return e.slice(2).trim()}return P(t)}function Et(e,t){try{let{data:t}=y(d(e,`utf-8`)),n=typeof t.title==`string`?t.title.trim():``;if(n.length>0)return n}catch(t){r.warn(`Unable to read article title from ${e}: ${t}`)}return P(t)}function Dt(e){let t=e.match(_t);return t?{index:Number(t[1]),slug:t[2],fileName:e}:null}function Ot(e,t,n){let i=K(e),a=At(n,i.length),o=h(e,`${a}.${V(t)}.md`);if(l(o))throw Error(`Article already exists at ${o}`);return kt(e,i,a),m(o,y.stringify(``,{title:t}),`utf-8`),r.success(`Created article: ${o}`),q(e),o}function kt(e,t,n){let i=t.filter(e=>e.index>=n).sort((e,t)=>t.index-e.index);for(let t of i){let n=`${t.index+1}.${t.slug}.md`,i=h(e,n);ee(t.path,i),r.info(`Renamed ${t.fileName} → ${n}`)}}function At(e,t){if(!Number.isInteger(e))throw TypeError(`Article index must be an integer`);if(e<1||e>t+1)throw RangeError(`Article index must be between 1 and ${t+1}`);return e}function jt(e,t){let n=t.trim();if(n.length===0)throw Error(`Series title is required`);let i=V(n),a=G(e),o=h(a,i);if(u(a,{recursive:!0}),l(o))throw Error(`Series already exists: series/${i}`);return u(o),m(h(o,`README.md`),`# ${n}\n`,`utf-8`),q(o),r.success(`Created series: ${o}`),{name:i,path:o,title:n}}function Mt(e,t){let{content:n,frontmatter:i}=j(e),a=Ne(n,t);return M(e,{content:n,frontmatter:{...i,time:a}}),r.success(`Estimated time set to ${a} min: ${e}`),a}function Nt(e){I({path:e.path,relativePath:e.fileName},`series article`)}async function Pt(){return N({message:`What is the title of your series?`,placeholder:`Enter series title`,requiredMessage:`Title is required`})}async function Ft(){return N({message:`What is the title of your article?`,placeholder:`Enter article title`,requiredMessage:`Title is required`})}async function J(e){return Rt({entries:vt(e),message:`Which series do you want to work on?`,emptyMessage:`No series directories found`,resolveMessage:`Unable to resolve selected series`,buildOption:e=>({label:e.title,value:e.path,hint:`series/${e.name}`})})}async function It(e){let t=K(e),n=t.map(e=>({label:`Insert at #${e.index} · before ${e.title}`,value:String(e.index),hint:e.fileName}));n.push({label:`Append as #${t.length+1}`,value:String(t.length+1),hint:`Add the new article at the end of the series`});let r=A(await o({message:`Where should the new article be inserted?`,options:n}));return typeof r!=`string`&&k(`Unable to resolve selected insert position: ${String(r)}`),Number(r)}async function Lt(e){return Rt({entries:K(e),message:`Which series article do you want to update?`,emptyMessage:`No series articles found`,resolveMessage:`Unable to resolve selected series article`,buildOption:e=>({label:`#${e.index} · ${e.title}`,value:e.path,hint:e.fileName})})}async function Rt({entries:e,message:t,emptyMessage:n,resolveMessage:r,buildOption:i}){if(e.length===0&&k(n),e.length===1)return e[0];let a=A(await o({message:t,options:e.map(i)}));typeof a!=`string`&&k(`${r}: ${String(a)}`);let s=e.find(e=>e.path===a);return s||k(`${r}: ${a}`),s}async function zt(e){t(`Content Creation - Estimate Series Article Time`);let n=await E(),r=await J(e),i=await Lt(r.path),o=Mt(i.path,n.reading.wordsPerMinute);a(`✓ Estimated time updated for ${r.name}/${i.fileName}: ${o} min`)}async function Bt(e){t(`Content Creation - Create Series`),a(`✓ Series created: series/${jt(e,await Pt()).name}`)}async function Vt(e){t(`Content Creation - Create Series Article`);let n=await J(e),r=await Ft(),i=await It(n.path),o=Ot(n.path,r,i);a(`✓ Series article created: ${n.name}/${te(o)}`)}async function Ht(e){t(`Content Creation - Mark Series Article Ready`);let n=await E(),r=await J(e),i=await Lt(r.path);Nt(i);let o=Mt(i.path,n.reading.wordsPerMinute);a(`✓ Series article updated for ${r.name}/${i.fileName}: ready + ${o} min`)}async function Ut(e,t){let n=e.path,{content:i,frontmatter:a}=j(n);if(a.scheduled===!0)return r.warn(`This X publication is already scheduled. Skipping.`),!1;(!t.scheduling.automationEndpoint||!t.scheduling.cfAccessClientId||!t.scheduling.cfAccessClientSecret)&&k(`Scheduling configuration is missing. Please set AUTOMATION_ENDPOINT, CF_ACCESS_CLIENT_ID, and CF_ACCESS_CLIENT_SECRET in your .env file or config.`);let o=new Date(e.date);o=ae(o,w.hours),o=oe(o,w.minutes),o=se(o,w.seconds);let s={content:i.trim(),scheduleAt:o.getTime()};try{let e=await fetch(t.scheduling.automationEndpoint,{method:`POST`,headers:{"Content-Type":`application/json`,Accept:`application/json`,"CF-Access-Client-Id":t.scheduling.cfAccessClientId,"CF-Access-Client-Secret":t.scheduling.cfAccessClientSecret},body:JSON.stringify(s)});if(!e.ok){let t=await e.text();k(`Failed to schedule reminder: ${e.status} ${e.statusText}\n${t}`)}let o=await e.json();M(n,{content:i,frontmatter:{...a,scheduled:!0}});let c=new Date(o.scheduledAt);return r.success(`✓ X publication reminder scheduled`),r.info(`Reminder scheduled for: ${v(c,`yyyy-MM-dd HH:mm:ss`)} UTC`),!0}catch(e){k(`Failed to schedule X publication reminder: ${e instanceof Error?e.message:String(e)}`)}}async function Wt(e){t(`Content Creation - Schedule X Publication Reminder`);let n=await E();a(await Ut(await He(e),n)?`✓ X publication reminder scheduled`:`✓ X publication was already scheduled`)}const Y=new c;Y.name(fe).description(me).version(pe).showHelpAfterError();const X=Y.command(`content`);X.description(`Manage dated content`),X.command(`new`).description(`Create a dated content directory`).action(()=>Ke(e.cwd())),X.command(`upcoming`).description(`List upcoming publications`).action(()=>qe(e.cwd())),X.command(`rebuild-indexes`).description(`Rebuild all content index files`).action(()=>Je(e.cwd()));const Z=Y.command(`series`);Z.description(`Manage article series`),Z.command(`new`).description(`Create a new series`).action(()=>Bt(e.cwd()));const Q=Z.command(`article`).description(`Manage series articles`);Q.command(`new`).description(`Create a new article in a series`).action(()=>Vt(e.cwd())),Q.command(`ready`).description(`Mark a series article as ready`).action(()=>Ht(e.cwd())),Q.command(`estimate-time`).description(`Estimate the reading time of a series article`).action(()=>zt(e.cwd()));const $=Y.command(`linkedin`);$.description(`Manage LinkedIn publications`),$.command(`ready`).description(`Prepare a LinkedIn publication and mark it as ready`).action(()=>mt(e.cwd())),$.command(`publish-calendar`).description(`Publish the LinkedIn calendar`).action(()=>pt(e.cwd()));const Gt=Y.command(`x`);Gt.description(`Manage X publications`),Gt.command(`schedule-reminder`).description(`Schedule an X publication reminder`).action(()=>Wt(e.cwd()));const Kt=Y.command(`resource`);Kt.description(`Manage resources`),Kt.command(`new`).description(`Create a new resource`).action(()=>gt(e.cwd())),Y.parseAsync().catch(t=>{r.error(t instanceof Error?t.message:String(t)),e.exit(1)});export{};
|
|
2
|
+
import{C as e,S as t,a as n,g as r,i,o as a,r as o,s,t as c,v as l,w as u,y as d}from"./x-dKYi_aTZ.mjs";import f from"node:process";import{log as p}from"@clack/prompts";import{Command as m}from"commander";var h=`@barbapapazes/content-creation`,g=`0.20.0`,_=`CLI tool for content creation and management`;const v=new m;v.name(h).description(_).version(g).showHelpAfterError();const y=v.command(`content`);y.description(`Manage dated content`),y.command(`new`).description(`Create a dated content directory`).action(()=>u(f.cwd())),y.command(`upcoming`).description(`List upcoming publications`).action(()=>e(f.cwd())),y.command(`rebuild-indexes`).description(`Rebuild all content index files`).action(()=>t(f.cwd()));const b=v.command(`series`);b.description(`Manage article series`),b.command(`new`).description(`Create a new series`).action(()=>a(f.cwd())),b.command(`video`).description(`Manage series videos`).command(`process`).description(`Process a video from a series directory`).action(()=>i(f.cwd()));const x=b.command(`article`).description(`Manage series articles`);x.command(`new`).description(`Create a new article in a series`).action(()=>n(f.cwd())),x.command(`ready`).description(`Mark a series article as ready`).action(()=>o(f.cwd())),x.command(`estimate-time`).description(`Estimate the reading time of a series article`).action(()=>s(f.cwd()));const S=v.command(`linkedin`);S.description(`Manage LinkedIn publications`),S.command(`ready`).description(`Prepare a LinkedIn publication and mark it as ready`).action(()=>l(f.cwd())),S.command(`publish-calendar`).description(`Publish the LinkedIn calendar`).action(()=>d(f.cwd()));const C=v.command(`x`);C.description(`Manage X publications`),C.command(`schedule-reminder`).description(`Schedule an X publication reminder`).action(()=>c(f.cwd()));const w=v.command(`resource`);w.description(`Manage resources`),w.command(`new`).description(`Create a new resource`).action(()=>r(f.cwd())),v.parseAsync().catch(e=>{p.error(e instanceof Error?e.message:String(e)),f.exit(1)});export{};
|
package/dist/index.d.mts
CHANGED
|
@@ -1,11 +1,24 @@
|
|
|
1
|
+
//#region src/constants.d.ts
|
|
2
|
+
declare const CONTENT_TYPES: readonly ["linkedin", "x", "youtube", "instagram"];
|
|
3
|
+
//#endregion
|
|
1
4
|
//#region src/types.d.ts
|
|
2
|
-
|
|
5
|
+
type ContentType = (typeof CONTENT_TYPES)[number];
|
|
3
6
|
interface LinkedInTemplateConfig {
|
|
4
7
|
footerPath?: string;
|
|
5
8
|
}
|
|
6
9
|
interface VideoTemplateConfig {
|
|
7
10
|
templatePath?: string;
|
|
8
11
|
}
|
|
12
|
+
interface VideoProcessingConfig {
|
|
13
|
+
openaiApiKey?: string;
|
|
14
|
+
language?: string;
|
|
15
|
+
model?: string;
|
|
16
|
+
}
|
|
17
|
+
interface ResolvedVideoProcessingConfig {
|
|
18
|
+
openaiApiKey: string;
|
|
19
|
+
language: string;
|
|
20
|
+
model: string;
|
|
21
|
+
}
|
|
9
22
|
interface SchedulingConfig {
|
|
10
23
|
automationEndpoint?: string;
|
|
11
24
|
cfAccessClientId?: string;
|
|
@@ -26,12 +39,187 @@ interface Config {
|
|
|
26
39
|
youtube?: VideoTemplateConfig;
|
|
27
40
|
instagram?: VideoTemplateConfig;
|
|
28
41
|
};
|
|
42
|
+
videoProcessing?: VideoProcessingConfig;
|
|
29
43
|
scheduling?: SchedulingConfig;
|
|
30
44
|
calendar?: CalendarPublishingConfig;
|
|
31
45
|
reading?: ReadingConfig;
|
|
32
46
|
}
|
|
47
|
+
interface ResolvedConfig extends Omit<Required<Config>, 'reading' | 'videoProcessing'> {
|
|
48
|
+
reading: {
|
|
49
|
+
wordsPerMinute: number;
|
|
50
|
+
};
|
|
51
|
+
videoProcessing: ResolvedVideoProcessingConfig;
|
|
52
|
+
}
|
|
53
|
+
interface ContentFrontmatter {
|
|
54
|
+
title: string;
|
|
55
|
+
theme?: string;
|
|
56
|
+
video?: boolean;
|
|
57
|
+
images?: (string | null)[];
|
|
58
|
+
scheduled?: boolean;
|
|
59
|
+
ready?: boolean;
|
|
60
|
+
}
|
|
61
|
+
interface ContentEntry {
|
|
62
|
+
contentType: ContentType;
|
|
63
|
+
path: string;
|
|
64
|
+
folderPath: string;
|
|
65
|
+
date: Date;
|
|
66
|
+
title: string;
|
|
67
|
+
relativePath: string;
|
|
68
|
+
frontmatter: ContentFrontmatter;
|
|
69
|
+
}
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region src/common/frontmatter.d.ts
|
|
72
|
+
interface MarkdownDocument<TFrontmatter extends object = Record<string, unknown>> {
|
|
73
|
+
content: string;
|
|
74
|
+
frontmatter: TFrontmatter;
|
|
75
|
+
}
|
|
76
|
+
declare function readMarkdownDocument<TFrontmatter extends object = Record<string, unknown>>(filePath: string): MarkdownDocument<TFrontmatter>;
|
|
77
|
+
declare function writeMarkdownDocument<TFrontmatter extends object>(filePath: string, document: MarkdownDocument<TFrontmatter>): void;
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region src/common/prompts.d.ts
|
|
80
|
+
interface PromptForRequiredTextOptions {
|
|
81
|
+
message: string;
|
|
82
|
+
placeholder: string;
|
|
83
|
+
requiredMessage?: string;
|
|
84
|
+
}
|
|
85
|
+
declare function promptForRequiredText(options: PromptForRequiredTextOptions): Promise<string>;
|
|
86
|
+
//#endregion
|
|
87
|
+
//#region src/common/reading.d.ts
|
|
88
|
+
declare function estimateReadingMinutesFromMarkdown(markdownContent: string, wordsPerMinute: number): number;
|
|
89
|
+
//#endregion
|
|
90
|
+
//#region src/common/ready.d.ts
|
|
91
|
+
interface ReadyEntry {
|
|
92
|
+
path: string;
|
|
93
|
+
relativePath: string;
|
|
94
|
+
}
|
|
95
|
+
declare function markEntryAsReady(entry: ReadyEntry, entryLabel: string): void;
|
|
96
|
+
//#endregion
|
|
97
|
+
//#region src/common/slugify.d.ts
|
|
98
|
+
declare function slugify(title: string): string;
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region src/common/strings.d.ts
|
|
101
|
+
declare function capitalize(value: string): string;
|
|
102
|
+
declare function humanizeSlug(value: string): string;
|
|
103
|
+
declare function escapeRegExp(value: string): string;
|
|
104
|
+
//#endregion
|
|
105
|
+
//#region ../shared/src/cli.d.ts
|
|
106
|
+
interface BasePathOption {
|
|
107
|
+
path?: string;
|
|
108
|
+
}
|
|
109
|
+
declare function exitAsCancelled(message?: string): never;
|
|
110
|
+
declare function exitWithError(message: string): never;
|
|
111
|
+
declare function formatErrorMessage(error: unknown): string;
|
|
112
|
+
declare function unwrapPromptValue<T>(value: T, cancellationMessage?: string): Exclude<T, symbol>;
|
|
113
|
+
declare function resolveBasePath(basePath?: string): string;
|
|
114
|
+
//#endregion
|
|
115
|
+
//#region src/config.d.ts
|
|
116
|
+
declare function loadContentCreationConfig(): Promise<ResolvedConfig>;
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/modules/content/steps/create-content-directory.d.ts
|
|
119
|
+
interface ContentCreationOptions {
|
|
120
|
+
title: string;
|
|
121
|
+
hasVideo?: boolean;
|
|
122
|
+
hasImages?: boolean;
|
|
123
|
+
theme?: string;
|
|
124
|
+
}
|
|
125
|
+
declare function createContentDirectory(date: Date, selectedTypes: ContentType[], options: ContentCreationOptions, config: ResolvedConfig, basePath: string): string;
|
|
126
|
+
//#endregion
|
|
127
|
+
//#region src/modules/content/steps/list-upcoming-content.d.ts
|
|
128
|
+
declare function listUpcomingContent(basePath: string): void;
|
|
129
|
+
//#endregion
|
|
130
|
+
//#region src/modules/content/steps/rebuild-content-indexes.d.ts
|
|
131
|
+
declare function rebuildContentIndexes(basePath: string): void;
|
|
132
|
+
//#endregion
|
|
133
|
+
//#region src/modules/content/workflows/content-new.d.ts
|
|
134
|
+
declare function contentNewWorkflow(basePath: string): Promise<void>;
|
|
135
|
+
//#endregion
|
|
136
|
+
//#region src/modules/content/workflows/content-upcoming.d.ts
|
|
137
|
+
declare function contentUpcomingWorkflow(basePath: string): void;
|
|
138
|
+
//#endregion
|
|
139
|
+
//#region src/modules/content/workflows/rebuild-content-indexes.d.ts
|
|
140
|
+
declare function rebuildContentIndexesWorkflow(basePath: string): void;
|
|
141
|
+
//#endregion
|
|
142
|
+
//#region src/modules/linkedin/steps/publish-linkedin-calendar.d.ts
|
|
143
|
+
declare function publishLinkedInCalendar(basePath: string, config: ResolvedConfig): Promise<string | null>;
|
|
144
|
+
//#endregion
|
|
145
|
+
//#region src/modules/linkedin/steps/ready-linkedin-publication.d.ts
|
|
146
|
+
interface LinkedInReadyResult {
|
|
147
|
+
imageCount: number;
|
|
148
|
+
scriptMarked: boolean;
|
|
149
|
+
}
|
|
150
|
+
declare function readyLinkedInPublication(entry: ContentEntry): LinkedInReadyResult;
|
|
151
|
+
//#endregion
|
|
152
|
+
//#region src/modules/linkedin/workflows/linkedin-publish-calendar.d.ts
|
|
153
|
+
declare function linkedinPublishCalendarWorkflow(basePath: string): Promise<void>;
|
|
154
|
+
//#endregion
|
|
155
|
+
//#region src/modules/linkedin/workflows/linkedin-ready.d.ts
|
|
156
|
+
declare function linkedinReadyWorkflow(basePath: string): Promise<void>;
|
|
157
|
+
//#endregion
|
|
158
|
+
//#region src/modules/resource/steps/create-resource-directory.d.ts
|
|
159
|
+
declare function createResourceDirectory(title: string, resourceType: 'article' | 'video' | 'audio' | 'tweet', basePath: string): string;
|
|
160
|
+
//#endregion
|
|
161
|
+
//#region src/modules/resource/workflows/resource-new.d.ts
|
|
162
|
+
declare function resourceNewWorkflow(basePath: string): Promise<void>;
|
|
163
|
+
//#endregion
|
|
164
|
+
//#region src/modules/series/steps/create-series-article.d.ts
|
|
165
|
+
declare function createSeriesArticle(seriesPath: string, title: string, insertAtIndex: number): string;
|
|
166
|
+
//#endregion
|
|
167
|
+
//#region src/modules/series/types.d.ts
|
|
168
|
+
interface SeriesDirectoryEntry {
|
|
169
|
+
name: string;
|
|
170
|
+
path: string;
|
|
171
|
+
title: string;
|
|
172
|
+
}
|
|
173
|
+
interface SeriesArticleEntry {
|
|
174
|
+
index: number;
|
|
175
|
+
slug: string;
|
|
176
|
+
title: string;
|
|
177
|
+
fileName: string;
|
|
178
|
+
path: string;
|
|
179
|
+
}
|
|
180
|
+
interface SeriesVideoEntry {
|
|
181
|
+
name: string;
|
|
182
|
+
path: string;
|
|
183
|
+
relativePath: string;
|
|
184
|
+
}
|
|
185
|
+
//#endregion
|
|
186
|
+
//#region src/modules/series/steps/create-series-directory.d.ts
|
|
187
|
+
declare function createSeriesDirectory(basePath: string, title: string): SeriesDirectoryEntry;
|
|
188
|
+
//#endregion
|
|
189
|
+
//#region src/modules/series/steps/estimate-series-article-time.d.ts
|
|
190
|
+
declare function estimateSeriesArticleTime(filePath: string, wordsPerMinute: number): number;
|
|
191
|
+
//#endregion
|
|
192
|
+
//#region src/modules/series/steps/mark-series-article-ready.d.ts
|
|
193
|
+
declare function markSeriesArticleReady(entry: SeriesArticleEntry): void;
|
|
194
|
+
//#endregion
|
|
195
|
+
//#region src/modules/series/steps/series.d.ts
|
|
196
|
+
declare function getSeriesRootPath(basePath: string): string;
|
|
197
|
+
declare function getSeriesDirectories(basePath: string): SeriesDirectoryEntry[];
|
|
198
|
+
declare function getSeriesArticles(seriesPath: string): SeriesArticleEntry[];
|
|
199
|
+
declare function createSeriesIndex(seriesPath: string): void;
|
|
200
|
+
//#endregion
|
|
201
|
+
//#region src/modules/series/workflows/estimate-series-article-time.d.ts
|
|
202
|
+
declare function seriesArticleEstimateTimeWorkflow(basePath: string): Promise<void>;
|
|
203
|
+
//#endregion
|
|
204
|
+
//#region src/modules/series/workflows/new-series.d.ts
|
|
205
|
+
declare function seriesNewWorkflow(basePath: string): Promise<void>;
|
|
206
|
+
//#endregion
|
|
207
|
+
//#region src/modules/series/workflows/new-series-article.d.ts
|
|
208
|
+
declare function seriesArticleNewWorkflow(basePath: string): Promise<void>;
|
|
209
|
+
//#endregion
|
|
210
|
+
//#region src/modules/series/workflows/process-series-video.d.ts
|
|
211
|
+
declare function seriesVideoProcessWorkflow(basePath: string): Promise<void>;
|
|
212
|
+
//#endregion
|
|
213
|
+
//#region src/modules/series/workflows/ready-series-article.d.ts
|
|
214
|
+
declare function seriesArticleReadyWorkflow(basePath: string): Promise<void>;
|
|
215
|
+
//#endregion
|
|
216
|
+
//#region src/modules/x/steps/schedule-reminder.d.ts
|
|
217
|
+
declare function scheduleReminder(entry: ContentEntry, config: ResolvedConfig): Promise<boolean>;
|
|
218
|
+
//#endregion
|
|
219
|
+
//#region src/modules/x/workflows/x-schedule-reminder.d.ts
|
|
220
|
+
declare function xScheduleReminderWorkflow(basePath: string): Promise<void>;
|
|
33
221
|
//#endregion
|
|
34
222
|
//#region src/index.d.ts
|
|
35
223
|
declare function defineConfig(config: Config): Config;
|
|
36
224
|
//#endregion
|
|
37
|
-
export { defineConfig };
|
|
225
|
+
export { BasePathOption, type Config, ContentCreationOptions, LinkedInReadyResult, MarkdownDocument, type ResolvedConfig, SeriesArticleEntry, SeriesDirectoryEntry, SeriesVideoEntry, capitalize, contentNewWorkflow, contentUpcomingWorkflow, createContentDirectory, createResourceDirectory, createSeriesArticle, createSeriesDirectory, createSeriesIndex, defineConfig, escapeRegExp, estimateReadingMinutesFromMarkdown, estimateSeriesArticleTime, exitAsCancelled, exitWithError, formatErrorMessage, getSeriesArticles, getSeriesDirectories, getSeriesRootPath, humanizeSlug, linkedinPublishCalendarWorkflow, linkedinReadyWorkflow, listUpcomingContent, loadContentCreationConfig, markEntryAsReady, markSeriesArticleReady, promptForRequiredText, publishLinkedInCalendar, readMarkdownDocument, readyLinkedInPublication, rebuildContentIndexes, rebuildContentIndexesWorkflow, resolveBasePath, resourceNewWorkflow, scheduleReminder, seriesArticleEstimateTimeWorkflow, seriesArticleNewWorkflow, seriesArticleReadyWorkflow, seriesNewWorkflow, seriesVideoProcessWorkflow, slugify, unwrapPromptValue, writeMarkdownDocument, xScheduleReminderWorkflow };
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function
|
|
1
|
+
import{A as e,B as t,C as n,D as r,E as i,F as a,H as o,I as s,L as c,M as l,N as u,O as d,P as f,R as p,S as m,T as h,V as g,_,a as v,b as y,c as b,d as x,f as S,g as C,h as w,i as T,j as E,k as D,l as O,m as k,n as A,o as j,p as M,r as N,s as P,t as F,u as I,v as L,w as R,x as z,y as B,z as V}from"./x-dKYi_aTZ.mjs";function H(e){return e}export{r as capitalize,R as contentNewWorkflow,n as contentUpcomingWorkflow,g as createContentDirectory,_ as createResourceDirectory,x as createSeriesArticle,I as createSeriesDirectory,S as createSeriesIndex,H as defineConfig,d as escapeRegExp,e as estimateReadingMinutesFromMarkdown,O as estimateSeriesArticleTime,f as exitAsCancelled,a as exitWithError,s as formatErrorMessage,M as getSeriesArticles,k as getSeriesDirectories,w as getSeriesRootPath,D as humanizeSlug,B as linkedinPublishCalendarWorkflow,L as linkedinReadyWorkflow,t as listUpcomingContent,o as loadContentCreationConfig,i as markEntryAsReady,b as markSeriesArticleReady,E as promptForRequiredText,z as publishLinkedInCalendar,l as readMarkdownDocument,y as readyLinkedInPublication,V as rebuildContentIndexes,m as rebuildContentIndexesWorkflow,c as resolveBasePath,C as resourceNewWorkflow,A as scheduleReminder,P as seriesArticleEstimateTimeWorkflow,v as seriesArticleNewWorkflow,N as seriesArticleReadyWorkflow,j as seriesNewWorkflow,T as seriesVideoProcessWorkflow,h as slugify,p as unwrapPromptValue,u as writeMarkdownDocument,F as xScheduleReminderWorkflow};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import e from"node:process";import{intro as t,isCancel as n,log as r,multiselect as i,outro as a,select as o,text as s}from"@clack/prompts";import{existsSync as c,mkdirSync as l,readFileSync as u,readdirSync as d,renameSync as f,statSync as p,writeFileSync as m}from"node:fs";import{basename as h,dirname as ee,extname as g,isAbsolute as te,join as _,relative as ne,resolve as v}from"node:path";import{addDays as y,format as b,parseISO as re,setHours as ie,setMinutes as ae,setSeconds as oe}from"date-fns";import x from"gray-matter";import{loadConfig as se}from"c12";import{execFileSync as ce}from"node:child_process";import{createHash as le}from"node:crypto";import{fileURLToPath as ue}from"node:url";import{cleanupFile as de,extractAudio as fe,generateThumbnail as pe,generateTranscription as me}from"@barbapapazes/video-toolkit";const S={thematic:[],templatesDir:void 0,templates:{},videoProcessing:{openaiApiKey:``,language:`fr`,model:`whisper-1`},scheduling:{},calendar:{},reading:{wordsPerMinute:100}};function he(t,n){return t?te(t)?t:v(n?ee(n):e.cwd(),t):n?ee(n):e.cwd()}function ge(e,t,n){if(!t)return;let i=v(e,t);if(!c(i)){r.warn(`Configured ${n} template was not found: ${i}`);return}return i}function _e(e,t){return{footerPath:ge(t,e?.footerPath,`LinkedIn footer`)}}function ve(e,t){return{templatePath:ge(t,e?.templatePath,`video description`)}}async function C(){let{config:t,configFile:n}=await se({name:`content-creation`,defaults:S,globalRc:!0,dotenv:!0}),r=he(t.templatesDir,n),i={linkedin:_e(t.templates?.linkedin,r),youtube:ve(t.templates?.youtube,r),instagram:ve(t.templates?.instagram,r)},a={automationEndpoint:t.scheduling?.automationEndpoint??e.env.AUTOMATION_ENDPOINT,cfAccessClientId:t.scheduling?.cfAccessClientId??e.env.CF_ACCESS_CLIENT_ID,cfAccessClientSecret:t.scheduling?.cfAccessClientSecret??e.env.CF_ACCESS_CLIENT_SECRET},o={openaiApiKey:t.videoProcessing?.openaiApiKey??e.env.OPENAI_API_KEY??``,language:t.videoProcessing?.language??e.env.OPENAI_TRANSCRIPTION_LANGUAGE??S.videoProcessing?.language??`fr`,model:t.videoProcessing?.model??e.env.OPENAI_TRANSCRIPTION_MODEL??S.videoProcessing?.model??`whisper-1`},s={publicUrl:t.calendar?.publicUrl??e.env.CALENDAR_PUBLIC_URL,token:t.calendar?.token??e.env.CALENDAR_TOKEN},c=t.reading?.wordsPerMinute??S.reading?.wordsPerMinute??100;if(!Number.isFinite(c)||c<=0)throw RangeError(`Reading wordsPerMinute must be a positive number`);return{thematic:t.thematic??[],templatesDir:t.templatesDir??``,templates:i,videoProcessing:o,scheduling:a,calendar:s,reading:{wordsPerMinute:c}}}function w(e){return e?c(e)?u(e,`utf-8`):(r.warn(`Template file not found: ${e}`),``):``}const T=[`linkedin`,`x`,`youtube`,`instagram`],E={linkedin:`LinkedIn`,x:`X`,youtube:`YouTube`,instagram:`Instagram`},ye={linkedin:{mainFile:`linkedin.md`,additionalFiles:[`linkedin-script.md`]},x:{mainFile:`x.md`},youtube:{mainFile:`youtube-script.md`,additionalFiles:[`youtube-description.md`]},instagram:{mainFile:`instagram-script.md`,additionalFiles:[`instagram-description.md`]}},be={linkedin:{fileName:`linkedin-posts.md`,heading:`LinkedIn Posts`},x:{fileName:`x-posts.md`,heading:`X Posts`},youtube:{fileName:`youtube-videos.md`,heading:`YouTube Videos`},instagram:{fileName:`instagram-posts.md`,heading:`Instagram Posts`}},xe=`__load-more__`,Se=[`.jpg`,`.jpeg`,`.png`],D={hours:11,minutes:0,seconds:0};function Ce(e,t,n,i,a){let o=v(_(a,b(e,`yyyy`),b(e,`MM`),b(e,`dd`)));c(o)||(l(o,{recursive:!0}),r.success(`Created directory: ${o}`));let s=we(n);for(let e of t)Te(e,o,s,i);return o}function we(e){return e.hasVideo?{...e,hasImages:!1}:{...e,hasImages:!0}}function Te(e,t,n,i){let a=ye[e],o=_(t,a.mainFile),s=`additionalFiles`in a?a.additionalFiles:void 0;if(c(o)?r.info(`${a.mainFile} already exists, skipping`):Ee(e,o,n,i),s)for(let a of s){let o=_(t,a);Oe(a,e,n)&&(c(o)?r.info(`${a} already exists, skipping`):De(e,o,a,i))}}function Ee(e,t,n,i){let a={title:n.title};e===`linkedin`&&(n.theme&&(a.theme=n.theme),n.hasVideo&&(a.video=!0),n.hasImages&&(a.images=[null]));let o=``;e===`linkedin`&&(o=w(i.templates.linkedin?.footerPath)),m(t,x.stringify(o,a),`utf-8`),r.success(`Created ${t}`)}function De(e,t,n,i){let a=``;n.endsWith(`-description.md`)&&(a=w(i.templates[e]?.templatePath)),m(t,a,`utf-8`),r.success(`Created ${t}`)}function Oe(e,t,n){return t===`linkedin`&&e===`linkedin-script.md`?n.hasVideo||!1:!0}function ke(e,t,n){let r=n===`date-asc`?e.date.getTime()-t.date.getTime():t.date.getTime()-e.date.getTime();if(r!==0)return r;let i=T.indexOf(e.contentType)-T.indexOf(t.contentType);return i===0?e.relativePath.localeCompare(t.relativePath):i}function O(e,t={}){let n=[],i=t.contentTypes?.length?t.contentTypes:[...T],a=t.fromDate,o=t.sort||`date-desc`;try{let t=d(e).filter(t=>p(_(e,t)).isDirectory()&&/^\d{4}$/.test(t));for(let o of t){let t=_(e,o),s=d(t).filter(e=>p(_(t,e)).isDirectory()&&/^\d{2}$/.test(e));for(let e of s){let s=_(t,e),l=d(s).filter(e=>p(_(s,e)).isDirectory()&&/^\d{2}$/.test(e));for(let t of l){let l=_(s,t),d=`${o}-${e}-${t}`,f=new Date(Number(o),Number(e)-1,Number(t));if(!(a&&f.getTime()<a.getTime()))for(let a of i){let i=ye[a].mainFile,s=v(l,i);if(c(s))try{let{data:r}=x(u(s,`utf-8`)),c=r,p=c.title||`Untitled (${d})`;n.push({contentType:a,path:s,folderPath:l,date:f,title:p,relativePath:`${o}/${e}/${t}/${i}`,frontmatter:c})}catch(e){r.warn(`Error reading ${s}: ${e}`)}}}}}}catch(e){return r.error(`Error reading directories: ${e}`),[]}return n.sort((e,t)=>ke(e,t,o))}function k(t){let n=new Date;n.setHours(0,0,0,0);let i=O(t,{fromDate:n,sort:`date-asc`});if(i.length===0){r.info(`No upcoming publications found from today onward.`);return}r.info(`Found ${i.length} upcoming publication${i.length===1?``:`s`}:`),e.stdout.write(`
|
|
2
|
+
`);for(let t of i){let n=b(t.date,`EEEE, MMMM d, yyyy`),r=b(t.date,`yyyy-MM-dd`);e.stdout.write(`${n} (${r}) · ${E[t.contentType]} · ${t.title}\n`),e.stdout.write(`${t.path}\n\n`)}}function Ae(e,t){let n=O(e,{contentTypes:[t],sort:`date-desc`});if(n.length===0){r.info(`No ${t} posts found`);return}let{fileName:i,heading:a}=be[t],o=[`# ${a}`,``,`_Generated on ${new Date().toLocaleDateString()}_`,``,`Total posts: ${n.length}`,``];for(let e of n){let t=e.date.toLocaleDateString(`en-US`,{year:`numeric`,month:`long`,day:`numeric`});o.push(`- [${e.title}](${e.relativePath}) - _${t}_`)}m(_(e,i),`${o.join(`
|
|
3
|
+
`)}\n`,`utf-8`),r.success(`Created ${i} with ${n.length} posts`)}function A(e){for(let t of T)Ae(e,t)}function j(t=`Operation cancelled.`){r.error(t),e.exit(0)}function M(t){r.error(t),e.exit(1)}function je(e){return e instanceof Error?e.message:String(e)}function N(e,t){return n(e)&&j(t),e}function Me(t){return v(t??e.cwd())}async function P(e){let t=O(e.basePath,{contentTypes:e.contentTypes,sort:e.sort});t.length===0&&M(e.emptyMessage);let n=20;for(;;){let r=N(await o({message:e.message,options:Ne(t,n)}));if(r===`__load-more__`){n+=20;continue}typeof r!=`string`&&M(`Unable to resolve selected publication: ${String(r)}`);let i=t.find(e=>e.path===r);return i||M(`Unable to resolve selected publication: ${r}`),i}}function Ne(e,t){let n=e.slice(0,t).map(e=>Pe(e));if(t>=e.length)return n;let r=Math.min(t+20,e.length);return[...n,{label:`Show ${r-t} more publications (${t+1}-${r} of ${e.length})`,value:xe,hint:`Extend the list to ${e[r-1]?.relativePath}`}]}function Pe(e){return{label:`${b(e.date,`yyyy-MM-dd`)} · ${E[e.contentType]} · ${e.title}`,value:e.path,hint:e.relativePath}}function F(e){let{content:t,data:n}=x(u(e,`utf-8`));return{content:t,frontmatter:n}}function I(e,t){m(e,x.stringify(t.content,t.frontmatter),`utf-8`)}async function L(e){return N(await s({message:e.message,placeholder:e.placeholder,validate:t=>{if(!t||t.trim().length===0)return e.requiredMessage||`Value is required`}})).trim()}const Fe=/<!--([\s\S]*?)-->/g;function R(e,t){let n=e.replace(Fe,``).replace(/\s+/g,``);if(n.length===0)return 0;let r=n.length/5;return Math.ceil(r/t)}function z(e){return e.charAt(0).toUpperCase()+e.slice(1)}function B(e){return e.split(`-`).filter(Boolean).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(` `)}function V(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function H(e,t){try{let{content:n,frontmatter:i}=F(e.path);if(i.ready===!0){r.warn(`This ${t} is already marked as ready.`);return}let a={...i,ready:!0};I(e.path,{content:n,frontmatter:a}),r.success(`${z(t)} marked as ready: ${e.relativePath}`)}catch(e){M(`Failed to mark ${t} as ready: ${e instanceof Error?e.message:String(e)}`)}}function U(e){return e.toLowerCase().trim().normalize(`NFD`).replace(/[\u0300-\u036F]/g,``).replace(/[^a-z0-9\s-]/g,``).replace(/\s+/g,`-`).replace(/-+/g,`-`).replace(/^-+|-+$/g,``)||`untitled`}async function Ie(){let e=N(await i({message:`Which content types do you want to create?`,options:[{label:`LinkedIn`,value:`linkedin`,hint:`Create LinkedIn post and optional script`},{label:`X (Twitter)`,value:`x`,hint:`Create X post`},{label:`YouTube`,value:`youtube`,hint:`Create YouTube script and description`},{label:`Instagram`,value:`instagram`,hint:`Create Instagram script and description`}],required:!0}));return(!Array.isArray(e)||e.some(e=>!T.includes(e)))&&M(`Unable to resolve selected content types.`),e}async function Le(){let e=new Date,t=7;for(;;){let n=N(await o({message:`When do you want to create content?`,options:Re(e,t)}));if(n===`__load-more__`){t+=7;continue}return typeof n!=`string`&&M(`Unable to resolve selected date: ${String(n)}`),re(n)}}function Re(e,t){let n=[];for(let r=0;r<t;r++){let t=y(e,r),i=b(t,`yyyy-MM-dd`),a=b(t,`EEEE`),o=i;o=r===0?`${a.padEnd(10)} ${i} - Today`:r===1?`${a.padEnd(10)} ${i} - Tomorrow`:`${a.padEnd(10)} ${i} - ${r} days ahead`,n.push({label:o,value:i})}return n.push({label:`Show 7 more dates (${t+1}-${t+7} days ahead)`,value:xe,hint:`Extend the list up to ${b(y(e,t+7-1),`yyyy-MM-dd`)}`}),n}async function ze(e){if(e.length===0)return;let t=N(await o({message:`What is the content theme? (optional)`,options:[{label:`No theme`,value:``,hint:`Leave theme empty`},...e.map(e=>({label:e,value:e,hint:`Use theme: ${e}`}))]}));typeof t!=`string`&&M(`Unable to resolve selected theme: ${String(t)}`);let n=t;return n.length>0?n:void 0}async function Be(){return L({message:`What is the title of your content?`,placeholder:`Enter content title`,requiredMessage:`Title is required`})}async function Ve(){let e=N(await o({message:`Is a LinkedIn video planned?`,options:[{label:`Yes`,value:!0,hint:`Add video: true to frontmatter`},{label:`No`,value:!1,hint:`No video metadata`}]}));return typeof e!=`boolean`&&M(`Unable to resolve LinkedIn video choice: ${String(e)}`),e}async function He(e){t(`Content Creation - Create dated content directory`);let n=await C(),r=await Be(),i=await Ie(),o={title:r};i.includes(`linkedin`)&&(o.hasVideo=await Ve(),o.hasImages=!o.hasVideo,n.thematic.length>0&&(o.theme=await ze(n.thematic)));let s=await Le();Ce(s,i,o,n,e);let c=i.join(`, `);a(`✓ Content directory created: ${b(s,`yyyy/MM/dd`)} (${c})`)}function Ue(e){t(`Content Creation - List Upcoming Publications`),k(e),a(`✓ Upcoming publications listed`)}function We(e){t(`Content Creation - Rebuild Content Indexes`),A(e),a(`✓ Content indexes rebuilt successfully`)}const Ge=ue(new URL(`../../../../`,import.meta.url)),W=`content-creation.ics`;function G(e){return e.replace(/\\/g,`\\\\`).replace(/\r\n|\r|\n/g,`\\n`).replace(/;/g,`\\;`).replace(/,/g,`\\,`)}function Ke(e){if(e.length<=75)return e;let t=[],n=e;for(;n.length>75;)t.push(n.slice(0,75)),n=` ${n.slice(75)}`;return t.push(n),t.join(`\r
|
|
4
|
+
`)}function qe(e){return e.toISOString().replace(/[-:]/g,``).replace(/\.\d{3}Z$/,`Z`)}function Je(e){return b(e,`yyyyMMdd`)}function Ye(e){return e.split(`/`).map(e=>encodeURIComponent(e)).join(`/`)}function Xe(e){return`https://github.com/barbapapazes/content-creation/blob/main/${Ye(e)}`}function Ze(e){let t=Ye(e);return`vscode://file${t.startsWith(`/`)?``:`/`}${t}`}function Qe(e){let t=[`GitHub: ${Xe(e.relativePath)}`,`VS Code: ${Ze(e.path)}`,`Status: ${e.ready?`ready`:`not ready`}`];return e.theme&&t.push(`Theme: ${e.theme}`),typeof e.video==`boolean`&&t.push(`Video planned: ${e.video?`yes`:`no`}`),e.imageCount>0&&t.push(`Images planned: ${e.imageCount}`),t.join(`
|
|
5
|
+
`)}function $e(e){return`${e.ready?`Ready`:`Not ready`} · ${e.title} · ${E.linkedin}`}function et(e){return`linkedin-${le(`sha1`).update(e).digest(`hex`)}@barbapapazes`}function tt(e){let t=[`BEGIN:VCALENDAR`,`VERSION:2.0`,`PRODID:-//Barbapapazes//Content Creation LinkedIn Calendar//EN`,`CALSCALE:GREGORIAN`,`METHOD:PUBLISH`,`X-WR-CALNAME:${G(`content-creation.ics`)}`];for(let n of e){let e=qe(new Date(Date.UTC(n.date.getFullYear(),n.date.getMonth(),n.date.getDate()))),r=y(n.date,1);t.push(`BEGIN:VEVENT`,`UID:${et(n.relativePath)}`,`DTSTAMP:${e}`,`SUMMARY:${G($e(n))}`,`DTSTART;VALUE=DATE:${Je(n.date)}`,`DTEND;VALUE=DATE:${Je(r)}`,`TRANSP:TRANSPARENT`,`DESCRIPTION:${G(Qe(n))}`,`END:VEVENT`)}return t.push(`END:VCALENDAR`),`${t.map(Ke).join(`\r
|
|
6
|
+
`)}\r\n`}function nt(){let t=_(Ge,`node_modules`,`.bin`,e.platform===`win32`?`wrangler.cmd`:`wrangler`);return c(t)?t:`wrangler`}function rt(t){(!t.calendar.publicUrl||!t.calendar.token)&&(r.error(`Calendar publishing configuration is missing. Please set CALENDAR_PUBLIC_URL and CALENDAR_TOKEN in your .env file or config.`),e.exit(1));let n=t.calendar.publicUrl.endsWith(`/`)?t.calendar.publicUrl:`${t.calendar.publicUrl}/`,i=new URL(W,n);return i.searchParams.set(`token`,t.calendar.token),i.toString()}function it(e){return O(e,{contentTypes:[`linkedin`],sort:`date-asc`}).map(e=>{let t=e.frontmatter;return{...e,title:t.title||e.title,ready:t.ready===!0,theme:t.theme,video:t.video,imageCount:t.images?.filter(Boolean).length??0}})}function at(t){let n=nt();try{ce(n,[`r2`,`object`,`put`,`content-creation/${W}`,`--pipe`,`--content-type`,`text/calendar; charset=utf-8`,`--remote`],{cwd:Ge,stdio:[`pipe`,`inherit`,`inherit`],env:e.env,input:t})}catch(t){r.error(`Failed to upload LinkedIn calendar: ${t instanceof Error?t.message:String(t)}`),e.exit(1)}}async function ot(e,t){let n=it(e);if(n.length===0)return r.info(`No LinkedIn publications found. Skipping calendar upload.`),null;let i=rt(t);return at(tt(n)),r.success(`✓ LinkedIn calendar uploaded to R2 as ${W}`),r.info(`Subscription URL: ${i}`),i}function st(e){let t=[];try{let n=d(e);for(let r of n){if(!p(_(e,r)).isFile())continue;let n=r.toLowerCase().substring(r.lastIndexOf(`.`));Se.includes(n)&&t.push(r)}}catch(e){M(`Error reading directory: ${e instanceof Error?e.message:String(e)}`)}return t.sort()}function ct(e,t){try{let{content:n,frontmatter:i}=F(e.path);I(e.path,{content:n,frontmatter:{...i,images:t.length>0?t:[null]}}),r.success(`LinkedIn publication updated: ${e.relativePath} (${t.length} image(s))`)}catch(e){M(`Failed to update LinkedIn publication: ${e instanceof Error?e.message:String(e)}`)}}function lt(e){let t=st(e.folderPath);t.length===0?r.info(`No images found for LinkedIn publication: ${e.relativePath}`):r.info(`Found ${t.length} image(s) for ${e.relativePath}: ${t.join(`, `)}`),ct(e,t),H({path:e.path,relativePath:e.relativePath},`linkedin publication`);let n=_(e.folderPath,`linkedin-script.md`),i=e.relativePath.slice(0,e.relativePath.lastIndexOf(`/`)),a=c(n);return a&&H({path:n,relativePath:`${i}/linkedin-script.md`},`linkedin script`),{imageCount:t.length,scriptMarked:a}}async function ut(e){if(t(`Content Creation - Publish LinkedIn Calendar`),await ot(e,await C())){a(`✓ LinkedIn calendar published successfully`);return}a(`✓ No LinkedIn publications found, calendar was not updated`)}async function dt(e){return P({basePath:e,contentTypes:[`linkedin`],message:`Which LinkedIn publication do you want to mark as ready?`,emptyMessage:`No LinkedIn publications found in dated folders`,sort:`date-desc`})}async function ft(e){t(`Content Creation - Mark LinkedIn Publication Ready`);let n=await dt(e),r=lt(n),i=r.scriptMarked?`script marked ready`:`no script found`;a(`✓ LinkedIn publication ready: ${n.relativePath} (${r.imageCount} image(s), ${i})`)}function pt(e,t,n){let i=U(e),a=_(v(_(n,`resources`)),i),o=`${t}.md`,s=_(a,o);if(c(s))return r.info(`${o} already exists at: ${s}`),a;l(a,{recursive:!0}),r.success(`Created directory: ${a}`);let u={title:e,url:``,date:``};return m(s,x.stringify(``,u),`utf-8`),r.success(`Created ${o} at: ${s}`),a}async function mt(){let e=N(await o({message:`What type of resource is this?`,options:[{label:`Article`,value:`article`,hint:`Create article.md`},{label:`Video`,value:`video`,hint:`Create video.md`},{label:`Audio`,value:`audio`,hint:`Create audio.md`},{label:`Tweet`,value:`tweet`,hint:`Create tweet.md`}]}));return[`article`,`video`,`audio`,`tweet`].includes(String(e))||M(`Unable to resolve resource type: ${String(e)}`),e}async function ht(){return L({message:`What is the title of your content?`,placeholder:`Enter content title`,requiredMessage:`Title is required`})}async function gt(e){t(`Content Creation - Create Resource`);let n=await ht(),r=await mt();pt(n,r,e),a(`✓ Resource created: resources/${U(n)}/${r}.md`)}const _t=`## Index`,K=`<!-- content-creation:series-index:start -->`,q=`<!-- content-creation:series-index:end -->`,vt=/^(\d+)\.([^.]+(?:\.[^.]+)*)\.md$/;function J(e){return v(_(e,`series`))}function yt(e){let t=J(e);return c(t)?d(t).filter(e=>Ct(_(t,e))).map(e=>wt(t,e)).sort((e,t)=>e.title.localeCompare(t.title,`fr`)):[]}function Y(e){return d(e).map(Ot).filter(e=>e!==null).map(t=>Tt(e,t)).sort((e,t)=>e.index-t.index)}function X(e){let t=Y(e),n=_(e,`README.md`);m(n,bt(c(n)?u(n,`utf-8`):``,t,e),`utf-8`),r.success(`Generated series index: ${n}`)}function bt(e,t,n){let r=xt(t);if(e.includes(K)&&e.includes(q))return e.replace(St(),r);let i=e.trimEnd();return i.length>0?`${i}\n\n${r}\n`:`# ${B(n.split(`/`).pop()||`series`)}\n\n${r}\n`}function xt(e){let t=[_t,``,K];if(e.length===0)t.push(``,`_No articles yet._`);else{t.push(``);for(let n of e)t.push(`${n.index}. [${n.title}](./${n.fileName})`)}return t.push(``,q),t.join(`
|
|
7
|
+
`)}function St(){return RegExp(`${V(_t)}\n\n${V(K)}[\\s\\S]*?${V(q)}`)}function Ct(e){return p(e).isDirectory()}function wt(e,t){let n=_(e,t);return{name:t,path:n,title:Et(n,t)}}function Tt(e,t){let n=_(e,t.fileName);return{...t,path:n,title:Dt(n,t.slug)}}function Et(e,t){let n=_(e,`README.md`);if(c(n)){let e=u(n,`utf-8`).split(`
|
|
8
|
+
`).map(e=>e.trim()).find(e=>e.startsWith(`# `));if(e)return e.slice(2).trim()}return B(t)}function Dt(e,t){try{let{data:t}=x(u(e,`utf-8`)),n=typeof t.title==`string`?t.title.trim():``;if(n.length>0)return n}catch(t){r.warn(`Unable to read article title from ${e}: ${t}`)}return B(t)}function Ot(e){let t=e.match(vt);return t?{index:Number(t[1]),slug:t[2],fileName:e}:null}function kt(e,t,n){let i=Y(e),a=jt(n,i.length),o=_(e,`${a}.${U(t)}.md`);if(c(o))throw Error(`Article already exists at ${o}`);return At(e,i,a),m(o,x.stringify(``,{title:t}),`utf-8`),r.success(`Created article: ${o}`),X(e),o}function At(e,t,n){let i=t.filter(e=>e.index>=n).sort((e,t)=>t.index-e.index);for(let t of i){let n=`${t.index+1}.${t.slug}.md`,i=_(e,n);f(t.path,i),r.info(`Renamed ${t.fileName} → ${n}`)}}function jt(e,t){if(!Number.isInteger(e))throw TypeError(`Article index must be an integer`);if(e<1||e>t+1)throw RangeError(`Article index must be between 1 and ${t+1}`);return e}function Mt(e,t){let n=t.trim();if(n.length===0)throw Error(`Series title is required`);let i=U(n),a=J(e),o=_(a,i);if(l(a,{recursive:!0}),c(o))throw Error(`Series already exists: series/${i}`);return l(o),m(_(o,`README.md`),`# ${n}\n`,`utf-8`),X(o),r.success(`Created series: ${o}`),{name:i,path:o,title:n}}function Z(e,t){let{content:n,frontmatter:i}=F(e),a=R(n,t);return I(e,{content:n,frontmatter:{...i,time:a}}),r.success(`Estimated time set to ${a} min: ${e}`),a}function Nt(e){H({path:e.path,relativePath:e.fileName},`series article`)}async function Pt(e){let t=Y(e);if(t.length===0&&M(`No series articles found`),t.length===1)return t[0];let n=N(await o({message:`Which series article do you want to update?`,options:t.map(e=>({label:`#${e.index} · ${e.title}`,value:e.path,hint:e.fileName}))}));typeof n!=`string`&&M(`Unable to resolve selected series article: ${String(n)}`);let r=t.find(e=>e.path===n);return r||M(`Unable to resolve selected series article: ${n}`),r}async function Ft(){return L({message:`What is the title of your article?`,placeholder:`Enter article title`,requiredMessage:`Title is required`})}async function Q(e){let t=yt(e);if(t.length===0&&M(`No series directories found`),t.length===1)return t[0];let n=N(await o({message:`Which series do you want to work on?`,options:t.map(e=>({label:e.title,value:e.path,hint:`series/${e.name}`}))}));typeof n!=`string`&&M(`Unable to resolve selected series: ${String(n)}`);let r=t.find(e=>e.path===n);return r||M(`Unable to resolve selected series: ${n}`),r}async function It(e){let t=Y(e),n=t.map(e=>({label:`Insert at #${e.index} · before ${e.title}`,value:String(e.index),hint:e.fileName}));n.push({label:`Append as #${t.length+1}`,value:String(t.length+1),hint:`Add the new article at the end of the series`});let r=N(await o({message:`Where should the new article be inserted?`,options:n}));return typeof r!=`string`&&M(`Unable to resolve selected insert position: ${String(r)}`),Number(r)}async function Lt(){return L({message:`What is the title of your series?`,placeholder:`Enter series title`,requiredMessage:`Title is required`})}function Rt(e){return zt(e,e).sort((e,t)=>e.relativePath.localeCompare(t.relativePath))}function zt(e,t){let n=d(t,{withFileTypes:!0}),r=[];for(let i of n){let n=_(t,i.name);if(i.isDirectory()){r.push(...zt(e,n));continue}!i.isFile()||g(i.name).toLowerCase()!==`.mp4`||r.push({name:i.name,path:n,relativePath:ne(e,n)})}return r}async function Bt(e){let t=Rt(e);if(t.length===0&&M(`No .mp4 files found in series directory: ${e}`),t.length===1)return t[0];let n=N(await o({message:`Which series video do you want to process?`,options:t.map(e=>({label:e.relativePath,value:e.path,hint:e.name}))}));typeof n!=`string`&&M(`Unable to resolve selected series video: ${String(n)}`);let r=t.find(e=>e.path===n);return r||M(`Unable to resolve selected series video: ${n}`),r}async function Vt(){let e=await s({message:`Enter text for the thumbnail (or press Enter to skip):`,placeholder:`Optional thumbnail text`});if(typeof e!=`symbol`)return e.trim()||void 0}async function Ht(e){let t=Ut(e);if(t.length===0){r.error(`No templates found in ${e||`the configured templates directory`}`),r.info(`Please add SVG templates before generating thumbnails.`);return}let n=await o({message:`Choose a template:`,options:t.map(e=>({label:e,value:e}))});if(typeof n!=`symbol`)return n}function Ut(e){if(!e||!c(e))return[];try{return d(e,{withFileTypes:!0}).filter(e=>e.isFile()&&g(e.name).toLowerCase()===`.svg`).map(e=>e.name.replace(/\.svg$/i,``)).sort((e,t)=>e.localeCompare(t))}catch{return[]}}async function Wt(e){t(`Content Creation - Estimate Series Article Time`);let n=await C(),r=await Q(e),i=await Pt(r.path),o=Z(i.path,n.reading.wordsPerMinute);a(`✓ Estimated time updated for ${r.name}/${i.fileName}: ${o} min`)}async function Gt(e){t(`Content Creation - Create Series`),a(`✓ Series created: series/${Mt(e,await Lt()).name}`)}async function Kt(e){t(`Content Creation - Create Series Article`);let n=await Q(e),r=await Ft(),i=await It(n.path),o=kt(n.path,r,i);a(`✓ Series article created: ${n.name}/${h(o)}`)}async function qt(e){let n=await C();n.videoProcessing.openaiApiKey||M(`OpenAI API key is required. Set OPENAI_API_KEY in .env or configure videoProcessing.openaiApiKey in content-creation.config.{ts,js,mjs,json}.`);let i=await Q(e),o=await Bt(i.path);t(`Content Creation - Process Series Video: ${h(o.path)}`);let{videoPath:s,audioPath:c}=await fe(o.path),{srtPath:l}=await me(s,c,{openaiApiKey:n.videoProcessing.openaiApiKey,language:n.videoProcessing.language,model:n.videoProcessing.model,templatesDir:n.templatesDir});de(c,`audio file`);let u=await Vt();if(!u){a(`✓ Transcription saved to: ${l}`);return}let d=await Ht(n.templatesDir);if(!d){r.warn(`No template selected. Skipping thumbnail generation.`),a(`✓ Transcription saved to: ${l}`);return}let{thumbnailPaths:f}=await pe(s,u,d,n.templatesDir);a(`✓ Series video processed: series/${i.name}/${o.relativePath}\n✓ Transcription saved to: ${l}\n✓ Thumbnails saved:\n - ${f.join(`
|
|
9
|
+
- `)}`)}async function Jt(e){t(`Content Creation - Mark Series Article Ready`);let n=await C(),r=await Q(e),i=await Pt(r.path);Nt(i);let o=Z(i.path,n.reading.wordsPerMinute);a(`✓ Series article updated for ${r.name}/${i.fileName}: ready + ${o} min`)}async function $(e,t){let n=e.path,{content:i,frontmatter:a}=F(n);if(a.scheduled===!0)return r.warn(`This X publication is already scheduled. Skipping.`),!1;(!t.scheduling.automationEndpoint||!t.scheduling.cfAccessClientId||!t.scheduling.cfAccessClientSecret)&&M(`Scheduling configuration is missing. Please set AUTOMATION_ENDPOINT, CF_ACCESS_CLIENT_ID, and CF_ACCESS_CLIENT_SECRET in your .env file or config.`);let o=new Date(e.date);o=ie(o,D.hours),o=ae(o,D.minutes),o=oe(o,D.seconds);let s={content:i.trim(),scheduleAt:o.getTime()};try{let e=await fetch(t.scheduling.automationEndpoint,{method:`POST`,headers:{"Content-Type":`application/json`,Accept:`application/json`,"CF-Access-Client-Id":t.scheduling.cfAccessClientId,"CF-Access-Client-Secret":t.scheduling.cfAccessClientSecret},body:JSON.stringify(s)});if(!e.ok){let t=await e.text();M(`Failed to schedule reminder: ${e.status} ${e.statusText}\n${t}`)}let o=await e.json();I(n,{content:i,frontmatter:{...a,scheduled:!0}});let c=new Date(o.scheduledAt);return r.success(`✓ X publication reminder scheduled`),r.info(`Reminder scheduled for: ${b(c,`yyyy-MM-dd HH:mm:ss`)} UTC`),!0}catch(e){M(`Failed to schedule X publication reminder: ${e instanceof Error?e.message:String(e)}`)}}async function Yt(e){return P({basePath:e,contentTypes:[`x`],message:`Which X publication do you want to schedule a reminder for?`,emptyMessage:`No X publications found in dated folders`,sort:`date-desc`})}async function Xt(e){t(`Content Creation - Schedule X Publication Reminder`);let n=await C();a(await $(await Yt(e),n)?`✓ X publication reminder scheduled`:`✓ X publication was already scheduled`)}export{R as A,k as B,Ue as C,z as D,H as E,M as F,C as H,je as I,Me as L,F as M,I as N,V as O,j as P,N as R,We as S,U as T,Ce as V,pt as _,Kt as a,lt as b,Nt as c,kt as d,X as f,gt as g,J as h,qt as i,L as j,B as k,Z as l,yt as m,$ as n,Gt as o,Y as p,Jt as r,Wt as s,Xt as t,Mt as u,ft as v,He as w,ot as x,ut as y,A as z};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barbapapazes/content-creation",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.20.0",
|
|
5
5
|
"description": "CLI tool for content creation and management",
|
|
6
6
|
"author": "Estéban Soubiran <esteban@soubiran.dev>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -31,17 +31,18 @@
|
|
|
31
31
|
"node": ">=24"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@clack/prompts": "^
|
|
34
|
+
"@clack/prompts": "^1.4.0",
|
|
35
35
|
"c12": "^3.3.4",
|
|
36
36
|
"commander": "15.0.0-0",
|
|
37
37
|
"date-fns": "^4.1.0",
|
|
38
38
|
"gray-matter": "^4.0.3",
|
|
39
|
-
"wrangler": "^4.61.1"
|
|
39
|
+
"wrangler": "^4.61.1",
|
|
40
|
+
"@barbapapazes/video-toolkit": "0.20.0"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
42
43
|
"@tsconfig/node24": "^24.0.4",
|
|
43
44
|
"@types/node": "^22.19.17",
|
|
44
|
-
"tsdown": "^0.
|
|
45
|
+
"tsdown": "^0.22.0",
|
|
45
46
|
"typescript": "^5.9.3"
|
|
46
47
|
},
|
|
47
48
|
"scripts": {
|