@barbapapazes/content-creation 0.18.9 → 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 CHANGED
@@ -2,454 +2,56 @@
2
2
 
3
3
  A CLI tool to streamline multi-platform content creation by generating dated directories with content files for LinkedIn, X (Twitter), YouTube, and Instagram.
4
4
 
5
- ## Features
6
-
7
- - Create dated directory structure (YYYY/MM/DD format)
8
- - Support for multiple content types: LinkedIn, X, YouTube, Instagram
9
- - User-friendly date selection that starts with 7 days and can keep loading more future dates
10
- - Multi-select content types to create multiple files in one run
11
- - Config-driven templates for each content type
12
- - Never overwrites existing files (skip if exists behavior)
13
- - Generate separate index files per content type
14
- - Manage article series with generated README indexes and ordered inserts
15
- - Estimate series article reading time and write it to frontmatter
16
- - List upcoming content from today onward with absolute file paths
17
- - Generate and upload a LinkedIn publication calendar for Google Calendar subscriptions
18
- - Link images to LinkedIn post frontmatter
19
- - Mark a publication as ready by setting `ready: true` in frontmatter
20
- - Create resource directories (articles, videos, audio)
21
-
22
5
  ## Installation
23
6
 
24
7
  ```bash
25
8
  npm install -g @barbapapazes/content-creation
26
9
  ```
27
10
 
28
- ## Usage
29
-
30
- Several workflows are now grouped under domain-oriented commands for better discoverability:
31
-
32
- - `content-creation index ...`
33
- - `content-creation series ...`
34
- - `content-creation linkedin ...`
35
- - `content-creation x ...`
36
- - `content-creation publication ...`
37
- - `content-creation resource ...`
38
-
39
- ### Create Content Directory
40
-
41
- Create a dated directory with content files for selected platforms:
42
-
43
- ```bash
44
- # Create content directory in current working directory
45
- content-creation
46
- # Prompts you to select: LinkedIn, X, YouTube, and/or Instagram
47
-
48
- # Create content directory at a specific path
49
- content-creation --path /path/to/content
50
- ```
51
-
52
- The tool will prompt you to:
53
- 1. Enter a title (shared across all selected types)
54
- 2. Select one or more content types
55
- 3. Answer type-specific questions:
56
- - **LinkedIn**: Whether to include a video. If not, photos are automatically enabled.
57
- - **YouTube/Instagram**: Whether to create script and description files
58
- 4. Choose a date, with the option to keep loading more future dates
59
-
60
- #### Content Files by Type
61
-
62
- Each content type creates specific files:
63
-
64
- - **LinkedIn**: `linkedin.md` with `images: [null]` by default, plus `linkedin-script.md` when video is planned
65
- - **X**: `x.md`
66
- - **YouTube**: `youtube.md`, `youtube-script.md`, `youtube-description.md`
67
- - **Instagram**: `instagram.md`, `instagram-script.md`, `instagram-description.md`
68
-
69
- All main files (`*.md`) include frontmatter with at least a `title` field for indexing.
70
-
71
- **Important**: Files are never overwritten. If a file already exists, it will be skipped.
72
-
73
- ### Create Index Files
74
-
75
- Generate index files for each content type found in your dated folders:
76
-
77
- ```bash
78
- # Generate index files in current directory
79
- content-creation index create
80
-
81
- # Generate index files at a specific path
82
- content-creation index create --path /path/to/content
83
- ```
84
-
85
- This command scans your `YYYY/MM/DD` directory structure and creates:
86
-
87
- - `linkedin-posts.md` - Index of all LinkedIn posts
88
- - `x-posts.md` - Index of all X posts
89
- - `youtube-videos.md` - Index of all YouTube videos
90
- - `instagram-posts.md` - Index of all Instagram posts
91
-
92
- Each index file contains:
93
-
94
- ```markdown
95
- # LinkedIn Posts
96
-
97
- _Generated on 1/31/2026_
98
-
99
- Total posts: 15
100
-
101
- - [Understanding AI Agents](2026/01/20/linkedin.md) - _January 20, 2026_
102
- - [Deep Dive into RAG](2026/01/15/linkedin.md) - _January 15, 2026_
103
- ...
104
- ```
105
-
106
- ### List Upcoming Content
107
-
108
- List all content scheduled for today or later:
109
-
110
- ```bash
111
- content-creation publication list-upcoming
112
-
113
- # List upcoming content at a specific path
114
- content-creation publication list-upcoming --path /path/to/content
115
- ```
116
-
117
- This command scans your `YYYY/MM/DD` directory structure, reads the content title from frontmatter, and prints each matching content item with its absolute file path so it is easy to click from the terminal.
118
-
119
- Example output:
120
-
121
- ```text
122
- 2026-04-18 · LinkedIn · Shipping a better content workflow
123
- /absolute/path/to/content/2026/04/18/linkedin.md
124
-
125
- 2026-04-20 · X · A tiny automation tip
126
- /absolute/path/to/content/2026/04/20/x.md
127
- ```
128
-
129
- ### Manage Series
130
-
131
- Work with article series stored under `series/<series-slug>/`.
132
-
133
- Each series directory can contain:
134
-
135
- - a `README.md` file with the editorial context for the series;
136
- - numbered markdown files such as `1.introduction.md`, `2.my-next-article.md`, etc.
137
-
138
- #### Generate the series index
139
-
140
- Refresh the generated `## Index` section in each series `README.md` based on the numbered article files:
141
-
142
- ```bash
143
- # Generate indexes for all series in the current directory
144
- content-creation series create-index
145
-
146
- # Generate the index for a specific series only
147
- content-creation series create-index --series my-series-slug
148
-
149
- # Generate series indexes from another base path
150
- content-creation series create-index --path /path/to/content
151
- ```
152
-
153
- The command preserves the existing `README.md` content and only manages the generated `## Index` section.
154
-
155
- #### Introduce a new article in a series
156
-
157
- Insert a new article at a specific position in the series:
158
-
159
- ```bash
160
- # Interactive mode
161
- content-creation series introduce
162
-
163
- # Fully scripted mode
164
- content-creation series introduce \
165
- --series my-series-slug \
166
- --title "What tool calling really changes" \
167
- --position 6
168
- ```
169
-
170
- This command will:
171
-
172
- 1. create a new markdown file with frontmatter containing the title;
173
- 2. rename subsequent numbered files to make room for the new article;
174
- 3. regenerate the series `README.md` index automatically.
175
-
176
- Generated article files follow the pattern `<index>.<slug>.md`.
177
-
178
- #### Estimate a series article reading time
179
-
180
- Compute the reading duration of a numbered series article and store it in the `time` frontmatter field.
181
-
182
- The estimate ignores frontmatter and HTML comments, assumes 5 characters per word, and uses `100` words per minute by default.
183
-
184
- ```bash
185
- # Interactive mode
186
- content-creation series estimate-time
187
-
188
- # Fully scripted mode
189
- content-creation series estimate-time \
190
- --series my-series-slug \
191
- --file 6.what-tool-calling-really-changes.md \
192
- --wpm 120
193
- ```
194
-
195
- This command will:
196
-
197
- 1. prompt you for the series and the article file when needed;
198
- 2. estimate the reading time in whole minutes from the markdown body only;
199
- 3. ignore frontmatter and HTML comments during the calculation;
200
- 4. write the resulting value into `time` in the article frontmatter.
201
-
202
- ### Publish LinkedIn Calendar
203
-
204
- Generate a Google Calendar-compatible `.ics` file for LinkedIn publications, upload it to Cloudflare R2 using Wrangler, and print the subscription URL:
205
-
206
- ```bash
207
- content-creation linkedin publish-calendar
208
-
209
- # Publish the LinkedIn calendar from a specific content directory
210
- content-creation linkedin publish-calendar --path /path/to/content
211
- ```
212
-
213
- This command will:
214
- 1. Scan your `YYYY/MM/DD` directory structure for `linkedin.md` files only
215
- 2. Read the LinkedIn frontmatter and generate all-day calendar events
216
- 3. Stream the generated calendar directly to Cloudflare R2 using Wrangler
217
- 4. Upload `content-creation.ics` to the `content-creation` R2 bucket
218
- 5. Print the final subscription URL for Google Calendar
219
-
220
- **Required Configuration**: You must set up the following environment variables in a `.env` file:
221
-
222
- ```bash
223
- CALENDAR_PUBLIC_URL=https://calendar.soubiran.dev
224
- CALENDAR_TOKEN=your-calendar-token-here
225
- ```
226
-
227
- See `.env.example` for a template. The printed URL uses the format:
228
-
229
- ```text
230
- https://calendar.soubiran.dev/content-creation.ics?token=<your-calendar-token>
231
- ```
232
-
233
- **Current Scope**: This command publishes LinkedIn entries only for now.
234
-
235
- ### Link Images
236
-
237
- Scan recent LinkedIn posts and link images to their frontmatter:
238
-
239
- ```bash
240
- content-creation linkedin link-images
241
-
242
- # Link images at a specific path
243
- content-creation linkedin link-images --path /path/to/content
244
- ```
245
-
246
- ### Create Resource
247
-
248
- Create a resource directory with article, video, or audio content:
249
-
250
- ```bash
251
- # Preferred grouped command
252
- content-creation resource create
253
-
254
- # Create resource at a specific path
255
- content-creation resource create --path /path/to/resources
256
- ```
257
-
258
- ### Schedule Reminder
11
+ ## CLI help
259
12
 
