YouTube and Pocket Casts transcripts to summaries, threads, blogs, with automatic LLM Wiki ingestion. Optimized for low token cost — transcripts go to disk, subagents read from file. Auto-resolves Pocket Casts URLs to YouTube.
Use when the user shares a YouTube URL, Pocket Casts URL (pca.st/episode/...), or video link and asks to extract, summarize, or ingest the transcript. The default workflow is: resolve URL (if Pocket Casts) → fetch transcript → save to disk → dispatch subagents for wiki extraction → validate → report.
This skill is designed to minimize API token costs. Key design decisions:
The skill requires youtube-transcript-api. The setup handles PEP 668 (externally-managed Python) automatically:
cd SKILL_DIR
One-time setup — creates .venv if needed, installs deps
if [ ! -d ".venv" ]; then
uv venv 2>/dev/null
uv pip install youtube-transcript-api 2>&1
fi
To verify: uv run python3 -c "from youtube_transcript_api import YouTubeTranscriptApi; print('ready')"
The .venv persists across sessions — this only runs once.
When the user says "extract transcript from [url] and add to wiki" (or similar):
<!--
UNCOMMENT WHEN SWITCHING BACK TO OPENROUTER OR PER-TOKEN PROVIDER:
Phase 0 and Phase 6 provide cost delta tracking via check_credits.py.
With OpenCode Go ($10/month flat-rate), this is unnecessary.
See Phase 6 for the matching end-of-pipeline block.
uv run python3 SKILL_DIR/scripts/check_credits.py
Queries Hermes state.db for cumulative estimated cost (provider-agnostic — works with OpenCode, OpenRouter, etc.). Save the total_usage value for the delta calculation at the end.
"total_credits": null, that's expected for OpenCode.
Estimated costs by transcript length (based on actual measured costs, Jul–Aug 2026, DeepSeek v4-pro):
Longer videos cost disproportionately more because subagents re-read the long transcript for each page they create.
-->
SKIPPED: OpenCode Go is flat-rate ($10/month) — per-video cost tracking is meaningless. If you switch back to a per-token provider (OpenRouter, etc.), uncomment Phase 0 and Phase 6.When the user shares a pca.st/episode/... URL (or any Pocket Casts URL), resolve it to a YouTube URL BEFORE running the normal pipeline:
cd SKILL_DIR && uv run python3 scripts/resolve_pocketcasts.py "URL"
The script outputs JSON with podcast and episode_title. Use these to search YouTube:
curl -s "https://www.youtube.com/results?search_query=$(python3 -c 'import urllib.parse; print(urllib.parse.quote("<podcast> <episode_title>"))')" \
-H "User-Agent: Mozilla/5.0" | grep -oP '"videoId":"[^"]+"' | head -1
Extract the video ID, verify with oembed:
curl -s "https://www.youtube.com/oembed?url=https://www.youtube.com/watch?v=<VIDEO_ID>&format=json"
If the author_name matches the podcast and the title is similar (YouTube often re-titles), proceed. If ambiguous, confirm with the user using clarify.
1. STOP the YouTube search. Don't spend more than 3-4 search attempts.
2. Check if audio is available. Pocket Casts pages usually have a downloadable MP3 (use browser console: document.querySelector('a[download]').href). The download link is also visible on the page as "Download file."
3. If audio exists, transcribe with Whisper:
# Download audio
curl -sL --max-time 120 -o /tmp/<slug>.mp3 "<audio_url>"
# Transcribe with Whisper small model (English-only for speed)
export PATH="/opt/data/home/.local/bin:$PATH"
whisper /tmp/<slug>.mp3 --model small --language en --output_dir /tmp --output_format txt
uv tool install openai-whispersmall model is ~461MB (downloaded on first use) and takes ~5-10 min for a 45-min episode on CPU/tmp/<slug>.txt4. Use the transcript for the rest of the pipeline (Phase 2 onwards) — save to wiki with transcription_method: whisper-small in frontmatter.
5. If no audio is available either, tell the user: "This episode has no YouTube mirror and no downloadable audio. Cannot transcribe."
Do NOT attempt Vimeo API, Pocket Casts API, or any other workaround. The approved fallback is: YouTube transcript → Whisper from downloadable audio. Nothing else.
Once confirmed, use the YouTube URL for the rest of the pipeline (Phase 1 onwards).
IMPORTANT: The Pocket Casts source URL still goes intosource_url in the transcript frontmatter (Phase 2). YouTube is only used for fetching the transcript.
Check memory FIRST (fast, deterministic, near-zero token cost). Only fall back to file search if memory doesn't have it.
Step 1: Check memory via recall# Use mnemosyne_recall with the video ID
mnemosyne_recall(query="tivaWTTVRhY", limit=1)
If the result contains the video ID (e.g., "Video tivaWTTVRhY ingested 2026-07-31"), read the ingested: date from the result and jump to the clarify step below.
If memory doesn't contain it, use a deterministic triple lookup as a second check:
mnemosyne_triple_query(subject="tivaWTTVRhY", predicate="ingested")
Step 2: Fall back to file search (only if memory has no record)
search_files(path="$WIKI_PATH/consume/raw", pattern="$VIDEO_ID", target="content", output_mode="files_only")
Note: consume/raw/ may be empty if previous raw files were deleted after processing. If no file is found here but memory also has no record, the video hasn't been ingested — proceed to Phase 1.
> "This video was already ingested on [date]. The entity page, concept pages, and highlights page already exist in the wiki. Do you want me to re-extract (overwriting existing pages) or skip?"
Use clarify with choices: ["Re-extract (overwrite)", "Skip (keep existing)"].
cd SKILL_DIR && uv run python3 scripts/fetch_transcript.py "URL" --text-only --timestamps
--language en.cd SKILL_DIR && uv run python3 scripts/fetch_transcript.py "URL" --text-only --timestamps > /tmp/yt-transcript-<slug>.txt
/opt/data/wiki/consume. All wiki paths below are relative to this base.
Staging directory: consume/raw/ — All raw source files (YouTube transcripts, article PDFs, etc.) go here. This directory is intentionally ephemeral: files land here for processing, then get deleted after Joseph confirms the extracted outputs (concepts, highlights, entities) are good. The directory will be empty most of the time — that's by design.
1. Compute sha256 of the transcript file: sha256sum /tmp/yt-transcript-<slug>.txt
2. Write to /opt/data/wiki/consume/raw/<video-slug>.md with frontmatter:
---
source_url: <original YouTube URL>
ingested: YYYY-MM-DD
sha256: <hex digest>
Then append the full transcript content below the frontmatter.
3. VERIFY FILE PLACEMENT: Confirm the file is at consume/raw/:
test -f /opt/data/wiki/consume/raw/<video-slug>.md && echo "✓ correct path" || echo "✗ WRONG PATH — investigate"
4. POST-PROCESSING DELETION: After Phase 4 (spot-check) confirms the extracted outputs are good, delete the raw transcript:
import os
os.unlink("/opt/data/wiki/consume/raw/<video-slug>.md")
Tell the user: "Raw transcript deleted. Extracted outputs (concepts, highlights, entities) are permanent."
Use delegate_task with tasks array to dispatch all three subagents simultaneously. Each subagent gets the file path to the transcript — NOT the transcript text.
1. Do NOT poll live logs. The delegation system auto-delivers consolidated results when all subagents finish. Reading /opt/data/cache/delegation/live/.../task-*.log wastes tokens and turns — just wait.
2. Self-contained subagents. Each subagent MUST update index.md and log.md for its own pages. This eliminates 3-4 parent-agent validation turns (the single biggest cost after the transcript).
3. Context is file-path ONLY. Do NOT paste the transcript content into the context field. This is the single biggest cost saver.
The three subagent tasks:
references/subagent-entity.md from this skill, follow its instructions, read the transcript from the provided file path, create entity page at /opt/data/wiki/consume/entities/<person-slug>.md, then update /opt/data/wiki/consume/index.md and /opt/data/wiki/consume/log.md./opt/data/wiki/consume), and any relevant wiki conventions. DO NOT include the transcript text itself.references/subagent-concepts.md from this skill, follow its instructions, read the transcript from the provided file path, create concept pages at /opt/data/wiki/consume/concepts/<concept-slug>.md, then update /opt/data/wiki/consume/index.md and /opt/data/wiki/consume/log.md. CRITICAL: Do NOT abbreviate paths — always use /opt/data/wiki/consume/ not /opt/data/wiki/. Do NOT put anything under /opt/data/wiki/raw/./opt/data/wiki/consume), conventions. NO transcript text. Include the transcript line count so the subagent knows how large the source is.references/subagent-highlights.md from this skill, follow its instructions, read the transcript from the provided file path, create highlights page at /opt/data/wiki/consume/highlights/<video-slug>.md, then update /opt/data/wiki/consume/index.md and /opt/data/wiki/consume/log.md./opt/data/wiki/consume), conventions. NO transcript text.TRANSCRIPT_PATH=/tmp/yt-transcript-<slug>.txt
WIKI_PATH=/opt/data/wiki/consume
Read the transcript from TRANSCRIPT_PATH using the read_file tool.
Follow the instructions in the youtube-content skill's references/subagent-*.md.
Use [[wikilinks]] to cross-reference pages. Minimum 2 outbound links per page.
ALL file writes go under /opt/data/wiki/consume/ — NEVER under /opt/data/wiki/raw/.
After all subagents complete (they've already updated index.md and log.md for their pages):
1. Quick spot-check: Read 1-2 concept pages at random. Verify:
2. Fix broken wikilinks across pages: Entity and highlights subagents run in parallel with concepts — they may link to concept slugs that don't exist. Compare [[concepts/...]] links in the entity and highlights pages against the actual files in /opt/data/wiki/consume/concepts/. Fix any mismatches. Do this check every time.
3. Fix any issues found in spot-check (use patch). Do NOT do exhaustive validation of every page — trust subagent self-validation.
3. Read /opt/data/wiki/consume/index.md to verify subagents added their entries correctly. Fix only if broken.
4. Prepend to /opt/data/wiki/consume/log.md with the ingest record:
## [YYYY-MM-DD] ingest | [video title]
- Pages created: [count] (entities: X, concepts: Y, highlights: 1)
- Source: [YouTube URL]
DO NOT re-read every page. The subagents already self-validated and updated navigation. Parent agent's role is a quick sanity check, not exhaustive audit.
Store a compact wiki summary in Mnemosyne so future sessions can skip re-reading SCHEMA.md, index.md, and log.md:
Use mnemosyne_remember with:
content: "Wiki at $WIKI_PATH: N pages. Entities: [list]. Concepts: [list]. Highlights: [list]. Tracking: [list]. Last ingest: [video title] on [date]. Video $VIDEO_ID ingested $DATE → entity: [name], highlights: [slug], concepts: [count]."
importance: 0.7
scope: global
source: tool
And store a deterministic triple for fast lookup:
mnemosyne_triple_add(subject="$VIDEO_ID", predicate="ingested", object="$DATE | $video_title | entity:$entity_slug | highlights:$highlight_slug | concepts:$count", source="youtube-content")
This gives two lookup paths: semantic recall (mnemosyne_recall("tivaWTTVRhY")) and deterministic fact lookup (mnemosyne_triple_query(subject="tivaWTTVRhY")).
<!--
UNCOMMENT WHEN SWITCHING BACK TO OPENROUTER OR PER-TOKEN PROVIDER.
This is the matching end-of-pipeline block for Phase 0.
uv run python3 SKILL_DIR/scripts/check_credits.py
Compute delta: ending_usage - starting_usage = cost of this transcription (in USD).
Report to the user:
Wiki ingest complete: [video title]
Pages created: [count] (entities: X, concepts: Y, highlights: 1)
Cost: $Z.ZZ (estimated, via state.db delta)
-->
SKIPPED: See Phase 0 note.Remove the temp transcript file AND the raw wiki file using execute_code (avoids terminal approval prompt for rm):
import os
os.unlink("/tmp/yt-transcript-<slug>.txt")
os.unlink("/opt/data/wiki/consume/raw/<slug>.md") # raw transcript deleted after processing
Located in references/:
| File | Subagent | What it extracts |
|---|---|---|
subagent-entity.md |
Entity page | Speaker bio, career, philosophy, key relationships, notable quotes |
subagent-concepts.md |
Concept pages | All frameworks, mental models, heuristics, and ideas from the video |
subagent-highlights.md |
Highlights page | Narrative summary, top quotes with timestamps, theme index, cross-links |
Each template includes:
If the user does NOT have a wiki or asks for a different format:
/opt/data/cache/delegation/live/... wastes tokens and turns — just wait for the consolidated result.index.md and log.md for its own pages. This eliminates the single biggest parent-agent cost.test -d .venv first.check_credits.py script is still maintained and provider-agnostic.hermes-cost-monitoring skill.-->
--language flag.pca.st/episode/UUID link and Phase -1 extracts podcast + episode title, searches YouTube, and confirms the match. No manual YouTube searching needed. If multiple videos match or results are ambiguous, confirm with the user./opt/data/wiki/consume/ is the only consume path. Raw source files go to consume/raw/, concepts to consume/concepts/, entities to consume/entities/, highlights to consume/highlights/. The consume/raw/ directory is ephemeral — files are deleted after processing. Don't worry if it's empty.consume/raw/. The extracted concepts, highlights, and entities are the permanent artifacts. The sources: frontmatter in those files references the YouTube URL directly, not the raw file.voice-first-workflow vs voice-note-first-architecture). In Phase 4 spot-check, compare all [[concepts/...]] wikilinks across entity and highlights pages against what the concepts subagent actually created. Fix mismatches. Do this BEFORE finalizing.## Origin section must include [[highlights/video-slug]] and the ## Related section must also list it.Timothy Keller — "Your Plans: God's Plans" breaks YAML parsing because : inside the value is read as a key-value separator. Obsidian shows the raw YAML text with a red highlight instead of rendering properties. Fix: use single quotes around the entire title value and escape internal apostrophes as '' (e.g., title: 'Timothy Keller — "Your Plans: God''s Plans" — Key Highlights'). The highlights subagent template already enforces this; if you're writing a page manually, apply the same rule. Check memory FIRST: mnemosyne_recall("$VIDEO_ID") or mnemosyne_triple_query(subject="$VIDEO_ID"). These are near-zero token cost and deterministic. Only fall back to searching consume/raw/ files if memory has no record (e.g., for videos ingested before this dedup feature was added). Note that consume/raw/ may be empty if previous raw files were deleted after processing — an empty directory doesn't mean the video wasn't ingested.