From limitless
Generates publish-ready content packages (articles, blog posts, social media kits, newsletters, podcast transcripts) from research or notes. Supports multi-platform publishing and AI image generation.
How this skill is triggered — by the user, by Claude, or both
Slash command
/limitless:article-packThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Generate complete, publish-ready content packages from research, notes, or draft material.
Generate complete, publish-ready content packages from research, notes, or draft material.
Activate when the user:
This skill runs as an interactive wizard. Guide the user through each step using AskUserQuestion.
Before starting the wizard, analyze whether sufficient source material exists for a meaningful article pack.
Evaluate the available content for:
If content is SUFFICIENT:
If content is INSUFFICIENT:
Question: "Your topic is interesting, but I need more material to create a strong pack. Want me to research first?"
Options:
- Yes, research this topic for me (I'll search the web)
- Let me add more context/notes first
- Proceed anyway with what we have (lighter output)
- Let me specify what to research
Research loop: When researching, use web search to gather:
After each research round, summarize findings and ask:
Question: "I found [summary]. Is this enough, or should I dig deeper?"
Options:
- That's enough, let's proceed to article pack
- Research more on [specific angle]
- Add this to my notes, I'll provide more context
- Start over with a different focus
First, summarize what we're working with:
Then check for author voice context (memory, previous work, instructions). If no voice context available, ask:
First ask whether the user has existing content to match:
Question: "Do you have existing content (articles, tweets, style guides) I should match the voice to?"
Header: "Voice source"
Options:
- Yes, I have a reference folder (I'll analyze your existing content and extract a voice profile)
- No, let me pick a preset voice
If "Yes, reference folder" → proceed to Voice from Reference Folder below.
If "No, pick a preset" → ask:
Question: "Which writing style fits best?"
Header: "Voice"
Options:
- Opinionated & direct (hot takes, strong positions)
- Professional & measured (thought leadership)
- Academic & thorough (detailed, referenced)
- Casual & conversational (friendly, accessible)
If user selects "Reference folder", ask them to provide a path:
"Give me a folder path containing your existing content. I'll analyze everything in it to extract your voice, tone, and style patterns.
This can include any mix of: articles, blog posts, tweets/threads, LinkedIn posts, style guides, brand docs, newsletter archives, social media exports, or even just notes you've written."
After receiving the path:
Scan the folder recursively for readable content:
- .md, .txt, .html files (articles, posts, guides)
- .json, .csv files (social media exports, tweet archives)
- .pdf files (style guides, brand books)
- Subdirectories (scan everything)
Read a representative sample — prioritize:
Extract a voice profile — analyze the content for:
Present the voice profile to the user for confirmation:
## Voice Profile (extracted from {N} files)
**Tone:** [e.g., "Confident and opinionated with technical depth. Uses humor
sparingly but effectively. Not afraid of hot takes."]
**Signature patterns:**
- [e.g., "Opens with a bold claim or counter-intuitive observation"]
- [e.g., "Uses one-sentence paragraphs for emphasis"]
- [e.g., "Ends sections with a forward-looking hook"]
**Vocabulary:** [e.g., "High technical fluency, avoids corporate jargon,
prefers concrete over abstract"]
**Social voice:** [e.g., "Punchier, more provocative, uses emoji sparingly,
thread-style with cliffhangers between tweets"]
**Sample phrases I'll echo:**
- "[actual phrase from their content]"
- "[actual phrase from their content]"
- "[actual phrase from their content]"
Ask for confirmation:
Question: "Does this voice profile capture your style? I'll use it across all articles and extras."
Header: "Voice OK?"
Options:
- Yes, that's me — proceed
- Close but needs tweaks (let me adjust)
- Rescan with different/additional files
- Scrap this, let me pick a preset instead
If "Close but needs tweaks" — ask what to adjust (more formal? less humor? different opener style?) and update the profile.
Use the voice profile throughout generation — apply it to:
Store the extracted profile in memory for future article-pack runs if the user's persistent memory is available. This way, subsequent packs can offer: "Use your saved voice profile from last time?"
Question: "How many article versions do you need?"
Options:
- 1 article
- 2 articles
- 3 articles
- 4+ articles
For each article (repeat N times), ask a single multi-part question:
Question: "Article {N}: What language, style, and platform?"
Header: "Article {N}"
Options:
- English / Full voice / Substack or Blog
- English / Professional / LinkedIn
- German / Full voice / Substack or Blog
- German / Professional / LinkedIn
- Other (let me specify)
If user selects "Other", follow up for custom combination.
Available dimensions:
| Dimension | Options |
|---|---|
| Language | English, German, French, Spanish, Other |
| Style | Full voice (opinionated), Professional (toned down), Academic, Casual |
| Platform | Substack, LinkedIn, Medium, Company blog, Newsletter, Other |
IMPORTANT: Present ALL options to the user. Do not truncate this list.
Note: Images are NOT listed here — they are handled proactively in a separate step after the user confirms the pack (see "Image Generation" in the Generation Process). This keeps the image decision focused and allows skill assessment.
Question: "What additional content do you want?"
Header: "Extras"
multiSelect: true
Options (present all 9):
1. Social media kit (tweets, LinkedIn posts, hashtags, hooks)
2. Slide deck (presentation version)
3. Twitter/X thread (standalone thread format)
4. Email newsletter version
5. Executive summary (TL;DR card)
6. Press release
7. Quote cards (shareable image quotes)
8. Podcast transcript (NotebookLM-style two-host dialogue)
9. Prose story (creative narrative woven around the topic)
Note: AskUserQuestion only supports 4 options max, so present in batches:
Batch 1 (Core):
Batch 2 (Distribution):
Batch 3 (Creative):
Question: "Where should I save the article pack?"
Header: "Location"
Options:
- Default: ~/.limitless/article-pack/{YYYY-MM-DD}-{Slug}/
- Let me specify a path
- Current directory
CRITICAL: Always get explicit confirmation before generating.
First, display the full configuration summary:
## 📋 Article Pack Configuration
**Source:** [topic summary - 2-3 sentences]
**Voice:** [detected or selected style]
**Articles:**
1. English / Full voice / Substack
2. German / Professional / LinkedIn
**Extras:**
- Social media kit
- Podcast transcript (hosts will be dice-rolled!)
- Prose story
**Images:** Will be decided after confirmation (OpenRouter detected / skill assessment)
**Output:** ~/.limitless/article-pack/{YYYY-MM-DD}-{Slug}/
Then use AskUserQuestion for explicit confirmation:
Question: "Ready to generate? You can also request changes first."
Header: "Confirm"
multiSelect: false
Options:
- Yes, let's go! Generate everything
- Wait, I want to change something (tell me what)
- Add more detail/context first (let me explain)
- Cancel, start over
If user selects "Wait, I want to change something":
Only after explicit "Yes" confirmation: Use TodoWrite to track all deliverables and begin generation.
index.html is always a container shell (app shell with nav bar + iframe) — not the article itself. The primary article lives in its own file. Just open index.html in any browser to navigate the entire pack.
Default location:
~/.limitless/article-pack/{YYYY-MM-DD}-{Slug}/
~/.limitless/article-pack/{YYYY-MM-DD}-{Slug}/
├── assets/ # Generated images
│ ├── 01-{description}.png
│ ├── 02-{description}.png
│ └── ...
├── index.html # Container shell (nav + iframe)
├── {YYYY-MM-DD} - {Title} - {ContentType}.html # All content pages (articles + extras)
├── {YYYY-MM-DD} - {Title} - Social Media Kit.html
├── {YYYY-MM-DD} - {Title} - Slide Deck.html
├── {YYYY-MM-DD} - {Title} - Newsletter.html
├── {YYYY-MM-DD} - {Title} - Thread.html
├── {YYYY-MM-DD} - {Title} - TL;DR.html
├── {YYYY-MM-DD} - {Title} - Press Release.html
├── {YYYY-MM-DD} - {Title} - Podcast Transcript.html
└── {YYYY-MM-DD} - {Title} - Prose Story.html
Naming rules:
index.html is always the container shell — never an article2024-11-29 - Title - EN-Substack.html or 2024-11-29 - Title - Release-Blog.html){YYYY-MM-DD} - {Title} - {Suffix}.html pattern{YYYY-MM-DD} - {Title} - EN-Substack.html) — NOT as index.htmlassets/ folderREQUIRED: After generating all deliverables, create index.html as an app shell — a lightweight page with a nav bar and an iframe that loads content pages. This eliminates nav duplication and gives the pack a polished, single-window experience.
Architecture:
index.html is a dark-themed shell with a top nav bar + full-height iframesrc — users never leave index.htmlRequired HTML structure for index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{Pack Title} — Content Pack</title>
<style>
:root {
--shell-bg: #0f1117;
--nav-bg: #161b22;
--nav-border: #30363d;
--text: #e6edf3;
--text-muted: #8b949e;
--accent: #0969da;
--pill-bg: #21262d;
--pill-hover: #30363d;
--pill-active-bg: #0969da;
--pill-active-text: #ffffff;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { height: 100%; overflow: hidden; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
background: var(--shell-bg);
color: var(--text);
display: flex;
flex-direction: column;
}
.shell-nav {
background: var(--nav-bg);
border-bottom: 1px solid var(--nav-border);
padding: 0.65rem 1.25rem;
display: flex;
align-items: center;
gap: 1.25rem;
flex-shrink: 0;
z-index: 10;
}
.shell-brand {
display: flex;
align-items: center;
gap: 0.5rem;
white-space: nowrap;
flex-shrink: 0;
}
.shell-brand-icon { font-size: 1.1rem; }
.shell-brand-text {
font-size: 0.82rem;
font-weight: 600;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.shell-divider {
width: 1px;
height: 1.4rem;
background: var(--nav-border);
flex-shrink: 0;
}
.shell-links {
display: flex;
gap: 0.4rem;
flex-wrap: wrap;
align-items: center;
}
.shell-links a {
display: inline-flex;
align-items: center;
gap: 0.3rem;
padding: 0.35rem 0.75rem;
border-radius: 6px;
font-size: 0.8rem;
font-weight: 500;
color: var(--text-muted);
text-decoration: none;
background: var(--pill-bg);
border: 1px solid transparent;
transition: all 0.15s ease;
white-space: nowrap;
cursor: pointer;
}
.shell-links a:hover {
color: var(--text);
background: var(--pill-hover);
border-color: var(--nav-border);
}
.shell-links a.active {
color: var(--pill-active-text);
background: var(--pill-active-bg);
border-color: var(--pill-active-bg);
font-weight: 600;
}
.shell-links a .icon { font-size: 0.95rem; line-height: 1; }
.shell-content {
flex: 1;
position: relative;
overflow: hidden;
}
.shell-content iframe {
width: 100%;
height: 100%;
border: none;
background: #ffffff;
}
.shell-loading {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: var(--shell-bg);
z-index: 5;
transition: opacity 0.3s ease;
}
.shell-loading.hidden { opacity: 0; pointer-events: none; }
.shell-loading-spinner {
width: 28px;
height: 28px;
border: 3px solid var(--nav-border);
border-top-color: var(--accent);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
@media (max-width: 640px) {
.shell-nav { flex-wrap: wrap; gap: 0.5rem; padding: 0.5rem 0.75rem; }
.shell-divider { display: none; }
.shell-links { gap: 0.3rem; }
.shell-links a { padding: 0.3rem 0.55rem; font-size: 0.75rem; }
}
</style>
</head>
<body>
<nav class="shell-nav">
<div class="shell-brand">
<span class="shell-brand-icon">{emoji}</span>
<span class="shell-brand-text">{Pack Title}</span>
</div>
<div class="shell-divider"></div>
<div class="shell-links">
<!-- Add one link per generated deliverable. First link gets class="active" -->
<a href="#" data-page="{primary-article-filename}.html" class="active">
<span class="icon">📰</span> Article
</a>
<a href="#" data-page="{dated-filename}-TLDR.html">
<span class="icon">⚡</span> TL;DR
</a>
<!-- Include only links for deliverables that were actually generated -->
</div>
</nav>
<div class="shell-content">
<div class="shell-loading" id="loader">
<div class="shell-loading-spinner"></div>
</div>
<iframe id="content-frame" src="{primary-article-filename}.html"></iframe>
</div>
<script>
(function() {
const links = document.querySelectorAll('.shell-links a');
const frame = document.getElementById('content-frame');
const loader = document.getElementById('loader');
links.forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const page = link.dataset.page;
if (frame.src.endsWith(page)) return;
links.forEach(l => l.classList.remove('active'));
link.classList.add('active');
loader.classList.remove('hidden');
frame.src = page;
});
});
frame.addEventListener('load', () => { loader.classList.add('hidden'); });
document.addEventListener('keydown', (e) => {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
const active = document.querySelector('.shell-links a.active');
const all = Array.from(links);
const idx = all.indexOf(active);
if (e.key === 'ArrowRight' && idx < all.length - 1) all[idx + 1].click();
else if (e.key === 'ArrowLeft' && idx > 0) all[idx - 1].click();
});
})();
</script>
</body>
</html>
Rules:
class="active" by defaultsrc defaults to the primary article filenamefile:// without a web serverBEFORE writing any articles (i.e., right after the user confirms the pack configuration in Step 6), proactively check whether the OpenRouter API key is available:
# Check 1: Environment variable
echo "ENV_CHECK: ${OPENROUTER_API_KEY:+SET}"
# Check 2: Try to read from Claude settings (may have key but not loaded yet)
cat ~/.claude/settings.json 2>/dev/null | grep -o '"OPENROUTER_API_KEY"' || echo "NOT_IN_SETTINGS"
# Check 3: Verify it's not empty/whitespace
[ -n "${OPENROUTER_API_KEY// }" ] && echo "KEY_VALID" || echo "KEY_EMPTY_OR_MISSING"
Interpret results:
ENV_CHECK: SET AND KEY_VALID → Key is availableKEY_EMPTY_OR_MISSING → Key exists but needs restartRegardless of whether the user selected "AI-generated images" as an extra, if the OpenRouter key IS available, proactively ask:
Question: "I detected an OpenRouter API key. Want me to generate images for the pack?"
Header: "Images"
Options:
- AI-generated images (OpenRouter — Gemini, FLUX models)
- Hand-crafted visuals (I'll use design skills to create SVG/HTML/CSS graphics natively)
- Skip images (text-only pack)
If the key is NOT available, still offer the native option:
Question: "No OpenRouter API key detected. How should I handle images?"
Header: "Images"
Options:
- Set up OpenRouter key now (I'll walk you through it)
- Hand-crafted visuals (I'll use design skills to create SVG/HTML/CSS graphics natively)
- Skip images (text-only pack)
If user selects AI-generated images and the key is ready:
python scripts/generate_images.py \
--prompt "prompt from placeholder" \
--output "{folder}/assets/01-name.png"
Models (--model):
| Model | Best For |
|---|---|
gemini (default) | Text, infographics, editorial |
pro | FLUX 2 Pro - stylized |
flex | FLUX 2 Flex - photorealistic |
After generation, replace placeholders with <figure><img> tags.
Only if user chose "Set up OpenRouter key now". Tell the user:
"OpenRouter gives you access to multiple image models (Gemini, FLUX) through one API.
Get your free key: https://openrouter.ai/keys
Paste your key below (starts with
sk-or-):"
After receiving the key, validate format:
[[ "$USER_KEY" =~ ^sk-or-.{20,}$ ]] && echo "FORMAT_OK" || echo "FORMAT_BAD"
Then ask where to save:
Question: "Where should I save this API key?"
Options:
- Claude Code settings (recommended - auto-loads every session)
- Shell profile (~/.bashrc or ~/.zshrc)
- This session only (won't persist)
- Cancel - skip image generation
For Claude Code settings (recommended):
SETTINGS_FILE="$HOME/.claude/settings.json"
mkdir -p ~/.claude
if [ -f "$SETTINGS_FILE" ]; then
cat "$SETTINGS_FILE"
else
echo '{}' > "$SETTINGS_FILE"
fi
Update the JSON to add the env key:
{
"env": {
"OPENROUTER_API_KEY": "sk-or-v1-xxxxx"
}
}
After saving to Claude settings, tell the user they need to restart Claude Code, then stop the wizard — don't continue without the key active.
For shell profile:
SHELL_RC="$HOME/.bashrc"
[ -n "$ZSH_VERSION" ] || [ "$SHELL" = "/bin/zsh" ] && SHELL_RC="$HOME/.zshrc"
grep -q "OPENROUTER_API_KEY" "$SHELL_RC" 2>/dev/null || \
echo 'export OPENROUTER_API_KEY="sk-or-v1-xxxxx"' >> "$SHELL_RC"
source "$SHELL_RC"
For session only: export OPENROUTER_API_KEY="sk-or-v1-xxxxx" (warn: lost on close).
If user selects hand-crafted visuals, before creating anything, perform a skill assessment:
Step 1: Scan for available design-related skills.
Check which design-capable skills are currently available in the environment. Look for skills like frontend-design, design-foundation, or any skill with design/visual/UI capabilities.
Step 2: Report the assessment to the user.
Present a brief report of what you found:
## Design Skills Assessment
**Available:**
- `frontend-design` — Can create production-grade HTML/CSS/SVG graphics,
diagrams, and visual components. Good for: hero banners, architecture
diagrams, comparison charts, data visualizations.
**Not available:**
- No dedicated illustration or icon-design skills detected.
**My native capabilities:**
- HTML/CSS graphics (gradients, shapes, layouts)
- SVG diagrams and charts (flowcharts, architecture diagrams, data viz)
- CSS art and decorative elements
- Mermaid-style diagrams rendered as SVG
**Recommendation:** Between `frontend-design` and my native SVG/CSS capabilities,
I can create [hero banners, architecture diagrams, comparison charts] for this pack.
The output will be crisp vector graphics (SVG) or styled HTML — no raster artifacts.
Adapt the assessment honestly based on what's actually available. Be specific about what each skill can and can't do for image creation.
Step 3: Ask user to confirm.
Question: "Here's what I can do for visuals. Want me to proceed with these capabilities?"
Header: "Confirm"
Options:
- Yes, create visuals with these skills
- Actually, let me set up OpenRouter instead (switch to AI-generated)
- Skip images entirely
Step 4: If confirmed, create the visuals.
.svg or .png (screenshot SVG if PNG needed) in the assets/ folder<figure><img> tags pointing to the created assetsWhat to create (adapt to the article content):
If user chooses to skip images:
<!-- IMAGE: [prompt description] -->Dark-themed HTML with copy-to-clipboard buttons:
NotebookLM-style two-host conversational dialogue.
Host Personality Generator 🎲
IMPORTANT: Use the dice roll script for true randomness:
# Quick archetype mode (default)
python3 scripts/dice_roll.py --hosts 2
# Full 5-dimension personality mode
python3 scripts/dice_roll.py --hosts 2 --mode full
# JSON output for programmatic use
python3 scripts/dice_roll.py --hosts 2 --json
Alternatively, roll manually on each dimension for each host (or let user pick):
| Dimension | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|
| Energy | Chill & laid-back | Thoughtful | Warm | Enthusiastic | Hyped | Chaotic gremlin |
| Expertise | Total newbie | Curious learner | Informed amateur | Knowledgeable | Expert | World authority |
| Style | Analytical | Storyteller | Comedian | Philosopher | Skeptic | Devil's advocate |
| Quirk | Loves analogies | Goes on tangents | "But why though?" | Pop culture refs | Personal anecdotes | Conspiracy brain |
| Vibe | NPR host | Joe Rogan | Tech bro | Academic | Your smart friend | Unhinged genius |
Example rolls:
Generation rules:
Structure:
[INTRO MUSIC]
HOST A (Enthusiastic Storyteller): Okay okay okay, so you know how everyone's
been losing their minds about AI lately?
HOST B (Chill Devil's Advocate): *sighs* Which part? The benchmarks discourse
or the actual interesting stuff nobody's talking about?
HOST A: SEE, that's exactly what we're getting into today...
[Continue natural dialogue covering all article points]
[OUTRO]
Creative narrative that weaves the article's themes into fiction:
Example approaches:
## Article Pack Complete ✅
### Entry Point
→ Open index.html in any browser (container shell with nav — loads all content in-place)
### Articles Generated
1. {dated} - EN-Substack.html (2,400 words, full voice) ← default in shell
2. {dated} - DE-LinkedIn.html (1,800 words, professional)
### Additional Content
- Social Media Kit.html (12 posts, 3 languages)
- Slide Deck.html (12 slides)
- Podcast Transcript.html (15 min read, 2 hosts)
- Prose Story.html (short story, ~1500 words)
- assets/01-hero.png
- assets/02-diagram.png
- assets/03-quote.png
### Container Shell
- index.html (app shell with nav bar + iframe, keyboard nav, loading spinner)
### Output Location
~/.limitless/article-pack/2024-11-29-Article-Title/
### Next Steps
- [ ] Open index.html to review the full pack
- [ ] Zip the folder and share with stakeholders
- [ ] Upload images to platforms
- [ ] Schedule social posts
- [ ] Send newsletter
User: "Generate an article-pack based on this draft about AI trends"
Claude: "Great! Let me walk you through the setup.
I see this is about AI trends for 2026 - covering software engineering
bifurcation, agent risks, world models, robotics, and taste as a moat.
[Checks memory for voice context - finds author style notes]
Step 1 of 4: How many article versions do you need?"
User: Selects "2 articles"
Claude: "Step 2 of 4: Article 1 - What language, style, and platform?"
User: Selects "English / Full voice / Substack"
Claude: "Step 3 of 4: Article 2 - What language, style, and platform?"
User: Selects "German / Professional / LinkedIn"
Claude: "Step 4 of 4: What additional content do you want?"
User: Selects "Social media kit" + "Slide deck"
Claude: "Perfect! Here's your configuration:
[Shows summary]
Ready to generate?"
User: "Yes"
Claude: [Checks OpenRouter key → found! Asks about image strategy]
Claude: "I detected an OpenRouter API key. Want me to generate images?"
User: Selects "AI-generated images (OpenRouter)"
Claude: [Creates TodoWrite, generates articles, then images, then extras, reports completion]
npx claudepluginhub pyros-projects/limitless --plugin limitlessGuides completion of development work by verifying tests, detecting environment, and presenting structured options for merge, PR, or cleanup.
Enforces test-driven development: write failing test first, then minimal code to pass. Use when implementing features or bugfixes.
Guides creation and editing of skills using test-driven development with pressure scenarios and subagents to verify agent compliance.