Resources
1Install
npx skillscat add brucehart/codex-skills/generate-story Install via the SkillsCat registry.
Generate Story (Codex)
You will be given:
- A story subject (prompt) for James.
- A folder path that may contain reference images and may include a date in the folder name.
- Optionally a markdown file name that may include a date.
Required environment variables:
REPLICATE_API_TOKENfor Replicate. Must be exported in the current environment (dotfiles may not be sourced).STORY_API_TOKENfor the worker automation API.STORY_API_BASE_URL(optional). If not set, usehttps://bedtimestories.bruce-hart.workers.dev.
Activate the Python venv for any steps that run Python:
source ~/scripts/.venv/bin/activateUse the following workflow exactly.
The image script prints the generated image path so it can be passed to the video script.
Preflight (fail fast)
Before doing anything else, verify the required env vars exist in the current shell environment:
python -c 'import os; assert os.getenv("REPLICATE_API_TOKEN"), "REPLICATE_API_TOKEN missing"'
python -c 'import os; assert os.getenv("STORY_API_TOKEN"), "STORY_API_TOKEN missing"'
command -v ffmpeg >/dev/null
command -v curl >/dev/nullStep 0: Resolve story date
- If the folder name contains a
YYYY-MM-DDdate, use that. - Else if the markdown file name contains a
YYYY-MM-DDdate, use that. - Else find the next date (including today) that does not already have a story:
- Use the story API token to query existing story days.
- Fetch a range starting today through today + 365 days (extend the range if needed).
- Pick the earliest date in that range that is not present in the calendar data.
Example:
START=$(date -u +%Y-%m-%d)
END=$(date -u -d "+365 days" +%Y-%m-%d)
curl -s "https://bedtimestories.bruce-hart.workers.dev/api/stories/calendar?start=$START&end=$END" \
-H "X-Story-Token: $STORY_API_TOKEN"The response includes { "days": [ { "day": "YYYY-MM-DD", "count": N }, ... ] }.
Choose the first date starting at START that is missing from the days list.
Helper script:
STORY_API_TOKEN=... \
STORY_API_BASE_URL=https://bedtimestories.bruce-hart.workers.dev \
python .codex/skills/generate-story/scripts/next-open-date.pyEnvironment variables:
STORY_API_TOKEN(required): story automation API token.STORY_API_BASE_URL(optional): defaults tohttps://bedtimestories.bruce-hart.workers.dev.STORY_CALENDAR_DAYS(optional): number of days to scan per request (default 365).STORY_TIMEZONE(optional): timezone used to interpret "today" (defaultAmerica/New_York).
Step 1: Write the story text (Markdown-compatible plain text)
Follow these instructions exactly:
For the given prompt, write a bedtime story including a title for my six year old son James. Make the text simple and phonics friendly for a beginning reader. Return the title separately and do not include it in the story content.
[Story Content]
In addition to Mom and Dad in his family, James has a three year old sister named Grace. He also has a small tuxedo cat named Trixie. In his family is also Granny and Grandpa Rick and Grandma and Grandpa Bruce. Only include people that are relevant to the story. Do not include people just to include them.
Do not use words in any image generated. Images should be in landscape format and have a cartoon aesthetic. Only include people in the images that are relevant to the scene of the story being visualized.
James has fair skin and short brown hair.
Mom has fair skin, shoulder length brown hair with blonde highlights and glasses.
Grace has long brown hair and fair skin.
Dad is bald and clean shaven with brown hair and thick dark eyebrows.
Granny is short with short gray hair.
Grandma has short blonde hair and glasses.
Grandpa Bruce is bald and clean shaven.
Grandpa Rick is bald, clean shaven and wears glasses.
Trixie is a black cat with short legs, white paws, black chin, black face and a white chest.
Story format requirements:
- Plain text paragraphs only. (Plain text with blank lines is valid Markdown; do not use Markdown formatting like headings/lists.)
- Do not include the title in the story content.
- Use short paragraphs separated by blank lines.
- Keep sentences short and easy to read.
Recommended: One-command run (Steps 0,2,3,4,5)
After you have the title and story content, put the content in a file (example: /tmp/story.txt) and run:
source ~/scripts/.venv/bin/activate
python .codex/skills/generate-story/scripts/run-generate-story.py \
--title "TITLE" \
--content-file /tmp/story.txtOptional flags:
--date YYYY-MM-DDto force a specific date instead of auto-selecting next open date.--story-id IDto update an existing story'simage_url/video_url(regenerates media, uploads it, thenPUTs the keys).--ref-image /path/to.jpg(repeatable) to guide the cover image with reference photos.--image-prompt "..."/--video-prompt "..."to override the default media prompts.--jsonto print a single JSON object to stdout.
Step 2: Generate a cover image (Replicate)
Default model: google/nano-banana-pro
Fallback model: black-forest-labs/flux-1.1-pro
Image requirements:
- Landscape, 16:9.
- 2K resolution.
- Cartoon aesthetic.
- No text, letters, or signage.
- Only include characters relevant to the selected scene.
- Incorporate reference images as guidance when available.
The script handles creating/polling Replicate predictions and downloading the generated image to /tmp.
Generate and save the image locally (example with optional reference images):
IMAGE_PATH=$(python .codex/skills/generate-story/scripts/generate-image.py \
--image "/path/to/reference-1.jpg" \
--image "/path/to/reference-2.png" \
"YOUR_IMAGE_PROMPT")Use --model if you need to override the default.
Step 3: Generate a short video (Replicate)
Default model: pixverse/pixverse-v5
Video requirements:
- 16:9 landscape.
- 5 seconds.
- Cartoon aesthetic.
- No text or letters.
- Use the generated cover image as a visual reference.
- Use the story to build a concise scene prompt.
- Use quality
540p, effectNone.
The script handles creating/polling Replicate predictions and downloading the generated video to /tmp.
Generate and save the video locally (example):
VIDEO_PATH=$(python .codex/skills/generate-story/scripts/generate-video.py \
"$IMAGE_PATH" \
"YOUR_VIDEO_PROMPT")Use --model if you need to override the default.
Re-encode the video for iPhone compatibility before uploading:
ffmpeg -y -i "$VIDEO_PATH" \
-c:v libx264 -profile:v high -level 4.0 -pix_fmt yuv420p \
-c:a aac -b:a 128k -movflags +faststart \
/tmp/story-video-encoded.mp4Step 4: Upload media to R2 via worker API
Base URL:
- If
STORY_API_BASE_URLis set, use that. - Else use
https://bedtimestories.bruce-hart.workers.dev.
Upload image:
curl -s "$STORY_API_BASE_URL/api/media" \
-H "X-Story-Token: $STORY_API_TOKEN" \
-F "file=@/path/to/image.jpg"Upload video:
curl -s "$STORY_API_BASE_URL/api/media" \
-H "X-Story-Token: $STORY_API_TOKEN" \
-F "file=@/tmp/story-video-encoded.mp4"Each response returns { "key": "..." }. Keep those keys.
Step 5: Create the story record
Send the story to the worker:
curl -s "$STORY_API_BASE_URL/api/stories" \
-H "X-Story-Token: $STORY_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "TITLE",
"content": "STORY_CONTENT",
"date": "YYYY-MM-DD",
"image_url": "IMAGE_KEY",
"video_url": "VIDEO_KEY"
}'Final output to user
Return:
- Title
- Story content (plain text)
- Image key
- Video key
- Story id