Absent
Zero config. The git root is the project and default discovery supplies the command tree. Most projects can stay here.
$ cd ~/code/acme/infra/cloud$ mnmd root/home/you/code/acmezero config · bash & zsh · any language · Go engine
Drop executables into your repo's folders, in any language, and they're already a CLI: monom db migrate, with Tab completion in bash and zsh. No config file, no registration, nothing generated.
$ curl -fsSL https://monom.dev/install.sh | bashA live port of mnmd filter running against the example project used across this site. Folders complete as groups, scripts as commands.
adopting monom
01
Install monom once per machine. After that, a project adopts it by doing nothing: the scripts it already has become commands the moment you cd into it.
02
It looks at the executable files in your project and registers each one with a shebang or a command-style name, or that you declared. Then it maps the file tree to a ready-to-use CLI, with Tab completion in bash and zsh.
Any language works: Python, Node, bash, Ruby, a compiled Go binary. The path is the command, minus the file's extension, so db/migrate.py is monom db migrate. A command-style name matches ^[a-z0-9][a-z0-9_-]*$ (lowercase, no dots), so a file with an extension needs a shebang, and a compiled binary like tools/lint gets in by name.
Hidden and _-prefixed entries, node_modules, vendor, venv, target, dist, __pycache__ and nested monom projects are never scanned. Add discover.hide patterns to skip more.
03
Each folder is a word, and the file, without its extension, is the last one. The spaces you type are the slashes in the path, so monom infra cloud deploy runs infra/cloud/deploy.sh, from any folder inside the project.
Hover or tap a file to see its command.
mnmd check is the doctor. It flags executables discovery found but didn't register, as a warning by default, so a fresh project passes. When you're ready to enforce it, one line in a monom file makes it an error.
Outside git? touch monom marks a root. An empty file is a valid config.
$ mnmd checkwarning: [root-contents] executable not registered: tools/Format.sh (no shebang, and the name does not match ^[a-z0-9][a-z0-9_-]*$); add a shebang, rename it, or declare it in the monom config file✔ 6 commands OK (default discovery), 1 warning(s)$ echo $?0$ echo 'check.root-contents = error' > monom$ mnmd checkerror: [root-contents] executable not registered: tools/Format.sh (no shebang, and the name does not match ^[a-z0-9][a-z0-9_-]*$); add a shebang, rename it, or declare it in the monom config filemnmd check: 1 error(s), 0 warning(s)$ echo $?1the monom file
A file named monom at the root marks the project and customizes it. It comes in three shapes, decided by its first line.
Zero config. The git root is the project and default discovery supplies the command tree. Most projects can stay here.
$ cd ~/code/acme/infra/cloud$ mnmd root/home/you/code/acmeAny file without a shebang, including an empty one. monom parses it and never runs it: declare paths the gate skips, hide entries, set mnmd check's severity.
# register an executable the gate would skiptools/Format.sh# make mnmd check fail on unregistered executablescheck.root-contents = error# hide more entries, like dot-files (repeatable)discover.hide = infra/localAn executable with a shebang. monom runs it for hooks, all optional: complete, run, config, debug. Leave one out and the default applies.
#!/usr/bin/env bash# every hook is optional: without complete,# default discovery lists the commandscase "$1" in run) shift # monom ship → infra cloud deploy if [ "$1" = ship ]; then echo infra cloud deploy; fi ;; config) echo "discover.hide = tools" ;;esac$_MONOM_PROJECT_ROOT, the pin an alias or your rc file sets;monom;why monom
monom resolves a path and execs it. Your script never imports, sources, or links anything; a shebang or a binary is all it needs.
Discovery, filtering and resolution live in mnmd, a compiled Go binary. The shell layer is only what Go can't do: define monom, register completion, exec.
$ type -t monom mnmdfunctionfunctionCompletion is covered by YAML cases that start interactive bash and zsh in a pseudo-terminal, press Tab, and check the edit line and the listing on screen.
- name: tab completes a unique prefix and adds a space input: monom in action: keys tabs: 1 line: "monom infra "mnmd filter always exits 0. A broken path yields no suggestion, not an error splashed over your prompt. Diagnostics live in mnmd check.
$ echo 'bad path/x' | mnmd filter bad$ echo $?0A complete hook that prints paths replaces default discovery entirely: read git ls-files, parse a manifest, anything that prints lines.
complete) cd "$(dirname "$0")" || exit # tracked executables, minus this file git ls-files -s | awk '$1 == 100755 { print $4 }' | grep -vx monom ;;Your commands are plain executables in plain folders. Delete monom tomorrow and ./infra/cloud/deploy.sh still runs.
$ ./infra/cloud/deploy.shdeploying to cloud...nouns and verbs
Type a folder and monom tells you what's inside instead of failing, using the same pipeline as Tab, so the listing always matches what completion would offer.
Want monom infra to do something? Map it in the run hook. A group is never runnable by accident.
$ monom inframonom: 'infra' is a command groupavailable: cloud, localwho it's for
Every repo grows a pile of scripts nobody can navigate. With monom installed, a teammate's first monom Tab in the repo shows every workflow the team has, with nothing committed for monom's sake.
$ monom <Tab>db infra release tools$ monom db <Tab>migrate seedPin monom to a folder of personal scripts and they're tab-completable from any directory, organized by topic instead of prefixed my-thing-do-x.sh.
# ~/.zshrc, after the source line mnmd install addedexport _MONOM_PROJECT_ROOT="$HOME/scripts"compare
monom is closest to Basecamp's sub, a folder of scripts with completion, grown nested and given a Go engine. It is not a build tool: if you need a task graph or caching, reach for just, Task, or mise. And today every word you type is part of the command path: commands don't take arguments yet.
| monom | sub | just | Task | mise | bashly | |
|---|---|---|---|---|---|---|
| Each command is its own executable file | ● | ● | ○recipes in a justfile | ○tasks in Taskfile.yml | ●file tasks | ○compiled into one script |
| Nested command groups from folders | ●any depth | ○flat | ◐modules | ◐includes | ●as a:b | ●declared in YAML |
| Commands in any language | ● | ● | ●shebang recipes | ◐via shell | ● | ○bash |
| No generate or build step | ● | ● | ● | ● | ● | ○bashly generate |
| Your code can decide the command list | ●optional complete hook | ○ | ○ | ○ | ○ | ○ |
| Arguments, flag parsing and generated help | ○on the backlog | ◐help from comments | ● | ◐ | ●usage spec | ● |
| Dependencies between tasks | ○not a build tool | ○ | ● | ●+ caching | ● | ○ |
Compiled from each project's documentation, September 2026. Spotted something wrong? Open an issue.
principles
Shell code exists only where Go is technically impossible: sourcing into your session, registering completion, exec.
Every process boundary on the Tab path has to justify itself. Fewer hops, less latency, less to debug.
One central registry. A command group exits 3, always, and nothing else does.
monom requires nothing from your monom file, not even that it exists. Changing that takes a constitutional amendment.
One command, one rc line. Then open any repo and press Tab.
$ curl -fsSL https://monom.dev/install.sh | bashZero-config discovery, mnmd check and bash/zsh completion work today. Commands don't take arguments yet; flag parsing and scaffolding (mnmd init, mnmd new command) are on the backlog.