kagi

A secure, team-ready CLI for managing encrypted secrets and environment variables — a drop-in replacement for .env files with per-service isolation and team sharing.
kagi keeps your secrets encrypted at rest using XChaCha20-Poly1305 while making them easy to inject into applications during development and deployment.
Features
- Encrypted secrets at rest with XChaCha20-Poly1305.
- Team-ready by default: one developer is just one member.
- Service and environment scopes like
api/developmentandweb/production. developmentis the default environment, so daily commands stay short.- Nested service inference lets
kagi run bun devwork inside./apiand mapped monorepo packages like./apps/api. .kagi/is designed to be committed; private keys stay on each device.get --showandexportrequire terminal confirmation before revealing values.- Conservative
.envmigration inkagi initdetects high-confidence.env*files and can map monorepo service folders. - Encrypted small-file artifacts via
kagi file, scoped the same way as env secrets. - Shell completions for bash, zsh, fish, elvish, and powershell via
kagi completions.
Installation
From crates.io
# Default: includes the remote sync server
# CLI-only: excludes server code and server-related commands
Requires Rust 1.85+ (2024 edition).
From a local checkout
# Default: includes the remote sync server
# CLI-only: excludes server code and server-related commands
As a library
Individual crates can be added as dependencies:
# Core domain types and traits
# Encryption (XChaCha20-Poly1305)
# Local storage and key management
# Remote sync client
# HTTP server (requires server feature)
Optional Codex / OpenCode skill
This repository includes a skill for agents that help users operate kagi
projects safely. The skill files live in skills/kagi/SKILL.md and are
registered via skills.sh.json on the skills.sh directory.
Install with skills.sh
Use the skills.sh CLI (requires Node.js / npx):
This copies the skill into the agent's skills directory. To force reinstall:
Daily Development
1. Initialize once
--envs without a value creates the standard environments:
development, test, and production.
development is the default, so you usually do not type it. --nested lets
kagi infer the service from the folder you are in. In monorepos, init records
detected service paths such as apps/api and packages/api so commands inside
those folders do not need --service.
Commit the generated .kagi/ files:
Private keys are not written to .kagi/.
2. Set secrets
From the repository root:
Keys passed to set and get are normalized to upper snake case by default,
so abc-d becomes ABC_D. kagi prints a short tip when it normalizes a key.
Inside ./api:
Both short commands write to the same scopes:
| Command | Scope |
|---|---|
kagi set api DATABASE_URL ... |
api/development |
kagi set DATABASE_URL ... inside ./api |
api/development |
kagi set api production DATABASE_URL ... |
api/production |
kagi set production DATABASE_URL ... inside ./api |
api/production |
3. Check what exists
get lists services, environments, and keys with masked values. Reveal values
only when you really need them:
Both commands require an interactive y confirmation.
4. Run your app
From the repository root:
Inside ./api:
kagi run injects the selected environment variables into the child process.
For shell syntax such as pipes, redirects, or $VAR expansion, run a shell
explicitly:
5. Commit encrypted changes
Do not commit real .env files. kagi init updates .gitignore so .env,
.env.*, and local private material stay out of Git.
Common Commands
| Task | Command |
|---|---|
| Initialize with standard envs | kagi init --nested --envs |
| Set development secret | kagi set api KEY value |
| Set production secret | kagi set api production KEY value |
| List masked keys | kagi get |
| Reveal listed values | kagi get api --show |
| Search keys | kagi search DATABASE |
| Search including values | kagi search --values localhost |
| Show current project context | kagi status |
| Run app with development env | kagi run api bun dev |
| Run app from inside service folder | kagi run bun dev |
| Add an environment | kagi env add staging |
| Rename an environment | kagi env rename staging preview |
| Delete an environment | kagi env remove preview |
| List environments | kagi env list |
| List project members | kagi member list |
| Import an env file | kagi import api --file .env.local |
| Preview an import | kagi import api --file .env.local --dry-run |
| Import and normalize keys | kagi import api --file .env.local --upper-snake |
| Add an encrypted file | kagi file add api service-account.json |
| List encrypted files | kagi file list api |
| Restore an encrypted file | kagi file restore api service-account.json |
| Export all service envs | kagi export api --out . |
| Sync missing keys from example | kagi sync --service api |
| Generate shell completions | kagi completions zsh |
Use --service <name> when a shortcut would be ambiguous:
Environment names cannot conflict with existing service names.
Working With .env Files
kagi init detects high-confidence .env* files up to four levels deep and
offers to import them during initialization. It is intentionally conservative:
.env and .env.example map to the default environment, while files like
.env.dev are only imported automatically when dev is one of the configured
environments. Use --no-migrate to skip this import prompt.
When --nested is enabled, detected nested env files also create monorepo
service mappings. For example, apps/api/.env.dev maps commands run inside
apps/api to the apps-api service, while packages/api/.env.dev maps to
packages-api.
Import existing local files manually:
Manual imports preserve key names by default. Add --upper-snake if you want
imported keys normalized the same way as set and get.
Export creates normal runtime files when needed:
That writes one file per environment:
.env.development
.env.test
.env.production
Exporting decrypted values requires terminal confirmation. Prefer kagi run
for day-to-day scripts.
sync is useful when .env.example gains a new key:
Existing values are never overwritten.
Working With Encrypted Files
Use kagi file for small secret files that should share the same service and
environment scope as your env secrets:
Inside a nested or monorepo service directory, scope inference works the same as
set, get, run, and import:
The command stores encrypted blobs under .kagi/files/. File names, restore
paths, and scopes are stored in encrypted files/index.enc; Git and the remote
server only see opaque encrypted artifact ids such as files/kgf_xxxxxx.enc.
Restoring writes plaintext back to the repo-relative path captured at add
time, unless --out <path> is supplied:
Safety limits:
- Default max file size is 1 MiB.
--allow-largeraises the limit to 5 MiB.- Directories, symlinks, device files, build artifacts,
.git/, and.kagi/paths are rejected. - Git-tracked plaintext files are rejected; remove them from Git first with
git rm --cached <path>. showrequires terminal confirmation because it prints decrypted content.restorerefuses to overwrite existing plaintext unless--forceis used.
Git-backed and server-backed sharing use the same encrypted project state. Commit
or push .kagi/files/**; do not commit restored plaintext files.
Team Flow
A project is always team-ready. If you work alone, you are the only member.
New device or teammate:
An existing member approves:
If multiple people request access at the same time, keep all pending entries in
.kagi/access.json when merging their PRs.
Remove access:
member remove rotates the project key internally and re-encrypts current
secrets for active members. If rotation is interrupted, kagi writes a local
journal outside the repository and retries safely on the next command.
Architecture Overview
Without Server (Git-backed)

Encrypted secrets are shared through Git commits and pulls. New members use kagi member request to request access, and existing members use kagi member approve to grant it.
With Server (Remote Sync)
1. Server initialization

2. Team collaboration

.kagi/stays local and out of Git.- Project tokens contain the remote URL, project ID, and server fingerprint.
Choose:
- Without Server: commit
.kagi/to Git (default, simple). - With Server: keep
.kagi/local, use remote sync (team control).
Remote Server Sync
Git-backed .kagi/ sharing is the default workflow. If a team does not want to
commit .kagi/, run a self-hosted Kagi server instead.
Status: production-ready for self-hosted team use — requires HTTPS, proper
backups, and monitoring. See docs/remote-sync-server.md for the full
protocol, deployment guide, and Docker/systemd examples.
The Kagi server is explicitly single-tenant. Each server instance serves one team or organization. Do not run a single server instance for unrelated tenants without additional isolation.
HTTP restrictions
The server rejects non-localhost http:// remotes by default. Use HTTPS for
any public or LAN deployment. For local development only, pass
--allow-insecure-http or set KAGI_ALLOW_INSECURE_HTTP=1:
Start the server
On first startup, the server prints one admin token. Save it securely, then log in from the admin machine:
Create a local project and request server registration:
An admin approves the pending request:
Admin commands:
approve prints a project token. Give that token to the requester once. The
token contains the remote URL, project id, and server fingerprint:
In server mode, keep .kagi/ local and out of Git. Project tokens are bearer
credentials and are stored outside .kagi/; admin tokens are stored in the OS
keychain or supplied with KAGI_ADMIN_TOKEN.
CI and Containers
For CI, store the project key in your secret manager and mount it as a file:
KAGI_PROJECT_KEY_FILE=/run/secrets/kagi_project_key
KAGI_PROJECT_KEY=<64-hex-chars> is also supported when a file mount is not
available, but a file secret is easier to keep out of logs.
For local Docker development, prefer running the process through kagi on the host:
If the container itself must read env files, export them when needed and keep
.env* ignored by Git.
Safety Model
For Git-backed projects, commit these:
.kagi/kagi.json
.kagi/access.json
.kagi/secrets/**/*.enc
.env.example
Do not commit these:
real .env / .env.* files
local project keys
local age identities / private keys
KAGI_PROJECT_KEY values
logs or screenshots containing secrets
The repository contains encrypted secret stores, public member recipients, and encrypted access wrappers. It does not contain the raw project key or private identity keys.
For server-backed projects, keep .kagi/ local and sync encrypted state with
kagi remote push / kagi remote pull instead.
Secrets are encrypted with XChaCha20-Poly1305 and authenticated with their scope name, so an encrypted file cannot be silently moved to another scope.
kagi get <key>, kagi get --show, and kagi export reveal decrypted data and
require confirmation. kagi run is safer for scripts, but it is not a sandbox:
the child process receives the selected secrets as environment variables.
If every active member loses their local key material and no CI secret exists, the encrypted secrets are unrecoverable by design.
Architecture
kagi is organized as a Cargo workspace with 6 crates:
| Crate | Purpose | Dependencies |
|---|---|---|
| kagi-domain | Core domain: entities (Service, Secret), config, errors, parsers, repository traits |
None |
| kagi-crypto | XChaCha20-Poly1305 encryption | kagi-domain |
| kagi-store | Local storage (FileStore), key manager, env injector |
kagi-domain, kagi-crypto |
| kagi-sync | Sync protocol types + remote client (age-encrypted HTTP transport) |
kagi-domain |
| kagi-server | Axum HTTP server + SQLite remote backend | kagi-domain, kagi-sync |
| kagi-app | CLI application: argument parsing and command dispatch | kagi-domain, kagi-crypto, kagi-store, kagi-sync |
| kagi-vault | Meta-package: provides the kagi binary by re-exporting kagi-app |
kagi-app |
All crates share the same version via version.workspace = true. The design
lets you depend on individual crates (e.g., kagi-domain for types, or
kagi-crypto for encryption) without pulling in the entire CLI or server.
Development
# Run all tests (with server feature)
# Run tests without server feature
# Run integration tests only
# Run the real OS keychain smoke test
# Test a single crate
# Try the Bun example
# Install locally
The default test suite uses isolated local storage so it can run in CI. The ignored keychain smoke test requires a real unlocked OS keychain/session and verifies that kagi can still load the project key after local data files are removed.
Releasing
Tag-based release (recommended)
Push a git tag and let GitHub Actions handle everything:
# Create and push tag (triggers CI: tests, builds, GitHub Release, crates.io)
VERSION=0.1.3
GitHub Actions workflow (release.yml) runs automatically on tag push:
- Tests on all platforms
- Builds cross-platform binaries
- Creates GitHub Release with artifacts
- Publishes all crates to crates.io
Full cargo-release workflow
# Preview what a release would do
# Bump version, commit, tag, and push
# Publish all crates to crates.io (local, not via CI)
cargo-release handles the dependency ordering automatically. See the Makefile for details.
License
MIT