
Meeting Processor
- 164 installs
- 339 repo stars
- Updated August 4, 2026
- glebis/claude-skills
Normalize raw meeting transcripts into summaries, decisions, risks, and ticket-ready tasks for engineering and product follow-through.
About
meeting-processor turns raw meeting transcripts into structured outputs—summaries, decisions, risks, and actionable tasks—for product and engineering teams. It supports SaaS and agent operators who hold frequent syncs and need consistent post-meeting artifacts without manual reformatting across tools.
- Transcript normalization
- Decision and risk extraction
- Owner assignment
- Ticket-ready tasks
- Multi-meeting rollups
Meeting Processor by the numbers
- 164 all-time installs (skills.sh)
- Ranked #634 of 2,715 Automation & Workflows skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/glebis/claude-skills --skill meeting-processorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 164 |
|---|---|
| repo stars | ★ 339 |
| Last updated | August 4, 2026 |
| Repository | glebis/claude-skills ↗ |
What it does
Normalize raw meeting transcripts into summaries, decisions, risks, and ticket-ready tasks for engineering and product follow-through.
Files
Meeting Processor
Intelligent meeting transcript processor that auto-detects meeting type and applies type-specific extraction with optional interactive clarification.
When to Use
- After syncing Fathom or Granola transcripts (
/fathom --today,/granola export) - When asked to process, analyze, or summarize a meeting transcript
- When a new meeting transcript appears in the vault root matching
YYYYMMDD-*.md - For coaching sessions, delegate to
coaching-session-summarizerskill instead
Prerequisites
pip install openai pyyamlRequires CEREBRAS_API_KEY environment variable (uses Cerebras API with llama-3.3-70b).
Supported Meeting Types
| Type | Description | Key Extractions |
|---|---|---|
| leadgen | Sales/business development calls | Commitments, pain points, budget, timeline, decision makers, deal stage, sentiment |
| partnership | Collaboration/partnership exploration | Opportunity overview, value proposition, strategic alignment, technical needs, fit assessment |
| coaching | Coaching/mentoring sessions | Insights, decisions, action items, themes, emotional arc, techniques, session quality |
| internal | Internal team meetings | Coming soon |
Usage
Interactive Mode (default)
Run the processor, which auto-detects meeting type and asks clarifying questions:
python3 ~/.claude/skills/meeting-processor/scripts/process.py <transcript-file> --mode interactiveInteractive flow: 1. Script analyzes transcript and detects meeting type 2. Extracts structured data via LLM 3. Identifies missing/ambiguous fields 4. Returns questions as JSON (exit code 2 signals interaction needed) 5. Parse the JSON between __INTERACTIVE_QUESTIONS__ markers 6. Use AskUserQuestion to collect answers for each question 7. Save answers to a temp JSON file and re-run with process_with_answers.py
Handling interactive questions:
When the script exits with code 2, parse the output for questions JSON. Each question has:
question: The question textheader: Short label (used as answer key)options: Array of{label, description}for AskUserQuestion
After collecting answers, create two temp files:
questions.json— the original questions context (includespartial_data,meeting_type,transcript_file)answers.json— map of{header_lowercase: selected_label}
Then run:
python3 ~/.claude/skills/meeting-processor/scripts/process_with_answers.py questions.json answers.jsonBatch Mode
Extract only high-confidence information without user interaction:
python3 ~/.claude/skills/meeting-processor/scripts/process.py <transcript-file> --mode batchForce Meeting Type
Skip auto-detection:
python3 ~/.claude/skills/meeting-processor/scripts/process.py <transcript-file> --type leadgen
python3 ~/.claude/skills/meeting-processor/scripts/process.py <transcript-file> --type partnershipOutput
Analysis is appended to the transcript file as a ## Meeting Analysis section. Frontmatter is updated with meeting_type, processed_date, and processing_mode.
Leadgen Output Structure
- Commitments & Actions — with deadlines and owners
- Follow-up — next meeting date if scheduled
- Client Context — pain points, budget, timeline, decision makers
- Deal Assessment — stage (cold/warm/hot), probability (1-5), blocker, sentiment
Partnership Output Structure
- Opportunity — description and value proposition for both sides
- Commitments & Actions — with deadlines and owners
- Follow-up — next meeting date if scheduled
- Partnership Context — strategic alignment, technical needs, resources, challenges
- Opportunity Assessment — fit (strong/medium/weak), readiness, success factors, sentiment
Step 2: Auto-Link Prep Notes
After the meeting analysis is complete (Step 1), automatically link any matching meeting-prep notes to the session note. This replaces the need to manually run /meeting-prep link.
How It Works
1. Derive the meetings directory from the processed session note's parent directory (do not hardcode paths).
2. Extract session metadata from the processed note:
datefrom frontmatter (YYYYMMDD format)participantsfrom frontmatter (list of names)- If no
participantsfield, extract names from the transcript header or attendee list
3. Search for matching prep notes:
find <MEETINGS_DIR> -name "YYYYMMDD-prep-*" -type f 2>/dev/nullWhere YYYYMMDD is the session date.
4. Validate the match: For each candidate prep note, read its frontmatter and confirm:
- The
datefield matches the session date - The
participantfield matches one of the session's participants (fuzzy: check both full name and first name, case-insensitive) - The
session_notefield is empty ("") — skip already-linked prep notes
5. Update both files when a match is found:
In the prep note:
- Set
session_note: "[[session-note-filename]]"(without.mdextension) - Set
status: done
In the session note:
- If a
## See alsosection exists, add- [[YYYYMMDD-prep-participant-slug]]to it - Otherwise, append a new section at the end:
## Prep Note
- [[YYYYMMDD-prep-participant-slug]]- Never create duplicate links — check if the link already exists before adding
6. Report in the processing output which prep notes were linked, skipped, or not found.
Rules
- Derive
MEETINGS_DIRfrom the session note path, not from hardcoded values - If the meeting-prep
config.yamlis available, readprep_notes.prefix(default:prep) andprep_notes.type_tag(default:meeting-prep) - This step is non-blocking: if it fails or finds no prep notes, processing still succeeds
{
"name": "meeting-processor",
"description": "This skill should be used when processing meeting transcripts to auto-detect meeting type (leadgen, partnership, coachin",
"author": {
"name": "Gleb Kalinin"
},
"repository": "https://github.com/glebis/claude-skills",
"license": "MIT"
}#!/bin/bash
#
# Interactive meeting processor wrapper
# Called by Claude Code to handle AskUserQuestion flow
#
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TRANSCRIPT_FILE="$1"
MODE="${2:-interactive}"
if [ -z "$TRANSCRIPT_FILE" ]; then
echo "Usage: $0 <transcript-file> [mode]"
exit 1
fi
# Load environment
if [ -f "$SCRIPT_DIR/scripts/.env" ]; then
export $(cat "$SCRIPT_DIR/scripts/.env" | grep -v '^#' | xargs)
fi
# Run initial processing
cd "$SCRIPT_DIR/scripts"
python3 process.py "$TRANSCRIPT_FILE" --mode "$MODE"
echo ""
echo "Interactive processing complete!"
Meeting Processor
Intelligent meeting transcript processor that detects meeting type and applies type-specific analysis with optional interactive clarification.
Features
- Auto-detection: Identifies meeting type from transcript content
- Interactive mode: Uses AskUserQuestion to clarify ambiguous details
- Batch mode: Auto-extracts only high-confidence information
- Type-specific extraction: Custom analysis per meeting type
Supported Meeting Types
1. Leadgen Call
Sales/business development calls with potential clients.
Extracts:
- Commitments (both sides) with deadlines
- Client pain points & needs
- Budget/timeline discussed
- Decision makers identified
- Next follow-up scheduled
- Deal stage assessment
- Objections/blockers
- Meeting sentiment
2. Partnership/Collaboration
Strategic partnership and collaboration exploration calls.
Extracts:
- Opportunity overview & value proposition
- Commitments & actions with deadlines
- Strategic alignment points
- Technical integration needs
- Resource requirements
- Challenges/concerns
- Fit & readiness assessment
- Meeting sentiment
3. Coaching Session (delegates to coaching-session-summarizer)
4. Internal Meeting (coming soon)
Usage
Interactive Mode (default)
python3 ~/.claude/skills/meeting-processor/scripts/process.py \
<transcript-file> \
--mode interactiveFlow: 1. Script analyzes transcript 2. Identifies missing/ambiguous fields 3. Outputs questions in JSON format (exit code 2) 4. Claude Code uses AskUserQuestion to collect answers 5. Script reprocesses with user input 6. Final analysis appended to transcript
Example questions:
- Leadgen: Follow-up scheduled? Budget discussed? Decision makers? Confidence level?
- Partnership: Follow-up scheduled? Resource requirements? Partnership fit assessment?
Batch Mode
python3 ~/.claude/skills/meeting-processor/scripts/process.py \
<transcript-file> \
--mode batchExtracts only high-confidence information without user interaction.
Force Meeting Type
python3 ~/.claude/skills/meeting-processor/scripts/process.py \
<transcript-file> \
--type leadgen \
--mode interactiveOutput
Appends analysis section to transcript file:
---
meeting_type: leadgen
processed_date: 2026-02-04
processing_mode: interactive
---
## Meeting Analysis
### Type
Leadgen Call
### Commitments & Actions
- [DEADLINE: 2026-02-10] Send proposal document (Gleb)
- [DEADLINE: 2026-02-07] Review technical requirements (Client)
### Follow-up
Next call: 2026-02-15 14:00 CET
### Client Context
**Pain Points:**
- Current system too slow for production needs
- Manual data entry causing errors
**Budget:** €50-75K discussed
**Timeline:** Q2 2026 deployment target
**Decision Makers:** CTO (technical approval), CFO (budget approval)
### Deal Assessment
**Stage:** Warm
**Probability:** 4/5
**Main Blocker:** Budget approval timeline
**Sentiment:** Positive - client engaged, asked detailed technical questionsRequirements
- Python 3.8+
anthropicpackageANTHROPIC_API_KEYenvironment variable
Installation
pip install anthropic pyyamlConfiguration
Set meeting type detection thresholds in scripts/detectors.py.
"""
Meeting type detection using LLM analysis
"""
import os
from openai import OpenAI
def detect_meeting_type(transcript_content, interactive=True):
"""
Detect meeting type from transcript content.
Args:
transcript_content: The transcript text
interactive: If True and uncertain, will ask user via AskUserQuestion
Returns:
str: Meeting type identifier (leadgen, partnership, coaching, internal)
"""
client = OpenAI(
api_key=os.environ.get('CEREBRAS_API_KEY'),
base_url="https://api.cerebras.ai/v1"
)
# Use Claude to classify
prompt = f"""Analyze this meeting transcript and classify it into ONE of these types:
1. **leadgen** - Sales/business development call with potential client
- Discussing services/products
- Exploring client needs
- Pricing/budget discussions
- Follow-up scheduling
2. **partnership** - Collaboration/partnership discussion
- Exploring joint opportunities
- Discussing mutual benefits
- Technical integration talks
- Strategic alignment
3. **coaching** - Coaching or mentoring session
- Personal development
- Goal setting
- Reflective questions
- Action planning
4. **internal** - Internal team meeting
- Project updates
- Team coordination
- Internal planning
- Status reviews
Respond with ONLY the type identifier (leadgen/partnership/coaching/internal) and confidence (high/medium/low) in format:
TYPE: <type>
CONFIDENCE: <confidence>
Transcript excerpt (first 2000 chars):
{transcript_content[:2000]}
"""
response = client.chat.completions.create(
model="qwen-3-235b-a22b-instruct-2507",
max_tokens=100,
messages=[{"role": "user", "content": prompt}]
)
result = response.choices[0].message.content.strip()
# Parse response
lines = result.split('\n')
meeting_type = None
confidence = None
for line in lines:
if line.startswith('TYPE:'):
meeting_type = line.split(':', 1)[1].strip().lower()
elif line.startswith('CONFIDENCE:'):
confidence = line.split(':', 1)[1].strip().lower()
# If low confidence and interactive, could use AskUserQuestion here
# For now, return best guess
if meeting_type not in ['leadgen', 'partnership', 'coaching', 'internal']:
meeting_type = 'leadgen' # Default fallback
return meeting_type
# Extractor modules
"""
Coaching session processor - extracts coaching-specific information
"""
import os
import json
import sys
from pathlib import Path
from openai import OpenAI
sys.path.insert(0, str(Path(__file__).parent.parent))
from interactive import generate_questions_coaching, apply_answers_coaching
def process(transcript_content, mode='interactive', user_answers=None):
"""
Process coaching session transcript.
Args:
transcript_content: The transcript text
mode: 'interactive' or 'batch'
user_answers: Dict of answers from interactive mode
Returns:
str: Formatted markdown analysis
"""
client = OpenAI(
api_key=os.environ.get('CEREBRAS_API_KEY'),
base_url="https://api.cerebras.ai/v1"
)
prompt = f"""Analyze this coaching/mentoring session transcript and extract:
1. **Key Insights** - Main realizations and discoveries (3-5 items)
- What did the coachee learn or understand?
2. **Decisions Made** - Concrete choices and commitments
- What was decided during the session?
3. **Action Items** - Specific next steps
- Format: Action item (Owner) [urgency: high/medium/low]
4. **Session Themes** - Recurring topics and patterns (2-4 themes)
5. **Emotional Arc** - How energy/mood shifted during the session
6. **Coaching Techniques Used** - Methods the coach employed
7. **Follow-up**
- Next session scheduled? When?
Return as JSON:
{{
"insights": ["..."],
"decisions": ["..."],
"action_items": [
{{"action": "...", "owner": "...", "urgency": "high/medium/low"}}
],
"themes": ["..."],
"emotional_arc": "...",
"techniques": ["..."],
"followup": {{"scheduled": true/false, "date": "YYYY-MM-DD HH:MM timezone or null"}},
"session_quality": {{
"engagement": "high/medium/low",
"depth": "high/medium/low",
"sentiment": "positive/neutral/negative",
"sentiment_reason": "..."
}}
}}
Transcript:
{transcript_content}
"""
response = client.chat.completions.create(
model="qwen-3-235b-a22b-instruct-2507",
max_tokens=2500,
messages=[{"role": "user", "content": prompt}]
)
result_text = response.choices[0].message.content.strip()
# Parse JSON response
try:
if '```json' in result_text:
result_text = result_text.split('```json')[1].split('```')[0].strip()
elif '```' in result_text:
result_text = result_text.split('```')[1].split('```')[0].strip()
data = json.loads(result_text)
except json.JSONDecodeError:
return f"**Error:** Could not parse analysis\n\n```\n{result_text}\n```"
# Interactive mode: apply user answers if provided
if mode == 'interactive' and user_answers:
data = apply_answers_coaching(data, user_answers)
# Interactive mode: check if questions needed
if mode == 'interactive' and not user_answers:
questions = generate_questions_coaching(data)
if questions:
return {
'needs_interaction': True,
'questions': questions,
'partial_data': data
}
# Format output
output = []
output.append("### Type")
output.append("Coaching Session\n")
# Key Insights
if data.get('insights'):
output.append("### Key Insights")
for insight in data['insights']:
output.append(f"- {insight}")
output.append("")
# Decisions
if data.get('decisions'):
output.append("### Decisions Made")
for decision in data['decisions']:
output.append(f"- {decision}")
output.append("")
# Action Items
if data.get('action_items'):
output.append("### Action Items")
for item in data['action_items']:
urgency = f" [{item['urgency']}]" if item.get('urgency') else ""
output.append(f"- {item['action']} ({item['owner']}){urgency}")
output.append("")
# Themes
if data.get('themes'):
output.append("### Session Themes")
for theme in data['themes']:
output.append(f"- {theme}")
output.append("")
# Emotional Arc
if data.get('emotional_arc'):
output.append("### Emotional Arc")
output.append(data['emotional_arc'])
output.append("")
# Techniques
if data.get('techniques'):
output.append("### Coaching Techniques")
for technique in data['techniques']:
output.append(f"- {technique}")
output.append("")
# Follow-up
if data.get('followup', {}).get('scheduled'):
output.append("### Follow-up")
output.append(f"Next session: {data['followup']['date']}\n")
# Session Quality
quality = data.get('session_quality', {})
if quality:
output.append("### Session Quality")
output.append(f"**Engagement:** {quality.get('engagement', 'unknown').capitalize()}")
output.append(f"**Depth:** {quality.get('depth', 'unknown').capitalize()}")
sentiment = quality.get('sentiment', 'neutral').capitalize()
reason = quality.get('sentiment_reason', '')
output.append(f"**Sentiment:** {sentiment} - {reason}")
return '\n'.join(output)
"""
Leadgen call processor - extracts sales-specific information
"""
import os
import json
import sys
from pathlib import Path
from openai import OpenAI
# Add parent directory to path for imports
sys.path.insert(0, str(Path(__file__).parent.parent))
from interactive import generate_questions_leadgen, apply_answers_leadgen
def process(transcript_content, mode='interactive', user_answers=None):
"""
Process leadgen call transcript.
Args:
transcript_content: The transcript text
mode: 'interactive' or 'batch'
Returns:
str: Formatted markdown analysis
"""
client = OpenAI(
api_key=os.environ.get('CEREBRAS_API_KEY'),
base_url="https://api.cerebras.ai/v1"
)
prompt = f"""Analyze this leadgen/sales call transcript and extract:
1. **Commitments & Actions** (with deadlines if mentioned)
- Format: [DEADLINE: YYYY-MM-DD] Action item (Owner)
- Include both sides' commitments
2. **Follow-up**
- Next meeting scheduled? When?
3. **Client Context**
- Pain points mentioned
- Budget discussed (if any)
- Timeline mentioned
- Decision makers identified
4. **Deal Assessment**
- Stage: cold/warm/hot
- Probability: 1-5 (your assessment)
- Main blocker (if any)
- Meeting sentiment: positive/neutral/negative with brief reason
Return as JSON:
{{
"commitments": [
{{"action": "...", "owner": "...", "deadline": "YYYY-MM-DD or null"}}
],
"followup": {{"scheduled": true/false, "date": "YYYY-MM-DD HH:MM timezone or null"}},
"client_context": {{
"pain_points": ["..."],
"budget": "... or null",
"timeline": "... or null",
"decision_makers": ["name/role"]
}},
"deal_assessment": {{
"stage": "cold/warm/hot",
"probability": 1-5,
"blocker": "... or null",
"sentiment": "positive/neutral/negative",
"sentiment_reason": "..."
}}
}}
Transcript:
{transcript_content}
"""
response = client.chat.completions.create(
model="qwen-3-235b-a22b-instruct-2507",
max_tokens=2000,
messages=[{"role": "user", "content": prompt}]
)
result_text = response.choices[0].message.content.strip()
# Parse JSON response
try:
# Extract JSON from markdown code blocks if present
if '```json' in result_text:
result_text = result_text.split('```json')[1].split('```')[0].strip()
elif '```' in result_text:
result_text = result_text.split('```')[1].split('```')[0].strip()
data = json.loads(result_text)
except json.JSONDecodeError:
return f"**Error:** Could not parse analysis\n\n```\n{result_text}\n```"
# Interactive mode: apply user answers if provided
if mode == 'interactive' and user_answers:
data = apply_answers_leadgen(data, user_answers)
# Interactive mode: check if questions needed
if mode == 'interactive' and not user_answers:
questions = generate_questions_leadgen(data)
if questions:
# Return questions for Claude to ask
return {
'needs_interaction': True,
'questions': questions,
'partial_data': data
}
# Format output
output = []
output.append("### Type")
output.append("Leadgen Call\n")
# Commitments
if data.get('commitments'):
output.append("### Commitments & Actions")
for item in data['commitments']:
deadline = f"[DEADLINE: {item['deadline']}] " if item.get('deadline') else ""
output.append(f"- {deadline}{item['action']} ({item['owner']})")
output.append("")
# Follow-up
if data.get('followup', {}).get('scheduled'):
output.append("### Follow-up")
output.append(f"Next call: {data['followup']['date']}\n")
# Client context
ctx = data.get('client_context', {})
if any(ctx.values()):
output.append("### Client Context")
if ctx.get('pain_points'):
output.append("**Pain Points:**")
for point in ctx['pain_points']:
output.append(f"- {point}")
output.append("")
if ctx.get('budget'):
output.append(f"**Budget:** {ctx['budget']}")
if ctx.get('timeline'):
output.append(f"**Timeline:** {ctx['timeline']}")
if ctx.get('decision_makers'):
output.append(f"**Decision Makers:** {', '.join(ctx['decision_makers'])}")
output.append("")
# Deal assessment
assess = data.get('deal_assessment', {})
if assess:
output.append("### Deal Assessment")
output.append(f"**Stage:** {assess.get('stage', 'unknown').capitalize()}")
output.append(f"**Probability:** {assess.get('probability', '?')}/5")
if assess.get('blocker'):
output.append(f"**Main Blocker:** {assess['blocker']}")
output.append("")
sentiment = assess.get('sentiment', 'neutral').capitalize()
reason = assess.get('sentiment_reason', '')
output.append(f"**Sentiment:** {sentiment} - {reason}")
return '\n'.join(output)
"""
Partnership/collaboration call processor
"""
import os
import json
import sys
from pathlib import Path
from openai import OpenAI
# Add parent directory to path for imports
sys.path.insert(0, str(Path(__file__).parent.parent))
from interactive import generate_questions_partnership, apply_answers_partnership
def process(transcript_content, mode='interactive', user_answers=None):
"""
Process partnership/collaboration call transcript.
Args:
transcript_content: The transcript text
mode: 'interactive' or 'batch'
Returns:
str: Formatted markdown analysis
"""
client = OpenAI(
api_key=os.environ.get('CEREBRAS_API_KEY'),
base_url="https://api.cerebras.ai/v1"
)
prompt = f"""Analyze this partnership/collaboration call transcript and extract:
1. **Opportunity Overview**
- What collaboration/partnership was discussed?
- Main value proposition for each party
2. **Commitments & Actions** (with deadlines if mentioned)
- Format: [DEADLINE: YYYY-MM-DD] Action item (Owner)
- Include both parties' commitments
3. **Follow-up**
- Next meeting scheduled? When?
4. **Partnership Context**
- Strategic alignment points
- Technical integration needs (if any)
- Resource requirements
- Potential challenges/concerns raised
5. **Opportunity Assessment**
- Fit: strong/medium/weak
- Readiness: both ready/needs work/unclear
- Key success factors
- Meeting sentiment: positive/neutral/negative with brief reason
Return as JSON:
{{
"opportunity": {{
"description": "...",
"value_proposition": {{
"our_side": "...",
"their_side": "..."
}}
}},
"commitments": [
{{"action": "...", "owner": "...", "deadline": "YYYY-MM-DD or null"}}
],
"followup": {{"scheduled": true/false, "date": "YYYY-MM-DD HH:MM timezone or null"}},
"context": {{
"strategic_alignment": ["..."],
"technical_needs": ["... or empty list"],
"resource_requirements": ["... or empty list"],
"challenges": ["... or empty list"]
}},
"assessment": {{
"fit": "strong/medium/weak",
"readiness": "both ready/needs work/unclear",
"success_factors": ["..."],
"sentiment": "positive/neutral/negative",
"sentiment_reason": "..."
}}
}}
Transcript:
{transcript_content}
"""
response = client.chat.completions.create(
model="qwen-3-235b-a22b-instruct-2507",
max_tokens=2500,
messages=[{"role": "user", "content": prompt}]
)
result_text = response.choices[0].message.content.strip()
# Parse JSON response
try:
# Extract JSON from markdown code blocks if present
if '```json' in result_text:
result_text = result_text.split('```json')[1].split('```')[0].strip()
elif '```' in result_text:
result_text = result_text.split('```')[1].split('```')[0].strip()
data = json.loads(result_text)
except json.JSONDecodeError:
return f"**Error:** Could not parse analysis\n\n```\n{result_text}\n```"
# Interactive mode: apply user answers if provided
if mode == 'interactive' and user_answers:
data = apply_answers_partnership(data, user_answers)
# Interactive mode: check if questions needed
if mode == 'interactive' and not user_answers:
questions = generate_questions_partnership(data)
if questions:
# Return questions for Claude to ask
return {
'needs_interaction': True,
'questions': questions,
'partial_data': data
}
# Format output
output = []
output.append("### Type")
output.append("Partnership/Collaboration Call\n")
# Opportunity overview
opp = data.get('opportunity', {})
if opp:
output.append("### Opportunity")
output.append(opp.get('description', 'N/A'))
output.append("")
vp = opp.get('value_proposition', {})
if vp:
output.append("**Value Proposition:**")
if vp.get('our_side'):
output.append(f"- *Our side:* {vp['our_side']}")
if vp.get('their_side'):
output.append(f"- *Their side:* {vp['their_side']}")
output.append("")
# Commitments
if data.get('commitments'):
output.append("### Commitments & Actions")
for item in data['commitments']:
deadline = f"[DEADLINE: {item['deadline']}] " if item.get('deadline') else ""
output.append(f"- {deadline}{item['action']} ({item['owner']})")
output.append("")
# Follow-up
if data.get('followup', {}).get('scheduled'):
output.append("### Follow-up")
output.append(f"Next call: {data['followup']['date']}\n")
# Context
ctx = data.get('context', {})
if any(ctx.values()):
output.append("### Partnership Context")
if ctx.get('strategic_alignment'):
output.append("**Strategic Alignment:**")
for point in ctx['strategic_alignment']:
output.append(f"- {point}")
output.append("")
if ctx.get('technical_needs'):
output.append("**Technical Integration:**")
for need in ctx['technical_needs']:
output.append(f"- {need}")
output.append("")
if ctx.get('resource_requirements'):
output.append("**Resource Requirements:**")
for req in ctx['resource_requirements']:
output.append(f"- {req}")
output.append("")
if ctx.get('challenges'):
output.append("**Challenges/Concerns:**")
for challenge in ctx['challenges']:
output.append(f"- {challenge}")
output.append("")
# Assessment
assess = data.get('assessment', {})
if assess:
output.append("### Opportunity Assessment")
output.append(f"**Fit:** {assess.get('fit', 'unknown').capitalize()}")
output.append(f"**Readiness:** {assess.get('readiness', 'unclear').capitalize()}")
if assess.get('success_factors'):
output.append("\n**Key Success Factors:**")
for factor in assess['success_factors']:
output.append(f"- {factor}")
output.append("")
sentiment = assess.get('sentiment', 'neutral').capitalize()
reason = assess.get('sentiment_reason', '')
output.append(f"**Sentiment:** {sentiment} - {reason}")
return '\n'.join(output)
"""
Interactive mode handler - generates questions for ambiguous fields
"""
import json
def generate_questions_leadgen(extracted_data):
"""
Generate clarifying questions for leadgen meeting based on extracted data.
Returns:
list: Question objects for AskUserQuestion tool
"""
questions = []
# Check follow-up scheduling
followup = extracted_data.get('followup', {})
if not followup.get('scheduled'):
questions.append({
"question": "Was a follow-up call scheduled with this lead?",
"header": "Follow-up",
"multiSelect": False,
"options": [
{"label": "Yes, scheduled", "description": "Follow-up call has a confirmed date/time"},
{"label": "No, not scheduled", "description": "No specific follow-up arranged"},
{"label": "Tentative", "description": "Follow-up discussed but not confirmed"}
]
})
# Check budget discussion
ctx = extracted_data.get('client_context', {})
if not ctx.get('budget'):
questions.append({
"question": "Was budget discussed during this call?",
"header": "Budget",
"multiSelect": False,
"options": [
{"label": "Yes, specific range", "description": "Client mentioned specific budget amount/range"},
{"label": "Yes, vague", "description": "Budget mentioned but no specific numbers"},
{"label": "Not discussed", "description": "Budget topic not covered"}
]
})
# Check decision makers
if not ctx.get('decision_makers'):
questions.append({
"question": "Were decision makers identified in the conversation?",
"header": "Decision makers",
"multiSelect": False,
"options": [
{"label": "Yes, identified", "description": "Specific people/roles mentioned"},
{"label": "Not clear", "description": "Decision process unclear"},
{"label": "Not discussed", "description": "Topic not covered"}
]
})
# Always ask for confidence assessment
questions.append({
"question": "What's your confidence level in closing this deal?",
"header": "Confidence",
"multiSelect": False,
"options": [
{"label": "Very high (5/5)", "description": "Strong buying signals, ready to move forward"},
{"label": "High (4/5)", "description": "Interested and engaged, likely to proceed"},
{"label": "Medium (3/5)", "description": "Some interest but unclear commitment"},
{"label": "Low (2/5)", "description": "Lukewarm response, significant barriers"},
{"label": "Very low (1/5)", "description": "Poor fit or low interest"}
]
})
return questions
def generate_questions_partnership(extracted_data):
"""
Generate clarifying questions for partnership meeting.
Returns:
list: Question objects for AskUserQuestion tool
"""
questions = []
# Check follow-up
followup = extracted_data.get('followup', {})
if not followup.get('scheduled'):
questions.append({
"question": "Was a follow-up meeting scheduled?",
"header": "Follow-up",
"multiSelect": False,
"options": [
{"label": "Yes, scheduled", "description": "Next meeting has confirmed date/time"},
{"label": "No, not scheduled", "description": "No specific follow-up arranged"},
{"label": "Action needed first", "description": "Waiting on actions before scheduling"}
]
})
# Check resource requirements
ctx = extracted_data.get('context', {})
if not ctx.get('resource_requirements'):
questions.append({
"question": "Were resource requirements discussed?",
"header": "Resources",
"multiSelect": False,
"options": [
{"label": "Yes, specific", "description": "Clear resource needs identified"},
{"label": "Yes, general", "description": "High-level resource discussion"},
{"label": "Not discussed", "description": "Resources not covered"}
]
})
# Always ask for fit assessment
questions.append({
"question": "How would you assess the partnership fit?",
"header": "Fit",
"multiSelect": False,
"options": [
{"label": "Strong fit", "description": "Aligned goals, clear value exchange, both motivated"},
{"label": "Medium fit", "description": "Some alignment but gaps to address"},
{"label": "Weak fit", "description": "Misaligned priorities or unclear value"}
]
})
return questions
def apply_answers_leadgen(extracted_data, answers):
"""
Apply user answers to leadgen extracted data.
Args:
extracted_data: Original extraction from LLM
answers: Dict of answers from AskUserQuestion
Returns:
dict: Updated extraction data
"""
# Follow-up
if 'followup' in answers:
if answers['followup'] == 'Yes, scheduled':
# Already has date from transcript or ask for specific date
if not extracted_data.get('followup', {}).get('date'):
extracted_data.setdefault('followup', {})['scheduled'] = True
extracted_data['followup']['date'] = 'Date to be confirmed'
else:
extracted_data['followup'] = {'scheduled': False, 'date': None}
# Budget
if 'budget' in answers:
if answers['budget'] in ['Yes, specific range', 'Yes, vague']:
extracted_data.setdefault('client_context', {})['budget'] = answers['budget']
# Decision makers
if 'decision_makers' in answers:
if answers['decision_makers'] == 'Yes, identified':
if not extracted_data.get('client_context', {}).get('decision_makers'):
extracted_data.setdefault('client_context', {})['decision_makers'] = ['Mentioned in call']
# Confidence
if 'confidence' in answers:
confidence_map = {
'Very high (5/5)': 5,
'High (4/5)': 4,
'Medium (3/5)': 3,
'Low (2/5)': 2,
'Very low (1/5)': 1
}
prob = confidence_map.get(answers['confidence'], 3)
extracted_data.setdefault('deal_assessment', {})['probability'] = prob
return extracted_data
def generate_questions_coaching(extracted_data):
"""
Generate clarifying questions for coaching session.
Returns:
list: Question objects for AskUserQuestion tool
"""
questions = []
# Check follow-up
followup = extracted_data.get('followup', {})
if not followup.get('scheduled'):
questions.append({
"question": "Was a follow-up session scheduled?",
"header": "Follow-up",
"multiSelect": False,
"options": [
{"label": "Yes, scheduled", "description": "Next session has confirmed date/time"},
{"label": "No, not scheduled", "description": "No specific follow-up arranged"},
{"label": "Regular cadence", "description": "Follows existing recurring schedule"}
]
})
# Always ask for session depth assessment
questions.append({
"question": "How would you rate the depth of this session?",
"header": "Depth",
"multiSelect": False,
"options": [
{"label": "High", "description": "Breakthrough insights, deep emotional work"},
{"label": "Medium", "description": "Good progress, some new understanding"},
{"label": "Low", "description": "Surface-level, mostly check-in"}
]
})
return questions
def apply_answers_coaching(extracted_data, answers):
"""
Apply user answers to coaching extracted data.
"""
# Follow-up
if 'followup' in answers:
if answers['followup'] == 'Yes, scheduled':
extracted_data.setdefault('followup', {})['scheduled'] = True
if not extracted_data['followup'].get('date'):
extracted_data['followup']['date'] = 'Date to be confirmed'
elif answers['followup'] == 'Regular cadence':
extracted_data.setdefault('followup', {})['scheduled'] = True
extracted_data['followup']['date'] = 'Regular recurring schedule'
else:
extracted_data['followup'] = {'scheduled': False, 'date': None}
# Depth
if 'depth' in answers:
depth_map = {
'High': 'high',
'Medium': 'medium',
'Low': 'low'
}
depth = depth_map.get(answers['depth'], 'medium')
extracted_data.setdefault('session_quality', {})['depth'] = depth
return extracted_data
def apply_answers_partnership(extracted_data, answers):
"""
Apply user answers to partnership extracted data.
"""
# Follow-up
if 'followup' in answers:
if answers['followup'] == 'Yes, scheduled':
extracted_data.setdefault('followup', {})['scheduled'] = True
if not extracted_data['followup'].get('date'):
extracted_data['followup']['date'] = 'Date to be confirmed'
else:
extracted_data['followup'] = {'scheduled': False, 'date': None}
# Resources
if 'resources' in answers:
if answers['resources'] in ['Yes, specific', 'Yes, general']:
extracted_data.setdefault('context', {}).setdefault('resource_requirements', []).append(
'Resource requirements discussed'
)
# Fit
if 'fit' in answers:
fit_map = {
'Strong fit': 'strong',
'Medium fit': 'medium',
'Weak fit': 'weak'
}
fit = fit_map.get(answers['fit'], 'medium')
extracted_data.setdefault('assessment', {})['fit'] = fit
return extracted_data
#!/usr/bin/env python3
"""
Re-process meeting with user answers from interactive mode
"""
import os
import sys
import json
import argparse
from pathlib import Path
# Add scripts directory to path
sys.path.insert(0, str(Path(__file__).parent))
from process import load_transcript, save_analysis, MEETING_PROCESSORS
def main():
parser = argparse.ArgumentParser(description='Re-process with user answers')
parser.add_argument('questions_file', help='Path to questions JSON file')
parser.add_argument('answers_file', help='Path to answers JSON file')
args = parser.parse_args()
# Load questions context
with open(args.questions_file, 'r') as f:
context = json.load(f)
# Load user answers
with open(args.answers_file, 'r') as f:
answers = json.load(f)
meeting_type = context['meeting_type']
transcript_file = context['transcript_file']
partial_data = context['partial_data']
print(f"📄 Reprocessing {meeting_type} meeting with user answers...")
# Load transcript
frontmatter, transcript_content = load_transcript(transcript_file)
# Get processor and apply answers
processor = MEETING_PROCESSORS[meeting_type]
analysis = processor(transcript_content, mode='interactive', user_answers=answers)
# Save results
print(f"💾 Saving analysis to {transcript_file}...")
save_analysis(transcript_file, frontmatter, transcript_content, analysis, meeting_type, 'interactive')
print("✓ Processing complete!")
if __name__ == '__main__':
main()
#!/usr/bin/env python3
"""
Meeting Processor - Intelligent transcript analysis with type detection
"""
import os
import sys
import argparse
import json
import tempfile
from pathlib import Path
from datetime import datetime
import yaml
# Add scripts directory to path
sys.path.insert(0, str(Path(__file__).parent))
from detectors import detect_meeting_type
from extractors import leadgen, partnership, coaching
MEETING_PROCESSORS = {
'leadgen': leadgen.process,
'partnership': partnership.process,
'coaching': coaching.process,
# 'internal': internal.process, # Future
}
def load_transcript(file_path):
"""Load transcript and extract existing frontmatter if present."""
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Parse frontmatter if exists
frontmatter = {}
transcript_content = content
if content.startswith('---'):
parts = content.split('---', 2)
if len(parts) >= 3:
try:
frontmatter = yaml.safe_load(parts[1]) or {}
transcript_content = parts[2].strip()
except yaml.YAMLError:
pass
return frontmatter, transcript_content
def save_analysis(file_path, frontmatter, transcript_content, analysis, meeting_type, mode):
"""Append analysis to transcript file."""
# Update frontmatter
frontmatter.update({
'meeting_type': meeting_type,
'processed_date': datetime.now().strftime('%Y-%m-%d'),
'processing_mode': mode
})
# Build output
output = f"---\n{yaml.dump(frontmatter, default_flow_style=False)}---\n\n{transcript_content}"
# Remove old analysis section if exists
if '\n## Meeting Analysis\n' in output:
output = output.split('\n## Meeting Analysis\n')[0]
# Append new analysis
output += f"\n\n## Meeting Analysis\n\n{analysis}"
with open(file_path, 'w', encoding='utf-8') as f:
f.write(output)
def main():
parser = argparse.ArgumentParser(description='Process meeting transcripts with type-specific analysis')
parser.add_argument('transcript', help='Path to transcript file')
parser.add_argument('--mode', choices=['interactive', 'batch'], default='interactive',
help='Processing mode (default: interactive)')
parser.add_argument('--type', choices=list(MEETING_PROCESSORS.keys()),
help='Force meeting type (auto-detect if not specified)')
parser.add_argument('--output', help='Output file (default: append to transcript)')
args = parser.parse_args()
# Validate file
if not os.path.exists(args.transcript):
print(f"❌ Error: File not found: {args.transcript}")
sys.exit(1)
print("📄 Loading transcript...")
frontmatter, transcript_content = load_transcript(args.transcript)
# Detect or use specified meeting type
if args.type:
meeting_type = args.type
print(f"📋 Using specified type: {meeting_type}")
else:
print("🔍 Detecting meeting type...")
meeting_type = detect_meeting_type(transcript_content, args.mode == 'interactive')
print(f"📋 Detected type: {meeting_type}")
# Get processor
if meeting_type not in MEETING_PROCESSORS:
print(f"❌ Error: No processor for meeting type: {meeting_type}")
sys.exit(1)
# Process transcript
processor = MEETING_PROCESSORS[meeting_type]
print(f"🤖 Processing as {meeting_type} call ({args.mode} mode)...")
analysis = processor(transcript_content, mode=args.mode)
# Check if interactive questions needed
if isinstance(analysis, dict) and analysis.get('needs_interaction'):
print("\n🤔 Interactive mode: questions identified")
print(f" {len(analysis['questions'])} questions need user input")
# Output questions as JSON for Claude to parse
questions_json = json.dumps({
'meeting_type': meeting_type,
'questions': analysis['questions'],
'partial_data': analysis['partial_data'],
'transcript_file': args.transcript
}, indent=2)
print("\n__INTERACTIVE_QUESTIONS__")
print(questions_json)
print("__END_INTERACTIVE_QUESTIONS__")
sys.exit(2) # Exit code 2 signals interactive mode needed
# Save results
output_file = args.output or args.transcript
print(f"💾 Saving analysis to {output_file}...")
save_analysis(output_file, frontmatter, transcript_content, analysis, meeting_type, args.mode)
print("✓ Processing complete!")
if __name__ == '__main__':
main()
{
"name": "meeting-processor",
"version": "0.1.0",
"description": "Intelligent meeting transcript processor with auto-detection and type-specific analysis",
"trigger": {
"keywords": ["process meeting", "analyze meeting", "meeting summary", "process transcript"],
"patterns": ["after.*sync", "new.*meeting", "analyze.*call"]
},
"capabilities": [
"Auto-detects meeting type (leadgen, partnership, coaching, internal)",
"Extracts commitments with deadlines",
"Identifies follow-up actions",
"Assesses opportunities and fit",
"Interactive mode for clarifications",
"Batch mode for automated processing"
],
"meeting_types": {
"leadgen": {
"description": "Sales/business development calls",
"extracts": ["commitments", "client_pain_points", "budget", "timeline", "decision_makers", "deal_stage"]
},
"partnership": {
"description": "Collaboration and partnership exploration",
"extracts": ["opportunity_overview", "value_proposition", "strategic_alignment", "technical_needs", "fit_assessment"]
}
},
"requirements": {
"python": "3.8+",
"packages": ["openai", "pyyaml"],
"api_keys": ["CEREBRAS_API_KEY"]
}
}