Skip to main content

Extract Notability Learn quizzes, summaries, and note text; export to Anki / JSON / Markdown with a PySide6 GUI

Project description

notability-extractor

CI Linux CI macOS PyPI version Python versions License: MIT

Extract Notability Learn content (AI-generated quizzes, summaries, and OCR'd note text) and export it as an Anki .apkg deck, JSON, or Markdown for review.

How it works

Notability Learn generates quizzes via Claude Haiku and summaries via Gemini 2.5 Flash, server-side. Both get cached locally in the app's HTTP cache. Handwriting OCR and PDF text live inside .nbn note bundles.

The tool runs in two phases:

  1. Extract (macOS only): walks the iCloud Drive .nbn bundles and the HTTP cache (Cache.db + fsCachedData/), writes a normalized export directory at ~/notability_export/.
  2. Build (any OS): reads the export directory, merges flashcards into a persistent JSONL archive at ~/.notability_extractor/cards.jsonl, and emits outputs: .apkg for Anki, .json for programmatic review, .md for human reading.

Linux and Windows machines can skip phase 1 by pointing --input-dir at a directory produced on a Mac. Useful if you want to do the Anki packaging on a different machine than the one Notability runs on.

The JSONL archive is the source of truth for flashcards. Both the CLI and the GUI read from and write to it, and the build always reconstructs .apkg from the archive (not the input dir) so edits and tags persist across rebuilds.

Install

End users (recommended)

Install from PyPI with pip. You do NOT need to clone this repo and you do NOT need uv or make.

Linux:

pip install --user notability-extractor

macOS:

# macOS only ships `pip3`, not `pip`. Use this:
pip3 install --user notability-extractor
# or equivalently:
python3 -m pip install --user notability-extractor

This installs two console scripts to ~/.local/bin/:

  • notability-extractor - the CLI
  • notability-extractor-gui - the desktop GUI

Make sure ~/.local/bin is on your PATH. On macOS that usually means adding export PATH="$HOME/.local/bin:$PATH" to ~/.zshrc.

The first time you launch notability-extractor-gui, it auto-installs a desktop shortcut (a .desktop entry on Linux, a .app bundle in ~/Applications/ on macOS) so you can launch it from your app menu next time. If you'd rather skip that step, run notability-extractor --remove-shortcut.

Developers

This part only matters if you want to hack on the code:

git clone https://github.com/mdeguzis/notability-extractor.git
cd notability-extractor
make install-dev   # requires `uv` - see https://docs.astral.sh/uv/

make install-dev uses uv to set up .venv/ for dev work and drops the console scripts into ~/.local/bin/. uv is a dev-only tool here, end users don't need it. (make install is intentionally not the dev target - it just prints these instructions, so people who try the obvious thing don't accidentally pull in the dev toolchain.)

Usage

# macOS: auto-extract and build all outputs in the current dir
notability-extractor

# Anywhere: build from a pre-extracted directory
notability-extractor --input-dir ~/notability_export

# Custom output directory
notability-extractor --input-dir ~/notability_export --out-dir ./decks

# macOS: just run phase 1 (produce export dir, no .apkg/json/md)
notability-extractor --extract-only

# Custom Anki deck name (only affects what shows up inside Anki)
notability-extractor --deck-name "Biology 101"

Output filenames are fixed:

File Contents
notability_flashcards.apkg Anki deck (quiz questions only)
notability_flashcards.json Full structured dump for programmatic review
notability_flashcards.md Human-readable flashcards
notability_notes.{json,md} Note transcripts
notability_summaries.{json,md} Generated summaries

Archive management CLI flags

# Open a prompt-driven editor against the archive
notability-extractor --edit-flashcards

# One-shot interactive add
notability-extractor --add-card

# Print archive contents (optionally filter by tag)
notability-extractor --list-cards
notability-extractor --list-cards --tag biology

# Backup / restore round-trip
notability-extractor --backup
notability-extractor --export ~/cards-backup.jsonl
notability-extractor --import ~/cards-backup.jsonl --mode merge
notability-extractor --import ~/cards-backup.jsonl --mode replace

# Launch the GUI
notability-extractor --gui

GUI

A PySide6 desktop companion ships in the same package. After install:

notability-extractor-gui

Pages: Library (browse / edit / add / delete cards with tag filter), Notes (read-only), Summaries (read-only), Build (export apkg / json / md), Settings (theme, paths, backups, schedule).

On Wayland with no DISPLAY set, the GUI auto-applies QT_QPA_PLATFORM=wayland so SSH/login sessions don't need a manual export.

Backups

The archive at ~/.notability_extractor/cards.jsonl is snapshotted on every save to ~/.notability_extractor/backups/cards-YYYYMMDD-HHMMSS.jsonl, hash-deduped so unchanged saves don't make redundant copies. Default retention is the last 10 snapshots (configurable in Settings).

For a scheduled backup when the GUI is closed, run from cron:

0 * * * * notability-extractor --backup

The Settings page surfaces this exact line for convenience.

Importing into Anki

  1. Open Anki on your desktop.
  2. File > Import and select the generated .apkg file.
  3. The deck appears as "Notability Flashcards" (or whatever you passed to --deck-name).

