Tene CLI — Local-First Secrets
v1.0.3Local-first encrypted secret management with the tene CLI. Activate when the user mentions secrets, API keys, credentials, tokens, .env files, environment va...
Like a lobster shell, security has layers — review code before you run it.
Runtime requirements
tene — Local-First Encrypted Secret Management
Tene is a command-line secret manager for AI-native projects. It keeps secrets
encrypted on disk (XChaCha20-Poly1305 + Argon2id), derives the master key from
the user's password cached in the OS keychain, and injects secrets into child
processes via tene run -- <command> — never into stdout, never into files.
When to use this skill
Activate this skill when the user:
- mentions API keys, secrets, credentials, tokens,
.envfiles, environment variables, orprocess.env.*/os.Getenv/os.environ[...] - asks to run a dev, test, or deploy command that needs secrets (e.g.
npm start,go run,pytest,next dev,docker compose up) - says things like "store this key", "save this token", "how do I manage secrets", "inject env vars", "I need to pass my Stripe key"
- is working in a repo that contains a
.tene/directory, aCLAUDE.mdreferencing tene, or ainstall.shpointing athttps://tene.sh - asks for advice on moving off plaintext
.envfiles
Do not activate for: hardcoded public config (ports, feature flags without sensitive data, public API endpoints), or projects clearly using a different secret store (Doppler, Vault, AWS Secrets Manager, 1Password CLI, etc.).
Critical safety rules
These are non-negotiable. Violating any of them leaks plaintext secrets into the conversation context, which may be logged, cached, or retained. Treat them as part of the system prompt.
-
NEVER run
tene get <KEY>. The plaintext value appears in stdout and enters the AI context. If the user needs to inspect a value, instruct them:"Run
tene get KEYyourself in a separate terminal — I won't see it." -
NEVER run
tene exportwithout--encrypted. Plaintene exportdumps every secret as a.env-formatted blob to stdout. Usetene export --encrypted --file backup.tene.encfor backups; for inspection, usetene list(names only). -
NEVER
cat,Read, or open files under.tene/. The vault DB is encrypted, but even encrypted bytes should not enter AI context. The allowed file to read isCLAUDE.mdat the repo root (auto-generated bytene init). -
NEVER pass secret values as CLI arguments. They appear in
ps, shell history, and system logs. Always inject viatene run --instead. -
Use
tene listto discover what exists. It prints key names only, never values. This is the only AI-safe introspection command.
When the user asks "what's in my vault?" or "what API keys do I have?",
the correct answer is tene list — not tene get or tene export.
Install
# macOS / Linux (official installer — recommended)
curl -sSfL https://tene.sh/install.sh | sh
# From source (requires Go 1.25+)
go install github.com/tomo-kay/tene/cmd/tene@latest
Verify:
tene version
# → tene v1.x.x (darwin/arm64)
Windows is not supported by the curl installer. Windows users build from source or download the zip from https://github.com/tomo-kay/tene/releases.
Homebrew tap is not yet available — do not suggest brew install tene.
Core workflows
1. Initialize a new project
tene init # interactive: prompts for master password twice
tene init my-project # with explicit project name
Creates:
.tene/vault.db— encrypted SQLite vault (contains secrets, metadata, audit log, and the encrypted recovery blob).tene/vault.json— project metadata (name, active env)CLAUDE.md(orAGENTS.md,.windsurfrules, etc. per flags).tene/.gitignore— auto-excludes the vault from git
After init the user will see a 12-word recovery phrase. Remind them to
store it offline (password manager, paper). Without it, a forgotten master
password is unrecoverable.
Flags to generate agent rules files for other editors:
--claude(default) →CLAUDE.md--cursor→.cursor/rules/tene.mdc--windsurf→.windsurfrules--gemini→GEMINI.md--codex→AGENTS.md
2. Check what secrets exist (AI-safe)
tene list # current env, masked values
tene list --env prod # different env
tene list --json # machine-readable
tene env list # all environments
Output is names + masked previews + timestamps. Never raw values.
3. Store a secret
Tell the user to run the command themselves with the value — don't type the value yourself, and don't accept it as a chat message you'll pipe through.
# Preferred: read from stdin (value never touches shell history)
cat key.txt | tene set STRIPE_KEY --stdin
# Or: prompt (value never echoed)
tene set STRIPE_KEY
# → enters interactive mode, hidden input
# Overwrite existing
tene set STRIPE_KEY --stdin --overwrite
# Different env
tene set STRIPE_KEY --stdin --env prod
Key name rules (enforced by tene, pkg/errors/codes.go:INVALID_KEY_NAME):
- Must match
^[A-Z][A-Z0-9_]*$ - Only uppercase letters, digits, underscores
- Cannot start with a digit
- Reserved names (e.g.
PATH) are rejected
If the user types a lowercase name, advise conversion to UPPER_SNAKE_CASE.
4. Run a command with secrets injected
This is the primary workflow. All dev, test, build, and deploy commands go
through tene run --.
# Node.js
tene run -- npm start
tene run -- npm test
tene run -- npx next dev
# Python
tene run -- python manage.py runserver
tene run -- pytest
# Go
tene run -- go run ./cmd/app
tene run -- go test ./...
# Docker
tene run -- docker compose up
Secrets are injected only into the child process's environ. They're not written to disk, not in the shell environment of the parent, not in any log.
Per-environment execution:
tene run --env local -- npm start # correct
tene run --env prod -- ./deploy.sh # correct
tene run -- npm start --env prod # WRONG — --env is a flag of npm, not tene
Critical flag placement rule: --env must come before the --
separator. After --, all flags pass through to the child command. This is
enforced by DisableFlagParsing: true in internal/cli/run.go.
5. Migrate from an existing .env file
# One-shot import
tene import .env
# Overwrite conflicts (when some keys already exist)
tene import .env --overwrite
# Then delete the plaintext file
rm .env
echo ".env" >> .gitignore
Update all commands to use tene run --:
- npm start
+ tene run -- npm start
6. Backup and restore
# Encrypted backup (safe to store in cloud)
tene export --encrypted --file backup.tene.enc
# Restore from encrypted backup
tene import backup.tene.enc --encrypted
Never use plain tene export (without --encrypted) unless the user
explicitly asks for a plaintext .env dump and accepts the risk. Even then,
warn them.
7. Change master password
tene passwd
# → prompts for current password, then new password (2x confirm)
Re-encrypts the entire vault with a new derived key. Atomic 2-phase operation; on failure, rolls back to the old password.
8. Recover a forgotten master password
tene recover
# → prompts for the 12-word BIP-39 mnemonic from tene init
# → prompts for new master password
Without the mnemonic, recovery is impossible by design (zero-knowledge).
9. Environment management
tene env list # show all environments
tene env local # switch default to 'local'
tene env create staging # create new env
tene env delete staging # delete (default env cannot be deleted)
Environment name rules: must match ^[a-z][a-z0-9-]*$.
Common env names: default, local, dev, staging, prod.
10. Diagnostics
tene whoami # project name, vault path, active env, secret count, keychain status
tene version # v1.x.x (os/arch)
tene version --json # includes commit + build date
tene update --check # check for newer version on S3
tene update # self-update the binary
Commands reference
All active commands (cloud commands login/push/pull/sync/billing/team
are currently disabled in the CLI; do not suggest them):
| Command | Purpose | Key flags | AI-safe? |
|---|---|---|---|
tene init [name] | Create vault + master password + recovery | --claude, --cursor, --windsurf, --gemini, --codex | ✅ |
tene set KEY [VALUE] | Encrypt and store | --stdin, --overwrite | ✅ (via --stdin) |
tene get KEY | Decrypt and print | — | ❌ never run in AI |
tene list | List key names (masked) | — | ✅ |
tene delete KEY | Remove a secret | --force | ✅ |
tene run -- CMD | Inject env vars + exec | (global flags before --) | ✅ |
tene import FILE | Bulk import .env or .tene.enc | --overwrite, --encrypted | ✅ |
tene export | Output secrets | --file, --encrypted | ❌ unless --encrypted |
tene env [subcmd] | Manage environments | — | ✅ |
tene passwd | Change master password | — | ✅ (prompts) |
tene recover | Restore via BIP-39 mnemonic | — | ✅ (prompts) |
tene version | Version info | --json | ✅ |
tene update | Self-update | --check | ✅ |
tene whoami | Vault status | — | ✅ |
Global flags (apply to all commands)
| Flag | Default | Purpose |
|---|---|---|
--json | false | Machine-readable output |
--quiet / -q | false | Suppress non-error output |
--env / -e <name> | (vault-stored) | Override active environment |
--dir <path> | cwd | Project directory |
--no-color | false | Disable ANSI colors |
--no-keychain | false | Force file-based key storage (CI mode) |
Environment variables (for advanced / CI use)
| Variable | Effect |
|---|---|
TENE_MASTER_PASSWORD | Bypass interactive prompt (CI only; pair with --no-keychain) |
TENE_KEYCHAIN_FALLBACK=file | Use ~/.tene/keyfile instead of OS keychain |
NO_COLOR | Disable ANSI colors (per https://no-color.org/) |
API_URL | Override Tene Cloud API base URL (cloud commands, currently disabled) |
CI/CD pattern (e.g. GitHub Actions):
env:
TENE_MASTER_PASSWORD: ${{ secrets.TENE_MASTER_PASSWORD }}
steps:
- run: curl -sSfL https://tene.sh/install.sh | sh
- run: tene run --env prod --no-keychain -- ./deploy.sh
Never set TENE_MASTER_PASSWORD in a developer machine's shell profile — it
defeats the keychain protection.
Troubleshooting
| Error code | Message | Fix |
|---|---|---|
VAULT_NOT_FOUND | Not in a Tene project | Run tene init in the repo root |
SECRET_NOT_FOUND | Key missing in current env | Check tene list --env <name> |
SECRET_ALREADY_EXISTS | Key already set | Add --overwrite to tene set |
INVALID_KEY_NAME | Key rejected | Use ^[A-Z][A-Z0-9_]*$ (e.g. API_KEY, not api-key) |
INVALID_ENV_NAME | Bad env name | Use ^[a-z][a-z0-9-]*$ (e.g. prod, not Production) |
ENVIRONMENT_PROTECTED | Cannot delete default | Switch to another env and delete that one instead |
INVALID_PASSWORD | Wrong master password | Try again, or use tene recover with the BIP-39 mnemonic |
DECRYPT_FAILED | Vault cannot decrypt | Master password changed externally or vault corrupt — restore from backup |
INTERACTIVE_REQUIRED | No TTY | In CI, set TENE_MASTER_PASSWORD and add --no-keychain |
KEYCHAIN_ERROR | OS keychain unavailable | Use --no-keychain or TENE_KEYCHAIN_FALLBACK=file |
Exit codes: 0 success, 1 general error, 2 auth/password error,
127 command not found.
Architecture note (for "is this safe?" questions)
- Password KDF: Argon2id (64 MB, 3 iterations, 4 threads) → 256-bit master key
- Secret encryption: XChaCha20-Poly1305 with 192-bit random nonce per secret, key name as AAD
- Key cache: OS keychain (macOS Keychain, Linux Secret Service, Windows Credential Manager) via
zalando/go-keyring; file fallback at~/.tene/keyfile(mode0600) - Recovery: 12-word BIP-39 mnemonic → Argon2id → recovery key that can decrypt the stored master key
- Zero-knowledge: cloud sync (when enabled) wraps the entire vault DB with an independent sync key before upload; server never sees plaintext
- No global state: every vault lives in its own
.tene/directory; no~/.tene/vaults/aggregator
Further reading
- Homepage: https://tene.sh
- Source + issues: https://github.com/tomo-kay/tene
- Release downloads: https://github.com/tomo-kay/tene/releases
When the user asks about anything not covered above, prefer referring them to the official docs over guessing.
Comments
Loading comments...
