unotoken
v0.3.0
Published
unotoken — yokotoken + cloud features (OAuth unlock, team vaults, key escrow, device signatures)
Maintainers
Readme
unotoken
yokotoken + cloud features.
unotoken is a private extension of yokotoken, the agent-native encrypted credential vault. It adds cloud features on top of the open-source MIT core:
- OAuth unlock -- Sign in with Google or GitHub to unlock your vault (no passphrase needed)
- Device signatures -- New devices must be approved via email code before accessing the vault
- Team vaults -- Share encrypted vaults across a team (coming soon)
- Key escrow -- Recovery keys held by your organization (coming soon)
How it works
unotoken depends on yokotoken as an npm package. All of yokotoken's public API is re-exported, so unotoken is a drop-in replacement:
// Before
import { getSecret } from 'yokotoken/sdk';
import { NetworkVaultClient } from 'yokotoken/client';
// After (identical API)
import { getSecret } from 'unotoken/sdk';
import { NetworkVaultClient } from 'unotoken/client';The CLI wraps yokotoken and adds new commands:
# All yokotoken commands work as-is
unotoken init
unotoken serve
unotoken unlock
unotoken set my/secret "value"
unotoken get my/secret
# New: OAuth commands
unotoken auth link google # Link Google account for passwordless unlock
unotoken auth link github # Link GitHub account
unotoken auth list # Show linked providers
unotoken auth unlink google # Remove a linked provider
unotoken auth config --show # View OAuth configurationInstallation
npm install unotokenOr for CLI usage:
npm install -g unotokenPackage exports
| Import path | Description |
|-------------|-------------|
| unotoken | Full vault API (re-exports yokotoken) |
| unotoken/sdk | Worker SDK for agent access (re-exports yokotoken/sdk) |
| unotoken/client | Network client for remote vaults (re-exports yokotoken/client) |
Development
npm install
npm run typecheck
npm test
npm run buildBuild-Time Env Injection
unotoken replaces .env files with vault-backed secrets. Two modes:
exec -- inject secrets into a child process
No .env file needed. Secrets are injected as environment variables directly:
# Run your app with vault secrets injected
unotoken exec --prefix myapp/ -- npm run dev
# In package.json:
"scripts": {
"dev": "unotoken exec --prefix myapp/ -- next dev",
"build": "unotoken exec --prefix myapp/ -- next build"
}Path mapping: myapp/stripe/secret/key becomes STRIPE_SECRET_KEY.
dotenv -- generate .env files from vault
For tools that require a .env file on disk:
# Generate .env.local from vault
unotoken dotenv --prefix myapp/ --out .env.local
# Clean up after build (CI/CD)
unotoken dotenv --clean .env.localScoped tokens
Create tokens that only access specific prefixes:
# Create a token restricted to myapp/* secrets
unotoken token create --name myapp-ci --prefix myapp/
# Multi-prefix tokens
unotoken token create --name shared --prefix myapp/ --prefix common/Real-app demo (levelfit)
A hands-on demo script walks through the complete flow using real secrets:
bash scripts/demo-levelfit.shThe demo covers:
- Scoped token creation -- create a token restricted to
levelfit/* - Access verification -- confirm the token can access
levelfit/but not other prefixes - exec injection -- run a process with vault secrets as env vars (no file on disk)
- dotenv generation -- produce a
.envfile equivalent to a hand-maintained.env.local - Cleanup -- remove generated files and revoke demo tokens
Prerequisites: vault running (unotoken serve), vault unlocked (unotoken unlock), secrets stored under levelfit/ prefix.
Package.json Scripts Integration
Replace .env files with vault-backed secrets in your npm scripts. Here are before/after patterns for common setups.
Next.js
Before (.env.local on disk):
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
}
}After (vault-backed):
{
"scripts": {
"dev": "unotoken exec --prefix myapp/ -- next dev",
"build": "unotoken exec --prefix myapp/ -- next build",
"start": "unotoken exec --prefix myapp/ -- next start"
}
}Vite
Before:
{
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
}
}After:
{
"scripts": {
"dev": "unotoken exec --prefix myapp/ -- vite",
"build": "unotoken exec --prefix myapp/ -- vite build",
"preview": "unotoken exec --prefix myapp/ -- vite preview"
}
}Node.js API Server
Before:
{
"scripts": {
"dev": "tsx watch src/server.ts",
"start": "node dist/server.js"
}
}After:
{
"scripts": {
"dev": "unotoken exec --prefix myapi/ -- tsx watch src/server.ts",
"start": "unotoken exec --prefix myapi/ -- node dist/server.js"
}
}CI/CD (GitHub Actions)
Use UNOTOKEN_TOKEN and UNOTOKEN_URL environment variables for remote vault access:
# .github/workflows/deploy.yml
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- name: Build with vault secrets
env:
UNOTOKEN_TOKEN: ${{ secrets.VAULT_TOKEN }}
UNOTOKEN_URL: https://vault.example.com:13100
run: unotoken exec --prefix myapp/ -- npm run buildOr as a single inline command:
UNOTOKEN_TOKEN=${{ secrets.VAULT_TOKEN }} UNOTOKEN_URL=https://vault.example.com unotoken exec --prefix myapp/ -- npm run buildFallback Pattern
If the vault might be unavailable (e.g., a new team member who hasn't set up the vault yet), use a fallback to .env.local:
{
"scripts": {
"dev": "unotoken exec --prefix myapp/ -- next dev || (echo 'Vault unavailable, using .env.local fallback' && next dev)",
"build": "unotoken exec --prefix myapp/ -- next build || (echo 'Vault unavailable, using .env.local fallback' && next build)"
}
}This runs with vault secrets when available, and falls back to any .env.local file on disk when the vault is locked or unreachable.
Moving from .env to unotoken
A step-by-step migration guide for replacing hand-maintained .env files with vault-backed secrets.
Step 1: Import existing secrets into the vault
# Start the vault if not running
unotoken serve &
unotoken unlock
# Import each secret from your .env.local
unotoken set myapp/database/url "postgresql://user:pass@host/db"
unotoken set myapp/stripe/secret/key "sk_live_..."
unotoken set myapp/next/public/stripe/publishable/key "pk_live_..."
# Or bulk-import from a .env file (one per line)
while IFS='=' read -r key value; do
# Convert KEY_NAME to key/name path format
path=$(echo "$key" | tr '[:upper:]' '[:lower:]' | tr '_' '/')
unotoken set "myapp/$path" "$value"
done < .env.localStep 2: Create a scoped token
# Create a token restricted to your app's prefix
unotoken token create --name myapp-dev --prefix myapp/
# Save the displayed token -- it's only shown once
# For CI/CD, store it as a GitHub Actions secret (VAULT_TOKEN)Step 3: Update package.json scripts
Replace direct commands with unotoken exec:
{
"scripts": {
- "dev": "next dev",
- "build": "next build"
+ "dev": "unotoken exec --prefix myapp/ -- next dev",
+ "build": "unotoken exec --prefix myapp/ -- next build"
}
}Step 4: Update .gitignore
If using dotenv mode (generating .env.local from the vault), add the generated file to .gitignore since it's now a build artifact:
# Generated by unotoken (vault-backed, not hand-maintained)
.env.localStep 5: Remove .env.local
Once you've verified everything works with vault-backed secrets:
# Verify vault injection works
npm run dev # should start with all env vars injected
# Remove the old .env.local
rm .env.localPath-to-Variable Mapping Reference
Vault paths are converted to environment variable names by:
- Stripping the prefix (e.g.,
myapp/) - Replacing
/with_ - Uppercasing the result
| Vault Path | Env Variable |
|------------|-------------|
| myapp/stripe/secret/key | STRIPE_SECRET_KEY |
| myapp/database/url | DATABASE_URL |
| myapp/next/public/stripe/publishable/key | NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY |
| myapp/resend/api/key | RESEND_API_KEY |
Architecture
yokotoken (MIT, public)
|
+-- unotoken (private, cloud add-on)
|-- Re-exports all yokotoken API
|-- OAuth PKCE browser flow engine
|-- Google + GitHub OIDC/OAuth providers
|-- Wrapped key storage for OAuth-derived unlock
|-- Device signatures (email-verified device approval)
|-- CLI extensions (auth link, auth unlink, auth list, auth config)Upstream updates are clean: just bump the yokotoken version in package.json.
License
UNLICENSED (private)
