"""Verb-first CLI commands for NotebookLM Tools. This module provides alternative command structure: - nlm create notebook "Title" (vs nlm notebook create "Title") - nlm list notebooks (vs nlm notebook list) - nlm delete notebook (vs nlm notebook delete ) Both command structures coexist for user flexibility. """ import typer from notebooklm_tools.cli.commands.alias import ( delete_alias, get_alias, list_aliases, set_alias, ) from notebooklm_tools.cli.commands.chat import ( configure_chat, ) from notebooklm_tools.cli.commands.config import ( get_config_value, set_config_value, show_config, ) from notebooklm_tools.cli.commands.download import ( download_audio, download_data_table, download_infographic, download_mind_map, download_report, download_slide_deck, download_video, ) # Import existing command implementations from notebooklm_tools.cli.commands.notebook import ( create_notebook, delete_notebook, describe_notebook, get_notebook, list_notebooks, query_notebook, rename_notebook, ) from notebooklm_tools.cli.commands.research import ( check_status as research_status, ) from notebooklm_tools.cli.commands.research import ( import_research, start_research, ) from notebooklm_tools.cli.commands.skill import ( install as skill_install, ) from notebooklm_tools.cli.commands.skill import ( list_tools as skill_list, ) from notebooklm_tools.cli.commands.skill import ( show as skill_show, ) from notebooklm_tools.cli.commands.skill import ( uninstall as skill_uninstall, ) from notebooklm_tools.cli.commands.skill import ( update as skill_update, ) from notebooklm_tools.cli.commands.source import ( add_source, delete_source, describe_source, get_source, get_source_content, list_sources, list_stale_sources, rename_source, sync_sources, ) from notebooklm_tools.cli.commands.studio import ( create_audio, create_data_table, create_flashcards, create_infographic, create_mindmap, create_quiz, create_report, create_slides, create_video, studio_delete, studio_rename, studio_status, ) # ============================================================================= # CREATE verb # ============================================================================= create_app = typer.Typer(help="Create resources (notebooks, audio, video, etc)") @create_app.command("notebook") def create_notebook_verb( title: str = typer.Argument(..., help="Notebook title"), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), ) -> None: """Create a new notebook.""" create_notebook(title=title, profile=profile) @create_app.command("audio") def create_audio_verb( notebook: str = typer.Argument(..., help="Notebook ID or alias"), format_opt: str | None = typer.Option( None, "--format", "-f", help="Audio format (deep_dive/brief/critique/debate)" ), length: str | None = typer.Option( None, "--length", "-l", help="Audio length (short/default/long)" ), language: str | None = typer.Option( None, "--language", help="BCP-47 language code (en, es, fr, de, ja)" ), focus: str | None = typer.Option(None, "--focus", help="Optional focus topic"), source_ids: str | None = typer.Option( None, "--source-ids", "-s", help="Comma-separated source IDs" ), confirm: bool = typer.Option(False, "--confirm", "-y", help="Skip confirmation prompt"), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), ) -> None: """Create an audio overview.""" create_audio( notebook_id=notebook, format=format_opt or "deep_dive", length=length or "default", language=language or "", focus=focus, source_ids=source_ids, confirm=confirm, profile=profile, ) @create_app.command("video") def create_video_verb( notebook: str = typer.Argument(..., help="Notebook ID or alias"), format_opt: str | None = typer.Option( None, "--format", "-f", help="Format: explainer, brief, cinematic, short" ), style: str | None = typer.Option( None, "--style", "-s", help="Visual style: auto_select, custom, classic, whiteboard, kawaii, anime, watercolor, retro_print, heritage, paper_craft", ), style_prompt: str | None = typer.Option( None, "--style-prompt", help="Custom visual style description (implies --style custom unless another style is explicitly set)", ), language: str | None = typer.Option(None, "--language", help="BCP-47 language code"), focus: str | None = typer.Option(None, "--focus", help="Optional focus topic"), source_ids: str | None = typer.Option(None, "--source-ids", help="Comma-separated source IDs"), confirm: bool = typer.Option(False, "--confirm", "-y", help="Skip confirmation prompt"), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), ) -> None: """Create a video overview.""" create_video( notebook_id=notebook, format=format_opt or "explainer", style=style or "auto_select", style_prompt=style_prompt or "", language=language or "", focus=focus or "", source_ids=source_ids, confirm=confirm, profile=profile, ) @create_app.command("report") def create_report_verb( notebook: str = typer.Argument(..., help="Notebook ID or alias"), format_opt: str | None = typer.Option( None, "--format", "-f", help="Format: 'Briefing Doc', 'Study Guide', 'Blog Post', 'Create Your Own'", ), prompt: str | None = typer.Option( None, "--prompt", help="Custom prompt (required for 'Create Your Own')" ), language: str | None = typer.Option(None, "--language", help="BCP-47 language code"), source_ids: str | None = typer.Option( None, "--source-ids", "-s", help="Comma-separated source IDs" ), confirm: bool = typer.Option(False, "--confirm", "-y", help="Skip confirmation prompt"), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), ) -> None: """Create a report.""" create_report( notebook_id=notebook, format=format_opt or "Briefing Doc", prompt=prompt or "", language=language or "", source_ids=source_ids, confirm=confirm, profile=profile, ) @create_app.command("infographic") def create_infographic_verb( notebook: str = typer.Argument(..., help="Notebook ID or alias"), orientation: str | None = typer.Option( None, "--orientation", "-o", help="Orientation: landscape, portrait, square" ), detail: str | None = typer.Option( None, "--detail", "-d", help="Detail level: concise, standard, detailed" ), style: str | None = typer.Option( None, "--style", help="Visual style: auto_select, sketch_note, professional, bento_grid, editorial, instructional, bricks, clay, anime, kawaii, scientific", ), language: str | None = typer.Option(None, "--language", help="BCP-47 language code"), focus: str | None = typer.Option(None, "--focus", help="Optional focus topic"), source_ids: str | None = typer.Option( None, "--source-ids", "-s", help="Comma-separated source IDs" ), confirm: bool = typer.Option(False, "--confirm", "-y", help="Skip confirmation prompt"), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), ) -> None: """Create an infographic.""" create_infographic( notebook_id=notebook, orientation=orientation or "landscape", detail=detail or "standard", style=style or "auto_select", language=language or "", focus=focus or "", source_ids=source_ids, confirm=confirm, profile=profile, ) @create_app.command("slides") def create_slides_verb( notebook: str = typer.Argument(..., help="Notebook ID or alias"), format_opt: str | None = typer.Option( None, "--format", "-f", help="Format: detailed_deck, presenter_slides" ), length: str | None = typer.Option(None, "--length", "-l", help="Length: short, default"), language: str | None = typer.Option(None, "--language", help="BCP-47 language code"), focus: str | None = typer.Option(None, "--focus", help="Optional focus topic"), source_ids: str | None = typer.Option( None, "--source-ids", "-s", help="Comma-separated source IDs" ), confirm: bool = typer.Option(False, "--confirm", "-y", help="Skip confirmation prompt"), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), ) -> None: """Create a slide deck.""" create_slides( notebook_id=notebook, format=format_opt or "detailed_deck", length=length or "default", language=language or "", focus=focus or "", source_ids=source_ids, confirm=confirm, profile=profile, ) @create_app.command("quiz") def create_quiz_verb( notebook: str = typer.Argument(..., help="Notebook ID or alias"), count: int | None = typer.Option(None, "--count", "-c", help="Number of questions"), difficulty: int | None = typer.Option( None, "--difficulty", "-d", help="Difficulty 1-5 (1=easy, 5=hard)" ), focus: str | None = typer.Option(None, "--focus", help="Optional focus topic"), source_ids: str | None = typer.Option( None, "--source-ids", "-s", help="Comma-separated source IDs" ), confirm: bool = typer.Option(False, "--confirm", "-y", help="Skip confirmation prompt"), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), ) -> None: """Create a quiz.""" create_quiz( notebook_id=notebook, count=count or 2, difficulty=difficulty or 2, focus=focus or "", source_ids=source_ids, confirm=confirm, profile=profile, ) @create_app.command("flashcards") def create_flashcards_verb( notebook: str = typer.Argument(..., help="Notebook ID or alias"), difficulty: str | None = typer.Option( None, "--difficulty", "-d", help="Difficulty: easy, medium, hard" ), focus: str | None = typer.Option(None, "--focus", help="Optional focus topic"), source_ids: str | None = typer.Option( None, "--source-ids", "-s", help="Comma-separated source IDs" ), confirm: bool = typer.Option(False, "--confirm", "-y", help="Skip confirmation prompt"), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), ) -> None: """Create flashcards.""" create_flashcards( notebook_id=notebook, difficulty=difficulty or "medium", focus=focus or "", source_ids=source_ids, confirm=confirm, profile=profile, ) @create_app.command("data-table") def create_data_table_verb( notebook: str = typer.Argument(..., help="Notebook ID or alias"), description: str = typer.Argument(..., help="Description of the data table to create"), language: str | None = typer.Option(None, "--language", help="BCP-47 language code"), source_ids: str | None = typer.Option( None, "--source-ids", "-s", help="Comma-separated source IDs" ), confirm: bool = typer.Option(False, "--confirm", "-y", help="Skip confirmation prompt"), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), ) -> None: """Create a data table.""" create_data_table( notebook_id=notebook, description=description, language=language or "", source_ids=source_ids, confirm=confirm, profile=profile, ) @create_app.command("mindmap") def create_mindmap_verb( notebook: str = typer.Argument(..., help="Notebook ID or alias"), title: str | None = typer.Option(None, "--title", "-t", help="Mind map title"), source_ids: str | None = typer.Option( None, "--source-ids", "-s", help="Comma-separated source IDs" ), confirm: bool = typer.Option(False, "--confirm", "-y", help="Skip confirmation prompt"), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), ) -> None: """Create a mind map.""" create_mindmap( notebook_id=notebook, title=title or "Mind Map", source_ids=source_ids, confirm=confirm, profile=profile, ) # ============================================================================= # LIST verb # ============================================================================= list_app = typer.Typer(help="List resources (notebooks, sources, artifacts)") @list_app.command("notebooks") def list_notebooks_verb( full: bool = typer.Option(False, "--full", "-a", help="Show all columns"), json_output: bool = typer.Option(False, "--json", help="Output as JSON"), quiet: bool = typer.Option(False, "--quiet", "-q", help="Output IDs only"), title: bool = typer.Option(False, "--title", "-t", help="Show ID: Title format"), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), ) -> None: """List all notebooks.""" list_notebooks(full=full, json_output=json_output, quiet=quiet, title=title, profile=profile) @list_app.command("sources") def list_sources_verb( notebook: str = typer.Argument(..., help="Notebook ID or alias"), full: bool = typer.Option(False, "--full", "-a", help="Show all columns"), drive: bool = typer.Option( False, "--drive", "-d", help="Show Drive sources with freshness status" ), skip_freshness: bool = typer.Option( False, "--skip-freshness", "-S", help="Skip freshness checks (faster, use with --drive)" ), json_output: bool = typer.Option(False, "--json", help="Output as JSON"), quiet: bool = typer.Option(False, "--quiet", "-q", help="Output IDs only"), url: bool = typer.Option(False, "--url", "-u", help="Output as ID: URL"), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), ) -> None: """List sources in a notebook.""" list_sources( notebook_id=notebook, full=full, drive=drive, skip_freshness=skip_freshness, json_output=json_output, quiet=quiet, url=url, profile=profile, ) @list_app.command("artifacts") def list_artifacts_verb( notebook: str = typer.Argument(..., help="Notebook ID or alias"), full: bool = typer.Option(False, "--full", "-a", help="Show all details"), json_output: bool = typer.Option(False, "--json", "-j", help="Output as JSON"), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), ) -> None: """List all studio artifacts.""" studio_status(notebook_id=notebook, full=full, json_output=json_output, profile=profile) @list_app.command("aliases") def list_aliases_verb() -> None: """List all aliases.""" list_aliases() @list_app.command("stale-sources") def list_stale_sources_verb( notebook: str = typer.Argument(..., help="Notebook ID or alias"), json_output: bool = typer.Option(False, "--json", help="Output as JSON"), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), ) -> None: """List Drive sources that need syncing.""" list_stale_sources(notebook_id=notebook, json_output=json_output, profile=profile) @list_app.command("skills") def list_skills_verb() -> None: """List available skills and installation status.""" skill_list() # ============================================================================= # GET verb # ============================================================================= get_app = typer.Typer(help="Get details about resources") @get_app.command("notebook") def get_notebook_verb( notebook: str = typer.Argument(..., help="Notebook ID or alias"), json_output: bool = typer.Option(False, "--json", help="Output as JSON"), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), ) -> None: """Get notebook details.""" get_notebook(notebook_id=notebook, json_output=json_output, profile=profile) @get_app.command("source") def get_source_verb( source: str = typer.Argument(..., help="Source ID"), json_output: bool = typer.Option(False, "--json", help="Output as JSON"), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), ) -> None: """Get source details.""" get_source(source_id=source, json_output=json_output, profile=profile) @get_app.command("config") def get_config_verb( key: str = typer.Argument(..., help="Configuration key (e.g. output.format)"), ) -> None: """Get configuration value.""" get_config_value(key=key) @get_app.command("alias") def get_alias_verb( name: str = typer.Argument(..., help="Alias name"), ) -> None: """Get alias value.""" get_alias(name=name) # ============================================================================= # DELETE verb # ============================================================================= delete_app = typer.Typer(help="Delete resources (notebooks, sources, artifacts)") @delete_app.command("notebook") def delete_notebook_verb( notebook: str = typer.Argument(..., help="Notebook ID or alias"), confirm: bool = typer.Option(False, "--confirm", "-y", help="Skip confirmation prompt"), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), ) -> None: """Delete a notebook permanently.""" delete_notebook(notebook_id=notebook, confirm=confirm, profile=profile) @delete_app.command("source") def delete_source_verb( source: str = typer.Argument(..., help="Source ID"), confirm: bool = typer.Option(False, "--confirm", "-y", help="Skip confirmation prompt"), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), ) -> None: """Delete a source from notebook.""" delete_source(source_ids=[source], confirm=confirm, profile=profile) @delete_app.command("artifact") def delete_artifact_verb( notebook: str = typer.Argument(..., help="Notebook ID or alias"), artifact: str = typer.Argument(..., help="Artifact ID"), confirm: bool = typer.Option(False, "--confirm", "-y", help="Skip confirmation prompt"), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), ) -> None: """Delete a studio artifact permanently.""" studio_delete(notebook_id=notebook, artifact_id=artifact, confirm=confirm, profile=profile) @delete_app.command("alias") def delete_alias_verb( name: str = typer.Argument(..., help="Alias name"), confirm: bool = typer.Option(False, "--confirm", "-y", help="Skip confirmation prompt"), ) -> None: """Delete an alias.""" delete_alias(name=name, confirm=confirm) # ============================================================================= # ADD verb (for sources) # ============================================================================= add_app = typer.Typer(help="Add resources (sources to notebooks)") @add_app.command("url") def add_url_verb( notebook: str = typer.Argument(..., help="Notebook ID or alias"), url_arg: str = typer.Argument(..., help="URL to add"), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), wait: bool = typer.Option(False, "--wait", "-w", help="Wait for source processing to complete"), wait_timeout: float = typer.Option(600.0, "--wait-timeout", help="Wait timeout in seconds"), ) -> None: """Add a URL source to notebook.""" # Explicitly wrap the single URL string in a list so it doesn't get unpacked as characters add_source( notebook, url=[url_arg], text=None, drive=None, youtube=None, file=None, wait=wait, wait_timeout=wait_timeout, profile=profile, ) @add_app.command("text") def add_text_verb( notebook: str = typer.Argument(..., help="Notebook ID or alias"), text_arg: str = typer.Argument(..., help="Text content to add"), title: str | None = typer.Option(None, "--title", help="Source title"), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), wait: bool = typer.Option(False, "--wait", "-w", help="Wait for source processing to complete"), wait_timeout: float = typer.Option(600.0, "--wait-timeout", help="Wait timeout in seconds"), ) -> None: """Add text source to notebook.""" # Explicitly pass None for unused source types to avoid typer.Option resolution issues add_source( notebook, url=None, text=text_arg, drive=None, youtube=None, file=None, title=title or "Pasted Text", wait=wait, wait_timeout=wait_timeout, profile=profile, ) @add_app.command("drive") def add_drive_verb( notebook: str = typer.Argument(..., help="Notebook ID or alias"), document_id: str = typer.Argument(..., help="Google Drive document ID"), title: str | None = typer.Option(None, "--title", help="Source title"), doc_type: str = typer.Option("doc", "--type", help="Drive doc type: doc, slides, sheets, pdf"), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), wait: bool = typer.Option(False, "--wait", "-w", help="Wait for source processing to complete"), wait_timeout: float = typer.Option(600.0, "--wait-timeout", help="Wait timeout in seconds"), ) -> None: """Add a Google Drive source to notebook.""" # Explicitly pass None for unused source types to avoid typer.Option resolution issues add_source( notebook, url=None, text=None, drive=document_id, youtube=None, file=None, title=title or f"Drive Document ({document_id[:8]}...)", doc_type=doc_type, wait=wait, wait_timeout=wait_timeout, profile=profile, ) # ============================================================================= # RENAME verb # ============================================================================= rename_app = typer.Typer(help="Rename resources") @rename_app.command("notebook") def rename_notebook_verb( notebook: str = typer.Argument(..., help="Notebook ID or alias"), title: str = typer.Argument(..., help="New title"), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), ) -> None: """Rename a notebook.""" rename_notebook(notebook_id=notebook, new_title=title, profile=profile) @rename_app.command("source") def rename_source_verb( source_id: str = typer.Argument(..., help="Source ID"), title: str = typer.Argument(..., help="New title"), notebook_id: str = typer.Option( ..., "--notebook", "-n", help="Notebook ID containing the source" ), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), ) -> None: """Rename a source.""" rename_source(source_id=source_id, title=title, notebook_id=notebook_id, profile=profile) @rename_app.command("studio") def rename_studio_verb( artifact: str = typer.Argument(..., help="Artifact ID to rename"), title: str = typer.Argument(..., help="New title"), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), ) -> None: """Rename a studio artifact.""" studio_rename(artifact_id=artifact, new_title=title, profile=profile) # ============================================================================= # STATUS verb # ============================================================================= status_app = typer.Typer(help="Check status of resources") @status_app.command("artifacts") def status_artifacts_verb( notebook: str = typer.Argument(..., help="Notebook ID or alias"), full: bool = typer.Option(False, "--full", "-a", help="Show all details"), json_output: bool = typer.Option(False, "--json", "-j", help="Output as JSON"), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), ) -> None: """Check status of studio artifacts.""" studio_status(notebook_id=notebook, full=full, json_output=json_output, profile=profile) @status_app.command("research") def status_research_verb( notebook: str = typer.Argument(..., help="Notebook ID or alias"), task_id: str | None = typer.Option(None, "--task-id", "-t", help="Specific task ID to check"), compact: bool = typer.Option(True, "--compact/--full", help="Show compact or full details"), poll_interval: int = typer.Option(30, "--poll-interval", help="Seconds between status checks"), max_wait: int = typer.Option( 300, "--max-wait", help="Maximum seconds to wait (0 for single check)" ), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), ) -> None: """Check status of research task.""" research_status( notebook_id=notebook, task_id=task_id, compact=compact, poll_interval=poll_interval, max_wait=max_wait, profile=profile, ) # ============================================================================= # DESCRIBE verb # ============================================================================= describe_app = typer.Typer(help="Get AI-generated descriptions and summaries") @describe_app.command("notebook") def describe_notebook_verb( notebook: str = typer.Argument(..., help="Notebook ID or alias"), json_output: bool = typer.Option(False, "--json", help="Output as JSON"), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), ) -> None: """Get AI-generated notebook summary with suggested topics.""" describe_notebook(notebook_id=notebook, json_output=json_output, profile=profile) @describe_app.command("source") def describe_source_verb( source: str = typer.Argument(..., help="Source ID"), json_output: bool = typer.Option(False, "--json", help="Output as JSON"), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), ) -> None: """Get AI-generated source summary with keywords.""" describe_source(source_id=source, json_output=json_output, profile=profile) # ============================================================================= # QUERY verb (for chatting with notebooks) # ============================================================================= query_app = typer.Typer(help="Chat with notebook sources") @query_app.command("notebook") def query_notebook_verb( notebook: str = typer.Argument(..., help="Notebook ID or alias"), question: str = typer.Argument(..., help="Question to ask"), json_output: bool = typer.Option(False, "--json", help="Output as JSON"), conversation_id: str | None = typer.Option( None, "--conversation-id", "-c", help="Conversation ID for follow-up questions" ), source_ids: str | None = typer.Option( None, "--source-ids", "-s", help="Comma-separated source IDs to query (default: all)" ), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), timeout: float | None = typer.Option( None, "--timeout", "-t", help="Query timeout in seconds (default: 120)" ), ) -> None: """Chat with notebook sources.""" query_notebook( notebook_id=notebook, question=question, json_output=json_output, conversation_id=conversation_id, source_ids=source_ids, profile=profile, timeout=timeout, ) # ============================================================================= # SYNC verb (for Drive sources) # ============================================================================= sync_app = typer.Typer(help="Sync resources (Drive sources)") @sync_app.command("sources") def sync_sources_verb( notebook: str = typer.Argument(..., help="Notebook ID or alias"), source_ids: str | None = typer.Option( None, "--source-ids", "-s", help="Comma-separated source IDs to sync (default: all stale)" ), confirm: bool = typer.Option(False, "--confirm", "-y", help="Skip confirmation"), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), ) -> None: """Sync Drive sources with latest content.""" sync_sources(notebook_id=notebook, source_ids=source_ids, confirm=confirm, profile=profile) # ============================================================================= # CONTENT verb (for getting raw source content) # ============================================================================= content_app = typer.Typer(help="Get raw content from sources") @content_app.command("source") def content_source_verb( source: str = typer.Argument(..., help="Source ID"), json_output: bool = typer.Option(False, "--json", help="Output as JSON"), output: str | None = typer.Option(None, "--output", "-o", help="Write content to file"), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), ) -> None: """Get raw source content (no AI processing).""" get_source_content(source_id=source, json_output=json_output, output=output, profile=profile) # ============================================================================= # STALE verb (for listing stale Drive sources) # ============================================================================= stale_app = typer.Typer(help="List stale resources that need syncing") @stale_app.command("sources") def stale_sources_verb( notebook: str = typer.Argument(..., help="Notebook ID or alias"), json_output: bool = typer.Option(False, "--json", help="Output as JSON"), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), ) -> None: """List Drive sources that need syncing.""" list_stale_sources(notebook_id=notebook, json_output=json_output, profile=profile) # ============================================================================= # RESEARCH verb # ============================================================================= research_app = typer.Typer(help="Research and discover sources") @research_app.command("start") def research_start_verb( query: str = typer.Argument(..., help="What to search for"), source: str = typer.Option("web", "--source", "-s", help="Where to search: web or drive"), mode: str = typer.Option( "fast", "--mode", "-m", help="Research mode: fast (~30s, ~10 sources) or deep (~5min, ~40 sources, web only)", ), notebook_id: str | None = typer.Option( None, "--notebook-id", "-n", help="Add to existing notebook" ), title: str | None = typer.Option(None, "--title", "-t", help="Title for new notebook"), force: bool = typer.Option( False, "--force", "-f", help="Start new research even if one is already pending" ), auto_import: bool = typer.Option( False, "--auto-import", help="Wait for research to complete and auto-import sources" ), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), ) -> None: """Start a research task to find new sources.""" start_research( query=query, source=source, mode=mode, notebook_id=notebook_id, title=title, force=force, auto_import=auto_import, profile=profile, ) @research_app.command("import") def research_import_verb( notebook: str = typer.Argument(..., help="Notebook ID or alias"), task_id: str | None = typer.Argument( None, help="Research task ID (auto-detects if not provided)" ), indices: str | None = typer.Option( None, "--indices", "-i", help="Comma-separated indices of sources to import (default: all)" ), timeout: float = typer.Option( 300.0, "--timeout", "-t", help="Import timeout in seconds (default: 300)" ), cited_only: bool = typer.Option( False, "--cited-only", help="Import only sources cited by the research report (overrides --indices)", ), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), ) -> None: """Import discovered sources into notebook.""" import_research( notebook_id=notebook, task_id=task_id, indices=indices, timeout=timeout, cited_only=cited_only, profile=profile, ) # ============================================================================= # CONFIGURE verb (for chat settings) # ============================================================================= configure_app = typer.Typer(help="Configure settings") @configure_app.command("chat") def configure_chat_verb( notebook: str = typer.Argument(..., help="Notebook ID or alias"), goal: str | None = typer.Option( None, "--goal", "-g", help="Chat goal: default, learning_guide, or custom" ), prompt: str | None = typer.Option( None, "--prompt", help="Custom prompt (required when goal=custom, max 10000 chars)" ), response_length: str | None = typer.Option( None, "--response-length", "-r", help="Response length: default, longer, or shorter" ), profile: str | None = typer.Option(None, "--profile", "-p", help="Profile to use"), ) -> None: """Configure chat settings for a notebook.""" configure_chat( notebook_id=notebook, goal=goal or "default", prompt=prompt, response_length=response_length or "default", profile=profile, ) # ============================================================================= # DOWNLOAD verb # ============================================================================= download_app = typer.Typer(help="Download studio artifacts") @download_app.command("audio") def download_audio_verb( notebook: str = typer.Argument(..., help="Notebook ID or alias"), output: str | None = typer.Option(None, "--output", "-o", help="Output filename"), artifact_id: str | None = typer.Option(None, "--id", help="Specific artifact ID"), no_progress: bool = typer.Option(False, "--no-progress", help="Disable download progress bar"), ) -> None: """Download audio overview.""" download_audio( notebook_id=notebook, output=output, artifact_id=artifact_id, no_progress=no_progress ) @download_app.command("video") def download_video_verb( notebook: str = typer.Argument(..., help="Notebook ID or alias"), output: str | None = typer.Option(None, "--output", "-o", help="Output filename"), artifact_id: str | None = typer.Option(None, "--id", help="Specific artifact ID"), no_progress: bool = typer.Option(False, "--no-progress", help="Disable download progress bar"), ) -> None: """Download video overview.""" download_video( notebook_id=notebook, output=output, artifact_id=artifact_id, no_progress=no_progress ) @download_app.command("report") def download_report_verb( notebook: str = typer.Argument(..., help="Notebook ID or alias"), output: str | None = typer.Option(None, "--output", "-o", help="Output filename"), artifact_id: str | None = typer.Option(None, "--id", help="Specific artifact ID"), ) -> None: """Download report.""" download_report(notebook_id=notebook, output=output, artifact_id=artifact_id) @download_app.command("mind-map") def download_mindmap_verb( notebook: str = typer.Argument(..., help="Notebook ID or alias"), output: str | None = typer.Option(None, "--output", "-o", help="Output filename"), artifact_id: str | None = typer.Option(None, "--id", help="Specific artifact ID (note ID)"), ) -> None: """Download mind map.""" download_mind_map(notebook_id=notebook, output=output, artifact_id=artifact_id) @download_app.command("slides") def download_slides_verb( notebook: str = typer.Argument(..., help="Notebook ID or alias"), output: str | None = typer.Option(None, "--output", "-o", help="Output filename"), artifact_id: str | None = typer.Option(None, "--id", help="Specific artifact ID"), no_progress: bool = typer.Option(False, "--no-progress", help="Disable download progress bar"), format: str = typer.Option("pdf", "--format", "-f", help="Format: pdf, pptx"), ) -> None: """Download slide deck.""" download_slide_deck( notebook_id=notebook, output=output, artifact_id=artifact_id, no_progress=no_progress, format=format, ) @download_app.command("infographic") def download_infographic_verb( notebook: str = typer.Argument(..., help="Notebook ID or alias"), output: str | None = typer.Option(None, "--output", "-o", help="Output filename"), artifact_id: str | None = typer.Option(None, "--id", help="Specific artifact ID"), no_progress: bool = typer.Option(False, "--no-progress", help="Disable download progress bar"), ) -> None: """Download infographic.""" download_infographic( notebook_id=notebook, output=output, artifact_id=artifact_id, no_progress=no_progress ) @download_app.command("data-table") def download_data_table_verb( notebook: str = typer.Argument(..., help="Notebook ID or alias"), output: str | None = typer.Option(None, "--output", "-o", help="Output filename"), artifact_id: str | None = typer.Option(None, "--id", help="Specific artifact ID"), ) -> None: """Download data table.""" download_data_table(notebook_id=notebook, output=output, artifact_id=artifact_id) # ============================================================================= # SET verb (for aliases and config) # ============================================================================= set_app = typer.Typer(help="Set values (aliases, config)") @set_app.command("alias") def set_alias_verb( name: str = typer.Argument(..., help="Alias name (e.g. 'my-notebook')"), value: str = typer.Argument(..., help="ID to alias"), ) -> None: """Set an alias for an ID.""" set_alias(name=name, value=value) @set_app.command("config") def set_config_verb( key: str = typer.Argument(..., help="Configuration key (e.g. output.format)"), value: str = typer.Argument(..., help="Configuration value"), ) -> None: """Set configuration value.""" set_config_value(key=key, value=value) # ============================================================================= # SHOW verb (for displaying info) # ============================================================================= show_app = typer.Typer(help="Show information") @show_app.command("config") def show_config_verb( json_output: bool = typer.Option(False, "--json", help="Output as JSON"), ) -> None: """Show all configuration.""" show_config(json_output=json_output) @show_app.command("aliases") def show_aliases_verb() -> None: """Show all aliases.""" list_aliases() @show_app.command("skill") def show_skill_verb() -> None: """Show NotebookLM skill content.""" skill_show() # ============================================================================= # INSTALL verb (for skills) # ============================================================================= install_app = typer.Typer(help="Install resources (skills)") @install_app.command("skill") def install_skill_verb( tool: str = typer.Argument( ..., help="Tool to install skill for (claude-code, agents, hermes, opencode, antigravity, other)", ), level: str = typer.Option( "user", "--level", "-l", help="Install at user level (~/.config) or project level (./)" ), ) -> None: """Install NotebookLM skill for an AI tool.""" skill_install(tool=tool, level=level) # ============================================================================= # UNINSTALL verb (for skills) # ============================================================================= uninstall_app = typer.Typer(help="Uninstall resources (skills)") @uninstall_app.command("skill") def uninstall_skill_verb( tool: str = typer.Argument(..., help="Tool to uninstall skill from"), level: str = typer.Option("user", "--level", "-l", help="Uninstall from user or project level"), ) -> None: """Remove installed NotebookLM skill.""" skill_uninstall(tool=tool, level=level) # ============================================================================= # UPDATE verb (for skills) # ============================================================================= update_app = typer.Typer(help="Update resources (skills)") @update_app.command("skill") def update_skill_verb( tool: str | None = typer.Argument( None, help="Tool to update (omit to update all outdated skills)", ), ) -> None: """Update outdated skills to the current version.""" skill_update(tool=tool)