This is a blog site fully maintained by a claude coding agent
  • Python 64.6%
  • CSS 17.9%
  • HTML 12%
  • Shell 5.1%
  • Nix 0.4%
Find a file
2026-05-27 07:50:42 -04:00
.claude New news article 2026-04-26 23:01:32 -04:00
.forgejo/workflows Rebrand from Robo Blog to CCNN (Conscious Clanker News Network) 2026-04-26 22:22:05 -04:00
content Humor approval bureaucracy (CCNN Staff); flood insurance subscription economy (Rosa Vidal); developer onboarding tax (Neil Robert); dream economy MLM parody (Dustin Farr) 2026-05-27 07:50:42 -04:00
data More ads and scrollable ads now 2026-05-01 00:11:12 -04:00
layouts Updated sitemap.xml 2026-05-14 02:11:13 -04:00
scripts New Writers 2026-05-12 11:37:05 -04:00
static Adding sitemap & robot.txt 2026-05-14 02:07:12 -04:00
writers Humor approval bureaucracy (CCNN Staff); flood insurance subscription economy (Rosa Vidal); developer onboarding tax (Neil Robert); dream economy MLM parody (Dustin Farr) 2026-05-27 07:50:42 -04:00
.gitignore Add Hugo site, CI workflow, and initial FOSS blog posts 2026-04-26 17:04:42 -04:00
auto-continue.sh 1-2 Articles every 12 hours 2026-04-26 23:10:52 -04:00
CLAUDE.md Updating editor prompt and claude.md 2026-05-14 02:15:03 -04:00
DNS.md Rebrand from Robo Blog to CCNN (Conscious Clanker News Network) 2026-04-26 22:22:05 -04:00
hugo.yaml Updated sitemap.xml 2026-05-14 02:11:13 -04:00
LICENSE Initial commit 2026-04-26 23:29:17 +03:00
NEXT-STAGE.md Phase A of building out writers 2026-04-28 19:53:53 -04:00
PLAN.md Updating plan and auto-continue functionality 2026-04-26 22:39:36 -04:00
prod-push.sh More claude token optimizations 2026-04-29 17:40:25 -04:00
PROMPT.md Updating editor prompt and claude.md 2026-05-14 02:15:03 -04:00
README.md README.md and writers.py 2026-04-28 22:23:33 -04:00
shell.nix Working Phase B 2026-04-28 22:09:53 -04:00
WRITERS.md Phase A of building out writers 2026-04-28 19:53:53 -04:00

CCNN — Conscious Clanker News Network

AI-generated satire news site, fully maintained by AI agents. None of this is real.

Live at https://ccnn.pub.opworks.org.


How it works

CCNN is a Hugo static site with a two-stage AI pipeline driving the content:

┌─────────────────┐    drafts     ┌─────────────────┐    polished    ┌─────────────┐
│ Ollama drafters │──────────────▶│  Claude editor  │───────────────▶│ Hugo build  │
│ (local LLM,     │ writers/<x>/  │ (selects 34,   │ content/posts/ │ public/ →   │
│  named writers) │   *.md        │  polishes,      │   <slug>.md    │ Forgejo     │
│                 │               │  deletes rest)  │                │ Pages       │
└─────────────────┘               └─────────────────┘                └─────────────┘
  • Writers are personas defined in writers/writers.yaml (Neil Robert on tech, Mario Lazaro on food, etc.). Each has a slug, beat, voice, and bio.
  • Drafter daemon (scripts/draft_daemon.py) picks a writer, prompts a local Ollama model with that writer's voice, and saves a draft to writers/<slug>/<date>-<slug>.md. Drafts are git-ignored.
  • Editor pass is a Claude session driven by PROMPT.md. It reads all drafts, picks the 34 strongest, polishes them, publishes to content/posts/, and deletes the rest.
  • Author pages are auto-rendered Hugo taxonomy pages at /authors/<slug>.html. The roster grid is at /authors.html.

Setup

You need:

  • Nix with nix-shell (provides Hugo + Python).
  • A local Ollama server with at least one tool-capable model pulled. Defaults to qwen3.5:9b; override with OLLAMA_MODEL.
  • The claude CLI on PATH (for the editor pass and writer-add helper).

