clawgallery 0.2.3

Agent-native screenshot gallery CLI with JSONL metadata, visual captions, and safe rename workflows.
clawgallery-0.2.3 is not a library.

ClawGallery

A friendly, agent-native gallery for all your screenshots and photos.


What is ClawGallery?

Your screenshots folder is a mess. Hundreds of Screenshot 2025-11-01 at 14.32.55.png and IMG_0034.jpg files, and no way to find the one you actually need.

ClawGallery is a small, fast command-line tool that turns that pile into a searchable gallery. Think of it as a tidy little lobster curator that:

  • Indexes the image folders you point it at.
  • Understands what's in each picture using a vision model, writing a title and caption for it.
  • Searches by keyword or by visual meaning ("that login error screen") so you can find images even when the filename says nothing.
  • Renames auto-generated filenames into human-readable ones — safely, with a dry-run by default.
  • Deduplicates, finding exact and visually-similar copies.

Everything is stored as plain, append-only JSONL files on your own machine. No cloud, no database daemon, no lock-in. It's built to be driven by both humans and AI agents (every command speaks --json).

Supported formats: png, jpg, jpeg, webp, avif, gif, heic, heif. HEIC/HEIF images are converted to JPEG automatically before captioning (macOS uses sips by default; set CLAWGALLERY_HEIC_CONVERTER to use a different tool).


Install

# Build, test, and install into your PATH
make ci
cargo install --path .

Enjoying ClawGallery? Please star the repo! ⭐️ It genuinely helps the project grow. If you have the GitHub CLI installed, it's one command:

gh repo star NomaDamas/ClawGallery

Quickstart

Get from zero to a searchable gallery in a few commands:

clawgallery init                    # set up local state
clawgallery folder add ~/Desktop    # tell it where your images live
clawgallery bootstrap               # scan the folders and index images
clawgallery search screenshot       # search by keyword right away

Want AI-generated titles and captions, then cleaner filenames? Preview first (everything below is a safe dry-run):

clawgallery caption --dry-run       # see what would be captioned
clawgallery rename --dry-run        # see the filename suggestions

When you're happy, drop --dry-run (and add --apply to actually rename files).


Everyday usage

1. Point it at your images

clawgallery folder add ~/Pictures
clawgallery folder add ~/Pictures/screenshots --recursive
clawgallery folder list
clawgallery bootstrap               # add --prune to drop files deleted on disk

2. Caption your images

Captioning asks a vision model to describe each image, which powers better search and renaming. It can call a paid API, so always preview first:

clawgallery caption --dry-run
clawgallery caption --missing       # caption everything not yet captioned
clawgallery caption --file ~/Pictures/one.png

You'll need credentials for a provider (see Vision model setup).

3. Search

By default, search is hybrid: Reciprocal Rank Fusion over keyword matches, V-SPLADE lexical ranks (if you synced --backend vsplade), and dense VDR ranks (if you built a visual index). Missing vector channels degrade instead of failing: keyword-only, keyword+dense, or keyword+sparse. Hybrid --json rows include used_channels, skipped_channels, and degraded so callers can tell a true hybrid result from a degraded one.

--mode embedding requires an active dense index; --mode lexical requires an active V-SPLADE index. Both fail before starting a query-embedding server and print the vdr sync command needed to build the missing index. --mode keyword always searches captions/paths and never needs a VDR server.

clawgallery search "login error"
clawgallery search "login error" --json --limit 5
clawgallery search --mode keyword "github actions"    # caption/path text only
clawgallery search --mode lexical "invoice total"     # V-SPLADE sparse retrieval
clawgallery search --mode embedding "sunset photo"    # dense visual only
clawgallery vdr status --json                         # dense vs sparse availability

Search understands fzf-style operators:

Syntax Meaning Example
foo bar Match both terms (fuzzy) clawgallery search login error
'foo Exact substring clawgallery search "'github"
^foo Starts with clawgallery search ^Login
foo$ Ends with clawgallery search modal$
!foo Exclude clawgallery search login !test
\ Literal space clawgallery search github\ actions

Lowercase queries are case-insensitive; add an uppercase letter (or --case-sensitive) to match case exactly. If nothing matches, ClawGallery automatically retries with typo tolerance. Use --no-fuzzy for plain exact-substring output.

4. Rename messy filenames

ClawGallery only renames files that look auto-generated (like IMG_0034 or Screenshot 2025-…) and leaves your meaningful names alone. It never overwrites existing files and is dry-run by default:

clawgallery rename --dry-run        # preview
clawgallery rename --apply          # actually rename
clawgallery rename --undo --last    # undo the last applied rename

More on how it stays safe in Rename safety.

5. Find duplicates

dedup only reports — it never deletes anything:

