用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/Aleks-Ya/yaal_examples --skill add-english-word-to-anki命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| name | add-english-word-to-anki |
| description | Quickly add a new English word to Anki as a flashcard. |
This skill creates a new English word note in Anki, including filling all fields for the new note.
Read shared/references/skill-conventions.md first — it holds the conventions shared with the
populate-existing-english-anki-notes skill: abbreviations (POS/flashcard/NID), the target note
type (En-word-or-sentence) and deck (En::English), the --dry-run and --no-pictures
semantics, the single-write rule, and the output-report style. Only the parts specific to this
skill are spelled out below.
Per skill-conventions.md, all deterministic work is delegated to the scripts under
shared/scripts/ — run them; never read their source. Load each reference doc lazily, only when
its step is reached:
shared/references/field-plan.md — the index of how each field's value is derived; read it
once when the first note's fields are being prepared (step 2.1). Rows whose rule needs more than a
line link to a per-field reference file (english-article-prefix.md, definition-rules.md,
example-real-life-format.md, audio-procedure.md, picture-procedure.md) — open one lazily,
when that field is actually being generated.shared/references/backfill-routine.md — the shared routine for filling a note's empty
Claude-owned fields (single-write rule, media slugs, batch TTS, absence tags); read together
with field-plan.md.shared/assets/en-pos-anki-tags.md — POS tag vocabulary; read at step 2.1.2.shared/scripts/list_source_tags.py — fetches the current source::* tag vocabulary live from
Anki (so newly-added source tags are picked up without editing any file); run at step 2.1.3.shared/assets/new_words.md — example input file, for humans; don't load it.Input data is provided as a path to a single plain-text/Markdown file; if no path is given,
default to new_anki_words.md in the project root. H1 headers (# Source)
delimit sources — every sentence under a header belongs to that source until the next header:
# The Guard 2011 -> source "The Guard 2011". The special header # NO_SOURCE marks a source-less section: its sentences are still processed, but the source is null (not mentioned in any field or tag). Empty sections (a header with no sentences, e.g. # Python) are allowed. shared/scripts/parse_input.py emits this source per entry (null for a # NO_SOURCE section) — don't hand-derive it.Just _pin_ a medal to me body. -> word "pin". Only underscores at a word boundary delimit the marker, so an identifier inside a sentence is literal text (…the message_delta event are _cumulative_. -> word "cumulative") while _message_delta_ marks the whole word. A non-blank line under a header with no _..._ marker at all is not a sentence to import — parse_input.py silently skips it (no entry, no error) and it stays in the file untouched by step 4's clearing. A line with more than one marker, or a sentence before the first header, is a validation error (parse_input.py exits non-zero).If the arguments include --dry-run (in addition to the file path, in either order), run in dry-run mode. Otherwise run in live mode (default): e.g. /add-english-word-to-anki --dry-run '/home/aleks/tmp/new_anki_words.md' vs. /add-english-word-to-anki '/path/to/file.md'. If no file path is given at all (only --dry-run/--no-pictures, or no arguments), use the default input file new_anki_words.md in the project root — e.g. /add-english-word-to-anki --dry-run runs a dry-run over that default file.
--dry-run and --no-pictures follow the shared semantics in skill-conventions.md. Skill-specific
details:
--no-pictures has no effect on this: a Picture skip is a normal successful outcome, so
a --no-pictures note still counts as cleanly imported. Note a Picture skip in the row's Outcome,
e.g. "Created new note (Picture skipped)".find_duplicate.py duplicate check and
the search_images.py lookup plus candidate download/visual check and the search_icons.py
lookup for Picture (unless
--no-pictures is also given); the note-mutating calls skipped are addNote/addNotes,
updateNoteFields, addTags, storeMediaFile. What to report per word: for a new note, the full
planned field values (per field-plan.md), tags, and target deck; for a duplicate, the existing
note id and the planned Example-real-life update (or "sentence already present, no change" if it's
already there) plus any fields that would be backfilled.Follow the output-report style in skill-conventions.md (one accumulated Markdown table, no per-word narration, one row per word/sentence pair in input order, then an aggregate summary line). This skill's aggregate line reads e.g. "N new notes, M duplicates updated, K skipped". Also append an inbox-cleared note (step 4): in live mode "; cleared 5 imported sentence(s)", or in dry-run mode "; would clear 5 imported sentence(s)" (omit if zero).
Columns:
addNote's return value) for a new note in live mode; the literal (new) for a new note in dry-run mode (no id exists yet since addNote is skipped).source (its section's # header text, from parse_input.py's output), e.g. "The Guard 2011"; blank for a # NO_SOURCE section (source null).parse_input.py's output), e.g. "beggars".en::parts::* tag.find_duplicate.py returns more than one id.Example:
| NID | Source | Word | Base form | POS | Outcome |
|----------------|----------------|---------|---------------|------|-------------------------------------|
| 1579307261208 | The Guard 2011 | beggars | a beggar | noun | Duplicate — appended new sentence |
| (new) | The Guard 2011 | pin | to pin | verb | Created new note (Picture found) |
| 1482172556889 | The Guard 2011 | batting | to bat around | verb | Duplicate — nothing to update |
python3 "shared/scripts/parse_input.py" <input file> to validate and parse the lines. Lines with no _..._ marker at all are not errors — the script silently skips them (no entry) and leaves them in the file. If it exits non-zero, stop and show the user the reported line errors (e.g. line 3: multiple words marked with _..._, or a sentence before the first # source header) rather than guessing at a fix. On success it prints a JSON object {entries}: entries is a JSON array of {source, line, word, sentence} objects (underscores already stripped from sentence, source = the section's # header text, or null for a # NO_SOURCE section) — use that as the list of word/sentence pairs. Keep each entry's line — step 4 needs it to clear the imported sentences from the file.shared/references/field-plan.md and shared/references/backfill-routine.md now, if not already loaded).
shared/assets/en-pos-anki-tags.md (read it
now, if not already loaded) — follow its "Choosing the right tag" guidance: use the most
specific applicable sub-tag(s) (e.g. en::parts::noun::countable, not a bare
en::parts::noun), applying every sub-tag that fits and omitting the bare parent when a
sub-tag applies.source from step 1). Get the current source-tag vocabulary by running python3 "shared/scripts/list_source_tags.py" once per run — it queries Anki live (getTags, filtered to the source:: prefix) and prints {"source_tags": [...]}; cache that list and reuse it for the remaining words rather than re-running it per word. Match the entry's source against that live list, keeping empty if not found. If the entry's source is null (a # NO_SOURCE section), apply no source tag at all. Otherwise match loosely (e.g. ignoring a trailing year in the source, such as "The Guard 2011" matching ) — this tag lookup is independent of the literal source text shown in fields, which always keeps the full header-derived source (year included).source::movie::the-guarda/an for singular countable nouns (e.g. "a bucket", "an idea"), to for verbs (e.g. "to conquer"). Other POS get no prefix.en::unit::idiom — figurative meaning, can't be inferred from the individual words (e.g. "kick the bucket" = die)en::unit::phrase — literal multi-word unit, meaning follows from the words (e.g. "casting off" = literally departing by boat)en::unit::collocation — words that simply commonly co-occur (e.g. "heavy rain", "make a decision")python3 "shared/scripts/find_duplicate.py" "<base form from step 2.1.4>" "<POS tag from step 2.1.2>". It queries AnkiConnect directly (a wildcard findNotes scoped to the note type + notesInfo) and already handles the a/an/to normalization and en::parts::* tag-family matching — don't re-derive that by eye, and don't run findNotes/notesInfo through the MCP tools for this. It prints {duplicates, candidates_checked, note}. Zero ids in duplicates means no duplicate (continue to step 2.3); more than one id is unusual — record a report-table row (see ## Output report) flagging the ambiguous match, then move on to the next word without creating or updating a note, instead of picking one. If exactly one id is found, note holds everything needed — {id, tags, fields} (the small text fields, incl. English and Example-real-life) and status (a ready-made note_status.py result) — so no follow-up notesInfo is needed. Then, collecting all field changes from the sub-steps below into one updateNoteFields call (the routine's single-write rule; skip it in dry-run mode):
note.fields.English. (Whether English-audio-generated is stale, i.e. currently non-empty, follows from note.status: it is non-empty iff it is not listed in status.audio_to_generate.) Note the edit in the row's Outcome (e.g. "added article to English, regenerated audio").Example-real-life value (from note.fields, or null if empty), the surface-form word exactly as it appears in the sentence (from parse_input.py's output — not the base form), the plain sentence, and the entry's source (pass it through as-is; a null source is fine — the script then omits the (Source) suffix) into shared/scripts/build_example_html.py (JSON in on stdin, JSON out). It returns {html, changed, already_present}, handling the legacy-plain-text wrap, dedupe-by-sentence, and <b> bolding — don't hand-edit the field. If changed is true, include its html in the single write; if already_present, don't touch the field.note.status as the routine's step B1 output (don't call note_status.py again for the worklist). It fills only the empty fields (respecting the absence tags from step 2.4), synthesizes the needed audio in one batch, and tags genuine absences. Skip every mutating call in dry-run mode and report the planned fills instead.## Output report) — Outcome summarizes what changed (article added to English, new sentence appended and/or which fields were backfilled, or "nothing to update" if the sentence already existed, English already had its article, and no fields were empty) — then move to the next input object without creating a new note.English to the base form from step 2.1.4 (with its a/an/to prefix). Set Example-real-life by calling shared/scripts/build_example_html.py the same way as step 2.2.2 but with existing: null, using its html output. Fill every remaining Claude-owned field — and its audio — by running the shared backfill routine (backfill-routine.md): on a brand-new note all those fields are empty, so the routine fills them all per field-plan.md, synthesizes the audio in one batch, and applies any absence tags (step 2.4); the values land in the single final addNote call (skip the storeMediaFile/mutating calls in dry-run mode).~api::absent::synonym1, ~api::absent::synonyms, ~api::absent::antonym1, ~api::absent::antonyms respectively; if neither a photo nor an icon was found for Picture (see field-plan.md), leave it empty and add ~api::absent::picture.En::English (skip this call if running in dry-run mode), then record a report-table row for this word (see ## Output report).## Mode). Collect the line (from step 1's entries) of every cleanly-imported entry — every entry whose Outcome was a new note created, or a duplicate created/updated/"nothing to update" — i.e. all entries except any with an ambiguous-match skip ("Ambiguous match (N candidates) — skipped"; the only per-word skip, whose sentence is left in place so nothing is lost). Then:
python3 "shared/scripts/parse_input.py" --clear <input file> with {"remove_lines": [<the collected line numbers>]} on stdin (e.g. echo '{"remove_lines": [2, 5, 6]}' | python3 "shared/scripts/parse_input.py" --clear "<path>"). It deletes exactly those lines and keeps every other line — all headers included, even ones whose section is now empty — verbatim, and prints {"removed": N}. The <input file> is the original path argument (a single file). Don't clear anything if the list is empty.## Output report.