Original post

Go Report Card Built with Nix License: MIT v1.0.0

nighthook is a simple background service for macOS that enables arbitrary execution of commands/scripts when the system changes between light & dark mode

As the sun sets, wherever you are on our wonderful planet 🌍 trigger a reminder, post some stats, or help other parts of your system keep up appearances, even if they don’t have native support for light/dark mode


nighthook -action < command or path to executable script >

nighthook will pass a single argument to the action: either light or dark depending on the mode the system just changed to.
You can also interpolate the mode into your action by using the %mode variable. For example, if the -action string is command --long-flag="%mode" and the system switches to dark mode, the resulting action executed will be command --long-flag="dark".

Having trouble? See the examples, limitations section, or raise an issue


There are a few ways of installing nighthook:

  • Precompiled binaries are available on the releases page
  • If you have installed you can simply go get github.com/cmacrae/nighthook or clone this repo and go build from within
  • A Nix module is included as part of this repo. It provides a package and customizable launchd user service. See the next section for using the Nix module

Nix Module

To use the included Nix module, you must have nix-darwin installed.


Option Description
services.nighthook.enable Whether to enable the user service
services.nighthook.action The action to execute upon mode change
services.nighthook.environment A set of environment variables to expose to the user service

Note: For further details, please see default.nix

Example Configuration

{ config, lib, pkgs, ... }:
  # Fetch this repository and import it
  imports = [
    (builtins.fetchTarball https://github.com/cmacrae/nighthook/archive/master.tar.gz)

  # Enable the service
  # Switching to this configuration will yield a 'ae.cmacr.nighthook' launchd service
  services.nighthook.enable = true;

  # Populate the launchd manifest's environment with a PATH
  # value that mimics the user's shell environment
  services.nighthook.environment = {
    PATH = (replaceStrings ["$HOME"] [builtins.getEnv("HOME")] config.environment.systemPath);

  # Set the '-action' to the path of a resulting shell script
  services.nighthook.action = ''${pkgs.writeShellScript "nighthook-action" ''

    emacsSwitchTheme () {
      if pgrep -q Emacs; then
        if [[  $MODE == "dark"  ]]; then
            emacsclient --eval "(my/switch-theme 'doom-one)"
        elif [[  $MODE == "light"  ]]; then
            emacsclient --eval "(my/switch-theme 'doom-solarized-light)"

    spacebarSwitchTheme() {
      if pgrep -q spacebar; then
        if [[  $MODE == "dark"  ]]; then
            spacebar -m config background_color 0xff202020
            spacebar -m config foreground_color 0xffa8a8a8
        elif [[  $MODE == "light"  ]]; then
            spacebar -m config background_color 0xffeee8d5
            spacebar -m config foreground_color 0xff073642

    alacrittySwitchTheme() {
      if [[  $MODE == "dark"  ]]; then
        cp -f $DIR/alacritty.yml $DIR/live.yml
      elif [[  $MODE == "light"  ]]; then
        cp -f $DIR/light.yml $DIR/live.yml

    yabaiSwitchTheme() {
      if [[  $MODE == "dark"  ]]; then
        yabai -m config active_window_border_color "0xff5c7e81"
        yabai -m config normal_window_border_color "0xff505050"
        yabai -m config insert_window_border_color "0xffd75f5f"
      elif [[  $MODE == "light"  ]]; then
        yabai -m config active_window_border_color "0xff2aa198"
        yabai -m config normal_window_border_color "0xff839496 "
        yabai -m config insert_window_border_color "0xffdc322f"

    emacsSwitchTheme $@
    spacebarSwitchTheme $@
    alacrittySwitchTheme $@
    yabaiSwitchTheme $@


It’s important to keep in mind that nighthook is not capable of triggering fully fledged shell pipelines directly, via the -action flag.
However this is easily achievable by pointing -action at the path to a script. So long as the script meets the following requirements:

  • It’s executable
  • It has a valid interpreter (#!)
  • The environment in the script is set up so binaries can be found (PATH)

This isn’t just limited to shell scripts. So long as -action is pointed at something executable, it should just run it.

Futhermore, due to the mechanisms nighthook uses (watching for filesystem events), actions can trigger at varying rates (usually between ~1s & 7s).
This is a limitation imposed by how macOS handles saving the current state of light/dark mode. Generally speaking, nighthook was written with the ‘auto’ mode in mind.