Skip to content

File Format

Dotkit files come in two formats that produce identical shell output. Use whichever fits your workflow: plain text is terse and script-like, Markdown renders as readable documentation on GitHub.

Plain .sh files in a run folder are sourced directly with no parsing.

Blocks are separated by --- on its own line. The first non-blank, non-comment line of each block sets its type. Lines beginning with # are comments.

# This is a comment
try command -v brew
NONINTERACTIVE=1
dotkit install https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh
---
map brew install {{item}}
gh
ripgrep
neovim
---
ask Restart terminal?
exec zsh -l

## headers open blocks. The heading verb sets the block type. Top-level bullets (- ) are commands or data. Everything else (H1 headers, prose, nested bullets, blockquotes) is documentation and ignored.

# Homebrew & Formulae
This installs Homebrew if missing, then installs formulae.
## try
- command -v brew
- dotkit install [install.sh](https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)
## map
- brew install **item**
- gh
- ripgrep
- neovim
## ask
- Restart terminal?
- exec zsh -l

In the .md format, substitution markers use **word** instead of {{word}} so the file renders as valid Markdown. The parser converts **item**{{item}} before processing.

Markdown links ([label](url)) are converted to bare URLs. This lets you write readable link text while the parser uses the URL.

The body runs only if the check command exits non-zero. Use it to skip installation when a tool is already present.

try command -v brew
NONINTERACTIVE=1
dotkit install https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh

Compiles to:

Terminal window
command -v brew || {
NONINTERACTIVE=1
dotkit install https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh
}

Runs a template command once per data row, substituting placeholders. Empty lines and comment lines in the data are skipped.

map brew install {{item}}
gh
ripgrep
neovim

Data rows split on = (space-padded equals) into positional fields:

PlaceholderAliasValue
{{1}}{{item}}, {{key}}First field
{{2}}{{value}}Second field
{{3}}N/AThird field

Single-field rows (no =) fill {{1}} / {{item}} / {{key}}.

Key-value example:

map git config --global {{key}} {{value}}
user.name = Your Name
user.email = [email protected]
init.defaultBranch = main

Three-field example:

map git clone [email protected]:{{1}}/{{2}}.git ~/dev/{{3}}
yourname = dotkit = dotkit
yourname = dotfiles = dotfiles

Three variants depending on the message and whether a body is present:

Pause - print message and wait for Enter (no body):

ask Restart your terminal to apply PATH changes

Yes/no - run body only if user answers yes:

ask Install Mac App Store apps?
mas install 497799835
mas install 310633997

Input capture - prompt for hidden input, store in a variable:

ask $GITHUB_TOKEN GitHub personal access token
gh auth login --with-token <<< "$GITHUB_TOKEN"

Input is hidden (stty -echo). Suited for tokens and passwords.

Any block whose first content line doesn’t start with try, map, or ask is treated as plain shell and passed through verbatim.

echo "Configuring git..."
git config --global core.excludesfile ~/.gitignore_global
git config --global push.autoSetupRemote true

In .md, use ## run to mark a plain-shell block explicitly. Any unrecognized ## heading also produces a plain-shell block.

A single file can have any number of blocks in any order:

# Install Homebrew if missing
try command -v brew
NONINTERACTIVE=1
dotkit install https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh
for p in /opt/homebrew /usr/local ~/.linuxbrew; do
eval "$($p/bin/brew shellenv 2>/dev/null)" 2>/dev/null || true
done
---
# Taps
map brew tap {{item}}
hashicorp/tap
---
# Formulae
map brew install {{item}}
gh
ripgrep
terraform
---
# Casks
map brew install --cask {{item}}
firefox
visual-studio-code
ghostty