03 / 06

Projects

A single file stops being enough as soon as you want tests, dependencies or a binary you can copy somewhere else. A project gives you all three, and it is one command away.

nyx init

$ nyx init my-app
nyx v0.31.0 — init
CAPABILITIES.md generado desde ~/.nyx/std (59 archivos escaneados)
Initialized project: my-app
  Created: my-app/
  Next:    cd my-app && nyx run

That seeds nine files:

my-app/
  nyx.toml
  .gitignore
  src/main.nx
  AGENTS.md
  CAPABILITIES.md
  docs/nyx/LLM.md
  docs/nyx/guides/write-a-program.md
  docs/nyx/guides/fix-a-compile-error.md
  docs/nyx/guides/report-friction.md

The first three are the project. The other six are the context an assistant needs in order to write Nyx here without guessing — step 04 is about them. If you want the documents in Spanish, nyx init my-app --lang es seeds the Spanish set instead; --agent= and --sdd add more, and step 04 covers both.

nyx.toml

[package]
name = "my-app"
version = "0.1.0"
main = "src/main.nx"

[dependencies]

name is also the name of the binary that nyx build produces, and the line the seeded .gitignore already ignores. main is the entry point every tool falls back to when you give it no file.

Build and run

$ nyx build
nyx v0.31.0 — build
-> building my-app v0.1.0
   compiling src/main.nx
✓ Built: my-app
build complete

That leaves ./my-app next to nyx.toml: a native executable you can copy to another machine of the same architecture. nyx run does the same build and then runs it.

Arguments after nyx run go to your program, not to Nyx. When an argument could be mistaken for a flag, put -- first:

fn main() {
    let args: Array = get_args()
    var i: int = 0
    while i < args.length() {
        let a: String = args[i]
        print(int_to_string(i) + ": " + a)
        i = i + 1
    }
}
$ nyx run -- --verbose one
nyx v0.31.0 — run
-> building my-app v0.1.0
   compiling src/main.nx
✓ Built: my-app
0: ./my-app
1: --verbose
2: one

Element 0 is the path of the binary itself, so your own arguments start at 1. Note the type annotation on a: an element read out of an Array without one is treated as an int, and printing it gives you a number instead of the string.

Tests

nyx test runs every tests/*.nx. A test is a test "name" { … } block — not a function:

test "greeting is not empty" {
    let msg: String = "hello"
    assert(msg.length() > 0)
}

test "sum works" {
    assert(1 + 1 == 2)
}
$ nyx test
=== Nyx Test Runner ===
  Found 1 test file(s)

  PASS  tests/greet_test.nx (2 tests)

===================================
  Files:  1 passed, 0 failed (1 total)
  Tests:  2 passed, 0 failed (2 total)
  Status: ALL TESTS PASSED
===================================

The trap worth knowing before it bites. A file full of functions named test_something() is found, counted, and then skipped — the runner says No files with test blocks found and exits happily. Nothing failed, because nothing ran. If your test count is zero and you expected tests, this is why: the blocks are test "name" { … }, always.

Note also that assert() aborts the process on the first failure. Inside nyx test that is handled for you — it reports per test and exits at the end — but in a program of your own, a failed assertion is the end of the program.

Dependencies

$ nyx add some-package
$ nyx add some-package --from https://example.com/some-package.git

The dependency is written into [dependencies] in nyx.toml, and nyx build clones what is missing into packages/ — which the seeded .gitignore ignores, on the assumption that you would rather fetch than vendor. Deleting that line and committing packages/ is the other reasonable choice: it makes the build reproducible without a network.

The standard library needs none of this. Modules like std/json, std/http or std/toml ship with the toolchain and are used with a plain import "std/json".

Two commands you will want later

$ nyx info
nyx v0.31.0 — info
Project: my-app
Version: 0.1.0
Main:    src/main.nx
Mode:    GC (default)

And, after upgrading the toolchain, nyx update --sync-docs — run inside the project — refreshes the seeded documents to the version you now have, keeping a .bak of anything it replaces. What it refreshes, and why it matters, is the next step.