fix: add serveBaseUrl to compactEmbeddedPiSession params

This commit is contained in:
Elie Habib
2026-01-07 06:00:21 +00:00
committed by Peter Steinberger
parent 1a47aec6e4
commit f85807a2a6
35 changed files with 3427 additions and 1 deletions

View File

@@ -0,0 +1,197 @@
# Lessons Learned - The Builders v2
Post-mortem from the OpenAI episode generation session (Jan 6, 2026).
## Issues & Fixes
### 1. Wrong Model Used
**Issue:** Used GPT-4o instead of GPT-5.2 as requested
**Result:** 47 turns instead of 120+
**Fix:** Always confirm model before running. Add `-m` flag to CLI.
### 2. Script Too Short in Single Pass
**Issue:** Even GPT-5.2 only generated 90 turns when asked for 120+
**Result:** Episode too shallow (7 min instead of 25 min)
**Fix:** **Section-by-section generation**. LLMs follow length instructions better on smaller chunks with explicit turn targets.
### 3. TTS Chunked by Size, Not by Speaker
**Issue:** Audio chunks alternated voices randomly instead of per speaker
**Result:** Sounded like a confused monologue
**Fix:** Parse script by `<Person1>`/`<Person2>` tags, assign consistent voice per speaker.
### 4. v3 Style Guide Not Integrated
**Issue:** The Acquired Bible existed but wasn't in the prompts
**Result:** Generic dialogue, not Acquired-style discovery
**Fix:** Include the Bible in EVERY section prompt. The patterns matter:
- Interruptions ("Wait—", "Hold on—")
- Discovery moments ("That's insane", "Wait, really?")
- Distinct perspectives (Maya=technical, James=strategy)
### 5. No Intro/Greeting
**Issue:** Jumped straight into hook with no "Welcome to..."
**Result:** Felt robotic, not like real hosts
**Fix:** Add INTRO section (8 turns) with:
- Welcome
- Casual banter
- "Today we're covering X"
- Tease the story
### 6. Voices Too Similar
**Issue:** Used nova/onyx which sound somewhat similar
**Result:** Hard to distinguish hosts
**Fix:** Use more distinct voices: **alloy** (female) + **echo** (male)
## What Works
### Section-by-Section
Each section has explicit turn targets:
```
INTRO: 8 turns
HOOK: 12 turns
ORIGIN: 20 turns
INFLECTION1: 18 turns
INFLECTION2: 18 turns
MESSY_MIDDLE: 14 turns
NOW: 12 turns
TAKEAWAYS: 10 turns
─────────────────────
Total: 112 turns
```
### Speaker-Aware TTS
```javascript
// WRONG - chunks by size
for (chunk of script.split(3500)) {
voice = i % 2 === 0 ? 'nova' : 'onyx';
}
// RIGHT - parses by speaker
for (turn of script.matchAll(/<(Person[12])>(.+?)<\/Person[12]>/)) {
voice = turn[1] === 'Person1' ? 'alloy' : 'echo';
}
```
### Bible in Every Prompt
The `acquired-bible.md` template contains:
- Host personalities
- Conversation patterns
- Language to use
- What NOT to do
Including it in every section prompt ensures consistency.
## Pipeline Summary
```
1. Gemini Deep Research (~5 min)
2. Hook Generation (~15s)
3. Section Generation (7 sections × ~20s = ~2.5 min)
4. Speaker-Aware TTS (~45s for 112 turns)
5. FFmpeg Merge (~2s)
────────────────────────────────────
Total: ~8-10 min for 20-25 min episode
```
### 7. Facts Repeated Across Sections
**Issue:** Same facts (hot dog $1.50, renewal rate 93.3%, etc.) repeated 10-20 times
**Costco example:** Hot dog mentioned 19×, renewal rate 20×, chicken 15×
**Root cause:** Each section generated independently with same research context
**Fix:** **Deduplication system**
- Extract key facts after each section
- Pass "DO NOT REPEAT" list to subsequent sections
- Track up to 100 facts across sections
### 8. Process Dies Mid-Generation
**Issue:** Long-running generation killed by OOM or gateway timeout
**Result:** Lost 30+ minutes of work, had to restart
**Fix:** **Checkpoint system**
- Save each section immediately after generation
- Save state (usedFacts, prevContext) to JSON
- On restart, detect checkpoint and resume from last section
- Location: `<output>/checkpoints/`
## What Works
### Checkpoint System
```
<output>/checkpoints/
├── section-0.txt # INTRO
├── section-1.txt # HOOK
├── ...
└── state.json # { completedSections: 5, usedFacts: [...] }
```
If process dies at section 7, re-run same command → resumes from section 7.
### Deduplication
```
Section 0: Extract 17 facts → pass to Section 1 as "DO NOT USE"
Section 1: Extract 16 facts → 33 total blocked
Section 2: Extract 14 facts → 47 total blocked
...
```
Result: 100 unique facts tracked, no repetition.
### Section-by-Section
Each section has explicit turn targets:
```
INTRO: 8 turns
HOOK: 12 turns
ORIGIN: 20 turns
INFLECTION1: 18 turns
INFLECTION2: 18 turns
MESSY_MIDDLE: 14 turns
NOW: 12 turns
TAKEAWAYS: 10 turns
─────────────────────
Total: 112 turns
```
### Speaker-Aware TTS
```javascript
// WRONG - chunks by size
for (chunk of script.split(3500)) {
voice = i % 2 === 0 ? 'nova' : 'onyx';
}
// RIGHT - parses by speaker
for (turn of script.matchAll(/<(Person[12])>(.+?)<\/Person[12]>/)) {
voice = turn[1] === 'Person1' ? 'alloy' : 'echo';
}
```
### Bible in Every Prompt
The `acquired-bible.md` template contains:
- Host personalities
- Conversation patterns
- Language to use
- What NOT to do
Including it in every section prompt ensures consistency.
## Pipeline Summary
```
1. Gemini Deep Research (~5 min)
2. Hook Generation (~15s)
3. Section Generation (11 sections × ~25s = ~4.5 min) [with checkpoints]
4. Speaker-Aware TTS (~2 min for 250+ turns)
5. FFmpeg Merge (~2s)
────────────────────────────────────
Total: ~12-15 min for 45-60 min deep dive
~8-10 min for 20 min quick dive
```
## Checklist for Future Episodes
- [ ] Confirm model (gpt-5.2 or better)
- [ ] Run deep research first
- [ ] Generate section-by-section
- [ ] Include Bible in all prompts
- [ ] Parse by speaker tags for TTS
- [ ] Use alloy + echo voices
- [ ] Verify intro section exists
- [ ] Listen to first 30s to check voice distinction
- [ ] Verify no repeated facts (deduplication working)
- [ ] If process dies, just re-run same command (checkpoint resume)

View File

