Metadata-Version: 2.4
Name: privatelens
Version: 1.0.0
Summary: Local-first, read-only photo search sidecar with explainable hybrid search
Project-URL: Homepage, https://github.com/kenny2077/PrivateLens
Project-URL: Documentation, https://github.com/kenny2077/PrivateLens#readme
Project-URL: Repository, https://github.com/kenny2077/PrivateLens.git
Project-URL: Issues, https://github.com/kenny2077/PrivateLens/issues
Project-URL: Changelog, https://github.com/kenny2077/PrivateLens/blob/main/CHANGELOG.md
Author: PrivateLens contributors
License-Expression: MIT
License-File: LICENSE
Keywords: clip,local-first,ocr,photo-search,privacy,sqlite-vec
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: End Users/Desktop
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Multimedia :: Graphics
Requires-Python: <3.14,>=3.11
Requires-Dist: aiosqlite<1,>=0.20
Requires-Dist: click<9,>=8.1
Requires-Dist: cryptography<50,>=44.0
Requires-Dist: fastapi<1,>=0.115
Requires-Dist: httpx<1,>=0.27
Requires-Dist: imagehash<5,>=4.3
Requires-Dist: jinja2<4,>=3.1
Requires-Dist: numpy<3,>=2.0
Requires-Dist: pillow-heif<2,>=1.1
Requires-Dist: pillow<13,>=11.0
Requires-Dist: pydantic-settings<3,>=2.6
Requires-Dist: pydantic<3,>=2.9
Requires-Dist: python-multipart<1,>=0.0.17
Requires-Dist: rich<16,>=13.9
Requires-Dist: sqlalchemy<3,>=2.0
Requires-Dist: sqlite-vec<0.2,>=0.1
Requires-Dist: uvicorn<1,>=0.32
Requires-Dist: watchdog<7,>=6.0
Provides-Extra: core
Provides-Extra: dev
Requires-Dist: mypy<3,>=1.13; extra == 'dev'
Requires-Dist: pre-commit<5,>=4.0; extra == 'dev'
Requires-Dist: pytest<10,>=8.0; extra == 'dev'
Requires-Dist: ruff<1,>=0.8; extra == 'dev'
Provides-Extra: full
Requires-Dist: insightface<2,>=0.7; extra == 'full'
Requires-Dist: onnxruntime<2,>=1.20; extra == 'full'
Requires-Dist: open-clip-torch<4,>=2.24; extra == 'full'
Requires-Dist: rapidocr-onnxruntime==1.4.4; extra == 'full'
Requires-Dist: torch<3,>=2.5; extra == 'full'
Requires-Dist: torchvision<1,>=0.20; extra == 'full'
Provides-Extra: ml
Requires-Dist: insightface<2,>=0.7; extra == 'ml'
Requires-Dist: onnxruntime<2,>=1.20; extra == 'ml'
Requires-Dist: open-clip-torch<4,>=2.24; extra == 'ml'
Requires-Dist: rapidocr-onnxruntime==1.4.4; extra == 'ml'
Requires-Dist: torch<3,>=2.5; extra == 'ml'
Requires-Dist: torchvision<1,>=0.20; extra == 'ml'
Description-Content-Type: text/markdown

<p align="center">
  <img src="https://raw.githubusercontent.com/kenny2077/PrivateLens/main/docs/assets/privatelens-hero.svg" alt="PrivateLens — search what you remember, keep what you own" width="100%">
</p>

# PrivateLens

<p align="center">
  <strong>The private search layer for photo folders you already own.</strong>
</p>

<p align="center">
  <a href="#fast-setup"><img src="https://img.shields.io/badge/Quickstart-3_commands-67E8F9?style=for-the-badge&labelColor=111827" alt="Quickstart"></a>
  <a href="https://github.com/kenny2077/PrivateLens/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/kenny2077/PrivateLens/ci.yml?branch=main&style=for-the-badge&label=build" alt="Build status"></a>
  <a href="https://www.python.org/"><img src="https://img.shields.io/badge/Python-3.11%E2%80%933.13-818CF8?style=for-the-badge&logo=python&logoColor=white" alt="Python 3.11 through 3.13"></a>
  <a href="https://github.com/kenny2077/PrivateLens/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-A78BFA?style=for-the-badge" alt="MIT License"></a>
  <a href="https://github.com/kenny2077/PrivateLens/stargazers"><img src="https://img.shields.io/github/stars/kenny2077/PrivateLens?style=for-the-badge&color=F472B6&labelColor=111827" alt="GitHub stars"></a>
