Age-based secrets manager that supports cross-repo sharing. Secrets stored one-file-per-key in a git-backed .himitsu/ store, with path-based recipient control and typed codegen.
- Features
- Install
- Quick Start
- TUI
- Store Layout
- Commands
- Global Options
- Configuration
- Sync & Store Health
- Demo & Recordings
- Nix Integration
- Development
- License
himitsu solves duplication and eases maintenance, and is made for the following workflow:
- Create your personal store as a git repo e.g.
user/secrets- personal secrets go here. - Any orgs/teams you are part of have a store at
org/secrets- team secrets go here. - Access-control is path-based - configure who can access
prod/*in.himitsu.yaml - Store secrets, organizing using plain directories
- Map secrets to environments:
# .himitsu.yaml
...
envs:
web-service-{dev,staging,prod}:
# includes all secrets in the "common" directory, converting 'foo-bar' to 'FOO_BAR'
- common/*
# includes dev/database-url for dev, etc
- $1/database-url
# override environment variable key
- SOME_VALUE: path/to/some-secret
# use the full ref to access external stores
- SHARED_SECRET: github:org/secrets#prod/api-keyWith this config you can run himitsu generate --target gen which will build SOPS-compatible yaml files to the gen/ directory.
- Age-only encryption -- no KMS, no GPG, no SOPS. One
.agefile per secret. - One file per secret --
.himitsu/secrets/<env>/<KEY>.agekeeps diffs readable. - Path-based recipients -- organize keys into directories (e.g.
ops/alice,team/*); re-encrypt for all withrekey. - Typed codegen -- generate TypeScript, Go, Python, or Rust type stubs from your secret store.
- Cross-store search --
himitsu searchreads every known store directly; results are live, no index to rebuild. - TUI -- in-terminal dashboard with fuzzy search, secret viewer, inline editing, and store health.
- Import / Export -- bulk import from 1Password (
op) or SOPS files; export as SOPS-encrypted YAML/JSON. - Nix integration -- flake library for devShell injection, secret packaging, and container entrypoints.
# Nix (run directly)
nix run github:darkmatter/himitsu -- <command>
# Nix (add to a devShell)
{
inputs.himitsu.url = "github:darkmatter/himitsu";
# ...
devShells.default = pkgs.mkShell {
packages = [ himitsu.packages.${system}.default ];
};
}
# Build from source
cargo build --releaseThe fastest path is the TUI. Run himitsu with no subcommand: if no age key
exists yet, the init wizard launches automatically and walks you through
scaffolding a store. Once that completes you land on the search dashboard.
himitsu # opens the TUI (init wizard on first run, dashboard otherwise)From the dashboard you can fuzzy-search secrets, ctrl-n to create one,
ctrl-p for the command palette, enter to view, e to edit -- see
TUI for the full keymap.
The same operations are available as scriptable subcommands. Use these in CI, shell scripts, or when you'd rather stay in your editor:
# Initialize headlessly (skip the wizard)
himitsu init --name you/secrets
# Store and read secrets
himitsu set prod/API_KEY "sk_live_abc123"
himitsu set prod/DB_PASSWORD "hunter2"
himitsu set dev/DB_PASSWORD "devpass" --no-push # batch-friendly
himitsu get prod/API_KEY # -> sk_live_abc123
# List, rekey, search
himitsu ls # -> dev/, prod/
himitsu ls prod # -> API_KEY, DB_PASSWORD
himitsu rekey # re-encrypt for current recipient set
himitsu search DB # cross-store fuzzy searchOverride the active store with -s (path) or -r (slug):
himitsu -s /path/to/.himitsu set prod/API_KEY "sk_live_xxx"
himitsu -r org/repo get prod/API_KEYLaunch the interactive terminal UI:
himitsu # no subcommand opens the TUISearch is the root view -- the app opens straight into a fuzzy filter over every secret in the active store. Start typing to narrow the list, arrow keys to move, enter to open. Press ? in any view for a help overlay, or ctrl-p to open the command palette -- the canonical, fuzzy-filterable list of every action the current view exposes.
| Key | Action |
|---|---|
| type | filter results |
up / down |
move selection |
enter |
open selected secret |
backspace |
delete filter char |
ctrl-p |
command palette |
ctrl-n |
new secret |
ctrl-s |
switch store |
ctrl-y |
copy selected value |
shift-e |
browse env presets |
? |
help |
esc / ctrl-c |
quit |
| Key | Action |
|---|---|
r |
reveal / hide value |
y |
copy to clipboard |
e |
edit in $EDITOR |
R |
rekey for current recipients |
d |
delete (confirms with y) |
? |
help |
esc |
back |
Fields: path, value, description, url, totp, env_key, expires_at.
| Key | Action |
|---|---|
tab / enter |
next field |
shift-tab |
previous field |
ctrl-s / ctrl-w |
save |
esc / ctrl-c |
cancel |
.himitsu/
secrets/
prod/
API_KEY.age
DB_PASSWORD.age
dev/
DB_PASSWORD.age
recipients/
self.pub # your key (added on init)
ops/
alice.pub # path-based: ops/alice
deploy-bot.pub # path-based: ops/deploy-bot
config.yaml # store-level config
schemas/
secrets.json
The keyring lives separately:
~/.local/share/himitsu/ # $XDG_DATA_HOME/himitsu
key # age private key
key.pub # age public key
Create an age keypair and scaffold the store. Adds self.pub as a recipient automatically.
himitsu init # interactive TUI wizard
himitsu init --name you/secrets # headless, creates/restores a primary store
himitsu init --name org/repo --url <url> # restore from a custom git remoteEncrypt and store a secret. Path is slash-delimited (prod/API_KEY).
himitsu set prod/API_KEY "sk_live_abc123"
himitsu set dev/DB_PASSWORD "devpass" --no-pushDecrypt and print a secret.
himitsu get prod/API_KEYBrowse secrets like a directory.
himitsu ls # -> dev/, prod/
himitsu ls prod # -> API_KEY, DB_PASSWORDRe-encrypt secrets for the current recipient set. Run after adding or removing recipients.
himitsu rekey # everything
himitsu rekey prod # one subtreeFuzzy-search secret paths across every known store. Results are read live from store files on every invocation -- there is no SQLite index to rebuild, and decrypted descriptions are pulled best-effort with the ambient age key.
himitsu search DB # search all stores
himitsu search DB --refresh # accepted as a no-op; kept for backward compatManage recipients with path-based names. Slash-separated paths create a directory hierarchy (e.g. ops/alice -> recipients/ops/alice.pub).
himitsu recipient add laptop --self
himitsu recipient add ops/alice --age-key "age1abc..." --description "Alice"
himitsu recipient show ops/alice
himitsu recipient rm ops/alice
himitsu recipient lsManage remote store registrations.
himitsu remote add org/repo # clone from GitHub
himitsu remote add git@github.com:org/repo.git # full URL also works
himitsu remote add org/repo --url https://custom/url # custom git host
himitsu remote default org/repo # set default store
himitsu remote list
himitsu remote remove org/repoPull from the git remote and optionally rekey drifted secrets.
himitsu sync # all environments
himitsu sync prod # one environmentSee Sync & Store Health for the auto-commit / auto-push
behavior, --no-push, and the auto_pull config switch.
Import secrets from 1Password (--op) or a SOPS-encrypted file (--sops).
Both backends shell out to external tools, so they must be on PATH:
- 1Password -- requires the
opCLI, signed in to the relevant account. - SOPS -- requires
sops; decryption runs assops -d <file>.
himitsu import prod/STRIPE_KEY --op "op://Engineering/Stripe/api_key"
himitsu import prod --op "op://Engineering/Stripe" # whole item
himitsu import prod --sops secrets.enc.yaml --dry-runExport secrets matching a glob as a SOPS-encrypted file. Requires sops on
PATH; encryption is piped through sops --encrypt so plaintext never hits
disk.
himitsu export "prod/*" -o prod.sops.yamlGenerate SOPS-encrypted output files from env definitions in project config.
Also requires sops on PATH -- same pipe-through-stdin contract as
export.
himitsu generate # all envs defined in himitsu.yaml
himitsu generate --env prodRun any git command inside the store directory.
himitsu git status
himitsu git log --oneline
himitsu git --all status # all storesRender this README in the terminal.
himitsu docs| Flag | Description |
|---|---|
-s, --store <path> |
Override the store path directly. |
-r, --remote <slug> |
Select a store by org/repo slug (or full git URL). |
-v, --verbose |
Increase log verbosity (-v debug, -vv trace). |
User-level settings live at ~/.config/himitsu/config.yaml. Every field has a
HIMITSU_<FIELD> environment override (uppercased, dots become underscores)
that takes precedence over the file.
# Default store when neither -s nor -r is given.
default_store: myorg/secrets # env: HIMITSU_DEFAULT_STORE
# Where age private keys live: "disk" or "macos-keychain".
key_provider: disk # env: HIMITSU_KEY_PROVIDER
# When true, every store-touching command first runs `git fetch` and
# fast-forwards before dispatching, so reads see latest state and writes
# can't fast-fail on a remote-side commit. Failures are non-fatal.
auto_pull: false # env: HIMITSU_AUTO_PULL
tui:
# Built-in palette. `random` (the default) picks one per launch.
# Accepted: random, himitsu, apathy, apathy-minted, apathy-theory,
# apathy-storm, ayu, catppuccin, material, rose-pine.
theme: random # env: HIMITSU_TUI_THEME
# Opt in to Nerd Font glyphs in the dashboard. Off by default because
# there is no reliable runtime check for font support.
nerd_fonts: false
# Per-action keybindings. Each action takes a list, so multiple keys can
# trigger the same action. Unspecified actions fall back to the
# hardcoded defaults documented in [TUI](#tui).
keys:
new_secret: ["F2", "ctrl+n"]
quit: ["esc", "ctrl+q"]Binding strings are <mod>+<mod>+<code>, lowercased, modifiers first --
e.g. "ctrl+n", "shift+tab", "esc", "?", "F2". Uppercase
characters imply shift ("Y" == "shift+y"). Bare letters match
case-insensitively, so "y" matches both y and Y. Malformed bindings
surface as a clear config error at startup.
The full action list (with defaults) is in
rust/src/tui/keymap.rs:
quit, help, command_palette, new_secret, switch_store,
copy_selected, envs, reveal, copy_value, rekey, edit, delete,
back, save_secret, next_field, prev_field, cancel.
Himitsu treats the store as an append-only git repo and keeps git status
clean for you.
- Auto-commit on every mutation.
set,write,rekey,import,recipient add/rm, etc. each produce a commit (himitsu: <action>) on success orhimitsu: FAILED: <action>: <error>on failure -- the working tree is never left dirty. - Auto-push on success. When the commit lands and a remote is
configured, himitsu also runs
git push. Pass--no-pushonset,write, orimportto skip the push (handy for batch loads); the next mutation without--no-pushwill flush everything. - Auto-pull (opt-in). Set
auto_pull: true(orHIMITSU_AUTO_PULL=1) to fetch and fast-forward before every store-touching command. Failures surface as a stderr warning rather than aborting the command.
The TUI dashboard renders a store-health indicator in the header bar with the following states:
| State | Meaning |
|---|---|
synced |
Local checkout matches the remote tracking branch. |
behind N |
Tracking branch is ahead of local by N commits -- run himitsu sync. |
dirty |
Working tree has uncommitted changes (rare; usually a manual edit). |
behind+dirty |
Both behind remote AND has local changes. |
no remote |
Repo exists but no remote -- run himitsu remote add <slug>. |
not pushed |
Remote configured, tracking branch missing -- run himitsu git push -u origin main. |
not git |
Store directory is not a git repo (init bug, almost never happens). |
unknown |
Status couldn't be determined; treat as a hint to investigate. |
The headline demo at the top of this README is demo/demo-vhs.gif,
rendered from demo/demo.tape. The smaller demo/tui-us-*.{tape,gif,cast}
files are per-user-story regression / demo artifacts -- one per shipped TUI
story (US-008 new-secret form, US-011 browse, US-012 viewer, etc.) -- and
double as visual tests that the documented flow still works.
To regenerate locally:
cargo build --release
vhs demo/demo.tape # canonical headline GIF
vhs demo/tui-us-011.tape # one specific storyCI re-renders every tape on changes to demo/**, rust/**, or the
workflow file (.github/workflows/vhs.yml). The CI run redirects each
tape's Output line to a scratch path under target/vhs-out/ so the
checkout never picks up binary diffs; the rendered GIFs are uploaded as a
build artifact (vhs-demo-renders) for inspection. Commit a refreshed
demo-vhs.gif only when you intend the README hero to change.
{
inputs.himitsu.url = "github:darkmatter/himitsu";
outputs = { self, nixpkgs, himitsu, ... }: let
system = "x86_64-linux";
lib = himitsu.lib.${system};
in {
devShells.default = lib.mkDevShell {
devShell = pkgs.mkShell { packages = [ nodejs ]; };
store = ./.himitsu;
env = "dev";
};
packages.my-secrets = lib.packSecrets ./.himitsu/secrets/prod;
};
}| Output | Description |
|---|---|
packages.default |
The himitsu CLI binary. |
packages.age-key-cmd |
Prints the local age private key (useful as SOPS_AGE_KEY_CMD). |
lib.mkDevShell |
Wrap a devShell with automatic secret decryption. |
lib.packSecrets |
Collect .age files into a Nix derivation. |
lib.wrapAge |
age pre-configured with the local identity. |
lib.wrapSops |
sops pre-configured to discover the himitsu key. |
lib.mkEntrypoint |
Container entrypoint that decrypts then execs. |
nix develop # enter dev shell
cargo build # debug build
cargo build --release # release build
cargo test --workspace # all tests
cargo fmt --all -- --check # format check
cargo clippy --workspace --all-targets -- -D warningsMIT



