# Read a file from a CLI

URL: /docs/cookbook/cli_files
Markdown: /docs/cookbook/cli_files.md

> 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](https://github.com/icantcodefyi/sakuralang/tree/master/compiler/examples/cli_files) · harness mode: `run + test`.

From the repository root, with this checkout's `sakura` on `PATH`:

```sh
cd compiler/examples/cli_files
sakura check --json
sakura run -- --input note.txt
sakura test --json
```

### Manifest and permissions [#manifest-and-permissions]

```toml
[package]
name = "cli_files"
version = "0.1.0"
edition = "2026"

[permissions]
fs.read = ["./data"]
```

### Source and tests [#source-and-tests]

`src/main.sakura` includes all required imports and tests.

```sakura test id=cookbook-cli_files project=cli_files
// 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 [#expected-stdout]

```text
4 bytes
```

### `data/note.txt` [#datanotetxt]

```text
tea
```

### Compiler provenance and regeneration [#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](https://github.com/icantcodefyi/sakuralang/commit/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.
