Skip to content

Commit 5e569e6

Browse files
feat: Add anki skill
1 parent 7550ce2 commit 5e569e6

2 files changed

Lines changed: 201 additions & 0 deletions

File tree

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# Anki Python API Reference
2+
3+
## Database schema (SQLite — access via `col.db`)
4+
5+
### `cards` table
6+
7+
| Column | Meaning |
8+
| ------ | ------------------------------------------ |
9+
| id | Card ID (epoch ms timestamp) |
10+
| nid | Note ID (foreign key) |
11+
| did | Deck ID |
12+
| ord | Card ordinal (template index) |
13+
| type | 0=new, 1=learning, 2=review, 3=relearning |
14+
| queue | See "Card states" below |
15+
| due | Due value (queue-dependent interpretation) |
16+
| ivl | Interval in days (review cards) |
17+
| factor | Ease factor × 1000 (e.g. 2500 = 250%) |
18+
| reps | Number of reviews |
19+
| lapses | Number of lapses |
20+
21+
### `notes` table
22+
23+
| Column | Meaning |
24+
| ------ | ------------------------------------------------ |
25+
| id | Note ID |
26+
| mid | Model (note type) ID |
27+
| flds | Field values separated by `\x1f` |
28+
| tags | Space-separated tags with leading/trailing space |
29+
30+
### Card states (`cards.queue`)
31+
32+
| Value | State |
33+
| ----- | ----------------------------------------- |
34+
| -3 | Buried manually (pre-scheduler) |
35+
| -2 | Suspended |
36+
| -1 | Buried by sibling |
37+
| 0 | New |
38+
| 1 | Learning |
39+
| 2 | Review |
40+
| 3 | Day-learn (learning with day-based steps) |
41+
| 4 | Preview |
42+
43+
## Search syntax (pass to `col.find_notes()` or `col.find_cards()`)
44+
45+
```python
46+
col.find_notes("deck:English") # cards in a deck
47+
col.find_notes("deck:English is:new") # new cards
48+
col.find_notes("deck:English is:due") # due cards
49+
col.find_notes("tag:vocabulary") # by tag
50+
col.find_notes('"some text"') # full-text search
51+
col.find_notes("added:7") # added in last 7 days
52+
col.find_notes("rated:1:hard") # rated Hard in last 1 day
53+
col.find_notes("prop:ivl>=21") # interval ≥ 21 days
54+
col.find_notes("prop:ease<2") # ease < 200%
55+
col.find_notes("-is:suspended -is:buried") # exclude suspended/buried
56+
```
57+
58+
Combine freely: `"deck:English is:review -is:due"`
59+
60+
## Practical query recipes
61+
62+
### Cards due today
63+
64+
```python
65+
due = col.db.scalar(
66+
"SELECT count() FROM cards WHERE due <= ? AND queue > 0",
67+
col.sched.today
68+
)
69+
```
70+
71+
### Card state breakdown
72+
73+
```python
74+
from collections import Counter
75+
states = Counter()
76+
for c in col.find_cards(""):
77+
card = col.get_card(c)
78+
# card.type: 0=new, 1=learning, 2=review, 3=relearning
79+
# card.queue: see table above
80+
```
81+
82+
### Ease & interval stats
83+
84+
```python
85+
avg_ease = col.db.scalar("SELECT avg(factor)/10.0 FROM cards WHERE factor > 0")
86+
avg_ivl = col.db.scalar("SELECT avg(ivl) FROM cards WHERE ivl > 0 AND type = 2")
87+
max_ivl = col.db.scalar("SELECT max(ivl) FROM cards")
88+
```
89+
90+
### Cards reviewed today
91+
92+
```python
93+
import time
94+
day_start_ms = int(time.time() - time.time() % 86400) * 1000
95+
reviews_today = col.db.scalar(
96+
"SELECT count() FROM revlog WHERE id > ?", day_start_ms
97+
)
98+
```
99+
100+
### Cards added recently
101+
102+
```python
103+
import time
104+
thirty_days_ago_ms = (int(time.time()) - 30 * 86400) * 1000
105+
recent = col.db.scalar("SELECT count() FROM cards WHERE id > ?", thirty_days_ago_ms)
106+
```
107+
108+
### Iterate notes with fields
109+
110+
```python
111+
for nid in col.find_notes("deck:English"):
112+
note = col.get_note(nid)
113+
fields = note.fields # list of strings
114+
tags = note.tags # list of strings
115+
ntype = note.note_type()["name"]
116+
print(f"[{ntype}] {fields}")
117+
```
118+
119+
## Writing (requires Anki to be closed)
120+
121+
```python
122+
col = Collection(path) # Anki must NOT be running
123+
124+
# Add a basic note
125+
note = col.new_note(col.models.by_name("Basic"))
126+
note.fields[0] = "What is X?" # Front
127+
note.fields[1] = "Y" # Back
128+
note.tags = ["vocabulary"]
129+
col.add_note(note, deck_id=col.decks.id("English"))
130+
131+
# Add a cloze note
132+
note = col.new_note(col.models.by_name("Cloze"))
133+
note.fields[0] = "She {{c1::went}} to the store."
134+
col.add_note(note, deck_id=col.decks.id("English"))
135+
136+
col.close()
137+
```
138+
139+
## Important notes
140+
141+
- `col.close()` must be called when done (or use `try/finally`).
142+
- Card/note IDs are epoch-millisecond timestamps (e.g. `1700000000000`).
143+
- The `anki` package bundles a Rust backend (`anki._backend`) — no extra install needed.
144+
- Deck IDs are integers; use `col.decks.id("DeckName")` to resolve by name.
145+
- For read-only inspection while Anki runs, always copy the `.anki2` file first.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
---
2+
name: anki
3+
description: Inspect, query, and manipulate a local Anki flashcard collection using the Anki Python library. Use when the user asks about their Anki decks, cards, notes, review stats, or wants to programmatically add/update/delete flashcards.
4+
---
5+
6+
# Anki Python Library
7+
8+
## Running scripts
9+
10+
Always use `uv` to run scripts — it handles the `anki` dependency automatically:
11+
12+
```sh
13+
uv run --with anki python -c ''
14+
15+
uv run --with anki ./your_script.py
16+
```
17+
18+
## Opening the collection
19+
20+
**macOS path:**
21+
```
22+
~/Library/Application Support/Anki2/User 1/collection.anki2
23+
```
24+
25+
```python
26+
from anki.collection import Collection
27+
from pathlib import Path
28+
29+
path = Path.home() / "Library/Application Support/Anki2/User 1/collection.anki2"
30+
31+
col = Collection(path)
32+
```
33+
34+
## Key API patterns
35+
36+
```python
37+
# Decks
38+
for d in col.decks.all_names_and_ids():
39+
count = col.decks.card_count(d.id, include_subdecks=True)
40+
41+
# Counts
42+
col.card_count() # total cards
43+
col.note_count() # total notes
44+
45+
# Search notes (Anki query syntax)
46+
note_ids = col.find_notes("deck:English is:new")
47+
note = col.get_note(note_id)
48+
print(note.fields) # list of field values
49+
print(note.note_type()["name"]) # e.g. "Basic", "Cloze"
50+
51+
# Direct SQL via col.db
52+
col.db.scalar("SELECT count() FROM cards WHERE queue = 0") # new cards
53+
col.db.all("SELECT id, flds FROM notes LIMIT 10")
54+
```
55+
56+
For full API reference — tables, card states, search syntax, and more examples — see [REFERENCE.md](REFERENCE.md).

0 commit comments

Comments
 (0)