Files
wehub-resource-sync 09e9f3545f
Test / Code Quality (push) Has been cancelled
Test / Test (macos-latest, Python 3.10) (push) Has been cancelled
Test / Test (macos-latest, Python 3.11) (push) Has been cancelled
Test / Test (macos-latest, Python 3.12) (push) Has been cancelled
Test / Test (macos-latest, Python 3.13) (push) Has been cancelled
Test / Test (macos-latest, Python 3.14) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.10) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.11) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.12) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.13) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.14) (push) Has been cancelled
Test / Test (windows-latest, Python 3.10) (push) Has been cancelled
Test / Test (windows-latest, Python 3.11) (push) Has been cancelled
Test / Test (windows-latest, Python 3.12) (push) Has been cancelled
Test / Test (windows-latest, Python 3.13) (push) Has been cancelled
Test / Test (windows-latest, Python 3.14) (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled
dependency-audit / pip-audit (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:30:13 +08:00

589 lines
38 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Installation
**Last Updated:** 2026-07-11
This is the canonical installation guide for `notebooklm-py`. The README has a quickstart; everything else lives here.
**Contents**
- [Prerequisites](#prerequisites)
- [Quick install (TL;DR by persona)](#quick-install-tldr-by-persona)
- [Choose your install path](#choose-your-install-path)
- [A. AI Agent (primary persona)](#a-ai-agent-primary-persona)
- [B. End user](#b-end-user)
- [C. Library user](#c-library-user)
- [D. Headless server or CI](#d-headless-server-or-ci)
- [E. Contributor](#e-contributor)
- [F. Power user](#f-power-user)
- [Optional extras matrix](#optional-extras-matrix)
- [Post-install steps](#post-install-steps)
- [Verifying your install](#verifying-your-install)
- [Platform notes](#platform-notes)
- [Upgrading and uninstalling](#upgrading-and-uninstalling)
- [Common gotchas (appendix)](#common-gotchas-appendix)
- [All vs All-Extras](#all-vs-all-extras)
- [`uv pip install` vs `uv sync`](#uv-pip-install-vs-uv-sync)
---
## Prerequisites
- **Python 3.10 or later.** Tested and classified for 3.10, 3.11, 3.12, 3.13, 3.14. The CLI hard-fails with a clear error on older versions (see `_version_check.py`).
- **Operating systems.** macOS (primary development platform), Linux (Debian/Ubuntu, Fedora), Windows 10/11, WSL.
- **`uv` (optional but recommended for contributors).** Install with `curl -LsSf https://astral.sh/uv/install.sh | sh` or `brew install uv` / `winget install astral-sh.uv`. End users can use plain `pip` or `pipx`.
- **Disk and bandwidth.** Base install is small (~10 MB). The first `notebooklm login` downloads Chromium (~170 MB; 3090 s; **no progress bar** — be patient).
---
## Quick install (TL;DR by persona)
> **Installing the CLI on macOS / Linux — use an isolated installer.** Plain
> `pip install` into the *system* interpreter fails on modern macOS (Homebrew
> Python) and Debian/Ubuntu with `error: externally-managed-environment`
> ([PEP 668](https://peps.python.org/pep-0668/)). For CLI/app use, prefer
> **`uv tool install`** or **`pipx install`** — they put `notebooklm` (and
> `notebooklm-mcp` / `notebooklm-server`) on your PATH in a dedicated environment
> without touching system Python. Plain `pip` still works **inside a virtualenv**
> and on **Windows** (python.org's Python is not externally-managed). Library
> users install into their own project's venv (`uv add` / `pip install`), so
> PEP 668 never applies.
| Persona | Install command |
|---|---|
| **A — AI Agent** | `pip install "notebooklm-py[browser]"` in the user's active env (fall back to `uv tool install` / `pipx install` on an *externally-managed-environment* error) |
| **B — End user** | `uv tool install "notebooklm-py[browser]"` or `pipx install "notebooklm-py[browser]"` (isolated; avoids the PEP 668 error) |
| **C — Library user** | `uv add notebooklm-py` (or `pip install notebooklm-py` inside your project venv) |
| **D — Headless server / CI** | `pip install notebooklm-py` inside a venv/container; ship a `storage_state.json` (no Playwright) |
| **E — Contributor** | `uv sync --frozen --extra browser --extra dev --extra markdown && uv run playwright install chromium && uv run pre-commit install` |
| **F — Power user** | `uv tool install --python 3.12 "notebooklm-py[browser,cookies,markdown]"` (the `cookies` extra needs Python ≤ 3.12; `--python 3.12` makes uv provision a matching interpreter even if your default is 3.13+) |
---
## Choose your install path
### A. AI Agent (primary persona)
For Claude Code, Codex, and similar agent harnesses.
The project ships `notebooklm skill install`, [SKILL.md](../SKILL.md), and [AGENTS.md](../AGENTS.md). Agents run install on the user's behalf in the user's existing environment — no new venv. They typically can't *interact* with a browser, but most agent harnesses (Claude Code, Codex) *can* shell out to Playwright when a user is present, and `[cookies]` is a preferred optimization for reusing the user's already-logged-in browser cookies.
> **Note on agent harness coverage:** `notebooklm skill install` empirically writes to `~/.claude/skills/notebooklm/SKILL.md` and `~/.agents/skills/notebooklm/SKILL.md`. Cursor and other harnesses with bespoke skill formats are not auto-targeted; they fall back to `pip install` + manual skill registration.
**Recommended install (Python-version-aware; surfaces real errors instead of swallowing them):**
<!-- not mirrored: end-user install path (Persona A); CONTRIBUTING.md tracks the in-repo `uv sync` flow only -->
```bash
pip install "notebooklm-py[browser]" # mandatory; errors must propagate
# [cookies] (rookiepy) is optional and known to FAIL TO BUILD on Python 3.13+.
# Skip it deliberately on 3.13+ rather than swallowing the error — that lets
# *real* install failures (typos, network, PyPI outages) surface for the agent.
if python -c "import sys; sys.exit(0 if sys.version_info < (3, 13) else 1)"; then
pip install "notebooklm-py[cookies]" # errors propagate
else
echo "Skipping [cookies] on Python 3.13+ (rookiepy unavailable). Use 'notebooklm login' interactively."
fi
```
> If `pip install` errors with `externally-managed-environment` (modern macOS / Debian system Python, [PEP 668](https://peps.python.org/pep-0668/)), retry with `uv tool install "notebooklm-py[browser]"` or `pipx install "notebooklm-py[browser]"` — isolated installs that don't touch system Python. Inside an active virtualenv, `pip` works as-is.
**Why two separate calls (not `[browser,cookies]`):** the combined form is atomic — if `rookiepy` fails to compile, the whole install fails and the user gets **nothing**. Splitting means `[browser]` always succeeds; `[cookies]` is recoverable.
**Skill install (separate from the Python package):**
<!-- not mirrored: agent skill registration; not part of the contributor install flow -->
```bash
notebooklm skill install # writes to ~/.claude/skills/, ~/.agents/skills/
# OR (alternative ecosystem):
npx skills add teng-lin/notebooklm-py
```
If the agent is reading `SKILL.md` from inside an already-installed location (e.g. `~/.claude/skills/notebooklm/SKILL.md`), the skill is already present — you only need the Python package install + auth.
**Authentication — `notebooklm login` is the primary path:**
<!-- not mirrored: end-user auth setup; contributors usually source storage_state from a personal account -->
```bash
notebooklm login # primary: opens browser, user signs in to Google once
```
After login, `storage_state.json` persists at `~/.notebooklm/profiles/default/storage_state.json` and is reused on every subsequent run. **Verify with `notebooklm auth check --test --json`** (require `"status": "ok"` AND `"checks.token_fetch": true` — bare `auth check --json` only proves the file parses, not that the cookies still authenticate against Google).
**Headless / sandboxed agent contexts** (no display, can't open a browser): use the cookie-extraction path instead, requires the `[cookies]` extra installed in step 2:
<!-- not mirrored: headless-agent auth path; out of scope for the contributor README -->
```bash
notebooklm login --browser-cookies auto # rookiepy autodetects an installed browser
```
If the agent is in a no-display sandbox AND `[cookies]` isn't installed (Python 3.13+ skipped it), ask the user to run `notebooklm login` on a workstation and copy the resulting `~/.notebooklm/profiles/default/storage_state.json` to the agent's environment (or set `NOTEBOOKLM_AUTH_JSON`).
#### Sandboxed agents (Claude Cowork)
**Claude Cowork** (Anthropic's sandboxed desktop agent for non-developers) and similar no-display sandboxes are a special case of the headless path above: there is no browser for `notebooklm login`, and the sandbox resets between sessions. Two adjustments make everything except `login` work:
- **Bootstrap each session** with the base install — `[browser]`/Playwright is *not* needed here, only for `login` (which you run elsewhere, once):
<!-- not mirrored: Cowork per-session bootstrap; not part of the contributor flow -->
```bash
pip install notebooklm-py # queries/generation/download need no extras
```
- **Reuse a host-generated `storage_state.json`.** Run `notebooklm login` once on a machine with a display, then bring the file into a sandbox-accessible folder. Point at it with the root `--storage` flag or `NOTEBOOKLM_AUTH_JSON` — the same mechanism as [Persona D](#d-headless-server-or-ci):
<!-- not mirrored: Cowork auth reuse; not part of the contributor flow -->
```bash
notebooklm --storage /path/to/storage_state.json list # per-invocation flag
# OR inline via env var (no file needed — e.g. from a Cowork-stored secret):
export NOTEBOOKLM_AUTH_JSON="$(cat /path/to/storage_state.json)"
notebooklm list
```
> ⚠️ **Security:** `storage_state.json` and `NOTEBOOKLM_AUTH_JSON` are bearer credentials for your Google account — store the file `0600` or in the sandbox's secret store, never commit or log them, and `unset NOTEBOOKLM_AUTH_JSON` after use.
Pass an explicit `-n/--notebook <id>` on notebook-scoped commands — the selected-notebook context does not survive a session reset. If Cowork reads `~/.claude/skills/`, `notebooklm skill install` registers the skill there; otherwise build the uploadable archive on the host with `notebooklm skill package` (writes `notebooklm-skill.zip`; see [cli-reference.md](cli-reference.md#skill-commands-notebooklm-skill-cmd)) and add it via **Claude Settings → Capabilities**.
**Verification (machine-parseable):**
<!-- not mirrored: agent-targeted verification; CONTRIBUTING.md uses the test+lint suite as its smoke check -->
```bash
notebooklm --version # text version
notebooklm auth check --json # JSON: {"status": "ok", "checks": {...}}
notebooklm auth check --test --json # same + network token-fetch validation
notebooklm list --json # JSON list (may be empty for new accounts)
```
> **Important:** `notebooklm status` is *context state* (selected notebook), **NOT auth**. Do not grep its output for auth signals.
**Error strings the agent should grep:**
- `"Playwright not installed"` → install `[browser]`
- `"rookiepy"` (in stderr of `pip install`) → expected on Python 3.13+; skip `[cookies]` and use interactive `notebooklm login`
- `"status": "ok"` (in `auth check --json`) → auth file present and parses; pair with `--test` for network validation
### B. End user
Occasional CLI use.
**Prerequisites:** Python 3.10+ already installed.
**Recommended — isolated install (macOS / Linux / Windows):**
<!-- not mirrored: end-user isolated install (pipx / uv tool); CONTRIBUTING.md targets in-repo contributors -->
```bash
uv tool install "notebooklm-py[browser]"
# OR, with pipx:
pipx install "notebooklm-py[browser]"
```
Both put `notebooklm` on your PATH in a dedicated environment, so they work even where the system Python is locked down — modern macOS (Homebrew) and Debian/Ubuntu reject a plain `pip install` into it with `error: externally-managed-environment` ([PEP 668](https://peps.python.org/pep-0668/)). (If you don't have `uv` yet: <https://docs.astral.sh/uv/getting-started/installation/>.)
Plain `pip` is fine **inside a virtualenv**, or on Windows (python.org's Python is not externally-managed):
<!-- not mirrored: end-user pip install in a venv (Persona B); CONTRIBUTING.md tracks the in-repo `uv sync` flow only -->
```bash
pip install "notebooklm-py[browser]"
```
**Post-install:** Run `notebooklm login` once. The CLI auto-installs Chromium on first run (~170 MB, 3090 s, **no progress bar — be patient**).
**Verify:**
<!-- not mirrored: end-user post-install verify (Persona B); contributors run the test suite instead -->
```bash
notebooklm --version
notebooklm login # opens Chromium for Google sign-in
notebooklm auth check --test # confirms auth roundtrip, with explicit success message
```
### C. Library user
Embedding `notebooklm-py` in a Python application.
**Recommended:** `pip install notebooklm-py` (in your app's venv).
**Post-install:** None for runtime use. To programmatically run interactive login from your app, add `[browser]` and run `playwright install chromium`.
**Why no extras by default:** all RPC traffic uses `httpx`; auth is cookie-based (`src/notebooklm/auth.py`). Apps can ship a pre-generated `storage_state.json` and never touch Playwright.
**Verify:**
```python
import notebooklm
print(notebooklm.__version__)
```
> **Production deployment patterns (tracked in [#417](https://github.com/teng-lin/notebooklm-py/issues/417)).** Production-grade FastAPI/Django integration — client lifetime in a `lifespan` handler, httpx pool sizing, behavior under concurrent CSRF refresh, multi-tenant `storage_state.json` rotation, a service-shaped Dockerfile, and structured rate-limit/backoff patterns — is not yet covered in `docs/python-api.md`. These were intentionally deferred from the install-docs consolidation (PR #416) to keep its scope focused. See [#417](https://github.com/teng-lin/notebooklm-py/issues/417) for the gap inventory and acceptance criteria.
### D. Headless server or CI
**Recommended:** `pip install notebooklm-py`
**Post-install (3-step recipe — Playwright is *not* required on the server):**
1. **On a workstation with a display**, install with `[browser]` and log in once:
<!-- not mirrored: headless-server bootstrap step 1 (Persona D); not part of contributor flow -->
```bash
pip install "notebooklm-py[browser]"
playwright install chromium
notebooklm login # writes ~/.notebooklm/profiles/default/storage_state.json
```
2. **Move the auth file to the server.** Either ship it as a file:
<!-- not mirrored: headless-server bootstrap step 2a (scp); not part of contributor flow -->
```bash
scp ~/.notebooklm/profiles/default/storage_state.json \
user@server:~/.notebooklm/profiles/default/storage_state.json
```
or stuff the contents into a CI / deployment env var (preferred for ephemeral runners):
<!-- not mirrored: headless-server bootstrap step 2b (env var); not part of contributor flow -->
```bash
export NOTEBOOKLM_AUTH_JSON="$(cat ~/.notebooklm/profiles/default/storage_state.json)"
```
> **CI env-var notes:**
> - `storage_state.json` is typically 415 KB — well under GitHub Actions' 48 KB single-secret cap.
> - Watch for trailing newlines: pipe with `tr -d '\n'` if your secret-set tool adds one (`cat ... | tr -d '\n' | gh secret set NOTEBOOKLM_AUTH_JSON`).
> - For **ephemeral runners** (GitHub Actions, GitLab CI — no persistent disk between runs), the layer-5 in-process refresh from [troubleshooting.md](troubleshooting.md#authentication-errors) cannot persist rotated cookies. Run `notebooklm auth refresh` periodically on a workstation cron and push the refreshed file with `gh secret set NOTEBOOKLM_AUTH_JSON < ~/.notebooklm/profiles/default/storage_state.json`.
3. **On the server**, run any non-`login` command:
<!-- not mirrored: headless-server smoke test; not part of contributor flow -->
```bash
notebooklm list
notebooklm auth check --test # verifies the cookies still authenticate against Google
```
**Why no extras:** reduces the install surface to 4 deps (`httpx`, `click`, `rich`, `filelock`); avoids 200+ MB Chromium download in CI images.
For runtime configuration (env vars, profiles, parallel agents), see [configuration.md#headless-servers--containers](configuration.md#headless-servers--containers).
#### Alternative: master-token auth (no cookie file to ship, survives expiry)
The cookie-copy recipe above ships a `storage_state.json` that eventually
expires (cookies are short-lived; ephemeral CI runners can't persist rotations).
The **master-token** path instead holds one durable Google master token and
**mints fresh web cookies from it on demand** — no browser per session, and an
expired session **re-mints automatically** (no manual re-login). One browser
sign-in, then headless forever.
<!-- not mirrored: master-token headless bootstrap (Persona D); not part of contributor flow -->
```bash
pip install "notebooklm-py[headless]" # adds gpsoauth (pure-Python)
# One-time bootstrap (a visible browser opens Google's EmbeddedSetup; sign in
# with a DEDICATED/throwaway account, and the single-use oauth_token is captured
# automatically). Add [browser] for the auto-capture, or paste it with
# --oauth-token <value> on a headless box.
notebooklm login --master-token --account you@gmail.com
# Ship master_token.json to the server instead of storage_state.json:
scp ~/.notebooklm/profiles/default/master_token.json \
user@server:~/.notebooklm/profiles/default/master_token.json
# On the server, just run commands — cookies are minted/refreshed as needed:
notebooklm list
# Force a re-mint by hand (or from cron) any time:
notebooklm login --master-token-refresh
```
When a `master_token.json` sits beside a profile's `storage_state.json`, an
expired session is recovered by re-minting from the master token in-process
(after the normal homepage/RotateCookies/headless ladder is exhausted) — so
long-lived headless workers self-heal.
> ⚠️ **Security:** the master token is **full-account, durable, and
> infostealer-grade** — a materially larger blast radius than an expiring
> `storage_state.json` (it survives password changes until explicitly revoked).
> Use a **dedicated/throwaway Google account only**, store it `0600` (the CLI
> does), and never commit or log it. This path uses Google's Android auth flow
> (`gpsoauth`) and is unofficial/ToS-grey, like the rest of this client. See
> [ADR-0023](adr/0023-master-token-headless-auth.md) for the design and rationale.
### E. Contributor
Working on this repo.
**Recommended (respects the checked-in `uv.lock`):**
<!-- not mirrored: contributor bootstrap with git clone + cd; CONTRIBUTING.md picks up after the clone with the canonical `uv sync --frozen --extra browser --extra dev --extra markdown` command (enforced verbatim by scripts/check_ci_install_parity.py). -->
```bash
git clone https://github.com/teng-lin/notebooklm-py.git
cd notebooklm-py
uv sync --frozen --extra browser --extra dev --extra markdown
source .venv/bin/activate
uv run playwright install chromium
pre-commit install
```
**Why `uv sync --frozen` and not `uv pip install -e ".[all]"`:** the repo has a checked-in `uv.lock`. `uv sync --frozen` enforces the lockfile and fails fast on drift; `uv pip install` ignores the lockfile and re-resolves transitively (will silently get newer versions of `playwright`, `ruff`, etc.).
**Why three extras and not `[all]`:** `[all]` is `pip` extras semantics. `uv sync --extra X` is the `uv` equivalent. The three extras here are the contributor subset of `[all]` = `[browser, dev, markdown, mcp, server]`. `cookies` is intentionally excluded (`rookiepy` build issues on Python 3.13+), and `mcp` / `server` are omitted from the default contributor flow because those adapters are not needed for the standard local suite; opt in via `--extra cookies` / `--extra mcp` / `--extra server` if needed.
**Why `browser` is part of the contributor install:** the default local test suite includes unit tests that import and patch `playwright.sync_api`, even though they do not launch a real browser. `uv sync --frozen --extra dev` installs pytest/ruff/mypy but not Playwright, so `uv run pytest` will fail with `ModuleNotFoundError: No module named 'playwright'`. Use the full contributor command above before running the default test suite.
**Linux only:** `uv run playwright install-deps chromium` (scoped form, matches `test.yml`).
**Pre-commit checklist (run before every commit):**
```bash
uv run ruff format --check . && \
uv run ruff check . && \
uv run mypy src/notebooklm --ignore-missing-imports && \
uv run pytest --cov=src/notebooklm --cov-report=term-missing --cov-fail-under=90
```
**Verify:**
<!-- not mirrored: contributor verify block; CONTRIBUTING.md mirrors only the pre-commit checklist (the more frequent, per-commit version) -->
```bash
notebooklm --version
uv run pytest --cov=src/notebooklm --cov-report=term-missing --cov-fail-under=90
uv run pre-commit run --all-files
```
### F. Power user
Non-default browsers, cookie extraction, markdown source dumps.
> **Why this section uses the combined `[browser,cookies]` form** — unlike Persona A, which uses two separate `pip install` calls so a `rookiepy` build failure doesn't leave the user with nothing: power users explicitly opted in, know what `rookiepy` is, and prefer the all-or-nothing tradeoff (single command, no wrapping logic).
> ⚠️ **Don't use `[all]` for power-user setups.** `[all]` deliberately *excludes* `cookies` (see [§ All vs All-Extras](#all-vs-all-extras)). If you `pip install "notebooklm-py[all]"` and then try `--browser-cookies`, you'll get an opaque `rookiepy` import error. For everything-and-the-kitchen-sink, use `pip install "notebooklm-py[browser,cookies,markdown]"` explicitly (Python ≤ 3.12 only).
- **`--browser-cookies` (no Playwright login):** `pip install "notebooklm-py[browser,cookies]"`. **Caveat:** `rookiepy` may fail to install on Python 3.13/3.14; use Python 3.12 or accept the risk. See [cli-reference.md#authentication-login](cli-reference.md#authentication-login) for the full `--browser-cookies` syntax, including `chrome::<profile-name-or-directory>` for one Chromium user-profile and `firefox::<container>` for Firefox Multi-Account Containers (on every OS — not just macOS). Use `notebooklm auth inspect --browser <browser>` for previewing available accounts before import.
- **Markdown source dumps:** `pip install "notebooklm-py[markdown]"` for `notebooklm source fulltext -f markdown`.
- **Edge instead of Chromium:** install Microsoft Edge from [microsoft.com/edge](https://www.microsoft.com/edge) first — `--browser msedge` does NOT auto-install Edge (only `--browser chromium` auto-installs). Then `notebooklm login --browser msedge`.
- **Multi-account (personal + work):** see [configuration.md#multiple-accounts](configuration.md#multiple-accounts). Common power-user flow: `notebooklm profile create work && notebooklm -p work login --browser-cookies edge --account work@corp.com`. Use `--all-accounts` to bootstrap profiles for every signed-in Google account in one command.
---
## Optional extras matrix
Source of truth: `pyproject.toml` `[project.optional-dependencies]`.
| Extra | What it adds | When you need it | pip command | uv (in your project) |
|---|---|---|---|---|
| (none) | `httpx`, `click`, `rich`, `filelock` | All RPC operations, all CLI commands except `login`. Suffices when you ship a `storage_state.json`. | `pip install notebooklm-py` | `uv add notebooklm-py` |
| `browser` | `playwright>=1.40.0` | `notebooklm login` (interactive). | `pip install "notebooklm-py[browser]"` | `uv add "notebooklm-py[browser]"` |
| `cookies` | `rookiepy>=0.1.0` | `notebooklm login --browser-cookies <browser>`, `notebooklm auth inspect`. | `pip install "notebooklm-py[cookies]"` | `uv add "notebooklm-py[cookies]"` |
| `headless` | `gpsoauth>=1.1.0` | `notebooklm login --master-token` — headless auth that mints/refreshes web cookies from a durable master token, no per-session browser. Pure-Python (in `all`). See [§ D](#d-headless-server-or-ci). | `pip install "notebooklm-py[headless]"` | `uv add "notebooklm-py[headless]"` |
| `impersonate` | `curl_cffi>=0.11` | **Experimental.** Browser TLS/JA3 impersonation transport — set `NOTEBOOKLM_TRANSPORT=curl_cffi` to route the authenticated API surface through a Chrome-fingerprinted connection (insurance vs TLS fingerprint-gating); override the profile with `NOTEBOOKLM_IMPERSONATE` (default `chrome`, e.g. `safari`, `chrome131`). Native wheels. | `pip install "notebooklm-py[impersonate]"` | `uv add "notebooklm-py[impersonate]"` |
| `markdown` | `markdownify>=0.14.1` | `notebooklm source fulltext -f markdown`. | `pip install "notebooklm-py[markdown]"` | `uv add "notebooklm-py[markdown]"` |
| `mcp` | `fastmcp>=2.14` | Run the MCP server (`notebooklm-mcp`) so an MCP client/agent can drive NotebookLM as tools. | `pip install "notebooklm-py[mcp]"` | `uv add "notebooklm-py[mcp]"` |
| `server` | `fastapi`, `uvicorn[standard]`, `python-multipart` | The localhost REST API server (`notebooklm-server`, experimental). See [§ REST API server](#rest-api-server). | `pip install "notebooklm-py[server]"` | `uv add "notebooklm-py[server]"` |
| `dev` | pytest stack, mypy, ruff (`==0.15.15` exact pin), pre-commit (`>=4.5.1`), vcrpy | Contributor tooling only. Not sufficient for this repo's default `uv run pytest`; add `browser` too because some unit tests import Playwright. | `pip install "notebooklm-py[dev]"` | `uv add "notebooklm-py[dev]"` (in your project) — but contributors *to this repo* use the [Persona E](#e-contributor) `uv sync` flow instead |
| `all` | Resolves to `browser` + `dev` + `headless` + `markdown` + `mcp` + `server` (**not `cookies`**) | Contributors who do not need `rookiepy`. | `pip install "notebooklm-py[all]"` | `uv add "notebooklm-py[all]"` (in your project) — see [All vs All-Extras](#all-vs-all-extras) |
> **Note on `uv` columns:** the `uv (in your project)` column is for users adding `notebooklm-py` as a dependency in **their own** project (requires a `pyproject.toml` in that project). Contributors working inside *this* repo use the Persona E flow (`uv sync --frozen --extra ...`), governed by this repo's `uv.lock`. Do not run `uv sync` outside a project — it errors with `No pyproject.toml found`.
---
## REST API server
> **⚠️ Experimental.** Like the MCP adapter, the REST server is experimental: the `/v1` surface and behavior may change in a minor release, and it is excluded from the public-API compatibility gate. Pin a version before relying on it for automation. The server also logs an experimental warning on every startup.
A single-tenant, localhost REST API over the same transport-neutral core as the CLI — the natural shape for scripting and agent automation (feed a notebook, generate an artifact, pull it down) without spawning a CLI process per call.
<!-- not mirrored: the server extra is end-user/automation tooling, not part of the contributor `uv sync` flow; CONTRIBUTING.md tracks only browser/dev/markdown. -->
```bash
uv tool install "notebooklm-py[server]" # fastapi + uvicorn + python-multipart
# OR, with pipx: pipx install "notebooklm-py[server]" (or plain pip inside a venv)
```
**Prerequisite:** a provisioned account (`storage_state.json`) from `notebooklm login`. The server holds one account for the process; it does not run browser login itself.
**Launch:**
<!-- not mirrored: REST-server launch (end-user/automation tooling); CONTRIBUTING.md tracks only the contributor `uv sync` flow. -->
```bash
export NOTEBOOKLM_SERVER_TOKEN="$(openssl rand -hex 32)" # REQUIRED — the server refuses to start without it
notebooklm-server --host 127.0.0.1 --port 8000 # loopback-only by default
```
Configuration is read from `NOTEBOOKLM_SERVER_*` env vars (overridable by the matching flags):
| Variable | Default | Purpose |
| --- | --- | --- |
| `NOTEBOOKLM_SERVER_TOKEN` | *(unset)* | Bearer token every request must present. **Required** — fail-closed if unset. |
| `NOTEBOOKLM_SERVER_HOST` | `127.0.0.1` | Bind host. Non-loopback is refused unless the elevated-risk override below is set. |
| `NOTEBOOKLM_SERVER_PORT` | `8000` | Bind port. |
| `NOTEBOOKLM_SERVER_ALLOW_EXTERNAL_BIND` | *(unset)* | ⚠️ Set to `1` to bind a non-loopback interface. Only behind a trusted reverse proxy — this exposes account-fronting credentials to the network. |
| `NOTEBOOKLM_SERVER_SOURCE_MUTATION_CONCURRENCY` | `4` | Max concurrent source create/rename/delete/batch handlers. |
| `NOTEBOOKLM_SERVER_SOURCE_WAIT_CONCURRENCY` | `4` | Max concurrent source wait handlers. |
| `NOTEBOOKLM_SERVER_GENERATION_CONCURRENCY` | `2` | Max concurrent artifact generation/retry handlers. |
| `NOTEBOOKLM_SERVER_DOWNLOAD_CONCURRENCY` | `2` | Max concurrent artifact download handlers. |
| `NOTEBOOKLM_SERVER_RESEARCH_CONCURRENCY` | `2` | Max concurrent research start/cancel/import handlers. |
| `NOTEBOOKLM_SERVER_CHAT_CONCURRENCY` | `4` | Max concurrent blocking chat ask handlers. |
The concurrency knobs are route-group backpressure for expensive work. They do not gate `/healthz` or cheap read/list/poll routes.
**Surface:** every route is under `/v1` and requires `Authorization: Bearer <token>` plus a loopback `Host` header (a DNS-rebinding guard). `/healthz` is the one public, token-less route. The auto-generated `/docs` / `/openapi.json` schema UI is disabled (it would otherwise be reachable token-less).
<!-- not mirrored: REST-server curl examples (end-user/automation tooling); not part of the contributor install flow. -->
```bash
TOKEN=$NOTEBOOKLM_SERVER_TOKEN
BASE=http://127.0.0.1:8000
curl $BASE/healthz # {"ok": true} (no token)
curl -H "Authorization: Bearer $TOKEN" $BASE/v1/notebooks # list notebooks
curl -H "Authorization: Bearer $TOKEN" -d '{"title":"My NB"}' \
-H 'Content-Type: application/json' $BASE/v1/notebooks # create
curl -H "Authorization: Bearer $TOKEN" -d '{"url":"https://example.com"}' \
-H 'Content-Type: application/json' $BASE/v1/notebooks/<id>/sources/url
curl -H "Authorization: Bearer $TOKEN" -d '{"question":"Summarize"}' \
-H 'Content-Type: application/json' $BASE/v1/notebooks/<id>/chat # blocking answer
curl -H "Authorization: Bearer $TOKEN" $BASE/v1/notebooks/<id>/share # sharing status
```
Endpoints: `/v1/notebooks` (list/get/create/delete); `/v1/notebooks/{id}/sources` (list/get/add via `url`·`text`·`file`/delete); `/v1/notebooks/{id}/notes` (list/get/create/update via `PUT`/delete); `/v1/notebooks/{id}/chat` (blocking ask, no streaming); `/v1/notebooks/{id}/artifacts` (list / generate / poll / download); `/v1/notebooks/{id}/share` (status / public link / users / view level). Long-running work (source ingest, artifact generation) is **poll-the-resource**: the create call returns immediately and the matching `GET` reports `pending` until the resource is ready (`200`), `404` for an id the server never created, `409`/`410` for a failed/removed artifact.
**Artifacts & uploads:**
<!-- not mirrored: REST-server artifact/upload curl examples (end-user/automation tooling); not part of the contributor install flow. -->
```bash
# Generate (non-blocking → 202 + task_id). Omit source_ids to use ALL sources
# (like the CLI); pass them to scope. Some types (quiz/flashcards) need at least one source.
curl -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"type":"quiz"}' $BASE/v1/notebooks/<id>/artifacts # → {"task_id": ...}
curl -H "Authorization: Bearer $TOKEN" $BASE/v1/notebooks/<id>/artifacts/<task_id> # poll
curl -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"type":"audio"}' $BASE/v1/notebooks/<id>/artifacts/download -o out.mp3 # download
# File upload is multipart (the original filename + content-type are preserved):
curl -H "Authorization: Bearer $TOKEN" -F 'file=@./notes.pdf' \
$BASE/v1/notebooks/<id>/sources/file
```
**Error envelope:** every failure is `{"error": {"category": "...", "message": "..."}}` with a category-derived HTTP status — `not_found`→404, `validation`→400/422, `auth`→401/403, `rate_limited`→429, `notebook_limit`→409, server/network→502, timeouts→504. The category is classified once by `_app.errors.classify`, shared with the CLI.
---
## Post-install steps
### `playwright install chromium` — when required, when auto-installed
- **Required**: when you'll use `notebooklm login` (the interactive Playwright flow), unless the CLI auto-installs Chromium for you (it does — see `ensure_chromium_installed()` in `cli/services/playwright_login.py`, which runs `python -m playwright install chromium` on first login if Chromium is missing).
- **Not required**: for headless servers (Persona D), library use (Persona C), or `--browser-cookies`-based auth (Persona A/F with `[cookies]`).
### `playwright install-deps chromium` — Linux system libraries
On Debian/Ubuntu, Playwright needs system libs for Chromium. Run after `playwright install chromium`:
<!-- not mirrored: Linux-specific Playwright system-library install; CI runs `uv run playwright install-deps chromium` directly in test.yml -->
```bash
playwright install-deps chromium # scoped to chromium; matches CI
```
Works without `sudo` if you're root or have passwordless sudo. Otherwise `sudo playwright install-deps chromium`.
### First-time `notebooklm login`
<!-- not mirrored: end-user first-login walkthrough; contributors typically reuse an existing storage_state.json -->
```bash
notebooklm login # opens Chromium for Google sign-in
notebooklm auth check --test # verify
```
The login command:
- Auto-installs Chromium if missing (Persona A/B/E).
- Saves cookies to `~/.notebooklm/profiles/<profile>/storage_state.json`.
- Uses a *persistent* browser profile so subsequent logins are faster.
### `notebooklm skill install` — for AI agents (Persona A)
Registers the skill into local agent skill directories:
<!-- not mirrored: agent skill directory registration; out of scope for the contributor README -->
```bash
notebooklm skill install # writes ~/.claude/skills/notebooklm/, ~/.agents/skills/notebooklm/
```
Optional — only needed if your agent harness reads from those directories and the skill isn't already present.
### Running the MCP server (`mcp` extra)
The MCP server ships behind the optional `mcp` extra (see the extras matrix above) and exposes the same `_app/` business logic over the Model Context Protocol.
<!-- not mirrored: end-user MCP run/config pointer; out of scope for the contributor README -->
```bash
notebooklm-mcp # installed console script (stdio transport)
uvx --from "notebooklm-py[mcp]" notebooklm-mcp # no install — run straight from PyPI
```
Wire it into an MCP client with either:
- `notebooklm mcp install <client>` — auto-writes the server config for `claude-desktop`, `claude-code`, `cursor`, or `windsurf`; or
- the one-click `.mcpb` desktop bundle — download it from the [latest release](https://github.com/teng-lin/notebooklm-py/releases/latest) (**Assets**) and use Claude Desktop's "Install Extension". Each stable release attaches a prebuilt, version-matched bundle; see [`desktop-extension/README.md`](../desktop-extension/README.md).
Full usage walkthrough (auth, transports, the 35 tools, workflows, troubleshooting): **[mcp-guide.md](mcp-guide.md)**.
---
## Verifying your install
| Command | What it checks | Use when |
|---|---|---|
| `notebooklm --version` | Package installed correctly. | Always. |
| `notebooklm auth check --json` | Auth file parses; `SID` cookie present. Returns `{"status": "ok"\|"error", "checks": {...}}`. | Agents (machine-parseable). |
| `notebooklm auth check --test` | Same + network token-fetch validates that cookies still authenticate against Google. | End users (after login). |
| `notebooklm auth check --test --json` | Both. | Agents that need to confirm the cookies aren't stale. |
| `notebooklm list` | Package + auth + RPC roundtrip all work. | After login, as a smoke test. |
> **Important:** `notebooklm status` reports *context state* (which notebook is selected). It is **not** an auth check. See [Common gotchas](#common-gotchas-appendix).
**Your first end-to-end run:**
<!-- not mirrored: end-user smoke test; contributors run `uv run pytest` instead -->
```bash
notebooklm create "My First Notebook"
notebooklm source add 'https://en.wikipedia.org/wiki/Python_(programming_language)'
notebooklm ask "Summarize the sources in three sentences"
```
For the full CLI surface, see [cli-reference.md](cli-reference.md).
---
## Platform notes
| Platform | Install-time notes | Diagnostic detail |
|---|---|---|
| **macOS** | Chromium auto-downloads on first login. `--browser-cookies` from Chrome/Edge/Brave/Opera may prompt for Keychain access. | [troubleshooting.md#macos](troubleshooting.md#macos) |
| **Linux** | (a) `playwright install-deps chromium` for system libs (Debian/Ubuntu). (b) **Known bug:** `playwright > 1.57` may fail with `TypeError: onExit is not a function` — pin `playwright==1.57.0`. | [troubleshooting.md#linux](troubleshooting.md#linux) |
| **Windows** | The library auto-configures `WindowsSelectorEventLoopPolicy` and `PYTHONUTF8=1`. Prefer plain `pip install` (uv/pipx less common on Windows). | [troubleshooting.md#windows](troubleshooting.md#windows) |
| **WSL** | The browser opens in the Windows host (expected); `storage_state.json` lives in the WSL filesystem. | [troubleshooting.md#wsl](troubleshooting.md#wsl) |
---
## Upgrading and uninstalling
<!-- not mirrored: end-user upgrade commands; contributors `git pull && uv sync --frozen ...` instead -->
```bash
pip install --upgrade notebooklm-py # latest patch
pip install --upgrade "notebooklm-py[browser]" # preserves your extras
```
For pinning patterns and version-stability guarantees, see [stability.md](stability.md).
To uninstall:
<!-- not mirrored: end-user uninstall; contributors `git clean -fdx` and remove the worktree instead -->
```bash
pip uninstall notebooklm-py
rm -rf ~/.notebooklm # optional: remove auth state
```
---
## Common gotchas (appendix)
### All vs All-Extras
> ⚠️ **`pip install ".[all]"` and `uv sync --all-extras` are not equivalent.**
>
> - `pyproject.toml` defines: `all = ["notebooklm-py[browser,dev,markdown,mcp,server]"]` — a self-referential extras string that resolves to **browser + dev + markdown + mcp + server only**. It deliberately excludes `cookies` because `rookiepy` has install issues on Python 3.13+ ([CHANGELOG `[0.4.1]`](../CHANGELOG.md)).
> - `uv sync --all-extras` installs **every** extra including `cookies`, and may fail on Python 3.13/3.14.
> - In this repo, prefer `uv sync --frozen --extra browser --extra dev --extra markdown`.
### `uv pip install` vs `uv sync`
- `uv pip install -e ".[all]"` ignores the checked-in `uv.lock` — it re-resolves dependencies and may pull newer versions of `playwright`, `ruff`, etc. than the lock specifies.
- `uv sync --frozen` enforces the lockfile and fails fast on drift. **This is what contributors should use.**
- `uv sync` (no `--frozen`) silently updates `uv.lock` if `pyproject.toml` has changed. Use only when intentionally bumping deps.
### `notebooklm status` ≠ auth
`notebooklm status` reports the *currently selected notebook* (context). It does NOT report whether you are authenticated. For auth, use `notebooklm auth check` (or `--json` / `--test --json` for machine output and network validation).