8000 GitHub - jlopez/owlcatraz: Export your Duolingo vocabulary to an Anki deck for personal spaced-repetition study. · GitHub
[go: up one dir, main page]

Skip to content
jlopezPublic

About

Export your Duolingo vocabulary to an Anki deck for personal spaced-repetition study.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

13 Commits

Folders and files

Repository files navigation

Owlcatraz

CI Latest release License: MIT Conventional Commits Code style: Prettier PRs welcome

Owlcatraz — export your Duolingo vocabulary to an Anki deck.

What it does

Owlcatraz is a Chrome (MV3) extension that reads your own learned-vocabulary list from your logged-in Duolingo session and writes it as notes into a local Anki deck via the AnkiConnect add-on. The whole flow runs in your browser: Duolingo fetch, grammatical enrichment (via the Anthropic API using your own key), and the AnkiConnect write. The data and sync layers are language-agnostic; the morphology and enrichment layers are course-specific, and the extension currently supports the Greek and French courses. Additional courses are on the roadmap.

What it does not do

  • It does not bypass Duolingo paid features (Super, Max, Family, etc.). It only reads vocabulary you have already earned through normal study.
  • It does not scrape or read other users' data. Only the signed-in user's own learned-lexemes list is read.
  • It does not store, transmit, or sell any user data to any developer-controlled server. There is no Owlcatraz backend.
  • It does not run automated mass requests against Duolingo's servers. It walks the user's paginated lexeme list once per sync, using the same endpoints the Duolingo website itself uses.
  • It does not modify Duolingo's site behavior beyond initiating the export on the user's request.
  • It does not give you a free Duolingo Super subscription, unlock premium features, or interact with billing in any way. (Worth saying explicitly — please don't ask.)

Why this exists

I'm a Duolingo user who also uses Anki. I wanted my vocabulary in both places — Duolingo for daily practice, Anki for spaced-repetition review on my own schedule. This tool does that for me, and now for anyone else who wants the same. The two services are complementary, not competing.

Installation

Chrome Web Store: coming soon. The extension isn't on the store yet — a listing is being prepared. Until it ships there, install it one of two ways. Both load it as an unpacked extension, so keep whichever folder you point Chrome at — Chrome reads from it on every launch, and Chrome will remind you on startup that a developer-mode extension is installed.

Option A — From a release (no build tools)

  1. Download the latest owlcatraz-<version>.zip from the Releases page.
  2. Unzip it into a folder you intend to keep (not a temporary directory).
  3. Open chrome://extensions in Chrome, toggle Developer mode on (top-right).
  4. Click Load unpacked and select the unzipped folder (the one containing manifest.json).

To update later, download the newer release, replace the folder's contents, and click Reload (↻) on the extension card. The build ships with a pinned extension ID, so your settings and enrichment cache carry across updates (see the note below for the one exception — moving to the Web Store build).

Option B — Build from source (for development)

  1. Clone the repo: git clone https://github.com/jlopez/owlcatraz.git
  2. Build it:
    cd owlcatraz/extension
    pnpm install
    pnpm build
    
  3. Open chrome://extensions in Chrome, toggle Developer mode on (top-right).
  4. Click Load unpacked and select owlcatraz/extension/dist/.

Option C — One command (from a repo checkout)

If you have the repo checked out, three pnpm scripts do the fetch-and-unpack work for you and leave only the one-time Load unpacked click. Each populates the same stable folder (~/.owlcatraz/unpacked), so after the first load, updating is just the Reload (↻) click. Run them from extension/:

Command Installs
pnpm install:latest The newest published GitHub release
pnpm install:pr The CI preview build for the current branch's open PR
pnpm install:local A fresh pnpm build of your working tree

Chrome can't silently install a non-Web-Store extension, so the final Load unpacked (first time) or Reload (updates) step is still manual — the scripts open chrome://extensions, and on macOS copy the folder path to your clipboard, to make it a few seconds. install:latest and install:pr need the GitHub CLI (gh auth login); install:pr detects the PR from your current branch and, if its CI is still running, waits for the preview build to finish before loading it.

Usage

  1. The Owlcatraz toolbar icon appears. Click it on any tab where you are logged in to duolingo.com.
  2. Open Settings, paste your Anthropic API key (used for grammatical enrichment — gender, part-of-speech, inflection notes), optionally adjust the Anki deck name, and Save.
  3. Start Anki with the AnkiConnect add-on installed.
  4. Click Sync to Anki. The popup streams progress through the steps: read Duolingo session → fetch lexemes → enrich → write notes.

Re-running Sync to Anki is safe — duplicate notes are detected via a preflight pass and skipped without re-fetching audio.

When the Web Store release lands: the published build will have a different extension ID than the unpacked builds above (both of which share a pi 8000 nned ID), and Chrome scopes chrome.storage.local per extension ID. So your settings and the grammatical-enrichment cache (the API key and the per-word LLM results) will not carry over automatically when you switch to the Store version — you'll re-enter your Anthropic API key once, and the enrichment cache rebuilds itself on the next sync (re-calling the Anthropic API for words it had previously cached). The Anki deck itself is unaffected; it lives in Anki, not in the extension.

Privacy

Owlcatraz is local-first. There is no Owlcatraz server, no analytics, no telemetry, and no developer-controlled endpoint that ever receives your data.

The extension makes outbound network requests only to:

  • *.duolingo.com — to fetch your own profile and learned-lexemes list, using the Duolingo session cookie that's already in your browser. Same data the website shows you, fetched the same way.
  • Duolingo's audio CDN (*.cloudfront.net) — to download the pronunciation audio served for each lexeme so it can be embedded in the Anki note. The exact subdomain is pinned in the manifest's host_permissions and lives in extension/manifest.config.ts; URLs come directly from the Duolingo API response.
  • api.anthropic.com — to call Claude Haiku for grammatical enrichment (gender, inflection notes, English glosses). This uses your Anthropic API key, which you provide in the extension's Settings panel; calls are billed to your Anthropic account and the prompt/response data is governed by Anthropic's privacy policy. If you would rather not send your vocabulary to Anthropic, simply don't configure the key — the extension will refuse to sync until a key is present.
  • 127.0.0.1:8765 — your local Anki + AnkiConnect endpoint, on loopback. Nothing leaves your machine.

The Anthropic API key, Anki deck name, and enrichment cache are stored in chrome.storage.local — i.e. on your local Chrome profile. They are not synced to your Google account.

If you ever want to verify any of this, the relevant code is in extension/src/lib/ and the manifest permissions are documented inline in extension/manifest.config.ts.

Relationship to Duolingo

This project is not affiliated with, endorsed by, or sponsored by Duolingo, Inc. "Duolingo" and the Duolingo owl are trademarks of Duolingo, Inc. The name Owlcatraz is an affectionate parody reference and does not imply any official relationship. The extension does not include any Duolingo logos, the owl character, or other Duolingo branding assets.

Owlcatraz uses the same authenticated session your browser already uses to talk to Duolingo's API on your behalf, walking the same lexeme-list endpoints the duolingo.com website uses to display your vocabulary.

If you are at Duolingo and have concerns about this project — whether about scope, traffic patterns, naming, or anything else — please reach out at owlcatraz@jesusla.com. I'd much rather hear from you directly than have anyone guess at intent. The project's documented scope (personal vocabulary export, narrowly construed) is the project's actual scope; the CONTRIBUTING.md file documents the out-of-scope categories that will be declined.

Contributing

Issues and pull requests are welcome. Please read CONTRIBUTING.md first — it covers how to file good bug reports (Duolingo ships A/B-tested API changes constantly, so structured reports help a lot) and what categories of contribution are out of scope.

Bug reports go in Issues; open-ended questions and design conversations go in Discussions.

This project follows the Contributor Covenant.

Security

To report a security vulnerability, please follow SECURITY.md rather than opening a public issue.

License

MIT © 2026 Jesus Lopez.


Developer notes

The remainder of this file is for contributors and curious readers. End users don't need any of it.

Project layout

  • extension/ — the Vite + TypeScript MV3 extension.
  • fixtures/ — synthesized lexeme pages and a minimal profile (~50 basic Greek words at the root, plus ~50 French words under fixtures/fr/, all drawn from general public-domain vocabulary) used to drive the 221-test offline suite.
  • .github/workflows/ci.yml — typecheck, lint, format-check, test, and build on every PR and on push to main.

Commands (run from extension/)

Command Purpose
pnpm dev Vite build in watch mode (load unpacked from dist/)
pnpm build Production build to dist/
pnpm test Run vitest once
pnpm test:watch Watch-mode vitest
pnpm typecheck tsc --noEmit strict typecheck
pnpm lint ESLint (flat config) — fails on any warning
pnpm format Prettier write
pnpm format:check Prettier check (used by CI)

End-to-end smoke test

The 221-test offline suite (pnpm test) covers the data, morphology, enrichment, sync orchestration, and popup-render layers. The live Duolingo API and AnkiConnect interactions can only be exercised against a real Chrome profile and a running Anki + AnkiConnect. The smoke test below assumes Greek is your active Duolingo course; a French course works identically — the popup shows course fr and the default deck is Duolingo::French. Smoke-test steps:

  1. cd extension && pnpm build.
  2. Open chrome://extensions in a Chrome window where you are signed in to Duolingo. Toggle Developer mode on.
  3. Click Load unpacked and select extension/dist/. The Owlcatraz action icon appears in the toolbar.
  4. Click the Service worker link on the extension card. The DevTools console should be empty on a fresh load.
  5. Logged-out check: open an Incognito window (with extensions enabled) and click the icon. Should say "Please log in at duolingo.com".
  6. Confirm Greek is the active Duolingo course on duolingo.com.
  7. Click the icon. The popup should show your userId and "course el".
  8. Open Settings, paste your Anthropic API key (sk-ant-…), optionally adjust the deck name (default Duolingo::Greek), Save.
  9. Reopen the popup — the API key should round-trip back.
  10. Start Anki with the AnkiConnect add-on enabled.
  11. Click Sync to Anki. Progress should stream through five steps (session read → profile lookup → lexeme fetch → enrichment → AnkiConnect write).
  12. On completion, the stats table reports words fetched, enriched, added, skipped, audio stored, audio failed. In Anki, the deck contains notes with English, Target, Lemma, POS, Inflection, Notes, Audio populated; audio plays on the Recognition back side.
  13. Click Sync again. Second run reports everything as duplicates and finishes in seconds (the preflight skips re-fetching audio for known notes).

If anything misbehaves, check the service-worker console (it captures syncError text plus any per-URL audio-fetch warnings) and verify the Anthropic API key.

Roadmap

In rough priority order:

  • Enrichment-algorithm bug fixes. A handful of known issues to track and address as they surface — the build-version auto-heal lets fixes ship without forcing users to delete and re-sync the deck.
  • "Clear enrichment cache" toggle. When the LLM prompt itself changes (rather than the rendering code), bumping the build version re-renders notes from the existing cache. A user-facing toggle to drop the cache and force re-enrichment would close that gap.
  • Sync progress bar. The popup currently streams text-only progress lines; a visual bar against per-step totals would be a cheap UX win for the enrichment and AnkiConnect-write steps (which have known totals up front).
  • Incremental sync. A "since last sync" mode would save the Duolingo walk on daily-driver re-syncs. Lower priority because the LLM cost is already amortized by the cache and Duolingo's pagination is fast.
  • Regression tests for live-only paths. Three code paths are only exercised end-to-end against a live runtime: the Origin-stripping DNR session rule, the AnkiConnect rollback-on-duplicate response shape, and the LLM-omission re-queue logic. Each is a likely candidate for silent breakage and worth a targeted boundary test.
  • More courses. Greek and French are implemented as self-contained LanguageModules under extension/src/lib/lang/; adding another course (Spanish, German, …) means a new module + fixtures + tests, with no changes to the data or sync layers. Each language picks the article convention that best teaches its gender — Greek uses the definite article, French the indefinite (une eau, not the elided l'eau, so gender stays visible before a vowel).

About

Export your Duolingo vocabulary to an Anki deck for personal spaced-repetition study.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

0