Introduction
This book, by name, wants to introduce you with cutler, a software made to declaratively manage your macOS preferences and essentials. It can do a number of things. This book will help you navigate all of them, including new ones which are currently in development.
Basic Directions
When you follow along the book, you'll notice both command and code snippets as examples, showing you certain behavior. If the command is prefixed with a $, it may mean that you may need to only "follow" the structure
instead of just copy-pasting it. Other times, you can freely do so if the criterias specified are met.
If instead the code blocks have their file paths mentioned on top (for most of the time it will be the config file of cutler), it describes the structure of certail parts of the file.
Why cutler
While using macOS, I have come to realize that most solutions like Nix are not always easy to maintain either. So, I wanted to create a tool that abstracts away all the complexity into simple, already-known terms for most power users.
And that's how cutler came to be.
Project Links
There are some additional links that you may want to keep tabs on:
Let's Go!
To get started with your cutlery, move to the next page.
Installation
You can install cutler by directly running this command in the terminal:
curl -fsSL https://machlit.github.io/scripts/install-cutler.sh | /bin/bash
Other installation methods are:
- Using 🍺 Homebrew:
brew install machlit/tap/cutler
- Using
cargo:
cargo install cutler
- Using
mise:
# NOTE: This will compile the binary manually for your system.
mise use -g cargo:cutler
Once installed, you can also enable shell completions for your shell instance if needed. Installing via Homebrew doesn't require doing this step.
Manual Installation
Get the latest prebuilt compressed binaries if you would like to manually install the project.
Note than on devices running macOS, you'll have to remove the quarantine attribute from the binary:
# inside extracted zip
xattr -d com.apple.quarantine bin/cutler
Shell Integrations
Completions
cutler supports built-in shell completion for your ease of access for a variety of system shells, including Bash, Zsh, Powershell etc. Below you will find setup instructions to enable completions automatically for every new shell session.
NOTE: If you have installed cutler using Homebrew, the shell completion will automatically be installed. Just restart your shell after initial installation.
Bash
Run the command below:
eval "$(cutler completion bash)" > .bashrc # or .bash_profile
Then restart your shell or run:
source ~/.bashrc
Zsh
-
Create a directory for custom completions (if it doesn't exist):
mkdir -p ~/.zfunc -
Generate the completion script and move it:
cutler completion zsh > ~/.zfunc/_cutler -
Add the following to your
~/.zshrc:fpath=(~/.zfunc $fpath) autoload -U compinit && compinit -
Restart your shell or run:
source ~/.zshrc
Fish
Add the completion script to your fish configuration directory:
cutler completion fish > ~/.config/fish/completions/cutler.fish
Restart your shell or open a new fish session.
Elvish
Add the following to your Elvish configuration file (usually ~/.elvish/rc.elv):
eval (cutler completion elvish)
Restart your shell or source your config file.
PowerShell
Add the following to your PowerShell profile (you can find your profile path with $PROFILE):
cutler completion powershell | Out-String | Invoke-Expression
Restart your shell or run:
. $PROFILE
Quickstart
To easily get started, simply type the following command to generate a sample configuration:
Most commands support a set of global flags that affect output and behavior. See Global Flags for details.
cutler init
Once you run this command, you will get a copy of this starter template in the configuration path.
Configuration Paths
The path defaults to $HOME/.config/cutler/config.toml but can
be set anywhere within these locations:
$HOME/.config/cutler/config.toml$HOME/.config/cutler.toml$XDG_CONFIG_HOME/cutler/config.toml$XDG_CONFIG_HOME/cutler.toml
How to write a config?
Learn about this in the next section: Basics & System Settings
Basics & System Settings
Syntax
Here is a basic example of a cutler configuration file:
# ~/.config/cutler/config.toml
[set.dock]
tilesize = 46
[set.menuextra.clock]
FlashDateSeparators = true
macOS heavily relies on preference files (in .plist format) stored in certain ways to save the state of your Mac's apps and settings. cutler takes advantage of this mechanism to automatically put your desired system settings in place by following the config file you wrote. It's a "declarative" way to set your settings without even touching the app itself.
This will do the following:
- Set your Dock's tilesize to 46.
- Enable flashing date separators for the clock of your menu bar.
If you were to do the same with a terminal, you would use these commands:
$ defaults write com.apple.dock "tilesize" -int "46"
$ defaults write com.apple.menuextra.clock "FlashDateSeparators" -bool true
Global Preferences
You can also configure global preferences like this:
# ~/.config/cutler/config.toml
[set.NSGlobalDomain]
InitialKeyRepeat = 15
ApplePressAndHoldEnabled = true
"com.apple.mouse.linear" = true
# or, for the third entry, alternate structure:
#
# [set.NSGlobalDomain.com.apple.mouse]
# linear = true
Again, if you were to use defaults, it would look something like this:
$ defaults write NSGlobalDomain "ApplePressAndHoldEnabled" -bool true
$ defaults write NSGlobalDomain com.apple.mouse.linear -bool true
Applying & Undoing
Once you're ready, run this command to apply everything:
cutler apply
The apply command has multiple functionalities which happen alongside of applying the preferences. You may execute cutler apply -h or append the --help flag to see all the different options, or just read the cookbook.
To compare your system with your configuration and see what needs to be done, run:
cutler status
Unapplying everything is also as easy. Run the command below and cutler will restore your preferences to the exact previous state:
cutler unapply
Action Hints
The fun part about using cutler is, it will mostly tell you to take certain actions based on what command you are using, without you having to think about it. This is due to cutler's immense synchronization between commands.
Say, for example, if my dock should automatically hide based on cutler's configuration, and if it does not right now, cutler will show this when running cutler status:
$ cutler status
WARN com.apple.dock
WARN autohide: should be true (now: false)
WARN Preferences diverged. Run `cutler apply` to apply the config onto the system.
🍎 Homebrew status on sync.
$
As you can see, it suggests me to run cutler apply. Running the suggested command will only affect the changed portion of the preferences, and cutler will skip the rest.
Risky Operations
If you would like to write non-existent domains (create them) using cutler, use the --no-dom-check flag:
cutler apply --no-dom-check
This will disable the "Domain does not exist" error which happens when cutler's backend does not recognize a domain.
Homebrew Backups
If you're a person who struggles to keep tabs on all the installed formulae or apps using Homebrew, then cutler could be a great choice for you!
Backing up
You can back up your formula/cask names into your existng config file (or a new one) with this command:
cutler brew backup
# or, only backup the ones which are not a dependency:
#
# cutler brew backup --no-deps
How it saves:
The formulae and casks are always forced to back up in full-name convention, meaning that if you have a formula which derives from a tap, it would be saved like this:
{tap}/{formula/cask}
And, the backup is stored in a separate [brew] table below the system preferences or external commands that you may have declared. So, it would look something like this:
# ~/.config/cutler/config.toml
[brew]
taps = [
"machlit/tap"
]
casks = [
"nikitabobko/tap/aerospace", # full-name forced wherever needed to
"zulu@21",
"android-studio"
]
formulae = [
"rust",
"machlit/tap/cutler" # same here for the formula
]
# Ensure dependencies aren't accounted for.
# This is auto-set if --no-deps is used in `brew backup`.
no_deps = true
Installing
Now, when you want to install from the file, simply run:
cutler brew install
You can also invoke the command's functionalty from within cutler apply:
cutler apply --brew
This will install every formula/cask alongside applying preferences and running external commands.
The structure of the brew table inside cutler's configuration is like such:
While running this command, cutler will also notify you about any extra software which is untracked by it. Then, you can run cutler brew backup again to sync.
Backend Requirements (Optional)
Obviously, running Homebrew on a Mac requires the Xcode Command-Line Tools to be installed, let it be through Xcode itself or through the preincluded utility in macOS. By default, cutler will try to ensure that it is there, before executing any of the subprocesses.
If you want to manually install it, you can do so by running:
xcode-select --install
External Commands
Running commands to spring up your environment is essential for any workflow. Luckily, cutler is made with most scenarios in mind, given that most people usually set their dotfiles up with shell scripts which require manual execution and intervention.
You can define external commands with simple syntax like this:
# ~/.config/cutler/config.toml
[command.greet]
run = "echo Hello World"
# This runs:
# echo Hello World
Variables
You can store localized variables (not available to the shell environment) inside cutler for your commands as such:
# ~/.config/cutler/config.toml
[vars]
hostname = "darkstar"
[command.hostname]
run = """
#!/usr/bin/env bash
scutil --set LocalHostName $hostname
scutil --set HostName $hostname
scutil --set ComputerName $hostname
"""
sudo = true # a more "annotated" sudo
Prioritizing Commands
Some people would like to run their commands "before" other commands. But, cutler runs all commands in parallel, which might not be what you want. In that case, you can use the ensure_first key to run then in your desired serial. You can apply this to multiple commands.
# ~/.config/cutler/config.toml
[command.dotfiles]
run = "git clone repo && cd repo && stow . -t ~"
ensure_first = true
Ensuring Binaries
You may want to ensure that certain binaries/programs are available in $PATH before running an external command. You can do so with the required field, like this:
[command.development-tools]
run = "mise install && mise up"
required = ["mise"] # won't run if mise is not in $PATH
Running
External commands are run whenever you run cutler apply by default. However, if you'd like to only run the commands and not apply defaults, run:
cutler exec
You can also run a specific external command by attaching a name parameter:
$ cutler exec hostname # this runs the hostname command
Execution Modes & Flagging
You can flag certain commands to only run when a particular flag is passed through either apply or exec. Say, if you want to flag a Hello World command:
[command.greet]
run = "echo 'Hello World'"
flag = true
Now that this command is flagged, it will only run if you pass one of these flags:
--all-cmd/-a: Runs all declared commands.--flagged-cmd/-f: Runs flagged commands only.
$ cutler apply --all-cmd # or -a
$ cutler apply --flagged-cmd # or -f
Same goes for cutler exec since it also, by default, executes your commands in "regular" mode:
$ cutler exec --all # or -r
$ cutler exec --flagged # or -f
Remote Config & Sync
cutler features a simple built-in remote sync logic. For example, if you want to apply a config from a given URL without placing it manually on your machine, simply use the following command:
cutler apply --url https://example.com/config.toml
cutler will then download and validate the config file, and if all looks good, it will be loaded to be applied.
You can also use this functionality on your "existing" configurations, by utilizing the [remote] table:
[remote]
url = "https://example.com/config.toml"
autosync = true
Here, the autosync flag will ensure that the next time you run any command except the disabled commands, it will automatically fetch the config file beforehand from the provided remote.
Or, you can simply fetch from the config URL written in [remote] manually using the fetch command:
cutler fetch
In order to disable remote sync behavior while running any command, use the --no-sync global flag:
cutler status --no-sync
Disabled Commands
Some commands will not respect autosync = true and therefore NOT synchronize the config automatically:
fetchbrew backupself-updatecheck-updatecookbookcompletioninitresetconfig
Updating cutler
Please note that this only works if you have installed cutler manually.
To check for updates, run:
cutler check-update
To upgrade cutler, run:
cutler self-update
Configuration Features
There are some nifty features built into the software for your convenience. These configuration features have been documented below so that you can have a quick look:
Config-Locking
When you run cutler init, the configuration file will usually contain this key-value pair at the very top:
# ~/.config/cutler/config.toml
lock = true
...
Unless you remove it, this will happen:
$ cutler apply
ERR The config file is locked. Run `cutler unlock` to unlock.
$
You can use this feature to mark configurations as potentially unsafe to apply. cutler uses it to generate new configuration files for you so that you don't accidentally apply the sample.
From your terminal, it's also as easy to lock:
cutler lock
and, to unlock:
cutler unlock
Global Flags
cutler supports several global flags that can be used with any command:
-v,--verbose: Increase output verbosity.--quiet: Suppress all output except errors and warnings. This is useful for scripting or when you only want to see problems.--dry-run: Print what would be done, but do not execute any changes.-y,--accept-interactive: Accept all interactive prompts automatically.-n,--no-restart-services: Do not restart system services after command execution.--no-sync: Do not sync with remote config (if autosync = true).
Example usage:
cutler apply --quiet
This will apply your configuration, but only errors and warnings will be "hushed".
Uninstallation
Obviously, cutler is still an experimental software in heavy development, so if you would like to uninstall it, please follow these steps:
For Script Installs
Run this command in your terminal:
curl -fsSL https://machlit.github.io/scripts/uninstall-cutler.sh | /bin/bash
For Package Manager Installs
If you have installed cutler through a package manager, please follow the instructions that match your configuration:
- For Homebrew:
brew uninstall cutler
brew untap machlit/tap # if you had only installed cutler from the tap
- For
cargo:
cargo uninstall cutler
- For
mise:
mise unuse -g cargo:cutler
# when prompted,
# choose 'All' when pruning files if available
System Preferences Backend
defaults-rs
In order to automate the process for setting up System Preferences, instead of relying on the defaults command, cutler uses
the defaults-rs crate.
It communicates with the preferences daemon through the CoreFoundation API bindings in Rust. This is primarily known as the cfprefsd process inside macOS, which is used for setting, storing and caching preference and/or default key-value pairs.
You can view the source of the backend through the given links below. Consider contributing to make it even better for everyone!
Project Links
Contribution Guidelines
This is the standard contribution/development guidelines for the project. You may follow these to get a hold of the project quickly.
Table of Contents
Requirements
The prerequisites are as follows:
- Rust (
cutleris configured to use the 2024 edition of the language) - A Mac (preferably with Apple Silicon) for rapid development
I would personally recommend using the latest Rust version available. As of now, I'm using Rust v1.90 as my version.
Cloning the Repository
Once you have ensured the prerequisites, fork the repository from here and clone it using the following command:
# https
$ git clone https://github.com/<username>/cutler.git
# ssh
$ git clone git@github.com:<username>/cutler.git
Replace <username> with your GitHub username.
Required Rust Components
Make sure your environment has these tools (NOTE: This list can change based on what currently suits the project).
Production Workflow
CI/CD for cutler is done using GitHub Actions. You may find these workflows useful to look at:
- Release: .github/workflows/release.yml
- Unit tests: .github/workflows/tests.yml
The release workflow sets
MACOSX_DEPLOYMENT_TARGETto11.0, meaning all general distributions of cutler will be compatible with macOS Big Sur (11.0) or later versions. You may change this according to your own needs in the workflow file as cutler is largely version-agnostic.
The unit tests in the CI workflow are done using an Apple Silicon M1 (3-core) runner provided by GitHub Actions. See this page in GitHub's documentation for more information on all the runners. If the runners used in this project get outdated and don't get a bump, you may suggest one through GitHub Issues.
Build Reproduction
You can easily create a release build for cutler using the following command:
cargo build --release --locked
The major part of the release automation is currently done with GitHub Actions via the following workflow so, you can have a look at it to view the entire pipeline.
The unit testing is done via this workflow.
Code Formatting
The project uses core Rust tools to format and prettify the codebase:
# For global formatting
cargo fmt --all
# For code quality
cargo clippy --fix
Pull Request Guidelines
Before submitting a pull request, please ensure the following:
- Your code is well-documented and follows the established coding standards.
- The repository is correctly forked and your working branch is up-to-date with the latest changes from the main branch.
- All tests pass locally, and you have verified that your changes do not introduce regressions.
- If your pull request fixes an issue, mention the issue number in your PR description (e.g., Fixes #123).
- For larger changes, consider discussing your approach by opening an issue first.
Pull requests and issues must have the following pattern:
<type>: <title>
Possible types include:
- feat: New feature or enhancement
- fix: Bug fix
- docs: Documentation update
- style: Code style or formatting change
- refactor: Code refactoring without changing functionality
- test: Test-related changes
- chore: Maintenance or boring tasks
Resources
Finding the ideal set of macOS defaults can be challenging. Visit this website to have a look at some useful ones fast:
Sample configuration files are preincluded with this repository for you to have a look at and get hold of the tool quickly: see examples
License
This project is licensed permissively and will remain free forever. You may follow by the rules and regulations covered by the license when modifying, redistributing, or using cutler.