Sharkdown is a local-first CLI and REST API that converts handwritten PDF notes into structured Markdown files.
You supply a PDF of your handwritten notes, and Sharkdown gives you back a clean .md file with the content transcribed and organized — titles, lists, callouts, and all.
PDF → page images (pdf2image) → AI Vision (OCR + structuring) → .md file
Each page is rendered as an image and sent to the selected AI model in a single call. The model handles both transcription and Markdown structuring at once, keeping the pipeline simple and the output consistent.
| Content type | Output |
|---|---|
| General text | Markdown with headings, lists, paragraphs |
| Text in a cloud / box | Callout > [!NOTE] |
| Diagrams or visual schemas | Placeholder [DIAGRAMA] |
| Illegible words | Placeholder [ILEGIBLE] |
| Model | CLI flag | API endpoint | Tier |
|---|---|---|---|
| Gemini 2.5 Flash | --model gemini (default) |
POST /convert/gemini |
Free |
| Claude | --model claude |
POST /convert/claude |
Paid |
-
Python 3.11+
-
Poppler — required by
pdf2imageto render PDF pages:# Debian / Ubuntu sudo apt install poppler-utils # macOS brew install poppler # Arch sudo pacman -S poppler
If you get a
PopplerNotInstallederror at runtime, this is the dependency to check.
All Python dependencies are declared in pyproject.toml and installed with pip install ..
| Library | Purpose |
|---|---|
pdf2image |
Renders each PDF page as a PIL.Image.Image |
Pillow |
Image type used across the pipeline |
google-genai |
Official Gemini SDK (Vision + text generation) |
anthropic |
Official Anthropic SDK (Claude) |
fastapi + uvicorn |
REST API server |
pydantic / pydantic-settings |
Data validation and settings management |
click |
CLI interface |
pip install .
pip install uvicorn python-multipart # only needed for the APICreate a .env file at the project root. Use .env.example as a reference:
cp .env.example .env| Variable | Required | Description |
|---|---|---|
GEMINI_API_KEY |
Only if using Gemini | API key from Google AI Studio |
ANTHROPIC_API_KEY |
Only if using Claude | API key from Anthropic Console |
CORS_ORIGINS |
No | Comma-separated list of allowed frontend origins (see Web UI) |
# Convert with Gemini (default)
sharkdown img --file path/to/notes.pdf
# Convert with Claude
sharkdown img --file path/to/notes.pdf --model claudeThe output .md file is saved alongside the original PDF.
Start the server:
uvicorn sharkdown.api.app:app --host 0.0.0.0 --port 8000Convert a PDF:
curl -X POST http://localhost:8000/convert/gemini \
-F "file=@notes.pdf" \
-o notes.mdInteractive docs: http://localhost:8000/docs
A browser-based interface is available in the client/ directory.
See client/README.md for setup instructions.
Both images include Poppler — no extra system setup needed.
Runs the backend and the web UI together behind an nginx reverse proxy.
cp .env.example .env # fill in your API key(s)
make up # build and start both servicesThe web UI will be available at http://localhost:7070.
| Command | Description |
|---|---|
make up |
Build (if needed) and start all services |
make down |
Stop and remove containers |
make restart |
Restart all services without rebuilding |
make build |
Force a full image rebuild (no cache) |
make logs |
Follow logs from all services |
make ps |
Show running containers |
make clean |
Stop containers and remove project-built images |
CORS_ORIGINSis automatically set tohttp://localhostin docker-compose — no manual change needed.
docker build -t sharkdown .
# Pass the API key directly
docker run -d -p 8000:8000 --name sharkdown -e GEMINI_API_KEY=your_key sharkdown
# Or use a .env file
docker run -d -p 8000:8000 --name sharkdown --env-file .env sharkdownThe API will be available at http://localhost:8000.
# Default: empty VITE_API_BASE → relative URLs (expects a proxy in front)
docker build -t sharkdown-client ./client
# With a specific backend URL baked in
docker build --build-arg VITE_API_BASE=http://localhost:8000 -t sharkdown-client ./client
docker run -d -p 80:80 sharkdown-client