260
- Schedule a reminder for X (Twitter) content to be posted at 11:00 AM UTC on the date specified in the folder path:
13
+ Use the built-in help to discover the available commands and subcommands:
261
14
 
262
15
  ```bash
263
- content-creation x schedule-reminder
264
-
265
- # Schedule reminder at a specific path
266
- content-creation x schedule-reminder --path /path/to/content
16
+ content-creation --help
17
+ content-creation <command> --help
267
18
  ```
268
19
 
269
- The command will:
270
- 1. Scan your directory for dated folders containing `x.md` files
271
- 2. Prompt you to select which content to schedule
272
- 3. Check if the content is already scheduled (exits early if `scheduled: true` in frontmatter)
273
- 4. Extract the tweet content from `x.md`
274
- 5. Calculate the scheduled time (11:00 AM UTC based on the folder date: YYYY/MM/DD)
275
- 6. Send a POST request to your automation endpoint
276
- 7. Update the frontmatter with `scheduled: true`
277
- 8. Display a confirmation message with the scheduled date/time
278
-
279
- **Required Configuration**: You must set up the following environment variables in a `.env` file:
20
+ ## Commands
280
21
 
281
- ```bash
282
- AUTOMATION_ENDPOINT=https://automation.soubiran.dev/trigger
283
- CF_ACCESS_CLIENT_ID=your-client-id-here
284
- CF_ACCESS_CLIENT_SECRET=your-client-secret-here
285
- ```
286
-
287
- See `.env.example` for a template.
288
-
289
- ### Mark Publication as Ready
290
-
291
- Mark a publication as ready by setting `ready: true` in the selected markdown file frontmatter:
292
-
293
- ```bash
294
- content-creation publication ready
295
-
296
- # Mark a publication as ready from a specific content directory
297
- content-creation publication ready --path /path/to/content
298
- ```
299
-
300
- This command will:
301
- 1. Scan your `YYYY/MM/DD` directory structure for publication files
302
- 2. Prompt you to select which publication to update
303
- 3. Add `ready: true` to the selected file frontmatter
22
+ - `content new` — create a dated content directory
23
+ - `content upcoming` — list upcoming publications
24
+ - `content rebuild-indexes` — regenerate content indexes
25
+ - `series new` — create a new series directory
26
+ - `series video process` — process a series video into subtitles and optional thumbnails
27
+ - `series article new` — insert a new article in a series
28
+ - `series article ready` — mark a series article as ready and refresh reading time
29
+ - `series article estimate-time` — compute reading time for a series article
30
+ - `linkedin ready` link images, mark the post ready, and mark the script ready when present
31
+ - `linkedin publish-calendar` — publish the LinkedIn calendar to Cloudflare R2
32
+ - `x schedule-reminder` schedule an X reminder through the automation endpoint
33
+ - `resource new` — create a new resource directory
304
34
 
305
35
  ## Configuration
306
36
 
