Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 81 additions & 10 deletions SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,59 @@ python scripts/run.py notebook_manager.py remove --id notebook-id
```

### Quick Workflow
1. Check library: `python scripts/run.py notebook_manager.py list`
2. Ask question: `python scripts/run.py ask_question.py --question "..." --notebook-id ID`
```bash
notebook_manager.py sync # discover all notebooks
notebook_manager.py find "topic" # find by name or source
ask_question.py --question "..." --notebook-id ID # ask a question
```

### Step 3: Manage Notebooks (Web-Backed)

```bash
# Discover all notebooks from web
python scripts/run.py notebook_manager.py sync
python scripts/run.py notebook_manager.py sync --deep # also scrape source names
python scripts/run.py notebook_manager.py sync --deep --stale # only re-scrape changed notebooks

# Search notebooks (instant, searches names + source filenames)
python scripts/run.py notebook_manager.py find "eigenform"

# Import a notebook by URL (auto-discovers title + sources)
python scripts/run.py notebook_manager.py import --url "https://notebooklm.google.com/notebook/..."

# Create, rename, delete notebooks on web
python scripts/run.py notebook_manager.py create --title "New Research"
python scripts/run.py notebook_manager.py rename --id ID --title "Better Name"
python scripts/run.py notebook_manager.py delete --id ID --confirm

# Local library operations (instant, no browser)
python scripts/run.py notebook_manager.py list [--format json]
python scripts/run.py notebook_manager.py activate --id ID
python scripts/run.py notebook_manager.py exclude --id ID
python scripts/run.py notebook_manager.py stats
```

### Step 3b: Manage Sources

```bash
# List sources (writes to library, then displays from library)
python scripts/run.py source_manager.py list --notebook-id ID

# Add sources
python scripts/run.py source_manager.py add-text --text "Your content" --notebook-id ID
python scripts/run.py source_manager.py add-text --from-file /path/to/notes.txt --notebook-id ID
python scripts/run.py source_manager.py add-file --file /path/to/file.pdf --notebook-id ID
python scripts/run.py source_manager.py add-website --urls "https://example.com" --notebook-id ID

# Read, rename, remove sources
python scripts/run.py source_manager.py read --name "Source Name" --notebook-id ID
python scripts/run.py source_manager.py rename --name "Old Name" --title "New Name" --notebook-id ID
python scripts/run.py source_manager.py remove --name "Source Name" --notebook-id ID

