Sakura Lang

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 --json

Manifest 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 bytes

data/note.txt

tea

Compiler 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.

Edit on GitHub

On this page