Skip to main content

AirDropped iPhone images into your Obsidian vault. OCR + routing via Claude vision.

Project description

image2obsidian

Bridge AirDropped iPhone photos into your Obsidian vault. OCR, classify, and route — automatically.

I like living in the real world. I like paper and pens. I like brushes and paint (shameless artfunk plug). I do not want everything in my life to be entirely digital. Todo lists, diagrams, charts, whatever - I love a notebook. But I had relevant context I wanted to share with my agents in some of these notes. So I built image2obsidian - an open source spinoff of a small part of www.toto.tech.

I have found it very useful and I hope you will too.

Take a photo with an iPhone -> Airdrop it to the device running your agent -> i2o takes the image, semantically analyzes it and incorporates it into your obisidian vault right where it belongs.

image2obsidian watches your Downloads folder for AirDropped images, runs OCR and content analysis with Claude's vision model, and writes structured Markdown documents into your Obsidian vault — sorted into subfolders by content type, with the original image attached as a wikilink.

Watch the image2obsidian demo on X

Works two ways. Same routing rules, same document format, same vault layout:

  • CLIpip install image2obsidian. Scriptable, scheduleable, anyone with an Anthropic API key.
  • Claude Code skill — drop the SKILL.md into your Claude Code skills directory. No API key needed; uses Claude Code's built-in vision.