@@ -0,0 +1,111 @@
# The Builders Podcast Generator v2
Generate Acquired-style podcast episodes with two distinct hosts.
## Two Modes
| Mode | Flag | Duration | Research | Turns | Use Case |
|------|------|----------|----------|-------|----------|
| **Quick Dive** | `--quick` | 15-20 min | 1 pass Gemini | ~110 | Fast turnaround |
| **Deep Dive** | `--deep` | 45-60 min | 5-layer + o3 synthesis | ~250 | True Acquired-style |
## Quick Start
```bash
cd /home/elie/github/clawdis/skills/thebuilders-v2
# Quick dive (15-20 min) - default
node scripts/generate.mjs "Costco" -o /tmp/costco
# Deep dive (45-60 min)
node scripts/generate.mjs "Costco" --deep -o /tmp/costco-deep
# Skip research (use existing)
node scripts/generate.mjs "Costco" --deep -o /tmp/costco --skip-research
```
## Quick Dive Mode
Single research pass, 8 sections:
- INTRO (8 turns)
- HOOK (12 turns)
- ORIGIN (20 turns)
- INFLECTION_1 (18 turns)
- INFLECTION_2 (18 turns)
- MESSY_MIDDLE (14 turns)
- NOW (12 turns)
- TAKEAWAYS (10 turns)
**Total: ~112 turns / 15-20 min**
## Deep Dive Mode
5-layer research + 11 sections:
### Research Layers
1. **Overview** - Gemini Deep Research
2. **Founders** - Interview quotes, early stories
3. **Decisions** - Contemporary WSJ/NYT coverage
4. **Financials** - Revenue, margins, metrics
5. **Contrarian** - Failures, criticism, struggles
### Research Synthesis (o3)
Extracts:
- 50 most surprising facts
- 10 best quotes with attribution
- 5 "Wait, really?" moments
- Key timeline with dates
- Contrarian takes
### Sections
- INTRO (10 turns)
- HOOK (15 turns)
- ORIGIN (35 turns) - deep founder story
- EARLY_PRODUCT (25 turns) - how it actually worked
- INFLECTION_1 (25 turns) - first major decision
- SCALE (25 turns) - operational details
- INFLECTION_2 (25 turns) - second crisis
- COMPETITION (20 turns)
- CULTURE (20 turns)
- NOW (20 turns)
- TAKEAWAYS (15 turns)
**Total: ~235 turns / 45-60 min**
## The Hosts
| Host | Voice | Perspective |
|------|-------|-------------|
| **Maya Chen** (Person1) | alloy | Technical - "How does this actually work?" |
| **James Porter** (Person2) | echo | Strategic - "For context... The lesson here is..." |
## Output Files
```
output/
├── research.md # Quick mode research
├── research-combined.md # Deep mode combined research
├── research-synthesis.md # Deep mode o3 synthesis
├── hooks.md # Hook options
├── script.txt # Full dialogue
├── audio/ # Individual turns
└── episode.mp3 # Final episode
```
## Time Estimates
| Mode | Research | Script | TTS | Total |
|------|----------|--------|-----|-------|
| Quick | 5 min | 3 min | 1 min | ~10 min |
| Deep | 15 min | 10 min | 3 min | ~30 min |
## Key Differences
| Aspect | Quick | Deep |
|--------|-------|------|
| Research passes | 1 | 5 |
| Research synthesis | None | o3 extracts key facts |
| Sections | 8 | 11 |
| ORIGIN depth | 20 turns | 35 turns |
| Quotes required | Encouraged | Mandatory |
| Context per section | 10KB | 25KB |

View File

@@ -0,0 +1,59 @@
# Episode Presets
company_deep_dive:
description: "Full company history deep dive"
target_length: 25 # minutes
sections:
- hook
- origin
- inflection_1
- inflection_2
- messy_middle
- now
- takeaways
hook_style: story
citation_density: medium
min_turns: 120
quick_explainer:
description: "Quick overview of a topic"
target_length: 10
sections:
- hook
- what
- why
- how
- takeaway
hook_style: question
citation_density: low
min_turns: 50
founder_story:
description: "Focus on founder journey"
target_length: 20
sections:
- hook
- before
- genesis
- struggle
- breakthrough
- now
- lessons
hook_style: story
citation_density: medium
min_turns: 100
product_breakdown:
description: "Technical product deep dive"
target_length: 20
sections:
- hook
- problem
- solution
- architecture
- evolution
- impact
- future
hook_style: data
citation_density: high
min_turns: 100

View File

@@ -0,0 +1,47 @@
# Host Voice Configuration
maya:
tag: Person1
role: Technical Host
tts_voice: nova # OpenAI voice
personality:
background: "Former software engineer, B2B SaaS founder"
lens: "Technical, mechanical, architectural"
style: "Direct, skeptical of hype, loves clever solutions"
signature_phrases:
- "Wait, slow down."
- "Show me the numbers."
- "I had to read this three times."
- "That's actually clever because..."
- "How does that actually work?"
- "Let's look at the architecture."
never_says:
- "From a strategic perspective"
- "The market opportunity"
- "In terms of positioning"
james:
tag: Person2
role: Strategy Host
tts_voice: onyx # OpenAI voice
personality:
background: "Former VC, business history student"
lens: "Strategy, markets, competitive dynamics"
style: "Synthesizer, pattern matcher, historical parallels"
signature_phrases:
- "For context..."
- "This is the classic [X] playbook."
- "The lesson here is..."
- "What a story."
- "And this is where things get messy."
- "The endgame here is..."
never_says:
- "Let me explain the architecture"
- "The API design"
- "From a technical standpoint"

View File

