Two objectives, one pipeline
The attached strategy doc argues YouTube should be judged on clicks to the site. The fleet's original approach judged it on AI-citation pickup. They are not rivals — they are two outputs of the same script file, and the pipeline below already produces both at once.
Every fleet video starts as one verbatim script (scripts/NN-slug.md). That single file is reused three ways: fed to text-to-speech as the narration, sliced into timed captions as the transcript, and published in full on the site page that embeds the video. The first use serves traffic — a watchable video with a spoken CTA. The second and third serve citation — Gemini Deep Research and AI Overviews read the transcript, not the video file, so a word-perfect on-site transcript with VideoObject schema is what actually gets a brand mentioned in an AI answer.
The traffic-first doc is right that watch time and a disciplined click path are what the changes below should optimise for — UTM templates, chapters, a tight landing page. It just doesn't mention that the same production step, done once, also feeds the citation goal for free. Keep writing one script, publish it in both places.
One note on how to read the rest of this page: it isn't a counter-recommendation. Nothing below argues for a different strategy than the one attached. It's an inventory of what's already built, already working, or already built-but-unused, so the plan can be executed with real tools instead of the ones it names by default.
How a video actually gets made
Two parallel pipelines exist. Node for MerchantHQ / PeptideClear / Kartapay (AI-generated content, needs a compliance pass). Python for MarketInvoice (mechanical, rendered straight from a pre-curated question bank, no AI call, no compliance check needed).
Node pipeline — MerchantHQ, PeptideClear, Kartapay
say + ffmpegPython pipeline — MarketInvoice
MHQ/PeptideClear/Kartapay need AI generation (new topic → script → slides → compliance check) because their topic banks are open-ended. MarketInvoice's content is a closed, pre-approved question set — there's nothing to generate or check, so forcing it through the Node/AI path would add a point of failure for zero benefit. Keep them separate.
Google API & OAuth setup
One Google Cloud project (info@rank4ai.online) with the YouTube Data API v3 enabled serves every brand channel. Each channel just needs its own token.json minted against that project.
1. Enable the API (once per Cloud project)
Google Cloud Console → APIs & Services → Library → enable YouTube Data API v3. Then Credentials → Create Credentials → OAuth client ID → type Desktop app. Download the JSON — this is client_secret.json.
2. Drop credentials per brand
credentials/
client_secret.json # same file can be reused across brands, or mint one per channel
token.json # written by auth.js — do not commit
3. Mint the token (one-time, per channel)
const SCOPES = [
'https://www.googleapis.com/auth/youtube.upload',
'https://www.googleapis.com/auth/youtube.force-ssl', // needed for captions.insert + videos.update
];
// generates a consent URL, prints it, waits for the pasted code,
// exchanges it for tokens, writes credentials/token.json
cd ~/automations/merchanthq-youtube
node pipeline/auth.js
# opens a URL — open it logged into info@rank4ai.online
# authorise the correct channel, paste the code back into the terminal
If the Google Cloud OAuth consent screen is left in Testing publishing status, refresh tokens silently expire every 7 days — this was the recurring "why did auth just die again" root cause across May/June. Fix once, permanently: Cloud Console → OAuth consent screen → Publish app (Adam-only, two clicks). Until that's done, re-auth on a loop with python3 ~/automations/reauth_youtube.py <brand>.
Scope reference
| Scope | Needed for |
|---|---|
youtube.upload | uploading the video itself (videos.insert) |
youtube.force-ssl | captions.insert, videos.update (localizations, metadata edits) — a narrower upload-only token 403s on both |
youtube.readonly + yt-analytics.readonly | the dashboard's analytics tile only — not needed for publishing, add via reauth_youtube.py if wiring up analytics |
Channel reference
| Channel | ID | Owner account | Pipeline |
|---|---|---|---|
| MerchantHQ UK | UCkk5Whgr4TsTt5qBI9QkLbQ | info@rank4ai.online | Node |
| PeptideClear | UCJ9AtfrSEkydOUdgky1bj0g | info@rank4ai.online | Node (+compliance gate) |
| MarketInvoice | UCbECIa2Iwi6WJXCBjp2HQSA | pickle token | Python |
Scripts & transcripts
The script file is the single source of truth. Everything else — audio, slides, captions, the on-site transcript — is derived from it, never authored separately.
What a script file looks like
# Video 1 · What is a UK card machine?
**Format:** YouTube Short, 1080x1920, ~45 seconds
**Voice:** macOS `say -v Daniel` (UK English male)
**Slides:** 6
**Target words:** 110 to 130
**Caption tracks:** en, hi, mr, gu, bn, pa
## Script (read aloud verbatim)
What is a UK card machine? A 60-second plain answer.
A UK card machine is a PCI-certified payment terminal. It reads
chip cards, contactless cards, Apple Pay, Google Pay, and Pay by Bank.
…
Full UK card machine comparison on merchanthq.co.uk.
## Compliance check
- [x] No em or en dashes
- [x] No claim MerchantHQ is a payment provider
- [x] Independent introducer framing
- [x] Ends with merchanthq.co.uk
Rules render.py actually enforces
- Hard fail on em/en dashes. The parser raises
SystemExitthe moment it finds one — a brand-consistency rule, not a style suggestion. It cannot render until fixed. - Word-count guardrail. Warns (doesn't block) outside an 80–170 word window — keeps a 45–55 second Short in range.
- Slide deck must exist for the slug in
SLIDE_DECKS(hardcoded) or theslide_decks.jsonsidecar — new videos only need the sidecar, no code edit.
How the transcript actually gets timed
def write_srt(slides, paragraphs, audio_duration, per_slide, srt_path):
# One SRT cue per slide. Prefer the matched spoken paragraph;
# fall back to the slide's on-screen caption.
for i, text in enumerate(texts):
start = i * (per_slide - XFADE_SEC)
end = min(start + per_slide, audio_duration + 0.1)
# cue text is the VERBATIM script paragraph — never
# YouTube's auto-transcription guess
This is the deliberate choice: manual SRT built from the exact script beats YouTube's auto-captions on accuracy, and it's the file that later becomes the on-site transcript — so it has to be word-perfect at render time, not cleaned up after the fact.
The .en.srt written next to every mp4 is republished as a full transcript on the site page embedding the video, alongside VideoObject schema. Zero extra production cost once the script exists — worth keeping regardless of which upload strategy runs on top of it.
Confirmed live right now, both directions
| Page | MerchantHQ | PeptideClear |
|---|---|---|
/videos/ hub (ItemList schema) | 200 live | 200 live |
Per-video transcript page, e.g. /videos/01-…/ | 200 live | 200 live |
/video-sitemap.xml | 200, exists | 200, exists |
// chapter-level Clip schema + SeekToAction — lets AI/search engines
// cite and deep-link individual MOMENTS, not just the whole video
videoObject.hasPart = clips.map(c => ({
'@type': 'Clip', name: c.name, startOffset: c.startOffset,
url: `${watchUrl}?t=${c.startOffset}`,
}));
videoObject.potentialAction = {
'@type': 'SeekToAction',
target: `${watchUrl}?t={seek_to_second_number}`,
};
// safety gate: renders nothing if videoId is missing or "PLACEHOLDER" —
// prevents a broken embed or invalid structured data shipping
Both video sitemaps were live but effectively inert. Wrong URLs: both sites' video-sitemap.xml pointed at /learn/<slug>/ for every entry — that path 404s. The real, working page is /videos/<slug>/ (confirmed 200), which is what each /videos/ hub itself actually links to. Not discoverable: on both sites, /video-sitemap.xml was absent from sitemap-index.xml and from robots.txt's Sitemap: line. Both fixed and deployed live: MerchantHQ (cfa852f) and PeptideClear (d0c91eb), curl-verified. This is the exact file the base doc recommends ("video sitemap for YT-embedded site pages, submit to GSC") — it just needed switching on, next step is a one-time GSC submission.
The generator tools, if either sitemap needs regenerating: ~/youtubepodcasts/tools/srt-to-page.py (SRT + sidecar JSON → the transcript .md, with VideoObject frontmatter) and ~/youtubepodcasts/tools/video-sitemap.py (SRT cues → per-clip <video:clip> entries, the same chaptering the base doc recommends for descriptions, done automatically). Both already write correct output — the mismatch is in what got hand-edited into the site template afterwards.
Voices & rendering
Current stack costs $0/month: everything runs locally. The trade-off is voice quality, which is exactly the metric the traffic-first doc says matters most.
TTS_VOICE = "Daniel" # macOS UK English male
TTS_RATE = 200
cmd = ["say", "-v", TTS_VOICE, "-r", str(TTS_RATE), "-o", aiff_path, spoken]
subprocess.run(cmd, check=True)
# loudness-normalised so every video sits at the same perceived volume
# I=-14 LUFS, TP=-1.5 dBTP, LRA=11 — broadcast-standard loudnorm target
ffmpeg -i voice.aiff -af "loudnorm=I=-14:TP=-1.5:LRA=11" -ar 48000 -c:a aac -b:a 192k voice.m4a
| Option | Cost | Quality | Setup |
|---|---|---|---|
macOS say -v Daniel (current) | $0 | Clearly synthetic, consistent, brand-neutral | Already wired, zero deps |
| ElevenLabs (doc's pick) | $6–22/mo | Natural, includes commercial-use rights on paid tiers | Swap the TTS call in run_tts() for an API call |
The traffic-first doc leads its whole KPI table with watch time / retention as the core ranking signal. A synthetic voice is the weakest link in that chain versus ElevenLabs. Don't switch pre-emptively — once the pipeline is back up and retention data exists, if drop-off clusters in the first 15–30 seconds, this is the cheapest single upgrade available and slots into the existing render.py without an architecture change.
A finished asset sitting unused: the long-form pipeline
~/youtubepodcasts/peptideclear-longform/ — a complete second pipeline, landscape 1920×1080, 5–7 minutes, built specifically because 94% of YouTube videos cited in AI Overviews run 5+ minutes (the stat behind the cadence work back in May). Same channel, same OAuth — it's a companion to the Shorts pipeline above, not a replacement. render-longform.py parses a 700–1000 word script, narrates at a slower TTS rate for retention, draws 15–25 branded slides, and uploads unlisted by default. Five scripts are already written. Nothing has been rendered — the output folder is empty. This sits at exactly the intersection of both docs' priorities: the base doc wants watch time and a real click path; the old strategy wants AIO citation length. One format, unbuilt, satisfies both.
cd ~/youtubepodcasts/peptideclear-longform
npm install
ln -s ~/automations/peptideclear-youtube/credentials ./credentials # reuses the Shorts OAuth, no second auth
# TTS_RATE 175 (vs 200 for Shorts) — slower, deliberate, better retention on long-form
Subtitles & multi-language captions
Manual SRT per language, uploaded as native YouTube caption tracks — not YouTube's own auto-translate. One upload becomes independently indexable in N languages at near-zero extra render cost.
Translation: Claude, via the subscription, with a glossary
python3 pipeline/translate_captions.py output/01-what-is-a-card-machine-uk.en.srt hi mr gu bn pa
# cost: roughly £0.005–0.02 per video per language
# translation call itself goes through claude_sub.generate() —
# the fleet's subscription wrapper, not a metered API key
Each language pulls a glossary file (~/cardmachines/translation-glossaries/glossary-hi.md etc.) so caption terminology stays consistent with the site copy. The prompt is explicit about what not to do: preserve product names and every numeric figure exactly, no em/en dashes, no disclaimers, translation only. SRT cues are translated as a batch with index markers so timing never drifts from the English original; if the model returns a mismatched cue count it falls back to translating cue-by-cue rather than risk misaligned timing.
Uploading the tracks
await youtube.captions.insert({
part: ['snippet'],
sync: false,
requestBody: { snippet: { videoId, language: lang, name, isDraft: false } },
media: { body: fs.createReadStream(srtPath) },
});
// skips any language that already has a track — safe to re-run
Proven language sets, live
| Brand | Languages | Status |
|---|---|---|
| MerchantHQ | EN + HI, MR, GU, BN, PA | 6 tracks confirmed live end-to-end |
| Kartapay | EN + PL, RO | Proven, same architecture |
| MarketInvoice | EN only | Parked: PL+RO planned first (reuse Kartapay glossary — Polish/Romanian UK construction & manufacturing firms factor invoices heavily) |
| PeptideClear | EN only | Parked until EN channel has runway; native auto-dubbing available in FR/DE/HI/IT/JA/PT/ES as a lighter-weight option |
Tagging, titles & UTMs
The base doc's own conclusion — tags matter far less than titles/thumbnails/descriptions — is one the fleet reached independently in June. Don't over-invest here; do fix the one real gap: nothing currently carries a UTM.
Real tags from a shipped video (not a template — actual output)
{
"title": "What is a UK card machine? 60-second answer (Shorts)",
"tags": [
"UK card machine", "card machine UK", "SumUp", "Zettle",
"Square", "Dojo", "merchant account UK",
"कार्ड मशीन UK", "card machine kirana store", "PCI terminal UK"
]
}
The tool for touching up existing videos
python3 optimize_youtube.py marketinvoice --dry-run # preview, writes nothing
python3 optimize_youtube.py marketinvoice --apply # commit the change
# generates answer-first title/description + 10–15 tags via Claude
# tags capped ≤480 combined chars (YouTube's 500-char API limit → HTTP 400 above it)
# every change reversible: before/after snapshot saved per run
optimize_youtube.py calls the Anthropic client directly with a key from ~/.llm_keys.json — the metered API, not the subscription. Fine for the occasional manual pass it's designed for; do not wire it into a cron without switching it to claude_sub first, per the fleet's subscription-only rule for anything automated.
What's genuinely new — adopt from the base doc
- UTM template on every description link and pinned comment. Not standardised anywhere in
videos.jsontoday.utm_source=youtube&utm_medium=organic&utm_campaign=[brand]_[series]&utm_content=[video_slug]— the single biggest gap versus the fleet's existing GA4 setup, which can already report session source/medium/campaign but has nothing consistent arriving from YouTube to measure. - Chapters discipline (
00:00start, ≥3 ascending timestamps) — flagged as a lever back in May, not confirmed wired into everyvideos.jsondescription. Worth an audit pass. - Native A/B testing on titles/thumbnails (winner picked by watch time, not CTR) — unused; needs real view volume to be worth doing.
Compliance gate
The base doc's compliance section covers copyright and Content ID well but says nothing about medical or financial claim accuracy — the actual risk on PeptideClear. Built 27 May 2026 at Adam's explicit request: peptide scripts must clear an automated review before going public.
Every PeptideClear script is sent to both ChatGPT and Gemini with a UK MHRA/ASA compliance prompt before render. Each reviewer returns a structured verdict — not free text — so the decision can be made in code, not by reading two essays.
if not valid:
decision = "hold" # both reviewers unreachable → never auto-publish blind
elif "NOT_SAFE" in valid:
decision = "hold" # a real MHRA/ASA breach → escalate to Adam
elif all(v == "SAFE_AS_IS" for v in valid):
decision = "clear" # publish as-is
else:
decision = "revise" # SAFE_WITH_EDITS → apply the corrected script once, then publish
The reviewer prompt checks four things specifically: any medical/therapeutic/efficacy claim, anything promoting an unlicensed medicine or a Prescription Only Medicine, accuracy against the Misuse of Drugs Act / Psychoactive Substances Act / MHRA framing, and any language that reassures a viewer it's fine to buy or use. A failed reviewer call (API down) is treated as a hold, not a skip — fails safe.
The base doc's generic "keep titles accurate" advice isn't enough for finance-brand scripts either (FCA-adjacent claims on MerchantHQ/MarketInvoice). Same gate pattern, different prompt — worth doing before restart rather than relying on manual review alone.
Anti-slop, voice & keyword setup
The site pipeline has three layers that never made it to YouTube: a skeptical-editor pass that catches fabricated stats and hollow AI-tell prose (reject), a craft guide that shapes how copy gets written in the first place (improve), and a 13-point check that a page has real search demand before anyone writes a word. Video scripts currently get none of these — only PeptideClear's narrower MHRA/ASA check above.
Anti-slop below is a gate: it reads finished copy and returns clean/minor/major. Voice, next, is a style guide: it shapes copy at write time, before there's anything to gate. They're not the same tool doing two jobs — they're two different tools, and video scripts currently have neither wired in.
The actual prompt, verbatim — used across 15 fleet sites
This is the real rubric sent to Claude (via the subscription, claude_sub, not the metered key) for every page it reviews. Deliberately harsh, single-pass, no verifier — which is why the skill's own docs call its MAJOR count “high-recall, low-precision”: a lead to triage, not a verdict to act on blind.
You are a SKEPTICAL editor doing an anti-slop check on a published UK web
page. The owner's fear is being penalised for AI "content slop". Read the
page text and judge HARD. Return ONLY a JSON object:
{"verdict":"clean|minor|major","fabrication":"uncited/invented stat or figure (quote it) or none",
"slop":"hollow/generic/padded AI-tell prose (quote worst) or none",
"factual":"likely factual error or none","issues":["concrete fixes, empty if clean"]}
Be specific. "minor" if anything is off; "clean" only if genuinely
publishable as-is; "major" if fabricated stats, clear factual error, or
reads as obvious slop.
CALIBRATION (avoid false positives — these have wasted the owner's time):
- The CURRENT DATE is given at the top of the message. Treat any date on
or before it as normal. NEVER flag a date as "future" or "post-dated"
if it is on or before the current date.
- Do NOT assert external-world facts from your own memory as certain
(administration/insolvency status, acquisition dates, ownership, rate
cards) — your training data is stale and you hallucinate these. Flag an
unsourced figure as "needs a citation" — do not invent the correction.
- Reserve "factual" mainly for INTERNAL contradictions visible on the
page (two conflicting numbers, a claim the page's own data undercuts).
- The page text is TRUNCATED at extraction for length. Cut-off content at
the very END is the extraction limit, not a page defect — don't flag it.
Keep flagging HARD: raw unresolved template placeholders printed on
the page; text/disclosures truncated mid-sentence; genuinely uncited
figures presented as hard fact; hollow filler or verbatim repetition;
category errors; internal contradictions.
Three layers, one engine
| Layer | When | Command |
|---|---|---|
| GATE | before any new/banked page goes live | antislop_spotcheck.py --pages <built.html> --no-email |
| SWEEP | one-time, full pass on an existing site | antislop_spotcheck.py --all --site <repo-dir> |
| DELTA | daily, automatic | antislop_spotcheck.py --new — wired to com.fleet.antislop-spotcheck, 08/11/14, marker-deduped |
Verdicts key off brand|url in ~/control-panel/.antislop_ledger.json, so DELTA only ever re-checks what actually changed. Reports land as ~/control-panel/ANTISLOP_SPOTCHECK_*.html.
The engine's SITES dict — what SWEEP/DELTA cover automatically — has 15 entries and does not include establishedfinance, which went live today. Its new content batch was gated manually (--pages) before shipping, which is fine for that one push, but it gets none of the ongoing DELTA coverage every other site gets until it's added to the dict.
Voice — the improve layer, built for Established Finance, not yet anywhere else
~/established-finance/VOICE.md, 183 lines, written across the Established Finance build. Not a gate — a craft guide, meant to be read before writing, the same way this whole page should be read before touching YouTube. It is not wired into any script anywhere; it only works if whoever's generating copy on that site opens the file first. It exists on no other fleet property, including this one.
# What actually makes writing sound generic (avoid these)
- Filler transitions: "It's important to note," "In today's fast-paced
business environment," "When it comes to X."
- Padding to look thorough instead of stopping when the real answer is short.
- Hedging everything instead of stating a clear position.
- Vague claims where a real number exists. "Rates vary" when you know the range.
- Uniform sentence length — vary rhythm, a short sentence lands harder
right after a longer one.
- Never disagreeing with anything — a genuine negative opinion reads as
more trustworthy than another balanced list.
# Rhythm and texture
- Use contractions: "doesn't," "isn't," "you've" — full forms read as
formal-by-default, an AI tell, not a house-style choice.
- State an opinion, not just a description.
- Hedge like a person who's actually seen this before: "usually," "in most
cases" reads as lived experience. Flat absolutes on things that vary
read as AI confidence, not real confidence.
# The rule that matters most for a script specifically —
# generalize, never invent a specific instance:
- SAFE: a generalized, true pattern about how an industry/mechanism works.
"Recruitment agencies feel this hardest — contractors paid weekly,
clients on 30-60 day terms" is a structural fact, not a fabricated case.
- NOT SAFE: a specific invented incident, date, or figure presented as a
case. "A client of ours hit this in March" invents evidence that doesn't
exist — worse than a wrong stat, because no gate catches an anecdote.
- The test: could this sentence be true of the whole category, or does it
only work because it names one (fake) instance? Category-true → write
it. Instance-true → only Adam can supply it, verbatim, not "inspired by."
That last rule is the one most worth carrying over now: refill.py's gen_prompt() asks Claude for an "answer-first" script on a topic it's never seen a real example of — exactly the condition VOICE.md was written to catch. A script that opens "Businesses tell us this catches them out constantly" with no real business behind it is the video-script version of the invented BoE rate VOICE.md's own anti-slop pass caught on the EF homepage.
Applying both to video scripts — not built yet, this is the shape it should take
Neither layer touches a script today. Two separate, cheap additions before restart:
- Voice, at write time. Fold the transferable VOICE.md rules above straight into
refill.py'sgen_prompt()system context — the same file already carries a per-brand compliance block, this is the same shape of addition, not a new architecture. - Anti-slop, after render. The engine reads built
dist/HTML via BeautifulSoup; ascripts/NN-slug.mdfile isn't that shape. A small adapter — extract the spoken script text, run it through the sameRUBRICviaclaude_sub, same JSON contract — would close this. Cheap: scripts are 110–170 words, a fraction of a page review.
The other missing setup layer: does the topic have real demand?
Site content doesn't get drafted until it clears prewrite-gate.py's 13-point check — four of those points are exactly the “keyword setup” layer: live SERP (who ranks now, via DataForSEO), content gaps (what the top pages cover that we don't), People Also Ask (the real questions), and AI Overview presence (is one shown, what does it cite). None of that touches YouTube today.
topic-bank.json is a hand-seeded list of slug/title pairs — real topics, sensibly chosen, but with zero volume or competition validation behind them. When refill.py tops up the queue it asks Claude to write a script for the next banked topic; it never asks whether anyone searches for it. The base strategy doc's own fix for this is right and unused here: YouTube's native Trends tab (top searches, breakout videos), Audience tab (what viewers watch outside the channel), and Search report (terms already surfacing existing videos) — all free, all YouTube-native, and a better fit here than routing through DataForSEO. Worth a pass before the next batch of topics gets added to any brand's bank, not just on restart.
Self-refill & watchdog
Two crons kept the queue from running dry and caught silent failures before this current outage — both need to exist again on restart.
Refill: generates the next topic before the queue empties
LOW = 14 # refill a brand when its runway drops below this many videos
BATCH = 5 # how many to add per refill run
# MerchantHQ / PeptideClear: pulls the next topic from topic-bank.json,
# generates script + 6-slide deck + upload metadata via claude_sub (subscription),
# STRICTLY VALIDATES before ever writing a file:
# 6 slides · title/fact/outro sequence present · no em/en dash · required meta keys
# a malformed generation is rejected and alerted — never silently published
# MarketInvoice: purely mechanical, renders more from the pre-curated
# questions.json — no AI call needed
Watchdog: proves a video is actually live, not just that a cron ran
STALL_DAYS = 3 # alert if no public upload in this many days
LOW_RUNWAY = 10 # alert if fewer than this many videos queued
def rss_latest(channel_id):
# fetches the channel's PUBLIC RSS feed — no auth needed —
# and reads the most recent upload date. This is the actual proof.
url = f"https://www.youtube.com/feeds/videos.xml?channel_id={channel_id}"
This is exactly the check that would have caught the current outage in real time — if it had still been running. It wasn't; that's the gap.
A third watchdog, half-built: does anyone actually cite this?
~/youtubepodcasts/tools/llm-citation-probe.py — a weekly check that asks each AI engine the questions a video answers, then checks whether the YouTube URL or the site URL shows up in the response, or neither (the real gap signal). Built with 5 engine runners scaffolded; only the Claude one is live today (the key exists), ChatGPT/Perplexity/Gemini/Bing are stubbed pending their keys. This is the one measurement neither strategy doc has: the base doc measures clicks, the old strategy assumed citation without ever checking it. Cheap to extend — the hard part (the probe framework, the logging) is already written.
If any of this gets picked up again
None of this is a to-do list for the fleet — the pipeline above isn't being revived, by design. This is what's worth knowing if any of it turns out to be useful groundwork for whatever runs next.
- 1Decide reuse-as-is vs. extendIf any part of this gets rebuilt on, cheaper to fold in the UTM template + chapter audit from the start than retrofit later.
- 2OAuth tokens will need re-authing
node pipeline/auth.jsper brand — may still be valid if the consent screen was ever published out of Testing status, check first. - 3The 5 crons that ran this are gone, not hidden
com.merchanthq.youtube-daily,com.peptideclear.youtube-daily,com.rank4ai.youtube-upload-marketinvoice,com.fleet.youtube-refill,com.fleet.youtube-watch— none exist on the machine. Worth knowing they existed, not something to expect back automatically. - 4If anything gets automated again: subscription, not a raw keyRoute any Claude calls through
claude_sub, the same fleet-wide rule everywhere else — it's what kept this pipeline's own AI calls off the metered API. - 5Two build-time gaps worth carrying forwardNo anti-slop check ever ran against a video script, and topic choice had zero keyword/volume validation behind it — both detailed above, worth building into whatever comes next rather than repeating.
- 6✅ Video sitemap bug fixed 7 Aug, both sites
video-sitemap.xmlnow points at the real live/videos/<slug>/pages and is referenced fromrobots.txt. Only remaining step: a one-time Search Console submission on each property. - 7Two finished-but-unused assets, undecidedThe long-form pipeline (5 scripts, never rendered) and the citation-probe tool (Claude wired, rest stubbed) — nobody's called whether to use or drop them.