Getting started
Platforms: nautilus is one static binary with no runtime dependencies.
Every release ships builds for ### macOS (Apple Silicon and Intel),
### Linux (x86-64 and arm64), and ### Windows (x86-64 and arm64); the
controller it builds runs anywhere the same binary does, from a laptop to a
Raspberry Pi to a Kubernetes pod. VS Code is the editor experience on all
three. The
release page
has the archives and a checksums.txt.
1. Install the CLI
Section titled “1. Install the CLI”macOS — pick arm64 for Apple Silicon, amd64 for Intel:
Section titled “macOS — pick arm64 for Apple Silicon, amd64 for Intel:”v=$(curl -fsSL https://api.github.com/repos/joyautomation/nautilus/releases/latest | grep -m1 '"tag_name"' | cut -d'"' -f4)curl -fsSL "https://github.com/joyautomation/nautilus/releases/download/$v/nautilus_${v#v}_darwin_arm64.tar.gz" | tar xz nautsudo mv naut /usr/local/bin/Downloading with curl skips Gatekeeper’s quarantine. If you fetched the
archive in a browser instead and macOS refuses to open the binary, clear the
flag once: xattr -d com.apple.quarantine /usr/local/bin/naut.
Linux — amd64 or arm64:
Section titled “Linux — amd64 or arm64:”v=$(curl -fsSL https://api.github.com/repos/joyautomation/nautilus/releases/latest | grep -m1 '"tag_name"' | cut -d'"' -f4)curl -fsSL "https://github.com/joyautomation/nautilus/releases/download/$v/nautilus_${v#v}_linux_amd64.tar.gz" | tar xz nautsudo install naut /usr/local/bin/nautWindows — PowerShell, amd64 or arm64:
Section titled “Windows — PowerShell, amd64 or arm64:”$v = (Invoke-RestMethod https://api.github.com/repos/joyautomation/nautilus/releases/latest).tag_nameInvoke-WebRequest "https://github.com/joyautomation/nautilus/releases/download/$v/nautilus_$($v.TrimStart('v'))_windows_amd64.zip" -OutFile nautilus.zipExpand-Archive nautilus.zip -DestinationPath "$env:LOCALAPPDATA\nautilus" -Force[Environment]::SetEnvironmentVariable("Path", "$env:LOCALAPPDATA\nautilus;" + [Environment]::GetEnvironmentVariable("Path", "User"), "User")Open a new terminal afterwards so the Path change is picked up.
Any OS, with Go 1.24+ installed
Section titled “Any OS, with Go 1.24+ installed”go install github.com/joyautomation/nautilus/cmd/naut@latestThis puts the binary in $(go env GOPATH)/bin, which needs to be on your
PATH. Whichever route you took, naut version should now answer.
The one binary is the whole toolchain: naut new (scaffold a
project), run, test, check (the CI gate: compiles every program and
cross-checks it against the manifest), build, pull (bring a controller’s
running program back into the repo), lsp (the language server the VS Code
extension uses), and the eip, modbus, sparkplug, and historian tools.
2. Scaffold a project
Section titled “2. Scaffold a project”naut new my-plant # the tour: 3 tasks, 3 IEC languages, simulated plantnaut new my-plant --template minimal # one task, one program, one test filenaut new my-plant --template sdk # Go project, for a custom field busnaut new my-plant --template sdk-demo # Go project with plant physics in GoRun it bare for the interactive form — it asks for the template, the program language, and the features you want.
A nautilus project is your logic and a manifest. Run, test, and ship it with the CLI alone, no toolchain:
cd my-plantnaut run # scan loop + dashboard + tag API on http://localhost:8080naut test # acceptance tests, in virtual timenaut check # compile (the CI gate)naut build # emit ./my-plant — a self-contained controller binarynautilus.yaml declares the tasks (one program file each, any language,
own scan rates), the tags by role, the server, and the field driver.
naut build emits one deployable binary, with no Go toolchain anywhere.
*_test.yaml holds the acceptance tests, and they run against a virtual
clock, so a ten-second on-delay or a loop’s settling time is asserted
exactly, deterministically, in milliseconds:
- name: low-temp alarm waits its full 10 s suspend: [sim] # freeze the plant; drive the value directly given: { TempC: 45.0 } steps: - advance: 9.5s expect: { TempLowAlm: false } # the TON has not elapsed - advance: 1s expect: { TempLowAlm: true } # ... and now it hasSee Testing for the whole format.
Go is the SDK. Reach for --template sdk when you need a custom field
bus or richer simulation physics. That form is the same
runtime with the manifest written as code, and it’s an ordinary Go
program:
cd my-plantgo mod tidy # resolves github.com/joyautomation/nautilus from the proxygo run . # scan loop + tag API on http://localhost:8080go test ./... # Go tests that scan the program against an in-memory driverThe scaffolded program_test.go calls rt.Scan() one scan at a time
against the loopback Memory driver and asserts on the outputs. For timed
behaviour, a Go test can run on the same virtual clock as naut test
(acceptance.NewClock and acceptance.NewScheduler); the
go-sdk example
does.
Open http://localhost:8080 for the built-in live dashboard, or
GET /api/state for the raw tag snapshot. Setpoints are click-to-set right in
the tag table — click a value and type, or flip a BOOL with its toggle — so you
can drive the loop before there is an HMI. Inputs and outputs stay read-only:
the driver rewrites an input before every scan and the logic rewrites an output
after, so an edit there would be discarded within a scan.
3. Develop in VS Code
Section titled “3. Develop in VS Code”Install nautilus IEC 61131-3 from the
VS Code Marketplace
or Open VSX. (Switch
to Pre-Release Version on the extension page gets builds from main.) With
your project open and the controller running you get compile
diagnostics as you type, go-to-definition, find all references, hover,
completion, and live tag values next to identifiers in your program.
On macOS, VS Code launched from the Dock or Spotlight gets the login PATH,
not your shell’s, so it may not find naut even though your terminal
does. If the extension reports it could not start the language server, run
nautilus: Install or Update the naut CLI, set nautilus.cliPath to the
full path (which naut), or launch VS Code from a terminal with code ..
4. Make it yours
Section titled “4. Make it yours”- Write control logic in
program.st, or in.ld,.fbd, or.sfc; the graphical languages open in full diagram editors in VS Code. - Assert on it in
*_test.yaml, and keep asserting as you tune. The fixture comes fromnautilus.yaml, so a retuned gain can’t drift away from what the tests verify. - Swap the simulated plant for real field I/O when you have hardware:
delete the
simtask and pointdriver:at the bus. The control logic doesn’t change — it reads the same tags either way. - Add an HMI with the SvelteKit component kit: faceplates, trends, and an SSE realtime client.
- Ship it as one binary. The scaffolded CI gates on
naut check,naut test, andnaut build; an SDK project’s CI runsgo vet,naut check(throughgo run),go test, andgo buildinstead. Add--deploytonaut newfor a Dockerfile, a redundant-pair Kubernetes manifest, and the workflow that ships a merged commit to the controller.
- Online edits — change logic on a running controller from VS Code, and pull field edits back into git.
- Testing — virtual time, and the
*_test.yamlformat for asserting on timers and loop responses. - The tag model — how tags come to exist, which role fits which job, and the one rule that bites.
- Language reference — evaluation semantics and every built-in operator, function, and function block.