CLI reference
The commands and flags a user reaches for, with a pointer to per-command help.
The commands and flags a user reaches for. agni <command> --help always prints the
authoritative, up-to-date detail. This page is the map. The reader for a file is chosen by
its extension, so you pass the design file directly and never name a format.
Where a <file> is expected you may also name a design folder, one holding a design.yaml
that declares which file is the design’s entry. Agni then reads that entry and picks up the
companion views the descriptor lists, so a netlist’s connectivity rules and a
board’s copper rules run from one argument. See
Projects and designs.
The commands fall into four groups by what each one takes as its argument.
Commands ¶
stats <file> ¶
Print component, section, and net counts for one design. Run it first to confirm the tool read your design the way you expect before trusting anything downstream.
check <file> ¶
Run the rule catalog and report findings. The workhorse. See Checks and reports.
| flag | what it does |
|---|---|
--rule <name> |
run only this rule (repeatable) |
--tag <key>=<value> |
run only rules with this tag, e.g. --tag category=power (repeatable) |
--format <fmt> |
text (default), markdown, json, csv, report, or html. csv emits one row per finding with a stable header, for a spreadsheet-driven review. html is the verdict report as a self-contained page and turns --verdicts on by itself |
--verdicts |
report the CONSIDERED SET instead of the violations: what each rule concluded about every subject it looked at, with the evidence for a pass. A separate table, so plain --format csv is unchanged. Honours --format text/csv/json/html. Only rules that state one contribute, and a rule absent from the output is declining to say rather than reporting that it considered nothing |
--server <where> |
where the links this run mints should point. Empty (the default) mints none, which is what a pipeline wants. self starts a viewer on a free port, serves THIS run’s mount table, and blocks until Ctrl-C, so a link cannot disagree with what was read and a mount minted for the run is linkable like any other. self:PORT does the same on that port and fails if it is taken. A URL names a server someone else is running, which is asked whether it serves the same mounts from the same roots |
-o, --out <file> |
write the --format output to this file, - for stdout (the default), so a report needs no shell redirect and composes with a pipe. Distinct from --results-out: this writes what you would have redirected, that writes the check-result DOCUMENT agni results re-renders. The written-file note goes to stderr |
--fail-on <sev> |
exit non-zero when a finding sits at or above error / warning / info. This is the severity axis. For the coverage axis, see review --fail-on-outcome and --min-answered below |
--conventions <file> |
compose a naming-convention config into the run (see Naming conventions) |
--profile-path <dir> |
compose a directory of YAML interface-profile declarations into the catalog, namespaced profile-overlay/ (see Interface profiles) |
--params <dir> |
load a datasheet parameter set, enabling datasheet-backed rules (see Datasheets) |
--board-path <file> |
attach a board that is NOT a declared companion of the design, so board-tier rules resolve instead of finding no copper |
review <file>... ¶
Run a review checklist over one or more designs and report one outcome per item. Where check asks
“what is wrong with this board”, review answers “which of our questions did we actually answer”.
Its outcome vocabulary distinguishes a check that passed from one that never ran.
| flag | what it does |
|---|---|
--checklist <file> |
the review manifest (YAML) declaring areas and their items. Optional when the design belongs to a project that declares one; passing it overrides the project’s |
--conventions <file> |
a naming-convention config, whose rules join the catalog and whose lexicon reaches the design read |
--profile-path <dir> |
interface-profile declarations added to the catalog |
--params <dir> |
a datasheet parameter set, enabling datasheet-backed items |
--intent-path <file> |
a design-intent declaration, so intent-bound items resolve instead of reading needs-design-intent |
--board-path <file> |
a board-geometry file attached to a netlist design, so board-tier items resolve instead of n/a |
--coverage |
a per-area rollup of how many items each area decided, instead of the per-item report. Renders as markdown only, so it refuses an explicit --format; the --format html page carries the same rollup in its header |
--ratified-floor <n> |
datasheet-confidence floor below which a fail reports as provisional (default 0.9) |
--fail-on-outcome <list> |
exit non-zero when any item sits at one of these outcomes, e.g. fail or fail,provisional. Off by default |
--min-answered <n> |
exit non-zero when fewer than n items produced an answer. Off by default |
--format <fmt> |
markdown (default), json, or html. html is the checklist as a self-contained page: areas and items in the manifest’s order, every finding per item rather than the markdown cell’s first three, and one link per finding when --server is given. One design at a time, since a page’s title, hash and links all name one design |
--server <where> |
where the links this run mints should point. Empty (the default) mints none, which is what a pipeline wants. self starts a viewer on a free port, serves THIS run’s mount table, and blocks until Ctrl-C, so a link cannot disagree with what was read and a mount minted for the run is linkable like any other. self:PORT does the same on that port and fails if it is taken. A URL names a server someone else is running, which is asked whether it serves the same mounts from the same roots |
--results-out <file> |
also write the run as a self-contained check-result document |
-o, --out <file> |
write the --format output to this file, - for stdout (the default), so a report needs no shell redirect and composes with a pipe. Distinct from --results-out: this writes what you would have redirected, that writes the check-result DOCUMENT agni results re-renders. The written-file note goes to stderr |
--render <dir> |
also write an annotated schematic SVG per design, each finding highlighted in place |
--companion <file> |
a geometry file to draw --render images on, joined to netlist findings by net name |
Gating a pipeline on a review ¶
check --fail-on and the two flags above gate on different axes, and the difference is not a matter
of taste. --fail-on pivots on finding severity, which states how bad an answer was.
--fail-on-outcome and --min-answered pivot on item outcome, which states whether the question
was answered at all. A checklist can stop answering four of its items with its failure count unchanged
at zero, and no severity predicate can see that.
--min-answered counts the items that produced an answer: pass, fail, provisional, and
computed-n/a. It is deliberately stricter than the covered count the report also shows.
Why answered is stricter than covered
Covered subtracts only not-automated, which moves when a rule leaves the catalog. It does not move
when a rule is present and its inputs are gone. A datasheet-backed item whose corpus moved reads
not-applicable, which still counts as covered and does not count as answered. That gap is the
regression worth gating on.
A provisional does not trip --fail-on-outcome fail. It is a failure resting on mock or
below-floor datasheet data, so gating on it by default fails a pipeline on data quality rather than on
design quality. Name it explicitly when you want it: --fail-on-outcome fail,provisional.
With several designs the gate reads every one of them, and the first design to violate it stops the run. Exit codes:
| code | meaning |
|---|---|
0 |
the run completed and no gate tripped |
2 |
a gate tripped |
1 |
the run itself failed: an unreadable design, an invalid manifest, an unknown outcome name |
check --fail-on uses the same codes, so one CI script can treat every gate in this CLI alike: 2
means the design has problems, 1 means the tool could not do its job.
Machine configuration: agni.yaml ¶
The flags that say where bytes are can live in a file instead of on every command. agni.yaml is
searched for beside the working directory, upward a few levels, then in $XDG_CONFIG_HOME/agni/
(or ~/.config/agni/). The first one found wins outright.
# agni.yaml
mounts:
boards: /srv/boards
shared: /srv/shared
symbol_paths:
- /usr/share/kicad/symbols
web_dir: /usr/share/agni/web
native_tools:
- kicad-cli
Paths are resolved against the directory you ran from, and a mount naming a directory that is not there is an error, so a file meant to serve every working directory wants absolute paths.
A run says which file it took config from, on stderr. An explicit --mount, --symbol-path,
--web-dir or --enable-native wins outright rather than merging: naming a mount table is answering
for the whole table.
web_dir is where the viewer’s own assets are. It belongs to this tier because it locates bytes and
cannot change what a run concludes, and a wrong value fails at startup rather than quietly. A repo
checkout needs nothing here, since the default web already resolves per-directory; an installed
binary run from a design folder has no relative answer, and that is what this and AGNI_WEB_DIR are
for.
native_tools is the file form of serve --enable-native, and belongs here on the same test: it
says which golden renderers EXIST, and naming one that is not installed fails at the point of use
with the tool’s own name in the error. Only serve consumes it, though every command reports having
read it, because the note says what the file supplied rather than what the command went on to use.
It carries only tier-1 config, and that is a boundary rather than a to-do. Naming conventions, interface profiles, seeded parameters, design intent and a review checklist belong to a project instead. See Projects and designs.
Why analysis config cannot live here
Those five decide what a design is checked against, so they belong where they are scoped to the
designs that declared them. A machine-wide conventions file applying to every design a CLI opened is
the bug per-design config fixed. Unknown keys are rejected, so reaching for conventions: here is
told no rather than quietly becoming a global analysis tier.
start <design-file> [dir] ¶
Scaffold a review project around an existing design file, so the commands above can stop taking
flags. dir defaults to the current directory.
agni start boards/gateway.edn ./gateway-review
gateway-review/
├── project.yaml declares the project's id
├── conventions.yaml stub — your team's naming vocabulary
├── review.yaml seeded from the shipped catalog
└── designs/gateway/
├── design.yaml names the entry and its companion views
├── gateway.edn copied
└── gateway.kicad_pcb copied, declared as a companion
After it, agni check gateway-review/designs/gateway and agni review … resolve the whole
configuration from the descriptors.
| flag | what it does |
|---|---|
--name <id> |
the project’s declared id; defaults to the target directory’s name, lowercased with anything outside [a-z0-9._-] replaced by - |
--title <text> |
the human-readable label; defaults to the id |
The design is copied, and the project owns its copy. Editing the original afterwards does not
reach the project. The command prints what it copied and from where, and the generated design.yaml
records the origin in a comment.
Companions are detected, not guessed at. A sibling is declared a companion only when it shares
the design’s stem and carries schematic geometry or a board. That excludes a later revision
(gateway-rev-b.edn, a different stem and a legitimate analysis source of its own) and a second
netlist encoding (gateway.edf, same stem but no view to contribute). Check the generated
design.yaml and edit it: membership is declared per file precisely because it cannot be inferred
reliably.
Nothing is overwritten. An existing file stops the command and is named, and every planned write
is checked before any write happens, so a refusal leaves nothing half-created. Pointing at a folder
that already holds a project.yaml adds the design to that project rather than nesting a second
one.
intake <file> ¶
Extract a sanitized summary of a design: counts, class census, rail voltages, anomalies, and the parts list. It carries the shape of the design and structurally cannot carry a net name or a connection, so it is safe to hand to someone who should not see the design itself.
| flag | what it does |
|---|---|
--params <dir> |
a parameter set, which populates the MPN and datasheet-gap columns. A design inside a project reads the params/ that project declares and needs no flag; this names a corpus for a design that belongs to none |
--parts <view> |
types (BOM by distinct part type, default) or full (per-component AVL) |
--format <fmt> |
md (default) or json |
params <mpn> ¶
Print one part’s whole datasheet record: its source documents, every parameter with its limit kind, bounds, test conditions and citation, the pins the datasheet declares, and any constraints between them.
This is the record behind a query answer. The datalog relations carry what a query can bind
(param, param.range, param.typ, param.pin); the conditions a value is valid under, the pin
bindings, the full provenance and the verification state live here.
It needs no design, because a spec library is not a design. Name a corpus with --params, or name a
design with --design so the project that design belongs to supplies its own params/.
A parameter someone has verified reports whether that still means anything. When the corpus has moved
to a later revision of the document, the row reads stale and names BOTH revisions: the one that was
checked, and the one held now. That pair is the re-confirm task; a pair of content hashes is not.
| flag | what it does |
|---|---|
--params <dir> |
the datasheet corpus to read. A project’s own params/ wins over this when --design names a design in that project |
--design <path> |
a design whose project supplies the corpus, for a part seeded in a project rather than a loose directory |
--format <fmt> |
text (default) or json. json emits the PartSpec itself, so a script binds to the same message the viewer carries |
agni params LM1117 --params seed/
agni params LM1117 --design designs/gateway/gateway.kicad_sch
agni params LM1117 --params seed/ --format json
results <file> ¶
Render a check-result document written earlier by check --results-out or review --results-out.
The document is self-contained, so this works with the design deleted.
| flag | what it does |
|---|---|
--format <fmt> |
the same output formats the live run offers |
--compare <file> |
compare against another results document and print the three-way entity split instead of a report |
import-results <report.json> ¶
Read another tool’s check report (a kicad-cli DRC or ERC JSON report) as a check-result document,
so it can be rendered and compared against a run of this engine with results --compare.
query <file> <query> ¶
Search the design as data with an ad-hoc datalog query. Each answer prints with its provenance. See Querying your design.
| flag | what it does |
|---|---|
--params <dir> |
a parameter set, to query datasheet facts (param(...)) |
--conventions <file> |
apply a naming convention’s LEXICON to the read, so rail/feedback/pin.type answer under your project’s vocabulary (see Naming conventions). The rules half is unused here: a query runs no rules |
--board-path <file> |
attach a separate board export so the board.* relations have facts; without it they are empty |
--format <fmt> |
text (default aligned table), csv (spreadsheet-safe, header row, table only), json (protojson of the RunQueryResponse the RunQuery rpc returns, so a script and a client parse one shape; it carries the column kinds, and echoes the query and the design so an answer names its own question), markdown or html (a view: the question above its answer). See Taking a view out of the tool |
--title <name> |
name the view, used as the heading in --format markdown and html |
-o, --out <file> |
write the --format output to this file, - for stdout (the default). A view is a thing to hand to someone, so it wants a filename rather than a redirect. The written-file note goes to stderr |
trace <file> ¶
Follow a signal from one pin to another and print what it goes through. See Following a signal.
The walk crosses series pass elements (resistors, inductors, ferrites, fuses), because those split a net without breaking the path. A capacitor is a DC block and is never crossed. A rail or a plane may be where a route ENDS and is never passed through, so a trace onto a supply reports the supply and stops there.
Both endpoints are pins, named <ref-des>.<pin>. An endpoint that names nothing the design has is an
error and exits non-zero, because a pin spelled wrong in a declaration is not the same answer as two
pins that are not connected.
| flag | what it does |
|---|---|
--from <ref.pin> |
the pin to start at, e.g. U7.3 |
--to <ref.pin> |
the pin to end at |
--hops <n> |
how many series crossings to search through (default 6). Unlike the protection radii this is a search budget rather than an electrical claim, and every answer states the value it rests on, so a no-route can be re-asked wider |
--format <fmt> |
text (default), or json, which emits the same Trace message the TraceDesign rpc returns, in protojson, so a script reading the CLI and a client reading the API parse one shape |
--server <where> |
where the links this run mints should point. Empty (the default) mints none, which is what a pipeline wants. self starts a viewer on a free port, serves THIS run’s mount table, and blocks until Ctrl-C, so a link cannot disagree with what was read and a mount minted for the run is linkable like any other. self:PORT does the same on that port and fails if it is taken. A URL names a server someone else is running, which is asked whether it serves the same mounts from the same roots |
-o, --out <file> |
write the --format output to this file, - for stdout (the default), so a report needs no shell redirect and composes with a pipe. Distinct from --results-out: this writes what you would have redirected, that writes the check-result DOCUMENT agni results re-renders. The written-file note goes to stderr |
--render <file.svg> |
also draw the answer: the route’s nets and the parts crossed, on the design’s own schematic where it has one and on an auto-layout of its netlist where it does not, which it says so you never take the second for the first. A no-route draws too, marking the two nets that fail to join |
diff <old> <new> ¶
Structural diff between two revisions, over the IR. See Comparing revisions.
| flag | what it does |
|---|---|
--format <fmt> |
text (default human summary), json, or csv (one row per change, with a change_class column naming which kind it is) |
render <file> ¶
Draw a design’s schematic or board view.
| flag | what it does |
|---|---|
--layout <name> |
faithful (default, the design’s own geometry) or an auto-layout (force, grid, layered, orthogonal, stress) computed from the netlist |
--format <fmt> |
svg (default) or pack (for the WebGL viewer) |
-o <file> |
output path |
open <design> ¶
Serve one design and print the URL that shows it, so a board can be looked at from the directory it
lives in. Binds loopback on a free port and serves only that design (and its project, where it has
one). It also prints a ready-made agni check … --server … line, which carries --mount because a
mount is minted per process and a second agni would not know this one.
agni check <design> --server self is the same idea from the other end and needs no second command:
one process reads the design and serves it, so the links resolve without anything being declared.
open remains the way to look at a board without running anything over it.
| flag | what it does |
|---|---|
--addr <addr> |
listen address; empty picks a free port on loopback |
--web-dir <dir> |
the viewer’s own assets, as for serve |
serve ¶
Host the browser viewer and the web API on one port. Build the web bundle first.
| flag | what it does |
|---|---|
--addr <addr> |
listen address (default :8080) |
--web-dir <dir> |
the viewer’s OWN assets, not designs (default web; then web_dir in the nearest agni.yaml, then AGNI_WEB_DIR) |
--mount <name>=<path> |
expose a design folder in the file browser (repeatable) |
--theme <name> |
render palette: default or dark |
--profile-path <dir> |
compose interface profiles into the catalog every rule-running surface uses, the check panel included (see Interface profiles) |
--review-store <dir> |
a writable directory that keeps review runs, created if absent; without it the review endpoints report that this server stores none (see Running the server) |
healthcheck ¶
GET /healthz on a running server and exit 0 only on a 200. It is what the container image’s
HEALTHCHECK runs, so the image needs no curl or wget: the runtime is debian-slim, which ships
neither, and pulling one in to make a single HTTP request adds surface area to every deployment for
the sake of a probe the binary can make itself. Running the server covers
where it fits in a container.
| flag | what it does |
|---|---|
--addr <host:port> |
the server to probe (default localhost:8080). serve’s own default is :8080, which binds every interface and is not a dialable host, so the probe names loopback explicitly |
--timeout <duration> |
how long to wait for a response (default 3s) |
It asks one question and interprets nothing: whether the server answers 200 on /healthz, which is
the whole question a restart policy acts on. It reads no other route and does not look at the body.
emit <in> [out] ¶
Convert any design the tool reads into IPC-2581 or an EDIF netlist (stdout if out is omitted).
A conversion is A -> IR -> B with the IR as the pivot, so the input format does not have to match
the output one.
| Flag | Meaning |
|---|---|
--format <fmt> |
ipc2581 or edif. Omitted, the OUT file’s extension decides: .edn, .edf and .edif write EDIF, everything else writes IPC-2581. Writing to stdout has no extension to read, so it writes IPC-2581 |
agni emit board.kicad_sch board.edn
Both writers are lossy-bounded and state their limits, so a conversion carries what the IR holds rather than what the source file held. The EDIF writer emits the netlist tier only. A hierarchical design writes out FLAT, because the reader scopes a read to the design’s top cell and drops every sub-cell’s contents before the writer sees it, and array bus declarations are not written because the diagnostic they reach the IR as does not record which port declared them.
An EDIF instance needs a name, because a net anchors each of its pins with an instanceRef naming
one. The writer prefers the id the source used, falls back to the ref-des (with the section index
appended for a multi-gate part), and breaks a collision by suffixing, so the names are unique across
the file and stable between two exports of the same design. Neither seed is unique by itself: two of
the readers record no instance id at all, and a KiCad symbol placed on two sheets of one hierarchy
carries the same id under two ref-des.
One connection shape has no instance to name. A KiCad power symbol or PWR_FLAG reaches the IR as a
connection on #PWR01 while the component list stays physical, so there is nothing in the contents
to anchor it to and minting one would invent a component the source never had. Those are written as
bare portRefs and read back as connections with no component, which is what an EDIF no-ref
connection has always been. Every other connection is anchored.
The output is written to be read by tools other than this one, which is a stricter target than
round-tripping through agni’s own reader and was not always met. Three things follow from it. The
libraries come first and the design node last, so a reader that resolves a reference the moment it
meets one is never asked to follow a forward reference. A portRef names the cell’s PORT rather than
the pin’s physical designator, with the mapping between them carried per instance in a portInstance
table, which is the construct EDIF has for it. And an identifier holds no character a reader rejects,
with anything richer moved into the quoted display half of a (rename ...).
Anything the file references, it also declares. Where a part type declares fewer pins than the
netlist connects, the cell’s interface is completed from the connections; where no part type exists
at all, the cell is declared too, and so is the top cell the contents live in. A board file is the
case that needs all three, because copper carries footprints, pads and nets and never says what the
part is. This states what the cellRefs and portRefs in the same file already name rather than
inventing a part, and it is bounded the way a netlist is: a pin on no net is invisible, so a cell
completed this way carries the pins the design uses and not the pins the part has.
A part named gateway:CONN4 inside a library called gateway is written as cell CONN4, since
EDIF’s own library scoping already carries the qualification and a reader that treats the qualified
form as one name rejects the colon in it. The prefix stays in the IR, where it selects the symbol
file an external reference resolves from, so this is an encoding choice in the writer and not a
rename. A design taken out through EDIF and read back therefore names that part CONN4 in library
gateway, which is the same fact with the redundancy gone.
.eds is refused rather than treated as EDIF. It is a dual-capability format, a netlist plus the
faithful schematic geometry beside it, and there is no schematic writer yet, so emitting one from
the netlist writer alone would produce a file claiming to carry a drawing that carries none.
version ¶
Print this build’s identity: the version, the commit it was built from, and the Go toolchain and
platform. agni --version prints just the first line.
This is the same string a results document records as its producer, so a report archived months ago and the build in front of you can be compared directly. Worth capturing alongside any report you keep.
agni v0.1.1
built: 2026-08-10T14:46:21Z
go: go1.26.4
platform: darwin/arm64
A build from a source clone reports the commit rather than a release (b020fea02643, suffixed
+dirty when the tree had uncommitted changes), because that is what it honestly is.
Global flags ¶
| flag | what it does |
|---|---|
--symbol-path <dir> |
directory to search for symbol files (.sym for xschem/gEDA, .kicad_sym for KiCad) so schematics that name rather than embed their symbols resolve to pin-level nets and faithful artwork. Repeatable, searched recursively, and the schematic’s own directory is always searched. Defaults to AGNI_SYMBOL_PATH (colon-separated) when the flag is absent, which is how the container image supplies its bundled libraries to every subcommand. |
--mount <name>=<path> |
expose a folder as a named mount (repeatable). Every command takes it, not just serve. An artifact is addressed as mount://<mount>/<path>, and with a matching --mount the CLI and a server produce identical URIs for the same design, so a review created either way records the same design and the two are directly comparable. Without it the CLI mints a mount per argument, rooted at the enclosing project when there is one, so agni check some/board.edn still works with no configuration. You may also pass a full mount://... URI in place of a path. |
--as-named |
read exactly the file named, even when its design.yaml declares it a companion view of a different entry. Without it, analysing a declared companion (a schematic export, a board) reads the design’s entry instead, because a companion is a view of the design rather than a second source of it. Reach for this when reading a view as a netlist is the point, such as checking that two views of one design still agree. |
--version |
print the build version and exit. |
Advanced and developer commands ¶
agni also has native (render/open with the design’s own EDA tool), validate
(reader-health smoke over many files), census, and derive (datasheet extraction). validate is
worth knowing as a user: point it at a folder of exports and it reports which of them this tool can
actually read. The rest sit closer to the engine and are covered in the developer docs.
Which formats are read ¶
The reader is picked by file extension (case-insensitive): EDIF netlists (.edn/.edf/.edif) and
geometry (.eds), KiCad (.kicad_sch/.kicad_pcb/.kicad_pro), IPC-2581 (.xml/.cvg), Telesis
netlists (.tel, netlist only, no geometry), and .sch (xschem / gEDA / legacy KiCad, sniffed by
header).
A schematic that references external symbols resolves them by one of three routes, in the order they
are searched: its own sym-lib-table for a KiCad project, the symbols library its design descriptor
declares (defaulting to symbols beside the descriptor), and any --symbol-path directory. A design
that belongs to a project needs no flag, and the flag stays the escape hatch for a library the
project does not know about.