Read a file from a CLI
Decode command-line flags into a record and count a selected file's bytes.
Generated by cargo run -p agent_docs from compiler/examples/. Edit the example files, then regenerate.
Decode command-line flags into a record and count a selected file's bytes.
Cli.parse infers Args from the binding. Path validates the relative path;
Fs.rooted(Dir.subdir(...)) narrows access to data before reading. The manifest
permits reads only below ./data. Counting bytes avoids treating file contents as
trusted instructions or decoding them unnecessarily. This small recipe reads the
whole file; use Fs.with_file and bounded Fs.read_chunk for large inputs.
CliError and FsError propagate to the CLI's failure reporter. Tests replace
arguments and files with Cli.of and Fs.memory, checking success, missing flags
and a missing file. The harness also runs sakura run -- --input note.txt against
the committed data file and expects 4 bytes.
Source project · harness mode: run + test.
From the repository root, with this checkout's sakura on PATH:
cd compiler/examples/cli_files
sakura check --json
sakura run -- --input note.txt
sakura test --jsonManifest and permissions
[package]
name = "cli_files"
version = "0.1.0"
edition = "2026"
[permissions]
fs.read = ["./data"]Source and tests
src/main.sakura includes all required imports and tests.
// milestone: M3
// mode: run
// spec: design §17.145
// args: --input note.txt
import std/cli.{Cli, CliError}
import std/fs.{Dir, Fs, FsError}
import std/path.{Path}
type Args = { input: Path }
fn byte_count() -> Int ! {Cli, Fs, Fail<CliError | FsError>} {
let args: Args = Cli.parse(Cli.args())
with Fs.rooted(Dir.subdir(Fs.root(), "data")) { Bytes.len(Fs.read(args.input)) }
}
pub fn main() -> Unit ! {Cli, Console, Fs, Fail<CliError | FsError>} {
Console.print("{byte_count()} bytes")
}
test "CLI arguments select a file inside data" {
with Cli.of(["--input", "note.txt"]),
Fs.memory([(Path.of("data/note.txt"), String.to_bytes("tea\n"))]) {
expect_eq(byte_count(), 4)
}
}
test "missing arguments fail before reading" {
with Cli.of([]), Fs.memory([]) {
expect_fails_with(fn() { byte_count() }, fn(e) {
match e {
CliError { .. } => true,
_ => false,
}
})
}
}
test "a missing file remains FsError" {
with Cli.of(["--input", "missing.txt"]),
Fs.memory([(Path.of("data/note.txt"), String.to_bytes("tea\n"))]) {
expect_fails_with(fn() { byte_count() }, fn(e) {
match e {
FsError { .. } => true,
_ => false,
}
})
}
}Expected stdout
4 bytesdata/note.txt
teaCompiler provenance and regeneration
These eight projects teach supported Sakura APIs on the JavaScript backend. Each
project is exercised offline by cargo test -p sakura_cli --test examples;
HTTP and LLM exchanges are synthetic committed cassettes, with no network or API keys.
Use Node 26 for execution, including its built-in SQLite implementation.
The initial API baseline is compiler revision
5cae349516c21714d90d36e87f1cfd6debe980c6.
CI reruns these projects against the compiler in the same checkout. Build that
checkout's CLI and run sakura --version and sakura prompt before adapting a recipe;
pre-alpha APIs can change. An installed skill's frontmatter records its compiler
version and build revision. These teaching examples are separate from the agent
evaluation tasks.
The source of each page is its example's recipe.md, sakura.toml,
src/main.sakura and fixture files. Run cargo run -p agent_docs from compiler/
to regenerate the cookbook and skill reference. Edit those sources, not the rendered pages.