Reference for the word-list and definitions pipeline. All commands run from the
repo root via yarn <script> or tsx scripts/....
ops/scripts/words/{locale}/{level}_words.txt ──► crawler ──► ops/crawl-output/{locale}/{letter}.jsonl ──► merge ──► public/definitions/{locale}/{letter}_definitions.json
│
ops/scripts/words/{locale}/{level}_words.txt ◄────────────────────────────────────────────────────────── words:transform ──► public/words/{locale}/
- Crawlers never write directly to
public/definitions/— they append to JSONL staging files, which are then merged. - Word lists (
*_words.txt) are the source of truth for which words get crawled and which game level they belong to.
Runs the full pipeline: ES (crawlRAE.ts) then EN (crawlDictionary.ts), each
through beginner → intermediate → advanced, merging after every level.
Resumable — re-running skips words already crawled or already in
public/definitions/. No flags; edit LOCALES/LEVELS in
scripts/crawlers/crawlAll.ts to narrow scope.
If the RAE API daily quota is exhausted mid-run, ES stops for the day and the script moves on to EN.
Fetches ES definitions from rae-api.com (unofficial RAE
API). Requires RAE_API_KEY in .env.local.
yarn tsx scripts/crawlers/crawlRAE.ts ops/scripts/words/es/beginner_words.txt \
--level beginner --skip-existing --quiet-skip \
--output-dir ops/crawl-output/es --not-found-file ops/crawl-output/es/not-found.txt| Option | Description |
|---|---|
--output-dir <dir> |
JSONL output folder (default: ops/crawl-output/es) |
--start <word> |
Resume from this word |
--letter <l> |
Only process words starting with this letter |
--level <level> |
Tag entries with beginner|intermediate|advanced |
--skip-existing |
Skip words already in public/definitions/es/ |
--force-update |
Fetch even if the word already has definitions |
--delay <min>-<max> |
Random delay in ms between requests (default 7000-15000) |
--quiet-skip |
Suppress per-word skip messages, show a total at the end |
--not-found-file <f> |
Track words with no RAE entry (default: ops/crawl-output/es/not-found.txt) |
Fetches EN definitions from api.dictionaryapi.dev.
Same options as crawlRAE.ts (output dir defaults to ops/crawl-output/en).
Merges JSONL crawl output into public/definitions/{locale}/{letter}_definitions.json.
Files are read oldest-first (later entries win on conflict). Merges definitions
by their number field and sets level on the word entry if not already present.
yarn crawl:merge ops/crawl-output/es/*.jsonl --dry-run
yarn crawl:merge ops/crawl-output/en/j.jsonl --locale en --letter j| Option | Description |
|---|---|
--dry-run |
Print what would change, write nothing |
--locale <es|en> |
Only process entries for this locale |
--letter <l> |
Only process entries for words starting with this letter |
Validates EN words against api.dictionaryapi.dev. Words that return HTTP 200
are written to the output file (resumable).
Removes invalid entries from ops/scripts/words/{locale}/{level}_words.txt
in-place. A valid word contains only letters (accented/ñ allowed for ES).
Also dedupes and lowercases. No args = all locales + levels.
Re-classifies words across all 3 levels using a frequency corpus
(ops/scripts/frequency/wordfreq_{locale}.txt — rank 1 = most common).
| Option | Description |
|---|---|
--beginner-limit <n> |
Rank cutoff for beginner (default 3000) |
--intermediate-limit <n> |
Rank cutoff for intermediate (default 10000) |
--move-unknown |
Move words not found in the corpus to advanced (default: leave at current level) |
--dry-run |
Print counts without writing files |
Appends words from source.txt that aren't already in target.txt.
Reads words from a file (or stdin). For each word already present in
advanced_words.txt but missing from <targetLevel>_words.txt, appends it to
the target level list.
Reads ops/scripts/words/{locale}/{level}_words.txt, sorts + dedupes, writes
100k-word chunked JSON arrays to public/words/{locale}/, and updates
config/LevelConfig.ts. Run this after any change to the *_words.txt files.
Learns new words from book files. Runs, in order:
extractFromBook.ts— tokenizes the book(s), filters out anything already inpublic/definitions/,not-found.txt, any*_words.txt, or a previousdiscovered_words.txtrun. New words are appended toops/crawl-output/{locale}/discovered_words.txt(never touches the existing word lists or definitions).mergeWords.ts— appendsdiscovered_words.txtintoadvanced_words.txt.sortByFrequency.ts— reclassifies all levels, so common new words land inbeginner/intermediate.
Single file: pass the path as the second argument.
yarn words:from-book es path/to/book.txt
Batch mode: drop any number of .txt, .pdf, or .epub files into
ops/books/{locale}/ and run without a second argument — all files are
processed in one pass.
# put your books in ops/books/es/ or ops/books/en/
yarn words:from-book es
yarn words:from-book en
The ops/books/ directories are gitignored (copyrighted material stays local).
Run yarn words:transform afterwards to regenerate public/words/ chunks, then
yarn crawl:all to fetch definitions for the newly added words.