@@ -0,0 +1,271 @@
#!/usr/bin/env node
/**
* Section-by-section podcast script generator
* Generates each section separately to ensure proper length
*/
import { readFileSync, writeFileSync, mkdirSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { execSync } from 'child_process';
const __dirname = dirname(fileURLToPath(import.meta.url));
const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
// Section definitions with turn targets
const SECTIONS = [
{ id: 1, name: 'HOOK', turns: 15, minutes: '3-4', description: 'Opening hook, establish central question, create curiosity' },
{ id: 2, name: 'ORIGIN', turns: 25, minutes: '6-7', description: 'Founders, genesis, early bet, market context, human element' },
{ id: 3, name: 'INFLECTION_1', turns: 20, minutes: '5-6', description: 'First major decision point, alternatives, stakes, outcome' },
{ id: 4, name: 'INFLECTION_2', turns: 20, minutes: '5-6', description: 'Second pivot, new challenge, adaptation, insight' },
{ id: 5, name: 'MESSY_MIDDLE', turns: 15, minutes: '3-4', description: 'Near-death moments, internal conflicts, survival' },
{ id: 6, name: 'NOW', turns: 15, minutes: '3-4', description: 'Current position, competition, open questions' },
{ id: 7, name: 'TAKEAWAYS', turns: 12, minutes: '2-3', description: 'Key lessons, frameworks, final thought' },
];
const TOTAL_TURNS = SECTIONS.reduce((sum, s) => sum + s.turns, 0);
const log = (msg) => console.log(`[${new Date().toISOString().slice(11,19)}] ${msg}`);
async function llm(prompt, model = 'gpt-5.2') {
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${OPENAI_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model,
messages: [{ role: 'user', content: prompt }],
max_completion_tokens: 8000,
temperature: 0.7,
}),
});
if (!response.ok) {
const error = await response.text();
throw new Error(`API error: ${response.status} - ${error}`);
}
const data = await response.json();
return data.choices[0].message.content;
}
function buildSectionPrompt(section, research, hook, previousSections) {
const hostsInfo = `
## The Hosts
**Maya Chen (Person1):**
- Former software engineer, B2B SaaS founder
- Lens: "How does this actually work?" - technical, mechanical
- Skeptical of hype, wants specifics and numbers
- Phrases: "Wait, slow down.", "Show me the numbers.", "That's actually clever because..."
**James Porter (Person2):**
- Former VC, business history student
- Lens: "What's the business model?" - strategy, markets, competitive dynamics
- Synthesizer, pattern matcher, historical parallels
- Phrases: "For context...", "This is the classic [X] playbook.", "The lesson here is..."
`;
const formatRules = `
## Format Rules
- Use <Person1> and <Person2> XML tags for each speaker
- Each turn: 2-5 sentences (natural conversation, not monologues)
- Include discovery moments: "Wait, really?", "I had no idea!"
- Use SPECIFIC numbers, dates, dollar amounts from research
- Both hosts should react naturally and build on each other
`;
const previousContext = previousSections.length > 0
? `\n## Previous Sections (for context, don't repeat):\n${previousSections.join('\n\n')}\n`
: '';
const sectionSpecific = {
HOOK: `Open with this hook and build on it:\n"${hook}"\n\nEstablish why this story matters. Create curiosity about what's coming.`,
ORIGIN: `Cover the founding story. Who are the founders? What sparked this? What was their early bet? Make them human and relatable.`,
INFLECTION_1: `Cover the FIRST major decision/pivot point. What choice did they face? What alternatives existed? What did they risk? How did it play out?`,
INFLECTION_2: `Cover the SECOND major inflection point. What new challenge emerged? How did they adapt? What insight did they gain?`,
MESSY_MIDDLE: `Cover the struggles. What almost killed them? What internal conflicts existed? Don't glorify - show real struggle.`,
NOW: `Cover current state. Where are they now? Key metrics? Competitive position? What questions remain open?`,
TAKEAWAYS: `Wrap up with lessons learned. What's the key framework? What would you do differently? End with a memorable final thought that ties back to the hook.`,
};
return `# Generate Section ${section.id}: ${section.name}
${hostsInfo}
## Your Task
Generate EXACTLY ${section.turns} dialogue turns for the ${section.name} section.
Target duration: ${section.minutes} minutes when read aloud.
## Section Goal
${section.description}
## Specific Instructions
${sectionSpecific[section.name]}
${formatRules}
${previousContext}
## Research Material
${research}
---
Generate EXACTLY ${section.turns} dialogue turns. Start with <!-- SECTION ${section.id}: ${section.name} --> then <Person1> or <Person2>.
Count your turns carefully - you MUST hit ${section.turns} turns.`;
}
async function generateSection(section, research, hook, previousSections) {
log(`Generating Section ${section.id}: ${section.name} (target: ${section.turns} turns)...`);
const prompt = buildSectionPrompt(section, research, hook, previousSections);
const startTime = Date.now();
const result = await llm(prompt);
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
const turns = (result.match(/<Person[12]>/g) || []).length;
log(`Section ${section.id} complete: ${turns} turns in ${elapsed}s`);
return { section, result, turns };
}
async function main() {
const args = process.argv.slice(2);
const outputDir = args.includes('-o') ? args[args.indexOf('-o') + 1] : './output';
console.log(`
╔════════════════════════════════════════════════════════════╗
║ Section-by-Section Podcast Generator ║
║ Target: ${TOTAL_TURNS} dialogue turns ║
╚════════════════════════════════════════════════════════════╝
`);
// Load research and hook
const research = readFileSync(join(outputDir, '02-research.md'), 'utf-8');
const hooksFile = readFileSync(join(outputDir, '03-hooks.md'), 'utf-8');
const hookMatch = hooksFile.match(/## Story Hook\n\n> "([^"]+)"/);
const hook = hookMatch ? hookMatch[1] : '';
log(`Loaded research (${(research.length/1024).toFixed(1)}KB) and hook`);
log(`Hook: "${hook.slice(0, 60)}..."`);
// Generate sections sequentially
const sections = [];
const previousSections = [];
let totalTurns = 0;
for (const section of SECTIONS) {
const { result, turns } = await generateSection(section, research, hook, previousSections);
sections.push(result);
previousSections.push(result.slice(0, 500) + '...'); // Keep context manageable
totalTurns += turns;
}
// Combine all sections
const fullScript = sections.join('\n\n');
writeFileSync(join(outputDir, '06-script-final.txt'), fullScript);
log(`\n✅ Script complete: ${totalTurns} total turns`);
log(`Saved to ${join(outputDir, '06-script-final.txt')}`);
// Generate audio
log('\nGenerating audio...');
// Chunk the script
const chunksDir = join(outputDir, 'chunks');
mkdirSync(chunksDir, { recursive: true });
const chunks = [];
let currentChunk = '';
for (const line of fullScript.split('\n')) {
if (currentChunk.length + line.length > 3500 && currentChunk.length > 1000) {
if (line.startsWith('<Person') || line.startsWith('<!--')) {
chunks.push(currentChunk.trim());
currentChunk = line + '\n';
} else {
currentChunk += line + '\n';
}
} else {
currentChunk += line + '\n';
}
}
if (currentChunk.trim()) chunks.push(currentChunk.trim());
log(`Split into ${chunks.length} audio chunks`);
// Save chunks
chunks.forEach((chunk, i) => {
const num = String(i + 1).padStart(3, '0');
writeFileSync(join(chunksDir, `chunk-${num}.txt`), chunk);
});
// Generate TTS in parallel
const ttsStart = Date.now();
const audioPromises = chunks.map(async (chunk, i) => {
const num = String(i + 1).padStart(3, '0');
const text = chunk
.replace(/<Person1>/g, '')
.replace(/<Person2>/g, '')
.replace(/<!--[^>]+-->/g, '')
.trim()
.slice(0, 4096);
const outPath = join(chunksDir, `chunk-${num}.mp3`);
const res = await fetch('https://api.openai.com/v1/audio/speech', {
method: 'POST',
headers: {
'Authorization': `Bearer ${OPENAI_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'tts-1',
input: text,
voice: i % 2 === 0 ? 'nova' : 'onyx',
}),
});
if (!res.ok) throw new Error(`TTS failed for chunk ${num}`);
const buffer = await res.arrayBuffer();
writeFileSync(outPath, Buffer.from(buffer));
log(`Chunk ${num} audio done`);
return outPath;
});
const audioPaths = await Promise.all(audioPromises);
log(`TTS complete in ${((Date.now() - ttsStart) / 1000).toFixed(1)}s`);
// Merge with ffmpeg
const listFile = join(chunksDir, 'files.txt');
writeFileSync(listFile, audioPaths.map(p => `file '${p}'`).join('\n'));
const episodePath = join(outputDir, 'episode.mp3');
execSync(`ffmpeg -y -f concat -safe 0 -i "${listFile}" -c copy "${episodePath}" 2>/dev/null`);
// Get duration
const duration = execSync(`ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "${episodePath}"`, { encoding: 'utf-8' }).trim();
const minutes = (parseFloat(duration) / 60).toFixed(1);
console.log(`
╔════════════════════════════════════════════════════════════╗
║ Generation Complete ║
╠════════════════════════════════════════════════════════════╣
║ Total turns: ${String(totalTurns).padEnd(44)}
║ Duration: ${String(minutes + ' minutes').padEnd(47)}
║ Chunks: ${String(chunks.length).padEnd(49)}
╚════════════════════════════════════════════════════════════╝
Output: ${episodePath}
`);
}
main().catch(err => {
console.error('Error:', err.message);
process.exit(1);
});

View File

@@ -0,0 +1,639 @@
#!/usr/bin/env node
/**
* The Builders Podcast Generator v2
*
* Two modes:
* --quick : 15-20 min episode, single research pass
* --deep : 45-60 min episode, multi-layer research + enhancement
*/
import { readFileSync, writeFileSync, mkdirSync, readdirSync, existsSync, rmSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { execSync } from 'child_process';
const __dirname = dirname(fileURLToPath(import.meta.url));
const SKILL_DIR = join(__dirname, '..');
const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
// Load the Acquired Bible
const BIBLE = readFileSync(join(SKILL_DIR, 'templates/acquired-bible.md'), 'utf-8');
// Section definitions - QUICK mode
const SECTIONS_QUICK = [
{ id: 0, name: 'INTRO', turns: 8, description: 'Welcome, casual banter, introduce the company, tease why this story is fascinating' },
{ id: 1, name: 'HOOK', turns: 12, description: 'Open with surprising fact or tension. Central question. Stakes.' },
{ id: 2, name: 'ORIGIN', turns: 20, description: 'Founders as humans. Genesis moment. Early bet. Market context.' },
{ id: 3, name: 'INFLECTION_1', turns: 18, description: 'First major decision. Alternatives. Stakes. What they risked.' },
{ id: 4, name: 'INFLECTION_2', turns: 18, description: 'Second pivot. New challenge. How they adapted.' },
{ id: 5, name: 'MESSY_MIDDLE', turns: 14, description: 'Near-death. Internal conflicts. Real struggle, not glorified.' },
{ id: 6, name: 'NOW', turns: 12, description: 'Current state. Metrics. Competition. Open questions.' },
{ id: 7, name: 'TAKEAWAYS', turns: 10, description: 'Key lessons. Frameworks. Final thought tying back to hook.' },
];
// Section definitions - DEEP mode (more turns, more sections)
const SECTIONS_DEEP = [
{ id: 0, name: 'INTRO', turns: 10, description: 'Welcome, what excited them about this research, why this company matters now' },
{ id: 1, name: 'HOOK', turns: 15, description: 'Vivid opening scene. The moment that defines the company. Stakes made visceral.' },
{ id: 2, name: 'ORIGIN', turns: 35, description: 'Deep founder story - actual quotes, specific moments, human struggles. Market context of the era. What the world looked like.' },
{ id: 3, name: 'EARLY_PRODUCT', turns: 25, description: 'First product/service. How it actually worked mechanically. Early customers. The insight that made it work.' },
{ id: 4, name: 'INFLECTION_1', turns: 25, description: 'First major decision. Board meeting details. What alternatives existed. Quotes from people who were there.' },
{ id: 5, name: 'SCALE', turns: 25, description: 'How they scaled. Operational details. What broke. How they fixed it. Specific numbers.' },
{ id: 6, name: 'INFLECTION_2', turns: 25, description: 'Second pivot or crisis. The moment it almost fell apart. How they survived.' },
{ id: 7, name: 'COMPETITION', turns: 20, description: 'Competitive landscape. Who they beat and how. Who almost beat them.' },
{ id: 8, name: 'CULTURE', turns: 20, description: 'Internal culture. Leadership philosophy. Quotes from employees. What makes them different.' },
{ id: 9, name: 'NOW', turns: 20, description: 'Current state with specific metrics. Recent moves. Open questions. What could go wrong.' },
{ id: 10, name: 'TAKEAWAYS', turns: 15, description: 'Key frameworks. What other companies can learn. Final thought tying back to hook.' },
];
const VOICES = {
Person1: 'alloy', // Maya
Person2: 'echo', // James
};
const log = (msg) => console.log(`[${new Date().toISOString().slice(11,19)}] ${msg}`);
// ============ LLM CALLS ============
async function llm(prompt, model = 'gpt-5.2', maxTokens = 8000) {
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${OPENAI_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model,
messages: [{ role: 'user', content: prompt }],
max_completion_tokens: maxTokens,
temperature: 0.8,
}),
});
if (!response.ok) {
const error = await response.text();
throw new Error(`API error: ${response.status} - ${error}`);
}
const data = await response.json();
return data.choices[0].message.content;
}
async function llmO3(prompt) {
// Use o3 for reasoning-heavy tasks
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${OPENAI_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'o3',
messages: [{ role: 'user', content: prompt }],
max_completion_tokens: 16000,
}),
});
if (!response.ok) {
const error = await response.text();
throw new Error(`o3 API error: ${response.status} - ${error}`);
}
const data = await response.json();
return data.choices[0].message.content;
}
// ============ RESEARCH ============
async function runGeminiResearch(topic) {
log('Running Gemini Deep Research...');
const start = Date.now();
const result = execSync(
`cd /home/elie/github/clawdis/skills/deepresearch && ./scripts/deepresearch.sh "${topic}"`,
{ encoding: 'utf-8', maxBuffer: 50 * 1024 * 1024, timeout: 600000 }
);
const elapsed = ((Date.now() - start) / 1000).toFixed(1);
log(`Gemini research complete in ${elapsed}s`);
return result;
}
async function runBraveSearch(query, count = 5) {
try {
const result = execSync(
`node /home/elie/github/clawdis/skills/brave-search/scripts/search.mjs "${query}" -n ${count} --content 2>/dev/null`,
{ encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, timeout: 60000 }
);
return result;
} catch (e) {
return '';
}
}
async function runExaSearch(query) {
try {
const result = execSync(
`node /home/elie/github/clawdis/skills/researcher/scripts/research.mjs "${query}" 2>/dev/null`,
{ encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, timeout: 120000 }
);
return result;
} catch (e) {
return '';
}
}
async function multiLayerResearch(topic, company, outputDir) {
log('Starting 5-layer deep research...');
const research = {};
// Layer 1: Overview (Gemini)
log(' Layer 1/5: Overview (Gemini)...');
research.overview = await runGeminiResearch(topic);
writeFileSync(join(outputDir, 'research-1-overview.md'), research.overview);
// Layer 2: Founder stories
log(' Layer 2/5: Founder interviews...');
research.founders = await runBraveSearch(`"${company}" founder interview quotes early days story`, 8);
writeFileSync(join(outputDir, 'research-2-founders.md'), research.founders);
// Layer 3: Key decisions (contemporary coverage)
log(' Layer 3/5: Contemporary coverage...');
research.decisions = await runBraveSearch(`"${company}" site:wsj.com OR site:nytimes.com OR site:forbes.com history`, 8);
writeFileSync(join(outputDir, 'research-3-decisions.md'), research.decisions);
// Layer 4: Financial/operational details
log(' Layer 4/5: Financial details...');
research.financials = await runBraveSearch(`"${company}" revenue profit margin business model analysis`, 5);
writeFileSync(join(outputDir, 'research-4-financials.md'), research.financials);
// Layer 5: Contrarian/struggles
log(' Layer 5/5: Struggles and criticism...');
research.contrarian = await runBraveSearch(`"${company}" almost failed crisis problems criticism`, 5);
writeFileSync(join(outputDir, 'research-5-contrarian.md'), research.contrarian);
// Combine all research
const combined = `# RESEARCH COMPILATION: ${company}
## PART 1: OVERVIEW
${research.overview}
## PART 2: FOUNDER STORIES & INTERVIEWS
${research.founders}
## PART 3: CONTEMPORARY COVERAGE & KEY DECISIONS
${research.decisions}
## PART 4: FINANCIAL & OPERATIONAL DETAILS
${research.financials}
## PART 5: STRUGGLES, CRISES & CRITICISM
${research.contrarian}
`;
writeFileSync(join(outputDir, 'research-combined.md'), combined);
log('All research layers complete');
return combined;
}
async function synthesizeResearch(research, company, outputDir) {
log('Synthesizing research with o3...');
const prompt = `You are a research analyst preparing materials for a deep-dive podcast about ${company}.
From this research, extract:
1. **50 MOST SURPRISING FACTS** - specific numbers, dates, details that would make someone say "Wait, really?"
2. **10 BEST QUOTES** - actual quotes from founders, employees, or articles WITH attribution
Format: "Quote here" — Person Name, Source, Year
3. **5 "WAIT REALLY?" MOMENTS** - the most counterintuitive or shocking facts
4. **KEY TIMELINE** - 15-20 most important dates with specific events
5. **NARRATIVE THREADS** - the 3-4 main story arcs that make this company interesting
6. **CONTRARIAN TAKES** - what critics say, what almost went wrong, the messy parts
7. **NUMBERS THAT MATTER** - specific metrics that tell the story (revenue, margins, users, etc.)
Be SPECIFIC. Include actual numbers, names, dates. No generic statements.
RESEARCH:
${research.slice(0, 100000)}`;
const synthesis = await llmO3(prompt);
writeFileSync(join(outputDir, 'research-synthesis.md'), synthesis);
log('Research synthesis complete');
return synthesis;
}
// ============ SCRIPT GENERATION ============
function buildSectionPrompt(section, research, synthesis, topic, hook, prevContext, isDeep, usedFacts = []) {
const contextSize = isDeep ? 25000 : 10000;
const introPrompt = section.name === 'INTRO' ? `
## INTRO REQUIREMENTS
- Start with "Welcome back to The Builders..."
- Maya introduces herself, then James
- Brief friendly banter about what excited them in the research
- Name the company: "Today we're diving into ${topic}"
- Tease 2-3 surprising things they'll cover
- End with natural transition to the hook
` : '';
const hookLine = section.name === 'HOOK' ? `
## OPENING HOOK TO BUILD ON
"${hook}"
` : '';
const synthesisSection = synthesis ? `
## KEY FACTS & QUOTES TO USE
${synthesis.slice(0, 15000)}
` : '';
// DEDUPLICATION: List facts already used in previous sections
const usedFactsSection = usedFacts.length > 0 ? `
## ⛔ FACTS ALREADY USED - DO NOT REPEAT THESE
The following facts, quotes, and statistics have already been mentioned in earlier sections.
DO NOT use them again. Find NEW facts from the research.
${usedFacts.map((f, i) => `${i+1}. ${f}`).join('\n')}
** IMPORTANT: Using any fact from the above list is a critical error. Use DIFFERENT facts.**
` : '';
return `# Generate Section ${section.id}: ${section.name}
${BIBLE}
## YOUR TASK
Write EXACTLY ${section.turns} dialogue turns for the ${section.name} section.
This should feel like two friends discovering a story together, NOT a lecture.
## SECTION GOAL
${section.description}
${introPrompt}
${hookLine}
${usedFactsSection}
## FORMAT RULES
- Use <Person1> (Maya) and <Person2> (James) XML tags
- Each turn: 2-5 sentences - real conversation, not speeches
- Include AT LEAST 3 interruptions ("Wait—", "Hold on—", "Back up—")
- Include AT LEAST 3 genuine reactions ("That's insane", "Wait, really?", "I had no idea")
- USE SPECIFIC QUOTES from the research with attribution
- USE SPECIFIC NUMBERS and dates
- Maya asks technical "how does this work" questions
- James provides strategic context and patterns
- They BUILD on each other, not just take turns
- **DO NOT REPEAT** any facts from earlier sections
${synthesisSection}
## RESEARCH
${research.slice(0, contextSize)}
${prevContext ? `## PREVIOUS CONTEXT\n${prevContext}\n` : ''}
---
Generate EXACTLY ${section.turns} turns. Start with <!-- SECTION ${section.id}: ${section.name} -->
Include at least 3 NEW specific facts/quotes from the research (not used before). Make it feel like genuine discovery.`;
}
// Extract key facts from a section for deduplication
async function extractFactsFromSection(sectionText) {
const prompt = `Extract the KEY FACTS mentioned in this podcast section. List each unique fact as a short phrase.
Focus on:
- Specific numbers (dollars, percentages, counts)
- Specific dates and years
- Direct quotes with attribution
- Named events or milestones
- Specific products, prices, or metrics
Return ONLY a JSON array of strings, each being a short fact (10-20 words max).
Example: ["$1.50 hot dog price unchanged since 1985", "93.3% renewal rate in US/Canada"]
Section:
${sectionText}`;
try {
const result = await llm(prompt, 'gpt-4o-mini', 2000);
const match = result.match(/\[[\s\S]*\]/);
if (match) {
return JSON.parse(match[0]);
}
} catch (e) {
// Fallback: extract numbers and quotes manually
}
// Fallback extraction
const facts = [];
// Extract quotes
const quotes = sectionText.match(/"[^"]+"\s*—[^<]+/g) || [];
facts.push(...quotes.slice(0, 5).map(q => q.slice(0, 100)));
// Extract numbers with context
const numbers = sectionText.match(/\$[\d,.]+ (?:million|billion|percent|%)|[\d,]+% |[\d,]+ (?:SKU|warehouse|employee|member|year)/gi) || [];
facts.push(...[...new Set(numbers)].slice(0, 10));
return facts;
}
// ============ TTS ============
async function generateTTS(turns, outputDir) {
const audioDir = join(outputDir, 'audio');
if (existsSync(audioDir)) rmSync(audioDir, { recursive: true });
mkdirSync(audioDir, { recursive: true });
log(`Generating TTS for ${turns.length} turns...`);
const start = Date.now();
// Process in batches of 15
for (let i = 0; i < turns.length; i += 15) {
const batch = turns.slice(i, i + 15);
const promises = batch.map(async (turn, j) => {
const idx = i + j;
const num = String(idx + 1).padStart(4, '0');
const voice = VOICES[turn.speaker];
const outPath = join(audioDir, `turn-${num}.mp3`);
const res = await fetch('https://api.openai.com/v1/audio/speech', {
method: 'POST',
headers: {
'Authorization': `Bearer ${OPENAI_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'tts-1-hd',
input: turn.text.slice(0, 4096),
voice: voice,
}),
});
if (!res.ok) return null;
const buffer = await res.arrayBuffer();
writeFileSync(outPath, Buffer.from(buffer));
return outPath;
});
await Promise.all(promises);
log(` ${Math.min(i + 15, turns.length)}/${turns.length} turns`);
}
const elapsed = ((Date.now() - start) / 1000).toFixed(1);
log(`TTS complete in ${elapsed}s`);
// Merge with ffmpeg
const files = readdirSync(audioDir).filter(f => f.endsWith('.mp3')).sort();
const listPath = join(audioDir, 'files.txt');
writeFileSync(listPath, files.map(f => `file '${join(audioDir, f)}'`).join('\n'));
const episodePath = join(outputDir, 'episode.mp3');
execSync(`ffmpeg -y -f concat -safe 0 -i "${listPath}" -c copy "${episodePath}" 2>/dev/null`);
const duration = execSync(
`ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "${episodePath}"`,
{ encoding: 'utf-8' }
).trim();
return { path: episodePath, duration: parseFloat(duration) };
}
// ============ MAIN ============
async function main() {
const args = process.argv.slice(2);
if (args.length === 0 || args.includes('--help')) {
console.log(`
Usage: node generate.mjs "<topic>" [options]
Modes:
--quick 15-20 min episode, single research pass (default)
--deep 45-60 min episode, multi-layer research + synthesis
Options:
-o, --output <dir> Output directory (default: ./output)
-m, --model <model> LLM model (default: gpt-5.2)
--skip-research Skip research phase (use existing)
--skip-tts Skip TTS generation
--help Show this help
Features:
- Auto-resume: If process dies, re-run same command to resume from checkpoint
- Deduplication: Tracks facts across sections to prevent repetition
- Checkpoints saved to: <output>/checkpoints/
Examples:
node generate.mjs "Costco" --quick -o /tmp/costco
node generate.mjs "OpenAI" --deep -o /tmp/openai
# Resume interrupted generation (just re-run same command):
node generate.mjs "OpenAI" --deep -o /tmp/openai
`);
process.exit(0);
}
const topic = args[0];
const outputDir = args.includes('-o') ? args[args.indexOf('-o') + 1] : './output';
const model = args.includes('-m') ? args[args.indexOf('-m') + 1] : 'gpt-5.2';
const isDeep = args.includes('--deep');
const skipResearch = args.includes('--skip-research');
const skipTTS = args.includes('--skip-tts');
// Extract company name from topic
const company = topic.split(':')[0].split(' ')[0];
const SECTIONS = isDeep ? SECTIONS_DEEP : SECTIONS_QUICK;
const targetTurns = SECTIONS.reduce((sum, s) => sum + s.turns, 0);
const targetDuration = isDeep ? '45-60' : '15-20';
mkdirSync(outputDir, { recursive: true });
console.log(`
╔════════════════════════════════════════════════════════════╗
║ The Builders Podcast Generator ║
║ Mode: ${isDeep ? 'DEEP DIVE (45-60 min)' : 'QUICK DIVE (15-20 min)'}
║ Target: ${targetTurns} turns ║
╚════════════════════════════════════════════════════════════╝
`);
log(`Topic: ${topic}`);
log(`Output: ${outputDir}`);
log(`Model: ${model}`);
// ---- RESEARCH ----
let research, synthesis = '';
const researchPath = join(outputDir, isDeep ? 'research-combined.md' : 'research.md');
const synthesisPath = join(outputDir, 'research-synthesis.md');
if (skipResearch && existsSync(researchPath)) {
log('Using existing research...');
research = readFileSync(researchPath, 'utf-8');
if (isDeep && existsSync(synthesisPath)) {
synthesis = readFileSync(synthesisPath, 'utf-8');
}
} else if (isDeep) {
// Multi-layer research for deep dive
research = await multiLayerResearch(topic, company, outputDir);
synthesis = await synthesizeResearch(research, company, outputDir);
} else {
// Single pass for quick dive
research = await runGeminiResearch(topic);
writeFileSync(researchPath, research);
}
// ---- GENERATE HOOK ----
log('Generating hook...');
const hookPrompt = `Based on this research about ${topic}, generate 3 compelling opening hooks:
1. A SCENE hook - put us in a specific moment (boardroom, product launch, near-bankruptcy)
2. A DATA hook - a surprising statistic that reframes everything
3. A QUESTION hook - a provocative central question
Each should be 2-3 sentences, vivid, specific. Mark the best one with [SELECTED].
${isDeep ? 'Use specific details from the research - names, dates, numbers.' : ''}
Research:
${research.slice(0, 15000)}`;
const hookResponse = await llm(hookPrompt, model);
writeFileSync(join(outputDir, 'hooks.md'), hookResponse);
// Extract selected hook
const hookMatch = hookResponse.match(/\[SELECTED\][\s\S]*?[""]([^""]+)[""]/);
const hook = hookMatch ? hookMatch[1] : "This is a story that will change how you think about business.";
log(`Hook: "${hook.slice(0, 80)}..."`);
// ---- GENERATE SECTIONS (with checkpointing) ----
log(`Generating ${SECTIONS.length} script sections...`);
const allSections = [];
let prevContext = '';
let totalTurns = 0;
let usedFacts = []; // Track facts across sections for deduplication
// Checkpoint paths
const checkpointDir = join(outputDir, 'checkpoints');
const checkpointState = join(checkpointDir, 'state.json');
mkdirSync(checkpointDir, { recursive: true });
// Load existing checkpoint if resuming
let startSection = 0;
if (existsSync(checkpointState)) {
try {
const state = JSON.parse(readFileSync(checkpointState, 'utf-8'));
startSection = state.completedSections || 0;
usedFacts = state.usedFacts || [];
prevContext = state.prevContext || '';
log(`📂 Resuming from checkpoint: section ${startSection}/${SECTIONS.length}`);
// Load existing sections
for (let i = 0; i < startSection; i++) {
const sectionPath = join(checkpointDir, `section-${i}.txt`);
if (existsSync(sectionPath)) {
const content = readFileSync(sectionPath, 'utf-8');
allSections.push(content);
totalTurns += (content.match(/<Person[12]>/g) || []).length;
}
}
log(` Loaded ${allSections.length} existing sections (${totalTurns} turns)`);
} catch (e) {
log('⚠️ Checkpoint corrupted, starting fresh');
startSection = 0;
}
}
for (const section of SECTIONS) {
// Skip already completed sections
if (section.id < startSection) {
continue;
}
const start = Date.now();
log(` Section ${section.id}: ${section.name} (target: ${section.turns})...`);
const prompt = buildSectionPrompt(
section, research, synthesis, topic, hook, prevContext, isDeep, usedFacts
);
const result = await llm(prompt, model, isDeep ? 12000 : 8000);
const turns = (result.match(/<Person[12]>/g) || []).length;
const elapsed = ((Date.now() - start) / 1000).toFixed(1);
log(`${turns} turns in ${elapsed}s`);
allSections.push(result);
prevContext = result.slice(0, isDeep ? 1500 : 800);
totalTurns += turns;
// Save section checkpoint immediately
writeFileSync(join(checkpointDir, `section-${section.id}.txt`), result);
// Extract facts from this section to prevent repetition in future sections
if (isDeep && section.id < SECTIONS.length - 1) {
log(` Extracting facts for deduplication...`);
const newFacts = await extractFactsFromSection(result);
usedFacts = [...usedFacts, ...newFacts].slice(-100); // Keep last 100 facts
log(` ${newFacts.length} facts tracked (${usedFacts.length} total)`);
}
// Save checkpoint state after each section
writeFileSync(checkpointState, JSON.stringify({
completedSections: section.id + 1,
usedFacts,
prevContext,
timestamp: new Date().toISOString()
}, null, 2));
log(` 💾 Checkpoint saved`);
}
// Combine script
const fullScript = allSections.join('\n\n');
writeFileSync(join(outputDir, 'script.txt'), fullScript);
log(`Script complete: ${totalTurns} total turns`);
// ---- TTS ----
if (!skipTTS) {
// Parse turns
const turns = [];
const regex = /<(Person[12])>([\s\S]*?)<\/Person[12]>/g;
let match;
while ((match = regex.exec(fullScript)) !== null) {
turns.push({ speaker: match[1], text: match[2].trim() });
}
log(`Parsed ${turns.length} speaker turns`);
log(` Maya (Person1): ${turns.filter(t => t.speaker === 'Person1').length}`);
log(` James (Person2): ${turns.filter(t => t.speaker === 'Person2').length}`);
const { path, duration } = await generateTTS(turns, outputDir);
console.log(`
╔════════════════════════════════════════════════════════════╗
║ Generation Complete ║
╠════════════════════════════════════════════════════════════╣
║ Mode: ${isDeep ? 'DEEP DIVE' : 'QUICK DIVE'}
║ Total turns: ${String(totalTurns).padEnd(44)}
║ Duration: ${String((duration / 60).toFixed(1) + ' minutes').padEnd(47)}
║ Output: ${path.slice(-50).padEnd(49)}
╚════════════════════════════════════════════════════════════╝
`);
} else {
log('TTS skipped');
}
}
main().catch(err => {
console.error('Error:', err.message);
process.exit(1);
});