</p>

**Find photos by meaning instead of scrolling.** PrivateLens builds a local
search sidecar over your existing folders—without importing, moving, rewriting,
or managing the originals. Ask for `"my driver license backup"`,
`"receipt from Target"`, or `"cat on the sofa"` and get structured evidence for
why every result matched.

Think of it as **ripgrep for a photo library**: terminal-first, scriptable, and
designed to stay beside the collection rather than become the collection.

<table>
<tr><td><b>Search what you remember</b></td><td>Combine natural-language image similarity, OCR text, paths, dates, camera metadata, faces, and optional local captions.</td></tr>
<tr><td><b>Keep originals untouched</b></td><td>Index folders in place. PrivateLens never imports, moves, rewrites, or organizes source media.</td></tr>
<tr><td><b>See why it matched</b></td><td>Evidence cards expose the available semantic, text, metadata, detection, and recipe signals behind each result.</td></tr>
<tr><td><b>Stay local by default</b></td><td>SQLite, vectors, thumbnails, and inference stay on your machine unless you explicitly configure an integration or model download.</td></tr>
<tr><td><b>Use it like a Unix tool</b></td><td>Readable terminal output plus JSON and NDJSON contracts fit interactive search, shell pipelines, and local agents.</td></tr>
</table>

---

## Fast setup

### macOS and Linux

```bash
git clone https://github.com/kenny2077/PrivateLens.git
cd PrivateLens
uv sync --python 3.11 --locked --extra full
source .venv/bin/activate
```

Then point PrivateLens at a folder you already have:

```bash
privatelens scan ~/Pictures
privatelens index --skip-face --skip-vlm --batch-size 1
privatelens search "Target receipt" --json
```

> [!NOTE]
> Source photos stay in place and are opened read-only. The first indexing run
> may download model weights; cached runs perform inference locally. Run
> `privatelens quickstart` for the memory-conscious guided path.

Want to look before touching a real library? Generate a private-data-free demo:

```bash
privatelens demo --output-dir /tmp/privatelens-demo-photos
privatelens scan /tmp/privatelens-demo-photos
privatelens index --skip-face --skip-vlm --batch-size 1
privatelens search receipt --fast --json --limit 5
```

---

## One query, multiple signals

```console
$ privatelens search "Target receipt" --json --limit 1

receipt.jpg
├── semantic    image meaning matches “receipt”
├── OCR         “TARGET · TOTAL 23.20”
├── detection   receipt
└── metadata    captured 2025-04-18
```

PrivateLens fuses only the evidence available for each photo. Face detection and
local VLM captions remain opt-in; the default indexing path uses CLIP and OCR.

---

## 30-Second Terminal Demo

