Process mode (default): Crawl an online course page, extract the lecture schedule with all materials (slides PDFs, YouTube videos, readings), then process each lecture into a detailed vault-formatted note with embedded slide images and synthesized transcripts. Creates a Course index note linking everything together.
Refine mode: Re-read existing course lecture notes and improve them — deepen equation explanations, add missing analogies, fix structure, strengthen cross-references. Triggered when user says "refine", "improve", "fix", or references existing course notes rather than a URL.
This is an orchestrator — it coordinates downloading, extraction, and synthesis across multiple lectures, producing a complete course knowledge package in the vault. </Purpose>
<Use_When>
- User shares a course URL (syllabus, schedule, or homepage)
- User says "process this course" or "take notes on these lectures"
- User pastes a university course page with a lecture list
- User wants to batch-process a series of lectures from one course
- User has a YouTube playlist of course lectures
- User wants to refine/improve existing course notes ("refine mit diffusion notes", "improve L03") </Use_When>
<Do_Not_Use_When>
- User has a single YouTube video (use /youtube)
- User has a single local video file (use /lecture)
- User wants to process an existing vault note that's NOT a lecture (use /process)
- User wants a single paper summarized (use /paper) </Do_Not_Use_When>
Stage 1: CRAWL — Extract the Lecture List
Parse the course URL from $ARGUMENTS. If no URL, ask the user.
Fetch the course page and extract structured lecture data:
WebFetch(
url="COURSE_URL",
prompt="Extract the complete course structure as JSON. For each lecture/session, include:
- number (int)
- title (string)
- date (string, if available)
- slides_url (string or null — look for PDF links to slides/lecture notes)
- video_url (string or null — look for YouTube links)
- readings (array of {title, url} — papers, blog posts, textbook chapters)
- description (string or null — any summary text)
Also extract:
- course_title (string)
- course_code (string or null)
- instructors (array of strings)
- course_url (string — the page URL)
- course_notes_url (string or null — if there's a single PDF of all course notes)
Return ONLY valid JSON, no markdown fencing."
)
Parse the JSON response. If the page has relative URLs for slides/videos, resolve them against the course URL's base.
Handle edge cases:
- If the page is a YouTube playlist: extract video IDs and titles from the playlist
- If slides URLs are relative (e.g.,
../docs/lecture_01.pdf): resolve to absolute URLs - If no structured schedule found: ask user to provide lecture list manually
Stage 1b: DERIVE COURSE TAG
Generate a short, memorable course hashtag from the course code or title. This tag will be used consistently across ALL notes for this course.
Rules for the tag:
- Use a descriptive short slug from the course topic, not just the code
- The tag should tell you what the course is ABOUT at a glance
- Examples:
#mit-diffusion,#cs231n-vision,#stanford-rl,#fast-ai-dl - Format:
institution-topicorcode-topic— lowercase, hyphens - Keep it under 20 characters — short enough to type, long enough to understand
- Confirm the tag with the user before proceeding
Also derive a COURSE_SLUG for asset folder naming (same as tag without #).
Example: assets folder assets/mit-diffusion/, tag #mit-diffusion.
Stage 2: PLAN — Confirm Scope with User
Present the extracted lecture list to the user in a clear table:
Found N lectures for "Course Title":
Course tag: #6s184
Assets folder: assets/6s184/
| # | Title | Slides | Video | Readings |
|---|-------|--------|-------|----------|
| 1 | Topic | PDF | YT | 2 papers |
| 2 | Topic | PDF | — | 1 paper |
...
Which lectures should I process? (default: all)
Options: "all", "1-3", "1,3,5", or specific numbers
If $ARGUMENTS includes a range (e.g., "1-3"), skip confirmation and use that range.
Stage 3: PROCESS — Handle Each Lecture
Content Source Priority
Not all sources are equal. A 50-minute video transcript where the instructor explains intuition, tells stories, and works through examples is 10x richer than a terse slide deck with equations and bullet points. The skill must be smart about which sources to use:
Priority order (use the best available, not just one):
- YouTube transcript + slides — the gold standard. Transcript provides the instructor's voice, explanations, and examples. Slides provide structure and visual reference. Use BOTH together.
- YouTube transcript only — still very rich. The instructor's words carry most of the value. Synthesize without slide embeds.
- Course notes PDF + slide PDFs — some courses publish comprehensive written notes (like a textbook). These can be as good as transcripts. Read the course notes PDF for the lecture's section, plus extract slide images.
- Slide PDFs only — the weakest source. Slides are terse by design — they're prompts for the speaker, not standalone explanations. When this is all you have, the noter agent must work harder to reconstruct meaning and add explanatory context. The notes will be less rich.
Key rule: When a YouTube video exists, ALWAYS extract its transcript even if slides are also available. The transcript is the primary content source; slides are supplementary visual aids.
For each lecture in scope, spawn a parallel subagent. Each subagent does:
3a. Download & Extract Slides (if PDF available)
SKILL_DIR="${CLAUDE_SKILL_DIR}"
COURSE_SLUG="mit-diffusion" # derived from course tag
LECTURE_NUM="01"
SLIDES_DIR="temp/course-slides-${COURSE_SLUG}"
mkdir -p "$SLIDES_DIR"
# Download PDF
curl -sL "SLIDES_PDF_URL" -o "$SLIDES_DIR/lecture-${LECTURE_NUM}.pdf"
# Convert PDF pages to images
uv run "$SKILL_DIR/scripts/extract_pdf_slides.py" \
"$SLIDES_DIR/lecture-${LECTURE_NUM}.pdf" \
--output-dir "$SLIDES_DIR/lecture-${LECTURE_NUM}-frames" \
--prefix "${COURSE_SLUG}-L${LECTURE_NUM}"
The script outputs images and a manifest JSON. Copy selected frames to a
course-specific subfolder in assets/:
ASSETS_DIR="assets/${COURSE_SLUG}"
mkdir -p "$ASSETS_DIR"
cp "$SLIDES_DIR/lecture-${LECTURE_NUM}-frames"/*.png "$ASSETS_DIR/"
All slides for this course live in assets/6s184/, keeping them organized
and easy to find. The embed syntax still works: ![[6s184-L01-03.png]]
(Obsidian resolves short names across subfolders).
3b. Extract YouTube Transcript (if video available)
Reuse the youtube skill's fetch script:
YT_SKILL_DIR="${CLAUDE_PLUGIN_ROOT}/skills/youtube"
YT_OUTPUT="temp/course-yt-${COURSE_SLUG}-L${LECTURE_NUM}.json"
uv run "$YT_SKILL_DIR/scripts/fetch_youtube.py" "VIDEO_URL" --lang en > "$YT_OUTPUT"
Read the JSON output. If transcript fails, proceed with slides only.
3c. Read Supplementary Content
If course notes PDF exists: Read the relevant section from the course notes PDF. This is often richer than individual slide PDFs — it's written prose with explanations, not just bullet points. Pass this to the noter agent as primary text content alongside any transcript.
If no video transcript and no course notes: Read the slide PDF directly via the Read tool. This is the weakest source — the noter agent must reconstruct meaning from terse bullets and equations. Flag this in the agent prompt so it knows to add more explanatory context.
3d. Synthesize Lecture Note
Read the agent definition:
Read("${CLAUDE_SKILL_DIR}/agents/course-noter.md")
Launch the course-noter agent:
Agent(
subagent_type="general-purpose",
model="sonnet",
run_in_background=true,
prompt="You are Course Noter. Follow these instructions exactly:
[INSERT FULL CONTENT OF agen