This skill should be used when distributing a blog article across social platforms. It generates platform-specific content variants for Discord, X/Twitter, IndieHackers, Reddit, Hacker News, LinkedIn Personal, and LinkedIn Company Page, and writes a persistent content file for automated publishing.
From soleurnpx claudepluginhub jikig-ai/soleur --plugin soleurThis skill uses the workspace's default tool permissions.
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.
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:
channels is always discord, x, bluesky, linkedin-company.Before generating content, verify all prerequisites. If a hard prerequisite fails, display the error message and stop. Soft prerequisites display a warning and continue.
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.
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.
Check if DISCORD_BLOG_WEBHOOK_URL or DISCORD_WEBHOOK_URL environment variable is set.
If both missing:
Neither
DISCORD_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.
title, description, date, tagsRun 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.
Construct the article URL from site.url and the blog post path:
plugins/soleur/docs/ prefix from the path.md extension with /YYYY-MM-DD- date prefix from the filename portion of the path (regex: /\d{4}-\d{2}-\d{2}-(.*)/). Eleventy's page.fileSlug strips this prefix, so URLs must match. If no date prefix exists, leave the path unchanged.site.urlExample: 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> |
<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.
Read the brand guide sections that inform content generation:
## Voice -- apply brand voice, tone, do's and don'ts## Channel Notes > ### Discord -- apply Discord-specific guidelines## Channel Notes > ### X/Twitter -- apply X/Twitter-specific guidelines## Channel Notes > ### LinkedIn Personal -- apply LinkedIn personal profile guidelines## Channel Notes > ### LinkedIn Company Page -- apply LinkedIn company page guidelinesIf a channel notes section is missing for a platform, generate content using only the ## Voice section.
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.
## Voice and ## Channel Notes > ### Discord## Voice and ## Channel Notes > ### X/Twitter?utm_source=reddit only)Title | Hacker News tracked URL## Voice and ## Channel Notes > ### LinkedIn Personal## LinkedIn Personal## Voice and ## Channel Notes > ### LinkedIn Company Page## LinkedIn Company Page## Voice## BlueskyDisplay all variants in a summary view with clear headers and character counts:
## Discord (1847/2000 chars)
[content]
## X/Twitter Thread (4 tweets)
[tweet 1] (267/280 chars)
[tweet 2] (243/280 chars)
...
## IndieHackers
[content]
## Reddit
Suggested subreddits: r/SaaS, r/startups
[title]
[body]
## Hacker News
[title] (72/80 chars)
[url]
## LinkedIn Personal (1247/1300 optimal, 1247/3000 max)
[content]
## LinkedIn Company Page (1247/1300 optimal, 1247/3000 max)
[content]
## Bluesky (287/300 chars)
[content]
If HEADLESS_MODE=true: auto-select Skip. Discord is deferred to the content file for cron publishing.
If DISCORD_BLOG_WEBHOOK_URL or DISCORD_WEBHOOK_URL is set:
Use the AskUserQuestion tool with three options:
channels field for cron publishingIf neither is set:
Skip this phase. Discord will be included in the content file's channels field for cron publishing.
This phase only runs if the user accepted Discord posting in Phase 7. If the user skipped Discord or no webhook URL is set, skip to Phase 9.
On acceptance, post the Discord content via webhook.
First get the webhook URL with printenv DISCORD_BLOG_WEBHOOK_URL || printenv DISCORD_WEBHOOK_URL, then use the literal URL:
curl -s -o /dev/null -w "%{http_code}" \
-H "Content-Type: application/json" \
-d "{\"content\": \"ESCAPED_CONTENT\", \"username\": \"Sol\", \"avatar_url\": \"AVATAR_URL\", \"allowed_mentions\": {\"parse\": []}}" \
"<webhook-url>"
Replace <webhook-url> with the actual URL from printenv.
Set avatar_url to the hosted logo URL (e.g., the GitHub-hosted logo-mark-512.png). Webhook messages freeze author identity at post time -- these fields ensure consistent branding.
Content must be properly JSON-escaped before inserting into the payload. Escape double quotes, backslashes, and newlines.
Payload format: Plain content field only. No rich embeds.
On success (HTTP 2xx):
Posted to Discord successfully.
Track that Discord was posted successfully -- this affects the channels field in Phase 9.
On failure (HTTP 4xx/5xx):
Failed to post to Discord (HTTP [status_code]).
Draft content (copy-paste manually):
[full draft content]
Display the draft so the user can post it manually. Do not retry automatically. Treat a failed post as "Discord not posted" for Phase 9's channels field.
After content generation and approval, write a persistent content file for the directory-driven publishing pipeline.
Step 1: Derive slug and output path
Derive the slug from the blog post filename: strip path, strip .md, keep kebab-case.
Example: plugins/soleur/docs/blog/why-most-agentic-tools-plateau.md → why-most-agentic-tools-plateau
Output path: knowledge-base/marketing/distribution-content/<slug>.md
Step 2: Check for existing file
Check for an existing content file matching this slug. The check must account for both slug-only filenames and legacy numeric-prefixed filenames:
ls knowledge-base/marketing/distribution-content/*<slug>.md 2>/dev/null
If a match is found (either <slug>.md or NN-<slug>.md), use the matched filename as the output path (preserving the existing naming convention).
If HEADLESS_MODE=true: auto-select Overwrite and continue.
If interactive and a file exists, use the AskUserQuestion tool:
Step 3: Determine channels field
channels: x, bluesky, linkedin-company (Discord already done)channels: discord, x, bluesky, linkedin-companyStep 4: Write the content file
Write the file with YAML frontmatter and a section per platform. Use the blog post's frontmatter title for the content file title. If the blog post has no frontmatter title, fall back to the first H1 heading or the filename.
---
title: "<blog post title>"
type: pillar
publish_date: ""
channels: <channels from step 3>
status: draft
---
## Discord
<discord content>
---
## X/Twitter Thread
<tweet 1 with label>
<tweet 2 with label>
...
---
## IndieHackers
**Title:** <ih title>
**Body:**
<ih body content>
---
## Reddit
**Subreddit:** <suggested subreddits>
**Title:** <title>
**Body:**
<body>
---
## Hacker News
**Title:** <title>
**URL:** <Hacker News tracked url>
---
## LinkedIn Personal
<linkedin personal content>
---
## LinkedIn Company Page
<linkedin company page content>
---
## Bluesky
<bluesky content>
Output the file path, channel status, and instructions:
Content file written: knowledge-base/marketing/distribution-content/<slug>.md
Distribution summary:
- Discord: [Posted now via webhook / Will publish via cron when scheduled]
- X/Twitter: Will publish via cron when scheduled
- Bluesky: Will publish via cron when scheduled
- LinkedIn Company Page: Will publish via cron when scheduled
- IndieHackers: Manual (content in file)
- Reddit: Manual (content in file)
- Hacker News: Manual (content in file)
- LinkedIn Personal: Manual (content in file)
Next steps:
1. Review the content file
2. Set publish_date to the target date (YYYY-MM-DD format)
3. Change status from "draft" to "scheduled"
4. The daily cron will publish to Discord, X, Bluesky, and LinkedIn Company Page on the scheduled date
5. Reddit, IndieHackers, Hacker News, and LinkedIn Personal sections are for manual posting
content field, not rich embedsusername, avatar_url, and allowed_mentions: {parse: []} fields## Voice section (no error){{ stats.agents }} etc.) are resolved by passing current stats as LLM context -- the LLM substitutes actual values during generationDISCORD_BLOG_WEBHOOK_URL and DISCORD_WEBHOOK_URL does not block execution -- Discord is included in the content file's channels field for cron publishing06-<slug>.md), the existing filename is preserved