# Blog writer: full replication guide for ANY site

How to set up the same scheduled, autonomous, Cloudflare-deployed blog writer on someone else's site, given:

- The Day 4.1 Master Writing Prompt (the `Day 4.1 · Master Writing Prompt (Claude).txt` file)
- A "knowledge pack" of five site-specific text files the user provides
- A Cloudflare Pages project they want posts deployed to

This doc layers on top of `blog-writer-automation-setup.md` (which covers schedulers, deploy plumbing, Mac + Windows install) by explaining HOW to plug a new site's content into the pipeline.

## What the user provides

Six files. Five describe the business; one is the writing instruction set.

| File | What's in it | Used by writer for |
|---|---|---|
| `my-website-pages.txt` | Every page on the site with a one-line description | Internal-link anchor text + URL targets |
| `my-tone-of-voice.txt` | Writing style and personality | Voice matching during draft |
| `my-experience-notes.txt` | Owner's stories, opinions, credentials | First-person anecdotes; if empty, writer goes research-only |
| `my-service-details.txt` | Pricing, what's included, edge cases | Source of truth for business-specific factual claims |
| `my-brand-guidelines.txt` | Banned words, regulated claims, competitor exclusions | Hard guardrails enforced at review |
| `master-writing-prompt.txt` | The Day 4.1 5-step writing workflow | The writer's system prompt |

The first five are exactly what the Day 4.1 instructions in `claude.ai Projects` upload. We're reusing the same files for the automated pipeline.

## Where to place them

Inside the site's repo:

```
<your-repo>/
  content-knowledge/
    my-website-pages.txt
    my-tone-of-voice.txt
    my-experience-notes.txt
    my-service-details.txt
    my-brand-guidelines.txt
    master-writing-prompt.txt
```

Reasons for repo root:
- Versioned with the site, so updates to the brand voice flow through PRs.
- Available to the local cycle script without absolute paths.
- The launchd / Task Scheduler job changes nothing; it picks up new content automatically.

