Skip to content

Commit 18eaf6d

Browse files
committed
feat(cli): add agent show commands for Codex and Claude
1 parent 586cb0a commit 18eaf6d

File tree

9 files changed

+184
-7
lines changed

9 files changed

+184
-7
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@ Other useful CLI commands:
159159

160160
```bash
161161
notebooklm auth check --test # Diagnose auth/cookie issues
162+
notebooklm agent show codex # Print bundled Codex instructions
163+
notebooklm agent show claude # Print bundled Claude Code skill template
162164
notebooklm language list # List supported output languages
163165
notebooklm metadata --json # Export notebook metadata and sources
164166
notebooklm share status # Inspect sharing state
@@ -206,6 +208,7 @@ asyncio.run(main())
206208
```bash
207209
# Install via CLI or ask Claude Code to do it
208210
notebooklm skill install
211+
notebooklm agent show claude
209212

210213
# Then use natural language:
211214
# "Create a podcast about quantum computing"
@@ -219,6 +222,7 @@ Codex reads repo-level instructions from [`AGENTS.md`](AGENTS.md), so there is n
219222

220223
```bash
221224
uv sync --extra dev --extra browser
225+
notebooklm agent show codex
222226
notebooklm login
223227
notebooklm list --json
224228
```

docs/cli-reference.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ See [Configuration](configuration.md) for details on environment variables and C
2727
- **Session commands** - Authentication and context management
2828
- **Notebook commands** - CRUD operations on notebooks
2929
- **Chat commands** - Querying and conversation management
30-
- **Grouped commands** - `source`, `artifact`, `generate`, `download`, `note`, `share`, `research`, `language`, `skill`, `auth`
30+
- **Grouped commands** - `source`, `artifact`, `agent`, `generate`, `download`, `note`, `share`, `research`, `language`, `skill`, `auth`
3131
- **Utility commands** - `metadata`
3232

3333
---
@@ -208,6 +208,17 @@ After installation, Claude Code recognizes NotebookLM commands via `/notebooklm`
208208

209209
Codex does not use the `skill` subcommand. In this repository it reads the root [`AGENTS.md`](../AGENTS.md) file and invokes the `notebooklm` CLI or Python API directly.
210210

211+
### Agent Commands (`notebooklm agent <cmd>`)
212+
213+
Show bundled instructions for supported agent environments.
214+
215+
| Command | Description | Example |
216+
|---------|-------------|---------|
217+
| `show codex` | Print the Codex repository guidance | `agent show codex` |
218+
| `show claude` | Print the bundled Claude Code skill template | `agent show claude` |
219+
220+
`agent show codex` prefers the root [`AGENTS.md`](../AGENTS.md) file when running from a source checkout, so the CLI mirrors the same instructions Codex sees in the repository.
221+
211222
### Features Beyond the Web UI
212223

213224
These CLI capabilities are not available in NotebookLM's web interface:

src/notebooklm/cli/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
Command groups are organized into separate modules:
66
- source.py: Source management commands (includes add-research)
77
- artifact.py: Artifact management commands
8+
- agent.py: Agent integration helpers
89
- generate.py: Content generation commands
910
- download.py: Download commands
1011
- note.py: Note management commands
@@ -16,6 +17,7 @@
1617
"""
1718

1819
# Command groups (subcommand style)
20+
from .agent import agent
1921
from .artifact import artifact
2022
from .chat import register_chat_commands
2123
from .download import download
@@ -80,6 +82,7 @@
8082
# Command groups (subcommand style)
8183
"source",
8284
"artifact",
85+
"agent",
8386
"generate",
8487
"download",
8588
"note",

src/notebooklm/cli/agent.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""Agent integration commands."""
2+
3+
import click
4+
5+
from .agent_templates import get_agent_source_content
6+
from .helpers import console
7+
8+
9+
@click.group()
10+
def agent():
11+
"""Show bundled instructions for supported agent environments."""
12+
pass
13+
14+
15+
@agent.command("show")
16+
@click.argument("target", type=click.Choice(["codex", "claude"], case_sensitive=False))
17+
def show_agent(target: str):
18+
"""Display instructions for Codex or Claude Code."""
19+
content = get_agent_source_content(target)
20+
if content is None:
21+
console.print(f"[red]Error:[/red] {target} instructions not found in package data.")
22+
raise SystemExit(1)
23+
24+
console.print(content)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"""Shared agent instruction loading helpers."""
2+
3+
from importlib import resources
4+
from pathlib import Path
5+
6+
AGENT_TEMPLATE_FILES = {
7+
"claude": "SKILL.md",
8+
"codex": "CODEX.md",
9+
}
10+
11+
REPO_ROOT_AGENTS = Path(__file__).resolve().parents[3] / "AGENTS.md"
12+
13+
14+
def _read_package_data(filename: str) -> str | None:
15+
"""Read a packaged agent template file."""
16+
try:
17+
return (resources.files("notebooklm") / "data" / filename).read_text(encoding="utf-8")
18+
except (FileNotFoundError, TypeError):
19+
return None
20+
21+
22+
def get_agent_source_content(target: str) -> str | None:
23+
"""Return bundled instructions for a supported agent target."""
24+
normalized = target.lower()
25+
26+
# Prefer the repo-level Codex guide when running from a source checkout so
27+
# the CLI mirrors the instructions Codex actually sees in this repository.
28+
if normalized == "codex" and REPO_ROOT_AGENTS.exists():
29+
return REPO_ROOT_AGENTS.read_text(encoding="utf-8")
30+
31+
filename = AGENT_TEMPLATE_FILES.get(normalized)
32+
if filename is None:
33+
return None
34+
35+
return _read_package_data(filename)

