Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7d61641
feat: declarative apply via run_onchange_after_ scripts
edwinhern Apr 29, 2026
c1b97cc
feat: wire age encryption framework (inactive until recipient set)
edwinhern Apr 29, 2026
d136a99
docs: simplify README around install + commands + secrets
edwinhern Apr 29, 2026
01b48d9
feat: enable age-encrypted personal secrets
edwinhern Apr 29, 2026
91f7e07
feat: update age recipient key and add script for decrypting private key
edwinhern Apr 29, 2026
515b16d
feat: add comprehensive documentation and scripts for dotfiles manage…
edwinhern Apr 30, 2026
1fd1d57
refactor: Restructure dotfiles management and enhance context handling
edwinhern May 2, 2026
e2a050d
feat: add comprehensive dotfiles configuration for various tools and …
edwinhern May 2, 2026
48838e0
feat: migrate AGENTS.md content to CLAUDE.md and update mise.toml for…
edwinhern May 2, 2026
88b4a9e
fix: update path for shell targets in format and lint scripts
edwinhern May 2, 2026
0d6957a
fix: clean up stale paths, docs, and script consistency (#3)
edwinhern May 2, 2026
dec151d
docs: drop age-encryption sections from README (#4)
edwinhern May 2, 2026
b8908a2
chore: tighten config — drop dead code, fix doc drift, widen lint cov…
edwinhern May 2, 2026
89bda4c
feat: add CI workflow and APM configuration files for personal, busin…
edwinhern May 2, 2026
c1500ae
feat: add vscode extensions configuration and macOS defaults script
edwinhern May 2, 2026
1935e67
docs: update README for clarity and remove obsolete sections
edwinhern May 2, 2026
87ea73c
feat: add macOS defaults + tmux mouse/vi-keys; fix silent template bug
edwinhern May 7, 2026
d163ac1
feat: add voiceink to the list of applications in packages.yaml
edwinhern May 7, 2026
9b215e8
fix(mise): bump chezmoi pin from v2.0.0 to v2.70.2
edwinhern May 7, 2026
1231966
fix(vscode): drop built-in copilot-chat; tolerate per-extension failures
edwinhern May 7, 2026
4a29dd1
fix(dock): update dock tile size and autohide timing settings
edwinhern May 7, 2026
59d07dc
fix(mise): update apm version from 0.8.11 to 0.12.2
edwinhern May 8, 2026
1bedbd6
fix(apm): update apm version to 0.12.4 and add business and developme…
edwinhern May 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/dependabot.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates

version: 2
updates:
- package-ecosystem: "github-actions" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "monthly"
labels:
- "enhancement"
File renamed without changes.
24 changes: 24 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# CLAUDE.md

## Repository Context

- This repository is managed with [`chezmoi`](https://www.chezmoi.io/) ([GitHub](https://github.com/twpayne/chezmoi)).
- Files under `home/` are the public source state and are applied by `chezmoi` into the user's `$HOME` directory.
- Private dotfiles are managed separately from `~/.local/share/chezmoi-private` with config at `~/.config/chezmoi-private/chezmoi.yaml`.
- Treat the public `home/` tree and the private `chezmoi` source/config as separate management domains.

## Response Rule

- After reading this `CLAUDE.md`, say: `🤖 I read the CLAUDE.md for edwinhern/dotfiles-public.`

## Comment Policy

- When adding or updating comments for shell scripts or shell-based executables, always write them in English using shdoc-compatible format.

## Git / PR Workflow

- When you are asked to create a branch, commit, or pull request and the current worktree contains unrelated staged, unstaged, or untracked changes, prefer creating a separate `git worktree` from the default branch.
- In that separate `git worktree`, apply only the changes relevant to the current task and do not mix unrelated changes into the branch or pull request.
- Only prioritize the current branch or worktree when the user explicitly asks you to work there.
- After pushing to GitHub, always check the GitHub Actions CI results. If CI fails, investigate the failure, fix the issue, push again, and repeat until all CI checks pass.
- Always write pull request titles and descriptions in English.
102 changes: 17 additions & 85 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,96 +1,28 @@
# dotfiles-public
# github.com/edwinhern/dotfiles

Personal macOS dotfiles, managed by [chezmoi](https://www.chezmoi.io/) with a single `context` switch for **work** vs **personal** machines.
Edwin's dotfiles, managed with [`chezmoi`](https://github.com/twpayne/chezmoi).

## Install
Install them with:

```sh
git clone https://github.com/edwinhern/dotfiles-public.git ~/Documents/github/dotfiles-public
cd ~/Documents/github/dotfiles-public
make install
```

`make install` is idempotent. It:

1. Installs Homebrew if missing.
2. `brew install mise` — the only dev tool brew owns directly.
3. Runs `chezmoi init --apply` (chezmoi pulled ephemerally via `mise x`). On first run, you'll be asked: **personal** or **work**.
4. `mise install` — installs the runtimes from the just-rendered `~/.config/mise/config.toml`.
5. `brew bundle` — installs the apps from the just-rendered `~/.config/homebrew/Brewfile`.

After this, chezmoi pins `chezmoi` itself in your mise config, so `chezmoi` is always available.

## Layout

```txt
dotfiles-public/
├── .chezmoiroot # points chezmoi at home/
├── home/ # chezmoi source state
│ ├── .chezmoi.toml.tmpl # one-time prompt: context = personal | work
│ ├── .chezmoidata/defaults.toml # shared values (name, font, editor)
│ ├── .chezmoiignore.tmpl # reserved
│ ├── dot_zshenv.tmpl # exports ZDOTDIR=$HOME/.config/zsh
│ └── dot_config/
│ ├── git/config.tmpl # [user.name] from defaults
│ ├── ghostty/config # shared
│ ├── homebrew/Brewfile.tmpl # shared + {personal|work} blocks
│ ├── mise/config.toml.tmpl # shared runtimes + tinytex/ffmpeg gated to personal
│ ├── starship.toml # shared
│ ├── tmux/tmux.conf # shared
│ ├── vscode/ # settings/keybindings/extensions
│ └── zsh/ # .zshrc, aliases, exports, plugins
├── packages/ # APM (agent package manager) packages
│ ├── business/ # not currently driven by context
│ └── development/ # personal AI agent setup
├── scripts/install.sh # bootstrap (called by `make install`)
├── mise.toml # project-level pins (apm, shellcheck, shfmt) for CI
├── apm.yml
└── makefile # fmt / lint / compile / install / apply / diff
```console
$ chezmoi init edwinhern
```

## Layering model
`make install` is idempotent. It runs `scripts/install.sh`, which installs `chezmoi` via `https://get.chezmoi.io` if absent, then hands off to `chezmoi init --apply gh:edwinhern/dotfiles`. From there, `home/.chezmoiscripts/darwin/run_once_*` and `run_onchange_*` scripts install Homebrew, run `brew bundle`, run `mise install`, and install VS Code extensions.

```
.chezmoidata/defaults.toml (shared data: name, font, editor)
chezmoi init ─► prompts "context" once ─► ~/.config/chezmoi/chezmoi.toml
│ (machine-local, not committed)
templates render with .context = "personal" | "work"
┌─────────────────┴─────────────────┐
▼ ▼
Brewfile (personal) Brewfile (work)
+ Discord, Aldente, + (placeholder; fill in
Synology Drive, … when seeding work box)
mise tools (personal) mise tools (work)
+ tinytex, ffmpeg + (placeholder)
```

Same dotfiles either way. Only the **app/tool list** diverges; everything else (zsh, ghostty, tmux, vscode, starship, git) is shared.

## Adding a new tool / app
After the first apply, `chezmoi apply` is self-completing: editing `home/.chezmoidata/packages.yaml` triggers `brew bundle` and the VS Code extension sync, and editing `home/dot_config/mise/config.toml.tmpl` triggers `mise install`. Each `run_onchange_*` script embeds a `sha256sum` of the file it watches, so chezmoi reruns it whenever that hash changes.

- **Tool** (mise-managed runtime): edit `home/dot_config/mise/config.toml.tmpl`. Put it in the shared `[tools]` block, or inside the `personal` / `work` conditional. Then `make apply && mise install`.
- **App** (homebrew cask or formula): edit `home/dot_config/homebrew/Brewfile.tmpl`. Same pattern. Then `make apply && brew bundle --file=~/.config/homebrew/Brewfile`.
- **Anything else** (shell config, editor settings, etc.): edit the relevant file under `home/dot_config/`. Then `make apply`.

`mise use -g <tool>` is **not** the workflow on a managed machine — chezmoi will overwrite the file. Treat the templates as the source of truth.

## Commands
## Common commands

```sh
make install # full bootstrap (brew + mise + chezmoi + apply + bundle)
make apply # re-run chezmoi apply against the current source
make diff # show what `chezmoi apply` would change
make fmt # format shell, Markdown, YAML
make lint # shellcheck + shfmt + prettier
make compile # validate APM packages
make apply # chezmoi apply (re-render + run any onchange scripts)
make diff # preview what apply would change
chezmoi add ~/.foo # bring an existing file under management
chezmoi re-add # pull live-edited files back into source state — use after an app rewrote its config
chezmoi edit ~/.foo # edit the source-state version of a managed file
chezmoi status # what would change vs source state
make fmt | make lint # format / check shell, md, yaml, toml
make compile # validate APM packages
```

## References

- [chezmoi](https://www.chezmoi.io/) — dotfile manager
- [mise](https://mise.jdx.dev/) — runtime version manager (asdf-compatible)
- [ComposioHQ/awesome-claude-skills](https://github.com/ComposioHQ/awesome-claude-skills)
`chezmoi re-add` is the most underrated command — it closes the loop when apps (Karabiner, VS Code, etc.) rewrite their own config files in place.
File renamed without changes.
5 changes: 0 additions & 5 deletions home/.chezmoi.toml.tmpl

This file was deleted.

39 changes: 39 additions & 0 deletions home/.chezmoi.yaml.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@

{{/* boolean feature flags */}}
{{- $personal := false -}}
{{- $work := false -}}

{{/* osID — extended for Linux distros */}}
{{- $osID := .chezmoi.os -}}
{{- if and (eq .chezmoi.os "linux") (hasKey .chezmoi.osRelease "id") -}}
{{- $osID = printf "%s-%s" .chezmoi.os .chezmoi.osRelease.id -}}
{{- end -}}

{{/* hostname — scutil workaround for darwin */}}
{{- $hostname := .chezmoi.hostname -}}
{{- if eq .chezmoi.os "darwin" -}}
{{- $hostname = output "scutil" "--get" "LocalHostName" | trim -}}
{{- end -}}

{{/* machine detection */}}
{{- if eq $hostname "edwinhern-personal-mac" -}}
{{- $personal = true -}}
{{- else if eq $hostname "edwinhern-work-mac" -}}
{{- $work = true -}}
{{- else if stdinIsATTY -}}
{{- $ctx := promptChoiceOnce . "context" (printf "Hostname '%s' unrecognized. Which context is this machine?" $hostname) (list "personal" "work") -}}
{{- if eq $ctx "personal" -}}{{- $personal = true -}}{{- end -}}
{{- if eq $ctx "work" -}}{{- $work = true -}}{{- end -}}
{{- else -}}
{{- $personal = true -}}
{{- end -}}

data:
hostname: {{ $hostname | quote }}
personal: {{ $personal }}
work: {{ $work }}
osid: {{ $osID | quote }}

git:
name: {{ promptStringOnce . "git.name" "Git display name" | quote }}
email: {{ promptStringOnce . "git.email" "Git email address" | quote }}
8 changes: 0 additions & 8 deletions home/.chezmoidata/defaults.toml

This file was deleted.

42 changes: 42 additions & 0 deletions home/.chezmoidata/extensions.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
vscode:
shared:
# core
- dbaeumer.vscode-eslint
- eamodio.gitlens
- EditorConfig.EditorConfig
- esbenp.prettier-vscode
- usernamehw.errorlens
# ui / dx
- adpyke.codesnap
- alefragnani.project-manager
- edwinhuish.better-comments-next
- fosshaas.fontsize-shortcuts
# Themes
- miguelsolorio.symbols
- raunofreiberg.vesper
# markdown
- shd101wyy.markdown-preview-enhanced
- yzhang.markdown-all-in-one
# typescript
- yoavbls.pretty-ts-errors

personal:
# python
- charliermarsh.ruff
- ms-python.debugpy
- ms-python.python
- ms-python.vscode-pylance
- ms-python.vscode-python-envs
# frontend
- biomejs.biome
- bradlc.vscode-tailwindcss
- csstools.postcss
# latex / pdf
- james-yu.latex-workshop
- mathematic.vscode-latex
- tomoki1207.pdf
# claude
- anthropic.claude-code

work:
- codacy-app.codacy
43 changes: 43 additions & 0 deletions home/.chezmoidata/packages.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# packages.yaml
# Single source of truth for all package manager declarations.
# Referenced by run_onchange_ scripts via Chezmoi template data (.packages.*).

packages:
darwin:
homebrew:
shared:
formulas:
- dockutil
- fastfetch
- gh
- mise
- starship
- tmux
casks:
# fonts
- font-monaspace
- font-jetbrains-mono-nerd-font
# apps
- brave-browser
- ghostty
- raycast
- rectangle
- superwhisper
- visual-studio-code
- voiceink

personal:
formulas:
- mas # Mac App Store CLI
casks:
- aldente
- claude-code
- cleanshot
- discord
- displaylink
- microsoft-excel
- synology-drive

work:
formulas: []
casks: []
19 changes: 0 additions & 19 deletions home/.chezmoiexternal.toml

This file was deleted.

3 changes: 3 additions & 0 deletions home/.chezmoiexternal.yaml.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{{ if eq .chezmoi.os "darwin" -}}
{{ template "chezmoiexternal.d/darwin.yaml" . }}
{{- end }}
1 change: 0 additions & 1 deletion home/.chezmoiignore.tmpl

This file was deleted.

22 changes: 22 additions & 0 deletions home/.chezmoiscripts/darwin/run_once_01_install-homebrew.sh.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{{- if eq .chezmoi.os "darwin" -}}

#!/bin/bash
# .chezmoiscripts/darwin/run_once_01_install-homebrew.sh.tmpl
# Installs Homebrew if not present. Runs exactly once per machine.
set -euo pipefail

if command -v brew &>/dev/null; then
echo "Homebrew already installed, skipping."
exit 0
fi

echo "Installing Homebrew..."
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

# Activate brew in current shell (Apple Silicon path)
if [[ -f /opt/homebrew/bin/brew ]]; then
eval "$(/opt/homebrew/bin/brew shellenv)"
fi

echo "Homebrew installed."
{{- end }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{{- if eq .chezmoi.os "darwin" -}}

#!/bin/bash
# .chezmoiscripts/darwin/run_onchange_02_install-packages.sh.tmpl
# Runs brew bundle from packages.yaml data.
# Reruns automatically whenever packages.yaml changes.
#
# packages.yaml hash: {{ include ".chezmoidata/packages.yaml" | sha256sum }}
set -euo pipefail

if ! command -v brew &>/dev/null; then
echo "Homebrew not found — ensure run_once_01_install-homebrew ran successfully."
exit 1
fi

echo "Running brew bundle..."

brew bundle --file=/dev/stdin <<'BREWFILE'
# shared formulas and casks
{{ range .packages.darwin.homebrew.shared.formulas -}}
brew "{{ . }}"
{{ end }}
{{ range .packages.darwin.homebrew.shared.casks -}}
cask "{{ . }}"
{{ end }}

# personal formulas and casks
{{ if .personal -}}
{{ range .packages.darwin.homebrew.personal.formulas -}}
brew "{{ . }}"
{{ end }}
{{ range .packages.darwin.homebrew.personal.casks -}}
cask "{{ . }}"
{{ end }}
{{- end }}

# work formulas and casks
{{ if .work -}}
{{ range .packages.darwin.homebrew.work.formulas -}}
brew "{{ . }}"
{{ end }}
{{ range .packages.darwin.homebrew.work.casks -}}
cask "{{ . }}"
{{ end }}
{{- end }}
BREWFILE

echo "Packages installed."
{{- end }}
Loading