307
- You can configure the tool using a configuration file. Create one of the following files:
308
-
309
- - Local config: `content-creation.config.{ts,js,mjs,json}` in your project root
310
- - Global config: `~/.content-creationrc` (JSON format)
311
-
312
- ### Configuration Options
313
-
314
- ```typescript
315
- export default {
316
- // Array of thematic areas (reserved for future features)
317
- thematic: ['JavaScript', 'TypeScript', 'Node.js'],
318
-
319
- // Base directory for external template files (optional)
320
- templatesDir: '~/.config/content-creation/templates',
321
-
322
- // Templates configuration per content type
323
- templates: {
324
- linkedin: {
325
- body: '', // Inline template for body content
326
- bodyPath: 'linkedin-body.md', // Or path to template file
327
- footer: '\n\n---\n\nCustom footer text', // Inline footer
328
- footerPath: 'linkedin-footer.md', // Or path to footer file
329
- },
330
- x: {
331
- body: '',
332
- bodyPath: 'x-body.md',
333
- },
334
- youtube: {
335
- body: '',
336
- script: '',
337
- description: '',
338
- // Or use paths: bodyPath, scriptPath, descriptionPath
339
- },
340
- instagram: {
341
- body: '',
342
- script: '',
343
- description: '',
344
- },
345
- },
37
+ The package reads configuration from:
346
38
 
347
- // Scheduling configuration (optional, can also be set via environment variables)
348
- scheduling: {
349
- automationEndpoint: 'https://automation.soubiran.dev/trigger',
350
- cfAccessClientId: 'your-client-id',
351
- cfAccessClientSecret: 'your-client-secret',
352
- },
39
+ - `content-creation.config.{ts,js,mjs,json}` in your project
40
+ - `~/.content-creationrc`
41
+ - environment variables loaded from `.env`
353
42
 
354
- // Calendar publishing configuration (optional, can also be set via environment variables)
355
- calendar: {
356
- publicUrl: 'https://calendar.soubiran.dev',
357
- token: 'your-calendar-token',
358
- },
43
+ Environment variables used by advanced workflows:
359
44
 
360
- // Reading-time configuration (optional)
361
- reading: {
362
- wordsPerMinute: 100,
363
- },
364
- }
365
- ```
366
-
367
- ### Template Resolution
368
-
369
- For each template field (body, footer, script, description):
370
-
371
- 1. If `*Path` is specified, it's resolved relative to `templatesDir` or the config file location
372
- 2. Otherwise, the inline string value is used
373
- 3. If neither is specified, defaults are used (LinkedIn has a default footer)
374
-
375
- ### Example: Customizing LinkedIn Footer
376
-
377
- Create a `content-creation.config.ts` file:
378
-
379
- ```typescript
380
- import { defineConfig } from '@barbapapazes/content-creation'
381
-
382
- export default defineConfig({
383
- templates: {
384
- linkedin: {
385
- footer: '\n\n---\n\nCustom signature here! 🚀',
386
- },
387
- },
388
- })
389
- ```
390
-
391
- Or store templates in external files:
392
-
393
- ```typescript
394
- import { defineConfig } from '@barbapapazes/content-creation'
395
-
396
- export default defineConfig({
397
- templatesDir: '~/.config/content-creation/templates',
398
- templates: {
399
- linkedin: {
400
- footerPath: 'linkedin-footer.md',
401
- },
402
- youtube: {
403
- descriptionPath: 'youtube-description.md',
404
- },
405
- },
406
- })
407
- ```
408
-
409
- Then create `~/.config/content-creation/templates/linkedin-footer.md`:
410
-
411
- ```markdown
412
- ---
413
-
414
- Subscribe for more content! 🎯
415
- ```
416
-
417
- ### JSON Configuration
418
-
419
- For a JSON config file (`~/.content-creationrc`):
420
-
421
- ```json
422
- {
423
- "thematic": ["JavaScript", "TypeScript", "Node.js"],
424
- "templates": {
425
- "linkedin": {
426
- "footer": "\n\n---\n\nFollow for more! 🚀"
427
- }
428
- }
429
- }
430
- ```
431
-
432
- ## Directory Structure
433
-
434
- The CLI creates a nested directory structure organized by date:
435
-
436
- ```
437
- base-path/
438
- └── YYYY/ # Year folder
439
- └── MM/ # Month folder
440
- └── DD/ # Day folder
441
- ├── linkedin.md
442
- ├── linkedin-script.md
443
- ├── x.md
444
- ├── youtube.md
445
- ├── youtube-script.md
446
- ├── youtube-description.md
447
- ├── instagram.md
448
- ├── instagram-script.md
449
- └── instagram-description.md
450
- ```
45
+ - `CALENDAR_PUBLIC_URL`
46
+ - `CALENDAR_TOKEN`
47
+ - `AUTOMATION_ENDPOINT`
48
+ - `CF_ACCESS_CLIENT_ID`
49
+ - `CF_ACCESS_CLIENT_SECRET`
50
+ - `OPENAI_API_KEY`
51
+ - `OPENAI_TRANSCRIPTION_LANGUAGE`
52
+ - `OPENAI_TRANSCRIPTION_MODEL`
451
53
 
452
- Example: Running the CLI on January 31, 2026 and selecting all types will create a `2026/01/31/` directory with all the relevant files.
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.
453
55
 
454
56
  ## License
455
57
 