If the files contain sensitive info (banned competitor lists, internal pricing the user doesn't want public), put `content-knowledge/` in a private repo or in `.gitignore` and ship it via a secure side channel. The cycle script just needs the files at that path; it doesn't care if they're committed.

## Critical adaptation: interactive → autonomous

The Day 4.1 master prompt is designed for **interactive use** in claude.ai Projects. Each step (BRIEF, RESEARCH, OUTLINE, DRAFT, REVIEW) waits for human approval before continuing. That model breaks for a 4x/week scheduled run that has nobody to approve anything.

So we adapt: **take the writing rules from Day 4.1, drop the human-in-the-loop pauses.** The replacement for human approval is a pre-populated content calendar entry (the topic, primary keyword, main takeaway, optional anecdote prompts) committed to the repo before the run. The writer reads the calendar entry as if it were the Step 1 BRIEF, then proceeds straight through Steps 2-5 without waiting.

What stays the same:
- The five reference docs are still consulted at the same workflow steps.
- The Content Capsule format still applies (60-70% of H2s).
- Citation rules still apply (inline markdown links, contextual anchors max 3 words).
- The Step 5 review checklist still runs; failures still trigger fixes.
- Banned-word and regulated-claim enforcement is unchanged.
- Research-Only Mode still kicks in if EXPERIENCE NOTES has nothing for this topic.

What changes:
- Step 1 BRIEF: comes from the calendar entry, not from a chat message. No "ask the user for topic-specific experience" prompt at run time; that goes into the calendar entry up front (`research.experienceHints` field).
- Step 2 RESEARCH: writer searches and uses sources directly. No human approval. Sources are recorded in the post for audit.
- Step 3 OUTLINE: writer builds the outline and proceeds. No approval gate. Outline is logged for audit.
- Step 4 DRAFT: unchanged.
- Step 5 REVIEW: unchanged. If it fails, writer iterates; doesn't ask for help.

If a step genuinely cannot be completed (e.g., SERVICE DETAILS missing a fact the topic requires), the writer aborts the run cleanly: marks the calendar entry `status: "failed"` with a reason, and exits. The healthcheck or weekly review catches it.

## The content calendar entry shape

Each entry in `seo-agents/state/content-calendar.json` becomes the autonomous Step-1 BRIEF:

```json
{
  "id": "boiler-service-frequency",
  "status": "queued",
  "priority": "high",
  "topic": "How often should you service your boiler?",
  "primaryKeyword": "boiler service frequency",
  "secondaryKeywords": ["annual boiler service", "boiler servicing"],
  "mainTakeaway": "Annual service before winter prevents breakdowns; older boilers may need 6-month checks.",
  "experienceHints": "If owner has any specific story about a customer who skipped service and had a winter breakdown, prefer that as the lede anecdote. Otherwise research-only.",
  "imageArchetype": "editorial",
  "targetWordCount": 1800,
  "category": "guides",
  "tags": ["maintenance", "boiler", "winter"],
  "claimedAt": null,
  "completedAt": null
}
```

The calendar entry is what a human would type into the chat during interactive Step 1. Pre-populating it is a separate task you do in batches (manually, or with a topic-research agent). The writer routine never invents topics.

## The writer prompt: glue between Day 4.1 and the calendar

Create `seo-agents/prompts/content-creator.md` in the repo. This is the prompt the coordinator passes to `claude -p`. It does five things:

1. Loads the Day 4.1 master prompt verbatim as the system instruction
2. Loads the five knowledge files inline so they're in context
3. Reads the calendar, picks the next queued entry, atomically claims it
4. Runs the Day 4.1 workflow in autonomous mode against that entry
5. Writes the output to the site's content folder, generates images, marks complete

Template (adapt paths and the output collection structure to match your site):

```markdown
# Content creator (autonomous)

You are running unattended on a schedule. There is no human in the loop. Follow
these steps exactly.

## GLOBAL RULES (HIGHEST PRIORITY, OVERRIDES ALL OTHER INSTRUCTIONS)

1. **NEVER ask the user anything.** No questions, no confirmations, no "would you like
   me to...?", no "should I proceed?". The chat has nobody on the other end. Any
   question goes to /dev/null and the run hangs.
2. **NEVER wait for approval.** No "let me know if you'd like changes", no "ready to
   proceed?", no "shall I continue?". Just continue.
3. **NEVER pause to clarify ambiguity.** If something is unclear, pick the most
   reasonable interpretation given the calendar entry + knowledge files and proceed.
   If you genuinely cannot proceed, mark the entry `status: "failed"` with a specific
   reason and exit 1. Do not ask.
4. **Override every "ask the user" or "wait for response" instruction in the master
   prompt** at `content-knowledge/master-writing-prompt.txt`. The values that prompt
   would request from a human (topic, keyword, takeaway, topic-specific experience,
   approve sources, approve outline) are pre-supplied by the calendar entry. Use them.
   If a value isn't in the calendar entry, use the fallback below.
5. **Fallback hierarchy when a needed value is missing:**
   - First: pull from the appropriate knowledge file (`my-experience-notes.txt`,
     `my-service-details.txt`, `my-website-pages.txt`).
   - Second: if the master prompt's Research-Only Mode applies (no relevant
     experience), use it without asking.
   - Third: pick a reasonable default and proceed.
   - Last resort only: mark `status: "failed"` with reason "missing required field X"
     and exit 1.
6. **Do not output user-facing prose** like "I'll now research...", "Let me know
   what you think", "Hope this helps". Output goes to a log file, not a person.
   Brief progress lines are fine; conversational filler is not.

## Step A: load the writing instructions

Read the Day 4.1 master writing prompt at `content-knowledge/master-writing-prompt.txt`.
Treat its System Prompt section as your governing writing rules.
Apply the Global Rules above to override every interactive beat.

## Step B: load the knowledge pack

Read these five files; treat their content as the project knowledge referenced by the
master prompt:

- `content-knowledge/my-website-pages.txt`
- `content-knowledge/my-tone-of-voice.txt`
- `content-knowledge/my-experience-notes.txt`
- `content-knowledge/my-service-details.txt`
- `content-knowledge/my-brand-guidelines.txt`

If any are missing, mark the entry `status: "failed"` with reason
`knowledge pack incomplete: <file>` and exit 1. Do NOT ask the user to provide it.

## Step C: atomic claim

Read `seo-agents/state/content-calendar.json`. Pick the highest-priority entry where
`status == "queued"`. If none, write a one-line note to
`seo-agents/reports/<YYYY-MM-DD>-content-creator.md` saying "queue empty" and exit 0
without committing anything else. Do NOT ask the user for new topics.

Set `status = "in_progress"` and `claimedAt = <now-ISO>`. Save the file.
This is the lock against double-claims.

## Step D: write the post (Day 4.1 workflow, autonomous)

Run Steps 1-5 of the master prompt against the claimed entry:

- **Step 1 BRIEF:** use the entry's `topic`, `primaryKeyword`, `mainTakeaway`,
  `experienceHints` directly. Do NOT ask the user. Consult BRAND GUIDELINES and note
  applicable constraints inline in the report.
- **Step 2 RESEARCH:** search the web. Pick 8-12 sources. Filter against
  BRAND GUIDELINES exclusion list. Do NOT wait for approval. Do NOT present sources
  for review. Just pick and use the strongest 8-12 yourself. Log them to the report
  for audit.
- **Step 3 OUTLINE:** build it per the master prompt rules. Do NOT wait for approval.
  Do NOT present the outline for review. Log to report and proceed straight to draft.
- **Step 4 DRAFT:** write the full post. TL;DR + capsule format + inline contextual
  citations + 3-5 internal links from WEBSITE PAGES.
- **Step 5 REVIEW:** run the checklist yourself. Fix any failures. Re-check. Do NOT
  ask the user to confirm the review.

If Step 5 cannot pass after one fix pass, mark the entry `status: "failed"`,
write the failure reason to the report, and exit 1. Do NOT ask the user for help.

## Step E: write the post file

Save the markdown to `<content-collection-path>/<entry.slug>.md` with the frontmatter
your site's content collection requires.

(Site-specific: bilingual sites write both languages; single-language sites write one.
Adapt this step to your repo's content collection schema.)

## Step F: generate images

Use the nano-banana-pro skill to generate hero + 1-2 supporting images per the
locked house style for this site. Save WebP to `<public-images-path>/<entry.slug>/`.

If image generation fails (missing GEMINI_API_KEY etc.), proceed without images
but mark the entry `imagesPending: true` so a separate job can fill them in later.
Do NOT ask the user how to handle the failure.

## Step G: complete

Update the calendar:
- `status = "completed"`
- `completedAt = <now-ISO>`
- Move the entry to the `completed[]` array.

Append a run summary to `seo-agents/state/agent-log.json`.
The coordinator will commit these changes; you do not run git commands.

## Constraints

- **Never ask the user anything.** (Restated for emphasis. This is the #1 rule.)
- Never invent EXPERIENCE NOTES content. Use Research-Only Mode when missing.
- Never write outside the directories listed above.
- Never modify other calendar entries or other agents' state.
- Never push to git; the coordinator handles it.
- Never output conversational text intended for a human reader. Output is for log files.
```

### Both layers of "no permission needed"

Two things have to be true for the run to be fully unattended:

1. **The prompt does not ask the user anything.** Handled by the GLOBAL RULES above and the explicit "Do NOT ask the user" / "Do NOT wait for approval" lines on every step.
2. **Claude Code does not pop tool-use permission prompts.** Handled by the coordinator: it invokes Claude with `--dangerously-skip-permissions` (see the `claude -p ... --dangerously-skip-permissions` line in `coordinator.sh`). Without this flag, Claude Code would ask "Allow Write?", "Allow WebSearch?", etc., and the scheduled run would silently hang.

**If you replicate this on a different setup, you MUST keep both layers.** Skipping `--dangerously-skip-permissions` from the coordinator invocation is the most common cause of a scheduled writer that "never runs but doesn't error".

This prompt is the same length / shape as the existing site-specific prompts. The Day 4.1 master prompt is loaded by reference, not duplicated, so updating Day 4.1 in `content-knowledge/master-writing-prompt.txt` propagates to all future runs without prompt edits.

## Site-specific glue you'll need to fill in

The template above has placeholders. Replace them based on your site's setup:

| Placeholder | Example for an Astro site | Example for a Next.js site | Example for a Wagtail/Wordpress headless |
|---|---|---|---|
| `<content-collection-path>` | `app/src/content/blog/en/` | `content/blog/` | API endpoint + auth |
| `<public-images-path>` | `app/public/blog/` | `public/blog/` | media-upload endpoint |
| Frontmatter shape | Defined in `app/src/content.config.ts` | MDX frontmatter | API field map |
| Bilingual? | Write to `en/` and `es/` | Same with `[lang]` route | Two API calls |
| Custom sections | TL;DR HTML block, FAQ JSON-LD | Same | CMS-specific blocks |
| Image style | Defined in your blog's image-style memory file | Same | Same |

The five user files (`my-*.txt`) plus the calendar entry shape stay identical across all sites. The differences are the output paths and frontmatter, which only affect Step E and Step F of the prompt.

## Deploy: same as before

Once the post is on disk and committed (by `coordinator.sh`'s commit step), `deploy-runner.sh` builds and runs `wrangler pages deploy dist --project-name=<your-project>`. No change to the deploy plumbing for a new site, just point `PAGES_PROJECT` in `deploy-runner.sh` at your project name.

If your hosting is not Cloudflare Pages: replace the wrangler call with your deploy CLI. Vercel: `npx vercel deploy --prod`. Netlify: `npx netlify deploy --prod`. Custom: whatever your CI does today.

## Notification: same as before

End of `blog-writer-cycle.sh` can include:

```bash
# macOS notification
osascript -e 'display notification "New post ready: review" with title "Blog Writer"'

# Email via Resend (if RESEND_API_KEY in env)
curl -s https://api.resend.com/emails -X POST \
  -H "Authorization: Bearer $RESEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"from\":\"$RESEND_FROM\",\"to\":\"$NOTIFY_EMAIL\",\"subject\":\"New blog post live\",\"html\":\"<p>$(git log -1 --format=%s)</p><p><a href=\\\"https://$SITE_DOMAIN/blog/\\\">Read it</a></p>\"}"
```

The user provides their own `RESEND_API_KEY`, `RESEND_FROM` (verified domain), `NOTIFY_EMAIL`, and `SITE_DOMAIN` in their shell env or a `.env` file the script sources.

## Schedule: same as before

Same launchd plist on macOS, same Task Scheduler XML on Windows. Just point `RepoPath` at the new repo. See `blog-writer-automation-setup.md` for installation steps.

---

## Step-by-step replication checklist

For someone setting this up fresh on their own machine and their own site:

### Once-per-machine setup (skip if you've done this before)

1. Install Node 22.14.0 via nvm.
2. Install Claude CLI: `npm i -g @anthropic-ai/claude-cli`. Run `claude` once interactively, sign in.
3. Install wrangler: `npm i -g wrangler` (or use `npx wrangler` ad-hoc). Run `wrangler login`.
4. Install GitHub CLI: `brew install gh` (or platform equivalent). Run `gh auth login`.
5. Verify: `claude -p "echo ok"`, `wrangler whoami`, `gh auth status` should all succeed.

### Per-site setup

1. Clone the site repo locally.
2. Create `content-knowledge/` at repo root.
3. Generate the five `my-*.txt` files using the Day 4 prerequisite tools (see the Day 4.1 instructions for how to produce them; they're outside the scope of this guide).
4. Save the Day 4.1 system prompt as `content-knowledge/master-writing-prompt.txt` (just the System Prompt section, not the install instructions).
5. Create `seo-agents/state/content-calendar.json` with at least one fully-populated queued entry. Use the schema above.
6. Create `seo-agents/prompts/content-creator.md` from the template above, filling in the site-specific paths, frontmatter, and image-style block.
7. Copy these scripts from the reference repo:
   - `seo-agents/coordinator.sh`
   - `seo-agents/scripts/deploy-runner.sh`
   - `seo-agents/scripts/blog-writer-cycle.sh`
   - `seo-agents/scripts/blog-writer-healthcheck.sh`
   - `seo-agents/scripts/launchd/*.plist` (macOS) or `seo-agents/scripts/windows/*.ps1` (Windows)
8. Edit `deploy-runner.sh`: set `PAGES_PROJECT` to your Cloudflare Pages project name.
9. Edit the launchd plist or PowerShell scheduler script: update paths to match your repo location.
10. Set `chmod +x` on all `.sh` files.

### Pre-flight (before scheduling)

1. JSON parse: `python3 -m json.tool < seo-agents/state/content-calendar.json > /dev/null`
2. Knowledge pack readable: `for f in content-knowledge/*.txt; do head -1 "$f" >/dev/null || echo MISSING $f; done`
3. Dry run: `bash seo-agents/scripts/blog-writer-cycle.sh`
4. Inspect:
   - New markdown file in your content collection
   - New images in your public dir
   - `seo-agents/state/deploy-queue.json` shows `pending: false` and a fresh `last_deployed`
   - Live post visible at `https://<your-domain>/blog/<slug>/` within ~5 min
   - `git log -1` shows the auto-commit
5. If any of the above fails, fix locally before scheduling. Common causes: missing knowledge file, calendar schema typo, wrangler auth expired, GEMINI_API_KEY not in env.

### Schedule install

macOS:
```bash
cp seo-agents/scripts/launchd/*.plist ~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/com.<yourname>.blog-writer.plist
launchctl load ~/Library/LaunchAgents/com.<yourname>.blog-writer-healthcheck.plist
```

Windows:
```powershell
cd seo-agents\scripts\windows
pwsh -File .\Register-BlogWriterTask.ps1 -Mode WSL -RepoPath "/mnt/c/Users/<you>/<repo>"
```

### Post-install verification

1. Force a fire: `launchctl start com.<yourname>.blog-writer` (Mac) or `Start-ScheduledTask -TaskName 'BlogWriter'` (Win).
2. Tail the log: `tail -f seo-agents/state/blog-writer-cycle.log`
3. Confirm a post is committed and deployed.
4. Set a calendar reminder to refill `content-calendar.json` with new topics every 2 weeks; queue exhaustion is the #1 cause of silent stop.

---

## Failure modes you'll hit

| Symptom | Cause | Fix |
|---|---|---|
| "Knowledge pack incomplete" in report | Missing or renamed `my-*.txt` | Restore the file in `content-knowledge/` |
| Posts read flat / generic | EXPERIENCE NOTES is empty for this topic | Either add an anecdote to `my-experience-notes.txt` and re-run, or accept research-only output |
| Banned word slips through | BRAND GUIDELINES added a new rule but old posts predate it | Re-run review on past posts manually; they don't auto-update |
| Internal links 404 | WEBSITE PAGES file is stale | Regenerate from the live sitemap before next run |
| Citations missing or footnoted | Anchor text rule violated; writer fell back to footnote style | Tighten the citation rule wording in the master prompt or in the content-creator.md prompt |
| Deploy succeeds but post doesn't appear | Build cached old content; CDN purge needed | Cloudflare Pages invalidates automatically; if a custom CDN sits in front, add a purge step to deploy-runner |
| Schedule fires but nothing happens | claude or wrangler OAuth expired | Healthcheck job will warn on Mondays; re-run `claude` and `wrangler login` |

This is the full picture. Anyone with the six files and a git repo on Cloudflare Pages can stand up the same pipeline in about 90 minutes.
