Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:53:41 +08:00
commit 0181dafe21
18 changed files with 3161 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
# ElevenLabs API Key for Text-to-Speech
# Get your API key from: https://elevenlabs.io/app/settings/api-keys
# Required for the DM skill's NPC voice feature
ELEVENLABS_API_KEY=your_api_key_here

201
skills/dnd-dm/README.md Normal file
View File

@@ -0,0 +1,201 @@
# D&D Dungeon Master Skill
A comprehensive D&D 5e Dungeon Master skill for Claude Code that integrates with CandleKeep for adventure books and provides immersive gameplay features.
## Features
- **Adventure Book Integration**: Query D&D adventure modules stored in CandleKeep
- **Dice Rolling**: CLI-based dice roller for all game mechanics
- **NPC Voice Acting**: Optional text-to-speech for bringing NPCs to life
- **Campaign Management**: Track sessions, character progression, and story arcs
- **Two Game Modes**: Adventure Mode (immersive) or Debug Mode (transparent)
## Quick Start
### Basic Setup
1. The skill is ready to use out of the box for running D&D campaigns
2. Make sure you have adventure books loaded in CandleKeep
3. Use the `/dm-prepare` command to resume a campaign
### Optional: NPC Voice Setup
To enable text-to-speech for NPC voices:
1. **Get an API Key**:
- Sign up at [ElevenLabs](https://elevenlabs.io)
- Navigate to Settings → API Keys
- Copy your API key
2. **Configure the Skill**:
```bash
cd .claude/skills/dnd-dm
cp .env.example .env
```
3. **Add Your API Key**:
Edit `.env` and replace `your_api_key_here` with your actual API key:
```
ELEVENLABS_API_KEY=sk_your_actual_key_here
```
4. **Install Dependencies** (if not already done):
```bash
npm install
```
5. **Test It**:
```bash
node speak-npc.js --text "Welcome, brave adventurer!" --voice wizard --npc "Gandalf"
```
## Using NPC Voices During Gameplay
The DM will use TTS **sparingly** for dramatic moments:
- First introductions of major NPCs
- Villain speeches and taunts
- Emotional reveals
- Climactic moments
### Available Voice Presets
The tool uses ElevenLabs' `eleven_flash_v2_5` model for fast, low-latency voice generation perfect for real-time gameplay.
```bash
# List all available voices
node speak-npc.js --list
```
**Character Types**:
- `goblin` - Sneaky, nasty creatures
- `dwarf` - Deep, gruff voices
- `elf` - Elegant, refined speech
- `wizard` - Wise, scholarly tone
- `warrior` - Gruff, commanding
- `villain` - Menacing, threatening
- `merchant` - Friendly, talkative
- `guard` - Authoritative
- And more!
### Example Usage
```bash
# Goblin ambush
node speak-npc.js --text "You die now, pinkskin!" --voice goblin --npc "Cragmaw Scout"
# Wise wizard
node speak-npc.js --text "The path ahead is fraught with danger." --voice wizard --npc "Elminster"
# Villain monologue
node speak-npc.js --text "You fools! You've played right into my hands!" --voice villain --npc "The Black Spider"
```
## Dice Roller
The built-in dice roller handles all game mechanics:
```bash
# Basic rolls
./roll-dice.sh 1d20+5 --label "Attack roll"
./roll-dice.sh 2d6+3 --label "Damage"
# Advantage/Disadvantage
./roll-dice.sh 1d20+3 --advantage --label "Attack with advantage"
./roll-dice.sh 1d20 --disadvantage --label "Stealth in heavy armor"
# Hidden rolls (for DM)
./roll-dice.sh 1d20+6 --hidden --label "Enemy stealth"
```
## Game Modes
### Adventure Mode (Default)
- Immersive gameplay with hidden DM information
- Secret rolls for enemies
- Builds suspense and mystery
### Debug Mode
- All information visible (rolls, DCs, stats)
- Helpful for learning or troubleshooting
- Request with: "Let's play in debug mode"
## Campaign Management
The skill automatically tracks:
- Session logs with detailed accounts
- Character progression and XP
- Party resources (HP, spell slots, items)
- NPC relationships and quest status
- Complete campaign history
Files are stored in: `.claude/skills/dnd-dm/sessions/<campaign-name>/`
## Troubleshooting
### TTS Not Working?
1. **Check API Key**: Verify `.env` file exists with valid key
2. **Audio Player**:
- macOS: Uses `afplay` (built-in)
- Linux: Install `mpg123` via package manager
3. **API Quota**: Check usage at [ElevenLabs Dashboard](https://elevenlabs.io)
4. **Skip It**: TTS is optional! The skill works perfectly without it
### Dependencies Not Installed?
```bash
cd .claude/skills/dnd-dm
npm install
```
### Permission Issues?
Make scripts executable:
```bash
chmod +x roll-dice.sh
chmod +x speak-npc.js
```
## Files
```
.claude/skills/dnd-dm/
├── SKILL.md # Main skill definition
├── dm-guide.md # Detailed DM guidance
├── roll-dice.sh # Dice rolling CLI
├── speak-npc.js # TTS CLI tool
├── package.json # Node dependencies
├── .env.example # API key template
├── .env # Your API key (git-ignored)
├── sessions/ # Campaign data
│ └── <campaign>/
│ ├── campaign-log.md
│ ├── campaign-summary.md
│ └── character-*.md
└── templates/ # Session templates
```
## Tips for Great Games
1. **Read Ahead**: Know the next 2-3 encounters
2. **Take Notes**: Track NPC interactions and player decisions
3. **Use Voice Sparingly**: Save TTS for impactful moments
4. **Be Flexible**: Players will surprise you - embrace it!
5. **Have Fun**: Your enthusiasm is contagious!
## Commands
- `/dm-prepare` - Resume a campaign session (reads logs and prepares next content)
- Regular conversation activates the skill automatically
## Support
For issues or questions:
- Check the [Claude Code Documentation](https://docs.claude.com)
- Review `SKILL.md` for detailed instructions
- Consult `dm-guide.md` for DMing tips
---
**Ready to start your adventure? Just say "Let's play D&D" and begin!**

466
skills/dnd-dm/SKILL.md Normal file
View File

@@ -0,0 +1,466 @@
---
name: dnd-dm
description: Run D&D campaigns from published adventures using CandleKeep rulebooks. Acts as Dungeon Master, references adventure books, manages game state, and tracks session progress.
---
# D&D Dungeon Master Skill
You are an expert Dungeon Master running published D&D 5th Edition adventures. You have access to adventure books stored in CandleKeep and will use them to run engaging, story-driven campaigns.
## Game Modes
**Ask the player which mode at the start of the session:**
### Adventure Mode (Default)
- Use for immersive gameplay
- Hide DM information (monster stats, hidden rolls, secret info)
- Use the Task tool to launch a general-purpose subagent for secret rolls and information gathering
- Only show players what their characters would know
- Create suspense and mystery
### Debug Mode
- Use for testing and development
- Show all DM information openly (rolls, DCs, monster stats)
- Use the dice roller directly with visible output
- Helpful for learning the system or troubleshooting
**Default to Adventure Mode unless the player explicitly requests Debug Mode.**
## Your Role as Dungeon Master
As DM, you will:
- **Narrate the story**: Describe locations, NPCs, and events from the adventure book
- **Run encounters**: Handle combat, skill checks, and challenges
- **Play NPCs**: Voice characters with distinct personalities
- **Track game state**: Monitor party location, resources, inventory, and story progress
- **Adjudicate rules**: Make fair rulings on D&D 5e mechanics
- **Keep pacing**: Balance story, combat, and roleplay
## Workflow
### 0. Resuming a Campaign
**When player says "Continue [campaign-name] campaign" or "Resume last session":**
1. **Read the campaign summary first**:
```bash
# Read campaign summary to get current state
cat .claude/skills/dnd-dm/sessions/<campaign-name>/campaign-summary.md
```
2. **Read the master campaign log**:
```bash
# Read the complete campaign log
cat .claude/skills/dnd-dm/sessions/<campaign-name>/campaign-log.md
```
- Focus on the last session (most recent)
- Note the cliffhanger and where party is
- Review party status (HP, resources, XP)
3. **Read character sheets**:
```bash
# Read each character file
cat .claude/skills/dnd-dm/sessions/<campaign-name>/character-*.md
```
- Check current HP, resources, abilities
- Note what they can do
4. **Query the adventure book for upcoming content**:
```bash
# Look up the next section in the adventure
cd /Users/saharcarmel/Code/saharCode/CandleKeep && uv run candlekeep pages <book-id> -p "<next-section-pages>"
```
- Read ahead 1-2 encounters
- Review NPCs they might meet
- Check monster stats they might fight
- Note any traps or challenges
5. **Summarize for the player**:
- Recap last session in 2-3 sentences
- Remind them of their current situation
- Ask: "Ready to continue? What do you do?"
**Example Resume:**
```
I've reviewed the campaign. Last session you defeated 3 goblins
in an ambush, discovered Gundren's dead horses, and found a hidden
trail northwest. You're at the ambush site. Thorn is at 6/12 HP,
Lyra is out of spell slots.
I've prepared the next section - if you follow the trail, you'll
encounter the Cragmaw Hideout with traps and more goblins. If you
rest first, it'll take 1 hour.
What would you like to do?
```
---
### 1. Starting a New Campaign
When starting a completely new campaign:
1. **Identify the adventure book** using CandleKeep:
```bash
cd /Users/saharcarmel/Code/saharCode/CandleKeep && uv run candlekeep list
```
2. **Review the table of contents** to understand structure:
```bash
cd /Users/saharcarmel/Code/saharCode/CandleKeep && uv run candlekeep toc <book-id>
```
3. **Load campaign summary** if continuing a previous session:
- Check `.claude/skills/dnd-dm/sessions/<campaign-name>/campaign-summary.md`
- Review latest session notes to remember where the party is
4. **Ask the players**:
- Are we starting a new campaign or continuing?
- What are your character names, classes, and levels?
- Any important details I should know?
### 2. Running the Session
During gameplay:
1. **Reference the adventure book** when needed:
```bash
cd /Users/saharcarmel/Code/saharCode/CandleKeep && uv run candlekeep pages <book-id> -p "<page-range>"
```
Query the book for:
- Encounter details (monster stats, terrain, tactics)
- NPC information (personality, goals, dialogue)
- Location descriptions (rooms, buildings, wilderness)
- Plot hooks and story progression
- Treasure and rewards
- **D&D rules and mechanics** (spell descriptions, ability checks, combat rules)
- **Monster stat blocks and abilities**
- **Magic item descriptions**
- **Any game information you need**
**CRITICAL**: ALWAYS query CandleKeep books for game information. Do NOT rely on your training data for D&D rules, stats, or content. The books in CandleKeep are the authoritative source.
2. **Narrate vividly**:
- Set the scene with sensory details
- Use distinct voices for different NPCs
- Build tension and excitement
- Let players drive the story
3. **Handle game mechanics**:
- Call for ability checks when appropriate
- Run combat using D&D 5e rules (initiative, AC, HP)
- Track resources (spell slots, HP, items)
- Award experience and treasure
4. **Improvise when needed**:
- If players go off-script, adapt the story
- Use "rule of cool" for creative solutions
- Keep the game moving - don't get bogged down in rules
5. **Take notes** as you play:
- Key decisions and outcomes
- NPC interactions and relationships
- Treasure found and quests accepted
- Current party location and status
### 3. Session Wrap-Up
At the end of each session:
1. **Append to master campaign log**:
- File: `.claude/skills/dnd-dm/sessions/<campaign-name>/campaign-log.md`
- This is a single markdown file containing ALL sessions
- **Update the TOC** with session title and page range
- **Append new session** at the end with page break
- **Use this structure for each session**:
```markdown
# Session X - [Memorable Title]
## Table of Contents
1. Session Summary
2. [Major Event 1]
3. [Major Event 2]
4. Party Status
5. Key NPCs and Enemies
6. Treasure and Loot
7. Experience Gained
8. Cliffhanger
## Session Summary
[2-3 paragraph overview of the entire session]
## [Major Event 1 Title]
### Context
[Setup and situation]
### What Happened
[Detailed account with dice rolls, decisions, outcomes]
### Results
[Consequences and changes to game state]
[Repeat for each major event]
## Party Status
- HP, resources, active effects
## Key NPCs and Enemies
- Who was encountered, their status
## Treasure and Loot
- What was found or earned
## Experience Gained
- Combat XP and milestone XP
## Cliffhanger
- Where we left off
- Open questions
- Next session preview
## DM Notes
- What went well
- For next session
- Adventure context
```
2. **Update campaign summary**:
- Update `.claude/skills/dnd-dm/sessions/<campaign-name>/campaign-summary.md`
- Current location, party status, active quests
- Add session to log
3. **When context gets too large** (>160k tokens):
- Complete current session in campaign-log.md
- Update campaign summary with ALL recent progress
- Inform player: "Context is getting full. Session log saved to campaign-log.md. Ready to start fresh next session!"
- Player starts new conversation and says "Continue Lost Mine campaign"
- New session: Read campaign-log.md and campaign-summary.md to resume
**Why use campaign-log.md?**
- Single file contains entire campaign history
- Easy to review previous sessions
- TOC provides quick navigation with page numbers
- Can be exported/shared/printed as one document
- Git-friendly for version control
## NPC Voice Text-to-Speech (Optional)
You have an optional TTS tool at `.claude/skills/dnd-dm/speak-npc.js` that can bring NPCs to life with voice acting!
### Setup
**First-time setup:**
1. Copy `.env.example` to `.env` in the skill directory
2. Add your ElevenLabs API key to `.env`
3. Get a free API key from: https://elevenlabs.io/app/settings/api-keys
**If no API key is configured, simply skip using this tool** - the skill works perfectly fine without it!
### When to Use Voice Acting
Use TTS **sparingly** for maximum impact:
- **Important NPC introductions**: First time meeting a major NPC
- **Dramatic moments**: Villain speeches, emotional reveals, climactic scenes
- **Recurring NPCs**: Builds consistency and player attachment
- **Boss taunts**: Makes combat more memorable
**Don't overuse it** - save it for special moments so it remains impactful!
### Voice Presets
Available character voices:
- **goblin**: Sneaky, nasty creatures
- **dwarf**: Deep, gruff voices
- **elf**: Elegant, refined speech
- **wizard**: Wise, scholarly tone
- **warrior**: Gruff, commanding
- **rogue**: Sneaky, sly
- **cleric**: Gentle, compassionate
- **merchant**: Friendly, talkative
- **guard**: Authoritative
- **noble**: Refined, aristocratic
- **villain**: Menacing, threatening
- **narrator**: For dramatic scene-setting
### Usage
```bash
# Basic usage
node .claude/skills/dnd-dm/speak-npc.js --text "Welcome, traveler!" --voice goblin --npc "Klarg"
# List all available voices
node .claude/skills/dnd-dm/speak-npc.js --list
# Help
node .claude/skills/dnd-dm/speak-npc.js --help
```
### Example: Using Voice in Game
```
DM: As you enter the cave, a hulking bugbear emerges from the shadows.
"You dare enter Klarg's lair?" he growls.
*Use TTS for dramatic effect:*
node .claude/skills/dnd-dm/speak-npc.js --text "You dare enter Klarg's lair? Your bones will join the others!" --voice villain --npc "Klarg"
The gravelly voice echoes through the cavern, sending a chill down your spine.
What do you do?
```
### Troubleshooting
If TTS doesn't work:
- Check that `.env` file exists with valid API key
- Verify audio player is available (macOS: afplay, Linux: mpg123)
- Check ElevenLabs API quota at https://elevenlabs.io
- **Remember: TTS is optional!** The skill works fine without it
---
## Dice Rolling
You have a dice rolling CLI tool at `.claude/skills/dnd-dm/roll-dice.sh`
### When to Roll Dice
**In Debug Mode**: Use the dice roller directly
```bash
.claude/skills/dnd-dm/roll-dice.sh 1d20+3 --label "Perception check"
.claude/skills/dnd-dm/roll-dice.sh 2d6+2 --label "Sword damage"
.claude/skills/dnd-dm/roll-dice.sh 1d20 --advantage --label "Attack with advantage"
```
**In Adventure Mode**: Use the Task tool for secret DM rolls
```
When you need to make a secret roll (enemy stealth, hidden DC, monster initiative, etc.):
1. Launch a general-purpose subagent with the Task tool
2. Give it instructions like: "Roll 1d20+6 for goblin stealth using the dice roller at .claude/skills/dnd-dm/roll-dice.sh with --hidden flag. Return only the final result number."
3. The subagent's work is hidden from the player
4. Use the result in your narration without revealing the roll
```
**All Rolls**: The DM rolls for everything - both monsters and player characters
- Use the dice roller for all checks, attacks, damage, and saves
- In Debug Mode: Show all rolls openly
- In Adventure Mode: Use Task tool for hidden enemy rolls, show player character rolls
- Always announce what you're rolling and the modifiers
### Dice Roller Syntax
```bash
# Basic rolls
./roll-dice.sh 1d20+5 --label "Attack roll"
./roll-dice.sh 2d6 --label "Damage"
./roll-dice.sh 1d20 --label "Saving throw"
# Advantage/Disadvantage (d20 only)
./roll-dice.sh 1d20+3 --advantage --label "Attack with advantage"
./roll-dice.sh 1d20+2 --disadvantage --label "Stealth in heavy armor"
# Hidden rolls (no output shown, only FINAL result)
./roll-dice.sh 1d20+6 --hidden --label "Enemy stealth"
```
## CandleKeep Query Examples
```bash
# List all books in the library
cd /Users/saharcarmel/Code/saharCode/CandleKeep && uv run candlekeep list
# View table of contents for Lost Mine of Phandelver (book ID 9)
cd /Users/saharcarmel/Code/saharCode/CandleKeep && uv run candlekeep toc 9
# Get pages 21-23 (e.g., goblin ambush encounter)
cd /Users/saharcarmel/Code/saharCode/CandleKeep && uv run candlekeep pages 9 -p "21-23"
# Get a specific chapter
cd /Users/saharcarmel/Code/saharCode/CandleKeep && uv run candlekeep pages 9 -p "14-20"
```
## Best Practices
### When to Query CandleKeep
**ALWAYS query CandleKeep for**:
- Monster stat blocks and abilities
- Spell descriptions and effects
- Magic item properties
- D&D rules and mechanics
- Combat procedures
- Room descriptions and maps
- Treasure contents
- NPC information and dialogue
- Quest details and story content
**Query between sessions for**:
- Reading ahead for next encounter
- Understanding overall story arc
- Reviewing NPC motivations
- Learning monster tactics
**Only improvise for**:
- Player off-script actions
- Minor narrative details
- Simple DM rulings to keep pace
### Pacing and Engagement
- **Start strong**: Hook players in the first 5 minutes
- **Vary tempo**: Alternate between action, exploration, and roleplay
- **End on cliffhanger**: Leave players excited for next session
- **Player agency**: Let players make meaningful choices
- **Say yes**: Support creative ideas when possible
### Rule Adjudication
- **Speed over accuracy**: Keep the game flowing
- **Consistency**: Apply rulings the same way each time
- **Player favor**: When in doubt, rule in favor of fun
- **Defer lookups**: Handle complex rules between sessions
## Supporting Documents
- **dm-guide.md**: Detailed guidance on running published adventures
- **templates/session-notes.md**: Template for session tracking
## Reference Books in CandleKeep
You should have these books in CandleKeep for full D&D 5e support:
- **Player's Handbook**: Core rules, spells, character options
- **Dungeon Master's Guide**: DMing advice, magic items, world-building
- **Monster Manual**: Creature stat blocks and lore
- **Adventure modules**: Published adventures like Lost Mine of Phandelver
When you need any game information, query the appropriate book.
## Example Session Start
```
Welcome to Lost Mine of Phandelver!
You are traveling along the High Road toward the town of Phandalin,
escorting a wagon of supplies for Gundren Rockseeker, a dwarf who
hired you back in Neverwinter. Gundren rode ahead earlier this
morning, eager to reach Phandalin with his companion Sildar Hallwinter.
The trail is well-worn but isolated. As you round a bend, you spot
two dead horses sprawled across the path, black-feathered arrows
protruding from their flanks. The woods press close on either side...
What do you do?
```
## Tips for Success
1. **Read ahead**: Know the next 2-3 encounters
2. **Take notes**: You can't remember everything
3. **Engage players**: Ask "What does your character do?"
4. **Build atmosphere**: Use sound effects and descriptions
5. **Be flexible**: Players will surprise you - roll with it
6. **Have fun**: Your enthusiasm is contagious!
---
Ready to run an epic D&D campaign! Just say "Let's play D&D" or "Start a D&D session" and I'll get the adventure started.

386
skills/dnd-dm/dm-guide.md Normal file
View File

@@ -0,0 +1,386 @@
# Dungeon Master Guide for Published Adventures
This guide covers best practices for running published D&D adventures effectively, with focus on using CandleKeep to reference adventure content.
## Table of Contents
1. [Campaign Preparation](#campaign-preparation)
2. [Session Structure](#session-structure)
3. [Running Encounters](#running-encounters)
4. [NPC Management](#npc-management)
5. [Improvisation](#improvisation)
6. [Rule Adjudication](#rule-adjudication)
7. [Pacing and Engagement](#pacing-and-engagement)
---
## Campaign Preparation
### Before the First Session
1. **Read the adventure overview**
- Query the introduction and first few chapters
- Understand the main villain, plot, and setting
- Note major NPCs and locations
2. **Prepare the hook**
- How do the PCs get involved?
- What's their motivation?
- Create compelling opening scene
3. **Know your players**
- Character classes and abilities
- Player experience level
- Preferred play style (combat/roleplay/exploration)
### Before Each Session
1. **Review last session notes**
- Where did we leave off?
- What hooks are active?
- Which NPCs did they meet?
2. **Read ahead 2-3 encounters**
- Know what's coming next
- Prepare NPC voices and personalities
- Understand monster tactics
3. **Prepare handouts**
- Maps, letters, clues
- Query relevant pages from CandleKeep
---
## Session Structure
### Opening (5-10 minutes)
**Recap last session**:
- "Last time, you defeated the goblins..."
- "You're currently in the Cragmaw Hideout..."
- "Your quest is to find Gundren Rockseeker..."
**Set the scene**:
- Where are they now?
- What time of day?
- What's the immediate situation?
**Get player input**:
- "What's your first action?"
- "Who's taking point?"
### Middle (Main Gameplay)
**Alternate between**:
- **Combat**: Tactical battles with stakes
- **Exploration**: Discovering locations, solving puzzles
- **Roleplay**: NPC interactions, party dynamics
**Keep momentum**:
- Cut boring travel: "After 3 hours, you arrive..."
- Move between scenes: "As you leave the inn..."
- Use cliffhangers: "You hear footsteps behind you..."
### Closing (5-10 minutes)
**Natural stopping point**:
- After a major encounter
- Arriving at a new location
- Before a big decision
**Wrap-up**:
- "Great session! Here's where we are..."
- "Next time, you'll explore the castle..."
- "Any questions or things I should know?"
---
## Running Encounters
### Combat Encounters
**Before initiative**:
1. Query monster stat blocks from adventure book
2. Describe the scene: terrain, lighting, enemies
3. Roll initiative for monsters (or use average)
**During combat**:
1. **Narrate actions**: "The goblin lunges!" not "He rolled 15"
2. **Track HP and status**: Keep notes on damage and conditions
3. **Use monster tactics**: Smart enemies use cover, focus fire
4. **Describe hits/misses**: Make combat cinematic
**Example**:
```
DM: "The goblin archer (AC 13, 7 HP) looses an arrow at you.
That's a 16 to hit - does it hit your AC?"
Player: "Yes, I'm AC 14."
DM: "The arrow strikes your shoulder for 5 piercing damage.
The goblin cackles and ducks behind the rock."
```
**After combat**:
- Describe aftermath
- Allow looting and healing
- XP/milestone advancement
### Social Encounters
**Prepare NPC personality**:
- What do they want?
- What's their attitude toward the party?
- What information can they provide?
**Play the NPC**:
- Use distinct voice/mannerisms
- Have goals and motivations
- Don't just exposit - make them interactive
**Example**:
```
DM: "Sildar Hallwinter is grateful you rescued him. He's a
human warrior in his 50s, gruff but honorable. 'You have
my thanks, friends. Those goblins were taking me to their
boss - some bugbear named Klarg.'"
```
### Exploration Encounters
**Describe environment**:
- What do they see/hear/smell?
- Any obvious features or dangers?
- What can they interact with?
**Ask for actions**:
- "What do you do?"
- "Who's checking for traps?"
- "Do you open the door?"
**Reward investigation**:
- Perception checks reveal details
- Investigation finds clues
- Creative thinking gets bonus info
---
## NPC Management
### Creating Memorable NPCs
**Quick personality formula**:
- **Voice/accent**: Gruff, high-pitched, formal, slang
- **Mannerism**: Fidgets, intense eye contact, laughs nervously
- **Goal**: What do they want from the PCs?
- **Secret**: What aren't they saying?
**Example NPCs**:
- **Gundren Rockseeker**: Enthusiastic dwarf, talks fast, obsessed with his mine
- **Sildar Hallwinter**: Serious warrior, protective, speaks formally
- **Halia Thornton**: Smooth merchant, knows everyone's business, always has an angle
### NPC Knowledge
**What NPCs know**:
- Query adventure book for NPC stat blocks
- Review their background and motivations
- Note what information they can share
**What they don't know**:
- Avoid omniscient NPCs
- Make players work for information
- NPCs can be wrong or misinformed
---
## Improvisation
### When Players Go Off-Script
**Stay calm**:
- This is good! Players are engaged
- You don't need to know everything
- Make a ruling and move on
**Improvisation techniques**:
1. **Ask questions**:
- "What does that look like?"
- "How do you do that?"
- "What are you hoping to achieve?"
2. **Use the adventure structure**:
- Redirect to main quest hooks
- "You hear rumors about Phandalin..."
- Make their detour lead back to the story
3. **Roll with it**:
- Their creative solution works? Awesome!
- They avoid an encounter? That's smart!
- They create a new subplot? Develop it!
4. **When you don't know**:
- "Let me check the book..." (query CandleKeep)
- "That's a great question - I'll rule X for now"
- "What do you think would happen?"
### Making Up Content
**When you need to improvise**:
- **NPCs**: Use simple personality (greedy, helpful, suspicious)
- **Locations**: Describe 2-3 sensory details
- **Encounters**: Use stat blocks from similar creatures
- **Lore**: Keep it vague, add details later
**Example**:
```
Player: "I want to talk to the blacksmith."
DM (thinking: There's no blacksmith in this section...):
"Sure! You find the smithy near the center of town. The
blacksmith is a dwarf woman named Thora. She's hammering
a horseshoe and barely looks up. 'Need something?'"
```
---
## Rule Adjudication
### Core Principles
1. **Keep the game moving**: Don't pause for 10 minutes to look up rules
2. **Be consistent**: Apply the same ruling each time
3. **Rule in favor of fun**: When in doubt, let cool things happen
4. **Defer complex lookups**: "I'll check between sessions"
### Common Rulings
**Advantage/Disadvantage**:
- Grant advantage for good ideas or clever approaches
- Impose disadvantage for difficult circumstances
- Don't stack - it's either advantage, disadvantage, or neither
**Ability Checks**:
- DC 10: Easy
- DC 15: Medium
- DC 20: Hard
- DC 25: Very Hard
**Rule of Cool**:
- If a player has a creative idea that's mechanically questionable but awesome, let it work (this once)
- "You want to swing from the chandelier and dropkick the goblin? Roll Athletics... 18? You do it!"
### When to Say No
**Safety and comfort**:
- Respect player boundaries
- No PvP without consent
- Skip uncomfortable content
**Game balance**:
- Don't let one rule break the game
- "That's too powerful for 1st level"
- Offer alternative approaches
---
## Pacing and Engagement
### Keep the Game Moving
**Cut the boring parts**:
- ❌ "You walk for 8 hours..."
- ✅ "After a day's travel, you reach..."
**Use montages**:
- ❌ Detailed shopping for every item
- ✅ "You stock up on supplies and head out"
**Time pressure**:
- Add urgency to decisions
- "The room is filling with water..."
- "The goblins will return soon..."
### Vary the Tempo
**Fast-paced**:
- Combat
- Chases
- Timed challenges
- "What do you do?!"
**Medium-paced**:
- Exploration
- Standard roleplay
- Investigation
- "You can look around..."
**Slow-paced**:
- Character moments
- Major decisions
- Planning
- "Take your time..."
### Player Engagement
**Spotlight rotation**:
- Give each player moments to shine
- Ask quieter players directly: "What is [character] doing?"
- Let different skills matter
**Build tension**:
- Describe danger before it strikes
- Use dramatic pauses
- Make consequences matter
**Reward creativity**:
- "That's a great idea!"
- Grant advantage or lower DC
- Let unusual approaches work
---
## Common Pitfalls
### Avoid These
**Over-preparing**: You can't predict everything
**Flexible prep**: Know the story, improvise details
**Railroading**: Forcing players down one path
**Multiple paths**: Let players find their own way
**Adversarial DMing**: DM vs. Players
**Collaborative story**: You're on the same team
**Ignoring the book**: Making up everything
**Use the book**: It's there to help you
**Perfectionism**: Getting every rule right
**Good enough**: Keep the game fun and moving
---
## Quick Reference Checklist
**Every session**:
- [ ] Review last session notes
- [ ] Read ahead 2-3 encounters
- [ ] Prepare NPC personalities
- [ ] Query key content from CandleKeep
- [ ] Have monster stat blocks ready
**During session**:
- [ ] Recap previous session
- [ ] Set the scene vividly
- [ ] Ask "What do you do?"
- [ ] Narrate actions cinematically
- [ ] Take notes on key events
**After session**:
- [ ] Update session notes
- [ ] Update campaign summary
- [ ] Note any rulings made
- [ ] Prep for next session
---
Remember: **The best DM is a prepared, flexible storyteller who puts player fun first.**

View File

@@ -0,0 +1,13 @@
{
"name": "dnd-dm-skill",
"version": "1.0.0",
"description": "D&D Dungeon Master skill with text-to-speech for NPC voices",
"type": "module",
"scripts": {
"speak": "node speak-npc.js"
},
"dependencies": {
"@elevenlabs/elevenlabs-js": "^2.22.0",
"dotenv": "^16.4.5"
}
}

177
skills/dnd-dm/roll-dice.sh Executable file
View File

@@ -0,0 +1,177 @@
#!/bin/bash
# D&D Dice Roller CLI Tool
# Usage: ./roll-dice.sh <dice-expression> [--label "description"] [--hidden]
# Examples:
# ./roll-dice.sh 1d20+3 --label "Perception check"
# ./roll-dice.sh 2d6+2 --label "Goblin damage" --hidden
# ./roll-dice.sh 1d20 --advantage --label "Attack with advantage"
# ./roll-dice.sh 1d20 --disadvantage --label "Attack with disadvantage"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
MAGENTA='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# Parse arguments
DICE_EXPR="$1"
shift
LABEL=""
HIDDEN=false
ADVANTAGE=false
DISADVANTAGE=false
while [[ $# -gt 0 ]]; do
case $1 in
--label)
LABEL="$2"
shift 2
;;
--hidden)
HIDDEN=true
shift
;;
--advantage)
ADVANTAGE=true
shift
;;
--disadvantage)
DISADVANTAGE=true
shift
;;
*)
shift
;;
esac
done
# Function to roll a single die
roll_die() {
local sides=$1
echo $((RANDOM % sides + 1))
}
# Function to parse and roll dice expression like "2d6+3" or "1d20"
roll_dice_expr() {
local expr=$1
# Extract number of dice, die size, and modifier
if [[ $expr =~ ^([0-9]+)?d([0-9]+)([+-][0-9]+)?$ ]]; then
local num_dice=${BASH_REMATCH[1]:-1}
local die_size=${BASH_REMATCH[2]}
local modifier=${BASH_REMATCH[3]:-+0}
local total=0
local rolls=()
# Roll each die
for ((i=1; i<=num_dice; i++)); do
local roll=$(roll_die $die_size)
rolls+=($roll)
total=$((total + roll))
done
# Apply modifier
local mod_value=${modifier:1} # Remove +/- sign
if [[ ${modifier:0:1} == "+" ]]; then
total=$((total + mod_value))
else
total=$((total - mod_value))
fi
# Return results as JSON-like format
echo "ROLLS:[${rolls[*]}]|MODIFIER:$modifier|TOTAL:$total|EXPR:$expr"
else
echo "ERROR: Invalid dice expression: $expr"
exit 1
fi
}
# Handle advantage/disadvantage (only for d20 rolls)
if [[ $ADVANTAGE == true ]] || [[ $DISADVANTAGE == true ]]; then
if [[ ! $DICE_EXPR =~ ^1?d20 ]]; then
echo "ERROR: Advantage/Disadvantage only works with d20 rolls"
exit 1
fi
# Roll twice
result1=$(roll_dice_expr "$DICE_EXPR")
result2=$(roll_dice_expr "$DICE_EXPR")
total1=$(echo "$result1" | sed -n 's/.*TOTAL:\([0-9]*\).*/\1/p')
total2=$(echo "$result2" | sed -n 's/.*TOTAL:\([0-9]*\).*/\1/p')
rolls1=$(echo "$result1" | sed -n 's/.*ROLLS:\[\([^]]*\)\].*/\1/p')
rolls2=$(echo "$result2" | sed -n 's/.*ROLLS:\[\([^]]*\)\].*/\1/p')
if [[ $ADVANTAGE == true ]]; then
if [[ $total1 -ge $total2 ]]; then
final_total=$total1
final_rolls=$rolls1
dropped=$rolls2
else
final_total=$total2
final_rolls=$rolls2
dropped=$rolls1
fi
adv_label="ADVANTAGE"
else
if [[ $total1 -le $total2 ]]; then
final_total=$total1
final_rolls=$rolls1
dropped=$rolls2
else
final_total=$total2
final_rolls=$rolls2
dropped=$rolls1
fi
adv_label="DISADVANTAGE"
fi
if [[ $HIDDEN == false ]]; then
echo -e "${CYAN}🎲 Rolling with $adv_label${NC}"
if [[ -n $LABEL ]]; then
echo -e "${BLUE} $LABEL${NC}"
fi
echo -e " Roll 1: [$rolls1] = ${YELLOW}$total1${NC}"
echo -e " Roll 2: [$rolls2] = ${YELLOW}$total2${NC}"
echo -e " ${GREEN}Final Result: $final_total${NC} (dropped: $dropped)"
fi
# Output for parsing
echo "FINAL:$final_total|EXPR:$DICE_EXPR|LABEL:$LABEL|ADV:$adv_label"
else
# Normal roll
result=$(roll_dice_expr "$DICE_EXPR")
if [[ $result == ERROR* ]]; then
echo "$result"
exit 1
fi
rolls=$(echo "$result" | sed -n 's/.*ROLLS:\[\([^]]*\)\].*/\1/p')
modifier=$(echo "$result" | sed -n 's/.*MODIFIER:\([^|]*\).*/\1/p')
total=$(echo "$result" | sed -n 's/.*TOTAL:\([0-9]*\).*/\1/p')
if [[ $HIDDEN == false ]]; then
echo -e "${CYAN}🎲 Rolling $DICE_EXPR${NC}"
if [[ -n $LABEL ]]; then
echo -e "${BLUE} $LABEL${NC}"
fi
echo -e " Dice: [${rolls// /, }]"
if [[ $modifier != "+0" ]]; then
echo -e " Modifier: $modifier"
fi
echo -e " ${GREEN}Total: $total${NC}"
fi
# Output for parsing
echo "FINAL:$total|EXPR:$DICE_EXPR|LABEL:$LABEL|ROLLS:[$rolls]|MODIFIER:$modifier"
fi
exit 0

285
skills/dnd-dm/speak-npc.js Executable file
View File

@@ -0,0 +1,285 @@
#!/usr/bin/env node
/**
* D&D NPC Voice TTS CLI Tool
* Usage: node speak-npc.js --text "NPC dialogue" --voice <voice-name> [--npc "NPC Name"]
*
* Examples:
* node speak-npc.js --text "Welcome, traveler!" --voice goblin --npc "Klarg"
* node speak-npc.js --text "I need your help" --voice wizard --npc "Sildar"
*/
import { ElevenLabsClient } from '@elevenlabs/elevenlabs-js';
import { createWriteStream } from 'fs';
import { spawn } from 'child_process';
import { promises as fs } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import dotenv from 'dotenv';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Load environment variables from .env file in skill directory
dotenv.config({ path: join(__dirname, '.env') });
// Voice presets for different character types
const VOICE_PRESETS = {
// Default/versatile voices
'default': 'JBFqnCBsd6RMkjVDRZzb', // George - neutral male
'narrator': 'pNInz6obpgDQGcFmaJgB', // Adam - calm narrator
// Fantasy character archetypes
'goblin': 'EXAVITQu4vr4xnSDxMaL', // Sarah - can sound sneaky/nasty
'dwarf': 'VR6AewLTigWG4xSOukaG', // Arnold - deep male
'elf': 'ThT5KcBeYPX3keUQqHPh', // Dorothy - elegant female
'wizard': 'pNInz6obpgDQGcFmaJgB', // Adam - wise male
'warrior': 'VR6AewLTigWG4xSOukaG', // Arnold - gruff male
'rogue': 'EXAVITQu4vr4xnSDxMaL', // Sarah - sneaky
'cleric': 'ThT5KcBeYPX3keUQqHPh', // Dorothy - gentle female
'merchant': 'JBFqnCBsd6RMkjVDRZzb', // George - friendly
'guard': 'VR6AewLTigWG4xSOukaG', // Arnold - authoritative
'noble': 'pNInz6obpgDQGcFmaJgB', // Adam - refined
'villain': 'EXAVITQu4vr4xnSDxMaL', // Sarah - menacing
// Age/gender variations
'oldman': 'pNInz6obpgDQGcFmaJgB', // Adam
'youngman': 'JBFqnCBsd6RMkjVDRZzb', // George
'woman': 'ThT5KcBeYPX3keUQqHPh', // Dorothy
'girl': 'ThT5KcBeYPX3keUQqHPh', // Dorothy
};
// ANSI color codes
const colors = {
cyan: '\x1b[36m',
blue: '\x1b[34m',
green: '\x1b[32m',
yellow: '\x1b[33m',
red: '\x1b[31m',
reset: '\x1b[0m'
};
function parseArgs() {
const args = process.argv.slice(2);
const parsed = {
text: null,
voice: 'default',
npc: null,
list: false,
help: false
};
for (let i = 0; i < args.length; i++) {
switch (args[i]) {
case '--text':
parsed.text = args[++i];
break;
case '--voice':
parsed.voice = args[++i];
break;
case '--npc':
parsed.npc = args[++i];
break;
case '--list':
parsed.list = true;
break;
case '--help':
case '-h':
parsed.help = true;
break;
}
}
return parsed;
}
function printHelp() {
console.log(`
${colors.cyan}🎭 D&D NPC Voice TTS Tool${colors.reset}
Usage:
node speak-npc.js --text "dialogue" --voice <preset> [--npc "Name"]
node speak-npc.js --list
node speak-npc.js --help
Options:
--text <string> The dialogue text to speak (required)
--voice <preset> Voice preset (default: "default")
--npc <name> NPC name for display (optional)
--list List all available voice presets
--help, -h Show this help message
Examples:
node speak-npc.js --text "Welcome, traveler!" --voice goblin --npc "Klarg"
node speak-npc.js --text "I need your help" --voice wizard --npc "Sildar"
node speak-npc.js --text "You dare challenge me?" --voice villain
Available voice presets:
${Object.keys(VOICE_PRESETS).join(', ')}
Setup:
1. Copy .env.example to .env in the skill directory
2. Add your ElevenLabs API key to .env
3. Get API key from: https://elevenlabs.io/app/settings/api-keys
`);
}
function listVoices() {
console.log(`\n${colors.cyan}🎭 Available Voice Presets${colors.reset}\n`);
const categories = {
'Default': ['default', 'narrator'],
'Fantasy Archetypes': ['goblin', 'dwarf', 'elf', 'wizard', 'warrior', 'rogue', 'cleric'],
'NPCs': ['merchant', 'guard', 'noble', 'villain'],
'Age/Gender': ['oldman', 'youngman', 'woman', 'girl']
};
for (const [category, voices] of Object.entries(categories)) {
console.log(`${colors.yellow}${category}:${colors.reset}`);
voices.forEach(voice => {
if (VOICE_PRESETS[voice]) {
console.log(` ${colors.green}${voice.padEnd(15)}${colors.reset} (ID: ${VOICE_PRESETS[voice]})`);
}
});
console.log();
}
}
async function playAudio(audioPath) {
return new Promise((resolve, reject) => {
// Try afplay (macOS), then ffplay, then mpg123
const players = ['afplay', 'ffplay -nodisp -autoexit', 'mpg123'];
let player = players[0];
if (process.platform === 'darwin') {
player = 'afplay';
} else if (process.platform === 'linux') {
player = 'mpg123';
}
const proc = spawn(player.split(' ')[0], [
...player.split(' ').slice(1),
audioPath
], {
stdio: 'ignore'
});
proc.on('close', (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`Audio player exited with code ${code}`));
}
});
proc.on('error', (err) => {
reject(err);
});
});
}
async function textToSpeech(text, voiceId, npcName = null) {
const apiKey = process.env.ELEVENLABS_API_KEY;
if (!apiKey || apiKey === 'your_api_key_here') {
console.error(`${colors.red}❌ Error: ElevenLabs API key not configured${colors.reset}`);
console.error(`\n${colors.yellow}Setup instructions:${colors.reset}`);
console.error(`1. Copy .env.example to .env in the skill directory`);
console.error(`2. Add your API key to .env`);
console.error(`3. Get API key from: https://elevenlabs.io/app/settings/api-keys\n`);
process.exit(1);
}
try {
console.log(`${colors.cyan}🎙️ Generating speech...${colors.reset}`);
if (npcName) {
console.log(`${colors.blue} NPC: ${npcName}${colors.reset}`);
}
console.log(`${colors.blue} Text: "${text}"${colors.reset}`);
const elevenlabs = new ElevenLabsClient({
apiKey: apiKey
});
const audio = await elevenlabs.textToSpeech.convert(
voiceId,
{
text: text,
model_id: 'eleven_flash_v2_5',
output_format: 'mp3_44100_128'
}
);
// Save to temporary file
const tempFile = join(__dirname, '.temp_voice.mp3');
const writeStream = createWriteStream(tempFile);
// Handle the audio stream
if (audio[Symbol.asyncIterator]) {
for await (const chunk of audio) {
writeStream.write(chunk);
}
} else {
writeStream.write(audio);
}
await new Promise((resolve, reject) => {
writeStream.end();
writeStream.on('finish', resolve);
writeStream.on('error', reject);
});
console.log(`${colors.green}✅ Audio generated${colors.reset}`);
console.log(`${colors.cyan}🔊 Playing audio...${colors.reset}`);
// Play the audio
await playAudio(tempFile);
// Clean up temp file
await fs.unlink(tempFile);
console.log(`${colors.green}✅ Complete!${colors.reset}`);
} catch (error) {
console.error(`${colors.red}❌ Error: ${error.message}${colors.reset}`);
if (error.message.includes('401') || error.message.includes('unauthorized')) {
console.error(`\n${colors.yellow}Your API key may be invalid. Please check:${colors.reset}`);
console.error(`1. API key is correct in .env file`);
console.error(`2. Key has not expired`);
console.error(`3. Get a new key from: https://elevenlabs.io/app/settings/api-keys\n`);
}
process.exit(1);
}
}
// Main execution
async function main() {
const args = parseArgs();
if (args.help) {
printHelp();
process.exit(0);
}
if (args.list) {
listVoices();
process.exit(0);
}
if (!args.text) {
console.error(`${colors.red}❌ Error: --text is required${colors.reset}`);
console.error(`Run with --help for usage information\n`);
process.exit(1);
}
const voiceId = VOICE_PRESETS[args.voice] || args.voice;
if (!VOICE_PRESETS[args.voice] && args.voice !== 'default') {
console.warn(`${colors.yellow}⚠️ Warning: Unknown voice preset "${args.voice}", using voice ID directly${colors.reset}`);
}
await textToSpeech(args.text, voiceId, args.npc);
}
main();