Install
openclaw skills install tene-cliLocal-first encrypted secret management with the tene CLI. Activate when the user mentions secrets, API keys, credentials, tokens, .env files, environment variables, or asks to run a command that needs secrets injected. Enforces strict AI safety rules (never print plaintext, never read .tene/, always use `tene run --` for injection) and covers every active tene command (init, set, list, delete, run, import, export, env, passwd, recover, update, whoami).
openclaw skills install tene-cliTene 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.
Activate this skill when the user:
.env files, environment variables, or process.env.* / os.Getenv / os.environ[...]npm start, go run, pytest, next dev, docker compose up).tene/ directory, a CLAUDE.md referencing tene, or a install.sh pointing at https://tene.sh.env filesDo 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.).
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 export without --encrypted. Plain tene export dumps
every secret as a .env-formatted blob to stdout. Use
tene export --encrypted --file backup.tene.enc for backups; for
inspection, use tene 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 is CLAUDE.md at the repo root (auto-generated by
tene init).
NEVER pass secret values as CLI arguments. They appear in ps, shell
history, and system logs. Always inject via tene run -- instead.
Use tene list to 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.
# 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.
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 (or AGENTS.md, .windsurfrules, etc. per flags).tene/.gitignore — auto-excludes the vault from gitAfter 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.mdtene 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.
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):
^[A-Z][A-Z0-9_]*$PATH) are rejectedIf the user types a lowercase name, advise conversion to UPPER_SNAKE_CASE.
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.
.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
# 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.
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.
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).
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.
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
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 | — | ✅ |
| 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) |
| 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.
| 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.
zalando/go-keyring; file fallback at ~/.tene/keyfile (mode 0600).tene/ directory; no ~/.tene/vaults/ aggregatorWhen the user asks about anything not covered above, prefer referring them to the official docs over guessing.