package/dist/cli.mjs CHANGED
@@ -1,7 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{cac as e}from"cac";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{addDays as c,format as l,parseISO as u,setHours as d,setMinutes as f,setSeconds as ee}from"date-fns";import{existsSync as p,mkdirSync as m,readFileSync as h,readdirSync as g,renameSync as te,statSync as _,writeFileSync as v}from"node:fs";import{dirname as y,isAbsolute as ne,join as b,resolve as x}from"node:path";import S from"node:process";import C from"gray-matter";import{loadConfig as re}from"c12";import{execFileSync as ie}from"node:child_process";import{createHash as ae}from"node:crypto";import{fileURLToPath as oe}from"node:url";var se=`0.18.9`;const w={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`]}},T={thematic:[],templatesDir:void 0,templates:{},scheduling:{},calendar:{},reading:{}};function ce(e,t){return e?ne(e)?e:x(t?y(t):S.cwd(),e):t?y(t):S.cwd()}function le(e,t){let n={};if(e?.footerPath){let r=x(t,e.footerPath);p(r)&&(n.footerPath=r)}return n}function E(e,t){let n={};if(e?.templatePath){let r=x(t,e.templatePath);p(r)&&(n.templatePath=r)}return n}async function D(){let{config:e,configFile:t}=await re({name:`content-creation`,defaults:T,globalRc:!0,dotenv:!0}),n=ce(e.templatesDir,t),r={linkedin:le(e.templates?.linkedin,n),youtube:E(e.templates?.youtube,n),instagram:E(e.templates?.instagram,n)},i={automationEndpoint:e.scheduling?.automationEndpoint||S.env.AUTOMATION_ENDPOINT,cfAccessClientId:e.scheduling?.cfAccessClientId||S.env.CF_ACCESS_CLIENT_ID,cfAccessClientSecret:e.scheduling?.cfAccessClientSecret||S.env.CF_ACCESS_CLIENT_SECRET},a={publicUrl:e.calendar?.publicUrl||S.env.CALENDAR_PUBLIC_URL,token:e.calendar?.token||S.env.CALENDAR_TOKEN},o={wordsPerMinute:e.reading?.wordsPerMinute||T.reading?.wordsPerMinute||100};return{thematic:e.thematic||[],templatesDir:e.templatesDir||``,templates:r,scheduling:i,calendar:a,reading:o}}function O(e){return p(e)?h(e,`utf-8`):``}function ue(e,t,n,i,a=S.cwd()){let o=x(b(a,l(e,`yyyy`),l(e,`MM`),l(e,`dd`)));p(o)||(m(o,{recursive:!0}),r.success(`Created directory: ${o}`));let s=de(n);for(let e of t)fe(e,o,s,i);return o}function de(e){return e.hasVideo?{...e,hasImages:!1}:{...e,hasImages:!0}}function fe(e,t,n,i){let a=w[e],o=b(t,a.mainFile);if(p(o)?r.info(`${a.mainFile} already exists, skipping`):pe(e,o,n,i),a.additionalFiles)for(let o of a.additionalFiles){let a=b(t,o);he(o,e,n)&&(p(a)?r.info(`${o} already exists, skipping`):me(e,a,o,i))}}function pe(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=``;if(e===`linkedin`){let e=i.templates.linkedin?.footerPath;o=e?O(e):``}v(t,C.stringify(o,a),`utf-8`),r.success(`Created ${t}`)}function me(e,t,n,i){let a=``;if(n.endsWith(`-description.md`)){let t=i.templates[e]?.templatePath;t&&(a=O(t))}v(t,a,`utf-8`),r.success(`Created ${t}`)}function he(e,t,n){return t===`linkedin`&&e===`linkedin-script.md`?n.hasVideo||!1:!0}const k=[`linkedin`,`x`,`youtube`,`instagram`],A={linkedin:`LinkedIn`,x:`X`,youtube:`YouTube`,instagram:`Instagram`};function ge(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=k.indexOf(e.contentType)-k.indexOf(t.contentType);return i===0?e.relativePath.localeCompare(t.relativePath):i}function j(e,t={}){let n=[],i=t.contentTypes?.length?t.contentTypes:[...k],a=t.fromDate,o=t.sort||`date-desc`;try{let t=g(e).filter(t=>_(b(e,t)).isDirectory()&&/^\d{4}$/.test(t));for(let o of t){let t=b(e,o),s=g(t).filter(e=>_(b(t,e)).isDirectory()&&/^\d{2}$/.test(e));for(let e of s){let s=b(t,e),c=g(s).filter(e=>_(b(s,e)).isDirectory()&&/^\d{2}$/.test(e));for(let t of c){let c=b(s,t),l=`${o}-${e}-${t}`,u=new Date(Number(o),Number(e)-1,Number(t));if(!(a&&u.getTime()<a.getTime()))for(let a of i){let i=w[a].mainFile,s=x(c,i);if(p(s))try{let{data:r}=C(h(s,`utf-8`)),d=r,f=d.title||`Untitled (${l})`;n.push({contentType:a,path:s,folderPath:c,date:u,title:f,relativePath:`${o}/${e}/${t}/${i}`,frontmatter:d})}catch(e){r.warn(`Error reading ${s}: ${e}`)}}}}}}catch(e){return r.error(`Error reading directories: ${e}`),[]}return n.sort((e,t)=>ge(e,t,o))}const M=`## Index`,N=`<!-- content-creation:series-index:start -->`,P=`<!-- content-creation:series-index:end -->`;function _e(e){return x(b(e,`series`))}function F(e){let t=_e(e);return p(t)?g(t).filter(e=>_(b(t,e)).isDirectory()).map(e=>{let n=b(t,e);return{name:e,path:n,title:be(n,e)}}).sort((e,t)=>e.title.localeCompare(t.title,`fr`)):(r.error(`Series directory not found: ${t}`),[])}function I(e,t){return F(e).find(e=>e.name===t)}function L(e){return g(e).map(e=>Se(e)).filter(e=>e!==null).map(t=>({...t,path:b(e,t.fileName),title:xe(b(e,t.fileName),t.slug)})).sort((e,t)=>e.index-t.index)}function R(e,t){return L(e).find(e=>e.fileName===t)}function z(e){let t=L(e),n=b(e,`README.md`);v(n,ve(p(n)?h(n,`utf-8`):``,t,e),`utf-8`),r.success(`Generated series index: ${n}`)}function ve(e,t,n){let r=ye(t);if(e.includes(N)&&e.includes(P))return e.replace(RegExp(`${V(M)}\\n\\n${V(N)}[\\s\\S]*?${V(P)}`),r);let i=e.trimEnd();return i.length>0?`${i}\n\n${r}\n`:`# ${B(n.split(`/`).pop()||`series`)}\n\n${r}\n`}function ye(e){let t=[M,``,N];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(``,P),t.join(`
3
- `)}function be(e,t){let n=b(e,`README.md`);if(p(n)){let e=h(n,`utf-8`).split(`
4
- `).map(e=>e.trim()).find(e=>e.startsWith(`# `));if(e)return e.slice(2).trim()}return B(t)}function xe(e,t){try{let{data:t}=C(h(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 Se(e){let t=e.match(/^(\d+)\.([^.]+(?:\.[^.]+)*)\.md$/);return t?{index:Number(t[1]),slug:t[2],fileName:e}:null}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,`\\$&`)}const H=`load-more`;async function Ce(){let e=new Date,t=7;for(;;){let i=await o({message:`When do you want to create content?`,options:we(e,t)});if(n(i)&&(r.error(`Operation cancelled.`),S.exit(0)),i===`load-more`){t+=7;continue}return u(i)}}function we(e,t){let n=[];for(let r=0;r<t;r++){let t=c(e,r),i=l(t,`yyyy-MM-dd`),a=l(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:`load-more`,hint:`Extend the list up to ${l(c(e,t+6),`yyyy-MM-dd`)}`}),n}async function U(){let e=await s({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(e)&&(r.error(`Operation cancelled.`),S.exit(0)),e.trim()}async function Te(){let e=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 n(e)&&(r.error(`Operation cancelled.`),S.exit(0)),e}async function Ee(e){if(e.length===0)return;let t=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}`}))]});n(t)&&(r.error(`Operation cancelled.`),S.exit(0));let i=t;return i.length>0?i:void 0}async function De(){let e=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 n(e)&&(r.error(`Operation cancelled.`),S.exit(0)),e}async function Oe(){let e=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 n(e)&&(r.error(`Operation cancelled.`),S.exit(0)),e}async function ke(e){return W({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 Ae(e){return W({basePath:e,contentTypes:[`linkedin`],message:`Which LinkedIn publication do you want to link images for?`,emptyMessage:`No LinkedIn publications found in dated folders`,sort:`date-desc`})}async function je(e){return W({basePath:e,contentTypes:[...k],message:`Which publication do you want to mark as ready?`,emptyMessage:`No publications found in dated folders`,sort:`date-desc`})}async function W(e){let t=j(e.basePath,{contentTypes:e.contentTypes,sort:e.sort});t.length===0&&(r.error(e.emptyMessage),S.exit(1));let i=20;for(;;){let a=await o({message:e.message,options:Me(t,i)});if(n(a)&&(r.error(`Operation cancelled.`),S.exit(0)),a===H){i+=20;continue}let s=t.find(e=>e.path===a);return s||(r.error(`Unable to resolve selected publication: ${a}`),S.exit(1)),s}}function Me(e,t){let n=e.slice(0,t).map(e=>Ne(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:H,hint:`Extend the list to ${e[r-1]?.relativePath}`}]}function Ne(e){return{label:`${l(e.date,`yyyy-MM-dd`)} · ${A[e.contentType]} · ${e.title}`,value:e.path,hint:e.relativePath}}async function G(e){let t=F(e);if(t.length===0&&(r.error(`No series directories found`),S.exit(1)),t.length===1)return t[0];let i=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}`}))});n(i)&&(r.error(`Operation cancelled.`),S.exit(0));let a=t.find(e=>e.path===i);return a||(r.error(`Unable to resolve selected series: ${i}`),S.exit(1)),a}async function Pe(e){let t=L(e),i=t.map(e=>({label:`Insert at #${e.index} · before ${e.title}`,value:String(e.index),hint:e.fileName}));i.push({label:`Append as #${t.length+1}`,value:String(t.length+1),hint:`Add the new article at the end of the series`});let a=await o({message:`Where should the new article be inserted?`,options:i});return n(a)&&(r.error(`Operation cancelled.`),S.exit(0)),Number(a)}async function K(e){let t=L(e);if(t.length===0&&(r.error(`No series articles found`),S.exit(1)),t.length===1)return t[0];let i=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}))});n(i)&&(r.error(`Operation cancelled.`),S.exit(0));let a=t.find(e=>e.path===i);return a||(r.error(`Unable to resolve selected series article: ${i}`),S.exit(1)),a}function q(e,t){return e||t?.path||S.cwd()}async function Fe(e,n){t(`Content Creation - Create dated content directory`);let r=await D(),i=await U(),o=await Oe(),s={title:i};o.includes(`linkedin`)&&(s.hasVideo=await Te(),s.hasImages=!s.hasVideo,r.thematic.length>0&&(s.theme=await Ee(r.thematic)));let c=await Ce();ue(c,o,s,r,q(e,n));let u=o.join(`, `);a(`✓ Content directory created: ${l(c,`yyyy/MM/dd`)} (${u})`)}function Ie(e){switch(e){case`linkedin`:return`linkedin-posts.md`;case`x`:return`x-posts.md`;case`youtube`:return`youtube-videos.md`;case`instagram`:return`instagram-posts.md`}}function Le(e){switch(e){case`linkedin`:return`LinkedIn Posts`;case`x`:return`X Posts`;case`youtube`:return`YouTube Videos`;case`instagram`:return`Instagram Posts`}}function Re(e,t){let n=j(e,{contentTypes:[t],sort:`date-desc`});if(n.length===0){r.info(`No ${t} posts found`);return}let i=Le(t),a=Ie(t),o=`# ${i}\n\n`;o+=`_Generated on ${new Date().toLocaleDateString()}_\n\n`,o+=`Total posts: ${n.length}\n\n`;for(let e of n){let t=e.date.toLocaleDateString(`en-US`,{year:`numeric`,month:`long`,day:`numeric`});o+=`- [${e.title}](${e.relativePath}) - _${t}_\n`}v(b(e,a),o,`utf-8`),r.success(`Created ${a} with ${n.length} posts`)}function ze(e=S.cwd(),t){let n=t||[...k];for(let t of n)Re(e,t)}function Be(e,n){t(`Content Creation - Create Index Files`),ze(q(e,n)),a(`✓ Index files generated successfully`)}function Ve(e,t,n){if(e===`create`){Be(t,n);return}throw Error(`Unknown index action: ${e}. Supported actions are: create`)}function He(e){let t=[`.jpg`,`.jpeg`,`.png`],n=[];try{let r=g(e);for(let i of r)if(_(b(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 J(e,t){try{let{data:n,content:i}=C(h(e.path,`utf-8`));n.images=t.length>0?t:[null];let a=C.stringify(i,n);v(e.path,a,`utf-8`),r.success(`LinkedIn publication updated: ${e.relativePath} (${t.length} image(s))`)}catch(e){r.error(`Error updating frontmatter: ${e}`)}}async function Ue(e=S.cwd()){let t=await Ae(e),n=He(t.folderPath);if(n.length===0){r.info(`No images found for LinkedIn publication: ${t.relativePath}`),J(t,[]);return}r.info(`Found ${n.length} image(s) for ${t.relativePath}: ${n.join(`, `)}`),J(t,n)}const We=oe(new URL(`../../`,import.meta.url)),Y=`content-creation.ics`;function X(e){return e.replace(/\\/g,`\\\\`).replace(/\r\n|\r|\n/g,`\\n`).replace(/;/g,`\\;`).replace(/,/g,`\\,`)}function Ge(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 Ke(e){return e.toISOString().replace(/[-:]/g,``).replace(/\.\d{3}Z$/,`Z`)}function qe(e){return l(e,`yyyyMMdd`)}function Je(e){return e.split(`/`).map(e=>encodeURIComponent(e)).join(`/`)}function Ye(e){return`https://github.com/barbapapazes/content-creation/blob/main/${Je(e)}`}function Xe(e){let t=Je(e);return`vscode://file${t.startsWith(`/`)?``:`/`}${t}`}function Ze(e){let t=[`GitHub: ${Ye(e.relativePath)}`,`VS Code: ${Xe(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 Qe(e){return`${e.ready?`Ready`:`Not ready`} · ${e.title} · ${A.linkedin}`}function $e(e){return`linkedin-${ae(`sha1`).update(e).digest(`hex`)}@barbapapazes`}function et(e){let t=[`BEGIN:VCALENDAR`,`VERSION:2.0`,`PRODID:-//Barbapapazes//Content Creation LinkedIn Calendar//EN`,`CALSCALE:GREGORIAN`,`METHOD:PUBLISH`,`X-WR-CALNAME:${X(`content-creation.ics`)}`];for(let n of e){let e=Ke(new Date(Date.UTC(n.date.getFullYear(),n.date.getMonth(),n.date.getDate()))),r=c(n.date,1);t.push(`BEGIN:VEVENT`,`UID:${$e(n.relativePath)}`,`DTSTAMP:${e}`,`SUMMARY:${X(Qe(n))}`,`DTSTART;VALUE=DATE:${qe(n.date)}`,`DTEND;VALUE=DATE:${qe(r)}`,`TRANSP:TRANSPARENT`,`DESCRIPTION:${X(Ze(n))}`,`END:VEVENT`)}return t.push(`END:VCALENDAR`),`${t.map(Ge).join(`\r
7
- `)}\r\n`}function tt(){let e=b(We,`node_modules`,`.bin`,S.platform===`win32`?`wrangler.cmd`:`wrangler`);return p(e)?e:`wrangler`}function nt(e){(!e.calendar.publicUrl||!e.calendar.token)&&(r.error(`Calendar publishing configuration is missing. Please set CALENDAR_PUBLIC_URL and CALENDAR_TOKEN in your .env file or config.`),S.exit(1));let t=e.calendar.publicUrl.endsWith(`/`)?e.calendar.publicUrl:`${e.calendar.publicUrl}/`,n=new URL(Y,t);return n.searchParams.set(`token`,e.calendar.token),n.toString()}function rt(e){return j(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 it(e){let t=tt();try{ie(t,[`r2`,`object`,`put`,`content-creation/${Y}`,`--pipe`,`--content-type`,`text/calendar; charset=utf-8`,`--remote`],{cwd:We,stdio:[`pipe`,`inherit`,`inherit`],env:S.env,input:e})}catch(e){r.error(`Failed to upload LinkedIn calendar: ${e instanceof Error?e.message:String(e)}`),S.exit(1)}}async function at(e,t){let n=rt(e);if(n.length===0)return r.info(`No LinkedIn publications found. Skipping calendar upload.`),null;let i=nt(t);return it(et(n)),r.success(`✓ LinkedIn calendar uploaded to R2 as ${Y}`),r.info(`Subscription URL: ${i}`),i}async function ot(e,n){t(`Content Creation - Link Images to LinkedIn Publication`),await Ue(q(e,n)),a(`✓ LinkedIn publication images linked`)}async function st(e,n){t(`Content Creation - Publish LinkedIn Calendar`);let r=await D();if(await at(q(e,n),r)){a(`✓ LinkedIn calendar published successfully`);return}a(`✓ No LinkedIn publications found, calendar was not updated`)}async function ct(e,t,n){if(e===`link-images`){await ot(t,n);return}if(e===`publish-calendar`){await st(t,n);return}throw Error(`Unknown linkedin action: ${e}. Supported actions are: link-images, publish-calendar`)}function lt(e){let t=new Date;t.setHours(0,0,0,0);let n=j(e,{fromDate:t,sort:`date-asc`});if(n.length===0){r.info(`No upcoming publications found from today onward.`);return}r.info(`Found ${n.length} upcoming publication${n.length===1?``:`s`}:`),console.log(``);for(let e of n){let t=l(e.date,`EEEE, MMMM d, yyyy`),n=l(e.date,`yyyy-MM-dd`);console.log(`${t} (${n}) · ${A[e.contentType]} · ${e.title}`),console.log(e.path),console.log(``)}}const ut=/<!--([\s\S]*?)-->/g;function dt(e,t){try{let{data:n,content:i}=C(h(e,`utf-8`)),a=ft(i,t);return v(e,C.stringify(i,{...n,time:a}),`utf-8`),r.success(`Estimated time set to ${a} min: ${e}`),a}catch(e){r.error(`Failed to estimate series article time: ${e instanceof Error?e.message:String(e)}`),S.exit(1)}}function ft(e,t){if(!Number.isFinite(t)||t<=0)throw RangeError(`Words per minute must be a positive number`);let n=e.replace(ut,``).replace(/\s+/g,``);if(n.length===0)return 0;let r=n.length/5;return Math.ceil(r/t)}function pt(e){ht({path:e.path,relativePath:e.relativePath},`publication`)}function mt(e,t){return ht({path:e.path,relativePath:e.fileName},`series article`),dt(e.path,t)}function ht(e,t){let n=e.path;try{let{data:i,content:a}=C(h(n,`utf-8`));if(i.ready===!0){r.warn(`This ${t} is already marked as ready.`);return}let o={...i,ready:!0};v(n,C.stringify(a,o),`utf-8`),r.success(`${gt(t)} marked as ready: ${e.relativePath}`)}catch(e){r.error(`Failed to mark ${t} as ready: ${e instanceof Error?e.message:String(e)}`),S.exit(1)}}function gt(e){return e.charAt(0).toUpperCase()+e.slice(1)}function _t(e,n){t(`Content Creation - List Upcoming Publications`),lt(q(e,n)),a(`✓ Upcoming publications listed`)}async function vt(e,n){t(`Content Creation - Mark Publication Ready`),pt(await je(q(e,n))),a(`✓ Publication marked as ready`)}async function yt(e,t,n){if(e===`list-upcoming`){_t(t,n);return}if(e===`ready`){await vt(t,n);return}throw Error(`Unknown publication action: ${e}. Supported actions are: list-upcoming, ready`)}function Z(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 bt(e,t,n=S.cwd()){let i=Z(e),a=b(x(b(n,`resources`)),i),o=`${t}.md`,s=b(a,o);if(p(s))return r.info(`${o} already exists at: ${s}`),a;m(a,{recursive:!0}),r.success(`Created directory: ${a}`);let c={title:e,url:``,date:``};return v(s,C.stringify(``,c),`utf-8`),r.success(`Created ${o} at: ${s}`),a}async function xt(e,n){t(`Content Creation - Create Resource`);let r=await U(),i=await De();bt(r,i,q(e,n)),a(`✓ Resource created: resources/${Z(r)}/${i}.md`)}async function St(e,t,n){if(e===`create`){await xt(t,n);return}throw Error(`Unknown resource action: ${e}. Supported actions are: create`)}function Ct(e,t){let n=F(e);if(n.length===0)return;let r=t?n.filter(e=>e.name===t):n;if(r.length===0)throw Error(`Series not found: ${t}`);for(let e of r)z(e.path)}function wt(e,t,n){let i=L(e),a=Et(n,i.length),o=b(e,`${a}.${Z(t)}.md`);if(p(o))throw Error(`Article already exists at ${o}`);return Tt(e,i,a),v(o,C.stringify(``,{title:t}),`utf-8`),r.success(`Created article: ${o}`),z(e),o}function Tt(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=b(e,n);te(t.path,i),r.info(`Renamed ${t.fileName} → ${n}`)}}function Et(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}async function Dt(e,n,r){let i=q(n,r);if(e===`create-index`){t(`Content Creation - Create Series Index`),Ct(i,r?.series),a(r?.series?`✓ Series index generated for ${r.series}`:`✓ Series indexes generated successfully`);return}if(e===`introduce`){t(`Content Creation - Introduce Series Article`);let e=r?.title||await U(),n=r?.series?I(i,r.series):await G(i);if(!n)throw Error(`Series not found: ${r?.series}`);let o=r?.position?Number(r.position):await Pe(n.path);wt(n.path,e,o),a(`✓ Series article created: ${n.name}/${o}.${Z(e)}.md`);return}if(e===`estimate-time`){t(`Content Creation - Estimate Series Article Time`);let e=await D(),n=r?.series?I(i,r.series):await G(i);if(!n)throw Error(`Series not found: ${r?.series}`);let o=r?.file?R(n.path,r.file):await K(n.path);if(!o)throw Error(`Series article not found: ${r?.file}`);let s=Q(r?.wpm,e.reading.wordsPerMinute),c=dt(o.path,s);a(`✓ Estimated time updated for ${n.name}/${o.fileName}: ${c} min`);return}if(e===`ready`){t(`Content Creation - Mark Series Article Ready`);let e=await D(),n=r?.series?I(i,r.series):await G(i);if(!n)throw Error(`Series not found: ${r?.series}`);let o=r?.file?R(n.path,r.file):await K(n.path);if(!o)throw Error(`Series article not found: ${r?.file}`);let s=mt(o,Q(r?.wpm,e.reading.wordsPerMinute));a(`✓ Series article marked as ready and time updated for ${n.name}/${o.fileName}: ${s} min`);return}throw Error(`Unknown series action: ${e}. Supported actions are: create-index, introduce, estimate-time, ready`)}function Q(e,t){let n=e?Number(e):t??100;if(!Number.isFinite(n)||n<=0)throw RangeError(`Words per minute must be a positive number`);return n}async function Ot(e,t){let n=e.path,{data:i,content:a}=C(h(n,`utf-8`));i.scheduled===!0&&(r.warn(`This X publication is already scheduled. Skipping.`),S.exit(0)),(!t.scheduling.automationEndpoint||!t.scheduling.cfAccessClientId||!t.scheduling.cfAccessClientSecret)&&(r.error(`Scheduling configuration is missing. Please set AUTOMATION_ENDPOINT, CF_ACCESS_CLIENT_ID, and CF_ACCESS_CLIENT_SECRET in your .env file or config.`),S.exit(1));let o=new Date(e.date);o=d(o,11),o=f(o,0),o=ee(o,0);let s=o.getTime(),c={content:a.trim(),scheduleAt:s};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(c)});if(!e.ok){let t=await e.text();r.error(`Failed to schedule reminder: ${e.status} ${e.statusText}\n${t}`),S.exit(1)}let o=await e.json(),s={...i,scheduled:!0};v(n,C.stringify(a,s),`utf-8`);let u=new Date(o.scheduledAt);r.success(`✓ X publication reminder scheduled`),r.info(`Reminder scheduled for: ${l(u,`yyyy-MM-dd HH:mm:ss`)} UTC`)}catch(e){r.error(`Failed to schedule X publication reminder: ${e instanceof Error?e.message:String(e)}`),S.exit(1)}}async function kt(e,n){t(`Content Creation - Schedule X Publication Reminder`);let r=await D();await Ot(await ke(q(e,n)),r),a(`✓ X publication reminder scheduled`)}async function At(e,t,n){if(e===`schedule-reminder`){await kt(t,n);return}throw Error(`Unknown x action: ${e}. Supported actions are: schedule-reminder`)}const $=e(`content-creation`);$.command(`[path]`,`Create a dated content directory with content files`).option(`--path <path>`,`Base path for content directory (defaults to current directory)`).action(Fe),$.command(`resource <action> [path]`,`Manage resource markdown files`).option(`--path <path>`,`Base path for resource directory (defaults to current directory)`).action(St),$.command(`series <action> [path]`,`Manage article series and generated indexes`).option(`--path <path>`,`Base path for content directory (defaults to current directory)`).option(`--file <file>`,`Series article file name inside the selected series`).option(`--series <series>`,`Series folder name inside the series directory`).option(`--title <title>`,`Title of the new series article`).option(`--position <position>`,`Insert position in the series index (1-based)`).option(`--wpm <wpm>`,`Words per minute used to estimate reading time`).action(Dt),$.command(`index <action> [path]`,`Manage generated indexes`).option(`--path <path>`,`Base path for content directory (defaults to current directory)`).action(Ve),$.command(`linkedin <action> [path]`,`Manage LinkedIn-specific workflows`).option(`--path <path>`,`Base path for content directory (defaults to current directory)`).action(ct),$.command(`x <action> [path]`,`Manage X-specific workflows`).option(`--path <path>`,`Base path for content directory (defaults to current directory)`).action(At),$.command(`publication <action> [path]`,`Manage publication workflows`).option(`--path <path>`,`Base path for content directory (defaults to current directory)`).action(yt),$.help(),$.version(se),$.parse();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,99 +1,225 @@
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
-
3
- /**
4
- * Template configuration for LinkedIn
5
- */
5
+ type ContentType = (typeof CONTENT_TYPES)[number];
6
6
  interface LinkedInTemplateConfig {
7
- /**
8
- * Path to a template file for the footer (resolved relative to templatesDir or config file location)
9
- */
10
7
  footerPath?: string;
11
8
  }
