AGENTS.md ยท diff

git:20260109.2488a89 to git:20260313.167f4f4

21 added, 101 removed. Audit A to A.

- # AGENTS.md
-
- Guidelines for AI agents working on `notebooklm-py`.
-
- **IMPORTANT:** Follow documentation rules in [CONTRIBUTING.md](CONTRIBUTING.md) - especially the file creation and naming conventions.
-
- ## Quick Reference
-
- See [CLAUDE.md](CLAUDE.md) for full project context. Essential commands:
-
- ```bash
- source .venv/bin/activate # Always activate venv first
- pytest # Run tests
- pip install -e ".[all]" # Install in dev mode
- ```
-
- ## Code Style Guidelines
-
- ### Type Annotations (Python 3.10+)
- ```python
- def process(items: list[str]) -> dict[str, Any]: ...
- async def query(notebook_id: str, source_ids: Optional[list[str]] = None): ...
-
- # Use TYPE_CHECKING for circular imports
- from typing import TYPE_CHECKING
- if TYPE_CHECKING:
- from ..api_client import NotebookLMClient
- ```
-
- ### Async Patterns
- ```python
- # All client methods are async - use namespaced APIs
- async with await NotebookLMClient.from_storage() as client:
- notebooks = await client.notebooks.list()
- await client.sources.add_url(nb_id, url)
- result = await client.chat.ask(nb_id, question)
- ```
+ # Repository Guidelines
- ### Data Structures
- ```python
- @dataclass
- class Notebook:
- id: str
- title: str
- created_at: Optional[datetime] = None
+ **Status:** Active
+ **Last Updated:** 2026-03-13
- @classmethod
- def from_api_response(cls, data: list[Any]) -> "Notebook": ...
- ```
+ ## Project Structure & Module Organization
- ### Enums for Constants
- ```python
- class RPCMethod(str, Enum):
- LIST_NOTEBOOKS = "wXbhsf"
+ `src/notebooklm/` contains the async client and typed APIs. Internal feature modules use `_` prefixes such as `_sources.py` and `_artifacts.py`; `src/notebooklm/cli/` holds Click commands, and `src/notebooklm/rpc/` handles protocol encoding and decoding. Tests are split by scope: `tests/unit/`, `tests/integration/`, and `tests/e2e/`. Recorded HTTP fixtures live in `tests/cassettes/`. Examples are in `docs/examples/`, and diagnostics live in `scripts/`.
- class AudioFormat(int, Enum):
- DEEP_DIVE = 1
- ```
+ ## Build, Test, and Development Commands
- ### Error Handling
- ```python
- class RPCError(Exception):
- def __init__(self, message: str, rpc_id: Optional[str] = None, code: Optional[Any] = None):
- self.rpc_id, self.code = rpc_id, code
- super().__init__(message)
+ Use `uv` for local work:
- raise RPCError(f"No result found for RPC ID: {rpc_id}", rpc_id=rpc_id)
- raise ValueError(f"Invalid YouTube URL: {url}") # For validation
+ ```bash
+ uv sync --extra dev --extra browser
+ uv run pytest
+ uv run ruff check src/ tests/
+ uv run ruff format src/ tests/
+ uv run mypy src/notebooklm
+ uv run pre-commit run --all-files
```
- ### Docstrings
- ```python
- def decode_response(raw_response: str, rpc_id: str, allow_null: bool = False) -> Any:
- """Complete decode pipeline: strip prefix -> parse chunks -> extract result.
-
- Args:
- raw_response: Raw response text from batchexecute
- rpc_id: RPC method ID to extract result for
- allow_null: If True, return None instead of raising when null
-
- Returns:
- Decoded result data
-
- Raises:
- RPCError: If RPC returned an error or result not found
- """
- ```
+ Run `uv run pytest tests/e2e -m readonly` only after `notebooklm login` and setting test notebook env vars.
- ## Testing Patterns
+ ## Coding Style & Naming Conventions
- ```python
- # Class-based for related tests
- class TestDecodeResponse:
- def test_full_decode_pipeline(self): ...
+ Target Python 3.10+, 4-space indentation, and double quotes. Ruff enforces formatting and import order with a 100-character line length. Keep module and test file names in `snake_case`; prefer descriptive Click command names that match existing groups such as `source`, `artifact`, and `research`. Preserve the internal/public split: `_*.py` for implementation, exported types in `src/notebooklm/__init__.py`.
- # Markers
- @pytest.mark.e2e # End-to-end (requires auth)
- @pytest.mark.slow # Long-running (audio/video)
- @pytest.mark.asyncio # Async tests
+ ## Testing Guidelines
- # Async tests
- @pytest.mark.asyncio
- async def test_list_notebooks(self, client):
- notebooks = await client.notebooks.list()
- assert isinstance(notebooks, list)
- ```
+ Put pure logic in `tests/unit/`, VCR-backed flows in `tests/integration/`, and authenticated NotebookLM coverage in `tests/e2e/`. Name tests `test_<behavior>.py` and record cassettes with `NOTEBOOKLM_VCR_RECORD=1 uv run pytest tests/integration/test_vcr_*.py -v`. Coverage is expected to stay at or above the configured 90% threshold.
- ## Do NOT
+ ## Commit, PR, and Agent Notes
- - Suppress type errors with `# type: ignore`
- - Commit `.env` files or credentials
- - Add dependencies without updating `pyproject.toml`
- - Change RPC method IDs without verifying via network capture
- - Delete or modify e2e tests without running them
- - Create documentation files without following CONTRIBUTING.md rules
+ Follow the existing commit style: `feat(cli): ...`, `fix(cli): ...`, `refactor(test): ...`, `style: ...`. PRs should include a short summary, linked issue when relevant, and the commands run locally. For Codex or other parallel agents, prefer `--json`, pass explicit notebook IDs instead of relying on `notebooklm use`, and isolate runs with `NOTEBOOKLM_HOME=/tmp/<agent-id>` when multiple agents share one machine.