Elixir bindings to Harper — a fast, offline, privacy-first English grammar and spell checker written in Rust by Automattic. Harper runs entirely in-process: no server, no network, no LLM, no model download. It's the engine behind writewithharper.com.

This package wraps harper-core as a Rustler NIF and ships precompiled binaries for common targets, so most consumers don't need a Rust toolchain.

Installation

def deps do
  [{:harper, "~> 0.1"}]
end

Precompiled artifacts are provided for macOS (aarch64/x86_64) and Linux (aarch64/x86_64, gnu). Other targets — including FreeBSD — build from source via rustler, which needs a Rust toolchain (≥ 1.85, for edition 2024). To force a source build on a supported target:

RUSTLER_PRECOMPILATION_FORCE_BUILD=1 mix deps.compile harper

Usage

Harper.lint/1,2,3 returns a list of Harper.Lint structs. It's a pure, stateless function and runs on a dirty CPU scheduler, so linting a large document won't block the BEAM's normal schedulers.

iex> Harper.lint("Ths is a testt.")
[
  %Harper.Lint{
    start: 0, end: 3, kind: "Spelling", priority: 63,
    message: "Did you mean to spell `Ths` this way?",
    suggestions: [%Harper.Suggestion{kind: "replace", text: "This"}, ...]
  },
  ...
]

iex> Harper.lint("This is a clean sentence.")
[]

Dialects

English spelling is dialect-sensitive. lint/2 takes one of Harper.dialects/0 (:american — the default — :british, :canadian, :australian, :indian):

iex> Harper.lint("I love the colour blue.", :american) |> Enum.any?(& &1.kind == "Spelling")
true
iex> Harper.lint("I love the colour blue.", :british) |> Enum.any?(& &1.kind == "Spelling")
false

Custom dictionary

Pass words (proper nouns, brand terms, handles) that should never be flagged as misspellings — the server-side equivalent of "add to dictionary":

iex> Harper.lint("I write for acme daily.", :american, ["acme"]) |> Enum.any?(& &1.kind == "Spelling")
false

Offsets

Harper.Lint :start/:end are UTF-16 code-unit offsets into the linted string, end-exclusive — chosen so they drop straight into JavaScript string indexing and editor APIs (Quill, contenteditable ranges) without conversion.

Notes

  • English only. Gate callers to English-authored content.
  • :kind is Harper's LintKind ("Spelling", "Agreement", "WordChoice", "Style", "Typo", "Miscellaneous", …) — useful for grouping/colouring.
  • Harper.Suggestion :kind is "replace", "insert_after", or "remove"; apply it over the lint's span.

Releasing (maintainers)

Precompiled tarballs are built locally on macOS and hosted on the sr.ht release ref. See scripts/:

  • build_artifacts.sh — mac (native) + linux (via cargo-zigbuild).
  • build_freebsd_via_ssh.sh — FreeBSD, built on a remote box (opt-in).
  • deploy.sh — bump, build, hash into the checksum manifest, tag, upload, mix hex.publish.

License

Apache-2.0. Bundles the Apache-2.0 harper-core (statically linked into the NIF); each precompiled tarball ships LICENSE, LICENSE-APACHE, and a THIRD-PARTY-NOTICES file generated from the full dependency tree.