Build a script library from your own work, make it sound like you, then make and schedule two weeks of videos in one sitting.
Swipe or tap Next · checked on 2026-10-05
Same deal as part one. You paste one prompt into Claude Code, and Claude turns into your setup helper. It interviews you about your work, writes the scripts with you, and builds the new pieces right into your part-one folder. Your job is talking about what you know and saying yes or no. Claude does the typing.
~/content-engine), with at least one video made start to finish. Skipped it? The setup prompt sends you back there first. Sorry. It's worth it.No new accounts. It's the same starter stack as part one: about $73 a month at list prices. Prices change, so check each page before you buy.
Open the terminal (part one's guide shows how) and paste these two lines. If Claude asks whether you trust the folder, say yes.
Start Claude in your engine folder
cd ~/content-engine
claudeClick Copy, paste it into Claude Code, and press Enter. Claude Code may show it as one short line that says Pasted text. That's normal. It's long on purpose: it tells Claude how to talk to you, what it must never do, and the order to build things in. You don't have to read all of it. The safety rules are worth a look, though.
The setup prompt (copy all of it) · lines 1-3 of 66. Copy all copies the whole prompt; read it in full on the page.
Hi Claude. Please be my patient setup helper again.
Last time we built my content engine in this folder: one idea in, one finished, scheduled video out. Now I want two weeks of videos at a time. A library of scripts built from my own work, in my voice, planned two weeks at a time, made as one batch and scheduled in one sitting. I don't know how to code, so treat me like a smart beginner.Hi Claude. Please be my patient setup helper again.
Last time we built my content engine in this folder: one idea in, one finished, scheduled video out. Now I want two weeks of videos at a time. A library of scripts built from my own work, in my voice, planned two weeks at a time, made as one batch and scheduled in one sitting. I don't know how to code, so treat me like a smart beginner.
THE GUIDE
We're following this guide:
https://receiptsgroup.com/guides/two-weeks-of-videos
Its "manual way" sections are the source of truth for every file and command. When you need a file, download the page with curl (ask me first) and copy that file exactly from its code box. Each box is labeled with its file name, like engine/plan.py. Don't rewrite the scripts. Only fill in my details where the guide shows <angle brackets>. If you can't reach the page, tell me before you build anything from memory. Part one's guide is https://receiptsgroup.com/guides/ai-content-engine if you need it.
HOW TO TALK TO ME
- Plain words and short sentences. If you need a tech word, explain it in five words or less.
- Ask ONE question at a time. Then stop and wait for my answer.
- Before each step, tell me in one sentence what it does and why.
- Before each command, tell me in plain words what it does, so I know what I'm saying yes to.
- If I'm stuck, slow down and try another way. Never make me feel dumb.
SAFETY RULES (never break these)
1. Never ask me to paste a password, API key or secret into this chat. If I paste one by accident, tell me to delete that key and make a new one. Never show what's inside .env.
2. Ask me first before anything that costs money or credits, and before anything that posts in public. Do the free dry run first, every time.
3. Three steps wait for me to type an answer: the avatar renders (python3 engine/batch.py avatar), watching the videos (python3 engine/batch.py watch) and the real schedule (python3 engine/schedule.py --send). Never run those yourself. Tell me the exact command, I run it in a second terminal window, and you help me read what it says. The edit and captions steps run for a long time, so I run those there too.
4. AI disclosure stays on. Every video and every caption says it was made with an AI avatar.
5. My avatar is only me. Extra looks are new looks of me, never anyone else.
6. Scripts come only from my material and my answers. No made-up stories, numbers, results, customers or quotes. Leave out customer names and private details, even if they're in my files.
7. A script goes into a batch only after I've said ok to it. A video gets scheduled only after I've watched it and said yes.
8. Stay in this folder. Ask before you install anything, and tell me what it is in plain words.
THE PLAN
Go one step at a time. After each step, run any free check and tell me in one line if it passed. Keep a short checklist in SETUP-NOTES.md of what's done (no keys in it). If we stop partway, read it first next time and pick up where we left off.
Step 1. Check part one.
Look for CLAUDE.md, rules/VOICE.md, rules/GUARDRAILS.md, prompts/01-script.md to 03-captions.md, and engine/check.py, avatar.py and post.py. Run python3 engine/check.py on one of my runs/ folders. If part one isn't here or doesn't work, stop and help me finish part one first.
Step 2. Add the new pieces. (Guide: "Set up the new pieces".)
Make the sources, library and plans folders. Add the new line to CLAUDE.md. Copy rules/SHAPE.md, prompts/04-topics.md to 08-voice-pass.md, and engine/plan.py, batch.py and schedule.py from the guide. Show me the list of what you added.
Step 3. Gather my material. (Guide: "Build your script library from your own work", steps 1 and 2.)
Ask me what I've got: notes, how-tos, checklists, emails where I explained something, old posts, talks. Help me copy them into sources/ as text files. Remind me to leave out anything with a customer's name or private details. Then follow prompts/04-topics.md and show me the topics by series. I cut the ones that aren't me and add any you missed.
Step 4. Interview me. (Guide: "Build your script library from your own work", step 3.)
Follow prompts/05-interview.md: 50 questions in five rounds of ten, one at a time. I can stop between rounds and pick it up another day.
Step 5. My voice. (Guide: "Make it sound like you".)
Show me rules/VOICE.md and help me write my note at the top: how I want to sound, in my words. Then follow prompts/06-scripts.md for the first 10 topics, and prompts/07-review.md to go through them with me, one at a time. Every line I fix goes into my voice guide as a before-and-after pair. Once there are at least five pairs, follow prompts/08-voice-pass.md so the rest of the drafts match.
Step 6. Fill the library.
Repeat: 06 for the next 10, then 07 with me, then 08 when I've fixed a few more lines. Keep going until I've said ok to at least 14 scripts from at least 3 series. Ask if I want to stop for today between rounds.
Step 7. My looks. (Guide: "Plan two weeks", step 1.)
Ask if I have more than one HeyGen look of me. If I do, help me find their ids with heygen avatar looks list and put them in looks.txt. If not, skip it. One look works fine.
Step 8. The plan. (Guide: "Plan two weeks", step 2.)
Ask me which day to start (a few days out, so there's time to make everything) and what time I want to post. Run engine/plan.py. Show me the two weeks and ask if it looks right.
Step 9. The batch. (Guide: "Make the batch".)
Run python3 engine/batch.py to show me where everything is. Then walk me through the steps in my second terminal window: avatar (it spends HeyGen credits, so remind me first), edit, captions. They take a while. Tell me I can walk away, and how to pick up if one stops.
Step 10. Watch. (Guide: "Make the batch", step 5.)
I run python3 engine/batch.py watch and watch every video with sound, start to finish. Help me fix any I say no to.
Step 11. Schedule. (Guide: "Schedule the whole batch".)
Run python3 engine/schedule.py (the dry run). Explain anything that isn't ready, in plain words. When every day says ready, show me one day's full dry run with --show. Then I run --send myself. The AI label stays on.
Step 12. Wrap up.
Give me a short cheat sheet for the next batch: the commands in order, and the three things I always do myself (say ok to every script, watch every video, say yes before anything is scheduled).
Ready? Say hi and ask me your first question.How long: the library is the long part, an afternoon or two, and you can split it across days. After that, a batch is: plan it, let the computer make it, then watch and schedule the whole thing in one sitting.
What you'll have at the end: a library of scripts from your own work, a voice guide full of your own before-and-after examples, and two weeks of videos you've watched, approved and scheduled, with the AI label on.
Need a break? Close the window. When you're back, open the terminal and paste these two lines. Claude picks up where you left off; it keeps a checklist in SETUP-NOTES.md, too.
Pick up where you left off
cd ~/content-engine
claude --continueWant to see every step yourself? The manual way is below.
This is the manual way: every file and every command. Use it to do the whole thing by hand, or keep it open to check what Claude is doing. It's also what the setup prompt tells Claude to follow.
Part one was a straight line: one thought, one video. This puts a library in front of it and a batch around it. Each box is one job, and each one leaves a file behind, so when something comes out weird you can see which step did it.
rules/VOICE.md. A page of real examples: the stiff version, and how you'd actually say it.Batching makes it easy to post a lot. That's also the risk. In July 2025, YouTube renamed its “repetitious content” monetization policy to “inauthentic content,” to make clear it covers videos that are repetitive or mass-produced.
So every video still needs a real idea from your work, and the hooks, series and looks change day to day. That's why the planner rotates, and why you read every script and watch every video. Two weeks of the same video with new titles isn't a batch. It's a problem.
No new tools. This runs on part one's starter stack (Claude Code, HeyGen, HyperFrames and Upload-Post), about $73 a month at list prices. What's new is a few files and a lot more of you.
The library is the long part: an afternoon or two, and most of it is you talking about your work. After that, a batch is three moves: plan it (a second), let the computer make it (that part runs while you do something else), then watch and schedule it all in one sitting. You do the thinking once. The computer does the waiting.
Everything goes in your part-one folder: one new rule file, five prompts, three small scripts and a one-line change to CLAUDE.md. Here's where it all lands:
Repo layout (part one's files plus the new ones) · Part 1 of 2 · Copy copies all 29 lines
content-engine/
├── CLAUDE.md now also loads rules/SHAPE.md
├── rules/
│ ├── VOICE.md how you talk, plus your before-and-after pairs
│ ├── GUARDRAILS.md what never goes out (part one)
│ ├── SHAPE.md the six beats every script follows (new)
│ └── banned.txt (part one)
├── prompts/
│ ├── 01 to 03 part one: script, edit, captions
│ ├── 04-topics.md your material -> topics (new)
│ ├── 05-interview.md 50 questions -> more topics (new)
│ ├── 06-scripts.md topics -> draft scripts (new)
│ ├── 07-review.md you review the drafts; fixes go in VOICE.md (new)
│ └── 08-voice-pass.md rewrite the drafts to match VOICE.md (new)
├── engine/content-engine/
├── CLAUDE.md now also loads rules/SHAPE.md
├── rules/
│ ├── VOICE.md how you talk, plus your before-and-after pairs
│ ├── GUARDRAILS.md what never goes out (part one)
│ ├── SHAPE.md the six beats every script follows (new)
│ └── banned.txt (part one)
├── prompts/
│ ├── 01 to 03 part one: script, edit, captions
│ ├── 04-topics.md your material -> topics (new)
│ ├── 05-interview.md 50 questions -> more topics (new)
│ ├── 06-scripts.md topics -> draft scripts (new)
│ ├── 07-review.md you review the drafts; fixes go in VOICE.md (new)
│ └── 08-voice-pass.md rewrite the drafts to match VOICE.md (new)
├── engine/
│ ├── check.py, avatar.py, post.py part one
│ ├── plan.py two weeks from your approved scripts (new)
│ ├── batch.py part one's steps for every day (new)
│ └── schedule.py the whole batch to Upload-Post (new)
├── sources/ your material (new)
├── library/
│ ├── topics.md every topic, by series
│ ├── interview.md your answers, in your words
│ └── scripts.json the library: draft, approved or cut
├── looks.txt optional: your HeyGen look ids
├── plan.json the batch you're working on
├── plans/ finished batches
└── runs/
└── 2026-10-19-P01/ one folder per day, same as part oneRepo layout (part one's files plus the new ones) · Part 2 of 2 · Copy copies all 29 lines
│ ├── check.py, avatar.py, post.py part one
│ ├── plan.py two weeks from your approved scripts (new)
│ ├── batch.py part one's steps for every day (new)
│ └── schedule.py the whole batch to Upload-Post (new)
├── sources/ your material (new)
├── library/
│ ├── topics.md every topic, by series
│ ├── interview.md your answers, in your words
│ └── scripts.json the library: draft, approved or cut
├── looks.txt optional: your HeyGen look ids
├── plan.json the batch you're working on
├── plans/ finished batches
└── runs/
└── 2026-10-19-P01/ one folder per day, same as part oneMake the new folders
cd ~/content-engine
mkdir -p sources library plansThen add one line to CLAUDE.md, so the six-beat shape rides along on every Claude run, part one's single videos included:
CLAUDE.md
# My content engine
This folder turns one thought into a short vertical video and a scheduled post.
Every script, caption and word on screen follows these files:
@rules/VOICE.md
@rules/GUARDRAILS.md
@rules/SHAPE.md
Work only inside this folder. Never open, print or copy .env.# My content engine
This folder turns one thought into a short vertical video and a scheduled post.
Every script, caption and word on screen follows these files:
@rules/VOICE.md
@rules/GUARDRAILS.md
@rules/SHAPE.md
Work only inside this folder. Never open, print or copy .env.Every other new file sits in the section where you use it, in a code box labeled with its file name. Save each one as you go. None of them touch your accounts. If you keep this folder in git, add sources/ to .gitignore too: it's your private material.
Here's the thing about a content calendar you download: it's somebody else's calendar. My library came from my real work. I had AI dig through the systems I've built and the notes I keep, and that turned into 119 topics. Then I answered 50 interview questions about my story and how I work, and that added 20 more. 139 scripts, and every one of them traces back to something I actually did or think.
You'll do the same thing, smaller. Your files first, then an interview for everything you never wrote down.
Copy in anything where you explained your work in your own words: notes, how-tos, checklists, emails where you answered a question, old posts, the outline of a talk. More is better, and messy is fine. Plain text or Markdown works best.
Leave out anything with a customer's name or details, prices you charged someone, passwords or keys, and anything you'd mind seeing on a screen.
Check what's there
ls sources/Claude reads everything in sources/ and writes a topic list, grouped into a handful of series, each topic with the file it came from and a short quote. Then you read the list and cut anything that isn't really you.
Run it, then read the list
claude -p "Follow prompts/04-topics.md" --permission-mode acceptEdits --allowedTools "Read,Write,Edit"
cat library/topics.mdprompts/04-topics.md · Part 1 of 2 · Copy copies all 19 lines
Find video topics in my own material: every file in sources/ (my notes, how-tos,
emails I wrote, old posts, anything I made).
1. Read every file in sources/. If library/topics.md exists, read it too.
2. Pull out the ideas a short video could teach. For each one write:
- the idea in one line, in my words
- its series: 4-6 short series names that fit my work (reuse them, don't
make a new one for every topic)
- where it came from: the file name and a short quote from it
3. Rules:
- Only ideas that are really in my material. No invented stories, results,
numbers, customers or quotes.
- Skip anything with a customer's name or details, prices I charge someone,
passwords, or anything marked private.
- Two topics that teach the same lesson count as one.Find video topics in my own material: every file in sources/ (my notes, how-tos,
emails I wrote, old posts, anything I made).
1. Read every file in sources/. If library/topics.md exists, read it too.
2. Pull out the ideas a short video could teach. For each one write:
- the idea in one line, in my words
- its series: 4-6 short series names that fit my work (reuse them, don't
make a new one for every topic)
- where it came from: the file name and a short quote from it
3. Rules:
- Only ideas that are really in my material. No invented stories, results,
numbers, customers or quotes.
- Skip anything with a customer's name or details, prices I charge someone,
passwords, or anything marked private.
- Two topics that teach the same lesson count as one.
4. Write library/topics.md as a numbered list under a heading per series. If it
already exists, add the new topics at the end and don't repeat old ones.
Change no other file. End with one line: DONE <number of new topics> or FAILED <reason>.prompts/04-topics.md · Part 2 of 2 · Copy copies all 19 lines
4. Write library/topics.md as a numbered list under a heading per series. If it
already exists, add the new topics at the end and don't repeat old ones.
Change no other file. End with one line: DONE <number of new topics> or FAILED <reason>.Your files only cover what you wrote down. The interview gets the rest: your story, how you actually work, what people ask you, what you think your field gets wrong, and what you never want said. It's a conversation, so run it in a normal Claude session, not with -p. Answer like you're talking to a friend. Stop between rounds whenever you want; paste the same line next time and it starts at the next question.
Start the interview
cd ~/content-engine
claude
# then type: Follow prompts/05-interview.mdprompts/05-interview.md · Part 1 of 2 · Copy copies all 20 lines
Interview me so my scripts come from my real story and sound like me.
Ask me 50 questions in five rounds of ten, ONE question at a time. Wait for my
answer before the next one. Short, plain questions. Follow up when an answer
has a story in it.
Round 1: my story. How I got into this work, the turns, what I learned the hard way.
Round 2: how I actually do the work, step by step, and the tools I use.
Round 3: who I work with, what they ask me most, what they get wrong.
Round 4: my opinions. What my field overcomplicates, oversells or gets backwards.
Round 5: my lines. What I never talk about, words I'd never say, how I want to come across.
After each round:
- Save my answers in my exact words to library/interview.md (question, then answer).
- Add every new idea to library/topics.md with its series and the source
"interview, question <number>".Interview me so my scripts come from my real story and sound like me.
Ask me 50 questions in five rounds of ten, ONE question at a time. Wait for my
answer before the next one. Short, plain questions. Follow up when an answer
has a story in it.
Round 1: my story. How I got into this work, the turns, what I learned the hard way.
Round 2: how I actually do the work, step by step, and the tools I use.
Round 3: who I work with, what they ask me most, what they get wrong.
Round 4: my opinions. What my field overcomplicates, oversells or gets backwards.
Round 5: my lines. What I never talk about, words I'd never say, how I want to come across.
After each round:
- Save my answers in my exact words to library/interview.md (question, then answer).
- Add every new idea to library/topics.md with its series and the source
"interview, question <number>".
- Add phrases I use a lot to a "Phrases I use" list at the end of rules/VOICE.md.
- Ask if I want to keep going or stop for today. If I stop, the next time I paste
this prompt, read library/interview.md and start at the next question.
Never invent an answer or put words in my mouth. Change no other file.prompts/05-interview.md · Part 2 of 2 · Copy copies all 20 lines
- Add phrases I use a lot to a "Phrases I use" list at the end of rules/VOICE.md.
- Ask if I want to keep going or stop for today. If I stop, the next time I paste
this prompt, read library/interview.md and start at the next question.
Never invent an answer or put words in my mouth. Change no other file.Each run writes the next ten topics into library/scripts.json as drafts. Don't write all of them yet. Write ten, review them (next section), fix your voice guide, then write the rest. Otherwise you fix the same stiff line a hundred times.
Write ten drafts
claude -p "Follow prompts/06-scripts.md" --permission-mode acceptEdits --allowedTools "Read,Write,Edit"prompts/06-scripts.md · Part 1 of 2 · Copy copies all 20 lines
Write scripts for my library from library/topics.md.
1. Read library/topics.md, library/interview.md (if it's there) and
library/scripts.json (if it's there).
2. Take the next 10 topics that don't have a script yet.
3. For each one, write the script I'll say as my avatar. Follow CLAUDE.md and the
rules it imports: my voice, my guardrails and the six-beat shape. 60-110 words.
Use my own words from the source and the interview wherever they work.
No numbers, names or results that aren't in my material.
4. Add each one to library/scripts.json (a JSON list; make it if it's missing):
{"id": "letters and numbers only, e.g. P07",
"series": "the topic's series",
"title": "the hook as a title, 60 characters max",
"script": "the full script as plain text",
"source": "the topic's source line",Write scripts for my library from library/topics.md.
1. Read library/topics.md, library/interview.md (if it's there) and
library/scripts.json (if it's there).
2. Take the next 10 topics that don't have a script yet.
3. For each one, write the script I'll say as my avatar. Follow CLAUDE.md and the
rules it imports: my voice, my guardrails and the six-beat shape. 60-110 words.
Use my own words from the source and the interview wherever they work.
No numbers, names or results that aren't in my material.
4. Add each one to library/scripts.json (a JSON list; make it if it's missing):
{"id": "letters and numbers only, e.g. P07",
"series": "the topic's series",
"title": "the hook as a title, 60 characters max",
"script": "the full script as plain text",
"source": "the topic's source line",
"status": "draft",
"notes": ["anything you weren't sure about"]}
Don't change the scripts that are already there.
Change no other file. End with one line: DONE <the new ids> or FAILED <reason>.prompts/06-scripts.md · Part 2 of 2 · Copy copies all 20 lines
"status": "draft",
"notes": ["anything you weren't sure about"]}
Don't change the scripts that are already there.
Change no other file. End with one line: DONE <the new ids> or FAILED <reason>.My first drafts had a problem. They didn't sound like me. They sounded like a training manual. So I told the AI what I wanted: more casual and funny, and not so proper. That note is now the first rule in my voice guide.
But a note only gets you so far. “Casual” means something different to everybody. What actually works is examples. Every fix went into the guide: the stiff version, and how I'd actually say it, side by side. Then every script got rewritten to match. Here's one pair from mine:
Before: “The decision starts with the missing capability, not with how many subscriptions I can cancel.”
How I'd actually say it: “Could I spend my time making my own chat app? Probably. Do I need another project? Absolutely not.”
Same point. One sounds like a memo, the other sounds like me. Your guide needs your pairs, not mine.
Open rules/VOICE.md from part one. Under the first line, write how you want to sound, in one or two lines, in your words. Keep everything else that's there. Its “Before -> how I'd actually say it” list is where your pairs go, and part one's two example pairs can go once you have real ones.
rules/VOICE.md (the top of it)
# How I talk
My note: <how you want to sound, in your own words. Mine is "more casual
and funny, and not so proper.">
Who's talking: <your job title> at <your company> ...Read each draft out loud. Your ear catches what your eyes skip. Say ok, cut, or how you'd say it. Every fix goes into your voice guide as a pair, in your exact words. That's the step that makes it sound like you, and it's the one you can't hand off.
Start a review
cd ~/content-engine
claude
# then type: Follow prompts/07-review.mdprompts/07-review.md
Help me review my draft scripts in library/scripts.json, one at a time.
For each script with "status": "draft":
1. Show me its title and script. Ask me to read it out loud.
2. Wait for my answer:
- "ok": set its status to "approved".
- "cut": set its status to "cut".
- anything else is a fix: rewrite it my way and show me again.
3. Every time I fix a line, add the pair to the "Before -> how I'd actually say it"
list in rules/VOICE.md: your line, then my line, in my exact words.
4. After every five scripts, ask if I want to keep going.
Never approve a script I didn't say ok to. Change nothing else.Help me review my draft scripts in library/scripts.json, one at a time.
For each script with "status": "draft":
1. Show me its title and script. Ask me to read it out loud.
2. Wait for my answer:
- "ok": set its status to "approved".
- "cut": set its status to "cut".
- anything else is a fix: rewrite it my way and show me again.
3. Every time I fix a line, add the pair to the "Before -> how I'd actually say it"
list in rules/VOICE.md: your line, then my line, in my exact words.
4. After every five scripts, ask if I want to keep going.
Never approve a script I didn't say ok to. Change nothing else.After a review round with five or more new pairs, have Claude rewrite every draft that's still waiting. Then write the next ten (prompt 06), review them (07), and go again. The goal is fewer fixes every round.
Rewrite the waiting drafts
claude -p "Follow prompts/08-voice-pass.md" --permission-mode acceptEdits --allowedTools "Read,Write,Edit"prompts/08-voice-pass.md
My voice guide (rules/VOICE.md) has new before-and-after examples. Rewrite every
script in library/scripts.json with "status": "draft" so it sounds like them.
- Keep each script's idea, example and facts. Change how it sounds, not what it says.
- Keep the six-beat shape (rules/SHAPE.md) and 60-110 words.
- Leave "approved" and "cut" scripts exactly as they are.
- Add a note to each rewritten script saying what you changed.
Change no other file. End with one line: DONE <number rewritten> or FAILED <reason>.My voice guide (rules/VOICE.md) has new before-and-after examples. Rewrite every
script in library/scripts.json with "status": "draft" so it sounds like them.
- Keep each script's idea, example and facts. Change how it sounds, not what it says.
- Keep the six-beat shape (rules/SHAPE.md) and 60-110 words.
- Leave "approved" and "cut" scripts exactly as they are.
- Add a note to each rewritten script saying what you changed.
Change no other file. End with one line: DONE <number rewritten> or FAILED <reason>.The pairs teach Claude how you talk, not what happened. If a rewrite adds a story, a number or a result you never gave it, cut the line, not just the wording. And if it keeps doing it, add a rule to rules/GUARDRAILS.md.
Next, I wanted to know what actually works in my space. So we studied 98 short videos from 14 channels: each channel's top videos against its typical ones. Three things stood out:
My study: 98 Shorts from 14 channels, 70 top and 28 typical. It points a direction; it doesn't prove anything, and a top video is partly top because of its channel's audience.
So every script gets rebuilt in one shape: six beats. Here they are with the real lines from one of my videos, a short about testing your landing page on your own phone:
rules/SHAPE.md · Part 1 of 2 · Copy copies all 16 lines
# The shape of every script
Six beats, in this order. A beat can be one short sentence.
1. The hook. The point, a question someone asked me, or a result, in the
first sentence. No warm-up, no "in this video".
2. The idea. The one thing to do or know, said plainly, in the first few seconds.
3. An example. One real situation from my work, with a detail you can picture.
4. A surprise. A turn around the middle: the catch, the part people get
wrong, or a dry aside.
5. Something to copy. Exact words, a check or a step the viewer can use today.
6. The punchline. The last line pays off the hook. Don't trail off. Don't pitch.# The shape of every script
Six beats, in this order. A beat can be one short sentence.
1. The hook. The point, a question someone asked me, or a result, in the
first sentence. No warm-up, no "in this video".
2. The idea. The one thing to do or know, said plainly, in the first few seconds.
3. An example. One real situation from my work, with a detail you can picture.
4. A surprise. A turn around the middle: the catch, the part people get
wrong, or a dry aside.
5. Something to copy. Exact words, a check or a step the viewer can use today.
6. The punchline. The last line pays off the hook. Don't trail off. Don't pitch.
- One idea per video. Two ideas means two videos.
- 60-110 words (engine/check.py checks).
- Mix up the hooks. Two scripts in a row never open the same way.rules/SHAPE.md · Part 2 of 2 · Copy copies all 16 lines
- One idea per video. Two ideas means two videos.
- 60-110 words (engine/check.py checks).
- Mix up the hooks. Two scripts in a row never open the same way.CLAUDE.md loads it (see “Set up the new pieces”), so the drafts, the voice pass and part one's single scripts all follow it.
Same shape doesn't mean same video. Change the kind of hook (a question someone asked you, a result, a mistake), the example and the punchline every time. If two scripts in a row open the same way, rewrite one. Real-use prompt 4 below checks a whole batch for exactly that.
Now a planner lays out the two weeks. It follows two rules: the same series never runs two days in a row, and your looks take turns, so your feed never looks like the same video over and over. It takes a second and spends nothing.
A look is another HeyGen avatar of you: another outfit, another room. I rotate six. You don't need any extra: with one look (or no looks.txt), the planner skips this and every video uses your part-one avatar. Want more? Make new looks from your own photo in HeyGen (part one's guide covers it), then list their ids. Only you. Never anyone else's face.
Find your look ids
heygen avatar looks list # JSON: copy the id of each look of youlooks.txt
# One HeyGen look id per line. Anything after a # is a note.
<look id> # studio
<look id> # office
<look id> # patioIt only takes scripts you've marked approved. Each day gets the next script from the series that has waited longest (never yesterday's), and the next look in your list. It makes a run folder per day with that day's script.json, exactly like part one, and runs check.py on every one. A script that's already been used has a run folder, so it's skipped: the next batch never repeats this one.
Plan two weeks (made-up dates)
python3 engine/plan.py --start 2026-10-19 --weekend-time 10:30engine/plan.py · lines 1-15 of 117. Copy all copies the whole file; read it in full on the page.
#!/usr/bin/env python3
"""Plan a batch: one video a day from your approved scripts. The same series never runs two days in a row, and
your looks take turns. Writes plan.json and one run folder per day (with its script.json), then runs the
guardrail check on each day.
python3 engine/plan.py --start 2026-10-19 # 14 days from that date, 12:15 each day
python3 engine/plan.py --start 2026-10-19 --weekend-time 10:30 # a different time on Saturday and Sunday
python3 engine/plan.py --start 2026-10-19 --days 7 --time 18:00
python3 engine/plan.py --start 2026-10-19 --redo # throw away a plan you haven't rendered yet
Reads library/scripts.json (only scripts marked "approved") and looks.txt if you have one (one HeyGen look id
per line; anything after a # is a note). A script that already has a run folder is skipped, so the next batch
never repeats this one. Spends nothing and changes nothing outside plan.json and runs/.
"""
import argparse#!/usr/bin/env python3
"""Plan a batch: one video a day from your approved scripts. The same series never runs two days in a row, and
your looks take turns. Writes plan.json and one run folder per day (with its script.json), then runs the
guardrail check on each day.
python3 engine/plan.py --start 2026-10-19 # 14 days from that date, 12:15 each day
python3 engine/plan.py --start 2026-10-19 --weekend-time 10:30 # a different time on Saturday and Sunday
python3 engine/plan.py --start 2026-10-19 --days 7 --time 18:00
python3 engine/plan.py --start 2026-10-19 --redo # throw away a plan you haven't rendered yet
Reads library/scripts.json (only scripts marked "approved") and looks.txt if you have one (one HeyGen look id
per line; anything after a # is a note). A script that already has a run folder is skipped, so the next batch
never repeats this one. Spends nothing and changes nothing outside plan.json and runs/.
"""
import argparse
import datetime
import json
import shutil
import subprocess
import sys
from pathlib import Path
PLAN = Path("plan.json")
def looks():
p = Path("looks.txt")
if not p.exists():
return []
return [ln.split("#")[0].strip() for ln in p.read_text().splitlines() if ln.split("#")[0].strip()]
def used_ids():
"""Script ids that already have a run folder (runs/<date>-<id>)."""
return {d.name.split("-", 3)[3] for d in Path("runs").glob("*-*-*-*") if d.is_dir()}
def drop_old_plan():
"""--redo: remove the old plan and its run folders, but only if nothing has been made in them yet."""
runs = [Path(d["run"]) for d in json.loads(PLAN.read_text())]
made = [r.name for r in runs if r.exists() and {p.name for p in r.iterdir()} - {"script.json", "look.txt"}]
if made:
sys.exit("--redo only replaces a plan before anything is made. These days already have more than a "
"script: " + ", ".join(made))
for r in runs:
if r.exists():
shutil.rmtree(r)
PLAN.unlink()
def pick(todo, last_series, last_used):
"""The next script: never yesterday's series; among the rest, the series that has waited longest."""
options = [s for s in todo if s["series"] != last_series] or todo
if options is todo and last_series is not None:
print(f" note: only '{last_series}' scripts left, so it runs two days in a row")
return min(options, key=lambda s: (last_used.get(s["series"], -1), todo.index(s)))
def main():
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--start", required=True, help="the first day, e.g. 2026-10-19")
ap.add_argument("--days", type=int, default=14)
ap.add_argument("--time", default="12:15", help="posting time, 24-hour clock, e.g. 12:15 or 18:00")
ap.add_argument("--weekend-time", help="a different time on Saturday and Sunday")
ap.add_argument("--redo", action="store_true", help="replace a plan you haven't rendered yet")
a = ap.parse_args()
if PLAN.exists():
if not a.redo:
sys.exit("plan.json already exists. Finish that batch first, or add --redo to replace it.\n"
"(After a batch is scheduled, engine/schedule.py moves its plan into plans/.)")
drop_old_plan()
lib = json.loads(Path("library/scripts.json").read_text())
used = used_ids()
todo = [s for s in lib if s.get("status") == "approved" and s["id"] not in used]
if len(todo) < a.days:
sys.exit(f"only {len(todo)} approved scripts haven't been used yet; you asked for {a.days} days")
start = datetime.date.fromisoformat(a.start)
lk = looks()
plan, last_series, last_used = [], None, {}
for i in range(a.days):
day = start + datetime.timedelta(days=i)
s = pick(todo, last_series, last_used)
todo.remove(s)
last_series, last_used[s["series"]] = s["series"], i
weekend = day.weekday() >= 5
at = f"{day.isoformat()}T{a.weekend_time if weekend and a.weekend_time else a.time}"
run = Path("runs") / f"{day.isoformat()}-{s['id']}"
run.mkdir(parents=True)
(run / "script.json").write_text(json.dumps(
{"title": s["title"], "script": s["script"], "word_count": len(s["script"].split()),
"notes": [f"from library/scripts.json ({s['id']}, series {s['series']})"]}, indent=1))
entry = {"date": day.isoformat(), "at": at, "id": s["id"], "series": s["series"], "title": s["title"],
"run": str(run)}
if lk:
entry["look"] = lk[i % len(lk)]
(run / "look.txt").write_text(entry["look"] + "\n")
plan.append(entry)
PLAN.write_text(json.dumps(plan, indent=1))
fails = 0
for d in plan:
r = subprocess.run([sys.executable, "engine/check.py", d["run"]], capture_output=True, text=True)
result = r.stdout.strip().splitlines()[0] if r.stdout.strip() else "no answer"
fails += r.returncode != 0
when = datetime.date.fromisoformat(d["date"]).strftime("%a %b %d")
look = f" look {lk.index(d['look']) + 1}" if lk else ""
print(f"{when} {d['at'][11:]} {d['series']:<12} {d['id']:<10} {result}{look}")
if r.returncode:
print(" " + "\n ".join(r.stdout.strip().splitlines()[1:]))
print(f"\nplan.json: {len(plan)} days. " + (f"{fails} failed the check: fix those scripts, then run "
"python3 engine/check.py on their run folders." if fails else "Every script passed the check."))
if __name__ == "__main__":
main()What it prints (example, made up) · Part 1 of 2 · Copy copies all 16 lines
Mon Oct 19 12:15 pricing P01 PASS (84 words) look 1
Tue Oct 20 12:15 hiring H01 PASS (97 words) look 2
Wed Oct 21 12:15 tools T01 PASS (72 words) look 3
Thu Oct 22 12:15 mistakes M01 PASS (88 words) look 1
Fri Oct 23 12:15 story S01 PASS (103 words) look 2
Sat Oct 24 10:30 pricing P02 PASS (79 words) look 3
Sun Oct 25 10:30 hiring H02 PASS (91 words) look 1
Mon Oct 26 12:15 tools T02 PASS (66 words) look 2
Tue Oct 27 12:15 mistakes M02 PASS (95 words) look 3
Wed Oct 28 12:15 story S02 PASS (82 words) look 1
Thu Oct 29 12:15 pricing P03 PASS (77 words) look 2
Fri Oct 30 12:15 hiring H03 PASS (90 words) look 3
Sat Oct 31 10:30 tools T03 PASS (70 words) look 1
Sun Nov 01 10:30 mistakes M03 PASS (86 words) look 2Mon Oct 19 12:15 pricing P01 PASS (84 words) look 1
Tue Oct 20 12:15 hiring H01 PASS (97 words) look 2
Wed Oct 21 12:15 tools T01 PASS (72 words) look 3
Thu Oct 22 12:15 mistakes M01 PASS (88 words) look 1
Fri Oct 23 12:15 story S01 PASS (103 words) look 2
Sat Oct 24 10:30 pricing P02 PASS (79 words) look 3
Sun Oct 25 10:30 hiring H02 PASS (91 words) look 1
Mon Oct 26 12:15 tools T02 PASS (66 words) look 2
Tue Oct 27 12:15 mistakes M02 PASS (95 words) look 3
Wed Oct 28 12:15 story S02 PASS (82 words) look 1
Thu Oct 29 12:15 pricing P03 PASS (77 words) look 2
Fri Oct 30 12:15 hiring H03 PASS (90 words) look 3
Sat Oct 31 10:30 tools T03 PASS (70 words) look 1
Sun Nov 01 10:30 mistakes M03 PASS (86 words) look 2
plan.json: 14 days. Every script passed the check.What it prints (example, made up) · Part 2 of 2 · Copy copies all 16 lines
plan.json: 14 days. Every script passed the check.Does the order make sense? Does any week lean too hard on one series? Want a different start day or time? Run it again with --redo. That only works before anything's rendered, on purpose. Leave a few days between today and the first post, so the batch has time to render.
Then it makes them, one after another. Same steps as part one: the video of you, the edit with captions and graphics, the captions for each app. The difference is that batch.py does one step for every day before the next step, and skips anything that's already done. So if it stops (your Claude limit, a HeyGen hiccup, your laptop going to sleep), you run the same command again and it picks up where it left off.
engine/batch.py · lines 1-15 of 125. Copy all copies the whole file; read it in full on the page.
#!/usr/bin/env python3
"""Make every video in plan.json with part one's steps, one step at a time across the whole batch.
python3 engine/batch.py # where every day is (spends nothing)
python3 engine/batch.py avatar # render every missing avatar video (spends HeyGen credits; asks first)
python3 engine/batch.py edit # build and render every missing edit (Claude + HyperFrames; slow)
python3 engine/batch.py captions # write and check every missing set of captions
python3 engine/batch.py watch # play each finished video and ask you: post it or not?
Each step skips the days that already have what it makes, so you can stop any time and run it again.
A day with a look.txt (from engine/plan.py and your looks.txt) renders in that look.
"""
import json
import os
import shlex#!/usr/bin/env python3
"""Make every video in plan.json with part one's steps, one step at a time across the whole batch.
python3 engine/batch.py # where every day is (spends nothing)
python3 engine/batch.py avatar # render every missing avatar video (spends HeyGen credits; asks first)
python3 engine/batch.py edit # build and render every missing edit (Claude + HyperFrames; slow)
python3 engine/batch.py captions # write and check every missing set of captions
python3 engine/batch.py watch # play each finished video and ask you: post it or not?
Each step skips the days that already have what it makes, so you can stop any time and run it again.
A day with a look.txt (from engine/plan.py and your looks.txt) renders in that look.
"""
import json
import os
import shlex
import subprocess
import sys
from pathlib import Path
EDIT_TOOLS = "Read,Write,Edit,Skill,Bash(npx hyperframes *),Bash(ffprobe *),Bash(ffmpeg *)"
CAPTION_TOOLS = "Read,Write,Edit,Bash(python3 engine/check.py *)"
def ask(question):
"""Your typed answer, or None when nobody can type one (e.g. Claude running the step for you)."""
try:
return input(question).strip().lower()
except EOFError:
print("\nThis step needs your answer: run it yourself in a terminal window.")
return None
def days():
if not Path("plan.json").exists():
sys.exit("no plan.json yet: run engine/plan.py first")
return [Path(d["run"]) for d in json.loads(Path("plan.json").read_text())]
def run(cmd, env=None):
print(" $ " + " ".join(shlex.quote("python3" if c == sys.executable else c) for c in cmd), flush=True)
return subprocess.run(cmd, env=env).returncode == 0
def checks_ok(r):
return subprocess.run([sys.executable, "engine/check.py", str(r)], capture_output=True).returncode == 0
def status():
print(f"{'day':<26} {'check':<6} {'avatar':<7} {'edit':<5} {'captions':<9} you")
for r in days():
yes = lambda f: "yes" if (r / f).exists() else "-"
ok = (r / "approved.txt").read_text().strip() if (r / "approved.txt").exists() else "-"
print(f"{r.name:<26} {'PASS' if checks_ok(r) else 'FAIL':<6} {yes('avatar.mp4'):<7} {yes('final.mp4'):<5} "
f"{yes('captions.json'):<9} {ok}")
def avatar():
todo = [r for r in days() if not (r / "avatar.mp4").exists()]
bad = [r.name for r in todo if not checks_ok(r)]
if bad:
sys.exit("fix these scripts first (python3 engine/check.py runs/<day>): " + ", ".join(bad))
if not todo:
return print("every day already has its avatar video")
for r in todo:
print(f"{r.name}: {len(json.loads((r / 'script.json').read_text())['script'].split())} words")
if ask(f"\nRender {len(todo)} avatar videos with HeyGen? This spends credits. Type yes: ") != "yes":
return print("nothing rendered")
for r in todo:
env = dict(os.environ)
if (r / "look.txt").exists():
env["HEYGEN_AVATAR_ID"] = (r / "look.txt").read_text().strip()
if not run([sys.executable, "engine/avatar.py", str(r)], env):
sys.exit(f"stopped at {r.name}; run this step again to pick up from there")
def edit():
for r in days():
if (r / "final.mp4").exists() or not (r / "avatar.mp4").exists():
continue
print(f"\n{r.name}")
if not (r / "edit").exists() and not run(["npx", "hyperframes", "init", str(r / "edit"), "--video",
str(r / "avatar.mp4"), "--resolution", "portrait", "--non-interactive"]):
sys.exit(f"stopped at {r.name}: hyperframes init failed")
run(["claude", "-p", f"Using /hyperframes:hyperframes, follow prompts/02-edit.md. Run folder: {r}",
"--permission-mode", "acceptEdits", "--allowedTools", EDIT_TOOLS])
if not (r / "final.mp4").exists():
sys.exit(f"stopped at {r.name}: no final.mp4 (read Claude's last line above)")
def captions():
for r in days():
if (r / "captions.json").exists() or not (r / "final.mp4").exists():
continue
print(f"\n{r.name}")
run(["claude", "-p", f"Follow prompts/03-captions.md. Run folder: {r}",
"--permission-mode", "acceptEdits", "--allowedTools", CAPTION_TOOLS])
if not checks_ok(r):
sys.exit(f"stopped at {r.name}: the captions don't pass engine/check.py yet")
def watch():
opener = "open" if sys.platform == "darwin" else "xdg-open"
for r in days():
if not (r / "final.mp4").exists() or (r / "approved.txt").exists():
continue
print(f"\n{r.name}: {json.loads((r / 'script.json').read_text())['title']}")
try:
subprocess.run([opener, str(r / "final.mp4")])
except FileNotFoundError:
print(f" open {r / 'final.mp4'} yourself")
answer = ask(" Watched the whole thing, with sound? Post it? (yes / no / stop): ")
if answer in (None, "stop"):
break
if answer == "yes":
(r / "approved.txt").write_text("yes\n")
else:
print(" not approved: fix it (part one's Real use prompts help), then run watch again")
if __name__ == "__main__":
steps = {"status": status, "avatar": avatar, "edit": edit, "captions": captions, "watch": watch}
step = sys.argv[1] if len(sys.argv) > 1 else "status"
if step not in steps:
sys.exit(__doc__)
steps[step]()The avatar step, the watch step and the real schedule wait for you to type an answer, and the edit step runs for a long time. Run them yourself in a terminal window (open a second one if Claude Code is in the first). If Claude tries to run one for you, the script stops and says so.
A table: every day, whether its script passes the check, and which steps are done. Free. Run it any time.
Status
python3 engine/batch.pyIt lists every day that still needs its avatar video and how long each script is, then waits for you to type yes. A day with a look renders in that look; the rest use your part-one avatar. Avatar IV is named in every request, same as part one.
Render every missing avatar video
python3 engine/batch.py avatarThe slow one. For each day, it starts the HyperFrames project and runs part one's edit prompt with the same allowed tools, so Claude adds the captions, the title and a graphic or two, checks the project and renders final.mp4. Start it and go do something else.
Edit every video that's ready
python3 engine/batch.py editPart one's captions prompt, once per video, then the guardrail check. It stops on the first day whose captions don't pass, so nothing broken slips through.
Captions for every finished video
python3 engine/batch.py captionsIt opens each finished video and asks: post it? Yes, no or stop. Watch the whole thing, with sound, on your phone if you can. Yes saves an approved.txt in that day's folder, and nothing gets scheduled without one. No means fix it (part one's real-use prompts help) and run watch again. When the batch is done, I watch them and say yes. That part's not optional.
Watch and approve
python3 engine/batch.py watchNow, the posting. Part one's post.py already sends one video to all five apps through Upload-Post, with its own caption for each app and the AI label on every app that has one. schedule.py runs it for every day in the plan, at that day's time.
Run the dry run first. It checks every day (the video's there, the captions pass the check, you said yes, the time is still in the future) and lists what it would schedule. Nothing gets sent.
engine/schedule.py · lines 1-15 of 103. Copy all copies the whole file; read it in full on the page.
#!/usr/bin/env python3
"""Schedule the whole batch in plan.json through Upload-Post, each video at its own day and time, with part
one's engine/post.py.
python3 engine/schedule.py # dry run: checks every day and lists what it would schedule
python3 engine/schedule.py --show # the same, plus every field post.py would send
python3 engine/schedule.py --send # really schedule them (asks first)
(add --tz Europe/London if you're not on US Eastern)
A day goes out only if it has final.mp4, captions that pass engine/check.py, your yes from
`engine/batch.py watch` (approved.txt) and a time that's still in the future. A day that already has
sent.json is skipped, so running --send again never posts anything twice. Once every day is sent,
plan.json moves to plans/ and you're ready to plan the next batch.
"""
import argparse#!/usr/bin/env python3
"""Schedule the whole batch in plan.json through Upload-Post, each video at its own day and time, with part
one's engine/post.py.
python3 engine/schedule.py # dry run: checks every day and lists what it would schedule
python3 engine/schedule.py --show # the same, plus every field post.py would send
python3 engine/schedule.py --send # really schedule them (asks first)
(add --tz Europe/London if you're not on US Eastern)
A day goes out only if it has final.mp4, captions that pass engine/check.py, your yes from
`engine/batch.py watch` (approved.txt) and a time that's still in the future. A day that already has
sent.json is skipped, so running --send again never posts anything twice. Once every day is sent,
plan.json moves to plans/ and you're ready to plan the next batch.
"""
import argparse
import datetime
import json
import subprocess
import sys
from pathlib import Path
try:
from zoneinfo import ZoneInfo
except ImportError: # Python 3.8 and older
ZoneInfo = None
def ask(question):
"""Your typed answer, or None when nobody can type one (e.g. Claude running the step for you)."""
try:
return input(question).strip().lower()
except EOFError:
print("\nThis step needs your answer: run it yourself in a terminal window.")
return None
def problems(day, tz):
r = Path(day["run"])
out = []
if (r / "sent.json").exists():
return ["already sent"]
for f, why in (("final.mp4", "no final.mp4"), ("captions.json", "no captions.json"),
("approved.txt", "you haven't said yes (engine/batch.py watch)")):
if not (r / f).exists():
out.append(why)
if (r / "captions.json").exists() and subprocess.run(
[sys.executable, "engine/check.py", str(r)], capture_output=True).returncode:
out.append("engine/check.py fails")
when = datetime.datetime.fromisoformat(day["at"])
now = datetime.datetime.now(ZoneInfo(tz)).replace(tzinfo=None) if ZoneInfo else datetime.datetime.now()
if when <= now:
out.append(f"{day['at']} is in the past: change its time in plan.json")
return out
def main():
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--tz", default="America/New_York")
ap.add_argument("--show", action="store_true", help="print every field post.py would send")
ap.add_argument("--send", action="store_true", help="really schedule (default: dry run)")
a = ap.parse_args()
plan_file = Path("plan.json")
if not plan_file.exists():
sys.exit("no plan.json: run engine/plan.py first")
plan = json.loads(plan_file.read_text())
ready = []
for d in plan:
p = problems(d, a.tz)
print(f"{d['at'].replace('T', ' ')} {d['id']:<10} {d['title'][:44]:<44} {'ready' if not p else '; '.join(p)}")
if not p:
ready.append(d)
sent = sum((Path(d["run"]) / "sent.json").exists() for d in plan)
print(f"\n{len(ready)} ready to schedule, {sent} already sent, {len(plan) - len(ready) - sent} not ready ({a.tz}).")
if a.show:
for d in ready:
print(f"\n=== {d['run']} at {d['at']}", flush=True)
subprocess.run([sys.executable, "engine/post.py", d["run"], "--at", d["at"], "--tz", a.tz])
if not a.send:
print("Dry run: nothing sent. Add --send to schedule the ready ones.")
return
if not ready:
return
if ask(f"Schedule {len(ready)} videos on every app in captions.json, AI label on? Type yes: ") != "yes":
return print("nothing sent")
for d in ready:
print(f"\n=== {d['run']} at {d['at']}", flush=True)
subprocess.run([sys.executable, "engine/post.py", d["run"], "--at", d["at"], "--tz", a.tz, "--send"])
sent_file = Path(d["run"]) / "sent.json"
if not sent_file.exists():
print(" not sent (read post.py's message above)")
continue
for label, res in json.loads(sent_file.read_text()).items():
if not str(res.get("http", "")).startswith("2"):
print(f" check {label}: HTTP {res.get('http')} (see {d['run']}/sent.json)")
if all((Path(d["run"]) / "sent.json").exists() for d in plan):
Path("plans").mkdir(exist_ok=True)
plan_file.rename(Path("plans") / f"plan-{plan[0]['date']}.json")
print(f"\nEvery day is sent. The plan moved to plans/plan-{plan[0]['date']}.json.")
if __name__ == "__main__":
main()One line per day: ready, or exactly what's missing. Add --show to see every field post.py would send for each day: the captions, the time, the time zone and the AI flags.
Check the whole batch
python3 engine/schedule.py
python3 engine/schedule.py --showIt asks once, then schedules every ready day. Each day gets a sent.json with Upload-Post's replies, so running it again never sends a day twice. Once every day is sent, plan.json moves into plans/, and you're clear to plan the next batch. Times are read in US Eastern unless you add --tz, and Upload-Post takes dates up to a year out.
Schedule every ready day
python3 engine/schedule.py --sendThe first call lists everything scheduled on your profile. Need to move a day? Cancel its jobs with the second call, one job id at a time (one per app group; the ids are in the list), then schedule that day again by hand with part one's post.py and a new time. Upload-Post can also change a scheduled job's date, title or caption; its scheduled-posts page has that call.
List, cancel, re-send one day
set -a; source .env; set +a
curl -s -H "Authorization: Apikey $UPLOAD_POST_API_KEY" "https://api.upload-post.com/api/uploadposts/schedule?profile_username=$UPLOAD_POST_PROFILE"
curl -s -X DELETE -H "Authorization: Apikey $UPLOAD_POST_API_KEY" https://api.upload-post.com/api/uploadposts/schedule/<job_id>
python3 engine/post.py runs/<day> --at 2026-10-24T18:00 --sendset -a; source .env; set +a
curl -s -H "Authorization: Apikey $UPLOAD_POST_API_KEY" "https://api.upload-post.com/api/uploadposts/schedule?profile_username=$UPLOAD_POST_PROFILE"
curl -s -X DELETE -H "Authorization: Apikey $UPLOAD_POST_API_KEY" https://api.upload-post.com/api/uploadposts/schedule/<job_id>
python3 engine/post.py runs/<day> --at 2026-10-24T18:00 --sendAnd there it is. Two weeks, every slot filled. Approve it once, and you're done until the numbers come back.
Every job post.py sends carries the AI flag, and Upload-Post turns it into each app's own label where the app has one (part one's guide has the table). Threads has no flag, so the caption has to say it, and part one's captions prompt does. Upload-Post's docs are blunt about it: labeling accurately is the publisher's job. That's you.
Start claude inside your engine folder and paste one of these. Claude will ask before it changes anything, which is fine: you're right there.
1 · New topics from this week's notes
I added sources/this-week.md. Follow prompts/04-topics.md for that file only, then show me just the new topics. Don't write any scripts yet.2 · A second hook for every draft
For each script in library/scripts.json with "status": "draft", write a second hook of a different kind (a question someone asked me, a result, a mistake, a count). Show me both hooks side by side and ask which one to keep. Change a script only after I pick, and keep it 60-110 words.For each script in library/scripts.json with "status": "draft", write a second hook of a different kind (a question someone asked me, a result, a mistake, a count). Show me both hooks side by side and ask which one to keep. Change a script only after I pick, and keep it 60-110 words.3 · Find the stiff lines
Read every approved script in library/scripts.json and my voice guide, rules/VOICE.md. List the ten lines that sound least like my before-and-after pairs: the script id, the line, and how I'd more likely say it. Don't change anything.4 · Check the batch for sameness
Read the script.json in every run folder listed in plan.json. Tell me which hooks open the same way, which two scripts teach nearly the same thing, and which endings trail off instead of landing a punchline. One line each: the date, the problem, a fix. Don't change any files.Read the script.json in every run folder listed in plan.json. Tell me which hooks open the same way, which two scripts teach nearly the same thing, and which endings trail off instead of landing a punchline. One line each: the date, the problem, a fix. Don't change any files.5 · Captions that don't repeat
Read the captions.json in every run folder listed in plan.json. Flag any two captions on the same app that open the same way or ask the same question. Rewrite the later one in my words from its script, then run python3 engine/check.py on that run folder and fix anything it flags.Read the captions.json in every run folder listed in plan.json. Flag any two captions on the same app that open the same way or ask the same question. Rewrite the later one in my words from its script, then run python3 engine/check.py on that run folder and fix anything it flags.Number 4 is the one I'd never skip. Fourteen videos in a row is exactly where the sameness sneaks in.
The same as part one: about $73 a month at list prices for the starter stack. Part one's guide has the breakdown by tool. Prices change, so check each page before you buy.
A batch doesn't add a tool. It uses more of what you already pay for:
Honest part again. Here's what's in mine that isn't in this guide:
Start with the plain version. Add the stricter rules the first time two videos in a row look like twins.
Your sources are too thin. Add more of your own writing, especially anything where you explained something to someone. Or do the interview first, then run the topics prompt again.
Check that your voice guide has real pairs, not adjectives. Then run real-use prompt 2 on the drafts and prompt 4 on the batch: different hooks, different examples, real punchlines.
Your guide needs more pairs. Review another ten drafts (prompt 07), fix every line that bugs you, then run the voice pass (08) again.
Cut the line. If it's a phrase, add it to rules/banned.txt. If it's a habit, add a rule to rules/GUARDRAILS.md, like “Never mention a customer unless my material does.”
plan.py: only N approved scripts haven't been used yetApprove more (prompt 07), or plan fewer days with --days.
plan.py: only one series left, so it runs two days in a rowYour approved scripts lean on one series. Approve some from other series, then plan again with --redo.
plan.py: plan.json already existsYou're mid-batch. Finish it (schedule.py moves it to plans/ once every day is sent), or add --redo if nothing's been rendered yet.
--redo says some days already have more than a scriptSomething's been rendered, and the planner won't throw that away. Finish the batch, or fix the one day by hand.
Open that day's runs/<day>/script.json, fix the script, and run python3 engine/check.py runs/<day>. Fix it in library/scripts.json too, so the library stays right.
batch.py stops partwayRead its last line. Then run the same step again; it skips every day that's done. HeyGen exit codes and HyperFrames errors are in part one's “When it breaks”.
Claude (or a script) tried to run a step that waits for you. Open a terminal, cd ~/content-engine, and run that command yourself.
schedule.py: a day's time is in the pastChange that day's "at" in plan.json to a future time, same format (like 2026-10-24T18:00), then run it again.
The other apps on that day went through, so don't send the whole day again (that would post twice). Fix the cause (part one's “When it breaks” lists Upload-Post's errors), then post that one app by hand in Upload-Post.
Rotate more: more series, more looks, different kinds of hooks. One good week beats two weeks of the same video.
The same as part one, plus your material: Claude Code sends what it reads from sources/ and your interview answers to Anthropic to work on them, and your library stays in this folder on your computer. Disconnecting works the same as part one, too.
What each page backs up is listed at the end of the full guide.
Your library, your voice, two weeks planned, made, watched and scheduled. Then you're done until the numbers come back. You do the thinking once. The computer does the waiting.
Part one: one idea to a finished, scheduled video, if you haven't built the engine yet. Next time: how the critic decides what's broken.
Want help setting this up? Receipts Group builds these systems.
Open the full guide as a page (every file in full, plus the table of contents).