How Omarchy Handles Arguments Passed to CLI Commands: Group-Command Dispatch Pattern
TLDR: Omarchy uses a hierarchical dispatcher in bin/omarchy that extracts the first two positional arguments as group and command, shifts them from the argument list, and executes the corresponding sub-script (bin/omarchy-${group}-${cmd}) with remaining arguments forwarded via $@.
Omarchy is Basecamp’s open-source system configuration tool that implements a unique group-command architecture for its command-line interface. Understanding how Omarchy handles arguments passed to CLI commands reveals a lightweight yet extensible pattern where a central dispatcher delegates parsing to specialized sub-scripts. This design keeps the core CLI minimal while allowing individual commands to evolve their own flag sets independently.
The Group-Command Dispatcher in bin/omarchy
The entry point for all Omarchy CLI operations is the dispatcher script located at bin/omarchy in the repository root. This script implements a strict two-level namespace where commands are organized into groups, and each combination maps to a specific executable sub-script.
Extracting Group and Command Identifiers
When the omarchy binary is invoked, the script reads the first positional argument ($1) as the group (e.g., theme, update, toggle) and the second argument ($2) as the command within that group. These values determine which sub-script handles the request.
Shifting and Forwarding Arguments
After extracting the group and command, the dispatcher applies shift 2 to remove these tokens from the argument list. The remaining arguments—whether flags like -f or positional values—are preserved in $@ and forwarded to the target script. The dispatcher constructs the script path using the pattern "${OMARCHY_PATH}/bin/omarchy-${group}-${cmd}" and executes it via exec, replacing the current process entirely.
#!/usr/bin/env bash
group=$1
cmd=$2
shift 2
script="${OMARCHY_PATH}/bin/omarchy-${group}-${cmd}"
if [[ -x "$script" ]]; then
exec "$script" "$@"
else
echo "Unknown command: $group $cmd"
exit 1
fi
Sub-Script Argument Parsing Strategy
Because the dispatcher forwards arguments verbatim without interpretation, each sub-script retains full control over its own argument parsing logic. This allows different commands to implement distinct flag sets without modification to the central dispatcher.
Option Parsing with getopts
Command-specific scripts typically use Bash’s built-in getopts utility to process short options. For example, the update-related sub-script located at bin/omarchy-update implements a standard while loop to handle flags:
#!/usr/bin/env bash
while getopts "fd" opt; do
case $opt in
f) FORCE=1 ;;
d) DRY_RUN=1 ;;
*) echo "Invalid option"; exit 1 ;;
esac
done
shift $((OPTIND-1))
# $@ now contains only non-option arguments
Manual Argument Handling
Sub-scripts that do not require flag parsing can access forwarded arguments directly through positional parameters ($1, $2, etc.) or iterate over $@. This approach is common for commands like toggle that expect simple on/off values or device identifiers without complex option sets.
Practical CLI Usage Examples
The hierarchical argument structure enables intuitive command composition where the group and command form a namespace, followed by command-specific options:
# General pattern: omarchy <group> <command> [options] [arguments]
# Force a system update (parsed by bin/omarchy-update)
omarchy update install -f
# Dry-run update to preview changes
omarchy update install -d
# Toggle a feature (receives arguments directly)
omarchy toggle touchscreen
In the update install example, the dispatcher extracts update and install, resolves the script path to bin/omarchy-update-install (following the ${group}-${cmd} convention), and forwards -f to that script. The sub-script’s getopts loop then processes the force flag accordingly.
Summary
- Centralized dispatch: The
bin/omarchyscript routes all commands by extracting group and command identifiers from the first two positional arguments. - Argument isolation: Using
shift 2, the dispatcher strips the namespace components and forwards the remaining$@array to the target sub-script. - Decentralized parsing: Sub-scripts like
bin/omarchy-updateimplement their owngetoptsloops or manual parsing, allowing independent evolution of command-line interfaces. - Path convention: Sub-scripts follow the naming pattern
bin/omarchy-${group}-${cmd}and must be executable to be invoked by the dispatcher.
Frequently Asked Questions
How does Omarchy parse command-line flags?
Omarchy does not parse flags at the top level. Instead, the dispatcher in bin/omarchy forwards all arguments after the group and command to the appropriate sub-script. Each sub-script is responsible for its own option parsing, typically using Bash’s getopts built-in as demonstrated in bin/omarchy-update.
What happens if I provide an invalid group or command?
If the dispatcher cannot find an executable file at the constructed path "${OMARCHY_PATH}/bin/omarchy-${group}-${cmd}", it outputs "Unknown command: $group $cmd" to stderr and exits with status code 1. No partial matching or fuzzy logic is applied.
Can Omarchy sub-scripts accept positional arguments?
Yes, sub-scripts receive all forwarded arguments via $@ after the dispatcher executes shift 2. They can access positional arguments directly using $1, $2, etc., or process them iteratively. The omarchy toggle command, for instance, receives device identifiers as positional arguments.
Where is the main argument dispatch logic located?
The primary dispatch logic resides in bin/omarchy at the repository root. This script handles the initial extraction of group and command identifiers, validates the target script’s existence, and manages the exec call that transfers control to sub-scripts like bin/omarchy-update or bin/omarchy-toggle.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →