🧪 Skills

NotebookLM Skill

Use this skill to query your Google NotebookLM notebooks directly from Claude Code for source-grounded, citation-backed answers from Gemini. Browser automati...

v1.0.1
❤️ 3
⬇️ 1.2k
👁 1
Share

Description


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> <h1>Extended Documentation</h1> <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 tabindex="0" style="color:#f8f8f2;background-color:#272822;"><code><span style="display:flex;"><span><span style="color:#75715e"># Smart Add (auto-discovers name, description, topics)</span> </span></span><span style="display:flex;"><span>python scripts/run.py notebook_manager.py add <notebook-id-or-url> </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span><span style="color:#75715e"># With optional overrides</span> </span></span><span style="display:flex;"><span>python scripts/run.py notebook_manager.py add <id> --name <span style="color:#e6db74">"Custom Name"</span> --topics <span style="color:#e6db74">"custom,topics"</span> </span></span></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 tabindex="0" style="color:#f8f8f2;background-color:#272822;"><code><span style="display:flex;"><span><span style="color:#75715e"># ✅ CORRECT - Always use run.py:</span> </span></span><span style="display:flex;"><span>python scripts/run.py auth_manager.py status </span></span><span style="display:flex;"><span>python scripts/run.py notebook_manager.py list </span></span><span style="display:flex;"><span>python scripts/run.py ask_question.py --question <span style="color:#e6db74">"..."</span> </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span><span style="color:#75715e"># ❌ WRONG - Never call directly:</span> </span></span><span style="display:flex;"><span>python scripts/auth_manager.py status <span style="color:#75715e"># Fails without venv!</span> </span></span></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 tabindex="0" style="color:#f8f8f2;background-color:#272822;"><code><span style="display:flex;"><span>python scripts/run.py auth_manager.py status </span></span></code></pre><p>If not authenticated, proceed to setup.</p> <h3>Step 2: Authenticate (One-Time Setup)</h3> <pre tabindex="0" style="color:#f8f8f2;background-color:#272822;"><code><span style="display:flex;"><span><span style="color:#75715e"># Browser MUST be visible for manual Google login</span> </span></span><span style="display:flex;"><span>python scripts/run.py auth_manager.py setup </span></span></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 tabindex="0" style="color:#f8f8f2;background-color:#272822;"><code><span style="display:flex;"><span><span style="color:#75715e"># List all notebooks</span> </span></span><span style="display:flex;"><span>python scripts/run.py notebook_manager.py list </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span><span style="color:#75715e"># BEFORE ADDING: Ask user for metadata if unknown!</span> </span></span><span style="display:flex;"><span><span style="color:#75715e"># "What does this notebook contain?"</span> </span></span><span style="display:flex;"><span><span style="color:#75715e"># "What topics should I tag it with?"</span> </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span><span style="color:#75715e"># Add notebook to library (ALL parameters are REQUIRED!)</span> </span></span><span style="display:flex;"><span>python scripts/run.py notebook_manager.py add <span style="color:#ae81ff">\ </span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span> --url <span style="color:#e6db74">"https://notebooklm.google.com/notebook/..."</span> <span style="color:#ae81ff">\ </span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span> --name <span style="color:#e6db74">"Descriptive Name"</span> <span style="color:#ae81ff">\ </span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span> --description <span style="color:#e6db74">"What this notebook contains"</span> <span style="color:#ae81ff">\ </span> <span style="color:#75715e"># REQUIRED - ASK USER IF UNKNOWN!</span> </span></span><span style="display:flex;"><span> --topics <span style="color:#e6db74">"topic1,topic2,topic3"</span> <span style="color:#75715e"># REQUIRED - ASK USER IF UNKNOWN!</span> </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span><span style="color:#75715e"># Search notebooks by topic</span> </span></span><span style="display:flex;"><span>python scripts/run.py notebook_manager.py search --query <span style="color:#e6db74">"keyword"</span> </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span><span style="color:#75715e"># Set active notebook</span> </span></span><span style="display:flex;"><span>python scripts/run.py notebook_manager.py activate --id notebook-id </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span><span style="color:#75715e"># Remove notebook</span> </span></span><span style="display:flex;"><span>python scripts/run.py notebook_manager.py remove --id notebook-id </span></span></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 tabindex="0" style="color:#f8f8f2;background-color:#272822;"><code><span style="display:flex;"><span><span style="color:#75715e"># Basic query (uses active notebook if set)</span> </span></span><span style="display:flex;"><span>python scripts/run.py ask_question.py --question <span style="color:#e6db74">"Your question here"</span> </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span><span style="color:#75715e"># Query specific notebook</span> </span></span><span style="display:flex;"><span>python scripts/run.py ask_question.py --question <span style="color:#e6db74">"..."</span> --notebook-id notebook-id </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span><span style="color:#75715e"># Query with notebook URL directly</span> </span></span><span style="display:flex;"><span>python scripts/run.py ask_question.py --question <span style="color:#e6db74">"..."</span> --notebook-url <span style="color:#e6db74">"https://..."</span> </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span><span style="color:#75715e"># Show browser for debugging</span> </span></span><span style="display:flex;"><span>python scripts/run.py ask_question.py --question <span style="color:#e6db74">"..."</span> --show-browser </span></span></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 tabindex="0" style="color:#f8f8f2;background-color:#272822;"><code><span style="display:flex;"><span>python scripts/run.py ask_question.py --question <span style="color:#e6db74">"Follow-up with context..."</span> </span></span></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 tabindex="0" style="color:#f8f8f2;background-color:#272822;"><code><span style="display:flex;"><span><span style="color:#75715e"># Authenticate with Z-Library</span> </span></span><span style="display:flex;"><span>python scripts/run.py auth_manager.py setup --service zlibrary </span></span></code></pre><h3>Commands</h3> <pre tabindex="0" style="color:#f8f8f2;background-color:#272822;"><code><span style="display:flex;"><span><span style="color:#75715e"># Add book from Z-Library</span> </span></span><span style="display:flex;"><span>python scripts/run.py source_manager.py add --url <span style="color:#e6db74">"https://zh.zlib.li/book/..."</span> </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span><span style="color:#75715e"># Check Z-Library auth status</span> </span></span><span style="display:flex;"><span>python scripts/run.py auth_manager.py status --service zlibrary </span></span></code></pre><h2>Script Reference</h2> <h3>Authentication Management (<code>auth_manager.py</code>)</h3> <pre tabindex="0" style="color:#f8f8f2;background-color:#272822;"><code><span style="display:flex;"><span>python scripts/run.py auth_manager.py setup <span style="color:#75715e"># Default: Google</span> </span></span><span style="display:flex;"><span>python scripts/run.py auth_manager.py setup --service google </span></span><span style="display:flex;"><span>python scripts/run.py auth_manager.py setup --service zlibrary </span></span><span style="display:flex;"><span>python scripts/run.py auth_manager.py status <span style="color:#75715e"># Show all services</span> </span></span><span style="display:flex;"><span>python scripts/run.py auth_manager.py status --service zlibrary </span></span><span style="display:flex;"><span>python scripts/run.py auth_manager.py clear --service zlibrary <span style="color:#75715e"># Clear auth</span> </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span><span style="color:#75715e"># Multi-Account Management (Google)</span> </span></span><span style="display:flex;"><span>python scripts/run.py auth_manager.py accounts list <span style="color:#75715e"># List all accounts</span> </span></span><span style="display:flex;"><span>python scripts/run.py auth_manager.py accounts add <span style="color:#75715e"># Add new account</span> </span></span><span style="display:flex;"><span>python scripts/run.py auth_manager.py accounts switch <span style="color:#ae81ff">1</span> <span style="color:#75715e"># Switch by index</span> </span></span><span style="display:flex;"><span>python scripts/run.py auth_manager.py accounts switch user@gmail.com <span style="color:#75715e"># Switch by email</span> </span></span><span style="display:flex;"><span>python scripts/run.py auth_manager.py accounts remove <span style="color:#ae81ff">2</span> <span style="color:#75715e"># Remove account</span> </span></span></code></pre><h3>Notebook Management (<code>notebook_manager.py</code>)</h3> <pre tabindex="0" style="color:#f8f8f2;background-color:#272822;"><code><span style="display:flex;"><span>python scripts/run.py notebook_manager.py add --url URL --name NAME --description DESC --topics TOPICS </span></span><span style="display:flex;"><span><span style="color:#75715e"># OR use notebook ID directly:</span> </span></span><span style="display:flex;"><span>python scripts/run.py notebook_manager.py add --notebook-id ID --name NAME --description DESC --topics TOPICS </span></span><span style="display:flex;"><span>python scripts/run.py notebook_manager.py list </span></span><span style="display:flex;"><span>python scripts/run.py notebook_manager.py search --query QUERY </span></span><span style="display:flex;"><span>python scripts/run.py notebook_manager.py activate --id ID </span></span><span style="display:flex;"><span>python scripts/run.py notebook_manager.py remove --id ID </span></span><span style="display:flex;"><span>python scripts/run.py notebook_manager.py stats </span></span></code></pre><h3>Question Interface (<code>ask_question.py</code>)</h3> <pre tabindex="0" style="color:#f8f8f2;background-color:#272822;"><code><span style="display:flex;"><span>python scripts/run.py ask_question.py --question <span style="color:#e6db74">"..."</span> <span style="color:#f92672">[</span>--notebook-id ID<span style="color:#f92672">]</span> <span style="color:#f92672">[</span>--notebook-url URL<span style="color:#f92672">]</span> <span style="color:#f92672">[</span>--show-browser<span style="color:#f92672">]</span> </span></span></code></pre><h3>Source Manager (<code>source_manager.py</code>)</h3> <pre tabindex="0" style="color:#f8f8f2;background-color:#272822;"><code><span style="display:flex;"><span><span style="color:#75715e"># Upload to active notebook</span> </span></span><span style="display:flex;"><span>python scripts/run.py source_manager.py add --file <span style="color:#e6db74">"/path/to/book.pdf"</span> --use-active </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span><span style="color:#75715e"># Create new notebook for upload</span> </span></span><span style="display:flex;"><span>python scripts/run.py source_manager.py add --file <span style="color:#e6db74">"/path/to/book.pdf"</span> --create-new </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span><span style="color:#75715e"># Upload to specific notebook</span> </span></span><span style="display:flex;"><span>python scripts/run.py source_manager.py add --file <span style="color:#e6db74">"/path/to/book.pdf"</span> --notebook-id NOTEBOOK_ID </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span><span style="color:#75715e"># Z-Library download and upload</span> </span></span><span style="display:flex;"><span>python scripts/run.py source_manager.py add --url <span style="color:#e6db74">"https://zh.zlib.li/book/..."</span> --use-active </span></span><span style="display:flex;"><span>python scripts/run.py source_manager.py add --url <span style="color:#e6db74">"https://zh.zlib.li/book/..."</span> --create-new </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span><span style="color:#75715e"># Sync a folder (new!)</span> </span></span><span style="display:flex;"><span>python scripts/run.py source_manager.py sync <span style="color:#e6db74">"/path/to/docs"</span> --use-active </span></span><span style="display:flex;"><span>python scripts/run.py source_manager.py sync <span style="color:#e6db74">"/path/to/docs"</span> --create-new </span></span><span style="display:flex;"><span>python scripts/run.py source_manager.py sync <span style="color:#e6db74">"/path/to/docs"</span> --notebook-id NOTEBOOK_ID </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span><span style="color:#75715e"># Sync options (new!)</span> </span></span><span style="display:flex;"><span>python scripts/run.py source_manager.py sync <span style="color:#e6db74">"/path/to/docs"</span> --dry-run <span style="color:#75715e"># Preview only</span> </span></span><span style="display:flex;"><span>python scripts/run.py source_manager.py sync <span style="color:#e6db74">"/path/to/docs"</span> --rebuild <span style="color:#75715e"># Force re-hash all files</span> </span></span></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 tabindex="0" style="color:#f8f8f2;background-color:#272822;"><code><span style="display:flex;"><span>python scripts/run.py cleanup_manager.py <span style="color:#75715e"># Preview cleanup</span> </span></span><span style="display:flex;"><span>python scripts/run.py cleanup_manager.py --confirm <span style="color:#75715e"># Execute cleanup</span> </span></span><span style="display:flex;"><span>python scripts/run.py cleanup_manager.py --preserve-library <span style="color:#75715e"># Keep notebooks</span> </span></span></code></pre><h3>Watchdog Status (<code>auth_manager.py</code>)</h3> <pre tabindex="0" style="color:#f8f8f2;background-color:#272822;"><code><span style="display:flex;"><span>python scripts/run.py auth_manager.py watchdog-status </span></span></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 tabindex="0" style="color:#f8f8f2;background-color:#272822;"><code><span style="display:flex;"><span>python -m venv .venv </span></span><span style="display:flex;"><span>source .venv/bin/activate <span style="color:#75715e"># Linux/Mac</span> </span></span><span style="display:flex;"><span>pip install -r requirements.txt </span></span><span style="display:flex;"><span>npm install </span></span><span style="display:flex;"><span>npm run install-browsers </span></span></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 tabindex="0" style="color:#f8f8f2;background-color:#272822;"><code><span style="display:flex;"><span>HEADLESS<span style="color:#f92672">=</span>false <span style="color:#75715e"># Browser visibility</span> </span></span><span style="display:flex;"><span>SHOW_BROWSER<span style="color:#f92672">=</span>false <span style="color:#75715e"># Default browser display</span> </span></span><span style="display:flex;"><span>STEALTH_ENABLED<span style="color:#f92672">=</span>true <span style="color:#75715e"># Human-like behavior</span> </span></span><span style="display:flex;"><span>TYPING_WPM_MIN<span style="color:#f92672">=</span><span style="color:#ae81ff">160</span> <span style="color:#75715e"># Typing speed</span> </span></span><span style="display:flex;"><span>TYPING_WPM_MAX<span style="color:#f92672">=</span><span style="color:#ae81ff">240</span> </span></span><span style="display:flex;"><span>DEFAULT_NOTEBOOK_ID<span style="color:#f92672">=</span> <span style="color:#75715e"># Default notebook</span> </span></span></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> </div></section><!-- Changelog --><!-- Version History --><!-- Reviews --><div id="reviews-section"><section><h2 class="text-lg font-semibold text-gray-900 mb-4">Reviews (0)</h2><!-- Sign in prompt --> <div class="bg-gray-50 rounded-lg p-4 mb-6 text-sm text-gray-500"><a href="/auth/login" class="text-brand-600 hover:text-brand-700 font-medium">Sign in</a> to write a review.</div><!-- Empty state --> <div class="bg-gray-50 rounded-xl p-8 text-center"><p class="text-sm text-gray-500">No reviews yet. Be the first to review!</p></div></section></div><!-- Comments --><div id="comments-section"><section><h2 class="text-lg font-semibold text-gray-900 mb-4" id="comments">Comments (0)</h2><div class="bg-gray-50 rounded-lg p-4 mb-6 text-sm text-gray-500"><a href="/auth/login" class="text-brand-600 hover:text-brand-700 font-medium">Sign in</a> to join the discussion.</div><div class="bg-gray-50 rounded-xl p-8 text-center"><p class="text-sm text-gray-500">No comments yet. Be the first to share your thoughts!</p></div></section></div></div><!-- Sidebar (1/3) --><div class="space-y-6"><!-- Platforms --><div class="bg-white rounded-xl border border-gray-200 p-5"><h3 class="text-sm font-semibold text-gray-900 mb-3">Compatible Platforms</h3><div class="flex flex-wrap gap-2"></div></div><!-- Tags --><!-- Links --><div class="bg-white rounded-xl border border-gray-200 p-5"><h3 class="text-sm font-semibold text-gray-900 mb-3">Links</h3><div class="space-y-2"><a href="https://clawhub.ai/skills/nblm" target="_blank" rel="noopener noreferrer" class="flex items-center gap-2 text-sm text-brand-600 hover:text-brand-700">📂 Source Code</a> </div></div><!-- Pricing (DISABLED: free-only mode — always show Free) --><div class="bg-white rounded-xl border border-gray-200 p-5"><h3 class="text-sm font-semibold text-gray-900 mb-3">Pricing</h3><span class="text-sm font-medium text-green-700 bg-green-50 px-3 py-1 rounded-full">Free</span></div></div></div><!-- Related Items --><section class="mt-12"><h2 class="text-xl font-bold text-gray-900 mb-6">Related Configs</h2><div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"><div class="relative"><a href="/item/skill-self-improving-agent" class="card-hover block"><div class="flex items-start justify-between gap-3 mb-3"><div class="flex items-center gap-2 min-w-0"><span class="badge-skill">🧪 Skill</span><h3 class="text-base font-semibold text-gray-900 truncate">self-improving-agent</h3></div><span class="shrink-0 text-xs font-medium text-green-700 bg-green-50 px-2 py-0.5 rounded-full mr-6">Free</span> </div><p class="text-sm text-gray-500 mb-3 line-clamp-2">Captures learnings, errors, and corrections to enable continuous improvement. Use when: (1) A command or operation fails unexpectedly, (2) User corrects Clau...</p><div class="flex items-center justify-between"><div class="flex items-center gap-3 text-xs text-gray-500"><span class="flex items-center gap-0.5"><span>❤️</span> 2.0k</span> <span class="flex items-center gap-0.5"><span>⬇️</span> 218k</span></div></div></a> <button data-compare-slug="skill-self-improving-agent" onclick="event.preventDefault();event.stopPropagation();toggleCompare(this.dataset.compareSlug)" class="absolute top-3 right-3 w-6 h-6 flex items-center justify-center rounded border border-gray-200 text-gray-400 hover:text-brand-600 hover:border-brand-300 transition-colors" title="Add to comparison"><svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="20" x2="18" y2="10"></line> <line x1="12" y1="20" x2="12" y2="4"></line> <line x1="6" y1="20" x2="6" y2="14"></line></svg></button></div><div class="relative"><a href="/item/self-improving-agent" class="card-hover block"><div class="flex items-start justify-between gap-3 mb-3"><div class="flex items-center gap-2 min-w-0"><span class="badge-skill">🧪 Skill</span><h3 class="text-base font-semibold text-gray-900 truncate">Self Improving Agent</h3></div><span class="shrink-0 text-xs font-medium text-green-700 bg-green-50 px-2 py-0.5 rounded-full mr-6">Free</span> </div><p class="text-sm text-gray-500 mb-3 line-clamp-2">Captures learnings, errors, and corrections to enable continuous improvement. And also 50+ models for image generation, video generation, text-to-speech, spe...</p><div class="flex items-center justify-between"><div class="flex items-center gap-3 text-xs text-gray-500"><span class="flex items-center gap-0.5"><span>❤️</span> 2.0k</span> <span class="flex items-center gap-0.5"><span>⬇️</span> 206k</span></div></div></a> <button data-compare-slug="self-improving-agent" onclick="event.preventDefault();event.stopPropagation();toggleCompare(this.dataset.compareSlug)" class="absolute top-3 right-3 w-6 h-6 flex items-center justify-center rounded border border-gray-200 text-gray-400 hover:text-brand-600 hover:border-brand-300 transition-colors" title="Add to comparison"><svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="20" x2="18" y2="10"></line> <line x1="12" y1="20" x2="12" y2="4"></line> <line x1="6" y1="20" x2="6" y2="14"></line></svg></button></div><div class="relative"><a href="/item/find-skills" class="card-hover block"><div class="flex items-start justify-between gap-3 mb-3"><div class="flex items-center gap-2 min-w-0"><span class="badge-skill">🧪 Skill</span><h3 class="text-base font-semibold text-gray-900 truncate">Find Skills</h3></div><span class="shrink-0 text-xs font-medium text-green-700 bg-green-50 px-2 py-0.5 rounded-full mr-6">Free</span> </div><p class="text-sm text-gray-500 mb-3 line-clamp-2">Search, discover, and install skills from the open agent skills ecosystem to extend agent capabilities for specific tasks or domains.</p><div class="flex items-center justify-between"><div class="flex items-center gap-3 text-xs text-gray-500"><span class="flex items-center gap-0.5"><span>❤️</span> 814</span> <span class="flex items-center gap-0.5"><span>⬇️</span> 199k</span></div></div></a> <button data-compare-slug="find-skills" onclick="event.preventDefault();event.stopPropagation();toggleCompare(this.dataset.compareSlug)" class="absolute top-3 right-3 w-6 h-6 flex items-center justify-center rounded border border-gray-200 text-gray-400 hover:text-brand-600 hover:border-brand-300 transition-colors" title="Add to comparison"><svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="20" x2="18" y2="10"></line> <line x1="12" y1="20" x2="12" y2="4"></line> <line x1="6" y1="20" x2="6" y2="14"></line></svg></button></div><div class="relative"><a href="/item/skill-summarize" class="card-hover block"><div class="flex items-start justify-between gap-3 mb-3"><div class="flex items-center gap-2 min-w-0"><span class="badge-skill">🧪 Skill</span><h3 class="text-base font-semibold text-gray-900 truncate">Summarize</h3></div><span class="shrink-0 text-xs font-medium text-green-700 bg-green-50 px-2 py-0.5 rounded-full mr-6">Free</span> </div><p class="text-sm text-gray-500 mb-3 line-clamp-2">--- name: summarize description: Summarize URLs or files with the summarize CLI (web, PDFs, images, audio, YouTube). homepage: https://summarize.sh metadata: {"clawdbot":{"emoji":"🧾","requires":{"b</p><div class="flex items-center justify-between"><div class="flex items-center gap-3 text-xs text-gray-500"><span class="flex items-center gap-0.5"><span>❤️</span> 609</span> <span class="flex items-center gap-0.5"><span>⬇️</span> 160k</span></div></div></a> <button data-compare-slug="skill-summarize" onclick="event.preventDefault();event.stopPropagation();toggleCompare(this.dataset.compareSlug)" class="absolute top-3 right-3 w-6 h-6 flex items-center justify-center rounded border border-gray-200 text-gray-400 hover:text-brand-600 hover:border-brand-300 transition-colors" title="Add to comparison"><svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="20" x2="18" y2="10"></line> <line x1="12" y1="20" x2="12" y2="4"></line> <line x1="6" y1="20" x2="6" y2="14"></line></svg></button></div></div></section></div></main><footer class="bg-white border-t border-gray-200 mt-16"><div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12"><div class="grid grid-cols-2 md:grid-cols-5 gap-8"><!-- Brand --><div class="col-span-2 md:col-span-1"><a href="/" class="flex items-center gap-2 text-lg font-bold text-brand-600"><span>☕</span> <span>Skillbrew</span></a><p class="mt-2 text-sm text-gray-500">Brew your perfect agent</p></div><!-- Product --><div><h3 class="text-sm font-semibold text-gray-900 mb-3">Product</h3><ul class="space-y-2"><li><a href="/browse" class="text-sm text-gray-500 hover:text-gray-700">Browse</a></li><li><a href="/collections" class="text-sm text-gray-500 hover:text-gray-700">Collections</a></li><li><a href="/weekly" class="text-sm text-gray-500 hover:text-gray-700">Weekly</a></li><li><a href="/submit" class="text-sm text-gray-500 hover:text-gray-700">Submit</a></li></ul></div><!-- Community --><div><h3 class="text-sm font-semibold text-gray-900 mb-3">Community</h3><ul class="space-y-2"><li><a href="https://github.com/skillbrew-dev" target="_blank" rel="noopener" class="text-sm text-gray-500 hover:text-gray-700">GitHub</a></li><li><a href="https://discord.gg/zuXQfjUW" target="_blank" rel="noopener" class="text-sm text-gray-500 hover:text-gray-700">Discord</a></li><li><a href="mailto:skillbrew@skillbrew.dev" class="text-sm text-gray-500 hover:text-gray-700">Email</a></li></ul></div><!-- Resources --><div><h3 class="text-sm font-semibold text-gray-900 mb-3">Resources</h3><ul class="space-y-2"><li><a href="/api/docs" class="text-sm text-gray-500 hover:text-gray-700">API Docs</a></li><li><a href="/feed.xml" class="text-sm text-gray-500 hover:text-gray-700">RSS Feed</a></li></ul></div><!-- Legal --><div><h3 class="text-sm font-semibold text-gray-900 mb-3">Legal</h3><ul class="space-y-2"><li><a href="/about" class="text-sm text-gray-500 hover:text-gray-700">About</a></li><li><a href="/privacy" class="text-sm text-gray-500 hover:text-gray-700">Privacy Policy</a></li><li><a href="/terms" class="text-sm text-gray-500 hover:text-gray-700">Terms of Service</a></li></ul></div></div><div class="mt-8 pt-8 border-t border-gray-200"><p class="text-sm text-gray-400 text-center">© 2026 Skillbrew. All rights reserved.</p></div></div></footer><!-- Compare floating bar --><div id="compare-bar" class="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 shadow-lg py-3 px-4 z-50 hidden"><div class="max-w-7xl mx-auto flex items-center justify-between"><span class="text-sm text-gray-700"><strong id="compare-count">0</strong> items selected for comparison</span><div class="flex items-center gap-2"><button onclick="clearCompareSet()" class="px-3 py-1.5 text-sm text-gray-500 hover:text-gray-700 transition-colors">Clear</button> <button onclick="goCompare()" class="px-4 py-1.5 text-sm font-medium text-white bg-brand-600 rounded-lg hover:bg-brand-700 transition-colors">Compare Now</button></div></div></div><script> document.body.addEventListener('htmx:responseError', function(event) { if (event.detail.xhr.status === 401) { window.location.href = '/auth/login?return_url=' + encodeURIComponent(window.location.pathname); } }); // Include CSRF token in all HTMX requests document.body.addEventListener('htmx:configRequest', function(evt) { var meta = document.querySelector('meta[name="csrf-token"]'); if (meta) { evt.detail.headers['X-CSRF-Token'] = meta.content; } }); // Compare set (localStorage) function getCompareSet() { try { return JSON.parse(localStorage.getItem('sb_compare') || '[]'); } catch { return []; } } function saveCompareSet(set) { localStorage.setItem('sb_compare', JSON.stringify(set)); updateCompareBar(); } function toggleCompare(slug) { let set = getCompareSet(); const idx = set.indexOf(slug); if (idx >= 0) { set.splice(idx, 1); } else { if (set.length >= 3) { set.shift(); } set.push(slug); } saveCompareSet(set); updateCompareButtons(); } function clearCompareSet() { saveCompareSet([]); updateCompareButtons(); } function goCompare() { const set = getCompareSet(); if (set.length >= 2) { window.location.href = '/compare?items=' + set.join(','); } } function updateCompareBar() { const set = getCompareSet(); const bar = document.getElementById('compare-bar'); const count = document.getElementById('compare-count'); if (bar) { if (set.length >= 2) { bar.classList.remove('hidden'); } else { bar.classList.add('hidden'); } if (count) count.textContent = set.length; } } function updateCompareButtons() { const set = getCompareSet(); document.querySelectorAll('[data-compare-slug]').forEach(btn => { const slug = btn.dataset.compareSlug; if (set.includes(slug)) { btn.classList.add('bg-brand-50', 'text-brand-600', 'border-brand-300'); btn.classList.remove('text-gray-400', 'border-gray-200'); btn.title = 'Remove from comparison'; } else { btn.classList.remove('bg-brand-50', 'text-brand-600', 'border-brand-300'); btn.classList.add('text-gray-400', 'border-gray-200'); btn.title = 'Add to comparison'; } }); } document.addEventListener('DOMContentLoaded', function() { updateCompareBar(); updateCompareButtons(); }); document.body.addEventListener('htmx:afterSwap', function() { updateCompareButtons(); }); </script></body></html>