TakeMyCourseForMe
Extract a course you paid for, then turn it into cliff notes you can scan in under a minute and a "know enough to build/implement it" handoff for your team. Works on Skool and Circle from one skill, one entry point.
The capture (extraction) is the means. The cliff notes + build handoff are the point. You are not archiving a course — you are converting a paid course into something you can act on.
When to use
Use this skill when the user says any of:
- "extract this course" / "pull this course down"
- "cliff notes" / "study this course" / "TLDR this course" / "summarize my course"
- "I bought this Skool/Circle course, go get it"
- "turn this into a study guide" / "give me the cliff notes for my team"
⚠️ THE ONE RULE THAT SAVES YOU HOURS
NEVER use a headless browser for Skool.
Skool sits behind a login wall plus CloudFront signed URLs that only a real
session — a physical click or JavaScript running inside the user's actual
logged-in Chrome tab — can produce. A headless browser gets a 403 on every
asset (and a 146-byte MissingKey error body is the tell).
The user has already logged in. Drive their real Chrome. Do not try to replicate the session in a headless browser — you will burn hours and fail.
Skool -> osascript "execute javascript" in the live logged-in Chrome tab
Circle -> two options: live Chrome (same bridge), OR Chrome cookie
decrypt -> import into a headless fetch (Circle HTML is
server-rendered, so plain HTTP with the right cookies works)
The pipeline at a glance (8 steps)
| # | Step | What you produce |
|---|---|---|
| 1 | Auth | A working way to read the course (live Chrome, never headless for Skool) |
| 2 | Map | Full course/module tree as a JSON manifest |
| 3 | Capture (resumable) | Lesson text + resource links (driver); video, screenshots, transcripts are manual (Step 4) |
| 4 | Transcripts | Clean text transcript per video lesson |
| 5 | Cliff notes | TLDR + scannable study guide (the centerpiece) |
| 6 | Deep read | Read every lesson line-by-line — never summarize summaries |
| 7 | Gap analysis | Compare to what you already have; build only real gaps |
| 8 | Handoff | "Here's everything you need to build/implement this" |
The scripts in scripts/ automate steps 1–4. Steps 5–8 are done by the agent
(you) using the captured files — with make_cliff_notes.py scaffolding step 5.
Step 1 — Auth (never headless)
Personal-use boundary: for personal study of content you've paid for. Do not redistribute the extracted material.
Skool — drive the live Chrome tab. Use scripts/chrome_js.py, an
AppleScript bridge that runs JavaScript in the user's already-open, already
logged-in Chrome tab:
python3 scripts/chrome_js.py 'skool.com' 'document.title'
It finds the tab whose URL contains the substring you pass (e.g. skool.com)
and runs your JS there. You can also pass a JS file with @path:
python3 scripts/chrome_js.py 'skool.com' '@scripts/skool_tree.js'
chrome_js.pyis macOS-only (it shells out toosascript). On other OSes, use an equivalent "run JS in the real browser" mechanism — or fall back to the cookie-import path below. The rule is the same: real session, never headless.
Circle — two options. (1) Same live-Chrome bridge above; or (2) cookie decrypt → headless import: Circle pages are server-rendered HTML, so a plain HTTP request with the user's session cookies returns the full lesson content. Export the cookies (a browser extension's "cookies.txt", or a cookie-export library), then:
python3 scripts/circle_walk.py --url 'https://YOUR.circle.so/c/...' --cookies cookies.txt
Document both in your run notes; Skool needs the bridge, Circle is happy either way.
Step 2 — Map the course
Pull the full course/module tree and save it as a manifest (JSON) with title, URL, video link, and resource links per module. This is your resumable checkpoint — never lose it.
Skool — Skool is a Next.js app; the tree lives in __NEXT_DATA__ and in
_next/data/<buildId>/...json. Run the walker in the live tab:
python3 scripts/chrome_js.py 'skool.com' '@scripts/skool_tree.js' > tree.json
Then build the manifest from it:
python3 scripts/extract_course.py --make-manifest --tree tree.json \
--platform skool --course "Course Title" \
--url 'https://www.skool.com/...' --out manifest.json
Circle — server-rendered HTML; the walker extracts lesson links directly and writes a manifest:
python3 scripts/circle_walk.py --url 'https://YOUR.circle.so/c/...' \
--cookies cookies.txt --out manifest.json
Circle manifests do not carry video_url. The course tree page lists
lesson links but not each lesson's embedded video — the player lives on the
individual lesson page. Leave video_url null for Circle and pull videos
per-lesson in Step 4 (yt-dlp on the lesson page's Wistia/Vimeo iframe or
<video src>).
Manifest shape (the contract every script reads/writes):
{
"platform": "skool",
"course": "Course Title",
"url": "https://...",
"sections": [
{
"title": "Section 1",
"modules": [
{
"title": "Lesson 1",
"url": "https://...",
"video_url": "https://...",
"resources": [{"name": "workbook.zip", "url": "https://..."}],
"status": "pending",
"files": {"text": "lessons/01-section-slug/01-lesson-slug.md", "transcript": null, "screenshot": null}
}
]
}
]
}
Step 3 — Capture every lesson (resumable)
The driver captures text + resource links per lesson. Video, screenshots, and transcripts are manual jobs (see below and Step 4).
Automated (the driver does this):
- Text — the lesson body as markdown (Skool rich text is ProseMirror; the capture JS converts it).
- Resource links — ZIP/PDF/workbook/video links. On the Circle http backend the driver also downloads them; on Skool it records the links for you to click. These files are the actual course deliverables and the most-missed target.
Manual (you do these; the driver does NOT):
- Video — the MP4. Download it yourself with yt-dlp in Step 4, to an external drive (courses can be tens of GB). The driver does not download video.
- Screenshot — not automated; the Chrome backend can be extended (see
ONBOARDING). The driver only reserves a
screenshots/dir with--screenshots. - Transcript — see Step 4.
The driver does text + resource capture lesson-by-lesson and is resumable
— it tracks status in the manifest and skips anything already done, so a
crash loses nothing:
# Skool (drive live Chrome):
python3 scripts/extract_course.py --manifest manifest.json \
--out-dir ./captured --platform skool --backend chrome --tab 'skool.com'
# Circle (server-rendered HTML over HTTP):
python3 scripts/extract_course.py --manifest manifest.json \
--out-dir ./captured --platform circle --backend http --cookies cookies.txt
- Use
--limit 3to test on the first 3 lessons before the full run. A smoke test marks those lessonsdone; add--resetto clear allstatusandfilesback to pending for a clean re-run. - Run it as a background terminal process — long extractions outlive short-lived subagent timeouts. Kill and rerun; it resumes.
Downloadable files: the driver records every resource URL. On the http
backend it tries to download each one; any that fail (403 / a ~146-byte
MissingKey body = the CloudFront signed-URL wall) are logged to
manual-downloads.txt for you to click in the real browser — a real click
renews the signed URL and the download succeeds. On the chrome backend every
resource is signed, so all of them go to manual-downloads.txt. Successful
downloads never appear in the list.
Step 4 — Transcripts
Priority order (proven):
- Platform UI transcript — if the platform shows a transcript, grab it first.
- VTT captions — for Wistia/Vimeo/YouTube-hosted lessons, pull the caption
file. Manual (human) subtitles need
--write-subs, NOT--write-auto-subs(auto subs are the machine-generated ones and are wrong/noisy):
Then clean the VTT into text:yt-dlp --skip-download --write-subs --sub-langs en "VIDEO_URL" # en = your course's language; "all" also pulls noisy auto-subspython3 scripts/download_transcripts.py --input subs/ --out transcripts/ - Whisper fallback — if there are no captions, transcribe the downloaded
audio/video locally (e.g.
whisperorfaster-whisper).
The cleanup script also splits single-line transcripts on timestamp markers (a common export bug that otherwise produces one unreadable wall).
Step 5 — Cliff notes (THE DELIVERABLE)
Run the scaffolder, then you fill in the TLDR and takeaways from your deep read (Step 6):
python3 scripts/make_cliff_notes.py --manifest manifest.json \
--captured ./captured --out cliff-notes.md
The output must be scannable in under a minute. Concrete target format:
# Course Name — Cliff Notes
## ⚡ TLDR (30 seconds)
**What it is:** <one sentence — what the course actually teaches>
**Who it's for:** <one line>
**The one big idea:** <the core method / mindset>
**To build it you need:** <1–2 sentences — the implementation in a nutshell>
## 🗺️ Course at a glance
- **Section 1 — Setup** (6 lessons) → <one-line point>
- **Section 2 — The method** (9 lessons) → <one-line point>
- **Section 3 — Launch** (4 lessons) → <one-line point>
## 📚 Lesson index
1. **Pick a niche** — <one-line takeaway>
2. **Build the offer** — <one-line takeaway>
...
## 🛠️ To build/implement this you need:
- <tool / step 1>
- <tool / step 2>
- <tool / step 3>
## 📦 Files you downloaded
- <file.zip> · <file.pdf> · <video.mp4> (×N)
ADHD-friendly rules (non-negotiable):
- TLDR block at the top, bolded.
- Short sections, headers you can scan.
- Bold the takeaways — one bolded line per lesson, no walls of text.
- Over ~30 lessons, the index collapses to section-level takeaways; the full per-lesson index moves to an appendix — keep it scannable.
- Bullet lists, never paragraphs of prose.
- Scannable in under a minute.
Step 6 — Deep read (know enough to build)
Read every lesson line-by-line. NEVER summarize summaries.
This is the step that separates "extracted a course" from "can actually build the thing." Do not read the course's architecture labels and call it done — read the lesson files themselves. A past run was rejected for summarizing the summary of the summary. Three layers of abstraction = failure.
Read each captured .md, transcript, and the downloaded ZIP/PDF contents, then
extract what is buildable: steps, prompts, configs, scripts, templates,
pricing, funnels, checklists.
Step 7 — Gap analysis
Compare what the course teaches against what already exists (your prior work, your team's existing systems, other courses you already have). Build only the genuine gaps.
- If a superset already exists, don't rebuild it — note the overlap.
- When the user names what they want, everything else is excluded — respect that scope exactly; don't pad it and don't trim it.
- On overlap, still glean: "there's no way there's not something we glean from that."
Step 8 — Handoff
End with a "here's everything you need to build/implement this" brief: the method, the steps, the tools, the files, and the gaps — written so a builder can start immediately without re-reading the course.
Pitfalls (dedicated list — memorize these)
- CloudFront signed-URL 403 on downloads — 146-byte
MissingKeyerror body is the tell. Needs a real click in the logged-in browser, not a scriptedGET. Log tomanual-downloads.txt. - "Don't summarize summaries." Read lesson files line-by-line, not architecture labels. Three layers of abstraction got a course rejected.
- Download the ZIP files. Attachments are the deliverables — the most-missed target. Your first QA check: did you capture every ZIP/PDF attachment? They are the actual deliverables.
- Scope. When the user names what they want, everything else is excluded. Don't pad, don't trim, don't ask for a second correction.
async fetchnever resolves through Chrome'sexecute javascriptbridge → use synchronous XHR (xhr.open('GET', url, false)). This is baked intoskool_tree.js.- Manual captions need
--write-subs, not--write-auto-subs. Auto subs are machine-generated and wrong. - Long extraction must be resumable. Track
statusin the manifest and skipdonelessons — never lose the manifest on a crash. - Single-line transcript files need timestamp splitting —
download_transcripts.pydoes this automatically. - Subagents timeout at ~600s. Run long extraction in a background terminal process, not a delegated subagent.
- Apple Events permission (macOS).
chrome_js.pyhangs silently until Chrome > View > Developer > "Allow JavaScript from Apple Events" is checked (accept the "Terminal wants to control Google Chrome" dialog too). - Rate-limiting / Cloudflare bot-challenge on long runs. Space lessons out (a 1–3s delay between lessons) if the platform starts throwing challenges. The run is resumable, so back off and re-run rather than hammering.
- Course schema drift. Skool changes its
__NEXT_DATA__shape. Iftree.jsoncomes back empty or noisy, inspect the Network tab for_next/data/…jsonkeys and adjustskool_tree.js's key detection. - Session expiry mid-run. If captures come back empty or show a login page,
your session expired — re-auth in the real browser and re-run. Do NOT mark
those lessons
done(they stayfailed, so resume retries them). - Wrong-tab hijack. Extra Skool/Circle tabs matching the
--tabsubstring can steal the extraction — the driver uses the first match and warns on stderr when more than one matches. Close stray tabs first. - Print FULL absolute paths when there are errors — never a shorthand.
Scripts reference
| Script | Purpose |
|---|---|
scripts/chrome_js.py |
Run JS in the live logged-in Chrome tab (macOS/osascript). |
scripts/skool_tree.js |
JS payload: walk Skool __NEXT_DATA__ / _next/data (sync XHR). |
scripts/circle_walk.py |
Walk Circle server-rendered HTML into a manifest. |
scripts/lesson_capture.js |
JS payload: ProseMirror/rich text → markdown + resource links. |
scripts/extract_course.py |
Resumable lesson-by-lesson capture driver (both platforms). |
scripts/download_transcripts.py |
VTT → clean text; splits single-line timestamp dumps. |
scripts/make_cliff_notes.py |
Scaffold the ADHD cliff notes + build handoff from captures. |
See ONBOARDING.md for installation and a full walkthrough.
Output layout
Three separate outputs live in three separate places — keep them straight:
manifest.json # resumable checkpoint (tree + per-lesson status).
# Written where --out points (cwd by default), NOT
# inside the capture dir.
<out-dir>/ # the --out-dir you passed (default ./captured)
lessons/<sec>/<n>.md # lesson text as markdown
downloads/ # ZIP/PDF/video — the real deliverables
screenshots/ # reserved dir (screenshots are not auto-captured)
manual-downloads.txt # resource URLs the driver could NOT download (403/signed-URL wall) — click them in the real browser
transcripts/ # cleaned transcripts — written where Step 4's --out
# points (NOT inside the capture dir)
cliff-notes.md # THE deliverable (TLDR + study guide)
build-handoff.md # "know enough to build/implement it"
# both written next to make_cliff_notes.py --out