A Roc platform for command-line programs.
basic-cli supports command execution, directories, environment variables, files, HTTP, locales, paths, random seeds, sleeping, SQLite, standard input/output/error, TCP, terminal raw mode, and UTC time.
The platform builds and runs on these targets in CI:
| Roc target | Operating system | Architecture |
|---|---|---|
x64mac |
macOS | x86-64 |
arm64mac |
macOS | ARM64 |
x64musl |
Linux (musl) | x86-64 |
arm64musl |
Linux (musl) | ARM64 |
x64win |
Windows | x86-64 |
Other targets are not currently supported. In particular, Windows support is
x86-64 only and uses the UCRT supplied with Windows 10 and 11. Applications do
not require a separate Visual C++ runtime installation. Windows library sources,
build commands, hashes and licenses accompany the platform bundle in
targets/x64win/PROVENANCE.json and targets/x64win/licenses/.
These current host-level limitations may affect application design:
- HTTP and HTTPS use HTTP/1 only. Issue #455 tracks research into HTTP/2 support and its tradeoffs.
- HTTPS certificates are validated against bundled WebPKI roots, not the operating system trust store. A certificate trusted only through a locally installed OS root will therefore be rejected. Issue #454 tracks research into the appropriate trust configuration for basic-cli.
- The timeout configured on an HTTP
Requestis the deadline for the whole exchange, response body included;NoTimeoutleaves it unbounded.Http.send!and the functions built on it buffer the response completely without a configurable size limit (issue #436). Http.send_streaming!returns once the status and headers have arrived and hands the body over in chunks of at most 64 KiB, so a download need not fit in memory. Itsidle_timeout(30 seconds unless set) bounds each single wait, for the headers and for each part of the body, so a large download can leave theRequestatNoTimeout. After an idle timeout the response can still be read; theRequestdeadline passing is final. Each streaming response has its own connection, closed when the body ends or fails, or when the response and its streams are released. Request bodies are not streamed.- Several network and TLS failures currently use a generic HTTP transport error (issue #438).
- TCP reads and writes always have a timeout: a
Tcp.Connectionholds theio_timeoutit was created with, which bounds each read or write as a whole, andwith_timeoutreturns the same connection with another. The wait to connect or accept is separate; onlyaccept!may wait indefinitely (NoTimeout).Duration.zeroexpires immediately, and expiry is alwaysTcpErr(TimedOut). - A TCP read that times out consumes nothing: the bytes that had arrived stay buffered for the next read. A write that times out may have sent any prefix.
Tcp.Connection.read_until!,read_line!andlinesstop at the connection's line limit (1 MiB unless changed withwith_line_limit), preventing an untrusted peer from causing unbounded buffering. The rest of an over-long record is discarded before the next read returns.File.Writerbuffers written bytes in the host; they reach the operating system when the buffer fills and onflush!. The file is closed when the last reference to the writer is released, which writes what is still buffered but cannot report a failure, so flush after the last write or useFile.with_writer!. A crash loses buffered bytes.- Every
Sqlite.open!call opens its own connection;Sqlite.open_in_memory!and the path":memory:"open a new, private in-memory database each time. A connection waits up to five seconds for another connection's lock unlessbusy_timeoutsays otherwise. It is closed byclose!, or when theDband every unfinishedrowsstream made from it have been released. Each connection keeps up to 32 prepared statements, keyed by their SQL text, and drops the least recently used.
The examples directory contains executable, application-shaped CLI programs for common tasks like reading files, running commands, constructing URLs, making HTTP requests, working with SQLite, and reading stdin.
The examples are pinned to the 0.25.0 release:
app [main!] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.25.0/CZsY7tYZwR3rj9kYbpaCfxki2yVAaRL8bBwMLvB2xkbA.tar.zst" }To run examples from a local checkout instead, build the platform first and use "../platform/main.roc"; see CONTRIBUTING.md.
HTTP examples use Roc's builtin Json parser and encoder directly through
Http.get! and Http.send_json!.
Fetch the pinned published platform without rebuilding its Rust hosts:
nix build github:roc-lang/basic-cli#platform # result/main.roc and result/targets/
nix build github:roc-lang/basic-cli#bundle # result is the original release archiveThe default package is platform. These outputs provide the release recorded in
nix/release.json, rather than the current checkout's platform
sources. The flake also provides the compiler and dependency pins needed to build
applications offline; see the downstream application example.
Ask questions on Roc Zulip, especially in the #beginners stream.
Contributor setup, verification, generated glue, and documentation publishing notes live in CONTRIBUTING.md.