A dotfile manager for macOS & Linux

Declarative dotfiles, thoughtfully reconciled.

Compose your configuration in Nickel. Blend renders it into the files your applications read, compares them by meaning rather than bytes, and helps both sides meet safely.

brew install frantic1048/tap/blend

The house blend

One Order, one honest file.

An Order declares which files to produce and where. Ordinary Nickel handles the differences between machines.

orders/editor/order.ncl
let { Order, .. } = import "../order.contract.ncl" in
let metadata = import "../metadata.ncl" in
{
  blend = {
    prefix = ["~/.config/editor/"],
    files = [
      {
        name = "settings.toml",
        from_config = {
          theme = "latte",
          font_size =
            if metadata.os == "darwin" then 14 else 12,
          plugins = ["git", "lsp"],
        },
      },
    ],
  },
} | Order
~/.config/editor/settings.toml
font_size = 14
plugins = [
    "git",
    "lsp",
]
theme = "latte"

Renders and compares

  • TOML
  • JSON & JSONC
  • YAML
  • Plaintext
  • key value lines
  • key=value lines
Format details →

The return pour

Then the file talks back.

Applications rewrite their settings, and so do you. Blend notices, explains the change by meaning, and lets you choose which side wins, even carrying the edit back into your Nickel.

  1. The app changes a value

    You pick a darker theme and a larger font in the editor. The Target no longer matches its Source.

    ~/.config/editor/settings.toml
    font_size = 16
    plugins = [
        "git",
        "lsp",
    ]
    theme = "mocha"
  2. Blend explains it by meaning

    blend view compares parsed values, not lines, so reformatting or reordering keys is not a difference. Changed values are.

    $ blend view editor
    editor
      settings.toml -> ~/.config/editor/settings.toml
        ~ font_size
          << Source: 14
          >> Target: 16
        ~ theme
          << Source: "latte"
          >> Target: "mocha"
  3. You choose a direction

    blend sync asks per key. The last sync’s snapshot shows which side moved. Source rewrites the file; Target adopts the edit.

    $ blend sync editor
      editor:settings.toml (~/.config/editor/settings.toml)
        ~ font_size
          << Source: 14
          >> Target: 16
          || Base: 14
        [snapshot] font_size: Target changed (suggests Target)
        [s]ource  [t]arget  s[k]ip  [a]ll-source  a[l]l-target  [q]uit: t

Rewritten in your declaration

A concrete value is edited in place, even inside the active branch of a condition: the macOS size becomes 16 and Linux keeps 12.

orders/editor/order.ncl
from_config = {
  theme = "mocha",
  font_size =
    if metadata.os == "darwin" then 16 else 12,
  plugins = ["git", "lsp"],
},

Handed back to you

A computed value has no single literal to rewrite. Blend adopts what it safely can, says what it could not, and keeps reporting the difference until you edit the declaration.

orders/editor/order.ncl
from_config = {
  theme = "mocha",
  font_size = base_size + 2,
  plugins = ["git", "lsp"],
},

Cannot apply Target -> Source for font_size: unsupported expression type (update order.ncl manually)

What reverse sync can rewrite →

How a sync goes

Look first, then pour.

  1. blend

    Summarize which Orders agree with their Targets. Writes nothing.

  2. blend view

    Preview generated content and semantic differences. Writes nothing.

  3. blend sync

    Reconcile, choosing Source or Target per difference, or let declared policies decide.

First cup

Start with a file you already have.

brew install frantic1048/tap/blend
mkdir dotfiles && cd dotfiles
blend init
blend add git ~/.config/git
Other ways to install

Release installer

Verifies the release checksum and installs to ~/.local/bin by default.

release=https://github.com/frantic1048/blend/releases/latest/download
curl -fsSLO "$release/blend-installer.sh"
sh blend-installer.sh

Container

The image entrypoint is blend.

docker run --rm \
  -v "$PWD:/workspace" \
  -w /workspace \
  ghcr.io/frantic1048/blend:latest check

From source

The binary is written to target/release/blend.

git clone https://github.com/frantic1048/blend.git
cd blend
cargo build --release

Release archives support Apple silicon macOS, Intel macOS, and ARM64 or x86-64 GNU/Linux.

Continue with the guide