mise install installs the mbx version pinned in mise.toml. mise run activates the project's transparent
Cargo wrapper, so compilation-heavy mise tasks and hk checks use ordinary
cargo commands. Standalone Cargo commands require an activated mise shell. If
the wrapper fails or creates a development papercut, rerun the exact equivalent
command from CONTRIBUTING.md with MBX_DISABLE=1; this unblocks work without
weakening the check. If bypassed Cargo succeeds, surface the mismatch and recommend a
mr-boxington Discussion with
the repository and commit, OS, mbx --version, mbx doctor, and both commands
and outputs. Redact secrets, absolute cache paths, remote URLs, namespaces, and
other sensitive or identifying details. Do not permanently disable the wrapper,
and do not post externally without user authorization.
PR titles must use <type>[optional scope][optional !]: <description>. Intermediate commit
subjects should use the same format. Start the description with a lowercase
character and use imperative mood.
Types: feat, fix, refactor, docs, style, perf, test, chore, ci, revert, security
Scopes: command names (get, set, exec, list, provider), provider names (age, 1password, bitwarden, bitwarden-sm, aws-kms, aws-sm, aws-ps, keychain, keepass, infisical, passwordstate, pass, proton-pass), subsystems (config, encryption, env, deps)
Examples: fix(aws-sm): handle pagination for large secret lists, feat(exec): add --no-inherit flag
CI validates the pull request title and re-runs when it is edited. Intermediate commit subjects are not checked because pull requests are squash-merged. CI mechanically checks the allowed type, syntax, and lowercase-leading description; imperative mood remains a review rule.
rust-version in the workspace Cargo.toml is kept behind the latest stable
Rust on purpose. fnox is built from source by packagers that provide their own,
often older, rustc — Linux distro packages (cargo install against a
distro-provided toolchain) and nixpkgs (one shared, conservative rustc for the
whole tree). Raising the MSRV can make fnox unbuildable there.
Do not raise the MSRV to satisfy a dependency. If a dependency bump requires
a newer rustc than our declared MSRV (the msrv CI job, cargo msrv verify,
will fail), pin that dependency to its last MSRV-compatible version instead.
.cargo/config.toml sets resolver.incompatible-rust-versions = "fallback" so
cargo update/cargo add prefer MSRV-compatible versions automatically.
- Use the lowest compatibility-significant specificity in
Cargo.toml(for example,"1"for stable 1.x dependencies). - When the existing manifest requirement accepts a routine dependency update, change only
Cargo.lock. - Keep lockfile updates focused and avoid unrelated transitive dependency churn.
mise run build # Build (debug mode, never use --release)
mise run test # Run all tests (cargo + bats)
mise run test:cargo # Cargo tests only
mise run test:bats # Bats tests only (run build first)
mise run test:bats -- test/init.bats # Specific bats test file
mise run ci # Full CI: build + test + lint
mise run lint # Lint (hk)
mise run lint-fix # Auto-fix lint issuesProvider test requirements (all skip gracefully if credentials unavailable):
- 1Password:
OP_SERVICE_ACCOUNT_TOKENenv var - Bitwarden:
BW_SESSIONenv var (usesource ./test/setup-bitwarden-test.shfor local vaultwarden) - Infisical:
INFISICAL_TOKENenv var - KeePass:
KEEPASS_PASSWORDenv var (self-contained, no external services) - Passwordstate:
PASSWORDSTATE_BASE_URL,PASSWORDSTATE_API_KEY,PASSWORDSTATE_LIST_IDenv vars
- Error handling:
anyhow::Resultin commands,thiserror/FnoxErrorfor domain errors - Logging:
tracing(notprintln!) - Naming: modules
snake_case, structsPascalCase, functionssnake_case, constantsSCREAMING_SNAKE_CASE, CLI argskebab-case - Async: all commands and provider methods are async,
tokio::mainentry point
src/commands/ # One file per command
crates/fnox-core/src/providers/ # Provider implementations and encryption
crates/fnox-core/src/config.rs # Config parsing and layering
crates/fnox-core/src/env.rs # Centralized FNOX_* environment handling
- Use
mod.rsfor module exports - Import env vars via
use crate::env;/env::FNOX_*— avoid directstd::env::calls - CLI flags:
-P, --profile,-p, --provider,-d, --description,-k, --key-name
FNOX_PROFILE— profile to use (default: "default")FNOX_CONFIG_DIR— config directory (default:~/.config/fnox)FNOX_AGE_KEY— age encryption keyFNOX_PROMPT_AUTH— enable/disable auth prompting in TTY (default: true)
Loading order (later overrides earlier):
~/.config/fnox/config.toml(global)- Parent directory
fnox.tomlfiles (recursion, closer = higher priority) fnox.toml(project)fnox.$FNOX_PROFILE.toml(profile-specific, if not "default")fnox.local.toml(local overrides, gitignored)
Steps 3-5 apply at each discovered directory, from outermost to innermost. A closer directory overrides its parent, including parent-local values.
An explicit -c/--config path skips steps 2-5 (no directory recursion, no local
overrides) but still loads the global config and the file's own imports.
Secret options:
if_missing:"error"|"warn"(default) |"ignore"as_file = true: write to temp file instead of env var
Auth prompting: on provider auth failure in TTY, fnox prompts to run the provider's auth command (e.g., aws sso login, op signin). Disable with prompt_auth = false in config or FNOX_PROMPT_AUTH=false.
Encryption providers store ciphertext in fnox.toml; remote and local storage providers store references there. The plain provider returns unencrypted values. See crates/fnox-core/src/providers/ for implementations and docs/providers/overview.md for the complete provider catalog.
| Type | Config type |
Storage | Key crate/CLI |
|---|---|---|---|
| Age | age |
Encrypted in config | age crate |
| 1Password | 1password |
1Password vault | op CLI |
| Bitwarden | bitwarden |
Bitwarden vault | bw CLI |
| Bitwarden SM | bitwarden-sm |
Bitwarden Secrets Manager | bws CLI |
| AWS KMS | aws-kms |
Encrypted in config | aws-sdk-kms |
| AWS Secrets Manager | aws-sm |
AWS SM | aws-sdk-secretsmanager |
| AWS Parameter Store | aws-ps |
AWS SSM | aws-sdk-ssm |
| Keychain | keychain |
OS keychain | keyring crate |
| KeePass | keepass |
.kdbx file |
keepass-rs crate |
| Infisical | infisical |
Infisical | infisical CLI |
| Passwordstate | passwordstate |
Passwordstate server | reqwest HTTP |
| password-store | password-store |
GPG files | pass CLI |
| Proton Pass | proton-pass |
Proton Pass vault | pass-cli CLI |
Provider fields: type is required. Fields such as prefix, region, and vault depend on the provider type; use its schema and guide for supported fields and reference formats.
Pull request titles must follow the same Conventional Commit format as commits: <type>[optional scope][optional !]: <description> in lowercase imperative mood. Do not prefix PR titles with agent/tool labels such as [codex] or [claude].
Do not modify version numbers or changelogs in non-release pull requests.
When AI contributes GitHub content—including a pull request description, review, pull request comment, or discussion post—append this disclosure:
*AI-assisted — Tool: <tool>; model: <provider>/<model>; version: <version-or-unavailable>.*
Use the exact model and version identifiers exposed by the runtime. Never infer or guess them; use
unavailable when either value is not exposed.