Homedir setup for @lsimons (and @lsimons-bot)
A modular dotfiles configuration for macOS, featuring ZSH and Bash support, XDG Base Directory compliance, and 1Password CLI integration for secure secret management.
- Modular topic-based structure - Inspired by holman/dotfiles
- XDG Base Directory compliant - Follows the freedesktop.org specification
- 1Password integration - Load secrets securely without storing them in git
- ZSH configuration - Clean, modular ZSH setup with Oh My Zsh
- Bash configuration - Modular Bash setup with the same topic-based loading
- Python-based installation - Idempotent installation automation
- Homebrew integration - Automatically installs packages via Homebrew
- Development tools - Includes editors, terminals, CLI tools, and coding agents
For a fresh VM setup (UTM, Little Snitch, accounts), see AGENT_SETUP.md first. For the Windows 11 ARM64 sandbox variant, see AGENT_WINDOWS_SETUP.md and windows/README.md.
On an existing macOS system with Homebrew:
mkdir -p ~/git/lsimons && cd ~/git/lsimons
git clone https://github.com/lsimons/lsimons-dotfiles.git
cd lsimons-dotfiles
./script/install.py # preview first: ./script/install.py --dry-run
source ~/.zshrcOnce mise is installed you can also use mise run install (add
-- --dry-run to preview) and mise run check for subsequent runs.
Run mise run check (or python3 script/check.py) to validate the
repo without touching your system — this is what CI runs on every push.
Prefer mise run check: it bootstraps ruff via .mise.toml's
[tools] section so linting is always covered. The bare check.py
entry point skips ruff with a warning when it's not on PATH.
The installation script (./script/install.py) will:
- Install Homebrew (if not present)
- Install Python via Homebrew (if not present)
- Create
~/.dotfilessymlink pointing to this repository - Set up XDG directories (
~/.config,~/.local/share,~/.cache,~/.local/state) - Symlink dotfiles to appropriate locations
- Run topic installers for development tools:
| Topic | Installs |
|---|---|
1password/ |
1Password app and CLI (op) |
agents/ |
Shared coding-agent instructions, links to the lsimons-skills collection, and repository config sync |
ansible/ |
Ansible and related tools |
aws/ |
AWS CLI (awscli) + default ~/.aws/config; saml2aws configured with the Browser provider for Okta OIE |
azure/ |
Azure CLI (az) |
bash/ |
Bash configuration and directories |
bash-it/ |
Bash-it framework (prompt, plugins) |
claude/ |
Claude Code CLI and configuration |
colors/ |
pastel color CLI + docs for theme/palette files across tools |
codex/ |
OpenAI Codex CLI and configuration |
copilot/ |
GitHub Copilot CLI (git-config-ai routing) |
docker/ |
Docker |
dock/ |
Pins apps to the macOS Dock via dockutil (runs last) |
gemini/ |
Gemini CLI |
fnox/ |
fnox (1Password secret injection, via mise) |
fonts/ |
Fonts (Cascadia Code, Iosevka, JetBrains Mono, Lilex, Lilex Nerd Font) |
gh/ |
GitHub CLI + extensions (gh stack) |
go/ |
Go (via mise) |
ghostty/ |
Ghostty terminal |
git/ |
Git (via Homebrew) + Git Credential Manager, git-filter-repo, Git LFS (installed and initialized) |
herdr/ |
herdr terminal agent multiplexer + LSD Warm Light theme |
jdk/ |
OpenJDK (via mise) |
jq/ |
jq JSON processor (used by the Claude statusline) |
lsimons-agent/ |
LLM agent environment configuration |
memex/ |
memex agent-transcript search + its herdr plugin |
mise/ |
mise (polyglot tool version manager) |
node/ |
Node.js (via mise) + pnpm (via corepack) |
oh-my-zsh/ |
Oh My Zsh |
opencode/ |
OpenCode CLI (permissions, model variants, LSD Warm theme, git-config-ai routing) |
openspec/ |
openspec |
pi-coding-agent/ |
pi-coding-agent (settings, LSD Warm themes, git-config-ai routing) |
python/ |
Python (via mise) + XDG config |
quarto/ |
Quarto (via Homebrew cask) |
ruby/ |
Ruby (via mise) |
rust/ |
Rust (via mise) + CARGO_HOME |
sh/ |
Shared shell configuration (PATH, XDG, settings) |
ssh/ |
SSH configuration (post-quantum warning, 1Password agent) |
terminal/ |
macOS Terminal.app "LSD Warm Light" profile (mirrors Ghostty) |
terraform/ |
tfenv and Terraform |
timeout/ |
timeout command for macOS (via the aisk/tap Homebrew tap) |
tmux/ |
tmux |
topgrade/ |
topgrade (automated updates) |
uv/ |
uv (Python package manager) |
vivaldi/ |
Vivaldi Browser |
wordpress/ |
WordPress shell environment |
zed/ |
Zed editor |
zsh/ |
ZSH directories |
If you're an AI coding agent (GitHub Copilot, Claude Code, etc.) working on this repository, please read AGENTS.md for detailed instructions and guidelines.
.
├── script/ # Installation scripts and helpers
│ ├── install.py # Main installer (supports --dry-run)
│ ├── check.py # Validation checks (py_compile, ruff, JSON, install dry-run)
│ └── helpers.py # Shared functions for topic installers
├── machines/ # Machine-specific configuration
│ ├── default.json # Default config (used when no hostname match)
│ └── <hostname>.json # Per-machine overrides
└── <topic>/ # One directory per topic (see table above)
*.symlink- Files symlinked to home directory or XDG directories*.sh- Shared shell config, sourced by both bash and zsh*.zsh- ZSH-specific config, sourced only by zsh*.bash- Bash-specific config, sourced only by bashpath.sh/path.zsh/path.bash- Loaded first, for PATH configurationcompletion.sh/completion.zsh/completion.bash- Loaded lastinstall.py- Topic-specific installation script
Loading order: shared and shell-specific path.* files first, ordinary shared
and shell-specific files second, then shared and shell-specific completion.*
files.
This setup follows the XDG Base Directory specification:
| Variable | Default | Purpose |
|---|---|---|
| XDG_CONFIG_HOME | ~/.config | Configuration files |
| XDG_DATA_HOME | ~/.local/share | Data files |
| XDG_CACHE_HOME | ~/.cache | Cache files |
| XDG_STATE_HOME | ~/.local/state | State files (logs, history) |
All tools are configured to respect these directories:
- ZSH history:
~/.local/state/zsh/history - Bash history:
~/.local/state/bash/history - Python history:
~/.local/state/python/history - Git config:
~/.config/git/config - mise installs/shims:
~/.local/share/mise/
The machines/ directory contains per-machine configuration in JSON format. During installation, get_machine_config() in helpers.py loads machines/<short-hostname>.json, merging with machines/default.json.
Every machine must be enrolled before installing. machines/default.json intentionally has no SSH keys and no git signing key, so get_machine_config() refuses to fall back to it for a hostname with no dedicated file — the installer exits with an error instead of silently generating a git config with commit signing enabled but no signing key, or a 1Password SSH agent config with no exposed keys. To enroll a new machine, create machines/<hostname>.json (use hostname -s to get the short hostname) before running the installer.
Some tools (currently the Codex and OpenCode shell wrappers, see
codex/codex.sh and opencode/opencode.sh) need an LLM provider API key
that lives in 1Password under a different account on different machines
— e.g. the SBP work account on a work laptop, a personal account
elsewhere. This is configured per machine under providers, mirroring
the shape of the existing ssh.keys[].op_account/op_vault fields:
{
"providers": {
"litellm": {
"op_account": "schubergphilis",
"op_ref": "op://Employee/litellm-pat/token"
}
}
}op_accountis passed toop read --account.op_refis a fullop://vault/item/fieldreference.- Only a reference is stored here, never the secret itself.
Resolution happens at call time: codex()/opencode() invoke
script/provider_credential.py <provider>, which reads the CURRENT
machine's config (via get_machine_config()/get_provider_credential()
in helpers.py) and prints the account/reference for op read to
consume. A machine with no providers.<provider> entry configured fails
closed — the wrapper returns a non-zero exit and an explicit error
instead of falling back to another machine's account or reference.
Secrets are loaded from 1Password, not stored in git. See the 1Password topic.
-
Create a directory:
mkdir ~/.dotfiles/mytopic -
Add files:
mytopic.sh- Shared shell config (auto-loaded in both bash and zsh)mytopic.zsh- ZSH-specific config (optional)mytopic.bash- Bash-specific config (optional)mytopic.symlink- File to symlinkinstall.py- Installation script (optional)
-
Re-run installer:
~/.dotfiles/script/install.py
Re-run installer:
~/.dotfiles/script/install.pySee LICENSE.md file.