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

CategoryHighlights
EditorsEmacs (full IDE with 13+ modules), Vim
ShellZsh with Starship prompt, completions, syntax highlighting
GitDelta diffs, Magit, Forge (GitHub PRs from Emacs)
Dev Toolsripgrep, jq, curl, gh CLI, language servers
Window ManagerAeroSpace tiling WM with fuzzy workspace picker
AI ToolingAugment Code agent with declarative MCP config
CLIdecknix 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

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:

  1. Install Nix with flakes enabled
  2. Install nix-darwin
  3. Create your local config directory at ~/.config/decknix/
  4. Initialize a flake in ~/.config/decknix/
  5. 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

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:

ProfileEmacs IncludesVim Includes
minimalCore, completion, editing, UI, undoBase config
standard+ development, magit, treemacs, languages, welcome+ whitespace, skim
full (default)+ LSP, org-mode, HTTP client, agent-shell
customYour 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

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:

  1. mkSystem reads your settings.nix (username, hostname, system, role)
  2. Constructs a darwinConfigurations.default that merges:
    • Framework modules (Layer 1)
    • Org modules passed via darwinModules / homeModules (Layer 2)
    • configLoader output from ~/.config/decknix/ (Layer 3)
  3. Calls darwin-rebuild switch to atomically activate the new generation

Key Design Decisions

  • lib.mkDefault everywhere — the framework never fights your preferences
  • Filesystem auto-discovery — drop a .nix file in the right place and it's loaded
  • Flake inputs for teams — version-pinned, reproducible, Renovate-watchable
  • Secrets separatedsecrets.nix files are gitignored and loaded alongside home.nix
  • Impure builds--impure is required so the config loader can read ~/.config/decknix/ at build time

Next

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 org
  • secrets.nix files are gitignored and loaded alongside home.nix
  • home/ subdirectories are recursively scanned for additional .nix files
  • 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

  1. Scan ~/.config/decknix/ for all subdirectories
  2. For each directory, look for:
    • home.nix — home-manager module
    • system.nix — nix-darwin module
    • secrets.nix — secrets (merged into home-manager)
    • home/**/*.nix — recursively loaded home modules
  3. Also check for root-level files (~/.config/decknix/home.nix, etc.)
  4. 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:

  1. Framework darwinModules.default / homeModules.default
  2. Your darwinModules / homeModules args (org configs)
  3. configLoader output (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

FieldTypeDefaultDescription
usernamestring"setup-required"Your macOS login username (whoami)
hostnamestring"setup-required"Machine hostname (hostname -s)
systemstring"aarch64-darwin"Nix system identifier
roleenum"developer"Determines which bootstrap template is applied

Roles

The role field selects a starter template for first-time setup:

RoleWhat It Adds
developerGit config template + nodejs
designerInkscape
minimalNothing 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 role to home-manager for template selection
  • Configure configLoader paths

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 pinningflake.lock pins a known-good version
  • Reproducibility — every team member gets the same tools
  • Easy updatesnix flake update my-org-config to 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:

  1. ~/.config/decknix/secrets.nix (root level)
  2. ~/.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

  1. Go to GitHub → Settings → Developer Settings → Personal Access Tokens
  2. Generate a classic token with scopes: repo, read:org, read:user
  3. 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

  1. Never commit secrets — always gitignore secrets.nix
  2. Use GPG encryption — encrypt .authinfo as .authinfo.gpg
  3. Use short-lived tokens — set token expiration when possible
  4. Limit token scopes — only grant necessary permissions
  5. Prefer SSH — use SSH over HTTPS for git operations
  6. 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

ModuleDescriptionPage
EmacsFull IDE with 13+ sub-modules, profiles, daemonEmacs →
VimWhitespace cleanup, skim fuzzy finderVim →
Shell & TerminalZsh, Starship prompt, completionsShell →
GitDelta diffs, global config, LFSGit →
Window ManagementAeroSpace, Hammerspoon, SpacesWM →
AI ToolingAugment Code agent, MCP servers, Agent ShellAI →

Darwin (System) Modules

ModuleDescription
System DefaultsPackages (vim, git, curl, skim), Nerd Fonts, Dock/Finder prefs
AeroSpace SystemDisables Stage Manager, Mission Control shortcuts, separate Spaces
Emacs DaemonBackground Emacs service via launchd, ec wrapper command
CLI ModuleInstalls decknix binary, generates extensions config

Core Options

OptionDescriptionDefault
decknix.roleBootstrap template: "developer", "designer", "minimal""developer"
decknix.usernameYour macOS username (set automatically by mkSystem)
decknix.hostnameMachine hostname

Editor Profiles

Instead of toggling individual modules, choose a profile tier:

Emacs Profiles

ProfileModules Included
minimalcore, completion, editing, UI, undo, project
standardminimal + development, magit, treemacs, languages, welcome
full (default)standard + LSP, org-mode, HTTP client, agent-shell
customDisables framework Emacs — bring your own config

Vim Profiles

ProfileModules Included
minimalBase config (exrc, line numbers, secure)
standard (default)minimal + whitespace + skim
customDisables 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

Full Emacs documentation

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

Full Vim documentation

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

ModuleDescriptionProfile
CoreModus theme, line numbers, better defaultsminimal+
CompletionVertico, Consult, Corfu, Embarkminimal+
EditingSmartparens, Crux, Move-text, EditorConfigminimal+
UIWhich-key, Helpful, Nerd-iconsminimal+
Undoundo-fu, vundo (visual undo tree)minimal+
ProjectProject management and navigationminimal+
WelcomeStartup screen with keybinding cheat sheetstandard+
DevelopmentFlycheck, Yasnippetstandard+
MagitGit interface, Forge (GitHub PRs), code-reviewstandard+
TreemacsProject file tree with git integrationstandard+
Languages30+ language modes with syntax highlightingstandard+
LSPEglot, kotlin-ls, jdt-ls, dape (debugging)full
Org-modeModern styling, presentations (Olivetti)full
HTTPREST client, jq integration, org-babelfull
Agent ShellAI agent interface (Augment Code)full

Key Bindings — Quick Reference

KeyAction
C-sSearch in buffer (consult-line)
C-x bSwitch buffer with preview
M-s rProject-wide ripgrep search
M-yBrowse kill ring
C-.Context actions (Embark)

Git (Magit)

KeyAction
C-x gMagit status
@ f fFetch forge topics (PRs/issues)
@ c pCreate pull request
@ l pList pull requests

File Tree (Treemacs)

KeyAction
C-x t tToggle treemacs
C-x t fFind current file in tree

LSP / Code

KeyAction
C-c l rRename symbol
C-c l aCode actions
C-c l fFormat region
C-c l FFormat buffer
C-c l dShow documentation

Debugging (dape)

KeyAction
C-c d dStart debugger
C-c d bToggle breakpoint
C-c d nStep over
C-c d sStep in
C-c d cContinue

Editing

KeyAction
C-aSmart home (Crux)
C-c dDuplicate line
M-up/downMove line/region
C-/Undo
C-?Redo
C-x uVisual undo tree (vundo)

Org-mode

KeyAction
F5 or C-c pStart/stop presentation
n / pNext/previous slide

Languages

30+ languages with syntax highlighting:

CategoryLanguages
PrimaryKotlin, Java, Scala, SQL, Terraform/HCL, Shell, Nix, Python
DataJSON, YAML, TOML, XML, Markdown
WebHTML, 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.

OptionDefaultDescription
services.emacs.decknix.enabletrueEnable Emacs daemon
services.emacs.decknix.packagepkgs.emacsEmacs 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 .vimrc
  • set secure — restrict commands in project .vimrc
  • Line numbers enabled

Whitespace Module

Plugin: vim-better-whitespace

Automatically strips trailing whitespace on save.

OptionDefaultDescription
programs.vim.decknix.whitespace.enabletrue (standard profile)Enable whitespace cleanup
programs.vim.decknix.whitespace.stripModifiedOnlytrueOnly strip modified lines
programs.vim.decknix.whitespace.confirmfalsePrompt before stripping

Skim Module

Plugin: skim (fuzzy finder)

Integrates skim into Vim for fast file and buffer searching.

OptionDefaultDescription
programs.vim.decknix.skim.enabletrue (standard profile)Enable skim integration

Profiles

ProfileIncludes
minimalBase config only
standard (default)Base + whitespace + skim
customDisables 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

SettingValueDescription
init.defaultBranchmainDefault branch name
pull.rebasetrueRebase on pull instead of merge
push.autoSetupRemotetrueAuto-create remote tracking branch
core.pagerdeltaSyntax-highlighted diffs
lfs.enabletrueGit 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

OptionDefaultDescription
decknix.wm.aerospace.enablefalseEnable AeroSpace
decknix.wm.aerospace.prefixKey"cmd+alt"Prefix key for commands
decknix.wm.aerospace.keyStyle"emacs""emacs" (arrows) or "vim" (hjkl)
decknix.wm.aerospace.enableModeIndicatorShow 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:

OptionDefaultEffect
decknix.services.aerospace.disableStageManagertruePrevents conflicts
decknix.services.aerospace.disableSeparateSpacestrueMulti-monitor support
decknix.services.aerospace.disableMissionControlShortcutstrueFrees Ctrl+arrow keys
decknix.services.aerospace.autohideDocktrueMaximises 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

ComponentDescriptionPage
Auggie CLIAugment Code agent with Nix-managed settings and MCP serversConfiguration →
Agent ShellEmacs-native multi-session AI interface (7 sub-modules)Agent Shell →

Design Principles

  1. Declarative first — all configuration lives in Nix. decknix switch reproduces your entire AI setup on any machine.
  2. Runtime-mutable — settings are copied (not symlinked) so tools can modify them at runtime. The next decknix switch resets to the Nix-managed baseline.
  3. Composable — each component is independently toggleable. Use the CLI without Emacs, or Emacs without MCP servers.
  4. 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

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:

  1. Create or reuse a Slack app at api.slack.com/apps
  2. Enable OAuth with appropriate scopes (e.g., search:read.public, chat:write, channels:history)
  3. Publish as an internal app or to the Slack Marketplace
  4. 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

SourcePersists across decknix switch?How to add
Nix config✅ Yesdecknix.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:

PackageSourcePurpose
shell-makernixpkgs unstableComint-like shell buffer management
acpnixpkgs unstableAugment Code Protocol client
agent-shellnixpkgs unstableCore agent interface
agent-shell-managerCustom derivationTabulated session dashboard
agent-shell-workspaceCustom derivationDedicated tab-bar workspace
agent-shell-attentionCustom derivationMode-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:

LayerNameWhat It ProvidesPage
1FoundationCore shell, ACP protocol, package sourcingFoundation →
2Multi-SessionSession picker, resume, history, quitMulti-Session →
3ProductivityCompose buffer, templates, commands, tagsProductivity →
4IntegrationMCP servers, declarative tool configIntegration →
5ContextIssues, PRs, CI status, review threadsContext →

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.

Full keybinding reference

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

SettingValueWhy
agent-shell-preferred-agent-config'auggieSkip agent selection prompt
agent-shell-session-strategy'newAlways start fresh; session management via our picker
agent-shell-header-style'textModel/mode in mode-line, not graphical header
agent-shell-show-session-idtShow 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:

  1. Appends --resume <session-id> to the ACP command
  2. Starts a new agent-shell buffer with the auggie session restored
  3. 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 hDWIM: if in an agent-shell buffer with a known session, shows that session's history. Otherwise, prompts to pick.
  • C-c HAlways 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:

  1. Prompts for confirmation (y-or-n-p)
  2. Switches to the previous buffer
  3. 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 agent
  • C-c C-k — cancel and close the compose buffer
  • C-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: RET for newlines, no accidental submissions

Prompt History (M-p / M-n)

The compose buffer supports prompt history across all sessions — cycle through previously sent prompts:

KeyAction
M-pPrevious prompt (older)
M-nNext prompt (newer)
M-rSearch 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:

TemplateKeyPurpose
/reviewC-c t t → reviewCode review with focus selector (bugs, performance, security, readability)
/refactorC-c t t → refactorRefactoring with pattern selector (extract, rename, DRY up, etc.)
/testC-c t t → testTest generation covering happy path, edge cases, errors
/explainC-c t t → explainCode explanation with aspect focus
/fixC-c t t → fixBug fix with stack trace placeholder
/implementC-c t t → implementFeature implementation following existing patterns
/debugC-c t t → debugDebugging 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 template
  • C-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

CommandDescription
/startCreate a new session from a Jira ticket key or plain name
/find-sessionSearch all saved sessions by keyword (up to 500)
/pivot-conversationHard pivot — discard current plan, re-evaluate
/step-backStop, 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:

KeyAction
C-c T tAdd a tag (with completion from existing tags)
C-c T rRemove a tag
C-c T lFilter sessions by tag → resume picker
C-c T eRename a tag across all sessions
C-c T dDelete a tag globally
C-c T cCleanup 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:

  1. Prompts for PR URL (auto-detects from clipboard)
  2. Creates a named session: Review: owner/repo#123
  3. Tags the session with review and 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):

