The nd command
Every nd subcommand, its flags, and one example of each.
nd is one binary (crates/nd-cli). Run it from an app's directory, the one that holds Cargo.toml and nativedesktop.toml. nd <command> --help prints the same flags that this page lists.
| Command | What it does |
|---|---|
new | Creates an app. |
upgrade | Moves the app to a newer nd release. |
check | Runs fmt, clippy, the framework lints, and the appearance audit. |
test | Runs the test tiers through nextest. |
build | Builds the native release, or the web bundle. |
serve | Builds and serves the web bundle. |
diagnose | Summarizes incident bundles. |
package | Builds installers. |
release | Packs, signs, and publishes an update feed. |
diag | Measures framework budgets. |
docs | Checks, builds, and captures this documentation. |
new
Creates a directory with an app that builds: Cargo.toml, nativedesktop.toml, a justfile, a nextest config, tests, and the agent docs (for agents). It refuses a directory that isn't empty and a name that isn't a lowercase package name.
<name>: the directory to create. Its last component is the package name.--id <id>: the reverse-DNS app id. The default iscom.example.<name>.--nd-path <path>(envND_PATH): depend on a local checkout of thendcrate (crates/nd) instead of the CLI's release tag.--nd-git <url>(envND_GIT): the repository to fetch nd from, for a mirror or a fork. The default ishttps://github.com/FormalStudio/NativeDesktop.--standalone: add an empty[workspace]so the app builds on its own inside another workspace.
nd new notes --id es.canarycoders.notes && cd notes && nd check && nd test
upgrade
Moves every nd dependency and the GPUI [patch.crates-io] entries from their current tag to a newer one. It then re-resolves Cargo.lock (other locked versions stay) and prints the CHANGELOG.md entries between the two versions. If cargo can't resolve the new tag, nd upgrade restores Cargo.toml.
--to <tag>: the tag to move to. The default is the newestvX.Y.Ztag on the remote.--path <dir>: the app's directory. The default is the current directory.
nd upgrade --to v0.7.0
check
Runs, in order: cargo fmt --check, the framework lints, cargo clippy --all-targets with the framework lint table denied (warnings, unwrap_used, expect_used, dbg_macro, todo, print_stdout), and the appearance audit of every package that has tests/appearance.rs (see testing). It exits non-zero if any step fails. To silence a framework lint, put // nd-allow(rule): reason on its line or the line above.
-p, --package <name>: check only these packages. The default is the workspace's default members.--release: also fail on<!-- nd: ... -->placeholders left in markdown files.--no-clippy: skip clippy.--no-audit: skip the appearance audit.--manifest-path <path>: use anotherCargo.toml.--target native|web:webadds the web lints, builds clippy forwasm32-unknown-unknownand skips the audit (web).
nd check --target web -p notes
test
Maps a tier to a nextest profile and runs cargo nextest run (testing). It needs cargo-nextest.
--tier fast|real|all:fast(the default) is tiers 1 and 2.realis tier 3 and needs a window server.allruns fast, then real.-p, --package <name>: test only these packages.<filters>...: test name filters, as nextest takes them.
nd test --tier real -p hello real_add
build
Without a target flag, nd build makes the native release build. With --target web, it compiles the app for wasm32-unknown-unknown, runs the matching wasm-bindgen, and writes a static bundle with an index.html (web).
--target native|web: the default isnative.-p, --package <name>: the default is the package in the current directory.--debug: build without optimizations.--out <dir>: where the web bundle goes. The default istarget/nd/web/<package>/.
nd build --target web --out dist
serve
Builds the web bundle and serves it on localhost. Pass --no-build to skip the build.
--target web: the only target, and the default.-p, --package <name>,--debug,--out <dir>: as inbuild.--port <n>: the default is 8080.--no-build: serve the last bundle as it is.
nd serve --target web --port 3000
diagnose
Summarizes the incident bundles that an app left behind: panics, crashes, hangs, and issue reports (diagnostics). With no argument, it summarizes the newest one.
<id>: a bundle id, or a unique part of it, as--listprints it.--last: the newest bundle (the default).--list: list bundles, newest first.--json: machine-readable output.--app <name>: the app's Cargo package name, when the current directory doesn't identify it.--manifest-path <path>: use anotherCargo.toml.
nd diagnose --list && nd diagnose --last --json
package
Builds the installer for one OS through Velopack: a macOS .app, .pkg, and .dmg, a Linux AppImage, or a Windows Setup.exe (packaging). The result goes to target/nd/releases.
<os>:mac,linux, orwindows. The default is the current OS.--dir <path>: the app directory. The default is..--channel <name>: one ofupdates.channels. The default is the first one.--notes <file>: markdown release notes for the app's "what's new".--binary <path>: package this prebuilt binary instead of runningcargo build --release(cross-packing).--runtime <id>: the Velopack runtime id for cross-packing, such aslinux-arm64.
nd package mac --channel beta --notes CHANGES.md
release
Packs the way package does, then writes a delta against the newest full package, signs the feed with an ed25519 key, and uploads it to [updates.publish] (packaging). If nativedesktop.toml has no [updates] feed and public_key, it stops with a message.
--dir,--channel,--notes,--binary: as inpackage.--os mac|linux|windows: the default is the current OS.--key <file>: the signing key (a base64 seed). Without it,nd releaseusesND_UPDATE_SIGNING_KEY(the key itself), then the file thatkeygenwrote.--no-publish: write and sign the feed directory without uploading it.
nd release --channel stable --notes CHANGES.md
nd release has two subcommands:
nd release keygen [--dir <path>] [--out <file>]: makes an ed25519 key, writes it to--out(default~/.config/nd/keys/<app id>.key), and prints the public key for[updates] public_key.nd release serve [--dir <path>] [--root <dir>] [--host <addr>] [--port <n>]: serves the feed directory over HTTP for update tests.--rootreplaces the defaulttarget/nd/releases. The host defaults to127.0.0.1and the port to 8790.
nd release keygen && nd release serve --port 8790
diag
Measures a framework budget. Its only subcommand is scroll (performance).
nd diag scroll --rows <n>: the number of rows in the list. The default is 100000.--seconds <s>: how long to scroll. The default is 10.--step <points>: the points to scroll per display frame. The default is 40.--bin <path>(envND_SCROLL_BIN): thescroll-benchbinary. The default is the one next tond.
It prints the frame percentiles. It exits non-zero when more than 2 frames are missed after warm-up, or when p95 is over 1.25 refresh periods.
nd diag scroll --rows 100000 --seconds 10
docs
Works on the documentation in docs/content (see docs/WRITING.md). Run it from anywhere inside the repository.
nd docs check: checks that every page loads (snippets, examples, and directives resolve), that links and anchors resolve, and that the prose rules hold over the pages and thend newtemplates. It also prints a capture report per OS.--require macosfails on a missing or stale macOS capture.--compilechecks every example natively and the web ones for wasm32.--webbuilds the site and loads each web example from it in headless Chrome.nd docs build: writes the static site totarget/nd/docs-site/. That's an HTML page and the raw markdown for every page,llms.txt,llms-full.txt, the captures, and the web bundle underrun/that runs the web examples live on their pages.nd docs servebuilds and serves it. CI deploys it to https://native.formalsnake.dev on every push to main.nd docs capture [example..]: runs each example through the test harness on this OS, light and dark, and records its source hash in the capture manifest.--staletakes only the examples whose capture is missing or out of date. A changed example shows "not captured yet" until you capture it again. On Windows the examples are launched and captured byndshell agent, which runs in the logged-in user's session (ndshell agent install), so the command also works over ssh.
nd docs check --require macos && nd docs capture --stale
The README's screenshots come from scripts/shots macos|linux|windows|all, run from the macbook. Each leg writes the gallery's dark capture to docs/shots/<os>.png. The Linux leg runs nd docs capture gallery on g815 through scripts/linux-gate. The macOS leg launches the gallery through scripts/mac/shoot, which captures with ndshot ($ND_NDSHOT), since an agent's terminal can't hold the Screen Recording grant that screencapture needs. The Windows leg builds in the Windows guest on g815 and captures through scripts/windows/vm shoot, which launches the app in the guest's desktop session through ndshell agent, dark and with a fresh data dir. Set ND_WINDOWS_HOST=<ssh host> to use a Windows machine instead of the guest. Messages and FormalMusic ship their own scripts/shots and call scripts/mac/shoot and scripts/windows/vm by path; Lynk's drives its landing page captures instead.