Command-line tool
Twain includes a command-line tool, twain-md, that checks and renders documents using the
same engine as the app. It is handy in scripts and continuous integration, where you want to catch
broken links or missing images before they ship.
The CLI is built but not yet published to npm. For now, run it from a checkout with
node cli/twain-md.mjs …, or build a local package with npm pack in the cli/ folder and
install that. It requires Node.js 18 or later. The examples below use twain-md as the command
name for once it is published.
Commands
twain-md check <file...|dir> [--json] [--strict] [--component-map <name|path>] [--remote [--timeout <ms>] [--concurrency <n>]]
twain-md inventory <dir> [--json] [--component-map <name|path>]
twain-md render <file> [-o out.html] [--component-map <name|path>]
twain-md export <file> [-o out.html] [--profile <name|path>] [--component-map <name|path>]
twain-md open <file> [--changes] [--print]
check
check reports problems in one or more documents: unknown components, missing local images, broken
local links, and (with --remote) unreachable remote links. By default it makes no network
requests.
| Option | Effect |
|---|---|
--json | Output machine-readable JSON instead of text |
--strict | Treat informational findings (imports, expressions) as failures |
--remote | Also check remote http and https links and images over the network. It has no effect in project mode, when the argument is a directory |
--timeout <ms> | Per-request timeout for remote checks (default 5000) |
--concurrency <n> | How many remote checks to run at once (default 8) |
--component-map <name|path> | Use a framework map or a custom map file (see Component maps) |
Findings are one of: import and expression (informational), and unknown component,
missing image, broken link, remote image, and remote link (warnings). Project mode adds two
more warnings: broken anchor, for a cross-file link to a heading that does not exist, and
file too large, for a file over 4 MB that was inventoried but not analysed. The text output lists
each file with its findings, line by line.
Exit codes:
| Code | Meaning |
|---|---|
| 0 | Clean, or only informational findings without --strict; also a successful render or export |
| 1 | One or more failing findings |
| 2 | A usage error, an unknown profile or component map, or a file that could not be read |
Example:
twain-md check docs/**/*.md --strict
twain-md check guide.mdx --remote --timeout 3000
Give check a directory instead of files and it runs in project mode: it walks the folder for
Markdown and MDX, adding the cross-file link graph and broken cross-file links and anchors. The walk
skips hidden entries and the usual build and dependency folders (node_modules, .git, dist,
build, and similar), goes at most ten levels deep, and stops after 5000 files, saying so when it
does. A file larger than 4 MB is inventoried but not analysed, and is reported as a file too large
warning, which on its own is enough to make check exit 1.
inventory
inventory walks a folder of Markdown and MDX and prints a structured picture of the whole set:
per file its headings with their rendered anchor slugs, frontmatter, links and images with line
numbers, MDX components, and task-list items; and across the project the link graph, orphaned
files, and broken cross-file links and anchors. It is the CLI form of the MCP
twain_md_project tool.
twain-md inventory docs/ # human-readable summary
twain-md inventory docs/ --json # the full structured inventory
render
render produces a single self-contained HTML page, with styling and local images embedded, and
prints it to standard output (or to a file with -o):
twain-md render guide.mdx -o guide.html
This is close to the app's HTML export, with one difference: the CLI has no browser, so Mermaid diagrams stay as fenced code blocks instead of being drawn. KaTeX web fonts are not embedded in either export, so maths falls back to a system serif.
export
export is render with a profile that sets the theme, font, width, whether assets are
embedded, and the title:
twain-md export guide.mdx --profile dark -o guide.html
The built-in profiles are default, dark, sepia, and print. You can also point
--profile at your own JSON file, or keep named profiles in
~/.config/twain-md/profiles/<name>.json (under $XDG_CONFIG_HOME/twain-md/profiles/ if you set
that variable). A profile understands these fields:
| Field | Values | Default |
|---|---|---|
theme | auto, light, dark, sepia, high-contrast | auto |
font | sans, serif | sans |
width | narrow, normal, wide | normal |
embedAssets | true, false | true |
title | any text | the document's title, or the file path when it has none |
open
open hands a file to the Twain app through its twain-md:// URL scheme, so a script or an
editor can jump straight to a document. It only navigates: it never saves or changes anything,
and the app re-validates the path it is given.
twain-md open guide.md # open it in the app, rendered
twain-md open guide.md --changes # open it with changes against the last save
twain-md open guide.md --print # print the URL and exit, without launching
| Option | Effect |
|---|---|
--changes | Open with changes marked against the last saved version |
--print | Write the twain-md:// URL to standard output instead of launching the app |
Launching needs macOS and the Twain app installed. On any other platform, open prints the URL
to standard error and exits 2, while --print still works everywhere. On the Mac App Store
edition the sandbox only lets the app open a file it has already been granted access to, so this
works best for files inside a folder you have opened in Twain; the direct download has no such
restriction. See Editions.