This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
An unofficial Node.js/TypeScript client for Gemini Notebook. It talks to Gemini Notebook's
internal batchexecute RPC endpoints (no public API exists), shipped as both a programmatic
library (GeminiNotebookClient) and an agent-first CLI (gemini-notebook). The protocol layer is a
TypeScript port of notebooklm-py — many files cite
the upstream Python module they were ported from, and that project is the source of truth for
wire-format details.
Package manager is pnpm (there is a pnpm-lock.yaml).
pnpm dev <command...> # run the CLI from source via tsx, no build (e.g. pnpm dev list)
pnpm build # tsc -p tsconfig.build.json → dist/ (src only, excludes tests)
pnpm test # vitest run (183 unit tests)
pnpm test:watch # vitest watch mode
pnpm vitest run tests/unit/encoder.test.ts # run a single test file
pnpm vitest run -t 'nestSourceIds' # run tests matching a name
pnpm typecheck # tsc --noEmit (full strict check incl. tests)
pnpm lint # biome check src tests
pnpm lint:fix # biome check --write (autofix)
pnpm format # biome format --writeTypeScript is maximally strict (noUncheckedIndexedAccess, exactOptionalPropertyTypes,
verbatimModuleSyntax, etc.). All imports use explicit .js extensions (NodeNext ESM) even for
.ts source files. Biome enforces single quotes, 2-space indent, 100-col width, trailing commas.
The stack is strictly layered; a request flows CLI → GeminiNotebookClient → feature API → Session → Transport → RPC encode/decode.
src/rpc/— the wire protocol, ported verbatim from notebooklm-py.types.tsholds the obfuscated RPC method IDs (RPCMethod, e.g.LIST_NOTEBOOKS: 'wXbhsf') plus all artifact/format enums. Google changes these IDs without notice; this file must be kept in sync with upstream. Escape hatch: theGEMINI_NOTEBOOK_RPC_OVERRIDESenv var (overrides.ts) patches IDs at runtime without a release.encoder.tsbuilds thef.reqbody — the format is a triple-nested array[[[rpcId, jsonParams, null, "generic"]]], URL-encoded.nestSourceIds(ids, depth)wraps source IDs in N layers of arrays — generation params demand specific nesting depths.decoder.tsparses the chunked anti-XSSI response ()]}'prefix, alternating byte-count / JSON lines,["wrb.fr", id, result, …]or["er", …]envelopes) and raises the typed errors.- Request/response params are position-sensitive nested arrays (
[[2], notebookId, [null, null, typeCode, …]]). Positions andnullpadding matter — an off-by-one silently makes the backend drop config and return no result. Read withsafeIndex()(returnsundefinedOOB; setGEMINI_NOTEBOOK_STRICT_DECODE=1to throw and catch shape regressions in dev).
src/session/—Sessioncaches auth tokens (CSRF + session id, 25-min TTL, lazily re-extracted from the homepage HTML), dispatches RPC calls, and retries once onAuthError.Transport(undici) owns cookies, Set-Cookie persistence, retry/backoff for 429/5xx + transient socket faults, the keepaliveRotateCookiespoke, and the multi-hop signed-URL download chain.src/api/— one class per feature domain (notebooks,sources,chat,artifacts,notes,share,research,user), each constructed with aSessionand exposed as a field onGeminiNotebookClient(src/client.ts). API methods build the nested params, callsession.call('METHOD_NAME', params, { allowNull? }), and parse the result. Artifact rows retain their type-specific generation prompt, exposed throughartifacts.getPrompt().user.whoami()reads tier code and quotas from the single authoritativeGET_USER_SETTINGSlimits block.src/cli/—index.tswires up Commander;artifactCommands.tsregisters thegenerate/artifact/downloadsubtrees.output.tsdefines the agent contract (see below).src/auth/—storage_state.jsonis Playwright-compatible (same shape asBrowserContext.storageState()). Default path~/.config/gemini-notebook-cli/storage_state.json, overridable via--storageorGEMINI_NOTEBOOK_STORAGE. Login has three paths: browser auto-capture (loginBrowser.ts, the only thing needing Playwright — an optional peer dep), paste-a-cURL (loginPaste.ts/curlCookies.ts), and macOS Chrome cookie decrypt (chromeCookies.ts). Browser login accepts both the legacynotebooklm.google.comhost and the currentnotebook.google.comapp destination and waits only for navigation commit because the streaming SPA may never fire load.
Most operations go through the batchexecute RPC (Session.call). Two endpoints don't:
- Chat (
api/chat.ts) posts to a streaming endpoint (GenerateFreeFormStreamed) with a different body shape (f.req = [null, jsonParams], no triple nesting) and parses a stream ofwrb.frenvelopes for answer text + citations. Don't assume the RPC encoder/decoder applies. - File upload (
api/sources.tsaddFile+api/sourceUpload.ts) is a Google "Scotty" resumable upload to/upload/_/(getUploadUrl()): register viaADD_SOURCE_FILE→starthandshake (read session URL from thex-goog-upload-urlresponse header) → stream bytes withx-goog-upload-command: upload, finalize.validateResumableUploadUrlpins the returned URL to the configured host/path before sending bytes.ADD_SOURCE_FILEis the one RPC that needs a non-defaultsource-path(/notebook/<id>) — hence thesourcePathoption onSession.call.
This is the project's core design invariant — preserve it when adding commands:
- Every data command takes
--json. Results go to stdout, progress/logs to stderr. - Errors are data: under
--json, failures print{ "error": { "code", "message", … } }to stdout (not stderr) so a singleout=$(cmd --json)capture always parses. - Exit codes are a stable contract keyed off the error class (
EXITmap):0OK,2USAGE,3AUTH,4NOT_FOUND,5NOT_READY,6RATE_LIMIT,7RPC,8NETWORK.classifyError()maps the error hierarchy (src/rpc/errors.ts) to these codes. New commands shouldemit()on success andfail(opts, err)on error rather than printing/exiting ad hoc.
The CLI opens clients with disableKeepalive: true + readOnlyStorage: true (see
cli/helpers.ts): each invocation is a short-lived process, and rotating __Secure-1PSIDTS per
call across processes degrades the session and causes homepage redirect loops.
Unit tests live in tests/unit/*.test.ts (vitest, node env). They exercise the pure logic —
encoder/decoder wire format, citation/artifact parsing, generation param shapes, CLI output
classification — by feeding captured response fixtures, not by hitting the live API. When changing
nested param construction or response parsing, add/adjust a fixture-based test; the param-shape
tests (generationParams.test.ts, encoder.test.ts) are the guardrail against off-by-one nesting
bugs that the live backend silently swallows.
GEMINI_NOTEBOOK_RPC_OVERRIDES— JSON map ofMethodName → rpcIdto patch drifted IDs.GEMINI_NOTEBOOK_STORAGE— override the storage_state.json path.GEMINI_NOTEBOOK_DEBUG=1— verbose redirect/download logging; disables response-preview truncation.GEMINI_NOTEBOOK_STRICT_DECODE=1— makesafeIndexthrow on OOB to surface response-shape drift.GEMINI_NOTEBOOK_HL— default interface language for artifact generation (defaulten).GEMINI_NOTEBOOK_BASE_URL— base URL; hosts are allowlisted to the currentnotebook.google.comand legacynotebooklm.google.comapp domains.