Automate Google NotebookLM - create notebooks, add sources, generate podcasts/videos/quizzes, download artifacts. Supports browser-based content extraction for anti-crawler platforms (Twitter/X, LinkedIn, etc.). Activates on explicit /notebooklm or intent like "create a podcast about X"
Automate Google NotebookLM: create notebooks, add sources, chat with content, generate artifacts (podcasts, videos, quizzes), and download results.
IMPORTANT: Before using any command, you MUST authenticate:
notebooklm login # Opens browser for Google OAuth
notebooklm list # Verify authentication works
If commands fail with authentication errors, re-run notebooklm login.
For automated environments, multiple accounts, or parallel agent workflows:
| Variable | Purpose |
|----------|---------|
| NOTEBOOKLM_HOME | Custom config directory (default: ~/.notebooklm) |
| NOTEBOOKLM_AUTH_JSON | Inline auth JSON - no file writes needed |
CI/CD setup: Set NOTEBOOKLM_AUTH_JSON from a secret containing your storage_state.json contents.
Multiple accounts: Use different NOTEBOOKLM_HOME directories per account.
Parallel agents: The CLI stores notebook context in a shared file (~/.notebooklm/context.json). Multiple concurrent agents using notebooklm use can overwrite each other's context.
Solutions for parallel workflows:
-n <notebook_id> (for wait/download commands) or --notebook <notebook_id> (for others) instead of relying on useNOTEBOOKLM_HOME per agent: export NOTEBOOKLM_HOME=/tmp/agent-$IDBefore starting workflows, verify the CLI is ready:
notebooklm status → Should show "Authenticated as: email@..."notebooklm list --json → Should return valid JSON (even if empty notebooks list)notebooklm loginExplicit: User says "/notebooklm", "use notebooklm", or mentions the tool by name
Intent detection: Recognize requests like:
Progressive fallback strategy (graceful degradation):
notebooklm source add "url" (always try first)/tmp/ before adding to NotebookLMRun automatically (no confirmation):
notebooklm status - check contextnotebooklm list - list notebooksnotebooklm source list - list sourcesnotebooklm artifact list - list artifactsnotebooklm artifact wait - wait for artifact completion (in subagent context)notebooklm source wait - wait for source processing (in subagent context)notebooklm research status - check research statusnotebooklm research wait - wait for research (in subagent context)notebooklm use <id> - set context (⚠️ SINGLE-AGENT ONLY - use -n flag in parallel workflows)notebooklm create - create notebooknotebooklm ask "..." - chat queriesnotebooklm source add - add sourcesSmart Decision Framework (Pragmatic Approach)
Installation Status System:
# Installation flag - prevents redundant checks
INSTALL_FLAG="$HOME/.cache/notebooklm/.playwright_installed"
# Function to check installation status (only once per session)
check_install_status() {
# Fast check only if not explicitly checking
if [[ "$1" == "--check-install-only" ]]; then
if [ -f "$INSTALL_FLAG" ]; then
echo "✓ Playwright is marked as installed"
return 0
else
echo "⚠️ Playwright not marked, checking installation..."
# Here you have options:
# Option A: Quick check (try once)
# Option B: Full installation verification
# Option A (recommended): Quick check
if timeout 3 python3 -c "import playwright" 2>/dev/null; then
echo "✓ Playwright Python library installed"
touch "$INSTALL_FLAG"
return 0
fi
# Option B: Full verification (if needed)
# pip3 install playwright
# python3 -m playwright install chromium
fi
}
Platform and Content Detection:
# Function: Platform type detection
detect_platform_type() {
local url="$1"
# Twitter/X: Always anti-crawler
if [[ "$url" =~ (twitter\.com|x\.com) ]]; then
echo "ANTICRAWLER_TWITTER"
return 0
# Other known anti-crawler platforms
elif [[ "$url" =~ (linkedin\.com|facebook\.com|instagram\.com) ]]; then
echo "ANTICRAWLER_OTHER"
return 1
# Open web (could support direct addition)
else
echo "OPEN_WEB"
return 2
fi
}
/tmp/ before adding to NotebookLMAsk before running:
notebooklm delete - destructivenotebooklm generate * - long-running, may failnotebooklm download * - writes to filesystemnotebooklm artifact wait - long-running (when in main conversation)notebooklm source wait - long-running (when in main conversation)notebooklm research wait - long-running (when in main conversation)| Task | Command |
|------|---------|
| Authenticate | notebooklm login |
| List notebooks | notebooklm list |
| Create notebook | notebooklm create "Title" |
| Set context | notebooklm use <notebook_id> |
| Show context | notebooklm status |
| Add URL source | notebooklm source add "https://..." |
| Add file | notebooklm source add ./file.pdf |
| Add YouTube | notebooklm source add "https://youtube.com/..." |
| Extract Twitter/X via browser | Use Playwright skill → Save → notebooklm source add ./file.md |
| Extract anti-crawler content | Use browser automation → Save → Add to notebook |
| List sources | notebooklm source list |
| Wait for source processing | notebooklm source wait <source_id> |
| Web research (fast) | notebooklm source add-research "query" |
| Web research (deep) | notebooklm source add-research "query" --mode deep --no-wait |
| Check research status | notebooklm research status |
| Wait for research | notebooklm research wait --import-all |
| Chat | notebooklm ask "question" |
| Chat (new conversation) | notebooklm ask "question" --new |
| Chat (specific sources) | notebooklm ask "question" -s src_id1 -s src_id2 |
| Generate podcast | notebooklm generate audio "instructions" |
| Generate podcast (specific sources) | notebooklm generate audio -s src_id1 -s src_id2 |
| Generate video | notebooklm generate video "instructions" |
| Generate quiz | notebooklm generate quiz |
| Check artifact status | notebooklm artifact list |
| Wait for completion | notebooklm artifact wait <artifact_id> |
| Download audio | notebooklm download audio ./output.mp3 |
| Download video | notebooklm download video ./output.mp4 |
| Delete notebook | notebooklm notebook delete <id> |
Parallel safety: Use explicit notebook IDs in parallel workflows. Commands supporting -n shorthand: artifact wait, source wait, research wait/status, download *. Other commands use --notebook. For chat, use --new to start fresh conversations (avoids conversation ID conflicts).
Partial IDs: Use first 6+ characters of UUIDs. Must be unique prefix (fails if ambiguous). Works for: use, delete, wait commands. For automation, prefer full UUIDs to avoid ambiguity.
Commands with --json return structured data for parsing:
Create notebook:
$ notebooklm create "Research" --json
{"id": "abc123de-...", "title": "Research"}
Add source:
$ notebooklm source add "https://example.com" --json
{"source_id": "def456...", "title": "Example", "status": "PROCESSING"}
Generate artifact:
$ notebooklm generate audio "Focus on key points" --json
{"artifact_id": "xyz789...", "status": "PENDING", "type": "AUDIO_OVERVIEW"}
Extract IDs: Parse the id, source_id, or artifact_id field from JSON output.
All generate commands support -s, --source to use specific source(s) instead of all sources.
| Type | Command | Downloadable |
|------|---------|--------------|
| Podcast | generate audio | Yes (.mp3) |
| Video | generate video | Yes (.mp4) |
| Slides | generate slide-deck | Yes (.pdf) |
| Infographic | generate infographic | Yes (.png) |
| Quiz | generate quiz | No (view in UI) |
| Flashcards | generate flashcards | No (view in UI) |
| Mind Map | generate mind-map | No (view in UI) |
| Data Table | generate data-table | No (export to Sheets) |
| Report | generate report | No (export to Docs) |
Installation Status System:
# Installation flag - prevents redundant checks
INSTALL_FLAG="$HOME/.cache/notebooklm/.playwright_installed"
# Function to check installation status (only once per session)
check_install_status() {
# Fast check only if not explicitly checking
if [[ "$1" == "--check-install-only" ]]; then
if [ -f "$INSTALL_FLAG" ]; then
echo "✓ Playwright is marked as installed"
return 0
else
echo "⚠️ Playwright not marked, checking installation..."
# Here you have options:
# Option A: Quick check (try once)
# Option B: Full installation verification
# Option A (recommended): Quick check
if timeout 3 python3 -c "import playwright" 2>/dev/null; then
echo "✓ Playwright Python library installed"
touch "$INSTALL_FLAG"
return 0
fi
# Option B: Full verification (if needed)
# pip3 install playwright
# python3 -m playwright install chromium
# touch "$INSTALL_FLAG"
# echo "✓ Installation complete"
# fi
fi
# Auto-install if missing and not checking
if ! check_install_status && command -v python3 -c "import playwright" 2>/dev/null; then
echo "📦 Installing Playwright..."
pip3 install playwright
python3 -m playwright install chromium
touch "$INSTALL_FLAG"
echo "✓ Installation complete"
fi
}
Platform and Content Detection:
# Function: Platform type detection
detect_platform_type() {
local url="$1"
# Twitter/X: Always anti-crawler
if [[ "$url" =~ (twitter\.com|x\.com) ]]; then
echo "ANTICRAWLER_TWITTER"
return 0
# Other known anti-crawler platforms
elif [[ "$url" =~ (linkedin\.com|facebook\.com|instagram\.com) ]]; then
echo "ANTICRAWLER_OTHER"
return 1
# Open web (could support direct addition)
else
echo "OPEN_WEB"
return 2
fi
}
Scenario 1: Twitter/X (Anti-Crawler)
# Direct path: Skip regular method, use browser extraction immediately
extract_twitter_content() {
local url="$1"
# Navigate
skill_mcp playwright browser_navigate --arguments "{\"url\": \"$url\"}"
# Wait and extract with multi-selector strategy
skill_mcp playwright browser_run_code --arguments '{
"code": "async (page) => {
await page.waitForTimeout(6000);
// Multi-selector priority
const selectors = [
'\''article [data-testid=\"tweetText\"]\'', // Primary: tweet text
'\''div[data-testid=\"tweet\"] span\'', // Fallback 1
'\''[role=\"article\"]\'' // Fallback 2
];
let content = null;
for (const selector of selectors) {
try {
const element = await page.locator(selector).first();
if (await element.count() > 0) {
content = await element.textContent();
if (content && content.trim().length > 0) {
console.log(`Found with selector: ${selector}`);
break;
}
}
} catch (e) {
// Try next selector
}
}
// Ultimate fallback
if (!content) {
content = await page.evaluate(() => {
const articles = document.querySelectorAll("article");
return Array.from(articles).map(a => a.innerText).join("\\n---\\n");
});
}
return {
url: page.url(),
title: await page.title(),
content: content?.trim()
};
}"
}'
# Save to file and add to NotebookLM
local filename="/tmp/twitter_extract_$(date +%s).md"
echo "$EXTRACTED_CONTENT" > "$filename"
notebooklm source add "$filename" --notebook "$NOTEBOOK_ID"
echo "⚠️ Browser extraction used (anti-crawler protection)"
}
# Usage
extract_twitter_content "https://x.com/user/status/123"
Scenario 2: Open Web (Regular Method First)
# Try regular method first
try_regular_add() {
local url="$1"
local result=$(notebooklm source add "$url" --notebook "$NOTEBOOK_ID" --json)
local status=$(echo "$result" | jq -r '.source.status // empty')
case "$status" in
READY|PROCESSING)
echo "✓ Regular method succeeded"
return 0
;;
FAILED|AUTH_REQUIRED)
echo "⚠️ Failed - trying browser extraction"
# Fall back to browser extraction
extract_content_via_browser "$url"
;;
*)
echo "❓ Unknown status, trying browser"
extract_content_via_browser "$url"
;;
esac
}
Scenario 3: Smart Batch Processing
# Process multiple URLs with intelligent method selection per URL
process_urls_intelligent() {
local urls=("$@")
local notebook_id="$1"
for url in "${urls[@]}"; do
local platform=$(detect_platform_type "$url")
case "$platform" in
ANTICRAWLER_TWITTER|ANTICRAWLER_OTHER)
# Direct to browser (skip regular method)
extract_content_via_browser "$url"
;;
OPEN_WEB)
# Try regular first
try_regular_add "$url"
;;
esac
done
}
Time: 5-10 minutes total
notebooklm create "Research: [topic]" — if fails: check auth with notebooklm loginnotebooklm source add for each URL/document — if one fails: log warning, continue with othersnotebooklm source list --json until all status=READY — required before generationnotebooklm generate audio "Focus on [specific angle]" (confirm when asked) — if rate limited: wait 5 min, retry oncenotebooklm artifact list later for statusnotebooklm download audio ./podcast.mp3 when complete (confirm when asked)Time: 5-10 minutes, but continues in background
When user wants full automation (generate and download when ready):
source wait or check source list --json)notebooklm generate audio "..." --json → parse artifact_id from outputTask(
prompt="Wait for artifact {artifact_id} in notebook {notebook_id} to complete, then download.
Use: notebooklm artifact wait {artifact_id} -n {notebook_id} --timeout 600
Then: notebooklm download audio ./podcast.mp3 -a {artifact_id} -n {notebook_id}",
subagent_type="general-purpose"
)
Error handling in subagent:
artifact wait returns exit code 2 (timeout): Report timeout, suggest checking artifact listBenefits: Non-blocking, user can do other work, automatic download on completion
Time: 1-2 minutes
notebooklm create "Analysis: [project]"notebooklm source add ./doc.pdf (or URLs)notebooklm ask "Summarize the key points"notebooklm ask "What are the main arguments?"Time: Varies by source count
notebooklm create "Collection: [name]" # Always try direct addition first
notebooklm source add "https://open-site.com/article" --notebook <id> --json
# Monitor status: if FAILED, then use browser extraction
# For anti-crawler URLs (fallback applied only if direct fails):
# Twitter/X example
notebooklm source add "https://x.com/user/status/123" --notebook <id> --json
# If returns FAILED:
skill_mcp playwright browser_navigate --arguments '{"url": "https://x.com/user/status/123"}'
skill_mcp playwright browser_run_code --arguments '{...extract code...}'
echo "# Extracted content" > /tmp/twitter_content.md
notebooklm source add /tmp/twitter_content.md --notebook <id>
notebooklm source list to verify all sources statusSource limits: Max 50 sources per notebook Supported types: PDFs, YouTube URLs, web URLs, Google Docs, text files
Time: Varies by source count
When adding multiple sources and needing to wait for processing before chat/generation:
--json to capture IDs:
notebooklm source add "https://url1.com" --json # → {"source_id": "abc..."}
notebooklm source add "https://url2.com" --json # → {"source_id": "def..."}
Task(
prompt="Wait for sources {source_ids} in notebook {notebook_id} to be ready.
For each: notebooklm source wait {id} -n {notebook_id} --timeout 120
Report when all ready or if any fail.",
subagent_type="general-purpose"
)
Why wait for sources? Sources must be indexed before chat or generation. Takes 10-60 seconds per source.
Time: 2-5 minutes, runs in background
Time: 5-15 minutes
When working with mixed sources (some direct, some anti-crawler):
notebooklm create "Mixed Collection"notebooklm source add "https://open-site.com/article"
notebooklm source add ./local-document.pdf
# Twitter/X
skill_mcp playwright browser_navigate --arguments '{"url": "https://x.com/user/status/123"}'
skill_mcp playwright browser_run_code --arguments '{"code": "async (page) => { \
await page.waitForTimeout(5000); \
return await page.evaluate(() => document.querySelector('article')?.innerText); \
}"}'
# Save output to /tmp/twitter_extracted.md
# LinkedIn
skill_mcp playwright browser_navigate --arguments '{"url": "https://linkedin.com/post/..."}'
skill_mcp playwright browser_run_code --arguments '{...code for LinkedIn...}'
# Save output to /tmp/linkedin_extracted.md
# Add extracted files
notebooklm source add /tmp/twitter_extracted.md --notebook <id>
notebooklm source add /tmp/linkedin_extracted.md --notebook <id>
notebooklm source list --json until all READYBenefits:
Deep research finds and analyzes web sources on a topic:
notebooklm create "Research: [topic]"notebooklm source add-research "topic query" --mode deep --no-wait
Task(
prompt="Wait for research in notebook {notebook_id} to complete and import sources.
Use: notebooklm research wait -n {notebook_id} --import-all --timeout 300
Report how many sources were imported.",
subagent_type="general-purpose"
)
Alternative (blocking): For simple cases, omit --no-wait:
notebooklm source add-research "topic" --mode deep --import-all
# Blocks for up to 5 minutes
When to use each mode:
--mode fast: Specific topic, quick overview needed (5-10 sources, seconds)--mode deep: Broad topic, comprehensive analysis needed (20+ sources, 2-5 min)Research sources:
--from web: Search the web (default)--from drive: Search Google DriveProgress updates: Brief status for each step
Fire-and-forget for long operations:
artifact waitJSON output: Use --json flag for machine-readable output:
notebooklm list --json
notebooklm source list --json
notebooklm artifact list --json
JSON schemas (key fields):
notebooklm list --json:
{"notebooks": [{"id": "...", "title": "...", "created_at": "..."}]}
notebooklm source list --json:
{"sources": [{"id": "...", "title": "...", "status": "READY|PROCESSING|FAILED"}]}
notebooklm artifact list --json:
{"artifacts": [{"id": "...", "title": "...", "type": "AUDIO_OVERVIEW", "status": "COMPLETED|PENDING|FAILED"}]}
Status values:
PROCESSING → READY (or FAILED)PENDING → COMPLETED (or FAILED)When regular source addition fails:
| Error Type | Detection | Fallback Action | User Notification |
|-----------|-----------|----------------|------------------|
| Anti-crawler (Twitter/X) | URL contains twitter.com, x.com | Browser extraction via Playwright | "⚠️ Required browser extraction (anti-crawler)" |
| Login required | Auth errors from source add | Browser extraction (if possible) or manual copy | "⚠️ Login required - using browser extraction" |
| 403/404 Access Denied | Network error | Browser extraction attempt | "⚠️ Access denied - trying browser method" |
| Timeout | Source processing timeout | Retry or browser extraction | "⏱️ Timeout - retrying..." |
Success cases: | Method | Condition | User Notification | |--------|-----------|-----------------| | Direct URL addition | Most websites, no anti-crawler | "✓ Added via direct URL" | | Browser extraction | Protected platforms only | "⚠️ Required browser extraction (anti-crawler)" |
<!-- Content truncated for initial SEO render. Open the source file tab for the full file. -->Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer