npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

notebooklm-node

v0.1.1

Published

Unofficial Node.js / TypeScript client and CLI for Google NotebookLM (port of notebooklm-py). Auto-refreshing auth, RPC batchexecute transport, and a commander-based CLI.

Readme

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.