| name | copy-prep |
| description | Shared building blocks for project-promotion skills — project intent capture, language picker, subject format presets, and the "manual / 직접 입력하기" override pattern. Not user-invokable on its own; channel skills (gitmail / viral / future channels) reference this file so each channel skill can stay focused on its unique pipeline (collection, send, post, log). |
| level | 0 |
copy-prep — shared content-prep building blocks
Not user-invokable. Channel skills cite a section here when they need the shared sub-routine, so they don't each redefine the same option lists / prompts.
When another skill says "follow §Foo from skills/copy-prep/SKILL.md", read this file and apply the named section.
§Project intent capture
Goal: turn $ARGUMENTS (URL and/or free text) into a stable struct the caller can use:
{
url: "<github URL or empty>",
name: "<owner/repo or user-supplied name>",
description: "<free-text description fed into analyse>",
keyword: "<first-pass keyword for search>",
}
Rules:
- First token starts with
https://github.com/ → treat as URL. Do call gh repo view <slug> --json description,homepageUrl to pull the project's GitHub description, and do read the README's first ~2KB (e.g. gh api repos/<slug>/readme --jq .content | base64 -d | head -c 2000). Combine into description so the analyse step sees the project's actual identity, not just the repo slug. Without this, description defaults to the slug and analyse mis-classifies projects (e.g. a JVM monitoring tool with a GC analyser sub-feature gets pitched as "GC tuner"). The 1-2 GitHub API calls cost is dwarfed by the per-recipient GraphQL/REST budget gitmail will spend later.
- If the user supplied free text alongside the URL, append it to the fetched description (don't replace) — user-supplied phrasing usually carries the angle they want emphasised.
- If only free text is given (no URL), use it as
description directly.
- If both URL and description are missing, ask once and wait. Do not guess.
Print 2–3 lines back to the user before the batch question so they have shared context:
Project: <name> (<url>)
What I understood: <one-line summary — based on user description + URL slug>
If this is right, please answer the questions below. If wrong, correct me
and I'll redo it.
§Language picker
Used wherever the caller needs to pick the user-facing language of the output.
| option | description |
|---|
| Korean (default) | 기본값. Korean audience. |
| English | English output. For global outreach. |
| Chinese | 中文. |
| Japanese | 日本語. |
Caller maps the picked value into its own arg shape (e.g. gitmail prefixes --tone "in English, …"; viral sets --lang ko/en). For Korean, the prefix is omitted.
§Voice register vs language (caveat for callers)
Don't confuse the two:
- Language (above) is the output language — shared across channels.
- Voice register is how it sounds within that language. Each channel skill defines its own register options because the right register depends on the channel:
- gitmail's register options are short tone hints (cold-email-appropriate).
- viral's register options are
growth-story | casual-hype | show-and-tell | contrarian-take (post-appropriate).
The language picker IS shared via this file; the register picker is NOT — keep it in the channel skill.
§Subject format presets
Used only by callers that produce a subject line (gitmail; LinkedIn announcement posts; Reddit titles when user wants help). Skip in channels with no subject (X posts; Reddit body-only flow when the title is user-supplied).
| key | pattern | example (Argus, English) |
|---|
auto | LLM picks freely. May vary per recipient. | (LLM-decided) |
headline | "Hi, now you can easily <benefit> too." | Hi, now you can easily watch your JVM in production too. |
tag | [Label] product — one-line value | [New Tool] Argus — JVM monitoring without the heavy agent. |
simple | Under 30 chars, no marketing tone | Argus — JVM monitoring |
manual (직접 입력하기) | User provides the exact subject AND body. Skips the LLM entirely — placeholder substitution per recipient is supported by callers that have per-recipient placeholders (e.g. gitmail). | (waiting for your input) |
Pass each option's example text into the preview field of AskUserQuestion so the user can compare side-by-side. The manual option must be placed last so the LLM-driven choices stay grouped.
§Manual override pattern (직접 입력하기)
Common pattern: when the user picks manual (subject) or otherwise signals "I'll write it myself", do NOT proceed to the next pipeline step. Ask one follow-up prompt that requests:
- the subject line, if the channel has one (free text; placeholders allowed per channel — gitmail supports
{login}, {starred_repo}, {project_name}, {project_url}).
- the body (free text; multi-line OK; same placeholders as above).
The caller routes user-provided content through its channel's "no-LLM" path:
| channel | no-LLM path |
|---|
| gitmail | --prewritten-subject "<subject>" --prewritten-body /tmp/gitmail_user_body.txt (skips compose step entirely; placeholders substitute per recipient). |
| viral / X / Reddit / LinkedIn | Skip the viral-writer agent. Pass the user-provided body directly to the matching scripts/post_*.py. Run the ai-tell-sniffer once for safety; if the user explicitly says "post as-is", honor that and skip sniffer. |
Always preview what will be sent before the live action — even on the no-LLM path. The preview proves placeholder substitution worked.
§Boundaries
- This file never holds credentials, never reads
~/.viralman/.env, never calls scripts on its own. Channel skills do that.
- The structs and option tables defined here are stable contracts; if a channel skill needs a new field, add it here so all callers see the same shape.
- This skill is not user-invokable. There is no
/copy-prep slash command. Trigger phrases are intentionally absent.