![PrivateLens 30-second synthetic demo](https://raw.githubusercontent.com/kenny2077/PrivateLens/main/docs/assets/terminal-demo.svg)

```bash
DEMO_DIR=/tmp/privatelens-demo-photos
DEMO_DATA=/tmp/privatelens-demo-index

privatelens --data-dir "$DEMO_DATA" demo --output-dir "$DEMO_DIR"
privatelens --data-dir "$DEMO_DATA" scan "$DEMO_DIR"
privatelens --data-dir "$DEMO_DATA" index --skip-face --skip-vlm --batch-size 1
privatelens --data-dir "$DEMO_DATA" status
privatelens --data-dir "$DEMO_DATA" search receipt --fast --json --limit 5
privatelens --data-dir "$DEMO_DATA" recipes --detail
```

The demo uses generated, non-private images and exercises the complete core
path: read-only scan, local CLIP/OCR indexing, structured receipt search, and
an evidence card with machine-readable timing. The first run may download model
weights; the terminal sequence itself is the 30-second path once those weights
are cached. A model-free path-only smoke remains available with
`search receipt --type path --json`.

---

## Technical guide

The sections below preserve the release boundaries, platform matrix, complete
command surface, benchmarks, privacy model, and deployment details behind the
short product path above.

### v1.0 status

This source is release-final for PrivateLens `1.0.0`. Official PyPI, GitHub
Release, and GHCR artifacts are produced only from the `v1.0.0` tag. Local
Apple Silicon gates include 185 tests, a 1,000-image reliability run, a
15-image local real-photo evaluation reported only in aggregate, and core/full
CPU Docker builds. Hosted checks pass Python 3.11–3.13 and an isolated wheel
consumer; the full CPU image also builds and passes HTTP health on Linux amd64
while running non-root with a read-only root filesystem.

The full Compose/Ollama flow remains a preview. CUDA and the desktop application
are unsupported and are not shipped in 1.0.

### Core principles

1. **Read-only originals** — PrivateLens never imports, moves, rewrites, or manages source photos.
2. **Local-first inference** — Search data and inference stay local unless you explicitly configure an integration or download a model.
3. **Multi-signal search** — Combine semantic CLIP vectors, OCR, faces, captions, and metadata.
4. **Explainable results** — Return the available evidence behind each match.
5. **CLI-first operation** — Human-readable output and JSON/NDJSON modes support both interactive and scripted use.

### Requirements and platform status

- Python 3.11, 3.12, or 3.13 for the core package.
- Python 3.11 for the verified `privatelens[full]` stack and CPU container.
  Its locked RapidOCR runtime also resolves on Python 3.12, but the complete
  ML stack is not release-gated there in 1.0.
- `uv` for a source checkout, or `pip` after the PyPI release.
- Ollama only if you enable optional VLM captioning or reranking.
- Free disk space for the SQLite sidecar, thumbnails, and model caches. Source
  photos remain in place.

| Environment | Status |
|-------------|--------|
| macOS on Apple Silicon | Primary development environment; verified locally with memory-conscious settings |
| CPU Docker image | Core and full images build locally on arm64; the hosted full image builds and passes HTTP health on Linux amd64 while running non-root with a read-only root filesystem; the release workflow publishes the model-free `1.0.0-core` image |
| Full Docker Compose + Ollama | Preview; full-stack validation is still pending |
| Linux x86_64 | Hosted Python 3.11–3.13 and full CPU-container gates pass on Linux; bare-metal installation is not separately validated |
| Windows WSL2 | Not validated for 1.0 |
| Native Windows | Not currently a supported target |
| NVIDIA acceleration | Unsupported; 1.0 ships no CUDA image, Compose file, extra, or helper script |
| Desktop application | Unsupported; no desktop binary is shipped in 1.0 |

### Installation details

#### From a source checkout

Run these commands from the repository root:

```bash
uv sync --python 3.11 --locked --extra full
source .venv/bin/activate
```

For development tools as well, run
`uv sync --python 3.11 --locked --all-extras`. These project commands use the
locked CPU-only PyTorch source on Linux. See the
[contribution guide](https://github.com/kenny2077/PrivateLens/blob/main/CONTRIBUTING.md).

#### From PyPI after the v1.0 tag

Use Python 3.11 for the complete, release-gated local indexing stack:

```bash
python3.11 -m venv .venv
source .venv/bin/activate
# Linux CPU-only install: prevent PyPI's CUDA-enabled PyTorch build.
python -m pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu
python -m pip install "privatelens[full]"
```

On macOS and Windows, omit the separate PyTorch command because their PyPI
wheels are already CPU-only. The Linux command uses PyTorch's official CPU
wheel index; PrivateLens 1.0 does not ship a supported CUDA runtime.

On Python 3.12 or 3.13, install `privatelens` without an ML extra. The CLI/API
core is tested on both versions. The locked OCR runtime excludes Python 3.13,
and the complete ML stack is not release-gated on Python 3.12 in 1.0.

`privatelens[full]` installs the local CLIP, OCR, and face runtime used to build
a searchable index. `privatelens` without the extra is the lightweight CLI/API
core: it can scan folders, inspect an existing sidecar, and run non-ML search
modes, but it cannot run the normal CLIP/OCR indexing pass. Ollama and its VLM
weights remain separate. Read the
[third-party model record](https://github.com/kenny2077/PrivateLens/blob/main/THIRD_PARTY_MODELS.md)
before enabling face or VLM features.

### Command reference

```bash
# Print exact env, package, Ollama, model-cache, and verification commands
privatelens setup
privatelens setup --json

# See the Mac-safe first-run indexing workflow
privatelens quickstart

# Create a safe synthetic demo library
privatelens demo --output-dir /tmp/privatelens-demo-photos

# Optional: enable shell completion
privatelens completion zsh

# Scan a folder without moving photos
privatelens scan ~/Pictures
privatelens scan ~/Pictures --dry-run

# Index the core CLIP/OCR signals; face and VLM passes are opt-in
privatelens index --skip-face --skip-vlm --batch-size 1
privatelens index --dry-run

# Optional: keep the sidecar index current as files change
privatelens watch ~/Pictures

# Search with evidence and timing
privatelens search "driver license backup" --json
privatelens search "Target receipt" --json
privatelens search receipt --type path --json --limit 5
privatelens search --recipe find_receipt "Target"
privatelens search receipt --fast --json --limit 5
privatelens search receipt --open

# Inspect index status
privatelens status
privatelens status --json

# Preview and remove stale index records for photos that were moved/deleted
privatelens prune
privatelens prune --json --yes

# Privacy audit
privatelens doctor
privatelens doctor --json

# Face clustering
privatelens cluster --json

# Panic delete the index while keeping photos
privatelens purge
privatelens purge --json --yes
```

`privatelens watch` requires the full local ML pipeline because each debounced
change batch runs the canonical scan and index workflow. It skips face detection
and VLM captions by default for memory safety; opt in with `--with-face` or
`--with-vlm`. Use `privatelens watch ~/Pictures --json` for newline-delimited JSON
cycle and shutdown events suitable for process supervisors.

The regular `index` command also skips face detection and VLM captioning by
default. Enable them explicitly with `--with-face` / `--with-vlm`, or run the
separate `--only-face` / `--only-vlm` passes.

## Configuration and local data

PrivateLens uses Pydantic settings and environment variables prefixed with
`PRIVATELENS_`. Copy the
[example environment file](https://github.com/kenny2077/PrivateLens/blob/main/.env.example)
to `.env` only when you need to override a default. `privatelens setup` prints
the effective setup path and safe remediation commands.

By default, runtime data stays outside the repository:

| Data | Default location |
|------|------------------|
| SQLite index | `~/.privatelens/privatelens.db` |
| Thumbnails | `~/.privatelens/thumbnails/` |
| Model cache | `~/.privatelens/models/` |

Use the global `--data-dir` option to isolate an index, as the demo does.
Configure `PRIVATELENS_ENCRYPTION_KEY` to encrypt only the auxiliary provenance
payload (`type`, `confidence`, and `source`) attached to newly detected sensitive
items. Sensitivity flags, sensitivity type/confidence columns, OCR text,
captions, embeddings, paths, thumbnails, and the SQLite database itself remain
plaintext inside the data directory. Protect that directory with
operating-system permissions and full-disk encryption.

## Supported inputs

Folder discovery recognizes `.jpg`, `.jpeg`, `.png`, `.gif`, `.bmp`, `.webp`,
`.heic`, `.heif`, and `.tiff` images. HEIC/HEIF decoding is supplied by the
core `pillow-heif` dependency and is covered by a real encoded-file regression
test. Other formats still depend on Pillow decoder support. Video indexing is
not part of the current v1 scope.

## Why PrivateLens

PrivateLens is not another photo manager. It is a search sidecar for folders and libraries you already own.

| Capability | PrivateLens | Immich | PhotoPrism | Caption/tag tools |
|------------|-------------|--------|------------|-------------------|
| Read-only sidecar over existing folders | Core design | Supports [read-only external libraries](https://docs.immich.app/features/libraries/) | Can [index originals in place](https://docs.photoprism.app/user-guide/library/originals/) | Varies |
| Primary product shape | CLI-first search sidecar | Photo-management server and clients | Photo-management application | Usually tagging or caption generation |
| Search recipes with evidence cards | Core workflow | Different search model; not evaluated here | Different search model; not evaluated here | Varies |
| Original-media management | Never imports, moves, or manages originals | Managed and external-library workflows | Index and import workflows | Varies |
| Machine-readable search output | JSON/NDJSON CLI contracts | Different interface; not evaluated here | Different interface; not evaluated here | Varies |

This comparison describes product shape, not a quality benchmark. Competitor
capabilities change; the linked official documentation was checked on
2026-07-13. PrivateLens differentiates itself through structured, explainable
CLI search without becoming the system of record for a photo library.

## Search Quality Benchmark

PrivateLens ships a deterministic ten-case gate covering every built-in recipe:

```bash
privatelens benchmark
privatelens benchmark --json
```

| Metric | Current result | Release gate |
|--------|----------------|--------------|
| Top-5 hit rate | 100% | At least 80% |
| Mean recall@5 | 100% | Reported |
| Mean reciprocal rank | 1.000 | Reported |
| Mean precision@5 | 0.220 | Reported |

The fixture executes real FTS, metadata, face-count, detection, recipe-filter, ranking, and evidence-card code against deterministic signal annotations. It deliberately does not measure CLIP or VLM model quality. The checked-in report is the [search-quality v1 result](https://github.com/kenny2077/PrivateLens/blob/main/results/benchmarks/search-quality-v1.json).

## Model Quality Benchmark

With the ML extras and local Ollama model installed, run the separate model-dependent gate:

```bash
privatelens benchmark-models
privatelens benchmark-models --json
privatelens benchmark-models --skip-vlm  # CLIP + OCR only
```

| Metric | Current result | Release gate |
|--------|----------------|--------------|
| CLIP top-1 retrieval | 100% | 100% |
| OCR top-1 retrieval | 100% | 100% |
| VLM document classification | 100% | 100% |
| VLM caption term recall | 100% | 100% |

This gate generates four inspectable, non-private images for a receipt, driver license, travel screenshot, and whiteboard. It exercises the configured OpenCLIP model, RapidOCR, the real sqlite-vec/FTS retrieval paths, and the local Qwen VLM without reading the user's photo library. It is a reproducible model/integration smoke benchmark, not a broad real-world retrieval claim. The checked-in run is the [model-quality v1 result](https://github.com/kenny2077/PrivateLens/blob/main/results/benchmarks/model-quality-v1.json).

## v1.0 verification

| Gate | Result on 2026-07-14 | Boundary |
|------|----------------------------|----------|
| Automated suite | 185 local tests plus lint, typing, bytecode, lock, and diff checks; hosted Linux x86_64 jobs pass on Python 3.11–3.13, including an isolated wheel consumer | Hosted jobs exercise the core across the matrix; the full ML stack is release-gated on Python 3.11 |
| Scale reliability | 1,000 generated images scanned, indexed, searched, and rerun idempotently | Deterministic extractor stand-ins; not a relevance benchmark |
| Real-photo retrieval | 15-image local evaluation: 91.7% hit@1, 100% hit@5, 95.8% MRR@5 | Aggregate metrics only; too small for a broad quality claim |
| CPU containers | Core and full images built locally on arm64; the hosted full image builds and passes non-root/read-only HTTP health on Linux amd64; the tag workflow publishes a model-free core image to GHCR | The complete Compose/Ollama flow remains preview-only; the full image is not redistributed because upstream OCR-model terms are unclear |
| Full-image runtime | Local CPU-only ML imports, HEIC decoding, and a 15/15 read-only scan; hosted service runs non-root with a read-only root filesystem | Hosted health is a service smoke, not a Compose/Ollama or model-quality gate |

No private filenames, OCR text, paths, or image contents are included in these
reported metrics or in tracked release artifacts.

## Docker CPU Quick Start

The core and full non-root CPU images build locally on arm64. The hosted full
image also builds and passes non-root/read-only HTTP health on Linux amd64;
locally it passes CPU-only ML imports, HEIC decoding, and a 15/15 scan from a
read-only photo mount. The complete Compose workflow with Ollama is still a
preview until its full scan/index/search gate passes.

The release `-core` image intentionally excludes ML packages and model files.
It can scan metadata, inspect an existing sidecar, and run non-ML searches.
The pull command below becomes available only after the signed `v1.0.0` tag
publishes the GHCR artifact; until then, use the source-build command below.

```bash
docker pull ghcr.io/kenny2077/privatelens:1.0.0-core
docker run --detach --name privatelens \
  --publish 127.0.0.1:8000:8000 \
  --read-only --security-opt no-new-privileges:true \
  --tmpfs /tmp:rw,noexec,nosuid,size=256m \
  --mount type=bind,src="$HOME/Pictures",dst=/photos,readonly \
  --mount type=volume,src=privatelens-data,dst=/data \
  ghcr.io/kenny2077/privatelens:1.0.0-core

docker exec -it privatelens python -m privatelens.cli scan /photos
docker exec -it privatelens python -m privatelens.cli status --json
docker exec -it privatelens python -m privatelens.cli search receipt --type path --json
```

The full image remains build-verified but is not published because the exact
license grant for RapidOCR's embedded Baidu-copyrighted ONNX models is unclear.
To build it for local use after reviewing the
[third-party model record](https://github.com/kenny2077/PrivateLens/blob/main/THIRD_PARTY_MODELS.md),
use the preview Compose stack from a source checkout. Review the resolved
mounts before starting it:

```bash
export PHOTOS_DIR="$HOME/Pictures"
# `privatelens setup` can add this key to .env; Compose reads .env automatically.
export PRIVATELENS_ENCRYPTION_KEY="$(python -c 'from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())')"
docker compose config --quiet
docker compose up --build

# In another terminal, scan/index/search inside the container
docker exec -it privatelens python -m privatelens.cli scan /photos
docker exec -it privatelens python -m privatelens.cli index --skip-face --skip-vlm --batch-size 1
docker exec -it privatelens python -m privatelens.cli search receipt --json --limit 5
```

To reproduce the model-free release image from source:

```bash
PRIVATELENS_EXTRAS=core docker compose build privatelens
```

The application root filesystem is read-only. Only the named data, model-cache,
and thumbnail volumes plus `/tmp` are writable; source photos remain mounted
read-only at `/photos`.

## Unsupported CUDA and desktop paths

PrivateLens 1.0 ships neither CUDA artifacts nor a desktop application. NVIDIA
acceleration is deferred until it passes an end-to-end gate on the external GPU
machine; there is no supported CUDA image, Compose file, dependency extra, or
helper script. Use the verified CPU path above. The
[gaming-PC guide](https://github.com/kenny2077/PrivateLens/blob/main/docs/deploy-gaming-pc.md) records the future promotion
checklist without presenting an unverified quick start.

## Architecture

PrivateLens is a **sidecar indexer**, not a photo manager. It reads your existing folders and builds a private searchable index.

```
Your Photos (read-only)
    ↓
File Scanner → EXIF → pHash fingerprint
    ↓
Vision Pipeline:
    - Derived thumbnails
    - CLIP embeddings (OpenCLIP)
    - OCR text (RapidOCR)
    - Face detection (InsightFace, opt-in)
    - VLM captions (Ollama, opt-in)
    - Document classification
    - Sensitive content detection
    ↓
Local Index (SQLite + sqlite-vec + FTS5)
    ↓
Hybrid Search Engine
    - Semantic (CLIP vector)
    - Text (OCR + captions FTS5)
    - Face (person clusters)
    - Metadata (path, explicit date, camera, dimensions/type)
    - Search recipes (pre-built query plans)
    ↓
Evidence Cards (why each result matched)
```

## Search Recipes

Built-in search recipes for common retrieval tasks:

| Recipe | Trigger | Description |
|--------|---------|-------------|
| `find_id_photo` | "driver license", "passport" | Government IDs and licenses |
| `find_selfie` | "selfie" | Likely single-person selfies |
| `find_two_person` | "two people", "couple" | Photos with exactly two detected faces |
| `find_screenshot` | "screenshot" | Screenshots of apps/docs |
| `find_receipt` | "receipt", "invoice" | Receipts and expenses |
| `find_pet` | "my dog", "cat" | Pet photos |
| `find_document` | "whiteboard", "notes" | Documents and notes |
| `find_car` | "car", "dashboard" | Vehicle photos |
| `find_sensitive` | "bank card" | Sensitive documents |
| `find_memory` | "2024", "2024-03" | Photos in an explicit year/month/day range |

## AnythingLLM Integration

PrivateLens can sync structured photo documents to your local AnythingLLM workspace for chat-based search:

```bash
privatelens sync-anythingllm
privatelens sync-anythingllm --json
```

This is an explicit export operation. Each exported document can contain the
absolute source path, date, dimensions, media type, camera, GPS coordinates,
sensitivity flag, captions, OCR text, identified person names/associations, and
tags. It does not include original image bytes or raw face embeddings. Review
the destination and payload before running it; PrivateLens does not guarantee
how AnythingLLM presents or cites the exported document. With the default
local-only guard, the AnythingLLM endpoint must resolve to a recognized local
host.

## Privacy Features

- **Read-only originals**: Scan and index operations do not move, rewrite, or delete source photos
- **Local sidecar**: The SQLite index, thumbnails, and model cache stay under the configured local data paths
- **Local-only guard**: App-managed VLM and AnythingLLM requests default-deny non-local endpoints; this is not a firewall and does not wrap package or model-library downloads
- **Optional classification encryption**: A Fernet key encrypts only auxiliary sensitive-item provenance; sensitivity flags/type/confidence and the rest of the sidecar remain plaintext
- **Face-data control**: `privatelens purge --faces-only` deletes face rows, face vectors, and people clusters
- **Panic delete**: `privatelens purge` deletes assets, paths/GPS metadata, search history, people, derived thumbnails, and every indexed signal while never deleting source photos
- **Safe maintenance**: `privatelens prune` previews missing-file records; add `--yes` to remove only those index records
- **Privacy audit**: `privatelens doctor` — verify local-only status

See the [privacy guide](https://github.com/kenny2077/PrivateLens/blob/main/docs/privacy-guide.md) for the threat model and operating
guidance. Sensitive detection is heuristic; do not treat it as a data-loss
prevention guarantee.

## Limitations and v1.0 boundaries

- The checked-in four-image model benchmark is an integration gate, not a
  broad real-world retrieval claim. The separate 15-image local evaluation is
  also too small to support a broad quality claim; a larger labeled campaign
  remains future work.
- Normal CLI and API searches do not retain queries. Only an explicitly
  interactive CLI `search --feedback` run stores its plaintext query and result
  event; a selected result can receive a capped `0.05` boost on later searches.
  This is a small local ranking heuristic, not model training, and full purge
  deletes the stored events.
- OCR, document, face, and sensitive-content detection can produce false
  positives and false negatives.
- PrivateLens does not provide full-database encryption, encrypted thumbnails,
  access control, multi-user isolation, or a cloud backup service.
- Video indexing is outside the current v1 scope.
- The full Compose/Ollama flow remains a preview. Hosted checks cover the core
  on Python 3.11–3.13, an isolated wheel consumer, and the full CPU image's
  build plus non-root/read-only HTTP-health smoke on Linux amd64; they do not
  validate bare-metal Linux, a multi-OS matrix, or Compose/Ollama.
- The `full` and `ml` extras are release-gated on Python 3.11. Their locked
  dependencies resolve on Python 3.12, but the complete ML stack is not tested
  there in 1.0; RapidOCR 1.4.4 excludes Python 3.13.
- The tag workflow publishes the model-free `1.0.0-core` image to GHCR. The full image remains a
  local build until the exact license grant for RapidOCR's embedded OCR models
  is clear.
- CUDA, native Windows, and the desktop application are unsupported and are not
  shipped in 1.0.
- Optional model weights have licenses independent of the PrivateLens code.
  In particular, the default InsightFace `buffalo_l` weights are not covered
  by PrivateLens's MIT license. Read the [third-party model record](https://github.com/kenny2077/PrivateLens/blob/main/THIRD_PARTY_MODELS.md)
  before enabling face recognition.

## Troubleshooting

Start with machine-readable diagnostics:

```bash
privatelens setup --json
privatelens doctor --json
privatelens status --json
```

- If indexing reports missing CLIP or OCR packages, install the `full` extra;
  the lightweight core is not a complete indexing runtime.
- On a memory-constrained host, run core, face, and VLM indexing as separate
  commands. `--batch-size` controls commit cadence; it is not a model-memory cap.
- If photos were moved or deleted, run `privatelens prune` to preview stale
  sidecar records before using `--yes`.
- If native sqlite-vec cannot load, `privatelens doctor` reports the active
  vector backend; the BLOB fallback remains available but should be benchmarked
  for your collection size.
- Before Compose troubleshooting, run `docker compose config --quiet`, then confirm
  the photos mount resolves to `/photos` with read-only access.

When opening a bug report, use synthetic reproduction data and redact absolute
paths, OCR text, face data, database contents, tokens, and encryption keys. See
[support policy](https://github.com/kenny2077/PrivateLens/blob/main/SUPPORT.md).

## Documentation

- [Architecture](https://github.com/kenny2077/PrivateLens/blob/main/docs/architecture.md)
- [Search recipes](https://github.com/kenny2077/PrivateLens/blob/main/docs/search-recipes.md)
- [Privacy guide](https://github.com/kenny2077/PrivateLens/blob/main/docs/privacy-guide.md)
- [Unsupported CUDA validation guide](https://github.com/kenny2077/PrivateLens/blob/main/docs/deploy-gaming-pc.md)
- [Deep analysis and v1.0 roadmap](https://github.com/kenny2077/PrivateLens/blob/main/docs/deep-analysis-and-roadmap.md)
- [Changelog](https://github.com/kenny2077/PrivateLens/blob/main/CHANGELOG.md)

## Security and responsible use

Report vulnerabilities through the [private security process](https://github.com/kenny2077/PrivateLens/blob/main/SECURITY.md);
never place private photos, OCR text, face embeddings, keys, or unredacted paths
in a public issue. Face recognition may involve biometric data and additional
legal or consent requirements in your jurisdiction.

PrivateLens code is MIT-licensed, but model packages and weights retain their
own terms. Review the [third-party model record](https://github.com/kenny2077/PrivateLens/blob/main/THIRD_PARTY_MODELS.md) before
downloading or deploying them, especially for commercial use.

## Support

PrivateLens is maintained on a best-effort basis with no guaranteed response
time. Use the [support policy](https://github.com/kenny2077/PrivateLens/blob/main/SUPPORT.md) to choose between setup help, a reproducible
bug report, a feature request, and a private security report.

## Contributing

The core package supports Python 3.11, 3.12, and 3.13; the full ML extra is
release-gated on Python 3.11. See the
[changelog](https://github.com/kenny2077/PrivateLens/blob/main/CHANGELOG.md)
for release status and notable changes.

Contributions are welcome when they preserve the read-only sidecar boundary,
include proportionate verification, and never add private media to the
repository. Read the [contribution guide](https://github.com/kenny2077/PrivateLens/blob/main/CONTRIBUTING.md) and the
[Code of Conduct](https://github.com/kenny2077/PrivateLens/blob/main/CODE_OF_CONDUCT.md) before opening a pull request.

## License

PrivateLens source code is available under the [MIT License](https://github.com/kenny2077/PrivateLens/blob/main/LICENSE).
Dependencies, services, and model weights are not relicensed by PrivateLens;
see the [third-party model record](https://github.com/kenny2077/PrivateLens/blob/main/THIRD_PARTY_MODELS.md).