KeySnippetDescription
---Group header--- <name> : <workspace> with tab stops
prPR URLGeneric 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:

  1. Nix-managed (persistent) — defined in your Nix config, deployed on decknix switch. This is the baseline.
  2. Runtime (temporary) — added via auggie mcp add during a session. Lost on next decknix 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:

ServerCapability
context7Query up-to-date library documentation
gcp-monitoringSearch GCP logs, error groups, Datastore entities
nurturecloud-knowledge-baseSearch resolved Jira tickets and internal docs
jiraRead/create/transition Jira issues
confluenceSearch and create Confluence pages
githubFull 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:

PatternDetected AsExample
#123GitHub issue/PR (current repo)#51
org/repo#123GitHub issue/PR (specific repo)ldeck/decknix#52
PROJ-1234Jira ticketALR-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

IndicatorMeaning
Issues: #51 #52Tracked issues (green = open, grey = closed)
PR: #50Tracked PRs (green = open, purple = merged, red = closed)
CI: ✅Latest CI run passed
CI: ❌Latest CI run failed
CI: 🔄CI run in progress
Reviews: 2 unresolvedUnresolved 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:

KeyAction
C-c i aPin an issue/PR (e.g., #49, NC-1234, org/repo#12)
C-c i dUnpin — remove from tracked context

Pinned items are marked with 📌 in the detail panel.

KeyAction
C-c i iList tracked issues (completing-read → open in browser)
C-c i pList tracked PRs
C-c i cRefresh and show CI status
C-c i rRefresh and show review thread count
C-c i gOpen any tracked item in external browser
C-c i fVisit 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-bufferGlobalAction
C-c sC-c A sSession picker (live + saved + new)
C-c qC-c A qQuit session (saves automatically)
C-c hC-c A hView history (current session or pick)
C-c HC-c A HView history (always pick)
C-c rC-c A rRename buffer
C-c A aStart / switch to agent
C-c A nForce new session
C-c A kInterrupt agent

Input & Editing

KeyAction
C-c e / C-c A eCompose buffer (multi-line editor)
RETSend prompt (at end of input)
S-RETInsert newline in prompt
C-c C-cInterrupt running agent
C-c EInterrupt agent and open compose buffer
TABExpand yasnippet template

In Compose Buffer

KeyAction
C-c C-cSubmit composed prompt
C-c C-kCancel / close compose buffer
C-c C-sToggle sticky (stays open) vs transient
C-c k kInterrupt agent
C-c k C-cInterrupt agent and submit
M-pPrevious prompt (history)
M-nNext prompt (history)
M-rSearch prompt history (consult)

Templates (C-c t / C-c A t)

KeyAction
tInsert a prompt template
nCreate new template
eEdit existing template

Commands (C-c c / C-c A c)

KeyAction
cPick & insert a slash command
nCreate new command
eEdit existing command
rReview PR (quick action)
BBatch process (multi-session launcher)

Tags (C-c T / C-c A T)

KeyAction
tTag current session
rRemove tag
lList / filter by tag
eRename a tag
dDelete tag globally
cCleanup orphaned tags

Model & Mode

In-bufferGlobalAction
C-c vC-c A vPick model
C-c MC-c A MPick mode

Context (C-c i / C-c A i)

KeyAction
iList tracked issues
pList tracked PRs
cShow CI status
rShow review threads
aPin issue/PR to context
dUnpin from context
gOpen in browser
fVisit in forge
In-bufferGlobalAction
C-c IC-c A IFull context panel

Extensions

In-bufferGlobalAction
C-c mC-c A mManager dashboard toggle
C-c wC-c A wWorkspace tab toggle
C-c jC-c A jJump to session needing attention
C-c A SMCP server list

Help

In-bufferGlobalAction
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

WorkflowWhat the Agent Does
InvestigationQuery logs (GCP MCP), search knowledge base, correlate errors, produce root cause analysis
Architecture ReviewAnalyse codebase structure, identify coupling, suggest decomposition, generate ADRs
Incident ResponseReal-time log tailing, alert correlation, runbook execution, post-mortem drafting
Code ReviewAutomated review on commit, PR summary generation, review thread resolution
OnboardingGuided 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:

CapabilityDescription
Migration planningAnalyse a legacy codebase, identify migration paths, estimate effort, generate step-by-step plans
Pattern extractionDetect repeated patterns across services, propose shared libraries, generate extraction PRs
Observability gap analysisCompare metric/alert coverage against error hierarchies, identify blind spots
Test coverage expansionAnalyse untested paths, generate test scaffolds, prioritise by risk
Cross-service coherenceValidate 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:

RoleWorkflow
ProductSpec refinement sessions, user story generation, acceptance criteria drafting
QATest plan generation, exploratory testing guidance, regression analysis
SupportTicket investigation with knowledge base search, escalation drafting
LeadershipSprint 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:

  1. Every AI conversation produces artefacts — not just code changes, but documentation, decisions, and knowledge
  2. Workflows are reproducible — a new team member gets the same investigation tools, templates, and MCP access as a senior engineer
  3. Context is continuous — switching between sessions preserves the full picture of what you're working on
  4. The tooling adapts to the role — engineers, product managers, and support staff each get workflows tailored to their needs
  5. The environment is declarativedecknix switch reproduces 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

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

  1. cd ~/.config/decknix
  2. Runs sudo darwin-rebuild switch --flake .#default --impure
  3. With --dev, adds --override-input decknix path:<dev-path>
  4. With --dry-run, uses build instead of switch

Dev Path Resolution

When --dev is used, the framework path is resolved in order:

  1. --dev-path flag (highest priority)
  2. DECKNIX_DEV environment variable
  3. ~/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:

CommandDescription
decknix boardIssue dashboard across GitHub repos
decknix cheatsheetShow window manager keybinding cheatsheet
decknix spaceSpace picker (GUI)
decknix verifyVerify 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.sh for one-command onboarding
  • Include a secrets.nix.example showing 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

  1. 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 ];
  };
}
  1. The module is auto-imported — all .nix files in modules/home/options/ are loaded.

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

  1. Did you run decknix switch?
  2. Check for conflicting configs: ~/.emacs, ~/.emacs.d/init.el
  3. 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 .nix file — check the error message for the file path
  • Missing input — run nix flake update to fetch all inputs
  • Stale lock file — delete flake.lock and rebuild

Config Not Taking Effect

  1. Did you run decknix switch?
  2. Check that your file is in the right location (the loader traces what it finds)
  3. 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

  1. Rebuild: decknix switch
  2. Restart Emacs: pkill emacs && launchctl start org.nix-community.home.emacs
  3. Check for conflicting configs:
    • ~/.emacs
    • ~/.emacs.d/init.el
    • ~/.config/emacs/init.el
  4. Test in Emacs: M-x describe-key RET then 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.

JavaScript is required for the interactive configuration hub.

External Resources

Nix

nix-darwin

Home Manager

Emacs

Window Management

AI Tooling

Decknix