# Select specific sources for querying (deselect others)
python scripts/run.py source_manager.py select --names "source1,source2" --notebook-id ID
python scripts/run.py source_manager.py select --all --notebook-id ID
```

### Step 4: Ask Questions

Expand All @@ -116,10 +167,10 @@ python scripts/run.py notebook_manager.py remove --id notebook-id
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
python scripts/run.py ask_question.py --question "..." --notebook-id ID

# Query with notebook URL directly
python scripts/run.py ask_question.py --question "..." --notebook-url "https://..."
# Query only specific sources (others excluded)
python scripts/run.py ask_question.py --question "..." --sources "source1,source2" --notebook-id ID

# Show browser for debugging
python scripts/run.py ask_question.py --question "..." --show-browser
Expand Down Expand Up @@ -152,17 +203,37 @@ python scripts/run.py auth_manager.py clear # Clear authentication

### Notebook Management (`notebook_manager.py`)
```bash
python scripts/run.py notebook_manager.py add --url URL --name NAME --description DESC --topics TOPICS
python scripts/run.py notebook_manager.py list
python scripts/run.py notebook_manager.py search --query QUERY
# Web-backed
python scripts/run.py notebook_manager.py sync [--deep] [--deep --stale] [--library-only] [--force]
python scripts/run.py notebook_manager.py import --url URL
python scripts/run.py notebook_manager.py create --title "..."
python scripts/run.py notebook_manager.py rename --id ID --title "..."
python scripts/run.py notebook_manager.py delete --id ID --confirm
# Local library
python scripts/run.py notebook_manager.py list [--format json]
python scripts/run.py notebook_manager.py find "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 exclude --id ID
python scripts/run.py notebook_manager.py stats
```

### Question Interface (`ask_question.py`)
```bash
python scripts/run.py ask_question.py --question "..." [--notebook-id ID] [--notebook-url URL] [--show-browser]
python scripts/run.py ask_question.py --question "..." [--notebook-id ID] [--sources "s1,s2"] [--show-browser]
```

### Source Management (`source_manager.py`)
```bash
python scripts/run.py source_manager.py list [--notebook-id ID]
python scripts/run.py source_manager.py add-text --text "..." [--from-file PATH] [--notebook-id ID]
python scripts/run.py source_manager.py add-file --file PATH [--notebook-id ID]
python scripts/run.py source_manager.py add-website --urls "URL1,URL2" [--notebook-id ID]
python scripts/run.py source_manager.py read --name "Source Name" [--notebook-id ID]
python scripts/run.py source_manager.py rename --name "Name" --title "New Name" [--notebook-id ID]
python scripts/run.py source_manager.py select --names "s1,s2" [--notebook-id ID]
python scripts/run.py source_manager.py select --all [--notebook-id ID]
python scripts/run.py source_manager.py remove --name "Source Name" [--notebook-id ID]
python scripts/run.py source_manager.py test [--notebook-id ID] [--show-browser]
```

### Data Cleanup (`cleanup_manager.py`)
Expand Down
63 changes: 61 additions & 2 deletions scripts/ask_question.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,54 @@
)


def ask_notebooklm(question: str, notebook_url: str, headless: bool = True) -> str:
def select_sources_for_query(page, source_names: list) -> None:
"""Select specific sources before asking a question."""
from config import SOURCE_ITEM_SELECTOR

print(f" 🎯 Selecting {len(source_names)} source(s)...")

# Click Sources tab
sources_tab = page.get_by_text("Sources", exact=True).first
sources_tab.click()
time.sleep(2)

# Get all source names
all_sources = []
for el in page.query_selector_all(SOURCE_ITEM_SELECTOR):
label = el.get_attribute("aria-label") or ""
if label:
all_sources.append(label)

# Resolve fuzzy names
selected = set()
for name in source_names:
name_lower = name.lower()
for src in all_sources:
if name_lower in src.lower() or src.lower() in name_lower:
selected.add(src)
break

# Toggle checkboxes
for src_name in all_sources:
checkbox = page.query_selector(f"input[aria-label='{src_name}']")
if not checkbox:
continue
is_checked = checkbox.is_checked()
should_check = src_name in selected
if is_checked != should_check:
checkbox.click(force=True)
time.sleep(0.3)

for s in selected:
print(f" ✓ {s}")

# Switch back to Chat tab
chat_tab = page.get_by_text("Chat", exact=True).first
chat_tab.click()
time.sleep(2)


def ask_notebooklm(question: str, notebook_url: str, headless: bool = True, sources: list = None) -> str:
"""
Ask a question to NotebookLM

Expand Down Expand Up @@ -79,6 +126,11 @@ def ask_notebooklm(question: str, notebook_url: str, headless: bool = True) -> s
# Wait for NotebookLM
page.wait_for_url(re.compile(r"^https://notebooklm\.google\.com/"), timeout=10000)

# Select specific sources if requested
if sources:
time.sleep(3)
select_sources_for_query(page, sources)

# Wait for query input (MCP approach)
print(" ⏳ Waiting for query input...")
query_element = None
Expand Down Expand Up @@ -193,6 +245,7 @@ def main():
parser.add_argument('--question', required=True, help='Question to ask')
parser.add_argument('--notebook-url', help='NotebookLM notebook URL')
parser.add_argument('--notebook-id', help='Notebook ID from library')
parser.add_argument('--sources', help='Comma-separated source names to query (others excluded)')
parser.add_argument('--show-browser', action='store_true', help='Show browser')

args = parser.parse_args()
Expand Down Expand Up @@ -231,11 +284,17 @@ def main():
print("python scripts/run.py notebook_manager.py add --url URL --name NAME --description DESC --topics TOPICS")
return 1

# Parse sources if provided
sources = None
if args.sources:
sources = [s.strip() for s in args.sources.split(",")]

# Ask the question
answer = ask_notebooklm(
question=args.question,
notebook_url=notebook_url,
headless=not args.show_browser
headless=not args.show_browser,
sources=sources
)

if answer:
Expand Down
15 changes: 15 additions & 0 deletions scripts/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,21 @@
"[data-message-author='assistant']",
]

# Source Management Selectors
SOURCE_ITEM_SELECTOR = "button.source-stretched-button"
MORE_VERT_SELECTOR = 'button:has-text("more_vert")'
REMOVE_MENU_SELECTOR = "[role='menuitem']:has-text('Remove')"
DELETE_DIALOG_SELECTOR = "mat-dialog-container"
WEBSITE_URL_INPUT_SELECTOR = "[formcontrolname='urls']"
COPIED_TEXT_INPUT_SELECTOR = "textarea[placeholder='Paste text here']"

# Notebook Management Selectors (homepage)
NOTEBOOK_CARD_SELECTOR = "mat-card.project-button-card"
NOTEBOOK_LINK_SELECTOR = "a[href*='/notebook/']"
NOTEBOOK_MENU_SELECTOR = "button[aria-label='Project Actions Menu']"
NOTEBOOK_TITLE_FC_SELECTOR = "[formcontrolname='title']"
NOTEBOOKLM_HOME_URL = "https://notebooklm.google.com/"

# Browser Configuration
BROWSER_ARGS = [
'--disable-blink-features=AutomationControlled', # Patches navigator.webdriver
Expand Down
Loading