Follow me on X for more (please i'm so follower poor)

Both paths are MIT-licensed. Both are open source.

Why

Most people who keep a serious knowledge base in Obsidian have a "physical world to digital" gap. Things you write on paper, sketch on a whiteboard, or scrawl in a notebook never make it in — not because you don't want them to, but because the friction (transcribe, classify, file) is just high enough to lose every time.

This tool closes that gap with one keystroke. Take a photo, AirDrop it, run image2obsidian. The note is in the vault, OCR'd, classified, and linked to the original image, in seconds.

Install

CLI

pip install image2obsidian

Set your Anthropic API key:

export ANTHROPIC_API_KEY=sk-ant-...

Create ~/.image2obsidian.json:

{
  "vault_path": "/absolute/path/to/your/Obsidian Vault",
  "vault_root": "Inbox/AirDrop"
}

That's it.

Claude Code skill

If you use Claude Code, you can use this without installing anything Python-side. Drop SKILL.md into your Claude Code skills directory:

mkdir -p ~/.claude/skills/image2obsidian
curl -o ~/.claude/skills/image2obsidian/SKILL.md \
  https://raw.githubusercontent.com/a-funk/image2obsidian/main/SKILL.md

Then create ~/.image2obsidian.json (same config as the CLI) and run /image2obsidian inside Claude Code.

Usage

image2obsidian                  # scan AirDrops from the last 24 hours
image2obsidian --hours 48       # last 48 hours
image2obsidian --all            # every AirDrop in Downloads
image2obsidian --file IMG.jpg   # process one specific image
image2obsidian --dry-run        # show plan, write nothing
image2obsidian --yes            # skip the interactive picker

The Claude Code skill takes the same arguments via /image2obsidian --hours 48.

Example

You AirDropped a hand-drawn architecture sketch and a page of meeting notes. You run image2obsidian:

=== AirDrop scan — 2 image(s) ===

  1. IMG_2401.jpg — May 6, 11:14 AM — iPhone 16 Pro
  2. IMG_2402.HEIC — May 6, 11:15 AM — iPhone 16 Pro

Process which? ("all", numbers like "1 3", or "none") [all]: all

=== Processing 2 image(s) with claude-sonnet-4-6 ===

  IMG_2401.jpg …
    ✓ Inbox/AirDrop/diagrams/sync-engine-architecture.md
  IMG_2402.HEIC …
    ✓ Inbox/AirDrop/notes/q2-planning-meeting-notes.md

=== Done — 2 written, 0 failed ===

Each Markdown doc has the OCR'd content, a summary, key concepts, and the original image inline-rendered via wikilink. The image is copied into the same subfolder so the link stays valid even if you move the vault.

How AirDrop detection works

The single most reliable signal on macOS is the com.apple.quarantine extended attribute. AirDrop sets it with the sharingd agent name:

0081;{hex_timestamp};sharingd;{UUID}

The hex field decodes to the receive time. Local screenshots have no quarantine attribute. Browser downloads use the browser name (e.g. Chrome) instead of sharingd. So checking for sharingd in the quarantine string is a perfect AirDrop filter.

Full investigation in docs/airdrop-detection.md.

Configuration

Full config, with all optional fields:

{
  "vault_path": "/absolute/path/to/your/Obsidian Vault",
  "vault_root": "Inbox/AirDrop",
  "downloads_path": "~/Downloads",
  "default_hours": 24,
  "model": "claude-sonnet-4-6",
  "subfolders": {
    "notes": "notes",
    "diagram": "diagrams",
    "drawing": "drawings",
    "principles": "principles",
    "screenshot": "screenshots",
    "photo": "photos",
    "mixed": "uploads"
  }
}

You can override any of these with CLI flags (--vault, --config, --model) or env vars (IMAGE2OBSIDIAN_VAULT, IMAGE2OBSIDIAN_CONFIG).

Document format

Every imported image becomes a Markdown doc shaped like this:

# Sync Engine Architecture

> Captured 2026-05-06 11:14 via AirDrop from iPhone 16 Pro
> Source: `IMG_2401.jpg`

![[Inbox/AirDrop/diagrams/sync-engine-architecture.jpg]]

## Content

(OCR'd text, structure preserved)

## Analysis

Two-tier sync: local SQLite store mirrors a Postgres source of truth via
last-writer-wins per row. Conflict resolution defers to the server.

**Key concepts:**
- last-writer-wins
- SQLite mirror
- Postgres source of truth

**Visual:** Three boxes (Client, Local DB, Server) connected by labeled
arrows. Sync engine sits between Client and Local DB.

---
*Imported by image2obsidian on 2026-05-06 11:15*

The image is copied into the same subfolder as the doc and linked with a wikilink. Both the doc and the image use the same slug (lowercased, hyphenated title), so collisions are easy to spot and handle.

Platform support

macOS only. AirDrop detection relies on xattr and sips, which are macOS-specific.

For Linux/Windows users, the CLI works fine on a folder of arbitrary images via --file — it just can't auto-filter to "AirDropped today". Pull requests for other platforms welcome.

Development

git clone https://github.com/a-funk/image2obsidian
cd image2obsidian
pip install -e ".[dev]"
pytest

License

MIT — see LICENSE. Use it for whatever you want, commercial or otherwise.

Acknowledgements

Originally extracted from the Toto project's /toto-digest skill, which had the same job but tightly coupled to Toto's task system. This is the same idea, generalized for everyone who lives in Obsidian.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

image2obsidian-0.1.0.tar.gz (264.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

image2obsidian-0.1.0-py3-none-any.whl (16.2 kB view details)

Uploaded Python 3

File details

Details for the file image2obsidian-0.1.0.tar.gz.

File metadata

  • Download URL: image2obsidian-0.1.0.tar.gz
  • Upload date:
  • Size: 264.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.12

File hashes

Hashes for image2obsidian-0.1.0.tar.gz
Algorithm Hash digest
SHA256 ff481b2a1a40507bfcb71efb6b79fafa0900ffc57c20e2863c8a703e64746986
MD5 382baf9aff3311c18f816e4029a6eaca
BLAKE2b-256 73944c3fa52746d93cbbb48708162a26fa0efdd8a7187e4ddd029b1497bd583d

See more details on using hashes here.

File details

Details for the file image2obsidian-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: image2obsidian-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 16.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.12

File hashes

Hashes for image2obsidian-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 bf5f28929bb6b0947699c04636c1d9814d94766739a06697f7bdebf3bbe570cc
MD5 b193bd986260f86ec2290cbf9ff7370f
BLAKE2b-256 4b9d07832f90f70d748b35c7ac0fcb9b034fa48309543ed72c1e3daa45a5c0e2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page