Caveats

  • The Learn cache only contains content from sessions you've actively opened in Notability. If a note has never had Learn run on it, no quiz is cached.
  • Notability does not provide a stable export API. The tool reads on-disk formats that could change between app versions. If extraction breaks after a Notability update, open an issue.
  • iPadOS-only setups need iCloud Drive sync enabled so the .nbn bundles and cache files are present on a Mac. Without sync, you'd need physical access to the iPad's sandbox (not currently supported).

Releasing

Releases are semi-automated via GitHub Actions. Publishing to PyPI is gated on a maintainer explicitly publishing a GitHub Release - normal commits to main never push to PyPI.

Releases are semi-automated. CI proves the code is shippable (tests + builds + draft GitHub Release with the wheel attached). The maintainer drives the actual ship via two explicit local commands. Nothing publishes automatically from CI.

To cut a new release:

  1. Bump the version = "X.Y.Z" line in pyproject.toml
  2. Commit and push to main
  3. CI runs on the push: tests pass, wheel + sdist build, smoke-installed in a fresh venv. CI does not tag, does not create a Release, does not publish anywhere.
  4. (optional) make smoke-test to verify the wheel pip-installs cleanly in a throwaway venv
  5. make github-release does the entire GitHub release in one shot: creates the vX.Y.Z tag, pushes it, builds the wheel (if dist/ is empty), creates the draft Release with the wheel attached, seeds notes from git log since the last tag, opens them in $EDITOR (vim default), and on save+quit flips the draft to published. This does NOT push to PyPI.
  6. make pypi-release — confirms the version, checks PyPI doesn't already have it, then builds + uploads the wheel via upload-to-pypi.sh (twine + ~/.pypirc API token).

Run steps 5 and 6 in either order, or run only one of them. If you want a release-notes-only update on a previously-published version, run just make github-release (which short-circuits and exits cleanly if the release is already published).

No CI auto-tag. No CI auto-publish. The whole release path is local.

Testing pip install before pushing to PyPI

To verify a pre-release wheel installs cleanly the same way pip3 install notability-extractor would for end users, run:

make smoke-test

This is a self-contained PASS/FAIL check:

  1. cleans dist/, builds a fresh wheel
  2. creates a throwaway venv at .venv-smoke/
  3. pip install dist/*.whl (real pip, mimics PyPI)
  4. runs notability-extractor --version and checks the GUI binary is there
  5. pip uninstall then nukes the venv

If anything in that chain breaks, you find out before pushing to PyPI.

To separately exercise the GUI's first-launch desktop-shortcut auto-install:

make reset-shortcut       # clear any existing launcher + first-launch marker
notability-extractor-gui  # should auto-install the shortcut on first launch
ls ~/.local/share/applications/notability-extractor.desktop   # (Linux) verify
ls ~/Applications/'Notability Extractor.app'                  # (macOS) verify

If smoke-test ever gets interrupted halfway and leaves state behind:

make smoke-clean   # remove the .venv-smoke dir + any leftover shortcut

One-time PyPI setup

make pypi-release uses ~/.pypirc + twine to upload (not OIDC), so the only setup is:

  1. Get a PyPI API token: https://pypi.org/manage/account/token/

  2. Drop it into ~/.pypirc:

    [distutils]
    index-servers = pypi testpypi
    
    [pypi]
    username = __token__
    password = pypi-<your-token>
    
    [testpypi]
    repository = https://test.pypi.org/legacy/
    username = __token__
    password = pypi-<your-test-token>
    

That's it. No trusted-publisher config, no GitHub pypi environment, no OIDC exchange. The maintainer's local machine is the trust anchor.

Manual ad-hoc upload

Only needed if CI is broken:

./upload-to-pypi.sh --test   # TestPyPI dry-run first
./upload-to-pypi.sh          # then prod PyPI

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

notability_extractor-0.9.1.tar.gz (155.5 kB view details)

Uploaded Source

Built Distribution

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

notability_extractor-0.9.1-py3-none-any.whl (82.9 kB view details)

Uploaded Python 3

File details

Details for the file notability_extractor-0.9.1.tar.gz.

File metadata

  • Download URL: notability_extractor-0.9.1.tar.gz
  • Upload date:
  • Size: 155.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for notability_extractor-0.9.1.tar.gz
Algorithm Hash digest
SHA256 cd28b0e222aa1e4d108698d1a80fa151000ca4f8f427bdede20348f4421694d0
MD5 29c54443d1adf279550c2712cc01e5a2
BLAKE2b-256 eb42668386dd3305276fad30c98ff1754877c164cfdb768d3a380cb445b306de

See more details on using hashes here.

File details

Details for the file notability_extractor-0.9.1-py3-none-any.whl.

File metadata

File hashes

Hashes for notability_extractor-0.9.1-py3-none-any.whl
Algorithm Hash digest
SHA256 1713b674917b0e09addfb1eb97abfe65540433c3b419b6c122563f6b53c72639
MD5 c6c53a73110b2612b3038bf54aa874c6
BLAKE2b-256 2834fa13578faf291f90d39577d1f77d18d3b1d5fe06f2c40526a01216e57907

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