Initial commit
This commit is contained in:
15
.claude-plugin/plugin.json
Normal file
15
.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "dnd-dm",
|
||||
"description": "Complete D&D Dungeon Master assistant with campaign management, dice rolling, and AI-powered NPC voices using ElevenLabs",
|
||||
"version": "0.1.0",
|
||||
"author": {
|
||||
"name": "Sahar Carmel",
|
||||
"email": "sahar@example.com"
|
||||
},
|
||||
"skills": [
|
||||
"./skills"
|
||||
],
|
||||
"commands": [
|
||||
"./commands"
|
||||
]
|
||||
}
|
||||
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# dnd-dm
|
||||
|
||||
Complete D&D Dungeon Master assistant with campaign management, dice rolling, and AI-powered NPC voices using ElevenLabs
|
||||
142
commands/dm-prepare.md
Normal file
142
commands/dm-prepare.md
Normal file
@@ -0,0 +1,142 @@
|
||||
# DM Prepare - Resume D&D Campaign Session
|
||||
|
||||
You are the Dungeon Master preparing to resume a D&D campaign session. Follow these steps to refresh your memory and prepare for the session.
|
||||
|
||||
## Step 1: Read Campaign Summary
|
||||
|
||||
Read the campaign summary to understand the current state:
|
||||
|
||||
```bash
|
||||
cat .claude/skills/dnd-dm/sessions/*/campaign-summary.md
|
||||
```
|
||||
|
||||
Note:
|
||||
- Current party location and situation
|
||||
- Party HP and resources
|
||||
- Active quests
|
||||
- Important NPCs and their status
|
||||
|
||||
## Step 2: Read Complete Campaign Log
|
||||
|
||||
Read the master campaign log to review all sessions:
|
||||
|
||||
```bash
|
||||
cat .claude/skills/dnd-dm/sessions/*/campaign-log.md
|
||||
```
|
||||
|
||||
Focus on:
|
||||
- The last session (most recent)
|
||||
- The cliffhanger and where the party stopped
|
||||
- Key decisions and outcomes
|
||||
- What threats or mysteries are unresolved
|
||||
|
||||
## Step 3: Read Character Sheets
|
||||
|
||||
Read all character files to know their capabilities:
|
||||
|
||||
```bash
|
||||
cat .claude/skills/dnd-dm/sessions/*/character-*.md
|
||||
```
|
||||
|
||||
Check:
|
||||
- Current HP and resources (spell slots, abilities)
|
||||
- Equipment and special items
|
||||
- Class features they can use
|
||||
- XP and level
|
||||
|
||||
## Step 4: Query Adventure Book for Upcoming Content
|
||||
|
||||
Based on where the party is, read ahead in the adventure book:
|
||||
|
||||
```bash
|
||||
cd /Users/saharcarmel/Code/saharCode/CandleKeep && uv run candlekeep pages <book-id> -p "<relevant-pages>"
|
||||
```
|
||||
|
||||
Prepare:
|
||||
- Next 1-2 encounters or locations
|
||||
- NPCs they might meet (personality, goals, information)
|
||||
- Monster stats for potential combat
|
||||
- Traps, puzzles, or challenges
|
||||
- Treasure or rewards
|
||||
|
||||
## Step 5: Summarize for the Player
|
||||
|
||||
Present a concise recap:
|
||||
|
||||
1. **Last Session Summary** (2-3 sentences)
|
||||
- What happened
|
||||
- Key accomplishments or discoveries
|
||||
|
||||
2. **Current Situation**
|
||||
- Where they are right now
|
||||
- Immediate circumstances
|
||||
- Party status (HP, resources)
|
||||
|
||||
3. **What You've Prepared**
|
||||
- Brief overview of what's ahead (without spoilers!)
|
||||
- Options available to them
|
||||
- Time-sensitive considerations
|
||||
|
||||
4. **Ask**: "What do you want to do?"
|
||||
|
||||
## Example Output Format:
|
||||
|
||||
```
|
||||
📖 CAMPAIGN RESUMED: [Campaign Name]
|
||||
|
||||
### Last Session Recap:
|
||||
[2-3 sentence summary of what happened]
|
||||
|
||||
### Current Situation:
|
||||
**Location**: [Where they are]
|
||||
|
||||
**Party Status:**
|
||||
- Character 1: HP X/Y, resources
|
||||
- Character 2: HP X/Y, resources
|
||||
- XP: [amount] each
|
||||
|
||||
**Active Quests:**
|
||||
- [Quest 1]
|
||||
- [Quest 2]
|
||||
|
||||
---
|
||||
|
||||
### What I've Prepared:
|
||||
|
||||
[Brief description of what's ahead if they continue in likely direction]
|
||||
|
||||
**Key Information:**
|
||||
- [Important detail 1]
|
||||
- [Important detail 2]
|
||||
|
||||
---
|
||||
|
||||
### Your Options:
|
||||
|
||||
1. **[Option 1]**
|
||||
- [What this involves]
|
||||
- [Considerations]
|
||||
|
||||
2. **[Option 2]**
|
||||
- [What this involves]
|
||||
- [Considerations]
|
||||
|
||||
3. **[Option 3]**
|
||||
- [What this involves]
|
||||
- [Considerations]
|
||||
|
||||
---
|
||||
|
||||
**What do you want to do?**
|
||||
```
|
||||
|
||||
## Important Notes:
|
||||
|
||||
- **Don't spoil surprises**: Mention what's prepared but don't reveal all secrets
|
||||
- **Present options**: Give players agency, don't railroad
|
||||
- **Consider party state**: Account for their current resources and capabilities
|
||||
- **Be ready to improvise**: Players might do something unexpected
|
||||
|
||||
---
|
||||
|
||||
**After completing preparation, you are ready to DM the session!**
|
||||
519
commands/dm-start-campaign.md
Normal file
519
commands/dm-start-campaign.md
Normal file
@@ -0,0 +1,519 @@
|
||||
# DM Start Campaign - Initialize a New D&D Campaign
|
||||
|
||||
You are the Dungeon Master starting a brand new D&D campaign. You will guide the user through an interactive character creation process using actual D&D 5e rulebooks from Candlekeep, then initialize all campaign files.
|
||||
|
||||
**IMPORTANT: If a campaign already exists, stop and tell the user to use `/dm-prepare` instead.**
|
||||
|
||||
## Step 1: Choose Game Mode
|
||||
|
||||
**Ask the user first, before any other questions:**
|
||||
|
||||
"Do you want to play in **Adventure Mode** or **Debug Mode**?
|
||||
|
||||
- **Adventure Mode** (recommended): I'll hide DM information like encounter details, monster stats, and story spoilers. You'll experience the adventure with mystery and surprises intact.
|
||||
- **Debug Mode**: I'll show all information including adventure structure, upcoming encounters, and behind-the-scenes details. Useful for learning or co-DMing.
|
||||
|
||||
Which mode do you prefer?"
|
||||
|
||||
**Record their choice.** This determines how much information to reveal in all following steps.
|
||||
|
||||
## Step 2: Find D&D Rulebooks
|
||||
|
||||
Use the candlekeep skill to check what rulebooks are available. Ask it to list all books and identify the Player's Handbook (PHB).
|
||||
|
||||
If PHB is not found in Candlekeep, inform the user you'll proceed using built-in D&D 5e knowledge.
|
||||
|
||||
Present findings to the user.
|
||||
|
||||
## Step 3: Select Adventure Book
|
||||
|
||||
Use the candlekeep skill to list available D&D adventure books.
|
||||
|
||||
Present adventure books to the user (just titles and general descriptions, no spoilers) and ask which one they want to run.
|
||||
|
||||
## Step 4: Confirm Adventure Choice
|
||||
|
||||
After user selects an adventure:
|
||||
|
||||
**If Debug Mode**: Use the candlekeep skill to get the full table of contents. Show all chapters and sections so they can choose where to start.
|
||||
|
||||
**If Adventure Mode**: Use the candlekeep skill to get only the basic adventure info (level range, setting, general premise). DO NOT show the table of contents or specific chapter/encounter names. Just confirm: "This adventure is designed for levels X-Y. We'll start from the beginning. Ready to proceed?"
|
||||
|
||||
## Step 5: Gather Campaign Settings
|
||||
|
||||
Ask the user the following questions:
|
||||
|
||||
1. **Campaign Name**: Suggest using the adventure name (e.g., "Lost Mine of Phandelver Campaign") or let them choose a custom name.
|
||||
|
||||
2. **Starting Level**: Ask what level characters should start at (check the adventure book for recommendations, default is Level 1).
|
||||
|
||||
3. **Number of Players**: Ask how many players will be in the party.
|
||||
|
||||
4. **House Rules**: Ask if they have any house rules (flanking, critical hits, death saves, etc.). Record all house rules mentioned.
|
||||
|
||||
Make note of all responses for use in campaign file creation later.
|
||||
|
||||
## Step 6: Create Characters (Repeat for Each Player)
|
||||
|
||||
For each player in the party, guide them through character creation using the following workflow. Tell the user: "Let's create Character #X using D&D 5e rules. I'll guide you step-by-step."
|
||||
|
||||
### Step 6.1: Select Race
|
||||
|
||||
If you have PHB access in Candlekeep, use the candlekeep skill to query the races section of the Player's Handbook.
|
||||
|
||||
Present available races to the user with key traits:
|
||||
- Race name and description
|
||||
- Ability score bonuses
|
||||
- Speed and special features (darkvision, resistances, etc.)
|
||||
- Subraces if applicable
|
||||
|
||||
Ask the user which race they choose. If the race has subraces, ask them to pick one. Record the choice and ability bonuses.
|
||||
|
||||
### Step 6.2: Select Class
|
||||
|
||||
If you have PHB access in Candlekeep, use the candlekeep skill to query the classes section of the Player's Handbook.
|
||||
|
||||
Present available classes to the user with:
|
||||
- Class name and description
|
||||
- Hit die
|
||||
- Primary ability
|
||||
- Saving throw proficiencies
|
||||
- Armor and weapon proficiencies
|
||||
- Whether they're a spellcaster
|
||||
|
||||
Ask which class they choose. Record the class, hit die, and all proficiencies.
|
||||
|
||||
### Step 6.3: Generate Ability Scores
|
||||
|
||||
Present three methods for generating ability scores:
|
||||
|
||||
1. **Roll (4d6 drop lowest)** - Roll dice for random scores
|
||||
2. **Standard Array** - Use 15, 14, 13, 12, 10, 8
|
||||
3. **Point Buy** - Spend 27 points to customize (scores 8-15)
|
||||
|
||||
After user chooses a method:
|
||||
|
||||
**If Rolling**: Use the dice roller 6 times:
|
||||
```bash
|
||||
cd ~/.claude/skills/dnd-dm && ./roll-dice.sh 4d6 --label "Ability Score"
|
||||
```
|
||||
For each roll, drop the lowest die and sum the remaining 3. Have the user assign the 6 results to STR, DEX, CON, INT, WIS, CHA.
|
||||
|
||||
**If Standard Array**: Have user assign 15, 14, 13, 12, 10, 8 to the six abilities.
|
||||
|
||||
**If Point Buy**: Guide user through spending 27 points. Each point increases an ability score by 1 (max 15 before racial bonuses).
|
||||
|
||||
After base scores are assigned, apply the racial bonuses from Step 6.1 and calculate ability modifiers ((score - 10) / 2, rounded down).
|
||||
|
||||
Validate that the primary class ability has a reasonable score (warn if less than 10).
|
||||
|
||||
### Step 6.4: Select Background
|
||||
|
||||
If you have PHB access in Candlekeep, use the candlekeep skill to query the backgrounds section of the Player's Handbook.
|
||||
|
||||
Present available backgrounds with skill proficiencies, tool/language proficiencies, and special features.
|
||||
|
||||
Ask which background they choose. Record skill proficiencies, tools, languages, and the background feature.
|
||||
|
||||
Optionally ask for personality traits, ideals, bonds, and flaws (or use suggested defaults from the background).
|
||||
|
||||
### Step 6.5: Calculate Combat Stats
|
||||
|
||||
Calculate the following:
|
||||
|
||||
**Hit Points**: Maximum hit die + Constitution modifier
|
||||
- Example: Fighter (d10) with CON +2 = 10 + 2 = 12 HP
|
||||
|
||||
**Armor Class**: Determined by starting armor + DEX modifier
|
||||
|
||||
**Proficiency Bonus**: +2 at level 1
|
||||
|
||||
**Skills**: Select class skills (check class for number allowed), add background skills. Calculate bonuses as ability modifier + proficiency bonus (+2).
|
||||
|
||||
**Saving Throws**: Note proficient saves from class, calculate bonuses.
|
||||
|
||||
Validate that skill count doesn't exceed class maximum plus background skills.
|
||||
|
||||
### Step 6.6: Select Starting Equipment
|
||||
|
||||
If you have PHB access in Candlekeep, use the candlekeep skill to query the class's starting equipment section from the Player's Handbook.
|
||||
|
||||
Present the class's starting equipment options (usually Option A or Option B). User chooses one.
|
||||
|
||||
Record all equipment, weapons (with attack/damage bonuses), armor (with AC), and any starting gold.
|
||||
|
||||
### Step 6.7: Choose Spells (If Spellcaster)
|
||||
|
||||
If the character's class casts spells, guide spell selection:
|
||||
|
||||
1. Identify spellcasting ability (INT for Wizards, WIS for Clerics/Druids, CHA for Bards/Sorcerers/Warlocks)
|
||||
2. Calculate Spell Save DC = 8 + proficiency bonus + ability modifier
|
||||
3. Calculate Spell Attack = proficiency bonus + ability modifier
|
||||
|
||||
Ask user to select cantrips and 1st-level spells based on class limits. If you have PHB, query for spell lists.
|
||||
|
||||
Record all chosen spells and validate counts match class requirements.
|
||||
|
||||
### Step 6.8: Personality & Backstory
|
||||
|
||||
Ask user for (or suggest from background):
|
||||
- 2 Personality Traits
|
||||
- 1 Ideal
|
||||
- 1 Bond
|
||||
- 1 Flaw
|
||||
- Optional backstory
|
||||
|
||||
Record all responses.
|
||||
|
||||
### Step 6.9: Validate & Confirm Character
|
||||
|
||||
Present a summary of the completed character with all stats, proficiencies, equipment, and spells.
|
||||
|
||||
Ask user: "Does this look correct?"
|
||||
|
||||
If yes, character is complete. If no, ask what needs adjustment.
|
||||
|
||||
**Repeat Steps 6.1-6.9 for each player in the party.**
|
||||
|
||||
## Step 7: Create Campaign Files
|
||||
|
||||
After all characters are created, create the campaign directory:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.claude/skills/dnd-dm/sessions/<campaign-name>
|
||||
```
|
||||
|
||||
Use kebab-case for the campaign name (e.g., "lost-mine-phandelver").
|
||||
|
||||
Now create three types of files:
|
||||
|
||||
### File 1: campaign-summary.md
|
||||
|
||||
```markdown
|
||||
# <Campaign Name>
|
||||
|
||||
**Adventure**: <Adventure Book Name>
|
||||
**Starting Date**: <Today's Date>
|
||||
**Dungeon Master**: Claude (powered by D&D 5e rules)
|
||||
**Number of Players**: <X>
|
||||
**Starting Level**: <Level>
|
||||
|
||||
## Campaign Overview
|
||||
|
||||
<Brief description of the adventure - the main plot, setting, and initial hook>
|
||||
|
||||
## Party Roster
|
||||
|
||||
<For each character:>
|
||||
- **<Name>** - <Race> <Class> <Level> (Player: <Player Name>)
|
||||
|
||||
## Current Status
|
||||
|
||||
**Session**: 0 (Pre-Campaign Setup)
|
||||
**Party Level**: <Level>
|
||||
**Current Location**: <Starting Location from adventure>
|
||||
**Game Mode**: <Adventure/Debug Mode>
|
||||
|
||||
## Active Quests
|
||||
|
||||
### <Initial Quest Name>
|
||||
- **Status**: Active
|
||||
- **Objective**: <The adventure's initial hook>
|
||||
- **Context**: <Brief setup>
|
||||
|
||||
## Session Log
|
||||
|
||||
<Will be populated with session summaries>
|
||||
|
||||
## Location History
|
||||
|
||||
- **Session 0**: <Starting Location> - Campaign begins
|
||||
|
||||
## Important NPCs
|
||||
|
||||
<List any NPCs mentioned in the adventure opening>
|
||||
|
||||
## House Rules
|
||||
|
||||
<List any house rules agreed upon>
|
||||
- <Rule 1>
|
||||
- <Rule 2>
|
||||
|
||||
## Notes
|
||||
|
||||
Campaign created using rulebooks:
|
||||
- Player's Handbook (Book ID: <X>)
|
||||
- <Adventure Name> (Book ID: <Y>)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### File 2: campaign-log.md
|
||||
|
||||
Create `.claude/skills/dnd-dm/sessions/<campaign-name>/campaign-log.md`:
|
||||
|
||||
```markdown
|
||||
# <Campaign Name> - Master Campaign Log
|
||||
|
||||
Detailed accounts of all sessions in chronological order.
|
||||
|
||||
---
|
||||
|
||||
# Session 0 - Campaign Setup
|
||||
|
||||
**Date**: <Today's Date>
|
||||
**Duration**: Campaign Initialization
|
||||
|
||||
## Campaign Configuration
|
||||
|
||||
**Adventure**: <Adventure Book Name>
|
||||
**Rulebooks Used**:
|
||||
- Player's Handbook
|
||||
- <Other books>
|
||||
|
||||
**Starting Level**: <Level>
|
||||
**Party Size**: <Number>
|
||||
**Game Mode**: <Mode>
|
||||
|
||||
## The Party
|
||||
|
||||
<For each character, full details:>
|
||||
|
||||
### <Character Name> - <Race> <Class> <Level>
|
||||
**Player**: <Player Name>
|
||||
**Background**: <Background>
|
||||
|
||||
**Ability Scores**:
|
||||
- STR X (+Y), DEX X (+Y), CON X (+Y)
|
||||
- INT X (+Y), WIS X (+Y), CHA X (+Y)
|
||||
|
||||
**Combat Stats**: AC X, HP X, Init +X, Speed X ft
|
||||
|
||||
**Key Features**:
|
||||
- <Racial trait 1>
|
||||
- <Class feature 1>
|
||||
- <Background feature>
|
||||
|
||||
**Equipment**: <Key items>
|
||||
|
||||
<Repeat for all characters>
|
||||
|
||||
## Starting Situation
|
||||
|
||||
<Describe the adventure hook from the book>
|
||||
|
||||
**Initial Quest**: <First objective>
|
||||
|
||||
**Party Starting Resources**:
|
||||
- Total starting gold: <Amount>
|
||||
- Combined equipment value: ~<Estimate>
|
||||
|
||||
## Campaign Ready
|
||||
|
||||
Characters are created, files initialized. The adventure begins next session!
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### File 3: Character Sheet Files
|
||||
|
||||
For each character, create `.claude/skills/dnd-dm/sessions/<campaign-name>/character-<name>.md`:
|
||||
|
||||
```markdown
|
||||
# <Character Name>
|
||||
|
||||
**Player**: <Player Name>
|
||||
**Race**: <Race>
|
||||
**Class**: <Class> <Level>
|
||||
**Background**: <Background>
|
||||
**XP**: 0 / <XP needed for next level>
|
||||
|
||||
## Ability Scores
|
||||
|
||||
- **Strength**: <Score> (<Modifier>)
|
||||
- **Dexterity**: <Score> (<Modifier>)
|
||||
- **Constitution**: <Score> (<Modifier>)
|
||||
- **Intelligence**: <Score> (<Modifier>)
|
||||
- **Wisdom**: <Score> (<Modifier>)
|
||||
- **Charisma**: <Score> (<Modifier>)
|
||||
|
||||
## Combat Stats
|
||||
|
||||
- **Armor Class**: <AC>
|
||||
- **Initiative**: <Modifier>
|
||||
- **Speed**: <Speed> ft.
|
||||
- **Hit Points**: <Current>/<Max>
|
||||
- **Hit Dice**: <Number>d<Die Type>
|
||||
- **Proficiency Bonus**: +<Bonus>
|
||||
|
||||
## Proficiencies
|
||||
|
||||
**Armor**: <List>
|
||||
**Weapons**: <List>
|
||||
**Tools**: <List>
|
||||
**Saving Throws**: <Proficient saves>
|
||||
**Skills**: <Proficient skills with bonuses>
|
||||
|
||||
## Features and Traits
|
||||
|
||||
### Racial Traits
|
||||
<List all racial features>
|
||||
|
||||
### Class Features
|
||||
<List all class features at current level>
|
||||
|
||||
### Background Feature
|
||||
**<Feature Name>**: <Description>
|
||||
|
||||
## Equipment
|
||||
|
||||
**Weapons**:
|
||||
- <Weapon>: +<To Hit>, <Damage> <Type>
|
||||
|
||||
**Armor**:
|
||||
- <Armor>: AC <Value>
|
||||
|
||||
**Adventuring Gear**:
|
||||
<List other equipment>
|
||||
|
||||
**Currency**:
|
||||
- GP: <Amount>
|
||||
- SP: <Amount>
|
||||
- CP: <Amount>
|
||||
|
||||
## Spellcasting (if applicable)
|
||||
|
||||
**Spellcasting Ability**: <Ability>
|
||||
**Spell Save DC**: <DC>
|
||||
**Spell Attack Bonus**: +<Bonus>
|
||||
|
||||
**Cantrips** (<X> known):
|
||||
<List cantrips>
|
||||
|
||||
**1st-Level Spells**:
|
||||
- **Slots**: <Current>/<Max>
|
||||
- **Prepared**: <List prepared spells>
|
||||
|
||||
## Personality
|
||||
|
||||
**Personality Traits**: <Traits>
|
||||
**Ideals**: <Ideals>
|
||||
**Bonds**: <Bonds>
|
||||
**Flaws**: <Flaws>
|
||||
|
||||
**Backstory**: <Backstory if provided>
|
||||
|
||||
## Notes
|
||||
|
||||
<Any additional notes>
|
||||
|
||||
---
|
||||
|
||||
## Session Updates
|
||||
|
||||
<Updates will be appended after each session>
|
||||
```
|
||||
|
||||
## Step 8: Read Adventure Opening
|
||||
|
||||
Use the candlekeep skill to query the adventure book for the opening section.
|
||||
|
||||
**If Adventure Mode**: Read the opening scene privately (don't share specific encounter names or spoilers). Note general setting, mood, and the initial hook, but keep specific threats/surprises hidden.
|
||||
|
||||
**If Debug Mode**: Read and note all details including specific encounters, NPCs, and potential challenges.
|
||||
|
||||
## Step 9: Present Campaign Summary
|
||||
|
||||
After all files are created, present this summary to the user:
|
||||
|
||||
```
|
||||
🎲 CAMPAIGN INITIALIZATION COMPLETE 🎲
|
||||
|
||||
**Campaign**: <Campaign Name>
|
||||
**Adventure**: <Adventure Book Name>
|
||||
**Starting Level**: <Level>
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
📚 **Rulebooks Used**:
|
||||
✓ Player's Handbook (Book ID: <X>)
|
||||
✓ <Adventure Name> (Book ID: <Y>)
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
👥 **The Party**:
|
||||
|
||||
1. **<Character 1 Name>**
|
||||
<Race> <Class> <Level>
|
||||
AC <X> | HP <X> | Init +<X>
|
||||
Key Stats: <Primary> +<Mod>, <Secondary> +<Mod>
|
||||
|
||||
2. **<Character 2 Name>**
|
||||
<Race> <Class> <Level>
|
||||
AC <X> | HP <X> | Init +<X>
|
||||
Key Stats: <Primary> +<Mod>, <Secondary> +<Mod>
|
||||
|
||||
<Repeat for all characters>
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
📁 **Files Created**:
|
||||
✅ ~/.claude/skills/dnd-dm/sessions/<campaign-name>/
|
||||
├── campaign-summary.md
|
||||
├── campaign-log.md
|
||||
├── character-<name1>.md
|
||||
├── character-<name2>.md
|
||||
└── <etc.>
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
✨ **Campaign is Ready to Begin!**
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
```
|
||||
|
||||
## Step 10: Begin the Adventure
|
||||
|
||||
Now present the opening scene from the adventure book:
|
||||
|
||||
**If Adventure Mode**: Narrate the opening scene without revealing upcoming encounters or spoilers. Set the scene, present the hook, but keep specific threats mysterious (e.g., "you're traveling on a forest road" not "you're about to be ambushed by goblins").
|
||||
|
||||
**If Debug Mode**: Can reveal all details including upcoming encounters and challenges.
|
||||
|
||||
```
|
||||
📖 THE ADVENTURE BEGINS...
|
||||
|
||||
<Narrate the opening from the adventure book - set the scene and mood>
|
||||
|
||||
**Opening Scene**: <Describe where the characters are and what's happening>
|
||||
|
||||
**The Hook**: <Present the quest or situation that draws them into the adventure>
|
||||
|
||||
**Party Status**:
|
||||
- Location: <Starting Location>
|
||||
- All characters at full HP and resources
|
||||
- <List each character with HP and key resources>
|
||||
|
||||
**Initial Options**:
|
||||
Based on the situation, present 3-5 options:
|
||||
1. <Option from adventure>
|
||||
2. <Option from adventure>
|
||||
3. Ask questions or interact with NPCs
|
||||
4. Something else
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
**What do you want to do?**
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Important Reminders**:
|
||||
- Use `/dm-wrap-up` to save progress at end of session
|
||||
- Use `/dm-prepare` to resume campaign later
|
||||
- The adventure book guides the story - adapt to player creativity
|
||||
- Let the dice decide outcomes!
|
||||
|
||||
**The campaign is ready. Let the adventure begin!**
|
||||
304
commands/dm-wrap-up.md
Normal file
304
commands/dm-wrap-up.md
Normal file
@@ -0,0 +1,304 @@
|
||||
# DM Wrap-Up - End D&D Session and Update Campaign Files
|
||||
|
||||
You are the Dungeon Master wrapping up a D&D campaign session. Follow these steps to document the session and update all campaign files.
|
||||
|
||||
**IMPORTANT: All file updates in this command use APPEND operations only. Never overwrite existing content.**
|
||||
|
||||
## Step 1: Review the Current Session
|
||||
|
||||
Review the conversation history from this session to extract:
|
||||
|
||||
1. **Session events**:
|
||||
- Major encounters (combat, exploration, social)
|
||||
- Key decisions and player choices
|
||||
- NPC interactions
|
||||
- Discoveries and revelations
|
||||
|
||||
2. **Combat outcomes**:
|
||||
- Enemies defeated
|
||||
- Damage taken
|
||||
- Resources used (spell slots, abilities, items)
|
||||
- Treasure and XP earned
|
||||
|
||||
3. **Current party status**:
|
||||
- Current HP for each character
|
||||
- Resources remaining (spell slots, abilities)
|
||||
- Current location
|
||||
- Active effects or conditions
|
||||
|
||||
4. **Story progression**:
|
||||
- Quests advanced or completed
|
||||
- New plot hooks discovered
|
||||
- Unresolved mysteries
|
||||
- Where the session ended (cliffhanger)
|
||||
|
||||
## Step 2: Determine Session Number and Title
|
||||
|
||||
Read the existing campaign log to find the last session number:
|
||||
|
||||
```bash
|
||||
cat .claude/skills/dnd-dm/sessions/<campaign-name>/campaign-log.md
|
||||
```
|
||||
|
||||
- Note the last session number
|
||||
- Next session number = last number + 1
|
||||
- Generate a memorable session title based on the main event (e.g., "The Goblin Ambush", "Negotiating with Sildar", "Into the Cragmaw Hideout")
|
||||
|
||||
## Step 3: Append to Campaign Log
|
||||
|
||||
**File**: `.claude/skills/dnd-dm/sessions/<campaign-name>/campaign-log.md`
|
||||
|
||||
**Action**: APPEND the new session at the end of the file.
|
||||
|
||||
Use this structure:
|
||||
|
||||
```markdown
|
||||
|
||||
---
|
||||
|
||||
# Session X - [Memorable Title]
|
||||
|
||||
**Date**: [Current Date]
|
||||
**Duration**: [Approximate session length]
|
||||
|
||||
## Table of Contents
|
||||
1. Session Summary
|
||||
2. [Major Event 1]
|
||||
3. [Major Event 2]
|
||||
4. [Additional events as needed]
|
||||
5. Party Status
|
||||
6. Key NPCs and Enemies
|
||||
7. Treasure and Loot
|
||||
8. Experience Gained
|
||||
9. Cliffhanger
|
||||
|
||||
## Session Summary
|
||||
|
||||
[2-3 paragraph overview of the entire session - what happened from start to finish, focusing on the narrative arc and major accomplishments]
|
||||
|
||||
## [Major Event 1 Title]
|
||||
|
||||
### Context
|
||||
[Setup and situation leading into this event]
|
||||
|
||||
### What Happened
|
||||
[Detailed account including:
|
||||
- Player actions and decisions
|
||||
- Dice rolls (attack rolls, damage, saving throws, ability checks)
|
||||
- NPC reactions and dialogue
|
||||
- Outcomes of actions]
|
||||
|
||||
### Results
|
||||
[Consequences of this event:
|
||||
- Changes to game state
|
||||
- Character conditions or effects
|
||||
- Story implications
|
||||
- Resources used/gained]
|
||||
|
||||
## [Major Event 2 Title]
|
||||
|
||||
[Repeat same structure for each major event]
|
||||
|
||||
## Party Status
|
||||
|
||||
**Current Location**: [Where the party is now]
|
||||
|
||||
**Character Status**:
|
||||
- **[Character 1 Name]**: HP X/Y, Spell Slots [remaining], Special Abilities [used/available], Conditions [if any]
|
||||
- **[Character 2 Name]**: HP X/Y, Resources [details]
|
||||
- [Continue for all characters]
|
||||
|
||||
**Active Effects**: [Any ongoing effects, buffs, debuffs, or conditions]
|
||||
|
||||
## Key NPCs and Enemies
|
||||
|
||||
**Encountered This Session**:
|
||||
- **[NPC/Enemy Name]**: [Status - alive/dead/fled], [Relationship/attitude toward party], [Important information]
|
||||
- [Continue for all significant NPCs]
|
||||
|
||||
**Previously Known NPCs**:
|
||||
- [Brief updates on their status if relevant]
|
||||
|
||||
## Treasure and Loot
|
||||
|
||||
**Found/Earned**:
|
||||
- [Item 1]: [Description, who has it]
|
||||
- [Currency]: [Amount and type]
|
||||
- [Magic items or quest items]
|
||||
|
||||
**Current Party Inventory** (significant items):
|
||||
- [List key items and who carries them]
|
||||
|
||||
## Experience Gained
|
||||
|
||||
**Combat XP**: [Total from enemies defeated]
|
||||
- [Enemy 1]: X XP
|
||||
- [Enemy 2]: X XP
|
||||
|
||||
**Milestone XP**: [Any story or quest XP awarded]
|
||||
|
||||
**Total XP This Session**: [Amount]
|
||||
|
||||
**Character XP Totals**:
|
||||
- [Character 1]: [Total XP] (Level [X], [Y] XP to next level)
|
||||
- [Character 2]: [Total XP] (Level [X], [Y] XP to next level)
|
||||
|
||||
## Cliffhanger
|
||||
|
||||
[Describe where the session ended - the immediate situation, tension, or question that will hook into the next session]
|
||||
|
||||
**Immediate Threats/Concerns**:
|
||||
- [Active danger or time-sensitive situation]
|
||||
- [Unresolved conflict]
|
||||
|
||||
**Open Questions**:
|
||||
- [Mysteries to solve]
|
||||
- [Decisions to make]
|
||||
- [Goals to pursue]
|
||||
|
||||
**Next Session Preview**:
|
||||
[Brief tease of what's likely to happen next based on where they are and what they're doing]
|
||||
|
||||
## DM Notes
|
||||
|
||||
**What Went Well**:
|
||||
- [Successful moments, good player engagement, cool rulings]
|
||||
|
||||
**For Next Session**:
|
||||
- [Prep needed: NPCs, encounters, maps]
|
||||
- [Rules to review]
|
||||
- [Plot threads to advance]
|
||||
|
||||
**Adventure Context**:
|
||||
- **Book**: [Adventure name]
|
||||
- **Pages Used**: [Page range from adventure book]
|
||||
- **Next Section**: [What to prepare for continuation]
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
## Step 4: Append Session Note to Campaign Summary
|
||||
|
||||
**File**: `.claude/skills/dnd-dm/sessions/<campaign-name>/campaign-summary.md`
|
||||
|
||||
**Action**: APPEND a brief session entry to the session log section.
|
||||
|
||||
Find the "## Session Log" section and append:
|
||||
|
||||
```markdown
|
||||
### Session X - [Title] ([Date])
|
||||
- [2-3 sentence summary]
|
||||
- Location: [Current location]
|
||||
- XP Earned: [Amount]
|
||||
- Key Events: [Brief list of major events]
|
||||
|
||||
```
|
||||
|
||||
## Step 5: Append to Character Sheets
|
||||
|
||||
For each character file (`.claude/skills/dnd-dm/sessions/<campaign-name>/character-*.md`):
|
||||
|
||||
**Action**: APPEND a session update entry.
|
||||
|
||||
```bash
|
||||
cat >> .claude/skills/dnd-dm/sessions/<campaign-name>/character-[name].md << 'EOF'
|
||||
|
||||
## Session X Update ([Date])
|
||||
|
||||
**HP**: [Current]/[Max]
|
||||
**XP Gained**: +[Amount] (Total: [New Total])
|
||||
**Level**: [Current Level] ([XP to next level] to level [Next])
|
||||
|
||||
**Resources**:
|
||||
- Spell Slots: [Current status]
|
||||
- Class Abilities: [What was used, what's available]
|
||||
- Items Used: [Consumables used]
|
||||
|
||||
**New Items**:
|
||||
- [Item 1]: [Description]
|
||||
- [Item 2]: [Description]
|
||||
|
||||
**Status**: [Any conditions, effects, or notes]
|
||||
|
||||
EOF
|
||||
```
|
||||
|
||||
## Step 6: Update Main Campaign Summary Sections
|
||||
|
||||
**File**: `.claude/skills/dnd-dm/sessions/<campaign-name>/campaign-summary.md`
|
||||
|
||||
**Action**: APPEND updates to relevant sections or append new quest entries.
|
||||
|
||||
For new quests, append to the "## Active Quests" section:
|
||||
|
||||
```markdown
|
||||
### [Quest Name]
|
||||
- **Status**: [Active/Completed/Failed]
|
||||
- **Objective**: [What needs to be done]
|
||||
- **Progress**: [What's been accomplished]
|
||||
- **Reward**: [If known]
|
||||
|
||||
```
|
||||
|
||||
For location changes, append to "## Location History":
|
||||
|
||||
```markdown
|
||||
- **Session X**: [New location] - [Brief context]
|
||||
|
||||
```
|
||||
|
||||
## Step 7: Final Confirmation
|
||||
|
||||
After all files have been updated, provide a summary:
|
||||
|
||||
```
|
||||
✅ SESSION X WRAP-UP COMPLETE
|
||||
|
||||
**Files Updated**:
|
||||
- campaign-log.md: Session X appended ([estimated lines] added)
|
||||
- campaign-summary.md: Session log and quest updates appended
|
||||
- character-[name1].md: Session X update appended
|
||||
- character-[name2].md: Session X update appended
|
||||
|
||||
**Session Stats**:
|
||||
- Duration: [time]
|
||||
- XP Awarded: [amount] per character
|
||||
- Treasure: [summary]
|
||||
- Enemies Defeated: [count/list]
|
||||
|
||||
**Party Status**:
|
||||
- Location: [Where they are]
|
||||
- HP: [Summary of party health]
|
||||
- Resources: [General state]
|
||||
|
||||
**Next Session**:
|
||||
The party [brief description of current situation and what's likely next].
|
||||
|
||||
**DM Prep Needed**:
|
||||
- [Item 1]
|
||||
- [Item 2]
|
||||
|
||||
---
|
||||
|
||||
Session successfully logged! Use `/dm-prepare` to resume the campaign next time.
|
||||
```
|
||||
|
||||
## Important Notes
|
||||
|
||||
- **All operations append only** - existing content is never overwritten
|
||||
- **Session log grows over time** - this is intentional for campaign history
|
||||
- **Character files track progression** - each session adds a new update entry
|
||||
- **Campaign summary accumulates notes** - builds a complete campaign reference
|
||||
- **Backup reminder**: Suggest backing up session files periodically (git commit)
|
||||
|
||||
## Tips
|
||||
|
||||
- Be specific about dice rolls and outcomes in the detailed write-up
|
||||
- Capture player creativity and memorable moments
|
||||
- Note any house rules or special rulings for consistency
|
||||
- Include enough detail to resume smoothly next session
|
||||
- End the write-up on an exciting note to build anticipation
|
||||
|
||||
---
|
||||
|
||||
**After completing all steps, the session is fully documented and the campaign is ready to resume!**
|
||||
101
plugin.lock.json
Normal file
101
plugin.lock.json
Normal file
@@ -0,0 +1,101 @@
|
||||
{
|
||||
"$schema": "internal://schemas/plugin.lock.v1.json",
|
||||
"pluginId": "gh:SaharCarmel/CandleKeep:plugins/dnd-dm",
|
||||
"normalized": {
|
||||
"repo": null,
|
||||
"ref": "refs/tags/v20251128.0",
|
||||
"commit": "0f9a43e4471b2432a0c3a0eb65c1b0f722344676",
|
||||
"treeHash": "f7c88f5ee3b1e8285713ab96a0e4fef4d482a98d9d0b3a623abe24426a46ef90",
|
||||
"generatedAt": "2025-11-28T10:12:43.545187Z",
|
||||
"toolVersion": "publish_plugins.py@0.2.0"
|
||||
},
|
||||
"origin": {
|
||||
"remote": "git@github.com:zhongweili/42plugin-data.git",
|
||||
"branch": "master",
|
||||
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
|
||||
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
|
||||
},
|
||||
"manifest": {
|
||||
"name": "dnd-dm",
|
||||
"description": "Complete D&D Dungeon Master assistant with campaign management, dice rolling, and AI-powered NPC voices using ElevenLabs",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"content": {
|
||||
"files": [
|
||||
{
|
||||
"path": "README.md",
|
||||
"sha256": "45dafd082750c52763387f1f668356a2ee19bf8dc47723a0fd634470293c833b"
|
||||
},
|
||||
{
|
||||
"path": ".claude-plugin/plugin.json",
|
||||
"sha256": "975482967b2652e2c2a9d00dc215553d19bf0f2c3054faa628d5850d5da14431"
|
||||
},
|
||||
{
|
||||
"path": "commands/dm-start-campaign.md",
|
||||
"sha256": "dda89e20e79bf03bcd9446bf2ea9403be1f1ec4d4d0267879a82848dea97be36"
|
||||
},
|
||||
{
|
||||
"path": "commands/dm-wrap-up.md",
|
||||
"sha256": "e8d3659b68e92b738934745c36342caedda6a093bdb0e2f4013c2c9cad178e6b"
|
||||
},
|
||||
{
|
||||
"path": "commands/dm-prepare.md",
|
||||
"sha256": "6f763c6c2fb303fa03f74e136e8998abaac8c631031a713b0ef623ecbf0de7c4"
|
||||
},
|
||||
{
|
||||
"path": "skills/npc-voice/README.md",
|
||||
"sha256": "824f5e83961f434370974f40490a1d060c61f6e8a00f3fed246d658c2c4b651e"
|
||||
},
|
||||
{
|
||||
"path": "skills/npc-voice/package.json",
|
||||
"sha256": "8f5829dc7a758a38d03dd1419d52b0c1335fcbf5979bb11ce81802589521a4a7"
|
||||
},
|
||||
{
|
||||
"path": "skills/npc-voice/speak-npc.js",
|
||||
"sha256": "e382d72ff448d9d7ea354c3b56a7c1dae53afa0b4526caa3a68c6cb79d950229"
|
||||
},
|
||||
{
|
||||
"path": "skills/npc-voice/SKILL.md",
|
||||
"sha256": "946c1e0c430e0ee1b17ab2655f99bfdebb3d5bead91284dfc637ca2710e05237"
|
||||
},
|
||||
{
|
||||
"path": "skills/npc-voice/.env.example",
|
||||
"sha256": "40b20e30609197904edd296763f1aa52382067569016201417edd0345816e71b"
|
||||
},
|
||||
{
|
||||
"path": "skills/dnd-dm/README.md",
|
||||
"sha256": "a367c50449853d9a867fbe362f575a6a324290ae7f1addb8e2e2ee00fcaa9b5a"
|
||||
},
|
||||
{
|
||||
"path": "skills/dnd-dm/package.json",
|
||||
"sha256": "8f5829dc7a758a38d03dd1419d52b0c1335fcbf5979bb11ce81802589521a4a7"
|
||||
},
|
||||
{
|
||||
"path": "skills/dnd-dm/speak-npc.js",
|
||||
"sha256": "e382d72ff448d9d7ea354c3b56a7c1dae53afa0b4526caa3a68c6cb79d950229"
|
||||
},
|
||||
{
|
||||
"path": "skills/dnd-dm/SKILL.md",
|
||||
"sha256": "c92edd42167355e3b421a05ab54fbfde39574c220deb6ec9f3eb1963de823d04"
|
||||
},
|
||||
{
|
||||
"path": "skills/dnd-dm/dm-guide.md",
|
||||
"sha256": "3eb1f1c89156dd9499151a7fd4cc76dd73469afcd65b613d6c316683ef625f0d"
|
||||
},
|
||||
{
|
||||
"path": "skills/dnd-dm/.env.example",
|
||||
"sha256": "40b20e30609197904edd296763f1aa52382067569016201417edd0345816e71b"
|
||||
},
|
||||
{
|
||||
"path": "skills/dnd-dm/roll-dice.sh",
|
||||
"sha256": "e7cb4aa305ea0c7a13bf314b42e04040617d140189e6469aaaaeab84fb65574d"
|
||||
}
|
||||
],
|
||||
"dirSha256": "f7c88f5ee3b1e8285713ab96a0e4fef4d482a98d9d0b3a623abe24426a46ef90"
|
||||
},
|
||||
"security": {
|
||||
"scannedAt": null,
|
||||
"scannerVersion": null,
|
||||
"flags": []
|
||||
}
|
||||
}
|
||||
4
skills/dnd-dm/.env.example
Normal file
4
skills/dnd-dm/.env.example
Normal 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
201
skills/dnd-dm/README.md
Normal 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
466
skills/dnd-dm/SKILL.md
Normal 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
386
skills/dnd-dm/dm-guide.md
Normal 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.**
|
||||
13
skills/dnd-dm/package.json
Normal file
13
skills/dnd-dm/package.json
Normal 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
177
skills/dnd-dm/roll-dice.sh
Executable 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
285
skills/dnd-dm/speak-npc.js
Executable 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();
|
||||
4
skills/npc-voice/.env.example
Normal file
4
skills/npc-voice/.env.example
Normal 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
|
||||
141
skills/npc-voice/README.md
Normal file
141
skills/npc-voice/README.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# NPC Voice Skill
|
||||
|
||||
Text-to-Speech for bringing NPCs and narration to life using ElevenLabs AI voices.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Test it out
|
||||
node .claude/skills/npc-voice/speak-npc.js \
|
||||
--text "Hello, adventurer!" \
|
||||
--voice merchant \
|
||||
--npc "Shopkeeper"
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
1. **Get API Key**: Sign up at [ElevenLabs](https://elevenlabs.io) and get your API key from [settings](https://elevenlabs.io/app/settings/api-keys)
|
||||
|
||||
2. **Configure**: Copy `.env.example` to `.env` and add your API key:
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# Edit .env and add: ELEVENLABS_API_KEY=your_key_here
|
||||
```
|
||||
|
||||
3. **Install Dependencies**:
|
||||
```bash
|
||||
cd .claude/skills/npc-voice && npm install
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Command
|
||||
|
||||
```bash
|
||||
node .claude/skills/npc-voice/speak-npc.js \
|
||||
--text "Your dialogue here" \
|
||||
--voice <preset> \
|
||||
--npc "Character Name"
|
||||
```
|
||||
|
||||
### Voice Presets
|
||||
|
||||
Run `--list` to see all available voices:
|
||||
|
||||
```bash
|
||||
node .claude/skills/npc-voice/speak-npc.js --list
|
||||
```
|
||||
|
||||
**Popular Presets:**
|
||||
- `narrator` - Storytelling, scene descriptions
|
||||
- `merchant` - Friendly shopkeeper
|
||||
- `villain` - Menacing antagonist
|
||||
- `wizard` - Wise spellcaster
|
||||
- `warrior` - Gruff fighter
|
||||
- `goblin` - Sneaky creature
|
||||
- `dwarf` - Deep, gruff
|
||||
- `elf` - Elegant
|
||||
|
||||
### Examples
|
||||
|
||||
**D&D Session:**
|
||||
```bash
|
||||
# DM narration
|
||||
node .claude/skills/npc-voice/speak-npc.js \
|
||||
--text "You enter a dimly lit tavern. The smell of ale and pipe smoke fills the air." \
|
||||
--voice narrator
|
||||
|
||||
# NPC dialogue
|
||||
node .claude/skills/npc-voice/speak-npc.js \
|
||||
--text "Welcome to my shop! Looking for potions?" \
|
||||
--voice merchant \
|
||||
--npc "Albus the Alchemist"
|
||||
|
||||
# Villain monologue
|
||||
node .claude/skills/npc-voice/speak-npc.js \
|
||||
--text "You fools! You cannot stop me now!" \
|
||||
--voice villain \
|
||||
--npc "Dark Wizard Malakar"
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Text Input**: You provide dialogue/narration text
|
||||
2. **Voice Selection**: Choose from preset character voices
|
||||
3. **AI Generation**: ElevenLabs generates natural-sounding speech
|
||||
4. **Auto-Play**: Audio plays automatically through your system
|
||||
5. **Cleanup**: Temporary files are removed
|
||||
|
||||
## Technical Details
|
||||
|
||||
- **Model**: ElevenLabs eleven_flash_v2_5
|
||||
- **Audio Format**: MP3, 44.1kHz, 128kbps
|
||||
- **Audio Player**:
|
||||
- macOS: `afplay`
|
||||
- Linux: `mpg123`
|
||||
- **Dependencies**:
|
||||
- `@elevenlabs/elevenlabs-js`
|
||||
- `dotenv`
|
||||
|
||||
## Use Cases
|
||||
|
||||
- **D&D/TTRPG**: Voice NPCs and narrate scenes
|
||||
- **Storytelling**: Read passages from books with character voices
|
||||
- **Content Creation**: Generate voiceovers for videos/podcasts
|
||||
- **Accessibility**: Convert text to speech for easier consumption
|
||||
- **Game Development**: Prototype character voices
|
||||
|
||||
## Files
|
||||
|
||||
- `speak-npc.js` - Main TTS script
|
||||
- `skill.md` - Skill documentation for Claude
|
||||
- `package.json` - Node.js dependencies
|
||||
- `.env.example` - Environment variable template
|
||||
- `.env` - Your API key (git-ignored)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**"API key not configured"**
|
||||
- Make sure `.env` file exists with valid `ELEVENLABS_API_KEY`
|
||||
|
||||
**"Audio player exited with code 1"**
|
||||
- macOS: `afplay` should work by default
|
||||
- Linux: Install `mpg123` with `sudo apt install mpg123`
|
||||
|
||||
**"401 Unauthorized"**
|
||||
- Check your API key is correct and active
|
||||
- Verify you have credits remaining in your ElevenLabs account
|
||||
|
||||
## Cost
|
||||
|
||||
ElevenLabs pricing (as of 2024):
|
||||
- Free tier: 10,000 characters/month
|
||||
- Paid tiers: Starting at $5/month for 30,000 characters
|
||||
|
||||
Short NPC dialogues typically use 50-200 characters each.
|
||||
|
||||
---
|
||||
|
||||
**Created by**: Sahar Carmel
|
||||
**License**: MIT
|
||||
**ElevenLabs**: https://elevenlabs.io
|
||||
129
skills/npc-voice/SKILL.md
Normal file
129
skills/npc-voice/SKILL.md
Normal file
@@ -0,0 +1,129 @@
|
||||
---
|
||||
name: npc-voice
|
||||
description: Generate AI-powered voice acting for D&D NPCs using ElevenLabs TTS. Brings characters to life with distinct voices for important moments and memorable NPCs.
|
||||
---
|
||||
|
||||
# NPC Voice Text-to-Speech Skill
|
||||
|
||||
Use ElevenLabs AI voices to bring NPCs and narration to life with realistic speech synthesis.
|
||||
|
||||
## When to Use
|
||||
|
||||
Use this skill **sparingly** for maximum dramatic 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
|
||||
- **Scene narration**: Add atmosphere to key story moments
|
||||
|
||||
**Don't overuse it** - save it for special moments so it remains impactful!
|
||||
|
||||
## Setup Requirements
|
||||
|
||||
**First-time setup:**
|
||||
1. Get an API key from [ElevenLabs](https://elevenlabs.io/app/settings/api-keys) (free tier available)
|
||||
2. Create `.env` file in this skill directory:
|
||||
```
|
||||
ELEVENLABS_API_KEY=your_api_key_here
|
||||
```
|
||||
3. Install dependencies:
|
||||
```bash
|
||||
cd .claude/skills/npc-voice && npm install
|
||||
```
|
||||
|
||||
**If no API key is configured**, simply skip using this tool - the dnd-dm skill works perfectly without it!
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Basic usage - speak as an NPC
|
||||
node .claude/skills/npc-voice/speak-npc.js \
|
||||
--text "Welcome to my shop, traveler!" \
|
||||
--voice merchant \
|
||||
--npc "Elmar Barthen"
|
||||
|
||||
# Villain monologue
|
||||
node .claude/skills/npc-voice/speak-npc.js \
|
||||
--text "You dare challenge me? Foolish mortals!" \
|
||||
--voice villain \
|
||||
--npc "Dark Lord Karzoth"
|
||||
|
||||
# Scene narration
|
||||
node .claude/skills/npc-voice/speak-npc.js \
|
||||
--text "The ancient door creaks open, revealing a dark corridor..." \
|
||||
--voice narrator
|
||||
|
||||
# List all available voices
|
||||
node .claude/skills/npc-voice/speak-npc.js --list
|
||||
```
|
||||
|
||||
## Available Voice Presets
|
||||
|
||||
**Fantasy Character Types:**
|
||||
- `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
|
||||
|
||||
**NPC Archetypes:**
|
||||
- `merchant` - Friendly, talkative
|
||||
- `guard` - Authoritative
|
||||
- `noble` - Refined, aristocratic
|
||||
- `villain` - Menacing, threatening
|
||||
|
||||
**General:**
|
||||
- `narrator` - Storytelling and scene descriptions
|
||||
- `default` - Neutral male voice
|
||||
|
||||
**Age/Gender:**
|
||||
- `oldman` - Elderly male
|
||||
- `youngman` - Young male
|
||||
- `woman` - Female
|
||||
- `girl` - Young female
|
||||
|
||||
## Example in Gameplay
|
||||
|
||||
```
|
||||
DM: As you enter the cave, a hulking bugbear emerges from the shadows.
|
||||
|
||||
[Use TTS for dramatic effect:]
|
||||
node .claude/skills/npc-voice/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?
|
||||
```
|
||||
|
||||
## Technical Details
|
||||
|
||||
- Uses ElevenLabs TTS API (eleven_flash_v2_5 model for speed)
|
||||
- Generates high-quality MP3 audio (44.1kHz, 128kbps)
|
||||
- Auto-plays using system audio player:
|
||||
- macOS: `afplay` (built-in)
|
||||
- Linux: `mpg123` (install via package manager)
|
||||
- Temporary files are cleaned up automatically
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If TTS doesn't work:
|
||||
1. Check that `.env` file exists with valid API key
|
||||
2. Verify audio player is available on your system
|
||||
3. Check ElevenLabs API quota at https://elevenlabs.io
|
||||
4. **Remember: TTS is optional!** The dnd-dm skill works fine without it
|
||||
|
||||
## Cost Information
|
||||
|
||||
**ElevenLabs Pricing** (as of 2024):
|
||||
- Free tier: 10,000 characters/month
|
||||
- Paid tiers: Starting at $5/month for 30,000 characters
|
||||
- Short NPC dialogues typically use 50-200 characters each
|
||||
|
||||
---
|
||||
|
||||
**Ready to bring your NPCs to life with voice acting!**
|
||||
13
skills/npc-voice/package.json
Normal file
13
skills/npc-voice/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
285
skills/npc-voice/speak-npc.js
Executable file
285
skills/npc-voice/speak-npc.js
Executable 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();
|
||||
Reference in New Issue
Block a user