Guerrilla marketing
Skill dasein108/slope-studio/.agent-instructions/skills/guerrilla-marketing
"Automated AI short-video studio: idea → published YouTube Short, $0.06/video"
npx -y skills add dasein108/slope-studio --skill guerrilla-marketingAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 3 stars3 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
Operator playbook for `studio guerrilla` — a bot that posts a capped number of on-topic, transcript-grounded comments per day under other creators' recent videos from the configured channel, to attract viewers indirectly. Use when the user wants to RUN the guerrilla comment loop (`tick`), DEPLOY the commenter to a VPS, GENERATE a preview batch to read before posting live, SHOW posted comment links, TUNE the age window / daily cap, CHECK the circuit breaker, MANAGE the watchlist, or DEBUG why nothing posted on a tick. Covers the full pipeline (discover → rank → transcript → topic → highlight → compose → critic → rails → post → track), the ban-defense gates, and the remote-deploy Makefile.
SKILL.md
15.3 KB, as published. Nobody here has run it
guerrilla-marketing — indirect reach via grounded comments
studio guerrilla finds recent uploads on a watchlist of other creators' YouTube
channels, writes one comment that reacts to something specific the video actually
says, scores it with a critic, runs it through a set of rate-discipline rails, and —
only if everything passes — posts it from the configured channel. It then tracks
whether the comment survives and, over weeks, whether any of this moves subscribers.
The mechanism is indirect. Nobody clicks a link — there isn't one; the deny-list makes posting one a code-level impossibility. The bet is that an interesting, on-topic comment makes a reader click the commenter's name, which lands them on the configured channel's own page. That's the whole conversion path: one good comment, one profile click, maybe one subscribe.
This is gray-hat and ToS-adjacent. Automated commenting is spam under YouTube's Terms of Service under any honest reading of the policy — nothing here asks permission or hides behind a gray area. It just tries to stay far enough inside YouTube's actual enforcement behavior (comment removal, shadow-holds, account flags) to not get the channel banned. Every comment posts through the operator's own OAuth client, so every comment is attributable to the real channel — there is no throwaway identity absorbing the risk. Ban-defense is therefore the core design, not an afterthought: a hard daily cap, per-channel cooldowns, a denylist that makes self-promotion impossible in code (not just an LLM instruction), a near-duplicate filter, style/opening diversity checks, and a circuit breaker that watches comment survival and pages the operator when it drops. None of that makes this compliant — it makes it a calculated, monitored bet.
The pipeline
watchlist channels
│
▼
DISCOVER ── new uploads per watched channel (YouTube Data API, cheap quota)
│
▼
RANK ── hard gates: age, comment count, channel size, cooldown, daily cap,
│ active window (cheapest gate first — nothing paid runs yet)
▼
TRANSCRIPT ── cached fetch (network, not LLM); no captions → skip at zero LLM cost
│
▼
TOPIC ── classify against a transcript excerpt; unclear topic → skip
│
▼
HIGHLIGHT ── find one real, specific moment in the video to react to
│
▼
COMPOSE ── several comment variants, each a different style + wording
│
▼
CRITIC ── grounded judge scores each variant against the transcript excerpt
│
▼
RAILS ── denylist, near-duplicate, opening-repeat, style-overuse,
│ timestamp-verify, spacing, active-window, blackout
▼
POST ── the OAuth client posts the #1 surviving variant
│
▼
TRACK ── re-check likes/replies/survival; the circuit breaker lives here
Order is a cost contract as much as control flow: the cheapest gate always runs first, so a video that will never qualify (too old, too many comments already, channel too small, on cooldown) is rejected before anything LLM-priced runs, and a video whose topic is unclear is rejected before the more expensive highlight/compose/ critic chain. A comment that collides with recent posting history (near-duplicate, same opening, style overused) falls through to the next-best composed variant instead of abandoning the video outright — compose already paid for several for exactly this reason.
CLI surface
studio guerrilla --help # always the live source of truth
| Command | What it does |
|---|---|
guerrilla watchlist add <UC...> --channel <ch> --title "..." --median-views N --tags "..." | Add a target channel to comment on. No auto-discovery yet — --median-views is supplied by hand; lowballing it makes the channel-size gate reject that channel's videos for no reason. |
guerrilla watchlist list [--all] | List active watchlist channels (--all includes paused/banned/rejected). |
guerrilla watchlist resume <UC...> --channel <ch> | Un-pause a channel a transient discovery error paused. |
guerrilla tick --channel <ch> [--cap N] [--dry-run] [--max-age-min N] [--cite-timestamps] | One full discover→post cycle. --cap is 10-25 (default 12). |
guerrilla track --channel <ch> | Refresh likes/replies/survival on posted comments; evaluates the circuit breaker. |
guerrilla resume --channel <ch> | Manually clear a tripped circuit breaker — only after investigating. |
guerrilla report --channel <ch> [--write] | Effectiveness by style tag, skip-reason counts, switchback readout. |
guerrilla rollup --channel <ch> --channel-id <UC...> --start <d> --end <d> --experiment-start <d> | Pull daily subs/views into channel_daily so report's switchback section can resolve. |
guerrilla queue --channel <ch> | List comments the critic scored into the "queue" band (3.0-4.0), awaiting approval. |
guerrilla approve <video_id> --channel <ch> [--cap N] | Post one queued comment's exact proposed text through the same rails as a live tick. |
--dry-run is the pre-flight review. It runs discovery through the content
rails (denylist, near-duplicate, opening-repeat, style-overuse, timestamp-verify)
and prints every comment that would post, then stops — no YouTube write, no
rate-limit/cooldown state touched. It costs the same LLM calls a real tick would.
Read every line it prints before ever dropping the flag. This gate is not optional
and nothing skips it.
The gates and rails
Rank gates (rank.py, cheapest first — reject before anything paid runs):
| Gate | Default | Why |
|---|---|---|
| age | < 90 min since publish | early enough to still hold a visible slot |
| comment count | < 40 existing comments | past this a new comment is buried |
| channel size | median views ≥ 5000 | below this there's no audience to reach |
| channel cooldown | 72h since last comment on that channel | spreads posts across the watchlist |
| active window | 09:00-23:00 UTC | a 4am comment reads as a bot |
| daily cap | 10-25/day, operator-set | the hard ceiling on total risk exposure |
Content rails (rails.py, checked immediately before every post):
| Rail | What it catches |
|---|---|
| deny-list | links, the channel's own name/aliases, self-promo phrasing ("subscribe", "check out my..."), hashtags, emoji spam — enforced in code, not just prompted against, so an LLM ignoring instructions once in a thousand tries still can't post it |
| near-duplicate | word-shingle (long comments) or character-gram (short comments) jaccard overlap against recently posted text — two different thresholds because the two representations behave differently at the same cutoff |
| opening-repeat | same first-4-words scaffold as a recent comment — catches template-driven phrasing that near-duplicate alone misses |
| style-overuse | the same style tag (provocative_question / joke / contrarian_take / insight) appearing ≥2 times in the last 5 posts |
| timestamp-verify | any cited M:SS/H:MM:SS must fall inside the video's real duration and land near an actual transcript segment — an unverifiable citation is treated as unsafe |
| spacing | at most one post per tick, jittered interval (~12min base ± jitter), never a fixed cadence |
| publish blackout | suppressed for a window around the operator's own uploads, so comment-driven and upload-driven traffic stay separable |
The critic scores every composed variant 0-5 against on-topic / provocative-or-
funny / not-generic criteria, grounded against the real transcript excerpt (a claim
the excerpt doesn't support scores 0, no partial credit). ≥4.0 auto-posts, 3.0-4.0
queues for guerrilla approve, below that is discarded.
The circuit breaker (track.py): every track run computes survival — the
fraction of the trailing 50 posted comments still live. Below 85%, the loop
auto-pauses (tick returns PAUSED, exits non-zero) and sends a notification.
Comments quietly disappearing or getting held for review is what a shadowban looks
like from the outside — there is no other signal for it. The correct response is
to stop and investigate, never to lower the threshold. Read report's survival-
by-style breakdown to see whether it's concentrated on one channel, one style, or
one time window before deciding it's safe to guerrilla resume.
Autonomous operation
tick is meant to run unattended on a cron-like schedule, roughly every 20-40
minutes across the active window — more often doesn't buy anything, since the
spacing rail already jitters pacing and discovery quota is cheap regardless of poll
frequency; the daily cap is the real limiter, not tick frequency. track should run
at least twice a day so a comment removed hours or days later still gets caught.
rollup needs to run daily once a switchback experiment is running, or report's
effectiveness section stays insufficient_data indefinitely.
--cite-timestamps defaults OFF, and the reason is worth keeping in mind before
turning it on: the highlight stage's found-moment timestamps are unreliable —
in practice they cluster near the start of a video regardless of where the quoted
content actually is — and the "at 0:0X, when he says…" citation phrasing itself
became a recognizably bot-shaped template. The composed comment still reacts to the
found moment either way; turning citations on only changes whether it names a
time. Leave it off unless a specific need for cited timestamps has been verified
against real output.
Ramp the daily cap slowly. Start at the floor (10) and only raise it after
multiple clean weeks of track survival staying comfortably above the breaker
threshold — never raise it in response to the breaker firing, and never raise it
faster than the evidence supports.
Remote deploy
Some networks can't reach YouTube's Data API, transcript endpoints, and LLM
providers reliably from wherever the operator drives from. scripts/remote/guerrilla.mk
deploys the guerrilla pipeline to a VPS with clean, unrestricted internet access
to YouTube and drives it from there, without touching anything else on the box.
Host, SSH key, and any per-operator overrides live in the gitignored
scripts/remote/guerrilla.local.mk — copy scripts/remote/guerrilla.local.mk.example
to it and fill in REMOTE_HOST / SSH_KEY. The tracked guerrilla.mk never
hardcodes a host or key path; it fails fast with a clear error if the local file is
missing.
Local is the source of truth. Every operation is push (code + secrets + db → server) → exec → pull (db + logs → local), so the canonical SQLite db lives on your machine and a
wiped server self-heals on the next run. Never run a cron on the server that mutates the
db — it would fight the push. Schedule the Makefile targets locally instead.
make -f scripts/remote/guerrilla.mk tick # push → one live cycle → pull the db back
make -f scripts/remote/guerrilla.mk track # push → refresh metrics + breaker → pull
make -f scripts/remote/guerrilla.mk preview # push → review batch (posts nothing) → pull batch.json
make -f scripts/remote/guerrilla.mk posted # list every comment actually posted, as YouTube links
make -f scripts/remote/guerrilla.mk deploy # full push + uv sync (no exec)
make -f scripts/remote/guerrilla.mk shell # interactive ssh into the deploy dir
| Target | What it does (all push-then-pull) |
|---|---|
tick | one discover→…→post cycle on the server, db synced back |
track | refresh comment metrics + run the circuit breaker |
report | effectiveness by style tag + the switchback verdict |
resume | clear a tripped circuit breaker |
preview | run scripts/guerrilla_preview.py (never imports post, so no live-post path); pull batch.json |
posted | every posted comment as a direct youtube.com/watch?v=...&lc=... link |
deploy | full _push (code + secrets + db + uv sync), no exec — first run or forced redeploy |
shell | interactive SSH into the remote deploy directory |
Overridable vars: CHANNEL, CAP (daily cap), MAX_AGE_MIN (age window), TARGET /
PER_CHANNEL (preview sizing).
Transcripts on a fresh network may need a proxy. Some IPs — including some
datacenter/VPS ranges — are rate-limited or blocked by YouTube for caption
scraping. Set TRANSCRIPT_PROXY_URL (e.g. http://user:pass@host:port) in .env
to route transcript fetches through an HTTP proxy; leave it unset for a direct
connection. transcript.py retries a few times when a proxy is configured, since a
proxied fetch occasionally hands out a rate-limited exit and a retry lands a
working one.
Hard operator rules
- This posts from the real channel. There is no disposable identity absorbing the risk — treat every comment the bot posts as if it carries the operator's name, because it does.
- Ramp the daily cap slowly. Start low, raise only after clean weeks, never in response to a breaker trip.
- Always run
--dry-runand read every comment before the first live post, and again after any change to the composer, the critic, or the rails. This is a human gate, not a formality — if a comment reads as filler, as bait, or as something you'd wince at being asked about, the pipeline needs work before it posts, not after. - Never bypass the circuit breaker. Lowering the survival threshold or hand-
posting through a tripped breaker doesn't fix whatever tripped it — it just
removes the only sensor that would have told the operator the channel was at
risk.
guerrilla approveitself refuses to post while the breaker is tripped. - Expect a
nullswitchback verdict, and don't read that as failure. YouTube's API exposes no per-comment attribution — there's no way to trace a subscriber back to a specific comment — so the switchback experiment (days on / days off, comparing subscriber gain) is the only design that can answer "does this move the needle at all" without that attribution, and it needsMIN_DAYS_PER_ARMin each arm beforereportreturns anything butinsufficient_data. Anullresult is the system doing its job cheaply; don't re-run hoping for a different answer, and don't read apositiveverdict with a wide confidence interval as more certain than itsci_low/ci_highactually says.
Full reference
docs/50-marketing/guerrilla/ — deeper
architecture notes. The module-level source (studio/guerrilla/*.py) is
extensively self-documented; when this skill and the code disagree, the code wins.