5.7 KiB
CLAUDE.md
Guidance for Claude Code working in this repo. Also follow the file/naming conventions in CONTRIBUTING.md.
Project Overview
notebooklm-py is an unofficial async Python client for Google NotebookLM. It drives Google's internal batchexecute RPC protocol to automate notebooks, sources, AI querying, and studio artifacts (podcasts, videos, quizzes, …).
Critical constraint: the obfuscated RPC method IDs in src/notebooklm/rpc/types.py are undocumented and can break whenever Google changes them — the #1 breakage class.
Development Commands
# Canonical contributor install (respects uv.lock; full guide: docs/installation.md)
uv sync --frozen --extra browser --extra dev --extra markdown
source .venv/bin/activate
uv run playwright install chromium
uv run pytest # all tests (e2e excluded by default)
uv run pytest --cov # with coverage
uv run pytest tests/e2e -m e2e # e2e (requires auth)
uv run notebooklm --help # CLI
Before Pushing
The pre-commit hook runs ruff (format + lint) on staged files. Also run these manually — CI fails otherwise:
uv run mypy src/notebooklm --ignore-missing-imports
uv run pytest
Architecture
cli/ (Click) → _app/ (transport-neutral business logic, reusable by MCP/HTTP adapters) → client.py + _*.py (client runtime) → rpc/ (batchexecute encode/decode).
See docs/architecture.md for the layered design, call flows, cross-cutting policies (loop affinity, idempotency, schema validation), the per-file index, and the full repository tree.
Common Pitfalls
- RPC method IDs change — re-capture network traffic and update
rpc/types.py. - Position-sensitive nested params — copy the shape from an existing implementation; source-id nesting varies (
[id]/[[id]]/[[[id]]]/[[[[id]]]]). - CSRF tokens expire — call
client.refresh_auth()or re-runnotebooklm login. - Rate limiting — add delays between bulk operations.
- Concurrency — one
NotebookLMClientis bound to itsopen()-time event loop: create one per thread, never reuse across event loops orAuthTokenstenants. See the concurrency contract.
Usage
async with NotebookLMClient.from_storage() as client:
notebooks = await client.notebooks.list()
await client.sources.add_url(nb_id, url)
answer = await client.chat.ask(nb_id, question)
status = await client.artifacts.generate_audio(nb_id)
CLI: top-level commands (login, use, status, list, ask) plus grouped subcommands (source add, label list, artifact list, generate audio, download video, note create, mcp install <client>, …). Full reference: docs/cli-reference.md.
An opt-in MCP server (mcp extra, console script notebooklm-mcp) exposes the same _app/ business logic over the Model Context Protocol; notebooklm mcp install <client> wires it into Claude Desktop/Code, Cursor, or Windsurf, and desktop-extension/ packages a one-click .mcpb bundle.
An opt-in single-tenant REST server (server extra, console script notebooklm-server) exposes guarded /v1 FastAPI routes over the same _app/ layer. It is experimental, loopback-bound by default, and requires NOTEBOOKLM_SERVER_TOKEN; see docs/installation.md#rest-api-server.
Testing
Unit (tests/unit/, no network; includes _app, CLI, server, and guardrail tests) · integration (tests/integration/, VCR cassette replay) · e2e (tests/e2e/, real API, @pytest.mark.e2e). VCR cassettes match on rpcids + decoded body shape. Details: docs/development.md.
Docs
docs/: installation · cli-reference · python-api · configuration · troubleshooting · development · architecture · mcp-guide · rpc-development · rpc-reference · stability · adr/.
Pull Request Workflow (required)
After opening a PR, drive it to merge:
- Poll
gh pr checks <PR>until all pass; investigate and fix any failures. - Address every review comment (especially
gemini-code-assist): make the fix, push, then reply on the thread (Addressed in <SHA>: …). Unreplied threads block merge. - Not done until all checks pass, all threads addressed, and
mergeStateStatusisCLEAN.
Claude review is not automatic — comment @claude review on the PR to trigger the .github/workflows/claude.yml workflow. Treat claude[bot] as a first-class reviewer the merge gate waits on, alongside gemini-code-assist / coderabbitai:
- It posts inline review-thread comments plus a sticky summary comment ("Claude finished … task"). The action does not submit a formal GitHub review, so
claude[bot]never appears ingh pr view --json reviews/reviewDecisionand is not a required check — do not infer "claude reviewed" from those. gh pr checksmay show aclaudeentry as skipping: every comment (incl. other bots') firesclaude.yml, and runs not from ateng-lin@claudecomment correctly skip via the jobif:gate. That skip is not the review run — find the real one withgh run list --workflow=claude.yml --json event,conclusion(look for thesuccessrun) or just read the comment below.- Before merging, confirm the review landed and address it. The two halves live on different endpoints: the sticky summary is an issue comment (
gh api /repos/<owner>/<repo>/issues/<PR>/comments), and the inline findings are pull-request review comments on the diff (gh api /repos/<owner>/<repo>/pulls/<PR>/comments) — filter either with--jq '.[]|select(.user.login=="claude[bot]").body'. Resolve any inlineclaude[bot]threads like any other bot's.