@fireart/envsync
v0.1.0
Published
Sync environment configuration across your team and devices
Maintainers
Readme
EnvSync CLI
The envsync command owns the local machine and the developer workflow: the
filesystem, .env files, device identity, terminal UX and local project
config. The API owns the source of truth.
Install
Requires Node.js 22.12+.
npm install -g envsyncOr run without installing:
npx envsync loginCommands
| Command | What it does |
| ----------------- | -------------------------------------------------- |
| envsync login | Signs in with Google, then registers this device |
| envsync current | Shows the signed-in account and the current device |
| envsync logout | Removes stored credentials from this machine |
init, switch and save land in later steps.
Using the short env form
The binary is named envsync on purpose. Installing a binary called env
would shadow the POSIX /usr/bin/env on PATH and break things like
env FOO=1 node app.js. If you want the short form from CLI.md, alias it:
alias env="envsync"Local development
# from the repo root
yarn build:cli
node apps/cli/dist/index.js login
# or watch mode
yarn dev:cliPoint the CLI at a different API with ENVSYNC_API_URL:
ENVSYNC_API_URL=http://localhost:3000 node apps/cli/dist/index.js currentPublishing
From the repo root (requires an npm account that can publish envsync):
npm login
yarn publish:cliprepublishOnly builds dist/ first. Only dist/ is included in the tarball
(files in package.json). Bump version in apps/cli/package.json before
each release.
Where state lives
~/.config/envsync/config.json device id + device name
OS keychain (account "auth") access token
OS keychain (account "identity") Ed25519 device identity key pair$XDG_CONFIG_HOME is honoured on macOS/Linux; Windows uses
%APPDATA%/envsync. If no OS credential store is available (a container, CI, or
Linux without libsecret) the CLI falls back to
~/.config/envsync/credentials.json with 0600 permissions and warns that it
did so.
How login works
Google sign-in only bootstraps a brand-new device. Once the device is registered, every later authentication is a signed challenge with no browser.
First run (bootstrap)
- The CLI starts a throwaway HTTP server on an OS-assigned loopback port.
- It opens the browser at
GET /auth/google?state=..., wherestatecarries the loopback port and a random nonce through Google. - The API mints tokens, stores them behind a single-use code (60s TTL) and
redirects to
http://127.0.0.1:<port>/callback?code=...&nonce=.... - The CLI checks the nonce and exchanges the code at
POST /auth/cli/exchange. - It generates an Ed25519 identity key pair and registers the device, sending only the public key.
Tokens never travel through the browser URL, so they stay out of browser history.
Every later authentication (challenge)
POST /auth/challenge/startwith the device id returns a random nonce that the API stores with a 60s expiry.- The CLI signs the nonce with its identity private key.
POST /auth/challenge/completeverifies the signature against the stored public key and returns an access token only.
There is no refresh token for the CLI: when the access token expires, the CLI silently runs another challenge. The private key never leaves this machine.
