Load secrets from your vault into your shell via mise — silent unlock after one-time setup, no daemon, no shell-specific glue code.
open terminal → secrets already in shell
open nvim → AI keys already there
open new tab → instant from local snapshot
Note
Status: MVP in progress. The current working path is macOS + Vaultwarden/Bitwarden with .sive project config and encrypted local snapshots. Everything else is intentionally deferred until that path is proven solid.
Most secret-loading workflows are too manual (run bw unlock, export, re-source on every machine) or too magical (background daemons, fragile per-shell hooks). sive takes a simpler path:
- a secrets vault is the source of truth
- mise handles shell integration across all shells
- the OS keychain stores the unlock credential for silent unlock
- secrets are fetched out-of-band and cached in an encrypted local snapshot
The result should feel boring, reliable, and fast.
Vault support: The MVP targets Bitwarden and Vaultwarden via the
bwCLI. The design is vault-agnostic —vaults.tomlis keyed by vault name and the backend is pluggable. Other vault providers (1Password, HashiCorp Vault, etc.) are deferred, not excluded.
| Rule | Detail |
|---|---|
| Vault is pluggable | MVP uses bw (Bitwarden/Vaultwarden). Server URL is always explicit — no silent fallback to a default cloud server. |
| Secrets are grouped into tags | global, ai, projectX — each tag maps to a folder in your vault |
| Tags are ordered, last wins on conflict | Declare from most general to most specific: ["global", "ai", "projectX"] |
| Global identity secrets → shell mode | ANTHROPIC_API_KEY, GITHUB_TOKEN, etc. — loaded automatically |
| Sensitive project secrets → exec mode | Database URLs, payment keys — injected only into the subprocess that needs them |
| Encrypted local snapshot is the primary cache | AES-256-GCM, key stored in OS keychain — no plaintext secrets on disk |
| No daemon | No background service, no socket |
For always-on global secrets (AI keys, identity tokens, GitHub credentials).
When a new shell starts:
- mise runs the
siveenv hook sivereads the encrypted local snapshot — no vault calls on the hot path- decrypts using a key stored in the OS keychain
- loads env vars into the shell instantly
The hook path reads the local encrypted snapshot only. It does not call the vault during shell startup.
For sensitive project secrets that should not live in the shell.
sive run -- node server.jsOr with a .sive reference file committed to the repo:
DATABASE_URL=vault://personal:env/project-x/DATABASE_URL
STRIPE_API_KEY=vault://personal:env/project-x/STRIPE_API_KEY
- macOS (MVP scope)
- mise installed
- Python 3.11+
- gum (
brew install gum) — used for styled prompts and spinners; falls back to plain text if missing bwCLI (installed automatically bysive setupwhen needed)
brew install PeachlifeAB/tap/siveOr install from source with uv:
git clone git@github.com:PeachlifeAB/sive.git
cd sive
uv syncsive setupOn a new machine, sive setup will:
- Verify
bwis installed and install it automatically if missing - Prompt for your vault server URL (required — no default)
- Run
bw config server <url>andbw login - Prompt once for your master password and store it in macOS Keychain
- Validate silent unlock
- Install or repair hidden shell integration as needed
- Configure the current directory for the tags you select
sive setup is the only onboarding command users should run. It performs vault bootstrap internally when needed.
Important
server is always required in ~/.config/sive/vaults.toml. There is no built-in default server. A missing server field is a config error.
Example ~/.config/sive/vaults.toml:
[vaults.personal]
server = "https://vw.yourdomain.com"sive setup # Main onboarding + configure current project directory
sive set KEY [value] # Write a secret into the active project tag or an explicit --tag
sive --versionThe internal sive _mise-env command is called by the mise hook. It reads the local encrypted snapshot only — no vault calls, no network. It always exits 0 and always emits valid JSON so shell startup is never broken.
Related docs:
A tag is a named group of secrets stored in a vault folder. Tags are declared in order — last tag wins on key conflicts.
# .sive in project root
version = 1
vault = "personal"
tags = ["global", "ai", "projectX"]# Configure the current project directory interactively
sive setup
# Write a secret into the active project context (prompts for value)
sive set STRIPE_KEY
# Write a secret into an explicit tag
sive set STRIPE_KEY sk_live_xxx --tag projectXTag folder mapping is internal — users only see tag names, never folder paths.
| Tag | Vault folder (Bitwarden/Vaultwarden) |
|---|---|
global |
env/global |
ai |
env/ai |
projectX |
env/projectX |
If secret loading fails for any reason:
sivereturns{}to stdout- emits one short warning to stderr
- exits
0
The shell opens normally. A broken shell startup is worse than missing env vars.
sive/
├── hooks/
│ └── mise_env.lua # Thin Lua bridge — calls sive _mise-env
├── metadata.lua # mise plugin metadata
├── src/sive/
│ ├── cli.py # Entry point
│ ├── commands/
│ │ ├── setup.py # sive setup
│ │ ├── refresh.py # sive refresh
│ │ ├── set_secret.py # sive set KEY [value]
│ │ ├── status.py # sive status
│ │ └── mise_env.py # sive _mise-env (called by hook)
│ └── core/
│ ├── bw.py # bw CLI wrapper (Bitwarden/Vaultwarden)
│ ├── keychain_macos.py # macOS Keychain via security(1)
│ ├── project_config.py # .sive project file read/write
│ ├── snapshot.py # Encrypted snapshot read/write
│ ├── snapshot_crypto.py # AES-256-GCM encryption primitives
│ ├── source_loader.py # Resolves source strings to env dicts
│ ├── sync_state.py # Background sync state and locking
│ ├── ui.py # gum-based TUI with plain fallback
│ └── vaults.py # Reads ~/.config/sive/vaults.toml
└── tests/
# Install dev dependencies
uv sync --group dev
# Run tests
uv run pytest
# Baseline check
./bin/repo-state