diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 0000000..d06edf3 --- /dev/null +++ b/.github/dependabot.yaml @@ -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" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yaml similarity index 100% rename from .github/workflows/ci.yml rename to .github/workflows/ci.yaml diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..3dd2d92 --- /dev/null +++ b/CLAUDE.md @@ -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. diff --git a/README.md b/README.md index f194f71..aeed052 100644 --- a/README.md +++ b/README.md @@ -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 ` 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. diff --git a/apm.yml b/apm.yaml similarity index 100% rename from apm.yml rename to apm.yaml diff --git a/home/.chezmoi.toml.tmpl b/home/.chezmoi.toml.tmpl deleted file mode 100644 index 4cc2cd1..0000000 --- a/home/.chezmoi.toml.tmpl +++ /dev/null @@ -1,5 +0,0 @@ -{{- $context := promptChoiceOnce . "context" "Which context is this machine?" (list "personal" "work") -}} -{{- $email := promptStringOnce . "email" "Git email address" -}} -[data] -context = {{ $context | quote }} -email = {{ $email | quote }} diff --git a/home/.chezmoi.yaml.tmpl b/home/.chezmoi.yaml.tmpl new file mode 100644 index 0000000..cf99260 --- /dev/null +++ b/home/.chezmoi.yaml.tmpl @@ -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 }} \ No newline at end of file diff --git a/home/.chezmoidata/defaults.toml b/home/.chezmoidata/defaults.toml deleted file mode 100644 index 7b81baa..0000000 --- a/home/.chezmoidata/defaults.toml +++ /dev/null @@ -1,8 +0,0 @@ -[git] -name = "Edwin Hernandez" - -[font] -mono = "Monaspace Argon" - -[editor] -default = "nvim" diff --git a/home/.chezmoidata/extensions.yaml b/home/.chezmoidata/extensions.yaml new file mode 100644 index 0000000..7f0cb73 --- /dev/null +++ b/home/.chezmoidata/extensions.yaml @@ -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 diff --git a/home/.chezmoidata/packages.yaml b/home/.chezmoidata/packages.yaml new file mode 100644 index 0000000..6c2fb8f --- /dev/null +++ b/home/.chezmoidata/packages.yaml @@ -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: [] diff --git a/home/.chezmoiexternal.toml b/home/.chezmoiexternal.toml deleted file mode 100644 index ab618a7..0000000 --- a/home/.chezmoiexternal.toml +++ /dev/null @@ -1,19 +0,0 @@ -[".config/zsh/plugins/zsh-autosuggestions"] -type = "git-repo" -url = "https://github.com/zsh-users/zsh-autosuggestions.git" -refreshPeriod = "168h" - -[".config/zsh/plugins/zsh-history-substring-search"] -type = "git-repo" -url = "https://github.com/zsh-users/zsh-history-substring-search.git" -refreshPeriod = "168h" - -[".config/zsh/plugins/zsh-syntax-highlighting"] -type = "git-repo" -url = "https://github.com/zsh-users/zsh-syntax-highlighting.git" -refreshPeriod = "168h" - -[".config/zsh/plugins/zsh-transient-prompt"] -type = "git-repo" -url = "https://github.com/olets/zsh-transient-prompt.git" -refreshPeriod = "168h" diff --git a/home/.chezmoiexternal.yaml.tmpl b/home/.chezmoiexternal.yaml.tmpl new file mode 100644 index 0000000..b9526f2 --- /dev/null +++ b/home/.chezmoiexternal.yaml.tmpl @@ -0,0 +1,3 @@ +{{ if eq .chezmoi.os "darwin" -}} +{{ template "chezmoiexternal.d/darwin.yaml" . }} +{{- end }} \ No newline at end of file diff --git a/home/.chezmoiignore.tmpl b/home/.chezmoiignore.tmpl deleted file mode 100644 index f5f0225..0000000 --- a/home/.chezmoiignore.tmpl +++ /dev/null @@ -1 +0,0 @@ -{{/* No context-specific ignores yet. Add patterns here when needed. */}} diff --git a/home/.chezmoiscripts/darwin/run_once_01_install-homebrew.sh.tmpl b/home/.chezmoiscripts/darwin/run_once_01_install-homebrew.sh.tmpl new file mode 100644 index 0000000..855add3 --- /dev/null +++ b/home/.chezmoiscripts/darwin/run_once_01_install-homebrew.sh.tmpl @@ -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 }} diff --git a/home/.chezmoiscripts/darwin/run_onchange_02_install-packages.sh.tmpl b/home/.chezmoiscripts/darwin/run_onchange_02_install-packages.sh.tmpl new file mode 100644 index 0000000..7f894a7 --- /dev/null +++ b/home/.chezmoiscripts/darwin/run_onchange_02_install-packages.sh.tmpl @@ -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 }} diff --git a/home/.chezmoiscripts/darwin/run_onchange_03_install-mise-tools.sh.tmpl b/home/.chezmoiscripts/darwin/run_onchange_03_install-mise-tools.sh.tmpl new file mode 100644 index 0000000..2c0ec6a --- /dev/null +++ b/home/.chezmoiscripts/darwin/run_onchange_03_install-mise-tools.sh.tmpl @@ -0,0 +1,20 @@ +{{- if eq .chezmoi.os "darwin" -}} + +#!/bin/bash +# .chezmoiscripts/darwin/run_onchange_03_install-mise-tools.sh.tmpl +# Runs `mise install` to sync tools declared in ~/.config/mise/config.toml. +# Reruns automatically whenever the mise config template changes. +# +# mise config hash: {{ include "dot_config/mise/config.toml.tmpl" | sha256sum }} +set -euo pipefail + +if ! command -v mise &>/dev/null; then + echo "mise not found — ensure run_onchange_02_install-packages ran successfully." + exit 1 +fi + +echo "Installing mise tools..." +mise install --yes + +echo "Mise tools installed." +{{- end }} diff --git a/home/.chezmoiscripts/darwin/run_onchange_04_install-vscode-extensions.sh.tmpl b/home/.chezmoiscripts/darwin/run_onchange_04_install-vscode-extensions.sh.tmpl new file mode 100644 index 0000000..0b8ca87 --- /dev/null +++ b/home/.chezmoiscripts/darwin/run_onchange_04_install-vscode-extensions.sh.tmpl @@ -0,0 +1,54 @@ +{{- if eq .chezmoi.os "darwin" -}} + +#!/bin/bash +# .chezmoiscripts/darwin/run_onchange_04_install-vscode-extensions.sh.tmpl +# Installs VS Code extensions from extensions.yaml vscode section. +# Reruns automatically whenever extensions.yaml changes. +# +# extensions.yaml hash: {{ include ".chezmoidata/extensions.yaml" | sha256sum }} +set -euo pipefail + +if ! command -v code &>/dev/null; then + echo "VS Code CLI not found — open VS Code and run: Shell Command: Install 'code' command in PATH" + exit 0 +fi + +echo "Installing VS Code extensions..." + +extensions=( + # shared + {{ range .vscode.shared -}} + "{{ . }}" + {{ end }} + {{ if .personal -}} + # personal + {{ range .vscode.personal -}} + "{{ . }}" + {{ end -}} + {{- end }} + {{ if .work -}} + # work + {{ range .vscode.work -}} + "{{ . }}" + {{ end -}} + {{- end }} +) + +# Install best-effort. A built-in / pre-release extension may refuse to be +# replaced by an older marketplace version — we log and continue rather than +# aborting the whole apply. +failed=() +for ext in "${extensions[@]}"; do + [[ -z "$ext" ]] && continue + if ! code --install-extension "$ext" --force; then + failed+=("$ext") + fi +done + +if [[ ${#failed[@]} -gt 0 ]]; then + echo "WARNING: ${#failed[@]} extension(s) failed to install:" >&2 + printf ' - %s\n' "${failed[@]}" >&2 +fi + +echo "VS Code extensions installed." +{{- end }} diff --git a/home/.chezmoiscripts/darwin/run_onchange_05_defaults.sh.tmpl b/home/.chezmoiscripts/darwin/run_onchange_05_defaults.sh.tmpl new file mode 100644 index 0000000..3578a0b --- /dev/null +++ b/home/.chezmoiscripts/darwin/run_onchange_05_defaults.sh.tmpl @@ -0,0 +1,74 @@ +{{- if eq .chezmoi.os "darwin" -}} + +#!/bin/bash +# .chezmoiscripts/darwin/run_onchange_05_defaults.sh.tmpl +# Sets default preferences for macOS. +# Reruns automatically whenever this script's content changes. +set -euo pipefail + +osascript -e 'tell application "System Settings" to quit' + +# Finder +defaults write NSGlobalDomain AppleShowAllExtensions -bool true +defaults write NSGlobalDomain NSDocumentSaveNewDocumentsToCloud -bool false +defaults write com.apple.finder AppleShowAllFiles -bool true +defaults write com.apple.finder ShowPathbar -bool true +defaults write com.apple.finder ShowTabView -bool true +defaults write com.apple.finder ShowStatusBar -bool true +defaults write com.apple.finder FXPreferredViewStyle -string "Nlsv" +defaults write com.apple.finder FXEnableExtensionChangeWarning -bool false +defaults write com.apple.finder _FXShowPosixPathInTitle -bool true +defaults write com.apple.finder _FXSortFoldersFirst -bool true +defaults write com.apple.finder CreateDesktop -bool false +killall Finder + +# Dock — behavior +defaults write com.apple.dock orientation -string bottom +defaults write com.apple.dock launchanim -bool false +defaults write com.apple.dock tilesize -int 42 +defaults write com.apple.dock autohide -bool true +defaults write com.apple.dock autohide-time-modifier -float 0.5 +defaults write com.apple.dock autohide-delay -float 0 +defaults write com.apple.dock show-recents -bool false + +# Dock — pinned apps (declarative via dockutil; missing apps are skipped) +if command -v dockutil >/dev/null 2>&1; then + dockutil --no-restart --remove all >/dev/null 2>&1 || true + for app in \ + "/System/Applications/System Settings.app" \ + "/System/Applications/Utilities/Activity Monitor.app" \ + "/Applications/Brave Browser.app" \ + "/Applications/Visual Studio Code.app" \ + "/Applications/Ghostty.app" \ + "/Applications/Discord.app" \ + "/System/Applications/Music.app" \ + "/System/Applications/Mail.app" \ + "/System/Applications/Notes.app" \ + "/System/Applications/Reminders.app" \ + "/Applications/CleanShot X.app" \ + "/Applications/superwhisper.app"; do + [ -d "$app" ] && dockutil --no-restart --add "$app" >/dev/null + done +else + echo "dockutil not found — skipping dock pinning. Run 'brew install dockutil' or rerun 'chezmoi apply'." >&2 +fi +killall Dock + +# Screenshots +defaults write com.apple.screencapture disable-shadow -bool true +mkdir -p "$HOME/Pictures/Screenshots" +defaults write com.apple.screencapture location -string "$HOME/Pictures/Screenshots" +defaults write com.apple.screencapture show-thumbnail -bool false +killall SystemUIServer + +# Misc +defaults write com.apple.CrashReporter DialogType none +defaults write com.apple.LaunchServices LSQuarantine -bool false +defaults write -g AppleShowScrollBars -string Always + +# Default browser — Brave (only set if not already, avoids macOS confirmation popup) +if ! defaults read com.apple.LaunchServices/com.apple.launchservices.secure 2>/dev/null \ + | grep -q '"LSHandlerRoleAll" = "com\.brave\.browser"'; then + open -a "Brave Browser" --args --make-default-browser +fi +{{- end }} diff --git a/home/.chezmoitemplates/chezmoiexternal.d/darwin.yaml b/home/.chezmoitemplates/chezmoiexternal.d/darwin.yaml new file mode 100644 index 0000000..954fc0e --- /dev/null +++ b/home/.chezmoitemplates/chezmoiexternal.d/darwin.yaml @@ -0,0 +1,19 @@ +".config/zsh/plugins/zsh-autosuggestions": + type: git-repo + url: https://github.com/zsh-users/zsh-autosuggestions.git + refreshPeriod: 168h + +".config/zsh/plugins/zsh-history-substring-search": + type: git-repo + url: https://github.com/zsh-users/zsh-history-substring-search.git + refreshPeriod: 168h + +".config/zsh/plugins/zsh-syntax-highlighting": + type: git-repo + url: https://github.com/zsh-users/zsh-syntax-highlighting.git + refreshPeriod: 168h + +".config/zsh/plugins/zsh-transient-prompt": + type: git-repo + url: https://github.com/olets/zsh-transient-prompt.git + refreshPeriod: 168h diff --git a/home/dot_config/gh/config.yml b/home/dot_config/gh/config.yml deleted file mode 100644 index bb9e9e8..0000000 --- a/home/dot_config/gh/config.yml +++ /dev/null @@ -1,27 +0,0 @@ -# The current version of the config schema -version: 1 -# What protocol to use when performing git operations. Supported values: ssh, https -git_protocol: https -# What editor gh should run when creating issues, pull requests, etc. If blank, will refer to environment. -editor: -# When to interactively prompt. This is a global config that cannot be overridden by hostname. Supported values: enabled, disabled -prompt: enabled -# Preference for editor-based interactive prompting. This is a global config that cannot be overridden by hostname. Supported values: enabled, disabled -prefer_editor_prompt: disabled -# A pager program to send command output to, e.g. "less". If blank, will refer to environment. Set the value to "cat" to disable the pager. -pager: -# Aliases allow you to create nicknames for gh commands -aliases: - co: pr checkout -# The path to a unix socket through which to send HTTP connections. If blank, HTTP traffic will be handled by net/http.DefaultTransport. -http_unix_socket: -# What web browser gh should use when opening URLs. If blank, will refer to environment. -browser: -# Whether to display labels using their RGB hex color codes in terminals that support truecolor. Supported values: enabled, disabled -color_labels: disabled -# Whether customizable, 4-bit accessible colors should be used. Supported values: enabled, disabled -accessible_colors: disabled -# Whether an accessible prompter should be used. Supported values: enabled, disabled -accessible_prompter: disabled -# Whether to use a animated spinner as a progress indicator. If disabled, a textual progress indicator is used instead. Supported values: enabled, disabled -spinner: enabled diff --git a/home/dot_config/git/config.tmpl b/home/dot_config/git/config.tmpl index 44df038..4967678 100644 --- a/home/dot_config/git/config.tmpl +++ b/home/dot_config/git/config.tmpl @@ -1,25 +1,50 @@ [user] - name = {{ .git.name | quote }} - email = {{ .email | quote }} + name = {{ .git.name }} + email = {{ .git.email }} [core] - excludesfile = ~/.gitignore - editor = {{ .editor.default }} + editor = code --wait + pager = delta + autocrlf = input + excludesfile = ~/.config/git/ignore ignorecase = false +[pull] + rebase = true [push] - default = simple -[color] - status = auto - diff = auto - branch = auto - interactive = auto - grep = auto - ui = auto + autoSetupRemote = true +[rebase] + autoStash = true +[interactive] + diffFilter = delta --color-only + +[diff] + colorMoved = default + +[delta] + features = unobtrusive-line-numbers decorations + whitespace-error-style = 22 reverse + plus-color = "#012800" + minus-color = "#340001" + syntax-theme = Monokai Extended + light = false + navigate = true + +[merge] + conflictstyle = diff3 + +[delta "unobtrusive-line-numbers"] + line-numbers = true + line-numbers-minus-style = "#444444" + line-numbers-zero-style = "#444444" + line-numbers-plus-style = "#444444" + line-numbers-left-format = "{nm:>4}┊" + line-numbers-right-format = "{np:>4}│" + line-numbers-left-style = blue + line-numbers-right-style = blue + +[delta "decorations"] + commit-decoration-style = bold yellow box ul + file-style = bold yellow ul + file-decoration-style = none + hunk-header-decoration-style = yellow box [hub] protocol = https -[github] - user = edwinhern -[ghq] - root = ~/.ghq -{{- if eq .context "work" }} -{{- /* placeholder - no work-specific git config yet */ -}} -{{- end }} diff --git a/home/dot_config/git/ignore b/home/dot_config/git/ignore index 66d62f8..5816a0e 100644 --- a/home/dot_config/git/ignore +++ b/home/dot_config/git/ignore @@ -1 +1,61 @@ +# AI **/.claude/settings.local.json + +#################### +# Mac +#################### + +# General +.DS_Store +.AppleDouble +.LSOverride +Icon[] + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdiskre + +#################### +# Mise +#################### + +# Mise configuration files +# https://mise.jdx.dev/configuration.html +# https://mise.jdx.dev/configuration/environments.html +.mise.*.local.toml +.mise.local.toml +mise.*.local.toml +mise.local.toml +.mise/*.local.toml +mise/*.local.toml + + +#################### +# VSCode +#################### + +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets +!*.code-workspace + +# Built Visual Studio Code Extensions +*.vsix \ No newline at end of file diff --git a/home/dot_config/homebrew/Brewfile.tmpl b/home/dot_config/homebrew/Brewfile.tmpl deleted file mode 100644 index a45da8f..0000000 --- a/home/dot_config/homebrew/Brewfile.tmpl +++ /dev/null @@ -1,57 +0,0 @@ -# vi: ft=ruby -# Shared (all contexts) ---------------------------------------------------- - -# Fonts -cask "font-monaspace" -cask "font-jetbrains-mono-nerd-font" - -# Apps -cask "brave-browser" -cask "ghostty" -cask "raycast" -cask "rectangle" -cask "superwhisper" -cask "visual-studio-code" - -# VS Code extensions -vscode "adpyke.codesnap" -vscode "alefragnani.project-manager" -vscode "edwinhuish.better-comments-next" -vscode "esbenp.prettier-vscode" -vscode "fosshaas.fontsize-shortcuts" -vscode "github.copilot-chat" -vscode "miguelsolorio.symbols" -vscode "raunofreiberg.vesper" -vscode "shd101wyy.markdown-preview-enhanced" -vscode "usernamehw.errorlens" -vscode "yoavbls.pretty-ts-errors" -vscode "yzhang.markdown-all-in-one" - -{{ if eq .context "personal" -}} -# Personal-only ----------------------------------------------------------- -brew "mas" -cask "aldente" -cask "claude-code" -cask "cleanshot" -cask "discord" -cask "displaylink" -cask "microsoft-excel" -cask "synology-drive" - -vscode "anthropic.claude-code" -vscode "biomejs.biome" -vscode "bradlc.vscode-tailwindcss" -vscode "charliermarsh.ruff" -vscode "csstools.postcss" -vscode "ms-python.debugpy" -vscode "ms-python.python" -vscode "ms-python.vscode-pylance" -vscode "ms-python.vscode-python-envs" -vscode "james-yu.latex-workshop" -vscode "mathematic.vscode-latex" -vscode "tomoki1207.pdf" -{{- end }} -{{ if eq .context "work" -}} -# Work-only --------------------------------------------------------------- -# placeholder - add work-specific casks/brews here -{{- end }} diff --git a/home/dot_config/mise/config.toml.tmpl b/home/dot_config/mise/config.toml.tmpl index 83336ac..a5dec59 100644 --- a/home/dot_config/mise/config.toml.tmpl +++ b/home/dot_config/mise/config.toml.tmpl @@ -1,23 +1,16 @@ [tools] -chezmoi = 'latest' -node = 'lts' -python = 'latest' -github-cli = 'latest' -tmux = 'latest' -starship = 'latest' -fastfetch = 'latest' -{{ if eq .context "personal" -}} -pnpm = 'latest' -uv = 'latest' -biome = 'latest' +node = "lts" +pnpm = "latest" +python = "latest" + +{{ if .personal -}} +biome = "latest" +uv = "latest" {{- end }} -{{- if eq .context "work" }} -# placeholder - add work-specific tools here + +{{ if .work -}} +redis = "latest" {{- end }} [settings] -# tools can read the versions files used by other version managers -idiomatic_version_file_enable_tools = ['node', 'python', 'pnpm'] - -# config files with these prefixes will be trusted by default -trusted_config_paths = ['~/Documents/github'] +idiomatic_version_file_enable_tools = ["node", "python", "pnpm"] \ No newline at end of file diff --git a/home/dot_config/raycast/Raycast 2026-04-26 00.55.21.rayconfig b/home/dot_config/raycast/raycast.rayconfig similarity index 100% rename from home/dot_config/raycast/Raycast 2026-04-26 00.55.21.rayconfig rename to home/dot_config/raycast/raycast.rayconfig diff --git a/home/dot_config/tmux/tmux.conf b/home/dot_config/tmux/tmux.conf index db406c6..eb9a3df 100644 --- a/home/dot_config/tmux/tmux.conf +++ b/home/dot_config/tmux/tmux.conf @@ -1 +1,5 @@ # tmux configuration + +set -g mouse on + +setw -g mode-keys vi \ No newline at end of file diff --git a/home/dot_config/vscode/extensions.json b/home/dot_config/vscode/extensions.json deleted file mode 100644 index d17bf93..0000000 --- a/home/dot_config/vscode/extensions.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "recommendations": [ - "adpyke.codesnap", - "alefragnani.project-manager", - "anthropic.claude-code", - "biomejs.biome", - "bradlc.vscode-tailwindcss", - "charliermarsh.ruff", - "csstools.postcss", - "edwinhuish.better-comments-next", - "esbenp.prettier-vscode", - "fosshaas.fontsize-shortcuts", - "github.copilot-chat", - "james-yu.latex-workshop", - "mathematic.vscode-latex", - "miguelsolorio.symbols", - "ms-python.debugpy", - "ms-python.python", - "ms-python.vscode-pylance", - "ms-python.vscode-python-envs", - "raunofreiberg.vesper", - "shd101wyy.markdown-preview-enhanced", - "tomoki1207.pdf", - "usernamehw.errorlens", - "yoavbls.pretty-ts-errors", - "yzhang.markdown-all-in-one" - ] -} diff --git a/home/dot_config/zsh/custom/.gitkeep b/home/dot_config/zsh/custom/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/home/dot_config/zsh/exports.zsh b/home/dot_config/zsh/exports.zsh index 3d7e00a..14b4dd7 100644 --- a/home/dot_config/zsh/exports.zsh +++ b/home/dot_config/zsh/exports.zsh @@ -1,4 +1,6 @@ # zsh exports (PATH, environment variables) +# Personal: chezmoi decrypts ~/.secrets.local from age-encrypted source state. +# Work: maintain ~/.secrets.local by hand on the machine; never commit it. # shellcheck source=/dev/null -[[ -f ~/.secrets ]] && source ~/.secrets +[[ -f ~/.secrets.local ]] && source ~/.secrets.local diff --git a/home/dot_zshenv.tmpl b/home/dot_zshenv similarity index 100% rename from home/dot_zshenv.tmpl rename to home/dot_zshenv diff --git a/makefile b/makefile index e5cd49d..04fbfcb 100644 --- a/makefile +++ b/makefile @@ -1,17 +1,17 @@ fmt: - sh scripts/format.sh + ./scripts/format.sh .PHONY: fmt lint: - sh scripts/lint.sh + ./scripts/lint.sh .PHONY: lint compile: - sh scripts/compile.sh + ./scripts/compile.sh .PHONY: compile install: - bash scripts/install.sh + ./scripts/install.sh .PHONY: install apply: diff --git a/mise.toml b/mise.toml index afe3c02..a9f3500 100644 --- a/mise.toml +++ b/mise.toml @@ -1,5 +1,6 @@ [tools] -apm = "0.8.11" +chezmoi = "2.70.2" +apm = "0.12.4" prettier = "3.8.3" shellcheck = "0.11.0" shfmt = "3.12.0" diff --git a/scripts/format.sh b/scripts/format.sh index 350a09a..f04d808 100755 --- a/scripts/format.sh +++ b/scripts/format.sh @@ -17,7 +17,7 @@ printf "\n* %s\n\n" "Formatting markdown and YAML..." # format Markdown and YAML files. mise exec -- prettier --write --ignore-unknown \ "**/*.md" \ - "**/*.yml" + "**/*.yaml" printf "\n* %s\n\n" "Formatting TOML..." diff --git a/scripts/install.sh b/scripts/install.sh index c015d4d..fdd129b 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1,47 +1,32 @@ -#!/usr/bin/env bash -# Bootstrap a fresh macOS machine from this dotfiles repo. -# Order: brew -> mise -> chezmoi (via mise) -> chezmoi apply -> mise install -> brew bundle. -# Idempotent: safe to re-run. - -set -euo pipefail - -REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" - -step() { - printf "\n==> %s\n" "$*" -} - -# 1. Homebrew -------------------------------------------------------------- -if ! command -v brew >/dev/null 2>&1; then - step "Installing Homebrew" - /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" -fi - -# Ensure brew is on PATH for this shell (Apple Silicon path). -if [ -x /opt/homebrew/bin/brew ]; then - eval "$(/opt/homebrew/bin/brew shellenv)" -elif [ -x /usr/local/bin/brew ]; then - eval "$(/usr/local/bin/brew shellenv)" +#!/usr/bin/env sh +# install.sh +# Bootstrap script for a fresh macOS machine. +# Installs Chezmoi if absent, then hands off to chezmoi init --apply. +# Chezmoi run_once_ and run_onchange_ scripts handle everything from there. +# +# Usage: +# curl -fsSL https://raw.githubusercontent.com/edwinhern/dotfiles/main/scripts/install.sh | sh +# — or — +# ./scripts/install.sh (from a local clone) + +set -e + +if [ ! "$(command -v chezmoi)" ]; then + bin_dir="$HOME/.local/bin" + chezmoi="$bin_dir/chezmoi" + + if [ "$(command -v curl)" ]; then + sh -c "$(curl -fsSL https://get.chezmoi.io)" -- -b "$bin_dir" + elif [ "$(command -v wget)" ]; then + sh -c "$(wget -qO- https://get.chezmoi.io)" -- -b "$bin_dir" + else + echo "To install chezmoi, you must have curl or wget installed." >&2 + exit 1 + fi +else + chezmoi=chezmoi fi -# 2. mise ------------------------------------------------------------------ -if ! command -v mise >/dev/null 2>&1; then - step "Installing mise" - brew install mise -fi - -eval "$(mise activate bash --shims)" - -# 3. chezmoi (ephemeral via mise; the managed config will pin it after apply) -step "Running chezmoi init --apply (will prompt for context on first run)" -mise x chezmoi@latest -- chezmoi init --apply --source "${REPO_ROOT}" - -# 4. mise install ---------------------------------------------------------- -step "Installing mise-managed runtimes" -mise install - -# 5. brew bundle ----------------------------------------------------------- -step "Installing apps via brew bundle" -brew bundle --file="${HOME}/.config/homebrew/Brewfile" - -step "Done. Open a new shell to pick up ZDOTDIR/PATH changes." +# init + apply in one shot. +# Chezmoi will prompt for git.name, git.email, and context if hostname is unrecognized. +exec "$chezmoi" init --apply "gh:edwinhern/dotfiles" diff --git a/scripts/lint.sh b/scripts/lint.sh index e804ce8..8961ab7 100755 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -5,7 +5,7 @@ set -eu # check format Markdown and YAML files. mise exec -- prettier --check --ignore-unknown \ "**/*.md" \ - "**/*.yml" + "**/*.yaml" printf "* %s\n" "Linting shell scripts..."