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/blendThe house blend
One Order, one honest file.
An Order declares which files to produce and where. Ordinary Nickel handles the differences between machines.
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"],
},
},
],
},
} | Orderfont_size = 14
plugins = [
"git",
"lsp",
]
theme = "latte"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.
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"Blend explains it by meaning
blend viewcompares 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"You choose a direction
blend syncasks 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.
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.
from_config = {
theme = "mocha",
font_size = base_size + 2,
plugins = ["git", "lsp"],
},How a sync goes
Look first, then pour.
blendSummarize which Orders agree with their Targets. Writes nothing.
blend viewPreview generated content and semantic differences. Writes nothing.
blend syncReconcile, 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/gitOther 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.shContainer
The image entrypoint is blend.
docker run --rm \
-v "$PWD:/workspace" \
-w /workspace \
ghcr.io/frantic1048/blend:latest checkFrom source
The binary is written to target/release/blend.
git clone https://github.com/frantic1048/blend.git
cd blend
cargo build --releaseRelease archives support Apple silicon macOS, Intel macOS, and ARM64 or x86-64 GNU/Linux.