src/notebooklm/cli/skill.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55

66
import contextlib
77
import re
8-
from importlib import resources
98
from pathlib import Path
109

1110
import click
1211

12+
from .agent_templates import get_agent_source_content
1313
from .helpers import console
1414

1515
# Skill paths
@@ -19,11 +19,7 @@
1919

2020
def get_skill_source_content() -> str | None:
2121
"""Read the skill source file from package data."""
22-
try:
23-
# Python 3.9+ way to read package data (use / operator for path traversal)
24-
return (resources.files("notebooklm") / "data" / "SKILL.md").read_text(encoding="utf-8")
25-
except (FileNotFoundError, TypeError):
26-
return None
22+
return get_agent_source_content("claude")
2723

2824

2925
def get_package_version() -> str:

src/notebooklm/data/CODEX.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Repository Guidelines
2+
3+
**Status:** Active
4+
**Last Updated:** 2026-03-13
5+
6+
## Project Structure & Module Organization
7+
8+
`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/`.
9+
10+
## Build, Test, and Development Commands
11+
12+
Use `uv` for local work:
13+
14+
```bash
15+
uv sync --extra dev --extra browser
16+
uv run pytest
17+
uv run ruff check src/ tests/
18+
uv run ruff format src/ tests/
19+
uv run mypy src/notebooklm
20+
uv run pre-commit run --all-files
21+
```
22+
23+
Run `uv run pytest tests/e2e -m readonly` only after `notebooklm login` and setting test notebook env vars.
24+
25+
## Coding Style & Naming Conventions
26+
27+
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`.
28+
29+
## Testing Guidelines
30+
31+
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.
32+
33+
## Commit, PR, and Agent Notes
34+
35+
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.

src/notebooklm/notebooklm_cli.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161

6262
# Import command groups from cli package
6363
from .cli import (
64+
agent,
6465
artifact,
6566
download,
6667
generate,
@@ -135,6 +136,7 @@ def cli(ctx, storage, verbose):
135136
# Register command groups (subcommand style)
136137
cli.add_command(source)
137138
cli.add_command(artifact)
139+
cli.add_command(agent)
138140
cli.add_command(generate)
139141
cli.add_command(download)
140142
cli.add_command(note)

tests/unit/cli/test_agent.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"""Tests for agent CLI commands."""
2+
3+
from unittest.mock import patch
4+
5+
import pytest
6+
from click.testing import CliRunner
7+
8+
from notebooklm.notebooklm_cli import cli
9+
10+
from .conftest import get_cli_module
11+
12+
agent_module = get_cli_module("agent")
13+
agent_templates_module = get_cli_module("agent_templates")
14+
15+
16+
@pytest.fixture
17+
def runner():
18+
return CliRunner()
19+
20+
21+
class TestAgentShow:
22+
"""Tests for agent show command."""
23+
24+
def test_agent_show_codex_displays_content(self, runner):
25+
"""Test that agent show codex displays the bundled instructions."""
26+
with patch.object(
27+
agent_module, "get_agent_source_content", return_value="# Repository Guidelines"
28+
):
29+
result = runner.invoke(cli, ["agent", "show", "codex"])
30+
31+
assert result.exit_code == 0
32+
assert "Repository Guidelines" in result.output
33+
34+
def test_agent_show_claude_displays_content(self, runner):
35+
"""Test that agent show claude displays the bundled instructions."""
36+
with patch.object(agent_module, "get_agent_source_content", return_value="# Claude Skill"):
37+
result = runner.invoke(cli, ["agent", "show", "claude"])
38+
39+
assert result.exit_code == 0
40+
assert "Claude Skill" in result.output
41+
42+
def test_agent_show_missing_content_returns_error(self, runner):
43+
"""Test error when bundled agent instructions are missing."""
44+
with patch.object(agent_module, "get_agent_source_content", return_value=None):
45+
result = runner.invoke(cli, ["agent", "show", "codex"])
46+
47+
assert result.exit_code == 1
48+
assert "not found" in result.output.lower()
49+
50+
51+
class TestAgentTemplates:
52+
"""Tests for bundled agent template loading."""
53+
54+
def test_codex_template_falls_back_to_package_data(self, tmp_path):
55+
"""Test that codex content falls back to packaged data outside repo root."""
56+
with patch.object(agent_templates_module, "REPO_ROOT_AGENTS", tmp_path / "AGENTS.md"):
57+
content = agent_templates_module.get_agent_source_content("codex")
58+
59+
assert content is not None
60+
assert "Repository Guidelines" in content
61+
62+
def test_claude_template_reads_package_data(self):
63+
"""Test that claude content reads from packaged skill data."""
64+
content = agent_templates_module.get_agent_source_content("claude")
65+
66+
assert content is not None
67+
assert "NotebookLM Automation" in content

0 commit comments

Comments
 (0)