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.
Builds use the Sonatina codegen pipeline and generate EVM bytecode directly.
fe build [--standalone] [--contract <name>] [--optimize <level>] [--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.
Optimization is controlled by --optimize <level> / -O <level>.
Defaults:
--optimizedefaults to1.- Supported levels:
0: nones: size-oriented1: balanced (default)2: aggressive
For each built contract, fe build writes (depending on --emit):
<out>/<contract>.bin(deploy bytecode, hex + trailing newline)<out>/<contract>.runtime.bin(runtime bytecode, hex + trailing newline)<out>/<contract>.abi.json(Solidity-compatible ABI; only when--emit abi)<out>/<contract>.metadata.json(Solidity-standard contract metadata for verifiers like Sourcify; only when--emit metadata)
For the Sonatina backend, .bin is the init section bytes and .runtime.bin is the runtime section bytes.
The metadata.json artifact follows the Solidity Contract Metadata schema (version: 1), adapted to Fe. It is a complete, deterministic recompilation input: rebuilding with the same compiler version, sources, and settings reproduces the same bytecode. Its shape:
{
"version": 1,
"language": "Fe",
"compiler": { "version": "<fe version>", "commit": "<git hash, if available>" },
"sources": {
"<source path>": { "keccak256": "0x...", "content": "..." }, ...
},
"settings": {
"compilationTarget": { "<source path>": "<ContractName>" },
"optimizer": { "level": "0|1|2|s" },
"arithmetic": "checked|unchecked",
"dependencyArithmetic": "defer|checked|unchecked",
"evmVersion": "osaka",
"ingots": [
{
"name": "<ingot>", "version": "<semver>|null", "namespace": "<prefix>",
"arithmetic": "checked|unchecked", "dependencyArithmetic": "defer|checked|unchecked",
"dependencies": { "<alias>": "<namespace|std|core>" }
}, ...
]
},
"output": { "abi": [ ... ] }
}
<source path> is the file's path relative to its owning ingot root (e.g. src/counter.fe); for a
standalone .fe target, it is the file's basename. sources contains every .fe file of the
contract's owning ingot plus all transitive dependency ingots, the latter namespaced by the
ingot's dependency alias (e.g. mylib/src/lib.fe). The bundled core and std are excluded
because they are pinned to compiler.version (a verifier re-runs the same compiler, which ships
them). settings.ingots[] records, per non-builtin ingot, what a verifier needs to regenerate its
fe.toml and src/ layout.
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 Sonatina IR 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] [--optimize <level>] [--trace-evm] [--trace-evm-keep <n>] [--trace-evm-stack-n <n>] [--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).
--trace-evm,--trace-evm-keep,--trace-evm-stack-nenable EVM opcode tracing.--debug-dir <dir>writes debug outputs (traces) into a directory.--call-traceprints a normalized call trace for each test.
- 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).
Optimization flags:
- Optimization is controlled by
--optimize <level>/-O <level>. - Supported levels are
0,s,1, and2.
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.