nblm

star 7

Use this skill to query your Google NotebookLM notebooks directly from Claude Code for source-grounded, citation-backed answers from Gemini. Browser automation, library management, persistent auth. Drastically reduced hallucinations through document-only responses.

modbender By modbender schedule Updated 3/6/2026

name: nblm description: Use this skill to query your Google NotebookLM notebooks directly from Claude Code for source-grounded, citation-backed answers from Gemini. Browser automation, library management, persistent auth. Drastically reduced hallucinations through document-only responses.

NotebookLM Quick Commands

Query Google NotebookLM for source-grounded, citation-backed answers.

Environment

All dependencies and authentication are handled automatically by run.py:

  • First run creates .venv and installs Python/Node.js dependencies
  • If Google auth is missing or expired, a browser window opens automatically
  • No manual pre-flight steps required

Usage

/nblm <command> [args]

Commands

Notebook Management

Command Description
login Authenticate with Google
status Show auth and library status
accounts List all Google accounts
accounts add Add a new Google account
accounts switch <id> Switch active account (by index or email)
accounts remove <id> Remove an account
local List notebooks in local library
remote List all notebooks from NotebookLM API
create <name> Create a new notebook
delete [--id ID] Delete a notebook
rename <name> [--id ID] Rename a notebook
summary [--id ID] Get AI-generated summary
describe [--id ID] Get description and suggested topics
add <url-or-id> Add notebook to local library (auto-detects URL vs notebook ID)
activate <id> Set active notebook

Source Management

Command Description
sources [--id ID] List sources in notebook
upload <file> Upload a single file
upload <folder> Sync a folder of files to NotebookLM
upload-zlib <url> Download from Z-Library and upload
upload-url <url> Add URL as source
upload-youtube <url> Add YouTube video as source
upload-text <title> [--content TEXT] Add text as source
source-text <source-id> Get full indexed text
source-guide <source-id> Get AI summary and keywords
source-rename <source-id> <name> Rename a source
source-refresh <source-id> Re-fetch URL content
source-delete <source-id> Delete a source

Upload options:

  • --use-active - Upload to the currently active notebook
  • --create-new - Create a new notebook named after the file/folder
  • --notebook-id <id> - Upload to a specific notebook
  • --dry-run - Show sync plan without executing (folder sync)
  • --rebuild - Force rebuild tracking file (folder sync)

Important: When user runs upload without specifying a target, ASK them first:

"Would you like to upload to the active notebook, or create a new notebook?" Then pass the appropriate flag (--use-active or --create-new).

Chat & Audio/Media

Command Description
ask <question> Query NotebookLM
podcast [--instructions TEXT] Generate audio podcast
podcast-status <task-id> Check podcast generation status
podcast-download [output-path] Download latest podcast
briefing [--instructions TEXT] Generate brief audio summary
debate [--instructions TEXT] Generate debate-style audio
slides [--instructions TEXT] Generate slide deck
slides-download [output-path] Download slide deck as PDF
infographic [--instructions TEXT] Generate infographic
infographic-download [output-path] Download infographic
media-list [--type TYPE] List generated media (audio/video/slides/infographic)
media-delete <id> Delete a generated media item

Command Routing

Based on $ARGUMENTS, execute the appropriate command:

$IF($ARGUMENTS, Parse the command from: "$ARGUMENTS"

loginpython scripts/run.py auth_manager.py setup --service google

accountspython scripts/run.py auth_manager.py accounts list

accounts addpython scripts/run.py auth_manager.py accounts add

accounts switch python scripts/run.py auth_manager.py accounts switch "<id>"

accounts remove python scripts/run.py auth_manager.py accounts remove "<id>"

status → Run both:

  • python scripts/run.py auth_manager.py status
  • python scripts/run.py notebook_manager.py list

localpython scripts/run.py notebook_manager.py list

remotepython scripts/run.py nblm_cli.py notebooks

create python scripts/run.py nblm_cli.py create "<name>"

delete [--id ID]python scripts/run.py nblm_cli.py delete <args>

rename [--id ID]python scripts/run.py nblm_cli.py rename "<name>" <args>

summary [--id ID]python scripts/run.py nblm_cli.py summary <args>

describe [--id ID]python scripts/run.py nblm_cli.py describe <args>

add → Smart add workflow (auto-detects URL vs notebook ID)

activate python scripts/run.py notebook_manager.py activate --id "<id>"

sources [--id ID]python scripts/run.py nblm_cli.py sources <args>

upload → First ASK user: "Upload to active notebook or create new?" Then: - Active: python scripts/run.py source_manager.py add --file "<file>" --use-active - New: python scripts/run.py source_manager.py add --file "<file>" --create-new

upload → Sync a folder: - First ASK user: "Sync to active notebook, create new, or specify notebook?" - Active: python scripts/run.py source_manager.py sync "<folder>" --use-active - New: python scripts/run.py source_manager.py sync "<folder>" --create-new - Specific: python scripts/run.py source_manager.py sync "<folder>" --notebook-id ID - Dry-run: python scripts/run.py source_manager.py sync "<folder>" --dry-run - Rebuild: python scripts/run.py source_manager.py sync "<folder>" --rebuild

upload-zlib → First ASK user: "Upload to active notebook or create new?" Then: - Active: python scripts/run.py source_manager.py add --url "<url>" --use-active - New: python scripts/run.py source_manager.py add --url "<url>" --create-new

upload-url python scripts/run.py nblm_cli.py upload-url "<url>"

upload-youtube python scripts/run.py nblm_cli.py upload-youtube "<url>"

upload-text </strong> → <code>python scripts/run.py nblm_cli.py upload-text "<title>" <args></code></p> <p> <strong>source-text <id></strong> → <code>python scripts/run.py nblm_cli.py source-text "<id>"</code></p> <p> <strong>source-guide <id></strong> → <code>python scripts/run.py nblm_cli.py source-guide "<id>"</code></p> <p> <strong>source-rename <id> <name></strong> → <code>python scripts/run.py nblm_cli.py source-rename "<id>" "<name>"</code></p> <p> <strong>source-refresh <id></strong> → <code>python scripts/run.py nblm_cli.py source-refresh "<id>"</code></p> <p> <strong>source-delete <id></strong> → <code>python scripts/run.py nblm_cli.py source-delete "<id>"</code></p> <p> <strong>ask <question></strong> → <code>python scripts/run.py nblm_cli.py ask "<question>"</code></p> <p> <strong>podcast</strong> → <code>python scripts/run.py artifact_manager.py generate --format DEEP_DIVE <args></code></p> <p> <strong>podcast-status <task-id></strong> → <code>python scripts/run.py artifact_manager.py status --task-id "<task-id>"</code></p> <p> <strong>podcast-download [output-path]</strong> → <code>python scripts/run.py artifact_manager.py download "<output-path>"</code></p> <p> <strong>briefing</strong> → <code>python scripts/run.py artifact_manager.py generate --format BRIEF <args></code></p> <p> <strong>debate</strong> → <code>python scripts/run.py artifact_manager.py generate --format DEBATE <args></code></p> <p> <strong>slides</strong> → <code>python scripts/run.py artifact_manager.py generate-slides <args></code></p> <p> <strong>slides-download [output-path]</strong> → <code>python scripts/run.py artifact_manager.py download "<output-path>" --type slide-deck</code></p> <p> <strong>infographic</strong> → <code>python scripts/run.py artifact_manager.py generate-infographic <args></code></p> <p> <strong>infographic-download [output-path]</strong> → <code>python scripts/run.py artifact_manager.py download "<output-path>" --type infographic</code></p> <p> <strong>media-list [--type TYPE]</strong> → <code>python scripts/run.py artifact_manager.py list <args></code></p> <p> <strong>media-delete <id></strong> → <code>python scripts/run.py artifact_manager.py delete "<id>"</code></p> <p> If command not recognized, show usage help.,</p> <p> Show available commands with <code>/nblm</code> (no arguments) )</p> <h2>Podcast Options</h2> <pre><code>/nblm podcast --length DEFAULT --wait --output ./podcast.mp3 /nblm podcast --instructions "Focus on the key findings" /nblm briefing --wait --output ./summary.mp3 /nblm debate --instructions "Compare the two approaches" </code></pre> <table> <thead> <tr> <th>Option</th> <th>Values</th> </tr> </thead> <tbody><tr> <td><code>--length</code></td> <td><code>SHORT</code>, <code>DEFAULT</code>, <code>LONG</code></td> </tr> <tr> <td><code>--instructions</code></td> <td>Custom instructions for the content</td> </tr> <tr> <td><code>--wait</code></td> <td>Wait for generation to complete</td> </tr> <tr> <td><code>--output</code></td> <td>Download path (requires <code>--wait</code>)</td> </tr> </tbody></table> <h2>Slide Deck Options</h2> <pre><code>/nblm slides --format DETAILED_DECK --wait --output ./presentation.pdf /nblm slides --instructions "Focus on key diagrams" --format PRESENTER_SLIDES </code></pre> <table> <thead> <tr> <th>Option</th> <th>Values</th> </tr> </thead> <tbody><tr> <td><code>--format</code></td> <td><code>DETAILED_DECK</code>, <code>PRESENTER_SLIDES</code></td> </tr> <tr> <td><code>--length</code></td> <td><code>SHORT</code>, <code>DEFAULT</code></td> </tr> <tr> <td><code>--instructions</code></td> <td>Custom instructions for the content</td> </tr> <tr> <td><code>--wait</code></td> <td>Wait for generation to complete</td> </tr> <tr> <td><code>--output</code></td> <td>Download path (requires <code>--wait</code>)</td> </tr> </tbody></table> <h2>Infographic Options</h2> <pre><code>/nblm infographic --orientation LANDSCAPE --wait --output ./visual.png /nblm infographic --instructions "Highlight comparison" --detail-level DETAILED </code></pre> <table> <thead> <tr> <th>Option</th> <th>Values</th> </tr> </thead> <tbody><tr> <td><code>--orientation</code></td> <td><code>LANDSCAPE</code>, <code>PORTRAIT</code>, <code>SQUARE</code></td> </tr> <tr> <td><code>--detail-level</code></td> <td><code>CONCISE</code>, <code>STANDARD</code>, <code>DETAILED</code></td> </tr> <tr> <td><code>--instructions</code></td> <td>Custom instructions for the content</td> </tr> <tr> <td><code>--wait</code></td> <td>Wait for generation to complete</td> </tr> <tr> <td><code>--output</code></td> <td>Download path (requires <code>--wait</code>)</td> </tr> </tbody></table> <h2>Media Generation</h2> <table> <thead> <tr> <th>Command</th> <th>Description</th> <th>Output</th> </tr> </thead> <tbody><tr> <td><code>/nblm podcast</code></td> <td>Deep-dive audio discussion</td> <td>MP3</td> </tr> <tr> <td><code>/nblm briefing</code></td> <td>Brief audio summary</td> <td>MP3</td> </tr> <tr> <td><code>/nblm debate</code></td> <td>Debate-style audio</td> <td>MP3</td> </tr> <tr> <td><code>/nblm slides</code></td> <td>Slide deck presentation</td> <td>PDF</td> </tr> <tr> <td><code>/nblm infographic</code></td> <td>Visual infographic</td> <td>PNG</td> </tr> </tbody></table> <h3>Examples</h3> <pre><code>/nblm podcast --wait --output ./deep-dive.mp3 /nblm briefing --instructions "Focus on chapter 3" --wait /nblm debate --length LONG --wait --output ./debate.mp3 /nblm slides --instructions "Include key diagrams" --format DETAILED_DECK --wait --output ./presentation.pdf /nblm infographic --orientation LANDSCAPE --detail-level DETAILED --wait --output ./summary.png </code></pre> <h3>Download & Manage</h3> <pre><code>/nblm podcast-download ./my-podcast.mp3 /nblm slides-download ./presentation.pdf /nblm infographic-download ./visual.png /nblm media-list # List all generated media /nblm media-list --type audio # List only audio /nblm media-delete <id> # Delete a media item </code></pre> <hr> <h2>Extended Documentation</h2> <h2>When to Use This Skill</h2> <p>Trigger when user:</p> <ul> <li>Mentions NotebookLM explicitly</li> <li>Shares NotebookLM URL (<code>https://notebooklm.google.com/notebook/...</code>)</li> <li>Asks to query their notebooks/documentation</li> <li>Wants to add documentation to NotebookLM library</li> <li>Uses phrases like "ask my NotebookLM", "check my docs", "query my notebook"</li> </ul> <h2>⚠️ CRITICAL: Add Command - Smart Discovery</h2> <p>The add command now <strong>automatically discovers metadata</strong> from the notebook:</p> <pre><code class="language-bash"># Smart Add (auto-discovers name, description, topics) python scripts/run.py notebook_manager.py add <notebook-id-or-url> # With optional overrides python scripts/run.py notebook_manager.py add <id> --name "Custom Name" --topics "custom,topics" </code></pre> <p><strong>What Smart Add does:</strong></p> <ol> <li>Fetches notebook title from NotebookLM API</li> <li>Queries the notebook content to generate description and topics</li> <li>Adds to local library with discovered metadata</li> </ol> <p><strong>Supported input formats:</strong></p> <ul> <li>Notebook ID: <code>5fd9f36b-8000-401d-a7a0-7aa3f7832644</code></li> <li>Full URL: <code>https://notebooklm.google.com/notebook/5fd9f36b-8000-401d-a7a0-7aa3f7832644</code></li> </ul> <p>NEVER manually specify <code>--name</code>, <code>--description</code>, or <code>--topics</code> unless the user explicitly provides them.</p> <h2>Critical: Always Use run.py Wrapper</h2> <p><strong>NEVER call scripts directly. ALWAYS use <code>python scripts/run.py [script]</code>:</strong></p> <pre><code class="language-bash"># ✅ CORRECT - Always use run.py: python scripts/run.py auth_manager.py status python scripts/run.py notebook_manager.py list python scripts/run.py ask_question.py --question "..." # ❌ WRONG - Never call directly: python scripts/auth_manager.py status # Fails without venv! </code></pre> <p>The <code>run.py</code> wrapper automatically:</p> <ol> <li>Creates <code>.venv</code> if needed</li> <li>Installs all dependencies</li> <li>Activates environment</li> <li>Executes script properly</li> </ol> <h2>Core Workflow</h2> <h3>Step 1: Check Authentication Status</h3> <pre><code class="language-bash">python scripts/run.py auth_manager.py status </code></pre> <p>If not authenticated, proceed to setup.</p> <h3>Step 2: Authenticate (One-Time Setup)</h3> <pre><code class="language-bash"># Browser MUST be visible for manual Google login python scripts/run.py auth_manager.py setup </code></pre> <p><strong>Important:</strong></p> <ul> <li>Browser is VISIBLE for authentication</li> <li>Browser window opens automatically</li> <li>User must manually log in to Google</li> <li>Tell user: "A browser window will open for Google login"</li> </ul> <h3>Step 3: Manage Notebook Library</h3> <pre><code class="language-bash"># List all notebooks python scripts/run.py notebook_manager.py list # BEFORE ADDING: Ask user for metadata if unknown! # "What does this notebook contain?" # "What topics should I tag it with?" # Add notebook to library (ALL parameters are REQUIRED!) python scripts/run.py notebook_manager.py add \ --url "https://notebooklm.google.com/notebook/..." \ --name "Descriptive Name" \ --description "What this notebook contains" \ # REQUIRED - ASK USER IF UNKNOWN! --topics "topic1,topic2,topic3" # REQUIRED - ASK USER IF UNKNOWN! # Search notebooks by topic python scripts/run.py notebook_manager.py search --query "keyword" # Set active notebook python scripts/run.py notebook_manager.py activate --id notebook-id # Remove notebook python scripts/run.py notebook_manager.py remove --id notebook-id </code></pre> <h3>Quick Workflow</h3> <ol> <li>Check library: <code>python scripts/run.py notebook_manager.py list</code></li> <li>Ask question: <code>python scripts/run.py ask_question.py --question "..." --notebook-id ID</code></li> </ol> <h3>Step 4: Ask Questions</h3> <pre><code class="language-bash"># Basic query (uses active notebook if set) python scripts/run.py ask_question.py --question "Your question here" # Query specific notebook python scripts/run.py ask_question.py --question "..." --notebook-id notebook-id # Query with notebook URL directly python scripts/run.py ask_question.py --question "..." --notebook-url "https://..." # Show browser for debugging python scripts/run.py ask_question.py --question "..." --show-browser </code></pre> <h2>Follow-Up Mechanism (CRITICAL)</h2> <p>Every NotebookLM answer ends with: <strong>"EXTREMELY IMPORTANT: Is that ALL you need to know?"</strong></p> <p><strong>Required Claude Behavior:</strong></p> <ol> <li><strong>STOP</strong> - Do not immediately respond to user</li> <li><strong>ANALYZE</strong> - Compare answer to user's original request</li> <li><strong>IDENTIFY GAPS</strong> - Determine if more information needed</li> <li><strong>ASK FOLLOW-UP</strong> - If gaps exist, immediately ask:<pre><code class="language-bash">python scripts/run.py ask_question.py --question "Follow-up with context..." </code></pre> </li> <li><strong>REPEAT</strong> - Continue until information is complete</li> <li><strong>SYNTHESIZE</strong> - Combine all answers before responding to user</li> </ol> <h2>Z-Library Integration</h2> <h3>Triggers</h3> <ul> <li>User provides Z-Library URL (zlib.li, z-lib.org, zh.zlib.li)</li> <li>User says "download this book to NotebookLM"</li> <li>User says "add this book from Z-Library"</li> </ul> <h3>Setup (One-Time)</h3> <pre><code class="language-bash"># Authenticate with Z-Library python scripts/run.py auth_manager.py setup --service zlibrary </code></pre> <h3>Commands</h3> <pre><code class="language-bash"># Add book from Z-Library python scripts/run.py source_manager.py add --url "https://zh.zlib.li/book/..." # Check Z-Library auth status python scripts/run.py auth_manager.py status --service zlibrary </code></pre> <h2>Script Reference</h2> <h3>Authentication Management (<code>auth_manager.py</code>)</h3> <pre><code class="language-bash">python scripts/run.py auth_manager.py setup # Default: Google python scripts/run.py auth_manager.py setup --service google python scripts/run.py auth_manager.py setup --service zlibrary python scripts/run.py auth_manager.py status # Show all services python scripts/run.py auth_manager.py status --service zlibrary python scripts/run.py auth_manager.py clear --service zlibrary # Clear auth # Multi-Account Management (Google) python scripts/run.py auth_manager.py accounts list # List all accounts python scripts/run.py auth_manager.py accounts add # Add new account python scripts/run.py auth_manager.py accounts switch 1 # Switch by index python scripts/run.py auth_manager.py accounts switch user@gmail.com # Switch by email python scripts/run.py auth_manager.py accounts remove 2 # Remove account </code></pre> <h3>Notebook Management (<code>notebook_manager.py</code>)</h3> <pre><code class="language-bash">python scripts/run.py notebook_manager.py add --url URL --name NAME --description DESC --topics TOPICS # OR use notebook ID directly: python scripts/run.py notebook_manager.py add --notebook-id ID --name NAME --description DESC --topics TOPICS python scripts/run.py notebook_manager.py list python scripts/run.py notebook_manager.py search --query QUERY python scripts/run.py notebook_manager.py activate --id ID python scripts/run.py notebook_manager.py remove --id ID python scripts/run.py notebook_manager.py stats </code></pre> <h3>Question Interface (<code>ask_question.py</code>)</h3> <pre><code class="language-bash">python scripts/run.py ask_question.py --question "..." [--notebook-id ID] [--notebook-url URL] [--show-browser] </code></pre> <h3>Source Manager (<code>source_manager.py</code>)</h3> <pre><code class="language-bash"># Upload to active notebook python scripts/run.py source_manager.py add --file "/path/to/book.pdf" --use-active # Create new notebook for upload python scripts/run.py source_manager.py add --file "/path/to/book.pdf" --create-new # Upload to specific notebook python scripts/run.py source_manager.py add --file "/path/to/book.pdf" --notebook-id NOTEBOOK_ID # Z-Library download and upload python scripts/run.py source_manager.py add --url "https://zh.zlib.li/book/..." --use-active python scripts/run.py source_manager.py add --url "https://zh.zlib.li/book/..." --create-new # Sync a folder (new!) python scripts/run.py source_manager.py sync "/path/to/docs" --use-active python scripts/run.py source_manager.py sync "/path/to/docs" --create-new python scripts/run.py source_manager.py sync "/path/to/docs" --notebook-id NOTEBOOK_ID # Sync options (new!) python scripts/run.py source_manager.py sync "/path/to/docs" --dry-run # Preview only python scripts/run.py source_manager.py sync "/path/to/docs" --rebuild # Force re-hash all files </code></pre> <p><strong>Folder Sync:</strong></p> <ul> <li>Scans folder for supported types: PDF, TXT, MD, DOCX, HTML, EPUB</li> <li>Tracks sync state internally (no per-folder tracking file to manage)</li> <li>Sync strategy: add new, update modified (delete + re-upload), skip unchanged</li> <li>Multi-account aware (tracks which Google account was used) <strong>Note:</strong> One of <code>--use-active</code>, <code>--create-new</code>, or <code>--notebook-id</code> is REQUIRED. Uploads wait for NotebookLM processing and print progress as <code>Ready: N/T</code>. Press Ctrl+C to stop waiting. Local file uploads use browser automation and require Google authentication. If browser automation is unavailable, set <code>NOTEBOOKLM_UPLOAD_MODE=text</code> to upload extracted text instead (PDFs require <code>pypdf</code>).</li> </ul> <h3>Data Cleanup (<code>cleanup_manager.py</code>)</h3> <pre><code class="language-bash">python scripts/run.py cleanup_manager.py # Preview cleanup python scripts/run.py cleanup_manager.py --confirm # Execute cleanup python scripts/run.py cleanup_manager.py --preserve-library # Keep notebooks </code></pre> <h3>Watchdog Status (<code>auth_manager.py</code>)</h3> <pre><code class="language-bash">python scripts/run.py auth_manager.py watchdog-status </code></pre> <h2>Environment Management</h2> <p>The virtual environment is automatically managed:</p> <ul> <li>First run creates <code>.venv</code> automatically</li> <li>Dependencies install automatically</li> <li>Node.js dependencies install automatically</li> <li>agent-browser daemon starts on demand and keeps browser state in memory</li> <li>daemon stops after 10 minutes of inactivity (any agent-browser command resets the timer)</li> <li>set <code>AGENT_BROWSER_OWNER_PID</code> to auto-stop when the agent process exits</li> <li><code>scripts/run.py</code> sets <code>AGENT_BROWSER_OWNER_PID</code> to its parent PID by default</li> <li>Everything isolated in skill directory</li> </ul> <p>Manual setup (only if automatic fails):</p> <pre><code class="language-bash">python -m venv .venv source .venv/bin/activate # Linux/Mac pip install -r requirements.txt npm install npm run install-browsers </code></pre> <h2>Data Storage</h2> <p>All data stored in <code>~/.claude/skills/notebooklm/data/</code>:</p> <ul> <li><code>library.json</code> - Notebook metadata (with account associations)</li> <li><code>auth/google/</code> - Multi-account Google auth<ul> <li><code>index.json</code> - Account index (active account, list)</li> <li><code><n>-<email>.json</code> - Per-account credentials</li> </ul> </li> <li><code>auth/zlibrary.json</code> - Z-Library auth state</li> <li><code>agent_browser/session_id</code> - Current daemon session ID</li> <li><code>agent_browser/last_activity.json</code> - Last activity timestamp for idle shutdown</li> <li><code>agent_browser/watchdog.pid</code> - Idle watchdog process ID</li> </ul> <p><strong>Security:</strong> Protected by <code>.gitignore</code>, never commit to git.</p> <h2>Configuration</h2> <p>Optional <code>.env</code> file in skill directory:</p> <pre><code class="language-env">HEADLESS=false # Browser visibility SHOW_BROWSER=false # Default browser display STEALTH_ENABLED=true # Human-like behavior TYPING_WPM_MIN=160 # Typing speed TYPING_WPM_MAX=240 DEFAULT_NOTEBOOK_ID= # Default notebook </code></pre> <h2>Decision Flow</h2> <pre><code>User mentions NotebookLM ↓ Check auth → python scripts/run.py auth_manager.py status ↓ If not authenticated → python scripts/run.py auth_manager.py setup ↓ Check/Add notebook → python scripts/run.py notebook_manager.py list/add (with --description) ↓ Activate notebook → python scripts/run.py notebook_manager.py activate --id ID ↓ Ask question → python scripts/run.py ask_question.py --question "..." ↓ See "Is that ALL you need?" → Ask follow-ups until complete ↓ Synthesize and respond to user </code></pre> <h2>Troubleshooting</h2> <table> <thead> <tr> <th>Problem</th> <th>Solution</th> </tr> </thead> <tbody><tr> <td>ModuleNotFoundError</td> <td>Use <code>run.py</code> wrapper</td> </tr> <tr> <td>Authentication fails</td> <td>Browser must be visible for setup! --show-browser</td> </tr> <tr> <td>DAEMON_UNAVAILABLE</td> <td>Ensure Node.js/npm installed, run <code>npm install</code>, retry</td> </tr> <tr> <td>AUTH_REQUIRED</td> <td>Run <code>python scripts/run.py auth_manager.py setup</code></td> </tr> <tr> <td>ELEMENT_NOT_FOUND</td> <td>Verify notebook URL and re-run with fresh page load</td> </tr> <tr> <td>Rate limit (50/day)</td> <td>Wait or add another Google account with <code>accounts add</code></td> </tr> <tr> <td>Browser crashes</td> <td><code>python scripts/run.py cleanup_manager.py --preserve-library</code></td> </tr> <tr> <td>Notebook not found</td> <td>Check with <code>notebook_manager.py list</code></td> </tr> </tbody></table> <h2>Best Practices</h2> <ol> <li><strong>Always use run.py</strong> - Handles environment automatically</li> <li><strong>Check auth first</strong> - Before any operations</li> <li><strong>Follow-up questions</strong> - Don't stop at first answer</li> <li><strong>Browser visible for auth</strong> - Required for manual login</li> <li><strong>Include context</strong> - Each question is independent</li> <li><strong>Synthesize answers</strong> - Combine multiple responses</li> </ol> <h2>Limitations</h2> <ul> <li>No session persistence (each question = new browser)</li> <li>Rate limits on free Google accounts (50 queries/day per account; use multiple accounts to increase)</li> <li>Manual upload required (user must add docs to NotebookLM)</li> <li>Browser overhead (few seconds per question)</li> </ul> <h2>Resources (Skill Structure)</h2> <p><strong>Important directories and files:</strong></p> <ul> <li><code>scripts/</code> - All automation scripts (ask_question.py, notebook_manager.py, etc.)</li> <li><code>data/</code> - Local storage for authentication and notebook library</li> <li><code>references/</code> - Extended documentation:<ul> <li><code>api_reference.md</code> - Detailed API documentation for all scripts</li> <li><code>troubleshooting.md</code> - Common issues and solutions</li> <li><code>usage_patterns.md</code> - Best practices and workflow examples</li> </ul> </li> <li><code>.venv/</code> - Isolated Python environment (auto-created on first run)</li> <li><code>.gitignore</code> - Protects sensitive data from being committed</li> </ul> </article> </div> <!-- Right: Metadata & Command Sidebar --> <div class="w-full lg:w-80 shrink-0 flex flex-col gap-6" data-astro-cid-7zzsworf> <!-- Install Card --> <div class="p-6 rounded-xl bg-surface-container border border-border/80 flex flex-col gap-4 shadow-sm" data-astro-cid-7zzsworf> <span class="text-xs font-bold uppercase tracking-widest text-on-surface-variant/60 font-mono" data-astro-cid-7zzsworf>Install via CLI</span> <div class="flex flex-col gap-2" data-astro-cid-7zzsworf> <div id="detail-install-cmd" class="font-mono text-[11px] p-3 rounded-lg bg-black/40 border border-border select-all break-all text-primary font-bold leading-relaxed" data-astro-cid-7zzsworf> npx skills add https://github.com/modbender/skill-library-mcp --skill nblm </div> <button id="detail-copy-btn" class="w-full py-2.5 rounded-lg bg-primary hover:bg-primary-hover text-on-primary font-sans font-bold text-sm shadow transition-all active:scale-95 flex items-center justify-center gap-1.5" data-astro-cid-7zzsworf> <span class="material-symbols-outlined text-[16px]" data-astro-cid-7zzsworf>content_copy</span> <span data-astro-cid-7zzsworf>Copy Command</span> </button> </div> </div> <!-- Details & Stats Card --> <div class="p-6 rounded-xl bg-surface-container border border-border/80 flex flex-col gap-4 shadow-sm text-on-surface" data-astro-cid-7zzsworf> <span class="text-xs font-bold uppercase tracking-widest text-on-surface-variant/60 font-sans" data-astro-cid-7zzsworf>Repository Details</span> <div class="flex flex-col gap-3.5" data-astro-cid-7zzsworf> <div class="flex justify-between items-center text-sm" data-astro-cid-7zzsworf> <span class="text-on-surface-variant/70 flex items-center gap-1.5" data-astro-cid-7zzsworf> <span class="material-symbols-outlined text-[16px] text-on-surface-variant/60" data-astro-cid-7zzsworf>star</span> Stars </span> <span class="font-mono font-bold text-on-surface" data-astro-cid-7zzsworf>7</span> </div> <div class="flex justify-between items-center text-sm" data-astro-cid-7zzsworf> <span class="text-on-surface-variant/70 flex items-center gap-1.5" data-astro-cid-7zzsworf> <span class="material-symbols-outlined text-[16px] text-on-surface-variant/60" data-astro-cid-7zzsworf>call_split</span> Forks </span> <span class="font-mono font-bold text-on-surface" data-astro-cid-7zzsworf>2</span> </div> <div class="flex justify-between items-center text-sm" data-astro-cid-7zzsworf> <span class="text-on-surface-variant/70 flex items-center gap-1.5" data-astro-cid-7zzsworf> <span class="material-symbols-outlined text-[16px] text-on-surface-variant/60" data-astro-cid-7zzsworf>navigation</span> Branch </span> <span class="font-mono bg-surface border border-border px-2 py-0.5 rounded text-[11px] text-on-surface-variant" data-astro-cid-7zzsworf>main</span> </div> <div class="flex justify-between items-start text-sm" data-astro-cid-7zzsworf> <span class="text-on-surface-variant/70 flex items-center gap-1.5 mt-0.5" data-astro-cid-7zzsworf> <span class="material-symbols-outlined text-[16px] text-on-surface-variant/60" data-astro-cid-7zzsworf>article</span> Path </span> <span class="font-mono bg-surface border border-border px-2 py-0.5 rounded text-[11px] text-on-surface-variant truncate max-w-[150px]" title="SKILL.md" data-astro-cid-7zzsworf>SKILL.md</span> </div> </div> </div> <!-- Occupations Tag Card --> <!-- Related Creators Card --> <div class="p-6 rounded-xl bg-surface-container border border-border/80 flex flex-col gap-3 shadow-sm" data-astro-cid-7zzsworf> <span class="text-xs font-bold uppercase tracking-widest text-on-surface-variant/60 font-sans" data-astro-cid-7zzsworf>More from Creator</span> <div class="flex items-center gap-2" data-astro-cid-7zzsworf> <img class="w-8 h-8 rounded-full border border-border" src="https://avatars.githubusercontent.com/u/25717245?u=c449344d64a2761389dd157ff45ee109895a35bb&v=4" alt="modbender" onerror="this.src='https://avatars.githubusercontent.com/u/9919?v=4'" data-astro-cid-7zzsworf> <div class="flex flex-col min-w-0" data-astro-cid-7zzsworf> <span class="font-bold text-sm truncate text-on-surface" data-astro-cid-7zzsworf>modbender</span> <a href="/?creator=modbender" class="text-xs text-primary hover:underline font-semibold transition-all" data-astro-cid-7zzsworf>Explore all skills →</a> </div> </div> </div> </div> </div> </div> </div> <script> const copyBtn = document.getElementById("detail-copy-btn"); const installCmd = document.getElementById("detail-install-cmd"); if (copyBtn && installCmd) { copyBtn.addEventListener("click", () => { const cmd = installCmd.textContent.trim(); navigator.clipboard.writeText(cmd).then(() => { const originalText = copyBtn.innerHTML; copyBtn.innerHTML = ` <span class="material-symbols-outlined text-[16px]">check</span> <span>Copied!</span> `; copyBtn.style.background = "#10b981"; copyBtn.style.borderColor = "#10b981"; setTimeout(() => { copyBtn.innerHTML = originalText; copyBtn.style.background = ""; copyBtn.style.borderColor = ""; }, 1500); }); }); } </script> </div> <!-- Footer --> <footer class="border-t border-border bg-surface-container-low text-on-surface-variant py-8 px-gutter mt-16 rounded-xl"> <div class="max-w-container-max mx-auto flex flex-col md:flex-row justify-between items-center gap-6"> <div class="flex items-center gap-2"> <div class="w-6 h-6 rounded bg-primary bg-opacity-20 flex items-center justify-center"> <span class="material-symbols-outlined text-primary text-sm">code_blocks</span> </div> <span class="font-bold text-on-surface text-sm">SkillMD</span> </div> <div class="flex flex-wrap justify-center gap-6 text-sm"> <a href="/about" class="hover:text-primary transition-colors">About Us</a> <a href="/contact" class="hover:text-primary transition-colors">Contact Us</a> <a href="/privacy" class="hover:text-primary transition-colors">Privacy Policy</a> <a href="/terms" class="hover:text-primary transition-colors">Terms of Service</a> <a href="/support" class="hover:text-primary transition-colors">Support</a> </div> <div class="text-xs text-on-surface-variant/80"> © 2026 SkillMD. All rights reserved. </div> </div> </footer> </main> <!-- Script for Theme Toggle, Mobile Menu, and Sidebar Filter Redirection --> <script> // Theme setup const savedTheme = localStorage.getItem("theme") || "dark"; function applyTheme(theme) { document.documentElement.classList.remove("dark", "green", "dracula", "nord"); if (theme === "dark") { document.documentElement.classList.add("dark"); } else if (theme === "green") { document.documentElement.classList.add("dark", "green"); } else if (theme === "dracula") { document.documentElement.classList.add("dark", "dracula"); } else if (theme === "nord") { document.documentElement.classList.add("dark", "nord"); } document.documentElement.setAttribute("data-theme", theme); const themeMoon = document.getElementById("theme-moon"); const themeSun = document.getElementById("theme-sun"); const themeLeaf = document.getElementById("theme-leaf"); const themeDracula = document.getElementById("theme-dracula"); const themeNord = document.getElementById("theme-nord"); if (themeMoon && themeSun && themeLeaf && themeDracula && themeNord) { themeMoon.style.display = theme === "dark" ? "inline" : "none"; themeSun.style.display = theme === "light" ? "inline" : "none"; themeLeaf.style.display = theme === "green" ? "inline" : "none"; themeDracula.style.display = theme === "dracula" ? "inline" : "none"; themeNord.style.display = theme === "nord" ? "inline" : "none"; } } applyTheme(savedTheme); const themeToggleBtn = document.getElementById("theme-toggle-btn"); if (themeToggleBtn) { themeToggleBtn.addEventListener("click", () => { const currentTheme = document.documentElement.getAttribute("data-theme") || "dark"; let newTheme = "dark"; if (currentTheme === "dark") { newTheme = "light"; } else if (currentTheme === "light") { newTheme = "green"; } else if (currentTheme === "green") { newTheme = "dracula"; } else if (currentTheme === "dracula") { newTheme = "nord"; } else { newTheme = "dark"; } applyTheme(newTheme); localStorage.setItem("theme", newTheme); }); } // Mobile menu toggle and sidebar logic const mobileMenuToggle = document.getElementById("mobile-menu-toggle"); const sidebarMenu = document.getElementById("sidebar-menu"); const sidebarOverlay = document.getElementById("sidebar-overlay"); function isMobile() { return window.innerWidth < 768; // 768px is the 'md' breakpoint in Tailwind } function openSidebar() { if (sidebarMenu) { sidebarMenu.classList.remove("-translate-x-full"); } if (sidebarOverlay) { sidebarOverlay.classList.remove("hidden"); } } function closeSidebar() { if (sidebarMenu && isMobile()) { sidebarMenu.classList.add("-translate-x-full"); } if (sidebarOverlay) { sidebarOverlay.classList.add("hidden"); } } if (mobileMenuToggle && sidebarMenu) { mobileMenuToggle.addEventListener("click", (e) => { e.stopPropagation(); if (isMobile()) { const isClosed = sidebarMenu.classList.contains("-translate-x-full"); if (isClosed) { openSidebar(); } else { closeSidebar(); } } }); document.addEventListener("click", (e) => { if (isMobile()) { if (!sidebarMenu.contains(e.target) && !mobileMenuToggle.contains(e.target)) { closeSidebar(); } } }); if (sidebarOverlay) { sidebarOverlay.addEventListener("click", () => { if (isMobile()) { closeSidebar(); } }); } // Collapse sidebar when clicking a filter button, creator button, or nav item inside it sidebarMenu.addEventListener("click", (e) => { if (isMobile()) { const clickTarget = e.target.closest("button, a"); if (clickTarget) { closeSidebar(); } } }); // Sync sidebar state on window resize window.addEventListener("resize", () => { if (!isMobile()) { // Desktop: sidebar should be visible, no overlay if (sidebarMenu) { sidebarMenu.classList.remove("-translate-x-full"); } if (sidebarOverlay) { sidebarOverlay.classList.add("hidden"); } } else { // Mobile: start collapsed if (sidebarMenu) { sidebarMenu.classList.add("-translate-x-full"); } if (sidebarOverlay) { sidebarOverlay.classList.add("hidden"); } } }); } // If not on homepage, redirect on sidebar filter click const isHomepage = window.location.pathname === "/"; document.querySelectorAll("#occupation-filters .filter-btn").forEach(btn => { btn.addEventListener("click", (e) => { const occ = e.currentTarget.getAttribute("data-occupation"); if (!isHomepage) { window.location.href = occ ? `/?occupation=${encodeURIComponent(occ)}` : "/"; } }); }); document.querySelectorAll("#creator-filters .creator-btn").forEach(btn => { btn.addEventListener("click", (e) => { const creator = e.currentTarget.getAttribute("data-creator"); if (!isHomepage) { window.location.href = `/?creator=${encodeURIComponent(creator)}`; } }); }); </script> </body> </html>