From a004d289d6773fdc45fe6c7a3c971773574f54b2 Mon Sep 17 00:00:00 2001 From: marauder-actual Date: Wed, 20 May 2026 11:52:41 +0200 Subject: [PATCH] feat(db-list): default to remote DB, add --local override By default 'tsr db list' read from the LOCAL SQLite DB even when default_remote was configured, which created a confusing UX where the table never reflected the actual generation host. On a typical setup (chi@fuji with default_remote='runpod') it would happily display ghost entries pointing at /home/madcat/comfyui/models/... paths that don't exist on macOS at all. Flip the default: - 'tsr db list' -> remote (when default_remote set), else local - 'tsr db list --local' -> force local SQLite DB - 'tsr db list -r ' -> explicit remote (overrides default_remote) Precedence: --local wins; else -r; else default_remote; else local. Adds remote_db_files() helper in tensors/remote.py that calls the existing GET /api/db/files server endpoint (no server-side changes needed). Mirrors the pattern used by remote_models(). Scope kept minimal: only 'db list' for now. db search / triggers / stats stay local-default; can flip later under the same pattern if the UX wins are similar. --- tensors/cli.py | 57 ++++++++++++++++++++++++++++++++++++----------- tensors/remote.py | 45 +++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 13 deletions(-) diff --git a/tensors/cli.py b/tensors/cli.py index 95906b9..1829a7e 100644 --- a/tensors/cli.py +++ b/tensors/cli.py @@ -2600,27 +2600,57 @@ def db_cache( @db_app.command("list") -def db_list( +def db_list( # noqa: PLR0915 model_type: Annotated[ str | None, typer.Option("-t", "--type", help="Filter by model type (Checkpoint, LORA, VAE, etc.)") ] = None, base: Annotated[ str | None, typer.Option("-b", "--base", help="Filter by base model (Pony, Illustrious, SDXL 1.0, SD 1.5, etc.)") ] = None, + remote: Annotated[ + str | None, typer.Option("-r", "--remote", help="Remote server name or URL (overrides default_remote)") + ] = None, + local: Annotated[ + bool, typer.Option("--local", help="Force read from local DB, ignoring default_remote") + ] = False, json_output: Annotated[bool, typer.Option("--json", "-j", help="Output as JSON")] = False, ) -> None: - """List local files with CivitAI info. + """List files indexed in the tensors DB. + + Defaults to the configured remote (config.toml default_remote) so the + table reflects what's actually on the generation host. Pass --local to + inspect the local SQLite DB on this machine instead. If no remote is + configured and --local is not passed, falls back to local silently. Examples: - tsr db list # All local files - tsr db list -t Checkpoint # Only checkpoints - tsr db list -t LORA # Only LoRAs - tsr db list -t Checkpoint -b Pony # Pony checkpoints only - tsr db list -b "SDXL 1.0" # All SDXL 1.0 models + tsr db list # Remote (or local if no default_remote) + tsr db list --local # Force local DB + tsr db list -r junkpile # Explicit remote + tsr db list -t Checkpoint -b Pony # Filter, default source + tsr db list -b "SDXL 1.0" # All SDXL 1.0 models """ - with Database() as db: - db.init_schema() - files = db.list_local_files() + from tensors.config import resolve_remote as do_resolve_remote # noqa: PLC0415 + + # Resolution precedence: --local wins; else explicit -r; else default_remote; else local. + remote_url: str | None = None + if not local: + remote_url = do_resolve_remote(remote) if remote else do_resolve_remote(None) + + files: list[dict[str, Any]] | None + if remote_url: + from tensors.remote import remote_db_files # noqa: PLC0415 + + if not json_output: + console.print(f"[dim]Remote: {remote_url}[/dim]") + files = remote_db_files(remote or remote_url, console=console) + if files is None: + raise typer.Exit(1) + source_label = "Remote Files" + else: + with Database() as db: + db.init_schema() + files = db.list_local_files() + source_label = "Local Files" # Apply filters (case-insensitive substring match) if model_type: @@ -2635,17 +2665,18 @@ def db_list( return if not files: - console.print("[yellow]No files found. Try 'tsr db scan' or adjust filters.[/yellow]") + hint = "Remote DB is empty" if remote_url else "Try 'tsr db scan'" + console.print(f"[yellow]No files found. {hint} or adjust filters.[/yellow]") return - title = "Local Files" + title = source_label if model_type or base: parts = [] if model_type: parts.append(model_type) if base: parts.append(base) - title = f"Local Files ({', '.join(parts)})" + title = f"{source_label} ({', '.join(parts)})" table = Table(title=title, show_header=True, header_style="bold magenta") table.add_column("Path", style="cyan", max_width=50) diff --git a/tensors/remote.py b/tensors/remote.py index e0a252e..4d964fc 100644 --- a/tensors/remote.py +++ b/tensors/remote.py @@ -302,3 +302,48 @@ def remote_download_status( return result except (httpx.HTTPStatusError, httpx.RequestError): return None + + +def remote_db_files( + remote: str, + *, + console: Console | None = None, +) -> list[dict[str, Any]] | None: + """Fetch the local-files index from a remote tensors server's DB. + + Returns the same shape as Database.list_local_files() but reflecting the + remote host's SQLite DB rather than ours. + + Args: + remote: Remote name or URL (resolved via config) + console: Rich console for error output + + Returns: + List of file dicts, or None on connection / API error + """ + base_url = resolve_remote(remote) + if not base_url: + if console: + console.print("[red]Error: Could not resolve remote server[/red]") + return None + + try: + with _build_client(base_url) as client: + response = client.get("/api/db/files") + response.raise_for_status() + result: list[dict[str, Any]] = response.json() + return result + except httpx.HTTPStatusError as e: + if console: + console.print(f"[red]Remote API error: {e.response.status_code}[/red]") + try: + detail = e.response.json().get("detail", "") + if detail: + console.print(f" [yellow]{detail}[/yellow]") + except Exception: + pass + return None + except httpx.RequestError as e: + if console: + console.print(f"[red]Remote connection error: {e}[/red]") + return None