{"id":49317263,"url":"https://github.com/padrio/doclens","last_synced_at":"2026-04-26T16:01:31.396Z","repository":{"id":353976274,"uuid":"1221640399","full_name":"Padrio/doclens","owner":"Padrio","description":"Make image-heavy PDFs grep-able for AI agents. Convert PDF corpora into structured Markdown where every diagram, screenshot and table is searchable as text. No vector DB, no embeddings - just clean Markdown with LLM-generated image descriptions.","archived":false,"fork":false,"pushed_at":"2026-04-26T14:34:13.000Z","size":33,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-26T15:26:06.050Z","etag":null,"topics":["ai-agents","anthropic","claude","docling","document-processing","knowledge-base","llm","markdown","ocr","pdf","rag-alternative"],"latest_commit_sha":null,"homepage":"https://github.com/Padrio/doclens","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Padrio.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-04-26T13:45:46.000Z","updated_at":"2026-04-26T14:34:06.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/Padrio/doclens","commit_stats":null,"previous_names":["padrio/doclens"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/Padrio/doclens","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Padrio%2Fdoclens","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Padrio%2Fdoclens/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Padrio%2Fdoclens/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Padrio%2Fdoclens/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Padrio","download_url":"https://codeload.github.com/Padrio/doclens/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Padrio%2Fdoclens/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32303177,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-26T09:34:17.070Z","status":"ssl_error","status_checked_at":"2026-04-26T09:34:00.993Z","response_time":129,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-agents","anthropic","claude","docling","document-processing","knowledge-base","llm","markdown","ocr","pdf","rag-alternative"],"created_at":"2026-04-26T16:01:30.657Z","updated_at":"2026-04-26T16:01:31.380Z","avatar_url":"https://github.com/Padrio.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# doclens\n\n\u003e **Make image-heavy PDFs grep-able for AI agents.**\n\u003e\n\u003e Convert any document corpus (specs, manuals, research papers, regulatory PDFs) into structured Markdown where **every diagram, screenshot, and table is searchable as text** — so LLMs and coding agents can navigate it like a codebase, not like a stack of opaque blobs.\n\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n[![Docker Image](https://img.shields.io/badge/ghcr.io-padrio%2Fdoclens-blue?logo=docker)](https://github.com/Padrio/doclens/pkgs/container/doclens)\n[![Python 3.12](https://img.shields.io/badge/python-3.12-blue)](.python-version)\n[![Multi-arch](https://img.shields.io/badge/arch-amd64%20%7C%20arm64-green)](Dockerfile)\n\n---\n\n## Why\n\nModern AI coding agents (Claude Code, Cursor, Aider, Copilot) are **excellent at code** and **terrible at image-heavy specifications**. When you point one at a 250-page PDF full of sequence diagrams, OCR'd tables and Visio screenshots, you get one of three failure modes:\n\n1. **Truncation** — the PDF is too big to fit in context, so the agent reads the first 30 pages and confidently extrapolates.\n2. **Image blindness** — even if it does ingest the PDF, every PNG is just `\u003cimage\u003e`. Sequence diagrams, ER models, flow charts → invisible.\n3. **No anchor** — without a section index, the agent re-reads the same chapters in every session.\n\nThe standard answer is \"build a RAG pipeline.\" That's a ton of moving parts (embeddings, vector DB, chunking strategy, hybrid search, re-ranker) for a problem that, for most teams, just needs **good Markdown**.\n\n`doclens` is the boring middle layer: PDFs in, navigable Markdown out. Every image gets an LLM-generated structured description embedded as an HTML comment — so `grep` finds it. An `INDEX.md` and per-doc `TOC.md` give agents stable entry points. A `CLAUDE.md` / `AGENTS.md` teaches the agent how to navigate.\n\nNo vector DB. No embeddings. No re-ranker. Just Markdown that an agent can read like source code.\n\n---\n\n## What it actually produces\n\nDrop a PDF into a folder. Run three commands. Get this:\n\n```\nyour-project/\n├── EGVP_Fachkonzept_4-3-1.pdf       (the source — untouched)\n├── INDEX.md                           ← entry point: title table, abstracts, deep-links\n├── docs/\n│   └── egvp-fachkonzept/\n│       ├── document.md                ← full text, headers preserved, tables as Markdown\n│       ├── TOC.md                     ← header outline with line anchors\n│       ├── assets/image_*.png         ← every diagram, extracted as a file\n│       ├── descriptions.json          ← SHA-256 cached image descriptions\n│       └── meta.json                  ← page count, conversion time, source mtime\n├── CLAUDE.md                          ← navigation rules for AI sessions\n└── AGENTS.md                          ← same, for runtimes that prefer this filename\n```\n\nInside `document.md`, every image looks like this:\n\n```markdown\n![Figure 12](assets/image_0042.png)\n\u003c!-- DOCLENS_DESC_SHA=a1b2c3... --\u003e\n\u003c!-- DOCLENS_DESC\nType: Sequence diagram\nSummary: SAML-based Single Sign-On flow between Principal, SP and IdP.\nStructured:\nActors: Principal (browser), Service Provider (SP), Identity Provider (IdP)\n1) Principal → SP:  GET /resource\n2) SP → Principal:  HTTP 302 redirect with SAMLRequest\n3) Principal → IdP: SAMLRequest (HTTP-Redirect binding)\n4) IdP ↔ Principal: authentication challenge\n5) IdP → Principal: SAMLResponse + assertion\n6) Principal → SP:  POST /assert with SAMLResponse\n7) SP → Principal:  granted access to /resource\nUncertainties: Step 4 binding (form-post vs browser-native auth) not explicit.\n--\u003e\n```\n\n`grep \"SAMLResponse\"` now finds it. So does an agent.\n\n---\n\n## Quick start\n\n### Prerequisites\n\n- **Docker** (Desktop, OrbStack, colima — anything with `docker build` / `docker run`)\n- **Anthropic API key** ([console.anthropic.com](https://console.anthropic.com/settings/keys)) for the image-description step\n- **`ripgrep`** on host is optional (the search script falls back to `grep`)\n\n### 30-second setup\n\n```bash\n# 1. Clone or init in your project\ngit clone https://github.com/Padrio/doclens.git\ncd doclens\necho \"ANTHROPIC_API_KEY=sk-ant-...\" \u003e .env\n\n# 2. Drop your PDFs in (any number, any names)\ncp ~/Downloads/*.pdf .\n\n# 3. Run the pipeline (image is auto-pulled from ghcr.io on first run)\n./scripts/doclens.sh all             # convert + describe + index\n```\n\nThat's it. Open `INDEX.md`, point your agent at it.\n\nThe pre-built image (`ghcr.io/padrio/doclens:latest`, multi-arch amd64+arm64) is pulled automatically on first invocation — no local build needed. Pin a specific version with:\n\n```bash\nDOCLENS_IMAGE=ghcr.io/padrio/doclens:v0.1.0 ./scripts/doclens.sh all\n```\n\nIf you want to build locally (e.g. you've modified the Dockerfile):\n\n```bash\n./scripts/doclens.sh build           # local build, ~5–15 min\n```\n\n### Per-step commands\n\n```bash\n./scripts/doclens.sh convert                  # PDFs → Markdown\n./scripts/doclens.sh convert --only my-doc    # one PDF\n./scripts/doclens.sh describe                 # all images\n./scripts/doclens.sh describe --slug my-doc --sample 5   # dry-run, prints to stdout\n./scripts/doclens.sh index                    # rebuild INDEX.md + TOC.md\n./scripts/doclens.sh report                   # coverage stats\n./scripts/doclens.sh search \"SAMLResponse\"    # ripgrep with image-desc included\n./scripts/doclens.sh shell                    # bash inside the container\n```\n\nEverything is **idempotent**. Re-running `convert` on unchanged PDFs is a no-op. Re-running `describe` only hits the API for new images (SHA-256 cache).\n\n---\n\n## How it works\n\n```\n                ┌──────────────────────┐\n                │   PDFs in ./*.pdf    │\n                └──────────┬───────────┘\n                           │\n                           │  scripts/convert.py\n                           │  (Docling 2.x, Tesseract OCR)\n                           ▼\n                ┌──────────────────────┐\n                │  docs/\u003cslug\u003e/        │\n                │  ├─ document.md      │  text, headers, tables\n                │  ├─ assets/*.png     │  extracted images as files\n                │  └─ meta.json        │\n                └──────────┬───────────┘\n                           │\n                           │  scripts/describe_images.py\n                           │  (Anthropic Claude vision)\n                           ▼\n                ┌──────────────────────────────────────┐\n                │  document.md, now with HTML comments │\n                │  under every image:                  │\n                │  \u003c!-- DOCLENS_DESC                   │\n                │  Type: Sequence diagram              │\n                │  Actors: ...                         │\n                │  --\u003e                                 │\n                │  + descriptions.json (SHA-cached)    │\n                └──────────┬───────────────────────────┘\n                           │\n                           │  scripts/build_index.py\n                           ▼\n                ┌──────────────────────┐\n                │  INDEX.md            │  global navigation\n                │  docs/*/TOC.md       │  per-doc outline\n                └──────────────────────┘\n```\n\n| Step | Tool | What it does |\n|---|---|---|\n| 1. Convert | [Docling](https://github.com/DS4SD/docling) 2.x | OCR + layout-aware Markdown export with image extraction |\n| 2. Describe | Anthropic Claude (configurable model) | Structured description per image, cached by SHA-256 |\n| 3. Index | Pure regex / JSON | Deterministic `INDEX.md` + `TOC.md`, no LLM, fast |\n| 4. Search | ripgrep (host) | Finds matches across text **and** image descriptions |\n\n---\n\n## For AI agents — install doclens in any project\n\nIf you're an AI coding agent (Claude Code, Cursor, Aider, etc.) reading this to set `doclens` up in a user's project, follow this exact sequence:\n\n### Detect prerequisites\n\n```bash\ndocker --version || echo \"MISSING: docker\"\ntest -f .env || cp .env.example .env       # then prompt user to add ANTHROPIC_API_KEY\n```\n\n### Option A — clone, drop in PDFs, run\n\n```bash\ngit clone https://github.com/Padrio/doclens.git \u003ctarget-dir\u003e\ncd \u003ctarget-dir\u003e\n# user adds PDFs and ANTHROPIC_API_KEY\n./scripts/doclens.sh build\n./scripts/doclens.sh all\n```\n\n### Option B — embed in an existing project\n\n```bash\n# from a doclens checkout\n./scripts/init.sh /path/to/existing-project/docs-source\n# this copies Dockerfile, scripts/, pyproject.toml, .env.example,\n# CLAUDE.md, AGENTS.md into the target — does NOT touch existing files\n```\n\nThe init script never overwrites; pass `--force` if you mean it.\n\n### Verify after setup\n\n```bash\nls scripts/                          # convert.py, describe_images.py, build_index.py, doclens.sh, search.sh, search_sections.sh, init.sh\ntest -f Dockerfile -a -f pyproject.toml -a -f .env.example\ntest -f CLAUDE.md -a -f AGENTS.md\n```\n\n### What to tell the user\n\nAfter running `doclens.sh all`, your next session in their project should:\n\n1. **Read `INDEX.md` first** in any task that touches the source documents.\n2. **Use `./scripts/doclens.sh search \"term\"`** before reading `document.md` directly.\n3. **Slice `document.md`** with `Read offset=\u003cline\u003e limit=200` based on TOC line anchors. Never read the whole file.\n4. **Treat PDFs as last resort** — they're untouched, but Markdown + descriptions cover ~99% of needs.\n\nThe `CLAUDE.md` (or `AGENTS.md`) the init script wrote enforces this in future sessions automatically.\n\n---\n\n## Configuration\n\nAll config is **optional**. doclens works on a folder full of PDFs with zero extra files.\n\n### `slugs.json` — override auto-derived slugs\n\nBy default, `My_Doc.pdf` → slug `my-doc`. Override per file or set a display order:\n\n```json\n{\n  \"_order\": [\"primary-doc\", \"secondary-doc\"],\n  \"Confusing_Filename_v1.2.3.pdf\": \"auth-spec\",\n  \"OTHER FILE WITH SPACES.pdf\": \"deployment-guide\"\n}\n```\n\n### `feature-map.json` — feature → document mapping in `INDEX.md`\n\nFor project-specific shortcuts:\n\n```json\n[\n  {\"feature\": \"Authentication flow\",  \"primary\": \"auth-spec\",        \"secondary\": \"deployment-guide\"},\n  {\"feature\": \"Database schema\",       \"primary\": \"data-model\",       \"secondary\": \"—\"},\n  {\"feature\": \"Error handling\",        \"primary\": \"api-spec\",         \"secondary\": \"deployment-guide\"}\n]\n```\n\nRenders as a table in `INDEX.md` with deep-links.\n\n### `system-prompt.txt` — domain-specific image-description prompt\n\nDefault prompts are generic (\"technical document\", in `en` or `de` via `DOCLENS_LANG`). For domain-specific corpora, drop a custom prompt:\n\n```bash\necho \"These images come from medical-device regulatory submissions. Pay special attention to risk-class diagrams and traceability matrices...\" \u003e system-prompt.txt\necho \"DOCLENS_SYSTEM_PROMPT_FILE=system-prompt.txt\" \u003e\u003e .env\n```\n\n### Environment variables (`.env`)\n\n| Variable | Default | Purpose |\n|---|---|---|\n| `ANTHROPIC_API_KEY` | (required) | API key for image descriptions |\n| `ANTHROPIC_MODEL` | `claude-sonnet-4-6` | Vision model |\n| `DOCLENS_LANG` | `en` | Language of default system prompt (`en` or `de`) |\n| `DOCLENS_SYSTEM_PROMPT_FILE` | — | Path to custom system prompt (overrides `DOCLENS_LANG`) |\n| `DOCLENS_OCR_LANG` | `eng+deu` | Tesseract languages, `+`-separated |\n| `DOCLENS_MAX_TOKENS` | `2048` | Per-description response limit |\n| `DOCLENS_REQUEST_DELAY` | `0.2` | Seconds between API calls (rate-limit friendly) |\n| `DOCLENS_IMAGE` | `doclens:latest` | Docker image tag |\n| `DOCLENS_ROOT` | `$PWD` | Mount root inside container |\n\n---\n\n## Cost\n\nPer image description: ~400 input tokens (auto-resized) + ~500 output tokens.\n\nWith `claude-sonnet-4-6` ($3 / MTok input, $15 / MTok output):\n\n| Corpus | Images | Cost |\n|---|---|---|\n| Single 100-page spec | ~50 | $0.05 – $0.15 |\n| Mid-size project (8 docs, 500 pages total) | ~350 | $0.30 – $1.00 |\n| Large enterprise corpus (50 docs, 5000 pages) | ~3500 | $3 – $10 |\n\nRe-runs are free (SHA-256 cache). New PDFs only describe new images.\n\n---\n\n## doclens vs. RAG vs. Vector DB\n\n|  | doclens | Classic RAG | Pure Vector DB |\n|---|---|---|---|\n| Setup time | 15 min | 1–3 days | 2–4 hours |\n| Moving parts | 1 (Markdown) | 5+ (embeddings, store, retrieval, re-ranker, prompt builder) | 3+ (embedder, DB, retriever) |\n| Image content | ✅ Greppable text | ⚠️ Only if you preprocess | ❌ Skipped |\n| Source attribution | ✅ Line numbers | ⚠️ Chunk IDs | ⚠️ Chunk IDs |\n| Cost (50 PDFs) | ~$5 once | ~$5 + recurring | ~$3 + recurring |\n| Works with any LLM | ✅ Markdown is universal | ✅ | ✅ |\n| Works **offline** after build | ✅ | Depends | Depends |\n| Iteration | Edit a `.md`, commit | Re-embed, re-deploy | Re-embed |\n| Best for | Spec/doc corpora \u003c 10k pages | Customer support, FAQ | Semantic search at scale |\n| **Sweet spot** | Coding agents on technical specs | Conversational Q\u0026A | Production search |\n\ndoclens is **not** a replacement for RAG when you need fuzzy semantic recall over millions of chunks. It's a replacement for **\"how do I get my agent to actually read this PDF correctly.\"**\n\n---\n\n## FAQ\n\n### Why Docker? I want native.\n\nDocling pulls PyTorch + transformers + HuggingFace models. PyTorch dropped macOS-Intel wheels at 2.3, the dependency graph for native installs is a maze, and we don't want to maintain a \"supported OS\" matrix. Docker gives us a Linux x86_64 / arm64 base where the wheels are sane.\n\n### Can I use a non-Anthropic vision model?\n\nToday, no — `describe_images.py` is hard-coded to the Anthropic SDK. PRs welcome to add an OpenAI / Ollama / local-VLM backend. The structure is straightforward (one function `describe_with_anthropic`).\n\n### What about non-PDF input?\n\nDocling supports DOCX, PPTX, HTML, images directly. Right now `convert.py` only iterates PDFs; extending to other formats is a 5-line change. PRs welcome.\n\n### Does it commit images?\n\nBy default yes — `docs/\u003cslug\u003e/assets/*.png` are part of the KB and version-controlled. They're typically 50–200 KB each. If your corpus produces gigabytes of images, gitignore `assets/` and use Git LFS or external storage.\n\n### What happens to my PDFs?\n\nNothing. doclens reads them; it never writes. The pipeline is purely additive — it generates `docs/`, `INDEX.md`, etc., and leaves your sources untouched.\n\n### Is the API key sent anywhere besides Anthropic?\n\nNo. `describe_images.py` makes direct calls to `api.anthropic.com`. Image bytes are sent as base64 in the request body; descriptions come back as text. Nothing else leaves your machine.\n\n### What if the description is wrong?\n\nThree escape hatches:\n\n1. **Manually edit** `descriptions.json` — keep the SHA key, change the body. Then `doclens.sh describe --force-repatch` re-injects into `document.md`.\n2. **Re-describe** — delete the SHA from `descriptions.json`, re-run `describe`.\n3. **Custom prompt** — drop hints in `system-prompt.txt` and `--force-repatch` everything.\n\n---\n\n## Roadmap\n\n- [ ] OpenAI / local-VLM backend for `describe_images`\n- [ ] DOCX / PPTX input\n- [ ] Incremental Markdown patching when source PDF changes (currently full re-convert)\n- [ ] Optional embedding sidecar for RAG hybrid setups\n- [ ] CI helper: detect un-described images, fail PR\n\nIf any of these matter to you, open an issue or PR.\n\n---\n\n## Credits\n\n- [Docling](https://github.com/DS4SD/docling) — the heavy lifting on PDF → structured Markdown\n- [Anthropic Claude](https://www.anthropic.com/claude) — vision model for image descriptions\n- The pattern was distilled from real production use on a German legal-tech (eBO/EGVP) project where 8 image-heavy government specs (~500 pages) needed to be made navigable for Claude Code\n\n## License\n\n[MIT](LICENSE) — use it, fork it, ship it.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpadrio%2Fdoclens","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpadrio%2Fdoclens","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpadrio%2Fdoclens/lists"}