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.

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 -> autotag creates vX.Y.Z tag -> build produces wheel and sdist -> a draft GitHub Release is created with the wheel attached
  4. (optional) make smoke-test to verify the wheel pip-installs cleanly in a throwaway venv
  5. make release — auto-generates release notes from commits since the last tag, shows them in your terminal, prompts for confirmation, then:
    • writes the notes to the draft Release
    • flips the draft to published
    • publishing fires the release: published event in CI
    • CI re-runs and pushes the wheel to PyPI via OIDC trusted publishing

make release is the only thing that triggers a PyPI push. If you push a fix to main after seeing the draft Release, the workflow rebuilds and re-attaches the fresh wheel to the same draft, so the eventual make release always ships the latest code.

No manual tag step. No release notes to hand-write. No API tokens in CI.

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

(Skip this if the package is already on PyPI with OIDC configured against the current workflow filename.)

  1. First upload manually with ./upload-to-pypi.sh (needs ~/.pypirc with a PyPI API token) to claim the package name.
  2. On PyPI: https://pypi.org/manage/project/notability-extractor/settings/publishing/ -> Add a new trusted publisher -> GitHub:
    • Owner: mdeguzis
    • Repository: notability-extractor
    • Workflow filename: ci-linux.yml (the publish job lives there)
    • Environment name: pypi
  3. On GitHub: https://github.com/mdeguzis/notability-extractor/settings/environments -> Create pypi environment (no protection rules required).

Note: if you previously configured a trusted publisher against ci.yml (the old combined workflow), it stops matching after the OS split. Either edit that entry to point at ci-linux.yml or delete it and add the new one above.

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.4.1.tar.gz (136.9 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.4.1-py3-none-any.whl (65.6 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: notability_extractor-0.4.1.tar.gz
  • Upload date:
  • Size: 136.9 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.4.1.tar.gz
Algorithm Hash digest
SHA256 5ec9d8431a51a144cd46daa126afba16d2fb7c3fccefc3f767a4704f98ced8bb
MD5 bdfad75f2108b8f5b5f4219ebd4162c9
BLAKE2b-256 b36252a4f46738b8ad12c43622be4e6e8acbab272719e4875b9c777574b75367

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for notability_extractor-0.4.1-py3-none-any.whl
Algorithm Hash digest
SHA256 43d16199ffb5ab4c88706fe0ae64cc397e73cb91427cbfb1ddb34132aca10ac6
MD5 24c3677e72e3d8a74109847ec3490e90
BLAKE2b-256 cb84ab5d7eadf73badcef80e74f36e62ac3dffb341577da399d47a6160b6514d

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