:::meta
description: The tern command line (new, check, parse, build, mv, corpus), the language server, its tern/outline notification and the language module editors can embed, editor setup for Neovim, VS Code and Helix, a skill for coding agents, and the note-detection rule editors share.
:::
::site-nav
# Command line and editors {#top}
::toc
Tern has one command-line program, `tern-cli.js`. It writes new notes, checks them, prints their syntax tree, pre-renders them into static pages, moves them with their links, and runs a language server for your editor. It runs under Node and is never loaded by a note: a note needs only `tern.js`.
Every command runs the same engine the browser runs, on the same note, with the same vocabulary. So `tern check` reports the same diagnostics as the browser's panel, for everything the browser does not find only at run time (an add-on that fails, a script that calls `document.write`), and your editor shows them again as you type.
The examples on this page are real output, run against the engine in this repository. Paths in them are relative to the folder the command ran in. A few long absolute paths are shortened with `…`.
## Installing and running {#install}
You need **Node 22 or later** (`package.json` asks for `node >= 22`). The command line has no dependencies of its own: a checkout of the repository is all it needs.
| How | Command | Notes |
|---|---|---|
| Directly | `node path/to/tern/tern-cli.js check notes/` | Works from anywhere. |
| npx, inside the checkout | `npx --no -- tern check notes/` | Uses the `bin` entry of the checkout's `package.json`. |
| A `tern` command | `npm link` in the checkout | Puts `tern` on your `PATH`, linked to the checkout. |
| A symbolic link | `ln -s ~/src/tern/tern-cli.js ~/.local/bin/tern` | `tern-cli.js` is executable and starts with `#!/usr/bin/env node`. |
The rest of this page writes `tern` for whichever you chose.
:::warning
Run `npx` only inside the checkout. Elsewhere, `npx tern` looks for a package called `tern` on the npm registry, which is an unrelated project. The `--no` flag makes `npx` refuse to install anything, so a mistake fails instead of downloading it. Also put `--` before the command: `npx --no tern --version` prints npm's own version.
:::
`tern help` (or `tern` alone, `tern --help`, `tern -h`) prints the usage to stdout and exits 0. `tern --version` (or `-v`, `version`) prints the engine's version:
```sh
$ tern --version
0.6.0
$ tern bogus
tern: unknown command "bogus"
usage: tern COMMAND …
…
$ echo $?
2
```
| Command | What it does | Section |
|---|---|---|
| `tern new FILE` | writes a new note's first line | [tern new](#new) |
| `tern check PATH…` | prints the diagnostics of notes; exits 1 on errors | [tern check](#check) |
| `tern parse FILE` | prints the note's syntax tree as JSON | [tern parse](#parse) |
| `tern build FILE` | writes a static, pre-rendered page | [tern build](#build) |
| `tern mv FROM… TO` | moves notes and files, rewriting the links to and from them | [tern mv](#mv) |
| `tern search QUERY` | searches notes by word, each hit at its file and line | [tern search](#search) |
| `tern index [PATH…]` | writes `notes.json` and `search.json`, what the `::notes` and `::search` add-ons read | [tern index](#index) |
| `tern corpus PATH…` | prints documentation pages' live examples as corpus cases | [tern corpus](#corpus) |
| `tern lsp [--stdio]` | the language server, over stdio | [The language server](#lsp) |
## What every command shares {#common}
### Exit codes {#exit-codes}
| Code | Meaning |
|---|---|
| `0` | Done. |
| `1` | `tern check` found a diagnostic at or above `--fail-on`, `tern new` refused to write the file, `tern mv` refused to move or a move failed, `tern search` found nothing, `tern index --check` found a file missing or stale, or `tern corpus` left out an example. |
| `2` | A usage error (a missing argument, an unknown option, a bad value), or a path that cannot be used: a missing or unreadable file, or, for `tern parse` and `tern build`, a file that is not a note. |
Every command takes `--help` (or `-h`), which prints its synopsis and exits 0. Options are strict: an unknown option is a usage error, printed with the synopsis. When the reader of the output closes before the command is done (`tern parse FILE | head`), the command ends quietly with exit 0.
```sh
$ tern new x.html --bogus
tern new: Unknown option '--bogus'. To specify a positional argument starting with a '-', place it at the end of the command after '--', as in '-- "--bogus"
usage: tern new FILE [--tern=PATH] [--use=LIST] [--title=TEXT] [--guarded] [--force]
```
### How diagnostics print {#output}
`tern check` and `tern build` print each diagnostic on one line, with its hint indented on the next:
```sh
notes/kernel.html:6:1: error block.unclosed: `:::aside` opened on line 5 is still open at line 9
add `:::` or `:::/aside`
```
The parts are `FILE:LINE:COLUMN: SEVERITY CODE: MESSAGE`. This is the shape most editors and CI systems recognise as a clickable location.
- **Lines are file lines.** The note starts on the line after the `tern.js` tag, so with the canonical first line, note line 5 is file line 6. Your editor shows file lines, so these are the numbers to jump to.
- **Messages cite note lines.** The message comes from the engine, which counts lines from the first line of the note. Above, `opened on line 5` is the same line as `:6:1`.
- **Columns** count UTF-16 code units from 1, as the engine does. They are the same in the file and in the note.
Every code, with what it means and how to fix it, is on the [Diagnostics](diagnostics.html#top) page.
### Which files are notes {#notes}
A file is a note when one of its lines loads `tern.js` with a plain script tag. The rule is exact and shared by every command, the language server and editors; it is [Note detection](#detection) below. The note is everything after that line, and everything before the tag is the preserved head.
When a command is given a file that is not a note, it says why:
| The file | What the commands say |
|---|---|
| has no line loading `tern.js` | `no line loads tern.js` |
| loads it with `defer`, `async` or `type=module` | `it loads tern.js with defer, so tern.js does not render it; use a plain
```
| Option | What it does |
|---|---|
| `--tern=PATH` | The `src` of the script tag, as given: a relative path, a root-relative path, or a URL. Without it, see below. |
| `--use=LIST` | The add-ons, written as a [`:::use` block](syntax-blocks.html#use), one per line, after the `:::meta` block, and each preloaded from the first line (``, `as="script"` or `as="style"`), so the browser fetches it alongside `tern.js` instead of after the note has arrived. Separate them with commas or spaces; the option may repeat. |
| `--title=TEXT` | Adds a `:::meta` block with `title: TEXT`, then a blank line. One non-empty line. |
| `--guarded` | Adds the guard, which shows the note's source when `tern.js` does not run. See [The guard](#guarded). |
| `--force` | Overwrites an existing file. |
### The path to tern.js {#new-path}
Without `--tern`, `tern new` looks for a file called `tern.js` in the new note's folder, then in each folder above it, and writes the nearest one as a relative path with forward slashes. Two folders down, that is `../../tern.js`.
When there is none, it writes `tern.js` (the note's own folder) and says so on stderr:
```sh
$ tern new note.html
created note.html, loading tern.js
tern new: no tern.js in . or above; the note loads tern.js from its own directory
```
Relative paths are what you want for publishing: they keep working when the folder is copied, opened from disk, or served under a sub-path such as a GitHub project page (see [Publishing](publishing.html#github-pages)).
### Add-ons and a title {#new-options}
```sh
$ tern new notes/first.html --title="Linear maps" --use=theorems.js,theme.css
created notes/first.html, loading ../tern.js
tern new: :::use names theorems.js, which does not exist yet
tern new: :::use names theme.css, which does not exist yet
$ cat notes/first.html
:::meta
title: Linear maps
:::
:::use
theorems.js
theme.css
:::
```
- `--use=a.js,b.css`, `--use "a.js b.css"` and `--use=a.js --use=b.css` all give the same `:::use` block, `a.js` then `b.css`, one per line, and the same two preloads on the first line. `tern.js` preloads `data-use` entries itself as it starts; a `:::use` entry it finds only once the note has arrived, so the head's preload saves a round trip there.
- A local add-on that does not exist yet (relative to the note's folder) gets a note on stderr. The file is still written: you may be about to create it. Remote URLs and root-relative paths are not checked.
- `--title` is written as `:::meta` (see [Metadata](elements.html#metadata)). Before writing, `tern new` reads the file back and checks that the title survives as written, and that the add-ons read back from `:::use`; a title that would not is refused.
### The guard (--guarded) {#guarded}
A note whose `tern.js` does not load shows its source run together as one paragraph of text, with any raw HTML in it rendered. The guard makes that case readable: it shows a one-line message, then the note exactly as written.
```sh
$ tern new notes/guarded.html --guarded
created notes/guarded.html, loading ../tern.js, guarded
$ cat notes/guarded.html
```
The guard is the rest of the first line, after the tag. It is still one line, so the file is still a note. What happens in each case (all three are tested in Chromium and Firefox):
| Case | What the reader sees | How |
|---|---|---|
| `tern.js` loads | the rendered note | `tern.js` takes everything after its tag as text, the guard included, and the note starts on the next line. The guard is never parsed as HTML. |
| `tern.js` does not load | "tern.js did not load; the source follows.", then the note as plain text | The `