Social Distribute
Generate platform-specific content variants from a blog article and write them to a persistent content file for the automated publishing pipeline. Discord can optionally be posted immediately via webhook after approval. The content file feeds into the directory-driven cron pipeline (content-publisher.sh) for scheduled publishing.
Headless Mode Detection
If $ARGUMENTS contains --headless, set HEADLESS_MODE=true and strip --headless from $ARGUMENTS. The remainder is the blog post path.
Argument format: <blog-post-path> [--headless]
Headless defaults for interactive gates:
- Phase 7 (Discord Approval): auto-selects Skip (never auto-post to external platforms without human approval).
channelsis alwaysdiscord, x, bluesky, linkedin-company. - Phase 9 Step 2 (Overwrite Check): auto-selects Overwrite (content is regenerated from the same blog post, so overwriting is idempotent).
Prerequisites
Before generating content, verify all prerequisites. If a hard prerequisite fails, display the error message and stop. Soft prerequisites display a warning and continue.
1. Brand Guide (hard)
Check if knowledge-base/marketing/brand-guide.md exists.
If missing:
No brand guide found. Run the brand architect agent first to establish brand identity:
Use the brand-architect agent to define our brand.
Stop execution.
2. Blog Post Path (hard)
The skill expects a blog post path as an argument (e.g., /soleur:social-distribute plugins/soleur/docs/blog/my-article.md).
If no path provided or file does not exist:
Provide a path to a blog post markdown file:
/soleur:social-distribute <path-to-blog-post.md>
Stop execution.
3. Discord Webhook URL (soft)
Check if DISCORD_BLOG_WEBHOOK_URL or DISCORD_WEBHOOK_URL environment variable is set.
If both missing:
NeitherDISCORD_BLOG_WEBHOOK_URLnorDISCORD_WEBHOOK_URLis set. Discord posting will be skipped (manual output only).
To configure: Server Settings > Integrations > Webhooks > Copy URL from the #blog channel > export DISCORD_BLOG_WEBHOOK_URL="..."
Continue execution -- Discord will be included in the content file's channels field for cron publishing instead of immediate webhook posting.
Content Input
Phase 1: Read Blog Post
- Read the blog post markdown file at the provided path
- Parse YAML frontmatter to extract:
title,description,date,tags
Phase 2: Gather Current Stats
Run these shell commands to get current component counts:
# Count agents (recursive .md files under agents/)
find plugins/soleur/agents -name '*.md' | wc -l
# Count skills (directories with SKILL.md)
find plugins/soleur/skills -maxdepth 2 -name 'SKILL.md' | wc -l
# Count commands
ls plugins/soleur/commands/*.md | wc -l
# Count departments (non-empty top-level dirs under agents/)
find plugins/soleur/agents -mindepth 1 -maxdepth 1 -type d | wc -l
# Get site URL
cat plugins/soleur/docs/_data/site.json
Extract: agents count, skills count, commands count, departments count, site.url.
Phase 3: Build Article URL
Construct the article URL from site.url and the blog post path:
- Strip
plugins/soleur/docs/prefix from the path - Replace
.mdextension with/ - Strip any leading
YYYY-MM-DD-date prefix from the filename portion of the path (regex:/\d{4}-\d{2}-\d{2}-(.*)/). Eleventy'spage.fileSlugstrips this prefix, so URLs must match. If no date prefix exists, leave the path unchanged. - Prepend
site.url
Example: plugins/soleur/docs/blog/2026-03-24-vibe-coding-vs-agentic-engineering.md becomes https://soleur.ai/blog/vibe-coding-vs-agentic-engineering/
UTM Tracking: Derive the campaign slug from the article URL path — strip /blog/ prefix and trailing /. Example: /blog/caas-pillar/ → caas-pillar. If the slug contains characters other than a-z, 0-9, hyphens, or underscores, replace them with hyphens.
Construct platform-specific tracked URLs using this mapping:
| Platform | Tracked URL | |----------|------------| | Discord | <base-url>?utm_source=discord&utm_medium=community&utm_campaign=<slug> | | X/Twitter | <base-url>?utm_source=x&utm_medium=social&utm_campaign=<slug> | | IndieHackers | <base-url>?utm_source=indiehackers&utm_medium=community&utm_campaign=<slug> | | Reddit | <base-url>?utm_source=reddit | | Hacker News | <base-url>?utm_source=hackernews&utm_medium=community&utm_campaign=<slug> | | LinkedIn Personal | <base-url>?utm_source=linkedin-personal&utm_medium=social&utm_campaign=<slug> | | LinkedIn Company Page | <base-url>?utm_source=linkedin-company&utm_medium=social&utm_campaign=<slug> | | Bluesky | <base-url>?utm_source=bluesky&utm_medium=social&utm_campaign=<slug> |
Reddit gets minimal UTM parameters (utm_source only) to reduce spam filter risk — long marketing-looking URLs can trigger irreversible domain reputation damage on Reddit.
Use each platform's tracked URL in the corresponding Phase 5 variant section instead of the bare base URL.
Content Generation
Phase 4: Read Brand Guide
Read the brand guide sections that inform content generation:
- Read
## Voice-- apply brand voice, tone, do's and don'ts - Read
## Channel Notes > ### Discord-- apply Discord-specific guidelines - Read
## Channel Notes > ### X/Twitter-- apply X/Twitter-specific guidelines - Read
## Channel Notes > ### LinkedIn Personal-- apply LinkedIn personal profile guidelines - Read
## Channel Notes > ### LinkedIn Company Page-- apply LinkedIn company page guidelines
If a channel notes section is missing for a platform, generate content using only the ## Voice section.
Phase 5: Generate All Variants
Using the blog post content, stats values, article URL, and brand guide as context, generate all platform-specific variants. The LLM handles template variable substitution (replace {{ stats.agents }} with actual counts), markup stripping (ignore JSON-LD, HTML tags, FAQ accordions), and content adaptation per platform.
Important: Every variant must contain resolved numbers, not template syntax like {{ stats.agents }}. Use the stats gathered in Phase 2.
Lead with the differentiating hook, not the source. For a news-hook / response post (adopting an external term, reacting to an essay or announcement), open EACH variant with the strongest claim about what is unique here — credit the source immediately after, but never bury the original contribution under the news peg. The previews readers actually see — X tweet 1, the LinkedIn first line (shown before "see more"), the Bluesky opener, the Discord first line — must carry the differentiator. Keep that opening hook consistent across all channels for campaign cohesion. Why (#5088): the first loop-engineering draft opened every variant with "Addy Osmani coined…" and buried Soleur's cross-domain expansion in paragraph 3; the operator rewrote all four to lead with the founder-vs-engineer differentiator ("loop engineering replaced the engineer who prompts the coding agent; Soleur replaces the founder who prompts every department").
5.1 Discord Announcement
- Maximum 2000 characters (UTM-tagged URLs are ~60-80 chars longer than bare URLs — account for this in the character budget)
- Include Discord tracked URL
- Match brand voice from
## Voiceand## Channel Notes > ### Discord - Plain text only (no rich embeds)
- Declarative, concrete, builder-to-builder tone
5.2 X/Twitter Thread
- Hook tweet (standalone value, no "thread" announcement)
- Numbered body tweets (2/ 3/ 4/)
- Final tweet with X/Twitter tracked URL and up to one hashtag
- Each tweet maximum 280 characters
- Match brand voice from
## Voiceand## Channel Notes > ### X/Twitter - Links only in final tweet
- No emojis in hook tweet
5.3 IndieHackers Building Update
- Markdown format
- Transparent metrics and numbers
- Building-in-public framing
- Include Indi