Skip to content

Commands

Overview

Terminal window
hush <command> [options]

Hush keeps secrets encrypted at rest. The primary way to use secrets is hush run -- <command>, which decrypts to memory and injects environment variables—secrets never touch the disk.

Command Categories

CategoryCommandsDescription
Primary (AI-Safe)run, materialize, set, copy-key, move-key, edit, inspect, hasSafe command surfaces that avoid printing secret values
Setupbootstrap, config, init, migrate, encrypt, status, doctor, skillRepository setup, diagnostics, and bounded migration, with encrypt kept only as a retired legacy bridge
Topologyfile, bundle, targetManage the files, bundles, and targets that define what secrets go where
Deploymentpush, checkCI/CD and cloud deployment
Debuggingresolve, trace, verify-target, diff, export-exampleDebug safe output, provenance, target completeness, and example generation

Global Options

OptionDescription
-e, --env <env>Environment: development or production (default: development)
-r, --root <dir>Start directory for project mode, execution directory for run
--globalUse the explicit global store at ~/.hush
-h, --helpShow help message
-v, --versionShow version number

Update check

Hush checks for new versions once per day via a standard HTTP request to the npm registry. No telemetry is sent. To disable, set HUSH_NO_UPDATE_CHECK=1 (also respects NO_UPDATE_NOTIFIER=1 and any CI environment variable).


run

Run a command with secrets injected as environment variables. This is the primary way to use secrets. Secrets are decrypted to memory only—they never touch the disk.

Terminal window
# Run with development secrets (default)
hush run -- npm start
# Run with a specific v3 target
hush run -t api -- wrangler dev
hush run --target web -- npm start
# Run with global secrets only
hush run --global -- npm start

Options

OptionDescription
-t, --target <name>Resolve a specific v3 target; otherwise Hush uses runtime or the only non-example target
-- <command>The command to run (everything after --)

How It Works

  1. Loads the v3 repository from .hush/
  2. Resolves the selected target through the shared resolver/materializer
  3. Decrypts to memory only and emits audit events
  4. Spawns the child process with the resolved environment
  5. Cleans up any staged runtime state automatically on failure or signals

When --global is used, Hush reads only from ~/.hush. It does not merge project secrets.

Examples

Terminal window
# Local development
hush run -- npm run dev
# Wrangler with API secrets only
hush run -t api -- wrangler dev
# Docker with secrets
hush run -- docker compose up
# Any command that needs secrets
hush run -- node scripts/migrate.js

materialize

Write a v3 target or bundle to explicit file paths for CI, native build tooling, or other file-based consumers.

This command uses the existing v3 resolver/materializer, but unlike hush run it persists the resulting plaintext files under a chosen output root so later steps can import them into keychains, provisioning-profile directories, or other tools.

Terminal window
# Materialize a target to an explicit output root
hush materialize -t ios-signing --json --to /tmp/fitbot-signing
# Materialize for the lifetime of one command, then auto-clean
hush materialize -t ios-signing --to /tmp/fitbot-signing -- bash scripts/ci/install-ios-signing.sh /tmp/fitbot-signing
# Materialize a bundle directly
hush materialize --bundle fitbot-signing --to /tmp/fitbot-signing
# Clean up a previously materialized output root
hush materialize --cleanup --to /tmp/fitbot-signing

Options

OptionDescription
-t, --target <name>Resolve a specific v3 target; otherwise Hush uses runtime or the only non-example target
--bundle <name>Materialize a bundle directly instead of selecting a target
--jsonEmit machine-readable JSON describing written paths and cleanup hints
--format <dotenv>Render env output in dotenv format (default)
--output-root <dir> / --to <dir>Output root for persisted plaintext artifacts (default: .hush-materialized/)
--cleanupRemove the materialized output root instead of writing files
-- <command>Run a child command with the materialized files available, then auto-clean the output root when it exits

Artifact path metadata

Artifact entries can control where they land under the chosen output root:

  • filename — override the final filename
  • subpath — override the output directory under the root
  • materializeAs — override the full relative output path

Targets can also declare the same metadata for the target artifact itself.

How It Works

  1. Loads the v3 repository and resolves the selected target or bundle
  2. Materializes file and binary artifacts using the shared runtime pipeline in persisted mode
  3. Writes files under the chosen output root with restrictive permissions
  4. If a child command is provided after --, runs that command with the resolved env plus HUSH_MATERIALIZE_OUTPUT_ROOT available and auto-cleans on exit
  5. Otherwise emits an audit event and returns only metadata/path information to stdout
  6. Leaves manual cleanup available via hush materialize --cleanup when you intentionally need a longer-lived output root

JSON output shape

