Introduction
Decknix is an opinionated Nix framework for macOS configuration management. It combines Nix Flakes, nix-darwin, and home-manager into a batteries-included system that's easy to customise and share across teams.
Why Decknix?
- One command to set up a Mac — bootstrap installs Nix, nix-darwin, and your full dev environment
- Layered configuration — framework defaults → org/team configs → personal overrides
- Everything in Nix — editors, shell, git, window manager, CLI tools, AI tooling
- Team-friendly — org configs are versioned flake inputs that everyone shares
- Easy to override — every default uses
lib.mkDefault, so your preferences always win
What's Included
| Category | Highlights |
|---|---|
| Editors | Emacs (full IDE with 13+ modules), Vim |
| Shell | Zsh with Starship prompt, completions, syntax highlighting |
| Git | Delta diffs, Magit, Forge (GitHub PRs from Emacs) |
| Dev Tools | ripgrep, jq, curl, gh CLI, language servers |
| Window Manager | AeroSpace tiling WM with fuzzy workspace picker |
| AI Tooling | Augment Code agent with declarative MCP config |
| CLI | decknix switch, decknix update, extensible subcommands |
How This Documentation Is Organised
- Getting Started — install and build your first configuration
- Architecture — understand the 3-layer model, config loader, and directory layout
- Configuration — customise settings, features, secrets, and org configs
- Modules — explore every module: editors, shell, git, WM, AI
- CLI Reference — core commands and the extension system
- Guides — set up org configs for your team, develop the framework, troubleshoot
Installation
This guide walks you through setting up decknix on a fresh or existing macOS system.
Prerequisites
- macOS (Apple Silicon or Intel)
- Administrator access (for initial Nix installation)
- ~10GB disk space for the Nix store
Option 1: Fresh Install (Recommended)
Run the bootstrap script to install everything from scratch:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/ldeck/decknix/main/bin/bootstrap)"
This will:
- Install Nix with flakes enabled
- Install nix-darwin
- Create your local config directory at
~/.config/decknix/ - Initialize a flake in
~/.config/decknix/ - Prompt you for username and hostname
Option 2: Existing Nix Installation
If you already have Nix with flakes enabled:
# Create and enter your config directory
mkdir -p ~/.config/decknix && cd ~/.config/decknix
# Initialize from template
nix flake init -t github:ldeck/decknix
# Edit your settings
$EDITOR settings.nix
Edit settings.nix with your machine details:
{
username = "your-username"; # macOS username
hostname = "your-hostname"; # Machine name
system = "aarch64-darwin"; # or "x86_64-darwin" for Intel
role = "developer"; # "developer", "designer", or "minimal"
}
Then build and switch:
decknix switch
# Or if decknix CLI isn't installed yet:
sudo darwin-rebuild switch --flake .#default --impure
For Organisation Members
If your team maintains an org config repo, check their README for a dedicated bootstrap script that sets up both decknix and the team configuration in one step.
See Organisation Configs for how org configs work.
Next Steps
- First Configuration — set up your identity and packages
- Applying Changes — learn the day-to-day workflow
First Configuration
After installation, you'll have this directory structure:
~/.config/decknix/
├── flake.nix # Main flake (imports decknix)
├── flake.lock # Locked dependencies
├── settings.nix # username, hostname, system, role
└── local/ # Your personal overrides
├── home.nix # Home-manager config
└── system.nix # Darwin system config
Set Up Git Identity
Edit ~/.config/decknix/local/home.nix:
{ pkgs, ... }: {
programs.git.settings = {
user.email = "you@example.com";
user.name = "Your Name";
};
}
Add Your Packages
{ pkgs, ... }: {
home.packages = with pkgs; [
nodejs
python3
go
];
}
Choose Your Editor Profile
Decknix offers tiered editor profiles:
| Profile | Emacs Includes | Vim Includes |
|---|---|---|
minimal | Core, completion, editing, UI, undo | Base config |
standard | + development, magit, treemacs, languages, welcome | + whitespace, skim |
full (default) | + LSP, org-mode, HTTP client, agent-shell | — |
custom | Your own config (disables framework) | Your own config |
Change profiles in your home.nix:
{ ... }: {
decknix.editors.emacs.profile = "standard";
decknix.editors.vim.profile = "minimal";
}
Apply Your Changes
decknix switch
The first build takes a few minutes as it downloads packages. Subsequent builds are faster.
Verify Installation
# Check Emacs daemon is running
launchctl list | grep emacs
# Open a file in Emacs
ec test.txt
# Check Magit
# In Emacs: C-x g (opens git status)
Next Steps
- Applying Changes — day-to-day workflow
- Personal Overrides — directory layout and advanced customisation
- Secrets & Authentication — GitHub tokens, GPG, SSH keys
Applying Changes
Day-to-Day Workflow
The core loop is: edit → switch → done.
Switch to Your New Configuration
decknix switch
This runs darwin-rebuild switch under the hood, applying both system and home-manager changes atomically.
Dry Run (Build Without Activating)
decknix switch --dry-run
Builds the configuration to check for errors without actually switching to it.
Update Framework and Dependencies
# Update all flake inputs (decknix, nixpkgs, etc.)
decknix update
# Update a specific input
decknix update decknix
After updating, run decknix switch to apply.
Development Mode
Test local framework changes before pushing:
# Use your local decknix checkout
decknix switch --dev
# Or specify an explicit path
decknix switch --dev-path ~/projects/decknix
This passes --override-input decknix path:~/tools/decknix to darwin-rebuild.
Common Patterns
Edit Personal Config
$EDITOR ~/.config/decknix/local/home.nix
decknix switch
Add a New Package
# ~/.config/decknix/local/home.nix
{ pkgs, ... }: {
home.packages = with pkgs; [
kubectl
helm
];
}
decknix switch
Disable a Module
# ~/.config/decknix/local/home.nix
{ ... }: {
programs.emacs.decknix.welcome.enable = false;
decknix.wm.aerospace.enable = false;
}
decknix switch
Troubleshooting
"command not found: decknix"
The CLI isn't in your path yet. Use the full command:
sudo darwin-rebuild switch --flake ~/.config/decknix#default --impure
Build Errors
Check the trace output to see which files were loaded:
[Loader] home + /Users/you/.config/decknix/local/home.nix
[Loader] system + /Users/you/.config/decknix/local/system.nix
Reset to Clean State
rm -rf ~/.config/decknix
# Re-run bootstrap or nix flake init
How Decknix Works
Decknix uses a 3-layer configuration model where each layer can override the one below it.
The 3 Layers
┌─────────────────────────────────────────┐
│ Layer 3: Personal Overrides │ ~/.config/decknix/local/
│ Your packages, identity, preferences │ ~/.config/decknix/<org>/
├─────────────────────────────────────────┤
│ Layer 2: Organisation Configs │ Flake inputs (versioned repos)
│ Team tools, standards, shared settings │ e.g. inputs.my-org-config
├─────────────────────────────────────────┤
│ Layer 1: Decknix Framework │ github:ldeck/decknix
│ Sensible defaults for everything │ darwinModules + homeModules
└─────────────────────────────────────────┘
Layer 1 — Framework
The decknix flake provides darwinModules.default and homeModules.default containing opinionated defaults for shell, editors, git, window management, and more. Every value uses lib.mkDefault, so it can be overridden by any higher layer without lib.mkForce.
Layer 2 — Organisation Configs
Teams create separate repos (e.g. github:MyOrg/decknix-config) that export their own darwinModules.default and homeModules.default. These are added as flake inputs and supply team-specific tools, packages, and settings.
Layer 3 — Personal Overrides
Each user's ~/.config/decknix/ directory contains personal overrides that are auto-discovered and merged at build time. These live outside git (or in a personal dotfiles repo) and let you customise without touching shared configs.
How Builds Work
When you run decknix switch:
mkSystemreads yoursettings.nix(username, hostname, system, role)- Constructs a
darwinConfigurations.defaultthat merges:- Framework modules (Layer 1)
- Org modules passed via
darwinModules/homeModules(Layer 2) configLoaderoutput from~/.config/decknix/(Layer 3)
- Calls
darwin-rebuild switchto atomically activate the new generation
Key Design Decisions
lib.mkDefaulteverywhere — the framework never fights your preferences- Filesystem auto-discovery — drop a
.nixfile in the right place and it's loaded - Flake inputs for teams — version-pinned, reproducible, Renovate-watchable
- Secrets separated —
secrets.nixfiles are gitignored and loaded alongsidehome.nix - Impure builds —
--impureis required so the config loader can read~/.config/decknix/at build time
Next
- Directory Layout — where everything lives
- Config Loader — how files are discovered and merged
Directory Layout
User Configuration
~/.config/decknix/ # Your flake + personal overrides
├── flake.nix # Main flake (imports decknix + org configs)
├── flake.lock # Pinned dependency versions
├── settings.nix # username, hostname, system, role
│
├── local/ # Personal overrides (always loaded)
│ ├── home.nix # Packages, git identity, shell aliases
│ ├── system.nix # macOS system preferences
│ └── secrets.nix # Auth tokens, keys (gitignored)
│
├── <org-name>/ # Per-org personal overrides
│ ├── home.nix # Org-specific personal tweaks
│ ├── system.nix
│ ├── secrets.nix
│ └── home/ # Nested home modules (recursively loaded)
│ └── extra.nix
│
└── secrets.nix # Root-level secrets (also supported)
Key Points
local/is for generic personal config — git identity, extra packages, shell aliases<org-name>/directories match flake input names — overrides specific to that orgsecrets.nixfiles are gitignored and loaded alongsidehome.nixhome/subdirectories are recursively scanned for additional.nixfiles- All directories are auto-discovered — no registration needed
Framework Source
decknix/
├── bin/ # Bootstrap scripts
│ └── bootstrap.sh # Fresh install script
├── cli/ # Rust CLI source
│ └── src/main.rs # switch, update, help, extensions
├── docs/ # This documentation site
├── lib/
│ ├── default.nix # mkSystem + configLoader
│ └── find.nix # File discovery utilities
├── modules/
│ ├── cli/ # decknix CLI nix-darwin module
│ │ └── default.nix # Subtask system, extensions.json
│ ├── common/
│ │ └── unfree.nix # Unfree package allowlist
│ ├── darwin/ # macOS system modules
│ │ ├── default.nix # System packages, fonts, defaults
│ │ ├── aerospace.nix # AeroSpace tiling WM (system-level)
│ │ └── emacs.nix # Emacs daemon service
│ └── home/ # Home-manager modules
│ ├── default.nix # Imports + default packages
│ ├── options.nix # Role templates, core options
│ └── options/
│ ├── cli/ # auggie, board, extensions, nix-github-auth
│ ├── editors/ # emacs/ (13 modules), vim/
│ └── wm/ # aerospace/, hammerspoon/, spaces.nix
├── pkgs/ # Custom Nix packages
├── templates/ # Flake templates for `nix flake init`
└── flake.nix # Framework flake
Config Loader
The config loader (decknix.lib.configLoader) is the engine that discovers and merges personal override files from ~/.config/decknix/.
How It Works
- Scan
~/.config/decknix/for all subdirectories - For each directory, look for:
home.nix— home-manager modulesystem.nix— nix-darwin modulesecrets.nix— secrets (merged into home-manager)home/**/*.nix— recursively loaded home modules
- Also check for root-level files (
~/.config/decknix/home.nix, etc.) - Import every discovered file and trace what was loaded
Discovery Order
~/.config/decknix/
├── local/home.nix ← loaded
├── local/system.nix ← loaded
├── local/secrets.nix ← loaded (merged into home)
├── my-org/home.nix ← loaded
├── my-org/home/
│ └── extra.nix ← loaded (recursive)
├── secrets.nix ← loaded (root-level)
└── home.nix ← loaded (root-level)
All files are merged — ordering within a layer is discovery order (alphabetical by directory name).
Trace Output
When you build, the loader traces what it finds:
[Loader] home + /Users/you/.config/decknix/local/home.nix
[Loader] home + /Users/you/.config/decknix/my-org/home.nix
[Loader] system + /Users/you/.config/decknix/local/system.nix
[Loader] No secrets modules found.
Use this to verify which files are being picked up:
decknix switch 2>&1 | grep "\[Loader\]"
API Reference
configLoader
decknix.lib.configLoader {
lib = nixpkgs.lib; # Required
username = "your-username"; # Required
hostname = "your-hostname"; # Optional (default: "unknown")
system = "aarch64-darwin"; # Optional (default: "unknown")
role = "developer"; # Optional (default: "developer")
homeDir = "/Users/you"; # Optional (auto-derived from username + system)
configDir = "/Users/you/.config/decknix"; # Optional (auto-derived)
}
Returns:
{
modules = {
home = [ ... ]; # List of imported home + secrets modules
system = [ ... ]; # List of imported system modules
};
allDirs = [ "local" "my-org" ]; # Discovered directory names
}
mkSystem
The top-level builder that wires everything together:
decknix.lib.mkSystem {
inputs; # Your flake inputs (must include decknix, nixpkgs, nix-darwin)
settings = import ./settings.nix; # { username, hostname, system, role }
darwinModules = [ ... ]; # Extra darwin modules (org configs)
homeModules = [ ... ]; # Extra home-manager modules (org configs)
extraSpecialArgs = {}; # Additional args passed to modules
stateVersion = "24.05"; # home.stateVersion
}
Returns: { darwinConfigurations.default = ...; }
The build merges modules in this order:
- Framework
darwinModules.default/homeModules.default - Your
darwinModules/homeModulesargs (org configs) configLoaderoutput (personal overrides from filesystem)
Settings Reference
The settings.nix file in your flake directory defines your machine identity:
# ~/.config/decknix/settings.nix
{
username = "ldeck"; # Your macOS username
hostname = "lds-mbp"; # Machine hostname
system = "aarch64-darwin"; # "aarch64-darwin" (Apple Silicon) or "x86_64-darwin" (Intel)
role = "developer"; # Bootstrap template: "developer", "designer", or "minimal"
}
Fields
| Field | Type | Default | Description |
|---|---|---|---|
username | string | "setup-required" | Your macOS login username (whoami) |
hostname | string | "setup-required" | Machine hostname (hostname -s) |
system | string | "aarch64-darwin" | Nix system identifier |
role | enum | "developer" | Determines which bootstrap template is applied |
Roles
The role field selects a starter template for first-time setup:
| Role | What It Adds |
|---|---|
developer | Git config template + nodejs |
designer | Inkscape |
minimal | Nothing extra — blank slate |
After the first build, the role has minimal impact. You can always add or remove packages in your home.nix regardless of role.
Where Settings Are Used
Settings flow into the build via mkSystem:
# flake.nix
outputs = inputs@{ decknix, ... }:
decknix.lib.mkSystem {
inherit inputs;
settings = import ./settings.nix;
};
mkSystem uses them to:
- Set
networking.hostName - Set
system.primaryUser - Derive the home directory path
- Pass
roleto home-manager for template selection - Configure
configLoaderpaths
Personal Overrides
Personal overrides live in ~/.config/decknix/ and are auto-discovered by the config loader.
Directory Structure
~/.config/decknix/
├── local/ # Generic personal config (always loaded)
│ ├── home.nix
│ ├── system.nix
│ └── secrets.nix # Gitignored
├── my-org/ # Per-org overrides (matches flake input name)
│ ├── home.nix
│ └── home/
│ └── kubernetes.nix # Recursively loaded
└── secrets.nix # Root-level secrets
How Overrides Work
Decknix framework defaults all use lib.mkDefault, so any value you set in your personal files wins automatically:
# Framework sets: programs.git.settings.pull.rebase = lib.mkDefault true;
# Your override: programs.git.settings.pull.rebase = false; ← wins
For cases where another module explicitly sets a value (without mkDefault), use lib.mkForce:
{ lib, ... }: {
programs.emacs.decknix.welcome.enable = lib.mkForce false;
}
Common Override Patterns
Add Packages
# ~/.config/decknix/local/home.nix
{ pkgs, ... }: {
home.packages = with pkgs; [
kubectl
helm
terraform
awscli2
];
}
Shell Aliases
{ ... }: {
programs.zsh.shellAliases = {
k = "kubectl";
tf = "terraform";
};
}
Environment Variables
{ ... }: {
home.sessionVariables = {
EDITOR = "emacsclient -c";
AWS_PROFILE = "default";
};
}
macOS System Preferences
# ~/.config/decknix/local/system.nix
{ ... }: {
system.defaults = {
dock.autohide = true;
finder.ShowPathbar = true;
NSGlobalDomain.KeyRepeat = 2;
};
}
Homebrew Casks
# ~/.config/decknix/local/system.nix
{ ... }: {
homebrew.casks = [
"docker"
"slack"
"1password"
];
}
Debugging
See which files are loaded:
decknix switch 2>&1 | grep "\[Loader\]"
Evaluate a specific option without building:
nix repl
:lf .
darwinConfigurations.default.config.home-manager.users.YOU.home.packages
Organisation Configs
Org configs let teams share a standard set of tools, packages, and settings through versioned flake inputs.
How It Works
An org config is a separate git repo that exports darwinModules.default and homeModules.default. These are wired into your flake as inputs:
# ~/.config/decknix/flake.nix
{
inputs = {
decknix.url = "github:ldeck/decknix";
nixpkgs.follows = "decknix/nixpkgs";
nix-darwin.follows = "decknix/nix-darwin";
# Team config
my-org-config = {
url = "github:MyOrg/decknix-config";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = inputs@{ decknix, ... }:
decknix.lib.mkSystem {
inherit inputs;
settings = import ./settings.nix;
darwinModules = [ inputs.my-org-config.darwinModules.default ];
homeModules = [ inputs.my-org-config.homeModules.default ];
};
}
Creating an Org Config Repo
Minimal structure:
my-org-config/
├── flake.nix
├── home.nix # Team home-manager modules
├── system.nix # Team darwin modules
└── README.md
flake.nix
{
description = "My Org - Decknix Config";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
outputs = { self, nixpkgs, ... }: {
darwinModules.default = import ./system.nix;
homeModules.default = import ./home.nix;
};
}
home.nix
{ pkgs, ... }: {
home.packages = with pkgs; [
awscli2
terraform
jdk17
];
}
Benefits
- Version pinning —
flake.lockpins a known-good version - Reproducibility — every team member gets the same tools
- Easy updates —
nix flake update my-org-configto pull latest - Automated updates — Renovate or Dependabot can watch for new versions
- Personal overrides — users can still override anything in
~/.config/decknix/<org-name>/
Testing Changes
Before merging changes to an org config, test locally:
cd ~/.config/decknix
decknix switch --dev-path ~/Code/my-org/decknix-config
# Or:
sudo darwin-rebuild switch --flake .#default --impure \
--override-input my-org-config path:~/Code/my-org/decknix-config
See also: Adding Org Configs for Your Team for a step-by-step walkthrough.
Secrets & Authentication
Decknix keeps secrets separate from your main configuration via gitignored secrets.nix files.
How secrets.nix Works
The config loader discovers and loads secrets.nix files alongside home.nix:
~/.config/decknix/secrets.nix(root level)~/.config/decknix/<org>/secrets.nix(per-org)
Both are merged into your home-manager configuration.
Quick Setup
# ~/.config/decknix/local/secrets.nix
{ ... }: {
home.file.".authinfo".text = ''
machine api.github.com login YOUR_USERNAME^forge password ghp_YOUR_TOKEN
'';
}
Make sure secrets.nix is gitignored:
echo "secrets.nix" >> ~/.config/decknix/.gitignore
GitHub Token for Forge
Forge (GitHub PRs in Emacs) needs a Personal Access Token.
1. Create a Token
- Go to GitHub → Settings → Developer Settings → Personal Access Tokens
- Generate a classic token with scopes:
repo,read:org,read:user - Copy the token (starts with
ghp_)
2. Add to secrets.nix
{ ... }: {
home.file.".authinfo".text = ''
machine api.github.com login YOUR_USERNAME^forge password ghp_xxxxxxxxxxxx
'';
}
3. Verify in Emacs
M-x auth-source-search RET
host: api.github.com
user: YOUR_USERNAME^forge
GPG-Encrypted Alternative
# Create and encrypt
echo "machine api.github.com login USER^forge password ghp_xxx" | \
gpg --encrypt --recipient YOUR_KEY_ID > ~/.authinfo.gpg
{ ... }: {
programs.emacs.extraConfig = ''
(setq auth-sources '("~/.authinfo.gpg"))
'';
}
macOS Keychain
{ ... }: {
programs.emacs.extraConfig = ''
(setq auth-sources '(macos-keychain-internet macos-keychain-generic))
'';
}
security add-internet-password -a "USER^forge" -s "api.github.com" -w "ghp_xxx"
Multi-Account GitHub Setup
For multiple GitHub accounts (personal + work), add entries for each:
{ ... }: {
home.file.".authinfo".text = ''
machine api.github.com login personal-user^forge password ghp_personal_xxx
machine api.github.com login work-user^forge password ghp_work_yyy
'';
}
When you first use Forge in a repo, it prompts for which username to use. The choice is stored in .git/config.
Combine with Git conditional includes for automatic email switching:
# ~/.config/decknix/my-org/home.nix
{ ... }: {
programs.git.includes = [{
condition = "gitdir:~/Code/my-org/";
contents.user.email = "you@my-org.com";
}];
}
SSH Keys
{ ... }: {
programs.ssh = {
enable = true;
matchBlocks."github.com" = {
identityFile = "~/.ssh/id_ed25519";
user = "git";
};
extraConfig = ''
AddKeysToAgent yes
UseKeychain yes
'';
};
}
GPG Setup
{ pkgs, ... }: {
home.packages = [ pkgs.gnupg pkgs.pinentry_mac ];
programs.gpg.enable = true;
home.file.".gnupg/gpg-agent.conf".text = ''
pinentry-program ${pkgs.pinentry_mac}/bin/pinentry-mac
default-cache-ttl 3600
max-cache-ttl 86400
'';
}
Nix GitHub Auth
Decknix automatically provides authenticated GitHub API access to Nix (5,000 req/hr instead of 60). This uses gh auth token to generate ~/.config/nix/access-tokens.conf on every decknix switch.
No configuration needed — enabled by default via decknix.nix.githubAuth.enable.
Security Best Practices
- Never commit secrets — always gitignore
secrets.nix - Use GPG encryption — encrypt
.authinfoas.authinfo.gpg - Use short-lived tokens — set token expiration when possible
- Limit token scopes — only grant necessary permissions
- Prefer SSH — use SSH over HTTPS for git operations
- Rotate regularly — update tokens periodically
Enabling & Disabling Features
Every decknix default uses lib.mkDefault, making it easy to override in your personal config.
Emacs Modules
# ~/.config/decknix/local/home.nix
{ ... }: {
# Disable specific modules
programs.emacs.decknix.welcome.enable = false;
programs.emacs.decknix.org.presentation.enable = false;
programs.emacs.decknix.http.enable = false;
# Disable specific language modes
programs.emacs.decknix.languages.rust.enable = false;
programs.emacs.decknix.languages.go.enable = false;
# Disable all Emacs config (use your own)
programs.emacs.decknix.enable = false;
}
See Emacs module reference for all available options.
Editor Profiles
Switch between pre-defined tiers instead of toggling individual modules:
{ ... }: {
decknix.editors.emacs.profile = "standard"; # minimal | standard | full | custom
decknix.editors.vim.profile = "minimal"; # minimal | standard | custom
}
Window Manager
{ ... }: {
# AeroSpace
decknix.wm.aerospace.enable = false;
# Or enable with custom workspaces
decknix.wm.aerospace = {
enable = true;
workspaces = {
"1" = { name = "Terminal"; };
"2" = { name = "Browser"; };
"3" = { name = "Code"; };
};
};
}
AI Tooling
{ ... }: {
decknix.cli.auggie.enable = false;
}
Git Features
{ ... }: {
# Disable Forge (GitHub PRs in Emacs)
programs.emacs.decknix.magit.forge.enable = false;
# Disable code-review
programs.emacs.decknix.magit.codeReview.enable = false;
}
System-Level Features
# ~/.config/decknix/local/system.nix
{ ... }: {
# Disable Emacs daemon
services.emacs.decknix.enable = false;
# Disable AeroSpace system optimisations
decknix.services.aerospace.enable = false;
}
Using lib.mkForce
When a simple override doesn't work (because another module sets a value explicitly), use mkForce:
{ lib, ... }: {
programs.emacs.decknix.enable = lib.mkForce false;
}
Use sparingly — in most cases, a normal override is sufficient because the framework uses lib.mkDefault.
Modules Overview
Decknix is organised into modular components, each responsible for a specific area of your environment. Every module uses lib.mkDefault so you can override any setting.
Module Categories
Home-Manager Modules
| Module | Description | Page |
|---|---|---|
| Emacs | Full IDE with 13+ sub-modules, profiles, daemon | Emacs → |
| Vim | Whitespace cleanup, skim fuzzy finder | Vim → |
| Shell & Terminal | Zsh, Starship prompt, completions | Shell → |
| Git | Delta diffs, global config, LFS | Git → |
| Window Management | AeroSpace, Hammerspoon, Spaces | WM → |
| AI Tooling | Augment Code agent, MCP servers, Agent Shell | AI → |
Darwin (System) Modules
| Module | Description |
|---|---|
| System Defaults | Packages (vim, git, curl, skim), Nerd Fonts, Dock/Finder prefs |
| AeroSpace System | Disables Stage Manager, Mission Control shortcuts, separate Spaces |
| Emacs Daemon | Background Emacs service via launchd, ec wrapper command |
| CLI Module | Installs decknix binary, generates extensions config |
Core Options
| Option | Description | Default |
|---|---|---|
decknix.role | Bootstrap template: "developer", "designer", "minimal" | "developer" |
decknix.username | Your macOS username (set automatically by mkSystem) | — |
decknix.hostname | Machine hostname | — |
Editor Profiles
Instead of toggling individual modules, choose a profile tier:
Emacs Profiles
| Profile | Modules Included |
|---|---|
minimal | core, completion, editing, UI, undo, project |
standard | minimal + development, magit, treemacs, languages, welcome |
full (default) | standard + LSP, org-mode, HTTP client, agent-shell |
custom | Disables framework Emacs — bring your own config |
Vim Profiles
| Profile | Modules Included |
|---|---|
minimal | Base config (exrc, line numbers, secure) |
standard (default) | minimal + whitespace + skim |
custom | Disables framework Vim — bring your own config |
# Change profiles
{ ... }: {
decknix.editors.emacs.profile = "standard";
decknix.editors.vim.profile = "minimal";
}
Default Packages
Installed for all users regardless of role:
coreutils · curl · wget · tree · jq · ripgrep · gh
System-level: vim · git · curl · skim
Fonts: JetBrains Mono Nerd Font
Editors
Decknix provides opinionated configurations for two editors: Emacs (full IDE experience) and Vim (lightweight enhancements).
Emacs
A batteries-included Emacs configuration with 13+ modules covering completion, git, LSP, languages, org-mode, and more. Runs as a background daemon on macOS.
Highlights:
- Modern completion stack (Vertico, Consult, Corfu)
- Git integration via Magit + Forge (GitHub PRs)
- LSP support for Kotlin, Java, and more via Eglot
- 30+ language modes with syntax highlighting
- Org-mode presentations
- REST API client
- AI Agent Shell — multi-session AI interface
Vim
Lightweight enhancements on top of the base Vim config.
Highlights:
- Trailing whitespace cleanup (vim-better-whitespace)
- Fuzzy file finder (skim)
- Base config: line numbers, exrc, secure mode
Profiles
Both editors support tiered profiles to control how much framework config is applied:
{ ... }: {
decknix.editors.emacs.profile = "standard"; # minimal | standard | full | custom
decknix.editors.vim.profile = "minimal"; # minimal | standard | custom
}
Setting custom disables the framework's editor config entirely, letting you bring your own.
Emacs
Decknix provides a modern, batteries-included Emacs experience with 13+ modules, background daemon, and three profile tiers.
Modules
| Module | Description | Profile |
|---|---|---|
| Core | Modus theme, line numbers, better defaults | minimal+ |
| Completion | Vertico, Consult, Corfu, Embark | minimal+ |
| Editing | Smartparens, Crux, Move-text, EditorConfig | minimal+ |
| UI | Which-key, Helpful, Nerd-icons | minimal+ |
| Undo | undo-fu, vundo (visual undo tree) | minimal+ |
| Project | Project management and navigation | minimal+ |
| Welcome | Startup screen with keybinding cheat sheet | standard+ |
| Development | Flycheck, Yasnippet | standard+ |
| Magit | Git interface, Forge (GitHub PRs), code-review | standard+ |
| Treemacs | Project file tree with git integration | standard+ |
| Languages | 30+ language modes with syntax highlighting | standard+ |
| LSP | Eglot, kotlin-ls, jdt-ls, dape (debugging) | full |
| Org-mode | Modern styling, presentations (Olivetti) | full |
| HTTP | REST client, jq integration, org-babel | full |
| Agent Shell | AI agent interface (Augment Code) | full |
Key Bindings — Quick Reference
Navigation & Search
| Key | Action |
|---|---|
C-s | Search in buffer (consult-line) |
C-x b | Switch buffer with preview |
M-s r | Project-wide ripgrep search |
M-y | Browse kill ring |
C-. | Context actions (Embark) |
Git (Magit)
| Key | Action |
|---|---|
C-x g | Magit status |
@ f f | Fetch forge topics (PRs/issues) |
@ c p | Create pull request |
@ l p | List pull requests |
File Tree (Treemacs)
| Key | Action |
|---|---|
C-x t t | Toggle treemacs |
C-x t f | Find current file in tree |
LSP / Code
| Key | Action |
|---|---|
C-c l r | Rename symbol |
C-c l a | Code actions |
C-c l f | Format region |
C-c l F | Format buffer |
C-c l d | Show documentation |
Debugging (dape)
| Key | Action |
|---|---|
C-c d d | Start debugger |
C-c d b | Toggle breakpoint |
C-c d n | Step over |
C-c d s | Step in |
C-c d c | Continue |
Editing
| Key | Action |
|---|---|
C-a | Smart home (Crux) |
C-c d | Duplicate line |
M-up/down | Move line/region |
C-/ | Undo |
C-? | Redo |
C-x u | Visual undo tree (vundo) |
Org-mode
| Key | Action |
|---|---|
F5 or C-c p | Start/stop presentation |
n / p | Next/previous slide |
Languages
30+ languages with syntax highlighting:
| Category | Languages |
|---|---|
| Primary | Kotlin, Java, Scala, SQL, Terraform/HCL, Shell, Nix, Python |
| Data | JSON, YAML, TOML, XML, Markdown |
| Web | HTML, CSS/SCSS/LESS, JavaScript, TypeScript, JSX, Vue, Svelte |
Emacs Daemon
Configured in modules/darwin/emacs.nix, enabled by default on macOS. Runs as a background launchd service — no Dock icon, no Cmd+Tab entry.
ec filename # Open file in Emacs
ec -c -n # New GUI frame
ec -c -n file.txt # Open file in new GUI frame
ec -t file.txt # Open in terminal
emacsclient -c # Create new GUI frame
GUI frames appear in the Dock while open; closing a frame doesn't kill the daemon.
| Option | Default | Description |
|---|---|---|
services.emacs.decknix.enable | true | Enable Emacs daemon |
services.emacs.decknix.package | pkgs.emacs | Emacs package to use |
services.emacs.decknix.additionalPath | [] | Extra PATH entries for daemon |
Module Options Reference
Disabling Modules
{ ... }: {
programs.emacs.decknix.enable = false; # ALL emacs config
programs.emacs.decknix.welcome.enable = false; # Welcome screen
programs.emacs.decknix.magit.enable = false; # Git interface
programs.emacs.decknix.magit.forge.enable = false; # Just Forge
programs.emacs.decknix.completion.enable = false; # Completion stack
programs.emacs.decknix.treemacs.enable = false; # File tree
programs.emacs.decknix.undo.enable = false; # Undo enhancements
programs.emacs.decknix.editing.enable = false; # Editing enhancements
programs.emacs.decknix.development.enable = false; # Flycheck/Yasnippet
programs.emacs.decknix.ui.enable = false; # UI enhancements
programs.emacs.decknix.ui.icons.enable = false; # Just icons
programs.emacs.decknix.org.enable = false; # Org enhancements
programs.emacs.decknix.lsp.enable = false; # LSP/IDE
programs.emacs.decknix.http.enable = false; # REST client
programs.emacs.decknix.languages.enable = false; # All languages
}
Customisation
{ pkgs, ... }: {
# Add your own packages
programs.emacs.extraPackages = epkgs: [ epkgs.evil epkgs.lsp-mode ];
# Add your own config
programs.emacs.extraConfig = ''
(evil-mode 1)
(setq my-custom-variable t)
'';
}
Note: Evil mode (Vim emulation) is not included by default. Add it in your personal config as shown above.
Vim
Decknix provides lightweight Vim enhancements on top of a sensible base config.
Base Config (All Profiles)
set exrc— load project-local.vimrcset secure— restrict commands in project.vimrc- Line numbers enabled
Whitespace Module
Plugin: vim-better-whitespace
Automatically strips trailing whitespace on save.
| Option | Default | Description |
|---|---|---|
programs.vim.decknix.whitespace.enable | true (standard profile) | Enable whitespace cleanup |
programs.vim.decknix.whitespace.stripModifiedOnly | true | Only strip modified lines |
programs.vim.decknix.whitespace.confirm | false | Prompt before stripping |
Skim Module
Plugin: skim (fuzzy finder)
Integrates skim into Vim for fast file and buffer searching.
| Option | Default | Description |
|---|---|---|
programs.vim.decknix.skim.enable | true (standard profile) | Enable skim integration |
Profiles
| Profile | Includes |
|---|---|
minimal | Base config only |
standard (default) | Base + whitespace + skim |
custom | Disables framework Vim entirely |
{ ... }: {
decknix.editors.vim.profile = "minimal";
}
Adding Your Own Config
{ ... }: {
programs.vim = {
enable = true;
plugins = [ pkgs.vimPlugins.vim-surround ];
extraConfig = ''
set relativenumber
'';
};
}
Shell & Terminal
Decknix configures Zsh as the default shell with modern enhancements.
Zsh
Enabled by default with:
- Completion — case-insensitive, menu-driven completion
- Autosuggestion — fish-like suggestions from history
- Syntax highlighting — command validation as you type
- History — 50,000 entries, shared across sessions, prefix-based search
Starship Prompt
Starship provides a fast, customisable prompt showing git status, language versions, and more.
Customise the prompt character:
{ ... }: {
programs.starship.settings.character = {
success_symbol = "[➜](bold green)";
error_symbol = "[✗](bold red)";
};
}
Delta (Diff Viewer)
Delta is configured as the default git pager, providing syntax-highlighted diffs.
Default Shell Aliases
Decknix doesn't impose shell aliases — add your own:
{ ... }: {
programs.zsh.shellAliases = {
ll = "ls -la";
gs = "git status";
gp = "git pull --rebase";
};
}
Extra Init
Add custom shell initialization:
{ ... }: {
programs.zsh.initExtra = ''
# Source work credentials
[[ -f ~/.config/secrets/env.sh ]] && source ~/.config/secrets/env.sh
'';
}
Session Variables
{ ... }: {
home.sessionVariables = {
EDITOR = "emacsclient -c";
VISUAL = "emacsclient -c";
};
}
Git
Decknix provides a sensible Git configuration with modern tooling.
Default Settings
| Setting | Value | Description |
|---|---|---|
init.defaultBranch | main | Default branch name |
pull.rebase | true | Rebase on pull instead of merge |
push.autoSetupRemote | true | Auto-create remote tracking branch |
core.pager | delta | Syntax-highlighted diffs |
lfs.enable | true | Git Large File Storage |
Delta Integration
Delta provides beautiful, syntax-highlighted diffs in the terminal with line numbers.
Customising Git
# ~/.config/decknix/local/home.nix
{ ... }: {
programs.git.settings = {
user.email = "you@example.com";
user.name = "Your Name";
core.editor = "emacsclient -c";
};
}
Conditional Includes
Use different identities for different directories:
{ ... }: {
programs.git.includes = [{
condition = "gitdir:~/Code/work/";
contents = {
user.email = "you@company.com";
user.name = "Your Name";
commit.gpgsign = true;
};
}];
}
Magit (Emacs Git Interface)
The Emacs module includes Magit — a full Git interface inside Emacs:
C-x g→ Git status- Stage, commit, push, pull, rebase — all from keyboard
- Forge — manage GitHub PRs and issues without leaving Emacs
- code-review — inline PR review with comments
See Secrets & Authentication for Forge token setup.
GitHub CLI
The gh CLI is installed by default. Decknix also auto-configures authenticated GitHub API access for Nix itself via decknix.nix.githubAuth (see Secrets).
Window Management
Decknix includes tiling window manager support for macOS.
AeroSpace
AeroSpace is a tiling window manager inspired by i3. Decknix provides both home-manager and darwin modules.
Enabling
# home.nix
{ ... }: {
decknix.wm.aerospace.enable = true;
}
Options
| Option | Default | Description |
|---|---|---|
decknix.wm.aerospace.enable | false | Enable AeroSpace |
decknix.wm.aerospace.prefixKey | "cmd+alt" | Prefix key for commands |
decknix.wm.aerospace.keyStyle | "emacs" | "emacs" (arrows) or "vim" (hjkl) |
decknix.wm.aerospace.enableModeIndicator | — | Show current mode in status bar |
Workspaces
Define named workspaces with optional monitor assignment:
{ ... }: {
decknix.wm.aerospace.workspaces = {
"1" = { name = "Terminal"; };
"2" = { name = "Browser"; };
"3" = { name = "Code"; };
"4" = { name = "Chat"; monitor = "secondary"; };
};
}
Default workspaces: 1–5 (main, web, term, mail, chat) + D, E, N, M, S (decknix, emacs, notes, music, system).
System-Level Settings
The darwin module optimises macOS for tiling:
| Option | Default | Effect |
|---|---|---|
decknix.services.aerospace.disableStageManager | true | Prevents conflicts |
decknix.services.aerospace.disableSeparateSpaces | true | Multi-monitor support |
decknix.services.aerospace.disableMissionControlShortcuts | true | Frees Ctrl+arrow keys |
decknix.services.aerospace.autohideDock | true | Maximises screen space |
Hammerspoon
Hammerspoon provides Lua-based macOS automation.
{ ... }: {
decknix.wm.hammerspoon = {
enable = true;
modifier = "Meta + Ctrl";
};
}
Generates Lua configuration with space navigation bindings.
Spaces
Multi-monitor workspace management with named space groups:
{ ... }: {
decknix.wm.spaces.workspaces = {
dev = {
name = "Development";
startSpace = 1;
spaces = [ "terminal" "editor" "browser" ];
key = "d";
};
};
}
Generates space picker scripts with shortcodes for quick switching.
AI Tooling
Decknix provides a declarative, Nix-managed AI development environment — from CLI agent configuration to a full Emacs-native agent interface with session management, work context awareness, and prompt engineering tools.
What's Included
| Component | Description | Page |
|---|---|---|
| Auggie CLI | Augment Code agent with Nix-managed settings and MCP servers | Configuration → |
| Agent Shell | Emacs-native multi-session AI interface (7 sub-modules) | Agent Shell → |
Design Principles
- Declarative first — all configuration lives in Nix.
decknix switchreproduces your entire AI setup on any machine. - Runtime-mutable — settings are copied (not symlinked) so tools can modify them at runtime. The next
decknix switchresets to the Nix-managed baseline. - Composable — each component is independently toggleable. Use the CLI without Emacs, or Emacs without MCP servers.
- Session-as-first-class — AI conversations are persistent objects with tags, context items, and metadata — not disposable chat buffers.
Architecture
┌─────────────────────────────────────────────────┐
│ Nix Configuration (agent-shell.nix, auggie.nix)│
│ ┌──────────┐ ┌──────────┐ ┌────────────────┐│
│ │ Settings │ │ MCP │ │ Commands & ││
│ │ & Model │ │ Servers │ │ Templates ││
│ └─────┬─────┘ └─────┬────┘ └───────┬────────┘│
└────────┼──────────────┼───────────────┼──────────┘
▼ ▼ ▼
~/.augment/ ~/.augment/ ~/.augment/commands/
settings.json settings.json ~/.emacs.d/snippets/
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────┐
│ Emacs Agent Shell │
│ ┌──────┐ ┌─────────┐ ┌────────┐ ┌───────────┐│
│ │Core │ │Sessions │ │Prompts │ │Context ││
│ │ACP │ │Tags │ │Commands│ │CI/PR/Issue││
│ └──────┘ └─────────┘ └────────┘ └───────────┘│
└─────────────────────────────────────────────────┘
Quick Start
AI tooling is enabled by default in the full Emacs profile. To start:
C-c A a → Start an agent session
C-c A s → Session picker (live + saved + new)
C-c A ? → Full keybinding reference
For CLI-only usage without Emacs:
auggie # Interactive agent session
auggie session list # List saved sessions
auggie session resume ID # Resume a session
Next Steps
- Configuration — Auggie CLI settings, MCP servers, model selection
- Agent Shell Overview — The Emacs agent interface
- Vision — Where this is heading
AI Configuration
All AI tooling is configured declaratively in Nix and deployed via decknix switch.
Auggie CLI
Enabling
{ ... }: {
decknix.cli.auggie.enable = true;
}
Settings
{ ... }: {
decknix.cli.auggie.settings = {
model = "opus4.6";
indexingAllowDirs = [
"~/tools/decknix"
"~/Code"
];
};
}
Settings are written to ~/.augment/settings.json. The file is copied (not symlinked) so auggie can modify it at runtime; the next decknix switch overwrites with the Nix-managed version.
MCP Servers
Declaratively configure Model Context Protocol servers:
{ ... }: {
decknix.cli.auggie.mcpServers = {
context7 = {
type = "stdio";
command = "npx";
args = [ "-y" "@upstash/context7-mcp@latest" ];
env = {};
};
"gcp-monitoring" = {
type = "stdio";
command = "npx";
args = [ "-y" "gcp-monitoring-mcp" ];
env.GOOGLE_APPLICATION_CREDENTIALS = "~/.config/gcloud/credentials.json";
};
};
}
MCP servers are written into the mcpServers section of ~/.augment/settings.json.
Slack MCP Workspaces
Connect auggie to one or more Slack workspaces using the official Slack MCP server:
{ ... }: {
decknix.cli.auggie.slack.workspaces = {
acme-corp = {
clientId = "3660753192626.123456";
description = "ACME Corp team workspace";
};
personal = {
clientId = "3660753192626.789012";
};
};
}
Each workspace generates a slack-<name> entry in mcpServers pointing at https://mcp.slack.com/mcp with the workspace's CLIENT_ID for OAuth authentication.
Setup requirements:
- Create or reuse a Slack app at api.slack.com/apps
- Enable OAuth with appropriate scopes (e.g.,
search:read.public,chat:write,channels:history) - Publish as an internal app or to the Slack Marketplace
- Copy the Client ID from the app's OAuth settings
Multiple workspaces merge naturally — define some in your org config, others in your personal config, and they all appear in settings.json.
Viewing Configured Servers
From Emacs: C-c A S opens a formatted buffer showing all configured MCP servers with their type, command, args, and environment variables.
Runtime vs Nix-Managed
| Source | Persists across decknix switch? | How to add |
|---|---|---|
| Nix config | ✅ Yes | decknix.cli.auggie.mcpServers |
auggie mcp add | ❌ No (temporary) | Runtime command |
Agent Shell Module
The Emacs agent-shell module is enabled by default in the full profile:
{ ... }: {
programs.emacs.decknix.agentShell = {
enable = true; # Core agent-shell.el + ACP
manager.enable = true; # Tabulated session dashboard
workspace.enable = true; # Dedicated tab-bar workspace
attention.enable = true; # Mode-line attention tracker
templates.enable = true; # Yasnippet prompt templates
commands.enable = true; # Nix-managed slash commands
context.enable = true; # Work context panel (issues, PRs, CI)
};
}
Each sub-module can be independently disabled. See Agent Shell Overview for details on each component.
Custom Commands
Nix-managed commands are deployed to ~/.augment/commands/ as symlinks. User-created commands (regular files) coexist in the same directory and are not affected by decknix switch.
# Commands are defined in agent-shell.nix and deployed automatically.
# To add your own at runtime:
# C-c c n → Create new command (opens template in ~/.augment/commands/)
See Productivity for the full command framework.
Agent Shell
The Emacs Agent Shell is a native, multi-session AI agent interface built on agent-shell.el and the Augment Code Protocol (ACP). It turns Emacs into a first-class AI development environment where sessions are persistent, context-aware, and deeply integrated with your workflow.
Why Not Just a Chat Buffer?
Most AI integrations treat conversations as disposable text. Agent Shell treats them as first-class objects:
- Sessions persist — resume any conversation from days ago with full context
- Sessions have metadata — tags, pinned issues, CI status, review threads
- Sessions are searchable — find any past session by keyword or tag
- Sessions are composable — structured prompts via templates and slash commands
- Sessions are work-aware — auto-detect issues, PRs, and Jira tickets from conversation text
Package Ecosystem
Agent Shell is assembled from 6 packages using tiered sourcing:
| Package | Source | Purpose |
|---|---|---|
shell-maker | nixpkgs unstable | Comint-like shell buffer management |
acp | nixpkgs unstable | Augment Code Protocol client |
agent-shell | nixpkgs unstable | Core agent interface |
agent-shell-manager | Custom derivation | Tabulated session dashboard |
agent-shell-workspace | Custom derivation | Dedicated tab-bar workspace |
agent-shell-attention | Custom derivation | Mode-line attention tracker |
Plus ~1,800 lines of custom Elisp in agent-shell.nix providing sessions, tags, compose, commands, templates, and context awareness.
Layers
The implementation is organised into 5 layers, each building on the previous:
| Layer | Name | What It Provides | Page |
|---|---|---|---|
| 1 | Foundation | Core shell, ACP protocol, package sourcing | Foundation → |
| 2 | Multi-Session | Session picker, resume, history, quit | Multi-Session → |
| 3 | Productivity | Compose buffer, templates, commands, tags | Productivity → |
| 4 | Integration | MCP servers, declarative tool config | Integration → |
| 5 | Context | Issues, PRs, CI status, review threads | Context → |
Quick Reference
C-c A a Start / switch to agent
C-c A s Session picker (live + saved + new)
C-c A e Compose multi-line prompt
C-c A ? Full keybinding help
C-c A I Context panel (issues, PRs, CI)
Inside an agent-shell buffer, drop the A prefix: C-c s, C-c e, C-c ?, etc.
Foundation (Layer 1)
The foundation layer provides the core shell infrastructure that everything else builds on.
Core Components
shell-maker
The underlying comint-like buffer management library. Handles prompt rendering, input submission, scroll behaviour, and process lifecycle. Agent Shell inherits its robust terminal semantics.
ACP (Augment Code Protocol)
The acp package implements the wire protocol between Emacs and the auggie CLI. Unlike HTTP-based integrations, ACP runs auggie as a subprocess — no server, no ports, no latency.
agent-shell.el
The main interface package. Provides:
- Agent configuration and model selection
- Session lifecycle (start, interrupt, rename)
- Mode-line status display
- Buffer management
Tiered Package Sourcing
Packages are sourced from the most stable channel available:
Priority 1: stable nixpkgs → (nothing currently — all are too new)
Priority 2: unstable nixpkgs → shell-maker, acp, agent-shell
Priority 3: custom derivations → agent-shell-manager, workspace, attention
Custom derivations use trivialBuild with pinned GitHub revisions and hashes:
agent-shell-manager-el = pkgs.emacsPackages.trivialBuild {
pname = "agent-shell-manager";
version = "0-unstable-2026-03-17";
src = pkgs.fetchFromGitHub {
owner = "jethrokuan";
repo = "agent-shell-manager";
rev = "53b73f1...";
hash = "sha256-JPB/OnOhYbM0LMirSYQhpB6hW8SAg0Ri6buU8tMP7rA=";
};
packageRequires = [ agent-shell ];
};
As packages mature into nixpkgs, they'll migrate up the priority chain automatically.
Default Behaviour
| Setting | Value | Why |
|---|---|---|
agent-shell-preferred-agent-config | 'auggie | Skip agent selection prompt |
agent-shell-session-strategy | 'new | Always start fresh; session management via our picker |
agent-shell-header-style | 'text | Model/mode in mode-line, not graphical header |
agent-shell-show-session-id | t | Show session ID for resume/history |
Welcome Message
Every new session displays a custom welcome with a quick-reference keybinding card:
Welcome to Auggie (opus4.6, agent mode)
────────────────────────────────────────────────────
Quick Reference
C-c e Compose Open multi-line prompt editor
C-c s Sessions Pick / resume / start session
C-c q Quit Save and quit session
C-c h History View conversation history
C-c t t Template Insert a prompt template
C-c c c Command Pick & insert a slash command
C-c T t Tag Tag this session
C-c T l By tag Filter sessions by tag
C-c ? Help Full keybinding reference
────────────────────────────────────────────────────
The welcome is implemented as an :override advice on agent-shell-auggie--welcome-message, preserving the original auggie welcome while appending the reference card.
Nix Options
programs.emacs.decknix.agentShell.enable = true; # Enable the entire ecosystem
Disabling this single option removes all agent-shell packages and configuration.
Multi-Session (Layer 2)
Layer 2 turns agent-shell from a single-buffer chat into a full session management system.
Unified Session Picker (C-c s)
The session picker combines three sources into one completing-read:
Agent session:
[new] Start a new auggie session
[live] *agent-shell*<proptrack-fix>
[live] *agent-shell*<decknix-docs>
[saved] a1b2c3d4 2h ago 12x Investigate proptrack pubsub timeout...
[saved] e5f6g7h8 3d ago 8x Refactor agent-shell keybindings... [decknix, refactor]
- [new] — starts a fresh agent-shell session
- [live] — switches to an existing Emacs buffer
- [saved] — resumes a saved auggie session (boots a new agent-shell with
--resume <id>)
Saved sessions show: truncated ID, relative time, exchange count, first message preview, and tags (if any).
Session Resume
When you select a saved session, the picker:
- Appends
--resume <session-id>to the ACP command - Starts a new agent-shell buffer with the auggie session restored
- Stores the auggie session ID in a buffer-local variable for history/tagging
;; The resume mechanism — auggie CLI handles the actual session restore
(let ((agent-shell-auggie-acp-command
(append agent-shell-auggie-acp-command
(list "--resume" session-id))))
(agent-shell-start :config (agent-shell-auggie-make-agent-config)))
Session History (C-c h / C-c H)
View the full conversation history for any session:
C-c h— DWIM: if in an agent-shell buffer with a known session, shows that session's history. Otherwise, prompts to pick.C-c H— Always pick: shows the session picker regardless of current buffer.
History is rendered by generating a share link via auggie session share <id> and opening it in xwidget-webkit (embedded browser) or eww (text browser) as fallback — all inside Emacs.
Clean Quit (C-c q)
Quitting a session:
- Prompts for confirmation (
y-or-n-p) - Switches to the previous buffer
- Kills the agent-shell buffer (sends SIGHUP to auggie, which auto-saves the session)
The session is immediately available in the picker's saved list for future resume.
Buffer Rename (C-c r)
Rename the agent-shell buffer for clarity:
*agent-shell* → *agent-shell*<proptrack-pubsub-fix>
Uses agent-shell-rename-buffer from the core package.
Extensions
Manager Dashboard (C-c m)
agent-shell-manager provides a tabulated list of all agent-shell buffers at the bottom of the frame. Toggle with C-c m.
Workspace Tab (C-c w)
agent-shell-workspace creates a dedicated tab-bar tab with a sidebar showing all agent sessions. Toggle with C-c w.
Attention Tracker (C-c j)
agent-shell-attention adds a mode-line indicator showing pending/busy session counts:
AS:2/1 ← 2 sessions pending input, 1 busy
C-c j jumps to the next session needing attention.
Nix Options
programs.emacs.decknix.agentShell = {
manager.enable = true; # Tabulated dashboard
workspace.enable = true; # Tab-bar workspace
attention.enable = true; # Mode-line tracker
};
Productivity (Layer 3)
Layer 3 provides the tools for structured, repeatable interactions with the AI agent.
Compose Buffer (C-c e)
A magit-style multi-line editor for writing prompts. Opens at the bottom of the frame:
┌─────────────────────────────────────────────┐
│ *agent-shell*<my-session> │
│ │
│ ... conversation ... │
│ │
├─────────────────────────────────────────────┤
│ Compose prompt → C-c C-c submit, C-c C-k │
│ │
│ Please refactor the UnrecoverableError │
│ handler to support a new category: │
│ │
│ - Add `TransientNetworkError` subclass │
│ - Wire it into the metrics recorder │
│ - Update the Terraform alert definitions │
│ │
└─────────────────────────────────────────────┘
C-c C-c— submit the composed text to the agentC-c C-k— cancel and close the compose bufferC-c C-s— toggle sticky (stays open after submit) vs transient (closes after submit)C-c k k— interrupt the agent,C-c k C-c— interrupt and submit- Full text-mode editing:
RETfor newlines, no accidental submissions
Prompt History (M-p / M-n)
The compose buffer supports prompt history across all sessions — cycle through previously sent prompts:
| Key | Action |
|---|---|
M-p | Previous prompt (older) |
M-n | Next prompt (newer) |
M-r | Search all prompts (consult fuzzy match) |
History is loaded lazily from auggie session files — M-p starts with the current session, then progressively loads older sessions as you keep pressing. M-r provides a full fuzzy search across all sessions using consult.
Your current input is saved when you start navigating and restored when you cycle past the newest entry.
Yasnippet Prompt Templates (C-c t t)
Seven built-in templates with interactive fields:
| Template | Key | Purpose |
|---|---|---|
/review | C-c t t → review | Code review with focus selector (bugs, performance, security, readability) |
/refactor | C-c t t → refactor | Refactoring with pattern selector (extract, rename, DRY up, etc.) |
/test | C-c t t → test | Test generation covering happy path, edge cases, errors |
/explain | C-c t t → explain | Code explanation with aspect focus |
/fix | C-c t t → fix | Bug fix with stack trace placeholder |
/implement | C-c t t → implement | Feature implementation following existing patterns |
/debug | C-c t t → debug | Debugging with logs and steps-taken fields |
Templates auto-populate the current file from the source buffer. TAB advances between fields; yas-choose-value provides dropdown selectors.
Creating Templates
C-c t n— create a new yasnippet templateC-c t e— edit an existing template
Templates are stored in ~/.emacs.d/snippets/agent-shell-mode/.
Custom Slash Commands (C-c c c)
Slash commands are markdown files with YAML frontmatter, deployed to ~/.augment/commands/:
---
description: Create a new session from a Jira ticket key
argument-hint: [session name or JIRA-KEY]
---
Create a new Augment session and rename it in one step.
...
Built-in Commands
| Command | Description |
|---|---|
/start | Create a new session from a Jira ticket key or plain name |
/find-session | Search all saved sessions by keyword (up to 500) |
/pivot-conversation | Hard pivot — discard current plan, re-evaluate |
/step-back | Stop, summarise progress, wait for direction |
Command Discovery
The picker (C-c c c) scans both global (~/.augment/commands/) and project-level (.augment/commands/) directories. Each command shows its scope and description:
Command:
/start (global) Create a new session from a Jira ticket key
/find-session (global) Search all saved sessions by keyword
/pivot-conversation (global) Hard pivot — discard current plan
/deploy-check (project) Verify deployment prerequisites
Nix-Managed vs Runtime Commands
Nix-managed commands are deployed as symlinks. User-created commands are regular files. On decknix switch, Nix refreshes its symlinks; runtime-created commands persist untouched.
Session Tagging (C-c T)
Tag sessions with freeform labels for organisation:
| Key | Action |
|---|---|
C-c T t | Add a tag (with completion from existing tags) |
C-c T r | Remove a tag |
C-c T l | Filter sessions by tag → resume picker |
C-c T e | Rename a tag across all sessions |
C-c T d | Delete a tag globally |
C-c T c | Cleanup orphaned tags (sessions that no longer exist) |
Tags are stored in ~/.config/decknix/agent-sessions.json, keyed by auggie session ID. The session picker shows tags inline:
[saved] a1b2c3d4 2h ago 12x Fix pubsub timeout... [proptrack, bug]
Quick Actions
PR Review (C-c c r / C-c A c r)
Start a code review session for a GitHub PR:
- Prompts for PR URL (auto-detects from clipboard)
- Creates a named session:
Review: owner/repo#123 - Tags the session with
reviewand sends/review-service-pr <url>
Batch Process (C-c c B / C-c A c B)
Launch multiple sessions from a single editor — ideal for batch code reviews or parallel investigations:
┌─────────────────────────────────────────────┐
│ Batch: C-c C-c submit | C-c C-k cancel │
│ │
│ # Batch session launcher — workspace: ~/.. │
│ --- backend : ~/Code/my-project │
│ https://github.com/org/api/pull/42 │
│ https://github.com/org/api/pull/43 │
│ │
│ --- frontend │
│ https://github.com/org/web/pull/17 │
│ │
└─────────────────────────────────────────────┘
Syntax:
--- <name> [: <workspace>]— group header (items below launch as one session)- Ungrouped URLs — each gets its own session
#lines — comments, ignored
Snippets (via yasnippet in batch compose mode):
| Key | Snippet | Description |
|---|---|---|
--- | Group header | --- <name> : <workspace> with tab stops |
pr | PR URL | Generic github.com/<owner>/<repo>/pull/<number> |
Org-specific snippets (e.g., pre-filled workspace paths) can be added via downstream decknix-config.
C-c C-c parses the buffer and launches all sessions. A summary buffer shows success/failure for each.
Nix Options
programs.emacs.decknix.agentShell = {
templates.enable = true; # Yasnippet prompt templates
commands.enable = true; # Nix-managed slash commands
};
Integration (Layer 4)
Layer 4 connects the agent shell to external tools and services via the Model Context Protocol (MCP).
MCP Server Configuration
MCP servers extend the agent's capabilities by providing access to external data sources and APIs. Decknix manages them declaratively:
{ ... }: {
decknix.cli.auggie.mcpServers = {
context7 = {
type = "stdio";
command = "npx";
args = [ "-y" "@upstash/context7-mcp@latest" ];
};
"gcp-monitoring" = {
type = "stdio";
command = "npx";
args = [ "-y" "gcp-monitoring-mcp" ];
env.GOOGLE_APPLICATION_CREDENTIALS = "~/.config/gcloud/credentials.json";
};
"nurturecloud-knowledge-base" = {
type = "stdio";
command = "npx";
args = [ "-y" "nurturecloud-kb-mcp" ];
};
};
}
Viewing Servers (C-c A S)
The MCP server listing shows all configured servers in a formatted buffer:
MCP Server Configuration
════════════════════════════════════════════════════════
Source: ~/.augment/settings.json
context7
type: stdio
command: npx
args: -y @upstash/context7-mcp@latest
gcp-monitoring
type: stdio
command: npx
args: -y gcp-monitoring-mcp
env:
GOOGLE_APPLICATION_CREDENTIALS=~/.config/gcloud/credentials.json
════════════════════════════════════════════════════════
Runtime changes (auggie mcp add) are temporary.
To persist, edit Nix config and run decknix switch.
Press q to close this buffer.
Declarative vs Runtime
The two-tier model:
- Nix-managed (persistent) — defined in your Nix config, deployed on
decknix switch. This is the baseline. - Runtime (temporary) — added via
auggie mcp addduring a session. Lost on nextdecknix switch.
This lets you experiment with new MCP servers without committing to them, while ensuring your team's standard servers are always present.
How MCP Enhances the Agent
With MCP servers configured, the agent can:
| Server | Capability |
|---|---|
context7 | Query up-to-date library documentation |
gcp-monitoring | Search GCP logs, error groups, Datastore entities |
nurturecloud-knowledge-base | Search resolved Jira tickets and internal docs |
jira | Read/create/transition Jira issues |
confluence | Search and create Confluence pages |
github | Full GitHub API access (issues, PRs, code search) |
The agent automatically discovers available MCP servers and uses them when relevant to the conversation.
Organisation-Specific Servers
Org configs can layer additional MCP servers on top of the framework defaults. See Organisation Configs for how this works.
For NurtureCloud-specific MCP server configuration, see the NC Agent Shell Workflows documentation.
Context Awareness (Layer 5)
Layer 5 makes the agent shell work-aware — it passively tracks the issues, PRs, CI status, and review threads relevant to your current conversation.
How It Works
┌─────────────────────────────────────────────────────────────┐
│ Issues: #51 #52 | PR: #50 | CI: ✅ | Reviews: 2 unresolved │
├─────────────────────────────────────────────────────────────┤
│ *agent-shell*<cherries-epic> │
│ │
│ Let's work on the migration wizard (#52). The PR (#50) │
│ is passing CI now. There are 2 unresolved review threads. │
│ │
└─────────────────────────────────────────────────────────────┘
The header-line at the top of every agent-shell buffer shows a live summary of tracked context.
Auto-Detection
The context panel scans buffer text for references:
| Pattern | Detected As | Example |
|---|---|---|
#123 | GitHub issue/PR (current repo) | #51 |
org/repo#123 | GitHub issue/PR (specific repo) | ldeck/decknix#52 |
PROJ-1234 | Jira ticket | ALR-4268, NC-7801 |
False positives are filtered: HTTP-200, SHA-256, UTF-8, ISO-8601 are excluded.
Data Fetching
For each detected GitHub reference, the panel fetches metadata via gh CLI:
- Issues/PRs: number, title, state (open/closed/merged), URL, type (issue vs PR)
- CI: latest run status for the current branch (pass/fail/running)
- Reviews: unresolved review thread count across open PRs in context
CI status auto-polls every 60 seconds.
Header-Line Indicators
| Indicator | Meaning |
|---|---|
Issues: #51 #52 | Tracked issues (green = open, grey = closed) |
PR: #50 | Tracked PRs (green = open, purple = merged, red = closed) |
CI: ✅ | Latest CI run passed |
CI: ❌ | Latest CI run failed |
CI: 🔄 | CI run in progress |
Reviews: 2 unresolved | Unresolved PR review threads (yellow warning) |
Context Panel (C-c I)
The full detail panel shows everything in a formatted buffer:
Agent Context Panel
────────────────────────────────────────────────────
Issues
────────────────────────────────────────
#51 Cherries epic — high-appeal features open
#52 Migration wizard open 📌
Pull Requests
────────────────────────────────────────
🟢 #50 Agent shell context awareness open
Branch & CI
────────────────────────────────────────
Branch: feature/context-panel (ldeck/decknix)
CI: ✅ success CI / Build and Test
Reviews
────────────────────────────────────────
4 threads, 2 unresolved
────────────────────────────────────────────────────
Press q to close. C-c i g to open item in browser.
Pin / Unpin
Manually pin items to keep them in context even if they're not mentioned in the conversation:
| Key | Action |
|---|---|
C-c i a | Pin an issue/PR (e.g., #49, NC-1234, org/repo#12) |
C-c i d | Unpin — remove from tracked context |
Pinned items are marked with 📌 in the detail panel.
Navigation
| Key | Action |
|---|---|
C-c i i | List tracked issues (completing-read → open in browser) |
C-c i p | List tracked PRs |
C-c i c | Refresh and show CI status |
C-c i r | Refresh and show review thread count |
C-c i g | Open any tracked item in external browser |
C-c i f | Visit in magit-forge |
Persistence
Pinned context items are saved per-session in ~/.config/decknix/agent-sessions.json (the same file used for tags). When you resume a session, pinned items are restored and metadata is re-fetched.
Nix Options
programs.emacs.decknix.agentShell.context.enable = true;
Requires gh CLI on $PATH (included in decknix default packages).
Keybindings Reference
All agent-shell keybindings are available in two forms:
- In-buffer:
C-c <key>— short form, only inside agent-shell buffers - Global:
C-c A <key>— works from any buffer
The C-c A prefix is labelled "Agent" in which-key.
Session Management
| In-buffer | Global | Action |
|---|---|---|
C-c s | C-c A s | Session picker (live + saved + new) |
C-c q | C-c A q | Quit session (saves automatically) |
C-c h | C-c A h | View history (current session or pick) |
C-c H | C-c A H | View history (always pick) |
C-c r | C-c A r | Rename buffer |
| — | C-c A a | Start / switch to agent |
| — | C-c A n | Force new session |
| — | C-c A k | Interrupt agent |
Input & Editing
| Key | Action |
|---|---|
C-c e / C-c A e | Compose buffer (multi-line editor) |
RET | Send prompt (at end of input) |
S-RET | Insert newline in prompt |
C-c C-c | Interrupt running agent |
C-c E | Interrupt agent and open compose buffer |
TAB | Expand yasnippet template |
In Compose Buffer
| Key | Action |
|---|---|
C-c C-c | Submit composed prompt |
C-c C-k | Cancel / close compose buffer |
C-c C-s | Toggle sticky (stays open) vs transient |
C-c k k | Interrupt agent |
C-c k C-c | Interrupt agent and submit |
M-p | Previous prompt (history) |
M-n | Next prompt (history) |
M-r | Search prompt history (consult) |
Templates (C-c t / C-c A t)
| Key | Action |
|---|---|
t | Insert a prompt template |
n | Create new template |
e | Edit existing template |
Commands (C-c c / C-c A c)
| Key | Action |
|---|---|
c | Pick & insert a slash command |
n | Create new command |
e | Edit existing command |
r | Review PR (quick action) |
B | Batch process (multi-session launcher) |
Tags (C-c T / C-c A T)
| Key | Action |
|---|---|
t | Tag current session |
r | Remove tag |
l | List / filter by tag |
e | Rename a tag |
d | Delete tag globally |
c | Cleanup orphaned tags |
Model & Mode
| In-buffer | Global | Action |
|---|---|---|
C-c v | C-c A v | Pick model |
C-c M | C-c A M | Pick mode |
Context (C-c i / C-c A i)
| Key | Action |
|---|---|
i | List tracked issues |
p | List tracked PRs |
c | Show CI status |
r | Show review threads |
a | Pin issue/PR to context |
d | Unpin from context |
g | Open in browser |
f | Visit in forge |
| In-buffer | Global | Action |
|---|---|---|
C-c I | C-c A I | Full context panel |
Extensions
| In-buffer | Global | Action |
|---|---|---|
C-c m | C-c A m | Manager dashboard toggle |
C-c w | C-c A w | Workspace tab toggle |
C-c j | C-c A j | Jump to session needing attention |
| — | C-c A S | MCP server list |
Help
| In-buffer | Global | Action |
|---|---|---|
C-c ? | C-c A ? | Full keybinding reference (this page, in Emacs) |
Vision: The Future of AI Tooling in Decknix
The current agent shell is Layer 5 of a broader vision. Here's where it's heading.
Literate Session Export (Next)
Issue: decknix#55
Every AI session becomes publishable knowledge:
C-c E o → Export to Org-mode
C-c E m → Export to Markdown
C-c E h → Export to HTML
C-c E c → Export to Confluence (ADF)
Why this matters: An investigation session becomes a post-mortem. An architecture discussion becomes a design doc. A bug hunt becomes a runbook. The AI conversation is the documentation — export makes it shareable.
Role-Based Workflow Profiles
The agent shell currently serves a single persona: the developer writing code. But AI-assisted workflows extend far beyond coding.
Engineering Workflows
| Workflow | What the Agent Does |
|---|---|
| Investigation | Query logs (GCP MCP), search knowledge base, correlate errors, produce root cause analysis |
| Architecture Review | Analyse codebase structure, identify coupling, suggest decomposition, generate ADRs |
| Incident Response | Real-time log tailing, alert correlation, runbook execution, post-mortem drafting |
| Code Review | Automated review on commit, PR summary generation, review thread resolution |
| Onboarding | Guided codebase exploration, convention explanation, first-task scaffolding |
Transformative Engineering
Beyond individual developer productivity, the tooling enables transformative engineering — systematic, AI-assisted modernisation of large codebases:
| Capability | Description |
|---|---|
| Migration planning | Analyse a legacy codebase, identify migration paths, estimate effort, generate step-by-step plans |
| Pattern extraction | Detect repeated patterns across services, propose shared libraries, generate extraction PRs |
| Observability gap analysis | Compare metric/alert coverage against error hierarchies, identify blind spots |
| Test coverage expansion | Analyse untested paths, generate test scaffolds, prioritise by risk |
| Cross-service coherence | Validate that API contracts, event schemas, and alert definitions stay consistent across services |
Beyond Engineering
The same session-as-first-class-object model applies to non-engineering roles:
| Role | Workflow |
|---|---|
| Product | Spec refinement sessions, user story generation, acceptance criteria drafting |
| QA | Test plan generation, exploratory testing guidance, regression analysis |
| Support | Ticket investigation with knowledge base search, escalation drafting |
| Leadership | Sprint retrospective analysis, technical debt quantification, roadmap impact assessment |
Workflow Templates
Future slash commands and templates will be role-aware:
/investigate <property-id> → Full NC property sync investigation
/incident <alert-name> → Incident response runbook
/review-pr <PR-number> → Structured code review
/onboard <repo-name> → Guided codebase tour
/migrate <from> <to> → Migration planning session
These would combine MCP server access, knowledge base search, and structured output into repeatable workflows.
Multi-Agent Orchestration
The session manager and attention tracker already support multiple concurrent sessions. The next step is coordinated multi-agent workflows:
- Parallel investigation — spawn multiple agents to investigate different aspects of an incident simultaneously
- Review pipeline — one agent reviews code, another checks test coverage, a third validates observability
- Continuous monitoring — background agents that watch CI, alert channels, or deployment status and inject findings into active sessions
Declarative Workflow Definitions
Workflows as Nix configuration:
{ ... }: {
decknix.ai.workflows = {
investigate = {
description = "Full property sync investigation";
mcpServers = [ "gcp-monitoring" "org-knowledge-base" ];
template = "investigate";
context.autoPin = [ "jira" ]; # Auto-pin Jira tickets mentioned
};
incident = {
description = "Incident response runbook";
mcpServers = [ "gcp-monitoring" "pagerduty" ];
template = "incident";
attention.priority = "high"; # Always show in attention tracker
};
};
}
The Endgame
The vision is an environment where:
- Every AI conversation produces artefacts — not just code changes, but documentation, decisions, and knowledge
- Workflows are reproducible — a new team member gets the same investigation tools, templates, and MCP access as a senior engineer
- Context is continuous — switching between sessions preserves the full picture of what you're working on
- The tooling adapts to the role — engineers, product managers, and support staff each get workflows tailored to their needs
- The environment is declarative —
decknix switchreproduces the entire AI-assisted workflow on any machine
decknix CLI
The decknix CLI is a Rust binary that provides the primary interface for managing your configuration.
Usage
decknix [COMMAND]
Commands:
switch Switch system configuration
update Update flake inputs
help Show help (including extensions)
<ext> Run a user-defined extension
The CLI automatically discovers user extensions defined via Nix and displays them alongside built-in commands.
Architecture
┌──────────────┐ ┌──────────────────────────┐
│ Rust Binary │ ──→ │ darwin-rebuild / nix │
│ (decknix) │ │ (actual build commands) │
└──────┬───────┘ └──────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ Extension Discovery │
│ /etc/decknix/extensions.json (system) │
│ ~/.config/decknix/extensions.json (home) │
└──────────────────────────────────────────────┘
The binary always runs from ~/.config/decknix/ so --flake .#default resolves correctly.
Getting Help
# Show all commands (built-in + extensions)
decknix help
# Help for a specific command
decknix switch --help
decknix help board
Next
- Core Commands —
switch,update - Extensions — add custom subcommands
Core Commands
decknix switch
Build and activate your configuration.
Usage: decknix switch [OPTIONS]
Options:
--dry-run Build only — don't activate
--dev Use local framework checkout instead of pinned remote
--dev-path <PATH> Explicit path to local decknix checkout (implies --dev)
Examples
# Normal switch
decknix switch
# Dry run (check for errors without activating)
decknix switch --dry-run
# Test local framework changes
decknix switch --dev
# Specify framework path explicitly
decknix switch --dev-path ~/projects/decknix
How It Works
cd ~/.config/decknix- Runs
sudo darwin-rebuild switch --flake .#default --impure - With
--dev, adds--override-input decknix path:<dev-path> - With
--dry-run, usesbuildinstead ofswitch
Dev Path Resolution
When --dev is used, the framework path is resolved in order:
--dev-pathflag (highest priority)DECKNIX_DEVenvironment variable~/tools/decknix(default fallback)
decknix update
Update flake inputs (dependencies).
Usage: decknix update [INPUT]
Arguments:
[INPUT] Specific input to update (optional)
Examples
# Update all inputs
decknix update
# Update only decknix
decknix update decknix
# Update only nixpkgs
decknix update nixpkgs
Runs nix flake update [input] under the hood. After updating, run decknix switch to apply.
decknix help
Show help for all commands, including dynamically discovered extensions.
# Show all commands
decknix help
# Help for a specific command or extension
decknix help switch
decknix help board
Extensions show their description and underlying command.
Extensions
The decknix CLI supports user-defined subcommands via a Nix-based extension system.
How It Works
Extensions are defined in Nix and compiled into JSON config files that the Rust binary reads at runtime:
/etc/decknix/extensions.json ← system-level (from programs.decknix-cli.subtasks)
~/.config/decknix/extensions.json ← home-level (from decknix.cli.extensions)
Both files are merged. Extensions appear in decknix help and support --help.
Defining Extensions (Home-Manager)
{ ... }: {
decknix.cli.extensions = {
board = {
description = "Issue dashboard across repos";
command = "${boardScript}/bin/decknix-board";
};
cheatsheet = {
description = "Show WM keybinding cheatsheet";
command = "${cheatsheetScript}/bin/decknix-cheatsheet";
};
};
}
Defining Extensions (System-Level)
# system.nix
{ ... }: {
programs.decknix-cli.subtasks = {
cleanup = {
description = "Garbage collect Nix store";
command = "nix-collect-garbage -d";
pinned = true; # Also creates standalone 'cleanup' command
};
};
}
Setting pinned = true creates a standalone wrapper so you can run cleanup directly without the decknix prefix.
Built-in Extensions
Decknix ships with several extensions:
| Command | Description |
|---|---|
decknix board | Issue dashboard across GitHub repos |
decknix cheatsheet | Show window manager keybinding cheatsheet |
decknix space | Space picker (GUI) |
decknix verify | Verify system integration |
Zsh Completion
Extensions automatically get zsh tab-completion. The module generates a completion script that includes all built-in commands plus discovered extensions.
Using Extensions
# Run an extension
decknix board
# Pass arguments
decknix board open --no-color
# Get help
decknix board --help
# Or:
decknix help board
Arguments after the extension name are passed through as $1, $2, etc.
Adding Org Configs for Your Team
This guide walks through creating a shared configuration repo for your organisation.
Step 1: Create the Repo
mkdir my-org-config && cd my-org-config
git init
Step 2: Create the Flake
# flake.nix
{
description = "My Org - Decknix Config";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
outputs = { self, nixpkgs, ... }: {
darwinModules.default = import ./system.nix;
homeModules.default = import ./home.nix;
};
}
Step 3: Define Team Packages
# home.nix
{ pkgs, ... }: {
home.packages = with pkgs; [
awscli2
terraform
jdk17
nodejs
python3
];
}
# system.nix
{ pkgs, ... }: {
homebrew.casks = [
"docker"
"slack"
];
}
Step 4: Push and Reference
git add . && git commit -m "Initial org config"
git remote add origin git@github.com:MyOrg/decknix-config.git
git push -u origin main
Team members add it to their ~/.config/decknix/flake.nix:
inputs.my-org = {
url = "github:MyOrg/decknix-config";
inputs.nixpkgs.follows = "nixpkgs";
};
And wire the modules:
outputs = inputs@{ decknix, ... }:
decknix.lib.mkSystem {
inherit inputs;
settings = import ./settings.nix;
darwinModules = [ inputs.my-org.darwinModules.default ];
homeModules = [ inputs.my-org.homeModules.default ];
};
Step 5: Test Before Merging
decknix switch --dev-path ~/Code/my-org/decknix-config
Or manually:
sudo darwin-rebuild switch --flake .#default --impure \
--override-input my-org path:~/Code/my-org/decknix-config
Step 6: Automate Updates
Add Renovate or Dependabot to auto-PR when the org config updates.
Team members pull updates with:
decknix update my-org
decknix switch
Tips
- Keep the org config minimal — only team-wide requirements
- Let individuals override in
~/.config/decknix/<org-name>/ - Add a
bootstrap.shfor one-command onboarding - Include a
secrets.nix.exampleshowing what credentials team members need
Framework Development
This guide covers how to develop and test changes to the decknix framework itself.
Setup
Clone the framework:
git clone git@github.com:ldeck/decknix.git ~/tools/decknix
Testing Changes
Quick Test with --dev
The fastest way to test framework changes:
# Edit framework code
$EDITOR ~/tools/decknix/modules/home/options/editors/emacs/default.nix
# Build using local checkout
decknix switch --dev
This passes --override-input decknix path:~/tools/decknix to darwin-rebuild.
Manual Override
cd ~/.config/decknix
sudo darwin-rebuild switch --flake .#default --impure \
--override-input decknix path:~/tools/decknix
Using DECKNIX_DEV
Set the environment variable to avoid --dev-path every time:
export DECKNIX_DEV=~/tools/decknix
decknix switch --dev
Project Structure
decknix/
├── cli/src/main.rs # Rust CLI binary
├── lib/default.nix # mkSystem + configLoader
├── modules/
│ ├── cli/ # CLI nix-darwin module
│ ├── darwin/ # System modules
│ └── home/ # Home-manager modules
│ └── options/
│ ├── cli/ # auggie, board, extensions
│ ├── editors/ # emacs/, vim/
│ └── wm/ # aerospace/, hammerspoon/
├── pkgs/ # Custom packages
└── flake.nix # Framework flake
Adding a New Module
- Create the module file in the appropriate directory:
# modules/home/options/my-tool.nix
{ config, lib, pkgs, ... }:
let cfg = config.decknix.myTool;
in {
options.decknix.myTool.enable = lib.mkEnableOption "My Tool";
config = lib.mkIf cfg.enable {
home.packages = [ pkgs.my-tool ];
};
}
-
The module is auto-imported — all
.nixfiles inmodules/home/options/are loaded. -
Test:
decknix switch --dev
Verifying
# Check the generated Emacs config
find /nix/store -name "default.el" -path "*/emacs-packages-deps/*" 2>/dev/null | head -1 | xargs cat
# Check loaded modules
decknix switch --dev 2>&1 | grep "\[Loader\]"
# Evaluate an option
nix repl
:lf .
darwinConfigurations.default.config.programs.emacs.decknix.languages.kotlin.enable
Troubleshooting
Emacs Daemon Issues
# Check if daemon is running
launchctl list | grep emacs
# Restart daemon
launchctl stop org.nix-community.home.emacs
launchctl start org.nix-community.home.emacs
# View logs
log show --predicate 'process == "emacs"' --last 1h
Keybindings Not Working
- Did you run
decknix switch? - Check for conflicting configs:
~/.emacs,~/.emacs.d/init.el - Test in Emacs:
M-x describe-key RET <key>
Troubleshooting
Common Issues
"command not found: decknix"
The CLI hasn't been installed yet. Run the full command manually:
sudo darwin-rebuild switch --flake ~/.config/decknix#default --impure
After the first successful switch, decknix will be in your PATH.
Build Errors
Check the loader trace to see which files are being loaded:
decknix switch 2>&1 | grep "\[Loader\]"
Common causes:
- Syntax error in a
.nixfile — check the error message for the file path - Missing input — run
nix flake updateto fetch all inputs - Stale lock file — delete
flake.lockand rebuild
Config Not Taking Effect
- Did you run
decknix switch? - Check that your file is in the right location (the loader traces what it finds)
- Another module may be setting the value with higher priority — try
lib.mkForce
Emacs Daemon Not Starting
# Check service status
launchctl list | grep emacs
# View logs
log show --predicate 'process == "emacs"' --last 1h
# Manually start
launchctl start org.nix-community.home.emacs
Emacs Keybindings Not Working
- Rebuild:
decknix switch - Restart Emacs:
pkill emacs && launchctl start org.nix-community.home.emacs - Check for conflicting configs:
~/.emacs~/.emacs.d/init.el~/.config/emacs/init.el
- Test in Emacs:
M-x describe-key RETthen press the key
Emacsclient Can't Connect
# Check if daemon is running
ps aux | grep "emacs.*daemon"
# Try starting manually
emacs --daemon
# Then connect
emacsclient -c
Debugging
Evaluate Without Building
nix repl
:lf .
darwinConfigurations.default.config.home-manager.users.YOU.home.packages
Check Option Values
nix repl
:lf .
darwinConfigurations.default.config.programs.emacs.decknix.languages.kotlin.enable
Check Generated Emacs Config
find /nix/store -name "default.el" -path "*/emacs-packages-deps/*" 2>/dev/null | head -1 | xargs cat
Reset to Clean State
rm -rf ~/.config/decknix
# Re-run bootstrap or nix flake init -t github:ldeck/decknix
Configuration Hub
Your single entry point for configuring a decknix-managed system. Browse framework options, or jump to upstream references for home-manager, nix-darwin, packages, and casks.
External Resources
Nix
- Nix Manual — official Nix reference
- nix.dev — community tutorials and guides
- Nix Flakes — flakes overview on NixOS Wiki
- Nixpkgs Search — find packages by name
nix-darwin
- nix-darwin Manual — macOS system options
- nix-darwin GitHub — source and issues
Home Manager
- Home Manager Manual — all home-manager options
- Home Manager Options Search — searchable options index
- Home Manager GitHub — source and issues
Emacs
- GNU Emacs Manual
- Magit Manual — Git interface
- Forge Manual — GitHub/GitLab integration
- Vertico — completion UI
- Consult — enhanced commands
- Eglot Manual — built-in LSP client
Window Management
- AeroSpace — tiling WM for macOS
- Hammerspoon — macOS automation
AI Tooling
- Augment Code — AI coding agent
- Model Context Protocol — MCP specification
Decknix
- Framework Repo — source, issues, releases