Aeo daily
Skill waseemnasir2k26/skynetlabs-all-claude-code/skills/content-and-reels/aeo-daily
44 production Claude Code skills — content & reels, SEO/AEO, client delivery, code review, planning, token efficiency. One-command install.
npx -y skills add waseemnasir2k26/skynetlabs-all-claude-code --skill aeo-dailyAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Local AEO daily content engine — FORKED VOICE + voice filter (v0.4.2). One command → today's pack across 5 BUSINESS channels by default (was 11). Travel pack on-demand via --voice flag. BUSINESS voice (5 channels, default): LI personal, FB page, IG pro, YT pro, TikTok pro. Carousel + reels + comment-bait. TRAVEL voice (6 channels, on-demand): FB profile, IG travel x2, TikTok travel x2, Pinterest. Daily-life narrative, no agency talk. Pipeline writes single MASTER.html (all channels embedded as iframes + sticky TOC) at outputs/<DATE>/MASTER.html. All saved to <repo>\outputs\YYYY-MM-DD\copy.json (forked schema, see config/copy-schema.json). Legacy v0.3 still supported: --legacy flag falls back to single LI post + 8-slide carousel. Voice = the author's locked LinkedIn template (contrarian hook → reframe → → arrow lines → vulnerability beat → CTA + 5-8 hashtags). Authenticity layer required: 1 local anchor per post (named cafe / local currency / scooter / your local spot). 1 real client win referenced (a SaaS client / a real-estate client / dental flagship / a client). Voice-linted before render via lint_voice.py — hard-fails on 60+ AI tells. Hook-bank rotation locked: Mon test-LLMs / Tue prompt-exposure / Wed contrarian / Thu client-proof / Fri tool-stack / Sat builder-life / Sun meta-recap. Each day = 2 hook variants for A/B pick. Replaces v2 n8n cloud flow. $0 infra. Runs in Claude Code. Trigger: "/aeo-daily", "today's aeo pack", "ship today's content", "aeo daily content".
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
28.0 KB, as published. Nobody here has run it
/aeo-daily v0.6 — Local Daily AEO Content Engine
Ships 1 LinkedIn post (voice-clean) + 8-slide carousel + comment-bait reply pack + GHL CSV row every day.
Source repo: <repo>
WEDGE (locked 2026-06-08): ONE thing this engine sells — "Get Cited": make SMBs the answer ChatGPT / Perplexity / Gemini gives. Every BUSINESS post points at that. TRAVEL voice is NOT a second business — it is the proof-of-life layer of the same wedge (solo operator running an AI agency from a local cafe). Default
--voice business. Travel runs 3x/week max on personal IG/TikTok only, never competing for the business CTA, with at most ONE subtle bridge line/week ("built this from a local cafe").FUNNEL (locked 2026-06-08): content is not the product, leads are. Every business post carries a
funnelblock{cta_keyword, lead_magnet_ref, booking_link}. Flow: reel/post -> comment keyword (e.g. CITED) -> DM auto-reply -> free AEO Visibility Snapshot (scripts/snapshot_gen.py) -> book call. Snapshot = lead magnet + demo + close in one. CTA bank:funnel/cta-bank.json. DM script:funnel/dm-flow.md. Full spec:funnel/README.md.
CHANGELOG
- v0.6.0 (2026-06-08): WEDGE + FUNNEL + engine truth-up (4-agent fanout). (1) Wedge locked = "Get Cited" AEO-for-SMBs; travel demoted to 3x/wk proof-of-life layer (see WEDGE callout above). (2) Funnel built —
scripts/snapshot_gen.py(AEO Visibility Snapshot lead magnet, client-presentable cream/purple HTML),funnel/cta-bank.json(10 one-word keyword CTAs, zero "DM me"),funnel/dm-flow.md,funnel/README.md.copy.jsonnow carries afunnelblock. (3) Missing scripts now REAL — the v0.5.0 vaporware is built:config/scroll-hooks.json(150 hooks / 15 triggers,___blanks,_meta.angle_to_category),scripts/suggest_hooks.py,scripts/export_text.py,scripts/export_variants.py,scripts/build_master_html.py. (4) lint_voice.py hardened (v1.2.0) — now enforcesai_tell_structures(negation-parallelism / rule-of-three / balanced-pair-warn / framework-promise), scans ALL business channels (was linkedin.post only), added U+2018 + funnel.cta_keyword rule (fail under --strict, warn without), UTF-8 stdout fix. (5) Engagement loop live (true v0.6) —analytics/weekly.csv+scripts/weight_hooks.py->config/hook-weights.json;suggest_hooks.pyreorders an angle's categories by learned cta_trigger weight. (6) client-wins.json refreshed (+5 real wins through 06-07). Canonical psych-category vocab = the 15 hyphen-named categories in scroll-hooks.json (weekly.csv must use them). - v0.5.0 (2026-06-07): HOOK LIBRARY wired in. New
config/scroll-hooks.json= 150 scroll-stopper hooks across 15 psychology triggers (curiosity-gap, loss-aversion, contrarian, identity-callout, transformation, secret, authority, urgency, shock, question, specificity, story-loop, problem-agitate, confession, benefit), each with___blank for any topic. Newscripts/suggest_hooks.pymaps weekday angle → best psych categories and prints N filled hooks (deterministic,--offsetto rotate). New MANDATORY PHASE 2.5 — engine ALWAYS surfaces 8 matched hooks before writing copy; LI hook + video 0-4s beat must be built from them. Browsable viewerHOOK-LIBRARY.html(live topic-fill + filter + search + copy). - v0.4.2 (2026-05-20): TRAVEL SOFT-DISABLED by default + MASTER.html + image v2.
build_pack_v2.pynew--voice business|travel|allflag (defaultbusiness). Travel platforms skipped from render loop unless explicitly requested. Travel code path preserved — flip to--voice allto resume both tracks.run-aeo-daily.batnow passes--voice businessfor the 07:00 local-time auto-build.scripts/build_master_html.pynew — fans every channel pack into ONEMASTER.html(iframes + sticky TOC + angle header + GHL CSV download block). Auto-invoked bybuild_pack_v2.pyright afterINDEX.html. Voice filter respected → travel iframes simply don't exist when skipped.INDEX.htmltemplate no longer renders empty "Travel Voice" section when 0 travel cards present.process_image.pyv2 — face-detect now runs on ANY filename NOT startingDJI_(was: onlyIMG_*, missed allSCENE-*portraits). Multi-cascade union (frontal_default+frontal_alt2+profilefacewith horizontal flip). CLAHE histogram equalize before detect → recovers faces in shaded/backlit outdoor frames. Headroom guard in_crop_toshifts crop DOWN when face would clip top edge (kills "forehead at hairline" bug). Per-ratio focal-Y lift — 1×1 → 0.40, 4×5 → 0.38, 9×16 → 0.32 (face lands in upper third for reels).--debugflag for verbose face-detect logging.
- v0.4.1 (2026-05-19 PM): Round-3 production patch. (a)
build_ghl_csvs.pyis now schema-aware: tries nestedcopy[track][slug]first, falls back to legacy flat keys → 4 travel CSVs (ig_travel_1/2,tt_travel_1/2) no longer SKIPPED on extended schema. New--copyflag accepts override path. Image refs sourced from_picks_log.json(Track C source-of-truth) with avatar-dir glob fallback. (b)config/hook-bank.json::image_picker_hintsnow holds real filename PATTERNS (glob) + literal stems pulled from rotation pool, never invented filenames. (c)build_pack_v2._resolve_image_hintsupports glob matching (DJI_*,SCENE-2026-*-street-*) in addition to exact/prefix. (d)copy.image_picksMUST reference real stems or glob patterns — see PHASE 3 note below. - v0.4.0 (2026-05-19): FORKED VOICE schema. copy.json now contains shared{} + business{linkedin,fbpage,ig_pro,yt,tt_pro} + travel{fb_profile,ig_travel_1,ig_travel_2,tt_travel_1,tt_travel_2,pinterest} = 11 per-platform variants written in one pass. New
travel_rotation7-day hook bank added toconfig/hook-bank.json(cafe-discovery / visa-life / expat-mistake / scooter-day / food-find / builder-life / week-recap-travel).lint_voice.py --voice business|travel|bothenforces both tracks independently. Travel voice hard-blocks AEO/agency/client/SkynetLabs/schema/n8n/MRR/Loom/Fiverr terms + DM-me CTAs. Image picker hint per voice — business uses IMG_ portraits, travel uses DJI__ + scene_* scenes. Schema doc atconfig/copy-schema.json. - v0.3.0 (2026-05-18): brand palette swap (drop pure-black → cream base + brand purple #6b3fa0 + cyan #2dc6d6 + coral #ff6b5b + gold detail), 2→4 photo picks (portrait priority — IMG** over DJI**), photo prominence (hook bg duotone, context top strip, data 2-photo grid, CTA gold-bordered portrait), per-role slide styling (hook/context/data/confession/fix/cta classes).
- v0.2.0 (2026-05-18): 5→8 slide carousel (research: 60% dwell gap), voice-lint script (60+ AI tells blocked), client-wins + local-anchor + banned-phrases configs, hook variants A/B per day, hashtag tail required, comment-bait reply pack (first 60 min), founder-voice + local authenticity layer, arrow glyph fix (→ not ▸).
- v0.1.0 (2026-05-18): initial scaffold + 5-slide pipeline.
VOICE FORK — v0.4 (READ FIRST)
The engine now writes 11 per-platform variants in 2 voices in a single pass.
BUSINESS voice (5 channels: linkedin, fb_page, ig_pro, yt, tt_pro)
- Voice spine = locked LinkedIn template (contrarian hook → reframe → arrow lines → vulnerability beat → CTA).
- Anchor: local-life PUNCH (one specific detail dropped mid-post).
- Client win mandatory (
shared.client_win_reffromconfig/client-wins.json). - Hashtags: business pool (
#aeo #linkedinmarketing #agencyowner #automation #brandtracking #claudecode #n8n #b2b). - Hook bank:
config/hook-bank.json→rotation[<weekday>]. - Image picker: IMG_* portraits (face-on-camera).
- Lint:
python scripts/lint_voice.py --date <DATE> --voice business --strict.
TRAVEL voice (6 channels: fb_profile, ig_travel_1, ig_travel_2, tt_travel_1, tt_travel_2, pinterest)
- NO AEO, NO agency, NO client pitches, NO SkynetLabs, NO schema/citation/n8n/MRR/B2B talk.
- Self-deprecating, observational. Local daily-life (cafes, scooters, beach, expat scene, food, local-currency prices).
- Anchor: local-life DRIFT (one detail per post, plus 1 named place + 1 real currency amount).
- Story anchor mandatory (
travel.fb_profile.story_anchor) = small embarrassing/funny moment. - Hashtags: travel pool (
#travel #digitalnomad #nomadlife #expatlife #cafehopping #scooterlife #slowtravel). Blocklist: any business tag. - Hook bank:
config/hook-bank.json→travel_rotation[<weekday>]. - Image picker: DJI** + scene**.jpg + IMG_4*** scenes (NOT portraits).
- Lint:
python scripts/lint_voice.py --date <DATE> --voice travel --strict. - Banned CTA patterns: "DM me", "I'll reply with X", "follow for more", "save this".
Travel rotation (locked v0.4)
| Day | Angle | Sample hook |
|---|---|---|
| Mon | cafe-discovery | "New cafe in town. Flat white was cheap. Wifi held for 2 hours." |
| Tue | visa-life | "Visa run on Tuesday. Agent quoted the 60-day rate. Standard." |
| Wed | expat-mistake | "Rented a scooter without checking the brakes. Found out at the first downhill." |
| Thu | scooter-day | "Cross-town on the scooter today. 90 minutes if you skip the toll." |
| Fri | food-find | "Local plate for a few bucks at a spot behind the apartment. Extra sauce added." |
| Sat | builder-life | "Saturday looks like: wifi-cafe morning, beach walk noon, local dinner." |
| Sun | week-recap-travel | "Week recap: 1 visa run, 2 new cafes, 1 wipeout learning to surf." |
Lint both tracks in one call
python scripts/lint_voice.py --date <DATE> --voice both --strict
VOICE — LOCKED (the author's reference posts, do not flatten)
Reference POST 1 — "Freelancer → Operator" reframe (his top-engaging)
Stop calling yourself a freelancer. Words shape ceilings. Freelancer says: "I trade hours for cash." Operator says: "I run systems that deliver outcomes." The market pays operators 10x more. Same skills. Different label. Different price. → Stop saying "rate" → start saying "engagement" → Stop saying "available hours" → start saying "client capacity" → Stop sending invoices → start sending retainer agreements → Stop chasing leads → start filtering inbound What's on your LinkedIn headline today? Calling myself out first — mine literally said "freelancer" 12 months ago.
Reference POST 2 — "Claude Routines killed my n8n flows"
I spent 2 years building automations on n8n. Last week, Claude Routines replaced three of them in under 10 minutes. No nodes. No webhooks. No 2 AM error emails. Just a text box and a schedule. Here's one I'm running daily at 9 AM: → Pulls last 24-48 hrs of news across AI automation, n8n, Claude API, Next.js, Meta Ads → Filters for what's actually relevant to my agency and content pipeline → Writes 30-45 sec vertical video script — hook, body, CTA — ready to film I wake up. Script is waiting. I shoot. I post. A genuine ask to the builders here: What's the most creative Routine you've deployed?
Voice spine (every post)
- Contrarian hook — 1 line, ≤210 chars, ≤10 words ideal (hits 2.3x engagement per 2026 LI data).
- Reframe — 2-3 short lines, fragments OK.
- Arrow lines (
→) — 3-5 items (keep glyph per voice memory — do NOT use ▸ ▪ • or em-dash). - Personal vulnerability beat — admit specific past mistake, dated ("12 months ago" / "last quarter" / "the dental call last week").
- CTA question + compile-promise — "drop X below, I'll reply with Y."
- Hashtag tail — 5-8 tags (
#aeo #linkedinmarketingetc).
Anti-AI authenticity layer (NEW v0.2 — required)
- 1 anchor per post — a named local cafe, a ride-hail detail, a local-currency amount, a power-cut, a surf/commute time, or another concrete detail from your city.
- 1 real client win referenced — slug from
config/client-wins.json(dental-flagship / real-estate-voice / saas-email / healthcare-workflow / security-av / travel-client-site). - Allowed local code-switch sparingly: 1-2 colloquial words from your own dialect (max 1 per post, italicized).
- Mid-post drift allowed — "Anyway." / "Scratch that, actually —" / trail-off ellipsis (
...straight, not…).
BANNED PATTERNS (lint_voice.py hard-fails)
Full list: config/banned-phrases.json. Highlights:
Phrases: delve, moreover, navigate, leverage, robust, seamless, holistic, transformative, groundbreaking, pivotal, evolving landscape, in essence, at its core, the real question is, embark on, foster, harness, "let that sink in," "read that again."
Openers BANNED: "Unpopular opinion:" / "Hot take:" / "Plot twist:" / "Real talk:"
Closers BANNED: "What's your take?" / "Agree?" / "Save this post" / "Bookmark this"
Structures BANNED:
- Negation parallelism — "It's not X, it's Y"
- Forced rule-of-three — "innovation, inspiration, insight"
- Balanced sentence pairs — "Talent gets you in. Discipline keeps you there."
- Unnamed framework promise — "Here's the 3-step framework" (OK if named after real client)
Encoding BANNED: em-dash (—), en-dash (–), curly quotes (" " '), unicode ellipsis (…). Use straight ASCII.
Travel cliches BANNED: paradise, island vibes, manifesting, sunset views, nomad lifestyle.
EXECUTION FLOW (one session, ~6 min)
PHASE 1 — Fetch + dedup feeds (30s)
cd "<repo>" && \
python scripts/fetch_feeds.py --date $(date +%Y-%m-%d) --top 30
20 RSS, 48h SQLite dedup, writes outputs/<DATE>/today_items.json.
PHASE 2 — Pick today's angle (5s)
python scripts/pick_angle.py --date $(date +%Y-%m-%d)
Weekday → angle.json w/ 2 hook variants + top 5 candidates.
PHASE 2.5 — Surface scroll-stopper hooks (MANDATORY, 3s)
Always show the user matched psychology hooks BEFORE writing copy. Pull from the 150-hook library, filled with today's topic noun.
python scripts/suggest_hooks.py --angle <angle_slug> --topic "<topic noun>" --n 8
<angle_slug>=angle.json::angle_slug. The script maps angle → best psych categories viaconfig/scroll-hooks.json::_meta.angle_to_category.<topic noun>= the concrete subject of today's post (the niche/service/pain, e.g. "editing video in the terminal", "AEO for clinics").- Print the 8 hooks to the user. The LinkedIn hook + the video 0-4s beat MUST be built from one of these (paraphrase tightly to keep voice — never paste verbatim).
- Full library viewer for browsing/filtering:
HOOK-LIBRARY.html(live topic-fill box + search + copy). Dump all 150 grouped:python scripts/suggest_hooks.py --all --topic "<noun>". - Hook rule: first line ONLY. Pay off the open loop within the next 2 lines or it trains distrust. ASCII only (no em-dash / curly quotes).
PHASE 3 — Claude writes copy.json (~5 min, v0.4 FORKED schema)
Read: angle.json, config/client-wins.json, config/local-anchors.json, config/banned-phrases.json, config/hook-bank.json (read BOTH rotation for business AND travel_rotation for travel), config/copy-schema.json.
Pick once per day:
- 1 source from candidates (most agency-relevant) → business block only.
- 1 business hook variant from
hook-bank.rotation[<weekday>](or tight paraphrase). - 1 travel hook variant from
hook-bank.travel_rotation[<weekday>]. - 1 client_win_slug (
shared.client_win_ref). - 1 local anchor (
shared.local_anchor) — STYLED DIFFERENTLY in each track (business = punch / travel = drift). - 1 vulnerability_beat (
shared.vulnerability_beat) — used by business only. - 1 story_anchor (
travel.fb_profile.story_anchor) — used by travel only.
Write outputs/<DATE>/copy.json with the forked schema:
{
"date": "2026-05-19",
"angle_slug": "prompt-exposure",
"weekday": "tuesday",
"source_url": "https://...",
"source_title": "...",
"hook_variant_chosen": 2,
"shared": {
"news_anchor": "...",
"client_win_ref": "dental-flagship",
"local_anchor": "Coffee at the corner cafe, a few bucks.",
"vulnerability_beat": "I used to think AEO was schema + FAQ. Cost me 2 closes in Q1."
},
"business": {
"linkedin": {"post": "<900-1200ch w/ 8-role spine>", "slides": [...8 slides...], "hashtags": [...5-8...], "comment_bait": [...3...]},
"fb_page": {"cards": [...3...], "post": "<200-800ch>", "hashtags": [...3-5...]},
"ig_pro": {"slides": [...8...], "caption": "...", "hashtags": [...5-8...], "reel_script": "30-45s"},
"yt": {"thumb": {"headline":"...","sub":"..."}, "shorts_script": "45-60s", "title": "<=70ch", "desc": "..."},
"tt_pro": {"cover": {"headline":"...","sub":"..."}, "script": "30-45s", "caption": "...", "hashtags": [...3-5...]}
},
"travel": {
"angle_slug": "visa-life",
"hook_variant_chosen": 1,
"story_anchor": "Stood in the immigration line 90 min. Agent took 20 USD over WA.",
"fb_profile": {"cards": [...3...], "post": "<150-700ch>", "story_anchor": "..."},
"ig_travel_1": {"slides": [...5...], "caption": "...", "hashtags": [...5-8...], "reel_script": "15-30s"},
"ig_travel_2": {"slides": [...5...], "caption": "...", "hashtags": [...5-8...]},
"tt_travel_1": {"script": "...", "caption": "...", "hashtags": [...3-5...]},
"tt_travel_2": {"script": "...", "caption": "...", "hashtags": [...3-5...]},
"pinterest": {"pin": {"headline":"...","overlay":"...","size":"1000x1500"}, "desc": "...", "board": "Travel", "hashtags": [...8-12...]}
}
}
Full schema spec: config/copy-schema.json.
image_picks block — MUST use real filenames OR glob patterns (v0.4.1)
Authoring image_picks is the #1 cause of "PENDING_IMAGES" CSV rows. The hint MUST be one of:
-
A literal stem present in the rotation pool — verify before writing:
python "<your-images>/_pick-next.py" --count 400 | grep -E "^DJI_|^SCENE-|^IMG_"Examples that resolve today:
IMG_4105,SCENE-2026-05-07-garden-variant,DJI_20260518_0023. -
A glob pattern with
*or?—build_pack_v2._resolve_image_hintrunsfnmatchagainst every candidate stem (case-insensitive). The FIRST match wins, which means the rotation-log order decides which actual file you get.- Travel scenes:
DJI_*,SCENE-2026-*-street-*,SCENE-2026-*-garden-*. - Business portraits:
IMG_*,SCENE-*-rooftop-portrait-*,SCENE-*-veranda-*.
- Travel scenes:
-
NEVER invent filenames like
scene_street_01.jpgorscene_beach_low_tideunless they actually exist on disk. They will not resolve, drop to fallback (whatever-track-appropriate is available), and you lose authorial control over which image pairs with which slide.
Pattern cheat-sheet lives in config/hook-bank.json::image_picker_hints. Add new globs there as new naming families enter the pool. NEVER edit process_image.py for this — the resolver is in build_pack_v2.py.
PHASE 3.5 — Voice lint, BOTH tracks (3s, HARD GATE)
python scripts/lint_voice.py --date $(date +%Y-%m-%d) --voice both --strict
Business fails on ANY: banned phrase, encoding tell, hook>210 chars, hashtag count off, missing vulnerability beat, missing anchor, missing client_win_ref, wrong slide count/roles.
Travel fails on ANY: banned business term (aeo/agency/client/SkynetLabs/n8n/MRR/Loom/Fiverr/schema/citation), blocked travel hashtag, missing local-currency anchor, missing named place, missing story_anchor, business-CTA pattern ("DM me" / "I'll reply with"), encoding tell.
If lint fails → fix copy.json + re-run BEFORE Phase 4.
PHASE 4 — Export TEXT-ONLY pack (5s) — DEFAULT since 2026-05-31
Preferred: 3 variants per platform (story / contrarian / confession-style angles for A/B). Author outputs/<DATE>/variants.json (see shape in scripts/export_variants.py header — platforms[].variants[] each = {style, angle, body, hashtags}), then:
python scripts/export_variants.py --date $(date +%Y-%m-%d)
Emits VARIANTS.html (bright theme, 3 labelled copy-boxes per platform + Copy button) + VARIANTS.txt. 11 platforms x 3 = 33 ready posts. Pick one variant per platform, attach own image, post.
Voice rules for variants (research-backed, 2026): open IN-SCENE (a moment, not a thesis) · uneven rhythm (short. fragment. one longer line) · KILL rule-of-three / "it's not X it's Y" / inflated words / em-dashes · end on a LOW-LOAD CTA that drives comments (binary "chase or wait?", "tell me I'm wrong", fill-in-blank, oddly-specific question) NOT "what's your take?" · LI 900-1500 chars, IG hook <125 chars, FB/TT short.
Video = ONE shared, SHOOT-READY script, NOT per-platform. video_scripts.{business,travel}[] each = {style, angle, shots:[{t, line, angle, delivery, broll}], caption, hashtags}. The shots array is a director's breakdown: each beat tells you the exact LINE to say, the CAMERA angle/framing, the DELIVERY cue, and the ON-SCREEN/B-roll text. Rotate camera angles across beats (front-on tight -> push-in/lean -> 3/4 angle -> back to front-on for CTA) so the video isn't static. One shoot -> post to YouTube Short + TikTok + IG Reel + FB Reel. Do NOT write separate YT/TikTok/IG/FB scripts.
- business video scripts MUST be driven by the LATEST industry/service news — pull a real, dated item from PHASE 1's
today_items.json(AI agents / automation / AEO / the niches we sell), attribute the source claim (no fabricated stats), then tie it to a SMB service angle + comment CTA. One real news anchor per variant. - travel video scripts stay personal lifestyle (local daily-life), NOT industry news.
Single-copy fallback (1 per platform, from copy.json):
python scripts/export_text.py --date $(date +%Y-%m-%d)
- Reads
copy.json, emitsCOPY.html+COPY.txt. - NO images, NO
_avatars, NO picker, NO html2canvas. You select + push your own images.
Image carousels are DEPRECATED (the old
build_pack_v2.pyrender still works if ever needed, but the renderer had a silent blank-text bug; text export is the supported path). To resume rendered images:python scripts/build_pack_v2.py --date <DATE>.
PHASE 5 — (optional) GHL CSV row (5s)
python scripts/append_ghl.py --date $(date +%Y-%m-%d) --slot 10:00
6-col canonical CSV → tomorrow 10:00 local time. imageUrls left blank; paste manually-chosen image URLs before import. Skip entirely if posting by hand.
PHASE 6 — Tell the user what to do
Print:
OPEN: outputs/<DATE>/COPY.html
1. 📋 Each platform = a Copy button. Click → paste into the native composer.
2. 🖼 Pick + attach your own image(s) per post.
3. 📋 LinkedIn: fire the 3 comment-baits 1 per 15-20 min in first 60 min.
4. Done.
TOOL-STACK WARNING (research-backed)
- NEVER post via Taplio — 30% reach penalty + 55% engagement penalty. LinkedIn algo flags AI-template patterns from Taplio.
- Safe: native LI scheduler, Buffer, GHL scheduler.
- Analytics-only (zero algo risk): AuthoredUp (hook preview), Shield Analytics (retro).
- AVOID: Hypefury for LI (it's Twitter-first, weaker LI signal).
OUTPUT CONVENTION
outputs/YYYY-MM-DD/
├── today_items.json # 30 deduped RSS items
├── angle.json # angle + 2 hook variants + 5 candidates
├── copy.json # Claude-written, voice-linted
├── index.html # 8-slide carousel + post + reply pack
├── _avatars/ # 2 picked photos
├── _picked_images.json # rotation audit
└── csv/ghl-linkedin.csv # tomorrow 10:00 local time
RULES (REPRIORITIZED v0.2)
- Voice spine = locked (reference POST 1 + POST 2). Don't flatten.
- Anchor required — 1 real local detail per post.
- Client win required — slug from
client-wins.json. No invented stats. - Vulnerability beat required — dated, specific, uncomfortable.
- Hook ≤210 chars — LI "See more" truncate point.
- Hashtags 5-8 — no more, no less.
- Comment-bait pack — 3 pre-staged replies. Fire 1 per 15-20 min first hour.
- lint_voice.py --strict must pass before render.
- Rotation log sacred — always
--markphotos. - No Taplio. Ever.
ANTI-PATTERNS
- Don't paste hook from
hook_variantsverbatim — paraphrase tightly to keep voice unique. - Don't reuse a client win slug 2 days in a row.
- Don't drop the
→arrow glyph for▸•or em-dash — voice memory locked. - Don't fill
vulnerability_beatwith generic "I used to think AI was overhyped" — needs date, person, currency, or specific fail. - Don't omit the comment-bait pack — that's where 70% of reach lives.
TROUBLESHOOTING
| Symptom | Fix |
|---|---|
feedparser missing | pip install -r requirements.txt |
| 0 items after dedup | rm cache/seen.sqlite |
| Image picker fails | check _USAGE_LOG.json exists in <your-images> |
| html2canvas blank | hard-refresh; check _avatars/ populated |
| lint_voice.py FAILS | read each FAIL line; fix copy.json; re-run |
| Hook too long | use only first variant from angle.json, strip trailing clause |
| Anchor missing | inject 1 line from local-anchors.json into Slide 2 + LI post body |
ROADMAP
- v0.3 — wire
/reel-studiomograph mode for daily 30s video off same angle. - v0.4 — auto-push PNGs to GitHub
carousels-png/<DATE>/for ManyChat asset library. - v0.5 — Task Scheduler invocation:
claude -p /aeo-dailydaily 7am local time. - v0.6 — DONE (2026-06-08): engagement-loop live via
weight_hooks.py+hook-weights.jsonoverlay read bysuggest_hooks.py. - v0.7 — auto-generate 2 hook variants via Claude inside Phase 3 (not pre-baked).
- v0.8 — wire
build_master_html.pyauto-invoke intobuild_pack_v2.py; add--voiceflag tobuild_pack_v2.py(still missing — renderer always emits all channels). - v0.9 — live-query the AEO Visibility Snapshot (auto-run ChatGPT/Perplexity/Gemini for the prospect instead of manual fill).
Last updated: 2026-06-08 (v0.6.0 — WEDGE "Get Cited" + FUNNEL (snapshot lead magnet, cta-bank, dm-flow) + 6 missing scripts built + lint v1.2.0 hardened across all channels + engagement hook-weighting loop live + client-wins refreshed)