zed is the CLI for zed-pkg, the universal package
manager backed by the VCS hosts you already use. Artifacts live on the
zpkg.tech registry (S3/Cloudflare R2 behind Rust servers); your declared
backing repo on GitHub, GitLab, Bitbucket, Codeberg, SourceHut, Forgejo,
Gitea, or a self-hosted server doubles as mirror and provenance anchor.
Why it exists:
- Install packages, not repositories. No cloning entire repos (tests
included) onto laptops and servers. Published artifacts are pruned: tests,
CI config,
.github/, and READMEs are stripped by default. Licenses are always kept. - pnpm philosophy. One content-addressed copy per machine under
~/.zed-pkg/store, symlinked into each project'szed_modules/. No more hefty per-project dependency folders. - Provenance by tags. Publishing requires a VCS tag matching the version
(
v{version}by default) pointing at the exact published commit; the tag and commit are pinned in.zpkg.lock. - Container-first. The documented copy install ownership contract materializes independent package, adapter, build-output, and hoisted-bin files for Docker build contexts, OCI layers, and read-only runtimes.
curl -fsSL https://zpkg.tech/install.sh | bashThe installer detects your OS/arch, drops the zed binary in ~/.zed/bin,
and adds it to your PATH idempotently. Or via Homebrew:
brew tap zed-pkg/tap
brew install zed-pkgOr from source:
cargo install --path .Keep zed current with zed self-update (checks the latest GitHub release for
your platform and replaces the binary in place; --check reports only).
Note: the Zed editor also installs a zed binary. The Homebrew formula
declares the conflict; if you use both, install with
cargo install --path . --root ~/.zed-pkg-cli and alias as you like.
# author a package
cd my-lib
zed init --org acme
git tag v0.1.0
zed r2g # consume your own artifact before shipping (add --docker for a container)
zed publish
# consume packages from a manifest
zed add acme/http-kit@^1
zed install
zed uninstall # remove files; keep the exact lock
zed install --frozen # restore the same artifacts
# first dependency install in a project with no .zpkg.toml
zed install acme/http-kit@^1 # creates a basic durable .zpkg.toml
# explicit one-shot/ephemeral install: keep the project manifestless
zed install acme/http-kit@^1 --do-not-write-new-manifest
zed find http
# opt into a confirmation at every mutating lifecycle step
zed install --interactive
zed r2g --docker --interactive
zed publish --interactiveEvery authored package is <org>/<name>, declared in a .zpkg.toml manifest
at the repo root (TOML only). A dependency-bearing first install also creates a
deterministic local consumer manifest when one is missing, so direct dependency
intent survives the shell invocation. The explicit
--do-not-write-new-manifest flag preserves the older in-memory consumer path
for throwaway or lock-only workflows. See zed init output for the annotated
authoring template.
Packages declare a version_scheme (default semver):
- semver —
1.2.3; ranges (^1.2,>=0.2 <0.5) resolve to the max satisfying stable version. - calver — calendar versions (
2026.07.24); normalized to a semver total order so the same range algebra applies (>=2026.0.0 <2027.0.0). - opaque — arbitrary tags (
legacy-api); a requirement must match a published version exactly.
Foreign tag spellings are tolerated on resolution: a leading v, Go's
+incompatible, and common PEP 440 pre-releases all normalize to a comparable
version. See zed-interfaces version.
Artifacts are tar.gz by default; zip is fully supported (both pack
deterministically and install through the store's magic-byte extraction). The
registry hosts both on S3/Cloudflare R2.
| Command | What it does |
|---|---|
zed init |
Write a .zpkg.toml template |
zed add <org>/<name>[@req] |
Add a dependency and install |
zed remove <org>/<name> |
Remove a dependency |
zed install [<org>/<name>[@req] ...] (zed i) |
Resolve, download once into the store, and install; package operands create a durable consumer manifest when one is missing |
zed install --frozen |
Install exactly what the manifest/lock pair pins; a manifestless lock-only restore additionally requires --do-not-write-new-manifest |
zed uninstall [<org>/<name> ...] (zed un) |
Transactionally remove all or selected materialized packages while retaining the manifest and lockfile for a frozen reinstall |
zed env import mise [--config PATH] [--lock PATH] [--frozen] [--json] |
Import the supported project-local mise tool/lock subset as the shared normalized EnvironmentPlan; never loads parent/global config or executes hooks |
zed env verify mise [--config PATH] [--lock PATH] --frozen [--json] |
Fail closed on missing lock coverage, drift, malformed checksums, unsupported semantics, or non-portable frozen state and report the stable plan digest |
zed env import asdf [--config .tool-versions] [--lock .zed/asdf.lock.toml] [--frozen] [--json] |
Import project-local asdf selections and optional immutable plugin/artifact provenance without invoking asdf or plugin code |
zed env verify asdf [--config .tool-versions] [--lock .zed/asdf.lock.toml] --frozen [--json] |
Verify exact asdf tool, plugin revision, artifact SHA-256, platform, and normalized plan identity without reading parent/global configuration |
zed task list|info|graph|run ... |
Use the shared schema-v2 runtime to discover, inspect, graph, dry-run, execute, confirm, parallelize, and content-cache project tasks; zed-task remains a compatibility binary |
zed find <query> |
Search the registry |
zed pack |
Build the pruned, deterministic tar.gz artifact |
zed release plan [--json] |
Print the credential-free Zed, native-registry, and forge-package release set derived from .zpkg.toml |
zed release preflight |
Validate native manifests, then run fixed credential-free package preflight adapters |
zed publish |
Verify clean tree + matching VCS tag at HEAD, pack, upload |
zed r2g (zed test-local) |
Roundtrip-test your artifact: install it into a mock consumer under ~/.zed-pkg/r2g and run publish.smoke_test, optionally inside an OCI container (--docker) |
zed run <bin> [args] |
Run an executable a dependency exposes via [bin], with zed_modules/.bin on PATH (npx-style, no global pollution) |
zed build [--force] |
Run (or warm the cache for) dependencies' [build] steps |
zed yank <org>/<name>@<version> [--undo] |
Hide a version from fresh resolution (existing lockfiles keep working) |
zed login / zed signin |
Sign in (zed auth login / zed auth signin are identical) |
zed signup / zed register |
Create an account (zed auth signup / zed auth register are identical) |
zed logout / zed signout |
Revoke and remove the session (zed auth logout / zed auth signout are identical) |
zed auth status |
Show the current account, authorities, and JWT expiries |
zed auth refresh |
Rotate shared-auth and Supabase refresh tokens now |
zed auth token |
Print the preferred current access JWT for scripting |
zed auth import-token |
Save a legacy opaque registry token to credentials.toml |
zed org claim <slug> |
Claim a namespace |
zed org audit <slug> [--limit N] |
Read the org's audit log — who changed published state, newest first (server registries only; needs an owner token) |
zed store status|path|prune |
Inspect the store or prune unreferenced entries |
zed gc [--older-than 90d] [--dry-run] |
LRU collection: drop store/build entries no live project references and unused past the cutoff, plus stale downloads |
zed cache clean |
Drop cached downloads |
zed self-update [--check] [--force] |
Replace the binary with the latest GitHub release for your platform |
zed completions bash|zsh |
Generate shell completion from the same Clap model used by the executable |
# Bash for the current shell
source <(zed completions bash)
# Zsh (persistent user completion)
mkdir -p ~/.zfunc
zed completions zsh > ~/.zfunc/_zed
fpath=(~/.zfunc $fpath)
autoload -Uz compinit && compinitThe generated scripts include aliases, subcommands, and install flags directly from the typed parser. GitHub Actions syntax-checks and registers them in real Bash and Zsh processes.
A polyglot target can name its canonical ecosystem registry and optional copies in package registries operated by GitHub, GitLab, or Bitbucket:
[targets.nodejs]
dir = "clients/typescript"
[targets.nodejs.native]
registry = "npm"
package = "@acme/client"
forge = ["github-packages", "gitlab-packages", "bitbucket-packages"]A single-language repository uses [publish.native] with the same registry,
package, and forge fields; its native package-manager manifest is read from
the repository root.
Tag-resolved packages can add a native tag_format. Go modules below a
subdirectory must use the module-directory prefix, such as
tag_format = "clients/go/v{version}", while the coordinated Zed release can
continue using the repository tag v{version}.
zed release plan --json emits one coordinated release set containing the
Zed artifacts, canonical native packages, and forge mirrors. It does not read
credentials or upload. zed release preflight verifies the native package
identity/version and runs the ecosystem's packaging command without publishing.
The manifest rejects unsupported combinations before CI reaches credentials: GitHub Packages accepts npm, Maven, RubyGems, and NuGet routes; GitLab also accepts PyPI, Packagist/Composer, and Go module routes; Bitbucket Packages accepts npm and Maven routes. Cargo and pub.dev remain canonical-native plus Zed destinations because those forges do not expose matching registry protocols.
zed install accepts package specs in an existing repository or folder:
zed install oresoftware/flags-2-env@^0.1Zed first searches upward for a Zed manifest. Without one, it looks for the
nearest native project marker (package.json, Cargo.toml, go.mod,
pyproject.toml, and other supported ecosystems). When invoked at a repository
shell containing exactly one clear nested app such as apps/web/package.json,
that app becomes the install root. Ambiguous monorepos stay at the requested
root and use the safe universal zed_modules/ layout rather than guessing.
By default, a dependency-bearing first install writes a deterministic basic
.zpkg.toml at that selected root. It records the requested packages as direct
dependencies, records supported inferred target/adapter values, writes no
timestamps or machine-specific paths, and then runs the ordinary resolver,
integrity checks, lockfile transaction, store, materializer, adapters, and
build-hook policy. A failed install removes the exact generated manifest rather
than leaving a half-adopted project.
The generated manifest uses a local zed-local/<directory-name> identity,
version 0.0.0, a non-authoritative localhost repository URL, and the
zed-generated-consumer marker. It is immediately suitable for dependency
management but zed publish rejects it until a maintainer reviews the real
package identity/repository metadata and removes the marker. --skip-vcs-checks
does not bypass that guard.
Use the canonical escape hatch when the project must remain manifestless:
zed install oresoftware/flags-2-env@^0.1 --do-not-write-new-manifestThis preserves the established in-memory consumer plan. The normal installer
can still write .zpkg.lock, zed_modules/, hoisted bins, and supported
ecosystem adapter outputs; only creation of a missing .zpkg.toml is
suppressed. The canonical environment equivalent is
ZED_PKG_DO_NOT_WRITE_NEW_MANIFEST=1.
--allow-no-manifest, --skip-manifest, and
ZED_PKG_ALLOW_NO_MANIFEST=1 remain compatibility spellings for one migration
window and emit deprecation guidance. When a manifest already exists, the new
flag is an informational no-op and never changes a managed project into an
ephemeral one.
A generated consumer manifest may accept additional package operands, which
lets two concurrent first installs retain both direct dependencies under the
project-scoped manifest lock. Conflicting requirements fail without replacing
the file. A human-authored existing manifest keeps the stricter rule: use
zed add to persist a dependency; positional operands on zed install are
rejected rather than silently editing authored package metadata.
A lockfile alone does not identify which packages were direct versus transitive. Therefore an explicit lock-only restoration is:
zed install --frozen --do-not-write-new-manifestWithout the flag, Zed fails instead of inventing a misleading manifest from the whole locked graph.
zed complements npm/maven/etc. rather than replacing them, so its (few,
hand-picked) dependencies live in their own tree alongside the native one.
That tree defaults to zed_modules/ and can be relocated:
[install]
dir = ".vendor/.zed" # default: zed_modules
adapter = "node" # optional; omitted = auto-detect (or --adapter)Every command that touches the tree honors this: zed install writes it,
zed uninstall removes all or selected materialized packages, zed run
finds hoisted bins in <dir>/.bin/, zed remove updates the manifest,
and zed pack/zed publish always exclude it — a relocated dependency tree
is never published (see a_relocated_install_dir_is_never_published).
--interactive is a global opt-in flag (ZED_PKG_INTERACTIVE=1) declared in
the same .cli-flags.toml contract audited by flags-2-env. Mutating lifecycle
commands put confirmations immediately before the steps they own: package
materialization, lock/ref updates, each publish upload, and each r2g phase.
A declined answer, EOF, or redirected stdin fails closed.
Install and uninstall protect project-tree changes with durable UUID-v4
transactions under .zpkg-staging/<uuid>/. Replaced paths are renamed into
the staging area before mutation. Normal errors restore them immediately; a
hard exit leaves the transaction metadata in place, and the next Zed
lifecycle invocation recovers it before starting new work. Successful
transactions remove their staging directory, and packing always excludes
.zpkg-staging/**.
A root manifest with a [workspace] table links member packages from source
instead of the registry, so edits are live in consumers with no publish step:
# .zpkg.toml at the monorepo root
[workspace]
members = ["packages/*", "apps/*"]When a dependency resolves to a workspace member, zed install symlinks the
member's source directory straight into zed_modules/ and keeps resolving its
transitive deps. Members are not pinned in .zpkg.lock (there is no artifact).
A package with native code or a codegen step declares a [build]:
[build]
command = "cargo build --release"
outputs = ["target/release/libfoo.so"] # empty = keep the whole tree
[build-dependencies] # tools needed only during the build
"acme/cmake" = "^3.20"Builds run in an isolated staging copy — never inside the immutable source
store — and results cache per (sha256, platform, command) under
~/.zed-pkg/builds/, so a consumer override never collides with the
package's own build.
Because a build runs arbitrary author code, it is opt-in: pass --allow-build
(or set ZED_PKG_ALLOW_BUILD=1). A consumer can patch or replace a
dependency's build without waiting on upstream:
[overrides.build."acme/crypto"]
command = "make install CC=clang"Following the flags-2-env
convention, every flag can be set via a ZED_PKG_* environment variable. The
full mapping is declared, TOML-only, in
.cli-flags.toml — a cargo test asserts that file and the
actual CLI never drift, so it is always authoritative:
| Flag | Env var | Default |
|---|---|---|
--registry |
ZED_PKG_REGISTRY |
https://registry.zpkg.net |
--home |
ZED_PKG_HOME |
~/.zed-pkg |
--token |
ZED_PKG_TOKEN |
saved credentials |
--auth-url |
ZED_PKG_AUTH_URL |
<registry>/shared-auth |
--supabase-url |
ZED_PKG_SUPABASE_URL |
optional Supabase project URL |
--supabase-key |
ZED_PKG_SUPABASE_KEY |
optional public publishable/anon key |
--interactive |
ZED_PKG_INTERACTIVE |
off; confirm each mutating lifecycle step in a real terminal |
--install-mode |
ZED_PKG_INSTALL_MODE |
symlink |
--adapter |
ZED_PKG_ADAPTER |
auto — context-aware linking: package.json projects also get node_modules/@org/name links; pom.xml/build.gradle projects get a generated .zed/classpath of installed jars for java -cp "$(cat .zed/classpath)"; python site-packages planned |
--frozen |
ZED_PKG_FROZEN |
off |
--allow-build (install) |
ZED_PKG_ALLOW_BUILD |
off |
--do-not-write-new-manifest (install) |
ZED_PKG_DO_NOT_WRITE_NEW_MANIFEST |
off; normal first installs create a basic durable .zpkg.toml |
deprecated --allow-no-manifest / --skip-manifest |
deprecated ZED_PKG_ALLOW_NO_MANIFEST |
compatibility aliases for --do-not-write-new-manifest |
--force (build) |
ZED_PKG_FORCE |
off |
--older-than (gc) |
ZED_PKG_GC_OLDER_THAN |
90d |
--dry-run (gc) |
ZED_PKG_GC_DRY_RUN |
off |
--dry-run (publish) |
ZED_PKG_DRY_RUN |
off |
--allow-dirty |
ZED_PKG_ALLOW_DIRTY |
off |
--skip-vcs-checks |
ZED_PKG_SKIP_VCS_CHECKS |
off |
--undo (yank) |
ZED_PKG_YANK_UNDO |
off |
--out (pack) |
ZED_PKG_PACK_OUT |
.zed/pack |
--org (init) |
ZED_PKG_ORG |
- |
--name (init) |
ZED_PKG_NAME |
directory name |
--check (self-update) |
ZED_PKG_UPDATE_CHECK |
off |
--force (self-update) |
ZED_PKG_UPDATE_FORCE |
off |
--docker (r2g) |
ZED_PKG_R2G_DOCKER |
off |
--image (r2g) |
ZED_PKG_R2G_IMAGE |
debian:stable-slim |
--runtime (r2g) |
ZED_PKG_R2G_RUNTIME |
auto (docker, then podman) |
--r2g-root (r2g) |
ZED_PKG_R2G_ROOT |
<home>/r2g |
--clean (r2g) |
ZED_PKG_R2G_CLEAN |
off |
--registry file:///path selects a directory-backed registry: hermetic CI,
air-gapped mirrors, and zed r2g all use it.
zed login and zed auth login are the same operation. With
ZED_PKG_SUPABASE_URL and the public ZED_PKG_SUPABASE_KEY configured, zed
uses Supabase Auth for the credential exchange and then exchanges that provider
JWT at shared-auth. It retains both independently refreshable sessions:
shared-auth is preferred, while the Supabase JWT remains available as the
dual-auth fallback. Without Supabase configuration, login and registration use
shared-auth directly.
Passwords are read from a hidden terminal prompt and never stored. For
non-interactive use, pass --password-stdin or inject
ZED_PKG_AUTH_PASSWORD. Access and rotating refresh tokens are stored in
~/.zed-pkg/auth/sessions.toml; the directory is mode 0700 and the file is
mode 0600 on Unix. zed logout attempts revocation at both authorities and
always removes the local session.
Symlinks into $HOME/.zed-pkg do not survive a COPY --from=build between
image stages, so use copy mode inside builds and cache-mount the store:
FROM rust:1-slim AS build
RUN cargo install zed-cli --root /usr/local # or COPY a prebuilt zed
WORKDIR /app
COPY .zpkg.toml .zpkg.lock ./
RUN --mount=type=cache,target=/root/.zed-pkg \
zed install --frozen --install-mode copy
COPY . .
RUN make build
FROM gcr.io/distroless/cc
COPY --from=build /app/out /app--frozenkeeps builds reproducible: exactly the sha256s in.zpkg.lock.--install-mode copymaterializes files so the layer is self-contained; the cache mount still deduplicates downloads across builds.- Artifacts are pre-pruned at publish time, so images stay small without extra cleanup steps.
The test suite asserts copy-mode installs contain zero symlinks; run the
whole suite inside a clean container with scripts/container-smoke.sh.
Named after r2g: the failure mode it
kills is "works in my repo, breaks when installed." Instead of testing your
working tree, zed r2g exercises the published artifact the way a real
consumer would:
- Pack — builds the exact pruned, deterministic tarball
zed publishwould upload. - Publish — to a throwaway
file://registry. - Install — into a mock consumer project (
zed-local/consumer) with its own throwaway store, so the tarball actually roundtrips through extraction — no reaching back into your source tree. - Smoke test — runs your
publish.smoke_testwithZED_PKG_TEST_TARGETpointing at the installed package.
The whole workspace lives under your home directory at
~/.zed-pkg/r2g/<org>-<name>-<uuid-v4>/ (registry + consumer + store). Unique
run directories prevent stale or concurrent state from masking a failure and
are left behind for inspection (pass --clean, or set --r2g-root to
relocate them). zed test-local is a backwards-compatible alias.
zed r2g # roundtrip on the host
zed r2g --docker # ...inside a fresh debian:stable-slim container
zed r2g --docker --image node:22-slim # pick an image with the runtime you needWith --docker, r2g installs in copy mode (self-contained, zero symlinks —
the same guarantee --install-mode copy gives OCI builds), bind-mounts the
mock consumer into a throwaway container, and runs the smoke test there —
proving the artifact works in a clean, host-independent environment (fresh
$HOME, distro libraries, none of your host toolchain leaking in). The
runtime is auto-detected (docker, then podman) or forced with --runtime.
If the smoke test passes here, it will pass for your users.
| VCS | Worktree check | Tag verification |
|---|---|---|
| git | yes | yes |
| jj (colocated) | via git | via git |
| sapling (colocated) | via git | via git |
| hg | yes | yes |
| fossil | yes | not yet (--skip-vcs-checks) |
| pijul | not yet | not yet (--skip-vcs-checks) |
zed install is safe to run from many processes at once (two terminals,
parallel CI runners). Store extraction and reference updates retain their
existing advisory locks. A dependency-bearing first install also takes a
project-scoped manifest lock under ~/.zed-pkg/locks/projects/, keyed by the
canonical project path. Two simultaneous first installs therefore create one
valid manifest and merge distinct direct dependencies instead of losing one
caller's intent. Exact conflicting requirements fail rather than choosing a
winner, and the OS releases every lock if a process dies.
Releases ship prebuilt zed binaries for macOS (Apple Silicon + Intel),
Linux (arm64 + x64, gnu and musl), and Windows via
.github/workflows/release.yml on every
v* tag. musl builds are static — ideal for distroless/scratch containers.
~/.zed-pkg/
store/v1/<aa>/<sha256>/pkg/ extracted source artifacts (content-addressed, immutable)
builds/v1/<platform>/<aa>/<sha256>/ per-platform build-hook outputs
cache/<sha256>.tar.gz downloaded archives
locks/projects/<hash>.manifest.lock project-scoped first-install serialization
locks/ advisory flocks (per-artifact, per-install, per-build)
auth/sessions.toml shared-auth + Supabase token pairs (0600)
refs.json project -> artifact references (for prune/gc)
credentials.toml legacy opaque registry tokens (0600)
Artifacts arrive over the network, so the client treats them as untrusted:
- Digest-addressed everything. Registry-returned
org,name, andsha256are validated (slug / 64-char hex) before they touch a filesystem path — a hostile registry can't traverse out of the store orzed_modules/. - Extraction is a security boundary. Tar/zip entries are screened for
path traversal (
.., absolute, prefix components), symlink/hardlink and non-regular entries are refused, per-entry size is bounded by the declared header (a lying header can't over-read), and total unpacked size and entry count are capped (ZED_PKG_MAX_UNPACKED_BYTES) against decompression bombs. - Bounded downloads. Artifact fetches are size-capped
(
ZED_PKG_MAX_ARTIFACT_BYTES) and a registry-supplieddownload_urlmust be https (or loopback/http only when the registry itself is http). - No install-time code execution. Installing a dependency never runs its
scripts;
[build]steps run only with explicit--allow-build. - Generated identities fail closed. A first-install consumer manifest cannot be published until its inferred local package identity is reviewed and the generated marker is removed.
- Recoverable project mutations. Install and uninstall stage old paths in UUID-v4 transaction directories and restore interrupted work before the next lifecycle operation.
- Tokens at 0600 from creation, with no write-then-chmod window.
Clone side by side with zed-interfaces (path dependency), then:
cargo testMIT