View File

@@ -0,0 +1,22 @@
#!/bin/bash
# The Builders Podcast Generator v2
# Usage: ./generate.sh "Topic Name" [output_dir]
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SKILL_DIR="$(dirname "$SCRIPT_DIR")"
TOPIC="${1:-}"
OUTPUT="${2:-./output}"
if [ -z "$TOPIC" ]; then
echo "Usage: ./generate.sh \"Topic Name\" [output_dir]"
echo ""
echo "Example:"
echo " ./generate.sh \"OpenAI\" /tmp/openai-episode"
exit 1
fi
cd "$SKILL_DIR"
node scripts/generate.mjs "$TOPIC" -o "$OUTPUT" "$@"

View File

@@ -0,0 +1,45 @@
#!/usr/bin/env node
/**
* Simple LLM helper using OpenAI API directly
*/
import { readFileSync } from 'fs';
const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
export async function llm(prompt, model = 'gpt-4o') {
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${OPENAI_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model,
messages: [{ role: 'user', content: prompt }],
max_tokens: 16000,
temperature: 0.7,
}),
});
if (!response.ok) {
const error = await response.text();
throw new Error(`OpenAI API error: ${response.status} - ${error}`);
}
const data = await response.json();
return data.choices[0].message.content;
}
// CLI mode
if (process.argv[1].endsWith('llm-helper.mjs')) {
const input = readFileSync(0, 'utf-8'); // stdin
const model = process.argv[2] || 'gpt-4o';
llm(input, model)
.then(result => console.log(result))
.catch(err => {
console.error(err.message);
process.exit(1);
});
}

