Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 16 additions & 0 deletions .agents/skills/finicky/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
name: finicky
description: Finicky browser-router config for this chezmoi repo (macOS). Covers the mandatory restart-after-apply procedure and why Finicky's auto-reload fails under chezmoi. Triggers: Finicky, browser router, auto-reload, inode, kqueue, fsnotify, restart required, macOS. Load before changing home/dot_config/finicky.js.tmpl.
---

# Finicky (macOS)

**Scope:** `home/dot_config/finicky.js.tmpl` (target `~/.config/finicky.js`).

After applying changes to the Finicky config (`~/.config/finicky.js`), reload it by:

1. `killall Finicky || true`
2. `open -a Finicky`
3. Close the foreground window manually (AppleScript window close is not available without assistive access)

Finicky's built-in auto-reload does NOT work when the config is managed by chezmoi. Chezmoi replaces the file with a new inode on every write; Finicky's fsnotify watcher (kqueue on macOS) tracks by inode and loses the watch when this happens. A restart is always required.
17 changes: 17 additions & 0 deletions .agents/skills/hammerspoon/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
name: hammerspoon
description: Hammerspoon macOS Space-switching config for this chezmoi repo. Covers the Option number-row keybinds (⌥1..⌥5) via eventtap, full-screen spaces, the menu-bar Space indicator, mru-spaces position stability, Accessibility, and debugging via the hs CLI. Triggers: Hammerspoon, macOS Spaces, Space switching, Option+1, eventtap, hotkey.bind, Carbon hotkey reservation, hs.spaces, space_indicator, mru-spaces, menu bar, hs.ipc, Accessibility. Load before changing home/dot_hammerspoon/*.
---

# Hammerspoon (macOS Spaces)

**Scope:** `home/dot_hammerspoon/*` (`init.lua`, `spaces.lua`, `space_indicator.lua` → `~/.hammerspoon/`). The mru-spaces stability rule below also drives `home/.chezmoiscripts/run_onchange_after_macos_defaults.sh.tmpl`.

Hammerspoon provides direct macOS Space switching: `⌥1`–`⌥5` (Option, matching Komorebi's `alt`+number on Windows) jump to the Nth Space on the focused screen, including full-screen-app spaces, and create a new empty Space when the target position does not exist yet. Cask in `home/.chezmoidata/brew/00-base.yaml` (macOS-gated); config at `home/dot_hammerspoon/{init.lua,spaces.lua,space_indicator.lua}` (→ `~/.hammerspoon/`), gated to darwin in `home/.chezmoiignore`. A menu bar badge (`space_indicator.lua`) shows the active Space's position. Gotchas that have bitten us:

- **Bind number-row Space switches with `hs.eventtap`, not `hs.hotkey.bind`.** The keys are `⌥1`..`⌥5` (Option, matching Komorebi's `alt`+number). Two reasons the tap is needed: (1) on macOS `⌥`+number normally types special characters (`¡™£¢∞`), and the tap returns `true` to swallow the keystroke before the input system inserts the character, so the Space switches and nothing is typed (verified by reading a focused text field after a synthetic `⌥1`); (2) `⌃`+number can't go through `hs.hotkey` at all: macOS reserves `⌃1`..`⌃N` as Carbon hotkeys for "Switch to Desktop" by the number of Mission Control *user* desktops even when the System Settings checkboxes are off, so `RegisterEventHotKey` rejects them (`hs.hotkey:enable()` returns nil, console logs `-9878 ... already registered`). Diagnose hotkey issues with `hs -c 'return #hs.hotkey.getHotkeys()'` plus the Hammerspoon console.
- **`hs.spaces` reaches full-screen spaces.** `hs.spaces.spacesForScreen(uuid)` lists every Space (user and full screen/tiled, per `hs.spaces.spaceType`) in Mission Control order, and `hs.spaces.gotoSpace(id)` activates one by driving its Mission Control thumbnail, so it can land on full-screen spaces (the native `⌃`+number shortcuts cannot). Map "Space N" by indexing that list by position.
- **Space positions are not stable while "rearrange by most recent use" is on.** With `com.apple.dock` `mru-spaces` unset or true (the macOS default), visiting a full-screen space reorders it in Mission Control, so the index `⌥N` and the menu bar indicator use drifts (the order is stable only while idle). `defaults write com.apple.dock mru-spaces -bool false && killall Dock` gives stable positions; this is applied declaratively by `home/.chezmoiscripts/run_onchange_after_macos_defaults.sh.tmpl` (darwin-guarded, restarts the Dock only when it is running). The indicator (`hs.menubar` badge driven by `hs.spaces.watcher`) recomputes ~0.25s after a change so it reads the settled order, not a mid-transition one; the watcher fires before `activeSpaceOnScreen` settles otherwise.
- **Auto-reload works.** `hs.pathwatcher` uses FSEvents (path-based), so it survives chezmoi's atomic-rename writes (new inode each apply). By contrast, Finicky's `fsnotify`/kqueue watcher is inode-based and loses the watch on every chezmoi write, so Finicky needs a manual restart (see the `finicky` skill).
- **`hs.ipc` is enabled** in `init.lua`, so the running config is queryable from the `hs` CLI (`hs -c '...'`), the main way to debug Spaces and hotkeys.
- **Requires Accessibility** permission (System Settings → Privacy & Security → Accessibility); `gotoSpace` drives Mission Control through the Accessibility API. One-time manual grant, cannot be automated by chezmoi.
22 changes: 22 additions & 0 deletions .agents/skills/komorebi/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
name: komorebi
description: Komorebi Windows tiling-WM config for this chezmoi repo. Covers KOMOREBI_CONFIG_HOME, manage/layered/ignore rules, the never-Stop-Process-Force socket trap, whkd quirks, and reload. Triggers: Komorebi, Windows tiling WM, whkd, KOMOREBI_CONFIG_HOME, Stop-Process, sock file, AF_UNIX, layered_applications, FancyZones, reload-configuration. Load before changing home/dot_config/komorebi/* or home/dot_config/whkdrc.
---

# Komorebi (Windows tiling)

**Scope:** `home/dot_config/komorebi/*` (`komorebi.json`, `start.ps1`), `home/dot_config/whkdrc`.

Komorebi is the Windows tiling WM (it replaced GlazeWM). Config is chezmoi-managed at `home/dot_config/komorebi/komorebi.json` (→ `~/.config/komorebi/komorebi.json`); keybindings at `home/dot_config/whkdrc` (→ `~/.config/whkdrc`); a launcher at `home/dot_config/komorebi/start.ps1` is wired into the HKCU Run key by the Windows bootstrap. Gotchas that have bitten us:

- **`KOMOREBI_CONFIG_HOME` must be set.** Without it, komorebi looks for `komorebi.json` in `$USERPROFILE` and silently runs with built-in defaults. `start.ps1` sets it before launch; the bootstrap also sets it as a User env var so interactive `komorebic` works.
- **Komorebi is default-ignore for many app categories**, unlike GlazeWM which was default-manage. For Discord/Vivaldi-style "obvious" windows komorebi tiles them out of the box, but terminals, MSIX/UWP apps, and Electron apps with layered rendering need explicit rules. The community rulebook is `komorebic fetch-asc` — fetches `applications.json` covering hundreds of common apps; point `komorebi.json`'s `app_specific_configuration_path` at it.
- **Layered windows (`WS_EX_LAYERED`) are skipped before any `manage_rules` fires.** Diagnose by checking exstyle (PowerShell + `GetWindowLong`); fix by adding the exe to `layered_applications` *in addition to* `manage_rules`. **Gotcha: `layered_applications` is per-exe, not per-window.** If an app has a non-layered main window plus a layered popup under the same exe (e.g. Claude Desktop's main window vs the Ctrl+Alt+Space launcher), adding the exe causes komorebi to tile the popup too. Leave the exe out — komorebi manages the non-layered main via `manage_rules` and auto-skips the layered popup. Win32 file/save dialogs (class `#32770`) belong in `ignore_rules` once, globally — they're modal child windows komorebi shouldn't touch in any app.
- **Colour format in `komorebi.json` is `{r, g, b}` objects.** Hex strings (`"0xFFBC8D"`) and integer COLORREFs are both rejected by the `Colour` enum.
- **Never `Stop-Process -Force` on komorebi.** It leaves a stuck `%LOCALAPPDATA%\komorebi\komorebi.sock` AF_UNIX file that Windows refuses to delete via *any* API (Remove-Item, fsutil reparsepoint delete, WSL rm, error 1920) and the file even survives reboots. Subsequent komorebi launches then fail with error 1920 at `window_manager.rs:143`. Always `komorebic stop` instead. If a stuck sock already exists, the only workaround is to `Rename-Item` the parent dir (`%LOCALAPPDATA%\komorebi` → `komorebi.stale.<N>`) so komorebi recreates a fresh one.
- **`is_paused: true` silently no-ops every command.** Before deep debugging of "commands do nothing", check `komorebic state | ConvertFrom-Json | Select-Object is_paused`. `komorebic toggle-pause` flips it.
- **`komorebic` IPC fails from non-interactive/background shells** with WSAEINVAL (error 10022) on the AF_UNIX socket — even when the same komorebi instance happily talks to commands from a real interactive PowerShell. Don't waste time debugging from agent shells; have the user run commands themselves.
- **Komorebi can't launch from a non-interactive session** either — it fails with "failed call to AllowSetForegroundWindow after 5 retries". The Run key (logon-time) and a user-launched PowerShell window both work; agent-spawned PowerShell doesn't.
- **whkd quirks:** key name is `return`, not `enter`. `.shell cmd` is more reliable than `.shell pwsh` because pwsh isn't always on the PATH whkd inherits from `start.ps1`. Under `.shell cmd`, chain commands with `&&` (or `&`) — cmd doesn't treat `;` as a separator, so PowerShell-style `cmd1; cmd2` silently parses `;` as a literal arg. whkd has no modal binding modes (GlazeWM's `alt+r` resize mode has no direct equivalent — use direct chords like `alt+ctrl+hjkl`).
- **PowerToys FancyZones competes** with komorebi for foreground/window management and can prevent komorebi launching. Disable the FancyZones module in PowerToys before relying on komorebi.
- **`komorebic reload-configuration` reloads `komorebi.json`. whkd has no live reload** — restart it (`Stop-Process whkd` then `Start-Process`) to pick up `whkdrc` changes. The chezmoi `run_onchange_after_komorebi_reload_windows.ps1.tmpl` script does both.
14 changes: 14 additions & 0 deletions .agents/skills/mise-on-windows/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
name: mise-on-windows
description: mise on Windows gotchas for this chezmoi repo: the Chocolatey mise package omits mise-shim.exe, breaking binary-path resolution for MCP servers/IDEs. Covers shim placement, the always-run recovery guard, PS 5.1 stderr promotion, and the claude-code-win32 optional-dep trap. Triggers: mise Windows, mise-shim.exe, binary path, file shim mode, MCP servers, cannot find binary path, Chocolatey mise, reshim, PS 5.1 stderr, claude-code-win32. Load before changing home/.chezmoitemplates/windows-mise-shim-placement.ps1.tmpl, home/.chezmoiscripts/run_after_97_mise_shim_windows.ps1.tmpl, or the mise parts of run_onchange_after_bootstrap_windows.ps1.tmpl.
---

# mise on Windows

**Scope:** `home/.chezmoitemplates/windows-mise-shim-placement.ps1.tmpl`, `home/.chezmoiscripts/run_after_97_mise_shim_windows.ps1.tmpl`, and the mise-shim portion of `home/.chezmoiscripts/run_onchange_after_bootstrap_windows.ps1.tmpl` (shared with the `windows-internals` skill).

- **Chocolatey `mise` package omits `mise-shim.exe`.** Without it, mise falls back to "file" shim mode (cmd wrappers under `~/AppData/Local/mise/shims/`). File shims work from a shell but cannot return a real binary path to non-shell parents (MCP servers, IDE integrations), which fail with `cannot find binary path`. Resolution rule: mise looks for `mise-shim.exe` next to `mise.exe` or on `PATH`; otherwise it falls back to file mode. See https://github.com/jdx/mise/discussions/7998.
- **The fix is hash-pinned to the choco install layout.** `home/.chezmoiscripts/run_onchange_after_bootstrap_windows.ps1.tmpl` (elevated block, after `choco install`) downloads `mise-v<version>-windows-x64.zip` from the matching GitHub release and drops `mise-shim.exe` at `C:\ProgramData\chocolatey\lib\mise\tools\x64\`. Shared logic lives in `home/.chezmoitemplates/windows-mise-shim-placement.ps1.tmpl`.
- **Recovery after choco upgrades:** `home/.chezmoiscripts/run_after_97_mise_shim_windows.ps1.tmpl` is an always-run guard that fires after `run_after_90_package_updates_windows.ps1.tmpl`. If a `choco upgrade` replaced `mise.exe` and dropped the shim, the guard re-places it and runs `mise reshim` so per-tool shims convert back to binary shims. Fast no-op when the shim is present.
- **Elevated-block stderr gotcha (PS 5.1).** `Invoke-ElevatedWithLog` spawns `powershell.exe` (Windows PowerShell 5.1, not pwsh). Under `$ErrorActionPreference = 'Stop'`, PS 5.1 promotes native-command stderr into a terminating error. `mise --version` emits a `WARN ... version available` line to stderr when a newer release exists, so the placement template uses `[System.Diagnostics.ProcessStartInfo]` instead of `& mise --version 2>$null` to read stdout cleanly. Outer `mise reshim` calls run under `pwsh` (PS 7+) and are unaffected.
- **`npm:@anthropic-ai/claude-code` needs the `@anthropic-ai/claude-code-win32-*` optional dependency.** If that package is missing, `install.cjs` never drops `bin/claude.exe` and you may only see `claude.exe.old.<timestamp>`; the generated `claude.ps1` shim then fails with “not recognized”. Fix: `mise uninstall 'npm:@anthropic-ai/claude-code@<version>' -y` then `mise install` (or `mise install npm:@anthropic-ai/claude-code@latest`) so npm re-fetches optional deps.
31 changes: 31 additions & 0 deletions .agents/skills/windows-internals/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
name: windows-internals
description: Windows-operational internals for this chezmoi repo: WSL cloud-init provisioning, Rancher Desktop/Docker, ssh-tpm-agent commit signing, the dual gpg.exe split, WezTerm, and the Windows bootstrap/elevation block. Triggers: Windows internals, WSL, cloud-init, rsync --delete, Rancher Desktop, moby, rdctl, Docker daemon pipe, ssh-tpm-agent, TPM, two gpg.exe, WezTerm, Windows bootstrap, elevation, YASB. Load before changing Windows-operational scripts/data.
---

# Windows internals

**Scope:** `home/.chezmoiscripts/*windows*.ps1.tmpl` (except the mise-shim scripts; see the `mise-on-windows` skill), `home/.chezmoiscripts/run_onchange_after_bootstrap_windows.ps1.tmpl` (shared with `mise-on-windows`), `home/.chezmoiscripts/run_after_96_rancher_desktop_windows.ps1.tmpl`, `home/.chezmoidata/wsl/`, `home/.chezmoidata/ssh-tpm-agent.yaml`, `home/.chezmoidata/ssh-signing.yaml`, `home/private_dot_ssh/allowed_signers.tmpl`, `home/.chezmoitemplates/wsl-*.tmpl`, `home/dot_config/wezterm/`.

## Windows bootstrap / elevation

- **Windows bootstrap** — `home/.chezmoiscripts/run_onchange_after_bootstrap_windows.ps1.tmpl` installs packages via Chocolatey (`choco install -y`) and Winget, runs `mise install`, syncs Neovim plugins, and configures/starts YASB autostart via `HKCU\Software\Microsoft\Windows\CurrentVersion\Run`. `home/.chezmoiscripts/run_onchange_after_choco_review_windows.ps1.tmpl` runs interactive Chocolatey drift review after overlay changes when `bash` is available. Runs only on Windows. The elevated block does **not** skip Chocolatey/WSL when `CI` is set (same code path as interactive runs; `windows-elevation.ps1.tmpl` still avoids UAC in CI by running the block in-process). The same bootstrap script also places `mise-shim.exe` — that part is owned by the `mise-on-windows` skill.


## WezTerm, ssh-tpm-agent, gpg, WSL

- **WezTerm** — `home/dot_config/wezterm/wezterm.lua` (→ `~/.config/wezterm/wezterm.lua`). Windows terminal emulator with kitty graphics protocol support. Ignored on non-Windows via `.chezmoiignore`.
- **ssh-tpm-agent (Windows commit signing):** TPM-sealed SSH signing key for git on Windows. Version pinned in `home/.chezmoidata/ssh-tpm-agent.yaml`; installed by `home/.chezmoiscripts/run_onchange_after_98_ssh_tpm_agent_windows.ps1.tmpl` into `%LOCALAPPDATA%\Programs\ssh-tpm-agent\`; autostart via HKCU Run key. Windows `ssh-agent` service is disabled by `home/.chezmoiscripts/run_once_after_91_disable_windows_ssh_agent_windows.ps1.tmpl` so ssh-tpm-agent can bind the openssh named pipe. Per-machine signing keys listed in `home/.chezmoidata/ssh-signing.yaml`, rendered to `~/.ssh/allowed_signers` via `home/private_dot_ssh/allowed_signers.tmpl`. Gated everywhere on `sshSigning.enabled`. See `docs/ssh-signing.md` for the per-machine setup flow.
- **Two `gpg.exe` on Windows.** Git-for-Windows ships an MSYS2 `gpg` at `/usr/bin/gpg` (`C:\Program Files\Git\usr\bin\gpg.exe`) with a TTY-only pinentry that hangs from non-TTY shells, and homedir `~/.gnupg`. Chocolatey's `gnupg` package installs upstream GnuPG at `C:\Program Files\GnuPG\bin\gpg.exe` with `pinentry-basic.exe` and homedir `%APPDATA%\gnupg` (`~/AppData/Roaming/gnupg`). The two share nothing: different keyrings, agents, and sockets. For signing, smartcard, or TPM work always invoke the Chocolatey gpg by full path or set `gpg.program = "C:/Program Files/GnuPG/bin/gpg.exe"` in `~/.gitconfig.overrides`. Switch agent state with `"/c/Program Files/GnuPG/bin/gpgconf.exe" --kill all` before retrying after a hung run. For unattended signing add `pinentry-mode loopback` to `%APPDATA%\gnupg\gpg.conf` and `allow-loopback-pinentry` to `%APPDATA%\gnupg\gpg-agent.conf`.
- **WSL** — The Windows bootstrap script provisions WSL Ubuntu non-interactively via cloud-init. The cloud-init YAML lives in `home/.chezmoidata/wsl/00-ubuntu-user-data.yaml` as `windowsWsl.ubuntuCloudInitUserData` (static data; `.chezmoi.toml` `[data] wsl` remains the boolean “running inside WSL”). On **first** WSL setup, `~/.local/share/chezmoi` is created with **`cp -a` from the Windows chezmoi source** on `/mnt/<drive>/...` (see `home/.chezmoitemplates/wsl-windows-chezmoi-source-mount-path.tmpl`: maps `.chezmoi.sourceDir`). On **subsequent** runs the WSL source is **refreshed with `rsync -a --delete` from the Windows source** (`home/.chezmoitemplates/wsl-update-dotfiles-bash.sh.tmpl`) so renames and deletions on the Windows side are mirrored in WSL — without `--delete`, stale files plus `.chezmoiremove` tombstones produce `inconsistent state` errors that abort apply. **Treat the Windows chezmoi source as the authoritative tree:** any WSL-only edits to files under `~/.local/share/chezmoi/` on the WSL side are overwritten on the next Windows apply. Make source edits on Windows, run `chezmoi apply` there, and WSL will be reconciled. Shared Windows WSL script inputs (`profile.windows.wslUser` + mount path) are built in `home/.chezmoitemplates/wsl-windows-script-context.json.tmpl` (`toJson` / `fromJson`) for `run_onchange_after_bootstrap_windows.ps1.tmpl` and `run_after_95_wsl_ensure_windows.ps1.tmpl`. Set `profile.windows.wslUser` in `home/.chezmoidata/profile.yaml` (required on Windows; empty fails apply for WSL/bootstrap scripts). Scripts substitute `__WSL_CHEZMOI_SOURCE__` and `__WSL_USER__` into that blob at apply time, write `~/.cloud-init/Ubuntu.user-data`, install Ubuntu with `--no-launch`, and wait for cloud-init. Inside WSL, `chezmoi.os` is `"linux"` so the full Unix config stack (zsh, brew, tmux, etc.) applies without modification.

## Rancher Desktop / Docker (Windows)

- Rancher Desktop provides the Docker daemon on Windows via a WSL2 backend (`rancher-desktop` distro).
- Container engine must be **`moby`** (not containerd) for the `docker` CLI to work.
- Docker CLI and `rdctl` live at `C:\Program Files\Rancher Desktop\resources\resources\win32\bin\`.
- Settings file: `%LOCALAPPDATA%\rancher-desktop\settings.json` (schema version in `version` field).
- `rdctl set` covers engine and Kubernetes flags; WSL integration (`WSL.integrations.<distro>`) must use `rdctl api -X PUT --input <file> /v1/settings` with a `version` field.
- `home/.chezmoiscripts/run_after_96_rancher_desktop_windows.ps1.tmpl` starts RD, waits for the Docker daemon pipe (`\\.\pipe\docker_engine`), and enables WSL integration for Ubuntu.
- The script runs after WSL ensure (95) so Ubuntu is available for integration.
- If the backend crashes (WSL distros show Stopped while UI shows online), fix with: `rdctl shutdown --wait && wsl --shutdown && rdctl start --container-engine.name moby`.
Loading