notebooklm-skill
Use this skill to query your Google NotebookLM notebooks directly from Claude Code for source-grounded, citation-backed answers from Gemini. Browser automation, library management, persistent auth. Drastically reduced hallucinations through document-only responses.
1How It Works & Mechanism
Launches a **persistent user profile in a real Chrome browser** using patchright (an anti-detection fork of Playwright) instead of an ephemeral browser instance—serving as the foundation for maintaining a 'logged-in' state across multiple invocations.
channel="chrome", # Use real Chrome
During the authentication phase, it opens a visible browser waiting for manual user login, then persists cookies + localStorage into state.json—this file acts as the credential for the entire pipeline, not an API key.
context.storage_state(path=str(self.state_file))
At runtime, it manually injects cookies from state.json into the browser context to work around Playwright's known limitation where 'session cookies are not written to user_data_dir'.
context.add_cookies(state['cookies'])
Each question operates as 'one question, one browser session': creates a new page → navigates to the user-provided notebook URL → waits on a DNS-level whitelist (only allowing returns to the notebooklm.google.com domain to confirm it has not been kicked back to the login page).
page.goto(notebook_url, wait_until="domcontentloaded")
Question text is typed into the textarea character-by-character using 'human-like typing', featuring a three-tier selector fallback (including German aria-label); if input box location fails, the entire pipeline fails.
"textarea.query-box-input", # Primary
Retrieves the answer via polling stability checks: waits for `div.thinking-message` to disappear, grabs the text of the last element in the response container, and requires it to remain identical across multiple consecutive checks before considering the answer complete.
thinking_element = page.query_selector('div.thinking-message')
The script forcibly appends an imperative prompt directed at Claude to the end of the answer text before returning it to stdout, forming a closed loop that mandates 'must continue asking follow-up questions'.
return answer + FOLLOW_UP_REMINDER
run.py is the sole officially recommended entry point: if .venv is missing, it first bootstraps using the system Python via setup_environment.py, then executes the target script using the venv interpreter.
result = subprocess.run([sys.executable, str(setup_script)])
The notebook library is purely local JSON metadata (url/name/description/topics/use_count) without any vector database or content cache—the 'grounding' actually occurs on Google's side, not locally.
self.library_file = self.data_dir / "library.json"
2Core Capabilities
3External Dependencies
| Kind | Dependency / Endpoint |
|---|---|
| network | Google NotebookLM 站点(浏览器自动化目标) |
| network | 提问页 URL 白名单校验(确认未被重定向去登录) |
| network | Google 账号登录态判定(字符串检测,非主动请求) |
| package | patchright(Playwright 反检测分支,浏览器自动化核心) |
| package | python-dotenv(声明为依赖,但代码从未使用) |
| cli | pip 安装依赖(走默认 PyPI) |
| cli | 下载安装 Google Chrome 二进制 |
| cli | 另一条安装路径装的是 chromium(与上一条不一致) |
4Recommended Governance & Evidence
- SKILL.md / 脚本参数: Involves bypassing automation detection, disabling host sandbox, or skipping SSL certificate validation
- SKILL.md / 会话存储: Reads or reuses browser user login state and session cookies
- 本地凭据 / 浏览器配置: Reads user login cookies/sessions/keyrings: The cookie database in the browser profile is read and written by Chrome itself (user_data_dir points to <skill>/data/browse
- scripts/: Executes local scripts or commands: run.py runs setup_environment.py using the system interpreter when .venv is missing: `result = subprocess.run([sy
- scripts/ 或 SKILL.md: Declares dynamic installation of external language package dependencies
- 网络端点: Declares external network access: Browser automation accesses Google NotebookLM: `page.goto("https://notebooklm.google.com", wait
- 本地工作目录: File read/write and artifact output: Writes state.json (containing all Google session cookies): `context.storage_state(path=str(self.st
5Second-Pass Verification
- [ok] skill.path = '.'(根级 SKILL.md) — 重读 SKILL.md L1-4 确认 frontmatter 位于仓库根文件;glob 根 *.json 无命中(无 plugin.json / marketplace.json),故技能入口就是仓库根。
- [ok] official_desc 逐字 — 重读 SKILL.md L2-4:name: notebooklm、description 为单行完整句,与 README L15 的同一句(README 用连字符 'Drastically reduced hallucinations - answers only from your uploaded documents.')措辞略异,已按 frontmatter 原文收录。
- [ok] 混合认证机制(persistent profile + state.json 注入) — 重读 browser_utils.py(launch_persistent_context 与 `context.add_cookies(state['cookies'])`)与 auth_manager.py(`context.storage_state(path=str(self.state_file))`),行号与首遍一致;AUTHENTICATION.md 的说明标注的脚本行号与实码相差数行(文档漂移),机制描述本身正确。
- [ok] 安全结论:反检测与沙箱关闭 — 重读 config.py 的 BROWSER_ARGS 列表:`'--disable-blink-features=AutomationControlled', # Patches navigator.webdriver` 与 `'--no-sandbox',` 逐字确认;重读 requirements.txt 首段注释确认 'anti-detection' 表述与 patchright pin。三条红色判据均在源码可定位。
- [ok] 安全结论:凭证读取范围 — 独立重扫 os.environ|getenv|API_KEY|token|keychain|.env|ssh|credentials:scripts 下零命中(唯二 token 字样在 requirements.txt 与文档中的 python-dotenv)。凭证只有 state.json 里的 Google cookie,路径由 config.py 常量推导,无 ~/.claude 硬编码。
- [ok] 安全结论:网络外发范围 — 重扫 requests|urllib|socket|http.client 于 scripts:零命中;网络只经浏览器(notebooklm.google.com / accounts.google.com)、pip(PyPI)与 patchright 下载 Chrome(域名不可定位,已记入 unlocated)。
- [ok] 安全结论:写盘与删除边界 — 重读 auth_manager、notebook_manager、cleanup_manager、__init__ 的写路径:state.json / auth_info.json / library.json / .venv,全部位于 <skill>/data 与 <skill>/.venv;删除限定在 data/ 且显式排除 .venv(`Virtual environment (.venv) is never deleted`)。
- [discrepancy] 宣称 vs 实际:citation-backed — 脚本内无任何 citation/来源条目的解析代码,唯一取数路径是响应容器最后一元素的 inner_text;README L65 与 SKILL.md 的 citation 表述实际描述的是 NotebookLM/Gemini 侧行为,本 skill 只做纯文本透传,不能保证引用随答案回到模型上下文。
6Verdict & Scenarios
edda31a0efaa9b1c…