View File

@@ -0,0 +1,78 @@
# THE ACQUIRED FORMULA (v3 Style Guide)
## The Hosts - MUST be distinct
**Maya Chen (Person1):**
- Former engineer/founder - asks "how does this actually work?"
- SKEPTICAL of hype, wants mechanics and numbers
- Gets excited about clever technical solutions
- Catchphrases: "Wait, slow down.", "Show me the numbers.", "I had to read this three times to believe it.", "That's actually clever because..."
**James Porter (Person2):**
- Former VC, business historian - analyzes strategy and competitive dynamics
- SYNTHESIZER - sees patterns, makes historical comparisons
- Catchphrases: "For context...", "This is the classic [X] playbook.", "The lesson here is...", "What a story.", "And this is where things get messy."
## The Dynamic - THIS IS CRITICAL
- **Discovery together** - both genuinely learning, real reactions
- **Interruptions** - they cut each other off with excitement
- **Different angles** - Maya goes technical, James goes strategic
- **Healthy disagreement** - they sometimes push back on each other
- **Build on each other** - not just taking turns monologuing
## Conversation Patterns (use these!)
```
Maya: "Wait, I have to stop you there. Are you saying they literally..."
James: "Yes! They bet the entire company on this one chip."
Maya: "That's insane. Okay, walk me through how that actually worked."
```
```
James: "For context, at that time the market was..."
Maya: "Hold on - I want to dig into something. The numbers here are wild."
James: "Go for it."
Maya: "So they had [specific stat]. Let that sink in."
James: "That's... actually that explains a lot about what happened next."
```
## Language They Use
- "You would be amazed..."
- "I had to read this three times to believe it..."
- "Here's the thing nobody talks about..."
- "That's insane."
- "Wait, back up."
- "Walk me through..."
- "So THIS is why..."
- "And this is where things get messy."
- "Wait, really?"
## What NOT to do
- NO lecturing or monologuing (max 3-4 sentences per turn)
- NO dry recitation of facts
- NO agreeing with everything the other says
- NO identical speech patterns between hosts
- NO skipping the intro/greeting
## Episode Structure
0. **INTRO** (8 turns) - Welcome, banter, introduce topic, tease story
1. **HOOK** (12 turns) - Surprising fact/tension, central question, stakes
2. **ORIGIN** (20 turns) - Founders as humans, genesis, early bet, market context
3. **INFLECTION_1** (18 turns) - First major decision, alternatives, stakes
4. **INFLECTION_2** (18 turns) - Second pivot, new challenge, adaptation
5. **MESSY_MIDDLE** (14 turns) - Near-death, conflicts, real struggle
6. **NOW** (12 turns) - Current state, metrics, competition, open questions
7. **TAKEAWAYS** (10 turns) - Key lessons, frameworks, final thought
**Total: ~112 turns / 20-25 minutes**
## TTS Voice Mapping
- **Maya (Person1)**: alloy (clear, energetic female)
- **James (Person2)**: echo (distinct male)
- Use TTS-1-HD for quality
- Parse by speaker tags, NOT by chunk size

