@aicopycoders/exodus 2026.9.1100-beta.3 → 2026.9.1400-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/assets/docs/CLAUDE.md +22 -1
  2. package/assets/docs/PIPELINES.md +20 -0
  3. package/assets/skills/anatomy-of-ads/SKILL.md +138 -0
  4. package/assets/skills/anatomy-of-ads/references/beat-map.md +69 -0
  5. package/assets/skills/anatomy-of-ads/references/concept-angle.md +50 -0
  6. package/assets/skills/anatomy-of-ads/references/hook-and-structure.md +69 -0
  7. package/assets/skills/bot-builder/SKILL.md +147 -0
  8. package/assets/skills/bot-builder/references/exodus-prompt-node.md +113 -0
  9. package/assets/skills/bot-builder/references/prompt-blocks.md +169 -0
  10. package/assets/skills/copy-instincts/SKILL.md +154 -0
  11. package/assets/skills/copy-instincts/references/instincts-catalog.md +131 -0
  12. package/assets/skills/copy-instincts/references/locked-examples.md +104 -0
  13. package/assets/skills/copy-instincts/references/worked-runs.md +148 -0
  14. package/assets/skills/exodus/SKILL.md +10 -1
  15. package/assets/skills/find-competitors/SKILL.md +148 -0
  16. package/assets/skills/find-competitors/references/query-grid.md +78 -0
  17. package/assets/skills/find-competitors/scripts/__pycache__/adlib_sweep.cpython-314.pyc +0 -0
  18. package/assets/skills/find-competitors/scripts/__pycache__/competitor_signals.cpython-314.pyc +0 -0
  19. package/assets/skills/find-competitors/scripts/adlib_sweep.py +100 -0
  20. package/assets/skills/find-competitors/scripts/competitor_signals.py +168 -0
  21. package/assets/skills/handoff/SKILL.md +81 -0
  22. package/assets/skills/mechanism/SKILL.md +130 -0
  23. package/assets/skills/mechanism/references/bot-prompts.md +94 -0
  24. package/assets/skills/mechanism/references/competitor-mechanisms.md +39 -0
  25. package/assets/skills/odf/SKILL.md +167 -0
  26. package/assets/skills/patterns-and-gaps/SKILL.md +120 -0
  27. package/assets/skills/positioning-spiral/SKILL.md +166 -0
  28. package/assets/skills/remember-that/SKILL.md +56 -0
  29. package/assets/skills/segment-grid/README.md +45 -0
  30. package/assets/skills/segment-grid/SKILL.md +84 -0
  31. package/assets/skills/thinking-vs-build/SKILL.md +120 -0
  32. package/assets/templates/STANDARDS.md +49 -0
  33. package/dist/commands/init.d.ts +1 -0
  34. package/dist/commands/init.js +10 -5
  35. package/dist/commands/update.js +1 -1
  36. package/dist/commands/video.d.ts +6 -0
  37. package/dist/commands/video.js +30 -1
  38. package/dist/lib/assets.d.ts +1 -0
  39. package/dist/lib/assets.js +3 -0
  40. package/dist/lib/scaffold.d.ts +3 -0
  41. package/dist/lib/scaffold.js +18 -1
  42. package/package.json +1 -1
