Image refinder
  • TypeScript 94.6%
  • JavaScript 5.4%
Find a file
Nikolaus Sperat 7da4b8e9f8 fix: raise default max_tokens to 4096, make configurable via LLM_MAX_TOKENS
512 tokens was exhausted by reasoning_content on thinking models (e.g.
gemma-4-26B), leaving message.content empty. 4096 gives enough headroom
for both reasoning and the actual description.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 22:56:43 +02:00
prisma feat: initial scaffold for recall 2026-05-04 21:57:00 +02:00
prompts feat: initial scaffold for recall 2026-05-04 21:57:00 +02:00
src fix: raise default max_tokens to 4096, make configurable via LLM_MAX_TOKENS 2026-05-04 22:56:43 +02:00
tests test: add full unit and integration test suites 2026-05-04 22:11:12 +02:00
.env.example feat: initial scaffold for recall 2026-05-04 21:57:00 +02:00
.gitignore feat: initial scaffold for recall 2026-05-04 21:57:00 +02:00
.oxlintrc.json feat: initial scaffold for recall 2026-05-04 21:57:00 +02:00
.prettierignore feat: initial scaffold for recall 2026-05-04 21:57:00 +02:00
.prettierrc.json feat: initial scaffold for recall 2026-05-04 21:57:00 +02:00
AGENTS.md feat: initial scaffold for recall 2026-05-04 21:57:00 +02:00
CLAUDE.md feat: initial scaffold for recall 2026-05-04 21:57:00 +02:00
compose.yml feat: initial scaffold for recall 2026-05-04 21:57:00 +02:00
package-lock.json test: add full unit and integration test suites 2026-05-04 22:11:12 +02:00
package.json test: add full unit and integration test suites 2026-05-04 22:11:12 +02:00
README.md docs: document current project state in README 2026-05-04 22:12:13 +02:00
TODO.md feat: initial scaffold for recall 2026-05-04 21:57:00 +02:00
tsconfig.json feat: initial scaffold for recall 2026-05-04 21:57:00 +02:00
vitest.config.ts feat: initial scaffold for recall 2026-05-04 21:57:00 +02:00

recall

Small CLI tool that analyzes a folder of images using a local vision LLM and makes them searchable by natural-language description.

Purpose

Point the tool at a folder of images. Each image is sent to a local vision LLM (via an OpenAI-compatible HTTP endpoint, e.g. vLLM or Ollama serving gemma3:4b). The model produces a text summary, which is stored in SQLite and indexed in Meilisearch. Later you can query Meilisearch (CLI now, webapp later) to find images by content.

Status

Complete and working prototype.

  • Full pipeline implemented: walk → hash → dedup → LLM → SQLite → Meilisearch
  • 7 test suites, 41 tests (unit + integration), all independently runnable
  • CLI commands: analyze, reindex, search
  • Config via .env, Meilisearch via podman-compose

Not yet implemented (see TODO.md): webapp, HEIC/WebP/RAW formats, parallel workers, watch mode, structured LLM output.

Stack

  • Node.js + TypeScript
  • LLM: OpenAI-compatible HTTP API (vLLM/Ollama), vision-capable model
  • Image processing: sharp
  • Database: SQLite via Prisma ORM (prisma + @prisma/client)
  • Search: Meilisearch (run via podman-compose)
  • CLI: commander
  • Config: dotenv + zod
  • Lint/format/test: oxlint, prettier, tsc, vitest

Repository Structure

image-analyzer/
├── src/
│   ├── cli.ts              # commander entry
│   ├── config.ts           # .env load + zod validation
│   ├── db/
│   │   └── index.ts        # Prisma client singleton
├── prisma/
│   └── schema.prisma       # data model + SQLite datasource
│   ├── ingest/
│   │   ├── walk.ts         # folder walk + ext filter
│   │   └── hash.ts         # sha256 of file
│   ├── llm/
│   │   ├── client.ts       # OpenAI-compat client
│   │   └── prompt.ts       # default prompt, configurable
│   ├── analyze/
│   │   └── pipeline.ts     # ingest → hash → skip? → llm → db → meili
│   ├── meili/
│   │   └── index.ts        # client + sync from sqlite
│   └── commands/
│       ├── analyze.ts
│       ├── reindex.ts
│       └── search.ts
├── tests/
├── compose.yml             # podman-compose, Meilisearch service
├── .env.example
├── TODO.md
├── AGENTS.md / CLAUDE.md
└── package.json

Configuration

All configuration via .env. See .env.example.

Key Purpose
LLM_BASE_URL OpenAI-compatible endpoint (e.g. http://192.168.1.179/v1)
LLM_API_KEY API key (dummy value OK for local/unauth)
LLM_MODEL Model name (e.g. gemma3:4b)
LLM_PROMPT_FILE Path to file containing prompt template
IMAGE_DIR Folder to analyze
RECURSIVE true/false — recurse subfolders
DATABASE_URL SQLite path (e.g. file:./data/images.db) — read by Prisma
MEILI_URL Meilisearch URL
MEILI_KEY Meilisearch master key

CLI

recall analyze [--force] [--dir <path>]   # walk + analyze
recall reindex                            # rebuild Meili index from sqlite
recall search <query>                     # query Meili

analyze skips images whose content hash already exists in the DB unless --force is passed.

Setup

npm install
cp .env.example .env
# edit .env
npx prisma migrate dev      # creates SQLite DB + runs migrations
podman-compose up -d        # starts Meilisearch
npm run dev analyze

Verification

npm run lint
npm run format
npm run typecheck
npm run test

Conventions

See AGENTS.md (CLAUDE.md is a symlink to it).