clawgallery dedup                   # exact duplicates (same content)
clawgallery vdr sync                # build the visual index first
clawgallery dedup --similar --threshold 0.95 --json

To remove a duplicate you chose yourself: clawgallery forget --file <path> --delete (or omit --delete to just stop tracking it).

6. Keep it up to date automatically

Poll a folder for new images on an interval:

clawgallery poll --interval 30
clawgallery poll --interval 30 --caption --sync   # also caption + reindex each pass

--caption captions new images each pass; --sync then updates the visual index. Failures are logged to errors.jsonl and reported without stopping the loop.

Or run it as a background service (see Run as a background service).


Lexical search (V-SPLADE)

V-SPLADE encodes each page image into a sparse vocabulary vector. Queries are an inference-free lookup, then ClawGallery ranks by sparse dot product. This is the --mode lexical backend and one of the hybrid RRF lists.

CLAWGALLERY_PYTHON=/path/to/splade-mlx/.venv/bin/python \
  clawgallery vdr sync --backend vsplade
clawgallery search --mode lexical "invoice total"

The managed V-SPLADE server requires splade_mlx and its MLX dependencies in the Python interpreter used to start it. If --python and CLAWGALLERY_PYTHON are not set, ClawGallery first uses an active VIRTUAL_ENV interpreter and then the platform default (python3 on macOS/Linux, python on Windows). It checks the runtime before starting the server and prints the exact interpreter and remediation when the dependency is missing. For example:

python3 -m pip install git+https://github.com/NomaDamas/SPLADE-mlx.git
CLAWGALLERY_PYTHON=python3 clawgallery vdr sync --backend vsplade

On macOS/Linux, install the MLX package into the active environment. Windows uses the native PyTorch V-SPLADE runtime instead, so it does not require Apple MLX:

git clone https://github.com/naver/v-splade.git $env:USERPROFILE\v-splade
python -m pip install torch torchvision "transformers==4.57.6" pillow safetensors accelerate huggingface_hub "colpali-engine==0.3.13"
$env:CLAWGALLERY_VSPLADE_REPO = "$env:USERPROFILE\v-splade"
clawgallery vdr sync --backend vsplade

The Windows backend selects CUDA automatically when an NVIDIA CUDA runtime is available and otherwise uses CPU. It loads naver/v-splade-efficient through the upstream inference helper. Set CLAWGALLERY_VSPLADE_REPO in every shell used for sync or search. Default model: NomaDamas/v-splade-efficient-mlx on MLX and naver/v-splade-efficient on Windows (both use a 50368-dim vocabulary). Sparse postings are stored in vdr.sqlite3 next to dense VDR rows and do not deactivate them.

Visual search (VDR)

"Visual Document Retrieval" is what lets ClawGallery find images by how they look, not just by their captions. It's optional — plain keyword search works without it — but it's what makes "find that screenshot of the error dialog" work even when the filename is garbage.

How it works

ClawGallery stores image embeddings in an embedded SQLite file (vdr.sqlite3) right alongside your other state. No separate vector database, no extra daemon to babysit. Building the index is incremental: unchanged images and captions are skipped, and only new or changed content is re-embedded.

The embedding model itself runs in Python (the best late-interaction ColQwen-family runtimes on macOS are MLX/Python-based), but ClawGallery starts, waits for, and shuts down that runtime for you.

Setup (macOS, recommended)

brew install rust uv
cargo install --path .

# Install the embedding runtime once
uv tool install mlx-embeddings --with pillow --with torch --with torchvision

# Build the visual index — ClawGallery starts the model server automatically
CLAWGALLERY_PYTHON="$(uv tool dir)/mlx-embeddings/bin/python" clawgallery vdr sync

Then just search — the query is embedded automatically:

clawgallery search "login error"              # hybrid RRF (keyword + lexical + visual)
clawgallery search --mode lexical "invoice"   # V-SPLADE sparse retrieval
clawgallery search --mode embedding "sunset"  # visual only
clawgallery vdr status --json

The default model is qnguyen3/colqwen2.5-v0.2-mlx (128 dimensions). The first run downloads and caches model weights. If Hugging Face downloads stall on macOS, retry with HF_HUB_DISABLE_XET=1.

Setup (Windows)

MLX backends (mlx, jina-mlx) are Apple Silicon-only. On Windows, use the managed ColQwen2 PyTorch backend (vidore/colqwen2-v1.0, 128 dimensions):

rustup default stable
python -m venv "$env:LOCALAPPDATA\clawgallery\colqwen"
& "$env:LOCALAPPDATA\clawgallery\colqwen\Scripts\python.exe" -m pip install -U pip huggingface_hub
& "$env:LOCALAPPDATA\clawgallery\colqwen\Scripts\python.exe" -m pip install torch --index-url https://download.pytorch.org/whl/cpu
& "$env:LOCALAPPDATA\clawgallery\colqwen\Scripts\python.exe" -m pip install colpali-engine transformers pillow
cargo install --path .