@@ -0,0 +1,104 @@
1
+ # What "locked" looks like
2
+
3
+ Locked (coherence, resonance, the click) is when all the dimensions rhyme. The product stops being a product and becomes the inevitable conclusion of a worldview. The audience does not feel sold to; they feel shown how the world works. The test: say the whole thing, product, mechanism, proof, enemy, solution, in one breath, and the listener nods before you finish. Not because it is vague. Because every piece implies the next.
4
+
5
+ Not locked: you keep adding explanations. "Well, the mechanism is this, but the spokesperson is more like… and the history is kind of separate but…". Every "but" and "kind of" is a dimension not rhyming.
6
+
7
+ ## Five full locks
8
+
9
+ ### Olive oil (lock through place)
10
+
11
+ Premium olive oil from Cyprus. Everything in the category is grey: cold-pressed, high polyphenol, extra virgin. Then the student mentioned the land: the trees grow on a former UN buffer zone untouched for 35 years. No pesticides, no agriculture, nothing.
12
+
13
+ - Mechanism: trees fight harder in undisturbed soil → more polyphenols as a stress response → the oil is more potent because of the adversity. The mechanism rhymes with the origin.
14
+ - Visual: untouched earth, ancient trees, a no-man's land nature reclaimed.
15
+ - Narrative: protected for 3,000 years, a conflict zone, then a buffer zone, then someone planted olives in the most pristine soil on earth.
16
+ - Binary: "Most premium olive oil is dead. This is the world's only living olive oil."
17
+ - Proof: the polyphenol count is higher, and the reason it is higher is the story. Proof and story are the same thing.
18
+ - Moat: a place with an irreproducible history. Nobody can manufacture 35 untouched years.
19
+
20
+ One breath: "The only olive oil in the world grown on land untouched by a single pesticide in 35 years, a former UN no-man's land in Cyprus where the trees fight so hard to survive they produce the highest polyphenol levels ever measured. Most premium olive oil is dead. This is the world's only living olive oil."
21
+
22
+ Notice what disappeared: cold-pressed, extraction, extra virgin. The grey claims became supporting details.
23
+
24
+ ### Volcanic shower head (lock through ingredient origin)
25
+
26
+ A filtered shower head. Grey claims: filters chlorine, improves skin, better pressure. Colour: one filtration layer contains volcanic rock from the Faroe Islands.
27
+
28
+ - Mechanism: rock forged under extreme heat and pressure naturally purifies through mineral filtration. The rock's violent creation is the reason it works.
29
+ - Visual: "I got a volcano shower."
30
+ - Cultural: the Faroes were settled by Vikings; Vikings have legendary hair. Association, not logic. Let the imagination connect it.
31
+ - Binary: volcanic rock (ancient, forged by earth) versus activated carbon (industrial, manufactured).
32
+ - Sensory: "secret minerals flooding my skin". It does not just filter, it gives.
33
+ - Conspiracy: fluoride bans in two US states and Europe. "What's in your water that governments are banning?"
34
+
35
+ One breath: "This shower head filters your water through volcanic rock from the Faroe Islands, the same mineral-rich stone the Vikings bathed in, forged under extreme pressure deep in the earth, purifying and mineralising every drop. It's not a filter. It's a volcano shower."
36
+
37
+ ### Melatonin strip (lock through conspiracy)
38
+
39
+ The most commoditised supplement in America. Colour: MIT patented melatonin at 0.3 to 1mg. The industry mega-dosed to 5 to 10mg to avoid paying MIT.
40
+
41
+ - Mechanism: the effective dose is tiny; the common dose is 10 to 100x too high and trains the body to stop making its own. The trusted solution is the enemy. An inversion.
42
+ - Conspiracy: documented patent strategy, not speculation.
43
+ - Binary: horse pills versus targeted micro-strip.
44
+ - Proof: "Melatonin is the only hormone you can buy over the counter, banned in Europe and Japan." Two facts side by side; the reader builds the implication.
45
+ - Names: melatonin overdose, melatonin hangover, melatonin resistance, targeted melatonin. Every name implies the same enemy and the same answer.
46
+
47
+ One breath: "MIT patented the effective melatonin dose at under 1mg. The supplement industry didn't want to pay, so they mega-dosed to 5 to 10mg, cut it with fillers, and sold it as sleep support. Those horse pills aren't helping you sleep. They're training your brain to stop making melatonin. This strip delivers the exact dose MIT patented."
48
+
49
+ ### Norwegian fitness coach (lock through archetype)
50
+
51
+ A fitness program for men over 35 from a 26-year-old Norwegian. Original pitch: three-pillar system, metabolic reset. Every coach says that, and a 26-year-old telling 45-year-olds is a credibility gap.
52
+
53
+ - Archetype: Rolex, polo, travel, chill, ex-footballer. Not "become Wolverine". Nordic mystique: scientific, elite, measured, old money, enjoys luxurious food.
54
+ - Mechanism: cortisol. Workouts are short because long workouts spike cortisol. Food is flexible because restriction spikes cortisol. Results come fast because when cortisol is fixed the rest follows.
55
+ - The rhyme: the archetype is the mechanism expressed as a lifestyle. His entire being is the proof. He is not stressed, not grinding, training three times a week.
56
+ - Case study: the yacht owner who travels three weeks a month, eats out constantly, dropped 60 pounds. A lifestyle demonstration, not a boot camp before-and-after.
57
+ - Cultural positioning: "Why are you Americans going so crazy?" Playful superiority that rhymes with the mechanism (overcomplication is the problem).
58
+ - Binary: overcomplicated American bro-science versus measured Scandinavian science.
59
+
60
+ One breath: "A 26-year-old Norwegian sports scientist figured out what American fitness gets wrong: you're working too hard, stressing too much, and spiking the one hormone that makes fat loss impossible. His clients, CEOs who travel three weeks a month and eat out every night, drop 40 to 60 pounds training three times a week and eating what they want, because when you manage cortisol the way Scandinavian sports science has for decades, everything else fixes itself."
61
+
62
+ ### CIA self-defence (lock through exclusivity)
63
+
64
+ - Archetype: he was in the CIA. Everything radiates from that. "They're declassifying stuff from 50 years ago that's cutting edge today. Imagine what's still classified."
65
+ - Mechanism: every martial art is trained for tournaments and cardio classes. Real combat has no referee, no gloves, no timer. CIA training is the only training built for that reality. The mechanism is the archetype.
66
+ - Exclusivity: "I can't teach you everything. What I can show you is how they think." More exists that you cannot access, and what is shared is the distilled essence.
67
+ - Audience: 50-year-old dads. Three moves, simple. The simplicity is the proof: if it is distilled for operatives with two seconds, it must be the most effective.
68
+ - Binary: tournament fighters in Lululemon versus operatives with no rules.
69
+
70
+ One breath: "A former CIA operative is sharing the three pressure-point techniques agents learn for when there are no rules, no referees, and no second chances. He can't teach you everything; most of it is still classified. But what he can share is simple enough for any 50-year-old dad to learn in an afternoon, and powerful enough he has to warn you not to use it unless your family's safety is at stake."
71
+
72
+ ## Almost locked, and where the rhyme broke
73
+
74
+ **Copper pillowcase.** The copy built desire around pressure on the face during sleep. The product's mechanism was copper ion transfer. Pain said "your pillow is crushing your face"; mechanism said "copper seeps into your skin". Two stories. Locked version: your skin is starving while you sleep, cells trying to repair without the one mineral that activates regeneration; this pillowcase transfers copper ions for the eight hours you are asleep. "It's not a pillow. It's an 8-hour spa treatment." Now pain (starving cells) rhymes with mechanism (copper feeds cells) rhymes with time (eight hours) rhymes with visual (ions flowing from fabric to skin).
75
+
76
+ **Menopause joint supplement.** Mechanism: "changing hormones have hijacked how your body uses the nutrients it needs to repair your joints." Six moving parts. Too complex to picture, so no visual, so no binary, so nowhere for the pain and benefit cascades to anchor. Locked version collapsed to two links: hormones deplete, so cells cannot absorb. Now you can draw it (sealed walls versus open walls), name it (nutrient lockout), make the binary (absorbing versus starving), and the star ingredient rhymes (bee propolis penetrates the cell wall). Simplification did not dumb it down; it enabled the lock.
77
+
78
+ **Cannibal brain.** A memory supplement scaled to $9M on "your brain is eating itself". Vivid, novel, and it rhymed within the pain dimension: eating itself → irreversible → despair. But despair has no product at the end. "Nobody believes a drop is gonna make your brain rebuild itself." Compare "dirty brain": dirty implies cleanable, gunk implies flushable. The pain rhyme and the solution rhyme converge on the product.
79
+
80
+ **Fire blanket.** Strong mechanism (extinguishers are the dangerous thing), strong conspiracy (most are expired), strong binary (chemical spray versus clean blanket). Then the story ad: she left oil on the stove while chatting with her mom. Gut reaction: "this bitch is kind of dumb, it's her fault." The character broke the worldview. The worldview said the system failed you; the character said user error. Locked version: she does everything right, has the extinguisher because she is a good mom, grabs it, and nothing happens because it expired and nobody told her. Or it works and the residue wrecks the kitchen worse than the fire. Better still, a firefighter spokesperson: "I've seen extinguishers fail hundreds of times. Here's what I make my family use."
81
+
82
+ ## The diagnostic
83
+
84
+ - You keep needing "and also". Locked copy says "which means", "because", "which is why".
85
+ - Mechanism and star ingredient were chosen in separate sessions and are now being connected. Locked: they are the same discovery.
86
+ - The spokesperson could be swapped. Locked: their background is what makes the discovery possible.
87
+ - You are defending the mechanism ("I know this sounds crazy, but…", "studies show…"). Locked: proof is a chapter of the story.
88
+ - Pain and solution use different metaphors. Locked: invert the pain metaphor and you get the solution metaphor.
89
+
90
+ ## The feel of it
91
+
92
+ It feels like a slope, not a staircase. Each sentence tilts you into the next. It feels like one idea expressed seven ways: purity through adversity as land, chemistry, history, binary, moat, sensation, visual. It feels inevitable rather than clever. You can hand it to anyone at dinner and they get it in fifteen seconds. And the writer feels the product deserves to exist: the conviction is real, so the writing reads as advocacy instead of marketing.
93
+
94
+ ## Getting there: the practical seven
95
+
96
+ 1. **Load everything before committing to anything.** Every ingredient's origin, history, natural behaviour, science. The spokesperson's full backstory. The market's current arguments and charged words. Suppressions, patents, bans. Competitors' exact claims. One interesting thing gives you one dial position; you need five or six dials with several positions each.
97
+ 2. **Find the single strongest shade of colour.** The one fact that makes your gut tighten and that no competitor has. If you could paste it into a competitor's ad, it is grey.
98
+ 3. **Ask what that detail's natural metaphor is, and whether it rhymes with a mechanism.** Volcanic rock purifies. Bees penetrate. Untouched land makes hardier plants. If it does not rhyme, change the detail or the mechanism; do not force it.
99
+ 4. **Check the spokesperson.** Does their specific history make them the natural discoverer of this? If not, pick a different aspect of their background to emphasise.
100
+ 5. **Generate the binary.** The bad side is life without the mechanism; the good side is life with it.
101
+ 6. **Run the one-breath test.** Three or four sentences a stranger finds compelling. If a listener can predict the ending after the first sentence, that is inevitability, not boredom.
102
+ 7. **Check the cascades against the root metaphor.** Pain drilled to bedrock and benefit cascaded upward must both stay inside the same metaphorical universe.
103
+
104
+ The deepest reason this is hard: you cannot fake the lock. You can fake a hook, a mechanism name, a backstory. The convergence depends on finding something true about the product that naturally generates the rest. Students assemble from the outside. The lock is discovered from the inside. The skill is perception: looking at a product deeply enough to find the one true thing that makes everything else obvious.
@@ -0,0 +1,148 @@
1
+ # Worked runs
2
+
3
+ Five runs of the process at different levels. The first four are Luke and Mario's public teaching examples. The fifth is a fictional cortisol-style supplement so the shape is visible on a category like the ones this workspace handles.
4
+
5
+ ---
6
+
7
+ ## Run 1. The beard oil ad (ad level → hook)
8
+
9
+ **Draft.** "If you oil your beard daily and it's still dry, you're not doing it wrong, you're using the wrong product entirely. And here's why it's not your fault. The oil you're using, whether it's from the drugstore or Amazon, was formulated for straight hair. Different texture. Different biology. On your hair, it just sits on top and does nothing… Your hair, 4b, 4c texture, curls so tight that the natural oils your skin produces get trapped at the root… So your beard is starving on the outside while your skin is drowning in oil underneath… That's exactly why two brothers from Atlanta built this kit…"
10
+
11
+ **Frame.** Ad. Black men with beards. Money symptom: patches (not dryness, not acne).
12
+
13
+ **See.**
14
+ - Body scan: reads AI, dashes, mildly bored, one line woke me up.
15
+ - Copy blocks: pain present and long, promise thin, proof at the end (100,000 kings, 4,000 reviews), curiosity is the straight-hair mechanism, no conditions beyond "first-time discount".
16
+ - Colour: formulated for straight hair · sits on top and does nothing · 4b/4c curls so tight oil can't move · starving outside, drowning underneath · two brothers from Atlanta. Grey: "you've tried everything", "here's why it's not your fault", the cycle paragraph.
17
+ - Title: "Your beard oil isn't working." Rated 3 to 4 out of 10.
18
+ - Ideas: one, but the best lines are on lines 6 to 12, not line 1.
19
+
20
+ **Attract (light).** Unique: built for 4c texture by two brothers from Atlanta; no water, no sulfates. Thesis underneath: the beard companies made oil for straight hair and sold it in every Black neighbourhood in America. They don't care about Black men.
21
+
22
+ **Make.**
23
+ - Why the colour line is interesting: strong mental image, contrarian, "starving" and "drowning".
24
+ - Relevance: do I care that my skin is drowning in oil? Acne, maybe. If I'm a beard person I care about my beard. So: the oil clogs the follicles that cause growth. Now it hits patches.
25
+ - Contrast: "sits on top and does nothing" is neutral. "Clogs the follicles" is good thing bad. Then: "most oil" → "white beard oil". Loaded phrase.
26
+ - Claim ladder: most oil is formulated for straight hair and not designed for your hair → most oil doesn't work for your hair → most oil doesn't work for Black hair → most oil hurts Black hair → white beard oil hurts Black hair.
27
+ - Visual: two images. Oil moving down a straight shaft versus oil trapped at a coiled root. Then metaphor, specific: curly hair strangles the root like a boa constrictor. Name it: follicle strangulation.
28
+ - Blame placement (Mario): blame what the oil does, not their curly hair. The oil turns into the snake.
29
+ - Intensification and ratchet: drowning your follicles, making it impossible to grow a beard.
30
+
31
+ **Rewrite (hook).** "If you're using white beard oil as a Black man, you're probably drowning your follicles and making it impossible to grow a beard."
32
+
33
+ Alternates from the same idea: "Most beard oil is built for white beards. It feeds straight hair and strangles curly hair. That's why your beard stays patchy." · "The beard companies made beard oil for straight hair and sold it in every Black neighbourhood in America. It nourishes straight beards while choking ours." · "Your genes are fine. Beard oil made for straight hair suffocates curly hair. That's why your beard grows in patches."
34
+
35
+ **Protect.** Redditor: "racist?" is the bail risk; the payoff is that it is true of the formulation, and the body walks it back with the texture biology. Cold scroll: line 1 stands alone. Congruence: the enemy is the oil, the prospect is absolved.
36
+
37
+ **Probability.** Hook 2 is more likely than hook 1. Nobody knows until the test.
38
+
39
+ ---
40
+
41
+ ## Run 2. PDRN micro-infusion (mechanism / big idea level)
42
+
43
+ **Draft.** A product page: PDRN (salmon DNA), hyaluronic acid, peptides, growth factors, delivered through 24k gold micro-infusion stamps. Brief: "market it."
44
+
45
+ **Frame.** Mechanism. Women buying anti-aging. Money symptom: visible aging the mirror shows.
46
+
47
+ **See.** Grey: hyaluronic acid, peptides, growth factors. All good, all sayable by everyone. Colour: PDRN itself (early majority; known but still novel) and the gold micro-infusion delivery. No spokesperson with a story.
48
+
49
+ **Attract (full).**
50
+ - North star: I need a worldview where PDRN plus gold micro-infusion is the absolute best form of skincare.
51
+ - Two mechanisms exist for PDRN: receptor signalling, or building block that fills the cell. Building block felt more substantial, we are adding something lost to your cells. Chosen.
52
+ - Preeminent: if PDRN must be the perfect solution, what must be true about salmon? Salmon swim upstream, get battered, their skin gets wrecked, and they regenerate. Fish skin heals faster than mammal skin. PDRN is used in wound care. So: salmon have the most regenerative skin of any animal. Plausible argument, not a lie; if it is not defensible, move on.
53
+ - Association: farm-raised salmon from the Chinese sea, or Norwegian, or Arctic? There is a cherry salmon grown in Korea. Korean cherry salmon. Cherry reads sweet and beautiful, Korean opens the K-beauty file, salmon keeps the mechanism. Nothing about the fish changed. Ask the brand whether it is true before you say it.
54
+ - Delivery: why micro-infusion? The capillaries are stuck; nothing taken internally reaches that layer. Why gold? Most valuable metal for its properties; any other metal would degrade PDRN. So it has to be gold, and it has to be infused.
55
+ - Coherence: beauty, gold, luxury, Korea, cherry salmon, regenerative skin, DNA, rejuvenating your cells. Cleopatra. Now there is a movie.
56
+
57
+ **One breath.** "PDRN is DNA from the most regenerative animal on earth, salmon that tear their skin apart swimming upstream and rebuild it. This one comes from Korean cherry salmon, and it's delivered through 24k gold micro-infusion because gold is the only metal that doesn't destroy it and micro-infusion is the only way past your stuck capillaries into the cell."
58
+
59
+ **Protect.** Cold scroll: "PDRN" needs one clause of definition on line 1. Compliance: "most regenerative animal on earth" is the rung to check; "fish skin heals faster than mammal skin" is the walk-back.
60
+
61
+ ---
62
+
63
+ ## Run 3. The freelancer headline (hook / headline level)
64
+
65
+ **Draft.** "The 3-Pillar System That's Helped 1,000+ Freelancers Go From Struggling to Building Predictable Revenue in 6 Weeks (Not Years). Only 15 Slots Left."
66
+
67
+ **See.**
68
+ - Copy blocks: curiosity = 3-pillar system (rated 3), promise = predictable revenue (4), proof = 1,000+ freelancers (7), pain = struggling (vague), constraint = 6 weeks not years (6), condition = 15 slots. All six present. Not missing blocks; each block is weak.
69
+ - Title: "A system helps freelancers earn predictably." Rated 3 to 4.
70
+
71
+ **Make.**
72
+ - "3-pillar" feels like three steps. Simplicity: one step. "Client Domino System" is one specific action that creates inevitable momentum, and domino carries associations of chain reaction, gambling, money.
73
+ - "Struggling" → specific: feast-or-famine, cold pitching, dry spells, held hostage by referrals.
74
+ - Ratchet on the pain: "Every month you spend surviving feast-or-famine is another month you're falling further behind."
75
+ - Proof made specific: 16-year pro; worked with Netflix, Google, Lionsgate.
76
+ - Promise made specific: land premium clients, add six figures, predictable $10K+ months.
77
+
78
+ **Rewrites.**
79
+ - "Every Month You Spend Surviving Feast-or-Famine Is Another Month You're Falling Further Behind… The 'Predictable Freelance Business' Blueprint That 1,000+ Members Used to Finally Escape the Panic, Land Premium Clients, and Add Six Figures to Their Income."
80
+ - "7-Figure Freelancing Expert Who Worked With Netflix, Google and Lionsgate Reveals… New 'Client Domino System' That's Helping THOUSANDS of Freelancers Go From Feast-Or-Famine To Predictable $10K+ Months (Without Cold Pitching, Job Boards, Or Begging For Referrals)."
81
+
82
+ Not more blocks; each block stronger.
83
+
84
+ ---
85
+
86
+ ## Run 4. The melatonin claim ladder (advertorial lead)
87
+
88
+ **Draft idea.** "Melatonin doesn't always work for some people." True, hedged, boring. Nobody stops for it.
89
+
90
+ **Ladder.**
91
+ 1. Melatonin doesn't always work for some people
92
+ 2. Melatonin doesn't always work
93
+ 3. Melatonin doesn't work
94
+ 4. Melatonin hurts sleep
95
+ 5. Melatonin hurts sleep by making you pee more
96
+
97
+ Rung 5 is the one you would look at. You will never find it by staring at facts about melatonin. You find it by knowing rung 5 is psychologically stronger, then working back: over-the-counter doses are 10 to 100x the effective amount, the surge wears off mid-night, you wake, you pee, you cannot get back down, and your receptors downregulate over time. Walk it back in the body, not in the hook. If it cannot be made true, kill it and take rung 4.
98
+
99
+ ---
100
+
101
+ ## Run 5. Evenkeel Reset Gummies (fictional, cortisol-style supplement, ad level)
102
+
103
+ Fictional brand, fictional competitor, fictional facts. Saffron extract, magnesium glycinate, rhodiola, evening gummy. Founder is a former ER nurse. Sister SKUs: Night Tea and Glow Collagen (which already claims cortisol belly).
104
+
105
+ **Draft.** "Feeling stressed and wired at night? Evenkeel Reset Gummies combine saffron, magnesium and rhodiola to support healthy cortisol levels so you can relax, sleep better and wake up refreshed. Formulated by a nurse. Clinically studied ingredients. Try it risk-free."
106
+
107
+ **Frame.** Ad. Women 40 to 55 who fall asleep fine and wake at 3AM. Money symptom: the 3AM wake with a racing mind. Not "stress".
108
+
109
+ **See.**
110
+ - Body scan: nothing. Grey from the first word. "Support healthy cortisol levels" is a phrase from every label in the category.
111
+ - Copy blocks: promise (relax, sleep, refreshed, rated 2), curiosity (none; the ingredients are listed, not explained), proof (clinically studied, 3), pain (stressed and wired, 2), constraint none, condition (risk-free, 4).
112
+ - Colour: "formulated by a nurse" is the only owned line, and it is buried. Everything else is grey.
113
+ - Title: "A calming gummy." Rated 2.
114
+ - Ideas: several benefits, no idea.
115
+
116
+ **Attract (full, because the idea is under 6).**
117
+ - Unique candidates: an ER nurse founder; the evening timing; saffron as the star (magnesium is everywhere, rhodiola is a morning herb).
118
+ - North star: what would have to be true for an evening saffron gummy made by an ER nurse to be the best possible answer to the 3AM wake? Cortisol is supposed to fall at night. In these women it does not; it spikes around 3AM (a second surge), and that surge is what opens their eyes. Falling asleep was never the problem. Staying asleep is. So the whole category, built on falling asleep (melatonin, Night Tea, sleepy teas), is aimed at the wrong hour.
119
+ - Spokesperson: an ER nurse works nights, lives on cortisol, and sees what a 3AM surge does to a body every shift. She would notice the second surge before a researcher would. Irreplaceable, because her shift is the mechanism.
120
+ - Sympathetic magic: saffron is harvested at dawn, by hand, from a flower that opens for one day. The most expensive spice on earth, calming by tradition. Weaker than salmon, but it carries "rare, hand-picked, precious". Magnesium glycinate rhymes with "the mineral the night spends".
121
+ - Binary: fall-asleep products versus stay-asleep products. Or: night cortisol dropping versus night cortisol spiking.
122
+ - Name: the 3AM surge. Night cortisol. Check: "surge" implies it can be flattened. Good.
123
+ - Catalog check: Glow Collagen owns cortisol belly and face. Reset owns night cortisol and the 3AM wake. Night Tea owns falling asleep. No collision once the lane is "stay asleep".
124
+
125
+ **One breath.** "You don't have a falling-asleep problem, you have a 3AM problem. Your cortisol is supposed to fall all night; yours spikes at 3AM and that surge is what opens your eyes. An ER nurse who lived that surge every night shift built an evening gummy with saffron and magnesium to flatten it, so you sleep through instead of waking up wired."
126
+
127
+ **Make.**
128
+ - Relevance: 3AM by name, racing mind, ceiling-staring. Not "sleep better".
129
+ - Contrast, good thing bad: "Your sleep tea is why you wake at 3AM." (Fall-asleep products push you down hard and let cortisol bounce back.) Bad thing good: "Skip the bedtime routine. Fix 3AM instead."
130
+ - Visual: a cortisol line that should slide downhill all night and instead jumps at 3 like a spike on a heart monitor. The nurse has seen that spike on a screen.
131
+ - Claim ladder: sleep aids don't help everyone stay asleep → sleep aids don't fix 3AM → sleep aids don't work after midnight → sleep aids cause the 3AM wake → your sleep tea is causing the 3AM surge.
132
+ - Thesis: the sleep industry sold you falling asleep because it is easy to promise, and left you alone at 3AM.
133
+ - Permission slip: keep your wine, keep your late dinner; the surge is the target.
134
+ - Name and reify: "the 3AM surge".
135
+ - Ratchet: every 3AM wake trains the surge to come earlier.
136
+ - Constraint: one gummy at 8PM.
137
+
138
+ **Rewrites (hooks).**
139
+ - "If you fall asleep fine but wake up at 3AM wired, your cortisol is spiking in the middle of the night, and your sleep tea is making it worse."
140
+ - "An ER nurse figured out why women over 40 wake at 3AM. It isn't stress. It's a cortisol surge that hits while you sleep."
141
+ - "Stop trying to fall asleep faster. You already do. Fix the 3AM surge instead."
142
+
143
+ **Protect.**
144
+ - Redditor: "cortisol at 3AM, is that real?" The body has to carry the walk-back (the normal overnight cortisol curve and the early-morning rise). Rung 5 of the ladder ("your sleep tea is causing it") is the one compliance will pull; rung 3 or 4 survives.
145
+ - Cold scroll: hook 1 stands alone. Hook 3 needs "3AM" to be understood as a wake, which most of this market will get.
146
+ - Congruence: enemy is the category and the hour, not the woman. Nurse founder rhymes with the mechanism. Glow Collagen keeps belly and face. Night Tea keeps falling asleep. Nothing points the other way.
147
+
148
+ **Probability.** Hook 1 is the highest-probability opener because it names the symptom, the hour, and the enemy. Hook 2 tests the spokesperson lane. Only the account decides.
@@ -30,9 +30,18 @@ This skill ROUTES. Read what the user wants, name the destination skill, and han
30
30
  | Open or create Google Docs/Sheets | `exodus-drive` |
