From viralman
Automates personalized cold emails to GitHub stargazers of similar repos via /gitmail. Batches user decisions on language/subject/targeting/count, finds repos/collects emails, previews body, confirms before SMTP send.
npx claudepluginhub art8engine/viralman --plugin viralmanThis skill uses the workspace's default tool permissions.
Goal: when the user types `/gitmail <project>`, Claude analyzes the project, asks four upfront decisions in one batch, renders a fast single-LLM-call body preview, and only sends for real after the user has explicitly said "send" / "발송해줘".
Provides UI/UX resources: 50+ styles, color palettes, font pairings, guidelines, charts for web/mobile across React, Next.js, Vue, Svelte, Tailwind, React Native, Flutter. Aids planning, building, reviewing interfaces.
Fetches up-to-date documentation from Context7 for libraries and frameworks like React, Next.js, Prisma. Use for setup questions, API references, and code examples.
Explores codebases via GitNexus: discover repos, query execution flows, trace processes, inspect symbol callers/callees, and review architecture.
Share bugs, ideas, or general feedback.
Goal: when the user types /gitmail <project>, Claude analyzes the project, asks four upfront decisions in one batch, renders a fast single-LLM-call body preview, and only sends for real after the user has explicitly said "send" / "발송해줘".
Auto-trigger on:
/gitmailKorean: "이 프로젝트 홍보메일 보내줘", "GitHub 스타거에게 메일", "비슷한 레포 사용자한테 메일", "이거 메일로 알려줘", "asyncprofiler 별표한 사람한테 보내줘"
English: "email people who starred similar repos", "send a launch outreach to stargazers", "promote my project via cold email"
Chinese: "给类似仓库的 stargazer 发邮件", "推广我的项目 邮件"
Japanese: "似たリポジトリのスターガザーにメール", "プロジェクトを紹介するメール"
Disambiguation rule: when the user's intent is generic ("바이럴 해줘" / "promote this" / "make it viral") without mentioning email, mail, gitmail, or GitHub stargazers, do NOT auto-trigger gitmail — defer to the
viralskill's router (X / Reddit / Gitmail 3-way question). gitmail only auto-triggers when the words above name email/mail/gitmail explicitly.
When the user enters via /gitmail, the argument parsing in commands/gitmail.md runs first.
Binary check (run this first). The skill drives the viralman console-command and its subcommands (viralman gitmail, viralman save-creds, …). The binary is created by /viralman-setup Step 0; if which viralman is empty, route the user to /viralman-setup and stop. Do not ls/find/probe ~/.claude/plugins/cache/viralman/** to find scripts directly — the permission layer flags that traversal as credential discovery and blocks it. There is no longer a "switch to the repo" requirement: viralman works from any cwd.
Permission denial protocol. If a Bash invocation in this skill (e.g. viralman gitmail recipients --max-users 500) is denied by the Claude Code permission layer or auto-mode classifier — typical reasons include "mass scraping" / "unsolicited cold email" classifications, even though the run is the user-initiated gitmail flow — do not attempt to self-edit ~/.claude/settings.json or .claude/settings.local.json. Claude Code's harness blocks agent self-permission-grants and the second attempt will be denied for that reason instead. Surface the snippet to the user verbatim and stop:
명령이 권한 레이어에 막혔습니다. ~/.claude/settings.json 의 permissions.allow
배열에 아래를 추가하시면 (한 번만 paste, 새 세션부터 적용) 다음부터는
가벼운 명령들이 매번 묻지 않고 바로 진행됩니다:
"Bash(viralman:*)"
자세한 안내는 /viralman-setup의 Step 5 참조. auto-mode classifier 는 별개
레이어라 대량 발송류는 위 룰을 추가해도 한 번 다이얼로그가 뜰 수 있습니다 —
그땐 한 번만 Allow 눌러주세요.
Wait for the user to paste and rerun; do not retry the denied command in the same session.
Run viralman save-creds --show-keys and confirm (never print the values):
GITHUB_TOKEN — without it, GitHub API caps at 60 req/h.ANTHROPIC_API_KEY / OPENAI_API_KEY / GEMINI_API_KEY, or detect Claude Code CLI via which claude.SMTP_HOST, SMTP_USER, SMTP_PASSWORD, SMTP_FROM.If anything is missing, route the user to /viralman-setup gitmail and stop. Never read or print .env values.
Follow skills/copy-prep/SKILL.md §Project intent capture for the URL parsing rule, the keyword derivation, and the 2–3 line confirmation print. The struct it produces (url / name / description / keyword) feeds Step 3's viralman gitmail recipients --description "$DESC" --project-url "$URL".
Critical: do not run viralman gitmail recipients or send-from-recipients until the batch question is answered. Letting the user decide upfront is the core of this skill.
Use the AskUserQuestion tool to surface all four questions in one call (multiSelect=false on every one):
Use the option list from skills/copy-prep/SKILL.md §Language picker. The selected value becomes a prefix to --tone (e.g. English → --tone "in English, ..."). For Korean, the prefix is omitted.
Use the 5-option table from skills/copy-prep/SKILL.md §Subject format presets. Pass each option's example into the preview field of AskUserQuestion. The manual (직접 입력하기) option must be last.
If the user picks manual, follow §Manual override pattern from copy-prep — gitmail's no-LLM path is --prewritten-subject "<subject>" --prewritten-body /tmp/gitmail_user_body.txt (placeholders {login}, {starred_repo}, {project_name}, {project_url} substitute per recipient). Save the user-supplied body to /tmp/gitmail_user_body.txt. Step 4 keeps --dry-run so the user still sees a literal preview before live send.
| option | behavior |
|---|---|
| Recommended seeds (Claude picks) | Claude proposes 3–5 domain-specific repos based on the Step 1 analysis. Highest accuracy. |
| Keyword search | The user types keywords directly. Maximum flexibility. |
| Auto (LLM extracts) | viralman gitmail's analyse step decides on its own. Fast but average accuracy. |
For "Recommended seeds" — Claude shows the chosen seed repos explicitly (e.g. "for JVM monitoring I'll go with jvm-profiling-tools/async-profiler, glowroot/glowroot, pinpoint-apm/pinpoint, prometheus/jmx_exporter"). Honor any pushback from the user; otherwise proceed.
| option | description |
|---|---|
| 100 (recommended for first try) | First send. Safe on free Gmail / Workspace / any SMTP. |
| 500 | Exactly the free @gmail.com daily ceiling (500 msg / 24h rolling). The largest single-batch send. |
| 1000 | Workspace recommended (within 2,000/24h). On free Gmail this needs a 2-day split. |
| 1500 | GitHub collection cap (GraphQL+REST dual bucket). Free Gmail = 3-day split, Workspace = single day. |
| Other | User-supplied (1–1500). Above 1500 will stall on GitHub rate limit. |
Two caps, separate concerns:
- Collection cap = 1,500 / run — GitHub API budgets (GraphQL 5,000 pt/hr + REST 5,000 req/hr).
- Send cap = SMTP policy — free @gmail.com is 500 msg / rolling 24h, Google Workspace is 2,000 msg / 24h per user.
Even after collecting 1,500, free Gmail can only deliver 500 in a day. When the SMTP daily limit is hit,
step_sendaborts cleanly, emitssend_abortedplus a Korean stderr line, and counts the remainder asunprocessed(retry after the rolling 24h reset).
These do not get a batch question. Translate from what the user says and pass the matching flags. If the user doesn't mention any of these, send no flags (default = no filter, current behavior).
| User says (any language) | Flag(s) to add |
|---|---|
| "팔로워 N명 이상", "followers >= N", "influencer 위주로", "팔로워 많은 사람만" | --min-followers N (default N=500 if unspecified for "influencer") |
| "팔로워 적은 사람", "early adopter", "초기 사용자" | --max-followers 200 (or user-supplied number) |
| "팔로잉 너무 많은 사람 빼", "follow 봇 빼고" | --max-following 5000 |
| "활발한 사람만", "active developer", "공개 레포 N개 이상" | --min-public-repos 3 (or user N) |
| "봇 빼고", "유령 계정 빼", "공개 레포 0개는 빼" | --min-public-repos 1 |
| "신규 가입자 빼", "오래된 계정만", "가입 N일 이상" | --min-account-age-days 90 (or user N) |
| "bio 없는 사람 빼", "프로필 비어있는 사람 빼" | --require-bio |
The filter applies after the GraphQL bulk profile fetch, before the email-resolution loop — so dropped users don't consume the REST PushEvent budget. The recipients_filtered event in the stream reports before/after/dropped plus a per-dimension breakdown.
After all four answers are in, run once:
viralman gitmail recipients \
--description "$DESC" \
--project-url "$URL" \
--max-users $MAX \
[--seed-repos "$SEEDS"] # Q3=recommended seeds OR keywords
[--keywords "$KW"] # Q3=keywords only
[--min-followers N] # only if user requested follower-quality filter
[--min-public-repos N] # only if user asked to exclude bot/ghost accounts
[--min-account-age-days N] # only if user wants seasoned accounts
[--require-bio] # only if user wants bios required
> /tmp/gitmail_recipients.json 2>&1
stdout is a JSONL event stream followed by a final recipients array. Cut everything from the first line that begins with
^\[and save asrecipients_clean.jsonso the next step can read it directly.
When done, summarize briefly for the user (preview up to 8):
Collected N recipients.
1. @asyncuser — alice@example.com (async-profiler ★)
2. @graalfan — bob@example.com (graalvm ★)
... (up to 8)
Generate a body preview with this list? (yes / adjust count / cancel)
Critical: combine --template-only --dry-run so only one LLM call composes the body, then it's reused for all N recipients in the preview. Cuts a 50-recipient dry-run from 13 minutes down to ~16 seconds.
viralman gitmail send-from-recipients \
--recipients-file /tmp/gitmail_recipients_clean.json \
--project-name "$NAME" \
--description "$DESC" \
--project-url "$URL" \
--tone "$LANG_PREFIX$TONE" \
--emphasis "$EMPHASIS" \
--subject-style "$STYLE" \
--template-only --dry-run \
> /tmp/gitmail_dryrun.json 2>&1
Manual path (Q2 = manual / 직접 입력하기) — skip the LLM entirely:
viralman gitmail send-from-recipients \
--recipients-file /tmp/gitmail_recipients_clean.json \
--project-name "$NAME" \
--description "$DESC" \
--project-url "$URL" \
--prewritten-subject "$USER_SUBJECT" \
--prewritten-body /tmp/gitmail_user_body.txt \
--dry-run \
> /tmp/gitmail_dryrun.json 2>&1
Pull the first body from the compose_done event and show it:
[Preview] First mail
TO: <first recipient email>
SUBJECT: <subject>
---
<body>
---
Reply with one of:
• "발송해줘" / "send" / "go" → send for real to all 50 (template_only fast path)
• feedback (e.g. "make it shorter", "change the tone", "more direct subject") → regenerate
• "cancel" → stop
Showing the body counts as composition agreement, NOT send agreement. Body agreement ≠ send agreement (본문 합의 ≠ 발송 합의) — until the user explicitly says "발송해줘" / "send" / "go", do not call the real-send command.
Run only when the user has explicitly signaled intent to send:
viralman gitmail send-from-recipients \
--recipients-file /tmp/gitmail_recipients_clean.json \
--project-name "$NAME" \
--description "$DESC" \
--project-url "$URL" \
--tone "$LANG_PREFIX$TONE" \
--emphasis "$EMPHASIS" \
--subject-style "$STYLE" \
--template-only \
> /tmp/gitmail_send.json 2>&1
Keep --template-only — the body is the one the user already approved, so regenerating costs nothing useful. Calling the LLM 50 more times is wasted budget and time.
Manual path (Q2 = manual / 직접 입력하기) — same as Step 4 minus --dry-run, no LLM call:
viralman gitmail send-from-recipients \
--recipients-file /tmp/gitmail_recipients_clean.json \
--project-name "$NAME" \
--description "$DESC" \
--project-url "$URL" \
--prewritten-subject "$USER_SUBJECT" \
--prewritten-body /tmp/gitmail_user_body.txt \
> /tmp/gitmail_send.json 2>&1
When done, summarize:
Send complete.
Sent: N
Failed: M (grouped by reason: 4xx / 5xx / unsubscribed / invalid-address)
Unsubscribe log: .viralman_unsubscribes.jsonl
No automatic retry on failures. Pass the failure reason through to the user verbatim.
If the user says things like "shorter", "different subject", "change the tone":
--emphasis or --tone--subject-style (or accept a free-form headline)--toneOnly return to Step 3 if the user signals they want to recollect (e.g. "use different seeds", "more recipients").
send-from-recipients without --dry-run until the user has explicitly said "발송해줘" / "send" / "go". Body agreement ≠ send agreement (본문 합의 ≠ 발송 합의).List-Unsubscribe header.--max-users greater than 1500 (the safe GraphQL 5,000 pt/hr + REST 5,000 req/hr ceiling at 3x oversample). For larger campaigns, advise the user to split runs across a secondary GitHub account token.step_send will auto-abort and split the remainder into unprocessed — tell the user when the rolling 24h window resets and how to use the retry-recipients file.viralman gitmail-watch --auto in a separate terminal/tab (auto-picks the newest /tmp/gitmail_send_*.json). Single-line carriage-return display; --once prints once and exits, suitable for a Claude Code statusLine command.~/.viralman/.env.SMTP_RATE_PER_MIN themselves if they want a faster send.