$env:CLAWGALLERY_PYTHON = "$env:LOCALAPPDATA\clawgallery\colqwen\Scripts\python.exe"
clawgallery vdr sync --backend colqwen
clawgallery search --mode embedding "login error" --json

CUDA hosts can omit the CPU PyTorch index URL and use --device cuda. If a previous download stalled at Fetching 2 files: 0%, delete %USERPROFILE%\.cache\huggingface\hub\models--vidore--colqwen2-*, upgrade huggingface_hub, and retry. Some networks need HF_HUB_DISABLE_XET=1; others fail DNS for cdn-lfs.huggingface.co and need xet left enabled. mlx / jina-mlx fail immediately with a pointer to --backend colqwen.

Jina v5 Omni retrieval on Apple Silicon

ClawGallery also packages the MLX conversion of jinaai/jina-embeddings-v5-omni-small-retrieval-mlx (1024 dimensions). It requires Apple Silicon and loads the immutable Hugging Face revision 049ae923674456656be891ebb22849dd58124994.

uv venv ~/.local/share/clawgallery/jina-mlx
uv pip install --python ~/.local/share/clawgallery/jina-mlx/bin/python \
  'mlx>=0.23' tokenizers huggingface_hub 'transformers>=4.57,<5' pillow \
  torch torchvision requests librosa av

CLAWGALLERY_PYTHON=~/.local/share/clawgallery/jina-mlx/bin/python \
  clawgallery vdr sync --backend jina-mlx

The first sync downloads and caches the model weights. Later visual searches read the model ID and dimensions from the active index, so they automatically start the Jina MLX runtime without repeating --backend. Keep CLAWGALLERY_PYTHON pointed at the Jina environment when searching.

This Jina model is licensed under CC BY-NC 4.0 and is restricted to noncommercial use. Review the model license before using it in a product or service.

Using your own embedding server

To reuse a long-running server instead of the managed one, point ClawGallery at it and it won't auto-start anything:

# Terminal A: keep a server running
CLAWGALLERY_PYTHON="$(uv tool dir)/mlx-embeddings/bin/python" \
  clawgallery vdr serve --backend mlx --host 127.0.0.1 --port 8765

# Terminal B: sync and search against it
clawgallery vdr sync --embedding-url http://127.0.0.1:8765
clawgallery search --mode embedding "login error" --json

You can also set CLAWGALLERY_VDR_EMBEDDING_URL instead of passing --embedding-url. The managed server binds to 127.0.0.1 and refuses non-loopback hosts unless you pass --allow-remote.

Legacy ColQwen2 (vidore/colqwen2-v1.0, 128 dims). Prefer managed --backend colqwen above; this path is for an already-running external server:

uv pip install colpali-engine torch pillow
python scripts/colqwen2_server.py --device auto
clawgallery vdr sync --no-auto-start --model vidore/colqwen2-v1.0 --dimensions 128
clawgallery search --mode embedding "login error" --json

SentenceTransformer Jina Omni (jinaai/jina-embeddings-v5-omni-small, 1024 dims):

python scripts/jina_omni_server.py --device auto
clawgallery vdr sync --no-auto-start --model jinaai/jina-embeddings-v5-omni-small --dimensions 1024
clawgallery search --mode embedding "login error" --json

This external SentenceTransformer path is separate from the managed jina-mlx backend above. Search must use the same model and dimensions as the synced index. The server enables Hugging Face trust_remote_code; if xet downloads stall, retry with HF_HUB_DISABLE_XET=1.

The embedding server contract is a single POST /embed:

{"model":"vidore/colqwen2-v1.0","dimensions":128,"inputs":[{"kind":"image|text|caption","role":"document|query","value":"path or text"}]}

kind is image (path), text, or caption. For images, value is the file path (including .heic/.heif), so the server needs an HEIC decoder such as Pillow + pillow-heif.


Vision model setup

Captioning needs a vision-capable model. ClawGallery supports two providers.

OpenAI-compatible (default)

Uses /v1/responses-style requests.

  • OPENAI_API_KEY — your key
  • OPENAI_BASE_URL — defaults to https://api.openai.com/v1
  • CLAWGALLERY_MODEL — defaults to gpt-4.1-mini

It can also reuse Codex credentials from $CODEX_HOME/auth.json or ~/.codex/auth.json.

Google Gemini

  • GEMINI_API_KEY — your key
  • Default model: gemini-2.5-flash

Choosing a provider

clawgallery caption --provider gemini --model gemini-2.5-flash
clawgallery caption --provider openai-compatible --model gpt-4.1-mini

How your data is stored

Everything lives in ~/.config/clawgallery by default (override with CLAWGALLERY_CONFIG_DIR):