31
31
  | Read their OWN live Meta ads and their numbers — "what are my cheapest ads", "where is my spend going", "show me that ad", "what are people saying in the comments on my ads" (`exodus ads` / `exodus comments`) | `exodus-ads` (read-only; already synced by the dashboard, no Meta MCP needed) |
32
32
  | Import their OWN winning ads from their Meta ad account ("import my winners", "mine my ad account for winners", "exodus winners") | `exodus-winners` (it checks first whether the brand's Meta account is connected — if it is, it reads `exodus ads` and `exodus winners definition` instead of a Meta MCP) |
33
+ | Figure out WHO to sell to, segments, personas, "map the market" | `odf` (or `positioning-spiral` when no coherent positioning exists yet) |
34
+ | The product's mechanism, "why does this work", "which ingredient leads" | `mechanism` |
35
+ | Who the real competitors are, a watch list | `find-competitors` |
36
+ | What patterns the ads show, what competitors do that we don't | `patterns-and-gaps` |
37
+ | Break an ad down, tag a batch, write a brief | `anatomy-of-ads` |
38
+ | Make an existing draft hit harder, "what's wrong with this ad" | `copy-instincts` |
39
+ | Build a bot or a workflow prompt node | `bot-builder` |
40
+ | "remember that", "never do that again" | `remember-that` |
41
+ | "handoff", "wrap up", "what did we do today" | `handoff` |
33
42
  | Update the CLI + skills ("update exodus", "run an exodus update") | run `npx @aicopycoders/exodus@latest update` — the ONLY command that refreshes skills; `doctor` checks health but never rewrites them |
34
43
 
35
- If a custom pipeline skill is installed in this folder (any additional `exodus-*` directory under `.claude/skills/`), it routes the same way — match the user's words to that skill's description.
44
+ The method skills above are Luke's thinking as skills; they run as a conversation and are listed in `CLAUDE.md` under **Method skills**. If a custom pipeline skill is installed in this folder (any additional `exodus-*` directory under `.claude/skills/`), it routes the same way — match the user's words to that skill's description.
36
45
 
37
46
  For any creative judgment along the way (awareness calls, hook critique, which pipeline fits), `exodus-strategist` is the operating persona — it activates with the work.
38
47
 
@@ -0,0 +1,148 @@
1
+ ---
2
+ name: find-competitors
3
+ description: Find the competitors that are actually strong (scaling and working) in any niche, on Meta first and Amazon second, and turn them into a ranked competitor sheet and a watch list. Use it whenever someone asks "who are the real competitors", "who's scaling in this niche", "find brands that are working", "is anyone doing cortisol / sleep / X at scale", "check these brands", "who else sells to these people", "build me a watch list", "who should we spy on", or hands over a list of brands with notes and wants it verified and extended. Three entry points: BRIEF (cold start, no list), HEAD LIST (the user arrives with brands grouped by mechanism and a read on each), and GRID CELL (an existing segment grid with empty cells that need a competitor column). Carries the tool quirks (Atria, Meta Ad Library via Apify, Amazon) and two scripts so nobody rediscovers them. It ends at the ranked sheet and the watch list; ripping ads is the swipe process and patterns and gaps are their own skills.
4
+ ---
5
+
6
+ # Find competitors
7
+
8
+ A competitor is worth following when they are scaling and working, not when they are in the same category. The test is measurable and the search is by mechanism. Everything else here exists to make the first hour not depend on knowing the tools.
9
+
10
+ Think mode until the plan-back. The sweep and the signal check are build bursts: say the plan and the cost back in two lines, get the go, run them comprehensively, write files as they land, report spend. Then back to a short list he can cut. Read the `thinking-vs-build` skill if installed.
11
+
12
+ ## What "strong" means
13
+
14
+ Six signals, each cheap to check. The first five come from Atria's brand feed and the script computes them; the sixth comes from Amazon.
15
+
16
+ | Signal | What it proves | Strong when |
17
+ |---|---|---|
18
+ | Active ads now | Breadth of spend | 50+ |
19
+ | Longest-running active ad, days | A proven angle | 90+ |
20
+ | New ads in the last 30 days | Momentum | 10+ |
21
+ | Creative reuse (re-launched ads) | Winners they trust | 5+ in the top-impression sample |
22
+ | Offer aggression | A funnel that converts cold | 3+ ads with intro-offer words; persona pages and quiz funnels count double |
23
+ | Amazon review count and velocity | Demand off-platform | Top ten in two or more searches, or 1,000+ reviews and growing |
24
+
25
+ **Follow** at three or more strong. **Watch** at two. **Skip** at one or none. Spend estimates from Atria or Motion are a tiebreak only; they are rough. Adjust the thresholds to the category (a niche with three advertisers is not a niche with three hundred), and say so on the sheet.
26
+
27
+ One qualifier that is not a number: are they selling a mechanism (a UMP) or a category? Mechanism brands are the ones worth following. Group the sheet by UMP.
28
+
29
+ ## The doors, ranked by yield
30
+
31
+ 1. **The user's head list**, with their read on each brand. Highest signal, fastest. Keep their notes verbatim as the brief.
32
+ 2. **Keyword lanes on the Meta Ad Library**, 25 to 40 queries from the grid in `references/query-grid.md`, aggregated by advertiser. Run through Apify with `scripts/adlib_sweep.py`.
33
+ 3. **Amazon category and ingredient shortlists.** Brand names fall out of the top tens; review volume is a signal on its own.
34
+ 4. **Neighbours of a known strong brand.** Same Atria lane, named in comments and Reddit threads, the domains behind advertorial pages, the brands that share a spokesperson type.
35
+ 5. **Motion and Foreplay library pages** for the lanes, when the user has access.
36
+ 6. **A shadow Instagram account** saturated in the market and scanned for outliers. Slowest, best for zeitgeist. The Scout library in Exodus (`exodus-hooks`) already holds this when the brand is set up there.
37
+
38
+ ## Entry point 1: BRIEF (cold start)
39
+
40
+ The strategist has a product and knows nothing about the market.
41
+
42
+ 1. **Load and say back.** Product, outcome sold today, star ingredient, the segment grid if one exists, focus order if agreed. One paragraph.
43
+ 2. **Build the query grid.** Outcome × mechanism × ingredient words, 25 to 40 lanes, written to a text file. Show the lanes as a list the user can cut before spending.
44
+ 3. **Plan-back.** "Sweep N lanes at 150 results each through Apify, about $X, then score the top 30 advertisers on Atria, a few minutes. Go?"
45
+ 4. **Sweep.** `adlib_sweep.py` writes one JSON per lane and `advertisers.md`, ranked by lanes hit and ads seen. Read the aggregate the way the reference describes (mechanism brands, single-angle brands, advertorial fronts, retailers).
46
+ 5. **Score.** `competitor_signals.py` on the top 30 advertisers, plus any brand the Amazon door added. Writes the sheet.
47
+ 6. **Fill the hand columns** for every FOLLOW: UMP, offer, persona pages, why it matters, what to steal. Two lines each, from the longest-running ads the script printed.
48
+ 7. **Hand back** the sheet in think-mode form: top ten as a list, verdicts, one line each. The user cuts. Then the watch list.
49
+
50
+ Stop rule: 20 to 30 scored brands. More does not change the top ten.
51
+
52
+ ## Entry point 2: HEAD LIST (the user arrives with brands and a read)
53
+
54
+ The list is grouped by UMP and each brand has a note ("spends 2M a month", "mostly gut, but they call out weed smokers", "not sure how well it performs").
55
+
56
+ 1. **Keep the notes verbatim** as the brief. They say what to look for, what to discount, and which segments to keep in mind.
57
+ 2. **Score every brand** with the script. No sweep needed yet.
58
+ 3. **Compare the numbers to the read.** Where the signals disagree with the note ("thought they hit cortisol harder" but the longest-running ads are gut), say so in one line per brand. That is the whole value of this pass.
59
+ 4. **Find the blind spots.** Two or three neighbours per UMP group: the same Atria lane, brands named in the comments under the listed brands' ads, the domains behind any advertorial page in the list. Score them.
60
+ 5. **Segments seen.** Any segment a listed brand calls out (a demographic, a state, an occasion) goes on the running segments-seen list for the `odf` skill.
61
+ 6. **Hand back:** the list confirmed and extended, ranked by UMP group, then straight into the rip. Do not rebuild what the user already knows.
62
+
63
+ ## Entry point 3: GRID CELL (an account going horizontal)
64
+
65
+ A brand with winners wants new segments and needs to know who already sells to those people.
66
+
67
+ 1. **Take the grid** and the empty cells from the `odf` skill.
68
+ 2. **Search per cell, by the facet's self-ID words**, not the outcome word. "On HRT and still wake at 3am" finds different advertisers than "sleep supplement". Two to four lanes per cell.
69
+ 3. **Score** whoever appears in two or more of a cell's lanes.
70
+ 4. **Write a competitor column** onto the gap ranking: who is in the cell, how strong, on which UMP, and whether the cell is empty because nobody has found it or because someone tried and left (an inactive brand with a long past run is a warning).
71
+ 5. **Hand back** to the gaps ranking. This entry does not produce a general sheet.
72
+
73
+ ## The tools and their quirks
74
+
75
+ Carry these into every run. They were learned the hard way. Both tools are the member's own paid accounts: get the keys from Atria and Apify, add them to `.env` by hand (`keys pull` leaves lines it does not own alone), and expect the scripts to stop with a clear message when a key is missing.
76
+
77
+ **Atria** (`https://api.tryatria.com/open/v1`, header `X-API-Key`, key in `.env` as `ATRIA_API_KEY`).
78
+ - Brand search: `/brand-library/search?keyword=` (the parameter is `keyword`, not `q`). Do not take the first result blindly; match the name or report the candidates.
79
+ - Brand ads: `/brand-library/{id}/ads?status=active&page_size=50&order=` with `order` in `most_impressions`, `newest`, `oldest`, `most_active`, `best_match`. Fields include `days_running`, `start_date` (ISO with offset), `creative_duplicates`, `impression_rank`, `impression_trend`, `media_format`, `video_duration`, `link_url`.
80
+ - `/ad-library/search` ignores every filter and returns the same feed. Never use it for discovery.
81
+ - Single ad: `/ad-library/{mID}` gives body, title, link, video URLs on the Atria CDN, days running. The public ad page hides the transcript behind login, so ripping needs local transcription.
82
+ - Rate limit: about ten quick calls returns 429. Pause 20 seconds. The script does this.
83
+ - DCO ads store their videos under `cards`, not `videos`.
84
+
85
+ **Meta Ad Library via Apify** (`apify~facebook-ads-scraper`, run-sync-get-dataset-items, token in `.env` as `APIFY_API_TOKEN`).
86
+ - Keyword URL: `search_type=keyword_unordered&active_status=active&country=US&q=<phrase>`. Exact phrase in body: `search_type=keyword_exact_phrase`.
87
+ - Page URL: `search_type=page&view_all_page_id=<id>` sorted by total impressions is the watch-list link shape.
88
+ - Fields: `pageName`, `pageId`, `startDate`, `isActive`, `collationCount`, `snapshot.body`, `snapshot.title`, `snapshot.linkUrl`, `snapshot.videos[]`. Aggregate by `pageName`; the domain in `linkUrl` is the real brand behind advertorial pages.
89
+ - Bills per result. 30 lanes at 150 is a few dollars. Plan-back first.
90
+
91
+ **Amazon.** Search in the in-app browser, two result pages per query, pull ASIN, title, rating, review count, price, sponsored flag with a page script. Free and fast. Rank by relevance × review count. See the reference for the door.
92
+
93
+ **Transcripts** for the rip that follows: ElevenLabs Scribe (`cloud_storage_url` = the video URL) or local whisper-cpp. Standardise on one per project so bank rows are comparable.
94
+
95
+ ## Scripts
96
+
97
+ Run from the workspace root so `.env` is found. Write outputs into the brand's `inputs/research-raw/competitors/` folder.
98
+
99
+ ```
100
+ python3 .claude/skills/find-competitors/scripts/adlib_sweep.py --keywords lanes.txt --outdir <brand>/inputs/research-raw/competitors/adlib --limit 150
101
+ python3 .claude/skills/find-competitors/scripts/competitor_signals.py --file <brand>/inputs/research-raw/competitors/adlib/advertisers.txt --out <brand>/inputs/research-raw/competitors/competitor-sheet.md --json signals.json
102
+ ```
103
+
104
+ `competitor_signals.py --names "Brand A" "Brand B"` scores a head list directly. Edit `STRONG` at the top of the script to change the thresholds for a category. Both scripts print progress per lane or brand so a cut connection loses only what was in flight.
105
+
106
+ ## The sheet
107
+
108
+ ```
109
+ # <Brand> — Competitor sheet (v<n>, <date>)
110
+ Thresholds used: <…>. Verdict: FOLLOW 3+ · WATCH 2 · SKIP 0–1.
111
+
112
+ ## By UMP
113
+ ### <UMP, e.g. cortisol>
114
+ | Brand | Verdict | Active | Longest (d) | Launches 30d | Reuse | Offer | Amazon reviews | Offer & funnel | Why it matters | What to steal |
115
+
116
+ ### <UMP 2>
117
+ …
118
+
119
+ ## Segments seen
120
+ <a> · <b> · <c>
121
+
122
+ ## Watch list (FOLLOW rows)
123
+ | Brand | Ad Library page link | Atria id | Domain |
124
+
125
+ ## Tool feedback from this run
126
+ - …
127
+ ```
128
+
129
+ The sheet lives in the folder. The FOLLOW rows become the watch list: an Ad Library page link per brand (the shape above) saved as a links file the rip can consume, and the brands added to Atria's followed list by hand in the app. When the brand is set up in Exodus, the Scout feed covers the Instagram side.
130
+
131
+ ## Hand-offs
132
+
133
+ - **Rip:** the FOLLOW rows and their longest-running ads go to the swipe process (transcript, headline, body, rank, on-screen text) and into the ad bank next to the brand's own ads.
134
+ - **Patterns:** what the FOLLOW set has in common is the `patterns-and-gaps` skill's job.
135
+ - **Gaps:** the empty cells and the competitor column go to `patterns-and-gaps` and the `odf` grid.
136
+ - **Positioning:** the `positioning-spiral` calls this at its research station.
137
+
138
+ ## Worked example (fictional)
139
+
140
+ Evenkeel Reset Gummies, an evening saffron and magnesium gummy. Entry point BRIEF. Query grid of 28 lanes (see the reference). Sweep returned 214 advertisers; 31 appeared in three or more lanes. Signal check on those 31 plus four brands from the Amazon top tens. Result: nine FOLLOW, eight WATCH, the rest SKIP. The nine grouped into three UMPs: cortisol (four brands, all symptom-stacking in the hook, one at 289 days on a doctor-skit), perimenopause (three, all persona-page funnels), and sleep-onset (two, both melatonin-adjacent, both fatiguing). What it changed upstream: nobody strong owns "stay asleep" as a UMP; the cortisol brands own belly and face, not the night. That is a lane, and it went back to the positioning spiral as the mechanism station's next question.
141
+
142
+ ## What this skill is not
143
+
144
+ It does not rip ads, find the patterns across them, or rank the gaps. It does not decide the mechanism. It finds who is strong, proves it with numbers, and puts them where the rest of the process can reach them.
145
+
146
+ ## Standards
147
+
148
+ Before producing anything, read `STANDARDS.md` at the workspace root (and the section for this output type). Grade the draft against it before showing it; rewrite failures first. When the user reacts with a judgment, a banking phrase, or the same note twice, hand the reaction to `remember-that`. At the end of the session, `handoff` scrapes the chat and proposes entries.
@@ -0,0 +1,78 @@
1
+ # The query grid
2
+
3
+ Competitors are found by mechanism, not by category. "Stress relief supplement" is the worst query in any grid because it returns the category's grey middle. The grid crosses three word lists, and the strong brands show up in several cells at once.
4
+
5
+ ## Build the three lists
6
+
7
+ Take them from the segment grid if one exists (the `odf` skill), otherwise from the brief and ten minutes with the reviews.
8
+
9
+ 1. **Outcome words**, in the market's own language, one per sub-outcome that matters. Not the label ("sleep"), the phrase a buyer types ("wake up at 3am", "can't stay asleep", "puffy face", "rage at my kids").
10
+ 2. **Mechanism words**, the beliefs the market already holds about the cause: cortisol, hormones, gut, nervous system, inflammation, blood sugar, estrogen, testosterone, thyroid. These are Belief facets, and they are also the UMPs competitors sell.
11
+ 3. **Ingredient words**, the star and its likely partners, plus the two or three ingredients the category's big brands lead with.
12
+
13
+ ## Cross them
14
+
15
+ Not every combination. Use these shapes:
16
+
17
+ - outcome alone (the plain phrase): `wake up at 3am`
18
+ - mechanism + outcome: `cortisol belly`, `cortisol 3am`
19
+ - mechanism + product word: `cortisol supplement`, `cortisol gummies`
20
+ - ingredient alone: `saffron supplement`, `magnesium glycinate sleep`
21
+ - ingredient + outcome: `ashwagandha sleep`
22
+ - the demographic word when it is a market on its own: `perimenopause supplement`, `menopause belly`
23
+
24
+ Aim for 25 to 40 lanes. Fewer than 20 misses brands; more than 50 returns the same advertisers again.
25
+
26
+ ## Worked grid (fictional evening gummy: saffron, magnesium glycinate, rhodiola)
27
+
28
+ ```
29
+ # outcome phrases
30
+ wake up at 3am
31
+ can't stay asleep
32
+ racing mind at night
33
+ puffy face morning
34
+ rage at my kids
35
+ stress eating at night
36
+ no energy after 40
37
+ # mechanism + outcome
38
+ cortisol belly
39
+ cortisol 3am
40
+ cortisol face
41
+ high cortisol symptoms
42
+ hormone balance sleep
43
+ nervous system regulation
44
+ # mechanism + product
45
+ cortisol supplement
46
+ cortisol gummies
47
+ cortisol reset
48
+ # ingredients
49
+ saffron supplement
50
+ saffron mood
51
+ magnesium glycinate sleep
52
+ rhodiola energy
53
+ ashwagandha sleep
54
+ ashwagandha cortisol
55
+ l-theanine
56
+ # demographic markets
57
+ perimenopause supplement
58
+ menopause belly
59
+ menopause mood swings
60
+ sleep supplement women over 40
61
+ ```
62
+
63
+ ## Reading the aggregate
64
+
65
+ `adlib_sweep.py` writes `advertisers.md` ranked by how many lanes each advertiser appeared in. Read it like this:
66
+
67
+ - **Many lanes, many ads:** a mechanism brand. Score it first.
68
+ - **One lane, many ads:** a single-angle brand or a lane that is really a different market (a menopause brand in the "menopause belly" lane). Score it if the lane is in your focus order.
69
+ - **Many lanes, few ads:** an affiliate or advertorial page pushing several products. Check the link domains column; the domains are the real brands.
70
+ - **Pages with community-style names** ("The Blood Sugar Journal", "Cholesterol Relief Community") are advertorial fronts. Follow the domain, not the page.
71
+
72
+ Drop retailers, marketplaces, and apps unless the brief says otherwise.
73
+
74
+ ## The Amazon door
75
+
76
+ Amazon is a demand signal off Meta and a source of brand names the ad sweeps miss. For each outcome and ingredient word, search Amazon, take two result pages, and pull ASIN, title, rating, review count, price, and the sponsored flag with a page script in the in-app browser. Dedupe sponsored against organic. Rank by relevance times review count. Brand names in the top ten of two or more searches go into the signal check. Review count and velocity become the sixth column on the sheet.
77
+
78
+ Apify has Amazon search-result actors if the browser route is slow, but they bill per result and the browser route was free and fast for 12 categories.
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ adlib_sweep.py — sweep the Meta Ad Library by keyword lanes via Apify and aggregate advertisers.
4
+
5
+ Usage:
6
+ python3 adlib_sweep.py --keywords lanes.txt --outdir adlib --limit 150 --country US # prints the plan, spends nothing
7
+ python3 adlib_sweep.py --keywords lanes.txt --outdir adlib --limit 150 --country US --yes # runs the sweep
8
+ python3 adlib_sweep.py --keywords lanes.txt --outdir adlib --aggregate-only
9
+
10
+ lanes.txt: one query per line (the outcome × mechanism × ingredient grid, see references/query-grid.md).
11
+
12
+ Needs APIFY_API_TOKEN in .env. Cost: the apify~facebook-ads-scraper actor bills per result; 30 lanes × 150
13
+ results is a few dollars. Without --yes the script prints the plan and stops; say it back, get a go, rerun with --yes.
14
+
15
+ Output:
16
+ <outdir>/<lane>.json raw ads per lane
17
+ <outdir>/advertisers.md advertisers ranked by how many lanes they appear in and ad count,
18
+ with the lanes each one showed up in. This is the candidate list.
19
+ """
20
+ import argparse, json, os, sys, urllib.parse, glob, collections
21
+ from concurrent.futures import ThreadPoolExecutor
22
+
23
+ try:
24
+ import requests
25
+ from dotenv import load_dotenv
26
+ except ImportError:
27
+ sys.exit("pip3 install requests python-dotenv")
28
+
29
+ here = os.path.abspath(os.getcwd())
30
+ for _ in range(7):
31
+ env = os.path.join(here, ".env")
32
+ if os.path.exists(env): load_dotenv(env); break
33
+ here = os.path.dirname(here)
34
+
35
+
36
+ def run(kw, outdir, limit, country, tok):
37
+ u = ("https://www.facebook.com/ads/library/?active_status=active&ad_type=all&country=" + country +
38
+ "&q=" + urllib.parse.quote(kw) + "&search_type=keyword_unordered&media_type=all")
39
+ path = os.path.join(outdir, kw.replace(" ", "_").replace("/", "-") + ".json")
40
+ try:
41
+ r = requests.post(f"https://api.apify.com/v2/acts/apify~facebook-ads-scraper/run-sync-get-dataset-items?token={tok}&timeout=600",
42
+ json={"startUrls": [{"url": u}], "resultsLimit": limit}, timeout=(30, 660))
43
+ d = r.json() if r.status_code == 200 else []
44
+ if not isinstance(d, list): d = []
45
+ json.dump(d, open(path, "w"))
46
+ print(f"{kw:<40} {r.status_code} {len(d)} ads", flush=True)
47
+ except Exception as e:
48
+ print(kw, "ERR", e, flush=True)
49
+
50
+
51
+ def aggregate(outdir):
52
+ by_page = collections.defaultdict(lambda: dict(lanes=set(), ads=0, page_id=None, longest_active=0, links=set()))
53
+ for f in glob.glob(os.path.join(outdir, "*.json")):
54
+ lane = os.path.basename(f)[:-5].replace("_", " ")
55
+ try: ads = json.load(open(f))
56
+ except Exception: continue
57
+ if not isinstance(ads, list): continue
58
+ for a in ads:
59
+ p = a.get("pageName") or (a.get("snapshot") or {}).get("pageName")
60
+ if not p: continue
61
+ e = by_page[p]; e["lanes"].add(lane); e["ads"] += 1; e["page_id"] = e["page_id"] or a.get("pageId")
62
+ lu = (a.get("snapshot") or {}).get("linkUrl")
63
+ if lu: e["links"].add(urllib.parse.urlparse(lu).netloc)
64
+ rows = sorted(by_page.items(), key=lambda kv: (-len(kv[1]["lanes"]), -kv[1]["ads"]))
65
+ with open(os.path.join(outdir, "advertisers.md"), "w") as f:
66
+ f.write("# Advertisers found across lanes\n\nRanked by number of lanes, then ads seen. Feed the top rows to competitor_signals.py.\n\n")
67
+ f.write("| Advertiser | Lanes | Ads seen | Domains | Lanes hit | Ad Library page |\n|---|---|---|---|---|---|\n")
68
+ for p, e in rows:
69
+ link = f"https://www.facebook.com/ads/library/?active_status=active&ad_type=all&country=ALL&view_all_page_id={e['page_id']}&search_type=page&sort_data[mode]=total_impressions&sort_data[direction]=desc" if e["page_id"] else ""
70
+ f.write(f"| {p} | {len(e['lanes'])} | {e['ads']} | {', '.join(sorted(e['links']))[:80]} | {', '.join(sorted(e['lanes']))[:120]} | {link} |\n")
71
+ with open(os.path.join(outdir, "advertisers.txt"), "w") as f:
72
+ for p, e in rows: f.write(p + "\n")
73
+ print("wrote", os.path.join(outdir, "advertisers.md"), f"({len(rows)} advertisers)")
74
+
75
+
76
+ def main():
77
+ ap = argparse.ArgumentParser()
78
+ ap.add_argument("--keywords", required=True)
79
+ ap.add_argument("--outdir", default="adlib")
80
+ ap.add_argument("--limit", type=int, default=150)
81
+ ap.add_argument("--country", default="US")
82
+ ap.add_argument("--workers", type=int, default=5)
83
+ ap.add_argument("--aggregate-only", action="store_true")
84
+ ap.add_argument("--yes", action="store_true", help="actually run the paid sweep")
85
+ a = ap.parse_args()
86
+ os.makedirs(a.outdir, exist_ok=True)
87
+ if not a.aggregate_only:
88
+ tok = os.environ.get("APIFY_API_TOKEN") or sys.exit("APIFY_API_TOKEN not found in .env or environment")
89
+ lanes = [l.strip() for l in open(a.keywords) if l.strip() and not l.startswith("#")]
90
+ if not a.yes:
91
+ print(f"Plan: {len(lanes)} lanes x up to {a.limit} results = up to {len(lanes) * a.limit} billed results on apify~facebook-ads-scraper (country {a.country}).")
92
+ print(f"Outputs land in {a.outdir}/. Nothing was run. Say the plan back, get a go, then rerun with --yes.")
93
+ sys.exit(2)
94
+ with ThreadPoolExecutor(a.workers) as ex:
95
+ list(ex.map(lambda k: run(k, a.outdir, a.limit, a.country, tok), lanes))
96
+ aggregate(a.outdir)
97
+
98
+
99
+ if __name__ == "__main__":
100
+ main()