Modular, declarative, reversible terminal configuration for macOS and Linux.
One config.toml lists the modules you want; omnishell apply installs, renders and wires them into a single tool-managed init file.
π config.toml Β βΒ βοΈ omnishell apply Β βΒ π init.zsh Β· init.bash
omnishell is a single static binary that applies terminal configuration on
macOS and Linux in a modular, declarative, reversible way. You keep a plain
local file β config.toml β that lists which modules are enabled and their
options. Running omnishell apply then:
- installs any programs a module needs (via the detected system package
manager β
brew,apt,dnf,pacman,zypper,apkβ with agitclone fallback, pinned to a release tag, when the manager has no package for the module or its repositories cannot provide it), - renders each module's shell snippet,
- writes everything into a single tool-managed init file per shell (
init.zsh/init.bash), sourced from your~/.zshrc/~/.bashrcvia one marker block, - records the result in a lockfile for idempotency, drift detection and clean removal.
Note
Your own .zshrc / .bashrc is never rewritten beyond that one marker block,
every write is backed up first, and running apply twice with no config change
is a no-op. No git repo, no account, and no network are required for normal use.
Homebrew:
brew install jthegunner/tap/omnishellCurl (servers without Homebrew β downloads the matching binary):
curl -fsSL https://raw.githubusercontent.com/JtheGunner/omnishell/main/install.sh | shThe installer resolves the latest release through the github.com redirect, so
it does not use the rate-limited GitHub API. To install a specific release, set
OMNISHELL_VERSION (for example OMNISHELL_VERSION=v0.5.0).
Go:
go install github.com/JtheGunner/omnishell/cmd/omnishell@latest| OS | Architecture | uname -m |
Release archive | |
|---|---|---|---|---|
| π | macOS | x86_64, arm64 | x86_64, arm64 |
omnishell_darwin_amd64, omnishell_darwin_arm64 |
| π§ | Linux | x86_64 | x86_64 |
omnishell_linux_amd64 |
| π§ | Linux | arm64 | aarch64 |
omnishell_linux_arm64 |
| π§ | Linux | armv7 (also a 32-bit userland on a 64-bit CPU) | armv7l, armv8l |
omnishell_linux_armv7 |
| π§ | Linux | armv6 (Raspberry Pi 1 / Zero) | armv6l |
omnishell_linux_armv6 |
On 32-bit ARM, starship installs a release binary on armv6 and armv7 and mise on armv7. broot has no 32-bit ARM binary, so it builds from source there (needs Rust).
omnishell init # create the config, hook it into your shells
omnishell enable fzf # or edit ~/.config/omnishell/config.toml by hand
omnishell apply # install packages + write the init files
exec $SHELL # open a new shell to pick up the changesomnishell init creates ~/.config/omnishell/config.toml with everything
disabled and inserts the source marker block into the detected shells' rc
files. enable / disable / set only edit config.toml β nothing touches
your system until you run omnishell apply.
-h / --help works on every command.
| Command | Purpose | Flags |
|---|---|---|
omnishell init |
Create the omnishell config and hook it into your shells. Idempotent. | β |
omnishell list |
List known modules and their status (enabled/disabled, packages, platforms, shells, source, description). | --json (emit a JSON array instead of the table; also includes each module's homepage) |
omnishell tui |
Browse and toggle modules in a full-screen terminal UI: a module list with description, homepage, package status, platforms and shells, a / filter, and Space to enable or disable the selected module (written to config.toml at once, like enable / disable). o edits the module's options (bool, enum, string and int; each change is written at once and validated like omnishell set). a previews the plan; confirming it closes the UI and runs omnishell apply, which still asks before it changes anything. Modules that cannot run on this host are dimmed with the reason; a module that only lacks OS support can still be enabled, but apply skips it. Needs an interactive terminal (exit 2 otherwise). |
β |
omnishell enable <module> |
Enable a module in the config. Does not apply. | β |
omnishell disable <module> |
Disable a module in the config. Does not apply. | β |
omnishell set <module>.<key> <value> |
Set a module option in the config, validated against that module's option schema. | β |
omnishell validate |
Check config.toml against the module option schemas and dependency rules (requires / conflicts / cycles). Computes no plan and probes nothing on the host; exit 2 on any problem. Useful in CI for a version-controlled config.toml. |
--json (emit a JSON object instead of the text report) |
omnishell apply |
Bring your shells up to date with the config. | --dry-run (show the plan without changing anything), --force (overwrite init files that were edited by hand), --no-packages (skip package installation), -y / --yes (apply without the confirmation prompt), --reload (re-exec $SHELL after a successful apply; no-op in a non-interactive shell) |
omnishell diff |
Show what apply would change (apply --dry-run). |
β |
omnishell doctor |
Check the installed shell environment for drift. Exit 3 if any drift is found. --fix repairs the drift a re-apply resolves (missing / stale init file, missing rc source line, orphaned lockfile entries); a hand-edited init file and missing packages are left for a manual apply / apply --force. |
--fix (repair re-apply-able drift), -y / --yes (with --fix, skip the prompt) |
omnishell bench |
Measure how much sourcing the generated init.<shell> adds to shell startup, per managed shell, and warn when it exceeds the startup budget. A rough guide (moves with machine load), always exits 0. |
--json (emit a JSON object), --runs <n> (timed runs per measurement, default 5) |
omnishell rollback |
List backup snapshots, or restore files/lockfile to their state before a chosen one (--to <timestamp>), undoing that run and everything after it. Never touches packages or config.toml. |
--to <timestamp>, --dry-run, -y / --yes |
omnishell remove <id> |
Disable a module and drop its shell section, then re-apply. | --dry-run, --purge (also uninstall the module's packages, run its remove hook, and delete vendored files), -y / --yes |
omnishell uninstall |
Remove omnishell's shell integration (marker block + generated init.<shell> files). Every touched file is backed up first. |
--purge (also delete ~/.config/omnishell entirely), -y / --yes |
omnishell version |
Print the omnishell version. | β |
omnishell completion |
Cobra-generated shell autocompletion script generator (distinct from the completion module). |
β |
Not to be confused with the completion module (which configures your
shell's completion system) β this is tab-completion for typing omnishell
itself, plus man omnishell.
- Homebrew installs the bash/zsh completions and the man pages automatically.
install.shdrops them into$XDG_DATA_HOME(~/.local/share) on a best-effort basis β bash intobash-completion/completions/, zsh intozsh/site-functions/(make sure that dir is on your$fpath), man intoman/man1/.go install: generate on demand, e.g.omnishell completion zsh > ~/.local/share/zsh/site-functions/_omnishell.
The config directory is $XDG_CONFIG_HOME/omnishell, or ~/.config/omnishell
when XDG_CONFIG_HOME is unset:
| Path | What it is | |
|---|---|---|
| π | config.toml |
The file you edit (by hand, or via enable / disable / set). The [omnishell] table also takes startup_budget_ms (default 200) β the per-shell added-cost threshold omnishell bench warns above. |
| π | init.zsh / init.bash |
Tool-generated. Do not edit β apply regenerates them and doctor flags manual edits. |
| π | state.lock.json |
Machine-managed lockfile (idempotency, drift detection, clean removal). Not for editing. |
| πΎ | backups/<timestamp>/ |
Every rc-file and init-file write is copied here first. |
| π§© | modules/<id>/ |
Your own modules (same format as the built-ins). |
| π₯ | vendor/ |
Clones made by the git package fallback (apply moves them to the tag the manifest pins and rebuilds) and bin/ with release binaries. |
Each managed rc file gets exactly one marker block, appended at the end:
# >>> omnishell >>>
[[ -f "$HOME/.config/omnishell/init.zsh" ]] && source "$HOME/.config/omnishell/init.zsh"
# <<< omnishell <<<The generated init.<shell> file carries a header with a Content hash, then
one section per module wrapped in
# >>> omnishell:<id> (v<ver>) >>> β¦ # <<< omnishell:<id> <<< markers, in
dependency order. It is written atomically (temp file + rename) after the old
copy is backed up. Only apply, remove, and uninstall change your system (and init inserts the rc block); everything else just reads or edits
config.toml.
| id | Description | Packages | Shells | Options |
|---|---|---|---|---|
completion |
Enable shell completion with case-insensitive matching | β (config only) | zsh, bash | β |
history |
Larger, de-duplicated, shared shell history and prefix search | β (config only) | zsh, bash | size (int, default 50000) |
autosuggestions |
Fish-style grey inline command suggestions from history (zsh) β zsh-users/zsh-autosuggestions | zsh-autosuggestions; git fallback |
zsh | highlight_style (string, default fg=8) |
syntax-highlighting |
Colour commands green/red as you type depending on validity (zsh) β zsh-users/zsh-syntax-highlighting | zsh-syntax-highlighting; git fallback |
zsh | β |
fzf |
Ctrl+R history search as a fuzzy, scrollable list (+ optional Ctrl+T) β junegunn/fzf | fzf; git fallback |
zsh, bash | ctrl_r (bool, default true), ctrl_t (bool, default false), default_opts (string, default --height 40% --reverse --border) |
zoxide |
Smarter cd that learns your most-used directories β ajeetdsouza/zoxide | zoxide; git fallback |
zsh, bash | cmd (string, default z) |
direnv |
Per-directory environment variables loaded from .envrc files (each new .envrc needs a one-time direnv allow) β direnv/direnv |
direnv; git fallback |
zsh, bash | log_format (string, default "" = silent), whitelist (list<string>, default []) |
mise |
Per-project runtime versions (node, python, go, β¦) from mise.toml / .tool-versions β jdx/mise |
mise (brew, pacman); else release binary, then git + cargo fallback |
zsh, bash | mode (enum activate | shims, default activate) |
pay-respects |
Suggest a corrected command after a failed one, run via the alias (a Rust thefuck alternative) β iffse/pay-respects |
pay-respects (brew); else git + cargo fallback (needs Rust) |
zsh, bash | alias (string, default f) |
atuin |
SQLite-backed shell history with fuzzy search, context and stats β atuinsh/atuin. Mutually exclusive with fzf (both bind Ctrl+R) β enabling both is a config error. |
atuin (brew, pacman); else git + cargo fallback (needs Rust) |
zsh, bash | bind_ctrl_r (bool, default true), bind_up_arrow (bool, default true) |
modern-aliases |
Replace ls/cat/find with eza, bat and fd when selected | eza, bat, fd |
zsh, bash | replace (list<enum> of ls, cat, find; default ["ls", "cat", "find"]) |
colorized-man |
Syntax-highlighted man pages via bat when present, with a zero-dependency less colour fallback |
β (config only) | zsh, bash | β |
ls-colors |
A consistent, readable colour palette for ls / eza and filename completion (LS_COLORS on Linux, LSCOLORS on macOS) |
β (config only) | zsh, bash | β |
root-loops |
Recolour the terminal to a rootloops.sh-generated 16-colour palette on shell start via OSC 4/10/11 escapes (tmux-aware). Opt-in: it overrides your terminal's own colours. auto follows the system light/dark setting (AppleInterfaceStyle on macOS, the freedesktop color-scheme portal on Linux). |
β (config only) | zsh, bash | appearance (enum auto | dark | light, default auto) |
pager-defaults |
Sensible less defaults: colour passthrough, quit-if-one-screen, smart-case search, a saved search history |
β (config only) | zsh, bash | β |
window-title |
Keep the terminal window/tab title set to the current working directory | β (config only) | zsh, bash | β |
fzf-tab |
Replace the zsh completion menu with a scrollable, previewable fzf picker (cd <Tab>, git checkout <Tab>, β¦) β Aloxaf/fzf-tab. Requires fzf. |
git clone (no distro package) |
zsh | cd_preview (bool, default true) |
omnishell-prompt |
A small, fast, git-aware two-line prompt with no external dependency. Opt-in: it sets PROMPT / PS1 β leave it off if you already run a prompt framework (starship, powerlevel10k, β¦). |
β (config only) | zsh, bash | style (enum minimal | full, default minimal), show_duration (bool, default false; zsh only), char (string, default β―) |
starship |
The minimal, blazing-fast cross-shell prompt β starship.rs. Opt-in: it sets the prompt. Seeds ~/.config/omnishell/starship.toml (a curated single-line theme) the first time a shell starts and points STARSHIP_CONFIG at it β edit that file to taste, omnishell never overwrites it. Mutually exclusive with omnishell-prompt β enabling both is a config error. |
starship (brew, apt, pacman, apk); else release binary, then git + cargo fallback |
zsh, bash | β |
broot |
A navigable directory-tree TUI; the br function cd's into the directory you pick β Canop/broot |
broot (brew, apt, dnf, pacman); else release binary, then git + cargo fallback |
zsh, bash | cmd (string, default br) |
welcome |
Run fastfetch on every interactive shell start for a system summary. Opt-in β this runs a program on each new shell and adds visible startup latency. | fastfetch; git + cmake fallback |
zsh, bash | only_ssh (bool, default false) |
tmux |
Auto-attach to (or start) a tmux session on interactive shell start, unless already inside one ($TMUX set). Opt-in β every new interactive shell drops you into the multiplexer. Loads before the prompt modules so the session is up before the prompt initialises. Set auto_attach = false to only manage the tmux package and emit nothing into shell startup. |
tmux (all managers) |
zsh, bash | session (string, default default), auto_attach (bool, default true) |
autosuggestions and syntax-highlighting are zsh-only and always render last (with syntax-highlighting after autosuggestions); fzf-tab, when enabled,
loads after completion/fzf and before both of those. tmux, when enabled,
loads before the prompt modules (starship, omnishell-prompt).
Note
mise, starship and broot install a verified release binary first (Linux x86_64 and arm64; on 32-bit ARM starship, and mise on armv7). The prerequisites below only apply to them on other architectures.
When no package is available, some modules are built from source. Before
cloning anything, omnishell apply checks that the required tools are present
(<tool> --version) and, if a minimum applies, recent enough. If something is
missing, the module is reported as degraded with a message such as
fallback prerequisites missing: cargo >= 1.95 (found 1.75.0); cmake (not found).
omnishell does not install build tools β install them yourself and re-run
omnishell apply.
| π§± | Module | Needs |
|---|---|---|
| π¦ | mise |
cargo β₯ 1.95, cmake |
| π¦ | starship |
cargo β₯ 1.95 |
| π¦ | atuin |
cargo β₯ 1.95 |
| π¦ | broot |
cargo β₯ 1.85 |
| π¦ | pay-respects |
cargo β₯ 1.85 |
| βοΈ | welcome |
cmake |
Tip
Distribution cargo packages are often too old. Install a current Rust
toolchain with rustup, and cmake from your package
manager. The versions above are the minimums the upstream projects declare
and can change with new releases.
A module is just a folder: a manifest.toml, one zsh.tmpl / bash.tmpl per
shell, and an optional hooks/ directory. Drop it in
~/.config/omnishell/modules/<id>/ and it shows up in omnishell list with no
rebuild; a user module with the same id as a built-in overrides it (and
doctor flags the shadowing). See
docs/writing-a-module.md for the full manifest
reference, the template context, and a worked example.
- Nothing changes on your system without
omnishell apply,omnishell remove, oromnishell uninstall(initalso inserts the rc marker block). - Every write to a rc file or init file is preceded by a timestamped backup
under
~/.config/omnishell/backups/<timestamp>/. applyrefuses to overwrite aninit.<shell>file that was edited by hand (exit 1); it tells you to re-run with--force.omnishell doctorreports the same condition asinitfile-editeddrift.- Init files are swapped in atomically only at the very end of
apply; an abort before that leaves the old state intact. omnishell doctorreports drift (missing packages, hand-edited init files, missingsourceline, orphaned lockfile entries, shadowed built-ins) and is CI-friendly via exit code 3.omnishell rollbackrestores files and the lockfile from a backup snapshot (see Commands) β but never packages or vendored files, and neverconfig.toml. The one-time backupomnishell inittakes of your rc file before inserting the marker block is a snapshot like any other: rolling back to it removes omnishell's rc integration (like a targeteduninstall), leavingconfig.tomlin place. Two cases stay outsiderollback: anuninstall --purgebackup (saved outside the config directory since--purgedeletes it β restoring it is manual, the path is printed when it runs); and anythingremove --purgeuninstalled (packages, vendored files) βremove --purgereverses those, notrollback.
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Application error β e.g. package install failed, or β₯1 module degraded |
| 2 | Config / schema error β nothing was changed |
| 3 | Drift detected (omnishell doctor only) |
- fish support
- remote module registry / installing modules from a URL
- profiles (different module sets per machine)
- self-update (
brew/install.shcover it) - Windows / PowerShell
See CONTRIBUTING.md for build commands, test requirements, and the module-authoring reference.
See CHANGELOG.md for release history.
MIT β see LICENSE. Β© 2026 Jeffry WΓΌrmli.