Skip to main content
This page takes you from nothing to narrating your own code and your own diffs, first with the CLI and then from code. The @usenarrator/* packages are published as ESM JavaScript with type declarations, so they work in Node and Bun without a bundler. The CLI needs Node 22.17 or newer because it uses Node’s built-in glob support, which became stable in that release. The examples are ES modules. In a new npm project, run npm pkg set type=module first, because npm init marks projects as CommonJS. Otherwise name the files .mts (or .mjs without types).
1

Try the CLI

Install the CLI, or run it once with npx:
Give it files, directories or globs. It prints the English for each TypeScript or JavaScript file it finds. Inside directories and globs it skips anything your .gitignore excludes (exceptions such as !keep.ts are respected), plus files people don’t write by hand: dependencies, build output such as dist and .next, vendored code such as .yarn, minified files, and anything .gitattributes marks linguist-generated or linguist-vendored. A file you name directly is always narrated. With no arguments it reads code from standard input.
The last command prints If apple is in basket, bite the apple.By default, the CLI reads the package.json files in your repository and loads only the plugins for libraries you depend on, so a Hono app gets the Hono plugin without any configuration. See Dependency detection.The CLI reference lists every plugin name and the packages that turn it on.
2

Narrate a diff from the terminal

narrator diff narrates a git change set. Give it a range, or a single revision to compare against your working tree.
Each changed declaration is printed as a unit, with removed sentences marked - and added sentences marked +. Generated, vendored and minified files are skipped the same way as above and listed with one line each, such as .yarn/releases/yarn.cjs [deleted, skipped: vendored]. A file that can’t be read, for example because of a syntax error, gets a line starting with ! and the rest of the diff carries on. The next step shows the same output from code. Use --json for structured output, or --html for a standalone side-by-side page.
3

Narrate a file from code

Install the packages this page imports, plus any plugins you want:
@usenarrator/node already depends on the other three, but you import from them directly below, so list them yourself. Strict package managers such as pnpm only let you import what you declare. See What to install for the full picture.createNodeNarrator sets up the TypeScript language with the native parser, the English locale, and the dependency manifests of the git repository around your working directory.
node-narrator.ts
Running it prints:
Output
narrate returns an object whose lines are Line objects, not a string. Each line knows its indent depth, its kind (heading, statement, bullet and so on), and the source offsets it came from. renderText is the simplest way to turn them into text. In a browser, or when you want to choose every piece yourself, use createNarrator from @usenarrator/core instead. See Embedding in your app.
4

Narrate a diff from code

Give narrator.diffFiles the old and new source of each changed file. Narrator narrates both sides, splits them into units (one per top-level declaration), and lines up the English so you see what changed in meaning rather than in characters. renderDiffText prints the result the way the CLI does.
narrate-diff.ts
Output
The first condition was edited in place, so it shows as a removed line and an added line. The return grew into two sentences, which is easier to review than the code change that caused it. To render diffs as HTML instead, use narrator.diff, described in Units and diffs.To build the file list from a git range, the way the CLI does, use changedFilesFromGit from @usenarrator/node. It also marks generated and vendored files so they are skipped without being read. See the Node reference.

Bad code and failing plugins

Narration never stops for one bad file. narrate returns a problems list next to the lines, and each entry in a diff has one too. A syntax error is reported with its line number and Narrator keeps whatever it could read. A plugin rule that throws is reported with the plugin’s name and the plain reading is used instead. If a file can’t be narrated at all, it has no lines and a single problem explaining why, while every other file in the call is narrated as usual. See NarrationProblem.

What to install

How it works

The path from source code to sentences, and the pieces you can swap.

Embedding in your app

Use the native parser on the server or the WASM parser in a browser.

Plugins

Add React, Next.js, Drizzle, Stripe and more so library calls read naturally.

Write a plugin

Teach Narrator what your own helpers mean.