|
| 1 | +# TAGLINE |
| 2 | + |
| 3 | +CLI and pipeline framework for loading flash cards into Anki |
| 4 | + |
| 5 | +# TLDR |
| 6 | + |
| 7 | +**Import a CSV** of cards into a deck (Basic note type by default) |
| 8 | + |
| 9 | +```hanky pipe [words.csv] --into [english::vocab]``` |
| 10 | + |
| 11 | +**Import every matching file** under a directory into nested decks |
| 12 | + |
| 13 | +```hanky pipe-dir [french/] ["*.csv"] -r``` |
| 14 | + |
| 15 | +**Override the Anki note type** / model name |
| 16 | + |
| 17 | +```hanky pipe [cards.csv] -m [Basic] --into [deck]``` |
| 18 | + |
| 19 | +**Dry-run** processors without writing the collection |
| 20 | + |
| 21 | +```hanky pipe [words.csv] --dry-run``` |
| 22 | + |
| 23 | +**Verbose report** of each processed card |
| 24 | + |
| 25 | +```hanky pipe [words.csv] -v --into [deck]``` |
| 26 | + |
| 27 | +**Run a custom pipeline script** the same way as the hanky binary |
| 28 | + |
| 29 | +```python3 [my_script.py] pipe [words.csv] --into [english::vocab]``` |
| 30 | + |
| 31 | +# SYNOPSIS |
| 32 | + |
| 33 | +**hanky** **pipe** [**-m** _MODEL_] [**--into** _DECK_] [**--fail-fast**] [**--dry-run**] [**-v**] [**--args** _K=V_ ...] _file_ |
| 34 | + |
| 35 | +**hanky** **pipe-dir** [**-m** _MODEL_] [**-r**] [**--fail-fast**] [**--dry-run**] [**-v**] [**--args** _K=V_ ...] _dir_ _pattern_ |
| 36 | + |
| 37 | +# PARAMETERS |
| 38 | + |
| 39 | +**pipe** _file_ |
| 40 | +> Load cards from a single file (**.csv**, **.json**, or a registered extension) and add them to Anki. |
| 41 | +
|
| 42 | +**pipe-dir** _dir_ _pattern_ |
| 43 | +> Load many files under _dir_ matching the glob _pattern_. Deck names are derived from relative paths (e.g. **french/animals.csv** → **french::animals**). |
| 44 | +
|
| 45 | +**-m** _MODEL_, **--model** _MODEL_ |
| 46 | +> Override the Anki model (note type) used for new cards. Standalone **hanky** defaults to **Basic**; pipeline scripts set the model in **HankyPipeline(...)**. |
| 47 | +
|
| 48 | +**--into** _DECK_ |
| 49 | +> Destination deck for **pipe**. Defaults to the input filename without extension. |
| 50 | +
|
| 51 | +**-r**, **--recursive** |
| 52 | +> For **pipe-dir**, also descend into subdirectories. |
| 53 | +
|
| 54 | +**--fail-fast** |
| 55 | +> Stop and raise on the first card that cannot be added instead of skipping and reporting at the end. |
| 56 | +
|
| 57 | +**--dry-run** |
| 58 | +> Run card processors and report results without modifying the Anki collection (no decks, media, cards, or backup). |
| 59 | +
|
| 60 | +**-v**, **--verbose** |
| 61 | +> Print each card dict after processors run, plus errors in the final report. |
| 62 | +
|
| 63 | +**--args** _K=V_ |
| 64 | +> Key=value pairs forwarded to custom card processors (pipeline scripts only). May be repeated. |
| 65 | +
|
| 66 | +# DESCRIPTION |
| 67 | + |
| 68 | +**hanky** is a Python CLI and library that reads flash cards from files (or in-script sources), optionally transforms them through user-defined **card processors**, and writes notes into a local **Anki** collection. It is aimed at ETL-style workflows: scrape or OCR material, enrich fields (translations, examples, TTS audio), then bulk-load into decks. |
| 69 | + |
| 70 | +Built-in **pipe** / **pipe-dir** commands work with CSV and JSON out of the box when column or key names match the target note type fields. For custom logic, install the package, write a small script that registers **@hanky.card_processor** functions on a **HankyPipeline**, and call **hanky.run()** so the same CLI surface is available as **python3 my_script.py pipe ...**. Processors may attach audio media via **CardMedia** (**.mp3**, **.oga**, **.opus**, **.wav**, **.weba**, **.aac**). Custom file loaders can be registered for formats such as Excel. |
| 71 | + |
| 72 | +hanky only **adds** cards, media, and decks. Note types (models) must already exist in the collection; create them in the Anki UI first. Requires Python **3.11+** and a local Anki collection database. Not affiliated with AnkiWeb or the Anki project. |
| 73 | + |
| 74 | +# CONFIGURATION |
| 75 | + |
| 76 | +**~/.config/hanky/hanky.toml** |
| 77 | +> Optional TOML config. Needed mainly if you do not use the default Anki profile (**User 1**) or you are on Windows. |
| 78 | +
|
| 79 | +**ANKI_DB_PATH** |
| 80 | +> Path to the Anki collection SQLite file (**collection.anki2**). Defaults to the **User 1** profile under the platform Anki2 data directory. |
| 81 | +
|
| 82 | +**DO_SAFETY_CHECK** |
| 83 | +> When true (default), refuse to open the collection if another process (e.g. Anki) appears to hold it. |
| 84 | +
|
| 85 | +**ALLOW_DUPLICATES** |
| 86 | +> When false (default), skip cards that duplicate existing notes. |
| 87 | +
|
| 88 | +**BACKUP_FOLDER** |
| 89 | +> Directory for collection backups before writes. Default: **~/.local/share/hanky/backups**. |
| 90 | +
|
| 91 | +A **Config** object passed to **HankyPipeline** in a script overrides file settings. |
| 92 | + |
| 93 | +# CAVEATS |
| 94 | + |
| 95 | +Close Anki (or disable the safety check only if you accept corruption risk) before writing the collection. Note types must pre-exist; hanky does not create models. Duplicate detection and field names depend on your note type. Windows and non-default profiles almost always need **ANKI_DB_PATH**. Custom processors that call external APIs (translation, TTS, vision) need their own credentials and dependencies. |
| 96 | + |
| 97 | +# HISTORY |
| 98 | + |
| 99 | +Written by **HBA** as an open-source Python package on PyPI under the MIT license. Provides both a **hanky** console script and a library API for programmable Anki import pipelines. |
| 100 | + |
| 101 | +# SEE ALSO |
| 102 | + |
| 103 | +[anki](/man/anki)(1), [python](/man/python)(1), [pip](/man/pip)(1) |
| 104 | + |
| 105 | +# RESOURCES |
| 106 | + |
| 107 | +```[Source code](https://github.com/Haeata-Ash/hanky)``` |
| 108 | + |
| 109 | +<!-- verified: 2026-07-23 --> |
0 commit comments