Your first program
Scaffold a project
bash
cleat new myapp
This creates:
text
myapp/ ├── cleat.toml # Project manifest └── main.cleat # Entry point
Project manifest
cleat.toml:
toml
[project] name = "myapp" version = "0.1.0" entry = "main.cleat" [build] output = "myapp"
Fields:
project.name— project name (required)project.version— semantic version (required)project.entry— file containingfn main()(default:main.cleat)build.output— output binary name (default: project name)
The minimal program
main.cleat:
cleat
import "std/io" fn main() -> int needs { io } { io.println("hello from myapp") return 0 }
Every Cleat program has a fn main() -> int that returns an exit code. The needs { io } clause declares that this function performs I/O — without it, calling io.println would be a compile error.
Build and run
bash
cd myapp # Compile and run in one step cleat run main.cleat # Or compile to a binary cleat build ./myapp
When you run cleat build with no arguments inside a directory with cleat.toml, the compiler reads the manifest and uses entry as the source file and output as the binary name.
Adding a test
Add a test block to main.cleat:
cleat
import "std/io" import "std/strings" fn greet(name: string) -> string { return "hello, ${name}!" } fn main() -> int needs { io } { io.println(greet("cleat")) return 0 } test "greet produces correct output" { let result = greet("world") assert(result == "hello, world!") } test "greet handles empty name" { assert(strings.contains(greet(""), "hello")) }
Run tests:
bash
cleat test main.cleat
Output:
text
=== RUN test "greet produces correct output" --- PASS test "greet produces correct output" === RUN test "greet handles empty name" --- PASS test "greet handles empty name" 2 tests: 2 passed, 0 failed
Tests live alongside production code. No separate test files needed. See Test Runner for details.
Next steps
- Types & Variables — learn the type system
- Agents — build your first AI agent
- Effect System — understand
needsclauses