"""Click-based CLI for the NoteGraph ingestion pipeline. Subcommands: import - Import files or directories into NoteGraph watch - Start drop-folder watcher for continuous ingestion sync - One-shot Nextcloud inbox sync upload - Start web upload server """ import logging import sys from pathlib import Path import click from ingestion.config import IngestionConfig def _setup_logging(verbose: bool) -> None: """Configure logging based on verbosity.""" level = logging.DEBUG if verbose else logging.INFO logging.basicConfig( level=level, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", datefmt="%H:%M:%S", ) @click.group() @click.version_option(version="0.1.0", prog_name="notegraph-ingest") def cli(): """NoteGraph Ingestion — import documents into your knowledge base.""" pass @cli.command("import") @click.argument("path", type=click.Path(exists=True)) @click.option("--output-dir", "-o", type=click.Path(), default=None, help="Override output directory") @click.option("--create-tasks", is_flag=True, help="Create OrgMyLife tasks for action items") @click.option("--provider", type=str, default=None, help="Override LLM provider (openai, anthropic, etc.)") @click.option("--dry-run", is_flag=True, help="Show what would be created without writing files") @click.option("--no-commit", is_flag=True, help="Skip git auto-commit") @click.option("--verbose", "-v", is_flag=True, help="Enable debug logging") def import_cmd(path, output_dir, create_tasks, provider, dry_run, no_commit, verbose): """Import files or directories into NoteGraph. PATH can be a single file or a directory to scan recursively. """ _setup_logging(verbose) config = IngestionConfig() if provider: config.llm_provider = provider config.validate_api_key() from ingestion.discovery import discover_files from ingestion.pipeline import process_batch source_path = Path(path) files = discover_files(source_path) if not files: click.echo("No supported files found.") sys.exit(0) click.echo(f"Found {len(files)} file(s) to process.") if dry_run: click.echo("[DRY RUN] No files will be written.\n") def progress(current, total, filename): click.echo(f" [{current}/{total}] {filename}") result = process_batch( paths=files, config=config, dry_run=dry_run, no_commit=no_commit, create_tasks=create_tasks, output_dir=Path(output_dir) if output_dir else None, progress_callback=progress, ) # Summary click.echo("") click.echo(f"Done! Processed {result.total} file(s):") click.echo(f" ✓ Success: {result.success}") if result.failed: click.echo(f" ✗ Failed: {result.failed}") @cli.command("watch") @click.option("--verbose", "-v", is_flag=True, help="Enable debug logging") def watch_cmd(verbose): """Start drop-folder watcher for continuous ingestion.""" _setup_logging(verbose) from ingestion.watcher import start_watcher config = IngestionConfig() config.validate_api_key() click.echo(f"Watching inbox: {config.inbox_dir}") click.echo("Press Ctrl+C to stop.\n") try: start_watcher(config) except KeyboardInterrupt: click.echo("\nWatcher stopped.") @cli.command("sync") @click.option("--verbose", "-v", is_flag=True, help="Enable debug logging") def sync_cmd(verbose): """One-shot Nextcloud inbox sync.""" _setup_logging(verbose) from ingestion.integrations.nextcloud import NextcloudInboxSync config = IngestionConfig() if not config.nextcloud_inbox_url: click.echo("ERROR: NEXTCLOUD_INBOX_URL not configured.") sys.exit(1) config.validate_api_key() sync = NextcloudInboxSync(config) downloaded = sync.poll() if not downloaded: click.echo("No new files in Nextcloud inbox.") return click.echo(f"Downloaded {len(downloaded)} file(s) from Nextcloud.") # Process downloaded files from ingestion.pipeline import process_batch result = process_batch( paths=downloaded, config=config, ) # Mark processed files on Nextcloud for r in result.results: if r.success: sync.mark_processed(r.source_file.name) click.echo(f"Processed: {result.success} success, {result.failed} failed.") @cli.command("upload") @click.option("--host", default="0.0.0.0", help="Bind host") @click.option("--port", default=8001, type=int, help="Bind port") @click.option("--verbose", "-v", is_flag=True, help="Enable debug logging") def upload_cmd(host, port, verbose): """Start web upload server for mobile file ingestion.""" _setup_logging(verbose) import uvicorn click.echo(f"Starting upload server on {host}:{port}") uvicorn.run("ingestion.web.upload:app", host=host, port=port, log_level="info") if __name__ == "__main__": cli()