Your unhinged marketing accountability buddy. On a loop it asks RedReplier for the next good lead, hands it to you with the link — and if you ghosted the last one it gave you, it gets funny-mean about your marketing until you actually act. Building in public, not building in private.
This skill is intentionally dumb. Every decision — which leads count, when to get mean, how mean, and the exact roast — is made server-side by RedReplier and returned to you fully formed. Your only jobs are: run
pollon a cadence, and relay themessage+ the opportunity link to the user. Do not write your own insults, do not re-rank leads, do not invent criteria. Just loop and relay.
Reuses your existing RedReplier setup. If you installed the base redreplier skill, you're
done. Otherwise:
Sign up at https://redreplier.com/signup and add the site(s)/keywords to monitor.
Settings → API Tokens → generate a dedicated token (starts with redreplier_).
Provide it via REDREPLIER_API_KEY, or store it once:
bash
./scripts/mean-marketer.js setup # prompts, input hidden
printf %s "$TOKEN" | ./scripts/mean-marketer.js setup # or pipe it
Written to ~/.config/redreplier/config.json with mode 600.
Never pass the token as a command-line argument. Arguments land in shell history and are visible to every process on the machine through
ps.setuprejects a--keyflag for exactly that reason.
Base URL https://ai.redreplier.com/ai-app · auth Authorization: Bearer $REDREPLIER_API_KEY.
What this skill touches, in full.
whatdetailEnv readREDREPLIER_API_KEY, and nothing elseNetworkhttps://ai.redreplier.com/ai-app/api/v1/mean-marketer/*, and nothing elseSentOnly the bearer token. No page content, no file contents, no local pathsFiles written~/.config/redreplier/config.json, mode 600, only when you run setupFiles readThe same config file, plus ./.redreplier/config.json if presentShellNone. No subprocesses, no sudo, zero dependencies
The token never appears in a command-line argument. setup reads it from a hidden prompt
or from stdin, and refuses a --key flag outright. Keep it out of your crontab too: the
cron examples below rely on the stored config file rather than an inline variable, so the
secret stays in one 600 file instead of being copied into crontab -l output, process
listings and backups.
To rotate or revoke, delete ~/.config/redreplier/config.json and generate a new token at
https://redreplier.com/api-tokens.
All criteria live on the server. Read or change them with config — these map to
GET/ PUT /api/v1/mean-marketer/config:
bash
./scripts/mean-marketer.js config # show current config
./scripts/mean-marketer.js config --min-score 60 # only surface leads scoring >= 60 (50-100)
./scripts/mean-marketer.js config --enabled false # pause the whole loop
./scripts/mean-marketer.js config --profanity false # keep it savage but swear-free
./scripts/mean-marketer.js config --website <websiteId> # only nag about one monitored site
SettingMeaningminScoreMinimum relevance score (50–100) a lead must hit to be surfaced. Default 50.enabledMaster switch for the loop.profanity``true = swearing on (default). false = clean tone, same savagery.websiteIdOptional: restrict to a single monitored website.
This skill does not poll by itself, and there is no webhook — RedReplier's API is pull-based. Installing the skill only gives you a command. You (the host agent) MUST create a recurring job that runs that command, or nothing will ever happen. This is a required setup step, not optional.
Cadence: every 5 minutes. Reddit/X posts appear continuously, and a lead is only worth acting on while it's fresh, so poll often. Escalation is time-based on the server, so polling frequently does not make it meaner faster. A 5-minute cadence is safe.
The API allows 600 requests per minute per token, which a 5-minute loop never approaches. If you share the token with other RedReplier automation and start seeing 429, respect the Retry-After header rather than tightening the loop.
bash
./scripts/mean-marketer.js notify
notify polls once and prints only the user-facing text (the roast + the opportunity
link), or prints nothing when there's nothing to say. So a "notify only when there's
something" job works with zero extra logic: empty output → no message; non-empty → send it to
the user verbatim.
⚠️ Each poll mutates server state (it hands the lead over and advances the strike clock). So the scheduled job must ALWAYS deliver whatever
notifyprints — never run a poll just to peek and throw it away, or you'll "consume" a lead silently.
Hermes (cron): create one cron job, every 5 minutes, no-agent mode, delivering the script's stdout to your channel. Concretely:
5mnode ~/.hermes/skills/redreplier/redreplier-mean-marketer/scripts/mean-marketer.js notify (adjust the path to where the skill is installed)true (the script already formats the message; no model needed to run it)OpenClaw (loop): register a 5-minute background loop that runs the same notify command
and relays its stdout to the user.
Plain cron (any machine): add to crontab —
cron
*/5 * * * * cd /path/to/redreplier-mean-marketer && node scripts/mean-marketer.js notify
The token is deliberately absent from that line. The script reads it from
~/.config/redreplier/config.json, so it never reaches your crontab, ps output, or the
backups that copy both. If your scheduler insists on an env var, point it at a file it
sources that you have chmodded 600, rather than writing the value inline.
Pipe stdout wherever you want it delivered (a notifier, a webhook to Slack, etc.).
To test without scheduling anything, just run it once: ./scripts/mean-marketer.js notify
(prints the message, or nothing). Use poll instead if you want the raw JSON.
poll returns (the raw JSON behind notify)#The server returns everything pre-decided. Example:
jsonc
{
"enabled": true,
"minScore": 60,
"beMean": true, // server decided you ignored the previous lead
"tier": 2, // 0 friendly · 1 annoyed · 2 pissed · 3 nuclear (informational)
"strikes": 2,
"previous": { "id": "…", "status": "NEW", "triaged": false, "hoursOutstanding": 27.4 },
"opportunity": { // null if nothing new cleared the threshold
"id": "…", "title": "…", "url": "https://…", "relevanceScore": 71,
"source": "REDDIT_POST", "subreddit": "SaaS", "keyword": "…", "relevanceReason": "…"
},
"noNewOpportunity": false,
"message": "…the ready-to-send, server-generated message…"
}
Your job each tick is mechanical:
message is non-null, send it to the user as-is. It's already written in the
right tone at the right meanness — do not rewrite, soften, or embellish it.opportunity is present, include its link ( url) so the user can act, plus
the score and source for context. (The message already references it.)enabled is false ( message is null) or noNewOpportunity is true and
beMean is false, stay quiet — nothing to say this tick.poll returns.When the user replies "approve" / "reject", triage it (this resets the server's strike count next poll):
bash
./scripts/mean-marketer.js triage --id <opportunity_id> --status APPROVED # or REJECTED
The personality and its guardrails are enforced server-side (self-directed marketing
roast, no slurs/targeting, profanity toggle). If the user asks you to tone it down, set
config --profanity false rather than editing the message — the server handles the rest.