File What it holds
config.json Your settings
folders.jsonl Registered folders
images.jsonl One record per discovered / pruned / renamed image
captions.jsonl One record per successful caption
renames.jsonl Rename history
errors.jsonl Logged failures (API keys redacted)
vdr.sqlite3 The visual embedding index

The event logs are append-only and joined by image_id. This is deliberate: cheap, free, repeatable indexing (bootstrap) is kept separate from paid network calls (caption) and from irreversible file changes (rename --apply). Every command treats the newest record per file as the truth and ignores anything marked inactive.


Rename safety

Renaming files is the one thing that touches your disk, so it's cautious by design:

  • Dry-run by default. You must pass --apply to move files. Dry-runs never touch files or write history.
  • Meaningful names are left alone. Only auto-generated stems get renamed (IMG_0034, PXL_20240316_080000123, Screenshot 2025-11-01 at 14.32.55, 1696862563748, image (1), …). A local regex catches ~12 common camera/screenshot/messenger families for free; anything ambiguous triggers a text-only model check on the filename (no image content) whose answer is cached as filename_meaningful in captions.jsonl.
  • No clobbering. Unsafe characters are stripped, extensions preserved, and existing files are never overwritten.
  • Batch-safe. If a tracked file has vanished from disk, ClawGallery marks it inactive and keeps going. Per-file failures are logged and summarized (renamed N, skipped M, failed K) instead of aborting.
clawgallery rename --dry-run                    # preview the whole batch
clawgallery rename --apply                       # apply
clawgallery rename --apply --file one.png        # single file, skips the gate
clawgallery rename --apply --force               # rename everything captioned
clawgallery rename --undo --last                 # reverse the last apply

Run as a background service

Install a user service that polls for new images continuously:

clawgallery daemon install --interval 30 --caption --sync
clawgallery daemon start
clawgallery daemon status
clawgallery daemon logs
clawgallery daemon stop
clawgallery daemon uninstall

On macOS this is a LaunchAgent (~/Library/LaunchAgents); on Linux it's a systemd --user unit. Logs go to daemon.log in your config directory. Set CLAWGALLERY_DAEMON_DIR to write the service file elsewhere.


Command reference

clawgallery init
clawgallery folder add <path> [--recursive]
clawgallery folder remove <id-or-path>
clawgallery folder list
clawgallery bootstrap [--folder <id>] [--path <path>] [--prune]
clawgallery poll [--folder <id>] [--path <path>] [--once] [--interval <seconds>] [--prune] [--caption] [--sync] [--embedding-url <url>] [--vdr-model <model>] [--vdr-dimensions <n>] [--max-retries <n>]
clawgallery caption [--missing] [--file <path>] [--dry-run] [--model <model>] [--provider <provider>] [--concurrency <n>] [--max-retries <n>]
clawgallery rename [--apply] [--dry-run] [--file <path>] [--style title|caption|date-title] [--force]
clawgallery rename --undo [--last] [--file <path>] [--dry-run]
clawgallery forget --file <path> [--delete]
clawgallery dedup [--exact] [--similar] [--threshold <0..1>] [--json]
clawgallery search [--mode keyword|embedding|lexical|hybrid] <query...> [--limit <n>] [--json] [--case-sensitive] [--no-fuzzy] [--embedding-url <url>]
clawgallery vdr sync [--prune] [--embedding-url <url>] [--model <model>] [--dimensions <n>] [--max-retries <n>] [--auto-start|--no-auto-start] [--backend mlx|jina-mlx|colqwen|vsplade] [--host <host>] [--port <port>] [--device auto|mps|cpu|cuda] [--python <path>] [--allow-remote]
clawgallery vdr serve [--backend mlx|jina-mlx|colqwen|vsplade] [--host <host>] [--port <port>] [--model <model>] [--dimensions <n>] [--device auto|mps|cpu|cuda] [--python <path>] [--allow-remote]
clawgallery vdr status [--json]
clawgallery daemon install [--interval <seconds>] [--caption] [--sync] [--path <path>] [--folder <id>]
clawgallery daemon start|stop|status|uninstall|logs
clawgallery status
clawgallery skill path|print

jina-mlx supports --device auto|mps; cpu is available only with the default mlx backend. colqwen supports --device auto|cpu|cuda and is the Windows dense default.


For AI agents

ClawGallery ships a skill so agents can drive it safely. Every command supports --json for stable, parseable output — prefer it. Run clawgallery skill print to load the guidance, and remember the safe defaults: rename never touches files without --apply, and bulk caption --missing may cost money, so preview with --dry-run first.


Community

License

Apache-2.0. See LICENSE.

If ClawGallery saved you from filename chaos, don't forget to ⭐️ star the repogh repo star NomaDamas/ClawGallery. Thank you!