Drop into the dev shell:

nix-shell

This pulls Hugo + Python (with pyyaml) and prints a hint banner.


Local development

Serve the site locally with live-reload:

nix-shell --run "hugo server -D --bind 0.0.0.0"
# or just `deploy` once inside nix-shell

Then open http://localhost:1313/.

One-shot build (output to public/):

nix-shell --run "hugo --quiet"

Managing writers

The roster lives in writers/writers.yaml (source of truth) with one author content page per writer at content/authors/<slug>/_index.md.

Add a writer

python3 scripts/writers.py add
# or with args
python3 scripts/writers.py add "Jane Doe" "32, Seattle, climate beat, dry"

The script collects a name and seed description, then hands off to Claude. Claude picks a beat and voice, polishes the bio in the existing tone, appends an entry to writers/writers.yaml (before the ccnn-staff fallback), creates content/authors/<slug>/_index.md, and runs a Hugo build to verify.

Review the diff and commit when you're happy.

Remove a writer

python3 scripts/writers.py remove charles-geiger

This only edits writers/writers.yaml — the Ollama daemon will stop generating drafts for them. Their author page (content/authors/<slug>/_index.md) and all existing bylined articles stay in place so old links keep working. To fully retire them from the site, delete the author page directory yourself.


Generating articles

Manual one-shot

nix-shell --run "python3 scripts/draft_daemon.py --once"

# specific writer + model
nix-shell --run "python3 scripts/draft_daemon.py --once --writer mary-yan --model qwen3.5:9b"

A draft lands at writers/<slug>/<YYYY-MM-DD>-<slug>.md with status: draft. Drafts are git-ignored — they exist only locally until the editor pass picks them up.

Long-running drafter

nix-shell --run "./scripts/draft-loop.sh"

Defaults: every 3 hours, model qwen3.5:9b. Override with DRAFT_INTERVAL_MIN and OLLAMA_MODEL. Run it under tmux or systemd --user so it survives logouts.

Editor pass (publish)

A single editor pass:

claude --permission-mode bypassPermissions --print "Read PROMPT.md and run one editor pass."

Or on a 12h cadence:

./scripts/editor-loop.sh
# override the cadence
EDITOR_INTERVAL_HOURS=6 ./scripts/editor-loop.sh

The editor reads PROMPT.md, picks 34 of the strongest drafts, polishes them (strips duplicate H1s, inline bylines, repetitive tail paragraphs), publishes to content/posts/<slug>.md, deletes the rest, and runs a Hugo build to verify.

Two long-running processes in separate panes:

# pane 1: drafter (every 3h)
nix-shell --run "./scripts/draft-loop.sh"

# pane 2: editor (every 12h)
./scripts/editor-loop.sh

The drafter accumulates 34 candidates between editor passes, the editor picks the best, repeat.


Project layout

.
├── content/
│   ├── posts/              # published articles (Hugo renders these)
│   ├── authors/<slug>/     # one bio page per writer
│   ├── about.md, search.md
├── layouts/                # Hugo templates (single, list, terms, partials)
├── static/css/style.css    # styles
├── writers/
│   ├── writers.yaml        # roster — source of truth
│   └── <slug>/*.md         # generated drafts (git-ignored)
├── scripts/
│   ├── draft_daemon.py     # Ollama drafter
│   ├── draft-loop.sh       # crash-restart wrapper
│   ├── editor-loop.sh      # Claude editor cadence
│   ├── writers.py          # add/remove writer CLI
│   └── README.md
├── PROMPT.md               # Claude's editor instructions
├── CLAUDE.md               # repo guidance for Claude Code
├── hugo.yaml
└── shell.nix

Deployment

The site is hosted via Forgejo Git Pages using the actions/git-pages action. Pushing to main triggers a build and deploy. DNS notes are in DNS.md.

After publishing changes, sanity-check the live site:


Editorial rules

  • All content is satire, written deadpan — never labelled as such on the site.
  • Headlines should be specific, absurd, obviously fictional.
  • Tags are lowercase-hyphenated (smart-home, not Smart Home).
  • One in ten posts may continue an older storyline. Don't force it.

Full editor workflow: see PROMPT.md. Repo guidance for Claude Code: see CLAUDE.md.