Function bodies 108 total
emit_hook_result function · python · L206-L217 (12 LOC)hooks/utils.py
def emit_hook_result(event_name: str, context: str) -> None:
"""Print hook result JSON to stdout (Claude Code protocol).
WHY: Almost every hook constructs this dict manually — 30+ lines saved.
"""
result = {
"hookSpecificOutput": {
"hookEventName": event_name,
"additionalContext": context,
}
}
print(json.dumps(result))sanitize_text function · python · L220-L229 (10 LOC)hooks/utils.py
def sanitize_text(text: str, max_len: int = 200) -> str:
"""Strip newlines and limit length to prevent prompt injection.
WHY: Duplicated in pattern_extractor (sanitize_commit_msg)
and input_guard (sanitize). Unified version.
"""
clean = text.replace("\n", " ").replace("\r", " ").strip()
if len(clean) > max_len:
clean = clean[:max_len] + "..."
return cleanextract_tool_response function · python · L232-L244 (13 LOC)hooks/utils.py
def extract_tool_response(data: dict) -> str:
"""Extract tool response text from hook data, handling multiple formats.
WHY: Duplicated in memory_guard, post_commit_memory, pattern_extractor
(identical response extraction with fallbacks).
"""
tool_response = data.get("tool_response", data.get("tool_result", {}))
if isinstance(tool_response, dict):
return str(tool_response.get("stdout", tool_response.get("output", "")))
elif isinstance(tool_response, str):
return tool_response
else:
return str(tool_response)is_failed_commit function · python · L247-L252 (6 LOC)hooks/utils.py
def is_failed_commit(response_text: str) -> bool:
"""Check if a git commit failed based on response text.
WHY: Duplicated in memory_guard, post_commit_memory, pattern_extractor.
"""
return "nothing to commit" in response_text or "error" in response_text.lower()redact function · python · L57-L66 (10 LOC)scripts/redact.py
def redact(text: str) -> str:
for pattern, replacement in PATTERNS:
def replace_if_not_excluded(m, repl=replacement):
if should_exclude(m.group(0)):
return m.group(0)
return repl
text = re.sub(pattern, replace_if_not_excluded, text)
return textclean function · python · L69-L76 (8 LOC)scripts/redact.py
def clean(obj):
if isinstance(obj, str):
return redact(obj)
elif isinstance(obj, dict):
return {k: clean(v) for k, v in obj.items()}
elif isinstance(obj, list):
return [clean(item) for item in obj]
return objask_notebooklm function · python · L40-L187 (148 LOC)skills/extensions/notebooklm/scripts/ask_question.py
def ask_notebooklm(question: str, notebook_url: str, headless: bool = True) -> str:
"""
Ask a question to NotebookLM
Args:
question: Question to ask
notebook_url: NotebookLM notebook URL
headless: Run browser in headless mode
Returns:
Answer text from NotebookLM
"""
auth = AuthManager()
if not auth.is_authenticated():
print("⚠️ Not authenticated. Run: python auth_manager.py setup")
return None
print(f"💬 Asking: {question}")
print(f"📚 Notebook: {notebook_url}")
playwright = None
context = None
try:
# Start playwright
playwright = sync_playwright().start()
# Launch persistent browser context using factory
context = BrowserFactory.launch_persistent_context(
playwright,
headless=headless
)
# Navigate to notebook
page = context.new_page()
print(" 🌐 Opening notebook...")
page.goto(notebook_url,All rows above produced by Repobility · https://repobility.com
main function · python · L190-L252 (63 LOC)skills/extensions/notebooklm/scripts/ask_question.py
def main():
parser = argparse.ArgumentParser(description='Ask NotebookLM a question')
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('--show-browser', action='store_true', help='Show browser')
args = parser.parse_args()
# Resolve notebook URL
notebook_url = args.notebook_url
if not notebook_url and args.notebook_id:
library = NotebookLibrary()
notebook = library.get_notebook(args.notebook_id)
if notebook:
notebook_url = notebook['url']
else:
print(f"❌ Notebook '{args.notebook_id}' not found")
return 1
if not notebook_url:
# Check for active notebook first
library = NotebookLibrary()
active = library.get_active_notebook()
if active:
notebook_url = acAuthManager.__init__ method · python · L42-L50 (9 LOC)skills/extensions/notebooklm/scripts/auth_manager.py
def __init__(self):
"""Initialize the authentication manager"""
# Ensure directories exist
DATA_DIR.mkdir(parents=True, exist_ok=True)
BROWSER_STATE_DIR.mkdir(parents=True, exist_ok=True)
self.state_file = STATE_FILE
self.auth_info_file = AUTH_INFO_FILE
self.browser_state_dir = BROWSER_STATE_DIRAuthManager.is_authenticated method · python · L52-L62 (11 LOC)skills/extensions/notebooklm/scripts/auth_manager.py
def is_authenticated(self) -> bool:
"""Check if valid authentication exists"""
if not self.state_file.exists():
return False
# Check if state file is not too old (7 days)
age_days = (time.time() - self.state_file.stat().st_mtime) / 86400
if age_days > 7:
print(f"⚠️ Browser state is {age_days:.1f} days old, may need re-authentication")
return TrueAuthManager.get_auth_info method · python · L64-L84 (21 LOC)skills/extensions/notebooklm/scripts/auth_manager.py
def get_auth_info(self) -> Dict[str, Any]:
"""Get authentication information"""
info = {
'authenticated': self.is_authenticated(),
'state_file': str(self.state_file),
'state_exists': self.state_file.exists()
}
if self.auth_info_file.exists():
try:
with open(self.auth_info_file, 'r') as f:
saved_info = json.load(f)
info.update(saved_info)
except Exception:
pass
if info['state_exists']:
age_hours = (time.time() - self.state_file.stat().st_mtime) / 3600
info['state_age_hours'] = age_hours
return infoAuthManager.setup_auth method · python · L86-L158 (73 LOC)skills/extensions/notebooklm/scripts/auth_manager.py
def setup_auth(self, headless: bool = False, timeout_minutes: int = 10) -> bool:
"""
Perform interactive authentication setup
Args:
headless: Run browser in headless mode (False for login)
timeout_minutes: Maximum time to wait for login
Returns:
True if authentication successful
"""
print("🔐 Starting authentication setup...")
print(f" Timeout: {timeout_minutes} minutes")
playwright = None
context = None
try:
playwright = sync_playwright().start()
# Launch using factory
context = BrowserFactory.launch_persistent_context(
playwright,
headless=headless
)
# Navigate to NotebookLM
page = context.new_page()
page.goto("https://notebooklm.google.com", wait_until="domcontentloaded")
# Check if already authenticated
if "notebooklm.gAuthManager._save_browser_state method · python · L160-L168 (9 LOC)skills/extensions/notebooklm/scripts/auth_manager.py
def _save_browser_state(self, context: BrowserContext):
"""Save browser state to disk"""
try:
# Save storage state (cookies, localStorage)
context.storage_state(path=str(self.state_file))
print(f" 💾 Saved browser state to: {self.state_file}")
except Exception as e:
print(f" ❌ Failed to save browser state: {e}")
raiseAuthManager._save_auth_info method · python · L170-L180 (11 LOC)skills/extensions/notebooklm/scripts/auth_manager.py
def _save_auth_info(self):
"""Save authentication metadata"""
try:
info = {
'authenticated_at': time.time(),
'authenticated_at_iso': time.strftime('%Y-%m-%d %H:%M:%S')
}
with open(self.auth_info_file, 'w') as f:
json.dump(info, f, indent=2)
except Exception:
pass # Non-criticalAuthManager.clear_auth method · python · L182-L212 (31 LOC)skills/extensions/notebooklm/scripts/auth_manager.py
def clear_auth(self) -> bool:
"""
Clear all authentication data
Returns:
True if cleared successfully
"""
print("🗑️ Clearing authentication data...")
try:
# Remove browser state
if self.state_file.exists():
self.state_file.unlink()
print(" ✅ Removed browser state")
# Remove auth info
if self.auth_info_file.exists():
self.auth_info_file.unlink()
print(" ✅ Removed auth info")
# Clear entire browser state directory
if self.browser_state_dir.exists():
shutil.rmtree(self.browser_state_dir)
self.browser_state_dir.mkdir(parents=True, exist_ok=True)
print(" ✅ Cleared browser data")
return True
except Exception as e:
print(f" ❌ Error clearing auth: {e}")
return FalseRepobility — same analyzer, your code, free for public repos · /scan/
AuthManager.re_auth method · python · L214-L231 (18 LOC)skills/extensions/notebooklm/scripts/auth_manager.py
def re_auth(self, headless: bool = False, timeout_minutes: int = 10) -> bool:
"""
Perform re-authentication (clear and setup)
Args:
headless: Run browser in headless mode
timeout_minutes: Login timeout in minutes
Returns:
True if successful
"""
print("🔄 Starting re-authentication...")
# Clear existing auth
self.clear_auth()
# Setup new auth
return self.setup_auth(headless, timeout_minutes)AuthManager.validate_auth method · python · L233-L284 (52 LOC)skills/extensions/notebooklm/scripts/auth_manager.py
def validate_auth(self) -> bool:
"""
Validate that stored authentication works
Uses persistent context to match actual usage pattern
Returns:
True if authentication is valid
"""
if not self.is_authenticated():
return False
print("🔍 Validating authentication...")
playwright = None
context = None
try:
playwright = sync_playwright().start()
# Launch using factory
context = BrowserFactory.launch_persistent_context(
playwright,
headless=True
)
# Try to access NotebookLM
page = context.new_page()
page.goto("https://notebooklm.google.com", wait_until="domcontentloaded", timeout=30000)
# Check if we can access NotebookLM
if "notebooklm.google.com" in page.url and "accounts.google.com" not in page.url:
print(" ✅ Authentication main function · python · L287-L354 (68 LOC)skills/extensions/notebooklm/scripts/auth_manager.py
def main():
"""Command-line interface for authentication management"""
parser = argparse.ArgumentParser(description='Manage NotebookLM authentication')
subparsers = parser.add_subparsers(dest='command', help='Commands')
# Setup command
setup_parser = subparsers.add_parser('setup', help='Setup authentication')
setup_parser.add_argument('--headless', action='store_true', help='Run in headless mode')
setup_parser.add_argument('--timeout', type=float, default=10, help='Login timeout in minutes (default: 10)')
# Status command
subparsers.add_parser('status', help='Check authentication status')
# Validate command
subparsers.add_parser('validate', help='Validate authentication')
# Clear command
subparsers.add_parser('clear', help='Clear authentication')
# Re-auth command
reauth_parser = subparsers.add_parser('reauth', help='Re-authenticate (clear + setup)')
reauth_parser.add_argument('--timeout', type=float, default=10, helBrowserSession.__init__ method · python · L30-L49 (20 LOC)skills/extensions/notebooklm/scripts/browser_session.py
def __init__(self, session_id: str, context: BrowserContext, notebook_url: str):
"""
Initialize a new browser session
Args:
session_id: Unique identifier for this session
context: Browser context (shared or dedicated)
notebook_url: Target NotebookLM URL for this session
"""
self.id = session_id
self.created_at = time.time()
self.last_activity = time.time()
self.message_count = 0
self.notebook_url = notebook_url
self.context = context
self.page = None
self.stealth = StealthUtils()
# Initialize the session
self._initialize()BrowserSession._initialize method · python · L51-L80 (30 LOC)skills/extensions/notebooklm/scripts/browser_session.py
def _initialize(self):
"""Initialize the browser session and navigate to NotebookLM"""
print(f"🚀 Creating session {self.id}...")
# Create new page (tab) in context
self.page = self.context.new_page()
print(f" 🌐 Navigating to NotebookLM...")
try:
# Navigate to notebook
self.page.goto(self.notebook_url, wait_until="domcontentloaded", timeout=30000)
# Check if login is needed
if "accounts.google.com" in self.page.url:
raise RuntimeError("Authentication required. Please run auth_manager.py setup first.")
# Wait for page to be ready
self._wait_for_ready()
# Simulate human inspection
self.stealth.random_mouse_movement(self.page)
self.stealth.random_delay(300, 600)
print(f"✅ Session {self.id} ready!")
except Exception as e:
print(f"❌ Failed to initialize session: {e}")
BrowserSession._wait_for_ready method · python · L82-L89 (8 LOC)skills/extensions/notebooklm/scripts/browser_session.py
def _wait_for_ready(self):
"""Wait for NotebookLM page to be ready"""
try:
# Wait for chat input
self.page.wait_for_selector("textarea.query-box-input", timeout=10000, state="visible")
except Exception:
# Try alternative selector
self.page.wait_for_selector('textarea[aria-label="Feld für Anfragen"]', timeout=5000, state="visible")BrowserSession.ask method · python · L91-L155 (65 LOC)skills/extensions/notebooklm/scripts/browser_session.py
def ask(self, question: str) -> Dict[str, Any]:
"""
Ask a question in this session
Args:
question: The question to ask
Returns:
Dict with status, question, answer, session_id
"""
try:
self.last_activity = time.time()
self.message_count += 1
print(f"💬 [{self.id}] Asking: {question}")
# Snapshot current answer to detect new response
previous_answer = self._snapshot_latest_response()
# Find chat input
chat_input_selector = "textarea.query-box-input"
try:
self.page.wait_for_selector(chat_input_selector, timeout=5000, state="visible")
except Exception:
chat_input_selector = 'textarea[aria-label="Feld für Anfragen"]'
self.page.wait_for_selector(chat_input_selector, timeout=5000, state="visible")
# Click and type with human-like behavior
BrowserSession._snapshot_latest_response method · python · L157-L166 (10 LOC)skills/extensions/notebooklm/scripts/browser_session.py
def _snapshot_latest_response(self) -> Optional[str]:
"""Get the current latest response text"""
try:
# Use correct NotebookLM selector
responses = self.page.query_selector_all(".to-user-container .message-text-content")
if responses:
return responses[-1].inner_text()
except Exception:
pass
return NoneAbout: code-quality intelligence by Repobility · https://repobility.com
BrowserSession._wait_for_latest_answer method · python · L168-L207 (40 LOC)skills/extensions/notebooklm/scripts/browser_session.py
def _wait_for_latest_answer(self, previous_answer: Optional[str], timeout: int = 120) -> str:
"""Wait for and extract the new answer"""
start_time = time.time()
last_candidate = None
stable_count = 0
while time.time() - start_time < timeout:
# Check if NotebookLM is still thinking (most reliable indicator)
try:
thinking_element = self.page.query_selector('div.thinking-message')
if thinking_element and thinking_element.is_visible():
time.sleep(0.5)
continue
except Exception:
pass
try:
# Use correct NotebookLM selector
responses = self.page.query_selector_all(".to-user-container .message-text-content")
if responses:
latest_text = responses[-1].inner_text().strip()
# Check if it's a new response
if latBrowserSession.reset method · python · L209-L221 (13 LOC)skills/extensions/notebooklm/scripts/browser_session.py
def reset(self):
"""Reset the chat by reloading the page"""
print(f"🔄 Resetting session {self.id}...")
self.page.reload(wait_until="domcontentloaded")
self._wait_for_ready()
previous_count = self.message_count
self.message_count = 0
self.last_activity = time.time()
print(f"✅ Session reset (cleared {previous_count} messages)")
return previous_countBrowserSession.close method · python · L223-L233 (11 LOC)skills/extensions/notebooklm/scripts/browser_session.py
def close(self):
"""Close this session and clean up resources"""
print(f"🛑 Closing session {self.id}...")
if self.page:
try:
self.page.close()
except Exception as e:
print(f" ⚠️ Error closing page: {e}")
print(f"✅ Session {self.id} closed")BrowserSession.get_info method · python · L235-L245 (11 LOC)skills/extensions/notebooklm/scripts/browser_session.py
def get_info(self) -> Dict[str, Any]:
"""Get information about this session"""
return {
"id": self.id,
"created_at": self.created_at,
"last_activity": self.last_activity,
"age_seconds": time.time() - self.created_at,
"inactive_seconds": time.time() - self.last_activity,
"message_count": self.message_count,
"notebook_url": self.notebook_url
}BrowserFactory.launch_persistent_context method · python · L19-L43 (25 LOC)skills/extensions/notebooklm/scripts/browser_utils.py
def launch_persistent_context(
playwright: Playwright,
headless: bool = True,
user_data_dir: str = str(BROWSER_PROFILE_DIR)
) -> BrowserContext:
"""
Launch a persistent browser context with anti-detection features
and cookie workaround.
"""
# Launch persistent context
context = playwright.chromium.launch_persistent_context(
user_data_dir=user_data_dir,
channel="chrome", # Use real Chrome
headless=headless,
no_viewport=True,
ignore_default_args=["--enable-automation"],
user_agent=USER_AGENT,
args=BROWSER_ARGS
)
# Cookie Workaround for Playwright bug #36139
# Session cookies (expires=-1) don't persist in user_data_dir automatically
BrowserFactory._inject_cookies(context)
return contextBrowserFactory._inject_cookies method · python · L46-L56 (11 LOC)skills/extensions/notebooklm/scripts/browser_utils.py
def _inject_cookies(context: BrowserContext):
"""Inject cookies from state.json if available"""
if STATE_FILE.exists():
try:
with open(STATE_FILE, 'r') as f:
state = json.load(f)
if 'cookies' in state and len(state['cookies']) > 0:
context.add_cookies(state['cookies'])
# print(f" 🔧 Injected {len(state['cookies'])} cookies from state.json")
except Exception as e:
print(f" ⚠️ Could not load state.json: {e}")StealthUtils.human_type method · python · L68-L89 (22 LOC)skills/extensions/notebooklm/scripts/browser_utils.py
def human_type(page: Page, selector: str, text: str, wpm_min: int = 320, wpm_max: int = 480):
"""Type with human-like speed"""
element = page.query_selector(selector)
if not element:
# Try waiting if not immediately found
try:
element = page.wait_for_selector(selector, timeout=2000)
except:
pass
if not element:
print(f"⚠️ Element not found for typing: {selector}")
return
# Click to focus
element.click()
# Type
for char in text:
element.type(char, delay=random.uniform(25, 75))
if random.random() < 0.05:
time.sleep(random.uniform(0.15, 0.4))StealthUtils.realistic_click method · python · L92-L107 (16 LOC)skills/extensions/notebooklm/scripts/browser_utils.py
def realistic_click(page: Page, selector: str):
"""Click with realistic movement"""
element = page.query_selector(selector)
if not element:
return
# Optional: Move mouse to element (simplified)
box = element.bounding_box()
if box:
x = box['x'] + box['width'] / 2
y = box['y'] + box['height'] / 2
page.mouse.move(x, y, steps=5)
StealthUtils.random_delay(100, 300)
element.click()
StealthUtils.random_delay(100, 300)Repobility's GitHub App fixes findings like these · https://github.com/apps/repobility-bot
CleanupManager.get_cleanup_paths method · python · L30-L114 (85 LOC)skills/extensions/notebooklm/scripts/cleanup_manager.py
def get_cleanup_paths(self, preserve_library: bool = False) -> Dict[str, Any]:
"""
Get paths that would be cleaned up
Args:
preserve_library: Keep library.json if True
Returns:
Dict with paths and sizes
Note: .venv is NEVER deleted - it's part of the skill infrastructure
"""
paths = {
'browser_state': [],
'sessions': [],
'library': [],
'auth': [],
'other': []
}
total_size = 0
if self.data_dir.exists():
# Browser state
browser_state_dir = self.data_dir / "browser_state"
if browser_state_dir.exists():
for item in browser_state_dir.iterdir():
size = self._get_size(item)
paths['browser_state'].append({
'path': str(item),
'size': size,
'type': 'dir' if item.isCleanupManager._get_size method · python · L116-L129 (14 LOC)skills/extensions/notebooklm/scripts/cleanup_manager.py
def _get_size(self, path: Path) -> int:
"""Get size of file or directory in bytes"""
if path.is_file():
return path.stat().st_size
elif path.is_dir():
total = 0
try:
for item in path.rglob('*'):
if item.is_file():
total += item.stat().st_size
except Exception:
pass
return total
return 0CleanupManager._format_size method · python · L131-L137 (7 LOC)skills/extensions/notebooklm/scripts/cleanup_manager.py
def _format_size(self, size: int) -> str:
"""Format size in human-readable form"""
for unit in ['B', 'KB', 'MB', 'GB']:
if size < 1024:
return f"{size:.1f} {unit}"
size /= 1024
return f"{size:.1f} TB"CleanupManager.perform_cleanup method · python · L139-L197 (59 LOC)skills/extensions/notebooklm/scripts/cleanup_manager.py
def perform_cleanup(
self,
preserve_library: bool = False,
dry_run: bool = False
) -> Dict[str, Any]:
"""
Perform the actual cleanup
Args:
preserve_library: Keep library.json if True
dry_run: Preview only, don't delete
Returns:
Dict with cleanup results
"""
cleanup_data = self.get_cleanup_paths(preserve_library)
deleted_items = []
failed_items = []
deleted_size = 0
if dry_run:
return {
'dry_run': True,
'would_delete': cleanup_data['total_items'],
'would_free': cleanup_data['total_size']
}
# Perform deletion
for category, items in cleanup_data['categories'].items():
for item_info in items:
path = Path(item_info['path'])
try:
if path.exists():
if path.is_dir():
CleanupManager.print_cleanup_preview method · python · L199-L223 (25 LOC)skills/extensions/notebooklm/scripts/cleanup_manager.py
def print_cleanup_preview(self, preserve_library: bool = False):
"""Print a preview of what will be cleaned"""
data = self.get_cleanup_paths(preserve_library)
print("\n🔍 Cleanup Preview")
print("=" * 60)
for category, items in data['categories'].items():
if items:
print(f"\n📁 {category.replace('_', ' ').title()}:")
for item in items:
path = Path(item['path'])
size_str = self._format_size(item['size'])
type_icon = "📂" if item['type'] == 'dir' else "📄"
print(f" {type_icon} {path.name:<30} {size_str:>10}")
print("\n" + "=" * 60)
print(f"Total items: {data['total_items']}")
print(f"Total size: {self._format_size(data['total_size'])}")
if preserve_library:
print("\n📚 Library will be preserved")
print("\nThis preview shows what would be deleted.")
print("Use --cmain function · python · L226-L298 (73 LOC)skills/extensions/notebooklm/scripts/cleanup_manager.py
def main():
"""Command-line interface for cleanup management"""
parser = argparse.ArgumentParser(
description='Clean up NotebookLM skill data',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Preview what will be deleted
python cleanup_manager.py
# Perform cleanup (delete everything)
python cleanup_manager.py --confirm
# Cleanup but keep library
python cleanup_manager.py --confirm --preserve-library
# Force cleanup without preview
python cleanup_manager.py --confirm --force
"""
)
parser.add_argument(
'--confirm',
action='store_true',
help='Actually perform the cleanup (without this, only preview)'
)
parser.add_argument(
'--preserve-library',
action='store_true',
help='Keep the notebook library (library.json)'
)
parser.add_argument(
'--force',
action='store_true',
help='Skip confirmation prompt'
)
ensure_venv_and_run function · python · L13-L77 (65 LOC)skills/extensions/notebooklm/scripts/__init__.py
def ensure_venv_and_run():
"""
Ensure virtual environment exists and run the requested script.
This is called when any script is imported or run directly.
"""
# Only do this if we're not already in the skill's venv
skill_dir = Path(__file__).parent.parent
venv_dir = skill_dir / ".venv"
# Check if we're in a venv
in_venv = hasattr(sys, 'real_prefix') or (
hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix
)
# Check if it's OUR venv
if in_venv:
venv_path = Path(sys.prefix)
if venv_path == venv_dir:
# We're already in the correct venv
return
# We need to set up or switch to our venv
if not venv_dir.exists():
print("🔧 First-time setup detected...")
print(" Creating isolated environment for NotebookLM skill...")
print(" This ensures clean dependency management...")
# Create venv
import venv
venv.create(venv_dir, with_pip=TNotebookLibrary.__init__ method · python · L20-L32 (13 LOC)skills/extensions/notebooklm/scripts/notebook_manager.py
def __init__(self):
"""Initialize the notebook library"""
# Store data within the skill directory
skill_dir = Path(__file__).parent.parent
self.data_dir = skill_dir / "data"
self.data_dir.mkdir(parents=True, exist_ok=True)
self.library_file = self.data_dir / "library.json"
self.notebooks: Dict[str, Dict[str, Any]] = {}
self.active_notebook_id: Optional[str] = None
# Load existing library
self._load_library()All rows above produced by Repobility · https://repobility.com
NotebookLibrary._load_library method · python · L34-L48 (15 LOC)skills/extensions/notebooklm/scripts/notebook_manager.py
def _load_library(self):
"""Load library from disk"""
if self.library_file.exists():
try:
with open(self.library_file, 'r') as f:
data = json.load(f)
self.notebooks = data.get('notebooks', {})
self.active_notebook_id = data.get('active_notebook_id')
print(f"📚 Loaded library with {len(self.notebooks)} notebooks")
except Exception as e:
print(f"⚠️ Error loading library: {e}")
self.notebooks = {}
self.active_notebook_id = None
else:
self._save_library()NotebookLibrary._save_library method · python · L50-L61 (12 LOC)skills/extensions/notebooklm/scripts/notebook_manager.py
def _save_library(self):
"""Save library to disk"""
try:
data = {
'notebooks': self.notebooks,
'active_notebook_id': self.active_notebook_id,
'updated_at': datetime.now().isoformat()
}
with open(self.library_file, 'w') as f:
json.dump(data, f, indent=2)
except Exception as e:
print(f"❌ Error saving library: {e}")NotebookLibrary.add_notebook method · python · L63-L121 (59 LOC)skills/extensions/notebooklm/scripts/notebook_manager.py
def add_notebook(
self,
url: str,
name: str,
description: str,
topics: List[str],
content_types: Optional[List[str]] = None,
use_cases: Optional[List[str]] = None,
tags: Optional[List[str]] = None
) -> Dict[str, Any]:
"""
Add a new notebook to the library
Args:
url: NotebookLM notebook URL
name: Display name for the notebook
description: What's in this notebook
topics: Topics covered
content_types: Types of content (optional)
use_cases: When to use this notebook (optional)
tags: Additional tags for organization (optional)
Returns:
The created notebook object
"""
# Generate ID from name
notebook_id = name.lower().replace(' ', '-').replace('_', '-')
# Check for duplicates
if notebook_id in self.notebooks:
raise ValueError(f"Notebook withNotebookLibrary.remove_notebook method · python · L123-L148 (26 LOC)skills/extensions/notebooklm/scripts/notebook_manager.py
def remove_notebook(self, notebook_id: str) -> bool:
"""
Remove a notebook from the library
Args:
notebook_id: ID of notebook to remove
Returns:
True if removed, False if not found
"""
if notebook_id in self.notebooks:
del self.notebooks[notebook_id]
# Clear active if it was removed
if self.active_notebook_id == notebook_id:
self.active_notebook_id = None
# Set new active if there are other notebooks
if self.notebooks:
self.active_notebook_id = list(self.notebooks.keys())[0]
self._save_library()
print(f"✅ Removed notebook: {notebook_id}")
return True
print(f"⚠️ Notebook not found: {notebook_id}")
return FalseNotebookLibrary.update_notebook method · python · L150-L196 (47 LOC)skills/extensions/notebooklm/scripts/notebook_manager.py
def update_notebook(
self,
notebook_id: str,
name: Optional[str] = None,
description: Optional[str] = None,
topics: Optional[List[str]] = None,
content_types: Optional[List[str]] = None,
use_cases: Optional[List[str]] = None,
tags: Optional[List[str]] = None,
url: Optional[str] = None
) -> Dict[str, Any]:
"""
Update notebook metadata
Args:
notebook_id: ID of notebook to update
Other args: Fields to update (None = keep existing)
Returns:
Updated notebook object
"""
if notebook_id not in self.notebooks:
raise ValueError(f"Notebook not found: {notebook_id}")
notebook = self.notebooks[notebook_id]
# Update fields if provided
if name is not None:
notebook['name'] = name
if description is not None:
notebook['description'] = description
if topics is notNotebookLibrary.search_notebooks method · python · L206-L232 (27 LOC)skills/extensions/notebooklm/scripts/notebook_manager.py
def search_notebooks(self, query: str) -> List[Dict[str, Any]]:
"""
Search notebooks by query
Args:
query: Search query (searches name, description, topics, tags)
Returns:
List of matching notebooks
"""
query_lower = query.lower()
results = []
for notebook in self.notebooks.values():
# Search in various fields
searchable = [
notebook['name'].lower(),
notebook['description'].lower(),
' '.join(notebook['topics']).lower(),
' '.join(notebook['tags']).lower(),
' '.join(notebook.get('use_cases', [])).lower()
]
if any(query_lower in field for field in searchable):
results.append(notebook)
return resultsNotebookLibrary.select_notebook method · python · L234-L252 (19 LOC)skills/extensions/notebooklm/scripts/notebook_manager.py
def select_notebook(self, notebook_id: str) -> Dict[str, Any]:
"""
Set a notebook as active
Args:
notebook_id: ID of notebook to activate
Returns:
The activated notebook
"""
if notebook_id not in self.notebooks:
raise ValueError(f"Notebook not found: {notebook_id}")
self.active_notebook_id = notebook_id
self._save_library()
notebook = self.notebooks[notebook_id]
print(f"✅ Activated notebook: {notebook['name']}")
return notebookNotebookLibrary.increment_use_count method · python · L260-L278 (19 LOC)skills/extensions/notebooklm/scripts/notebook_manager.py
def increment_use_count(self, notebook_id: str) -> Dict[str, Any]:
"""
Increment usage counter for a notebook
Args:
notebook_id: ID of notebook that was used
Returns:
Updated notebook
"""
if notebook_id not in self.notebooks:
raise ValueError(f"Notebook not found: {notebook_id}")
notebook = self.notebooks[notebook_id]
notebook['use_count'] += 1
notebook['last_used'] = datetime.now().isoformat()
self._save_library()
return notebookRepobility — same analyzer, your code, free for public repos · /scan/
NotebookLibrary.get_stats method · python · L280-L305 (26 LOC)skills/extensions/notebooklm/scripts/notebook_manager.py
def get_stats(self) -> Dict[str, Any]:
"""Get library statistics"""
total_notebooks = len(self.notebooks)
total_topics = set()
total_use_count = 0
for notebook in self.notebooks.values():
total_topics.update(notebook['topics'])
total_use_count += notebook['use_count']
# Find most used
most_used = None
if self.notebooks:
most_used = max(
self.notebooks.values(),
key=lambda n: n['use_count']
)
return {
'total_notebooks': total_notebooks,
'total_topics': len(total_topics),
'total_use_count': total_use_count,
'active_notebook': self.get_active_notebook(),
'most_used_notebook': most_used,
'library_path': str(self.library_file)
}main function · python · L308-L406 (99 LOC)skills/extensions/notebooklm/scripts/notebook_manager.py
def main():
"""Command-line interface for notebook management"""
parser = argparse.ArgumentParser(description='Manage NotebookLM library')
subparsers = parser.add_subparsers(dest='command', help='Commands')
# Add command
add_parser = subparsers.add_parser('add', help='Add a notebook')
add_parser.add_argument('--url', required=True, help='NotebookLM URL')
add_parser.add_argument('--name', required=True, help='Display name')
add_parser.add_argument('--description', required=True, help='Description')
add_parser.add_argument('--topics', required=True, help='Comma-separated topics')
add_parser.add_argument('--use-cases', help='Comma-separated use cases')
add_parser.add_argument('--tags', help='Comma-separated tags')
# List command
subparsers.add_parser('list', help='List all notebooks')
# Search command
search_parser = subparsers.add_parser('search', help='Search notebooks')
search_parser.add_argument('--query', required=Trget_venv_python function · python · L13-L23 (11 LOC)skills/extensions/notebooklm/scripts/run.py
def get_venv_python():
"""Get the virtual environment Python executable"""
skill_dir = Path(__file__).parent.parent
venv_dir = skill_dir / ".venv"
if os.name == 'nt': # Windows
venv_python = venv_dir / "Scripts" / "python.exe"
else: # Unix/Linux/Mac
venv_python = venv_dir / "bin" / "python"
return venv_python