Skip to content

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.

macOS — pick arm64 for Apple Silicon, amd64 for Intel:

Section titled “macOS — pick arm64 for Apple Silicon, amd64 for Intel:”
Terminal window
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 nautilus
sudo mv nautilus /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/nautilus.

Terminal window
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 nautilus
sudo install nautilus /usr/local/bin/
Terminal window
$v = (Invoke-RestMethod https://api.github.com/repos/joyautomation/nautilus/releases/latest).tag_name
Invoke-WebRequest "https://github.com/joyautomation/nautilus/releases/download/$v/nautilus_$($v.TrimStart('v'))_windows_amd64.zip" -OutFile nautilus.zip
Expand-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.

Terminal window
go install github.com/joyautomation/nautilus/cmd/nautilus@latest

This puts the binary in $(go env GOPATH)/bin, which needs to be on your PATH. Whichever route you took, nautilus version should now answer.

The one binary is the whole toolchain: nautilus 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.

Terminal window
nautilus new my-plant # the tour: 3 tasks, 3 IEC languages, simulated plant
nautilus new my-plant --template minimal # one task, one program, one test
nautilus new my-plant --template sdk # Go project, for a custom field bus
nautilus new my-plant --template sdk-demo # Go project with plant physics in Go

Run 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:

Terminal window
cd my-plant
nautilus run # scan loop + dashboard + tag API on http://localhost:8080
nautilus test # acceptance tests, in virtual time
nautilus check # compile (the CI gate)
nautilus build # emit ./my-plant — a self-contained controller binary

nautilus.yaml declares the tasks (one program file each, any language, own scan rates), the tags by role, the server, and the field driver. nautilus 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 has

See 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:

Terminal window
cd my-plant
go mod tidy # resolves github.com/joyautomation/nautilus from the proxy
go run . # scan loop + tag API on http://localhost:8080
go test ./... # the program's acceptance tests, on the same virtual clock

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.

Install nautilus IEC 61131-3 from the VS Code Marketplace or Open VSX — currently on the pre-release channel, so use Install Pre-Release Version. With your project open and the controller running you get compile diagnostics as you type, go-to-definition, 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 nautilus even though your terminal does. If the extension reports it could not start the language server, set nautilus.cliPath to the full path (which nautilus), or launch VS Code from a terminal with code ..

  • 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 from nautilus.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 sim task and point driver: 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 nautilus check, nautilus test, and nautilus build. Add --deploy to nautilus new for 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.yaml format 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.