Upgrade from 0.16
penv 1.0 is a new binary with one committed schema, so this page names what each 0.16 part became and the one path that gets your values across.
penv 1.0 is one static binary with nothing under it. The 0.16 line was a Node launcher that downloaded an engine, kept a tree of value files, and loaded plugins. None of that survives, so this is a move rather than an upgrade.
The move is one plain file per environment out of 0.16, then one push per environment into penv.cloud. Read the table first so you know what you are giving up.
Do it in order
Get a plain file out of 0.16, one per environment. Run this with the version you have installed today.
penv generate --env development --out .env.development --allow-decrypt
penv generate --env production --out .env.production --allow-decrypt--allow-decrypt is what writes a sealed value as plaintext. Without it, generate refuses the sealed
ones and you get an incomplete file.
Move those files into a holding directory outside the repository, say ~/penv-move/. They are
plaintext, and the next steps delete the tree they came from.
mkdir -p ~/penv-move
mv .env.development .env.production ~/penv-move/Remove 0.16. Uninstall the global launcher, then delete the engine store it downloaded into.
npm rm -g @penvhq/launcher
rm -rf ~/.penv/engines ~/.penv/extensionsInstall 1.0. The two installers always take the latest release.
curl -fsSL https://penv.cloud/install | shSee install the CLI for Windows and for npm.
Write the schema. Put the development file back as .env, then run penv init in the
project. It reads the file and writes .env.schema, inferring a type and a sensitivity per key. It
never copies a value into the schema.
cp ~/penv-move/.env.development .env
penv init
penv checkRead the guesses and fix the ones it got wrong. See from a dotenv file.
Send the first environment up. The .env you copied in the last step is still there.
penv login
penv pushpush creates the project with one environment, development, writes the @penv= header into
.env.schema, sends the values, then deletes .env.
Create every other environment in the console. push creates development and nothing else, so
production has to exist before you can push to it. Open the project and add each one. See
projects and environments.
Push each remaining environment. Copy its file back to .env first, because push reads .env
and deleted the last one.
cp ~/penv-move/.env.production .env
penv push --env productionRepeat that pair for every environment you saved.
Read the values in the console. Open each environment and check its keys are there. This is the last moment the plaintext copies are your only source.
Delete what 0.16 left in the repository. Remove these:
penv.config.tsandpenv.schema.ts- the whole
.penvdirectory - any
@envor#envpath mapping intsconfig.jsonorpackage.json
Then delete ~/penv-move/.
The files generate --allow-decrypt writes hold every value in the clear. Keep them until every
environment shows its values in the console, then delete the whole holding directory and check that
none of them reached a commit. Anything sealed under a local key in 0.16 has no other way across:
penv 1.0 cannot read the penv:1: envelope format.
What each name became
This is the one page that names the retired parts. Nothing below exists in 1.0.
Configuration
| 0.16 | 1.0 |
|---|---|
penv.config.ts and defineConfig | One committed .env.schema. There is no config file to import. |
penv.schema.ts and .penv/env.ts | penv gen ts writes the typed accessor from the schema. |
The @env and #env alias | The target decides the import line, remembered in .penv/targets/ts/target.toml. |
.penv/state/records/ | Values live in .env in local mode and in penv.cloud in cloud mode. |
.penv/state/manifest.json | No replacement. There is no engine to pin. |
.penv/state/extensions/ and local-extensions.json | No replacement. |
.penv/state/cutover.json and rollback/ | No replacement. penv init leaves .env where it is until push. |
| The four-scope cascade | One environment per read: --env, then PENV_ENV, then development. |
| Providers and provider extensions | Two modes built into the binary: local and cloud. |
@penvhq/provider-penv-cloud | No replacement. Cloud mode is the binary. |
Commands
| 0.16 | 1.0 |
|---|---|
penv init | penv init, which now writes .env.schema from your .env. |
penv import <file> | penv init. |
penv generate | penv pull, which writes a plain .env from the cloud. |
penv get <key> | penv reveal, which needs a person's approval under a coding agent. |
penv set <key> [value] | penv set. |
penv fill | penv set, one key at a time. |
penv mv <from> <to> | No replacement. |
penv pull | penv pull, now reading from the cloud. |
penv push | penv push, now sending to the cloud and deleting .env. |
penv rotate <key> | penv set writes a new version. @rotate in the schema records the interval. |
penv run | penv run. |
penv remove <key> | penv unset. |
penv list | penv ls. |
penv cleanup | No replacement. There is no rollback bundle to drop. |
penv migrate | No replacement. |
penv encrypt and penv decrypt | No replacement. Values are sealed by the server. |
penv key create | No replacement in the binary. Your own wrapping key is set in the console on Enterprise. |
penv validate | penv check. |
penv doctor | penv check, plus penv with no arguments, which prints the state and the next command. |
penv watch | No replacement. |
penv artifact build | No replacement. penv pull writes a plain .env. |
penv install | No replacement. The installer places the binary. |
penv add <package> | No replacement. |
penv upgrade [version] | penv upgrade replaces the binary from the latest release. |
New in 1.0 and worth knowing about: penv login and penv logout for a device-code sign-in,
penv machine enroll for a host that can prove nothing, penv gen for typed access, along with
penv guard and penv hook for keeping a coding agent out of .env.
penv gen is a different command from 0.16's penv generate, and the similar name is the trap. gen
writes typed access for your language. The old generate wrote a value file, and penv pull is what
does that now.
The variables penv reads
| 0.16 | 1.0 |
|---|---|
PENV_ENV | PENV_ENV. Local mode accepts development alone. |
NODE_ENV as an environment fallback | No replacement. PENV_ENV is the only variable that names one. |
PENV_HOME | PENV_INSTALL_DIR, read by the installers. There is no engine store. |
PENV_KEY_* | No replacement. There is no local key. |
PENV_DELIVERY | No replacement. |
PENV_SNAPSHOT | No replacement. |
PENV_RUN | No replacement. |
PENV_SCHEMA_HARVEST | No replacement. |
PENV_DEBUG | No replacement. |
PENV_CLOUD_TOKEN | PENV_TOKEN, holding a pck_ machine credential. |
The full 1.0 list is on the variables penv reads.
Encryption
0.16 sealed a value on your disk with AES-256-GCM under a 32-byte key you supplied, either from
PENV_KEY_<ID> or from the operating system keychain, and wrote it as a one-line penv:1: envelope.
1.0 has no local envelope. A value is sealed by the server under a key of its own, and the copy penv keeps on your machine is sealed with ChaCha20-Poly1305 under a key in the keychain. See encryption in the cloud.
There is no converter between the two. The generate --allow-decrypt step above is the path across.
About the npm packages
The 0.16 packages are still published, and latest still points at 0.16.2 on 10 September 2026.
Nothing is marked deprecated on npm.
| Tag | Version |
|---|---|
latest | 0.16.2 |
next | 1.0.0-alpha.3 |
npm i -g @penvhq/cli installs 0.16.2 today, whose only executable is penv-engine, so you end up
with no penv on your PATH at all. While 1.0 is in prerelease, ask for it by tag:
npm i -g @penvhq/cli@next. The two installers at https://penv.cloud/install always take the latest
release, so they need no tag.
In 0.16 the command named penv came from @penvhq/launcher, which is why that is the package the
uninstall step names. @penvhq/provider-penv-cloud was never published, so there is nothing to remove
for it.
Next: changelog.