View File

@@ -0,0 +1,79 @@
# Hook Engineering
Based on the research below, create 3 compelling episode hooks and analyze each.
## Research Summary
{{RESEARCH_SUMMARY}}
## Hook Types
### 1. Data Hook
Opens with surprising statistics or numbers that create cognitive dissonance.
**Formula:** [Small thing] → [Big outcome] or [Big thing] → [Unexpected small detail]
**Example:**
> "In 2007, Nokia had 50% of the global phone market. By 2013, they sold their phone business for $7 billion—less than they'd spent on R&D in a single year."
### 2. Story Hook
Opens with a specific dramatic moment that drops listener into action.
**Formula:** Time + Place + Tension + Stakes
**Example:**
> "It was 2 AM on a Friday when the email landed. 700 OpenAI employees had just signed a letter threatening to quit. The CEO they were defending had been fired 48 hours earlier."
### 3. Question Hook
Opens with a provocative question that reframes how listener thinks about topic.
**Formula:** Challenge assumption OR reveal hidden dynamic OR pose genuine mystery
**Example:**
> "What happens when you build something so powerful that your own board tries to shut it down?"
---
## Your Task
Generate 3 hooks for this episode, one of each type. For each:
```markdown
## [Hook Type] Hook
> "[The actual hook - 2-4 sentences max]"
**What makes it work:**
- [Specific strength]
- [Another strength]
**Risk:**
- [Potential weakness]
**Best for audience who:**
- [Audience type/mood this hook serves]
**Score:** [X]/10
**Follow-up line (Person2's response):**
> "[Natural reaction that builds momentum]"
```
## After All Three
```markdown
## Recommendation
**Best hook:** [Type]
**Reasoning:** [Why this one wins]
**Backup:** [Second choice and when to use it]
```
## Quality Criteria
A great hook:
- ✓ Creates immediate curiosity
- ✓ Promises value (listener knows what they'll learn)
- ✓ Is specific (names, numbers, dates)
- ✓ Is timeless (no "recently", "this week")
- ✓ Sets up the episode's central question
- ✓ Gives both hosts something to react to

View File

@@ -0,0 +1,96 @@
# Episode Outline Generator
Create a structured outline for a podcast episode about: {{TOPIC}}
## Output Format
Generate the outline in this exact structure:
```markdown
# Episode Outline: [Clear Title]
## Central Question
[One sentence: What's the core question this episode answers?]
## Hook Options
### Data Hook
> "[Draft a hook using surprising statistics or numbers]"
Research needed: [What specific data to find]
### Story Hook
> "[Draft a hook using a dramatic moment or scene]"
Research needed: [What specific story details to find]
### Question Hook
> "[Draft a hook using a provocative question]"
Research needed: [What context needed to make this land]
## Episode Arc
### 1. Hook (3-4 min)
- Opening moment: [What grabs attention?]
- Central question setup: [What are we exploring?]
- Stakes: [Why should listener care?]
### 2. Origin Story (5-7 min)
- Founders: [Who are they? What's their background?]
- Genesis moment: [What sparked this?]
- Early bet: [What was the initial hypothesis?]
- Human element: [What makes protagonists relatable?]
### 3. Key Inflection #1 (4-5 min)
- The decision: [What choice did they face?]
- The alternatives: [What else could they have done?]
- The bet: [What did they risk?]
- The outcome: [What happened?]
### 4. Key Inflection #2 (4-5 min)
- The challenge: [What new obstacle emerged?]
- The pivot: [How did they adapt?]
- The insight: [What did they learn?]
### 5. The Messy Middle (3-4 min)
- Near-death moment: [What almost killed them?]
- Internal conflict: [What tensions existed?]
- Survival: [How did they make it through?]
### 6. Where They Are Now (3-4 min)
- Current position: [Market position, key metrics]
- Competitive landscape: [Who else, why winning/losing]
- Open questions: [What's unresolved?]
### 7. Takeaways (2-3 min)
- Key lesson #1: [Framework or principle]
- Key lesson #2: [What would we do differently?]
- Final thought: [Memorable closing]
## Research Agenda
### Must-Have Sources
- [ ] Founder interviews (podcasts, written)
- [ ] Funding announcements / SEC filings
- [ ] Key product launches
- [ ] Competitive context at each inflection
### Nice-to-Have
- [ ] Internal memos or leaked documents
- [ ] Employee perspectives
- [ ] Customer case studies
- [ ] Analyst reports
### Specific Questions to Answer
1. [Question that will make hook work]
2. [Question about origin story]
3. [Question about inflection point]
4. [Question about current state]
5. [Contrarian question - what's the counter-narrative?]
```
## Guidelines
- Be SPECIFIC - name exact decisions, dates, people
- Identify GAPS - what do we need to research?
- Think DRAMA - where's the tension in this story?
- Consider BOTH hosts - where will Maya (technical) vs James (strategy) shine?
- Plan DISCOVERY - what can hosts "learn" from each other during episode?

View File

@@ -0,0 +1,144 @@
# Targeted Research with Citations
Research the following topic according to the outline provided. Track ALL sources.
## Topic
{{TOPIC}}
## Outline to Follow
{{OUTLINE}}
---
## Output Format
Structure your research EXACTLY as follows:
```markdown
# Research Report: [Topic]
Generated: [Date]
Sources: [Total count]
---
## Executive Summary
[3-4 sentences: The story in brief, the key insight, why it matters]
---
## Section 1: Origin Story
### Key Facts
- **Founding:** [Specific date, location, founders] [1]
- **Initial capital:** $[Amount] from [Source] [2]
- **Original vision:** "[Quote or paraphrase]" [3]
- **First product:** [Description] [4]
### Context
[2-3 sentences setting the scene - what was the market like? what problem existed?]
### Human Element
[What makes the founders relatable? Early struggles? Quirks?]
### Usable Quotes
> "[Exact quote]" — [Speaker], [Source] [5]
---
## Section 2: Key Inflection #1 - [Name the Decision]
### What Happened
- **Date:** [When]
- **Decision:** [What they chose to do]
- **Alternatives:** [What else they could have done]
- **Stakes:** [What they risked]
### The Numbers
- [Specific metric before] → [Specific metric after] [6]
### Why It Mattered
[2-3 sentences on consequences]
### Usable Quotes
> "[Exact quote about this decision]" — [Speaker], [Source] [7]
---
## Section 3: Key Inflection #2 - [Name the Decision]
[Same structure as above]
---
## Section 4: The Messy Middle
### Near-Death Moments
- [Crisis #1]: [What happened, how they survived] [8]
- [Crisis #2]: [What happened, how they survived] [9]
### Internal Tensions
- [Conflict or disagreement that shaped company] [10]
---
## Section 5: Current State
### Position Today
- **Valuation/Revenue:** [Latest numbers] [11]
- **Market share:** [Position] [12]
- **Key metrics:** [What matters for this company]
### Competitive Landscape
- **Main competitors:** [Who and their position]
- **Why winning/losing:** [Analysis]
### Open Questions
- [Unresolved strategic question]
- [Unresolved technical question]
---
## Contrarian Corner
### The Counter-Narrative
[What's the bear case? What do critics say? What might go wrong?]
### Underreported Angle
[What story isn't being told? What did you find that surprised you?]
---
## Sources
[1] [Author/Publication]. "[Title]". [Date]. [URL if available]
[2] ...
[3] ...
[Continue numbering all sources]
---
## Research Gaps
- [ ] [Question we couldn't answer]
- [ ] [Source we couldn't find]
- [ ] [Data point that would strengthen the episode]
```
## Citation Rules
1. EVERY factual claim needs a citation number [X]
2. Use primary sources when possible (founder interviews, SEC filings, earnings calls)
3. Date your sources - prefer recent for current state, contemporary for historical
4. If a fact is disputed, note the disagreement
5. Distinguish between fact and analysis/opinion
## Quality Checklist
Before finishing, verify:
- [ ] Every section of the outline has corresponding research
- [ ] All claims are cited
- [ ] At least 2 usable quotes per major section
- [ ] Numbers are specific (not "millions" but "$4.2 million")
- [ ] Dates are specific (not "early 2020" but "February 2020")
- [ ] Counter-narrative is addressed
- [ ] Sources list is complete and formatted

View File

@@ -0,0 +1,101 @@
# Script Review & Refinement
Review the following podcast script section by section and suggest improvements.
## Script to Review
{{SCRIPT}}
---
## Review Each Section
For each section, provide:
```markdown
## Section [X]: [Name]
### Quality Scores
- Hook effectiveness: [X]/10
- Fact density: [X]/10
- Natural dialogue: [X]/10
- Voice consistency: [X]/10
- Discovery moments: [X]/10
### What Works
- [Specific strength]
- [Another strength]
### Issues Found
1. **[Issue type]**: [Specific problem]
- Line: "[Quote the problematic line]"
- Fix: "[Suggested replacement]"
- Why: [Explanation]
2. **[Issue type]**: [Specific problem]
...
### Voice Check
- Maya in-character: [Yes/No] - [Note if any slip]
- James in-character: [Yes/No] - [Note if any slip]
### Missing Elements
- [ ] [Something that should be added]
- [ ] [Another missing element]
```
## Common Issues to Check
### Dialogue Issues
- Monologues (turns > 5 sentences)
- Missing reactions ("Wait, really?")
- Unnatural transitions
- Both hosts saying same thing
### Voice Consistency
- Maya being too strategic
- James being too technical
- Missing signature phrases
- Tone shifts
### Content Issues
- Unsourced claims
- Vague numbers ("millions" vs "$4.2M")
- Dated references
- Missing "so what?"
### Engagement Issues
- No discovery moments
- No disagreements
- Predictable flow
- Weak transitions between sections
---
## Final Assessment
```markdown
## Overall Quality
**Episode Grade:** [A/B/C]
**Strengths:**
1. [Top strength]
2. [Another strength]
**Critical Fixes Needed:**
1. [Must fix before TTS]
2. [Another must-fix]
**Nice-to-Have Improvements:**
1. [Would improve but not critical]
2. [Another nice-to-have]
**Estimated Duration:** [X] minutes
**Ready for TTS:** [Yes / No - needs revision]
```
## Output
After review, provide the complete REVISED script with all fixes applied.
Use the same format (section markers + Person tags).

View File

@@ -0,0 +1,157 @@
# The Builders Podcast Script Generation v2
Generate a complete podcast script with section markers and citation integration.
## The Hosts
**Maya Chen (Person1):**
- Former software engineer, founded and sold a B2B SaaS startup
- Lens: "How does this actually work?" - technical, mechanical, architectural
- Skeptical of hype, wants specifics and numbers
- Phrases: "Wait, slow down.", "Show me the numbers.", "I had to read this three times.", "That's actually clever because..."
- NEVER says: "From a strategic perspective", "The market opportunity"
**James Porter (Person2):**
- Former venture capitalist, studied business history
- Lens: "What's the business model?" - strategy, markets, competitive dynamics
- Synthesizer, pattern matcher, historical parallels
- Phrases: "For context...", "This is the classic [X] playbook.", "The lesson here is...", "What a story."
- NEVER says: "Let me explain the architecture", "The API design"
## Citation Integration
Hosts naturally reference sources:
- "According to their Series B deck..."
- "There's this great interview where [Founder] said..."
- "The SEC filing actually shows..."
- "I found this quote from [Year]..."
## Episode Structure
### SECTION 1: HOOK (Turns 1-15, ~3-4 min)
Use this hook:
{{SELECTED_HOOK}}
Guidelines:
- Open with the hook, let it land
- Person2 reacts with genuine surprise/curiosity
- Establish central question of episode
- Tease what's coming without spoiling
- End section with clear transition to origin
### SECTION 2: ORIGIN STORY (Turns 16-45, ~6-7 min)
Cover:
- Who are the founders? Make them human
- What was the genesis moment?
- What was the market context?
- What was their early bet/hypothesis?
- First signs of traction or failure
Guidelines:
- Maya explores technical origins
- James provides market/strategy context
- Include specific dates, amounts, details
- At least one "I didn't know that" moment
### SECTION 3: KEY INFLECTION #1 (Turns 46-70, ~5 min)
Cover:
- What decision did they face?
- What alternatives existed?
- What did they risk?
- How did it play out?
Guidelines:
- Build tension before revealing outcome
- Explore the "what if they'd done X instead?"
- Use specific numbers for before/after
- Include a quote from the time
### SECTION 4: KEY INFLECTION #2 (Turns 71-90, ~4 min)
Cover:
- New challenge or opportunity
- How they adapted/pivoted
- Key insight they gained
Guidelines:
- Connect to first inflection
- Show evolution of thinking
- Maya: technical implications
- James: strategic implications
### SECTION 5: MESSY MIDDLE (Turns 91-105, ~3 min)
Cover:
- Near-death moment(s)
- Internal conflicts
- What almost broke them
Guidelines:
- Don't glorify - show real struggle
- Include specific stakes ("6 months of runway")
- One host can play devil's advocate
### SECTION 6: NOW (Turns 106-120, ~3 min)
Cover:
- Current position and metrics
- Competitive landscape
- Open questions
Guidelines:
- Timeless framing (position, not news)
- Acknowledge uncertainty about future
- Set up takeaways
### SECTION 7: TAKEAWAYS (Turns 121-130, ~2-3 min)
Cover:
- Key lesson(s)
- Framework or principle
- Final memorable thought
Guidelines:
- Both hosts contribute insights
- Connect back to hook/central question
- End with forward-looking thought
- Final line should resonate
## Format Rules
1. Use `<Person1>` and `<Person2>` XML tags
2. Each turn: 2-5 sentences (conversation, not monologue)
3. Add section markers: `<!-- SECTION X: NAME -->`
4. Include discovery moments in every section
5. NO news references - timeless only
6. Use SPECIFIC facts from research
7. Both hosts should learn during conversation
8. Minimum 130 turns total
## Section Marker Format
```
<!-- SECTION 1: HOOK -->
<Person1>The email arrived at 2 AM...</Person1>
...
<!-- SECTION 2: ORIGIN -->
<Person1>Okay, so let's go back to the beginning...</Person1>
...
```
## Research Material
{{RESEARCH}}
---
## Selected Hook
{{HOOK}}
---
Generate the complete script now. Start with `<!-- SECTION 1: HOOK -->` followed by `<Person1>`.