| name | voice-configurator |
| description | Guide user through creating their voice/tone profile by analyzing recent posts. Optionally load a custom tone-of-voice document. Build a system prompt that captures their authentic voice for all generation.
|
| version | 1.0.0 |
Voice Configurator Skill
Guides user through defining their professional voice and tone. Creates a system prompt used by all generation skills.
Triggers
This skill runs when:
- User runs
/setup-content-engine (mandatory voice setup)
- User runs
/configure and selects "Voice & Tone"
- User requests voice recalibration
How It Works
Step 1: Load Recent Posts and Analyze
import sqlite3
import json
import glob
import os
from datetime import datetime, timedelta
def analyze_user_voice(db_path):
"""
Fetch user's last 20-30 own_posts and analyze writing patterns.
"""
conn = sqlite3.connect(db_path)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
cursor.execute("SELECT value FROM config WHERE key = 'linkedin_user'")
user_row = cursor.fetchone()
username = user_row[0] if user_row else "User"
cursor.execute('''
SELECT content_short, posted_at, likes, comments FROM own_posts
WHERE posted_at IS NOT NULL
ORDER BY posted_at DESC
LIMIT 30
''')
posts = [dict(row) for row in cursor.fetchall()]
conn.close()
if not posts:
return None
analysis = {
'sample_count': len(posts),
'posts': posts,
'avg_engagement': sum(p['likes'] + p['comments'] for p in posts) / len(posts)
}
return analysis
def profile_voice_from_posts(analysis):
"""
Use Claude to analyze writing patterns across recent posts.
"""
posts_sample = "\n\n".join([
f"Post {i+1} ({p['likes']} likes, {p['comments']} comments):\n{p['content_short']}"
for i, p in enumerate(analysis['posts'][:10])
])
analysis_prompt = f"""
Analyze these {analysis['sample_count']} recent LinkedIn posts and describe the author's writing voice:
{posts_sample}
Provide JSON with:
{{
"tone": "1-3 word description (e.g., 'professional but witty', 'mentor-like', 'thought-leader')",
"length_pref": "short/medium/long (sentence count typical)",
"emoji_usage": "none/minimal/moderate/frequent (count typical per post)",
"hashtag_usage": "none/minimal/moderate/frequent (count typical per post)",
"common_hooks": ["list", "of", "hook", "types", "used"],
"signature_phrases": ["phrases or patterns that appear often"],
"authenticity_level": "very authentic / somewhat authentic / professional but guarded",
"key_themes": ["topics", "author", "focuses", "on"],
"writing_characteristics": "Short description of what makes this voice unique"
}}
"""
result = call_claude(analysis_prompt)
return result
def present_voice_analysis(analysis, username):
"""
Present findings to user in conversational format.
"""
voice = profile_voice_from_posts(analysis)
summary = f"""
✓ I analyzed your last {analysis['sample_count']} posts and here's what I found:
**Tone:** {voice['tone']}
Your voice comes across as {voice['tone']}. You're not trying to be something you're not.
**Length:** {voice['length_pref']}
You typically write {voice['length_pref']} posts — usually {3 if voice['length_pref'] == 'short' else (5 if voice['length_pref'] == 'medium' else 8)}+ sentences.
**Emoji & Hashtag Usage:**
- Emojis: {voice['emoji_usage']} (avg {1 if voice['emoji_usage'] == 'minimal' else (3 if voice['emoji_usage'] == 'moderate' else 5)} per post)
- Hashtags: {voice['hashtag_usage']} (avg {1 if voice['hashtag_usage'] == 'minimal' else (3 if voice['hashtag_usage'] == 'moderate' else 5)} per post)
**Hook Types You Use:**
{', '.join(voice['common_hooks'])}
**What Makes Your Voice Distinctive:**
{voice['writing_characteristics']}
**Key Topics You Cover:**
{', '.join(voice['key_themes'])}
Is this accurate? Would you like to:
1. Refine these settings manually
2. Upload a custom tone-of-voice document
3. Keep these and move to authority topics
"""
return summary, voice
Step 2: Offer Voice Configuration Options
Present user with three paths:
Option A: Accept and Refine Analysis
Offer adjustments:
- Change tone to something more/less [current]
- Adjust length preference
- Toggle emoji/hashtag usage up or down
User can tweak each field interactively.
Option B: Upload Custom Voice Guide
User can paste or upload a markdown document. Example structure:
# My Brand Voice
## Tone
Casual but credible. I sound like a mentor, not a guru.
## Topics
Startup scaling, remote culture, founder wellness
## Writing Patterns
- Always ask a question at the end
- Use real stories from experience
- Avoid hype language
- Short, punchy sentences preferred
## Example Posts
[Link to 3 favorite posts]
Store in user_voice_config.custom_voice_doc.
Option C: Manual Entry
Let user fill in fields directly:
- Tone (free text or predefined list)
- Length (short/medium/long)
- Emoji usage (none/minimal/moderate/frequent)
- Hashtag usage (none/minimal/moderate/frequent)
Step 3: Collect Authority Topics
def collect_authority_topics(username):
"""
Ask user to define topics they want to be known for.
"""
prompt = f"""
{username}, what topics do you want to be known for? Think about:
- Your expertise areas
- Topics you're passionate about
- What you want people to associate with your brand
Examples: "Startup scaling", "Remote culture", "Founder wellness", "AI for business"
Enter 3-5 topics (comma-separated):
"""
topics = get_user_input(prompt).split(',')
topics = [t.strip() for t in topics if t.strip()]
return topics
Step 4: Build System Prompt
def build_system_prompt(voice_config, authority_topics, username, custom_doc=None):
"""
Create a comprehensive system prompt for Claude to use in all generation skills.
"""
prompt = f"""You are {username}, a professional in {', '.join(authority_topics)}.
## Your Voice
Tone: {voice_config['tone']}
You write in a {voice_config['tone']} style. Your posts are {voice_config['length_pref']} ({
'1-3 sentences' if voice_config['length_pref'] == 'short' else
'3-5 sentences' if voice_config['length_pref'] == 'medium' else
'5+ sentences or threads'
}).
## Emoji & Hashtag Preferences
- Emoji usage: {voice_config['emoji_usage']} (use {
'0-1' if voice_config['emoji_usage'] == 'minimal' else
'1-3' if voice_config['emoji_usage'] == 'moderate' else
'3-5' if voice_config['emoji_usage'] == 'frequent' else '0'
} per post)
- Hashtag usage: {voice_config['hashtag_usage']} (use {
'0-1' if voice_config['hashtag_usage'] == 'minimal' else
'1-3' if voice_config['hashtag_usage'] == 'moderate' else
'3-5' if voice_config['hashtag_usage'] == 'frequent' else '0'
} per post)
## Authority Topics
You are known for expertise in: {', '.join(authority_topics)}
Write primarily about these topics. When you venture outside, connect it back.
## Writing Patterns
- Be authentic. Share real experiences and lessons.
- Avoid corporate jargon. Use conversational language.
- When asking questions, make them thoughtful, not generic.
- Use specific examples over vague claims.
## What NOT to Do
- Don't be salesy or promotional
- Don't humble-brag
- Don't oversell your insights
- Don't use stock LinkedIn phrases like "thought leader" or "synergy"
## On Hook Types
When asked to write with a specific hook (story, question, contrarian, etc.), prioritize that hook style while staying true to your voice.
{f'## Custom Voice Guide\n{custom_doc}' if custom_doc else ''}
Now write posts that sound authentically like {username}."""
return prompt
Step 5: Store Voice Config
def store_voice_config(db_path, voice_config, authority_topics, system_prompt):
"""
Store in user_voice_config table (singleton, id=1)
"""
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
cursor.execute("SELECT id FROM user_voice_config WHERE id = 1")
exists = cursor.fetchone() is not None
authority_json = json.dumps(authority_topics)
if exists:
cursor.execute('''
UPDATE user_voice_config
SET tone = ?, length_pref = ?, emoji_usage = ?, hashtag_usage = ?,
authority_topics = ?, system_prompt = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = 1
''', (
voice_config['tone'],
voice_config['length_pref'],
voice_config['emoji_usage'],
voice_config['hashtag_usage'],
authority_json,
system_prompt
))
else:
cursor.execute('''
INSERT INTO user_voice_config
(id, tone, length_pref, emoji_usage, hashtag_usage, authority_topics, system_prompt)
VALUES (1, ?, ?, ?, ?, ?, ?)
''', (
voice_config['tone'],
voice_config['length_pref'],
voice_config['emoji_usage'],
voice_config['hashtag_usage'],
authority_json,
system_prompt
))
conn.commit()
conn.close()
Input
Called by /setup-content-engine or /configure. Takes:
regenerate_from_posts (bool) — Re-analyze recent posts?
custom_voice_doc (optional string) — User-provided tone guide
override_values (optional dict) — Manual overrides for tone, length, etc.
Output
Returns JSON:
{
"status": "ok",
"username": "Steve Gustafson",
"voice_config": {
"tone": "mentor-like, thought-provoking",
"length_pref": "medium",
"emoji_usage": "minimal",
"hashtag_usage": "minimal"
},
"authority_topics": ["Startup Scaling", "Remote Culture", "Founder Wellness"],
"system_prompt": "[Full system prompt stored]",
"next_steps": "Run /generate to create post ideas with this voice."
}
User Interaction
The full voice setup takes 5-10 minutes:
- Analysis shown (1-2 min read)
- User chooses refinement path (2-3 min)
- Authority topics entered (1-2 min)
- Confirmation shown (1 min)
Database Schema
Reads:
own_posts (Plugin 1, to analyze voice)
config (Plugin 1, to get username)
Writes:
user_voice_config (1 row, id=1)
Error Handling
if not analysis:
return {
"error": "No posts found. Post some content first, or I can create a default voice profile.",
"offer_default": True
}
If user has no posts, offer defaults based on selected authority topics.
Next Steps
After voice is configured:
- User runs
/setup-content-engine (if starting fresh) to set posting schedule
- Or user runs
/generate to start creating ideas
- Or user runs
/configure to adjust authority topics