--json returns metadata only — never raw secret contents. It includes full metadata like repositoryRoot, files, logicalPaths, hashes, and path details. This makes it suitable for CI pipelines that need a stable handoff surface without logging private values.

Auto-cleaning command flow

When you append a command after --, Hush treats the materialized files as ephemeral job-scoped state:

Terminal window
hush materialize -t ios-signing --to /tmp/fitbot-signing -- bash scripts/ci/install-ios-signing.sh /tmp/fitbot-signing

In that mode Hush:

  1. writes the files under the chosen output root
  2. runs the child command
  3. deletes the materialized output root afterwards, even if the command fails

Use this mode for signing, certificates, provisioning profiles, or other native-tooling flows that only need the files for one step.


set

Set a secret. Supports inline values, interactive prompts, or piped input.

Terminal window
# Inline value (recommended for AI agents)
hush set DATABASE_URL "postgres://user:pass@host/db"
hush set STRIPE_KEY "sk_live_xxx" -e production
hush set ELEVENLABS_API_KEY "$ELEVENLABS_API_KEY" --global
hush set FEATURE_FLAG "enabled" --local
# Write to a specific file using --file
hush set DATABASE_URL "postgres://user:pass@host/db" --file env/project/local
hush set API_KEY "sk_test_xxx" --file local
# Write to repo-local file using --repo-local shorthand
hush set DATABASE_URL "postgres://user:pass@host/db" --repo-local
# Interactive prompt (for users)
hush set DATABASE_URL
hush set API_KEY -e production
# Set in machine-local overrides
hush set MY_OVERRIDE --local
# Set in the explicit global store
hush set --global OPENAI_API_KEY
# Bootstrap the global store without a separate init step
hush keys generate --global
hush set --global OPENAI_API_KEY
# Piped input (for scripts/automation)
echo "my-secret" | hush set MY_KEY
cat cert.pem | hush set CERTIFICATE

Options

OptionDescription
-e, --env <env>Write to env/project/development or env/project/production instead of the shared v3 file
--localWrite to the machine-local override document under ~/.hush/state/projects/<slug>/user/local-overrides.encrypted
--file <path-or-alias>Write to a specific file: full path (env/project/local) or alias (shared, development, production, local)
--repo-localShorthand for --file env/project/local
--guiUse a native GUI dialog for input instead of terminal or piped input
--globalSet the secret in ~/.hush instead of the current project store

Input Methods (Priority Order)

  1. Inline value: hush set KEY "value" - value provided directly
  2. Piped input: echo "value" | hush set KEY - reads from stdin
  3. GUI dialog: Opens when --gui flag is used
  4. Interactive prompt: Terminal prompt with visible input

Flags can appear before or after the optional inline value. These are equivalent:

Terminal window
hush set --global API_KEY "value"
hush set API_KEY "value" --global

