| name | neo-cli |
| description | The NeoHaskell `neo` CLI subcommand-and-flag reference. Use when the user needs to know WHICH `neo` subcommand and flags to run for a tooling intent: building, running, testing, scaffolding (neo new, library vs app), locking domain files, opening the IDE, or installing skills. Maps each intent to the exact invocation, explains the Nix wrapping (build/run/test are NOT plain cabal), states the real ports (IDE 2323 vs app 8080), and confirms flag names/order or that a subcommand is fake (no neo serve/deploy/openapi). Also use when a `neo` invocation fails with an unknown flag or subcommand. Do NOT use to actually RUN the app and interpret neo inspect output, hit endpoints, or check health probes (neo-run-and-inspect owns the live HTTP surface and inspect results); to explain the lock/V2 versioning RULE (neo-immutability-and-versioning); to declare config fields or secrets (neo-config-and-secrets); or to review code (neohaskell-code-review). |
| metadata | {"model":"haiku"} |
This is NeoHaskell, not vanilla Haskell. The .hs extension is shared, but NeoHaskell
uses a custom Core prelude and the neo CLI wraps every Haskell build step inside
nix develop --command bash -c "cabal …". Never call cabal, stack, or ghc
directly; always go through neo.
Inputs / Outputs / Next
Input: a tooling intent ("build the project", "run tests", "open the IDE", "scaffold
a new project", "lock a file") plus the project root (must contain flake.nix and
neo.json).
Output: the exact neo <subcommand> [flags] command(s) + what they wrap/require.
Next skills:
neo-run-and-inspect — HTTP endpoints, OpenAPI, /health, neo inspect
neo-immutability-and-versioning — .locked-files, lock gate, V-bump rule
wire-feature — after building, to register new commands/queries
write-hurl-e2e — after neo test is confirmed working
Command surface (grounded in neo/src/cli.rs + neo/src/subprocess/nix.rs)
Global flags (always placed BEFORE the subcommand)
neo [--verbose | -v] [--ci] <subcommand> …
| Flag | Effect |
|---|
--verbose / -v | Enable debug-level output |
--ci | Disable interactive prompts, animations, and colors. Incompatible with --watch; neo --ci build --watch is rejected. |
Always use neo --ci <cmd> in any agent, CI, or non-interactive (non-TTY) shell. With no
controlling terminal, bare neo build / neo test panics before doing anything — it tries to
drive an interactive TTY progress animation for its nix subprocess and dies with:
thread 'main' panicked at src/subprocess/nix.rs:108: called `Result::unwrap()` on an `Err` value:
Os { code: 6, kind: Uncategorized, message: "Device not configured" }
(errno 6 / ENXIO). neo --ci build / neo --ci test fixes it; the flag goes before the
subcommand. This is the single highest-value fact for agent/CI use — it blocks the very first build.
neo new [<project-name>] [--library]
Scaffold a new NeoHaskell project (interactive interview, or CI-defaults if --ci).
neo new my-app
neo new my-lib --library
neo --ci new my-app
Requires: nothing (downloads the starter template).
neo build [--watch] [--skip-lock-check]
Lock-gate check, then reconcile neo.json → flake.nix + .cabal, then:
nix develop --command bash -c "cabal build all"
neo build
neo build --watch
neo build --skip-lock-check
Reconcile manages the cabal module lists. The "Reconciling project artifacts" step
regenerates exposed-modules (and each test suite's other-modules) from the filesystem and
alphabetizes them. You do not need to hand-edit the .cabal: adding a .hs file under src/
(or a *Spec.hs under tests/) is enough to register it — any manual edits are normalized on the
next neo build / neo test.
Reconcile auto-bumps the neohaskell dependency. Because neo.json sets
"neo-version": "main", every neo build / neo test tracks the latest main and may print e.g.
Updated input 'neohaskell': b5aa84d → e55fdf6, rewriting flake.nix (rev + neohaskellCommit),
cabal.project (tag:), and flake.lock together. The pinned rev drifting forward is by
design — not corruption and not a dirty tree. Also covered by neo-immutability-and-versioning.
Requires: nix on PATH + a flake-enabled directory (flake.nix present).
The lock gate aborts neo build if any path listed in .locked-files appears in
git status --porcelain. Fix = create a V2 sibling (see neo-immutability-and-versioning),
never edit the locked file.
Verify the build compiled: neo build exits 0 on success.
neo run [--watch]
Reconcile, then:
nix develop --command bash -c "cabal run all"
neo run
neo run --watch
App port: 8080 — this comes from the project's Config.hs / env var (typically
PORT), not from a CLI flag. There is no --port option on neo run.
Requires: nix on PATH + a flake-enabled directory.
neo test [--watch]
nix develop --command bash -c "cabal test all"
then discovers and runs every tests/**/*.hurl file against a freshly booted app.
neo test
neo test --watch
Boot wait: neo test boots the app and polls http://127.0.0.1:8080/ (root path)
every ~250 ms for up to 60 s. It fires the hurl files as soon as any HTTP response
arrives — even a 404 — so it does not require a readiness endpoint to exist. It does
not use a fixed sleep. (For /ready endpoint details see neo-run-and-inspect.)
Build-chain for hurl tests: see write-hurl-e2e.
Requires: nix on PATH + git (required by the test runner) + a flake-enabled directory.
neo lock
Manage the .locked-files immutability manifest. Three forms:
neo lock <search-string>
neo lock --all
neo lock install
neo lock check
There is no neo lock --remove or neo lock uninstall subcommand.
Full .locked-files mechanics → neo-immutability-and-versioning.
neo ide [--host <IP>] [--port <N>]
Start the bundled in-browser event-modeling IDE.
neo ide
neo ide --host 0.0.0.0
neo ide --host 0.0.0.0 --port 9000
Defaults from cli.rs:
--host default: 127.0.0.1 (loopback; literal IPv4/IPv6 address required — not a hostname like localhost)
--port default: 2323
IDE port 2323 is not the app API port (8080). Do not confuse them.
Open the printed URL in a browser. Press Ctrl-C to stop.
neo inspect [<subcommand>]
Print the project's domain layout. With no subcommand, dumps everything as JSON.
neo inspect
neo inspect domains
neo inspect commands
neo inspect events
neo inspect queries
neo inspect integrations
neo inspect wiring
neo inspect sync
Warning — neo inspect sync clobbers event-model.json. It regenerates the file
entirely from source code. Any hand-authored additions (e.g. from the event-modeling
skill) that have not yet been committed to code will be lost. Only run it when you want
source code to be the authoritative source of truth for the model.
HTTP endpoint inspection → neo-run-and-inspect.
neo validate [<path>] [--json]
Lint event-model.json against the embedded JSON Schema and referential-integrity rules —
read-only (never modifies the file). This is the CLI form of the check the neo ide runs.
neo validate
neo validate path/to/event-model.json
neo validate --json
neo validate --json | jq -e '.status == "valid"'
Exit codes: 0 valid · 1 IO error · 2 invalid (schema/referential) · 3 malformed JSON ·
4 file missing. Used by verify-event-model for PASS 1 + PASS 2. (For best-practice/naming review
— PASS 3 — see verify-event-model; neo validate does the mechanical passes only.)
neo skills setup [flags]
Fetch github.com/neohaskell/skills and install skills into the project's AI tool folders.
neo skills setup
neo skills setup --tool claude
neo skills setup --tool claude --tool cursor
neo skills setup --all-tools
neo skills setup --skill neo-cli
neo skills setup --force
neo skills setup --dry-run
neo skills setup --refresh
neo --ci skills setup --all-tools
Supported tool IDs: claude, codex, kiro, cursor, agents.
DO / DON'T
| You might reach for | NeoHaskell-correct |
|---|
cabal build directly | neo build (Nix-wrapped; runs the lock gate first) |
cabal test directly | neo test (also runs hurl e2e files) |
cabal run directly | neo run |
stack build / ghc | never; neo build only |
neo serve / neo deploy / neo openapi | these subcommands do not exist |
neo run --port 9090 | no --port flag on neo run; port is Config/env-var (PORT) |
neo --ci build --watch | --watch and --ci are mutually exclusive; pick one |
neo ide --host localhost | must be a literal IP address (e.g. 127.0.0.1); hostnames are rejected by clap |
neo lock --remove | does not exist; only install/check/<search>/--all |
assume /ready polls before hurl | neo test polls root / every ~250 ms (any HTTP response triggers hurl); /ready is ON by default (useReadinessEndpoint to customize) — see neo-run-and-inspect |
neo inspect sync to see the model | sync clobbers event-model.json; use neo inspect (no subcommand) to read |
| global flags after the subcommand | global flags (--verbose, --ci) must come before the subcommand |
bare neo build / neo test in an agent/CI shell |
Verify
After any build-related change, confirm with:
neo build
Exit 0 = compiled. Non-zero = the lock gate tripped or cabal compilation failed (read
the error output; lock violations say which file; compilation errors show the GHC
message from inside the Nix shell).