@barbapapazes/content-creation 0.12.0 → 0.14.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 CHANGED
@@ -7,6 +7,9 @@ A CLI tool to streamline content creation by generating dated directories with c
7
7
  - Create dated directory structure (YYYY/MM/DD format)
8
8
  - User-friendly date selection (up to 7 days ahead or custom date)
9
9
  - Automatic `linkedin.md` file creation in the day directory
10
+ - Link images to LinkedIn post frontmatter
11
+ - Create resource directories (articles, videos, audio)
12
+ - Generate a markdown list of all LinkedIn posts ordered by date
10
13
 
11
14
  ## Installation
12
15
 
@@ -16,6 +19,10 @@ npm install -g @barbapapazes/content-creation
16
19
 
17
20
  ## Usage
18
21
 
22
+ ### Create Content Directory
23
+
24
+ Create a dated directory with a LinkedIn post file:
25
+
19
26
  ```bash
20
27
  # Create content directory in current working directory
21
28
  content-creation
@@ -26,6 +33,56 @@ content-creation --path /path/to/content
26
33
  # Result: /path/to/content/2026/01/01/linkedin.md
27
34
  ```
28
35
 
36
+ ### Link Images
37
+
38
+ Scan recent LinkedIn posts and link images to their frontmatter:
39
+
40
+ ```bash
41
+ # Link images in current directory
42
+ content-creation link-images
43
+
44
+ # Link images at a specific path
45
+ content-creation link-images --path /path/to/content
46
+ ```
47
+
48
+ ### Create Resource
49
+
50
+ Create a resource directory with article, video, or audio content:
51
+
52
+ ```bash
53
+ # Create resource in current directory
54
+ content-creation resource
55
+
56
+ # Create resource at a specific path
57
+ content-creation resource --path /path/to/resources
58
+ ```
59
+
60
+ ### List Posts
61
+
62
+ Generate a markdown file listing all LinkedIn posts ordered by date (most recent first):
63
+
64
+ ```bash
65
+ # Generate posts list in current directory
66
+ content-creation list-posts
67
+
68
+ # Generate posts list at a specific path
69
+ content-creation list-posts --path /path/to/content
70
+ ```
71
+
72
+ This creates a `posts-list.md` file at the root with clickable links to each post:
73
+
74
+ ```markdown
75
+ # LinkedIn Posts
76
+
77
+ _Generated on 1/21/2026_
78
+
79
+ Total posts: 15
80
+
81
+ - [Understanding AI Agents](2026/01/20/linkedin.md) - _January 20, 2026_
82
+ - [Deep Dive into RAG](2026/01/15/linkedin.md) - _January 15, 2026_
83
+ ...
84
+ ```
85
+
29
86
  ## Configuration
30
87
 
31
88
  You can configure the tool using a configuration file. Create one of the following files:
package/dist/cli.mjs CHANGED
@@ -1,7 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import e from"node:process";import{intro as t,isCancel as n,log as r,outro as i,select as a,text as o}from"@clack/prompts";import{cac as s}from"cac";import{addDays as c,format as l,parse as u}from"date-fns";import{existsSync as d,mkdirSync as f,readFileSync as p,readdirSync as m,statSync as h,writeFileSync as g}from"node:fs";import{join as _,resolve as v}from"node:path";import y from"gray-matter";var b=`0.12.0`;function x(t,n,i,a,o=e.cwd()){let s=v(_(o,l(t,`yyyy`),l(t,`MM`),l(t,`dd`))),c=_(s,`linkedin.md`);if(d(c))return r.info(`linkedin.md already exists at: ${c}`),s;f(s,{recursive:!0}),r.success(`Created directory: ${s}`);let u={title:n};if(i&&(u.video=!0),a&&(u.images=[null]),g(c,y.stringify(`
2
+ import e from"node:process";import{intro as t,isCancel as n,log as r,outro as i,select as a,text as o}from"@clack/prompts";import{cac as s}from"cac";import{addDays as c,format as l,parse as u}from"date-fns";import{existsSync as d,mkdirSync as f,readFileSync as p,readdirSync as m,statSync as h,writeFileSync as g}from"node:fs";import{join as _,resolve as v}from"node:path";import y from"gray-matter";var b=`0.14.0`;function x(t,n,i,a,o=e.cwd()){let s=v(_(o,l(t,`yyyy`),l(t,`MM`),l(t,`dd`))),c=_(s,`linkedin.md`);if(d(c))return r.info(`linkedin.md already exists at: ${c}`),s;f(s,{recursive:!0}),r.success(`Created directory: ${s}`);let u={title:n};if(i&&(u.video=!0),a&&(u.images=[null]),g(c,y.stringify(`
3
3
 
4
4
  ---
5
5
 
6
6
  Je m'appelle Estéban, et pour toi, je décode l'IA pour t'aider à passer à l'action. Abonne-toi ! 🛠️
7
- `,u),`utf-8`),r.success(`Created linkedin.md at: ${c}`),i){let e=_(s,`linkedin-script.md`);g(e,``,`utf-8`),r.success(`Created linkedin-script.md at: ${e}`)}return s}function S(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 C(t,n,i=e.cwd()){let a=S(t),o=_(v(_(i,`resources`)),a),s=`${n}.md`,c=_(o,s);if(d(c))return r.info(`${s} already exists at: ${c}`),o;f(o,{recursive:!0}),r.success(`Created directory: ${o}`);let l={title:t,url:``,date:``};return g(c,y.stringify(``,l),`utf-8`),r.success(`Created ${s} at: ${c}`),o}function w(t=e.cwd()){let n=[];try{let e=m(t).filter(e=>h(_(t,e)).isDirectory()&&/^\d{4}$/.test(e));for(let r of e){let e=_(t,r),i=m(e).filter(t=>h(_(e,t)).isDirectory()&&/^\d{2}$/.test(t));for(let t of i){let i=_(e,t),a=m(i).filter(e=>h(_(i,e)).isDirectory()&&/^\d{2}$/.test(e));for(let e of a){let a=_(i,e);if(d(_(a,`linkedin.md`))){let i=`${r}-${t}-${e}`;n.push({path:a,date:new Date(i),displayPath:`${r}/${t}/${e}`})}}}}}catch(e){return r.error(`Error reading directories: ${e}`),[]}return n.sort((e,t)=>t.date.getTime()-e.date.getTime()).slice(0,8)}function T(e){let t=[`.jpg`,`.jpeg`,`.png`],n=[];try{let r=m(e);for(let i of r)if(h(_(e,i)).isFile()){let e=i.toLowerCase().substring(i.lastIndexOf(`.`));t.includes(e)&&n.push(i)}}catch(e){r.error(`Error reading directory: ${e}`)}return n.sort()}function E(e,t){try{let{data:n,content:i}=y(p(e,`utf-8`));n.images=t.length>0?t:[null],g(e,y.stringify(i,n),`utf-8`),r.success(`Updated ${e} with ${t.length} image(s)`)}catch(e){r.error(`Error updating frontmatter: ${e}`)}}async function D(t){if(t.length===0)return r.warn(`No dated folders with linkedin.md found`),null;let i=await a({message:`Select a folder to link images:`,options:t.map(e=>({label:e.displayPath,value:e.path,hint:`Link images in ${e.displayPath}`}))});return n(i)&&(r.error(`Operation cancelled.`),e.exit(0)),t.find(e=>e.path===i)||null}async function O(t=e.cwd()){let n=w(t);if(n.length===0){r.warn(`No dated folders with linkedin.md found`);return}let i=await D(n);if(!i)return;let a=T(i.path);if(a.length===0){r.info(`No images found in ${i.displayPath}`),E(_(i.path,`linkedin.md`),[]);return}r.info(`Found ${a.length} image(s): ${a.join(`, `)}`),E(_(i.path,`linkedin.md`),a)}async function k(){let t=new Date,i=[];for(let e=0;e<7;e++){let n=l(c(t,e),`yyyy-MM-dd`),r=n,a=`Create content for ${n}`;e===0?(r=`Today (${n})`,a=`Create content for today`):e===1?(r=`Tomorrow (${n})`,a=`Create content for tomorrow`):(r=`In ${e} days (${n})`,a=`Create content for ${n}`),i.push({label:r,value:`day-${e}`,hint:a})}i.push({label:`Custom date`,value:`custom`,hint:`Enter a specific date`});let s=await a({message:`When do you want to create content?`,options:i});if(n(s)&&(r.error(`Operation cancelled.`),e.exit(0)),s!==`custom`)return c(t,Number.parseInt(s.split(`-`)[1]));let d=await o({message:`Enter date (YYYY-MM-DD):`,placeholder:l(t,`yyyy-MM-dd`),validate:e=>{if(!e)return`Date is required`;if(!/^\d{4}-\d{2}-\d{2}$/.test(e))return`Invalid date format. Please use YYYY-MM-DD`;try{let t=u(e,`yyyy-MM-dd`,new Date);if(Number.isNaN(t.getTime()))return`Invalid date`}catch{return`Invalid date`}}});return n(d)&&(r.error(`Operation cancelled.`),e.exit(0)),u(d,`yyyy-MM-dd`,new Date)}async function A(){let t=await o({message:`What is the title of your content?`,placeholder:`Enter content title`,validate:e=>{if(!e||e.trim().length===0)return`Title is required`}});return n(t)&&(r.error(`Operation cancelled.`),e.exit(0)),t.trim()}async function j(){let t=await a({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 n(t)&&(r.error(`Operation cancelled.`),e.exit(0)),t}async function M(){let t=await a({message:`Are there images?`,options:[{label:`Yes`,value:!0,hint:`Add images section to frontmatter`},{label:`No`,value:!1,hint:`No images metadata`}]});return n(t)&&(r.error(`Operation cancelled.`),e.exit(0)),t}async function N(){let t=await a({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`}]});return n(t)&&(r.error(`Operation cancelled.`),e.exit(0)),t}const P=s(`content-creation`);P.command(`[path]`,`Create a dated content directory with linkedin.md file`).option(`--path <path>`,`Base path for content directory (defaults to current directory)`).action(async(n,r)=>{t(`Content Creation - Create dated content directory`);let a=await A(),o=await j(),s=await M(),c=await k();x(c,a,o,s,n||r?.path||e.cwd()),i(`✓ Content directory created: ${l(c,`yyyy/MM/dd`)}/linkedin.md`)}),P.command(`link-images [path]`,`Link images to LinkedIn frontmatter in recent folders`).option(`--path <path>`,`Base path for content directory (defaults to current directory)`).action(async(n,r)=>{t(`Content Creation - Link Images to LinkedIn`),await O(n||r?.path||e.cwd()),i(`✓ Images linked successfully`)}),P.command(`resource [path]`,`Create a resource with article/video/audio markdown file`).option(`--path <path>`,`Base path for resource directory (defaults to current directory)`).action(async(n,r)=>{t(`Content Creation - Create Resource`);let a=await A(),o=await N();C(a,o,n||r?.path||e.cwd()),i(`✓ Resource created: resources/${S(a)}/${o}.md`)}),P.help(),P.version(b),P.parse();export{};
7
+ `,u),`utf-8`),r.success(`Created linkedin.md at: ${c}`),i){let e=_(s,`linkedin-script.md`);g(e,``,`utf-8`),r.success(`Created linkedin-script.md at: ${e}`)}return s}function S(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 C(t,n,i=e.cwd()){let a=S(t),o=_(v(_(i,`resources`)),a),s=`${n}.md`,c=_(o,s);if(d(c))return r.info(`${s} already exists at: ${c}`),o;f(o,{recursive:!0}),r.success(`Created directory: ${o}`);let l={title:t,url:``,date:``};return g(c,y.stringify(``,l),`utf-8`),r.success(`Created ${s} at: ${c}`),o}function w(t=e.cwd()){let n=[];try{let e=m(t).filter(e=>h(_(t,e)).isDirectory()&&/^\d{4}$/.test(e));for(let r of e){let e=_(t,r),i=m(e).filter(t=>h(_(e,t)).isDirectory()&&/^\d{2}$/.test(t));for(let t of i){let i=_(e,t),a=m(i).filter(e=>h(_(i,e)).isDirectory()&&/^\d{2}$/.test(e));for(let e of a){let a=_(i,e);if(d(_(a,`linkedin.md`))){let i=`${r}-${t}-${e}`;n.push({path:a,date:new Date(i),displayPath:`${r}/${t}/${e}`})}}}}}catch(e){return r.error(`Error reading directories: ${e}`),[]}return n.sort((e,t)=>t.date.getTime()-e.date.getTime()).slice(0,8)}function T(e){let t=[`.jpg`,`.jpeg`,`.png`],n=[];try{let r=m(e);for(let i of r)if(h(_(e,i)).isFile()){let e=i.toLowerCase().substring(i.lastIndexOf(`.`));t.includes(e)&&n.push(i)}}catch(e){r.error(`Error reading directory: ${e}`)}return n.sort()}function E(e,t){try{let{data:n,content:i}=y(p(e,`utf-8`));n.images=t.length>0?t:[null],g(e,y.stringify(i,n),`utf-8`),r.success(`Updated ${e} with ${t.length} image(s)`)}catch(e){r.error(`Error updating frontmatter: ${e}`)}}async function D(t){if(t.length===0)return r.warn(`No dated folders with linkedin.md found`),null;let i=await a({message:`Select a folder to link images:`,options:t.map(e=>({label:e.displayPath,value:e.path,hint:`Link images in ${e.displayPath}`}))});return n(i)&&(r.error(`Operation cancelled.`),e.exit(0)),t.find(e=>e.path===i)||null}async function O(t=e.cwd()){let n=w(t);if(n.length===0){r.warn(`No dated folders with linkedin.md found`);return}let i=await D(n);if(!i)return;let a=T(i.path);if(a.length===0){r.info(`No images found in ${i.displayPath}`),E(_(i.path,`linkedin.md`),[]);return}r.info(`Found ${a.length} image(s): ${a.join(`, `)}`),E(_(i.path,`linkedin.md`),a)}function k(t=e.cwd()){let n=[];try{let e=m(t).filter(e=>h(_(t,e)).isDirectory()&&/^\d{4}$/.test(e));for(let i of e){let e=_(t,i),a=m(e).filter(t=>h(_(e,t)).isDirectory()&&/^\d{2}$/.test(t));for(let t of a){let a=_(e,t),o=m(a).filter(e=>h(_(a,e)).isDirectory()&&/^\d{2}$/.test(e));for(let e of o){let o=_(_(a,e),`linkedin.md`);if(d(o)){let a=`${i}-${t}-${e}`,s=new Date(a);try{let{data:r}=y(p(o,`utf-8`)),a=r.title||`Untitled`;n.push({path:o,date:s,title:a,relativePath:`${i}/${t}/${e}/linkedin.md`})}catch(e){r.warn(`Error reading ${o}: ${e}`)}}}}}}catch(e){return r.error(`Error reading directories: ${e}`),[]}return n.sort((e,t)=>t.date.getTime()-e.date.getTime())}function A(t=e.cwd()){let n=k(t);if(n.length===0){r.warn(`No LinkedIn posts found in the directory structure`);return}let i=`# LinkedIn Posts
8
+
9
+ `;i+=`_Generated on ${new Date().toLocaleDateString()}_\n\n`,i+=`Total posts: ${n.length}\n\n`;for(let e of n){let t=e.date.toLocaleDateString(`en-US`,{year:`numeric`,month:`long`,day:`numeric`});i+=`- [${e.title}](${e.relativePath}) - _${t}_\n`}let a=_(t,`posts-list.md`);g(a,i,`utf-8`),r.success(`Created posts list at: ${a}`),r.info(`Total posts listed: ${n.length}`)}async function j(){let t=new Date,i=[];for(let e=0;e<7;e++){let n=c(t,e),r=l(n,`yyyy-MM-dd`),a=l(n,`EEEE`),o=r,s=`Create content for ${a}, ${r}`;e===0?(o=`Today - ${a} (${r})`,s=`Create content for today (${a})`):e===1?(o=`Tomorrow - ${a} (${r})`,s=`Create content for tomorrow (${a})`):(o=`In ${e} days - ${a} (${r})`,s=`Create content for ${a}, ${r}`),i.push({label:o,value:`day-${e}`,hint:s})}i.push({label:`Custom date`,value:`custom`,hint:`Enter a specific date`});let s=await a({message:`When do you want to create content?`,options:i});if(n(s)&&(r.error(`Operation cancelled.`),e.exit(0)),s!==`custom`)return c(t,Number.parseInt(s.split(`-`)[1]));let d=await o({message:`Enter date (YYYY-MM-DD):`,placeholder:l(t,`yyyy-MM-dd`),validate:e=>{if(!e)return`Date is required`;if(!/^\d{4}-\d{2}-\d{2}$/.test(e))return`Invalid date format. Please use YYYY-MM-DD`;try{let t=u(e,`yyyy-MM-dd`,new Date);if(Number.isNaN(t.getTime()))return`Invalid date`}catch{return`Invalid date`}}});return n(d)&&(r.error(`Operation cancelled.`),e.exit(0)),u(d,`yyyy-MM-dd`,new Date)}async function M(){let t=await o({message:`What is the title of your content?`,placeholder:`Enter content title`,validate:e=>{if(!e||e.trim().length===0)return`Title is required`}});return n(t)&&(r.error(`Operation cancelled.`),e.exit(0)),t.trim()}async function N(){let t=await a({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 n(t)&&(r.error(`Operation cancelled.`),e.exit(0)),t}async function P(){let t=await a({message:`Are there images?`,options:[{label:`Yes`,value:!0,hint:`Add images section to frontmatter`},{label:`No`,value:!1,hint:`No images metadata`}]});return n(t)&&(r.error(`Operation cancelled.`),e.exit(0)),t}async function F(){let t=await a({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 n(t)&&(r.error(`Operation cancelled.`),e.exit(0)),t}const I=s(`content-creation`);I.command(`[path]`,`Create a dated content directory with linkedin.md file`).option(`--path <path>`,`Base path for content directory (defaults to current directory)`).action(async(n,r)=>{t(`Content Creation - Create dated content directory`);let a=await M(),o=await N(),s=await P(),c=await j();x(c,a,o,s,n||r?.path||e.cwd()),i(`✓ Content directory created: ${l(c,`yyyy/MM/dd`)}/linkedin.md`)}),I.command(`link-images [path]`,`Link images to LinkedIn frontmatter in recent folders`).option(`--path <path>`,`Base path for content directory (defaults to current directory)`).action(async(n,r)=>{t(`Content Creation - Link Images to LinkedIn`),await O(n||r?.path||e.cwd()),i(`✓ Images linked successfully`)}),I.command(`resource [path]`,`Create a resource with article/video/audio markdown file`).option(`--path <path>`,`Base path for resource directory (defaults to current directory)`).action(async(n,r)=>{t(`Content Creation - Create Resource`);let a=await M(),o=await F();C(a,o,n||r?.path||e.cwd()),i(`✓ Resource created: resources/${S(a)}/${o}.md`)}),I.command(`list-posts [path]`,`Create or update a markdown file listing all LinkedIn posts`).option(`--path <path>`,`Base path for content directory (defaults to current directory)`).action(async(n,r)=>{t(`Content Creation - List Posts`),A(n||r?.path||e.cwd()),i(`✓ Posts list generated successfully`)}),I.help(),I.version(b),I.parse();export{};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@barbapapazes/content-creation",
3
3
  "type": "module",
4
- "version": "0.12.0",
4
+ "version": "0.14.0",
5
5
  "author": "Estéban Soubiran <esteban@soubiran.dev>",
6
6
  "license": "MIT",
7
7
  "funding": "https://github.com/sponsors/Barbapapazes",