nem.toml
The manifest declaring a directory's packages and environment variables. Meant
to be committed. nem finds it by walking up from the current directory to
the nearest nem.toml, and nem use creates one in the current directory
when there is none. The global twin lives at $NEM_HOME/nem.toml; inside a
project, its packages and [env] apply underneath the project's.
Example
[tools]
'dev:kubectl' = '1.36.3'
go = '1.27.0'
[env]
KUBECONFIG = '$HOME/.kube/config'This is the same shape nem use writes: keys sorted, bare version strings,
single quotes. go has no catalog prefix, so it resolves from the first
catalog (in configuration order) that carries it. 'dev:kubectl' pins the
package to the catalog named dev — TOML requires quoting a prefixed key.
[tools]
- A key is
[<catalog>:]<name>. Without a prefix, lookup walks the configured catalogs first-match-wins, in configuration order, skipping disabled ones. With a prefix, lookup uses that catalog only; a prefix naming a disabled or unknown catalog is an error. - A package name appears at most once, prefixed or not — so
nem unuse <pkg>is never ambiguous about which entry to remove. - Values are the exact version string as it appears in the catalog. There's
no
"latest"keyword and no ranges, and no empty value:nem lockrejects a package declared without a version andnem syncwarns about it. - Parsing is strict: an unknown table or key — a typo like
[tool]— is an error, not a silent skip. - Only direct intent lives here. The resolved closure, including dependencies, lives in nem.lock.
[env]
Names must match ^[A-Za-z_][A-Za-z0-9_]*$; one that does not is a parse
error. A name on nem's
reserved list is
accepted here but dropped with a warning when the environment is composed.
A value supports $VAR/${VAR} expansion (unset expands to empty) and $$
for a literal $, performed by nem itself, not the shell. See
Environment variables for expansion
and precedence.
How versions resolve
nem use, nem unuse, nem update, and nem lock re-resolve the whole
manifest every time — there's no sticky state carried over from the last
resolution. A nem use or nem update without a version takes the first
entry of the package's catalog versions list, which catalogs keep newest
first. Across platforms and dependents, one version per package wins: the
first entry of that list that satisfies every requirement, stepping further
down only when a dependency needs an older or compat-constrained one.
Requirements that no single entry satisfies fail to resolve, and a package
you pinned directly is never silently overridden by a dependency that wants
something else — that's a pin-conflict error instead. nem sync never
resolves; stability between manifest changes comes from nem.lock, not
from the resolver remembering anything.