| ci(test): parallelize matrix pytest with -n auto --dist loadgroup (#1659) * ci(test): parallelize matrix pytest with -n auto --dist loadgroup The 15-leg test matrix ran the ~11,155-test suite single-threaded; the pytest step dominates each leg (ubuntu/macos ~7-10 min, windows 11-17 min). pytest-xdist is already a dev dep and the suite is already parallel-safe (per-test NOTEBOOKLM_HOME tmp isolation + the #1482 pythonpath fix), so parallelize it. - -n auto: one worker per runner core. - --dist loadgroup: honors @pytest.mark.xdist_group (the documented per-test isolation escape hatch in tests/integration/concurrency/); unmarked tests distribute like load. Chosen over loadscope, which would silently ignore the documented xdist_group marker. - Coverage unaffected: pytest-cov combines per-worker data; verified the 90% gate + per-file-floor coverage.json verdict are IDENTICAL single vs parallel. Local 4-core: pytest 303s -> 95s (3.2x), identical 96% coverage, stable across 5x repeated runs (zero intermittency). Only the matrix coverage run changes; publish/testpypi/verify-package smoke + nightly e2e stay serial (e2e must never get -n: live-API rate-limit/ordering hazards). Per-OS speedup TBD by CI (Windows spawn overhead may differ); if Windows regresses, make -n OS-conditional rather than reverting. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YG1NZU5zNDAX2gWGNubjwi * ci(test): trim the xdist comment per review (drop benchmark numbers) Address claude[bot] review nit: the inline comment duplicated PR/commit benchmark detail. Keep the invariants (what the flags do, why parallel-safe, coverage handling), drop the historical numbers. No behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YG1NZU5zNDAX2gWGNubjwi --------- Co-authored-by: Claude <noreply@anthropic.com> | 3 天前 |
| fix(deploy): stop Makefile from shadowing NOTEBOOKLM_PROFILE_DIR in .env (#1651) `make dev` ignored a NOTEBOOKLM_PROFILE_DIR set in deploy/.env and always mounted the "default" profile. Root cause: make does not read .env, so the Makefile's `export NOTEBOOKLM_PROFILE_DIR ?= $(HOME)/.notebooklm/profiles/default` resolved to the default and EXPORTED it into the docker-compose child env — where shell-env outranks the .env file, silently overriding the user's value. The documented "set it in .env" path therefore only worked with raw `docker compose`, never via make. Fix: don't set/export NOTEBOOKLM_PROFILE_DIR in the Makefile at all. Compose already reads it from .env and defaults to ~/.notebooklm/profiles/default via its own `:-` fallback, and make still auto-exports a shell/CLI override (`NOTEBOOKLM_PROFILE_DIR=… make dev` or `make dev NOTEBOOKLM_PROFILE_DIR=…`). UID/GID are computed defaults the user doesn't set, so they stay exported. Verified via `make config`: .env value honored, compose default applies when unset, and the CLI/shell override still propagates. Claude-Session: https://claude.ai/code/session_01YG1NZU5zNDAX2gWGNubjwi Co-authored-by: Claude <noreply@anthropic.com> | 3 天前 |
| feat(mcp): unify notes + artifacts into a Studio surface (35→32 tools) (#1734) * mcp(studio 1/3): rename artifact_* tools → studio_* Pure MCP-adapter rename (tool names only): artifact_list/generate/status/get_prompt/ download/rename/retry/delete → studio_*. Client method client.artifacts.*, the artifact_type/artifact_id params, and ArtifactType are UNCHANGED. Updated manifest, error hints, unit/vcr/e2e tests, docs, desktop-extension. Count unchanged (35); UI-aligned naming (NotebookLM's Studio panel). Part of the notes+artifacts→Studio unification (target 32). Plan: .sisyphus/plans/mcp-studio.md. * mcp(studio 2/3): fold note_create + note_update → note_save (upsert) [35→34] note_save(notebook, note?, title?, content?): omit note → create (title+content required); pass note → update (title and/or content; unresolved ref → NoteNotFoundError, never a stray create). Precedent: share_set_user upsert. Deletes note_create + note_update; unit + VCR tests renamed to note_save; manifest Notes (4)→(3), count 34. MCP-adapter-only (CLI note commands untouched). Plan: .sisyphus/plans/mcp-studio.md. * mcp(studio 3/3): unified studio_list + cross-type studio_delete [34→32] Fold note_list + note_delete into the Studio surface (32 tools): - studio_list(notebook, item?, kind?, limit?, offset?): merges notes+artifacts into one 'items' list with a shared hyphenated 'type' (note|audio|…|slide-deck); item → single-fetch, kind → filter (validated), else paginate. - studio_delete(notebook, item, confirm): cross-type — resolves over the merged list, routes note→DELETE_NOTE, artifact→DELETE_ARTIFACT (clears note-backed mind maps). Absent full-UUID stays idempotent (routes to artifact delete); non-UUID miss → NOT_FOUND. A full-UUID is id-only (never title-matched → no wrong-note delete). - New tools/_studio.py (studio_items merge/projection + resolve_studio_item + hyphenated_type + STUDIO_KINDS); extracted to stay under the ADR-0008 module cap. - ADR-0026 documents the surface; docs + e2e + VCR (reuse mind_maps_interactive.yaml) updated. MCP-adapter-only. Known gaps: #1732/#1733 (VCR for sharing + studio deletes). Plan: .sisyphus/plans/mcp-studio.md. * test(mcp): update CLI↔MCP parity to studio_* tool names The full-suite CLI↔MCP parity test drives the MCP tools by name (artifact_generate/ artifact_download); repoint to studio_* after the rename. Outside the mcp-scoped verify subset, so it went green locally but red on CI's full Test job. * mcp(studio): address bot review — kind filter, wire shape, docstrings - STUDIO_KINDS excludes 'unknown' (a display-only pass-through, not a filterable kind) — studio_list(kind='unknown') now VALIDATION-errors (claude[bot] Medium). - studio_delete: hoist item.strip() once (gemini Medium); note path returns was_note_backed:False for a stable wire shape (claude[bot]/codex Minor). - chat suggest_prompts docstring: studio_generate's arg is 'instructions' not 'prompt' (coderabbit Minor); studio_list docstring documents item+kind scoping (claude[bot] Minor). Tests updated. --------- Co-authored-by: Claude <noreply@anthropic.com> | 12 小时前 |
| feat(mcp): surface signed-in account email in server_info (#1735) * feat(mcp): surface signed-in account email in server_info GET_USER_SETTINGS carries no identity, so add a client-layer source and surface it. - NotebookLMClient.get_account_email(live_fallback=True) / get_account_authuser(): resolve from the in-memory AuthTokens → persisted profile metadata (network-free) → a single WIZ_global_data page probe on the open session (persisted back, memoized). Never raises for network/on-disk faults (probe httpx.HTTPError and self-heal OSError/RuntimeError swallowed; blocking write off-loop via asyncio.to_thread). - server_info(include_account=True) account block now includes {email, authuser} on every path (authed / unauthed / degraded); live probe only when authenticated. - OneGoogle MhtACe was probed and returns empty for a headless session; storage/ in-memory + WIZ probe is the reliable source. - Docs (python-api, mcp-guide); email is opt-in-gated PII. Additive public API. Plan: .sisyphus/plans/mcp-account-email.md (3-model momus converged); polish gate (claude review) applied — redirect test now exercises the real is_google_auth_redirect branch. * fix(client): init _account_email_cache in _assemble_client (factory-shell parity) test_client_factory_parity requires every __init__-set attribute to also exist on a factory-built shell; move the memo assignment into _assemble_client (incidents #1196/#1225), leaving a class-level type annotation on the client. Guardrail lives in tests/_guardrails (outside the tests/unit+integration scope I ran locally). * docs(mcp): clarify live_fallback=authenticated intent in _account_block Inline comment per claude[bot] review nit — no behavior change. --------- Co-authored-by: Claude <noreply@anthropic.com> | 4 小时前 |
| chore: move example scripts from docs/examples/ to examples/ (#1573) Move the runnable example scripts (bulk-import, chat, notes, quickstart, research-to-podcast, video) from docs/examples/ to the repo-root examples/, and update all references (idiom guard, AGENTS.md, CONTRIBUTING.md, docs/releasing.md). Pure git mv -- no script changes. | 18 天前 |
| feat(mcp): suggest_prompts tool — studio surface selector + mode cap 9→10 (#1726) (#1730) * feat(mcp): suggest_prompts tool — studio surface selector + mode cap 9→10 (#1726) Dedicated READ_ONLY MCP tool returning AI-suggested generation prompts for a chosen studio surface (audio/video/quiz/flashcards/chat), backed by client.notebooks.suggest_prompts(mode=…). surface->mode verified live in #1726 (browser-verified audio format cards; real web captures for video; client-probes for quiz/flashcards/chat). Raises the mode cap 1..9 -> 1..10 across client/CLI/tests/docs (video-short=10). Ceiling unchanged (40); count 36->37. Corrects the stale #1612 mode comment + adds docs/notes/otmp3b-suggest-modes.md evidence record. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ZUzFEAC3XBxwjYQ435QFQ * polish: key _SUGGEST_SURFACE by the SuggestSurface Literal (mypy-enforced sync) * polish: query accepts null (str|None); CLI mode-11 boundary test; tighten comments Addresses triple-review (claude/codex/agy): make suggest_prompts query MCP-nullable (str|None, consistent with chat_ask optionals); pin the CLI mirror cap with an 11 boundary reject; correct the _SUGGEST_SURFACE mypy-drift comment + de-run-on the manifest ceiling note. * fix(mcp): normalize suggest_prompts query in the payload builder, not per-caller (#1730) Pass query through verbatim; build_prompt_suggestions_params is the single normalization point (None/""/whitespace -> null steer). Addresses gemini review. --------- Co-authored-by: Claude <noreply@anthropic.com> | 1 天前 |
| feat(mcp): surface signed-in account email in server_info (#1735) * feat(mcp): surface signed-in account email in server_info GET_USER_SETTINGS carries no identity, so add a client-layer source and surface it. - NotebookLMClient.get_account_email(live_fallback=True) / get_account_authuser(): resolve from the in-memory AuthTokens → persisted profile metadata (network-free) → a single WIZ_global_data page probe on the open session (persisted back, memoized). Never raises for network/on-disk faults (probe httpx.HTTPError and self-heal OSError/RuntimeError swallowed; blocking write off-loop via asyncio.to_thread). - server_info(include_account=True) account block now includes {email, authuser} on every path (authed / unauthed / degraded); live probe only when authenticated. - OneGoogle MhtACe was probed and returns empty for a headless session; storage/ in-memory + WIZ probe is the reliable source. - Docs (python-api, mcp-guide); email is opt-in-gated PII. Additive public API. Plan: .sisyphus/plans/mcp-account-email.md (3-model momus converged); polish gate (claude review) applied — redirect test now exercises the real is_google_auth_redirect branch. * fix(client): init _account_email_cache in _assemble_client (factory-shell parity) test_client_factory_parity requires every __init__-set attribute to also exist on a factory-built shell; move the memo assignment into _assemble_client (incidents #1196/#1225), leaving a class-level type annotation on the client. Guardrail lives in tests/_guardrails (outside the tests/unit+integration scope I ran locally). * docs(mcp): clarify live_fallback=authenticated intent in _account_block Inline comment per claude[bot] review nit — no behavior change. --------- Co-authored-by: Claude <noreply@anthropic.com> | 4 小时前 |
| feat(mcp): surface signed-in account email in server_info (#1735) * feat(mcp): surface signed-in account email in server_info GET_USER_SETTINGS carries no identity, so add a client-layer source and surface it. - NotebookLMClient.get_account_email(live_fallback=True) / get_account_authuser(): resolve from the in-memory AuthTokens → persisted profile metadata (network-free) → a single WIZ_global_data page probe on the open session (persisted back, memoized). Never raises for network/on-disk faults (probe httpx.HTTPError and self-heal OSError/RuntimeError swallowed; blocking write off-loop via asyncio.to_thread). - server_info(include_account=True) account block now includes {email, authuser} on every path (authed / unauthed / degraded); live probe only when authenticated. - OneGoogle MhtACe was probed and returns empty for a headless session; storage/ in-memory + WIZ probe is the reliable source. - Docs (python-api, mcp-guide); email is opt-in-gated PII. Additive public API. Plan: .sisyphus/plans/mcp-account-email.md (3-model momus converged); polish gate (claude review) applied — redirect test now exercises the real is_google_auth_redirect branch. * fix(client): init _account_email_cache in _assemble_client (factory-shell parity) test_client_factory_parity requires every __init__-set attribute to also exist on a factory-built shell; move the memo assignment into _assemble_client (incidents #1196/#1225), leaving a class-level type annotation on the client. Guardrail lives in tests/_guardrails (outside the tests/unit+integration scope I ran locally). * docs(mcp): clarify live_fallback=authenticated intent in _account_block Inline comment per claude[bot] review nit — no behavior change. --------- Co-authored-by: Claude <noreply@anthropic.com> | 4 小时前 |
| feat(mcp): remote HTTP transport — bearer auth + Docker/Cloudflare deploy (Phase A) (#1645) * feat(mcp): remote HTTP transport — bearer auth + Docker/Cloudflare deploy (Phase A) Make `notebooklm-mcp` deployable as a REMOTE MCP connector (Claude Code / Claude.ai / Cursor), single-tenant, behind a bearer token. The durable master-token headless auth (#1638) keeps the upstream Google session alive unattended, which is what makes a long-running remote server viable; the HTTP transport + bind guard already existed, so this adds the client→server auth layer and a turn-key deploy. - `mcp/_auth.py`: `McpBearerAuthProvider(TokenVerifier)` — constant-time byte-compare of `NOTEBOOKLM_MCP_TOKEN` (env-only; no `--token` flag → no `ps aux` leak); name-mangled token + redacted `__repr__`; the validated `AccessToken` stamps an opaque client id, never the live token (it is stored on the request scope). Mirrors `server/_auth.py`; deliberately NOT fastmcp's `StaticTokenVerifier` (plaintext-dict warning + authlib pull + client_id trap). - `mcp/__main__.py`: fail-closed — a non-loopback http bind without a token refuses to start (checked BEFORE building the server, keyed off the effective bind, not the external-bind flag). Token resolved once and passed in. - `mcp/server.py`: `create_server(auth=...)` is env-free — auth attaches only when the caller passes it, so stdio and the unit suite never gate. - `deploy/`: Dockerfile (non-root, stdlib TCP healthcheck) + docker-compose with a `cloudflared` sidecar + a READ-WRITE profile mount (a ro mount kills cookie rotation/recovery ~1h in) + `.env.example` + `.gitignore` + README. - Docs: mcp-guide "Remote deployment" + architecture.md tree. Pin `fastmcp>=3.0` (auth API is 3.0+). Out of scope (single-tenant): OAuth/DCR, multi-tenant, file brokers — files are delegated to Drive / the NotebookLM app / local stdio. Verified: custom provider gates the real FastMCP HTTP middleware end-to-end (no/wrong token → 401, correct → MCP session); a live run against a real profile listed notebooks through the remote transport. mypy + ruff clean; new auth/bind test matrix; 1248 unit/guardrail tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YG1NZU5zNDAX2gWGNubjwi * test(mcp): cover the bogus-transport env-default guard Addresses a polish-review gap: the env-derived NOTEBOOKLM_MCP_TRANSPORT fail-loud path had no test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YG1NZU5zNDAX2gWGNubjwi * fix(mcp): address bot review + change default port to 9420 - _auth.py: store only a SHA-256 digest of the token (never cleartext — off vars()/scope dumps); verify by latin-1-recovering the request bytes and comparing digests, fixing the latin-1/utf-8 encoding mismatch that failed (or 500'd) non-ASCII bearers (gemini/claude/coderabbit). - __main__.py: normalize (strip) the host once, use it for both guards and the bind so " 127.0.0.1 " can't pass the guards then fail at bind (coderabbit). - docker-compose: pin cloudflared via CLOUDFLARED_VERSION (default latest) and gate the sidecar on `condition: service_healthy` (claude). - Default HTTP port 8000 → 9420 (CLI default, Dockerfile, compose, docs). - Tests: utf-8-token-through-latin1-header round-trip; default-port=9420. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YG1NZU5zNDAX2gWGNubjwi * fix(deploy): healthcheck reads NOTEBOOKLM_MCP_PORT at runtime Exec-form HEALTHCHECK programs still see their environment, so the python TCP check now reads NOTEBOOKLM_MCP_PORT (default 9420) instead of hardcoding it — an overridden port no longer marks a healthy container unhealthy (which, now that cloudflared gates on service_healthy, would block the tunnel). (coderabbit) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YG1NZU5zNDAX2gWGNubjwi * test(mcp): full integration test for an authenticated tool call over HTTP Drives a real fastmcp Client through the real FastMCP HTTP transport + bearer auth middleware + MCP handshake + tool dispatch end to end (in-process via httpx ASGI — no port, no network, no new deps): correct bearer → notebook_list dispatches to the stubbed client and returns; wrong bearer → 401. Closes the gap where only the auth gate (not a full tool call) was covered over HTTP; the in-memory transport is already covered by tests/integration/mcp_vcr. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YG1NZU5zNDAX2gWGNubjwi * fix(deploy): build the image from source (dev) with a PyPI build-arg (prod) `pip install notebooklm-py[mcp,headless]` from PyPI can't carry an unreleased branch, which is why `docker compose up` failed with `exec: "notebooklm-mcp": executable file not found`. The Dockerfile now installs THIS checkout by default (compose build context = repo root) and falls back to a PyPI spec when NOTEBOOKLM_SPEC is set, so both dev and production work. Added a repo-root .dockerignore (excludes .git/.venv/.worktrees/caches/secrets) and documented both modes. Built + verified in a real container: `notebooklm-mcp --help` runs (script on PATH) and the http fail-closed token guard fires. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YG1NZU5zNDAX2gWGNubjwi * feat(deploy): Makefile for one-command dev/prod mode switching `make dev` builds + installs this checkout (source); `make prod VERSION=x.y.z` builds + installs a published PyPI release; plus logs/restart/down/config. Wraps the dual-mode Dockerfile so switching is one command. README updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YG1NZU5zNDAX2gWGNubjwi * feat(deploy): mount ~/.notebooklm/profiles/default + run as host uid (no chown) Profile mounting reworked so it's frictionless and doesn't disturb the host: - Default-mounts ~/.notebooklm/profiles/default (override via NOTEBOOKLM_PROFILE_DIR) instead of a copied ./profile — no copy step. - Runs the container as your uid:gid (NOTEBOOKLM_UID/GID, default 1000; `make` fills them from `id`), so the mounted profile is read/writable with NO chown and your own `notebooklm` CLI keeps owning the files. Drops the sudo chown step. - Points the app at the mount via NOTEBOOKLM_HOME=/data + HOME (so an arbitrary uid with no /etc/passwd entry never trips a ~/getpwuid lookup). Built + run-verified end-to-end via `docker compose up` against a real profile: container becomes healthy, reads the profile as the host uid (no permission/ getpwuid errors), and the bearer gate returns 401. README/.env.example/Makefile updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YG1NZU5zNDAX2gWGNubjwi * test(mcp): assert 401 explicitly on the wrong-bearer path The integration test's wrong-token branch asserted only that some HTTPStatusError was raised; pin it to 401 so a different error can't pass silently. (coderabbit) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YG1NZU5zNDAX2gWGNubjwi --------- Co-authored-by: Claude <noreply@anthropic.com> | 3 天前 |
| docs: sync documentation with E2E fixture redesign - Update adding-rpc-methods.md example to use read_only_notebook_id - Add fixture selection guidance comments in E2E test example - Fix hardcoded ~/.notebooklm paths to reference NOTEBOOKLM_HOME Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> | 5 个月前 |
| refactor: relocate CLI business logic to transport-neutral _app/ layer (ADR-0021) (#1479) Relocate all CLI business logic into a transport-neutral src/notebooklm/_app/ layer (28 modules) reusable by the CLI + future MCP/HTTP adapters. Non-breaking (underscore-private; --json byte-stable; cassettes reused). 145->623 direct _app tests; full suite + 14-way CI matrix green; 11 live e2e passes. Recorded in ADR-0021. Reviewed by gemini, CodeQL, claude, and CodeRabbit. | 24 天前 |
| pre-commit: drive ruff via uv run to remove version-pin drift (#1578) * pre-commit: drive ruff via uv run to remove version-pin drift Replace the ruff-pre-commit mirror (which pinned ruff a second time via rev:) with local hooks that invoke the project's own ruff through `uv run`. pyproject.toml's dev dependency pin is now the single source of truth, so the pre-commit ruff version can no longer drift from the version used in CI and locally. * pre-commit: add --force-exclude to local ruff hooks The official ruff-pre-commit mirror passes --force-exclude by default; restore that behavior on the local hooks so ruff honors its configured (and default) excludes even when pre-commit passes filenames explicitly. --------- Co-authored-by: Claude <noreply@anthropic.com> | 17 天前 |
| chore: move example scripts from docs/examples/ to examples/ (#1573) Move the runnable example scripts (bulk-import, chat, notes, quickstart, research-to-podcast, video) from docs/examples/ to the repo-root examples/, and update all references (idiom guard, AGENTS.md, CONTRIBUTING.md, docs/releasing.md). Pure git mv -- no script changes. | 18 天前 |
| feat(mcp): soft-404 body-pattern detection + surface in source_add batch (#1713) Extends the #1698 (PR #1709) char-thin content-sanity heuristic, which missed a real soft-404 that ingests READY as a 1,766-char "Whoops! broken link" boilerplate body (1766 >> the 100-char threshold). - Strengthen `_thin_content_warning`: after the existing char-thin check, scan a body SHORTER than `_SOFT_404_BODY_SCAN_LIMIT` (2000) for an anchored dead-link phrase (`_SOFT_404_PHRASES`). Body-only (titles never scanned), length-gated before casefold, advisory-only, zero extra RPC (body already fetched). - `_add_url_batch` now annotates synchronously-ready web-page items via the existing concurrent `_annotate_thin_warnings` helper, so a ready soft-404 carries its warning in the batch result. - Extract the content-sanity helpers + constants to a sibling `mcp/tools/_content_sanity.py` so `sources.py` stays under the ADR-0008 module-size budget (split, not allowlist). Claude-Session: https://claude.ai/code/session_0158QsP9LWmJdfob2491LW3Z Co-authored-by: Claude <noreply@anthropic.com> | 2 天前 |
| ci(claude): post @claude review as inline threads + document the merge gate (#1623) * ci(claude): post @claude review as inline threads + document the gate The claude-code-action posted its review as a single sticky issue comment that never shows in reviewDecision/reviews and isn't a required check, so the merge gate (and humans) could miss it. - claude.yml: grant `pull-requests: write` and allow the GitHub inline-comment tool (`--allowedTools mcp__github_inline_comment__create_inline_comment`) so `@claude review` lands findings as inline PR review-thread comments (visible + addressable like gemini/coderabbit). `--allowedTools` is additive in this action (base Read/Glob/Grep/LS stay included), so review file/diff access is unaffected. Trigger stays gated to teng-lin + @claude. - CLAUDE.md: document claude[bot] as a first-class reviewer the merge gate waits on — how to find the real review run (vs the comment-fan-out "skipping" entries) and how to read/address its output before merge. Process-only: no auto-review-on-PR workflow, no branch-protection change. Note: claude.yml runs from the default branch for issue_comment events, so the inline behavior activates on the next PR after this merges. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0158QsP9LWmJdfob2491LW3Z * fix(review): correct inline-comments endpoint + single-line claude_args Address bot review on #1623: - CLAUDE.md (gemini): inline claude[bot] findings are on the pulls /comments endpoint, not issues/comments (which only returns the sticky summary); use leading-slash paths. Now documents both endpoints. - claude.yml (claude[bot]): use a single-line claude_args scalar to drop the block-scalar trailing newline; note the verified `--allowedTools` additivity in the comment. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0158QsP9LWmJdfob2491LW3Z --------- Co-authored-by: Claude <noreply@anthropic.com> | 11 天前 |
| chore: move example scripts from docs/examples/ to examples/ (#1573) Move the runnable example scripts (bulk-import, chat, notes, quickstart, research-to-podcast, video) from docs/examples/ to the repo-root examples/, and update all references (idiom guard, AGENTS.md, CONTRIBUTING.md, docs/releasing.md). Pure git mv -- no script changes. | 18 天前 |
| Initial commit: Fresh start of notebooklm-client v0.1.0 | 5 个月前 |
| docs: sharpen "seen in the wild" link descriptions Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R6TEiVAR3ywy7FEYrgy9Az | 1 天前 |
| docs: refresh markdown for v0.8 surface (#1554) * docs: refresh markdown for v0.8 surface * docs(cli): fix shell exit-status recipe * docs(api): remove stale Session reference * docs(review): address coderabbit feedback --------- Co-authored-by: Claude <noreply@anthropic.com> | 20 天前 |
| feat(cli): suggest-prompts command for notebooks.suggest_prompts (#1616) (#1617) Add a top-level `notebooklm suggest-prompts` command mirroring the sibling top-level `ask`: a read-only notebook-level AI helper that surfaces `client.notebooks.suggest_prompts()` over the CLI. notebooklm suggest-prompts [-n ID] [--mode N] [--query TEXT] [-s ID ...] [--json] `--mode` (default 4) selects the suggestion surface (4=chat questions, 5=critique, 6=audio/debate, 8=quiz, 9=flashcards); the method validates 1..9 and raises ValidationError, which the shared `@with_client` error envelope maps to a clean VALIDATION_ERROR / exit-1 JSON envelope. Text mode prints a numbered title+prompt list; `--json` emits `{notebook_id, suggestions:[{title,prompt}], count}`. Uses the same client/notebook/source-resolution seam as the sibling top-level commands (resolve_client_factory + require/resolve notebook + resolve_source_ids), so the RPC-envelope guardrail passes automatically. Wired into: grouped.py Chat section, cli_contract baseline (ADR-0022), json stdout-purity + error-exit sweeps, SKILL.md (autonomy + quick reference), docs/cli-reference.md, and a new tests/unit/cli/test_suggest_prompts.py. Stacks on #1615 (the suggest_prompts method); must merge after it. Claude-Session: https://claude.ai/code/session_0158QsP9LWmJdfob2491LW3Z Co-authored-by: Claude <noreply@anthropic.com> | 11 天前 |
| docs: add project logo to README 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> | 5 个月前 |
| deps: bump ruff from 0.15.18 to 0.15.20 in the dev-dependencies group (#1648) Bumps the dev-dependencies group with 1 update: [ruff](https://github.com/astral-sh/ruff). Updates `ruff` from 0.15.18 to 0.15.20 - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.15.18...0.15.20) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.20 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> | 3 天前 |
| fix(cli): reject chat configure --mode + custom fields (transport-neutral) (#1716) (#1717) * fix(cli): reject chat configure --mode + custom fields (transport-neutral) (#1716) `notebooklm chat configure --mode X --response-length Y` silently dropped the custom fields because execute_configure short-circuits on chat_mode. Move the preset-vs-custom mutual-exclusion guard into the transport-neutral `_app.chat.execute_configure` so the CLI and the MCP tool share one rule, and remove the now-redundant guard from the MCP tool (its rejection tests still pass via the tool->core path). Empty persona "" stays a no-op (does not block a preset); any explicit response_length (incl. "default") is rejected. Fixes the misleading `--mode detailed --response-length longer` CLI help example. Tests: parametrized core rejection + empty-persona-ok; CLI combo errors with a non-zero exit and no RPC. Also reconciles uv.lock (ruff 0.15.18 -> 0.15.20) to match the pyproject pin — the committed lock lagged and `uv lock --check` failed on main; this is the lock update, not an accidental bump. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ZUzFEAC3XBxwjYQ435QFQ * test(app): assert configure not called on empty-persona preset Addresses @claude review on #1717: the empty-persona preset test now asserts `client.chat.configure.assert_not_called()`, matching the sibling short-circuit test and making the guard intent explicit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ZUzFEAC3XBxwjYQ435QFQ --------- Co-authored-by: Claude <noreply@anthropic.com> | 1 天前 |