Conversation
The binary is not installed, so znap ran the completion command anyway and every new shell opened with a `command not found`.
Rename the Claude Code alias cc -> cl. As `cc` it shadowed the C compiler outright: `command -v cc` resolved to the alias rather than /usr/bin/cc, which breaks any ./configure, make, or native gem/npm build that shells out to cc. Add a Brewfile so a fresh machine can be provisioned with `brew bundle`. Generated from the installed state after auditing every formula against actual invocation counts in ~/.zsh_history (with shell aliases resolved, so `ls`->eza and `k`->kubectl are credited to the right package). Drop 16 formulae that the audit found were never invoked and that nothing else depends on: buildifier, cffi, ffmpeg, fontforge, git-filter-repo, librsvg, meson, mupdf, mysql@8.0, poppler, qemu, yq, docutils, sphinx-doc, gobject-introspection and pyenv. Removing them let brew autoremove reclaim their orphaned dependencies too, taking the formula count from 91 to 75. pyenv is the notable one: zero invocations against 253 for uv, yet 01-environment.zsh still ran both `pyenv init --path` and `pyenv init -` on every shell start. The zsh side of that is removed separately. Kept several packages the audit scored at zero, because they are invoked by other tools rather than typed: git-delta (gitconfig sets it as pager), tree-sitter-cli (nvim-treesitter compiles parsers with it), gopls, shellcheck and rust. Declare the cursor and visual-studio-code casks. Both were installed by hand, so the 51 vscode extension entries had no editor to install into on a clean machine.
pyenv was uninstalled in the previous commit but 01-environment.zsh still ran `pyenv init --path` and `pyenv init -`, so every new shell printed two "command not found: pyenv" errors. Remove both evals along with PYENV_ROOT and its $PATH entry. Drop PYTHONPATH. It was set to /opt/homebrew/bin/python3, which is an interpreter, not a module search path. Python treats PYTHONPATH entries as directories to search for imports, so pointing it at a binary contributed nothing and risked confusing imports. Drop the ~/Library/Python/3.9/bin path entry: that is the system Python 3.9 user-scripts directory, unused now that uv manages Python. Remove the commented-out NVM block and NVM_DIR export. nvm is not installed; fnm has replaced it. Wrap the pasted-in Graphite (gt) yargs completion block in a `(( $+commands[gt] ))` guard, and fix its embedded literal newline, which had been pasted in as a raw line break inside the IFS assignment.
Startup drops from ~0.90s to ~0.15s. zprof attributed 731ms of that, 69.5%
of the total, to a single plugin: oh-my-zsh's gpg-agent, which shells out to
gpg-connect-agent on every start. It was redundant anyway. The old .zshrc
already exported GPG_TTY itself and pinentry-mac is configured in
gpg-agent.conf, so the plugin is dropped and GPG_TTY kept.
The rest of the win comes from loading oh-my-zsh a la carte, and from
wrapping every `<tool> init` in `znap eval`, which caches the generated
init to disk instead of forking a subshell per shell start.
Restructure to XDG. ~/.zshenv is now the only file in $HOME; it sets
ZDOTDIR to ~/.config/zsh and everything else moves under there, with
rc.d/*.zsh replacing ~/.zsh/*.zsh. znap's clones move from ~/.zsh_plugins
to ~/.local/share/znap. History moves to ~/.local/state/zsh/history, with
the existing 10494 lines carried over.
History config lives in .zshrc rather than .zshenv, which is not where it
looks like it belongs. macOS ships an /etc/zshrc that hardcodes
HISTFILE=${ZDOTDIR:-$HOME}/.zsh_history, HISTSIZE=2000 and SAVEHIST=1000,
and zsh sources it after .zshenv for every interactive shell. Setting these
in .zshenv looked correct and was silently overwritten; SAVEHIST had been
pinned at oh-my-zsh's 10000 floor rather than the intended value.
Replace p10k with starship, matching the eos-dotfiles and dungeonbooks
repos. The format string is listed explicitly so starship only evaluates
the named modules instead of probing for every language it supports.
kubectl and aws context are on the prompt since kubectl is by a wide margin
the most-used command in this shell's history.
Replace fnm with mise, which also absorbs the pyenv role removed earlier.
uv stays: mise manages interpreter versions, uv manages packages and venvs,
and mise defers to uv for venv creation when it is installed. Worth noting
that fnm's hook is genuinely faster than mise's (3.9ms vs 9.8ms measured
with hyperfine); the tradeoff is one tool for every runtime, and 6ms is
noise next to what was just removed.
Add atuin on Ctrl-R with the existing history imported (10001 commands).
Up and down arrows stay on prefix-aware history search, hence
--disable-up-arrow. Sync is off by default.
Swap alias-tips for zsh-you-should-use. alias-tips cost ~129ms in preexec,
so it was charged on every command rather than once at startup. YSU's
default three-line message is trimmed to a single "alias tip: gst" line.
Set ZSH_CACHE_DIR explicitly. The oh-my-zsh docker and kubectl plugins
cache generated completions there; the framework normally sets it, and
without it they tried to write to /completions and failed.
fnm is left in place as a fallback even though mise now handles node; it is a small formula and its shell hook is measurably faster if the mise tradeoff ever stops being worth it.
The first pass at this hand-built a p10k-style powerline and it was the wrong approach twice over. Starship has no segment concept, so every powerline background has to be maintained by hand, and naming modules explicitly in `format` means any module not listed never renders at all. That silently hid the gleam module in taranat.com. Starship's own default is `$all`, which cannot lose a module that way, so the explicit format is gone. It also cannot reproduce what made p10k's rainbow readable: p10k swapped the git segment's background from green to yellow on a dirty tree, and starship only makes foreground colours conditional. Separately, the arrows were never going to render. ghostty's config set no font-family at all, so it fell back to a system font with no Nerd Font glyphs. MesloLGS Nerd Font was already installed (p10k shipped it) and alacritty was already pointing at it; ghostty just had not been told. Note that `ghostty +list-fonts` does not enumerate user-installed fonts, which made the family look unavailable when it was not. "SF Mono Square" from the delphinus/sfmono-square tap is installed as an alternative. Keep Nerd Font glyphs for module symbols, but replace the starship defaults that are actual emoji rather than glyphs; gleam ships a star emoji and has no Nerd Font glyph, so it gets a text label. Disable docker_context: with a single context it printed "orbstack" in every directory containing a compose file. Leave the kubernetes module enabled even though the kubeconfig is now empty, so it comes back on its own if a cluster is added rather than being a module to remember to re-enable.
Switching to starship was not justified. The ~730ms that made startup slow was oh-my-zsh's gpg-agent plugin, not the prompt; with that gone, p10k starts in 0.17s against starship's 0.15s, so the prompt choice costs essentially nothing either way. Everything the earlier commits fixed stays fixed. p10k also does things starship structurally cannot. It swaps a segment's background colour on a dirty worktree, which is what makes the rainbow style readable and which starship cannot express -- only foreground colours are conditional there. It has transient prompt, already enabled in this config. And `p10k configure` is a wizard, rather than hand-editing TOML and guessing hex values, which is how the starship attempt went wrong. Upstream is in maintenance mode: no new features and most bugs unfixed, but the author is explicit that it is stable and not expected to break, which is a reasonable bet for a zsh theme. Track .p10k.zsh in the repo for the first time. It was 1734 lines living only in $HOME, so a fresh machine would have come up with an unconfigured prompt. It now sits in $ZDOTDIR alongside .zshrc. The instant prompt block goes at the very top of .zshrc, above everything including the history setup, since it replays a cached prompt and any earlier output or input would corrupt the replay. No separate INSTANT_PROMPT setting is needed in .zshrc because .p10k.zsh already sets it to quiet, which is what keeps the rc.d banner from being reported as unexpected console output. Remove the starship config. The formula stays installed for now.
The local .p10k.zsh was generated in 2022 and had drifted 68 lines from stock p10k-rainbow, of which only about ten were deliberate. Rather than patch the old file, this regenerates from the current template and ports those ten across, so four years of upstream changes come along too. That drift was hiding two bugs. The old config listed `.tool-version` as a directory anchor where stock has `.tool-versions` -- a missing "s", so the anchor never matched. And it predates mise entirely, so `.mise.toml` was absent from the same list. Ported: single-line prompt (no `newline` element either side, PROMPT_ADD_NEWLINE=false), the vi_mode segment, blanked visual identifiers on vcs/command_execution_time/time, Nerd Font battery stages, TRANSIENT_PROMPT=always, INSTANT_PROMPT=quiet, unconditional ahead/behind counts, and an empty DIR_CLASSES. DIR_CLASSES is the subtle one: assigning an empty array is not the same as leaving it unset. Unset, p10k falls back to default directory styling, which includes a folder icon. That icon appeared in the prompt until the empty assignment was restored. Gained from upstream: newer tool detection in the kubecontext, aws, azure and google_app_cred show-on-command lists (tofu, cdk, kubent, kubecolor, cmctl, sparkctl), a wider VPN interface pattern, and the chezmoi_shell segment, which will be useful shortly. Add a mise prompt segment. p10k has none built in, as it predates mise and only ships asdf. It parses the nearest .mise.toml or .tool-versions in pure zsh rather than shelling out: `mise current` measures at ~15ms, which is more than p10k's whole budget for a prompt and would be paid on every one. Reading the file is effectively free and shows what the project pins, which is the more useful information. The walk stops at /, and the global config lives at ~/.config/mise/config.toml rather than ~/.mise.toml, so the segment stays empty outside mise projects. Comment out the fifteen version-manager segments mise supersedes (asdf, pyenv, nodenv, nvm, rbenv and friends). Track the mise config, which existed only in $HOME. It had no [settings] at all; now it enables lockfile pinning, uv venv auto-activation (uv is the most-used tool in this shell's history), idiomatic version files for node and python, auto_install and env_cache. Remove starship, which the previous commit stopped using.
prompt_mise used ${line%%#*} to strip comments, which fails outright under
EXTENDED_GLOB: p10k enables that option, and with it a bare '#' is a pattern
operator, making '#*' an invalid pattern. Every call raised
"prompt_mise:14: bad pattern: #*", and because the failure happened during
prompt rendering it corrupted p10k's expansion and left raw internals like
${(e)_p9k_t[7]} on screen instead of a prompt. Escaping it as \# fixes both.
This was missed because the earlier test stubbed p10k and ran without
EXTENDED_GLOB set, so the pattern parsed fine there and only broke in a real
shell. The fixtures now set the option explicitly and cover .mise.toml,
.tool-versions, .nvmrc alone, both together, and no config at all.
Also read .nvmrc, .node-version and .python-version. mise honours these
because idiomatic_version_file_enable_tools is enabled for node and python,
so ignoring them under-reported what was active: guild pins node in .nvmrc
(where CI reads it) and uses mise.toml only for pnpm, so the segment showed
just pnpm while node was equally managed. Files at the same level are
merged, with the toml winning if it names the same tool twice.
Three problems, all found by capturing the real prompt through a pty rather than trusting a stubbed `p10k segment`. Icon names do not resolve. Passing -i NODE_ICON printed the literal string "NODE_ICON": unlike p10k's built-in segments, which hand an icon name to _p9k_prompt_segment for lookup, `p10k segment -i` emits its argument verbatim. Tool glyphs are now literal characters. Those glyphs are written as \u escapes rather than pasted in. They live in the Unicode private use area and an earlier attempt to embed them literally produced an array with 24 keys and 24 empty values -- the fallback to the plain tool name masked it, so the segment looked merely verbose rather than broken. Escapes also keep this file ASCII. Per-tool colours are not achievable and have been dropped. p10k resolves a segment's style from its name, so every `p10k segment` call inside prompt_mise picked up POWERLEVEL9K_MISE_* whatever -b said; the pty capture showed both segments rendering with background 7 despite being passed 34. The redundant calls also left a stray empty segment at the right edge, which was the unexplained gap there. It is one segment again, and tools without a glyph keep their name as a label. Background moves from 7 to 6. Seven was inherited from asdf and reads as a bland gray; cyan is distinct from the blue directory and green vcs segments on either side. Cap the tool list at four so a monorepo pinning many tools cannot run the prompt off the edge.
Replace the file-parsing mise segment with PATH scraping, adapted from 2KAbhishek/dots2k. mise activation prepends .../mise/installs/<tool>/<version>/bin, so the active versions are already in the environment: no config parsing, and it reports what mise actually resolved rather than what some file requests. That also fixes a bug the old approach could not. A stray ~/.nvmrc and ~/.node-version, both left over from fnm and both containing "24", meant the walk up the tree found them from any directory under $HOME, so every project appeared to pin node. Global versions are now cached once at startup from `mise ls --offline` and hidden when they match, so the segment shows only what a project actually overrides. ssh-bookshop is correctly empty again while guild still shows node 24.15.0 and pnpm 11.0.9. Per-tool colours now work, via -s to set a segment state: p10k resolves style from segment name plus state, so POWERLEVEL9K_MISE_<TOOL>_BACKGROUND applies. The earlier attempt passed -b, which always loses to the segment-level setting -- a pty capture showed both segments rendering with background 7 despite being handed 34. Those colours are palette indices 0-7 rather than 256-colour values. Indices are what the terminal theme defines, so they follow srcery; fixed values like 34 and 208 ignore the theme, which is why they looked foreign next to the surrounding segments. Glyphs are written as \u escape sequences. Embedding the literal characters failed three times: they sit in the Unicode private use area and were silently stripped to empty strings on write, leaving an array of 24 keys with 24 empty values. The fallback to the tool name hid it, so the segment looked merely verbose rather than broken. Update MesloLGS Nerd Font from the 2022 v2 build to the 2025 v3 build. The family name is unchanged, so ghostty needs no edit; the old files are archived in ~/Downloads/meslo-v2-backup.
Reclaims about 1.65GB and removes a hidden global node pin. ~/.nvmrc and ~/.node-version both contained "24" and were left over from fnm. Because idiomatic_version_file_enable_tools is enabled for node, mise honoured them, so every directory under $HOME inherited a node pin from $HOME itself -- which is why ssh-bookshop appeared to pin node 24 despite having no version file of its own. The mise prompt segment's hide-global logic already masked the symptom; this removes the cause. Deleted: ~/.local/share/fnm (1.0G of node builds), ~/.nvm (646M, from before fnm), ~/.local/state/fnm_multishells, and ~/.pyenv (empty shims and versions directories left behind when pyenv was uninstalled). Uninstall the fnm formula and drop it from the Brewfile; it had been kept as a fallback but mise has fully replaced it. Also remove ~/.npmrc. Its only content was a scoped registry pointing at a former employer's internal Nexus, which no longer resolves, so any install of a package in that scope would have hung. Small files are backed up to ~/Downloads/fnm-era-backup.
Explains what the mise prompt segment is doing: the cached global versions, each mise PATH entry raw and symlink-resolved, whether each is hidden or shown and why, and what the segment finally renders. It has to be a function rather than a script. The state it reports on lives in shell variables (_p9k_mise_global and the POWERLEVEL9K_MISE_* settings), which a separate process cannot see -- an earlier standalone script version reported an empty global map and so claimed every tool would be SHOWN, when the segment was in fact correctly hiding them.
The p10k config was a single 1928-line file with about a dozen local edits
scattered through it. That makes upstream updates effectively impossible to
review: regenerating produces a diff where personal settings and template
churn are indistinguishable, and it is easy to silently lose a customisation
-- which already happened once with DIR_CLASSES, whose absence put a folder
icon back in the prompt.
Split into prompt/, following the layout in 2KAbhishek/dots2k:
init.zsh sources the other three in order; later files win
p10k.zsh stock p10k-rainbow vendored verbatim, byte-identical to
upstream and never hand-edited
overrides.zsh every local customisation, 175 lines
mise.zsh the mise segment, which p10k has no equivalent for
Taking a p10k update is now `cp` over p10k.zsh, with any conflict showing up
as an error rather than a silent loss.
my_git_formatter is redefined in overrides.zsh rather than patched into the
vendored copy. The only difference from stock is that ahead/behind counts
print unconditionally, where stock wraps them in a conditional with an
`elif` that shows the remote branch name instead.
BATTERY_STAGES needs an explicit unset first. The template declares it as an
array and this config wants a scalar string of glyphs; both are valid to
p10k, but zsh refuses to change a variable's type in place and the
assignment failed with "inconsistent type for assignment".
Verified the rendered prompt is unchanged: guild still shows the branch plus
pnpm 11.0.9 and node 24.15.0, transient prompt, instant prompt, single-line
layout and empty DIR_CLASSES all survive, and startup is ~0.17s.
The [maintenance] section listed 30 repositories under ~/.zsh_plugins, a
path that no longer exists: the XDG restructure moved znap's clones to
~/.local/share/znap. Every entry was a dead path. No launchd job was
registered either, so `git maintenance` had not been running regardless.
znap manages plugin updates itself, so the section is removed rather than
repointed.
Add settings that pay for themselves on any rebase-heavy workflow:
rerere.enabled + autoUpdate record a conflict resolution once and replay
it when the same conflict reappears
rebase.updateRefs move stacked branches along with a rebase
instead of leaving them on pre-rebase commits
rebase.autoStash stash and restore a dirty worktree rather
than refusing to start
fetch.prune + pruneTags drop local refs for deleted remote branches
branch.sort = -committerdate most recent branches first
column.ui = auto multi-column listings
diff.algorithm = histogram clearer diffs than Myers when blocks move
diff.renames = copies detect copies as well as renames
Upgrade merge.conflictstyle from diff3 to zdiff3, which keeps the common
ancestor context but omits lines both sides already agree on, giving smaller
conflict hunks. Requires git >= 2.35; this machine is on 2.55.
Fold the stray second [diff] section into one. Duplicate sections are legal
and git merges them, but having diff.tool separated from the rest was only
going to confuse.
GPG commit signing is left as it is. Switching to SSH signing
(gpg.format = ssh) would avoid the GPG keyring, but the current setup works
and the existing key is already trusted on GitHub.
GitHub already has the SSH signing key registered, so this drops the GPG dependency for signing: no keyring, no gpg-agent, no pinentry prompt, and the same key that already authenticates to GitHub now signs. user.signingkey changes meaning with this: under gpg.format = ssh it is a path to a public key rather than a GPG key id. The old GPG key CCACDB1344B8B7EC0B4DD439655CBB5895152028 remains in this file's history if it is ever needed for verifying old commits. The [gpg] section name and `gpgsign = true` stay as they are. Those names are historical -- git kept them when it added other signing formats, so gpgsign with format = ssh means SSH-signed, not GPG-signed. Add an allowed_signers file mapping the identity to the key. Without it local verification fails with "No principal matched" even for signatures git has just created, because git cannot otherwise know which key may sign for which identity. It contains only public keys, so it is safe to commit. Verified end to end: a test commit signs and `git verify-commit` reports a good signature. Note the key is 3072-bit RSA from 2022. It works and GitHub accepts it, but ed25519 would be the modern choice; switching means generating a key and registering it as a signing key on GitHub. gpg itself is left installed, since gpg-agent.conf and pinentry-mac are still configured and may be used for things other than git.
The work submodule's dungeonbooks file was reached via a ../../../../work/dungeonbooks/zsh/.zsh/ relative symlink sitting inside rc.d. That path had to be recomputed by hand when the zsh config moved to XDG, and broke silently in between. It also meant a public repo contained a symlink whose target only resolves if a private submodule happens to be checked out. .zshrc now globs $ZDOTDIR/work/*.zsh as a second pass after rc.d, so work config can override the public config and simply does not load on a machine without access to the private repo. The work repo has been flattened to match: files live at its root rather than in a stow-shaped dungeonbooks/zsh/.zsh/ tree. Also drops rokt from that repo. It defined AWS SSO profiles, EKS kubeconfig commands and go run wrappers for a job left two years ago, against clusters already removed from the kubeconfig. ~/.config/zsh/work is currently a hand-made symlink to the submodule. The chezmoi migration will replace it with a .chezmoiexternal.toml entry that clones the private repo directly, at which point the submodule goes away.
fish is not installed and its config dates from 2022; the "fish" match in the Brewfile was a word inside atuin's description. Also removes a stray vim/.claude/settings.local.json, which was never tracked and is not config. alacritty.toml had silently diverged. Unlike every other package it was a real file in ~/.config rather than a stow symlink, so edits made on disk in October 2025 never reached this repo. The live copy uses [terminal.shell] and [terminal], the schema alacritty has wanted since 0.14 (0.17 is installed); the repo still had the pre-0.14 [shell]. Taking the live version here, since migrating from the repo copy would have silently reverted the config to a deprecated schema. Found by diffing every tracked file against its deployed counterpart before the chezmoi migration. It was the only genuine divergence.
Replaces per-package symlink farms with a single source tree that chezmoi renders into $HOME. The layout maps by filename rather than by position: dot_config/zsh/dot_zshrc becomes ~/.config/zsh/.zshrc, and executable_ marks files that need +x on disk. The practical difference is that a fresh machine is now two commands (chezmoi init --apply, then brew bundle) instead of an undocumented sequence of stow invocations plus hand-cloning znap. Both git submodules are retired in favour of .chezmoiexternal.toml entries. A submodule pins a SHA that must be committed here and needs --recurse-submodules on clone; externals are fetched on apply and refresh on their own schedule. znap is now an external too -- it had been cloned by hand, so a fresh machine would have come up with no plugins and no prompt, which is exactly the sort of undocumented step this migration removes. Verified before switching by building the source tree in a scratch directory and comparing every managed file's rendered content against the live copy: all 58 were byte-identical. Every path still shows in `chezmoi diff` because stow deployed symlinks and chezmoi writes regular files, so the type changes even where content does not. Post-apply checks: shell starts clean in ~0.17s, ZDOTDIR and HISTFILE resolve, node comes from mise, the p10k prompt and mise segment load, atuin holds Ctrl-R, and the private work aliases load from the external. Add a README covering the bootstrap, the layout, the prompt split, the externals and the edit-source-not-target workflow.
Nothing uses it now that chezmoi manages the tree.
The zshrc/zshalias/vimrc family opened the deployed file directly. Under chezmoi that edits a rendered artifact: it works until the next apply silently overwrites it. They now go through `chezmoi edit --apply`, which opens the source and writes the result out in one step. Adds the usual chezmoi shortcuts (cm, cma, cmd, cme, cmu, cms, cmcd, cmadd) plus zshprompt, gitconfig and brewfile, which had no alias before.
`pgrep -x ghostty` could never succeed. macOS truncates a process name to 16 characters, so Ghostty appears as "/Applications/Gh" and the exact-name match always failed. The binding fell through to `open -a`, which focuses the existing window rather than opening a new one -- so alt-enter appeared to stop working. Match on the bundle path instead, including Contents/MacOS. The narrower path matters: a looser `pgrep -f Ghostty.app` matches the full command line including environment, and every process launched from a Ghostty terminal inherits GHOSTTY_RESOURCES_DIR pointing at Contents/Resources. That made an unrelated npm process match during testing, so Ghostty would have looked like it was running long after being quit. The AppleScript half needed no change: System Events still refers to the process as "ghostty" regardless of the truncated name.
Adds rc.d/05-completion.zsh. fzf-tab was already loaded but had no zstyles,
so it replaced the completion menu with fzf and then showed nothing in the
preview pane, which is most of the point. Previews are now wired per command:
directories list through eza, files render through bat, and git dispatches on
the completion group so that tab-completing `git checkout` previews a branch's
log, a modified file's diff through delta, or a commit's contents, depending
on what is under the cursor. Also covers chezmoi (shows what applying would
change), mise, brew and kill.
Adds rc.d/04-functions.zsh:
review browse everything changed on this branch, diff in the
preview pane, enter opens the file
_git_run_on_files run a command over only the changed files, split by
"diff" (this branch vs its base) and "modified" (dirty
worktree) -- the two questions have different answers
lintjs/lintpy/... thin wrappers over the above per toolchain
search rg to fzf to $EDITOR, opening at the matched line
plz, xin sudo the last command; run something in another dir
The base branch is resolved from origin/HEAD rather than hardcoded to main,
falling back to asking the remote and then to conventional names, so repos on
master or develop work unchanged.
Adds aube to the mise global config, pinned to major version 1 rather than
"latest" so it cannot shift under a project mid-work.
Startup goes from ~0.17s to ~0.20s, which is the completion zstyles being
evaluated at load.
Owner
Author
This stack of pull requests is managed by Graphite. Learn more about stacking. |
`[url "git@github.com:"] insteadOf = https://github.com/` came from an old "git: force ssh" commit and rewrote every GitHub HTTPS URL to SSH. It now costs more than it gives. Every tool that clones from GitHub over HTTPS -- lazy.nvim plugins, mason servers, go modules, npm git dependencies, cargo -- became an SSH operation needing an unlocked agent, which is why a neovim plugin install printed "sign_and_send_pubkey: signing failed for RSA" for most of its forty plugins. It also breaks the bootstrap this repo now documents: a fresh machine cannot clone anything from GitHub until keys are generated and registered, where public HTTPS clones need no auth at all. It is also largely redundant. credential.helper = gh auth git-credential, added since, handles HTTPS auth for private repos. Nothing of ours changes: this repo's origin and the private work external in .chezmoiexternal.toml are both written as git@github.com: URLs, so they stay on SSH regardless.
Owner
Author
ptaranat
added a commit
that referenced
this pull request
Jul 31, 2026
) chore: add Brewfile, prune unused packages, rename claude alias Rename the Claude Code alias cc -> cl. As `cc` it shadowed the C compiler outright: `command -v cc` resolved to the alias rather than /usr/bin/cc, which breaks any ./configure, make, or native gem/npm build that shells out to cc. Add a Brewfile so a fresh machine can be provisioned with `brew bundle`. Generated from the installed state after auditing every formula against actual invocation counts in ~/.zsh_history (with shell aliases resolved, so `ls`->eza and `k`->kubectl are credited to the right package). Drop 16 formulae that the audit found were never invoked and that nothing else depends on: buildifier, cffi, ffmpeg, fontforge, git-filter-repo, librsvg, meson, mupdf, mysql@8.0, poppler, qemu, yq, docutils, sphinx-doc, gobject-introspection and pyenv. Removing them let brew autoremove reclaim their orphaned dependencies too, taking the formula count from 91 to 75. pyenv is the notable one: zero invocations against 253 for uv, yet 01-environment.zsh still ran both `pyenv init --path` and `pyenv init -` on every shell start. The zsh side of that is removed separately. Kept several packages the audit scored at zero, because they are invoked by other tools rather than typed: git-delta (gitconfig sets it as pager), tree-sitter-cli (nvim-treesitter compiles parsers with it), gopls, shellcheck and rust. Declare the cursor and visual-studio-code casks. Both were installed by hand, so the 51 vscode extension entries had no editor to install into on a clean machine. fix(zsh): drop pyenv, remove dead config, guard gt completions pyenv was uninstalled in the previous commit but 01-environment.zsh still ran `pyenv init --path` and `pyenv init -`, so every new shell printed two "command not found: pyenv" errors. Remove both evals along with PYENV_ROOT and its $PATH entry. Drop PYTHONPATH. It was set to /opt/homebrew/bin/python3, which is an interpreter, not a module search path. Python treats PYTHONPATH entries as directories to search for imports, so pointing it at a binary contributed nothing and risked confusing imports. Drop the ~/Library/Python/3.9/bin path entry: that is the system Python 3.9 user-scripts directory, unused now that uv manages Python. Remove the commented-out NVM block and NVM_DIR export. nvm is not installed; fnm has replaced it. Wrap the pasted-in Graphite (gt) yargs completion block in a `(( $+commands[gt] ))` guard, and fix its embedded literal newline, which had been pasted in as a raw line break inside the IFS assignment. refactor(zsh): XDG layout, starship, mise and atuin; 6x faster startup Startup drops from ~0.90s to ~0.15s. zprof attributed 731ms of that, 69.5% of the total, to a single plugin: oh-my-zsh's gpg-agent, which shells out to gpg-connect-agent on every start. It was redundant anyway. The old .zshrc already exported GPG_TTY itself and pinentry-mac is configured in gpg-agent.conf, so the plugin is dropped and GPG_TTY kept. The rest of the win comes from loading oh-my-zsh a la carte, and from wrapping every `<tool> init` in `znap eval`, which caches the generated init to disk instead of forking a subshell per shell start. Restructure to XDG. ~/.zshenv is now the only file in $HOME; it sets ZDOTDIR to ~/.config/zsh and everything else moves under there, with rc.d/*.zsh replacing ~/.zsh/*.zsh. znap's clones move from ~/.zsh_plugins to ~/.local/share/znap. History moves to ~/.local/state/zsh/history, with the existing 10494 lines carried over. History config lives in .zshrc rather than .zshenv, which is not where it looks like it belongs. macOS ships an /etc/zshrc that hardcodes HISTFILE=${ZDOTDIR:-$HOME}/.zsh_history, HISTSIZE=2000 and SAVEHIST=1000, and zsh sources it after .zshenv for every interactive shell. Setting these in .zshenv looked correct and was silently overwritten; SAVEHIST had been pinned at oh-my-zsh's 10000 floor rather than the intended value. Replace p10k with starship, matching the eos-dotfiles and dungeonbooks repos. The format string is listed explicitly so starship only evaluates the named modules instead of probing for every language it supports. kubectl and aws context are on the prompt since kubectl is by a wide margin the most-used command in this shell's history. Replace fnm with mise, which also absorbs the pyenv role removed earlier. uv stays: mise manages interpreter versions, uv manages packages and venvs, and mise defers to uv for venv creation when it is installed. Worth noting that fnm's hook is genuinely faster than mise's (3.9ms vs 9.8ms measured with hyperfine); the tradeoff is one tool for every runtime, and 6ms is noise next to what was just removed. Add atuin on Ctrl-R with the existing history imported (10001 commands). Up and down arrows stay on prefix-aware history search, hence --disable-up-arrow. Sync is off by default. Swap alias-tips for zsh-you-should-use. alias-tips cost ~129ms in preexec, so it was charged on every command rather than once at startup. YSU's default three-line message is trimmed to a single "alias tip: gst" line. Set ZSH_CACHE_DIR explicitly. The oh-my-zsh docker and kubectl plugins cache generated completions there; the framework normally sets it, and without it they tried to write to /completions and failed. chore(brew): add mise, atuin, starship and hyperfine to Brewfile fnm is left in place as a fallback even though mise now handles node; it is a small formula and its shell hook is measurably faster if the mise tradeoff ever stops being worth it. fix(prompt): use starship defaults, set a Nerd Font in ghostty The first pass at this hand-built a p10k-style powerline and it was the wrong approach twice over. Starship has no segment concept, so every powerline background has to be maintained by hand, and naming modules explicitly in `format` means any module not listed never renders at all. That silently hid the gleam module in taranat.com. Starship's own default is `$all`, which cannot lose a module that way, so the explicit format is gone. It also cannot reproduce what made p10k's rainbow readable: p10k swapped the git segment's background from green to yellow on a dirty tree, and starship only makes foreground colours conditional. Separately, the arrows were never going to render. ghostty's config set no font-family at all, so it fell back to a system font with no Nerd Font glyphs. MesloLGS Nerd Font was already installed (p10k shipped it) and alacritty was already pointing at it; ghostty just had not been told. Note that `ghostty +list-fonts` does not enumerate user-installed fonts, which made the family look unavailable when it was not. "SF Mono Square" from the delphinus/sfmono-square tap is installed as an alternative. Keep Nerd Font glyphs for module symbols, but replace the starship defaults that are actual emoji rather than glyphs; gleam ships a star emoji and has no Nerd Font glyph, so it gets a text label. Disable docker_context: with a single context it printed "orbstack" in every directory containing a compose file. Leave the kubernetes module enabled even though the kubeconfig is now empty, so it comes back on its own if a cluster is added rather than being a module to remember to re-enable. revert(prompt): back to powerlevel10k, and track .p10k.zsh Switching to starship was not justified. The ~730ms that made startup slow was oh-my-zsh's gpg-agent plugin, not the prompt; with that gone, p10k starts in 0.17s against starship's 0.15s, so the prompt choice costs essentially nothing either way. Everything the earlier commits fixed stays fixed. p10k also does things starship structurally cannot. It swaps a segment's background colour on a dirty worktree, which is what makes the rainbow style readable and which starship cannot express -- only foreground colours are conditional there. It has transient prompt, already enabled in this config. And `p10k configure` is a wizard, rather than hand-editing TOML and guessing hex values, which is how the starship attempt went wrong. Upstream is in maintenance mode: no new features and most bugs unfixed, but the author is explicit that it is stable and not expected to break, which is a reasonable bet for a zsh theme. Track .p10k.zsh in the repo for the first time. It was 1734 lines living only in $HOME, so a fresh machine would have come up with an unconfigured prompt. It now sits in $ZDOTDIR alongside .zshrc. The instant prompt block goes at the very top of .zshrc, above everything including the history setup, since it replays a cached prompt and any earlier output or input would corrupt the replay. No separate INSTANT_PROMPT setting is needed in .zshrc because .p10k.zsh already sets it to quiet, which is what keeps the rc.d banner from being reported as unexpected console output. Remove the starship config. The formula stays installed for now. feat(p10k): rebase config on current template, add a mise segment The local .p10k.zsh was generated in 2022 and had drifted 68 lines from stock p10k-rainbow, of which only about ten were deliberate. Rather than patch the old file, this regenerates from the current template and ports those ten across, so four years of upstream changes come along too. That drift was hiding two bugs. The old config listed `.tool-version` as a directory anchor where stock has `.tool-versions` -- a missing "s", so the anchor never matched. And it predates mise entirely, so `.mise.toml` was absent from the same list. Ported: single-line prompt (no `newline` element either side, PROMPT_ADD_NEWLINE=false), the vi_mode segment, blanked visual identifiers on vcs/command_execution_time/time, Nerd Font battery stages, TRANSIENT_PROMPT=always, INSTANT_PROMPT=quiet, unconditional ahead/behind counts, and an empty DIR_CLASSES. DIR_CLASSES is the subtle one: assigning an empty array is not the same as leaving it unset. Unset, p10k falls back to default directory styling, which includes a folder icon. That icon appeared in the prompt until the empty assignment was restored. Gained from upstream: newer tool detection in the kubecontext, aws, azure and google_app_cred show-on-command lists (tofu, cdk, kubent, kubecolor, cmctl, sparkctl), a wider VPN interface pattern, and the chezmoi_shell segment, which will be useful shortly. Add a mise prompt segment. p10k has none built in, as it predates mise and only ships asdf. It parses the nearest .mise.toml or .tool-versions in pure zsh rather than shelling out: `mise current` measures at ~15ms, which is more than p10k's whole budget for a prompt and would be paid on every one. Reading the file is effectively free and shows what the project pins, which is the more useful information. The walk stops at /, and the global config lives at ~/.config/mise/config.toml rather than ~/.mise.toml, so the segment stays empty outside mise projects. Comment out the fifteen version-manager segments mise supersedes (asdf, pyenv, nodenv, nvm, rbenv and friends). Track the mise config, which existed only in $HOME. It had no [settings] at all; now it enables lockfile pinning, uv venv auto-activation (uv is the most-used tool in this shell's history), idiomatic version files for node and python, auto_install and env_cache. Remove starship, which the previous commit stopped using. fix(p10k): escape '#' in prompt_mise, and read idiomatic version files prompt_mise used ${line%%#*} to strip comments, which fails outright under EXTENDED_GLOB: p10k enables that option, and with it a bare '#' is a pattern operator, making '#*' an invalid pattern. Every call raised "prompt_mise:14: bad pattern: #*", and because the failure happened during prompt rendering it corrupted p10k's expansion and left raw internals like ${(e)_p9k_t[7]} on screen instead of a prompt. Escaping it as \# fixes both. This was missed because the earlier test stubbed p10k and ran without EXTENDED_GLOB set, so the pattern parsed fine there and only broke in a real shell. The fixtures now set the option explicitly and cover .mise.toml, .tool-versions, .nvmrc alone, both together, and no config at all. Also read .nvmrc, .node-version and .python-version. mise honours these because idiomatic_version_file_enable_tools is enabled for node and python, so ignoring them under-reported what was active: guild pins node in .nvmrc (where CI reads it) and uses mise.toml only for pnpm, so the segment showed just pnpm while node was equally managed. Files at the same level are merged, with the toml winning if it names the same tool twice. fix(p10k): render mise segment with glyphs, cyan, one segment Three problems, all found by capturing the real prompt through a pty rather than trusting a stubbed `p10k segment`. Icon names do not resolve. Passing -i NODE_ICON printed the literal string "NODE_ICON": unlike p10k's built-in segments, which hand an icon name to _p9k_prompt_segment for lookup, `p10k segment -i` emits its argument verbatim. Tool glyphs are now literal characters. Those glyphs are written as \u escapes rather than pasted in. They live in the Unicode private use area and an earlier attempt to embed them literally produced an array with 24 keys and 24 empty values -- the fallback to the plain tool name masked it, so the segment looked merely verbose rather than broken. Escapes also keep this file ASCII. Per-tool colours are not achievable and have been dropped. p10k resolves a segment's style from its name, so every `p10k segment` call inside prompt_mise picked up POWERLEVEL9K_MISE_* whatever -b said; the pty capture showed both segments rendering with background 7 despite being passed 34. The redundant calls also left a stray empty segment at the right edge, which was the unexplained gap there. It is one segment again, and tools without a glyph keep their name as a label. Background moves from 7 to 6. Seven was inherited from asdf and reads as a bland gray; cyan is distinct from the blue directory and green vcs segments on either side. Cap the tool list at four so a monorepo pinning many tools cannot run the prompt off the edge. refactor(p10k): read mise versions from $path, theme-aware colours Replace the file-parsing mise segment with PATH scraping, adapted from 2KAbhishek/dots2k. mise activation prepends .../mise/installs/<tool>/<version>/bin, so the active versions are already in the environment: no config parsing, and it reports what mise actually resolved rather than what some file requests. That also fixes a bug the old approach could not. A stray ~/.nvmrc and ~/.node-version, both left over from fnm and both containing "24", meant the walk up the tree found them from any directory under $HOME, so every project appeared to pin node. Global versions are now cached once at startup from `mise ls --offline` and hidden when they match, so the segment shows only what a project actually overrides. ssh-bookshop is correctly empty again while guild still shows node 24.15.0 and pnpm 11.0.9. Per-tool colours now work, via -s to set a segment state: p10k resolves style from segment name plus state, so POWERLEVEL9K_MISE_<TOOL>_BACKGROUND applies. The earlier attempt passed -b, which always loses to the segment-level setting -- a pty capture showed both segments rendering with background 7 despite being handed 34. Those colours are palette indices 0-7 rather than 256-colour values. Indices are what the terminal theme defines, so they follow srcery; fixed values like 34 and 208 ignore the theme, which is why they looked foreign next to the surrounding segments. Glyphs are written as \u escape sequences. Embedding the literal characters failed three times: they sit in the Unicode private use area and were silently stripped to empty strings on write, leaving an array of 24 keys with 24 empty values. The fallback to the tool name hid it, so the segment looked merely verbose rather than broken. Update MesloLGS Nerd Font from the 2022 v2 build to the 2025 v3 build. The family name is unchanged, so ghostty needs no edit; the old files are archived in ~/Downloads/meslo-v2-backup. chore: remove fnm-era leftovers Reclaims about 1.65GB and removes a hidden global node pin. ~/.nvmrc and ~/.node-version both contained "24" and were left over from fnm. Because idiomatic_version_file_enable_tools is enabled for node, mise honoured them, so every directory under $HOME inherited a node pin from $HOME itself -- which is why ssh-bookshop appeared to pin node 24 despite having no version file of its own. The mise prompt segment's hide-global logic already masked the symptom; this removes the cause. Deleted: ~/.local/share/fnm (1.0G of node builds), ~/.nvm (646M, from before fnm), ~/.local/state/fnm_multishells, and ~/.pyenv (empty shims and versions directories left behind when pyenv was uninstalled). Uninstall the fnm formula and drop it from the Brewfile; it had been kept as a fallback but mise has fully replaced it. Also remove ~/.npmrc. Its only content was a scoped registry pointing at a former employer's internal Nexus, which no longer resolves, so any install of a package in that scope would have hung. Small files are backed up to ~/Downloads/fnm-era-backup. feat(zsh): add mise-prompt-debug as a shell function Explains what the mise prompt segment is doing: the cached global versions, each mise PATH entry raw and symlink-resolved, whether each is hidden or shown and why, and what the segment finally renders. It has to be a function rather than a script. The state it reports on lives in shell variables (_p9k_mise_global and the POWERLEVEL9K_MISE_* settings), which a separate process cannot see -- an earlier standalone script version reported an empty global map and so claimed every tool would be SHOWN, when the segment was in fact correctly hiding them. refactor(p10k): split config into vendored template plus overrides The p10k config was a single 1928-line file with about a dozen local edits scattered through it. That makes upstream updates effectively impossible to review: regenerating produces a diff where personal settings and template churn are indistinguishable, and it is easy to silently lose a customisation -- which already happened once with DIR_CLASSES, whose absence put a folder icon back in the prompt. Split into prompt/, following the layout in 2KAbhishek/dots2k: init.zsh sources the other three in order; later files win p10k.zsh stock p10k-rainbow vendored verbatim, byte-identical to upstream and never hand-edited overrides.zsh every local customisation, 175 lines mise.zsh the mise segment, which p10k has no equivalent for Taking a p10k update is now `cp` over p10k.zsh, with any conflict showing up as an error rather than a silent loss. my_git_formatter is redefined in overrides.zsh rather than patched into the vendored copy. The only difference from stock is that ahead/behind counts print unconditionally, where stock wraps them in a conditional with an `elif` that shows the remote branch name instead. BATTERY_STAGES needs an explicit unset first. The template declares it as an array and this config wants a scalar string of glyphs; both are valid to p10k, but zsh refuses to change a variable's type in place and the assignment failed with "inconsistent type for assignment". Verified the rendered prompt is unchanged: guild still shows the branch plus pnpm 11.0.9 and node 24.15.0, transient prompt, instant prompt, single-line layout and empty DIR_CLASSES all survive, and startup is ~0.17s. chore(git): modern defaults, drop dead maintenance repo list The [maintenance] section listed 30 repositories under ~/.zsh_plugins, a path that no longer exists: the XDG restructure moved znap's clones to ~/.local/share/znap. Every entry was a dead path. No launchd job was registered either, so `git maintenance` had not been running regardless. znap manages plugin updates itself, so the section is removed rather than repointed. Add settings that pay for themselves on any rebase-heavy workflow: rerere.enabled + autoUpdate record a conflict resolution once and replay it when the same conflict reappears rebase.updateRefs move stacked branches along with a rebase instead of leaving them on pre-rebase commits rebase.autoStash stash and restore a dirty worktree rather than refusing to start fetch.prune + pruneTags drop local refs for deleted remote branches branch.sort = -committerdate most recent branches first column.ui = auto multi-column listings diff.algorithm = histogram clearer diffs than Myers when blocks move diff.renames = copies detect copies as well as renames Upgrade merge.conflictstyle from diff3 to zdiff3, which keeps the common ancestor context but omits lines both sides already agree on, giving smaller conflict hunks. Requires git >= 2.35; this machine is on 2.55. Fold the stray second [diff] section into one. Duplicate sections are legal and git merges them, but having diff.tool separated from the rest was only going to confuse. GPG commit signing is left as it is. Switching to SSH signing (gpg.format = ssh) would avoid the GPG keyring, but the current setup works and the existing key is already trusted on GitHub. chore(git): sign commits with SSH instead of GPG GitHub already has the SSH signing key registered, so this drops the GPG dependency for signing: no keyring, no gpg-agent, no pinentry prompt, and the same key that already authenticates to GitHub now signs. user.signingkey changes meaning with this: under gpg.format = ssh it is a path to a public key rather than a GPG key id. The old GPG key CCACDB1344B8B7EC0B4DD439655CBB5895152028 remains in this file's history if it is ever needed for verifying old commits. The [gpg] section name and `gpgsign = true` stay as they are. Those names are historical -- git kept them when it added other signing formats, so gpgsign with format = ssh means SSH-signed, not GPG-signed. Add an allowed_signers file mapping the identity to the key. Without it local verification fails with "No principal matched" even for signatures git has just created, because git cannot otherwise know which key may sign for which identity. It contains only public keys, so it is safe to commit. Verified end to end: a test commit signs and `git verify-commit` reports a good signature. Note the key is 3072-bit RSA from 2022. It works and GitHub accepts it, but ed25519 would be the modern choice; switching means generating a key and registering it as a signing key on GitHub. gpg itself is left installed, since gpg-agent.conf and pinentry-mac are still configured and may be used for things other than git. refactor(zsh): source work config from a directory, not a symlink The work submodule's dungeonbooks file was reached via a ../../../../work/dungeonbooks/zsh/.zsh/ relative symlink sitting inside rc.d. That path had to be recomputed by hand when the zsh config moved to XDG, and broke silently in between. It also meant a public repo contained a symlink whose target only resolves if a private submodule happens to be checked out. .zshrc now globs $ZDOTDIR/work/*.zsh as a second pass after rc.d, so work config can override the public config and simply does not load on a machine without access to the private repo. The work repo has been flattened to match: files live at its root rather than in a stow-shaped dungeonbooks/zsh/.zsh/ tree. Also drops rokt from that repo. It defined AWS SSO profiles, EKS kubeconfig commands and go run wrappers for a job left two years ago, against clusters already removed from the kubeconfig. ~/.config/zsh/work is currently a hand-made symlink to the submodule. The chezmoi migration will replace it with a .chezmoiexternal.toml entry that clones the private repo directly, at which point the submodule goes away. chore: drop fish, sync alacritty config from disk fish is not installed and its config dates from 2022; the "fish" match in the Brewfile was a word inside atuin's description. Also removes a stray vim/.claude/settings.local.json, which was never tracked and is not config. alacritty.toml had silently diverged. Unlike every other package it was a real file in ~/.config rather than a stow symlink, so edits made on disk in October 2025 never reached this repo. The live copy uses [terminal.shell] and [terminal], the schema alacritty has wanted since 0.14 (0.17 is installed); the repo still had the pre-0.14 [shell]. Taking the live version here, since migrating from the repo copy would have silently reverted the config to a deprecated schema. Found by diffing every tracked file against its deployed counterpart before the chezmoi migration. It was the only genuine divergence. feat: migrate from GNU stow to chezmoi Replaces per-package symlink farms with a single source tree that chezmoi renders into $HOME. The layout maps by filename rather than by position: dot_config/zsh/dot_zshrc becomes ~/.config/zsh/.zshrc, and executable_ marks files that need +x on disk. The practical difference is that a fresh machine is now two commands (chezmoi init --apply, then brew bundle) instead of an undocumented sequence of stow invocations plus hand-cloning znap. Both git submodules are retired in favour of .chezmoiexternal.toml entries. A submodule pins a SHA that must be committed here and needs --recurse-submodules on clone; externals are fetched on apply and refresh on their own schedule. znap is now an external too -- it had been cloned by hand, so a fresh machine would have come up with no plugins and no prompt, which is exactly the sort of undocumented step this migration removes. Verified before switching by building the source tree in a scratch directory and comparing every managed file's rendered content against the live copy: all 58 were byte-identical. Every path still shows in `chezmoi diff` because stow deployed symlinks and chezmoi writes regular files, so the type changes even where content does not. Post-apply checks: shell starts clean in ~0.17s, ZDOTDIR and HISTFILE resolve, node comes from mise, the p10k prompt and mise segment load, atuin holds Ctrl-R, and the private work aliases load from the external. Add a README covering the bootstrap, the layout, the prompt split, the externals and the edit-source-not-target workflow. chore: remove stow Nothing uses it now that chezmoi manages the tree. feat(zsh): point config-edit aliases at chezmoi The zshrc/zshalias/vimrc family opened the deployed file directly. Under chezmoi that edits a rendered artifact: it works until the next apply silently overwrites it. They now go through `chezmoi edit --apply`, which opens the source and writes the result out in one step. Adds the usual chezmoi shortcuts (cm, cma, cmd, cme, cmu, cms, cmcd, cmadd) plus zshprompt, gitconfig and brewfile, which had no alias before. fix(aerospace): make the alt-enter ghostty check actually match `pgrep -x ghostty` could never succeed. macOS truncates a process name to 16 characters, so Ghostty appears as "/Applications/Gh" and the exact-name match always failed. The binding fell through to `open -a`, which focuses the existing window rather than opening a new one -- so alt-enter appeared to stop working. Match on the bundle path instead, including Contents/MacOS. The narrower path matters: a looser `pgrep -f Ghostty.app` matches the full command line including environment, and every process launched from a Ghostty terminal inherits GHOSTTY_RESOURCES_DIR pointing at Contents/Resources. That made an unrelated npm process match during testing, so Ghostty would have looked like it was running long after being quit. The AppleScript half needed no change: System Events still refers to the process as "ghostty" regardless of the truncated name. feat(zsh): fzf-tab previews, git workflow functions, aube Adds rc.d/05-completion.zsh. fzf-tab was already loaded but had no zstyles, so it replaced the completion menu with fzf and then showed nothing in the preview pane, which is most of the point. Previews are now wired per command: directories list through eza, files render through bat, and git dispatches on the completion group so that tab-completing `git checkout` previews a branch's log, a modified file's diff through delta, or a commit's contents, depending on what is under the cursor. Also covers chezmoi (shows what applying would change), mise, brew and kill. Adds rc.d/04-functions.zsh: review browse everything changed on this branch, diff in the preview pane, enter opens the file _git_run_on_files run a command over only the changed files, split by "diff" (this branch vs its base) and "modified" (dirty worktree) -- the two questions have different answers lintjs/lintpy/... thin wrappers over the above per toolchain search rg to fzf to $EDITOR, opening at the matched line plz, xin sudo the last command; run something in another dir The base branch is resolved from origin/HEAD rather than hardcoded to main, falling back to asking the remote and then to conventional names, so repos on master or develop work unchanged. Adds aube to the mise global config, pinned to major version 1 rather than "latest" so it cannot shift under a project mid-work. Startup goes from ~0.17s to ~0.20s, which is the completion zstyles being evaluated at load.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

chore: add Brewfile, prune unused packages, rename claude alias
Rename the Claude Code alias cc -> cl. As
ccit shadowed the C compileroutright:
command -v ccresolved to the alias rather than /usr/bin/cc,which breaks any ./configure, make, or native gem/npm build that shells
out to cc.
Add a Brewfile so a fresh machine can be provisioned with
brew bundle.Generated from the installed state after auditing every formula against
actual invocation counts in ~/.zsh_history (with shell aliases resolved,
so
ls->eza andk->kubectl are credited to the right package).Drop 16 formulae that the audit found were never invoked and that nothing
else depends on: buildifier, cffi, ffmpeg, fontforge, git-filter-repo,
librsvg, meson, mupdf, mysql@8.0, poppler, qemu, yq, docutils, sphinx-doc,
gobject-introspection and pyenv. Removing them let brew autoremove reclaim
their orphaned dependencies too, taking the formula count from 91 to 75.
pyenv is the notable one: zero invocations against 253 for uv, yet
01-environment.zsh still ran both
pyenv init --pathandpyenv init -on every shell start. The zsh side of that is removed separately.
Kept several packages the audit scored at zero, because they are invoked
by other tools rather than typed: git-delta (gitconfig sets it as pager),
tree-sitter-cli (nvim-treesitter compiles parsers with it), gopls,
shellcheck and rust.
Declare the cursor and visual-studio-code casks. Both were installed by
hand, so the 51 vscode extension entries had no editor to install into on
a clean machine.
fix(zsh): drop pyenv, remove dead config, guard gt completions
pyenv was uninstalled in the previous commit but 01-environment.zsh still
ran
pyenv init --pathandpyenv init -, so every new shell printed two"command not found: pyenv" errors. Remove both evals along with PYENV_ROOT
and its $PATH entry.
Drop PYTHONPATH. It was set to /opt/homebrew/bin/python3, which is an
interpreter, not a module search path. Python treats PYTHONPATH entries as
directories to search for imports, so pointing it at a binary contributed
nothing and risked confusing imports.
Drop the ~/Library/Python/3.9/bin path entry: that is the system Python
3.9 user-scripts directory, unused now that uv manages Python.
Remove the commented-out NVM block and NVM_DIR export. nvm is not
installed; fnm has replaced it.
Wrap the pasted-in Graphite (gt) yargs completion block in a
(( $+commands[gt] ))guard, and fix its embedded literal newline, whichhad been pasted in as a raw line break inside the IFS assignment.
refactor(zsh): XDG layout, starship, mise and atuin; 6x faster startup
Startup drops from ~0.90s to ~0.15s. zprof attributed 731ms of that, 69.5%
of the total, to a single plugin: oh-my-zsh's gpg-agent, which shells out to
gpg-connect-agent on every start. It was redundant anyway. The old .zshrc
already exported GPG_TTY itself and pinentry-mac is configured in
gpg-agent.conf, so the plugin is dropped and GPG_TTY kept.
The rest of the win comes from loading oh-my-zsh a la carte, and from
wrapping every
<tool> initinznap eval, which caches the generatedinit to disk instead of forking a subshell per shell start.
Restructure to XDG. ~/.zshenv is now the only file in $HOME; it sets
ZDOTDIR to ~/.config/zsh and everything else moves under there, with
rc.d/.zsh replacing ~/.zsh/.zsh. znap's clones move from ~/.zsh_plugins
to ~/.local/share/znap. History moves to ~/.local/state/zsh/history, with
the existing 10494 lines carried over.
History config lives in .zshrc rather than .zshenv, which is not where it
looks like it belongs. macOS ships an /etc/zshrc that hardcodes
HISTFILE=${ZDOTDIR:-$HOME}/.zsh_history, HISTSIZE=2000 and SAVEHIST=1000,
and zsh sources it after .zshenv for every interactive shell. Setting these
in .zshenv looked correct and was silently overwritten; SAVEHIST had been
pinned at oh-my-zsh's 10000 floor rather than the intended value.
Replace p10k with starship, matching the eos-dotfiles and dungeonbooks
repos. The format string is listed explicitly so starship only evaluates
the named modules instead of probing for every language it supports.
kubectl and aws context are on the prompt since kubectl is by a wide margin
the most-used command in this shell's history.
Replace fnm with mise, which also absorbs the pyenv role removed earlier.
uv stays: mise manages interpreter versions, uv manages packages and venvs,
and mise defers to uv for venv creation when it is installed. Worth noting
that fnm's hook is genuinely faster than mise's (3.9ms vs 9.8ms measured
with hyperfine); the tradeoff is one tool for every runtime, and 6ms is
noise next to what was just removed.
Add atuin on Ctrl-R with the existing history imported (10001 commands).
Up and down arrows stay on prefix-aware history search, hence
--disable-up-arrow. Sync is off by default.
Swap alias-tips for zsh-you-should-use. alias-tips cost ~129ms in preexec,
so it was charged on every command rather than once at startup. YSU's
default three-line message is trimmed to a single "alias tip: gst" line.
Set ZSH_CACHE_DIR explicitly. The oh-my-zsh docker and kubectl plugins
cache generated completions there; the framework normally sets it, and
without it they tried to write to /completions and failed.
chore(brew): add mise, atuin, starship and hyperfine to Brewfile
fnm is left in place as a fallback even though mise now handles node; it
is a small formula and its shell hook is measurably faster if the mise
tradeoff ever stops being worth it.
fix(prompt): use starship defaults, set a Nerd Font in ghostty
The first pass at this hand-built a p10k-style powerline and it was the
wrong approach twice over.
Starship has no segment concept, so every powerline background has to be
maintained by hand, and naming modules explicitly in
formatmeans anymodule not listed never renders at all. That silently hid the gleam module
in taranat.com. Starship's own default is
$all, which cannot lose amodule that way, so the explicit format is gone.
It also cannot reproduce what made p10k's rainbow readable: p10k swapped
the git segment's background from green to yellow on a dirty tree, and
starship only makes foreground colours conditional.
Separately, the arrows were never going to render. ghostty's config set no
font-family at all, so it fell back to a system font with no Nerd Font
glyphs. MesloLGS Nerd Font was already installed (p10k shipped it) and
alacritty was already pointing at it; ghostty just had not been told.
Note that
ghostty +list-fontsdoes not enumerate user-installed fonts,which made the family look unavailable when it was not. "SF Mono Square"
from the delphinus/sfmono-square tap is installed as an alternative.
Keep Nerd Font glyphs for module symbols, but replace the starship defaults
that are actual emoji rather than glyphs; gleam ships a star emoji and has
no Nerd Font glyph, so it gets a text label.
Disable docker_context: with a single context it printed "orbstack" in
every directory containing a compose file.
Leave the kubernetes module enabled even though the kubeconfig is now
empty, so it comes back on its own if a cluster is added rather than being
a module to remember to re-enable.
revert(prompt): back to powerlevel10k, and track .p10k.zsh
Switching to starship was not justified. The ~730ms that made startup slow
was oh-my-zsh's gpg-agent plugin, not the prompt; with that gone, p10k
starts in 0.17s against starship's 0.15s, so the prompt choice costs
essentially nothing either way. Everything the earlier commits fixed stays
fixed.
p10k also does things starship structurally cannot. It swaps a segment's
background colour on a dirty worktree, which is what makes the rainbow
style readable and which starship cannot express -- only foreground colours
are conditional there. It has transient prompt, already enabled in this
config. And
p10k configureis a wizard, rather than hand-editing TOML andguessing hex values, which is how the starship attempt went wrong.
Upstream is in maintenance mode: no new features and most bugs unfixed, but
the author is explicit that it is stable and not expected to break, which
is a reasonable bet for a zsh theme.
Track .p10k.zsh in the repo for the first time. It was 1734 lines living
only in $HOME, so a fresh machine would have come up with an unconfigured
prompt. It now sits in $ZDOTDIR alongside .zshrc.
The instant prompt block goes at the very top of .zshrc, above everything
including the history setup, since it replays a cached prompt and any
earlier output or input would corrupt the replay. No separate
INSTANT_PROMPT setting is needed in .zshrc because .p10k.zsh already sets
it to quiet, which is what keeps the rc.d banner from being reported as
unexpected console output.
Remove the starship config. The formula stays installed for now.
feat(p10k): rebase config on current template, add a mise segment
The local .p10k.zsh was generated in 2022 and had drifted 68 lines from
stock p10k-rainbow, of which only about ten were deliberate. Rather than
patch the old file, this regenerates from the current template and ports
those ten across, so four years of upstream changes come along too.
That drift was hiding two bugs. The old config listed
.tool-versionas adirectory anchor where stock has
.tool-versions-- a missing "s", so theanchor never matched. And it predates mise entirely, so
.mise.tomlwasabsent from the same list.
Ported: single-line prompt (no
newlineelement either side,PROMPT_ADD_NEWLINE=false), the vi_mode segment, blanked visual identifiers
on vcs/command_execution_time/time, Nerd Font battery stages,
TRANSIENT_PROMPT=always, INSTANT_PROMPT=quiet, unconditional ahead/behind
counts, and an empty DIR_CLASSES.
DIR_CLASSES is the subtle one: assigning an empty array is not the same as
leaving it unset. Unset, p10k falls back to default directory styling,
which includes a folder icon. That icon appeared in the prompt until the
empty assignment was restored.
Gained from upstream: newer tool detection in the kubecontext, aws, azure
and google_app_cred show-on-command lists (tofu, cdk, kubent, kubecolor,
cmctl, sparkctl), a wider VPN interface pattern, and the chezmoi_shell
segment, which will be useful shortly.
Add a mise prompt segment. p10k has none built in, as it predates mise and
only ships asdf. It parses the nearest .mise.toml or .tool-versions in pure
zsh rather than shelling out:
mise currentmeasures at ~15ms, which ismore than p10k's whole budget for a prompt and would be paid on every one.
Reading the file is effectively free and shows what the project pins, which
is the more useful information. The walk stops at /, and the global config
lives at ~/.config/mise/config.toml rather than ~/.mise.toml, so the
segment stays empty outside mise projects.
Comment out the fifteen version-manager segments mise supersedes (asdf,
pyenv, nodenv, nvm, rbenv and friends).
Track the mise config, which existed only in $HOME. It had no [settings] at
all; now it enables lockfile pinning, uv venv auto-activation (uv is the
most-used tool in this shell's history), idiomatic version files for node
and python, auto_install and env_cache.
Remove starship, which the previous commit stopped using.
fix(p10k): escape '#' in prompt_mise, and read idiomatic version files
prompt_mise used ${line%%#} to strip comments, which fails outright under
EXTENDED_GLOB: p10k enables that option, and with it a bare '#' is a pattern
operator, making '#' an invalid pattern. Every call raised
"prompt_mise:14: bad pattern: #*", and because the failure happened during
prompt rendering it corrupted p10k's expansion and left raw internals like
${(e)_p9k_t[7]} on screen instead of a prompt. Escaping it as # fixes both.
This was missed because the earlier test stubbed p10k and ran without
EXTENDED_GLOB set, so the pattern parsed fine there and only broke in a real
shell. The fixtures now set the option explicitly and cover .mise.toml,
.tool-versions, .nvmrc alone, both together, and no config at all.
Also read .nvmrc, .node-version and .python-version. mise honours these
because idiomatic_version_file_enable_tools is enabled for node and python,
so ignoring them under-reported what was active: guild pins node in .nvmrc
(where CI reads it) and uses mise.toml only for pnpm, so the segment showed
just pnpm while node was equally managed. Files at the same level are
merged, with the toml winning if it names the same tool twice.
fix(p10k): render mise segment with glyphs, cyan, one segment
Three problems, all found by capturing the real prompt through a pty rather
than trusting a stubbed
p10k segment.Icon names do not resolve. Passing -i NODE_ICON printed the literal string
"NODE_ICON": unlike p10k's built-in segments, which hand an icon name to
_p9k_prompt_segment for lookup,
p10k segment -iemits its argumentverbatim. Tool glyphs are now literal characters.
Those glyphs are written as \u escapes rather than pasted in. They live in
the Unicode private use area and an earlier attempt to embed them literally
produced an array with 24 keys and 24 empty values -- the fallback to the
plain tool name masked it, so the segment looked merely verbose rather than
broken. Escapes also keep this file ASCII.
Per-tool colours are not achievable and have been dropped. p10k resolves a
segment's style from its name, so every
p10k segmentcall insideprompt_mise picked up POWERLEVEL9K_MISE_* whatever -b said; the pty capture
showed both segments rendering with background 7 despite being passed 34.
The redundant calls also left a stray empty segment at the right edge, which
was the unexplained gap there. It is one segment again, and tools without a
glyph keep their name as a label.
Background moves from 7 to 6. Seven was inherited from asdf and reads as a
bland gray; cyan is distinct from the blue directory and green vcs segments
on either side.
Cap the tool list at four so a monorepo pinning many tools cannot run the
prompt off the edge.
refactor(p10k): read mise versions from $path, theme-aware colours
Replace the file-parsing mise segment with PATH scraping, adapted from
2KAbhishek/dots2k. mise activation prepends
.../mise/installs///bin, so the active versions are already
in the environment: no config parsing, and it reports what mise actually
resolved rather than what some file requests.
That also fixes a bug the old approach could not. A stray ~/.nvmrc and
~/.node-version, both left over from fnm and both containing "24", meant the
walk up the tree found them from any directory under $HOME, so every project
appeared to pin node. Global versions are now cached once at startup from
mise ls --offlineand hidden when they match, so the segment shows onlywhat a project actually overrides. ssh-bookshop is correctly empty again
while guild still shows node 24.15.0 and pnpm 11.0.9.
Per-tool colours now work, via -s to set a segment state: p10k resolves
style from segment name plus state, so POWERLEVEL9K_MISE__BACKGROUND
applies. The earlier attempt passed -b, which always loses to the
segment-level setting -- a pty capture showed both segments rendering with
background 7 despite being handed 34.
Those colours are palette indices 0-7 rather than 256-colour values. Indices
are what the terminal theme defines, so they follow srcery; fixed values
like 34 and 208 ignore the theme, which is why they looked foreign next to
the surrounding segments.
Glyphs are written as \u escape sequences. Embedding the literal characters
failed three times: they sit in the Unicode private use area and were
silently stripped to empty strings on write, leaving an array of 24 keys
with 24 empty values. The fallback to the tool name hid it, so the segment
looked merely verbose rather than broken.
Update MesloLGS Nerd Font from the 2022 v2 build to the 2025 v3 build. The
family name is unchanged, so ghostty needs no edit; the old files are
archived in ~/Downloads/meslo-v2-backup.
chore: remove fnm-era leftovers
Reclaims about 1.65GB and removes a hidden global node pin.
~/.nvmrc and ~/.node-version both contained "24" and were left over from
fnm. Because idiomatic_version_file_enable_tools is enabled for node, mise
honoured them, so every directory under $HOME inherited a node pin from
$HOME itself -- which is why ssh-bookshop appeared to pin node 24 despite
having no version file of its own. The mise prompt segment's hide-global
logic already masked the symptom; this removes the cause.
Deleted: ~/.local/share/fnm (1.0G of node builds), ~/.nvm (646M, from
before fnm), ~/.local/state/fnm_multishells, and ~/.pyenv (empty shims and
versions directories left behind when pyenv was uninstalled). Uninstall the
fnm formula and drop it from the Brewfile; it had been kept as a fallback
but mise has fully replaced it.
Also remove ~/.npmrc. Its only content was a scoped registry pointing at a
former employer's internal Nexus, which no longer resolves, so any install
of a package in that scope would have hung.
Small files are backed up to ~/Downloads/fnm-era-backup.
feat(zsh): add mise-prompt-debug as a shell function
Explains what the mise prompt segment is doing: the cached global versions,
each mise PATH entry raw and symlink-resolved, whether each is hidden or
shown and why, and what the segment finally renders.
It has to be a function rather than a script. The state it reports on lives
in shell variables (p9k_mise_global and the POWERLEVEL9K_MISE* settings),
which a separate process cannot see -- an earlier standalone script version
reported an empty global map and so claimed every tool would be SHOWN, when
the segment was in fact correctly hiding them.
refactor(p10k): split config into vendored template plus overrides
The p10k config was a single 1928-line file with about a dozen local edits
scattered through it. That makes upstream updates effectively impossible to
review: regenerating produces a diff where personal settings and template
churn are indistinguishable, and it is easy to silently lose a customisation
-- which already happened once with DIR_CLASSES, whose absence put a folder
icon back in the prompt.
Split into prompt/, following the layout in 2KAbhishek/dots2k:
init.zsh sources the other three in order; later files win
p10k.zsh stock p10k-rainbow vendored verbatim, byte-identical to
upstream and never hand-edited
overrides.zsh every local customisation, 175 lines
mise.zsh the mise segment, which p10k has no equivalent for
Taking a p10k update is now
cpover p10k.zsh, with any conflict showing upas an error rather than a silent loss.
my_git_formatter is redefined in overrides.zsh rather than patched into the
vendored copy. The only difference from stock is that ahead/behind counts
print unconditionally, where stock wraps them in a conditional with an
elifthat shows the remote branch name instead.BATTERY_STAGES needs an explicit unset first. The template declares it as an
array and this config wants a scalar string of glyphs; both are valid to
p10k, but zsh refuses to change a variable's type in place and the
assignment failed with "inconsistent type for assignment".
Verified the rendered prompt is unchanged: guild still shows the branch plus
pnpm 11.0.9 and node 24.15.0, transient prompt, instant prompt, single-line
layout and empty DIR_CLASSES all survive, and startup is ~0.17s.
chore(git): modern defaults, drop dead maintenance repo list
The [maintenance] section listed 30 repositories under ~/.zsh_plugins, a
path that no longer exists: the XDG restructure moved znap's clones to
~/.local/share/znap. Every entry was a dead path. No launchd job was
registered either, so
git maintenancehad not been running regardless.znap manages plugin updates itself, so the section is removed rather than
repointed.
Add settings that pay for themselves on any rebase-heavy workflow:
rerere.enabled + autoUpdate record a conflict resolution once and replay
it when the same conflict reappears
rebase.updateRefs move stacked branches along with a rebase
instead of leaving them on pre-rebase commits
rebase.autoStash stash and restore a dirty worktree rather
than refusing to start
fetch.prune + pruneTags drop local refs for deleted remote branches
branch.sort = -committerdate most recent branches first
column.ui = auto multi-column listings
diff.algorithm = histogram clearer diffs than Myers when blocks move
diff.renames = copies detect copies as well as renames
Upgrade merge.conflictstyle from diff3 to zdiff3, which keeps the common
ancestor context but omits lines both sides already agree on, giving smaller
conflict hunks. Requires git >= 2.35; this machine is on 2.55.
Fold the stray second [diff] section into one. Duplicate sections are legal
and git merges them, but having diff.tool separated from the rest was only
going to confuse.
GPG commit signing is left as it is. Switching to SSH signing
(gpg.format = ssh) would avoid the GPG keyring, but the current setup works
and the existing key is already trusted on GitHub.
chore(git): sign commits with SSH instead of GPG
GitHub already has the SSH signing key registered, so this drops the GPG
dependency for signing: no keyring, no gpg-agent, no pinentry prompt, and
the same key that already authenticates to GitHub now signs.
user.signingkey changes meaning with this: under gpg.format = ssh it is a
path to a public key rather than a GPG key id. The old GPG key
CCACDB1344B8B7EC0B4DD439655CBB5895152028 remains in this file's history if
it is ever needed for verifying old commits.
The [gpg] section name and
gpgsign = truestay as they are. Those namesare historical -- git kept them when it added other signing formats, so
gpgsign with format = ssh means SSH-signed, not GPG-signed.
Add an allowed_signers file mapping the identity to the key. Without it
local verification fails with "No principal matched" even for signatures git
has just created, because git cannot otherwise know which key may sign for
which identity. It contains only public keys, so it is safe to commit.
Verified end to end: a test commit signs and
git verify-commitreports agood signature.
Note the key is 3072-bit RSA from 2022. It works and GitHub accepts it, but
ed25519 would be the modern choice; switching means generating a key and
registering it as a signing key on GitHub.
gpg itself is left installed, since gpg-agent.conf and pinentry-mac are
still configured and may be used for things other than git.
refactor(zsh): source work config from a directory, not a symlink
The work submodule's dungeonbooks file was reached via a
../../../../work/dungeonbooks/zsh/.zsh/ relative symlink sitting inside
rc.d. That path had to be recomputed by hand when the zsh config moved to
XDG, and broke silently in between. It also meant a public repo contained a
symlink whose target only resolves if a private submodule happens to be
checked out.
.zshrc now globs $ZDOTDIR/work/*.zsh as a second pass after rc.d, so work
config can override the public config and simply does not load on a machine
without access to the private repo. The work repo has been flattened to
match: files live at its root rather than in a stow-shaped
dungeonbooks/zsh/.zsh/ tree.
Also drops rokt from that repo. It defined AWS SSO profiles, EKS kubeconfig
commands and go run wrappers for a job left two years ago, against clusters
already removed from the kubeconfig.
~/.config/zsh/work is currently a hand-made symlink to the submodule. The
chezmoi migration will replace it with a .chezmoiexternal.toml entry that
clones the private repo directly, at which point the submodule goes away.
chore: drop fish, sync alacritty config from disk
fish is not installed and its config dates from 2022; the "fish" match in
the Brewfile was a word inside atuin's description. Also removes a stray
vim/.claude/settings.local.json, which was never tracked and is not config.
alacritty.toml had silently diverged. Unlike every other package it was a
real file in ~/.config rather than a stow symlink, so edits made on disk in
October 2025 never reached this repo. The live copy uses [terminal.shell]
and [terminal], the schema alacritty has wanted since 0.14 (0.17 is
installed); the repo still had the pre-0.14 [shell]. Taking the live version
here, since migrating from the repo copy would have silently reverted the
config to a deprecated schema.
Found by diffing every tracked file against its deployed counterpart before
the chezmoi migration. It was the only genuine divergence.
feat: migrate from GNU stow to chezmoi
Replaces per-package symlink farms with a single source tree that chezmoi
renders into $HOME. The layout maps by filename rather than by position:
dot_config/zsh/dot_zshrc becomes ~/.config/zsh/.zshrc, and executable_
marks files that need +x on disk.
The practical difference is that a fresh machine is now two commands
(chezmoi init --apply, then brew bundle) instead of an undocumented sequence
of stow invocations plus hand-cloning znap.
Both git submodules are retired in favour of .chezmoiexternal.toml entries.
A submodule pins a SHA that must be committed here and needs
--recurse-submodules on clone; externals are fetched on apply and refresh on
their own schedule. znap is now an external too -- it had been cloned by
hand, so a fresh machine would have come up with no plugins and no prompt,
which is exactly the sort of undocumented step this migration removes.
Verified before switching by building the source tree in a scratch
directory and comparing every managed file's rendered content against the
live copy: all 58 were byte-identical. Every path still shows in
chezmoi diffbecause stow deployed symlinks and chezmoi writes regular files, sothe type changes even where content does not.
Post-apply checks: shell starts clean in ~0.17s, ZDOTDIR and HISTFILE
resolve, node comes from mise, the p10k prompt and mise segment load, atuin
holds Ctrl-R, and the private work aliases load from the external.
Add a README covering the bootstrap, the layout, the prompt split, the
externals and the edit-source-not-target workflow.
chore: remove stow
Nothing uses it now that chezmoi manages the tree.
feat(zsh): point config-edit aliases at chezmoi
The zshrc/zshalias/vimrc family opened the deployed file directly. Under
chezmoi that edits a rendered artifact: it works until the next apply
silently overwrites it. They now go through
chezmoi edit --apply, whichopens the source and writes the result out in one step.
Adds the usual chezmoi shortcuts (cm, cma, cmd, cme, cmu, cms, cmcd, cmadd)
plus zshprompt, gitconfig and brewfile, which had no alias before.
fix(aerospace): make the alt-enter ghostty check actually match
pgrep -x ghosttycould never succeed. macOS truncates a process name to 16characters, so Ghostty appears as "/Applications/Gh" and the exact-name match
always failed. The binding fell through to
open -a, which focuses theexisting window rather than opening a new one -- so alt-enter appeared to
stop working.
Match on the bundle path instead, including Contents/MacOS. The narrower
path matters: a looser
pgrep -f Ghostty.appmatches the full command lineincluding environment, and every process launched from a Ghostty terminal
inherits GHOSTTY_RESOURCES_DIR pointing at Contents/Resources. That made an
unrelated npm process match during testing, so Ghostty would have looked
like it was running long after being quit.
The AppleScript half needed no change: System Events still refers to the
process as "ghostty" regardless of the truncated name.
feat(zsh): fzf-tab previews, git workflow functions, aube
Adds rc.d/05-completion.zsh. fzf-tab was already loaded but had no zstyles,
so it replaced the completion menu with fzf and then showed nothing in the
preview pane, which is most of the point. Previews are now wired per command:
directories list through eza, files render through bat, and git dispatches on
the completion group so that tab-completing
git checkoutpreviews a branch'slog, a modified file's diff through delta, or a commit's contents, depending
on what is under the cursor. Also covers chezmoi (shows what applying would
change), mise, brew and kill.
Adds rc.d/04-functions.zsh:
review browse everything changed on this branch, diff in the
preview pane, enter opens the file
_git_run_on_files run a command over only the changed files, split by
"diff" (this branch vs its base) and "modified" (dirty
worktree) -- the two questions have different answers
lintjs/lintpy/... thin wrappers over the above per toolchain
search rg to fzf to $EDITOR, opening at the matched line
plz, xin sudo the last command; run something in another dir
The base branch is resolved from origin/HEAD rather than hardcoded to main,
falling back to asking the remote and then to conventional names, so repos on
master or develop work unchanged.
Adds aube to the mise global config, pinned to major version 1 rather than
"latest" so it cannot shift under a project mid-work.
Startup goes from ~0.17s to ~0.20s, which is the completion zstyles being
evaluated at load.