Skip to content

clonn/notebooklm-node

Repository files navigation

notebooklm-node

CI npm version npm downloads types node license

Unofficial Node.js / TypeScript port of notebooklm-py — a programmatic client and CLI for Google NotebookLM, built on the same undocumented batchexecute RPC protocol.

Phase 1 release. Foundation is complete: RPC transport, auth with auto-refresh, profile system, notebooks CRUD, and chat.ask. The other domain namespaces (sources, artifacts, notes, research, sharing, settings) are exposed as typed stubs that throw NotImplementedError on call. They're already implemented in upstream notebooklm-py and are being ported in subsequent releases.


Install

As a CLI (recommended for shell use):

# global install
npm install -g notebooklm-node
# or one-off via npx (no install)
npx notebooklm-node --help

As an SDK in your project:

npm  add notebooklm-node       # or
pnpm add notebooklm-node       # or
yarn add notebooklm-node

To use notebooklm login (interactive Chromium auth flow) you also need playwright as a peer dep:

npm  add -D playwright && npx playwright install chromium

CI environments without a browser can skip playwright entirely and supply auth via the NOTEBOOKLM_AUTH_JSON env var.

Requires Node ≥ 20.


CLI

After npm install -g notebooklm-node, the notebooklm binary is on your $PATH:

notebooklm [-p|--profile <name>] [--storage <path>] [--json] <command> [args]

Global flags (apply to every command):

Flag Description
-p, --profile <name> Auth profile name. Falls back to NOTEBOOKLM_PROFILE, then default.
--storage <path> Path to a storage_state.json file (overrides profile resolution).
--json Emit machine-readable JSON instead of human-readable text.
-V, --version Print version.
-h, --help Per-command help (notebooklm <cmd> --help).

notebooklm login

Open Chromium, sign you in, and write storage_state.json to the active profile dir.

notebooklm login
notebooklm login --headless                 # rare: only useful in tests
notebooklm login --timeout 600000           # raise the 5-minute timeout
notebooklm -p work login                    # use the "work" profile

Requires playwright installed.

notebooklm use <notebookId>

Set the active notebook for subsequent commands. Stored in ~/.notebooklm/profiles/<profile>/context.json.

notebooklm use 9c0e1234-aaaa-bbbb-cccc-111122223333

notebooklm status

Show resolved profile, storage path, and the active notebook.

notebooklm status
notebooklm --json status                    # machine-parseable output

notebooklm clear

Clear the active notebook context (deletes context.json).

notebooklm clear

notebooklm list

List notebooks belonging to the authenticated user.

notebooklm list
notebooklm --json list                      # full notebook objects as JSON

Default output is tab-separated <id>\t<title>, one per line.

notebooklm create <title>

Create a notebook.

notebooklm create "Q2 Research Drop"
notebooklm --json create "Q2 Research Drop"

notebooklm rename <notebookId> <newTitle>

Rename a notebook.

notebooklm rename 9c0e1234-... "Q2 Research (final)"

notebooklm delete <notebookId>

Delete a notebook. Irreversible.

notebooklm delete 9c0e1234-...

notebooklm ask <question...>

Ask the active notebook a question (or pass --notebook <id> to override).

# uses the active notebook
notebooklm ask what are the key themes
# explicit notebook
notebooklm ask -n 9c0e1234-... "summarise chapter 3"
# follow-up in an existing conversation
notebooklm ask -c <conversationId> "elaborate on point 2"
# JSON output (answer + conversationId + references[])
notebooklm --json ask "list the citations"

--json returns:

{
  "answer": "",
  "conversation_id": "",
  "turn_number": 1,
  "is_follow_up": false,
  "references": [
    { "sourceId": "", "citationNumber": 1, "citedText": "", "startChar": 12, "endChar": 87, "chunkId": "" }
  ]
}

Typical end-to-end session

notebooklm login                                   # one-time per machine
notebooklm list
notebooklm use 9c0e1234-...
notebooklm ask "give me three main takeaways"
notebooklm --json ask "follow up on the second one" \
  | jq -r .answer

