A simple macOS specific dotfiles management system for making my developer experience consistent across the many Macs I use. Design tenets (in tension) are:
- Evolving -- breaking changes can happen
- Grokable -- I understand how everything works
- Contained -- changes are local to
$PROJECT_DIR
The system does three things:
- Uses Homebrew for managing system-wide dependencies using Brew Bundles
- Uses mise-en-place for managing development environments and project level dependencies
- Uses GNU Stow for managing dotfiles
xcode-select --install
sudo xcodebuild -license
git clone https://github.com/kulesh/dotfiles.git ~/.dotfiles
cd ~/.dotfiles
/bin/zsh install.shThis installs dependencies from the Brewfile and creates symlinks to dotfiles.
# Clone a GitHub project
cloneproject gh kulesh/example
# Or create a new project from template
mkproject my-api fastapi
# Switch between projects (tab completion works)
workon my-api
# See all your projects
lsprojects
# View current project details
showproject| Command | Description |
|---|---|
mkproject <name> [template] |
Create new project (default: base template) |
cloneproject gh <user>/<repo> |
Clone and setup GitHub project |
workon <project> |
Switch to project (with tab completion) |
workon <project> -s |
Switch to project in sandbox mode |
lsprojects |
List all projects with metadata |
lsprojects --tools |
List projects with mise tools |
showproject |
Show current project details |
cdproject |
Jump to current project root |
updateproject [name] |
Pull latest changes (cloned projects) |
rmproject <name> |
Archive project (safe removal) |
rmproject <name> --delete |
Permanently delete project |
list_project_types |
Show available templates |
Projects can run in a macOS sandbox that restricts filesystem access—useful for running AI coding agents safely.
workon myproject -s # First time enables sandbox for project
workon myproject # Subsequent runs auto-enter sandbox| Access | Allowed | Blocked |
|---|---|---|
| Read | Everything except secrets | ~/.ssh/* (private keys), ~/.aws, keychains |
| Write | Project dir, ~/.cache, ~/.config, ~/.local, /tmp |
System dirs, shell configs, ~/.ssh, ~/.gitconfig |
| Network | All outbound | — |
lsprojects # Shows 🔒 next to sandboxed projects
showproject # Shows "Sandbox: enabled/disabled"Each workon spawns an independent sandboxed shell—run as many as you need for the same project (e.g., one for Claude, one for the backend, one for frontend).
| Template | Description |
|---|---|
| base | Git, README, .gitignore, CLAUDE.md, AGENTS.md |
| python | pyproject.toml structure |
| fastapi | Async API with testing setup |
| ruby | Bundler setup |
| rails | Full MVC structure |
Each template includes a CLAUDE.project.md with framework-specific guidance for AI assistants. A shared header lives in mise/.config/mise/tasks/mkproject/_shared/, and mkproject generates CLAUDE.md and AGENTS.md in the new project.
mise/.config/mise/tasks/mkproject/
├── _shared/
│ ├── CLAUDE.header.md # Shared guidance header
│ └── AGENTS.header.md # Shared guidance header (Codex)
├── base/
│ └── CLAUDE.project.md # Base project guidance
├── fastapi/
│ ├── CLAUDE.project.md # FastAPI-specific guidance
│ └── files/
│ └── .env.example # Configuration template
└── fastapi.sh # Generates project structure
When you run mkproject my-api fastapi:
- Static files are copied from the template
files/directory - The template script generates framework-specific code
CLAUDE.md+AGENTS.mdare generated from shared + template-specific docs- Dependencies are installed via mise and package managers
-
Create the template directory:
mkdir mise/.config/mise/tasks/mkproject/mytemplate
-
Add static files (optional):
mkdir mise/.config/mise/tasks/mkproject/mytemplate/files touch mise/.config/mise/tasks/mkproject/mytemplate/files/.gitignore touch mise/.config/mise/tasks/mkproject/mytemplate/CLAUDE.project.md
If you want Codex-specific guidance, add
AGENTS.project.mdtoo. If it’s missing,mkprojectreusesCLAUDE.project.md. -
Create the template script:
# mise/.config/mise/tasks/mkproject/mytemplate.sh #!/usr/bin/env zsh #MISE description="Setup my custom project" #MISE dir="{{cwd}}" #MISE depends=["mkproject:base"] echo "Setting up custom project..." TEMPLATE_DIR="${0:a:h}/mytemplate/files" if [[ -d "$TEMPLATE_DIR" ]]; then cp -r "$TEMPLATE_DIR"/. "$PWD/" fi
-
Test:
mkproject test-project mytemplate
See Brewfile for the full list. Highlights:
- Neovim with CodeCompanion - editor and AI
- Ghostty - terminal
- mise - version management and task runner
- starship - shell prompt