Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Backstory

Some of us really have a craze for configs and ricing. Though, looking at the dotfiles of others, I have realized that although they have really efficient scripts, they are not always easy to maintain and/or understand for others. I, personally, sometimes used to have a hard time navigating others' configurations.

And while, I might not be able to solve it for everyone, I have been using macOS for the past 4 years. Using it has given me a pretty good grip on the ecosystem and how preferences are handled.

Why?

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.

What this book does

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.

It's a solo project, but I hope it will give you the proper impression for shrinking dotfiles by an order of magnitude.

To get started with your cutlery, move to the next page.

Installation

You can install cutler by directly running this command in the terminal:

$ /bin/sh -c "$(curl -fsSL https://raw.githubusercontent.com/hitblast/cutler/main/install.sh)"

Other installation methods are:

  1. Using 🍺 homebrew:
$ brew install hitblast/tap/cutler
  1. Using cargo:
$ cargo install cutler
  1. 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:

$ xattr -d com.apple.quarantine bin/cutler  # inside extracted zip

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 instructions for each of them.

If you have installed cutler using Homebrew, the shell completion will automatically be installed. Just restart your shell after initial installation.

Bash completions setup

  1. Make a directory to store Bash-specific completions:
$ mkdir ~/.bash-completion.d/
  1. Generate the completion script using the following command and pipe the output to a new file:
$ cutler completion bash > cutler.bash
$ mv cutler.bash ~/.bash-completion.d/
  1. Finally, source the completion script. The best way would be to simply add it to your .bashrc file:
$ source ~/.bash_completion.d/cutler.bash > ~/.bashrc

Zsh completions setup

  1. Make sure you have a directory for custom completions:
$ mkdir -p ~/.zfunc
  1. Then, generate the completion script and move it over:
$ cutler completion zsh > _cutler
$ mv _cutler ~/.zfunc/
  1. Then, add to your ~/.zshrc:
$ fpath=(~/.zfunc $fpath)
$ autoload -U compinit && compinit
  1. Restart your shell or run:
$ source ~/.zshrc

For other shells

# Fish
$ cutler completion fish

# Elvish
$ cutler completion elvish

# PowerShell
$ cutler completion powershell

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

By default, cutler stores your configuration in ~/.config/cutler/config.toml. But, it can also have other values depending on your setup:

  • $XDG_CONFIG_HOME/cutler/config.toml
  • ~/.config/cutler/config.toml
  • ~/.config/cutler.toml
  • cutler.toml in the current directory

It respects your $XDG_CONFIG_HOME, so you don't have to worry about path issues.

The next section will describe how you can use this one configuration file to orchestrate between or use all of cutler's capabilities.

System Preferences

cutler can do a number of things if you use it right. Here's a basic example of a TOML configuration for cutler:

[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.

Ideally, the block above would look something like this if you were to manually call the defaults CLI tool which is used to modify these values on macOS:

$ defaults write com.apple.dock "tilesize" -int "46"
$ defaults write com.apple.menuextra.clock "FlashDateSeparators" -bool true

You can also configure global preferences like this:

[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

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. Please read the sections below in depth to learn more.

cutler also takes the changes into account and tracks them. To see your status, 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 be at the right based on cutler's configuration, and bottom is its actual value right now, cutler will show this when running cutler status:

$ cutler status
[WARNING] com.apple.dock | orientation: should be right (currently bottom)
[WARNING] Run `cutler apply` to apply these changes from your config.

🍎 All Homebrew formulae/casks match config.

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, you can run the apply command with a particular flag:

$ cutler apply --disable-checks

This will disable the "Domain does not exist" error which happens when cutler's backend does not recognize a domain.

Homebrew Management

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! Make sure your Homebrew installation is accessible from the $PATH variable, and then you can back up the necessary formula/cask names into the config file you previously wrote, using this command:

$ cutler brew backup

# or, only backup the ones which are not a dependency:
$ cutler brew backup --no-deps

This eliminates the usage of the notorious brew bundle command which creates a separate Bundlefile for you to track. Why do so much when all you need is just one, central file?

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 --with-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:

[brew]
taps = ["hitblast/tap"]
casks = ["zed", "zulu@21", "android-studio"]
formulae = ["rust", "python3"]

# Ensure dependencies aren't accounted for.
# This is auto-set if --no-deps is used in `brew backup`.
no-deps = true

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 Prerequisites

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:

[commands.greet]
run = "echo Hello World"

# This runs:
# $ echo Hello World

The ideal structure for writing external commands should be like this:

[vars]
hostname = "darkstar"

[commands.hostname]
run = """
#!/usr/bin/env bash
scutil --set LocalHostName $hostname
scutil --set HostName $hostname
scutil --set ComputerName $hostname
"""
sudo = true  # a more "annotated" sudo

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.

[commands.dotfiles]
run = "git clone repo && cd repo && stow . -t ~"
ensure-first = true

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

Configuration Management

cutler's configuration can be tiny or versatile depending on your needs. But, there are some nifty features built into the software for your convenience.

Config-locking

When you run cutler init, the configuration file will usually contain this key-value pair at the very top:

lock = true

Unless you remove it, this will happen:

$ cutler apply
[ERROR] The config file is locked. Run `cutler config 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.

There are two commands to manage the lock status:

$ cutler config lock
# or
$ cutler config unlock

View or delete

To view your cutler configuration without the use of cat or any other fancy tools, use:

$ cutler config show

You can also delete it using:

$ cutler config delete

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.

Example usage:

cutler apply --quiet

This will apply your configuration, but only errors and warnings will be "hushed".

Resources

Finding the ideal set of macOS defaults can be challenging. Visit this website to have a look at some useful ones fast:

  1. macOS defaults website

Sample configuration files are preincluded with this repository for you to have a look at and get hold of the tool quickly: see examples

Backend for Preferences

In cutler's earlier versions, it used to wrap around the defaults CLI using asynchronous command invocations. However, this had a counterpart of being slow and extensive in terms of I/O and shell command execution.

cutler's latest versions include defaults-rs, a frontend for macOS preferences which has been directly written for interop with cutler. Instead of command invocation, it parses the preferences and can also modify them in batches, all the while minimizing the amount of I/O per call.

It has a neat little Rust API which is also asynchronous.

This gives some visible benefits:

  1. Instead of lag between commands, now you get instantaneous modifications to your settings (use cutler apply -v to feel the speed).

  2. Granular control over what gets modified and what doesn't.

  3. The community gets to modify and upgrade the backend.

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

Getting Started

The commonplace of contributing is to first clone the repository and install the dependencies.

The prerequisites are as follows:

  • Rust (cutler is configured to use the 2024 edition of the language)
  • A Mac (preferably with Apple Silicon) for rapid development

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/<your_username>/cutler.git

# ssh
$ git clone git@github.com:<your_username>/cutler.git

Replace <your_username> with your GitHub username.

Preparing the environment

Working on this project will require a few Rust components beforehand:

Production Release Workflow

This chain of commands can be used to fully test and build the final product.

Testing

# raw command
$ cargo fmt --all -- --check && cargo test --verbose && cargo clippy && cargo build

If you want to manually run this command before pushing, I recommend using hookman. I've built it to automate setting up git hooks. Just run this command inside the source directory to set things up:

$ hookman build

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 --verbose --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

cutler uses basic Rust formatting for code reliability and maintainability. This ensures that the codebase remains clean, readable, and consistent across different contributors.

Simply run the following command to format the code:

$ cargo fmt --all

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 administrative tasks

License

This project is licensed under the MIT License - see the LICENSE file for details.