SDK

import { NotebookLMClient } from "notebooklm-node";

const client = await NotebookLMClient.fromStorage();        // ~/.notebooklm/profiles/<profile>/storage_state.json
// or:
const client = await NotebookLMClient.fromEnv();            // $NOTEBOOKLM_AUTH_JSON

await client.connect();

const notebooks = await client.notebooks.list();
const nb        = await client.notebooks.create({ title: "Hello" });
const renamed   = await client.notebooks.rename(nb.id, "World");
await client.notebooks.delete(nb.id);

const result = await client.chat.ask(nb.id, "what is this about?");
console.log(result.answer, result.references);

await client.close();

NotebookLMClient extends EventEmitter and emits "auth:refreshed" when an auto-refresh fires.

Auto-refresh

Every RPC call goes through a refresh-aware retry layer. If the call fails with HTTP 401/403 or an auth-shaped RPC error, the client will:

  1. Acquire a single in-flight refresh lock (concurrent callers join the same promise).
  2. Re-fetch the NotebookLM homepage and extract a fresh SNlM0e (CSRF) and FdrFJe (session ID).
  3. Update the in-memory cookie + auth state.
  4. Retry the original RPC once.

If the refresh itself fails (cookies expired), an AuthError is thrown asking you to run notebooklm login.


Configuration

Env vars (compatible with notebooklm-py):

Variable Purpose
NOTEBOOKLM_HOME Base directory (default ~/.notebooklm)
NOTEBOOKLM_PROFILE Active profile (default default)
NOTEBOOKLM_AUTH_JSON Inline storage state JSON (CI-friendly; no file needed)
NOTEBOOKLM_BL Override the build-label query param sent to the chat endpoint
NOTEBOOKLM_DEBUG=1 Verbose logging (or DEBUG=notebooklm)

The on-disk layout is identical to notebooklm-py, so you can flip back and forth between the Python and Node clients on the same machine without re-authenticating.


What's implemented

Namespace Status Notes
client.notebooks ✅ shipping list, create, get, rename, delete, getRaw
client.chat ✅ shipping ask with conversation cache + citation parsing
client.sources ⏳ stub Available in upstream notebooklm-py
client.artifacts ⏳ stub Available in upstream notebooklm-py
client.notes ⏳ stub Available in upstream notebooklm-py
client.research ⏳ stub Available in upstream notebooklm-py
client.sharing ⏳ stub Available in upstream notebooklm-py
client.settings ⏳ stub Available in upstream notebooklm-py

Stubs throw NotImplementedError at call time but are typed, so SDK consumers get autocomplete today.


Layout

src/
├── client.ts            # NotebookLMClient (orchestrator + refresh hook)
├── auth/                # storage_state I/O, SNlM0e/FdrFJe extraction, login
├── rpc/                 # batchexecute encoder/decoder + method-id table
├── core/                # HTTP wrapper + refresh-aware ClientCore + errors
├── api/
│   ├── notebooks.ts     # NotebooksAPI
│   ├── chat.ts          # ChatAPI.ask
│   └── stubs.ts         # Phase 2 namespaces
├── cli/                 # commander root + login/session/notebooks/ask
└── ...

Testing

pnpm test            # vitest unit + integration (80 tests)
pnpm test:coverage
pnpm typecheck
pnpm build

The integration suite stubs globalThis.fetch via a tiny MockFetch helper (tests/helpers/mock-fetch.ts) — no network is touched during pnpm test.

The auto-refresh integration test in tests/integration/notebooks.test.ts asserts that:

  • A 401 on the first call triggers exactly one homepage refresh.
  • The retry carries the new CSRF token (at=…) and f.sid=….
  • Concurrent in-flight requests share a single refresh.

License

MIT. Unofficial: this project is not affiliated with or endorsed by Google. Use at your own risk; the underlying API is undocumented and Google can change it at any time.

About

Unofficial Node.js / TypeScript client and CLI for Google NotebookLM (port of notebooklm-py)

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors