This document describes the current behavior of the fe CLI as implemented in this repository (crate crates/fe).
It is not a stability guarantee.
--color <auto|always|never>: controls colored output (default:auto).
- Stdout: “normal” command output (e.g. artifact paths, formatted file paths, dependency trees).
- Stderr: diagnostics, errors, warnings, and hints.
User-facing diagnostics follow these conventions:
Error: ...for errors (typically causes non-zero exit forbuild,check,test,fmt --check).Warning: ...for non-fatal warnings.Hint: ...for suggestions following an error.
The CLI intentionally does not use emoji/icon markers.
0on success.1on failure.2on CLI usage/argument parsing errors (emitted byclap, e.g. unknown flags or missing values).
Notable exceptions:
fe check/fe build/fe treeon a workspace root with no members prints a warning explaining why (no members configured, or configured paths don't exist) and exits0.
Many CLI paths use camino::Utf8PathBuf internally. If a relevant path (including the current directory) is not valid UTF-8, the CLI may error.
Some subcommands emit ANSI-colored output:
fe fmt --checkprints colored diffs.fe testprints coloredok/FAILED.fe treerenders cycle nodes in red via ANSI escape codes.
Color emission is controlled by --color and respects common environment conventions (CLICOLOR_FORCE, NO_COLOR, CLICOLOR) in auto mode.
Several subcommands take a single “target” argument which can be:
- a standalone
.fefile, - a directory containing
fe.toml(ingot root or workspace root), - or a workspace member name (when run from within a workspace context).
The general rules are:
fe.tomlfile paths are rejected. Pass the containing directory instead.- A path that is a file must end in
.feto be treated as a source file. - A path that is a directory must contain
fe.tomlto be treated as a project. - A workspace member name is only considered when the argument:
- looks like a name (ASCII alphanumeric and
_), and - the current working directory is inside a workspace context that contains a matching member.
- If the argument also exists as a filesystem path, the CLI requires that the name and path refer to the same member (see disambiguation below).
- Note: name lookup uses the workspace’s “default selection” of members. If the workspace
fe.tomlsetsdefault-members, only those members are considered by default; non-default members may need to be targeted by path.
- looks like a name (ASCII alphanumeric and
If the argument both:
- looks like a workspace member name, and
- exists as a path,
then the CLI requires that they refer to the same workspace member; otherwise it errors with a disambiguation message (e.g. “argument matches a workspace member name but does not match the provided path”).
For build and check:
- If you pass a
.fefile that lives under an ingot (nearest ancestorfe.tomlparses as an ingot config), the command runs in ingot context (so imports resolve as they would from the ingot).- Override:
--standaloneforces standalone mode for that.fefile target.
- Override:
- If you pass a
.fefile that lives under a workspace root (nearest ancestorfe.tomlparses as a workspace config), the command treats the file as standalone unless you explicitly target the workspace/ingot by passing a directory or member name.
Compiles Fe contracts to EVM bytecode.
--backend sonatina(default): generates bytecode directly (nosolcrequired).--backend yul: emits Yul and invokessolc.
fe build [--standalone] [--contract <name>] [--backend <backend>] [--opt-level <level>] [--optimize] [--solc <path>] [--out-dir <dir>] [--report [--report-out <out>] [--report-failed-only]] [path]
If path is omitted, it defaults to ..
fe build accepts:
- a
.fefile path (standalone mode unless the file is inside an ingot; see above), - an ingot directory (contains
fe.tomlparsing as[ingot]), - a workspace root directory (contains
fe.tomlparsing as[workspace]), - a workspace member name (when run from inside a workspace).
- Standalone
.fefile default:<file parent>/out - Ingot directory default:
<ingot root>/out - Workspace root default:
<workspace root>/out - Override:
--out-dir <dir>- If
<dir>is relative, it is resolved relative to the current working directory. - Note: default output directories are derived from the canonicalized (absolute) target path, so the printed
Wrote ...paths are absolute by default unless--out-diris set.
- If
- The compiler analyzes the file’s top-level module.
- Contracts are discovered and, by default, all contracts in that module are built.
- The ingot and its dependencies are resolved/initialized.
src/lib.feis treated as the ingot’s root module. If it is missing, the CLI behaves as if an emptysrc/lib.feexisted (a “phantom” root module).- Contracts are discovered across the ingot’s entire source set (
src/**/*.fetop-level modules), not onlysrc/lib.fe. - By default, all discovered contracts are built.
Workspace builds use a flat output directory:
- All member artifacts are written directly into the same
outdirectory. - Before building, the CLI checks for artifact name collisions across workspace members:
- Artifact filenames are derived from a sanitized contract name (see below).
- Collision detection is case-insensitive (e.g.
Fooandfoocollide) to avoid filesystem-dependent behavior on case-insensitive filesystems. - If multiple contracts (possibly from different members) map to the same artifact base name, the build errors and lists the conflicts.
Workspace member selection:
- The set of members considered is the workspace’s “default selection”.
- If
default-membersis present, only those members are built. - Otherwise, all discovered members are built (including any
devmembers).
- If
- Workspace builds skip members with zero contracts; if all selected members have zero contracts, the build fails with
Error: No contracts found to build.
- For standalone files and ingots:
- If
<name>exists, only that contract is built. - If not found, the build errors and prints “Available contracts:” with a list.
- If
- For workspace roots:
- If exactly one workspace member contains the contract, that member is built for that contract.
- If zero members contain the contract, it errors.
- It also prints “Available contracts:” for the workspace (unique contract names), capped at
50, followed by... and N moreif applicable.
- It also prints “Available contracts:” for the workspace (unique contract names), capped at
- If multiple members contain the contract, it errors and prints a “Matches:” list and a hint to build a specific member by name or path.
fe build currently supports:
sonatina(default): directly generates EVM bytecode via Sonatina IR.--opt-level <level>controls Sonatina optimizations (see below).--solcis ignored; if passed, it printsWarning: --solc is only used with --backend yul; ignoring --solc.FE_SOLC_PATHis ignored.
yul: emits Yul and invokessolcto produce bytecode.
When --backend yul, fe build invokes solc using:
--solc <path>if provided (highest priority),FE_SOLC_PATHif set,- otherwise
solcresolved viaPATH.
If solc fails, fe build prints an error and a hint:
Error: solc failed for contract "<name>": <details>
Hint: install solc, set FE_SOLC_PATH, or pass --solc <path>.
Optimization is controlled by --opt-level <level> (and the --optimize shorthand).
Defaults:
--opt-leveldefaults tos(for both backends).
Backend behavior:
- Yul backend:
--opt-level 0disables the solc optimizer, and--opt-level 1/2enables it.- Note:
--opt-level 2currently has no additional effect over--opt-level 1for solc; the CLI prints a warning.
- Note:
- Sonatina backend:
--opt-level <level>controls Sonatina optimizations:0: nones: size-optimized (default)1: balanced2: aggressive
--optimize is shorthand for --opt-level 1.
- If you pass
--optimize --opt-level 0, the CLI errors. - If you pass
--optimize --opt-level 2, it has no effect.
For each built contract, fe build writes:
<out>/<contract>.bin(deploy bytecode, hex + trailing newline)<out>/<contract>.runtime.bin(runtime bytecode, hex + trailing newline)
For the Sonatina backend, .bin is the init section bytes and .runtime.bin is the runtime section bytes.
The on-screen output is per-artifact:
Wrote <out>/<name>.bin
Wrote <out>/<name>.runtime.bin
Filenames are “sanitized” from contract names:
- Allowed: ASCII alphanumeric,
_,- - Other characters become
_ - If the sanitized name is empty, it becomes
contract
This sanitization is also what the workspace collision check uses.
fe build can optionally write a .tar.gz debugging report (useful for sharing failures):
--report: enable report generation.--report-out <out>: output path (default:fe-build-report.tar.gz).--report-failed-only: only write the report iffe buildfails.
The build report is best-effort and includes:
inputs/: the ingot or.fefile inputs (same rules asfe check/fe test).artifacts/: emitted IR (e.g. Yul) and bytecode artifacts (when available).errors/: best-effort captured errors/panics (if any).meta/: environment and tool metadata.
Type-checks and analyzes Fe code (no bytecode output).
fe check [--standalone] [--dump-mir] [--report [--report-out <out>] [--report-failed-only]] [path]
If path is omitted, it defaults to ..
Same target resolution rules as fe build:
.fefile, ingot directory, workspace root directory, or workspace member name.
fe check <workspace-root>checks all members in the workspace’s default selection.- If the workspace
fe.tomlsetsdefault-members, only those member paths are checked. - Otherwise, all discovered members are checked.
- If the workspace
- If the selection is empty, it prints a warning explaining why (no members configured, or configured paths don't exist on disk) and exits
0.
When checking an ingot with dependencies, if downstream ingots have errors, fe check prints a summary line:
Error: Downstream ingot has errors- or
Error: Downstream ingots have errors
Then, for each dependency with errors, it prints a short header (name/version when available) and its URL, followed by emitted diagnostics.
--dump-mir: prints MIR for the root module (only when there are no analysis errors).
fe check can optionally write a .tar.gz debugging report (useful for sharing failures):
--report: enable report generation.--report-out <out>: output path (default:fe-check-report.tar.gz).--report-failed-only: only write the report iffe checkfails.
The check report is analysis-only and includes:
inputs/: the ingot or.fefile inputs.errors/: diagnostics output (when available).artifacts/: MIR dump (when available).meta/: environment and tool metadata.
Prints the ingot dependency tree.
fe tree [path]
If path is omitted, it defaults to ..
fe tree accepts:
- a directory path (ingot root or workspace root),
- a workspace member name (when run from inside a workspace).
Unlike build/check, fe tree does not take a .fe file target.
The tree output is a text tree using ├──/└── connectors.
Annotations:
- Cycle closures are labeled with
[cycle]. - Local → remote edges are labeled with
[remote]. - Nodes that are part of a cycle are rendered in red via ANSI escape codes.
- This respects
--color(andNO_COLORinautomode).
- This respects
When the target is a workspace root, fe tree prints a separate tree per member, each preceded by:
== <member name> ==
If fe tree prints any Error: diagnostics (including ingot initialization diagnostics like dependency cycles), it exits 1.
Even when it exits 1, it still prints the dependency tree for the target (best-effort).
Formats Fe source code.
fe fmt [path] [--check]
- If
pathis a file: formats that single file. - If
pathis a directory: formats all.fefiles under that directory (recursive). - If
pathis omitted: finds the current project root (viafe.toml) and formats all.fefiles under<root>/src.
- Does not write changes.
- Prints a unified diff for each file that would change.
- Exits
1if any files are unformatted (or if IO errors occur).
Runs Fe tests via the test harness (revm-based execution).
fe test [--filter <pattern>] [--jobs <n>] [--grouped] [--show-logs] [--debug[=<mode>]] [--backend <backend>] [--solc <path>] [--opt-level <level>] [--optimize] [--trace-evm] [--trace-evm-keep <n>] [--trace-evm-stack-n <n>] [--sonatina-symtab] [--debug-dir <dir>] [--report [--report-out <out>]] [--report-dir <dir> [--report-failed-only]] [--call-trace] [path]...
- Zero or more paths (files or directories).
- Supports glob patterns (e.g.
crates/fe/tests/fixtures/fe_test/*.fe). - When omitted, defaults to the current project root (like
cargo test).
- Tests are functions marked with a
#[test]attribute. --filter <pattern>is a substring match against the test’s name.
--jobs <n>controls how many suites run in parallel (0= auto).- By default, parallel execution uses per-test jobs after suite discovery.
--groupedkeeps suite-by-suite execution (each worker runs whole suites).
--debug[=<failures|all>]prints Yul output when using the Yul backend.--trace-evm,--trace-evm-keep,--trace-evm-stack-nenable EVM opcode tracing.--sonatina-symtabdumps the Sonatina runtime symbol table (function offsets/sizes).--debug-dir <dir>writes debug outputs (traces, symtabs) into a directory.--call-traceprints a normalized call trace for each test (backend comparison).
- Per-test output is
PASS [<seconds>s] <name>/FAIL [<seconds>s] <name>(colored). - In multi-input runs, output is tabular:
<status> <suite> <message>, with suite names colored magenta. - Progress/status labels include
COMPILING,READY(blue),PASS(green),FAIL(red), andERROR(red). --show-logsprints EVM logs (when available).- A summary is printed if at least one test ran.
- If a suite has no tests, it prints
Warning: No tests found in <path>and continues (exit code is still0if there are no failures elsewhere).
--reportwrites a single.tar.gzreport (default output:fe-test-report.tar.gz).--report-dir <dir>writes one.tar.gzreport per input suite into<dir>(useful with globs).--report-failed-onlyonly writes per-suite reports for failing suites (requires--report-dir).
Default backend is sonatina.
When --backend yul, fe test compiles generated Yul using solc selected by:
--solc <path>if provided (highest priority),FE_SOLC_PATHif set,- otherwise
solcresolved viaPATH.
Optimization flags:
- Optimization is controlled by
--opt-level <level>(and the--optimizeshorthand). - Sonatina backend:
--opt-level <level>controls the optimization pipeline. - Yul backend:
--opt-level 0disables the solc optimizer, and--opt-level 1/2enables it.- Note:
--opt-level 2currently has no additional effect over--opt-level 1for Yul/solc; the CLI prints a warning.
- Note:
--optimizeis shorthand for--opt-level 1.- If you pass
--optimize --opt-level 0, the CLI errors.
- If you pass
When --backend sonatina, fe test generates bytecode directly and does not require solc (--solc is ignored with a warning).
Creates a new ingot or workspace layout.
fe new [--workspace] [--name <name>] [--version <version>] <path>
fe new <path>creates an ingot:<path>/fe.toml(ingot config)<path>/src/lib.fe(conventional root module; missingsrc/lib.feis treated as an empty root module)
fe new --workspace <path>creates a workspace root with<path>/fe.toml.
Safety checks:
- Refuses to overwrite an existing
fe.tomlorsrc/lib.fe. - Errors if the target path exists and is a file.
Workspace suggestion:
- After creating an ingot, if an enclosing workspace is detected,
fe newmay print a suggestion to add the ingot path to the workspace’smembers(ormembers.mainfor grouped configs). - If workspace member discovery fails, it prints a warning:
Warning: failed to check workspace members: <details>
Generates shell completion scripts for fe.
fe completion <shell>
This writes the completion script to stdout. Supported shells are determined by clap_complete (commonly: bash, zsh, fish, powershell, elvish).
Generates an LSIF index for code navigation.
fe lsif [-o, --output <path>] [path]
- If
pathis omitted, it defaults to.. - If
--outputis omitted, the index is written to stdout.
Generates a SCIP index for code navigation.
fe scip [-o, --output <path>] [path]
- If
pathis omitted, it defaults to.. --outputdefaults toindex.scip.