coursera-cli
v0.3.2
Published
Your Coursera courses from the terminal: CLI + MCP server that pulls transcripts and readings from the courses you are enrolled in
Maintainers
Readme
coursera-cli
██████ █████ ██ ██ ██████ ███████ ███████ ██████ █████
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ ██████ ███████ █████ ██████ ███████
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ █████ █████ ██ ██ ███████ ███████ ██ ██ ██ ██Your own Coursera courses, from the terminal — and from Claude Code, over MCP.
Downloads the transcripts and readings of courses you are enrolled in, through Coursera's internal API. No HTML scraping, no gigabytes of video.
coursera courses --search pricing find the course
coursera map see what you actually studied
coursera transcript <slug> pull the content as textInstall
Requires Bun — the CLI is TypeScript and Bun runs it directly, with no build step.
bun add -g coursera-cli # or: npm install -g coursera-cliThat puts a coursera command on your PATH. It works even when Bun itself is not on
PATH: the installed command is a small Node launcher that goes looking for Bun, and says
so plainly if it cannot find it.
From source
git clone https://github.com/StephCastrof001/coursera-cli.git
cd coursera-cli
bun install
bun link # optional: same global `coursera` commandWithout bun link, run it as bun run index.ts <command> from the repo root.
Session
The CLI needs your account's CAUTH cookie. It looks in three places, in order:
- The
COURSERA_CAUTHenvironment variable - Its own store, under the platform config directory
- A store left by an earlier capture (
~/.config/coursera_recon/session.json)
Getting the cookie
Logging in is not automated. Typing credentials from a script is what triggers the CAPTCHA, so the cookie is copied by hand — once. It lasts days, not hours: the longest measured lifetime so far is 126 hours.
- Log in to coursera.org in your browser
- Open DevTools (
F12) → Application tab on Chrome, Storage on Firefox - Cookies →
https://www.coursera.org→ find the row namedCAUTH - Copy its Value — a long opaque string
Then hand it to the CLI:
# macOS, Linux, Git Bash
export COURSERA_CAUTH="paste-the-value-here"
# PowerShell
$env:COURSERA_CAUTH = "paste-the-value-here"Make it permanent by adding that line to your shell profile, or keep it in the session
store. Either way, coursera session tells you whether it worked:
coursera session # is it alive, where did it come from, how old is it
coursera doctor # session + routes + paths, all at once[OK] version: coursera-cli 0.3.1 on win32, bun 1.3.11
[OK] session-present: found via legacy, 108.2 h old
[OK] session-alive: 215 courses visible
[OK] endpoints: 10 routes declared, domains.v1 alive
[OK] paths: state C:\Users\you\AppData\Local\coursera-cli
5/5 checks passedCommands
| Command | What it does |
|---|---|
| coursera session | Session state: source, age, alive or dead |
| coursera doctor | Diagnoses session, live routes and writable paths |
| coursera courses [filters] | Your courses |
| coursera map [--detail] | Branches, levels, institutions, unfinished specializations |
| coursera course <slug> | Syllabus, institution, instructors, declared level |
| coursera transcript <slug> | Downloads transcripts and readings |
Filters
Filters compose with AND, and multi-word values need no quotes:
coursera courses --search machine learning
coursera courses --level intermediate --domain data-science
coursera courses --lang es --hours 5 # short courses in Spanish
coursera courses --university duke| Flag | Example |
|---|---|
| --search <text> | name or slug |
| --level | beginner, intermediate, advanced |
| --domain <id> | branch or sub-branch, e.g. data-science, machine-learning |
| --lang <code> | primary language, e.g. es |
| --hours <n> | at most n estimated hours |
| --university <name> | e.g. duke |
Global flags
| Flag | Default | Purpose |
|---|---|---|
| --json | off | Structured output. Implied when stdout is piped |
| --out <dir> | platform data dir | Where to write |
| --limit <n> | all | Stop after n items |
| --lang es,en | es,es-LA,en | Subtitle preference order |
| --quiet | off | No progress output |
| --color / --no-color | auto | Force colour on or off. Auto-detects TTY, and honours NO_COLOR and FORCE_COLOR |
Library map
coursera map cross-references your library against Coursera's own taxonomy:
215 courses in your library
levels: beginner 99 intermediate 66 advanced 7 undeclared 43
business 158 ████████████████████████ 1366 h
data-science 90 ██████████████ 735 h
computer-science 35 █████ 255 h
SPECIALIZATIONS (15)
✓ AI Product Management 3/3 complete
○ Digital Product Management 3/5 2 missingTwo caveats it prints itself, because both are real: a course filed under two branches counts in both, and hours come from Coursera's free-text workload field, which 78 of 215 courses do not state readably.
MCP for Claude
Register the server with the absolute path to the Bun binary — the process that
launches MCP servers does not inherit your PATH, so "command": "bun" fails with
"Failed to connect". Find yours with which bun (where.exe bun on Windows).
Claude Code
In .mcp.json at your project root, or in ~/.claude.json to have it everywhere:
{
"mcpServers": {
"coursera": {
"command": "C:/Users/you/.bun/bin/bun.exe",
"args": ["C:/Users/you/coursera-cli/src/mcp/index.ts"]
}
}
}Claude Desktop
Same shape, in the app's own config file — %APPDATA%\Claude\claude_desktop_config.json
on Windows, ~/Library/Application Support/Claude/claude_desktop_config.json on macOS:
{
"mcpServers": {
"coursera": {
"command": "/Users/you/.bun/bin/bun",
"args": ["/Users/you/coursera-cli/src/mcp/index.ts"]
}
}
}Tools
| Tool | Returns |
|---|---|
| session_status | Whether the session is alive, where it came from, how many courses it sees |
| list_courses | Your courses, with the same filters as the CLI |
| get_library_map | Branches, levels, institutions, specialization progress |
| get_course_outline | The course tree plus institution and instructors |
| fetch_transcripts | Downloads a course, returns the index of what landed |
| read_transcript | The text of one item |
fetch_transcripts returns paths, not text: a course is ~130 KB (≈35k tokens) and sending
it whole would blow up the conversation. To read, use read_transcript.
What it downloads
Text, by default: a .txt transcript per video and a .reading.md per reading. No video —
a course is gigabytes as MP4 against ~130 KB as text, and for studying or summarizing the
text carries the same signal.
Files are organized per module, numbered in order, with a manifest.json index.
Locked modules
When a course is in preview or has locked weeks, the aggregating API censors the item type: it returns them empty, and extractors that filter by type skip 75% of the syllabus believing it is empty.
This CLI does not filter. It asks the video and reading microservices about every item directly and keeps whatever answers.
Architecture
Four layers, one direction of dependency: commands and MCP call services, services call
http.ts, http.ts calls the network. Commands never build URLs and services never print —
that is what lets the CLI and the MCP server share every line of business logic.
index.ts → CLI dispatcher (one file per command, lazily imported)
src/
constants.ts → base URL, user agent, rate limit, paths
config.ts → session loading: env var, own store, legacy Python store
session.ts → bridge between the stored session and the HTTP client
http.ts → typed client; 200-with-HTML means a dead route, 401 a dead session
errors.ts → error vocabulary: every failure has a code and a hint
output.ts → flag parsing, table rendering, human vs JSON mode
endpoints.json → the routes. A deprecation is fixed here, not in the code
services/ → business logic, shared by CLI and MCP
memberships.ts → your enrolled courses, paginated and filtered
courses.ts → the syllabus tree, rebuilt from flat linked lists
transcripts.ts → probes video and reading microservices per item
download.ts → writes files, records where each course landed
library.ts → taxonomy cross-reference behind `coursera map`
partners.ts → resolves institution names to ids
commands/ → one file per CLI command
mcp/index.ts → MCP server, 6 tools
ui/
theme.ts → colour tokens, truecolor detection
banner.ts → wordmark and status dashboard
cli/ → cligentic blocks: json-mode, error-map, xdg-paths, doctor
test/ → 87 tests against responses captured live from the APITech Stack
- Bun — runtime. TypeScript runs directly, no build step
- TypeScript — strict mode, no
any - MCP SDK — the Claude integration, 6 tools
- picocolors — the only runtime dependency
besides the MCP SDK. Truecolor is layered on top of it in
ui/theme.ts - cligentic — copy-paste blocks for agent-facing CLI concerns: structured output, typed errors, XDG paths, health checks
Docs
| File | Contents |
|---|---|
| SPEC.md | The spec: problem, decisions, scope, verification |
| CONTEXT.md | Domain glossary |
| RESEARCH.md | Portal recon: endpoints, gotchas, what is alive |
| CHANGELOG.md | What changed in each version |
| endpoints.json | The routes. A deprecation is fixed here, not in the code |
Tests
bun test # 87 tests against responses captured live from the API
bun run typecheckLegal
Reaches your own account with your own session, for courses you are already enrolled in. Downloaded material is copyrighted by Coursera and its universities: it is for your personal study. Do not redistribute it.
