@nowline/cli
The nowline command-line tool parses, validates, and renders .nowline roadmap files.
License: Apache 2.0
Part of: lolay/nowline monorepo
Install
A single nowline binary ships every export format (SVG, PNG, PDF, HTML,
Markdown+Mermaid, XLSX, MS Project XML) plus AST round-trip
(json ↔ nowline). Approximate size: ~70 MB (the bun runtime is
~60 MB of that — see
specs/cli-distribution.md for why
we don't ship a smaller "tiny" tier).
brew install lolay/tap/nowline
curl -L -o nowline.deb \
https://github.com/lolay/nowline/releases/latest/download/nowline_amd64.deb
sudo dpkg -i nowline.deb
npm install -g @nowline/cli
After any of the package-manager installs above, man nowline shows the
CLI manual (flags + a LANGUAGE cheatsheet) and man 5 nowline shows
the full .nowline DSL reference. The Homebrew formula's
resource "manpage" / resource "manpage5", the .deb's
/usr/share/man/man1/nowline.1.gz / /usr/share/man/man5/nowline.5.gz,
and npm's "man" field (which lists both files) all install the same
hand-authored mdoc sources from man/nowline.1 and
man/nowline.5. For direct binary downloads from
GitHub Releases, both man pages ship as separate nowline.1 and
nowline.5 assets alongside the platform binaries.
Usage
nowline is verbless: rendering is the default. Other modes are flags on the same command:
nowline <input> [options]
nowline <input> --serve [-p <port>]
nowline --init [<name>]
nowline <input> --dry-run
nowline --help
nowline --version
nowline
Examples
nowline roadmap.nowline
nowline roadmap.nowline -f png
nowline roadmap.nowline -f pdf
nowline roadmap.nowline -f html
nowline roadmap.nowline -f mermaid
nowline roadmap.nowline -f xlsx
nowline roadmap.nowline -f msproj
nowline roadmap.nowline -o roadmap.pdf
nowline roadmap.nowline -o -
nowline roadmap.json -f svg
cat foo.nowline | nowline -
nowline roadmap.nowline -f json -o roadmap.json
nowline roadmap.nowline --dry-run
nowline roadmap.nowline --serve -p 8080
nowline --init
nowline --init my-project
nowline roadmap.nowline -f pdf -o report
nowline roadmap.nowline -f pdf --page-size a4 --orientation landscape --margin 0.5in
nowline roadmap.nowline -f png --scale 3
nowline roadmap.nowline --headless
Flags
I/O
-f, --format <fmt> | inferred | svg, png, pdf, html, mermaid, xlsx, msproj, json, nowline. |
-o, --output <path> | <cwd>/<input-base>.<format> | - for stdout. Existing files silently overwritten. |
--input-format <fmt> | by extension; stdin → nowline | nowline or json. |
Mode flags (mutually exclusive)
--serve | Live HTTP preview. Opt-in -o <path> writes on each rebuild. |
--init [<name>] | Scaffold a starter .nowline in cwd. Auto-appends .nowline. |
-n, --dry-run | Run pipeline; skip the write. Replaces the old validate verb. |
Render options
-t, --theme <name> | light | light | dark. |
--now YYYY-MM-DD | today (UTC) | Override the now-line anchor date. Use --now - to suppress it. |
--no-links | (off) | Omit link icons from items. |
-s, --scale <n> | 1 | Raster scale (PNG only). |
--strict | (off) | Promote asset / sanitizer warnings to errors. |
-w, --width <px> | 1280 | Canvas width. |
--asset-root <dir> | input dir | Root for logo: / image refs. |
Format-specific options
--page-size <size> | letter | pdf | Preset (letter, legal, tabloid, ledger, a1–a5, b3–b5), content for auto-fit, or WxHunit for custom (8.5x11in, 210x297mm). |
--orientation <name> | auto | pdf | portrait | landscape | auto. |
--margin <length> | 36pt | pdf | Page margin. Bare numbers are points; 0.5in, 12mm, 1cm accepted. |
--font-sans <path|alias> | platform-resolved | png, pdf | TTF/OTF path, or alias sf, helvetica, dejavu, etc. |
--font-mono <path|alias> | platform-resolved | png, pdf | TTF/OTF path or alias for monospace. |
--headless | (off) | png, pdf | Skip platform font probe; use bundled DejaVu pair. Byte-stable across machines. |
--start YYYY-MM-DD | today | msproj | Anchor date for relative-only roadmaps. |
Serve options
-p, --port <n> | 4318 |
--host <host> | 127.0.0.1 |
--open | (off) |
Logging (mutually exclusive)
-v, --verbose | Extra diagnostics on stderr. |
-q, --quiet | Suppress non-error stderr. |
Standard
-h, --help | Print help, exit 0. |
-V, --version | Print version, exit 0. |
Behavior contracts
Format resolution (precedence chain)
-f / --format flag — explicit always wins.
-o <path> extension — recognized: .svg, .png, .pdf, .html/.htm, .md/.markdown (mermaid), .xlsx, .json, .nowline. .xml is ambiguous and requires -f msproj.
.nowlinerc defaultFormat — project default.
svg — built-in fallback.
If -f and -o extension disagree, -f wins and the output filename is preserved as written (no auto-rename).
Output extension auto-add
-o report -f pdf → report.pdf (no extension → append canonical).
-o report.pdf -f pdf → report.pdf (matching → leave alone).
-o foo.txt -f pdf → foo.txt (mismatched → leave alone, write PDF bytes there).
-o - (stdout) is never rewritten.
Default output paths
All default-named outputs land in cwd:
- File input:
<cwd>/<input-base>.<format>.
- Stdin input:
<cwd>/roadmap.<format>.
--init (no -o): <cwd>/<name>.nowline (default name roadmap).
Existing files are silently overwritten — no --force flag, matching POSIX redirection and peer tools (mmdc, d2, prettier, tsc).
Stdout
-o - is the only way to write to stdout (Unix dash convention).
- Binary formats (png, pdf, xlsx) on a TTY are refused with exit 2 (
nowline: binary output (png) to terminal refused; use -o or pipe to a file). Pipes / redirects are fine.
Mode dispatch
Mutual exclusivity rules (all exit 2 with a message):
--serve + --init.
--dry-run + --serve (serve doesn't write by default).
--dry-run + --init (init is the write).
-v / --verbose + -q / --quiet.
nowline (no args) prints help and exits 0 (matches git, kubectl, gh, cargo, docker).
Exit codes
| 0 | Success |
| 1 | Validation error |
| 2 | Usage error (missing input, bad flags, unsupported format, file not found, binary→TTY refusal) |
| 3 | Output error (cannot write to destination, exporter failure, page too small for margin) |
Configuration: .nowlinerc
On any operation that takes an <input> file, nowline walks up from the input file's directory looking for a .nowlinerc (JSON or YAML). The nearest one wins. CLI flags override config values. Environment variables are not consulted.
theme: dark
defaultFormat: svg
width: 1200
pdfPageSize: a4
pdfOrientation: landscape
pdfMargin: 0.5in
fontSans: sf
fontMono: sf-mono
headlessFonts: false
{
"theme": "dark",
"defaultFormat": "svg",
"width": 1200,
"pdfPageSize": "a4",
"pdfOrientation": "landscape",
"pdfMargin": "0.5in",
"fontSans": "sf",
"fontMono": "sf-mono",
"headlessFonts": false
}
Unknown keys are ignored.
Validation (--dry-run)
nowline roadmap.nowline --dry-run
nowline roadmap.nowline -n
nowline roadmap.nowline -n --diagnostic-format json
Each diagnostic is rendered in a biome/oxc-style frame via @babel/code-frame:
roadmap.nowline:7:34 error: Unknown reference 'auth-refactro' in after — did you mean 'auth-refactor'?
5 | item auth-refactor "Auth refactor" size:l
6 | parallel after:auth-refactor
> 7 | group audit-track "Audit Track" labels:security
| ^^^^^^^^^^^^^^^
8 | item audit-log "Audit log v2" size:xl before:code-freeze
--diagnostic-format json emits the stable diagnostic schema:
type Diagnostic = {
file: string;
line: number;
column: number;
severity: 'error' | 'warning';
code: string;
message: string;
suggestion?: string;
};
Exit 0 if no errors; exit 1 if any errors. Warnings never change the exit code.
JSON AST round-trip
-f json emits the JSON AST; -f nowline re-prints canonical .nowline:
nowline roadmap.nowline -f json -o roadmap.json
nowline roadmap.json -f nowline -o roadmap.nowline
The JSON form is a versioned, published contract ($nowlineSchema: "1") intended for MCP (m7) and editor (m5) round-trips:
type NowlineDocument = {
$nowlineSchema: "1";
file: { uri: string; source: string };
ast: NowlineFileNode;
};
Every node carries $type, $position, and the properties defined by the corresponding @nowline/core AST interface. Container back-references ($container, $containerProperty, $containerIndex) are omitted — parent-child relationship is captured by document structure.
Canonical .nowline printer rules
- Indent: 2 spaces.
- Positional order on declaration lines:
id → title → keyed properties.
- Keyed-property order:
date, length, on, duration, status, owner, after, before, remaining, labels, style, link, (any remaining keys, alphabetical).
- List shape: single-element lists render as bare (
labels:enterprise); multi-element lists use bracket form (labels:[enterprise, security]).
description sub-directive: rendered on its own line indented one level under its host.
- Comments: not preserved across round-trips. Documented limitation; a follow-up grammar ticket will add trivia support.
Round-trip property: for every file in examples/, text → json → text and json → text → json are idempotent modulo comment loss. Enforced by the test suite.
--serve
Live-reload preview. Opens a minimal HTML shell at http://<host>:<port>/ that fetches /svg and subscribes to /events (SSE). On file changes, the server re-parses, re-validates, re-lays-out, and re-renders; clients refresh automatically. Validation errors appear as an overlay on top of the most recent successful render.
nowline roadmap.nowline --serve
nowline roadmap.nowline --serve -p 4400 -t dark --open
nowline roadmap.nowline --serve -o latest.svg
--serve is intended for local authoring only. It is not a production preview service. -o - (stdout) is rejected.
--init
Create a starter .nowline file in the current directory. Three templates (minimal, teams, product) correspond to the three files in examples/ and are embedded into the CLI at build time — binaries are self-contained.
- Positional argument is the project name, not a file path.
.nowline is auto-appended if missing.
- Other extensions (
.txt, .json) are rejected with exit 2.
- Existing files are silently overwritten.
nowline --init
nowline --init my-project
nowline --init my-plan.nowline
nowline --init --template product
Arg parser choice
@nowline/cli parses arguments with Node's native util.parseArgs. Reasons:
- Zero runtime dependency.
- Native short/long flag support, kebab-case option names, mixed-position positionals, and the
-- end-of-options sentinel.
- Verbless dispatch is straightforward — modes are just boolean flags resolved after parsing.
Supporting libraries:
@babel/code-frame — biome/oxc-style source excerpts with caret/tilde underlines for --dry-run text output.
js-yaml — .nowlinerc YAML parsing (JSON also supported).
Distribution
A single nowline binary ships per platform, bundling every
@nowline/export-* package. Compiled with bun build --compile; the
CLI's format dispatch uses dynamic import() of each per-format package
so the heavy exporter deps stay off cold paths and the binary stays
straightforward to slim down later if a profile change ever justifies
re-introducing a tier (see
specs/cli-distribution.md).
Six platform binaries ship per release (macOS arm64/x64, Linux
x64/arm64, Windows x64/arm64), attached to every GitHub Release. Size
budgets are per target (Bun's standalone runtime varies by ~50 MB across
platforms): macOS-arm64 ~70 MB, macOS-x64 ~75 MB, Linux ~107 MB, Windows
~119–122 MB. CI asserts on disk for every target via
packages/cli/scripts/compile.mjs. See
specs/cli-distribution.md
for the full ceiling table.
Windows SmartScreen walkthrough
The shipped .exe binaries are unsigned. Windows may show a SmartScreen warning ("Windows protected your PC"). To run the downloaded binary:
- Right-click the
.exe → Properties.
- Check Unblock at the bottom of the General tab → OK.
- Run the binary from a terminal (
cmd or PowerShell).
Corporate endpoints may block unsigned binaries entirely. In that case, npm install -g @nowline/cli (which runs on Node/Bun) is an alternative.