12
- /**
13
- * Template configuration for YouTube/Instagram
14
- */
15
9
  interface VideoTemplateConfig {
16
- /**
17
- * Path to a template file for the description blueprint (resolved relative to templatesDir or config file location)
18
- */
19
10
  templatePath?: string;
20
11
  }
21
- /**
22
- * Scheduling configuration
23
- */
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
+ }
24
22
  interface SchedulingConfig {
25
- /**
26
- * Automation endpoint URL
27
- */
28
23
  automationEndpoint?: string;
29
- /**
30
- * Cloudflare Access Client ID
31
- */
32
24
  cfAccessClientId?: string;
33
- /**
34
- * Cloudflare Access Client Secret
35
- */
36
25
  cfAccessClientSecret?: string;
37
26
  }
38
- /**
39
- * Calendar publishing configuration
40
- */
41
27
  interface CalendarPublishingConfig {
42
- /**
43
- * Public base URL used to build the Google Calendar subscription link
44
- */
45
28
  publicUrl?: string;
46
- /**
47
- * Token appended to the public calendar subscription URL
48
- */
49
29
  token?: string;
50
30
  }
51
- /**
52
- * Reading-time estimation configuration.
53
- */
54
31
  interface ReadingConfig {
55
- /**
56
- * Reading speed used when estimating content duration.
57
- * @default 100
58
- */
59
32
  wordsPerMinute?: number;
60
33
  }
61
- /**
62
- * Configuration for content-creation
63
- */
64
34
  interface Config {
65
- /**
66
- * List of thematic areas
67
- * @default []
68
- */
69
35
  thematic?: string[];
70
- /**
71
- * Base directory for external template files
72
- */
73
36
  templatesDir?: string;
74
- /**
75
- * Templates configuration per content type
76
- */
77
37
  templates?: {
78
38
  linkedin?: LinkedInTemplateConfig;
79
39
  youtube?: VideoTemplateConfig;
80
40
  instagram?: VideoTemplateConfig;
81
41
  };
82
- /**
83
- * Scheduling configuration
84
- */
42
+ videoProcessing?: VideoProcessingConfig;
85
43
  scheduling?: SchedulingConfig;
86
- /**
87
- * Calendar publishing configuration
88
- */
89
44
  calendar?: CalendarPublishingConfig;
90
- /**
91
- * Reading-time configuration for content metadata helpers.
92
- */
93
45
  reading?: ReadingConfig;
94
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>;
95
221
  //#endregion
96
222
  //#region src/index.d.ts
97
223
  declare function defineConfig(config: Config): Config;
98
224
  //#endregion
99
- 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 e(e){return e}export{e as defineConfig};
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,8 @@
1
1
  {
2
2
  "name": "@barbapapazes/content-creation",
3
3
  "type": "module",
4
- "version": "0.18.9",
4
+ "version": "0.20.0",
5
+ "description": "CLI tool for content creation and management",
5
6
  "author": "Estéban Soubiran <esteban@soubiran.dev>",
6
7
  "license": "MIT",
7
8
  "funding": "https://github.com/sponsors/Barbapapazes",
@@ -18,7 +19,7 @@
18
19
  "import": "./dist/index.mjs"
19
20
  }
20
21
  },
21
- "main": ".dist/index.mjs",
22
+ "main": "./dist/index.mjs",
22
23
  "types": "./dist/index.d.mts",
23
24
  "bin": {
24
25
  "content-creation": "./dist/cli.mjs"
@@ -30,17 +31,18 @@
30
31
  "node": ">=24"
31
32
  },
32
33
  "dependencies": {
33
- "@clack/prompts": "^0.10.1",
34
+ "@clack/prompts": "^1.4.0",
34
35
  "c12": "^3.3.4",
35
- "cac": "^6.7.14",
36
+ "commander": "15.0.0-0",
36
37
  "date-fns": "^4.1.0",
37
38
  "gray-matter": "^4.0.3",
38
- "wrangler": "^4.61.1"
39
+ "wrangler": "^4.61.1",
40
+ "@barbapapazes/video-toolkit": "0.20.0"
39
41
  },
40
42
  "devDependencies": {
41
43
  "@tsconfig/node24": "^24.0.4",
42
44
  "@types/node": "^22.19.17",
43
- "tsdown": "^0.18.4",
45
+ "tsdown": "^0.22.0",
44
46
  "typescript": "^5.9.3"
45
47
  },
46
48
  "scripts": {