envy for agents
This page is intentionally not prose. It is a low-token, maximal-density summary for AI coding agents. Humans should read the rest of the manual.
envy = per-project package manager. Lua manifest envy.lua at project root pins
everything incl. envy itself. No install step, server, registry, lockfile.
Committed bootstrap <bin>/envy + envy.bat downloads pinned envy on first run.
Current release 0.4.12. (0.4.N+) = minimum version, check the project's
@envy version. Unmarked = 0.3.0+. Newest: imported PACKAGE_DEPOTS inert
(0.4.12), bundle/SETUP pair rows = package rows (0.4.10-11), archives (0.4.9).
Release assets under
https://github.com/envy-package-manager/envy/releases/download/v<ver>/:
envy-{linux,darwin}-{x86_64,arm64}.tar.gz, envy-windows-{x86_64,arm64}.zip,
SHA256SUMS. Archived binary already 0755.
manifest + spec
-- @envy version "0.4.12"
-- @envy sha256sums "9f2c...e10b"
-- @envy bin "bin"
-- @envy deploy "true"
BUNDLES = {
["first-party"] = {
identity = "envy.package-specs@r5",
source = "https://github.com/envy-package-manager/package-specs.git",
ref = "<full sha from `envy git-resolve <url> main`>",
},
}
VENDOR_ROOT = "third_party/vendored" -- only if something below vendors
PACKAGES = {
-- from a bundle
{ spec = "envy.cmake@r1", bundle = "first-party", options = { version = "4.4.3" } },
{ spec = "envy.ninja@r0", bundle = "first-party", options = { version = "1.13.2" } },
-- from a URL, pinned
{ spec = "acme.mytool@r0", source = "https://example.com/specs/mytool.lua",
sha256 = "24ad...37be" },
-- local file, with options, copied into the tree for a GN/Bazel build
{ spec = "local.nanocobs@r3", source = envy.abspath("envy/nanocobs.lua"),
options = { version = "0.4.1" }, vendor = true },
}
- header: before first code line, one per line, exactly
-- @envy <key> "<value>".binrequired. - bundle contents NOT discoverable from envy (no
envy bundle list): read the bundle repo (package-specs:specs/+ README table).ref= full sha. Wrong@rev= hard error, no fallback. - entry
sha256pins the SPEC file, not what the spec fetches. Bundle entry: nosha256/ref(BUNDLEScarries the pin),vendorok (0.4.6+).
IDENTITY = "acme.mytool@r0"
PLATFORMS = { "darwin", "linux", "windows" }
EXPORTABLE = true
-- plain Lua, file-scope locals ok. Key hashes by EVERY option the payload varies with.
local hashes = {
["libusb/hidapi"] = {
["1.4.0"] = { ["darwin-arm64"] = "24ad...37be", ["linux-x86_64"] = "8c91...02af" },
},
}
OPTIONS = {
version = { required = true, type = "string" },
repo = { required = true, type = "string" },
}
-- 0.4.2+. extra label on progress rows, for one spec instantiated many times
DISPLAY = function(opts) return opts.repo end
FETCH = function(tmp_dir, opts)
local ext = (envy.PLATFORM == "windows") and ".zip" or ".tar.gz"
return {
source = "https://github.com/" .. opts.repo .. "/releases/download/v"
.. opts.version .. "/mytool-" .. envy.PLATFORM_ARCH .. ext,
sha256 = hashes[opts.repo][opts.version][envy.PLATFORM_ARCH],
}
end
STAGE = { strip = 1 }
-- product NAMES are manifest-global, duplicate = error: derive from an option if
-- instantiated >1x. Runs BEFORE OPTIONS validates: guard every read.
PRODUCTS = function(opts)
local repo = opts.repo or ""
return { [repo:match("[^/]+$") or "mytool"] = "bin/mytool" .. envy.EXE_EXT }
end
- OPTIONS keys exactly
required,type(string|int|float|boolean|table|list|semver),range(">=1 <=64"),choices(array),validate(fn:nil/trueok,falsefail, string = message shown). OrOPTIONS = functionvalidator. Nodefault: omitted option =nilin every verb. envy.PLATFORMdarwin|linux|windows,envy.ARCHarm64|x86_64,envy.PLATFORM_ARCHdarwin-arm64etc,envy.EXE_EXT.exe|"".
First binary (only step init cannot bootstrap):
curl -fsSL -O https://github.com/envy-package-manager/envy/releases/latest/download/envy-darwin-arm64.tar.gz
tar -xzf envy-darwin-arm64.tar.gz -C /tmp
/tmp/envy init . ./bin --pin-sums --deploy=true # writes envy.lua + bin/, then delete /tmp/envy
Commit envy.lua, WHOLE bin dir, .luarc.json (machine-independent,
~/${env:...} placeholders). init gitignores .envy/, .envy-cache-*.
Existing project: NO setup step. ./bin/<tool> or ./bin/envy run <cmd>
bootstraps envy + installs that tool's subgraph on demand. Windows
bin\envy.bat, bin\<tool>.bat. Shell hook puts bin dir on PATH, so bare
envy sync / cmake (the form the rest of the docs use). CI/scripts: explicit
path.
model
- manifest globals:
PACKAGES(required),BUNDLES,PACKAGE_DEPOTS,DEFAULT_SHELL,VENDOR_ROOT. Real Lua: conditionals,envy.import,envy.extendlegal. - directives:
versionpins envy.sha256sumspins release checksums, needsversion.binREQUIRED, relative to manifest, no drive / leading sep /~/$/%(./..legal,deployjudges escape).mirror= release mirror.deploy "true|false"default FALSE: without itsyncdeploys nothing + warns.root "true|false"superproject boundary, default true.cache-local= cache tree in project, tuned bycache-mode,state-dir(0.2.0+).cache-posix/cache-win= errors.schema. - spec = one package recipe.
IDENTITY = "ns.name@rev"required,@revversions the SPEC not the payload,local.*= project-local. Package =(identity, options, platform). Canonical keyns.name@rev{["k"]=v,...}, keys quoted + sorted. Option tables: all string keys or contiguous1..n, no functions, any depth. - verbs
FETCH → STAGE → BUILD → INSTALL, plusSETUP(named CHECK/INSTALL pairs). See verb forms. - products:
PRODUCTS = { cmake = "bin/cmake" }. Consumers name the product, not the identity:envy product cmake,envy.product("cmake"),./bin/cmake. Non-executable (header, data file):PRODUCTS = { doctest_h = { value = "doctest.h", script = false } }= no wrapper, path viaenvy product doctest_h. - reproducibility: no lockfile. Pins =
@envy version+sha256sums, per-sourcesha256, gitreffull sha (envy git-resolve <url> <ref>). Unhashed fetch re-downloads every run. - re-exec: every manifest-aware command compares own version to
@envy versionFIRST, mismatch → replaces itself with that release. Invoked binary ≠ acting binary,envy --version≠ project version: read the header. Lookup: project cache tree → user-wide tree (see cache) → download from mirror to temp, checked againstsha256sumsif pinned, child installs itself into cache. ONE hop (ENVY_REEXEC=1), argv minus envy-choosing options (init --envy-version). Skipped: no pin, match, self0.0.0(dev build),ENVY_NO_REEXEC. Hard error: resolved cache mode (cache-local/cache-mode/state-dir/envy cache --localmarker) + pin< 0.2.0. Never re-exec:use,cache,version(read header as text).--trace=file:spans the handoff (0.4.4+). - bin dir:
envy,envy.bat, one wrapper per product (+.battwin under--platform all). Fresh clone:./bin/cmake→bin/envy→ pinned envy → install. Wrappers resolve at call time (exec "$("$ENVY_SCRIPT_DIR/envy" product cmake)" "$@"), never stale. Each prepends own bin dir to PATH (siblings callable), exportsENVY_PROJECT_ROOT(root manifest only,root "false"keeps caller's). Schema4(envy-managed schema "4", POSIXset -Eeuo pipefail). Bootstrap restamped on content change. - ownership: envy touches only bin files containing
envy-managed(substring). Unmarked = skipped (--strict: error), so your ownbin/gnowns that name for good (supported way to wrap/pre-step).envy/envy.batalways restamped, never pruned. Filteredsyncprunes marked wrappers outside the filtered subgraph. - deploy walk-back: ROOT manifest whose
@envy binwalks up to a DIFFERENTenvy.lua= error (..in bin,.gitbetween,--manifestoutside bin tree). Nothing found / not namedenvy.lua= warn.root "false"skips it (superproject restamps submodule bin byte-identically). Stamping from an unpinned version (dev build,ENVY_NO_REEXEC) warns. - manifest discovery: walk up from anchor.
--manifest <path>(no walk) >--project <dir>> CWD.--subproject= nearest to CWD even under--project, firstenvy.lua, ignores@envy root.envy runalso anchors on-- <script>or a first arg naming an existing file,--projectwins. Bootstrap + wrappers inject--project <own dir>before argv (typed one still wins), so../B/bin/uv run x.pyacts on B.
cache
- content-addressed, safe to delete. Default user-wide shared, or a project-local
tree. Root:
--cache-root/$ENVY_CACHE_ROOT(absolute) >.envy-cache-local/.envy-cache-sharedmarker (envy cache --local/--shared) >@envy cache-mode>@envy cache-localpresent > default (~/Library/Caches/envy,$XDG_CACHE_HOME/envy,%LOCALAPPDATA%\envy). - layout
envy/<ver>/{envy,envy.lua},envy/latest,packages/,specs/,shell/,locks/. Entryidentity/<platform>-<arch>-blake3-<hash>. First-run stderr notice for LOCAL tree only. Never prompts. - local tree reads user-wide, never writes it: launchers/re-exec try
<project cache>/envy/<ver>/envythen<user-wide>/envy/<ver>/envy, the second ONLY without@envy sha256sums. Never the reverse. Non-regular / empty / non-exec candidate skipped. Borrowed binary self-deploys into project tree.envy cache --local/--shareddeploys into the mode being established.envy cache --root/--user-wide-rootprint the two roots. - shell hooks: user-wide only (
--cache-root/$ENVY_CACHE_ROOTelse default). Local-cache project writes NO hooks,envy shellsays so. Refresh by content (_ENVY_HOOK_STAMP=<ver>:<digest>), never overwrites a hook a NEWER envy wrote. Refresh prints once:Shell hooks updated (bash, zsh, fish) — restart your shell.
shells
- string verbs + strings returned from function verbs run under
DEFAULT_SHELL(root manifest only). Default bash (POSIX), PowerShell (Windows). Built-insENVY_SHELL.BASH|SH|CMD|POWERSHELL, wrong platform = error.{ file = <path|argv>, ext = ".py" }→argv <tempfile>.{ inline = argv }→argv <script text>. Per call:envy.run(script, { shell = ... }). - envy-installed interpreter REQUIRES the
{ DEPENDS, SHELL }form (bare function has no edges,envy.productfails):DEFAULT_SHELL = { DEPENDS = { "envy.python@r1" }, SHELL = function() return { file = { envy.product("python3") }, ext = ".py" } end }. DEPENDS = queries vs PACKAGES (strict matcher, see dependencies). SHELL must be a function, either half alone = error. Lazy: a run with no string verb never installs it. Bootstrap closures (DEPENDS,source.dependencies, PACKAGE_DEPOTS DEPENDS, manifest Lua) use the platform built-in.
windows
Same manifest, specs, cache layout. Not a port.
bin\envy.batparses the header itself, walks up, honorsENVY_CACHE_ROOT/ENVY_MIRROR. Wrappersbin\<tool>.bat(setlocal, forward%ERRORLEVEL%).--platform posix|windows|alloninit/sync/deploy, default host. Bootstrap AND wrappers per flavor: plainsyncon macOS does NOT restampenvy.bat. Cross-platform repo:--platform all. Host-only deploy never prunes the other flavor.- newlines per TARGET: CRLF every
.bat, LF else, POSIX 0755. LF.batsilently stops parsing@envydirectives.core.autocrlf→ every deploy says "updated":.gitattributesbin/** -text(*.bat eol=crlfalso ok). - string verbs: PowerShell (no profile, policy bypassed) from a temp script.
POSIX gets
bash -e. Windows fail-fast injected only whencheck=true(default), socheck=falsekeeps going on Windows where POSIX stops. - paths native (
envy.path.join,envy.abspath,envy product→ backslashes). Build filesystem paths withenvy.path.join. Declarative package-relative strings always/:PRODUCTSvalues,STAGE.only,VENDOR,vendor. envy run <name>findsbin\<name>.bat(spawn + wait, forwards exit). PATH sep;. Long paths, AV file locks handled.- hook PowerShell only:
. "${env:USERPROFILE}/AppData/Local/envy/shell/hook.ps1"in$PROFILE. cmd.exe: no hook, use.bat/envy run. - PowerShell
>= UTF-16: redirectexport/hash/product --jsonvia| Out-File -Encoding ascii|utf8. - host mutation:
winget/chocoin a USER_MANAGED SETUP pair. Elevation needsinteractive = true.
verb forms
| Verb | string | table | function | omitted |
|---|---|---|---|---|
FETCH(tmp_dir, opts) | one URL, unverified | {source, sha256?, ref?, dest?, post_data?} or array | imperative, or return any declarative form | error unless USER_MANAGED |
STAGE(fetch_dir, stage_dir, tmp_dir, opts) | shell | {strip=N, only={globs}, archives={globs}} | programmatic | extract all fetched archives |
BUILD(install_dir, stage_dir, fetch_dir, tmp_dir, opts) | shell, cwd = stage | no | may return string → shell | no-op |
INSTALL(install_dir, stage_dir, fetch_dir, tmp_dir, opts) | shell | no | may return string → shell | promote stage to install dir |
SETUP.<name>.CHECK(pkg_dir, opts) | shell, exit 0 = satisfied | no | bool, or string → shell | pair needs CHECK + INSTALL |
SETUP.<name>.INSTALL(pkg_dir, opts) | shell | no | may return string → shell | n/a |
- other spec globals:
DEPENDENCIES,PLATFORMS(darwin|linux|windows[-arch]),USER_MANAGED(host-mutating: SETUP pairs only, no FETCH/STAGE/BUILD/INSTALL),EXPORTABLE(false → depot exports fetched bytes, not install output),VENDOR(selectors: what ofpkg/a vendoring manifest copies),DISPLAY(0.4.2+). - SETUP = host work that is NOT part of the package hash. Runs only if selected
(
setup = {"pair"}on the entry). Pair row =[identity]+ DISPLAY, gone on success (0.4.10+). Fail:SETUP.<name>.INSTALL shell script failed for <id>. - PRODUCTS runs BEFORE OPTIONS, gets the entry's options raw: unvalidated,
no defaults,
{}if none. Every key may be nil.opts.target:lower()→attempt to index a nil value (field 'target'), andrequireddoes NOT save you (OPTIONS has not run). Guard every read.DISPLAYruns AFTER OPTIONS, gets the checked table. - fetched file = archive by EXTENSION (
.tar.*,.zip,.7z, bare.gz...), else copied whole, so.whl/.jar/.nupkgstay zipped.archives(0.4.9+, table STAGE +envy.extract_all): filename globs unpack anyway,!keeps whole, unmatched include = error. Older envy ignores the key.
output
- exit 0 = success, every command.
envy runforwards its child's code. Only reliable signal. - human output → stderr. stdout = machine-readable only, empty unless the
command answers:
product(path,--json),package(dir),hash(<sha256> <name>),export/merge-depot(index lines),git-resolve(sha),import(dir),cache(usage). - row =
[identity]+ specDISPLAY+ status, columns padded. DISPLAY(0.4.2+): string orfunction(options)→ string|nil, after OPTIONS. Adds to identity, reaches the outcome line. One printable line: any byte< 0x20or0x7f= error. No spec sets one = no column.- no work = no row (0.4.2+). Rows only for timed outcomes (
installed, bundles too 0.4.11+,imported from depot), a SETUP pair that ran, a vendor copy that wrote.cache hit,setup complete,local bundlesilent. Warminstallwrites NOTHING to the terminal (0.4.3+).deploy:summary only when a wrapper changed. Silence ≠ failure: check exit code. - off a TTY every package reports, cache hits too: piped run lists MORE than
terminal.
--verbosenarrates all. - short/empty CI log: stderr not captured (
2>&1), global-q, Windows>UTF-16,platformsfilter. Not the silence rule. - vendored package's row =
vendored N files to <dir>, replacescache hitandinstalled (2.5s)alike (0.4.5+).pkg_outcometrace keeps the payload verdict. Underenvy vendorthe command reports, not the row. - vendor destinations shown project-relative with
/. Absolute only in filesystem-failure errors and "resolves outside the project".
vendoring
0.4.0+. Copy cached pkg/ into the project tree for build systems that need
in-tree inputs (GN, Bazel // labels). Make/CMake: use envy product's
absolute path, skip this. Copy, not move/link. Cache stays authoritative.
- manifest asks (
vendoron a PACKAGES entry, bundle shape 0.4.6+), spec narrows (VENDOR = { selectors }, absent/empty = all, bad glob fails at spec_fetch). A spec never requests it. DEPENDENCIES /source.dependenciesentry withvendor= unknown-key error. - forms:
true(derived leaf underVENDOR_ROOT) |false/absent |"dir"(project-relative, no VENDOR_ROOT needed) |{ path, auto_sync }|{}= true.VENDOR_ROOTneeded ONLY fortrue. - derived names escalate per colliding group:
name→ns.name→ns.name@rev→ns.name@rev-<options hash>. Explicit path never escalates. - path: relative, no leading sep / drive /
./../~/$/%. Anchored on ROOT manifest dir, NOT the file that wrote it (unlikesourceunder import): a component'svendorpath differs standalone vs imported. Gate onENVY_IMPORTERor vendor from root. - whole plan checked before any write: duplicate or nested destinations = error naming both. Platform-excluded entries dropped first. Destination resolved through symlinks, outside project = refused.
- per-package phase of
install/sync, soinstallwrites the work tree when the manifest vendors. - drift: no project-side state. BLAKE3 tree digest of destination vs pristine digest in cache entry. ANY mismatch (edit, stray/deleted file, empty dir, symlink target, exec bit, package moved) → wipe + recopy. mtime ignored. Committed vendored tree adopted as-is on a fresh machine.
auto_sync = false: mismatch → warning namingenvy vendor --force, dir left. Absent destination still copied.- destination that IS a symlink: wipe removes link, copy makes real dir.
- never prunes: moved destination leaves old dir, removed
vendorleaves dir. - refusals:
USER_MANAGED(no payload),vendor = "", bad type, unknown key in table, non-booleanauto_sync. VENDOR_ROOTroot-only (see superprojects).- selector language shared by
STAGE.only,envy.extractonly,envy extract --only,VENDOR,envy hash --tree --only:/-separated, case-sensitive,*/?within a component,**spans,[a-z]/[!a-z]. Leading!excludes, beats inclusion. Empty include list = all. Inclusion matching nothing = error, exclusion matching nothing = fine. envy hash --tree <dir>= the digest vendoring compares (path, kind, exec bit, contents, empty dirs. Not mtimes. Symlinks by stored target. Exec bit 0 on Windows).
dependencies
- kinds: strong
{spec, source}| weak (query +weak = {fallback}, used only if nothing else provides) | reference-only (query, satisfied elsewhere) | product{product = "ninja"}. needed_by= phase of the DEPENDENT that blocks:check|import|fetch|stage|build|install, defaultbuild.- entry shapes closed. Unknown key = error listing allowed, prefixed
<manifest>: PACKAGES[2]:/spec 'x@r1': DEPENDENCIES[2]:.- manifest PACKAGES:
spec|source|bundle|sha256|ref|options|platforms|setup|needed_by|product|vendor. Always a table. Noweak, nosource = { fetch = ... }. - bundle entry:
spec|bundle|options|platforms|setup|needed_by|product|vendor. - spec DEPENDENCIES: PACKAGES set minus
platforms/vendor, plusweak. Gate platforms withif envy.PLATFORM. source.dependencies: also minusneeded_by(always spec_fetch). Strong only, whole closure.weak = {...}fallback: complete strong decl, nobundle|setup|weak|needed_by.
- manifest PACKAGES:
envy.product/envy.package/envy.loadenv_spec: DIRECT deps only, transitive = error naming it. Declare it too (same package, built once). Fuzzy match, earliestneeded_bywins, ties by identity.- one identity, one option set, per dependency list: same identity + different
options= error. NOT top-level PACKAGES: N option sets there = N packages (DISPLAY's use case). - cycle = error naming path (
Dependency cycle detected: a -> b -> c -> a). - fetch dependencies: installed before the dependent's spec is fetched
(Artifactory / auth CLI).
source = { dependencies = {{spec, source}}, fetch = function(tmp_dir, opts) ... end }. fetch fn commitsspec.lua(+ helper files) viaenvy.commit_fetch.envy.product/envy.packagework inside. Spec-declared fetch runs as the CHILD: entry's own options + ownsource.dependencies, one spec cache entry per option set.sourcetable withoutfetch/dependencies= error. - bundles: one container, many specs.
BUNDLES = { alias = { identity, source, ref } }, entrybundle = "alias"instead ofsource. Alias resolves against the DECLARING file'sBUNDLES(a spec's own, an imported manifest's own then root's). Lua underlib/: see lua modules. - same identity declared with different sources = error naming both files.
- depot (OPTIONAL):
PACKAGE_DEPOTS = { "s3://bucket/packages.txt" }, index of prebuilt.tar.zst. Hit skips fetch + build. Bypass--ignore-depot/ENVY_IGNORE_DEPOT=1. Publish:envy export→envy merge-depot→ upload. Depot's own DEPENDS closure exempt from the index. - queries: fuzzy
name|ns.name|name@rev| canonical key, dotted rev ok (gcc@13.2.0). CLI takes FIRST match in manifest order.DEFAULT_SHELL/PACKAGE_DEPOTSDEPENDS strict: 0 or 2 distinct matches = error. - failure: each package's own error, dedup + sorted. Hang →
Deadlock: no task is running while N wait(s) are blocked:+ each wait.
lua modules
| call | scope | resolves against |
|---|---|---|
require(mod) | spec INSIDE a bundle | bundle root, on package.path |
envy.loadenv(mod) | anywhere, incl. envy lua | CALLING FILE's dir |
envy.loadenv_spec(id, mod) | spec PHASE functions only | declared dependency's root (bundle root if from one) |
envy.loadenv_bundle(alias, mod) | manifest GLOBAL SCOPE only (0.4.6+) | calling file's BUNDLES alias |
envy.*loaders: fresh sandbox, ALWAYS re-execute.requirecaches.- returns (0.4.6+): module's returned table, else globals it assigned (if it
returned nothing), else error naming module.
local M = {} ... return Mworks. Pre-0.4.6: always globals,return Mcame back{}. - reads (0.4.8+): sandbox falls through to the LOADING file's globals: root
manifest, imported fragment (its
VENDOR_ROOT,ENVY_IMPORTER), spec, outer module. Writes stay in the sandbox. Pre-0.4.8: root manifest's (or spec's) globals regardless of loader, so a module loaded by anenvy.imported file or another module silently read the root's values /nil. modforloadenv_spec/loadenv_bundle: dot syntax, no separators, no.., no leading/trailing., re-checked under load root.loadenvallows a separator, cannot escape caller's dir (0.4.6+).loadenv_bundle: fetches the bundle DURING manifest global scope, so entries go straight into PACKAGES.BUNDLESmust be assigned ABOVE the call. Fragment: ownBUNDLESthen root's.local.dir bundle read in place, else cached once. Row: bar →installed (Xs)(0.4.11+), hit silent. Refused: outside manifest scope, unknown alias (names alias + file), custom-fetch bundle. Tracelua_ctx_loadenv_bundle.- returned entries parse like literal ones:
bundle= CONSUMER's alias (any, even another bundle),vendorok. ENVY_BUNDLE(0.4.7+):{ identity, alias, root }in a module thatloadenv_bundle/loadenv_specloaded out of a bundle.alias= caller's name for it,nilunderloadenv_spec. Whole globalnilelsewhere: manifest, spec,envy.loadenvtarget (incl. a sibling a bundled module loads, never inherited),required module. Not a module global: not handed back, not leaked intooptions.- bare globals envy installs:
ENVY_SHELL,ENVY_IMPORTER,ENVY_BUNDLE.
Builder pattern, a bundle shipping the helper that writes its consumers' entries:
-- lib/github.lua, in the bundle
local M = {}
function M.repo(name, repo, ref)
return { spec = "acme.github@r0", bundle = ENVY_BUNDLE.alias,
vendor = (VENDOR_ROOT or "vendor") .. "/" .. name, -- consumer's VENDOR_ROOT
options = { repo = repo, ref = ref } }
end
return M
-- consumer envy.lua (BUNDLES above)
VENDOR_ROOT = "third_party"
local gh = envy.loadenv_bundle("tools", "lib.github")
PACKAGES = { gh.repo("libb64", "libb64/libb64", "<sha>") } -- → third_party/libb64
Spell the leaf: N entries of one identity with vendor = true escalate to
options-hash names.
superprojects
- component manifest:
@envy root "false". Superproject:local sub = envy.import("libs/common")+PACKAGES = envy.extend(sub.PACKAGES, {...}). Commands walk up to root,--subprojectstops at nearest. envy.import(path): MANIFEST SCOPE ONLY (not specs, notenvy lua). Relative to caller, directory →envy.lua. Sandbox, returns its globals. Nesting ok, cycle = error naming chain.- imported entry stays tied to its file: relative
source→ imported dir,bundle→ itsBUNDLESthen root's (no re-export). Provenance (conflict messages) and custom-fetch cache key = imported file. Project root, SETUP cwd = root. - only
PACKAGES/BUNDLEStagged, splice others by hand. Root-onlyDEFAULT_SHELL/VENDOR_ROOTset in an import: root must assign SAME value, else error.VENDOR_ROOT = sub.VENDOR_ROOT. - imported
PACKAGE_DEPOTS(0.4.12+): ignored until root assigns it. No fetch, no DEPENDS build, no error, noENVY_IMPORTERgate needed. Merge:envy.extend({...}, a.PACKAGE_DEPOTS, b.PACKAGE_DEPOTS), nil arg = error. Using a depot = trusting its artifacts. ENVY_IMPORTER= importer's absolute path in the imported file (and in modules it loads, 0.4.8+),nilstandalone.if not ENVY_IMPORTER then= standalone gate.- imported header INERT (
bin,deploy,cache-*,state-dir,mirror,sha256sums,root). Only@envy versionchecked, and only if root pins: newer than root = error, older = warn.
CLI
Global flags BEFORE subcommand: --verbose -q --trace[=sinks] --cache-root --project (envy sync --verbose = parse error). --project honored by sync install deploy vendor product package run export import use cache shell.
[q] = package queries, omitted = whole manifest.
install [q]: packages only, NO bin-dir writes (vendored trees yes). DEFAULT for "just want bytes": warm CI/Docker cache, prefetch, prove a spec builds, bump a version option, after cache wipe orenvy cache --local/--shared.sync [q]: install + deploy. Needed only when the bin dir changes: product added/removed/renamed, afteruse,--platform all, restoring bin dir. Options edit →install. Unsure →syncis safe.deploy [q] [--strict] [--platform ...]: scripts only, no installs, prunes marked wrappers outside graph.vendor <q>... | --all [--force] [--dry-run] [--threads N] [--manifest=...](0.4.0+): vendor step alone. Re-execs. No--subproject/--ignore-depot. Selection REQUIRED + exclusive (bare = error, q +--all= error). Plan checked over whole manifest, then filtered, vendored deps of a target NOT included. Runs targets to completion (fetch/build),--dry-runtoo. Named package withoutvendor= error ('x' is not vendored),--allskips them, manifest vendoring nothing = error.--force= treat all asauto_sync = true(up-to-date still no-op).--dry-run= no destination writes (cache still written),would (re-)vendor.--threads 0= default, negative = error. Report per target, stderr:vendored N files to <dir>|re-vendored N files to <dir>: contents were dirty|up to date: <dir>|kept <dir>: contents differ from the package.init <project-dir> <bin-dir> [--envy-version X.Y.Z] [--mirror URL] [--pin-sums] [--deploy=bool] [--root=bool] [--platform ...]: manifest + bootstrap +.luarc.json, appends.envy/,.envy-cache-*to.gitignoreif.gitexists (skips equivalents).--envy-versionre-execs into that release (pin, stamp, types all from it), downloads from--mirror. Dev build /ENVY_NO_REEXECwarns, stamps itself. Relative<bin-dir>resolves against<project-dir>, NOT cwd. Escaping bin dir under root manifest warns. No relative path possible (two Windows drives) = error.product [name] [--json]: path. Name installs its provider. No name lists.--json= all products, NO install.package <identity>: install, print pkg dir.run <cmd...>: exec with bin dir on PATH +ENVY_PROJECT_ROOT.shell <bash|zsh|fish|powershell>: hook source line.use <version>: retarget the pin.git-resolve <url> <ref>: full sha.hash <paths>: sha256 lines (depot).hash --tree <dirs>: BLAKE3 tree digest per dir,--only,--threads(0 = perf cores),--json(duration_ms= hash time),--stats(stderr, in object under--json,-qsuppresses).--treeexcludes--prefix. Tree flags without--tree= error. Args must be dirs.export,import,merge-depot: depot publish/consume.- no manifest needed:
fetch <src> <dst>,extract <archive> [dst] [--only PATH|GLOB|!GLOB]...,hash,git-resolve,lua <script>. Built in: AWS SDK (s3://, ambient creds, no AWS CLI), libgit2 (nogit), libarchive (tar/gz/xz/bz2/zst/zip/7z/rar/iso). cache [--root | --user-wide-root | --local | --shared]: location + usage, one flag max.version.mirror-envy(s3://creds checked before download).- fetch retry:
connect,transfer,timeout, 5xx, 429 retry. Other 4xx, bad URL, local error = fatal. Budget (0.4.6+):ENVY_FETCH_BUDGET_MSdefault 90000 from FIRST failure, never-handshake capped 5s.ENVY_FETCH_ATTEMPTS= ceiling, default 10. Backoff 1x/2x/4xENVY_FETCH_RETRY_BASE_MS(1000) ±50%, max 30s.Retry-After= jittered floor, longer than remaining budget → give up.s3://excluded (SDK retries). Rowretry 2 in 6s (http_status) tool.tar.gz. Non-status error names what arrived (... after 0 of 54881 bytes (HTTP 200, text/html)) = HTML interstitial, not network. - editor:
initwrites.luarc.json(3 platform cache paths, LuaCATS types onworkspace.library).sync/deployrewrite staleenvy/<semver>entries, keep the rest. Delete to opt out.BUNDLESnot indiagnostics.globals,ENVY_BUNDLEis (0.4.7+). --trace[=sinks]: one structured event per decision. Schemas in the Logging & Tracing reference, not here.pkg_outcome= PAYLOAD verdict (cache_hit,installed,imported, ...) whatever the row said.- env read:
ENVY_CACHE_ROOT,ENVY_MIRROR,ENVY_IGNORE_DEPOT,ENVY_NO_REEXEC,ENVY_FETCH_BUDGET_MS,ENVY_FETCH_ATTEMPTS,ENVY_FETCH_RETRY_BASE_MS. Hook-onlyENVY_SHELL_HOOK_DISABLE,ENVY_SHELL_NO_ENTER_EXIT_ANNOUNCE,ENVY_SHELL_NO_ICON. Written:ENVY_PROJECT_ROOT,PATH(byenvy run, hook, every wrapper).
lua API
envy.run(script|{lines}, {quiet, check, capture, interactive, env, cwd, shell}),
envy.fetch(src, {dest}), envy.commit_fetch, envy.verify_hash,
envy.extract, envy.extract_all(src, dst, {strip, only, archives}),
envy.copy/move/remove/exists/is_file/is_dir, envy.path.*, envy.abspath,
envy.template(str, vars), envy.product(name), envy.package(identity),
envy.options(schema), envy.import, envy.extend, loaders (see
lua modules), constants (see top). Log:
envy.debug/info/warn/error + print → stderr, envy.stdout → stdout (a
contract, keep it clean).