How It Works

  1. Gets the value from inline arg, pipe, GUI, or prompt
  2. Loads the v3 repository and requires an active identity
  3. Shows planned destination before writing (e.g., will write DATABASE_URL -> env/project/local)
  4. Updates the selected .hush/files/**.encrypted document, or the machine-local override doc
  5. Writes the YAML document back in place and emits a write audit event
  6. Confirms success with destination file and scope

For global mode, Hush binds global writes to ~/.hush/.sops.yaml explicitly, so running hush set --global from inside another repo still uses the global recipient instead of a repo-local .sops.yaml.

Conflict Detection

When writing to shared, Hush checks if the key already exists in local, development, or production. If a conflict is detected, it warns:

will write DATABASE_URL -> env/project/shared
⚠ DATABASE_URL already exists in env/project/local

Example Output

$ hush set DATABASE_URL "postgres://localhost/mydb"
will write DATABASE_URL -> env/project/shared
✓ DATABASE_URL set in env/project/shared (25 chars)
$ hush set API_KEY
Enter value for API_KEY: sk_test_xxx
will write API_KEY -> env/project/shared
✓ API_KEY set in env/project/shared (14 chars)
$ hush set DATABASE_URL "postgres://localhost/mydb" --repo-local
will write DATABASE_URL -> env/project/local
✓ DATABASE_URL set in env/project/local (25 chars)

TTY Input

When entering secrets interactively, trailing newlines (\r, \n) are automatically trimmed to prevent issues with trailing whitespace in secrets.


copy-key / move-key

Copy or move one key between encrypted v3 file documents without printing the decrypted value. Use this when a secret exists in Hush but belongs in a different service/environment file for target resolution.

Terminal window
# Copy a shared production key into the API production file
hush copy-key RESEND_API_KEY --from env/project/production --to env/api/production
# Move a key when ownership should change to the destination service file
hush move-key RESEND_API_KEY --from env/project/production --to env/api/production
# JSON metadata only; no values
hush copy-key RESEND_API_KEY --from env/project/production --to env/api/production --json

Options

OptionDescription
--from <file-path>Source encrypted v3 file path, such as env/project/production
--to <file-path>Destination encrypted v3 file path, such as env/api/production
--jsonOutput action metadata without values

How It Works

  1. Requires an owner active identity because the command mutates encrypted repository documents
  2. Finds exactly one matching leaf key in the source file
  3. Writes the same encrypted entry under the destination file path
  4. Deletes the source entry only for move-key
  5. Re-encrypts touched file documents and emits audit metadata

copy-key and move-key do not edit bundle imports. They are explicit ownership/location changes. If multiple services intentionally share one secret, keep it in a project-* file and add explicit bundle imports instead.


delete-key

Delete one key from an encrypted v3 file document. This is the safe way to remove accidental or unwanted secrets from Hush.

Terminal window
# Delete a key from shared secrets
hush delete-key OLD_API_KEY --from env/project/shared
# Delete a key with JSON output
hush delete-key OLD_API_KEY --from env/project/shared --json
# Delete without confirmation prompt (for automation)
hush delete-key OLD_API_KEY --from env/project/shared --yes

Options

OptionDescription
--from <file-path>Source encrypted v3 file path, such as env/project/shared (required)
--yesSkip confirmation prompt
--jsonOutput action metadata without values

How It Works

  1. Requires an owner active identity because the command mutates encrypted repository documents
  2. Finds exactly one matching leaf key in the specified file
  3. Shows preview of what will be deleted
  4. Prompts for confirmation (unless --yes)
  5. Removes the key from the document
  6. Re-encrypts the file and emits audit metadata

Example Output

$ hush delete-key OLD_API_KEY --from env/project/shared
This will delete OLD_API_KEY from env/project/shared
Type "yes" to confirm: yes
ok: true
action: delete
key: OLD_API_KEY
from: env/project/shared
logicalPath: env/project/shared/OLD_API_KEY

edit

Edit a v3 document through a decrypted temporary YAML file in your $EDITOR.

Terminal window
# Edit shared secrets
hush edit
# Edit environment-specific secrets
hush edit development
hush edit production
hush edit dev
hush edit prod
# Edit local overrides
hush edit local
# Edit the explicit global store
hush edit --global
# Use a specific editor for this session
hush edit shared --editor vim
hush edit shared --editor "code --wait"
# Use EDITOR environment variable
EDITOR=nano hush edit shared

Options

OptionDescription
--editor <command>Override editor for this session (e.g., vim, code --wait)
--globalEdit secrets in ~/.hush instead of the current project store

How It Works

  1. Resolves the target v3 document (shared, development, production, or machine-local local)
  2. Decrypts that document to a temporary YAML file
  3. Opens the temp file in $EDITOR (or --editor override), validates it on close, then re-encrypts it back to the .encrypted path
  4. Logs the resolved editor command for debugging
  5. Emits a write audit event

When --global is used, the edit flow also binds to ~/.hush/.sops.yaml, even if the current working directory has its own .sops.yaml.

Editor Resolution

Hush resolves the editor in this order:

  1. --editor flag (if provided)
  2. EDITOR environment variable
  3. vi (default fallback)

bootstrap

Bootstrap a v3 repository rooted at .hush/.

Terminal window
hush bootstrap
# Bootstrap the explicit global store
hush bootstrap --global
# Force a child-local repo even when a parent .hush/ exists
hush bootstrap --new-repo
# Skip interactive confirmation (CI / non-interactive)
hush bootstrap --yes

hush bootstrap is the canonical setup flow for Task 6. It creates the initial v3 repository layout, .sops.yaml, default identities, bundle and target shells, and the machine-local active identity pointer. A fresh bootstrap should leave both hush status and hush inspect ready to run without extra repair steps.

When run inside a nested git repository, bootstrap walks upward to find an existing parent .hush/ repository and joins it. Use --new-repo to force a child-local repository instead. Use --yes (or -y) to skip the interactive confirmation prompt in non-interactive mode.

When package metadata does not declare a project identifier, bootstrap falls back to the repo basename (for example bottown) instead of inventing a nested local/<repo> identity.

Created layout

.hush/
manifest.encrypted
files/
env/project/shared.encrypted
.sops.yaml
~/.hush/state/projects/<project-slug>/active-identity.json

Default identities and shells

  • Identities: owner-local, member-local, ci
  • Bundle shell: project
  • Target shells: runtime, example
  • Initial active identity: owner-local

config

Inspect or update v3 structural config without treating plaintext YAML as the source of truth.

Terminal window
hush config show
hush config show --json
hush config show identities
hush config active-identity
hush config active-identity member-local
hush config readers env/project/shared --roles owner,member,ci
hush config readers env/project/shared --identities owner-local,ci

Subcommands

SubcommandDescription
show [section]Show v3 structure. Sections: manifest, identities, bundles, targets, imports, files, state
active-identity [name]Show the current active identity, or set it when a name is provided
readers <file-path> --roles <csv> --identities <csv>Update the file-scoped readers on one encrypted file

Notes

  • show returns structural data only. It does not materialize secret values.
  • Add --json to show for machine-readable structural output. JSON output includes paths, readers, bundles, targets, and state metadata only — never decrypted values.
  • active-identity writes machine-local state under ~/.hush/state/projects/<project-slug>/active-identity.json.
  • readers updates one ACL unit at a time, because files are the only ACL boundary in v3.

Topology Management

Files, bundles, and targets form a three-layer hierarchy. Build from the bottom up and tear down from the top.

Files → Bundles → Targets
  • Files are the encrypted documents that hold secret entries. Each file has its own ACL (readers).
  • Bundles package one or more file references together. A bundle is the unit of access for a target.
  • Targets consume a bundle and declare how secrets are materialized (format, mode, filename).

All topology mutations require the owner role. Reference validation runs before any disk write, so dangling refs are rejected before encryption.

Lifecycle example

Terminal window
# 1. Create an encrypted file
hush file add env/api/production --roles owner,ci
# 2. Create a bundle that references it
hush bundle add api-production --files env/api/production
# 3. Create a target that consumes the bundle
hush target add api-production --bundle api-production --format dotenv
# 4. Verify the target resolves
hush verify-target api-production --require DATABASE_URL
# 5. Teardown in reverse order
hush target remove api-production
hush bundle remove api-production
hush file remove env/api/production

file

Manage encrypted file documents in the v3 repository.

Terminal window
# Add a new encrypted file
hush file add env/api/production --roles owner,ci
hush file add env/api/staging --roles owner,member,ci
hush file add env/project/shared --identities owner-local,ci
# Update readers on an existing file
hush file readers env/api/production --roles owner,ci
hush file readers env/api/production --identities owner-local,ci
# List all files
hush file list
hush file list --json
# Remove a file
hush file remove env/api/staging
hush file remove env/api/production --keep-file

Subcommands

SubcommandDescription
add <namespaced-path>Create a new encrypted file document with the given readers
remove <namespaced-path>Remove a file from the manifest and delete the encrypted disk file
listList all encrypted files with their readers
readers <namespaced-path>Update the ACL readers on an existing file

Options for file add and file readers

OptionDescription
--roles <csv>Comma-separated roles: owner, member, ci
--identities <csv>Comma-separated identity names

Options for file remove

OptionDescription
--keep-fileRemove only the manifest entry; leave the encrypted disk file in place

Safety semantics

  • Requires the owner role. Non-owner identities cannot add, remove, or update files.
  • file remove fails if the file is still referenced by any bundle. Remove the bundle first.
  • All mutations emit metadata_change audit events.

bundle

Manage bundles of encrypted file references.

Terminal window
# Create a bundle from explicit file refs
hush bundle add api-production --files env/api/production
hush bundle add api-production --files env/api/production,env/project/shared
# Add or remove a file from an existing bundle
hush bundle add-file api-production env/project/shared
hush bundle remove-file api-production env/project/shared
# List all bundles
hush bundle list
hush bundle list --json
# Remove a bundle
hush bundle remove api-production

Subcommands

SubcommandDescription
add <name>Create a new bundle, optionally with an initial file list
add-file <bundle> <file>Add a file reference to an existing bundle
remove-file <bundle> <file>Remove a file reference from a bundle
remove <name>Remove a bundle from the manifest
listList all bundles with their file references

Options for bundle add

OptionDescription
--files <csv>Comma-separated namespaced file paths to include in the bundle

Safety semantics

  • Requires the owner role. All bundle mutations emit metadata_change audit events.
  • bundle add validates that every file path exists in the file index before writing.
  • Duplicate file refs in --files are rejected.
  • bundle remove fails if the bundle is still referenced by any target. Remove the target first.

target

Manage targets in the v3 repository.

Terminal window
# Add a target
hush target add api-production --bundle api-production --format dotenv
hush target add ios-signing --bundle ios-signing --format json --mode file
hush target add web-example --bundle web --format dotenv --mode example
# List all targets
hush target list
hush target list --json
# Remove a target
hush target remove api-production

Subcommands

SubcommandDescription
add <name>Create a new target
remove <name>Remove a target from the manifest
listList all targets with their bundle and format

Options for target add

OptionDescription
--bundle <name>Required. The bundle this target consumes
--format <format>Required. Output format: dotenv, json, wrangler
--mode <mode>Materialization mode: process (default), file, example
--filename <name>Override the output filename for file-mode targets
--subpath <path>Override the output subdirectory under the materialization root
--materialize-as <name>Override the full relative output path

Safety semantics

  • Requires the owner role.
  • target add validates that the referenced bundle exists before writing.
  • target remove fails if the target name is not found.
  • All mutations emit metadata_change audit events.

init

Deprecated alias for hush bootstrap.

Terminal window
hush init

This command still works for one transition cycle, but it only prints a deprecation warning and delegates to hush bootstrap. New setup flows should use hush bootstrap directly.


encrypt

Retired legacy bridge. This command no longer performs general encryption work outside the migration path.

Terminal window
hush encrypt

Current behavior

hush encrypt now fails fast and points you to hush migrate --from v2. The v3 CLI no longer preserves a standalone plaintext-to-encrypted runtime bridge outside migration.


inspect

Inspect readable v3 logical paths with redaction applied. Safe for AI agents.

Terminal window
hush inspect
hush inspect -e production
hush inspect --json

Options

OptionDescription
-e, --env <env>Environment: development or production
--jsonOutput machine-readable JSON (never includes secret values)

Example Output

Hush inspect
Active identity: owner-local
Readable files: 2
Unreadable files: 1
Readable entries:
env/app/shared (roles=owner,member identities=owner-local,member-local)
env/apps/web/env/NEXT_PUBLIC_API_URL
kind=value exposure=visible
https://api.example.com
env/app/secrets (roles=owner identities=owner-local)
env/apps/api/env/STRIPE_SECRET_KEY
kind=value exposure=sensitive
[redacted]
Unreadable files:
env/app/ci-only (roles=ci identities=ci)

JSON output shape

--json returns { target, entries: [{ key, file, sensitive, set, value? }] }. The value field is included only for entries where sensitive: false. Sensitive entries never have a value field.

inspect reports the v3 file boundary directly. It does not summarize legacy source files or target filters.


has

Check if a specific secret resolves to a non-empty value. Returns exit code 0 if set, 1 if missing or empty.

Terminal window
# Check if a variable is set
hush has DATABASE_URL
# Quiet mode (no output, just exit code)
hush has API_KEY -q
# Use in scripts
hush has DATABASE_URL -q && echo "DB configured"
# Machine-readable output for agents and scripts
hush has DATABASE_URL --json

Options

OptionDescription
-q, --quietSuppress output, only return exit code
--jsonEmit { key, target, exists, declared } (never values)

Example Output

DATABASE_URL is set (45 chars)

If the key exists but resolves to an empty value:

DATABASE_URL exists but is empty

Or if not set:

DATABASE_URL not found in target runtime

has is a target-runtime presence check. It does not validate whether a value is a real credential versus a placeholder, example, or template string.


push

Push resolved v3 target output to Cloudflare Workers.

Terminal window
# Push all configured targets
hush push
# Push a specific target
hush push -t api
hush push -t app
# Preview without pushing
hush push --dry-run
# Detailed preview showing each variable
hush push --dry-run --verbose
hush push -t app --dry-run --verbose

Options

OptionDescription
-t, --target <name>Push only the specified target
--dry-runPreview what would be pushed without making changes
--verboseShow detailed output (with --dry-run)

Supported Destinations

Target TypeConfigurationWrangler Command
Cloudflare Workersformat: wranglerwrangler secret put

Configuration Examples

Cloudflare Workers (automatic with format: wrangler):

targets:
api:
bundle: api-runtime
format: wrangler

Legacy push_to: cloudflare-pages mappings are not part of the v3 runtime target model. Migrate those repos first, then rewrite the deployment flow explicitly.

How It Works

  1. Loads the v3 repository and resolves a wrangler-formatted target through the shared materializer
  2. Emits the same audit-safe materialization flow used by hush run
  3. Pushes each resolved environment variable with wrangler secret put

status

Show repository and machine-local v3 state. This is the first command to run when troubleshooting.

Terminal window
hush status
# Inspect the explicit global store
hush status --global
# Machine-readable output
hush status --json

Options

OptionDescription
--jsonOutput structured JSON with repository readiness, paths, counts, and machine-local state (never includes secret values)

Example Output

Hush status
Repository: ready
Root: /path/to/repo
Store: project (/path/to/repo)
Manifest: /path/to/repo/.hush/manifest.encrypted
Files root: /path/to/repo/.hush/files
Active identity: owner-local
Repository state:
kind: v3
manifest files: 1
encrypted files: 3
identities: 3
bundles: 2
targets: 2
imports: 0
Machine-local state:
project slug: myorg-myrepo-1234abcd
state root: ~/.hush/state/projects/myorg-myrepo-1234abcd
active identity path: ~/.hush/state/projects/.../active-identity.json (present)
audit log path: ~/.hush/state/projects/.../audit.jsonl (present)

If the repo still uses hush.yaml, status reports that migration is required before relying on normal v3 command flows.

Troubleshooting with status

You SeeMeaningFix
Repository: missingNo v3 repo at this roothush bootstrap
Repository: legacy-v2Repo still uses hush.yaml authoritymigrate or bootstrap into v3
Active identity: (not set)Machine-local identity pointer is missinghush config active-identity <name>
active identity path ... (missing)State file has not been created yetset the active identity
audit log path ... (missing)No diagnostic command has written audit yetrun a read/write command or check state permissions

doctor

Diagnose root discovery, key resolution, and store configuration for the current directory.

Terminal window
hush doctor
# Diagnose as if bootstrapping a child-local repo
hush doctor --new-repo
# Machine-readable output
hush doctor --json

Options

OptionDescription
--new-repoDiagnose as if bootstrapping a child-local repository
--jsonOutput { checks: [{ name, ok, detail }] } mirroring the diagnostic sections

hush doctor prints a structured diagnostic report covering:

  1. Directory Context — current directory, git root
  2. Repository Root Discovery — which .hush/ was found (or not), whether a parent repo exists, and the resolved root
  3. Key Resolution — selected key source, key identity, all attempted paths with existence markers
  4. SOPS Key Match — whether the selected private key’s public half matches the .sops.yaml creation rule
  5. Decryption Check — whether the repository loads and how many files it contains
  6. Recommendations — actionable fixes for any detected issues

Use this command when hush bootstrap fails, when hush inspect reports “no identity matched”, or when you need to understand why Hush picks a particular repository root in a nested-git-repo layout.


check

Validate the v3 repository and flag leftover plaintext or legacy artifacts. Useful for pre-commit hooks.

Terminal window
# Basic check
hush check
# Warn but don't fail
hush check --warn
# JSON output for CI
hush check --json

Options

OptionDescription
--warnWarn on drift but exit 0
--jsonOutput machine-readable JSON
--quietSuppress output
--allow-plaintextIgnore leftover plaintext or legacy artifacts (not recommended)

Exit Codes

CodeMeaning
0Repository is valid
3Repository validation failed
4Leftover plaintext or legacy artifacts were found

Pre-commit Hook

.husky/pre-commit
npx @chriscode/hush check || exit 1

check validates .hush/manifest.encrypted, every .hush/files/**.encrypted document, and scans for leftovers such as hush.yaml, .env*, legacy .hush*.encrypted, or .hush-materialized/.


resolve

Resolve a v3 target and show file provenance. Use this to see what a target actually depends on.

Terminal window
# Resolve a target
hush resolve runtime
# The env flag is accepted for CLI compatibility, but v3 resolution is driven by bundles and files
hush resolve runtime -e production
# Machine-readable provenance without secret values
hush resolve runtime --json

Options

OptionDescription
-e, --env <env>Environment: development or production
--jsonOutput target, bundle, file, logical path, and provenance metadata without values

Example Output

Hush resolve
Target: app-dev
Bundle: app
Format: dotenv
Active identity: developer-local
Resolved files: 2
Resolved logical paths: 2
Files:
env/app/shared
env/app/secrets
Values:
env/apps/api/env/STRIPE_SECRET_KEY
file=env/app/secrets namespace=env
env/apps/web/env/NEXT_PUBLIC_API_URL
file=env/app/shared namespace=env
Artifacts:
(none)

If resolution fails because the active identity cannot read a required file, resolve prints the denied file paths and their readers.

--json is safe for automation: it includes logical paths and provenance, but does not include decrypted scalar values.


trace

Trace a logical path or leaf key through repository files and targets. Use this to understand where a value lives and why a target can or cannot see it.

Terminal window
# Trace a leaf key
hush trace DATABASE_URL
# Trace a full logical path
hush trace env/apps/api/env/DATABASE_URL
# Machine-readable target reachability diagnostics
hush trace RESEND_API_KEY --json

Options

OptionDescription
-e, --env <env>Environment: development or production
--jsonOutput repository matches and per-target reachability diagnostics without values

Example Output

Hush trace
Selector: STRIPE_SECRET_KEY
Active identity: teammate-local
Matched logical paths: 1
Repository files:
env/app/secrets (unreadable; roles=owner identities=developer-local)
env/apps/api/env/STRIPE_SECRET_KEY
Targets:
app-dev (acl denied)
env/app/secrets (roles=owner identities=developer-local)

trace uses the same v3 resolver as runtime commands, so ACL failures and interpolation provenance match real resolution behavior.

When a key exists in Hush but is not selected by a target bundle, trace now explains that explicitly and lists candidate source bundles. The fix is still explicit: add a bundle import, or copy/move the key into the file consumed by that target bundle. Hush does not perform ambient inheritance.


verify-target

Verify that a v3 target resolves and contains required keys. Use this in release automation before syncing remote runtime secrets or flipping traffic.

Terminal window
# Verify the target resolves at all
hush verify-target api-production
# Require specific runtime keys
hush verify-target api-production --require JWT_SECRET --require RESEND_API_KEY
# JSON output for CI or agents
hush verify-target api-production --require RESEND_API_KEY --json

Options

OptionDescription
--require <key>Require a leaf key to resolve from the selected target bundle. Repeatable.
--jsonOutput machine-readable verification results without values

How It Works

  1. Loads the v3 repository and active identity
  2. Resolves the named target with the same resolver used by run, push, and materialize
  3. Checks file ACLs, conflicts, and required leaf keys
  4. Prints only paths, key names, files, bundles, and errors — never decrypted values

If a required key is missing, run hush trace <KEY> to see whether it exists in another file or bundle that the target does not import.

Service × environment topology guidance

For monorepos, model concrete consumer surfaces as service-environment bundles/targets:

api-development
api-staging
api-production
root-development
root-staging
root-production

Use project-* only for intentionally shared material:

project-shared
project-development
project-staging
project-production

Files remain the security boundary, bundles package files, and targets consume bundles. A target such as api-production should not see project-production secrets unless its bundle explicitly imports that shared bundle/file or the key is copied/moved into an API-owned file. This keeps production secret flow visible and agent-safe.


diff

Compare the current v3 target or bundle against HEAD by default, or another git ref with --ref. Use this when you need a safe review surface with provenance and redaction preserved.

Terminal window
# Compare the default runtime target against HEAD
hush diff
# Compare against another git ref
hush diff --ref HEAD~1
# Compare a specific target or bundle
hush diff --target runtime
hush diff --bundle project

Options

OptionDescription
-t, --target <name>Compare a specific target; otherwise Hush uses runtime or the only non-example target
--bundle <name>Compare a bundle instead of a target
--ref <git-ref>Git ref to compare against (default: HEAD)

Example Output

Hush diff
Reference: HEAD
Selection: target runtime
Active identity: owner-local
File changes:
~ env/project/shared
ref roles=owner,member identities=owner-local,member-local
current roles=owner identities=owner-local
Resolved changes:
~ env/project/shared/API_TOKEN (value)
ref [redacted]
file=env/project/shared namespace=env
current [redacted]
file=env/project/shared namespace=env

diff compares both sides through the shared v3 resolver, so file ACL failures, interpolation provenance, and imported provenance are judged the same way real command resolution works.


export-example

Emit a deterministic, redacted example from a target or bundle. Use this for docs, onboarding, and scaffolding without materializing protected values.

Terminal window
# Safe example for the default target
hush export-example
# Choose a specific target or bundle
hush export-example --target runtime
hush export-example --bundle project
# Write output to .env.example in the repo root
hush export-example --write
# Write to a custom path
hush export-example --write --to /path/to/.env.example
# Overwrite an existing file with different content
hush export-example --write --force

Options

OptionDescription
-t, --target <name>Export a specific target; otherwise Hush uses runtime or the only non-example target
--bundle <name>Export a bundle summary instead of a target
--writeWrite the redacted example to a file instead of only printing to stdout
--to <path>Destination file path for --write (default: .env.example in the repo root)
--forceOverwrite the destination file even if its content differs from the generated output

Writing .env.example for fresh-clone discovery

Commit a generated .env.example so new contributors can see the required environment variables without decrypting secrets:

Terminal window
hush export-example --write
git add .env.example
git commit -m "chore: update .env.example"

Re-run after changing the repository topology so the example stays in sync with the actual secrets surface. The --write flag refuses to overwrite a file whose content diverges without --force, protecting against accidental overwrites.

Example Output

Hush export-example
Selection: target runtime
Format: dotenv
Active identity: owner-local
Protected values omitted: 1
Target example:
PUBLIC_URL=https://example.com
Artifacts:
artifacts/project/runtime/env-file (file:dotenv)
# [redacted sensitive artifact]

export-example never emits raw sensitive: true values. Protected scalars are omitted from the example surface, and protected file or binary artifacts become deterministic placeholder content.


migrate

Convert a legacy hush.yaml repository to the v3 .hush/ layout with a single bounded migration flow.

Terminal window
# Inventory only
hush migrate --from v2 --dry-run
# Convert and validate
hush migrate --from v2
# Remove validated leftovers
hush migrate --from v2 --cleanup

Options

OptionDescription
--from v2Required legacy source version selector for the big-bang migration flow
--dry-runInventory the legacy repo without mutating it
--cleanupRemove validated legacy leftovers after a successful migration

How It Works

  1. Inventories the legacy hush.yaml repo, encrypted source files, targets, and repo references
  2. Creates .hush/manifest.encrypted and v3 file documents
  3. Migrates local overrides into machine-local state
  4. Validates the converted repo through the shared v3 materialization pipeline
  5. Leaves legacy files in place until --cleanup removes them

skill

Install the Claude Code / OpenCode skill for AI-safe secrets management.

Terminal window
# Interactive: choose global or local
hush skill
# Install globally (all projects)
hush skill --global
# Install locally (this project only)
hush skill --local

Options

OptionDescription
--globalInstall to ~/.claude/skills/
--localInstall to ./.claude/skills/

Global vs Local

  • Global - Works across all your projects. Recommended for personal use.
  • Local - Bundled with the project. Recommended for teams (commit .claude/ to git).

completion

Generate a shell completion script for bash, zsh, or fish.

Terminal window
# Generate and install for your shell
hush completion bash >> ~/.bashrc
hush completion zsh > ~/.zsh/completions/_hush
hush completion fish > ~/.config/fish/completions/hush.fish

Options

ShellInstall command
bashhush completion bash >> ~/.bashrc (or /etc/bash_completion.d/hush)
zshhush completion zsh > ~/.zsh/completions/_hush (make sure fpath includes the directory, then autoload -Uz compinit && compinit)
fishhush completion fish > ~/.config/fish/completions/hush.fish

Installation examples

Terminal window
hush completion bash >> ~/.bashrc
source ~/.bashrc

Unknown or missing shell argument exits with an error listing supported shells.


decrypt —force (Last Resort)

Write decrypted targets to .hush-materialized/ as persisted plaintext artifacts. Requires --force flag and interactive confirmation.

Terminal window
hush decrypt --force
hush decrypt --force -e production

Why This Exists

Some edge cases genuinely require persisted plaintext artifacts:

  • Docker or packaging workflows that cannot inherit process env directly
  • Legacy tooling that can only read files on disk
  • Temporary break-glass debugging of target output

Safety Features

  1. Requires --force flag - Won’t run without explicit opt-in
  2. Interactive confirmation - Must type yes to proceed
  3. Writes under .hush-materialized/ - Plaintext is isolated to one explicit directory
  4. Shared materialization audit - Uses the same audited runtime flow as hush run

How It Works

  1. Loads the v3 repository and selects all non-example targets
  2. Materializes each target through the shared runtime pipeline in persisted mode
  3. Writes target artifacts under .hush-materialized/targets/ and related artifact paths
  4. Leaves the files on disk until you delete them explicitly

list

List variable names. Values are masked by default. Use --reveal to print plaintext values.

Terminal window
hush list
hush list --reveal
hush list -e production

Options

OptionDescription
--revealPrint plaintext values instead of masked output

Troubleshooting

”no identity matched any of the recipients”

This error means SOPS cannot find your decryption key.

Most common cause: direnv not loaded.

Hush stores per-project keys at ~/.config/sops/age/keys/{project}.txt. Hush now auto-matches those files against the .sops.yaml recipient for normal CLI commands, so a fresh hush bootstrap works even if package.json.repository is missing. SOPS_AGE_KEY_FILE is still the best explicit override when you need to force a specific key file.

Terminal window
# 1. Verify direnv is installed and hooked
brew install direnv
echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc # or bash
source ~/.zshrc
# 2. Allow direnv in the project
cd /path/to/project
direnv allow
# 3. Verify the env var when you want an explicit override
echo $SOPS_AGE_KEY_FILE
# Optional, but if set it should output: /Users/you/.config/sops/age/keys/project-name.txt
# 4. Test
hush status
hush inspect

”age key not found”

The key file doesn’t exist at the expected location.

Terminal window
# Check where hush expects the key
hush status # Look at "Local key:" line
# Get the key from a team member and save it manually
# Save to the path shown in hush status, then run:
hush keys setup

Key exists but wrong project

If you have a key but it’s for a different project:

Terminal window
# List all local keys
hush keys list
# Copy the correct key into the expected local path, then verify it
hush keys setup

“SOPS is not installed”

Terminal window
brew install sops age # macOS
# See Getting Started for Linux/Windows

direnv not loading automatically

Make sure you’ve added the direnv hook to your shell:

Terminal window
# For zsh (~/.zshrc)
eval "$(direnv hook zsh)"
# For bash (~/.bashrc)
eval "$(direnv hook bash)"

Then reload your shell or open a new terminal.