Auto-publishing to fervorai.dev

Both scheduled tasks push their own output. The briefing task publishes each briefing; article-spotter publishes each article draft. Netlify builds on the push, so a run that finishes at 7:12am is live by about 7:13.

The push happens from the cloud session, straight to GitHub. It does not go through the device bridge, because the bridge blocks git writes in a mounted folder (EPERM: unlink .git/index.lock). That also means publishing keeps working when the Mac Mini is asleep, off, or has no folder connected.


One-time setup

1. Create a scoped token

GitHub → Settings → Developer settings → Personal access tokens → Fine-grained tokens → Generate new token.

Field Value
Token name fervorai-site-publish
Expiration 90 days (calendar reminder to rotate)
Repository access Only select repositoriesKtopper/fervorai-site
Permissions Repository permissions → Contents: Read and write. Nothing else.

That token can commit to one repo and do nothing else. It cannot read your other repos, cannot touch settings, cannot delete anything. If it leaks, revoking it costs you one click and a re-generate.

2. Save it where the tasks can read it

mkdir -p ~/Documents/Claude/Projects/"Meduim Writer GOAT"/.secrets
pbpaste > ~/Documents/Claude/Projects/"Meduim Writer GOAT"/.secrets/fervorai-github-token
chmod 600 ~/Documents/Claude/Projects/"Meduim Writer GOAT"/.secrets/fervorai-github-token

It lives in the workspace folder, not in this repo, so it can never be committed by accident. The file holds the token and nothing else, no export, no quotes, trailing newline is fine.

3. Commit the publish script

cd ~/dev/fervorai-site
git add scripts/publish.sh PUBLISHING.md && git commit -m "add auto-publish script" && git push

This has to be pushed before the first automated run, because each run clones the repo and executes the copy of the script it finds inside.

4. Confirm Netlify builds on push

Netlify → Site configuration → Build & deploy → Continuous deployment: branch main, auto-publish on. Nothing else to change.


What the scheduled tasks run

One bootstrap block, added as the last step of each task. It finds the token, clones, and hands off to scripts/publish.sh inside the clone:

TOKEN_FILE="$(ls /sessions/*/mnt/"Meduim Writer GOAT"/.secrets/fervorai-github-token \
                 /mnt/user-data/uploads/"Meduim Writer GOAT"/.secrets/fervorai-github-token \
                 2>/dev/null | head -1)"
if [ -z "$TOKEN_FILE" ]; then
  echo "PUBLISH SKIPPED: token file not reachable this run"
else
  GH_TOKEN="$(tr -d '\r\n' < "$TOKEN_FILE")"
  rm -rf /tmp/fervorai-pub
  git clone --quiet --depth 1 \
    "https://x-access-token:${GH_TOKEN}@github.com/Ktopper/fervorai-site.git" /tmp/fervorai-pub \
    && GH_TOKEN="$GH_TOKEN" bash /tmp/fervorai-pub/scripts/publish.sh <FILES>
fi

<FILES> is the absolute path of the briefing (or the two article drafts) the run just wrote. Pass the *-trending-ai.md briefing and the article .md files only.

What the script refuses to publish

X-articles, X-bursts, newsletters, promo posts, selection memos, the topic ledger, and image-prompt files are rejected by filename even if you pass them. Two reasons: the X-article is a near-duplicate of the briefing and would compete with it in search, and the rest are working files, not published pages.

How it fails

Every failure path exits 0 and prints one line, so a publish problem never fails a briefing run:

Line Meaning
PUBLISHED: 1 briefing, 2 articles pushed to … live, Netlify building, page URLs follow
PUBLISH: nothing new … the files were already in the repo, no commit made
PUBLISH SKIPPED: GH_TOKEN is empty … token file missing or the folder wasn't mounted
PUBLISH FAILED: clone failed … token expired or revoked, or no network
PUBLISH FAILED: push rejected … rebase-and-retry also failed, needs a look

The token is never printed, and it's scrubbed from any error text before it's shown. It's also stripped out of the clone's .git/config right after cloning, so it isn't left sitting in a temp dir.

A skipped run loses nothing. The next run publishes any file the repo doesn't have yet, or you can catch up by hand:

cd ~/dev/fervorai-site
cp ~/Documents/Claude/Projects/"Meduim Writer GOAT"/briefings/2026-07-27-*-trending-ai.md content/briefings/
git add content && git commit -m "publish: catch-up" && git push

Rotating the token

Generate a new one, overwrite the file in .secrets/, revoke the old one on GitHub. Nothing in the repo or the task prompts refers to the token's value, so there's nothing else to update.