{"id":50254063,"url":"https://github.com/nvlang/ankify","last_synced_at":"2026-05-27T03:39:53.717Z","repository":{"id":358766578,"uuid":"1002692102","full_name":"nvlang/ankify","owner":"nvlang","description":"Generate and sync Anki flashcards from your Typst documents.","archived":false,"fork":false,"pushed_at":"2026-05-20T00:41:54.000Z","size":1875,"stargazers_count":1,"open_issues_count":7,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-27T03:39:50.336Z","etag":null,"topics":["anki","typst"],"latest_commit_sha":null,"homepage":"https://typst.app/universe/package/ankify","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nvlang.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-06-16T01:47:33.000Z","updated_at":"2026-05-25T02:38:56.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/nvlang/ankify","commit_stats":null,"previous_names":["nvlang/ankify"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/nvlang/ankify","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nvlang%2Fankify","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nvlang%2Fankify/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nvlang%2Fankify/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nvlang%2Fankify/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nvlang","download_url":"https://codeload.github.com/nvlang/ankify/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nvlang%2Fankify/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33549782,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-27T02:00:06.184Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["anki","typst"],"created_at":"2026-05-27T03:39:52.266Z","updated_at":"2026-05-27T03:39:53.707Z","avatar_url":"https://github.com/nvlang.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cbr\u003e\n\u003cdiv align=\"center\"\u003e\n\u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://raw.githubusercontent.com/nvlang/ankify/main/res/dark/logotype.svg\"\u003e\n    \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"https://raw.githubusercontent.com/nvlang/ankify/main/res/light/logotype.svg\"\u003e\n    \u003cimg alt=\"Ankify Logotype\" src=\"https://raw.githubusercontent.com/nvlang/ankify/main/res/light/logotype.svg\" width=\"33%\"\u003e\n\u003c/picture\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\u003cdiv\u003e\n\n[\u003cpicture\u003e\u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://img.shields.io/badge/ankify-_?style=flat-square\u0026logo=typst\u0026logoColor=a3acb7\u0026labelColor=21262d\u0026color=21262d\u0026logoSize=auto\"\u003e\u003csource media=\"(prefers-color-scheme: light)\" srcset=\"https://img.shields.io/badge/ankify-_?style=flat-square\u0026logo=typst\u0026logoColor=24292f\u0026labelColor=eaeef2\u0026color=eaeef2\u0026logoSize=auto\"\u003e\u003cimg alt=\"Typst package name\" src=\"https://img.shields.io/badge/ankify-_?style=flat-square\u0026logo=typst\u0026logoColor=24292f\u0026labelColor=eaeef2\u0026color=eaeef2\u0026logoSize=auto\"\u003e\u003c/picture\u003e](https://typst.app/universe/package/ankify)\n[\u003cpicture\u003e\u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://img.shields.io/badge/crates/ankify-_?style=flat-square\u0026logo=rust\u0026logoColor=a3acb7\u0026labelColor=21262d\u0026color=21262d\u0026logoSize=auto\"\u003e\u003csource media=\"(prefers-color-scheme: light)\" srcset=\"https://img.shields.io/badge/crates/ankify-_?style=flat-square\u0026logo=rust\u0026logoColor=24292f\u0026labelColor=eaeef2\u0026color=eaeef2\u0026logoSize=auto\"\u003e\u003cimg alt=\"Rust crate name\" src=\"https://img.shields.io/badge/crates/ankify-_?style=flat-square\u0026logo=rust\u0026logoColor=24292f\u0026labelColor=eaeef2\u0026color=eaeef2\u0026logoSize=auto\"\u003e\u003c/picture\u003e](https://crates.io/crates/ankify)\n\u003c/div\u003e\n\u003c/div\u003e\n\u003cbr\u003e\n\n\u003e [!WARNING]\n\u003e `ankify` is in alpha and under active development. Expect bugs, rough edges,\n\u003e and breaking changes between releases.\n\nAnkify lets you take notes in [Typst](https://typst.app) the way you normally\nwould and, with very little extra effort, generate an [Anki](https://apps.ankiweb.net)\nflashcard for each definition, theorem, lemma — whatever you like.\n\nThe trick: you write a small helper function that both typesets an item in your\nnotes *and* registers a flashcard for it. Define it once, and every definition\nor theorem in your document becomes a card.\n\n## How it works\n\nThe project has two parts:\n\n- **The `ankify` Typst package** — provides `configure()` and `note()`. Calling\n  `note()` records a flashcard as document metadata; it produces no visible\n  output, so you call it from inside your own helpers.\n- **The `ankify` CLI** — reads a `.typ` file, renders each note's fields, and\n  pushes them to Anki through the [AnkiConnect](https://foosoft.net/projects/anki-connect/)\n  add-on.\n\nWhen you run `ankify notes.typ`, the CLI:\n\n1. queries the document for note metadata and configuration;\n2. renders each note field — to an image, so math and formatting survive;\n3. sends `addNotes` / `updateNote` requests to AnkiConnect;\n4. records what it pushed in a cache (`.ankify/cache.json`), so the next run\n   only syncs notes that were added or changed.\n\n```mermaid\nflowchart TD\n    doc[\"Typst document:\u003cbr/\u003econfigure() + note() calls\"]\n\n    subgraph cli [\"ankify CLI\"]\n        direction TB\n        meta[\"Document metadata:\u003cbr/\u003econfig + one entry per note\"]\n        tmp[\"Temp render file:\u003cbr/\u003eone page per note field\"]\n        fields[\"Rendered fields:\u003cbr/\u003esvg, png, or plain text\"]\n        notes[\"Anki notes:\u003cbr/\u003eone per note() call\"]\n        reqs[\"AnkiConnect requests:\u003cbr/\u003eaddNotes / updateNote JSON\"]\n        meta --\u003e|generate| tmp\n        tmp --\u003e|typst compile| fields\n        fields --\u003e|assemble| notes\n        notes --\u003e|diff against cache| reqs\n    end\n\n    cache[(\"Sync cache:\u003cbr/\u003e.ankify/cache.json\")]\n    anki[\"Anki\"]\n\n    doc --\u003e|typst query| meta\n    cache -.-\u003e reqs\n    reqs --\u003e|send| anki\n    anki -.-\u003e|record sync| cache\n```\n\n## Requirements\n\n- **Anki**, running, with the **AnkiConnect** add-on installed.\n- The **Typst** CLI (`typst`) on your `PATH`.\n- A **Rust** toolchain, to build the CLI.\n\n## Installation\n\n### Typst package\n\nUntil `ankify` is published to the Typst Universe, install it locally so it can\nbe imported as `@preview/ankify:0.1.0`. Copy `packages/ankify-typst/` into your\nTypst package directory:\n\n```sh\n# macOS\nDEST=\"$HOME/Library/Application Support/typst/packages/preview/ankify/0.1.0\"\n# Linux:   ~/.local/share/typst/packages/preview/ankify/0.1.0\n# Windows: %APPDATA%\\typst\\packages\\preview\\ankify\\0.1.0\n\nmkdir -p \"$DEST\" \u0026\u0026 cp -r packages/ankify-typst/* \"$DEST\"\n```\n\n### CLI\n\n```sh\ncargo install --path packages/ankify-cli\n# or: cargo build --release  -\u003e  ./target/release/ankify\n```\n\n## Usage\n\n### A first card\n\n```typ\n#import \"@preview/ankify:0.1.0\": note, configure\n\n#configure(defaults: (deck: \"My Course\"))\n\n#note(\n  \"pythagoras\",\n  data: (Front: \"Pythagorean theorem?\", Back: [$a^2 + b^2 = c^2$]),\n)\n```\n\nStart Anki, then run:\n\n```sh\nankify notes.typ\n```\n\n### The real workflow: helpers\n\n`note()` shines when wrapped in a helper that also renders the item. Define a\n`definition` / `theorem` helper once, then every use of it becomes a card:\n\n```typ\n#import \"@preview/ankify:0.1.0\": note, configure\n\n#configure(defaults: (deck: \"Analysis\", tags: (\"analysis\",)))\n\n#let definition(label, term, body) = {\n  note(label, data: (Front: [Define: #term], Back: body))\n  block(inset: 8pt, stroke: 0.5pt)[*Definition (#term).* #body]\n}\n\n= Lecture 3: Continuity\n\n#definition(\"def-continuity\", \"continuity\")[\n  A function $f$ is continuous at $a$ if $lim_(x -\u003e a) f(x) = f(a)$.\n]\n```\n\nTake notes as usual; each `definition(...)` both typesets the definition and\nproduces a flashcard.\n\nA complete, runnable version of this pattern lives in [`demo/`](demo/) — a short\nlecture that compiles to a PDF *and* syncs to Anki.\n\n### `note()`\n\n| Parameter | Meaning | Default |\n|---|---|---|\n| label *(positional)* | Unique identifier; the sync key for the card. | — |\n| `data` | Dictionary of Anki field name → content/string. | — |\n| `deck` | Target Anki deck. | `\"Default\"` |\n| `model` | Anki note type. | `\"Basic\"` |\n| `tags` | Array of tag strings. | `()` |\n| `format` | How fields render: `\"png\"`, `\"svg\"`, or `\"plain\"`. | `\"svg\"` |\n| `other` | Extra metadata passed through to AnkiConnect. | `none` |\n| `render` | Function transforming each field before it is rendered (advanced). | identity |\n\nA field's value may be a string, Typst content, or a `(value, format)`\ndictionary to override the format per field.\n\n### `format`\n\n- `plain` — the field is sent to Anki as plain text.\n- `svg` *(the default)* — the field is rendered to SVG and inlined into the\n  card. Crisp, scalable, and **theme-aware** (see [Dark mode](#dark-mode) below).\n- `png` — the field is rendered to a raster image and attached as media. Use it\n  for genuinely raster content.\n\nUse `plain` for short text answers and `svg`/`png` for anything with math or\nformatting.\n\n### Dark mode\n\n`svg` cards adapt to your Anki theme automatically. They render with a\ntransparent background and a `currentColor` foreground, so they take on the\ncard's own colours and follow Anki's light/dark mode with no extra setup.\n\n`png` cards are not theme-aware — a raster image can't recolour itself — so\nprefer `svg` if you review in dark mode.\n\n### `configure()`\n\nOptional — call it once near the top of the document.\n\n```typ\n#configure(\n  ankiconnect-url: \"http://127.0.0.1:8765\",\n  verbose: true,\n  scale: 1.5,\n  defaults: (deck: \"My Course\", model: \"Basic\", tags: (\"lecture\",), format: \"svg\"),\n)\n```\n\n`scale` enlarges every rendered card image (default `1.5`) — raise it if your\ncards look too small, lower it towards `1.0` if they look too big. If you omit\n`configure()` entirely, built-in defaults are used.\n\n### Re-syncing\n\nRun `ankify notes.typ` again whenever you add or edit notes. Thanks to the\ncache, unchanged notes are skipped, edited notes are updated, and new notes are\nadded. Renaming a note's label is recognised as a rename when its content is\notherwise unchanged, so the existing card is updated in place rather than\nduplicated; a label that disappears from the document is reported as a warning,\nits Anki note left untouched. (Changing a note's *deck* is not migrated —\nAnkiConnect cannot move an existing card between decks.)\n\n## CLI options\n\n```\nankify \u003cFILE\u003e [options]\n\n  -v, --verbose                Verbose output\n      --cache-file \u003cPATH\u003e      Custom cache file location\n      --ankiconnect-url \u003cURL\u003e  AnkiConnect URL (default: http://127.0.0.1:8765)\n      --root \u003cDIR\u003e             Typst project root\n      --font-path \u003cPATH\u003e       Additional font path (repeatable)\n```\n\n## Project layout\n\n| Path | What |\n|---|---|\n| `packages/ankify-typst/` | The `ankify` Typst package (`note`, `configure`). |\n| `packages/ankify-cli/` | The `ankify` CLI and library crate. |\n| `demo/` | A runnable example: a lecture that becomes an Anki deck. |\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n\n## Trademarks\n\nAnki is a trademark of Ankitects Pty Ltd. Ankify is an independent project,\nnot affiliated with, endorsed by, or sponsored by Ankitects Pty Ltd.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnvlang%2Fankify","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnvlang%2Fankify","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnvlang%2Fankify/lists"}